@stonecrop/utilities 0.13.4 → 0.13.6
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.
- package/dist/src/composables/keyboard.d.ts.map +1 -1
- package/dist/src/composables/keyboard.js +118 -88
- package/dist/utilities.js +198 -177
- package/dist/utilities.js.map +1 -1
- package/package.json +6 -9
- package/src/composables/keyboard.ts +111 -91
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/utilities",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": {
|
|
@@ -37,18 +37,14 @@
|
|
|
37
37
|
"vue": "^3.5.33"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@eslint/js": "^10.0.1",
|
|
41
40
|
"@microsoft/api-documenter": "^7.30.5",
|
|
42
41
|
"@rushstack/heft": "^1.2.17",
|
|
43
42
|
"@vitejs/plugin-vue": "^6.0.6",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"eslint-plugin-vue": "^10.9.0",
|
|
47
|
-
"globals": "^17.6.0",
|
|
43
|
+
"oxlint": "1.67.0",
|
|
44
|
+
"oxlint-tsgolint": "0.23.0",
|
|
48
45
|
"typescript": "^6.0.3",
|
|
49
|
-
"typescript-eslint": "^8.59.1",
|
|
50
|
-
"vue": "^3.5.33",
|
|
51
46
|
"vite": "^7.3.2",
|
|
47
|
+
"vue": "^3.5.33",
|
|
52
48
|
"vue-router": "^5.0.6",
|
|
53
49
|
"stonecrop-rig": "0.7.0"
|
|
54
50
|
},
|
|
@@ -63,7 +59,8 @@
|
|
|
63
59
|
"build": "heft build && vite build && rushx docs",
|
|
64
60
|
"dev": "vite",
|
|
65
61
|
"docs": "bash ../common/scripts/run-docs.sh utilities",
|
|
66
|
-
"lint": "
|
|
62
|
+
"lint": "oxlint",
|
|
63
|
+
"lint:fix": "oxlint --fix",
|
|
67
64
|
"preview": "vite preview"
|
|
68
65
|
}
|
|
69
66
|
}
|
|
@@ -6,9 +6,9 @@ import type { KeyboardNavigationOptions, KeypressHandlers } from '../types'
|
|
|
6
6
|
|
|
7
7
|
// helper functions
|
|
8
8
|
const isVisible = (element: HTMLElement) => {
|
|
9
|
-
let
|
|
10
|
-
|
|
11
|
-
return
|
|
9
|
+
let visible = useElementVisibility(element).value
|
|
10
|
+
visible = visible && element.offsetHeight > 0
|
|
11
|
+
return visible
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const isFocusable = (element: HTMLElement) => {
|
|
@@ -17,177 +17,189 @@ const isFocusable = (element: HTMLElement) => {
|
|
|
17
17
|
|
|
18
18
|
// navigation functions
|
|
19
19
|
const getUpCell = (event: KeyboardEvent) => {
|
|
20
|
-
|
|
21
|
-
return
|
|
20
|
+
if (!(event.target instanceof HTMLElement)) return undefined
|
|
21
|
+
return getUpCellEl(event.target)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const
|
|
24
|
+
const getUpCellEl = (element: HTMLElement): HTMLElement | undefined => {
|
|
25
25
|
let $upCell: HTMLElement | undefined
|
|
26
26
|
if (element instanceof HTMLTableCellElement) {
|
|
27
|
-
const $
|
|
28
|
-
if ($
|
|
29
|
-
const $prevRowCells = Array.from($
|
|
30
|
-
const $
|
|
31
|
-
if ($
|
|
32
|
-
$upCell = $
|
|
27
|
+
const $prevSibling = element.parentElement?.previousElementSibling
|
|
28
|
+
if ($prevSibling instanceof HTMLTableRowElement) {
|
|
29
|
+
const $prevRowCells = Array.from($prevSibling.children)
|
|
30
|
+
const $cellEl = $prevRowCells[element.cellIndex]
|
|
31
|
+
if ($cellEl instanceof HTMLElement) {
|
|
32
|
+
$upCell = $cellEl
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
} else if (element instanceof HTMLTableRowElement) {
|
|
36
|
-
const $
|
|
37
|
-
if ($
|
|
38
|
-
$upCell = $
|
|
36
|
+
const $prevSibling = element.previousElementSibling
|
|
37
|
+
if ($prevSibling instanceof HTMLTableRowElement) {
|
|
38
|
+
$upCell = $prevSibling
|
|
39
39
|
}
|
|
40
40
|
} else {
|
|
41
41
|
// handle other contexts
|
|
42
42
|
}
|
|
43
43
|
if ($upCell && (!isFocusable($upCell) || !isVisible($upCell))) {
|
|
44
|
-
return
|
|
44
|
+
return getUpCellEl($upCell)
|
|
45
45
|
}
|
|
46
46
|
return $upCell
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const getTopCell = (event: KeyboardEvent) => {
|
|
50
|
-
|
|
50
|
+
if (!(event.target instanceof HTMLElement)) return undefined
|
|
51
|
+
const $target = event.target
|
|
51
52
|
let $topCell: HTMLElement | undefined
|
|
52
53
|
if ($target instanceof HTMLTableCellElement) {
|
|
53
54
|
const $table = $target.parentElement?.parentElement
|
|
54
55
|
if ($table) {
|
|
55
56
|
const $firstRow = $table.firstElementChild
|
|
56
|
-
const $
|
|
57
|
-
if ($
|
|
58
|
-
$topCell = $
|
|
57
|
+
const $navEl = $firstRow?.children[$target.cellIndex]
|
|
58
|
+
if ($navEl instanceof HTMLElement) {
|
|
59
|
+
$topCell = $navEl
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
} else if ($target instanceof HTMLTableRowElement) {
|
|
62
|
-
const $
|
|
63
|
-
if ($
|
|
64
|
-
const $
|
|
65
|
-
if ($
|
|
66
|
-
$topCell = $
|
|
63
|
+
const $parent = $target.parentElement
|
|
64
|
+
if ($parent) {
|
|
65
|
+
const $firstEl = $parent.firstElementChild
|
|
66
|
+
if ($firstEl instanceof HTMLTableRowElement) {
|
|
67
|
+
$topCell = $firstEl
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
} else {
|
|
70
71
|
// handle other contexts
|
|
71
72
|
}
|
|
72
73
|
if ($topCell && (!isFocusable($topCell) || !isVisible($topCell))) {
|
|
73
|
-
return
|
|
74
|
+
return getDownCellEl($topCell)
|
|
74
75
|
}
|
|
75
76
|
return $topCell
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
const getDownCell = (event: KeyboardEvent) => {
|
|
79
|
-
|
|
80
|
-
return
|
|
80
|
+
if (!(event.target instanceof HTMLElement)) return undefined
|
|
81
|
+
return getDownCellEl(event.target)
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
const
|
|
84
|
+
const getDownCellEl = (element: HTMLElement): HTMLElement | undefined => {
|
|
84
85
|
let $downCell: HTMLElement | undefined
|
|
85
86
|
if (element instanceof HTMLTableCellElement) {
|
|
86
|
-
const $
|
|
87
|
-
if ($
|
|
88
|
-
const $nextRowCells = Array.from($
|
|
89
|
-
const $
|
|
90
|
-
if ($
|
|
91
|
-
$downCell = $
|
|
87
|
+
const $nextSibling = element.parentElement?.nextElementSibling
|
|
88
|
+
if ($nextSibling) {
|
|
89
|
+
const $nextRowCells = Array.from($nextSibling.children)
|
|
90
|
+
const $cellEl = $nextRowCells[element.cellIndex]
|
|
91
|
+
if ($cellEl instanceof HTMLElement) {
|
|
92
|
+
$downCell = $cellEl
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
} else if (element instanceof HTMLTableRowElement) {
|
|
95
|
-
const $
|
|
96
|
-
if ($
|
|
97
|
-
$downCell = $
|
|
96
|
+
const $nextSibling = element.nextElementSibling
|
|
97
|
+
if ($nextSibling instanceof HTMLTableRowElement) {
|
|
98
|
+
$downCell = $nextSibling
|
|
98
99
|
}
|
|
99
100
|
} else {
|
|
100
101
|
// handle other contexts
|
|
101
102
|
}
|
|
102
103
|
if ($downCell && (!isFocusable($downCell) || !isVisible($downCell))) {
|
|
103
|
-
return
|
|
104
|
+
return getDownCellEl($downCell)
|
|
104
105
|
}
|
|
105
106
|
return $downCell
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
const getBottomCell = (event: KeyboardEvent) => {
|
|
109
|
-
|
|
110
|
+
if (!(event.target instanceof HTMLElement)) return undefined
|
|
111
|
+
const $target = event.target
|
|
110
112
|
let $bottomCell: HTMLElement | undefined
|
|
111
113
|
if ($target instanceof HTMLTableCellElement) {
|
|
112
114
|
const $table = $target.parentElement?.parentElement
|
|
113
115
|
if ($table) {
|
|
114
116
|
const $lastRow = $table.lastElementChild
|
|
115
|
-
const $
|
|
116
|
-
if ($
|
|
117
|
-
$bottomCell = $
|
|
117
|
+
const $navEl = $lastRow?.children[$target.cellIndex]
|
|
118
|
+
if ($navEl instanceof HTMLElement) {
|
|
119
|
+
$bottomCell = $navEl
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
} else if ($target instanceof HTMLTableRowElement) {
|
|
121
|
-
const $
|
|
122
|
-
if ($
|
|
123
|
-
const $
|
|
124
|
-
if ($
|
|
125
|
-
$bottomCell = $
|
|
123
|
+
const $parent = $target.parentElement
|
|
124
|
+
if ($parent) {
|
|
125
|
+
const $lastEl = $parent.lastElementChild
|
|
126
|
+
if ($lastEl instanceof HTMLTableRowElement) {
|
|
127
|
+
$bottomCell = $lastEl
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
130
|
} else {
|
|
129
131
|
// handle other contexts
|
|
130
132
|
}
|
|
131
133
|
if ($bottomCell && (!isFocusable($bottomCell) || !isVisible($bottomCell))) {
|
|
132
|
-
return
|
|
134
|
+
return getUpCellEl($bottomCell)
|
|
133
135
|
}
|
|
134
136
|
return $bottomCell
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
const getPrevCell = (event: KeyboardEvent) => {
|
|
138
|
-
|
|
139
|
-
return
|
|
140
|
+
if (!(event.target instanceof HTMLElement)) return undefined
|
|
141
|
+
return getPrevCellEl(event.target)
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
const
|
|
144
|
+
const getPrevCellEl = (element: HTMLElement): HTMLElement | undefined => {
|
|
143
145
|
let $prevCell: HTMLElement | undefined
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
const $prevSibling = element.previousElementSibling
|
|
147
|
+
if ($prevSibling instanceof HTMLElement) {
|
|
148
|
+
$prevCell = $prevSibling
|
|
146
149
|
} else {
|
|
147
150
|
const $prevRow = element.parentElement?.previousElementSibling
|
|
148
|
-
$
|
|
151
|
+
const $lastEl = $prevRow?.lastElementChild
|
|
152
|
+
if ($lastEl instanceof HTMLElement) {
|
|
153
|
+
$prevCell = $lastEl
|
|
154
|
+
}
|
|
149
155
|
}
|
|
150
156
|
if ($prevCell && (!isFocusable($prevCell) || !isVisible($prevCell))) {
|
|
151
|
-
return
|
|
157
|
+
return getPrevCellEl($prevCell)
|
|
152
158
|
}
|
|
153
159
|
return $prevCell
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
const getNextCell = (event: KeyboardEvent) => {
|
|
157
|
-
|
|
158
|
-
return
|
|
163
|
+
if (!(event.target instanceof HTMLElement)) return undefined
|
|
164
|
+
return getNextCellEl(event.target)
|
|
159
165
|
}
|
|
160
166
|
|
|
161
|
-
const
|
|
167
|
+
const getNextCellEl = (element: HTMLElement): HTMLElement | undefined => {
|
|
162
168
|
let $nextCell: HTMLElement | undefined
|
|
163
|
-
|
|
164
|
-
|
|
169
|
+
const $nextSibling = element.nextElementSibling
|
|
170
|
+
if ($nextSibling instanceof HTMLElement) {
|
|
171
|
+
$nextCell = $nextSibling
|
|
165
172
|
} else {
|
|
166
173
|
const $nextRow = element.parentElement?.nextElementSibling
|
|
167
|
-
$
|
|
174
|
+
const $firstEl = $nextRow?.firstElementChild
|
|
175
|
+
if ($firstEl instanceof HTMLElement) {
|
|
176
|
+
$nextCell = $firstEl
|
|
177
|
+
}
|
|
168
178
|
}
|
|
169
179
|
if ($nextCell && (!isFocusable($nextCell) || !isVisible($nextCell))) {
|
|
170
|
-
return
|
|
180
|
+
return getNextCellEl($nextCell)
|
|
171
181
|
}
|
|
172
182
|
return $nextCell
|
|
173
183
|
}
|
|
174
184
|
|
|
175
185
|
const getFirstCell = (event: KeyboardEvent) => {
|
|
176
|
-
|
|
177
|
-
const $parent =
|
|
178
|
-
const $
|
|
186
|
+
if (!(event.target instanceof HTMLElement)) return undefined
|
|
187
|
+
const $parent = event.target.parentElement
|
|
188
|
+
const $firstEl = $parent?.firstElementChild
|
|
189
|
+
const $firstCell = $firstEl instanceof HTMLElement ? $firstEl : null
|
|
179
190
|
if ($firstCell && (!isFocusable($firstCell) || !isVisible($firstCell))) {
|
|
180
|
-
return
|
|
191
|
+
return getNextCellEl($firstCell)
|
|
181
192
|
}
|
|
182
193
|
return $firstCell
|
|
183
194
|
}
|
|
184
195
|
|
|
185
196
|
const getLastCell = (event: KeyboardEvent) => {
|
|
186
|
-
|
|
187
|
-
const $parent =
|
|
188
|
-
const $
|
|
197
|
+
if (!(event.target instanceof HTMLElement)) return undefined
|
|
198
|
+
const $parent = event.target.parentElement
|
|
199
|
+
const $lastEl = $parent?.lastElementChild
|
|
200
|
+
const $lastCell = $lastEl instanceof HTMLElement ? $lastEl : null
|
|
189
201
|
if ($lastCell && (!isFocusable($lastCell) || !isVisible($lastCell))) {
|
|
190
|
-
return
|
|
202
|
+
return getPrevCellEl($lastCell)
|
|
191
203
|
}
|
|
192
204
|
return $lastCell
|
|
193
205
|
}
|
|
@@ -281,7 +293,8 @@ export const defaultKeypressHandlers: KeypressHandlers = {
|
|
|
281
293
|
}
|
|
282
294
|
},
|
|
283
295
|
'keydown.enter': (event: KeyboardEvent) => {
|
|
284
|
-
|
|
296
|
+
if (!(event.target instanceof HTMLElement)) return
|
|
297
|
+
const $target = event.target
|
|
285
298
|
if ($target instanceof HTMLTableCellElement) {
|
|
286
299
|
event.preventDefault()
|
|
287
300
|
event.stopPropagation()
|
|
@@ -294,7 +307,8 @@ export const defaultKeypressHandlers: KeypressHandlers = {
|
|
|
294
307
|
}
|
|
295
308
|
},
|
|
296
309
|
'keydown.shift.enter': (event: KeyboardEvent) => {
|
|
297
|
-
|
|
310
|
+
if (!(event.target instanceof HTMLElement)) return
|
|
311
|
+
const $target = event.target
|
|
298
312
|
if ($target instanceof HTMLTableCellElement) {
|
|
299
313
|
event.preventDefault()
|
|
300
314
|
event.stopPropagation()
|
|
@@ -332,26 +346,30 @@ export const defaultKeypressHandlers: KeypressHandlers = {
|
|
|
332
346
|
},
|
|
333
347
|
}
|
|
334
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Gets the parent element from a keyboard navigation option.
|
|
351
|
+
* @internal
|
|
352
|
+
*/
|
|
353
|
+
function getParentElement(option: KeyboardNavigationOptions): HTMLElement | null {
|
|
354
|
+
let $parent: HTMLElement | null = null
|
|
355
|
+
if (option.parent) {
|
|
356
|
+
if (typeof option.parent === 'string') {
|
|
357
|
+
$parent = document.querySelector(option.parent)
|
|
358
|
+
} else if (option.parent instanceof HTMLElement) {
|
|
359
|
+
$parent = option.parent
|
|
360
|
+
} else {
|
|
361
|
+
$parent = option.parent.value
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return $parent
|
|
365
|
+
}
|
|
366
|
+
|
|
335
367
|
/**
|
|
336
368
|
* Keyboard navigation composable
|
|
337
369
|
* @param options - Keyboard navigation options
|
|
338
370
|
* @public
|
|
339
371
|
*/
|
|
340
372
|
export function useKeyboardNav(options: KeyboardNavigationOptions[]) {
|
|
341
|
-
const getParentElement = (option: KeyboardNavigationOptions) => {
|
|
342
|
-
let $parent: HTMLElement | null = null
|
|
343
|
-
if (option.parent) {
|
|
344
|
-
if (typeof option.parent === 'string') {
|
|
345
|
-
$parent = document.querySelector(option.parent)
|
|
346
|
-
} else if (option.parent instanceof HTMLElement) {
|
|
347
|
-
$parent = option.parent
|
|
348
|
-
} else {
|
|
349
|
-
$parent = option.parent.value
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
return $parent
|
|
353
|
-
}
|
|
354
|
-
|
|
355
373
|
const getSelectorsFromOption = (option: KeyboardNavigationOptions) => {
|
|
356
374
|
// assumes that option.selectors is provided
|
|
357
375
|
const $parent = getParentElement(option)
|
|
@@ -365,6 +383,7 @@ export function useKeyboardNav(options: KeyboardNavigationOptions[]) {
|
|
|
365
383
|
if (element instanceof HTMLElement) {
|
|
366
384
|
selectors.push(element)
|
|
367
385
|
} else {
|
|
386
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- Vue component $el is typed as any; shape guaranteed by VNode contract
|
|
368
387
|
selectors.push(element.$el as HTMLElement)
|
|
369
388
|
}
|
|
370
389
|
}
|
|
@@ -376,6 +395,7 @@ export function useKeyboardNav(options: KeyboardNavigationOptions[]) {
|
|
|
376
395
|
if (element instanceof HTMLElement) {
|
|
377
396
|
selectors.push(element)
|
|
378
397
|
} else {
|
|
398
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- Vue component $el is typed as any; shape guaranteed by VNode contract
|
|
379
399
|
selectors.push(element.$el as HTMLElement)
|
|
380
400
|
}
|
|
381
401
|
}
|
|
@@ -393,7 +413,7 @@ export function useKeyboardNav(options: KeyboardNavigationOptions[]) {
|
|
|
393
413
|
selectors = getSelectorsFromOption(option)
|
|
394
414
|
} else if ($parent) {
|
|
395
415
|
// TODO: what should happen if no parent or selectors are provided?
|
|
396
|
-
const $children = Array.from($parent.children)
|
|
416
|
+
const $children = Array.from($parent.children).filter((el): el is HTMLElement => el instanceof HTMLElement)
|
|
397
417
|
selectors = $children.filter(selector => {
|
|
398
418
|
// ignore elements not in the tab order or are not visible
|
|
399
419
|
return isFocusable(selector) && isVisible(selector)
|
|
@@ -419,9 +439,9 @@ export function useKeyboardNav(options: KeyboardNavigationOptions[]) {
|
|
|
419
439
|
|
|
420
440
|
// check if the handler has modifiers, and if the modifier is active;
|
|
421
441
|
// this is to ensure exact key-press matches
|
|
422
|
-
const hasModifier = keys.filter(
|
|
423
|
-
const isModifierActive = modifierKeys.some(
|
|
424
|
-
const modifierKey =
|
|
442
|
+
const hasModifier = keys.filter(k => modifierKeys.includes(k))
|
|
443
|
+
const isModifierActive = modifierKeys.some(mk => {
|
|
444
|
+
const modifierKey = mk.charAt(0).toUpperCase() + mk.slice(1)
|
|
425
445
|
return event.getModifierState(modifierKey)
|
|
426
446
|
})
|
|
427
447
|
|