@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 +1 -1
- package/src/derive.svelte.js +58 -0
- package/src/index.js +5 -4
- package/src/list-controller-dup.svelte.js +155 -0
- package/src/list-controller.svelte.js +176 -0
- package/src/nested-controller.svelte.js +100 -0
- package/src/node-proxy.svelte.js +5 -5
- package/src/nested.svelte.js +0 -267
package/package.json
CHANGED
|
@@ -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
|
+
}
|
package/src/node-proxy.svelte.js
CHANGED
|
@@ -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.
|
|
55
|
+
has(this.fields.expanded, this.original) && Boolean(this.original[this.fields.expanded])
|
|
56
56
|
this.selected =
|
|
57
|
-
has(this.fields.
|
|
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
|
|
118
|
-
if (has(this.fields.
|
|
119
|
-
this.original[this.fields.
|
|
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
|
}
|
package/src/nested.svelte.js
DELETED
|
@@ -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
|
-
}
|