@tensor_1/react 2.46.0 → 2.48.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 +509 -1
- package/dist/{SchedulingCalendar-B6TakO2h.d.ts → SchedulingCalendar-WWyFL3Eo.d.ts} +51 -2
- package/dist/announce.d.ts +10 -0
- package/dist/announce.js +30 -0
- package/dist/chart-analytic.cjs +33 -25
- package/dist/chart-preset.cjs +23 -15
- package/dist/chart.cjs +23 -15
- package/dist/combobox-model.d.ts +10 -1
- package/dist/combobox-model.js +15 -0
- package/dist/command-palette-model.d.ts +44 -0
- package/dist/command-palette-model.js +36 -0
- package/dist/components/AccountMenu.d.ts +44 -0
- package/dist/components/AccountMenu.js +43 -0
- package/dist/components/AppShell.d.ts +45 -2
- package/dist/components/AppShell.js +116 -5
- package/dist/components/AuditLog.d.ts +128 -0
- package/dist/components/AuditLog.js +196 -0
- package/dist/components/BrandProfiles.js +4 -3
- package/dist/components/Breadcrumbs.d.ts +9 -2
- package/dist/components/Breadcrumbs.js +3 -2
- package/dist/components/BusyBar.d.ts +13 -0
- package/dist/components/BusyBar.js +10 -0
- package/dist/components/BusyRegion.d.ts +18 -0
- package/dist/components/BusyRegion.js +12 -0
- package/dist/components/Button.d.ts +33 -5
- package/dist/components/Button.js +12 -6
- package/dist/components/ButtonFeedback.d.ts +30 -0
- package/dist/components/ButtonFeedback.js +78 -0
- package/dist/components/Card.d.ts +18 -1
- package/dist/components/Card.js +5 -1
- package/dist/components/ChangeHighlight.d.ts +30 -0
- package/dist/components/ChangeHighlight.js +21 -0
- package/dist/components/ChartCore.js +2 -1
- package/dist/components/Checkbox.d.ts +27 -1
- package/dist/components/Checkbox.js +31 -2
- package/dist/components/CodeDisplay.d.ts +65 -0
- package/dist/components/CodeDisplay.js +124 -0
- package/dist/components/Combobox.d.ts +27 -6
- package/dist/components/Combobox.js +195 -58
- package/dist/components/CommandPalette.d.ts +36 -1
- package/dist/components/CommandPalette.js +254 -2
- package/dist/components/ContactCard.d.ts +66 -0
- package/dist/components/ContactCard.js +74 -0
- package/dist/components/Count.d.ts +24 -0
- package/dist/components/Count.js +32 -0
- package/dist/components/DataWorkspace.d.ts +167 -0
- package/dist/components/DataWorkspace.js +383 -0
- package/dist/components/DateFieldParts.d.ts +2 -1
- package/dist/components/DateFieldParts.js +7 -0
- package/dist/components/DatePicker.d.ts +9 -2
- package/dist/components/DatePicker.js +67 -28
- package/dist/components/DateRangePicker.d.ts +12 -2
- package/dist/components/DateRangePicker.js +13 -12
- package/dist/components/DelayedRender.d.ts +15 -0
- package/dist/components/DelayedRender.js +10 -0
- package/dist/components/DetailPage.d.ts +7 -1
- package/dist/components/DetailPage.js +2 -1
- package/dist/components/Dialogs.d.ts +71 -10
- package/dist/components/Dialogs.js +130 -30
- package/dist/components/DiffViewer.d.ts +23 -3
- package/dist/components/DiffViewer.js +26 -12
- package/dist/components/DiscardPrompt.d.ts +1 -0
- package/dist/components/DiscardPrompt.js +16 -0
- package/dist/components/DisplayPreferences.d.ts +83 -0
- package/dist/components/DisplayPreferences.js +185 -0
- package/dist/components/Drawer.d.ts +1 -0
- package/dist/components/EditorialHome.d.ts +23 -3
- package/dist/components/EditorialHome.js +16 -11
- package/dist/components/EntityPicker.d.ts +20 -3
- package/dist/components/EntityPicker.js +32 -13
- package/dist/components/FilterEditor.d.ts +18 -4
- package/dist/components/FilterEditor.js +141 -19
- package/dist/components/InfoTip.js +11 -12
- package/dist/components/InlineEdit.d.ts +87 -20
- package/dist/components/InlineEdit.js +197 -54
- package/dist/components/Input.d.ts +12 -2
- package/dist/components/Input.js +5 -3
- package/dist/components/Kbd.d.ts +6 -1
- package/dist/components/Kbd.js +6 -6
- package/dist/components/Lightbox.d.ts +41 -5
- package/dist/components/Lightbox.js +95 -9
- package/dist/components/MatchReview.d.ts +40 -1
- package/dist/components/MatchReview.js +130 -4
- package/dist/components/MetricInput.js +3 -2
- package/dist/components/Modal.d.ts +3 -1
- package/dist/components/Modal.js +13 -4
- package/dist/components/NavFavourites.d.ts +5 -0
- package/dist/components/NavFavourites.js +7 -7
- package/dist/components/OtpInput.d.ts +33 -0
- package/dist/components/OtpInput.js +123 -0
- package/dist/components/PageHeader.d.ts +7 -1
- package/dist/components/PageHeader.js +2 -2
- package/dist/components/Pagination.js +6 -5
- package/dist/components/PasswordInput.d.ts +34 -0
- package/dist/components/PasswordInput.js +101 -0
- package/dist/components/Peek.js +3 -3
- package/dist/components/Popover.js +2 -0
- package/dist/components/PrintLayout.d.ts +63 -0
- package/dist/components/PrintLayout.js +114 -0
- package/dist/components/QrCode.d.ts +47 -0
- package/dist/components/QrCode.js +55 -0
- package/dist/components/QrScanner.d.ts +95 -0
- package/dist/components/QrScanner.js +266 -0
- package/dist/components/RelationshipGraph.d.ts +32 -1
- package/dist/components/RelationshipGraph.js +18 -5
- package/dist/components/RelationshipGraphLayered.d.ts +1 -1
- package/dist/components/RelationshipGraphLayered.js +190 -115
- package/dist/components/RelationshipGraphList.js +33 -6
- package/dist/components/RuleBuilder.js +39 -7
- package/dist/components/SaveStatus.d.ts +7 -1
- package/dist/components/SaveStatus.js +13 -4
- package/dist/components/SchedulingAgenda.d.ts +3 -1
- package/dist/components/SchedulingCalendar.d.ts +3 -1
- package/dist/components/SchedulingCalendar.js +43 -4
- package/dist/components/SchedulingResourceDay.d.ts +57 -0
- package/dist/components/SchedulingResourceDay.js +287 -0
- package/dist/components/SchedulingTimeGrid.d.ts +3 -1
- package/dist/components/SchedulingTimeline.d.ts +3 -1
- package/dist/components/SearchBar.d.ts +1 -0
- package/dist/components/SearchSuggest.d.ts +23 -3
- package/dist/components/SearchSuggest.js +48 -17
- package/dist/components/Segmented.d.ts +39 -4
- package/dist/components/Segmented.js +31 -3
- package/dist/components/Select.d.ts +3 -2
- package/dist/components/Select.js +1 -1
- package/dist/components/ShortcutHint.d.ts +11 -10
- package/dist/components/ShortcutHint.js +44 -38
- package/dist/components/Sidebar.d.ts +36 -4
- package/dist/components/Sidebar.js +37 -12
- package/dist/components/SortableList.d.ts +45 -0
- package/dist/components/SortableList.js +134 -0
- package/dist/components/Spinner.d.ts +7 -1
- package/dist/components/Spinner.js +7 -1
- package/dist/components/Stat.d.ts +28 -3
- package/dist/components/Stat.js +13 -3
- package/dist/components/States.d.ts +7 -1
- package/dist/components/States.js +7 -1
- package/dist/components/StatusPill.d.ts +14 -3
- package/dist/components/StatusPill.js +11 -3
- package/dist/components/Steps.js +2 -1
- package/dist/components/Table.d.ts +16 -2
- package/dist/components/Table.js +8 -4
- package/dist/components/TableControls.js +18 -58
- package/dist/components/TableOverflowProbe.d.ts +12 -0
- package/dist/components/TableOverflowProbe.js +27 -0
- package/dist/components/Tabs.d.ts +2 -1
- package/dist/components/Tabs.js +1 -0
- package/dist/components/TagInput.d.ts +67 -0
- package/dist/components/TagInput.js +342 -0
- package/dist/components/Textarea.d.ts +5 -0
- package/dist/components/Textarea.js +2 -2
- package/dist/components/TimePicker.d.ts +80 -0
- package/dist/components/TimePicker.js +347 -0
- package/dist/components/Toast.js +3 -2
- package/dist/components/Tooltip.d.ts +8 -4
- package/dist/components/Tooltip.js +24 -21
- package/dist/components/Tree.d.ts +8 -0
- package/dist/components/Tree.js +11 -5
- package/dist/display-preferences-model.d.ts +53 -0
- package/dist/display-preferences-model.js +64 -0
- package/dist/emoji.cjs +28 -35
- package/dist/filter-model.d.ts +59 -3
- package/dist/filter-model.js +67 -2
- package/dist/hooks/useChangeHighlight.d.ts +33 -0
- package/dist/hooks/useChangeHighlight.js +44 -0
- package/dist/hooks/useDelayedPending.d.ts +22 -0
- package/dist/hooks/useDelayedPending.js +35 -0
- package/dist/hooks/useDialog.d.ts +5 -0
- package/dist/hooks/useDialog.js +3 -2
- package/dist/hooks/useOptimisticValue.d.ts +51 -0
- package/dist/hooks/useOptimisticValue.js +61 -0
- package/dist/hooks/useReducedMotion.d.ts +4 -1
- package/dist/hooks/useReducedMotion.js +7 -1
- package/dist/hooks/useResolvedFilterLabels.d.ts +15 -0
- package/dist/hooks/useResolvedFilterLabels.js +34 -0
- package/dist/hooks/useVirtualRows.d.ts +21 -2
- package/dist/hooks/useVirtualRows.js +30 -3
- package/dist/icons/Icon.d.ts +1 -1
- package/dist/index.cjs +18977 -13378
- package/dist/index.d.cts +3065 -664
- package/dist/index.d.ts +46 -16
- package/dist/index.js +149 -7
- package/dist/labels-context.d.ts +93 -3
- package/dist/motion-timing.d.ts +8 -0
- package/dist/motion-timing.js +8 -0
- package/dist/overlay-stack.d.ts +6 -0
- package/dist/overlay-stack.js +13 -0
- package/dist/qr-model.d.ts +65 -0
- package/dist/qr-model.js +323 -0
- package/dist/relationship-graph-model.d.ts +78 -3
- package/dist/relationship-graph-model.js +112 -5
- package/dist/reorder.d.ts +30 -0
- package/dist/reorder.js +74 -0
- package/dist/rich-text.cjs +4 -2
- package/dist/scheduling-resource-model.d.ts +76 -0
- package/dist/scheduling-resource-model.js +74 -0
- package/dist/search-suggest-model.js +3 -1
- package/dist/shell-context.d.ts +11 -0
- package/dist/shell-context.js +7 -0
- package/dist/time-field-model.d.ts +46 -0
- package/dist/time-field-model.js +152 -0
- package/dist/toast-store.d.ts +17 -1
- package/dist/toast-store.js +23 -7
- package/package.json +2 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
5
|
import { fillLabel } from "../fill-label.js";
|
|
6
6
|
import { useDialog } from "../hooks/useDialog.js";
|
|
7
|
+
import { useUixLabels } from "../labels-context.js";
|
|
7
8
|
const DEFAULT_LIGHTBOX_LABELS = {
|
|
8
9
|
region: "Media viewer",
|
|
9
10
|
previous: "Previous",
|
|
@@ -13,10 +14,42 @@ const DEFAULT_LIGHTBOX_LABELS = {
|
|
|
13
14
|
zoomIn: "Show actual size",
|
|
14
15
|
zoomOut: "Fit to screen"
|
|
15
16
|
};
|
|
17
|
+
function containedBox(media) {
|
|
18
|
+
const boxW = media.clientWidth;
|
|
19
|
+
const boxH = media.clientHeight;
|
|
20
|
+
const naturalW = "naturalWidth" in media ? media.naturalWidth : media.videoWidth;
|
|
21
|
+
const naturalH = "naturalHeight" in media ? media.naturalHeight : media.videoHeight;
|
|
22
|
+
if (!naturalW || !naturalH || !boxW || !boxH) return { left: media.offsetLeft, top: media.offsetTop, width: boxW, height: boxH };
|
|
23
|
+
const scale = Math.min(boxW / naturalW, boxH / naturalH);
|
|
24
|
+
const width = naturalW * scale;
|
|
25
|
+
const height = naturalH * scale;
|
|
26
|
+
return { left: media.offsetLeft + (boxW - width) / 2, top: media.offsetTop + (boxH - height) / 2, width, height };
|
|
27
|
+
}
|
|
16
28
|
const Chevron = ({ dir }) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: dir === "left" ? "m15 18-6-6 6-6" : "m9 18 6-6-6-6" }) });
|
|
17
|
-
function Lightbox({
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
function Lightbox({
|
|
30
|
+
items,
|
|
31
|
+
index,
|
|
32
|
+
onIndexChange,
|
|
33
|
+
open,
|
|
34
|
+
onClose,
|
|
35
|
+
loop = false,
|
|
36
|
+
labels: labelOverrides,
|
|
37
|
+
className,
|
|
38
|
+
actions,
|
|
39
|
+
renderOverlay,
|
|
40
|
+
onKeyDown: onKeyDownProp,
|
|
41
|
+
onEscapeKeyDown
|
|
42
|
+
}) {
|
|
43
|
+
const labels = { ...DEFAULT_LIGHTBOX_LABELS, ...useUixLabels().lightbox, ...labelOverrides };
|
|
44
|
+
const escapeClaimed = useRef(false);
|
|
45
|
+
const ref = useDialog(open, onClose, () => {
|
|
46
|
+
const claimed = escapeClaimed.current;
|
|
47
|
+
escapeClaimed.current = false;
|
|
48
|
+
return claimed;
|
|
49
|
+
});
|
|
50
|
+
const mediaRef = useRef(null);
|
|
51
|
+
const pressedBackdrop = useRef(false);
|
|
52
|
+
const [overlayBox, setOverlayBox] = useState(null);
|
|
20
53
|
const closeRef = useRef(null);
|
|
21
54
|
const returnTo = useRef(null);
|
|
22
55
|
const swipe = useRef(null);
|
|
@@ -37,13 +70,47 @@ function Lightbox({ items, index, onIndexChange, open, onClose, loop = false, la
|
|
|
37
70
|
useEffect(() => {
|
|
38
71
|
setZoomed(false);
|
|
39
72
|
}, [index]);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (!open) return;
|
|
75
|
+
const reset = () => {
|
|
76
|
+
escapeClaimed.current = false;
|
|
77
|
+
};
|
|
78
|
+
window.addEventListener("keydown", reset, true);
|
|
79
|
+
return () => window.removeEventListener("keydown", reset, true);
|
|
80
|
+
}, [open]);
|
|
81
|
+
const hasOverlay = renderOverlay != null;
|
|
82
|
+
const measure = useCallback(() => {
|
|
83
|
+
const media = mediaRef.current;
|
|
84
|
+
if (!media) {
|
|
85
|
+
setOverlayBox(null);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const box = containedBox(media);
|
|
89
|
+
setOverlayBox((prev) => prev && prev.left === box.left && prev.top === box.top && prev.width === box.width && prev.height === box.height ? prev : box);
|
|
90
|
+
}, []);
|
|
91
|
+
useLayoutEffect(() => {
|
|
92
|
+
if (!open || !hasOverlay) return;
|
|
93
|
+
measure();
|
|
94
|
+
const media = mediaRef.current;
|
|
95
|
+
if (!media || typeof ResizeObserver === "undefined") return;
|
|
96
|
+
const observer = new ResizeObserver(measure);
|
|
97
|
+
observer.observe(media);
|
|
98
|
+
return () => observer.disconnect();
|
|
99
|
+
}, [open, index, zoomed, hasOverlay, measure]);
|
|
40
100
|
const go = (to) => {
|
|
41
101
|
if (total === 0) return;
|
|
42
102
|
const next = loop ? (to + total) % total : Math.min(Math.max(to, 0), total - 1);
|
|
43
103
|
if (next !== index) onIndexChange(next);
|
|
44
104
|
};
|
|
45
105
|
const onKeyDown = (event) => {
|
|
106
|
+
onKeyDownProp?.(event);
|
|
107
|
+
if (event.key === "Escape" && !event.defaultPrevented) onEscapeKeyDown?.(event);
|
|
108
|
+
if (event.defaultPrevented) {
|
|
109
|
+
if (event.key === "Escape") escapeClaimed.current = true;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
46
112
|
if (event.target.closest("video")) return;
|
|
113
|
+
if (event.target.closest('input, textarea, select, [contenteditable="true"]')) return;
|
|
47
114
|
if (event.key === "ArrowLeft") {
|
|
48
115
|
event.preventDefault();
|
|
49
116
|
go(index - 1);
|
|
@@ -58,8 +125,9 @@ function Lightbox({ items, index, onIndexChange, open, onClose, loop = false, la
|
|
|
58
125
|
go(total - 1);
|
|
59
126
|
}
|
|
60
127
|
};
|
|
128
|
+
const onOverlay = (event) => event.target.closest?.(".uix-lightbox__overlay") != null;
|
|
61
129
|
const onPointerDown = (event) => {
|
|
62
|
-
if (event.pointerType !== "mouse") swipe.current = { x: event.clientX, id: event.pointerId };
|
|
130
|
+
if (event.pointerType !== "mouse" && !onOverlay(event)) swipe.current = { x: event.clientX, id: event.pointerId };
|
|
63
131
|
};
|
|
64
132
|
const onPointerUp = (event) => {
|
|
65
133
|
const start = swipe.current;
|
|
@@ -74,15 +142,30 @@ function Lightbox({ items, index, onIndexChange, open, onClose, loop = false, la
|
|
|
74
142
|
ref,
|
|
75
143
|
className: cx("uix-lightbox", "uix-lightbox--gallery", className),
|
|
76
144
|
"aria-label": labels.region,
|
|
145
|
+
"data-actions": actions != null || void 0,
|
|
77
146
|
onKeyDown,
|
|
147
|
+
onPointerDown: (event) => {
|
|
148
|
+
pressedBackdrop.current = event.target === event.currentTarget;
|
|
149
|
+
},
|
|
78
150
|
onClick: (event) => {
|
|
79
|
-
if (event.target === event.currentTarget) onClose();
|
|
151
|
+
if (event.target === event.currentTarget && pressedBackdrop.current) onClose();
|
|
152
|
+
pressedBackdrop.current = false;
|
|
80
153
|
},
|
|
81
154
|
children: [
|
|
82
155
|
current && /* @__PURE__ */ jsxs("figure", { className: "uix-lightbox__figure", children: [
|
|
83
|
-
/* @__PURE__ */
|
|
156
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-lightbox__stage", "data-zoomed": zoomed || void 0, onPointerDown, onPointerUp, children: [
|
|
157
|
+
current.kind === "video" ? /* @__PURE__ */ jsx("video", { ref: mediaRef, className: "uix-lightbox__media", src: current.src, controls: true, "aria-label": current.alt, width: current.width, height: current.height, onLoadedMetadata: hasOverlay ? measure : void 0 }) : /* @__PURE__ */ jsx("img", { ref: mediaRef, className: "uix-lightbox__media", src: current.src, alt: current.alt, width: current.width, height: current.height, onLoad: hasOverlay ? measure : void 0 }),
|
|
158
|
+
renderOverlay && /* @__PURE__ */ jsx(
|
|
159
|
+
"div",
|
|
160
|
+
{
|
|
161
|
+
className: "uix-lightbox__overlay",
|
|
162
|
+
style: overlayBox ? { left: overlayBox.left, top: overlayBox.top, width: overlayBox.width, height: overlayBox.height } : { inset: 0 },
|
|
163
|
+
children: renderOverlay({ index, item: current, media: mediaRef.current, width: overlayBox?.width ?? 0, height: overlayBox?.height ?? 0 })
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
] }),
|
|
84
167
|
/* @__PURE__ */ jsxs("figcaption", { className: "uix-lightbox__bar", children: [
|
|
85
|
-
/* @__PURE__ */ jsx("span", { className: "uix-lightbox__counter", "aria-live": "polite", children: fillLabel(labels.counter, { index: index + 1, total }) }),
|
|
168
|
+
total > 1 && /* @__PURE__ */ jsx("span", { className: "uix-lightbox__counter", "aria-live": "polite", children: fillLabel(labels.counter, { index: index + 1, total }) }),
|
|
86
169
|
current.caption != null && /* @__PURE__ */ jsx("span", { className: "uix-lightbox__caption", children: current.caption }),
|
|
87
170
|
current.kind !== "video" && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-lightbox__tool", "aria-pressed": zoomed, onClick: () => setZoomed((z) => !z), children: zoomed ? labels.zoomOut : labels.zoomIn })
|
|
88
171
|
] })
|
|
@@ -91,7 +174,10 @@ function Lightbox({ items, index, onIndexChange, open, onClose, loop = false, la
|
|
|
91
174
|
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-lightbox__nav uix-lightbox__nav--prev", "aria-label": labels.previous, disabled: !hasPrev, onClick: () => go(index - 1), children: /* @__PURE__ */ jsx(Chevron, { dir: "left" }) }),
|
|
92
175
|
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-lightbox__nav uix-lightbox__nav--next", "aria-label": labels.next, disabled: !hasNext, onClick: () => go(index + 1), children: /* @__PURE__ */ jsx(Chevron, { dir: "right" }) })
|
|
93
176
|
] }),
|
|
94
|
-
/* @__PURE__ */
|
|
177
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-lightbox__toolbar", children: [
|
|
178
|
+
actions,
|
|
179
|
+
/* @__PURE__ */ jsx("button", { ref: closeRef, type: "button", className: "uix-lightbox__close", "aria-label": labels.close, onClick: onClose, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M4 4l8 8M12 4l-8 8" }) }) })
|
|
180
|
+
] })
|
|
95
181
|
]
|
|
96
182
|
}
|
|
97
183
|
);
|
|
@@ -63,6 +63,33 @@ interface MatchReviewLabels {
|
|
|
63
63
|
failedSuffix: string;
|
|
64
64
|
bulkAcceptFailed: string;
|
|
65
65
|
bulkDismissFailed: string;
|
|
66
|
+
/** Adopt mode (HAR-1369): a candidate's heading when `candidateLabel` gives none. `{id}`. */
|
|
67
|
+
candidate: string;
|
|
68
|
+
/** Adopt mode: the compare table's caption. `{candidate}`. */
|
|
69
|
+
compareCaption: string;
|
|
70
|
+
field: string;
|
|
71
|
+
/** Adopt mode: the column of the record as it is now. */
|
|
72
|
+
currentValue: string;
|
|
73
|
+
/** Adopt mode: the column of the candidate's values. */
|
|
74
|
+
candidateValue: string;
|
|
75
|
+
/** Adopt mode: the column of checkboxes. */
|
|
76
|
+
adopt: string;
|
|
77
|
+
/** Adopt mode: one checkbox's name. `{field}`, `{candidate}`. */
|
|
78
|
+
adoptField: string;
|
|
79
|
+
/** Adopt mode: shown instead of a checkbox when the candidate has the same value. */
|
|
80
|
+
same: string;
|
|
81
|
+
/** Adopt mode: the button with one field chosen. `{count}`. */
|
|
82
|
+
adoptSelectedOne: string;
|
|
83
|
+
/** Adopt mode: the button with any other number of fields chosen (0 too). `{count}`. */
|
|
84
|
+
adoptSelectedMany: string;
|
|
85
|
+
/** Adopt mode: the provenance disclosure. */
|
|
86
|
+
provenance: string;
|
|
87
|
+
/** Adopt mode: announced after `onAdopt` resolves, one field. `{count}`, `{candidate}`. */
|
|
88
|
+
adoptedOne: string;
|
|
89
|
+
/** Adopt mode: announced after `onAdopt` resolves, more fields. `{count}`, `{candidate}`. */
|
|
90
|
+
adoptedMany: string;
|
|
91
|
+
/** Adopt mode: shown when `onAdopt` rejects. */
|
|
92
|
+
adoptFailed: string;
|
|
66
93
|
}
|
|
67
94
|
declare const DEFAULT_MATCH_REVIEW_LABELS: MatchReviewLabels;
|
|
68
95
|
interface MatchReviewProps {
|
|
@@ -79,8 +106,20 @@ interface MatchReviewProps {
|
|
|
79
106
|
onRetry?: () => void;
|
|
80
107
|
className?: string;
|
|
81
108
|
labels?: Partial<MatchReviewLabels>;
|
|
109
|
+
/**
|
|
110
|
+
* `decide` (default): one row per candidate, decided as a whole. `adopt` (HAR-1369, MOTUS B-A9
|
|
111
|
+
* registry candidates): per candidate a table of the record now against the candidate, field
|
|
112
|
+
* by field, a checkbox per field that differs, and an Adopt button that calls `onAdopt`.
|
|
113
|
+
*/
|
|
114
|
+
fieldMode?: 'decide' | 'adopt';
|
|
115
|
+
/** Adopt mode: take the chosen fields from the candidate. A promise keeps the button busy until it settles. */
|
|
116
|
+
onAdopt?: (candidateId: string, fieldIds: string[]) => void | Promise<unknown>;
|
|
117
|
+
/** Adopt mode: the candidate's heading, e.g. its source ("DNB", "Wikidata"). Default `labels.candidate`. */
|
|
118
|
+
candidateLabel?: (candidate: MatchReviewCandidate) => ReactNode;
|
|
119
|
+
/** Adopt mode: where a candidate comes from, in a disclosure under its table (source, retrieved, match reason). */
|
|
120
|
+
provenance?: (candidate: MatchReviewCandidate) => ReactNode;
|
|
82
121
|
}
|
|
83
122
|
/** Generic descriptor-driven incoming-record to candidate-match decision surface. */
|
|
84
|
-
declare function MatchReview({ incoming, incomingLabel, fields, candidates, selectedIds, onSelectionChange, onDecision, onBulkDecision, loading, error, onRetry, className, labels: labelOverrides, }: MatchReviewProps): react.JSX.Element;
|
|
123
|
+
declare function MatchReview({ incoming, incomingLabel, fields, candidates, selectedIds, onSelectionChange, onDecision, onBulkDecision, loading, error, onRetry, className, labels: labelOverrides, fieldMode, onAdopt, candidateLabel, provenance, }: MatchReviewProps): react.JSX.Element;
|
|
85
124
|
|
|
86
125
|
export { DEFAULT_MATCH_REVIEW_LABELS, type MatchBulkResult, MatchReview, type MatchReviewAction, type MatchReviewCandidate, type MatchReviewField, type MatchReviewLabels, type MatchReviewProps };
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useMemo, useState } from "react";
|
|
3
|
+
import { useId, useMemo, useState } from "react";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
5
|
import { StatusPill } from "./StatusPill.js";
|
|
6
|
+
import { Button } from "./Button.js";
|
|
6
7
|
import { fillLabel } from "../fill-label.js";
|
|
8
|
+
import { useUixLabels } from "../labels-context.js";
|
|
9
|
+
import { ChevronDownIcon } from "../icons/components.js";
|
|
7
10
|
const DEFAULT_MATCH_REVIEW_LABELS = {
|
|
8
11
|
region: "Match review",
|
|
9
12
|
loading: "Loading match candidates\u2026",
|
|
@@ -43,7 +46,21 @@ const DEFAULT_MATCH_REVIEW_LABELS = {
|
|
|
43
46
|
dismissedMany: "{count} candidates dismissed",
|
|
44
47
|
failedSuffix: "; {count} failed",
|
|
45
48
|
bulkAcceptFailed: "Bulk accept failed. Try again.",
|
|
46
|
-
bulkDismissFailed: "Bulk dismiss failed. Try again."
|
|
49
|
+
bulkDismissFailed: "Bulk dismiss failed. Try again.",
|
|
50
|
+
candidate: "Candidate {id}",
|
|
51
|
+
compareCaption: "{candidate}: the record now and the candidate, field by field",
|
|
52
|
+
field: "Field",
|
|
53
|
+
currentValue: "Now",
|
|
54
|
+
candidateValue: "Candidate",
|
|
55
|
+
adopt: "Adopt",
|
|
56
|
+
adoptField: "Adopt {field} from {candidate}",
|
|
57
|
+
same: "Same",
|
|
58
|
+
adoptSelectedOne: "Adopt {count} field",
|
|
59
|
+
adoptSelectedMany: "Adopt {count} fields",
|
|
60
|
+
provenance: "Where this candidate comes from",
|
|
61
|
+
adoptedOne: "{count} field adopted from {candidate}",
|
|
62
|
+
adoptedMany: "{count} fields adopted from {candidate}",
|
|
63
|
+
adoptFailed: "Could not adopt the fields. Try again."
|
|
47
64
|
};
|
|
48
65
|
const valueText = (value) => value == null ? "\u2014" : typeof value === "string" ? value : JSON.stringify(value);
|
|
49
66
|
function MatchReview({
|
|
@@ -59,9 +76,13 @@ function MatchReview({
|
|
|
59
76
|
error,
|
|
60
77
|
onRetry,
|
|
61
78
|
className,
|
|
62
|
-
labels: labelOverrides
|
|
79
|
+
labels: labelOverrides,
|
|
80
|
+
fieldMode = "decide",
|
|
81
|
+
onAdopt,
|
|
82
|
+
candidateLabel,
|
|
83
|
+
provenance
|
|
63
84
|
}) {
|
|
64
|
-
const labels = { ...DEFAULT_MATCH_REVIEW_LABELS, ...labelOverrides };
|
|
85
|
+
const labels = { ...DEFAULT_MATCH_REVIEW_LABELS, ...useUixLabels().matchReview, ...labelOverrides };
|
|
65
86
|
const plural = (n, one, many) => fillLabel(n === 1 ? one : many, { count: n });
|
|
66
87
|
const decisionLabel = { accepted: labels.decisionAccepted, dismissed: labels.decisionDismissed, split: labels.decisionSplit, other: labels.decisionOther };
|
|
67
88
|
const actionLabel = { accept: labels.accept, dismiss: labels.dismiss, split: labels.split, "pick-other": labels.pickOther };
|
|
@@ -107,6 +128,29 @@ function MatchReview({
|
|
|
107
128
|
/* @__PURE__ */ jsx("p", { children: labels.empty }),
|
|
108
129
|
/* @__PURE__ */ jsx("p", { children: labels.emptyDetail })
|
|
109
130
|
] });
|
|
131
|
+
if (fieldMode === "adopt") {
|
|
132
|
+
return /* @__PURE__ */ jsxs("section", { className: cx("uix-match-review", "uix-match-review--adopt", className), "aria-label": labels.region, children: [
|
|
133
|
+
/* @__PURE__ */ jsx("div", { className: "uix-match-review__summary", children: /* @__PURE__ */ jsxs("div", { children: [
|
|
134
|
+
/* @__PURE__ */ jsx("strong", { children: incomingLabel }),
|
|
135
|
+
/* @__PURE__ */ jsx("span", { children: fillLabel(labels.pendingOf, { pending: pending.length, total: candidates.length }) })
|
|
136
|
+
] }) }),
|
|
137
|
+
candidates.map((candidate) => /* @__PURE__ */ jsx(
|
|
138
|
+
AdoptCandidate,
|
|
139
|
+
{
|
|
140
|
+
candidate,
|
|
141
|
+
incoming,
|
|
142
|
+
fields,
|
|
143
|
+
labels,
|
|
144
|
+
heading: candidateLabel?.(candidate) ?? fillLabel(labels.candidate, { id: candidate.id }),
|
|
145
|
+
provenance: provenance?.(candidate),
|
|
146
|
+
onAdopt,
|
|
147
|
+
onDecision,
|
|
148
|
+
decisionLabel
|
|
149
|
+
},
|
|
150
|
+
candidate.id
|
|
151
|
+
))
|
|
152
|
+
] });
|
|
153
|
+
}
|
|
110
154
|
return /* @__PURE__ */ jsxs("section", { className: cx("uix-match-review", className), "aria-label": labels.region, children: [
|
|
111
155
|
/* @__PURE__ */ jsxs("div", { className: "uix-match-review__summary", children: [
|
|
112
156
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
@@ -156,6 +200,88 @@ function MatchReview({
|
|
|
156
200
|
/* @__PURE__ */ jsx("p", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
|
|
157
201
|
] });
|
|
158
202
|
}
|
|
203
|
+
const sameValue = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
204
|
+
function AdoptCandidate({ candidate, incoming, fields, labels, heading, provenance, onAdopt, onDecision, decisionLabel }) {
|
|
205
|
+
const id = useId();
|
|
206
|
+
const [chosen, setChosen] = useState(/* @__PURE__ */ new Set());
|
|
207
|
+
const [pending, setPending] = useState(false);
|
|
208
|
+
const [message, setMessage] = useState(null);
|
|
209
|
+
const resolved = candidate.status === "resolved";
|
|
210
|
+
const headingText = typeof heading === "string" ? heading : fillLabel(labels.candidate, { id: candidate.id });
|
|
211
|
+
const toggle = (fieldId, on) => {
|
|
212
|
+
if (pending) return;
|
|
213
|
+
const next = new Set(chosen);
|
|
214
|
+
if (on) next.add(fieldId);
|
|
215
|
+
else next.delete(fieldId);
|
|
216
|
+
setChosen(next);
|
|
217
|
+
setMessage(null);
|
|
218
|
+
};
|
|
219
|
+
const adopt = async () => {
|
|
220
|
+
if (!onAdopt || chosen.size === 0 || pending) return;
|
|
221
|
+
const fieldIds = fields.map((f) => f.id).filter((f) => chosen.has(f));
|
|
222
|
+
setPending(true);
|
|
223
|
+
setMessage(null);
|
|
224
|
+
try {
|
|
225
|
+
await onAdopt(candidate.id, fieldIds);
|
|
226
|
+
setChosen(/* @__PURE__ */ new Set());
|
|
227
|
+
setMessage({ text: fillLabel(fieldIds.length === 1 ? labels.adoptedOne : labels.adoptedMany, { count: fieldIds.length, candidate: headingText }), failed: false });
|
|
228
|
+
} catch {
|
|
229
|
+
setMessage({ text: labels.adoptFailed, failed: true });
|
|
230
|
+
} finally {
|
|
231
|
+
setPending(false);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const show = (field, record) => field.render?.(record[field.id], record) ?? valueText(record[field.id]);
|
|
235
|
+
return /* @__PURE__ */ jsxs("section", { className: "uix-match-review__candidate", "aria-labelledby": `${id}-heading`, "data-status": candidate.status ?? "pending", children: [
|
|
236
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-match-review__candidate-head", children: [
|
|
237
|
+
/* @__PURE__ */ jsx("h3", { id: `${id}-heading`, className: "uix-match-review__candidate-title", children: heading }),
|
|
238
|
+
candidate.confidence != null && /* @__PURE__ */ jsx("span", { className: "uix-match-review__confidence", children: `${Math.round(candidate.confidence * 100)}%` }),
|
|
239
|
+
resolved && /* @__PURE__ */ jsx(StatusPill, { tone: "success", children: candidate.decision ? decisionLabel[candidate.decision] : labels.resolved })
|
|
240
|
+
] }),
|
|
241
|
+
/* @__PURE__ */ jsx("div", { className: "uix-match-review__table-wrap", children: /* @__PURE__ */ jsxs("table", { className: "uix-table uix-match-review__compare", role: "table", children: [
|
|
242
|
+
/* @__PURE__ */ jsx("caption", { className: "uix-visually-hidden", children: fillLabel(labels.compareCaption, { candidate: headingText }) }),
|
|
243
|
+
/* @__PURE__ */ jsx("thead", { role: "rowgroup", children: /* @__PURE__ */ jsxs("tr", { role: "row", children: [
|
|
244
|
+
/* @__PURE__ */ jsx("th", { role: "columnheader", scope: "col", children: labels.field }),
|
|
245
|
+
/* @__PURE__ */ jsx("th", { role: "columnheader", scope: "col", children: labels.currentValue }),
|
|
246
|
+
/* @__PURE__ */ jsx("th", { role: "columnheader", scope: "col", children: labels.candidateValue }),
|
|
247
|
+
/* @__PURE__ */ jsx("th", { role: "columnheader", scope: "col", children: labels.adopt })
|
|
248
|
+
] }) }),
|
|
249
|
+
/* @__PURE__ */ jsx("tbody", { role: "rowgroup", children: fields.map((field) => {
|
|
250
|
+
const same = sameValue(incoming[field.id], candidate.record[field.id]);
|
|
251
|
+
return /* @__PURE__ */ jsxs("tr", { role: "row", "data-differs": same ? void 0 : "", "data-chosen": chosen.has(field.id) || void 0, children: [
|
|
252
|
+
/* @__PURE__ */ jsx("th", { role: "rowheader", scope: "row", children: field.label }),
|
|
253
|
+
/* @__PURE__ */ jsx("td", { role: "cell", "data-label": labels.currentValue, children: show(field, incoming) }),
|
|
254
|
+
/* @__PURE__ */ jsx("td", { role: "cell", "data-label": labels.candidateValue, children: show(field, candidate.record) }),
|
|
255
|
+
/* @__PURE__ */ jsx("td", { role: "cell", children: same ? /* @__PURE__ */ jsx("span", { className: "uix-match-review__same", children: labels.same }) : /* @__PURE__ */ jsx(
|
|
256
|
+
"input",
|
|
257
|
+
{
|
|
258
|
+
type: "checkbox",
|
|
259
|
+
className: "uix-match-review__adopt",
|
|
260
|
+
checked: chosen.has(field.id),
|
|
261
|
+
disabled: resolved,
|
|
262
|
+
"aria-label": fillLabel(labels.adoptField, { field: field.label, candidate: headingText }),
|
|
263
|
+
onChange: (event) => toggle(field.id, event.currentTarget.checked)
|
|
264
|
+
}
|
|
265
|
+
) })
|
|
266
|
+
] }, field.id);
|
|
267
|
+
}) })
|
|
268
|
+
] }) }),
|
|
269
|
+
provenance != null && provenance !== false && /* @__PURE__ */ jsxs("details", { className: "uix-match-review__provenance", children: [
|
|
270
|
+
/* @__PURE__ */ jsxs("summary", { className: "uix-match-review__provenance-summary", children: [
|
|
271
|
+
/* @__PURE__ */ jsx("span", { children: labels.provenance }),
|
|
272
|
+
/* @__PURE__ */ jsx("span", { className: "uix-collapsible__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: "sm" }) })
|
|
273
|
+
] }),
|
|
274
|
+
/* @__PURE__ */ jsx("div", { className: "uix-match-review__provenance-body", children: provenance })
|
|
275
|
+
] }),
|
|
276
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-match-review__candidate-actions", children: [
|
|
277
|
+
!resolved && onAdopt && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "primary", disabled: chosen.size === 0, loading: pending, onClick: () => void adopt(), children: fillLabel(chosen.size === 1 ? labels.adoptSelectedOne : labels.adoptSelectedMany, { count: chosen.size }) }),
|
|
278
|
+
!resolved && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
|
|
279
|
+
if (!pending) onDecision(candidate.id, "dismiss");
|
|
280
|
+
}, children: labels.dismiss }),
|
|
281
|
+
/* @__PURE__ */ jsx("span", { className: cx("uix-match-review__result", message?.failed && "uix-match-review__result--failed"), role: "status", children: message?.text })
|
|
282
|
+
] })
|
|
283
|
+
] });
|
|
284
|
+
}
|
|
159
285
|
export {
|
|
160
286
|
DEFAULT_MATCH_REVIEW_LABELS,
|
|
161
287
|
MatchReview
|
|
@@ -4,6 +4,7 @@ import { useEffect, useId, useState } from "react";
|
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
5
|
import { clampMetricValue, parseMetricValue, stepMetricValue } from "../metric-model.js";
|
|
6
6
|
import { Input } from "./Input.js";
|
|
7
|
+
import { MinusIcon, PlusIcon } from "../icons/components.js";
|
|
7
8
|
const defaultMetricFormat = (input) => input == null ? "" : String(input);
|
|
8
9
|
function MetricInput({
|
|
9
10
|
value,
|
|
@@ -83,8 +84,8 @@ function MetricInput({
|
|
|
83
84
|
),
|
|
84
85
|
unit && /* @__PURE__ */ jsx("span", { className: "uix-metric-input__unit", "aria-hidden": "true", children: unit }),
|
|
85
86
|
/* @__PURE__ */ jsxs("span", { className: "uix-metric-input__steps", children: [
|
|
86
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(1), disabled: disabled || readOnly, "aria-label": `Increase ${label}`, children:
|
|
87
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(-1), disabled: disabled || readOnly, "aria-label": `Decrease ${label}`, children:
|
|
87
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(1), disabled: disabled || readOnly, "aria-label": `Increase ${label}`, children: /* @__PURE__ */ jsx(PlusIcon, {}) }),
|
|
88
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-metric-input__step", onClick: () => stepBy(-1), disabled: disabled || readOnly, "aria-label": `Decrease ${label}`, children: /* @__PURE__ */ jsx(MinusIcon, {}) })
|
|
88
89
|
] })
|
|
89
90
|
] }),
|
|
90
91
|
unit && /* @__PURE__ */ jsxs("span", { className: "uix-visually-hidden", children: [
|
|
@@ -4,6 +4,7 @@ import { DialogCloseEvent, DiscardPromptLabels } from './DiscardPrompt.js';
|
|
|
4
4
|
import './Button.js';
|
|
5
5
|
import '../link.js';
|
|
6
6
|
import './Kbd.js';
|
|
7
|
+
import './ButtonFeedback.js';
|
|
7
8
|
|
|
8
9
|
interface ModalProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
|
|
9
10
|
open: boolean;
|
|
@@ -34,6 +35,7 @@ interface ModalProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
|
|
|
34
35
|
*/
|
|
35
36
|
instant?: boolean;
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
/** A modal `<dialog>`; the ref reaches it (HAR-1831). */
|
|
39
|
+
declare const Modal: react.ForwardRefExoticComponent<ModalProps & react.RefAttributes<HTMLDialogElement>>;
|
|
38
40
|
|
|
39
41
|
export { Modal, type ModalProps };
|
package/dist/components/Modal.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useId, useRef } from "react";
|
|
3
|
+
import { forwardRef, useCallback, useId, useRef } from "react";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
5
|
import { useUixLabels } from "../labels-context.js";
|
|
6
6
|
import { useGuardedDialog } from "./DiscardPrompt.js";
|
|
7
7
|
const CloseIcon = () => /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M4 4l8 8M12 4l-8 8" }) });
|
|
8
|
-
|
|
8
|
+
const Modal = forwardRef(function Modal2({
|
|
9
9
|
open,
|
|
10
10
|
onClose,
|
|
11
11
|
dirty = false,
|
|
@@ -20,16 +20,17 @@ function Modal({
|
|
|
20
20
|
instant = false,
|
|
21
21
|
onKeyDown,
|
|
22
22
|
...rest
|
|
23
|
-
}) {
|
|
23
|
+
}, ref) {
|
|
24
24
|
const uixLabels = useUixLabels();
|
|
25
25
|
const closeLabel = closeLabelProp ?? uixLabels.modal?.close ?? "Close dialog";
|
|
26
26
|
const closeRef = useRef(null);
|
|
27
27
|
const guard = useGuardedDialog({ open, onClose, dirty, pending, labels: discardLabels, closeRef });
|
|
28
28
|
const titleId = useId();
|
|
29
|
+
const dialogRef = useMergedRef(guard.ref, ref);
|
|
29
30
|
return /* @__PURE__ */ jsxs(
|
|
30
31
|
"dialog",
|
|
31
32
|
{
|
|
32
|
-
ref:
|
|
33
|
+
ref: dialogRef,
|
|
33
34
|
className: cx("uix-dialog", className),
|
|
34
35
|
role,
|
|
35
36
|
"aria-labelledby": title != null ? titleId : void 0,
|
|
@@ -65,6 +66,14 @@ function Modal({
|
|
|
65
66
|
]
|
|
66
67
|
}
|
|
67
68
|
);
|
|
69
|
+
});
|
|
70
|
+
Modal.displayName = "Modal";
|
|
71
|
+
function useMergedRef(own, forwarded) {
|
|
72
|
+
return useCallback((el) => {
|
|
73
|
+
own.current = el;
|
|
74
|
+
if (typeof forwarded === "function") forwarded(el);
|
|
75
|
+
else if (forwarded) forwarded.current = el;
|
|
76
|
+
}, [own, forwarded]);
|
|
68
77
|
}
|
|
69
78
|
export {
|
|
70
79
|
Modal
|
|
@@ -20,6 +20,11 @@ interface NavFavouritesLabels {
|
|
|
20
20
|
readonly remove: string;
|
|
21
21
|
/** Per-row overflow-trigger accessible-name template with `{label}`. Defaults to "{label} favourite options". */
|
|
22
22
|
readonly options?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Polite announcement after a move: `{label}`, `{position}`, `{count}`. Defaults to
|
|
25
|
+
* "{label} moved to position {position} of {count}" (it was English only before HAR-1375).
|
|
26
|
+
*/
|
|
27
|
+
readonly moved?: string;
|
|
23
28
|
}
|
|
24
29
|
interface NavFavouritesProps {
|
|
25
30
|
/** The already-resolved favourites, in display order. */
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { memo, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
|
+
import { fillLabel } from "../fill-label.js";
|
|
6
|
+
import { placeId } from "../reorder.js";
|
|
5
7
|
const ChevronDownIcon = () => /* @__PURE__ */ jsx(
|
|
6
8
|
"svg",
|
|
7
9
|
{
|
|
@@ -91,15 +93,13 @@ function NavFavourites({
|
|
|
91
93
|
const idx = order.indexOf(id);
|
|
92
94
|
const next = idx + dir;
|
|
93
95
|
if (idx < 0 || next < 0 || next >= order.length) return;
|
|
94
|
-
const reordered =
|
|
95
|
-
|
|
96
|
-
if (it === void 0) return;
|
|
97
|
-
reordered.splice(next, 0, it);
|
|
96
|
+
const reordered = placeId(order, id, next);
|
|
97
|
+
if (reordered === order) return;
|
|
98
98
|
focusAfterRef.current = id;
|
|
99
99
|
const moved = items.find((i) => i.id === id);
|
|
100
|
-
if (moved) setAnnouncement(
|
|
101
|
-
onReorder(reordered);
|
|
102
|
-
}, [onReorder, order]);
|
|
100
|
+
if (moved) setAnnouncement(fillLabel(labels.moved ?? "{label} moved to position {position} of {count}", { label: moved.label, position: next + 1, count: order.length }));
|
|
101
|
+
onReorder([...reordered]);
|
|
102
|
+
}, [onReorder, order, items, labels.moved]);
|
|
103
103
|
const remove = useCallback((id) => {
|
|
104
104
|
const idx = order.indexOf(id);
|
|
105
105
|
const neighbour = order[idx + 1] ?? order[idx - 1] ?? null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { InputHTMLAttributes, AriaAttributes, CSSProperties } from 'react';
|
|
3
|
+
|
|
4
|
+
interface OtpInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue' | 'onChange' | 'type' | 'size' | 'maxLength' | 'pattern' | 'inputMode'> {
|
|
5
|
+
/** How many characters the code has. Default 6. */
|
|
6
|
+
length?: number;
|
|
7
|
+
/** Controlled value: the characters typed so far. */
|
|
8
|
+
value?: string;
|
|
9
|
+
defaultValue?: string;
|
|
10
|
+
/** Every change, with the cleaned code (only allowed characters, at most `length`). */
|
|
11
|
+
onValueChange?: (value: string) => void;
|
|
12
|
+
/** Called once each time the code reaches `length` characters. */
|
|
13
|
+
onComplete?: (code: string) => void;
|
|
14
|
+
/** `numeric` (default): digits, with the phone's number pad. `alphanumeric`: letters and digits, upper-cased. */
|
|
15
|
+
mode?: 'numeric' | 'alphanumeric';
|
|
16
|
+
/** Draw a gap after every n cells (3 → "123 456"). Visual only: the value has no separator. */
|
|
17
|
+
groupSize?: number;
|
|
18
|
+
invalid?: boolean;
|
|
19
|
+
'aria-invalid'?: AriaAttributes['aria-invalid'];
|
|
20
|
+
className?: string;
|
|
21
|
+
style?: CSSProperties;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A one-time code (HAR-1376): ONE native input, so screen readers, the phone's code autofill
|
|
25
|
+
* (`autoComplete="one-time-code"`) and password managers see a single field, drawn as `length`
|
|
26
|
+
* cells. Pasting "123 456" or "123-456" keeps the characters; typing past the end is ignored;
|
|
27
|
+
* Backspace deletes the last one. `onComplete(code)` runs when the last character arrives. Put it in
|
|
28
|
+
* a `Field` for its label, hint and error (the cells turn red with `aria-invalid`). `name` submits
|
|
29
|
+
* the code. `ref` is the input.
|
|
30
|
+
*/
|
|
31
|
+
declare const OtpInput: react.ForwardRefExoticComponent<OtpInputProps & react.RefAttributes<HTMLInputElement>>;
|
|
32
|
+
|
|
33
|
+
export { OtpInput, type OtpInputProps };
|