@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
package/dist/emoji.cjs CHANGED
@@ -83,7 +83,7 @@ function fitsOn(side, a, f, vp, offset, padding) {
83
83
  return spaceOn(side, a, vp, offset) >= need + padding;
84
84
  }
85
85
  function computePosition(anchor, floating, viewport, options = {}) {
86
- const { placement = "bottom-start", offset = 6, padding = 8, flip = true, shift = true, stickySide } = options;
86
+ const { placement = "bottom-start", offset = 6, padding = 8, flip = true, shift = true, shiftMainAxis = true, stickySide } = options;
87
87
  const { side: preferred, align } = parsePlacement(placement);
88
88
  let side = preferred;
89
89
  const sticky = stickySide === preferred || stickySide === OPPOSITE[preferred] ? stickySide : void 0;
@@ -116,12 +116,27 @@ function computePosition(anchor, floating, viewport, options = {}) {
116
116
  y = align === "start" ? start : align === "end" ? end : center;
117
117
  if (shift) y = clamp(y, padding, Math.max(padding, viewport.height - floating.height - padding));
118
118
  }
119
+ const mainAxisNatural = horizontal ? y : x;
120
+ const anchorInView = horizontal ? anchor.y + anchor.height > 0 && anchor.y < viewport.height : anchor.x + anchor.width > 0 && anchor.x < viewport.width;
121
+ let mainAxisRange = null;
122
+ if (shiftMainAxis && anchorInView) {
123
+ const extent = horizontal ? viewport.height - floating.height : viewport.width - floating.width;
124
+ mainAxisRange = [padding, Math.max(padding, extent - padding)];
125
+ if (horizontal) y = clamp(y, mainAxisRange[0], mainAxisRange[1]);
126
+ else x = clamp(x, mainAxisRange[0], mainAxisRange[1]);
127
+ }
119
128
  return {
120
129
  x,
121
130
  y,
122
131
  placement: align === "center" ? side : `${side}-${align}`,
123
132
  side,
124
- align
133
+ align,
134
+ mainAxisNatural,
135
+ mainAxisRange,
136
+ available: {
137
+ width: Math.max(0, viewport.width - 2 * padding),
138
+ height: Math.max(0, viewport.height - 2 * padding)
139
+ }
125
140
  };
126
141
  }
127
142
 
@@ -140,6 +155,7 @@ function supportsAnchoring() {
140
155
  }
141
156
  return anchoringSupported;
142
157
  }
158
+ var ANCHOR_BAND = 64;
143
159
  var anchorCount = 0;
144
160
  var namesOf = (value) => value.split(",").map((n) => n.trim()).filter((n) => n.startsWith("--"));
145
161
  function addAnchorName(el, name) {
@@ -164,9 +180,12 @@ function translationOf(el) {
164
180
  }
165
181
  var signed = (n) => `${n < 0 ? "-" : "+"} ${Math.round(Math.abs(n) * 100) / 100}px`;
166
182
  var prepared = /* @__PURE__ */ new WeakSet();
167
- function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8 }) {
168
- const opts = (0, import_react.useRef)({ placement, offset, padding });
169
- opts.current = { placement, offset, padding };
183
+ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8, shiftMainAxis = true, capHeight = false, onAnchorHidden }) {
184
+ const opts = (0, import_react.useRef)({ placement, offset, padding, shiftMainAxis, capHeight });
185
+ opts.current = { placement, offset, padding, shiftMainAxis, capHeight };
186
+ const onAnchorHiddenRef = (0, import_react.useRef)(onAnchorHidden);
187
+ onAnchorHiddenRef.current = onAnchorHidden;
188
+ const heightCap = (0, import_react.useRef)(null);
170
189
  const session = (0, import_react.useRef)(false);
171
190
  const side = (0, import_react.useRef)(null);
172
191
  const written = (0, import_react.useRef)("");
@@ -190,6 +209,15 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
190
209
  }
191
210
  removeAnchorName(current.anchor, current.name);
192
211
  }, [floatingRef]);
