@ohhwells/bridge 0.1.16 → 0.1.17-next.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -31,19 +31,770 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.ts
32
32
  var index_exports = {};
33
33
  __export(index_exports, {
34
+ LinkEditorPanel: () => LinkEditorPanel,
35
+ LinkPopover: () => LinkPopover,
34
36
  OhhwellsBridge: () => OhhwellsBridge,
37
+ SchedulingWidget: () => SchedulingWidget,
35
38
  Toggle: () => Toggle,
36
39
  ToggleGroup: () => ToggleGroup,
37
40
  ToggleGroupItem: () => ToggleGroupItem,
38
- toggleVariants: () => toggleVariants
41
+ buildTarget: () => buildTarget,
42
+ filterAvailablePages: () => filterAvailablePages,
43
+ getEditModeInitialState: () => getEditModeInitialState,
44
+ isValidUrl: () => isValidUrl,
45
+ parseTarget: () => parseTarget,
46
+ toggleVariants: () => toggleVariants,
47
+ validateUrlInput: () => validateUrlInput
39
48
  });
40
49
  module.exports = __toCommonJS(index_exports);
41
50
 
42
51
  // src/OhhwellsBridge.tsx
43
- var import_react = __toESM(require("react"), 1);
52
+ var import_react5 = __toESM(require("react"), 1);
53
+ var import_client = require("react-dom/client");
54
+ var import_react_dom = require("react-dom");
55
+
56
+ // src/ui/SchedulingWidget.tsx
57
+ var import_react2 = require("react");
58
+
59
+ // src/ui/EmailCaptureModal.tsx
60
+ var import_react = require("react");
61
+ var import_radix_ui = require("radix-ui");
62
+ var import_jsx_runtime = require("react/jsx-runtime");
63
+ function Spinner() {
64
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
65
+ "svg",
66
+ {
67
+ width: "32",
68
+ height: "32",
69
+ viewBox: "0 0 32 32",
70
+ fill: "none",
71
+ style: { animation: "ohw-spin 0.75s linear infinite" },
72
+ children: [
73
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes ohw-spin { to { transform: rotate(360deg); } }` }),
74
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "16", r: "12", stroke: "#e5e7eb", strokeWidth: "3" }),
75
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
76
+ "path",
77
+ {
78
+ d: "M16 4a12 12 0 0 1 12 12",
79
+ stroke: "#3b82f6",
80
+ strokeWidth: "3",
81
+ strokeLinecap: "round"
82
+ }
83
+ )
84
+ ]
85
+ }
86
+ );
87
+ }
88
+ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
89
+ const [email, setEmail] = (0, import_react.useState)("");
90
+ const [submittedEmail, setSubmittedEmail] = (0, import_react.useState)("");
91
+ const [loading, setLoading] = (0, import_react.useState)(false);
92
+ const [success, setSuccess] = (0, import_react.useState)(false);
93
+ const [error, setError] = (0, import_react.useState)(null);
94
+ const isBook = title === "Confirm your spot";
95
+ const handleSubmit = async () => {
96
+ if (!email.trim()) return;
97
+ setLoading(true);
98
+ setError(null);
99
+ try {
100
+ await onSubmit(email.trim());
101
+ setSubmittedEmail(email.trim());
102
+ setSuccess(true);
103
+ } catch (e) {
104
+ setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
105
+ } finally {
106
+ setLoading(false);
107
+ }
108
+ };
109
+ const handleKeyDown = (e) => {
110
+ if (e.key === "Enter" && !loading) handleSubmit();
111
+ };
112
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_radix_ui.Dialog.Root, { open: true, onOpenChange: (open) => {
113
+ if (!open) onClose();
114
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_radix_ui.Dialog.Portal, { children: [
115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
116
+ import_radix_ui.Dialog.Overlay,
117
+ {
118
+ className: "fixed inset-0 z-50",
119
+ style: { background: "rgba(0,0,0,0.45)" }
120
+ }
121
+ ),
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
123
+ import_radix_ui.Dialog.Content,
124
+ {
125
+ className: "fixed left-1/2 top-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 bg-white rounded-xl shadow-xl outline-none font-body box-border overflow-hidden",
126
+ style: { maxWidth: 400 },
127
+ children: [
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between gap-3 p-6", children: [
129
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1", children: [
130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
131
+ import_radix_ui.Dialog.Title,
132
+ {
133
+ className: "font-body text-xl font-bold m-0 leading-tight",
134
+ style: { color: "var(--color-dark,#200C02)" },
135
+ children: success ? isBook ? "Reservation confirmed!" : "You're on the waitlist!" : title
136
+ }
137
+ ),
138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
139
+ import_radix_ui.Dialog.Description,
140
+ {
141
+ className: "font-body text-sm m-0",
142
+ style: { color: "#6B7280" },
143
+ children: success ? isBook ? `A confirmation email has been sent to ${submittedEmail}.` : "We'll let you know via email if someone cancels." : subtitle
144
+ }
145
+ )
146
+ ] }),
147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
148
+ import_radix_ui.Dialog.Close,
149
+ {
150
+ className: "shrink-0 w-7 h-7 flex items-center justify-center rounded-full bg-transparent border-none cursor-pointer mt-0.5 absolute top-2.5 right-1.5",
151
+ style: { color: "#6B7280" },
152
+ "aria-label": "Close",
153
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
156
+ ] })
157
+ }
158
+ )
159
+ ] }),
160
+ !success && (loading ? (
161
+ /* Loading state — full body replaced */
162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center justify-center gap-3 px-7 py-12", children: [
163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-sm m-0", style: { color: "#6B7280" }, children: "Please wait" })
165
+ ] })
166
+ ) : (
167
+ /* Email form */
168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "px-7 pt-2 pb-6", children: [
169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1.5 mb-8", children: [
170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "font-body text-sm font-medium", style: { color: "var(--color-dark,#200C02)" }, children: "Email" }),
171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
172
+ "input",
173
+ {
174
+ type: "email",
175
+ value: email,
176
+ onChange: (e) => setEmail(e.target.value),
177
+ onKeyDown: handleKeyDown,
178
+ placeholder: "hello@gmail.com",
179
+ autoFocus: true,
180
+ className: "w-full h-10 px-3 rounded-lg border font-body text-sm outline-none box-border",
181
+ style: {
182
+ borderColor: "#E7E5E4",
183
+ background: "#fff",
184
+ color: "var(--color-dark,#200C02)"
185
+ }
186
+ }
187
+ ),
188
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-xs text-red-500 m-0", children: error })
189
+ ] }),
190
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
191
+ "button",
192
+ {
193
+ onClick: handleSubmit,
194
+ disabled: !email.trim(),
195
+ className: "h-9 px-5 rounded-lg font-body text-sm font-semibold cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed",
196
+ style: {
197
+ background: "var(--color-primary,#3D312B)",
198
+ color: "var(--color-light,#FEFFF0)"
199
+ },
200
+ children: "Send"
201
+ }
202
+ ) })
203
+ ] })
204
+ ))
205
+ ]
206
+ }
207
+ )
208
+ ] }) });
209
+ }
210
+
211
+ // src/ui/SchedulingWidget.tsx
212
+ var import_jsx_runtime2 = require("react/jsx-runtime");
213
+ var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
214
+ var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
215
+ function getApiDomain() {
216
+ const h = window.location.hostname;
217
+ for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
218
+ if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
219
+ }
220
+ return h;
221
+ }
222
+ function classRunsOnDate(cls, date, type) {
223
+ if (type === "FIXED") {
224
+ const d = String(date.getDate()).padStart(2, "0");
225
+ const m = String(date.getMonth() + 1).padStart(2, "0");
226
+ return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
227
+ }
228
+ return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
229
+ }
230
+ function formatClassTime(cls) {
231
+ let h = parseInt(cls.startTime.hour, 10);
232
+ const m = parseInt(cls.startTime.minutes, 10);
233
+ if (cls.startTime.time === "PM" && h !== 12) h += 12;
234
+ if (cls.startTime.time === "AM" && h === 12) h = 0;
235
+ const endTotal = h * 60 + m + cls.duration;
236
+ const eh = Math.floor(endTotal / 60) % 24;
237
+ const em = endTotal % 60;
238
+ return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
239
+ }
240
+ function getBookingsOnDate(cls, date) {
241
+ if (!cls.bookings?.length) return 0;
242
+ const ds = date.toISOString().split("T")[0];
243
+ return cls.bookings.filter((b) => {
244
+ try {
245
+ return new Date(b.classDate).toISOString().split("T")[0] === ds;
246
+ } catch {
247
+ return false;
248
+ }
249
+ }).length;
250
+ }
251
+ function buildTimezoneLabel(tz) {
252
+ try {
253
+ const now = /* @__PURE__ */ new Date();
254
+ const timeStr = new Intl.DateTimeFormat("en-US", {
255
+ hour: "2-digit",
256
+ minute: "2-digit",
257
+ hour12: false,
258
+ timeZone: tz
259
+ }).format(now);
260
+ const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
261
+ const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
262
+ return `${timeStr} (${offsetPart}) ${city} time`;
263
+ } catch {
264
+ return tz;
265
+ }
266
+ }
267
+ function EmptyState({ inEditor }) {
268
+ const handleAddSchedule = () => {
269
+ if (inEditor) {
270
+ window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
271
+ }
272
+ };
273
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("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: [
274
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-10 h-10 bg-[#F5F5F4] rounded-[10px] flex items-center justify-center shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
275
+ "svg",
276
+ {
277
+ width: "20",
278
+ height: "20",
279
+ viewBox: "0 0 24 24",
280
+ fill: "none",
281
+ stroke: "var(--color-accent, #A89B83)",
282
+ strokeWidth: "1.5",
283
+ strokeLinecap: "round",
284
+ strokeLinejoin: "round",
285
+ children: [
286
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
287
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
288
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
289
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
290
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
291
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
292
+ ]
293
+ }
294
+ ) }),
295
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-2", children: [
296
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
297
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-base font-normal text-center text-(--color-accent,#A89B83) m-0", children: "Add a scheduling widget to get instant bookings." })
298
+ ] }),
299
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
300
+ "button",
301
+ {
302
+ onClick: handleAddSchedule,
303
+ className: "h-9 px-2 py-1.5 flex items-center justify-center bg-white border border-[#E7E5E4] rounded-md font-body text-sm font-medium text-(--color-dark,#200C02) cursor-pointer",
304
+ children: "Add Schedule"
305
+ }
306
+ )
307
+ ] });
308
+ }
309
+ function LoadingSkeleton() {
310
+ const shimmer = {
311
+ background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
312
+ backgroundSize: "200% 100%",
313
+ animation: "ohw-shimmer 1.5s infinite"
314
+ };
315
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
316
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
317
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex h-[70px] items-stretch", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col justify-between items-center px-1", children: [
318
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
319
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
320
+ ] }, i)) }),
321
+ [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
322
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:hidden", children: [
323
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0", children: [
324
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100px", height: "16px", borderRadius: "4px" } }),
325
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "52px", height: "20px", borderRadius: "999px" } }),
326
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "60px", height: "14px", borderRadius: "4px" } })
327
+ ] }),
328
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2", children: [
329
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "75%", height: "16px", borderRadius: "4px" } }),
330
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "50%", height: "14px", borderRadius: "4px" } }),
331
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100%", height: "44px", borderRadius: "8px" } })
332
+ ] })
333
+ ] }),
334
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hidden sm:flex items-center gap-[60px] py-4", children: [
335
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
336
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2", children: [
337
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
338
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
339
+ ] }),
340
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
341
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
342
+ ] }),
343
+ i < 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
344
+ ] }, i))
345
+ ] });
346
+ }
347
+ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal }) {
348
+ const todayMs = (0, import_react2.useMemo)(() => {
349
+ const d = /* @__PURE__ */ new Date();
350
+ d.setHours(0, 0, 0, 0);
351
+ return d.getTime();
352
+ }, []);
353
+ const scrollRef = (0, import_react2.useRef)(null);
354
+ const [canScrollLeft, setCanScrollLeft] = (0, import_react2.useState)(false);
355
+ const [canScrollRight, setCanScrollRight] = (0, import_react2.useState)(false);
356
+ const updateScrollState = () => {
357
+ const el = scrollRef.current;
358
+ if (!el) return;
359
+ setCanScrollLeft(el.scrollLeft > 0);
360
+ setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
361
+ };
362
+ (0, import_react2.useEffect)(() => {
363
+ const el = scrollRef.current;
364
+ if (!el) return;
365
+ updateScrollState();
366
+ el.addEventListener("scroll", updateScrollState);
367
+ const ro = new ResizeObserver(updateScrollState);
368
+ ro.observe(el);
369
+ return () => {
370
+ el.removeEventListener("scroll", updateScrollState);
371
+ ro.disconnect();
372
+ };
373
+ }, [dates]);
374
+ const scroll = (dir) => {
375
+ scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
376
+ };
377
+ const isDragging = (0, import_react2.useRef)(false);
378
+ const dragStartX = (0, import_react2.useRef)(0);
379
+ const dragStartScrollLeft = (0, import_react2.useRef)(0);
380
+ const onDragStart = (e) => {
381
+ if (!scrollRef.current) return;
382
+ isDragging.current = true;
383
+ dragStartX.current = e.clientX;
384
+ dragStartScrollLeft.current = scrollRef.current.scrollLeft;
385
+ scrollRef.current.style.cursor = "grabbing";
386
+ scrollRef.current.style.userSelect = "none";
387
+ };
388
+ const onDragMove = (e) => {
389
+ if (!isDragging.current || !scrollRef.current) return;
390
+ const delta = e.clientX - dragStartX.current;
391
+ scrollRef.current.scrollLeft = dragStartScrollLeft.current - delta;
392
+ };
393
+ const onDragEnd = () => {
394
+ if (!scrollRef.current) return;
395
+ isDragging.current = false;
396
+ scrollRef.current.style.cursor = "grab";
397
+ scrollRef.current.style.userSelect = "";
398
+ };
399
+ const datesWithClasses = (0, import_react2.useMemo)(
400
+ () => new Set(dates.map(
401
+ (d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
402
+ ).filter((i) => i >= 0)),
403
+ [dates, schedule]
404
+ );
405
+ const selectedDate = dates[selectedIdx];
406
+ const selectedClasses = (0, import_react2.useMemo)(
407
+ () => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
408
+ [schedule, selectedDate]
409
+ );
410
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
411
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative w-full", children: [
412
+ canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
413
+ "button",
414
+ {
415
+ onClick: () => scroll("left"),
416
+ className: "hidden sm:flex absolute left-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 items-center justify-center rounded-full border-none cursor-pointer",
417
+ style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
418
+ "aria-label": "Scroll left",
419
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "var(--color-dark,#200C02)", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "15 18 9 12 15 6" }) })
420
+ }
421
+ ),
422
+ canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
423
+ "button",
424
+ {
425
+ onClick: () => scroll("right"),
426
+ className: "hidden sm:flex absolute right-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 items-center justify-center rounded-full border-none cursor-pointer",
427
+ style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
428
+ "aria-label": "Scroll right",
429
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "var(--color-dark,#200C02)", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "9 18 15 12 9 6" }) })
430
+ }
431
+ ),
432
+ canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
433
+ "div",
434
+ {
435
+ className: "sm:hidden absolute left-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
436
+ style: { background: "linear-gradient(to right, var(--color-light,#FEFFF0), transparent)" }
437
+ }
438
+ ),
439
+ canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
440
+ "div",
441
+ {
442
+ className: "sm:hidden absolute right-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
443
+ style: { background: "linear-gradient(to left, var(--color-light,#FEFFF0), transparent)" }
444
+ }
445
+ ),
446
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
447
+ "div",
448
+ {
449
+ ref: scrollRef,
450
+ className: "overflow-x-auto w-full",
451
+ style: { scrollbarWidth: "none", msOverflowStyle: "none", touchAction: "pan-x", cursor: "grab" },
452
+ onMouseDown: onDragStart,
453
+ onMouseMove: onDragMove,
454
+ onMouseUp: onDragEnd,
455
+ onMouseLeave: onDragEnd,
456
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
457
+ const isToday = date.getTime() === todayMs;
458
+ const isSelected = i === selectedIdx;
459
+ const clickable = datesWithClasses.has(i);
460
+ const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
461
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
462
+ "div",
463
+ {
464
+ onClick: () => clickable && onSelectDate(i),
465
+ className: `flex-1 h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
466
+ children: [
467
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
468
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
469
+ "div",
470
+ {
471
+ className: "w-10 h-10 rounded-full flex items-center justify-center",
472
+ style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
473
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
474
+ "span",
475
+ {
476
+ className: "font-body text-xl font-bold text-center tracking-display-tight",
477
+ style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
478
+ children: date.getDate()
479
+ }
480
+ )
481
+ }
482
+ )
483
+ ]
484
+ },
485
+ i
486
+ );
487
+ }) })
488
+ }
489
+ )
490
+ ] }),
491
+ selectedClasses.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-base text-(--color-accent,#A89B83) text-center py-8 m-0", children: "No classes scheduled for this day." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-col", children: selectedClasses.map((cls, i) => {
492
+ const booked = getBookingsOnDate(cls, selectedDate);
493
+ const available = cls.maxParticipants - booked;
494
+ const isFull = available <= 0;
495
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
496
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
497
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
498
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:w-[120px] sm:shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
499
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
500
+ "span",
501
+ {
502
+ className: "inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium sm:hidden",
503
+ style: { borderColor: "#0885FE", color: "#0885FE" },
504
+ children: "GROUP"
505
+ }
506
+ ),
507
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
508
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
509
+ available,
510
+ "/",
511
+ cls.maxParticipants
512
+ ] }),
513
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
514
+ ] }) })
515
+ ] }),
516
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
517
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
518
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
519
+ cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
520
+ ] }),
521
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
522
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
523
+ available,
524
+ "/",
525
+ cls.maxParticipants
526
+ ] }),
527
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
528
+ ] }) }),
529
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
530
+ "button",
531
+ {
532
+ className: "w-full sm:w-[200px] sm:shrink-0 px-5 py-[10px] flex items-center justify-center rounded-lg font-body text-base font-bold cursor-pointer box-border",
533
+ style: isFull ? {
534
+ background: "transparent",
535
+ border: "1px solid var(--color-dark, #200C02)",
536
+ color: "var(--color-dark, #200C02)"
537
+ } : {
538
+ background: "var(--color-primary, #3D312B)",
539
+ border: "none",
540
+ color: "var(--color-light, #FEFFF0)"
541
+ },
542
+ onClick: () => {
543
+ if (!cls.id) return;
544
+ onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
545
+ },
546
+ children: isFull ? "Join Waitlist" : "Book Now"
547
+ }
548
+ )
549
+ ] })
550
+ ] }),
551
+ i < selectedClasses.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
552
+ ] }, cls.id ?? i);
553
+ }) })
554
+ ] });
555
+ }
556
+ function CalendarFoldIcon() {
557
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
558
+ "svg",
559
+ {
560
+ width: "16",
561
+ height: "16",
562
+ viewBox: "0 0 24 24",
563
+ fill: "none",
564
+ stroke: "currentColor",
565
+ strokeWidth: "2",
566
+ strokeLinecap: "round",
567
+ strokeLinejoin: "round",
568
+ style: { flexShrink: 0 },
569
+ children: [
570
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 2v4" }),
571
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 2v4" }),
572
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }),
573
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 10h18" }),
574
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
575
+ ]
576
+ }
577
+ );
578
+ }
579
+ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
580
+ const [schedule, setSchedule] = (0, import_react2.useState)(null);
581
+ const [loading, setLoading] = (0, import_react2.useState)(true);
582
+ const [inEditor, setInEditor] = (0, import_react2.useState)(false);
583
+ const [isHovered, setIsHovered] = (0, import_react2.useState)(false);
584
+ const [modalState, setModalState] = (0, import_react2.useState)(null);
585
+ const switchScheduleIdRef = (0, import_react2.useRef)(null);
586
+ const dates = (0, import_react2.useMemo)(() => {
587
+ const today = /* @__PURE__ */ new Date();
588
+ today.setHours(0, 0, 0, 0);
589
+ return Array.from({ length: 14 }, (_, i) => {
590
+ const d = new Date(today);
591
+ d.setDate(today.getDate() + i);
592
+ return d;
593
+ });
594
+ }, []);
595
+ const [selectedIdx, setSelectedIdx] = (0, import_react2.useState)(0);
596
+ (0, import_react2.useEffect)(() => {
597
+ if (!schedule?.classes) return;
598
+ for (let i = 0; i < dates.length; i++) {
599
+ if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
600
+ setSelectedIdx(i);
601
+ return;
602
+ }
603
+ }
604
+ }, [schedule, dates]);
605
+ (0, import_react2.useEffect)(() => {
606
+ if (typeof window === "undefined") return;
607
+ const isInEditor = window.self !== window.top;
608
+ setInEditor(isInEditor);
609
+ if (isInEditor) {
610
+ const startEmpty = initialScheduleId === null;
611
+ if (startEmpty) setLoading(false);
612
+ let initialized = startEmpty;
613
+ const timer = !startEmpty ? setTimeout(() => {
614
+ if (!initialized) {
615
+ initialized = true;
616
+ setLoading(false);
617
+ }
618
+ }, 5e3) : null;
619
+ const handler = (e) => {
620
+ if (e.data?.type === "ow:clear-scheduling-widget") {
621
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
622
+ setSchedule(null);
623
+ setLoading(false);
624
+ return;
625
+ }
626
+ if (e.data?.type === "ow:switch-schedule") {
627
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
628
+ switchScheduleIdRef.current = e.data.scheduleId ?? null;
629
+ initialized = false;
630
+ setLoading(true);
631
+ window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
632
+ return;
633
+ }
634
+ if (e.data?.type !== "ow:user-schedules-response") return;
635
+ if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
636
+ if (initialized && switchScheduleIdRef.current === null) return;
637
+ initialized = true;
638
+ if (timer) clearTimeout(timer);
639
+ const schedules = e.data.schedules ?? [];
640
+ const isSwitching = switchScheduleIdRef.current !== null;
641
+ 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;
642
+ switchScheduleIdRef.current = null;
643
+ setSchedule(target);
644
+ setLoading(false);
645
+ if (notifyOnConnect && target && !isSwitching) {
646
+ window.parent.postMessage({
647
+ type: "ow:schedule-connected",
648
+ schedule: { id: target.id, name: target.name },
649
+ insertAfter
650
+ }, "*");
651
+ window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
652
+ }
653
+ };
654
+ window.addEventListener("message", handler);
655
+ if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
656
+ return () => {
657
+ window.removeEventListener("message", handler);
658
+ if (timer) clearTimeout(timer);
659
+ };
660
+ }
661
+ if (initialScheduleId === null) {
662
+ setLoading(false);
663
+ return;
664
+ }
665
+ ;
666
+ (async () => {
667
+ try {
668
+ if (initialScheduleId) {
669
+ const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
670
+ const data = await res.json();
671
+ setSchedule(data?.id ? data : null);
672
+ } else {
673
+ const domain = getApiDomain();
674
+ const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
675
+ const slugs = await sitemapRes.json();
676
+ if (!Array.isArray(slugs) || !slugs.length) {
677
+ setLoading(false);
678
+ return;
679
+ }
680
+ const { slug } = [...slugs].sort(
681
+ (a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
682
+ )[0];
683
+ const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
684
+ const data = await res.json();
685
+ setSchedule(data?.id ? data : null);
686
+ }
687
+ } catch {
688
+ }
689
+ setLoading(false);
690
+ })();
691
+ }, []);
692
+ const timezoneLabel = schedule?.timezone ? (() => {
693
+ try {
694
+ return buildTimezoneLabel(schedule.timezone);
695
+ } catch {
696
+ return void 0;
697
+ }
698
+ })() : void 0;
699
+ const handleModalSubmit = async (email) => {
700
+ if (!modalState) return;
701
+ const { mode, classId, classDate } = modalState;
702
+ const endpoint = mode === "book" ? `${API_URL}/api/schedule/class/${classId}/booking` : `${API_URL}/api/schedule/class/${classId}/waitlist`;
703
+ const res = await fetch(endpoint, {
704
+ method: "POST",
705
+ headers: { "Content-Type": "application/json" },
706
+ body: JSON.stringify({ classDate: classDate.toISOString(), email })
707
+ });
708
+ if (!res.ok) {
709
+ const data = await res.json().catch(() => ({}));
710
+ throw new Error(data?.message ?? "Something went wrong. Please try again.");
711
+ }
712
+ };
713
+ const handleReplaceSchedule = () => {
714
+ setIsHovered(false);
715
+ if (schedule) {
716
+ window.parent.postMessage({
717
+ type: "ow:schedule-connected",
718
+ schedule: { id: schedule.id, name: schedule.name },
719
+ insertAfter
720
+ }, "*");
721
+ } else {
722
+ window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
723
+ }
724
+ };
725
+ const sectionId = `scheduling-${insertAfter}`;
726
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
727
+ "section",
728
+ {
729
+ "data-ohw-section": sectionId,
730
+ "data-ohw-scheduling-anchor": insertAfter,
731
+ className: "w-full box-border py-10 px-5 sm:py-20 sm:px-16 font-body bg-(--color-light,#FEFFF0) relative",
732
+ onMouseEnter: () => inEditor && setIsHovered(true),
733
+ onMouseLeave: () => setIsHovered(false),
734
+ children: [
735
+ inEditor && isHovered && !!schedule && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
736
+ "div",
737
+ {
738
+ className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
739
+ onClick: handleReplaceSchedule,
740
+ children: [
741
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
742
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
743
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
744
+ "div",
745
+ {
746
+ className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
747
+ style: {
748
+ fontSize: 14,
749
+ lineHeight: "24px",
750
+ fontFamily: "'Figtree', system-ui, sans-serif",
751
+ fontWeight: 500,
752
+ color: "#0C0A09"
753
+ },
754
+ children: [
755
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CalendarFoldIcon, {}),
756
+ "Replace schedule"
757
+ ]
758
+ }
759
+ ) })
760
+ ]
761
+ }
762
+ ),
763
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
764
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
765
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
766
+ schedule?.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
767
+ ] }),
768
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
769
+ timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
770
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyState, { inEditor }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
771
+ ScheduleView,
772
+ {
773
+ schedule,
774
+ dates,
775
+ selectedIdx,
776
+ onSelectDate: setSelectedIdx,
777
+ onOpenModal: inEditor ? void 0 : (mode, classId, classDate) => setModalState({ mode, classId, classDate })
778
+ }
779
+ ) })
780
+ ] })
781
+ ] }),
782
+ modalState && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
783
+ EmailCaptureModal,
784
+ {
785
+ title: modalState.mode === "book" ? "Confirm your spot" : "Leave your email",
786
+ subtitle: modalState.mode === "book" ? "We'll send your booking confirmation here." : "We'll let you know when spots open up!",
787
+ onSubmit: handleModalSubmit,
788
+ onClose: () => setModalState(null)
789
+ }
790
+ )
791
+ ]
792
+ }
793
+ );
794
+ }
44
795
 
