@rokkit/actions 1.0.0-next.98 → 1.0.1

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/src/types.js CHANGED
@@ -1,132 +1,63 @@
1
1
  /**
2
- * @typedef SvelteActionReturn
3
- * @property {() => void} destroy
4
- * @property {() => void} [update]
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 FillableData
9
- * @property {string} value
10
- * @property {integer} actualIndex
11
- * @property {integer} expectedIndex
9
+ * @typedef {Object<string, (string[]|RegExp) >} KeyboardConfig
12
10
  */
13
11
 
14
12
  /**
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
13
+ * @typedef {'vertical'|'horizontal'} Direction
19
14
  */
20
15
 
21
16
  /**
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
17
+ * @typedef {Object} NavigatorOptions
18
+ * @property {boolean} enabled - Whether the navigator is enabled
19
+ * @property {Direction} direction - Whether the navigator is vertical or horizontal
20
+ * @property {boolean} multiselect - Whether the navigator supports multiple selections
28
21
  */
29
22
 
30
23
  /**
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
24
+ * @typedef {Object} DataWrapper
25
+ * @property {Function} moveNext
26
+ * @property {Function} movePrev
27
+ * @property {Function} moveFirst
28
+ * @property {Function} moveLast
29
+ * @property {Function} expand
30
+ * @property {Function} collapse
31
+ * @property {Function} select
32
+ * @property {Function} toggleExpansion
36
33
  */
37
34
 
38
35
  /**
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
36
+ * @typedef {Object} NavigatorActions
37
+ * @property {Function} next
38
+ * @property {Function} prev
39
+ * @property {Function} first
40
+ * @property {Function} last
41
+ * @property {Function} expand
42
+ * @property {Function} collapse
43
+ * @property {Function} select
44
44
  */
45
45
 
46
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
47
+ * @typedef {Object} NavigatorConfig
48
+ * @property {Navigator} wrapper - Whether the navigator is enabled
49
+ * @property {NavigatorOptions} options - Whether the navigator is vertical or horizontal
53
50
  */
54
51
 
