@ohhwells/bridge 0.1.30 → 0.1.31-next.30

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
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.ts
32
32
  var index_exports = {};
33
33
  __export(index_exports, {
34
+ DragHandle: () => DragHandle,
34
35
  LinkEditorPanel: () => LinkEditorPanel,
35
36
  LinkPopover: () => LinkPopover,
36
37
  OhhwellsBridge: () => OhhwellsBridge,
@@ -41,6 +42,7 @@ __export(index_exports, {
41
42
  buildTarget: () => buildTarget,
42
43
  filterAvailablePages: () => filterAvailablePages,
43
44
  getEditModeInitialState: () => getEditModeInitialState,
45
+ isEditSessionActive: () => isEditSessionActive,
44
46
  isValidUrl: () => isValidUrl,
45
47
  parseTarget: () => parseTarget,
46
48
  toggleVariants: () => toggleVariants,
@@ -4211,6 +4213,34 @@ function ToggleGroupItem({
4211
4213
  );
4212
4214
  }
4213
4215
 
4216
+ // src/ui/drag-handle.tsx
4217
+ var React3 = __toESM(require("react"), 1);
4218
+ var import_lucide_react = require("lucide-react");
4219
+ var import_jsx_runtime5 = require("react/jsx-runtime");
4220
+ var DragHandle = React3.forwardRef(
4221
+ ({ className, type = "button", ...props }, ref) => {
4222
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4223
+ "button",
4224
+ {
4225
+ ref,
4226
+ type,
4227
+ "data-slot": "drag-handle",
4228
+ className: cn(
4229
+ "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200",
4230
+ "bg-white border border-transparent text-stone-500 shadow-md cursor-grab",
4231
+ "enabled:hover:border enabled:hover:border-stone-200 enabled:hover:text-stone-950",
4232
+ "enabled:active:border enabled:active:border-primary enabled:active:bg-primary-50 enabled:active:text-stone-950 enabled:active:shadow enabled:active:cursor-grabbing",
4233
+ "disabled:cursor-not-allowed disabled:opacity-40 disabled:text-stone-950 disabled:pointer-events-none",
4234
+ className
4235
+ ),
4236
+ ...props,
4237
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react.GripVertical, { className: "size-4 shrink-0", "aria-hidden": "true" })
4238
+ }
4239
+ );
4240
+ }
4241
+ );
4242
+ DragHandle.displayName = "DragHandle";
4243
+
4214
4244
  // src/OhhwellsBridge.tsx
4215
4245
  var import_react_dom2 = require("react-dom");
4216
4246
  var import_navigation = require("next/navigation");
@@ -4489,61 +4519,9 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
4489
4519
  tick();
4490
4520
  }
4491
4521
 
4492
- // src/ui/link-modal/calcPopoverPos.ts
4493
- function calcPopoverPos(rect, parentScroll, approxW = 483, approxH = 320) {
4494
- const GAP = 8;
4495
- const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
4496
- const visibleTop = rect.top - canvasTopInIframe;
4497
- let top = rect.top - GAP;
4498
- let transform = "translateX(-50%) translateY(-100%)";
4499
- if (visibleTop < approxH + GAP) {
4500
- top = rect.bottom + GAP;
4501
- transform = "translateX(-50%)";
4502
- }
4503
- const rawLeft = rect.left + rect.width / 2;
4504
- const left = Math.max(GAP + approxW / 2, Math.min(rawLeft, window.innerWidth - GAP - approxW / 2));
4505
- return { top, left, transform };
4506
- }
4507
-
4508
- // src/ui/button.tsx
4509
- var React3 = __toESM(require("react"), 1);
4522
+ // src/ui/dialog.tsx
4523
+ var React4 = __toESM(require("react"), 1);
4510
4524
  var import_radix_ui3 = require("radix-ui");
4511
- var import_jsx_runtime5 = require("react/jsx-runtime");
4512
- var buttonVariants = cva(
4513
- "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 min-w-[80px] px-3 py-2",
4514
- {
4515
- variants: {
4516
- variant: {
4517
- default: "bg-primary text-primary-foreground hover:opacity-90",
4518
- outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4519
- ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4520
- },
4521
- size: {
4522
- default: "h-9",
4523
- sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
4524
- }
4525
- },
4526
- defaultVariants: {
4527
- variant: "default",
4528
- size: "default"
4529
- }
4530
- }
4531
- );
4532
- var Button = React3.forwardRef(
4533
- ({ className, variant, size, asChild = false, ...props }, ref) => {
4534
- const Comp = asChild ? import_radix_ui3.Slot.Root : "button";
4535
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4536
- Comp,
4537
- {
4538
- ref,
4539
- "data-slot": "button",
4540
- className: cn(buttonVariants({ variant, size, className })),
4541
- ...props
4542
- }
4543
- );
4544
- }
4545
- );
4546
- Button.displayName = "Button";
4547
4525
 
4548
4526
  // src/ui/icons.tsx
4549
4527
  var import_jsx_runtime6 = require("react/jsx-runtime");
@@ -4583,38 +4561,161 @@ function IconSection({ className, ...props }) {
4583
4561
  ] });
4584
4562
  }
4585
4563
 
4586
- // src/ui/link-modal/DestinationBreadcrumb.tsx
4564
+ // src/ui/dialog.tsx
4587
4565
  var import_jsx_runtime7 = require("react/jsx-runtime");
4566
+ function Dialog2({ ...props }) {
4567
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui3.Dialog.Root, { "data-slot": "dialog", ...props });
4568
+ }
4569
+ function DialogPortal({ ...props }) {
4570
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui3.Dialog.Portal, { ...props });
4571
+ }
4572
+ function DialogOverlay({
4573
+ className,
4574
+ ...props
4575
+ }) {
4576
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4577
+ import_radix_ui3.Dialog.Overlay,
4578
+ {
4579
+ "data-slot": "dialog-overlay",
4580
+ "data-ohw-link-modal-root": "",
4581
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
4582
+ ...props
4583
+ }
4584
+ );
4585
+ }
4586
+ var DialogContent = React4.forwardRef(({ className, children, showCloseButton = true, container, ...props }, ref) => {
4587
+ const positionMode = container ? "absolute" : "fixed";
4588
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(DialogPortal, { container: container ?? void 0, children: [
4589
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
4590
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
4591
+ import_radix_ui3.Dialog.Content,
4592
+ {
4593
+ ref,
4594
+ "data-slot": "dialog-content",
4595
+ className: cn(
4596
+ positionMode,
4597
+ "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-hidden",
4598
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
4599
+ container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
4600
+ className
4601
+ ),
4602
+ onMouseDown: (e) => e.stopPropagation(),
4603
+ ...props,
4604
+ children: [
4605
+ children,
4606
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4607
+ import_radix_ui3.Dialog.Close,
4608
+ {
4609
+ type: "button",
4610
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4611
+ "aria-label": "Close",
4612
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconX, { "aria-hidden": true })
4613
+ }
4614
+ ) : null
4615
+ ]
4616
+ }
4617
+ )
4618
+ ] });
4619
+ });
4620
+ DialogContent.displayName = import_radix_ui3.Dialog.Content.displayName;
4621
+ function DialogHeader({ className, ...props }) {
4622
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
4623
+ }
4624
+ function DialogFooter({ className, ...props }) {
4625
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: cn("flex items-center justify-end gap-2", className), ...props });
4626
+ }
4627
+ var DialogTitle = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4628
+ import_radix_ui3.Dialog.Title,
4629
+ {
4630
+ ref,
4631
+ className: cn("text-lg font-semibold leading-none tracking-tight text-card-foreground", className),
4632
+ ...props
4633
+ }
4634
+ ));
4635
+ DialogTitle.displayName = import_radix_ui3.Dialog.Title.displayName;
4636
+ var DialogDescription = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4637
+ import_radix_ui3.Dialog.Description,
4638
+ {
4639
+ ref,
4640
+ className: cn("text-sm text-muted-foreground", className),
4641
+ ...props
4642
+ }
4643
+ ));
4644
+ DialogDescription.displayName = import_radix_ui3.Dialog.Description.displayName;
4645
+ var DialogClose = import_radix_ui3.Dialog.Close;
4646
+
4647
+ // src/ui/button.tsx
4648
+ var React5 = __toESM(require("react"), 1);
4649
+ var import_radix_ui4 = require("radix-ui");
4650
+ var import_jsx_runtime8 = require("react/jsx-runtime");
4651
+ var buttonVariants = cva(
4652
+ "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 min-w-[80px] px-3 py-2",
4653
+ {
4654
+ variants: {
4655
+ variant: {
4656
+ default: "bg-primary text-primary-foreground hover:opacity-90",
4657
+ outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4658
+ ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4659
+ },
4660
+ size: {
4661
+ default: "h-9",
4662
+ sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
4663
+ }
4664
+ },
4665
+ defaultVariants: {
4666
+ variant: "default",
4667
+ size: "default"
4668
+ }
4669
+ }
4670
+ );
4671
+ var Button = React5.forwardRef(
4672
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
4673
+ const Comp = asChild ? import_radix_ui4.Slot.Root : "button";
4674
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4675
+ Comp,
4676
+ {
4677
+ ref,
4678
+ "data-slot": "button",
4679
+ className: cn(buttonVariants({ variant, size, className })),
4680
+ ...props
4681
+ }
4682
+ );
4683
+ }
4684
+ );
4685
+ Button.displayName = "Button";
4686
+
4687
+ // src/ui/link-modal/DestinationBreadcrumb.tsx
4688
+ var import_jsx_runtime9 = require("react/jsx-runtime");
4588
4689
  function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
