@rokkit/ui 1.0.0-next.125 → 1.0.0-next.128
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/README.md +198 -101
- package/package.json +42 -34
- package/src/components/BreadCrumbs.svelte +90 -0
- package/src/components/Button.svelte +93 -0
- package/src/components/ButtonGroup.svelte +18 -0
- package/src/components/Card.svelte +61 -0
- package/src/components/Carousel.svelte +174 -0
- package/src/components/Code.svelte +189 -0
- package/src/components/Connector.svelte +46 -0
- package/src/components/FloatingAction.svelte +334 -0
- package/src/components/FloatingNavigation.svelte +235 -0
- package/src/components/Grid.svelte +128 -0
- package/src/components/ItemContent.svelte +25 -0
- package/src/components/LazyTree.svelte +165 -0
- package/src/components/List.svelte +188 -0
- package/src/components/Menu.svelte +270 -0
- package/src/components/MultiSelect.svelte +369 -0
- package/src/components/PaletteManager.svelte +364 -0
- package/src/components/Pill.svelte +83 -0
- package/src/components/ProgressBar.svelte +31 -0
- package/src/components/Range.svelte +330 -0
- package/src/components/Rating.svelte +101 -0
- package/src/components/Reveal.svelte +58 -0
- package/src/components/SearchFilter.svelte +88 -0
- package/src/components/Select.svelte +396 -0
- package/src/{Shine.svelte → components/Shine.svelte} +29 -21
- package/src/components/Stepper.svelte +172 -0
- package/src/components/Switch.svelte +75 -0
- package/src/components/Table.svelte +242 -0
- package/src/components/Tabs.svelte +192 -0
- package/src/components/Tilt.svelte +68 -0
- package/src/components/Timeline.svelte +61 -0
- package/src/components/Toggle.svelte +93 -0
- package/src/components/Toolbar.svelte +308 -0
- package/src/components/ToolbarGroup.svelte +17 -0
- package/src/components/Tree.svelte +144 -0
- package/src/components/UploadFileStatus.svelte +83 -0
- package/src/components/UploadProgress.svelte +131 -0
- package/src/components/UploadTarget.svelte +124 -0
- package/src/components/index.ts +38 -0
- package/src/index.ts +46 -0
- package/src/types/button.ts +86 -0
- package/src/types/code.ts +46 -0
- package/src/types/floating-action.ts +123 -0
- package/src/types/floating-navigation.ts +80 -0
- package/src/types/index.ts +55 -0
- package/src/types/list.ts +200 -0
- package/src/types/menu.ts +95 -0
- package/src/types/palette.ts +160 -0
- package/src/types/range.ts +51 -0
- package/src/types/search-filter.ts +67 -0
- package/src/types/select.ts +176 -0
- package/src/types/switch.ts +68 -0
- package/src/types/table.ts +210 -0
- package/src/types/tabs.ts +103 -0
- package/src/types/timeline.ts +53 -0
- package/src/types/toggle.ts +68 -0
- package/src/types/toolbar.ts +164 -0
- package/src/types/tree.ts +250 -0
- package/src/types/upload-file-status.ts +45 -0
- package/src/types/upload-progress.ts +111 -0
- package/src/types/upload-target.ts +68 -0
- package/src/utils/palette.ts +582 -0
- package/src/utils/shiki.ts +122 -0
- package/src/utils/upload.js +128 -0
- package/dist/constants.d.ts +0 -2
- package/dist/index.d.ts +0 -41
- package/dist/lib/fields.d.ts +0 -16
- package/dist/lib/form.d.ts +0 -95
- package/dist/lib/index.d.ts +0 -6
- package/dist/lib/layout.d.ts +0 -7
- package/dist/lib/nested.d.ts +0 -48
- package/dist/lib/schema.d.ts +0 -7
- package/dist/lib/select.d.ts +0 -8
- package/dist/lib/tree.d.ts +0 -9
- package/dist/tree/List.spec.svelte.d.ts +0 -1
- package/dist/tree/Node.spec.svelte.d.ts +0 -1
- package/dist/tree/Root.spec.svelte.d.ts +0 -1
- package/dist/types.d.ts +0 -5
- package/dist/wrappers/index.d.ts +0 -3
- package/src/Accordion.svelte +0 -118
- package/src/BreadCrumbs.svelte +0 -32
- package/src/Button.svelte +0 -57
- package/src/Calendar.svelte +0 -93
- package/src/Card.svelte +0 -45
- package/src/Carousel.svelte +0 -49
- package/src/CheckBox.svelte +0 -56
- package/src/Connector.svelte +0 -40
- package/src/DropDown.svelte +0 -68
- package/src/DropSearch.svelte +0 -37
- package/src/Fillable.svelte +0 -19
- package/src/GraphPaper.svelte +0 -43
- package/src/Icon.svelte +0 -81
- package/src/Item.svelte +0 -25
- package/src/Link.svelte +0 -21
- package/src/List.svelte +0 -89
- package/src/ListBody.svelte +0 -43
- package/src/Message.svelte +0 -11
- package/src/MultiSelect.svelte +0 -48
- package/src/NestedList.svelte +0 -78
- package/src/NestedPaginator.svelte +0 -63
- package/src/Node.svelte +0 -76
- package/src/Overlay.svelte +0 -21
- package/src/PageNavigator.svelte +0 -94
- package/src/PickOne.svelte +0 -60
- package/src/Pill.svelte +0 -41
- package/src/ProgressBar.svelte +0 -21
- package/src/ProgressDots.svelte +0 -53
- package/src/RadioGroup.svelte +0 -52
- package/src/Range.svelte +0 -45
- package/src/RangeMinMax.svelte +0 -124
- package/src/RangeSlider.svelte +0 -79
- package/src/RangeTick.svelte +0 -28
- package/src/Rating.svelte +0 -95
- package/src/ResponsiveGrid.svelte +0 -88
- package/src/Scrollable.svelte +0 -7
- package/src/Select.svelte +0 -114
- package/src/Separator.svelte +0 -1
- package/src/Slider.svelte +0 -14
- package/src/SlidingColumns.svelte +0 -50
- package/src/Stage.svelte +0 -41
- package/src/Stepper.svelte +0 -66
- package/src/Summary.svelte +0 -22
- package/src/Switch.svelte +0 -106
- package/src/TableCell.svelte +0 -51
- package/src/TableHeaderCell.svelte +0 -54
- package/src/Tabs.svelte +0 -176
- package/src/Tilt.svelte +0 -66
- package/src/Toggle.svelte +0 -58
- package/src/ToggleThemeMode.svelte +0 -23
- package/src/Tree.svelte +0 -80
- package/src/TreeTable.svelte +0 -171
- package/src/ValidationReport.svelte +0 -23
- package/src/constants.js +0 -4
- package/src/index.js +0 -48
- package/src/lib/fields.js +0 -118
- package/src/lib/form.js +0 -72
- package/src/lib/index.js +0 -13
- package/src/lib/layout.js +0 -63
- package/src/lib/nested.js +0 -192
- package/src/lib/schema.js +0 -32
- package/src/lib/select.js +0 -38
- package/src/lib/tree.js +0 -22
- package/src/tree/List.spec.svelte.js +0 -84
- package/src/tree/List.svelte +0 -78
- package/src/tree/Node.spec.svelte.js +0 -104
- package/src/tree/Node.svelte +0 -80
- package/src/tree/Root.spec.svelte.js +0 -63
- package/src/tree/Root.svelte +0 -81
- package/src/types.js +0 -9
- package/src/wrappers/Category.svelte +0 -27
- package/src/wrappers/Section.svelte +0 -16
- package/src/wrappers/Wrapper.svelte +0 -12
- package/src/wrappers/index.js +0 -3
package/src/lib/nested.js
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { omit, pick } from 'ramda'
|
|
2
|
-
import { isObject } from '@rokkit/core'
|
|
3
|
-
import { typeOf } from '@rokkit/data'
|
|
4
|
-
import { deriveSchemaFromValue } from './schema'
|
|
5
|
-
import { deriveLayoutFromValue } from './layout'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Flattens an object into a flat object
|
|
9
|
-
*
|
|
10
|
-
* @param {Object} input - The object to flatten
|
|
11
|
-
* @param {String} scope - The scope of the object
|
|
12
|
-
*/
|
|
13
|
-
export function flattenObject(input, scope = '#') {
|
|
14
|
-
// eslint-disable-next-line no-use-before-define
|
|
15
|
-
return flattenAttributes(input, scope).reduce(
|
|
16
|
-
// eslint-disable-next-line no-use-before-define
|
|
17
|
-
(acc, item) => ({ ...acc, ...flattenElement(item) }),
|
|
18
|
-
{
|
|
19
|
-
[scope]: {
|
|
20
|
-
type: 'object',
|
|
21
|
-
value: input,
|
|
22
|
-
scope,
|
|
23
|
-
key: scope.split('/').slice(-1)[0]
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Flattens an object into an array of key-value pairs
|
|
30
|
-
*
|
|
31
|
-
* @param {Object} input - The object to flatten
|
|
32
|
-
* @param {String} scope - The scope of the object
|
|
33
|
-
*/
|
|
34
|
-
export function flattenAttributes(input, scope = '#') {
|
|
35
|
-
return Object.entries(input).map(([key, value]) => ({
|
|
36
|
-
key,
|
|
37
|
-
value,
|
|
38
|
-
type: typeOf(value),
|
|
39
|
-
scope: [scope, key].join('/')
|
|
40
|
-
}))
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Derives a nested schema from an object
|
|
45
|
-
*
|
|
46
|
-
* @param {Object} input - The object to derive the schema from
|
|
47
|
-
* @param {String} scope - The scope of the object
|
|
48
|
-
* @returns {Object} The derived schema
|
|
49
|
-
*/
|
|
50
|
-
export function deriveNestedSchema(input, scope = '#') {
|
|
51
|
-
const elements = flattenAttributes(input)
|
|
52
|
-
const atoms = elements.filter(({ type }) => !['object', 'array'].includes(type))
|
|
53
|
-
|
|
54
|
-
const schema = {
|
|
55
|
-
type: 'object'
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (atoms.length > 0) {
|
|
59
|
-
schema.properties = atoms.reduce(
|
|
60
|
-
(acc, { key, type, value }) => ({
|
|
61
|
-
...acc,
|
|
62
|
-
[key]: {
|
|
63
|
-
type,
|
|
64
|
-
default: value
|
|
65
|
-
}
|
|
66
|
-
}),
|
|
67
|
-
{}
|
|
68
|
-
)
|
|
69
|
-
schema.layout = {
|
|
70
|
-
type: 'vertical',
|
|
71
|
-
elements: atoms.map((el) => ({ label: el.key, scope: el.scope }))
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (atoms.length < elements.length) {
|
|
76
|
-
// eslint-disable-next-line no-use-before-define
|
|
77
|
-
schema.children = deriveSchemaForChildren(elements, scope)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (scope !== '#') return schema
|
|
81
|
-
return schema.properties ? [schema] : schema.children
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Derives the children of an object
|
|
86
|
-
*
|
|
87
|
-
* @param {Array} elements - The elements to derive children from
|
|
88
|
-
* @param {String} scope - The scope of the object
|
|
89
|
-
* @returns {Array} The derived children
|
|
90
|
-
*/
|
|
91
|
-
function deriveSchemaForChildren(elements, scope) {
|
|
92
|
-
return [
|
|
93
|
-
...elements
|
|
94
|
-
.filter(({ type }) => type === 'object')
|
|
95
|
-
.map((item) => ({
|
|
96
|
-
...omit(['value', 'scope'], item),
|
|
97
|
-
scope: [scope, item.key].join('/'),
|
|
98
|
-
...deriveNestedSchema(item.value, [scope, item.key].join('/'))
|
|
99
|
-
})),
|
|
100
|
-
...elements
|
|
101
|
-
.filter(({ type }) => type === 'array')
|
|
102
|
-
.map((item) => ({
|
|
103
|
-
...omit(['value'], item),
|
|
104
|
-
default: [],
|
|
105
|
-
scope: [scope, item.key].join('/'),
|
|
106
|
-
items: deriveSchemaFromValue(item.value.length ? item.value[0] : null),
|
|
107
|
-
layout: deriveLayoutFromValue(item.value.length ? item.value[0] : null)
|
|
108
|
-
}))
|
|
109
|
-
]
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Flattens an element into a flat object
|
|
114
|
-
*
|
|
115
|
-
* @param {Object} element - The element to flatten
|
|
116
|
-
*/
|
|
117
|
-
export function flattenElement(element) {
|
|
118
|
-
if (element.type === 'object') {
|
|
119
|
-
return flattenObject(element.value, element.scope)
|
|
120
|
-
} else if (element.type === 'array') {
|
|
121
|
-
return element.value
|
|
122
|
-
.map((item, index) => ({
|
|
123
|
-
value: item,
|
|
124
|
-
scope: [element.scope, `[${index}]`].join('/'),
|
|
125
|
-
key: `[${index}]`,
|
|
126
|
-
type: typeOf(item)
|
|
127
|
-
}))
|
|
128
|
-
.reduce((acc, item) => ({ ...acc, ...flattenElement(item) }), {
|
|
129
|
-
[element.scope]: pick(['key', 'type', 'scope', 'value'], element)
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
return { [element.scope]: element }
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Generates an index array referencing the input data
|
|
137
|
-
*
|
|
138
|
-
* @param {Object} data - The flat object to index
|
|
139
|
-
* @param {String} key - The key to use as index
|
|
140
|
-
*/
|
|
141
|
-
export function generateIndex(data, key = 'scope') {
|
|
142
|
-
const index = data
|
|
143
|
-
.map((item) => ({
|
|
144
|
-
...item,
|
|
145
|
-
_path: item[key],
|
|
146
|
-
_isParent: false,
|
|
147
|
-
_isExpanded: true,
|
|
148
|
-
_levels: []
|
|
149
|
-
}))
|
|
150
|
-
.sort((a, b) => a[key].localeCompare(b[key]))
|
|
151
|
-
.filter((item) => item[key] !== '#')
|
|
152
|
-
|
|
153
|
-
let levels = [0]
|
|
154
|
-
let current = 0
|
|
155
|
-
|
|
156
|
-
index.forEach((item, row) => {
|
|
157
|
-
const path = item._path.split('/').slice(1)
|
|
158
|
-
item._depth = path.length - 1
|
|
159
|
-
if (row === 0) {
|
|
160
|
-
item._levels = [0]
|
|
161
|
-
} else if (path.length > levels.length) {
|
|
162
|
-
index[row - 1]._isParent = true
|
|
163
|
-
item._levels = [...levels, 0]
|
|
164
|
-
} else {
|
|
165
|
-
current = levels[path.length - 1] + 1
|
|
166
|
-
item._levels = [...levels.slice(0, path.length - 1), current]
|
|
167
|
-
}
|
|
168
|
-
levels = item._levels
|
|
169
|
-
})
|
|
170
|
-
return index
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Generates a tree table from the input data
|
|
175
|
-
*
|
|
176
|
-
* @param {Object} data - The data to generate the tree table from
|
|
177
|
-
* @param {String} key - The key to use as index
|
|
178
|
-
* @param {Boolean} ellipsis - Whether to truncate the value
|
|
179
|
-
*/
|
|
180
|
-
export function generateTreeTable(data, key = 'scope', ellipsis = false) {
|
|
181
|
-
let result = []
|
|
182
|
-
if (Array.isArray(data)) result = generateIndex(data, key)
|
|
183
|
-
if (isObject(data)) result = generateIndex(Object.values(flattenObject(data)), key)
|
|
184
|
-
|
|
185
|
-
if (ellipsis) {
|
|
186
|
-
result = result.map((item) => ({
|
|
187
|
-
...omit(['value'], item),
|
|
188
|
-
value: ['array', 'object'].includes(item.type) ? '...' : item.value
|
|
189
|
-
}))
|
|
190
|
-
}
|
|
191
|
-
return result
|
|
192
|
-
}
|
package/src/lib/schema.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { typeOf } from '@rokkit/data'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Derives a schema for properties of an object.
|
|
5
|
-
*
|
|
6
|
-
* @param {Object} data
|
|
7
|
-
* @returns
|
|
8
|
-
*/
|
|
9
|
-
function deriveObjectProperties(data) {
|
|
10
|
-
const properties = {}
|
|
11
|
-
for (const [key, value] of Object.entries(data)) {
|
|
12
|
-
// eslint-disable-next-line no-use-before-define
|
|
13
|
-
properties[key] = deriveSchemaFromValue(value)
|
|
14
|
-
}
|
|
15
|
-
return properties
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Derives a schema from a given value.
|
|
20
|
-
*
|
|
21
|
-
* @param {any} data
|
|
22
|
-
* @returns {import('../types').DataSchema}
|
|
23
|
-
*/
|
|
24
|
-
export function deriveSchemaFromValue(data) {
|
|
25
|
-
const schema = { type: typeOf(data) }
|
|
26
|
-
if (schema.type === 'array') {
|
|
27
|
-
schema.items = deriveSchemaFromValue(data.length > 0 ? data[0] : {})
|
|
28
|
-
} else if (schema.type === 'object') {
|
|
29
|
-
schema.properties = deriveObjectProperties(data)
|
|
30
|
-
}
|
|
31
|
-
return schema
|
|
32
|
-
}
|
package/src/lib/select.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate the CSS position properties and values based on the anchor and viewport dimensions.
|
|
3
|
-
*
|
|
4
|
-
* @param {DOMRect} bounds - The bounding rectangle of the anchor element.
|
|
5
|
-
* @param {HTMLElement} viewport - The viewport element that determines the position.
|
|
6
|
-
* @returns {string} - A string with CSS position properties and values.
|
|
7
|
-
*/
|
|
8
|
-
function generatePositionCSS(bounds, viewport) {
|
|
9
|
-
const { width: viewportWidth, height: viewportHeight } = viewport.getBoundingClientRect()
|
|
10
|
-
let pos = ''
|
|
11
|
-
if (bounds.left + viewportWidth > window.innerWidth) {
|
|
12
|
-
pos += `right: ${window.innerWidth - bounds.left - bounds.width}px;`
|
|
13
|
-
} else pos += `left: ${bounds.left}px;`
|
|
14
|
-
|
|
15
|
-
if (bounds.top + viewportHeight > window.innerHeight) {
|
|
16
|
-
pos += `bottom: ${window.innerHeight - bounds.top}px;`
|
|
17
|
-
} else pos += `top: ${bounds.top + bounds.height}px;`
|
|
18
|
-
|
|
19
|
-
return pos
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Get the optimal position for the list based on anchor and viewport dimensions.
|
|
24
|
-
*
|
|
25
|
-
* @param {HTMLElement} anchor - The anchor element to position the list relative to.
|
|
26
|
-
* @param {HTMLElement} viewport - The viewport element that determines the position.
|
|
27
|
-
* @returns {string} - A string with CSS position properties and values.
|
|
28
|
-
*/
|
|
29
|
-
export function getListPosition(anchor, viewport) {
|
|
30
|
-
if (typeof window === 'undefined' || !anchor || !viewport) return ''
|
|
31
|
-
|
|
32
|
-
const bounds = anchor.getBoundingClientRect()
|
|
33
|
-
|
|
34
|
-
bounds.top += window.scrollX
|
|
35
|
-
bounds.left += window.scrollY
|
|
36
|
-
|
|
37
|
-
return generatePositionCSS(bounds, viewport)
|
|
38
|
-
}
|
package/src/lib/tree.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { defaultMapping } from '../constants'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Adds a root node to the items array converting it into a nested tree with one root node
|
|
5
|
-
*
|
|
6
|
-
* @param {Array<Object>} items
|
|
7
|
-
* @param {string} root
|
|
8
|
-
* @param {import('@rokkit/core').FieldMapping} fields
|
|
9
|
-
* @returns
|
|
10
|
-
*/
|
|
11
|
-
export function addRootNode(items, root = '/', mapping = defaultMapping) {
|
|
12
|
-
if (items.length > 1 && root) {
|
|
13
|
-
return [
|
|
14
|
-
{
|
|
15
|
-
[mapping.fields.text]: root,
|
|
16
|
-
[mapping.fields.expanded]: true,
|
|
17
|
-
[mapping.fields.children]: items
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
}
|
|
21
|
-
return items
|
|
22
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
2
|
-
import { cleanup, render, fireEvent } from '@testing-library/svelte'
|
|
3
|
-
import List from './List.svelte'
|
|
4
|
-
|
|
5
|
-
const fields = {
|
|
6
|
-
children: 'children',
|
|
7
|
-
expanded: 'expanded',
|
|
8
|
-
text: 'text'
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function makeTreeData() {
|
|
12
|
-
return [
|
|
13
|
-
{
|
|
14
|
-
text: 'Root 1',
|
|
15
|
-
expanded: true,
|
|
16
|
-
children: [
|
|
17
|
-
{ text: 'Child 1.1', expanded: false, children: [] },
|
|
18
|
-
{ text: 'Child 1.2', expanded: false, children: [] }
|
|
19
|
-
]
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
text: 'Root 2',
|
|
23
|
-
expanded: false,
|
|
24
|
-
children: [{ text: 'Child 2.1', expanded: false, children: [] }]
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe('List.svelte', () => {
|
|
30
|
-
beforeEach(() => cleanup())
|
|
31
|
-
|
|
32
|
-
it('renders all root nodes', () => {
|
|
33
|
-
const items = makeTreeData()
|
|
34
|
-
const { container, getByText } = render(List, { props: { items, fields } })
|
|
35
|
-
expect(getByText('Root 1')).toBeTruthy()
|
|
36
|
-
expect(getByText('Root 2')).toBeTruthy()
|
|
37
|
-
expect(container).toMatchSnapshot()
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('renders children when expanded', () => {
|
|
41
|
-
const items = makeTreeData()
|
|
42
|
-
const { container, getByText } = render(List, { props: { items, fields } })
|
|
43
|
-
expect(getByText('Child 1.1')).toBeTruthy()
|
|
44
|
-
expect(getByText('Child 1.2')).toBeTruthy()
|
|
45
|
-
// Child 2.1 should not be rendered because Root 2 is not expanded
|
|
46
|
-
expect(() => getByText('Child 2.1')).toThrow()
|
|
47
|
-
expect(container).toMatchSnapshot()
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('renders leaf and branch data attributes', () => {
|
|
51
|
-
const items = makeTreeData()
|
|
52
|
-
const { container } = render(List, { props: { items, fields } })
|
|
53
|
-
const nodes = container.querySelectorAll('[data-tree-node]')
|
|
54
|
-
expect(nodes.length).toBeGreaterThan(0)
|
|
55
|
-
// At least one branch and one leaf
|
|
56
|
-
const branch = container.querySelector('[data-tree-branch]')
|
|
57
|
-
const leaf = container.querySelector('[data-tree-leaf]')
|
|
58
|
-
expect(branch).toBeTruthy()
|
|
59
|
-
expect(leaf).toBeTruthy()
|
|
60
|
-
expect(container).toMatchSnapshot()
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('passes focused and selected props', () => {
|
|
64
|
-
const items = makeTreeData()
|
|
65
|
-
const selectedKeys = new Set(['0'])
|
|
66
|
-
const { container } = render(List, {
|
|
67
|
-
props: { items, fields, focusedKey: '0', selectedKeys }
|
|
68
|
-
})
|
|
69
|
-
const focusedNode = container.querySelector('[data-tree-node][aria-current="true"]')
|
|
70
|
-
const selectedNode = container.querySelector('[data-tree-node][aria-selected="true"]')
|
|
71
|
-
expect(focusedNode).toBeTruthy()
|
|
72
|
-
expect(selectedNode).toBeTruthy()
|
|
73
|
-
expect(container).toMatchSnapshot()
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('renders nested lists recursively', () => {
|
|
77
|
-
const items = makeTreeData()
|
|
78
|
-
const { container } = render(List, { props: { items, fields } })
|
|
79
|
-
// Should have nested data-tree-list elements
|
|
80
|
-
const lists = container.querySelectorAll('[data-tree-list]')
|
|
81
|
-
expect(lists.length).toBeGreaterThan(1)
|
|
82
|
-
expect(container).toMatchSnapshot()
|
|
83
|
-
})
|
|
84
|
-
})
|
package/src/tree/List.svelte
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import Node from './Node.svelte'
|
|
3
|
-
import List from './List.svelte'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @typedef {Object} Props
|
|
7
|
-
* @property {Array<any>} items
|
|
8
|
-
* @property {any} value
|
|
9
|
-
* @property {Object} fields
|
|
10
|
-
* @property {Array<number>} [path]
|
|
11
|
-
* @property {Object} icons
|
|
12
|
-
* @property {Array<string>} [types]
|
|
13
|
-
* @property {string} [focusedKey]
|
|
14
|
-
* @property {Set<string>} [selectedKeys]
|
|
15
|
-
* @property {Function} [stub]
|
|
16
|
-
* @property {Object<string, Function>} [snippets]
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/** @type {Props} */
|
|
20
|
-
let {
|
|
21
|
-
items = $bindable([]),
|
|
22
|
-
value = $bindable(null),
|
|
23
|
-
fields,
|
|
24
|
-
path = [],
|
|
25
|
-
icons = {},
|
|
26
|
-
types = [],
|
|
27
|
-
focusedKey,
|
|
28
|
-
selectedKeys = new Set(),
|
|
29
|
-
stub,
|
|
30
|
-
snippets
|
|
31
|
-
} = $props()
|
|
32
|
-
|
|
33
|
-
// Helper to check if a node has children
|
|
34
|
-
function hasChildren(node, fields) {
|
|
35
|
-
return Array.isArray(node?.[fields.children]) && node[fields.children].length > 0
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Helper to get key from path
|
|
39
|
-
function getKeyFromPath(path) {
|
|
40
|
-
return path.join('-')
|
|
41
|
-
}
|
|
42
|
-
</script>
|
|
43
|
-
|
|
44
|
-
<div data-tree-list role="group">
|
|
45
|
-
{#each items as item, index (index)}
|
|
46
|
-
{@const nodePath = [...path, index]}
|
|
47
|
-
{@const key = getKeyFromPath(nodePath)}
|
|
48
|
-
{@const expanded = item[fields.expanded]}
|
|
49
|
-
{@const isBranch = hasChildren(item, fields)}
|
|
50
|
-
<Node
|
|
51
|
-
value={item}
|
|
52
|
-
{fields}
|
|
53
|
-
{icons}
|
|
54
|
-
{types}
|
|
55
|
-
focused={focusedKey === key}
|
|
56
|
-
selected={selectedKeys.has(key)}
|
|
57
|
-
{expanded}
|
|
58
|
-
path={nodePath}
|
|
59
|
-
{stub}
|
|
60
|
-
{snippets}
|
|
61
|
-
>
|
|
62
|
-
{#if isBranch && expanded}
|
|
63
|
-
<List
|
|
64
|
-
items={item[fields.children]}
|
|
65
|
-
{value}
|
|
66
|
-
{fields}
|
|
67
|
-
path={nodePath}
|
|
68
|
-
{icons}
|
|
69
|
-
{types}
|
|
70
|
-
{focusedKey}
|
|
71
|
-
{selectedKeys}
|
|
72
|
-
{stub}
|
|
73
|
-
{snippets}
|
|
74
|
-
/>
|
|
75
|
-
{/if}
|
|
76
|
-
</Node>
|
|
77
|
-
{/each}
|
|
78
|
-
</div>
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
-
import { cleanup, render, fireEvent } from '@testing-library/svelte'
|
|
3
|
-
import Node from './Node.svelte'
|
|
4
|
-
|
|
5
|
-
describe('Node.svelte', () => {
|
|
6
|
-
const fields = {
|
|
7
|
-
text: 'label',
|
|
8
|
-
children: 'children',
|
|
9
|
-
expanded: 'expanded',
|
|
10
|
-
snippet: 'snippet'
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const baseNode = {
|
|
14
|
-
label: 'Root Node',
|
|
15
|
-
expanded: false,
|
|
16
|
-
children: []
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
beforeEach(() => cleanup())
|
|
20
|
-
|
|
21
|
-
it('renders node with label', () => {
|
|
22
|
-
const { container, getByText } = render(Node, {
|
|
23
|
-
props: {
|
|
24
|
-
value: baseNode,
|
|
25
|
-
fields,
|
|
26
|
-
path: [0],
|
|
27
|
-
focused: false,
|
|
28
|
-
selected: false,
|
|
29
|
-
expanded: false
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
const contentEl = container.querySelector('[data-tree-content]')
|
|
33
|
-
expect(contentEl).not.toBeNull()
|
|
34
|
-
expect(contentEl.textContent).toContain('Root Node')
|
|
35
|
-
expect(container).toMatchSnapshot()
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('sets data-tree-leaf for leaf node', () => {
|
|
39
|
-
const { container } = render(Node, {
|
|
40
|
-
props: {
|
|
41
|
-
value: baseNode,
|
|
42
|
-
fields,
|
|
43
|
-
path: [0]
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
const nodeDiv = container.querySelector('[data-tree-node]')
|
|
47
|
-
expect(nodeDiv.getAttribute('data-tree-leaf')).toBe('true')
|
|
48
|
-
expect(nodeDiv.getAttribute('data-tree-branch')).toBeNull()
|
|
49
|
-
expect(container).toMatchSnapshot()
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('sets data-tree-branch for branch node', () => {
|
|
53
|
-
const branchNode = {
|
|
54
|
-
label: 'Branch Node',
|
|
55
|
-
expanded: true,
|
|
56
|
-
children: [{ label: 'Child', expanded: false, children: [] }]
|
|
57
|
-
}
|
|
58
|
-
const { container } = render(Node, {
|
|
59
|
-
props: {
|
|
60
|
-
value: branchNode,
|
|
61
|
-
fields,
|
|
62
|
-
path: [0]
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
const nodeDiv = container.querySelector('[data-tree-node]')
|
|
66
|
-
expect(nodeDiv.getAttribute('data-tree-branch')).toBe('true')
|
|
67
|
-
expect(nodeDiv.getAttribute('data-tree-leaf')).toBeNull()
|
|
68
|
-
expect(container).toMatchSnapshot()
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('sets aria attributes correctly', () => {
|
|
72
|
-
const { container } = render(Node, {
|
|
73
|
-
props: {
|
|
74
|
-
value: baseNode,
|
|
75
|
-
fields,
|
|
76
|
-
path: [0],
|
|
77
|
-
focused: true,
|
|
78
|
-
selected: true,
|
|
79
|
-
expanded: true
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
const nodeDiv = container.querySelector('[data-tree-node]')
|
|
83
|
-
expect(nodeDiv.getAttribute('aria-current')).toBe('true')
|
|
84
|
-
expect(nodeDiv.getAttribute('aria-selected')).toBe('true')
|
|
85
|
-
expect(nodeDiv.getAttribute('aria-expanded')).toBe('true')
|
|
86
|
-
expect(container).toMatchSnapshot()
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
// it('renders custom snippet if provided', () => {
|
|
90
|
-
// const snippet = (node) => `<span>Custom: ${node.label}</span>`
|
|
91
|
-
// const { container, getByText } = render(Node, {
|
|
92
|
-
// props: {
|
|
93
|
-
// value: baseNode,
|
|
94
|
-
// fields,
|
|
95
|
-
// path: [0],
|
|
96
|
-
// snippets: { [baseNode.snippet]: snippet }
|
|
97
|
-
// }
|
|
98
|
-
// })
|
|
99
|
-
// const contentEl = container.querySelector('[data-tree-content]')
|
|
100
|
-
// expect(contentEl).not.toBeNull()
|
|
101
|
-
// expect(contentEl.textContent).toContain('Custom: Root Node')
|
|
102
|
-
// expect(container).toMatchSnapshot()
|
|
103
|
-
// })
|
|
104
|
-
})
|
package/src/tree/Node.svelte
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { defaultStateIcons, getKeyFromPath, getSnippet } from '@rokkit/core'
|
|
3
|
-
import Icon from '../Icon.svelte'
|
|
4
|
-
import Connector from '../Connector.svelte'
|
|
5
|
-
import Item from '../Item.svelte'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @typedef {Object} Props
|
|
9
|
-
* @property {any} value
|
|
10
|
-
* @property {import('../types').FieldMapping} fields
|
|
11
|
-
* @property {any[]} types
|
|
12
|
-
* @property {import('../types').NodeStateIcons} stateIcons
|
|
13
|
-
* @property {number[]} path
|
|
14
|
-
* @property {boolean} focused
|
|
15
|
-
* @property {boolean} selected
|
|
16
|
-
* @property {boolean} expanded
|
|
17
|
-
* @property {Function} children
|
|
18
|
-
* @property {Function} stub
|
|
19
|
-
* @property {Object<string, Function>} snippets
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/** @type {Props} */
|
|
23
|
-
let {
|
|
24
|
-
value = $bindable(null),
|
|
25
|
-
fields,
|
|
26
|
-
types = [],
|
|
27
|
-
stateIcons = defaultStateIcons.node,
|
|
28
|
-
path = [],
|
|
29
|
-
focused = false,
|
|
30
|
-
selected = false,
|
|
31
|
-
expanded = false,
|
|
32
|
-
children,
|
|
33
|
-
stub = null,
|
|
34
|
-
snippets = {}
|
|
35
|
-
} = $props()
|
|
36
|
-
|
|
37
|
-
let stateName = $derived(expanded ? 'opened' : 'closed')
|
|
38
|
-
let icons = $derived({ ...defaultStateIcons.node, ...stateIcons })
|
|
39
|
-
let state = $derived(
|
|
40
|
-
expanded ? { icon: icons.opened, label: 'collapse' } : { icon: icons.closed, label: 'expand' }
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
const template = getSnippet(value[fields.snippet], snippets, stub)
|
|
44
|
-
const isLeaf = !value?.[fields.children] || value[fields.children]?.length === 0
|
|
45
|
-
</script>
|
|
46
|
-
|
|
47
|
-
<div
|
|
48
|
-
data-tree-node
|
|
49
|
-
data-tree-leaf={isLeaf ? true : undefined}
|
|
50
|
-
data-tree-branch={!isLeaf ? true : undefined}
|
|
51
|
-
aria-current={focused}
|
|
52
|
-
aria-selected={selected}
|
|
53
|
-
aria-expanded={expanded}
|
|
54
|
-
role="treeitem"
|
|
55
|
-
data-path={getKeyFromPath(path)}
|
|
56
|
-
data-depth={path.length}
|
|
57
|
-
tabindex="-1"
|
|
58
|
-
class="flex flex-row items-center"
|
|
59
|
-
>
|
|
60
|
-
{#each types as type, index (index)}
|
|
61
|
-
{#if type === 'icon'}
|
|
62
|
-
<Icon name={state.icon} label={state.label} state={stateName} class="w-4" size="small" />
|
|
63
|
-
{:else}
|
|
64
|
-
<Connector {type} />
|
|
65
|
-
{/if}
|
|
66
|
-
{/each}
|
|
67
|
-
<div data-tree-content>
|
|
68
|
-
<svelte:boundary>
|
|
69
|
-
{#if template}
|
|
70
|
-
{@render template(value)}
|
|
71
|
-
{#snippet failed()}
|
|
72
|
-
<Item {value} {fields} />
|
|
73
|
-
{/snippet}
|
|
74
|
-
{:else}
|
|
75
|
-
<Item {value} {fields} />
|
|
76
|
-
{/if}
|
|
77
|
-
</svelte:boundary>
|
|
78
|
-
</div>
|
|
79
|
-
{@render children?.()}
|
|
80
|
-
</div>
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
-
import { cleanup, render, fireEvent } from '@testing-library/svelte'
|
|
3
|
-
import Root from './Root.svelte'
|
|
4
|
-
|
|
5
|
-
describe('Tree Root Component', () => {
|
|
6
|
-
const items = [
|
|
7
|
-
{ id: 1, label: 'Node 1', children: [{ id: 2, label: 'Node 1.1' }] },
|
|
8
|
-
{ id: 3, label: 'Node 2' }
|
|
9
|
-
]
|
|
10
|
-
|
|
11
|
-
beforeEach(() => cleanup())
|
|
12
|
-
|
|
13
|
-
it('renders tree root with items', () => {
|
|
14
|
-
const { container } = render(Root, {
|
|
15
|
-
props: { items, fields: { text: 'label', children: 'children' } }
|
|
16
|
-
})
|
|
17
|
-
const rootEl = container.querySelector('[data-tree-root]')
|
|
18
|
-
expect(rootEl).not.toBeNull()
|
|
19
|
-
// const contentEls = container.querySelectorAll('[data-tree-content]')
|
|
20
|
-
// expect(Array.from(contentEls).some((el) => el.textContent.includes('Node 1'))).toBe(true)
|
|
21
|
-
// expect(Array.from(contentEls).some((el) => el.textContent.includes('Node 2'))).toBe(true)
|
|
22
|
-
expect(rootEl).toMatchSnapshot()
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
// it('renders header and footer if provided', () => {
|
|
26
|
-
// const header = () => 'Header'
|
|
27
|
-
// const footer = () => 'Footer'
|
|
28
|
-
// const { container } = render(Root, {
|
|
29
|
-
// props: { items, fields: { label: 'label', children: 'children' }, header, footer }
|
|
30
|
-
// })
|
|
31
|
-
// expect(container.textContent).toContain('Header')
|
|
32
|
-
// expect(container.textContent).toContain('Footer')
|
|
33
|
-
// expect(container).toMatchSnapshot()
|
|
34
|
-
// })
|
|
35
|
-
|
|
36
|
-
it('renders empty state when items are empty', () => {
|
|
37
|
-
const { container } = render(Root, {
|
|
38
|
-
props: { items: [], fields: { label: 'label', children: 'children' } }
|
|
39
|
-
})
|
|
40
|
-
expect(container.textContent).toContain('No data available')
|
|
41
|
-
expect(container).toMatchSnapshot()
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
// it('calls event handlers on select', async () => {
|
|
45
|
-
// let selectedValue = null
|
|
46
|
-
// const handleSelect = (e) => {
|
|
47
|
-
// selectedValue = e.detail.value
|
|
48
|
-
// }
|
|
49
|
-
// const { container } = render(Root, {
|
|
50
|
-
// props: {
|
|
51
|
-
// items,
|
|
52
|
-
// fields: { label: 'label', children: 'children' },
|
|
53
|
-
// onselect: handleSelect
|
|
54
|
-
// }
|
|
55
|
-
// })
|
|
56
|
-
// const contentEls = container.querySelectorAll('[data-tree-content]')
|
|
57
|
-
// const node = Array.from(contentEls).find((el) => el.textContent.includes('Node 1'))
|
|
58
|
-
// expect(node).not.toBeNull()
|
|
59
|
-
// await fireEvent.click(node)
|
|
60
|
-
// expect(selectedValue).toBe(items[0])
|
|
61
|
-
// expect(container).toMatchSnapshot()
|
|
62
|
-
// })
|
|
63
|
-
})
|