@rokkit/actions 1.0.0-next.100 → 1.0.0-next.103
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/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/event-manager.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EventManager class to manage event listeners on an element.
|
|
3
|
-
*
|
|
4
|
-
* @param {HTMLElement} element - The element to listen for events on.
|
|
5
|
-
* @param {Object} handlers - An object with event names as keys and event handlers as values.
|
|
6
|
-
* @returns {Object} - An object with methods to activate, reset, and update the event listeners.
|
|
7
|
-
*/
|
|
8
|
-
export function EventManager(element, handlers = {}) {
|
|
9
|
-
let listening = false
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Activate the event listeners.
|
|
13
|
-
*/
|
|
14
|
-
function activate() {
|
|
15
|
-
if (!listening) {
|
|
16
|
-
Object.entries(handlers).forEach(([event, handler]) =>
|
|
17
|
-
element.addEventListener(event, handler)
|
|
18
|
-
)
|
|
19
|
-
listening = true
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Reset the event listeners.
|
|
25
|
-
*/
|
|
26
|
-
function reset() {
|
|
27
|
-
if (listening) {
|
|
28
|
-
Object.entries(handlers).forEach(([event, handler]) =>
|
|
29
|
-
element.removeEventListener(event, handler)
|
|
30
|
-
)
|
|
31
|
-
listening = false
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Update the event listeners.
|
|
37
|
-
*
|
|
38
|
-
* @param {Object} newHandlers - An object with event names as keys and event handlers as values.
|
|
39
|
-
* @param {boolean} enabled - Whether to enable or disable the event listeners.
|
|
40
|
-
*/
|
|
41
|
-
function update(newHandlers = handlers, enabled = true) {
|
|
42
|
-
if (listening !== enabled || handlers !== newHandlers) {
|
|
43
|
-
reset()
|
|
44
|
-
handlers = newHandlers
|
|
45
|
-
if (enabled) activate()
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return { activate, reset, update }
|
|
50
|
-
}
|
package/src/lib/index.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { dimensionAttributes, defaultResizerOptions, defaultVirtualListOptions } from './constants'
|
|
2
|
-
// skipcq: JS-E1004 - Needed for exposing all functions
|
|
3
|
-
export * from './internal'
|
|
4
|
-
export { EventManager } from './event-manager'
|
|
5
|
-
export { virtualListViewport } from './viewport'
|
package/src/lib/internal.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { compact, hasChildren, isExpanded } from '@rokkit/core'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Emits a custom event with the given data.
|
|
5
|
-
*
|
|
6
|
-
* @param {HTMLElement} element
|
|
7
|
-
* @param {string} event
|
|
8
|
-
* @param {*} data
|
|
9
|
-
* @returns {void}
|
|
10
|
-
*/
|
|
11
|
-
export function emit(element, event, data) {
|
|
12
|
-
element.dispatchEvent(new CustomEvent(event, { detail: data }))
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Maps keyboard events to actions based on the given handlers and options.
|
|
17
|
-
*
|
|
18
|
-
* @param {import('../types').ActionHandlers} handlers
|
|
19
|
-
* @param {import('../types').NavigationOptions} options
|
|
20
|
-
* @returns {import('../types').KeyboardActions}
|
|
21
|
-
*/
|
|
22
|
-
export function mapKeyboardEventsToActions(handlers, options) {
|
|
23
|
-
const { next, previous, select, escape } = handlers
|
|
24
|
-
const { horizontal, nested } = {
|
|
25
|
-
horizontal: false,
|
|
26
|
-
nested: false,
|
|
27
|
-
...options
|
|
28
|
-
}
|
|
29
|
-
const expand = nested ? handlers.expand : null
|
|
30
|
-
const collapse = nested ? handlers.collapse : null
|
|
31
|
-
|
|
32
|
-
return compact({
|
|
33
|
-
ArrowDown: horizontal ? expand : next,
|
|
34
|
-
ArrowUp: horizontal ? collapse : previous,
|
|
35
|
-
ArrowRight: horizontal ? next : expand,
|
|
36
|
-
ArrowLeft: horizontal ? previous : collapse,
|
|
37
|
-
Enter: select,
|
|
38
|
-
Escape: escape,
|
|
39
|
-
' ': select
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Finds the closest ancestor of the given element that has the given attribute.
|
|
45
|
-
*
|
|
46
|
-
* @param {HTMLElement} element
|
|
47
|
-
* @param {string} attribute
|
|
48
|
-
* @returns {HTMLElement|null}
|
|
49
|
-
*/
|
|
50
|
-
export function getClosestAncestorWithAttribute(element, attribute) {
|
|
51
|
-
if (!element) return null
|
|
52
|
-
if (element.getAttribute(attribute)) return element
|
|
53
|
-
return getClosestAncestorWithAttribute(element.parentElement, attribute)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Sets up event handlers based on the given options.
|
|
58
|
-
* Returns whether or not the event handlers are listening.
|
|
59
|
-
*
|
|
60
|
-
* @param {HTMLElement} element
|
|
61
|
-
* @param {import('../types').EventHandlers} listeners
|
|
62
|
-
* @param {import('../types').TraversableOptions} options
|
|
63
|
-
* @returns {void}
|
|
64
|
-
*/
|
|
65
|
-
export function setupListeners(element, listeners, options) {
|
|
66
|
-
const { enabled } = { enabled: true, ...options }
|
|
67
|
-
if (enabled) {
|
|
68
|
-
Object.entries(listeners).forEach(([event, listener]) =>
|
|
69
|
-
element.addEventListener(event, listener)
|
|
70
|
-
)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Removes event handlers based on the given options.
|
|
76
|
-
* Returns whether or not the event handlers are listening.
|
|
77
|
-
*
|
|
78
|
-
* @param {HTMLElement} element
|
|
79
|
-
* @param {import('../types').EventHandlers} listeners
|
|
80
|
-
* @returns {void}
|
|
81
|
-
*/
|
|
82
|
-
export function removeListeners(element, listeners) {
|
|
83
|
-
if (listeners) {
|
|
84
|
-
Object.entries(listeners).forEach(([event, listener]) => {
|
|
85
|
-
element.removeEventListener(event, listener)
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Handles the click event.
|
|
92
|
-
* @param {HTMLElement} element - The root element.
|
|
93
|
-
* @param {CurrentItem} current - A reference to the current Item
|
|
94
|
-
* @returns {CurrentItem} The updated current item.
|
|
95
|
-
*/
|
|
96
|
-
export function handleItemClick(element, current) {
|
|
97
|
-
const { item, fields, position } = current
|
|
98
|
-
const detail = { item, position }
|
|
99
|
-
|
|
100
|
-
if (hasChildren(item, fields)) {
|
|
101
|
-
if (isExpanded(item, fields)) {
|
|
102
|
-
item[fields.isOpen] = false
|
|
103
|
-
emit(element, 'collapse', detail)
|
|
104
|
-
} else {
|
|
105
|
-
item[fields.isOpen] = true
|
|
106
|
-
emit(element, 'expand', detail)
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
emit(element, 'select', detail)
|
|
110
|
-
}
|
|
111
|
-
return current
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Caclulates sum of array values between the given bounds.
|
|
116
|
-
* If a value is null, the default size is used.
|
|
117
|
-
*
|
|
118
|
-
* @param {Array<number|null>} sizes
|
|
119
|
-
* @param {number} lower
|
|
120
|
-
* @param {number} upper
|
|
121
|
-
* @param {number} [defaultSize]
|
|
122
|
-
* @returns {number}
|
|
123
|
-
*/
|
|
124
|
-
export function calculateSum(sizes, lower, upper, defaultSize = 40, gap = 0) {
|
|
125
|
-
return (
|
|
126
|
-
sizes
|
|
127
|
-
.slice(lower, upper)
|
|
128
|
-
.map((size) => size ?? defaultSize)
|
|
129
|
-
.reduce((acc, size) => acc + size + gap, 0) - gap
|
|
130
|
-
)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Updates the sizes array with the given values.
|
|
135
|
-
*
|
|
136
|
-
* @param {Array<number|null>} sizes
|
|
137
|
-
* @param {Array<number>} values
|
|
138
|
-
* @param {number} [offset]
|
|
139
|
-
* @returns {Array<number|null>}
|
|
140
|
-
*/
|
|
141
|
-
export function updateSizes(sizes, values, offset = 0) {
|
|
142
|
-
const result = [...sizes.slice(0, offset), ...values, ...sizes.slice(offset + values.length)]
|
|
143
|
-
|
|
144
|
-
return result
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Adjusts the viewport to ensure that the bounds contain the given number of items.
|
|
149
|
-
*
|
|
150
|
-
* @param {import('../types').Bounds} current
|
|
151
|
-
* @param {number} count
|
|
152
|
-
* @param {number} visibleCount
|
|
153
|
-
* @returns {import('../types').Bounds}
|
|
154
|
-
*/
|
|
155
|
-
export function fixViewportForVisibileCount(current, count, visibleCount) {
|
|
156
|
-
let { lower, upper } = current
|
|
157
|
-
if (lower < 0) lower = 0
|
|
158
|
-
if (lower + visibleCount > count) {
|
|
159
|
-
upper = count
|
|
160
|
-
lower = Math.max(0, upper - visibleCount)
|
|
161
|
-
} else if (lower + visibleCount !== upper) {
|
|
162
|
-
upper = lower + visibleCount
|
|
163
|
-
}
|
|
164
|
-
return { lower, upper }
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Adjusts the viewport to ensure the given index is visible.
|
|
169
|
-
*
|
|
170
|
-
* @param {number} index
|
|
171
|
-
* @param {import('../types').Bounds} current
|
|
172
|
-
* @param {number} visibleCount
|
|
173
|
-
* @returns {import('../types').Bounds}
|
|
174
|
-
*/
|
|
175
|
-
export function fitIndexInViewport(index, current, visibleCount) {
|
|
176
|
-
let { lower, upper } = current
|
|
177
|
-
if (index >= upper) {
|
|
178
|
-
upper = index + 1
|
|
179
|
-
lower = upper - visibleCount
|
|
180
|
-
} else if (index < lower) {
|
|
181
|
-
lower = index
|
|
182
|
-
upper = lower + visibleCount
|
|
183
|
-
}
|
|
184
|
-
return { lower, upper }
|
|
185
|
-
}
|
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
|
-
}
|
package/src/navigable.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { handleAction, getKeyboardActions } from './utils'
|
|
2
|
-
|
|
3
|
-
const defaultOptions = { horizontal: true, nested: false, enabled: true }
|
|
4
|
-
/**
|
|
5
|
-
* A svelte action function that captures keyboard evvents and emits event for corresponding movements.
|
|
6
|
-
*
|
|
7
|
-
* @param {HTMLElement} node
|
|
8
|
-
* @param {import('./types').NavigableOptions} options
|
|
9
|
-
* @returns {import('./types').SvelteActionReturn}
|
|
10
|
-
*/
|
|
11
|
-
export function navigable(node, options) {
|
|
12
|
-
options = { ...defaultOptions, ...options }
|
|
13
|
-
|
|
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'))
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let actions = {} //getKeyboardActions(node, { horizontal, nested })
|
|
24
|
-
const handleKeydown = (event) => handleAction(actions, event)
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Update the listeners based on the input configuration.
|
|
28
|
-
* @param {import('./types').NavigableOptions} input
|
|
29
|
-
*/
|
|
30
|
-
function updateListeners(input) {
|
|
31
|
-
options = { ...options, ...input }
|
|
32
|
-
if (listening) node.removeEventListener('keydown', handleKeydown)
|
|
33
|
-
|
|
34
|
-
actions = getKeyboardActions(node, input, handlers)
|
|
35
|
-
if (input.enabled) node.addEventListener('keydown', handleKeydown)
|
|
36
|
-
|
|
37
|
-
listening = input.enabled
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
updateListeners(options)
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
update: (config) => updateListeners(config),
|
|
44
|
-
destroy: () => updateListeners({ enabled: false })
|
|
45
|
-
}
|
|
46
|
-
}
|