45
796
  // src/ui/toggle-group.tsx
46
- var React = __toESM(require("react"), 1);
797
+ var React2 = __toESM(require("react"), 1);
47
798
 
48
799
  // node_modules/clsx/dist/clsx.mjs
49
800
  function r(e) {
@@ -3362,8 +4113,8 @@ var cva = (base, config) => (props) => {
3362
4113
  };
3363
4114
 
3364
4115
  // src/ui/toggle.tsx
3365
- var import_radix_ui = require("radix-ui");
3366
- var import_jsx_runtime = require("react/jsx-runtime");
4116
+ var import_radix_ui2 = require("radix-ui");
4117
+ var import_jsx_runtime3 = require("react/jsx-runtime");
3367
4118
  var toggleVariants = cva(
3368
4119
  "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
3369
4120
  {
@@ -3390,8 +4141,8 @@ function Toggle({
3390
4141
  size,
3391
4142
  ...props
3392
4143
  }) {
3393
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3394
- import_radix_ui.Toggle.Root,
4144
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4145
+ import_radix_ui2.Toggle.Root,
3395
4146
  {
3396
4147
  "data-slot": "toggle",
3397
4148
  className: cn(toggleVariants({ variant, size, className })),
@@ -3401,8 +4152,8 @@ function Toggle({
3401
4152
  }
3402
4153
 
3403
4154
  // src/ui/toggle-group.tsx
3404
- var import_jsx_runtime2 = require("react/jsx-runtime");
3405
- var ToggleGroupContext = React.createContext({
4155
+ var import_jsx_runtime4 = require("react/jsx-runtime");
4156
+ var ToggleGroupContext = React2.createContext({
3406
4157
  value: "",
3407
4158
  onValueChange: () => {
3408
4159
  }
@@ -3414,13 +4165,13 @@ function ToggleGroup({
3414
4165
  children,
3415
4166
  ...props
3416
4167
  }) {
3417
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4168
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3418
4169
  "div",
3419
4170
  {
3420
4171
  role: "group",
3421
4172
  className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
3422
4173
  ...props,
3423
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
4174
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
3424
4175
  }
3425
4176
  );
3426
4177
  }
@@ -3430,8 +4181,8 @@ function ToggleGroupItem({
3430
4181
  children,
3431
4182
  ...props
3432
4183
  }) {
3433
- const { value: groupValue, onValueChange } = React.useContext(ToggleGroupContext);
3434
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4184
+ const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
4185
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3435
4186
  Toggle,
3436
4187
  {
3437
4188
  pressed: groupValue === value,
@@ -3451,7 +4202,7 @@ function ToggleGroupItem({
3451
4202
  }
3452
4203
 
3453
4204
  // src/OhhwellsBridge.tsx
3454
- var import_react_dom = require("react-dom");
4205
+ var import_react_dom2 = require("react-dom");
3455
4206
  var import_navigation = require("next/navigation");
3456
4207
 
3457
4208
  // src/lib/session-search.ts
@@ -3472,8 +4223,866 @@ function isEditSessionActive() {
3472
4223
  return new URLSearchParams(q).get("mode") === "edit";
3473
4224
  }
3474
4225
 
4226
+ // src/ui/link-modal/calcPopoverPos.ts
4227
+ function calcPopoverPos(rect, parentScroll, approxW = 483, approxH = 320) {
4228
+ const GAP = 8;
4229
+ const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
4230
+ const visibleTop = rect.top - canvasTopInIframe;
4231
+ let top = rect.top - GAP;
4232
+ let transform = "translateX(-50%) translateY(-100%)";
4233
+ if (visibleTop < approxH + GAP) {
4234
+ top = rect.bottom + GAP;
4235
+ transform = "translateX(-50%)";
4236
+ }
4237
+ const rawLeft = rect.left + rect.width / 2;
4238
+ const left = Math.max(GAP + approxW / 2, Math.min(rawLeft, window.innerWidth - GAP - approxW / 2));
4239
+ return { top, left, transform };
4240
+ }
4241
+
4242
+ // src/ui/button.tsx
4243
+ var React3 = __toESM(require("react"), 1);
4244
+ var import_radix_ui3 = require("radix-ui");
4245
+ var import_jsx_runtime5 = require("react/jsx-runtime");
4246
+ var buttonVariants = cva(
4247
+ "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",
4248
+ {
4249
+ variants: {
4250
+ variant: {
4251
+ default: "bg-primary text-primary-foreground hover:opacity-90",
4252
+ outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
4253
+ ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
4254
+ },
4255
+ size: {
4256
+ default: "h-9",
4257
+ sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
4258
+ }
4259
+ },
4260
+ defaultVariants: {
4261
+ variant: "default",
4262
+ size: "default"
4263
+ }
4264
+ }
4265
+ );
4266
+ var Button = React3.forwardRef(
4267
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
4268
+ const Comp = asChild ? import_radix_ui3.Slot.Root : "button";
4269
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4270
+ Comp,
4271
+ {
4272
+ ref,
4273
+ "data-slot": "button",
4274
+ className: cn(buttonVariants({ variant, size, className })),
4275
+ ...props
4276
+ }
4277
+ );
4278
+ }
4279
+ );
4280
+ Button.displayName = "Button";
4281
+
4282
+ // src/ui/icons.tsx
4283
+ var import_jsx_runtime6 = require("react/jsx-runtime");
4284
+ function IconX({ className, ...props }) {
4285
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4286
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M18 6 6 18" }),
4287
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m6 6 12 12" })
4288
+ ] });
4289
+ }
4290
+ function IconChevronDown({ className, ...props }) {
4291
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m6 9 6 6 6-6" }) });
4292
+ }
4293
+ function IconFile({ className, ...props }) {
4294
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4295
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
4296
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
4297
+ ] });
4298
+ }
4299
+ function IconInfo({ className, ...props }) {
4300
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4301
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
4302
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 16v-4" }),
4303
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 8h.01" })
4304
+ ] });
4305
+ }
4306
+ function IconArrowRight({ className, ...props }) {
4307
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4308
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5 12h14" }),
4309
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m12 5 7 7-7 7" })
4310
+ ] });
4311
+ }
4312
+ function IconSection({ className, ...props }) {
4313
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4314
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
4315
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
4316
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
4317
+ ] });
4318
+ }
4319
+
4320
+ // src/ui/link-modal/DestinationBreadcrumb.tsx
4321
+ var import_jsx_runtime7 = require("react/jsx-runtime");
4322
+ function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
4323
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
4324
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
4325
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-3", children: [
4326
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
4327
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4328
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
4329
+ ] }),
4330
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
4331
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
4332
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4333
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
4334
+ ] })
4335
+ ] })
4336
+ ] });
4337
+ }
4338
+
4339
+ // src/ui/link-modal/SectionTreeItem.tsx
4340
+ var import_jsx_runtime8 = require("react/jsx-runtime");
4341
+ function SectionTreeItem({ section, onSelect, selected }) {
4342
+ const interactive = Boolean(onSelect);
4343
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
4344
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4345
+ "div",
4346
+ {
4347
+ className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
4348
+ "aria-hidden": true
4349
+ }
4350
+ ),
4351
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4352
+ "div",
4353
+ {
4354
+ role: interactive ? "button" : void 0,
4355
+ tabIndex: interactive ? 0 : void 0,
4356
+ onClick: interactive ? () => onSelect?.(section) : void 0,
4357
+ onKeyDown: interactive ? (e) => {
4358
+ if (e.key === "Enter" || e.key === " ") {
4359
+ e.preventDefault();
4360
+ onSelect?.(section);
4361
+ }
4362
+ } : void 0,
4363
+ className: cn(
4364
+ "flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
4365
+ interactive && "cursor-pointer hover:bg-muted/30",
4366
+ interactive && selected && "border-primary"
4367
+ ),
4368
+ children: [
4369
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4370
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
4371
+ ]
4372
+ }
4373
+ )
4374
+ ] });
4375
+ }
4376
+ function SectionPickerList({ sections, onSelect }) {
4377
+ if (sections.length === 0) {
4378
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
4379
+ }
4380
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-1", children: [
4381
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
4382
+ sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SectionTreeItem, { section, onSelect }, section.id))
4383
+ ] });
4384
+ }
4385
+
4386
+ // src/ui/link-modal/UrlOrPageInput.tsx
4387
+ var import_react3 = require("react");
4388
+
4389
+ // src/ui/input.tsx
4390
+ var React4 = __toESM(require("react"), 1);
4391
+ var import_jsx_runtime9 = require("react/jsx-runtime");
4392
+ var Input = React4.forwardRef(
4393
+ ({ className, type, ...props }, ref) => {
4394
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4395
+ "input",
4396
+ {
4397
+ type,
4398
+ ref,
4399
+ "data-slot": "input",
4400
+ className: cn(
4401
+ "flex h-full w-full min-w-0 bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground",
4402
+ className
4403
+ ),
4404
+ ...props
4405
+ }
4406
+ );
4407
+ }
4408
+ );
4409
+ Input.displayName = "Input";
4410
+
4411
+ // src/ui/label.tsx
4412
+ var import_radix_ui4 = require("radix-ui");
4413
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4414
+ function Label({ className, ...props }) {
4415
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4416
+ import_radix_ui4.Label.Root,
4417
+ {
4418
+ "data-slot": "label",
4419
+ className: cn("text-sm font-medium leading-5 text-foreground", className),
4420
+ ...props
4421
+ }
4422
+ );
4423
+ }
4424
+
4425
+ // src/ui/popover.tsx
4426
+ var import_radix_ui5 = require("radix-ui");
4427
+ var import_jsx_runtime11 = require("react/jsx-runtime");
4428
+ function Popover({ ...props }) {
4429
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Root, { "data-slot": "popover", ...props });
4430
+ }
4431
+ function PopoverTrigger({ ...props }) {
4432
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
4433
+ }
4434
+ function PopoverContent({
4435
+ className,
4436
+ align = "start",
4437
+ sideOffset = 6,
4438
+ ...props
4439
+ }) {
4440
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4441
+ import_radix_ui5.Popover.Content,
4442
+ {
4443
+ "data-slot": "popover-content",
4444
+ align,
4445
+ sideOffset,
4446
+ className: cn(
4447
+ "z-[2147483647] w-[var(--radix-popover-trigger-width)] overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg outline-none",
4448
+ className
4449
+ ),
4450
+ ...props
4451
+ }
4452
+ ) });
4453
+ }
4454
+
4455
+ // src/ui/link-modal/UrlOrPageInput.tsx
4456
+ var import_jsx_runtime12 = require("react/jsx-runtime");
4457
+ function FieldChevron({ onClick }) {
4458
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4459
+ "button",
4460
+ {
4461
+ type: "button",
4462
+ className: "flex shrink-0 items-center justify-end border-0 bg-transparent p-0 pl-4 text-muted-foreground outline-none",
4463
+ onClick,
4464
+ "aria-label": "Open page list",
4465
+ tabIndex: -1,
4466
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconChevronDown, {})
4467
+ }
4468
+ );
4469
+ }
4470
+ function UrlOrPageInput({
4471
+ value,
4472
+ selectedPage,
4473
+ placeholder = "Paste a URL or choose a page",
4474
+ dropdownOpen,
4475
+ onDropdownOpenChange,
4476
+ filteredPages,
4477
+ onInputChange,
4478
+ onPageSelect,
4479
+ readOnly = false,
4480
+ urlError
4481
+ }) {
4482
+ const inputId = (0, import_react3.useId)();
4483
+ const inputRef = (0, import_react3.useRef)(null);
4484
+ const [isEditing, setIsEditing] = (0, import_react3.useState)(false);
4485
+ const [isFocused, setIsFocused] = (0, import_react3.useState)(false);
4486
+ const showFilledDisplay = Boolean(selectedPage) && !isEditing && !readOnly;
4487
+ (0, import_react3.useEffect)(() => {
4488
+ if (selectedPage) setIsEditing(false);
4489
+ }, [selectedPage]);
4490
+ const openDropdown = () => {
4491
+ if (readOnly || filteredPages.length === 0) return;
4492
+ onDropdownOpenChange(true);
4493
+ };
4494
+ const toggleDropdown = (e) => {
4495
+ e.stopPropagation();
4496
+ e.preventDefault();
4497
+ if (readOnly || filteredPages.length === 0) return;
4498
+ onDropdownOpenChange(!dropdownOpen);
4499
+ };
4500
+ const enterEditMode = () => {
4501
+ if (readOnly) return;
4502
+ setIsEditing(true);
4503
+ requestAnimationFrame(() => inputRef.current?.focus());
4504
+ };
4505
+ const fieldClassName = cn(
4506
+ "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]",
4507
+ urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
4508
+ );
4509
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
4510
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
4511
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
4512
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverTrigger, { asChild: true, children: showFilledDisplay ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
4513
+ "button",
4514
+ {
4515
+ type: "button",
4516
+ "data-ohw-link-field": true,
4517
+ className: cn(fieldClassName, "m-0 cursor-pointer border-solid text-left"),
4518
+ onClick: openDropdown,
4519
+ onFocus: () => setIsFocused(true),
4520
+ onBlur: () => setIsFocused(false),
4521
+ onKeyDown: (e) => {
4522
+ if (e.key === "Backspace" || e.key === "Delete" || e.key.length === 1) {
4523
+ enterEditMode();
4524
+ }
4525
+ },
4526
+ children: [
4527
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }),
4528
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-normal text-foreground", children: selectedPage.title }),
4529
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(FieldChevron, { onClick: toggleDropdown })
4530
+ ]
4531
+ }
4532
+ ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
4533
+ "div",
4534
+ {
4535
+ "data-ohw-link-field": true,
4536
+ className: fieldClassName,
4537
+ onMouseDown: (e) => {
4538
+ if (readOnly) return;
4539
+ if (e.target === e.currentTarget) enterEditMode();
4540
+ },
4541
+ children: [
4542
+ selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
4543
+ readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4544
+ Input,
4545
+ {
4546
+ ref: inputRef,
4547
+ id: inputId,
4548
+ value,
4549
+ onChange: (e) => onInputChange(e.target.value),
4550
+ onFocus: () => {
4551
+ setIsFocused(true);
4552
+ setIsEditing(true);
4553
+ openDropdown();
4554
+ },
4555
+ onBlur: () => setIsFocused(false),
4556
+ placeholder,
4557
+ className: cn(
4558
+ "min-w-0 flex-1 truncate border-0 bg-transparent p-0 text-sm font-normal leading-5 shadow-none outline-none ring-0",
4559
+ value ? "text-foreground" : "text-muted-foreground placeholder:text-muted-foreground"
4560
+ )
4561
+ }
4562
+ ),
4563
+ !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
4564
+ ]
4565
+ }
4566
+ ) }),
4567
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
4568
+ "button",
4569
+ {
4570
+ type: "button",
4571
+ 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",
4572
+ onClick: () => onPageSelect(page),
4573
+ children: [
4574
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
4575
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate", children: page.title })
4576
+ ]
4577
+ },
4578
+ page.path
4579
+ )) })
4580
+ ] }),
4581
+ urlError ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4582
+ ] });
4583
+ }
4584
+
4585
+ // src/ui/link-modal/useLinkModalState.ts
4586
+ var import_react4 = require("react");
4587
+
4588
+ // src/ui/link-modal/linkModal.utils.ts
4589
+ function parseTarget(target) {
4590
+ if (!target) return { pageRoute: "", sectionId: null };
4591
+ const hashIndex = target.indexOf("#");
4592
+ if (hashIndex === -1) return { pageRoute: target, sectionId: null };
4593
+ return {
4594
+ pageRoute: target.slice(0, hashIndex),
4595
+ sectionId: target.slice(hashIndex + 1) || null
4596
+ };
4597
+ }
4598
+ function normalizePath(path) {
4599
+ const trimmed = path.trim();
4600
+ if (!trimmed) return "/";
4601
+ try {
4602
+ if (/^https?:\/\//i.test(trimmed)) {
4603
+ return new URL(trimmed).pathname || "/";
4604
+ }
4605
+ } catch {
4606
+ }
4607
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
4608
+ if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
4609
+ return withSlash;
4610
+ }
4611
+ function findPageByPath(pages, path) {
4612
+ const normalized = normalizePath(path);
4613
+ return pages.find((p) => normalizePath(p.path) === normalized);
4614
+ }
4615
+ function inferPageTitleFromPath(path) {
4616
+ const normalized = normalizePath(path);
4617
+ if (normalized === "/") return "Home";
4618
+ const segment = normalized.split("/").filter(Boolean).pop() ?? "";
4619
+ if (!segment) return "Home";
4620
+ return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
4621
+ }
4622
+ function isInternalPath(route) {
4623
+ const trimmed = route.trim();
4624
+ if (!trimmed) return false;
4625
+ if (/^https?:\/\//i.test(trimmed)) return false;
4626
+ if (trimmed.startsWith("/")) return true;
4627
+ if (trimmed.includes(" ") || trimmed.includes(".")) return false;
4628
+ return true;
4629
+ }
4630
+ function resolvePage(route, pages) {
4631
+ const found = findPageByPath(pages, route);
4632
+ if (found) return { path: found.path, title: found.title };
4633
+ return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
4634
+ }
4635
+ function getSectionsForPath(sectionsByPath, pageRoute, fallback) {
4636
+ if (!sectionsByPath) return fallback;
4637
+ const normalized = normalizePath(pageRoute);
4638
+ return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? fallback;
4639
+ }
4640
+ function buildTarget(page, section) {
4641
+ if (section) return `${page.path}#${section.id}`;
4642
+ return page.path;
4643
+ }
4644
+ function isValidUrl(urlString) {
4645
+ try {
4646
+ const url = new URL(urlString);
4647
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
4648
+ const hostname = url.hostname;
4649
+ if (!hostname || hostname.length === 0) return false;
4650
+ if (hostname.includes(" ")) return false;
4651
+ return hostname.includes(".") || hostname === "localhost";
4652
+ } catch {
4653
+ return false;
4654
+ }
4655
+ }
4656
+ function normalizeUrl(value) {
4657
+ const trimmed = value.trim();
4658
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
4659
+ return `https://${trimmed}`;
4660
+ }
4661
+ function validateUrlInput(value, pages, allPages) {
4662
+ if (!value.trim()) return "";
4663
+ const filtered = pages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase()));
4664
+ if (filtered.length > 0) return "";
4665
+ const matchingPage = allPages.find((p) => p.title.toLowerCase() === value.toLowerCase());
4666
+ if (matchingPage) return "";
4667
+ if (!isValidUrl(normalizeUrl(value))) {
4668
+ return "Please enter a valid URL (e.g., https://example.com)";
4669
+ }
4670
+ return "";
4671
+ }
4672
+ function getEditModeInitialState(target, pages, sections) {
4673
+ const empty = {
4674
+ searchValue: "",
4675
+ selectedPage: null,
4676
+ selectedSection: null,
4677
+ step: "input"
4678
+ };
4679
+ if (!target) return empty;
4680
+ if (isValidUrl(normalizeUrl(target))) {
4681
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
4682
+ }
4683
+ const { pageRoute, sectionId } = parseTarget(target);
4684
+ if (sectionId) {
4685
+ if (isInternalPath(pageRoute)) {
4686
+ const page = resolvePage(pageRoute, pages);
4687
+ const sec = sections.find((s) => s.id === sectionId);
4688
+ return {
4689
+ searchValue: page.title,
4690
+ selectedPage: page,
4691
+ selectedSection: sec ? { id: sec.id, label: sec.label } : null,
4692
+ step: "input"
4693
+ };
4694
+ }
4695
+ return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
4696
+ }
4697
+ if (isInternalPath(pageRoute)) {
4698
+ const page = resolvePage(pageRoute, pages);
4699
+ return {
4700
+ searchValue: page.title,
4701
+ selectedPage: page,
4702
+ selectedSection: null,
4703
+ step: "input"
4704
+ };
4705
+ }
4706
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
4707
+ }
4708
+ function filterAvailablePages(pages, existingTargets) {
4709
+ const taken = new Set(existingTargets);
4710
+ return pages.filter((p) => !taken.has(p.path));
4711
+ }
4712
+
4713
+ // src/ui/link-modal/useLinkModalState.ts
4714
+ function useLinkModalState({
4715
+ open,
4716
+ mode,
4717
+ pages,
4718
+ sections,
4719
+ sectionsByPath,
4720
+ initialTarget,
4721
+ existingTargets,
4722
+ onClose,
4723
+ onSubmit
4724
+ }) {
4725
+ const availablePages = (0, import_react4.useMemo)(
4726
+ () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
4727
+ [mode, pages, existingTargets]
4728
+ );
4729
+ const [searchValue, setSearchValue] = (0, import_react4.useState)("");
4730
+ const [selectedPage, setSelectedPage] = (0, import_react4.useState)(null);
4731
+ const [selectedSection, setSelectedSection] = (0, import_react4.useState)(null);
4732
+ const [step, setStep] = (0, import_react4.useState)("input");
4733
+ const [dropdownOpen, setDropdownOpen] = (0, import_react4.useState)(false);
4734
+ const [urlError, setUrlError] = (0, import_react4.useState)("");
4735
+ const reset = (0, import_react4.useCallback)(() => {
4736
+ setSearchValue("");
4737
+ setSelectedPage(null);
4738
+ setSelectedSection(null);
4739
+ setStep("input");
4740
+ setDropdownOpen(false);
4741
+ setUrlError("");
4742
+ }, []);
4743
+ (0, import_react4.useEffect)(() => {
4744
+ if (!open) return;
4745
+ if (mode === "edit" && initialTarget) {
4746
+ const { pageRoute } = parseTarget(initialTarget);
4747
+ const targetSections = getSectionsForPath(sectionsByPath, pageRoute, sections);
4748
+ const init = getEditModeInitialState(initialTarget, pages, targetSections);
4749
+ setSearchValue(init.searchValue);
4750
+ setSelectedPage(init.selectedPage);
4751
+ setSelectedSection(init.selectedSection);
4752
+ setStep(init.step);
4753
+ } else {
4754
+ reset();
4755
+ }
4756
+ setDropdownOpen(false);
4757
+ setUrlError("");
4758
+ }, [open, mode, initialTarget, pages, sections, sectionsByPath, reset]);
4759
+ const filteredPages = (0, import_react4.useMemo)(() => {
4760
+ if (!searchValue.trim()) return availablePages;
4761
+ return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
4762
+ }, [availablePages, searchValue]);
4763
+ const activeSections = (0, import_react4.useMemo)(() => {
4764
+ if (selectedPage && sectionsByPath) {
4765
+ return getSectionsForPath(sectionsByPath, selectedPage.path, sections);
4766
+ }
4767
+ return sections;
4768
+ }, [selectedPage, sectionsByPath, sections]);
4769
+ const showBreadcrumb = step === "confirmed" && selectedPage && selectedSection;
4770
+ const showSectionPicker = step === "sectionPicker" && selectedPage;
4771
+ const showSectionRow = mode === "edit" && selectedPage && selectedSection && step === "input";
4772
+ const showChooseSection = step === "input" && selectedPage && !selectedSection && !showSectionRow;
4773
+ const handleInputChange = (value) => {
4774
+ setSearchValue(value);
4775
+ setSelectedPage(null);
4776
+ setSelectedSection(null);
4777
+ setStep("input");
4778
+ setUrlError("");
4779
+ const filtered = value.trim() ? availablePages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase())) : availablePages;
4780
+ setDropdownOpen(filtered.length > 0);
4781
+ if (value.trim() && filtered.length === 0) {
4782
+ const error = validateUrlInput(value, availablePages, pages);
4783
+ if (error) setUrlError(error);
4784
+ }
4785
+ };
4786
+ const handlePageSelect = (page) => {
4787
+ setSelectedPage({ path: page.path, title: page.title });
4788
+ setSelectedSection(null);
4789
+ setSearchValue(page.title);
4790
+ setStep("input");
4791
+ setDropdownOpen(false);
4792
+ setUrlError("");
4793
+ };
4794
+ const handleChooseSection = () => {
4795
+ setStep("sectionPicker");
4796
+ };
4797
+ const handleSectionSelect = (section) => {
4798
+ setSelectedSection({ id: section.id, label: section.label });
4799
+ if (mode === "create") {
4800
+ setStep("confirmed");
4801
+ } else {
4802
+ setStep("input");
4803
+ }
4804
+ };
4805
+ const handleBackToSections = () => {
4806
+ setStep("sectionPicker");
4807
+ };
4808
+ const handleClose = () => {
4809
+ reset();
4810
+ onClose();
4811
+ };
4812
+ const isValid = (0, import_react4.useMemo)(() => {
4813
+ if (urlError) return false;
4814
+ if (showBreadcrumb) return true;
4815
+ if (selectedPage) return true;
4816
+ if (!searchValue.trim()) return false;
4817
+ return validateUrlInput(searchValue, availablePages, pages) === "";
4818
+ }, [urlError, showBreadcrumb, selectedPage, searchValue, availablePages, pages]);
4819
+ const handleSubmit = () => {
4820
+ if (selectedPage && selectedSection) {
4821
+ onSubmit(buildTarget(selectedPage, selectedSection));
4822
+ handleClose();
4823
+ return;
4824
+ }
4825
+ if (selectedPage) {
4826
+ onSubmit(buildTarget(selectedPage, null));
4827
+ handleClose();
4828
+ return;
4829
+ }
4830
+ if (!searchValue.trim()) return;
4831
+ const error = validateUrlInput(searchValue, availablePages, pages);
4832
+ if (error) {
4833
+ setUrlError(error);
4834
+ return;
4835
+ }
4836
+ const matchingPage = pages.find((p) => p.title.toLowerCase() === searchValue.toLowerCase());
4837
+ if (matchingPage) {
4838
+ onSubmit(matchingPage.path);
4839
+ handleClose();
4840
+ return;
4841
+ }
4842
+ onSubmit(normalizeUrl(searchValue));
4843
+ handleClose();
4844
+ };
4845
+ const submitLabel = mode === "edit" ? "Save" : "Create";
4846
+ const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
4847
+ const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
4848
+ const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
4849
+ return {
4850
+ title,
4851
+ submitLabel,
4852
+ secondaryLabel,
4853
+ searchValue,
4854
+ selectedPage,
4855
+ selectedSection,
4856
+ step,
4857
+ dropdownOpen,
4858
+ setDropdownOpen,
4859
+ urlError,
4860
+ filteredPages,
4861
+ showBreadcrumb,
4862
+ showSectionPicker,
4863
+ showChooseSection,
4864
+ showSectionRow,
4865
+ isValid,
4866
+ activeSections,
4867
+ handleInputChange,
4868
+ handlePageSelect,
4869
+ handleChooseSection,
4870
+ handleSectionSelect,
4871
+ handleBackToSections,
4872
+ handleClose,
4873
+ handleSecondary,
4874
+ handleSubmit
4875
+ };
4876
+ }
4877
+
4878
+ // src/ui/link-modal/LinkEditorPanel.tsx
4879
+ var import_jsx_runtime13 = require("react/jsx-runtime");
4880
+ function LinkEditorPanel({
4881
+ open = true,
4882
+ mode = "create",
4883
+ pages,
4884
+ sections = [],
4885
+ sectionsByPath,
4886
+ initialTarget,
4887
+ existingTargets = [],
4888
+ onClose,
4889
+ onSubmit
4890
+ }) {
4891
+ const state = useLinkModalState({
4892
+ open,
4893
+ mode,
4894
+ pages,
4895
+ sections,
4896
+ sectionsByPath,
4897
+ initialTarget,
4898
+ existingTargets,
4899
+ onClose,
4900
+ onSubmit
4901
+ });
4902
+ const isCancel = state.secondaryLabel === "Cancel";
4903
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
4904
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4905
+ "button",
4906
+ {
4907
+ type: "button",
4908
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4909
+ onClick: state.handleClose,
4910
+ "aria-label": "Close",
4911
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconX, { "aria-hidden": true })
4912
+ }
4913
+ ),
4914
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("header", { className: "flex w-full flex-col gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { className: "m-0 w-full break-words text-lg font-semibold leading-none tracking-tight text-card-foreground", children: state.title }) }),
4915
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
4916
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4917
+ DestinationBreadcrumb,
4918
+ {
4919
+ pageTitle: state.selectedPage.title,
4920
+ sectionLabel: state.selectedSection.label
4921
+ }
4922
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4923
+ UrlOrPageInput,
4924
+ {
4925
+ value: state.searchValue,
4926
+ selectedPage: state.selectedPage,
4927
+ dropdownOpen: state.dropdownOpen,
4928
+ onDropdownOpenChange: state.setDropdownOpen,
4929
+ filteredPages: state.filteredPages,
4930
+ onInputChange: state.handleInputChange,
4931
+ onPageSelect: state.handlePageSelect,
4932
+ urlError: state.urlError
4933
+ }
4934
+ ),
4935
+ state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
4936
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
4937
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
4938
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
4939
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "Pick a section this link should scroll to." })
4940
+ ] })
4941
+ ] }) : null,
4942
+ state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
4943
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
4944
+ ] }),
4945
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("footer", { className: "flex w-full items-center justify-end gap-2 px-6 pb-6", children: [
4946
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4947
+ Button,
4948
+ {
4949
+ type: "button",
4950
+ variant: isCancel ? "outline" : "ghost",
4951
+ className: "leading-6 shadow-none cursor-pointer",
4952
+ style: isCancel ? {
4953
+ backgroundColor: "var(--ohw-background)",
4954
+ borderColor: "var(--ohw-border)",
4955
+ color: "var(--ohw-foreground)"
4956
+ } : void 0,
4957
+ onClick: state.handleSecondary,
4958
+ children: state.secondaryLabel
4959
+ }
4960
+ ),
4961
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4962
+ Button,
4963
+ {
4964
+ type: "button",
4965
+ className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
4966
+ style: {
4967
+ backgroundColor: "var(--ohw-primary)",
4968
+ color: "var(--ohw-primary-foreground)"
4969
+ },
4970
+ disabled: !state.isValid,
4971
+ onClick: state.handleSubmit,
4972
+ children: state.submitLabel
4973
+ }
4974
+ )
4975
+ ] })
4976
+ ] });
4977
+ }
4978
+
4979
+ // src/ui/link-modal/LinkPopover.tsx
4980
+ var import_jsx_runtime14 = require("react/jsx-runtime");
4981
+ function LinkPopover({
4982
+ rect,
4983
+ parentScroll,
4984
+ panelRef,
4985
+ ...editorProps
4986
+ }) {
4987
+ const { top, left, transform } = calcPopoverPos(rect, parentScroll);
4988
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4989
+ "div",
4990
+ {
4991
+ ref: panelRef,
4992
+ "data-ohw-link-popover-root": "",
4993
+ "data-ohw-link-modal-root": "",
4994
+ "data-ohw-bridge": "",
4995
+ className: cn(
4996
+ "pointer-events-auto fixed z-[2147483647] flex w-[483px] max-w-[calc(100vw-16px)] flex-col overflow-hidden",
4997
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none"
4998
+ ),
4999
+ style: { top, left, transform },
5000
+ onMouseDown: (e) => e.stopPropagation(),
5001
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(LinkEditorPanel, { ...editorProps, open: true })
5002
+ }
5003
+ );
5004
+ }
5005
+
5006
+ // src/ui/link-modal/devFixtures.ts
5007
+ var DEV_SITE_PAGES = [
5008
+ { path: "/", title: "Home" },
5009
+ { path: "/about", title: "About" },
5010
+ { path: "/classes", title: "Classes" },
5011
+ { path: "/pricing", title: "Pricing" },
5012
+ { path: "/studio", title: "Studio" },
5013
+ { path: "/instructors", title: "Instructors" },
5014
+ { path: "/contact", title: "Contact" }
5015
+ ];
5016
+ var DEV_SECTIONS_BY_PATH = {
5017
+ "/": [
5018
+ { id: "hero", label: "Hero" },
5019
+ { id: "lagree-intro", label: "Lagree Intro" },
5020
+ { id: "classes-strip", label: "Classes" },
5021
+ { id: "feel-split", label: "Feel Split" },
5022
+ { id: "founder-teaser", label: "Founder" },
5023
+ { id: "plan-form", label: "Plan Form" },
5024
+ { id: "testimonials", label: "Testimonials" },
5025
+ { id: "wordmark", label: "Wordmark" }
5026
+ ],
5027
+ "/about": [
5028
+ { id: "manifesto", label: "Manifesto" },
5029
+ { id: "story-letter", label: "Our Story" },
5030
+ { id: "lagree-explainer", label: "Lagree Explainer" },
5031
+ { id: "waitlist-cta", label: "Waitlist" },
5032
+ { id: "personal-training", label: "Personal training" }
5033
+ ],
5034
+ "/classes": [{ id: "class-library", label: "Class Library" }],
5035
+ "/pricing": [
5036
+ { id: "benefits-marquee", label: "Benefits" },
5037
+ { id: "monthly-memberships", label: "Memberships" },
5038
+ { id: "package-matcher", label: "Packages" },
5039
+ { id: "specialty-programs", label: "Specialty Programs" },
5040
+ { id: "free-class-cta", label: "Free Class" }
5041
+ ],
5042
+ "/studio": [
5043
+ { id: "studio-intro", label: "Studio Intro" },
5044
+ { id: "studio-features", label: "Studio Features" },
5045
+ { id: "studio-gallery", label: "Studio Gallery" },
5046
+ { id: "studio-visit", label: "Visit Studio" }
5047
+ ],
5048
+ "/instructors": [{ id: "instructor-grid", label: "Instructors" }],
5049
+ "/contact": [
5050
+ { id: "hello-hero", label: "Hello" },
5051
+ { id: "contact-form", label: "Contact Form" },
5052
+ { id: "faq", label: "FAQ" }
5053
+ ]
5054
+ };
5055
+ function shouldUseDevFixtures() {
5056
+ if (typeof window === "undefined") return false;
5057
+ const raw = readPreservedSearch();
5058
+ const q = raw.startsWith("?") ? raw.slice(1) : raw;
5059
+ return new URLSearchParams(q).get("ohw-fixtures") === "1";
5060
+ }
5061
+
5062
+ // src/ui/badge.tsx
5063
+ var import_jsx_runtime15 = require("react/jsx-runtime");
5064
+ var badgeVariants = cva(
5065
+ "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",
5066
+ {
5067
+ variants: {
5068
+ variant: {
5069
+ default: "border-transparent bg-primary text-primary-foreground",
5070
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
5071
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
5072
+ outline: "text-foreground"
5073
+ }
5074
+ },
5075
+ defaultVariants: {
5076
+ variant: "default"
5077
+ }
5078
+ }
5079
+ );
5080
+ function Badge({ className, variant, ...props }) {
5081
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
5082
+ }
5083
+
3475
5084
  // src/OhhwellsBridge.tsx
