@rokkit/actions 1.0.0-next.38 → 1.0.0-next.42

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": "@rokkit/actions",
3
- "version": "1.0.0-next.38",
3
+ "version": "1.0.0-next.42",
4
4
  "description": "Contains generic actions that can be used in various components.",
5
5
  "author": "Jerry Thomas <me@jerrythomas.name>",
6
6
  "license": "MIT",
@@ -15,6 +15,7 @@
15
15
  "devDependencies": {
16
16
  "@sveltejs/vite-plugin-svelte": "^2.4.3",
17
17
  "@testing-library/svelte": "^4.0.3",
18
+ "@types/ramda": "^0.29.3",
18
19
  "@vitest/coverage-v8": "^0.33.0",
19
20
  "@vitest/ui": "~0.33.0",
20
21
  "jsdom": "^22.1.0",
@@ -23,7 +24,7 @@
23
24
  "validators": "latest",
24
25
  "vite": "^4.4.7",
25
26
  "vitest": "~0.33.0",
26
- "shared-config": "1.0.0-next.38"
27
+ "shared-config": "1.0.0-next.42"
27
28
  },
28
29
  "files": [
29
30
  "src/**/*.js",
@@ -41,7 +42,8 @@
41
42
  },
42
43
  "dependencies": {
43
44
  "@rokkit/core": "latest",
44
- "@rokkit/stores": "latest"
45
+ "@rokkit/stores": "latest",
46
+ "ramda": "^0.29.0"
45
47
  },
46
48
  "scripts": {
47
49
  "format": "prettier --write .",
@@ -52,6 +54,6 @@
52
54
  "test": "vitest",
53
55
  "coverage": "vitest run --coverage",
54
56
  "latest": "pnpm upgrade --latest && pnpm test:ci",
55
- "release": "tsc && pnpm publish --access public"
57
+ "release": "pnpm publish --access public"
56
58
  }
57
59
  }
package/src/hierarchy.js CHANGED
@@ -46,20 +46,29 @@ export function moveNext(path, items, fields) {
46
46
  } else if (current.index < current.items.length - 1) {
47
47
  current.index++
48
48
  } else {
49
- let level = path.length - 2
50
- while (level >= 0) {
51
- const parent = path[level]
52
- if (parent.index < parent.items.length - 1) {
53
- parent.index++
54
- path = path.slice(0, level + 1)
55
- break
56
- }
57
- level--
58
- }
49
+ path = navigateToNextLevel(path)
59
50
  }
60
51
  return path
61
52
  }
62
53
 
54
+ /**
55
+ * Navigate to the next level
56
+ * @param {Array<import('./types').PathFragment>} path
57
+ * @returns {Array<import('./types').PathFragment>}
58
+ */
59
+ function navigateToNextLevel(path) {
60
+ let level = path.length - 2
61
+ while (level >= 0) {
62
+ const parent = path[level]
63
+ if (parent.index < parent.items.length - 1) {
64
+ parent.index++
65
+ path = path.slice(0, level + 1)
66
+ break
67
+ }
68
+ level--
69
+ }
70
+ return path
71
+ }
63
72
  /**
64
73
  * Navigate to the previous item
65
74
  *
@@ -0,0 +1,32 @@
1
+ /**
2
+ * EventManager class to manage event listeners on an element.
3
+ */
4
+ export function EventManager(element, handlers = {}) {
5
+ let listening = false
6
+
7
+ function activate() {
8
+ if (!listening) {
9
+ for (const event in handlers) {
10
+ element.addEventListener(event, handlers[event])
11
+ }
12
+ listening = true
13
+ }
14
+ }
15
+ function destroy() {
16
+ if (listening) {
17
+ for (const event in handlers) {
18
+ element.removeEventListener(event, handlers[event])
19
+ }
20
+ listening = false
21
+ }
22
+ }
23
+ function update(enabled, newHandlers = handlers) {
24
+ if (listening !== enabled || handlers !== newHandlers) {
25
+ destroy()
26
+ handlers = newHandlers
27
+ if (enabled) activate()
28
+ }
29
+ }
30
+
31
+ return { activate, destroy, update }
32
+ }
package/src/lib/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export * from './internal'
2
+ export * from './event-manager'
@@ -1,39 +1,24 @@
1
1
  import { compact } from '@rokkit/core'
