@tensor_1/react 2.26.2 → 2.27.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 (68) hide show
  1. package/README.md +51 -0
  2. package/dist/calendar-model.d.ts +4 -2
  3. package/dist/components/AppShell.d.ts +1 -1
  4. package/dist/components/AppShell.js +6 -2
  5. package/dist/components/Button.d.ts +28 -4
  6. package/dist/components/Button.js +19 -9
  7. package/dist/components/Card.d.ts +7 -1
  8. package/dist/components/Card.js +3 -3
  9. package/dist/components/Chip.d.ts +43 -0
  10. package/dist/components/Chip.js +73 -0
  11. package/dist/components/CollapsibleSection.d.ts +19 -1
  12. package/dist/components/CollapsibleSection.js +5 -2
  13. package/dist/components/CollapsibleSectionState.d.ts +11 -0
  14. package/dist/components/CollapsibleSectionState.js +67 -0
  15. package/dist/components/CommandPalette.d.ts +1 -1
  16. package/dist/components/CommandPalette.js +4 -1
  17. package/dist/components/DateRangePicker.js +3 -1
  18. package/dist/components/Dialogs.d.ts +54 -3
  19. package/dist/components/Dialogs.js +167 -6
  20. package/dist/components/Drawer.d.ts +6 -1
  21. package/dist/components/Drawer.js +5 -2
  22. package/dist/components/EmojiPicker.d.ts +3 -0
  23. package/dist/components/EmojiPicker.js +109 -54
  24. package/dist/components/Kbd.d.ts +27 -0
  25. package/dist/components/Kbd.js +45 -0
  26. package/dist/components/Menu.d.ts +51 -0
  27. package/dist/components/Menu.js +177 -0
  28. package/dist/components/Modal.d.ts +1 -1
  29. package/dist/components/Modal.js +4 -1
  30. package/dist/components/Pagination.d.ts +36 -5
  31. package/dist/components/Pagination.js +59 -37
  32. package/dist/components/Peek.d.ts +1 -1
  33. package/dist/components/Peek.js +6 -1
  34. package/dist/components/Popover.d.ts +15 -5
  35. package/dist/components/Popover.js +66 -6
  36. package/dist/components/SchedulingCalendar.d.ts +47 -2
  37. package/dist/components/SchedulingCalendar.js +69 -22
  38. package/dist/components/SearchSuggest.d.ts +1 -1
  39. package/dist/components/SearchSuggest.js +4 -1
  40. package/dist/components/Sidebar.d.ts +7 -2
  41. package/dist/components/Sidebar.js +15 -4
  42. package/dist/components/Stat.d.ts +16 -0
  43. package/dist/components/Stat.js +12 -5
  44. package/dist/components/Steps.d.ts +48 -0
  45. package/dist/components/Steps.js +42 -0
  46. package/dist/components/Toast.d.ts +2 -2
  47. package/dist/components/Toast.js +7 -2
  48. package/dist/components/Tooltip.d.ts +10 -4
  49. package/dist/components/Tooltip.js +2 -2
  50. package/dist/{emoji-image-WQLh8ZQP.d.cts → emoji-image-6mPZ15Zy.d.cts} +4 -1
  51. package/dist/emoji.cjs +326 -76
  52. package/dist/emoji.d.cts +3 -3
  53. package/dist/hooks/useAnchoredPosition.d.ts +13 -5
  54. package/dist/hooks/useAnchoredPosition.js +146 -15
  55. package/dist/index.cjs +2278 -1372
  56. package/dist/index.d.cts +622 -176
  57. package/dist/index.d.ts +10 -4
  58. package/dist/index.js +24 -2
  59. package/dist/labels-context.d.ts +72 -0
  60. package/dist/labels-context.js +23 -0
  61. package/dist/link.d.ts +20 -0
  62. package/dist/link.js +9 -0
  63. package/dist/{overlay-position-CEGSz6Z2.d.cts → overlay-position-Cjv5E7mn.d.cts} +7 -0
  64. package/dist/overlay-position.d.ts +7 -0
  65. package/dist/overlay-position.js +7 -2
  66. package/dist/rich-text.cjs +324 -74
  67. package/dist/rich-text.d.cts +3 -3
  68. package/package.json +1 -1