3476
- var import_jsx_runtime3 = require("react/jsx-runtime");
5085
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3477
5086
  var PRIMARY = "#0885FE";
3478
5087
  var IMAGE_FADE_MS = 300;
3479
5088
  function runOpacityFade(el, onDone) {
@@ -3524,8 +5133,173 @@ function fadeInBgImage(el, url, onReady) {
3524
5133
  if (!prevPos || prevPos === "static") el.style.position = prevPos;
3525
5134
  });
3526
5135
  }
5136
+ function getSectionsTracker() {
5137
+ let el = document.querySelector("[data-ohw-sections-tracker]");
5138
+ if (!el) {
5139
+ el = document.createElement("div");
5140
+ el.setAttribute("data-ohw-sections-tracker", "");
5141
+ el.style.display = "none";
5142
+ document.body.appendChild(el);
5143
+ }
5144
+ return el;
5145
+ }
5146
+ function updateSectionScheduleId(insertAfter, scheduleId) {
5147
+ const tracker = getSectionsTracker();
5148
+ let sections = [];
5149
+ try {
5150
+ sections = JSON.parse(tracker.textContent || "[]");
5151
+ } catch {
5152
+ }
5153
+ const currentPath = window.location.pathname;
5154
+ const updated = sections.map((s) => {
5155
+ if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
5156
+ if (s.pagePath && s.pagePath !== currentPath) return s;
5157
+ return { ...s, scheduleId };
5158
+ });
5159
+ tracker.textContent = JSON.stringify(updated);
5160
+ return tracker.textContent ?? "[]";
5161
+ }
5162
+ function schedulingSectionId(insertAfter) {
5163
+ return `scheduling-${insertAfter}`;
5164
+ }
5165
+ var INSERT_BEFORE_MARKER = ":before:";
5166
+ function parseSchedulingInsertAfter(insertAfter) {
5167
+ const idx = insertAfter.indexOf(INSERT_BEFORE_MARKER);
5168
+ if (idx < 0) return { anchor: insertAfter, insertBefore: null };
5169
+ return {
5170
+ anchor: insertAfter.slice(0, idx),
5171
+ insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
5172
+ };
5173
+ }
5174
+ function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
5175
+ const parsed = parseSchedulingInsertAfter(insertAfter);
5176
+ const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
5177
+ const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
5178
+ return { effectiveInsertAfter, insertBefore };
5179
+ }
5180
+ function getSchedulingMountPoint(insertAfter) {
5181
+ const { anchor } = parseSchedulingInsertAfter(insertAfter);
5182
+ let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
5183
+ if (!anchorEl && anchor === "scheduling") {
5184
+ const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
5185
+ anchorEl = widgets.at(-1) ?? null;
5186
+ }
5187
+ if (!anchorEl) return null;
5188
+ return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
5189
+ }
5190
+ function schedulingMountDepth(insertAfter) {
5191
+ if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
5192
+ return insertAfter.split(INSERT_BEFORE_MARKER).length - 1;
5193
+ }
5194
+ function getPageSchedulingEntries(raw) {
5195
+ if (!raw) return [];
5196
+ try {
5197
+ const entries = JSON.parse(raw);
5198
+ const currentPath = window.location.pathname;
5199
+ return entries.filter((e) => e.type === "scheduling" && (!e.pagePath || e.pagePath === currentPath));
5200
+ } catch {
5201
+ return [];
5202
+ }
5203
+ }
5204
+ function isSchedulingWidgetMissing(entry) {
5205
+ const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
5206
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
5207
+ }
5208
+ function hasMissingSchedulingWidgets(entries) {
5209
+ return entries.some(isSchedulingWidgetMissing);
5210
+ }
5211
+ function retryMissingSchedulingMounts(entries, notifyOnConnect = false) {
5212
+ if (!hasMissingSchedulingWidgets(entries)) return;
5213
+ mountSchedulingEntries(entries, notifyOnConnect);
5214
+ }
5215
+ function initSectionsFromContent(content, removeExisting = false) {
5216
+ const raw = content["__ohw_sections"];
5217
+ if (!raw) return;
5218
+ try {
5219
+ if (removeExisting) {
5220
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => el.remove());
5221
+ }
5222
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
5223
+ if (inEditor) getSectionsTracker().textContent = raw;
5224
+ const pageEntries = getPageSchedulingEntries(raw);
5225
+ mountSchedulingEntries(pageEntries, false);
5226
+ requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, false));
5227
+ setTimeout(() => retryMissingSchedulingMounts(pageEntries, false), 250);
5228
+ } catch {
5229
+ }
5230
+ }
5231
+ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
5232
+ const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
5233
+ const sectionId = schedulingSectionId(effectiveInsertAfter);
5234
+ if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
5235
+ const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
5236
+ if (!mountPoint) return false;
5237
+ const container = document.createElement("div");
5238
+ container.dataset.ohwSectionContainer = "scheduling";
5239
+ if (insertBefore) {
5240
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
5241
+ const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
5242
+ if (!beforePoint) return false;
5243
+ beforePoint.insertAdjacentElement("beforebegin", container);
5244
+ } else {
5245
+ let tail = mountPoint;
5246
+ while (tail.nextElementSibling?.dataset.ohwSectionContainer === "scheduling") {
5247
+ tail = tail.nextElementSibling;
5248
+ }
5249
+ tail.insertAdjacentElement("afterend", container);
5250
+ }
5251
+ const root = (0, import_client.createRoot)(container);
5252
+ (0, import_react_dom.flushSync)(() => {
5253
+ root.render(
5254
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5255
+ SchedulingWidget,
5256
+ {
5257
+ notifyOnConnect,
5258
+ initialScheduleId: scheduleId,
5259
+ insertAfter: effectiveInsertAfter
5260
+ }
5261
+ )
5262
+ );
5263
+ });
5264
+ const tracker = getSectionsTracker();
5265
+ let sections = [];
5266
+ try {
5267
+ sections = JSON.parse(tracker.textContent || "[]");
5268
+ } catch {
5269
+ }
5270
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
5271
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
5272
+ sections.push({
5273
+ type: "scheduling",
5274
+ insertAfter: effectiveInsertAfter,
5275
+ pagePath: window.location.pathname,
5276
+ ...scheduleId ? { scheduleId } : {}
5277
+ });
5278
+ tracker.textContent = JSON.stringify(sections);
5279
+ }
5280
+ return true;
5281
+ }
5282
+ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5283
+ const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
5284
+ for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
5285
+ for (let i = pending.length - 1; i >= 0; i--) {
5286
+ const { insertAfter, scheduleId } = pending[i];
5287
+ if (mountSchedulingWidget(insertAfter, notifyOnConnect, scheduleId)) {
5288
+ pending.splice(i, 1);
5289
+ }
5290
+ }
5291
+ }
5292
+ }
5293
+ function getLinkHref(el) {
5294
+ const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5295
+ return anchor?.getAttribute("href") ?? "";
5296
+ }
5297
+ function applyLinkHref(el, val) {
5298
+ const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
5299
+ if (anchor) anchor.setAttribute("href", val);
5300
+ }
3527
5301
  function collectEditableNodes() {
3528
- return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
5302
+ const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
3529
5303
  if (el.dataset.ohwEditable === "image") {
3530
5304
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
3531
5305
  return { key: el.dataset.ohwKey ?? "", type: "image", text: img?.src ?? "" };
@@ -3535,12 +5309,52 @@ function collectEditableNodes() {
3535
5309
  const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
3536
5310
  return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
3537
5311
  }
5312
+ if (el.dataset.ohwEditable === "link") {
5313
+ return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
5314
+ }
3538
5315
  return {
3539
5316
  key: el.dataset.ohwKey ?? "",
3540
5317
  type: el.dataset.ohwEditable ?? "text",
3541
5318
  text: el.dataset.ohwEditable === "plain" ? el.innerText ?? "" : el.innerHTML
3542
5319
  };
3543
5320
  });
