@tensor_1/react 2.47.0 → 2.49.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 +540 -2
- 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/Button.d.ts +12 -5
- package/dist/components/Button.js +7 -3
- 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/Chip.d.ts +7 -3
- package/dist/components/Chip.js +2 -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/DashboardCanvas.js +14 -0
- package/dist/components/DataWorkspace.d.ts +167 -0
- package/dist/components/DataWorkspace.js +380 -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/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.js +16 -0
- package/dist/components/DisplayPreferences.d.ts +83 -0
- package/dist/components/DisplayPreferences.js +185 -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/FocusOnMount.d.ts +282 -0
- package/dist/components/FocusOnMount.js +12 -0
- 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/Kanban.d.ts +14 -3
- package/dist/components/Kanban.js +9 -4
- 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 +2 -1
- package/dist/components/Modal.js +13 -4
- package/dist/components/MotionList.d.ts +303 -0
- package/dist/components/MotionList.js +175 -0
- 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 +11 -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 +22 -6
- package/dist/components/RelationshipGraphLayered.d.ts +1 -1
- package/dist/components/RelationshipGraphLayered.js +268 -118
- package/dist/components/RelationshipGraphList.js +33 -6
- package/dist/components/RuleBuilder.js +39 -7
- 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/SearchSuggest.d.ts +23 -3
- package/dist/components/SearchSuggest.js +56 -18
- package/dist/components/Segmented.d.ts +39 -4
- package/dist/components/Segmented.js +39 -5
- package/dist/components/Select.d.ts +3 -2
- package/dist/components/Select.js +7 -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 +147 -0
- package/dist/components/Stat.d.ts +28 -3
- package/dist/components/Stat.js +13 -3
- package/dist/components/States.d.ts +33 -1
- package/dist/components/States.js +43 -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 +9 -2
- package/dist/components/Table.js +4 -2
- 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 +10 -2
- 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.d.ts +1 -1
- package/dist/components/Toast.js +109 -4
- package/dist/components/Tooltip.d.ts +8 -4
- package/dist/components/Tooltip.js +86 -28
- 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 +143 -49
- package/dist/filter-model.d.ts +59 -3
- package/dist/filter-model.js +67 -2
- package/dist/hooks/useAnchoredPosition.js +14 -2
- package/dist/hooks/useChangeHighlight.d.ts +33 -0
- package/dist/hooks/useChangeHighlight.js +44 -0
- package/dist/hooks/useDeferredReorder.d.ts +32 -0
- package/dist/hooks/useDeferredReorder.js +47 -0
- package/dist/hooks/useDialog.d.ts +5 -0
- package/dist/hooks/useDialog.js +17 -3
- package/dist/hooks/useKeyboardGrab.d.ts +58 -0
- package/dist/hooks/useKeyboardGrab.js +118 -0
- 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/useSlidingIndicator.d.ts +18 -0
- package/dist/hooks/useSlidingIndicator.js +80 -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 +19687 -13423
- package/dist/index.d.cts +3419 -651
- package/dist/index.d.ts +48 -18
- package/dist/index.js +153 -9
- package/dist/labels-context.d.ts +93 -3
- package/dist/motion.d.ts +11 -0
- package/dist/motion.js +35 -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 +92 -3
- package/dist/relationship-graph-model.js +134 -15
- package/dist/reorder.d.ts +30 -0
- package/dist/reorder.js +74 -0
- package/dist/rich-text.cjs +46 -4
- 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 +22 -3
- package/dist/toast-store.js +26 -9
- package/package.json +2 -1
|
@@ -8,7 +8,7 @@ function Kanban({ announcement, children, className, ...props }) {
|
|
|
8
8
|
children
|
|
9
9
|
] });
|
|
10
10
|
}
|
|
11
|
-
function KanbanColumn({ title, count, children, className, ...props }) {
|
|
11
|
+
function KanbanColumn({ title, count, dropTarget, children, className, ...props }) {
|
|
12
12
|
const headId = useId();
|
|
13
13
|
const hasHead = title != null || count != null;
|
|
14
14
|
return /* @__PURE__ */ jsxs("div", { className: cx("uix-kanban__col", className), ...props, children: [
|
|
@@ -16,7 +16,7 @@ function KanbanColumn({ title, count, children, className, ...props }) {
|
|
|
16
16
|
title,
|
|
17
17
|
count != null && /* @__PURE__ */ jsx("span", { className: "uix-kanban__count", children: count })
|
|
18
18
|
] }),
|
|
19
|
-
/* @__PURE__ */ jsx("div", { className: "uix-kanban__body", role: "list", "aria-labelledby": hasHead ? headId : void 0, children })
|
|
19
|
+
/* @__PURE__ */ jsx("div", { className: "uix-kanban__body", role: "list", "aria-labelledby": hasHead ? headId : void 0, "data-uix-drop-target": dropTarget || void 0, children })
|
|
20
20
|
] });
|
|
21
21
|
}
|
|
22
22
|
const MOVE_KEYS = {
|
|
@@ -25,7 +25,7 @@ const MOVE_KEYS = {
|
|
|
25
25
|
ArrowLeft: "left",
|
|
26
26
|
ArrowRight: "right"
|
|
27
27
|
};
|
|
28
|
-
function KanbanCard({ title, meta, children, className, onMove, onKeyDown, ...props }) {
|
|
28
|
+
function KanbanCard({ title, meta, children, className, onMove, onKeyDown, dragging, ...props }) {
|
|
29
29
|
const handleKeyDown = (e) => {
|
|
30
30
|
onKeyDown?.(e);
|
|
31
31
|
if (!onMove || !e.altKey || e.defaultPrevented) return;
|
|
@@ -42,6 +42,7 @@ function KanbanCard({ title, meta, children, className, onMove, onKeyDown, ...pr
|
|
|
42
42
|
tabIndex: 0,
|
|
43
43
|
className: cx("uix-kanban__card", className),
|
|
44
44
|
onKeyDown: handleKeyDown,
|
|
45
|
+
"data-uix-drag": dragging ? "lifted" : void 0,
|
|
45
46
|
...props,
|
|
46
47
|
children: [
|
|
47
48
|
title && /* @__PURE__ */ jsx("div", { className: "uix-kanban__card-title", children: title }),
|
|
@@ -51,8 +52,12 @@ function KanbanCard({ title, meta, children, className, onMove, onKeyDown, ...pr
|
|
|
51
52
|
}
|
|
52
53
|
);
|
|
53
54
|
}
|
|
55
|
+
function KanbanDropLine({ className, ...props }) {
|
|
56
|
+
return /* @__PURE__ */ jsx("div", { ...props, className: cx("uix-drop-line", className), "aria-hidden": "true" });
|
|
57
|
+
}
|
|
54
58
|
export {
|
|
55
59
|
Kanban,
|
|
56
60
|
KanbanCard,
|
|
57
|
-
KanbanColumn
|
|
61
|
+
KanbanColumn,
|
|
62
|
+
KanbanDropLine
|
|
58
63
|
};
|
package/dist/components/Kbd.d.ts
CHANGED
|
@@ -26,6 +26,11 @@ type KeyShortcut = readonly string[];
|
|
|
26
26
|
declare function ariaKeyShortcuts(keys: KeyShortcut, platform: KbdPlatform): string;
|
|
27
27
|
/** A `shortcut` prop that is key names rather than a node (HAR-2031). */
|
|
28
28
|
declare const isKeyShortcut: (value: unknown) => value is KeyShortcut;
|
|
29
|
+
/**
|
|
30
|
+
* What a screen reader should hear for a shortcut: the key names joined with "+", e.g.
|
|
31
|
+
* "Control+K" or "Command+K" (HAR-2416). The same text `KbdCombo` speaks. Internal.
|
|
32
|
+
*/
|
|
33
|
+
declare function spokenKeys(keys: KeyShortcut, platform: KbdPlatform, keyLabels?: Partial<Record<string, string>>): string;
|
|
29
34
|
interface KbdComboProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
|
|
30
35
|
/**
|
|
31
36
|
* Keys in order, e.g. `['Mod', 'K']`. `Mod` is ⌘ on Apple and Ctrl elsewhere; `Shift`,
|
|
@@ -43,4 +48,4 @@ interface KbdComboProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'
|
|
|
43
48
|
*/
|
|
44
49
|
declare function KbdCombo({ keys, platform, keyLabels, className, ...props }: KbdComboProps): react.JSX.Element;
|
|
45
50
|
|
|
46
|
-
export { Kbd, KbdCombo, type KbdComboProps, type KbdPlatform, type KbdProps, type KeyShortcut, ariaKeyShortcuts, isKeyShortcut, useKbdPlatform };
|
|
51
|
+
export { Kbd, KbdCombo, type KbdComboProps, type KbdPlatform, type KbdProps, type KeyShortcut, ariaKeyShortcuts, isKeyShortcut, spokenKeys, useKbdPlatform };
|
package/dist/components/Kbd.js
CHANGED
|
@@ -48,15 +48,14 @@ function ariaKeyShortcuts(keys, platform) {
|
|
|
48
48
|
return keys.map((key) => ARIA_KEYS[key]?.[platform] ?? (key.length === 1 ? key.toUpperCase() : key)).join("+");
|
|
49
49
|
}
|
|
50
50
|
const isKeyShortcut = (value) => Array.isArray(value) && value.length > 0 && value.every((key) => typeof key === "string");
|
|
51
|
+
function spokenKeys(keys, platform, keyLabels) {
|
|
52
|
+
return keys.map((key) => keyLabels?.[key] ?? KEYS[key]?.[platform]?.[1] ?? key).join("+");
|
|
53
|
+
}
|
|
51
54
|
function KbdCombo({ keys, platform, keyLabels, className, ...props }) {
|
|
52
55
|
const active = useKbdPlatform(platform);
|
|
53
|
-
const parts = keys.map((key) => {
|
|
54
|
-
const known = KEYS[key]?.[active];
|
|
55
|
-
return { glyph: known?.[0] ?? key, spoken: keyLabels?.[key] ?? known?.[1] ?? key };
|
|
56
|
-
});
|
|
57
56
|
return /* @__PURE__ */ jsxs("span", { className: cx("uix-kbd-combo", className), ...props, children: [
|
|
58
|
-
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children:
|
|
59
|
-
/* @__PURE__ */ jsx("span", { className: "uix-kbd-combo__keys", "aria-hidden": "true", children:
|
|
57
|
+
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: spokenKeys(keys, active, keyLabels) }),
|
|
58
|
+
/* @__PURE__ */ jsx("span", { className: "uix-kbd-combo__keys", "aria-hidden": "true", children: keys.map((key, i) => /* @__PURE__ */ jsx("kbd", { className: "uix-kbd", children: KEYS[key]?.[active]?.[0] ?? key }, `${key}-${i}`)) })
|
|
60
59
|
] });
|
|
61
60
|
}
|
|
62
61
|
export {
|
|
@@ -64,5 +63,6 @@ export {
|
|
|
64
63
|
KbdCombo,
|
|
65
64
|
ariaKeyShortcuts,
|
|
66
65
|
isKeyShortcut,
|
|
66
|
+
spokenKeys,
|
|
67
67
|
useKbdPlatform
|
|
68
68
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { ReactNode, KeyboardEvent } from 'react';
|
|
3
3
|
|
|
4
4
|
interface LightboxItem {
|
|
5
5
|
src: string;
|
|
@@ -22,6 +22,21 @@ interface LightboxLabels {
|
|
|
22
22
|
zoomOut: string;
|
|
23
23
|
}
|
|
24
24
|
declare const DEFAULT_LIGHTBOX_LABELS: LightboxLabels;
|
|
25
|
+
/** What `renderOverlay` receives (HAR-1826). */
|
|
26
|
+
interface LightboxOverlayContext {
|
|
27
|
+
/** The item on show (0-based) and the item itself. */
|
|
28
|
+
index: number;
|
|
29
|
+
item: LightboxItem;
|
|
30
|
+
/** The rendered `<img>` or `<video>`; `naturalWidth` / `videoWidth` give the bitmap size. */
|
|
31
|
+
media: HTMLImageElement | HTMLVideoElement | null;
|
|
32
|
+
/**
|
|
33
|
+
* The overlay's size in CSS pixels. The overlay covers exactly the picture (not the letterbox
|
|
34
|
+
* around a fitted one), so a point at `(x, y)` in it is bitmap pixel
|
|
35
|
+
* `(x / width * naturalWidth, y / height * naturalHeight)`. 0 until the media has loaded.
|
|
36
|
+
*/
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
}
|
|
25
40
|
interface LightboxProps {
|
|
26
41
|
items: readonly LightboxItem[];
|
|
27
42
|
/** The item on show (0-based). */
|
|
@@ -33,13 +48,34 @@ interface LightboxProps {
|
|
|
33
48
|
loop?: boolean;
|
|
34
49
|
labels?: Partial<LightboxLabels>;
|
|
35
50
|
className?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Buttons for the toolbar beside Close (HAR-1826), e.g. "Hide area" and "Undo" on a viewer
|
|
53
|
+
* that redacts a screenshot. Tab order: these, then Close. They sit inside the dialog.
|
|
54
|
+
*/
|
|
55
|
+
actions?: ReactNode;
|
|
56
|
+
/**
|
|
57
|
+
* Content laid over the picture, in the picture's own box: it moves and scales with the
|
|
58
|
+
* fit / actual-size toggle. Pointer events on it never swipe or page, so it can draw. Each
|
|
59
|
+
* child positions itself (`position: absolute` in `%`, or px of `width` / `height`).
|
|
60
|
+
*/
|
|
61
|
+
renderOverlay?: (context: LightboxOverlayContext) => ReactNode;
|
|
62
|
+
/**
|
|
63
|
+
* Runs first for every key in the viewer. Call `event.preventDefault()` to claim the key:
|
|
64
|
+
* the viewer then neither pages (arrows, Home, End) nor closes (Escape). For a draft that
|
|
65
|
+
* arrow keys move and Escape cancels.
|
|
66
|
+
*/
|
|
67
|
+
onKeyDown?: (event: KeyboardEvent<HTMLDialogElement>) => void;
|
|
68
|
+
/** Escape only: `event.preventDefault()` keeps the viewer open. */
|
|
69
|
+
onEscapeKeyDown?: (event: KeyboardEvent<HTMLDialogElement>) => void;
|
|
36
70
|
}
|
|
37
71
|
/**
|
|
38
72
|
* A full-screen media viewer over `.uix-lightbox` (HAR-1367; TENSOR C12 `capture-viewer.tsx`,
|
|
39
73
|
* MOTUS C-8 programme gallery): previous/next buttons, ←/→ and Home/End, swipe on touch, an
|
|
40
|
-
* announced "n of total" counter, a caption, and a fit/actual-size
|
|
41
|
-
* so focus stays inside and Escape closes; focus returns to the
|
|
74
|
+
* announced "n of total" counter (not for a single item), a caption, and a fit/actual-size
|
|
75
|
+
* toggle. Native `<dialog>`, so focus stays inside and Escape closes; focus returns to the
|
|
76
|
+
* control that opened it. An annotating viewer (HAR-1826) adds toolbar `actions`, draws in
|
|
77
|
+
* `renderOverlay`, and claims Escape and the arrows with `onKeyDown` while a draft is open.
|
|
42
78
|
*/
|
|
43
|
-
declare function Lightbox({ items, index, onIndexChange, open, onClose, loop, labels: labelOverrides, className }: LightboxProps): react.JSX.Element;
|
|
79
|
+
declare function Lightbox({ items, index, onIndexChange, open, onClose, loop, labels: labelOverrides, className, actions, renderOverlay, onKeyDown: onKeyDownProp, onEscapeKeyDown, }: LightboxProps): react.JSX.Element;
|
|
44
80
|
|
|
45
|
-
export { DEFAULT_LIGHTBOX_LABELS, Lightbox, type LightboxItem, type LightboxLabels, type LightboxProps };
|
|
81
|
+
export { DEFAULT_LIGHTBOX_LABELS, Lightbox, type LightboxItem, type LightboxLabels, type LightboxOverlayContext, type LightboxProps };
|
|
@@ -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: [
|
|
@@ -35,6 +35,7 @@ interface ModalProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
|
|
|
35
35
|
*/
|
|
36
36
|
instant?: boolean;
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
/** A modal `<dialog>`; the ref reaches it (HAR-1831). */
|
|
39
|
+
declare const Modal: react.ForwardRefExoticComponent<ModalProps & react.RefAttributes<HTMLDialogElement>>;
|
|
39
40
|
|
|
40
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
|