@ohhwells/bridge 0.1.24 → 0.1.25

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,12 +1,12 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React3, { useCallback, useEffect as useEffect2, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
4
+ import React5, { useCallback as useCallback2, useEffect as useEffect4, 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
 
8
8
  // src/ui/SchedulingWidget.tsx
9
- import { useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
9
+ import { useEffect, useMemo, useRef, useState as useState2 } from "react";
10
10
 
11
11
  // src/ui/EmailCaptureModal.tsx
12
12
  import { useState } from "react";
@@ -164,6 +164,13 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
164
164
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
165
165
  var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
166
166
  var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
167
+ function getApiDomain() {
168
+ const h = window.location.hostname;
169
+ for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
170
+ if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
171
+ }
172
+ return h;
173
+ }
167
174
  function classRunsOnDate(cls, date, type) {
168
175
  if (type === "FIXED") {
169
176
  const d = String(date.getDate()).padStart(2, "0");
@@ -209,10 +216,10 @@ function buildTimezoneLabel(tz) {
209
216
  return tz;
210
217
  }
211
218
  }
212
- function EmptyState({ inEditor, insertAfter }) {
219
+ function EmptyState({ inEditor }) {
213
220
  const handleAddSchedule = () => {
214
221
  if (inEditor) {
215
- window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
222
+ window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
216
223
  }
217
224
  };
218
225
  return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center gap-6 p-12 bg-[#FAFAF9] border-2 border-dashed border-[#E7E5E4] rounded-[10px] h-[264px] w-full box-border", children: [
@@ -437,21 +444,18 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
437
444
  const booked = getBookingsOnDate(cls, selectedDate);
438
445
  const available = cls.maxParticipants - booked;
439
446
  const isFull = available <= 0;
440
- const isPrivate = cls.maxParticipants === 1;
441
447
  return /* @__PURE__ */ jsxs2("div", { children: [
442
448
  /* @__PURE__ */ jsxs2("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
443
449
  /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
444
- /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 sm:w-[120px] sm:shrink-0", children: [
445
- /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }),
446
- /* @__PURE__ */ jsx2(
447
- "span",
448
- {
449
- className: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium w-fit",
450
- style: isPrivate ? { background: "#DDFBE3", color: "#199130", border: "1px solid #199130", fontWeight: 500 } : { background: "#EEEDFF", color: "#5953FF", border: "1px solid #5953FF", fontWeight: 500 },
451
- children: isPrivate ? "PRIVATE" : "GROUP"
452
- }
453
- )
454
- ] }),
450
+ /* @__PURE__ */ jsx2("div", { className: "sm:w-[120px] sm:shrink-0", children: /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
451
+ /* @__PURE__ */ jsx2(
452
+ "span",
453
+ {
454
+ className: "inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium sm:hidden",
455
+ style: { borderColor: "#0885FE", color: "#0885FE" },
456
+ children: "GROUP"
457
+ }
458
+ ),
455
459
  /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
456
460
  /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
457
461
  available,
@@ -524,9 +528,7 @@ function CalendarFoldIcon() {
524
528
  }
525
529
  );
526
530
  }
527
- function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter: insertAfterProp }) {
528
- const autoId = useId();
529
- const insertAfter = insertAfterProp ?? autoId;
531
+ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
530
532
  const [schedule, setSchedule] = useState2(null);
531
533
  const [loading, setLoading] = useState2(true);
532
534
  const [inEditor, setInEditor] = useState2(false);
@@ -556,99 +558,88 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
556
558
  if (typeof window === "undefined") return;
557
559
  const isInEditor = window.self !== window.top;
558
560
  setInEditor(isInEditor);
559
- function loadWithId(effectiveId) {
560
- if (isInEditor) {
561
- const startEmpty = effectiveId === null;
562
- if (startEmpty) setLoading(false);
563
- let initialized = startEmpty;
564
- const timer = !startEmpty ? setTimeout(() => {
565
- if (!initialized) {
566
- initialized = true;
567
- setLoading(false);
568
- }
569
- }, 5e3) : null;
570
- const handler = (e) => {
571
- if (e.data?.type === "ow:clear-scheduling-widget") {
572
- if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
573
- setSchedule(null);
574
- setLoading(false);
575
- return;
576
- }
577
- if (e.data?.type === "ow:switch-schedule") {
578
- if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
579
- switchScheduleIdRef.current = e.data.scheduleId ?? null;
580
- initialized = false;
581
- setLoading(true);
582
- window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
583
- return;
584
- }
585
- if (e.data?.type !== "ow:user-schedules-response") return;
586
- if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
587
- if (initialized && switchScheduleIdRef.current === null) return;
561
+ if (isInEditor) {
562
+ const startEmpty = initialScheduleId === null;
563
+ if (startEmpty) setLoading(false);
564
+ let initialized = startEmpty;
565
+ const timer = !startEmpty ? setTimeout(() => {
566
+ if (!initialized) {
588
567
  initialized = true;
589
- if (timer) clearTimeout(timer);
590
- const schedules = e.data.schedules ?? [];
591
- const isSwitching = switchScheduleIdRef.current !== null;
592
- const target = isSwitching ? schedules.find((s) => s.id === switchScheduleIdRef.current) ?? schedules[0] ?? null : effectiveId ? schedules.find((s) => s.id === effectiveId) ?? schedules[0] ?? null : schedules[0] ?? null;
593
- switchScheduleIdRef.current = null;
594
- setSchedule(target);
595
568
  setLoading(false);
596
- if (notifyOnConnect && target && !isSwitching) {
597
- window.parent.postMessage({
598
- type: "ow:schedule-connected",
599
- schedule: { id: target.id, name: target.name },
600
- insertAfter
601
- }, "*");
602
- window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
603
- }
604
- };
605
- window.addEventListener("message", handler);
606
- if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
607
- return () => {
608
- window.removeEventListener("message", handler);
609
- if (timer) clearTimeout(timer);
610
- };
611
- }
612
- if (!effectiveId) {
613
- setLoading(false);
614
- return;
615
- }
616
- ;
617
- (async () => {
618
- try {
619
- const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
620
- const data = await res.json();
621
- setSchedule(data?.id ? data : null);
622
- } catch {
623
569
  }
624
- setLoading(false);
625
- })();
626
- }
627
- if (initialScheduleId === void 0) {
628
- let done = false;
629
- let innerCleanup;
630
- const timer = setTimeout(() => {
631
- if (!done) {
632
- done = true;
570
+ }, 5e3) : null;
571
+ const handler = (e) => {
572
+ if (e.data?.type === "ow:clear-scheduling-widget") {
573
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
574
+ setSchedule(null);
633
575
  setLoading(false);
576
+ return;
577
+ }
578
+ if (e.data?.type === "ow:switch-schedule") {
579
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
580
+ switchScheduleIdRef.current = e.data.scheduleId ?? null;
581
+ initialized = false;
582
+ setLoading(true);
583
+ window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
584
+ return;
585
+ }
586
+ if (e.data?.type !== "ow:user-schedules-response") return;
587
+ if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
588
+ if (initialized && switchScheduleIdRef.current === null) return;
589
+ initialized = true;
590
+ if (timer) clearTimeout(timer);
591
+ const schedules = e.data.schedules ?? [];
592
+ const isSwitching = switchScheduleIdRef.current !== null;
593
+ const target = isSwitching ? schedules.find((s) => s.id === switchScheduleIdRef.current) ?? schedules[0] ?? null : initialScheduleId ? schedules.find((s) => s.id === initialScheduleId) ?? schedules[0] ?? null : schedules[0] ?? null;
594
+ switchScheduleIdRef.current = null;
595
+ setSchedule(target);
596
+ setLoading(false);
597
+ if (notifyOnConnect && target && !isSwitching) {
598
+ window.parent.postMessage({
599
+ type: "ow:schedule-connected",
600
+ schedule: { id: target.id, name: target.name },
601
+ insertAfter
602
+ }, "*");
603
+ window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
634
604
  }
635
- }, 2e3);
636
- const configHandler = (e) => {
637
- if (e.data?.type !== "ow:schedule-config" || e.data.insertAfter !== insertAfter || done) return;
638
- done = true;
639
- clearTimeout(timer);
640
- window.removeEventListener("message", configHandler);
641
- innerCleanup = loadWithId(e.data.scheduleId ?? null);
642
605
  };
643
- window.addEventListener("message", configHandler);
644
- window.postMessage({ type: "ow:request-schedule-config", insertAfter }, "*");
606
+ window.addEventListener("message", handler);
607
+ if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
645
608
  return () => {
646
- window.removeEventListener("message", configHandler);
647
- clearTimeout(timer);
648
- innerCleanup?.();
609
+ window.removeEventListener("message", handler);
610
+ if (timer) clearTimeout(timer);
649
611
  };
650
612
  }
651
- return loadWithId(initialScheduleId) ?? void 0;
613
+ if (initialScheduleId === null) {
614
+ setLoading(false);
615
+ return;
616
+ }
617
+ ;
618
+ (async () => {
619
+ try {
620
+ if (initialScheduleId) {
621
+ const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
622
+ const data = await res.json();
623
+ setSchedule(data?.id ? data : null);
624
+ } else {
625
+ const domain = getApiDomain();
626
+ const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
627
+ const slugs = await sitemapRes.json();
628
+ if (!Array.isArray(slugs) || !slugs.length) {
629
+ setLoading(false);
630
+ return;
631
+ }
632
+ const { slug } = [...slugs].sort(
633
+ (a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
634
+ )[0];
635
+ const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
636
+ const data = await res.json();
637
+ setSchedule(data?.id ? data : null);
638
+ }
639
+ } catch {
640
+ }
641
+ setLoading(false);
642
+ })();
652
643
  }, []);
653
644
  const timezoneLabel = schedule?.timezone ? (() => {
654
645
  try {
@@ -683,7 +674,6 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
683
674
  window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
684
675
  }
685
676
  };
686
- if (!inEditor && !loading && !schedule) return null;
687
677
  const sectionId = `scheduling-${insertAfter}`;
688
678
  return /* @__PURE__ */ jsxs2(
689
679
  "section",
@@ -729,7 +719,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
729
719
  ] }),
730
720
  /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col items-end gap-3", children: [
731
721
  timezoneLabel && /* @__PURE__ */ jsx2("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
732
- /* @__PURE__ */ jsx2("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ jsx2(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ jsx2(EmptyState, { inEditor, insertAfter }) : /* @__PURE__ */ jsx2(
722
+ /* @__PURE__ */ jsx2("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ jsx2(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ jsx2(EmptyState, { inEditor }) : /* @__PURE__ */ jsx2(
733
723
  ScheduleView,
734
724
  {
735
725
  schedule,
@@ -4185,8 +4175,844 @@ function isEditSessionActive() {
4185
4175
  return new URLSearchParams(q).get("mode") === "edit";
4186
4176
  }
4187
4177
 
4188
- // src/ui/badge.tsx
4178
+ // src/ui/link-modal/calcPopoverPos.ts
4179
+ function calcPopoverPos(rect, parentScroll, approxW = 483, approxH = 320) {
4180
+ const GAP = 8;
4181
+ const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
4182
+ const visibleTop = rect.top - canvasTopInIframe;
4183
+ let top = rect.top - GAP;
4184
+ let transform = "translateX(-50%) translateY(-100%)";
4185
+ if (visibleTop < approxH + GAP) {
4186
+ top = rect.bottom + GAP;
4187
+ transform = "translateX(-50%)";
4188
+ }
4189
+ const rawLeft = rect.left + rect.width / 2;
4190
+ const left = Math.max(GAP + approxW / 2, Math.min(rawLeft, window.innerWidth - GAP - approxW / 2));
4191
+ return { top, left, transform };
4192
+ }
4193
+
4194
+ // src/ui/button.tsx
4195
+ import * as React3 from "react";
4196
+ import { Slot } from "radix-ui";
4189
4197
  import { jsx as jsx5 } from "react/jsx-runtime";
4198
+ var buttonVariants = cva(
4199
+ "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",
4200
+ {
4201
+ variants: {
4202
+ variant: {
4203
+ default: "bg-primary text-primary-foreground hover:opacity-90",
4204
+ outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4205
+ ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4206
+ },
4207
+ size: {
4208
+ default: "h-9",
4209
+ sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
4210
+ }
4211
+ },
4212
+ defaultVariants: {
4213
+ variant: "default",
4214
+ size: "default"
4215
+ }
4216
+ }
4217
+ );
4218
+ var Button = React3.forwardRef(
4219
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
4220
+ const Comp = asChild ? Slot.Root : "button";
4221
+ return /* @__PURE__ */ jsx5(
4222
+ Comp,
4223
+ {
4224
+ ref,
4225
+ "data-slot": "button",
4226
+ className: cn(buttonVariants({ variant, size, className })),
4227
+ ...props
4228
+ }
4229
+ );
4230
+ }
4231
+ );
4232
+ Button.displayName = "Button";
4233
+
4234
+ // src/ui/icons.tsx
4235
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
4236
+ function IconX({ className, ...props }) {
4237
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4238
+ /* @__PURE__ */ jsx6("path", { d: "M18 6 6 18" }),
4239
+ /* @__PURE__ */ jsx6("path", { d: "m6 6 12 12" })
4240
+ ] });
4241
+ }
4242
+ function IconChevronDown({ className, ...props }) {
4243
+ return /* @__PURE__ */ jsx6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ jsx6("path", { d: "m6 9 6 6 6-6" }) });
4244
+ }
4245
+ function IconFile({ className, ...props }) {
4246
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4247
+ /* @__PURE__ */ jsx6("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
4248
+ /* @__PURE__ */ jsx6("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
4249
+ ] });
4250
+ }
4251
+ function IconInfo({ className, ...props }) {
4252
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4253
+ /* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "10" }),
4254
+ /* @__PURE__ */ jsx6("path", { d: "M12 16v-4" }),
4255
+ /* @__PURE__ */ jsx6("path", { d: "M12 8h.01" })
4256
+ ] });
4257
+ }
4258
+ function IconArrowRight({ className, ...props }) {
4259
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4260
+ /* @__PURE__ */ jsx6("path", { d: "M5 12h14" }),
4261
+ /* @__PURE__ */ jsx6("path", { d: "m12 5 7 7-7 7" })
4262
+ ] });
4263
+ }
4264
+ function IconSection({ className, ...props }) {
4265
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4266
+ /* @__PURE__ */ jsx6("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
4267
+ /* @__PURE__ */ jsx6("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
4268
+ /* @__PURE__ */ jsx6("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
4269
+ ] });
4270
+ }
4271
+
4272
+ // src/ui/link-modal/DestinationBreadcrumb.tsx
4273
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
4274
+ function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
4275
+ return /* @__PURE__ */ jsxs4("div", { className: "flex w-full flex-col gap-2", children: [
4276
+ /* @__PURE__ */ jsx7("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
4277
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3", children: [
4278
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
4279
+ /* @__PURE__ */ jsx7(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4280
+ /* @__PURE__ */ jsx7("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
4281
+ ] }),
4282
+ /* @__PURE__ */ jsx7(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
4283
+ /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
4284
+ /* @__PURE__ */ jsx7(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4285
+ /* @__PURE__ */ jsx7("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
4286
+ ] })
4287
+ ] })
4288
+ ] });
4289
+ }
4290
+
4291
+ // src/ui/link-modal/SectionTreeItem.tsx
4292
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
4293
+ function SectionTreeItem({ section, onSelect, selected }) {
4294
+ const interactive = Boolean(onSelect);
4295
+ return /* @__PURE__ */ jsxs5("div", { className: "flex h-9 w-full items-end pl-3", children: [
4296
+ /* @__PURE__ */ jsx8(
4297
+ "div",
4298
+ {
4299
+ className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
4300
+ "aria-hidden": true
4301
+ }
4302
+ ),
4303
+ /* @__PURE__ */ jsxs5(
4304
+ "div",
4305
+ {
4306
+ role: interactive ? "button" : void 0,
4307
+ tabIndex: interactive ? 0 : void 0,
4308
+ onClick: interactive ? () => onSelect?.(section) : void 0,
4309
+ onKeyDown: interactive ? (e) => {
4310
+ if (e.key === "Enter" || e.key === " ") {
4311
+ e.preventDefault();
4312
+ onSelect?.(section);
4313
+ }
4314
+ } : void 0,
4315
+ className: cn(
4316
+ "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
4317
+ interactive && "cursor-pointer hover:bg-muted/30",
4318
+ interactive && selected && "border-primary"
4319
+ ),
4320
+ children: [
4321
+ /* @__PURE__ */ jsx8(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4322
+ /* @__PURE__ */ jsx8("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
4323
+ ]
4324
+ }
4325
+ )
4326
+ ] });
4327
+ }
4328
+ function SectionPickerList({ sections, onSelect }) {
4329
+ if (sections.length === 0) {
4330
+ return /* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
4331
+ }
4332
+ return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", children: [
4333
+ /* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
4334
+ sections.map((section) => /* @__PURE__ */ jsx8(SectionTreeItem, { section, onSelect }, section.id))
4335
+ ] });
4336
+ }
4337
+
4338
+ // src/ui/link-modal/UrlOrPageInput.tsx
4339
+ import { useEffect as useEffect2, useId, useRef as useRef2, useState as useState3 } from "react";
4340
+
4341
+ // src/ui/input.tsx
4342
+ import * as React4 from "react";
4343
+ import { jsx as jsx9 } from "react/jsx-runtime";
4344
+ var Input = React4.forwardRef(
4345
+ ({ className, type, ...props }, ref) => {
4346
+ return /* @__PURE__ */ jsx9(
4347
+ "input",
4348
+ {
4349
+ type,
4350
+ ref,
4351
+ "data-slot": "input",
4352
+ className: cn(
4353
+ "flex h-full w-full min-w-0 bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground",
4354
+ className
4355
+ ),
4356
+ ...props
4357
+ }
4358
+ );
4359
+ }
4360
+ );
4361
+ Input.displayName = "Input";
4362
+
4363
+ // src/ui/label.tsx
4364
+ import { Label as LabelPrimitive } from "radix-ui";
4365
+ import { jsx as jsx10 } from "react/jsx-runtime";
4366
+ function Label({ className, ...props }) {
4367
+ return /* @__PURE__ */ jsx10(
4368
+ LabelPrimitive.Root,
4369
+ {
4370
+ "data-slot": "label",
4371
+ className: cn("text-sm font-medium leading-5 text-foreground", className),
4372
+ ...props
4373
+ }
4374
+ );
4375
+ }
4376
+
4377
+ // src/ui/popover.tsx
4378
+ import { Popover as PopoverPrimitive } from "radix-ui";
4379
+ import { jsx as jsx11 } from "react/jsx-runtime";
4380
+ function Popover({ ...props }) {
4381
+ return /* @__PURE__ */ jsx11(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
4382
+ }
4383
+ function PopoverTrigger({ ...props }) {
4384
+ return /* @__PURE__ */ jsx11(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
4385
+ }
4386
+ function PopoverContent({
4387
+ className,
4388
+ align = "start",
4389
+ sideOffset = 6,
4390
+ ...props
4391
+ }) {
4392
+ return /* @__PURE__ */ jsx11(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx11(
4393
+ PopoverPrimitive.Content,
4394
+ {
4395
+ "data-slot": "popover-content",
4396
+ align,
4397
+ sideOffset,
4398
+ className: cn(
4399
+ "z-[2147483647] w-[var(--radix-popover-trigger-width)] overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg outline-none",
4400
+ className
4401
+ ),
4402
+ ...props
4403
+ }
4404
+ ) });
4405
+ }
4406
+
4407
+ // src/ui/link-modal/UrlOrPageInput.tsx
4408
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
4409
+ function FieldChevron({ onClick }) {
4410
+ return /* @__PURE__ */ jsx12(
4411
+ "button",
4412
+ {
4413
+ type: "button",
4414
+ className: "flex shrink-0 items-center justify-end border-0 bg-transparent p-0 pl-4 text-muted-foreground outline-none",
4415
+ onClick,
4416
+ "aria-label": "Open page list",
4417
+ tabIndex: -1,
4418
+ children: /* @__PURE__ */ jsx12(IconChevronDown, {})
4419
+ }
4420
+ );
4421
+ }
4422
+ function UrlOrPageInput({
4423
+ value,
4424
+ selectedPage,
4425
+ placeholder = "Paste a URL or choose a page",
4426
+ dropdownOpen,
4427
+ onDropdownOpenChange,
4428
+ filteredPages,
4429
+ onInputChange,
4430
+ onPageSelect,
4431
+ readOnly = false,
4432
+ urlError
4433
+ }) {
4434
+ const inputId = useId();
4435
+ const inputRef = useRef2(null);
4436
+ const [isEditing, setIsEditing] = useState3(false);
4437
+ const [isFocused, setIsFocused] = useState3(false);
4438
+ const showFilledDisplay = Boolean(selectedPage) && !isEditing && !readOnly;
4439
+ useEffect2(() => {
4440
+ if (selectedPage) setIsEditing(false);
4441
+ }, [selectedPage]);
4442
+ const openDropdown = () => {
4443
+ if (readOnly || filteredPages.length === 0) return;
4444
+ onDropdownOpenChange(true);
4445
+ };
4446
+ const toggleDropdown = (e) => {
4447
+ e.stopPropagation();
4448
+ e.preventDefault();
4449
+ if (readOnly || filteredPages.length === 0) return;
4450
+ onDropdownOpenChange(!dropdownOpen);
4451
+ };
4452
+ const enterEditMode = () => {
4453
+ if (readOnly) return;
4454
+ setIsEditing(true);
4455
+ requestAnimationFrame(() => inputRef.current?.focus());
4456
+ };
4457
+ const fieldClassName = cn(
4458
+ "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]",
4459
+ urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
4460
+ );
4461
+ return /* @__PURE__ */ jsxs6("div", { className: "flex w-full flex-col gap-2 p-0", children: [
4462
+ /* @__PURE__ */ jsx12(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
4463
+ /* @__PURE__ */ jsxs6(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
4464
+ /* @__PURE__ */ jsx12(PopoverTrigger, { asChild: true, children: showFilledDisplay ? /* @__PURE__ */ jsxs6(
4465
+ "button",
4466
+ {
4467
+ type: "button",
4468
+ "data-ohw-link-field": true,
4469
+ className: cn(fieldClassName, "m-0 cursor-pointer border-solid text-left"),
4470
+ onClick: openDropdown,
4471
+ onFocus: () => setIsFocused(true),
4472
+ onBlur: () => setIsFocused(false),
4473
+ onKeyDown: (e) => {
4474
+ if (e.key === "Backspace" || e.key === "Delete" || e.key.length === 1) {
4475
+ enterEditMode();
4476
+ }
4477
+ },
4478
+ children: [
4479
+ /* @__PURE__ */ jsx12("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx12(IconFile, { className: "text-foreground", "aria-hidden": true }) }),
4480
+ /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate text-sm font-normal text-foreground", children: selectedPage.title }),
4481
+ /* @__PURE__ */ jsx12(FieldChevron, { onClick: toggleDropdown })
4482
+ ]
4483
+ }
4484
+ ) : /* @__PURE__ */ jsxs6(
4485
+ "div",
4486
+ {
4487
+ "data-ohw-link-field": true,
4488
+ className: fieldClassName,
4489
+ onMouseDown: (e) => {
4490
+ if (readOnly) return;
4491
+ if (e.target === e.currentTarget) enterEditMode();
4492
+ },
4493
+ children: [
4494
+ selectedPage ? /* @__PURE__ */ jsx12("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx12(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
4495
+ readOnly ? /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx12(
4496
+ Input,
4497
+ {
4498
+ ref: inputRef,
4499
+ id: inputId,
4500
+ value,
4501
+ onChange: (e) => onInputChange(e.target.value),
4502
+ onFocus: () => {
4503
+ setIsFocused(true);
4504
+ setIsEditing(true);
4505
+ openDropdown();
4506
+ },
4507
+ onBlur: () => setIsFocused(false),
4508
+ placeholder,
4509
+ className: cn(
4510
+ "min-w-0 flex-1 truncate border-0 bg-transparent p-0 text-sm font-normal leading-5 shadow-none outline-none ring-0",
4511
+ value ? "text-foreground" : "text-muted-foreground placeholder:text-muted-foreground"
4512
+ )
4513
+ }
4514
+ ),
4515
+ !readOnly ? /* @__PURE__ */ jsx12(FieldChevron, { onClick: toggleDropdown }) : null
4516
+ ]
4517
+ }
4518
+ ) }),
4519
+ /* @__PURE__ */ jsx12(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ jsxs6(
4520
+ "button",
4521
+ {
4522
+ type: "button",
4523
+ 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",
4524
+ onClick: () => onPageSelect(page),
4525
+ children: [
4526
+ /* @__PURE__ */ jsx12(IconFile, { className: "shrink-0", "aria-hidden": true }),
4527
+ /* @__PURE__ */ jsx12("span", { className: "truncate", children: page.title })
4528
+ ]
4529
+ },
4530
+ page.path
4531
+ )) })
4532
+ ] }),
4533
+ urlError ? /* @__PURE__ */ jsx12("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4534
+ ] });
4535
+ }
4536
+
4537
+ // src/ui/link-modal/useLinkModalState.ts
4538
+ import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useState as useState4 } from "react";
4539
+
4540
+ // src/ui/link-modal/linkModal.utils.ts
4541
+ function parseTarget(target) {
4542
+ if (!target) return { pageRoute: "", sectionId: null };
4543
+ const hashIndex = target.indexOf("#");
4544
+ if (hashIndex === -1) return { pageRoute: target, sectionId: null };
4545
+ return {
4546
+ pageRoute: target.slice(0, hashIndex),
4547
+ sectionId: target.slice(hashIndex + 1) || null
4548
+ };
4549
+ }
4550
+ function normalizePath(path) {
4551
+ const trimmed = path.trim();
4552
+ if (!trimmed) return "/";
4553
+ try {
4554
+ if (/^https?:\/\//i.test(trimmed)) {
4555
+ return new URL(trimmed).pathname || "/";
4556
+ }
4557
+ } catch {
4558
+ }
4559
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
4560
+ if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
4561
+ return withSlash;
4562
+ }
4563
+ function findPageByPath(pages, path) {
4564
+ const normalized = normalizePath(path);
4565
+ return pages.find((p) => normalizePath(p.path) === normalized);
4566
+ }
4567
+ function inferPageTitleFromPath(path) {
4568
+ const normalized = normalizePath(path);
4569
+ if (normalized === "/") return "Home";
4570
+ const segment = normalized.split("/").filter(Boolean).pop() ?? "";
4571
+ if (!segment) return "Home";
4572
+ return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
4573
+ }
4574
+ function isInternalPath(route) {
4575
+ const trimmed = route.trim();
4576
+ if (!trimmed) return false;
4577
+ if (/^https?:\/\//i.test(trimmed)) return false;
4578
+ if (trimmed.startsWith("/")) return true;
4579
+ if (trimmed.includes(" ") || trimmed.includes(".")) return false;
4580
+ return true;
4581
+ }
4582
+ function resolvePage(route, pages) {
4583
+ const found = findPageByPath(pages, route);
4584
+ if (found) return { path: found.path, title: found.title };
4585
+ return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
4586
+ }
4587
+ function getSectionsForPath(sectionsByPath, pageRoute, fallback) {
4588
+ if (!sectionsByPath) return fallback;
4589
+ const normalized = normalizePath(pageRoute);
4590
+ return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? fallback;
4591
+ }
4592
+ function buildTarget(page, section) {
4593
+ if (section) return `${page.path}#${section.id}`;
4594
+ return page.path;
4595
+ }
4596
+ function isValidUrl(urlString) {
4597
+ try {
4598
+ const url = new URL(urlString);
4599
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
4600
+ const hostname = url.hostname;
4601
+ if (!hostname || hostname.length === 0) return false;
4602
+ if (hostname.includes(" ")) return false;
4603
+ return hostname.includes(".") || hostname === "localhost";
4604
+ } catch {
4605
+ return false;
4606
+ }
4607
+ }
4608
+ function normalizeUrl(value) {
4609
+ const trimmed = value.trim();
4610
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
4611
+ return `https://${trimmed}`;
4612
+ }
4613
+ function validateUrlInput(value, pages, allPages) {
4614
+ if (!value.trim()) return "";
4615
+ const filtered = pages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase()));
4616
+ if (filtered.length > 0) return "";
4617
+ const matchingPage = allPages.find((p) => p.title.toLowerCase() === value.toLowerCase());
4618
+ if (matchingPage) return "";
4619
+ if (!isValidUrl(normalizeUrl(value))) {
4620
+ return "Please enter a valid URL (e.g., https://example.com)";
4621
+ }
4622
+ return "";
4623
+ }
4624
+ function getEditModeInitialState(target, pages, sections) {
4625
+ const empty = {
4626
+ searchValue: "",
4627
+ selectedPage: null,
4628
+ selectedSection: null,
4629
+ step: "input"
4630
+ };
4631
+ if (!target) return empty;
4632
+ if (isValidUrl(normalizeUrl(target))) {
4633
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
4634
+ }
4635
+ const { pageRoute, sectionId } = parseTarget(target);
4636
+ if (sectionId) {
4637
+ if (isInternalPath(pageRoute)) {
4638
+ const page = resolvePage(pageRoute, pages);
4639
+ const sec = sections.find((s) => s.id === sectionId);
4640
+ return {
4641
+ searchValue: page.title,
4642
+ selectedPage: page,
4643
+ selectedSection: sec ? { id: sec.id, label: sec.label } : null,
4644
+ step: "input"
4645
+ };
4646
+ }
4647
+ return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
4648
+ }
4649
+ if (isInternalPath(pageRoute)) {
4650
+ const page = resolvePage(pageRoute, pages);
4651
+ return {
4652
+ searchValue: page.title,
4653
+ selectedPage: page,
4654
+ selectedSection: null,
4655
+ step: "input"
4656
+ };
4657
+ }
4658
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
4659
+ }
4660
+ function filterAvailablePages(pages, existingTargets) {
4661
+ const taken = new Set(existingTargets);
4662
+ return pages.filter((p) => !taken.has(p.path));
4663
+ }
4664
+
4665
+ // src/ui/link-modal/useLinkModalState.ts
4666
+ function useLinkModalState({
4667
+ open,
4668
+ mode,
4669
+ pages,
4670
+ sections,
4671
+ sectionsByPath,
4672
+ initialTarget,
4673
+ existingTargets,
4674
+ onClose,
4675
+ onSubmit
4676
+ }) {
4677
+ const availablePages = useMemo2(
4678
+ () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
4679
+ [mode, pages, existingTargets]
4680
+ );
4681
+ const [searchValue, setSearchValue] = useState4("");
4682
+ const [selectedPage, setSelectedPage] = useState4(null);
4683
+ const [selectedSection, setSelectedSection] = useState4(null);
4684
+ const [step, setStep] = useState4("input");
4685
+ const [dropdownOpen, setDropdownOpen] = useState4(false);
4686
+ const [urlError, setUrlError] = useState4("");
4687
+ const reset = useCallback(() => {
4688
+ setSearchValue("");
4689
+ setSelectedPage(null);
4690
+ setSelectedSection(null);
4691
+ setStep("input");
4692
+ setDropdownOpen(false);
4693
+ setUrlError("");
4694
+ }, []);
4695
+ useEffect3(() => {
4696
+ if (!open) return;
4697
+ if (mode === "edit" && initialTarget) {
4698
+ const { pageRoute } = parseTarget(initialTarget);
4699
+ const targetSections = getSectionsForPath(sectionsByPath, pageRoute, sections);
4700
+ const init = getEditModeInitialState(initialTarget, pages, targetSections);
4701
+ setSearchValue(init.searchValue);
4702
+ setSelectedPage(init.selectedPage);
4703
+ setSelectedSection(init.selectedSection);
4704
+ setStep(init.step);
4705
+ } else {
4706
+ reset();
4707
+ }
4708
+ setDropdownOpen(false);
4709
+ setUrlError("");
4710
+ }, [open, mode, initialTarget, pages, sections, sectionsByPath, reset]);
4711
+ const filteredPages = useMemo2(() => {
4712
+ if (!searchValue.trim()) return availablePages;
4713
+ return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
4714
+ }, [availablePages, searchValue]);
4715
+ const activeSections = useMemo2(() => {
4716
+ if (selectedPage && sectionsByPath) {
4717
+ return getSectionsForPath(sectionsByPath, selectedPage.path, sections);
4718
+ }
4719
+ return sections;
4720
+ }, [selectedPage, sectionsByPath, sections]);
4721
+ const showBreadcrumb = step === "confirmed" && selectedPage && selectedSection;
4722
+ const showSectionPicker = step === "sectionPicker" && selectedPage;
4723
+ const showSectionRow = mode === "edit" && selectedPage && selectedSection && step === "input";
4724
+ const showChooseSection = step === "input" && selectedPage && !selectedSection && !showSectionRow;
4725
+ const handleInputChange = (value) => {
4726
+ setSearchValue(value);
4727
+ setSelectedPage(null);
4728
+ setSelectedSection(null);
4729
+ setStep("input");
4730
+ setUrlError("");
4731
+ const filtered = value.trim() ? availablePages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase())) : availablePages;
4732
+ setDropdownOpen(filtered.length > 0);
4733
+ if (value.trim() && filtered.length === 0) {
4734
+ const error = validateUrlInput(value, availablePages, pages);
4735
+ if (error) setUrlError(error);
4736
+ }
4737
+ };
4738
+ const handlePageSelect = (page) => {
4739
+ setSelectedPage({ path: page.path, title: page.title });
4740
+ setSelectedSection(null);
4741
+ setSearchValue(page.title);
4742
+ setStep("input");
4743
+ setDropdownOpen(false);
4744
+ setUrlError("");
4745
+ };
4746
+ const handleChooseSection = () => {
4747
+ setStep("sectionPicker");
4748
+ };
4749
+ const handleSectionSelect = (section) => {
4750
+ setSelectedSection({ id: section.id, label: section.label });
4751
+ if (mode === "create") {
4752
+ setStep("confirmed");
4753
+ } else {
4754
+ setStep("input");
4755
+ }
4756
+ };
4757
+ const handleBackToSections = () => {
4758
+ setStep("sectionPicker");
4759
+ };
4760
+ const handleClose = () => {
4761
+ reset();
4762
+ onClose();
4763
+ };
4764
+ const isValid = useMemo2(() => {
4765
+ if (urlError) return false;
4766
+ if (showBreadcrumb) return true;
4767
+ if (selectedPage) return true;
4768
+ if (!searchValue.trim()) return false;
4769
+ return validateUrlInput(searchValue, availablePages, pages) === "";
4770
+ }, [urlError, showBreadcrumb, selectedPage, searchValue, availablePages, pages]);
4771
+ const handleSubmit = () => {
4772
+ if (selectedPage && selectedSection) {
4773
+ onSubmit(buildTarget(selectedPage, selectedSection));
4774
+ handleClose();
4775
+ return;
4776
+ }
4777
+ if (selectedPage) {
4778
+ onSubmit(buildTarget(selectedPage, null));
4779
+ handleClose();
4780
+ return;
4781
+ }
4782
+ if (!searchValue.trim()) return;
4783
+ const error = validateUrlInput(searchValue, availablePages, pages);
4784
+ if (error) {
4785
+ setUrlError(error);
4786
+ return;
4787
+ }
4788
+ const matchingPage = pages.find((p) => p.title.toLowerCase() === searchValue.toLowerCase());
4789
+ if (matchingPage) {
4790
+ onSubmit(matchingPage.path);
4791
+ handleClose();
4792
+ return;
4793
+ }
4794
+ onSubmit(normalizeUrl(searchValue));
4795
+ handleClose();
4796
+ };
4797
+ const submitLabel = mode === "edit" ? "Save" : "Create";
4798
+ const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
4799
+ const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
4800
+ const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
4801
+ return {
4802
+ title,
4803
+ submitLabel,
4804
+ secondaryLabel,
4805
+ searchValue,
4806
+ selectedPage,
4807
+ selectedSection,
4808
+ step,
4809
+ dropdownOpen,
4810
+ setDropdownOpen,
4811
+ urlError,
4812
+ filteredPages,
4813
+ showBreadcrumb,
4814
+ showSectionPicker,
4815
+ showChooseSection,
4816
+ showSectionRow,
4817
+ isValid,
4818
+ activeSections,
4819
+ handleInputChange,
4820
+ handlePageSelect,
4821
+ handleChooseSection,
4822
+ handleSectionSelect,
4823
+ handleBackToSections,
4824
+ handleClose,
4825
+ handleSecondary,
4826
+ handleSubmit
4827
+ };
4828
+ }
4829
+
4830
+ // src/ui/link-modal/LinkEditorPanel.tsx
4831
+ import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
4832
+ function LinkEditorPanel({
4833
+ open = true,
4834
+ mode = "create",
4835
+ pages,
4836
+ sections = [],
4837
+ sectionsByPath,
4838
+ initialTarget,
4839
+ existingTargets = [],
4840
+ onClose,
4841
+ onSubmit
4842
+ }) {
4843
+ const state = useLinkModalState({
4844
+ open,
4845
+ mode,
4846
+ pages,
4847
+ sections,
4848
+ sectionsByPath,
4849
+ initialTarget,
4850
+ existingTargets,
4851
+ onClose,
4852
+ onSubmit
4853
+ });
4854
+ const isCancel = state.secondaryLabel === "Cancel";
4855
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
4856
+ /* @__PURE__ */ jsx13(
4857
+ "button",
4858
+ {
4859
+ type: "button",
4860
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4861
+ onClick: state.handleClose,
4862
+ "aria-label": "Close",
4863
+ children: /* @__PURE__ */ jsx13(IconX, { "aria-hidden": true })
4864
+ }
4865
+ ),
4866
+ /* @__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 }) }),
4867
+ /* @__PURE__ */ jsxs7("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
4868
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx13(
4869
+ DestinationBreadcrumb,
4870
+ {
4871
+ pageTitle: state.selectedPage.title,
4872
+ sectionLabel: state.selectedSection.label
4873
+ }
4874
+ ) : /* @__PURE__ */ jsx13(
4875
+ UrlOrPageInput,
4876
+ {
4877
+ value: state.searchValue,
4878
+ selectedPage: state.selectedPage,
4879
+ dropdownOpen: state.dropdownOpen,
4880
+ onDropdownOpenChange: state.setDropdownOpen,
4881
+ filteredPages: state.filteredPages,
4882
+ onInputChange: state.handleInputChange,
4883
+ onPageSelect: state.handlePageSelect,
4884
+ urlError: state.urlError
4885
+ }
4886
+ ),
4887
+ state.showChooseSection ? /* @__PURE__ */ jsxs7("div", { className: "flex flex-col justify-center gap-2", children: [
4888
+ /* @__PURE__ */ jsx13(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
4889
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
4890
+ /* @__PURE__ */ jsx13(IconInfo, { className: "shrink-0", "aria-hidden": true }),
4891
+ /* @__PURE__ */ jsx13("span", { children: "Pick a section this link should scroll to." })
4892
+ ] })
4893
+ ] }) : null,
4894
+ state.showSectionPicker ? /* @__PURE__ */ jsx13(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
4895
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx13(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
4896
+ ] }),
4897
+ /* @__PURE__ */ jsxs7("footer", { className: "flex w-full items-center justify-end gap-2 px-6 pb-6", children: [
4898
+ /* @__PURE__ */ jsx13(
4899
+ Button,
4900
+ {
4901
+ type: "button",
4902
+ variant: isCancel ? "outline" : "ghost",
4903
+ className: "leading-6 shadow-none cursor-pointer",
4904
+ style: isCancel ? {
4905
+ backgroundColor: "var(--ohw-background)",
4906
+ borderColor: "var(--ohw-border)",
4907
+ color: "var(--ohw-foreground)"
4908
+ } : void 0,
4909
+ onClick: state.handleSecondary,
4910
+ children: state.secondaryLabel
4911
+ }
4912
+ ),
4913
+ /* @__PURE__ */ jsx13(
4914
+ Button,
4915
+ {
4916
+ type: "button",
4917
+ className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
4918
+ style: {
4919
+ backgroundColor: "var(--ohw-primary)",
4920
+ color: "var(--ohw-primary-foreground)"
4921
+ },
4922
+ disabled: !state.isValid,
4923
+ onClick: state.handleSubmit,
4924
+ children: state.submitLabel
4925
+ }
4926
+ )
4927
+ ] })
4928
+ ] });
4929
+ }
4930
+
4931
+ // src/ui/link-modal/LinkPopover.tsx
4932
+ import { jsx as jsx14 } from "react/jsx-runtime";
4933
+ function LinkPopover({
4934
+ rect,
4935
+ parentScroll,
4936
+ panelRef,
4937
+ ...editorProps
4938
+ }) {
4939
+ const { top, left, transform } = calcPopoverPos(rect, parentScroll);
4940
+ return /* @__PURE__ */ jsx14(
4941
+ "div",
4942
+ {
4943
+ ref: panelRef,
4944
+ "data-ohw-link-popover-root": "",
4945
+ "data-ohw-link-modal-root": "",
4946
+ "data-ohw-bridge": "",
4947
+ className: cn(
4948
+ "pointer-events-auto fixed z-[2147483647] flex w-[483px] max-w-[calc(100vw-16px)] flex-col overflow-hidden",
4949
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none"
4950
+ ),
4951
+ style: { top, left, transform },
4952
+ onMouseDown: (e) => e.stopPropagation(),
4953
+ children: /* @__PURE__ */ jsx14(LinkEditorPanel, { ...editorProps, open: true })
4954
+ }
4955
+ );
4956
+ }
4957
+
4958
+ // src/ui/link-modal/devFixtures.ts
4959
+ var DEV_SITE_PAGES = [
4960
+ { path: "/", title: "Home" },
4961
+ { path: "/about", title: "About" },
4962
+ { path: "/classes", title: "Classes" },
4963
+ { path: "/pricing", title: "Pricing" },
4964
+ { path: "/studio", title: "Studio" },
4965
+ { path: "/instructors", title: "Instructors" },
4966
+ { path: "/contact", title: "Contact" }
4967
+ ];
4968
+ var DEV_SECTIONS_BY_PATH = {
4969
+ "/": [
4970
+ { id: "hero", label: "Hero" },
4971
+ { id: "lagree-intro", label: "Lagree Intro" },
4972
+ { id: "classes-strip", label: "Classes" },
4973
+ { id: "feel-split", label: "Feel Split" },
4974
+ { id: "founder-teaser", label: "Founder" },
4975
+ { id: "plan-form", label: "Plan Form" },
4976
+ { id: "testimonials", label: "Testimonials" },
4977
+ { id: "wordmark", label: "Wordmark" }
4978
+ ],
4979
+ "/about": [
4980
+ { id: "manifesto", label: "Manifesto" },
4981
+ { id: "story-letter", label: "Our Story" },
4982
+ { id: "lagree-explainer", label: "Lagree Explainer" },
4983
+ { id: "waitlist-cta", label: "Waitlist" },
4984
+ { id: "personal-training", label: "Personal training" }
4985
+ ],
4986
+ "/classes": [{ id: "class-library", label: "Class Library" }],
4987
+ "/pricing": [
4988
+ { id: "benefits-marquee", label: "Benefits" },
4989
+ { id: "monthly-memberships", label: "Memberships" },
4990
+ { id: "package-matcher", label: "Packages" },
4991
+ { id: "specialty-programs", label: "Specialty Programs" },
4992
+ { id: "free-class-cta", label: "Free Class" }
4993
+ ],
4994
+ "/studio": [
4995
+ { id: "studio-intro", label: "Studio Intro" },
4996
+ { id: "studio-features", label: "Studio Features" },
4997
+ { id: "studio-gallery", label: "Studio Gallery" },
4998
+ { id: "studio-visit", label: "Visit Studio" }
4999
+ ],
5000
+ "/instructors": [{ id: "instructor-grid", label: "Instructors" }],
5001
+ "/contact": [
5002
+ { id: "hello-hero", label: "Hello" },
5003
+ { id: "contact-form", label: "Contact Form" },
5004
+ { id: "faq", label: "FAQ" }
5005
+ ]
5006
+ };
5007
+ function shouldUseDevFixtures() {
5008
+ if (typeof window === "undefined") return false;
5009
+ const raw = readPreservedSearch();
5010
+ const q = raw.startsWith("?") ? raw.slice(1) : raw;
5011
+ return new URLSearchParams(q).get("ohw-fixtures") === "1";
5012
+ }
5013
+
5014
+ // src/ui/badge.tsx
5015
+ import { jsx as jsx15 } from "react/jsx-runtime";
4190
5016
  var badgeVariants = cva(
4191
5017
  "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",
4192
5018
  {
@@ -4204,11 +5030,11 @@ var badgeVariants = cva(
4204
5030
  }
4205
5031
  );
4206
5032
  function Badge({ className, variant, ...props }) {
4207
- return /* @__PURE__ */ jsx5("div", { className: cn(badgeVariants({ variant }), className), ...props });
5033
+ return /* @__PURE__ */ jsx15("div", { className: cn(badgeVariants({ variant }), className), ...props });
4208
5034
  }
4209
5035
 
4210
5036
  // src/OhhwellsBridge.tsx
4211
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
5037
+ import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
4212
5038
  var PRIMARY = "#0885FE";
4213
5039
  var IMAGE_FADE_MS = 300;
4214
5040
  function runOpacityFade(el, onDone) {
@@ -4348,14 +5174,9 @@ function initSectionsFromContent(content, removeExisting = false) {
4348
5174
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
4349
5175
  if (inEditor) getSectionsTracker().textContent = raw;
4350
5176
  const pageEntries = getPageSchedulingEntries(raw);
4351
- const preExisting = pageEntries.filter((e) => !isSchedulingWidgetMissing(e));
4352
- const notifyForEntry = inEditor ? (e) => !e.scheduleId : false;
4353
- mountSchedulingEntries(pageEntries, notifyForEntry);
4354
- requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry));
4355
- setTimeout(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry), 250);
4356
- for (const entry of preExisting) {
4357
- window.postMessage({ type: "ow:schedule-config", insertAfter: entry.insertAfter, scheduleId: entry.scheduleId ?? null }, "*");
4358
- }
5177
+ mountSchedulingEntries(pageEntries, false);
5178
+ requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, false));
5179
+ setTimeout(() => retryMissingSchedulingMounts(pageEntries, false), 250);
4359
5180
  } catch {
4360
5181
  }