5321
+ document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
5322
+ const key = el.getAttribute("data-ohw-href-key") ?? "";
5323
+ if (!key) return;
5324
+ nodes.push({ key, type: "link", text: getLinkHref(el) });
5325
+ });
5326
+ return nodes;
5327
+ }
5328
+ function applyLinkByKey(key, val) {
5329
+ document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
5330
+ if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
5331
+ });
5332
+ document.querySelectorAll(`[data-ohw-href-key="${key}"]`).forEach((el) => {
5333
+ applyLinkHref(el, val);
5334
+ });
5335
+ }
5336
+ function isInsideLinkEditor(target) {
5337
+ return Boolean(
5338
+ target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest('[data-slot="popover-content"]')
5339
+ );
5340
+ }
5341
+ function getHrefKeyFromElement(el) {
5342
+ if (!el) return null;
5343
+ const anchor = el.closest("[data-ohw-href-key]");
5344
+ if (!anchor) return null;
5345
+ const key = anchor.getAttribute("data-ohw-href-key");
5346
+ if (!key) return null;
5347
+ return { anchor, key };
5348
+ }
5349
+ function titleCaseSectionId(id) {
5350
+ return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
5351
+ }
5352
+ function collectSections() {
5353
+ return Array.from(document.querySelectorAll("[data-ohw-section]")).map((el) => {
5354
+ const id = el.getAttribute("data-ohw-section") ?? "";
5355
+ const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
5356
+ return { id, label };
5357
+ });
3544
5358
  }
