@tensor_1/react 2.19.2 → 2.21.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.
Files changed (70) hide show
  1. package/README.md +14 -0
  2. package/dist/{ChartCore-Do6gRBN3.d.cts → ChartCore-DzWe10bN.d.cts} +16 -1
  3. package/dist/chart-preset.cjs +25 -13
  4. package/dist/chart-preset.d.cts +2 -2
  5. package/dist/chart-preset.d.ts +1 -1
  6. package/dist/chart-preset.js +2 -1
  7. package/dist/chart.cjs +25 -13
  8. package/dist/chart.d.cts +2 -2
  9. package/dist/chart.d.ts +1 -1
  10. package/dist/chart.js +2 -1
  11. package/dist/components/AppShell.d.ts +5 -1
  12. package/dist/components/AppShell.js +4 -2
  13. package/dist/components/Avatar.d.ts +3 -1
  14. package/dist/components/Avatar.js +2 -2
  15. package/dist/components/BrandProfiles.d.ts +29 -2
  16. package/dist/components/BrandProfiles.js +45 -20
  17. package/dist/components/BuilderCanvas.d.ts +27 -2
  18. package/dist/components/BuilderCanvas.js +45 -22
  19. package/dist/components/Chart.d.ts +1 -1
  20. package/dist/components/Chart.js +2 -1
  21. package/dist/components/ChartCore.d.ts +17 -2
  22. package/dist/components/ChartCore.js +24 -13
  23. package/dist/components/ColorPicker.d.ts +24 -2
  24. package/dist/components/ColorPicker.js +38 -20
  25. package/dist/components/CommandPalette.d.ts +3 -1
  26. package/dist/components/CommandPalette.js +2 -2
  27. package/dist/components/CopyButton.d.ts +25 -0
  28. package/dist/components/CopyButton.js +52 -0
  29. package/dist/components/DateRangePicker.d.ts +21 -2
  30. package/dist/components/DateRangePicker.js +26 -6
  31. package/dist/components/DetailPage.d.ts +22 -3
  32. package/dist/components/DetailPage.js +33 -3
  33. package/dist/components/DiffViewer.d.ts +34 -3
  34. package/dist/components/DiffViewer.js +60 -40
  35. package/dist/components/Drawer.d.ts +3 -1
  36. package/dist/components/Drawer.js +2 -2
  37. package/dist/components/Inbox.d.ts +3 -1
  38. package/dist/components/Inbox.js +2 -2
  39. package/dist/components/MatchReview.d.ts +49 -2
  40. package/dist/components/MatchReview.js +72 -30
  41. package/dist/components/Meter.d.ts +3 -1
  42. package/dist/components/Meter.js +3 -2
  43. package/dist/components/Pagination.d.ts +10 -2
  44. package/dist/components/Pagination.js +11 -4
  45. package/dist/components/Peek.d.ts +7 -1
  46. package/dist/components/Peek.js +4 -4
  47. package/dist/components/RelationshipGraph.js +6 -2
  48. package/dist/components/RuleBuilder.d.ts +42 -2
  49. package/dist/components/RuleBuilder.js +71 -37
  50. package/dist/components/SchedulingCalendar.d.ts +25 -2
  51. package/dist/components/SchedulingCalendar.js +38 -15
  52. package/dist/components/Stat.d.ts +24 -3
  53. package/dist/components/Stat.js +51 -10
  54. package/dist/components/Table.d.ts +11 -3
  55. package/dist/components/Table.js +5 -8
  56. package/dist/components/Tabs.d.ts +14 -1
  57. package/dist/components/Tabs.js +88 -19
  58. package/dist/components/Toast.d.ts +6 -2
  59. package/dist/components/Toast.js +4 -4
  60. package/dist/components/Tree.d.ts +9 -1
  61. package/dist/components/Tree.js +55 -8
  62. package/dist/copy-model.d.ts +12 -0
  63. package/dist/copy-model.js +12 -0
  64. package/dist/fill-label.d.ts +7 -0
  65. package/dist/fill-label.js +9 -0
  66. package/dist/index.cjs +1480 -1004
  67. package/dist/index.d.cts +387 -30
  68. package/dist/index.d.ts +11 -10
  69. package/dist/index.js +20 -9
  70. package/package.json +1 -1
@@ -3,8 +3,24 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useId, useMemo, useRef, useState } from "react";
4
4
  import { addCalendarDays, addCalendarMonths, buildMonthGrid, isDateInRange, isDateUnavailable, selectRangeDate, startOfMonth } from "../calendar-model.js";
