@ohhwells/bridge 0.1.38-next.56 → 0.1.38-next.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  CustomToolbarButton: () => CustomToolbarButton,
36
36
  CustomToolbarDivider: () => CustomToolbarDivider,
37
37
  DragHandle: () => DragHandle,
38
+ DropIndicator: () => DropIndicator,
38
39
  ItemActionToolbar: () => ItemActionToolbar,
39
40
  ItemInteractionLayer: () => ItemInteractionLayer,
40
41
  LinkEditorPanel: () => LinkEditorPanel,
@@ -49,6 +50,7 @@ __export(index_exports, {
49
50
  TooltipProvider: () => TooltipProvider,
50
51
  TooltipTrigger: () => TooltipTrigger,
51
52
  buildTarget: () => buildTarget,
53
+ dropIndicatorVariants: () => dropIndicatorVariants,
52
54
  filterAvailablePages: () => filterAvailablePages,
53
55
  getEditModeInitialState: () => getEditModeInitialState,
54
56
  isEditSessionActive: () => isEditSessionActive,
@@ -5181,9 +5183,9 @@ var DialogContent = React7.forwardRef(
5181
5183
  "data-slot": "dialog-content",
5182
5184
  className: cn(
5183
5185
  positionMode,
5184
- "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[483px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-visible",
5186
+ "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[448px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-visible",
5185
5187
  "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
5186
- container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
5188
+ container ? "max-h-[calc(100%-32px)]" : "max-h-[calc(100vh-32px)]",
5187
5189
  className
5188
5190
  ),
5189
5191
  onMouseDown: (e) => e.stopPropagation(),
@@ -5448,7 +5450,7 @@ function UrlOrPageInput({
5448
5450
  requestAnimationFrame(() => inputRef.current?.focus());
5449
5451
  };
5450
5452
  const fieldClassName = cn(
5451
- "data-ohw-link-field flex h-10 w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5453
+ "data-ohw-link-field flex h-[36px] w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
5452
5454
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
5453
5455
  );
5454
5456
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
@@ -5634,6 +5636,26 @@ function rectsEqual(a, b) {
5634
5636
  }
5635
5637
  return true;
5636
5638
  }
5639
+ function readSectionRects(sectionIds) {
5640
+ const next = /* @__PURE__ */ new Map();
5641
+ for (const id of sectionIds) {
5642
+ const el = document.querySelector(
5643
+ `[data-ohw-section="${CSS.escape(id)}"]`
5644
+ );
5645
+ if (el) next.set(id, el.getBoundingClientRect());
5646
+ }
5647
+ return next;
5648
+ }
5649
+ function hitTestSectionId(x, y, sectionIds, rects) {
5650
+ for (const id of sectionIds) {
5651
+ const rect = rects.get(id);
5652
+ if (!rect || rect.width <= 0 || rect.height <= 0) continue;
5653
+ if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
5654
+ return id;
5655
+ }
5656
+ }
5657
+ return null;
5658
+ }
5637
5659
  function useSectionRects(sectionIdsKey, enabled) {
5638
5660
  const [rects, setRects] = (0, import_react5.useState)(/* @__PURE__ */ new Map());
5639
5661
  const sectionIds = (0, import_react5.useMemo)(
@@ -5646,13 +5668,7 @@ function useSectionRects(sectionIdsKey, enabled) {
5646
5668
  return;
5647
5669
  }
5648
5670
  const update = () => {
5649
- const next = /* @__PURE__ */ new Map();
5650
- for (const id of sectionIds) {
5651
- const el = document.querySelector(
5652
- `[data-ohw-section="${CSS.escape(id)}"]`
5653
- );
5654
- if (el) next.set(id, el.getBoundingClientRect());
5655
- }
5671
+ const next = readSectionRects(sectionIds);
5656
5672
  setRects((prev) => rectsEqual(prev, next) ? prev : next);
5657
5673
  };
5658
5674
  update();
@@ -5689,6 +5705,10 @@ function SectionPickerOverlay({
5689
5705
  const pathname = (0, import_navigation.usePathname)();
5690
5706
  const [selectedId, setSelectedId] = (0, import_react5.useState)(null);
5691
5707
  const [hoveredId, setHoveredId] = (0, import_react5.useState)(null);
5708
+ const pointerRef = (0, import_react5.useRef)(null);
5709
+ const pointerScreenRef = (0, import_react5.useRef)(null);
5710
+ const iframeOffsetTopRef = (0, import_react5.useRef)(null);
5711
+ const sectionIdsRef = (0, import_react5.useRef)([]);
5692
5712
  const [chromeClip, setChromeClip] = (0, import_react5.useState)(
5693
5713
  () => ({
5694
5714
  top: 0,
@@ -5711,6 +5731,29 @@ function SectionPickerOverlay({
5711
5731
  document.documentElement.removeAttribute("data-ohw-section-picking");
5712
5732
  };
5713
5733
  }, []);
5734
+ const liveSections = (0, import_react5.useMemo)(() => {
5735
+ if (!isOnTargetPage) return sections;
5736
+ const live = collectSectionsFromDom();
5737
+ if (live.length === 0) return sections;
5738
+ const labels = new Map(sections.map((s) => [s.id, s.label]));
5739
+ return live.map((s) => ({ id: s.id, label: labels.get(s.id) ?? s.label }));
5740
+ }, [isOnTargetPage, sections, pathname]);
5741
+ const sectionIdsKey = (0, import_react5.useMemo)(
5742
+ () => liveSections.map((s) => s.id).join("\0"),
5743
+ [liveSections]
5744
+ );
5745
+ const sectionIds = (0, import_react5.useMemo)(
5746
+ () => liveSections.map((s) => s.id),
5747
+ [liveSections]
5748
+ );
5749
+ sectionIdsRef.current = sectionIds;
5750
+ const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5751
+ const applyHoverAt = (0, import_react5.useCallback)((x, y, liveRects) => {
5752
+ const ids = sectionIdsRef.current;
5753
+ const map = liveRects ?? readSectionRects(ids);
5754
+ const next = hitTestSectionId(x, y, ids, map);
5755
+ setHoveredId((prev) => prev === next ? prev : next);
5756
+ }, []);
5714
5757
  (0, import_react5.useEffect)(() => {
5715
5758
  const applyClip = (iframeOffsetTop, visibleCanvasTop, canvasH) => {
5716
5759
  const top = Math.max(0, visibleCanvasTop - iframeOffsetTop);
@@ -5726,6 +5769,19 @@ function SectionPickerOverlay({
5726
5769
  if (e.data?.type !== "ow:parent-scroll") return;
5727
5770
  const { iframeOffsetTop, headerH, canvasH } = e.data;
5728
5771
  applyClip(iframeOffsetTop, headerH, canvasH);
5772
+ iframeOffsetTopRef.current = iframeOffsetTop;
5773
+ if (!pointerScreenRef.current && pointerRef.current) {
5774
+ pointerScreenRef.current = {
5775
+ x: pointerRef.current.x,
5776
+ y: pointerRef.current.y + iframeOffsetTop
5777
+ };
5778
+ }
5779
+ const screen = pointerScreenRef.current;
5780
+ if (screen) {
5781
+ const next = { x: screen.x, y: screen.y - iframeOffsetTop };
5782
+ pointerRef.current = next;
5783
+ applyHoverAt(next.x, next.y);
5784
+ }
5729
5785
  };
5730
5786
  const onResize = () => {
5731
5787
  setChromeClip((prev) => {
@@ -5739,19 +5795,36 @@ function SectionPickerOverlay({
5739
5795
  window.removeEventListener("message", onParentScroll);
5740
5796
  window.removeEventListener("resize", onResize);
5741
5797
  };
5742
- }, []);
5743
- const liveSections = (0, import_react5.useMemo)(() => {
5744
- if (!isOnTargetPage) return sections;
5745
- const live = collectSectionsFromDom();
5746
- if (live.length === 0) return sections;
5747
- const labels = new Map(sections.map((s) => [s.id, s.label]));
5748
- return live.map((s) => ({ id: s.id, label: labels.get(s.id) ?? s.label }));
5749
- }, [isOnTargetPage, sections, pathname]);
5750
- const sectionIdsKey = (0, import_react5.useMemo)(
5751
- () => liveSections.map((s) => s.id).join("\0"),
5752
- [liveSections]
5753
- );
5754
- const rects = useSectionRects(sectionIdsKey, isOnTargetPage);
5798
+ }, [applyHoverAt]);
5799
+ (0, import_react5.useEffect)(() => {
5800
+ const ptr = pointerRef.current;
5801
+ if (!ptr) return;
5802
+ applyHoverAt(ptr.x, ptr.y, rects);
5803
+ }, [rects, applyHoverAt]);
5804
+ (0, import_react5.useEffect)(() => {
5805
+ const rememberPointer = (clientX, clientY) => {
5806
+ pointerRef.current = { x: clientX, y: clientY };
5807
+ const top = iframeOffsetTopRef.current;
5808
+ if (top != null) {
5809
+ pointerScreenRef.current = { x: clientX, y: clientY + top };
5810
+ }
5811
+ applyHoverAt(clientX, clientY, rects);
5812
+ };
5813
+ const onPointerMove = (e) => {
5814
+ rememberPointer(e.clientX, e.clientY);
5815
+ };
5816
+ const onPointerSync = (e) => {
5817
+ if (e.data?.type !== "ow:pointer-sync") return;
5818
+ const { clientX, clientY } = e.data;
5819
+ rememberPointer(clientX, clientY);
5820
+ };
5821
+ window.addEventListener("pointermove", onPointerMove, { passive: true });
5822
+ window.addEventListener("message", onPointerSync);
5823
+ return () => {
5824
+ window.removeEventListener("pointermove", onPointerMove);
5825
+ window.removeEventListener("message", onPointerSync);
5826
+ };
5827
+ }, [rects, applyHoverAt]);
5755
5828
  const handleSelect = (0, import_react5.useCallback)(
5756
5829
  (section) => {
5757
5830
  if (selectedId) return;
@@ -5845,8 +5918,6 @@ function SectionPickerOverlay({
5845
5918
  backgroundColor: isLit ? "transparent" : DIM_OVERLAY
5846
5919
  },
5847
5920
  "aria-label": `Select section ${section.label}`,
5848
- onMouseEnter: () => setHoveredId(section.id),
5849
- onMouseLeave: () => setHoveredId((prev) => prev === section.id ? null : prev),
5850
5921
  onClick: () => handleSelect(section),
5851
5922
  children: isSelected ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5852
5923
  "span",
@@ -6090,7 +6161,8 @@ function LinkPopover({
6090
6161
  if (overflowY !== "auto" && overflowY !== "scroll") return false;
6091
6162
  if (el.scrollHeight <= el.clientHeight + 1) return false;
6092
6163
  if (deltaY < 0) return el.scrollTop > 0;
6093
- if (deltaY > 0) return el.scrollTop + el.clientHeight < el.scrollHeight - 1;
6164
+ if (deltaY > 0)
6165
+ return el.scrollTop + el.clientHeight < el.scrollHeight - 1;
6094
6166
  return true;
6095
6167
  };
6096
6168
  const allowsInternalScroll = (e) => {
@@ -6116,8 +6188,16 @@ function LinkPopover({
6116
6188
  body.style.overflow = prevBodyOverflow;
6117
6189
  html.style.overscrollBehavior = prevHtmlOverscroll;
6118
6190
  body.style.overscrollBehavior = prevBodyOverscroll;
6119
- document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6120
- document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6191
+ document.removeEventListener(
6192
+ "wheel",
6193
+ preventBackgroundScroll,
6194
+ scrollOpts
6195
+ );
6196
+ document.removeEventListener(
6197
+ "touchmove",
6198
+ preventBackgroundScroll,
6199
+ scrollOpts
6200
+ );
6121
6201
  };
6122
6202
  }, [open, sectionPickerActive]);
6123
6203
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
@@ -6137,7 +6217,7 @@ function LinkPopover({
6137
6217
  "data-ohw-link-modal-root": "",
6138
6218
  "data-ohw-bridge": "",
6139
6219
  showCloseButton: false,
6140
- className: "gap-0 p-0 w-full md:w-md pointer-events-auto z-[2147483646] overflow-visible",
6220
+ className: "gap-0 p-0 w-full max-w-[448px] pointer-events-auto z-[2147483646] overflow-visible",
6141
6221
  children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LinkEditorPanel, { state, onClose })
6142
6222
  }
6143
6223
  )
@@ -9413,14 +9493,20 @@ function OhhwellsBridge() {
9413
9493
  const session = linkPopoverSessionRef.current;
9414
9494
  if (!session) return;
9415
9495
  if (session.intent === "add-nav") {
9416
- const { pageRoute, sectionId } = parseTarget(target);
9417
- const page = resolvePage(pageRoute, sitePages);
9418
- const pageSections = getSectionsForPath(sectionsByPath, pageRoute);
9419
- const section = sectionId ? pageSections.find((s) => s.id === sectionId) : void 0;
9420
- const label = section?.label ?? page.title;
9496
+ const trimmed = target.trim();
9497
+ let href = trimmed;
9498
+ let label = "Untitled";
9499
+ if (trimmed) {
9500
+ const { pageRoute, sectionId } = parseTarget(trimmed);
9501
+ const page = resolvePage(pageRoute, sitePages);
9502
+ const pageSections = getSectionsForPath(sectionsByPath, pageRoute);
9503
+ const section = sectionId ? pageSections.find((s) => s.id === sectionId) : void 0;
9504
+ label = section?.label ?? page.title;
9505
+ href = trimmed;
9506
+ }
9421
9507
  const afterAnchor = addNavAfterAnchorRef.current;
9422
- const { anchor, hrefKey, labelKey, index, order } = insertNavbarItem(target, label, afterAnchor);
9423
- applyLinkByKey(hrefKey, target);
9508
+ const { anchor, hrefKey, labelKey, index, order } = insertNavbarItem(href, label, afterAnchor);
9509
+ applyLinkByKey(hrefKey, href);
9424
9510
  document.querySelectorAll(`[data-ohw-key="${labelKey}"]`).forEach((el) => {
9425
9511
  el.textContent = label;
9426
9512
  });
@@ -9428,7 +9514,7 @@ function OhhwellsBridge() {
9428
9514
  const orderJson = JSON.stringify(order);
9429
9515
  editContentRef.current = {
9430
9516
  ...editContentRef.current,
9431
- [hrefKey]: target,
9517
+ [hrefKey]: href,
9432
9518
  [labelKey]: label,
9433
9519
  [NAV_COUNT_KEY]: navCount,
9434
9520
  [NAV_ORDER_KEY]: orderJson
@@ -9436,12 +9522,13 @@ function OhhwellsBridge() {
9436
9522
  postToParent2({
9437
9523
  type: "ow:change",
9438
9524
  nodes: [
9439
- { key: hrefKey, text: target },
9525
+ { key: hrefKey, text: href },
9440
9526
  { key: labelKey, text: label },
9441
9527
  { key: NAV_COUNT_KEY, text: navCount },
9442
9528
  { key: NAV_ORDER_KEY, text: orderJson }
9443
9529
  ]
9444
9530
  });
9531
+ postToParent2({ type: "ow:toast", title: "Link added", toastType: "success" });
9445
9532
  addNavAfterAnchorRef.current = null;
9446
9533
  setLinkPopover(null);
9447
9534
  enforceLinkHrefs();
@@ -9680,12 +9767,55 @@ function OhhwellsBridge() {
9680
9767
  bridgeRoot
9681
9768
  ) : null;
9682
9769
  }
9770
+
9771
+ // src/ui/drop-indicator.tsx
9772
+ var React10 = __toESM(require("react"), 1);
9773
+ var import_jsx_runtime24 = require("react/jsx-runtime");
9774
+ var dropIndicatorVariants = cva(
9775
+ "ov-gap-line pointer-events-none shrink-0 transition-opacity duration-150",
9776
+ {
9777
+ variants: {
9778
+ direction: {
9779
+ vertical: "h-6 w-[3px]",
9780
+ horizontal: "h-[3px] w-[200px]"
9781
+ },
9782
+ state: {
9783
+ default: "opacity-0",
9784
+ hover: "opacity-100",
9785
+ dragIdle: "opacity-40",
9786
+ dragActive: "opacity-100"
9787
+ }
9788
+ },
9789
+ defaultVariants: {
9790
+ direction: "vertical",
9791
+ state: "default"
9792
+ }
9793
+ }
9794
+ );
9795
+ var DropIndicator = React10.forwardRef(
9796
+ ({ className, direction, state, ...props }, ref) => {
9797
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
9798
+ "div",
9799
+ {
9800
+ ref,
9801
+ "data-slot": "drop-indicator",
9802
+ "data-direction": direction ?? "vertical",
9803
+ "data-state": state ?? "default",
9804
+ "aria-hidden": "true",
9805
+ className: cn(dropIndicatorVariants({ direction, state }), className),
9806
+ ...props
9807
+ }
9808
+ );
9809
+ }
9810
+ );
9811
+ DropIndicator.displayName = "DropIndicator";
9683
9812
  // Annotate the CommonJS export names for ESM import in node:
9684
9813
  0 && (module.exports = {
9685
9814
  CustomToolbar,
9686
9815
  CustomToolbarButton,
9687
9816
  CustomToolbarDivider,
9688
9817
  DragHandle,
9818
+ DropIndicator,
9689
9819
  ItemActionToolbar,
9690
9820
  ItemInteractionLayer,
9691
9821
  LinkEditorPanel,
@@ -9700,6 +9830,7 @@ function OhhwellsBridge() {
9700
9830
  TooltipProvider,
9701
9831
  TooltipTrigger,
9702
9832
  buildTarget,
9833
+ dropIndicatorVariants,
9703
9834
  filterAvailablePages,
9704
9835
  getEditModeInitialState,
9705
9836
  isEditSessionActive,