@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonecrop/utilities",
3
- "version": "0.13.4",
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
- "eslint": "^10.3.0",
45
- "eslint-config-prettier": "^10.1.8",
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": "eslint .",
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 isVisible = useElementVisibility(element).value
10
- isVisible = isVisible && element.offsetHeight > 0
11
- return isVisible
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
- const $target = event.target as HTMLElement
21
- return _getUpCell($target)
20
+ if (!(event.target instanceof HTMLElement)) return undefined
21
+ return getUpCellEl(event.target)
22
22
  }
23
23
 
24
- const _getUpCell = (element: HTMLElement): HTMLElement | undefined => {
24
+ const getUpCellEl = (element: HTMLElement): HTMLElement | undefined => {
25
25
  let $upCell: HTMLElement | undefined
26
26
  if (element instanceof HTMLTableCellElement) {
27
- const $prevRow = element.parentElement?.previousElementSibling as HTMLTableRowElement
28
- if ($prevRow) {
29
- const $prevRowCells = Array.from($prevRow.children)
30
- const $prevCell = $prevRowCells[element.cellIndex] as HTMLElement
31
- if ($prevCell) {
32
- $upCell = $prevCell
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 $prevRow = element.previousElementSibling as HTMLTableRowElement
37
- if ($prevRow) {
38
- $upCell = $prevRow
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 _getUpCell($upCell)
44
+ return getUpCellEl($upCell)
45
45
  }
46
46
  return $upCell
47
47
  }
48
48
 
49
49
  const getTopCell = (event: KeyboardEvent) => {
50
- const $target = event.target as HTMLElement
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 $navCell = $firstRow?.children[$target.cellIndex] as HTMLElement
57
- if ($navCell) {
58
- $topCell = $navCell
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 $table = $target.parentElement as HTMLTableElement
63
- if ($table) {
64
- const $firstRow = $table.firstElementChild as HTMLTableRowElement
65
- if ($firstRow) {
66
- $topCell = $firstRow
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 _getDownCell($topCell)
74
+ return getDownCellEl($topCell)
74
75
  }
75
76
  return $topCell
76
77
  }
77
78
 
78
79
  const getDownCell = (event: KeyboardEvent) => {
79
- const $target = event.target as HTMLElement
80
- return _getDownCell($target)
80
+ if (!(event.target instanceof HTMLElement)) return undefined
81
+ return getDownCellEl(event.target)
81
82
  }
82
83
 
83
- const _getDownCell = (element: HTMLElement): HTMLElement | undefined => {
84
+ const getDownCellEl = (element: HTMLElement): HTMLElement | undefined => {
84
85
  let $downCell: HTMLElement | undefined
85
86
  if (element instanceof HTMLTableCellElement) {
86
- const $nextRow = element.parentElement?.nextElementSibling
87
- if ($nextRow) {
88
- const $nextRowCells = Array.from($nextRow.children)
89
- const $nextCell = $nextRowCells[element.cellIndex] as HTMLElement
90
- if ($nextCell) {
91
- $downCell = $nextCell
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 $nextRow = element.nextElementSibling as HTMLTableRowElement
96
- if ($nextRow) {
97
- $downCell = $nextRow
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 _getDownCell($downCell)
104
+ return getDownCellEl($downCell)
104
105
  }
105
106
  return $downCell
106
107
  }
107
108
 
108
109
  const getBottomCell = (event: KeyboardEvent) => {
109
- const $target = event.target as HTMLElement
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 $navCell = $lastRow?.children[$target.cellIndex] as HTMLElement
116
- if ($navCell) {
117
- $bottomCell = $navCell
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 $table = $target.parentElement as HTMLTableElement
122
- if ($table) {
123
- const $lastRow = $table.lastElementChild as HTMLTableRowElement
124
- if ($lastRow) {
125
- $bottomCell = $lastRow
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 _getUpCell($bottomCell)
134
+ return getUpCellEl($bottomCell)
133
135
  }
134
136
  return $bottomCell
135
137
  }
136
138
 
137
139
  const getPrevCell = (event: KeyboardEvent) => {
138
- const $target = event.target as HTMLElement
139
- return _getPrevCell($target)
140
+ if (!(event.target instanceof HTMLElement)) return undefined
141
+ return getPrevCellEl(event.target)
140
142
  }
141
143
 
142
- const _getPrevCell = (element: HTMLElement): HTMLElement | undefined => {
144
+ const getPrevCellEl = (element: HTMLElement): HTMLElement | undefined => {
143
145
  let $prevCell: HTMLElement | undefined
144
- if (element.previousElementSibling) {
145
- $prevCell = element.previousElementSibling as HTMLElement
146
+ const $prevSibling = element.previousElementSibling
147
+ if ($prevSibling instanceof HTMLElement) {
148
+ $prevCell = $prevSibling
146
149
  } else {
147
150
  const $prevRow = element.parentElement?.previousElementSibling
148
- $prevCell = $prevRow?.lastElementChild as HTMLElement
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 _getPrevCell($prevCell)
157
+ return getPrevCellEl($prevCell)
152
158
  }
153
159
  return $prevCell
154
160
  }
155
161
 
156
162
  const getNextCell = (event: KeyboardEvent) => {
157
- const $target = event.target as HTMLElement
158
- return _getNextCell($target)
163
+ if (!(event.target instanceof HTMLElement)) return undefined
164
+ return getNextCellEl(event.target)
159
165
  }
160
166
 
161
- const _getNextCell = (element: HTMLElement): HTMLElement | undefined => {
167
+ const getNextCellEl = (element: HTMLElement): HTMLElement | undefined => {
162
168
  let $nextCell: HTMLElement | undefined
163
- if (element.nextElementSibling) {
164
- $nextCell = element.nextElementSibling as HTMLElement
169
+ const $nextSibling = element.nextElementSibling
170
+ if ($nextSibling instanceof HTMLElement) {
171
+ $nextCell = $nextSibling
165
172
  } else {
166
173
  const $nextRow = element.parentElement?.nextElementSibling
167
- $nextCell = $nextRow?.firstElementChild as HTMLElement
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 _getNextCell($nextCell)
180
+ return getNextCellEl($nextCell)
171
181
  }
172
182
  return $nextCell
173
183
  }
174
184
 
175
185
  const getFirstCell = (event: KeyboardEvent) => {
176
- const $target = event.target as HTMLElement
177
- const $parent = $target.parentElement
178
- const $firstCell = $parent?.firstElementChild as HTMLElement | null
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 _getNextCell($firstCell)
191
+ return getNextCellEl($firstCell)
181
192
  }
182
193
  return $firstCell
183
194
  }
184
195
 
185
196
  const getLastCell = (event: KeyboardEvent) => {
186
- const $target = event.target as HTMLElement
187
- const $parent = $target.parentElement
188
- const $lastCell = $parent?.lastElementChild as HTMLElement | null
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 _getPrevCell($lastCell)
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
- const $target = event.target as HTMLElement
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
- const $target = event.target as HTMLElement
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) as HTMLElement[]
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(key => modifierKeys.includes(key))
423
- const isModifierActive = modifierKeys.some(key => {
424
- const modifierKey = key.charAt(0).toUpperCase() + key.slice(1)
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