5
5
  import { cx } from "../cx.js";
6
+ const DEFAULT_DATE_RANGE_PICKER_LABELS = {
7
+ previous: "Previous",
8
+ previousMonth: "Show previous month",
9
+ next: "Next",
10
+ nextMonth: "Show next month",
11
+ noneSelected: "No date range selected.",
12
+ startSelected: "Start date {start} selected. Choose an end date.",
13
+ rangeSelected: "{start} to {end} selected.",
14
+ rangeStart: "start of selected range",
15
+ rangeEnd: "end of selected range",
16
+ inRange: "in selected range"
17
+ };
6
18
  const monthLabel = (date, locale) => new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
7
19
  const dateLabel = (date, locale) => new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
20
+ const weekdayNames = (locale) => {
21
+ const fmt = new Intl.DateTimeFormat(locale, { weekday: "short", timeZone: "UTC" });
22
+ return Array.from({ length: 7 }, (_, i) => fmt.format(new Date(Date.UTC(2024, 0, 1 + i))));
23
+ };
8
24
  function DateRangePicker({
9
25
  value,
10
26
  onChange,
@@ -19,9 +35,12 @@ function DateRangePicker({
19
35
  invalid,
20
36
  invalidMessage = "The selected date range is invalid.",
21
37
  disabled,
22
- className
38
+ className,
39
+ labels: labelOverrides
23
40
  }) {
24
41
  const id = useId();
42
+ const labels = { ...DEFAULT_DATE_RANGE_PICKER_LABELS, ...labelOverrides };
43
+ const weekdays = useMemo(() => weekdayNames(locale), [locale]);
25
44
  const [internalMonth, setInternalMonth] = useState(() => startOfMonth(controlledMonth ?? value.start ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)));
26
45
  const visibleMonth = startOfMonth(controlledMonth ?? internalMonth);
27
46
  const [activeDate, setActiveDate] = useState(value.end ?? value.start ?? visibleMonth);
@@ -76,18 +95,18 @@ function DateRangePicker({
76
95
  focusDate(addCalendarMonths(date, event.key === "PageUp" ? -1 : 1));
77
96
  }
78
97
  };
79
- const announcement = !value.start ? "No date range selected." : !value.end ? `Start date ${dateLabel(value.start, locale)} selected. Choose an end date.` : `${dateLabel(value.start, locale)} to ${dateLabel(value.end, locale)} selected.`;
98
+ const announcement = !value.start ? labels.noneSelected : !value.end ? labels.startSelected.replace("{start}", dateLabel(value.start, locale)) : labels.rangeSelected.replace("{start}", dateLabel(value.start, locale)).replace("{end}", dateLabel(value.end, locale));
80
99
  return /* @__PURE__ */ jsxs("section", { className: cx("uix-date-range-picker", rangeInvalid && "uix-date-range-picker--invalid", className), "aria-labelledby": `${id}-label`, children: [
81
100
  /* @__PURE__ */ jsxs("div", { className: "uix-date-range-picker__header", children: [
82
101
  /* @__PURE__ */ jsx("h3", { id: `${id}-label`, children: label }),
83
102
  /* @__PURE__ */ jsxs("div", { children: [
84
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => setMonth(addCalendarMonths(visibleMonth, -1)), "aria-label": "Show previous month", children: "Previous" }),
85
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => setMonth(addCalendarMonths(visibleMonth, 1)), "aria-label": "Show next month", children: "Next" })
103
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => setMonth(addCalendarMonths(visibleMonth, -1)), "aria-label": labels.previousMonth, children: labels.previous }),
104
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => setMonth(addCalendarMonths(visibleMonth, 1)), "aria-label": labels.nextMonth, children: labels.next })
86
105
  ] })
87
106
  ] }),
