@sveltia/ui 0.53.1 → 0.55.0

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.
@@ -136,16 +136,20 @@
136
136
  * @param {PointerEvent} event `pointerup` or `pointercancel` event.
137
137
  */
138
138
  const onPointerUp = (event) => {
139
- if (!dragging || event.pointerId !== targetPointerId) return;
139
+ const { pointerId } = event;
140
140
 
141
+ if (!dragging || pointerId !== targetPointerId) return;
142
+
143
+ element?.releasePointerCapture(pointerId);
141
144
  dragging = false;
142
145
  startScreenPos = 0;
143
146
  targetPointerId = 0;
144
147
 
148
+ onResizeEnd?.();
149
+
145
150
  document.removeEventListener('pointermove', onPointerMove);
146
151
  document.removeEventListener('pointerup', onPointerUp);
147
152
  document.removeEventListener('pointercancel', onPointerUp);
148
- onResizeEnd?.();
149
153
  };
150
154
 
151
155
  /**
@@ -158,9 +162,13 @@
158
162
  event.preventDefault();
159
163
  event.stopPropagation();
160
164
 
165
+ const { screenX, screenY, pointerId } = event;
166
+
161
167
  dragging = true;
162
- startScreenPos = isHorizontal ? event.screenX : event.screenY;
163
- targetPointerId = event.pointerId;
168
+ startScreenPos = isHorizontal ? screenX : screenY;
169
+ targetPointerId = pointerId;
170
+ element?.setPointerCapture(pointerId);
171
+
164
172
  onResizeStart?.();
165
173
 
166
174
  document.addEventListener('pointermove', onPointerMove);
@@ -88,17 +88,24 @@
88
88
  // Convert physical position to logical position (always LTR)
89
89
  // In RTL, left side (physicalX=0) maps to max value (logicalX=barWidth)
90
90
  // In LTR, left side (physicalX=0) maps to min value (logicalX=0)
91
- const logicalX = isRTL() ? barWidth - physicalX : physicalX;
92
-
93
- if (logicalX < 0 || logicalX > barWidth) {
94
- return;
95
- }
96
-
91
+ // Clamp to the track bounds so a fast drag that overshoots the edge still resolves to the
92
+ // nearest valid position instead of dropping the update.
93
+ const logicalX = Math.min(barWidth, Math.max(0, isRTL() ? barWidth - physicalX : physicalX));
97
94
  const fromIndex = positionList.findLastIndex((s) => s <= logicalX);
98
95
  const toIndex = positionList.findIndex((s) => logicalX <= s);
99
- const fromDiff = Math.abs(positionList[fromIndex] - logicalX);
100
- const toDiff = Math.abs(positionList[toIndex] - logicalX);
101
- const index = fromDiff < toDiff ? fromIndex : toIndex;
96
+ /** @type {number} */
97
+ let index;
98
+
99
+ if (fromIndex === -1) {
100
+ index = toIndex;
101
+ } else if (toIndex === -1) {
102
+ index = fromIndex;
103
+ } else {
104
+ const fromDiff = Math.abs(positionList[fromIndex] - logicalX);
105
+ const toDiff = Math.abs(positionList[toIndex] - logicalX);
106
+
107
+ index = fromDiff < toDiff ? fromIndex : toIndex;
108
+ }
102
109
 
103
110
  if (
104
111
  sliderPositions[targetValueIndex] === positionList[index] ||
@@ -184,6 +191,7 @@
184
191
  return;
185
192
  }
186
193
 
194
+ event.preventDefault();
187
195
  event.stopPropagation();
188
196
 
189
197
  const screenDiff = screenX - startScreenX;
@@ -198,7 +206,7 @@
198
206
  * @param {PointerEvent} event `pointerup` or `pointercancel` event.
199
207
  */
