@rokkit/states 1.0.0-next.108 → 1.0.0-next.109
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/derive.svelte.js +17 -17
- package/src/list-controller.svelte.js +9 -8
- package/src/nested-controller.svelte.js +39 -17
- package/src/proxy.svelte.js +32 -7
- package/src/traversal.svelte.js +137 -0
- package/src/base-proxy.svelte.js +0 -243
- package/src/list-controller-dup.svelte.js +0 -155
- package/src/list-proxy.svelte.js +0 -127
- package/src/nested-proxy.svelte.js +0 -286
- package/src/node-proxy.svelte.js +0 -293
- package/src/node.js +0 -64
- package/src/tree-filter.svelte.js +0 -33
package/src/node-proxy.svelte.js
DELETED
|
@@ -1,293 +0,0 @@
|
|
|
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
|
-
}
|
package/src/node.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
export class Node {
|
|
2
|
-
#original
|
|
3
|
-
#state
|
|
4
|
-
#mapper
|
|
5
|
-
#children
|
|
6
|
-
|
|
7
|
-
constructor(data, mapper) {
|
|
8
|
-
this.#original = data
|
|
9
|
-
this.#state = {}
|
|
10
|
-
this.#mapper = mapper
|
|
11
|
-
|
|
12
|
-
// Initialize children if they exist
|
|
13
|
-
if (this.#mapper.hasChildren(data)) {
|
|
14
|
-
this.#children = this.#mapper.getChildren(data).map((child) => new Node(child, mapper))
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Create getters/setters for all properties except children
|
|
18
|
-
Object.keys(data).forEach((key) => {
|
|
19
|
-
if (key !== this.#mapper.fields.children) {
|
|
20
|
-
Object.defineProperty(this, key, {
|
|
21
|
-
get: () => this.#state[key] ?? this.#original[key],
|
|
22
|
-
set: (value) => {
|
|
23
|
-
this.#original[key] = value
|
|
24
|
-
this.#state[key] = value
|
|
25
|
-
},
|
|
26
|
-
enumerable: true
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
get children() {
|
|
33
|
-
return this.#children ?? []
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
get original() {
|
|
37
|
-
return this.#original
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
addChild(data) {
|
|
41
|
-
if (!this.#mapper.hasChildren(this.#original)) {
|
|
42
|
-
this.#original[this.#mapper.fields.children] = []
|
|
43
|
-
this.#children = []
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const newNode = new Node(data, this.#mapper)
|
|
47
|
-
this.#original[this.#mapper.fields.children].push(data)
|
|
48
|
-
this.#children.push(newNode)
|
|
49
|
-
|
|
50
|
-
return newNode
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
removeChild(node) {
|
|
54
|
-
const originalChildren = this.#original[this.#mapper.fields.children]
|
|
55
|
-
const index = originalChildren.findIndex((child) => child === node.original)
|
|
56
|
-
|
|
57
|
-
if (index !== -1) {
|
|
58
|
-
originalChildren.splice(index, 1)
|
|
59
|
-
this.#children.splice(index, 1)
|
|
60
|
-
return true
|
|
61
|
-
}
|
|
62
|
-
return false
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { Node } from './node'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Filters a tree structure based on a predicate
|
|
5
|
-
* @param {Array} items - The tree data
|
|
6
|
-
* @param {Function} predicate - Filter function that returns boolean
|
|
7
|
-
* @param {FieldMapper} mapper - FieldMapper instance
|
|
8
|
-
* @returns {Array} Filtered tree
|
|
9
|
-
*/
|
|
10
|
-
export function filterTree(nodes, predicate, mapper) {
|
|
11
|
-
const filterNode = (node) => {
|
|
12
|
-
// If node matches predicate, include it and all its children
|
|
13
|
-
if (predicate(node)) {
|
|
14
|
-
return new Node(node.original, mapper)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// If node has children, check them
|
|
18
|
-
if (node.children.length > 0) {
|
|
19
|
-
const filteredChildren = node.children.map((child) => filterNode(child)).filter(Boolean)
|
|
20
|
-
|
|
21
|
-
if (filteredChildren.length > 0) {
|
|
22
|
-
// Create new node with only matching children
|
|
23
|
-
const filteredData = { ...node.original }
|
|
24
|
-
filteredData[mapper.fields.children] = filteredChildren.map((n) => n.original)
|
|
25
|
-
return new Node(filteredData, mapper)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return null
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return nodes.map((node) => filterNode(node)).filter(Boolean)
|
|
33
|
-
}
|