@tensor_1/react 2.30.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 (122) hide show
  1. package/README.md +430 -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 +58 -0
  22. package/dist/components/Attachment.js +69 -0
  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/EntityPicker.d.ts +56 -0
  48. package/dist/components/EntityPicker.js +168 -0
  49. package/dist/components/FileIcon.d.ts +9 -0
  50. package/dist/components/FileIcon.js +17 -0
  51. package/dist/components/FileUpload.d.ts +95 -0
  52. package/dist/components/FileUpload.js +145 -0
  53. package/dist/components/FilterEditor.d.ts +55 -0
  54. package/dist/components/FilterEditor.js +186 -0
  55. package/dist/components/Heartbeat.d.ts +31 -0
  56. package/dist/components/Heartbeat.js +28 -0
  57. package/dist/components/Lightbox.d.ts +45 -0
  58. package/dist/components/Lightbox.js +102 -0
  59. package/dist/components/Menu.d.ts +53 -5
  60. package/dist/components/Menu.js +104 -4
  61. package/dist/components/Meter.d.ts +7 -3
  62. package/dist/components/Meter.js +2 -1
  63. package/dist/components/Popover.d.ts +21 -2
  64. package/dist/components/Popover.js +94 -17
  65. package/dist/components/Prose.d.ts +6 -2
  66. package/dist/components/Prose.js +1 -1
  67. package/dist/components/RuleBuilder.d.ts +52 -4
  68. package/dist/components/RuleBuilder.js +109 -24
  69. package/dist/components/SchedulingCalendar.d.ts +142 -11
  70. package/dist/components/SchedulingCalendar.js +234 -57
  71. package/dist/components/SchedulingTimeline.d.ts +1 -0
  72. package/dist/components/SearchSuggest.d.ts +6 -2
  73. package/dist/components/SearchSuggest.js +4 -0
  74. package/dist/components/Segmented.d.ts +17 -3
  75. package/dist/components/Segmented.js +59 -5
  76. package/dist/components/Select.d.ts +66 -2
  77. package/dist/components/Select.js +800 -10
  78. package/dist/components/Spinner.d.ts +2 -1
  79. package/dist/components/Spinner.js +1 -1
  80. package/dist/components/StatusPill.d.ts +8 -2
  81. package/dist/components/StatusPill.js +2 -2
  82. package/dist/components/Steps.d.ts +28 -4
  83. package/dist/components/Steps.js +46 -16
  84. package/dist/components/TableControls.js +4 -3
  85. package/dist/components/Tabs.d.ts +7 -2
  86. package/dist/components/Tabs.js +14 -1
  87. package/dist/components/TextDiff.d.ts +49 -0
  88. package/dist/components/TextDiff.js +124 -0
  89. package/dist/{emoji-image-6mPZ15Zy.d.cts → emoji-image-DjCKpdwE.d.cts} +1 -1
  90. package/dist/emoji.cjs +169 -27
  91. package/dist/emoji.d.cts +3 -3
  92. package/dist/file-model.d.ts +56 -0
  93. package/dist/file-model.js +52 -0
  94. package/dist/filter-model.d.ts +112 -0
  95. package/dist/filter-model.js +103 -0
  96. package/dist/hooks/useAnchoredPosition.d.ts +24 -1
  97. package/dist/hooks/useAnchoredPosition.js +58 -8
  98. package/dist/hooks/useVirtualRows.d.ts +17 -2
  99. package/dist/hooks/useVirtualRows.js +6 -5
  100. package/dist/icons/Icon.d.ts +14 -14
  101. package/dist/index.cjs +4938 -1631
  102. package/dist/index.d.cts +1622 -178
  103. package/dist/index.d.ts +20 -6
  104. package/dist/index.js +88 -4
  105. package/dist/labels-context.d.ts +28 -0
  106. package/dist/{overlay-position-Cjv5E7mn.d.cts → overlay-position-2Afke51l.d.cts} +20 -0
  107. package/dist/overlay-position.d.ts +20 -0
  108. package/dist/overlay-position.js +17 -2
  109. package/dist/rich-text.cjs +169 -27
  110. package/dist/rich-text.d.cts +3 -3
  111. package/dist/rule-builder-model.d.ts +34 -2
  112. package/dist/rule-builder-model.js +48 -12
  113. package/dist/scheduling-calendar-model.d.ts +66 -0
  114. package/dist/scheduling-calendar-model.js +82 -0
  115. package/dist/select-model.d.ts +80 -0
  116. package/dist/select-model.js +109 -0
  117. package/dist/table-engine.d.ts +7 -0
  118. package/dist/table-engine.js +2 -1
  119. package/dist/text-diff-model.d.ts +71 -0
  120. package/dist/text-diff-model.js +261 -0
  121. package/package.json +1 -1
  122. package/dist/ChartCore-DzWe10bN.d.cts +0 -59
