@tensor_1/react 2.31.0 → 2.32.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 (114) hide show
  1. package/README.md +350 -0
  2. package/dist/attachment-labels.d.ts +16 -0
  3. package/dist/attachment-labels.js +9 -0
  4. package/dist/calendar-model.d.ts +141 -1
  5. package/dist/calendar-model.js +200 -11
  6. package/dist/chart-preset.cjs +127 -7
  7. package/dist/chart-preset.d.cts +2 -2
  8. package/dist/chart-preset.d.ts +1 -0
  9. package/dist/chart-preset.js +6 -1
  10. package/dist/chart-theme-Cl0IrkKQ.d.cts +112 -0
  11. package/dist/chart-theme.d.ts +51 -0
  12. package/dist/chart-theme.js +99 -0
  13. package/dist/chart.cjs +127 -7
  14. package/dist/chart.d.cts +2 -2
  15. package/dist/chart.d.ts +1 -0
  16. package/dist/chart.js +6 -2
  17. package/dist/components/Alert.d.ts +17 -1
  18. package/dist/components/Alert.js +9 -4
  19. package/dist/components/AlertDismiss.d.ts +14 -0
  20. package/dist/components/AlertDismiss.js +11 -0
  21. package/dist/components/Attachment.d.ts +15 -12
  22. package/dist/components/Attachment.js +17 -17
  23. package/dist/components/AttachmentParts.d.ts +24 -0
  24. package/dist/components/AttachmentParts.js +20 -0
  25. package/dist/components/Chart.d.ts +1 -0
  26. package/dist/components/Chart.js +6 -1
  27. package/dist/components/ChartCore.d.ts +6 -1
  28. package/dist/components/ChartCore.js +23 -5
  29. package/dist/components/Chip.d.ts +17 -2
  30. package/dist/components/Chip.js +35 -11
  31. package/dist/components/ChipRemove.d.ts +17 -0
  32. package/dist/components/ChipRemove.js +13 -0
  33. package/dist/components/CollapsibleParts.d.ts +29 -0
  34. package/dist/components/CollapsibleParts.js +20 -0
  35. package/dist/components/CollapsibleSection.d.ts +30 -3
  36. package/dist/components/CollapsibleSection.js +7 -11
  37. package/dist/components/CollapsibleSectionState.d.ts +1 -1
  38. package/dist/components/CollapsibleSectionState.js +54 -27
  39. package/dist/components/DashboardGrid.d.ts +60 -0
  40. package/dist/components/DashboardGrid.js +40 -0
  41. package/dist/components/DateRangePicker.d.ts +7 -1
  42. package/dist/components/DateRangePicker.js +5 -1
  43. package/dist/components/DescriptionList.d.ts +12 -1
  44. package/dist/components/DescriptionList.js +5 -5
  45. package/dist/components/Dialogs.d.ts +24 -3
  46. package/dist/components/Dialogs.js +74 -16
  47. package/dist/components/FileUpload.d.ts +10 -2
  48. package/dist/components/FileUpload.js +10 -4
  49. package/dist/components/FilterEditor.d.ts +4 -0
  50. package/dist/components/FilterEditor.js +13 -2
  51. package/dist/components/Heartbeat.d.ts +31 -0
  52. package/dist/components/Heartbeat.js +28 -0
  53. package/dist/components/Menu.d.ts +53 -5
  54. package/dist/components/Menu.js +104 -4
  55. package/dist/components/Meter.d.ts +7 -3
  56. package/dist/components/Meter.js +2 -1
  57. package/dist/components/Popover.d.ts +21 -2
  58. package/dist/components/Popover.js +94 -17
  59. package/dist/components/Prose.d.ts +6 -2
  60. package/dist/components/Prose.js +1 -1
  61. package/dist/components/RuleBuilder.d.ts +52 -4
  62. package/dist/components/RuleBuilder.js +109 -24
  63. package/dist/components/SchedulingCalendar.d.ts +142 -11
  64. package/dist/components/SchedulingCalendar.js +234 -57
  65. package/dist/components/SchedulingTimeline.d.ts +1 -0
  66. package/dist/components/Segmented.d.ts +17 -3
  67. package/dist/components/Segmented.js +59 -5
  68. package/dist/components/Select.d.ts +66 -2
  69. package/dist/components/Select.js +800 -10
  70. package/dist/components/Spinner.d.ts +2 -1
  71. package/dist/components/Spinner.js +1 -1
  72. package/dist/components/StatusPill.d.ts +8 -2
  73. package/dist/components/StatusPill.js +2 -2
  74. package/dist/components/Steps.d.ts +28 -4
  75. package/dist/components/Steps.js +46 -16
  76. package/dist/components/TableControls.js +4 -3
  77. package/dist/components/Tabs.d.ts +7 -2
  78. package/dist/components/Tabs.js +14 -1
  79. package/dist/components/TextDiff.d.ts +49 -0
  80. package/dist/components/TextDiff.js +124 -0
  81. package/dist/{emoji-image-6mPZ15Zy.d.cts → emoji-image-DjCKpdwE.d.cts} +1 -1
  82. package/dist/emoji.cjs +169 -27
  83. package/dist/emoji.d.cts +3 -3
  84. package/dist/file-model.d.ts +22 -3
  85. package/dist/file-model.js +6 -4
  86. package/dist/filter-model.d.ts +26 -2
  87. package/dist/filter-model.js +13 -1
  88. package/dist/hooks/useAnchoredPosition.d.ts +24 -1
  89. package/dist/hooks/useAnchoredPosition.js +58 -8
  90. package/dist/hooks/useVirtualRows.d.ts +17 -2
  91. package/dist/hooks/useVirtualRows.js +6 -5
  92. package/dist/icons/Icon.d.ts +14 -14
  93. package/dist/index.cjs +4179 -1678
  94. package/dist/index.d.cts +1706 -660
  95. package/dist/index.d.ts +16 -9
  96. package/dist/index.js +53 -4
  97. package/dist/labels-context.d.ts +28 -0
  98. package/dist/{overlay-position-Cjv5E7mn.d.cts → overlay-position-2Afke51l.d.cts} +20 -0
  99. package/dist/overlay-position.d.ts +20 -0
  100. package/dist/overlay-position.js +17 -2
  101. package/dist/rich-text.cjs +169 -27
  102. package/dist/rich-text.d.cts +3 -3
  103. package/dist/rule-builder-model.d.ts +34 -2
  104. package/dist/rule-builder-model.js +48 -12
  105. package/dist/scheduling-calendar-model.d.ts +66 -0
  106. package/dist/scheduling-calendar-model.js +82 -0
  107. package/dist/select-model.d.ts +80 -0
  108. package/dist/select-model.js +109 -0
  109. package/dist/table-engine.d.ts +7 -0
  110. package/dist/table-engine.js +2 -1
  111. package/dist/text-diff-model.d.ts +71 -0
  112. package/dist/text-diff-model.js +261 -0
  113. package/package.json +1 -1
  114. package/dist/ChartCore-DzWe10bN.d.cts +0 -59
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useId, useRef } from "react";
4
4
  import { cx } from "../cx.js";
