@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.
@@ -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
- }