[READ-ONLY] Mirror of https://github.com/probablykasper/k5kit. Utilities for TypeScript and Svelte
0

Configure Feed

Select the types of activity you want to include in your feed.

Rewrite shortcut checker

Kasper (Jun 27, 2025, 1:39 AM +0200) 6b6a64b2 1462dead

+91 -41
+49
README.md
··· 1 1 # KUtils 2 2 3 3 Utilities for TypeScript and Svelte 4 + 5 + ## Virtual grid 6 + High-performance virtual grid. Renders rows as HTML elements directly for better scroll performance instead of relying on a JS framework (which means custom markup needs to be created with JS). 7 + 8 + [Virtual grid + KSelection usage example](src/routes/+page.svelte) 9 + 10 + Features: 11 + - Renders rows as HTML elements directly instead of using a JS framework (for better scroll performance) 12 + - Rows can be dynamically loaded 13 + - Columns can have fixed and percentage widths 14 + 15 + ## KSelection 16 + Row-based selection manager, with both a JS and Svelte implementation.. 17 + 18 + [Virtual grid + KSelection usage example](src/routes/+page.svelte) 19 + 20 + Features: 21 + - Multi-selection 22 + - Full OS-like mouse and keyboard support. Shift selection (with proper anchoring), Cmd/Ctrl selection, arrow keys, Alt/Option+ArrowKeys, Cmd/Ctrl+A, Esc to deselect, etc. 23 + - Stays intact when rows are added or removed 24 + - Easy to integrate with the virtual grid 25 + 26 + ## Auto snapshot 27 + 28 + Automatically snapshot Svelte form values for `input`, `textarea` and `select` elements. 29 + 30 + The `name` attribute is used as the key. If you need to use a different key than the `name` attribute, you can alternatively specify it with the `data-snapsho` attribute. 31 + 32 + Usage: 33 + ```svelte 34 + <script> 35 + import { auto_snapshot } from 'kutils' 36 + const snapshotter = auto_snapshot() 37 + export const snapshot = snapshotter 38 + </script> 39 + <div use:snapshotter.container> 40 + <input type="text" name="first_name" /> 41 + </div> 42 + ``` 43 + 44 + ## Shortcut checking 45 + 46 + ```js 47 + import { check_shortcut, check_modifiers } from 'kutils' 48 + check_shortcut(e, 'A') // 'A' with no modifiers pressed 49 + check_shortcut(e, 'A', 'shift', 'alt', 'cmdOrCtrl') 50 + check_modifiers(e) // Check that no modifiers are pressed (useful for mouse events) 51 + check_modifiers(e, 'shift', 'alt', 'cmdOrCtrl') 52 + ```
+1 -1
src/lib/index.ts
··· 1 - export { check_shortcut, check_mouse_shortcut, check_modifiers, is_mac } from './shortcuts.ts' 1 + export { check_shortcut, check_modifiers, is_mac } from './shortcuts.ts' 2 2 export { auto_snapshot } from './auto-snapshot.ts' 3 3 export { KSelection } from './selection-js.ts'
+13 -13
src/lib/selection-js.ts
··· 1 - import { check_mouse_shortcut, check_shortcut } from './shortcuts.ts' 1 + import { check_modifiers, check_shortcut } from './shortcuts.ts' 2 2 3 3 export type SelectionOptions<T> = { 4 4 scroll_to: (target: { item: T; index: number }) => void ··· 262 262 263 263 mouse_down_select(e: MouseEvent, index: number) { 264 264 const is_selected = this.items.has(this.all[index]) 265 - if (check_mouse_shortcut(e) && !is_selected) { 265 + if (check_modifiers(e) && !is_selected) { 266 266 this.clear() 267 267 this.add_index_unchecked(index) 268 - } else if (check_mouse_shortcut(e, { cmd_or_ctrl: true }) && !is_selected) { 268 + } else if (check_modifiers(e, 'cmdOrCtrl') && !is_selected) { 269 269 this.add_index_unchecked(index) 270 - } else if (check_mouse_shortcut(e, { shift: true })) { 270 + } else if (check_modifiers(e, 'shift')) { 271 271 this.shift_select_to(index) 272 272 } 273 273 } ··· 304 304 } 305 305 handle_click(e: MouseEvent, index: number) { 306 306 if (this.possible_row_click && e.button === 0) { 307 - if (check_mouse_shortcut(e)) { 307 + if (check_modifiers(e)) { 308 308 this.clear() 309 309 this.add_index_unchecked(index) 310 - } else if (check_mouse_shortcut(e, { cmd_or_ctrl: true })) { 310 + } else if (check_modifiers(e, 'cmdOrCtrl')) { 311 311 this.#toggle(index) 312 312 } 313 313 } ··· 316 316 handle_keydown(e: KeyboardEvent) { 317 317 if (check_shortcut(e, 'Escape')) { 318 318 this.clear() 319 - } else if (check_shortcut(e, 'A', { cmd_or_ctrl: true })) { 319 + } else if (check_shortcut(e, 'A', 'cmdOrCtrl')) { 320 320 if (this.all.length > 1) { 321 321 this.#add_index_range_in_shift_mode(0, this.all.length - 1) 322 322 } 323 323 } else if (check_shortcut(e, 'ArrowUp')) { 324 324 this.go_backward() 325 325 if (this.last_added) this.scroll_to({ ...this.last_added }) 326 - } else if (check_shortcut(e, 'ArrowUp', { shift: true })) { 326 + } else if (check_shortcut(e, 'ArrowUp', 'shift')) { 327 327 this.shift_select_backward() 328 328 if (this.last_added) this.scroll_to({ ...this.last_added }) 329 - } else if (check_shortcut(e, 'ArrowUp', { alt: true })) { 329 + } else if (check_shortcut(e, 'ArrowUp', 'alt')) { 330 330 this.clear() 331 331 if (this.all.length > 1) { 332 332 this.add_index_unchecked(0) 333 333 if (this.last_added) this.scroll_to({ ...this.last_added }) 334 334 } 335 - } else if (check_shortcut(e, 'ArrowUp', { shift: true, alt: true })) { 335 + } else if (check_shortcut(e, 'ArrowUp', 'shift', 'alt')) { 336 336 this.shift_select_to(0) 337 337 if (this.last_added) this.scroll_to({ ...this.last_added }) 338 338 } else if (check_shortcut(e, 'ArrowDown')) { 339 339 this.go_forward() 340 340 if (this.last_added) this.scroll_to({ ...this.last_added }) 341 - } else if (check_shortcut(e, 'ArrowDown', { shift: true })) { 341 + } else if (check_shortcut(e, 'ArrowDown', 'shift')) { 342 342 this.shift_select_forward() 343 343 if (this.last_added) this.scroll_to({ ...this.last_added }) 344 - } else if (check_shortcut(e, 'ArrowDown', { alt: true })) { 344 + } else if (check_shortcut(e, 'ArrowDown', 'alt')) { 345 345 this.clear() 346 346 if (this.all.length > 1) { 347 347 this.add_index_unchecked(this.all.length - 1) 348 348 if (this.last_added) this.scroll_to({ ...this.last_added }) 349 349 } 350 - } else if (check_shortcut(e, 'ArrowDown', { shift: true, alt: true })) { 350 + } else if (check_shortcut(e, 'ArrowDown', 'shift', 'alt')) { 351 351 this.shift_select_to(this.all.length - 1) 352 352 if (this.last_added) this.scroll_to({ ...this.last_added }) 353 353 } else {
+20 -27
src/lib/shortcuts.ts
··· 2 2 return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) 3 3 } 4 4 5 - type ShortcutOptions = { 6 - shift?: boolean 7 - alt?: boolean 8 - cmd_or_ctrl?: boolean 9 - } 5 + type Modifier = 'shift' | 'alt' | 'cmdOrCtrl' 10 6 11 - export function check_modifiers(e: KeyboardEvent | MouseEvent, options: ShortcutOptions = {}) { 12 - const target = { 13 - shift: options.shift || false, 14 - alt: options.alt || false, 15 - ctrl: (!is_mac() && options.cmd_or_ctrl) || false, 16 - meta: (is_mac() && options.cmd_or_ctrl) || false, 17 - } 18 - 19 - const pressed = { 20 - shift: !!e.shiftKey, 21 - alt: !!e.altKey, 22 - ctrl: !!e.ctrlKey, 23 - meta: !!e.metaKey, 7 + /** Check the pressed modifiers for an event. This is strict, so if you pass no modifiers, it will return true if the event has no modifiers. */ 8 + export function check_modifiers(e: KeyboardEvent | MouseEvent, ...modifiers: Modifier[]) { 9 + let shift = false 10 + let alt = false 11 + let ctrl = false 12 + let meta = false 13 + for (const modifier of modifiers) { 14 + if (modifier === 'shift') { 15 + shift = true 16 + } else if (modifier === 'alt') { 17 + alt = true 18 + } else if (modifier === 'cmdOrCtrl') { 19 + ctrl = !is_mac() 20 + meta = !ctrl 21 + } 24 22 } 25 23 26 24 return ( 27 - pressed.shift === target.shift && 28 - pressed.alt === target.alt && 29 - pressed.ctrl === target.ctrl && 30 - pressed.meta === target.meta 25 + !!e.shiftKey === shift && !!e.altKey === alt && !!e.ctrlKey === ctrl && !!e.metaKey === meta 31 26 ) 32 27 } 33 28 34 - export function check_shortcut(e: KeyboardEvent, key: string, options: ShortcutOptions = {}) { 29 + /** Check the pressed key and modifiers for a keyboard event. This is strict, so if you pass no modifiers, it will return true if the event has no modifiers. */ 30 + export function check_shortcut(e: KeyboardEvent, key: string, ...modifiers: Modifier[]) { 35 31 if (e.key.toUpperCase() !== key.toUpperCase()) return false 36 - return check_modifiers(e, options) 37 - } 38 - export function check_mouse_shortcut(e: MouseEvent, options: ShortcutOptions = {}) { 39 - return check_modifiers(e, options) 32 + return check_modifiers(e, ...modifiers) 40 33 }
+8
src/lib/virtual-grid.ts
··· 26 26 } 27 27 28 28 /** 29 + * ## Virtual grid 30 + * High-performance virtual grid. Renders rows as HTML elements directly for better scroll performance instead of relying on a JS framework (which means custom markup needs to be created with JS). 31 + * 32 + * Features: 33 + * - Renders rows as HTML elements directly instead of using a JS framework (for better scroll performance) 34 + * - Rows can be dynamically loaded 35 + * - Columns can have fixed and percentage widths 36 + * 29 37 * Note that parentElement is not reactive. 30 38 * Do not add other elements into the row elements. Things would break because cells are referenced by indexing into the row's children. 31 39 */