5
+ import { mergeChartTheme, uixChartTheme } from "../chart-theme.js";
5
6
  function readToken(name) {
6
7
  if (typeof getComputedStyle === "undefined") return "";
7
8
  return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
@@ -9,6 +10,9 @@ function readToken(name) {
9
10
  function uixChartPalette() {
10
11
  return [1, 2, 3, 4, 5, 6, 7, 8].map((i) => readToken(`--uix-chart-${i}`));
11
12
  }
13
+ function themed(option, theme, element) {
14
+ return theme === "none" ? option : mergeChartTheme(uixChartTheme(element), option);
15
+ }
12
16
  function motionSafe(option) {
13
17
  if (typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches) {
14
18
  return { ...option, animation: false };
@@ -37,7 +41,8 @@ function ChartCore({
37
41
  footer,
38
42
  loading = false,
39
43
  empty,
40
- labels: labelOverrides
44
+ labels: labelOverrides,
45
+ theme = "uix"
41
46
  }) {
42
47
  const labels = { ...DEFAULT_CHART_LABELS, ...labelOverrides };
43
48
  const containerRef = useRef(null);
@@ -49,7 +54,9 @@ function ChartCore({
49
54
  const hasEmpty = empty != null;
50
55
  const hasTextAlternative = Boolean(ariaLabel || title || tableData?.length);
51
56
  const hasTextAlternativeRef = useRef(hasTextAlternative);
57
+ const themeRef = useRef(theme);
52
58
  latestOptionRef.current = option;
59
+ themeRef.current = theme;
53
60
  onReadyRef.current = onReady;
54
61
  hasTextAlternativeRef.current = hasTextAlternative;
55
62
  useEffect(() => {
@@ -57,9 +64,17 @@ function ChartCore({
57
64
  if (!element) return;
58
65
  const chart = engine.init(element, null, { renderer: "svg" });
59
66
  chartRef.current = chart;
60
- chart.setOption(motionSafe(latestOptionRef.current));
67
+ chart.setOption(motionSafe(themed(latestOptionRef.current, themeRef.current, element)));
61
68
  appliedOptionRef.current = latestOptionRef.current;
62
69
  onReadyRef.current?.(chart);
70
+ const retheme = () => {
71
+ if (themeRef.current === "none") return;
72
+ chart.setOption(motionSafe(themed(latestOptionRef.current, themeRef.current, element)));
73
+ };
74
+ const themeObserver = typeof MutationObserver === "undefined" ? null : new MutationObserver(retheme);
75
+ themeObserver?.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "data-theme"] });
76
+ const scheme = typeof matchMedia === "undefined" ? null : matchMedia("(prefers-color-scheme: dark)");
77
+ scheme?.addEventListener?.("change", retheme);
63
78
  if (!hasTextAlternativeRef.current) {
64
79
  console.warn("[uix] Chart has no text alternative \u2014 provide ariaLabel, title, or tableData.");
65
80
  }
@@ -81,6 +96,8 @@ function ChartCore({
81
96
  observer.observe(element);
82
97
  return () => {
83
98
  observer.disconnect();
99
+ themeObserver?.disconnect();
100
+ scheme?.removeEventListener?.("change", retheme);
84
101
  if (frame) cancelAnimationFrame(frame);
85
102
  chart.dispose();
86
103
  chartRef.current = null;
@@ -89,10 +106,11 @@ function ChartCore({
89
106
  }, [engine, hasEmpty]);
90
107
  useEffect(() => {
91
108
  const chart = chartRef.current;
92
- if (!chart || appliedOptionRef.current === option) return;
93
- chart.setOption(motionSafe(option), { notMerge: false });
109
+ const element = containerRef.current;
110
+ if (!chart || !element || appliedOptionRef.current === option) return;
111
+ chart.setOption(motionSafe(themed(option, theme, element)), { notMerge: false });
94
112
  appliedOptionRef.current = option;
95
- }, [option]);
113
+ }, [option, theme]);
96
114
  const heightValue = typeof height === "number" ? `${height}px` : height;
97
115
  const effectiveAriaLabel = ariaLabel ?? title ?? labels.chart;
98
116
  const hasTable = Boolean(tableData?.length && tableHeaders?.length);
@@ -16,13 +16,26 @@ interface ChipProps extends Omit<HTMLAttributes<HTMLElement>, 'onClick'> {
16
16
  /** A link chip. */
17
17
  href?: string;
18
18
  renderLink?: UixRenderLink;
19
+ /**
20
+ * A link chip for the page that is open: `aria-current="page"` and the filled look
21
+ * (HAR-1632). `pressed` gives the same look but says "toggle button", which a link is not.
22
+ */
23
+ current?: boolean;
19
24
  /** Adds a × button that removes the chip. */
20
25
  onRemove?: () => void;
21
26
  /**
22
27
  * Accessible name of the × button. `{label}` is replaced by the chip text when it is a
23
- * string. Default "Remove {label}"; pass a full name when the text is not a string.
28
+ * string. Default: `UixLabelsProvider` `chip.remove`, then "Remove {label}"; pass a full name
29
+ * when the text is not a string.
24
30
  */
25
31
  removeLabel?: string;
32
+ /**
33
+ * Attributes for the body: the element that carries the chip's own action. On a plain chip
34
+ * that is the chip itself, where the other props already go; on a removable chip the other
35
+ * props go to the wrapper, so this is how `aria-haspopup`, `aria-expanded`, `aria-controls`
36
+ * or an `id` reach the button that opens the filter editor (HAR-1632).
37
+ */
38
+ bodyProps?: HTMLAttributes<HTMLElement>;
26
39
  /** `add` is the dashed "+ Add filter" chip. */
27
40
  variant?: 'default' | 'add';
28
41
  disabled?: boolean;
@@ -30,8 +43,10 @@ interface ChipProps extends Omit<HTMLAttributes<HTMLElement>, 'onClick'> {
30
43
  /**
31
44
  * Filter, tag and token chip over `.uix-chip` (HAR-1360; TENSOR C7/B34, MOTUS C-6/B-A18).
32
45
  * Plain text, a toggle, a link or a button, optionally with a × that removes it.
46
+ * The ref goes to the body (the button, link or span that carries the chip's action), also on
47
+ * a removable chip: anchor a popover to it and return focus to it without a DOM lookup.
33
48
  */
34
- declare function Chip({ children, icon, count, pressed, onPressedChange, onClick, href, renderLink, onRemove, removeLabel, variant, disabled, className, ...props }: ChipProps): react.JSX.Element;
49
+ declare const Chip: react.ForwardRefExoticComponent<ChipProps & react.RefAttributes<HTMLElement>>;
35
50
  interface ChipGroupProps extends HTMLAttributes<HTMLDivElement> {
36
51
  /** Names the group, e.g. "Active filters". */
37
52
  label?: string;
@@ -1,8 +1,9 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
2
3
  import { cx } from "../cx.js";
3
- import { fillLabel } from "../fill-label.js";
4
4
  import { renderUixLink } from "../link.js";
5
- function Chip({
5
+ import { ChipRemoveButton } from "./ChipRemove.js";
6
+ const Chip = forwardRef(function Chip2({
6
7
  children,
7
8
  icon,
8
9
  count,
@@ -11,15 +12,19 @@ function Chip({
11
12
  onClick,
12
13
  href,
13
14
  renderLink,
15
+ current,
14
16
  onRemove,
15
- removeLabel = "Remove {label}",
17
+ removeLabel,
18
+ bodyProps,
16
19
  variant = "default",
17
20
  disabled,
18
21
  className,
19
22
  ...props
20
- }) {
23
+ }, ref) {
21
24
  const toggle = pressed !== void 0;
22
25
  const interactive = toggle || onClick != null || href != null;
26
+ const link = href != null && !disabled;
27
+ const on = pressed || link && current || void 0;
23
28
  const body = /* @__PURE__ */ jsxs(Fragment, { children: [
24
29
  icon != null && /* @__PURE__ */ jsx("span", { className: "uix-chip__icon", "aria-hidden": "true", children: icon }),
25
30
  /* @__PURE__ */ jsx("span", { className: "uix-chip__label", children }),
@@ -38,7 +43,17 @@ function Chip({
38
43
  className
39
44
  );
40
45
  const main = (classes, extra) => {
41
- if (href != null && !disabled) return renderUixLink(renderLink, { href, className: classes, children: body, ...extra });
46
+ if (link) {
47
+ return renderUixLink(renderLink, {
48
+ href,
49
+ className: classes,
50
+ children: body,
51
+ "aria-current": current ? "page" : void 0,
52
+ ...extra,
53
+ ...bodyProps,
54
+ ref
55
+ });
56
+ }
42
57
  if (interactive) {
43
58
  return /* @__PURE__ */ jsx(
44
59
  "button",
@@ -49,21 +64,30 @@ function Chip({
49
64
  disabled,
50
65
  onClick: handleClick,
51
66
  ...extra,
67
+ ...bodyProps,
68
+ ref,
52
69
  children: body
53
70
  }
54
71
  );
55
72
  }
56
- return /* @__PURE__ */ jsx("span", { className: classes, ...extra, children: body });
73
+ return /* @__PURE__ */ jsx("span", { className: classes, ...extra, ...bodyProps, ref, children: body });
57
74
  };
58
75
  if (!onRemove) {
59
- return /* @__PURE__ */ jsx(Fragment, { children: main(outer, { ...props, "data-on": pressed || void 0 }) });
76
+ return /* @__PURE__ */ jsx(Fragment, { children: main(outer, { ...props, "data-on": on }) });
60
77
  }
61
- const removeName = fillLabel(removeLabel, { label: typeof children === "string" || typeof children === "number" ? String(children) : "" }).trim();
62
- return /* @__PURE__ */ jsxs("span", { className: outer, "data-on": pressed || void 0, ...props, children: [
78
+ return /* @__PURE__ */ jsxs("span", { className: outer, "data-on": on, ...props, children: [
63
79
  main("uix-chip__main"),
64
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-chip__remove", "aria-label": removeName, disabled, onClick: onRemove, 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.5 4.5l7 7M11.5 4.5l-7 7" }) }) })
80
+ /* @__PURE__ */ jsx(
81
+ ChipRemoveButton,
82
+ {
83
+ label: removeLabel,
84
+ text: typeof children === "string" || typeof children === "number" ? String(children) : "",
85
+ disabled,
86
+ onRemove
87
+ }
88
+ )
65
89
  ] });
66
- }
90
+ });
67
91
  function ChipGroup({ label, children, className, ...props }) {
68
92
  return /* @__PURE__ */ jsx("div", { role: "group", "aria-label": label, className: cx("uix-chip-group", className), ...props, children });
69
93
  }
@@ -0,0 +1,17 @@
1
+ import * as react from 'react';
2
+
3
+ interface ChipRemoveButtonProps {
4
+ /** The chip's `removeLabel`, when one was passed. */
5
+ label?: string;
6
+ /** The chip text for `{label}`; empty when the chip's children are not text. */
7
+ text: string;
8
+ disabled?: boolean;
9
+ onRemove: () => void;
10
+ }
11
+ /**
12
+ * The × of a removable `Chip`. Internal. Its own client component so its name can come from
13
+ * `UixLabelsProvider` (`chip.remove`) while `Chip` itself stays server-renderable (HAR-1632).
14
+ */
15
+ declare function ChipRemoveButton({ label, text, disabled, onRemove }: ChipRemoveButtonProps): react.JSX.Element;
16
+
17
+ export { ChipRemoveButton };
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { XIcon } from "../icons/components.js";
4
+ import { fillLabel } from "../fill-label.js";
5
+ import { useUixLabels } from "../labels-context.js";
6
+ function ChipRemoveButton({ label, text, disabled, onRemove }) {
7
+ const provided = useUixLabels().chip?.remove;
8
+ const template = label ?? provided ?? "Remove {label}";
9
+ return /* @__PURE__ */ jsx("button", { type: "button", className: "uix-chip__remove", "aria-label": fillLabel(template, { label: text }).trim(), disabled, onClick: onRemove, children: /* @__PURE__ */ jsx(XIcon, {}) });
10
+ }
11
+ export {
12
+ ChipRemoveButton
13
+ };
@@ -0,0 +1,29 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, Ref } from 'react';
3
+
4
+ type CollapsibleHeadingLevel = 2 | 3 | 4 | 5 | 6;
5
+ /** Root classes shared by the plain and the stateful `CollapsibleSection`. */
6
+ declare const collapsibleClass: (compact: boolean | undefined, className: string | undefined) => string;
7
+ /** With a heading level the body is a region named by the title. */
8
+ declare const collapsibleBodyProps: (headingLevel: CollapsibleHeadingLevel | undefined, titleId: string) => {
9
+ role: string;
10
+ 'aria-labelledby': string;
11
+ } | {
12
+ role?: undefined;
13
+ 'aria-labelledby'?: undefined;
14
+ };
15
+ interface CollapsibleSummaryProps {
16
+ title: ReactNode;
17
+ summary?: ReactNode;
18
+ headingLevel?: CollapsibleHeadingLevel;
19
+ titleId: string;
20
+ summaryRef?: Ref<HTMLElement>;
21
+ }
22
+ /**
23
+ * The `<summary>` row of a `CollapsibleSection`. Internal. With `headingLevel` the title sits
24
+ * in a real heading (a heading is the one block element a `<summary>` may hold), so the section
25
+ * shows up in the page outline; without it the markup is what 2.31.0 rendered.
26
+ */
27
+ declare function CollapsibleSummary({ title, summary, headingLevel, titleId, summaryRef }: CollapsibleSummaryProps): react.JSX.Element;
28
+
29
+ export { type CollapsibleHeadingLevel, CollapsibleSummary, collapsibleBodyProps, collapsibleClass };
@@ -0,0 +1,20 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { cx } from "../cx.js";
3
+ import { ChevronDownIcon } from "../icons/components.js";
4
+ const collapsibleClass = (compact, className) => cx("uix-collapsible", compact && "uix-collapsible--compact", className);
5
+ const collapsibleBodyProps = (headingLevel, titleId) => headingLevel ? { role: "region", "aria-labelledby": titleId } : {};
6
+ function CollapsibleSummary({ title, summary, headingLevel, titleId, summaryRef }) {
7
+ const Heading = headingLevel ? `h${headingLevel}` : "span";
8
+ return /* @__PURE__ */ jsxs("summary", { ref: summaryRef, className: "uix-collapsible__summary", children: [
9
+ /* @__PURE__ */ jsxs(Heading, { className: headingLevel ? "uix-collapsible__heading" : void 0, children: [
10
+ /* @__PURE__ */ jsx("span", { id: headingLevel ? titleId : void 0, className: "uix-collapsible__title", children: title }),
11
+ summary != null && /* @__PURE__ */ jsx("span", { className: "uix-collapsible__meta", children: summary })
12
+ ] }),
13
+ /* @__PURE__ */ jsx("span", { className: "uix-collapsible__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: "sm" }) })
14
+ ] });
15
+ }
16
+ export {
17
+ CollapsibleSummary,
18
+ collapsibleBodyProps,
19
+ collapsibleClass
20
+ };
@@ -5,6 +5,12 @@ interface CollapsibleSectionProps extends Omit<DetailsHTMLAttributes<HTMLDetails
5
5
  title: ReactNode;
6
6
  summary?: ReactNode;
7
7
  children?: ReactNode;
8
+ /**
9
+ * Open when first rendered. With `persistKey`, what the person last chose wins over it:
10
+ * "open by default, but remember" (HAR-1628). `open` is the same starting state and also
11
+ * re-applies whenever the parent changes it.
12
+ */
13
+ defaultOpen?: boolean;
8
14
  /**
9
15
  * Mount the body only while it is needed (HAR-1352; TENSOR B32): `true` mounts it while
10
16
  * open and unmounts it on close; `'keep'` mounts it on first open and keeps it. For
@@ -12,12 +18,33 @@ interface CollapsibleSectionProps extends Omit<DetailsHTMLAttributes<HTMLDetails
12
18
  */
13
19
  lazy?: boolean | 'keep';
14
20
  /**
15
- * Remember the open state for the session under this key (`sessionStorage`,
16
- * `uix:collapsible:<key>`). Applied after mount, so the server HTML uses `open`.
21
+ * Remember the open state under this key (`uix:collapsible:<key>`), in `persistStorage`.
22
+ * Applied after mount, so the server HTML uses `open` / `defaultOpen`; a remembered state
23
+ * then replaces either.
17
24
  */
18
25
  persistKey?: string;
19
- /** Each new value opens the section and scrolls it into view ("jump to SLA"). */
26
+ /**
27
+ * Where `persistKey` keeps the state: `'session'` (`sessionStorage`, this tab until it
28
+ * closes) or `'local'` (`localStorage`, across sessions). Default `'session'`.
29
+ */
30
+ persistStorage?: 'session' | 'local';
31
+ /**
32
+ * Each new value opens the section, scrolls it into view and moves focus to its summary
33
+ * ("jump to SLA"). A value already set when the section mounts is honoured too (a deep link
34
+ * that lands on a closed section); `undefined` and `0` mean "no request yet", so a counter
35
+ * that starts at 0 does not open anything.
36
+ */
20
37
  openRequest?: number;
38
+ /**
39
+ * A quiet row for a section inside a card: no border or background of its own, tighter
40
+ * padding, a rule between neighbouring compact sections.
41
+ */
42
+ compact?: boolean;
43
+ /**
44
+ * Render the title as a heading of this level, and make the body a `role="region"` named by
45
+ * it. Leave unset for a section that should stay out of the page outline.
46
+ */
47
+ headingLevel?: 2 | 3 | 4 | 5 | 6;
21
48
  }
22
49
  /**
23
50
  * A `<details>` disclosure. Without `lazy`, `persistKey` or `openRequest` it is a plain,
@@ -1,18 +1,14 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { cx } from "../cx.js";
2
+ import { useId } from "react";
3
+ import { CollapsibleSummary, collapsibleBodyProps, collapsibleClass } from "./CollapsibleParts.js";
3
4
  import { CollapsibleSectionState } from "./CollapsibleSectionState.js";
4
5
  function CollapsibleSection(props) {
5
- const { title, summary, children, className, lazy, persistKey, openRequest, ...rest } = props;
6
+ const { title, summary, children, className, lazy, persistKey, persistStorage, openRequest, open, defaultOpen, compact, headingLevel, ...rest } = props;
7
+ const titleId = useId();
6
8
  if (lazy || persistKey !== void 0 || openRequest !== void 0) return /* @__PURE__ */ jsx(CollapsibleSectionState, { ...props });
7
- return /* @__PURE__ */ jsxs("details", { className: cx("uix-collapsible", className), ...rest, children: [
8
- /* @__PURE__ */ jsxs("summary", { className: "uix-collapsible__summary", children: [
9
- /* @__PURE__ */ jsxs("span", { children: [
10
- /* @__PURE__ */ jsx("span", { className: "uix-collapsible__title", children: title }),
11
- summary != null && /* @__PURE__ */ jsx("span", { className: "uix-collapsible__meta", children: summary })
12
- ] }),
13
- /* @__PURE__ */ jsx("span", { className: "uix-collapsible__chevron", "aria-hidden": "true", children: "\u2304" })
14
- ] }),
15
- /* @__PURE__ */ jsx("div", { className: "uix-collapsible__body", children })
9
+ return /* @__PURE__ */ jsxs("details", { className: collapsibleClass(compact, className), ...rest, open: open ?? defaultOpen, children: [
10
+ /* @__PURE__ */ jsx(CollapsibleSummary, { title, summary, headingLevel, titleId }),
11
+ /* @__PURE__ */ jsx("div", { className: "uix-collapsible__body", ...collapsibleBodyProps(headingLevel, titleId), children })
16
12
  ] });
17
13
  }
18
14
  export {
@@ -6,6 +6,6 @@ import { CollapsibleSectionProps } from './CollapsibleSection.js';
6
6
  * state and `openRequest`. Internal — `CollapsibleSection` delegates here only when one
7
7
  * of those props is set, so the plain section stays server-renderable and JS-free.
8
8
  */
9
- declare function CollapsibleSectionState({ title, summary, children, className, lazy, persistKey, openRequest, open, onToggle, ...props }: CollapsibleSectionProps): react.JSX.Element;
9
+ declare function CollapsibleSectionState({ title, summary, children, className, lazy, persistKey, persistStorage, openRequest, open, defaultOpen, compact, headingLevel, onToggle, ...props }: CollapsibleSectionProps): react.JSX.Element;
10
10
 
11
11
  export { CollapsibleSectionState };
@@ -1,36 +1,59 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useEffect, useRef, useState } from "react";
4
- import { cx } from "../cx.js";
3
+ import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
4
+ import { CollapsibleSummary, collapsibleBodyProps, collapsibleClass } from "./CollapsibleParts.js";
5
+ const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
5
6
  const STORAGE_PREFIX = "uix:collapsible:";
6
- const readStored = (key) => {
7
+ const storageOf = (kind) => kind === "local" ? window.localStorage : window.sessionStorage;
8
+ const readStored = (kind, key) => {
7
9
  try {
8
- const value = window.sessionStorage.getItem(STORAGE_PREFIX + key);
10
+ const value = storageOf(kind).getItem(STORAGE_PREFIX + key);
9
11
  return value === "1" ? true : value === "0" ? false : void 0;
10
12
  } catch {
11
13
  return void 0;
12
14
  }
13
15
  };
14
- const writeStored = (key, open) => {
16
+ const writeStored = (kind, key, open) => {
15
17
  try {
16
- window.sessionStorage.setItem(STORAGE_PREFIX + key, open ? "1" : "0");
18
+ storageOf(kind).setItem(STORAGE_PREFIX + key, open ? "1" : "0");
17
19
  } catch {
18
20
  }
19
21
  };
20
- function CollapsibleSectionState({ title, summary, children, className, lazy, persistKey, openRequest, open, onToggle, ...props }) {
22
+ function CollapsibleSectionState({
23
+ title,
24
+ summary,
25
+ children,
26
+ className,
27
+ lazy,
28
+ persistKey,
29
+ persistStorage = "session",
30
+ openRequest,
31
+ open,
32
+ defaultOpen,
33
+ compact,
34
+ headingLevel,
35
+ onToggle,
36
+ ...props
37
+ }) {
21
38
  const ref = useRef(null);
22
- const [isOpen, setIsOpen] = useState(open ?? false);
23
- const [hasOpened, setHasOpened] = useState(open ?? false);
24
- const lastRequest = useRef(openRequest);
39
+ const summaryRef = useRef(null);
40
+ const titleId = useId();
41
+ const initial = open ?? defaultOpen ?? false;
42
+ const [isOpen, setIsOpen] = useState(initial);
43
+ const [hasOpened, setHasOpened] = useState(initial);
44
+ const lastOpen = useRef(open);
45
+ const lastRequest = useRef(openRequest ? void 0 : openRequest);
25
46
  useEffect(() => {
26
47
  if (!persistKey) return;
27
- const stored = readStored(persistKey);
48
+ const stored = readStored(persistStorage, persistKey);
28
49
  if (stored !== void 0) {
29
50
  setIsOpen(stored);
30
51
  if (stored) setHasOpened(true);
31
52
  }
32
- }, [persistKey]);
53
+ }, [persistKey, persistStorage]);
33
54
  useEffect(() => {
55
+ if (open === lastOpen.current) return;
56
+ lastOpen.current = open;
34
57
  if (open === void 0) return;
35
58
  setIsOpen(open);
36
59
  if (open) setHasOpened(true);
@@ -40,26 +63,30 @@ function CollapsibleSectionState({ title, summary, children, className, lazy, pe
40
63
  lastRequest.current = openRequest;
41
64
  setIsOpen(true);
42
65
  setHasOpened(true);
43
- if (persistKey) writeStored(persistKey, true);
44
- requestAnimationFrame(() => ref.current?.scrollIntoView?.({ block: "nearest" }));
45
- }, [openRequest, persistKey]);
66
+ if (persistKey) writeStored(persistStorage, persistKey, true);
67
+ requestAnimationFrame(() => {
68
+ ref.current?.scrollIntoView?.({ block: "nearest" });
69
+ summaryRef.current?.focus({ preventScroll: true });
70
+ });
71
+ }, [openRequest, persistKey, persistStorage]);
72
+ const renderedOpen = useRef(isOpen);
73
+ useIsomorphicLayoutEffect(() => {
74
+ renderedOpen.current = isOpen;
75
+ }, [isOpen]);
46
76
  const handleToggle = (event) => {
47
77
  const next = event.currentTarget.open;
48
- setIsOpen(next);
49
- if (next) setHasOpened(true);
50
- if (persistKey) writeStored(persistKey, next);
78
+ if (next !== renderedOpen.current) {
79
+ renderedOpen.current = next;
80
+ setIsOpen(next);
81
+ if (next) setHasOpened(true);
82
+ if (persistKey) writeStored(persistStorage, persistKey, next);
83
+ }
51
84
  onToggle?.(event);
52
85
  };
53
86
  const mountBody = !lazy || (lazy === "keep" ? hasOpened : isOpen);
54
- return /* @__PURE__ */ jsxs("details", { ref, className: cx("uix-collapsible", className), ...props, open: isOpen, onToggle: handleToggle, children: [
55
- /* @__PURE__ */ jsxs("summary", { className: "uix-collapsible__summary", children: [
56
- /* @__PURE__ */ jsxs("span", { children: [
57
- /* @__PURE__ */ jsx("span", { className: "uix-collapsible__title", children: title }),
58
- summary != null && /* @__PURE__ */ jsx("span", { className: "uix-collapsible__meta", children: summary })
59
- ] }),
60
- /* @__PURE__ */ jsx("span", { className: "uix-collapsible__chevron", "aria-hidden": "true", children: "\u2304" })
61
- ] }),
62
- /* @__PURE__ */ jsx("div", { className: "uix-collapsible__body", children: mountBody ? children : null })
87
+ return /* @__PURE__ */ jsxs("details", { ref, className: collapsibleClass(compact, className), ...props, open: isOpen, onToggle: handleToggle, children: [
88
+ /* @__PURE__ */ jsx(CollapsibleSummary, { title, summary, headingLevel, titleId, summaryRef }),
89
+ /* @__PURE__ */ jsx("div", { className: "uix-collapsible__body", ...collapsibleBodyProps(headingLevel, titleId), children: mountBody ? children : null })
63
90
  ] });
64
91
  }
65
92
  export {
@@ -0,0 +1,60 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes } from 'react';
3
+
4
+ /** Columns at one width. */
5
+ type DashboardGridColumnCount = 1 | 2 | 3 | 4;
6
+ /**
7
+ * A width of the GRID itself (a container query), not of the viewport:
8
+ * `base` any width, `sm` ≥ 36rem, `md` ≥ 48rem, `lg` ≥ 60rem, `xl` ≥ 80rem.
9
+ */
10
+ type DashboardGridBreakpoint = 'base' | 'sm' | 'md' | 'lg' | 'xl';
11
+ /**
12
+ * One column count for every width, or a count per breakpoint. A breakpoint you leave out keeps
13
+ * the count of the next smaller one, and a missing `base` is 1.
14
+ */
15
+ type DashboardGridColumns = DashboardGridColumnCount | Partial<Record<DashboardGridBreakpoint, DashboardGridColumnCount>>;
16
+ /** How much of the row an item takes. `2` clamps to the columns there are; `full` is always the whole row. */
17
+ type DashboardGridSpan = 1 | 2 | 'full';
18
+ /** Space between items: `sm` --uix-space-3, `md` --uix-space-4, `lg` --uix-space-6. */
19
+ type DashboardGridGap = 'sm' | 'md' | 'lg';
20
+ interface DashboardGridProps extends HTMLAttributes<HTMLDivElement> {
21
+ /** Default: 1 column, 2 from `sm`, 3 from `lg` (of the grid's own width). */
22
+ columns?: DashboardGridColumns;
23
+ /** Default `md`. */
24
+ gap?: DashboardGridGap;
25
+ }
26
+ interface DashboardGridItemProps extends HTMLAttributes<HTMLDivElement> {
27
+ /** Default `1`. */
28
+ span?: DashboardGridSpan;
29
+ }
30
+ /**
31
+ * The class list of a dashboard grid, for markup you render yourself (a server template, or a
32
+ * drag-and-drop library's own element). Same output as `<DashboardGrid>`.
33
+ */
34
+ declare function dashboardGridClassName(columns?: DashboardGridColumns, gap?: DashboardGridGap): string;
35
+ /** The class list of a dashboard grid item, for markup you render yourself. */
36
+ declare function dashboardGridItemClassName(span?: DashboardGridSpan): string;
37
+ /**
38
+ * A cell of a {@link DashboardGrid}: one column, two, or the full row. It is an inline-size
39
+ * container (container name: uix-dashboard-grid-item), so its content can size from it, e.g. a Chart with
40
+ * `height="clamp(160px, 40cqi, 360px)"`. Widgets in one row share a height.
41
+ */
42
+ declare const DashboardGridItem: react.ForwardRefExoticComponent<DashboardGridItemProps & react.RefAttributes<HTMLDivElement>>;
43
+ /**
44
+ * The layout grid for dashboard widgets (HAR-1555). The column count follows the grid's own
45
+ * width, so it adapts to a sidebar as well as to the screen. Pure layout: drag-and-drop,
46
+ * persistence and widget chrome stay in the product. Server-renderable.
47
+ *
48
+ * ```tsx
49
+ * <DashboardGrid columns={{ base: 1, sm: 2, lg: 3 }}>
50
+ * <DashboardGrid.Item><Stat … /></DashboardGrid.Item>
51
+ * <DashboardGrid.Item span={2}>…</DashboardGrid.Item>
52
+ * <DashboardGrid.Item span="full"><Chart … /></DashboardGrid.Item>
53
+ * </DashboardGrid>
54
+ * ```
55
+ */
56
+ declare const DashboardGrid: react.ForwardRefExoticComponent<DashboardGridProps & react.RefAttributes<HTMLDivElement>> & {
57
+ Item: react.ForwardRefExoticComponent<DashboardGridItemProps & react.RefAttributes<HTMLDivElement>>;
58
+ };
59
+
60
+ export { DashboardGrid, type DashboardGridBreakpoint, type DashboardGridColumnCount, type DashboardGridColumns, type DashboardGridGap, DashboardGridItem, type DashboardGridItemProps, type DashboardGridProps, type DashboardGridSpan, dashboardGridClassName, dashboardGridItemClassName };
@@ -0,0 +1,40 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cx } from "../cx.js";
4
+ const BREAKPOINTS = ["sm", "md", "lg", "xl"];
5
+ const count = (value, fallback) => {
6
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
7
+ return Math.min(4, Math.max(1, Math.round(value)));
8
+ };
9
+ function dashboardGridClassName(columns, gap = "md") {
10
+ const classes = ["uix-dashboard-grid"];
11
+ if (gap === "sm" || gap === "lg") classes.push(`uix-dashboard-grid--gap-${gap}`);
12
+ if (columns == null) return classes.join(" ");
13
+ if (typeof columns === "number") {
14
+ classes.push(`uix-dashboard-grid--cols-${count(columns, 1)}`);
15
+ return classes.join(" ");
16
+ }
17
+ classes.push(`uix-dashboard-grid--cols-${count(columns.base, 1)}`);
18
+ for (const breakpoint of BREAKPOINTS) {
19
+ if (columns[breakpoint] != null) classes.push(`uix-dashboard-grid--${breakpoint}-cols-${count(columns[breakpoint], 1)}`);
20
+ }
21
+ return classes.join(" ");
22
+ }
23
+ function dashboardGridItemClassName(span = 1) {
24
+ if (span === "full") return "uix-dashboard-grid__item uix-dashboard-grid__item--full";
25
+ if (span === 2) return "uix-dashboard-grid__item uix-dashboard-grid__item--span-2";
26
+ return "uix-dashboard-grid__item";
27
+ }
28
+ const DashboardGridItem = forwardRef(function DashboardGridItem2({ span = 1, className, ...props }, ref) {
29
+ return /* @__PURE__ */ jsx("div", { ref, className: cx(dashboardGridItemClassName(span), className), ...props });
30
+ });
31
+ const DashboardGridRoot = forwardRef(function DashboardGrid({ columns, gap = "md", className, ...props }, ref) {
32
+ return /* @__PURE__ */ jsx("div", { ref, className: cx(dashboardGridClassName(columns, gap), className), ...props });
33
+ });
34
+ const DashboardGrid2 = Object.assign(DashboardGridRoot, { Item: DashboardGridItem });
35
+ export {
36
+ DashboardGrid2 as DashboardGrid,
37
+ DashboardGridItem,
38
+ dashboardGridClassName,
39
+ dashboardGridItemClassName
40
+ };
@@ -35,8 +35,14 @@ interface DateRangePickerProps {
35
35
  className?: string;
36
36
  /** Translatable words; English defaults in {@link DEFAULT_DATE_RANGE_PICKER_LABELS}. */
37
37
  labels?: Partial<DateRangePickerLabels>;
38
+ /**
39
+ * The ISO date (`YYYY-MM-DD`) that gets the today ring and `aria-current="date"`.
40
+ * Defaults to the viewer's local date; pass it when rendering on a server (whose
41
+ * clock and zone may differ from the viewer's), or `null` for no today marker.
42
+ */
43
+ today?: string | null;
38
44
  }
39
45
  /** Controlled, two-month range picker using the UIx calendar language. */
40
- declare function DateRangePicker({ value, onChange, visibleMonth: controlledMonth, onVisibleMonthChange, months, min, max, isDateDisabled, locale, label, invalid, invalidMessage, disabled, className, labels: labelOverrides, }: DateRangePickerProps): react.JSX.Element;
46
+ declare function DateRangePicker({ value, onChange, visibleMonth: controlledMonth, onVisibleMonthChange, months, min, max, isDateDisabled, locale, label, invalid, invalidMessage, disabled, className, labels: labelOverrides, today, }: DateRangePickerProps): react.JSX.Element;
41
47
 
42
48
  export { DEFAULT_DATE_RANGE_PICKER_LABELS, DateRangePicker, type DateRangePickerLabels, type DateRangePickerProps };