@rokkit/states 1.0.0-next.106 → 1.0.0-next.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/base-proxy.svelte.js +243 -0
- package/src/derive.svelte.js +58 -0
- package/src/index.js +6 -1
- package/src/list-controller-dup.svelte.js +155 -0
- package/src/list-controller.svelte.js +176 -0
- package/src/list-proxy.svelte.js +127 -0
- package/src/nested-controller.svelte.js +100 -0
- package/src/nested-proxy.svelte.js +286 -0
- package/src/node-proxy.svelte.js +293 -0
- package/src/proxy.svelte.js +59 -0
- package/src/nested.svelte.js +0 -267
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { has, isNil } from 'ramda'
|
|
2
|
+
import { defaultFields } from '@rokkit/core'
|
|
3
|
+
/**
|
|
4
|
+
* Represents an individual node within a data structure
|
|
5
|
+
*/
|
|
6
|
+
export class NodeProxy {
|
|
7
|
+
/** @type {number[]} Path to this node */
|
|
8
|
+
path
|
|
9
|
+
/** @type {number} Depth in the hierarchy */
|
|
10
|
+
depth
|
|
11
|
+
/** @type {NodeProxy|null} Parent node */
|
|
12
|
+
parent = null
|
|
13
|
+
|
|
14
|
+
/** @type {any} Original data item */
|
|
15
|
+
original = $state({})
|
|
16
|
+
/** @type {string} Unique identifier */
|
|
17
|
+
id = $state()
|
|
18
|
+
|
|
19
|
+
/** @type {boolean} Whether this node is expanded */
|
|
20
|
+
expanded = $state(false)
|
|
21
|
+
|
|
22
|
+
/** @type {boolean} Whether this node is selected */
|
|
23
|
+
selected = $state(false)
|
|
24
|
+
|
|
25
|
+
/** @type {boolean} Whether this node has focus */
|
|
26
|
+
focused = $state(false)
|
|
27
|
+
|
|
28
|
+
/** @type {NodeProxy[]} Child nodes */
|
|
29
|
+
children = []
|
|
30
|
+
|
|
31
|
+
#fields = {}
|
|
32
|
+
/**
|
|
33
|
+
* Creates a new NodeProxy
|
|
34
|
+
*
|
|
35
|
+
* @param {any} item - Original data item
|
|
36
|
+
* @param {number[]} path - Path to this node
|
|
37
|
+
* @param {import('./field-mapper.js').FieldMapper} mapper - Field mapper
|
|
38
|
+
* @param {NodeProxy|null} parent - Parent node
|
|
39
|
+
*/
|
|
40
|
+
constructor(item, path, fields, parent = null) {
|
|
41
|
+
this.original = typeof item === 'object' ? item : { text: item }
|
|
42
|
+
this.path = path
|
|
43
|
+
this.depth = path.length - 1
|
|
44
|
+
this.parent = parent
|
|
45
|
+
this.fields = { ...defaultFields, ...fields }
|
|
46
|
+
|
|
47
|
+
this._init()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_init() {
|
|
51
|
+
this.id = has(this.fields.id, this.original)
|
|
52
|
+
? String(this.original[this.fields.id])
|
|
53
|
+
: this.path.join('-')
|
|
54
|
+
this.expanded =
|
|
55
|
+
has(this.fields.expanded, this.original) && Boolean(this.original[this.fields.expanded])
|
|
56
|
+
this.selected =
|
|
57
|
+
has(this.fields.selected, this.original) && Boolean(this.original[this.fields.selected])
|
|
58
|
+
this._refreshAllChildren(this.path)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
set fields(props) {
|
|
62
|
+
this.#fields = { ...defaultFields, ...props }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get fields() {
|
|
66
|
+
return this.#fields
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Gets a mapped attribute from the original item
|
|
71
|
+
*
|
|
72
|
+
* @param {string} fieldName - Name of the field to get
|
|
73
|
+
* @returns {any|null} - The attribute value or null if not found
|
|
74
|
+
*/
|
|
75
|
+
get(fieldName) {
|
|
76
|
+
const mappedField = this.fields[fieldName]
|
|
77
|
+
if (!mappedField || !has(mappedField, this.original)) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
return this.original[mappedField]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get the display text for this node
|
|
85
|
+
* @returns {string}
|
|
86
|
+
*/
|
|
87
|
+
get text() {
|
|
88
|
+
return this.get('text')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the icon for this node
|
|
93
|
+
* @returns {string|null}
|
|
94
|
+
*/
|
|
95
|
+
get icon() {
|
|
96
|
+
return this.get('icon')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get formatted text using a formatter function
|
|
101
|
+
* @param {Function} formatter - Function to format the text
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
104
|
+
formattedText(formatter) {
|
|
105
|
+
const text = this.get('text')
|
|
106
|
+
if (isNil(text)) return ''
|
|
107
|
+
if (typeof formatter !== 'function') return text.toString()
|
|
108
|
+
return formatter(text, this.get('currency'))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Toggles the expanded state of this node
|
|
113
|
+
*/
|
|
114
|
+
toggle() {
|
|
115
|
+
this.expanded = !this.expanded
|
|
116
|
+
|
|
117
|
+
// Update original data if it has the expanded field
|
|
118
|
+
if (has(this.fields.expanded, this.original)) {
|
|
119
|
+
this.original[this.fields.expanded] = this.expanded
|
|
120
|
+
}
|
|
121
|
+
return this
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Checks if this node has children
|
|
126
|
+
* @returns {boolean}
|
|
127
|
+
*/
|
|
128
|
+
hasChildren() {
|
|
129
|
+
return this.children.length > 0
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Expand all children
|
|
134
|
+
*/
|
|
135
|
+
expandAll() {
|
|
136
|
+
if (this.hasChildren()) {
|
|
137
|
+
this.expanded = true
|
|
138
|
+
this.children.forEach((child) => child.expandAll())
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Collapse all children
|
|
144
|
+
*/
|
|
145
|
+
collapseAll() {
|
|
146
|
+
if (this.hasChildren()) {
|
|
147
|
+
this.children.forEach((child) => child.collapseAll())
|
|
148
|
+
this.expanded = false
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Checks if this node is a leaf node (no children)
|
|
154
|
+
* @returns {boolean}
|
|
155
|
+
*/
|
|
156
|
+
isLeaf() {
|
|
157
|
+
return !this.hasChildren()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Gets the path to this node
|
|
162
|
+
* @returns {number[]}
|
|
163
|
+
*/
|
|
164
|
+
getPath() {
|
|
165
|
+
return [...this.path]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Adds a child node proxy from an existing data item.
|
|
170
|
+
* If index is provided, the child is inserted at that index, otherwise it is appended to the end
|
|
171
|
+
* @param {any} childData - Child data to add (already exists in original data)
|
|
172
|
+
* @returns {NodeProxy} - The newly created child node proxy
|
|
173
|
+
*/
|
|
174
|
+
addChild(childData, index = -1) {
|
|
175
|
+
if (this.children.length === 0) {
|
|
176
|
+
this.original[this.fields.children] = []
|
|
177
|
+
}
|
|
178
|
+
if (index < 0 || index > this.children.length) {
|
|
179
|
+
index = this.children.length
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.original[this.fields.children].splice(index, 0, childData)
|
|
183
|
+
this._refreshAllChildren(this.path)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Removes a child node by index
|
|
188
|
+
* @param {number} index - Index of the child to remove
|
|
189
|
+
* @returns {boolean} - Whether the removal was successful
|
|
190
|
+
*/
|
|
191
|
+
removeChild(index) {
|
|
192
|
+
if (index < 0 || index >= this.children.length) {
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
const child = this.original[this.fields.children][index]
|
|
196
|
+
this.original[this.fields.children].splice(index, 1)
|
|
197
|
+
this._refreshAllChildren(this.path)
|
|
198
|
+
return child
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Removes all children from this node
|
|
203
|
+
* @private
|
|
204
|
+
*/
|
|
205
|
+
_removeAllChildren() {
|
|
206
|
+
this.children.forEach((child) => child.destroy())
|
|
207
|
+
this.children = []
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Checks if the original data has children
|
|
212
|
+
* @private
|
|
213
|
+
*/
|
|
214
|
+
_hasChildren() {
|
|
215
|
+
const childAttr = this.fields.children
|
|
216
|
+
return has(childAttr, this.original) && Array.isArray(this.original[childAttr])
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Removes all children from this node
|
|
221
|
+
* @private
|
|
222
|
+
*/
|
|
223
|
+
_refreshAllChildren(path) {
|
|
224
|
+
if (this.children.length > 0) this._removeAllChildren()
|
|
225
|
+
if (this._hasChildren()) {
|
|
226
|
+
const childFields = this.fields.fields ?? this.fields
|
|
227
|
+
this.original[this.fields.children].forEach((child, index) => {
|
|
228
|
+
const childNode = new NodeProxy(child, [...path, index], childFields, this)
|
|
229
|
+
this.children.push(childNode)
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Destroys this node, cleaning up references
|
|
236
|
+
*/
|
|
237
|
+
destroy() {
|
|
238
|
+
// Clean up all children first
|
|
239
|
+
this.children.forEach((child) => child.destroy())
|
|
240
|
+
|
|
241
|
+
// Clear references
|
|
242
|
+
this.children = []
|
|
243
|
+
this.parent = null
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Clear selected, focused states
|
|
248
|
+
*/
|
|
249
|
+
resetStates() {
|
|
250
|
+
this.selected = false
|
|
251
|
+
this.focused = false
|
|
252
|
+
if (this.children.length > 0) {
|
|
253
|
+
this.children.forEach((child) => child.resetStates())
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get/Set the value for this node
|
|
259
|
+
*/
|
|
260
|
+
get value() {
|
|
261
|
+
return this.original
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
set value(newValue) {
|
|
265
|
+
if (typeof newValue === 'object') {
|
|
266
|
+
const removedKeys = Object.keys(this.original).filter(
|
|
267
|
+
(key) => !Object.keys(newValue).includes(key)
|
|
268
|
+
)
|
|
269
|
+
Object.entries(newValue).forEach(([k, v]) => {
|
|
270
|
+
this.original[k] = v
|
|
271
|
+
})
|
|
272
|
+
removedKeys.forEach((key) => {
|
|
273
|
+
delete this.original[key]
|
|
274
|
+
})
|
|
275
|
+
this._refreshAllChildren(this.path)
|
|
276
|
+
} else {
|
|
277
|
+
this.original.text = newValue
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Find nodes matching a criteria
|
|
283
|
+
* @param {function} condition - The condition to match
|
|
284
|
+
* @returns {NodeProxy|null} - First matching node or null if no matches found
|
|
285
|
+
*/
|
|
286
|
+
find(condition) {
|
|
287
|
+
if (condition(this)) return this
|
|
288
|
+
let result = null
|
|
289
|
+
for (let i = 0; i < this.children.length && !result; i++)
|
|
290
|
+
result = this.children[i].find(condition)
|
|
291
|
+
return result
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { defaultFields } from '@rokkit/core'
|
|
2
|
+
import { isNil, has } from 'ramda'
|
|
3
|
+
|
|
4
|
+
export class Proxy {
|
|
5
|
+
#value = $state({})
|
|
6
|
+
#fields = $state(defaultFields)
|
|
7
|
+
|
|
8
|
+
constructor(value, fields) {
|
|
9
|
+
this.#value = typeof value === 'object' ? value : { text: value }
|
|
10
|
+
this.fields = fields
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get fields() {
|
|
14
|
+
return this.#fields
|
|
15
|
+
}
|
|
16
|
+
set fields(value) {
|
|
17
|
+
this.#fields = { ...defaultFields, ...value }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get value() {
|
|
21
|
+
return this.#value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
set value(value) {
|
|
25
|
+
if (typeof value === 'object') {
|
|
26
|
+
const removedKeys = Object.keys(this.#value).filter(
|
|
27
|
+
(key) => !Object.keys(value).includes(key)
|
|
28
|
+
)
|
|
29
|
+
Object.entries(value).forEach(([k, v]) => {
|
|
30
|
+
this.#value[k] = v
|
|
31
|
+
})
|
|
32
|
+
removedKeys.forEach((key) => {
|
|
33
|
+
delete this.#value[key]
|
|
34
|
+
})
|
|
35
|
+
} else {
|
|
36
|
+
this.#value.text = value
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Gets a mapped attribute from the original item
|
|
42
|
+
*
|
|
43
|
+
* @param {string} fieldName - Name of the field to get
|
|
44
|
+
* @returns {any|null} - The attribute value or null if not found
|
|
45
|
+
*/
|
|
46
|
+
get(fieldName) {
|
|
47
|
+
return this.has(fieldName) ? this.value[this.fields[fieldName]] : null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Checks if a mapped attribute exists in the original item
|
|
52
|
+
* @param {string} fieldName - Name of the field to check
|
|
53
|
+
* @returns boolean
|
|
54
|
+
*/
|
|
55
|
+
has(fieldName) {
|
|
56
|
+
const mappedField = this.fields[fieldName]
|
|
57
|
+
return !isNil(mappedField) && has(mappedField, this.value)
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/nested.svelte.js
DELETED
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import { has, equals, pick, omit } from 'ramda'
|
|
2
|
-
import { SvelteMap } from 'svelte/reactivity'
|
|
3
|
-
import { DEFAULT_EVENTS } from './constants'
|
|
4
|
-
import { FieldMapper, getKeyFromPath } from '@rokkit/core'
|
|
5
|
-
|
|
6
|
-
export class DataWrapper {
|
|
7
|
-
/* @type number[] */
|
|
8
|
-
#path = []
|
|
9
|
-
#events = {}
|
|
10
|
-
#init = false
|
|
11
|
-
#keys = null
|
|
12
|
-
#options = { multiselect: false, autoCloseSiblings: false }
|
|
13
|
-
|
|
14
|
-
items = null
|
|
15
|
-
data = $state(null)
|
|
16
|
-
value = $state(null)
|
|
17
|
-
mapping = new FieldMapper()
|
|
18
|
-
currentNode = $state(null)
|
|
19
|
-
selected = new SvelteMap()
|
|
20
|
-
|
|
21
|
-
constructor(items, mapper, value, options = {}) {
|
|
22
|
-
this.items = items
|
|
23
|
-
this.data = items
|
|
24
|
-
if (mapper) this.mapping = mapper
|
|
25
|
-
|
|
26
|
-
this.#events = { ...DEFAULT_EVENTS, ...options.events }
|
|
27
|
-
this.#options = { ...options, ...pick(['multiselect', 'autoCloseSiblings'], options) }
|
|
28
|
-
this.#keys = options.keys || null
|
|
29
|
-
this.moveToValue(value)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
moveToValue(value) {
|
|
33
|
-
this.#init = true
|
|
34
|
-
this.value = value
|
|
35
|
-
this.moveTo(this.findPathToItem(value))
|
|
36
|
-
this.#expandPath(this.#path)
|
|
37
|
-
|
|
38
|
-
this.#init = false
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
#expandPath(path) {
|
|
42
|
-
if (!path.length) return
|
|
43
|
-
for (let i = 0; i < path.length; i++) {
|
|
44
|
-
const item = this.mapping.getItemByPath(this.data, path.slice(0, i + 1))
|
|
45
|
-
if (!this.mapping.isExpanded(item)) {
|
|
46
|
-
this.mapping.toggleExpansion(item)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
#matchItems(a, b) {
|
|
51
|
-
if (this.#keys) {
|
|
52
|
-
return equals(pick(this.#keys, a), pick(this.#keys, b))
|
|
53
|
-
} else {
|
|
54
|
-
return equals(a, b)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Finds an item in a tree structure and returns the path as array of indices
|
|
59
|
-
* @param {*} value - The value to find
|
|
60
|
-
* @param {number[]} parent - The current path being explored
|
|
61
|
-
* @returns {number[]|null} - Array of indices representing path to item, or null if not found
|
|
62
|
-
*/
|
|
63
|
-
findPathToItem(value, parent = []) {
|
|
64
|
-
const children = this.mapping.getChildrenByPath(this.data, parent)
|
|
65
|
-
// Direct child check
|
|
66
|
-
const directIndex = children.findIndex((item) => this.#matchItems(item, value))
|
|
67
|
-
if (directIndex !== -1) {
|
|
68
|
-
return [...parent, directIndex]
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Recursive search in children
|
|
72
|
-
return children.reduce((path, _, index) => {
|
|
73
|
-
if (path.length > 0) return path
|
|
74
|
-
if (!this.mapping.hasChildren(children[index])) return []
|
|
75
|
-
|
|
76
|
-
return this.findPathToItem(value, [...parent, index])
|
|
77
|
-
}, [])
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
#getLastVisibleDescendant(node, nodePath) {
|
|
81
|
-
if (!this.mapping.hasChildren(node) || !this.mapping.isExpanded(node)) {
|
|
82
|
-
return { node, path: nodePath }
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const children = this.mapping.getChildren(node)
|
|
86
|
-
if (children.length === 0) {
|
|
87
|
-
return { node, path: nodePath }
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const lastChildIndex = children.length - 1
|
|
91
|
-
const lastChild = children[lastChildIndex]
|
|
92
|
-
return this.#getLastVisibleDescendant(lastChild, [...nodePath, lastChildIndex])
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
#getPreviousSiblingPath() {
|
|
96
|
-
const currentIndex = this.#path[this.#path.length - 1]
|
|
97
|
-
const prevSiblingPath = [...this.#path.slice(0, -1), currentIndex - 1]
|
|
98
|
-
const prevSibling = this.mapping.getItemByPath(this.data, prevSiblingPath)
|
|
99
|
-
|
|
100
|
-
if (this.mapping.isExpanded(prevSibling)) {
|
|
101
|
-
const { path } = this.#getLastVisibleDescendant(prevSibling, prevSiblingPath)
|
|
102
|
-
return path
|
|
103
|
-
} else {
|
|
104
|
-
return prevSiblingPath
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
#getNextSiblingPath(inputPath) {
|
|
109
|
-
const parentPath = inputPath.slice(0, -1)
|
|
110
|
-
const currentIndex = Number(inputPath[inputPath.length - 1])
|
|
111
|
-
|
|
112
|
-
const siblings = this.mapping.getChildrenByPath(this.data, parentPath)
|
|
113
|
-
if (currentIndex < siblings.length - 1) {
|
|
114
|
-
return [...parentPath, currentIndex + 1]
|
|
115
|
-
} else if (parentPath.length > 0) {
|
|
116
|
-
return this.#getNextSiblingPath(parentPath)
|
|
117
|
-
}
|
|
118
|
-
return null
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
emit(type, data) {
|
|
122
|
-
if (!this.#init && has(type, this.#events)) this.#events[type](data)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
moveTo(path) {
|
|
126
|
-
if (!path) return
|
|
127
|
-
const currentPath = Array.isArray(path) ? path : [path]
|
|
128
|
-
|
|
129
|
-
if (equals(currentPath, this.#path)) return
|
|
130
|
-
|
|
131
|
-
this.#path = currentPath
|
|
132
|
-
if (currentPath.length === 0) {
|
|
133
|
-
this.currentNode = null
|
|
134
|
-
} else {
|
|
135
|
-
this.currentNode = this.mapping.getItemByPath(this.data, currentPath)
|
|
136
|
-
this.emit('move', { path: this.#path, node: this.currentNode })
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
movePrev() {
|
|
140
|
-
let currentPath = [0]
|
|
141
|
-
if (this.#path.length === 0) this.moveTo([0])
|
|
142
|
-
|
|
143
|
-
// Return false if at root level first item
|
|
144
|
-
if (this.#path.length === 1 && this.#path[0] === 0) {
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Get previous sibling index
|
|
149
|
-
const currentIndex = this.#path[this.#path.length - 1]
|
|
150
|
-
if (currentIndex > 0) {
|
|
151
|
-
// Has previous sibling
|
|
152
|
-
currentPath = this.#getPreviousSiblingPath()
|
|
153
|
-
} else {
|
|
154
|
-
// Move to parent
|
|
155
|
-
currentPath = this.#path.slice(0, -1)
|
|
156
|
-
}
|
|
157
|
-
this.moveTo(currentPath)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
moveNext() {
|
|
161
|
-
if (this.#path.length === 0) {
|
|
162
|
-
this.moveTo([0])
|
|
163
|
-
return
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const currentNode = this.currentNode
|
|
167
|
-
|
|
168
|
-
// If current node is expanded and has children, move to first child
|
|
169
|
-
if (this.mapping.isExpanded(currentNode) && this.mapping.hasChildren(currentNode)) {
|
|
170
|
-
this.moveTo([...this.#path, 0])
|
|
171
|
-
return
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Try to move to next sibling
|
|
175
|
-
const nextSiblingPath = this.#getNextSiblingPath(this.#path)
|
|
176
|
-
if (nextSiblingPath) {
|
|
177
|
-
this.moveTo(nextSiblingPath)
|
|
178
|
-
return
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
#collapseParent() {
|
|
183
|
-
this.moveTo(this.#path.slice(0, -1))
|
|
184
|
-
if (this.mapping.isExpanded(this.currentNode)) {
|
|
185
|
-
this.toggleExpansion()
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
collapse() {
|
|
190
|
-
// if not expanded child move to parent?
|
|
191
|
-
if (this.mapping.isExpanded(this.currentNode)) {
|
|
192
|
-
this.toggleExpansion()
|
|
193
|
-
} else if (this.#path.length > 1) {
|
|
194
|
-
this.#collapseParent()
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
expand() {
|
|
199
|
-
if (this.mapping.isExpanded(this.currentNode)) return
|
|
200
|
-
this.toggleExpansion()
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
collapseSiblings() {
|
|
204
|
-
if (!this.#options.autoCloseSiblings || !this.mapping.isExpanded(this.currentNode)) return
|
|
205
|
-
|
|
206
|
-
const parentPath = this.#path.slice(0, -1)
|
|
207
|
-
const siblings = this.mapping.getChildrenByPath(this.data, parentPath)
|
|
208
|
-
const currentIndex = this.#path[this.#path.length - 1]
|
|
209
|
-
|
|
210
|
-
siblings.forEach((sibling, index) => {
|
|
211
|
-
if (currentIndex !== index && this.mapping.isExpanded(sibling)) {
|
|
212
|
-
this.mapping.toggleExpansion(sibling)
|
|
213
|
-
}
|
|
214
|
-
})
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
toggleExpansion() {
|
|
218
|
-
if (!this.currentNode || !this.mapping.hasChildren(this.currentNode)) return
|
|
219
|
-
|
|
220
|
-
const eventType = this.mapping.isExpanded(this.currentNode) ? 'collapse' : 'expand'
|
|
221
|
-
this.mapping.toggleExpansion(this.currentNode)
|
|
222
|
-
this.collapseSiblings()
|
|
223
|
-
this.emit(eventType, { path: this.#path, node: this.currentNode })
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
select(path = null) {
|
|
227
|
-
this.moveTo(path)
|
|
228
|
-
|
|
229
|
-
if (this.currentNode) {
|
|
230
|
-
this.value = this.mapping.getItemByPath(this.data, this.#path)
|
|
231
|
-
this.selected.clear()
|
|
232
|
-
this.selected.set(getKeyFromPath(this.#path), this.currentNode)
|
|
233
|
-
this.emit('select', {
|
|
234
|
-
path: this.#path,
|
|
235
|
-
node: this.currentNode,
|
|
236
|
-
selected: this.selected
|
|
237
|
-
})
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
#toggleSelection() {
|
|
242
|
-
if (!this.currentNode) return
|
|
243
|
-
|
|
244
|
-
const isSelected = this.selected.has(getKeyFromPath(this.#path))
|
|
245
|
-
|
|
246
|
-
if (isSelected) {
|
|
247
|
-
this.selected.delete(getKeyFromPath(this.#path))
|
|
248
|
-
} else {
|
|
249
|
-
this.selected.set(getKeyFromPath(this.#path), this.currentNode)
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
this.emit('select', {
|
|
253
|
-
path: this.#path,
|
|
254
|
-
node: this.currentNode,
|
|
255
|
-
selected: this.selected
|
|
256
|
-
})
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
extendSelection(path = null) {
|
|
260
|
-
this.moveTo(path)
|
|
261
|
-
if (this.#options.multiselect) {
|
|
262
|
-
this.#toggleSelection()
|
|
263
|
-
} else {
|
|
264
|
-
this.select()
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|