package/dist/emoji.cjs CHANGED
@@ -83,10 +83,15 @@ 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 } = options;
86
+ const { placement = "bottom-start", offset = 6, padding = 8, flip = true, shift = true, stickySide } = options;
87
87
  const { side: preferred, align } = parsePlacement(placement);
88
88
  let side = preferred;
89
- if (flip && !fitsOn(side, anchor, floating, viewport, offset, padding)) {
89
+ const sticky = stickySide === preferred || stickySide === OPPOSITE[preferred] ? stickySide : void 0;
90
+ if (flip && sticky) {
91
+ side = sticky;
92
+ const opp = OPPOSITE[side];
93
+ if (!fitsOn(side, anchor, floating, viewport, offset, padding) && fitsOn(opp, anchor, floating, viewport, offset, padding)) side = opp;
94
+ } else if (flip && !fitsOn(side, anchor, floating, viewport, offset, padding)) {
90
95
  const opp = OPPOSITE[side];
91
96
  if (fitsOn(opp, anchor, floating, viewport, offset, padding) || spaceOn(opp, anchor, viewport, offset) > spaceOn(side, anchor, viewport, offset)) {
92
97
  side = opp;
@@ -122,57 +127,206 @@ function computePosition(anchor, floating, viewport, options = {}) {
122
127
 
123
128
  // src/hooks/useAnchoredPosition.ts
124
129
  var useIsomorphicLayoutEffect = typeof document !== "undefined" ? import_react.useLayoutEffect : import_react.useEffect;
125
- var resolve = (a) => a && typeof a === "object" && "current" in a ? a.current : a ?? null;
130
+ var resolveAnchor = (a) => a && typeof a === "object" && "current" in a ? a.current : a ?? null;
131
+ var anchoringSupported;
132
+ function supportsAnchoring() {
133
+ if (anchoringSupported === void 0) {
134
+ try {
135
+ const css = typeof CSS !== "undefined" ? CSS : void 0;
136
+ anchoringSupported = !!css && typeof css.supports === "function" && css.supports("anchor-name", "--uix-a") && css.supports("position-anchor", "--uix-a") && css.supports("top", "round(anchor(bottom) + 1px, 1px)");
137
+ } catch {
138
+ anchoringSupported = false;
139
+ }
140
+ }
141
+ return anchoringSupported;
142
+ }
143
+ var anchorCount = 0;
144
+ var namesOf = (value) => value.split(",").map((n) => n.trim()).filter((n) => n.startsWith("--"));
145
+ function addAnchorName(el, name) {
146
+ const names = namesOf(getComputedStyle(el).getPropertyValue("anchor-name"));
147
+ if (!names.includes(name)) names.push(name);
148
+ el.style.setProperty("anchor-name", names.join(", "));
149
+ }
150
+ function removeAnchorName(el, name) {
151
+ const names = namesOf(el.style.getPropertyValue("anchor-name")).filter((n) => n !== name);
152
+ if (names.length) el.style.setProperty("anchor-name", names.join(", "));
153
+ else el.style.removeProperty("anchor-name");
154
+ }
155
+ function translationOf(el) {
156
+ const t = getComputedStyle(el).transform;
157
+ if (!t || t === "none" || typeof DOMMatrixReadOnly === "undefined") return { x: 0, y: 0 };
158
+ try {
159
+ const m = new DOMMatrixReadOnly(t);
160
+ return { x: m.m41, y: m.m42 };
161
+ } catch {
162
+ return { x: 0, y: 0 };
163
+ }
164
+ }
165
+ var signed = (n) => `${n < 0 ? "-" : "+"} ${Math.round(Math.abs(n) * 100) / 100}px`;
166
+ var prepared = /* @__PURE__ */ new WeakSet();
126
167
  function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8 }) {
127
168
  const opts = (0, import_react.useRef)({ placement, offset, padding });
128
169
  opts.current = { placement, offset, padding };
170
+ const session = (0, import_react.useRef)(false);
171
+ const side = (0, import_react.useRef)(null);
172
+ const written = (0, import_react.useRef)("");
173
+ const anchoring = (0, import_react.useRef)(null);
174
+ const anchoringFailed = (0, import_react.useRef)(false);
175
+ const anchorName = (0, import_react.useRef)("");
176
+ const release = (0, import_react.useCallback)((keepPlace) => {
177
+ const current = anchoring.current;
178
+ if (!current) return;
179
+ anchoring.current = null;
180
+ written.current = "";
181
+ const floating = floatingRef.current;
182
+ if (floating) {
183
+ const s = floating.style;
184
+ if (keepPlace && current.anchor.isConnected) {
185
+ const a = current.anchor.getBoundingClientRect();
186
+ s.top = `${Math.round((current.yEdge === "bottom" ? a.bottom : a.top) + current.dy)}px`;
187
+ s.left = `${Math.round((current.xEdge === "right" ? a.right : a.left) + current.dx)}px`;
188
+ }
189
+ s.removeProperty("position-anchor");
190
+ }
191
+ removeAnchorName(current.anchor, current.name);
192
+ }, [floatingRef]);
129
193
  const reposition = (0, import_react.useCallback)(() => {
130
- const anchorEl = resolve(anchor);
194
+ const anchorEl = resolveAnchor(anchor);
131
195
  const floating = floatingRef.current;
132
196
  if (!anchorEl || !floating) return;
133
197
  const s = floating.style;
134
- s.position = "fixed";
135
- s.inset = "auto";
136
- s.margin = "0";
198
+ if (!prepared.has(floating)) {
199
+ s.position = "fixed";
200
+ s.inset = "auto";
201
+ s.margin = "0";
202
+ prepared.add(floating);
203
+ written.current = "";
204
+ }
137
205
  const a = anchorEl.getBoundingClientRect();
206
+ const size = { width: floating.offsetWidth, height: floating.offsetHeight };
138
207
  const { placement: p, offset: o, padding: pad } = opts.current;
139
208
  const pos = computePosition(
140
209
  { x: a.x, y: a.y, width: a.width, height: a.height },
141
- { width: floating.offsetWidth, height: floating.offsetHeight },
210
+ size,
142
211
  { width: window.innerWidth, height: window.innerHeight },
143
- { placement: p, offset: o, padding: pad }
212
+ { placement: p, offset: o, padding: pad, stickySide: side.current ?? void 0 }
144
213
  );
145
- s.left = `${Math.round(pos.x)}px`;
146
- s.top = `${Math.round(pos.y)}px`;
147
- floating.dataset.placement = pos.placement;
148
- }, [anchor, floatingRef]);
214
+ const measured = size.width > 0 || size.height > 0;
215
+ if (measured && session.current) side.current = pos.side;
216
+ if (floating.dataset.placement !== pos.placement) floating.dataset.placement = pos.placement;
217
+ const writeFixed = (at) => {
218
+ const left2 = `${Math.round(at.x)}px`;
219
+ const top2 = `${Math.round(at.y)}px`;
220
+ const key2 = `fixed ${left2} ${top2}`;
221
+ if (key2 === written.current) return;
222
+ s.left = left2;
223
+ s.top = top2;
224
+ written.current = key2;
225
+ };
226
+ if (anchoringFailed.current || !supportsAnchoring()) {
227
+ writeFixed(pos);
228
+ return;
229
+ }
230
+ if (anchoring.current && anchoring.current.anchor !== anchorEl) release(false);
231
+ if (!anchoring.current) {
232
+ anchorName.current || (anchorName.current = `--uix-anchor-${++anchorCount}`);
233
+ addAnchorName(anchorEl, anchorName.current);
234
+ s.setProperty("position-anchor", anchorName.current);
235
+ anchoring.current = { anchor: anchorEl, name: anchorName.current, yEdge: "bottom", xEdge: "left", dy: 0, dx: 0, verified: false };
236
+ written.current = "";
237
+ }
238
+ const current = anchoring.current;
239
+ current.yEdge = pos.side === "bottom" ? "bottom" : "top";
240
+ current.xEdge = pos.side === "right" ? "right" : "left";
241
+ current.dy = pos.y - (current.yEdge === "bottom" ? a.bottom : a.top);
242
+ current.dx = pos.x - (current.xEdge === "right" ? a.right : a.left);
243
+ const top = `round(anchor(${current.yEdge}) ${signed(current.dy)}, 1px)`;
244
+ const left = `round(anchor(${current.xEdge}) ${signed(current.dx)}, 1px)`;
245
+ const key = `anchored ${top} ${left}`;
246
+ if (key !== written.current) {
247
+ s.top = top;
248
+ s.left = left;
249
+ written.current = key;
250
+ }
251
+ if (!current.verified && measured) {
252
+ current.verified = true;
253
+ const box = floating.getBoundingClientRect();
254
+ const t = translationOf(floating);
255
+ const dxc = box.left + box.width / 2 - t.x - (pos.x + size.width / 2);
256
+ const dyc = box.top + box.height / 2 - t.y - (pos.y + size.height / 2);
257
+ if (Math.abs(dxc) > 2 || Math.abs(dyc) > 2) {
258
+ anchoringFailed.current = true;
259
+ release(false);
260
+ writeFixed(pos);
261
+ }
262
+ }
263
+ }, [anchor, floatingRef, release]);
149
264
  useIsomorphicLayoutEffect(() => {
150
265
  if (!open) return;
266
+ session.current = true;
151
267
  reposition();
152
- const onScroll = () => reposition();
268
+ const anchorEl = resolveAnchor(anchor);
269
+ const floating = floatingRef.current;
270
+ const onScroll = (event) => {
271
+ const target = event.target;
272
+ if (target && typeof target.contains === "function") {
273
+ if (floating?.contains(target)) return;
274
+ if (anchorEl && !target.contains(anchorEl)) return;
275
+ }
276
+ reposition();
277
+ };
278
+ const onResize = () => reposition();
153
279
  window.addEventListener("scroll", onScroll, { passive: true, capture: true });
154
- window.addEventListener("resize", onScroll);
280
+ window.addEventListener("resize", onResize);
281
+ const observer = typeof ResizeObserver === "function" ? new ResizeObserver(() => reposition()) : null;
282
+ if (floating) observer?.observe(floating);
283
+ if (anchorEl) observer?.observe(anchorEl);
155
284
  return () => {
156
285
  window.removeEventListener("scroll", onScroll, true);
157
- window.removeEventListener("resize", onScroll);
286
+ window.removeEventListener("resize", onResize);
287
+ observer?.disconnect();
288
+ session.current = false;
289
+ side.current = null;
290
+ anchoringFailed.current = false;
291
+ release(true);
158
292
  };
159
- }, [open, reposition]);
293
+ }, [open, reposition, anchor, floatingRef, release]);
294
+ (0, import_react.useEffect)(() => () => release(false), [release]);
160
295
  return reposition;
