@ohhwells/bridge 0.1.29 → 0.1.31-next.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  LinkEditorPanel: () => LinkEditorPanel,
35
35
  LinkPopover: () => LinkPopover,
36
36
  OhhwellsBridge: () => OhhwellsBridge,
37
+ SchedulingWidget: () => SchedulingWidget,
37
38
  Toggle: () => Toggle,
38
39
  ToggleGroup: () => ToggleGroup,
39
40
  ToggleGroupItem: () => ToggleGroupItem,
@@ -48,10 +49,762 @@ __export(index_exports, {
48
49
  module.exports = __toCommonJS(index_exports);
49
50
 
50
51
  // src/OhhwellsBridge.tsx
51
- var import_react3 = __toESM(require("react"), 1);
52
+ var import_react5 = __toESM(require("react"), 1);
53
+ var import_client = require("react-dom/client");
54
+ var import_react_dom = require("react-dom");
55
+
56
+ // src/ui/SchedulingWidget.tsx
57
+ var import_react2 = require("react");
58
+
59
+ // src/ui/EmailCaptureModal.tsx
60
+ var import_react = require("react");
61
+ var import_radix_ui = require("radix-ui");
62
+ var import_jsx_runtime = require("react/jsx-runtime");
63
+ function Spinner() {
64
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
65
+ "svg",
66
+ {
67
+ width: "32",
68
+ height: "32",
69
+ viewBox: "0 0 32 32",
70
+ fill: "none",
71
+ style: { animation: "ohw-spin 0.75s linear infinite" },
72
+ children: [
73
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes ohw-spin { to { transform: rotate(360deg); } }` }),
74
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "16", r: "12", stroke: "#e5e7eb", strokeWidth: "3" }),
75
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
76
+ "path",
77
+ {
78
+ d: "M16 4a12 12 0 0 1 12 12",
79
+ stroke: "#3b82f6",
80
+ strokeWidth: "3",
81
+ strokeLinecap: "round"
82
+ }
83
+ )
84
+ ]
85
+ }
86
+ );
87
+ }
88
+ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
89
+ const [email, setEmail] = (0, import_react.useState)("");
90
+ const [submittedEmail, setSubmittedEmail] = (0, import_react.useState)("");
91
+ const [loading, setLoading] = (0, import_react.useState)(false);
92
+ const [success, setSuccess] = (0, import_react.useState)(false);
93
+ const [error, setError] = (0, import_react.useState)(null);
94
+ const isBook = title === "Confirm your spot";
95
+ const handleSubmit = async () => {
96
+ if (!email.trim()) return;
97
+ setLoading(true);
98
+ setError(null);
99
+ try {
100
+ await onSubmit(email.trim());
101
+ setSubmittedEmail(email.trim());
102
+ setSuccess(true);
103
+ } catch (e) {
104
+ setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
105
+ } finally {
106
+ setLoading(false);
107
+ }
108
+ };
109
+ const handleKeyDown = (e) => {
110
+ if (e.key === "Enter" && !loading) handleSubmit();
111
+ };
112
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_radix_ui.Dialog.Root, { open: true, onOpenChange: (open) => {
113
+ if (!open) onClose();
114
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_radix_ui.Dialog.Portal, { children: [
115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
116
+ import_radix_ui.Dialog.Overlay,
117
+ {
118
+ className: "fixed inset-0 z-50",
119
+ style: { background: "rgba(0,0,0,0.45)" }
120
+ }
121
+ ),
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
123
+ import_radix_ui.Dialog.Content,
124
+ {
125
+ className: "fixed left-1/2 top-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 bg-white rounded-xl shadow-xl outline-none font-body box-border overflow-hidden",
126
+ style: { maxWidth: 400 },
127
+ children: [
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between gap-3 p-6", children: [
129
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1", children: [
130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
131
+ import_radix_ui.Dialog.Title,
132
+ {
133
+ className: "font-body text-xl font-bold m-0 leading-tight",
134
+ style: { color: "var(--color-dark,#200C02)" },
135
+ children: success ? isBook ? "Reservation confirmed!" : "You're on the waitlist!" : title
136
+ }
137
+ ),
138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
139
+ import_radix_ui.Dialog.Description,
140
+ {
141
+ className: "font-body text-sm m-0",
142
+ style: { color: "#6B7280" },
143
+ children: success ? isBook ? `A confirmation email has been sent to ${submittedEmail}.` : "We'll let you know via email if someone cancels." : subtitle
144
+ }
145
+ )
146
+ ] }),
147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
148
+ import_radix_ui.Dialog.Close,
149
+ {
150
+ className: "shrink-0 w-7 h-7 flex items-center justify-center rounded-full bg-transparent border-none cursor-pointer mt-0.5 absolute top-2.5 right-1.5",
151
+ style: { color: "#6B7280" },
152
+ "aria-label": "Close",
153
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
156
+ ] })
157
+ }
158
+ )
159
+ ] }),
160
+ !success && (loading ? (
161
+ /* Loading state — full body replaced */
162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center justify-center gap-3 px-7 py-12", children: [
163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-sm m-0", style: { color: "#6B7280" }, children: "Please wait" })
165
+ ] })
166
+ ) : (
167
+ /* Email form */
168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "px-7 pt-2 pb-6", children: [
169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1.5 mb-8", children: [
170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "font-body text-sm font-medium", style: { color: "var(--color-dark,#200C02)" }, children: "Email" }),
171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
172
+ "input",
173
+ {
174
+ type: "email",
175
+ value: email,
176
+ onChange: (e) => setEmail(e.target.value),
177
+ onKeyDown: handleKeyDown,
178
+ placeholder: "hello@gmail.com",
179
+ autoFocus: true,
180
+ className: "w-full h-10 px-3 rounded-lg border font-body text-sm outline-none box-border",
181
+ style: {
182
+ borderColor: "#E7E5E4",
183
+ background: "#fff",
184
+ color: "var(--color-dark,#200C02)"
185
+ }
186
+ }
187
+ ),
188
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-xs text-red-500 m-0", children: error })
189
+ ] }),
190
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
191
+ "button",
192
+ {
193
+ onClick: handleSubmit,
194
+ disabled: !email.trim(),
195
+ className: "h-9 px-5 rounded-lg font-body text-sm font-semibold cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed",
196
+ style: {
197
+ background: "var(--color-primary,#3D312B)",
198
+ color: "var(--color-light,#FEFFF0)"
199
+ },
200
+ children: "Send"
201
+ }
202
+ ) })
203
+ ] })
204
+ ))
205
+ ]
206
+ }
207
+ )
208
+ ] }) });
209
+ }
210
+
211
+ // src/ui/SchedulingWidget.tsx
212
+ var import_jsx_runtime2 = require("react/jsx-runtime");
213
+ var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
214
+ var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
215
+ function classRunsOnDate(cls, date, type) {
216
+ if (type === "FIXED") {
217
+ const d = String(date.getDate()).padStart(2, "0");
218
+ const m = String(date.getMonth() + 1).padStart(2, "0");
219
+ return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
220
+ }
221
+ return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
222
+ }
223
+ function formatClassTime(cls) {
224
+ let h = parseInt(cls.startTime.hour, 10);
225
+ const m = parseInt(cls.startTime.minutes, 10);
226
+ if (cls.startTime.time === "PM" && h !== 12) h += 12;
227
+ if (cls.startTime.time === "AM" && h === 12) h = 0;
228
+ const endTotal = h * 60 + m + cls.duration;
229
+ const eh = Math.floor(endTotal / 60) % 24;
230
+ const em = endTotal % 60;
231
+ return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
232
+ }
233
+ function getBookingsOnDate(cls, date) {
234
+ if (!cls.bookings?.length) return 0;
235
+ const ds = date.toISOString().split("T")[0];
236
+ return cls.bookings.filter((b) => {
237
+ try {
238
+ return new Date(b.classDate).toISOString().split("T")[0] === ds;
239
+ } catch {
240
+ return false;
241
+ }
242
+ }).length;
243
+ }
244
+ function buildTimezoneLabel(tz) {
245
+ try {
246
+ const now = /* @__PURE__ */ new Date();
247
+ const timeStr = new Intl.DateTimeFormat("en-US", {
248
+ hour: "2-digit",
249
+ minute: "2-digit",
250
+ hour12: false,
251
+ timeZone: tz
252
+ }).format(now);
253
+ const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
254
+ const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
255
+ return `${timeStr} (${offsetPart}) ${city} time`;
256
+ } catch {
257
+ return tz;
258
+ }
259
+ }
260
+ function EmptyState({ inEditor, insertAfter }) {
261
+ const handleAddSchedule = () => {
262
+ if (inEditor) {
263
+ window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
264
+ }
265
+ };
266
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center justify-center gap-6 p-12 bg-[#FAFAF9] border-2 border-dashed border-[#E7E5E4] rounded-[10px] h-[264px] w-full box-border", children: [
267
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-10 h-10 bg-[#F5F5F4] rounded-[10px] flex items-center justify-center shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
268
+ "svg",
269
+ {
270
+ width: "20",
271
+ height: "20",
272
+ viewBox: "0 0 24 24",
273
+ fill: "none",
274
+ stroke: "var(--color-accent, #A89B83)",
275
+ strokeWidth: "1.5",
276
+ strokeLinecap: "round",
277
+ strokeLinejoin: "round",
278
+ children: [
279
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
280
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
281
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
282
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
283
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
284
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
285
+ ]
286
+ }
287
+ ) }),
288
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-2", children: [
289
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
290
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-base font-normal text-center text-(--color-accent,#A89B83) m-0", children: "Add a scheduling widget to get instant bookings." })
291
+ ] }),
292
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
293
+ "button",
294
+ {
295
+ onClick: handleAddSchedule,
296
+ 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",
297
+ children: "Add Schedule"
298
+ }
299
+ )
300
+ ] });
301
+ }
302
+ function LoadingSkeleton() {
303
+ const shimmer = {
304
+ background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
305
+ backgroundSize: "200% 100%",
306
+ animation: "ohw-shimmer 1.5s infinite"
307
+ };
308
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
309
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
310
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex h-[70px] items-stretch", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col justify-between items-center px-1", children: [
311
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
312
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
313
+ ] }, i)) }),
314
+ [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
315
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:hidden", children: [
316
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0", children: [
317
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100px", height: "16px", borderRadius: "4px" } }),
318
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "52px", height: "20px", borderRadius: "999px" } }),
319
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "60px", height: "14px", borderRadius: "4px" } })
320
+ ] }),
321
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2", children: [
322
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "75%", height: "16px", borderRadius: "4px" } }),
323
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "50%", height: "14px", borderRadius: "4px" } }),
324
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100%", height: "44px", borderRadius: "8px" } })
325
+ ] })
326
+ ] }),
327
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hidden sm:flex items-center gap-[60px] py-4", children: [
328
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
329
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2", children: [
330
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
331
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
332
+ ] }),
333
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
334
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
335
+ ] }),
336
+ i < 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
337
+ ] }, i))
338
+ ] });
339
+ }
340
+ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal }) {
341
+ const todayMs = (0, import_react2.useMemo)(() => {
342
+ const d = /* @__PURE__ */ new Date();
343
+ d.setHours(0, 0, 0, 0);
344
+ return d.getTime();
345
+ }, []);
346
+ const scrollRef = (0, import_react2.useRef)(null);
347
+ const [canScrollLeft, setCanScrollLeft] = (0, import_react2.useState)(false);
348
+ const [canScrollRight, setCanScrollRight] = (0, import_react2.useState)(false);
349
+ const updateScrollState = () => {
350
+ const el = scrollRef.current;
351
+ if (!el) return;
352
+ setCanScrollLeft(el.scrollLeft > 0);
353
+ setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
354
+ };
355
+ (0, import_react2.useEffect)(() => {
356
+ const el = scrollRef.current;
357
+ if (!el) return;
358
+ updateScrollState();
359
+ el.addEventListener("scroll", updateScrollState);
360
+ const ro = new ResizeObserver(updateScrollState);
361
+ ro.observe(el);
362
+ return () => {
363
+ el.removeEventListener("scroll", updateScrollState);
364
+ ro.disconnect();
365
+ };
366
+ }, [dates]);
367
+ const scroll = (dir) => {
368
+ scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
369
+ };
370
+ const isDragging = (0, import_react2.useRef)(false);
371
+ const dragStartX = (0, import_react2.useRef)(0);
372
+ const dragStartScrollLeft = (0, import_react2.useRef)(0);
373
+ const onDragStart = (e) => {
374
+ if (!scrollRef.current) return;
375
+ isDragging.current = true;
376
+ dragStartX.current = e.clientX;
377
+ dragStartScrollLeft.current = scrollRef.current.scrollLeft;
378
+ scrollRef.current.style.cursor = "grabbing";
379
+ scrollRef.current.style.userSelect = "none";
380
+ };
381
+ const onDragMove = (e) => {
382
+ if (!isDragging.current || !scrollRef.current) return;
383
+ const delta = e.clientX - dragStartX.current;
384
+ scrollRef.current.scrollLeft = dragStartScrollLeft.current - delta;
385
+ };
386
+ const onDragEnd = () => {
387
+ if (!scrollRef.current) return;
388
+ isDragging.current = false;
389
+ scrollRef.current.style.cursor = "grab";
390
+ scrollRef.current.style.userSelect = "";
391
+ };
392
+ const datesWithClasses = (0, import_react2.useMemo)(
393
+ () => new Set(dates.map(
394
+ (d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
395
+ ).filter((i) => i >= 0)),
396
+ [dates, schedule]
397
+ );
398
+ const selectedDate = dates[selectedIdx];
399
+ const selectedClasses = (0, import_react2.useMemo)(
400
+ () => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
401
+ [schedule, selectedDate]
402
+ );
403
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
404
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative w-full", children: [
405
+ canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
406
+ "button",
407
+ {
408
+ onClick: () => scroll("left"),
409
+ 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",
410
+ style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
411
+ "aria-label": "Scroll left",
412
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "var(--color-dark,#200C02)", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "15 18 9 12 15 6" }) })
413
+ }
414
+ ),
415
+ canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
416
+ "button",
417
+ {
418
+ onClick: () => scroll("right"),
419
+ 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",
420
+ style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
421
+ "aria-label": "Scroll right",
422
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "var(--color-dark,#200C02)", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "9 18 15 12 9 6" }) })
423
+ }
424
+ ),
425
+ canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
426
+ "div",
427
+ {
428
+ className: "sm:hidden absolute left-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
429
+ style: { background: "linear-gradient(to right, var(--color-light,#FEFFF0), transparent)" }
430
+ }
431
+ ),
432
+ canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
433
+ "div",
434
+ {
435
+ className: "sm:hidden absolute right-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
436
+ style: { background: "linear-gradient(to left, var(--color-light,#FEFFF0), transparent)" }
437
+ }
438
+ ),
439
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
440
+ "div",
441
+ {
442
+ ref: scrollRef,
443
+ className: "overflow-x-auto w-full",
444
+ style: { scrollbarWidth: "none", msOverflowStyle: "none", touchAction: "pan-x", cursor: "grab" },
445
+ onMouseDown: onDragStart,
446
+ onMouseMove: onDragMove,
447
+ onMouseUp: onDragEnd,
448
+ onMouseLeave: onDragEnd,
449
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
450
+ const isToday = date.getTime() === todayMs;
451
+ const isSelected = i === selectedIdx;
452
+ const clickable = datesWithClasses.has(i);
453
+ const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
454
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
455
+ "div",
456
+ {
457
+ onClick: () => clickable && onSelectDate(i),
458
+ className: `flex-1 h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
459
+ children: [
460
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
461
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
462
+ "div",
463
+ {
464
+ className: "w-10 h-10 rounded-full flex items-center justify-center",
465
+ style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
466
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
467
+ "span",
468
+ {
469
+ className: "font-body text-xl font-bold text-center tracking-display-tight",
470
+ style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
471
+ children: date.getDate()
472
+ }
473
+ )
474
+ }
475
+ )
476
+ ]
477
+ },
478
+ i
479
+ );
480
+ }) })
481
+ }
482
+ )
483
+ ] }),
484
+ selectedClasses.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-base text-(--color-accent,#A89B83) text-center py-8 m-0", children: "No classes scheduled for this day." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-col", children: selectedClasses.map((cls, i) => {
485
+ const booked = getBookingsOnDate(cls, selectedDate);
486
+ const available = cls.maxParticipants - booked;
487
+ const isFull = available <= 0;
488
+ const isPrivate = cls.maxParticipants === 1;
489
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
490
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
491
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
492
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 sm:w-[120px] sm:shrink-0", children: [
493
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }),
494
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
495
+ "span",
496
+ {
497
+ className: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium w-fit",
498
+ style: isPrivate ? { background: "#DDFBE3", color: "#199130", border: "1px solid #199130", fontWeight: 500 } : { background: "#EEEDFF", color: "#5953FF", border: "1px solid #5953FF", fontWeight: 500 },
499
+ children: isPrivate ? "PRIVATE" : "GROUP"
500
+ }
501
+ )
502
+ ] }),
503
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
504
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
505
+ available,
506
+ "/",
507
+ cls.maxParticipants
508
+ ] }),
509
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
510
+ ] }) })
511
+ ] }),
512
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
513
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
514
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
515
+ cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
516
+ ] }),
517
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
518
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
519
+ available,
520
+ "/",
521
+ cls.maxParticipants
522
+ ] }),
523
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
524
+ ] }) }),
525
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
526
+ "button",
527
+ {
528
+ 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",
529
+ style: isFull ? {
530
+ background: "transparent",
531
+ border: "1px solid var(--color-dark, #200C02)",
532
+ color: "var(--color-dark, #200C02)"
533
+ } : {
534
+ background: "var(--color-primary, #3D312B)",
535
+ border: "none",
536
+ color: "var(--color-light, #FEFFF0)"
537
+ },
538
+ onClick: () => {
539
+ if (!cls.id) return;
540
+ onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
541
+ },
542
+ children: isFull ? "Join Waitlist" : "Book Now"
543
+ }
544
+ )
545
+ ] })
546
+ ] }),
547
+ i < selectedClasses.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
548
+ ] }, cls.id ?? i);
549
+ }) })
550
+ ] });
551
+ }
552
+ function CalendarFoldIcon() {
553
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
554
+ "svg",
555
+ {
556
+ width: "16",
557
+ height: "16",
558
+ viewBox: "0 0 24 24",
559
+ fill: "none",
560
+ stroke: "currentColor",
561
+ strokeWidth: "2",
562
+ strokeLinecap: "round",
563
+ strokeLinejoin: "round",
564
+ style: { flexShrink: 0 },
565
+ children: [
566
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 2v4" }),
567
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 2v4" }),
568
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }),
569
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 10h18" }),
570
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
571
+ ]
572
+ }
573
+ );
574
+ }
575
+ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter: insertAfterProp }) {
576
+ const autoId = (0, import_react2.useId)();
577
+ const insertAfter = insertAfterProp ?? autoId;
578
+ const [schedule, setSchedule] = (0, import_react2.useState)(null);
579
+ const [loading, setLoading] = (0, import_react2.useState)(true);
580
+ const [inEditor, setInEditor] = (0, import_react2.useState)(false);
581
+ const [isHovered, setIsHovered] = (0, import_react2.useState)(false);
582
+ const [modalState, setModalState] = (0, import_react2.useState)(null);
583
+ const switchScheduleIdRef = (0, import_react2.useRef)(null);
584
+ const dates = (0, import_react2.useMemo)(() => {
585
+ const today = /* @__PURE__ */ new Date();
586
+ today.setHours(0, 0, 0, 0);
587
+ return Array.from({ length: 14 }, (_, i) => {
588
+ const d = new Date(today);
589
+ d.setDate(today.getDate() + i);
590
+ return d;
591
+ });
592
+ }, []);
593
+ const [selectedIdx, setSelectedIdx] = (0, import_react2.useState)(0);
594
+ (0, import_react2.useEffect)(() => {
595
+ if (!schedule?.classes) return;
596
+ for (let i = 0; i < dates.length; i++) {
597
+ if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
598
+ setSelectedIdx(i);
599
+ return;
600
+ }
601
+ }
602
+ }, [schedule, dates]);
603
+ (0, import_react2.useEffect)(() => {
604
+ if (typeof window === "undefined") return;
605
+ const isInEditor = window.self !== window.top;
606
+ setInEditor(isInEditor);
607
+ function loadWithId(effectiveId) {
608
+ if (isInEditor) {
609
+ const startEmpty = effectiveId === null;
610
+ if (startEmpty) setLoading(false);
611
+ let initialized = startEmpty;
612
+ const timer = !startEmpty ? setTimeout(() => {
613
+ if (!initialized) {
614
+ initialized = true;
615
+ setLoading(false);
616
+ }
617
+ }, 5e3) : null;
618
+ const handler = (e) => {
619
+ if (e.data?.type === "ow:clear-scheduling-widget") {
620
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
621
+ setSchedule(null);
622
+ setLoading(false);
623
+ return;
624
+ }
625
+ if (e.data?.type === "ow:switch-schedule") {
626
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
627
+ switchScheduleIdRef.current = e.data.scheduleId ?? null;
628
+ initialized = false;
629
+ setLoading(true);
630
+ window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
631
+ return;
632
+ }
633
+ if (e.data?.type !== "ow:user-schedules-response") return;
634
+ if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
635
+ if (initialized && switchScheduleIdRef.current === null) return;
636
+ initialized = true;
637
+ if (timer) clearTimeout(timer);
638
+ const schedules = e.data.schedules ?? [];
639
+ const isSwitching = switchScheduleIdRef.current !== null;
640
+ 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;
641
+ switchScheduleIdRef.current = null;
642
+ setSchedule(target);
643
+ setLoading(false);
644
+ if (notifyOnConnect && target && !isSwitching) {
645
+ window.parent.postMessage({
646
+ type: "ow:schedule-connected",
647
+ schedule: { id: target.id, name: target.name },
648
+ insertAfter
649
+ }, "*");
650
+ window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
651
+ }
652
+ };
653
+ window.addEventListener("message", handler);
654
+ if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
655
+ return () => {
656
+ window.removeEventListener("message", handler);
657
+ if (timer) clearTimeout(timer);
658
+ };
659
+ }
660
+ if (!effectiveId) {
661
+ setLoading(false);
662
+ return;
663
+ }
664
+ ;
665
+ (async () => {
666
+ try {
667
+ const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
668
+ const data = await res.json();
669
+ setSchedule(data?.id ? data : null);
670
+ } catch {
671
+ }
672
+ setLoading(false);
673
+ })();
674
+ }
675
+ if (initialScheduleId === void 0) {
676
+ let done = false;
677
+ let innerCleanup;
678
+ const timer = setTimeout(() => {
679
+ if (!done) {
680
+ done = true;
681
+ setLoading(false);
682
+ }
683
+ }, 2e3);
684
+ const configHandler = (e) => {
685
+ if (e.data?.type !== "ow:schedule-config" || e.data.insertAfter !== insertAfter || done) return;
686
+ done = true;
687
+ clearTimeout(timer);
688
+ window.removeEventListener("message", configHandler);
689
+ innerCleanup = loadWithId(e.data.scheduleId ?? null);
690
+ };
691
+ window.addEventListener("message", configHandler);
692
+ window.postMessage({ type: "ow:request-schedule-config", insertAfter }, "*");
693
+ return () => {
694
+ window.removeEventListener("message", configHandler);
695
+ clearTimeout(timer);
696
+ innerCleanup?.();
697
+ };
698
+ }
699
+ return loadWithId(initialScheduleId) ?? void 0;
700
+ }, []);
701
+ const timezoneLabel = schedule?.timezone ? (() => {
702
+ try {
703
+ return buildTimezoneLabel(schedule.timezone);
704
+ } catch {
705
+ return void 0;
706
+ }
707
+ })() : void 0;
708
+ const handleModalSubmit = async (email) => {
709
+ if (!modalState) return;
710
+ const { mode, classId, classDate } = modalState;
711
+ const endpoint = mode === "book" ? `${API_URL}/api/schedule/class/${classId}/booking` : `${API_URL}/api/schedule/class/${classId}/waitlist`;
712
+ const res = await fetch(endpoint, {
713
+ method: "POST",
714
+ headers: { "Content-Type": "application/json" },
715
+ body: JSON.stringify({ classDate: classDate.toISOString(), email })
716
+ });
717
+ if (!res.ok) {
718
+ const data = await res.json().catch(() => ({}));
719
+ throw new Error(data?.message ?? "Something went wrong. Please try again.");
720
+ }
721
+ };
722
+ const handleReplaceSchedule = () => {
723
+ setIsHovered(false);
724
+ if (schedule) {
725
+ window.parent.postMessage({
726
+ type: "ow:schedule-connected",
727
+ schedule: { id: schedule.id, name: schedule.name },
728
+ insertAfter
729
+ }, "*");
730
+ } else {
731
+ window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
732
+ }
733
+ };
734
+ if (!inEditor && !loading && !schedule) return null;
735
+ const sectionId = `scheduling-${insertAfter}`;
736
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
737
+ "section",
738
+ {
739
+ "data-ohw-section": sectionId,
740
+ "data-ohw-scheduling-anchor": insertAfter,
741
+ className: "w-full box-border py-10 px-5 sm:py-20 sm:px-16 font-body bg-(--color-light,#FEFFF0) relative",
742
+ onMouseEnter: () => inEditor && setIsHovered(true),
743
+ onMouseLeave: () => setIsHovered(false),
744
+ children: [
745
+ inEditor && isHovered && !!schedule && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
746
+ "div",
747
+ {
748
+ className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
749
+ onClick: handleReplaceSchedule,
750
+ children: [
751
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
752
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
753
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
754
+ "div",
755
+ {
756
+ className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
757
+ style: {
758
+ fontSize: 14,
759
+ lineHeight: "24px",
760
+ fontFamily: "'Figtree', system-ui, sans-serif",
761
+ fontWeight: 500,
762
+ color: "#0C0A09"
763
+ },
764
+ children: [
765
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CalendarFoldIcon, {}),
766
+ "Replace schedule"
767
+ ]
768
+ }
769
+ ) })
770
+ ]
771
+ }
772
+ ),
773
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
774
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
775
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
776
+ schedule?.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
777
+ ] }),
778
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
779
+ timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
780
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyState, { inEditor, insertAfter }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
781
+ ScheduleView,
782
+ {
783
+ schedule,
784
+ dates,
785
+ selectedIdx,
786
+ onSelectDate: setSelectedIdx,
787
+ onOpenModal: inEditor ? void 0 : (mode, classId, classDate) => setModalState({ mode, classId, classDate })
788
+ }
789
+ ) })
790
+ ] })
791
+ ] }),
792
+ modalState && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
793
+ EmailCaptureModal,
794
+ {
795
+ title: modalState.mode === "book" ? "Confirm your spot" : "Leave your email",
796
+ subtitle: modalState.mode === "book" ? "We'll send your booking confirmation here." : "We'll let you know when spots open up!",
797
+ onSubmit: handleModalSubmit,
798
+ onClose: () => setModalState(null)
799
+ }
800
+ )
801
+ ]
802
+ }
803
+ );
804
+ }
52
805
 
