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

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.37",
3
+ "version": "1.0.0-next.38",
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",
@@ -23,7 +23,7 @@
23
23
  "validators": "latest",
24
24
  "vite": "^4.4.7",
25
25
  "vitest": "~0.33.0",
26
- "shared-config": "1.0.0-next.37"
26
+ "shared-config": "1.0.0-next.38"
27
27
  },
28
28
  "files": [
29
29
  "src/**/*.js",
package/src/hierarchy.js CHANGED
@@ -1,47 +1,4 @@
1
- /**
2
- * Check if the current item is a parent
3
- *
4
- * @param {*} item
5
- * @param {import('@rokkit/core').FieldMapping} fields
6
- * @returns {boolean}
7
- */
8
- export function hasChildren(item, fields) {
9
- return (
10
- typeof item === 'object' &&
11
- fields.children in item &&
12
- Array.isArray(item[fields.children])
13
- )
14
- }
15
-
16
- /**
17
- * Check if the current item is a parent and is expanded
18
- *
19
- * @param {*} item
20
- * @param {import('@rokkit/core').FieldMapping} fields
21
- * @returns {boolean}
22
- */
23
- export function isExpanded(item, fields) {
24
- if (item == null) return false
25
- if (!hasChildren(item, fields)) return false
26
- if (fields.isOpen in item) {
27
- return item[fields.isOpen]
28
- }
29
- return false
30
- }
31
-
32
- /**
33
- * Verify if at least one item has children
34
- *
35
- * @param {Array<*>} items
36
- * @param {import('@rokkit/core').FieldMapping} fields
37
- * @returns {boolean}
38
- */
39
- export function isNested(items, fields) {
40
- for (let i = 0; i < items.length; i++) {
41
- if (hasChildren(items[i], fields)) return true
42
- }
43
- return false
44
- }
1
+ import { isExpanded } from '@rokkit/core'
45
2
 
46
3
  /**
47
4
  * Navigate to last visible child in the hirarchy starting with the provided path
@@ -0,0 +1 @@
1
+ export * from './internal'
@@ -0,0 +1,63 @@
1
+ import { compact } from '@rokkit/core'
2
+
3
+ /**
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} [" "]
29
+ */
30
+
31
+ /**
32
+ * Maps keyboard events to actions based on the given handlers and options.
33
+ *
34
+ * @param {ActionHandlers} handlers
35
+ * @param {NavigationOptions} options
36
+ * @returns {KeyboardActions}
37
+ */
38
+ export function mapKeyboardEventsToActions(handlers, options) {
39
+ const { next, previous, select, escape } = handlers
40
+ const { horizontal, nested } = {
41
+ horizontal: false,
42
+ nested: false,
43
+ ...options
44
+ }
45
+ let expand = nested ? handlers.expand : null
46
+ let collapse = nested ? handlers.collapse : null
47
+
48
+ return compact({
49
+ ArrowDown: horizontal ? expand : next,
50
+ ArrowUp: horizontal ? collapse : previous,
51
+ ArrowRight: horizontal ? next : expand,
52
+ ArrowLeft: horizontal ? previous : collapse,
53
+ Enter: select,
54
+ Escape: escape,
55
+ ' ': select
56
+ })
57
+ }
58
+
59
+ export function getClosestAncestorWithAttribute(element, attribute) {
60
+ if (!element) return null
61
+ if (element.getAttribute(attribute)) return element
62
+ return getClosestAncestorWithAttribute(element.parentElement, attribute)
63
+ }
package/src/navigable.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { handleAction, getKeyboardActions } from './utils'
2
+
3
+ const defaultOptions = { horizontal: true, nested: false, enabled: true }
1
4
  /**
2
5
  * A svelte action function that captures keyboard evvents and emits event for corresponding movements.
3
6
  *
@@ -5,45 +8,40 @@
5
8
  * @param {import('./types').NavigableOptions} options
6
9
  * @returns {import('./types').SvelteActionReturn}
7
10
  */
8
- export function navigable(
9
- node,
10
- { horizontal = true, nested = false, enabled = true } = {}
11
- ) {
12
- if (!enabled) return { destroy() {} }
13
- const previous = () => node.dispatchEvent(new CustomEvent('previous'))
14
- const next = () => node.dispatchEvent(new CustomEvent('next'))
15
- const collapse = () => node.dispatchEvent(new CustomEvent('collapse'))
16
- const expand = () => node.dispatchEvent(new CustomEvent('expand'))
17
- const select = () => node.dispatchEvent(new CustomEvent('select'))
11
+ export function navigable(node, options) {
12
+ options = { ...defaultOptions, ...options }
18
13
 
19
- const movement = horizontal
20
- ? { ArrowLeft: previous, ArrowRight: next }
21
- : { ArrowUp: previous, ArrowDown: next }
22
- const change = nested
23
- ? horizontal
24
- ? { ArrowUp: collapse, ArrowDown: expand }
25
- : { ArrowLeft: collapse, ArrowRight: expand }
26
- : {}
27
- const actions = {
28
- Enter: select,
29
- ' ': select,
30
- ...movement,
31
- ...change
14
+ let listening = false
15
+ const handlers = {
16
+ previous: () => node.dispatchEvent(new CustomEvent('previous')),
17
+ next: () => node.dispatchEvent(new CustomEvent('next')),
18
+ collapse: () => node.dispatchEvent(new CustomEvent('collapse')),
19
+ expand: () => node.dispatchEvent(new CustomEvent('expand')),
20
+ select: () => node.dispatchEvent(new CustomEvent('select'))
32
21
  }
33
22
 
34
- function handleKeydown(event) {
35
- if (actions[event.key]) {
36
- event.preventDefault()
37
- event.stopPropagation()
38
- actions[event.key]()
39
- }
23
+ let actions = {} //getKeyboardActions(node, { horizontal, nested })
24
+ const handleKeydown = (event) => handleAction(actions, event)
25
+
26
+ function updateListeners(options) {
27
+ if (options.enabled) actions = getKeyboardActions(node, options, handlers)
28
+
29
+ if (options.enabled && !listening)
30
+ node.addEventListener('keydown', handleKeydown)
31
+ else if (!options.enabled && listening)
32
+ node.removeEventListener('keydown', handleKeydown)
33
+ listening = options.enabled
40
34
  }
41
35
 
42
- node.addEventListener('keydown', handleKeydown)
36
+ updateListeners(options)
43
37
 
44
38
  return {
45
- destroy() {
46
- node.removeEventListener('keydown', handleKeydown)
39
+ update: (config) => {
40
+ options = { ...options, ...config }
41
+ updateListeners(options)
42
+ },
43
+ destroy: () => {
44
+ updateListeners({ enabled: false })
47
45
  }
48
46
  }
49
47
  }
package/src/navigator.js CHANGED
@@ -1,13 +1,11 @@
1
- // import { tick } from 'svelte'
1
+ import { handleAction } from './utils'
2
+ import { isNested, hasChildren, isExpanded } from '@rokkit/core'
2
3
  import {
3
4
  moveNext,
4
5
  movePrevious,
5
- isNested,
6
- hasChildren,
7
6
  pathFromIndices,
8
7
  indicesFromPath,
9
- getCurrentNode,
10
- isExpanded
8
+ getCurrentNode
11
9
  } from './hierarchy'
12
10
 
13
11
  /**
@@ -80,27 +78,14 @@ export function navigator(element, options) {
80
78
  emit('expand', element, indicesFromPath(path), currentNode)
81
79
  }
82
80
  }
81
+ const handlers = { next, previous, select, collapse, expand }
83
82
 
84
83
  update(options)
85
84
 
86
85
  const nested = isNested(items, fields)
87
- const movement = vertical
88
- ? { ArrowDown: next, ArrowUp: previous }
89
- : { ArrowRight: next, ArrowLeft: previous }
90
- const states = !nested
91
- ? {}
92
- : vertical
93
- ? { ArrowRight: expand, ArrowLeft: collapse }
94
- : { ArrowDown: expand, ArrowUp: collapse }
95
- const actions = { ...movement, Enter: select, ...states }
96
-
97
- const handleKeyDown = (event) => {
98
- if (actions[event.key]) {
99
- event.preventDefault()
100
- event.stopPropagation()
101
- actions[event.key]()
102
- }
103
- }
86
+ const actions = mapKeyboardEventsToActions(vertical, nested, handlers)
87
+
88
+ const handleKeyDown = (event) => handleAction(actions, event)
104
89
 
105
90
  const handleClick = (event) => {
106
91
  let target = findParentWithDataPath(event.target, element)
@@ -189,3 +174,27 @@ function emit(event, element, indices, node) {
189
174
  })
190
175
  )
191
176
  }
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/swipeable.js CHANGED
@@ -16,8 +16,7 @@ export function swipeable(
16
16
  minSpeed = 300
17
17
  } = {}
18
18
  ) {
19
- if (!enabled) return { destroy() {} }
20
-
19
+ let listening = false
21
20
  let startX
22
21
  let startY
23
22
  let startTime
@@ -57,17 +56,37 @@ export function swipeable(
57
56
  }
58
57
  }
59
58
 
60
- node.addEventListener('touchstart', touchStart)
61
- node.addEventListener('touchend', touchEnd)
62
- node.addEventListener('mousedown', touchStart)
63
- node.addEventListener('mouseup', touchEnd)
64
-
65
- return {
66
- destroy() {
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) {
67
68
  node.removeEventListener('touchstart', touchStart)
68
69
  node.removeEventListener('touchend', touchEnd)
69
70
  node.removeEventListener('mousedown', touchStart)
70
71
  node.removeEventListener('mouseup', touchEnd)
72
+ listening = false
73
+ }
74
+ }
75
+
76
+ updateListeners(enabled)
77
+
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
85
+
86
+ updateListeners(enabled)
87
+ },
88
+ destroy() {
89
+ updateListeners(false)
71
90
  }
72
91
  }
73
92
  }
@@ -0,0 +1,77 @@
1
+ import {
2
+ getClosestAncestorWithAttribute,
3
+ mapKeyboardEventsToActions
4
+ } from './lib'
5
+
6
+ const defaultOptions = {
7
+ horizontal: false,
8
+ nested: false,
9
+ enabled: true
10
+ }
11
+ export function traversable(element, data) {
12
+ let listening = false
13
+ let options = {}
14
+ let tracker = {}
15
+ let handlers = {}
16
+
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
+ }
25
+
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')
33
+
34
+ if (target) {
35
+ const index = parseInt(target.getAttribute('data-index'))
36
+ tracker.index = index
37
+ actions.select()
38
+ }
39
+ }
40
+ }
41
+
42
+ const configure = (data) => {
43
+ // const valueChanged = options.value !== data.value
44
+ options = { ...options, ...data }
45
+
46
+ listening = setupEventHandlers(element, options, listening, listeners)
47
+ handlers = mapKeyboardEventsToActions(actions, options)
48
+ // if (valueChanged) handleValueChange(element, options)
49
+ }
50
+
51
+ configure({ ...defaultOptions, ...data })
52
+
53
+ return {
54
+ update: configure,
55
+ destroy: () => configure({ enabled: false })
56
+ }
57
+ }
58
+
59
+ function setupEventHandlers(element, options, listening, handlers) {
60
+ const { enabled } = options
61
+
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) {}
package/src/utils.js ADDED
@@ -0,0 +1,24 @@
1
+ export function handleAction(actions, event) {
2
+ if (event.key in actions) {
3
+ event.preventDefault()
4
+ event.stopPropagation()
5
+ actions[event.key]()
6
+ }
7
+ }
8
+
9
+ export function getKeyboardActions(node, options, handlers) {
10
+ const movement = options.horizontal
11
+ ? { ArrowLeft: handlers.previous, ArrowRight: handlers.next }
12
+ : { ArrowUp: handlers.previous, ArrowDown: handlers.next }
13
+ const change = options.nested
14
+ ? options.horizontal
15
+ ? { ArrowUp: handlers.collapse, ArrowDown: handlers.expand }
16
+ : { ArrowLeft: handlers.collapse, ArrowRight: handlers.expand }
17
+ : {}
18
+ return {
19
+ Enter: handlers.select,
20
+ ' ': handlers.select,
21
+ ...movement,
22
+ ...change
23
+ }
24
+ }