212
+ const uncap = (0, import_react.useCallback)(() => {
213
+ const cap = heightCap.current;
214
+ if (!cap) return;
215
+ heightCap.current = null;
216
+ const floating = floatingRef.current;
217
+ if (!floating) return;
218
+ floating.style.maxHeight = cap.inlineMaxHeight;
219
+ if (cap.setOverflow) floating.style.overflowY = cap.inlineOverflowY;
220
+ }, [floatingRef]);
193
221
  const reposition = (0, import_react.useCallback)(() => {
194
222
  const anchorEl = resolveAnchor(anchor);
195
223
  const floating = floatingRef.current;
@@ -202,14 +230,36 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
202
230
  prepared.add(floating);
203
231
  written.current = "";
204
232
  }
233
+ const { placement: p, offset: o, padding: pad, shiftMainAxis: keepInView, capHeight: cap } = opts.current;
234
+ if (cap) {
235
+ if (!heightCap.current) {
236
+ const own = getComputedStyle(floating);
237
+ const setOverflow = !own.overflowY || own.overflowY === "visible";
238
+ heightCap.current = {
239
+ inlineMaxHeight: s.maxHeight,
240
+ inlineOverflowY: s.overflowY,
241
+ ownMaxHeight: own.maxHeight && own.maxHeight !== "none" ? own.maxHeight : "",
242
+ setOverflow,
243
+ written: ""
244
+ };
245
+ if (setOverflow) s.overflowY = "auto";
246
+ }
247
+ const room = `${Math.max(0, window.innerHeight - 2 * pad)}px`;
248
+ const max = heightCap.current.ownMaxHeight ? `min(${room}, ${heightCap.current.ownMaxHeight})` : room;
249
+ if (max !== heightCap.current.written) {
250
+ s.maxHeight = max;
251
+ heightCap.current.written = max;
252
+ }
253
+ } else if (heightCap.current) {
254
+ uncap();
255
+ }
205
256
  const a = anchorEl.getBoundingClientRect();
206
257
  const size = { width: floating.offsetWidth, height: floating.offsetHeight };
207
- const { placement: p, offset: o, padding: pad } = opts.current;
208
258
  const pos = computePosition(
209
259
  { x: a.x, y: a.y, width: a.width, height: a.height },
210
260
  size,
211
261
  { width: window.innerWidth, height: window.innerHeight },
212
- { placement: p, offset: o, padding: pad, stickySide: side.current ?? void 0 }
262
+ { placement: p, offset: o, padding: pad, shiftMainAxis: keepInView, stickySide: side.current ?? void 0 }
213
263
  );
214
264
  const measured = size.width > 0 || size.height > 0;
215
265
  if (measured && session.current) side.current = pos.side;
@@ -223,7 +273,10 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
223
273
  s.top = top2;
224
274
  written.current = key2;
225
275
  };
226
- if (anchoringFailed.current || !supportsAnchoring()) {
276
+ const limit = pos.mainAxisRange;
277
+ const nearLimit = limit !== null && (pos.mainAxisNatural < limit[0] + ANCHOR_BAND || pos.mainAxisNatural > limit[1] - ANCHOR_BAND);
278
+ if (anchoringFailed.current || nearLimit || !supportsAnchoring()) {
279
+ if (anchoring.current) release(false);
227
280
  writeFixed(pos);
228
281
  return;
229
282
  }
@@ -260,7 +313,7 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
260
313
  writeFixed(pos);
261
314
  }
262
315
  }
263
- }, [anchor, floatingRef, release]);
316
+ }, [anchor, floatingRef, release, uncap]);
264
317
  useIsomorphicLayoutEffect(() => {
265
318
  if (!open) return;
266
319
  session.current = true;
@@ -289,8 +342,20 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
289
342
  side.current = null;
290
343
  anchoringFailed.current = false;
291
344
  release(true);
345
+ uncap();
292
346
  };
293
- }, [open, reposition, anchor, floatingRef, release]);
347
+ }, [open, reposition, anchor, floatingRef, release, uncap]);
348
+ const watchAnchor = open && !!onAnchorHidden;
349
+ (0, import_react.useEffect)(() => {
350
+ const anchorEl = resolveAnchor(anchor);
351
+ if (!watchAnchor || !anchorEl || typeof IntersectionObserver !== "function") return;
352
+ const observer = new IntersectionObserver((entries) => {
353
+ const latest = entries[entries.length - 1];
354
+ if (latest && !latest.isIntersecting) onAnchorHiddenRef.current?.();
355
+ });
356
+ observer.observe(anchorEl);
357
+ return () => observer.disconnect();
358
+ }, [watchAnchor, anchor]);
294
359
  (0, import_react.useEffect)(() => () => release(false), [release]);
295
360
  return reposition;
296
361
  }
@@ -312,16 +377,22 @@ var settle = (el) => {
312
377
  el.removeAttribute(PLACING);
313
378
  };