4361
5182
  }
@@ -4382,7 +5203,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
4382
5203
  const root = createRoot(container);
4383
5204
  flushSync(() => {
4384
5205
  root.render(
4385
- /* @__PURE__ */ jsx6(
5206
+ /* @__PURE__ */ jsx16(
4386
5207
  SchedulingWidget,
4387
5208
  {
4388
5209
  notifyOnConnect,
@@ -4414,16 +5235,23 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
4414
5235
  const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
4415
5236
  for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
4416
5237
  for (let i = pending.length - 1; i >= 0; i--) {
4417
- const entry = pending[i];
4418
- const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
4419
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
5238
+ const { insertAfter, scheduleId } = pending[i];
5239
+ if (mountSchedulingWidget(insertAfter, notifyOnConnect, scheduleId)) {
4420
5240
  pending.splice(i, 1);
4421
5241
  }
4422
5242
  }
4423
5243
  }
4424
5244
  }
5245
+ function getLinkHref(el) {
5246
+ const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5247
+ return anchor?.getAttribute("href") ?? "";
5248
+ }
5249
+ function applyLinkHref(el, val) {
5250
+ const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5251
+ if (anchor) anchor.setAttribute("href", val);
5252
+ }
4425
5253
  function collectEditableNodes() {
4426
- return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
5254
+ const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
4427
5255
  if (el.dataset.ohwEditable === "image") {
4428
5256
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
4429
5257
  return { key: el.dataset.ohwKey ?? "", type: "image", text: img?.src ?? "" };
@@ -4433,12 +5261,52 @@ function collectEditableNodes() {
4433
5261
  const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
4434
5262
  return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
4435
5263
  }
5264
+ if (el.dataset.ohwEditable === "link") {
5265
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
5266
+ }
4436
5267
  return {
4437
5268
  key: el.dataset.ohwKey ?? "",
4438
5269
  type: el.dataset.ohwEditable ?? "text",
4439
5270
  text: el.dataset.ohwEditable === "plain" ? el.innerText ?? "" : el.innerHTML
4440
5271
  };
4441
5272
  });
5273
+ document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
5274
+ const key = el.getAttribute("data-ohw-href-key") ?? "";
5275
+ if (!key) return;
5276
+ nodes.push({ key, type: "link", text: getLinkHref(el) });
5277
+ });
5278
+ return nodes;
5279
+ }
5280
+ function applyLinkByKey(key, val) {
5281
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
5282
+ if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
5283
+ });
5284
+ document.querySelectorAll(`[data-ohw-href-key="${key}"]`).forEach((el) => {
5285
+ applyLinkHref(el, val);
5286
+ });
5287
+ }
5288
+ function isInsideLinkEditor(target) {
5289
+ return Boolean(
5290
+ target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest('[data-slot="popover-content"]')
5291
+ );
5292
+ }
5293
+ function getHrefKeyFromElement(el) {
5294
+ if (!el) return null;
5295
+ const anchor = el.closest("[data-ohw-href-key]");
5296
+ if (!anchor) return null;
5297
+ const key = anchor.getAttribute("data-ohw-href-key");
5298
+ if (!key) return null;
5299
+ return { anchor, key };
5300
+ }
5301
+ function titleCaseSectionId(id) {
5302
+ return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
5303
+ }
5304
+ function collectSections() {
5305
+ return Array.from(document.querySelectorAll("[data-ohw-section]")).map((el) => {
5306
+ const id = el.getAttribute("data-ohw-section") ?? "";
5307
+ const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
5308
+ return { id, label };
5309
+ });
4442
5310
  }
