@godxjp/ui 31.5.0 → 31.7.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/agent/START-HERE.md +5 -5
- package/agent/components/BranchScopePicker.json +5 -0
- package/agent/components/Breadcrumb.json +1 -1
- package/agent/components/Cascader.json +1 -1
- package/agent/components/DataTable.json +2 -2
- package/agent/components/InfiniteQueryState.json +6 -1
- package/agent/components/PageContainer.json +1 -1
- package/agent/components/Progress.json +12 -1
- package/agent/components/RangeTimeline.json +6 -0
- package/agent/components/RecordPicker.json +10 -0
- package/agent/components/Text.json +1 -1
- package/agent/components/Transfer.json +5 -0
- package/agent/components/Tree.json +21 -4
- package/agent/components/TreeSelect.json +1 -1
- package/agent/components.json +72 -13
- package/agent/index.json +5 -5
- package/agent/llms.txt +6 -6
- package/agent/tokens.json +12 -0
- package/dist/components/data-display/data-table.d.ts +3 -0
- package/dist/components/data-display/data-table.js +415 -370
- package/dist/components/data-display/progress.d.ts +16 -0
- package/dist/components/data-display/progress.js +17 -3
- package/dist/components/data-display/range-timeline.d.ts +20 -0
- package/dist/components/data-display/range-timeline.js +217 -197
- package/dist/components/data-display/tree.js +179 -23
- package/dist/components/data-entry/branch-scope-picker.d.ts +1 -0
- package/dist/components/data-entry/branch-scope-picker.js +2 -1
- package/dist/components/data-entry/cascader.js +4 -5
- package/dist/components/data-entry/record-picker.d.ts +2 -0
- package/dist/components/data-entry/record-picker.js +22 -3
- package/dist/components/data-entry/transfer.d.ts +1 -1
- package/dist/components/data-entry/transfer.js +5 -2
- package/dist/components/data-entry/tree-select.js +14 -5
- package/dist/components/feedback/tooltip.d.ts +21 -0
- package/dist/components/feedback/tooltip.js +101 -0
- package/dist/components/general/typography.js +30 -11
- package/dist/components/layout/breadcrumb-ellipsis.d.ts +16 -0
- package/dist/components/layout/breadcrumb-ellipsis.js +28 -0
- package/dist/components/layout/breadcrumb.js +16 -7
- package/dist/components/layout/page-container.js +29 -20
- package/dist/components/query/infinite-query-state.d.ts +1 -1
- package/dist/components/query/infinite-query-state.js +2 -1
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +18 -3
- package/dist/i18n/messages/ja.json +9 -3
- package/dist/i18n/messages/vi.json +9 -3
- package/dist/lib/hooks.d.ts +8 -4
- package/dist/lib/hooks.js +5 -2
- package/dist/lib/tree.d.ts +23 -0
- package/dist/lib/tree.js +29 -0
- package/dist/props/components/data-display.prop.d.ts +21 -2
- package/dist/props/components/data-entry.prop.d.ts +31 -4
- package/dist/props/components/query.prop.d.ts +8 -1
- package/dist/props/registry.d.ts +29 -6
- package/dist/props/registry.js +46 -4
- package/dist/props/vocabulary/data.prop.d.ts +32 -2
- package/dist/props/vocabulary/index.d.ts +2 -2
- package/dist/props/vocabulary/navigation.prop.d.ts +14 -0
- package/dist/styles/data-display-layout.css +52 -0
- package/dist/styles/layers.json +3 -2
- package/dist/styles/layout.css +22 -0
- package/dist/styles/table-layout.css +24 -1
- package/dist/tokens/components/navigation.css +2 -0
- package/dist/tokens/components/tree.css +2 -0
- package/docs/data-display/data-table/examples/server-paged.tsx +23 -0
- package/docs/data-display/data-table/index.md +2 -0
- package/docs/data-display/progress.tsx +24 -6
- package/docs/data-display/timeline.tsx +33 -0
- package/docs/data-display/tree.tsx +154 -0
- package/docs/data-entry/branch-scope-picker.tsx +6 -1
- package/docs/data-entry/record-picker.tsx +1 -0
- package/docs/data-entry/transfer.tsx +1 -0
- package/docs/general/typography.tsx +84 -1
- package/docs/i18n/messages/en.json +48 -1
- package/docs/i18n/messages/ja.json +47 -1
- package/docs/i18n/messages/vi.json +47 -1
- package/docs/navigation/breadcrumb.tsx +24 -0
- package/docs/query/infinite-query-state.tsx +5 -1
- package/docs/roadmap/tree-components.md +2 -1
- package/package.json +2 -2
- package/scripts/ui-audit.mjs +15 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { mergeRefs } from "@react-aria/utils";
|
|
4
5
|
import { ChevronDown, ChevronRight, File as FileIcon, Folder, FolderOpen } from "lucide-react";
|
|
5
6
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
7
|
import { cn } from "../../lib/utils.js";
|
|
@@ -8,6 +9,7 @@ import { CheckboxVisual } from "../data-entry/checkbox.js";
|
|
|
8
9
|
import { Skeleton } from "../feedback/skeleton.js";
|
|
9
10
|
import {
|
|
10
11
|
collectAllExpandableKeys,
|
|
12
|
+
createLazyLoadLedger,
|
|
11
13
|
flattenVisibleTree,
|
|
12
14
|
getDescendantValues,
|
|
13
15
|
normalizeTreeOptions,
|
|
@@ -44,6 +46,7 @@ function indexTree(nodes, into = /* @__PURE__ */ new Map()) {
|
|
|
44
46
|
function isRtl(element) {
|
|
45
47
|
return element.closest("[dir]")?.getAttribute("dir")?.toLowerCase() === "rtl";
|
|
46
48
|
}
|
|
49
|
+
const VIRTUAL_OVERSCAN = 6;
|
|
47
50
|
function TreeRoot({
|
|
48
51
|
treeData,
|
|
49
52
|
fieldNames,
|
|
@@ -62,6 +65,9 @@ function TreeRoot({
|
|
|
62
65
|
defaultExpandAll = false,
|
|
63
66
|
loadData,
|
|
64
67
|
titleRender,
|
|
68
|
+
filterTreeNode,
|
|
69
|
+
height,
|
|
70
|
+
virtual = true,
|
|
65
71
|
showLine = false,
|
|
66
72
|
showIcon = false,
|
|
67
73
|
divided = false,
|
|
@@ -114,21 +120,29 @@ function TreeRoot({
|
|
|
114
120
|
if (!isCheckedControlled) setInternalChecked(settled);
|
|
115
121
|
onCheckedValuesChange?.(settled);
|
|
116
122
|
};
|
|
117
|
-
const
|
|
123
|
+
const [lazyLoads] = React.useState(createLazyLoadLedger);
|
|
124
|
+
const loadedFor = React.useRef(/* @__PURE__ */ new Set());
|
|
118
125
|
const [loadingValues, setLoadingValues] = React.useState(() => /* @__PURE__ */ new Set());
|
|
119
126
|
const requestLoad = (node) => {
|
|
120
127
|
if (!loadData) return;
|
|
121
128
|
if ((node.children?.length ?? 0) > 0 || node.isLeaf === true) return;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
const started = lazyLoads.run(
|
|
130
|
+
node.value,
|
|
131
|
+
() => loadData(node),
|
|
132
|
+
(ok) => {
|
|
133
|
+
setLoadingValues((prev) => {
|
|
134
|
+
const next = new Set(prev);
|
|
135
|
+
next.delete(node.value);
|
|
136
|
+
return next;
|
|
137
|
+
});
|
|
138
|
+
if (ok) return;
|
|
139
|
+
loadedFor.current.delete(node.value);
|
|
140
|
+
if (!isExpandedControlled) {
|
|
141
|
+
setInternalExpanded((prev) => prev.filter((entry) => entry !== node.value));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
if (started) setLoadingValues((prev) => new Set(prev).add(node.value));
|
|
132
146
|
};
|
|
133
147
|
const requestLoadRef = React.useRef(requestLoad);
|
|
134
148
|
requestLoadRef.current = requestLoad;
|
|
@@ -143,7 +157,6 @@ function TreeRoot({
|
|
|
143
157
|
walk(options);
|
|
144
158
|
return map;
|
|
145
159
|
}, [options]);
|
|
146
|
-
const loadedFor = React.useRef(/* @__PURE__ */ new Set());
|
|
147
160
|
React.useEffect(() => {
|
|
148
161
|
if (!loadData) return;
|
|
149
162
|
for (const value2 of expandedSet) {
|
|
@@ -157,6 +170,17 @@ function TreeRoot({
|
|
|
157
170
|
const visibleIndex = new Map(visible.map((entry, index) => [entry.node.value, index]));
|
|
158
171
|
const [activeValue, setActiveValue] = React.useState(null);
|
|
159
172
|
const rovingValue = (activeValue && visibleIndex.has(activeValue) ? activeValue : null) ?? visible.find((entry) => selected.includes(entry.node.value))?.node.value ?? visible[0]?.node.value ?? null;
|
|
173
|
+
const isWindowed = height !== void 0 && virtual;
|
|
174
|
+
const treeRef = React.useRef(null);
|
|
175
|
+
const [scrollTop, setScrollTop] = React.useState(0);
|
|
176
|
+
const [rowHeight, setRowHeight] = React.useState(0);
|
|
177
|
+
const [hasFocusWithin, setHasFocusWithin] = React.useState(false);
|
|
178
|
+
React.useLayoutEffect(() => {
|
|
179
|
+
if (!isWindowed) return;
|
|
180
|
+
const firstRow = treeRef.current?.querySelector('[role="treeitem"]');
|
|
181
|
+
const measured = firstRow?.offsetHeight ?? 0;
|
|
182
|
+
if (measured > 0 && measured !== rowHeight) setRowHeight(measured);
|
|
183
|
+
}, [isWindowed, rowHeight, size, visible.length]);
|
|
160
184
|
const nodeRefs = React.useRef(/* @__PURE__ */ new Map());
|
|
161
185
|
const pendingFocus = React.useRef(null);
|
|
162
186
|
React.useEffect(() => {
|
|
@@ -165,9 +189,23 @@ function TreeRoot({
|
|
|
165
189
|
pendingFocus.current = null;
|
|
166
190
|
nodeRefs.current.get(target)?.focus();
|
|
167
191
|
});
|
|
192
|
+
const revealRow = (value2) => {
|
|
193
|
+
const element = treeRef.current;
|
|
194
|
+
const rowIndex = rowIndexOf.get(value2);
|
|
195
|
+
if (!isWindowed || !element || rowHeight <= 0 || rowIndex === void 0) return;
|
|
196
|
+
const viewport = element.clientHeight || height || 0;
|
|
197
|
+
const top = rowIndex * rowHeight;
|
|
198
|
+
let next = element.scrollTop;
|
|
199
|
+
if (top < next) next = top;
|
|
200
|
+
else if (top + rowHeight > next + viewport) next = top + rowHeight - viewport;
|
|
201
|
+
if (next === element.scrollTop) return;
|
|
202
|
+
element.scrollTop = next;
|
|
203
|
+
setScrollTop(next);
|
|
204
|
+
};
|
|
168
205
|
const moveTo = (index) => {
|
|
169
206
|
const entry = visible[index];
|
|
170
207
|
if (!entry) return;
|
|
208
|
+
revealRow(entry.node.value);
|
|
171
209
|
setActiveValue(entry.node.value);
|
|
172
210
|
pendingFocus.current = entry.node.value;
|
|
173
211
|
};
|
|
@@ -318,19 +356,18 @@ function TreeRoot({
|
|
|
318
356
|
jumpByLabel(event.key, index);
|
|
319
357
|
}
|
|
320
358
|
};
|
|
321
|
-
const
|
|
359
|
+
const renderItem = (node, depth, setSize, posInSet, groupId) => {
|
|
322
360
|
const hasChildren = (node.children?.length ?? 0) > 0 && node.isLeaf !== true;
|
|
323
361
|
const expandable = expandableOf(node);
|
|
324
362
|
const isOpen = expandable && expandedSet.has(node.value);
|
|
325
363
|
const isLoading = loadingValues.has(node.value);
|
|
326
364
|
const isSelected = selected.includes(node.value);
|
|
365
|
+
const isFilterMatch = filterTreeNode ? filterTreeNode(node) : false;
|
|
327
366
|
const checkState = checkableProp ? checkStateOf(node) : "unchecked";
|
|
328
367
|
const nodeDisabled = disabled || Boolean(node.disabled);
|
|
329
368
|
const labelId = `${treeId}-${node.value}-label`;
|
|
330
|
-
const groupId = `${treeId}-${node.value}-group`;
|
|
331
|
-
const showGroup = isOpen && (hasChildren || isLoading);
|
|
332
369
|
const glyph = node.icon ?? (variant === "directory" ? hasChildren ? isOpen ? /* @__PURE__ */ jsx(FolderOpen, {}) : /* @__PURE__ */ jsx(Folder, {}) : /* @__PURE__ */ jsx(FileIcon, {}) : null);
|
|
333
|
-
|
|
370
|
+
return /* @__PURE__ */ jsxs(
|
|
334
371
|
"div",
|
|
335
372
|
{
|
|
336
373
|
ref: (element) => {
|
|
@@ -339,11 +376,11 @@ function TreeRoot({
|
|
|
339
376
|
role: "treeitem",
|
|
340
377
|
"aria-labelledby": labelId,
|
|
341
378
|
"aria-level": depth + 1,
|
|
342
|
-
"aria-setsize":
|
|
343
|
-
"aria-posinset":
|
|
379
|
+
"aria-setsize": setSize,
|
|
380
|
+
"aria-posinset": posInSet,
|
|
344
381
|
"aria-selected": isSelected,
|
|
345
382
|
"aria-expanded": expandable ? isOpen : void 0,
|
|
346
|
-
"aria-owns":
|
|
383
|
+
"aria-owns": groupId,
|
|
347
384
|
"aria-checked": checkableProp ? checkState === "indeterminate" ? "mixed" : checkState === "checked" : void 0,
|
|
348
385
|
"aria-disabled": nodeDisabled || void 0,
|
|
349
386
|
"aria-busy": isLoading || void 0,
|
|
@@ -357,9 +394,19 @@ function TreeRoot({
|
|
|
357
394
|
"data-value": node.value,
|
|
358
395
|
"data-selected": isSelected ? "true" : void 0,
|
|
359
396
|
"data-disabled": nodeDisabled ? "" : void 0,
|
|
397
|
+
"data-filter-node": isFilterMatch ? "true" : void 0,
|
|
360
398
|
className: "ui-tree-node ui-focus-ring",
|
|
361
399
|
style: { "--tree-node-level": depth },
|
|
362
400
|
children: [
|
|
401
|
+
isWindowed && showLine ? Array.from({ length: depth }, (_, level) => /* @__PURE__ */ jsx(
|
|
402
|
+
"span",
|
|
403
|
+
{
|
|
404
|
+
"aria-hidden": "true",
|
|
405
|
+
className: "ui-tree-rail",
|
|
406
|
+
style: { "--tree-rail-level": level + 1 }
|
|
407
|
+
},
|
|
408
|
+
`rail-${level}`
|
|
409
|
+
)) : null,
|
|
363
410
|
/* @__PURE__ */ jsx(
|
|
364
411
|
"span",
|
|
365
412
|
{
|
|
@@ -395,11 +442,26 @@ function TreeRoot({
|
|
|
395
442
|
) : null,
|
|
396
443
|
showIcon && glyph ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ui-tree-icon", children: glyph }) : null,
|
|
397
444
|
/* @__PURE__ */ jsx("span", { id: labelId, className: "ui-tree-label", children: titleRender ? titleRender(node) : node.label }),
|
|
398
|
-
isSelected ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("dataDisplay.tree.selected") }) : null
|
|
445
|
+
isSelected ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("dataDisplay.tree.selected") }) : null,
|
|
446
|
+
isFilterMatch ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("dataDisplay.tree.filterMatch") }) : null
|
|
399
447
|
]
|
|
400
448
|
},
|
|
401
449
|
node.value
|
|
402
450
|
);
|
|
451
|
+
};
|
|
452
|
+
const renderNodes = (nodes, depth) => nodes.flatMap((node, position) => {
|
|
453
|
+
const hasChildren = (node.children?.length ?? 0) > 0 && node.isLeaf !== true;
|
|
454
|
+
const isOpen = expandableOf(node) && expandedSet.has(node.value);
|
|
455
|
+
const isLoading = loadingValues.has(node.value);
|
|
456
|
+
const groupId = `${treeId}-${node.value}-group`;
|
|
457
|
+
const showGroup = isOpen && (hasChildren || isLoading);
|
|
458
|
+
const item = renderItem(
|
|
459
|
+
node,
|
|
460
|
+
depth,
|
|
461
|
+
nodes.length,
|
|
462
|
+
position + 1,
|
|
463
|
+
showGroup ? groupId : void 0
|
|
464
|
+
);
|
|
403
465
|
if (!showGroup) return [item];
|
|
404
466
|
return [
|
|
405
467
|
item,
|
|
@@ -426,13 +488,68 @@ function TreeRoot({
|
|
|
426
488
|
)
|
|
427
489
|
];
|
|
428
490
|
});
|
|
491
|
+
const flatRows = [];
|
|
492
|
+
if (isWindowed) {
|
|
493
|
+
const walk = (nodes, depth) => {
|
|
494
|
+
nodes.forEach((node, position) => {
|
|
495
|
+
flatRows.push({
|
|
496
|
+
kind: "node",
|
|
497
|
+
node,
|
|
498
|
+
depth,
|
|
499
|
+
setSize: nodes.length,
|
|
500
|
+
posInSet: position + 1
|
|
501
|
+
});
|
|
502
|
+
if (!expandableOf(node) || !expandedSet.has(node.value)) return;
|
|
503
|
+
if ((node.children?.length ?? 0) > 0 && node.isLeaf !== true)
|
|
504
|
+
walk(node.children, depth + 1);
|
|
505
|
+
else if (loadingValues.has(node.value)) {
|
|
506
|
+
flatRows.push({ kind: "loading", key: `${node.value}::loading`, depth: depth + 1 });
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
};
|
|
510
|
+
walk(options, 0);
|
|
511
|
+
}
|
|
512
|
+
const rowIndexOf = /* @__PURE__ */ new Map();
|
|
513
|
+
flatRows.forEach((row, index) => {
|
|
514
|
+
if (row.kind === "node") rowIndexOf.set(row.node.value, index);
|
|
515
|
+
});
|
|
516
|
+
let windowStart = 0;
|
|
517
|
+
let windowEnd = flatRows.length;
|
|
518
|
+
if (isWindowed && rowHeight > 0 && height !== void 0) {
|
|
519
|
+
const first = Math.floor(scrollTop / rowHeight);
|
|
520
|
+
windowStart = Math.max(0, first - VIRTUAL_OVERSCAN);
|
|
521
|
+
windowEnd = Math.min(flatRows.length, first + Math.ceil(height / rowHeight) + VIRTUAL_OVERSCAN);
|
|
522
|
+
const focusedRow = rovingValue === null ? void 0 : rowIndexOf.get(rovingValue);
|
|
523
|
+
if (hasFocusWithin && focusedRow !== void 0) {
|
|
524
|
+
windowStart = Math.min(windowStart, focusedRow);
|
|
525
|
+
windowEnd = Math.max(windowEnd, focusedRow + 1);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const rovingRow = rovingValue === null ? void 0 : rowIndexOf.get(rovingValue);
|
|
529
|
+
const rovingOffscreen = isWindowed && rovingRow !== void 0 && (rovingRow < windowStart || rovingRow >= windowEnd);
|
|
429
530
|
const isEmpty = visible.length === 0;
|
|
531
|
+
const body = isEmpty ? null : isWindowed ? flatRows.slice(windowStart, windowEnd).map(
|
|
532
|
+
(row) => row.kind === "node" ? renderItem(row.node, row.depth, row.setSize, row.posInSet, void 0) : (
|
|
533
|
+
// A windowed tree has no `group` to hold the placeholder, and `role="tree"` may own
|
|
534
|
+
// only treeitems — so the skeleton is decoration and `aria-busy` on the row speaks.
|
|
535
|
+
/* @__PURE__ */ jsx(
|
|
536
|
+
"div",
|
|
537
|
+
{
|
|
538
|
+
"aria-hidden": "true",
|
|
539
|
+
className: "ui-tree-loading",
|
|
540
|
+
style: { "--tree-node-level": row.depth },
|
|
541
|
+
children: /* @__PURE__ */ jsx(Skeleton, { className: "ui-tree-loading-bar" })
|
|
542
|
+
},
|
|
543
|
+
row.key
|
|
544
|
+
)
|
|
545
|
+
)
|
|
546
|
+
) : renderNodes(options, 0);
|
|
430
547
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
431
|
-
/* @__PURE__ */
|
|
548
|
+
/* @__PURE__ */ jsxs(
|
|
432
549
|
"div",
|
|
433
550
|
{
|
|
434
551
|
...ariaProps,
|
|
435
|
-
ref: forwardedRef,
|
|
552
|
+
ref: mergeRefs(forwardedRef, treeRef),
|
|
436
553
|
id: treeId,
|
|
437
554
|
role: "tree",
|
|
438
555
|
"aria-multiselectable": multiple || checkableProp,
|
|
@@ -442,8 +559,47 @@ function TreeRoot({
|
|
|
442
559
|
"data-show-line": showLine ? "true" : void 0,
|
|
443
560
|
"data-divided": divided ? "true" : void 0,
|
|
444
561
|
"data-empty": isEmpty ? "true" : void 0,
|
|
562
|
+
"data-virtual": isWindowed ? "true" : void 0,
|
|
445
563
|
className: cn("ui-tree", className),
|
|
446
|
-
|
|
564
|
+
tabIndex: rovingOffscreen ? 0 : void 0,
|
|
565
|
+
style: height === void 0 ? void 0 : {
|
|
566
|
+
maxBlockSize: height,
|
|
567
|
+
overflowY: "auto"
|
|
568
|
+
},
|
|
569
|
+
onScroll: isWindowed ? (event) => setScrollTop(event.currentTarget.scrollTop) : void 0,
|
|
570
|
+
onFocus: (event) => {
|
|
571
|
+
if (!isWindowed) return;
|
|
572
|
+
setHasFocusWithin(true);
|
|
573
|
+
if (event.target === event.currentTarget && rovingValue !== null) {
|
|
574
|
+
revealRow(rovingValue);
|
|
575
|
+
pendingFocus.current = rovingValue;
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
onBlur: (event) => {
|
|
579
|
+
if (!isWindowed) return;
|
|
580
|
+
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
581
|
+
setHasFocusWithin(false);
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
children: [
|
|
585
|
+
isWindowed ? /* @__PURE__ */ jsx(
|
|
586
|
+
"div",
|
|
587
|
+
{
|
|
588
|
+
"aria-hidden": "true",
|
|
589
|
+
className: "ui-tree-spacer",
|
|
590
|
+
style: { blockSize: windowStart * rowHeight }
|
|
591
|
+
}
|
|
592
|
+
) : null,
|
|
593
|
+
body,
|
|
594
|
+
isWindowed ? /* @__PURE__ */ jsx(
|
|
595
|
+
"div",
|
|
596
|
+
{
|
|
597
|
+
"aria-hidden": "true",
|
|
598
|
+
className: "ui-tree-spacer",
|
|
599
|
+
style: { blockSize: (flatRows.length - windowEnd) * rowHeight }
|
|
600
|
+
}
|
|
601
|
+
) : null
|
|
602
|
+
]
|
|
447
603
|
}
|
|
448
604
|
),
|
|
449
605
|
isEmpty ? /* @__PURE__ */ jsx("p", { role: "status", className: "ui-tree-empty", children: t("dataDisplay.tree.empty") }) : null
|
|
@@ -16,6 +16,7 @@ export declare const BranchScopePicker: React.ForwardRefExoticComponent<import("
|
|
|
16
16
|
error?: import("../../props/index.js").ErrorProp;
|
|
17
17
|
loading?: boolean;
|
|
18
18
|
empty?: React.ReactNode;
|
|
19
|
+
notFoundContent?: import("../../props/index.js").NotFoundContentProp;
|
|
19
20
|
listError?: React.ReactNode;
|
|
20
21
|
denied?: React.ReactNode;
|
|
21
22
|
allLabel?: React.ReactNode;
|
|
@@ -25,6 +25,7 @@ const BranchScopePicker = React.forwardRef(
|
|
|
25
25
|
error,
|
|
26
26
|
loading = false,
|
|
27
27
|
empty,
|
|
28
|
+
notFoundContent,
|
|
28
29
|
listError,
|
|
29
30
|
denied,
|
|
30
31
|
allLabel,
|
|
@@ -156,7 +157,7 @@ const BranchScopePicker = React.forwardRef(
|
|
|
156
157
|
ariaLabel: t("dataEntry.branchScope.searchPlaceholder")
|
|
157
158
|
}
|
|
158
159
|
),
|
|
159
|
-
filtered.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", "aria-live": "polite", children: t("dataEntry.branchScope.noMatches") }) : (
|
|
160
|
+
filtered.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "xs", tone: "muted", "aria-live": "polite", children: notFoundContent ?? t("dataEntry.branchScope.noMatches") }) : (
|
|
160
161
|
// A bounded list is a scroll container, so it MUST be reachable and scrollable
|
|
161
162
|
// with the keyboard alone even when every row is unfocusable (all checkboxes
|
|
162
163
|
// `disabled`) — WCAG 2.1.1 / axe `scrollable-region-focusable`; same contract as
|
|
@@ -16,6 +16,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover
|
|
|
16
16
|
import { ScrollArea } from "../data-display/scroll-area.js";
|
|
17
17
|
import { Command, CommandInput } from "./command.js";
|
|
18
18
|
import {
|
|
19
|
+
createLazyLoadLedger,
|
|
19
20
|
filterTreeOptions,
|
|
20
21
|
formatPathLabels,
|
|
21
22
|
getNodeByPath,
|
|
@@ -175,18 +176,16 @@ function Cascader({
|
|
|
175
176
|
},
|
|
176
177
|
[isSearchControlled, onSearchChange]
|
|
177
178
|
);
|
|
178
|
-
const
|
|
179
|
+
const [lazyLoads] = React.useState(createLazyLoadLedger);
|
|
179
180
|
const requestLoad = React.useCallback(
|
|
180
181
|
(path, chain) => {
|
|
181
182
|
if (!loadData) return;
|
|
182
183
|
const node = chain.at(-1);
|
|
183
184
|
if (!node || node.isLeaf === true || (node.children?.length ?? 0) > 0) return;
|
|
184
185
|
const key = pathKey(path);
|
|
185
|
-
|
|
186
|
-
requestedLoads.current.add(key);
|
|
187
|
-
void loadData(chain);
|
|
186
|
+
lazyLoads.run(key, () => loadData(chain));
|
|
188
187
|
},
|
|
189
|
-
[loadData]
|
|
188
|
+
[loadData, lazyLoads]
|
|
190
189
|
);
|
|
191
190
|
const isControlledSingle = !multiple && value !== void 0;
|
|
192
191
|
const isControlledMulti = multiple && value !== void 0;
|
|
@@ -85,6 +85,8 @@ export declare const RecordPicker: React.ForwardRefExoticComponent<Omit<React.Bu
|
|
|
85
85
|
};
|
|
86
86
|
placeholder?: string;
|
|
87
87
|
dialogTitle?: string;
|
|
88
|
+
notFoundContent?: import("../../props/index.js").NotFoundContentProp;
|
|
89
|
+
loadMoreLabel?: import("../../props/index.js").LabelProp;
|
|
88
90
|
size?: "xs" | "sm" | "md" | "lg";
|
|
89
91
|
"data-field"?: string;
|
|
90
92
|
} & React.RefAttributes<HTMLButtonElement | HTMLInputElement>>;
|
|
@@ -50,6 +50,8 @@ const RecordPicker = React.forwardRef(
|
|
|
50
50
|
emptyOption,
|
|
51
51
|
placeholder,
|
|
52
52
|
dialogTitle,
|
|
53
|
+
notFoundContent,
|
|
54
|
+
loadMoreLabel,
|
|
53
55
|
disabled = false,
|
|
54
56
|
size,
|
|
55
57
|
className,
|
|
@@ -90,6 +92,8 @@ const RecordPicker = React.forwardRef(
|
|
|
90
92
|
emptyOption,
|
|
91
93
|
placeholder,
|
|
92
94
|
dialogTitle,
|
|
95
|
+
notFoundContent,
|
|
96
|
+
loadMoreLabel,
|
|
93
97
|
disabled,
|
|
94
98
|
size,
|
|
95
99
|
className,
|
|
@@ -115,6 +119,7 @@ const RecordPicker = React.forwardRef(
|
|
|
115
119
|
value: isMultiple ? selected : selected[0] ?? void 0,
|
|
116
120
|
onValueChange: ((next) => commit(toArray(next))),
|
|
117
121
|
placeholder: placeholder ?? t("dataEntry.recordPicker.placeholder"),
|
|
122
|
+
notFoundContent,
|
|
118
123
|
disabled,
|
|
119
124
|
size,
|
|
120
125
|
className
|
|
@@ -136,6 +141,8 @@ const RecordPicker = React.forwardRef(
|
|
|
136
141
|
emptyOption,
|
|
137
142
|
placeholder,
|
|
138
143
|
dialogTitle,
|
|
144
|
+
notFoundContent,
|
|
145
|
+
loadMoreLabel,
|
|
139
146
|
disabled,
|
|
140
147
|
size,
|
|
141
148
|
className,
|
|
@@ -164,6 +171,8 @@ const InlinePicker = React.forwardRef(function InlinePicker2({
|
|
|
164
171
|
emptyOption,
|
|
165
172
|
placeholder,
|
|
166
173
|
dialogTitle,
|
|
174
|
+
notFoundContent,
|
|
175
|
+
loadMoreLabel,
|
|
167
176
|
disabled,
|
|
168
177
|
size,
|
|
169
178
|
className,
|
|
@@ -295,6 +304,8 @@ const InlinePicker = React.forwardRef(function InlinePicker2({
|
|
|
295
304
|
selectedOptions,
|
|
296
305
|
emptyOption,
|
|
297
306
|
dialogTitle,
|
|
307
|
+
notFoundContent,
|
|
308
|
+
loadMoreLabel,
|
|
298
309
|
initialQuery: dialogQuery,
|
|
299
310
|
autoOpen: true,
|
|
300
311
|
onDismiss: () => setDialogQuery(null)
|
|
@@ -313,6 +324,8 @@ const DialogPicker = React.forwardRef(function DialogPicker2({
|
|
|
313
324
|
emptyOption,
|
|
314
325
|
placeholder,
|
|
315
326
|
dialogTitle,
|
|
327
|
+
notFoundContent,
|
|
328
|
+
loadMoreLabel,
|
|
316
329
|
disabled,
|
|
317
330
|
size,
|
|
318
331
|
className,
|
|
@@ -479,11 +492,17 @@ const DialogPicker = React.forwardRef(function DialogPicker2({
|
|
|
479
492
|
variant: "outline",
|
|
480
493
|
size: "sm",
|
|
481
494
|
onClick: () => void load(query, filterValues),
|
|
482
|
-
children: t("
|
|
495
|
+
children: t("common.retry")
|
|
483
496
|
}
|
|
484
497
|
)
|
|
485
498
|
}
|
|
486
|
-
) : status === "loading" ? /* @__PURE__ */ jsx("div", { role: "status", className: "ui-record-picker-status", children: t("dataEntry.recordPicker.loading") }) : visible.length === 0 ? /* @__PURE__ */ jsx(
|
|
499
|
+
) : status === "loading" ? /* @__PURE__ */ jsx("div", { role: "status", className: "ui-record-picker-status", children: t("dataEntry.recordPicker.loading") }) : visible.length === 0 ? /* @__PURE__ */ jsx(
|
|
500
|
+
EmptyState,
|
|
501
|
+
{
|
|
502
|
+
variant: "compact",
|
|
503
|
+
title: notFoundContent ?? t("dataEntry.recordPicker.empty")
|
|
504
|
+
}
|
|
505
|
+
) : groups.map(([heading, items]) => {
|
|
487
506
|
const rendered = items.map((o) => {
|
|
488
507
|
const picked = (isMultiple ? draft : selected).includes(o.value);
|
|
489
508
|
return /* @__PURE__ */ jsxs(
|
|
@@ -516,7 +535,7 @@ const DialogPicker = React.forwardRef(function DialogPicker2({
|
|
|
516
535
|
variant: "outline",
|
|
517
536
|
size: "sm",
|
|
518
537
|
onClick: () => void load(query, filterValues, cursor),
|
|
519
|
-
children: t("dataEntry.recordPicker.more")
|
|
538
|
+
children: loadMoreLabel ?? t("dataEntry.recordPicker.more")
|
|
520
539
|
}
|
|
521
540
|
) : null
|
|
522
541
|
] }) }),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { TransferProp } from "../../props/components/data-entry.prop.js";
|
|
3
3
|
export type { TransferProp, TransferProp as TransferProps, TransferItemProp, } from "../../props/components/data-entry.prop.js";
|
|
4
|
-
export declare function Transfer({ dataSource, targetKeys: legacyTargetKeys, value, defaultValue, name, readOnly, pagination, defaultTargetKeys, onValueChange, titles, showSearch, showSelectAll, filterOption, render, oneWay, disabled, id, className, selectedKeys: selectedKeysProp, onSelectChange, ...ariaProps }: TransferProp): React.JSX.Element;
|
|
4
|
+
export declare function Transfer({ dataSource, targetKeys: legacyTargetKeys, value, defaultValue, name, readOnly, pagination, defaultTargetKeys, onValueChange, titles, showSearch, showSelectAll, filterOption, render, oneWay, disabled, id, className, selectedKeys: selectedKeysProp, onSelectChange, locale, ...ariaProps }: TransferProp): React.JSX.Element;
|
|
@@ -158,6 +158,7 @@ function Transfer({
|
|
|
158
158
|
className,
|
|
159
159
|
selectedKeys: selectedKeysProp,
|
|
160
160
|
onSelectChange,
|
|
161
|
+
locale,
|
|
161
162
|
...ariaProps
|
|
162
163
|
}) {
|
|
163
164
|
const { t } = useTranslation();
|
|
@@ -192,6 +193,8 @@ function Transfer({
|
|
|
192
193
|
if (!selectedKeysProp) setInternalSelected(cleared);
|
|
193
194
|
onSelectChange?.(cleared[0], cleared[1]);
|
|
194
195
|
};
|
|
196
|
+
const notFound = locale?.notFoundContent;
|
|
197
|
+
const [leftNotFound, rightNotFound] = Array.isArray(notFound) ? notFound : [notFound, notFound];
|
|
195
198
|
const leftTitle = titles?.[0] ?? t("dataEntry.transfer.source");
|
|
196
199
|
const rightTitle = titles?.[1] ?? t("dataEntry.transfer.target");
|
|
197
200
|
return /* @__PURE__ */ jsxs(
|
|
@@ -219,7 +222,7 @@ function Transfer({
|
|
|
219
222
|
disabled: disabled || readOnly,
|
|
220
223
|
pagination,
|
|
221
224
|
searchPlaceholder: t("dataEntry.transfer.searchPlaceholder"),
|
|
222
|
-
emptyText: t("dataEntry.transfer.empty")
|
|
225
|
+
emptyText: leftNotFound ?? t("dataEntry.transfer.empty")
|
|
223
226
|
}
|
|
224
227
|
),
|
|
225
228
|
/* @__PURE__ */ jsxs("div", { className: "ui-transfer-actions", children: [
|
|
@@ -263,7 +266,7 @@ function Transfer({
|
|
|
263
266
|
disabled: disabled || readOnly,
|
|
264
267
|
pagination,
|
|
265
268
|
searchPlaceholder: t("dataEntry.transfer.searchPlaceholder"),
|
|
266
|
-
emptyText: t("dataEntry.transfer.empty")
|
|
269
|
+
emptyText: rightNotFound ?? t("dataEntry.transfer.empty")
|
|
267
270
|
}
|
|
268
271
|
)
|
|
269
272
|
]
|
|
@@ -19,6 +19,7 @@ import { SearchInput } from "./search-input.js";
|
|
|
19
19
|
import { Separator } from "../layout/separator.js";
|
|
20
20
|
import {
|
|
21
21
|
collectAllExpandableKeys,
|
|
22
|
+
createLazyLoadLedger,
|
|
22
23
|
filterVisibleTree,
|
|
23
24
|
findNodeByValue,
|
|
24
25
|
flattenVisibleTree,
|
|
@@ -134,15 +135,23 @@ function TreeSelectRoot({
|
|
|
134
135
|
if (!isControlled) setInternal(next);
|
|
135
136
|
onValueChange?.(checkable || multiple ? next : next[0] ?? void 0);
|
|
136
137
|
};
|
|
137
|
-
const
|
|
138
|
+
const [lazyLoads] = React.useState(createLazyLoadLedger);
|
|
138
139
|
const toggleExpand = (node) => {
|
|
139
140
|
const key = node.value;
|
|
140
141
|
const willExpand = !expandedKeys.has(key);
|
|
141
142
|
if (willExpand && loadData && !(node.children?.length ?? 0) && node.isLeaf !== true) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
lazyLoads.run(
|
|
144
|
+
key,
|
|
145
|
+
() => loadData(node),
|
|
146
|
+
(ok) => {
|
|
147
|
+
if (ok) return;
|
|
148
|
+
setExpandedKeys((prev) => {
|
|
149
|
+
const next = new Set(prev);
|
|
150
|
+
next.delete(key);
|
|
151
|
+
return next;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
);
|
|
146
155
|
}
|
|
147
156
|
setExpandedKeys((prev) => {
|
|
148
157
|
const next = new Set(prev);
|
|
@@ -51,4 +51,25 @@ interface TooltipContentProps extends React.ComponentPropsWithRef<"div"> {
|
|
|
51
51
|
forceMount?: true;
|
|
52
52
|
}
|
|
53
53
|
export declare function TooltipContent({ className, style, children, ref, side, align, sideOffset, alignOffset, avoidCollisions, collisionPadding, sticky: _sticky, hideWhenDetached: _hideWhenDetached, forceMount: _forceMount, ...props }: TooltipContentProps): React.JSX.Element;
|
|
54
|
+
/**
|
|
55
|
+
* antd's ellipsis tooltip (`Base/Ellipsis` + `useTooltipProps`), ported for gh#1045 / gh#1046.
|
|
56
|
+
* Module-internal (not in the public index): `Text`/`Paragraph`/`Title` `ellipsis.tooltip` and
|
|
57
|
+
* `BreadcrumbItemProp.ellipsis` render it. It lives here, not in typography, so Breadcrumb does not
|
|
58
|
+
* pull the Typography module (and its Textarea) into its bundle and stylesheet layers.
|
|
59
|
+
*
|
|
60
|
+
* Open while the anchor is ACTUALLY clipping — measured at the moment of the pointer or the focus,
|
|
61
|
+
* so a box that fits never shows one — on pointer hover of the anchor, and on keyboard focus of the
|
|
62
|
+
* anchor itself (`Text asChild` link, a breadcrumb link) or of the nearest focusable ANCESTOR that is
|
|
63
|
+
* a control (`treeitem`, a sort `button`, a link). The anchor never becomes focusable, so a Text in
|
|
64
|
+
* a treeitem stays inside that item's single tab stop, and the accessible name stays the full text
|
|
65
|
+
* node — the truncation is paint only. Escape dismisses (WCAG 1.4.13); hovering the tooltip keeps
|
|
66
|
+
* it open.
|
|
67
|
+
*
|
|
68
|
+
* When the focus host is ALREADY a tooltip trigger (a DataTable sort button carries the sort hint on
|
|
69
|
+
* top), this one opens underneath so the two never cover each other.
|
|
70
|
+
*/
|
|
71
|
+
export declare function EllipsisTooltip({ anchorRef, title, }: {
|
|
72
|
+
anchorRef: React.RefObject<HTMLElement | null>;
|
|
73
|
+
title: React.ReactNode;
|
|
74
|
+
}): React.JSX.Element;
|
|
54
75
|
export {};
|