53
806
  // src/ui/toggle-group.tsx
54
- var React = __toESM(require("react"), 1);
807
+ var React2 = __toESM(require("react"), 1);
55
808
 
56
809
  // node_modules/clsx/dist/clsx.mjs
57
810
  function r(e) {
@@ -3370,8 +4123,8 @@ var cva = (base, config) => (props) => {
3370
4123
  };
3371
4124
 
3372
4125
  // src/ui/toggle.tsx
3373
- var import_radix_ui = require("radix-ui");
3374
- var import_jsx_runtime = require("react/jsx-runtime");
4126
+ var import_radix_ui2 = require("radix-ui");
4127
+ var import_jsx_runtime3 = require("react/jsx-runtime");
3375
4128
  var toggleVariants = cva(
3376
4129
  "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",
3377
4130
  {
@@ -3398,8 +4151,8 @@ function Toggle({
3398
4151
  size,
3399
4152
  ...props
3400
4153
  }) {
3401
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3402
- import_radix_ui.Toggle.Root,
4154
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4155
+ import_radix_ui2.Toggle.Root,
3403
4156
  {
3404
4157
  "data-slot": "toggle",
3405
4158
  className: cn(toggleVariants({ variant, size, className })),
@@ -3409,8 +4162,8 @@ function Toggle({
3409
4162
  }
3410
4163
 
3411
4164
  // src/ui/toggle-group.tsx
3412
- var import_jsx_runtime2 = require("react/jsx-runtime");
3413
- var ToggleGroupContext = React.createContext({
4165
+ var import_jsx_runtime4 = require("react/jsx-runtime");
4166
+ var ToggleGroupContext = React2.createContext({
3414
4167
  value: "",
3415
4168
  onValueChange: () => {
3416
4169
  }
@@ -3422,13 +4175,13 @@ function ToggleGroup({
3422
4175
  children,
3423
4176
  ...props
3424
4177
  }) {
3425
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4178
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3426
4179
  "div",
3427
4180
  {
3428
4181
  role: "group",
3429
4182
  className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
3430
4183
  ...props,
3431
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
4184
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
3432
4185
  }
3433
4186
  );
3434
4187
  }
@@ -3438,8 +4191,8 @@ function ToggleGroupItem({
3438
4191
  children,
3439
4192
  ...props
3440
4193
  }) {
3441
- const { value: groupValue, onValueChange } = React.useContext(ToggleGroupContext);
3442
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4194
+ const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
4195
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3443
4196
  Toggle,
3444
4197
  {
3445
4198
  pressed: groupValue === value,
@@ -3459,7 +4212,7 @@ function ToggleGroupItem({
3459
4212
  }
3460
4213
 
3461
4214
  // src/OhhwellsBridge.tsx
3462
- var import_react_dom = require("react-dom");
4215
+ var import_react_dom2 = require("react-dom");
3463
4216
  var import_navigation = require("next/navigation");
3464
4217
 
3465
4218
  // src/lib/session-search.ts
@@ -3736,26 +4489,135 @@ function scrollToHashSectionWhenReady(behavior = "smooth") {
3736
4489
  tick();
3737
4490
  }
3738
4491
 
3739
- // src/ui/link-modal/calcPopoverPos.ts
3740
- function calcPopoverPos(rect, parentScroll, approxW = 483, approxH = 320) {
3741
- const GAP = 8;
3742
- const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
3743
- const visibleTop = rect.top - canvasTopInIframe;
3744
- let top = rect.top - GAP;
3745
- let transform = "translateX(-50%) translateY(-100%)";
3746
- if (visibleTop < approxH + GAP) {
3747
- top = rect.bottom + GAP;
3748
- transform = "translateX(-50%)";
3749
- }
3750
- const rawLeft = rect.left + rect.width / 2;
3751
- const left = Math.max(GAP + approxW / 2, Math.min(rawLeft, window.innerWidth - GAP - approxW / 2));
3752
- return { top, left, transform };
4492
+ // src/ui/dialog.tsx
4493
+ var React3 = __toESM(require("react"), 1);
4494
+ var import_radix_ui3 = require("radix-ui");
4495
+
4496
+ // src/ui/icons.tsx
4497
+ var import_jsx_runtime5 = require("react/jsx-runtime");
4498
+ function IconX({ className, ...props }) {
4499
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4500
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M18 6 6 18" }),
4501
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m6 6 12 12" })
4502
+ ] });
4503
+ }
4504
+ function IconChevronDown({ className, ...props }) {
4505
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m6 9 6 6 6-6" }) });
3753
4506
  }
4507
+ function IconFile({ className, ...props }) {
4508
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4509
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
4510
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
4511
+ ] });
4512
+ }
4513
+ function IconInfo({ className, ...props }) {
4514
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4515
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
4516
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 16v-4" }),
4517
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 8h.01" })
4518
+ ] });
4519
+ }
4520
+ function IconArrowRight({ className, ...props }) {
4521
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4522
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M5 12h14" }),
4523
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m12 5 7 7-7 7" })
4524
+ ] });
4525
+ }
4526
+ function IconSection({ className, ...props }) {
4527
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
4528
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
4529
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
4530
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
4531
+ ] });
4532
+ }
4533
+
4534
+ // src/ui/dialog.tsx
4535
+ var import_jsx_runtime6 = require("react/jsx-runtime");
4536
+ function Dialog2({ ...props }) {
4537
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Dialog.Root, { "data-slot": "dialog", ...props });
4538
+ }
4539
+ function DialogPortal({ ...props }) {
4540
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Dialog.Portal, { ...props });
4541
+ }
4542
+ function DialogOverlay({
4543
+ className,
4544
+ ...props
4545
+ }) {
4546
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4547
+ import_radix_ui3.Dialog.Overlay,
4548
+ {
4549
+ "data-slot": "dialog-overlay",
4550
+ "data-ohw-link-modal-root": "",
4551
+ className: cn("fixed inset-0 z-[2147483646] bg-black/50", className),
4552
+ ...props
4553
+ }
4554
+ );
4555
+ }
4556
+ var DialogContent = React3.forwardRef(({ className, children, showCloseButton = true, container, ...props }, ref) => {
4557
+ const positionMode = container ? "absolute" : "fixed";
4558
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(DialogPortal, { container: container ?? void 0, children: [
4559
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DialogOverlay, { className: cn(positionMode, "inset-0") }),
4560
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
4561
+ import_radix_ui3.Dialog.Content,
4562
+ {
4563
+ ref,
4564
+ "data-slot": "dialog-content",
4565
+ className: cn(
4566
+ positionMode,
4567
+ "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",
4568
+ "rounded-xl border border-border bg-background font-sans shadow-lg outline-none",
4569
+ container ? "max-h-[calc(100%-32px)] max-w-[calc(100%-16px)]" : "max-h-[calc(100vh-32px)] max-w-[calc(100vw-16px)]",
4570
+ className
4571
+ ),
4572
+ onMouseDown: (e) => e.stopPropagation(),
4573
+ ...props,
4574
+ children: [
4575
+ children,
4576
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4577
+ import_radix_ui3.Dialog.Close,
4578
+ {
4579
+ type: "button",
4580
+ className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4581
+ "aria-label": "Close",
4582
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(IconX, { "aria-hidden": true })
4583
+ }
4584
+ ) : null
4585
+ ]
4586
+ }
4587
+ )
4588
+ ] });
4589
+ });
4590
+ DialogContent.displayName = import_radix_ui3.Dialog.Content.displayName;
4591
+ function DialogHeader({ className, ...props }) {
4592
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cn("flex flex-col gap-1.5", className), ...props });
4593
+ }
4594
+ function DialogFooter({ className, ...props }) {
4595
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: cn("flex items-center justify-end gap-2", className), ...props });
4596
+ }
4597
+ var DialogTitle = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4598
+ import_radix_ui3.Dialog.Title,
4599
+ {
4600
+ ref,
4601
+ className: cn("text-lg font-semibold leading-none tracking-tight text-card-foreground", className),
4602
+ ...props
4603
+ }
4604
+ ));
4605
+ DialogTitle.displayName = import_radix_ui3.Dialog.Title.displayName;
4606
+ var DialogDescription = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4607
+ import_radix_ui3.Dialog.Description,
4608
+ {
4609
+ ref,
4610
+ className: cn("text-sm text-muted-foreground", className),
4611
+ ...props
4612
+ }
4613
+ ));
4614
+ DialogDescription.displayName = import_radix_ui3.Dialog.Description.displayName;
4615
+ var DialogClose = import_radix_ui3.Dialog.Close;
3754
4616
 
