@surflane/ui 0.1.0 → 0.2.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.
- package/README.md +8 -8
- package/dist/advanced-forms.d.ts +2 -2
- package/dist/advanced-forms.js +39 -39
- package/dist/app-shell.d.ts +1 -1
- package/dist/app-shell.js +11 -11
- package/dist/button.d.ts +5 -5
- package/dist/button.js +12 -12
- package/dist/chart.d.ts +3 -3
- package/dist/chart.js +36 -34
- package/dist/commands.d.ts +5 -5
- package/dist/commands.js +17 -17
- package/dist/content.d.ts +2 -2
- package/dist/content.js +66 -28
- package/dist/context-overlays.d.ts +3 -3
- package/dist/context-overlays.js +11 -11
- package/dist/data.d.ts +1 -1
- package/dist/data.js +18 -18
- package/dist/date-picker.d.ts +8 -8
- package/dist/date-picker.js +18 -18
- package/dist/dialog-focus.js +7 -7
- package/dist/dialog.d.ts +5 -5
- package/dist/dialog.js +16 -16
- package/dist/feedback.d.ts +5 -5
- package/dist/feedback.js +20 -20
- package/dist/file-upload.d.ts +9 -9
- package/dist/file-upload.js +64 -63
- package/dist/filter-bar.d.ts +1 -1
- package/dist/filter-bar.js +9 -9
- package/dist/form-state.d.ts +1 -1
- package/dist/form-state.js +11 -11
- package/dist/forms.d.ts +5 -5
- package/dist/forms.js +32 -32
- package/dist/foundations.d.ts +10 -10
- package/dist/foundations.js +26 -26
- package/dist/icon.d.ts +7 -7
- package/dist/icon.js +22 -22
- package/dist/index.d.ts +37 -37
- package/dist/index.js +38 -38
- package/dist/input-extensions.d.ts +3 -3
- package/dist/input-extensions.js +21 -21
- package/dist/input.d.ts +2 -2
- package/dist/input.js +7 -7
- package/dist/markdown.d.ts +2 -2
- package/dist/markdown.js +38 -38
- package/dist/menu.d.ts +2 -2
- package/dist/menu.js +19 -19
- package/dist/messages.d.ts +2 -2
- package/dist/messages.js +18 -18
- package/dist/navigation-menu.d.ts +2 -2
- package/dist/navigation-menu.js +16 -16
- package/dist/navigation.d.ts +5 -5
- package/dist/navigation.js +40 -40
- package/dist/otp-field.d.ts +1 -1
- package/dist/otp-field.js +14 -14
- package/dist/pagination.d.ts +2 -2
- package/dist/pagination.js +12 -12
- package/dist/panels.d.ts +8 -8
- package/dist/panels.js +39 -39
- package/dist/parts.d.ts +2 -2
- package/dist/parts.js +8 -8
- package/dist/popover.d.ts +3 -3
- package/dist/popover.js +6 -6
- package/dist/scroll-area.d.ts +1 -1
- package/dist/scroll-area.js +20 -20
- package/dist/select.d.ts +8 -8
- package/dist/select.js +21 -21
- package/dist/sizing.d.ts +3 -3
- package/dist/sizing.js +9 -9
- package/dist/slot.d.ts +1 -1
- package/dist/slot.js +4 -4
- package/dist/status.d.ts +4 -4
- package/dist/status.js +8 -8
- package/dist/syntax-engine.js +43 -43
- package/dist/syntax-highlighter.d.ts +2 -2
- package/dist/syntax-highlighter.js +32 -32
- package/dist/toast.d.ts +4 -4
- package/dist/toast.js +33 -33
- package/dist/tone-transition.d.ts +1 -1
- package/dist/tone-transition.js +17 -17
- package/dist/tooltip.d.ts +1 -1
- package/dist/tooltip.js +4 -4
- package/dist/tree.d.ts +4 -4
- package/dist/tree.js +60 -60
- package/package.json +2 -2
- package/src/styles.css +233 -142
package/dist/tree.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { createElement as _createElement } from "react";
|
|
4
|
-
import * as React from
|
|
5
|
-
import { useComponentSize } from
|
|
6
|
-
import { buildProxiedInstance, syncDataLoaderFeature, } from
|
|
7
|
-
import { useTree } from
|
|
8
|
-
import { defaultRangeExtractor, useVirtualizer, } from
|
|
9
|
-
import { ScrollArea } from
|
|
10
|
-
const normalize = (text) => text.normalize(
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { useComponentSize } from './sizing.js';
|
|
6
|
+
import { buildProxiedInstance, syncDataLoaderFeature, } from '@headless-tree/core';
|
|
7
|
+
import { useTree } from '@headless-tree/react';
|
|
8
|
+
import { defaultRangeExtractor, useVirtualizer, } from '@tanstack/react-virtual';
|
|
9
|
+
import { ScrollArea } from './scroll-area.js';
|
|
10
|
+
const normalize = (text) => text.normalize('NFKC').toLocaleLowerCase();
|
|
11
11
|
/** Loaded, immutable hierarchy. Remote loading, permission checks and mutations remain caller-owned. */
|
|
12
|
-
export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExpandedIdsChange, selectedIds, defaultSelectedIds = [], onSelectedIdsChange, selectionMode =
|
|
12
|
+
export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExpandedIdsChange, selectedIds, defaultSelectedIds = [], onSelectedIdsChange, selectionMode = 'single', height = 320, rowHeight: explicitRowHeight, size, virtualized = true, emptyLabel = 'No items.', dir, className, }) {
|
|
13
13
|
const scheme = useComponentSize(size);
|
|
14
14
|
const touchFloor = React.useSyncExternalStore((listener) => {
|
|
15
|
-
const query = matchMedia(
|
|
16
|
-
query.addEventListener(
|
|
17
|
-
return () => query.removeEventListener(
|
|
18
|
-
}, () => matchMedia(
|
|
15
|
+
const query = matchMedia('(max-width: 760px), (pointer: coarse)');
|
|
16
|
+
query.addEventListener('change', listener);
|
|
17
|
+
return () => query.removeEventListener('change', listener);
|
|
18
|
+
}, () => matchMedia('(max-width: 760px), (pointer: coarse)').matches, () => false);
|
|
19
19
|
const rowHeight = explicitRowHeight ??
|
|
20
|
-
Math.max(touchFloor && scheme ? 44 : 0, scheme ===
|
|
20
|
+
Math.max(touchFloor && scheme ? 44 : 0, scheme === 'small' ? 36 : scheme === 'large' ? 52 : 44);
|
|
21
21
|
const rootId = React.useId();
|
|
22
22
|
const model = React.useMemo(() => {
|
|
23
23
|
const data = new Map();
|
|
@@ -42,7 +42,7 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
42
42
|
const expanded = React.useMemo(() => [...new Set(expandedIds ?? localExpanded)].filter((id) => model.data.get(id)?.children?.length), [expandedIds, localExpanded, model]);
|
|
43
43
|
const selected = React.useMemo(() => {
|
|
44
44
|
const ids = [...new Set(selectedIds ?? localSelected)].filter((id) => model.data.has(id) && !model.data.get(id)?.disabled);
|
|
45
|
-
return selectionMode ===
|
|
45
|
+
return selectionMode === 'single' ? ids.slice(-1) : ids;
|
|
46
46
|
}, [selectedIds, localSelected, model, selectionMode]);
|
|
47
47
|
const visibleIds = React.useMemo(() => {
|
|
48
48
|
const result = [], open = new Set(expanded);
|
|
@@ -66,7 +66,7 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
66
66
|
const viewport = React.useRef(null), container = React.useRef(null);
|
|
67
67
|
const virtualizerRef = React.useRef(null);
|
|
68
68
|
const pendingFocus = React.useRef(null), focusWithin = React.useRef(false), anchor = React.useRef(null);
|
|
69
|
-
const typeahead = React.useRef({ text:
|
|
69
|
+
const typeahead = React.useRef({ text: '', time: 0 });
|
|
70
70
|
function changeExpanded(ids) {
|
|
71
71
|
const next = [...new Set(ids)];
|
|
72
72
|
if (expandedIds === undefined)
|
|
@@ -75,7 +75,7 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
75
75
|
}
|
|
76
76
|
function changeSelected(ids) {
|
|
77
77
|
const allowed = [...new Set(ids)].filter((id) => model.data.has(id) && !model.data.get(id)?.disabled);
|
|
78
|
-
const next = selectionMode ===
|
|
78
|
+
const next = selectionMode === 'single' ? allowed.slice(-1) : allowed;
|
|
79
79
|
if (selectedIds === undefined)
|
|
80
80
|
setLocalSelected(next);
|
|
81
81
|
onSelectedIdsChange?.(next);
|
|
@@ -91,14 +91,14 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
91
91
|
getChildren: (id) => model.data.get(id)?.children?.map((node) => node.id) ?? [],
|
|
92
92
|
},
|
|
93
93
|
state: { expandedItems: expanded, focusedItem: focused },
|
|
94
|
-
setExpandedItems: (update) => changeExpanded(typeof update ===
|
|
95
|
-
setFocusedItem: (update) => setLocalFocus(typeof update ===
|
|
94
|
+
setExpandedItems: (update) => changeExpanded(typeof update === 'function' ? update(expanded) : update),
|
|
95
|
+
setFocusedItem: (update) => setLocalFocus(typeof update === 'function' ? update(focused) : update),
|
|
96
96
|
scrollToItem: (item) => virtualizerRef.current?.scrollToIndex(item.getItemMeta().index, {
|
|
97
|
-
align:
|
|
97
|
+
align: 'auto',
|
|
98
98
|
}),
|
|
99
99
|
});
|
|
100
100
|
const items = tree.getItems();
|
|
101
|
-
const focusedIndex = visibleIds.indexOf(focused ??
|
|
101
|
+
const focusedIndex = visibleIds.indexOf(focused ?? '');
|
|
102
102
|
const virtualizer = useVirtualizer({
|
|
103
103
|
count: items.length,
|
|
104
104
|
getScrollElement: () => viewport.current,
|
|
@@ -121,7 +121,7 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
121
121
|
pendingFocus.current = item.getId();
|
|
122
122
|
item.setFocused();
|
|
123
123
|
if (virtualized)
|
|
124
|
-
virtualizer.scrollToIndex(item.getItemMeta().index, { align:
|
|
124
|
+
virtualizer.scrollToIndex(item.getItemMeta().index, { align: 'auto' });
|
|
125
125
|
else if (viewport.current) {
|
|
126
126
|
const top = item.getItemMeta().index * rowHeight;
|
|
127
127
|
if (top < viewport.current.scrollTop)
|
|
@@ -157,7 +157,7 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
157
157
|
const id = item.getId();
|
|
158
158
|
if (item.getItemData().disabled)
|
|
159
159
|
return;
|
|
160
|
-
if (selectionMode ===
|
|
160
|
+
if (selectionMode === 'single')
|
|
161
161
|
changeSelected([id]);
|
|
162
162
|
else if (range && anchor.current && visibleIds.includes(anchor.current)) {
|
|
163
163
|
const a = visibleIds.indexOf(anchor.current), b = visibleIds.indexOf(id);
|
|
@@ -173,9 +173,9 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
173
173
|
function keydown(event) {
|
|
174
174
|
if (event.defaultPrevented ||
|
|
175
175
|
event.nativeEvent.isComposing ||
|
|
176
|
-
event.target.closest(
|
|
176
|
+
event.target.closest('button'))
|
|
177
177
|
return;
|
|
178
|
-
const targetId = event.target.closest(
|
|
178
|
+
const targetId = event.target.closest('[data-tree-id]')?.dataset.treeId;
|
|
179
179
|
const item = targetId
|
|
180
180
|
? tree.getItemInstance(targetId)
|
|
181
181
|
: focused
|
|
@@ -184,20 +184,20 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
184
184
|
if (!item)
|
|
185
185
|
return;
|
|
186
186
|
if ([
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
187
|
+
'ArrowDown',
|
|
188
|
+
'ArrowUp',
|
|
189
|
+
'ArrowLeft',
|
|
190
|
+
'ArrowRight',
|
|
191
|
+
'Home',
|
|
192
|
+
'End',
|
|
193
193
|
].includes(event.key))
|
|
194
|
-
typeahead.current = { text:
|
|
194
|
+
typeahead.current = { text: '', time: 0 };
|
|
195
195
|
const index = item.getItemMeta().index;
|
|
196
|
-
const rtl = getComputedStyle(event.currentTarget).direction ===
|
|
197
|
-
const forward = rtl ?
|
|
196
|
+
const rtl = getComputedStyle(event.currentTarget).direction === 'rtl';
|
|
197
|
+
const forward = rtl ? 'ArrowLeft' : 'ArrowRight', backward = rtl ? 'ArrowRight' : 'ArrowLeft';
|
|
198
198
|
if ((event.ctrlKey || event.metaKey) &&
|
|
199
|
-
event.key.toLowerCase() ===
|
|
200
|
-
selectionMode ===
|
|
199
|
+
event.key.toLowerCase() === 'a' &&
|
|
200
|
+
selectionMode === 'multiple') {
|
|
201
201
|
event.preventDefault();
|
|
202
202
|
changeSelected(visibleIds);
|
|
203
203
|
return;
|
|
@@ -205,26 +205,26 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
205
205
|
if (event.ctrlKey || event.metaKey || event.altKey)
|
|
206
206
|
return;
|
|
207
207
|
if ([
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
'ArrowDown',
|
|
209
|
+
'ArrowUp',
|
|
210
|
+
'Home',
|
|
211
|
+
'End',
|
|
212
212
|
forward,
|
|
213
213
|
backward,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
'Enter',
|
|
215
|
+
' ',
|
|
216
|
+
'*',
|
|
217
217
|
].includes(event.key))
|
|
218
218
|
event.preventDefault();
|
|
219
|
-
if (event.key ===
|
|
220
|
-
const next = items[index + (event.key ===
|
|
219
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
220
|
+
const next = items[index + (event.key === 'ArrowDown' ? 1 : -1)];
|
|
221
221
|
focusItem(next);
|
|
222
|
-
if (event.shiftKey && selectionMode ===
|
|
222
|
+
if (event.shiftKey && selectionMode === 'multiple' && next)
|
|
223
223
|
select(next, true);
|
|
224
224
|
}
|
|
225
|
-
else if (event.key ===
|
|
225
|
+
else if (event.key === 'Home')
|
|
226
226
|
focusItem(items[0]);
|
|
227
|
-
else if (event.key ===
|
|
227
|
+
else if (event.key === 'End')
|
|
228
228
|
focusItem(items.at(-1));
|
|
229
229
|
else if (event.key === forward &&
|
|
230
230
|
!item.getItemData().disabled &&
|
|
@@ -240,9 +240,9 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
240
240
|
else if (item.getItemMeta().level > 0)
|
|
241
241
|
focusItem(item.getParent());
|
|
242
242
|
}
|
|
243
|
-
else if (event.key ===
|
|
243
|
+
else if (event.key === 'Enter' || event.key === ' ')
|
|
244
244
|
select(item, event.shiftKey);
|
|
245
|
-
else if (event.key ===
|
|
245
|
+
else if (event.key === '*')
|
|
246
246
|
changeExpanded([
|
|
247
247
|
...expanded,
|
|
248
248
|
...items
|
|
@@ -252,7 +252,7 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
252
252
|
.map((sibling) => sibling.getId()),
|
|
253
253
|
]);
|
|
254
254
|
else if (event.key.length === 1) {
|
|
255
|
-
const now = performance.now(), char = normalize(event.key), previous = now - typeahead.current.time < 700 ? typeahead.current.text :
|
|
255
|
+
const now = performance.now(), char = normalize(event.key), previous = now - typeahead.current.time < 700 ? typeahead.current.text : '';
|
|
256
256
|
const prefix = previous === char ? char : previous + char;
|
|
257
257
|
typeahead.current = { text: prefix, time: now };
|
|
258
258
|
for (let offset = 1; offset <= items.length; offset++) {
|
|
@@ -271,16 +271,16 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
271
271
|
.getVirtualItems()
|
|
272
272
|
.map((row) => ({ index: row.index, start: row.start }))
|
|
273
273
|
: items.map((_, index) => ({ index, start: index * rowHeight }));
|
|
274
|
-
return (_jsx(ScrollArea, { orientation:
|
|
274
|
+
return (_jsx(ScrollArea, { orientation: 'vertical', label: `${label} scroll area`, tabIndex: -1, ref: viewport, scrollChain: 'contain', rootClassName: ['pui-tree-scroll', className].filter(Boolean).join(' '), dir: dir, rootStyle: { blockSize: height, direction: dir }, className: 'pui-tree-viewport', children: items.length ? (_jsx("div", { ...containerProps, ref: (element) => {
|
|
275
275
|
container.current = element;
|
|
276
276
|
containerProps.ref(element);
|
|
277
|
-
}, dir: dir, className:
|
|
277
|
+
}, dir: dir, className: 'pui-tree', "data-ui-size": scheme, "aria-multiselectable": selectionMode === 'multiple' || undefined, onKeyDown: keydown, onFocusCapture: () => {
|
|
278
278
|
focusWithin.current = true;
|
|
279
279
|
}, onBlurCapture: (event) => {
|
|
280
280
|
if (event.relatedTarget &&
|
|
281
281
|
!event.currentTarget.contains(event.relatedTarget)) {
|
|
282
282
|
focusWithin.current = false;
|
|
283
|
-
typeahead.current.text =
|
|
283
|
+
typeahead.current.text = '';
|
|
284
284
|
}
|
|
285
285
|
}, style: {
|
|
286
286
|
blockSize: virtualized
|
|
@@ -288,24 +288,24 @@ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExp
|
|
|
288
288
|
: items.length * rowHeight,
|
|
289
289
|
}, children: rows.map(({ index, start }) => {
|
|
290
290
|
const item = items[index], node = item.getItemData(), props = item.getProps();
|
|
291
|
-
return (_createElement("div", { ...props, key: item.getId(), className:
|
|
291
|
+
return (_createElement("div", { ...props, key: item.getId(), className: 'pui-tree-item', "data-tree-id": node.id, "aria-disabled": node.disabled || undefined, "aria-selected": node.disabled ? undefined : selected.includes(node.id), title: node.label, onFocus: () => item.setFocused(), onClick: (event) => {
|
|
292
292
|
focusItem(item);
|
|
293
293
|
select(item, event.shiftKey);
|
|
294
294
|
}, style: {
|
|
295
|
-
position:
|
|
295
|
+
position: 'absolute',
|
|
296
296
|
insetInline: 0,
|
|
297
297
|
top: start,
|
|
298
298
|
blockSize: rowHeight,
|
|
299
299
|
paddingInlineStart: `calc(var(--space-2) + ${Math.min(item.getItemMeta().level, 8)} * var(--space-4))`,
|
|
300
300
|
} },
|
|
301
|
-
item.isFolder() ? (_jsx("button", { type:
|
|
301
|
+
item.isFolder() ? (_jsx("button", { type: 'button', className: 'pui-tree-toggle', tabIndex: -1, disabled: node.disabled, "aria-label": `${item.isExpanded() ? 'Collapse' : 'Expand'} ${node.label}`, onClick: (event) => {
|
|
302
302
|
event.stopPropagation();
|
|
303
303
|
focusItem(item);
|
|
304
304
|
if (item.isExpanded())
|
|
305
305
|
item.collapse();
|
|
306
306
|
else
|
|
307
307
|
item.expand();
|
|
308
|
-
}, children: _jsx("svg", { "aria-hidden":
|
|
309
|
-
_jsx("span", { className:
|
|
310
|
-
}) })) : (_jsx("div", { role:
|
|
308
|
+
}, children: _jsx("svg", { "aria-hidden": 'true', width: '14', height: '14', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '1.6', "data-expanded": item.isExpanded() || undefined, children: _jsx("path", { d: 'm9 5 7 7-7 7' }) }) })) : (_jsx("span", { className: 'pui-tree-leaf', "aria-hidden": 'true' })),
|
|
309
|
+
_jsx("span", { className: 'pui-tree-label', children: node.label })));
|
|
310
|
+
}) })) : (_jsx("div", { role: 'status', className: 'pui-tree-empty', children: emptyLabel })) }));
|
|
311
311
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@surflane/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Surflane React components built on Base UI, with data, chart, code and Markdown components.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design-system",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"shiki": "4.5.0"
|
|
102
102
|
},
|
|
103
103
|
"peerDependencies": {
|
|
104
|
-
"@surflane/design-tokens": "^0.
|
|
104
|
+
"@surflane/design-tokens": "^0.2.0",
|
|
105
105
|
"react": "^19.0.0",
|
|
106
106
|
"react-dom": "^19.0.0"
|
|
107
107
|
},
|