200
208
  const onPointerUp = (event) => {
201
- const { pointerId } = event;
209
+ const { pointerId, target } = event;
202
210
 
203
211
  if (disabled || readonly || !dragging || pointerId !== targetPointerId) {
204
212
  return;
@@ -206,8 +214,10 @@
206
214
 
207
215
  event.stopPropagation();
208
216
 
217
+ const slider = /** @type {HTMLElement} */ (target);
218
+
209
219
  // Handle a click on the bars
210
- if (/** @type {HTMLElement} */ (event.target).matches('.base-bar, .slider-bar')) {
220
+ if (slider.matches('.base-bar, .slider-bar')) {
211
221
  const rect = /** @type {HTMLElement} */ (base).getBoundingClientRect();
212
222
  // Get physical X position from left edge
213
223
  const physicalX = /** @type {any} */ (event).clientX - rect.left;
@@ -216,6 +226,7 @@
216
226
  }
217
227
 
218
228
  // Reset everything
229
+ slider.releasePointerCapture(pointerId);
219
230
  dragging = false;
220
231
  startX = 0;
221
232
  startScreenX = 0;
@@ -233,16 +244,17 @@
233
244
  * @param {number} [valueIndex] Index in the {@link values} array to be used to get/set the value.
234
245
  */
235
246
  const onPointerDown = (event, valueIndex = 0) => {
236
- const { clientX, screenX, pointerId } = event;
237
-
238
247
  if (disabled || readonly) {
239
248
  return;
240
249
  }
241
250
 
251
+ event.preventDefault();
242
252
  event.stopPropagation();
243
253
 
244
254
  dragging = true;
245
255
 
256
+ const { clientX, screenX, pointerId, target } = event;
257
+ const slider = /** @type {HTMLElement} */ (target);
246
258
  const rect = /** @type {HTMLElement} */ (base).getBoundingClientRect();
247
259
 
248
260
  // Store physical X position from left edge (same in LTR and RTL)
@@ -250,6 +262,7 @@
250
262
  startScreenX = screenX;
251
263
  targetPointerId = pointerId;
252
264
  targetValueIndex = valueIndex;
265
+ slider.setPointerCapture(pointerId);
253
266
 
254
267
  document.addEventListener('pointermove', onPointerMove);
255
268
  document.addEventListener('pointerup', onPointerUp);
@@ -0,0 +1,243 @@
1
+ <!--
2
+ @component
3
+ An item (node) within the `<Tree>` widget. An item that has the `items` slot content becomes a
4
+ parent node that can be expanded and collapsed.
5
+ @see https://w3c.github.io/aria/#treeitem
6
+ @see https://www.w3.org/WAI/ARIA/apg/patterns/treeview/
7
+ -->
8
+ <script>
9
+ import { isRTL } from '@sveltia/i18n';
10
+ import { getContext, setContext } from 'svelte';
11
+ import Icon from '../icon/icon.svelte';
12
+
13
+ /**
14
+ * @import { Snippet } from 'svelte';
15
+ * @import { Attachment } from 'svelte/attachments';
16
+ * @import { CommonEventHandlers } from '../../typedefs';
17
+ */
18
+
19
+ /**
20
+ * @typedef {object} Props
21
+ * @property {string} [class] The `class` attribute on the wrapper element.
22
+ * @property {boolean} [hidden] Whether to hide the widget. An alias of the `aria-hidden`
23
+ * attribute.
24
+ * @property {boolean} [disabled] Whether to disable the widget. An alias of the `aria-disabled`
25
+ * attribute.
26
+ * @property {boolean} [selected] Whether to select the item. An alias of the `aria-selected`
27
+ * attribute.
28
+ * @property {boolean} [expanded] Whether to expand the item. An alias of the `aria-expanded`
29
+ * attribute. Ignored if the item has no `items` slot content.
30
+ * @property {string} [label] Text label displayed on the item.
31
+ * @property {any} [value] The `data-value` attribute on the item. Default: the `label`.
32
+ * @property {string} [valueType] Data type of the `value`. Typically `string`, `number` or
33
+ * `boolean`. Default: auto detect.
34
+ * @property {Snippet} [children] Primary slot content, used instead of the `label`.
35
+ * @property {Snippet} [items] Child items slot content, which makes the item a parent node.
36
+ * @property {Snippet} [startIcon] Start icon slot content.
37
+ * @property {Snippet} [endIcon] End icon slot content.
38
+ * @property {Snippet} [chevronIcon] Chevron icon slot content.
39
+ * @property {(event: CustomEvent) => void} [onChange] Custom `Change` event handler, called when
40
+ * the selection state is changed.
41
+ * @property {(event: CustomEvent) => void} [onSelect] Custom `Select` event handler.
42
+ * @property {(event: CustomEvent) => void} [onExpand] Custom `Expand` event handler, called when
43
+ * the item is expanded or collapsed.
44
+ */
45
+
46
+ /**
47
+ * @type {CommonEventHandlers & Props & Record<string, any>}
48
+ */
49
+ let {
50
+ /* eslint-disable prefer-const */
51
+ selected = $bindable(false),
52
+ expanded = $bindable(false),
53
+ class: className,
54
+ hidden = false,
55
+ disabled = false,
56
+ label = '',
57
+ // svelte-ignore state_referenced_locally
58
+ value = label,
59
+ // svelte-ignore state_referenced_locally
60
+ valueType = typeof value,
61
+ children,
62
+ items,
63
+ startIcon,
64
+ endIcon,
65
+ chevronIcon,
66
+ onChange,
67
+ onSelect,
68
+ onExpand,
69
+ ...restProps
70
+ /* eslint-enable prefer-const */
71
+ } = $props();
72
+
73
+ const id = $props.id();
74
+ /** Nesting level of the item, starting with 1 for the root level items. */
75
+ const level = /** @type {number} */ (getContext('sui:tree-item-level') ?? 0) + 1;
76
+
77
+ setContext('sui:tree-item-level', level);
78
+
79
+ /**
80
+ * Handle the `Change` event dispatched by the parent widget when the selection state is changed.
81
+ * @param {any} event `Change` event.
82
+ */
83
+ const handleChange = (event) => {
84
+ selected = event.detail.selected;
85
+ onChange?.(event);
86
+ };
87
+
88
+ /**
89
+ * Handle the `Expand` event dispatched by the parent widget when the item is expanded or
90
+ * collapsed.
91
+ * @param {any} event `Expand` event.
92
+ */
93
+ const handleExpand = (event) => {
94
+ expanded = event.detail.expanded;
95
+ onExpand?.(event);
96
+ };
97
+
98
+ /**
99
+ * Handle the `Select` event dispatched by the parent widget when the item is selected.
100
+ * @param {any} event `Select` event.
101
+ */
102
+ const handleSelect = (event) => {
103
+ onSelect?.(event);
104
+ };
105
+
106
+ /**
107
+ * Listen to the custom events dispatched on the item element by the parent `<Tree>` widget, so
108
+ * the component state can be kept in sync with the DOM state.
109
+ * @type {Attachment}
110
+ */
111
+ const handleEvents = (element) => {
112
+ /** @type {[string, (event: any) => void][]} */
113
+ const listeners = [
114
+ ['Change', handleChange],
115
+ ['Expand', handleExpand],
116
+ ['Select', handleSelect],
117
+ ];
118
+
119
+ listeners.forEach(([type, handler]) => {
120
+ element.addEventListener(type, handler);
121
+ });
122
+
123
+ return () => {
124
+ listeners.forEach(([type, handler]) => {
125
+ element.removeEventListener(type, handler);
126
+ });
127
+ };
128
+ };
129
+ </script>
130
+
131
+ <div
132
+ {...restProps}
133
+ {id}
134
+ role="treeitem"
135
+ class="sui treeitem {className}"
136
+ tabindex="-1"
137
+ {hidden}
138
+ aria-hidden={hidden}
139
+ aria-disabled={disabled}
140
+ aria-selected={selected}
141
+ aria-expanded={items ? expanded : undefined}
142
+ aria-level={level}
143
+ aria-labelledby="{id}-label"
144
+ data-label={label}
145
+ data-value={value}
146
+ data-type={valueType}
147
+ {@attach handleEvents}
148
+ >
149
+ <div role="none" class="row" style:--sui-tree-item-level={level}>
150
+ {#if items}
151
+ <span role="none" class="chevron" data-action="toggle">
152
+ {#if chevronIcon}
153
+ {@render chevronIcon()}
154
+ {:else}
155
+ <Icon name={isRTL() ? 'chevron_left' : 'chevron_right'} />
156
+ {/if}
157
+ </span>
158
+ {:else}
159
+ <span role="none" class="chevron placeholder"></span>
160
+ {/if}
161
+ {@render startIcon?.()}
162
+ <span role="none" class="label" id="{id}-label">
163
+ {#if label}
164
+ {label}
165
+ {:else}
166
+ {@render children?.()}
167
+ {/if}
168
+ </span>
169
+ {@render endIcon?.()}
170
+ </div>
171
+ {#if items}
172
+ <div role="group" class="group" hidden={!expanded}>
173
+ {@render items()}
174
+ </div>
175
+ {/if}
176
+ </div>
177
+
178
+ <style>[role=treeitem] {
179
+ display: block;
180
+ }
181
+ [role=treeitem][hidden] {
182
+ display: none;
183
+ }
184
+ [role=treeitem]:focus-visible {
185
+ outline-color: transparent;
186
+ }
187
+ [role=treeitem]:focus-visible > .row {
188
+ outline-color: var(--sui-focus-ring-color);
189
+ outline-offset: calc(var(--sui-focus-ring-width) * -1);
190
+ }
191
+ [role=treeitem][aria-selected=true] > .row {
192
+ color: var(--sui-highlight-foreground-color);
193
+ background-color: var(--sui-selected-background-color);
194
+ }
195
+ [role=treeitem] > .row:hover {
196
+ color: var(--sui-highlight-foreground-color);
197
+ background-color: var(--sui-hover-background-color);
198
+ }
199
+ [role=treeitem] > .row:active {
200
+ background-color: var(--sui-active-background-color);
201
+ }
202
+ [role=treeitem][aria-expanded=true] > .row > .chevron:dir(ltr) {
203
+ transform: rotate(90deg);
204
+ }
205
+ [role=treeitem][aria-expanded=true] > .row > .chevron:dir(rtl) {
206
+ transform: rotate(-90deg);
207
+ }
208
+
209
+ .row {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 4px;
213
+ border-radius: var(--sui-tree-item-border-radius, var(--sui-option-border-radius, 4px));
214
+ padding: var(--sui-tree-item-padding, 0 8px 0 4px);
215
+ padding-inline-start: calc((var(--sui-tree-item-level, 1) - 1) * var(--sui-tree-item-indent, 16px) + 4px);
216
+ min-height: var(--sui-tree-item-height, var(--sui-option-height));
217
+ cursor: pointer;
218
+ transition: background-color 200ms;
219
+ }
220
+
221
+ .chevron {
222
+ flex: none;
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: center;
226
+ width: var(--sui-tree-item-chevron-size, 24px);
227
+ height: var(--sui-tree-item-chevron-size, 24px);
228
+ transition: transform 200ms;
229
+ }
230
+
231
+ .label {
232
+ flex: auto;
233
+ overflow: hidden;
234
+ white-space: nowrap;
235
+ text-overflow: ellipsis;
236
+ }
237
+
238
+ .group {
239
+ display: block;
240
+ }
241
+ .group[hidden] {
242
+ display: none;
243
+ }</style>
@@ -0,0 +1,158 @@
1
+ export default TreeItem;
2
+ type TreeItem = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<KeyboardEventHandlers & MouseEventHandlers & PointerEventHandlers & FocusEventHandlers & DragEventHandlers & Props & Record<string, any>>): void;
5
+ };
6
+ /**
7
+ * An item (node) within the `<Tree>` widget. An item that has the `items` slot content becomes a
8
+ * parent node that can be expanded and collapsed.
9
+ * @see https://w3c.github.io/aria/#treeitem
10
+ * @see https://www.w3.org/WAI/ARIA/apg/patterns/treeview/
11
+ */
12
+ declare const TreeItem: import("svelte").Component<import("../../typedefs").KeyboardEventHandlers & import("../../typedefs").MouseEventHandlers & import("../../typedefs").PointerEventHandlers & import("../../typedefs").FocusEventHandlers & import("../../typedefs").DragEventHandlers & {
13
+ /**
14
+ * The `class` attribute on the wrapper element.
15
+ */
16
+ class?: string | undefined;
17
+ /**
18
+ * Whether to hide the widget. An alias of the `aria-hidden`
19
+ * attribute.
20
+ */
21
+ hidden?: boolean | undefined;
22
+ /**
23
+ * Whether to disable the widget. An alias of the `aria-disabled`
24
+ * attribute.
25
+ */
26
+ disabled?: boolean | undefined;
27
+ /**
28
+ * Whether to select the item. An alias of the `aria-selected`
29
+ * attribute.
30
+ */
31
+ selected?: boolean | undefined;
32
+ /**
33
+ * Whether to expand the item. An alias of the `aria-expanded`
34
+ * attribute. Ignored if the item has no `items` slot content.
35
+ */
36
+ expanded?: boolean | undefined;
37
+ /**
38
+ * Text label displayed on the item.
39
+ */
40
+ label?: string | undefined;
41
+ /**
42
+ * The `data-value` attribute on the item. Default: the `label`.
43
+ */
44
+ value?: any;
45
+ /**
46
+ * Data type of the `value`. Typically `string`, `number` or
47
+ * `boolean`. Default: auto detect.
48
+ */
49
+ valueType?: string | undefined;
50
+ /**
51
+ * Primary slot content, used instead of the `label`.
52
+ */
53
+ children?: Snippet<[]> | undefined;
54
+ /**
55
+ * Child items slot content, which makes the item a parent node.
56
+ */
57
+ items?: Snippet<[]> | undefined;
58
+ /**
59
+ * Start icon slot content.
60
+ */
61
+ startIcon?: Snippet<[]> | undefined;
62
+ /**
63
+ * End icon slot content.
64
+ */
65
+ endIcon?: Snippet<[]> | undefined;
66
+ /**
67
+ * Chevron icon slot content.
68
+ */
69
+ chevronIcon?: Snippet<[]> | undefined;
70
+ /**
71
+ * Custom `Change` event handler, called when
72
+ * the selection state is changed.
73
+ */
74
+ onChange?: ((event: CustomEvent) => void) | undefined;
75
+ /**
76
+ * Custom `Select` event handler.
77
+ */
78
+ onSelect?: ((event: CustomEvent) => void) | undefined;
79
+ /**
80
+ * Custom `Expand` event handler, called when
81
+ * the item is expanded or collapsed.
82
+ */
83
+ onExpand?: ((event: CustomEvent) => void) | undefined;
84
+ } & Record<string, any>, {}, "selected" | "expanded">;
85
+ type Props = {
86
+ /**
87
+ * The `class` attribute on the wrapper element.
88
+ */
89
+ class?: string | undefined;
90
+ /**
91
+ * Whether to hide the widget. An alias of the `aria-hidden`
92
+ * attribute.
93
+ */
94
+ hidden?: boolean | undefined;
95
+ /**
96
+ * Whether to disable the widget. An alias of the `aria-disabled`
97
+ * attribute.
98
+ */
99
+ disabled?: boolean | undefined;
100
+ /**
101
+ * Whether to select the item. An alias of the `aria-selected`
102
+ * attribute.
103
+ */
104
+ selected?: boolean | undefined;
105
+ /**
106
+ * Whether to expand the item. An alias of the `aria-expanded`
107
+ * attribute. Ignored if the item has no `items` slot content.
108
+ */
109
+ expanded?: boolean | undefined;
110
+ /**
111
+ * Text label displayed on the item.
112
+ */
113
+ label?: string | undefined;
114
+ /**
115
+ * The `data-value` attribute on the item. Default: the `label`.
116
+ */
117
+ value?: any;
118
+ /**
119
+ * Data type of the `value`. Typically `string`, `number` or
120
+ * `boolean`. Default: auto detect.
121
+ */
122
+ valueType?: string | undefined;
123
+ /**
124
+ * Primary slot content, used instead of the `label`.
125
+ */
126
+ children?: Snippet<[]> | undefined;
127
+ /**
128
+ * Child items slot content, which makes the item a parent node.
129
+ */
130
+ items?: Snippet<[]> | undefined;
131
+ /**
132
+ * Start icon slot content.
133
+ */
134
+ startIcon?: Snippet<[]> | undefined;
135
+ /**
136
+ * End icon slot content.
137
+ */
138
+ endIcon?: Snippet<[]> | undefined;
139
+ /**
140
+ * Chevron icon slot content.
141
+ */
142
+ chevronIcon?: Snippet<[]> | undefined;
143
+ /**
144
+ * Custom `Change` event handler, called when
145
+ * the selection state is changed.
146
+ */
147
+ onChange?: ((event: CustomEvent) => void) | undefined;
148
+ /**
149
+ * Custom `Select` event handler.
150
+ */
151
+ onSelect?: ((event: CustomEvent) => void) | undefined;
152
+ /**
153
+ * Custom `Expand` event handler, called when
154
+ * the item is expanded or collapsed.
155
+ */
156
+ onExpand?: ((event: CustomEvent) => void) | undefined;
157
+ };
158
+ import type { Snippet } from 'svelte';
@@ -0,0 +1,108 @@
1
+ <!--
2
+ @component
3
+ A tree view widget that displays a hierarchical list of items, which can be expanded, collapsed
4
+ and selected.
5
+ @see https://w3c.github.io/aria/#tree
6
+ @see https://www.w3.org/WAI/ARIA/apg/patterns/treeview/
7
+ -->
8
+ <script>
9
+ import { activateTree } from '../../services/tree.svelte.js';
10
+
11
+ /**
12
+ * @import { Snippet } from 'svelte';
13
+ * @import { CommonEventHandlers } from '../../typedefs';
14
+ */
15
+
16
+ /**
17
+ * @typedef {object} Props
18
+ * @property {HTMLElement} [element] A reference to the wrapper element.
19
+ * @property {string} [class] The `class` attribute on the wrapper element.
20
+ * @property {boolean} [hidden] Whether to hide the widget. An alias of the `aria-hidden`
21
+ * attribute.
22
+ * @property {boolean} [disabled] Whether to disable the widget. An alias of the `aria-disabled`
23
+ * attribute.
24
+ * @property {boolean} [readonly] Whether to make the widget read-only. An alias of the
25
+ * `aria-readonly` attribute.
26
+ * @property {boolean} [multiple] Whether to allow selecting more than one `<TreeItem>`. An alias
27
+ * of the `aria-multiselectable` attribute.
28
+ * @property {boolean} [clickToSelect] Whether to select an item by clicking on it.
29
+ * @property {boolean} [selectionFollowsFocus] Whether to select an item as soon as it receives
30
+ * focus. Default: `true` on a single-select tree, `false` on a multi-select tree.
31
+ * @property {boolean} [expandOnSelect] Whether to expand or collapse a parent item when the item
32
+ * itself, rather than its chevron, is clicked or activated with the Enter key.
33
+ * @property {string} [ariaLabel] The `aria-label` attribute on the wrapper element. Required
34
+ * unless the `aria-labelledby` attribute is provided.
35
+ * @property {Snippet} [children] Primary slot content.
36
+ * @property {(event: CustomEvent) => void} [onChange] Custom `Change` event handler.
37
+ */
38
+
39
+ /**
40
+ * @type {CommonEventHandlers & Props & Record<string, any>}
41
+ */
42
+ let {
43
+ /* eslint-disable prefer-const */
44
+ element = $bindable(),
45
+ class: className,
46
+ hidden = false,
47
+ disabled = false,
48
+ readonly = false,
49
+ multiple = false,
50
+ clickToSelect = true,
51
+ selectionFollowsFocus = undefined,
52
+ expandOnSelect = true,
53
+ ariaLabel = undefined,
54
+ children,
55
+ onChange,
56
+ ...restProps
57
+ /* eslint-enable prefer-const */
58
+ } = $props();
59
+ </script>
60
+
61
+ <!--
62
+ The `aria-readonly` attribute is not part of the `tree` role in ARIA, but we still use it to keep
63
+ the API consistent with the other selection widgets, including `<Listbox>`. It’s only rendered
64
+ when the widget is actually read-only, so assistive technologies can safely ignore it.
65
+ -->
66
+ <!-- svelte-ignore a11y_role_supports_aria_props -->
67
+ <div
68
+ bind:this={element}
69
+ {...restProps}
70
+ role="tree"
71
+ class="sui tree {className}"
72
+ {hidden}
73
+ aria-hidden={hidden}
74
+ aria-disabled={disabled}
75
+ aria-readonly={readonly || undefined}
76
+ aria-multiselectable={multiple}
77
+ aria-label={ariaLabel}
78
+ onChange={(/** @type {CustomEvent} */ event) => {
79
+ onChange?.(event);
80
+ }}
81
+ {@attach activateTree({ clickToSelect, selectionFollowsFocus, expandOnSelect })}
82
+ >
83
+ <div role="none" class="inner" inert={disabled}>
84
+ {@render children?.()}
85
+ </div>
86
+ </div>
87
+
88
+ <style>[role=tree] {
89
+ display: flex;
90
+ flex-direction: column;
91
+ margin: var(--sui-focus-ring-width);
92
+ border-width: var(--sui-tree-border-width, 1px);
93
+ border-style: var(--sui-tree-border-style, solid);
94
+ border-color: var(--sui-tree-border-color, var(--sui-secondary-border-color));
95
+ border-radius: var(--sui-tree-border-radius, 4px);
96
+ padding: var(--sui-tree-padding, 4px);
97
+ min-width: var(--sui-tree-min-width, calc(var(--sui-option-height) * 5));
98
+ overflow: auto;
99
+ color: var(--sui-tree-foreground-color, var(--sui-control-foreground-color));
100
+ background-color: var(--sui-tree-background-color);
101
+ font-family: var(--sui-control-font-family);
102
+ font-size: var(--sui-control-font-size);
103
+ line-height: var(--sui-control-line-height);
104
+ }
105
+
106
+ .inner {
107
+ display: contents;
108
+ }</style>