3755
4617
  // src/ui/button.tsx
3756
- var React2 = __toESM(require("react"), 1);
3757
- var import_radix_ui2 = require("radix-ui");
3758
- var import_jsx_runtime3 = require("react/jsx-runtime");
4618
+ var React4 = __toESM(require("react"), 1);
4619
+ var import_radix_ui4 = require("radix-ui");
4620
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3759
4621
  var buttonVariants = cva(
3760
4622
  "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",
3761
4623
  {
@@ -3776,10 +4638,10 @@ var buttonVariants = cva(
3776
4638
  }
3777
4639
  }
3778
4640
  );
3779
- var Button = React2.forwardRef(
4641
+ var Button = React4.forwardRef(
3780
4642
  ({ className, variant, size, asChild = false, ...props }, ref) => {
3781
- const Comp = asChild ? import_radix_ui2.Slot.Root : "button";
3782
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4643
+ const Comp = asChild ? import_radix_ui4.Slot.Root : "button";
4644
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3783
4645
  Comp,
3784
4646
  {
3785
4647
  ref,
@@ -3792,76 +4654,38 @@ var Button = React2.forwardRef(
3792
4654
  );
3793
4655
  Button.displayName = "Button";
3794
4656
 
3795
- // src/ui/icons.tsx
3796
- var import_jsx_runtime4 = require("react/jsx-runtime");
3797
- function IconX({ className, ...props }) {
3798
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3799
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M18 6 6 18" }),
3800
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m6 6 12 12" })
3801
- ] });
3802
- }
3803
- function IconChevronDown({ className, ...props }) {
3804
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m6 9 6 6 6-6" }) });
3805
- }
3806
- function IconFile({ className, ...props }) {
3807
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3808
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
3809
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
3810
- ] });
3811
- }
3812
- function IconInfo({ className, ...props }) {
3813
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3814
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
3815
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 16v-4" }),
3816
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 8h.01" })
3817
- ] });
3818
- }
3819
- function IconArrowRight({ className, ...props }) {
3820
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3821
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M5 12h14" }),
3822
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m12 5 7 7-7 7" })
3823
- ] });
3824
- }
3825
- function IconSection({ className, ...props }) {
3826
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
3827
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
3828
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
3829
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
3830
- ] });
3831
- }
3832
-
3833
- // src/ui/link-modal/DestinationBreadcrumb.tsx
3834
- var import_jsx_runtime5 = require("react/jsx-runtime");
3835
- function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
3836
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
3837
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
3838
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-3", children: [
3839
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
3840
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
3841
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
3842
- ] }),
3843
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
3844
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
3845
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
3846
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
3847
- ] })
3848
- ] })
4657
+ // src/ui/link-modal/DestinationBreadcrumb.tsx
4658
+ var import_jsx_runtime8 = require("react/jsx-runtime");
4659
+ function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
4660
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
4661
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
4662
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-3", children: [
4663
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2", children: [
4664
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4665
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
4666
+ ] }),
4667
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
4668
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
4669
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4670
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
4671
+ ] })
4672
+ ] })
3849
4673
  ] });
3850
4674
  }
3851
4675
 
3852
4676
  // src/ui/link-modal/SectionTreeItem.tsx
3853
- var import_jsx_runtime6 = require("react/jsx-runtime");
4677
+ var import_jsx_runtime9 = require("react/jsx-runtime");
3854
4678
  function SectionTreeItem({ section, onSelect, selected }) {
3855
4679
  const interactive = Boolean(onSelect);
3856
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
3857
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4680
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
4681
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3858
4682
  "div",
3859
4683
  {
3860
4684
  className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
3861
4685
  "aria-hidden": true
3862
4686
  }
3863
4687
  ),
3864
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
4688
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3865
4689
  "div",
3866
4690
  {
3867
4691
  role: interactive ? "button" : void 0,
@@ -3879,8 +4703,8 @@ function SectionTreeItem({ section, onSelect, selected }) {
3879
4703
  interactive && selected && "border-primary"
3880
4704
  ),
3881
4705
  children: [
3882
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
3883
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
4706
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
4707
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
3884
4708
  ]
3885
4709
  }
3886
4710
  )
@@ -3888,23 +4712,23 @@ function SectionTreeItem({ section, onSelect, selected }) {
3888
4712
  }
3889
4713
  function SectionPickerList({ sections, onSelect }) {
3890
4714
  if (sections.length === 0) {
3891
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
4715
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
3892
4716
  }
3893
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-1", children: [
3894
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
3895
- sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SectionTreeItem, { section, onSelect }, section.id))
4717
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-1", children: [
4718
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
4719
+ sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SectionTreeItem, { section, onSelect }, section.id))
3896
4720
  ] });
3897
4721
  }
3898
4722
 
3899
4723
  // src/ui/link-modal/UrlOrPageInput.tsx
3900
- var import_react = require("react");
4724
+ var import_react3 = require("react");
3901
4725
 
3902
4726
  // src/ui/input.tsx
3903
- var React3 = __toESM(require("react"), 1);
3904
- var import_jsx_runtime7 = require("react/jsx-runtime");
3905
- var Input = React3.forwardRef(
4727
+ var React5 = __toESM(require("react"), 1);
4728
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4729
+ var Input = React5.forwardRef(
3906
4730
  ({ className, type, ...props }, ref) => {
3907
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4731
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3908
4732
  "input",
3909
4733
  {
3910
4734
  type,
@@ -3922,11 +4746,11 @@ var Input = React3.forwardRef(
3922
4746
  Input.displayName = "Input";
3923
4747
 
3924
4748
  // src/ui/label.tsx
3925
- var import_radix_ui3 = require("radix-ui");
3926
- var import_jsx_runtime8 = require("react/jsx-runtime");
4749
+ var import_radix_ui5 = require("radix-ui");
4750
+ var import_jsx_runtime11 = require("react/jsx-runtime");
3927
4751
  function Label({ className, ...props }) {
3928
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3929
- import_radix_ui3.Label.Root,
4752
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4753
+ import_radix_ui5.Label.Root,
3930
4754
  {
3931
4755
  "data-slot": "label",
3932
4756
  className: cn("text-sm font-medium leading-5 text-foreground", className),
@@ -3935,40 +4759,10 @@ function Label({ className, ...props }) {
3935
4759
  );
3936
4760
  }
3937
4761
 
3938
- // src/ui/popover.tsx
3939
- var import_radix_ui4 = require("radix-ui");
3940
- var import_jsx_runtime9 = require("react/jsx-runtime");
3941
- function Popover({ ...props }) {
3942
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_radix_ui4.Popover.Root, { "data-slot": "popover", ...props });
3943
- }
3944
- function PopoverTrigger({ ...props }) {
3945
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_radix_ui4.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
3946
- }
3947
- function PopoverContent({
3948
- className,
3949
- align = "start",
3950
- sideOffset = 6,
3951
- ...props
3952
- }) {
3953
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_radix_ui4.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3954
- import_radix_ui4.Popover.Content,
3955
- {
3956
- "data-slot": "popover-content",
3957
- align,
3958
- sideOffset,
3959
- className: cn(
3960
- "z-[2147483647] w-[var(--radix-popover-trigger-width)] overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg outline-none",
3961
- className
3962
- ),
3963
- ...props
3964
- }
3965
- ) });
3966
- }
3967
-
3968
4762
  // src/ui/link-modal/UrlOrPageInput.tsx
3969
- var import_jsx_runtime10 = require("react/jsx-runtime");
4763
+ var import_jsx_runtime12 = require("react/jsx-runtime");
3970
4764
  function FieldChevron({ onClick }) {
3971
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4765
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3972
4766
  "button",
3973
4767
  {
3974
4768
  type: "button",
@@ -3976,7 +4770,7 @@ function FieldChevron({ onClick }) {
3976
4770
  onClick,
3977
4771
  "aria-label": "Open page list",
3978
4772
  tabIndex: -1,
3979
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconChevronDown, {})
4773
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconChevronDown, {})
3980
4774
  }
3981
4775
  );
3982
4776
  }
@@ -3992,9 +4786,9 @@ function UrlOrPageInput({
3992
4786
  readOnly = false,
3993
4787
  urlError
3994
4788
  }) {
3995
- const inputId = (0, import_react.useId)();
3996
- const inputRef = (0, import_react.useRef)(null);
3997
- const [isFocused, setIsFocused] = (0, import_react.useState)(false);
4789
+ const inputId = (0, import_react3.useId)();
4790
+ const inputRef = (0, import_react3.useRef)(null);
4791
+ const [isFocused, setIsFocused] = (0, import_react3.useState)(false);
3998
4792
  const openDropdown = () => {
3999
4793
  if (readOnly || filteredPages.length === 0) return;
4000
4794
  onDropdownOpenChange(true);
@@ -4015,12 +4809,12 @@ function UrlOrPageInput({
4015
4809
  "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]",
4016
4810
  urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
4017
4811
  );
4018
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
4019
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
4020
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
4021
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
4022
- selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
4023
- readOnly ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4812
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
4813
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
4814
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "relative w-full", children: [
4815
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
4816
+ selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
4817
+ readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4024
4818
  Input,
4025
4819
  {
4026
4820
  ref: inputRef,
@@ -4039,7 +4833,7 @@ function UrlOrPageInput({
4039
4833
  )
4040
4834
  }
4041
4835
  ),
4042
- selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4836
+ selectedPage && !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4043
4837
  "button",
4044
4838
  {
4045
4839
  type: "button",
@@ -4047,31 +4841,39 @@ function UrlOrPageInput({
4047
4841
  onMouseDown: clearSelection,
4048
4842
  "aria-label": "Clear selected page",
4049
4843
  tabIndex: -1,
4050
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconX, { "aria-hidden": true })
4844
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconX, { "aria-hidden": true })
4051
4845
  }
4052
4846
  ) : null,
4053
- !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
4054
- ] }) }),
4055
- filteredPages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4056
- "button",
4847
+ !readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
4848
+ ] }),
4849
+ dropdownOpen && !readOnly && filteredPages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4850
+ "div",
4057
4851
  {
4058
- type: "button",
4059
- 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",
4060
- onClick: () => onPageSelect(page),
4061
- children: [
4062
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
4063
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "truncate", children: page.title })
4064
- ]
4065
- },
4066
- page.path
4067
- )) })
4852
+ "data-ohw-link-page-dropdown": "",
4853
+ 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",
4854
+ onMouseDown: (e) => e.preventDefault(),
4855
+ children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
4856
+ "button",
4857
+ {
4858
+ type: "button",
4859
+ 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",
4860
+ onClick: () => onPageSelect(page),
4861
+ children: [
4862
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
4863
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate", children: page.title })
4864
+ ]
4865
+ },
4866
+ page.path
4867
+ ))
4868
+ }
4869
+ ) : null
4068
4870
  ] }),
