@stonecrop/utilities 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,473 +0,0 @@
1
- import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
2
- import { useFocusWithin } from '@vueuse/core';
3
- import { useElementVisibility } from './visibility';
4
- // helper functions
5
- const isVisible = (element) => {
6
- let isVisible = useElementVisibility(element).value;
7
- isVisible = isVisible && element.offsetHeight > 0;
8
- return isVisible;
9
- };
10
- const isFocusable = (element) => {
11
- return element.tabIndex >= 0;
12
- };
13
- // navigation functions
14
- const getUpCell = (event) => {
15
- const $target = event.target;
16
- return _getUpCell($target);
17
- };
18
- const _getUpCell = (element) => {
19
- let $upCell;
20
- if (element instanceof HTMLTableCellElement) {
21
- const $prevRow = element.parentElement?.previousElementSibling;
22
- if ($prevRow) {
23
- const $prevRowCells = Array.from($prevRow.children);
24
- const $prevCell = $prevRowCells[element.cellIndex];
25
- if ($prevCell) {
26
- $upCell = $prevCell;
27
- }
28
- }
29
- }
30
- else if (element instanceof HTMLTableRowElement) {
31
- const $prevRow = element.previousElementSibling;
32
- if ($prevRow) {
33
- $upCell = $prevRow;
34
- }
35
- }
36
- else {
37
- // handle other contexts
38
- }
39
- if ($upCell && (!isFocusable($upCell) || !isVisible($upCell))) {
40
- return _getUpCell($upCell);
41
- }
42
- return $upCell;
43
- };
44
- const getTopCell = (event) => {
45
- const $target = event.target;
46
- let $topCell;
47
- if ($target instanceof HTMLTableCellElement) {
48
- const $table = $target.parentElement?.parentElement;
49
- if ($table) {
50
- const $firstRow = $table.firstElementChild;
51
- const $navCell = $firstRow?.children[$target.cellIndex];
52
- if ($navCell) {
53
- $topCell = $navCell;
54
- }
55
- }
56
- }
57
- else if ($target instanceof HTMLTableRowElement) {
58
- const $table = $target.parentElement;
59
- if ($table) {
60
- const $firstRow = $table.firstElementChild;
61
- if ($firstRow) {
62
- $topCell = $firstRow;
63
- }
64
- }
65
- }
66
- else {
67
- // handle other contexts
68
- }
69
- if ($topCell && (!isFocusable($topCell) || !isVisible($topCell))) {
70
- return _getDownCell($topCell);
71
- }
72
- return $topCell;
73
- };
74
- const getDownCell = (event) => {
75
- const $target = event.target;
76
- return _getDownCell($target);
77
- };
78
- const _getDownCell = (element) => {
79
- let $downCell;
80
- if (element instanceof HTMLTableCellElement) {
81
- const $nextRow = element.parentElement?.nextElementSibling;
82
- if ($nextRow) {
83
- const $nextRowCells = Array.from($nextRow.children);
84
- const $nextCell = $nextRowCells[element.cellIndex];
85
- if ($nextCell) {
86
- $downCell = $nextCell;
87
- }
88
- }
89
- }
90
- else if (element instanceof HTMLTableRowElement) {
91
- const $nextRow = element.nextElementSibling;
92
- if ($nextRow) {
93
- $downCell = $nextRow;
94
- }
95
- }
96
- else {
97
- // handle other contexts
98
- }
99
- if ($downCell && (!isFocusable($downCell) || !isVisible($downCell))) {
100
- return _getDownCell($downCell);
101
- }
102
- return $downCell;
103
- };
104
- const getBottomCell = (event) => {
105
- const $target = event.target;
106
- let $bottomCell;
107
- if ($target instanceof HTMLTableCellElement) {
108
- const $table = $target.parentElement?.parentElement;
109
- if ($table) {
110
- const $lastRow = $table.lastElementChild;
111
- const $navCell = $lastRow?.children[$target.cellIndex];
112
- if ($navCell) {
113
- $bottomCell = $navCell;
114
- }
115
- }
116
- }
117
- else if ($target instanceof HTMLTableRowElement) {
118
- const $table = $target.parentElement;
119
- if ($table) {
120
- const $lastRow = $table.lastElementChild;
121
- if ($lastRow) {
122
- $bottomCell = $lastRow;
123
- }
124
- }
125
- }
126
- else {
127
- // handle other contexts
128
- }
129
- if ($bottomCell && (!isFocusable($bottomCell) || !isVisible($bottomCell))) {
130
- return _getUpCell($bottomCell);
131
- }
132
- return $bottomCell;
133
- };
134
- const getPrevCell = (event) => {
135
- const $target = event.target;
136
- return _getPrevCell($target);
137
- };
138
- const _getPrevCell = (element) => {
139
- let $prevCell;
140
- if (element.previousElementSibling) {
141
- $prevCell = element.previousElementSibling;
142
- }
143
- else {
144
- const $prevRow = element.parentElement?.previousElementSibling;
145
- $prevCell = $prevRow?.lastElementChild;
146
- }
147
- if ($prevCell && (!isFocusable($prevCell) || !isVisible($prevCell))) {
148
- return _getPrevCell($prevCell);
149
- }
150
- return $prevCell;
151
- };
152
- const getNextCell = (event) => {
153
- const $target = event.target;
154
- return _getNextCell($target);
155
- };
156
- const _getNextCell = (element) => {
157
- let $nextCell;
158
- if (element.nextElementSibling) {
159
- $nextCell = element.nextElementSibling;
160
- }
161
- else {
162
- const $nextRow = element.parentElement?.nextElementSibling;
163
- $nextCell = $nextRow?.firstElementChild;
164
- }
165
- if ($nextCell && (!isFocusable($nextCell) || !isVisible($nextCell))) {
166
- return _getNextCell($nextCell);
167
- }
168
- return $nextCell;
169
- };
170
- const getFirstCell = (event) => {
171
- const $target = event.target;
172
- const $parent = $target.parentElement;
173
- const $firstCell = $parent?.firstElementChild;
174
- if ($firstCell && (!isFocusable($firstCell) || !isVisible($firstCell))) {
175
- return _getNextCell($firstCell);
176
- }
177
- return $firstCell;
178
- };
179
- const getLastCell = (event) => {
180
- const $target = event.target;
181
- const $parent = $target.parentElement;
182
- const $lastCell = $parent?.lastElementChild;
183
- if ($lastCell && (!isFocusable($lastCell) || !isVisible($lastCell))) {
184
- return _getPrevCell($lastCell);
185
- }
186
- return $lastCell;
187
- };
188
- const modifierKeys = ['alt', 'control', 'shift', 'meta'];
189
- const eventKeyMap = {
190
- ArrowUp: 'up',
191
- ArrowDown: 'down',
192
- ArrowLeft: 'left',
193
- ArrowRight: 'right',
194
- };
195
- /**
196
- * Default keypress handlers for keyboard navigation
197
- * @public
198
- */
199
- export const defaultKeypressHandlers = {
200
- 'keydown.up': (event) => {
201
- const $upCell = getUpCell(event);
202
- if ($upCell) {
203
- event.preventDefault();
204
- event.stopPropagation();
205
- $upCell.focus();
206
- }
207
- },
208
- 'keydown.down': (event) => {
209
- const $downCell = getDownCell(event);
210
- if ($downCell) {
211
- event.preventDefault();
212
- event.stopPropagation();
213
- $downCell.focus();
214
- }
215
- },
216
- 'keydown.left': (event) => {
217
- const $prevCell = getPrevCell(event);
218
- // prevent default edit-cell behaviour on first cell
219
- event.preventDefault();
220
- event.stopPropagation();
221
- if ($prevCell) {
222
- $prevCell.focus();
223
- }
224
- },
225
- 'keydown.right': (event) => {
226
- const $nextCell = getNextCell(event);
227
- // prevent default edit-cell behaviour on last cell
228
- event.preventDefault();
229
- event.stopPropagation();
230
- if ($nextCell) {
231
- $nextCell.focus();
232
- }
233
- },
234
- 'keydown.control.up': (event) => {
235
- const $topCell = getTopCell(event);
236
- if ($topCell) {
237
- event.preventDefault();
238
- event.stopPropagation();
239
- $topCell.focus();
240
- }
241
- },
242
- 'keydown.control.down': (event) => {
243
- const $bottomCell = getBottomCell(event);
244
- if ($bottomCell) {
245
- event.preventDefault();
246
- event.stopPropagation();
247
- $bottomCell.focus();
248
- }
249
- },
250
- 'keydown.control.left': (event) => {
251
- const $firstCell = getFirstCell(event);
252
- if ($firstCell) {
253
- event.preventDefault();
254
- event.stopPropagation();
255
- $firstCell.focus();
256
- }
257
- },
258
- 'keydown.control.right': (event) => {
259
- const $lastCell = getLastCell(event);
260
- if ($lastCell) {
261
- event.preventDefault();
262
- event.stopPropagation();
263
- $lastCell.focus();
264
- }
265
- },
266
- 'keydown.end': (event) => {
267
- const $lastCell = getLastCell(event);
268
- if ($lastCell) {
269
- event.preventDefault();
270
- event.stopPropagation();
271
- $lastCell.focus();
272
- }
273
- },
274
- 'keydown.enter': (event) => {
275
- const $target = event.target;
276
- if ($target instanceof HTMLTableCellElement) {
277
- event.preventDefault();
278
- event.stopPropagation();
279
- const $downCell = getDownCell(event);
280
- if ($downCell) {
281
- $downCell.focus();
282
- }
283
- }
284
- else {
285
- // handle other contexts
286
- }
287
- },
288
- 'keydown.shift.enter': (event) => {
289
- const $target = event.target;
290
- if ($target instanceof HTMLTableCellElement) {
291
- event.preventDefault();
292
- event.stopPropagation();
293
- const $upCell = getUpCell(event);
294
- if ($upCell) {
295
- $upCell.focus();
296
- }
297
- }
298
- else {
299
- // handle other contexts
300
- }
301
- },
302
- 'keydown.home': (event) => {
303
- const $firstCell = getFirstCell(event);
304
- if ($firstCell) {
305
- event.preventDefault();
306
- event.stopPropagation();
307
- $firstCell.focus();
308
- }
309
- },
310
- 'keydown.tab': (event) => {
311
- const $nextCell = getNextCell(event);
312
- if ($nextCell) {
313
- event.preventDefault();
314
- event.stopPropagation();
315
- $nextCell.focus();
316
- }
317
- },
318
- 'keydown.shift.tab': (event) => {
319
- const $prevCell = getPrevCell(event);
320
- if ($prevCell) {
321
- event.preventDefault();
322
- event.stopPropagation();
323
- $prevCell.focus();
324
- }
325
- },
326
- };
327
- /**
328
- * Keyboard navigation composable
329
- * @param options - Keyboard navigation options
330
- * @public
331
- */
332
- export function useKeyboardNav(options) {
333
- const getParentElement = (option) => {
334
- let $parent = null;
335
- if (option.parent) {
336
- if (typeof option.parent === 'string') {
337
- $parent = document.querySelector(option.parent);
338
- }
339
- else if (option.parent instanceof HTMLElement) {
340
- $parent = option.parent;
341
- }
342
- else {
343
- $parent = option.parent.value;
344
- }
345
- }
346
- return $parent;
347
- };
348
- const getSelectorsFromOption = (option) => {
349
- // assumes that option.selectors is provided
350
- const $parent = getParentElement(option);
351
- let selectors = [];
352
- if (typeof option.selectors === 'string') {
353
- selectors = $parent
354
- ? Array.from($parent.querySelectorAll(option.selectors))
355
- : Array.from(document.querySelectorAll(option.selectors));
356
- }
357
- else if (Array.isArray(option.selectors)) {
358
- for (const element of option.selectors) {
359
- if (element instanceof HTMLElement) {
360
- selectors.push(element);
361
- }
362
- else {
363
- selectors.push(element.$el);
364
- }
365
- }
366
- }
367
- else if (option.selectors instanceof HTMLElement) {
368
- selectors.push(option.selectors);
369
- }
370
- else if (option.selectors?.value) {
371
- if (Array.isArray(option.selectors.value)) {
372
- for (const element of option.selectors.value) {
373
- if (element instanceof HTMLElement) {
374
- selectors.push(element);
375
- }
376
- else {
377
- selectors.push(element.$el);
378
- }
379
- }
380
- }
381
- else {
382
- selectors.push(option.selectors.value);
383
- }
384
- }
385
- return selectors;
386
- };
387
- const getSelectors = (option) => {
388
- const $parent = getParentElement(option);
389
- let selectors = [];
390
- if (option.selectors) {
391
- selectors = getSelectorsFromOption(option);
392
- }
393
- else if ($parent) {
394
- // TODO: what should happen if no parent or selectors are provided?
395
- const $children = Array.from($parent.children);
396
- selectors = $children.filter(selector => {
397
- // ignore elements not in the tab order or are not visible
398
- return isFocusable(selector) && isVisible(selector);
399
- });
400
- }
401
- return selectors;
402
- };
403
- const getEventListener = (option) => {
404
- return (event) => {
405
- const activeKey = eventKeyMap[event.key] || event.key.toLowerCase();
406
- if (modifierKeys.includes(activeKey))
407
- return; // ignore modifier key presses
408
- const handlers = option.handlers || defaultKeypressHandlers;
409
- for (const key of Object.keys(handlers)) {
410
- const [eventType, ...keys] = key.split('.');
411
- if (eventType !== 'keydown') {
412
- continue;
413
- }
414
- if (keys.includes(activeKey)) {
415
- const listener = handlers[key];
416
- // check if the handler has modifiers, and if the modifier is active;
417
- // this is to ensure exact key-press matches
418
- const hasModifier = keys.filter(key => modifierKeys.includes(key));
419
- const isModifierActive = modifierKeys.some(key => {
420
- const modifierKey = key.charAt(0).toUpperCase() + key.slice(1);
421
- return event.getModifierState(modifierKey);
422
- });
423
- if (hasModifier.length > 0) {
424
- if (isModifierActive) {
425
- for (const modifier of modifierKeys) {
426
- if (keys.includes(modifier)) {
427
- // docs: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState
428
- const modifierKey = modifier.charAt(0).toUpperCase() + modifier.slice(1);
429
- if (event.getModifierState(modifierKey)) {
430
- listener(event);
431
- }
432
- }
433
- }
434
- }
435
- }
436
- else {
437
- if (!isModifierActive) {
438
- listener(event);
439
- }
440
- }
441
- }
442
- }
443
- };
444
- };
445
- const watchStopHandlers = [];
446
- onMounted(() => {
447
- for (const option of options) {
448
- const $parent = getParentElement(option);
449
- const selectors = getSelectors(option);
450
- const listener = getEventListener(option);
451
- const listenerElements = $parent
452
- ? [$parent] // watch for focus recursively within the parent element
453
- : selectors; // watch for focus on each selector element TODO: too much JS?
454
- for (const element of listenerElements) {
455
- const { focused } = useFocusWithin(ref(element));
456
- const stopHandler = watch(focused, value => {
457
- if (value) {
458
- element.addEventListener('keydown', listener);
459
- }
460
- else {
461
- element.removeEventListener('keydown', listener);
462
- }
463
- });
464
- watchStopHandlers.push(stopHandler);
465
- }
466
- }
467
- });
468
- onBeforeUnmount(() => {
469
- for (const stopHandler of watchStopHandlers) {
470
- stopHandler();
471
- }
472
- });
473
- }
@@ -1,47 +0,0 @@
1
- import { defaultWindow, unrefElement, useEventListener, } from '@vueuse/core';
2
- import { ref, watch } from 'vue';
3
- /**
4
- * Tracks the visibility of an element within the viewport.
5
- *
6
- * Compatibility version to get Stonecrop keyboard navigation to work. This function is a copy of the
7
- * `useElementVisibility` function from VueUse v9.13.0, with the `IntersectionObserver` API removed.
8
- *
9
- * This version uses the `getBoundingClientRect` method to determine if an element is visible
10
- * in the viewport. This is less performant than the `IntersectionObserver` API, but it is
11
- * more compatible.
12
- *
13
- * Note: the newer versions of the VueUse dependencies imported here are sufficient for this composable.
14
- * (Last verified: v10.9.0 on May 2, 2024)
15
- *
16
- * @see https://v9-13-0.vueuse.org/core/useElementVisibility
17
- * @param element - Element to track visibility for
18
- * @param options - Configuration options (window, scrollTarget)
19
- */
20
- export function useElementVisibility(element, { window = defaultWindow, scrollTarget } = {}) {
21
- const elementIsVisible = ref(false);
22
- const testBounding = () => {
23
- if (!window)
24
- return;
25
- const document = window.document;
26
- const el = unrefElement(element);
27
- if (!el) {
28
- elementIsVisible.value = false;
29
- }
30
- else {
31
- const rect = el.getBoundingClientRect();
32
- elementIsVisible.value =
33
- rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
34
- rect.left <= (window.innerWidth || document.documentElement.clientWidth) &&
35
- rect.bottom >= 0 &&
36
- rect.right >= 0;
37
- }
38
- };
39
- watch(() => unrefElement(element), () => testBounding(), { immediate: true, flush: 'post' });
40
- if (window) {
41
- useEventListener(scrollTarget || window, 'scroll', testBounding, {
42
- capture: false,
43
- passive: true,
44
- });
45
- }
46
- return elementIsVisible;
47
- }
package/dist/src/index.js DELETED
@@ -1,8 +0,0 @@
1
- import { defaultKeypressHandlers, useKeyboardNav } from './composables/keyboard';
2
- /**
3
- * Install all utility components
4
- * @param _app - Vue app instance
5
- * @public
6
- */
7
- function install(_app /* options */) { }
8
- export { defaultKeypressHandlers, install, useKeyboardNav };
@@ -1,107 +0,0 @@
1
- /**
2
- * Naming Convention Utilities
3
- * Converts between SQL snake_case and JavaScript camelCase naming conventions
4
- * @packageDocumentation
5
- */
6
- /**
7
- * Result of name conversion
8
- * @public
9
- */
10
- export interface NameConversion {
11
- /** Converted fieldname (camelCase) */
12
- fieldname: string;
13
- /** Human-readable label */
14
- label: string;
15
- /** Original SQL name */
16
- originalName: string;
17
- }
18
- /**
19
- * Converts snake_case to camelCase
20
- * @param snakeCase - Snake case string
21
- * @returns Camel case string
22
- * @public
23
- * @example
24
- * ```typescript
25
- * snakeToCamel('user_email') // 'userEmail'
26
- * snakeToCamel('created_at') // 'createdAt'
27
- * snakeToCamel('user_id') // 'userId'
28
- * ```
29
- */
30
- export declare function snakeToCamel(snakeCase: string): string;
31
- /**
32
- * Converts camelCase to snake_case
33
- * @param camelCase - Camel case string
34
- * @returns Snake case string
35
- * @public
36
- * @example
37
- * ```typescript
38
- * camelToSnake('userEmail') // 'user_email'
39
- * camelToSnake('createdAt') // 'created_at'
40
- * camelToSnake('userId') // 'user_id'
41
- * ```
42
- */
43
- export declare function camelToSnake(camelCase: string): string;
44
- /**
45
- * Converts snake_case to Title Case label
46
- * @param snakeCase - Snake case string
47
- * @returns Title case label
48
- * @public
49
- * @example
50
- * ```typescript
51
- * snakeToLabel('user_email') // 'User Email'
52
- * snakeToLabel('first_name') // 'First Name'
53
- * snakeToLabel('created_at') // 'Created At'
54
- * ```
55
- */
56
- export declare function snakeToLabel(snakeCase: string): string;
57
- /**
58
- * Converts camelCase to Title Case label
59
- * @param camelCase - Camel case string
60
- * @returns Title case label
61
- * @public
62
- * @example
63
- * ```typescript
64
- * camelToLabel('userEmail') // 'User Email'
65
- * camelToLabel('firstName') // 'First Name'
66
- * camelToLabel('createdAt') // 'Created At'
67
- * ```
68
- */
69
- export declare function camelToLabel(camelCase: string): string;
70
- /**
71
- * Converts SQL column name to Stonecrop field naming convention
72
- * Handles special cases like ID suffixes
73
- * @param sqlName - SQL column name (snake_case)
74
- * @returns Field name and label
75
- * @public
76
- * @example
77
- * ```typescript
78
- * convertSQLName('user_email')
79
- * // { fieldname: 'userEmail', label: 'User Email', originalName: 'user_email' }
80
- *
81
- * convertSQLName('user_id')
82
- * // { fieldname: 'userId', label: 'User', originalName: 'user_id' }
83
- *
84
- * convertSQLName('created_at')
85
- * // { fieldname: 'createdAt', label: 'Created At', originalName: 'created_at' }
86
- * ```
87
- */
88
- export declare function convertSQLName(sqlName: string): NameConversion;
89
- /**
90
- * Batch converts multiple SQL column names
91
- * @param sqlNames - Array of SQL column names
92
- * @returns Array of name conversions
93
- * @public
94
- */
95
- export declare function convertSQLNames(sqlNames: string[]): NameConversion[];
96
- /**
97
- * Creates a bidirectional mapping between SQL and Stonecrop names
98
- * @param sqlNames - Array of SQL column names
99
- * @returns Mapping object with both directions
100
- * @public
101
- */
102
- export declare function createNameMapping(sqlNames: string[]): {
103
- sqlToFieldname: Map<string, string>;
104
- fieldnameToSQL: Map<string, string>;
105
- conversions: NameConversion[];
106
- };
107
- //# sourceMappingURL=naming-conventions.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"naming-conventions.d.ts","sourceRoot":"","sources":["../../src/naming-conventions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAA;IACjB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,wBAAwB;IACxB,YAAY,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAItD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAkB9D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG;IACtD,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,WAAW,EAAE,cAAc,EAAE,CAAA;CAC7B,CAeA"}