@stonecrop/utilities 0.2.10 → 0.2.13

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.2.10",
3
+ "version": "0.2.13",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "author": {
@@ -1,7 +1,8 @@
1
- import { onMounted, onBeforeUnmount } from 'vue'
2
- import { useElementVisibility } from '@/composables/visibility'
1
+ import { type WatchStopHandle, onBeforeUnmount, onMounted, ref, watch } from 'vue'
2
+ import { useFocusWithin } from '@vueuse/core'
3
3
 
4
4
  import type { KeyboardNavigationOptions, KeypressHandlers } from 'types'
5
+ import { useElementVisibility } from '@/composables/visibility'
5
6
 
6
7
  // helper functions
7
8
  const isVisible = (element: HTMLElement) => {
@@ -328,50 +329,67 @@ export const defaultKeypressHandlers: KeypressHandlers = {
328
329
  }
329
330
 
330
331
  export function useKeyboardNav(options: KeyboardNavigationOptions[]) {
331
- const getSelectors = (option: KeyboardNavigationOptions) => {
332
- // get parent element
333
- let $parent: Element | null = null
332
+ const getParentElement = (option: KeyboardNavigationOptions) => {
333
+ let $parent: HTMLElement | null = null
334
334
  if (option.parent) {
335
335
  if (typeof option.parent === 'string') {
336
336
  $parent = document.querySelector(option.parent)
337
- } else if (option.parent instanceof Element) {
337
+ } else if (option.parent instanceof HTMLElement) {
338
338
  $parent = option.parent
339
339
  } else {
340
340
  $parent = option.parent.value
341
341
  }
342
342
  }
343
+ return $parent
344
+ }
343
345
 
344
- // generate a list of selector(s)
345
- let selectors: Element[] = []
346
-
347
- if (option.selectors) {
348
- if (typeof option.selectors === 'string') {
349
- selectors = $parent
350
- ? Array.from($parent.querySelectorAll(option.selectors))
351
- : Array.from(document.querySelectorAll(option.selectors))
352
- } else if (option.selectors instanceof Element) {
353
- selectors.push(option.selectors)
354
- } else {
355
- if (Array.isArray(option.selectors.value)) {
356
- for (const element of option.selectors.value) {
357
- if (element instanceof Element) {
358
- selectors.push(element)
359
- } else {
360
- selectors.push(element.$el as Element)
361
- }
362
- }
346
+ const getSelectorsFromOption = (option: KeyboardNavigationOptions) => {
347
+ // assumes that option.selectors is provided
348
+ const $parent = getParentElement(option)
349
+ let selectors: HTMLElement[] = []
350
+ if (typeof option.selectors === 'string') {
351
+ selectors = $parent
352
+ ? Array.from($parent.querySelectorAll(option.selectors))
353
+ : Array.from(document.querySelectorAll(option.selectors))
354
+ } else if (Array.isArray(option.selectors)) {
355
+ for (const element of option.selectors) {
356
+ if (element instanceof HTMLElement) {
357
+ selectors.push(element)
363
358
  } else {
364
- selectors.push(option.selectors.value)
359
+ selectors.push(element.$el as HTMLElement)
365
360
  }
366
361
  }
362
+ } else if (option.selectors instanceof HTMLElement) {
363
+ selectors.push(option.selectors)
367
364
  } else {
368
- const $children = Array.from($parent.children)
369
- selectors = $children.filter((selector: HTMLElement) => {
365
+ if (Array.isArray(option.selectors.value)) {
366
+ for (const element of option.selectors.value) {
367
+ if (element instanceof HTMLElement) {
368
+ selectors.push(element)
369
+ } else {
370
+ selectors.push(element.$el as HTMLElement)
371
+ }
372
+ }
373
+ } else {
374
+ selectors.push(option.selectors.value)
375
+ }
376
+ }
377
+ return selectors
378
+ }
379
+
380
+ const getSelectors = (option: KeyboardNavigationOptions) => {
381
+ const $parent = getParentElement(option)
382
+ let selectors: HTMLElement[] = []
383
+ if (option.selectors) {
384
+ selectors = getSelectorsFromOption(option)
385
+ } else if ($parent) {
386
+ // TODO: what should happen if no parent or selectors are provided?
387
+ const $children = Array.from($parent.children) as HTMLElement[]
388
+ selectors = $children.filter(selector => {
370
389
  // ignore elements not in the tab order or are not visible
371
390
  return isFocusable(selector) && isVisible(selector)
372
391
  })
373
392
  }
374
-
375
393
  return selectors
376
394
  }
377
395
 
@@ -420,21 +438,33 @@ export function useKeyboardNav(options: KeyboardNavigationOptions[]) {
420
438
  }
421
439
  }
422
440
 
441
+ const watchStopHandlers: WatchStopHandle[] = []
423
442
  onMounted(() => {
424
443
  for (const option of options) {
444
+ const $parent = getParentElement(option)
425
445
  const selectors = getSelectors(option)
426
- for (const selector of selectors) {
427
- selector.addEventListener('keydown', getEventListener(option))
446
+ const listener = getEventListener(option)
447
+ const listenerElements = $parent
448
+ ? [$parent] // watch for focus recursively within the parent element
449
+ : selectors // watch for focus on each selector element TODO: too much JS?
450
+
451
+ for (const element of listenerElements) {
452
+ const { focused } = useFocusWithin(ref(element))
453
+ const stopHandler = watch(focused, value => {
454
+ if (value) {
455
+ element.addEventListener('keydown', listener)
456
+ } else {
457
+ element.removeEventListener('keydown', listener)
458
+ }
459
+ })
460
+ watchStopHandlers.push(stopHandler)
428
461
  }
429
462
  }
430
463
  })
431
464
 
432
465
  onBeforeUnmount(() => {
433
- for (const option of options) {
434
- const selectors = getSelectors(option)
435
- for (const selector of selectors) {
436
- selector.removeEventListener('keydown', getEventListener(option))
437
- }
466
+ for (const stopHandler of watchStopHandlers) {
467
+ stopHandler()
438
468
  }
439
469
  })
440
470
  }
package/src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { App } from 'vue'
2
2
 
3
3
  import { defaultKeypressHandlers, useKeyboardNav } from './composables/keyboard'
4
+ import type { KeypressHandlers } from '../types'
4
5
 
5
6
  function install(app: App /* options */) {}
6
7
 
7
- export { defaultKeypressHandlers, install, useKeyboardNav }
8
+ export { KeypressHandlers, defaultKeypressHandlers, install, useKeyboardNav }