4069
- urlError ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4871
+ urlError ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
4070
4872
  ] });
4071
4873
  }
4072
4874
 
4073
4875
  // src/ui/link-modal/useLinkModalState.ts
4074
- var import_react2 = require("react");
4876
+ var import_react4 = require("react");
4075
4877
  function useLinkModalState({
4076
4878
  open,
4077
4879
  mode,
@@ -4083,17 +4885,17 @@ function useLinkModalState({
4083
4885
  onClose,
4084
4886
  onSubmit
4085
4887
  }) {
4086
- const availablePages = (0, import_react2.useMemo)(
4888
+ const availablePages = (0, import_react4.useMemo)(
4087
4889
  () => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
4088
4890
  [mode, pages, existingTargets]
4089
4891
  );
4090
- const [searchValue, setSearchValue] = (0, import_react2.useState)("");
4091
- const [selectedPage, setSelectedPage] = (0, import_react2.useState)(null);
4092
- const [selectedSection, setSelectedSection] = (0, import_react2.useState)(null);
4093
- const [step, setStep] = (0, import_react2.useState)("input");
4094
- const [dropdownOpen, setDropdownOpen] = (0, import_react2.useState)(false);
4095
- const [urlError, setUrlError] = (0, import_react2.useState)("");
4096
- const reset = (0, import_react2.useCallback)(() => {
4892
+ const [searchValue, setSearchValue] = (0, import_react4.useState)("");
4893
+ const [selectedPage, setSelectedPage] = (0, import_react4.useState)(null);
4894
+ const [selectedSection, setSelectedSection] = (0, import_react4.useState)(null);
4895
+ const [step, setStep] = (0, import_react4.useState)("input");
4896
+ const [dropdownOpen, setDropdownOpen] = (0, import_react4.useState)(false);
4897
+ const [urlError, setUrlError] = (0, import_react4.useState)("");
4898
+ const reset = (0, import_react4.useCallback)(() => {
4097
4899
  setSearchValue("");
4098
4900
  setSelectedPage(null);
4099
4901
  setSelectedSection(null);
@@ -4101,7 +4903,7 @@ function useLinkModalState({
4101
4903
  setDropdownOpen(false);
4102
4904
  setUrlError("");
4103
4905
  }, []);
4104
- (0, import_react2.useEffect)(() => {
4906
+ (0, import_react4.useEffect)(() => {
4105
4907
  if (!open) return;
4106
4908
  if (mode === "edit" && initialTarget) {
4107
4909
  const { pageRoute } = parseTarget(initialTarget);
@@ -4117,11 +4919,11 @@ function useLinkModalState({
4117
4919
  setDropdownOpen(false);
4118
4920
  setUrlError("");
4119
4921
  }, [open, mode, initialTarget, pages, sectionsByPath, reset]);
4120
- const filteredPages = (0, import_react2.useMemo)(() => {
4922
+ const filteredPages = (0, import_react4.useMemo)(() => {
4121
4923
  if (!searchValue.trim()) return availablePages;
4122
4924
  return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
4123
4925
  }, [availablePages, searchValue]);
4124
- const activeSections = (0, import_react2.useMemo)(() => {
4926
+ const activeSections = (0, import_react4.useMemo)(() => {
4125
4927
  if (!selectedPage) return [];
4126
4928
  return getSectionsForPath(sectionsByPath, selectedPage.path);
4127
4929
  }, [selectedPage, sectionsByPath]);
@@ -4167,7 +4969,7 @@ function useLinkModalState({
4167
4969
  reset();
4168
4970
  onClose();
4169
4971
  };
4170
- const isValid = (0, import_react2.useMemo)(() => {
4972
+ const isValid = (0, import_react4.useMemo)(() => {
4171
4973
  if (urlError) return false;
4172
4974
  if (showBreadcrumb) return true;
4173
4975
  if (selectedPage) return true;
@@ -4234,7 +5036,7 @@ function useLinkModalState({
4234
5036
  }
4235
5037
 
4236
5038
  // src/ui/link-modal/LinkEditorPanel.tsx
4237
- var import_jsx_runtime11 = require("react/jsx-runtime");
5039
+ var import_jsx_runtime13 = require("react/jsx-runtime");
4238
5040
  function LinkEditorPanel({
4239
5041
  open = true,
4240
5042
  mode = "create",
@@ -4258,26 +5060,25 @@ function LinkEditorPanel({
4258
5060
  onSubmit
4259
5061
  });
4260
5062
  const isCancel = state.secondaryLabel === "Cancel";
4261
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
4262
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5063
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
5064
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DialogClose, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4263
5065
  "button",
4264
5066
  {
4265
5067
  type: "button",
4266
5068
  className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
4267
- onClick: state.handleClose,
4268
5069
  "aria-label": "Close",
4269
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconX, { "aria-hidden": true })
5070
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconX, { "aria-hidden": true })
4270
5071
  }
4271
- ),
4272
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("header", { className: "flex w-full flex-col gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { className: "m-0 w-full break-words text-lg font-semibold leading-none tracking-tight text-card-foreground", children: state.title }) }),
4273
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
4274
- state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5072
+ ) }),
5073
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DialogHeader, { className: "w-full gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DialogTitle, { className: "m-0 w-full break-words", children: state.title }) }),
5074
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
5075
+ state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4275
5076
  DestinationBreadcrumb,
4276
5077
  {
4277
5078
  pageTitle: state.selectedPage.title,
4278
5079
  sectionLabel: state.selectedSection.label
4279
5080
  }
4280
- ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5081
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4281
5082
  UrlOrPageInput,
4282
5083
  {
4283
5084
  value: state.searchValue,
@@ -4290,18 +5091,18 @@ function LinkEditorPanel({
4290
5091
  urlError: state.urlError
4291
5092
  }
4292
5093
  ),
4293
- state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
4294
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
4295
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
4296
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
4297
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "Pick a section this link should scroll to." })
5094
+ state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
5095
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
5096
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
5097
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
5098
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "Pick a section this link should scroll to." })
4298
5099
  ] })
4299
5100
  ] }) : null,
4300
- state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
4301
- state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
5101
+ state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
5102
+ state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
4302
5103
  ] }),
4303
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("footer", { className: "flex w-full items-center justify-end gap-2 px-6 pb-6", children: [
4304
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5104
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(DialogFooter, { className: "w-full px-6 pb-6", children: [
5105
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4305
5106
  Button,
4306
5107
  {
4307
5108
  type: "button",
@@ -4316,7 +5117,7 @@ function LinkEditorPanel({
4316
5117
  children: state.secondaryLabel
4317
5118
  }
4318
5119
  ),
4319
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5120
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4320
5121
  Button,
4321
5122
  {
4322
5123
  type: "button",
@@ -4335,28 +5136,34 @@ function LinkEditorPanel({
4335
5136
  }
4336
5137
 
4337
5138
  // src/ui/link-modal/LinkPopover.tsx
4338
- var import_jsx_runtime12 = require("react/jsx-runtime");
5139
+ var import_jsx_runtime14 = require("react/jsx-runtime");
4339
5140
  function LinkPopover({
4340
- rect,
4341
- parentScroll,
5141
+ open = true,
4342
5142
  panelRef,
5143
+ portalContainer,
5144
+ onClose,
4343
5145
  ...editorProps
4344
5146
  }) {
4345
- const { top, left, transform } = calcPopoverPos(rect, parentScroll);
4346
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4347
- "div",
5147
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5148
+ Dialog2,
4348
5149
  {
4349
- ref: panelRef,
4350
- "data-ohw-link-popover-root": "",
4351
- "data-ohw-link-modal-root": "",
4352
- "data-ohw-bridge": "",
4353
- className: cn(
4354
- "pointer-events-auto fixed z-[2147483647] flex w-[483px] max-w-[calc(100vw-16px)] flex-col overflow-hidden",
4355
- "rounded-xl border border-border bg-background font-sans shadow-lg outline-none"
4356
- ),
4357
- style: { top, left, transform },
4358
- onMouseDown: (e) => e.stopPropagation(),
4359
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(LinkEditorPanel, { ...editorProps, open: true })
5150
+ open,
5151
+ onOpenChange: (next) => {
5152
+ if (!next) onClose?.();
5153
+ },
5154
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5155
+ DialogContent,
5156
+ {
5157
+ ref: panelRef,
5158
+ container: portalContainer,
5159
+ "data-ohw-link-popover-root": "",
5160
+ "data-ohw-link-modal-root": "",
5161
+ "data-ohw-bridge": "",
5162
+ showCloseButton: false,
5163
+ className: "gap-0 p-0 w-full md:w-md pointer-events-auto",
5164
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(LinkEditorPanel, { ...editorProps, open, onClose })
5165
+ }
5166
+ )
4360
5167
  }
4361
5168
  );
4362
5169
  }
@@ -4419,8 +5226,30 @@ function shouldUseDevFixtures() {
4419
5226
  return new URLSearchParams(q).get("ohw-fixtures") === "1";
4420
5227
  }
4421
5228
 
5229
+ // src/ui/badge.tsx
5230
+ var import_jsx_runtime15 = require("react/jsx-runtime");
5231
+ var badgeVariants = cva(
5232
+ "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",
5233
+ {
5234
+ variants: {
5235
+ variant: {
5236
+ default: "border-transparent bg-primary text-primary-foreground",
5237
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
5238
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
5239
+ outline: "text-foreground"
5240
+ }
5241
+ },
5242
+ defaultVariants: {
5243
+ variant: "default"
5244
+ }
5245
+ }
5246
+ );
5247
+ function Badge({ className, variant, ...props }) {
5248
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
5249
+ }
5250
+
4422
5251
  // src/OhhwellsBridge.tsx
4423
- var import_jsx_runtime13 = require("react/jsx-runtime");
5252
+ var import_jsx_runtime16 = require("react/jsx-runtime");
4424
5253
  var PRIMARY = "#0885FE";
4425
5254
  var IMAGE_FADE_MS = 300;
4426
5255
  function runOpacityFade(el, onDone) {
@@ -4471,6 +5300,169 @@ function fadeInBgImage(el, url, onReady) {
4471
5300
  if (!prevPos || prevPos === "static") el.style.position = prevPos;
4472
5301
  });
4473
5302
  }
5303
+ function getSectionsTracker() {
5304
+ let el = document.querySelector("[data-ohw-sections-tracker]");
5305
+ if (!el) {
5306
+ el = document.createElement("div");
5307
+ el.setAttribute("data-ohw-sections-tracker", "");
5308
+ el.style.display = "none";
5309
+ document.body.appendChild(el);
5310
+ }
5311
+ return el;
5312
+ }
5313
+ function updateSectionScheduleId(insertAfter, scheduleId) {
5314
+ const tracker = getSectionsTracker();
5315
+ let sections = [];
5316
+ try {
5317
+ sections = JSON.parse(tracker.textContent || "[]");
5318
+ } catch {
5319
+ }
5320
+ const currentPath = window.location.pathname;
5321
+ const updated = sections.map((s) => {
5322
+ if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
5323
+ if (s.pagePath && s.pagePath !== currentPath) return s;
5324
+ return { ...s, scheduleId };
5325
+ });
5326
+ tracker.textContent = JSON.stringify(updated);
5327
+ return tracker.textContent ?? "[]";
5328
+ }
5329
+ function schedulingSectionId(insertAfter) {
5330
+ return `scheduling-${insertAfter}`;
5331
+ }
5332
+ var INSERT_BEFORE_MARKER = ":before:";
5333
+ function parseSchedulingInsertAfter(insertAfter) {
5334
+ const idx = insertAfter.indexOf(INSERT_BEFORE_MARKER);
5335
+ if (idx < 0) return { anchor: insertAfter, insertBefore: null };
5336
+ return {
5337
+ anchor: insertAfter.slice(0, idx),
5338
+ insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
5339
+ };
5340
+ }
5341
+ function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
5342
+ const parsed = parseSchedulingInsertAfter(insertAfter);
5343
+ const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
5344
+ const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
5345
+ return { effectiveInsertAfter, insertBefore };
5346
+ }
5347
+ function getSchedulingMountPoint(insertAfter) {
5348
+ const { anchor } = parseSchedulingInsertAfter(insertAfter);
5349
+ let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
5350
+ if (!anchorEl && anchor === "scheduling") {
5351
+ const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
5352
+ anchorEl = widgets.at(-1) ?? null;
5353
+ }
5354
+ if (!anchorEl) return null;
5355
+ return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
5356
+ }
5357
+ function schedulingMountDepth(insertAfter) {
5358
+ if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
5359
+ return insertAfter.split(INSERT_BEFORE_MARKER).length - 1;
5360
+ }
5361
+ function getPageSchedulingEntries(raw) {
5362
+ if (!raw) return [];
5363
+ try {
5364
+ const entries = JSON.parse(raw);
5365
+ const currentPath = window.location.pathname;
5366
+ return entries.filter((e) => e.type === "scheduling" && (!e.pagePath || e.pagePath === currentPath));
5367
+ } catch {
5368
+ return [];
5369
+ }
5370
+ }
5371
+ function isSchedulingWidgetMissing(entry) {
5372
+ const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
5373
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
5374
+ }
5375
+ function hasMissingSchedulingWidgets(entries) {
5376
+ return entries.some(isSchedulingWidgetMissing);
5377
+ }
5378
+ function retryMissingSchedulingMounts(entries, notifyOnConnect = false) {
5379
+ if (!hasMissingSchedulingWidgets(entries)) return;
5380
+ mountSchedulingEntries(entries, notifyOnConnect);
5381
+ }
5382
+ function initSectionsFromContent(content, removeExisting = false) {
5383
+ const raw = content["__ohw_sections"];
5384
+ if (!raw) return;
5385
+ try {
5386
+ if (removeExisting) {
5387
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => el.remove());
5388
+ }
5389
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
5390
+ if (inEditor) getSectionsTracker().textContent = raw;
5391
+ const pageEntries = getPageSchedulingEntries(raw);
5392
+ const preExisting = pageEntries.filter((e) => !isSchedulingWidgetMissing(e));
5393
+ const notifyForEntry = inEditor ? (e) => !e.scheduleId : false;
5394
+ mountSchedulingEntries(pageEntries, notifyForEntry);
5395
+ requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry));
5396
+ setTimeout(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry), 250);
5397
+ for (const entry of preExisting) {
5398
+ window.postMessage({ type: "ow:schedule-config", insertAfter: entry.insertAfter, scheduleId: entry.scheduleId ?? null }, "*");
5399
+ }
5400
+ } catch {
5401
+ }
5402
+ }
5403
+ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
5404
+ const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
5405
+ const sectionId = schedulingSectionId(effectiveInsertAfter);
5406
+ if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
5407
+ const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
5408
+ if (!mountPoint) return false;
5409
+ const container = document.createElement("div");
5410
+ container.dataset.ohwSectionContainer = "scheduling";
5411
+ if (insertBefore) {
5412
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
5413
+ const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
5414
+ if (!beforePoint) return false;
5415
+ beforePoint.insertAdjacentElement("beforebegin", container);
5416
+ } else {
5417
+ let tail = mountPoint;
5418
+ while (tail.nextElementSibling?.dataset.ohwSectionContainer === "scheduling") {
5419
+ tail = tail.nextElementSibling;
5420
+ }
5421
+ tail.insertAdjacentElement("afterend", container);
5422
+ }
5423
+ const root = (0, import_client.createRoot)(container);
5424
+ (0, import_react_dom.flushSync)(() => {
5425
+ root.render(
5426
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5427
+ SchedulingWidget,
5428
+ {
5429
+ notifyOnConnect,
5430
+ initialScheduleId: scheduleId,
5431
+ insertAfter: effectiveInsertAfter
5432
+ }
5433
+ )
5434
+ );
5435
+ });
5436
+ const tracker = getSectionsTracker();
5437
+ let sections = [];
5438
+ try {
5439
+ sections = JSON.parse(tracker.textContent || "[]");
5440
+ } catch {
5441
+ }
5442
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
5443
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
5444
+ sections.push({
5445
+ type: "scheduling",
5446
+ insertAfter: effectiveInsertAfter,
5447
+ pagePath: window.location.pathname,
5448
+ ...scheduleId ? { scheduleId } : {}
5449
+ });
5450
+ tracker.textContent = JSON.stringify(sections);
5451
+ }
5452
+ return true;
5453
+ }
5454
+ function mountSchedulingEntries(entries, notifyOnConnect = false) {
5455
+ const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
5456
+ for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
5457
+ for (let i = pending.length - 1; i >= 0; i--) {
5458
+ const entry = pending[i];
5459
+ const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
5460
+ if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
5461
+ pending.splice(i, 1);
5462
+ }
5463
+ }
5464
+ }
5465
+ }
4474
5466
  function getLinkHref(el) {
4475
5467
  const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
4476
5468
  return anchor?.getAttribute("href") ?? "";
@@ -4517,7 +5509,7 @@ function applyLinkByKey(key, val) {
4517
5509
  }
4518
5510
  function isInsideLinkEditor(target) {
4519
5511
  return Boolean(
4520
- target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest('[data-slot="popover-content"]')
5512
+ 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"]')
4521
5513
  );
4522
5514
  }
4523
5515
  function getHrefKeyFromElement(el) {
@@ -4528,6 +5520,15 @@ function getHrefKeyFromElement(el) {
4528
5520
  if (!key) return null;
4529
5521
  return { anchor, key };
4530
5522
  }
5523
+ function isNavbarButton(el) {
5524
+ return Boolean(el.closest('[data-ohw-role="navbar-button"]'));
5525
+ }
5526
+ function clearHrefKeyHover(anchor) {
5527
+ anchor.removeAttribute("data-ohw-hovered");
5528
+ anchor.querySelectorAll("[data-ohw-hovered]").forEach((el) => {
5529
+ el.removeAttribute("data-ohw-hovered");
5530
+ });
5531
+ }
4531
5532
  function collectSections() {
4532
5533
  return collectSectionsFromDom();
4533
5534
  }
@@ -4651,6 +5652,8 @@ var ICONS = {
4651
5652
  insertUnorderedList: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
4652
5653
  insertOrderedList: '<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>'
4653
5654
  };
5655
+ var TOOLBAR_PILL_PADDING = 2;
5656
+ var TOOLBAR_TOGGLE_GAP = 4;
4654
5657
  var TOOLBAR_GROUPS = [
4655
5658
  [
4656
5659
  { cmd: "bold", title: "Bold" },
@@ -4670,7 +5673,7 @@ var TOOLBAR_GROUPS = [
4670
5673
  ];
4671
5674
  function GlowFrame({ rect, elRef }) {
4672
5675
  const GAP = 6;
4673
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5676
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4674
5677
  "div",
4675
5678
  {
4676
5679
  ref: elRef,
@@ -4689,41 +5692,191 @@ function GlowFrame({ rect, elRef }) {
4689
5692
  }
4690
5693
  );
4691
5694
  }
4692
- function calcToolbarPos(rect, parentScroll) {
5695
+ function getIframeVisibleClip(parentScroll) {
5696
+ if (!parentScroll) return null;
5697
+ const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
5698
+ const clipTop = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5699
+ const clipBottom = Math.min(
5700
+ window.innerHeight,
5701
+ visibleCanvasTop + canvasH - iframeOffsetTop
5702
+ );
5703
+ return { top: clipTop, bottom: Math.max(clipTop, clipBottom) };
5704
+ }
5705
+ function applyVisibleViewport(el, parentScroll) {
5706
+ const clip = getIframeVisibleClip(parentScroll);
5707
+ const top = clip?.top ?? 0;
5708
+ const height = clip ? clip.bottom - clip.top : window.innerHeight;
5709
+ el.style.top = `${top}px`;
5710
+ el.style.height = `${height}px`;
5711
+ }
5712
+ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
5713
+ const isAbove = transform.includes("translateY(-100%)");
5714
+ let visualTop = isAbove ? top - approxH : top;
5715
+ let visualBottom = isAbove ? top : top + approxH;
5716
+ const minTop = clip.top + gap;
5717
+ const maxBottom = clip.bottom - gap;
5718
+ if (visualTop >= minTop && visualBottom <= maxBottom) {
5719
+ return { top, transform };
5720
+ }
5721
+ const belowTop = rect.bottom + gap;
5722
+ if (belowTop + approxH <= maxBottom && belowTop >= minTop) {
5723
+ return { top: belowTop, transform: "translateX(-50%)" };
5724
+ }
5725
+ const aboveTop = rect.top - gap;
5726
+ if (aboveTop - approxH >= minTop && aboveTop <= maxBottom) {
5727
+ return { top: aboveTop, transform: "translateX(-50%) translateY(-100%)" };
5728
+ }
5729
+ if (belowTop + approxH <= maxBottom) {
5730
+ return { top: belowTop, transform: "translateX(-50%)" };
5731
+ }
5732
+ const pinnedTop = Math.min(maxBottom - approxH, Math.max(minTop, clip.top + gap));
5733
+ return { top: pinnedTop + approxH, transform: "translateX(-50%) translateY(-100%)" };
5734
+ }
5735
+ function calcToolbarPos(rect, parentScroll, approxW = 306) {
4693
5736
  const GAP = 8;
4694
- const APPROX_H = 36;
4695
- const APPROX_W = 330;
5737
+ const APPROX_H = 32;
5738
+ const APPROX_W = approxW;
5739
+ const clip = getIframeVisibleClip(parentScroll);
5740
+ const clipTop = clip?.top ?? 0;
4696
5741
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
4697
5742
  const visibleTop = rect.top - canvasTopInIframe;
4698
5743
  const visibleBottom = rect.bottom - canvasTopInIframe;
4699
5744
  const isTall = rect.height > APPROX_H + GAP;
5745
+ const spaceAbove = rect.top - (clipTop + GAP);
5746
+ const fitsAbove = spaceAbove >= APPROX_H + GAP;
4700
5747
  let top;
4701
5748
  let transform;
4702
- if (visibleTop > 0 || !isTall) {
5749
+ if ((visibleTop > 0 || !isTall) && fitsAbove) {
4703
5750
  top = rect.top - GAP;
4704
5751
  transform = "translateX(-50%) translateY(-100%)";
5752
+ } else if (visibleTop > 0 || !isTall) {
5753
+ top = rect.bottom + GAP;
5754
+ transform = "translateX(-50%)";
4705
5755
  } else if (visibleBottom > APPROX_H + GAP) {
4706
- top = canvasTopInIframe + GAP;
5756
+ top = clipTop + GAP;
4707
5757
  transform = "translateX(-50%)";
4708
5758
  } else {
4709
5759
  top = rect.bottom + GAP;
4710
5760
  transform = "translateX(-50%)";
4711
5761
  }
5762
+ if (clip) {
5763
+ const clamped = clampToolbarToClip(top, transform, rect, clip, APPROX_H, GAP);
5764
+ top = clamped.top;
5765
+ transform = clamped.transform;
5766
+ }
4712
5767
  const rawLeft = rect.left + rect.width / 2;
4713
5768
  const left = Math.max(GAP + APPROX_W / 2, Math.min(rawLeft, window.innerWidth - GAP - APPROX_W / 2));
4714
5769
  return { top, left, transform };
4715
5770
  }
5771
+ var EDIT_LINK_ICON = /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
5772
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
5773
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
5774
+ ] });
4716
5775
  function FloatingToolbar({
4717
5776
  rect,
4718
5777
  parentScroll,
4719
5778
  elRef,
5779
+ variant = "rich-text",
4720
5780
  onCommand,
4721
5781
  activeCommands,
4722
5782
  showEditLink,
4723
5783
  onEditLink
4724
5784
  }) {
4725
- const { top, left, transform } = calcToolbarPos(rect, parentScroll);
4726
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
5785
+ const approxW = variant === "link-action" ? 112 : 306;
5786
+ const { top, left, transform } = calcToolbarPos(rect, parentScroll, approxW);
5787
+ if (variant === "link-action") {
5788
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5789
+ "div",
5790
+ {
5791
+ ref: elRef,
5792
+ "data-ohw-toolbar": "",
5793
+ style: {
5794
+ position: "fixed",
5795
+ top,
5796
+ left,
5797
+ transform,
5798
+ zIndex: 2147483647,
5799
+ background: "#fff",
5800
+ border: "1px solid #E7E5E4",
5801
+ borderRadius: 6,
5802
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
5803
+ display: "flex",
5804
+ alignItems: "center",
5805
+ padding: TOOLBAR_PILL_PADDING,
5806
+ gap: TOOLBAR_TOGGLE_GAP,
5807
+ fontFamily: "sans-serif",
5808
+ pointerEvents: "auto",
5809
+ whiteSpace: "nowrap"
5810
+ },
5811
+ onMouseDown: (e) => e.stopPropagation(),
5812
+ children: [
5813
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5814
+ "button",
5815
+ {
5816
+ type: "button",
5817
+ title: "Edit link",
5818
+ onMouseDown: (e) => {
5819
+ e.preventDefault();
5820
+ e.stopPropagation();
5821
+ onEditLink?.();
5822
+ },
5823
+ onClick: (e) => {
5824
+ e.preventDefault();
5825
+ e.stopPropagation();
5826
+ },
5827
+ onMouseEnter: (e) => {
5828
+ e.currentTarget.style.background = "#F5F5F4";
5829
+ },
5830
+ onMouseLeave: (e) => {
5831
+ e.currentTarget.style.background = "transparent";
5832
+ },
5833
+ style: {
5834
+ display: "flex",
5835
+ alignItems: "center",
5836
+ justifyContent: "center",
5837
+ border: "none",
5838
+ background: "transparent",
5839
+ borderRadius: 4,
5840
+ cursor: "pointer",
5841
+ color: "#1C1917",
5842
+ flexShrink: 0,
5843
+ padding: 6
5844
+ },
5845
+ children: EDIT_LINK_ICON
5846
+ }
5847
+ ),
5848
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5849
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5850
+ "button",
5851
+ {
5852
+ type: "button",
5853
+ title: "Coming soon",
5854
+ disabled: true,
5855
+ style: {
5856
+ display: "flex",
5857
+ alignItems: "center",
5858
+ justifyContent: "center",
5859
+ border: "none",
5860
+ background: "transparent",
5861
+ borderRadius: 4,
5862
+ cursor: "not-allowed",
5863
+ color: "#A8A29E",
5864
+ flexShrink: 0,
5865
+ padding: 6,
5866
+ opacity: 0.6
5867
+ },
5868
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: [
5869
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "5", cy: "12", r: "1.5" }),
5870
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "12", cy: "12", r: "1.5" }),
5871
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "19", cy: "12", r: "1.5" })
5872
+ ] })
5873
+ }
5874
+ )
5875
+ ]
5876
+ }
5877
+ );
5878
+ }
5879
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
4727
5880
  "div",