2
+ import { hasChildren, isExpanded } from '@rokkit/core'
2
3
 
3
4
  /**
4
- * @typedef {Object} ActionHandlers
5
- * @property {Function} [next]
6
- * @property {Function} [previous]
7
- * @property {Function} [select]
8
- * @property {Function} [escape]
9
- * @property {Function} [collapse]
10
- * @property {Function} [expand]
11
- */
12
-
13
- /**
14
- * @typedef {Object} NavigationOptions
15
- * @property {Boolean} [horizontal]
16
- * @property {Boolean} [nested]
17
- * @property {Boolean} [enabled]
18
- */
19
-
20
- /**
21
- * @typedef {Object} KeyboardActions
22
- * @property {Function} [ArrowDown]
23
- * @property {Function} [ArrowUp]
24
- * @property {Function} [ArrowRight]
25
- * @property {Function} [ArrowLeft]
26
- * @property {Function} [Enter]
27
- * @property {Function} [Escape]
28
- * @property {Function} [" "]
5
+ * Emits a custom event with the given data.
6
+ *
7
+ * @param {HTMLElement} element
8
+ * @param {string} event
9
+ * @param {*} data
10
+ * @returns {void}
29
11
  */
12
+ export function emit(element, event, data) {
13
+ element.dispatchEvent(new CustomEvent(event, { detail: data }))
14
+ }
30
15
 
31
16
  /**
32
17
  * Maps keyboard events to actions based on the given handlers and options.
33
18
  *
34
- * @param {ActionHandlers} handlers
35
- * @param {NavigationOptions} options
36
- * @returns {KeyboardActions}
19
+ * @param {import('../types').ActionHandlers} handlers
20
+ * @param {import('../types').NavigationOptions} options
21
+ * @returns {import('../types').KeyboardActions}
37
22
  */
38
23
  export function mapKeyboardEventsToActions(handlers, options) {
39
24
  const { next, previous, select, escape } = handlers
@@ -56,8 +41,73 @@ export function mapKeyboardEventsToActions(handlers, options) {
56
41
  })
57
42
  }
58
43
 
44
+ /**
45
+ * Finds the closest ancestor of the given element that has the given attribute.
46
+ *
47
+ * @param {HTMLElement} element
48
+ * @param {string} attribute
49
+ * @returns {HTMLElement|null}
50
+ */
59
51
  export function getClosestAncestorWithAttribute(element, attribute) {
60
52
  if (!element) return null
61
53
  if (element.getAttribute(attribute)) return element
62
54
  return getClosestAncestorWithAttribute(element.parentElement, attribute)
63
55
  }
