@tensor_1/react 2.47.0 → 2.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/README.md +540 -2
  2. package/dist/{SchedulingCalendar-B6TakO2h.d.ts → SchedulingCalendar-WWyFL3Eo.d.ts} +51 -2
  3. package/dist/announce.d.ts +10 -0
  4. package/dist/announce.js +30 -0
  5. package/dist/chart-analytic.cjs +33 -25
  6. package/dist/chart-preset.cjs +23 -15
  7. package/dist/chart.cjs +23 -15
  8. package/dist/combobox-model.d.ts +10 -1
  9. package/dist/combobox-model.js +15 -0
  10. package/dist/command-palette-model.d.ts +44 -0
  11. package/dist/command-palette-model.js +36 -0
  12. package/dist/components/AccountMenu.d.ts +44 -0
  13. package/dist/components/AccountMenu.js +43 -0
  14. package/dist/components/AppShell.d.ts +45 -2
  15. package/dist/components/AppShell.js +116 -5
  16. package/dist/components/AuditLog.d.ts +128 -0
  17. package/dist/components/AuditLog.js +196 -0
  18. package/dist/components/BrandProfiles.js +4 -3
  19. package/dist/components/Breadcrumbs.d.ts +9 -2
  20. package/dist/components/Breadcrumbs.js +3 -2
  21. package/dist/components/Button.d.ts +12 -5
  22. package/dist/components/Button.js +7 -3
  23. package/dist/components/Card.d.ts +18 -1
  24. package/dist/components/Card.js +5 -1
  25. package/dist/components/ChangeHighlight.d.ts +30 -0
  26. package/dist/components/ChangeHighlight.js +21 -0
  27. package/dist/components/ChartCore.js +2 -1
  28. package/dist/components/Checkbox.d.ts +27 -1
  29. package/dist/components/Checkbox.js +31 -2
  30. package/dist/components/Chip.d.ts +7 -3
  31. package/dist/components/Chip.js +2 -2
  32. package/dist/components/CodeDisplay.d.ts +65 -0
  33. package/dist/components/CodeDisplay.js +124 -0
  34. package/dist/components/Combobox.d.ts +27 -6
  35. package/dist/components/Combobox.js +195 -58
  36. package/dist/components/CommandPalette.d.ts +36 -1
  37. package/dist/components/CommandPalette.js +254 -2
  38. package/dist/components/ContactCard.d.ts +66 -0
  39. package/dist/components/ContactCard.js +74 -0
  40. package/dist/components/Count.d.ts +24 -0
  41. package/dist/components/Count.js +32 -0
  42. package/dist/components/DashboardCanvas.js +14 -0
  43. package/dist/components/DataWorkspace.d.ts +167 -0
  44. package/dist/components/DataWorkspace.js +380 -0
  45. package/dist/components/DateFieldParts.d.ts +2 -1
  46. package/dist/components/DateFieldParts.js +7 -0
  47. package/dist/components/DatePicker.d.ts +9 -2
  48. package/dist/components/DatePicker.js +67 -28
  49. package/dist/components/DateRangePicker.d.ts +12 -2
  50. package/dist/components/DateRangePicker.js +13 -12
  51. package/dist/components/DetailPage.d.ts +7 -1
  52. package/dist/components/DetailPage.js +2 -1
  53. package/dist/components/Dialogs.d.ts +71 -10
  54. package/dist/components/Dialogs.js +130 -30
  55. package/dist/components/DiffViewer.d.ts +23 -3
  56. package/dist/components/DiffViewer.js +26 -12
  57. package/dist/components/DiscardPrompt.js +16 -0
  58. package/dist/components/DisplayPreferences.d.ts +83 -0
  59. package/dist/components/DisplayPreferences.js +185 -0
  60. package/dist/components/EditorialHome.d.ts +23 -3
  61. package/dist/components/EditorialHome.js +16 -11
  62. package/dist/components/EntityPicker.d.ts +20 -3
  63. package/dist/components/EntityPicker.js +32 -13
  64. package/dist/components/FilterEditor.d.ts +18 -4
  65. package/dist/components/FilterEditor.js +141 -19
  66. package/dist/components/FocusOnMount.d.ts +282 -0
  67. package/dist/components/FocusOnMount.js +12 -0
  68. package/dist/components/InfoTip.js +11 -12
  69. package/dist/components/InlineEdit.d.ts +87 -20
  70. package/dist/components/InlineEdit.js +197 -54
  71. package/dist/components/Input.d.ts +12 -2
  72. package/dist/components/Input.js +5 -3
  73. package/dist/components/Kanban.d.ts +14 -3
  74. package/dist/components/Kanban.js +9 -4
  75. package/dist/components/Kbd.d.ts +6 -1
  76. package/dist/components/Kbd.js +6 -6
  77. package/dist/components/Lightbox.d.ts +41 -5
  78. package/dist/components/Lightbox.js +95 -9
  79. package/dist/components/MatchReview.d.ts +40 -1
  80. package/dist/components/MatchReview.js +130 -4
  81. package/dist/components/MetricInput.js +3 -2
  82. package/dist/components/Modal.d.ts +2 -1
  83. package/dist/components/Modal.js +13 -4
  84. package/dist/components/MotionList.d.ts +303 -0
  85. package/dist/components/MotionList.js +175 -0
  86. package/dist/components/NavFavourites.d.ts +5 -0
  87. package/dist/components/NavFavourites.js +7 -7
  88. package/dist/components/OtpInput.d.ts +33 -0
  89. package/dist/components/OtpInput.js +123 -0
  90. package/dist/components/PageHeader.d.ts +7 -1
  91. package/dist/components/PageHeader.js +2 -2
  92. package/dist/components/Pagination.js +6 -5
  93. package/dist/components/PasswordInput.d.ts +34 -0
  94. package/dist/components/PasswordInput.js +101 -0
  95. package/dist/components/Peek.js +3 -3
  96. package/dist/components/Popover.js +11 -0
  97. package/dist/components/PrintLayout.d.ts +63 -0
  98. package/dist/components/PrintLayout.js +114 -0
  99. package/dist/components/QrCode.d.ts +47 -0
  100. package/dist/components/QrCode.js +55 -0
  101. package/dist/components/QrScanner.d.ts +95 -0
  102. package/dist/components/QrScanner.js +266 -0
  103. package/dist/components/RelationshipGraph.d.ts +32 -1
  104. package/dist/components/RelationshipGraph.js +22 -6
  105. package/dist/components/RelationshipGraphLayered.d.ts +1 -1
  106. package/dist/components/RelationshipGraphLayered.js +268 -118
  107. package/dist/components/RelationshipGraphList.js +33 -6
  108. package/dist/components/RuleBuilder.js +39 -7
  109. package/dist/components/SchedulingAgenda.d.ts +3 -1
  110. package/dist/components/SchedulingCalendar.d.ts +3 -1
  111. package/dist/components/SchedulingCalendar.js +43 -4
  112. package/dist/components/SchedulingResourceDay.d.ts +57 -0
  113. package/dist/components/SchedulingResourceDay.js +287 -0
  114. package/dist/components/SchedulingTimeGrid.d.ts +3 -1
  115. package/dist/components/SchedulingTimeline.d.ts +3 -1
  116. package/dist/components/SearchSuggest.d.ts +23 -3
  117. package/dist/components/SearchSuggest.js +56 -18
  118. package/dist/components/Segmented.d.ts +39 -4
  119. package/dist/components/Segmented.js +39 -5
  120. package/dist/components/Select.d.ts +3 -2
  121. package/dist/components/Select.js +7 -1
  122. package/dist/components/ShortcutHint.d.ts +11 -10
  123. package/dist/components/ShortcutHint.js +44 -38
  124. package/dist/components/Sidebar.d.ts +36 -4
  125. package/dist/components/Sidebar.js +37 -12
  126. package/dist/components/SortableList.d.ts +45 -0
  127. package/dist/components/SortableList.js +147 -0
  128. package/dist/components/Stat.d.ts +28 -3
  129. package/dist/components/Stat.js +13 -3
  130. package/dist/components/States.d.ts +33 -1
  131. package/dist/components/States.js +43 -1
  132. package/dist/components/StatusPill.d.ts +14 -3
  133. package/dist/components/StatusPill.js +11 -3
  134. package/dist/components/Steps.js +2 -1
  135. package/dist/components/Table.d.ts +9 -2
  136. package/dist/components/Table.js +4 -2
  137. package/dist/components/TableControls.js +18 -58
  138. package/dist/components/TableOverflowProbe.d.ts +12 -0
  139. package/dist/components/TableOverflowProbe.js +27 -0
  140. package/dist/components/Tabs.d.ts +2 -1
  141. package/dist/components/Tabs.js +10 -2
  142. package/dist/components/TagInput.d.ts +67 -0
  143. package/dist/components/TagInput.js +342 -0
  144. package/dist/components/Textarea.d.ts +5 -0
  145. package/dist/components/Textarea.js +2 -2
  146. package/dist/components/TimePicker.d.ts +80 -0
  147. package/dist/components/TimePicker.js +347 -0
  148. package/dist/components/Toast.d.ts +1 -1
  149. package/dist/components/Toast.js +109 -4
  150. package/dist/components/Tooltip.d.ts +8 -4
  151. package/dist/components/Tooltip.js +86 -28
  152. package/dist/components/Tree.d.ts +8 -0
  153. package/dist/components/Tree.js +11 -5
  154. package/dist/display-preferences-model.d.ts +53 -0
  155. package/dist/display-preferences-model.js +64 -0
  156. package/dist/emoji.cjs +143 -49
  157. package/dist/filter-model.d.ts +59 -3
  158. package/dist/filter-model.js +67 -2
  159. package/dist/hooks/useAnchoredPosition.js +14 -2
  160. package/dist/hooks/useChangeHighlight.d.ts +33 -0
  161. package/dist/hooks/useChangeHighlight.js +44 -0
  162. package/dist/hooks/useDeferredReorder.d.ts +32 -0
  163. package/dist/hooks/useDeferredReorder.js +47 -0
  164. package/dist/hooks/useDialog.d.ts +5 -0
  165. package/dist/hooks/useDialog.js +17 -3
  166. package/dist/hooks/useKeyboardGrab.d.ts +58 -0
  167. package/dist/hooks/useKeyboardGrab.js +118 -0
  168. package/dist/hooks/useOptimisticValue.d.ts +51 -0
  169. package/dist/hooks/useOptimisticValue.js +61 -0
  170. package/dist/hooks/useReducedMotion.d.ts +4 -1
  171. package/dist/hooks/useReducedMotion.js +7 -1
  172. package/dist/hooks/useResolvedFilterLabels.d.ts +15 -0
  173. package/dist/hooks/useResolvedFilterLabels.js +34 -0
  174. package/dist/hooks/useSlidingIndicator.d.ts +18 -0
  175. package/dist/hooks/useSlidingIndicator.js +80 -0
  176. package/dist/hooks/useVirtualRows.d.ts +21 -2
  177. package/dist/hooks/useVirtualRows.js +30 -3
  178. package/dist/icons/Icon.d.ts +1 -1
  179. package/dist/index.cjs +19687 -13423
  180. package/dist/index.d.cts +3419 -651
  181. package/dist/index.d.ts +48 -18
  182. package/dist/index.js +153 -9
  183. package/dist/labels-context.d.ts +93 -3
  184. package/dist/motion.d.ts +11 -0
  185. package/dist/motion.js +35 -0
  186. package/dist/overlay-stack.d.ts +6 -0
  187. package/dist/overlay-stack.js +13 -0
  188. package/dist/qr-model.d.ts +65 -0
  189. package/dist/qr-model.js +323 -0
  190. package/dist/relationship-graph-model.d.ts +92 -3
  191. package/dist/relationship-graph-model.js +134 -15
  192. package/dist/reorder.d.ts +30 -0
  193. package/dist/reorder.js +74 -0
  194. package/dist/rich-text.cjs +46 -4
  195. package/dist/scheduling-resource-model.d.ts +76 -0
  196. package/dist/scheduling-resource-model.js +74 -0
  197. package/dist/search-suggest-model.js +3 -1
  198. package/dist/shell-context.d.ts +11 -0
  199. package/dist/shell-context.js +7 -0
  200. package/dist/time-field-model.d.ts +46 -0
  201. package/dist/time-field-model.js +152 -0
  202. package/dist/toast-store.d.ts +22 -3
  203. package/dist/toast-store.js +26 -9
  204. package/package.json +2 -1