88
107
  /* @__PURE__ */ jsx("div", { ref: rootRef, className: "uix-date-range-picker__months", children: calendars.map(({ month, days }) => /* @__PURE__ */ jsxs("div", { className: "uix-date-range-picker__month", children: [
89
108
  /* @__PURE__ */ jsx("h4", { children: monthLabel(month, locale) }),
90
- /* @__PURE__ */ jsx("div", { className: "uix-date-range-picker__weekdays", "aria-hidden": "true", children: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map((day) => /* @__PURE__ */ jsx("span", { children: day }, day)) }),
109
+ /* @__PURE__ */ jsx("div", { className: "uix-date-range-picker__weekdays", "aria-hidden": "true", children: weekdays.map((day, index) => /* @__PURE__ */ jsx("span", { children: day }, index)) }),
91
110
  /* @__PURE__ */ jsxs("div", { className: "uix-date-range-picker__grid", role: "group", "aria-label": monthLabel(month, locale), children: [
92
111
  Array.from({ length: ((/* @__PURE__ */ new Date(`${month}T00:00:00Z`)).getUTCDay() + 6) % 7 }, (_, index) => /* @__PURE__ */ jsx("span", { "aria-hidden": "true" }, `blank-${index}`)),
93
112
  days.map((day) => {
@@ -101,7 +120,7 @@ function DateRangePicker({
101
120
  "data-range-edge": edge,
102
121
  "data-in-range": isDateInRange(day.date, value) || void 0,
103
122
  "aria-pressed": edge != null || isDateInRange(day.date, value),
104
- "aria-label": `${dateLabel(day.date, locale)}${edge ? `, ${edge} of selected range` : isDateInRange(day.date, value) ? ", in selected range" : ""}`,
123
+ "aria-label": `${dateLabel(day.date, locale)}${edge ? `, ${edge === "start" ? labels.rangeStart : labels.rangeEnd}` : isDateInRange(day.date, value) ? `, ${labels.inRange}` : ""}`,
105
124
  tabIndex: activeDate === day.date ? 0 : -1,
106
125
  disabled: unavailable,
107
126
  onFocus: () => setActiveDate(day.date),
@@ -119,5 +138,6 @@ function DateRangePicker({
119
138
  ] });
120
139
  }
121
140
  export {
141
+ DEFAULT_DATE_RANGE_PICKER_LABELS,
122
142
  DateRangePicker
123
143
  };
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { HTMLAttributes, ReactNode } from 'react';
2
+ import { HTMLAttributes, ReactNode, MouseEvent } from 'react';
3
3
 
4
4
  interface DetailPageTab {
5
5
  id: string;
@@ -12,6 +12,14 @@ interface DetailPageMetric {
12
12
  label: ReactNode;
13
13
  value: ReactNode;
14
14
  }
15
+ /** What `renderLink` receives: render it as your router's link (TENSOR RX-125, UIX-10). */
16
+ interface DetailPageLinkProps {
17
+ href: string;
18
+ className: string;
19
+ children: ReactNode;
20
+ 'aria-current'?: 'page';
21
+ onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
22
+ }
15
23
  interface DetailPageProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
16
24
  back?: {
17
25
  href: string;
@@ -26,8 +34,19 @@ interface DetailPageProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
26
34
  side?: ReactNode;
27
35
  children?: ReactNode;
28
36
  tabsLabel?: string;
37
+ /**
38
+ * Renders the back link and every tab link. Default: a plain `<a>`. Pass your
39
+ * router's link (e.g. next/link) so switching tabs never reloads the page.
40
+ */
41
+ renderLink?: (props: DetailPageLinkProps) => ReactNode;
42
+ /**
43
+ * Controlled tabs: called with the tab id on a plain left click, which then does
44
+ * not navigate. `href` stays the progressive-enhancement fallback (new tab,
45
+ * modified click, no JS).
46
+ */
47
+ onTabSelect?: (id: string) => void;
29
48
  }
30
49
  /** Framework-neutral record-detail scaffold; routing and data policy stay with the application. */
31
- declare function DetailPage({ back, eyebrow, title, subtitle, actions, tabs, metrics, side, children, tabsLabel, className, ...props }: DetailPageProps): react.JSX.Element;
50
+ declare function DetailPage({ back, eyebrow, title, subtitle, actions, tabs, metrics, side, children, tabsLabel, renderLink, onTabSelect, className, ...props }: DetailPageProps): react.JSX.Element;
32
51
 
33
- export { DetailPage, type DetailPageMetric, type DetailPageProps, type DetailPageTab };
52
+ export { DetailPage, type DetailPageLinkProps, type DetailPageMetric, type DetailPageProps, type DetailPageTab };
@@ -1,16 +1,46 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment } from "react";
2
3
  import { cx } from "../cx.js";
3
4
  import { DetailLayout } from "./DetailLayout.js";
4
5
  import { PageHeader } from "./PageHeader.js";
5
- function DetailPage({ back, eyebrow, title, subtitle, actions, tabs, metrics, side, children, tabsLabel, className, ...props }) {
6
+ const plainLink = ({ href, className, children, onClick, ...rest }) => /* @__PURE__ */ jsx("a", { href, className, onClick, ...rest, children });
7
+ const isPlainLeftClick = (e) => e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;
8
+ function DetailPage({
9
+ back,
10
+ eyebrow,
11
+ title,
12
+ subtitle,
13
+ actions,
14
+ tabs,
15
+ metrics,
16
+ side,
17
+ children,
18
+ tabsLabel,
19
+ renderLink = plainLink,
20
+ onTabSelect,
21
+ className,
22
+ ...props
23
+ }) {
6
24
  return /* @__PURE__ */ jsxs("section", { className: cx("uix-detail-page", className), ...props, children: [
7
- back && /* @__PURE__ */ jsx("a", { className: "uix-link uix-link--quiet uix-detail-page__back", href: back.href, children: back.label }),
25
+ back && renderLink({ href: back.href, className: "uix-link uix-link--quiet uix-detail-page__back", children: back.label }),
8
26
  /* @__PURE__ */ jsx(PageHeader, { eyebrow, title, subtitle, actions }),
9
27
  metrics && metrics.length > 0 && /* @__PURE__ */ jsx("dl", { className: "uix-detail-page__metrics", children: metrics.map((metric) => /* @__PURE__ */ jsxs("div", { children: [
10
28
  /* @__PURE__ */ jsx("dt", { children: metric.label }),
11
29
  /* @__PURE__ */ jsx("dd", { children: metric.value })
12
30
  ] }, metric.id)) }),
13
- tabs && tabs.length > 0 && /* @__PURE__ */ jsx("nav", { className: "uix-tabs", "aria-label": tabsLabel, children: tabs.map((tab) => /* @__PURE__ */ jsx("a", { className: "uix-tab", "aria-current": tab.active ? "page" : void 0, href: tab.href, children: tab.label }, tab.id)) }),
31
+ tabs && tabs.length > 0 && /* @__PURE__ */ jsx("nav", { className: "uix-tabs", "aria-label": tabsLabel, children: tabs.map((tab) => /* @__PURE__ */ jsx(Fragment, { children: renderLink({
32
+ href: tab.href,
33
+ className: "uix-tab",
34
+ children: tab.label,
35
+ ...tab.active ? { "aria-current": "page" } : {},
36
+ ...onTabSelect ? {
37
+ onClick: (e) => {
38
+ if (!isPlainLeftClick(e)) return;
39
+ e.preventDefault();
40
+ onTabSelect(tab.id);
41
+ }
42
+ } : {}
43
+ }) }, tab.id)) }),
14
44
  /* @__PURE__ */ jsx(DetailLayout, { side, children })
15
45
  ] });
16
46
  }
@@ -2,6 +2,37 @@ import * as react from 'react';
2
2
  import { JsonValue } from '../json-value.js';
3
3
  import { DiffEntry, DiffResolution } from '../diff-model.js';
4
4
 
5
+ /**
6
+ * Every word the diff viewer renders (TENSOR RX-125, UIX-12). `base` / `current` /
7
+ * `incoming` keep their meaning from before; `{path}`, `{version}`, `{resolved}` and
8
+ * `{total}` are placeholders.
9
+ */
10
+ interface DiffViewerLabels {
11
+ base: string;
12
+ current: string;
13
+ incoming: string;
14
+ region: string;
15
+ summary: string;
16
+ noDifferences: string;
17
+ noDifferencesDetail: string;
18
+ conflicted: string;
19
+ changed: string;
20
+ added: string;
21
+ removed: string;
22
+ resolved: string;
23
+ pending: string;
24
+ valueFor: string;
25
+ resolve: string;
26
+ acceptIncoming: string;
27
+ keepCurrent: string;
28
+ markPending: string;
29
+ resolutionAccept: string;
30
+ resolutionSkip: string;
31
+ resolutionPending: string;
32
+ notPresent: string;
33
+ progress: string;
34
+ }
35
+ declare const DEFAULT_DIFF_VIEWER_LABELS: DiffViewerLabels;
5
36
  interface DiffViewerProps {
6
37
  base?: JsonValue;
7
38
  current?: JsonValue;
@@ -9,10 +40,10 @@ interface DiffViewerProps {
9
40
  entries?: DiffEntry[];
10
41
  resolutions?: Record<string, DiffResolution>;
11
42
  onResolutionChange?: (path: string, resolution: DiffResolution) => void;
12
- labels?: Partial<Record<'base' | 'current' | 'incoming', string>>;
43
+ labels?: Partial<DiffViewerLabels>;
13
44
  className?: string;
14
45
  }
15
46
  /** Three-way configuration diff with per-entry keyboard-operable resolution. */
16
- declare function DiffViewer({ base, current, incoming, entries: providedEntries, resolutions: controlledResolutions, onResolutionChange, labels, className, }: DiffViewerProps): react.JSX.Element;
47
+ declare function DiffViewer({ base, current, incoming, entries: providedEntries, resolutions: controlledResolutions, onResolutionChange, labels: labelOverrides, className, }: DiffViewerProps): react.JSX.Element;
17
48
 
18
- export { DiffViewer, type DiffViewerProps };
49
+ export { DEFAULT_DIFF_VIEWER_LABELS, DiffViewer, type DiffViewerLabels, type DiffViewerProps };
@@ -4,14 +4,34 @@ import { useId, useMemo, useState } from "react";
4
4
  import { buildThreeWayDiff, summarizeDiff } from "../diff-model.js";
5
5
  import { cx } from "../cx.js";
6
6
  import { StatusPill } from "./StatusPill.js";
7
- const GROUPS = [
8
- { kind: "conflicted", label: "Conflicted" },
9
- { kind: "changed", label: "Changed" },
10
- { kind: "added", label: "Added" },
11
- { kind: "removed", label: "Removed" }
12
- ];
13
- const DEFAULT_LABELS = {};
14
- const renderValue = (value) => value === void 0 ? "Not present" : JSON.stringify(value, null, 2);
7
+ import { fillLabel } from "../fill-label.js";
8
+ const DEFAULT_DIFF_VIEWER_LABELS = {
9
+ base: "Base",
10
+ current: "Current",
11
+ incoming: "Incoming",
12
+ region: "Configuration differences",
13
+ summary: "Difference summary",
14
+ noDifferences: "No differences",
15
+ noDifferencesDetail: "Base, current, and incoming values are aligned.",
16
+ conflicted: "Conflicted",
17
+ changed: "Changed",
18
+ added: "Added",
19
+ removed: "Removed",
20
+ resolved: "resolved",
21
+ pending: "pending",
22
+ valueFor: "{version} value for {path}",
23
+ resolve: "Resolve {path}",
24
+ acceptIncoming: "Accept incoming",
25
+ keepCurrent: "Keep current",
26
+ markPending: "Mark pending",
27
+ resolutionAccept: "accepted",
28
+ resolutionSkip: "kept",
29
+ resolutionPending: "pending",
30
+ notPresent: "Not present",
31
+ progress: "{resolved} of {total} differences resolved."
32
+ };
33
+ const GROUPS = ["conflicted", "changed", "added", "removed"];
34
+ const renderValue = (value, notPresent) => value === void 0 ? notPresent : JSON.stringify(value, null, 2);
15
35
  function DiffViewer({
16
36
  base,
17
37
  current,
@@ -19,9 +39,11 @@ function DiffViewer({
19
39
  entries: providedEntries,
20
40
  resolutions: controlledResolutions,
21
41
  onResolutionChange,
22
- labels = DEFAULT_LABELS,
42
+ labels: labelOverrides,
23
43
  className
24
44
  }) {
45
+ const labels = { ...DEFAULT_DIFF_VIEWER_LABELS, ...labelOverrides };
46
+ const resolutionLabel = { accept: labels.resolutionAccept, skip: labels.resolutionSkip, pending: labels.resolutionPending };
25
47
  const id = useId();
26
48
  const entries = useMemo(() => providedEntries ?? buildThreeWayDiff(base, current, incoming), [providedEntries, base, current, incoming]);
27
49
  const [internalResolutions, setInternalResolutions] = useState({});
@@ -31,32 +53,34 @@ function DiffViewer({
31
53
  if (controlledResolutions === void 0) setInternalResolutions((currentState) => ({ ...currentState, [path]: resolution }));
32
54
  onResolutionChange?.(path, resolution);
33
55
  };
34
- if (entries.length === 0) return /* @__PURE__ */ jsxs("section", { className: cx("uix-diff-viewer uix-diff-viewer--empty", className), "aria-label": "Configuration differences", children: [
35
- /* @__PURE__ */ jsx("h3", { children: "No differences" }),
36
- /* @__PURE__ */ jsx("p", { children: "Base, current, and incoming values are aligned." })
56
+ if (entries.length === 0) return /* @__PURE__ */ jsxs("section", { className: cx("uix-diff-viewer uix-diff-viewer--empty", className), "aria-label": labels.region, children: [
57
+ /* @__PURE__ */ jsx("h3", { children: labels.noDifferences }),
58
+ /* @__PURE__ */ jsx("p", { children: labels.noDifferencesDetail })
37
59
  ] });
38
- return /* @__PURE__ */ jsxs("section", { className: cx("uix-diff-viewer", className), "aria-label": "Configuration differences", children: [
39
- /* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__summary", "aria-label": "Difference summary", children: [
40
- GROUPS.map((group) => /* @__PURE__ */ jsxs("span", { children: [
41
- /* @__PURE__ */ jsx("b", { children: summary[group.kind] }),
60
+ return /* @__PURE__ */ jsxs("section", { className: cx("uix-diff-viewer", className), "aria-label": labels.region, children: [
61
+ /* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__summary", "aria-label": labels.summary, children: [
62
+ GROUPS.map((kind) => /* @__PURE__ */ jsxs("span", { children: [
63
+ /* @__PURE__ */ jsx("b", { children: summary[kind] }),
42
64
  " ",
43
- group.label.toLowerCase()
44
- ] }, group.kind)),
65
+ labels[kind].toLowerCase()
66
+ ] }, kind)),
45
67
  /* @__PURE__ */ jsxs("span", { children: [
46
68
  /* @__PURE__ */ jsx("b", { children: summary.resolved }),
47
- " resolved"
69
+ " ",
70
+ labels.resolved
48
71
  ] }),
49
72
  /* @__PURE__ */ jsxs("span", { children: [
50
73
  /* @__PURE__ */ jsx("b", { children: summary.pending }),
51
- " pending"
74
+ " ",
75
+ labels.pending
52
76
  ] })
53
77
  ] }),
54
- GROUPS.map((group) => {
55
- const grouped = entries.filter((entry) => entry.kind === group.kind);
78
+ GROUPS.map((kind) => {
79
+ const grouped = entries.filter((entry) => entry.kind === kind);
56
80
  if (!grouped.length) return null;
57
- return /* @__PURE__ */ jsxs("section", { className: "uix-diff-viewer__group", "aria-labelledby": `${id}-${group.kind}`, children: [
58
- /* @__PURE__ */ jsxs("h3", { id: `${id}-${group.kind}`, children: [
59
- group.label,
81
+ return /* @__PURE__ */ jsxs("section", { className: "uix-diff-viewer__group", "aria-labelledby": `${id}-${kind}`, children: [
82
+ /* @__PURE__ */ jsxs("h3", { id: `${id}-${kind}`, children: [
83
+ labels[kind],
60
84
  " ",
61
85
  /* @__PURE__ */ jsx("span", { children: grouped.length })
62
86
  ] }),
@@ -65,29 +89,25 @@ function DiffViewer({
65
89
  return /* @__PURE__ */ jsxs("details", { className: "uix-diff-viewer__entry", "data-kind": entry.kind, open: entry.kind === "conflicted", children: [
66
90
  /* @__PURE__ */ jsxs("summary", { children: [
67
91
  /* @__PURE__ */ jsx("code", { children: entry.path }),
68
- /* @__PURE__ */ jsx(StatusPill, { tone: resolution === "pending" ? "warning" : "success", children: resolution })
92
+ /* @__PURE__ */ jsx(StatusPill, { tone: resolution === "pending" ? "warning" : "success", children: resolutionLabel[resolution] })
69
93
  ] }),
70
- /* @__PURE__ */ jsx("div", { className: "uix-diff-viewer__versions", children: ["base", "current", "incoming"].map((version) => /* @__PURE__ */ jsxs("div", { role: "region", "aria-label": `${labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}`} value for ${entry.path}`, children: [
71
- /* @__PURE__ */ jsx("strong", { children: labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}` }),
72
- /* @__PURE__ */ jsx("pre", { children: renderValue(entry[version]) })
94
+ /* @__PURE__ */ jsx("div", { className: "uix-diff-viewer__versions", children: ["base", "current", "incoming"].map((version) => /* @__PURE__ */ jsxs("div", { role: "region", "aria-label": fillLabel(labels.valueFor, { version: labels[version], path: entry.path }), children: [
95
+ /* @__PURE__ */ jsx("strong", { children: labels[version] }),
96
+ /* @__PURE__ */ jsx("pre", { children: renderValue(entry[version], labels.notPresent) })
73
97
  ] }, version)) }),
74
- /* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__actions", "aria-label": `Resolve ${entry.path}`, children: [
75
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", "aria-pressed": resolution === "accept", onClick: () => resolve(entry.path, "accept"), children: "Accept incoming" }),
76
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", "aria-pressed": resolution === "skip", onClick: () => resolve(entry.path, "skip"), children: "Keep current" }),
77
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", "aria-pressed": resolution === "pending", onClick: () => resolve(entry.path, "pending"), children: "Mark pending" })
98
+ /* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__actions", "aria-label": fillLabel(labels.resolve, { path: entry.path }), children: [
99
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", "aria-pressed": resolution === "accept", onClick: () => resolve(entry.path, "accept"), children: labels.acceptIncoming }),
100
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", "aria-pressed": resolution === "skip", onClick: () => resolve(entry.path, "skip"), children: labels.keepCurrent }),
101
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", "aria-pressed": resolution === "pending", onClick: () => resolve(entry.path, "pending"), children: labels.markPending })
78
102
  ] })
79
103
  ] }, entry.path);
80
104
  })
81
- ] }, group.kind);
105
+ ] }, kind);
82
106
  }),
83
- /* @__PURE__ */ jsxs("p", { className: "uix-visually-hidden", "aria-live": "polite", children: [
84
- summary.resolved,
85
- " of ",
86
- entries.length,
87
- " differences resolved."
88
- ] })
107
+ /* @__PURE__ */ jsx("p", { className: "uix-visually-hidden", "aria-live": "polite", children: fillLabel(labels.progress, { resolved: summary.resolved, total: entries.length }) })
89
108
  ] });
90
109
  }
91
110
  export {
111
+ DEFAULT_DIFF_VIEWER_LABELS,
92
112
  DiffViewer
93
113
  };
@@ -2,12 +2,14 @@ import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode } from 'react';
3
3
 
4
4
  interface DrawerProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
5
+ /** TENSOR RX-125 (UIX-04): translatable; English default. */
6
+ closeLabel?: string;
5
7
  open: boolean;
6
8
  onClose?: () => void;
7
9
  title?: ReactNode;
8
10
  children?: ReactNode;
9
11
  footer?: ReactNode;
10
12
  }
11
- declare function Drawer({ open, onClose, title, children, footer, className, ...rest }: DrawerProps): react.JSX.Element;
13
+ declare function Drawer({ open, onClose, title, children, footer, closeLabel, className, ...rest }: DrawerProps): react.JSX.Element;
12
14
 
13
15
  export { Drawer, type DrawerProps };
@@ -4,7 +4,7 @@ import { useId } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { useDialog } from "../hooks/useDialog.js";
6
6
  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" }) });
7
- function Drawer({ open, onClose, title, children, footer, className, ...rest }) {
7
+ function Drawer({ open, onClose, title, children, footer, closeLabel = "Close drawer", className, ...rest }) {
8
8
  const ref = useDialog(open, onClose);
9
9
  const titleId = useId();
10
10
  return /* @__PURE__ */ jsxs("dialog", { ref, className: cx("uix-drawer", className), "aria-labelledby": title ? titleId : void 0, ...rest, children: [
@@ -15,7 +15,7 @@ function Drawer({ open, onClose, title, children, footer, className, ...rest })
15
15
  {
16
16
  style: { marginLeft: "auto", border: 0, background: "transparent", color: "var(--uix-text-muted)", cursor: "pointer", width: 30, height: 30, display: "grid", placeItems: "center", borderRadius: "var(--uix-radius-sm)" },
17
17
  onClick: onClose,
18
- "aria-label": "Close drawer",
18
+ "aria-label": closeLabel,
19
19
  children: /* @__PURE__ */ jsx(CloseIcon, {})
20
20
  }
21
21
  )
@@ -13,6 +13,8 @@ interface InboxProps extends HTMLAttributes<HTMLDivElement> {
13
13
  /** Two-pane inbox master-detail container backed by `.uix-inbox`. Place `<InboxList>` and `<InboxDetail>` as children. */
14
14
  declare function Inbox({ view, children, className, ...props }: InboxProps): react.JSX.Element;
15
15
  interface InboxListProps extends HTMLAttributes<HTMLDivElement> {
16
+ /** TENSOR RX-125 (UIX-04): translatable; English default. */
17
+ listLabel?: string;
16
18
  children?: ReactNode;
17
19
  }
18
20
  /**
@@ -20,7 +22,7 @@ interface InboxListProps extends HTMLAttributes<HTMLDivElement> {
20
22
  * (UIX-A11Y-2): the list is the single tab stop; ArrowUp/ArrowDown/Home/End move the
21
23
  * active option via `aria-activedescendant`, Enter/Space click it.
22
24
  */
23
- declare function InboxList({ children, className, ...props }: InboxListProps): react.JSX.Element;
25
+ declare function InboxList({ children, listLabel, className, ...props }: InboxListProps): react.JSX.Element;
24
26
  interface InboxItemProps extends HTMLAttributes<HTMLDivElement> {
25
27
  /** Primary subject / title line. */
26
28
  subject?: ReactNode;
@@ -6,7 +6,7 @@ function Inbox({ view, children, className, ...props }) {
6
6
  return /* @__PURE__ */ jsx("div", { className: cx("uix-inbox", className), "data-view": view, ...props, children });
7
7
  }
8
8
  const InboxListCtx = createContext({});
9
- function InboxList({ children, className, ...props }) {
9
+ function InboxList({ children, listLabel = "Inbox", className, ...props }) {
10
10
  const listRef = useRef(null);
11
11
  const [activeId, setActiveId] = useState(void 0);
12
12
  const ctx = useMemo(() => ({ activeId }), [activeId]);
@@ -51,7 +51,7 @@ function InboxList({ children, className, ...props }) {
51
51
  className: cx("uix-inbox__list", className),
52
52
  role: "listbox",
53
53
  tabIndex: 0,
54
- "aria-label": "Inbox",
54
+ "aria-label": listLabel,
55
55
  "aria-activedescendant": activeId,
56
56
  onKeyDown,
57
57
  ...props,
@@ -19,6 +19,52 @@ interface MatchBulkResult {
19
19
  succeeded: number;
20
20
  failed?: number;
21
21
  }
22
+ /**
23
+ * Every word the match review renders or announces (TENSOR RX-125, UIX-12). Plural
24
+ * pairs are `…One` / `…Many`; `{count}`, `{pending}`, `{total}`, `{id}` are placeholders.
25
+ */
26
+ interface MatchReviewLabels {
27
+ region: string;
28
+ loading: string;
29
+ retry: string;
30
+ empty: string;
31
+ emptyDetail: string;
32
+ pendingOf: string;
33
+ bulk: string;
34
+ selectedCount: string;
35
+ applying: string;
36
+ acceptSelected: string;
37
+ dismissSelected: string;
38
+ caption: string;
39
+ select: string;
40
+ confidence: string;
41
+ status: string;
42
+ actions: string;
43
+ selectCandidate: string;
44
+ notScored: string;
45
+ resolved: string;
46
+ pending: string;
47
+ decisionAccepted: string;
48
+ decisionDismissed: string;
49
+ decisionSplit: string;
50
+ decisionOther: string;
51
+ accept: string;
52
+ dismiss: string;
53
+ split: string;
54
+ pickOther: string;
55
+ acceptingOne: string;
56
+ acceptingMany: string;
57
+ dismissingOne: string;
58
+ dismissingMany: string;
59
+ acceptedOne: string;
60
+ acceptedMany: string;
61
+ dismissedOne: string;
62
+ dismissedMany: string;
63
+ failedSuffix: string;
64
+ bulkAcceptFailed: string;
65
+ bulkDismissFailed: string;
66
+ }
67
+ declare const DEFAULT_MATCH_REVIEW_LABELS: MatchReviewLabels;
22
68
  interface MatchReviewProps {
23
69
  incoming: Record<string, JsonValue>;
24
70
  incomingLabel?: string;
@@ -32,8 +78,9 @@ interface MatchReviewProps {
32
78
  error?: string;
33
79
  onRetry?: () => void;
34
80
  className?: string;
81
+ labels?: Partial<MatchReviewLabels>;
35
82
  }
36
83
  /** Generic descriptor-driven incoming-record to candidate-match decision surface. */
37
- declare function MatchReview({ incoming, incomingLabel, fields, candidates, selectedIds, onSelectionChange, onDecision, onBulkDecision, loading, error, onRetry, className, }: MatchReviewProps): react.JSX.Element;
84
+ declare function MatchReview({ incoming, incomingLabel, fields, candidates, selectedIds, onSelectionChange, onDecision, onBulkDecision, loading, error, onRetry, className, labels: labelOverrides, }: MatchReviewProps): react.JSX.Element;
38
85
 
39
- export { type MatchBulkResult, MatchReview, type MatchReviewAction, type MatchReviewCandidate, type MatchReviewField, type MatchReviewProps };
86
+ export { DEFAULT_MATCH_REVIEW_LABELS, type MatchBulkResult, MatchReview, type MatchReviewAction, type MatchReviewCandidate, type MatchReviewField, type MatchReviewLabels, type MatchReviewProps };