@geomak/ui 7.4.0 → 7.4.2

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.
package/dist/index.cjs CHANGED
@@ -4577,10 +4577,6 @@ function List2({
4577
4577
  );
4578
4578
  }) });
4579
4579
  }
4580
- var rectOf = (el) => {
4581
- const r = el.getBoundingClientRect();
4582
- return { left: r.left, top: r.top, width: r.width, height: r.height };
4583
- };
4584
4580
  var TOGGLE_POSITION_CLASS = {
4585
4581
  "top-left": "top-2 left-2",
4586
4582
  "top-right": "top-2 right-2",
@@ -4600,55 +4596,79 @@ function ScalableContainer({
4600
4596
  collapseIcon,
4601
4597
  togglePosition = "top-right",
4602
4598
  expandContainerRef,
4599
+ expandRatio = 3,
4603
4600
  className = ""
4604
4601
  }) {
4605
4602
  const containerRef = React30.useRef(null);
4606
4603
  const [internalScaled, setInternalScaled] = React30.useState(false);
4607
4604
  const isScaled = expanded ?? internalScaled;
4608
4605
  const reduced = framerMotion.useReducedMotion();
4609
- const usePortal = expandContainerRef != null;
4610
- const [overlay, setOverlay] = React30.useState("closed");
4611
- const [fromRect, setFromRect] = React30.useState(null);
4612
- const [targetRect, setTargetRect] = React30.useState(null);
4606
+ const usePush = expandContainerRef != null;
4607
+ const grownRef = React30.useRef([]);
4613
4608
  const prevScaled = React30.useRef(isScaled);
4614
- React30.useEffect(() => {
4615
- if (!usePortal || isScaled === prevScaled.current) return;
4616
- prevScaled.current = isScaled;
4617
- if (isScaled) {
4618
- const src = containerRef.current ? rectOf(containerRef.current) : null;
4619
- const tgt = expandContainerRef.current ? rectOf(expandContainerRef.current) : null;
4620
- if (src && tgt) {
4621
- setFromRect(src);
4622
- setTargetRect(tgt);
4623
- setOverlay("open");
4609
+ const kickResizeDuringTransition = () => {
4610
+ if (typeof window === "undefined") return;
4611
+ const kick = () => window.dispatchEvent(new Event("resize"));
4612
+ const interval = window.setInterval(kick, 80);
4613
+ window.setTimeout(() => {
4614
+ window.clearInterval(interval);
4615
+ kick();
4616
+ }, reduced ? 0 : 400);
4617
+ };
4618
+ const growAncestors = () => {
4619
+ const bound = expandContainerRef?.current;
4620
+ if (!bound || !containerRef.current) return;
4621
+ const grown = [];
4622
+ let el = containerRef.current.parentElement;
4623
+ while (el && el !== bound && bound.contains(el)) {
4624
+ const parent = el.parentElement;
4625
+ if (parent && getComputedStyle(parent).display.includes("flex")) {
4626
+ grown.push({
4627
+ el,
4628
+ prev: {
4629
+ flexGrow: el.style.flexGrow,
4630
+ flexBasis: el.style.flexBasis,
4631
+ transition: el.style.transition
4632
+ }
4633
+ });
4634
+ const grow = `flex-grow ${reduced ? 0 : 0.32}s cubic-bezier(0.16, 1, 0.3, 1), flex-basis ${reduced ? 0 : 0.32}s cubic-bezier(0.16, 1, 0.3, 1)`;
4635
+ el.style.transition = el.style.transition ? `${el.style.transition}, ${grow}` : grow;
4636
+ el.style.flexBasis = "0%";
4637
+ el.style.flexGrow = String(expandRatio);
4624
4638
  }
4625
- } else if (containerRef.current) {
4626
- setTargetRect(rectOf(containerRef.current));
4627
- setOverlay("closing");
4639
+ el = parent;
4628
4640
  }
4629
- }, [isScaled, usePortal, expandContainerRef]);
4630
- React30.useEffect(() => {
4631
- if (overlay !== "closing") return;
4632
- const t = window.setTimeout(() => setOverlay("closed"), reduced ? 0 : 360);
4633
- return () => window.clearTimeout(t);
4634
- }, [overlay, reduced]);
4641
+ grownRef.current = grown;
4642
+ };
4643
+ const restoreAncestors = () => {
4644
+ for (const { el, prev } of grownRef.current) {
4645
+ el.style.flexGrow = prev.flexGrow;
4646
+ el.style.flexBasis = prev.flexBasis;
4647
+ window.setTimeout(() => {
4648
+ el.style.transition = prev.transition;
4649
+ }, reduced ? 0 : 360);
4650
+ }
4651
+ grownRef.current = [];
4652
+ };
4635
4653
  React30.useEffect(() => {
4636
- if (overlay !== "open" || !expandContainerRef?.current) return;
4637
- const update = () => {
4638
- if (expandContainerRef.current) setTargetRect(rectOf(expandContainerRef.current));
4639
- };
4640
- window.addEventListener("resize", update);
4641
- window.addEventListener("scroll", update, true);
4642
- return () => {
4643
- window.removeEventListener("resize", update);
4644
- window.removeEventListener("scroll", update, true);
4645
- };
4646
- }, [overlay, expandContainerRef]);
4654
+ if (!usePush || isScaled === prevScaled.current) return;
4655
+ prevScaled.current = isScaled;
4656
+ if (isScaled) growAncestors();
4657
+ else restoreAncestors();
4658
+ kickResizeDuringTransition();
4659
+ }, [isScaled, usePush]);
4660
+ React30.useEffect(() => () => {
4661
+ for (const { el, prev } of grownRef.current) {
4662
+ el.style.flexGrow = prev.flexGrow;
4663
+ el.style.flexBasis = prev.flexBasis;
4664
+ el.style.transition = prev.transition;
4665
+ }
4666
+ }, []);
4647
4667
  const onToggle = () => {
4648
4668
  const next = !isScaled;
4649
4669
  if (expanded === void 0) setInternalScaled(next);
4650
4670
  onExpandedChange?.(next);
4651
- if (next && !usePortal) {
4671
+ if (next && !usePush) {
4652
4672
  window.setTimeout(
4653
4673
  () => containerRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" }),
4654
4674
  reduced ? 0 : 340
@@ -4656,79 +4676,56 @@ function ScalableContainer({
4656
4676
  }
4657
4677
  };
4658
4678
  const wrapperClass = isScaled ? assignClassOnClick : void 0;
4659
- const overlayActive = usePortal && overlay !== "closed";
4660
- const toggleButton = (scaled) => /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { placement: "bottom", title: scaled ? "Collapse" : "Expand", children: /* @__PURE__ */ jsxRuntime.jsx(
4661
- "button",
4679
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4680
+ framerMotion.motion.div,
4662
4681
  {
4663
- type: "button",
4664
- onClick: onToggle,
4665
- "aria-label": scaled ? "Collapse container" : "Expand container",
4666
- "aria-expanded": scaled,
4667
- className: [
4668
- "absolute z-10",
4669
- TOGGLE_POSITION_CLASS[togglePosition],
4670
- "w-7 h-7 inline-flex items-center justify-center",
4671
- "rounded-md bg-surface/80 backdrop-blur-sm border border-border",
4672
- "text-foreground-secondary hover:text-foreground hover:bg-surface",
4673
- "shadow-sm transition-colors duration-150",
4674
- "focus:outline-none focus-visible:ring-2 focus-visible:ring-accent"
4675
- ].join(" "),
4676
- children: scaled ? collapseIcon ?? /* @__PURE__ */ jsxRuntime.jsx(CollapseIcon, {}) : expandIcon ?? /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, {})
4677
- }
4678
- ) });
4679
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4680
- /* @__PURE__ */ jsxRuntime.jsxs(
4681
- framerMotion.motion.div,
4682
- {
4683
- ref: containerRef,
4684
- animate: {
4685
- // Breakout mode never grows in place — the in-flow box stays
4686
- // at its resting size and acts as the collapse target.
4687
- width: isScaled && !usePortal ? expandedWidth : width,
4688
- height: isScaled && !usePortal ? expandedHeight : height
4689
- },
4690
- transition: reduced ? { duration: 0 } : {
4691
- width: { type: "tween", duration: 0.32, ease: [0.16, 1, 0.3, 1] },
4692
- height: { type: "tween", duration: 0.32, ease: [0.16, 1, 0.3, 1] }
4693
- },
4694
- className: cx(
4695
- "relative rounded-lg overflow-hidden",
4696
- // OS-window aesthetic: subtle elevation at rest, lifted shadow
4697
- // when expanded. No background colour change.
4698
- isScaled && !usePortal ? "shadow-2xl" : "shadow-md",
4699
- "transition-shadow duration-300",
4700
- className
4701
- ),
4702
- children: [
4703
- !overlayActive && toggleButton(isScaled),
4704
- !overlayActive && /* @__PURE__ */ jsxRuntime.jsx("div", { className: wrapperClass, children })
4705
- ]
4706
- }
4707
- ),
4708
- overlayActive && fromRect && targetRect && reactDom.createPortal(
4709
- /* @__PURE__ */ jsxRuntime.jsxs(
4710
- framerMotion.motion.div,
4711
- {
4712
- initial: { ...fromRect },
4713
- animate: { ...targetRect },
4714
- transition: reduced ? { duration: 0 } : { type: "tween", duration: 0.32, ease: [0.16, 1, 0.3, 1] },
4715
- onAnimationComplete: () => {
4716
- if (overlay === "closing") setOverlay("closed");
4717
- },
4718
- style: { position: "fixed" },
4719
- className: cx(
4720
- "z-dropdown rounded-lg overflow-hidden bg-surface shadow-2xl",
4721
- className
4722
- ),
4723
- children: [
4724
- toggleButton(isScaled),
4725
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cx("h-full w-full", wrapperClass), children })
4726
- ]
4727
- }
4682
+ ref: containerRef,
4683
+ style: {
4684
+ width: isScaled && !usePush ? expandedWidth : width,
4685
+ height: isScaled && !usePush ? expandedHeight : height
4686
+ },
4687
+ animate: {
4688
+ // Push mode keeps the container filling its (now growing)
4689
+ // wrapper — the wrapper's flex-grow does the work.
4690
+ width: isScaled && !usePush ? expandedWidth : width,
4691
+ height: isScaled && !usePush ? expandedHeight : height
4692
+ },
4693
+ transition: reduced ? { duration: 0 } : {
4694
+ width: { type: "tween", duration: 0.32, ease: [0.16, 1, 0.3, 1] },
4695
+ height: { type: "tween", duration: 0.32, ease: [0.16, 1, 0.3, 1] }
4696
+ },
4697
+ className: cx(
4698
+ "relative rounded-lg overflow-hidden",
4699
+ // OS-window aesthetic: subtle elevation at rest, lifted shadow
4700
+ // when expanded. No background colour change.
4701
+ isScaled ? "shadow-2xl" : "shadow-md",
4702
+ "transition-shadow duration-300",
4703
+ className
4728
4704
  ),
4729
- document.body
4730
- )
4731
- ] });
4705
+ children: [
4706
+ /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { placement: "bottom", title: isScaled ? "Collapse" : "Expand", children: /* @__PURE__ */ jsxRuntime.jsx(
4707
+ "button",
4708
+ {
4709
+ type: "button",
4710
+ onClick: onToggle,
4711
+ "aria-label": isScaled ? "Collapse container" : "Expand container",
4712
+ "aria-expanded": isScaled,
4713
+ className: [
4714
+ "absolute z-10",
4715
+ TOGGLE_POSITION_CLASS[togglePosition],
4716
+ "w-7 h-7 inline-flex items-center justify-center",
4717
+ "rounded-md bg-surface/80 backdrop-blur-sm border border-border",
4718
+ "text-foreground-secondary hover:text-foreground hover:bg-surface",
4719
+ "shadow-sm transition-colors duration-150",
4720
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-accent"
4721
+ ].join(" "),
4722
+ children: isScaled ? collapseIcon ?? /* @__PURE__ */ jsxRuntime.jsx(CollapseIcon, {}) : expandIcon ?? /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, {})
4723
+ }
4724
+ ) }),
4725
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cx("h-full w-full", wrapperClass), children })
4726
+ ]
4727
+ }
4728
+ );
4732
4729
  }
4733
4730
  function CollapseIcon() {
4734
4731
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "w-4 h-4", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 9L4 4M9 9V4M9 9H4M15 9L20 4M15 9V4M15 9H20M9 15L4 20M9 15V20M9 15H4M15 15L20 20M15 15V20M15 15H20" }) });