@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 +2 -2
- package/src/hierarchy.js +1 -44
- package/src/lib/index.js +1 -0
- package/src/lib/internal.js +63 -0
- package/src/navigable.js +30 -32
- package/src/navigator.js +31 -22
- package/src/swipeable.js +28 -9
- package/src/traversable.js +77 -0
- package/src/utils.js +24 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/actions",
|
|
3
|
-
"version": "1.0.0-next.
|
|
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.
|
|
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
|
package/src/lib/index.js
ADDED
|
@@ -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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
36
|
+
updateListeners(options)
|
|
43
37
|
|
|
44
38
|
return {
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
+
}
|