55
52
  /**
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]
53
+ * @typedef {Object} Controller
54
+ * @property {Function} moveNext
55
+ * @property {Function} movePrev
56
+ * @property {Function} moveFirst
57
+ * @property {Function} moveLast
92
58
  * @property {Function} [expand]
59
+ * @property {Function} [collapse]
60
+ * @property {Function} select
61
+ * @property {Function} extendSelection
62
+ * @property {Function} [toggleExpansion]
93
63
  */
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,3 +1,44 @@
1
+ import { find, toPairs } from 'ramda'
2
+
3
+ // const defaultNavigationOptions = {
4
+ // orientation: 'vertical',
5
+ // dir: 'ltr',
6
+ // nested: false,
7
+ // enabled: true
8
+ // }
9
+ /**
10
+ * Finds the closest ancestor of the given element that has the given attribute.
11
+ *
12
+ * @param {HTMLElement} element
13
+ * @param {string} attribute
14
+ * @returns {HTMLElement|null}
15
+ */
16
+ export function getClosestAncestorWithAttribute(element, attribute) {
17
+ if (!element) return null
18
+ if (element.getAttribute(attribute)) return element
19
+ return getClosestAncestorWithAttribute(element.parentElement, attribute)
20
+ }
21
+
22
+ /**
23
+ * Get the event name for a given key.
24
+ * @param {import('./types.js').KeyboardConfig} keyMapping
25
+ * @param {string} key - The key to match.
26
+ * @returns {string|null} - The event name or null if no match is found.
27
+ */
28
+ export const getEventForKey = (keyMapping, key) => {
29
+ const matchEvent = ([_eventName, keys]) =>
30
+ (Array.isArray(keys) && keys.includes(key)) || (keys instanceof RegExp && keys.test(key))
31
+
32
+ const event = find(matchEvent, toPairs(keyMapping))
33
+ return event ? event[0] : null
34
+ }
35
+
36
+ /*
37
+ * Generic action handler for keyboard events.
38
+ *
39
+ * @param {Record<string, () => void>} actions
40
+ * @param {KeyboardEvent} event
41
+ */
1
42
  export function handleAction(actions, event) {
2
43
  if (event.key in actions) {
3
44
  event.preventDefault()
@@ -6,19 +47,100 @@ export function handleAction(actions, event) {
6
47
  }
7
48
  }
8
49
 
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
- }
50
+ // /**
51
+ // * Maps keys to actions based on the configuration.
52
+ // *
53
+ // * @param {import('./types').NavigableOptions} options
54
+ // * @param {import('./types').NavigableHandlers} handlers
55
+ // */
56
+ // export function getKeyboardActions(options, handlers) {
57
+ // const { horizontal, nested } = options
58
+ // if (!options.enabled) return {}
59
+
60
+ // const common = {
61
+ // Enter: handlers.select,
62
+ // ' ': handlers.select
63
+ // }
64
+ // const movement = horizontal
65
+ // ? { ArrowLeft: handlers.previous, ArrowRight: handlers.next }
66
+ // : { ArrowUp: handlers.previous, ArrowDown: handlers.next }
67
+ // const change = horizontal
68
+ // ? { ArrowUp: handlers.collapse, ArrowDown: handlers.expand }
69
+ // : { ArrowLeft: handlers.collapse, ArrowRight: handlers.expand }
70
+
71
+ // if (nested) return { ...common, ...movement, ...change }
72
+ // return { ...common, ...movement }
73
+ // }
74
+ // Keyboard related functions moved to kbd.js
75
+
76
+ /**
77
+ * Finds and returns an index path based on data-path attribute
78
+ *
79
+ * @param {MouseEvent} event
80
+ * @returns {number[]|null} null or index path array
81
+ */
82
+ export function getPathFromEvent(event) {
83
+ const node = getClosestAncestorWithAttribute(event.target, 'data-path')
84
+ return node?.getAttribute('data-path')
85
+ // return node ? getPathFromKey(node.getAttribute('data-path')) : null
86
+ }
87
+
88
+ // createKeyboardActionMap moved to kbd.js
89
+
90
+ // createModifierKeyboardActionMap moved to kbd.js
91
+ /**
92
+ * Identifies if an element is a collapsible icon
93
+ * @param {HTMLElement} target
94
+ * @returns {boolean}
95
+ */
96
+ function isNodeToggle(target) {
97
+ return (
98
+ target &&
99
+ target.hasAttribute('data-tag-icon') &&
100
+ ['closed', 'opened'].includes(target.getAttribute('data-state'))
101
+ )
102
+ }
103
+
104
+ /**
105
+ * Finds the closest ancestor (or self) that has the given attribute
106
+ * @param {HTMLElement} element
107
+ * @param {string} attribute
108
+ * @returns {HTMLElement|null}
109
+ */
110
+ function findClosestWithAttribute(element, attribute) {
111
+ if (!element) return null
112
+ if (element.hasAttribute && element.hasAttribute(attribute)) return element
113
+ return findClosestWithAttribute(element.parentElement, attribute)
114
+ }
115
+
116
+ /**
117
+ * Identifies if an element or its ancestors is an accordion/tree trigger
118
+ * @param {HTMLElement} target
119
+ * @returns {boolean}
120
+ */
121
+ function isAccordionTrigger(target) {
122
+ if (!target) return false
123
+ const trigger = findClosestWithAttribute(target, 'data-accordion-trigger')
124
+ return trigger !== null
125
+ }
126
+ // getKeyboardAction moved to kbd.js
127
+
128
+ function isToggleTarget(target) {
129
+ return isNodeToggle(target) || isNodeToggle(target.parentElement) || isAccordionTrigger(target)
130
+ }
131
+
132
+ /**
133
+ * Determines an action based on a click event
134
+ *
135
+ * @param {MouseEvent} event - The click event
136
+ * @returns {string} The determined action
137
+ */
138
+ export const getClickAction = (event) => {
139
+ const { ctrlKey, metaKey, shiftKey, target } = event
140
+
141
+ if (shiftKey) return 'range'
142
+ if (ctrlKey) return 'extend'
143
+ if (metaKey) return 'extend'
144
+ if (isToggleTarget(target)) return 'toggle'
145
+ return 'select'
24
146
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2022 Jerry Thomas
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
package/src/hierarchy.js DELETED
@@ -1,156 +0,0 @@
1
- import { isExpanded } from '@rokkit/core'
2
-
3
- /**
4
- * Navigate to last visible child in the hirarchy starting with the provided path
5
- *
6
- * @param {Array<import('./types').PathFragment>} path - path to a node in the hierarchy
7
- * @returns
8
- */
9
- export function navigateToLastVisibleChild(path) {
10
- let current = path[path.length - 1]
11
-
12
- while (isExpanded(current.items[current.index], current.fields)) {
13
- const items = current.items[current.index][current.fields.children]
14
- const level = {
15
- items,
16
- index: items.length - 1,
17
- fields: current.fields.fields ?? current.fields
18
- }
19
- path.push(level)
20
- current = level
21
- }
22
-
23
- return path
24
- }
25
-
26
- /**
27
- * Navigate to the next item
28
- *
29
- * @param {Array<import('./types').PathFragment>} path - path to a node in the hierarchy
30
- * @param {Array<*>} items - array of items
31
- * @param {import('@rokkit/core').FieldMapping} fields - field mapping
32
- * @returns
33
- */
34
- export function moveNext(path, items, fields) {
35
- if (path.length === 0) {
36
- return [{ index: 0, items, fields }]
37
- }
38
-
39
- const current = path[path.length - 1]
40
- if (isExpanded(current.items[current.index], current.fields)) {
41
- path.push({
42
- items: current.items[current.index][current.fields.children],
43
- index: 0,
44
- fields: current.fields.fields || current.fields
45
- })
46
- } else if (current.index < current.items.length - 1) {
47
- current.index++
48
- } else {
49
- path = navigateToNextLevel(path)
50
- }
51
- return path
52
- }
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
- }
72
- /**
73
- * Navigate to the previous item
74
- *
75
- * @param {Array<import('./types').PathFragment>} path - path to a node in the hierarchy
76
- * @returns
77
- */
78
- export function movePrevious(path) {
79
- if (path.length === 0) return []
80
-
81
- const current = path[path.length - 1]
82
-
83
- if (current.index === 0) {
84
- if (path.length > 1) path.pop()
85
- return path
86
- }
87
-
88
- current.index--
89
- if (isExpanded(current.items[current.index], current.fields)) {
90
- return navigateToLastVisibleChild(path)
91
- }
92
- return path
93
- }
94
-
95
- /**
96
- *
97
- * @param {Array<integer>} indices
98
- * @param {Array<*>} items
99
- * @param {import('@rokkit/core').FieldMapping} fields
100
- * @returns
101
- */
102
- export function pathFromIndices(indices, items, fields) {
103
- const path = []
104
- let fragment = {}
105
- indices.forEach((index, level) => {
106
- if (level === 0) {
107
- fragment = { index, items, fields }
108
- } else {
109
- fragment = {
110
- index,
111
- items: fragment.items[fragment.index][fragment.fields.children],
112
- fields: fragment.fields.fields ?? fragment.fields
113
- }
114
- }
115
- path.push(fragment)
116
- })
117
- return path
118
- }
119
-
120
- /**
121
- * Get the indices from the path
122
- * @param {Array<import('./types').PathFragment>} path
123
- * @returns {Array<integer>}
124
- */
125
- export function indicesFromPath(path) {
126
- return path.map(({ index }) => index)
127
- }
128
-
129
- /**
130
- * Get the current node from the path
131
- * @param {Array<import('./types').PathFragment>} path
132
- * @returns {*}
133
- */
134
- export function getCurrentNode(path) {
135
- if (path.length === 0) return null
136
- const lastIndex = path.length - 1
137
- return path[lastIndex].items[path[lastIndex].index]
138
- }
139
-
140
- /**
141
- * Find the item in the hierarchy using the indices
142
- *
143
- * @param {Array<*>} items
144
- * @param {Array<integer>} indices
145
- * @param {import('@rokkit/core').FieldMapping} fields
146
- * @returns {*}
147
- */
148
- export function findItem(items, indices, fields) {
149
- let item = items[indices[0]]
150
- let levelFields = fields
151
- for (let level = 1; level < indices.length; level++) {
152
- item = item[levelFields.children][indices[level]]
153
- levelFields = levelFields.fields ?? levelFields
154
- }
155
- return item
156
- }
@@ -1,35 +0,0 @@
1
- export const dimensionAttributes = {
2
- vertical: {
3
- scroll: 'scrollTop',
4
- offset: 'offsetHeight',
5
- paddingStart: 'paddingTop',
6
- paddingEnd: 'paddingBottom',
7
- size: 'height'
8
- },
9
- horizontal: {
10
- scroll: 'scrollLeft',
11
- offset: 'offsetWidth',
12
- paddingStart: 'paddingLeft',
13
- paddingEnd: 'paddingRight',
14
- size: 'width'
15
- }
16
- }
17
-
18
- export const defaultResizerOptions = {
19
- horizontal: false,
20
- minSize: 40,
21
- minVisible: 1,
22
- maxVisible: null,
23
- availableSize: 200,
24
- start: 0
25
- }
26
-
27
- export const defaultVirtualListOptions = {
28
- itemSelector: 'virtual-list-item',
29
- contentSelector: 'virtual-list-content',
30
- enabled: true,
31
- horizontal: false,
32
- start: 0,
33
- limit: null,
34
- index: null
35
- }
@@ -1,123 +0,0 @@
1
- import { writable, get } from 'svelte/store'
2
- import { pick } from 'ramda'
3
- import {
4
- updateSizes,
5
- calculateSum,
6
- fixViewportForVisibileCount,
7
- fitIndexInViewport
8
- } from './internal'
9
-
10
- export function virtualListViewport(options) {
11
- const { gap = 0 } = options
12
- let { minSize = 40, maxVisible = 0, visibleSize } = options
13
- let current = { lower: 0, upper: 0 }
14
- const bounds = writable({ lower: 0, upper: 0 })
15
- const space = writable({
16
- before: 0,
17
- after: 0
18
- })
19
- let items = null
20
- let averageSize = minSize
21
- let visibleCount = maxVisible
22
- let value = null
23
- let cache = []
24
- let index = -1
25
-
26
- const updateBounds = ({ lower, upper }) => {
27
- const previous = get(bounds)
28
- if (maxVisible > 0) {
29
- const visible = calculateSum(cache, lower, upper, averageSize, gap)
30
- space.update((state) => (state = { ...state, visible }))
31
- }
32
- if (previous.lower !== lower) {
33
- const before = calculateSum(cache, 0, lower, averageSize)
34
- space.update((state) => (state = { ...state, before }))
35
- }
36
- if (previous.upper !== upper) {
37
- const after = calculateSum(cache, upper, cache.length, averageSize)
38
- space.update((state) => (state = { ...state, after }))
39
- }
40
- if (previous.lower !== lower || previous.upper !== upper) {
41
- bounds.set({ lower, upper })
42
- }
43
- }
44
-
45
- const update = (data) => {
46
- // const previous = get(bounds)
47
-
48
- data = {
49
- start: current.lower,
50
- end: current.upper,
51
- value,
52
- ...data
53
- }
54
- items = data.items ?? items
55
- minSize = data.minSize ?? minSize
56
- maxVisible = data.maxVisible ?? maxVisible
57
- visibleSize = data.visibleSize ?? visibleSize
58
-
59
- if (items.length !== cache.length) {
60
- cache = Array.from({ length: items.length }).fill(null)
61
- if (items.length === 0) index = -1
62
- }
63
- current = { lower: data.start, upper: data.end }
64
-
65
- cache = updateSizes(cache, data.sizes ?? [], current.lower)
66
- averageSize =
67
- cache.length === 0
68
- ? minSize
69
- : calculateSum(cache, 0, cache.length, averageSize) / cache.length
70
-
71
- let visible = calculateSum(cache, current.lower, current.upper, averageSize, gap)
72
-
73
- if (maxVisible > 0) {
74
- visibleCount = maxVisible
75
- } else {
76
- while (visible < visibleSize) visible += averageSize
77
- while (visible - averageSize > visibleSize) visible -= averageSize
78
- visibleCount = Math.ceil(visible / averageSize)
79
- }
80
- current = fixViewportForVisibileCount(current, cache.length, visibleCount)
81
-
82
- // recalculate the lower, upper bounds based on current index
83
- if (items.length > 0 && data.value && data.value !== value) {
84
- index = items.findIndex((item) => item === data.value)
85
- if (index > -1) {
86
- value = data.value
87
- current = fitIndexInViewport(index, current, visibleCount)
88
- }
89
- }
90
- updateBounds(current)
91
- }
92
- const moveByOffset = (offset) => {
93
- if (cache.length > 0) {
94
- index = Math.max(0, Math.min(index + offset, cache.length - 1))
95
- current = fitIndexInViewport(index, current, visibleCount)
96
- updateBounds(current)
97
- }
98
- }
99
-
100
- const scrollTo = (position) => {
101
- const start = Math.round(position / averageSize)
102
- if (start !== current.lower) update({ start })
103
- }
104
-
105
- update(options)
106
-
107
- return {
108
- bounds: pick(['subscribe'], bounds),
109
- space: pick(['subscribe'], space),
110
- get index() {
111
- return index
112
- },
113
- update,
114
- scrollTo,
115
- moveByOffset,
116
- next: () => moveByOffset(1),
117
- previous: () => moveByOffset(-1),
118
- nextPage: () => moveByOffset(visibleCount),
119
- previousPage: () => moveByOffset(-visibleCount),
120
- first: () => moveByOffset(-cache.length),
121
- last: () => moveByOffset(cache.length + 1)
122
- }
123
- }