56
+
57
+ /**
58
+ * Sets up event handlers based on the given options.
59
+ * Returns whether or not the event handlers are listening.
60
+ *
61
+ * @param {HTMLElement} element
62
+ * @param {import('../types').EventHandlers} listeners
63
+ * @param {import('../types').TraversableOptions} options
64
+ * @returns {void}
65
+ */
66
+ export function setupListeners(element, listeners, options) {
67
+ const { enabled } = { enabled: true, ...options }
68
+ if (enabled) {
69
+ Object.entries(listeners).forEach(([event, listener]) =>
70
+ element.addEventListener(event, listener)
71
+ )
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Removes event handlers based on the given options.
77
+ * Returns whether or not the event handlers are listening.
78
+ *
79
+ * @param {HTMLElement} element
80
+ * @param {import('../types').EventHandlers} listeners
81
+ * @returns {void}
82
+ */
83
+ export function removeListeners(element, listeners) {
84
+ if (listeners) {
85
+ Object.entries(listeners).forEach(([event, listener]) => {
86
+ element.removeEventListener(event, listener)
87
+ })
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Handles the click event.
93
+ * @param {HTMLElement} element - The root element.
94
+ * @param {CurrentItem} current - A reference to the current Item
95
+ * @returns {CurrentItem} The updated current item.
96
+ */
97
+ export function handleItemClick(element, current) {
98
+ const { item, fields, position } = current
99
+ const detail = { item, position }
100
+
101
+ if (hasChildren(item, fields)) {
102
+ if (isExpanded(item, fields)) {
103
+ item[fields.isOpen] = false
104
+ emit(element, 'collapse', detail)
105
+ } else {
106
+ item[fields.isOpen] = true
107
+ emit(element, 'expand', detail)
108
+ }
109
+ } else {
110
+ emit(element, 'select', detail)
111
+ }
112
+ return current
113
+ }
package/src/navigator.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  indicesFromPath,
8
8
  getCurrentNode
9
9
  } from './hierarchy'
10
-
10
+ import { mapKeyboardEventsToActions } from './lib'
11
11
  /**
12
12
  * Keyboard navigation for Lists and NestedLists. The data is either nested or not and is not
13
13
  * expected to switch from nested to simple list or vice-versa.
@@ -83,11 +83,15 @@ export function navigator(element, options) {
83
83
  update(options)
84
84
 
85
85
  const nested = isNested(items, fields)
86
- const actions = mapKeyboardEventsToActions(vertical, nested, handlers)
86
+ const actions = mapKeyboardEventsToActions(handlers, {
87
+ horizontal: !vertical,
88
+ nested
89
+ })
87
90
 
88
91
  const handleKeyDown = (event) => handleAction(actions, event)
89
92
 
90
93
  const handleClick = (event) => {
94
+ event.stopPropagation()
91
95
  let target = findParentWithDataPath(event.target, element)
92
96
  let indices = !target
93
97
  ? []
@@ -106,7 +110,8 @@ export function navigator(element, options) {
106
110
  ? 'expand'
107
111
  : 'collapse'
108
112
  emit(event, element, indices, currentNode)
109
- } else if (currentNode) emit('select', element, indices, currentNode)
113
+ } else if (currentNode !== null)
114
+ emit('select', element, indices, currentNode)
110
115
  }
111
116
  }
112
117
 
@@ -174,27 +179,3 @@ function emit(event, element, indices, node) {
174
179
  })
175
180
  )
176
181
  }
177
-
178
- function mapKeyboardEventsToActions(vertical, nested, handlers) {
179
- let actions = { Enter: handlers.select }
180
-
181
- if (vertical) {
182
- actions = {
183
- ...actions,
184
- ...{ ArrowDown: handlers.next, ArrowUp: handlers.previous },
185
- ...(nested
186
- ? { ArrowRight: handlers.expand, ArrowLeft: handlers.collapse }
187
- : {})
188
- }
189
- } else {
190
- actions = {
191
- ...actions,
192
- ...{ ArrowRight: handlers.next, ArrowLeft: handlers.previous },
193
- ...(nested
194
- ? { ArrowDown: handlers.expand, ArrowUp: handlers.collapse }
195
- : {})
196
- }
197
- }
198
-
199
- return actions
200
- }
package/src/pannable.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { removeListeners, setupListeners } from './lib'
2
+
1
3
  /**
2
4
  * Handle drag and move events
3
5
  *
@@ -7,6 +9,18 @@
7
9
  export function pannable(node) {
8
10
  let x
9
11
  let y
12
+ let listeners = {
13
+ primary: {
14
+ mousedown: start,
15
+ touchstart: start
16
+ },
17
+ secondary: {
18
+ mousemove: move,
19
+ mouseup: stop,
20
+ touchmove: move,
21
+ touchend: stop
22
+ }
23
+ }
10
24
 
11
25
  function track(event, name, delta = {}) {
12
26
  x = event.clientX || event.touches[0].clientX
@@ -20,37 +34,26 @@ export function pannable(node) {
20
34
  )
21
35
  }
22
36
 
23
- function handleMousedown(event) {
37
+ function start(event) {
24
38
  track(event, 'panstart')
25
- window.addEventListener('mousemove', handleMousemove)
26
- window.addEventListener('mouseup', handleMouseup)
27
- window.addEventListener('touchmove', handleMousemove, { passive: false })
28
- window.addEventListener('touchend', handleMouseup)
39
+ setupListeners(window, listeners.secondary)
29
40
  }
30
41
 
31
- function handleMousemove(event) {
42
+ function move(event) {
32
43
  const dx = (event.clientX || event.touches[0].clientX) - x
33
44
  const dy = (event.clientY || event.touches[0].clientY) - y
34
45
 
35
46
  track(event, 'panmove', { dx, dy })
36
47
  }
37
48
 
38
- function handleMouseup(event) {
49
+ function stop(event) {
39
50
  track(event, 'panend')
40
-
41
- window.removeEventListener('mousemove', handleMousemove)
42
- window.removeEventListener('mouseup', handleMouseup)
43
- window.removeEventListener('touchmove', handleMousemove)
44
- window.removeEventListener('touchend', handleMouseup)
51
+ removeListeners(window, listeners.secondary)
45
52
  }
46
53
 
47
- node.addEventListener('mousedown', handleMousedown)
48
- node.addEventListener('touchstart', handleMousedown, { passive: false })
54
+ setupListeners(node, listeners.primary)
49
55
 
50
56
  return {
51
- destroy() {
52
- node.removeEventListener('mousedown', handleMousedown)
53
- node.removeEventListener('touchstart', handleMousedown)
54
- }
57
+ destroy: () => removeListeners(node, listeners.primary)
55
58
  }
56
59
  }
package/src/swipeable.js CHANGED
@@ -1,3 +1,13 @@
1
+ import { removeListeners, setupListeners } from './lib'
2
+
3
+ const defaultOptions = {
4
+ horizontal: true,
5
+ vertical: false,
6
+ threshold: 100,
7
+ enabled: true,
8
+ minSpeed: 300
9
+ }
10
+
1
11
  /**
2
12
  * A svelte action function that captures swipe actions and emits event for corresponding movements.
3
13
  *
@@ -5,88 +15,94 @@
5
15
  * @param {import(./types).SwipeableOptions} options
6
16
  * @returns {import('./types').SvelteActionReturn}
7
17
  */
18
+ export function swipeable(node, options = defaultOptions) {
19
+ let track = {}
20
+ let listeners = {}
8
21
 
9
- export function swipeable(
10
- node,
11
- {
12
- horizontal = true,
13
- vertical = false,
14
- threshold = 100,
15
- enabled = true,
16
- minSpeed = 300
17
- } = {}
18
- ) {
19
- let listening = false
20
- let startX
21
- let startY
22
- let startTime
23
-
24
- function touchStart(event) {
25
- const touch = event.touches ? event.touches[0] : event
26
- startX = touch.clientX
27
- startY = touch.clientY
28
- startTime = new Date().getTime()
22
+ const updateListeners = (options) => {
23
+ removeListeners(node, listeners)
24
+ listeners = getListeners(node, options, track)
25
+ setupListeners(node, listeners, options)
29
26
  }
30
27
 
31
- function touchEnd(event) {
32
- const touch = event.changedTouches ? event.changedTouches[0] : event
33
- const distX = touch.clientX - startX
34
- const distY = touch.clientY - startY
35
- const duration = (new Date().getTime() - startTime) / 1000
36
- const speed = Math.max(Math.abs(distX), Math.abs(distY)) / duration
28
+ options = { ...defaultOptions, ...options }
29
+ updateListeners(options)
37
30
 
38
- if (horizontal && speed > minSpeed) {
39
- if (Math.abs(distX) > Math.abs(distY) && Math.abs(distX) >= threshold) {
40
- if (distX > 0 && distX / duration > minSpeed) {
41
- node.dispatchEvent(new CustomEvent('swipeRight'))
42
- } else {
43
- node.dispatchEvent(new CustomEvent('swipeLeft'))
44
- }
45
- }
46
- }
47
-
48
- if (vertical && speed > minSpeed) {
49
- if (Math.abs(distY) > Math.abs(distX) && Math.abs(distY) >= threshold) {
50
- if (distY > 0) {
51
- node.dispatchEvent(new CustomEvent('swipeDown'))
52
- } else {
53
- node.dispatchEvent(new CustomEvent('swipeUp'))
54
- }
55
- }
31
+ return {
32
+ update: (data) => {
33
+ options = { ...options, ...data }
34
+ updateListeners(options)
35
+ },
36
+ destroy() {
37
+ removeListeners(node, listeners)
56
38
  }
57
39
  }
40
+ }
58
41
 
59
- const updateListeners = (enabled) => {
60
- if (enabled && !listening) {
61
- node.addEventListener('touchstart', touchStart)
62
- node.addEventListener('touchend', touchEnd)
63
- node.addEventListener('mousedown', touchStart)
64
- node.addEventListener('mouseup', touchEnd)
65
- listening = true
66
- }
67
- if (!enabled && listening) {
68
- node.removeEventListener('touchstart', touchStart)
69
- node.removeEventListener('touchend', touchEnd)
70
- node.removeEventListener('mousedown', touchStart)
71
- node.removeEventListener('mouseup', touchEnd)
72
- listening = false
73
- }
42
+ /**
43
+ * Returns the listeners for the swipeable action.
44
+ * @param {HTMLElement} node - The node where the event is dispatched.
45
+ * @param {import(./types).SwipeableOptions} options - The options for the swipe.
46
+ * @param {import(./types).TouchTracker} track - The tracking object.
47
+ * @returns {import(./types).Listeners}
48
+ */
49
+ function getListeners(node, options, track) {
50
+ if (!options.enabled) return {}
51
+
52
+ let listeners = {
53
+ touchend: (e) => touchEnd(e, node, options, track),
54
+ touchstart: (e) => touchStart(e, track),
55
+ mousedown: (e) => touchStart(e, track),
56
+ mouseup: (e) => touchEnd(e, node, options, track)
74
57
  }
58
+ return listeners
59
+ }
75
60
 
76
- updateListeners(enabled)
61
+ /**
62
+ * Handles the touch start event.
63
+ *
64
+ * @param {Event} event
65
+ * @param {import(./types).TouchTracker} track
66
+ */
67
+ function touchStart(event, track) {
68
+ const touch = event.touches ? event.touches[0] : event
69
+ track.startX = touch.clientX
70
+ track.startY = touch.clientY
71
+ track.startTime = new Date().getTime()
72
+ }
77
73
 
78
- return {
79
- update: (options) => {
80
- horizontal = options.horizontal
81
- vertical = options.vertical
82
- threshold = options.threshold
83
- enabled = options.enabled
84
- minSpeed = options.minSpeed
74
+ /**
75
+ * Handles the touch end event.
76
+ *
77
+ * @param {Event} event - The touch event.
78
+ * @param {HTMLElement} node - The node where the event is dispatched.
79
+ * @param {import(./types).SwipeableOptions} options options - The options for the swipe.
80
+ * @param {import(./types).TouchTracker} track - The tracking object.
81
+ */
82
+ function touchEnd(event, node, options, track) {
83
+ const touch = event.changedTouches ? event.changedTouches[0] : event
84
+ const distX = touch.clientX - track.startX
85
+ const distY = touch.clientY - track.startY
86
+ const duration = (new Date().getTime() - track.startTime) / 1000
87
+ const speed = Math.max(Math.abs(distX), Math.abs(distY)) / duration
85
88
 
86
- updateListeners(enabled)
87
- },
88
- destroy() {
89
- updateListeners(false)
90
- }
89
+ if (speed <= options.minSpeed) return
90
+
91
+ const isHorizontalSwipe =
92
+ options.horizontal && Math.abs(distX) >= options.threshold
93
+ const isVerticalSwipe =
94
+ options.vertical && Math.abs(distY) >= options.threshold
95
+
96
+ if (!isHorizontalSwipe && !isVerticalSwipe) return
97
+
98
+ const swipeDirection = getSwipeDirection(distX, distY)
99
+ node.dispatchEvent(new CustomEvent(`swipe${swipeDirection}`))
100
+ }
101
+
102
+ function getSwipeDirection(distX, distY) {
103
+ if (Math.abs(distX) > Math.abs(distY)) {
104
+ return distX > 0 ? 'Right' : 'Left'
105
+ } else {
106
+ return distY > 0 ? 'Down' : 'Up'
91
107
  }
92
108
  }
@@ -1,6 +1,11 @@
1
+ import { mappedList, isNested } from '@rokkit/core'
2
+ import { pick } from 'ramda'
1
3
  import {
2
4
  getClosestAncestorWithAttribute,
3
- mapKeyboardEventsToActions
5
+ mapKeyboardEventsToActions,
6
+ emit,
7
+ handleItemClick,
8
+ EventManager
4
9
  } from './lib'
5
10
 
6
11
  const defaultOptions = {
@@ -8,70 +13,89 @@ const defaultOptions = {
8
13
  nested: false,
9
14
  enabled: true
10
15
  }
11
- export function traversable(element, data) {
12
- let listening = false
13
- let options = {}
14
- let tracker = {}
16
+
17
+ /**
18
+ * An action that can be used to traverse a nested list of items using keyboard and mouse.
19
+ *
20
+ * @param {HTMLElement} element
21
+ * @param {import('./types').TraversableOptions} data
22
+ * @returns
23
+ */
24
+ export function traversable(element, options) {
25
+ const content = mappedList(options.items, options.fields)
26
+ const manager = EventManager(element)
27
+
28
+ let current = { position: [], item: null }
15
29
  let handlers = {}
16
30
 
17
- let actions = {
18
- next: () => emit(element, 'move', tracker),
19
- previous: () => emit(element, 'move', tracker),
20
- select: () => emit(element, 'select', tracker),
21
- escape: () => emit(element, 'escape', tracker),
22
- collapse: () => emit(element, 'collapse', tracker),
23
- expand: () => emit(element, 'expand', tracker)
24
- }
31
+ const moveCursor = (direction) => {
32
+ const result = content[direction](current.position)
33
+ if (result) {
34
+ current = result
25
35
 
26
- let listeners = {
27
- keydown: (event) => {
28
- const action = handlers[event.key]
29
- if (action) action(event)
30
- },
31
- click: (event) => {
32
- const target = getClosestAncestorWithAttribute(event.target, 'data-index')
36
+ checkAndEmit('move')
37
+ }
38
+ }
33
39
 
34
- if (target) {
35
- const index = parseInt(target.getAttribute('data-index'))
36
- tracker.index = index
37
- actions.select()
38
- }
40
+ const checkAndEmit = (event) => {
41
+ if (current && current.item) {
42
+ emit(element, event, pick(['item', 'position'], current))
39
43
  }
40
44
  }
41
45
 
42
- const configure = (data) => {
43
- // const valueChanged = options.value !== data.value
44
- options = { ...options, ...data }
46
+ const actions = {
47
+ next: () => moveCursor('next'),
48
+ previous: () => moveCursor('previous'),
49
+ select: () => checkAndEmit('select'),
50
+ escape: () => checkAndEmit('escape'),
51
+ collapse: () => checkAndEmit('collapse'),
52
+ expand: () => checkAndEmit('expand')
53
+ }
45
54
 
46
- listening = setupEventHandlers(element, options, listening, listeners)
55
+ const listeners = {
56
+ keydown: (event) => {
57
+ if (event.key in handlers) handlers[event.key](event)
58
+ }
59
+ // click: (event) => {
60
+ // const target = getClosestAncestorWithAttribute(event.target, 'data-path')
61
+ // if (target) {
62
+ // const position = target
63
+ // .getAttribute('data-path')
64
+ // .split(',')
65
+ // .map((i) => +i)
66
+ // current = content.findByPosition(position)
67
+ // current = handleItemClick(element, current)
68
+ // }
69
+ // }
70
+ }
71
+
72
+ const update = (data) => {
73
+ options = { ...defaultOptions, ...options, ...data }
74
+ options.nested = isNested(options.items, options.fields)
75
+ content.update(options.items, options.fields)
47
76
  handlers = mapKeyboardEventsToActions(actions, options)
48
- // if (valueChanged) handleValueChange(element, options)
77
+ manager.update(options.enabled, listeners)
78
+ // current = handleValueChange(element, data, content, current)
49
79
  }
50
80
 
51
- configure({ ...defaultOptions, ...data })
81
+ update(options)
52
82
 
53
83
  return {
54
- update: configure,
55
- destroy: () => configure({ enabled: false })
84
+ update,
85
+ destroy: () => manager.destroy()
56
86
  }
57
87
  }
58
88
 
59
- function setupEventHandlers(element, options, listening, handlers) {
60
- const { enabled } = options
89
+ // const handleValueChange = (element, data, content, current) => {
90
+ // if (data.value !== null && data.value !== current?.value) {
91
+ // current = content.findByValue(data.value)
92
+ // if (current) scrollIntoView(element, current.position)
93
+ // }
94
+ // return current
95
+ // }
61
96
 
62
- if (enabled && !listening) {
63
- Object.entries(handlers).forEach(([event, handler]) =>
64
- element.addEventListener(event, handler)
65
- )
66
- } else if (!enabled && listening) {
67
- Object.entries(handlers).forEach(([event, handler]) =>
68
- element.removeEventListener(event, handler)
69
- )
70
- }
71
- return enabled
72
- }
73
-
74
- function emit(element, event, tracker) {
75
- element.dispatchEvent(new CustomEvent(event, { detail: tracker }))
76
- }
77
- // function handleValueChange(element, options) {}
97
+ // function scrollIntoView(element, position) {
98
+ // if (!Array.isArray(position) || position.length == 0) return
99
+ // const node = element.querySelector(`[data-index="${position.join(',')}"]`)
100
+ // if (node) node.scrollIntoView()
101
+ // }
package/src/types.js CHANGED
@@ -51,3 +51,68 @@
51
51
  * @property {number} threshold - Threshold for swipe
52
52
  * @property {number} minSpeed - Minimum speed for swipe
53
53
  */
54
+
55
+ /**
56
+ * @typedef TraversableOptions
57
+ * @property {boolean} horizontal - Traverse horizontally
58
+ * @property {boolean} nested - Traverse nested items
59
+ * @property {boolean} enabled - Enable traversal
60
+ * @property {string} value - Value to be used for traversal
61
+ * @property {Array<*>} items - An array containing the data set to traverse
62
+ * @property {Array<integer} [indices] - Indices of the items to be traversed
63
+ */
64
+
65
+ /**
66
+ * @typedef PositionTracker
67
+ * @property {integer} index
68
+ * @property {integer} previousIndex
69
+ */
70
+
71
+ /**
72
+ * @typedef EventHandlers
73
+ * @property {function} [keydown]
74
+ * @property {function} [keyup]
75
+ * @property {function} [click]
76
+ * @property {function} [touchstart]
77
+ * @property {function} [touchmove]
78
+ * @property {function} [touchend]
79
+ * @property {function} [touchcancel]
80
+ * @property {function} [mousedown]
81
+ * @property {function} [mouseup]
82
+ * @property {function} [mousemove]
83
+ */
84
+
85
+ /**
86
+ * @typedef {Object} ActionHandlers
87
+ * @property {Function} [next]
88
+ * @property {Function} [previous]
89
+ * @property {Function} [select]
90
+ * @property {Function} [escape]
91
+ * @property {Function} [collapse]
92
+ * @property {Function} [expand]
93
+ */
94
+
95
+ /**
96
+ * @typedef {Object} NavigationOptions
97
+ * @property {Boolean} [horizontal]
98
+ * @property {Boolean} [nested]
99
+ * @property {Boolean} [enabled]
100
+ */
101
+
102
+ /**
103
+ * @typedef {Object} KeyboardActions
104
+ * @property {Function} [ArrowDown]
105
+ * @property {Function} [ArrowUp]
106
+ * @property {Function} [ArrowRight]
107
+ * @property {Function} [ArrowLeft]
108
+ * @property {Function} [Enter]
109
+ * @property {Function} [Escape]
110
+ * @property {Function} [" "]
111
+ */
112
+
113
+ /**
114
+ * @typedef {Object} TouchTracker
115
+ * @property {number} startX - The start X position of the touch.
116
+ * @property {number} startY - The start Y position of the touch.
117
+ * @property {number} startTime - The start time of the touch.
118
+ */