@ohhwells/bridge 0.1.121 → 0.1.122

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.js CHANGED
@@ -525,9 +525,9 @@ function styleSheetCss() {
525
525
  );
526
526
  }
527
527
  const pad = { tight: 40, balanced: 64, airy: 96 };
528
- for (const [spacing, px] of Object.entries(pad)) {
528
+ for (const [spacing, px2] of Object.entries(pad)) {
529
529
  rules.push(
530
- `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
530
+ `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px2}px !important; padding-bottom: ${px2}px !important; }`
531
531
  );
532
532
  }
533
533
  rules.push(
@@ -3094,12 +3094,12 @@ function deriveTemplateButtonStyle() {
3094
3094
  cs.borderBottomLeftRadius
3095
3095
  ].map((v) => v || "0px");
3096
3096
  const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
3097
- const px = (v) => parseFloat(v) || 0;
3098
- const lineHeight = px(cs.lineHeight) || px(cs.fontSize) * 1.2;
3099
- const contentH = btn.getBoundingClientRect().height - px(cs.borderTopWidth) - px(cs.borderBottomWidth);
3097
+ const px2 = (v) => parseFloat(v) || 0;
3098
+ const lineHeight = px2(cs.lineHeight) || px2(cs.fontSize) * 1.2;
3099
+ const contentH = btn.getBoundingClientRect().height - px2(cs.borderTopWidth) - px2(cs.borderBottomWidth);
3100
3100
  const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
3101
- const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom), impliedY);
3102
- const padX = Math.max(px(cs.paddingLeft), px(cs.paddingRight));
3101
+ const padY = Math.max(px2(cs.paddingTop), px2(cs.paddingBottom), impliedY);
3102
+ const padX = Math.max(px2(cs.paddingLeft), px2(cs.paddingRight));
3103
3103
  return {
3104
3104
  radius: radius || "10px",
3105
3105
  padding: `${padY}px ${padX}px`,
@@ -8016,6 +8016,14 @@ function ItemActionToolbar({
8016
8016
  e.preventDefault();
8017
8017
  e.stopPropagation();
8018
8018
  onStyle?.();
8019
+ },
8020
+ // Same as Edit link: mousedown opens the panel and remounts this toolbar
8021
+ // (styleActive flips). Without stopping click, the gesture lands on whatever
8022
+ // is now under the cursor — navbar CTAs park the toolbar below the button, so
8023
+ // that miss deselects and the panel closes as soon as it opens.
8024
+ onClick: (e) => {
8025
+ e.preventDefault();
8026
+ e.stopPropagation();
8019
8027
  }
8020
8028
  },
8021
8029
  children: /* @__PURE__ */ jsx11(Brush, { className: "size-4 shrink-0", "aria-hidden": true })
@@ -8338,20 +8346,32 @@ function ItemInteractionLayer({
8338
8346
  itemDragSurface = true,
8339
8347
  chromeGap,
8340
8348
  toolbarAlign = "center",
8349
+ hidden = false,
8341
8350
  className
8342
8351
  }) {
8352
+ const localRef = useRef2(null);
8353
+ useLayoutEffect2(() => {
8354
+ const node = localRef.current;
8355
+ if (!node) return;
8356
+ node.style.display = hidden ? "none" : "block";
8357
+ }, [hidden, rect.top, rect.left, rect.width, rect.height, state]);
8343
8358
  if (state === "default") return null;
8344
8359
  const gap = chromeGap ?? (state === "hover" ? HOVER_CHROME_GAP : SELECTION_CHROME_GAP);
8345
8360
  const isActive = state === "active-top" || state === "active-bottom";
8346
8361
  const isDragging = state === "dragging";
8347
8362
  const showToolbar = isActive && toolbar;
8348
8363
  const showDragHandle = (isActive || isDragging) && showHandle;
8349
- const itemDragEnabled = itemDragSurface && isActive && showHandle && !isDragging && !dragDisabled;
8364
+ const itemDragEnabled = itemDragSurface && isActive && !isDragging && !dragDisabled && Boolean(showHandle || onItemPointerDown);
8350
8365
  const useDetachedBelowToolbar = Boolean(showToolbar && state === "active-bottom" && toolbarBelowRect);
8366
+ const setRootRef = (node) => {
8367
+ localRef.current = node;
8368
+ if (typeof elRef === "function") elRef(node);
8369
+ else if (elRef) elRef.current = node;
8370
+ };
8351
8371
  return /* @__PURE__ */ jsxs5(
8352
8372
  "div",
8353
8373
  {
8354
- ref: elRef,
8374
+ ref: setRootRef,
8355
8375
  "data-ohw-item-interaction": "",
8356
8376
  "data-ohw-item-interaction-state": state,
8357
8377
  className: cn("pointer-events-none", className),
@@ -8361,7 +8381,8 @@ function ItemInteractionLayer({
8361
8381
  left: rect.left - gap,
8362
8382
  width: rect.width + gap * 2,
8363
8383
  height: rect.height + gap * 2,
8364
- zIndex: getChromeZIndex(state)
8384
+ zIndex: getChromeZIndex(state),
8385
+ display: hidden ? "none" : "block"
8365
8386
  },
8366
8387
  children: [
8367
8388
  /* @__PURE__ */ jsx12(
@@ -11182,6 +11203,13 @@ function findButtonAtPoint(x, y, media) {
11182
11203
  if (hits.length === 0) return null;
11183
11204
  return hits.reduce((best, el) => best.contains(el) ? el : best);
11184
11205
  }
11206
+ function findNavbarCtaAtPoint(x, y) {
11207
+ const hits = Array.from(
11208
+ document.querySelectorAll('[data-ohw-role="navbar-button"]')
11209
+ ).filter((el) => containsPoint(el, x, y) && !el.closest("[data-ohw-bridge]"));
11210
+ if (hits.length === 0) return null;
11211
+ return hits.reduce((best, el) => best.contains(el) ? el : best);
11212
+ }
11185
11213
 
11186
11214
  // src/lib/session-search.ts
11187
11215
  var STORAGE_KEY = "ohw-preserved-search";
@@ -11917,6 +11945,26 @@ import { useCallback as useCallback5, useEffect as useEffect8, useMemo as useMem
11917
11945
  import { createPortal as createPortal2 } from "react-dom";
11918
11946
  import { ArrowLeft as ArrowLeft2, Check as Check2 } from "lucide-react";
11919
11947
  import { usePathname, useRouter as useRouter2 } from "next/navigation";
11948
+
11949
+ // src/lib/parent-scroll.ts
11950
+ function visibleBand(parentScroll) {
11951
+ const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
11952
+ const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
11953
+ const bottom = Math.min(window.innerHeight, visibleCanvasTop + canvasH - iframeOffsetTop);
11954
+ return { top, bottom: Math.max(top, bottom) };
11955
+ }
11956
+ function getIframeVisibleClip(parentScroll) {
11957
+ if (!parentScroll) return null;
11958
+ return visibleBand(parentScroll);
11959
+ }
11960
+ function getIframeChromeClip(parentScroll) {
11961
+ if (!parentScroll) return null;
11962
+ const band = visibleBand(parentScroll);
11963
+ const promptBarH = Math.max(0, parentScroll.promptBarH ?? 0);
11964
+ return { top: band.top, bottom: Math.max(band.top, band.bottom - promptBarH) };
11965
+ }
11966
+
11967
+ // src/ui/link-modal/SectionPickerOverlay.tsx
11920
11968
  import { jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
11921
11969
  var DIM_OVERLAY = "rgba(0, 0, 0, 0.45)";
11922
11970
  function rectsEqual(a, b) {
@@ -12054,20 +12102,22 @@ function SectionPickerOverlay({
12054
12102
  []
12055
12103
  );
12056
12104
  useEffect8(() => {
12057
- const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
12058
- const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
12059
- const bottom = Math.min(
12060
- window.innerHeight,
12061
- visibleCanvasTop + canvasH - iframeOffsetTop
12062
- );
12105
+ const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH, promptBarH = 0) => {
12106
+ const next = getIframeVisibleClip({
12107
+ iframeOffsetTop,
12108
+ headerH: visibleCanvasTop,
12109
+ canvasH,
12110
+ promptBarH
12111
+ });
12112
+ if (!next) return;
12063
12113
  setChromeClip(
12064
- (prev) => prev.top === top && prev.bottom === bottom ? prev : { top, bottom: Math.max(top, bottom) }
12114
+ (prev) => prev.top === next.top && prev.bottom === next.bottom ? prev : next
12065
12115
  );
12066
12116
  };
12067
12117
  const onParentScroll = (e) => {
12068
12118
  if (e.data?.type !== "ow:parent-scroll") return;
12069
- const { iframeOffsetTop, headerH, canvasH } = e.data;
12070
- applyClip(iframeOffsetTop, headerH, canvasH);
12119
+ const { iframeOffsetTop, headerH, canvasH, promptBarH = 0 } = e.data;
12120
+ applyClip(iframeOffsetTop, headerH, canvasH, promptBarH);
12071
12121
  iframeOffsetTopRef.current = iframeOffsetTop;
12072
12122
  if (!pointerScreenRef.current && pointerRef.current) {
12073
12123
  pointerScreenRef.current = {
@@ -15737,36 +15787,50 @@ import { X as X5 } from "lucide-react";
15737
15787
  import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
15738
15788
  var PANEL_WIDTH = 256;
15739
15789
  var EDGE_MARGIN = 16;
15790
+ var HEADER_MIN = 56;
15740
15791
  function getVisibleClip(parentScroll) {
15741
15792
  const left = 0;
15742
15793
  const right = window.innerWidth;
15743
- if (!parentScroll) {
15794
+ const band = getIframeChromeClip(parentScroll);
15795
+ if (!band) {
15744
15796
  return { top: 0, bottom: window.innerHeight, left, right };
15745
15797
  }
15746
- const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
15747
- const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
15748
- const bottom = Math.min(window.innerHeight, visibleCanvasTop + canvasH - iframeOffsetTop);
15749
- return { top, bottom: Math.max(top, bottom), left, right };
15798
+ return { ...band, left, right };
15750
15799
  }
15751
- function defaultFloatingPanelPosition(parentScroll, panelHeight = 280) {
15800
+ function defaultFloatingPanelPosition(parentScroll, _panelHeight = 280) {
15752
15801
  const clip = getVisibleClip(parentScroll);
15753
15802
  return {
15754
15803
  x: Math.max(EDGE_MARGIN, clip.right - PANEL_WIDTH - EDGE_MARGIN),
15755
- y: Math.min(
15756
- Math.max(clip.top + EDGE_MARGIN, EDGE_MARGIN),
15757
- Math.max(clip.top + EDGE_MARGIN, clip.bottom - panelHeight - EDGE_MARGIN)
15758
- )
15804
+ y: clip.top + EDGE_MARGIN
15759
15805
  };
15760
15806
  }
15761
- function clampPosition(pos, parentScroll, panelW, panelH) {
15807
+ function clampX(x, panelW) {
15808
+ const left = EDGE_MARGIN;
15809
+ const maxX = Math.max(left, window.innerWidth - panelW - EDGE_MARGIN);
15810
+ return Math.min(Math.max(x, left), maxX);
15811
+ }
15812
+ function clampDragPosition(pos, parentScroll, panelW) {
15762
15813
  const clip = getVisibleClip(parentScroll);
15763
- const maxX = Math.max(clip.left + EDGE_MARGIN, clip.right - panelW - EDGE_MARGIN);
15764
- const maxY = Math.max(clip.top + EDGE_MARGIN, clip.bottom - panelH - EDGE_MARGIN);
15814
+ const minY = clip.top + EDGE_MARGIN;
15815
+ const maxY = Math.max(minY, clip.bottom - EDGE_MARGIN - HEADER_MIN);
15765
15816
  return {
15766
- x: Math.min(Math.max(pos.x, clip.left + EDGE_MARGIN), maxX),
15767
- y: Math.min(Math.max(pos.y, clip.top + EDGE_MARGIN), maxY)
15817
+ x: clampX(pos.x, panelW),
15818
+ y: Math.min(Math.max(pos.y, minY), maxY)
15768
15819
  };
15769
15820
  }
15821
+ function fitPanelInClip(homeY, parentScroll, preferredHeight) {
15822
+ const clip = getVisibleClip(parentScroll);
15823
+ const minY = clip.top + EDGE_MARGIN;
15824
+ const maxBottom = clip.bottom - EDGE_MARGIN;
15825
+ const band = Math.max(HEADER_MIN, maxBottom - minY);
15826
+ let y = Math.max(homeY, minY);
15827
+ const want = Math.min(Math.max(preferredHeight, HEADER_MIN), band);
15828
+ if (y + want > maxBottom) {
15829
+ y = Math.max(minY, maxBottom - want);
15830
+ }
15831
+ const maxHeight = Math.max(HEADER_MIN, Math.floor(maxBottom - y));
15832
+ return { y, maxHeight };
15833
+ }
15770
15834
  function FloatingPanel({
15771
15835
  open,
15772
15836
  title,
@@ -15783,19 +15847,54 @@ function FloatingPanel({
15783
15847
  const panelRef = useRef9(null);
15784
15848
  const [measured, setMeasured] = useState12({ w: PANEL_WIDTH, h: 280 });
15785
15849
  const dragRef = useRef9(null);
15786
- const resolved = position ?? defaultFloatingPanelPosition(parentScroll, measured.h);
15787
- const clamped = clampPosition(resolved, parentScroll, measured.w, measured.h);
15850
+ const homeRef = useRef9(null);
15851
+ if (!open) {
15852
+ homeRef.current = null;
15853
+ } else if (position) {
15854
+ homeRef.current = position;
15855
+ } else if (homeRef.current == null) {
15856
+ homeRef.current = defaultFloatingPanelPosition(parentScroll);
15857
+ }
15858
+ const [liveScroll, setLiveScroll] = useState12(parentScroll);
15859
+ const home = position ?? homeRef.current ?? defaultFloatingPanelPosition(parentScroll);
15860
+ const { y: fittedY, maxHeight } = fitPanelInClip(home.y, liveScroll, measured.h);
15861
+ const clamped = {
15862
+ x: clampX(home.x, measured.w),
15863
+ y: fittedY
15864
+ };
15865
+ useEffect11(() => {
15866
+ setLiveScroll(parentScroll);
15867
+ }, [parentScroll]);
15868
+ useEffect11(() => {
15869
+ if (!open) return;
15870
+ const onParentScroll = (e) => {
15871
+ if (e.data?.type !== "ow:parent-scroll") return;
15872
+ const { iframeOffsetTop, headerH, canvasH, promptBarH = 0 } = e.data;
15873
+ setLiveScroll({ iframeOffsetTop, headerH, canvasH, promptBarH });
15874
+ };
15875
+ window.addEventListener("message", onParentScroll);
15876
+ return () => window.removeEventListener("message", onParentScroll);
15877
+ }, [open]);
15878
+ const positionRef = useRef9(position);
15879
+ useLayoutEffect4(() => {
15880
+ if (!open) {
15881
+ positionRef.current = position;
15882
+ return;
15883
+ }
15884
+ if (position == null && positionRef.current != null) {
15885
+ homeRef.current = defaultFloatingPanelPosition(liveScroll ?? parentScroll);
15886
+ }
15887
+ positionRef.current = position;
15888
+ }, [open, position, liveScroll, parentScroll]);
15788
15889
  useLayoutEffect4(() => {
15789
15890
  if (!open || !panelRef.current) return;
15790
15891
  const el = panelRef.current;
15791
- const next = { w: el.offsetWidth || PANEL_WIDTH, h: el.offsetHeight || 280 };
15892
+ const header = el.querySelector("[data-ohw-floating-panel-header]");
15893
+ const body = el.querySelector("[data-ohw-floating-panel-body]");
15894
+ const contentH = (header?.offsetHeight ?? 0) + (body?.scrollHeight ?? 0) || el.scrollHeight || 280;
15895
+ const next = { w: el.offsetWidth || PANEL_WIDTH, h: contentH };
15792
15896
  setMeasured((prev) => prev.w === next.w && prev.h === next.h ? prev : next);
15793
15897
  }, [open, children, title, context]);
15794
- useEffect11(() => {
15795
- if (!open || !position || !onPositionChange) return;
15796
- const next = clampPosition(position, parentScroll, measured.w, measured.h);
15797
- if (next.x !== position.x || next.y !== position.y) onPositionChange(next);
15798
- }, [open, parentScroll, measured.w, measured.h, position, onPositionChange]);
15799
15898
  const onHeaderPointerDown = useCallback7(
15800
15899
  (e) => {
15801
15900
  if (e.button !== 0) return;
@@ -15829,18 +15928,17 @@ function FloatingPanel({
15829
15928
  return;
15830
15929
  }
15831
15930
  e.preventDefault();
15832
- const next = clampPosition(
15931
+ const next = clampDragPosition(
15833
15932
  {
15834
15933
  x: drag.originX + (e.clientX - drag.startX),
15835
15934
  y: drag.originY + (e.clientY - drag.startY)
15836
15935
  },
15837
- parentScroll,
15838
- measured.w,
15839
- measured.h
15936
+ liveScroll,
15937
+ measured.w
15840
15938
  );
15841
15939
  onPositionChange?.(next);
15842
15940
  },
15843
- [measured.h, measured.w, onPositionChange, parentScroll]
15941
+ [measured.w, onPositionChange, liveScroll]
15844
15942
  );
15845
15943
  const endDrag = useCallback7((e) => {
15846
15944
  const drag = dragRef.current;
@@ -15870,7 +15968,7 @@ function FloatingPanel({
15870
15968
  "fixed z-[2147483647] flex w-64 flex-col overflow-hidden rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
15871
15969
  className
15872
15970
  ),
15873
- style: { left: clamped.x, top: clamped.y },
15971
+ style: { left: clamped.x, top: clamped.y, maxHeight },
15874
15972
  onMouseDown: (e) => e.stopPropagation(),
15875
15973
  onPointerDown: (e) => e.stopPropagation(),
15876
15974
  onClick: (e) => e.stopPropagation(),
@@ -15879,7 +15977,7 @@ function FloatingPanel({
15879
15977
  "div",
15880
15978
  {
15881
15979
  "data-ohw-floating-panel-header": "",
15882
- className: "relative flex cursor-grab items-start gap-2 border-b border-border py-5 pl-5 pr-11 active:cursor-grabbing",
15980
+ className: "relative flex shrink-0 cursor-grab items-start gap-2 border-b border-border py-4! pl-5! pr-11! active:cursor-grabbing",
15883
15981
  onPointerDown: onHeaderPointerDown,
15884
15982
  onPointerMove: onHeaderPointerMove,
15885
15983
  onPointerUp: endDrag,
@@ -15914,7 +16012,11 @@ function FloatingPanel({
15914
16012
  "div",
15915
16013
  {
15916
16014
  "data-ohw-floating-panel-body": "",
15917
- className: cn("flex w-full flex-col gap-4 p-5", bodyClassName),
16015
+ className: cn(
16016
+ "flex min-h-0 w-full flex-1 flex-col gap-4 overflow-y-auto overscroll-contain p-4",
16017
+ bodyClassName
16018
+ ),
16019
+ style: { WebkitOverflowScrolling: "touch" },
15918
16020
  children
15919
16021
  }
15920
16022
  )
@@ -15992,199 +16094,1633 @@ function SocialsDisplayPanel({ display, onChange, className }) {
15992
16094
  ] });
15993
16095
  }
15994
16096
 
15995
- // src/lib/item-drag-interaction.ts
15996
- function disableNativeHrefDrag(el) {
15997
- if (el.draggable) el.draggable = false;
15998
- if (el.getAttribute("draggable") !== "false") {
15999
- el.setAttribute("draggable", "false");
16097
+ // src/ui/button-style-panel.tsx
16098
+ import { Check as Check3 } from "lucide-react";
16099
+
16100
+ // src/lib/button-styles.ts
16101
+ var BUTTON_VARIANTS_KEY = "__ohw_button_variants";
16102
+ var BUTTON_SIZES_KEY = "__ohw_button_sizes";
16103
+ var BUTTON_ASSIGNMENTS_KEY = "__ohw_button_assignments";
16104
+ var BUTTON_VARIANT_IDS = ["primary", "secondary", "ghost"];
16105
+ var BUTTON_SIZE_IDS = ["s", "m", "l"];
16106
+ var TRANSPARENT = "rgba(0, 0, 0, 0)";
16107
+ var REFERENCE_SIZES = {
16108
+ s: { height: 40, paddingTop: 8, paddingRight: 16, paddingBottom: 8, paddingLeft: 16, fontSize: 15, borderRadius: 8, gap: 8 },
16109
+ m: { height: 48, paddingTop: 12, paddingRight: 24, paddingBottom: 12, paddingLeft: 24, fontSize: 15, borderRadius: 8, gap: 8 },
16110
+ l: { height: 56, paddingTop: 16, paddingRight: 32, paddingBottom: 16, paddingLeft: 32, fontSize: 15, borderRadius: 8, gap: 8 }
16111
+ };
16112
+ function buttonStyleContext(_el) {
16113
+ return "Button";
16114
+ }
16115
+ function previewSlotBackground(el) {
16116
+ let node = el.parentElement;
16117
+ while (node && node !== el.ownerDocument.documentElement) {
16118
+ if (node.closest("[data-ohw-bridge], [data-ohw-bridge-root]")) {
16119
+ node = node.parentElement;
16120
+ continue;
16121
+ }
16122
+ const bg = getComputedStyle(node).backgroundColor;
16123
+ const parsed = parseColor(bg);
16124
+ if (parsed && !isTransparent(parsed)) return bg;
16125
+ node = node.parentElement;
16000
16126
  }
16127
+ const root = el.ownerDocument.documentElement;
16128
+ const style = getComputedStyle(root);
16129
+ for (const name of ["--brand-surface", "--brand-background", "--ohw-brand-light", "--color-light"]) {
16130
+ const value = style.getPropertyValue(name).trim();
16131
+ if (value) return value;
16132
+ }
16133
+ return "var(--brand-background, var(--ohw-brand-light, var(--color-light, #ffffff)))";
16001
16134
  }
16002
- function clearTextSelection() {
16003
- const sel = window.getSelection();
16004
- if (sel && !sel.isCollapsed) sel.removeAllRanges();
16135
+ function buttonId(el) {
16136
+ if (!el) return null;
16137
+ const button = el.matches(CTA_BUTTON_SELECTOR) ? el : el.closest(CTA_BUTTON_SELECTOR);
16138
+ const id = button?.getAttribute("data-ohw-href-key")?.trim();
16139
+ return id || null;
16005
16140
  }
16006
- function armItemPressDrag() {
16007
- document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
16141
+ function isButtonVariantId(value) {
16142
+ return BUTTON_VARIANT_IDS.includes(value);
16008
16143
  }
16009
- function lockItemDuringDrag() {
16010
- document.documentElement.removeAttribute("data-ohw-footer-press-drag");
16011
- document.documentElement.setAttribute("data-ohw-item-dragging", "");
16012
- clearTextSelection();
16144
+ function isButtonSizeId(value) {
16145
+ return BUTTON_SIZE_IDS.includes(value);
16013
16146
  }
16014
- function unlockItemDragInteraction() {
16015
- const wasDragging = document.documentElement.hasAttribute("data-ohw-item-dragging");
16016
- document.documentElement.removeAttribute("data-ohw-footer-press-drag");
16017
- document.documentElement.removeAttribute("data-ohw-item-dragging");
16018
- if (wasDragging) clearTextSelection();
16147
+ function px(value) {
16148
+ const rounded = Math.round(value * 100) / 100;
16149
+ return `${rounded}px`;
16019
16150
  }
16020
- var armFooterPressDrag = armItemPressDrag;
16021
- var lockFooterDuringDrag = lockItemDuringDrag;
16022
- var unlockFooterDragInteraction = unlockItemDragInteraction;
16023
-
16024
- // src/lib/nav-dnd.ts
16025
- function listReorderableNavItems(draggedEl) {
16026
- return listActiveNavbarItems(draggedEl).filter((el) => {
16027
- const key = el.getAttribute("data-ohw-href-key");
16028
- return isNavbarHrefKey(key) && !isNestedNavChild(el);
16029
- });
16151
+ function parsePx(value, rootFont = 16) {
16152
+ if (!value) return 0;
16153
+ const n = parseFloat(value);
16154
+ if (!Number.isFinite(n)) return 0;
16155
+ if (value.trim().endsWith("rem") || value.trim().endsWith("em")) return n * rootFont;
16156
+ return n;
16030
16157
  }
16031
- function resolveParentNavHrefKey(child) {
16032
- const childrenRoot = child.closest("[data-ohw-nav-children]");
16033
- const group = childrenRoot?.closest("[data-ohw-nav-group]");
16034
- if (!group) return null;
16035
- const trigger = group.querySelector(":scope > [data-ohw-href-key]") ?? Array.from(group.querySelectorAll("[data-ohw-href-key]")).find(
16036
- (el) => !el.closest("[data-ohw-nav-children]")
16037
- ) ?? null;
16038
- const key = trigger?.getAttribute("data-ohw-href-key");
16039
- return key && isNavbarHrefKey(key) ? key : null;
16158
+ function classifyHeight(height) {
16159
+ if (height < 44) return "s";
16160
+ if (height < 52) return "m";
16161
+ return "l";
16040
16162
  }
16041
- function listNavChildren(parentHrefKey, draggedEl) {
16042
- const items = listActiveNavbarItems(draggedEl);
16043
- const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
16044
- if (!parent) return [];
16045
- const group = parent.closest("[data-ohw-nav-group]");
16046
- const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
16047
- if (!childrenRoot) return [];
16048
- return Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
16049
- (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
16050
- );
16163
+ function scaleGeometry(geometry, scale = 0.75) {
16164
+ return {
16165
+ paddingTop: px(parsePx(geometry.paddingTop) * scale),
16166
+ paddingRight: px(parsePx(geometry.paddingRight) * scale),
16167
+ paddingBottom: px(parsePx(geometry.paddingBottom) * scale),
16168
+ paddingLeft: px(parsePx(geometry.paddingLeft) * scale),
16169
+ fontSize: px(parsePx(geometry.fontSize) * scale),
16170
+ borderRadius: px(parsePx(geometry.borderRadius) * scale),
16171
+ gap: px(parsePx(geometry.gap) * scale),
16172
+ height: Math.round(geometry.height * scale)
16173
+ };
16051
16174
  }
16052
- function isVerticalNavLayout(items, draggedEl) {
16053
- const visible = items.filter((el) => el.getClientRects().length > 0);
16054
- if (visible.length >= 2) {
16055
- const a = visible[0].getBoundingClientRect();
16056
- const b = visible[1].getBoundingClientRect();
16057
- const verticalGap = b.top - a.bottom;
16058
- const horizontalGap = b.left - a.right;
16059
- return verticalGap > horizontalGap;
16060
- }
16061
- const container = getActiveNavbarListContainer(draggedEl);
16062
- if (!container) return false;
16063
- const style = window.getComputedStyle(container);
16064
- return style.flexDirection === "column" || style.flexDirection === "column-reverse";
16175
+ function relativeLuminance(color) {
16176
+ const channel = (value) => {
16177
+ const s = value / 255;
16178
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
16179
+ };
16180
+ return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
16065
16181
  }
16066
- function buildVerticalRootNavDropSlots(items, draggedEl) {
16067
- const slots = [];
16068
- const barThickness = 3;
16069
- if (items.length === 0) {
16070
- const container = getActiveNavbarListContainer(draggedEl);
16071
- if (!container) return slots;
16072
- const rect = container.getBoundingClientRect();
16073
- slots.push({
16074
- insertIndex: 0,
16075
- parentId: null,
16076
- left: rect.left,
16077
- top: rect.top + rect.height / 2 - barThickness / 2,
16078
- width: Math.max(rect.width, 40),
16079
- height: barThickness,
16080
- direction: "horizontal"
16081
- });
16082
- return slots;
16083
- }
16084
- const edgeGap = (() => {
16085
- if (items.length < 2) return 16;
16086
- const a = items[0].getBoundingClientRect();
16087
- const b = items[1].getBoundingClientRect();
16088
- return Math.max(0, b.top - a.bottom);
16089
- })();
16090
- for (let i = 0; i <= items.length; i++) {
16091
- let top;
16092
- let width;
16093
- let left;
16094
- if (i === 0) {
16095
- const first = items[0].getBoundingClientRect();
16096
- top = first.top - edgeGap / 2 - barThickness / 2;
16097
- left = first.left;
16098
- width = first.width;
16099
- } else if (i === items.length) {
16100
- const last = items[items.length - 1].getBoundingClientRect();
16101
- const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap;
16102
- top = last.bottom + lastGap / 2 - barThickness / 2;
16103
- left = last.left;
16104
- width = last.width;
16105
- } else {
16106
- const prev = items[i - 1].getBoundingClientRect();
16107
- const next = items[i].getBoundingClientRect();
16108
- top = (prev.bottom + next.top) / 2 - barThickness / 2;
16109
- left = Math.min(prev.left, next.left);
16110
- width = Math.max(prev.right, next.right) - left;
16182
+ function contrastRatio(a, b) {
16183
+ const lighter = Math.max(relativeLuminance(a), relativeLuminance(b));
16184
+ const darker = Math.min(relativeLuminance(a), relativeLuminance(b));
16185
+ return (lighter + 0.05) / (darker + 0.05);
16186
+ }
16187
+ function compositeOver(fg, bg) {
16188
+ const a = fg.a + bg.a * (1 - fg.a);
16189
+ if (a < 1e-3) return { ...bg, a: 0 };
16190
+ return {
16191
+ r: (fg.r * fg.a + bg.r * bg.a * (1 - fg.a)) / a,
16192
+ g: (fg.g * fg.a + bg.g * bg.a * (1 - fg.a)) / a,
16193
+ b: (fg.b * fg.a + bg.b * bg.a * (1 - fg.a)) / a,
16194
+ a
16195
+ };
16196
+ }
16197
+ function previewSlotForChrome(chrome, slotBackground) {
16198
+ const text = parseColor(chrome.color);
16199
+ if (!text || text.a < 0.2) return slotBackground;
16200
+ const slot = parseColor(slotBackground) ?? { r: 250, g: 250, b: 249, a: 1 };
16201
+ const fill = parseColor(chrome.background);
16202
+ const effective = fill && !isTransparent(fill) ? compositeOver(fill, slot) : slot;
16203
+ if (contrastRatio(text, effective) >= 3) return slotBackground;
16204
+ return relativeLuminance(text) > 0.55 ? "#2c2c2c" : "#f4f4f5";
16205
+ }
16206
+ function parseColor(input) {
16207
+ if (!input) return null;
16208
+ const value = input.trim().toLowerCase();
16209
+ if (value === "transparent") return { r: 0, g: 0, b: 0, a: 0 };
16210
+ const hex = value.match(/^#([0-9a-f]{3,8})$/);
16211
+ if (hex) {
16212
+ const raw = hex[1];
16213
+ const expand = (pair) => parseInt(pair.length === 1 ? pair + pair : pair, 16);
16214
+ if (raw.length === 3 || raw.length === 4) {
16215
+ return {
16216
+ r: expand(raw[0]),
16217
+ g: expand(raw[1]),
16218
+ b: expand(raw[2]),
16219
+ a: raw.length === 4 ? expand(raw[3]) / 255 : 1
16220
+ };
16221
+ }
16222
+ if (raw.length === 6 || raw.length === 8) {
16223
+ return {
16224
+ r: expand(raw.slice(0, 2)),
16225
+ g: expand(raw.slice(2, 4)),
16226
+ b: expand(raw.slice(4, 6)),
16227
+ a: raw.length === 8 ? expand(raw.slice(6, 8)) / 255 : 1
16228
+ };
16111
16229
  }
16112
- slots.push({
16113
- insertIndex: i,
16114
- parentId: null,
16115
- left,
16116
- top,
16117
- width: Math.max(width, 40),
16118
- height: barThickness,
16119
- direction: "horizontal"
16120
- });
16121
16230
  }
16122
- return slots;
16123
- }
16124
- function buildHorizontalRootNavDropSlots(items, draggedEl) {
16125
- const slots = [];
16126
- const barThickness = 3;
16127
- if (items.length === 0) {
16128
- const container = getActiveNavbarListContainer(draggedEl);
16129
- if (!container) return slots;
16130
- const rect = container.getBoundingClientRect();
16131
- slots.push({
16132
- insertIndex: 0,
16133
- parentId: null,
16134
- left: rect.left + rect.width / 2 - barThickness / 2,
16135
- top: rect.top,
16136
- width: barThickness,
16137
- height: Math.max(rect.height, 24),
16138
- direction: "vertical"
16139
- });
16140
- return slots;
16231
+ const rgb = value.match(
16232
+ /^rgba?\(\s*([\d.]+)[%\s,]+([\d.]+)[%\s,]+([\d.]+)(?:%?\s*[,/]\s*([\d.]+%?))?\s*\)$/
16233
+ );
16234
+ if (rgb) {
16235
+ const alphaRaw = rgb[4];
16236
+ const alpha = alphaRaw == null ? 1 : alphaRaw.endsWith("%") ? parseFloat(alphaRaw) / 100 : parseFloat(alphaRaw);
16237
+ return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]), a: alpha };
16141
16238
  }
16142
- const edgeGap = (() => {
16143
- if (items.length < 2) return 32;
16144
- const a = items[0].getBoundingClientRect();
16145
- const b = items[1].getBoundingClientRect();
16146
- return Math.max(0, b.left - a.right);
16147
- })();
16148
- for (let i = 0; i <= items.length; i++) {
16149
- let left;
16150
- let top;
16151
- let height;
16152
- if (i === 0) {
16153
- const first = items[0].getBoundingClientRect();
16154
- left = first.left - edgeGap / 2 - barThickness / 2;
16155
- top = first.top;
16156
- height = first.height;
16157
- } else if (i === items.length) {
16158
- const last = items[items.length - 1].getBoundingClientRect();
16159
- const lastGap = items.length >= 2 ? Math.max(0, last.left - items[items.length - 2].getBoundingClientRect().right) : edgeGap;
16160
- left = last.right + lastGap / 2 - barThickness / 2;
16161
- top = last.top;
16162
- height = last.height;
16163
- } else {
16164
- const prev = items[i - 1].getBoundingClientRect();
16165
- const next = items[i].getBoundingClientRect();
16166
- left = (prev.right + next.left) / 2 - barThickness / 2;
16167
- top = Math.min(prev.top, next.top);
16168
- height = Math.max(prev.bottom, next.bottom) - top;
16169
- }
16170
- slots.push({
16171
- insertIndex: i,
16172
- parentId: null,
16173
- left,
16174
- top,
16175
- width: barThickness,
16176
- height: Math.max(height, 24),
16177
- direction: "vertical"
16178
- });
16239
+ const oklch = value.match(
16240
+ /^oklch\(\s*([\d.]+%?)\s+([\d.]+)\s+([\d.]+)(?:deg)?(?:\s*\/\s*([\d.]+%?))?\s*\)$/
16241
+ );
16242
+ if (oklch) {
16243
+ const Lraw = oklch[1];
16244
+ const L = Lraw.endsWith("%") ? parseFloat(Lraw) / 100 : parseFloat(Lraw);
16245
+ const gray = Math.max(0, Math.min(1, L)) * 255;
16246
+ const alphaRaw = oklch[4];
16247
+ const alpha = alphaRaw == null ? 1 : alphaRaw.endsWith("%") ? parseFloat(alphaRaw) / 100 : parseFloat(alphaRaw);
16248
+ return { r: gray, g: gray, b: gray, a: alpha };
16179
16249
  }
16180
- return slots;
16250
+ return null;
16181
16251
  }
16182
- function buildRootNavDropSlots(draggedEl) {
16183
- const items = listReorderableNavItems(draggedEl);
16184
- if (isVerticalNavLayout(items, draggedEl)) {
16185
- return buildVerticalRootNavDropSlots(items, draggedEl);
16252
+ function isTransparent(color) {
16253
+ return !color || color.a < 0.05;
16254
+ }
16255
+ function hasOpaqueBackground(background) {
16256
+ if (!background) return false;
16257
+ const value = background.trim().toLowerCase();
16258
+ if (!value || value === "transparent" || value === "rgba(0, 0, 0, 0)" || value === "rgb(0, 0, 0, 0)") {
16259
+ return false;
16186
16260
  }
16187
- return buildHorizontalRootNavDropSlots(items, draggedEl);
16261
+ const parsed = parseColor(background);
16262
+ if (parsed) return !isTransparent(parsed);
16263
+ return true;
16264
+ }
16265
+ function toCss(color) {
16266
+ const r2 = Math.round(color.r);
16267
+ const g = Math.round(color.g);
16268
+ const b = Math.round(color.b);
16269
+ if (color.a >= 0.995) return `rgb(${r2}, ${g}, ${b})`;
16270
+ return `rgba(${r2}, ${g}, ${b}, ${Math.round(color.a * 100) / 100})`;
16271
+ }
16272
+ function colorKey(input) {
16273
+ const parsed = parseColor(input);
16274
+ if (!parsed || isTransparent(parsed)) return "transparent";
16275
+ return `${Math.round(parsed.r / 8)}-${Math.round(parsed.g / 8)}-${Math.round(parsed.b / 8)}-${Math.round(parsed.a * 4)}`;
16276
+ }
16277
+ function declaredVariant(className) {
16278
+ const match = className.match(/\bbtn--(primary|secondary|ghost)\b/);
16279
+ return match && isButtonVariantId(match[1]) ? match[1] : null;
16280
+ }
16281
+ function fillSlot(sample) {
16282
+ const width = parseFloat(sample.borderWidth);
16283
+ const border = parseColor(sample.borderColor);
16284
+ if (width > 0 && !isTransparent(border)) return "secondary";
16285
+ if (hasOpaqueBackground(sample.background)) return "primary";
16286
+ return "ghost";
16287
+ }
16288
+ function sampleSlot(sample) {
16289
+ return declaredVariant(sample.className) ?? fillSlot(sample);
16290
+ }
16291
+ function fingerprint(sample) {
16292
+ const width = parseFloat(sample.borderWidth);
16293
+ const border = width > 0 ? colorKey(sample.borderColor) : "none";
16294
+ return `${colorKey(sample.background)}|${border}|${colorKey(sample.color)}|${sampleSlot(sample)}`;
16295
+ }
16296
+ function paintColor(chrome) {
16297
+ if (hasOpaqueBackground(chrome.background)) {
16298
+ const parsed = parseColor(chrome.background);
16299
+ return parsed ? toCss({ ...parsed, a: 1 }) : chrome.background;
16300
+ }
16301
+ const width = parseFloat(chrome.borderWidth);
16302
+ if (width > 0 && hasOpaqueBackground(chrome.borderColor)) {
16303
+ const parsed = parseColor(chrome.borderColor);
16304
+ return parsed ? toCss({ ...parsed, a: 1 }) : chrome.borderColor;
16305
+ }
16306
+ const text = parseColor(chrome.color);
16307
+ return text ? toCss({ ...text, a: 1 }) : chrome.color;
16308
+ }
16309
+ function accentColor(chrome) {
16310
+ const bg = parseColor(chrome.background);
16311
+ if (bg && !isTransparent(bg) && relativeLuminance(bg) < 0.55) {
16312
+ return toCss({ ...bg, a: 1 });
16313
+ }
16314
+ const text = parseColor(chrome.color);
16315
+ if (text && !isTransparent(text)) return toCss({ ...text, a: 1 });
16316
+ return paintColor(chrome);
16317
+ }
16318
+ function chromeFrom(sample) {
16319
+ return {
16320
+ background: sample.background || TRANSPARENT,
16321
+ color: sample.color || "currentColor",
16322
+ borderColor: sample.borderColor || TRANSPARENT,
16323
+ borderWidth: sample.borderWidth || "0px"
16324
+ };
16325
+ }
16326
+ function filledPrimary(seed) {
16327
+ const paint = parseColor(paintColor(seed)) ?? { r: 15, g: 15, b: 15, a: 1 };
16328
+ const luminance = relativeLuminance(paint);
16329
+ return {
16330
+ background: toCss({ ...paint, a: 1 }),
16331
+ color: luminance > 0.55 ? "rgb(15, 15, 15)" : "rgb(255, 255, 255)",
16332
+ borderColor: TRANSPARENT,
16333
+ borderWidth: "0px"
16334
+ };
16335
+ }
16336
+ function generatedSecondary(primary) {
16337
+ const paint = accentColor(primary);
16338
+ return {
16339
+ background: TRANSPARENT,
16340
+ color: paint,
16341
+ borderColor: paint,
16342
+ borderWidth: "1px"
16343
+ };
16344
+ }
16345
+ function generatedGhost(primary) {
16346
+ return {
16347
+ background: TRANSPARENT,
16348
+ color: accentColor(primary),
16349
+ borderColor: TRANSPARENT,
16350
+ borderWidth: "0px"
16351
+ };
16352
+ }
16353
+ function normalizeOutlineChrome(outline, primary) {
16354
+ const paint = accentColor(primary);
16355
+ const text = parseColor(outline.color);
16356
+ const paintRgb = parseColor(paint);
16357
+ const primaryLabel = parseColor(primary.color);
16358
+ const matchesOnPrimary = Boolean(text && primaryLabel) && colorKey(outline.color) === colorKey(primary.color);
16359
+ const textTooLight = text ? relativeLuminance(text) > 0.7 : false;
16360
+ const paintIsDarker = paintRgb ? relativeLuminance(paintRgb) < 0.65 : hasOpaqueBackground(primary.background);
16361
+ if (!(matchesOnPrimary || textTooLight) || !paintIsDarker) return outline;
16362
+ const hasBorder = parseFloat(outline.borderWidth) > 0;
16363
+ return {
16364
+ ...outline,
16365
+ color: paint,
16366
+ borderColor: hasBorder ? paint : outline.borderColor
16367
+ };
16368
+ }
16369
+ function generateButtonVariants(samples) {
16370
+ if (samples.length === 0) return { status: "unresolved" };
16371
+ const groups = /* @__PURE__ */ new Map();
16372
+ for (const sample of samples) {
16373
+ const key = fingerprint(sample);
16374
+ const list = groups.get(key) ?? [];
16375
+ list.push(sample);
16376
+ groups.set(key, list);
16377
+ }
16378
+ if (groups.size > 3) return { status: "unresolved" };
16379
+ const bySlot = /* @__PURE__ */ new Map();
16380
+ for (const group of groups.values()) {
16381
+ const sample = group[0];
16382
+ const slot = sampleSlot(sample);
16383
+ if (bySlot.has(slot)) return { status: "unresolved" };
16384
+ bySlot.set(slot, sample);
16385
+ }
16386
+ const primarySample = bySlot.get("primary");
16387
+ const seed = chromeFrom(primarySample ?? samples[0]);
16388
+ const primary = primarySample ? seed : filledPrimary(seed);
16389
+ const secondary = normalizeOutlineChrome(
16390
+ bySlot.has("secondary") ? chromeFrom(bySlot.get("secondary")) : generatedSecondary(primary),
16391
+ primary
16392
+ );
16393
+ const ghost = normalizeOutlineChrome(
16394
+ bySlot.has("ghost") ? chromeFrom(bySlot.get("ghost")) : generatedGhost(primary),
16395
+ primary
16396
+ );
16397
+ return {
16398
+ status: "ready",
16399
+ tokens: { primary, secondary, ghost }
16400
+ };
16401
+ }
16402
+ function geometryFromSample(sample) {
16403
+ return {
16404
+ paddingTop: px(sample.paddingTop),
16405
+ paddingRight: px(sample.paddingRight),
16406
+ paddingBottom: px(sample.paddingBottom),
16407
+ paddingLeft: px(sample.paddingLeft),
16408
+ fontSize: px(sample.fontSize),
16409
+ borderRadius: px(sample.borderRadius),
16410
+ gap: px(sample.gap),
16411
+ height: Math.round(sample.height)
16412
+ };
16413
+ }
16414
+ function shiftSample(base, steps) {
16415
+ return {
16416
+ height: Math.max(24, base.height + steps * 8),
16417
+ paddingTop: Math.max(0, base.paddingTop + steps * 4),
16418
+ paddingBottom: Math.max(0, base.paddingBottom + steps * 4),
16419
+ paddingLeft: Math.max(0, base.paddingLeft + steps * 8),
16420
+ paddingRight: Math.max(0, base.paddingRight + steps * 8),
16421
+ fontSize: base.fontSize,
16422
+ borderRadius: base.borderRadius,
16423
+ gap: base.gap
16424
+ };
16425
+ }
16426
+ function clusterSizes(samples) {
16427
+ const sorted = [...samples].sort((a, b) => a.height - b.height);
16428
+ const clusters = [];
16429
+ for (const sample of sorted) {
16430
+ const current = clusters[clusters.length - 1];
16431
+ const head = current?.[0];
16432
+ if (!current || !head || Math.abs(sample.height - head.height) > 2) clusters.push([sample]);
16433
+ else current.push(sample);
16434
+ }
16435
+ return clusters.map((group) => {
16436
+ const pick = (key) => group.reduce((sum, item) => sum + item[key], 0) / group.length;
16437
+ return {
16438
+ height: pick("height"),
16439
+ paddingTop: pick("paddingTop"),
16440
+ paddingRight: pick("paddingRight"),
16441
+ paddingBottom: pick("paddingBottom"),
16442
+ paddingLeft: pick("paddingLeft"),
16443
+ fontSize: pick("fontSize"),
16444
+ borderRadius: pick("borderRadius"),
16445
+ gap: pick("gap")
16446
+ };
16447
+ });
16448
+ }
16449
+ function tokensFromSamples(bySlot, radius, gap) {
16450
+ const fill = (id) => {
16451
+ const sample = bySlot[id] ?? REFERENCE_SIZES[id];
16452
+ return geometryFromSample({ ...sample, borderRadius: radius, gap });
16453
+ };
16454
+ return { s: fill("s"), m: fill("m"), l: fill("l") };
16455
+ }
16456
+ function interpolateSizes(known) {
16457
+ const order = BUTTON_SIZE_IDS;
16458
+ const indexOf = (id) => order.indexOf(id);
16459
+ const sorted = [...known].sort((a, b) => indexOf(a.id) - indexOf(b.id));
16460
+ const left = sorted[0];
16461
+ const right = sorted[1];
16462
+ const span = indexOf(right.id) - indexOf(left.id);
16463
+ const step = (key) => (right.sample[key] - left.sample[key]) / span;
16464
+ const at = (index) => {
16465
+ const steps = index - indexOf(left.id);
16466
+ return {
16467
+ height: left.sample.height + step("height") * steps,
16468
+ paddingTop: Math.max(0, left.sample.paddingTop + step("paddingTop") * steps),
16469
+ paddingBottom: Math.max(0, left.sample.paddingBottom + step("paddingBottom") * steps),
16470
+ paddingLeft: Math.max(0, left.sample.paddingLeft + step("paddingLeft") * steps),
16471
+ paddingRight: Math.max(0, left.sample.paddingRight + step("paddingRight") * steps),
16472
+ fontSize: Math.max(10, left.sample.fontSize + step("fontSize") * steps),
16473
+ borderRadius: left.sample.borderRadius,
16474
+ gap: left.sample.gap
16475
+ };
16476
+ };
16477
+ const bySlot = {
16478
+ [left.id]: left.sample,
16479
+ [right.id]: { ...right.sample, borderRadius: left.sample.borderRadius, gap: left.sample.gap }
16480
+ };
16481
+ for (const id of order) {
16482
+ if (!bySlot[id]) bySlot[id] = at(indexOf(id));
16483
+ }
16484
+ return tokensFromSamples(bySlot, left.sample.borderRadius, left.sample.gap);
16485
+ }
16486
+ function generateButtonSizes(samples) {
16487
+ const clusters = clusterSizes(samples.filter((sample) => sample.height > 0));
16488
+ if (clusters.length === 0) {
16489
+ return { status: "ready", tokens: tokensFromSamples(REFERENCE_SIZES, 8, 8) };
16490
+ }
16491
+ if (clusters.length > 3) return { status: "unresolved" };
16492
+ if (clusters.length === 3) {
16493
+ const sorted = [...clusters].sort((a, b) => a.height - b.height);
16494
+ const radius = sorted[1].borderRadius;
16495
+ const gap = sorted[1].gap;
16496
+ return {
16497
+ status: "ready",
16498
+ tokens: {
16499
+ s: geometryFromSample({ ...sorted[0], borderRadius: radius, gap }),
16500
+ m: geometryFromSample({ ...sorted[1], borderRadius: radius, gap }),
16501
+ l: geometryFromSample({ ...sorted[2], borderRadius: radius, gap })
16502
+ }
16503
+ };
16504
+ }
16505
+ if (clusters.length === 1) {
16506
+ const known = clusters[0];
16507
+ const slot = classifyHeight(known.height);
16508
+ const offset = BUTTON_SIZE_IDS.indexOf(slot);
16509
+ const bySlot = Object.fromEntries(
16510
+ BUTTON_SIZE_IDS.map((id, index) => [id, shiftSample(known, index - offset)])
16511
+ );
16512
+ bySlot[slot] = known;
16513
+ return { status: "ready", tokens: tokensFromSamples(bySlot, known.borderRadius, known.gap) };
16514
+ }
16515
+ const mapped = clusters.map((sample) => ({ id: classifyHeight(sample.height), sample }));
16516
+ if (mapped[0].id === mapped[1].id) {
16517
+ const larger = clusters.slice().sort((a, b) => b.height - a.height)[0];
16518
+ return generateButtonSizes([larger]);
16519
+ }
16520
+ return { status: "ready", tokens: interpolateSizes(mapped) };
16521
+ }
16522
+ function parseJson(raw) {
16523
+ if (!raw) return null;
16524
+ try {
16525
+ return JSON.parse(raw);
16526
+ } catch {
16527
+ return null;
16528
+ }
16529
+ }
16530
+ function isChrome(value) {
16531
+ if (!value || typeof value !== "object") return false;
16532
+ const chrome = value;
16533
+ return typeof chrome.background === "string" && typeof chrome.color === "string";
16534
+ }
16535
+ function isGeometry(value) {
16536
+ if (!value || typeof value !== "object") return false;
16537
+ const geometry = value;
16538
+ return typeof geometry.paddingTop === "string" && typeof geometry.height === "number";
16539
+ }
16540
+ function readVariantTokens(content) {
16541
+ const parsed = parseJson(content[BUTTON_VARIANTS_KEY]);
16542
+ if (!parsed) return null;
16543
+ if (!BUTTON_VARIANT_IDS.every((id) => isChrome(parsed[id]))) return null;
16544
+ return parsed;
16545
+ }
16546
+ function readSizeTokens(content) {
16547
+ const parsed = parseJson(content[BUTTON_SIZES_KEY]);
16548
+ if (!parsed) return null;
16549
+ if (!BUTTON_SIZE_IDS.every((id) => isGeometry(parsed[id]))) return null;
16550
+ return parsed;
16551
+ }
16552
+ function readAssignments(content) {
16553
+ const parsed = parseJson(content[BUTTON_ASSIGNMENTS_KEY]);
16554
+ if (!parsed || typeof parsed !== "object") return {};
16555
+ const next = {};
16556
+ for (const [id, value] of Object.entries(parsed)) {
16557
+ if (!value || !isButtonVariantId(value.variant) || !isButtonSizeId(value.size)) continue;
16558
+ next[id] = { variant: value.variant, size: value.size };
16559
+ }
16560
+ return next;
16561
+ }
16562
+ function cssLength(style, prop) {
16563
+ return style.getPropertyValue(prop) || "0px";
16564
+ }
16565
+ function readVariantSample(el) {
16566
+ const style = getComputedStyle(el);
16567
+ return {
16568
+ background: style.backgroundColor,
16569
+ color: style.color,
16570
+ borderColor: style.borderTopColor,
16571
+ borderWidth: style.borderTopWidth,
16572
+ className: el.className || ""
16573
+ };
16574
+ }
16575
+ function readSizeSample(el) {
16576
+ const style = getComputedStyle(el);
16577
+ const rootFont = parsePx(getComputedStyle(el.ownerDocument.documentElement).fontSize) || 16;
16578
+ const rect = el.getBoundingClientRect();
16579
+ return {
16580
+ height: rect.height,
16581
+ paddingTop: parsePx(cssLength(style, "padding-top"), rootFont),
16582
+ paddingRight: parsePx(cssLength(style, "padding-right"), rootFont),
16583
+ paddingBottom: parsePx(cssLength(style, "padding-bottom"), rootFont),
16584
+ paddingLeft: parsePx(cssLength(style, "padding-left"), rootFont),
16585
+ fontSize: parsePx(style.fontSize, rootFont),
16586
+ borderRadius: parsePx(style.borderTopLeftRadius, rootFont),
16587
+ gap: parsePx(style.columnGap || style.gap, rootFont)
16588
+ };
16589
+ }
16590
+ function readTypography(el) {
16591
+ const style = getComputedStyle(el);
16592
+ return {
16593
+ fontFamily: style.fontFamily,
16594
+ fontWeight: style.fontWeight,
16595
+ letterSpacing: style.letterSpacing,
16596
+ textTransform: style.textTransform,
16597
+ textDecoration: style.textDecorationLine || style.textDecoration || "none"
16598
+ };
16599
+ }
16600
+ var buttonOriginByEl = /* @__PURE__ */ new WeakMap();
16601
+ var ORIGIN_ATTR = "data-ohw-btn-origin";
16602
+ function parseOriginAttr(raw) {
16603
+ if (!raw) return null;
16604
+ try {
16605
+ const value = JSON.parse(raw);
16606
+ if (!value?.variant || !value.size || !value.typography) return null;
16607
+ return value;
16608
+ } catch {
16609
+ return null;
16610
+ }
16611
+ }
16612
+ function measureLiveOrigin(el) {
16613
+ return {
16614
+ variant: readVariantSample(el),
16615
+ size: readSizeSample(el),
16616
+ typography: readTypography(el)
16617
+ };
16618
+ }
16619
+ function captureButtonOrigin(el) {
16620
+ const cached = buttonOriginByEl.get(el);
16621
+ if (cached) return cached;
16622
+ const fromAttr = parseOriginAttr(el.getAttribute(ORIGIN_ATTR));
16623
+ if (fromAttr) {
16624
+ buttonOriginByEl.set(el, fromAttr);
16625
+ return fromAttr;
16626
+ }
16627
+ const origin = measureLiveOrigin(el);
16628
+ buttonOriginByEl.set(el, origin);
16629
+ try {
16630
+ el.setAttribute(ORIGIN_ATTR, JSON.stringify(origin));
16631
+ } catch {
16632
+ }
16633
+ return origin;
16634
+ }
16635
+ function listStyleButtons(doc) {
16636
+ return Array.from(doc.querySelectorAll(CTA_BUTTON_SELECTOR)).filter((el) => {
16637
+ if (el.closest("[data-ohw-bridge], [data-ohw-bridge-root]")) return false;
16638
+ const rect = el.getBoundingClientRect();
16639
+ return rect.width > 0 && rect.height > 0;
16640
+ });
16641
+ }
16642
+ function generateTokensFromButtons(buttons) {
16643
+ const variants = generateButtonVariants(buttons.map(readVariantSample));
16644
+ const sizes = generateButtonSizes(buttons.map(readSizeSample));
16645
+ if (variants.status !== "ready" || sizes.status !== "ready") return { status: "unresolved" };
16646
+ return { status: "ready", tokens: { variants: variants.tokens, sizes: sizes.tokens } };
16647
+ }
16648
+ function tokensFromButton(el) {
16649
+ const origin = captureButtonOrigin(el);
16650
+ const sample = origin.variant;
16651
+ const variants = generateButtonVariants([sample]);
16652
+ const sizes = generateButtonSizes([origin.size]);
16653
+ const primary = chromeFrom(sample);
16654
+ return {
16655
+ variants: variants.status === "ready" ? variants.tokens : { primary, secondary: generatedSecondary(primary), ghost: generatedGhost(primary) },
16656
+ sizes: sizes.status === "ready" ? sizes.tokens : tokensFromSamples(REFERENCE_SIZES, 8, 8)
16657
+ };
16658
+ }
16659
+ function readStoredStyleTokens(content) {
16660
+ const variants = readVariantTokens(content);
16661
+ const sizes = readSizeTokens(content);
16662
+ if (!variants || !sizes) return null;
16663
+ return { variants, sizes };
16664
+ }
16665
+ function styleTokenNodes(tokens) {
16666
+ return [
16667
+ { key: BUTTON_VARIANTS_KEY, text: JSON.stringify(tokens.variants) },
16668
+ { key: BUTTON_SIZES_KEY, text: JSON.stringify(tokens.sizes) }
16669
+ ];
16670
+ }
16671
+ function ensureButtonStyleTokens(doc, content) {
16672
+ const stored = readStoredStyleTokens(content);
16673
+ if (stored) return { tokens: stored, nodes: [] };
16674
+ const generated = generateTokensFromButtons(listStyleButtons(doc));
16675
+ if (generated.status !== "ready") return { tokens: null, nodes: [] };
16676
+ return { tokens: generated.tokens, nodes: styleTokenNodes(generated.tokens) };
16677
+ }
16678
+ function detectVariant(el, tokens) {
16679
+ const declared = declaredVariant(el.className || "");
16680
+ if (declared) return declared;
16681
+ const stamped = el.getAttribute("data-ohw-btn-variant");
16682
+ if (stamped && isButtonVariantId(stamped)) return stamped;
16683
+ const sample = readVariantSample(el);
16684
+ if (!tokens) return sampleSlot(sample);
16685
+ const key = fingerprint(sample);
16686
+ for (const id of BUTTON_VARIANT_IDS) {
16687
+ const chrome = tokens[id];
16688
+ const tokenKey = fingerprint({
16689
+ background: chrome.background,
16690
+ color: chrome.color,
16691
+ borderColor: chrome.borderColor,
16692
+ borderWidth: chrome.borderWidth,
16693
+ className: `btn btn--${id}`
16694
+ });
16695
+ if (tokenKey === key) return id;
16696
+ }
16697
+ return sampleSlot(sample);
16698
+ }
16699
+ function detectSize(el, tokens) {
16700
+ const stamped = el.getAttribute("data-ohw-btn-size");
16701
+ if (stamped && isButtonSizeId(stamped)) return stamped;
16702
+ const height = el.getBoundingClientRect().height;
16703
+ if (!tokens) return classifyHeight(height);
16704
+ let best = "m";
16705
+ let bestDelta = Number.POSITIVE_INFINITY;
16706
+ for (const id of BUTTON_SIZE_IDS) {
16707
+ const delta = Math.abs(tokens[id].height - height);
16708
+ if (delta < bestDelta) {
16709
+ best = id;
16710
+ bestDelta = delta;
16711
+ }
16712
+ }
16713
+ return best;
16714
+ }
16715
+ function assignmentFor(el, content, tokens) {
16716
+ const id = buttonId(el);
16717
+ const stored = id ? readAssignments(content)[id] : void 0;
16718
+ if (stored) return stored;
16719
+ return {
16720
+ variant: detectVariant(el, tokens?.variants ?? null),
16721
+ size: detectSize(el, tokens?.sizes ?? null)
16722
+ };
16723
+ }
16724
+ function labelElement(el) {
16725
+ return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? (el.matches('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ? el : null);
16726
+ }
16727
+ var paintLockByEl = /* @__PURE__ */ new WeakMap();
16728
+ var paintObserverByEl = /* @__PURE__ */ new WeakMap();
16729
+ var paintingButton = false;
16730
+ function paintButtonChrome(el, assignment, tokens) {
16731
+ const origin = captureButtonOrigin(el);
16732
+ const chrome = tokens.variants[assignment.variant];
16733
+ const originSlot = classifyHeight(origin.size.height);
16734
+ const geometry = assignment.size === originSlot ? geometryFromSample({ ...origin.size, borderRadius: origin.size.borderRadius, gap: origin.size.gap }) : tokens.sizes[assignment.size];
16735
+ const { typography } = origin;
16736
+ el.setAttribute("data-ohw-btn-variant", assignment.variant);
16737
+ el.setAttribute("data-ohw-btn-size", assignment.size);
16738
+ el.style.transition = "none";
16739
+ el.style.boxSizing = "border-box";
16740
+ el.style.display = "inline-flex";
16741
+ el.style.alignItems = "center";
16742
+ el.style.justifyContent = "center";
16743
+ el.style.background = chrome.background;
16744
+ el.style.color = chrome.color;
16745
+ el.style.borderStyle = parseFloat(chrome.borderWidth) > 0 ? "solid" : "none";
16746
+ el.style.borderColor = chrome.borderColor;
16747
+ el.style.borderWidth = chrome.borderWidth;
16748
+ el.style.padding = `${geometry.paddingTop} ${geometry.paddingRight} ${geometry.paddingBottom} ${geometry.paddingLeft}`;
16749
+ el.style.fontSize = geometry.fontSize;
16750
+ el.style.borderRadius = px(origin.size.borderRadius);
16751
+ el.style.gap = geometry.gap;
16752
+ el.style.height = px(geometry.height);
16753
+ el.style.fontFamily = typography.fontFamily;
16754
+ el.style.fontWeight = typography.fontWeight;
16755
+ el.style.letterSpacing = typography.letterSpacing;
16756
+ el.style.textTransform = typography.textTransform;
16757
+ if (typography.textDecoration && typography.textDecoration !== "none") {
16758
+ el.style.textDecoration = typography.textDecoration;
16759
+ } else {
16760
+ el.style.textDecoration = "none";
16761
+ }
16762
+ const label = labelElement(el);
16763
+ if (label && label !== el) label.style.color = chrome.color;
16764
+ }
16765
+ function guardTemplateHover(el) {
16766
+ if (paintObserverByEl.has(el)) return;
16767
+ const observer = new MutationObserver(() => {
16768
+ if (paintingButton) return;
16769
+ const lock = paintLockByEl.get(el);
16770
+ if (!lock) return;
16771
+ paintingButton = true;
16772
+ paintButtonChrome(el, lock.assignment, lock.tokens);
16773
+ paintingButton = false;
16774
+ });
16775
+ observer.observe(el, { attributes: true, attributeFilter: ["style", "class"] });
16776
+ paintObserverByEl.set(el, observer);
16777
+ }
16778
+ function applyButtonStyle(el, assignment, tokens) {
16779
+ paintLockByEl.set(el, { assignment, tokens });
16780
+ paintingButton = true;
16781
+ paintButtonChrome(el, assignment, tokens);
16782
+ paintingButton = false;
16783
+ guardTemplateHover(el);
16784
+ }
16785
+ function applyButtonStylesFromContent(doc, content) {
16786
+ const assignments = readAssignments(content);
16787
+ for (const [id, assignment] of Object.entries(assignments)) {
16788
+ const el = doc.querySelector(`[data-ohw-href-key="${CSS.escape(id)}"]`);
16789
+ if (!el) continue;
16790
+ applyButtonStyle(el, assignment, tokensFromButton(el));
16791
+ }
16792
+ }
16793
+ function withAssignment(content, id, assignment) {
16794
+ return { ...readAssignments(content), [id]: assignment };
16795
+ }
16796
+
16797
+ // src/ui/button-style-panel.tsx
16798
+ import { jsx as jsx31, jsxs as jsxs21 } from "react/jsx-runtime";
16799
+ var SIZE_LABEL = { s: "S", m: "M", l: "L" };
16800
+ function ButtonStylePanel({
16801
+ variants,
16802
+ sizes,
16803
+ variant,
16804
+ size,
16805
+ onVariant,
16806
+ onSize,
16807
+ previewLabel = "Book Now",
16808
+ slotBackground = "var(--brand-background, var(--ohw-brand-light, var(--color-light, #ffffff)))"
16809
+ }) {
16810
+ const preview = scaleGeometry(sizes.m, 0.75);
16811
+ const label = previewLabel.trim() || "Book Now";
16812
+ return /* @__PURE__ */ jsxs21("div", { className: "flex w-full flex-col gap-5", children: [
16813
+ /* @__PURE__ */ jsxs21("section", { className: "flex w-full flex-col gap-2.5", children: [
16814
+ /* @__PURE__ */ jsx31("h3", { className: "text-xs font-semibold uppercase leading-4 tracking-[0.6px] text-stone-500 font-sans", children: "Variants" }),
16815
+ /* @__PURE__ */ jsx31("div", { className: "flex w-full flex-col gap-2.5", children: BUTTON_VARIANT_IDS.map((id) => {
16816
+ const chrome = variants[id];
16817
+ const selected = id === variant;
16818
+ const hasBorder = parseFloat(chrome.borderWidth) > 0;
16819
+ const cardSlot = previewSlotForChrome(chrome, slotBackground);
16820
+ return /* @__PURE__ */ jsxs21(
16821
+ "button",
16822
+ {
16823
+ type: "button",
16824
+ "aria-pressed": selected,
16825
+ "aria-label": id,
16826
+ onMouseDown: (e) => {
16827
+ e.preventDefault();
16828
+ e.stopPropagation();
16829
+ },
16830
+ onClick: () => onVariant(id),
16831
+ className: cn(
16832
+ "relative w-full overflow-hidden rounded-xl border-solid text-left",
16833
+ selected ? "border-2 border-primary" : "border border-border"
16834
+ ),
16835
+ children: [
16836
+ /* @__PURE__ */ jsx31(
16837
+ "div",
16838
+ {
16839
+ className: "flex w-full items-center justify-center py-4",
16840
+ style: { background: cardSlot },
16841
+ children: /* @__PURE__ */ jsx31(
16842
+ "span",
16843
+ {
16844
+ className: "inline-flex items-center justify-center whitespace-nowrap font-semibold uppercase leading-none",
16845
+ style: {
16846
+ background: chrome.background,
16847
+ color: chrome.color,
16848
+ borderColor: chrome.borderColor,
16849
+ borderWidth: hasBorder ? chrome.borderWidth : "0px",
16850
+ borderStyle: hasBorder ? "solid" : "none",
16851
+ padding: `${preview.paddingTop} ${preview.paddingRight} ${preview.paddingBottom} ${preview.paddingLeft}`,
16852
+ fontSize: preview.fontSize,
16853
+ borderRadius: preview.borderRadius,
16854
+ height: preview.height,
16855
+ boxSizing: "border-box",
16856
+ letterSpacing: "0.12em"
16857
+ },
16858
+ children: label
16859
+ }
16860
+ )
16861
+ }
16862
+ ),
16863
+ selected ? /* @__PURE__ */ jsx31(
16864
+ "span",
16865
+ {
16866
+ className: "absolute right-0.5 top-0.5 flex size-5 items-center justify-center rounded-full bg-primary text-primary-foreground",
16867
+ "aria-hidden": true,
16868
+ children: /* @__PURE__ */ jsx31(Check3, { className: "size-3", strokeWidth: 3 })
16869
+ }
16870
+ ) : null
16871
+ ]
16872
+ },
16873
+ id
16874
+ );
16875
+ }) })
16876
+ ] }),
16877
+ /* @__PURE__ */ jsxs21("section", { className: "flex w-full flex-col gap-2.5", children: [
16878
+ /* @__PURE__ */ jsx31("h3", { className: "text-xs font-semibold uppercase leading-4 tracking-[0.6px] text-stone-500 font-sans", children: "Size" }),
16879
+ /* @__PURE__ */ jsx31(
16880
+ "div",
16881
+ {
16882
+ className: "flex w-full items-start rounded-lg bg-stone-100 p-1",
16883
+ role: "radiogroup",
16884
+ "aria-label": "Size",
16885
+ children: BUTTON_SIZE_IDS.map((id) => {
16886
+ const selected = id === size;
16887
+ return /* @__PURE__ */ jsx31(
16888
+ "button",
16889
+ {
16890
+ type: "button",
16891
+ role: "radio",
16892
+ "aria-checked": selected,
16893
+ onMouseDown: (e) => {
16894
+ e.preventDefault();
16895
+ e.stopPropagation();
16896
+ },
16897
+ onClick: () => onSize(id),
16898
+ className: cn(
16899
+ "flex min-w-14 flex-1 items-center justify-center rounded-md px-3 py-1.5 text-sm font-medium leading-5",
16900
+ selected ? "bg-white text-stone-950 shadow-sm" : "bg-transparent text-stone-500"
16901
+ ),
16902
+ children: SIZE_LABEL[id]
16903
+ },
16904
+ id
16905
+ );
16906
+ })
16907
+ }
16908
+ )
16909
+ ] })
16910
+ ] });
16911
+ }
16912
+
16913
+ // src/lib/button-collections.ts
16914
+ var BUTTON_COLLECTIONS_KEY = "__ohw_button_collections";
16915
+ var BUTTON_SNAPSHOTS_KEY = "__ohw_button_snapshots";
16916
+ var BUTTON_HIDDEN_KEY = "__ohw_button_hidden";
16917
+ var COLLECTION_ATTR = "data-ohw-button-collection";
16918
+ function parseJson2(raw) {
16919
+ if (!raw) return null;
16920
+ try {
16921
+ return JSON.parse(raw);
16922
+ } catch {
16923
+ return null;
16924
+ }
16925
+ }
16926
+ function readCollections(content) {
16927
+ const parsed = parseJson2(content[BUTTON_COLLECTIONS_KEY]);
16928
+ if (!parsed || typeof parsed !== "object") return {};
16929
+ const next = {};
16930
+ for (const [id, value] of Object.entries(parsed)) {
16931
+ if (!value || !Array.isArray(value.members)) continue;
16932
+ next[id] = {
16933
+ id,
16934
+ members: value.members.filter((member) => typeof member === "string" && member),
16935
+ gap: typeof value.gap === "string" ? value.gap : "12px"
16936
+ };
16937
+ }
16938
+ return next;
16939
+ }
16940
+ function readSnapshots(content) {
16941
+ const parsed = parseJson2(content[BUTTON_SNAPSHOTS_KEY]);
16942
+ if (!parsed || typeof parsed !== "object") return {};
16943
+ const next = {};
16944
+ for (const [id, value] of Object.entries(parsed)) {
16945
+ if (!value || typeof value.html !== "string") continue;
16946
+ next[id] = value;
16947
+ }
16948
+ return next;
16949
+ }
16950
+ function readHiddenButtons(content) {
16951
+ const parsed = parseJson2(content[BUTTON_HIDDEN_KEY]);
16952
+ if (!Array.isArray(parsed)) return [];
16953
+ return parsed.filter((id) => typeof id === "string" && id);
16954
+ }
16955
+ function buttonCollectionOf(el) {
16956
+ return el?.closest(`[${COLLECTION_ATTR}]`) ?? null;
16957
+ }
16958
+ function isNavbarCta(el) {
16959
+ return el.getAttribute("data-ohw-role") === "navbar-button" || Boolean(el.closest('[data-ohw-role="navbar-button"]'));
16960
+ }
16961
+ function navbarScope(el) {
16962
+ return el.closest("nav, [data-ohw-nav-container], [data-ohw-nav-drawer]") ?? el.ownerDocument;
16963
+ }
16964
+ function isLastNavbarButton(el) {
16965
+ if (!isNavbarCta(el)) return false;
16966
+ const buttons = Array.from(navbarScope(el).querySelectorAll(CTA_BUTTON_SELECTOR)).filter(
16967
+ (button) => isNavbarCta(button) && buttonId(button)
16968
+ );
16969
+ return buttons.length <= 1;
16970
+ }
16971
+ function canDeleteButton(el) {
16972
+ return !isLastNavbarButton(el);
16973
+ }
16974
+ function canDuplicateButton(el) {
16975
+ return isNavbarCta(el);
16976
+ }
16977
+ function labelElement2(el) {
16978
+ return el.querySelector('[data-ohw-editable="text"], [data-ohw-editable="plain"]') ?? el.querySelector("[data-ohw-key]");
16979
+ }
16980
+ function buttonLabel(el) {
16981
+ return (labelElement2(el)?.textContent ?? el.textContent ?? "").trim();
16982
+ }
16983
+ function buttonHref(el) {
16984
+ return el.getAttribute("href") ?? "";
16985
+ }
16986
+ function hrefKeyBase(hrefKey) {
16987
+ return hrefKey.replace(/-href$/, "").replace(/-copy-\d+$/, "");
16988
+ }
16989
+ function labelKeyForHrefKey(hrefKey) {
16990
+ return `${hrefKey.replace(/-href$/, "")}-label`;
16991
+ }
16992
+ function cleanClone(el) {
16993
+ const copy = el.cloneNode(true);
16994
+ copy.querySelectorAll("[data-ohw-selected], [data-ohw-hovered], [data-ohw-editing]").forEach((node) => {
16995
+ node.removeAttribute("data-ohw-selected");
16996
+ node.removeAttribute("data-ohw-hovered");
16997
+ node.removeAttribute("data-ohw-editing");
16998
+ node.removeAttribute("contenteditable");
16999
+ });
17000
+ copy.removeAttribute("data-ohw-selected");
17001
+ copy.removeAttribute("data-ohw-hovered");
17002
+ copy.removeAttribute("data-ohw-editing");
17003
+ copy.removeAttribute("contenteditable");
17004
+ copy.removeAttribute("data-ohw-drag-disabled");
17005
+ return copy;
17006
+ }
17007
+ function nextCopyId(sourceId, content, doc) {
17008
+ const used = /* @__PURE__ */ new Set([
17009
+ ...Object.keys(readSnapshots(content)),
17010
+ ...Object.keys(readAssignments(content)),
17011
+ ...Array.from(doc.querySelectorAll("[data-ohw-href-key]")).map(
17012
+ (el) => el.getAttribute("data-ohw-href-key") ?? ""
17013
+ )
17014
+ ]);
17015
+ const base = hrefKeyBase(sourceId);
17016
+ let n = 2;
17017
+ while (used.has(`${base}-copy-${n}-href`)) n += 1;
17018
+ return `${base}-copy-${n}-href`;
17019
+ }
17020
+ function nextCollectionId(content, doc) {
17021
+ const used = /* @__PURE__ */ new Set([
17022
+ ...Object.keys(readCollections(content)),
17023
+ ...Array.from(doc.querySelectorAll(`[${COLLECTION_ATTR}]`)).map(
17024
+ (el) => el.getAttribute(COLLECTION_ATTR) ?? ""
17025
+ )
17026
+ ]);
17027
+ let n = 1;
17028
+ while (used.has(`bc-${n}`)) n += 1;
17029
+ return `bc-${n}`;
17030
+ }
17031
+ var COLLECTION_GAP_MAX_PX = 12;
17032
+ var COLLECTION_GAP_DEFAULT = "12px";
17033
+ function usableGap(value) {
17034
+ if (!value || value === "normal") return null;
17035
+ const n = parseFloat(value);
17036
+ if (!Number.isFinite(n) || n <= 0) return null;
17037
+ if (n > COLLECTION_GAP_MAX_PX) return COLLECTION_GAP_DEFAULT;
17038
+ return `${Math.round(n * 100) / 100}px`;
17039
+ }
17040
+ function collectionGap(el, tokens) {
17041
+ const fromCollection = usableGap(buttonCollectionOf(el)?.style.gap);
17042
+ if (fromCollection) return fromCollection;
17043
+ const parent = el.parentElement;
17044
+ if (parent) {
17045
+ const style = getComputedStyle(parent);
17046
+ const fromParent = usableGap(style.columnGap) ?? usableGap(style.gap);
17047
+ if (fromParent) return fromParent;
17048
+ }
17049
+ const fromTokens = usableGap(tokens?.sizes.m.gap);
17050
+ if (fromTokens) return fromTokens;
17051
+ return COLLECTION_GAP_DEFAULT;
17052
+ }
17053
+ function styleCollection(collection, gap) {
17054
+ collection.style.display = "inline-flex";
17055
+ collection.style.alignItems = "center";
17056
+ collection.style.flexWrap = "wrap";
17057
+ collection.style.gap = gap;
17058
+ collection.style.maxWidth = "100%";
17059
+ }
17060
+ function createCollection(doc, id, gap) {
17061
+ const collection = doc.createElement("div");
17062
+ collection.setAttribute(COLLECTION_ATTR, id);
17063
+ styleCollection(collection, gap);
17064
+ return collection;
17065
+ }
17066
+ function setButtonDragAttr(el) {
17067
+ if (isNavbarCta(el) && buttonCollectionOf(el)) el.removeAttribute("data-ohw-drag-disabled");
17068
+ else el.setAttribute("data-ohw-drag-disabled", "true");
17069
+ }
17070
+ function rekeyCopy(copy, id) {
17071
+ copy.setAttribute("data-ohw-href-key", id);
17072
+ const label = labelElement2(copy);
17073
+ const labelKey = labelKeyForHrefKey(id);
17074
+ if (label) label.setAttribute("data-ohw-key", labelKey);
17075
+ setButtonDragAttr(copy);
17076
+ return { labelKey };
17077
+ }
17078
+ function stampVariantClass(el, variant) {
17079
+ const raw = typeof el.className === "string" ? el.className : el.getAttribute("class") || "";
17080
+ const without = raw.replace(/\bbtn--(?:primary|secondary|ghost)\b/g, "").replace(/\s+/g, " ").trim();
17081
+ el.className = without ? `${without} btn--${variant}` : `btn btn--${variant}`;
17082
+ }
17083
+ function snapshotOf(el) {
17084
+ const id = buttonId(el) ?? "";
17085
+ const label = labelElement2(el);
17086
+ return {
17087
+ id,
17088
+ href: buttonHref(el),
17089
+ label: buttonLabel(el),
17090
+ labelKey: label?.getAttribute("data-ohw-key") ?? labelKeyForHrefKey(id),
17091
+ html: cleanClone(el).outerHTML
17092
+ };
17093
+ }
17094
+ function htmlToElement(doc, html) {
17095
+ const wrap = doc.createElement("div");
17096
+ wrap.innerHTML = html;
17097
+ const el = wrap.firstElementChild;
17098
+ return el instanceof HTMLElement ? el : null;
17099
+ }
17100
+ function findButton(doc, id) {
17101
+ return doc.querySelector(`[data-ohw-href-key="${CSS.escape(id)}"]`);
17102
+ }
17103
+ function duplicateButton(source, content) {
17104
+ if (!canDuplicateButton(source)) return null;
17105
+ const sourceId = buttonId(source);
17106
+ if (!sourceId) return null;
17107
+ const doc = source.ownerDocument;
17108
+ const tokens = tokensFromButton(source);
17109
+ const copyId = nextCopyId(sourceId, content, doc);
17110
+ const copy = cleanClone(source);
17111
+ const { labelKey } = rekeyCopy(copy, copyId);
17112
+ const href = buttonHref(source);
17113
+ const label = buttonLabel(source);
17114
+ if (href) copy.setAttribute("href", href);
17115
+ const sourceAssignment = readAssignments(content)[sourceId] ?? assignmentFor(source, content, tokens);
17116
+ const copyAssignment = {
17117
+ variant: "secondary",
17118
+ size: sourceAssignment.size
17119
+ };
17120
+ stampVariantClass(copy, "secondary");
17121
+ applyButtonStyle(copy, copyAssignment, tokens);
17122
+ const copyLabelEl = labelElement2(copy);
17123
+ if (copyLabelEl) copyLabelEl.textContent = label;
17124
+ let collection = buttonCollectionOf(source);
17125
+ const collections = readCollections(content);
17126
+ let collectionId = collection?.getAttribute(COLLECTION_ATTR) ?? "";
17127
+ let members = collectionId && collections[collectionId] ? [...collections[collectionId].members] : [];
17128
+ const gap = collectionGap(source, tokens);
17129
+ if (!collection) {
17130
+ collectionId = nextCollectionId(content, doc);
17131
+ collection = createCollection(doc, collectionId, gap);
17132
+ source.parentElement?.insertBefore(collection, source);
17133
+ collection.append(source, copy);
17134
+ setButtonDragAttr(source);
17135
+ setButtonDragAttr(copy);
17136
+ members = [sourceId, copyId];
17137
+ } else {
17138
+ styleCollection(collection, gap);
17139
+ source.after(copy);
17140
+ setButtonDragAttr(copy);
17141
+ if (!members.includes(sourceId)) members = listCollectionMembers(collection);
17142
+ const at = members.indexOf(sourceId);
17143
+ members.splice(at >= 0 ? at + 1 : members.length, 0, copyId);
17144
+ }
17145
+ const sourceLabelKey = labelElement2(source)?.getAttribute("data-ohw-key");
17146
+ const snapshots = {
17147
+ ...readSnapshots(content),
17148
+ [sourceId]: snapshotOf(source),
17149
+ [copyId]: snapshotOf(copy)
17150
+ };
17151
+ const nextCollections = {
17152
+ ...collections,
17153
+ [collectionId]: { id: collectionId, members, gap }
17154
+ };
17155
+ const assignments = withAssignment(content, copyId, copyAssignment);
17156
+ const nextContent = {
17157
+ ...content,
17158
+ [copyId]: href,
17159
+ [labelKey]: label,
17160
+ [BUTTON_COLLECTIONS_KEY]: JSON.stringify(nextCollections),
17161
+ [BUTTON_SNAPSHOTS_KEY]: JSON.stringify(snapshots),
17162
+ [BUTTON_ASSIGNMENTS_KEY]: JSON.stringify(assignments)
17163
+ };
17164
+ if (sourceLabelKey && nextContent[sourceLabelKey] === void 0) {
17165
+ nextContent[sourceLabelKey] = label;
17166
+ }
17167
+ const nodeKeys = [
17168
+ copyId,
17169
+ labelKey,
17170
+ BUTTON_COLLECTIONS_KEY,
17171
+ BUTTON_SNAPSHOTS_KEY,
17172
+ BUTTON_ASSIGNMENTS_KEY
17173
+ ];
17174
+ if (sourceLabelKey) nodeKeys.push(sourceLabelKey);
17175
+ return {
17176
+ copy,
17177
+ content: nextContent,
17178
+ nodes: contentNodes(nextContent, nodeKeys)
17179
+ };
17180
+ }
17181
+ function listCollectionMembers(collection) {
17182
+ return Array.from(collection.querySelectorAll(CTA_BUTTON_SELECTOR)).map((el) => buttonId(el)).filter((id) => Boolean(id));
17183
+ }
17184
+ function contentNodes(content, keys) {
17185
+ return keys.filter((key) => content[key] !== void 0).map((key) => ({ key, text: content[key] }));
17186
+ }
17187
+ function deleteButton(source, content) {
17188
+ if (!canDeleteButton(source)) return null;
17189
+ const id = buttonId(source);
17190
+ if (!id) return null;
17191
+ const parent = source.parentElement;
17192
+ const nextSibling = source.nextSibling;
17193
+ const removed = source;
17194
+ const collection = buttonCollectionOf(source);
17195
+ const collectionId = collection?.getAttribute(COLLECTION_ATTR) ?? "";
17196
+ const previousCollections = readCollections(content);
17197
+ const previousHidden = readHiddenButtons(content);
17198
+ const snapshots = { ...readSnapshots(content) };
17199
+ const assignments = { ...readAssignments(content) };
17200
+ delete assignments[id];
17201
+ const remainingBeforeUnwrap = collection ? listCollectionMembers(collection).filter((member) => member !== id) : [];
17202
+ removed.remove();
17203
+ let nextCollections = { ...previousCollections };
17204
+ let unwrapped = null;
17205
+ if (collection && collectionId) {
17206
+ const members = listCollectionMembers(collection);
17207
+ if (members.length <= 1) {
17208
+ unwrapped = collection;
17209
+ unwrapCollection(collection);
17210
+ delete nextCollections[collectionId];
17211
+ } else {
17212
+ nextCollections[collectionId] = {
17213
+ id: collectionId,
17214
+ members,
17215
+ gap: collection.style.gap || previousCollections[collectionId]?.gap || "12px"
17216
+ };
17217
+ }
17218
+ }
17219
+ const hidden = previousHidden.includes(id) ? previousHidden : [...previousHidden, id];
17220
+ const nextContent = {
17221
+ ...content,
17222
+ [id]: "",
17223
+ [BUTTON_COLLECTIONS_KEY]: JSON.stringify(nextCollections),
17224
+ [BUTTON_HIDDEN_KEY]: JSON.stringify(hidden),
17225
+ [BUTTON_SNAPSHOTS_KEY]: JSON.stringify(snapshots),
17226
+ [BUTTON_ASSIGNMENTS_KEY]: JSON.stringify(assignments)
17227
+ };
17228
+ return {
17229
+ content: nextContent,
17230
+ nodes: contentNodes(nextContent, [id, BUTTON_COLLECTIONS_KEY, BUTTON_HIDDEN_KEY, BUTTON_SNAPSHOTS_KEY, BUTTON_ASSIGNMENTS_KEY]),
17231
+ undo: () => {
17232
+ if (unwrapped && remainingBeforeUnwrap.length === 1) {
17233
+ const remaining = findButton(removed.ownerDocument, remainingBeforeUnwrap[0]);
17234
+ const host = remaining?.parentElement;
17235
+ if (remaining && host) {
17236
+ const restoredCollection = createCollection(
17237
+ removed.ownerDocument,
17238
+ collectionId,
17239
+ previousCollections[collectionId]?.gap || "12px"
17240
+ );
17241
+ host.insertBefore(restoredCollection, remaining);
17242
+ restoredCollection.append(remaining, removed);
17243
+ return;
17244
+ }
17245
+ }
17246
+ if (nextSibling && nextSibling.parentNode === parent) parent?.insertBefore(removed, nextSibling);
17247
+ else parent?.appendChild(removed);
17248
+ }
17249
+ };
17250
+ }
17251
+ function unwrapCollection(collection) {
17252
+ const parent = collection.parentElement;
17253
+ if (!parent) return;
17254
+ while (collection.firstChild) parent.insertBefore(collection.firstChild, collection);
17255
+ collection.remove();
17256
+ }
17257
+ function reorderButtonInCollection(button, clientX) {
17258
+ const collection = buttonCollectionOf(button);
17259
+ const id = buttonId(button);
17260
+ if (!collection || !id) return null;
17261
+ const siblings = Array.from(collection.querySelectorAll(CTA_BUTTON_SELECTOR)).filter((el) => el !== button);
17262
+ let placed = false;
17263
+ for (const sibling of siblings) {
17264
+ const rect = sibling.getBoundingClientRect();
17265
+ if (clientX < rect.left + rect.width / 2) {
17266
+ sibling.before(button);
17267
+ placed = true;
17268
+ break;
17269
+ }
17270
+ }
17271
+ if (!placed) collection.append(button);
17272
+ return listCollectionMembers(collection);
17273
+ }
17274
+ function listVisibleCollectionButtons(collection) {
17275
+ return Array.from(collection.querySelectorAll(CTA_BUTTON_SELECTOR)).filter((el) => {
17276
+ if (!buttonId(el)) return false;
17277
+ return el.getClientRects().length > 0;
17278
+ });
17279
+ }
17280
+ function isVerticalButtonLayout(items, collection) {
17281
+ if (items.length >= 2) {
17282
+ const a = items[0].getBoundingClientRect();
17283
+ const b = items[1].getBoundingClientRect();
17284
+ return b.top - a.bottom > b.left - a.right;
17285
+ }
17286
+ const style = window.getComputedStyle(collection);
17287
+ return style.flexDirection === "column" || style.flexDirection === "column-reverse";
17288
+ }
17289
+ function buildButtonCollectionDropSlots(collection) {
17290
+ const items = listVisibleCollectionButtons(collection);
17291
+ const barThickness = 3;
17292
+ const slots = [];
17293
+ if (items.length === 0) {
17294
+ const rect = collection.getBoundingClientRect();
17295
+ slots.push({
17296
+ insertIndex: 0,
17297
+ left: rect.left + rect.width / 2 - barThickness / 2,
17298
+ top: rect.top,
17299
+ width: barThickness,
17300
+ height: Math.max(rect.height, 24),
17301
+ direction: "vertical"
17302
+ });
17303
+ return slots;
17304
+ }
17305
+ if (isVerticalButtonLayout(items, collection)) {
17306
+ const edgeGap2 = items.length >= 2 ? Math.max(0, items[1].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom) : 16;
17307
+ for (let i = 0; i <= items.length; i++) {
17308
+ let top;
17309
+ let left;
17310
+ let width;
17311
+ if (i === 0) {
17312
+ const first = items[0].getBoundingClientRect();
17313
+ top = first.top - edgeGap2 / 2 - barThickness / 2;
17314
+ left = first.left;
17315
+ width = first.width;
17316
+ } else if (i === items.length) {
17317
+ const last = items[items.length - 1].getBoundingClientRect();
17318
+ const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap2;
17319
+ top = last.bottom + lastGap / 2 - barThickness / 2;
17320
+ left = last.left;
17321
+ width = last.width;
17322
+ } else {
17323
+ const prev = items[i - 1].getBoundingClientRect();
17324
+ const next = items[i].getBoundingClientRect();
17325
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
17326
+ left = Math.min(prev.left, next.left);
17327
+ width = Math.max(prev.right, next.right) - left;
17328
+ }
17329
+ slots.push({
17330
+ insertIndex: i,
17331
+ left,
17332
+ top,
17333
+ width: Math.max(width, 40),
17334
+ height: barThickness,
17335
+ direction: "horizontal"
17336
+ });
17337
+ }
17338
+ return slots;
17339
+ }
17340
+ const edgeGap = items.length >= 2 ? Math.max(0, items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right) : 12;
17341
+ for (let i = 0; i <= items.length; i++) {
17342
+ let left;
17343
+ let top;
17344
+ let height;
17345
+ if (i === 0) {
17346
+ const first = items[0].getBoundingClientRect();
17347
+ left = first.left - edgeGap / 2 - barThickness / 2;
17348
+ top = first.top;
17349
+ height = first.height;
17350
+ } else if (i === items.length) {
17351
+ const last = items[items.length - 1].getBoundingClientRect();
17352
+ const lastGap = items.length >= 2 ? Math.max(0, last.left - items[items.length - 2].getBoundingClientRect().right) : edgeGap;
17353
+ left = last.right + lastGap / 2 - barThickness / 2;
17354
+ top = last.top;
17355
+ height = last.height;
17356
+ } else {
17357
+ const prev = items[i - 1].getBoundingClientRect();
17358
+ const next = items[i].getBoundingClientRect();
17359
+ left = (prev.right + next.left) / 2 - barThickness / 2;
17360
+ top = Math.min(prev.top, next.top);
17361
+ height = Math.max(prev.bottom, next.bottom) - top;
17362
+ }
17363
+ slots.push({
17364
+ insertIndex: i,
17365
+ left,
17366
+ top,
17367
+ width: barThickness,
17368
+ height: Math.max(height, 24),
17369
+ direction: "vertical"
17370
+ });
17371
+ }
17372
+ return slots;
17373
+ }
17374
+ function hitTestButtonCollectionDropSlot(collection, clientX, clientY) {
17375
+ const slots = buildButtonCollectionDropSlots(collection);
17376
+ let best = null;
17377
+ for (const slot of slots) {
17378
+ const cx2 = slot.left + slot.width / 2;
17379
+ const cy = slot.top + slot.height / 2;
17380
+ const dist = slot.direction === "vertical" ? Math.abs(clientX - cx2) + Math.abs(clientY - cy) * 0.25 : Math.abs(clientY - cy) + Math.abs(clientX - cx2) * 0.25;
17381
+ if (!best || dist < best.dist) best = { slot, dist };
17382
+ }
17383
+ return best?.slot ?? null;
17384
+ }
17385
+ function insertButtonAtCollectionIndex(button, insertIndex) {
17386
+ const collection = buttonCollectionOf(button);
17387
+ if (!collection) return null;
17388
+ const items = listVisibleCollectionButtons(collection);
17389
+ const from = items.indexOf(button);
17390
+ if (from < 0) return null;
17391
+ const others = items.filter((el) => el !== button);
17392
+ let index = insertIndex;
17393
+ if (from < insertIndex) index -= 1;
17394
+ if (index <= 0) {
17395
+ const first = others[0];
17396
+ if (first) first.before(button);
17397
+ else collection.append(button);
17398
+ } else if (index >= others.length) {
17399
+ collection.append(button);
17400
+ } else {
17401
+ others[index].before(button);
17402
+ }
17403
+ return listCollectionMembers(collection);
17404
+ }
17405
+ function persistCollectionOrder(collection, content) {
17406
+ const id = collection.getAttribute(COLLECTION_ATTR);
17407
+ if (!id) return null;
17408
+ const collections = readCollections(content);
17409
+ const current = collections[id];
17410
+ const members = listCollectionMembers(collection);
17411
+ const next = {
17412
+ ...collections,
17413
+ [id]: { id, members, gap: collection.style.gap || current?.gap || "12px" }
17414
+ };
17415
+ const nextContent = { ...content, [BUTTON_COLLECTIONS_KEY]: JSON.stringify(next) };
17416
+ return { content: nextContent, nodes: [{ key: BUTTON_COLLECTIONS_KEY, text: nextContent[BUTTON_COLLECTIONS_KEY] }] };
17417
+ }
17418
+ function hideButton(el) {
17419
+ if (el.getAttribute("data-ohw-button-hidden") === "") return;
17420
+ el.setAttribute("data-ohw-button-hidden", "");
17421
+ el.style.display = "none";
17422
+ }
17423
+ function syncButtonsFromContent(doc, content) {
17424
+ reconcileButtonCollections(doc, content);
17425
+ applyButtonContentFromContent(doc, content);
17426
+ applyButtonStylesFromContent(doc, content);
17427
+ }
17428
+ function applyButtonContentFromContent(doc, content) {
17429
+ const snapshots = readSnapshots(content);
17430
+ for (const snapshot of Object.values(snapshots)) {
17431
+ const el = findButton(doc, snapshot.id);
17432
+ if (!el) continue;
17433
+ applySnapshotContent(el, snapshot, content);
17434
+ }
17435
+ }
17436
+ function applySnapshotContent(el, snapshot, content) {
17437
+ const href = content[snapshot.id] ?? snapshot.href;
17438
+ if (href) el.setAttribute("href", href);
17439
+ const labelKey = snapshot.labelKey || labelKeyForHrefKey(snapshot.id);
17440
+ if (labelKey && !el.querySelector(`[data-ohw-key="${CSS.escape(labelKey)}"]`)) {
17441
+ const label2 = labelElement2(el);
17442
+ if (label2) label2.setAttribute("data-ohw-key", labelKey);
17443
+ }
17444
+ const text = content[labelKey];
17445
+ if (text === void 0) return;
17446
+ const label = labelElement2(el);
17447
+ if (label) {
17448
+ if (label.getAttribute("data-ohw-key") !== labelKey) label.setAttribute("data-ohw-key", labelKey);
17449
+ if (label.textContent !== text) label.textContent = text;
17450
+ }
17451
+ }
17452
+ function reconcileButtonCollections(doc, content) {
17453
+ const hidden = new Set(readHiddenButtons(content));
17454
+ for (const id of hidden) {
17455
+ const el = findButton(doc, id);
17456
+ if (el) hideButton(el);
17457
+ }
17458
+ const snapshots = readSnapshots(content);
17459
+ const collections = readCollections(content);
17460
+ for (const collectionState of Object.values(collections)) {
17461
+ const live = collectionState.members.map((id) => findButton(doc, id) ?? restoreSnapshot(doc, snapshots[id], content)).filter((el) => Boolean(el && !hidden.has(buttonId(el) ?? "")));
17462
+ if (live.length === 0) continue;
17463
+ if (live.length === 1) {
17464
+ const existing = buttonCollectionOf(live[0]);
17465
+ if (existing) unwrapCollection(existing);
17466
+ continue;
17467
+ }
17468
+ const anchor = live.find((el) => el.isConnected) ?? live[0];
17469
+ let collection = live.map((el) => buttonCollectionOf(el)).find((el) => el?.getAttribute(COLLECTION_ATTR) === collectionState.id) ?? null;
17470
+ if (!collection) {
17471
+ if (!anchor.parentElement) continue;
17472
+ collection = createCollection(doc, collectionState.id, collectionState.gap);
17473
+ anchor.parentElement.insertBefore(collection, anchor);
17474
+ }
17475
+ styleCollection(collection, collectionState.gap);
17476
+ const current = listCollectionMembers(collection);
17477
+ const sameOrder = current.length === live.length && current.every((id, index) => id === buttonId(live[index]));
17478
+ if (!sameOrder) {
17479
+ for (const member of live) collection.append(member);
17480
+ }
17481
+ for (const member of live) setButtonDragAttr(member);
17482
+ }
17483
+ }
17484
+ function restoreSnapshot(doc, snapshot, content) {
17485
+ if (!snapshot) return null;
17486
+ const existing = findButton(doc, snapshot.id);
17487
+ if (existing) {
17488
+ applySnapshotContent(existing, snapshot, content);
17489
+ return existing;
17490
+ }
17491
+ const el = htmlToElement(doc, snapshot.html);
17492
+ if (!el) return null;
17493
+ el.setAttribute("data-ohw-href-key", snapshot.id);
17494
+ const label = labelElement2(el);
17495
+ const labelKey = snapshot.labelKey || labelKeyForHrefKey(snapshot.id);
17496
+ if (label) label.setAttribute("data-ohw-key", labelKey);
17497
+ applySnapshotContent(el, snapshot, content);
17498
+ setButtonDragAttr(el);
17499
+ return el;
17500
+ }
17501
+ function navbarCtasInScope(el) {
17502
+ const scope = el.closest("nav, [data-ohw-nav-root], [data-ohw-nav-drawer]") ?? el.parentElement;
17503
+ if (!scope) return [];
17504
+ return Array.from(scope.querySelectorAll(CTA_BUTTON_SELECTOR)).filter(
17505
+ (button) => isNavbarCta(button) && buttonId(button)
17506
+ );
17507
+ }
17508
+ function ensureNavbarButtonCollection(button, content) {
17509
+ const existing = buttonCollectionOf(button);
17510
+ if (existing) return existing;
17511
+ if (!isNavbarCta(button) || !buttonId(button)) return null;
17512
+ const parent = button.parentElement;
17513
+ if (!parent) return null;
17514
+ const siblings = navbarCtasInScope(button).filter((el) => el.parentElement === parent);
17515
+ if (siblings.length < 2) return null;
17516
+ const id = nextCollectionId(content, button.ownerDocument);
17517
+ const collection = createCollection(button.ownerDocument, id, collectionGap(button, tokensFromButton(button)));
17518
+ siblings[0].before(collection);
17519
+ for (const member of siblings) {
17520
+ collection.append(member);
17521
+ setButtonDragAttr(member);
17522
+ }
17523
+ return collection;
17524
+ }
17525
+ function collectionDragEnabled(el) {
17526
+ if (!isNavbarCta(el) || !buttonId(el)) return false;
17527
+ if (buttonCollectionOf(el)) return true;
17528
+ return navbarCtasInScope(el).length >= 2;
17529
+ }
17530
+
17531
+ // src/lib/item-drag-interaction.ts
17532
+ function disableNativeHrefDrag(el) {
17533
+ if (el.draggable) el.draggable = false;
17534
+ if (el.getAttribute("draggable") !== "false") {
17535
+ el.setAttribute("draggable", "false");
17536
+ }
17537
+ }
17538
+ function clearTextSelection() {
17539
+ const sel = window.getSelection();
17540
+ if (sel && !sel.isCollapsed) sel.removeAllRanges();
17541
+ }
17542
+ function armItemPressDrag() {
17543
+ document.documentElement.setAttribute("data-ohw-footer-press-drag", "");
17544
+ }
17545
+ function lockItemDuringDrag() {
17546
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
17547
+ document.documentElement.setAttribute("data-ohw-item-dragging", "");
17548
+ clearTextSelection();
17549
+ }
17550
+ function unlockItemDragInteraction() {
17551
+ const wasDragging = document.documentElement.hasAttribute("data-ohw-item-dragging");
17552
+ document.documentElement.removeAttribute("data-ohw-footer-press-drag");
17553
+ document.documentElement.removeAttribute("data-ohw-item-dragging");
17554
+ if (wasDragging) clearTextSelection();
17555
+ }
17556
+ var armFooterPressDrag = armItemPressDrag;
17557
+ var lockFooterDuringDrag = lockItemDuringDrag;
17558
+ var unlockFooterDragInteraction = unlockItemDragInteraction;
17559
+
17560
+ // src/lib/nav-dnd.ts
17561
+ function listReorderableNavItems(draggedEl) {
17562
+ return listActiveNavbarItems(draggedEl).filter((el) => {
17563
+ const key = el.getAttribute("data-ohw-href-key");
17564
+ return isNavbarHrefKey(key) && !isNestedNavChild(el);
17565
+ });
17566
+ }
17567
+ function resolveParentNavHrefKey(child) {
17568
+ const childrenRoot = child.closest("[data-ohw-nav-children]");
17569
+ const group = childrenRoot?.closest("[data-ohw-nav-group]");
17570
+ if (!group) return null;
17571
+ const trigger = group.querySelector(":scope > [data-ohw-href-key]") ?? Array.from(group.querySelectorAll("[data-ohw-href-key]")).find(
17572
+ (el) => !el.closest("[data-ohw-nav-children]")
17573
+ ) ?? null;
17574
+ const key = trigger?.getAttribute("data-ohw-href-key");
17575
+ return key && isNavbarHrefKey(key) ? key : null;
17576
+ }
17577
+ function listNavChildren(parentHrefKey, draggedEl) {
17578
+ const items = listActiveNavbarItems(draggedEl);
17579
+ const parent = items.find((el) => el.getAttribute("data-ohw-href-key") === parentHrefKey);
17580
+ if (!parent) return [];
17581
+ const group = parent.closest("[data-ohw-nav-group]");
17582
+ const childrenRoot = group?.querySelector(":scope > [data-ohw-nav-children]");
17583
+ if (!childrenRoot) return [];
17584
+ return Array.from(childrenRoot.querySelectorAll(":scope > [data-ohw-href-key]")).filter(
17585
+ (el) => isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))
17586
+ );
17587
+ }
17588
+ function isVerticalNavLayout(items, draggedEl) {
17589
+ const visible = items.filter((el) => el.getClientRects().length > 0);
17590
+ if (visible.length >= 2) {
17591
+ const a = visible[0].getBoundingClientRect();
17592
+ const b = visible[1].getBoundingClientRect();
17593
+ const verticalGap = b.top - a.bottom;
17594
+ const horizontalGap = b.left - a.right;
17595
+ return verticalGap > horizontalGap;
17596
+ }
17597
+ const container = getActiveNavbarListContainer(draggedEl);
17598
+ if (!container) return false;
17599
+ const style = window.getComputedStyle(container);
17600
+ return style.flexDirection === "column" || style.flexDirection === "column-reverse";
17601
+ }
17602
+ function buildVerticalRootNavDropSlots(items, draggedEl) {
17603
+ const slots = [];
17604
+ const barThickness = 3;
17605
+ if (items.length === 0) {
17606
+ const container = getActiveNavbarListContainer(draggedEl);
17607
+ if (!container) return slots;
17608
+ const rect = container.getBoundingClientRect();
17609
+ slots.push({
17610
+ insertIndex: 0,
17611
+ parentId: null,
17612
+ left: rect.left,
17613
+ top: rect.top + rect.height / 2 - barThickness / 2,
17614
+ width: Math.max(rect.width, 40),
17615
+ height: barThickness,
17616
+ direction: "horizontal"
17617
+ });
17618
+ return slots;
17619
+ }
17620
+ const edgeGap = (() => {
17621
+ if (items.length < 2) return 16;
17622
+ const a = items[0].getBoundingClientRect();
17623
+ const b = items[1].getBoundingClientRect();
17624
+ return Math.max(0, b.top - a.bottom);
17625
+ })();
17626
+ for (let i = 0; i <= items.length; i++) {
17627
+ let top;
17628
+ let width;
17629
+ let left;
17630
+ if (i === 0) {
17631
+ const first = items[0].getBoundingClientRect();
17632
+ top = first.top - edgeGap / 2 - barThickness / 2;
17633
+ left = first.left;
17634
+ width = first.width;
17635
+ } else if (i === items.length) {
17636
+ const last = items[items.length - 1].getBoundingClientRect();
17637
+ const lastGap = items.length >= 2 ? Math.max(0, last.top - items[items.length - 2].getBoundingClientRect().bottom) : edgeGap;
17638
+ top = last.bottom + lastGap / 2 - barThickness / 2;
17639
+ left = last.left;
17640
+ width = last.width;
17641
+ } else {
17642
+ const prev = items[i - 1].getBoundingClientRect();
17643
+ const next = items[i].getBoundingClientRect();
17644
+ top = (prev.bottom + next.top) / 2 - barThickness / 2;
17645
+ left = Math.min(prev.left, next.left);
17646
+ width = Math.max(prev.right, next.right) - left;
17647
+ }
17648
+ slots.push({
17649
+ insertIndex: i,
17650
+ parentId: null,
17651
+ left,
17652
+ top,
17653
+ width: Math.max(width, 40),
17654
+ height: barThickness,
17655
+ direction: "horizontal"
17656
+ });
17657
+ }
17658
+ return slots;
17659
+ }
17660
+ function buildHorizontalRootNavDropSlots(items, draggedEl) {
17661
+ const slots = [];
17662
+ const barThickness = 3;
17663
+ if (items.length === 0) {
17664
+ const container = getActiveNavbarListContainer(draggedEl);
17665
+ if (!container) return slots;
17666
+ const rect = container.getBoundingClientRect();
17667
+ slots.push({
17668
+ insertIndex: 0,
17669
+ parentId: null,
17670
+ left: rect.left + rect.width / 2 - barThickness / 2,
17671
+ top: rect.top,
17672
+ width: barThickness,
17673
+ height: Math.max(rect.height, 24),
17674
+ direction: "vertical"
17675
+ });
17676
+ return slots;
17677
+ }
17678
+ const edgeGap = (() => {
17679
+ if (items.length < 2) return 32;
17680
+ const a = items[0].getBoundingClientRect();
17681
+ const b = items[1].getBoundingClientRect();
17682
+ return Math.max(0, b.left - a.right);
17683
+ })();
17684
+ for (let i = 0; i <= items.length; i++) {
17685
+ let left;
17686
+ let top;
17687
+ let height;
17688
+ if (i === 0) {
17689
+ const first = items[0].getBoundingClientRect();
17690
+ left = first.left - edgeGap / 2 - barThickness / 2;
17691
+ top = first.top;
17692
+ height = first.height;
17693
+ } else if (i === items.length) {
17694
+ const last = items[items.length - 1].getBoundingClientRect();
17695
+ const lastGap = items.length >= 2 ? Math.max(0, last.left - items[items.length - 2].getBoundingClientRect().right) : edgeGap;
17696
+ left = last.right + lastGap / 2 - barThickness / 2;
17697
+ top = last.top;
17698
+ height = last.height;
17699
+ } else {
17700
+ const prev = items[i - 1].getBoundingClientRect();
17701
+ const next = items[i].getBoundingClientRect();
17702
+ left = (prev.right + next.left) / 2 - barThickness / 2;
17703
+ top = Math.min(prev.top, next.top);
17704
+ height = Math.max(prev.bottom, next.bottom) - top;
17705
+ }
17706
+ slots.push({
17707
+ insertIndex: i,
17708
+ parentId: null,
17709
+ left,
17710
+ top,
17711
+ width: barThickness,
17712
+ height: Math.max(height, 24),
17713
+ direction: "vertical"
17714
+ });
17715
+ }
17716
+ return slots;
17717
+ }
17718
+ function buildRootNavDropSlots(draggedEl) {
17719
+ const items = listReorderableNavItems(draggedEl);
17720
+ if (isVerticalNavLayout(items, draggedEl)) {
17721
+ return buildVerticalRootNavDropSlots(items, draggedEl);
17722
+ }
17723
+ return buildHorizontalRootNavDropSlots(items, draggedEl);
16188
17724
  }
16189
17725
  function buildChildNavDropSlots(parentHrefKey, draggedEl) {
16190
17726
  const children = listNavChildren(parentHrefKey, draggedEl);
@@ -16421,7 +17957,7 @@ function useNavItemDrag({
16421
17957
  document.querySelectorAll(
16422
17958
  "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
16423
17959
  ).forEach((el) => {
16424
- if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
17960
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key")) || isCtaButton(el)) {
16425
17961
  disableNativeHrefDrag(el);
16426
17962
  }
16427
17963
  });
@@ -16730,12 +18266,6 @@ var SECTION_DRAG_EXCLUDED_SELECTOR = [
16730
18266
  "[data-ohw-media-overlay]",
16731
18267
  "[data-ohw-media-skeleton]"
16732
18268
  ].join(", ");
16733
- function visibleClip(ps) {
16734
- if (!ps) return null;
16735
- const top = Math.max(0, ps.headerH - ps.iframeOffsetTop);
16736
- const bottom = Math.min(window.innerHeight, ps.headerH + ps.canvasH - ps.iframeOffsetTop);
16737
- return { top, bottom: Math.max(top, bottom) };
16738
- }
16739
18269
  function useSectionDrag({
16740
18270
  isEditMode,
16741
18271
  editContentRef,
@@ -16772,7 +18302,7 @@ function useSectionDrag({
16772
18302
  }, [postToParentRef, stopAutoScroll]);
16773
18303
  const updateAutoScroll = useCallback9(
16774
18304
  (clientY) => {
16775
- const clip = visibleClip(parentScrollRef.current);
18305
+ const clip = getIframeChromeClip(parentScrollRef.current);
16776
18306
  let delta = 0;
16777
18307
  if (clip) {
16778
18308
  const distTop = clientY - clip.top;
@@ -16979,7 +18509,7 @@ function useSectionDrag({
16979
18509
 
16980
18510
  // src/ui/footer-container-chrome.tsx
16981
18511
  import { Plus as Plus2 } from "lucide-react";
16982
- import { jsx as jsx31, jsxs as jsxs21 } from "react/jsx-runtime";
18512
+ import { jsx as jsx32, jsxs as jsxs22 } from "react/jsx-runtime";
16983
18513
  function FooterContainerChrome({
16984
18514
  rect,
16985
18515
  onAdd,
@@ -16987,7 +18517,7 @@ function FooterContainerChrome({
16987
18517
  }) {
16988
18518
  const chromeGap = 6;
16989
18519
  const buttonMargin = 7;
16990
- return /* @__PURE__ */ jsx31(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx31(
18520
+ return /* @__PURE__ */ jsx32(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx32(
16991
18521
  "div",
16992
18522
  {
16993
18523
  "data-ohw-footer-container-chrome": "",
@@ -16999,8 +18529,8 @@ function FooterContainerChrome({
16999
18529
  width: rect.width + chromeGap * 2,
17000
18530
  height: rect.height + chromeGap * 2
17001
18531
  },
17002
- children: /* @__PURE__ */ jsxs21(Tooltip, { children: [
17003
- /* @__PURE__ */ jsx31(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx31(
18532
+ children: /* @__PURE__ */ jsxs22(Tooltip, { children: [
18533
+ /* @__PURE__ */ jsx32(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx32(
17004
18534
  "button",
17005
18535
  {
17006
18536
  type: "button",
@@ -17019,10 +18549,10 @@ function FooterContainerChrome({
17019
18549
  if (addDisabled) return;
17020
18550
  onAdd();
17021
18551
  },
17022
- children: /* @__PURE__ */ jsx31(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
18552
+ children: /* @__PURE__ */ jsx32(Plus2, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
17023
18553
  }
17024
18554
  ) }),
17025
- /* @__PURE__ */ jsx31(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
18555
+ /* @__PURE__ */ jsx32(TooltipContent, { side: "bottom", sideOffset: 9, children: addDisabled ? "Maximum columns reached" : "Add item" })
17026
18556
  ] })
17027
18557
  }
17028
18558
  ) });
@@ -17184,7 +18714,14 @@ function collectEditableNodes(extraContent, root = document) {
17184
18714
  // so collecting the DOM alone left them behind: the draft knew the row was showing icons and
17185
18715
  // had gained an item, and the published page went back to the template's own (OHH-736).
17186
18716
  SOCIALS_ORDER_KEY,
17187
- SOCIALS_DISPLAY_KEY
18717
+ SOCIALS_DISPLAY_KEY,
18718
+ // Button variant/size tokens and per-CTA assignments — JSON in editContent, applied on hydrate.
18719
+ BUTTON_VARIANTS_KEY,
18720
+ BUTTON_SIZES_KEY,
18721
+ BUTTON_ASSIGNMENTS_KEY,
18722
+ BUTTON_COLLECTIONS_KEY,
18723
+ BUTTON_SNAPSHOTS_KEY,
18724
+ BUTTON_HIDDEN_KEY
17188
18725
  ]) {
17189
18726
  const text = extraContent[key];
17190
18727
  if (typeof text === "string" && text.length > 0) {
@@ -17505,13 +19042,13 @@ function deleteSelectedNavFooterItem(deps) {
17505
19042
 
17506
19043
  // src/ui/navbar-container-chrome.tsx
17507
19044
  import { Plus as Plus3 } from "lucide-react";
17508
- import { jsx as jsx32 } from "react/jsx-runtime";
19045
+ import { jsx as jsx33 } from "react/jsx-runtime";
17509
19046
  function NavbarContainerChrome({
17510
19047
  rect,
17511
19048
  onAdd
17512
19049
  }) {
17513
19050
  const chromeGap = 6;
17514
- return /* @__PURE__ */ jsx32(
19051
+ return /* @__PURE__ */ jsx33(
17515
19052
  "div",
17516
19053
  {
17517
19054
  "data-ohw-navbar-container-chrome": "",
@@ -17523,7 +19060,7 @@ function NavbarContainerChrome({
17523
19060
  width: rect.width + chromeGap * 2,
17524
19061
  height: rect.height + chromeGap * 2
17525
19062
  },
17526
- children: /* @__PURE__ */ jsx32(
19063
+ children: /* @__PURE__ */ jsx33(
17527
19064
  "button",
17528
19065
  {
17529
19066
  type: "button",
@@ -17540,7 +19077,7 @@ function NavbarContainerChrome({
17540
19077
  e.stopPropagation();
17541
19078
  onAdd();
17542
19079
  },
17543
- children: /* @__PURE__ */ jsx32(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
19080
+ children: /* @__PURE__ */ jsx33(Plus3, { className: "size-4 shrink-0 text-foreground", "aria-hidden": true })
17544
19081
  }
17545
19082
  )
17546
19083
  }
@@ -17549,7 +19086,7 @@ function NavbarContainerChrome({
17549
19086
 
17550
19087
  // src/ui/drop-indicator.tsx
17551
19088
  import * as React11 from "react";
17552
- import { jsx as jsx33 } from "react/jsx-runtime";
19089
+ import { jsx as jsx34 } from "react/jsx-runtime";
17553
19090
  var dropIndicatorVariants = cva(
17554
19091
  "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
17555
19092
  {
@@ -17573,7 +19110,7 @@ var dropIndicatorVariants = cva(
17573
19110
  );
17574
19111
  var DropIndicator = React11.forwardRef(
17575
19112
  ({ className, direction, state, ...props }, ref) => {
17576
- return /* @__PURE__ */ jsx33(
19113
+ return /* @__PURE__ */ jsx34(
17577
19114
  "div",
17578
19115
  {
17579
19116
  ref,
@@ -17590,7 +19127,7 @@ var DropIndicator = React11.forwardRef(
17590
19127
  DropIndicator.displayName = "DropIndicator";
17591
19128
 
17592
19129
  // src/ui/badge.tsx
17593
- import { jsx as jsx34 } from "react/jsx-runtime";
19130
+ import { jsx as jsx35 } from "react/jsx-runtime";
17594
19131
  var badgeVariants = cva(
17595
19132
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
17596
19133
  {
@@ -17608,12 +19145,12 @@ var badgeVariants = cva(
17608
19145
  }
17609
19146
  );
17610
19147
  function Badge({ className, variant, ...props }) {
17611
- return /* @__PURE__ */ jsx34("div", { className: cn(badgeVariants({ variant }), className), ...props });
19148
+ return /* @__PURE__ */ jsx35("div", { className: cn(badgeVariants({ variant }), className), ...props });
17612
19149
  }
17613
19150
 
17614
19151
  // src/OhhwellsBridge.tsx
17615
- import { Link as Link2, Plus as Plus4, Settings } from "lucide-react";
17616
- import { Fragment as Fragment10, jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
19152
+ import { Brush as Brush2, Link as Link2, Plus as Plus4, Settings } from "lucide-react";
19153
+ import { Fragment as Fragment10, jsx as jsx36, jsxs as jsxs23 } from "react/jsx-runtime";
17617
19154
  var PRIMARY5 = "#0885FE";
17618
19155
  var IMAGE_FADE_MS = 300;
17619
19156
  var MEDIA_SETTLE_TIMEOUT_MS = 2500;
@@ -17735,7 +19272,7 @@ function duplicateSchedulingWidget(instanceId, scheduleId) {
17735
19272
  schedulingRoots.set(container, root);
17736
19273
  flushSync2(() => {
17737
19274
  root.render(
17738
- /* @__PURE__ */ jsx35(
19275
+ /* @__PURE__ */ jsx36(
17739
19276
  SchedulingWidget,
17740
19277
  {
17741
19278
  notifyOnConnect: false,
@@ -17873,7 +19410,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
17873
19410
  schedulingRoots.set(container, root);
17874
19411
  flushSync2(() => {
17875
19412
  root.render(
17876
- /* @__PURE__ */ jsx35(
19413
+ /* @__PURE__ */ jsx36(
17877
19414
  SchedulingWidget,
17878
19415
  {
17879
19416
  notifyOnConnect,
@@ -18036,8 +19573,8 @@ function lockVideoBox(video) {
18036
19573
  }
18037
19574
  const fit = video.style.objectFit || getComputedStyle(video).objectFit;
18038
19575
  const bg = getComputedStyle(video).backgroundColor;
18039
- const isTransparent = bg === "rgba(0, 0, 0, 0)" || bg === "transparent" || !bg;
18040
- if (fit === "contain" && isTransparent) {
19576
+ const isTransparent2 = bg === "rgba(0, 0, 0, 0)" || bg === "transparent" || !bg;
19577
+ if (fit === "contain" && isTransparent2) {
18041
19578
  video.style.backgroundColor = "var(--color-dark, #000)";
18042
19579
  }
18043
19580
  }
@@ -18187,7 +19724,10 @@ function listNavigationItems() {
18187
19724
  ).filter((el) => isNavigationItem2(el));
18188
19725
  }
18189
19726
  function getNavigationItemReorderState(anchor) {
18190
- if (isCtaButton(anchor)) return { key: null, disabled: false };
19727
+ if (isCtaButton(anchor)) {
19728
+ if (!collectionDragEnabled(anchor)) return { key: null, disabled: true };
19729
+ return { key: buttonId(anchor), disabled: false };
19730
+ }
18191
19731
  return getReorderHandleStateFromAnchor(anchor);
18192
19732
  }
18193
19733
  function getReorderHandleState(el) {
@@ -18286,6 +19826,12 @@ function isPointOverNavigation(x, y) {
18286
19826
  const r2 = panel.getBoundingClientRect();
18287
19827
  if (x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom) return true;
18288
19828
  }
19829
+ for (const el of document.querySelectorAll('[data-ohw-role="navbar-button"]')) {
19830
+ const r2 = el.getBoundingClientRect();
19831
+ if (r2.width > 0 && r2.height > 0 && x >= r2.left && x <= r2.right && y >= r2.top && y <= r2.bottom) {
19832
+ return true;
19833
+ }
19834
+ }
18289
19835
  return false;
18290
19836
  }
18291
19837
  function findHoveredNavOrFooterContainer(x, y) {
@@ -18619,7 +20165,7 @@ function EditGlowChrome({
18619
20165
  hideHandle = false
18620
20166
  }) {
18621
20167
  const GAP = SELECTION_CHROME_GAP2;
18622
- return /* @__PURE__ */ jsxs22(
20168
+ return /* @__PURE__ */ jsxs23(
18623
20169
  "div",
18624
20170
  {
18625
20171
  ref: elRef,
@@ -18634,7 +20180,7 @@ function EditGlowChrome({
18634
20180
  zIndex: 2147483646
18635
20181
  },
18636
20182
  children: [
18637
- /* @__PURE__ */ jsx35(
20183
+ /* @__PURE__ */ jsx36(
18638
20184
  "div",
18639
20185
  {
18640
20186
  style: {
@@ -18648,7 +20194,7 @@ function EditGlowChrome({
18648
20194
  }
18649
20195
  }
18650
20196
  ),
18651
- reorderHrefKey && !hideHandle && /* @__PURE__ */ jsx35(
20197
+ reorderHrefKey && !hideHandle && /* @__PURE__ */ jsx36(
18652
20198
  "div",
18653
20199
  {
18654
20200
  "data-ohw-drag-handle-container": "",
@@ -18660,7 +20206,7 @@ function EditGlowChrome({
18660
20206
  transform: "translate(calc(-100% - 7px), -50%)",
18661
20207
  pointerEvents: dragDisabled ? "none" : "auto"
18662
20208
  },
18663
- children: /* @__PURE__ */ jsx35(
20209
+ children: /* @__PURE__ */ jsx36(
18664
20210
  DragHandle,
18665
20211
  {
18666
20212
  "aria-label": `Reorder ${reorderHrefKey}`,
@@ -18674,22 +20220,12 @@ function EditGlowChrome({
18674
20220
  }
18675
20221
  );
18676
20222
  }
18677
- function getIframeVisibleClip(parentScroll) {
18678
- if (!parentScroll) return null;
18679
- const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
18680
- const clipTop = Math.max(0, visibleCanvasTop - iframeOffsetTop);
18681
- const clipBottom = Math.min(
18682
- window.innerHeight,
18683
- visibleCanvasTop + canvasH - iframeOffsetTop
18684
- );
18685
- return { top: clipTop, bottom: Math.max(clipTop, clipBottom) };
18686
- }
18687
20223
  function isRectOutsideClip(rect, clip) {
18688
20224
  if (!clip) return false;
18689
20225
  return rect.bottom < clip.top || rect.top > clip.bottom;
18690
20226
  }
18691
20227
  function applyVisibleViewport(el, parentScroll) {
18692
- const clip = getIframeVisibleClip(parentScroll);
20228
+ const clip = getIframeChromeClip(parentScroll);
18693
20229
  const top = clip?.top ?? 0;
18694
20230
  const height = clip ? clip.bottom - clip.top : window.innerHeight;
18695
20231
  el.style.top = `${top}px`;
@@ -18729,7 +20265,7 @@ function clampToolbarLeft(centerX, toolbarWidth) {
18729
20265
  function calcToolbarPos(rect, parentScroll, toolbarW = 306) {
18730
20266
  const GAP = TOOLBAR_OFFSET_FROM_ELEMENT;
18731
20267
  const APPROX_H = 32;
18732
- const clip = getIframeVisibleClip(parentScroll);
20268
+ const clip = getIframeChromeClip(parentScroll);
18733
20269
  const clipTop = clip?.top ?? 0;
18734
20270
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
18735
20271
  const visibleTop = rect.top - canvasTopInIframe;
@@ -18874,7 +20410,7 @@ function FloatingToolbar({
18874
20410
  return () => ro.disconnect();
18875
20411
  }, [showEditLink, activeCommands]);
18876
20412
  const { top, left, transform } = calcToolbarPos(rect, parentScroll, measuredW);
18877
- return /* @__PURE__ */ jsx35(
20413
+ return /* @__PURE__ */ jsx36(
18878
20414
  "div",
18879
20415
  {
18880
20416
  ref: setRefs,
@@ -18886,12 +20422,12 @@ function FloatingToolbar({
18886
20422
  zIndex: 2147483647,
18887
20423
  pointerEvents: "auto"
18888
20424
  },
18889
- children: /* @__PURE__ */ jsxs22(CustomToolbar, { children: [
18890
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs22(React12.Fragment, { children: [
18891
- gi > 0 && /* @__PURE__ */ jsx35(CustomToolbarDivider, {}),
20425
+ children: /* @__PURE__ */ jsxs23(CustomToolbar, { children: [
20426
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs23(React12.Fragment, { children: [
20427
+ gi > 0 && /* @__PURE__ */ jsx36(CustomToolbarDivider, {}),
18892
20428
  btns.map((btn) => {
18893
20429
  const isActive = activeCommands.has(btn.cmd);
18894
- return /* @__PURE__ */ jsx35(
20430
+ return /* @__PURE__ */ jsx36(
18895
20431
  CustomToolbarButton,
18896
20432
  {
18897
20433
  title: btn.title,
@@ -18900,7 +20436,7 @@ function FloatingToolbar({
18900
20436
  e.preventDefault();
18901
20437
  onCommand(btn.cmd);
18902
20438
  },
18903
- children: /* @__PURE__ */ jsx35(
20439
+ children: /* @__PURE__ */ jsx36(
18904
20440
  "svg",
18905
20441
  {
18906
20442
  width: "16",
@@ -18921,7 +20457,7 @@ function FloatingToolbar({
18921
20457
  );
18922
20458
  })
18923
20459
  ] }, gi)),
18924
- showEditLink ? /* @__PURE__ */ jsx35(
20460
+ showEditLink ? /* @__PURE__ */ jsx36(
18925
20461
  CustomToolbarButton,
18926
20462
  {
18927
20463
  type: "button",
@@ -18935,7 +20471,7 @@ function FloatingToolbar({
18935
20471
  e.preventDefault();
18936
20472
  e.stopPropagation();
18937
20473
  },
18938
- children: /* @__PURE__ */ jsx35(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
20474
+ children: /* @__PURE__ */ jsx36(Link2, { className: "size-4 shrink-0", "aria-hidden": true })
18939
20475
  }
18940
20476
  ) : null
18941
20477
  ] })
@@ -18952,7 +20488,7 @@ function StateToggle({
18952
20488
  states,
18953
20489
  onStateChange
18954
20490
  }) {
18955
- return /* @__PURE__ */ jsx35(
20491
+ return /* @__PURE__ */ jsx36(
18956
20492
  ToggleGroup,
18957
20493
  {
18958
20494
  "data-ohw-state-toggle": "",
@@ -18966,7 +20502,7 @@ function StateToggle({
18966
20502
  left: rect.right - 8,
18967
20503
  transform: "translateX(-100%)"
18968
20504
  },
18969
- children: states.map((state) => /* @__PURE__ */ jsx35(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
20505
+ children: states.map((state) => /* @__PURE__ */ jsx36(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
18970
20506
  }
18971
20507
  );
18972
20508
  }
@@ -18982,9 +20518,9 @@ var OHW_LOADER_STYLE = {
18982
20518
  justifyContent: "center"
18983
20519
  };
18984
20520
  function OhwLoaderSpinner() {
18985
- return /* @__PURE__ */ jsxs22("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", "aria-hidden": true, children: [
18986
- /* @__PURE__ */ jsx35("circle", { cx: "14", cy: "14", r: "11", stroke: "#E7E5E4", strokeWidth: "3" }),
18987
- /* @__PURE__ */ jsx35(
20521
+ return /* @__PURE__ */ jsxs23("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", "aria-hidden": true, children: [
20522
+ /* @__PURE__ */ jsx36("circle", { cx: "14", cy: "14", r: "11", stroke: "#E7E5E4", strokeWidth: "3" }),
20523
+ /* @__PURE__ */ jsx36(
18988
20524
  "circle",
18989
20525
  {
18990
20526
  cx: "14",
@@ -18994,7 +20530,7 @@ function OhwLoaderSpinner() {
18994
20530
  strokeWidth: "3",
18995
20531
  strokeDasharray: "17 52",
18996
20532
  strokeLinecap: "round",
18997
- children: /* @__PURE__ */ jsx35(
20533
+ children: /* @__PURE__ */ jsx36(
18998
20534
  "animateTransform",
18999
20535
  {
19000
20536
  attributeName: "transform",
@@ -19010,7 +20546,7 @@ function OhwLoaderSpinner() {
19010
20546
  ] });
19011
20547
  }
19012
20548
  function OhwBrandMark() {
19013
- return /* @__PURE__ */ jsxs22(
20549
+ return /* @__PURE__ */ jsxs23(
19014
20550
  "svg",
19015
20551
  {
19016
20552
  width: "16",
@@ -19021,7 +20557,7 @@ function OhwBrandMark() {
19021
20557
  style: { display: "block", flexShrink: 0 },
19022
20558
  xmlns: "http://www.w3.org/2000/svg",
19023
20559
  children: [
19024
- /* @__PURE__ */ jsx35(
20560
+ /* @__PURE__ */ jsx36(
19025
20561
  "mask",
19026
20562
  {
19027
20563
  id: "ohw-badge-mark",
@@ -19031,15 +20567,15 @@ function OhwBrandMark() {
19031
20567
  y: "0",
19032
20568
  width: "48",
19033
20569
  height: "48",
19034
- children: /* @__PURE__ */ jsx35("path", { d: "M23.8741 48C37.0594 48 47.7481 37.2548 47.7481 24C47.7481 10.7452 37.0594 0 23.8741 0C10.6888 0 0 10.7452 0 24C0 37.2548 10.6888 48 23.8741 48Z", fill: "white" })
20570
+ children: /* @__PURE__ */ jsx36("path", { d: "M23.8741 48C37.0594 48 47.7481 37.2548 47.7481 24C47.7481 10.7452 37.0594 0 23.8741 0C10.6888 0 0 10.7452 0 24C0 37.2548 10.6888 48 23.8741 48Z", fill: "white" })
19035
20571
  }
19036
20572
  ),
19037
- /* @__PURE__ */ jsxs22("g", { mask: "url(#ohw-badge-mark)", children: [
19038
- /* @__PURE__ */ jsx35("path", { d: "M23.8731 48.0497C37.0584 48.0497 47.7472 37.3046 47.7472 24.0497C47.7472 10.7949 37.0584 0.0497208 23.8731 0.0497208C10.6878 0.0497208 -0.000976562 10.7949 -0.000976562 24.0497C-0.000976562 37.3046 10.6878 48.0497 23.8731 48.0497Z", fill: "#0078E5" }),
19039
- /* @__PURE__ */ jsx35("path", { d: "M17.1307 14.7172C13.1687 14.7172 9.38102 18.1154 8.65114 22.34C8.38885 23.8488 8.5598 25.2581 9.06929 26.4451C6.20005 29.1677 1.77216 27.8721 -1.40212 26.1536C-2.73618 25.4317 -3.92695 27.4745 -2.59037 28.1981C1.33389 30.3226 6.86037 31.6621 10.4402 28.4188C11.4718 29.3859 12.867 29.9621 14.4894 29.9621C18.4161 29.9621 22.2038 26.5318 22.9337 22.34C23.6636 18.1162 21.0566 14.7172 17.1298 14.7172H17.1307ZM19.9798 22.34C19.5281 25.0399 17.2689 27.231 14.9754 27.231C12.6466 27.231 11.1877 25.0399 11.6394 22.34C12.1262 19.6401 14.3151 17.4482 16.6438 17.4482C18.9374 17.4482 20.4667 19.6401 19.9798 22.34Z", fill: "white" }),
19040
- /* @__PURE__ */ jsx35("path", { d: "M40.0017 27.0262C39.1797 27.081 38.2721 26.995 37.4668 26.7415C37.3344 26.6993 37.28 26.5401 37.3529 26.4205C37.5959 26.0212 37.8255 25.6152 38.009 25.1889C38.1071 24.9918 38.2018 24.793 38.2897 24.5908C38.3274 24.5041 38.4163 24.451 38.5101 24.4619C38.63 24.4754 38.7054 24.4821 38.8881 24.4821L39.1529 24.4796L39.8283 24.4543C45.9229 24.0492 50.4765 20.4319 54.8466 16.9014C56.0172 15.9554 57.6932 17.6208 56.5116 18.5752C51.7687 22.4065 47.1966 26.5081 40.9319 26.9689", fill: "white" }),
19041
- /* @__PURE__ */ jsx35("path", { d: "M37.9687 24.27C38.4472 23.1319 38.7656 21.9045 38.9609 20.6991C39.5927 16.76 38.5058 14.2193 36.1553 14.2193C34.1835 14.2193 33.1469 17.2427 32.9199 18.8694C32.743 19.9872 32.5914 22.1219 33.6028 23.9524C33.7553 24.259 34.15 24.7712 34.471 25.1259C34.5447 25.2067 34.6746 25.2 34.7349 25.1082C34.9528 24.7788 35.1615 24.4039 35.3584 24.0257C35.5444 23.6677 35.5888 23.6138 35.8587 23.0207C35.8838 22.966 35.8813 22.9002 35.8478 22.8505C35.5888 22.4597 35.2168 21.9787 35.1204 21.4614C34.9184 20.4455 34.9436 19.2257 35.2218 18.1078C35.4413 17.3118 35.7195 16.7844 35.9039 16.5106C35.9466 16.4466 36.0279 16.4129 36.0975 16.4449C36.369 16.5671 36.5827 16.8838 36.7385 17.396C37.0167 18.2089 37.0167 19.3782 36.814 20.6999C36.6991 21.5271 36.4771 22.3729 36.1746 23.1673C36.1293 23.308 36.0757 23.4461 36.0187 23.5826C36.0187 23.5868 36.0187 23.591 36.0187 23.5961C35.9911 23.6946 35.9207 23.8067 35.8846 23.901C35.5536 24.5497 35.2344 25.1697 34.8439 25.7838C34.8388 25.7863 34.8346 25.7914 34.8296 25.7931C34.6528 26.0525 34.4718 26.2901 34.2866 26.4965C34.2774 26.5099 34.2682 26.5234 34.2589 26.5369C34.2405 26.5638 34.2179 26.5815 34.1944 26.595C33.5064 27.3212 32.7665 27.6893 32.0123 27.6893C31.8606 27.6893 31.6838 27.664 31.507 27.4349C31.3042 27.1299 30.85 26.0879 31.2539 22.9112C31.4785 21.3949 31.8011 20.0268 31.931 19.5408C31.9579 19.4413 31.8908 19.3419 31.7886 19.3293L30.0062 19.1162C29.9241 19.1061 29.8478 19.1566 29.8252 19.2366C29.2704 21.1615 27.0305 27.6885 24.9599 27.6885C24.328 27.6885 24.1512 26.6211 24.1001 26.2909C23.775 23.6264 25.528 18.5492 29.5302 16.2267C29.6048 16.1838 29.635 16.0928 29.5998 16.0136L28.9042 14.467C28.8632 14.3752 28.7501 14.3389 28.6638 14.3886C25.8079 16.0414 24.1847 18.5231 23.2915 20.3436C22.2046 22.5793 21.6993 25.0189 21.9515 26.8738C22.1795 28.7794 23.1398 29.872 24.6054 29.872C26.0543 29.872 27.4369 28.9066 28.7098 27.0187C28.7953 26.8915 28.9889 26.9311 29.0149 27.0819C29.2646 28.5283 29.9811 29.872 31.6579 29.872C33.5282 29.872 35.3232 28.7288 36.7134 26.6447C36.7712 26.5874 36.7972 26.5411 36.8181 26.4906C36.8232 26.4931 36.8282 26.4948 36.8332 26.4973C37.01 26.2025 37.181 25.9051 37.3444 25.6027C37.4508 25.4005 37.5572 25.1992 37.6586 24.9945C37.7181 24.874 37.7743 24.7527 37.8262 24.6289C37.838 24.6002 37.8547 24.5665 37.8706 24.5337", fill: "white" }),
19042
- /* @__PURE__ */ jsx35("path", { d: "M30.5839 31.6397C25.7546 34.8577 19.4773 34.7853 14.6907 31.5243C13.5368 30.7384 12.5044 32.6498 13.6474 33.4281C19.034 37.0985 26.3077 37.096 31.7218 33.488C32.8791 32.7172 31.7478 30.8639 30.5839 31.6397Z", fill: "white" })
20573
+ /* @__PURE__ */ jsxs23("g", { mask: "url(#ohw-badge-mark)", children: [
20574
+ /* @__PURE__ */ jsx36("path", { d: "M23.8731 48.0497C37.0584 48.0497 47.7472 37.3046 47.7472 24.0497C47.7472 10.7949 37.0584 0.0497208 23.8731 0.0497208C10.6878 0.0497208 -0.000976562 10.7949 -0.000976562 24.0497C-0.000976562 37.3046 10.6878 48.0497 23.8731 48.0497Z", fill: "#0078E5" }),
20575
+ /* @__PURE__ */ jsx36("path", { d: "M17.1307 14.7172C13.1687 14.7172 9.38102 18.1154 8.65114 22.34C8.38885 23.8488 8.5598 25.2581 9.06929 26.4451C6.20005 29.1677 1.77216 27.8721 -1.40212 26.1536C-2.73618 25.4317 -3.92695 27.4745 -2.59037 28.1981C1.33389 30.3226 6.86037 31.6621 10.4402 28.4188C11.4718 29.3859 12.867 29.9621 14.4894 29.9621C18.4161 29.9621 22.2038 26.5318 22.9337 22.34C23.6636 18.1162 21.0566 14.7172 17.1298 14.7172H17.1307ZM19.9798 22.34C19.5281 25.0399 17.2689 27.231 14.9754 27.231C12.6466 27.231 11.1877 25.0399 11.6394 22.34C12.1262 19.6401 14.3151 17.4482 16.6438 17.4482C18.9374 17.4482 20.4667 19.6401 19.9798 22.34Z", fill: "white" }),
20576
+ /* @__PURE__ */ jsx36("path", { d: "M40.0017 27.0262C39.1797 27.081 38.2721 26.995 37.4668 26.7415C37.3344 26.6993 37.28 26.5401 37.3529 26.4205C37.5959 26.0212 37.8255 25.6152 38.009 25.1889C38.1071 24.9918 38.2018 24.793 38.2897 24.5908C38.3274 24.5041 38.4163 24.451 38.5101 24.4619C38.63 24.4754 38.7054 24.4821 38.8881 24.4821L39.1529 24.4796L39.8283 24.4543C45.9229 24.0492 50.4765 20.4319 54.8466 16.9014C56.0172 15.9554 57.6932 17.6208 56.5116 18.5752C51.7687 22.4065 47.1966 26.5081 40.9319 26.9689", fill: "white" }),
20577
+ /* @__PURE__ */ jsx36("path", { d: "M37.9687 24.27C38.4472 23.1319 38.7656 21.9045 38.9609 20.6991C39.5927 16.76 38.5058 14.2193 36.1553 14.2193C34.1835 14.2193 33.1469 17.2427 32.9199 18.8694C32.743 19.9872 32.5914 22.1219 33.6028 23.9524C33.7553 24.259 34.15 24.7712 34.471 25.1259C34.5447 25.2067 34.6746 25.2 34.7349 25.1082C34.9528 24.7788 35.1615 24.4039 35.3584 24.0257C35.5444 23.6677 35.5888 23.6138 35.8587 23.0207C35.8838 22.966 35.8813 22.9002 35.8478 22.8505C35.5888 22.4597 35.2168 21.9787 35.1204 21.4614C34.9184 20.4455 34.9436 19.2257 35.2218 18.1078C35.4413 17.3118 35.7195 16.7844 35.9039 16.5106C35.9466 16.4466 36.0279 16.4129 36.0975 16.4449C36.369 16.5671 36.5827 16.8838 36.7385 17.396C37.0167 18.2089 37.0167 19.3782 36.814 20.6999C36.6991 21.5271 36.4771 22.3729 36.1746 23.1673C36.1293 23.308 36.0757 23.4461 36.0187 23.5826C36.0187 23.5868 36.0187 23.591 36.0187 23.5961C35.9911 23.6946 35.9207 23.8067 35.8846 23.901C35.5536 24.5497 35.2344 25.1697 34.8439 25.7838C34.8388 25.7863 34.8346 25.7914 34.8296 25.7931C34.6528 26.0525 34.4718 26.2901 34.2866 26.4965C34.2774 26.5099 34.2682 26.5234 34.2589 26.5369C34.2405 26.5638 34.2179 26.5815 34.1944 26.595C33.5064 27.3212 32.7665 27.6893 32.0123 27.6893C31.8606 27.6893 31.6838 27.664 31.507 27.4349C31.3042 27.1299 30.85 26.0879 31.2539 22.9112C31.4785 21.3949 31.8011 20.0268 31.931 19.5408C31.9579 19.4413 31.8908 19.3419 31.7886 19.3293L30.0062 19.1162C29.9241 19.1061 29.8478 19.1566 29.8252 19.2366C29.2704 21.1615 27.0305 27.6885 24.9599 27.6885C24.328 27.6885 24.1512 26.6211 24.1001 26.2909C23.775 23.6264 25.528 18.5492 29.5302 16.2267C29.6048 16.1838 29.635 16.0928 29.5998 16.0136L28.9042 14.467C28.8632 14.3752 28.7501 14.3389 28.6638 14.3886C25.8079 16.0414 24.1847 18.5231 23.2915 20.3436C22.2046 22.5793 21.6993 25.0189 21.9515 26.8738C22.1795 28.7794 23.1398 29.872 24.6054 29.872C26.0543 29.872 27.4369 28.9066 28.7098 27.0187C28.7953 26.8915 28.9889 26.9311 29.0149 27.0819C29.2646 28.5283 29.9811 29.872 31.6579 29.872C33.5282 29.872 35.3232 28.7288 36.7134 26.6447C36.7712 26.5874 36.7972 26.5411 36.8181 26.4906C36.8232 26.4931 36.8282 26.4948 36.8332 26.4973C37.01 26.2025 37.181 25.9051 37.3444 25.6027C37.4508 25.4005 37.5572 25.1992 37.6586 24.9945C37.7181 24.874 37.7743 24.7527 37.8262 24.6289C37.838 24.6002 37.8547 24.5665 37.8706 24.5337", fill: "white" }),
20578
+ /* @__PURE__ */ jsx36("path", { d: "M30.5839 31.6397C25.7546 34.8577 19.4773 34.7853 14.6907 31.5243C13.5368 30.7384 12.5044 32.6498 13.6474 33.4281C19.034 37.0985 26.3077 37.096 31.7218 33.488C32.8791 32.7172 31.7478 30.8639 30.5839 31.6397Z", fill: "white" })
19043
20579
  ] })
19044
20580
  ]
19045
20581
  }
@@ -19076,7 +20612,7 @@ var OHW_BADGE_LABEL_STYLE = {
19076
20612
  whiteSpace: "nowrap"
19077
20613
  };
19078
20614
  function MadeWithOhhWells() {
19079
- return /* @__PURE__ */ jsxs22(
20615
+ return /* @__PURE__ */ jsxs23(
19080
20616
  "a",
19081
20617
  {
19082
20618
  href: "https://ohhwells.com",
@@ -19086,8 +20622,8 @@ function MadeWithOhhWells() {
19086
20622
  "data-ohw-badge": "",
19087
20623
  style: OHW_BADGE_STYLE,
19088
20624
  children: [
19089
- /* @__PURE__ */ jsx35(OhwBrandMark, {}),
19090
- /* @__PURE__ */ jsx35("span", { style: OHW_BADGE_LABEL_STYLE, children: "Made with OhhWells" })
20625
+ /* @__PURE__ */ jsx36(OhwBrandMark, {}),
20626
+ /* @__PURE__ */ jsx36("span", { style: OHW_BADGE_LABEL_STYLE, children: "Made with OhhWells" })
19091
20627
  ]
19092
20628
  }
19093
20629
  );
@@ -19577,7 +21113,15 @@ function OhhwellsBridge() {
19577
21113
  const [floatingPanel, setFloatingPanel] = useState16(null);
19578
21114
  const floatingPanelOpenRef = useRef12(false);
19579
21115
  floatingPanelOpenRef.current = floatingPanel !== null;
21116
+ const floatingPanelRef = useRef12(null);
21117
+ floatingPanelRef.current = floatingPanel;
19580
21118
  const [floatingPanelPos, setFloatingPanelPos] = useState16(null);
21119
+ const buttonCollectionDragRef = useRef12(null);
21120
+ const buttonDropInsertIndexRef = useRef12(null);
21121
+ const [buttonDropSlots, setButtonDropSlots] = useState16([]);
21122
+ const [activeButtonDropIndex, setActiveButtonDropIndex] = useState16(null);
21123
+ const buttonPointerDragRef = useRef12(null);
21124
+ const [buttonStyleTick, setButtonStyleTick] = useState16(0);
19581
21125
  const [logoSizeDraft, setLogoSizeDraft] = useState16(null);
19582
21126
  const [editorViewport, setEditorViewport] = useState16("desktop");
19583
21127
  const [parentScrollSnap, setParentScrollSnap] = useState16(null);
@@ -19878,6 +21422,7 @@ function OhhwellsBridge() {
19878
21422
  hoveredItemElRef.current = null;
19879
21423
  setHoveredItemRect(null);
19880
21424
  setFloatingPanel(null);
21425
+ setFloatingPanelPos(null);
19881
21426
  setLogoSizeDraft(null);
19882
21427
  if (!activeElRef.current) {
19883
21428
  if (nestedGroup?.isConnected) {
@@ -20478,6 +22023,47 @@ function OhhwellsBridge() {
20478
22023
  },
20479
22024
  [beginFooterDrag]
20480
22025
  );
22026
+ const persistButtonCollectionDrag = useCallback10(
22027
+ (button, clientX) => {
22028
+ const insertIndex = buttonDropInsertIndexRef.current;
22029
+ if (insertIndex != null) insertButtonAtCollectionIndex(button, insertIndex);
22030
+ else if (typeof clientX === "number" && clientX !== 0) reorderButtonInCollection(button, clientX);
22031
+ buttonDropInsertIndexRef.current = null;
22032
+ const collection = buttonCollectionOf(button);
22033
+ if (collection) {
22034
+ const persisted = persistCollectionOrder(collection, editContentRef.current);
22035
+ if (persisted) {
22036
+ editContentRef.current = persisted.content;
22037
+ postToParent2({ type: "ow:change", nodes: persisted.nodes });
22038
+ }
22039
+ }
22040
+ buttonCollectionDragRef.current = null;
22041
+ buttonPointerDragRef.current = null;
22042
+ setButtonDropSlots([]);
22043
+ setActiveButtonDropIndex(null);
22044
+ setIsItemDragging(false);
22045
+ unlockFooterDragInteraction();
22046
+ if (button.isConnected) setToolbarRect(button.getBoundingClientRect());
22047
+ },
22048
+ [postToParent2]
22049
+ );
22050
+ const refreshButtonDragVisuals = useCallback10((button, clientX, clientY) => {
22051
+ const collection = buttonCollectionOf(button);
22052
+ if (!collection) {
22053
+ setButtonDropSlots([]);
22054
+ setActiveButtonDropIndex(null);
22055
+ buttonDropInsertIndexRef.current = null;
22056
+ return;
22057
+ }
22058
+ const slots = buildButtonCollectionDropSlots(collection);
22059
+ const active = hitTestButtonCollectionDropSlot(collection, clientX, clientY);
22060
+ setButtonDropSlots(slots);
22061
+ const idx = active ? slots.findIndex((slot) => slot.insertIndex === active.insertIndex) : -1;
22062
+ setActiveButtonDropIndex(idx >= 0 ? idx : null);
22063
+ buttonDropInsertIndexRef.current = active?.insertIndex ?? null;
22064
+ setDraggedItemRect(button.getBoundingClientRect());
22065
+ if (selectedElRef.current === button) setToolbarRect(button.getBoundingClientRect());
22066
+ }, []);
20481
22067
  const handleItemDragStart = useCallback10(
20482
22068
  (e) => {
20483
22069
  const selected = selectedElRef.current;
@@ -20487,6 +22073,17 @@ function OhhwellsBridge() {
20487
22073
  }
20488
22074
  const clientX = e?.clientX ?? selected.getBoundingClientRect().left + 8;
20489
22075
  const clientY = e?.clientY ?? selected.getBoundingClientRect().top + 8;
22076
+ if (isCtaButton(selected) && collectionDragEnabled(selected)) {
22077
+ buttonCollectionDragRef.current = selected;
22078
+ setMediaHover(null);
22079
+ if (e?.dataTransfer) {
22080
+ e.dataTransfer.effectAllowed = "move";
22081
+ e.dataTransfer.setData("text/plain", buttonId(selected) ?? "button");
22082
+ }
22083
+ setIsItemDragging(true);
22084
+ refreshButtonDragVisuals(selected, clientX, clientY);
22085
+ return;
22086
+ }
20490
22087
  if (startFooterLinkDrag(selected, clientX, clientY, true)) return;
20491
22088
  if (selected.hasAttribute("data-ohw-footer-col") || isInferredFooterGroup2(selected) && selected.closest("footer")) {
20492
22089
  if (startFooterColumnDrag(selected, clientX, clientY, true)) return;
@@ -20496,10 +22093,15 @@ function OhhwellsBridge() {
20496
22093
  setSiblingHintRect(null);
20497
22094
  setIsItemDragging(true);
20498
22095
  },
20499
- [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag]
22096
+ [startFooterColumnDrag, startFooterLinkDrag, startNavLinkDrag, refreshButtonDragVisuals]
20500
22097
  );
20501
22098
  const handleItemDragEnd = useCallback10(
20502
22099
  (e) => {
22100
+ const collectionButton = buttonCollectionDragRef.current;
22101
+ if (collectionButton) {
22102
+ persistButtonCollectionDrag(collectionButton, e?.clientX);
22103
+ return;
22104
+ }
20503
22105
  if (footerDragRef.current) {
20504
22106
  const x = e?.clientX;
20505
22107
  const y = e?.clientY;
@@ -20522,12 +22124,22 @@ function OhhwellsBridge() {
20522
22124
  }
20523
22125
  setIsItemDragging(false);
20524
22126
  },
20525
- [commitFooterDrag, commitNavDrag, navDragRef]
22127
+ [commitFooterDrag, commitNavDrag, navDragRef, persistButtonCollectionDrag]
20526
22128
  );
20527
22129
  const handleItemChromePointerDown = useCallback10((e) => {
20528
22130
  if (e.button !== 0) return;
20529
22131
  const selected = selectedElRef.current;
20530
22132
  if (!selected) return;
22133
+ if (isCtaButton(selected) && collectionDragEnabled(selected)) {
22134
+ buttonPointerDragRef.current = {
22135
+ el: selected,
22136
+ startX: e.clientX,
22137
+ startY: e.clientY,
22138
+ pointerId: e.pointerId,
22139
+ started: false
22140
+ };
22141
+ return;
22142
+ }
20531
22143
  const hrefKey = selected.getAttribute("data-ohw-href-key");
20532
22144
  if (hrefKey && isFooterHrefKey(hrefKey)) {
20533
22145
  footerPointerDragRef.current = {
@@ -20606,10 +22218,29 @@ function OhhwellsBridge() {
20606
22218
  setIsFooterFrameSelection(false);
20607
22219
  setToolbarVariant("link-action");
20608
22220
  setToolbarRect(anchor.getBoundingClientRect());
22221
+ if (glowElRef.current) glowElRef.current.style.display = "";
20609
22222
  setToolbarShowEditLink(false);
20610
22223
  setActiveCommands(/* @__PURE__ */ new Set());
20611
- setFloatingPanel(null);
20612
- setLogoSizeDraft(null);
22224
+ const panel = floatingPanelRef.current;
22225
+ if (panel?.kind === "button-style" && isCtaButton(anchor)) {
22226
+ const id = buttonId(anchor);
22227
+ if (id && panel.buttonId !== id) {
22228
+ captureButtonOrigin(anchor);
22229
+ setFloatingPanelPos(null);
22230
+ setParentScrollSnap(parentScrollRef.current);
22231
+ setFloatingPanel({
22232
+ ...panel,
22233
+ key: `button-style:${id}`,
22234
+ buttonId: id,
22235
+ context: buttonStyleContext(anchor)
22236
+ });
22237
+ setButtonStyleTick((n) => n + 1);
22238
+ }
22239
+ } else {
22240
+ setFloatingPanel(null);
22241
+ setFloatingPanelPos(null);
22242
+ setLogoSizeDraft(null);
22243
+ }
20613
22244
  }, [deactivate, markSelected]);
20614
22245
  const selectFrame = useCallback10((el) => {
20615
22246
  if (!isNavigationContainer(el)) return;
@@ -20649,6 +22280,7 @@ function OhhwellsBridge() {
20649
22280
  setToolbarShowEditLink(false);
20650
22281
  setActiveCommands(/* @__PURE__ */ new Set());
20651
22282
  setFloatingPanel(null);
22283
+ setFloatingPanelPos(null);
20652
22284
  setLogoSizeDraft(null);
20653
22285
  }, [deactivate, markSelected, postToParent2]);
20654
22286
  const selectLogo = useCallback10(
@@ -20694,6 +22326,7 @@ function OhhwellsBridge() {
20694
22326
  });
20695
22327
  }, []);
20696
22328
  const openSocialsDisplayPanel = useCallback10((row) => {
22329
+ setFloatingPanelPos(null);
20697
22330
  setParentScrollSnap(parentScrollRef.current);
20698
22331
  setFloatingPanel({
20699
22332
  key: "socials-display",
@@ -20703,6 +22336,87 @@ function OhhwellsBridge() {
20703
22336
  row
20704
22337
  });
20705
22338
  }, []);
22339
+ const rememberButtonTokens = useCallback10(
22340
+ (content, tokens, nodes) => {
22341
+ if (nodes.length === 0) return content;
22342
+ const next = { ...content };
22343
+ for (const node of nodes) next[node.key] = node.text;
22344
+ editContentRef.current = next;
22345
+ postToParent2({ type: "ow:change", nodes });
22346
+ return next;
22347
+ },
22348
+ [postToParent2]
22349
+ );
22350
+ const tokensForButton = useCallback10(
22351
+ (el, persistFallback = false) => {
22352
+ const stored = readStoredStyleTokens(editContentRef.current);
22353
+ if (stored) return stored;
22354
+ const ensured = ensureButtonStyleTokens(document, editContentRef.current);
22355
+ if (ensured.tokens) {
22356
+ rememberButtonTokens(editContentRef.current, ensured.tokens, ensured.nodes);
22357
+ return ensured.tokens;
22358
+ }
22359
+ const fallback = tokensFromButton(el);
22360
+ if (persistFallback) rememberButtonTokens(editContentRef.current, fallback, styleTokenNodes(fallback));
22361
+ return fallback;
22362
+ },
22363
+ [rememberButtonTokens]
22364
+ );
22365
+ const openButtonStylePanel = useCallback10(
22366
+ (anchor) => {
22367
+ const id = buttonId(anchor);
22368
+ if (!id) return;
22369
+ captureButtonOrigin(anchor);
22370
+ tokensForButton(anchor);
22371
+ setFloatingPanelPos(null);
22372
+ setParentScrollSnap(parentScrollRef.current);
22373
+ setFloatingPanel({
22374
+ key: `button-style:${id}`,
22375
+ title: "Style",
22376
+ context: buttonStyleContext(anchor),
22377
+ kind: "button-style",
22378
+ buttonId: id
22379
+ });
22380
+ },
22381
+ [tokensForButton]
22382
+ );
22383
+ const applySelectedButtonStyle = useCallback10(
22384
+ (patch) => {
22385
+ const panel = floatingPanelRef.current;
22386
+ const id = panel?.kind === "button-style" ? panel.buttonId : null;
22387
+ const el = id ? document.querySelector(`[data-ohw-href-key="${CSS.escape(id)}"]`) : null;
22388
+ if (!el || !id || !isCtaButton(el)) return;
22389
+ const tokens = tokensFromButton(el);
22390
+ const next = { ...assignmentFor(el, editContentRef.current, tokens), ...patch };
22391
+ const assignments = withAssignment(editContentRef.current, id, next);
22392
+ const text = JSON.stringify(assignments);
22393
+ editContentRef.current = { ...editContentRef.current, [BUTTON_ASSIGNMENTS_KEY]: text };
22394
+ applyButtonStyle(el, next, tokens);
22395
+ postToParent2({ type: "ow:change", nodes: [{ key: BUTTON_ASSIGNMENTS_KEY, text }] });
22396
+ setButtonStyleTick((n) => n + 1);
22397
+ if (selectedElRef.current === el) setToolbarRect(el.getBoundingClientRect());
22398
+ },
22399
+ [postToParent2]
22400
+ );
22401
+ useEffect15(() => {
22402
+ const onDragOver = (e) => {
22403
+ const dragged = buttonCollectionDragRef.current;
22404
+ if (!dragged) return;
22405
+ e.preventDefault();
22406
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
22407
+ refreshButtonDragVisuals(dragged, e.clientX, e.clientY);
22408
+ };
22409
+ const onDrop = (e) => {
22410
+ if (!buttonCollectionDragRef.current) return;
22411
+ e.preventDefault();
22412
+ };
22413
+ document.addEventListener("dragover", onDragOver);
22414
+ document.addEventListener("drop", onDrop);
22415
+ return () => {
22416
+ document.removeEventListener("dragover", onDragOver);
22417
+ document.removeEventListener("drop", onDrop);
22418
+ };
22419
+ }, [refreshButtonDragVisuals]);
20706
22420
  const isEditModeRef = useRef12(false);
20707
22421
  const requestMissingSocialIconsRef = useRef12(() => {
20708
22422
  });
@@ -20745,11 +22459,13 @@ function OhhwellsBridge() {
20745
22459
  );
20746
22460
  const closeFloatingPanelOnly = useCallback10(() => {
20747
22461
  setFloatingPanel(null);
22462
+ setFloatingPanelPos(null);
20748
22463
  setLogoSizeDraft(null);
20749
22464
  }, []);
20750
22465
  closeFloatingPanelOnlyRef.current = closeFloatingPanelOnly;
20751
22466
  const closeFloatingPanelAndDeselect = useCallback10(() => {
20752
22467
  setFloatingPanel(null);
22468
+ setFloatingPanelPos(null);
20753
22469
  setLogoSizeDraft(null);
20754
22470
  deselectRef.current();
20755
22471
  }, []);
@@ -21018,6 +22734,7 @@ function OhhwellsBridge() {
21018
22734
  reconcileSocialsFromContent(content);
21019
22735
  applySocialsDisplayFromContent(content);
21020
22736
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
22737
+ syncButtonsFromContent(document, content);
21021
22738
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
21022
22739
  if (typeof content[STYLE_STORE_KEY] === "string") {
21023
22740
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
@@ -21218,6 +22935,7 @@ function OhhwellsBridge() {
21218
22935
  reconcileSocialsFromContent(content);
21219
22936
  applySocialsDisplayFromContent(content);
21220
22937
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
22938
+ syncButtonsFromContent(document, content);
21221
22939
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
21222
22940
  if (typeof content[STYLE_STORE_KEY] === "string") {
21223
22941
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
@@ -21306,7 +23024,10 @@ function OhhwellsBridge() {
21306
23024
  if (roots.length === 0) {
21307
23025
  reconcileNavbarItemsFromContent(contentForNav());
21308
23026
  reconcileFooterOrderFromContent(contentForNav());
21309
- if (isEditMode) syncNavigationDragCursorAttrs();
23027
+ if (isEditMode) {
23028
+ syncButtonsFromContent(document, contentForNav());
23029
+ syncNavigationDragCursorAttrs();
23030
+ }
21310
23031
  return;
21311
23032
  }
21312
23033
  let rafId = null;
@@ -21318,7 +23039,7 @@ function OhhwellsBridge() {
21318
23039
  }
21319
23040
  };
21320
23041
  const run = () => {
21321
- if (footerDragRef.current || navDragRef.current || applying) return;
23042
+ if (footerDragRef.current || navDragRef.current || buttonCollectionDragRef.current || applying) return;
21322
23043
  applying = true;
21323
23044
  observer?.disconnect();
21324
23045
  try {
@@ -21328,6 +23049,7 @@ function OhhwellsBridge() {
21328
23049
  reconcileSocialsFromContent(content);
21329
23050
  applySocialsDisplayFromContent(content);
21330
23051
  applySocialsLabelsFromContent(content, document, { fillEmptyOnly: isEditModeRef.current });
23052
+ syncButtonsFromContent(document, content);
21331
23053
  if (isEditModeRef.current) requestMissingSocialIconsRef.current();
21332
23054
  document.querySelectorAll("footer [data-ohw-href-key]").forEach((el) => {
21333
23055
  if (isFooterHrefKey(el.getAttribute("data-ohw-href-key")) || getSocialItem(el)) {
@@ -21335,9 +23057,9 @@ function OhhwellsBridge() {
21335
23057
  }
21336
23058
  });
21337
23059
  document.querySelectorAll(
21338
- "nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key]"
23060
+ 'nav [data-ohw-href-key], [data-ohw-nav-container] [data-ohw-href-key], [data-ohw-nav-drawer] [data-ohw-href-key], [data-ohw-role="navbar-button"]'
21339
23061
  ).forEach((el) => {
21340
- if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key"))) {
23062
+ if (isNavbarHrefKey(el.getAttribute("data-ohw-href-key")) || isCtaButton(el)) {
21341
23063
  disableNativeHrefDrag(el);
21342
23064
  }
21343
23065
  });
@@ -21362,6 +23084,44 @@ function OhhwellsBridge() {
21362
23084
  observer?.disconnect();
21363
23085
  };
21364
23086
  }, [isEditMode, pathname, subdomain, fetchState, resyncSelectedNavigationItem]);
23087
+ useEffect15(() => {
23088
+ if (!isEditMode) return;
23089
+ let applying = false;
23090
+ let rafId = null;
23091
+ const run = () => {
23092
+ if (applying || buttonCollectionDragRef.current) return;
23093
+ const content = editContentRef.current;
23094
+ if (!content[BUTTON_COLLECTIONS_KEY] && !content[BUTTON_SNAPSHOTS_KEY]) return;
23095
+ applying = true;
23096
+ try {
23097
+ syncButtonsFromContent(document, content);
23098
+ } finally {
23099
+ applying = false;
23100
+ }
23101
+ };
23102
+ const schedule = () => {
23103
+ if (rafId != null) return;
23104
+ rafId = requestAnimationFrame(() => {
23105
+ rafId = null;
23106
+ run();
23107
+ });
23108
+ };
23109
+ const observer = new MutationObserver((mutations) => {
23110
+ if (mutations.every((m) => {
23111
+ const t = m.target;
23112
+ return t instanceof Element && Boolean(t.closest("[data-ohw-bridge], [data-ohw-bridge-root], [data-ohw-button-collection]"));
23113
+ })) {
23114
+ return;
23115
+ }
23116
+ schedule();
23117
+ });
23118
+ observer.observe(document.body, { childList: true, subtree: true });
23119
+ run();
23120
+ return () => {
23121
+ if (rafId != null) cancelAnimationFrame(rafId);
23122
+ observer.disconnect();
23123
+ };
23124
+ }, [isEditMode, pathname]);
21365
23125
  useEffect15(() => {
21366
23126
  if (!isEditMode) return;
21367
23127
  let lastPosted = 0;
@@ -21888,14 +23648,6 @@ function OhhwellsBridge() {
21888
23648
  }
21889
23649
  const clickedButton = findClosestButtonLike(target);
21890
23650
  const buttonOnMedia = Boolean(clickedButton && isMediaEditable(editable) && editable.contains(clickedButton));
21891
- console.log("[click-debug]", {
21892
- editableType: editable.dataset.ohwEditable,
21893
- editableTag: editable.tagName,
21894
- targetTag: target.tagName,
21895
- clickedButtonTag: clickedButton?.tagName ?? null,
21896
- buttonOnMedia,
21897
- isMediaEditableEditable: isMediaEditable(editable)
21898
- });
21899
23651
  if (isMediaEditable(editable) && !buttonOnMedia) {
21900
23652
  e.preventDefault();
21901
23653
  e.stopPropagation();
@@ -21930,11 +23682,6 @@ function OhhwellsBridge() {
21930
23682
  const hrefLookupTarget = buttonOnMedia ? clickedButton : editable;
21931
23683
  const hrefCtx = getHrefKeyFromElement(hrefLookupTarget);
21932
23684
  const navAnchor = hrefCtx ? getNavigationItemAnchor(hrefCtx.anchor) : null;
21933
- console.log("[click-debug 2]", {
21934
- hrefLookupTargetTag: hrefLookupTarget.tagName,
21935
- hrefCtx: hrefCtx ? { key: hrefCtx.key } : null,
21936
- navAnchorTag: navAnchor?.tagName ?? null
21937
- });
21938
23685
  if (navAnchor) {
21939
23686
  e.preventDefault();
21940
23687
  e.stopPropagation();
@@ -22450,6 +24197,20 @@ function OhhwellsBridge() {
22450
24197
  }
22451
24198
  return;
22452
24199
  }
24200
+ const navbarCtaHit = findNavbarCtaAtPoint(x, y);
24201
+ if (navbarCtaHit) {
24202
+ hoveredNavContainerRef.current = null;
24203
+ setHoveredNavContainerRect(null);
24204
+ if (selected !== navbarCtaHit && !selected?.contains(navbarCtaHit)) {
24205
+ clearHrefKeyHover(navbarCtaHit);
24206
+ hoveredItemElRef.current = navbarCtaHit;
24207
+ setHoveredItemRect(navbarCtaHit.getBoundingClientRect());
24208
+ } else if (hoveredItemElRef.current === navbarCtaHit) {
24209
+ hoveredItemElRef.current = null;
24210
+ setHoveredItemRect(null);
24211
+ }
24212
+ return;
24213
+ }
22453
24214
  const navContainers = Array.from(
22454
24215
  document.querySelectorAll("[data-ohw-nav-container]")
22455
24216
  );
@@ -22552,6 +24313,24 @@ function OhhwellsBridge() {
22552
24313
  return;
22553
24314
  }
22554
24315
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
24316
+ if (!isDragOver) {
24317
+ const buttonHit = findButtonAtPoint(x, y);
24318
+ if (buttonHit) {
24319
+ dismissImageHover();
24320
+ if (findNavbarCtaAtPoint(x, y)) {
24321
+ probeNavigationHoverAt(x, y);
24322
+ return;
24323
+ }
24324
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
24325
+ const selected = selectedElRef.current;
24326
+ if (selected !== buttonHit && !selected?.contains(buttonHit)) {
24327
+ clearHrefKeyHover(buttonHit);
24328
+ hoveredItemElRef.current = buttonHit;
24329
+ setHoveredItemRect(buttonHit.getBoundingClientRect());
24330
+ }
24331
+ return;
24332
+ }
24333
+ }
22555
24334
  if (isPointOverNavigation(x, y)) {
22556
24335
  dismissImageHover();
22557
24336
  probeNavigationHoverAt(x, y);
@@ -22973,6 +24752,11 @@ function OhhwellsBridge() {
22973
24752
  return;
22974
24753
  }
22975
24754
  if (onNavDragOver(e)) return;
24755
+ if (buttonCollectionDragRef.current) {
24756
+ e.preventDefault();
24757
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
24758
+ return;
24759
+ }
22976
24760
  e.preventDefault();
22977
24761
  const el = findImageAtPoint(e.clientX, e.clientY, false);
22978
24762
  if (!el) {
@@ -22997,6 +24781,7 @@ function OhhwellsBridge() {
22997
24781
  };
22998
24782
  const handleDrop = (e) => {
22999
24783
  e.preventDefault();
24784
+ if (buttonCollectionDragRef.current || navDragRef.current || footerDragRef.current) return;
23000
24785
  const el = findImageAtPoint(e.clientX, e.clientY, false);
23001
24786
  if (!el) return;
23002
24787
  const file = e.dataTransfer?.files?.[0];
@@ -23352,6 +25137,7 @@ function OhhwellsBridge() {
23352
25137
  editContentRef.current = { ...editContentRef.current, ...content };
23353
25138
  reconcileNavbarItemsFromContent(editContentRef.current);
23354
25139
  reconcileFooterOrderFromContent(editContentRef.current);
25140
+ syncButtonsFromContent(document, editContentRef.current);
23355
25141
  syncNavigationDragCursorAttrs();
23356
25142
  if (typeof content[STYLE_STORE_KEY] === "string") {
23357
25143
  applyStylesToDom(parseStyleStore(content[STYLE_STORE_KEY]));
@@ -24261,11 +26047,8 @@ function OhhwellsBridge() {
24261
26047
  };
24262
26048
  const handleParentScroll = (e) => {
24263
26049
  if (e.data?.type !== "ow:parent-scroll") return;
24264
- const { iframeOffsetTop, headerH, canvasH } = e.data;
24265
- parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
24266
- if (floatingPanelOpenRef.current) {
24267
- setParentScrollSnap({ iframeOffsetTop, headerH, canvasH });
24268
- }
26050
+ const { iframeOffsetTop, headerH, canvasH, promptBarH = 0 } = e.data;
26051
+ parentScrollRef.current = { iframeOffsetTop, headerH, canvasH, promptBarH };
24269
26052
  if (visibleViewportRef.current) {
24270
26053
  applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
24271
26054
  }
@@ -24655,6 +26438,97 @@ function OhhwellsBridge() {
24655
26438
  unlockFooterDragInteraction();
24656
26439
  };
24657
26440
  }, [isEditMode]);
26441
+ useEffect15(() => {
26442
+ if (!isEditMode) return;
26443
+ const THRESHOLD = 10;
26444
+ const onPointerDown = (e) => {
26445
+ if (e.button !== 0) return;
26446
+ if (buttonCollectionDragRef.current || navDragRef.current || footerDragRef.current) return;
26447
+ if (buttonPointerDragRef.current) return;
26448
+ const target = e.target;
26449
+ if (!target) return;
26450
+ if (target.closest(
26451
+ '[data-ohw-drag-handle-container], [data-slot="drag-handle"], [data-ohw-toolbar], [data-ohw-item-toolbar-anchor], [data-ohw-link-popover-root], [data-ohw-floating-panel]'
26452
+ )) {
26453
+ return;
26454
+ }
26455
+ const fromTarget = target.closest('[data-ohw-role="navbar-button"]');
26456
+ const anchor = (fromTarget && !fromTarget.closest("[data-ohw-bridge]") ? fromTarget : null) ?? findNavbarCtaAtPoint(e.clientX, e.clientY);
26457
+ if (!anchor || !collectionDragEnabled(anchor)) return;
26458
+ disableNativeHrefDrag(anchor);
26459
+ setMediaHover(null);
26460
+ buttonPointerDragRef.current = {
26461
+ el: anchor,
26462
+ startX: e.clientX,
26463
+ startY: e.clientY,
26464
+ pointerId: e.pointerId,
26465
+ started: false
26466
+ };
26467
+ };
26468
+ const onPointerMove = (e) => {
26469
+ const pending = buttonPointerDragRef.current;
26470
+ if (!pending) return;
26471
+ if (pending.started) {
26472
+ e.preventDefault();
26473
+ clearTextSelection();
26474
+ const dragged = buttonCollectionDragRef.current;
26475
+ if (!dragged) return;
26476
+ refreshButtonDragVisuals(dragged, e.clientX, e.clientY);
26477
+ return;
26478
+ }
26479
+ const dx = e.clientX - pending.startX;
26480
+ const dy = e.clientY - pending.startY;
26481
+ if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
26482
+ e.preventDefault();
26483
+ pending.started = true;
26484
+ armFooterPressDrag();
26485
+ clearTextSelection();
26486
+ try {
26487
+ document.body.setPointerCapture(pending.pointerId);
26488
+ } catch {
26489
+ }
26490
+ if (linkPopoverOpenRef.current) setLinkPopoverRef.current(null);
26491
+ if (activeElRef.current) deactivateRef.current();
26492
+ ensureNavbarButtonCollection(pending.el, editContentRef.current);
26493
+ buttonCollectionDragRef.current = pending.el;
26494
+ lockFooterDuringDrag();
26495
+ setIsItemDragging(true);
26496
+ refreshButtonDragVisuals(pending.el, e.clientX, e.clientY);
26497
+ };
26498
+ const endPointerDrag = (e) => {
26499
+ const pending = buttonPointerDragRef.current;
26500
+ buttonPointerDragRef.current = null;
26501
+ try {
26502
+ if (document.body.hasPointerCapture(e.pointerId)) {
26503
+ document.body.releasePointerCapture(e.pointerId);
26504
+ }
26505
+ } catch {
26506
+ }
26507
+ if (!pending) return;
26508
+ if (!pending.started) {
26509
+ unlockFooterDragInteraction();
26510
+ return;
26511
+ }
26512
+ suppressNextClickRef.current = true;
26513
+ suppressClickUntilRef.current = Date.now() + 500;
26514
+ persistButtonCollectionDrag(pending.el, e.clientX);
26515
+ };
26516
+ const blockSelectStart = (e) => {
26517
+ if (buttonCollectionDragRef.current || buttonPointerDragRef.current?.started) e.preventDefault();
26518
+ };
26519
+ document.addEventListener("pointerdown", onPointerDown, true);
26520
+ document.addEventListener("pointermove", onPointerMove, true);
26521
+ document.addEventListener("pointerup", endPointerDrag, true);
26522
+ document.addEventListener("pointercancel", endPointerDrag, true);
26523
+ document.addEventListener("selectstart", blockSelectStart, true);
26524
+ return () => {
26525
+ document.removeEventListener("pointerdown", onPointerDown, true);
26526
+ document.removeEventListener("pointermove", onPointerMove, true);
26527
+ document.removeEventListener("pointerup", endPointerDrag, true);
26528
+ document.removeEventListener("pointercancel", endPointerDrag, true);
26529
+ document.removeEventListener("selectstart", blockSelectStart, true);
26530
+ };
26531
+ }, [isEditMode, persistButtonCollectionDrag, refreshButtonDragVisuals]);
24658
26532
  useEffect15(() => {
24659
26533
  const handler = (e) => {
24660
26534
  if (e.data?.type !== "ow:request-schedule-config") return;
@@ -24695,7 +26569,7 @@ function OhhwellsBridge() {
24695
26569
  postToParent2({
24696
26570
  type: "ow:ready",
24697
26571
  version: "1",
24698
- bridgeVersion: "0.1.120",
26572
+ bridgeVersion: "0.1.121",
24699
26573
  path: pathname,
24700
26574
  nodes: collectEditableNodes(editContentRef.current),
24701
26575
  sections
@@ -24774,12 +26648,12 @@ function OhhwellsBridge() {
24774
26648
  return;
24775
26649
  }
24776
26650
  if (type !== "ow:logo-size-set") return;
24777
- const px = Number(e.data.px);
24778
- if (!Number.isFinite(px)) return;
24779
- const next = editorViewport === "mobile" ? { ...logoSizeDraft, mobilePx: px, mobileFollowing: false } : {
26651
+ const px2 = Number(e.data.px);
26652
+ if (!Number.isFinite(px2)) return;
26653
+ const next = editorViewport === "mobile" ? { ...logoSizeDraft, mobilePx: px2, mobileFollowing: false } : {
24780
26654
  ...logoSizeDraft,
24781
- desktopPx: px,
24782
- mobilePx: logoSizeDraft.mobileFollowing ? px : logoSizeDraft.mobilePx
26655
+ desktopPx: px2,
26656
+ mobilePx: logoSizeDraft.mobileFollowing ? px2 : logoSizeDraft.mobilePx
24783
26657
  };
24784
26658
  setLogoSizeDraft(next);
24785
26659
  persistLogoSizeDraft(session.placement, next);
@@ -24877,6 +26751,36 @@ function OhhwellsBridge() {
24877
26751
  const handleDuplicateSelected = useCallback10(() => {
24878
26752
  const selected = selectedElRef.current;
24879
26753
  if (!selected || !isNavigationItem2(selected)) return;
26754
+ if (isCtaButton(selected)) {
26755
+ const id = buttonId(selected);
26756
+ if (!id) return;
26757
+ const tokens = tokensForButton(selected, true);
26758
+ if (!readAssignments(editContentRef.current)[id]) {
26759
+ const assignment = assignmentFor(selected, editContentRef.current, tokens);
26760
+ const assignments = withAssignment(editContentRef.current, id, assignment);
26761
+ editContentRef.current = {
26762
+ ...editContentRef.current,
26763
+ [BUTTON_ASSIGNMENTS_KEY]: JSON.stringify(assignments)
26764
+ };
26765
+ applyButtonStyle(selected, assignment, tokens);
26766
+ }
26767
+ const result = duplicateButton(selected, editContentRef.current);
26768
+ if (!result) return;
26769
+ editContentRef.current = result.content;
26770
+ const copyHref = result.copy.getAttribute("data-ohw-href-key");
26771
+ const copyHrefVal = copyHref ? result.content[copyHref] : void 0;
26772
+ if (copyHref && copyHrefVal !== void 0) applyLinkByKey(copyHref, copyHrefVal);
26773
+ postToParent2({ type: "ow:change", nodes: result.nodes });
26774
+ enforceLinkHrefs();
26775
+ requestAnimationFrame(() => {
26776
+ selectRef.current(result.copy);
26777
+ requestAnimationFrame(() => {
26778
+ const el = selectedElRef.current;
26779
+ if (el?.isConnected) setToolbarRect(el.getBoundingClientRect());
26780
+ });
26781
+ });
26782
+ return;
26783
+ }
24880
26784
  const hrefKey = selected.getAttribute("data-ohw-href-key");
24881
26785
  if (!hrefKey) return;
24882
26786
  const social = getSocialItem(selected);
@@ -25006,7 +26910,7 @@ function OhhwellsBridge() {
25006
26910
  selectRef.current(result.anchor);
25007
26911
  });
25008
26912
  }
25009
- }, [postToParent2]);
26913
+ }, [postToParent2, tokensForButton]);
25010
26914
  const runPendingDeleteUndo = useCallback10(() => {
25011
26915
  const pending = pendingDeleteUndoRef.current;
25012
26916
  if (!pending) return false;
@@ -25018,6 +26922,38 @@ function OhhwellsBridge() {
25018
26922
  const handleDeleteSelected = useCallback10(() => {
25019
26923
  const selected = selectedElRef.current;
25020
26924
  if (!selected) return false;
26925
+ if (isCtaButton(selected)) {
26926
+ if (!canDeleteButton(selected)) return false;
26927
+ const previousContent = editContentRef.current;
26928
+ const result = deleteButton(selected, previousContent);
26929
+ if (!result) return false;
26930
+ editContentRef.current = result.content;
26931
+ postToParent2({ type: "ow:change", nodes: result.nodes });
26932
+ const actionId = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `delete-${Date.now()}`;
26933
+ const restoreNodes = result.nodes.map((node) => ({
26934
+ key: node.key,
26935
+ text: previousContent[node.key] ?? ""
26936
+ }));
26937
+ pendingDeleteUndoRef.current = {
26938
+ actionId,
26939
+ restore: () => {
26940
+ result.undo();
26941
+ editContentRef.current = { ...editContentRef.current, ...Object.fromEntries(restoreNodes.map((node) => [node.key, node.text])) };
26942
+ postToParent2({ type: "ow:change", nodes: restoreNodes });
26943
+ syncButtonsFromContent(document, editContentRef.current);
26944
+ }
26945
+ };
26946
+ deselectRef.current();
26947
+ postToParent2({
26948
+ type: "ow:toast",
26949
+ title: "Button deleted",
26950
+ toastType: "success",
26951
+ actionLabel: "Undo",
26952
+ actionId,
26953
+ duration: 6e3
26954
+ });
26955
+ return true;
26956
+ }
25021
26957
  return deleteSelectedNavFooterItem({
25022
26958
  selected,
25023
26959
  isTextEditing: Boolean(activeElRef.current),
@@ -25155,14 +27091,14 @@ function OhhwellsBridge() {
25155
27091
  },
25156
27092
  [postToParent2]
25157
27093
  );
25158
- return /* @__PURE__ */ jsxs22(Fragment10, { children: [
25159
- /* @__PURE__ */ jsx35("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ jsx35(OhwLoaderSpinner, {}) }),
25160
- /* @__PURE__ */ jsx35("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
25161
- subdomain && !isEditMode && showBranding && /* @__PURE__ */ jsx35(MadeWithOhhWells, {}),
27094
+ return /* @__PURE__ */ jsxs23(Fragment10, { children: [
27095
+ /* @__PURE__ */ jsx36("div", { id: "ohw-loader", suppressHydrationWarning: true, style: { ...OHW_LOADER_STYLE, display: "none" }, children: /* @__PURE__ */ jsx36(OhwLoaderSpinner, {}) }),
27096
+ /* @__PURE__ */ jsx36("script", { suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: OHW_LOADER_PREHYDRATE_SCRIPT } }),
27097
+ subdomain && !isEditMode && showBranding && /* @__PURE__ */ jsx36(MadeWithOhhWells, {}),
25162
27098
  bridgeRoot ? createPortal3(
25163
- /* @__PURE__ */ jsxs22(Fragment10, { children: [
25164
- /* @__PURE__ */ jsx35("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
25165
- isEditMode && /* @__PURE__ */ jsx35(
27099
+ /* @__PURE__ */ jsxs23(Fragment10, { children: [
27100
+ /* @__PURE__ */ jsx36("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
27101
+ isEditMode && /* @__PURE__ */ jsx36(
25166
27102
  AiSectionOverlay,
25167
27103
  {
25168
27104
  postToParent: postToParent2,
@@ -25170,7 +27106,7 @@ function OhhwellsBridge() {
25170
27106
  onSelectionChange: handleAiSectionSelectionChange
25171
27107
  }
25172
27108
  ),
25173
- isEditMode && /* @__PURE__ */ jsx35(
27109
+ isEditMode && /* @__PURE__ */ jsx36(
25174
27110
  AiMediaStripOverlay,
25175
27111
  {
25176
27112
  postToParent: postToParent2,
@@ -25179,7 +27115,7 @@ function OhhwellsBridge() {
25179
27115
  clearInteractionChromeRef
25180
27116
  }
25181
27117
  ),
25182
- isEditMode && /* @__PURE__ */ jsx35(
27118
+ isEditMode && /* @__PURE__ */ jsx36(
25183
27119
  AiPricingCardOverlay,
25184
27120
  {
25185
27121
  postToParent: postToParent2,
@@ -25187,12 +27123,12 @@ function OhhwellsBridge() {
25187
27123
  applyTreeLocalRef
25188
27124
  }
25189
27125
  ),
25190
- isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ jsx35(
27126
+ isSectionDragging && sectionDropSlots.map((slot, i) => /* @__PURE__ */ jsx36(
25191
27127
  "div",
25192
27128
  {
25193
27129
  className: "pointer-events-none fixed z-2147483646",
25194
27130
  style: { left: slot.left, top: slot.y, width: slot.width, height: 3, transform: "translateY(-50%)" },
25195
- children: /* @__PURE__ */ jsx35(
27131
+ children: /* @__PURE__ */ jsx36(
25196
27132
  DropIndicator,
25197
27133
  {
25198
27134
  direction: "horizontal",
@@ -25203,7 +27139,7 @@ function OhhwellsBridge() {
25203
27139
  },
25204
27140
  `section-drop-${slot.insertIndex}-${i}`
25205
27141
  )),
25206
- Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx35(
27142
+ Object.entries(uploadingRects).map(([key, { rect, fadingOut }]) => /* @__PURE__ */ jsx36(
25207
27143
  MediaOverlay,
25208
27144
  {
25209
27145
  hover: { key, rect, elementType: "image", isDragOver: false, hasTextOverlap: false },
@@ -25214,7 +27150,7 @@ function OhhwellsBridge() {
25214
27150
  },
25215
27151
  `uploading-${key}`
25216
27152
  )),
25217
- mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ jsx35(
27153
+ mediaHover && !(mediaHover.key in uploadingRects) && mediaHover.key !== selectedMedia?.key && /* @__PURE__ */ jsx36(
25218
27154
  MediaOverlay,
25219
27155
  {
25220
27156
  hover: mediaHover,
@@ -25225,7 +27161,7 @@ function OhhwellsBridge() {
25225
27161
  onVideoSettingsChange: handleVideoSettingsChange
25226
27162
  }
25227
27163
  ),
25228
- selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ jsx35(
27164
+ selectedMedia && !(selectedMedia.key in uploadingRects) && !linkPopover && /* @__PURE__ */ jsx36(
25229
27165
  MediaOverlay,
25230
27166
  {
25231
27167
  hover: selectedMedia,
@@ -25237,7 +27173,7 @@ function OhhwellsBridge() {
25237
27173
  onVideoSettingsChange: handleVideoSettingsChange
25238
27174
  }
25239
27175
  ),
25240
- carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx35(
27176
+ carouselHover && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx36(
25241
27177
  CarouselOverlay,
25242
27178
  {
25243
27179
  hover: carouselHover,
@@ -25245,10 +27181,10 @@ function OhhwellsBridge() {
25245
27181
  cueOnly: !!carouselHover.fullBleedSectionId && carouselHover.fullBleedSectionId !== selectedAiSectionId
25246
27182
  }
25247
27183
  ),
25248
- siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx35(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
25249
- siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx35(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
25250
- isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && /* @__PURE__ */ jsx35(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
25251
- isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx35(
27184
+ siblingHintRect && !linkPopover && !isItemDragging && siblingHintRects.length === 0 && /* @__PURE__ */ jsx36(ItemInteractionLayer, { rect: siblingHintRect, state: "sibling-hint" }),
27185
+ siblingHintRects.length > 0 && !linkPopover && !isItemDragging && siblingHintRects.map((rect, i) => /* @__PURE__ */ jsx36(ItemInteractionLayer, { rect, state: "sibling-hint" }, `sibling-hint-${i}`)),
27186
+ isItemDragging && draggedItemRect && selectedElRef.current !== footerDragRef.current?.draggedEl && selectedElRef.current !== navDragRef.current?.draggedEl && selectedElRef.current !== buttonCollectionDragRef.current && /* @__PURE__ */ jsx36(ItemInteractionLayer, { rect: draggedItemRect, state: "dragging" }),
27187
+ isItemDragging && footerDropSlots.map((slot, i) => /* @__PURE__ */ jsx36(
25252
27188
  "div",
25253
27189
  {
25254
27190
  className: "pointer-events-none fixed z-2147483646",
@@ -25258,7 +27194,7 @@ function OhhwellsBridge() {
25258
27194
  width: slot.width,
25259
27195
  height: slot.height
25260
27196
  },
25261
- children: /* @__PURE__ */ jsx35(
27197
+ children: /* @__PURE__ */ jsx36(
25262
27198
  DropIndicator,
25263
27199
  {
25264
27200
  direction: slot.direction,
@@ -25269,7 +27205,7 @@ function OhhwellsBridge() {
25269
27205
  },
25270
27206
  `footer-drop-${slot.direction}-${slot.columnIndex}-${slot.insertIndex}-${i}`
25271
27207
  )),
25272
- isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx35(
27208
+ isItemDragging && navDropSlots.map((slot, i) => /* @__PURE__ */ jsx36(
25273
27209
  "div",
25274
27210
  {
25275
27211
  className: "pointer-events-none fixed z-2147483646",
@@ -25279,7 +27215,7 @@ function OhhwellsBridge() {
25279
27215
  width: slot.width,
25280
27216
  height: slot.height
25281
27217
  },
25282
- children: /* @__PURE__ */ jsx35(
27218
+ children: /* @__PURE__ */ jsx36(
25283
27219
  DropIndicator,
25284
27220
  {
25285
27221
  direction: slot.direction,
@@ -25290,10 +27226,31 @@ function OhhwellsBridge() {
25290
27226
  },
25291
27227
  `nav-drop-${slot.direction}-${slot.parentId ?? "root"}-${slot.insertIndex}-${i}`
25292
27228
  )),
25293
- hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx35(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
25294
- hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx35(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
25295
- hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx35(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
25296
- formPickRect && !isItemDragging && /* @__PURE__ */ jsx35(
27229
+ isItemDragging && buttonDropSlots.map((slot, i) => /* @__PURE__ */ jsx36(
27230
+ "div",
27231
+ {
27232
+ className: "pointer-events-none fixed z-2147483646",
27233
+ style: {
27234
+ left: slot.left,
27235
+ top: slot.top,
27236
+ width: slot.width,
27237
+ height: slot.height
27238
+ },
27239
+ children: /* @__PURE__ */ jsx36(
27240
+ DropIndicator,
27241
+ {
27242
+ direction: slot.direction,
27243
+ state: activeButtonDropIndex === i ? "dragActive" : "dragIdle",
27244
+ className: "!h-full !w-full"
27245
+ }
27246
+ )
27247
+ },
27248
+ `button-drop-${slot.direction}-${slot.insertIndex}-${i}`
27249
+ )),
27250
+ hoveredNavContainerRect && (toolbarVariant !== "select-frame" || isFooterFrameSelection) && !linkPopover && !isItemDragging && /* @__PURE__ */ jsx36(ItemInteractionLayer, { rect: hoveredNavContainerRect, state: "hover" }),
27251
+ hoveredItemRect && !linkPopover && !hoveredNavContainerRect && !isItemDragging && hoveredItemElRef.current !== selectedElRef.current && /* @__PURE__ */ jsx36(ItemInteractionLayer, { rect: hoveredItemRect, state: "hover" }),
27252
+ hoveredTextRect && !hoveredNavContainerRect && !hoveredItemRect && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx36(ItemInteractionLayer, { rect: hoveredTextRect, state: "hover" }),
27253
+ formPickRect && !isItemDragging && /* @__PURE__ */ jsx36(
25297
27254
  ItemInteractionLayer,
25298
27255
  {
25299
27256
  rect: formPickRect,
@@ -25301,13 +27258,13 @@ function OhhwellsBridge() {
25301
27258
  itemDragSurface: false,
25302
27259
  toolbarAlign: "left",
25303
27260
  chromeGap: 24,
25304
- toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ jsxs22(
27261
+ toolbar: fieldPickRect ? void 0 : /* @__PURE__ */ jsxs23(
25305
27262
  "div",
25306
27263
  {
25307
27264
  "data-ohw-form-toolbar": "",
25308
27265
  className: "pointer-events-auto flex items-center gap-0.5 whitespace-nowrap rounded-lg border border-border bg-background p-1 shadow-md",
25309
27266
  children: [
25310
- /* @__PURE__ */ jsx35(
27267
+ /* @__PURE__ */ jsx36(
25311
27268
  "button",
25312
27269
  {
25313
27270
  type: "button",
@@ -25315,11 +27272,11 @@ function OhhwellsBridge() {
25315
27272
  className: "flex h-7 w-7 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted/80",
25316
27273
  onClick: () => setFieldTypePickerOpen((open) => !open),
25317
27274
  "data-ohw-add-field": "",
25318
- children: /* @__PURE__ */ jsx35(Plus4, { size: 15, "aria-hidden": true })
27275
+ children: /* @__PURE__ */ jsx36(Plus4, { size: 15, "aria-hidden": true })
25319
27276
  }
25320
27277
  ),
25321
- /* @__PURE__ */ jsx35("div", { className: "mx-0.5 h-5 w-px bg-border" }),
25322
- /* @__PURE__ */ jsxs22(
27278
+ /* @__PURE__ */ jsx36("div", { className: "mx-0.5 h-5 w-px bg-border" }),
27279
+ /* @__PURE__ */ jsxs23(
25323
27280
  "button",
25324
27281
  {
25325
27282
  type: "button",
@@ -25335,11 +27292,11 @@ function OhhwellsBridge() {
25335
27292
  });
25336
27293
  },
25337
27294
  children: [
25338
- /* @__PURE__ */ jsx35(Settings, { size: 14, "aria-hidden": true }),
27295
+ /* @__PURE__ */ jsx36(Settings, { size: 14, "aria-hidden": true }),
25339
27296
  "Form settings",
25340
27297
  formPickCount ? (
25341
27298
  // Counter pill, per the design — not a text suffix.
25342
- /* @__PURE__ */ jsx35(
27299
+ /* @__PURE__ */ jsx36(
25343
27300
  "span",
25344
27301
  {
25345
27302
  "data-ohw-form-count": "",
@@ -25351,8 +27308,8 @@ function OhhwellsBridge() {
25351
27308
  ]
25352
27309
  }
25353
27310
  ),
25354
- /* @__PURE__ */ jsx35("div", { className: "mx-0.5 h-5 w-px bg-border" }),
25355
- /* @__PURE__ */ jsx35("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ jsx35(
27311
+ /* @__PURE__ */ jsx36("div", { className: "mx-0.5 h-5 w-px bg-border" }),
27312
+ /* @__PURE__ */ jsx36("div", { className: "flex items-center rounded-md bg-muted/70 p-0.5", children: ["default", "success"].map((state) => /* @__PURE__ */ jsx36(
25356
27313
  "button",
25357
27314
  {
25358
27315
  type: "button",
@@ -25383,7 +27340,7 @@ function OhhwellsBridge() {
25383
27340
  )
25384
27341
  }
25385
27342
  ),
25386
- formHoverRect && !isItemDragging && /* @__PURE__ */ jsx35(
27343
+ formHoverRect && !isItemDragging && /* @__PURE__ */ jsx36(
25387
27344
  ItemInteractionLayer,
25388
27345
  {
25389
27346
  rect: formHoverRect,
@@ -25391,7 +27348,7 @@ function OhhwellsBridge() {
25391
27348
  chromeGap: formHoverElRef.current && getFieldWrapper(formHoverElRef.current) ? 8 : 24
25392
27349
  }
25393
27350
  ),
25394
- fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx35(
27351
+ fieldPickRect && fieldPickState && !isItemDragging && toolbarVariant !== "rich-text" && /* @__PURE__ */ jsx36(
25395
27352
  ItemInteractionLayer,
25396
27353
  {
25397
27354
  rect: fieldPickRect,
@@ -25403,7 +27360,7 @@ function OhhwellsBridge() {
25403
27360
  dragHandleLabel: "Reorder field",
25404
27361
  onDragHandleDragStart: handleFieldDragStart,
25405
27362
  onDragHandleDragEnd: handleFieldDragEnd,
25406
- toolbar: /* @__PURE__ */ jsx35(
27363
+ toolbar: /* @__PURE__ */ jsx36(
25407
27364
  FormFieldToolbar,
25408
27365
  {
25409
27366
  type: fieldPickState.type,
@@ -25416,12 +27373,12 @@ function OhhwellsBridge() {
25416
27373
  )
25417
27374
  }
25418
27375
  ),
25419
- fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ jsx35(
27376
+ fieldDragging ? fieldDropSlots.map((slot, i) => /* @__PURE__ */ jsx36(
25420
27377
  "div",
25421
27378
  {
25422
27379
  className: "pointer-events-none fixed z-[2147483644]",
25423
27380
  style: { top: slot.top, left: slot.left, width: slot.width },
25424
- children: /* @__PURE__ */ jsx35(
27381
+ children: /* @__PURE__ */ jsx36(
25425
27382
  DropIndicator,
25426
27383
  {
25427
27384
  direction: "horizontal",
@@ -25434,7 +27391,7 @@ function OhhwellsBridge() {
25434
27391
  )) : null,
25435
27392
  fieldTypePickerOpen && formPickRect ? (() => {
25436
27393
  const toolbar = document.querySelector("[data-ohw-form-toolbar]")?.getBoundingClientRect();
25437
- return /* @__PURE__ */ jsx35(
27394
+ return /* @__PURE__ */ jsx36(
25438
27395
  "div",
25439
27396
  {
25440
27397
  className: "pointer-events-none fixed z-[2147483645]",
@@ -25442,12 +27399,12 @@ function OhhwellsBridge() {
25442
27399
  top: toolbar ? toolbar.bottom + 6 : formPickRect.top + 16,
25443
27400
  left: toolbar ? toolbar.left : formPickRect.left + 24
25444
27401
  },
25445
- children: /* @__PURE__ */ jsx35(FieldTypePicker, { onPick: handleAddField })
27402
+ children: /* @__PURE__ */ jsx36(FieldTypePicker, { onPick: handleAddField })
25446
27403
  }
25447
27404
  );
25448
27405
  })() : null,
25449
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx35(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
25450
- toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx35(
27406
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isNavbarLinksContainer2(selectedElRef.current) && /* @__PURE__ */ jsx36(NavbarContainerChrome, { rect: toolbarRect, onAdd: handleAddTopLevelNavItem }),
27407
+ toolbarVariant === "select-frame" && toolbarRect && !linkPopover && !isItemDragging && !isFooterFrameSelection && selectedElRef.current && isFooterLinksContainer(selectedElRef.current) && /* @__PURE__ */ jsx36(
25451
27408
  FooterContainerChrome,
25452
27409
  {
25453
27410
  rect: toolbarRect,
@@ -25455,13 +27412,17 @@ function OhhwellsBridge() {
25455
27412
  addDisabled: !canAddFooterColumn()
25456
27413
  }
25457
27414
  ),
25458
- toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ jsx35(
27415
+ toolbarRect && !linkPopover && (toolbarVariant === "link-action" || toolbarVariant === "select-frame" || toolbarVariant === "logo") && /* @__PURE__ */ jsx36(
25459
27416
  ItemInteractionLayer,
25460
27417
  {
25461
- rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected) ? draggedItemRect : toolbarRect,
27418
+ rect: isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected || selectedElRef.current === buttonCollectionDragRef.current) ? draggedItemRect : toolbarRect,
25462
27419
  toolbarBelowRect: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanelRect(selectedElRef.current) : null,
25463
27420
  toolbarBelowEl: selectedElRef.current && isNavigationItem2(selectedElRef.current) ? getOpenNavDropdownPanel(selectedElRef.current) : null,
25464
27421
  elRef: glowElRef,
27422
+ hidden: isRectOutsideClip(
27423
+ isItemDragging && draggedItemRect && (footerDragRef.current?.wasSelected || navDragRef.current?.wasSelected || selectedElRef.current === buttonCollectionDragRef.current) ? draggedItemRect : toolbarRect,
27424
+ getIframeVisibleClip(parentScrollRef.current)
27425
+ ),
25465
27426
  state: isItemDragging ? "dragging" : selectedElRef.current && isNavigationItem2(selectedElRef.current) && getOpenNavDropdownPanel(selectedElRef.current) ? "active-bottom" : resolveItemInteractionState(toolbarRect, parentScrollRef.current),
25466
27427
  showHandle: toolbarVariant === "link-action" && Boolean(reorderHrefKey) || toolbarVariant === "select-frame" && isFooterFrameSelection,
25467
27428
  dragDisabled: reorderDragDisabled,
@@ -25471,18 +27432,25 @@ function OhhwellsBridge() {
25471
27432
  onItemPointerDown: toolbarVariant === "logo" ? void 0 : handleItemChromePointerDown,
25472
27433
  onItemClick: toolbarVariant === "logo" ? void 0 : handleItemChromeClick,
25473
27434
  itemDragSurface: toolbarVariant !== "logo" && !isFooterFrameSelection && !selectedIsSocialsRow,
25474
- toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx35(
27435
+ toolbar: toolbarVariant === "link-action" && !isItemDragging || toolbarVariant === "select-frame" && (isFooterFrameSelection || selectedIsSocialsRow) && !isItemDragging ? /* @__PURE__ */ jsx36(
25475
27436
  ItemActionToolbar,
25476
27437
  {
25477
27438
  onEditLink: openLinkPopoverForSelected,
25478
27439
  onStyle: () => {
25479
27440
  const row = selectedElRef.current;
25480
27441
  if (!row) return;
25481
- if (floatingPanel?.kind === "socials-display") closeFloatingPanelOnly();
27442
+ suppressNextClickRef.current = true;
27443
+ suppressClickUntilRef.current = Date.now() + 500;
27444
+ if (isCtaButton(row)) {
27445
+ if (floatingPanelRef.current?.kind === "button-style") closeFloatingPanelOnly();
27446
+ else openButtonStylePanel(row);
27447
+ return;
27448
+ }
27449
+ if (floatingPanelRef.current?.kind === "socials-display") closeFloatingPanelOnly();
25482
27450
  else openSocialsDisplayPanel(row);
25483
27451
  },
25484
- showStyle: selectedIsSocialsRow,
25485
- styleActive: floatingPanel?.kind === "socials-display",
27452
+ showStyle: selectedIsSocialsRow || selectedIsCta,
27453
+ styleActive: floatingPanel?.kind === "socials-display" || floatingPanel?.kind === "button-style",
25486
27454
  onAddItem: handleAddChildItem,
25487
27455
  onSelectParent: handleSelectParent,
25488
27456
  onDuplicate: handleDuplicateSelected,
@@ -25493,11 +27461,11 @@ function OhhwellsBridge() {
25493
27461
  })(),
25494
27462
  editLinkDisabled: false,
25495
27463
  moreDisabled: false,
25496
- deleteDisabled: selectedElRef.current !== null && (() => {
27464
+ deleteDisabled: selectedElRef.current !== null && isCtaButton(selectedElRef.current) && !canDeleteButton(selectedElRef.current) || selectedElRef.current !== null && (() => {
25497
27465
  const social = getSocialItem(selectedElRef.current);
25498
27466
  return social ? !canRemoveSocialItem(social) : false;
25499
27467
  })(),
25500
- duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow || selectedElRef.current !== null && (() => {
27468
+ duplicateDisabled: isFooterFrameSelection || selectedIsSocialsRow || selectedElRef.current !== null && isCtaButton(selectedElRef.current) && !canDuplicateButton(selectedElRef.current) || selectedElRef.current !== null && (() => {
25501
27469
  const social = getSocialItem(selectedElRef.current);
25502
27470
  const row = social ? findSocialsRow(social) : null;
25503
27471
  return row ? !canAddSocialItem(row) : false;
@@ -25506,7 +27474,7 @@ function OhhwellsBridge() {
25506
27474
  showAddItem: isFooterFrameSelection || selectedIsSocialsRow || !selectedIsCta && Boolean(
25507
27475
  selectedElRef.current && isNavbarHrefKey(selectedElRef.current.getAttribute("data-ohw-href-key")) && !isNestedNavChild(selectedElRef.current)
25508
27476
  ),
25509
- showMore: (!selectedIsCta || isFooterFrameSelection) && !selectedIsSocialsRow,
27477
+ showMore: !selectedIsSocialsRow,
25510
27478
  dropdownOpen: navDropdownPreviewOpen,
25511
27479
  onDropdownOpenChange: handleNavDropdownOpenChange,
25512
27480
  headingVisible: footerHeadingVisible,
@@ -25515,8 +27483,8 @@ function OhhwellsBridge() {
25515
27483
  ) : void 0
25516
27484
  }
25517
27485
  ),
25518
- toolbarRect && toolbarVariant === "rich-text" && !linkPopover && !isRectOutsideClip(toolbarRect, getIframeVisibleClip(parentScrollRef.current)) && /* @__PURE__ */ jsxs22(Fragment10, { children: [
25519
- /* @__PURE__ */ jsx35(
27486
+ toolbarRect && toolbarVariant === "rich-text" && !linkPopover && !isRectOutsideClip(toolbarRect, getIframeVisibleClip(parentScrollRef.current)) && /* @__PURE__ */ jsxs23(Fragment10, { children: [
27487
+ /* @__PURE__ */ jsx36(
25520
27488
  EditGlowChrome,
25521
27489
  {
25522
27490
  rect: toolbarRect,
@@ -25526,7 +27494,7 @@ function OhhwellsBridge() {
25526
27494
  hideHandle: isItemDragging
25527
27495
  }
25528
27496
  ),
25529
- /* @__PURE__ */ jsx35(
27497
+ /* @__PURE__ */ jsx36(
25530
27498
  FloatingToolbar,
25531
27499
  {
25532
27500
  rect: toolbarRect,
@@ -25539,7 +27507,7 @@ function OhhwellsBridge() {
25539
27507
  }
25540
27508
  )
25541
27509
  ] }),
25542
- maxBadge && /* @__PURE__ */ jsxs22(
27510
+ maxBadge && /* @__PURE__ */ jsxs23(
25543
27511
  "div",
25544
27512
  {
25545
27513
  "data-ohw-max-badge": "",
@@ -25565,7 +27533,7 @@ function OhhwellsBridge() {
25565
27533
  ]
25566
27534
  }
25567
27535
  ),
25568
- toggleState && !linkPopover && /* @__PURE__ */ jsx35(
27536
+ toggleState && !linkPopover && /* @__PURE__ */ jsx36(
25569
27537
  StateToggle,
25570
27538
  {
25571
27539
  rect: toggleState.rect,
@@ -25574,15 +27542,15 @@ function OhhwellsBridge() {
25574
27542
  onStateChange: handleStateChange
25575
27543
  }
25576
27544
  ),
25577
- sectionGap && !linkPopover && /* @__PURE__ */ jsxs22(
27545
+ sectionGap && !linkPopover && /* @__PURE__ */ jsxs23(
25578
27546
  "div",
25579
27547
  {
25580
27548
  "data-ohw-section-insert-line": "",
25581
27549
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
25582
27550
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
25583
27551
  children: [
25584
- /* @__PURE__ */ jsx35("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
25585
- /* @__PURE__ */ jsx35(
27552
+ /* @__PURE__ */ jsx36("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } }),
27553
+ /* @__PURE__ */ jsx36(
25586
27554
  Badge,
25587
27555
  {
25588
27556
  className: "px-8 py-1 bg-bridge-primary hover:bg-bridge-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
@@ -25599,11 +27567,11 @@ function OhhwellsBridge() {
25599
27567
  children: "Add Section"
25600
27568
  }
25601
27569
  ),
25602
- /* @__PURE__ */ jsx35("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
27570
+ /* @__PURE__ */ jsx36("div", { className: "flex-1 bg-bridge-primary", style: { height: 3 } })
25603
27571
  ]
25604
27572
  }
25605
27573
  ),
25606
- linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx35(
27574
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx36(
25607
27575
  LinkPopover,
25608
27576
  {
25609
27577
  panelRef: linkPopoverPanelRef,
@@ -25620,7 +27588,7 @@ function OhhwellsBridge() {
25620
27588
  },
25621
27589
  linkPopover.key
25622
27590
  ) : null,
25623
- floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ jsx35(
27591
+ floatingPanel && floatingPanel.kind === "socials-display" ? /* @__PURE__ */ jsx36(
25624
27592
  FloatingPanel,
25625
27593
  {
25626
27594
  open: true,
@@ -25630,7 +27598,7 @@ function OhhwellsBridge() {
25630
27598
  onPositionChange: setFloatingPanelPos,
25631
27599
  parentScroll: parentScrollSnap ?? parentScrollRef.current,
25632
27600
  onClose: closeFloatingPanelOnly,
25633
- children: /* @__PURE__ */ jsx35(
27601
+ children: /* @__PURE__ */ jsx36(
25634
27602
  SocialsDisplayPanel,
25635
27603
  {
25636
27604
  display: socialsDisplayFor(floatingPanel.row, editContentRef.current),
@@ -25641,7 +27609,48 @@ function OhhwellsBridge() {
25641
27609
  }
25642
27610
  )
25643
27611
  }
25644
- ) : null
27612
+ ) : null,
27613
+ floatingPanel && floatingPanel.kind === "button-style" ? (() => {
27614
+ const button = document.querySelector(
27615
+ `[data-ohw-href-key="${CSS.escape(floatingPanel.buttonId)}"]`
27616
+ );
27617
+ if (!button) return null;
27618
+ captureButtonOrigin(button);
27619
+ const tokens = tokensFromButton(button);
27620
+ const current = assignmentFor(button, editContentRef.current, tokens);
27621
+ void buttonStyleTick;
27622
+ return /* @__PURE__ */ jsx36(
27623
+ FloatingPanel,
27624
+ {
27625
+ open: true,
27626
+ title: floatingPanel.title,
27627
+ context: floatingPanel.context,
27628
+ icon: /* @__PURE__ */ jsx36(Brush2, { className: "size-4 shrink-0", "aria-hidden": true }),
27629
+ position: floatingPanelPos,
27630
+ onPositionChange: setFloatingPanelPos,
27631
+ parentScroll: parentScrollSnap ?? parentScrollRef.current,
27632
+ onClose: closeFloatingPanelOnly,
27633
+ bodyClassName: "gap-5",
27634
+ children: /* @__PURE__ */ jsx36(
27635
+ ButtonStylePanel,
27636
+ {
27637
+ variants: tokens.variants,
27638
+ sizes: tokens.sizes,
27639
+ variant: current.variant,
27640
+ size: current.size,
27641
+ slotBackground: previewSlotBackground(button),
27642
+ previewLabel: (button.querySelector(
27643
+ '[data-ohw-editable="text"], [data-ohw-editable="plain"]'
27644
+ )?.textContent ?? button.textContent ?? "Book Now").trim() || "Book Now",
27645
+ onVariant: (variant) => applySelectedButtonStyle({ variant }),
27646
+ onSize: (size) => applySelectedButtonStyle({ size })
27647
+ },
27648
+ floatingPanel.key
27649
+ )
27650
+ },
27651
+ floatingPanel.key
27652
+ );
27653
+ })() : null
25645
27654
  ] }),
25646
27655
  bridgeRoot
25647
27656
  ) : null