4589
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
4590
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
4591
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-3", children: [
4592
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
4593
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4594
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
4690
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
4691
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
4692
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-3", children: [
4693
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2", children: [
4694
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4695
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
4595
4696
  ] }),
4596
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
4597
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
4598
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4599
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
4697
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
4698
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
4699
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4700
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
4600
4701
  ] })
4601
4702
  ] })
4602
4703
  ] });
4603
4704
  }
4604
4705
 
4605
4706
  // src/ui/link-modal/SectionTreeItem.tsx
4606
- var import_jsx_runtime8 = require("react/jsx-runtime");
4707
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4607
4708
  function SectionTreeItem({ section, onSelect, selected }) {
4608
4709
  const interactive = Boolean(onSelect);
4609
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
4610
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4710
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
4711
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4611
4712
  "div",
4612
4713
  {
4613
4714
  className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
4614
4715
  "aria-hidden": true
4615
4716
  }
4616
4717
  ),
4617
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4718
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4618
4719
  "div",
4619
4720
  {
4620
4721
  role: interactive ? "button" : void 0,
@@ -4632,8 +4733,8 @@ function SectionTreeItem({ section, onSelect, selected }) {
4632
4733
  interactive && selected && "border-primary"
4633
4734
  ),
4634
4735
  children: [
4635
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4636
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
4736
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4737
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
4637
4738
  ]
4638
4739
  }
4639
4740
  )
@@ -4641,11 +4742,11 @@ function SectionTreeItem({ section, onSelect, selected }) {
4641
4742
  }
4642
4743
  function SectionPickerList({ sections, onSelect }) {
4643
4744
  if (sections.length === 0) {
4644
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
4745
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
4645
4746
  }
4646
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-1", children: [
4647
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
4648
- sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SectionTreeItem, { section, onSelect }, section.id))
4747
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-1", children: [
4748
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
4749
+ sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SectionTreeItem, { section, onSelect }, section.id))
4649
4750
  ] });
4650
4751
  }
4651
4752
 
@@ -4653,11 +4754,11 @@ function SectionPickerList({ sections, onSelect }) {
4653
4754
  var import_react3 = require("react");
4654
4755
 
4655
4756
  // src/ui/input.tsx
4656
- var React4 = __toESM(require("react"), 1);
4657
- var import_jsx_runtime9 = require("react/jsx-runtime");
4658
- var Input = React4.forwardRef(
4757
+ var React6 = __toESM(require("react"), 1);
4758
+ var import_jsx_runtime11 = require("react/jsx-runtime");
4759
+ var Input = React6.forwardRef(
4659
4760
  ({ className, type, ...props }, ref) => {
4660
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4761
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4661
4762
  "input",
4662
4763
  {
4663
4764
  type,
@@ -4675,11 +4776,11 @@ var Input = React4.forwardRef(
4675
4776
  Input.displayName = "Input";
4676
4777
 
4677
4778
  // src/ui/label.tsx
4678
- var import_radix_ui4 = require("radix-ui");
4679
- var import_jsx_runtime10 = require("react/jsx-runtime");
4779
+ var import_radix_ui5 = require("radix-ui");
4780
+ var import_jsx_runtime12 = require("react/jsx-runtime");
4680
4781
  function Label({ className, ...props }) {
4681
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4682
- import_radix_ui4.Label.Root,
4782
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4783
+ import_radix_ui5.Label.Root,
4683
4784
  {
4684
4785
  "data-slot": "label",
4685
4786
  className: cn("text-sm font-medium leading-5 text-foreground", className),
@@ -4688,40 +4789,10 @@ function Label({ className, ...props }) {
4688
4789
  );
4689
4790
  }
4690
4791
 
4691
- // src/ui/popover.tsx
4692
- var import_radix_ui5 = require("radix-ui");
4693
- var import_jsx_runtime11 = require("react/jsx-runtime");
4694
- function Popover({ ...props }) {
4695
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Root, { "data-slot": "popover", ...props });
4696
- }
4697
- function PopoverTrigger({ ...props }) {
4698
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
4699
- }
4700
- function PopoverContent({
4701
- className,
4702
- align = "start",
4703
- sideOffset = 6,
4704
- ...props
4705
- }) {
4706
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4707
- import_radix_ui5.Popover.Content,
4708
- {
4709
- "data-slot": "popover-content",
4710
- align,
4711
- sideOffset,
4712
- className: cn(
4713
- "z-[2147483647] w-[var(--radix-popover-trigger-width)] overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg outline-none",
4714
- className
4715
- ),
4716
- ...props
4717
- }
4718
- ) });
4719
- }
4720
-
4721
4792
  // src/ui/link-modal/UrlOrPageInput.tsx
4722
- var import_jsx_runtime12 = require("react/jsx-runtime");
4793
+ var import_jsx_runtime13 = require("react/jsx-runtime");
4723
4794
  function FieldChevron({ onClick }) {
4724
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4795
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4725
4796
  "button",
4726
4797
  {
4727
4798
  type: "button",
@@ -4729,7 +4800,7 @@ function FieldChevron({ onClick }) {
4729
4800
  onClick,
4730
4801
  "aria-label": "Open page list",
4731
4802
  tabIndex: -1,
4732
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconChevronDown, {})
4803
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconChevronDown, {})
4733
4804
  }
4734
4805
  );
4735
4806
  }
@@ -4768,12 +4839,12 @@ function UrlOrPageInput({
4768
4839
  "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]",
4769
4840
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
4770
4841
  );
4771
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
4772
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
4773
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
4774
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
4775
- selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
4776
- readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4842
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
4843
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
4844
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "relative w-full", children: [
4845
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
4846
+ selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
4847
+ readOnly ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4777
4848
  Input,
4778
4849
  {
4779
4850
  ref: inputRef,
@@ -4792,7 +4863,7 @@ function UrlOrPageInput({
4792
4863
  )
4793
4864
  }
4794
4865
  ),
4795
- selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4866
+ selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4796
4867
  "button",
4797
4868
  {
4798
4869
  type: "button",
@@ -4800,26 +4871,34 @@ function UrlOrPageInput({
4800
4871
  onMouseDown: clearSelection,
4801
4872
  "aria-label": "Clear selected page",
4802
4873
  tabIndex: -1,
4803
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconX, { "aria-hidden": true })
4874
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconX, { "aria-hidden": true })
4804
4875
  }
4805
4876
  ) : null,
4806
- !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
4807
- ] }) }),
4808
- filteredPages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
4809
- "button",
4877
+ !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
4878
+ ] }),
4879
+ dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4880
+ "div",
4810
4881
  {
4811
- type: "button",
4812
- className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
4813
- onClick: () => onPageSelect(page),
4814
- children: [
4815
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
4816
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate", children: page.title })
4817
- ]
4818
- },
4819
- page.path
4820
- )) })
4882
+ "data-ohw-link-page-dropdown": "",
4883
+ className: "absolute left-0 right-0 h-20 top-[calc(100%+4px)] z-10 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
4884
+ onMouseDown: (e) => e.preventDefault(),
4885
+ children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4886
+ "button",
4887
+ {
4888
+ type: "button",
4889
+ className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
4890
+ onClick: () => onPageSelect(page),
4891
+ children: [
4892
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
4893
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "truncate", children: page.title })
4894
+ ]
4895
+ },
4896
+ page.path
4897
+ ))
4898
+ }
4899
+ ) : null
4821
4900
  ] }),
