···11# KUtils
2233Utilities for TypeScript and Svelte
44+55+## Virtual grid
66+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).
77+88+[Virtual grid + KSelection usage example](src/routes/+page.svelte)
99+1010+Features:
1111+- Renders rows as HTML elements directly instead of using a JS framework (for better scroll performance)
1212+- Rows can be dynamically loaded
1313+- Columns can have fixed and percentage widths
1414+1515+## KSelection
1616+Row-based selection manager, with both a JS and Svelte implementation..
1717+1818+[Virtual grid + KSelection usage example](src/routes/+page.svelte)
1919+2020+Features:
2121+- Multi-selection
2222+- 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.
2323+- Stays intact when rows are added or removed
2424+- Easy to integrate with the virtual grid
2525+2626+## Auto snapshot
2727+2828+Automatically snapshot Svelte form values for `input`, `textarea` and `select` elements.
2929+3030+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.
3131+3232+Usage:
3333+```svelte
3434+<script>
3535+ import { auto_snapshot } from 'kutils'
3636+ const snapshotter = auto_snapshot()
3737+ export const snapshot = snapshotter
3838+</script>
3939+<div use:snapshotter.container>
4040+ <input type="text" name="first_name" />
4141+</div>
4242+```
4343+4444+## Shortcut checking
4545+4646+```js
4747+import { check_shortcut, check_modifiers } from 'kutils'
4848+check_shortcut(e, 'A') // 'A' with no modifiers pressed
4949+check_shortcut(e, 'A', 'shift', 'alt', 'cmdOrCtrl')
5050+check_modifiers(e) // Check that no modifiers are pressed (useful for mouse events)
5151+check_modifiers(e, 'shift', 'alt', 'cmdOrCtrl')
5252+```
+1-1
src/lib/index.ts
···11-export { check_shortcut, check_mouse_shortcut, check_modifiers, is_mac } from './shortcuts.ts'
11+export { check_shortcut, check_modifiers, is_mac } from './shortcuts.ts'
22export { auto_snapshot } from './auto-snapshot.ts'
33export { KSelection } from './selection-js.ts'
+13-13
src/lib/selection-js.ts
···11-import { check_mouse_shortcut, check_shortcut } from './shortcuts.ts'
11+import { check_modifiers, check_shortcut } from './shortcuts.ts'
2233export type SelectionOptions<T> = {
44 scroll_to: (target: { item: T; index: number }) => void
···262262263263 mouse_down_select(e: MouseEvent, index: number) {
264264 const is_selected = this.items.has(this.all[index])
265265- if (check_mouse_shortcut(e) && !is_selected) {
265265+ if (check_modifiers(e) && !is_selected) {
266266 this.clear()
267267 this.add_index_unchecked(index)
268268- } else if (check_mouse_shortcut(e, { cmd_or_ctrl: true }) && !is_selected) {
268268+ } else if (check_modifiers(e, 'cmdOrCtrl') && !is_selected) {
269269 this.add_index_unchecked(index)
270270- } else if (check_mouse_shortcut(e, { shift: true })) {
270270+ } else if (check_modifiers(e, 'shift')) {
271271 this.shift_select_to(index)
272272 }
273273 }
···304304 }
305305 handle_click(e: MouseEvent, index: number) {
306306 if (this.possible_row_click && e.button === 0) {
307307- if (check_mouse_shortcut(e)) {
307307+ if (check_modifiers(e)) {
308308 this.clear()
309309 this.add_index_unchecked(index)
310310- } else if (check_mouse_shortcut(e, { cmd_or_ctrl: true })) {
310310+ } else if (check_modifiers(e, 'cmdOrCtrl')) {
311311 this.#toggle(index)
312312 }
313313 }
···316316 handle_keydown(e: KeyboardEvent) {
317317 if (check_shortcut(e, 'Escape')) {
318318 this.clear()
319319- } else if (check_shortcut(e, 'A', { cmd_or_ctrl: true })) {
319319+ } else if (check_shortcut(e, 'A', 'cmdOrCtrl')) {
320320 if (this.all.length > 1) {
321321 this.#add_index_range_in_shift_mode(0, this.all.length - 1)
322322 }
323323 } else if (check_shortcut(e, 'ArrowUp')) {
324324 this.go_backward()
325325 if (this.last_added) this.scroll_to({ ...this.last_added })
326326- } else if (check_shortcut(e, 'ArrowUp', { shift: true })) {
326326+ } else if (check_shortcut(e, 'ArrowUp', 'shift')) {
327327 this.shift_select_backward()
328328 if (this.last_added) this.scroll_to({ ...this.last_added })
329329- } else if (check_shortcut(e, 'ArrowUp', { alt: true })) {
329329+ } else if (check_shortcut(e, 'ArrowUp', 'alt')) {
330330 this.clear()
331331 if (this.all.length > 1) {
332332 this.add_index_unchecked(0)
333333 if (this.last_added) this.scroll_to({ ...this.last_added })
334334 }
335335- } else if (check_shortcut(e, 'ArrowUp', { shift: true, alt: true })) {
335335+ } else if (check_shortcut(e, 'ArrowUp', 'shift', 'alt')) {
336336 this.shift_select_to(0)
337337 if (this.last_added) this.scroll_to({ ...this.last_added })
338338 } else if (check_shortcut(e, 'ArrowDown')) {
339339 this.go_forward()
340340 if (this.last_added) this.scroll_to({ ...this.last_added })
341341- } else if (check_shortcut(e, 'ArrowDown', { shift: true })) {
341341+ } else if (check_shortcut(e, 'ArrowDown', 'shift')) {
342342 this.shift_select_forward()
343343 if (this.last_added) this.scroll_to({ ...this.last_added })
344344- } else if (check_shortcut(e, 'ArrowDown', { alt: true })) {
344344+ } else if (check_shortcut(e, 'ArrowDown', 'alt')) {
345345 this.clear()
346346 if (this.all.length > 1) {
347347 this.add_index_unchecked(this.all.length - 1)
348348 if (this.last_added) this.scroll_to({ ...this.last_added })
349349 }
350350- } else if (check_shortcut(e, 'ArrowDown', { shift: true, alt: true })) {
350350+ } else if (check_shortcut(e, 'ArrowDown', 'shift', 'alt')) {
351351 this.shift_select_to(this.all.length - 1)
352352 if (this.last_added) this.scroll_to({ ...this.last_added })
353353 } else {
+20-27
src/lib/shortcuts.ts
···22 return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)
33}
4455-type ShortcutOptions = {
66- shift?: boolean
77- alt?: boolean
88- cmd_or_ctrl?: boolean
99-}
55+type Modifier = 'shift' | 'alt' | 'cmdOrCtrl'
1061111-export function check_modifiers(e: KeyboardEvent | MouseEvent, options: ShortcutOptions = {}) {
1212- const target = {
1313- shift: options.shift || false,
1414- alt: options.alt || false,
1515- ctrl: (!is_mac() && options.cmd_or_ctrl) || false,
1616- meta: (is_mac() && options.cmd_or_ctrl) || false,
1717- }
1818-1919- const pressed = {
2020- shift: !!e.shiftKey,
2121- alt: !!e.altKey,
2222- ctrl: !!e.ctrlKey,
2323- meta: !!e.metaKey,
77+/** 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. */
88+export function check_modifiers(e: KeyboardEvent | MouseEvent, ...modifiers: Modifier[]) {
99+ let shift = false
1010+ let alt = false
1111+ let ctrl = false
1212+ let meta = false
1313+ for (const modifier of modifiers) {
1414+ if (modifier === 'shift') {
1515+ shift = true
1616+ } else if (modifier === 'alt') {
1717+ alt = true
1818+ } else if (modifier === 'cmdOrCtrl') {
1919+ ctrl = !is_mac()
2020+ meta = !ctrl
2121+ }
2422 }
25232624 return (
2727- pressed.shift === target.shift &&
2828- pressed.alt === target.alt &&
2929- pressed.ctrl === target.ctrl &&
3030- pressed.meta === target.meta
2525+ !!e.shiftKey === shift && !!e.altKey === alt && !!e.ctrlKey === ctrl && !!e.metaKey === meta
3126 )
3227}
33283434-export function check_shortcut(e: KeyboardEvent, key: string, options: ShortcutOptions = {}) {
2929+/** 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. */
3030+export function check_shortcut(e: KeyboardEvent, key: string, ...modifiers: Modifier[]) {
3531 if (e.key.toUpperCase() !== key.toUpperCase()) return false
3636- return check_modifiers(e, options)
3737-}
3838-export function check_mouse_shortcut(e: MouseEvent, options: ShortcutOptions = {}) {
3939- return check_modifiers(e, options)
3232+ return check_modifiers(e, ...modifiers)
4033}
+8
src/lib/virtual-grid.ts
···2626}
27272828/**
2929+ * ## Virtual grid
3030+ * 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).
3131+ *
3232+ * Features:
3333+ * - Renders rows as HTML elements directly instead of using a JS framework (for better scroll performance)
3434+ * - Rows can be dynamically loaded
3535+ * - Columns can have fixed and percentage widths
3636+ *
2937 * Note that parentElement is not reactive.
3038 * Do not add other elements into the row elements. Things would break because cells are referenced by indexing into the row's children.
3139 */