@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.
Files changed (154) hide show
  1. package/README.md +198 -101
  2. package/package.json +42 -34
  3. package/src/components/BreadCrumbs.svelte +90 -0
  4. package/src/components/Button.svelte +93 -0
  5. package/src/components/ButtonGroup.svelte +18 -0
  6. package/src/components/Card.svelte +61 -0
  7. package/src/components/Carousel.svelte +174 -0
  8. package/src/components/Code.svelte +189 -0
  9. package/src/components/Connector.svelte +46 -0
  10. package/src/components/FloatingAction.svelte +334 -0
  11. package/src/components/FloatingNavigation.svelte +235 -0
  12. package/src/components/Grid.svelte +128 -0
  13. package/src/components/ItemContent.svelte +25 -0
  14. package/src/components/LazyTree.svelte +165 -0
  15. package/src/components/List.svelte +188 -0
  16. package/src/components/Menu.svelte +270 -0
  17. package/src/components/MultiSelect.svelte +369 -0
  18. package/src/components/PaletteManager.svelte +364 -0
  19. package/src/components/Pill.svelte +83 -0
  20. package/src/components/ProgressBar.svelte +31 -0
  21. package/src/components/Range.svelte +330 -0
  22. package/src/components/Rating.svelte +101 -0
  23. package/src/components/Reveal.svelte +58 -0
  24. package/src/components/SearchFilter.svelte +88 -0
  25. package/src/components/Select.svelte +396 -0
  26. package/src/{Shine.svelte → components/Shine.svelte} +29 -21
  27. package/src/components/Stepper.svelte +172 -0
  28. package/src/components/Switch.svelte +75 -0
  29. package/src/components/Table.svelte +242 -0
  30. package/src/components/Tabs.svelte +192 -0
  31. package/src/components/Tilt.svelte +68 -0
  32. package/src/components/Timeline.svelte +61 -0
  33. package/src/components/Toggle.svelte +93 -0
  34. package/src/components/Toolbar.svelte +308 -0
  35. package/src/components/ToolbarGroup.svelte +17 -0
  36. package/src/components/Tree.svelte +144 -0
  37. package/src/components/UploadFileStatus.svelte +83 -0
  38. package/src/components/UploadProgress.svelte +131 -0
  39. package/src/components/UploadTarget.svelte +124 -0
  40. package/src/components/index.ts +38 -0
  41. package/src/index.ts +46 -0
  42. package/src/types/button.ts +86 -0
  43. package/src/types/code.ts +46 -0
  44. package/src/types/floating-action.ts +123 -0
  45. package/src/types/floating-navigation.ts +80 -0
  46. package/src/types/index.ts +55 -0
  47. package/src/types/list.ts +200 -0
  48. package/src/types/menu.ts +95 -0
  49. package/src/types/palette.ts +160 -0
  50. package/src/types/range.ts +51 -0
  51. package/src/types/search-filter.ts +67 -0
  52. package/src/types/select.ts +176 -0
  53. package/src/types/switch.ts +68 -0
  54. package/src/types/table.ts +210 -0
  55. package/src/types/tabs.ts +103 -0
  56. package/src/types/timeline.ts +53 -0
  57. package/src/types/toggle.ts +68 -0
  58. package/src/types/toolbar.ts +164 -0
  59. package/src/types/tree.ts +250 -0
  60. package/src/types/upload-file-status.ts +45 -0
  61. package/src/types/upload-progress.ts +111 -0
  62. package/src/types/upload-target.ts +68 -0
  63. package/src/utils/palette.ts +582 -0
  64. package/src/utils/shiki.ts +122 -0
  65. package/src/utils/upload.js +128 -0
  66. package/dist/constants.d.ts +0 -2
  67. package/dist/index.d.ts +0 -41
  68. package/dist/lib/fields.d.ts +0 -16
  69. package/dist/lib/form.d.ts +0 -95
  70. package/dist/lib/index.d.ts +0 -6
  71. package/dist/lib/layout.d.ts +0 -7
  72. package/dist/lib/nested.d.ts +0 -48
  73. package/dist/lib/schema.d.ts +0 -7
  74. package/dist/lib/select.d.ts +0 -8
  75. package/dist/lib/tree.d.ts +0 -9
  76. package/dist/tree/List.spec.svelte.d.ts +0 -1
  77. package/dist/tree/Node.spec.svelte.d.ts +0 -1
  78. package/dist/tree/Root.spec.svelte.d.ts +0 -1
  79. package/dist/types.d.ts +0 -5
  80. package/dist/wrappers/index.d.ts +0 -3
  81. package/src/Accordion.svelte +0 -118
  82. package/src/BreadCrumbs.svelte +0 -32
  83. package/src/Button.svelte +0 -57
  84. package/src/Calendar.svelte +0 -93
  85. package/src/Card.svelte +0 -45
  86. package/src/Carousel.svelte +0 -49
  87. package/src/CheckBox.svelte +0 -56
  88. package/src/Connector.svelte +0 -40
  89. package/src/DropDown.svelte +0 -68
  90. package/src/DropSearch.svelte +0 -37
  91. package/src/Fillable.svelte +0 -19
  92. package/src/GraphPaper.svelte +0 -43
  93. package/src/Icon.svelte +0 -81
  94. package/src/Item.svelte +0 -25
  95. package/src/Link.svelte +0 -21
  96. package/src/List.svelte +0 -89
  97. package/src/ListBody.svelte +0 -43
  98. package/src/Message.svelte +0 -11
  99. package/src/MultiSelect.svelte +0 -48
  100. package/src/NestedList.svelte +0 -78
  101. package/src/NestedPaginator.svelte +0 -63
  102. package/src/Node.svelte +0 -76
  103. package/src/Overlay.svelte +0 -21
  104. package/src/PageNavigator.svelte +0 -94
  105. package/src/PickOne.svelte +0 -60
  106. package/src/Pill.svelte +0 -41
  107. package/src/ProgressBar.svelte +0 -21
  108. package/src/ProgressDots.svelte +0 -53
  109. package/src/RadioGroup.svelte +0 -52
  110. package/src/Range.svelte +0 -45
  111. package/src/RangeMinMax.svelte +0 -124
  112. package/src/RangeSlider.svelte +0 -79
  113. package/src/RangeTick.svelte +0 -28
  114. package/src/Rating.svelte +0 -95
  115. package/src/ResponsiveGrid.svelte +0 -88
  116. package/src/Scrollable.svelte +0 -7
  117. package/src/Select.svelte +0 -114
  118. package/src/Separator.svelte +0 -1
  119. package/src/Slider.svelte +0 -14
  120. package/src/SlidingColumns.svelte +0 -50
  121. package/src/Stage.svelte +0 -41
  122. package/src/Stepper.svelte +0 -66
  123. package/src/Summary.svelte +0 -22
  124. package/src/Switch.svelte +0 -106
  125. package/src/TableCell.svelte +0 -51
  126. package/src/TableHeaderCell.svelte +0 -54
  127. package/src/Tabs.svelte +0 -176
  128. package/src/Tilt.svelte +0 -66
  129. package/src/Toggle.svelte +0 -58
  130. package/src/ToggleThemeMode.svelte +0 -23
  131. package/src/Tree.svelte +0 -80
  132. package/src/TreeTable.svelte +0 -171
  133. package/src/ValidationReport.svelte +0 -23
  134. package/src/constants.js +0 -4
  135. package/src/index.js +0 -48
  136. package/src/lib/fields.js +0 -118
  137. package/src/lib/form.js +0 -72
  138. package/src/lib/index.js +0 -13
  139. package/src/lib/layout.js +0 -63
  140. package/src/lib/nested.js +0 -192
  141. package/src/lib/schema.js +0 -32
  142. package/src/lib/select.js +0 -38
  143. package/src/lib/tree.js +0 -22
  144. package/src/tree/List.spec.svelte.js +0 -84
  145. package/src/tree/List.svelte +0 -78
  146. package/src/tree/Node.spec.svelte.js +0 -104
  147. package/src/tree/Node.svelte +0 -80
  148. package/src/tree/Root.spec.svelte.js +0 -63
  149. package/src/tree/Root.svelte +0 -81
  150. package/src/types.js +0 -9
  151. package/src/wrappers/Category.svelte +0 -27
  152. package/src/wrappers/Section.svelte +0 -16
  153. package/src/wrappers/Wrapper.svelte +0 -12
  154. 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
- })
@@ -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
- })
@@ -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
- })