4822
- urlError ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4901
+ urlError ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4823
4902
  ] });
4824
4903
  }
4825
4904
 
@@ -4987,7 +5066,7 @@ function useLinkModalState({
4987
5066
  }
4988
5067
 
4989
5068
  // src/ui/link-modal/LinkEditorPanel.tsx
4990
- var import_jsx_runtime13 = require("react/jsx-runtime");
5069
+ var import_jsx_runtime14 = require("react/jsx-runtime");
4991
5070
  function LinkEditorPanel({
4992
5071
  open = true,
4993
5072
  mode = "create",
@@ -5011,26 +5090,25 @@ function LinkEditorPanel({
5011
5090
  onSubmit
5012
5091
  });
5013
5092
  const isCancel = state.secondaryLabel === "Cancel";
5014
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
5015
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5093
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
5094
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5016
5095
  "button",
5017
5096
  {
5018
5097
  type: "button",
5019
5098
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
5020
- onClick: state.handleClose,
5021
5099
  "aria-label": "Close",
5022
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconX, { "aria-hidden": true })
5100
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconX, { "aria-hidden": true })
5023
5101
  }
5024
- ),
5025
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("header", { className: "flex w-full flex-col gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { className: "m-0 w-full break-words text-lg font-semibold leading-none tracking-tight text-card-foreground", children: state.title }) }),
5026
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5027
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5102
+ ) }),
5103
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5104
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5105
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5028
5106
  DestinationBreadcrumb,
5029
5107
  {
5030
5108
  pageTitle: state.selectedPage.title,
5031
5109
  sectionLabel: state.selectedSection.label
5032
5110
  }
5033
- ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5111
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5034
5112
  UrlOrPageInput,
5035
5113
  {
5036
5114
  value: state.searchValue,
@@ -5043,18 +5121,18 @@ function LinkEditorPanel({
5043
5121
  urlError: state.urlError
5044
5122
  }
5045
5123
  ),
5046
- state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5047
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5048
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5049
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5050
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "Pick a section this link should scroll to." })
5124
+ state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5125
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5126
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5127
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5128
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: "Pick a section this link should scroll to." })
5051
5129
  ] })
5052
5130
  ] }) : null,
5053
- state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5054
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5131
+ state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5132
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5055
5133
  ] }),