161
296
  }
162
297
 
163
298
  // src/components/Popover.tsx
164
299
  var import_jsx_runtime = require("react/jsx-runtime");
300
+ var useIsomorphicLayoutEffect2 = typeof document !== "undefined" ? import_react2.useLayoutEffect : import_react2.useEffect;
301
+ var PLACING = "data-uix-placing";
302
+ var isPopoverOpen = (el) => {
303
+ try {
304
+ return el.matches(":popover-open");
305
+ } catch {
306
+ return false;
307
+ }
308
+ };
309
+ var settle = (el) => {
310
+ if (!el.hasAttribute(PLACING)) return;
311
+ getComputedStyle(el).getPropertyValue("transform");
312
+ el.removeAttribute(PLACING);
313
+ };
165
314
  function Popover({
166
315
  popover = "auto",
167
316
  anchor,
168
317
  placement = "bottom-start",
169
318
  offset = 6,
319
+ closeWhenAnchorHidden = false,
320
+ onAnchorHidden,
170
321
  className,
171
322
  children,
172
323
  ...props
173
324
  }) {
174
325
  const ref = (0, import_react2.useRef)(null);
175
326
  const [open, setOpen] = (0, import_react2.useState)(false);
327
+ const openRef = (0, import_react2.useRef)(false);
328
+ const onAnchorHiddenRef = (0, import_react2.useRef)(onAnchorHidden);
329
+ onAnchorHiddenRef.current = onAnchorHidden;
176
330
  const popoverAttr = { popover };
177
331
  const reposition = useAnchoredPosition(anchor ?? null, ref, {
178
332
  open: open && !!anchor,
@@ -182,14 +336,55 @@ function Popover({
182
336
  (0, import_react2.useEffect)(() => {
183
337
  const el = ref.current;
184
338
  if (!el || !anchor) return;
339
+ const hold = (event) => {
340
+ if (event.newState !== "open") return;
341
+ el.setAttribute(PLACING, "");
342
+ window.setTimeout(() => settle(el), 150);
343
+ };
185
344
  const sync = () => {
186
- const isOpen = el.matches(":popover-open");
187
- if (isOpen) reposition();
188
- setOpen(isOpen);
345
+ if (!isPopoverOpen(el)) {
346
+ el.removeAttribute(PLACING);
347
+ setOpen(false);
348
+ return;
349
+ }
350
+ reposition();
351
+ window.setTimeout(() => {
352
+ if (!isPopoverOpen(el)) return;
353
+ if (!openRef.current) {
354
+ setOpen(true);
355
+ return;
356
+ }
357
+ reposition();
358
+ settle(el);
359
+ }, 0);
189
360
  };
361
+ el.addEventListener("beforetoggle", hold);
190
362
  el.addEventListener("toggle", sync);
191
- return () => el.removeEventListener("toggle", sync);
363
+ return () => {
364
+ el.removeEventListener("beforetoggle", hold);
365
+ el.removeEventListener("toggle", sync);
366
+ el.removeAttribute(PLACING);
367
+ };
192
368
  }, [anchor, reposition]);
369
+ useIsomorphicLayoutEffect2(() => {
370
+ openRef.current = open;
371
+ if (open && ref.current) settle(ref.current);
372
+ }, [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]);
193
388
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref, className: cx("uix-popover", className), ...popoverAttr, ...props, children });
194
389
  }
195
390
 
@@ -386,6 +581,14 @@ var DEFAULT_EMOJI_PICKER_LABELS = {
386
581
  loadFailed: "Emoji could not be loaded."
387
582
  };
388
583
  var EMOJI_GRID_COLUMNS = 8;
584
+ var useIsomorphicLayoutEffect3 = typeof document !== "undefined" ? import_react4.useLayoutEffect : import_react4.useEffect;
585
+ function revealInGrid(grid, cell) {
586
+ const g = grid.getBoundingClientRect();
587
+ const c = cell.getBoundingClientRect();
588
+ const top = g.top + (parseFloat(getComputedStyle(grid).scrollPaddingTop) || 0);
589
+ if (c.top < top) grid.scrollTop -= top - c.top;
590
+ else if (c.bottom > g.bottom) grid.scrollTop += c.bottom - g.bottom;
591
+ }
389
592
  function EmojiPicker({
390
593
  onSelect,
391
594
  trigger,
@@ -407,6 +610,8 @@ function EmojiPicker({
407
610
  const searchRef = (0, import_react4.useRef)(null);
408
611
  const gridRef = (0, import_react4.useRef)(null);
409
612
  const pointerWasOpen = (0, import_react4.useRef)(null);
613
+ const closedByAnchor = (0, import_react4.useRef)(false);
614
+ const pendingJump = (0, import_react4.useRef)(null);
410
615
  const [open, setOpen] = (0, import_react4.useState)(false);
411
616
  const [data, setData] = (0, import_react4.useState)(null);
412
617
  const [failed, setFailed] = (0, import_react4.useState)(false);
@@ -430,10 +635,14 @@ function EmojiPicker({
430
635
  setOpen(nowOpen);
431
636
  onOpenChange?.(nowOpen);
432
637
  if (nowOpen) {
638
+ closedByAnchor.current = false;
639
+ pendingJump.current = null;
433
640
  setRecent(readRecentEmoji(recentStorageKey));
434
641
  setQuery("");
435
642
  setActive(0);
436
- requestAnimationFrame(() => searchRef.current?.focus());
643
+ requestAnimationFrame(() => searchRef.current?.focus({ preventScroll: true }));
644
+ } else if (closedByAnchor.current) {
645
+ closedByAnchor.current = false;
437
646
  } else if (el.contains(document.activeElement) || document.activeElement === document.body) {
438
647
  focusTrigger();
439
648
  }
@@ -465,10 +674,7 @@ function EmojiPicker({
465
674
  const el = popoverEl();
466
675
  if (el && isOpen(el)) el.hidePopover();
467
676
  };
468
- const sections = (0, import_react4.useMemo)(() => {
469
- if (query.trim()) {
470
- return data ? [{ key: "results", title: labels.search, emojis: searchEmoji(data, query).map((e) => e.emoji) }] : [];
471
- }
677
+ const browseSections = (0, import_react4.useMemo)(() => {
472
678
  const list = [];
473
679
  if (quickPicks?.length) list.push({ key: "quick", title: labels.quickPicks, emojis: [...new Set(quickPicks)] });
474
680
  if (recent.length) list.push({ key: "recent", title: labels.recent, emojis: recent });
@@ -478,7 +684,11 @@ function EmojiPicker({
478
684
  });
479
685
  }
480
686
  return list;
481
- }, [query, data, quickPicks, recent, labels.search, labels.quickPicks, labels.recent]);
687
+ }, [data, quickPicks, recent, labels.quickPicks, labels.recent]);
688
+ const sections = (0, import_react4.useMemo)(() => {
689
+ if (!query.trim()) return browseSections;
690
+ return data ? [{ key: "results", title: labels.search, emojis: searchEmoji(data, query).map((e) => e.emoji) }] : [];
691
+ }, [query, data, browseSections, labels.search]);
482
692
  const flat = (0, import_react4.useMemo)(() => sections.flatMap((s) => s.emojis), [sections]);
483
693
  const sectionStart = (0, import_react4.useMemo)(() => {
484
694
  const starts = /* @__PURE__ */ new Map();
@@ -495,13 +705,43 @@ function EmojiPicker({
495
705
  onSelect(emoji);
496
706
  hide();
497
707
  };
498
- const focusCell = (0, import_react4.useCallback)((index) => {
708
+ const focusCell = (0, import_react4.useCallback)((index, reveal = true) => {
499
709
  setActive(index);
500
710
  const find = () => gridRef.current?.querySelector(`[data-emoji-index="${index}"]`);
711
+ const move = (cell2) => {
712
+ cell2.focus({ preventScroll: true });
713
+ if (reveal && gridRef.current) revealInGrid(gridRef.current, cell2);
714
+ };
501
715
  const cell = find();
502
- if (cell) cell.focus();
503
- else requestAnimationFrame(() => find()?.focus());
716
+ if (cell) move(cell);
717
+ else requestAnimationFrame(() => {
718
+ const later = find();
719
+ if (later) move(later);
720
+ });
504
721
  }, []);
722
+ const showSection = (key) => {
723
+ const grid = gridRef.current;
724
+ const section = grid?.querySelector(`[data-section="${key}"]`);
725
+ if (!grid || !section) return;
726
+ grid.scrollTop += section.getBoundingClientRect().top - grid.getBoundingClientRect().top;
727
+ const start = sectionStart.get(key);
728
+ if (start !== void 0) focusCell(start, false);
729
+ };
730
+ const onCategory = (key) => {
731
+ if (!query.trim()) {
732
+ showSection(key);
733
+ return;
734
+ }
735
+ pendingJump.current = key;
736
+ setQuery("");
737
+ setActive(0);
738
+ };
739
+ useIsomorphicLayoutEffect3(() => {
740
+ const key = pendingJump.current;
741
+ if (key === null || query.trim()) return;
742
+ pendingJump.current = null;
743
+ showSection(key);
744
+ }, [query, sections]);
505
745
  const onGridKeyDown = (event) => {
506
746
  const target = event.target;
507
747
  const index = Number(target.dataset.emojiIndex ?? -1);
@@ -509,7 +749,7 @@ function EmojiPicker({
509
749
  const next = emojiGridMove(index, event.key, flat.length, EMOJI_GRID_COLUMNS);
510
750
  if (event.key === "ArrowUp" && next === null) {
511
751
  event.preventDefault();
512
- searchRef.current?.focus();
752
+ searchRef.current?.focus({ preventScroll: true });
513
753
  return;
514
754
  }
515
755
  if (next === null) return;
@@ -565,6 +805,10 @@ function EmojiPicker({
565
805
  className: "uix-emoji-picker-popover",
566
806
  role: "dialog",
567
807
  "aria-label": labels.dialog,
808
+ closeWhenAnchorHidden: true,
809
+ onAnchorHidden: () => {
810
+ closedByAnchor.current = true;
811
+ },
568
812
  children: open ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "uix-emoji-picker uix-emoji-picker--full", lang: locale, children: [
569
813
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
570
814
  "input",
@@ -584,50 +828,56 @@ function EmojiPicker({
584
828
  spellCheck: false
585
829
  }
586
830
  ),
587
- data && !query.trim() ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "uix-emoji-picker__nav", role: "group", "aria-label": labels.categories, children: sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
588
- "button",
831
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
832
+ "div",
589
833
  {
590
- type: "button",
591
- className: "uix-emoji-picker__nav-btn",
592
- "aria-label": section.title,
593
- title: section.title,
594
- onClick: () => {
595
- const start = section.emojis.length ? sectionStart.get(section.key) ?? -1 : -1;
596
- gridRef.current?.querySelector(`[data-section="${section.key}"]`)?.scrollIntoView({ block: "start" });
597
- if (start >= 0) focusCell(start);
598
- },
599
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "aria-hidden": "true", children: section.emojis[0] ? renderEmoji(section.emojis[0]) : null })
600
- },
601
- section.key
602
- )) }) : null,
603
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "uix-emoji-picker__grid", ref: gridRef, onKeyDown: onGridKeyDown, children: sections.map((section) => {
604
- const start = offset;
605
- offset += section.emojis.length;
606
- if (!section.emojis.length) return null;
607
- const headingId = `${id}-${section.key}`;
608
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "uix-emoji-picker__section", "data-section": section.key, "aria-labelledby": headingId, children: [
609
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { id: headingId, className: "uix-emoji-picker__heading", children: section.title }),
610
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "uix-emoji-picker__cells", role: "group", "aria-labelledby": headingId, children: section.emojis.map((emoji, i) => {
611
- const index = start + i;
612
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
613
- "button",
614
- {
615
- type: "button",
616
- className: "uix-emoji-picker__btn",
617
- "data-emoji-index": index,
618
- tabIndex: index === activeIndex ? 0 : -1,
619
- "aria-label": names?.get(emoji) ?? emoji,
620
- title: names?.get(emoji),
621
- onClick: () => choose(emoji),
622
- onFocus: () => setActive(index),
623
- children: renderEmoji(emoji)
624
- },
625
- `${section.key}-${emoji}`
626
- );
627
- }) })
628
- ] }, section.key);
629
- }) }),
630
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "uix-emoji-picker__status", role: "status", children: status })
834
+ className: "uix-emoji-picker__nav",
835
+ role: data ? "group" : void 0,
836
+ "aria-label": data ? labels.categories : void 0,
837
+ children: data ? browseSections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
838
+ "button",
839
+ {
840
+ type: "button",
841
+ className: "uix-emoji-picker__nav-btn",
842
+ "aria-label": section.title,
843
+ title: section.title,
844
+ onClick: () => onCategory(section.key),
845
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "aria-hidden": "true", children: section.emojis[0] ? renderEmoji(section.emojis[0]) : null })
846
+ },
847
+ section.key
848
+ )) : null
849
+ }
850
+ ),
851
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "uix-emoji-picker__grid", ref: gridRef, onKeyDown: onGridKeyDown, children: [
852
+ sections.map((section) => {
853
+ const start = offset;
854
+ offset += section.emojis.length;
855
+ if (!section.emojis.length) return null;
856
+ const headingId = `${id}-${section.key}`;
857
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "uix-emoji-picker__section", "data-section": section.key, "aria-labelledby": headingId, children: [
858
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { id: headingId, className: "uix-emoji-picker__heading", children: section.title }),
859
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "uix-emoji-picker__cells", role: "group", "aria-labelledby": headingId, children: section.emojis.map((emoji, i) => {
860
+ const index = start + i;
861
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
862
+ "button",
863
+ {
864
+ type: "button",
865
+ className: "uix-emoji-picker__btn",
866
+ "data-emoji-index": index,
867
+ tabIndex: index === activeIndex ? 0 : -1,
868
+ "aria-label": names?.get(emoji) ?? emoji,
869
+ title: names?.get(emoji),
870
+ onClick: () => choose(emoji),
871
+ onFocus: () => setActive(index),
872
+ children: renderEmoji(emoji)
873
+ },
874
+ `${section.key}-${emoji}`
875
+ );
876
+ }) })
877
+ ] }, section.key);
878
+ }),
879
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "uix-emoji-picker__status", role: "status", children: status })
880
+ ] })
631
881
  ] }) : null
