@rokkit/actions 1.0.0-next.100 → 1.0.0-next.101
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/LICENSE +1 -1
- package/README.md +57 -1
- package/package.json +7 -26
- package/src/index.js +2 -12
- package/src/keyboard.svelte.js +58 -0
- package/src/types.js +5 -127
- package/src/utils.js +25 -21
- package/src/delegate.js +0 -34
- package/src/dismissable.js +0 -33
- package/src/fillable.js +0 -106
- package/src/hierarchy.js +0 -156
- package/src/lib/constants.js +0 -35
- package/src/lib/event-manager.js +0 -50
- package/src/lib/index.js +0 -5
- package/src/lib/internal.js +0 -185
- package/src/lib/viewport.js +0 -123
- package/src/navigable.js +0 -46
- package/src/navigator.js +0 -182
- package/src/pannable.js +0 -67
- package/src/swipeable.js +0 -150
- package/src/switchable.js +0 -52
- package/src/themeable.js +0 -42
- package/src/traversable.js +0 -385
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1 +1,57 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Actions
|
|
2
|
+
|
|
3
|
+
This package provides a set of actions that can be used to perform various tasks.
|
|
4
|
+
|
|
5
|
+
## Keyboard
|
|
6
|
+
|
|
7
|
+
The keyboard action can be used to map keyboard events to actions. The following example shows how to map the `K` key combination to an action:
|
|
8
|
+
|
|
9
|
+
The default behavior is to listen to keyup events.
|
|
10
|
+
|
|
11
|
+
- [x] Configuration driven
|
|
12
|
+
- [x] Custom events can be defined
|
|
13
|
+
- [x] Supports mapping an array of keys to an event
|
|
14
|
+
- [x] Supports mapping a regex to an event
|
|
15
|
+
- [ ] Support key modifiers
|
|
16
|
+
- [ ] Support a combination of regex patterns and array of keys
|
|
17
|
+
|
|
18
|
+
Default configuration
|
|
19
|
+
|
|
20
|
+
- _add_: alphabet keys cause an `add` event
|
|
21
|
+
- _submit_: enter causes a `submit` event
|
|
22
|
+
- _cancel_: escape causes a `cancel` event
|
|
23
|
+
- _delete_: backspace or delete causes a `delete` event
|
|
24
|
+
|
|
25
|
+
### Basic Usage
|
|
26
|
+
|
|
27
|
+
```svelte
|
|
28
|
+
<script>
|
|
29
|
+
import { keyboard } from '@fumbl/actions'
|
|
30
|
+
|
|
31
|
+
function handleKey(event) {
|
|
32
|
+
console.log(`${event.detail} pressed`)
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<div use:keyboard onadd={handleKey}></div>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Custom Events
|
|
40
|
+
|
|
41
|
+
```svelte
|
|
42
|
+
<script>
|
|
43
|
+
import { keyboard } from '@fumbl/actions'
|
|
44
|
+
function handleKey(event) {
|
|
45
|
+
console.log(`${event.detail} pressed`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const config = {
|
|
49
|
+
add: ['a', 'b', 'c'],
|
|
50
|
+
submit: 'enter',
|
|
51
|
+
cancel: 'escape',
|
|
52
|
+
delete: ['backspace', 'delete']
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<div use:keyboard={config} onadd={handleKey}></div>
|
|
57
|
+
```
|
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.101",
|
|
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",
|
|
@@ -11,20 +11,6 @@
|
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
|
-
"devDependencies": {
|
|
15
|
-
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
|
16
|
-
"@testing-library/svelte": "^5.2.1",
|
|
17
|
-
"@types/ramda": "^0.30.2",
|
|
18
|
-
"@vitest/coverage-v8": "^2.1.1",
|
|
19
|
-
"@vitest/ui": "~2.1.1",
|
|
20
|
-
"jsdom": "^25.0.0",
|
|
21
|
-
"svelte": "^4.2.19",
|
|
22
|
-
"typescript": "^5.6.2",
|
|
23
|
-
"vite": "^5.4.6",
|
|
24
|
-
"vitest": "~2.1.1",
|
|
25
|
-
"shared-config": "1.0.0-next.100",
|
|
26
|
-
"validators": "1.0.0-next.100"
|
|
27
|
-
},
|
|
28
14
|
"files": [
|
|
29
15
|
"src/**/*.js",
|
|
30
16
|
"src/**/*.svelte"
|
|
@@ -39,18 +25,13 @@
|
|
|
39
25
|
}
|
|
40
26
|
},
|
|
41
27
|
"dependencies": {
|
|
42
|
-
"ramda": "^0.30.1"
|
|
43
|
-
|
|
44
|
-
|
|
28
|
+
"ramda": "^0.30.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@rokkit/helpers": "1.0.0-next.101"
|
|
45
32
|
},
|
|
46
33
|
"scripts": {
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"test:ci": "vitest run",
|
|
50
|
-
"test:ui": "vitest --ui",
|
|
51
|
-
"test": "vitest",
|
|
52
|
-
"coverage": "vitest run --coverage",
|
|
53
|
-
"latest": "pnpm upgrade --latest && pnpm test:ci",
|
|
54
|
-
"release": "pnpm publish --access public"
|
|
34
|
+
"clean": "rm -rf dist",
|
|
35
|
+
"build": "pnpm clean && pnpm prepublishOnly"
|
|
55
36
|
}
|
|
56
37
|
}
|
package/src/index.js
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
1
|
// skipcq: JS-E1004 - Needed for exposing all types
|
|
2
|
-
export * from './types'
|
|
3
|
-
|
|
4
|
-
export * from './lib'
|
|
5
|
-
export { fillable } from './fillable'
|
|
6
|
-
export { pannable } from './pannable'
|
|
7
|
-
export { navigable } from './navigable'
|
|
8
|
-
export { navigator } from './navigator'
|
|
9
|
-
export { dismissable } from './dismissable'
|
|
10
|
-
export { themable } from './themeable'
|
|
11
|
-
export { swipeable } from './swipeable'
|
|
12
|
-
export { switchable } from './switchable'
|
|
13
|
-
export { delegateKeyboardEvents } from './delegate'
|
|
2
|
+
export * from './types.js'
|
|
3
|
+
export { keyboard } from './keyboard.svelte.js'
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { on } from 'svelte/events'
|
|
2
|
+
import { getClosestAncestorWithAttribute, getEventForKey } from './utils.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Default key mappings
|
|
6
|
+
* @type {import('./types.js').KeyboardConfig}
|
|
7
|
+
*/
|
|
8
|
+
const defaultKeyMappings = {
|
|
9
|
+
remove: ['Backspace', 'Delete'],
|
|
10
|
+
submit: ['Enter'],
|
|
11
|
+
add: /^[a-zA-Z]$/
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Handle keyboard events
|
|
16
|
+
*
|
|
17
|
+
* @param {HTMLElement} root
|
|
18
|
+
* @param {import('./types.js').KeyboardConfig} options - Custom key mappings
|
|
19
|
+
*/
|
|
20
|
+
export function keyboard(root, options = defaultKeyMappings) {
|
|
21
|
+
const keyMappings = options || defaultKeyMappings
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Handle keyboard events
|
|
25
|
+
*
|
|
26
|
+
* @param {KeyboardEvent} event
|
|
27
|
+
*/
|
|
28
|
+
const keyup = (event) => {
|
|
29
|
+
const { key } = event
|
|
30
|
+
const eventName = getEventForKey(keyMappings, key)
|
|
31
|
+
|
|
32
|
+
if (eventName) {
|
|
33
|
+
root.dispatchEvent(new CustomEvent(eventName, { detail: key }))
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const click = (event) => {
|
|
38
|
+
const node = getClosestAncestorWithAttribute(event.target, 'data-key')
|
|
39
|
+
|
|
40
|
+
if (node) {
|
|
41
|
+
const key = node.getAttribute('data-key')
|
|
42
|
+
const eventName = getEventForKey(keyMappings, key)
|
|
43
|
+
|
|
44
|
+
if (eventName) {
|
|
45
|
+
root.dispatchEvent(new CustomEvent(eventName, { detail: key }))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
$effect(() => {
|
|
51
|
+
const cleanupKeyupEvent = on(document, 'keyup', keyup)
|
|
52
|
+
const cleanupClickEvent = on(root, 'click', click)
|
|
53
|
+
return () => {
|
|
54
|
+
cleanupKeyupEvent()
|
|
55
|
+
cleanupClickEvent()
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
}
|
package/src/types.js
CHANGED
|
@@ -1,132 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef
|
|
3
|
-
* @property {
|
|
4
|
-
* @property {
|
|
2
|
+
* @typedef {Object} EventMapping
|
|
3
|
+
* @property {string} event - The event name
|
|
4
|
+
* @property {string[]} [keys] - The keys that trigger the event
|
|
5
|
+
* @property {RegExp} [pattern] - The pattern that triggers the event
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* @typedef
|
|
9
|
-
* @property {string} value
|
|
10
|
-
* @property {integer} actualIndex
|
|
11
|
-
* @property {integer} expectedIndex
|
|
9
|
+
* @typedef {Object<string, (string[]|RegExp) >} KeyboardConfig
|
|
12
10
|
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @typedef FillOptions
|
|
16
|
-
* @property {Array<FillableData>} options available options to fill
|
|
17
|
-
* @property {integer} current index of option to be filled
|
|
18
|
-
* @property {boolean} check validate filled values
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* A part of the path to node in hierarchy
|
|
23
|
-
*
|
|
24
|
-
* @typedef PathFragment
|
|
25
|
-
* @property {integer} index - Index to item in array
|
|
26
|
-
* @property {Array<*>} items - Array of items
|
|
27
|
-
* @property {import('@rokkit/core').FieldMapping} fields - Field mapping for the data
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Options for the Navigable action
|
|
32
|
-
* @typedef NavigableOptions
|
|
33
|
-
* @property {boolean} horizontal - Navigate horizontally
|
|
34
|
-
* @property {boolean} nested - Navigate nested items
|
|
35
|
-
* @property {boolean} enabled - Enable navigation
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @typedef NavigatorOptions
|
|
40
|
-
* @property {Array<*>} items - An array containing the data set to navigate
|
|
41
|
-
* @property {boolean} [vertical=true] - Identifies whether navigation shoud be vertical or horizontal
|
|
42
|
-
* @property {string} [idPrefix='id-'] - id prefix used for identifying individual node
|
|
43
|
-
* @property {import('../constants').FieldMapping} fields - Field mapping to identify attributes to be used for state and identification of children
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @typedef SwipeableOptions
|
|
48
|
-
* @property {boolean} horizontal - Swipe horizontally
|
|
49
|
-
* @property {boolean} vertical - Swipe vertically
|
|
50
|
-
* @property {boolean} enabled - Enable swiping
|
|
51
|
-
* @property {number} threshold - Threshold for swipe
|
|
52
|
-
* @property {number} minSpeed - Minimum speed for swipe
|
|
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
|
-
*/
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* @typedef {Object} PushDownOptions
|
|
122
|
-
* @property {string} selector - The CSS selector for the child element to which keyboard events will be forwarded.
|
|
123
|
-
* @property {Array<string>} [events=['keydown', 'keyup', 'keypress']] - The keyboard events to forward.
|
|
124
|
-
*/
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* @typedef {Object} Bounds
|
|
128
|
-
* @property {number} lower
|
|
129
|
-
* @property {number} upper
|
|
130
|
-
*/
|
|
131
|
-
|
|
132
|
-
export default {}
|
package/src/utils.js
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Finds the closest ancestor of the given element that has the given attribute.
|
|
3
|
+
*
|
|
4
|
+
* @param {HTMLElement} element
|
|
5
|
+
* @param {string} attribute
|
|
6
|
+
* @returns {HTMLElement|null}
|
|
7
|
+
*/
|
|
8
|
+
export function getClosestAncestorWithAttribute(element, attribute) {
|
|
9
|
+
if (!element) return null
|
|
10
|
+
if (element.getAttribute(attribute)) return element
|
|
11
|
+
return getClosestAncestorWithAttribute(element.parentElement, attribute)
|
|
7
12
|
}
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
14
|
+
import * as R from 'ramda'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the event name for a given key.
|
|
18
|
+
* @param {import('./types.js').KeyboardConfig} keyMapping
|
|
19
|
+
* @param {string} key - The key to match.
|
|
20
|
+
* @returns {string|null} - The event name or null if no match is found.
|
|
21
|
+
*/
|
|
22
|
+
export const getEventForKey = (keyMapping, key) => {
|
|
23
|
+
const matchEvent = ([eventName, keys]) =>
|
|
24
|
+
(Array.isArray(keys) && keys.includes(key)) || (keys instanceof RegExp && keys.test(key))
|
|
25
|
+
|
|
26
|
+
const event = R.find(matchEvent, R.toPairs(keyMapping))
|
|
27
|
+
return event ? event[0] : null
|
|
24
28
|
}
|
package/src/delegate.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { EventManager } from './lib'
|
|
2
|
-
/**
|
|
3
|
-
* Svelte action function for forwarding keyboard events from a parent element to a child.
|
|
4
|
-
* The child is selected using a CSS selector passed in the options object.
|
|
5
|
-
* Optionally, you can specify which keyboard events you want to forward: "keydown", "keyup", and/or "keypress".
|
|
6
|
-
* By default, all three events are forwarded.
|
|
7
|
-
* The action returns an object with a destroy method.
|
|
8
|
-
* The destroy method removes all event listeners from the parent.
|
|
9
|
-
*
|
|
10
|
-
* @param {HTMLElement} element - The parent element from which keyboard events will be forwarded.
|
|
11
|
-
* @param {import('./types').PushDownOptions} options - The options object.
|
|
12
|
-
* @returns {{destroy: Function}}
|
|
13
|
-
*/
|
|
14
|
-
export function delegateKeyboardEvents(
|
|
15
|
-
element,
|
|
16
|
-
{ selector, events = ['keydown', 'keyup', 'keypress'] }
|
|
17
|
-
) {
|
|
18
|
-
const child = element.querySelector(selector)
|
|
19
|
-
const handlers = {}
|
|
20
|
-
const manager = EventManager(element)
|
|
21
|
-
|
|
22
|
-
function forwardEvent(event) {
|
|
23
|
-
child.dispatchEvent(new KeyboardEvent(event.type, event))
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (child) {
|
|
27
|
-
events.forEach((event) => (handlers[event] = forwardEvent))
|
|
28
|
-
manager.update(handlers)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
destroy: () => manager.reset()
|
|
33
|
-
}
|
|
34
|
-
}
|
package/src/dismissable.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
const KEYCODE_ESC = 27
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A svelte action function that captures clicks outside the element or escape keypress
|
|
5
|
-
* emits a `dismiss` event. This is useful for closing a modal or dropdown.
|
|
6
|
-
*
|
|
7
|
-
* @param {HTMLElement} node
|
|
8
|
-
* @returns {import('./types').SvelteActionReturn}
|
|
9
|
-
*/
|
|
10
|
-
export function dismissable(node) {
|
|
11
|
-
const handleClick = (event) => {
|
|
12
|
-
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
|
13
|
-
node.dispatchEvent(new CustomEvent('dismiss', node))
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
const keyup = (event) => {
|
|
17
|
-
if (event.keyCode === KEYCODE_ESC || event.key === 'Escape') {
|
|
18
|
-
event.stopPropagation()
|
|
19
|
-
|
|
20
|
-
node.dispatchEvent(new CustomEvent('dismiss', node))
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
document.addEventListener('click', handleClick, true)
|
|
25
|
-
document.addEventListener('keyup', keyup, true)
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
destroy() {
|
|
29
|
-
document.removeEventListener('click', handleClick, true)
|
|
30
|
-
document.removeEventListener('keyup', keyup, true)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
package/src/fillable.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Action for filling a <del>?</del> element in html block.
|
|
3
|
-
*
|
|
4
|
-
* @param {HTMLElement} node
|
|
5
|
-
* @param {import('./types').FillOptions} options
|
|
6
|
-
* @returns
|
|
7
|
-
*/
|
|
8
|
-
export function fillable(node, { options, current, check }) {
|
|
9
|
-
const data = { options, current, check }
|
|
10
|
-
const blanks = node.getElementsByTagName('del')
|
|
11
|
-
|
|
12
|
-
function click(event) {
|
|
13
|
-
if (event.target.innerHTML !== '?') {
|
|
14
|
-
clear(event, node)
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
initialize(blanks, click)
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
update(input) {
|
|
22
|
-
data.options = input.options
|
|
23
|
-
data.current = input.current
|
|
24
|
-
data.check = check
|
|
25
|
-
|
|
26
|
-
fill(blanks, data.options, data.current)
|
|
27
|
-
if (data.check) validate(blanks, data)
|
|
28
|
-
},
|
|
29
|
-
destroy() {
|
|
30
|
-
Object.keys(blanks).forEach((ref) => {
|
|
31
|
-
blanks[ref].removeEventListener('click', click)
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Initialize empty fillable element style and add listener for click
|
|
39
|
-
*
|
|
40
|
-
* @param {HTMLCollection} blanks
|
|
41
|
-
* @param {EventListener} click
|
|
42
|
-
*/
|
|
43
|
-
function initialize(blanks, click) {
|
|
44
|
-
Object.keys(blanks).forEach((ref) => {
|
|
45
|
-
blanks[ref].addEventListener('click', click)
|
|
46
|
-
blanks[ref].classList.add('empty')
|
|
47
|
-
blanks[ref].name = `fill-${ref}`
|
|
48
|
-
blanks[ref]['data-index'] = ref
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Fill current blank with provided option
|
|
54
|
-
*
|
|
55
|
-
* @param {HTMLCollection} blanks
|
|
56
|
-
* @param {Array<import('./types.js').FillableData>} options
|
|
57
|
-
* @param {*} current
|
|
58
|
-
*/
|
|
59
|
-
function fill(blanks, options, current) {
|
|
60
|
-
if (current > -1 && current < Object.keys(blanks).length) {
|
|
61
|
-
const index = options.findIndex(({ actualIndex }) => actualIndex === current)
|
|
62
|
-
if (index > -1) {
|
|
63
|
-
blanks[current].innerHTML = options[index].value
|
|
64
|
-
blanks[current].classList.remove('empty')
|
|
65
|
-
blanks[current].classList.add('filled')
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Clear all fillable elements
|
|
72
|
-
*
|
|
73
|
-
* @param {EventListener} event
|
|
74
|
-
* @param {HTMLElement} node
|
|
75
|
-
*/
|
|
76
|
-
function clear(event, node) {
|
|
77
|
-
event.target.innerHTML = '?'
|
|
78
|
-
event.target.classList.remove('filled')
|
|
79
|
-
event.target.classList.remove('pass')
|
|
80
|
-
event.target.classList.remove('fail')
|
|
81
|
-
event.target.classList.add('empty')
|
|
82
|
-
node.dispatchEvent(
|
|
83
|
-
new CustomEvent('remove', {
|
|
84
|
-
detail: {
|
|
85
|
-
index: event.target.name.split('-')[1],
|
|
86
|
-
value: event.target['data-index']
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Validate the filled values
|
|
94
|
-
*
|
|
95
|
-
* @param {HTMLCollection} blanks
|
|
96
|
-
* @param {import('./types').FillOptions} data
|
|
97
|
-
*/
|
|
98
|
-
function validate(blanks, data) {
|
|
99
|
-
Object.keys(blanks).forEach((_, ref) => {
|
|
100
|
-
const index = data.options.findIndex(({ actualIndex }) => actualIndex === ref)
|
|
101
|
-
if (index > -1)
|
|
102
|
-
blanks[ref].classList.add(
|
|
103
|
-
data.options[index].expectedIndex === data.options[index].actualIndex ? 'pass' : 'fail'
|
|
104
|
-
)
|
|
105
|
-
})
|
|
106
|
-
}
|