@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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React5, { useCallback as useCallback2, useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState5 } from "react";
4
+ import React7, { useCallback as useCallback2, useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState5 } from "react";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { flushSync } from "react-dom";
7
7
 
@@ -4163,6 +4163,34 @@ function ToggleGroupItem({
4163
4163
  );
4164
4164
  }
4165
4165
 
4166
+ // src/ui/drag-handle.tsx
4167
+ import * as React3 from "react";
4168
+ import { GripVertical } from "lucide-react";
4169
+ import { jsx as jsx5 } from "react/jsx-runtime";
4170
+ var DragHandle = React3.forwardRef(
4171
+ ({ className, type = "button", ...props }, ref) => {
4172
+ return /* @__PURE__ */ jsx5(
4173
+ "button",
4174
+ {
4175
+ ref,
4176
+ type,
4177
+ "data-slot": "drag-handle",
4178
+ className: cn(
4179
+ "inline-flex h-7 w-4 shrink-0 items-center justify-center rounded-md transition-all duration-200",
4180
+ "bg-white border border-transparent text-stone-500 shadow-md cursor-grab",
4181
+ "enabled:hover:border enabled:hover:border-stone-200 enabled:hover:text-stone-950",
4182
+ "enabled:active:border enabled:active:border-primary enabled:active:bg-primary-50 enabled:active:text-stone-950 enabled:active:shadow enabled:active:cursor-grabbing",
4183
+ "disabled:cursor-not-allowed disabled:opacity-40 disabled:text-stone-950 disabled:pointer-events-none",
4184
+ className
4185
+ ),
4186
+ ...props,
4187
+ children: /* @__PURE__ */ jsx5(GripVertical, { className: "size-4 shrink-0", "aria-hidden": "true" })
4188
+ }
4189
+ );
4190
+ }
4191
+ );
4192
+ DragHandle.displayName = "DragHandle";
4193
+
4166
4194
  // src/OhhwellsBridge.tsx
4167
4195
  import { createPortal } from "react-dom";
4168
4196
  import { usePathname, useRouter, useSearchParams } from "next/navigation";
@@ -4441,61 +4469,9 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
4441
4469
  tick();
4442
4470
  }
4443
4471
 
4444
- // src/ui/link-modal/calcPopoverPos.ts
4445
- function calcPopoverPos(rect, parentScroll, approxW = 483, approxH = 320) {
4446
- const GAP = 8;
4447
- const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
4448
- const visibleTop = rect.top - canvasTopInIframe;
4449
- let top = rect.top - GAP;
4450
- let transform = "translateX(-50%) translateY(-100%)";
4451
- if (visibleTop < approxH + GAP) {
4452
- top = rect.bottom + GAP;
4453
- transform = "translateX(-50%)";
4454
- }
4455
- const rawLeft = rect.left + rect.width / 2;
4456
- const left = Math.max(GAP + approxW / 2, Math.min(rawLeft, window.innerWidth - GAP - approxW / 2));
4457
- return { top, left, transform };
4458
- }
4459
-
4460
- // src/ui/button.tsx
4461
- import * as React3 from "react";
4462
- import { Slot } from "radix-ui";
4463
- import { jsx as jsx5 } from "react/jsx-runtime";
4464
- var buttonVariants = cva(
4465
- "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",
4466
- {
4467
- variants: {
4468
- variant: {
4469
- default: "bg-primary text-primary-foreground hover:opacity-90",
4470
- outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4471
- ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4472
- },
4473
- size: {
4474
- default: "h-9",
4475
- sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
4476
- }
4477
- },
4478
- defaultVariants: {
4479
- variant: "default",
4480
- size: "default"
4481
- }
4482
- }
4483
- );
4484
- var Button = React3.forwardRef(
4485
- ({ className, variant, size, asChild = false, ...props }, ref) => {
4486
- const Comp = asChild ? Slot.Root : "button";
4487
- return /* @__PURE__ */ jsx5(
4488
- Comp,
4489
- {
4490
- ref,
4491
- "data-slot": "button",
4492
- className: cn(buttonVariants({ variant, size, className })),
4493
- ...props
4494
- }
4495
- );
4496
- }
4497
- );
4498
- Button.displayName = "Button";
4472
+ // src/ui/dialog.tsx
4473
+ import * as React4 from "react";
4474
+ import { Dialog as DialogPrimitive } from "radix-ui";
4499
4475
 
4500
4476
  // src/ui/icons.tsx
4501
4477
  import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -4535,38 +4511,161 @@ function IconSection({ className, ...props }) {
4535
4511
  ] });
4536
4512
  }
4537
4513
 
4538
- // src/ui/link-modal/DestinationBreadcrumb.tsx
4514
+ // src/ui/dialog.tsx
4539
4515
  import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
4516
+ function Dialog2({ ...props }) {
4517
+ return /* @__PURE__ */ jsx7(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
4518
+ }
4519
+ function DialogPortal({ ...props }) {
4520
+ return /* @__PURE__ */ jsx7(DialogPrimitive.Portal, { ...props });
4521
+ }
4522
+ function DialogOverlay({
4523
+ className,
4524
+ ...props
4525
+ }) {
4526
+ return /* @__PURE__ */ jsx7(
4527
+ DialogPrimitive.Overlay,
4528
+ {
4529
+ "data-slot": "dialog-overlay",
4530
+ "data-ohw-link-modal-root": "",
4531
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
4532
+ ...props
4533
+ }
4534
+ );
4535
+ }
4536
+ var DialogContent = React4.forwardRef(({ className, children, showCloseButton = true, container, ...props }, ref) => {
4537
+ const positionMode = container ? "absolute" : "fixed";
4538
+ return /* @__PURE__ */ jsxs4(DialogPortal, { container: container ?? void 0, children: [
4539
+ /* @__PURE__ */ jsx7(DialogOverlay, { className: cn(positionMode, "inset-0") }),
4540
+ /* @__PURE__ */ jsxs4(
4541
+ DialogPrimitive.Content,
4542
+ {
4543
+ ref,
4544
+ "data-slot": "dialog-content",
4545
+ className: cn(
4546
+ positionMode,
4547
+ "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",
4548
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
4549
+ container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
4550
+ className
4551
+ ),
4552
+ onMouseDown: (e) => e.stopPropagation(),
4553
+ ...props,
4554
+ children: [
4555
+ children,
4556
+ showCloseButton ? /* @__PURE__ */ jsx7(
4557
+ DialogPrimitive.Close,
4558
+ {
4559
+ type: "button",
4560
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4561
+ "aria-label": "Close",
4562
+ children: /* @__PURE__ */ jsx7(IconX, { "aria-hidden": true })
4563
+ }
4564
+ ) : null
4565
+ ]
4566
+ }
4567
+ )
4568
+ ] });
4569
+ });
4570
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
4571
+ function DialogHeader({ className, ...props }) {
4572
+ return /* @__PURE__ */ jsx7("div", { className: cn("flex flex-col gap-1.5", className), ...props });
4573
+ }
4574
+ function DialogFooter({ className, ...props }) {
4575
+ return /* @__PURE__ */ jsx7("div", { className: cn("flex items-center justify-end gap-2", className), ...props });
4576
+ }
4577
+ var DialogTitle = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx7(
4578
+ DialogPrimitive.Title,
4579
+ {
4580
+ ref,
4581
+ className: cn("text-lg font-semibold leading-none tracking-tight text-card-foreground", className),
4582
+ ...props
4583
+ }
4584
+ ));
4585
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
4586
+ var DialogDescription = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx7(
4587
+ DialogPrimitive.Description,
4588
+ {
4589
+ ref,
4590
+ className: cn("text-sm text-muted-foreground", className),
4591
+ ...props
4592
+ }
4593
+ ));
4594
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
4595
+ var DialogClose = DialogPrimitive.Close;
4596
+
4597
+ // src/ui/button.tsx
4598
+ import * as React5 from "react";
4599
+ import { Slot } from "radix-ui";
4600
+ import { jsx as jsx8 } from "react/jsx-runtime";
4601
+ var buttonVariants = cva(
4602
+ "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",
4603
+ {
4604
+ variants: {
4605
+ variant: {
4606
+ default: "bg-primary text-primary-foreground hover:opacity-90",
4607
+ outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4608
+ ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4609
+ },
4610
+ size: {
4611
+ default: "h-9",
4612
+ sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
4613
+ }
4614
+ },
4615
+ defaultVariants: {
4616
+ variant: "default",
4617
+ size: "default"
4618
+ }
4619
+ }
4620
+ );
4621
+ var Button = React5.forwardRef(
4622
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
4623
+ const Comp = asChild ? Slot.Root : "button";
4624
+ return /* @__PURE__ */ jsx8(
4625
+ Comp,
4626
+ {
4627
+ ref,
4628
+ "data-slot": "button",
4629
+ className: cn(buttonVariants({ variant, size, className })),
4630
+ ...props
4631
+ }
4632
+ );
4633
+ }
4634
+ );
4635
+ Button.displayName = "Button";
4636
+
4637
+ // src/ui/link-modal/DestinationBreadcrumb.tsx
4638
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
4540
4639
  function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