5056
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("footer", { className: "flex w-full items-center justify-end gap-2 px-6 pb-6", children: [
5057
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5134
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
5135
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5058
5136
  Button,
5059
5137
  {
5060
5138
  type: "button",
@@ -5069,7 +5147,7 @@ function LinkEditorPanel({
5069
5147
  children: state.secondaryLabel
5070
5148
  }
5071
5149
  ),
5072
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5150
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5073
5151
  Button,
5074
5152
  {
5075
5153
  type: "button",
@@ -5088,28 +5166,34 @@ function LinkEditorPanel({
5088
5166
  }
5089
5167
 
5090
5168
  // src/ui/link-modal/LinkPopover.tsx
5091
- var import_jsx_runtime14 = require("react/jsx-runtime");
5169
+ var import_jsx_runtime15 = require("react/jsx-runtime");
5092
5170
  function LinkPopover({
5093
- rect,
5094
- parentScroll,
5171
+ open = true,
5095
5172
  panelRef,
5173
+ portalContainer,
5174
+ onClose,
5096
5175
  ...editorProps
5097
5176
  }) {
5098
- const { top, left, transform } = calcPopoverPos(rect, parentScroll);
5099
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5100
- "div",
5177
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5178
+ Dialog2,
5101
5179
  {
5102
- ref: panelRef,
5103
- "data-ohw-link-popover-root": "",
5104
- "data-ohw-link-modal-root": "",
5105
- "data-ohw-bridge": "",
5106
- className: cn(
5107
- "pointer-events-auto fixed z-[2147483647] flex w-[483px] max-w-[calc(100vw-16px)] flex-col overflow-hidden",
5108
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none"
5109
- ),
5110
- style: { top, left, transform },
5111
- onMouseDown: (e) => e.stopPropagation(),
5112
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(LinkEditorPanel, { ...editorProps, open: true })
5180
+ open,
5181
+ onOpenChange: (next) => {
5182
+ if (!next) onClose?.();
5183
+ },
5184
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5185
+ DialogContent,
5186
+ {
5187
+ ref: panelRef,
5188
+ container: portalContainer,
5189
+ "data-ohw-link-popover-root": "",
5190
+ "data-ohw-link-modal-root": "",
5191
+ "data-ohw-bridge": "",
5192
+ showCloseButton: false,
5193
+ className: "gap-0 p-0 w-full md:w-md pointer-events-auto",
5194
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(LinkEditorPanel, { ...editorProps, open, onClose })
5195
+ }
5196
+ )
5113
5197
  }
5114
5198
  );
5115
5199
  }
@@ -5173,7 +5257,7 @@ function shouldUseDevFixtures() {
5173
5257
  }
5174
5258
 
5175
5259
  // src/ui/badge.tsx
5176
- var import_jsx_runtime15 = require("react/jsx-runtime");
5260
+ var import_jsx_runtime16 = require("react/jsx-runtime");
5177
5261
  var badgeVariants = cva(
5178
5262
  "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",
5179
5263
  {
@@ -5191,11 +5275,11 @@ var badgeVariants = cva(
5191
5275
  }
5192
5276
  );
5193
5277
  function Badge({ className, variant, ...props }) {
5194
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
5278
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
5195
5279
  }
5196
5280
 
5197
5281
  // src/OhhwellsBridge.tsx
5198
- var import_jsx_runtime16 = require("react/jsx-runtime");
5282
+ var import_jsx_runtime17 = require("react/jsx-runtime");
5199
5283
  var PRIMARY = "#0885FE";
5200
5284
  var IMAGE_FADE_MS = 300;
5201
5285
  function runOpacityFade(el, onDone) {
@@ -5369,7 +5453,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5369
5453
  const root = (0, import_client.createRoot)(container);
5370
5454
  (0, import_react_dom.flushSync)(() => {
5371
5455
  root.render(
5372
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5456
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5373
5457
  SchedulingWidget,
5374
5458
  {
5375
5459
  notifyOnConnect,
@@ -5417,6 +5501,20 @@ function applyLinkHref(el, val) {
5417
5501
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5418
5502
  if (anchor) anchor.setAttribute("href", val);
5419
5503
  }
5504
+ function getEditMeasureEl(editable) {
5505
+ return editable.closest("[data-ohw-href-key]") ?? editable;
5506
+ }
5507
+ function isDragHandleDisabled(el) {
5508
+ const carriers = [
5509
+ el.closest("[data-ohw-href-key]"),
5510
+ el.closest("[data-ohw-editable]")
5511
+ ];
5512
+ return carriers.some((carrier) => {
5513
+ if (!carrier) return false;
5514
+ const raw = carrier.getAttribute("data-ohw-drag-disabled");
5515
+ return raw === "true" || raw === "";
5516
+ });
5517
+ }
5420
5518
  function collectEditableNodes() {
5421
5519
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
5422
5520
  if (el.dataset.ohwEditable === "image") {
@@ -5455,7 +5553,7 @@ function applyLinkByKey(key, val) {
5455
5553
  }
5456
5554
  function isInsideLinkEditor(target) {
5457
5555
  return Boolean(
5458
- target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest('[data-slot="popover-content"]')
5556
+ target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
5459
5557
  );
5460
5558
  }
5461
5559
  function getHrefKeyFromElement(el) {
@@ -5466,6 +5564,23 @@ function getHrefKeyFromElement(el) {
5466
5564
  if (!key) return null;
5467
5565
  return { anchor, key };
5468
5566
  }
5567
+ function isNavbarButton(el) {
5568
+ return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
5569
+ }
5570
+ function getReorderHandleState(el) {
5571
+ const linkEl = el.closest("[data-ohw-href-key]");
5572
+ if (!linkEl || isNavbarButton(el)) return { key: null, disabled: false };
5573
+ return {
5574
+ key: linkEl.dataset.ohwHrefKey ?? null,
5575
+ disabled: isDragHandleDisabled(el)
5576
+ };
5577
+ }
5578
+ function clearHrefKeyHover(anchor) {
5579
+ anchor.removeAttribute("data-ohw-hovered");
5580
+ anchor.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5581
+ el.removeAttribute("data-ohw-hovered");
5582
+ });
5583
+ }
5469
5584
  function collectSections() {
5470
5585
  return collectSectionsFromDom();
5471
5586
  }
@@ -5589,6 +5704,8 @@ var ICONS = {
5589
5704
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
5590
5705
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
5591
5706
  };
5707
+ var TOOLBAR_PILL_PADDING = 2;
5708
+ var TOOLBAR_TOGGLE_GAP = 4;
5592
5709
  var TOOLBAR_GROUPS = [
5593
5710
  [
5594
5711
  { cmd: "bold", title: "Bold" },
@@ -5606,62 +5723,252 @@ var TOOLBAR_GROUPS = [
5606
5723
  { cmd: "insertOrderedList", title: "Numbered List" }
5607
5724
  ]
5608
5725
  ];
5609
- function GlowFrame({ rect, elRef }) {
5726
+ function EditGlowChrome({
5727
+ rect,
5728
+ elRef,
5729
+ reorderHrefKey,
5730
+ dragDisabled = false
5731
+ }) {
5610
5732
  const GAP = 6;
5611
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5733
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5612
5734
  "div",
5613
5735
  {
5614
5736
  ref: elRef,
5737
+ "data-ohw-edit-chrome": "",
5615
5738
  style: {
5616
5739
  position: "fixed",
5617
5740
  top: rect.top - GAP,
5618
5741
  left: rect.left - GAP,
5619
5742
  width: rect.width + GAP * 2,
5620
5743
  height: rect.height + GAP * 2,
5621
- border: "2px solid #0885FE",
5622
- borderRadius: 8,
5623
- boxShadow: "0 0 0 4px rgba(8, 133, 254, 0.12)",
5624
5744
  pointerEvents: "none",
5625
5745
  zIndex: 2147483646
5626
- }
5746
+ },
5747
+ children: [
5748
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5749
+ "div",
5750
+ {
5751
+ style: {
5752
+ position: "absolute",
5753
+ inset: 0,
5754
+ border: "2px solid #0885FE",
5755
+ borderRadius: 8,
5756
+ boxShadow: "0 0 0 4px rgba(8, 133, 254, 0.12)",
5757
+ pointerEvents: "none"
5758
+ }
5759
+ }
5760
+ ),
5761
+ reorderHrefKey && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5762
+ "div",
5763
+ {
5764
+ "data-ohw-drag-handle-container": "",
5765
+ "data-ohw-drag-disabled": dragDisabled ? "" : void 0,
5766
+ style: {
5767
+ position: "absolute",
5768
+ left: 0,
5769
+ top: "50%",
5770
+ transform: "translate(calc(-100% - 7px), -50%)",
5771
+ pointerEvents: dragDisabled ? "none" : "auto"
5772
+ },
5773
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5774
+ DragHandle,
5775
+ {
5776
+ "aria-label": `Reorder ${reorderHrefKey}`,
5777
+ disabled: dragDisabled,
5778
+ "aria-disabled": dragDisabled
5779
+ }
5780
+ )
5781
+ }
5782
+ )
5783
+ ]
5627
5784
  }
5628
5785
  );
5629
5786
  }
5630
- function calcToolbarPos(rect, parentScroll) {
5787
+ function getIframeVisibleClip(parentScroll) {
5788
+ if (!parentScroll) return null;
5789
+ const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
5790
+ const clipTop = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5791
+ const clipBottom = Math.min(
5792
+ window.innerHeight,
5793
+ visibleCanvasTop + canvasH - iframeOffsetTop
5794
+ );
5795
+ return { top: clipTop, bottom: Math.max(clipTop, clipBottom) };
5796
+ }
5797
+ function applyVisibleViewport(el, parentScroll) {
5798
+ const clip = getIframeVisibleClip(parentScroll);
5799
+ const top = clip?.top ?? 0;
5800
+ const height = clip ? clip.bottom - clip.top : window.innerHeight;
5801
+ el.style.top = `${top}px`;
5802
+ el.style.height = `${height}px`;
5803
+ }
5804
+ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
5805
+ const isAbove = transform.includes("translateY(-100%)");
5806
+ let visualTop = isAbove ? top - approxH : top;
5807
+ let visualBottom = isAbove ? top : top + approxH;
5808
+ const minTop = clip.top + gap;
5809
+ const maxBottom = clip.bottom - gap;
5810
+ if (visualTop >= minTop && visualBottom <= maxBottom) {
5811
+ return { top, transform };
5812
+ }
5813
+ const belowTop = rect.bottom + gap;
5814
+ if (belowTop + approxH <= maxBottom && belowTop >= minTop) {
5815
+ return { top: belowTop, transform: "translateX(-50%)" };
5816
+ }
5817
+ const aboveTop = rect.top - gap;
5818
+ if (aboveTop - approxH >= minTop && aboveTop <= maxBottom) {
5819
+ return { top: aboveTop, transform: "translateX(-50%) translateY(-100%)" };
5820
+ }
5821
+ if (belowTop + approxH <= maxBottom) {
5822
+ return { top: belowTop, transform: "translateX(-50%)" };
5823
+ }
5824
+ const pinnedTop = Math.min(maxBottom - approxH, Math.max(minTop, clip.top + gap));
5825
+ return { top: pinnedTop + approxH, transform: "translateX(-50%) translateY(-100%)" };
5826
+ }
5827
+ function calcToolbarPos(rect, parentScroll, approxW = 306) {
5631
5828
  const GAP = 8;
5632
- const APPROX_H = 36;
5633
- const APPROX_W = 330;
5829
+ const APPROX_H = 32;
5830
+ const APPROX_W = approxW;
5831
+ const clip = getIframeVisibleClip(parentScroll);
5832
+ const clipTop = clip?.top ?? 0;
5634
5833
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
5635
5834
  const visibleTop = rect.top - canvasTopInIframe;
5636
5835
  const visibleBottom = rect.bottom - canvasTopInIframe;
5637
5836
  const isTall = rect.height > APPROX_H + GAP;
5837
+ const spaceAbove = rect.top - (clipTop + GAP);
5838
+ const fitsAbove = spaceAbove >= APPROX_H + GAP;
5638
5839
  let top;
5639
5840
  let transform;
5640
- if (visibleTop > 0 || !isTall) {
5841
+ if ((visibleTop > 0 || !isTall) && fitsAbove) {
5641
5842
  top = rect.top - GAP;
5642
5843
  transform = "translateX(-50%) translateY(-100%)";
5844
+ } else if (visibleTop > 0 || !isTall) {
5845
+ top = rect.bottom + GAP;
5846
+ transform = "translateX(-50%)";
5643
5847
  } else if (visibleBottom > APPROX_H + GAP) {
5644
- top = canvasTopInIframe + GAP;
5848
+ top = clipTop + GAP;
5645
5849
  transform = "translateX(-50%)";
5646
5850
  } else {
5647
5851
  top = rect.bottom + GAP;
5648
5852
  transform = "translateX(-50%)";
5649
5853
  }
5854
+ if (clip) {
5855
+ const clamped = clampToolbarToClip(top, transform, rect, clip, APPROX_H, GAP);
5856
+ top = clamped.top;
5857
+ transform = clamped.transform;
5858
+ }
5650
5859
  const rawLeft = rect.left + rect.width / 2;
5651
5860
  const left = Math.max(GAP + APPROX_W / 2, Math.min(rawLeft, window.innerWidth - GAP - APPROX_W / 2));
5652
5861
  return { top, left, transform };
5653
5862
  }
5863
+ var EDIT_LINK_ICON = /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
5864
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
5865
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
5866
+ ] });
5654
5867
  function FloatingToolbar({
5655
5868
  rect,
5656
5869
  parentScroll,
5657
5870
  elRef,
5871
+ variant = "rich-text",
5658
5872
  onCommand,
5659
5873
  activeCommands,
5660
5874
  showEditLink,
5661
5875
  onEditLink
5662
5876
  }) {
5663
- const { top, left, transform } = calcToolbarPos(rect, parentScroll);
5664
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5877
+ const approxW = variant === "link-action" ? 112 : 306;
5878
+ const { top, left, transform } = calcToolbarPos(rect, parentScroll, approxW);
5879
+ if (variant === "link-action") {
5880
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5881
+ "div",
5882
+ {
5883
+ ref: elRef,
5884
+ "data-ohw-toolbar": "",
5885
+ style: {
5886
+ position: "fixed",
5887
+ top,
5888
+ left,
5889
+ transform,
5890
+ zIndex: 2147483647,
5891
+ background: "#fff",
5892
+ border: "1px solid #E7E5E4",
5893
+ borderRadius: 6,
5894
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
5895
+ display: "flex",
5896
+ alignItems: "center",
5897
+ padding: TOOLBAR_PILL_PADDING,
5898
+ gap: TOOLBAR_TOGGLE_GAP,
5899
+ fontFamily: "sans-serif",
5900
+ pointerEvents: "auto",
5901
+ whiteSpace: "nowrap"
5902
+ },
5903
+ onMouseDown: (e) => e.stopPropagation(),
5904
+ children: [
5905
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5906
+ "button",
5907
+ {
5908
+ type: "button",
5909
+ title: "Edit link",
5910
+ onMouseDown: (e) => {
5911
+ e.preventDefault();
5912
+ e.stopPropagation();
5913
+ onEditLink?.();
5914
+ },
5915
+ onClick: (e) => {
5916
+ e.preventDefault();
5917
+ e.stopPropagation();
5918
+ },
5919
+ onMouseEnter: (e) => {
5920
+ e.currentTarget.style.background = "#F5F5F4";
5921
+ },
5922
+ onMouseLeave: (e) => {
5923
+ e.currentTarget.style.background = "transparent";
5924
+ },
5925
+ style: {
5926
+ display: "flex",
5927
+ alignItems: "center",
5928
+ justifyContent: "center",
5929
+ border: "none",
5930
+ background: "transparent",
5931
+ borderRadius: 4,
5932
+ cursor: "pointer",
5933
+ color: "#1C1917",
5934
+ flexShrink: 0,
5935
+ padding: 6
5936
+ },
5937
+ children: EDIT_LINK_ICON
5938
+ }
5939
+ ),
5940
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5941
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5942
+ "button",
5943
+ {
5944
+ type: "button",
5945
+ title: "Coming soon",
5946
+ disabled: true,
5947
+ style: {
5948
+ display: "flex",
5949
+ alignItems: "center",
5950
+ justifyContent: "center",
5951
+ border: "none",
5952
+ background: "transparent",
5953
+ borderRadius: 4,
5954
+ cursor: "not-allowed",
5955
+ color: "#A8A29E",
5956
+ flexShrink: 0,
5957
+ padding: 6,
5958
+ opacity: 0.6
5959
+ },
5960
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: [
5961
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("circle", { cx: "5", cy: "12", r: "1.5" }),
5962
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("circle", { cx: "12", cy: "12", r: "1.5" }),
5963
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("circle", { cx: "19", cy: "12", r: "1.5" })
5964
+ ] })
5965
+ }
5966
+ )
5967
+ ]
5968
+ }
5969
+ );
5970
+ }
5971
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5665
5972
  "div",
5666
5973
  {
5667
5974
  ref: elRef,
@@ -5678,19 +5985,19 @@ function FloatingToolbar({
5678
5985
  boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
5679
5986
  display: "flex",
5680
5987
  alignItems: "center",
5681
- padding: 4,
5682
- gap: 6,
5988
+ padding: TOOLBAR_PILL_PADDING,
5989
+ gap: TOOLBAR_TOGGLE_GAP,
5683
5990
  fontFamily: "sans-serif",
5684
5991
  pointerEvents: "auto",
5685
5992
  whiteSpace: "nowrap"
5686
5993
  },
5687
5994
  onMouseDown: (e) => e.stopPropagation(),
5688
5995
  children: [
5689
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react5.default.Fragment, { children: [
5690
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5996
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react5.default.Fragment, { children: [
5997
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5691
5998
  btns.map((btn) => {
5692
5999
  const isActive = activeCommands.has(btn.cmd);
5693
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6000
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5694
6001
  "button",
5695
6002
  {
5696
6003
  title: btn.title,
@@ -5716,7 +6023,7 @@ function FloatingToolbar({
5716
6023
  flexShrink: 0,
5717
6024
  padding: 6
5718
6025
  },
5719
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6026
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5720
6027
  "svg",
5721
6028
  {
5722
6029
  width: "16",
@@ -5736,7 +6043,7 @@ function FloatingToolbar({
5736
6043
  );
5737
6044
  })
5738
6045
  ] }, gi)),
5739
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6046
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5740
6047
  "button",
5741
6048
  {
5742
6049
  type: "button",
@@ -5768,10 +6075,7 @@ function FloatingToolbar({
5768
6075
  flexShrink: 0,
5769
6076
  padding: 6
5770
6077
  },
5771
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
5772
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
5773
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
5774
- ] })
6078
+ children: EDIT_LINK_ICON
5775
6079
  }
5776
6080
  ) : null
5777
6081
  ]
@@ -5788,7 +6092,7 @@ function StateToggle({
5788
6092
  states,
5789
6093
  onStateChange
5790
6094
  }) {
5791
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6095
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5792
6096
  ToggleGroup,
5793
6097
  {
5794
6098
  "data-ohw-state-toggle": "",
@@ -5802,7 +6106,7 @@ function StateToggle({
5802
6106
  left: rect.right - 8,
5803
6107
  transform: "translateX(-100%)"
5804
6108
  },
5805
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6109
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
5806
6110
  }
5807
6111
  );
5808
6112
  }
@@ -5862,9 +6166,17 @@ function OhhwellsBridge() {
5862
6166
  const [fetchState, setFetchState] = (0, import_react5.useState)("idle");
5863
6167
  const autoSaveTimers = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
5864
6168
  const activeElRef = (0, import_react5.useRef)(null);
6169
+ const selectedElRef = (0, import_react5.useRef)(null);
5865
6170
  const originalContentRef = (0, import_react5.useRef)(null);
5866
6171
  const activeStateElRef = (0, import_react5.useRef)(null);
5867
6172
  const parentScrollRef = (0, import_react5.useRef)(null);
6173
+ const visibleViewportRef = (0, import_react5.useRef)(null);
6174
+ const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react5.useState)(null);
6175
+ const attachVisibleViewport = (0, import_react5.useCallback)((node) => {
6176
+ visibleViewportRef.current = node;
6177
+ setDialogPortalContainer(node);
6178
+ if (node) applyVisibleViewport(node, parentScrollRef.current);
6179
+ }, []);
5868
6180
  const toolbarElRef = (0, import_react5.useRef)(null);
5869
6181
  const glowElRef = (0, import_react5.useRef)(null);
5870
6182
  const hoveredImageRef = (0, import_react5.useRef)(null);
@@ -5877,6 +6189,10 @@ function OhhwellsBridge() {
5877
6189
  });
5878
6190
  const deactivateRef = (0, import_react5.useRef)(() => {
5879
6191
  });
6192
+ const selectRef = (0, import_react5.useRef)(() => {
6193
+ });
6194
+ const deselectRef = (0, import_react5.useRef)(() => {
6195
+ });
5880
6196
  const refreshActiveCommandsRef = (0, import_react5.useRef)(() => {
5881
6197
  });
5882
6198
  const postToParentRef = (0, import_react5.useRef)(postToParent);
@@ -5884,6 +6200,11 @@ function OhhwellsBridge() {
5884
6200
  const sectionsLoadedRef = (0, import_react5.useRef)(false);
5885
6201
  const pendingScheduleConfigRequests = (0, import_react5.useRef)([]);
5886
6202
  const [toolbarRect, setToolbarRect] = (0, import_react5.useState)(null);
6203
+ const [toolbarVariant, setToolbarVariant] = (0, import_react5.useState)("none");
6204
+ const toolbarVariantRef = (0, import_react5.useRef)("none");
6205
+ toolbarVariantRef.current = toolbarVariant;
6206
+ const [reorderHrefKey, setReorderHrefKey] = (0, import_react5.useState)(null);
6207
+ const [reorderDragDisabled, setReorderDragDisabled] = (0, import_react5.useState)(false);
5887
6208
  const [toggleState, setToggleState] = (0, import_react5.useState)(null);
5888
6209
  const [maxBadge, setMaxBadge] = (0, import_react5.useState)(null);
5889
6210
  const [activeCommands, setActiveCommands] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
@@ -5928,7 +6249,33 @@ function OhhwellsBridge() {
5928
6249
  hoveredImageRef.current = null;
5929
6250
  hoveredImageHasTextOverlapRef.current = false;
5930
6251
  }
6252
+ hoveredGapRef.current = null;
6253
+ setSectionGap(null);
5931
6254
  postToParent({ type: "ow:image-unhover" });
6255
+ postToParent({ type: "ow:link-modal-lock", locked: true });
6256
+ const html = document.documentElement;
6257
+ const body = document.body;
6258
+ const prevHtmlOverflow = html.style.overflow;
6259
+ const prevBodyOverflow = body.style.overflow;
6260
+ html.style.overflow = "hidden";
6261
+ body.style.overflow = "hidden";
6262
+ const preventBackgroundScroll = (e) => {
6263
+ const target = e.target;
6264
+ if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6265
+ return;
6266
+ }
6267
+ e.preventDefault();
6268
+ };
6269
+ const scrollOpts = { passive: false, capture: true };
6270
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6271
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6272
+ return () => {
6273
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6274
+ html.style.overflow = prevHtmlOverflow;
6275
+ body.style.overflow = prevBodyOverflow;
6276
+ document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6277
+ document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6278
+ };
5932
6279
  }, [linkPopover, postToParent]);
5933
6280
  (0, import_react5.useEffect)(() => {
5934
6281
  if (!isEditMode) return;
@@ -5963,8 +6310,8 @@ function OhhwellsBridge() {
5963
6310
  }, [isEditMode]);
5964
6311
  (0, import_react5.useEffect)(() => {
5965
6312
  const update = () => {
5966
- const el = activeElRef.current;
5967
- if (el) setToolbarRect(el.getBoundingClientRect());
6313
+ const el = activeElRef.current ?? selectedElRef.current;
6314
+ if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
5968
6315
  setToggleState((prev) => {
5969
6316
  if (!prev || !activeStateElRef.current) return prev;
5970
6317
  return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
@@ -6031,31 +6378,61 @@ function OhhwellsBridge() {
6031
6378
  }
6032
6379
  el.removeAttribute("contenteditable");
6033
6380
  activeElRef.current = null;
6034
- setToolbarRect(null);
6381
+ setReorderHrefKey(null);
6382
+ setReorderDragDisabled(false);
6383
+ if (!selectedElRef.current) {
6384
+ setToolbarRect(null);
6385
+ setToolbarVariant("none");
6386
+ }
6035
6387
  setMaxBadge(null);
6036
6388
  setActiveCommands(/* @__PURE__ */ new Set());
6037
6389
  setToolbarShowEditLink(false);
6038
6390
  postToParent({ type: "ow:exit-edit" });
6039
6391
  }, [postToParent]);
6392
+ const deselect = (0, import_react5.useCallback)(() => {
6393
+ selectedElRef.current = null;
6394
+ if (!activeElRef.current) {
6395
+ setToolbarRect(null);
6396
+ setToolbarVariant("none");
6397
+ }
6398
+ }, []);
6399
+ const select = (0, import_react5.useCallback)((anchor) => {
6400
+ if (!isNavbarButton(anchor)) return;
6401
+ if (activeElRef.current) deactivate();
6402
+ selectedElRef.current = anchor;
6403
+ clearHrefKeyHover(anchor);
6404
+ setToolbarVariant("link-action");
6405
+ setToolbarRect(anchor.getBoundingClientRect());
6406
+ setToolbarShowEditLink(false);
6407
+ setActiveCommands(/* @__PURE__ */ new Set());
6408
+ }, [deactivate]);
6040
6409
  const activate = (0, import_react5.useCallback)((el) => {
6041
6410
  if (activeElRef.current === el) return;
6411
+ selectedElRef.current = null;
6042
6412
  deactivate();
6043
6413
  if (hoveredImageRef.current) {
6044
6414
  hoveredImageRef.current = null;
6045
6415
  postToParentRef.current({ type: "ow:image-unhover" });
6046
6416
  }
6417
+ setToolbarVariant("rich-text");
6047
6418
  el.setAttribute("contenteditable", "true");
6048
6419
  el.removeAttribute("data-ohw-hovered");
6420
+ el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
6049
6421
  activeElRef.current = el;
6050
6422
  originalContentRef.current = el.innerHTML;
6051
6423
  el.focus();
6052
- setToolbarRect(el.getBoundingClientRect());
6053
6424
  setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
6425
+ const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
6426
+ setReorderHrefKey(reorderKey);
6427
+ setReorderDragDisabled(reorderDisabled);
6428
+ setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6054
6429
  postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
6055
6430
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
6056
6431
  }, [deactivate, postToParent]);
6057
6432
  activateRef.current = activate;
6058
6433
  deactivateRef.current = deactivate;
6434
+ selectRef.current = select;
6435
+ deselectRef.current = deselect;
6059
6436
  (0, import_react5.useLayoutEffect)(() => {
6060
6437
  if (!subdomain || isEditMode) {
6061
6438
  setFetchState("done");
@@ -6159,6 +6536,12 @@ function OhhwellsBridge() {
6159
6536
  (0, import_react5.useEffect)(() => {
6160
6537
  postToParent({ type: "ow:navigation", path: pathname });
6161
6538
  }, [pathname, postToParent]);
6539
+ (0, import_react5.useEffect)(() => {
6540
+ if (!isEditMode) return;
6541
+ setLinkPopover(null);
6542
+ deselectRef.current();
6543
+ deactivateRef.current();
6544
+ }, [pathname, isEditMode]);
6162
6545
  (0, import_react5.useEffect)(() => {
6163
6546
  if (!isEditMode) return;
6164
6547
  const measure = () => {
@@ -6264,17 +6647,22 @@ function OhhwellsBridge() {
6264
6647
  if (target.closest("[data-ohw-state-toggle]")) return;
6265
6648
  if (target.closest("[data-ohw-max-badge]")) return;
6266
6649
  if (isInsideLinkEditor(target)) return;
6650
+ if (target.closest('[data-ohw-edit-chrome], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
6651
+ e.preventDefault();
6652
+ e.stopPropagation();
6653
+ return;
6654
+ }
6267
6655
  const editable = target.closest("[data-ohw-editable]");
6268
6656
  if (editable) {
6269
6657
  if (editable.dataset.ohwEditable === "link") {
6270
6658
  e.preventDefault();
6271
6659
  e.stopPropagation();
6660
+ deselectRef.current();
6272
6661
  deactivateRef.current();
6273
6662
  bumpLinkPopoverGrace();
6274
6663
  setLinkPopoverRef.current({
6275
6664
  key: editable.dataset.ohwKey ?? "",
6276
- target: getLinkHref(editable),
6277
- rect: editable.getBoundingClientRect()
6665
+ target: getLinkHref(editable)
6278
6666
  });
6279
6667
  return;
6280
6668
  }
@@ -6284,11 +6672,31 @@ function OhhwellsBridge() {
6284
6672
  postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
6285
6673
  return;
6286
6674
  }
6675
+ const hrefCtx = getHrefKeyFromElement(editable);
6676
+ if (hrefCtx && isNavbarButton(hrefCtx.anchor)) {
6677
+ e.preventDefault();
6678
+ e.stopPropagation();
6679
+ const { anchor: anchor2 } = hrefCtx;
6680
+ if (selectedElRef.current === anchor2) {
6681
+ activateRef.current(editable);
6682
+ return;
6683
+ }
6684
+ selectRef.current(anchor2);
6685
+ return;
6686
+ }
6287
6687
  e.preventDefault();
6288
6688
  e.stopPropagation();
6289
6689
  activateRef.current(editable);
6290
6690
  return;
6291
6691
  }
6692
+ const hrefAnchor = target.closest("[data-ohw-href-key]");
6693
+ if (hrefAnchor && isNavbarButton(hrefAnchor)) {
6694
+ e.preventDefault();
6695
+ e.stopPropagation();
6696
+ if (selectedElRef.current === hrefAnchor) return;
6697
+ selectRef.current(hrefAnchor);
6698
+ return;
6699
+ }
6292
6700
  if (activeElRef.current) {
6293
6701
  const sel = window.getSelection();
6294
6702
  if (sel && !sel.isCollapsed) {
@@ -6303,24 +6711,35 @@ function OhhwellsBridge() {
6303
6711
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
6304
6712
  if (hrefCtx && (hrefCtx.anchor === target || hrefCtx.anchor.contains(target))) return;
6305
6713
  }
6714
+ if (linkPopoverOpenRef.current && selectedElRef.current) {
6715
+ const selected = selectedElRef.current;
6716
+ if (selected === target || selected.contains(target)) return;
6717
+ }
6306
6718
  if (linkPopoverOpenRef.current) {
6307
6719
  setLinkPopoverRef.current(null);
6308
6720
  return;
6309
6721
  }
6722
+ deselectRef.current();
6310
6723
  deactivateRef.current();
6311
6724
  };
6312
6725
  const handleMouseOver = (e) => {
6313
6726
  const editable = e.target.closest("[data-ohw-editable]");
6314
6727
  if (!editable) return;
6728
+ const selected = selectedElRef.current;
6729
+ if (selected && (selected === editable || selected.contains(editable))) return;
6315
6730
  if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image" && !editable.hasAttribute("contenteditable")) {
6316
- editable.setAttribute("data-ohw-hovered", "");
6731
+ const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
6732
+ hoverTarget.setAttribute("data-ohw-hovered", "");
6317
6733
  }
6318
6734
  };
6319
6735
  const handleMouseOut = (e) => {
6320
6736
  const editable = e.target.closest("[data-ohw-editable]");
6321
6737
  if (!editable) return;
6738
+ const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
6739
+ if (related?.closest("[data-ohw-drag-handle-container]")) return;
6322
6740
  if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image") {
6323
- editable.removeAttribute("data-ohw-hovered");
6741
+ const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
6742
+ hoverTarget.removeAttribute("data-ohw-hovered");
6324
6743
  }
6325
6744
  };
6326
6745
  const toProbeCoords = (clientX, clientY, fromParentViewport) => {
@@ -6435,11 +6854,20 @@ function OhhwellsBridge() {
6435
6854
  return;
6436
6855
  }
6437
6856
  }
6857
+ if (activeElRef.current) {
6858
+ if (hoveredImageRef.current) {
6859
+ hoveredImageRef.current = null;
6860
+ hoveredImageHasTextOverlapRef.current = false;
6861
+ resumeAnimTracks();
6862
+ postToParentRef.current({ type: "ow:image-unhover" });
6863
+ }
6864
+ return;
6865
+ }
6438
6866
  const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
6439
6867
  if (imgEl) {
6440
6868
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
6441
6869
  const topEl = document.elementFromPoint(x, y);
6442
- if (topEl?.closest("[data-ohw-toolbar]")) {
6870
+ if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
6443
6871
  if (hoveredImageRef.current) {
6444
6872
  hoveredImageRef.current = null;
6445
6873
  resumeAnimTracks();
@@ -6447,7 +6875,7 @@ function OhhwellsBridge() {
6447
6875
  }
6448
6876
  return;
6449
6877
  }
6450
- if (topEl?.closest("[data-ohw-link-popover-root]") || topEl?.closest("[data-ohw-link-modal-root]")) {
6878
+ if (topEl?.closest("[data-ohw-link-popover-root]") || topEl?.closest("[data-ohw-link-modal-root]") || topEl?.closest("[data-ohw-link-page-dropdown]")) {
6451
6879
  if (hoveredImageRef.current) {
6452
6880
  hoveredImageRef.current = null;
6453
6881
  hoveredImageHasTextOverlapRef.current = false;
@@ -6575,6 +7003,13 @@ function OhhwellsBridge() {
6575
7003
  }
6576
7004
  };
6577
7005
  const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
7006
+ if (linkPopoverOpenRef.current) {
7007
+ if (hoveredGapRef.current) {
7008
+ hoveredGapRef.current = null;
7009
+ setSectionGap(null);
7010
+ }
7011
+ return;
7012
+ }
6578
7013
  const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
6579
7014
  const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
6580
7015
  const ZONE = 20;
@@ -6725,7 +7160,7 @@ function OhhwellsBridge() {
6725
7160
  const el = e.target;
6726
7161
  const key = el.dataset.ohwKey;
6727
7162
  if (!key) return;
6728
- if (el === activeElRef.current) setToolbarRect(el.getBoundingClientRect());
7163
+ if (el === activeElRef.current) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6729
7164
  const maxLen = el.dataset.ohwMaxLength ? parseInt(el.dataset.ohwMaxLength, 10) : null;
6730
7165
  if (maxLen) {
6731
7166
  const current = el.innerText.replace(/\n$/, "").length;
@@ -6798,6 +7233,7 @@ function OhhwellsBridge() {
6798
7233
  setLinkPopoverRef.current(null);
6799
7234
  return;
6800
7235
  }
7236
+ deselectRef.current();
6801
7237
  deactivateRef.current();
6802
7238
  };
6803
7239
  window.addEventListener("message", handleDeactivate);
@@ -6806,6 +7242,10 @@ function OhhwellsBridge() {
6806
7242
  setLinkPopoverRef.current(null);
6807
7243
  return;
6808
7244
  }
7245
+ if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7246
+ deselectRef.current();
7247
+ return;
7248
+ }
6809
7249
  if (e.key !== "Escape") return;
6810
7250
  const el = activeElRef.current;
6811
7251
  if (el && originalContentRef.current !== null) {
@@ -6815,13 +7255,16 @@ function OhhwellsBridge() {
6815
7255
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: originalContentRef.current }] });
6816
7256
  }
6817
7257
  }
7258
+ deselectRef.current();
6818
7259
  deactivateRef.current();
6819
7260
  };
6820
7261
  const handleScroll = () => {
6821
- if (activeElRef.current) {
6822
- const r2 = activeElRef.current.getBoundingClientRect();
7262
+ const focusEl = activeElRef.current ?? selectedElRef.current;
7263
+ if (focusEl) {
7264
+ const r2 = focusEl.getBoundingClientRect();
6823
7265
  applyToolbarPos(r2);
6824
- setMaxBadge((prev) => prev ? { ...prev, rect: r2 } : null);
7266
+ setToolbarRect(r2);
7267
+ setMaxBadge((prev) => prev && activeElRef.current ? { ...prev, rect: r2 } : prev);
6825
7268
  }
6826
7269
  if (activeStateElRef.current) {
6827
7270
  const rect = activeStateElRef.current.getBoundingClientRect();
@@ -6946,8 +7389,9 @@ function OhhwellsBridge() {
6946
7389
  };
6947
7390
  const applyToolbarPos = (rect) => {
6948
7391
  const ps = parentScrollRef.current;
7392
+ const approxW = toolbarVariantRef.current === "link-action" ? 112 : 306;
6949
7393
  if (toolbarElRef.current) {
6950
- const { top, left, transform } = calcToolbarPos(rect, ps);
7394
+ const { top, left, transform } = calcToolbarPos(rect, ps, approxW);
6951
7395
  toolbarElRef.current.style.top = `${top}px`;
6952
7396
  toolbarElRef.current.style.left = `${left}px`;
6953
7397
  toolbarElRef.current.style.transform = transform;
@@ -6962,7 +7406,11 @@ function OhhwellsBridge() {
6962
7406
  if (e.data?.type !== "ow:parent-scroll") return;
6963
7407
  const { iframeOffsetTop, headerH, canvasH } = e.data;
6964
7408
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
6965
- if (activeElRef.current) applyToolbarPos(activeElRef.current.getBoundingClientRect());
7409
+ if (visibleViewportRef.current) {
7410
+ applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
7411
+ }
7412
+ const focusEl = activeElRef.current ?? selectedElRef.current;
7413
+ if (focusEl) applyToolbarPos(focusEl.getBoundingClientRect());
6966
7414
  };
6967
7415
  const handleClickAt = (e) => {
6968
7416
  if (e.data?.type !== "ow:click-at") return;
@@ -7012,6 +7460,12 @@ function OhhwellsBridge() {
7012
7460
  window.addEventListener("message", handleParentScroll);
7013
7461
  window.addEventListener("message", handlePointerSync);
7014
7462
  window.addEventListener("message", handleClickAt);
7463
+ const handleViewportResize = () => {
7464
+ if (visibleViewportRef.current) {
7465
+ applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
7466
+ }
7467
+ };
7468
+ window.addEventListener("resize", handleViewportResize, { passive: true });
7015
7469
  document.addEventListener("click", handleClick, true);
7016
7470
  document.addEventListener("paste", handlePaste, true);
7017
7471
  document.addEventListener("input", handleInput, true);
@@ -7049,6 +7503,7 @@ function OhhwellsBridge() {
7049
7503
  window.removeEventListener("message", handleAnimLock);
7050
7504
  window.removeEventListener("message", handleCanvasHeight);
7051
7505
  window.removeEventListener("message", handleParentScroll);
7506
+ window.removeEventListener("resize", handleViewportResize);
7052
7507
  window.removeEventListener("message", handlePointerSync);
7053
7508
  window.removeEventListener("message", handleClickAt);
7054
7509
  window.removeEventListener("message", handleHydrate);
@@ -7113,7 +7568,7 @@ function OhhwellsBridge() {
7113
7568
  const handleCommand = (0, import_react5.useCallback)((cmd) => {
7114
7569
  document.execCommand(cmd, false);
7115
7570
  activeElRef.current?.focus();
7116
- if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
7571
+ if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
7117
7572
  refreshActiveCommandsRef.current();
7118
7573
  }, []);
7119
7574
  const handleStateChange = (0, import_react5.useCallback)((state) => {
@@ -7136,10 +7591,22 @@ function OhhwellsBridge() {
7136
7591
  bumpLinkPopoverGrace();
7137
7592
  setLinkPopover({
7138
7593
  key: hrefCtx.key,
7139
- target: getLinkHref(hrefCtx.anchor),
7140
- rect: hrefCtx.anchor.getBoundingClientRect()
7594
+ target: getLinkHref(hrefCtx.anchor)
7141
7595
  });
7142
- }, []);
7596
+ deactivate();
7597
+ }, [deactivate]);
7598
+ const openLinkPopoverForSelected = (0, import_react5.useCallback)(() => {
7599
+ const anchor = selectedElRef.current;
7600
+ if (!anchor) return;
7601
+ const key = anchor.getAttribute("data-ohw-href-key");
7602
+ if (!key) return;
7603
+ bumpLinkPopoverGrace();
7604
+ setLinkPopover({
7605
+ key,
7606
+ target: getLinkHref(anchor)
7607
+ });
7608
+ deselect();
7609
+ }, [deselect]);
7143
7610
  const handleLinkPopoverSubmit = (0, import_react5.useCallback)(
7144
7611
  (target) => {
7145
7612
  if (!linkPopover) return;
@@ -7154,12 +7621,22 @@ function OhhwellsBridge() {
7154
7621
  const currentSections = sectionsByPath[pathname] ?? [];
7155
7622
  linkPopoverOpenRef.current = linkPopover !== null;
7156
7623
  return bridgeRoot ? (0, import_react_dom2.createPortal)(
7157
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
7158
- toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
7159
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
7160
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7624
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
7625
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
7626
+ toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
7627
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7628
+ EditGlowChrome,
7629
+ {
7630
+ rect: toolbarRect,
7631
+ elRef: glowElRef,
7632
+ reorderHrefKey,
7633
+ dragDisabled: reorderDragDisabled
7634
+ }
7635
+ ),
7636
+ toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7161
7637
  FloatingToolbar,
7162
7638
  {
7639
+ variant: "rich-text",
7163
7640
  rect: toolbarRect,
7164
7641
  parentScroll: parentScrollRef.current,
7165
7642
  elRef: toolbarElRef,
@@ -7168,9 +7645,21 @@ function OhhwellsBridge() {
7168
7645
  showEditLink,
7169
7646
  onEditLink: openLinkPopoverForActive
7170
7647
  }
7648
+ ),
7649
+ toolbarVariant === "link-action" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7650
+ FloatingToolbar,
7651
+ {
7652
+ variant: "link-action",
7653
+ rect: toolbarRect,
7654
+ parentScroll: parentScrollRef.current,
7655
+ elRef: toolbarElRef,
7656
+ onCommand: handleCommand,
7657
+ activeCommands,
7658
+ onEditLink: openLinkPopoverForSelected
7659
+ }
7171
7660
  )
7172
7661
  ] }),
7173
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7662
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
7174
7663
  "div",
7175
7664
  {
7176
7665
  "data-ohw-max-badge": "",
@@ -7196,7 +7685,7 @@ function OhhwellsBridge() {
7196
7685
  ]
7197
7686
  }
7198
7687
  ),
7199
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7688
+ toggleState && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7200
7689
  StateToggle,
7201
7690
  {
7202
7691
  rect: toggleState.rect,
@@ -7205,15 +7694,15 @@ function OhhwellsBridge() {
7205
7694
  onStateChange: handleStateChange
7206
7695
  }
7207
7696
  ),
7208
- sectionGap && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7697
+ sectionGap && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
7209
7698
  "div",
7210
7699
  {
7211
7700
  "data-ohw-section-insert-line": "",
7212
7701
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
7213
7702
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
7214
7703
  children: [
7215
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
7216
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7704
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
7705
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7217
7706
  Badge,
7218
7707
  {
7219
7708
  className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
@@ -7226,16 +7715,15 @@ function OhhwellsBridge() {
7226
7715
  children: "Add Section"
7227
7716
  }
7228
7717
  ),
7229
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
7718
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
7230
7719
  ]
7231
7720
  }
7232
7721
  ),
7233
- linkPopover ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7722
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7234
7723
  LinkPopover,
7235
7724
  {
7236
- rect: linkPopover.rect,
7237
- parentScroll: parentScrollRef.current,
7238
7725
  panelRef: linkPopoverPanelRef,
7726
+ portalContainer: dialogPortalContainer,
7239
7727
  open: true,
7240
7728
  mode: "edit",
7241
7729
  pages: sitePages,
@@ -7244,14 +7732,41 @@ function OhhwellsBridge() {
7244
7732
  initialTarget: linkPopover.target,
7245
7733
  onClose: closeLinkPopover,
7246
7734
  onSubmit: handleLinkPopoverSubmit
7735
+ },
7736
+ `${linkPopover.key}-${pathname}`
7737
+ ) : null,
7738
+ sectionGap && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
7739
+ "div",
7740
+ {
7741
+ "data-ohw-section-insert-line": "",
7742
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
7743
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
7744
+ children: [
7745
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
7746
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
7747
+ Badge,
7748
+ {
7749
+ className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
7750
+ onClick: () => {
7751
+ window.parent.postMessage(
7752
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
7753
+ "*"
7754
+ );
7755
+ },
7756
+ children: "Add Section"
7757
+ }
7758
+ ),
7759
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
7760
+ ]
7247
7761
  }
7248
- ) : null
7762
+ )
7249
7763
  ] }),
7250
7764
  bridgeRoot
7251
7765
  ) : null;
7252
7766
  }
7253
7767
  // Annotate the CommonJS export names for ESM import in node:
7254
7768
  0 && (module.exports = {
7769
+ DragHandle,
7255
7770
  LinkEditorPanel,
7256
7771
  LinkPopover,
7257
7772
  OhhwellsBridge,
@@ -7262,6 +7777,7 @@ function OhhwellsBridge() {
7262
7777
  buildTarget,
7263
7778
  filterAvailablePages,
7264
7779
  getEditModeInitialState,
7780
+ isEditSessionActive,
7265
7781
  isValidUrl,
7266
7782
  parseTarget,
7267
7783
  toggleVariants,