package/dist/emoji.cjs CHANGED
@@ -169,12 +169,17 @@ function removeAnchorName(el, name) {
169
169
  else el.style.removeProperty("anchor-name");
170
170
  }
171
171
  function translationOf(el) {
172
- const t = getComputedStyle(el).transform;
172
+ const style = getComputedStyle(el);
173
+ const t = style.transform;
173
174
  if (!t || t === "none" || typeof DOMMatrixReadOnly === "undefined") return { x: 0, y: 0 };
174
175
  try {
175
176
  const m = new DOMMatrixReadOnly(t);
177
+ const [ox = 0, oy = 0] = style.transformOrigin.split(" ").map((v) => parseFloat(v) || 0);
178
+ const cx2 = el.offsetWidth / 2 - ox;
179
+ const cy = el.offsetHeight / 2 - oy;
180
+ const p = m.transformPoint({ x: cx2, y: cy });
176
181
  const z = zoomOf(el);
177
- return { x: m.m41 * z, y: m.m42 * z };
182
+ return { x: (p.x - cx2) * z, y: (p.y - cy) * z };
178
183
  } catch {
179
184
  return { x: 0, y: 0 };
180
185
  }
@@ -271,6 +276,13 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
271
276
  const measured = size.width > 0 || size.height > 0;
272
277
  if (measured && session.current) side.current = pos.side;
273
278
  if (floating.dataset.placement !== pos.placement) floating.dataset.placement = pos.placement;
279
+ if (measured) {
280
+ const clamp2 = (v, max) => Math.min(Math.max(v, 0), max);
281
+ const ox = pos.side === "left" ? size.width : pos.side === "right" ? 0 : clamp2(a.x + a.width / 2 - pos.x, size.width);
282
+ const oy = pos.side === "top" ? size.height : pos.side === "bottom" ? 0 : clamp2(a.y + a.height / 2 - pos.y, size.height);
283
+ const origin = `${Math.round(ox / zoom)}px ${Math.round(oy / zoom)}px`;
284
+ if (s.getPropertyValue("--uix-anchor-origin") !== origin) s.setProperty("--uix-anchor-origin", origin);
285
+ }
274
286
  const writeFixed = (at) => {
275
287
  const left2 = `${Math.round(at.x / zoom)}px`;
276
288
  const top2 = `${Math.round(at.y / zoom)}px`;
@@ -367,6 +379,33 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
367
379
  return reposition;
368
380
  }
369
381
 
382
+ // src/motion.ts
383
+ var KEY_OPENED = "data-uix-key-opened";
384
+ var keyboard = false;
385
+ var watching = false;
386
+ function watchInputModality() {
387
+ if (watching || typeof document === "undefined") return;
388
+ watching = true;
389
+ document.addEventListener("keydown", (event) => {
390
+ if (!["Shift", "Control", "Alt", "Meta"].includes(event.key)) keyboard = true;
391
+ }, true);
392
+ document.addEventListener("pointerdown", () => {
393
+ keyboard = false;
394
+ }, true);
395
+ }
396
+ function markKeyOpened(el) {
397
+ if (!el) return;
398
+ if (keyboard) el.setAttribute(KEY_OPENED, "");
399
+ else el.removeAttribute(KEY_OPENED);
400
+ }
401
+ function tokenMs(name, fallback, el) {
402
+ if (typeof getComputedStyle === "undefined" || typeof document === "undefined") return fallback;
403
+ const raw = getComputedStyle(el ?? document.documentElement).getPropertyValue(name).trim();
404
+ const m = /^(-?\d*\.?\d+)(ms|s)$/.exec(raw);
405
+ if (!m) return fallback;
406
+ return m[2] === "s" ? Number(m[1]) * 1e3 : Number(m[1]);
407
+ }
408
+
370
409
  // src/components/Popover.tsx
371
410
  var import_jsx_runtime = require("react/jsx-runtime");
372
411
  var useIsomorphicLayoutEffect2 = typeof document !== "undefined" ? import_react2.useLayoutEffect : import_react2.useEffect;
@@ -420,6 +459,14 @@ function Popover({
420
459
  el.hidePopover();
421
460
  } : void 0
422
461
  });