314
379
  function Popover({
315
- popover = "auto",
380
+ popover: popoverProp,
316
381
  anchor,
317
382
  placement = "bottom-start",
318
383
  offset = 6,
319
384
  closeWhenAnchorHidden = false,
320
385
  onAnchorHidden,
386
+ capHeight = false,
387
+ openOnHover = false,
321
388
  className,
322
389
  children,
323
390
  ...props
324
391
  }) {
392
+ const hover = !!openOnHover && !!anchor;
393
+ const popover = popoverProp ?? (hover ? "manual" : "auto");
394
+ const openDelay = typeof openOnHover === "object" ? openOnHover.openDelay ?? 300 : 300;
395
+ const closeDelay = typeof openOnHover === "object" ? openOnHover.closeDelay ?? 150 : 150;
325
396
  const ref = (0, import_react2.useRef)(null);
326
397
  const [open, setOpen] = (0, import_react2.useState)(false);
327
398
  const openRef = (0, import_react2.useRef)(false);
@@ -331,7 +402,16 @@ function Popover({
331
402
  const reposition = useAnchoredPosition(anchor ?? null, ref, {
332
403
  open: open && !!anchor,
333
404
  placement,
334
- offset
405
+ offset,
406
+ capHeight,
407
+ onAnchorHidden: closeWhenAnchorHidden ? () => {
408
+ const el = ref.current;
409
+ if (!el || !isPopoverOpen(el)) return;
410
+ onAnchorHiddenRef.current?.();
411
+ const active = el.ownerDocument.activeElement;
412
+ if (active && el.contains(active)) active.blur();
413
+ el.hidePopover();
414
+ } : void 0
335
415
  });
336
416
  (0, import_react2.useEffect)(() => {
337
417
  const el = ref.current;
@@ -366,25 +446,87 @@ function Popover({
366
446
  el.removeAttribute(PLACING);
367
447
  };
368
448
  }, [anchor, reposition]);
449
+ (0, import_react2.useEffect)(() => {
450
+ const el = ref.current;
451
+ const anchorEl = resolveAnchor(anchor);
452
+ if (!hover || !el || !anchorEl) return;
453
+ let timer;
454
+ const inside = (node) => !!node && (anchorEl.contains(node) || el.contains(node));
455
+ const stillWanted = () => {
456
+ try {
457
+ return anchorEl.matches(":hover") || el.matches(":hover") || inside(el.ownerDocument.activeElement);
458
+ } catch {
459
+ return false;
460
+ }
461
+ };
462
+ const show = (delay) => {
463
+ window.clearTimeout(timer);
464
+ timer = window.setTimeout(() => {
465
+ if (!isPopoverOpen(el)) {
466
+ try {
467
+ el.showPopover?.();
468
+ } catch {
469
+ }
470
+ }
471
+ }, delay);
472
+ };
473
+ const hide = (delay) => {
474
+ window.clearTimeout(timer);
475
+ timer = window.setTimeout(() => {
476
+ if (isPopoverOpen(el) && !stillWanted()) {
477
+ try {
478
+ el.hidePopover?.();
479
+ } catch {
480
+ }
481
+ }
482
+ }, delay);
483
+ };
484
+ const onEnter = (event) => {
485
+ if (event.pointerType !== "touch") show(openDelay);
486
+ };
487
+ const onLeave = (event) => {
488
+ if (event.pointerType !== "touch") hide(closeDelay);
489
+ };
490
+ const onFocusIn = () => show(0);
491
+ const onFocusOut = (event) => {
492
+ if (!inside(event.relatedTarget)) hide(closeDelay);
493
+ };
494
+ const onKeyDown = (event) => {
495
+ if (event.key !== "Escape" || !isPopoverOpen(el)) return;
496
+ window.clearTimeout(timer);
497
+ const hadFocus = el.contains(el.ownerDocument.activeElement);
498
+ try {
499
+ el.hidePopover?.();
500
+ } catch {
501
+ }
502
+ if (hadFocus) {
503
+ anchorEl.removeEventListener("focusin", onFocusIn);
504
+ anchorEl.focus();
505
+ anchorEl.addEventListener("focusin", onFocusIn);
506
+ }
507
+ };
508
+ for (const node of [anchorEl, el]) {
509
+ node.addEventListener("pointerenter", onEnter);
510
+ node.addEventListener("pointerleave", onLeave);
511
+ node.addEventListener("focusout", onFocusOut);
512
+ node.addEventListener("keydown", onKeyDown);
513
+ }
514
+ anchorEl.addEventListener("focusin", onFocusIn);
515
+ return () => {
516
+ window.clearTimeout(timer);
517
+ for (const node of [anchorEl, el]) {
518
+ node.removeEventListener("pointerenter", onEnter);
519
+ node.removeEventListener("pointerleave", onLeave);
520
+ node.removeEventListener("focusout", onFocusOut);
521
+ node.removeEventListener("keydown", onKeyDown);
522
+ }
523
+ anchorEl.removeEventListener("focusin", onFocusIn);
524
+ };
525
+ }, [hover, anchor, openDelay, closeDelay]);
369
526
  useIsomorphicLayoutEffect2(() => {
370
527
  openRef.current = open;
371
528
  if (open && ref.current) settle(ref.current);
372
529
  }, [open]);
373
- (0, import_react2.useEffect)(() => {
374
- const el = ref.current;
375
- const anchorEl = resolveAnchor(anchor);
376
- if (!closeWhenAnchorHidden || !open || !el || !anchorEl || typeof IntersectionObserver !== "function") return;
377
- const observer = new IntersectionObserver((entries) => {
378
- const latest = entries[entries.length - 1];
379
- if (!latest || latest.isIntersecting || !isPopoverOpen(el)) return;
380
- onAnchorHiddenRef.current?.();
381
- const active = el.ownerDocument.activeElement;
382
- if (active && el.contains(active)) active.blur();
383
- el.hidePopover();
384
- });
385
- observer.observe(anchorEl);
386
- return () => observer.disconnect();
387
- }, [closeWhenAnchorHidden, open, anchor]);
388
530
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref, className: cx("uix-popover", className), ...popoverAttr, ...props, children });
389
531
  }
390
532
 
package/dist/emoji.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { E as EmojiPickerLabels, a as EmojiLocale, b as EmojiDataLoader } from './emoji-image-6mPZ15Zy.cjs';
2
- export { C as CompactEmoji, D as DEFAULT_EMOJI_PICKER_LABELS, c as EMOJI_GRID_COLUMNS, d as EmojiData, e as EmojiEntry, f as EmojiGroup, g as EmojiMessages, h as EmojiPicker, i as EmojiPickerProps, j as buildEmojiData, k as canRenderEmoji, l as emojiImageFileName, m as loadEmojiData, n as normalizeEmojiImageBaseUrl, s as searchEmoji } from './emoji-image-6mPZ15Zy.cjs';
3
- export { A as Align, P as Placement, S as Side } from './overlay-position-Cjv5E7mn.cjs';
1
+ import { E as EmojiPickerLabels, a as EmojiLocale, b as EmojiDataLoader } from './emoji-image-DjCKpdwE.cjs';
2
+ export { C as CompactEmoji, D as DEFAULT_EMOJI_PICKER_LABELS, c as EMOJI_GRID_COLUMNS, d as EmojiData, e as EmojiEntry, f as EmojiGroup, g as EmojiMessages, h as EmojiPicker, i as EmojiPickerProps, j as buildEmojiData, k as canRenderEmoji, l as emojiImageFileName, m as loadEmojiData, n as normalizeEmojiImageBaseUrl, s as searchEmoji } from './emoji-image-DjCKpdwE.cjs';
3
+ export { A as Align, P as Placement, S as Side } from './overlay-position-2Afke51l.cjs';
4
4
  import * as react from 'react';
5
5
 
6
6
  interface ReactionSummary {
@@ -1,6 +1,25 @@
1
1
  /** File helpers shared by `FileUpload` and `Attachment` (HAR-984/985). Pure. */
2
- /** "1.2 MB", "340 KB", "12 B" in the locale's number format (base 1000, like file managers). */
3
- declare function formatFileSize(bytes: number, locale?: string): string;
2
+ interface FileSizeOptions {
3
+ /**
4
+ * `1000` (default): decimal, as file managers on macOS and most of the web show sizes
5
+ * (1 MB = 1,000,000 bytes). `1024`: binary, as Windows and many servers count them
6
+ * (1 MB = 1,048,576 bytes) — use it when the rest of the product does, so one file never
7
+ * shows two sizes (HAR-1630).
8
+ */
9
+ base?: 1000 | 1024;
10
+ /**
11
+ * The five unit labels, bytes first. Default `['B', 'KB', 'MB', 'GB', 'TB']` for both bases;
12
+ * pass `['B', 'KiB', 'MiB', 'GiB', 'TiB']` to mark base 1024 the IEC way, or localised ones.
13
+ */
14
+ units?: readonly [string, string, string, string, string];
15
+ }
16
+ /** A product's own size formatter for `FileUpload` and `Attachment` (`formatSize`). */
17
+ type FileSizeFormatter = (bytes: number, locale?: string) => string;
18
+ /**
19
+ * "1.2 MB", "340 KB", "12 B" in the locale's number format. Base 1000 unless `options.base`
20
+ * is 1024.
21
+ */
22
+ declare function formatFileSize(bytes: number, locale?: string, options?: FileSizeOptions): string;
4
23
  /** Whether a file matches an `accept` list (".pdf,image/*,application/zip"). An empty list accepts all. */
5
24
  declare function fileMatchesAccept(file: {
6
25
  name: string;
@@ -34,4 +53,4 @@ declare function fileKind(file: {
34
53
  type?: string;
35
54
  }): 'image' | 'video' | 'pdf' | 'sheet' | 'archive' | 'text' | 'other';
36
55
 
37
- export { type FileRejection, type FileRejectionReason, fileKind, fileMatchesAccept, formatFileSize, partitionFiles };
56
+ export { type FileRejection, type FileRejectionReason, type FileSizeFormatter, type FileSizeOptions, fileKind, fileMatchesAccept, formatFileSize, partitionFiles };
@@ -1,10 +1,12 @@
1
- function formatFileSize(bytes, locale) {
1
+ const UNITS = ["B", "KB", "MB", "GB", "TB"];
2
+ function formatFileSize(bytes, locale, options = {}) {
2
3
  if (!Number.isFinite(bytes) || bytes < 0) return "";
3
- const units = ["B", "KB", "MB", "GB", "TB"];
4
+ const base = options.base === 1024 ? 1024 : 1e3;
5
+ const units = options.units ?? UNITS;
4
6
  let value = bytes;
5
7
  let unit = 0;
6
- while (value >= 1e3 && unit < units.length - 1) {
7
- value /= 1e3;
8
+ while (value >= base && unit < units.length - 1) {
9
+ value /= base;
8
10
  unit += 1;
9
11
  }
10
12
  const digits = unit === 0 || value >= 100 ? 0 : 1;
@@ -33,6 +33,16 @@ type FilterValue = {
33
33
  values: FilterChoice[];
34
34
  };
35
35
  type FilterValueKind = FilterValue['kind'];
36
+ /**
37
+ * A named selection for an `enum` field (HAR-1505), e.g. "Open work" = new, open and pending.
38
+ * The id, label and values come from the product.
39
+ */
40
+ interface FilterPreset {
41
+ id: string;
42
+ label: string;
43
+ /** The option values the preset selects. Compared as a set: order and repeats do not matter. */
44
+ values: readonly string[];
45
+ }
36
46
  interface FilterField {
37
47
  id: string;
38
48
  /** Column name, also the start of the chip summary. */
@@ -49,6 +59,12 @@ interface FilterField {
49
59
  /** `boolean`: words for true and false, e.g. Escalated / Not escalated. Default from labels. */
50
60
  trueLabel?: string;
51
61
  falseLabel?: string;
62
+ /**
63
+ * `enum`: named selections shown as toggle chips above the options. Activating one replaces
64
+ * the selection with its values; the one equal to the selection is pressed, and the chip
65
+ * summary names it.
66
+ */
67
+ presets?: readonly FilterPreset[];
52
68
  }
53
69
  interface FilterSummaryLabels {
54
70
  operators: Record<TextFilterOperator | NumberFilterOperator, string>;
@@ -62,6 +78,8 @@ interface FilterSummaryLabels {
62
78
  until: string;
63
79
  /** More than `maxListed` values: `{first}`, `{count}`. */
64
80
  more: string;
81
+ /** An `enum` value equal to a preset: `{label}` (the preset), `{field}`. Default `{label}`. */
82
+ preset?: string;
65
83
  }
66
84
  declare const DEFAULT_FILTER_SUMMARY_LABELS: FilterSummaryLabels;
67
85
  declare const TEXT_FILTER_OPERATORS: readonly TextFilterOperator[];
@@ -70,6 +88,11 @@ declare const NUMBER_FILTER_OPERATORS: readonly NumberFilterOperator[];
70
88
  declare function emptyFilterValue(kind: FilterValueKind): FilterValue;
71
89
  /** True when the value filters nothing (so the chip goes away). */
72
90
  declare function isFilterEmpty(value: FilterValue | undefined): boolean;
91
+ /**
92
+ * The first of `field.presets` whose values equal the value's as a set, or undefined. Only an
93
+ * `enum` value can match a preset.
94
+ */
95
+ declare function matchFilterPreset(field: FilterField, value: FilterValue | undefined): FilterPreset | undefined;
73
96
  interface SummarizeFilterOptions {
74
97
  labels?: Partial<FilterSummaryLabels>;
75
98
  /** Formats a `YYYY-MM-DD` date (a product format such as DD.MM.YYYY). Default: as given. */
@@ -81,8 +104,9 @@ interface SummarizeFilterOptions {
81
104
  }
82
105
  /**
83
106
  * One line for the filter chip, e.g. "State: Open, Pending", "Priority ≥ 2", "Created:
84
- * 01.10.2026 – 05.10.2026", "Escalated: Yes". Empty values give an empty string.
107
+ * 01.10.2026 – 05.10.2026", "Escalated: Yes". An enum value equal to one of `field.presets`
108
+ * gives that preset's label (the `preset` label). Empty values give an empty string.
85
109
  */
86
110
  declare function summarizeFilter(field: FilterField, value: FilterValue | undefined, options?: SummarizeFilterOptions): string;
87
111
 
88
- export { DEFAULT_FILTER_SUMMARY_LABELS, type FilterChoice, type FilterField, type FilterSummaryLabels, type FilterValue, type FilterValueKind, NUMBER_FILTER_OPERATORS, type NumberFilterOperator, type SummarizeFilterOptions, TEXT_FILTER_OPERATORS, type TextFilterOperator, emptyFilterValue, isFilterEmpty, summarizeFilter };
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 };
@@ -18,7 +18,8 @@ const DEFAULT_FILTER_SUMMARY_LABELS = {
18
18
  range: "{from} \u2013 {to}",
19
19
  from: "from {from}",
20
20
  until: "until {to}",
21
- more: "{first} +{count}"
21
+ more: "{first} +{count}",
22
+ preset: "{label}"
22
23
  };
23
24
  const TEXT_FILTER_OPERATORS = ["contains", "not-contains", "is", "is-not", "starts-with"];
24
25
  const NUMBER_FILTER_OPERATORS = ["eq", "neq", "lt", "lte", "gt", "gte", "between"];
@@ -54,6 +55,14 @@ function isFilterEmpty(value) {
54
55
  return value.value === void 0;
55
56
  }
56
57
  }
58
+ function matchFilterPreset(field, value) {
59
+ if (value?.kind !== "enum" || !field.presets?.length) return void 0;
60
+ const chosen = new Set(value.values);
61
+ return field.presets.find((preset) => {
62
+ const wanted = new Set(preset.values);
63
+ return wanted.size === chosen.size && [...wanted].every((v) => chosen.has(v));
64
+ });
65
+ }
57
66
  const fill = (template, values) => template.replace(/\{(\w+)\}/g, (match, key) => key in values ? String(values[key]) : match);
58
67
  function summarizeFilter(field, value, options = {}) {
59
68
  if (!value || isFilterEmpty(value)) return "";
@@ -66,6 +75,8 @@ function summarizeFilter(field, value, options = {}) {
66
75
  };
67
76
  switch (value.kind) {
68
77
  case "enum": {
78
+ const preset = matchFilterPreset(field, value);
79
+ if (preset) return fill(labels.preset ?? DEFAULT_FILTER_SUMMARY_LABELS.preset, { label: preset.label, field: field.label });
69
80
  const byValue = new Map((field.options ?? []).map((o) => [o.value, o.label]));
70
81
  return `${field.label}: ${list(value.values.map((v) => byValue.get(v) ?? v))}`;
71
82
  }
@@ -87,5 +98,6 @@ export {
87
98
  TEXT_FILTER_OPERATORS,
88
99
  emptyFilterValue,
89
100
  isFilterEmpty,
101
+ matchFilterPreset,
90
102
  summarizeFilter
91
103
  };
@@ -7,6 +7,25 @@ interface UseAnchoredPositionOptions {
7
7
  placement?: Placement;
8
8
  offset?: number;
9
9
  padding?: number;
10
+ /**
11
+ * Keep the element inside the viewport on the side it hangs off as well: when it fits neither
12
+ * above nor below (or left nor right) it slides over the anchor instead of leaving the
13
+ * viewport. Once the anchor itself has left the viewport the element follows it out; pair
14
+ * that with `onAnchorHidden` to close it. Default true.
15
+ */
16
+ shiftMainAxis?: boolean;
17
+ /**
18
+ * Cap the element's height to the viewport minus the padding on both edges, and let it scroll
19
+ * (`overflow-y: auto`, unless its own CSS already sets an overflow). A `max-height` from the
20
+ * element's own CSS still applies when it is smaller. Default false.
21
+ */
22
+ capHeight?: boolean;
23
+ /**
24
+ * Called when the anchor has scrolled completely out of view (outside the viewport or a
25
+ * clipping ancestor) while `open`. Close the overlay here; `Popover closeWhenAnchorHidden`
26
+ * is built on it.
27
+ */
28
+ onAnchorHidden?: () => void;
10
29
  }
11
30
  type AnchorArg = RefObject<HTMLElement | null> | HTMLElement | null | undefined;
12
31
  /** @internal The element behind an anchor ref or element. */
@@ -22,9 +41,13 @@ declare const resolveAnchor: (a: AnchorArg) => HTMLElement | null;
22
41
  * `position: fixed` + left/top and follows scroll/resize from JS. While `open`, the side
23
42
  * chosen at open is kept until it no longer fits and the other side does, scrolls inside
24
43
  * the overlay itself are ignored, and a size change of either element re-places it.
44
+ * An element that fits on neither side is kept inside the viewport (it covers the anchor).
45
+ * While it is held there, or within `ANCHOR_BAND` of the limit, it is written as fixed
46
+ * coordinates even where anchor positioning exists: those cannot drift while the page scrolls,
47
+ * so it is inside on every frame, not one frame later (HAR-1613).
25
48
  * Returns a `reposition` fn for callers that want to nudge it manually (e.g. right when a
26
49
  * native popover's `toggle` fires).
27
50
  */
28
- declare function useAnchoredPosition(anchor: RefObject<HTMLElement | null> | HTMLElement | null | undefined, floatingRef: RefObject<HTMLElement | null>, { open, placement, offset, padding }: UseAnchoredPositionOptions): () => void;
51
+ declare function useAnchoredPosition(anchor: RefObject<HTMLElement | null> | HTMLElement | null | undefined, floatingRef: RefObject<HTMLElement | null>, { open, placement, offset, padding, shiftMainAxis, capHeight, onAnchorHidden }: UseAnchoredPositionOptions): () => void;
29
52
 
30
53
  export { type UseAnchoredPositionOptions, resolveAnchor, useAnchoredPosition };
@@ -15,6 +15,7 @@ function supportsAnchoring() {
15
15
  }
16
16
  return anchoringSupported;
17
17
  }
18
+ const ANCHOR_BAND = 64;
18
19
  let anchorCount = 0;
19
20
  const namesOf = (value) => value.split(",").map((n) => n.trim()).filter((n) => n.startsWith("--"));
20
21
  function addAnchorName(el, name) {
@@ -39,9 +40,12 @@ function translationOf(el) {
39
40
  }
40
41
  const signed = (n) => `${n < 0 ? "-" : "+"} ${Math.round(Math.abs(n) * 100) / 100}px`;
41
42
  const prepared = /* @__PURE__ */ new WeakSet();
42
- function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8 }) {
43
- const opts = useRef({ placement, offset, padding });
44
- opts.current = { placement, offset, padding };
43
+ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8, shiftMainAxis = true, capHeight = false, onAnchorHidden }) {
44
+ const opts = useRef({ placement, offset, padding, shiftMainAxis, capHeight });
45
+ opts.current = { placement, offset, padding, shiftMainAxis, capHeight };
46
+ const onAnchorHiddenRef = useRef(onAnchorHidden);
47
+ onAnchorHiddenRef.current = onAnchorHidden;
48
+ const heightCap = useRef(null);
45
49
  const session = useRef(false);
46
50
  const side = useRef(null);
47
51
  const written = useRef("");
@@ -65,6 +69,15 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
65
69
  }
66
70
  removeAnchorName(current.anchor, current.name);
67
71
  }, [floatingRef]);
72
+ const uncap = useCallback(() => {
73
+ const cap = heightCap.current;
74
+ if (!cap) return;
75
+ heightCap.current = null;
76
+ const floating = floatingRef.current;
77
+ if (!floating) return;
78
+ floating.style.maxHeight = cap.inlineMaxHeight;
79
+ if (cap.setOverflow) floating.style.overflowY = cap.inlineOverflowY;
80
+ }, [floatingRef]);
68
81
  const reposition = useCallback(() => {
69
82
  const anchorEl = resolveAnchor(anchor);
70
83
  const floating = floatingRef.current;
@@ -77,14 +90,36 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
77
90
  prepared.add(floating);
78
91
  written.current = "";
79
92
  }
93
+ const { placement: p, offset: o, padding: pad, shiftMainAxis: keepInView, capHeight: cap } = opts.current;
94
+ if (cap) {
95
+ if (!heightCap.current) {
96
+ const own = getComputedStyle(floating);
97
+ const setOverflow = !own.overflowY || own.overflowY === "visible";
98
+ heightCap.current = {
99
+ inlineMaxHeight: s.maxHeight,
100
+ inlineOverflowY: s.overflowY,
101
+ ownMaxHeight: own.maxHeight && own.maxHeight !== "none" ? own.maxHeight : "",
102
+ setOverflow,
103
+ written: ""
104
+ };
105
+ if (setOverflow) s.overflowY = "auto";
106
+ }
107
+ const room = `${Math.max(0, window.innerHeight - 2 * pad)}px`;
108
+ const max = heightCap.current.ownMaxHeight ? `min(${room}, ${heightCap.current.ownMaxHeight})` : room;
109
+ if (max !== heightCap.current.written) {
110
+ s.maxHeight = max;
111
+ heightCap.current.written = max;
112
+ }
113
+ } else if (heightCap.current) {
114
+ uncap();
115
+ }
80
116
  const a = anchorEl.getBoundingClientRect();
81
117
  const size = { width: floating.offsetWidth, height: floating.offsetHeight };
82
- const { placement: p, offset: o, padding: pad } = opts.current;
83
118
  const pos = computePosition(
84
119
  { x: a.x, y: a.y, width: a.width, height: a.height },
85
120
  size,
86
121
  { width: window.innerWidth, height: window.innerHeight },
87
- { placement: p, offset: o, padding: pad, stickySide: side.current ?? void 0 }
122
+ { placement: p, offset: o, padding: pad, shiftMainAxis: keepInView, stickySide: side.current ?? void 0 }
88
123
  );
89
124
  const measured = size.width > 0 || size.height > 0;
90
125
  if (measured && session.current) side.current = pos.side;
@@ -98,7 +133,10 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
98
133
  s.top = top2;
99
134
  written.current = key2;
100
135
  };
101
- if (anchoringFailed.current || !supportsAnchoring()) {
136
+ const limit = pos.mainAxisRange;
137
+ const nearLimit = limit !== null && (pos.mainAxisNatural < limit[0] + ANCHOR_BAND || pos.mainAxisNatural > limit[1] - ANCHOR_BAND);
138
+ if (anchoringFailed.current || nearLimit || !supportsAnchoring()) {
139
+ if (anchoring.current) release(false);
102
140
  writeFixed(pos);
103
141
  return;
104
142
  }
@@ -135,7 +173,7 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
135
173
  writeFixed(pos);
136
174
  }
137
175
  }
138
- }, [anchor, floatingRef, release]);
176
+ }, [anchor, floatingRef, release, uncap]);
139
177
  useIsomorphicLayoutEffect(() => {
140
178
  if (!open) return;
141
179
  session.current = true;
@@ -164,8 +202,20 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
164
202
  side.current = null;
165
203
  anchoringFailed.current = false;
166
204
  release(true);
205
+ uncap();
167
206
  };
168
- }, [open, reposition, anchor, floatingRef, release]);
207
+ }, [open, reposition, anchor, floatingRef, release, uncap]);
208
+ const watchAnchor = open && !!onAnchorHidden;
209
+ useEffect(() => {
210
+ const anchorEl = resolveAnchor(anchor);
211
+ if (!watchAnchor || !anchorEl || typeof IntersectionObserver !== "function") return;
212
+ const observer = new IntersectionObserver((entries) => {
213
+ const latest = entries[entries.length - 1];
214
+ if (latest && !latest.isIntersecting) onAnchorHiddenRef.current?.();
215
+ });
216
+ observer.observe(anchorEl);
217
+ return () => observer.disconnect();
218
+ }, [watchAnchor, anchor]);
169
219
  useEffect(() => () => release(false), [release]);
