@ohhwells/bridge 0.1.29 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,762 @@
1
1
  "use client";
2
2
 
3
3
  // src/OhhwellsBridge.tsx
4
- import React4, { useCallback as useCallback2, useEffect as useEffect2, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
4
+ import React6, { useCallback as useCallback2, useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState5 } from "react";
5
+ import { createRoot } from "react-dom/client";
6
+ import { flushSync } from "react-dom";
7
+
8
+ // src/ui/SchedulingWidget.tsx
9
+ import { useEffect, useId, useMemo, useRef, useState as useState2 } from "react";
10
+
11
+ // src/ui/EmailCaptureModal.tsx
12
+ import { useState } from "react";
13
+ import { Dialog } from "radix-ui";
14
+ import { jsx, jsxs } from "react/jsx-runtime";
15
+ function Spinner() {
16
+ return /* @__PURE__ */ jsxs(
17
+ "svg",
18
+ {
19
+ width: "32",
20
+ height: "32",
21
+ viewBox: "0 0 32 32",
22
+ fill: "none",
23
+ style: { animation: "ohw-spin 0.75s linear infinite" },
24
+ children: [
25
+ /* @__PURE__ */ jsx("style", { children: `@keyframes ohw-spin { to { transform: rotate(360deg); } }` }),
26
+ /* @__PURE__ */ jsx("circle", { cx: "16", cy: "16", r: "12", stroke: "#e5e7eb", strokeWidth: "3" }),
27
+ /* @__PURE__ */ jsx(
28
+ "path",
29
+ {
30
+ d: "M16 4a12 12 0 0 1 12 12",
31
+ stroke: "#3b82f6",
32
+ strokeWidth: "3",
33
+ strokeLinecap: "round"
34
+ }
35
+ )
36
+ ]
37
+ }
38
+ );
39
+ }
40
+ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
41
+ const [email, setEmail] = useState("");
42
+ const [submittedEmail, setSubmittedEmail] = useState("");
43
+ const [loading, setLoading] = useState(false);
44
+ const [success, setSuccess] = useState(false);
45
+ const [error, setError] = useState(null);
46
+ const isBook = title === "Confirm your spot";
47
+ const handleSubmit = async () => {
48
+ if (!email.trim()) return;
49
+ setLoading(true);
50
+ setError(null);
51
+ try {
52
+ await onSubmit(email.trim());
53
+ setSubmittedEmail(email.trim());
54
+ setSuccess(true);
55
+ } catch (e) {
56
+ setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
57
+ } finally {
58
+ setLoading(false);
59
+ }
60
+ };
61
+ const handleKeyDown = (e) => {
62
+ if (e.key === "Enter" && !loading) handleSubmit();
63
+ };
64
+ return /* @__PURE__ */ jsx(Dialog.Root, { open: true, onOpenChange: (open) => {
65
+ if (!open) onClose();
66
+ }, children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [
67
+ /* @__PURE__ */ jsx(
68
+ Dialog.Overlay,
69
+ {
70
+ className: "fixed inset-0 z-50",
71
+ style: { background: "rgba(0,0,0,0.45)" }
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsxs(
75
+ Dialog.Content,
76
+ {
77
+ 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",
78
+ style: { maxWidth: 400 },
79
+ children: [
80
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 p-6", children: [
81
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
82
+ /* @__PURE__ */ jsx(
83
+ Dialog.Title,
84
+ {
85
+ className: "font-body text-xl font-bold m-0 leading-tight",
86
+ style: { color: "var(--color-dark,#200C02)" },
87
+ children: success ? isBook ? "Reservation confirmed!" : "You're on the waitlist!" : title
88
+ }
89
+ ),
90
+ /* @__PURE__ */ jsx(
91
+ Dialog.Description,
92
+ {
93
+ className: "font-body text-sm m-0",
94
+ style: { color: "#6B7280" },
95
+ children: success ? isBook ? `A confirmation email has been sent to ${submittedEmail}.` : "We'll let you know via email if someone cancels." : subtitle
96
+ }
97
+ )
98
+ ] }),
99
+ /* @__PURE__ */ jsx(
100
+ Dialog.Close,
101
+ {
102
+ 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",
103
+ style: { color: "#6B7280" },
104
+ "aria-label": "Close",
105
+ children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
106
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
107
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
108
+ ] })
109
+ }
110
+ )
111
+ ] }),
112
+ !success && (loading ? (
113
+ /* Loading state — full body replaced */
114
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-3 px-7 py-12", children: [
115
+ /* @__PURE__ */ jsx(Spinner, {}),
116
+ /* @__PURE__ */ jsx("p", { className: "font-body text-sm m-0", style: { color: "#6B7280" }, children: "Please wait" })
117
+ ] })
118
+ ) : (
119
+ /* Email form */
120
+ /* @__PURE__ */ jsxs("div", { className: "px-7 pt-2 pb-6", children: [
121
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 mb-8", children: [
122
+ /* @__PURE__ */ jsx("label", { className: "font-body text-sm font-medium", style: { color: "var(--color-dark,#200C02)" }, children: "Email" }),
123
+ /* @__PURE__ */ jsx(
124
+ "input",
125
+ {
126
+ type: "email",
127
+ value: email,
128
+ onChange: (e) => setEmail(e.target.value),
129
+ onKeyDown: handleKeyDown,
130
+ placeholder: "hello@gmail.com",
131
+ autoFocus: true,
132
+ className: "w-full h-10 px-3 rounded-lg border font-body text-sm outline-none box-border",
133
+ style: {
134
+ borderColor: "#E7E5E4",
135
+ background: "#fff",
136
+ color: "var(--color-dark,#200C02)"
137
+ }
138
+ }
139
+ ),
140
+ error && /* @__PURE__ */ jsx("p", { className: "font-body text-xs text-red-500 m-0", children: error })
141
+ ] }),
142
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
143
+ "button",
144
+ {
145
+ onClick: handleSubmit,
146
+ disabled: !email.trim(),
147
+ className: "h-9 px-5 rounded-lg font-body text-sm font-semibold cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed",
148
+ style: {
149
+ background: "var(--color-primary,#3D312B)",
150
+ color: "var(--color-light,#FEFFF0)"
151
+ },
152
+ children: "Send"
153
+ }
154
+ ) })
155
+ ] })
156
+ ))
157
+ ]
158
+ }
159
+ )
160
+ ] }) });
161
+ }
162
+
163
+ // src/ui/SchedulingWidget.tsx
164
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
165
+ var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
166
+ var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
167
+ function classRunsOnDate(cls, date, type) {
168
+ if (type === "FIXED") {
169
+ const d = String(date.getDate()).padStart(2, "0");
170
+ const m = String(date.getMonth() + 1).padStart(2, "0");
171
+ return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
172
+ }
173
+ return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
174
+ }
175
+ function formatClassTime(cls) {
176
+ let h = parseInt(cls.startTime.hour, 10);
177
+ const m = parseInt(cls.startTime.minutes, 10);
178
+ if (cls.startTime.time === "PM" && h !== 12) h += 12;
179
+ if (cls.startTime.time === "AM" && h === 12) h = 0;
180
+ const endTotal = h * 60 + m + cls.duration;
181
+ const eh = Math.floor(endTotal / 60) % 24;
182
+ const em = endTotal % 60;
183
+ return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
184
+ }
185
+ function getBookingsOnDate(cls, date) {
186
+ if (!cls.bookings?.length) return 0;
187
+ const ds = date.toISOString().split("T")[0];
188
+ return cls.bookings.filter((b) => {
189
+ try {
190
+ return new Date(b.classDate).toISOString().split("T")[0] === ds;
191
+ } catch {
192
+ return false;
193
+ }
194
+ }).length;
195
+ }
196
+ function buildTimezoneLabel(tz) {
197
+ try {
198
+ const now = /* @__PURE__ */ new Date();
199
+ const timeStr = new Intl.DateTimeFormat("en-US", {
200
+ hour: "2-digit",
201
+ minute: "2-digit",
202
+ hour12: false,
203
+ timeZone: tz
204
+ }).format(now);
205
+ const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
206
+ const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
207
+ return `${timeStr} (${offsetPart}) ${city} time`;
208
+ } catch {
209
+ return tz;
210
+ }
211
+ }
212
+ function EmptyState({ inEditor, insertAfter }) {
213
+ const handleAddSchedule = () => {
214
+ if (inEditor) {
215
+ window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
216
+ }
217
+ };
218
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center gap-6 p-12 bg-[#FAFAF9] border-2 border-dashed border-[#E7E5E4] rounded-[10px] h-[264px] w-full box-border", children: [
219
+ /* @__PURE__ */ jsx2("div", { className: "w-10 h-10 bg-[#F5F5F4] rounded-[10px] flex items-center justify-center shrink-0", children: /* @__PURE__ */ jsxs2(
220
+ "svg",
221
+ {
222
+ width: "20",
223
+ height: "20",
224
+ viewBox: "0 0 24 24",
225
+ fill: "none",
226
+ stroke: "var(--color-accent, #A89B83)",
227
+ strokeWidth: "1.5",
228
+ strokeLinecap: "round",
229
+ strokeLinejoin: "round",
230
+ children: [
231
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
232
+ /* @__PURE__ */ jsx2("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
233
+ /* @__PURE__ */ jsx2("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
234
+ /* @__PURE__ */ jsx2("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
235
+ /* @__PURE__ */ jsx2("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
236
+ /* @__PURE__ */ jsx2("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
237
+ ]
238
+ }
239
+ ) }),
240
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-2", children: [
241
+ /* @__PURE__ */ jsx2("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
242
+ /* @__PURE__ */ jsx2("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." })
243
+ ] }),
244
+ /* @__PURE__ */ jsx2(
245
+ "button",
246
+ {
247
+ onClick: handleAddSchedule,
248
+ 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",
249
+ children: "Add Schedule"
250
+ }
251
+ )
252
+ ] });
253
+ }
254
+ function LoadingSkeleton() {
255
+ const shimmer = {
256
+ background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
257
+ backgroundSize: "200% 100%",
258
+ animation: "ohw-shimmer 1.5s infinite"
259
+ };
260
+ return /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col gap-10", children: [
261
+ /* @__PURE__ */ jsx2("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
262
+ /* @__PURE__ */ jsx2("div", { className: "flex h-[70px] items-stretch", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col justify-between items-center px-1", children: [
263
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
264
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
265
+ ] }, i)) }),
266
+ [0, 1, 2].map((i) => /* @__PURE__ */ jsxs2("div", { children: [
267
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-4 py-4 sm:hidden", children: [
268
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 shrink-0", children: [
269
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "100px", height: "16px", borderRadius: "4px" } }),
270
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "52px", height: "20px", borderRadius: "999px" } }),
271
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "60px", height: "14px", borderRadius: "4px" } })
272
+ ] }),
273
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2", children: [
274
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "75%", height: "16px", borderRadius: "4px" } }),
275
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "50%", height: "14px", borderRadius: "4px" } }),
276
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "100%", height: "44px", borderRadius: "8px" } })
277
+ ] })
278
+ ] }),
279
+ /* @__PURE__ */ jsxs2("div", { className: "hidden sm:flex items-center gap-[60px] py-4", children: [
280
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
281
+ /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2", children: [
282
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
283
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
284
+ ] }),
285
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
286
+ /* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
287
+ ] }),
288
+ i < 2 && /* @__PURE__ */ jsx2("div", { className: "h-px bg-black/20" })
289
+ ] }, i))
290
+ ] });
291
+ }
292
+ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal }) {
293
+ const todayMs = useMemo(() => {
294
+ const d = /* @__PURE__ */ new Date();
295
+ d.setHours(0, 0, 0, 0);
296
+ return d.getTime();
297
+ }, []);
298
+ const scrollRef = useRef(null);
299
+ const [canScrollLeft, setCanScrollLeft] = useState2(false);
300
+ const [canScrollRight, setCanScrollRight] = useState2(false);
301
+ const updateScrollState = () => {
302
+ const el = scrollRef.current;
303
+ if (!el) return;
304
+ setCanScrollLeft(el.scrollLeft > 0);
305
+ setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
306
+ };
307
+ useEffect(() => {
308
+ const el = scrollRef.current;
309
+ if (!el) return;
310
+ updateScrollState();
311
+ el.addEventListener("scroll", updateScrollState);
312
+ const ro = new ResizeObserver(updateScrollState);
313
+ ro.observe(el);
314
+ return () => {
315
+ el.removeEventListener("scroll", updateScrollState);
316
+ ro.disconnect();
317
+ };
318
+ }, [dates]);
319
+ const scroll = (dir) => {
320
+ scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
321
+ };
322
+ const isDragging = useRef(false);
323
+ const dragStartX = useRef(0);
324
+ const dragStartScrollLeft = useRef(0);
325
+ const onDragStart = (e) => {
326
+ if (!scrollRef.current) return;
327
+ isDragging.current = true;
328
+ dragStartX.current = e.clientX;
329
+ dragStartScrollLeft.current = scrollRef.current.scrollLeft;
330
+ scrollRef.current.style.cursor = "grabbing";
331
+ scrollRef.current.style.userSelect = "none";
332
+ };
333
+ const onDragMove = (e) => {
334
+ if (!isDragging.current || !scrollRef.current) return;
335
+ const delta = e.clientX - dragStartX.current;
336
+ scrollRef.current.scrollLeft = dragStartScrollLeft.current - delta;
337
+ };
338
+ const onDragEnd = () => {
339
+ if (!scrollRef.current) return;
340
+ isDragging.current = false;
341
+ scrollRef.current.style.cursor = "grab";
342
+ scrollRef.current.style.userSelect = "";
343
+ };
344
+ const datesWithClasses = useMemo(
345
+ () => new Set(dates.map(
346
+ (d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
347
+ ).filter((i) => i >= 0)),
348
+ [dates, schedule]
349
+ );
350
+ const selectedDate = dates[selectedIdx];
351
+ const selectedClasses = useMemo(
352
+ () => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
353
+ [schedule, selectedDate]
354
+ );
355
+ return /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col gap-10", children: [
356
+ /* @__PURE__ */ jsxs2("div", { className: "relative w-full", children: [
357
+ canScrollLeft && /* @__PURE__ */ jsx2(
358
+ "button",
359
+ {
360
+ onClick: () => scroll("left"),
361
+ 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",
362
+ style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
363
+ "aria-label": "Scroll left",
364
+ children: /* @__PURE__ */ jsx2("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__ */ jsx2("polyline", { points: "15 18 9 12 15 6" }) })
365
+ }
366
+ ),
367
+ canScrollRight && /* @__PURE__ */ jsx2(
368
+ "button",
369
+ {
370
+ onClick: () => scroll("right"),
371
+ 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",
372
+ style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
373
+ "aria-label": "Scroll right",
374
+ children: /* @__PURE__ */ jsx2("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__ */ jsx2("polyline", { points: "9 18 15 12 9 6" }) })
375
+ }
376
+ ),
377
+ canScrollLeft && /* @__PURE__ */ jsx2(
378
+ "div",
379
+ {
380
+ className: "sm:hidden absolute left-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
381
+ style: { background: "linear-gradient(to right, var(--color-light,#FEFFF0), transparent)" }
382
+ }
383
+ ),
384
+ canScrollRight && /* @__PURE__ */ jsx2(
385
+ "div",
386
+ {
387
+ className: "sm:hidden absolute right-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
388
+ style: { background: "linear-gradient(to left, var(--color-light,#FEFFF0), transparent)" }
389
+ }
390
+ ),
391
+ /* @__PURE__ */ jsx2(
392
+ "div",
393
+ {
394
+ ref: scrollRef,
395
+ className: "overflow-x-auto w-full",
396
+ style: { scrollbarWidth: "none", msOverflowStyle: "none", touchAction: "pan-x", cursor: "grab" },
397
+ onMouseDown: onDragStart,
398
+ onMouseMove: onDragMove,
399
+ onMouseUp: onDragEnd,
400
+ onMouseLeave: onDragEnd,
401
+ children: /* @__PURE__ */ jsx2("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
402
+ const isToday = date.getTime() === todayMs;
403
+ const isSelected = i === selectedIdx;
404
+ const clickable = datesWithClasses.has(i);
405
+ const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
406
+ return /* @__PURE__ */ jsxs2(
407
+ "div",
408
+ {
409
+ onClick: () => clickable && onSelectDate(i),
410
+ className: `flex-1 h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
411
+ children: [
412
+ /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
413
+ /* @__PURE__ */ jsx2(
414
+ "div",
415
+ {
416
+ className: "w-10 h-10 rounded-full flex items-center justify-center",
417
+ style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
418
+ children: /* @__PURE__ */ jsx2(
419
+ "span",
420
+ {
421
+ className: "font-body text-xl font-bold text-center tracking-display-tight",
422
+ style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
423
+ children: date.getDate()
424
+ }
425
+ )
426
+ }
427
+ )
428
+ ]
429
+ },
430
+ i
431
+ );
432
+ }) })
433
+ }
434
+ )
435
+ ] }),
436
+ selectedClasses.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "font-body text-base text-(--color-accent,#A89B83) text-center py-8 m-0", children: "No classes scheduled for this day." }) : /* @__PURE__ */ jsx2("div", { className: "flex flex-col", children: selectedClasses.map((cls, i) => {
437
+ const booked = getBookingsOnDate(cls, selectedDate);
438
+ const available = cls.maxParticipants - booked;
439
+ const isFull = available <= 0;
440
+ const isPrivate = cls.maxParticipants === 1;
441
+ return /* @__PURE__ */ jsxs2("div", { children: [
442
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
443
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
444
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 sm:w-[120px] sm:shrink-0", children: [
445
+ /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }),
446
+ /* @__PURE__ */ jsx2(
447
+ "span",
448
+ {
449
+ className: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium w-fit",
450
+ style: isPrivate ? { background: "#DDFBE3", color: "#199130", border: "1px solid #199130", fontWeight: 500 } : { background: "#EEEDFF", color: "#5953FF", border: "1px solid #5953FF", fontWeight: 500 },
451
+ children: isPrivate ? "PRIVATE" : "GROUP"
452
+ }
453
+ )
454
+ ] }),
455
+ /* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
456
+ /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
457
+ available,
458
+ "/",
459
+ cls.maxParticipants
460
+ ] }),
461
+ /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
462
+ ] }) })
463
+ ] }),
464
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
465
+ /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
466
+ /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
467
+ cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
468
+ ] }),
469
+ /* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
470
+ /* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
471
+ available,
472
+ "/",
473
+ cls.maxParticipants
474
+ ] }),
475
+ /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
476
+ ] }) }),
477
+ /* @__PURE__ */ jsx2(
478
+ "button",
479
+ {
480
+ 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",
481
+ style: isFull ? {
482
+ background: "transparent",
483
+ border: "1px solid var(--color-dark, #200C02)",
484
+ color: "var(--color-dark, #200C02)"
485
+ } : {
486
+ background: "var(--color-primary, #3D312B)",
487
+ border: "none",
488
+ color: "var(--color-light, #FEFFF0)"
489
+ },
490
+ onClick: () => {
491
+ if (!cls.id) return;
492
+ onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
493
+ },
494
+ children: isFull ? "Join Waitlist" : "Book Now"
495
+ }
496
+ )
497
+ ] })
498
+ ] }),
499
+ i < selectedClasses.length - 1 && /* @__PURE__ */ jsx2("div", { className: "h-px bg-black/20" })
500
+ ] }, cls.id ?? i);
501
+ }) })
502
+ ] });
503
+ }
504
+ function CalendarFoldIcon() {
505
+ return /* @__PURE__ */ jsxs2(
506
+ "svg",
507
+ {
508
+ width: "16",
509
+ height: "16",
510
+ viewBox: "0 0 24 24",
511
+ fill: "none",
512
+ stroke: "currentColor",
513
+ strokeWidth: "2",
514
+ strokeLinecap: "round",
515
+ strokeLinejoin: "round",
516
+ style: { flexShrink: 0 },
517
+ children: [
518
+ /* @__PURE__ */ jsx2("path", { d: "M8 2v4" }),
519
+ /* @__PURE__ */ jsx2("path", { d: "M16 2v4" }),
520
+ /* @__PURE__ */ jsx2("path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }),
521
+ /* @__PURE__ */ jsx2("path", { d: "M3 10h18" }),
522
+ /* @__PURE__ */ jsx2("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
523
+ ]
524
+ }
525
+ );
526
+ }
527
+ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter: insertAfterProp }) {
528
+ const autoId = useId();
529
+ const insertAfter = insertAfterProp ?? autoId;
530
+ const [schedule, setSchedule] = useState2(null);
531
+ const [loading, setLoading] = useState2(true);
532
+ const [inEditor, setInEditor] = useState2(false);
533
+ const [isHovered, setIsHovered] = useState2(false);
534
+ const [modalState, setModalState] = useState2(null);
535
+ const switchScheduleIdRef = useRef(null);
536
+ const dates = useMemo(() => {
537
+ const today = /* @__PURE__ */ new Date();
538
+ today.setHours(0, 0, 0, 0);
539
+ return Array.from({ length: 14 }, (_, i) => {
540
+ const d = new Date(today);
541
+ d.setDate(today.getDate() + i);
542
+ return d;
543
+ });
544
+ }, []);
545
+ const [selectedIdx, setSelectedIdx] = useState2(0);
546
+ useEffect(() => {
547
+ if (!schedule?.classes) return;
548
+ for (let i = 0; i < dates.length; i++) {
549
+ if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
550
+ setSelectedIdx(i);
551
+ return;
552
+ }
553
+ }
554
+ }, [schedule, dates]);
555
+ useEffect(() => {
556
+ if (typeof window === "undefined") return;
557
+ const isInEditor = window.self !== window.top;
558
+ setInEditor(isInEditor);
559
+ function loadWithId(effectiveId) {
560
+ if (isInEditor) {
561
+ const startEmpty = effectiveId === null;
562
+ if (startEmpty) setLoading(false);
563
+ let initialized = startEmpty;
564
+ const timer = !startEmpty ? setTimeout(() => {
565
+ if (!initialized) {
566
+ initialized = true;
567
+ setLoading(false);
568
+ }
569
+ }, 5e3) : null;
570
+ const handler = (e) => {
571
+ if (e.data?.type === "ow:clear-scheduling-widget") {
572
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
573
+ setSchedule(null);
574
+ setLoading(false);
575
+ return;
576
+ }
577
+ if (e.data?.type === "ow:switch-schedule") {
578
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
579
+ switchScheduleIdRef.current = e.data.scheduleId ?? null;
580
+ initialized = false;
581
+ setLoading(true);
582
+ window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
583
+ return;
584
+ }
585
+ if (e.data?.type !== "ow:user-schedules-response") return;
586
+ if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
587
+ if (initialized && switchScheduleIdRef.current === null) return;
588
+ initialized = true;
589
+ if (timer) clearTimeout(timer);
590
+ const schedules = e.data.schedules ?? [];
591
+ const isSwitching = switchScheduleIdRef.current !== null;
592
+ const target = isSwitching ? schedules.find((s) => s.id === switchScheduleIdRef.current) ?? schedules[0] ?? null : effectiveId ? schedules.find((s) => s.id === effectiveId) ?? schedules[0] ?? null : schedules[0] ?? null;
593
+ switchScheduleIdRef.current = null;
594
+ setSchedule(target);
595
+ setLoading(false);
596
+ if (notifyOnConnect && target && !isSwitching) {
597
+ window.parent.postMessage({
598
+ type: "ow:schedule-connected",
599
+ schedule: { id: target.id, name: target.name },
600
+ insertAfter
601
+ }, "*");
602
+ window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
603
+ }
604
+ };
605
+ window.addEventListener("message", handler);
606
+ if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
607
+ return () => {
608
+ window.removeEventListener("message", handler);
609
+ if (timer) clearTimeout(timer);
610
+ };
611
+ }
612
+ if (!effectiveId) {
613
+ setLoading(false);
614
+ return;
615
+ }
616
+ ;
617
+ (async () => {
618
+ try {
619
+ const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
620
+ const data = await res.json();
621
+ setSchedule(data?.id ? data : null);
622
+ } catch {
623
+ }
624
+ setLoading(false);
625
+ })();
626
+ }
627
+ if (initialScheduleId === void 0) {
628
+ let done = false;
629
+ let innerCleanup;
630
+ const timer = setTimeout(() => {
631
+ if (!done) {
632
+ done = true;
633
+ setLoading(false);
634
+ }
635
+ }, 2e3);
636
+ const configHandler = (e) => {
637
+ if (e.data?.type !== "ow:schedule-config" || e.data.insertAfter !== insertAfter || done) return;
638
+ done = true;
639
+ clearTimeout(timer);
640
+ window.removeEventListener("message", configHandler);
641
+ innerCleanup = loadWithId(e.data.scheduleId ?? null);
642
+ };
643
+ window.addEventListener("message", configHandler);
644
+ window.postMessage({ type: "ow:request-schedule-config", insertAfter }, "*");
645
+ return () => {
646
+ window.removeEventListener("message", configHandler);
647
+ clearTimeout(timer);
648
+ innerCleanup?.();
649
+ };
650
+ }
651
+ return loadWithId(initialScheduleId) ?? void 0;
652
+ }, []);
653
+ const timezoneLabel = schedule?.timezone ? (() => {
654
+ try {
655
+ return buildTimezoneLabel(schedule.timezone);
656
+ } catch {
657
+ return void 0;
658
+ }
659
+ })() : void 0;
660
+ const handleModalSubmit = async (email) => {
661
+ if (!modalState) return;
662
+ const { mode, classId, classDate } = modalState;
663
+ const endpoint = mode === "book" ? `${API_URL}/api/schedule/class/${classId}/booking` : `${API_URL}/api/schedule/class/${classId}/waitlist`;
664
+ const res = await fetch(endpoint, {
665
+ method: "POST",
666
+ headers: { "Content-Type": "application/json" },
667
+ body: JSON.stringify({ classDate: classDate.toISOString(), email })
668
+ });
669
+ if (!res.ok) {
670
+ const data = await res.json().catch(() => ({}));
671
+ throw new Error(data?.message ?? "Something went wrong. Please try again.");
672
+ }
673
+ };
674
+ const handleReplaceSchedule = () => {
675
+ setIsHovered(false);
676
+ if (schedule) {
677
+ window.parent.postMessage({
678
+ type: "ow:schedule-connected",
679
+ schedule: { id: schedule.id, name: schedule.name },
680
+ insertAfter
681
+ }, "*");
682
+ } else {
683
+ window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
684
+ }
685
+ };
686
+ if (!inEditor && !loading && !schedule) return null;
687
+ const sectionId = `scheduling-${insertAfter}`;
688
+ return /* @__PURE__ */ jsxs2(
689
+ "section",
690
+ {
691
+ "data-ohw-section": sectionId,
692
+ "data-ohw-scheduling-anchor": insertAfter,
693
+ className: "w-full box-border py-10 px-5 sm:py-20 sm:px-16 font-body bg-(--color-light,#FEFFF0) relative",
694
+ onMouseEnter: () => inEditor && setIsHovered(true),
695
+ onMouseLeave: () => setIsHovered(false),
696
+ children: [
697
+ inEditor && isHovered && !!schedule && /* @__PURE__ */ jsxs2(
698
+ "div",
699
+ {
700
+ className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
701
+ onClick: handleReplaceSchedule,
702
+ children: [
703
+ /* @__PURE__ */ jsx2("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
704
+ /* @__PURE__ */ jsx2("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
705
+ /* @__PURE__ */ jsx2("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs2(
706
+ "div",
707
+ {
708
+ className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
709
+ style: {
710
+ fontSize: 14,
711
+ lineHeight: "24px",
712
+ fontFamily: "'Figtree', system-ui, sans-serif",
713
+ fontWeight: 500,
714
+ color: "#0C0A09"
715
+ },
716
+ children: [
717
+ /* @__PURE__ */ jsx2(CalendarFoldIcon, {}),
718
+ "Replace schedule"
719
+ ]
720
+ }
721
+ ) })
722
+ ]
723
+ }
724
+ ),
725
+ /* @__PURE__ */ jsxs2("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
726
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-4", children: [
727
+ /* @__PURE__ */ jsx2("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" }),
728
+ schedule?.description && /* @__PURE__ */ jsx2("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
729
+ ] }),
730
+ /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col items-end gap-3", children: [
731
+ timezoneLabel && /* @__PURE__ */ jsx2("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
732
+ /* @__PURE__ */ jsx2("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ jsx2(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ jsx2(EmptyState, { inEditor, insertAfter }) : /* @__PURE__ */ jsx2(
733
+ ScheduleView,
734
+ {
735
+ schedule,
736
+ dates,
737
+ selectedIdx,
738
+ onSelectDate: setSelectedIdx,
739
+ onOpenModal: inEditor ? void 0 : (mode, classId, classDate) => setModalState({ mode, classId, classDate })
740
+ }
741
+ ) })
742
+ ] })
743
+ ] }),
744
+ modalState && /* @__PURE__ */ jsx2(
745
+ EmailCaptureModal,
746
+ {
747
+ title: modalState.mode === "book" ? "Confirm your spot" : "Leave your email",
748
+ subtitle: modalState.mode === "book" ? "We'll send your booking confirmation here." : "We'll let you know when spots open up!",
749
+ onSubmit: handleModalSubmit,
750
+ onClose: () => setModalState(null)
751
+ }
752
+ )
753
+ ]
754
+ }
755
+ );
756
+ }
5
757
 
6
758
  // src/ui/toggle-group.tsx
7
- import * as React from "react";
759
+ import * as React2 from "react";
8
760
 
9
761
  // node_modules/clsx/dist/clsx.mjs
10
762
  function r(e) {
@@ -3324,7 +4076,7 @@ var cva = (base, config) => (props) => {
3324
4076
 
3325
4077
  // src/ui/toggle.tsx
3326
4078
  import { Toggle as TogglePrimitive } from "radix-ui";
3327
- import { jsx } from "react/jsx-runtime";
4079
+ import { jsx as jsx3 } from "react/jsx-runtime";
3328
4080
  var toggleVariants = cva(
3329
4081
  "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",
3330
4082
  {
@@ -3351,7 +4103,7 @@ function Toggle({
3351
4103
  size,
3352
4104
  ...props
3353
4105
  }) {
3354
- return /* @__PURE__ */ jsx(
4106
+ return /* @__PURE__ */ jsx3(
3355
4107
  TogglePrimitive.Root,
3356
4108
  {
3357
4109
  "data-slot": "toggle",
@@ -3362,8 +4114,8 @@ function Toggle({
3362
4114
  }
3363
4115
 
3364
4116
  // src/ui/toggle-group.tsx
3365
- import { jsx as jsx2 } from "react/jsx-runtime";
3366
- var ToggleGroupContext = React.createContext({
4117
+ import { jsx as jsx4 } from "react/jsx-runtime";
4118
+ var ToggleGroupContext = React2.createContext({
3367
4119
  value: "",
3368
4120
  onValueChange: () => {
3369
4121
  }
@@ -3375,13 +4127,13 @@ function ToggleGroup({
3375
4127
  children,
3376
4128
  ...props
3377
4129
  }) {
3378
- return /* @__PURE__ */ jsx2(
4130
+ return /* @__PURE__ */ jsx4(
3379
4131
  "div",
3380
4132
  {
3381
4133
  role: "group",
3382
4134
  className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
3383
4135
  ...props,
3384
- children: /* @__PURE__ */ jsx2(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
4136
+ children: /* @__PURE__ */ jsx4(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
3385
4137
  }
3386
4138
  );
3387
4139
  }
@@ -3391,8 +4143,8 @@ function ToggleGroupItem({
3391
4143
  children,
3392
4144
  ...props
3393
4145
  }) {
3394
- const { value: groupValue, onValueChange } = React.useContext(ToggleGroupContext);
3395
- return /* @__PURE__ */ jsx2(
4146
+ const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
4147
+ return /* @__PURE__ */ jsx4(
3396
4148
  Toggle,
3397
4149
  {
3398
4150
  pressed: groupValue === value,
@@ -3689,26 +4441,135 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
3689
4441
  tick();
3690
4442
  }
3691
4443
 
3692
- // src/ui/link-modal/calcPopoverPos.ts
3693
- function calcPopoverPos(rect, parentScroll, approxW = 483, approxH = 320) {
3694
- const GAP = 8;
3695
- const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
3696
- const visibleTop = rect.top - canvasTopInIframe;
3697
- let top = rect.top - GAP;
3698
- let transform = "translateX(-50%) translateY(-100%)";
3699
- if (visibleTop < approxH + GAP) {
3700
- top = rect.bottom + GAP;
3701
- transform = "translateX(-50%)";
3702
- }
3703
- const rawLeft = rect.left + rect.width / 2;
3704
- const left = Math.max(GAP + approxW / 2, Math.min(rawLeft, window.innerWidth - GAP - approxW / 2));
3705
- return { top, left, transform };
4444
+ // src/ui/dialog.tsx
4445
+ import * as React3 from "react";
4446
+ import { Dialog as DialogPrimitive } from "radix-ui";
4447
+
4448
+ // src/ui/icons.tsx
4449
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
4450
+ function IconX({ className, ...props }) {
4451
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4452
+ /* @__PURE__ */ jsx5("path", { d: "M18 6 6 18" }),
4453
+ /* @__PURE__ */ jsx5("path", { d: "m6 6 12 12" })
4454
+ ] });
4455
+ }
4456
+ function IconChevronDown({ className, ...props }) {
4457
+ return /* @__PURE__ */ jsx5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ jsx5("path", { d: "m6 9 6 6 6-6" }) });
4458
+ }
4459
+ function IconFile({ className, ...props }) {
4460
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4461
+ /* @__PURE__ */ jsx5("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
4462
+ /* @__PURE__ */ jsx5("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
4463
+ ] });
4464
+ }
4465
+ function IconInfo({ className, ...props }) {
4466
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4467
+ /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "10" }),
4468
+ /* @__PURE__ */ jsx5("path", { d: "M12 16v-4" }),
4469
+ /* @__PURE__ */ jsx5("path", { d: "M12 8h.01" })
4470
+ ] });
4471
+ }
4472
+ function IconArrowRight({ className, ...props }) {
4473
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4474
+ /* @__PURE__ */ jsx5("path", { d: "M5 12h14" }),
4475
+ /* @__PURE__ */ jsx5("path", { d: "m12 5 7 7-7 7" })
4476
+ ] });
4477
+ }
4478
+ function IconSection({ className, ...props }) {
4479
+ return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4480
+ /* @__PURE__ */ jsx5("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
4481
+ /* @__PURE__ */ jsx5("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
4482
+ /* @__PURE__ */ jsx5("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
4483
+ ] });
3706
4484
  }
3707
4485
 
4486
+ // src/ui/dialog.tsx
4487
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
4488
+ function Dialog2({ ...props }) {
4489
+ return /* @__PURE__ */ jsx6(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
4490
+ }
4491
+ function DialogPortal({ ...props }) {
4492
+ return /* @__PURE__ */ jsx6(DialogPrimitive.Portal, { ...props });
4493
+ }
4494
+ function DialogOverlay({
4495
+ className,
4496
+ ...props
4497
+ }) {
4498
+ return /* @__PURE__ */ jsx6(
4499
+ DialogPrimitive.Overlay,
4500
+ {
4501
+ "data-slot": "dialog-overlay",
4502
+ "data-ohw-link-modal-root": "",
4503
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
4504
+ ...props
4505
+ }
4506
+ );
4507
+ }
4508
+ var DialogContent = React3.forwardRef(({ className, children, showCloseButton = true, container, ...props }, ref) => {
4509
+ const positionMode = container ? "absolute" : "fixed";
4510
+ return /* @__PURE__ */ jsxs4(DialogPortal, { container: container ?? void 0, children: [
4511
+ /* @__PURE__ */ jsx6(DialogOverlay, { className: cn(positionMode, "inset-0") }),
4512
+ /* @__PURE__ */ jsxs4(
4513
+ DialogPrimitive.Content,
4514
+ {
4515
+ ref,
4516
+ "data-slot": "dialog-content",
4517
+ className: cn(
4518
+ positionMode,
4519
+ "left-1/2 top-1/2 z-[2147483647] flex w-full max-w-[483px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden",
4520
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
4521
+ container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
4522
+ className
4523
+ ),
4524
+ onMouseDown: (e) => e.stopPropagation(),
4525
+ ...props,
4526
+ children: [
4527
+ children,
4528
+ showCloseButton ? /* @__PURE__ */ jsx6(
4529
+ DialogPrimitive.Close,
4530
+ {
4531
+ type: "button",
4532
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4533
+ "aria-label": "Close",
4534
+ children: /* @__PURE__ */ jsx6(IconX, { "aria-hidden": true })
4535
+ }
4536
+ ) : null
4537
+ ]
4538
+ }
4539
+ )
4540
+ ] });
4541
+ });
4542
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
4543
+ function DialogHeader({ className, ...props }) {
4544
+ return /* @__PURE__ */ jsx6("div", { className: cn("flex flex-col gap-1.5", className), ...props });
4545
+ }
4546
+ function DialogFooter({ className, ...props }) {
4547
+ return /* @__PURE__ */ jsx6("div", { className: cn("flex items-center justify-end gap-2", className), ...props });
4548
+ }
4549
+ var DialogTitle = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx6(
4550
+ DialogPrimitive.Title,
4551
+ {
4552
+ ref,
4553
+ className: cn("text-lg font-semibold leading-none tracking-tight text-card-foreground", className),
4554
+ ...props
4555
+ }
4556
+ ));
4557
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
4558
+ var DialogDescription = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx6(
4559
+ DialogPrimitive.Description,
4560
+ {
4561
+ ref,
4562
+ className: cn("text-sm text-muted-foreground", className),
4563
+ ...props
4564
+ }
4565
+ ));
4566
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
4567
+ var DialogClose = DialogPrimitive.Close;
4568
+
3708
4569
  // src/ui/button.tsx
3709
- import * as React2 from "react";
4570
+ import * as React4 from "react";
3710
4571
  import { Slot } from "radix-ui";
3711
- import { jsx as jsx3 } from "react/jsx-runtime";
4572
+ import { jsx as jsx7 } from "react/jsx-runtime";
3712
4573
  var buttonVariants = cva(
3713
4574
  "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",
3714
4575
  {
@@ -3729,10 +4590,10 @@ var buttonVariants = cva(
3729
4590
  }
3730
4591
  }
3731
4592
  );
3732
- var Button = React2.forwardRef(
4593
+ var Button = React4.forwardRef(
3733
4594
  ({ className, variant, size, asChild = false, ...props }, ref) => {
3734
4595
  const Comp = asChild ? Slot.Root : "button";
3735
- return /* @__PURE__ */ jsx3(
4596
+ return /* @__PURE__ */ jsx7(
3736
4597
  Comp,
3737
4598
  {
3738
4599
  ref,
@@ -3745,76 +4606,38 @@ var Button = React2.forwardRef(
3745
4606
  );
3746
4607
  Button.displayName = "Button";
3747
4608
 
3748
- // src/ui/icons.tsx
3749
- import { jsx as jsx4, jsxs } from "react/jsx-runtime";
3750
- function IconX({ className, ...props }) {
3751
- return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3752
- /* @__PURE__ */ jsx4("path", { d: "M18 6 6 18" }),
3753
- /* @__PURE__ */ jsx4("path", { d: "m6 6 12 12" })
3754
- ] });
3755
- }
3756
- function IconChevronDown({ className, ...props }) {
3757
- return /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ jsx4("path", { d: "m6 9 6 6 6-6" }) });
3758
- }
3759
- function IconFile({ className, ...props }) {
3760
- return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3761
- /* @__PURE__ */ jsx4("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
3762
- /* @__PURE__ */ jsx4("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
3763
- ] });
3764
- }
3765
- function IconInfo({ className, ...props }) {
3766
- return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3767
- /* @__PURE__ */ jsx4("circle", { cx: "12", cy: "12", r: "10" }),
3768
- /* @__PURE__ */ jsx4("path", { d: "M12 16v-4" }),
3769
- /* @__PURE__ */ jsx4("path", { d: "M12 8h.01" })
3770
- ] });
3771
- }
3772
- function IconArrowRight({ className, ...props }) {
3773
- return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3774
- /* @__PURE__ */ jsx4("path", { d: "M5 12h14" }),
3775
- /* @__PURE__ */ jsx4("path", { d: "m12 5 7 7-7 7" })
3776
- ] });
3777
- }
3778
- function IconSection({ className, ...props }) {
3779
- return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3780
- /* @__PURE__ */ jsx4("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
3781
- /* @__PURE__ */ jsx4("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
3782
- /* @__PURE__ */ jsx4("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
3783
- ] });
3784
- }
3785
-
3786
4609
  // src/ui/link-modal/DestinationBreadcrumb.tsx
3787
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
4610
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
3788
4611
  function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
3789
- return /* @__PURE__ */ jsxs2("div", { className: "flex w-full flex-col gap-2", children: [
3790
- /* @__PURE__ */ jsx5("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
3791
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3", children: [
3792
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
3793
- /* @__PURE__ */ jsx5(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
3794
- /* @__PURE__ */ jsx5("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
4612
+ return /* @__PURE__ */ jsxs5("div", { className: "flex w-full flex-col gap-2", children: [
4613
+ /* @__PURE__ */ jsx8("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
4614
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3", children: [
4615
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
4616
+ /* @__PURE__ */ jsx8(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4617
+ /* @__PURE__ */ jsx8("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
3795
4618
  ] }),
3796
- /* @__PURE__ */ jsx5(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
3797
- /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
3798
- /* @__PURE__ */ jsx5(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
3799
- /* @__PURE__ */ jsx5("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
4619
+ /* @__PURE__ */ jsx8(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
4620
+ /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
4621
+ /* @__PURE__ */ jsx8(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4622
+ /* @__PURE__ */ jsx8("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
3800
4623
  ] })
3801
4624
  ] })
3802
4625
  ] });
3803
4626
  }
3804
4627
 
3805
4628
  // src/ui/link-modal/SectionTreeItem.tsx
3806
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
4629
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
3807
4630
  function SectionTreeItem({ section, onSelect, selected }) {
3808
4631
  const interactive = Boolean(onSelect);
3809
- return /* @__PURE__ */ jsxs3("div", { className: "flex h-9 w-full items-end pl-3", children: [
3810
- /* @__PURE__ */ jsx6(
4632
+ return /* @__PURE__ */ jsxs6("div", { className: "flex h-9 w-full items-end pl-3", children: [
4633
+ /* @__PURE__ */ jsx9(
3811
4634
  "div",
3812
4635
  {
3813
4636
  className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
3814
4637
  "aria-hidden": true
3815
4638
  }
3816
4639
  ),
3817
- /* @__PURE__ */ jsxs3(
4640
+ /* @__PURE__ */ jsxs6(
3818
4641
  "div",
3819
4642
  {
3820
4643
  role: interactive ? "button" : void 0,
@@ -3832,8 +4655,8 @@ function SectionTreeItem({ section, onSelect, selected }) {
3832
4655
  interactive && selected && "border-primary"
3833
4656
  ),
3834
4657
  children: [
3835
- /* @__PURE__ */ jsx6(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
3836
- /* @__PURE__ */ jsx6("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
4658
+ /* @__PURE__ */ jsx9(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4659
+ /* @__PURE__ */ jsx9("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
3837
4660
  ]
3838
4661
  }
3839
4662
  )
@@ -3841,23 +4664,23 @@ function SectionTreeItem({ section, onSelect, selected }) {
3841
4664
  }
3842
4665
  function SectionPickerList({ sections, onSelect }) {
3843
4666
  if (sections.length === 0) {
3844
- return /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
4667
+ return /* @__PURE__ */ jsx9("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
3845
4668
  }
3846
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1", children: [
3847
- /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
3848
- sections.map((section) => /* @__PURE__ */ jsx6(SectionTreeItem, { section, onSelect }, section.id))
4669
+ return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1", children: [
4670
+ /* @__PURE__ */ jsx9("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
4671
+ sections.map((section) => /* @__PURE__ */ jsx9(SectionTreeItem, { section, onSelect }, section.id))
3849
4672
  ] });
3850
4673
  }
3851
4674
 
3852
4675
  // src/ui/link-modal/UrlOrPageInput.tsx
3853
- import { useId, useRef, useState } from "react";
4676
+ import { useId as useId2, useRef as useRef2, useState as useState3 } from "react";
3854
4677
 
3855
4678
  // src/ui/input.tsx
3856
- import * as React3 from "react";
3857
- import { jsx as jsx7 } from "react/jsx-runtime";
3858
- var Input = React3.forwardRef(
4679
+ import * as React5 from "react";
4680
+ import { jsx as jsx10 } from "react/jsx-runtime";
4681
+ var Input = React5.forwardRef(
3859
4682
  ({ className, type, ...props }, ref) => {
3860
- return /* @__PURE__ */ jsx7(
4683
+ return /* @__PURE__ */ jsx10(
3861
4684
  "input",
3862
4685
  {
3863
4686
  type,
@@ -3876,9 +4699,9 @@ Input.displayName = "Input";
3876
4699
 
3877
4700
  // src/ui/label.tsx
3878
4701
  import { Label as LabelPrimitive } from "radix-ui";
3879
- import { jsx as jsx8 } from "react/jsx-runtime";
4702
+ import { jsx as jsx11 } from "react/jsx-runtime";
3880
4703
  function Label({ className, ...props }) {
3881
- return /* @__PURE__ */ jsx8(
4704
+ return /* @__PURE__ */ jsx11(
3882
4705
  LabelPrimitive.Root,
3883
4706
  {
3884
4707
  "data-slot": "label",
@@ -3888,40 +4711,10 @@ function Label({ className, ...props }) {
3888
4711
  );
3889
4712
  }
3890
4713
 
3891
- // src/ui/popover.tsx
3892
- import { Popover as PopoverPrimitive } from "radix-ui";
3893
- import { jsx as jsx9 } from "react/jsx-runtime";
3894
- function Popover({ ...props }) {
3895
- return /* @__PURE__ */ jsx9(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
3896
- }
3897
- function PopoverTrigger({ ...props }) {
3898
- return /* @__PURE__ */ jsx9(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
3899
- }
3900
- function PopoverContent({
3901
- className,
3902
- align = "start",
3903
- sideOffset = 6,
3904
- ...props
3905
- }) {
3906
- return /* @__PURE__ */ jsx9(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx9(
3907
- PopoverPrimitive.Content,
3908
- {
3909
- "data-slot": "popover-content",
3910
- align,
3911
- sideOffset,
3912
- className: cn(
3913
- "z-[2147483647] w-[var(--radix-popover-trigger-width)] overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg outline-none",
3914
- className
3915
- ),
3916
- ...props
3917
- }
3918
- ) });
3919
- }
3920
-
3921
4714
  // src/ui/link-modal/UrlOrPageInput.tsx
3922
- import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
4715
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
3923
4716
  function FieldChevron({ onClick }) {
3924
- return /* @__PURE__ */ jsx10(
4717
+ return /* @__PURE__ */ jsx12(
3925
4718
  "button",
3926
4719
  {
3927
4720
  type: "button",
@@ -3929,7 +4722,7 @@ function FieldChevron({ onClick }) {
3929
4722
  onClick,
3930
4723
  "aria-label": "Open page list",
3931
4724
  tabIndex: -1,
3932
- children: /* @__PURE__ */ jsx10(IconChevronDown, {})
4725
+ children: /* @__PURE__ */ jsx12(IconChevronDown, {})
3933
4726
  }
3934
4727
  );
3935
4728
  }
@@ -3945,9 +4738,9 @@ function UrlOrPageInput({
3945
4738
  readOnly = false,
3946
4739
  urlError
3947
4740
  }) {
3948
- const inputId = useId();
3949
- const inputRef = useRef(null);
3950
- const [isFocused, setIsFocused] = useState(false);
4741
+ const inputId = useId2();
4742
+ const inputRef = useRef2(null);
4743
+ const [isFocused, setIsFocused] = useState3(false);
3951
4744
  const openDropdown = () => {
3952
4745
  if (readOnly || filteredPages.length === 0) return;
3953
4746
  onDropdownOpenChange(true);
@@ -3968,12 +4761,12 @@ function UrlOrPageInput({
3968
4761
  "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]",
3969
4762
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
3970
4763
  );
3971
- return /* @__PURE__ */ jsxs4("div", { className: "flex w-full flex-col gap-2 p-0", children: [
3972
- /* @__PURE__ */ jsx10(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
3973
- /* @__PURE__ */ jsxs4(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
3974
- /* @__PURE__ */ jsx10(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs4("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
3975
- selectedPage ? /* @__PURE__ */ jsx10("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx10(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
3976
- readOnly ? /* @__PURE__ */ jsx10("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx10(
4764
+ return /* @__PURE__ */ jsxs7("div", { className: "flex w-full flex-col gap-2 p-0", children: [
4765
+ /* @__PURE__ */ jsx12(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
4766
+ /* @__PURE__ */ jsxs7("div", { className: "relative w-full", children: [
4767
+ /* @__PURE__ */ jsxs7("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
4768
+ selectedPage ? /* @__PURE__ */ jsx12("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx12(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
4769
+ readOnly ? /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx12(
3977
4770
  Input,
3978
4771
  {
3979
4772
  ref: inputRef,
@@ -3992,7 +4785,7 @@ function UrlOrPageInput({
3992
4785
  )
3993
4786
  }
3994
4787
  ),
3995
- selectedPage && !readOnly ? /* @__PURE__ */ jsx10(
4788
+ selectedPage && !readOnly ? /* @__PURE__ */ jsx12(
3996
4789
  "button",
3997
4790
  {
3998
4791
  type: "button",
@@ -4000,31 +4793,39 @@ function UrlOrPageInput({
4000
4793
  onMouseDown: clearSelection,
4001
4794
  "aria-label": "Clear selected page",
4002
4795
  tabIndex: -1,
4003
- children: /* @__PURE__ */ jsx10(IconX, { "aria-hidden": true })
4796
+ children: /* @__PURE__ */ jsx12(IconX, { "aria-hidden": true })
4004
4797
  }
4005
4798
  ) : null,
4006
- !readOnly ? /* @__PURE__ */ jsx10(FieldChevron, { onClick: toggleDropdown }) : null
4007
- ] }) }),
4008
- filteredPages.length > 0 && /* @__PURE__ */ jsx10(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ jsxs4(
4009
- "button",
4799
+ !readOnly ? /* @__PURE__ */ jsx12(FieldChevron, { onClick: toggleDropdown }) : null
4800
+ ] }),
4801
+ dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ jsx12(
4802
+ "div",
4010
4803
  {
4011
- type: "button",
4012
- 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",
4013
- onClick: () => onPageSelect(page),
4014
- children: [
4015
- /* @__PURE__ */ jsx10(IconFile, { className: "shrink-0", "aria-hidden": true }),
4016
- /* @__PURE__ */ jsx10("span", { className: "truncate", children: page.title })
4017
- ]
4018
- },
4019
- page.path
4020
- )) })
4804
+ "data-ohw-link-page-dropdown": "",
4805
+ className: "absolute left-0 right-0 h-20 top-[calc(100%+4px)] z-10 max-h-48 overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg",
4806
+ onMouseDown: (e) => e.preventDefault(),
4807
+ children: filteredPages.map((page) => /* @__PURE__ */ jsxs7(
4808
+ "button",
4809
+ {
4810
+ type: "button",
4811
+ 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",
4812
+ onClick: () => onPageSelect(page),
4813
+ children: [
4814
+ /* @__PURE__ */ jsx12(IconFile, { className: "shrink-0", "aria-hidden": true }),
4815
+ /* @__PURE__ */ jsx12("span", { className: "truncate", children: page.title })
4816
+ ]
4817
+ },
4818
+ page.path
4819
+ ))
4820
+ }
4821
+ ) : null
4021
4822
  ] }),
4022
- urlError ? /* @__PURE__ */ jsx10("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4823
+ urlError ? /* @__PURE__ */ jsx12("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4023
4824
  ] });
4024
4825
  }
4025
4826
 
4026
4827
  // src/ui/link-modal/useLinkModalState.ts
4027
- import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
4828
+ import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useState as useState4 } from "react";
4028
4829
  function useLinkModalState({
4029
4830
  open,
4030
4831
  mode,
@@ -4036,16 +4837,16 @@ function useLinkModalState({
4036
4837
  onClose,
4037
4838
  onSubmit
4038
4839
  }) {
4039
- const availablePages = useMemo(
4840
+ const availablePages = useMemo2(
4040
4841
  () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
4041
4842
  [mode, pages, existingTargets]
4042
4843
  );
4043
- const [searchValue, setSearchValue] = useState2("");
4044
- const [selectedPage, setSelectedPage] = useState2(null);
4045
- const [selectedSection, setSelectedSection] = useState2(null);
4046
- const [step, setStep] = useState2("input");
4047
- const [dropdownOpen, setDropdownOpen] = useState2(false);
4048
- const [urlError, setUrlError] = useState2("");
4844
+ const [searchValue, setSearchValue] = useState4("");
4845
+ const [selectedPage, setSelectedPage] = useState4(null);
4846
+ const [selectedSection, setSelectedSection] = useState4(null);
4847
+ const [step, setStep] = useState4("input");
4848
+ const [dropdownOpen, setDropdownOpen] = useState4(false);
4849
+ const [urlError, setUrlError] = useState4("");
4049
4850
  const reset = useCallback(() => {
4050
4851
  setSearchValue("");
4051
4852
  setSelectedPage(null);
@@ -4054,7 +4855,7 @@ function useLinkModalState({
4054
4855
  setDropdownOpen(false);
4055
4856
  setUrlError("");
4056
4857
  }, []);
4057
- useEffect(() => {
4858
+ useEffect2(() => {
4058
4859
  if (!open) return;
4059
4860
  if (mode === "edit" && initialTarget) {
4060
4861
  const { pageRoute } = parseTarget(initialTarget);
@@ -4070,11 +4871,11 @@ function useLinkModalState({
4070
4871
  setDropdownOpen(false);
4071
4872
  setUrlError("");
4072
4873
  }, [open, mode, initialTarget, pages, sectionsByPath, reset]);
4073
- const filteredPages = useMemo(() => {
4874
+ const filteredPages = useMemo2(() => {
4074
4875
  if (!searchValue.trim()) return availablePages;
4075
4876
  return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
4076
4877
  }, [availablePages, searchValue]);
4077
- const activeSections = useMemo(() => {
4878
+ const activeSections = useMemo2(() => {
4078
4879
  if (!selectedPage) return [];
4079
4880
  return getSectionsForPath(sectionsByPath, selectedPage.path);
4080
4881
  }, [selectedPage, sectionsByPath]);
@@ -4120,7 +4921,7 @@ function useLinkModalState({
4120
4921
  reset();
4121
4922
  onClose();
4122
4923
  };
4123
- const isValid = useMemo(() => {
4924
+ const isValid = useMemo2(() => {
4124
4925
  if (urlError) return false;
4125
4926
  if (showBreadcrumb) return true;
4126
4927
  if (selectedPage) return true;
@@ -4187,7 +4988,7 @@ function useLinkModalState({
4187
4988
  }
4188
4989
 
4189
4990
  // src/ui/link-modal/LinkEditorPanel.tsx
4190
- import { Fragment, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
4991
+ import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
4191
4992
  function LinkEditorPanel({
4192
4993
  open = true,
4193
4994
  mode = "create",
@@ -4211,26 +5012,25 @@ function LinkEditorPanel({
4211
5012
  onSubmit
4212
5013
  });
4213
5014
  const isCancel = state.secondaryLabel === "Cancel";
4214
- return /* @__PURE__ */ jsxs5(Fragment, { children: [
4215
- /* @__PURE__ */ jsx11(
5015
+ return /* @__PURE__ */ jsxs8(Fragment2, { children: [
5016
+ /* @__PURE__ */ jsx13(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx13(
4216
5017
  "button",
4217
5018
  {
4218
5019
  type: "button",
4219
5020
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4220
- onClick: state.handleClose,
4221
5021
  "aria-label": "Close",
4222
- children: /* @__PURE__ */ jsx11(IconX, { "aria-hidden": true })
5022
+ children: /* @__PURE__ */ jsx13(IconX, { "aria-hidden": true })
4223
5023
  }
4224
- ),
4225
- /* @__PURE__ */ jsx11("header", { className: "flex w-full flex-col gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx11("h2", { className: "m-0 w-full break-words text-lg font-semibold leading-none tracking-tight text-card-foreground", children: state.title }) }),
4226
- /* @__PURE__ */ jsxs5("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
4227
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx11(
5024
+ ) }),
5025
+ /* @__PURE__ */ jsx13(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx13(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5026
+ /* @__PURE__ */ jsxs8("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5027
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx13(
4228
5028
  DestinationBreadcrumb,
4229
5029
  {
4230
5030
  pageTitle: state.selectedPage.title,
4231
5031
  sectionLabel: state.selectedSection.label
4232
5032
  }
4233
- ) : /* @__PURE__ */ jsx11(
5033
+ ) : /* @__PURE__ */ jsx13(
4234
5034
  UrlOrPageInput,
4235
5035
  {
4236
5036
  value: state.searchValue,
@@ -4243,18 +5043,18 @@ function LinkEditorPanel({
4243
5043
  urlError: state.urlError
4244
5044
  }
4245
5045
  ),
4246
- state.showChooseSection ? /* @__PURE__ */ jsxs5("div", { className: "flex flex-col justify-center gap-2", children: [
4247
- /* @__PURE__ */ jsx11(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
4248
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
4249
- /* @__PURE__ */ jsx11(IconInfo, { className: "shrink-0", "aria-hidden": true }),
4250
- /* @__PURE__ */ jsx11("span", { children: "Pick a section this link should scroll to." })
5046
+ state.showChooseSection ? /* @__PURE__ */ jsxs8("div", { className: "flex flex-col justify-center gap-2", children: [
5047
+ /* @__PURE__ */ jsx13(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5048
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5049
+ /* @__PURE__ */ jsx13(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5050
+ /* @__PURE__ */ jsx13("span", { children: "Pick a section this link should scroll to." })
4251
5051
  ] })
4252
5052
  ] }) : null,
4253
- state.showSectionPicker ? /* @__PURE__ */ jsx11(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
4254
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx11(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5053
+ state.showSectionPicker ? /* @__PURE__ */ jsx13(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5054
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx13(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
4255
5055
  ] }),
4256
- /* @__PURE__ */ jsxs5("footer", { className: "flex w-full items-center justify-end gap-2 px-6 pb-6", children: [
4257
- /* @__PURE__ */ jsx11(
5056
+ /* @__PURE__ */ jsxs8(DialogFooter, { className: "w-full px-6 pb-6", children: [
5057
+ /* @__PURE__ */ jsx13(
4258
5058
  Button,
4259
5059
  {
4260
5060
  type: "button",
@@ -4269,7 +5069,7 @@ function LinkEditorPanel({
4269
5069
  children: state.secondaryLabel
4270
5070
  }
4271
5071
  ),
4272
- /* @__PURE__ */ jsx11(
5072
+ /* @__PURE__ */ jsx13(
4273
5073
  Button,
4274
5074
  {
4275
5075
  type: "button",
@@ -4288,28 +5088,34 @@ function LinkEditorPanel({
4288
5088
  }
4289
5089
 
4290
5090
  // src/ui/link-modal/LinkPopover.tsx
4291
- import { jsx as jsx12 } from "react/jsx-runtime";
5091
+ import { jsx as jsx14 } from "react/jsx-runtime";
4292
5092
  function LinkPopover({
4293
- rect,
4294
- parentScroll,
5093
+ open = true,
4295
5094
  panelRef,
5095
+ portalContainer,
5096
+ onClose,
4296
5097
  ...editorProps
4297
5098
  }) {
4298
- const { top, left, transform } = calcPopoverPos(rect, parentScroll);
4299
- return /* @__PURE__ */ jsx12(
4300
- "div",
5099
+ return /* @__PURE__ */ jsx14(
5100
+ Dialog2,
4301
5101
  {
4302
- ref: panelRef,
4303
- "data-ohw-link-popover-root": "",
4304
- "data-ohw-link-modal-root": "",
4305
- "data-ohw-bridge": "",
4306
- className: cn(
4307
- "pointer-events-auto fixed z-[2147483647] flex w-[483px] max-w-[calc(100vw-16px)] flex-col overflow-hidden",
4308
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none"
4309
- ),
4310
- style: { top, left, transform },
4311
- onMouseDown: (e) => e.stopPropagation(),
4312
- children: /* @__PURE__ */ jsx12(LinkEditorPanel, { ...editorProps, open: true })
5102
+ open,
5103
+ onOpenChange: (next) => {
5104
+ if (!next) onClose?.();
5105
+ },
5106
+ children: /* @__PURE__ */ jsx14(
5107
+ DialogContent,
5108
+ {
5109
+ ref: panelRef,
5110
+ container: portalContainer,
5111
+ "data-ohw-link-popover-root": "",
5112
+ "data-ohw-link-modal-root": "",
5113
+ "data-ohw-bridge": "",
5114
+ showCloseButton: false,
5115
+ className: "gap-0 p-0 w-full md:w-md pointer-events-auto",
5116
+ children: /* @__PURE__ */ jsx14(LinkEditorPanel, { ...editorProps, open, onClose })
5117
+ }
5118
+ )
4313
5119
  }
4314
5120
  );
4315
5121
  }
@@ -4372,8 +5178,30 @@ function shouldUseDevFixtures() {
4372
5178
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
4373
5179
  }
4374
5180
 
5181
+ // src/ui/badge.tsx
5182
+ import { jsx as jsx15 } from "react/jsx-runtime";
5183
+ var badgeVariants = cva(
5184
+ "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",
5185
+ {
5186
+ variants: {
5187
+ variant: {
5188
+ default: "border-transparent bg-primary text-primary-foreground",
5189
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
5190
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
5191
+ outline: "text-foreground"
5192
+ }
5193
+ },
5194
+ defaultVariants: {
5195
+ variant: "default"
5196
+ }
5197
+ }
5198
+ );
5199
+ function Badge({ className, variant, ...props }) {
5200
+ return /* @__PURE__ */ jsx15("div", { className: cn(badgeVariants({ variant }), className), ...props });
5201
+ }
5202
+
4375
5203
  // src/OhhwellsBridge.tsx
4376
- import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
5204
+ import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
4377
5205
  var PRIMARY = "#0885FE";
4378
5206
  var IMAGE_FADE_MS = 300;
4379
5207
  function runOpacityFade(el, onDone) {
@@ -4424,6 +5252,169 @@ function fadeInBgImage(el, url, onReady) {
4424
5252
  if (!prevPos || prevPos === "static") el.style.position = prevPos;
4425
5253
  });
4426
5254
  }
5255
+ function getSectionsTracker() {
5256
+ let el = document.querySelector("[data-ohw-sections-tracker]");
5257
+ if (!el) {
5258
+ el = document.createElement("div");
5259
+ el.setAttribute("data-ohw-sections-tracker", "");
5260
+ el.style.display = "none";
5261
+ document.body.appendChild(el);
5262
+ }
5263
+ return el;
5264
+ }
5265
+ function updateSectionScheduleId(insertAfter, scheduleId) {
5266
+ const tracker = getSectionsTracker();
5267
+ let sections = [];
5268
+ try {
5269
+ sections = JSON.parse(tracker.textContent || "[]");
5270
+ } catch {
5271
+ }
5272
+ const currentPath = window.location.pathname;
5273
+ const updated = sections.map((s) => {
5274
+ if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
5275
+ if (s.pagePath && s.pagePath !== currentPath) return s;
5276
+ return { ...s, scheduleId };
5277
+ });
5278
+ tracker.textContent = JSON.stringify(updated);
5279
+ return tracker.textContent ?? "[]";
5280
+ }
5281
+ function schedulingSectionId(insertAfter) {
5282
+ return `scheduling-${insertAfter}`;
5283
+ }
5284
+ var INSERT_BEFORE_MARKER = ":before:";
5285
+ function parseSchedulingInsertAfter(insertAfter) {
5286
+ const idx = insertAfter.indexOf(INSERT_BEFORE_MARKER);
5287
+ if (idx < 0) return { anchor: insertAfter, insertBefore: null };
5288
+ return {
5289
+ anchor: insertAfter.slice(0, idx),
5290
+ insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
5291
+ };
5292
+ }
5293
+ function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
5294
+ const parsed = parseSchedulingInsertAfter(insertAfter);
5295
+ const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
5296
+ const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
5297
+ return { effectiveInsertAfter, insertBefore };
5298
+ }
5299
+ function getSchedulingMountPoint(insertAfter) {
5300
+ const { anchor } = parseSchedulingInsertAfter(insertAfter);
5301
+ let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
5302
+ if (!anchorEl && anchor === "scheduling") {
5303
+ const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
5304
+ anchorEl = widgets.at(-1) ?? null;
5305
+ }
5306
+ if (!anchorEl) return null;
5307
+ return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
5308
+ }
5309
+ function schedulingMountDepth(insertAfter) {
5310
+ if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
5311
+ return insertAfter.split(INSERT_BEFORE_MARKER).length - 1;
5312
+ }
5313
+ function getPageSchedulingEntries(raw) {
5314
+ if (!raw) return [];
5315
+ try {
5316
+ const entries = JSON.parse(raw);
5317
+ const currentPath = window.location.pathname;
5318
+ return entries.filter((e) => e.type === "scheduling" && (!e.pagePath || e.pagePath === currentPath));
5319
+ } catch {
5320
+ return [];
5321
+ }
5322
+ }
5323
+ function isSchedulingWidgetMissing(entry) {
5324
+ const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
5325
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
5326
+ }
5327
+ function hasMissingSchedulingWidgets(entries) {
5328
+ return entries.some(isSchedulingWidgetMissing);
5329
+ }
5330
+ function retryMissingSchedulingMounts(entries, notifyOnConnect = false) {
5331
+ if (!hasMissingSchedulingWidgets(entries)) return;
5332
+ mountSchedulingEntries(entries, notifyOnConnect);
5333
+ }
5334
+ function initSectionsFromContent(content, removeExisting = false) {
5335
+ const raw = content["__ohw_sections"];
5336
+ if (!raw) return;
5337
+ try {
5338
+ if (removeExisting) {
5339
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => el.remove());
5340
+ }
5341
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
5342
+ if (inEditor) getSectionsTracker().textContent = raw;
5343
+ const pageEntries = getPageSchedulingEntries(raw);
5344
+ const preExisting = pageEntries.filter((e) => !isSchedulingWidgetMissing(e));
5345
+ const notifyForEntry = inEditor ? (e) => !e.scheduleId : false;
5346
+ mountSchedulingEntries(pageEntries, notifyForEntry);
5347
+ requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry));
5348
+ setTimeout(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry), 250);
5349
+ for (const entry of preExisting) {
5350
+ window.postMessage({ type: "ow:schedule-config", insertAfter: entry.insertAfter, scheduleId: entry.scheduleId ?? null }, "*");
5351
+ }
5352
+ } catch {
5353
+ }
5354
+ }
5355
+ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
5356
+ const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
5357
+ const sectionId = schedulingSectionId(effectiveInsertAfter);
5358
+ if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
5359
+ const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
5360
+ if (!mountPoint) return false;
5361
+ const container = document.createElement("div");
5362
+ container.dataset.ohwSectionContainer = "scheduling";
5363
+ if (insertBefore) {
5364
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
5365
+ const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
5366
+ if (!beforePoint) return false;
5367
+ beforePoint.insertAdjacentElement("beforebegin", container);
5368
+ } else {
5369
+ let tail = mountPoint;
5370
+ while (tail.nextElementSibling?.dataset.ohwSectionContainer === "scheduling") {
5371
+ tail = tail.nextElementSibling;
5372
+ }
5373
+ tail.insertAdjacentElement("afterend", container);
5374
+ }
5375
+ const root = createRoot(container);
5376
+ flushSync(() => {
5377
+ root.render(
5378
+ /* @__PURE__ */ jsx16(
5379
+ SchedulingWidget,
5380
+ {
5381
+ notifyOnConnect,
5382
+ initialScheduleId: scheduleId,
5383
+ insertAfter: effectiveInsertAfter
5384
+ }
5385
+ )
5386
+ );
5387
+ });
5388
+ const tracker = getSectionsTracker();
5389
+ let sections = [];
5390
+ try {
5391
+ sections = JSON.parse(tracker.textContent || "[]");
5392
+ } catch {
5393
+ }
5394
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
5395
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
5396
+ sections.push({
5397
+ type: "scheduling",
5398
+ insertAfter: effectiveInsertAfter,
5399
+ pagePath: window.location.pathname,
5400
+ ...scheduleId ? { scheduleId } : {}
5401
+ });
5402
+ tracker.textContent = JSON.stringify(sections);
5403
+ }
5404
+ return true;
5405
+ }
5406
+ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5407
+ const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
5408
+ for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
5409
+ for (let i = pending.length - 1; i >= 0; i--) {
5410
+ const entry = pending[i];
5411
+ const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
5412
+ if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
5413
+ pending.splice(i, 1);
5414
+ }
5415
+ }
5416
+ }
5417
+ }
4427
5418
  function getLinkHref(el) {
4428
5419
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
4429
5420
  return anchor?.getAttribute("href") ?? "";
@@ -4470,7 +5461,7 @@ function applyLinkByKey(key, val) {
4470
5461
  }
4471
5462
  function isInsideLinkEditor(target) {
4472
5463
  return Boolean(
4473
- target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest('[data-slot="popover-content"]')
5464
+ target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest("[data-ohw-link-page-dropdown]") || target.closest('[data-slot="popover-content"]') || target.closest('[data-slot="dialog-content"]') || target.closest('[data-slot="dialog-overlay"]')
4474
5465
  );
4475
5466
  }
4476
5467
  function getHrefKeyFromElement(el) {
@@ -4481,6 +5472,15 @@ function getHrefKeyFromElement(el) {
4481
5472
  if (!key) return null;
4482
5473
  return { anchor, key };
4483
5474
  }
5475
+ function isNavbarButton(el) {
5476
+ return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
5477
+ }
5478
+ function clearHrefKeyHover(anchor) {
5479
+ anchor.removeAttribute("data-ohw-hovered");
5480
+ anchor.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5481
+ el.removeAttribute("data-ohw-hovered");
5482
+ });
5483
+ }
4484
5484
  function collectSections() {
4485
5485
  return collectSectionsFromDom();
4486
5486
  }
@@ -4623,7 +5623,7 @@ var TOOLBAR_GROUPS = [
4623
5623
  ];
4624
5624
  function GlowFrame({ rect, elRef }) {
4625
5625
  const GAP = 6;
4626
- return /* @__PURE__ */ jsx13(
5626
+ return /* @__PURE__ */ jsx16(
4627
5627
  "div",
4628
5628
  {
4629
5629
  ref: elRef,
@@ -4642,41 +5642,191 @@ function GlowFrame({ rect, elRef }) {
4642
5642
  }
4643
5643
  );
4644
5644
  }
4645
- function calcToolbarPos(rect, parentScroll) {
5645
+ function getIframeVisibleClip(parentScroll) {
5646
+ if (!parentScroll) return null;
5647
+ const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
5648
+ const clipTop = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5649
+ const clipBottom = Math.min(
5650
+ window.innerHeight,
5651
+ visibleCanvasTop + canvasH - iframeOffsetTop
5652
+ );
5653
+ return { top: clipTop, bottom: Math.max(clipTop, clipBottom) };
5654
+ }
5655
+ function applyVisibleViewport(el, parentScroll) {
5656
+ const clip = getIframeVisibleClip(parentScroll);
5657
+ const top = clip?.top ?? 0;
5658
+ const height = clip ? clip.bottom - clip.top : window.innerHeight;
5659
+ el.style.top = `${top}px`;
5660
+ el.style.height = `${height}px`;
5661
+ }
5662
+ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
5663
+ const isAbove = transform.includes("translateY(-100%)");
5664
+ let visualTop = isAbove ? top - approxH : top;
5665
+ let visualBottom = isAbove ? top : top + approxH;
5666
+ const minTop = clip.top + gap;
5667
+ const maxBottom = clip.bottom - gap;
5668
+ if (visualTop >= minTop && visualBottom <= maxBottom) {
5669
+ return { top, transform };
5670
+ }
5671
+ const belowTop = rect.bottom + gap;
5672
+ if (belowTop + approxH <= maxBottom && belowTop >= minTop) {
5673
+ return { top: belowTop, transform: "translateX(-50%)" };
5674
+ }
5675
+ const aboveTop = rect.top - gap;
5676
+ if (aboveTop - approxH >= minTop && aboveTop <= maxBottom) {
5677
+ return { top: aboveTop, transform: "translateX(-50%) translateY(-100%)" };
5678
+ }
5679
+ if (belowTop + approxH <= maxBottom) {
5680
+ return { top: belowTop, transform: "translateX(-50%)" };
5681
+ }
5682
+ const pinnedTop = Math.min(maxBottom - approxH, Math.max(minTop, clip.top + gap));
5683
+ return { top: pinnedTop + approxH, transform: "translateX(-50%) translateY(-100%)" };
5684
+ }
5685
+ function calcToolbarPos(rect, parentScroll, approxW = 330) {
4646
5686
  const GAP = 8;
4647
5687
  const APPROX_H = 36;
4648
- const APPROX_W = 330;
5688
+ const APPROX_W = approxW;
5689
+ const clip = getIframeVisibleClip(parentScroll);
5690
+ const clipTop = clip?.top ?? 0;
4649
5691
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
4650
5692
  const visibleTop = rect.top - canvasTopInIframe;
4651
5693
  const visibleBottom = rect.bottom - canvasTopInIframe;
4652
5694
  const isTall = rect.height > APPROX_H + GAP;
5695
+ const spaceAbove = rect.top - (clipTop + GAP);
5696
+ const fitsAbove = spaceAbove >= APPROX_H + GAP;
4653
5697
  let top;
4654
5698
  let transform;
4655
- if (visibleTop > 0 || !isTall) {
5699
+ if ((visibleTop > 0 || !isTall) && fitsAbove) {
4656
5700
  top = rect.top - GAP;
4657
5701
  transform = "translateX(-50%) translateY(-100%)";
5702
+ } else if (visibleTop > 0 || !isTall) {
5703
+ top = rect.bottom + GAP;
5704
+ transform = "translateX(-50%)";
4658
5705
  } else if (visibleBottom > APPROX_H + GAP) {
4659
- top = canvasTopInIframe + GAP;
5706
+ top = clipTop + GAP;
4660
5707
  transform = "translateX(-50%)";
4661
5708
  } else {
4662
5709
  top = rect.bottom + GAP;
4663
5710
  transform = "translateX(-50%)";
4664
5711
  }
5712
+ if (clip) {
5713
+ const clamped = clampToolbarToClip(top, transform, rect, clip, APPROX_H, GAP);
5714
+ top = clamped.top;
5715
+ transform = clamped.transform;
5716
+ }
4665
5717
  const rawLeft = rect.left + rect.width / 2;
4666
5718
  const left = Math.max(GAP + APPROX_W / 2, Math.min(rawLeft, window.innerWidth - GAP - APPROX_W / 2));
4667
5719
  return { top, left, transform };
4668
5720
  }
5721
+ var EDIT_LINK_ICON = /* @__PURE__ */ jsxs9("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
5722
+ /* @__PURE__ */ jsx16("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
5723
+ /* @__PURE__ */ jsx16("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
5724
+ ] });
4669
5725
  function FloatingToolbar({
4670
5726
  rect,
4671
5727
  parentScroll,
4672
5728
  elRef,
5729
+ variant = "rich-text",
4673
5730
  onCommand,
4674
5731
  activeCommands,
4675
5732
  showEditLink,
4676
5733
  onEditLink
4677
5734
  }) {
4678
- const { top, left, transform } = calcToolbarPos(rect, parentScroll);
4679
- return /* @__PURE__ */ jsxs6(
5735
+ const approxW = variant === "link-action" ? 120 : 330;
5736
+ const { top, left, transform } = calcToolbarPos(rect, parentScroll, approxW);
5737
+ if (variant === "link-action") {
5738
+ return /* @__PURE__ */ jsxs9(
5739
+ "div",
5740
+ {
5741
+ ref: elRef,
5742
+ "data-ohw-toolbar": "",
5743
+ style: {
5744
+ position: "fixed",
5745
+ top,
5746
+ left,
5747
+ transform,
5748
+ zIndex: 2147483647,
5749
+ background: "#fff",
5750
+ border: "1px solid #E7E5E4",
5751
+ borderRadius: 6,
5752
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
5753
+ display: "flex",
5754
+ alignItems: "center",
5755
+ padding: 4,
5756
+ gap: 6,
5757
+ fontFamily: "sans-serif",
5758
+ pointerEvents: "auto",
5759
+ whiteSpace: "nowrap"
5760
+ },
5761
+ onMouseDown: (e) => e.stopPropagation(),
5762
+ children: [
5763
+ /* @__PURE__ */ jsx16(
5764
+ "button",
5765
+ {
5766
+ type: "button",
5767
+ title: "Edit link",
5768
+ onMouseDown: (e) => {
5769
+ e.preventDefault();
5770
+ e.stopPropagation();
5771
+ onEditLink?.();
5772
+ },
5773
+ onClick: (e) => {
5774
+ e.preventDefault();
5775
+ e.stopPropagation();
5776
+ },
5777
+ onMouseEnter: (e) => {
5778
+ e.currentTarget.style.background = "#F5F5F4";
5779
+ },
5780
+ onMouseLeave: (e) => {
5781
+ e.currentTarget.style.background = "transparent";
5782
+ },
5783
+ style: {
5784
+ display: "flex",
5785
+ alignItems: "center",
5786
+ justifyContent: "center",
5787
+ border: "none",
5788
+ background: "transparent",
5789
+ borderRadius: 4,
5790
+ cursor: "pointer",
5791
+ color: "#1C1917",
5792
+ flexShrink: 0,
5793
+ padding: 6
5794
+ },
5795
+ children: EDIT_LINK_ICON
5796
+ }
5797
+ ),
5798
+ /* @__PURE__ */ jsx16("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5799
+ /* @__PURE__ */ jsx16(
5800
+ "button",
5801
+ {
5802
+ type: "button",
5803
+ title: "Coming soon",
5804
+ disabled: true,
5805
+ style: {
5806
+ display: "flex",
5807
+ alignItems: "center",
5808
+ justifyContent: "center",
5809
+ border: "none",
5810
+ background: "transparent",
5811
+ borderRadius: 4,
5812
+ cursor: "not-allowed",
5813
+ color: "#A8A29E",
5814
+ flexShrink: 0,
5815
+ padding: 6,
5816
+ opacity: 0.6
5817
+ },
5818
+ children: /* @__PURE__ */ jsxs9("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: [
5819
+ /* @__PURE__ */ jsx16("circle", { cx: "5", cy: "12", r: "1.5" }),
5820
+ /* @__PURE__ */ jsx16("circle", { cx: "12", cy: "12", r: "1.5" }),
5821
+ /* @__PURE__ */ jsx16("circle", { cx: "19", cy: "12", r: "1.5" })
5822
+ ] })
5823
+ }
5824
+ )
5825
+ ]
5826
+ }
5827
+ );
5828
+ }
5829
+ return /* @__PURE__ */ jsxs9(
4680
5830
  "div",
4681
5831
  {
4682
5832
  ref: elRef,
@@ -4701,11 +5851,11 @@ function FloatingToolbar({
4701
5851
  },
4702
5852
  onMouseDown: (e) => e.stopPropagation(),
4703
5853
  children: [
4704
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs6(React4.Fragment, { children: [
4705
- gi > 0 && /* @__PURE__ */ jsx13("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5854
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs9(React6.Fragment, { children: [
5855
+ gi > 0 && /* @__PURE__ */ jsx16("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
4706
5856
  btns.map((btn) => {
4707
5857
  const isActive = activeCommands.has(btn.cmd);
4708
- return /* @__PURE__ */ jsx13(
5858
+ return /* @__PURE__ */ jsx16(
4709
5859
  "button",
4710
5860
  {
4711
5861
  title: btn.title,
@@ -4731,7 +5881,7 @@ function FloatingToolbar({
4731
5881
  flexShrink: 0,
4732
5882
  padding: 6
4733
5883
  },
4734
- children: /* @__PURE__ */ jsx13(
5884
+ children: /* @__PURE__ */ jsx16(
4735
5885
  "svg",
4736
5886
  {
4737
5887
  width: "16",
@@ -4751,7 +5901,7 @@ function FloatingToolbar({
4751
5901
  );
4752
5902
  })
4753
5903
  ] }, gi)),
4754
- showEditLink ? /* @__PURE__ */ jsx13(
5904
+ showEditLink ? /* @__PURE__ */ jsx16(
4755
5905
  "button",
4756
5906
  {
4757
5907
  type: "button",
@@ -4783,10 +5933,7 @@ function FloatingToolbar({
4783
5933
  flexShrink: 0,
4784
5934
  padding: 6
4785
5935
  },
4786
- children: /* @__PURE__ */ jsxs6("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: [
4787
- /* @__PURE__ */ jsx13("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
4788
- /* @__PURE__ */ jsx13("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
4789
- ] })
5936
+ children: EDIT_LINK_ICON
4790
5937
  }
4791
5938
  ) : null
4792
5939
  ]
@@ -4803,7 +5950,7 @@ function StateToggle({
4803
5950
  states,
4804
5951
  onStateChange
4805
5952
  }) {
4806
- return /* @__PURE__ */ jsx13(
5953
+ return /* @__PURE__ */ jsx16(
4807
5954
  ToggleGroup,
4808
5955
  {
4809
5956
  "data-ohw-state-toggle": "",
@@ -4817,18 +5964,35 @@ function StateToggle({
4817
5964
  left: rect.right - 8,
4818
5965
  transform: "translateX(-100%)"
4819
5966
  },
4820
- children: states.map((state) => /* @__PURE__ */ jsx13(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
5967
+ children: states.map((state) => /* @__PURE__ */ jsx16(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
4821
5968
  }
4822
5969
  );
4823
5970
  }
4824
5971
  var contentCache = /* @__PURE__ */ new Map();
5972
+ function resolveSubdomain(subdomainFromQuery) {
5973
+ if (subdomainFromQuery) return subdomainFromQuery;
5974
+ if (typeof window !== "undefined") {
5975
+ const parts = window.location.hostname.split(".");
5976
+ if (parts.length >= 3 && parts[0] !== "www") return parts[0];
5977
+ }
5978
+ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
5979
+ if (siteUrl) {
5980
+ try {
5981
+ const host = new URL(siteUrl).hostname;
5982
+ const siteParts = host.split(".");
5983
+ if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
5984
+ } catch {
5985
+ }
5986
+ }
5987
+ return "";
5988
+ }
4825
5989
  function OhhwellsBridge() {
4826
5990
  const pathname = usePathname();
4827
5991
  const router = useRouter();
4828
5992
  const searchParams = useSearchParams();
4829
5993
  const isEditMode = isEditSessionActive();
4830
- const [bridgeRoot, setBridgeRoot] = useState3(null);
4831
- useEffect2(() => {
5994
+ const [bridgeRoot, setBridgeRoot] = useState5(null);
5995
+ useEffect3(() => {
4832
5996
  const figtreeFontId = "ohw-figtree-font";
4833
5997
  if (!document.getElementById(figtreeFontId)) {
4834
5998
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -4851,50 +6015,65 @@ function OhhwellsBridge() {
4851
6015
  };
4852
6016
  }, []);
4853
6017
  const subdomainFromQuery = searchParams.get("subdomain");
4854
- const subdomain = subdomainFromQuery ?? (() => {
4855
- if (typeof window === "undefined") return "";
4856
- const parts = window.location.hostname.split(".");
4857
- return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
4858
- })();
6018
+ const subdomain = resolveSubdomain(subdomainFromQuery);
4859
6019
  const postToParent = useCallback2((data) => {
4860
6020
  if (typeof window !== "undefined" && window.parent !== window) {
4861
6021
  window.parent.postMessage(data, "*");
4862
6022
  }
4863
6023
  }, []);
4864
- const [fetchState, setFetchState] = useState3("idle");
4865
- const autoSaveTimers = useRef2(/* @__PURE__ */ new Map());
4866
- const activeElRef = useRef2(null);
4867
- const originalContentRef = useRef2(null);
4868
- const activeStateElRef = useRef2(null);
4869
- const parentScrollRef = useRef2(null);
4870
- const toolbarElRef = useRef2(null);
4871
- const glowElRef = useRef2(null);
4872
- const hoveredImageRef = useRef2(null);
4873
- const hoveredImageHasTextOverlapRef = useRef2(false);
4874
- const imageUnhoverTimerRef = useRef2(null);
4875
- const imageShowTimerRef = useRef2(null);
4876
- const editStylesRef = useRef2(null);
4877
- const activateRef = useRef2(() => {
6024
+ const [fetchState, setFetchState] = useState5("idle");
6025
+ const autoSaveTimers = useRef3(/* @__PURE__ */ new Map());
6026
+ const activeElRef = useRef3(null);
6027
+ const selectedElRef = useRef3(null);
6028
+ const originalContentRef = useRef3(null);
6029
+ const activeStateElRef = useRef3(null);
6030
+ const parentScrollRef = useRef3(null);
6031
+ const visibleViewportRef = useRef3(null);
6032
+ const [dialogPortalContainer, setDialogPortalContainer] = useState5(null);
6033
+ const attachVisibleViewport = useCallback2((node) => {
6034
+ visibleViewportRef.current = node;
6035
+ setDialogPortalContainer(node);
6036
+ if (node) applyVisibleViewport(node, parentScrollRef.current);
6037
+ }, []);
6038
+ const toolbarElRef = useRef3(null);
6039
+ const glowElRef = useRef3(null);
6040
+ const hoveredImageRef = useRef3(null);
6041
+ const hoveredImageHasTextOverlapRef = useRef3(false);
6042
+ const hoveredGapRef = useRef3(null);
6043
+ const imageUnhoverTimerRef = useRef3(null);
6044
+ const imageShowTimerRef = useRef3(null);
6045
+ const editStylesRef = useRef3(null);
6046
+ const activateRef = useRef3(() => {
6047
+ });
6048
+ const deactivateRef = useRef3(() => {
4878
6049
  });
4879
- const deactivateRef = useRef2(() => {
6050
+ const selectRef = useRef3(() => {
4880
6051
  });
4881
- const refreshActiveCommandsRef = useRef2(() => {
6052
+ const deselectRef = useRef3(() => {
4882
6053
  });
4883
- const postToParentRef = useRef2(postToParent);
6054
+ const refreshActiveCommandsRef = useRef3(() => {
6055
+ });
6056
+ const postToParentRef = useRef3(postToParent);
4884
6057
  postToParentRef.current = postToParent;
4885
- const [toolbarRect, setToolbarRect] = useState3(null);
4886
- const [toggleState, setToggleState] = useState3(null);
4887
- const [maxBadge, setMaxBadge] = useState3(null);
4888
- const [activeCommands, setActiveCommands] = useState3(/* @__PURE__ */ new Set());
4889
- const [toolbarShowEditLink, setToolbarShowEditLink] = useState3(false);
4890
- const [linkPopover, setLinkPopover] = useState3(null);
4891
- const [sitePages, setSitePages] = useState3([]);
4892
- const [sectionsByPath, setSectionsByPath] = useState3({});
4893
- const sectionsPrefetchGenRef = useRef2(0);
4894
- const setLinkPopoverRef = useRef2(setLinkPopover);
4895
- const linkPopoverPanelRef = useRef2(null);
4896
- const linkPopoverOpenRef = useRef2(false);
4897
- const linkPopoverGraceUntilRef = useRef2(0);
6058
+ const sectionsLoadedRef = useRef3(false);
6059
+ const pendingScheduleConfigRequests = useRef3([]);
6060
+ const [toolbarRect, setToolbarRect] = useState5(null);
6061
+ const [toolbarVariant, setToolbarVariant] = useState5("none");
6062
+ const toolbarVariantRef = useRef3("none");
6063
+ toolbarVariantRef.current = toolbarVariant;
6064
+ const [toggleState, setToggleState] = useState5(null);
6065
+ const [maxBadge, setMaxBadge] = useState5(null);
6066
+ const [activeCommands, setActiveCommands] = useState5(/* @__PURE__ */ new Set());
6067
+ const [sectionGap, setSectionGap] = useState5(null);
6068
+ const [toolbarShowEditLink, setToolbarShowEditLink] = useState5(false);
6069
+ const [linkPopover, setLinkPopover] = useState5(null);
6070
+ const [sitePages, setSitePages] = useState5([]);
6071
+ const [sectionsByPath, setSectionsByPath] = useState5({});
6072
+ const sectionsPrefetchGenRef = useRef3(0);
6073
+ const setLinkPopoverRef = useRef3(setLinkPopover);
6074
+ const linkPopoverPanelRef = useRef3(null);
6075
+ const linkPopoverOpenRef = useRef3(false);
6076
+ const linkPopoverGraceUntilRef = useRef3(0);
4898
6077
  setLinkPopoverRef.current = setLinkPopover;
4899
6078
  const bumpLinkPopoverGrace = () => {
4900
6079
  linkPopoverGraceUntilRef.current = Date.now() + 350;
@@ -4918,17 +6097,43 @@ function OhhwellsBridge() {
4918
6097
  );
4919
6098
  });
4920
6099
  }, [isEditMode, pathname]);
4921
- const runSectionsPrefetchRef = useRef2(runSectionsPrefetch);
6100
+ const runSectionsPrefetchRef = useRef3(runSectionsPrefetch);
4922
6101
  runSectionsPrefetchRef.current = runSectionsPrefetch;
4923
- useEffect2(() => {
6102
+ useEffect3(() => {
4924
6103
  if (!linkPopover) return;
4925
6104
  if (hoveredImageRef.current) {
4926
6105
  hoveredImageRef.current = null;
4927
6106
  hoveredImageHasTextOverlapRef.current = false;
4928
6107
  }
6108
+ hoveredGapRef.current = null;
6109
+ setSectionGap(null);
4929
6110
  postToParent({ type: "ow:image-unhover" });
6111
+ postToParent({ type: "ow:link-modal-lock", locked: true });
6112
+ const html = document.documentElement;
6113
+ const body = document.body;
6114
+ const prevHtmlOverflow = html.style.overflow;
6115
+ const prevBodyOverflow = body.style.overflow;
6116
+ html.style.overflow = "hidden";
6117
+ body.style.overflow = "hidden";
6118
+ const preventBackgroundScroll = (e) => {
6119
+ const target = e.target;
6120
+ if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6121
+ return;
6122
+ }
6123
+ e.preventDefault();
6124
+ };
6125
+ const scrollOpts = { passive: false, capture: true };
6126
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6127
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6128
+ return () => {
6129
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6130
+ html.style.overflow = prevHtmlOverflow;
6131
+ body.style.overflow = prevBodyOverflow;
6132
+ document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6133
+ document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6134
+ };
4930
6135
  }, [linkPopover, postToParent]);
4931
- useEffect2(() => {
6136
+ useEffect3(() => {
4932
6137
  if (!isEditMode) return;
4933
6138
  const useFixtures = shouldUseDevFixtures();
4934
6139
  if (useFixtures) {
@@ -4952,21 +6157,27 @@ function OhhwellsBridge() {
4952
6157
  if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
4953
6158
  return () => window.removeEventListener("message", onSitePages);
4954
6159
  }, [isEditMode, postToParent]);
4955
- useEffect2(() => {
6160
+ useEffect3(() => {
4956
6161
  if (!isEditMode || shouldUseDevFixtures()) return;
4957
6162
  void loadAllSectionsManifest().then((manifest) => {
4958
6163
  if (Object.keys(manifest).length === 0) return;
4959
6164
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
4960
6165
  });
4961
6166
  }, [isEditMode]);
4962
- useEffect2(() => {
6167
+ useEffect3(() => {
4963
6168
  const update = () => {
4964
- const el = activeElRef.current;
6169
+ const el = activeElRef.current ?? selectedElRef.current;
4965
6170
  if (el) setToolbarRect(el.getBoundingClientRect());
4966
6171
  setToggleState((prev) => {
4967
6172
  if (!prev || !activeStateElRef.current) return prev;
4968
6173
  return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
4969
6174
  });
6175
+ setSectionGap((prev) => {
6176
+ if (!prev) return prev;
6177
+ const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
6178
+ if (!anchor) return prev;
6179
+ return { ...prev, y: anchor.getBoundingClientRect().bottom };
6180
+ });
4970
6181
  };
4971
6182
  const vvp = window.visualViewport;
4972
6183
  if (!vvp) return;
@@ -4980,6 +6191,29 @@ function OhhwellsBridge() {
4980
6191
  const refreshStateRules = useCallback2(() => {
4981
6192
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
4982
6193
  }, []);
6194
+ const processConfigRequest = useCallback2((insertAfterVal) => {
6195
+ const tracker = getSectionsTracker();
6196
+ let entries = [];
6197
+ try {
6198
+ entries = JSON.parse(tracker.textContent || "[]");
6199
+ } catch {
6200
+ }
6201
+ const path = window.location.pathname;
6202
+ const entry = entries.find(
6203
+ (e) => e.type === "scheduling" && e.insertAfter === insertAfterVal && (!e.pagePath || e.pagePath === path)
6204
+ );
6205
+ if (entry) {
6206
+ window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: entry.scheduleId ?? null }, "*");
6207
+ return;
6208
+ }
6209
+ const newEntry = { type: "scheduling", insertAfter: insertAfterVal, pagePath: path };
6210
+ entries.push(newEntry);
6211
+ tracker.textContent = JSON.stringify(entries);
6212
+ if (isEditMode) {
6213
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
6214
+ }
6215
+ window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6216
+ }, [isEditMode]);
4983
6217
  const deactivate = useCallback2(() => {
4984
6218
  const el = activeElRef.current;
4985
6219
  if (!el) return;
@@ -5000,19 +6234,41 @@ function OhhwellsBridge() {
5000
6234
  }
5001
6235
  el.removeAttribute("contenteditable");
5002
6236
  activeElRef.current = null;
5003
- setToolbarRect(null);
6237
+ if (!selectedElRef.current) {
6238
+ setToolbarRect(null);
6239
+ setToolbarVariant("none");
6240
+ }
5004
6241
  setMaxBadge(null);
5005
6242
  setActiveCommands(/* @__PURE__ */ new Set());
5006
6243
  setToolbarShowEditLink(false);
5007
6244
  postToParent({ type: "ow:exit-edit" });
5008
6245
  }, [postToParent]);
6246
+ const deselect = useCallback2(() => {
6247
+ selectedElRef.current = null;
6248
+ if (!activeElRef.current) {
6249
+ setToolbarRect(null);
6250
+ setToolbarVariant("none");
6251
+ }
6252
+ }, []);
6253
+ const select = useCallback2((anchor) => {
6254
+ if (!isNavbarButton(anchor)) return;
6255
+ if (activeElRef.current) deactivate();
6256
+ selectedElRef.current = anchor;
6257
+ clearHrefKeyHover(anchor);
6258
+ setToolbarVariant("link-action");
6259
+ setToolbarRect(anchor.getBoundingClientRect());
6260
+ setToolbarShowEditLink(false);
6261
+ setActiveCommands(/* @__PURE__ */ new Set());
6262
+ }, [deactivate]);
5009
6263
  const activate = useCallback2((el) => {
5010
6264
  if (activeElRef.current === el) return;
6265
+ selectedElRef.current = null;
5011
6266
  deactivate();
5012
6267
  if (hoveredImageRef.current) {
5013
6268
  hoveredImageRef.current = null;
5014
6269
  postToParentRef.current({ type: "ow:image-unhover" });
5015
6270
  }
6271
+ setToolbarVariant("rich-text");
5016
6272
  el.setAttribute("contenteditable", "true");
5017
6273
  el.removeAttribute("data-ohw-hovered");
5018
6274
  activeElRef.current = el;
@@ -5025,6 +6281,8 @@ function OhhwellsBridge() {
5025
6281
  }, [deactivate, postToParent]);
5026
6282
  activateRef.current = activate;
5027
6283
  deactivateRef.current = deactivate;
6284
+ selectRef.current = select;
6285
+ deselectRef.current = deselect;
5028
6286
  useLayoutEffect(() => {
5029
6287
  if (!subdomain || isEditMode) {
5030
6288
  setFetchState("done");
@@ -5033,6 +6291,7 @@ function OhhwellsBridge() {
5033
6291
  const applyContent = (content) => {
5034
6292
  const imageLoads = [];
5035
6293
  for (const [key, val] of Object.entries(content)) {
6294
+ if (key === "__ohw_sections") continue;
5036
6295
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
5037
6296
  if (el.dataset.ohwEditable === "image") {
5038
6297
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -5054,6 +6313,9 @@ function OhhwellsBridge() {
5054
6313
  });
5055
6314
  applyLinkByKey(key, val);
5056
6315
  }
6316
+ initSectionsFromContent(content, true);
6317
+ sectionsLoadedRef.current = true;
6318
+ pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
5057
6319
  return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
5058
6320
  }) : Promise.resolve();
5059
6321
  };
@@ -5078,12 +6340,15 @@ function OhhwellsBridge() {
5078
6340
  cancelled = true;
5079
6341
  };
5080
6342
  }, [subdomain, isEditMode, pathname]);
5081
- useEffect2(() => {
6343
+ useEffect3(() => {
5082
6344
  if (!subdomain || isEditMode) return;
6345
+ let debounceTimer = null;
5083
6346
  const applyFromCache = () => {
5084
6347
  const content = contentCache.get(subdomain);
5085
6348
  if (!content) return;
6349
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
5086
6350
  for (const [key, val] of Object.entries(content)) {
6351
+ if (key === "__ohw_sections") continue;
5087
6352
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
5088
6353
  if (el.dataset.ohwEditable === "image") {
5089
6354
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -5100,21 +6365,34 @@ function OhhwellsBridge() {
5100
6365
  applyLinkByKey(key, val);
5101
6366
  }
5102
6367
  };
5103
- applyFromCache();
5104
- const observer = new MutationObserver(applyFromCache);
6368
+ const scheduleApply = () => {
6369
+ if (debounceTimer) clearTimeout(debounceTimer);
6370
+ debounceTimer = setTimeout(applyFromCache, 150);
6371
+ };
6372
+ scheduleApply();
6373
+ const observer = new MutationObserver(scheduleApply);
5105
6374
  observer.observe(document.body, { childList: true, subtree: true });
5106
- return () => observer.disconnect();
5107
- }, [subdomain, isEditMode]);
6375
+ return () => {
6376
+ observer.disconnect();
6377
+ if (debounceTimer) clearTimeout(debounceTimer);
6378
+ };
6379
+ }, [subdomain, isEditMode, pathname]);
5108
6380
  useLayoutEffect(() => {
5109
6381
  const el = document.getElementById("ohw-loader");
5110
6382
  if (!el) return;
5111
6383
  const visible = Boolean(subdomain) && fetchState !== "done";
5112
6384
  el.style.display = visible ? "flex" : "none";
5113
6385
  }, [subdomain, fetchState]);
5114
- useEffect2(() => {
6386
+ useEffect3(() => {
5115
6387
  postToParent({ type: "ow:navigation", path: pathname });
5116
6388
  }, [pathname, postToParent]);
5117
- useEffect2(() => {
6389
+ useEffect3(() => {
6390
+ if (!isEditMode) return;
6391
+ setLinkPopover(null);
6392
+ deselectRef.current();
6393
+ deactivateRef.current();
6394
+ }, [pathname, isEditMode]);
6395
+ useEffect3(() => {
5118
6396
  if (!isEditMode) return;
5119
6397
  const measure = () => {
5120
6398
  const h = document.body.scrollHeight;
@@ -5138,7 +6416,7 @@ function OhhwellsBridge() {
5138
6416
  window.removeEventListener("resize", handleResize);
5139
6417
  };
5140
6418
  }, [pathname, isEditMode, postToParent]);
5141
- useEffect2(() => {
6419
+ useEffect3(() => {
5142
6420
  if (!subdomainFromQuery || isEditMode) return;
5143
6421
  const handleClick = (e) => {
5144
6422
  const anchor = e.target.closest("a");
@@ -5154,7 +6432,7 @@ function OhhwellsBridge() {
5154
6432
  document.addEventListener("click", handleClick, true);
5155
6433
  return () => document.removeEventListener("click", handleClick, true);
5156
6434
  }, [subdomainFromQuery, isEditMode, router]);
5157
- useEffect2(() => {
6435
+ useEffect3(() => {
5158
6436
  if (!isEditMode) {
5159
6437
  editStylesRef.current?.base.remove();
5160
6438
  editStylesRef.current?.forceHover.remove();
@@ -5224,12 +6502,12 @@ function OhhwellsBridge() {
5224
6502
  if (editable.dataset.ohwEditable === "link") {
5225
6503
  e.preventDefault();
5226
6504
  e.stopPropagation();
6505
+ deselectRef.current();
5227
6506
  deactivateRef.current();
5228
6507
  bumpLinkPopoverGrace();
5229
6508
  setLinkPopoverRef.current({
5230
6509
  key: editable.dataset.ohwKey ?? "",
5231
- target: getLinkHref(editable),
5232
- rect: editable.getBoundingClientRect()
6510
+ target: getLinkHref(editable)
5233
6511
  });
5234
6512
  return;
5235
6513
  }
@@ -5239,11 +6517,31 @@ function OhhwellsBridge() {
5239
6517
  postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
5240
6518
  return;
5241
6519
  }
6520
+ const hrefCtx = getHrefKeyFromElement(editable);
6521
+ if (hrefCtx && isNavbarButton(hrefCtx.anchor)) {
6522
+ e.preventDefault();
6523
+ e.stopPropagation();
6524
+ const { anchor: anchor2 } = hrefCtx;
6525
+ if (selectedElRef.current === anchor2) {
6526
+ activateRef.current(editable);
6527
+ return;
6528
+ }
6529
+ selectRef.current(anchor2);
6530
+ return;
6531
+ }
5242
6532
  e.preventDefault();
5243
6533
  e.stopPropagation();
5244
6534
  activateRef.current(editable);
5245
6535
  return;
5246
6536
  }
6537
+ const hrefAnchor = target.closest("[data-ohw-href-key]");
6538
+ if (hrefAnchor && isNavbarButton(hrefAnchor)) {
6539
+ e.preventDefault();
6540
+ e.stopPropagation();
6541
+ if (selectedElRef.current === hrefAnchor) return;
6542
+ selectRef.current(hrefAnchor);
6543
+ return;
6544
+ }
5247
6545
  if (activeElRef.current) {
5248
6546
  const sel = window.getSelection();
5249
6547
  if (sel && !sel.isCollapsed) {
@@ -5258,15 +6556,22 @@ function OhhwellsBridge() {
5258
6556
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
5259
6557
  if (hrefCtx && (hrefCtx.anchor === target || hrefCtx.anchor.contains(target))) return;
5260
6558
  }
6559
+ if (linkPopoverOpenRef.current && selectedElRef.current) {
6560
+ const selected = selectedElRef.current;
6561
+ if (selected === target || selected.contains(target)) return;
6562
+ }
5261
6563
  if (linkPopoverOpenRef.current) {
5262
6564
  setLinkPopoverRef.current(null);
5263
6565
  return;
5264
6566
  }
6567
+ deselectRef.current();
5265
6568
  deactivateRef.current();
5266
6569
  };
5267
6570
  const handleMouseOver = (e) => {
5268
6571
  const editable = e.target.closest("[data-ohw-editable]");
5269
6572
  if (!editable) return;
6573
+ const selected = selectedElRef.current;
6574
+ if (selected && (selected === editable || selected.contains(editable))) return;
5270
6575
  if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image" && !editable.hasAttribute("contenteditable")) {
5271
6576
  editable.setAttribute("data-ohw-hovered", "");
5272
6577
  }
@@ -5402,7 +6707,7 @@ function OhhwellsBridge() {
5402
6707
  }
5403
6708
  return;
5404
6709
  }
5405
- if (topEl?.closest("[data-ohw-link-popover-root]") || topEl?.closest("[data-ohw-link-modal-root]")) {
6710
+ if (topEl?.closest("[data-ohw-link-popover-root]") || topEl?.closest("[data-ohw-link-modal-root]") || topEl?.closest("[data-ohw-link-page-dropdown]")) {
5406
6711
  if (hoveredImageRef.current) {
5407
6712
  hoveredImageRef.current = null;
5408
6713
  hoveredImageHasTextOverlapRef.current = false;
@@ -5455,6 +6760,15 @@ function OhhwellsBridge() {
5455
6760
  textEditable.setAttribute("data-ohw-hovered", "");
5456
6761
  return;
5457
6762
  }
6763
+ if (hoveredGapRef.current) {
6764
+ if (hoveredImageRef.current) {
6765
+ hoveredImageRef.current = null;
6766
+ resumeAnimTracks();
6767
+ postToParentRef.current({ type: "ow:image-unhover" });
6768
+ }
6769
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
6770
+ return;
6771
+ }
5458
6772
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
5459
6773
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
5460
6774
  hoveredImageRef.current = imgEl;
@@ -5520,8 +6834,37 @@ function OhhwellsBridge() {
5520
6834
  }
5521
6835
  }
5522
6836
  };
6837
+ const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
6838
+ if (linkPopoverOpenRef.current) {
6839
+ if (hoveredGapRef.current) {
6840
+ hoveredGapRef.current = null;
6841
+ setSectionGap(null);
6842
+ }
6843
+ return;
6844
+ }
6845
+ const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
6846
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
6847
+ const ZONE = 20;
6848
+ for (let i = 0; i < sections.length; i++) {
6849
+ const a = sections[i];
6850
+ const b = sections[i + 1] ?? null;
6851
+ const boundaryY = a.getBoundingClientRect().bottom;
6852
+ if (Math.abs(y - boundaryY) <= ZONE) {
6853
+ const insertAfter = a.dataset.ohwSection ?? "";
6854
+ const insertBefore = b?.dataset.ohwSection ?? null;
6855
+ hoveredGapRef.current = { insertAfter, insertBefore };
6856
+ setSectionGap({ insertAfter, insertBefore, y: boundaryY });
6857
+ return;
6858
+ }
6859
+ }
6860
+ if (hoveredGapRef.current) {
6861
+ hoveredGapRef.current = null;
6862
+ setSectionGap(null);
6863
+ }
6864
+ };
5523
6865
  const handleMouseMove = (e) => {
5524
6866
  const { clientX, clientY } = e;
6867
+ probeSectionGapAt(clientX, clientY);
5525
6868
  probeImageAt(clientX, clientY);
5526
6869
  probeHoverCardsAt(clientX, clientY);
5527
6870
  };
@@ -5529,6 +6872,7 @@ function OhhwellsBridge() {
5529
6872
  if (e.data?.type !== "ow:pointer-sync") return;
5530
6873
  const { clientX, clientY } = e.data;
5531
6874
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
6875
+ probeSectionGapAt(clientX, clientY);
5532
6876
  probeImageAt(clientX, clientY);
5533
6877
  probeHoverCardsAt(clientX, clientY);
5534
6878
  };
@@ -5686,7 +7030,12 @@ function OhhwellsBridge() {
5686
7030
  if (e.data?.type !== "ow:hydrate") return;
5687
7031
  const content = e.data.content;
5688
7032
  if (!content) return;
7033
+ let sectionsJson = null;
5689
7034
  for (const [key, val] of Object.entries(content)) {
7035
+ if (key === "__ohw_sections") {
7036
+ sectionsJson = val;
7037
+ continue;
7038
+ }
5690
7039
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
5691
7040
  if (el.dataset.ohwEditable === "image") {
5692
7041
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -5701,6 +7050,11 @@ function OhhwellsBridge() {
5701
7050
  });
5702
7051
  applyLinkByKey(key, val);
5703
7052
  }
7053
+ if (sectionsJson) {
7054
+ initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
7055
+ sectionsLoadedRef.current = true;
7056
+ pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7057
+ }
5704
7058
  postToParentRef.current({ type: "ow:hydrate-done" });
5705
7059
  };
5706
7060
  window.addEventListener("message", handleHydrate);
@@ -5711,6 +7065,7 @@ function OhhwellsBridge() {
5711
7065
  setLinkPopoverRef.current(null);
5712
7066
  return;
5713
7067
  }
7068
+ deselectRef.current();
5714
7069
  deactivateRef.current();
5715
7070
  };
5716
7071
  window.addEventListener("message", handleDeactivate);
@@ -5719,6 +7074,10 @@ function OhhwellsBridge() {
5719
7074
  setLinkPopoverRef.current(null);
5720
7075
  return;
5721
7076
  }
7077
+ if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7078
+ deselectRef.current();
7079
+ return;
7080
+ }
5722
7081
  if (e.key !== "Escape") return;
5723
7082
  const el = activeElRef.current;
5724
7083
  if (el && originalContentRef.current !== null) {
@@ -5728,13 +7087,16 @@ function OhhwellsBridge() {
5728
7087
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: originalContentRef.current }] });
5729
7088
  }
5730
7089
  }
7090
+ deselectRef.current();
5731
7091
  deactivateRef.current();
5732
7092
  };
5733
7093
  const handleScroll = () => {
5734
- if (activeElRef.current) {
5735
- const r2 = activeElRef.current.getBoundingClientRect();
7094
+ const focusEl = activeElRef.current ?? selectedElRef.current;
7095
+ if (focusEl) {
7096
+ const r2 = focusEl.getBoundingClientRect();
5736
7097
  applyToolbarPos(r2);
5737
- setMaxBadge((prev) => prev ? { ...prev, rect: r2 } : null);
7098
+ setToolbarRect(r2);
7099
+ setMaxBadge((prev) => prev && activeElRef.current ? { ...prev, rect: r2 } : prev);
5738
7100
  }
5739
7101
  if (activeStateElRef.current) {
5740
7102
  const rect = activeStateElRef.current.getBoundingClientRect();
@@ -5753,7 +7115,63 @@ function OhhwellsBridge() {
5753
7115
  };
5754
7116
  const handleSave = (e) => {
5755
7117
  if (e.data?.type !== "ow:save") return;
5756
- postToParentRef.current({ type: "ow:save-result", nodes: collectEditableNodes() });
7118
+ const nodes = collectEditableNodes();
7119
+ const tracker = document.querySelector("[data-ohw-sections-tracker]");
7120
+ if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
7121
+ postToParentRef.current({ type: "ow:save-result", nodes });
7122
+ };
7123
+ const handleInsertSection = (e) => {
7124
+ if (e.data?.type !== "ow:insert-section") return;
7125
+ const { widgetType, insertAfter, insertBefore } = e.data;
7126
+ if (widgetType !== "scheduling") return;
7127
+ const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
7128
+ if (inserted) {
7129
+ const tracker = getSectionsTracker();
7130
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
7131
+ const h = document.documentElement.scrollHeight;
7132
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
7133
+ }
7134
+ };
7135
+ const handleSwitchSchedule = (e) => {
7136
+ if (e.data?.type !== "ow:switch-schedule") return;
7137
+ const scheduleId = e.data.scheduleId;
7138
+ const insertAfter = e.data.insertAfter;
7139
+ if (!scheduleId || !insertAfter) return;
7140
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
7141
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
7142
+ };
7143
+ const handleScheduleLinked = (e) => {
7144
+ if (e.data?.type !== "ow:schedule-linked") return;
7145
+ const scheduleId = e.data.scheduleId;
7146
+ const insertAfter = e.data.insertAfter;
7147
+ if (!scheduleId || !insertAfter) return;
7148
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
7149
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
7150
+ };
7151
+ const handleClearSchedulingWidget = (e) => {
7152
+ if (e.data?.type !== "ow:clear-scheduling-widget") return;
7153
+ const insertAfter = e.data.insertAfter;
7154
+ if (!insertAfter) return;
7155
+ const text = updateSectionScheduleId(insertAfter, null);
7156
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
7157
+ };
7158
+ const handleRemoveSchedulingSection = (e) => {
7159
+ if (e.data?.type !== "ow:remove-scheduling-section") return;
7160
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
7161
+ el.remove();
7162
+ });
7163
+ const tracker = getSectionsTracker();
7164
+ let sections = [];
7165
+ try {
7166
+ sections = JSON.parse(tracker.textContent || "[]");
7167
+ } catch {
7168
+ }
7169
+ const currentPath = window.location.pathname;
7170
+ const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
7171
+ tracker.textContent = JSON.stringify(updated);
7172
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
7173
+ const h = document.documentElement.scrollHeight;
7174
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
5757
7175
  };
5758
7176
  const handleSelectionChange = () => {
5759
7177
  if (!activeElRef.current) return;
@@ -5803,8 +7221,9 @@ function OhhwellsBridge() {
5803
7221
  };
5804
7222
  const applyToolbarPos = (rect) => {
5805
7223
  const ps = parentScrollRef.current;
7224
+ const approxW = toolbarVariantRef.current === "link-action" ? 120 : 330;
5806
7225
  if (toolbarElRef.current) {
5807
- const { top, left, transform } = calcToolbarPos(rect, ps);
7226
+ const { top, left, transform } = calcToolbarPos(rect, ps, approxW);
5808
7227
  toolbarElRef.current.style.top = `${top}px`;
5809
7228
  toolbarElRef.current.style.left = `${left}px`;
5810
7229
  toolbarElRef.current.style.transform = transform;
@@ -5819,7 +7238,11 @@ function OhhwellsBridge() {
5819
7238
  if (e.data?.type !== "ow:parent-scroll") return;
5820
7239
  const { iframeOffsetTop, headerH, canvasH } = e.data;
5821
7240
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
5822
- if (activeElRef.current) applyToolbarPos(activeElRef.current.getBoundingClientRect());
7241
+ if (visibleViewportRef.current) {
7242
+ applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
7243
+ }
7244
+ const focusEl = activeElRef.current ?? selectedElRef.current;
7245
+ if (focusEl) applyToolbarPos(focusEl.getBoundingClientRect());
5823
7246
  };
5824
7247
  const handleClickAt = (e) => {
5825
7248
  if (e.data?.type !== "ow:click-at") return;
@@ -5858,12 +7281,23 @@ function OhhwellsBridge() {
5858
7281
  deactivateRef.current();
5859
7282
  };
5860
7283
  window.addEventListener("message", handleSave);
7284
+ window.addEventListener("message", handleInsertSection);
7285
+ window.addEventListener("message", handleSwitchSchedule);
7286
+ window.addEventListener("message", handleScheduleLinked);
7287
+ window.addEventListener("message", handleClearSchedulingWidget);
7288
+ window.addEventListener("message", handleRemoveSchedulingSection);
5861
7289
  window.addEventListener("message", handleImageUrl);
5862
7290
  window.addEventListener("message", handleAnimLock);
5863
7291
  window.addEventListener("message", handleCanvasHeight);
5864
7292
  window.addEventListener("message", handleParentScroll);
5865
7293
  window.addEventListener("message", handlePointerSync);
5866
7294
  window.addEventListener("message", handleClickAt);
7295
+ const handleViewportResize = () => {
7296
+ if (visibleViewportRef.current) {
7297
+ applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
7298
+ }
7299
+ };
7300
+ window.addEventListener("resize", handleViewportResize, { passive: true });
5867
7301
  document.addEventListener("click", handleClick, true);
5868
7302
  document.addEventListener("paste", handlePaste, true);
5869
7303
  document.addEventListener("input", handleInput, true);
@@ -5892,10 +7326,16 @@ function OhhwellsBridge() {
5892
7326
  document.removeEventListener("selectionchange", handleSelectionChange);
5893
7327
  window.removeEventListener("scroll", handleScroll, true);
5894
7328
  window.removeEventListener("message", handleSave);
7329
+ window.removeEventListener("message", handleInsertSection);
7330
+ window.removeEventListener("message", handleSwitchSchedule);
7331
+ window.removeEventListener("message", handleScheduleLinked);
7332
+ window.removeEventListener("message", handleClearSchedulingWidget);
7333
+ window.removeEventListener("message", handleRemoveSchedulingSection);
5895
7334
  window.removeEventListener("message", handleImageUrl);
5896
7335
  window.removeEventListener("message", handleAnimLock);
5897
7336
  window.removeEventListener("message", handleCanvasHeight);
5898
7337
  window.removeEventListener("message", handleParentScroll);
7338
+ window.removeEventListener("resize", handleViewportResize);
5899
7339
  window.removeEventListener("message", handlePointerSync);
5900
7340
  window.removeEventListener("message", handleClickAt);
5901
7341
  window.removeEventListener("message", handleHydrate);
@@ -5906,7 +7346,23 @@ function OhhwellsBridge() {
5906
7346
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
5907
7347
  };
5908
7348
  }, [isEditMode, refreshStateRules]);
5909
- useEffect2(() => {
7349
+ useEffect3(() => {
7350
+ const handler = (e) => {
7351
+ if (e.data?.type !== "ow:request-schedule-config") return;
7352
+ const insertAfterVal = e.data.insertAfter;
7353
+ if (!insertAfterVal) return;
7354
+ if (!sectionsLoadedRef.current) {
7355
+ if (!pendingScheduleConfigRequests.current.includes(insertAfterVal)) {
7356
+ pendingScheduleConfigRequests.current.push(insertAfterVal);
7357
+ }
7358
+ return;
7359
+ }
7360
+ processConfigRequest(insertAfterVal);
7361
+ };
7362
+ window.addEventListener("message", handler);
7363
+ return () => window.removeEventListener("message", handler);
7364
+ }, [processConfigRequest]);
7365
+ useEffect3(() => {
5910
7366
  if (!isEditMode) return;
5911
7367
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
5912
7368
  el.removeAttribute("data-ohw-active-state");
@@ -5935,7 +7391,7 @@ function OhhwellsBridge() {
5935
7391
  clearTimeout(timer);
5936
7392
  };
5937
7393
  }, [pathname, isEditMode, refreshStateRules, postToParent]);
5938
- useEffect2(() => {
7394
+ useEffect3(() => {
5939
7395
  scrollToHashSectionWhenReady();
5940
7396
  const onHashChange = () => scrollToHashSectionWhenReady();
5941
7397
  window.addEventListener("hashchange", onHashChange);
@@ -5967,10 +7423,22 @@ function OhhwellsBridge() {
5967
7423
  bumpLinkPopoverGrace();
5968
7424
  setLinkPopover({
5969
7425
  key: hrefCtx.key,
5970
- target: getLinkHref(hrefCtx.anchor),
5971
- rect: hrefCtx.anchor.getBoundingClientRect()
7426
+ target: getLinkHref(hrefCtx.anchor)
5972
7427
  });
5973
- }, []);
7428
+ deactivate();
7429
+ }, [deactivate]);
7430
+ const openLinkPopoverForSelected = useCallback2(() => {
7431
+ const anchor = selectedElRef.current;
7432
+ if (!anchor) return;
7433
+ const key = anchor.getAttribute("data-ohw-href-key");
7434
+ if (!key) return;
7435
+ bumpLinkPopoverGrace();
7436
+ setLinkPopover({
7437
+ key,
7438
+ target: getLinkHref(anchor)
7439
+ });
7440
+ deselect();
7441
+ }, [deselect]);
5974
7442
  const handleLinkPopoverSubmit = useCallback2(
5975
7443
  (target) => {
5976
7444
  if (!linkPopover) return;
@@ -5985,12 +7453,14 @@ function OhhwellsBridge() {
5985
7453
  const currentSections = sectionsByPath[pathname] ?? [];
5986
7454
  linkPopoverOpenRef.current = linkPopover !== null;
5987
7455
  return bridgeRoot ? createPortal(
5988
- /* @__PURE__ */ jsxs6(Fragment2, { children: [
5989
- toolbarRect && /* @__PURE__ */ jsxs6(Fragment2, { children: [
5990
- /* @__PURE__ */ jsx13(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
5991
- /* @__PURE__ */ jsx13(
7456
+ /* @__PURE__ */ jsxs9(Fragment3, { children: [
7457
+ /* @__PURE__ */ jsx16("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
7458
+ toolbarRect && /* @__PURE__ */ jsxs9(Fragment3, { children: [
7459
+ /* @__PURE__ */ jsx16(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
7460
+ toolbarVariant === "rich-text" && /* @__PURE__ */ jsx16(
5992
7461
  FloatingToolbar,
5993
7462
  {
7463
+ variant: "rich-text",
5994
7464
  rect: toolbarRect,
5995
7465
  parentScroll: parentScrollRef.current,
5996
7466
  elRef: toolbarElRef,
@@ -5999,9 +7469,21 @@ function OhhwellsBridge() {
5999
7469
  showEditLink,
6000
7470
  onEditLink: openLinkPopoverForActive
6001
7471
  }
7472
+ ),
7473
+ toolbarVariant === "link-action" && /* @__PURE__ */ jsx16(
7474
+ FloatingToolbar,
7475
+ {
7476
+ variant: "link-action",
7477
+ rect: toolbarRect,
7478
+ parentScroll: parentScrollRef.current,
7479
+ elRef: toolbarElRef,
7480
+ onCommand: handleCommand,
7481
+ activeCommands,
7482
+ onEditLink: openLinkPopoverForSelected
7483
+ }
6002
7484
  )
6003
7485
  ] }),
6004
- maxBadge && /* @__PURE__ */ jsxs6(
7486
+ maxBadge && /* @__PURE__ */ jsxs9(
6005
7487
  "div",
6006
7488
  {
6007
7489
  "data-ohw-max-badge": "",
@@ -6027,7 +7509,7 @@ function OhhwellsBridge() {
6027
7509
  ]
6028
7510
  }
6029
7511
  ),
6030
- toggleState && /* @__PURE__ */ jsx13(
7512
+ toggleState && /* @__PURE__ */ jsx16(
6031
7513
  StateToggle,
6032
7514
  {
6033
7515
  rect: toggleState.rect,
@@ -6036,12 +7518,36 @@ function OhhwellsBridge() {
6036
7518
  onStateChange: handleStateChange
6037
7519
  }
6038
7520
  ),
6039
- linkPopover ? /* @__PURE__ */ jsx13(
7521
+ sectionGap && /* @__PURE__ */ jsxs9(
7522
+ "div",
7523
+ {
7524
+ "data-ohw-section-insert-line": "",
7525
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
7526
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
7527
+ children: [
7528
+ /* @__PURE__ */ jsx16("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
7529
+ /* @__PURE__ */ jsx16(
7530
+ Badge,
7531
+ {
7532
+ 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",
7533
+ onClick: () => {
7534
+ window.parent.postMessage(
7535
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
7536
+ "*"
7537
+ );
7538
+ },
7539
+ children: "Add Section"
7540
+ }
7541
+ ),
7542
+ /* @__PURE__ */ jsx16("div", { className: "flex-1 bg-primary", style: { height: 3 } })
7543
+ ]
7544
+ }
7545
+ ),
7546
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ jsx16(
6040
7547
  LinkPopover,
6041
7548
  {
6042
- rect: linkPopover.rect,
6043
- parentScroll: parentScrollRef.current,
6044
7549
  panelRef: linkPopoverPanelRef,
7550
+ portalContainer: dialogPortalContainer,
6045
7551
  open: true,
6046
7552
  mode: "edit",
6047
7553
  pages: sitePages,
@@ -6050,7 +7556,8 @@ function OhhwellsBridge() {
6050
7556
  initialTarget: linkPopover.target,
6051
7557
  onClose: closeLinkPopover,
6052
7558
  onSubmit: handleLinkPopoverSubmit
6053
- }
7559
+ },
7560
+ `${linkPopover.key}-${pathname}`
6054
7561
  ) : null
6055
7562
  ] }),
6056
7563
  bridgeRoot
@@ -6060,6 +7567,7 @@ export {
6060
7567
  LinkEditorPanel,
6061
7568
  LinkPopover,
6062
7569
  OhhwellsBridge,
7570
+ SchedulingWidget,
6063
7571
  Toggle,
6064
7572
  ToggleGroup,
6065
7573
  ToggleGroupItem,