4541
- return /* @__PURE__ */ jsxs4("div", { className: "flex w-full flex-col gap-2", children: [
4542
- /* @__PURE__ */ jsx7("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
4543
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3", children: [
4544
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
4545
- /* @__PURE__ */ jsx7(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4546
- /* @__PURE__ */ jsx7("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
4640
+ return /* @__PURE__ */ jsxs5("div", { className: "flex w-full flex-col gap-2", children: [
4641
+ /* @__PURE__ */ jsx9("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
4642
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3", children: [
4643
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
4644
+ /* @__PURE__ */ jsx9(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4645
+ /* @__PURE__ */ jsx9("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
4547
4646
  ] }),
4548
- /* @__PURE__ */ jsx7(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
4549
- /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
4550
- /* @__PURE__ */ jsx7(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4551
- /* @__PURE__ */ jsx7("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
4647
+ /* @__PURE__ */ jsx9(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
4648
+ /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
4649
+ /* @__PURE__ */ jsx9(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4650
+ /* @__PURE__ */ jsx9("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
4552
4651
  ] })
4553
4652
  ] })
4554
4653
  ] });
4555
4654
  }
4556
4655
 
4557
4656
  // src/ui/link-modal/SectionTreeItem.tsx
4558
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
4657
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
4559
4658
  function SectionTreeItem({ section, onSelect, selected }) {
4560
4659
  const interactive = Boolean(onSelect);
4561
- return /* @__PURE__ */ jsxs5("div", { className: "flex h-9 w-full items-end pl-3", children: [
4562
- /* @__PURE__ */ jsx8(
4660
+ return /* @__PURE__ */ jsxs6("div", { className: "flex h-9 w-full items-end pl-3", children: [
4661
+ /* @__PURE__ */ jsx10(
4563
4662
  "div",
4564
4663
  {
4565
4664
  className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
4566
4665
  "aria-hidden": true
4567
4666
  }
4568
4667
  ),
4569
- /* @__PURE__ */ jsxs5(
4668
+ /* @__PURE__ */ jsxs6(
4570
4669
  "div",
4571
4670
  {
4572
4671
  role: interactive ? "button" : void 0,
@@ -4584,8 +4683,8 @@ function SectionTreeItem({ section, onSelect, selected }) {
4584
4683
  interactive && selected && "border-primary"
4585
4684
  ),
4586
4685
  children: [
4587
- /* @__PURE__ */ jsx8(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4588
- /* @__PURE__ */ jsx8("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
4686
+ /* @__PURE__ */ jsx10(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4687
+ /* @__PURE__ */ jsx10("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
4589
4688
  ]
4590
4689
  }
4591
4690
  )
@@ -4593,11 +4692,11 @@ function SectionTreeItem({ section, onSelect, selected }) {
4593
4692
  }
4594
4693
  function SectionPickerList({ sections, onSelect }) {
4595
4694
  if (sections.length === 0) {
4596
- return /* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
4695
+ return /* @__PURE__ */ jsx10("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
4597
4696
  }
4598
- return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", children: [
4599
- /* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
4600
- sections.map((section) => /* @__PURE__ */ jsx8(SectionTreeItem, { section, onSelect }, section.id))
4697
+ return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1", children: [
4698
+ /* @__PURE__ */ jsx10("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
4699
+ sections.map((section) => /* @__PURE__ */ jsx10(SectionTreeItem, { section, onSelect }, section.id))
4601
4700
  ] });
4602
4701
  }
4603
4702
 
@@ -4605,11 +4704,11 @@ function SectionPickerList({ sections, onSelect }) {
4605
4704
  import { useId as useId2, useRef as useRef2, useState as useState3 } from "react";
4606
4705
 
4607
4706
  // src/ui/input.tsx
4608
- import * as React4 from "react";
4609
- import { jsx as jsx9 } from "react/jsx-runtime";
4610
- var Input = React4.forwardRef(
4707
+ import * as React6 from "react";
4708
+ import { jsx as jsx11 } from "react/jsx-runtime";
4709
+ var Input = React6.forwardRef(
4611
4710
  ({ className, type, ...props }, ref) => {
4612
- return /* @__PURE__ */ jsx9(
4711
+ return /* @__PURE__ */ jsx11(
4613
4712
  "input",
4614
4713
  {
4615
4714
  type,
@@ -4628,9 +4727,9 @@ Input.displayName = "Input";
4628
4727
 
4629
4728
  // src/ui/label.tsx
4630
4729
  import { Label as LabelPrimitive } from "radix-ui";
4631
- import { jsx as jsx10 } from "react/jsx-runtime";
4730
+ import { jsx as jsx12 } from "react/jsx-runtime";
4632
4731
  function Label({ className, ...props }) {
4633
- return /* @__PURE__ */ jsx10(
4732
+ return /* @__PURE__ */ jsx12(
4634
4733
  LabelPrimitive.Root,
4635
4734
  {
4636
4735
  "data-slot": "label",
@@ -4640,40 +4739,10 @@ function Label({ className, ...props }) {
4640
4739
  );
4641
4740
  }
4642
4741
 
4643
- // src/ui/popover.tsx
4644
- import { Popover as PopoverPrimitive } from "radix-ui";
4645
- import { jsx as jsx11 } from "react/jsx-runtime";
4646
- function Popover({ ...props }) {
4647
- return /* @__PURE__ */ jsx11(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
4648
- }
4649
- function PopoverTrigger({ ...props }) {
4650
- return /* @__PURE__ */ jsx11(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
4651
- }
4652
- function PopoverContent({
4653
- className,
4654
- align = "start",
4655
- sideOffset = 6,
4656
- ...props
4657
- }) {
4658
- return /* @__PURE__ */ jsx11(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx11(
4659
- PopoverPrimitive.Content,
4660
- {
4661
- "data-slot": "popover-content",
4662
- align,
4663
- sideOffset,
4664
- className: cn(
4665
- "z-[2147483647] w-[var(--radix-popover-trigger-width)] overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg outline-none",
4666
- className
4667
- ),
4668
- ...props
4669
- }
4670
- ) });
4671
- }
4672
-
4673
4742
  // src/ui/link-modal/UrlOrPageInput.tsx
4674
- import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
4743
+ import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
4675
4744
  function FieldChevron({ onClick }) {
4676
- return /* @__PURE__ */ jsx12(
4745
+ return /* @__PURE__ */ jsx13(
4677
4746
  "button",
4678
4747
  {
4679
4748
  type: "button",
@@ -4681,7 +4750,7 @@ function FieldChevron({ onClick }) {
4681
4750
  onClick,
4682
4751
  "aria-label": "Open page list",
4683
4752
  tabIndex: -1,
4684
- children: /* @__PURE__ */ jsx12(IconChevronDown, {})
4753
+ children: /* @__PURE__ */ jsx13(IconChevronDown, {})
4685
4754
  }
4686
4755
  );
4687
4756
  }
@@ -4720,12 +4789,12 @@ function UrlOrPageInput({
4720
4789
  "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]",
4721
4790
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
4722
4791
  );
4723
- return /* @__PURE__ */ jsxs6("div", { className: "flex w-full flex-col gap-2 p-0", children: [
4724
- /* @__PURE__ */ jsx12(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
4725
- /* @__PURE__ */ jsxs6(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
4726
- /* @__PURE__ */ jsx12(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs6("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
4727
- selectedPage ? /* @__PURE__ */ jsx12("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx12(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
4728
- readOnly ? /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx12(
4792
+ return /* @__PURE__ */ jsxs7("div", { className: "flex w-full flex-col gap-2 p-0", children: [
4793
+ /* @__PURE__ */ jsx13(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
4794
+ /* @__PURE__ */ jsxs7("div", { className: "relative w-full", children: [
4795
+ /* @__PURE__ */ jsxs7("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
4796
+ selectedPage ? /* @__PURE__ */ jsx13("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx13(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
4797
+ readOnly ? /* @__PURE__ */ jsx13("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx13(
4729
4798
  Input,
4730
4799
  {
4731
4800
  ref: inputRef,
@@ -4744,7 +4813,7 @@ function UrlOrPageInput({
4744
4813
  )
4745
4814
  }
4746
4815
  ),
4747
- selectedPage && !readOnly ? /* @__PURE__ */ jsx12(
4816
+ selectedPage && !readOnly ? /* @__PURE__ */ jsx13(
4748
4817
  "button",
4749
4818
  {
4750
4819
  type: "button",
@@ -4752,26 +4821,34 @@ function UrlOrPageInput({
4752
4821
  onMouseDown: clearSelection,
4753
4822
  "aria-label": "Clear selected page",
4754
4823
  tabIndex: -1,
4755
- children: /* @__PURE__ */ jsx12(IconX, { "aria-hidden": true })
4824
+ children: /* @__PURE__ */ jsx13(IconX, { "aria-hidden": true })
4756
4825
  }
4757
4826
  ) : null,
4758
- !readOnly ? /* @__PURE__ */ jsx12(FieldChevron, { onClick: toggleDropdown }) : null
4759
- ] }) }),
4760
- filteredPages.length > 0 && /* @__PURE__ */ jsx12(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ jsxs6(
4761
- "button",
4827
+ !readOnly ? /* @__PURE__ */ jsx13(FieldChevron, { onClick: toggleDropdown }) : null
4828
+ ] }),
4829
+ dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ jsx13(
4830
+ "div",
4762
4831
  {
4763
- type: "button",
4764
- 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",
4765
- onClick: () => onPageSelect(page),
4766
- children: [
4767
- /* @__PURE__ */ jsx12(IconFile, { className: "shrink-0", "aria-hidden": true }),
4768
- /* @__PURE__ */ jsx12("span", { className: "truncate", children: page.title })
4769
- ]
4770
- },
4771
- page.path
4772
- )) })
4832
+ "data-ohw-link-page-dropdown": "",
4833
+ 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",
4834
+ onMouseDown: (e) => e.preventDefault(),
4835
+ children: filteredPages.map((page) => /* @__PURE__ */ jsxs7(
4836
+ "button",
4837
+ {
4838
+ type: "button",
4839
+ 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",
4840
+ onClick: () => onPageSelect(page),
4841
+ children: [
4842
+ /* @__PURE__ */ jsx13(IconFile, { className: "shrink-0", "aria-hidden": true }),
4843
+ /* @__PURE__ */ jsx13("span", { className: "truncate", children: page.title })
4844
+ ]
4845
+ },
4846
+ page.path
4847
+ ))
4848
+ }
4849
+ ) : null
4773
4850
  ] }),
4774
- urlError ? /* @__PURE__ */ jsx12("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4851
+ urlError ? /* @__PURE__ */ jsx13("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4775
4852
  ] });
4776
4853
  }
4777
4854
 
@@ -4939,7 +5016,7 @@ function useLinkModalState({
4939
5016
  }
4940
5017
 
4941
5018
  // src/ui/link-modal/LinkEditorPanel.tsx
4942
- import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
5019
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
4943
5020
  function LinkEditorPanel({
4944
5021
  open = true,
4945
5022
  mode = "create",
@@ -4963,26 +5040,25 @@ function LinkEditorPanel({
4963
5040
  onSubmit
4964
5041
  });
4965
5042
  const isCancel = state.secondaryLabel === "Cancel";
4966
- return /* @__PURE__ */ jsxs7(Fragment2, { children: [
4967
- /* @__PURE__ */ jsx13(
5043
+ return /* @__PURE__ */ jsxs8(Fragment2, { children: [
5044
+ /* @__PURE__ */ jsx14(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx14(
4968
5045
  "button",
4969
5046
  {
4970
5047
  type: "button",
4971
5048
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4972
- onClick: state.handleClose,
4973
5049
  "aria-label": "Close",
4974
- children: /* @__PURE__ */ jsx13(IconX, { "aria-hidden": true })
5050
+ children: /* @__PURE__ */ jsx14(IconX, { "aria-hidden": true })
4975
5051
  }
4976
- ),
4977
- /* @__PURE__ */ jsx13("header", { className: "flex w-full flex-col gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx13("h2", { className: "m-0 w-full break-words text-lg font-semibold leading-none tracking-tight text-card-foreground", children: state.title }) }),
4978
- /* @__PURE__ */ jsxs7("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
4979
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx13(
5052
+ ) }),
5053
+ /* @__PURE__ */ jsx14(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx14(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5054
+ /* @__PURE__ */ jsxs8("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5055
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx14(
4980
5056
  DestinationBreadcrumb,
4981
5057
  {
4982
5058
  pageTitle: state.selectedPage.title,
4983
5059
  sectionLabel: state.selectedSection.label
4984
5060
  }
4985
- ) : /* @__PURE__ */ jsx13(
5061
+ ) : /* @__PURE__ */ jsx14(
4986
5062
  UrlOrPageInput,
4987
5063
  {
4988
5064
  value: state.searchValue,
@@ -4995,18 +5071,18 @@ function LinkEditorPanel({
4995
5071
  urlError: state.urlError
4996
5072
  }
4997
5073
  ),
4998
- state.showChooseSection ? /* @__PURE__ */ jsxs7("div", { className: "flex flex-col justify-center gap-2", children: [
4999
- /* @__PURE__ */ jsx13(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5000
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5001
- /* @__PURE__ */ jsx13(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5002
- /* @__PURE__ */ jsx13("span", { children: "Pick a section this link should scroll to." })
5074
+ state.showChooseSection ? /* @__PURE__ */ jsxs8("div", { className: "flex flex-col justify-center gap-2", children: [
5075
+ /* @__PURE__ */ jsx14(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5076
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5077
+ /* @__PURE__ */ jsx14(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5078
+ /* @__PURE__ */ jsx14("span", { children: "Pick a section this link should scroll to." })
5003
5079
  ] })
5004
5080
  ] }) : null,
5005
- state.showSectionPicker ? /* @__PURE__ */ jsx13(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5006
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx13(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5081
+ state.showSectionPicker ? /* @__PURE__ */ jsx14(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5082
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx14(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5007
5083
  ] }),
5008
- /* @__PURE__ */ jsxs7("footer", { className: "flex w-full items-center justify-end gap-2 px-6 pb-6", children: [
5009
- /* @__PURE__ */ jsx13(
5084
+ /* @__PURE__ */ jsxs8(DialogFooter, { className: "w-full px-6 pb-6", children: [
5085
+ /* @__PURE__ */ jsx14(
5010
5086
  Button,
5011
5087
  {
5012
5088
  type: "button",
@@ -5021,7 +5097,7 @@ function LinkEditorPanel({
5021
5097
  children: state.secondaryLabel
5022
5098
  }
5023
5099
  ),
5024
- /* @__PURE__ */ jsx13(
5100
+ /* @__PURE__ */ jsx14(
5025
5101
  Button,
5026
5102
  {
5027
5103
  type: "button",
@@ -5040,28 +5116,34 @@ function LinkEditorPanel({
5040
5116
  }
5041
5117
 
5042
5118
  // src/ui/link-modal/LinkPopover.tsx
5043
- import { jsx as jsx14 } from "react/jsx-runtime";
5119
+ import { jsx as jsx15 } from "react/jsx-runtime";
5044
5120
  function LinkPopover({
5045
- rect,
5046
- parentScroll,
5121
+ open = true,
5047
5122
  panelRef,
5123
+ portalContainer,
5124
+ onClose,
5048
5125
  ...editorProps
5049
5126
  }) {
5050
- const { top, left, transform } = calcPopoverPos(rect, parentScroll);
5051
- return /* @__PURE__ */ jsx14(
5052
- "div",
5127
+ return /* @__PURE__ */ jsx15(
5128
+ Dialog2,
5053
5129
  {
5054
- ref: panelRef,
5055
- "data-ohw-link-popover-root": "",
5056
- "data-ohw-link-modal-root": "",
5057
- "data-ohw-bridge": "",
5058
- className: cn(
5059
- "pointer-events-auto fixed z-[2147483647] flex w-[483px] max-w-[calc(100vw-16px)] flex-col overflow-hidden",
5060
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none"
5061
- ),
5062
- style: { top, left, transform },
5063
- onMouseDown: (e) => e.stopPropagation(),
5064
- children: /* @__PURE__ */ jsx14(LinkEditorPanel, { ...editorProps, open: true })
5130
+ open,
5131
+ onOpenChange: (next) => {
5132
+ if (!next) onClose?.();
5133
+ },
5134
+ children: /* @__PURE__ */ jsx15(
5135
+ DialogContent,
5136
+ {
5137
+ ref: panelRef,
5138
+ container: portalContainer,
5139
+ "data-ohw-link-popover-root": "",
5140
+ "data-ohw-link-modal-root": "",
5141
+ "data-ohw-bridge": "",
5142
+ showCloseButton: false,
5143
+ className: "gap-0 p-0 w-full md:w-md pointer-events-auto",
5144
+ children: /* @__PURE__ */ jsx15(LinkEditorPanel, { ...editorProps, open, onClose })
5145
+ }
5146
+ )
5065
5147
  }
5066
5148
  );
5067
5149
  }
@@ -5125,7 +5207,7 @@ function shouldUseDevFixtures() {
5125
5207
  }
5126
5208
 
5127
5209
  // src/ui/badge.tsx
5128
- import { jsx as jsx15 } from "react/jsx-runtime";
5210
+ import { jsx as jsx16 } from "react/jsx-runtime";
5129
5211
  var badgeVariants = cva(
5130
5212
  "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",
5131
5213
  {
@@ -5143,11 +5225,11 @@ var badgeVariants = cva(
5143
5225
  }
5144
5226
  );
5145
5227
  function Badge({ className, variant, ...props }) {
5146
- return /* @__PURE__ */ jsx15("div", { className: cn(badgeVariants({ variant }), className), ...props });
5228
+ return /* @__PURE__ */ jsx16("div", { className: cn(badgeVariants({ variant }), className), ...props });
5147
5229
  }
5148
5230
 
5149
5231
  // src/OhhwellsBridge.tsx
5150
- import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
5232
+ import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
5151
5233
  var PRIMARY = "#0885FE";
5152
5234
  var IMAGE_FADE_MS = 300;
5153
5235
  function runOpacityFade(el, onDone) {
@@ -5321,7 +5403,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
5321
5403
  const root = createRoot(container);
5322
5404
  flushSync(() => {
5323
5405
  root.render(
5324
- /* @__PURE__ */ jsx16(
5406
+ /* @__PURE__ */ jsx17(
5325
5407
  SchedulingWidget,
5326
5408
  {
5327
5409
  notifyOnConnect,
@@ -5369,6 +5451,20 @@ function applyLinkHref(el, val) {
5369
5451
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5370
5452
  if (anchor) anchor.setAttribute("href", val);
5371
5453
  }
5454
+ function getEditMeasureEl(editable) {
5455
+ return editable.closest("[data-ohw-href-key]") ?? editable;
5456
+ }
5457
+ function isDragHandleDisabled(el) {
5458
+ const carriers = [
5459
+ el.closest("[data-ohw-href-key]"),
5460
+ el.closest("[data-ohw-editable]")
5461
+ ];
5462
+ return carriers.some((carrier) => {
5463
+ if (!carrier) return false;
5464
+ const raw = carrier.getAttribute("data-ohw-drag-disabled");
5465
+ return raw === "true" || raw === "";
5466
+ });
5467
+ }
5372
5468
  function collectEditableNodes() {
5373
5469
  const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
5374
5470
  if (el.dataset.ohwEditable === "image") {
@@ -5407,7 +5503,7 @@ function applyLinkByKey(key, val) {
5407
5503
  }
5408
5504
  function isInsideLinkEditor(target) {
5409
5505
  return Boolean(
5410
- target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest('[data-slot="popover-content"]')
5506
+ 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"]')
5411
5507
  );
5412
5508
  }
5413
5509
  function getHrefKeyFromElement(el) {
@@ -5418,6 +5514,23 @@ function getHrefKeyFromElement(el) {
5418
5514
  if (!key) return null;
5419
5515
  return { anchor, key };
5420
5516
  }
5517
+ function isNavbarButton(el) {
5518
+ return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
5519
+ }
5520
+ function getReorderHandleState(el) {
5521
+ const linkEl = el.closest("[data-ohw-href-key]");
5522
+ if (!linkEl || isNavbarButton(el)) return { key: null, disabled: false };
5523
+ return {
5524
+ key: linkEl.dataset.ohwHrefKey ?? null,
5525
+ disabled: isDragHandleDisabled(el)
5526
+ };
5527
+ }
5528
+ function clearHrefKeyHover(anchor) {
5529
+ anchor.removeAttribute("data-ohw-hovered");
5530
+ anchor.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5531
+ el.removeAttribute("data-ohw-hovered");
5532
+ });
5533
+ }
5421
5534
  function collectSections() {
5422
5535
  return collectSectionsFromDom();
5423
5536
  }
@@ -5541,6 +5654,8 @@ var ICONS = {
5541
5654
  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"/>',
5542
5655
  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"/>'
5543
5656
  };
5657
+ var TOOLBAR_PILL_PADDING = 2;
5658
+ var TOOLBAR_TOGGLE_GAP = 4;
5544
5659
  var TOOLBAR_GROUPS = [
5545
5660
  [
5546
5661
  { cmd: "bold", title: "Bold" },
@@ -5558,62 +5673,252 @@ var TOOLBAR_GROUPS = [
5558
5673
  { cmd: "insertOrderedList", title: "Numbered List" }
5559
5674
  ]
5560
5675
  ];
5561
- function GlowFrame({ rect, elRef }) {
5676
+ function EditGlowChrome({
5677
+ rect,
5678
+ elRef,
5679
+ reorderHrefKey,
5680
+ dragDisabled = false
5681
+ }) {
5562
5682
  const GAP = 6;
5563
- return /* @__PURE__ */ jsx16(
5683
+ return /* @__PURE__ */ jsxs9(
5564
5684
  "div",
5565
5685
  {
5566
5686
  ref: elRef,
5687
+ "data-ohw-edit-chrome": "",
5567
5688
  style: {
5568
5689
  position: "fixed",
5569
5690
  top: rect.top - GAP,
5570
5691
  left: rect.left - GAP,
5571
5692
  width: rect.width + GAP * 2,
5572
5693
  height: rect.height + GAP * 2,
5573
- border: "2px solid #0885FE",
5574
- borderRadius: 8,
5575
- boxShadow: "0 0 0 4px rgba(8, 133, 254, 0.12)",
5576
5694
  pointerEvents: "none",
5577
5695
  zIndex: 2147483646
5578
- }
5696
+ },
5697
+ children: [
5698
+ /* @__PURE__ */ jsx17(
5699
+ "div",
5700
+ {
5701
+ style: {
5702
+ position: "absolute",
5703
+ inset: 0,
5704
+ border: "2px solid #0885FE",
5705
+ borderRadius: 8,
5706
+ boxShadow: "0 0 0 4px rgba(8, 133, 254, 0.12)",
5707
+ pointerEvents: "none"
5708
+ }
5709
+ }
5710
+ ),
5711
+ reorderHrefKey && /* @__PURE__ */ jsx17(
5712
+ "div",
5713
+ {
5714
+ "data-ohw-drag-handle-container": "",
5715
+ "data-ohw-drag-disabled": dragDisabled ? "" : void 0,
5716
+ style: {
5717
+ position: "absolute",
5718
+ left: 0,
5719
+ top: "50%",
5720
+ transform: "translate(calc(-100% - 7px), -50%)",
5721
+ pointerEvents: dragDisabled ? "none" : "auto"
5722
+ },
5723
+ children: /* @__PURE__ */ jsx17(
5724
+ DragHandle,
5725
+ {
5726
+ "aria-label": `Reorder ${reorderHrefKey}`,
5727
+ disabled: dragDisabled,
5728
+ "aria-disabled": dragDisabled
5729
+ }
5730
+ )
5731
+ }
5732
+ )
5733
+ ]
5579
5734
  }
5580
5735
  );
5581
5736
  }
5582
- function calcToolbarPos(rect, parentScroll) {
5737
+ function getIframeVisibleClip(parentScroll) {
5738
+ if (!parentScroll) return null;
5739
+ const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
5740
+ const clipTop = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5741
+ const clipBottom = Math.min(
5742
+ window.innerHeight,
5743
+ visibleCanvasTop + canvasH - iframeOffsetTop
5744
+ );
5745
+ return { top: clipTop, bottom: Math.max(clipTop, clipBottom) };
5746
+ }
5747
+ function applyVisibleViewport(el, parentScroll) {
5748
+ const clip = getIframeVisibleClip(parentScroll);
5749
+ const top = clip?.top ?? 0;
5750
+ const height = clip ? clip.bottom - clip.top : window.innerHeight;
5751
+ el.style.top = `${top}px`;
5752
+ el.style.height = `${height}px`;
5753
+ }
5754
+ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
5755
+ const isAbove = transform.includes("translateY(-100%)");
5756
+ let visualTop = isAbove ? top - approxH : top;
5757
+ let visualBottom = isAbove ? top : top + approxH;
5758
+ const minTop = clip.top + gap;
5759
+ const maxBottom = clip.bottom - gap;
5760
+ if (visualTop >= minTop && visualBottom <= maxBottom) {
5761
+ return { top, transform };
5762
+ }
5763
+ const belowTop = rect.bottom + gap;
5764
+ if (belowTop + approxH <= maxBottom && belowTop >= minTop) {
5765
+ return { top: belowTop, transform: "translateX(-50%)" };
5766
+ }
5767
+ const aboveTop = rect.top - gap;
5768
+ if (aboveTop - approxH >= minTop && aboveTop <= maxBottom) {
5769
+ return { top: aboveTop, transform: "translateX(-50%) translateY(-100%)" };
5770
+ }
5771
+ if (belowTop + approxH <= maxBottom) {
5772
+ return { top: belowTop, transform: "translateX(-50%)" };
5773
+ }
5774
+ const pinnedTop = Math.min(maxBottom - approxH, Math.max(minTop, clip.top + gap));
5775
+ return { top: pinnedTop + approxH, transform: "translateX(-50%) translateY(-100%)" };
5776
+ }
5777
+ function calcToolbarPos(rect, parentScroll, approxW = 306) {
5583
5778
  const GAP = 8;
5584
- const APPROX_H = 36;
5585
- const APPROX_W = 330;
5779
+ const APPROX_H = 32;
5780
+ const APPROX_W = approxW;
5781
+ const clip = getIframeVisibleClip(parentScroll);
5782
+ const clipTop = clip?.top ?? 0;
5586
5783
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
5587
5784
  const visibleTop = rect.top - canvasTopInIframe;
5588
5785
  const visibleBottom = rect.bottom - canvasTopInIframe;
5589
5786
  const isTall = rect.height > APPROX_H + GAP;
5787
+ const spaceAbove = rect.top - (clipTop + GAP);
5788
+ const fitsAbove = spaceAbove >= APPROX_H + GAP;
5590
5789
  let top;
5591
5790
  let transform;
5592
- if (visibleTop > 0 || !isTall) {
5791
+ if ((visibleTop > 0 || !isTall) && fitsAbove) {
5593
5792
  top = rect.top - GAP;
5594
5793
  transform = "translateX(-50%) translateY(-100%)";
5794
+ } else if (visibleTop > 0 || !isTall) {
5795
+ top = rect.bottom + GAP;
5796
+ transform = "translateX(-50%)";
5595
5797
  } else if (visibleBottom > APPROX_H + GAP) {
5596
- top = canvasTopInIframe + GAP;
5798
+ top = clipTop + GAP;
5597
5799
  transform = "translateX(-50%)";
5598
5800
  } else {
5599
5801
  top = rect.bottom + GAP;
5600
5802
  transform = "translateX(-50%)";
5601
5803
  }
5804
+ if (clip) {
5805
+ const clamped = clampToolbarToClip(top, transform, rect, clip, APPROX_H, GAP);
5806
+ top = clamped.top;
5807
+ transform = clamped.transform;
5808
+ }
5602
5809
  const rawLeft = rect.left + rect.width / 2;
5603
5810
  const left = Math.max(GAP + APPROX_W / 2, Math.min(rawLeft, window.innerWidth - GAP - APPROX_W / 2));
5604
5811
  return { top, left, transform };
5605
5812
  }
5813
+ var EDIT_LINK_ICON = /* @__PURE__ */ jsxs9("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: [
5814
+ /* @__PURE__ */ jsx17("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
5815
+ /* @__PURE__ */ jsx17("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
5816
+ ] });
5606
5817
  function FloatingToolbar({
5607
5818
  rect,
5608
5819
  parentScroll,
5609
5820
  elRef,
5821
+ variant = "rich-text",
5610
5822
  onCommand,
5611
5823
  activeCommands,
5612
5824
  showEditLink,
5613
5825
  onEditLink
5614
5826
  }) {
5615
- const { top, left, transform } = calcToolbarPos(rect, parentScroll);
5616
- return /* @__PURE__ */ jsxs8(
5827
+ const approxW = variant === "link-action" ? 112 : 306;
5828
+ const { top, left, transform } = calcToolbarPos(rect, parentScroll, approxW);
5829
+ if (variant === "link-action") {
5830
+ return /* @__PURE__ */ jsxs9(
5831
+ "div",
5832
+ {
5833
+ ref: elRef,
5834
+ "data-ohw-toolbar": "",
5835
+ style: {
5836
+ position: "fixed",
5837
+ top,
5838
+ left,
5839
+ transform,
5840
+ zIndex: 2147483647,
5841
+ background: "#fff",
5842
+ border: "1px solid #E7E5E4",
5843
+ borderRadius: 6,
5844
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
5845
+ display: "flex",
5846
+ alignItems: "center",
5847
+ padding: TOOLBAR_PILL_PADDING,
5848
+ gap: TOOLBAR_TOGGLE_GAP,
5849
+ fontFamily: "sans-serif",
5850
+ pointerEvents: "auto",
5851
+ whiteSpace: "nowrap"
5852
+ },
5853
+ onMouseDown: (e) => e.stopPropagation(),
5854
+ children: [
5855
+ /* @__PURE__ */ jsx17(
5856
+ "button",
5857
+ {
5858
+ type: "button",
5859
+ title: "Edit link",
5860
+ onMouseDown: (e) => {
5861
+ e.preventDefault();
5862
+ e.stopPropagation();
5863
+ onEditLink?.();
5864
+ },
5865
+ onClick: (e) => {
5866
+ e.preventDefault();
5867
+ e.stopPropagation();
5868
+ },
5869
+ onMouseEnter: (e) => {
5870
+ e.currentTarget.style.background = "#F5F5F4";
5871
+ },
5872
+ onMouseLeave: (e) => {
5873
+ e.currentTarget.style.background = "transparent";
5874
+ },
5875
+ style: {
5876
+ display: "flex",
5877
+ alignItems: "center",
5878
+ justifyContent: "center",
5879
+ border: "none",
5880
+ background: "transparent",
5881
+ borderRadius: 4,
5882
+ cursor: "pointer",
5883
+ color: "#1C1917",
5884
+ flexShrink: 0,
5885
+ padding: 6
5886
+ },
5887
+ children: EDIT_LINK_ICON
5888
+ }
5889
+ ),
5890
+ /* @__PURE__ */ jsx17("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5891
+ /* @__PURE__ */ jsx17(
5892
+ "button",
5893
+ {
5894
+ type: "button",
5895
+ title: "Coming soon",
5896
+ disabled: true,
5897
+ style: {
5898
+ display: "flex",
5899
+ alignItems: "center",
5900
+ justifyContent: "center",
5901
+ border: "none",
5902
+ background: "transparent",
5903
+ borderRadius: 4,
5904
+ cursor: "not-allowed",
5905
+ color: "#A8A29E",
5906
+ flexShrink: 0,
5907
+ padding: 6,
5908
+ opacity: 0.6
5909
+ },
5910
+ children: /* @__PURE__ */ jsxs9("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: [
5911
+ /* @__PURE__ */ jsx17("circle", { cx: "5", cy: "12", r: "1.5" }),
5912
+ /* @__PURE__ */ jsx17("circle", { cx: "12", cy: "12", r: "1.5" }),
5913
+ /* @__PURE__ */ jsx17("circle", { cx: "19", cy: "12", r: "1.5" })
5914
+ ] })
5915
+ }
5916
+ )
5917
+ ]
5918
+ }
5919
+ );
5920
+ }
5921
+ return /* @__PURE__ */ jsxs9(
5617
5922
  "div",
5618
5923
  {
5619
5924
  ref: elRef,
@@ -5630,19 +5935,19 @@ function FloatingToolbar({
5630
5935
  boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
5631
5936
  display: "flex",
5632
5937
  alignItems: "center",
5633
- padding: 4,
5634
- gap: 6,
5938
+ padding: TOOLBAR_PILL_PADDING,
5939
+ gap: TOOLBAR_TOGGLE_GAP,
5635
5940
  fontFamily: "sans-serif",
5636
5941
  pointerEvents: "auto",
5637
5942
  whiteSpace: "nowrap"
5638
5943
  },
5639
5944
  onMouseDown: (e) => e.stopPropagation(),
5640
5945
  children: [
5641
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs8(React5.Fragment, { children: [
5642
- gi > 0 && /* @__PURE__ */ jsx16("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5946
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs9(React7.Fragment, { children: [
5947
+ gi > 0 && /* @__PURE__ */ jsx17("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5643
5948
  btns.map((btn) => {
5644
5949
  const isActive = activeCommands.has(btn.cmd);
5645
- return /* @__PURE__ */ jsx16(
5950
+ return /* @__PURE__ */ jsx17(
5646
5951
  "button",
5647
5952
  {
5648
5953
  title: btn.title,
@@ -5668,7 +5973,7 @@ function FloatingToolbar({
5668
5973
  flexShrink: 0,
5669
5974
  padding: 6
5670
5975
  },
5671
- children: /* @__PURE__ */ jsx16(
5976
+ children: /* @__PURE__ */ jsx17(
5672
5977
  "svg",
5673
5978
  {
5674
5979
  width: "16",
@@ -5688,7 +5993,7 @@ function FloatingToolbar({
5688
5993
  );
5689
5994
  })
5690
5995
  ] }, gi)),
5691
- showEditLink ? /* @__PURE__ */ jsx16(
5996
+ showEditLink ? /* @__PURE__ */ jsx17(
5692
5997
  "button",
5693
5998
  {
5694
5999
  type: "button",
@@ -5720,10 +6025,7 @@ function FloatingToolbar({
5720
6025
  flexShrink: 0,
5721
6026
  padding: 6
5722
6027
  },
5723
- children: /* @__PURE__ */ jsxs8("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: [
5724
- /* @__PURE__ */ jsx16("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
5725
- /* @__PURE__ */ jsx16("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
5726
- ] })
6028
+ children: EDIT_LINK_ICON
5727
6029
  }
5728
6030
  ) : null
5729
6031
  ]
@@ -5740,7 +6042,7 @@ function StateToggle({
5740
6042
  states,
5741
6043
  onStateChange
5742
6044
  }) {
5743
- return /* @__PURE__ */ jsx16(
6045
+ return /* @__PURE__ */ jsx17(
5744
6046
  ToggleGroup,
5745
6047
  {
5746
6048
  "data-ohw-state-toggle": "",
@@ -5754,7 +6056,7 @@ function StateToggle({
5754
6056
  left: rect.right - 8,
5755
6057
  transform: "translateX(-100%)"
5756
6058
  },
5757
- children: states.map((state) => /* @__PURE__ */ jsx16(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6059
+ children: states.map((state) => /* @__PURE__ */ jsx17(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
5758
6060
  }
5759
6061
  );
5760
6062
  }
@@ -5814,9 +6116,17 @@ function OhhwellsBridge() {
5814
6116
  const [fetchState, setFetchState] = useState5("idle");
5815
6117
  const autoSaveTimers = useRef3(/* @__PURE__ */ new Map());
5816
6118
  const activeElRef = useRef3(null);
6119
+ const selectedElRef = useRef3(null);
5817
6120
  const originalContentRef = useRef3(null);
5818
6121
  const activeStateElRef = useRef3(null);
5819
6122
  const parentScrollRef = useRef3(null);
6123
+ const visibleViewportRef = useRef3(null);
6124
+ const [dialogPortalContainer, setDialogPortalContainer] = useState5(null);
6125
+ const attachVisibleViewport = useCallback2((node) => {
6126
+ visibleViewportRef.current = node;
6127
+ setDialogPortalContainer(node);
6128
+ if (node) applyVisibleViewport(node, parentScrollRef.current);
6129
+ }, []);
5820
6130
  const toolbarElRef = useRef3(null);
5821
6131
  const glowElRef = useRef3(null);
5822
6132
  const hoveredImageRef = useRef3(null);
@@ -5829,6 +6139,10 @@ function OhhwellsBridge() {
5829
6139
  });
5830
6140
  const deactivateRef = useRef3(() => {
5831
6141
  });
6142
+ const selectRef = useRef3(() => {
6143
+ });
6144
+ const deselectRef = useRef3(() => {
6145
+ });
5832
6146
  const refreshActiveCommandsRef = useRef3(() => {
5833
6147
  });
5834
6148
  const postToParentRef = useRef3(postToParent);
@@ -5836,6 +6150,11 @@ function OhhwellsBridge() {
5836
6150
  const sectionsLoadedRef = useRef3(false);
5837
6151
  const pendingScheduleConfigRequests = useRef3([]);
5838
6152
  const [toolbarRect, setToolbarRect] = useState5(null);
6153
+ const [toolbarVariant, setToolbarVariant] = useState5("none");
6154
+ const toolbarVariantRef = useRef3("none");
6155
+ toolbarVariantRef.current = toolbarVariant;
6156
+ const [reorderHrefKey, setReorderHrefKey] = useState5(null);
6157
+ const [reorderDragDisabled, setReorderDragDisabled] = useState5(false);
5839
6158
  const [toggleState, setToggleState] = useState5(null);
5840
6159
  const [maxBadge, setMaxBadge] = useState5(null);
5841
6160
  const [activeCommands, setActiveCommands] = useState5(/* @__PURE__ */ new Set());
@@ -5880,7 +6199,33 @@ function OhhwellsBridge() {
5880
6199
  hoveredImageRef.current = null;
5881
6200
  hoveredImageHasTextOverlapRef.current = false;
5882
6201
  }
6202
+ hoveredGapRef.current = null;
6203
+ setSectionGap(null);
5883
6204
  postToParent({ type: "ow:image-unhover" });
6205
+ postToParent({ type: "ow:link-modal-lock", locked: true });
6206
+ const html = document.documentElement;
6207
+ const body = document.body;
6208
+ const prevHtmlOverflow = html.style.overflow;
6209
+ const prevBodyOverflow = body.style.overflow;
6210
+ html.style.overflow = "hidden";
6211
+ body.style.overflow = "hidden";
6212
+ const preventBackgroundScroll = (e) => {
6213
+ const target = e.target;
6214
+ if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6215
+ return;
6216
+ }
6217
+ e.preventDefault();
6218
+ };
6219
+ const scrollOpts = { passive: false, capture: true };
6220
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6221
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6222
+ return () => {
6223
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6224
+ html.style.overflow = prevHtmlOverflow;
6225
+ body.style.overflow = prevBodyOverflow;
6226
+ document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6227
+ document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6228
+ };
5884
6229
  }, [linkPopover, postToParent]);
5885
6230
  useEffect3(() => {
5886
6231
  if (!isEditMode) return;
@@ -5915,8 +6260,8 @@ function OhhwellsBridge() {
5915
6260
  }, [isEditMode]);
5916
6261
  useEffect3(() => {
5917
6262
  const update = () => {
5918
- const el = activeElRef.current;
5919
- if (el) setToolbarRect(el.getBoundingClientRect());
6263
+ const el = activeElRef.current ?? selectedElRef.current;
6264
+ if (el) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
5920
6265
  setToggleState((prev) => {
5921
6266
  if (!prev || !activeStateElRef.current) return prev;
5922
6267
  return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
@@ -5983,31 +6328,61 @@ function OhhwellsBridge() {
5983
6328
  }
5984
6329
  el.removeAttribute("contenteditable");
5985
6330
  activeElRef.current = null;
5986
- setToolbarRect(null);
6331
+ setReorderHrefKey(null);
6332
+ setReorderDragDisabled(false);
6333
+ if (!selectedElRef.current) {
6334
+ setToolbarRect(null);
6335
+ setToolbarVariant("none");
6336
+ }
5987
6337
  setMaxBadge(null);
5988
6338
  setActiveCommands(/* @__PURE__ */ new Set());
5989
6339
  setToolbarShowEditLink(false);
5990
6340
  postToParent({ type: "ow:exit-edit" });
5991
6341
  }, [postToParent]);
6342
+ const deselect = useCallback2(() => {
6343
+ selectedElRef.current = null;
6344
+ if (!activeElRef.current) {
6345
+ setToolbarRect(null);
6346
+ setToolbarVariant("none");
6347
+ }
6348
+ }, []);
6349
+ const select = useCallback2((anchor) => {
6350
+ if (!isNavbarButton(anchor)) return;
6351
+ if (activeElRef.current) deactivate();
6352
+ selectedElRef.current = anchor;
6353
+ clearHrefKeyHover(anchor);
6354
+ setToolbarVariant("link-action");
6355
+ setToolbarRect(anchor.getBoundingClientRect());
6356
+ setToolbarShowEditLink(false);
6357
+ setActiveCommands(/* @__PURE__ */ new Set());
6358
+ }, [deactivate]);
5992
6359
  const activate = useCallback2((el) => {
5993
6360
  if (activeElRef.current === el) return;
6361
+ selectedElRef.current = null;
5994
6362
  deactivate();
5995
6363
  if (hoveredImageRef.current) {
5996
6364
  hoveredImageRef.current = null;
5997
6365
  postToParentRef.current({ type: "ow:image-unhover" });
5998
6366
  }
6367
+ setToolbarVariant("rich-text");
5999
6368
  el.setAttribute("contenteditable", "true");
6000
6369
  el.removeAttribute("data-ohw-hovered");
6370
+ el.closest("[data-ohw-href-key]")?.removeAttribute("data-ohw-hovered");
6001
6371
  activeElRef.current = el;
6002
6372
  originalContentRef.current = el.innerHTML;
6003
6373
  el.focus();
6004
- setToolbarRect(el.getBoundingClientRect());
6005
6374
  setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
6375
+ const { key: reorderKey, disabled: reorderDisabled } = getReorderHandleState(el);
6376
+ setReorderHrefKey(reorderKey);
6377
+ setReorderDragDisabled(reorderDisabled);
6378
+ setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6006
6379
  postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
6007
6380
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
6008
6381
  }, [deactivate, postToParent]);
6009
6382
  activateRef.current = activate;
6010
6383
  deactivateRef.current = deactivate;
6384
+ selectRef.current = select;
6385
+ deselectRef.current = deselect;
6011
6386
  useLayoutEffect(() => {
6012
6387
  if (!subdomain || isEditMode) {
6013
6388
  setFetchState("done");
@@ -6111,6 +6486,12 @@ function OhhwellsBridge() {
6111
6486
  useEffect3(() => {
6112
6487
  postToParent({ type: "ow:navigation", path: pathname });
6113
6488
  }, [pathname, postToParent]);
6489
+ useEffect3(() => {
6490
+ if (!isEditMode) return;
6491
+ setLinkPopover(null);
6492
+ deselectRef.current();
6493
+ deactivateRef.current();
6494
+ }, [pathname, isEditMode]);
6114
6495
  useEffect3(() => {
6115
6496
  if (!isEditMode) return;
6116
6497
  const measure = () => {
@@ -6216,17 +6597,22 @@ function OhhwellsBridge() {
6216
6597
  if (target.closest("[data-ohw-state-toggle]")) return;
6217
6598
  if (target.closest("[data-ohw-max-badge]")) return;
6218
6599
  if (isInsideLinkEditor(target)) return;
6600
+ if (target.closest('[data-ohw-edit-chrome], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
6601
+ e.preventDefault();
6602
+ e.stopPropagation();
6603
+ return;
6604
+ }
6219
6605
  const editable = target.closest("[data-ohw-editable]");
6220
6606
  if (editable) {
6221
6607
  if (editable.dataset.ohwEditable === "link") {
6222
6608
  e.preventDefault();
6223
6609
  e.stopPropagation();
6610
+ deselectRef.current();
6224
6611
  deactivateRef.current();
6225
6612
  bumpLinkPopoverGrace();
6226
6613
  setLinkPopoverRef.current({
6227
6614
  key: editable.dataset.ohwKey ?? "",
6228
- target: getLinkHref(editable),
6229
- rect: editable.getBoundingClientRect()
6615
+ target: getLinkHref(editable)
6230
6616
  });
6231
6617
  return;
6232
6618
  }
@@ -6236,11 +6622,31 @@ function OhhwellsBridge() {
6236
6622
  postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
6237
6623
  return;
6238
6624
  }
6625
+ const hrefCtx = getHrefKeyFromElement(editable);
6626
+ if (hrefCtx && isNavbarButton(hrefCtx.anchor)) {
6627
+ e.preventDefault();
6628
+ e.stopPropagation();
6629
+ const { anchor: anchor2 } = hrefCtx;
6630
+ if (selectedElRef.current === anchor2) {
6631
+ activateRef.current(editable);
6632
+ return;
6633
+ }
6634
+ selectRef.current(anchor2);
6635
+ return;
6636
+ }
6239
6637
  e.preventDefault();
6240
6638
  e.stopPropagation();
6241
6639
  activateRef.current(editable);
6242
6640
  return;
6243
6641
  }
6642
+ const hrefAnchor = target.closest("[data-ohw-href-key]");
6643
+ if (hrefAnchor && isNavbarButton(hrefAnchor)) {
6644
+ e.preventDefault();
6645
+ e.stopPropagation();
6646
+ if (selectedElRef.current === hrefAnchor) return;
6647
+ selectRef.current(hrefAnchor);
6648
+ return;
6649
+ }
6244
6650
  if (activeElRef.current) {
6245
6651
  const sel = window.getSelection();
6246
6652
  if (sel && !sel.isCollapsed) {
@@ -6255,24 +6661,35 @@ function OhhwellsBridge() {
6255
6661
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
6256
6662
  if (hrefCtx && (hrefCtx.anchor === target || hrefCtx.anchor.contains(target))) return;
6257
6663
  }
6664
+ if (linkPopoverOpenRef.current && selectedElRef.current) {
6665
+ const selected = selectedElRef.current;
6666
+ if (selected === target || selected.contains(target)) return;
6667
+ }
6258
6668
  if (linkPopoverOpenRef.current) {
6259
6669
  setLinkPopoverRef.current(null);
6260
6670
  return;
6261
6671
  }
6672
+ deselectRef.current();
6262
6673
  deactivateRef.current();
6263
6674
  };
6264
6675
  const handleMouseOver = (e) => {
6265
6676
  const editable = e.target.closest("[data-ohw-editable]");
6266
6677
  if (!editable) return;
6678
+ const selected = selectedElRef.current;
6679
+ if (selected && (selected === editable || selected.contains(editable))) return;
6267
6680
  if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image" && !editable.hasAttribute("contenteditable")) {
6268
- editable.setAttribute("data-ohw-hovered", "");
6681
+ const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
6682
+ hoverTarget.setAttribute("data-ohw-hovered", "");
6269
6683
  }
6270
6684
  };
6271
6685
  const handleMouseOut = (e) => {
6272
6686
  const editable = e.target.closest("[data-ohw-editable]");
6273
6687
  if (!editable) return;
6688
+ const related = e.relatedTarget instanceof Element ? e.relatedTarget : null;
6689
+ if (related?.closest("[data-ohw-drag-handle-container]")) return;
6274
6690
  if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image") {
6275
- editable.removeAttribute("data-ohw-hovered");
6691
+ const hoverTarget = editable.closest("[data-ohw-href-key]") ?? editable;
6692
+ hoverTarget.removeAttribute("data-ohw-hovered");
6276
6693
  }
6277
6694
  };
6278
6695
  const toProbeCoords = (clientX, clientY, fromParentViewport) => {
@@ -6387,11 +6804,20 @@ function OhhwellsBridge() {
6387
6804
  return;
6388
6805
  }
6389
6806
  }
6807
+ if (activeElRef.current) {
6808
+ if (hoveredImageRef.current) {
6809
+ hoveredImageRef.current = null;
6810
+ hoveredImageHasTextOverlapRef.current = false;
6811
+ resumeAnimTracks();
6812
+ postToParentRef.current({ type: "ow:image-unhover" });
6813
+ }
6814
+ return;
6815
+ }
6390
6816
  const imgEl = findImageAtPoint(clientX, clientY, fromParentViewport);
6391
6817
  if (imgEl) {
6392
6818
  const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
6393
6819
  const topEl = document.elementFromPoint(x, y);
6394
- if (topEl?.closest("[data-ohw-toolbar]")) {
6820
+ if (topEl?.closest('[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-drag-handle-container], [data-slot="drag-handle"]')) {
6395
6821
  if (hoveredImageRef.current) {
6396
6822
  hoveredImageRef.current = null;
6397
6823
  resumeAnimTracks();
@@ -6399,7 +6825,7 @@ function OhhwellsBridge() {
6399
6825
  }
6400
6826
  return;
6401
6827
  }
6402
- if (topEl?.closest("[data-ohw-link-popover-root]") || topEl?.closest("[data-ohw-link-modal-root]")) {
6828
+ if (topEl?.closest("[data-ohw-link-popover-root]") || topEl?.closest("[data-ohw-link-modal-root]") || topEl?.closest("[data-ohw-link-page-dropdown]")) {
6403
6829
  if (hoveredImageRef.current) {
6404
6830
  hoveredImageRef.current = null;
6405
6831
  hoveredImageHasTextOverlapRef.current = false;
@@ -6527,6 +6953,13 @@ function OhhwellsBridge() {
6527
6953
  }
6528
6954
  };
6529
6955
  const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
6956
+ if (linkPopoverOpenRef.current) {
6957
+ if (hoveredGapRef.current) {
6958
+ hoveredGapRef.current = null;
6959
+ setSectionGap(null);
6960
+ }
6961
+ return;
6962
+ }
6530
6963
  const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
6531
6964
  const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
6532
6965
  const ZONE = 20;
@@ -6677,7 +7110,7 @@ function OhhwellsBridge() {
6677
7110
  const el = e.target;
6678
7111
  const key = el.dataset.ohwKey;
6679
7112
  if (!key) return;
6680
- if (el === activeElRef.current) setToolbarRect(el.getBoundingClientRect());
7113
+ if (el === activeElRef.current) setToolbarRect(getEditMeasureEl(el).getBoundingClientRect());
6681
7114
  const maxLen = el.dataset.ohwMaxLength ? parseInt(el.dataset.ohwMaxLength, 10) : null;
6682
7115
  if (maxLen) {
6683
7116
  const current = el.innerText.replace(/\n$/, "").length;
@@ -6750,6 +7183,7 @@ function OhhwellsBridge() {
6750
7183
  setLinkPopoverRef.current(null);
6751
7184
  return;
6752
7185
  }
7186
+ deselectRef.current();
6753
7187
  deactivateRef.current();
6754
7188
  };
6755
7189
  window.addEventListener("message", handleDeactivate);
@@ -6758,6 +7192,10 @@ function OhhwellsBridge() {
6758
7192
  setLinkPopoverRef.current(null);
6759
7193
  return;
6760
7194
  }
7195
+ if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7196
+ deselectRef.current();
7197
+ return;
7198
+ }
6761
7199
  if (e.key !== "Escape") return;
6762
7200
  const el = activeElRef.current;
6763
7201
  if (el && originalContentRef.current !== null) {
@@ -6767,13 +7205,16 @@ function OhhwellsBridge() {
6767
7205
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: originalContentRef.current }] });
6768
7206
  }
6769
7207
  }
7208
+ deselectRef.current();
6770
7209
  deactivateRef.current();
6771
7210
  };
6772
7211
  const handleScroll = () => {
6773
- if (activeElRef.current) {
6774
- const r2 = activeElRef.current.getBoundingClientRect();
7212
+ const focusEl = activeElRef.current ?? selectedElRef.current;
7213
+ if (focusEl) {
7214
+ const r2 = focusEl.getBoundingClientRect();
6775
7215
  applyToolbarPos(r2);
6776
- setMaxBadge((prev) => prev ? { ...prev, rect: r2 } : null);
7216
+ setToolbarRect(r2);
7217
+ setMaxBadge((prev) => prev && activeElRef.current ? { ...prev, rect: r2 } : prev);
6777
7218
  }
6778
7219
  if (activeStateElRef.current) {
6779
7220
  const rect = activeStateElRef.current.getBoundingClientRect();
@@ -6898,8 +7339,9 @@ function OhhwellsBridge() {
6898
7339
  };
6899
7340
  const applyToolbarPos = (rect) => {
6900
7341
  const ps = parentScrollRef.current;
7342
+ const approxW = toolbarVariantRef.current === "link-action" ? 112 : 306;
6901
7343
  if (toolbarElRef.current) {
6902
- const { top, left, transform } = calcToolbarPos(rect, ps);
7344
+ const { top, left, transform } = calcToolbarPos(rect, ps, approxW);
6903
7345
  toolbarElRef.current.style.top = `${top}px`;
6904
7346
  toolbarElRef.current.style.left = `${left}px`;
6905
7347
  toolbarElRef.current.style.transform = transform;
@@ -6914,7 +7356,11 @@ function OhhwellsBridge() {
6914
7356
  if (e.data?.type !== "ow:parent-scroll") return;
6915
7357
  const { iframeOffsetTop, headerH, canvasH } = e.data;
6916
7358
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
6917
- if (activeElRef.current) applyToolbarPos(activeElRef.current.getBoundingClientRect());
7359
+ if (visibleViewportRef.current) {
7360
+ applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
7361
+ }
7362
+ const focusEl = activeElRef.current ?? selectedElRef.current;
7363
+ if (focusEl) applyToolbarPos(focusEl.getBoundingClientRect());
6918
7364
  };
6919
7365
  const handleClickAt = (e) => {
6920
7366
  if (e.data?.type !== "ow:click-at") return;
@@ -6964,6 +7410,12 @@ function OhhwellsBridge() {
6964
7410
  window.addEventListener("message", handleParentScroll);
6965
7411
  window.addEventListener("message", handlePointerSync);
6966
7412
  window.addEventListener("message", handleClickAt);
7413
+ const handleViewportResize = () => {
7414
+ if (visibleViewportRef.current) {
7415
+ applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
7416
+ }
7417
+ };
7418
+ window.addEventListener("resize", handleViewportResize, { passive: true });
6967
7419
  document.addEventListener("click", handleClick, true);
6968
7420
  document.addEventListener("paste", handlePaste, true);
6969
7421
  document.addEventListener("input", handleInput, true);
@@ -7001,6 +7453,7 @@ function OhhwellsBridge() {
7001
7453
  window.removeEventListener("message", handleAnimLock);
7002
7454
  window.removeEventListener("message", handleCanvasHeight);
7003
7455
  window.removeEventListener("message", handleParentScroll);
7456
+ window.removeEventListener("resize", handleViewportResize);
7004
7457
  window.removeEventListener("message", handlePointerSync);
7005
7458
  window.removeEventListener("message", handleClickAt);
7006
7459
  window.removeEventListener("message", handleHydrate);
@@ -7065,7 +7518,7 @@ function OhhwellsBridge() {
7065
7518
  const handleCommand = useCallback2((cmd) => {
7066
7519
  document.execCommand(cmd, false);
7067
7520
  activeElRef.current?.focus();
7068
- if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
7521
+ if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
7069
7522
  refreshActiveCommandsRef.current();
7070
7523
  }, []);
7071
7524
  const handleStateChange = useCallback2((state) => {
@@ -7088,10 +7541,22 @@ function OhhwellsBridge() {
7088
7541
  bumpLinkPopoverGrace();
7089
7542
  setLinkPopover({
7090
7543
  key: hrefCtx.key,
7091
- target: getLinkHref(hrefCtx.anchor),
7092
- rect: hrefCtx.anchor.getBoundingClientRect()
7544
+ target: getLinkHref(hrefCtx.anchor)
7093
7545
  });
7094
- }, []);
7546
+ deactivate();
7547
+ }, [deactivate]);
7548
+ const openLinkPopoverForSelected = useCallback2(() => {
7549
+ const anchor = selectedElRef.current;
7550
+ if (!anchor) return;
7551
+ const key = anchor.getAttribute("data-ohw-href-key");
7552
+ if (!key) return;
7553
+ bumpLinkPopoverGrace();
7554
+ setLinkPopover({
7555
+ key,
7556
+ target: getLinkHref(anchor)
7557
+ });
7558
+ deselect();
7559
+ }, [deselect]);
7095
7560
  const handleLinkPopoverSubmit = useCallback2(
7096
7561
  (target) => {
7097
7562
  if (!linkPopover) return;
@@ -7106,12 +7571,22 @@ function OhhwellsBridge() {
7106
7571
  const currentSections = sectionsByPath[pathname] ?? [];
7107
7572
  linkPopoverOpenRef.current = linkPopover !== null;
7108
7573
  return bridgeRoot ? createPortal(
7109
- /* @__PURE__ */ jsxs8(Fragment3, { children: [
7110
- toolbarRect && /* @__PURE__ */ jsxs8(Fragment3, { children: [
7111
- /* @__PURE__ */ jsx16(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
7112
- /* @__PURE__ */ jsx16(
7574
+ /* @__PURE__ */ jsxs9(Fragment3, { children: [
7575
+ /* @__PURE__ */ jsx17("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
7576
+ toolbarRect && /* @__PURE__ */ jsxs9(Fragment3, { children: [
7577
+ /* @__PURE__ */ jsx17(
7578
+ EditGlowChrome,
7579
+ {
7580
+ rect: toolbarRect,
7581
+ elRef: glowElRef,
7582
+ reorderHrefKey,
7583
+ dragDisabled: reorderDragDisabled
7584
+ }
7585
+ ),
7586
+ toolbarVariant === "rich-text" && /* @__PURE__ */ jsx17(
7113
7587
  FloatingToolbar,
7114
7588
  {
7589
+ variant: "rich-text",
7115
7590
  rect: toolbarRect,
7116
7591
  parentScroll: parentScrollRef.current,
7117
7592
  elRef: toolbarElRef,
@@ -7120,9 +7595,21 @@ function OhhwellsBridge() {
7120
7595
  showEditLink,
7121
7596
  onEditLink: openLinkPopoverForActive
7122
7597
  }
7598
+ ),
7599
+ toolbarVariant === "link-action" && /* @__PURE__ */ jsx17(
7600
+ FloatingToolbar,
7601
+ {
7602
+ variant: "link-action",
7603
+ rect: toolbarRect,
7604
+ parentScroll: parentScrollRef.current,
7605
+ elRef: toolbarElRef,
7606
+ onCommand: handleCommand,
7607
+ activeCommands,
7608
+ onEditLink: openLinkPopoverForSelected
7609
+ }
7123
7610
  )
7124
7611
  ] }),
7125
- maxBadge && /* @__PURE__ */ jsxs8(
7612
+ maxBadge && /* @__PURE__ */ jsxs9(
7126
7613
  "div",
7127
7614
  {
7128
7615
  "data-ohw-max-badge": "",
@@ -7148,7 +7635,7 @@ function OhhwellsBridge() {
7148
7635
  ]
7149
7636
  }
7150
7637
  ),
7151
- toggleState && /* @__PURE__ */ jsx16(
7638
+ toggleState && /* @__PURE__ */ jsx17(
7152
7639
  StateToggle,
7153
7640
  {
7154
7641
  rect: toggleState.rect,
@@ -7157,15 +7644,15 @@ function OhhwellsBridge() {
7157
7644
  onStateChange: handleStateChange
7158
7645
  }
7159
7646
  ),
7160
- sectionGap && /* @__PURE__ */ jsxs8(
7647
+ sectionGap && /* @__PURE__ */ jsxs9(
7161
7648
  "div",
7162
7649
  {
7163
7650
  "data-ohw-section-insert-line": "",
7164
7651
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
7165
7652
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
7166
7653
  children: [
7167
- /* @__PURE__ */ jsx16("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
7168
- /* @__PURE__ */ jsx16(
7654
+ /* @__PURE__ */ jsx17("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
7655
+ /* @__PURE__ */ jsx17(
7169
7656
  Badge,
7170
7657
  {
7171
7658
  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",
@@ -7178,16 +7665,15 @@ function OhhwellsBridge() {
7178
7665
  children: "Add Section"
7179
7666
  }
7180
7667
  ),
7181
- /* @__PURE__ */ jsx16("div", { className: "flex-1 bg-primary", style: { height: 3 } })
7668
+ /* @__PURE__ */ jsx17("div", { className: "flex-1 bg-primary", style: { height: 3 } })
7182
7669
  ]
7183
7670
  }
7184
7671
  ),
7185
- linkPopover ? /* @__PURE__ */ jsx16(
7672
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx17(
7186
7673
  LinkPopover,
7187
7674
  {
7188
- rect: linkPopover.rect,
7189
- parentScroll: parentScrollRef.current,
7190
7675
  panelRef: linkPopoverPanelRef,
7676
+ portalContainer: dialogPortalContainer,
7191
7677
  open: true,
7192
7678
  mode: "edit",
7193
7679
  pages: sitePages,
@@ -7196,13 +7682,40 @@ function OhhwellsBridge() {
7196
7682
  initialTarget: linkPopover.target,
7197
7683
  onClose: closeLinkPopover,
7198
7684
  onSubmit: handleLinkPopoverSubmit
7685
+ },
7686
+ `${linkPopover.key}-${pathname}`
7687
+ ) : null,
7688
+ sectionGap && /* @__PURE__ */ jsxs9(
7689
+ "div",
7690
+ {
7691
+ "data-ohw-section-insert-line": "",
7692
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
7693
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
7694
+ children: [
7695
+ /* @__PURE__ */ jsx17("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
7696
+ /* @__PURE__ */ jsx17(
7697
+ Badge,
7698
+ {
7699
+ 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",
7700
+ onClick: () => {
7701
+ window.parent.postMessage(
7702
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
7703
+ "*"
7704
+ );
7705
+ },
7706
+ children: "Add Section"
7707
+ }
7708
+ ),
7709
+ /* @__PURE__ */ jsx17("div", { className: "flex-1 bg-primary", style: { height: 3 } })
7710
+ ]
7199
7711
  }
7200
- ) : null
7712
+ )
7201
7713
  ] }),
7202
7714
  bridgeRoot
7203
7715
  ) : null;
7204
7716
  }
7205
7717
  export {
7718
+ DragHandle,
7206
7719
  LinkEditorPanel,
7207
7720
  LinkPopover,
7208
7721
  OhhwellsBridge,
@@ -7213,6 +7726,7 @@ export {
7213
7726
  buildTarget,
7214
7727
  filterAvailablePages,
7215
7728
  getEditModeInitialState,
7729
+ isEditSessionActive,
7216
7730
  isValidUrl,
7217
7731
  parseTarget,
7218
7732
  toggleVariants,