632
882
  }
633
883
  )
@@ -689,7 +939,7 @@ function EditorIcon({ name, ...props }) {
689
939
  // src/components/Tooltip.tsx
690
940
  var import_react5 = require("react");
691
941
  var import_jsx_runtime5 = require("react/jsx-runtime");
692
- function Tooltip({ label, placement = "top", className, children, ...props }) {
942
+ function Tooltip({ label, content, placement = "top", className, children, ...props }) {
693
943
  const triggerRef = (0, import_react5.useRef)(null);
694
944
  const bubbleRef = (0, import_react5.useRef)(null);
695
945
  const closeTimer = (0, import_react5.useRef)(null);
@@ -778,7 +1028,7 @@ function Tooltip({ label, placement = "top", className, children, ...props }) {
778
1028
  onMouseEnter: cancelClose,
779
1029
  onMouseLeave: scheduleHide,
780
1030
  ...bubbleAttr,
781
- children: label
1031
+ children: content ?? label
782
1032
  }
783
1033
  )
784
1034
  ]
package/dist/emoji.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { E as EmojiPickerLabels, a as EmojiLocale, b as EmojiDataLoader } from './emoji-image-WQLh8ZQP.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-WQLh8ZQP.cjs';
3
- export { A as Align, P as Placement, S as Side } from './overlay-position-CEGSz6Z2.cjs';
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';
4
4
  import * as react from 'react';
5
5
 
6
6
  interface ReactionSummary {
@@ -8,15 +8,23 @@ interface UseAnchoredPositionOptions {
8
8
  offset?: number;
9
9
  padding?: number;
10
10
  }
11
+ type AnchorArg = RefObject<HTMLElement | null> | HTMLElement | null | undefined;
12
+ /** @internal The element behind an anchor ref or element. */
13
+ declare const resolveAnchor: (a: AnchorArg) => HTMLElement | null;
11
14
  /**
12
15
  * Position a floating element (a native-popover overlay) against an anchor with
13
16
  * cross-browser flip/shift — the DOM half of the overlay-position engine (UIX-FIX-02).
14
17
  *
15
- * Measures both elements with getBoundingClientRect, applies `position: fixed` + left/top
16
- * (overriding the popover's default centered `inset:0; margin:auto`), and keeps the overlay
17
- * glued to its anchor on scroll/resize while `open`. Returns a `reposition` fn for callers
18
- * that want to nudge it manually (e.g. right when a native popover's `toggle` fires).
18
+ * The engine decides the side, alignment and viewport clamp from getBoundingClientRect.
19
+ * Where the browser supports CSS anchor positioning, that result is written as an offset
20
+ * from the anchor (`position-anchor` + `anchor()`), so the browser keeps the overlay glued
21
+ * to its anchor while the page scrolls, with no frame of lag; elsewhere it is written as
22
+ * `position: fixed` + left/top and follows scroll/resize from JS. While `open`, the side
23
+ * chosen at open is kept until it no longer fits and the other side does, scrolls inside
24
+ * the overlay itself are ignored, and a size change of either element re-places it.
25
+ * Returns a `reposition` fn for callers that want to nudge it manually (e.g. right when a
26
+ * native popover's `toggle` fires).
19
27
  */
20
28
  declare function useAnchoredPosition(anchor: RefObject<HTMLElement | null> | HTMLElement | null | undefined, floatingRef: RefObject<HTMLElement | null>, { open, placement, offset, padding }: UseAnchoredPositionOptions): () => void;
21
29
 
22
- export { type UseAnchoredPositionOptions, useAnchoredPosition };
30
+ export { type UseAnchoredPositionOptions, resolveAnchor, useAnchoredPosition };