@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
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { cn } from "../../lib/utils.js";
|
|
13
13
|
import { Slot } from "../../lib/slot.js";
|
|
14
14
|
const DEFAULT_DELAY_DURATION = 200;
|
|
15
|
+
const TooltipAnchorContext = React.createContext(null);
|
|
15
16
|
function toPlacement(side, align) {
|
|
16
17
|
if (align === "center") return side;
|
|
17
18
|
if (side === "top" || side === "bottom") return `${side} ${align}`;
|
|
@@ -76,9 +77,11 @@ function TooltipContent({
|
|
|
76
77
|
...props
|
|
77
78
|
}) {
|
|
78
79
|
const overlayPortalContainer = useOverlayPortalContainer();
|
|
80
|
+
const anchorRef = React.useContext(TooltipAnchorContext);
|
|
79
81
|
return /* @__PURE__ */ jsx(
|
|
80
82
|
AriaTooltip,
|
|
81
83
|
{
|
|
84
|
+
...anchorRef ? { triggerRef: anchorRef } : null,
|
|
82
85
|
UNSTABLE_portalContainer: overlayPortalContainer,
|
|
83
86
|
placement: toPlacement(side, align),
|
|
84
87
|
offset: sideOffset,
|
|
@@ -119,7 +122,105 @@ function TooltipContent({
|
|
|
119
122
|
}
|
|
120
123
|
);
|
|
121
124
|
}
|
|
125
|
+
const FOCUS_HOST = 'a[href], button, input, select, textarea, summary, [tabindex]:is([role="treeitem"], [role="gridcell"], [role="row"], [role="option"], [role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [role="tab"], [role="link"], [role="button"], [role="checkbox"], [role="radio"], [role="switch"])';
|
|
126
|
+
function isClipping(node) {
|
|
127
|
+
const box = node.querySelector(':scope > [data-slot="typography-content"]') ?? node;
|
|
128
|
+
return box.scrollWidth > box.clientWidth || box.scrollHeight > box.clientHeight;
|
|
129
|
+
}
|
|
130
|
+
function isFocusVisible(node) {
|
|
131
|
+
try {
|
|
132
|
+
return node.matches(":focus-visible");
|
|
133
|
+
} catch {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function EllipsisTooltip({
|
|
138
|
+
anchorRef,
|
|
139
|
+
title
|
|
140
|
+
}) {
|
|
141
|
+
const [open, setOpen] = React.useState(false);
|
|
142
|
+
const [side, setSide] = React.useState("top");
|
|
143
|
+
const timer = React.useRef(void 0);
|
|
144
|
+
React.useEffect(() => {
|
|
145
|
+
const node = anchorRef.current;
|
|
146
|
+
if (!node) return void 0;
|
|
147
|
+
const host = node.matches(FOCUS_HOST) ? node : node.parentElement?.closest(FOCUS_HOST);
|
|
148
|
+
const schedule = (next, delay) => {
|
|
149
|
+
clearTimeout(timer.current);
|
|
150
|
+
timer.current = setTimeout(() => {
|
|
151
|
+
setOpen(next);
|
|
152
|
+
}, delay);
|
|
153
|
+
};
|
|
154
|
+
const show = (delay) => {
|
|
155
|
+
if (!isClipping(node)) return;
|
|
156
|
+
setSide(host && host !== node && host.hasAttribute("data-state") ? "bottom" : "top");
|
|
157
|
+
schedule(true, delay);
|
|
158
|
+
};
|
|
159
|
+
const hide = () => {
|
|
160
|
+
schedule(false, 100);
|
|
161
|
+
};
|
|
162
|
+
const onPointerEnter = () => {
|
|
163
|
+
show(DEFAULT_DELAY_DURATION);
|
|
164
|
+
};
|
|
165
|
+
const onFocusIn = () => {
|
|
166
|
+
const active = document.activeElement;
|
|
167
|
+
if (active && (active === host || node.contains(active)) && isFocusVisible(active)) show(0);
|
|
168
|
+
};
|
|
169
|
+
node.addEventListener("pointerenter", onPointerEnter);
|
|
170
|
+
node.addEventListener("pointerleave", hide);
|
|
171
|
+
node.addEventListener("focusin", onFocusIn);
|
|
172
|
+
node.addEventListener("focusout", hide);
|
|
173
|
+
host?.addEventListener("focusin", onFocusIn);
|
|
174
|
+
host?.addEventListener("focusout", hide);
|
|
175
|
+
return () => {
|
|
176
|
+
clearTimeout(timer.current);
|
|
177
|
+
node.removeEventListener("pointerenter", onPointerEnter);
|
|
178
|
+
node.removeEventListener("pointerleave", hide);
|
|
179
|
+
node.removeEventListener("focusin", onFocusIn);
|
|
180
|
+
node.removeEventListener("focusout", hide);
|
|
181
|
+
host?.removeEventListener("focusin", onFocusIn);
|
|
182
|
+
host?.removeEventListener("focusout", hide);
|
|
183
|
+
};
|
|
184
|
+
}, [anchorRef]);
|
|
185
|
+
React.useEffect(() => {
|
|
186
|
+
if (!open) return void 0;
|
|
187
|
+
const onKeyDown = (event) => {
|
|
188
|
+
if (event.key === "Escape") {
|
|
189
|
+
clearTimeout(timer.current);
|
|
190
|
+
setOpen(false);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
document.addEventListener("keydown", onKeyDown);
|
|
194
|
+
return () => {
|
|
195
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
196
|
+
};
|
|
197
|
+
}, [open]);
|
|
198
|
+
return /* @__PURE__ */ jsx(
|
|
199
|
+
Tooltip,
|
|
200
|
+
{
|
|
201
|
+
open,
|
|
202
|
+
onOpenChange: (next) => {
|
|
203
|
+
if (!next) setOpen(false);
|
|
204
|
+
},
|
|
205
|
+
children: /* @__PURE__ */ jsx(TooltipAnchorContext.Provider, { value: anchorRef, children: /* @__PURE__ */ jsx(
|
|
206
|
+
TooltipContent,
|
|
207
|
+
{
|
|
208
|
+
side,
|
|
209
|
+
"data-ellipsis-tooltip": "",
|
|
210
|
+
onPointerEnter: () => {
|
|
211
|
+
clearTimeout(timer.current);
|
|
212
|
+
},
|
|
213
|
+
onPointerLeave: () => {
|
|
214
|
+
setOpen(false);
|
|
215
|
+
},
|
|
216
|
+
children: title
|
|
217
|
+
}
|
|
218
|
+
) })
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
}
|
|
122
222
|
export {
|
|
223
|
+
EllipsisTooltip,
|
|
123
224
|
Tooltip,
|
|
124
225
|
TooltipContent,
|
|
125
226
|
TooltipProvider,
|
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { Check, Copy, LoaderCircle, Pencil } from "lucide-react";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
6
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
7
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
|
|
7
|
+
import { EllipsisTooltip, Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
|
|
8
8
|
import { Textarea } from "../data-entry/textarea.js";
|
|
9
9
|
import { isDevelopment } from "../../lib/dev.js";
|
|
10
10
|
const COPIED_RESET_MS = 3e3;
|
|
@@ -347,7 +347,9 @@ function useTypographyBlock(props, allowRows) {
|
|
|
347
347
|
wrapsContent,
|
|
348
348
|
onTextClick: enableEdit && triggerType.includes("text") ? startEditing : void 0,
|
|
349
349
|
ellipsisTooltip,
|
|
350
|
-
isEllipsis
|
|
350
|
+
isEllipsis,
|
|
351
|
+
anchorRef: elementRef,
|
|
352
|
+
showsEllipsisTooltip: clamping && ellipsisTooltip !== void 0 && ellipsisTooltip !== null && ellipsisTooltip !== false && ellipsisTooltip !== ""
|
|
351
353
|
};
|
|
352
354
|
}
|
|
353
355
|
function usesBlockBehaviour(props) {
|
|
@@ -409,7 +411,11 @@ const TextBase = React.forwardRef((props, ref) => {
|
|
|
409
411
|
italic: _italic,
|
|
410
412
|
...rest
|
|
411
413
|
} = props;
|
|
412
|
-
const
|
|
414
|
+
const asChildElement = asChild && React.isValidElement(children) ? children : null;
|
|
415
|
+
const block = useTypographyBlock(
|
|
416
|
+
asChildElement ? { ...props, children: asChildElement.props.children } : props,
|
|
417
|
+
allowRows
|
|
418
|
+
);
|
|
413
419
|
const element = as ?? component ?? "span";
|
|
414
420
|
const clampLines = typeof clamp === "number" && Number.isFinite(clamp) && clamp >= 1 ? Math.floor(clamp) : void 0;
|
|
415
421
|
const ellipsisRowCount = ellipsis ? ellipsisRows(ellipsis) : 0;
|
|
@@ -492,21 +498,29 @@ const TextBase = React.forwardRef((props, ref) => {
|
|
|
492
498
|
) : block.text,
|
|
493
499
|
block.trailing
|
|
494
500
|
] });
|
|
501
|
+
let rendered;
|
|
495
502
|
if (asChild) {
|
|
496
503
|
const child = React.Children.only(children);
|
|
497
504
|
const cloned = {
|
|
498
505
|
...typography,
|
|
499
506
|
...child.props,
|
|
500
|
-
ref,
|
|
507
|
+
// The measuring ref too, or an `asChild` ellipsis never learns it is clipping (gh#1045).
|
|
508
|
+
ref: composeRefs(block.measureRef, ref),
|
|
501
509
|
className: cn(typography.className, child.props.className)
|
|
502
510
|
};
|
|
503
|
-
|
|
511
|
+
rendered = usesBlockBehaviour(props) ? React.cloneElement(child, cloned, body) : React.cloneElement(child, cloned);
|
|
512
|
+
} else {
|
|
513
|
+
rendered = React.createElement(
|
|
514
|
+
element,
|
|
515
|
+
{ ref: composeRefs(block.measureRef, ref), ...typography },
|
|
516
|
+
body
|
|
517
|
+
);
|
|
504
518
|
}
|
|
505
|
-
return
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
);
|
|
519
|
+
if (!block.showsEllipsisTooltip) return rendered;
|
|
520
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
521
|
+
rendered,
|
|
522
|
+
/* @__PURE__ */ jsx(EllipsisTooltip, { anchorRef: block.anchorRef, title: block.ellipsisTooltip })
|
|
523
|
+
] });
|
|
510
524
|
});
|
|
511
525
|
TextBase.displayName = "TextBase";
|
|
512
526
|
const Text = React.forwardRef((props, ref) => /* @__PURE__ */ jsx(TextBase, { ref, ...props }));
|
|
@@ -615,7 +629,7 @@ const Title = React.forwardRef((props, ref) => {
|
|
|
615
629
|
) : block.text,
|
|
616
630
|
block.trailing
|
|
617
631
|
] });
|
|
618
|
-
|
|
632
|
+
const rendered = React.createElement(
|
|
619
633
|
element,
|
|
620
634
|
{
|
|
621
635
|
ref: composeRefs(block.measureRef, ref),
|
|
@@ -635,6 +649,11 @@ const Title = React.forwardRef((props, ref) => {
|
|
|
635
649
|
},
|
|
636
650
|
body
|
|
637
651
|
);
|
|
652
|
+
if (!block.showsEllipsisTooltip) return rendered;
|
|
653
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
654
|
+
rendered,
|
|
655
|
+
/* @__PURE__ */ jsx(EllipsisTooltip, { anchorRef: block.anchorRef, title: block.ellipsisTooltip })
|
|
656
|
+
] });
|
|
638
657
|
});
|
|
639
658
|
Title.displayName = "Title";
|
|
640
659
|
const TypographyRoot = React.forwardRef(
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ReactElement } from "react";
|
|
2
|
+
import type { BreadcrumbItemProp, BreadcrumbProp } from "../../props/vocabulary/navigation.prop.js";
|
|
3
|
+
/** `data-ellipsis` for the `<ol>`: present when any crumb truncates, so the trail stays one line. */
|
|
4
|
+
export declare function trailEllipsis(items: BreadcrumbProp): "" | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* gh#1046 — one crumb's text box, bounded to one line with "…", and the full label in a tooltip on
|
|
7
|
+
* hover and on keyboard focus of the link. The crumb element itself is the clipped box (never a
|
|
8
|
+
* wrapper around it), so the link's own focus ring is not clipped. Shared with PageContainer's
|
|
9
|
+
* trail, which renders its own markup.
|
|
10
|
+
*/
|
|
11
|
+
export declare function BreadcrumbCrumbText({ item, children, }: {
|
|
12
|
+
item: BreadcrumbItemProp;
|
|
13
|
+
children: ReactElement<{
|
|
14
|
+
className?: string;
|
|
15
|
+
}>;
|
|
16
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cloneElement, useRef } from "react";
|
|
4
|
+
import { cn } from "../../lib/utils.js";
|
|
5
|
+
import { EllipsisTooltip } from "../feedback/tooltip.js";
|
|
6
|
+
function trailEllipsis(items) {
|
|
7
|
+
return items.some((item) => item.ellipsis) ? "" : void 0;
|
|
8
|
+
}
|
|
9
|
+
function BreadcrumbCrumbText({
|
|
10
|
+
item,
|
|
11
|
+
children
|
|
12
|
+
}) {
|
|
13
|
+
const anchorRef = useRef(null);
|
|
14
|
+
if (!item.ellipsis) return children;
|
|
15
|
+
const tooltip = typeof item.ellipsis === "object" && "tooltip" in item.ellipsis ? item.ellipsis.tooltip === true ? item.label : item.ellipsis.tooltip : item.label;
|
|
16
|
+
const hasTooltip = tooltip !== void 0 && tooltip !== null && tooltip !== false && tooltip !== "";
|
|
17
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
18
|
+
cloneElement(children, {
|
|
19
|
+
ref: anchorRef,
|
|
20
|
+
className: cn(children.props.className, "ui-breadcrumb-ellipsis")
|
|
21
|
+
}),
|
|
22
|
+
hasTooltip ? /* @__PURE__ */ jsx(EllipsisTooltip, { anchorRef, title: tooltip }) : null
|
|
23
|
+
] });
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
BreadcrumbCrumbText,
|
|
27
|
+
trailEllipsis
|
|
28
|
+
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
4
4
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
5
|
+
import { BreadcrumbCrumbText, trailEllipsis } from "./breadcrumb-ellipsis.js";
|
|
5
6
|
import {
|
|
6
7
|
DropdownMenu,
|
|
7
8
|
DropdownMenuContent,
|
|
@@ -17,12 +18,20 @@ function Breadcrumb({
|
|
|
17
18
|
}) {
|
|
18
19
|
const { t } = useTranslation();
|
|
19
20
|
const separatorNode = separator === void 0 ? /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" }) : separator;
|
|
20
|
-
return /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel ?? t("navigation.breadcrumb.ariaLabel"), className: "ui-breadcrumb", children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", children: items.map((item, index) => {
|
|
21
|
+
return /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel ?? t("navigation.breadcrumb.ariaLabel"), className: "ui-breadcrumb", children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", "data-ellipsis": trailEllipsis(items), children: items.map((item, index) => {
|
|
21
22
|
const isLast = index === items.length - 1;
|
|
22
|
-
return /* @__PURE__ */ jsxs(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
return /* @__PURE__ */ jsxs(
|
|
24
|
+
"li",
|
|
25
|
+
{
|
|
26
|
+
className: "ui-breadcrumb-item",
|
|
27
|
+
"data-ellipsis": item.ellipsis ? "" : void 0,
|
|
28
|
+
children: [
|
|
29
|
+
renderBreadcrumbSegment({ item, index, isLast, items, itemRender, LinkComponent }),
|
|
30
|
+
!isLast ? /* @__PURE__ */ jsx("span", { className: "ui-breadcrumb-separator", "aria-hidden": "true", children: separatorNode }) : null
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
item.to ?? index
|
|
34
|
+
);
|
|
26
35
|
}) }) });
|
|
27
36
|
}
|
|
28
37
|
function renderBreadcrumbSegment({
|
|
@@ -54,9 +63,9 @@ function renderBreadcrumbSegment({
|
|
|
54
63
|
] });
|
|
55
64
|
}
|
|
56
65
|
if (item.to && !isLast) {
|
|
57
|
-
return /* @__PURE__ */ jsx(LinkComponent, { href: item.to, to: item.to, className: "ui-breadcrumb-link", children: item.label });
|
|
66
|
+
return /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx(LinkComponent, { href: item.to, to: item.to, className: "ui-breadcrumb-link", children: item.label }) });
|
|
58
67
|
}
|
|
59
|
-
return /* @__PURE__ */ jsx("span", { className: "ui-breadcrumb-current", "aria-current": isLast ? "page" : void 0, children: item.label });
|
|
68
|
+
return /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx("span", { className: "ui-breadcrumb-current", "aria-current": isLast ? "page" : void 0, children: item.label }) });
|
|
60
69
|
}
|
|
61
70
|
export {
|
|
62
71
|
Breadcrumb
|
|
@@ -5,6 +5,7 @@ import { ChevronRight } from "lucide-react";
|
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { scrollParent, useInView } from "../../lib/hooks.js";
|
|
7
7
|
import { cn } from "../../lib/utils.js";
|
|
8
|
+
import { BreadcrumbCrumbText, trailEllipsis } from "./breadcrumb-ellipsis.js";
|
|
8
9
|
import { densityClass, pageContainerVariantClass, padStyle } from "../../lib/variants.js";
|
|
9
10
|
function useFooterReveal(enabled) {
|
|
10
11
|
const headerRef = useRef(null);
|
|
@@ -92,7 +93,7 @@ function PageContainerRoot({
|
|
|
92
93
|
{
|
|
93
94
|
"aria-label": breadcrumbLabel ?? breadcrumbAriaLabel ?? t("navigation.breadcrumb.ariaLabel"),
|
|
94
95
|
className: "ui-breadcrumb",
|
|
95
|
-
children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", children: breadcrumb.map((item, i) => {
|
|
96
|
+
children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", "data-ellipsis": trailEllipsis(breadcrumb), children: breadcrumb.map((item, i) => {
|
|
96
97
|
const isLast = i === breadcrumb.length - 1;
|
|
97
98
|
return (
|
|
98
99
|
// `--space-inline-xs` gap the previous `.ui-inline-xs` gave, and it brings
|
|
@@ -100,25 +101,33 @@ function PageContainerRoot({
|
|
|
100
101
|
// longer carries a `size-3` literal. It also drops `.ui-inline-xs`'s
|
|
101
102
|
// `flex-wrap: wrap` — a crumb must not break between its label and its
|
|
102
103
|
// chevron; the LIST wraps instead, exactly as <Breadcrumb> already behaves.
|
|
103
|
-
/* @__PURE__ */ jsxs(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
children:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
104
|
+
/* @__PURE__ */ jsxs(
|
|
105
|
+
"li",
|
|
106
|
+
{
|
|
107
|
+
className: "ui-breadcrumb-item",
|
|
108
|
+
"data-ellipsis": item.ellipsis ? "" : void 0,
|
|
109
|
+
children: [
|
|
110
|
+
item.to && !isLast ? /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx(
|
|
111
|
+
LinkComponent,
|
|
112
|
+
{
|
|
113
|
+
href: item.to,
|
|
114
|
+
to: item.to,
|
|
115
|
+
className: "hover:text-foreground hover:underline",
|
|
116
|
+
children: item.label
|
|
117
|
+
}
|
|
118
|
+
) }) : /* @__PURE__ */ jsx(BreadcrumbCrumbText, { item, children: /* @__PURE__ */ jsx(
|
|
119
|
+
"span",
|
|
120
|
+
{
|
|
121
|
+
className: isLast ? "text-foreground" : "",
|
|
122
|
+
"aria-current": isLast ? "page" : void 0,
|
|
123
|
+
children: item.label
|
|
124
|
+
}
|
|
125
|
+
) }),
|
|
126
|
+
!isLast && /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" })
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
i
|
|
130
|
+
)
|
|
122
131
|
);
|
|
123
132
|
}) })
|
|
124
133
|
}
|
|
@@ -27,4 +27,4 @@ export declare function flattenItemPages<TItem>(data: {
|
|
|
27
27
|
*/
|
|
28
28
|
export declare function InfiniteQueryState<TPage, TFlat = TPage extends {
|
|
29
29
|
items: (infer TItem)[];
|
|
30
|
-
} ? TItem[] : unknown>({ query, skeleton, empty, flatten, isEmpty, errorRenderer, showRetry, onRetry, onAuthError, loadingMore, loadMore, showLoadMore, children, }: InfiniteQueryStateProp<TPage, TFlat>): React.JSX.Element;
|
|
30
|
+
} ? TItem[] : unknown>({ query, skeleton, empty, flatten, isEmpty, errorRenderer, showRetry, onRetry, onAuthError, loadingMore, loadMore, loadMoreLabel, showLoadMore, children, }: InfiniteQueryStateProp<TPage, TFlat>): React.JSX.Element;
|
|
@@ -26,6 +26,7 @@ function InfiniteQueryState({
|
|
|
26
26
|
onAuthError,
|
|
27
27
|
loadingMore,
|
|
28
28
|
loadMore,
|
|
29
|
+
loadMoreLabel,
|
|
29
30
|
showLoadMore = true,
|
|
30
31
|
children
|
|
31
32
|
}) {
|
|
@@ -73,7 +74,7 @@ function InfiniteQueryState({
|
|
|
73
74
|
size: "sm",
|
|
74
75
|
disabled: query.isFetchingNextPage,
|
|
75
76
|
onClick: () => void query.fetchNextPage(),
|
|
76
|
-
children: query.isFetchingNextPage ? t("common.working") : t("query.loadMore")
|
|
77
|
+
children: query.isFetchingNextPage ? t("common.working") : loadMoreLabel ?? t("query.loadMore")
|
|
77
78
|
}
|
|
78
79
|
) }) : null;
|
|
79
80
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
|
|
3
|
-
"version": "31.
|
|
3
|
+
"version": "31.7.0",
|
|
4
4
|
"targetSize": {
|
|
5
5
|
"standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
|
|
6
6
|
"min": 24,
|
|
@@ -350,7 +350,8 @@
|
|
|
350
350
|
},
|
|
351
351
|
"progress": {
|
|
352
352
|
"ariaLabel": "Progress",
|
|
353
|
-
"breakdownSeparator": ", "
|
|
353
|
+
"breakdownSeparator": ", ",
|
|
354
|
+
"remainder": "Remaining"
|
|
354
355
|
},
|
|
355
356
|
"scrollArea": {
|
|
356
357
|
"region": "Scrollable region"
|
|
@@ -360,7 +361,8 @@
|
|
|
360
361
|
"collapse": "Collapse",
|
|
361
362
|
"loading": "Loading child nodes…",
|
|
362
363
|
"empty": "Nothing to show",
|
|
363
|
-
"selected": "Selected"
|
|
364
|
+
"selected": "Selected",
|
|
365
|
+
"filterMatch": "Matches the filter"
|
|
364
366
|
},
|
|
365
367
|
"thoughtChain": {
|
|
366
368
|
"label": "Reasoning steps",
|
|
@@ -585,7 +587,20 @@
|
|
|
585
587
|
"sortDescending": "Sort descending",
|
|
586
588
|
"sortCancel": "Cancel sort",
|
|
587
589
|
"selectColumn": "Select",
|
|
588
|
-
"scrollRegion": "Scrollable table"
|
|
590
|
+
"scrollRegion": "Scrollable table",
|
|
591
|
+
"selectPage": "Select all rows on this page",
|
|
592
|
+
"matchingPage": {
|
|
593
|
+
"one": "{count} row on this page is selected.",
|
|
594
|
+
"other": "All {count} rows on this page are selected."
|
|
595
|
+
},
|
|
596
|
+
"matchingSelectAll": {
|
|
597
|
+
"one": "Select {count} matching row",
|
|
598
|
+
"other": "Select all {count} matching rows"
|
|
599
|
+
},
|
|
600
|
+
"matchingSelected": {
|
|
601
|
+
"one": "{count} matching row is selected.",
|
|
602
|
+
"other": "All {count} matching rows are selected."
|
|
603
|
+
}
|
|
589
604
|
},
|
|
590
605
|
"dataGrid": {
|
|
591
606
|
"search": "Search",
|
|
@@ -343,7 +343,8 @@
|
|
|
343
343
|
},
|
|
344
344
|
"progress": {
|
|
345
345
|
"ariaLabel": "進捗",
|
|
346
|
-
"breakdownSeparator": "、"
|
|
346
|
+
"breakdownSeparator": "、",
|
|
347
|
+
"remainder": "残り"
|
|
347
348
|
},
|
|
348
349
|
"scrollArea": {
|
|
349
350
|
"region": "スクロール可能な領域"
|
|
@@ -353,7 +354,8 @@
|
|
|
353
354
|
"collapse": "折りたたむ",
|
|
354
355
|
"loading": "子ノードを読み込み中…",
|
|
355
356
|
"empty": "表示する項目がありません",
|
|
356
|
-
"selected": "選択中"
|
|
357
|
+
"selected": "選択中",
|
|
358
|
+
"filterMatch": "フィルターに一致"
|
|
357
359
|
},
|
|
358
360
|
"thoughtChain": {
|
|
359
361
|
"label": "思考の手順",
|
|
@@ -572,7 +574,11 @@
|
|
|
572
574
|
"sortDescending": "降順に並べ替え",
|
|
573
575
|
"sortCancel": "並べ替えを解除",
|
|
574
576
|
"selectColumn": "選択",
|
|
575
|
-
"scrollRegion": "スクロール可能な表"
|
|
577
|
+
"scrollRegion": "スクロール可能な表",
|
|
578
|
+
"selectPage": "このページの行をすべて選択",
|
|
579
|
+
"matchingPage": "このページの{count}件を選択中",
|
|
580
|
+
"matchingSelectAll": "{count}件すべてを選択",
|
|
581
|
+
"matchingSelected": "{count}件すべてを選択中"
|
|
576
582
|
},
|
|
577
583
|
"dataGrid": {
|
|
578
584
|
"search": "検索",
|
|
@@ -344,7 +344,8 @@
|
|
|
344
344
|
},
|
|
345
345
|
"progress": {
|
|
346
346
|
"ariaLabel": "Tiến độ",
|
|
347
|
-
"breakdownSeparator": ", "
|
|
347
|
+
"breakdownSeparator": ", ",
|
|
348
|
+
"remainder": "Còn lại"
|
|
348
349
|
},
|
|
349
350
|
"scrollArea": {
|
|
350
351
|
"region": "Vùng có thể cuộn"
|
|
@@ -354,7 +355,8 @@
|
|
|
354
355
|
"collapse": "Thu gọn",
|
|
355
356
|
"loading": "Đang tải nút con…",
|
|
356
357
|
"empty": "Không có gì để hiển thị",
|
|
357
|
-
"selected": "Đã chọn"
|
|
358
|
+
"selected": "Đã chọn",
|
|
359
|
+
"filterMatch": "Khớp bộ lọc"
|
|
358
360
|
},
|
|
359
361
|
"thoughtChain": {
|
|
360
362
|
"label": "Các bước suy luận",
|
|
@@ -573,7 +575,11 @@
|
|
|
573
575
|
"sortDescending": "Sắp xếp giảm dần",
|
|
574
576
|
"sortCancel": "Bỏ sắp xếp",
|
|
575
577
|
"selectColumn": "Chọn",
|
|
576
|
-
"scrollRegion": "Bảng có thể cuộn"
|
|
578
|
+
"scrollRegion": "Bảng có thể cuộn",
|
|
579
|
+
"selectPage": "Chọn tất cả các dòng trên trang này",
|
|
580
|
+
"matchingPage": "Đã chọn {count} dòng trên trang này.",
|
|
581
|
+
"matchingSelectAll": "Chọn tất cả {count} dòng khớp",
|
|
582
|
+
"matchingSelected": "Đã chọn tất cả {count} dòng khớp."
|
|
577
583
|
},
|
|
578
584
|
"dataGrid": {
|
|
579
585
|
"search": "Tìm kiếm",
|
package/dist/lib/hooks.d.ts
CHANGED
|
@@ -53,12 +53,16 @@ export declare function useScrollableRegionTabIndex(element: HTMLElement | null)
|
|
|
53
53
|
* overflows. Reading it as "no overflow" would strand the overflow from every keyboard user — a
|
|
54
54
|
* worse failure than an extra tab stop — so an unmeasured box counts as scrolling.
|
|
55
55
|
*
|
|
56
|
-
* `axis`
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* `axis` names the axes the caller KNOWS scroll; an axis whose computed `overflow` is `auto` or
|
|
57
|
+
* `scroll` is measured as well, because the box scrolls there whatever the caller asked. An axis
|
|
58
|
+
* that is `hidden` CLIPS rather than scrolls, so it is never measured — that would keep a tab stop
|
|
59
|
+
* that scrolls nothing. `ScrollArea`'s `orientation` is exactly this union.
|
|
59
60
|
*/
|
|
60
61
|
export declare function useScrollsOnAxis(ref: RefObject<HTMLElement | null>, enabled: boolean, axis: "horizontal" | "vertical" | "both"): boolean;
|
|
61
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* The horizontal question a table asks (gh#817). The block axis is measured TOO whenever the box's
|
|
64
|
+
* computed `overflow-y` scrolls — `overflow-x: auto` computes it to `auto` — see `useScrollsOnAxis`.
|
|
65
|
+
*/
|
|
62
66
|
export declare function useScrollsHorizontally(ref: RefObject<HTMLElement | null>, enabled: boolean): boolean;
|
|
63
67
|
/**
|
|
64
68
|
* Nearest scrollable ancestor — the box an element actually scrolls inside — else `null`, which is
|
package/dist/lib/hooks.js
CHANGED
|
@@ -111,9 +111,12 @@ function useScrollsOnAxis(ref, enabled, axis) {
|
|
|
111
111
|
useEffect(() => {
|
|
112
112
|
const el = ref.current;
|
|
113
113
|
if (!enabled || !el) return void 0;
|
|
114
|
-
const overflows = (client, scroll) =>
|
|
114
|
+
const overflows = (client, scroll) => scroll - client > 1;
|
|
115
|
+
const scrollsOn = (value) => value === "auto" || value === "scroll";
|
|
115
116
|
const update = (sync) => {
|
|
116
|
-
const
|
|
117
|
+
const unmeasured = el.clientWidth === 0 && el.clientHeight === 0;
|
|
118
|
+
const style = getComputedStyle(el);
|
|
119
|
+
const next = unmeasured || (axis !== "vertical" || scrollsOn(style.overflowX)) && overflows(el.clientWidth, el.scrollWidth) || (axis !== "horizontal" || scrollsOn(style.overflowY)) && overflows(el.clientHeight, el.scrollHeight);
|
|
117
120
|
if (next && sync) flushSync(() => setScrolls(true));
|
|
118
121
|
else setScrolls(next);
|
|
119
122
|
};
|
package/dist/lib/tree.d.ts
CHANGED
|
@@ -50,4 +50,27 @@ export declare function filterVisibleTree(options: NormalizedTreeOption[], query
|
|
|
50
50
|
}[];
|
|
51
51
|
export declare function collectAllExpandableKeys(options: NormalizedTreeOption[]): string[];
|
|
52
52
|
export declare function findNodeByValue(options: NormalizedTreeOption[], value: string): NormalizedTreeOption | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* rc-tree gives up on a node after this many rejected `loadData` calls and treats it as loaded
|
|
55
|
+
* (rc-tree `MAX_RETRY_TIMES`), so a permanently broken endpoint cannot be hammered forever.
|
|
56
|
+
*/
|
|
57
|
+
export declare const MAX_LAZY_LOAD_RETRIES = 10;
|
|
58
|
+
/**
|
|
59
|
+
* The lazy-children ledger shared by `Tree`, `TreeSelect` and `Cascader` — rc-tree's
|
|
60
|
+
* `loadedKeys` / `loadingKeys` / `loadingRetryTimes`, in one place (gh#1041).
|
|
61
|
+
*
|
|
62
|
+
* A key is asked for at most once while in flight and never again once it RESOLVED. A REJECTED
|
|
63
|
+
* load is not "loaded": the key leaves the in-flight set and the next expand asks again, until
|
|
64
|
+
* `MAX_LAZY_LOAD_RETRIES` failures. The old per-component ledgers recorded the key before the
|
|
65
|
+
* promise settled and never cleared it, so one network blip left a branch unloadable for the life
|
|
66
|
+
* of the component.
|
|
67
|
+
*/
|
|
68
|
+
export type LazyLoadLedger = {
|
|
69
|
+
/**
|
|
70
|
+
* Call `load` for `key` unless it is in flight or already loaded. A synchronous throw counts
|
|
71
|
+
* as a rejection. `onSettle(ok)` runs once the load settles. Returns whether `load` was called.
|
|
72
|
+
*/
|
|
73
|
+
run: (key: string, load: () => void | Promise<void>, onSettle?: (ok: boolean) => void) => boolean;
|
|
74
|
+
};
|
|
75
|
+
export declare function createLazyLoadLedger(): LazyLoadLedger;
|
|
53
76
|
export {};
|
package/dist/lib/tree.js
CHANGED
|
@@ -136,10 +136,39 @@ function findNodeByValue(options, value) {
|
|
|
136
136
|
}
|
|
137
137
|
return void 0;
|
|
138
138
|
}
|
|
139
|
+
const MAX_LAZY_LOAD_RETRIES = 10;
|
|
140
|
+
function createLazyLoadLedger() {
|
|
141
|
+
const loaded = /* @__PURE__ */ new Set();
|
|
142
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
143
|
+
const failures = /* @__PURE__ */ new Map();
|
|
144
|
+
return {
|
|
145
|
+
run(key, load, onSettle) {
|
|
146
|
+
if (loaded.has(key) || inFlight.has(key)) return false;
|
|
147
|
+
inFlight.add(key);
|
|
148
|
+
new Promise((resolve) => resolve(load())).then(
|
|
149
|
+
() => {
|
|
150
|
+
inFlight.delete(key);
|
|
151
|
+
loaded.add(key);
|
|
152
|
+
onSettle?.(true);
|
|
153
|
+
},
|
|
154
|
+
() => {
|
|
155
|
+
inFlight.delete(key);
|
|
156
|
+
const count = (failures.get(key) ?? 0) + 1;
|
|
157
|
+
failures.set(key, count);
|
|
158
|
+
if (count >= MAX_LAZY_LOAD_RETRIES) loaded.add(key);
|
|
159
|
+
onSettle?.(false);
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
139
166
|
export {
|
|
167
|
+
MAX_LAZY_LOAD_RETRIES,
|
|
140
168
|
collectAllExpandableKeys,
|
|
141
169
|
collectAllPaths,
|
|
142
170
|
collectLeafPaths,
|
|
171
|
+
createLazyLoadLedger,
|
|
143
172
|
filterTreeOptions,
|
|
144
173
|
filterVisibleTree,
|
|
145
174
|
findNodeByValue,
|