462
+ (0, import_react2.useEffect)(() => {
463
+ watchInputModality();
464
+ const el = ref.current;
465
+ if (!el) return;
466
+ const mark = () => markKeyOpened(el);
467
+ el.addEventListener("beforetoggle", mark);
468
+ return () => el.removeEventListener("beforetoggle", mark);
469
+ }, []);
423
470
  (0, import_react2.useEffect)(() => {
424
471
  const el = ref.current;
425
472
  if (!el || !anchor) return;
@@ -500,6 +547,8 @@ function Popover({
500
547
  };
501
548
  const onKeyDown = (event) => {
502
549
  if (event.key !== "Escape" || !isPopoverOpen(el)) return;
550
+ event.preventDefault();
551
+ event.stopPropagation();
503
552
  window.clearTimeout(timer);
504
553
  const hadFocus = el.contains(el.ownerDocument.activeElement);
505
554
  try {
@@ -554,9 +603,9 @@ function buildEmojiData(locale, compact, messages) {
554
603
  const sorted = [...compact].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
555
604
  for (const item of sorted) {
556
605
  if (item.group === void 0) continue;
557
- const group = groupIndex.get(item.group);
558
- if (group === void 0) continue;
559
- const entry = { emoji: item.unicode, name: item.label, tags: item.tags ?? [], group };
606
+ const group2 = groupIndex.get(item.group);
607
+ if (group2 === void 0) continue;
608
+ const entry = { emoji: item.unicode, name: item.label, tags: item.tags ?? [], group: group2 };
560
609
  emojis.push(entry);
561
610
  names.set(item.unicode, item.label);
562
611
  names.set(item.unicode.replace(/️/g, ""), item.label);
@@ -828,8 +877,8 @@ function EmojiPicker({
828
877
  if (quickPicks?.length) list.push({ key: "quick", title: labels.quickPicks, emojis: [...new Set(quickPicks)] });
829
878
  if (recent.length) list.push({ key: "recent", title: labels.recent, emojis: recent });
830
879
  if (data) {
831
- data.groups.forEach((group, index) => {
832
- list.push({ key: group.key, title: group.name, emojis: data.emojis.filter((e) => e.group === index).map((e) => e.emoji) });
880
+ data.groups.forEach((group2, index) => {
881
+ list.push({ key: group2.key, title: group2.name, emojis: data.emojis.filter((e) => e.group === index).map((e) => e.emoji) });
833
882
  });
834
883
  }
835
884
  return list;
@@ -1122,86 +1171,131 @@ function useKbdPlatform(platform) {
1122
1171
  const detected = (0, import_react5.useSyncExternalStore)(noSubscription, detectPlatform, serverPlatform);
1123
1172
  return platform ?? detected;
1124
1173
  }
1125
- var ARIA_KEYS = {
1126
- Mod: { mac: "Meta", other: "Control" },
1127
- Ctrl: { mac: "Control", other: "Control" },
1128
- Esc: { mac: "Escape", other: "Escape" },
1129
- Del: { mac: "Delete", other: "Delete" },
1130
- " ": { mac: "Space", other: "Space" },
1131
- "+": { mac: "Plus", other: "Plus" }
1132
- };
1133
- function ariaKeyShortcuts(keys, platform) {
1134
- return keys.map((key) => ARIA_KEYS[key]?.[platform] ?? (key.length === 1 ? key.toUpperCase() : key)).join("+");
1135
- }
1136
1174
  var isKeyShortcut = (value) => Array.isArray(value) && value.length > 0 && value.every((key) => typeof key === "string");
1175
+ function spokenKeys(keys, platform, keyLabels) {
1176
+ return keys.map((key) => keyLabels?.[key] ?? KEYS[key]?.[platform]?.[1] ?? key).join("+");
1177
+ }
1137
1178
  function KbdCombo({ keys, platform, keyLabels, className, ...props }) {
1138
1179
  const active = useKbdPlatform(platform);
1139
- const parts = keys.map((key) => {
1140
- const known = KEYS[key]?.[active];
1141
- return { glyph: known?.[0] ?? key, spoken: keyLabels?.[key] ?? known?.[1] ?? key };
1142
- });
1143
1180
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: cx("uix-kbd-combo", className), ...props, children: [
1144
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "uix-visually-hidden", children: parts.map((p) => p.spoken).join("+") }),
1145
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "uix-kbd-combo__keys", "aria-hidden": "true", children: parts.map((p, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("kbd", { className: "uix-kbd", children: p.glyph }, `${keys[i]}-${i}`)) })
1181
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "uix-visually-hidden", children: spokenKeys(keys, active, keyLabels) }),
1182
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "uix-kbd-combo__keys", "aria-hidden": "true", children: keys.map((key, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("kbd", { className: "uix-kbd", children: KEYS[key]?.[active]?.[0] ?? key }, `${key}-${i}`)) })
1146
1183
  ] });
1147
1184
  }
1148
1185
 
1149
1186
  // src/components/Tooltip.tsx
1150
1187
  var import_jsx_runtime6 = require("react/jsx-runtime");
1188
+ var group = { open: 0, lastClosed: -Infinity, current: null };
1189
+ var useIsomorphicLayoutEffect4 = typeof document !== "undefined" ? import_react6.useLayoutEffect : import_react6.useEffect;
1151
1190
  function Tooltip({ label, content, placement = "top", shortcut, className, children, ...props }) {
1152
1191
  const triggerRef = (0, import_react6.useRef)(null);
1153
1192
  const bubbleRef = (0, import_react6.useRef)(null);
1154
1193
  const closeTimer = (0, import_react6.useRef)(null);
1194
+ const openTimer = (0, import_react6.useRef)(null);
1195
+ const shown = (0, import_react6.useRef)(false);
1155
1196
  const [open, setOpen] = (0, import_react6.useState)(false);
1156
1197
  const id = (0, import_react6.useId)();
1157
1198
  const platform = useKbdPlatform();
1158
1199
  const childShortcut = (0, import_react6.isValidElement)(children) ? children.props.shortcut : void 0;
1159
1200
  const own = shortcut ?? (isKeyShortcut(childShortcut) ? childShortcut : void 0);
1160
1201
  const keys = own && own.length > 0 ? own : null;
1161
- const keyshortcuts = keys ? ariaKeyShortcuts(keys, platform) : void 0;
1162
1202
  useAnchoredPosition(triggerRef, bubbleRef, { open, placement, offset: 6 });
1203
+ (0, import_react6.useEffect)(() => {
1204
+ watchInputModality();
1205
+ }, []);
1163
1206
  const cancelClose = () => {
1164
1207
  if (closeTimer.current != null) {
1165
1208
  clearTimeout(closeTimer.current);
1166
1209
  closeTimer.current = null;
1167
1210
  }
1168
1211
  };
1169
- const show = () => {
1170
- cancelClose();
1212
+ const cancelOpen = () => {
1213
+ if (openTimer.current != null) {
1214
+ clearTimeout(openTimer.current);
1215
+ openTimer.current = null;
1216
+ }
1217
+ };
1218
+ const reveal = (instant) => {
1219
+ cancelOpen();
1220
+ if (group.current && group.current !== self) group.current.current(true);
1221
+ group.current = self;
1171
1222
  const b = bubbleRef.current;
1172
1223
  if (b && !b.matches(":popover-open")) {
1224
+ b.toggleAttribute("data-uix-instant", instant);
1225
+ markKeyOpened(b);
1173
1226
  try {
1174
1227
  b.showPopover();
1175
1228
  } catch {
1176
1229
  }
1177
1230
  }
1231
+ if (!shown.current) {
1232
+ shown.current = true;
1233
+ group.open += 1;
1234
+ }
1178
1235
  setOpen(true);
1179
1236
  };
1180
- const hide = () => {
1237
+ const show = (via) => {
1181
1238
  cancelClose();
1239
+ if (shown.current) return;
1240
+ const warm = group.open > 0 || performance.now() - group.lastClosed < tokenMs("--uix-delay-tooltip-skip", 300);
1241
+ if (via === "focus" || warm) {
1242
+ reveal(via === "pointer");
1243
+ return;
1244
+ }
1245
+ if (openTimer.current == null) openTimer.current = window.setTimeout(() => reveal(false), tokenMs("--uix-delay-tooltip", 400));
1246
+ };
1247
+ const hide = (instant = false) => {
1248
+ cancelClose();
1249
+ cancelOpen();
1182
1250
  const b = bubbleRef.current;
1183
1251
  if (b && b.matches(":popover-open")) {
1252
+ b.toggleAttribute("data-uix-instant", instant);
1184
1253
  try {
1185
1254
  b.hidePopover();
1186
1255
  } catch {
1187
1256
  }
1188
1257
  }
1258
+ if (shown.current) {
1259
+ shown.current = false;
1260
+ group.open -= 1;
1261
+ group.lastClosed = performance.now();
1262
+ }
1263
+ if (group.current === self) group.current = null;
1189
1264
  setOpen(false);
1190
1265
  };
1266
+ const self = (0, import_react6.useRef)(hide);
1267
+ self.current = hide;
1191
1268
  const scheduleHide = () => {
1192
1269
  cancelClose();
1193
- closeTimer.current = window.setTimeout(hide, 100);
1270
+ closeTimer.current = window.setTimeout(() => {
1271
+ closeTimer.current = null;
1272
+ try {
1273
+ if (triggerRef.current?.matches(":hover")) return;
1274
+ } catch {
1275
+ }
1276
+ hide();
1277
+ }, 100);
1194
1278
  };
1195
- (0, import_react6.useEffect)(() => {
1196
- if (!open) return;
1279
+ useIsomorphicLayoutEffect4(() => {
1280
+ if (!open) return void 0;
1197
1281
  const onKey = (e) => {
1198
- if (e.key === "Escape") hide();
1282
+ if (e.key !== "Escape") return;
1283
+ e.preventDefault();
1284
+ e.stopPropagation();
1285
+ hide();
1199
1286
  };
1200
- document.addEventListener("keydown", onKey);
1201
- return () => document.removeEventListener("keydown", onKey);
1287
+ document.addEventListener("keydown", onKey, true);
1288
+ return () => document.removeEventListener("keydown", onKey, true);
1202
1289
  }, [open]);
1203
1290
  (0, import_react6.useEffect)(() => () => {
1204
1291
  if (closeTimer.current != null) clearTimeout(closeTimer.current);
1292
+ if (openTimer.current != null) clearTimeout(openTimer.current);
1293
+ if (shown.current) {
1294
+ shown.current = false;
1295
+ group.open -= 1;
1296
+ group.lastClosed = performance.now();
1297
+ }
1298
+ if (group.current === self) group.current = null;
1205
1299
  const b = bubbleRef.current;
1206
1300
  if (b && b.matches(":popover-open")) {
1207
1301
  try {
@@ -1211,30 +1305,27 @@ function Tooltip({ label, content, placement = "top", shortcut, className, child
1211
1305
  }
1212
1306
  }, []);
1213
1307
  const bubbleAttr = { popover: "manual" };
1308
+ const text = content ?? label;
1309
+ const description = keys ? [text != null ? `${id}-text` : null, `${id}-keys`].filter(Boolean).join(" ") : id;
1214
1310
  const single = (0, import_react6.isValidElement)(children);
1215
1311
  const child = single ? (0, import_react6.cloneElement)(children, {
1216
- "aria-describedby": [children.props["aria-describedby"], id].filter(Boolean).join(" "),
1217
- ...keyshortcuts ? { "aria-keyshortcuts": keyshortcuts } : {}
1312
+ "aria-describedby": [children.props["aria-describedby"], description].filter(Boolean).join(" ")
1218
1313
  }) : children;
1219
1314
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1220
1315
  "span",
1221
1316
  {
1222
1317
  ref: triggerRef,
1223
1318
  className: cx("uix-tooltip", className),
1224
- "aria-describedby": single ? void 0 : id,
1225
- "aria-keyshortcuts": single ? void 0 : keyshortcuts,
1319
+ "aria-describedby": single ? void 0 : description,
1226
1320
  tabIndex: single ? void 0 : 0,
1227
- onMouseEnter: show,
1321
+ onMouseEnter: () => show("pointer"),
1228
1322
  onMouseLeave: scheduleHide,
1229
- onFocus: show,
1230
- onBlur: hide,
1231
- onKeyDown: (e) => {
1232
- if (e.key === "Escape") hide();
1233
- },
1323
+ onFocus: () => show("focus"),
1324
+ onBlur: () => hide(),
1234
1325
  ...props,
1235
1326
  children: [
1236
1327
  child,
1237
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1328
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1238
1329
  "span",
1239
1330
  {
1240
1331
  ref: bubbleRef,
@@ -1244,10 +1335,13 @@ function Tooltip({ label, content, placement = "top", shortcut, className, child
1244
1335
  onMouseEnter: cancelClose,
1245
1336
  onMouseLeave: scheduleHide,
1246
1337
  ...bubbleAttr,
1247
- children: [
1248
- content ?? label,
1249
- keys && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "uix-tooltip__shortcut", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KbdCombo, { keys: [...keys], platform }) })
1250
- ]
1338
+ children: keys ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1339
+ text != null && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { id: `${id}-text`, children: text }),
1340
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "uix-tooltip__shortcut", children: [
1341
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { id: `${id}-keys`, className: "uix-visually-hidden", children: spokenKeys(keys, platform) }),
1342
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KbdCombo, { keys: [...keys], platform, "aria-hidden": "true" })
1343
+ ] })
1344
+ ] }) : text
1251
1345
  }
1252
1346
  )
1253
1347
  ]
@@ -9,6 +9,12 @@ interface FilterChoice {
9
9
  }
10
10
  type TextFilterOperator = 'contains' | 'not-contains' | 'is' | 'is-not' | 'starts-with';
11
11
  type NumberFilterOperator = 'eq' | 'neq' | 'lt' | 'lte' | 'gt' | 'gte' | 'between';
12
+ /**
13
+ * A date condition (HAR-1832). `on`, `before` and `after` take one day in `from`; `between` takes
14
+ * `from` and `to`, both inclusive. "Before" excludes the day and "after" starts the next day:
15
+ * `dateFilterBounds` turns each into the half-open range a query needs.
16
+ */
17
+ type DateFilterOperator = 'on' | 'before' | 'after' | 'between';
12
18
  type FilterValue = {
13
19
  kind: 'enum';
14
20
  values: string[];
@@ -25,12 +31,24 @@ type FilterValue = {
25
31
  kind: 'date-range';
26
32
  from?: string;
27
33
  to?: string;
34
+ }
35
+ /** A date with a condition (HAR-1832); `YYYY-MM-DD`. A saved `between` stays `between`, even of one day. */
36
+ | {
37
+ kind: 'date';
38
+ operator: DateFilterOperator;
39
+ from?: string;
40
+ to?: string;
28
41
  } | {
29
42
  kind: 'boolean';
30
43
  value?: boolean;
31
44
  } | {
32
45
  kind: 'reference';
33
46
  values: FilterChoice[];
47
+ }
48
+ /** Free-text values typed in as tags (HAR-1832), e.g. several hostnames; matched by the product. */
49
+ | {
50
+ kind: 'tags';
51
+ values: string[];
34
52
  };
35
53
  type FilterValueKind = FilterValue['kind'];
36
54
  /**
@@ -52,8 +70,15 @@ interface FilterField {
52
70
  options?: readonly FilterChoice[];
53
71
  /** `reference`: finds records to pick; rejected promises show the error state. */
54
72
  onSearch?: (query: string) => Promise<readonly FilterChoice[]>;
73
+ /**
74
+ * `reference`: labels for chosen ids that arrived without one (a saved view restores ids).
75
+ * The editor asks once for every id whose label is missing or equal to the id.
76
+ */
77
+ resolveLabels?: (ids: readonly string[]) => Promise<readonly FilterChoice[]>;
55
78
  /** `text` / `number`: the operators offered, in order. Default: all of the kind. */
56
79
  operators?: readonly (TextFilterOperator | NumberFilterOperator)[];
80
+ /** `date`: the conditions offered, in order. Default: on, before, after, between. */
81
+ dateOperators?: readonly DateFilterOperator[];
57
82
  /** `number`: a unit shown after the value, e.g. "days". */
58
83
  unit?: string;
59
84
  /** `boolean`: words for true and false, e.g. Escalated / Not escalated. Default from labels. */
@@ -80,14 +105,45 @@ interface FilterSummaryLabels {
80
105
  more: string;
81
106
  /** An `enum` value equal to a preset: `{label}` (the preset), `{field}`. Default `{label}`. */
82
107
  preset?: string;
108
+ /** `date` conditions (HAR-1832), `{date}`. Default "{date}", "before {date}", "after {date}". */
109
+ on?: string;
110
+ before?: string;
111
+ after?: string;
83
112
  }
84
113
  declare const DEFAULT_FILTER_SUMMARY_LABELS: FilterSummaryLabels;
85
114
  declare const TEXT_FILTER_OPERATORS: readonly TextFilterOperator[];
86
115
  declare const NUMBER_FILTER_OPERATORS: readonly NumberFilterOperator[];
116
+ declare const DATE_FILTER_OPERATORS: readonly DateFilterOperator[];
87
117
  /** The empty value for a field kind (what Clear resets to). */
88
118
  declare function emptyFilterValue(kind: FilterValueKind): FilterValue;
89
119
  /** True when the value filters nothing (so the chip goes away). */
90
120
  declare function isFilterEmpty(value: FilterValue | undefined): boolean;
121
+ /**
122
+ * A range whose ends were typed the wrong way round (from 10 Oct to 1 Oct, 7 to 2) put in order,
123
+ * so it means what the person meant instead of matching nothing (PR #168 review). Number
124
+ * `between`, date `between` and `date-range`; every other value comes back as it is. Internal:
125
+ * FilterEditor applies through it, `dateFilterBounds` and `summarizeFilter` read through it.
126
+ */
127
+ declare function orderFilterRange<V extends FilterValue | undefined>(value: V): V;
128
+ /**
129
+ * The half-open range a `date` filter means, for a query (HAR-1832): `{ from }` is the first day
130
+ * included, `{ until }` the first day NOT included, either may be missing. "On 5 Oct" is
131
+ * `[5 Oct, 6 Oct)`, "before 5 Oct" is `[…, 5 Oct)`, "after 5 Oct" is `[6 Oct, …)`, "between 1 and
132
+ * 5 Oct" is `[1 Oct, 6 Oct)`. A `date-range` value reads as `between` with optional ends. Empty
133
+ * values give `{}`.
134
+ */
135
+ declare function dateFilterBounds(input: FilterValue | undefined): {
136
+ from?: string;
137
+ until?: string;
138
+ };
139
+ /** True for a chosen reference whose label is missing or only repeats its id (restored from an id). */
140
+ declare const isUnresolvedChoice: (choice: FilterChoice) => boolean;
141
+ /**
142
+ * Fills in the labels of a `reference` value restored from ids (HAR-1832), with
143
+ * `field.resolveLabels`. Other values, and values whose labels are all known, come back as they
144
+ * are. Ids the resolver does not return keep the label they had.
145
+ */
146
+ declare function resolveFilterLabels(field: FilterField, value: FilterValue | undefined): Promise<FilterValue | undefined>;
91
147
  /**
92
148
  * The first of `field.presets` whose values equal the value's as a set, or undefined. Only an
93
149
  * `enum` value can match a preset.
@@ -104,9 +160,9 @@ interface SummarizeFilterOptions {
104
160
  }
105
161
  /**
106
162
  * One line for the filter chip, e.g. "State: Open, Pending", "Priority ≥ 2", "Created:
107
- * 01.10.2026 – 05.10.2026", "Escalated: Yes". An enum value equal to one of `field.presets`
163
+ * 01.10.2026 – 05.10.2026", "Created: before 05.10.2026", "Host: db-01, db-02 +1", "Escalated: Yes". An enum value equal to one of `field.presets`
108
164
  * gives that preset's label (the `preset` label). Empty values give an empty string.
109
165
  */
110
- declare function summarizeFilter(field: FilterField, value: FilterValue | undefined, options?: SummarizeFilterOptions): string;
166
+ declare function summarizeFilter(field: FilterField, input: FilterValue | undefined, options?: SummarizeFilterOptions): string;
111
167
 
112
- export { DEFAULT_FILTER_SUMMARY_LABELS, type FilterChoice, type FilterField, type FilterPreset, type FilterSummaryLabels, type FilterValue, type FilterValueKind, NUMBER_FILTER_OPERATORS, type NumberFilterOperator, type SummarizeFilterOptions, TEXT_FILTER_OPERATORS, type TextFilterOperator, emptyFilterValue, isFilterEmpty, matchFilterPreset, summarizeFilter };
168
+ export { DATE_FILTER_OPERATORS, DEFAULT_FILTER_SUMMARY_LABELS, type DateFilterOperator, type FilterChoice, type FilterField, type FilterPreset, type FilterSummaryLabels, type FilterValue, type FilterValueKind, NUMBER_FILTER_OPERATORS, type NumberFilterOperator, type SummarizeFilterOptions, TEXT_FILTER_OPERATORS, type TextFilterOperator, dateFilterBounds, emptyFilterValue, isFilterEmpty, isUnresolvedChoice, matchFilterPreset, orderFilterRange, resolveFilterLabels, summarizeFilter };
@@ -19,10 +19,14 @@ const DEFAULT_FILTER_SUMMARY_LABELS = {
19
19
  from: "from {from}",
20
20
  until: "until {to}",
21
21
  more: "{first} +{count}",
22
- preset: "{label}"
22
+ preset: "{label}",
23
+ on: "{date}",
24
+ before: "before {date}",
25
+ after: "after {date}"
23
26
  };
24
27
  const TEXT_FILTER_OPERATORS = ["contains", "not-contains", "is", "is-not", "starts-with"];
25
28
  const NUMBER_FILTER_OPERATORS = ["eq", "neq", "lt", "lte", "gt", "gte", "between"];
29
+ const DATE_FILTER_OPERATORS = ["on", "before", "after", "between"];
26
30
  function emptyFilterValue(kind) {
27
31
  switch (kind) {
28
32
  case "enum":
@@ -33,10 +37,14 @@ function emptyFilterValue(kind) {
33
37
  return { kind, operator: "eq" };
34
38
  case "date-range":
35
39
  return { kind };
40
+ case "date":
41
+ return { kind, operator: "on" };
36
42
  case "boolean":
37
43
  return { kind };
38
44
  case "reference":
39
45
  return { kind, values: [] };
46
+ case "tags":
47
+ return { kind, values: [] };
40
48
  }
41
49
  }
42
50
  function isFilterEmpty(value) {
@@ -44,6 +52,7 @@ function isFilterEmpty(value) {
44
52
  switch (value.kind) {
45
53
  case "enum":
46
54
  case "reference":
55
+ case "tags":
47
56
  return value.values.length === 0;
48
57
  case "text":
49
58
  return value.text.trim() === "";
@@ -51,10 +60,53 @@ function isFilterEmpty(value) {
51
60
  return value.value === void 0 || value.operator === "between" && value.to === void 0;
52
61
  case "date-range":
53
62
  return !value.from && !value.to;
63
+ case "date":
64
+ return !value.from || value.operator === "between" && !value.to;
54
65
  case "boolean":
55
66
  return value.value === void 0;
56
67
  }
57
68
  }
69
+ function addDays(date, days) {
70
+ const [y, m, d] = date.split("-").map(Number);
71
+ const next = new Date(Date.UTC(y, m - 1, d + days));
72
+ return next.toISOString().slice(0, 10);
73
+ }
74
+ function orderFilterRange(value) {
75
+ if (!value) return value;
76
+ if (value.kind === "number" && value.operator === "between" && value.value !== void 0 && value.to !== void 0 && value.value > value.to) {
77
+ return { ...value, value: value.to, to: value.value };
78
+ }
79
+ if ((value.kind === "date-range" || value.kind === "date" && value.operator === "between") && value.from && value.to && value.from > value.to) {
80
+ return { ...value, from: value.to, to: value.from };
81
+ }
82
+ return value;
83
+ }
84
+ function dateFilterBounds(input) {
85
+ const value = orderFilterRange(input);
86
+ if (value?.kind === "date-range") {
87
+ return { ...value.from ? { from: value.from } : {}, ...value.to ? { until: addDays(value.to, 1) } : {} };
88
+ }
89
+ if (value?.kind !== "date" || isFilterEmpty(value)) return {};
90
+ const day = value.from;
91
+ switch (value.operator) {
92
+ case "on":
93
+ return { from: day, until: addDays(day, 1) };
94
+ case "before":
95
+ return { until: day };
96
+ case "after":
97
+ return { from: addDays(day, 1) };
98
+ case "between":
99
+ return { from: day, until: addDays(value.to, 1) };
100
+ }
101
+ }
102
+ const isUnresolvedChoice = (choice) => !choice.label || choice.label === choice.value;
103
+ async function resolveFilterLabels(field, value) {
104
+ if (value?.kind !== "reference" || !field.resolveLabels) return value;
105
+ const missing = value.values.filter(isUnresolvedChoice).map((v) => v.value);
106
+ if (missing.length === 0) return value;
107
+ const resolved = new Map((await field.resolveLabels(missing)).map((c) => [c.value, c.label]));
108
+ return { kind: "reference", values: value.values.map((v) => resolved.has(v.value) ? { value: v.value, label: resolved.get(v.value) } : v) };
109
+ }
58
110
  function matchFilterPreset(field, value) {
59
111
  if (value?.kind !== "enum" || !field.presets?.length) return void 0;
60
112
  const chosen = new Set(value.values);
@@ -64,7 +116,8 @@ function matchFilterPreset(field, value) {
64
116
  });
65
117
  }
66
118
  const fill = (template, values) => template.replace(/\{(\w+)\}/g, (match, key) => key in values ? String(values[key]) : match);
67
- function summarizeFilter(field, value, options = {}) {
119
+ function summarizeFilter(field, input, options = {}) {
120
+ const value = orderFilterRange(input);
68
121
  if (!value || isFilterEmpty(value)) return "";
69
122
  const labels = { ...DEFAULT_FILTER_SUMMARY_LABELS, ...options.labels, operators: { ...DEFAULT_FILTER_SUMMARY_LABELS.operators, ...options.labels?.operators } };
70
123
  const date = options.formatDate ?? ((d) => d);
@@ -82,22 +135,34 @@ function summarizeFilter(field, value, options = {}) {
82
135
  }
83
136
  case "reference":
84
137
  return `${field.label}: ${list(value.values.map((v) => v.label))}`;
138
+ case "tags":
139
+ return `${field.label}: ${list(value.values)}`;
85
140
  case "text":
86
141
  return `${field.label} ${labels.operators[value.operator]} \u201C${value.text.trim()}\u201D`;
87
142
  case "number":
88
143
  return value.operator === "between" ? `${field.label}: ${fill(labels.range, { from: num(value.value), to: num(value.to) })}` : `${field.label} ${labels.operators[value.operator]} ${num(value.value)}`;
89
144
  case "date-range":
90
145
  return `${field.label}: ${value.from && value.to ? fill(labels.range, { from: date(value.from), to: date(value.to) }) : value.from ? fill(labels.from, { from: date(value.from) }) : fill(labels.until, { to: date(value.to) })}`;
146
+ case "date": {
147
+ if (value.operator === "between") return `${field.label}: ${fill(labels.range, { from: date(value.from), to: date(value.to) })}`;
148
+ const template = labels[value.operator] ?? DEFAULT_FILTER_SUMMARY_LABELS[value.operator];
149
+ return `${field.label}: ${fill(template, { date: date(value.from) })}`;
150
+ }
91
151
  case "boolean":
92
152
  return `${field.label}: ${value.value ? field.trueLabel ?? labels.yes : field.falseLabel ?? labels.no}`;
93
153
  }
94
154
  }
95
155
  export {
156
+ DATE_FILTER_OPERATORS,
96
157
  DEFAULT_FILTER_SUMMARY_LABELS,
97
158
  NUMBER_FILTER_OPERATORS,
98
159
  TEXT_FILTER_OPERATORS,
160
+ dateFilterBounds,
99
161
  emptyFilterValue,
100
162
  isFilterEmpty,
163
+ isUnresolvedChoice,
101
164
  matchFilterPreset,
165
+ orderFilterRange,
166
+ resolveFilterLabels,
102
167
  summarizeFilter
103
168
  };
@@ -29,12 +29,17 @@ function removeAnchorName(el, name) {
29
29
  else el.style.removeProperty("anchor-name");
30
30
  }
31
31
  function translationOf(el) {
32
- const t = getComputedStyle(el).transform;
32
+ const style = getComputedStyle(el);
33
+ const t = style.transform;
33
34
  if (!t || t === "none" || typeof DOMMatrixReadOnly === "undefined") return { x: 0, y: 0 };
34
35
  try {
35
36
  const m = new DOMMatrixReadOnly(t);
37
+ const [ox = 0, oy = 0] = style.transformOrigin.split(" ").map((v) => parseFloat(v) || 0);
38
+ const cx = el.offsetWidth / 2 - ox;
39
+ const cy = el.offsetHeight / 2 - oy;
40
+ const p = m.transformPoint({ x: cx, y: cy });
36
41
  const z = zoomOf(el);
37
- return { x: m.m41 * z, y: m.m42 * z };
42
+ return { x: (p.x - cx) * z, y: (p.y - cy) * z };
38
43
  } catch {
39
44
  return { x: 0, y: 0 };
40
45
  }
@@ -131,6 +136,13 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
131
136
  const measured = size.width > 0 || size.height > 0;
132
137
  if (measured && session.current) side.current = pos.side;
133
138
  if (floating.dataset.placement !== pos.placement) floating.dataset.placement = pos.placement;
139
+ if (measured) {
140
+ const clamp = (v, max) => Math.min(Math.max(v, 0), max);
141
+ const ox = pos.side === "left" ? size.width : pos.side === "right" ? 0 : clamp(a.x + a.width / 2 - pos.x, size.width);
142
+ const oy = pos.side === "top" ? size.height : pos.side === "bottom" ? 0 : clamp(a.y + a.height / 2 - pos.y, size.height);
143
+ const origin = `${Math.round(ox / zoom)}px ${Math.round(oy / zoom)}px`;
144
+ if (s.getPropertyValue("--uix-anchor-origin") !== origin) s.setProperty("--uix-anchor-origin", origin);
145
+ }
134
146
  const writeFixed = (at) => {
135
147
  const left2 = `${Math.round(at.x / zoom)}px`;
136
148
  const top2 = `${Math.round(at.y / zoom)}px`;
@@ -0,0 +1,33 @@
1
+ import { RefObject } from 'react';
2
+
3
+ interface ChangeHighlightOptions {
4
+ /**
5
+ * Said through the page's polite live region when the value changes, e.g. "Status changed to
6
+ * Resolved by A. Novak". The highlight is the signal and its place is the location; this is
7
+ * the description for people who cannot see either (motion standard §2.5).
8
+ */
9
+ announce?: string;
10
+ /** Turn the highlight off, e.g. while the person is editing the value themselves. */
11
+ disabled?: boolean;
12
+ }
13
+ /**
14
+ * Flash `.uix-changed` on an element when `value` changes after mount — never on the first
15
+ * render (HAR-2585). A second change restarts the highlight. Compare by `Object.is`: pass a
16
+ * primitive (a status, a count, an `updatedAt`), not a fresh object.
17
+ *
18
+ * ```tsx
19
+ * const ref = useChangeHighlight<HTMLTableCellElement>(row.status, { announce: `Status: ${row.status}` });
20
+ * <Td ref={ref}>{row.status}</Td>
21
+ * ```
22
+ *
23
+ * The hook sets `data-uix-changed` on the element (React never renders that attribute, so a
24
+ * re-render cannot clear it) and removes it when the animation ends.
25
+ */
26
+ declare function useChangeHighlight<T extends HTMLElement = HTMLElement>(value: unknown, { announce, disabled }?: ChangeHighlightOptions): RefObject<T | null>;
27
+ /**
28
+ * Flash `.uix-changed` on `el` now (restarting a highlight already running). For changes that
29
+ * do not flow through a React value, e.g. a row a live update replaced.
30
+ */
31
+ declare function flashChanged(el: HTMLElement): void;
32
+
33
+ export { type ChangeHighlightOptions, flashChanged, useChangeHighlight };