3545
5359
  var FORCE_PSEUDO_STATES = [
3546
5360
  { pseudo: ":hover", state: "hover" },
@@ -3681,7 +5495,7 @@ var TOOLBAR_GROUPS = [
3681
5495
  ];
3682
5496
  function GlowFrame({ rect, elRef }) {
3683
5497
  const GAP = 6;
3684
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5498
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3685
5499
  "div",
3686
5500
  {
3687
5501
  ref: elRef,
@@ -3729,10 +5543,12 @@ function FloatingToolbar({
3729
5543
  parentScroll,
3730
5544
  elRef,
3731
5545
  onCommand,
3732
- activeCommands
5546
+ activeCommands,
5547
+ showEditLink,
5548
+ onEditLink
3733
5549
  }) {
3734
5550
  const { top, left, transform } = calcToolbarPos(rect, parentScroll);
3735
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5551
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3736
5552
  "div",
3737
5553
  {
3738
5554
  ref: elRef,
@@ -3755,56 +5571,97 @@ function FloatingToolbar({
3755
5571
  pointerEvents: "auto",
3756
5572
  whiteSpace: "nowrap"
3757
5573
  },
3758
- children: TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react.default.Fragment, { children: [
3759
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
3760
- btns.map((btn) => {
3761
- const isActive = activeCommands.has(btn.cmd);
3762
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3763
- "button",
3764
- {
3765
- title: btn.title,
3766
- onMouseDown: (e) => {
3767
- e.preventDefault();
3768
- onCommand(btn.cmd);
5574
+ onMouseDown: (e) => e.stopPropagation(),
5575
+ children: [
5576
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react5.default.Fragment, { children: [
5577
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5578
+ btns.map((btn) => {
5579
+ const isActive = activeCommands.has(btn.cmd);
5580
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5581
+ "button",
5582
+ {
5583
+ title: btn.title,
5584
+ onMouseDown: (e) => {
5585
+ e.preventDefault();
5586
+ onCommand(btn.cmd);
5587
+ },
5588
+ onMouseEnter: (e) => {
5589
+ if (!isActive) e.currentTarget.style.background = "#F5F5F4";
5590
+ },
5591
+ onMouseLeave: (e) => {
5592
+ if (!isActive) e.currentTarget.style.background = "transparent";
5593
+ },
5594
+ style: {
5595
+ display: "flex",
5596
+ alignItems: "center",
5597
+ justifyContent: "center",
5598
+ border: "none",
5599
+ background: isActive ? PRIMARY : "transparent",
5600
+ borderRadius: 4,
5601
+ cursor: "pointer",
5602
+ color: isActive ? "#FFFFFF" : "#1C1917",
5603
+ flexShrink: 0,
5604
+ padding: 6
5605
+ },
5606
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5607
+ "svg",
5608
+ {
5609
+ width: "16",
5610
+ height: "16",
5611
+ viewBox: "0 0 24 24",
5612
+ fill: "none",
5613
+ stroke: isActive ? "#FFFFFF" : "#1C1917",
5614
+ strokeWidth: "2.5",
5615
+ strokeLinecap: "round",
5616
+ strokeLinejoin: "round",
5617
+ style: isActive && gi > 0 ? { filter: "drop-shadow(0 0 0.5px #fff)" } : void 0,
5618
+ dangerouslySetInnerHTML: { __html: ICONS[btn.cmd] }
5619
+ }
5620
+ )
3769
5621
  },
3770
- onMouseEnter: (e) => {
3771
- if (!isActive) e.currentTarget.style.background = "#F5F5F4";
3772
- },
3773
- onMouseLeave: (e) => {
3774
- if (!isActive) e.currentTarget.style.background = "transparent";
3775
- },
3776
- style: {
3777
- display: "flex",
3778
- alignItems: "center",
3779
- justifyContent: "center",
3780
- border: "none",
3781
- background: isActive ? PRIMARY : "transparent",
3782
- borderRadius: 4,
3783
- cursor: "pointer",
3784
- color: isActive ? "#FFFFFF" : "#1C1917",
3785
- flexShrink: 0,
3786
- padding: 6
3787
- },
3788
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3789
- "svg",
3790
- {
3791
- width: "16",
3792
- height: "16",
3793
- viewBox: "0 0 24 24",
3794
- fill: "none",
3795
- stroke: isActive ? "#FFFFFF" : "#1C1917",
3796
- strokeWidth: "2.5",
3797
- strokeLinecap: "round",
3798
- strokeLinejoin: "round",
3799
- style: isActive && gi > 0 ? { filter: "drop-shadow(0 0 0.5px #fff)" } : void 0,
3800
- dangerouslySetInnerHTML: { __html: ICONS[btn.cmd] }
3801
- }
3802
- )
5622
+ btn.cmd
5623
+ );
5624
+ })
5625
+ ] }, gi)),
5626
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5627
+ "button",
5628
+ {
5629
+ type: "button",
5630
+ title: "Edit link",
5631
+ onMouseDown: (e) => {
5632
+ e.preventDefault();
5633
+ e.stopPropagation();
5634
+ onEditLink?.();
5635
+ },
5636
+ onClick: (e) => {
5637
+ e.preventDefault();
5638
+ e.stopPropagation();
3803
5639
  },
3804
- btn.cmd
3805
- );
3806
- })
3807
- ] }, gi))
5640
+ onMouseEnter: (e) => {
5641
+ e.currentTarget.style.background = "#F5F5F4";
5642
+ },
5643
+ onMouseLeave: (e) => {
5644
+ e.currentTarget.style.background = "transparent";
5645
+ },
5646
+ style: {
5647
+ display: "flex",
5648
+ alignItems: "center",
5649
+ justifyContent: "center",
5650
+ border: "none",
5651
+ background: "transparent",
5652
+ borderRadius: 4,
5653
+ cursor: "pointer",
5654
+ color: "#1C1917",
5655
+ flexShrink: 0,
5656
+ padding: 6
5657
+ },
5658
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
5659
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
5660
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
5661
+ ] })
5662
+ }
5663
+ ) : null
5664
+ ]
3808
5665
  }