@@ -3,13 +3,18 @@ import { ReactElement, ReactNode } from 'react';
3
3
  import { UixRenderLink } from '../link.js';
4
4
  import { Placement } from '../overlay-position.js';
5
5
 
6
+ /** `id` and `data-*` for the item's own element (HAR-1629): a stable hook for tests, journeys and analytics. */
7
+ interface MenuItemAttributes {
8
+ id?: string;
9
+ [dataAttribute: `data-${string}`]: string | number | boolean | undefined;
10
+ }
6
11
  interface MenuProps {
7
12
  /**
8
13
  * The menu button, usually a `Button`. It receives `aria-haspopup="menu"`,
9
14
  * `aria-expanded`, `aria-controls` and the open/keyboard handlers.
10
15
  */
11
16
  trigger: ReactElement;
12
- /** `MenuItem`, `MenuGroup` and `MenuSeparator`. */
17
+ /** `MenuItem`, `MenuItemRadio`, `MenuItemCheckbox`, `MenuRadioGroup`, `MenuGroup` and `MenuSeparator`. */
13
18
  children?: ReactNode;
14
19
  /** Controlled open state; pair with `onOpenChange`. */
15
20
  open?: boolean;
@@ -23,10 +28,11 @@ interface MenuProps {
23
28
  /**
24
29
  * A menu button with an APG menu (HAR-1359; TENSOR C1, MOTUS B-A14): ↓/↑ move, Home/End,
25
30
  * type-to-find, Enter/Space activate, Esc and Tab close, focus returns to the trigger.
26
- * The surface is the kit `Popover`, so it escapes clipping containers.
31
+ * The surface is the kit `Popover`, so it escapes clipping containers; a menu taller than
32
+ * the viewport is capped to it and scrolls (HAR-1613).
27
33
  */
28
34
  declare function Menu({ trigger, children, open: controlledOpen, defaultOpen, onOpenChange, label, placement, className }: MenuProps): react.JSX.Element;
29
- interface MenuItemProps {
35
+ interface MenuItemProps extends MenuItemAttributes {
30
36
  children?: ReactNode;
31
37
  /** Runs on click, Enter or Space; the menu then closes and focus returns to the trigger. */
32
38
  onSelect?: () => void;
@@ -40,7 +46,49 @@ interface MenuItemProps {
40
46
  disabled?: boolean;
41
47
  className?: string;
42
48
  }
43
- declare function MenuItem({ children, onSelect, href, renderLink, icon, shortcut, tone, disabled, className }: MenuItemProps): react.JSX.Element;
49
+ declare function MenuItem({ children, onSelect, href, renderLink, icon, shortcut, tone, disabled, className, ...rest }: MenuItemProps): react.JSX.Element;
50
+ interface MenuRadioGroupProps {
51
+ /** The `value` of the checked `MenuItemRadio`. */
52
+ value?: string;
53
+ onValueChange?: (value: string) => void;
54
+ /** Visible group heading; it also names the group. */
55
+ label?: ReactNode;
56
+ /** Names the group when there is no visible `label`. */
57
+ 'aria-label'?: string;
58
+ children?: ReactNode;
59
+ }
60
+ /** One choice out of several inside a `Menu`: wraps `MenuItemRadio` rows and holds their value (HAR-1629). */
61
+ declare function MenuRadioGroup({ value, onValueChange, label, 'aria-label': ariaLabel, children }: MenuRadioGroupProps): react.JSX.Element;
62
+ interface MenuItemRadioProps extends MenuItemAttributes {
63
+ /** Checked when it equals the surrounding `MenuRadioGroup`'s `value`. */
64
+ value: string;
65
+ /** For a radio item used without a `MenuRadioGroup`. */
66
+ checked?: boolean;
67
+ /** Runs when the item is chosen, after the group's `onValueChange`. */
68
+ onSelect?: () => void;
69
+ /** Close the menu after a click or Enter. Default `true`. Space never closes it. */
70
+ closeOnSelect?: boolean;
71
+ children?: ReactNode;
72
+ icon?: ReactNode;
73
+ shortcut?: ReactNode;
74
+ disabled?: boolean;
75
+ className?: string;
76
+ }
77
+ /** A `role="menuitemradio"` row with `aria-checked` and a check mark for the current choice. */
78
+ declare function MenuItemRadio({ value, checked, onSelect, closeOnSelect, ...rest }: MenuItemRadioProps): react.JSX.Element;
79
+ interface MenuItemCheckboxProps extends MenuItemAttributes {
80
+ checked: boolean;
81
+ onCheckedChange?: (checked: boolean) => void;
82
+ /** Close the menu after a click or Enter. Default `false`: several boxes are usually ticked in a row. */
83
+ closeOnSelect?: boolean;
84
+ children?: ReactNode;
85
+ icon?: ReactNode;
86
+ shortcut?: ReactNode;
87
+ disabled?: boolean;
88
+ className?: string;
89
+ }
90
+ /** A `role="menuitemcheckbox"` row with `aria-checked`; the menu stays open while boxes are ticked. */
91
+ declare function MenuItemCheckbox({ checked, onCheckedChange, closeOnSelect, ...rest }: MenuItemCheckboxProps): react.JSX.Element;
44
92
  interface MenuGroupProps {
45
93
  label: ReactNode;
46
94
  children?: ReactNode;
@@ -48,4 +96,4 @@ interface MenuGroupProps {
48
96
  declare function MenuGroup({ label, children }: MenuGroupProps): react.JSX.Element;
49
97
  declare function MenuSeparator(): react.JSX.Element;
50
98
 
51
- export { Menu, MenuGroup, type MenuGroupProps, MenuItem, type MenuItemProps, type MenuProps, MenuSeparator };
99
+ export { Menu, MenuGroup, type MenuGroupProps, MenuItem, type MenuItemAttributes, MenuItemCheckbox, type MenuItemCheckboxProps, type MenuItemProps, MenuItemRadio, type MenuItemRadioProps, type MenuProps, MenuRadioGroup, type MenuRadioGroupProps, MenuSeparator };
@@ -3,9 +3,16 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { cloneElement, createContext, isValidElement, useContext, useEffect, useId, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { Popover } from "./Popover.js";
6
+ import { CheckIcon } from "../icons/components.js";
6
7
  import { renderUixLink } from "../link.js";
7
8
  const MenuContext = createContext(null);
8
- const ITEM = '[role="menuitem"]:not([aria-disabled="true"])';
9
+ const MenuRadioContext = createContext(null);
10
+ const ITEM = '[role^="menuitem"]:not([aria-disabled="true"])';
11
+ const itemAttributes = (rest) => {
12
+ const out = {};
13
+ for (const key of Object.keys(rest)) if (key === "id" || key.startsWith("data-")) out[key] = rest[key];
14
+ return out;
15
+ };
9
16
  function Menu({ trigger, children, open: controlledOpen, defaultOpen = false, onOpenChange, label, placement = "bottom-start", className }) {
10
17
  const id = useId();
11
18
  const menuId = `${id}-menu`;
@@ -51,7 +58,8 @@ function Menu({ trigger, children, open: controlledOpen, defaultOpen = false, on
51
58
  }
52
59
  }
53
60
  const list = items();
54
- (focusOnOpen.current === "last" ? list[list.length - 1] : list[0])?.focus();
61
+ const start = focusOnOpen.current === "last" ? list[list.length - 1] : list.find((item) => item.getAttribute("role") === "menuitemradio" && item.getAttribute("aria-checked") === "true") ?? list[0];
62
+ start?.focus();
55
63
  const onPointer = (event) => {
56
64
  const target = event.target;
57
65
  if (el.contains(target) || triggerRef.current?.contains(target)) return;
@@ -109,6 +117,12 @@ function Menu({ trigger, children, open: controlledOpen, defaultOpen = false, on
109
117
  const list = items();
110
118
  (focusOnOpen.current === "last" ? list[list.length - 1] : list[0])?.focus();
111
119
  } else setOpen(true);
120
+ } else if (open && event.key === "Escape") {
121
+ event.preventDefault();
122
+ event.stopPropagation();
123
+ close(true);
124
+ } else if (open && event.key === "Tab") {
125
+ close(false);
112
126
  }
113
127
  }
114
128
  }) : trigger;
@@ -121,6 +135,7 @@ function Menu({ trigger, children, open: controlledOpen, defaultOpen = false, on
121
135
  popover: "manual",
122
136
  anchor: triggerRef,
123
137
  placement,
138
+ capHeight: true,
124
139
  role: "menu",
125
140
  "aria-label": label,
126
141
  "aria-labelledby": label ? void 0 : triggerProps.id ?? triggerId,
@@ -131,20 +146,22 @@ function Menu({ trigger, children, open: controlledOpen, defaultOpen = false, on
131
146
  )
132
147
  ] });
133
148
  }
134
- function MenuItem({ children, onSelect, href, renderLink, icon, shortcut, tone = "default", disabled, className }) {
149
+ function MenuItem({ children, onSelect, href, renderLink, icon, shortcut, tone = "default", disabled, className, ...rest }) {
135
150
  const menu = useContext(MenuContext);
136
151
  const classes = cx("uix-menu__item", tone === "danger" && "uix-menu__item--danger", className);
152
+ const attributes = itemAttributes(rest);
137
153
  const body = /* @__PURE__ */ jsxs(Fragment, { children: [
138
154
  icon != null && /* @__PURE__ */ jsx("span", { className: "uix-menu__icon", "aria-hidden": "true", children: icon }),
139
155
  /* @__PURE__ */ jsx("span", { className: "uix-menu__text", children }),
140
156
  shortcut != null && /* @__PURE__ */ jsx("span", { className: "uix-menu__shortcut", children: shortcut })
141
157
  ] });
142
158
  if (href != null && !disabled) {
143
- return /* @__PURE__ */ jsx(Fragment, { children: renderUixLink(renderLink, { href, role: "menuitem", tabIndex: -1, className: classes, onClick: () => menu?.close(false), children: body }) });
159
+ return /* @__PURE__ */ jsx(Fragment, { children: renderUixLink(renderLink, { ...attributes, href, role: "menuitem", tabIndex: -1, className: classes, onClick: () => menu?.close(false), children: body }) });
144
160
  }
145
161
  return /* @__PURE__ */ jsx(
146
162
  "button",
147
163
  {
164
+ ...attributes,
148
165
  type: "button",
149
166
  role: "menuitem",
150
167
  tabIndex: -1,
@@ -159,6 +176,86 @@ function MenuItem({ children, onSelect, href, renderLink, icon, shortcut, tone =
159
176
  }
160
177
  );
161
178
  }
179
+ function CheckableItem({
180
+ role,
181
+ checked,
182
+ onToggle,
183
+ closeOnSelect,
184
+ children,
185
+ icon,
186
+ shortcut,
187
+ disabled,
188
+ className,
189
+ ...rest
190
+ }) {
191
+ const menu = useContext(MenuContext);
192
+ return /* @__PURE__ */ jsxs(
193
+ "button",
194
+ {
195
+ ...itemAttributes(rest),
196
+ type: "button",
197
+ role,
198
+ "aria-checked": checked,
199
+ tabIndex: -1,
200
+ className: cx("uix-menu__item", className),
201
+ "aria-disabled": disabled || void 0,
202
+ onKeyDown: (event) => {
203
+ if (event.key !== " " || disabled) return;
204
+ event.preventDefault();
205
+ onToggle();
206
+ },
207
+ onKeyUp: (event) => {
208
+ if (event.key === " ") event.preventDefault();
209
+ },
210
+ onClick: () => {
211
+ if (disabled) return;
212
+ onToggle();
213
+ if (closeOnSelect) menu?.close(true);
214
+ },
215
+ children: [
216
+ /* @__PURE__ */ jsx("span", { className: "uix-menu__check", "aria-hidden": "true", children: checked && /* @__PURE__ */ jsx(CheckIcon, {}) }),
217
+ icon != null && /* @__PURE__ */ jsx("span", { className: "uix-menu__icon", "aria-hidden": "true", children: icon }),
218
+ /* @__PURE__ */ jsx("span", { className: "uix-menu__text", children }),
219
+ shortcut != null && /* @__PURE__ */ jsx("span", { className: "uix-menu__shortcut", children: shortcut })
220
+ ]
221
+ }
222
+ );
223
+ }
224
+ function MenuRadioGroup({ value, onValueChange, label, "aria-label": ariaLabel, children }) {
225
+ const id = useId();
226
+ return /* @__PURE__ */ jsx(MenuRadioContext.Provider, { value: { value, onValueChange }, children: /* @__PURE__ */ jsxs("div", { role: "group", "aria-labelledby": label != null ? id : void 0, "aria-label": label != null ? void 0 : ariaLabel, children: [
227
+ label != null && /* @__PURE__ */ jsx("div", { id, className: "uix-menu__label", children: label }),
228
+ children
229
+ ] }) });
230
+ }
231
+ function MenuItemRadio({ value, checked, onSelect, closeOnSelect = true, ...rest }) {
232
+ const group = useContext(MenuRadioContext);
233
+ return /* @__PURE__ */ jsx(
234
+ CheckableItem,
235
+ {
236
+ ...rest,
237
+ role: "menuitemradio",
238
+ checked: checked ?? (group != null && group.value === value),
239
+ closeOnSelect,
240
+ onToggle: () => {
241
+ group?.onValueChange?.(value);
242
+ onSelect?.();
243
+ }
244
+ }
245
+ );
246
+ }
247
+ function MenuItemCheckbox({ checked, onCheckedChange, closeOnSelect = false, ...rest }) {
248
+ return /* @__PURE__ */ jsx(
249
+ CheckableItem,
250
+ {
251
+ ...rest,
252
+ role: "menuitemcheckbox",
253
+ checked,
254
+ closeOnSelect,
255
+ onToggle: () => onCheckedChange?.(!checked)
256
+ }
257
+ );
258
+ }
162
259
  function MenuGroup({ label, children }) {
163
260
  const id = useId();
164
261
  return /* @__PURE__ */ jsxs("div", { role: "group", "aria-labelledby": id, children: [
@@ -173,5 +270,8 @@ export {
173
270
  Menu,
174
271
  MenuGroup,
175
272
  MenuItem,
273
+ MenuItemCheckbox,
274
+ MenuItemRadio,
275
+ MenuRadioGroup,
176
276
  MenuSeparator
177
277
  };
@@ -1,8 +1,12 @@
1
1
  import * as react from 'react';
2
2
  import { HTMLAttributes } from 'react';
3
3
 
4
- /** Fill color tone for the meter bar. Defaults to `success` (green). */
5
- type MeterTone = 'success' | 'warning' | 'danger' | 'attention' | 'overdue';
4
+ /**
5
+ * Fill color tone for the meter bar. Defaults to `success` (green).
6
+ * `neutral` and `accent` are for a plain proportion that is neither good nor bad (a poll
7
+ * option's share of the votes): a grey or brand fill, and no tone word is spoken (HAR-1606).
8
+ */
9
+ type MeterTone = 'success' | 'warning' | 'danger' | 'attention' | 'overdue' | 'neutral' | 'accent';
6
10
  interface MeterProps extends HTMLAttributes<HTMLDivElement> {
7
11
  /** Fill level 0–100. Clamped to this range. */
8
12
  value?: number;
@@ -11,7 +15,7 @@ interface MeterProps extends HTMLAttributes<HTMLDivElement> {
11
15
  /** Accessible name — rendered as `aria-label` (an explicit `aria-label` prop wins). */
12
16
  label?: string;
13
17
  /** TENSOR RX-125 (UIX-04): the spoken tone words; English defaults. */
14
- toneLabels?: Partial<Record<Exclude<MeterTone, 'success'>, string>>;
18
+ toneLabels?: Partial<Record<Exclude<MeterTone, 'success' | 'neutral' | 'accent'>, string>>;
15
19
  }
16
20
  /** Horizontal utilization / threshold bar backed by `.uix-meter`. */
17
21
  declare function Meter({ value, tone, label, toneLabels, className, ...props }: MeterProps): react.JSX.Element;
@@ -9,6 +9,7 @@ const toneText = {
9
9
  function Meter({ value = 0, tone, label, toneLabels, className, ...props }) {
10
10
  const spoken = { ...toneText, ...toneLabels };
11
11
  const pct = Math.max(0, Math.min(100, value));
12
+ const toneWord = tone ? spoken[tone] : void 0;
12
13
  return /* @__PURE__ */ jsx(
13
14
  "div",
14
15
  {
@@ -16,7 +17,7 @@ function Meter({ value = 0, tone, label, toneLabels, className, ...props }) {
16
17
  "aria-valuenow": pct,
17
18
  "aria-valuemin": 0,
18
19
  "aria-valuemax": 100,
19
- "aria-valuetext": tone && tone !== "success" ? `${pct}%, ${spoken[tone]}` : void 0,
20
+ "aria-valuetext": toneWord ? `${pct}%, ${toneWord}` : void 0,
20
21
  "aria-label": label,
21
22
  className: cx("uix-meter", className),
22
23
  ...props,
@@ -10,7 +10,8 @@ interface PopoverProps extends HTMLAttributes<HTMLDivElement> {
10
10
  popover?: 'auto' | 'manual';
11
11
  /**
12
12
  * Element (or ref) to anchor against. When set, the popover is placed with
13
- * cross-browser JS positioning — flip when it won't fit, shift to stay on-screen.
13
+ * cross-browser JS positioning — flip when it won't fit, shift to stay on-screen. A popover
14
+ * that fits neither above nor below slides over its anchor rather than leave the viewport.
14
15
  * Where the browser supports CSS anchor positioning, the placement is written as an
15
16
  * offset from the anchor, so it stays attached while the page scrolls; elsewhere it
16
17
  * follows scroll from JS. The native Popover API still provides the top layer (so it
@@ -30,6 +31,24 @@ interface PopoverProps extends HTMLAttributes<HTMLDivElement> {
30
31
  closeWhenAnchorHidden?: boolean;
31
32
  /** Called when `closeWhenAnchorHidden` is about to close the popover. */
32
33
  onAnchorHidden?: () => void;
34
+ /**
35
+ * Cap the popover's height to the viewport (minus 8 px at each edge) and scroll inside it,
36
+ * for content that can be taller than a phone screen. Needs `anchor`. Default `false`.
37
+ */
38
+ capHeight?: boolean;
39
+ /**
40
+ * Open while the pointer is over the `anchor` or the anchor has keyboard focus, and close
41
+ * when both have left the anchor and the popover — a hover card (HAR-1614). The pointer can
42
+ * travel from the anchor into the popover, and focus can move into it, without closing it;
43
+ * Escape closes it. Touch does not hover: open it from the anchor's own click there.
44
+ * `true` waits 300 ms before opening and 150 ms before closing; pass the delays to change
45
+ * them. Needs `anchor`; `popover` then defaults to `"manual"`, so a hover card does not
46
+ * dismiss a menu or dialog that is open.
47
+ */
48
+ openOnHover?: boolean | {
49
+ openDelay?: number;
50
+ closeDelay?: number;
51
+ };
33
52
  children?: ReactNode;
34
53
  }
35
54
  /**
@@ -37,6 +56,6 @@ interface PopoverProps extends HTMLAttributes<HTMLDivElement> {
37
56
  * things like a filter popover, menu, or rich select by putting controls inside.
38
57
  * Pass `anchor` to get collision-aware, cross-browser placement (UIX-FIX-02).
39
58
  */
40
- declare function Popover({ popover, anchor, placement, offset, closeWhenAnchorHidden, onAnchorHidden, className, children, ...props }: PopoverProps): react.JSX.Element;
59
+ declare function Popover({ popover: popoverProp, anchor, placement, offset, closeWhenAnchorHidden, onAnchorHidden, capHeight, openOnHover, className, children, ...props }: PopoverProps): react.JSX.Element;
41
60
 
42
61
  export { Popover, type PopoverProps };
@@ -18,16 +18,22 @@ const settle = (el) => {
18
18
  el.removeAttribute(PLACING);
19
19
  };
20
20
  function Popover({
21
- popover = "auto",
21
+ popover: popoverProp,
22
22
  anchor,
23
23
  placement = "bottom-start",
24
24
  offset = 6,
25
25
  closeWhenAnchorHidden = false,
26
26
  onAnchorHidden,
27
+ capHeight = false,
28
+ openOnHover = false,
27
29
  className,
28
30
  children,
29
31
  ...props
30
32
  }) {
33
+ const hover = !!openOnHover && !!anchor;
34
+ const popover = popoverProp ?? (hover ? "manual" : "auto");
35
+ const openDelay = typeof openOnHover === "object" ? openOnHover.openDelay ?? 300 : 300;
36
+ const closeDelay = typeof openOnHover === "object" ? openOnHover.closeDelay ?? 150 : 150;
31
37
  const ref = useRef(null);
32
38
  const [open, setOpen] = useState(false);
33
39
  const openRef = useRef(false);
@@ -37,7 +43,16 @@ function Popover({
37
43
  const reposition = useAnchoredPosition(anchor ?? null, ref, {
38
44
  open: open && !!anchor,
39
45
  placement,
40
- offset
46
+ offset,
47
+ capHeight,
48
+ onAnchorHidden: closeWhenAnchorHidden ? () => {
49
+ const el = ref.current;
50
+ if (!el || !isPopoverOpen(el)) return;
51
+ onAnchorHiddenRef.current?.();
52
+ const active = el.ownerDocument.activeElement;
53
+ if (active && el.contains(active)) active.blur();
54
+ el.hidePopover();
55
+ } : void 0
41
56
  });
42
57
  useEffect(() => {
43
58
  const el = ref.current;
@@ -72,25 +87,87 @@ function Popover({
72
87
  el.removeAttribute(PLACING);
73
88
  };
74
89
  }, [anchor, reposition]);
90
+ useEffect(() => {
91
+ const el = ref.current;
92
+ const anchorEl = resolveAnchor(anchor);
93
+ if (!hover || !el || !anchorEl) return;
94
+ let timer;
95
+ const inside = (node) => !!node && (anchorEl.contains(node) || el.contains(node));
96
+ const stillWanted = () => {
97
+ try {
98
+ return anchorEl.matches(":hover") || el.matches(":hover") || inside(el.ownerDocument.activeElement);
99
+ } catch {
100
+ return false;
101
+ }
102
+ };
103
+ const show = (delay) => {
104
+ window.clearTimeout(timer);
105
+ timer = window.setTimeout(() => {
106
+ if (!isPopoverOpen(el)) {
107
+ try {
108
+ el.showPopover?.();
109
+ } catch {
110
+ }
111
+ }
112
+ }, delay);
113
+ };
114
+ const hide = (delay) => {
115
+ window.clearTimeout(timer);
116
+ timer = window.setTimeout(() => {
117
+ if (isPopoverOpen(el) && !stillWanted()) {
118
+ try {
119
+ el.hidePopover?.();
120
+ } catch {
121
+ }
122
+ }
123
+ }, delay);
124
+ };
125
+ const onEnter = (event) => {
126
+ if (event.pointerType !== "touch") show(openDelay);
127
+ };
128
+ const onLeave = (event) => {
129
+ if (event.pointerType !== "touch") hide(closeDelay);
130
+ };
131
+ const onFocusIn = () => show(0);
132
+ const onFocusOut = (event) => {
133
+ if (!inside(event.relatedTarget)) hide(closeDelay);
134
+ };
135
+ const onKeyDown = (event) => {
136
+ if (event.key !== "Escape" || !isPopoverOpen(el)) return;
137
+ window.clearTimeout(timer);
138
+ const hadFocus = el.contains(el.ownerDocument.activeElement);
139
+ try {
140
+ el.hidePopover?.();
141
+ } catch {
142
+ }
143
+ if (hadFocus) {
144
+ anchorEl.removeEventListener("focusin", onFocusIn);
145
+ anchorEl.focus();
146
+ anchorEl.addEventListener("focusin", onFocusIn);
147
+ }
148
+ };
149
+ for (const node of [anchorEl, el]) {
150
+ node.addEventListener("pointerenter", onEnter);
151
+ node.addEventListener("pointerleave", onLeave);
152
+ node.addEventListener("focusout", onFocusOut);
153
+ node.addEventListener("keydown", onKeyDown);
154
+ }
155
+ anchorEl.addEventListener("focusin", onFocusIn);
156
+ return () => {
157
+ window.clearTimeout(timer);
158
+ for (const node of [anchorEl, el]) {
159
+ node.removeEventListener("pointerenter", onEnter);
160
+ node.removeEventListener("pointerleave", onLeave);
161
+ node.removeEventListener("focusout", onFocusOut);
162
+ node.removeEventListener("keydown", onKeyDown);
163
+ }
164
+ anchorEl.removeEventListener("focusin", onFocusIn);
165
+ };
166
+ }, [hover, anchor, openDelay, closeDelay]);
75
167
  useIsomorphicLayoutEffect(() => {
76
168
  openRef.current = open;
77
169
  if (open && ref.current) settle(ref.current);
78
170
  }, [open]);
79
- useEffect(() => {
80
- const el = ref.current;
81
- const anchorEl = resolveAnchor(anchor);
82
- if (!closeWhenAnchorHidden || !open || !el || !anchorEl || typeof IntersectionObserver !== "function") return;
83
- const observer = new IntersectionObserver((entries) => {
84
- const latest = entries[entries.length - 1];
85
- if (!latest || latest.isIntersecting || !isPopoverOpen(el)) return;
86
- onAnchorHiddenRef.current?.();
87
- const active = el.ownerDocument.activeElement;
88
- if (active && el.contains(active)) active.blur();
89
- el.hidePopover();
90
- });
91
- observer.observe(anchorEl);
92
- return () => observer.disconnect();
93
- }, [closeWhenAnchorHidden, open, anchor]);
94
171
  return /* @__PURE__ */ jsx("div", { ref, className: cx("uix-popover", className), ...popoverAttr, ...props, children });
95
172
  }
96
173
  export {
@@ -9,9 +9,13 @@ interface ProseProps extends HTMLAttributes<HTMLDivElement> {
9
9
  * release notes). Apply to a block of rendered HTML/markdown.
10
10
  */
11
11
  declare function Prose({ children, className, ...props }: ProseProps): react.JSX.Element;
12
- type NoteTone = 'info' | 'success' | 'warning' | 'danger';
12
+ type NoteTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger';
13
13
  interface NoteProps extends HTMLAttributes<HTMLDivElement> {
14
- tone?: NoteTone;
14
+ /**
15
+ * `'neutral'` and `undefined` are both the default look. `undefined` is spelled out in the type
16
+ * so `tone={maybeTone}` also compiles under `exactOptionalPropertyTypes` (HAR-1614).
17
+ */
18
+ tone?: NoteTone | undefined;
15
19
  icon?: ReactNode;
16
20
  children?: ReactNode;
17
21
  }
@@ -4,7 +4,7 @@ function Prose({ children, className, ...props }) {
4
4
  return /* @__PURE__ */ jsx("div", { className: cx("uix-prose", className), ...props, children });
5
5
  }
6
6
  function Note({ tone, icon, children, className, ...props }) {
7
- return /* @__PURE__ */ jsxs("div", { className: cx("uix-note", tone && `uix-note--${tone}`, className), ...props, children: [
7
+ return /* @__PURE__ */ jsxs("div", { className: cx("uix-note", tone && tone !== "neutral" && `uix-note--${tone}`, className), ...props, children: [
8
8
  icon != null && /* @__PURE__ */ jsx("div", { className: "uix-note__icon", children: icon }),
9
9
  /* @__PURE__ */ jsx("div", { className: "uix-note__body", children })
10
10
  ] });
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { JsonValue } from '../json-value.js';
4
- import { RuleAction, RuleDefinition, RuleCondition, RuleValidationIssue } from '../rule-builder-model.js';
4
+ import { RuleAction, RuleDefinition, RuleCondition, RuleCheck, RuleValidationIssue } from '../rule-builder-model.js';
5
5
 
6
6
  interface RuleValueEditorProps {
7
7
  condition: RuleCondition;
@@ -28,6 +28,10 @@ interface RuleActionDefinition {
28
28
  /**
29
29
  * Every word the rule builder renders (TENSOR RX-125, UIX-12). `{item}` is one of
30
30
  * `itemGroup` / `itemCondition` / `itemAction`; `{field}` a field label; `{count}` a number.
31
+ * The `issue…` keys are the built-in validation messages (`{id}` the duplicated id, `{max}`
32
+ * the depth limit) and the `summary…` / `and` / `or` keys the words of the
33
+ * `readOnly="summary"` sentence (HAR-1618). Translate once for a subtree with
34
+ * `UixLabelsProvider labels={{ ruleBuilder: … }}`.
31
35
  */
32
36
  interface RuleBuilderLabels {
33
37
  region: string;
@@ -59,6 +63,19 @@ interface RuleBuilderLabels {
59
63
  addCondition: string;
60
64
  addGroup: string;
61
65
  addAction: string;
66
+ issueDuplicateId: string;
67
+ issueMaxDepth: string;
68
+ issueEmptyGroup: string;
69
+ issueMissingField: string;
70
+ issueMissingOperator: string;
71
+ issueNoActions: string;
72
+ issueMissingActionType: string;
73
+ summarySentence: string;
74
+ summarySentenceConditionsOnly: string;
75
+ summaryNoConditions: string;
76
+ summaryNoActions: string;
77
+ and: string;
78
+ or: string;
62
79
  }
63
80
  declare const DEFAULT_RULE_BUILDER_LABELS: RuleBuilderLabels;
64
81
  interface RuleBuilderProps {
@@ -66,9 +83,40 @@ interface RuleBuilderProps {
66
83
  onChange: (value: RuleDefinition) => void;
67
84
  fields: RuleFieldDefinition[];
68
85
  operators: RuleOperatorDefinition[];
69
- actions: RuleActionDefinition[];
86
+ /** The action types of the "Then" block. Not needed with `conditionsOnly`. */
87
+ actions?: RuleActionDefinition[];
70
88
  maxDepth?: number;
71
- readOnly?: boolean;
89
+ /**
90
+ * `true`: the same builder with every control disabled and no add, move or remove buttons.
91
+ * `'summary'`: one sentence instead of the controls (what `readOnly` rendered up to 2.31.0;
92
+ * its words are the `summary…`, `and` and `or` labels).
93
+ */
94
+ readOnly?: boolean | 'summary';
95
+ /**
96
+ * Edit conditions only (HAR-1618): no "Then" block and no "Add action", and the two action
97
+ * checks (`noActions`, `missingActionType`) are off. `value.then` is passed through untouched.
98
+ */
99
+ conditionsOnly?: boolean;
100
+ /**
101
+ * Fix every group to this combinator, for a stored shape that is a flat AND-list. The Match
102
+ * choice becomes plain text ("Match all (AND)"), new groups get this combinator, and every
103
+ * `onChange` carries it on all groups, so the other value is never emitted.
104
+ */
105
+ combinator?: 'and' | 'or';
106
+ /** Hide the Match control (and its text when `combinator` fixes it). */
107
+ hideCombinator?: boolean;
108
+ /**
109
+ * `false` hides "Add group". It is hidden as well when `maxDepth` is 1, where it could never
110
+ * be used. Default `true`.
111
+ */
112
+ allowGroups?: boolean;
113
+ /**
114
+ * Turn built-in checks off: `{ emptyGroup: false }` for a rule that may have no condition
115
+ * (a catch-all), `{ noActions: false }` for a rule that may do nothing. Their messages are
116
+ * the `issue…` labels.
117
+ */
118
+ checks?: Partial<Record<RuleCheck, boolean>>;
119
+ /** Extra issues; they are appended to the built-in ones. */
72
120
  validate?: (value: RuleDefinition) => RuleValidationIssue[];
73
121
  className?: string;
74
122
  /** @deprecated use `labels.addCondition` */
@@ -80,6 +128,6 @@ interface RuleBuilderProps {
80
128
  labels?: Partial<RuleBuilderLabels>;
81
129
  }
82
130
  /** Declarative controlled when-conditions-to-actions editor with bounded nesting. */
83
- declare function RuleBuilder({ value, onChange, fields, operators, actions, maxDepth, readOnly, validate, className, addConditionLabel, addGroupLabel, addActionLabel, labels: labelOverrides, }: RuleBuilderProps): react.JSX.Element;
131
+ declare function RuleBuilder({ value, onChange, fields, operators, actions, maxDepth, readOnly, conditionsOnly, combinator, hideCombinator, allowGroups, checks, validate, className, addConditionLabel, addGroupLabel, addActionLabel, labels: labelOverrides, }: RuleBuilderProps): react.JSX.Element;
84
132
 
85
133
  export { DEFAULT_RULE_BUILDER_LABELS, type RuleActionDefinition, RuleBuilder, type RuleBuilderLabels, type RuleBuilderProps, type RuleFieldDefinition, type RuleOperatorDefinition, type RuleValueEditorProps };