@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/README.md +189 -1
- package/package.json +20 -31
- package/src/{delegate.js → delegate.svelte.js} +10 -10
- package/src/{dismissable.js → dismissable.svelte.js} +11 -11
- package/src/{fillable.js → fillable.svelte.js} +54 -45
- package/src/hover-lift.svelte.js +64 -0
- package/src/index.js +19 -12
- package/src/kbd.js +191 -0
- package/src/keyboard.svelte.js +59 -0
- package/src/keymap.js +89 -0
- package/src/lib/event-manager.js +35 -18
- package/src/lib/index.js +0 -2
- package/src/lib/internal.js +0 -152
- package/src/magnetic.svelte.js +63 -0
- package/src/nav-constants.js +61 -0
- package/src/navigable.svelte.js +40 -0
- package/src/navigator.js +241 -151
- package/src/navigator.svelte.js +235 -0
- package/src/{pannable.js → pannable.svelte.js} +38 -39
- package/src/reveal.svelte.js +147 -0
- package/src/ripple.svelte.js +92 -0
- package/src/skinnable.svelte.js +12 -0
- package/src/{swipeable.js → swipeable.svelte.js} +79 -89
- package/src/themable.svelte.js +46 -0
- package/src/tooltip.svelte.js +149 -0
- package/src/trigger.js +126 -0
- package/src/types.js +39 -108
- package/src/utils.js +137 -15
- package/LICENSE +0 -21
- package/src/hierarchy.js +0 -156
- package/src/lib/constants.js +0 -35
- package/src/lib/viewport.js +0 -123
- package/src/navigable.js +0 -46
- package/src/switchable.js +0 -52
- package/src/themeable.js +0 -42
- package/src/traversable.js +0 -385
package/src/types.js
CHANGED
|
@@ -1,132 +1,63 @@
|
|
|
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
11
|
|
|
14
12
|
/**
|
|
15
|
-
* @typedef
|
|
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
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* @
|
|
25
|
-
* @property {
|
|
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
|
-
*
|
|
32
|
-
* @
|
|
33
|
-
* @property {
|
|
34
|
-
* @property {
|
|
35
|
-
* @property {
|
|
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
|
|
40
|
-
* @property {
|
|
41
|
-
* @property {
|
|
42
|
-
* @property {
|
|
43
|
-
* @property {
|
|
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
|
|
48
|
-
* @property {
|
|
49
|
-
* @property {
|
|
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
|
|
57
|
-
* @property {
|
|
58
|
-
* @property {
|
|
59
|
-
* @property {
|
|
60
|
-
* @property {
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
}
|
package/src/lib/constants.js
DELETED
|
@@ -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
|
-
}
|
package/src/lib/viewport.js
DELETED
|
@@ -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
|
-
}
|