3809
5666
  );
3810
5667
  }
@@ -3818,7 +5675,7 @@ function StateToggle({
3818
5675
  states,
3819
5676
  onStateChange
3820
5677
  }) {
3821
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5678
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3822
5679
  ToggleGroup,
3823
5680
  {
3824
5681
  "data-ohw-state-toggle": "",
@@ -3832,18 +5689,35 @@ function StateToggle({
3832
5689
  left: rect.right - 8,
3833
5690
  transform: "translateX(-100%)"
3834
5691
  },
3835
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
5692
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
3836
5693
  }
3837
5694
  );
3838
5695
  }
3839
5696
  var contentCache = /* @__PURE__ */ new Map();
5697
+ function resolveSubdomain(subdomainFromQuery) {
5698
+ if (subdomainFromQuery) return subdomainFromQuery;
5699
+ if (typeof window !== "undefined") {
5700
+ const parts = window.location.hostname.split(".");
5701
+ if (parts.length >= 3 && parts[0] !== "www") return parts[0];
5702
+ }
5703
+ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
5704
+ if (siteUrl) {
5705
+ try {
5706
+ const host = new URL(siteUrl).hostname;
5707
+ const siteParts = host.split(".");
5708
+ if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
5709
+ } catch {
5710
+ }
5711
+ }
5712
+ return "";
5713
+ }
3840
5714
  function OhhwellsBridge() {
3841
5715
  const pathname = (0, import_navigation.usePathname)();
3842
5716
  const router = (0, import_navigation.useRouter)();
3843
5717
  const searchParams = (0, import_navigation.useSearchParams)();
3844
5718
  const isEditMode = isEditSessionActive();
3845
- const [bridgeRoot, setBridgeRoot] = (0, import_react.useState)(null);
3846
- (0, import_react.useEffect)(() => {
5719
+ const [bridgeRoot, setBridgeRoot] = (0, import_react5.useState)(null);
5720
+ (0, import_react5.useEffect)(() => {
3847
5721
  const figtreeFontId = "ohw-figtree-font";
3848
5722
  if (!document.getElementById(figtreeFontId)) {
3849
5723
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -3866,42 +5740,76 @@ function OhhwellsBridge() {
3866
5740
  };
3867
5741
  }, []);
3868
5742
  const subdomainFromQuery = searchParams.get("subdomain");
3869
- const subdomain = subdomainFromQuery ?? (() => {
3870
- if (typeof window === "undefined") return "";
3871
- const parts = window.location.hostname.split(".");
3872
- return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
3873
- })();
3874
- const postToParent = (0, import_react.useCallback)((data) => {
5743
+ const subdomain = resolveSubdomain(subdomainFromQuery);
5744
+ const postToParent = (0, import_react5.useCallback)((data) => {
3875
5745
  if (typeof window !== "undefined" && window.parent !== window) {
3876
5746
  window.parent.postMessage(data, "*");
3877
5747
  }
3878
5748
  }, []);
3879
- const [fetchState, setFetchState] = (0, import_react.useState)("idle");
3880
- const autoSaveTimers = (0, import_react.useRef)(/* @__PURE__ */ new Map());
3881
- const activeElRef = (0, import_react.useRef)(null);
3882
- const originalContentRef = (0, import_react.useRef)(null);
3883
- const activeStateElRef = (0, import_react.useRef)(null);
3884
- const parentScrollRef = (0, import_react.useRef)(null);
3885
- const toolbarElRef = (0, import_react.useRef)(null);
3886
- const glowElRef = (0, import_react.useRef)(null);
3887
- const hoveredImageRef = (0, import_react.useRef)(null);
3888
- const hoveredImageHasTextOverlapRef = (0, import_react.useRef)(false);
3889
- const imageUnhoverTimerRef = (0, import_react.useRef)(null);
3890
- const imageShowTimerRef = (0, import_react.useRef)(null);
3891
- const editStylesRef = (0, import_react.useRef)(null);
3892
- const activateRef = (0, import_react.useRef)(() => {
5749
+ const [fetchState, setFetchState] = (0, import_react5.useState)("idle");
5750
+ const autoSaveTimers = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
5751
+ const activeElRef = (0, import_react5.useRef)(null);
5752
+ const originalContentRef = (0, import_react5.useRef)(null);
5753
+ const activeStateElRef = (0, import_react5.useRef)(null);
5754
+ const parentScrollRef = (0, import_react5.useRef)(null);
5755
+ const toolbarElRef = (0, import_react5.useRef)(null);
5756
+ const glowElRef = (0, import_react5.useRef)(null);
5757
+ const hoveredImageRef = (0, import_react5.useRef)(null);
5758
+ const hoveredImageHasTextOverlapRef = (0, import_react5.useRef)(false);
5759
+ const hoveredGapRef = (0, import_react5.useRef)(null);
5760
+ const imageUnhoverTimerRef = (0, import_react5.useRef)(null);
5761
+ const imageShowTimerRef = (0, import_react5.useRef)(null);
5762
+ const editStylesRef = (0, import_react5.useRef)(null);
5763
+ const activateRef = (0, import_react5.useRef)(() => {
3893
5764
  });
3894
- const deactivateRef = (0, import_react.useRef)(() => {
5765
+ const deactivateRef = (0, import_react5.useRef)(() => {
3895
5766
  });
3896
- const refreshActiveCommandsRef = (0, import_react.useRef)(() => {
5767
+ const refreshActiveCommandsRef = (0, import_react5.useRef)(() => {
3897
5768
  });
3898
- const postToParentRef = (0, import_react.useRef)(postToParent);
5769
+ const postToParentRef = (0, import_react5.useRef)(postToParent);
3899
5770
  postToParentRef.current = postToParent;
3900
- const [toolbarRect, setToolbarRect] = (0, import_react.useState)(null);
3901
- const [toggleState, setToggleState] = (0, import_react.useState)(null);
3902
- const [maxBadge, setMaxBadge] = (0, import_react.useState)(null);
3903
- const [activeCommands, setActiveCommands] = (0, import_react.useState)(/* @__PURE__ */ new Set());
3904
- (0, import_react.useEffect)(() => {
5771
+ const [toolbarRect, setToolbarRect] = (0, import_react5.useState)(null);
5772
+ const [toggleState, setToggleState] = (0, import_react5.useState)(null);
5773
+ const [maxBadge, setMaxBadge] = (0, import_react5.useState)(null);
5774
+ const [activeCommands, setActiveCommands] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
5775
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react5.useState)(false);
5776
+ const [linkPopover, setLinkPopover] = (0, import_react5.useState)(null);
5777
+ const [sitePages, setSitePages] = (0, import_react5.useState)([]);
5778
+ const [sectionsByPath, setSectionsByPath] = (0, import_react5.useState)({});
5779
+ const setLinkPopoverRef = (0, import_react5.useRef)(setLinkPopover);
5780
+ const linkPopoverPanelRef = (0, import_react5.useRef)(null);
5781
+ const linkPopoverOpenRef = (0, import_react5.useRef)(false);
5782
+ const linkPopoverGraceUntilRef = (0, import_react5.useRef)(0);
5783
+ setLinkPopoverRef.current = setLinkPopover;
5784
+ const bumpLinkPopoverGrace = () => {
5785
+ linkPopoverGraceUntilRef.current = Date.now() + 350;
5786
+ };
5787
+ (0, import_react5.useEffect)(() => {
5788
+ if (!isEditMode) return;
5789
+ const useFixtures = shouldUseDevFixtures();
5790
+ if (useFixtures) {
5791
+ setSitePages(DEV_SITE_PAGES);
5792
+ setSectionsByPath(DEV_SECTIONS_BY_PATH);
5793
+ if (process.env.NODE_ENV === "development") {
5794
+ console.info("[ohhwells-bridge] Dev fixtures loaded (ohw-fixtures=1)");
5795
+ }
5796
+ }
5797
+ const onSitePages = (e) => {
5798
+ if (e.data?.type !== "ow:site-pages" || !Array.isArray(e.data.pages)) return;
5799
+ if (useFixtures) return;
5800
+ setSitePages(
5801
+ e.data.pages.map((p) => ({
5802
+ path: p.path,
5803
+ title: p.title
5804
+ }))
5805
+ );
5806
+ };
5807
+ window.addEventListener("message", onSitePages);
5808
+ if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
5809
+ return () => window.removeEventListener("message", onSitePages);
5810
+ }, [isEditMode, postToParent]);
5811
+ const [sectionGap, setSectionGap] = (0, import_react5.useState)(null);
5812
+ (0, import_react5.useEffect)(() => {
3905
5813
  const update = () => {
3906
5814
  const el = activeElRef.current;
3907
5815
  if (el) setToolbarRect(el.getBoundingClientRect());
@@ -3909,6 +5817,12 @@ function OhhwellsBridge() {
3909
5817
  if (!prev || !activeStateElRef.current) return prev;
3910
5818
  return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
3911
5819
  });
5820
+ setSectionGap((prev) => {
5821
+ if (!prev) return prev;
5822
+ const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
5823
+ if (!anchor) return prev;
5824
+ return { ...prev, y: anchor.getBoundingClientRect().bottom };
5825
+ });
3912
5826
  };
3913
5827
  const vvp = window.visualViewport;
3914
5828
  if (!vvp) return;
@@ -3919,10 +5833,10 @@ function OhhwellsBridge() {
3919
5833
  vvp.removeEventListener("resize", update);
3920
5834
  };
3921
5835
  }, []);
3922
- const refreshStateRules = (0, import_react.useCallback)(() => {
5836
+ const refreshStateRules = (0, import_react5.useCallback)(() => {
3923
5837
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
3924
5838
  }, []);
3925
- const deactivate = (0, import_react.useCallback)(() => {
5839
+ const deactivate = (0, import_react5.useCallback)(() => {
3926
5840
  const el = activeElRef.current;
3927
5841
  if (!el) return;
3928
5842
  const key = el.dataset.ohwKey;
@@ -3945,9 +5859,10 @@ function OhhwellsBridge() {
3945
5859
  setToolbarRect(null);
3946
5860
  setMaxBadge(null);
3947
5861
  setActiveCommands(/* @__PURE__ */ new Set());
5862
+ setToolbarShowEditLink(false);
3948
5863
  postToParent({ type: "ow:exit-edit" });
3949
5864
  }, [postToParent]);
3950
- const activate = (0, import_react.useCallback)((el) => {
5865
+ const activate = (0, import_react5.useCallback)((el) => {
3951
5866
  if (activeElRef.current === el) return;
3952
5867
  deactivate();
3953
5868
  if (hoveredImageRef.current) {
@@ -3960,12 +5875,13 @@ function OhhwellsBridge() {
3960
5875
  originalContentRef.current = el.innerHTML;
3961
5876
  el.focus();
3962
5877
  setToolbarRect(el.getBoundingClientRect());
5878
+ setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
3963
5879
  postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
3964
5880
  requestAnimationFrame(() => refreshActiveCommandsRef.current());
3965
5881
  }, [deactivate, postToParent]);
3966
5882
  activateRef.current = activate;
3967
5883
  deactivateRef.current = deactivate;
3968
- (0, import_react.useLayoutEffect)(() => {
5884
+ (0, import_react5.useLayoutEffect)(() => {
3969
5885
  if (!subdomain || isEditMode) {
3970
5886
  setFetchState("done");
3971
5887
  return;
@@ -3973,6 +5889,7 @@ function OhhwellsBridge() {
3973
5889
  const applyContent = (content) => {
3974
5890
  const imageLoads = [];
3975
5891
  for (const [key, val] of Object.entries(content)) {
5892
+ if (key === "__ohw_sections") continue;
3976
5893
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
3977
5894
  if (el.dataset.ohwEditable === "image") {
3978
5895
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -3986,11 +5903,15 @@ function OhhwellsBridge() {
3986
5903
  } else if (el.dataset.ohwEditable === "bg-image") {
3987
5904
  const next = `url('${val}')`;
3988
5905
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
5906
+ } else if (el.dataset.ohwEditable === "link") {
5907
+ applyLinkHref(el, val);
3989
5908
  } else if (el.innerHTML !== val) {
3990
5909
  el.innerHTML = val;
3991
5910
  }
3992
5911
  });
5912
+ applyLinkByKey(key, val);
3993
5913
  }
5914
+ initSectionsFromContent(content, true);
3994
5915
  return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
3995
5916
  }) : Promise.resolve();
3996
5917
  };
@@ -4015,12 +5936,15 @@ function OhhwellsBridge() {
4015
5936
  cancelled = true;
4016
5937
  };
4017
5938
  }, [subdomain, isEditMode, pathname]);
4018
- (0, import_react.useEffect)(() => {
5939
+ (0, import_react5.useEffect)(() => {
4019
5940
  if (!subdomain || isEditMode) return;
5941
+ let debounceTimer = null;
4020
5942
  const applyFromCache = () => {
4021
5943
  const content = contentCache.get(subdomain);
4022
5944
  if (!content) return;
5945
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
4023
5946
  for (const [key, val] of Object.entries(content)) {
5947
+ if (key === "__ohw_sections") continue;
4024
5948
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
4025
5949
  if (el.dataset.ohwEditable === "image") {
4026
5950
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -4028,27 +5952,37 @@ function OhhwellsBridge() {
4028
5952
  } else if (el.dataset.ohwEditable === "bg-image") {
4029
5953
  const next = `url('${val}')`;
4030
5954
  if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
5955
+ } else if (el.dataset.ohwEditable === "link") {
5956
+ applyLinkHref(el, val);
4031
5957
  } else if (el.innerHTML !== val) {
4032
5958
  el.innerHTML = val;
4033
5959
  }
4034
5960
  });
5961
+ applyLinkByKey(key, val);
4035
5962
  }
4036
5963
  };
4037
- applyFromCache();
4038
- const observer = new MutationObserver(applyFromCache);
5964
+ const scheduleApply = () => {
5965
+ if (debounceTimer) clearTimeout(debounceTimer);
5966
+ debounceTimer = setTimeout(applyFromCache, 150);
5967
+ };
5968
+ scheduleApply();
5969
+ const observer = new MutationObserver(scheduleApply);
4039
5970
  observer.observe(document.body, { childList: true, subtree: true });
4040
- return () => observer.disconnect();
4041
- }, [subdomain, isEditMode]);
4042
- (0, import_react.useLayoutEffect)(() => {
5971
+ return () => {
5972
+ observer.disconnect();
5973
+ if (debounceTimer) clearTimeout(debounceTimer);
5974
+ };
5975
+ }, [subdomain, isEditMode, pathname]);
5976
+ (0, import_react5.useLayoutEffect)(() => {
4043
5977
  const el = document.getElementById("ohw-loader");
4044
5978
  if (!el) return;
4045
5979
  const visible = Boolean(subdomain) && fetchState !== "done";
4046
5980
  el.style.display = visible ? "flex" : "none";
4047
5981
  }, [subdomain, fetchState]);
4048
- (0, import_react.useEffect)(() => {
5982
+ (0, import_react5.useEffect)(() => {
4049
5983
  postToParent({ type: "ow:navigation", path: pathname });
4050
5984
  }, [pathname, postToParent]);
4051
- (0, import_react.useEffect)(() => {
5985
+ (0, import_react5.useEffect)(() => {
4052
5986
  if (!isEditMode) return;
4053
5987
  const measure = () => {
4054
5988
  const h = document.body.scrollHeight;
@@ -4072,7 +6006,7 @@ function OhhwellsBridge() {
4072
6006
  window.removeEventListener("resize", handleResize);
4073
6007
  };
4074
6008
  }, [pathname, isEditMode, postToParent]);
4075
- (0, import_react.useEffect)(() => {
6009
+ (0, import_react5.useEffect)(() => {
4076
6010
  if (!subdomainFromQuery || isEditMode) return;
4077
6011
  const handleClick = (e) => {
4078
6012
  const anchor = e.target.closest("a");
@@ -4088,7 +6022,7 @@ function OhhwellsBridge() {
4088
6022
  document.addEventListener("click", handleClick, true);
4089
6023
  return () => document.removeEventListener("click", handleClick, true);
4090
6024
  }, [subdomainFromQuery, isEditMode, router]);
4091
- (0, import_react.useEffect)(() => {
6025
+ (0, import_react5.useEffect)(() => {
4092
6026
  if (!isEditMode) {
4093
6027
  editStylesRef.current?.base.remove();
4094
6028
  editStylesRef.current?.forceHover.remove();
@@ -4114,6 +6048,7 @@ function OhhwellsBridge() {
4114
6048
  [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]) { cursor: text !important; }
4115
6049
  [data-ohw-editable="image"], [data-ohw-editable="image"] *,
4116
6050
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
6051
+ [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
4117
6052
  [data-ohw-hovered]:not([contenteditable]) {
4118
6053
  outline: 2px dashed ${PRIMARY} !important;
4119
6054
  outline-offset: 4px;
@@ -4151,8 +6086,21 @@ function OhhwellsBridge() {
4151
6086
  if (target.closest("[data-ohw-toolbar]")) return;
4152
6087
  if (target.closest("[data-ohw-state-toggle]")) return;
4153
6088
  if (target.closest("[data-ohw-max-badge]")) return;
6089
+ if (isInsideLinkEditor(target)) return;
4154
6090
  const editable = target.closest("[data-ohw-editable]");
4155
6091
  if (editable) {
6092
+ if (editable.dataset.ohwEditable === "link") {
6093
+ e.preventDefault();
6094
+ e.stopPropagation();
6095
+ deactivateRef.current();
6096
+ bumpLinkPopoverGrace();
6097
+ setLinkPopoverRef.current({
6098
+ key: editable.dataset.ohwKey ?? "",
6099
+ target: getLinkHref(editable),
6100
+ rect: editable.getBoundingClientRect()
6101
+ });
6102
+ return;
6103
+ }
4156
6104
  if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
4157
6105
  e.preventDefault();
4158
6106
  e.stopPropagation();
@@ -4173,6 +6121,15 @@ function OhhwellsBridge() {
4173
6121
  }
4174
6122
  const anchor = target.closest("a");
4175
6123
  if (anchor) e.preventDefault();
6124
+ if (Date.now() < linkPopoverGraceUntilRef.current) return;
6125
+ if (linkPopoverOpenRef.current && activeElRef.current) {
6126
+ const hrefCtx = getHrefKeyFromElement(activeElRef.current);
6127
+ if (hrefCtx && (hrefCtx.anchor === target || hrefCtx.anchor.contains(target))) return;
6128
+ }
6129
+ if (linkPopoverOpenRef.current) {
6130
+ setLinkPopoverRef.current(null);
6131
+ return;
6132
+ }
4176
6133
  deactivateRef.current();
4177
6134
  };
4178
6135
  const handleMouseOver = (e) => {
@@ -4348,6 +6305,15 @@ function OhhwellsBridge() {
4348
6305
  textEditable.setAttribute("data-ohw-hovered", "");
4349
6306
  return;
4350
6307
  }
6308
+ if (hoveredGapRef.current) {
6309
+ if (hoveredImageRef.current) {
6310
+ hoveredImageRef.current = null;
6311
+ resumeAnimTracks();
6312
+ postToParentRef.current({ type: "ow:image-unhover" });
6313
+ }
6314
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
6315
+ return;
6316
+ }
4351
6317
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
4352
6318
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
4353
6319
  hoveredImageRef.current = imgEl;
@@ -4413,8 +6379,30 @@ function OhhwellsBridge() {
4413
6379
  }
4414
6380
  }
4415
6381
  };
6382
+ const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
6383
+ const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
6384
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
6385
+ const ZONE = 20;
6386
+ for (let i = 0; i < sections.length; i++) {
6387
+ const a = sections[i];
6388
+ const b = sections[i + 1] ?? null;
6389
+ const boundaryY = a.getBoundingClientRect().bottom;
6390
+ if (Math.abs(y - boundaryY) <= ZONE) {
6391
+ const insertAfter = a.dataset.ohwSection ?? "";
6392
+ const insertBefore = b?.dataset.ohwSection ?? null;
6393
+ hoveredGapRef.current = { insertAfter, insertBefore };
6394
+ setSectionGap({ insertAfter, insertBefore, y: boundaryY });
6395
+ return;
6396
+ }
6397
+ }
6398
+ if (hoveredGapRef.current) {
6399
+ hoveredGapRef.current = null;
6400
+ setSectionGap(null);
6401
+ }
6402
+ };
4416
6403
  const handleMouseMove = (e) => {
4417
6404
  const { clientX, clientY } = e;
6405
+ probeSectionGapAt(clientX, clientY);
4418
6406
  probeImageAt(clientX, clientY);
4419
6407
  probeHoverCardsAt(clientX, clientY);
4420
6408
  };
@@ -4422,6 +6410,7 @@ function OhhwellsBridge() {
4422
6410
  if (e.data?.type !== "ow:pointer-sync") return;
4423
6411
  const { clientX, clientY } = e.data;
4424
6412
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
6413
+ probeSectionGapAt(clientX, clientY);
4425
6414
  probeImageAt(clientX, clientY);
4426
6415
  probeHoverCardsAt(clientX, clientY);
4427
6416
  };
@@ -4579,27 +6568,47 @@ function OhhwellsBridge() {
4579
6568
  if (e.data?.type !== "ow:hydrate") return;
4580
6569
  const content = e.data.content;
4581
6570
  if (!content) return;
6571
+ let sectionsJson = null;
4582
6572
  for (const [key, val] of Object.entries(content)) {
6573
+ if (key === "__ohw_sections") {
6574
+ sectionsJson = val;
6575
+ continue;
6576
+ }
4583
6577
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
4584
6578
  if (el.dataset.ohwEditable === "image") {
4585
6579
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
4586
6580
  if (img) img.src = val;
4587
6581
  } else if (el.dataset.ohwEditable === "bg-image") {
4588
6582
  el.style.backgroundImage = `url('${val}')`;
6583
+ } else if (el.dataset.ohwEditable === "link") {
6584
+ applyLinkHref(el, val);
4589
6585
  } else {
4590
6586
  el.innerHTML = val;
4591
6587
  }
4592
6588
  });
6589
+ applyLinkByKey(key, val);
6590
+ }
6591
+ if (sectionsJson) {
6592
+ initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
4593
6593
  }
4594
6594
  postToParentRef.current({ type: "ow:hydrate-done" });
4595
6595
  };
4596
6596
  window.addEventListener("message", handleHydrate);
4597
6597
  const handleDeactivate = (e) => {
4598
6598
  if (e.data?.type !== "ow:deactivate") return;
6599
+ if (Date.now() < linkPopoverGraceUntilRef.current) return;
6600
+ if (linkPopoverOpenRef.current) {
6601
+ setLinkPopoverRef.current(null);
6602
+ return;
6603
+ }
4599
6604
  deactivateRef.current();
4600
6605
  };
4601
6606
  window.addEventListener("message", handleDeactivate);
4602
6607
  const handleKeyDown = (e) => {
6608
+ if (e.key === "Escape" && linkPopoverOpenRef.current) {
6609
+ setLinkPopoverRef.current(null);
6610
+ return;
6611
+ }
4603
6612
  if (e.key !== "Escape") return;
4604
6613
  const el = activeElRef.current;
4605
6614
  if (el && originalContentRef.current !== null) {
@@ -4634,7 +6643,63 @@ function OhhwellsBridge() {
4634
6643
  };
4635
6644
  const handleSave = (e) => {
4636
6645
  if (e.data?.type !== "ow:save") return;
4637
- postToParentRef.current({ type: "ow:save-result", nodes: collectEditableNodes() });
6646
+ const nodes = collectEditableNodes();
6647
+ const tracker = document.querySelector("[data-ohw-sections-tracker]");
6648
+ if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
6649
+ postToParentRef.current({ type: "ow:save-result", nodes });
6650
+ };
6651
+ const handleInsertSection = (e) => {
6652
+ if (e.data?.type !== "ow:insert-section") return;
6653
+ const { widgetType, insertAfter, insertBefore } = e.data;
6654
+ if (widgetType !== "scheduling") return;
6655
+ const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
6656
+ if (inserted) {
6657
+ const tracker = getSectionsTracker();
6658
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
6659
+ const h = document.documentElement.scrollHeight;
6660
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
6661
+ }
6662
+ };
6663
+ const handleSwitchSchedule = (e) => {
6664
+ if (e.data?.type !== "ow:switch-schedule") return;
6665
+ const scheduleId = e.data.scheduleId;
6666
+ const insertAfter = e.data.insertAfter;
6667
+ if (!scheduleId || !insertAfter) return;
6668
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
6669
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
6670
+ };
6671
+ const handleScheduleLinked = (e) => {
6672
+ if (e.data?.type !== "ow:schedule-linked") return;
6673
+ const scheduleId = e.data.scheduleId;
6674
+ const insertAfter = e.data.insertAfter;
6675
+ if (!scheduleId || !insertAfter) return;
6676
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
6677
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
6678
+ };
6679
+ const handleClearSchedulingWidget = (e) => {
6680
+ if (e.data?.type !== "ow:clear-scheduling-widget") return;
6681
+ const insertAfter = e.data.insertAfter;
6682
+ if (!insertAfter) return;
6683
+ const text = updateSectionScheduleId(insertAfter, null);
6684
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
6685
+ };
6686
+ const handleRemoveSchedulingSection = (e) => {
6687
+ if (e.data?.type !== "ow:remove-scheduling-section") return;
6688
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
6689
+ el.remove();
6690
+ });
6691
+ const tracker = getSectionsTracker();
6692
+ let sections = [];
6693
+ try {
6694
+ sections = JSON.parse(tracker.textContent || "[]");
6695
+ } catch {
6696
+ }
6697
+ const currentPath = window.location.pathname;
6698
+ const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
6699
+ tracker.textContent = JSON.stringify(updated);
6700
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
6701
+ const h = document.documentElement.scrollHeight;
6702
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
4638
6703
  };
4639
6704
  const handleSelectionChange = () => {
4640
6705
  if (!activeElRef.current) return;
@@ -4739,6 +6804,11 @@ function OhhwellsBridge() {
4739
6804
  deactivateRef.current();
4740
6805
  };
4741
6806
  window.addEventListener("message", handleSave);
6807
+ window.addEventListener("message", handleInsertSection);
6808
+ window.addEventListener("message", handleSwitchSchedule);
6809
+ window.addEventListener("message", handleScheduleLinked);
6810
+ window.addEventListener("message", handleClearSchedulingWidget);
6811
+ window.addEventListener("message", handleRemoveSchedulingSection);
4742
6812
  window.addEventListener("message", handleImageUrl);
4743
6813
  window.addEventListener("message", handleAnimLock);
4744
6814
  window.addEventListener("message", handleCanvasHeight);
@@ -4773,6 +6843,11 @@ function OhhwellsBridge() {
4773
6843
  document.removeEventListener("selectionchange", handleSelectionChange);
4774
6844
  window.removeEventListener("scroll", handleScroll, true);
4775
6845
  window.removeEventListener("message", handleSave);
6846
+ window.removeEventListener("message", handleInsertSection);
6847
+ window.removeEventListener("message", handleSwitchSchedule);
6848
+ window.removeEventListener("message", handleScheduleLinked);
6849
+ window.removeEventListener("message", handleClearSchedulingWidget);
6850
+ window.removeEventListener("message", handleRemoveSchedulingSection);
4776
6851
  window.removeEventListener("message", handleImageUrl);
4777
6852
  window.removeEventListener("message", handleAnimLock);
4778
6853
  window.removeEventListener("message", handleCanvasHeight);
@@ -4787,7 +6862,7 @@ function OhhwellsBridge() {
4787
6862
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
4788
6863
  };
4789
6864
  }, [isEditMode, refreshStateRules]);
4790
- (0, import_react.useEffect)(() => {
6865
+ (0, import_react5.useEffect)(() => {
4791
6866
  if (!isEditMode) return;
4792
6867
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
4793
6868
  el.removeAttribute("data-ohw-active-state");
@@ -4802,20 +6877,26 @@ function OhhwellsBridge() {
4802
6877
  const raf = requestAnimationFrame(() => refreshStateRules());
4803
6878
  const timer = setTimeout(() => {
4804
6879
  refreshStateRules();
4805
- postToParent({ type: "ow:ready", version: "1", nodes: collectEditableNodes() });
6880
+ const sections = collectSections();
6881
+ setSectionsByPath((prev) => {
6882
+ const next = { ...prev, [pathname]: sections };
6883
+ return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
6884
+ });
6885
+ postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
6886
+ postToParent({ type: "ow:request-site-pages" });
4806
6887
  }, 150);
4807
6888
  return () => {
4808
6889
  cancelAnimationFrame(raf);
4809
6890
  clearTimeout(timer);
4810
6891
  };
4811
6892
  }, [pathname, isEditMode, refreshStateRules, postToParent]);
4812
- const handleCommand = (0, import_react.useCallback)((cmd) => {
6893
+ const handleCommand = (0, import_react5.useCallback)((cmd) => {
4813
6894
  document.execCommand(cmd, false);
4814
6895
  activeElRef.current?.focus();
4815
6896
  if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
4816
6897
  refreshActiveCommandsRef.current();
4817
6898
  }, []);
4818
- const handleStateChange = (0, import_react.useCallback)((state) => {
6899
+ const handleStateChange = (0, import_react5.useCallback)((state) => {
4819
6900
  if (!activeStateElRef.current) return;
4820
6901
  const el = activeStateElRef.current;
4821
6902
  if (state === "Default") {
@@ -4828,13 +6909,48 @@ function OhhwellsBridge() {
4828
6909
  }
4829
6910
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
4830
6911
  }, [deactivate]);
4831
- return bridgeRoot ? (0, import_react_dom.createPortal)(
4832
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
4833
- toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
4834
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
4835
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FloatingToolbar, { rect: toolbarRect, parentScroll: parentScrollRef.current, elRef: toolbarElRef, onCommand: handleCommand, activeCommands })
6912
+ const closeLinkPopover = (0, import_react5.useCallback)(() => setLinkPopover(null), []);
6913
+ const openLinkPopoverForActive = (0, import_react5.useCallback)(() => {
6914
+ const hrefCtx = getHrefKeyFromElement(activeElRef.current);
6915
+ if (!hrefCtx) return;
6916
+ bumpLinkPopoverGrace();
6917
+ setLinkPopover({
6918
+ key: hrefCtx.key,
6919
+ target: getLinkHref(hrefCtx.anchor),
6920
+ rect: hrefCtx.anchor.getBoundingClientRect()
6921
+ });
6922
+ }, []);
6923
+ const handleLinkPopoverSubmit = (0, import_react5.useCallback)(
6924
+ (target) => {
6925
+ if (!linkPopover) return;
6926
+ const { key } = linkPopover;
6927
+ applyLinkByKey(key, target);
6928
+ postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
6929
+ setLinkPopover(null);
6930
+ },
6931
+ [linkPopover, postToParent]
6932
+ );
6933
+ const showEditLink = toolbarShowEditLink;
6934
+ const currentSections = sectionsByPath[pathname] ?? [];
6935
+ linkPopoverOpenRef.current = linkPopover !== null;
6936
+ return bridgeRoot ? (0, import_react_dom2.createPortal)(
6937
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
6938
+ toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
6939
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
6940
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6941
+ FloatingToolbar,
6942
+ {
6943
+ rect: toolbarRect,
6944
+ parentScroll: parentScrollRef.current,
6945
+ elRef: toolbarElRef,
6946
+ onCommand: handleCommand,
6947
+ activeCommands,
6948
+ showEditLink,
6949
+ onEditLink: openLinkPopoverForActive
6950
+ }
6951
+ )
4836
6952
  ] }),
4837
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
6953
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
4838
6954
  "div",
4839
6955
  {
4840
6956
  "data-ohw-max-badge": "",
@@ -4860,7 +6976,7 @@ function OhhwellsBridge() {
4860
6976
  ]
4861
6977
  }
4862
6978
  ),
4863
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
6979
+ toggleState && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4864
6980
  StateToggle,
4865
6981
  {
4866
6982
  rect: toggleState.rect,
@@ -4868,6 +6984,47 @@ function OhhwellsBridge() {
4868
6984
  states: toggleState.states,
4869
6985
  onStateChange: handleStateChange
4870
6986
  }
6987
+ ),
6988
+ linkPopover ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6989
+ LinkPopover,
6990
+ {
6991
+ rect: linkPopover.rect,
6992
+ parentScroll: parentScrollRef.current,
6993
+ panelRef: linkPopoverPanelRef,
6994
+ open: true,
6995
+ mode: "edit",
6996
+ pages: sitePages,
6997
+ sections: currentSections,
6998
+ sectionsByPath,
6999
+ initialTarget: linkPopover.target,
7000
+ onClose: closeLinkPopover,
7001
+ onSubmit: handleLinkPopoverSubmit
7002
+ }
7003
+ ) : null,
7004
+ sectionGap && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7005
+ "div",
7006
+ {
7007
+ "data-ohw-section-insert-line": "",
7008
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
7009
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
7010
+ children: [
7011
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
7012
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7013
+ Badge,
7014
+ {
7015
+ 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",
7016
+ onClick: () => {
7017
+ window.parent.postMessage(
7018
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
7019
+ "*"
7020
+ );
7021
+ },
7022
+ children: "Add Section"
7023
+ }
7024
+ ),
7025
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
7026
+ ]
7027
+ }
4871
7028
  )
4872
7029
  ] }),
4873
7030
  bridgeRoot
@@ -4875,10 +7032,19 @@ function OhhwellsBridge() {
4875
7032
  }
4876
7033
  // Annotate the CommonJS export names for ESM import in node:
4877
7034
  0 && (module.exports = {
7035
+ LinkEditorPanel,
7036
+ LinkPopover,
4878
7037
  OhhwellsBridge,
7038
+ SchedulingWidget,
4879
7039
  Toggle,
4880
7040
  ToggleGroup,
4881
7041
  ToggleGroupItem,
4882
- toggleVariants
7042
+ buildTarget,
7043
+ filterAvailablePages,
7044
+ getEditModeInitialState,
7045
+ isValidUrl,
7046
+ parseTarget,
7047
+ toggleVariants,
7048
+ validateUrlInput
4883
7049
  });
4884
7050
  //# sourceMappingURL=index.cjs.map