@underverse-ui/underverse 2.0.17 → 2.0.18

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  cn
3
- } from "./chunk-EPBYGZHZ.js";
3
+ } from "./chunk-PZCD342B.js";
4
4
 
5
5
  // src/components/emoji-ui.tsx
6
6
  import React from "react";
@@ -71,4 +71,4 @@ export {
71
71
  getEmojiImageUrl,
72
72
  EmojiGridButton
73
73
  };
74
- //# sourceMappingURL=chunk-SKCO2GFM.js.map
74
+ //# sourceMappingURL=chunk-SBDZKVUJ.js.map
package/dist/index.cjs CHANGED
@@ -2416,11 +2416,13 @@ function UEditorPortalProvider({
2416
2416
  function useUEditorPortalContainerGetter() {
2417
2417
  return React12.useContext(UEditorPortalContext);
2418
2418
  }
2419
- function resolveUEditorPortalContainer(getPortalContainer) {
2419
+ function resolveUEditorPortalContainer(getPortalContainer, fallbackGetPortalContainer) {
2420
2420
  if (typeof document === "undefined") return null;
2421
- if (getPortalContainer) {
2421
+ const getters = [getPortalContainer, fallbackGetPortalContainer];
2422
+ for (const getter of getters) {
2423
+ if (!getter) continue;
2422
2424
  try {
2423
- const container = getPortalContainer();
2425
+ const container = getter();
2424
2426
  if (container) return container;
2425
2427
  } catch {
2426
2428
  }
@@ -3967,24 +3969,26 @@ var init_Modal = __esm({
3967
3969
  });
3968
3970
 
3969
3971
  // src/utils/animations.ts
3970
- function ensureAnimationStylesInjected() {
3971
- if (typeof document !== "undefined") {
3972
+ function ensureAnimationStylesInjected(targetDocument) {
3973
+ const resolvedDocument = targetDocument ?? (typeof document !== "undefined" ? document : void 0);
3974
+ if (resolvedDocument) {
3972
3975
  const styleId = "shadcn-animations";
3973
- if (!document.getElementById(styleId)) {
3974
- const styleElement = document.createElement("style");
3976
+ if (!resolvedDocument.getElementById(styleId)) {
3977
+ const styleElement = resolvedDocument.createElement("style");
3975
3978
  styleElement.id = styleId;
3976
3979
  styleElement.textContent = shadcnAnimationStyles;
3977
- document.head.appendChild(styleElement);
3980
+ resolvedDocument.head.appendChild(styleElement);
3978
3981
  }
3979
3982
  }
3980
3983
  }
3981
- function useShadCNAnimations() {
3984
+ function useShadCNAnimations(targetDocument) {
3982
3985
  (0, import_react14.useEffect)(() => {
3983
- ensureAnimationStylesInjected();
3984
- }, []);
3986
+ if (targetDocument === null) return;
3987
+ ensureAnimationStylesInjected(targetDocument);
3988
+ }, [targetDocument]);
3985
3989
  }
3986
- function injectAnimationStyles() {
3987
- ensureAnimationStylesInjected();
3990
+ function injectAnimationStyles(targetDocument) {
3991
+ ensureAnimationStylesInjected(targetDocument);
3988
3992
  }
3989
3993
  function getAnimationStyles() {
3990
3994
  return shadcnAnimationStyles;
@@ -4193,20 +4197,30 @@ function overflowAmount(left, width, viewportWidth, padding) {
4193
4197
  return overflowLeft + overflowRight;
4194
4198
  }
4195
4199
  function computePopoverPosition(args) {
4196
- const { triggerRect, contentSize, placement, offset, padding, viewport } = args;
4200
+ const { triggerRect, contentSize, placement, offset, padding, viewport, constrainToAvailableHeight } = args;
4197
4201
  let { side, align } = normalizePlacement(placement);
4202
+ const availableBelow = Math.max(0, viewport.height - padding - triggerRect.bottom - offset);
4203
+ const availableAbove = Math.max(0, triggerRect.top - padding - offset);
4198
4204
  if (side === "bottom") {
4199
- const bottomTop = triggerRect.bottom + offset;
4200
- const overflowsBottom = bottomTop + contentSize.height > viewport.height - padding;
4201
- const topTop = triggerRect.top - offset - contentSize.height;
4202
- const fitsTop = topTop >= padding;
4203
- if (overflowsBottom && fitsTop) side = "top";
4205
+ if (constrainToAvailableHeight) {
4206
+ if (contentSize.height > availableBelow && availableAbove > availableBelow) side = "top";
4207
+ } else {
4208
+ const bottomTop = triggerRect.bottom + offset;
4209
+ const overflowsBottom = bottomTop + contentSize.height > viewport.height - padding;
4210
+ const topTop = triggerRect.top - offset - contentSize.height;
4211
+ const fitsTop = topTop >= padding;
4212
+ if (overflowsBottom && fitsTop) side = "top";
4213
+ }
4204
4214
  } else if (side === "top") {
4205
- const topTop = triggerRect.top - offset - contentSize.height;
4206
- const overflowsTop = topTop < padding;
4207
- const bottomTop = triggerRect.bottom + offset;
4208
- const fitsBottom = bottomTop + contentSize.height <= viewport.height - padding;
4209
- if (overflowsTop && fitsBottom) side = "bottom";
4215
+ if (constrainToAvailableHeight) {
4216
+ if (contentSize.height > availableAbove && availableBelow > availableAbove) side = "bottom";
4217
+ } else {
4218
+ const topTop = triggerRect.top - offset - contentSize.height;
4219
+ const overflowsTop = topTop < padding;
4220
+ const bottomTop = triggerRect.bottom + offset;
4221
+ const fitsBottom = bottomTop + contentSize.height <= viewport.height - padding;
4222
+ if (overflowsTop && fitsBottom) side = "bottom";
4223
+ }
4210
4224
  } else if (side === "right") {
4211
4225
  const rightLeft = triggerRect.right + offset;
4212
4226
  const overflowsRight = rightLeft + contentSize.width > viewport.width - padding;
@@ -4230,16 +4244,18 @@ function computePopoverPosition(args) {
4230
4244
  if (align === "start" && startOverflow > 0 && endOverflow < startOverflow) align = "end";
4231
4245
  if (align === "end" && endOverflow > 0 && startOverflow < endOverflow) align = "start";
4232
4246
  left = align === "end" ? leftEnd : leftStart;
4233
- top = side === "top" ? triggerRect.top - offset - contentSize.height : triggerRect.bottom + offset;
4247
+ const availableHeight = side === "top" ? availableAbove : availableBelow;
4248
+ const renderedHeight = constrainToAvailableHeight ? Math.min(contentSize.height, availableHeight) : contentSize.height;
4249
+ top = side === "top" ? triggerRect.top - offset - renderedHeight : triggerRect.bottom + offset;
4234
4250
  left = clamp2(left, padding, viewport.width - contentSize.width - padding);
4235
- top = clamp2(top, padding, viewport.height - contentSize.height - padding);
4236
- return { top, left, side, align };
4251
+ top = clamp2(top, padding, viewport.height - renderedHeight - padding);
4252
+ return { top, left, side, align, availableHeight };
4237
4253
  }
4238
4254
  top = triggerRect.top;
4239
4255
  left = side === "left" ? triggerRect.left - offset - contentSize.width : triggerRect.right + offset;
4240
4256
  left = clamp2(left, padding, viewport.width - contentSize.width - padding);
4241
4257
  top = clamp2(top, padding, viewport.height - contentSize.height - padding);
4242
- return { top, left, side, align };
4258
+ return { top, left, side, align, availableHeight: Math.max(0, viewport.height - padding * 2) };
4243
4259
  }
4244
4260
  var React25, import_react_dom3, import_jsx_runtime27, clamp2, Popover;
4245
4261
  var init_Popover = __esm({
@@ -4270,7 +4286,9 @@ var init_Popover = __esm({
4270
4286
  onOpenChange,
4271
4287
  matchTriggerWidth = false,
4272
4288
  contentWidth,
4273
- borderMode
4289
+ borderMode,
4290
+ getPortalContainer,
4291
+ constrainToAvailableHeight = false
4274
4292
  }) => {
4275
4293
  const isControlled = open !== void 0;
4276
4294
  const [internalOpen, setInternalOpen] = React25.useState(false);
@@ -4278,14 +4296,14 @@ var init_Popover = __esm({
4278
4296
  const positionerRef = React25.useRef(null);
4279
4297
  const panelRef = React25.useRef(null);
4280
4298
  const lastAppliedRef = React25.useRef(null);
4281
- useShadCNAnimations();
4282
4299
  const globalConfig = useUnderverseUIConfig();
4283
4300
  const resolvedBorderMode = borderMode ?? globalConfig.popover?.borderMode ?? globalConfig.borderMode;
4284
4301
  const isOpen = isControlled ? open : internalOpen;
4285
- const getPortalContainer = useUEditorPortalContainerGetter();
4286
- const portalContainer = isOpen ? resolveUEditorPortalContainer(getPortalContainer) : null;
4302
+ const contextGetPortalContainer = useUEditorPortalContainerGetter();
4303
+ const portalContainer = isOpen ? resolveUEditorPortalContainer(getPortalContainer, contextGetPortalContainer) : null;
4287
4304
  const portalDocument = portalContainer?.ownerDocument ?? null;
4288
4305
  const portalWindow = portalDocument?.defaultView ?? null;
4306
+ useShadCNAnimations(portalDocument);
4289
4307
  const setIsOpen = React25.useCallback(
4290
4308
  (next) => {
4291
4309
  if (!isControlled) setInternalOpen(next);
@@ -4295,20 +4313,14 @@ var init_Popover = __esm({
4295
4313
  );
4296
4314
  const offset = 4;
4297
4315
  const padding = 8;
4298
- const triggerSelector = React25.useId();
4299
4316
  const initialPlacement = React25.useMemo(() => normalizePlacement(placement), [placement]);
4300
- React25.useLayoutEffect(() => {
4301
- if (typeof document === "undefined") return;
4302
- const triggerEl = document.querySelector(`[data-underverse-popover-trigger="${triggerSelector}"]`);
4303
- if (triggerEl) {
4304
- triggerRef.current = triggerEl;
4305
- }
4306
- }, [triggerSelector, trigger]);
4307
4317
  const updatePosition = React25.useCallback(() => {
4308
4318
  const triggerEl = triggerRef.current;
4309
4319
  const positionerEl = positionerRef.current;
4310
4320
  const panelEl = panelRef.current;
4311
- if (!triggerEl || !positionerEl || !panelEl) return;
4321
+ if (!triggerEl || !positionerEl || !panelEl || !portalWindow) return;
4322
+ const contentEl = panelEl.firstElementChild;
4323
+ if (!contentEl) return;
4312
4324
  const triggerRect = triggerEl.getBoundingClientRect();
4313
4325
  const widthWanted = matchTriggerWidth ? triggerRect.width : contentWidth;
4314
4326
  const widthPx = widthWanted == null ? void 0 : Math.max(0, Math.round(widthWanted));
@@ -4322,7 +4334,7 @@ var init_Popover = __esm({
4322
4334
  const measuredHeight = positionerEl.offsetHeight;
4323
4335
  const contentRect = positionerEl.getBoundingClientRect();
4324
4336
  const contentBoxWidth = measuredWidth || contentRect.width;
4325
- const contentBoxHeight = measuredHeight || contentRect.height;
4337
+ const contentBoxHeight = Math.max(measuredHeight, contentRect.height, contentEl.scrollHeight);
4326
4338
  const next = computePopoverPosition({
4327
4339
  triggerRect,
4328
4340
  contentSize: { width: contentBoxWidth, height: contentBoxHeight },
@@ -4330,20 +4342,31 @@ var init_Popover = __esm({
4330
4342
  offset,
4331
4343
  padding,
4332
4344
  viewport: {
4333
- width: portalWindow?.innerWidth ?? window.innerWidth,
4334
- height: portalWindow?.innerHeight ?? window.innerHeight
4335
- }
4345
+ width: portalWindow.innerWidth,
4346
+ height: portalWindow.innerHeight
4347
+ },
4348
+ constrainToAvailableHeight
4336
4349
  });
4337
4350
  const top = Math.round(next.top);
4338
4351
  const left = Math.round(next.left);
4339
- const applied = prevApplied && Math.abs(prevApplied.top - top) < 0.5 && Math.abs(prevApplied.left - left) < 0.5 && prevApplied.side === next.side && prevApplied.align === next.align && prevApplied.width === widthPx;
4352
+ const applied = prevApplied && Math.abs(prevApplied.top - top) < 0.5 && Math.abs(prevApplied.left - left) < 0.5 && prevApplied.side === next.side && prevApplied.align === next.align && prevApplied.width === widthPx && prevApplied.availableHeight === next.availableHeight;
4340
4353
  if (applied) return;
4341
- lastAppliedRef.current = { top, left, side: next.side, align: next.align, width: widthPx };
4354
+ lastAppliedRef.current = {
4355
+ top,
4356
+ left,
4357
+ side: next.side,
4358
+ align: next.align,
4359
+ width: widthPx,
4360
+ availableHeight: next.availableHeight
4361
+ };
4342
4362
  positionerEl.style.transform = `translate3d(${left}px, ${top}px, 0)`;
4363
+ positionerEl.style.setProperty("--underverse-popover-available-height", `${next.availableHeight}px`);
4364
+ positionerEl.dataset.side = next.side;
4365
+ positionerEl.dataset.align = next.align;
4343
4366
  panelEl.style.transformOrigin = getTransformOrigin2(next.side, next.align);
4344
4367
  if (positionerEl.style.visibility !== "visible") positionerEl.style.visibility = "visible";
4345
4368
  if (positionerEl.style.pointerEvents !== "auto") positionerEl.style.pointerEvents = "auto";
4346
- }, [placement, matchTriggerWidth, contentWidth, portalWindow]);
4369
+ }, [placement, matchTriggerWidth, contentWidth, portalWindow, constrainToAvailableHeight]);
4347
4370
  React25.useLayoutEffect(() => {
4348
4371
  if (!isOpen || !portalWindow) return;
4349
4372
  updatePosition();
@@ -4492,7 +4515,6 @@ var init_Popover = __esm({
4492
4515
  ref: mergeRefs(childRef, (node) => {
4493
4516
  triggerRef.current = node;
4494
4517
  }),
4495
- "data-underverse-popover-trigger": triggerSelector,
4496
4518
  onClick: chainEventHandlers(
4497
4519
  (e) => {
4498
4520
  triggerRef.current = e.currentTarget;
@@ -14366,6 +14388,7 @@ var Combobox = ({
14366
14388
  variant = "default",
14367
14389
  allowClear = false,
14368
14390
  usePortal = true,
14391
+ getPortalContainer,
14369
14392
  label,
14370
14393
  labelClassName,
14371
14394
  required,
@@ -14407,7 +14430,6 @@ var Combobox = ({
14407
14430
  const [activeIndex, setActiveIndex] = React31.useState(null);
14408
14431
  const [localRequiredError, setLocalRequiredError] = React31.useState();
14409
14432
  const [triggerBackgroundColor, setTriggerBackgroundColor] = React31.useState();
14410
- useShadCNAnimations();
14411
14433
  const inputRef = React31.useRef(null);
14412
14434
  const optionsViewportRef = React31.useRef(null);
14413
14435
  useOverlayScrollbarTarget(optionsViewportRef, { enabled: open && useOverlayScrollbar && !virtualized });
@@ -14448,10 +14470,16 @@ var Combobox = ({
14448
14470
  });
14449
14471
  const virtualItems = canVirtualize ? optionVirtualizer.getVirtualItems() : [];
14450
14472
  const triggerRef = React31.useRef(null);
14473
+ React31.useEffect(() => {
14474
+ const triggerDocument = triggerRef.current?.ownerDocument;
14475
+ if (triggerDocument) injectAnimationStyles(triggerDocument);
14476
+ }, []);
14451
14477
  const updateTriggerBackgroundColor = React31.useCallback(() => {
14452
14478
  const trigger = triggerRef.current;
14453
14479
  if (!trigger) return;
14454
- const backgroundColor = window.getComputedStyle(trigger).backgroundColor;
14480
+ const triggerWindow = trigger.ownerDocument.defaultView;
14481
+ if (!triggerWindow) return;
14482
+ const backgroundColor = triggerWindow.getComputedStyle(trigger).backgroundColor;
14455
14483
  setTriggerBackgroundColor((current) => current === backgroundColor ? current : backgroundColor);
14456
14484
  }, []);
14457
14485
  const scrollVirtualListToIndex = React31.useCallback((index) => {
@@ -14491,15 +14519,21 @@ var Combobox = ({
14491
14519
  setActiveIndex(null);
14492
14520
  scrollVirtualListToStart();
14493
14521
  } else if (enableSearch) {
14494
- setTimeout(() => {
14522
+ const triggerWindow = triggerRef.current?.ownerDocument.defaultView;
14523
+ if (!triggerWindow) return void 0;
14524
+ const timeoutId = triggerWindow.setTimeout(() => {
14495
14525
  inputRef.current?.focus();
14496
14526
  }, 100);
14527
+ return () => triggerWindow.clearTimeout(timeoutId);
14497
14528
  }
14529
+ return void 0;
14498
14530
  }, [enableSearch, open, scrollVirtualListToStart]);
14499
14531
  React31.useEffect(() => {
14500
14532
  if (!onSearchChange) return void 0;
14501
- const timeoutId = window.setTimeout(() => onSearchChange(query), searchDebounceMs);
14502
- return () => window.clearTimeout(timeoutId);
14533
+ const triggerWindow = triggerRef.current?.ownerDocument.defaultView;
14534
+ if (!triggerWindow) return void 0;
14535
+ const timeoutId = triggerWindow.setTimeout(() => onSearchChange(query), searchDebounceMs);
14536
+ return () => triggerWindow.clearTimeout(timeoutId);
14503
14537
  }, [onSearchChange, query, searchDebounceMs]);
14504
14538
  React31.useEffect(() => {
14505
14539
  if (process.env.NODE_ENV !== "production" && options.length > 300 && !virtualized && searchMode !== "manual" && maxInitialOptions === void 0) {
@@ -14513,13 +14547,15 @@ var Combobox = ({
14513
14547
  setTriggerBackgroundColor(void 0);
14514
14548
  return void 0;
14515
14549
  }
14550
+ const triggerWindow = triggerRef.current?.ownerDocument.defaultView;
14551
+ if (!triggerWindow) return void 0;
14516
14552
  let raf = 0;
14517
14553
  const tick = () => {
14518
14554
  updateTriggerBackgroundColor();
14519
- raf = window.requestAnimationFrame(tick);
14555
+ raf = triggerWindow.requestAnimationFrame(tick);
14520
14556
  };
14521
14557
  tick();
14522
- return () => window.cancelAnimationFrame(raf);
14558
+ return () => triggerWindow.cancelAnimationFrame(raf);
14523
14559
  }, [matchTriggerBackground, open, updateTriggerBackgroundColor]);
14524
14560
  const selectedOption = findOptionByValue(options, value) ?? (selectedOptionProp && getOptionValue(selectedOptionProp) === value ? selectedOptionProp : void 0);
14525
14561
  const displayValue = selectedOption ? getOptionLabel(selectedOption) : "";
@@ -14631,9 +14667,10 @@ var Combobox = ({
14631
14667
  {
14632
14668
  "data-combobox-dropdown": true,
14633
14669
  "data-state": open ? "open" : "closed",
14634
- className: cn("w-full overflow-hidden", getPanelBorderRadiusClass(resolvedBorderMode)),
14670
+ style: usePortal ? { maxHeight: "var(--underverse-popover-available-height)" } : void 0,
14671
+ className: cn("w-full overflow-hidden", usePortal && "flex min-h-0 flex-col", getPanelBorderRadiusClass(resolvedBorderMode)),
14635
14672
  children: [
14636
- enableSearch && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: cn("relative border-b border-border/30", size === "xs" ? "p-1.5" : size === "sm" ? "p-2" : size === "lg" ? "p-3" : size === "xl" ? "p-3.5" : "p-2.5"), children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "relative", children: [
14673
+ enableSearch && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: cn("relative shrink-0 border-b border-border/30", size === "xs" ? "p-1.5" : size === "sm" ? "p-2" : size === "lg" ? "p-3" : size === "xl" ? "p-3.5" : "p-2.5"), children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "relative", children: [
14637
14674
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
14638
14675
  import_lucide_react15.Search,
14639
14676
  {
@@ -14703,7 +14740,7 @@ var Combobox = ({
14703
14740
  role: "listbox",
14704
14741
  "aria-labelledby": labelId,
14705
14742
  "data-os-ignore": virtualized ? "" : void 0,
14706
- className: cn("overflow-y-auto overscroll-contain", (!useOverlayScrollbar || virtualized) && comboboxScrollClassName),
14743
+ className: cn("overflow-y-auto overscroll-contain", usePortal && "min-h-0", (!useOverlayScrollbar || virtualized) && comboboxScrollClassName),
14707
14744
  style: { maxHeight },
14708
14745
  children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: cn(size === "xs" ? "p-1" : size === "sm" ? "p-1" : size === "lg" || size === "xl" ? "p-2" : "p-1.5"), children: loading2 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "px-3 py-10 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
14709
14746
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "relative", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "w-10 h-10 rounded-full border-2 border-primary/20 border-t-primary animate-spin" }) }),
@@ -14911,6 +14948,8 @@ var Combobox = ({
14911
14948
  matchTriggerWidth: true,
14912
14949
  className: "z-50",
14913
14950
  borderMode: resolvedBorderMode,
14951
+ getPortalContainer,
14952
+ constrainToAvailableHeight: true,
14914
14953
  contentClassName: "p-0 overflow-hidden",
14915
14954
  contentProps: { style: dropdownBackgroundStyle },
14916
14955
  children: dropdownBody