4443
5311
  var FORCE_PSEUDO_STATES = [
4444
5312
  { pseudo: ":hover", state: "hover" },
@@ -4579,7 +5447,7 @@ var TOOLBAR_GROUPS = [
4579
5447
  ];
4580
5448
  function GlowFrame({ rect, elRef }) {
4581
5449
  const GAP = 6;
4582
- return /* @__PURE__ */ jsx6(
5450
+ return /* @__PURE__ */ jsx16(
4583
5451
  "div",
4584
5452
  {
4585
5453
  ref: elRef,
@@ -4627,10 +5495,12 @@ function FloatingToolbar({
4627
5495
  parentScroll,
4628
5496
  elRef,
4629
5497
  onCommand,
4630
- activeCommands
5498
+ activeCommands,
5499
+ showEditLink,
5500
+ onEditLink
4631
5501
  }) {
4632
5502
  const { top, left, transform } = calcToolbarPos(rect, parentScroll);
4633
- return /* @__PURE__ */ jsx6(
5503
+ return /* @__PURE__ */ jsxs8(
4634
5504
  "div",
4635
5505
  {
4636
5506
  ref: elRef,
@@ -4653,56 +5523,97 @@ function FloatingToolbar({
4653
5523
  pointerEvents: "auto",
4654
5524
  whiteSpace: "nowrap"
4655
5525
  },
4656
- children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs3(React3.Fragment, { children: [
4657
- gi > 0 && /* @__PURE__ */ jsx6("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
4658
- btns.map((btn) => {
4659
- const isActive = activeCommands.has(btn.cmd);
4660
- return /* @__PURE__ */ jsx6(
4661
- "button",
4662
- {
4663
- title: btn.title,
4664
- onMouseDown: (e) => {
4665
- e.preventDefault();
4666
- onCommand(btn.cmd);
4667
- },
4668
- onMouseEnter: (e) => {
4669
- if (!isActive) e.currentTarget.style.background = "#F5F5F4";
4670
- },
4671
- onMouseLeave: (e) => {
4672
- if (!isActive) e.currentTarget.style.background = "transparent";
4673
- },
4674
- style: {
4675
- display: "flex",
4676
- alignItems: "center",
4677
- justifyContent: "center",
4678
- border: "none",
4679
- background: isActive ? PRIMARY : "transparent",
4680
- borderRadius: 4,
4681
- cursor: "pointer",
4682
- color: isActive ? "#FFFFFF" : "#1C1917",
4683
- flexShrink: 0,
4684
- padding: 6
5526
+ onMouseDown: (e) => e.stopPropagation(),
5527
+ children: [
5528
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs8(React5.Fragment, { children: [
5529
+ gi > 0 && /* @__PURE__ */ jsx16("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5530
+ btns.map((btn) => {
5531
+ const isActive = activeCommands.has(btn.cmd);
5532
+ return /* @__PURE__ */ jsx16(
5533
+ "button",
5534
+ {
5535
+ title: btn.title,
5536
+ onMouseDown: (e) => {
5537
+ e.preventDefault();
5538
+ onCommand(btn.cmd);
5539
+ },
5540
+ onMouseEnter: (e) => {
5541
+ if (!isActive) e.currentTarget.style.background = "#F5F5F4";
5542
+ },
5543
+ onMouseLeave: (e) => {
5544
+ if (!isActive) e.currentTarget.style.background = "transparent";
5545
+ },
5546
+ style: {
5547
+ display: "flex",
5548
+ alignItems: "center",
5549
+ justifyContent: "center",
5550
+ border: "none",
5551
+ background: isActive ? PRIMARY : "transparent",
5552
+ borderRadius: 4,
5553
+ cursor: "pointer",
5554
+ color: isActive ? "#FFFFFF" : "#1C1917",
5555
+ flexShrink: 0,
5556
+ padding: 6
5557
+ },
5558
+ children: /* @__PURE__ */ jsx16(
5559
+ "svg",
5560
+ {
5561
+ width: "16",
5562
+ height: "16",
5563
+ viewBox: "0 0 24 24",
5564
+ fill: "none",
5565
+ stroke: isActive ? "#FFFFFF" : "#1C1917",
5566
+ strokeWidth: "2.5",
5567
+ strokeLinecap: "round",
5568
+ strokeLinejoin: "round",
5569
+ style: isActive && gi > 0 ? { filter: "drop-shadow(0 0 0.5px #fff)" } : void 0,
5570
+ dangerouslySetInnerHTML: { __html: ICONS[btn.cmd] }
5571
+ }
5572
+ )
4685
5573
  },
4686
- children: /* @__PURE__ */ jsx6(
4687
- "svg",
4688
- {
4689
- width: "16",
4690
- height: "16",
4691
- viewBox: "0 0 24 24",
4692
- fill: "none",
4693
- stroke: isActive ? "#FFFFFF" : "#1C1917",
4694
- strokeWidth: "2.5",
4695
- strokeLinecap: "round",
4696
- strokeLinejoin: "round",
4697
- style: isActive && gi > 0 ? { filter: "drop-shadow(0 0 0.5px #fff)" } : void 0,
4698
- dangerouslySetInnerHTML: { __html: ICONS[btn.cmd] }
4699
- }
4700
- )
5574
+ btn.cmd
5575
+ );
5576
+ })
5577
+ ] }, gi)),
5578
+ showEditLink ? /* @__PURE__ */ jsx16(
5579
+ "button",
5580
+ {
5581
+ type: "button",
5582
+ title: "Edit link",
5583
+ onMouseDown: (e) => {
5584
+ e.preventDefault();
5585
+ e.stopPropagation();
5586
+ onEditLink?.();
5587
+ },
5588
+ onClick: (e) => {
5589
+ e.preventDefault();
5590
+ e.stopPropagation();
5591
+ },
5592
+ onMouseEnter: (e) => {
5593
+ e.currentTarget.style.background = "#F5F5F4";
5594
+ },
5595
+ onMouseLeave: (e) => {
5596
+ e.currentTarget.style.background = "transparent";
5597
+ },
5598
+ style: {
5599
+ display: "flex",
5600
+ alignItems: "center",
5601
+ justifyContent: "center",
5602
+ border: "none",
5603
+ background: "transparent",
5604
+ borderRadius: 4,
5605
+ cursor: "pointer",
5606
+ color: "#1C1917",
5607
+ flexShrink: 0,
5608
+ padding: 6
4701
5609
  },
4702
- btn.cmd
4703
- );
4704
- })
4705
- ] }, gi))
5610
+ 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: [
5611
+ /* @__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" }),
5612
+ /* @__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" })
5613
+ ] })
5614
+ }
5615
+ ) : null
5616
+ ]
4706
5617
  }
4707
5618
  );
4708
5619
  }
@@ -4716,7 +5627,7 @@ function StateToggle({
4716
5627
  states,
4717
5628
  onStateChange
4718
5629
  }) {
4719
- return /* @__PURE__ */ jsx6(
5630
+ return /* @__PURE__ */ jsx16(
4720
5631
  ToggleGroup,
4721
5632
  {
4722
5633
  "data-ohw-state-toggle": "",
@@ -4730,7 +5641,7 @@ function StateToggle({
4730
5641
  left: rect.right - 8,
4731
5642
  transform: "translateX(-100%)"
4732
5643
  },
4733
- children: states.map((state) => /* @__PURE__ */ jsx6(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
5644
+ children: states.map((state) => /* @__PURE__ */ jsx16(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
4734
5645
  }
4735
5646
  );
4736
5647
  }
@@ -4757,8 +5668,8 @@ function OhhwellsBridge() {
4757
5668
  const router = useRouter();
4758
5669
  const searchParams = useSearchParams();
4759
5670
  const isEditMode = isEditSessionActive();
4760
- const [bridgeRoot, setBridgeRoot] = useState3(null);
4761
- useEffect2(() => {
5671
+ const [bridgeRoot, setBridgeRoot] = useState5(null);
5672
+ useEffect4(() => {
4762
5673
  const figtreeFontId = "ohw-figtree-font";
4763
5674
  if (!document.getElementById(figtreeFontId)) {
4764
5675
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -4782,41 +5693,75 @@ function OhhwellsBridge() {
4782
5693
  }, []);
4783
5694
  const subdomainFromQuery = searchParams.get("subdomain");
4784
5695
  const subdomain = resolveSubdomain(subdomainFromQuery);
4785
- const postToParent = useCallback((data) => {
5696
+ const postToParent = useCallback2((data) => {
4786
5697
  if (typeof window !== "undefined" && window.parent !== window) {
4787
5698
  window.parent.postMessage(data, "*");
4788
5699
  }
4789
5700
  }, []);
4790
- const [fetchState, setFetchState] = useState3("idle");
4791
- const autoSaveTimers = useRef2(/* @__PURE__ */ new Map());
4792
- const activeElRef = useRef2(null);
4793
- const originalContentRef = useRef2(null);
4794
- const activeStateElRef = useRef2(null);
4795
- const parentScrollRef = useRef2(null);
4796
- const toolbarElRef = useRef2(null);
4797
- const glowElRef = useRef2(null);
4798
- const hoveredImageRef = useRef2(null);
4799
- const hoveredImageHasTextOverlapRef = useRef2(false);
4800
- const hoveredGapRef = useRef2(null);
4801
- const imageUnhoverTimerRef = useRef2(null);
4802
- const imageShowTimerRef = useRef2(null);
4803
- const editStylesRef = useRef2(null);
4804
- const activateRef = useRef2(() => {
5701
+ const [fetchState, setFetchState] = useState5("idle");
5702
+ const autoSaveTimers = useRef3(/* @__PURE__ */ new Map());
5703
+ const activeElRef = useRef3(null);
5704
+ const originalContentRef = useRef3(null);
5705
+ const activeStateElRef = useRef3(null);
5706
+ const parentScrollRef = useRef3(null);
5707
+ const toolbarElRef = useRef3(null);
5708
+ const glowElRef = useRef3(null);
5709
+ const hoveredImageRef = useRef3(null);
5710
+ const hoveredImageHasTextOverlapRef = useRef3(false);
5711
+ const hoveredGapRef = useRef3(null);
5712
+ const imageUnhoverTimerRef = useRef3(null);
5713
+ const imageShowTimerRef = useRef3(null);
5714
+ const editStylesRef = useRef3(null);
5715
+ const activateRef = useRef3(() => {
4805
5716
  });
4806
- const deactivateRef = useRef2(() => {
5717
+ const deactivateRef = useRef3(() => {
4807
5718
  });
4808
- const refreshActiveCommandsRef = useRef2(() => {
5719
+ const refreshActiveCommandsRef = useRef3(() => {
4809
5720
  });
4810
- const postToParentRef = useRef2(postToParent);
5721
+ const postToParentRef = useRef3(postToParent);
4811
5722
  postToParentRef.current = postToParent;
4812
- const sectionsLoadedRef = useRef2(false);
4813
- const pendingScheduleConfigRequests = useRef2([]);
4814
- const [toolbarRect, setToolbarRect] = useState3(null);
4815
- const [toggleState, setToggleState] = useState3(null);
4816
- const [maxBadge, setMaxBadge] = useState3(null);
4817
- const [activeCommands, setActiveCommands] = useState3(/* @__PURE__ */ new Set());
4818
- const [sectionGap, setSectionGap] = useState3(null);
4819
- useEffect2(() => {
5723
+ const [toolbarRect, setToolbarRect] = useState5(null);
5724
+ const [toggleState, setToggleState] = useState5(null);
5725
+ const [maxBadge, setMaxBadge] = useState5(null);
5726
+ const [activeCommands, setActiveCommands] = useState5(/* @__PURE__ */ new Set());
5727
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState5(false);
5728
+ const [linkPopover, setLinkPopover] = useState5(null);
5729
+ const [sitePages, setSitePages] = useState5([]);
5730
+ const [sectionsByPath, setSectionsByPath] = useState5({});
5731
+ const setLinkPopoverRef = useRef3(setLinkPopover);
5732
+ const linkPopoverPanelRef = useRef3(null);
5733
+ const linkPopoverOpenRef = useRef3(false);
5734
+ const linkPopoverGraceUntilRef = useRef3(0);
5735
+ setLinkPopoverRef.current = setLinkPopover;
5736
+ const bumpLinkPopoverGrace = () => {
5737
+ linkPopoverGraceUntilRef.current = Date.now() + 350;
5738
+ };
5739
+ useEffect4(() => {
5740
+ if (!isEditMode) return;
5741
+ const useFixtures = shouldUseDevFixtures();
5742
+ if (useFixtures) {
5743
+ setSitePages(DEV_SITE_PAGES);
5744
+ setSectionsByPath(DEV_SECTIONS_BY_PATH);
5745
+ if (process.env.NODE_ENV === "development") {
5746
+ console.info("[ohhwells-bridge] Dev fixtures loaded (ohw-fixtures=1)");
5747
+ }
5748
+ }
5749
+ const onSitePages = (e) => {
5750
+ if (e.data?.type !== "ow:site-pages" || !Array.isArray(e.data.pages)) return;
5751
+ if (useFixtures) return;
5752
+ setSitePages(
5753
+ e.data.pages.map((p) => ({
5754
+ path: p.path,
5755
+ title: p.title
5756
+ }))
5757
+ );
5758
+ };
5759
+ window.addEventListener("message", onSitePages);
5760
+ if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
5761
+ return () => window.removeEventListener("message", onSitePages);
5762
+ }, [isEditMode, postToParent]);
5763
+ const [sectionGap, setSectionGap] = useState5(null);
5764
+ useEffect4(() => {
4820
5765
  const update = () => {
4821
5766
  const el = activeElRef.current;
4822
5767
  if (el) setToolbarRect(el.getBoundingClientRect());
@@ -4840,33 +5785,10 @@ function OhhwellsBridge() {
4840
5785
  vvp.removeEventListener("resize", update);
4841
5786
  };
4842
5787
  }, []);
4843
- const refreshStateRules = useCallback(() => {
5788
+ const refreshStateRules = useCallback2(() => {
4844
5789
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
4845
5790
  }, []);
4846
- const processConfigRequest = useCallback((insertAfterVal) => {
4847
- const tracker = getSectionsTracker();
4848
- let entries = [];
4849
- try {
4850
- entries = JSON.parse(tracker.textContent || "[]");
4851
- } catch {
4852
- }
4853
- const path = window.location.pathname;
4854
- const entry = entries.find(
4855
- (e) => e.type === "scheduling" && e.insertAfter === insertAfterVal && (!e.pagePath || e.pagePath === path)
4856
- );
4857
- if (entry) {
4858
- window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: entry.scheduleId ?? null }, "*");
4859
- return;
4860
- }
4861
- const newEntry = { type: "scheduling", insertAfter: insertAfterVal, pagePath: path };
4862
- entries.push(newEntry);
4863
- tracker.textContent = JSON.stringify(entries);
4864
- if (isEditMode) {
4865
- postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
4866
- }
4867
- window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
4868
- }, [isEditMode]);
4869
- const deactivate = useCallback(() => {
5791
+ const deactivate = useCallback2(() => {
4870
5792
  const el = activeElRef.current;
4871
5793
  if (!el) return;
4872
5794
  const key = el.dataset.ohwKey;
@@ -4889,9 +5811,10 @@ function OhhwellsBridge() {
4889
5811
  setToolbarRect(null);
4890
5812
  setMaxBadge(null);
4891
5813
  setActiveCommands(/* @__PURE__ */ new Set());
5814
+ setToolbarShowEditLink(false);
4892
5815
  postToParent({ type: "ow:exit-edit" });
4893
5816
  }, [postToParent]);
4894
- const activate = useCallback((el) => {
5817
+ const activate = useCallback2((el) => {
4895
5818
  if (activeElRef.current === el) return;
4896
5819
  deactivate();
4897
5820
  if (hoveredImageRef.current) {
@@ -4904,6 +5827,7 @@ function OhhwellsBridge() {
4904
5827
  originalContentRef.current = el.innerHTML;
4905
5828
  el.focus();
4906
5829
  setToolbarRect(el.getBoundingClientRect());
5830
+ setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
4907
5831
  postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
4908
5832
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
4909
5833
  }, [deactivate, postToParent]);
@@ -4931,14 +5855,15 @@ function OhhwellsBridge() {
4931
5855
  } else if (el.dataset.ohwEditable === "bg-image") {
4932
5856
  const next = `url('${val}')`;
4933
5857
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
5858
+ } else if (el.dataset.ohwEditable === "link") {
5859
+ applyLinkHref(el, val);
4934
5860
  } else if (el.innerHTML !== val) {
4935
5861
  el.innerHTML = val;
4936
5862
  }
4937
5863
  });
5864
+ applyLinkByKey(key, val);
4938
5865
  }
4939
5866
  initSectionsFromContent(content, true);
4940
- sectionsLoadedRef.current = true;
4941
- pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
4942
5867
  return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
4943
5868
  }) : Promise.resolve();
4944
5869
  };
@@ -4963,7 +5888,7 @@ function OhhwellsBridge() {
4963
5888
  cancelled = true;
4964
5889
  };
4965
5890
  }, [subdomain, isEditMode, pathname]);
4966
- useEffect2(() => {
5891
+ useEffect4(() => {
4967
5892
  if (!subdomain || isEditMode) return;
4968
5893
  let debounceTimer = null;
4969
5894
  const applyFromCache = () => {
@@ -4979,10 +5904,13 @@ function OhhwellsBridge() {
4979
5904
  } else if (el.dataset.ohwEditable === "bg-image") {
4980
5905
  const next = `url('${val}')`;
4981
5906
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
5907
+ } else if (el.dataset.ohwEditable === "link") {
5908
+ applyLinkHref(el, val);
4982
5909
  } else if (el.innerHTML !== val) {
4983
5910
  el.innerHTML = val;
4984
5911
  }
4985
5912
  });
5913
+ applyLinkByKey(key, val);
4986
5914
  }
4987
5915
  };
4988
5916
  const scheduleApply = () => {
@@ -5003,10 +5931,10 @@ function OhhwellsBridge() {
5003
5931
  const visible = Boolean(subdomain) && fetchState !== "done";
5004
5932
  el.style.display = visible ? "flex" : "none";
5005
5933
  }, [subdomain, fetchState]);
5006
- useEffect2(() => {
5934
+ useEffect4(() => {
5007
5935
  postToParent({ type: "ow:navigation", path: pathname });
5008
5936
  }, [pathname, postToParent]);
5009
- useEffect2(() => {
5937
+ useEffect4(() => {
5010
5938
  if (!isEditMode) return;
5011
5939
  const measure = () => {
5012
5940
  const h = document.body.scrollHeight;
@@ -5030,7 +5958,7 @@ function OhhwellsBridge() {
5030
5958
  window.removeEventListener("resize", handleResize);
5031
5959
  };
5032
5960
  }, [pathname, isEditMode, postToParent]);
5033
- useEffect2(() => {
5961
+ useEffect4(() => {
5034
5962
  if (!subdomainFromQuery || isEditMode) return;
5035
5963
  const handleClick = (e) => {
5036
5964
  const anchor = e.target.closest("a");
@@ -5046,7 +5974,7 @@ function OhhwellsBridge() {
5046
5974
  document.addEventListener("click", handleClick, true);
5047
5975
  return () => document.removeEventListener("click", handleClick, true);
5048
5976
  }, [subdomainFromQuery, isEditMode, router]);
5049
- useEffect2(() => {
5977
+ useEffect4(() => {
5050
5978
  if (!isEditMode) {
5051
5979
  editStylesRef.current?.base.remove();
5052
5980
  editStylesRef.current?.forceHover.remove();
@@ -5072,6 +6000,7 @@ function OhhwellsBridge() {
5072
6000
  [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]) { cursor: text !important; }
5073
6001
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
5074
6002
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
6003
+ [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
5075
6004
  [data-ohw-hovered]:not([contenteditable]) {
5076
6005
  outline: 2px dashed ${PRIMARY} !important;
5077
6006
  outline-offset: 4px;
@@ -5109,8 +6038,21 @@ function OhhwellsBridge() {
5109
6038
  if (target.closest("[data-ohw-toolbar]")) return;
5110
6039
  if (target.closest("[data-ohw-state-toggle]")) return;
5111
6040
  if (target.closest("[data-ohw-max-badge]")) return;
6041
+ if (isInsideLinkEditor(target)) return;
5112
6042
  const editable = target.closest("[data-ohw-editable]");
5113
6043
  if (editable) {
6044
+ if (editable.dataset.ohwEditable === "link") {
6045
+ e.preventDefault();
6046
+ e.stopPropagation();
6047
+ deactivateRef.current();
6048
+ bumpLinkPopoverGrace();
6049
+ setLinkPopoverRef.current({
6050
+ key: editable.dataset.ohwKey ?? "",
6051
+ target: getLinkHref(editable),
6052
+ rect: editable.getBoundingClientRect()
6053
+ });
6054
+ return;
6055
+ }
5114
6056
  if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
5115
6057
  e.preventDefault();
5116
6058
  e.stopPropagation();
@@ -5131,6 +6073,15 @@ function OhhwellsBridge() {
5131
6073
  }
5132
6074
  const anchor = target.closest("a");
5133
6075
  if (anchor) e.preventDefault();
6076
+ if (Date.now() < linkPopoverGraceUntilRef.current) return;
6077
+ if (linkPopoverOpenRef.current && activeElRef.current) {
6078
+ const hrefCtx = getHrefKeyFromElement(activeElRef.current);
6079
+ if (hrefCtx && (hrefCtx.anchor === target || hrefCtx.anchor.contains(target))) return;
6080
+ }
6081
+ if (linkPopoverOpenRef.current) {
6082
+ setLinkPopoverRef.current(null);
6083
+ return;
6084
+ }
5134
6085
  deactivateRef.current();
5135
6086
  };
5136
6087
  const handleMouseOver = (e) => {
@@ -5581,25 +6532,35 @@ function OhhwellsBridge() {
5581
6532
  if (img) img.src = val;
5582
6533
  } else if (el.dataset.ohwEditable === "bg-image") {
5583
6534
  el.style.backgroundImage = `url('${val}')`;
6535
+ } else if (el.dataset.ohwEditable === "link") {
6536
+ applyLinkHref(el, val);
5584
6537
  } else {
5585
6538
  el.innerHTML = val;
5586
6539
  }
5587
6540
  });
6541
+ applyLinkByKey(key, val);
5588
6542
  }
5589
6543
  if (sectionsJson) {
5590
6544
  initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
5591
- sectionsLoadedRef.current = true;
5592
- pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
5593
6545
  }
5594
6546
  postToParentRef.current({ type: "ow:hydrate-done" });
5595
6547
  };
5596
6548
  window.addEventListener("message", handleHydrate);
5597
6549
  const handleDeactivate = (e) => {
5598
6550
  if (e.data?.type !== "ow:deactivate") return;
6551
+ if (Date.now() < linkPopoverGraceUntilRef.current) return;
6552
+ if (linkPopoverOpenRef.current) {
6553
+ setLinkPopoverRef.current(null);
6554
+ return;
6555
+ }
5599
6556
  deactivateRef.current();
5600
6557
  };
5601
6558
  window.addEventListener("message", handleDeactivate);
5602
6559
  const handleKeyDown = (e) => {
6560
+ if (e.key === "Escape" && linkPopoverOpenRef.current) {
6561
+ setLinkPopoverRef.current(null);
6562
+ return;
6563
+ }
5603
6564
  if (e.key !== "Escape") return;
5604
6565
  const el = activeElRef.current;
5605
6566
  if (el && originalContentRef.current !== null) {
@@ -5853,23 +6814,7 @@ function OhhwellsBridge() {
5853
6814
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
5854
6815
  };
5855
6816
  }, [isEditMode, refreshStateRules]);
5856
- useEffect2(() => {
5857
- const handler = (e) => {
5858
- if (e.data?.type !== "ow:request-schedule-config") return;
5859
- const insertAfterVal = e.data.insertAfter;
5860
- if (!insertAfterVal) return;
5861
- if (!sectionsLoadedRef.current) {
5862
- if (!pendingScheduleConfigRequests.current.includes(insertAfterVal)) {
5863
- pendingScheduleConfigRequests.current.push(insertAfterVal);
5864
- }
5865
- return;
5866
- }
5867
- processConfigRequest(insertAfterVal);
5868
- };
5869
- window.addEventListener("message", handler);
5870
- return () => window.removeEventListener("message", handler);
5871
- }, [processConfigRequest]);
5872
- useEffect2(() => {
6817
+ useEffect4(() => {
5873
6818
  if (!isEditMode) return;
5874
6819
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
5875
6820
  el.removeAttribute("data-ohw-active-state");
@@ -5884,20 +6829,26 @@ function OhhwellsBridge() {
5884
6829
  const raf = requestAnimationFrame(() => refreshStateRules());
5885
6830
  const timer = setTimeout(() => {
5886
6831
  refreshStateRules();
5887
- postToParent({ type: "ow:ready", version: "1", nodes: collectEditableNodes() });
6832
+ const sections = collectSections();
6833
+ setSectionsByPath((prev) => {
6834
+ const next = { ...prev, [pathname]: sections };
6835
+ return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
6836
+ });
6837
+ postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
6838
+ postToParent({ type: "ow:request-site-pages" });
5888
6839
  }, 150);
5889
6840
  return () => {
5890
6841
  cancelAnimationFrame(raf);
5891
6842
  clearTimeout(timer);
5892
6843
  };
5893
6844
  }, [pathname, isEditMode, refreshStateRules, postToParent]);
5894
- const handleCommand = useCallback((cmd) => {
6845
+ const handleCommand = useCallback2((cmd) => {
5895
6846
  document.execCommand(cmd, false);
5896
6847
  activeElRef.current?.focus();
5897
6848
  if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
5898
6849
  refreshActiveCommandsRef.current();
5899
6850
  }, []);
5900
- const handleStateChange = useCallback((state) => {
6851
+ const handleStateChange = useCallback2((state) => {
5901
6852
  if (!activeStateElRef.current) return;
5902
6853
  const el = activeStateElRef.current;
5903
6854
  if (state === "Default") {
@@ -5910,13 +6861,48 @@ function OhhwellsBridge() {
5910
6861
  }
5911
6862
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
5912
6863
  }, [deactivate]);
6864
+ const closeLinkPopover = useCallback2(() => setLinkPopover(null), []);
6865
+ const openLinkPopoverForActive = useCallback2(() => {
6866
+ const hrefCtx = getHrefKeyFromElement(activeElRef.current);
6867
+ if (!hrefCtx) return;
6868
+ bumpLinkPopoverGrace();
6869
+ setLinkPopover({
6870
+ key: hrefCtx.key,
6871
+ target: getLinkHref(hrefCtx.anchor),
6872
+ rect: hrefCtx.anchor.getBoundingClientRect()
6873
+ });
6874
+ }, []);
6875
+ const handleLinkPopoverSubmit = useCallback2(
6876
+ (target) => {
6877
+ if (!linkPopover) return;
6878
+ const { key } = linkPopover;
6879
+ applyLinkByKey(key, target);
6880
+ postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
6881
+ setLinkPopover(null);
6882
+ },
6883
+ [linkPopover, postToParent]
6884
+ );
6885
+ const showEditLink = toolbarShowEditLink;
6886
+ const currentSections = sectionsByPath[pathname] ?? [];
6887
+ linkPopoverOpenRef.current = linkPopover !== null;
5913
6888
  return bridgeRoot ? createPortal(
5914
- /* @__PURE__ */ jsxs3(Fragment2, { children: [
5915
- toolbarRect && /* @__PURE__ */ jsxs3(Fragment2, { children: [
5916
- /* @__PURE__ */ jsx6(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
5917
- /* @__PURE__ */ jsx6(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
6889
+ /* @__PURE__ */ jsxs8(Fragment3, { children: [
6890
+ toolbarRect && /* @__PURE__ */ jsxs8(Fragment3, { children: [
6891
+ /* @__PURE__ */ jsx16(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
6892
+ /* @__PURE__ */ jsx16(
6893
+ FloatingToolbar,
6894
+ {
6895
+ rect: toolbarRect,
6896
+ parentScroll: parentScrollRef.current,
6897
+ elRef: toolbarElRef,
6898
+ onCommand: handleCommand,
6899
+ activeCommands,
6900
+ showEditLink,
6901
+ onEditLink: openLinkPopoverForActive
6902
+ }
6903
+ )
5918
6904
  ] }),
5919
- maxBadge && /* @__PURE__ */ jsxs3(
6905
+ maxBadge && /* @__PURE__ */ jsxs8(
5920
6906
  "div",
5921
6907
  {
5922
6908
  "data-ohw-max-badge": "",
@@ -5942,7 +6928,7 @@ function OhhwellsBridge() {
5942
6928
  ]
5943
6929
  }
5944
6930
  ),
5945
- toggleState && /* @__PURE__ */ jsx6(
6931
+ toggleState && /* @__PURE__ */ jsx16(
5946
6932
  StateToggle,
5947
6933
  {
5948
6934
  rect: toggleState.rect,
@@ -5951,15 +6937,31 @@ function OhhwellsBridge() {
5951
6937
  onStateChange: handleStateChange
5952
6938
  }
5953
6939
  ),
5954
- sectionGap && /* @__PURE__ */ jsxs3(
6940
+ linkPopover ? /* @__PURE__ */ jsx16(
6941
+ LinkPopover,
6942
+ {
6943
+ rect: linkPopover.rect,
6944
+ parentScroll: parentScrollRef.current,
6945
+ panelRef: linkPopoverPanelRef,
6946
+ open: true,
6947
+ mode: "edit",
6948
+ pages: sitePages,
6949
+ sections: currentSections,
6950
+ sectionsByPath,
6951
+ initialTarget: linkPopover.target,
6952
+ onClose: closeLinkPopover,
6953
+ onSubmit: handleLinkPopoverSubmit
6954
+ }
6955
+ ) : null,
6956
+ sectionGap && /* @__PURE__ */ jsxs8(
5955
6957
  "div",
5956
6958
  {
5957
6959
  "data-ohw-section-insert-line": "",
5958
6960
  className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
5959
6961
  style: { top: sectionGap.y, transform: "translateY(-50%)" },
5960
6962
  children: [
5961
- /* @__PURE__ */ jsx6("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
5962
- /* @__PURE__ */ jsx6(
6963
+ /* @__PURE__ */ jsx16("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
6964
+ /* @__PURE__ */ jsx16(
5963
6965
  Badge,
5964
6966
  {
5965
6967
  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",
@@ -5972,7 +6974,7 @@ function OhhwellsBridge() {
5972
6974
  children: "Add Section"
5973
6975
  }
5974
6976
  ),
5975
- /* @__PURE__ */ jsx6("div", { className: "flex-1 bg-primary", style: { height: 3 } })
6977
+ /* @__PURE__ */ jsx16("div", { className: "flex-1 bg-primary", style: { height: 3 } })
5976
6978
  ]
5977
6979
  }
5978
6980
  )
@@ -5981,11 +6983,19 @@ function OhhwellsBridge() {
5981
6983
  ) : null;
5982
6984
  }
5983
6985
  export {
6986
+ LinkEditorPanel,
6987
+ LinkPopover,
5984
6988
  OhhwellsBridge,
5985
6989
  SchedulingWidget,
5986
6990
  Toggle,
5987
6991
  ToggleGroup,
5988
6992
  ToggleGroupItem,
5989
- toggleVariants
6993
+ buildTarget,
6994
+ filterAvailablePages,
6995
+ getEditModeInitialState,
6996
+ isValidUrl,
6997
+ parseTarget,
6998
+ toggleVariants,
6999
+ validateUrlInput
5990
7000
  };
5991
7001
  //# sourceMappingURL=index.js.map