170
220
  return reposition;
171
221
  }
@@ -5,8 +5,21 @@ interface UseVirtualRowsOptions {
5
5
  rowHeight: number;
6
6
  /** Rows rendered beyond each viewport edge. Default: 6. */
7
7
  overscan?: number;
8
- /** Render all rows at or below this count. Default: 100. */
8
+ /**
9
+ * Render all rows at or below this count: rows are windowed only when
10
+ * `rows.length > threshold` (so exactly `threshold` rows are still rendered whole).
11
+ * Default: 100.
12
+ */
9
13
  threshold?: number;
14
+ /**
15
+ * Viewport height in pixels to assume until the scroll element has been measured, and
16
+ * whenever it measures 0 (first render, a hidden tab, a test without layout). With it, a
17
+ * 5,000-row table mounts with one window of rows; without it every row is rendered until
18
+ * the first measurement, as before (HAR-1616).
19
+ */
20
+ estimatedViewportHeight?: number;
21
+ /** `false` renders every row with no spacers, whatever the count. Default: `true`. */
22
+ enabled?: boolean;
10
23
  }
11
24
  interface UseVirtualRowsResult<T> {
12
25
  /** Attach to the scrolling TableWrap. */
@@ -21,7 +34,9 @@ interface UseVirtualRowsResult<T> {
21
34
  /**
22
35
  * Owns scroll/resize observation and exposes the small visible row window.
23
36
  * Consumers render padTop/padBottom spacer rows inside their existing tbody.
37
+ * When the row count shrinks under a large scroll position the window is clamped to the new
38
+ * count in the same render, so `padTop` never exceeds the list (HAR-1616).
24
39
  */
25
- declare function useVirtualRows<T>(rows: readonly T[], { rowHeight, overscan, threshold }: UseVirtualRowsOptions): UseVirtualRowsResult<T>;
40
+ declare function useVirtualRows<T>(rows: readonly T[], { rowHeight, overscan, threshold, estimatedViewportHeight, enabled }: UseVirtualRowsOptions): UseVirtualRowsResult<T>;
26
41
 
27
42
  export { type UseVirtualRowsOptions, type UseVirtualRowsResult, useVirtualRows };