@rokkit/states 1.0.0-next.107 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/states",
3
- "version": "1.0.0-next.107",
3
+ "version": "1.0.0-next.108",
4
4
  "description": "Contains generic data manipulation functions that can be used in various components.",
5
5
  "author": "Jerry Thomas <me@jerrythomas.name>",
6
6
  "license": "MIT",
@@ -0,0 +1,58 @@
1
+ import { getKeyFromPath, defaultFields, getNestedFields } from '@rokkit/core'
2
+
3
+ /**
4
+ *
5
+ * @param {Array<*>} items
6
+ * @param {import('@rokkit/core').FieldMapping} fields
7
+ * @param {Array<number>} path
8
+ * @returns {Array<{ key: string, value: any }>}
9
+ */
10
+ export function flatVisibleNodes(items, fields = defaultFields, path = []) {
11
+ const data = []
12
+ items.forEach((item, index) => {
13
+ const itemPath = [...path, index]
14
+ const key = getKeyFromPath(itemPath)
15
+ const expanded =
16
+ Array.isArray(item[fields.children]) &&
17
+ item[fields.children].length > 0 &&
18
+ item[fields.expanded]
19
+
20
+ data.push({ key, value: item })
21
+
22
+ if (expanded) {
23
+ const childFields = getNestedFields(fields)
24
+ data.push(...flatVisibleNodes(item[fields.children], childFields, itemPath))
25
+ }
26
+ })
27
+ return data
28
+ }
29
+
30
+ /**
31
+ * Derives a flat lookup table for the given items, using the index path as key
32
+ *
33
+ * @param {Array<*>} items
34
+ * @param {import('@rokkit/core').FieldMapping} fields
35
+ * @param {Array<number>} path
36
+ * @returns {Record<string, { depth: number, item: any }>}
37
+ */
38
+ export function deriveLookup(items, fields = defaultFields, path = []) {
39
+ const lookup = {}
40
+
41
+ items.forEach((item, index) => {
42
+ const itemPath = [...path, index]
43
+ const key = getKeyFromPath(itemPath)
44
+
45
+ lookup[key] = { depth: itemPath.length - 1, value: item }
46
+ const hasChildren =
47
+ typeof item === 'object' &&
48
+ Array.isArray(item[fields.children]) &&
49
+ item[fields.children].length > 0
50
+
51
+ if (hasChildren) {
52
+ const childFields = getNestedFields(fields)
53
+ const result = deriveLookup(item[fields.children], childFields, itemPath)
54
+ Object.assign(lookup, result)
55
+ }
56
+ })
57
+ return lookup
58
+ }
package/src/index.js CHANGED
@@ -1,7 +1,8 @@
1
- export { DataWrapper } from './nested.svelte'
2
1
  export { TableWrapper } from './tabular.svelte'
3
- export { NodeProxy } from './node-proxy.svelte'
4
- export { ListProxy } from './list-proxy.svelte'
5
- export { NestedProxy } from './nested-proxy.svelte'
2
+ // export { NodeProxy } from './node-proxy.svelte'
3
+ // export { ListProxy } from './list-proxy.svelte'
4
+ // export { NestedProxy } from './nested-proxy.svelte'
6
5
  export { Proxy } from './proxy.svelte'
7
6
  export { vibe } from './vibe.svelte'