4728
5881
  {
4729
5882
  ref: elRef,
@@ -4740,19 +5893,19 @@ function FloatingToolbar({
4740
5893
  boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
4741
5894
  display: "flex",
4742
5895
  alignItems: "center",
4743
- padding: 4,
4744
- gap: 6,
5896
+ padding: TOOLBAR_PILL_PADDING,
5897
+ gap: TOOLBAR_TOGGLE_GAP,
4745
5898
  fontFamily: "sans-serif",
4746
5899
  pointerEvents: "auto",
4747
5900
  whiteSpace: "nowrap"
4748
5901
  },
4749
5902
  onMouseDown: (e) => e.stopPropagation(),
4750
5903
  children: [
4751
- TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react3.default.Fragment, { children: [
4752
- gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5904
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react5.default.Fragment, { children: [
5905
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
4753
5906
  btns.map((btn) => {
4754
5907
  const isActive = activeCommands.has(btn.cmd);
4755
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5908
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4756
5909
  "button",
4757
5910
  {
4758
5911
  title: btn.title,
@@ -4778,7 +5931,7 @@ function FloatingToolbar({
4778
5931
  flexShrink: 0,
4779
5932
  padding: 6
4780
5933
  },
4781
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5934
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4782
5935
  "svg",
4783
5936
  {
4784
5937
  width: "16",
@@ -4798,7 +5951,7 @@ function FloatingToolbar({
4798
5951
  );
4799
5952
  })
4800
5953
  ] }, gi)),
4801
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5954
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4802
5955
  "button",
4803
5956
  {
4804
5957
  type: "button",
@@ -4830,10 +5983,7 @@ function FloatingToolbar({
4830
5983
  flexShrink: 0,
4831
5984
  padding: 6
4832
5985
  },
4833
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
4834
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
4835
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
4836
- ] })
5986
+ children: EDIT_LINK_ICON
4837
5987
  }
4838
5988
  ) : null
4839
5989
  ]
@@ -4850,7 +6000,7 @@ function StateToggle({
4850
6000
  states,
4851
6001
  onStateChange
4852
6002
  }) {
4853
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6003
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4854
6004
  ToggleGroup,
4855
6005
  {
4856
6006
  "data-ohw-state-toggle": "",
@@ -4864,18 +6014,35 @@ function StateToggle({
4864
6014
  left: rect.right - 8,
4865
6015
  transform: "translateX(-100%)"
4866
6016
  },
4867
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6017
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
4868
6018
  }
4869
6019
  );
4870
6020
  }
4871
6021
  var contentCache = /* @__PURE__ */ new Map();
6022
+ function resolveSubdomain(subdomainFromQuery) {
6023
+ if (subdomainFromQuery) return subdomainFromQuery;
6024
+ if (typeof window !== "undefined") {
6025
+ const parts = window.location.hostname.split(".");
6026
+ if (parts.length >= 3 && parts[0] !== "www") return parts[0];
6027
+ }
6028
+ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
6029
+ if (siteUrl) {
6030
+ try {
6031
+ const host = new URL(siteUrl).hostname;
6032
+ const siteParts = host.split(".");
6033
+ if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
6034
+ } catch {
6035
+ }
6036
+ }
6037
+ return "";
6038
+ }
4872
6039
  function OhhwellsBridge() {
4873
6040
  const pathname = (0, import_navigation.usePathname)();
4874
6041
  const router = (0, import_navigation.useRouter)();
4875
6042
  const searchParams = (0, import_navigation.useSearchParams)();
4876
6043
  const isEditMode = isEditSessionActive();
4877
- const [bridgeRoot, setBridgeRoot] = (0, import_react3.useState)(null);
4878
- (0, import_react3.useEffect)(() => {
6044
+ const [bridgeRoot, setBridgeRoot] = (0, import_react5.useState)(null);
6045
+ (0, import_react5.useEffect)(() => {
4879
6046
  const figtreeFontId = "ohw-figtree-font";
4880
6047
  if (!document.getElementById(figtreeFontId)) {
4881
6048
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -4898,55 +6065,70 @@ function OhhwellsBridge() {
4898
6065
  };
4899
6066
  }, []);
4900
6067
  const subdomainFromQuery = searchParams.get("subdomain");
4901
- const subdomain = subdomainFromQuery ?? (() => {
4902
- if (typeof window === "undefined") return "";
4903
- const parts = window.location.hostname.split(".");
4904
- return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
4905
- })();
4906
- const postToParent = (0, import_react3.useCallback)((data) => {
6068
+ const subdomain = resolveSubdomain(subdomainFromQuery);
6069
+ const postToParent = (0, import_react5.useCallback)((data) => {
4907
6070
  if (typeof window !== "undefined" && window.parent !== window) {
4908
6071
  window.parent.postMessage(data, "*");
4909
6072
  }
4910
6073
  }, []);
4911
- const [fetchState, setFetchState] = (0, import_react3.useState)("idle");
4912
- const autoSaveTimers = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
4913
- const activeElRef = (0, import_react3.useRef)(null);
4914
- const originalContentRef = (0, import_react3.useRef)(null);
4915
- const activeStateElRef = (0, import_react3.useRef)(null);
4916
- const parentScrollRef = (0, import_react3.useRef)(null);
4917
- const toolbarElRef = (0, import_react3.useRef)(null);
4918
- const glowElRef = (0, import_react3.useRef)(null);
4919
- const hoveredImageRef = (0, import_react3.useRef)(null);
4920
- const hoveredImageHasTextOverlapRef = (0, import_react3.useRef)(false);
4921
- const imageUnhoverTimerRef = (0, import_react3.useRef)(null);
4922
- const imageShowTimerRef = (0, import_react3.useRef)(null);
4923
- const editStylesRef = (0, import_react3.useRef)(null);
4924
- const activateRef = (0, import_react3.useRef)(() => {
6074
+ const [fetchState, setFetchState] = (0, import_react5.useState)("idle");
6075
+ const autoSaveTimers = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
6076
+ const activeElRef = (0, import_react5.useRef)(null);
6077
+ const selectedElRef = (0, import_react5.useRef)(null);
6078
+ const originalContentRef = (0, import_react5.useRef)(null);
6079
+ const activeStateElRef = (0, import_react5.useRef)(null);
6080
+ const parentScrollRef = (0, import_react5.useRef)(null);
6081
+ const visibleViewportRef = (0, import_react5.useRef)(null);
6082
+ const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react5.useState)(null);
6083
+ const attachVisibleViewport = (0, import_react5.useCallback)((node) => {
6084
+ visibleViewportRef.current = node;
6085
+ setDialogPortalContainer(node);
6086
+ if (node) applyVisibleViewport(node, parentScrollRef.current);
6087
+ }, []);
6088
+ const toolbarElRef = (0, import_react5.useRef)(null);
6089
+ const glowElRef = (0, import_react5.useRef)(null);
6090
+ const hoveredImageRef = (0, import_react5.useRef)(null);
6091
+ const hoveredImageHasTextOverlapRef = (0, import_react5.useRef)(false);
6092
+ const hoveredGapRef = (0, import_react5.useRef)(null);
6093
+ const imageUnhoverTimerRef = (0, import_react5.useRef)(null);
6094
+ const imageShowTimerRef = (0, import_react5.useRef)(null);
6095
+ const editStylesRef = (0, import_react5.useRef)(null);
6096
+ const activateRef = (0, import_react5.useRef)(() => {
6097
+ });
6098
+ const deactivateRef = (0, import_react5.useRef)(() => {
4925
6099
  });
4926
- const deactivateRef = (0, import_react3.useRef)(() => {
6100
+ const selectRef = (0, import_react5.useRef)(() => {
4927
6101
  });
4928
- const refreshActiveCommandsRef = (0, import_react3.useRef)(() => {
6102
+ const deselectRef = (0, import_react5.useRef)(() => {
4929
6103
  });
4930
- const postToParentRef = (0, import_react3.useRef)(postToParent);
6104
+ const refreshActiveCommandsRef = (0, import_react5.useRef)(() => {
6105
+ });
6106
+ const postToParentRef = (0, import_react5.useRef)(postToParent);
4931
6107
  postToParentRef.current = postToParent;
4932
- const [toolbarRect, setToolbarRect] = (0, import_react3.useState)(null);
4933
- const [toggleState, setToggleState] = (0, import_react3.useState)(null);
4934
- const [maxBadge, setMaxBadge] = (0, import_react3.useState)(null);
4935
- const [activeCommands, setActiveCommands] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
4936
- const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react3.useState)(false);
4937
- const [linkPopover, setLinkPopover] = (0, import_react3.useState)(null);
4938
- const [sitePages, setSitePages] = (0, import_react3.useState)([]);
4939
- const [sectionsByPath, setSectionsByPath] = (0, import_react3.useState)({});
4940
- const sectionsPrefetchGenRef = (0, import_react3.useRef)(0);
4941
- const setLinkPopoverRef = (0, import_react3.useRef)(setLinkPopover);
4942
- const linkPopoverPanelRef = (0, import_react3.useRef)(null);
4943
- const linkPopoverOpenRef = (0, import_react3.useRef)(false);
4944
- const linkPopoverGraceUntilRef = (0, import_react3.useRef)(0);
6108
+ const sectionsLoadedRef = (0, import_react5.useRef)(false);
6109
+ const pendingScheduleConfigRequests = (0, import_react5.useRef)([]);
6110
+ const [toolbarRect, setToolbarRect] = (0, import_react5.useState)(null);
6111
+ const [toolbarVariant, setToolbarVariant] = (0, import_react5.useState)("none");
6112
+ const toolbarVariantRef = (0, import_react5.useRef)("none");
6113
+ toolbarVariantRef.current = toolbarVariant;
6114
+ const [toggleState, setToggleState] = (0, import_react5.useState)(null);
6115
+ const [maxBadge, setMaxBadge] = (0, import_react5.useState)(null);
6116
+ const [activeCommands, setActiveCommands] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
6117
+ const [sectionGap, setSectionGap] = (0, import_react5.useState)(null);
6118
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react5.useState)(false);
6119
+ const [linkPopover, setLinkPopover] = (0, import_react5.useState)(null);
6120
+ const [sitePages, setSitePages] = (0, import_react5.useState)([]);
6121
+ const [sectionsByPath, setSectionsByPath] = (0, import_react5.useState)({});
6122
+ const sectionsPrefetchGenRef = (0, import_react5.useRef)(0);
6123
+ const setLinkPopoverRef = (0, import_react5.useRef)(setLinkPopover);
6124
+ const linkPopoverPanelRef = (0, import_react5.useRef)(null);
6125
+ const linkPopoverOpenRef = (0, import_react5.useRef)(false);
6126
+ const linkPopoverGraceUntilRef = (0, import_react5.useRef)(0);
4945
6127
  setLinkPopoverRef.current = setLinkPopover;
4946
6128
  const bumpLinkPopoverGrace = () => {
4947
6129
  linkPopoverGraceUntilRef.current = Date.now() + 350;
4948
6130
  };
4949
- const runSectionsPrefetch = (0, import_react3.useCallback)((pages) => {
6131
+ const runSectionsPrefetch = (0, import_react5.useCallback)((pages) => {
4950
6132
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
4951
6133
  const gen = ++sectionsPrefetchGenRef.current;
4952
6134
  const paths = pages.map((p) => p.path);
@@ -4965,17 +6147,43 @@ function OhhwellsBridge() {
4965
6147
  );
4966
6148
  });
4967
6149
  }, [isEditMode, pathname]);
4968
- const runSectionsPrefetchRef = (0, import_react3.useRef)(runSectionsPrefetch);
6150
+ const runSectionsPrefetchRef = (0, import_react5.useRef)(runSectionsPrefetch);
4969
6151
  runSectionsPrefetchRef.current = runSectionsPrefetch;
4970
- (0, import_react3.useEffect)(() => {
6152
+ (0, import_react5.useEffect)(() => {
4971
6153
  if (!linkPopover) return;
4972
6154
  if (hoveredImageRef.current) {
4973
6155
  hoveredImageRef.current = null;
4974
6156
  hoveredImageHasTextOverlapRef.current = false;
4975
6157
  }
6158
+ hoveredGapRef.current = null;
6159
+ setSectionGap(null);
4976
6160
  postToParent({ type: "ow:image-unhover" });
6161
+ postToParent({ type: "ow:link-modal-lock", locked: true });
6162
+ const html = document.documentElement;
6163
+ const body = document.body;
6164
+ const prevHtmlOverflow = html.style.overflow;
6165
+ const prevBodyOverflow = body.style.overflow;
6166
+ html.style.overflow = "hidden";
6167
+ body.style.overflow = "hidden";
6168
+ const preventBackgroundScroll = (e) => {
6169
+ const target = e.target;
6170
+ if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6171
+ return;
6172
+ }
6173
+ e.preventDefault();
6174
+ };
6175
+ const scrollOpts = { passive: false, capture: true };
6176
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6177
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6178
+ return () => {
6179
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6180
+ html.style.overflow = prevHtmlOverflow;
6181
+ body.style.overflow = prevBodyOverflow;
6182
+ document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6183
+ document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6184
+ };
4977
6185
  }, [linkPopover, postToParent]);
4978
- (0, import_react3.useEffect)(() => {
6186
+ (0, import_react5.useEffect)(() => {
4979
6187
  if (!isEditMode) return;
4980
6188
  const useFixtures = shouldUseDevFixtures();
4981
6189
  if (useFixtures) {
@@ -4999,21 +6207,27 @@ function OhhwellsBridge() {
4999
6207
  if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
5000
6208
  return () => window.removeEventListener("message", onSitePages);
5001
6209
  }, [isEditMode, postToParent]);
5002
- (0, import_react3.useEffect)(() => {
6210
+ (0, import_react5.useEffect)(() => {
5003
6211
  if (!isEditMode || shouldUseDevFixtures()) return;
5004
6212
  void loadAllSectionsManifest().then((manifest) => {
5005
6213
  if (Object.keys(manifest).length === 0) return;
5006
6214
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
5007
6215
  });
5008
6216
  }, [isEditMode]);
5009
- (0, import_react3.useEffect)(() => {
6217
+ (0, import_react5.useEffect)(() => {
5010
6218
  const update = () => {
5011
- const el = activeElRef.current;
6219
+ const el = activeElRef.current ?? selectedElRef.current;
5012
6220
  if (el) setToolbarRect(el.getBoundingClientRect());
5013
6221
  setToggleState((prev) => {
5014
6222
  if (!prev || !activeStateElRef.current) return prev;
5015
6223
  return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
5016
6224
  });
6225
+ setSectionGap((prev) => {
6226
+ if (!prev) return prev;
6227
+ const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
6228
+ if (!anchor) return prev;
6229
+ return { ...prev, y: anchor.getBoundingClientRect().bottom };
6230
+ });
5017
6231
  };
5018
6232
  const vvp = window.visualViewport;
5019
6233
  if (!vvp) return;
@@ -5024,10 +6238,33 @@ function OhhwellsBridge() {
5024
6238
  vvp.removeEventListener("resize", update);
5025
6239
  };
5026
6240
  }, []);
5027
- const refreshStateRules = (0, import_react3.useCallback)(() => {
6241
+ const refreshStateRules = (0, import_react5.useCallback)(() => {
5028
6242
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
5029
6243
  }, []);
5030
- const deactivate = (0, import_react3.useCallback)(() => {
6244
+ const processConfigRequest = (0, import_react5.useCallback)((insertAfterVal) => {
6245
+ const tracker = getSectionsTracker();
6246
+ let entries = [];
6247
+ try {
6248
+ entries = JSON.parse(tracker.textContent || "[]");
6249
+ } catch {
6250
+ }
6251
+ const path = window.location.pathname;
6252
+ const entry = entries.find(
6253
+ (e) => e.type === "scheduling" && e.insertAfter === insertAfterVal && (!e.pagePath || e.pagePath === path)
6254
+ );
6255
+ if (entry) {
6256
+ window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: entry.scheduleId ?? null }, "*");
6257
+ return;
6258
+ }
6259
+ const newEntry = { type: "scheduling", insertAfter: insertAfterVal, pagePath: path };
6260
+ entries.push(newEntry);
6261
+ tracker.textContent = JSON.stringify(entries);
6262
+ if (isEditMode) {
6263
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
6264
+ }
6265
+ window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6266
+ }, [isEditMode]);
6267
+ const deactivate = (0, import_react5.useCallback)(() => {
5031
6268
  const el = activeElRef.current;
5032
6269
  if (!el) return;
5033
6270
  const key = el.dataset.ohwKey;
@@ -5047,19 +6284,41 @@ function OhhwellsBridge() {
5047
6284
  }
5048
6285
  el.removeAttribute("contenteditable");
5049
6286
  activeElRef.current = null;
5050
- setToolbarRect(null);
6287
+ if (!selectedElRef.current) {
6288
+ setToolbarRect(null);
6289
+ setToolbarVariant("none");
6290
+ }
5051
6291
  setMaxBadge(null);
5052
6292
  setActiveCommands(/* @__PURE__ */ new Set());
5053
6293
  setToolbarShowEditLink(false);
5054
6294
  postToParent({ type: "ow:exit-edit" });
5055
6295
  }, [postToParent]);
5056
- const activate = (0, import_react3.useCallback)((el) => {
6296
+ const deselect = (0, import_react5.useCallback)(() => {
6297
+ selectedElRef.current = null;
6298
+ if (!activeElRef.current) {
6299
+ setToolbarRect(null);
6300
+ setToolbarVariant("none");
6301
+ }
6302
+ }, []);
6303
+ const select = (0, import_react5.useCallback)((anchor) => {
6304
+ if (!isNavbarButton(anchor)) return;
6305
+ if (activeElRef.current) deactivate();
6306
+ selectedElRef.current = anchor;
6307
+ clearHrefKeyHover(anchor);
6308
+ setToolbarVariant("link-action");
6309
+ setToolbarRect(anchor.getBoundingClientRect());
6310
+ setToolbarShowEditLink(false);
6311
+ setActiveCommands(/* @__PURE__ */ new Set());
6312
+ }, [deactivate]);
6313
+ const activate = (0, import_react5.useCallback)((el) => {
5057
6314
  if (activeElRef.current === el) return;
6315
+ selectedElRef.current = null;
5058
6316
  deactivate();
5059
6317
  if (hoveredImageRef.current) {
5060
6318
  hoveredImageRef.current = null;
5061
6319
  postToParentRef.current({ type: "ow:image-unhover" });
5062
6320
  }
6321
+ setToolbarVariant("rich-text");
5063
6322
  el.setAttribute("contenteditable", "true");
5064
6323
  el.removeAttribute("data-ohw-hovered");
5065
6324
  activeElRef.current = el;
@@ -5072,7 +6331,9 @@ function OhhwellsBridge() {
5072
6331
  }, [deactivate, postToParent]);
5073
6332
  activateRef.current = activate;
5074
6333
  deactivateRef.current = deactivate;
5075
- (0, import_react3.useLayoutEffect)(() => {
6334
+ selectRef.current = select;
6335
+ deselectRef.current = deselect;
6336
+ (0, import_react5.useLayoutEffect)(() => {
5076
6337
  if (!subdomain || isEditMode) {
5077
6338
  setFetchState("done");
5078
6339
  return;
@@ -5080,6 +6341,7 @@ function OhhwellsBridge() {
5080
6341
  const applyContent = (content) => {
5081
6342
  const imageLoads = [];
5082
6343
  for (const [key, val] of Object.entries(content)) {
6344
+ if (key === "__ohw_sections") continue;
5083
6345
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
5084
6346
  if (el.dataset.ohwEditable === "image") {
5085
6347
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -5101,6 +6363,9 @@ function OhhwellsBridge() {
5101
6363
  });
5102
6364
  applyLinkByKey(key, val);
5103
6365
  }
6366
+ initSectionsFromContent(content, true);
6367
+ sectionsLoadedRef.current = true;
6368
+ pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
5104
6369
  return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
5105
6370
  }) : Promise.resolve();
5106
6371
  };
@@ -5125,12 +6390,15 @@ function OhhwellsBridge() {
5125
6390
  cancelled = true;
5126
6391
  };
5127
6392
  }, [subdomain, isEditMode, pathname]);
5128
- (0, import_react3.useEffect)(() => {
6393
+ (0, import_react5.useEffect)(() => {
5129
6394
  if (!subdomain || isEditMode) return;
6395
+ let debounceTimer = null;
5130
6396
  const applyFromCache = () => {
5131
6397
  const content = contentCache.get(subdomain);
5132
6398
  if (!content) return;
6399
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
5133
6400
  for (const [key, val] of Object.entries(content)) {
6401
+ if (key === "__ohw_sections") continue;
5134
6402
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
5135
6403
  if (el.dataset.ohwEditable === "image") {
5136
6404
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -5147,21 +6415,34 @@ function OhhwellsBridge() {
5147
6415
  applyLinkByKey(key, val);
5148
6416
  }
5149
6417
  };
5150
- applyFromCache();
5151
- const observer = new MutationObserver(applyFromCache);
6418
+ const scheduleApply = () => {
6419
+ if (debounceTimer) clearTimeout(debounceTimer);
6420
+ debounceTimer = setTimeout(applyFromCache, 150);
6421
+ };
6422
+ scheduleApply();
6423
+ const observer = new MutationObserver(scheduleApply);
5152
6424
  observer.observe(document.body, { childList: true, subtree: true });
5153
- return () => observer.disconnect();
5154
- }, [subdomain, isEditMode]);
5155
- (0, import_react3.useLayoutEffect)(() => {
6425
+ return () => {
6426
+ observer.disconnect();
6427
+ if (debounceTimer) clearTimeout(debounceTimer);
6428
+ };
6429
+ }, [subdomain, isEditMode, pathname]);
6430
+ (0, import_react5.useLayoutEffect)(() => {
5156
6431
  const el = document.getElementById("ohw-loader");
5157
6432
  if (!el) return;
5158
6433
  const visible = Boolean(subdomain) && fetchState !== "done";
5159
6434
  el.style.display = visible ? "flex" : "none";
5160
6435
  }, [subdomain, fetchState]);
5161
- (0, import_react3.useEffect)(() => {
6436
+ (0, import_react5.useEffect)(() => {
5162
6437
  postToParent({ type: "ow:navigation", path: pathname });
5163
6438
  }, [pathname, postToParent]);
5164
- (0, import_react3.useEffect)(() => {
6439
+ (0, import_react5.useEffect)(() => {
6440
+ if (!isEditMode) return;
6441
+ setLinkPopover(null);
6442
+ deselectRef.current();
6443
+ deactivateRef.current();
6444
+ }, [pathname, isEditMode]);
6445
+ (0, import_react5.useEffect)(() => {
5165
6446
  if (!isEditMode) return;
5166
6447
  const measure = () => {
5167
6448
  const h = document.body.scrollHeight;
@@ -5185,7 +6466,7 @@ function OhhwellsBridge() {
5185
6466
  window.removeEventListener("resize", handleResize);
5186
6467
  };
5187
6468
  }, [pathname, isEditMode, postToParent]);
5188
- (0, import_react3.useEffect)(() => {
6469
+ (0, import_react5.useEffect)(() => {
5189
6470
  if (!subdomainFromQuery || isEditMode) return;
5190
6471
  const handleClick = (e) => {
5191
6472
  const anchor = e.target.closest("a");
@@ -5201,7 +6482,7 @@ function OhhwellsBridge() {
5201
6482
  document.addEventListener("click", handleClick, true);
5202
6483
  return () => document.removeEventListener("click", handleClick, true);
5203
6484
  }, [subdomainFromQuery, isEditMode, router]);
5204
- (0, import_react3.useEffect)(() => {
6485
+ (0, import_react5.useEffect)(() => {
5205
6486
  if (!isEditMode) {
5206
6487
  editStylesRef.current?.base.remove();
5207
6488
  editStylesRef.current?.forceHover.remove();
@@ -5271,12 +6552,12 @@ function OhhwellsBridge() {
5271
6552
  if (editable.dataset.ohwEditable === "link") {
5272
6553
  e.preventDefault();
5273
6554
  e.stopPropagation();
6555
+ deselectRef.current();
5274
6556
  deactivateRef.current();
5275
6557
  bumpLinkPopoverGrace();
5276
6558
  setLinkPopoverRef.current({
5277
6559
  key: editable.dataset.ohwKey ?? "",
5278
- target: getLinkHref(editable),
5279
- rect: editable.getBoundingClientRect()
6560
+ target: getLinkHref(editable)
5280
6561
  });
5281
6562
  return;
5282
6563
  }
@@ -5286,11 +6567,31 @@ function OhhwellsBridge() {
5286
6567
  postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
5287
6568
  return;
5288
6569
  }
6570
+ const hrefCtx = getHrefKeyFromElement(editable);
6571
+ if (hrefCtx && isNavbarButton(hrefCtx.anchor)) {
6572
+ e.preventDefault();
6573
+ e.stopPropagation();
6574
+ const { anchor: anchor2 } = hrefCtx;
6575
+ if (selectedElRef.current === anchor2) {
6576
+ activateRef.current(editable);
6577
+ return;
6578
+ }
6579
+ selectRef.current(anchor2);
6580
+ return;
6581
+ }
5289
6582
  e.preventDefault();
5290
6583
  e.stopPropagation();
5291
6584
  activateRef.current(editable);
5292
6585
  return;
5293
6586
  }
6587
+ const hrefAnchor = target.closest("[data-ohw-href-key]");
6588
+ if (hrefAnchor && isNavbarButton(hrefAnchor)) {
6589
+ e.preventDefault();
6590
+ e.stopPropagation();
6591
+ if (selectedElRef.current === hrefAnchor) return;
6592
+ selectRef.current(hrefAnchor);
6593
+ return;
6594
+ }
5294
6595
  if (activeElRef.current) {
5295
6596
  const sel = window.getSelection();
5296
6597
  if (sel && !sel.isCollapsed) {
@@ -5305,15 +6606,22 @@ function OhhwellsBridge() {
5305
6606
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
5306
6607
  if (hrefCtx && (hrefCtx.anchor === target || hrefCtx.anchor.contains(target))) return;
5307
6608
  }
6609
+ if (linkPopoverOpenRef.current && selectedElRef.current) {
6610
+ const selected = selectedElRef.current;
6611
+ if (selected === target || selected.contains(target)) return;
6612
+ }
5308
6613
  if (linkPopoverOpenRef.current) {
5309
6614
  setLinkPopoverRef.current(null);
5310
6615
  return;
5311
6616
  }
6617
+ deselectRef.current();
5312
6618
  deactivateRef.current();
5313
6619
  };
5314
6620
  const handleMouseOver = (e) => {
5315
6621
  const editable = e.target.closest("[data-ohw-editable]");
5316
6622
  if (!editable) return;
6623
+ const selected = selectedElRef.current;
6624
+ if (selected && (selected === editable || selected.contains(editable))) return;
5317
6625
  if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image" && !editable.hasAttribute("contenteditable")) {
5318
6626
  editable.setAttribute("data-ohw-hovered", "");
5319
6627
  }
@@ -5449,7 +6757,7 @@ function OhhwellsBridge() {
5449
6757
  }
5450
6758
  return;
5451
6759
  }
5452
- if (topEl?.closest("[data-ohw-link-popover-root]") || topEl?.closest("[data-ohw-link-modal-root]")) {
6760
+ if (topEl?.closest("[data-ohw-link-popover-root]") || topEl?.closest("[data-ohw-link-modal-root]") || topEl?.closest("[data-ohw-link-page-dropdown]")) {
5453
6761
  if (hoveredImageRef.current) {
5454
6762
  hoveredImageRef.current = null;
5455
6763
  hoveredImageHasTextOverlapRef.current = false;
@@ -5502,6 +6810,15 @@ function OhhwellsBridge() {
5502
6810
  textEditable.setAttribute("data-ohw-hovered", "");
5503
6811
  return;
5504
6812
  }
6813
+ if (hoveredGapRef.current) {
6814
+ if (hoveredImageRef.current) {
6815
+ hoveredImageRef.current = null;
6816
+ resumeAnimTracks();
6817
+ postToParentRef.current({ type: "ow:image-unhover" });
6818
+ }
6819
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
6820
+ return;
6821
+ }
5505
6822
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
5506
6823
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
5507
6824
  hoveredImageRef.current = imgEl;
@@ -5567,8 +6884,37 @@ function OhhwellsBridge() {
5567
6884
  }
5568
6885
  }
5569
6886
  };
6887
+ const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
6888
+ if (linkPopoverOpenRef.current) {
6889
+ if (hoveredGapRef.current) {
6890
+ hoveredGapRef.current = null;
6891
+ setSectionGap(null);
6892
+ }
6893
+ return;
6894
+ }
6895
+ const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
6896
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
6897
+ const ZONE = 20;
6898
+ for (let i = 0; i < sections.length; i++) {
6899
+ const a = sections[i];
6900
+ const b = sections[i + 1] ?? null;
6901
+ const boundaryY = a.getBoundingClientRect().bottom;
6902
+ if (Math.abs(y - boundaryY) <= ZONE) {
6903
+ const insertAfter = a.dataset.ohwSection ?? "";
6904
+ const insertBefore = b?.dataset.ohwSection ?? null;
6905
+ hoveredGapRef.current = { insertAfter, insertBefore };
6906
+ setSectionGap({ insertAfter, insertBefore, y: boundaryY });
6907
+ return;
6908
+ }
6909
+ }
6910
+ if (hoveredGapRef.current) {
6911
+ hoveredGapRef.current = null;
6912
+ setSectionGap(null);
6913
+ }
6914
+ };
5570
6915
  const handleMouseMove = (e) => {
5571
6916
  const { clientX, clientY } = e;
6917
+ probeSectionGapAt(clientX, clientY);
5572
6918
  probeImageAt(clientX, clientY);
5573
6919
  probeHoverCardsAt(clientX, clientY);
5574
6920
  };
@@ -5576,6 +6922,7 @@ function OhhwellsBridge() {
5576
6922
  if (e.data?.type !== "ow:pointer-sync") return;
5577
6923
  const { clientX, clientY } = e.data;
5578
6924
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
6925
+ probeSectionGapAt(clientX, clientY);
5579
6926
  probeImageAt(clientX, clientY);
5580
6927
  probeHoverCardsAt(clientX, clientY);
5581
6928
  };
@@ -5733,7 +7080,12 @@ function OhhwellsBridge() {
5733
7080
  if (e.data?.type !== "ow:hydrate") return;
5734
7081
  const content = e.data.content;
5735
7082
  if (!content) return;
7083
+ let sectionsJson = null;
5736
7084
  for (const [key, val] of Object.entries(content)) {
7085
+ if (key === "__ohw_sections") {
7086
+ sectionsJson = val;
7087
+ continue;
7088
+ }
5737
7089
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
5738
7090
  if (el.dataset.ohwEditable === "image") {
5739
7091
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -5748,6 +7100,11 @@ function OhhwellsBridge() {
5748
7100
  });
5749
7101
  applyLinkByKey(key, val);
5750
7102
  }
7103
+ if (sectionsJson) {
7104
+ initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
7105
+ sectionsLoadedRef.current = true;
7106
+ pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7107
+ }
5751
7108
  postToParentRef.current({ type: "ow:hydrate-done" });
5752
7109
  };
5753
7110
  window.addEventListener("message", handleHydrate);
@@ -5758,6 +7115,7 @@ function OhhwellsBridge() {
5758
7115
  setLinkPopoverRef.current(null);
5759
7116
  return;
5760
7117
  }
7118
+ deselectRef.current();
5761
7119
  deactivateRef.current();
5762
7120
  };
5763
7121
  window.addEventListener("message", handleDeactivate);
@@ -5766,6 +7124,10 @@ function OhhwellsBridge() {
5766
7124
  setLinkPopoverRef.current(null);
5767
7125
  return;
5768
7126
  }
7127
+ if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7128
+ deselectRef.current();
7129
+ return;
7130
+ }
5769
7131
  if (e.key !== "Escape") return;
5770
7132
  const el = activeElRef.current;
5771
7133
  if (el && originalContentRef.current !== null) {
@@ -5775,13 +7137,16 @@ function OhhwellsBridge() {
5775
7137
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: originalContentRef.current }] });
5776
7138
  }
5777
7139
  }
7140
+ deselectRef.current();
5778
7141
  deactivateRef.current();
5779
7142
  };
5780
7143
  const handleScroll = () => {
5781
- if (activeElRef.current) {
5782
- const r2 = activeElRef.current.getBoundingClientRect();
7144
+ const focusEl = activeElRef.current ?? selectedElRef.current;
7145
+ if (focusEl) {
7146
+ const r2 = focusEl.getBoundingClientRect();
5783
7147
  applyToolbarPos(r2);
5784
- setMaxBadge((prev) => prev ? { ...prev, rect: r2 } : null);
7148
+ setToolbarRect(r2);
7149
+ setMaxBadge((prev) => prev && activeElRef.current ? { ...prev, rect: r2 } : prev);
5785
7150
  }
5786
7151
  if (activeStateElRef.current) {
5787
7152
  const rect = activeStateElRef.current.getBoundingClientRect();
@@ -5800,7 +7165,63 @@ function OhhwellsBridge() {
5800
7165
  };
5801
7166
  const handleSave = (e) => {
5802
7167
  if (e.data?.type !== "ow:save") return;
5803
- postToParentRef.current({ type: "ow:save-result", nodes: collectEditableNodes() });
7168
+ const nodes = collectEditableNodes();
7169
+ const tracker = document.querySelector("[data-ohw-sections-tracker]");
7170
+ if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
7171
+ postToParentRef.current({ type: "ow:save-result", nodes });
7172
+ };
7173
+ const handleInsertSection = (e) => {
7174
+ if (e.data?.type !== "ow:insert-section") return;
7175
+ const { widgetType, insertAfter, insertBefore } = e.data;
7176
+ if (widgetType !== "scheduling") return;
7177
+ const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
7178
+ if (inserted) {
7179
+ const tracker = getSectionsTracker();
7180
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
7181
+ const h = document.documentElement.scrollHeight;
7182
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
7183
+ }
7184
+ };
7185
+ const handleSwitchSchedule = (e) => {
7186
+ if (e.data?.type !== "ow:switch-schedule") return;
7187
+ const scheduleId = e.data.scheduleId;
7188
+ const insertAfter = e.data.insertAfter;
7189
+ if (!scheduleId || !insertAfter) return;
7190
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
7191
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
7192
+ };
7193
+ const handleScheduleLinked = (e) => {
7194
+ if (e.data?.type !== "ow:schedule-linked") return;
7195
+ const scheduleId = e.data.scheduleId;
7196
+ const insertAfter = e.data.insertAfter;
7197
+ if (!scheduleId || !insertAfter) return;
7198
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
7199
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
7200
+ };
7201
+ const handleClearSchedulingWidget = (e) => {
7202
+ if (e.data?.type !== "ow:clear-scheduling-widget") return;
7203
+ const insertAfter = e.data.insertAfter;
7204
+ if (!insertAfter) return;
7205
+ const text = updateSectionScheduleId(insertAfter, null);
7206
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
7207
+ };
7208
+ const handleRemoveSchedulingSection = (e) => {
7209
+ if (e.data?.type !== "ow:remove-scheduling-section") return;
7210
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
7211
+ el.remove();
7212
+ });
7213
+ const tracker = getSectionsTracker();
7214
+ let sections = [];
7215
+ try {
7216
+ sections = JSON.parse(tracker.textContent || "[]");
7217
+ } catch {
7218
+ }
7219
+ const currentPath = window.location.pathname;
7220
+ const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
7221
+ tracker.textContent = JSON.stringify(updated);
7222
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
7223
+ const h = document.documentElement.scrollHeight;
7224
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
5804
7225
  };
5805
7226
  const handleSelectionChange = () => {
5806
7227
  if (!activeElRef.current) return;
@@ -5850,8 +7271,9 @@ function OhhwellsBridge() {
5850
7271
  };
5851
7272
  const applyToolbarPos = (rect) => {
5852
7273
  const ps = parentScrollRef.current;
7274
+ const approxW = toolbarVariantRef.current === "link-action" ? 112 : 306;
5853
7275
  if (toolbarElRef.current) {
5854
- const { top, left, transform } = calcToolbarPos(rect, ps);
7276
+ const { top, left, transform } = calcToolbarPos(rect, ps, approxW);
5855
7277
  toolbarElRef.current.style.top = `${top}px`;
5856
7278
  toolbarElRef.current.style.left = `${left}px`;
5857
7279
  toolbarElRef.current.style.transform = transform;
@@ -5866,7 +7288,11 @@ function OhhwellsBridge() {
5866
7288
  if (e.data?.type !== "ow:parent-scroll") return;
5867
7289
  const { iframeOffsetTop, headerH, canvasH } = e.data;
5868
7290
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
5869
- if (activeElRef.current) applyToolbarPos(activeElRef.current.getBoundingClientRect());
7291
+ if (visibleViewportRef.current) {
7292
+ applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
7293
+ }
7294
+ const focusEl = activeElRef.current ?? selectedElRef.current;
7295
+ if (focusEl) applyToolbarPos(focusEl.getBoundingClientRect());
5870
7296
  };
5871
7297
  const handleClickAt = (e) => {
5872
7298
  if (e.data?.type !== "ow:click-at") return;
@@ -5905,12 +7331,23 @@ function OhhwellsBridge() {
5905
7331
  deactivateRef.current();
5906
7332
  };
5907
7333
  window.addEventListener("message", handleSave);
7334
+ window.addEventListener("message", handleInsertSection);
7335
+ window.addEventListener("message", handleSwitchSchedule);
7336
+ window.addEventListener("message", handleScheduleLinked);
7337
+ window.addEventListener("message", handleClearSchedulingWidget);
7338
+ window.addEventListener("message", handleRemoveSchedulingSection);
5908
7339
  window.addEventListener("message", handleImageUrl);
5909
7340
  window.addEventListener("message", handleAnimLock);
5910
7341
  window.addEventListener("message", handleCanvasHeight);
5911
7342
  window.addEventListener("message", handleParentScroll);
5912
7343
  window.addEventListener("message", handlePointerSync);
5913
7344
  window.addEventListener("message", handleClickAt);
7345
+ const handleViewportResize = () => {
7346
+ if (visibleViewportRef.current) {
7347
+ applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
7348
+ }
7349
+ };
7350
+ window.addEventListener("resize", handleViewportResize, { passive: true });
5914
7351
  document.addEventListener("click", handleClick, true);
5915
7352
  document.addEventListener("paste", handlePaste, true);
5916
7353
  document.addEventListener("input", handleInput, true);
@@ -5939,10 +7376,16 @@ function OhhwellsBridge() {
5939
7376
  document.removeEventListener("selectionchange", handleSelectionChange);
5940
7377
  window.removeEventListener("scroll", handleScroll, true);
5941
7378
  window.removeEventListener("message", handleSave);
7379
+ window.removeEventListener("message", handleInsertSection);
7380
+ window.removeEventListener("message", handleSwitchSchedule);
7381
+ window.removeEventListener("message", handleScheduleLinked);
7382
+ window.removeEventListener("message", handleClearSchedulingWidget);
7383
+ window.removeEventListener("message", handleRemoveSchedulingSection);
5942
7384
  window.removeEventListener("message", handleImageUrl);
5943
7385
  window.removeEventListener("message", handleAnimLock);
5944
7386
  window.removeEventListener("message", handleCanvasHeight);
5945
7387
  window.removeEventListener("message", handleParentScroll);
7388
+ window.removeEventListener("resize", handleViewportResize);
5946
7389
  window.removeEventListener("message", handlePointerSync);
5947
7390
  window.removeEventListener("message", handleClickAt);
5948
7391
  window.removeEventListener("message", handleHydrate);
@@ -5953,7 +7396,23 @@ function OhhwellsBridge() {
5953
7396
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
5954
7397
  };
5955
7398
  }, [isEditMode, refreshStateRules]);
5956
- (0, import_react3.useEffect)(() => {
7399
+ (0, import_react5.useEffect)(() => {
7400
+ const handler = (e) => {
7401
+ if (e.data?.type !== "ow:request-schedule-config") return;
7402
+ const insertAfterVal = e.data.insertAfter;
7403
+ if (!insertAfterVal) return;
7404
+ if (!sectionsLoadedRef.current) {
7405
+ if (!pendingScheduleConfigRequests.current.includes(insertAfterVal)) {
7406
+ pendingScheduleConfigRequests.current.push(insertAfterVal);
7407
+ }
7408
+ return;
7409
+ }
7410
+ processConfigRequest(insertAfterVal);
7411
+ };
7412
+ window.addEventListener("message", handler);
7413
+ return () => window.removeEventListener("message", handler);
7414
+ }, [processConfigRequest]);
7415
+ (0, import_react5.useEffect)(() => {
5957
7416
  if (!isEditMode) return;
5958
7417
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
5959
7418
  el.removeAttribute("data-ohw-active-state");
@@ -5982,19 +7441,19 @@ function OhhwellsBridge() {
5982
7441
  clearTimeout(timer);
5983
7442
  };
5984
7443
  }, [pathname, isEditMode, refreshStateRules, postToParent]);
5985
- (0, import_react3.useEffect)(() => {
7444
+ (0, import_react5.useEffect)(() => {
5986
7445
  scrollToHashSectionWhenReady();
5987
7446
  const onHashChange = () => scrollToHashSectionWhenReady();
5988
7447
  window.addEventListener("hashchange", onHashChange);
5989
7448
  return () => window.removeEventListener("hashchange", onHashChange);
5990
7449
  }, [pathname]);
5991
- const handleCommand = (0, import_react3.useCallback)((cmd) => {
7450
+ const handleCommand = (0, import_react5.useCallback)((cmd) => {
5992
7451
  document.execCommand(cmd, false);
5993
7452
  activeElRef.current?.focus();
5994
7453
  if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
5995
7454
  refreshActiveCommandsRef.current();
5996
7455
  }, []);
5997
- const handleStateChange = (0, import_react3.useCallback)((state) => {
7456
+ const handleStateChange = (0, import_react5.useCallback)((state) => {
5998
7457
  if (!activeStateElRef.current) return;
5999
7458
  const el = activeStateElRef.current;
6000
7459
  if (state === "Default") {
@@ -6007,18 +7466,30 @@ function OhhwellsBridge() {
6007
7466
  }
6008
7467
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
6009
7468
  }, [deactivate]);
6010
- const closeLinkPopover = (0, import_react3.useCallback)(() => setLinkPopover(null), []);
6011
- const openLinkPopoverForActive = (0, import_react3.useCallback)(() => {
7469
+ const closeLinkPopover = (0, import_react5.useCallback)(() => setLinkPopover(null), []);
7470
+ const openLinkPopoverForActive = (0, import_react5.useCallback)(() => {
6012
7471
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
6013
7472
  if (!hrefCtx) return;
6014
7473
  bumpLinkPopoverGrace();
6015
7474
  setLinkPopover({
6016
7475
  key: hrefCtx.key,
6017
- target: getLinkHref(hrefCtx.anchor),
6018
- rect: hrefCtx.anchor.getBoundingClientRect()
7476
+ target: getLinkHref(hrefCtx.anchor)
6019
7477
  });
6020
- }, []);
6021
- const handleLinkPopoverSubmit = (0, import_react3.useCallback)(
7478
+ deactivate();
7479
+ }, [deactivate]);
7480
+ const openLinkPopoverForSelected = (0, import_react5.useCallback)(() => {
7481
+ const anchor = selectedElRef.current;
7482
+ if (!anchor) return;
7483
+ const key = anchor.getAttribute("data-ohw-href-key");
7484
+ if (!key) return;
7485
+ bumpLinkPopoverGrace();
7486
+ setLinkPopover({
7487
+ key,
7488
+ target: getLinkHref(anchor)
7489
+ });
7490
+ deselect();
7491
+ }, [deselect]);
7492
+ const handleLinkPopoverSubmit = (0, import_react5.useCallback)(
6022
7493
  (target) => {
6023
7494
  if (!linkPopover) return;
6024
7495
  const { key } = linkPopover;
@@ -6031,13 +7502,15 @@ function OhhwellsBridge() {
6031
7502
  const showEditLink = toolbarShowEditLink;
6032
7503
  const currentSections = sectionsByPath[pathname] ?? [];
6033
7504
  linkPopoverOpenRef.current = linkPopover !== null;
6034
- return bridgeRoot ? (0, import_react_dom.createPortal)(
6035
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
6036
- toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
6037
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
6038
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7505
+ return bridgeRoot ? (0, import_react_dom2.createPortal)(
7506
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
7507
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
7508
+ toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
7509
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
7510
+ toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6039
7511
  FloatingToolbar,
6040
7512
  {
7513
+ variant: "rich-text",
6041
7514
  rect: toolbarRect,
6042
7515
  parentScroll: parentScrollRef.current,
6043
7516
  elRef: toolbarElRef,
@@ -6046,9 +7519,21 @@ function OhhwellsBridge() {
6046
7519
  showEditLink,
6047
7520
  onEditLink: openLinkPopoverForActive
6048
7521
  }
7522
+ ),
7523
+ toolbarVariant === "link-action" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7524
+ FloatingToolbar,
7525
+ {
7526
+ variant: "link-action",
7527
+ rect: toolbarRect,
7528
+ parentScroll: parentScrollRef.current,
7529
+ elRef: toolbarElRef,
7530
+ onCommand: handleCommand,
7531
+ activeCommands,
7532
+ onEditLink: openLinkPopoverForSelected
7533
+ }
6049
7534
  )
6050
7535
  ] }),
6051
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
7536
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
6052
7537
  "div",
6053
7538
  {
6054
7539
  "data-ohw-max-badge": "",
@@ -6074,7 +7559,7 @@ function OhhwellsBridge() {
6074
7559
  ]
6075
7560
  }
6076
7561
  ),
6077
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7562
+ toggleState && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6078
7563
  StateToggle,
6079
7564
  {
6080
7565
  rect: toggleState.rect,
@@ -6083,12 +7568,36 @@ function OhhwellsBridge() {
6083
7568
  onStateChange: handleStateChange
6084
7569
  }
6085
7570
  ),
6086
- linkPopover ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7571
+ sectionGap && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7572
+ "div",
7573
+ {
7574
+ "data-ohw-section-insert-line": "",
7575
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
7576
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
7577
+ children: [
7578
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
7579
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7580
+ Badge,
7581
+ {
7582
+ 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",
7583
+ onClick: () => {
7584
+ window.parent.postMessage(
7585
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
7586
+ "*"
7587
+ );
7588
+ },
7589
+ children: "Add Section"
7590
+ }
7591
+ ),
7592
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
7593
+ ]
7594
+ }
7595
+ ),
7596
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6087
7597
  LinkPopover,
6088
7598
  {
6089
- rect: linkPopover.rect,
6090
- parentScroll: parentScrollRef.current,
6091
7599
  panelRef: linkPopoverPanelRef,
7600
+ portalContainer: dialogPortalContainer,
6092
7601
  open: true,
6093
7602
  mode: "edit",
6094
7603
  pages: sitePages,
@@ -6097,8 +7606,34 @@ function OhhwellsBridge() {
6097
7606
  initialTarget: linkPopover.target,
6098
7607
  onClose: closeLinkPopover,
6099
7608
  onSubmit: handleLinkPopoverSubmit
7609
+ },
7610
+ `${linkPopover.key}-${pathname}`
7611
+ ) : null,
7612
+ sectionGap && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7613
+ "div",
7614
+ {
7615
+ "data-ohw-section-insert-line": "",
7616
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
7617
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
7618
+ children: [
7619
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
7620
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7621
+ Badge,
7622
+ {
7623
+ 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",
7624
+ onClick: () => {
7625
+ window.parent.postMessage(
7626
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
7627
+ "*"
7628
+ );
7629
+ },
7630
+ children: "Add Section"
7631
+ }
7632
+ ),
7633
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
7634
+ ]
6100
7635
  }
6101
- ) : null
7636
+ )
6102
7637
  ] }),
6103
7638
  bridgeRoot
6104
7639
  ) : null;
@@ -6108,6 +7643,7 @@ function OhhwellsBridge() {
6108
7643
  LinkEditorPanel,
6109
7644
  LinkPopover,
6110
7645
  OhhwellsBridge,
7646
+ SchedulingWidget,
6111
7647
  Toggle,
6112
7648
  ToggleGroup,
6113
7649
  ToggleGroupItem,