7
+ export { ListController } from './list-controller.svelte'
8
+ export { NestedController } from './nested-controller.svelte'
@@ -0,0 +1,155 @@
1
+ import { SvelteMap } from 'svelte/reactivity'
2
+ import { FieldMapper } from '@rokkit/core'
3
+ import { isNil, equals } from 'ramda'
4
+ import { getKeyFromPath } from '@rokkit/core'
5
+
6
+ export class ListController {
7
+ items = $state(null)
8
+ selected = new SvelteMap()
9
+ activeItem = $state(null)
10
+ activeIndex = -1
11
+ options = $state({})
12
+
13
+ mappers = $state([])
14
+ data = $state(null)
15
+ lookup = new SvelteMap()
16
+
17
+ constructor(items, value, fields, options) {
18
+ this.items = items
19
+ this.mappers.push(new FieldMapper(fields))
20
+ this.options = { multiSelect: false, ...options }
21
+ this.init(items, value)
22
+ }
23
+
24
+ get isNested() {
25
+ return this.mappers.length > 0
26
+ }
27
+
28
+ /**
29
+ * @protected
30
+ */
31
+ init(items, value) {
32
+ console.log('init', value)
33
+ this.data = items
34
+ this.data.forEach((item, index) => {
35
+ this.lookup.set(getKeyFromPath(index), item)
36
+ })
37
+ this.moveToValue(value)
38
+ }
39
+
40
+ /**
41
+ * @private
42
+ * @param {number|number[]} path
43
+ */
44
+ getIndexFromPath(path) {
45
+ return !isNil(path) && !Array.isArray(path) ? path : path[0]
46
+ }
47
+
48
+ moveToValue(value) {
49
+ if (!value) return false
50
+
51
+ const index = this.data.findIndex((item) => equals(item, value))
52
+ return this.select(index)
53
+ }
54
+ /**
55
+ * @private
56
+ * @param {numeric} index
57
+ */
58
+ setActiveItem(index) {
59
+ this.activeItem = this.data[index]
60
+ }
61
+ /**
62
+ * @private
63
+ * @param {numeric} index
64
+ */
65
+ getItemAtIndex(index) {
66
+ return this.data[index]
67
+ }
68
+
69
+ moveTo(path) {
70
+ const index = this.getIndexFromPath(path)
71
+ console.log('moveTo', path, index)
72
+ if (index >= 0 && index < this.data.length && index !== this.activeIndex) {
73
+ this.activeIndex = index
74
+ this.setActiveItem(index)
75
+ return true
76
+ }
77
+ return false
78
+ }
79
+
80
+ movePrev() {
81
+ if (this.activeIndex < 0) {
82
+ return this.moveTo(0)
83
+ } else if (this.activeIndex > 0) {
84
+ return this.moveTo(this.activeIndex - 1)
85
+ }
86
+ return false
87
+ }
88
+
89
+ moveNext() {
90
+ if (this.activeIndex < this.data.length - 1) {
91
+ return this.moveTo(this.activeIndex + 1)
92
+ }
93
+ return false
94
+ }
95
+
96
+ moveFirst() {
97
+ this.moveTo(0)
98
+ }
99
+
100
+ moveLast() {
101
+ this.moveTo(this.data.length - 1)
102
+ }
103
+
104
+ /**
105
+ * @private
106
+ * @param {number} index
107
+ */
108
+ toggleSelection(path) {
109
+ const key = getKeyFromPath(path)
110
+ const index = this.getIndexFromPath(path)
111
+
112
+ if (this.selected.has(key)) {
113
+ this.selected.delete(key)
114
+ } else {
115
+ this.selected.add(key, this.getItemAtIndex(index))
116
+ }
117
+ return true
118
+ }
119
+
120
+ /**
121
+ *
122
+ * @param {number|number[]} path
123
+ * @returns
124
+ */
125
+ extendSelection(path) {
126
+ const index = isNil(path) ? this.activeIndex : this.getIndexFromPath(path)
127
+ if (index >= 0 && index < this.data.length) return false
128
+
129
+ if (this.options.multiselect) {
130
+ return this.toggleSelection(path)
131
+ } else {
132
+ return this.select(path)
133
+ }
134
+ }
135
+
136
+ /**
137
+ *
138
+ * @param {number|number[]} path
139
+ * @returns
140
+ */
141
+ select(path) {
142
+ const index = isNil(path) ? this.activeIndex : this.getIndexFromPath(path)
143
+ if (index > -1 && index !== this.activeIndex) {
144
+ this.selected.clear()
145
+ this.selected.set(getKeyFromPath(path), this.getItemAtIndex(index))
146
+ }
147
+
148
+ this.moveTo(path)
149
+ return true
150
+ }
151
+
152
+ toggleExpansion() {
153
+ return false
154
+ }
155
+ }
@@ -0,0 +1,176 @@
1
+ import { FieldMapper, defaultFields } from '@rokkit/core'
2
+ import { equals } from 'ramda'
3
+ import { SvelteSet } from 'svelte/reactivity'
4
+ import { flatVisibleNodes } from './derive.svelte'
5
+
6
+ export class ListController {
7
+ items = $state(null)
8
+ fields = defaultFields
9
+ mappers = []
10
+ #options = $state({})
11
+ lookup = new Map()
12
+ selectedKeys = new SvelteSet()
13
+ focusedKey = $state(null)
14
+ #currentIndex = -1
15
+
16
+ selected = $derived(Array.from(this.selectedKeys).map((key) => this.lookup.get(key)))
17
+ focused = $derived(this.lookup.get(this.focusedKey))
18
+ data = $derived(flatVisibleNodes(this.items, this.fields))
19
+
20
+ constructor(items, value, fields, options) {
21
+ this.items = items
22
+ this.fields = { ...defaultFields, ...fields }
23
+ this.mappers.push(new FieldMapper(fields))
24
+ this.#options = { multiselect: false, ...options }
25
+ this.init(items, value)
26
+ }
27
+
28
+ /**
29
+ * @private
30
+ * @param {Array<*>} items
31
+ * @param {*} value
32
+ */
33
+ init(items, value) {
34
+ items.forEach((item, index) => this.lookup.set(String(index), item))
35
+ this.moveToValue(value)
36
+ }
37
+
38
+ get isNested() {
39
+ return this.mappers.length > 1
40
+ }
41
+
42
+ get currentKey() {
43
+ return this.focusedKey
44
+ }
45
+
46
+ /**
47
+ * @private
48
+ * @param {*} value
49
+ * @returns
50
+ */
51
+ findByValue(value) {
52
+ const index = this.data.findIndex((row) => equals(row.value, value))
53
+ return index < 0 ? { index } : { index, ...this.data[index] }
54
+ }
55
+
56
+ /**
57
+ * @private
58
+ * @param {*} value
59
+ * @returns
60
+ */
61
+ moveToValue(value = null) {
62
+ const { index, key } = this.findByValue(value)
63
+
64
+ this.selectedKeys.clear()
65
+ if (index >= 0) {
66
+ this.moveToIndex(index)
67
+ this.selectedKeys.add(key)
68
+ } else {
69
+ this.focusedKey = null
70
+ this.#currentIndex = -1
71
+ }
72
+ return true
73
+ }
74
+
75
+ /**
76
+ *
77
+ * @param {string} path
78
+ * @returns
79
+ */
80
+ moveTo(path) {
81
+ const index = Number(path)
82
+ return this.moveToIndex(index)
83
+ }
84
+
85
+ /**
86
+ * @private
87
+ * @param {number} index
88
+ */
89
+ moveToIndex(index) {
90
+ if (index >= 0 && index < this.data.length && this.#currentIndex !== index) {
91
+ this.#currentIndex = index
92
+ this.focusedKey = this.data[index].key
93
+ return true
94
+ }
95
+ return false
96
+ }
97
+
98
+ movePrev() {
99
+ if (this.#currentIndex > 0) {
100
+ return this.moveToIndex(this.#currentIndex - 1)
101
+ } else if (this.#currentIndex < 0) {
102
+ return this.moveLast()
103
+ }
104
+ return false
105
+ }
106
+
107
+ moveNext() {
108
+ if (this.#currentIndex < this.data.length - 1) {
109
+ return this.moveToIndex(this.#currentIndex + 1)
110
+ }
111
+ return false
112
+ }
113
+
114
+ moveFirst() {
115
+ return this.moveToIndex(0)
116
+ }
117
+
118
+ moveLast() {
119
+ return this.moveToIndex(this.data.length - 1)
120
+ }
121
+
122
+ /**
123
+ * Toggles the selection.
124
+ * @private
125
+ * @param {string} key
126
+ */
127
+ toggleSelection(key) {
128
+ if (this.selectedKeys.has(key)) {
129
+ this.selectedKeys.delete(key)
130
+ } else {
131
+ this.selectedKeys.add(key)
132
+ }
133
+
134
+ return true
135
+ }
136
+
137
+ /**
138
+ *
139
+ * @param {string} selectedKey
140
+ * @returns
141
+ */
142
+ select(selectedKey) {
143
+ const key = selectedKey ?? this.focusedKey
144
+
145
+ if (!this.lookup.has(key)) return false
146
+
147
+ if (this.focusedKey !== key) {
148
+ const { index } = this.findByValue(this.lookup.get(key))
149
+ this.moveToIndex(index)
150
+ }
151
+
152
+ if (!this.selectedKeys.has(key)) {
153
+ this.selectedKeys.clear()
154
+ this.selectedKeys.add(key)
155
+ }
156
+
157
+ return true
158
+ }
159
+
160
+ /**
161
+ *
162
+ * @param {string} selectedKey
163
+ * @returns
164
+ */
165
+ extendSelection(selectedKey) {
166
+ const key = selectedKey ?? this.focusedKey
167
+
168
+ if (!this.lookup.has(key)) return false
169
+
170
+ if (this.#options.multiselect) {
171
+ return this.toggleSelection(key)
172
+ } else {
173
+ return this.select(key)
174
+ }
175
+ }
176
+ }
@@ -0,0 +1,100 @@
1
+ import { getKeyFromPath, getPathFromKey, getNestedFields } from '@rokkit/core'
2
+ import { equals } from 'ramda'
3
+ import { ListController } from './list-controller.svelte'
4
+
5
+ export class NestedController extends ListController {
6
+ constructor(items, value, fields, options) {
7
+ super(items, value, fields, options)
8
+ }
9
+
10
+ /**
11
+ * @protected
12
+ * @param {Object} [value]
13
+ */
14
+ init(items, value) {
15
+ this.createLookup(items)
16
+ if (value) {
17
+ this.ensureVisible(value)
18
+ this.moveToValue(value)
19
+ }
20
+ }
21
+ /**
22
+ * @private
23
+ * @param {Object[]} items
24
+ * @param {number[]} [path]=[]
25
+ */
26
+ createLookup(items, path = []) {
27
+ const depth = path.length
28
+ if (depth >= this.mappers.length) {
29
+ this.mappers.push(this.mappers[depth - 1].getChildMapper())
30
+ }
31
+ const fm = this.mappers[depth]
32
+
33
+ items.forEach((item, index) => {
34
+ const itemPath = [...path, index]
35
+ const key = getKeyFromPath(itemPath)
36
+
37
+ this.lookup.set(key, item)
38
+ if (fm.get('selected', item)) {
39
+ this.selectedKeys.add(key)
40
+ }
41
+
42
+ if (fm.hasChildren(item)) {
43
+ this.createLookup(fm.get('children', item), itemPath)
44
+ }
45
+ })
46
+ }
47
+
48
+ ensureVisible(value) {
49
+ const result = this.lookup.entries().find((entry) => equals(entry[1], value))
50
+ if (!Array.isArray(result)) return false
51
+ const path = getPathFromKey(result[0])
52
+
53
+ for (let i = 1; i < path.length; i++) {
54
+ const nodeKey = getKeyFromPath(path.slice(0, i))
55
+ this.expand(nodeKey)
56
+ }
57
+ return true
58
+ }
59
+
60
+ toggleExpansion(key) {
61
+ if (!this.lookup.has(key)) return false
62
+ const item = this.lookup.get(key)
63
+ const fields = this.fieldsFor(key)
64
+ item[fields.expanded] = !item[this.fields.expanded]
65
+ return true
66
+ }
67
+
68
+ expand(key) {
69
+ const actualKey = key ?? this.focusedKey
70
+ if (!this.lookup.has(actualKey)) return false
71
+ const item = this.lookup.get(actualKey)
72
+ const fields = this.fieldsFor(actualKey)
73
+ item[fields.expanded] = true
74
+
75
+ return true
76
+ }
77
+
78
+ collapse(key) {
79
+ const actualKey = key ?? this.focusedKey
80
+ if (!this.lookup.has(actualKey)) return false
81
+ const item = this.lookup.get(actualKey)
82
+ const fields = this.fieldsFor(actualKey)
83
+ item[fields.expanded] = false
84
+ return true
85
+ }
86
+
87
+ /**
88
+ *
89
+ * @param {*} key
90
+ * @returns
91
+ */
92
+ fieldsFor(key) {
93
+ const path = getPathFromKey(key)
94
+ let fields = this.fields
95
+ for (let i = 1; i < path.length; i++) {
96
+ fields = getNestedFields(fields)
97
+ }
98
+ return fields
99
+ }
100
+ }
@@ -52,9 +52,9 @@ export class NodeProxy {
52
52
  ? String(this.original[this.fields.id])
53
53
  : this.path.join('-')
54
54
  this.expanded =
55
- has(this.fields.isOpen, this.original) && Boolean(this.original[this.fields.isOpen])
55
+ has(this.fields.expanded, this.original) && Boolean(this.original[this.fields.expanded])
56
56
  this.selected =
57
- has(this.fields.isSelected, this.original) && Boolean(this.original[this.fields.isSelected])
57
+ has(this.fields.selected, this.original) && Boolean(this.original[this.fields.selected])
58
58
  this._refreshAllChildren(this.path)
59
59
  }
60
60
 
@@ -114,9 +114,9 @@ export class NodeProxy {
114
114
  toggle() {
115
115
  this.expanded = !this.expanded
116
116
 
117
- // Update original data if it has the isOpen field
118
- if (has(this.fields.isOpen, this.original)) {
119
- this.original[this.fields.isOpen] = this.expanded
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
120
  }
121
121
  return this
122
122
  }
@@ -1,267 +0,0 @@
1
- import { has, equals, pick } 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
- }