@ohhwells/bridge 0.1.29 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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
  }
@@ -4670,7 +5671,7 @@ var TOOLBAR_GROUPS = [
4670
5671
  ];
4671
5672
  function GlowFrame({ rect, elRef }) {
4672
5673
  const GAP = 6;
4673
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5674
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4674
5675
  "div",
4675
5676
  {
4676
5677
  ref: elRef,
@@ -4689,41 +5690,191 @@ function GlowFrame({ rect, elRef }) {
4689
5690
  }
4690
5691
  );
4691
5692
  }
4692
- function calcToolbarPos(rect, parentScroll) {
5693
+ function getIframeVisibleClip(parentScroll) {
5694
+ if (!parentScroll) return null;
5695
+ const { iframeOffsetTop, headerH: visibleCanvasTop, canvasH } = parentScroll;
5696
+ const clipTop = Math.max(0, visibleCanvasTop - iframeOffsetTop);
5697
+ const clipBottom = Math.min(
5698
+ window.innerHeight,
5699
+ visibleCanvasTop + canvasH - iframeOffsetTop
5700
+ );
5701
+ return { top: clipTop, bottom: Math.max(clipTop, clipBottom) };
5702
+ }
5703
+ function applyVisibleViewport(el, parentScroll) {
5704
+ const clip = getIframeVisibleClip(parentScroll);
5705
+ const top = clip?.top ?? 0;
5706
+ const height = clip ? clip.bottom - clip.top : window.innerHeight;
5707
+ el.style.top = `${top}px`;
5708
+ el.style.height = `${height}px`;
5709
+ }
5710
+ function clampToolbarToClip(top, transform, rect, clip, approxH, gap) {
5711
+ const isAbove = transform.includes("translateY(-100%)");
5712
+ let visualTop = isAbove ? top - approxH : top;
5713
+ let visualBottom = isAbove ? top : top + approxH;
5714
+ const minTop = clip.top + gap;
5715
+ const maxBottom = clip.bottom - gap;
5716
+ if (visualTop >= minTop && visualBottom <= maxBottom) {
5717
+ return { top, transform };
5718
+ }
5719
+ const belowTop = rect.bottom + gap;
5720
+ if (belowTop + approxH <= maxBottom && belowTop >= minTop) {
5721
+ return { top: belowTop, transform: "translateX(-50%)" };
5722
+ }
5723
+ const aboveTop = rect.top - gap;
5724
+ if (aboveTop - approxH >= minTop && aboveTop <= maxBottom) {
5725
+ return { top: aboveTop, transform: "translateX(-50%) translateY(-100%)" };
5726
+ }
5727
+ if (belowTop + approxH <= maxBottom) {
5728
+ return { top: belowTop, transform: "translateX(-50%)" };
5729
+ }
5730
+ const pinnedTop = Math.min(maxBottom - approxH, Math.max(minTop, clip.top + gap));
5731
+ return { top: pinnedTop + approxH, transform: "translateX(-50%) translateY(-100%)" };
5732
+ }
5733
+ function calcToolbarPos(rect, parentScroll, approxW = 330) {
4693
5734
  const GAP = 8;
4694
5735
  const APPROX_H = 36;
4695
- const APPROX_W = 330;
5736
+ const APPROX_W = approxW;
5737
+ const clip = getIframeVisibleClip(parentScroll);
5738
+ const clipTop = clip?.top ?? 0;
4696
5739
  const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
4697
5740
  const visibleTop = rect.top - canvasTopInIframe;
4698
5741
  const visibleBottom = rect.bottom - canvasTopInIframe;
4699
5742
  const isTall = rect.height > APPROX_H + GAP;
5743
+ const spaceAbove = rect.top - (clipTop + GAP);
5744
+ const fitsAbove = spaceAbove >= APPROX_H + GAP;
4700
5745
  let top;
4701
5746
  let transform;
4702
- if (visibleTop > 0 || !isTall) {
5747
+ if ((visibleTop > 0 || !isTall) && fitsAbove) {
4703
5748
  top = rect.top - GAP;
4704
5749
  transform = "translateX(-50%) translateY(-100%)";
5750
+ } else if (visibleTop > 0 || !isTall) {
5751
+ top = rect.bottom + GAP;
5752
+ transform = "translateX(-50%)";
4705
5753
  } else if (visibleBottom > APPROX_H + GAP) {
4706
- top = canvasTopInIframe + GAP;
5754
+ top = clipTop + GAP;
4707
5755
  transform = "translateX(-50%)";
4708
5756
  } else {
4709
5757
  top = rect.bottom + GAP;
4710
5758
  transform = "translateX(-50%)";
4711
5759
  }
5760
+ if (clip) {
5761
+ const clamped = clampToolbarToClip(top, transform, rect, clip, APPROX_H, GAP);
5762
+ top = clamped.top;
5763
+ transform = clamped.transform;
5764
+ }
4712
5765
  const rawLeft = rect.left + rect.width / 2;
4713
5766
  const left = Math.max(GAP + APPROX_W / 2, Math.min(rawLeft, window.innerWidth - GAP - APPROX_W / 2));
4714
5767
  return { top, left, transform };
4715
5768
  }
5769
+ 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: [
5770
+ /* @__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" }),
5771
+ /* @__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" })
5772
+ ] });
4716
5773
  function FloatingToolbar({
4717
5774
  rect,
4718
5775
  parentScroll,
4719
5776
  elRef,
5777
+ variant = "rich-text",
4720
5778
  onCommand,
4721
5779
  activeCommands,
4722
5780
  showEditLink,
4723
5781
  onEditLink
4724
5782
  }) {
4725
- const { top, left, transform } = calcToolbarPos(rect, parentScroll);
4726
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
5783
+ const approxW = variant === "link-action" ? 120 : 330;
5784
+ const { top, left, transform } = calcToolbarPos(rect, parentScroll, approxW);
5785
+ if (variant === "link-action") {
5786
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5787
+ "div",
5788
+ {
5789
+ ref: elRef,
5790
+ "data-ohw-toolbar": "",
5791
+ style: {
5792
+ position: "fixed",
5793
+ top,
5794
+ left,
5795
+ transform,
5796
+ zIndex: 2147483647,
5797
+ background: "#fff",
5798
+ border: "1px solid #E7E5E4",
5799
+ borderRadius: 6,
5800
+ boxShadow: "0px 2px 4px -2px rgba(0,0,0,.1),0px 4px 6px -1px rgba(0,0,0,.1)",
5801
+ display: "flex",
5802
+ alignItems: "center",
5803
+ padding: 4,
5804
+ gap: 6,
5805
+ fontFamily: "sans-serif",
5806
+ pointerEvents: "auto",
5807
+ whiteSpace: "nowrap"
5808
+ },
5809
+ onMouseDown: (e) => e.stopPropagation(),
5810
+ children: [
5811
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5812
+ "button",
5813
+ {
5814
+ type: "button",
5815
+ title: "Edit link",
5816
+ onMouseDown: (e) => {
5817
+ e.preventDefault();
5818
+ e.stopPropagation();
5819
+ onEditLink?.();
5820
+ },
5821
+ onClick: (e) => {
5822
+ e.preventDefault();
5823
+ e.stopPropagation();
5824
+ },
5825
+ onMouseEnter: (e) => {
5826
+ e.currentTarget.style.background = "#F5F5F4";
5827
+ },
5828
+ onMouseLeave: (e) => {
5829
+ e.currentTarget.style.background = "transparent";
5830
+ },
5831
+ style: {
5832
+ display: "flex",
5833
+ alignItems: "center",
5834
+ justifyContent: "center",
5835
+ border: "none",
5836
+ background: "transparent",
5837
+ borderRadius: 4,
5838
+ cursor: "pointer",
5839
+ color: "#1C1917",
5840
+ flexShrink: 0,
5841
+ padding: 6
5842
+ },
5843
+ children: EDIT_LINK_ICON
5844
+ }
5845
+ ),
5846
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
5847
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5848
+ "button",
5849
+ {
5850
+ type: "button",
5851
+ title: "Coming soon",
5852
+ disabled: true,
5853
+ style: {
5854
+ display: "flex",
5855
+ alignItems: "center",
5856
+ justifyContent: "center",
5857
+ border: "none",
5858
+ background: "transparent",
5859
+ borderRadius: 4,
5860
+ cursor: "not-allowed",
5861
+ color: "#A8A29E",
5862
+ flexShrink: 0,
5863
+ padding: 6,
5864
+ opacity: 0.6
5865
+ },
5866
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: [
5867
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "5", cy: "12", r: "1.5" }),
5868
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "12", cy: "12", r: "1.5" }),
5869
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "19", cy: "12", r: "1.5" })
5870
+ ] })
5871
+ }
5872
+ )
5873
+ ]
5874
+ }
5875
+ );
5876
+ }
5877
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
4727
5878
  "div",
4728
5879
  {
4729
5880
  ref: elRef,
@@ -4748,11 +5899,11 @@ function FloatingToolbar({
4748
5899
  },
4749
5900
  onMouseDown: (e) => e.stopPropagation(),
4750
5901
  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 } }),
5902
+ TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react5.default.Fragment, { children: [
5903
+ gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
4753
5904
  btns.map((btn) => {
4754
5905
  const isActive = activeCommands.has(btn.cmd);
4755
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5906
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4756
5907
  "button",
4757
5908
  {
4758
5909
  title: btn.title,
@@ -4778,7 +5929,7 @@ function FloatingToolbar({
4778
5929
  flexShrink: 0,
4779
5930
  padding: 6
4780
5931
  },
4781
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5932
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4782
5933
  "svg",
4783
5934
  {
4784
5935
  width: "16",
@@ -4798,7 +5949,7 @@ function FloatingToolbar({
4798
5949
  );
4799
5950
  })
4800
5951
  ] }, gi)),
4801
- showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5952
+ showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4802
5953
  "button",
4803
5954
  {
4804
5955
  type: "button",
@@ -4830,10 +5981,7 @@ function FloatingToolbar({
4830
5981
  flexShrink: 0,
4831
5982
  padding: 6
4832
5983
  },
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
- ] })
5984
+ children: EDIT_LINK_ICON
4837
5985
  }
4838
5986
  ) : null
4839
5987
  ]
@@ -4850,7 +5998,7 @@ function StateToggle({
4850
5998
  states,
4851
5999
  onStateChange
4852
6000
  }) {
4853
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
6001
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4854
6002
  ToggleGroup,
4855
6003
  {
4856
6004
  "data-ohw-state-toggle": "",
@@ -4864,18 +6012,35 @@ function StateToggle({
4864
6012
  left: rect.right - 8,
4865
6013
  transform: "translateX(-100%)"
4866
6014
  },
4867
- children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
6015
+ children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
4868
6016
  }
4869
6017
  );
4870
6018
  }
4871
6019
  var contentCache = /* @__PURE__ */ new Map();
6020
+ function resolveSubdomain(subdomainFromQuery) {
6021
+ if (subdomainFromQuery) return subdomainFromQuery;
6022
+ if (typeof window !== "undefined") {
6023
+ const parts = window.location.hostname.split(".");
6024
+ if (parts.length >= 3 && parts[0] !== "www") return parts[0];
6025
+ }
6026
+ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
6027
+ if (siteUrl) {
6028
+ try {
6029
+ const host = new URL(siteUrl).hostname;
6030
+ const siteParts = host.split(".");
6031
+ if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
6032
+ } catch {
6033
+ }
6034
+ }
6035
+ return "";
6036
+ }
4872
6037
  function OhhwellsBridge() {
4873
6038
  const pathname = (0, import_navigation.usePathname)();
4874
6039
  const router = (0, import_navigation.useRouter)();
4875
6040
  const searchParams = (0, import_navigation.useSearchParams)();
4876
6041
  const isEditMode = isEditSessionActive();
4877
- const [bridgeRoot, setBridgeRoot] = (0, import_react3.useState)(null);
4878
- (0, import_react3.useEffect)(() => {
6042
+ const [bridgeRoot, setBridgeRoot] = (0, import_react5.useState)(null);
6043
+ (0, import_react5.useEffect)(() => {
4879
6044
  const figtreeFontId = "ohw-figtree-font";
4880
6045
  if (!document.getElementById(figtreeFontId)) {
4881
6046
  const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
@@ -4898,55 +6063,70 @@ function OhhwellsBridge() {
4898
6063
  };
4899
6064
  }, []);
4900
6065
  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) => {
6066
+ const subdomain = resolveSubdomain(subdomainFromQuery);
6067
+ const postToParent = (0, import_react5.useCallback)((data) => {
4907
6068
  if (typeof window !== "undefined" && window.parent !== window) {
4908
6069
  window.parent.postMessage(data, "*");
4909
6070
  }
4910
6071
  }, []);
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)(() => {
6072
+ const [fetchState, setFetchState] = (0, import_react5.useState)("idle");
6073
+ const autoSaveTimers = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
6074
+ const activeElRef = (0, import_react5.useRef)(null);
6075
+ const selectedElRef = (0, import_react5.useRef)(null);
6076
+ const originalContentRef = (0, import_react5.useRef)(null);
6077
+ const activeStateElRef = (0, import_react5.useRef)(null);
6078
+ const parentScrollRef = (0, import_react5.useRef)(null);
6079
+ const visibleViewportRef = (0, import_react5.useRef)(null);
6080
+ const [dialogPortalContainer, setDialogPortalContainer] = (0, import_react5.useState)(null);
6081
+ const attachVisibleViewport = (0, import_react5.useCallback)((node) => {
6082
+ visibleViewportRef.current = node;
6083
+ setDialogPortalContainer(node);
6084
+ if (node) applyVisibleViewport(node, parentScrollRef.current);
6085
+ }, []);
6086
+ const toolbarElRef = (0, import_react5.useRef)(null);
6087
+ const glowElRef = (0, import_react5.useRef)(null);
6088
+ const hoveredImageRef = (0, import_react5.useRef)(null);
6089
+ const hoveredImageHasTextOverlapRef = (0, import_react5.useRef)(false);
6090
+ const hoveredGapRef = (0, import_react5.useRef)(null);
6091
+ const imageUnhoverTimerRef = (0, import_react5.useRef)(null);
6092
+ const imageShowTimerRef = (0, import_react5.useRef)(null);
6093
+ const editStylesRef = (0, import_react5.useRef)(null);
6094
+ const activateRef = (0, import_react5.useRef)(() => {
6095
+ });
6096
+ const deactivateRef = (0, import_react5.useRef)(() => {
4925
6097
  });
4926
- const deactivateRef = (0, import_react3.useRef)(() => {
6098
+ const selectRef = (0, import_react5.useRef)(() => {
4927
6099
  });
4928
- const refreshActiveCommandsRef = (0, import_react3.useRef)(() => {
6100
+ const deselectRef = (0, import_react5.useRef)(() => {
4929
6101
  });
4930
- const postToParentRef = (0, import_react3.useRef)(postToParent);
6102
+ const refreshActiveCommandsRef = (0, import_react5.useRef)(() => {
6103
+ });
6104
+ const postToParentRef = (0, import_react5.useRef)(postToParent);
4931
6105
  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);
6106
+ const sectionsLoadedRef = (0, import_react5.useRef)(false);
6107
+ const pendingScheduleConfigRequests = (0, import_react5.useRef)([]);
6108
+ const [toolbarRect, setToolbarRect] = (0, import_react5.useState)(null);
6109
+ const [toolbarVariant, setToolbarVariant] = (0, import_react5.useState)("none");
6110
+ const toolbarVariantRef = (0, import_react5.useRef)("none");
6111
+ toolbarVariantRef.current = toolbarVariant;
6112
+ const [toggleState, setToggleState] = (0, import_react5.useState)(null);
6113
+ const [maxBadge, setMaxBadge] = (0, import_react5.useState)(null);
6114
+ const [activeCommands, setActiveCommands] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
6115
+ const [sectionGap, setSectionGap] = (0, import_react5.useState)(null);
6116
+ const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react5.useState)(false);
6117
+ const [linkPopover, setLinkPopover] = (0, import_react5.useState)(null);
6118
+ const [sitePages, setSitePages] = (0, import_react5.useState)([]);
6119
+ const [sectionsByPath, setSectionsByPath] = (0, import_react5.useState)({});
6120
+ const sectionsPrefetchGenRef = (0, import_react5.useRef)(0);
6121
+ const setLinkPopoverRef = (0, import_react5.useRef)(setLinkPopover);
6122
+ const linkPopoverPanelRef = (0, import_react5.useRef)(null);
6123
+ const linkPopoverOpenRef = (0, import_react5.useRef)(false);
6124
+ const linkPopoverGraceUntilRef = (0, import_react5.useRef)(0);
4945
6125
  setLinkPopoverRef.current = setLinkPopover;
4946
6126
  const bumpLinkPopoverGrace = () => {
4947
6127
  linkPopoverGraceUntilRef.current = Date.now() + 350;
4948
6128
  };
4949
- const runSectionsPrefetch = (0, import_react3.useCallback)((pages) => {
6129
+ const runSectionsPrefetch = (0, import_react5.useCallback)((pages) => {
4950
6130
  if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
4951
6131
  const gen = ++sectionsPrefetchGenRef.current;
4952
6132
  const paths = pages.map((p) => p.path);
@@ -4965,17 +6145,43 @@ function OhhwellsBridge() {
4965
6145
  );
4966
6146
  });
4967
6147
  }, [isEditMode, pathname]);
4968
- const runSectionsPrefetchRef = (0, import_react3.useRef)(runSectionsPrefetch);
6148
+ const runSectionsPrefetchRef = (0, import_react5.useRef)(runSectionsPrefetch);
4969
6149
  runSectionsPrefetchRef.current = runSectionsPrefetch;
4970
- (0, import_react3.useEffect)(() => {
6150
+ (0, import_react5.useEffect)(() => {
4971
6151
  if (!linkPopover) return;
4972
6152
  if (hoveredImageRef.current) {
4973
6153
  hoveredImageRef.current = null;
4974
6154
  hoveredImageHasTextOverlapRef.current = false;
4975
6155
  }
6156
+ hoveredGapRef.current = null;
6157
+ setSectionGap(null);
4976
6158
  postToParent({ type: "ow:image-unhover" });
6159
+ postToParent({ type: "ow:link-modal-lock", locked: true });
6160
+ const html = document.documentElement;
6161
+ const body = document.body;
6162
+ const prevHtmlOverflow = html.style.overflow;
6163
+ const prevBodyOverflow = body.style.overflow;
6164
+ html.style.overflow = "hidden";
6165
+ body.style.overflow = "hidden";
6166
+ const preventBackgroundScroll = (e) => {
6167
+ const target = e.target;
6168
+ if (target instanceof Element && target.closest('[data-ohw-link-modal-root], [data-slot="dialog-overlay"]')) {
6169
+ return;
6170
+ }
6171
+ e.preventDefault();
6172
+ };
6173
+ const scrollOpts = { passive: false, capture: true };
6174
+ document.addEventListener("wheel", preventBackgroundScroll, scrollOpts);
6175
+ document.addEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6176
+ return () => {
6177
+ postToParent({ type: "ow:link-modal-lock", locked: false });
6178
+ html.style.overflow = prevHtmlOverflow;
6179
+ body.style.overflow = prevBodyOverflow;
6180
+ document.removeEventListener("wheel", preventBackgroundScroll, scrollOpts);
6181
+ document.removeEventListener("touchmove", preventBackgroundScroll, scrollOpts);
6182
+ };
4977
6183
  }, [linkPopover, postToParent]);
4978
- (0, import_react3.useEffect)(() => {
6184
+ (0, import_react5.useEffect)(() => {
4979
6185
  if (!isEditMode) return;
4980
6186
  const useFixtures = shouldUseDevFixtures();
4981
6187
  if (useFixtures) {
@@ -4999,21 +6205,27 @@ function OhhwellsBridge() {
4999
6205
  if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
5000
6206
  return () => window.removeEventListener("message", onSitePages);
5001
6207
  }, [isEditMode, postToParent]);
5002
- (0, import_react3.useEffect)(() => {
6208
+ (0, import_react5.useEffect)(() => {
5003
6209
  if (!isEditMode || shouldUseDevFixtures()) return;
5004
6210
  void loadAllSectionsManifest().then((manifest) => {
5005
6211
  if (Object.keys(manifest).length === 0) return;
5006
6212
  setSectionsByPath((prev) => ({ ...manifest, ...prev }));
5007
6213
  });
5008
6214
  }, [isEditMode]);
5009
- (0, import_react3.useEffect)(() => {
6215
+ (0, import_react5.useEffect)(() => {
5010
6216
  const update = () => {
5011
- const el = activeElRef.current;
6217
+ const el = activeElRef.current ?? selectedElRef.current;
5012
6218
  if (el) setToolbarRect(el.getBoundingClientRect());
5013
6219
  setToggleState((prev) => {
5014
6220
  if (!prev || !activeStateElRef.current) return prev;
5015
6221
  return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
5016
6222
  });
6223
+ setSectionGap((prev) => {
6224
+ if (!prev) return prev;
6225
+ const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
6226
+ if (!anchor) return prev;
6227
+ return { ...prev, y: anchor.getBoundingClientRect().bottom };
6228
+ });
5017
6229
  };
5018
6230
  const vvp = window.visualViewport;
5019
6231
  if (!vvp) return;
@@ -5024,10 +6236,33 @@ function OhhwellsBridge() {
5024
6236
  vvp.removeEventListener("resize", update);
5025
6237
  };
5026
6238
  }, []);
5027
- const refreshStateRules = (0, import_react3.useCallback)(() => {
6239
+ const refreshStateRules = (0, import_react5.useCallback)(() => {
5028
6240
  editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
5029
6241
  }, []);
5030
- const deactivate = (0, import_react3.useCallback)(() => {
6242
+ const processConfigRequest = (0, import_react5.useCallback)((insertAfterVal) => {
6243
+ const tracker = getSectionsTracker();
6244
+ let entries = [];
6245
+ try {
6246
+ entries = JSON.parse(tracker.textContent || "[]");
6247
+ } catch {
6248
+ }
6249
+ const path = window.location.pathname;
6250
+ const entry = entries.find(
6251
+ (e) => e.type === "scheduling" && e.insertAfter === insertAfterVal && (!e.pagePath || e.pagePath === path)
6252
+ );
6253
+ if (entry) {
6254
+ window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: entry.scheduleId ?? null }, "*");
6255
+ return;
6256
+ }
6257
+ const newEntry = { type: "scheduling", insertAfter: insertAfterVal, pagePath: path };
6258
+ entries.push(newEntry);
6259
+ tracker.textContent = JSON.stringify(entries);
6260
+ if (isEditMode) {
6261
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
6262
+ }
6263
+ window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
6264
+ }, [isEditMode]);
6265
+ const deactivate = (0, import_react5.useCallback)(() => {
5031
6266
  const el = activeElRef.current;
5032
6267
  if (!el) return;
5033
6268
  const key = el.dataset.ohwKey;
@@ -5047,19 +6282,41 @@ function OhhwellsBridge() {
5047
6282
  }
5048
6283
  el.removeAttribute("contenteditable");
5049
6284
  activeElRef.current = null;
5050
- setToolbarRect(null);
6285
+ if (!selectedElRef.current) {
6286
+ setToolbarRect(null);
6287
+ setToolbarVariant("none");
6288
+ }
5051
6289
  setMaxBadge(null);
5052
6290
  setActiveCommands(/* @__PURE__ */ new Set());
5053
6291
  setToolbarShowEditLink(false);
5054
6292
  postToParent({ type: "ow:exit-edit" });
5055
6293
  }, [postToParent]);
5056
- const activate = (0, import_react3.useCallback)((el) => {
6294
+ const deselect = (0, import_react5.useCallback)(() => {
6295
+ selectedElRef.current = null;
6296
+ if (!activeElRef.current) {
6297
+ setToolbarRect(null);
6298
+ setToolbarVariant("none");
6299
+ }
6300
+ }, []);
6301
+ const select = (0, import_react5.useCallback)((anchor) => {
6302
+ if (!isNavbarButton(anchor)) return;
6303
+ if (activeElRef.current) deactivate();
6304
+ selectedElRef.current = anchor;
6305
+ clearHrefKeyHover(anchor);
6306
+ setToolbarVariant("link-action");
6307
+ setToolbarRect(anchor.getBoundingClientRect());
6308
+ setToolbarShowEditLink(false);
6309
+ setActiveCommands(/* @__PURE__ */ new Set());
6310
+ }, [deactivate]);
6311
+ const activate = (0, import_react5.useCallback)((el) => {
5057
6312
  if (activeElRef.current === el) return;
6313
+ selectedElRef.current = null;
5058
6314
  deactivate();
5059
6315
  if (hoveredImageRef.current) {
5060
6316
  hoveredImageRef.current = null;
5061
6317
  postToParentRef.current({ type: "ow:image-unhover" });
5062
6318
  }
6319
+ setToolbarVariant("rich-text");
5063
6320
  el.setAttribute("contenteditable", "true");
5064
6321
  el.removeAttribute("data-ohw-hovered");
5065
6322
  activeElRef.current = el;
@@ -5072,7 +6329,9 @@ function OhhwellsBridge() {
5072
6329
  }, [deactivate, postToParent]);
5073
6330
  activateRef.current = activate;
5074
6331
  deactivateRef.current = deactivate;
5075
- (0, import_react3.useLayoutEffect)(() => {
6332
+ selectRef.current = select;
6333
+ deselectRef.current = deselect;
6334
+ (0, import_react5.useLayoutEffect)(() => {
5076
6335
  if (!subdomain || isEditMode) {
5077
6336
  setFetchState("done");
5078
6337
  return;
@@ -5080,6 +6339,7 @@ function OhhwellsBridge() {
5080
6339
  const applyContent = (content) => {
5081
6340
  const imageLoads = [];
5082
6341
  for (const [key, val] of Object.entries(content)) {
6342
+ if (key === "__ohw_sections") continue;
5083
6343
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
5084
6344
  if (el.dataset.ohwEditable === "image") {
5085
6345
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -5101,6 +6361,9 @@ function OhhwellsBridge() {
5101
6361
  });
5102
6362
  applyLinkByKey(key, val);
5103
6363
  }
6364
+ initSectionsFromContent(content, true);
6365
+ sectionsLoadedRef.current = true;
6366
+ pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
5104
6367
  return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
5105
6368
  }) : Promise.resolve();
5106
6369
  };
@@ -5125,12 +6388,15 @@ function OhhwellsBridge() {
5125
6388
  cancelled = true;
5126
6389
  };
5127
6390
  }, [subdomain, isEditMode, pathname]);
5128
- (0, import_react3.useEffect)(() => {
6391
+ (0, import_react5.useEffect)(() => {
5129
6392
  if (!subdomain || isEditMode) return;
6393
+ let debounceTimer = null;
5130
6394
  const applyFromCache = () => {
5131
6395
  const content = contentCache.get(subdomain);
5132
6396
  if (!content) return;
6397
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
5133
6398
  for (const [key, val] of Object.entries(content)) {
6399
+ if (key === "__ohw_sections") continue;
5134
6400
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
5135
6401
  if (el.dataset.ohwEditable === "image") {
5136
6402
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -5147,21 +6413,34 @@ function OhhwellsBridge() {
5147
6413
  applyLinkByKey(key, val);
5148
6414
  }
5149
6415
  };
5150
- applyFromCache();
5151
- const observer = new MutationObserver(applyFromCache);
6416
+ const scheduleApply = () => {
6417
+ if (debounceTimer) clearTimeout(debounceTimer);
6418
+ debounceTimer = setTimeout(applyFromCache, 150);
6419
+ };
6420
+ scheduleApply();
6421
+ const observer = new MutationObserver(scheduleApply);
5152
6422
  observer.observe(document.body, { childList: true, subtree: true });
5153
- return () => observer.disconnect();
5154
- }, [subdomain, isEditMode]);
5155
- (0, import_react3.useLayoutEffect)(() => {
6423
+ return () => {
6424
+ observer.disconnect();
6425
+ if (debounceTimer) clearTimeout(debounceTimer);
6426
+ };
6427
+ }, [subdomain, isEditMode, pathname]);
6428
+ (0, import_react5.useLayoutEffect)(() => {
5156
6429
  const el = document.getElementById("ohw-loader");
5157
6430
  if (!el) return;
5158
6431
  const visible = Boolean(subdomain) && fetchState !== "done";
5159
6432
  el.style.display = visible ? "flex" : "none";
5160
6433
  }, [subdomain, fetchState]);
5161
- (0, import_react3.useEffect)(() => {
6434
+ (0, import_react5.useEffect)(() => {
5162
6435
  postToParent({ type: "ow:navigation", path: pathname });
5163
6436
  }, [pathname, postToParent]);
5164
- (0, import_react3.useEffect)(() => {
6437
+ (0, import_react5.useEffect)(() => {
6438
+ if (!isEditMode) return;
6439
+ setLinkPopover(null);
6440
+ deselectRef.current();
6441
+ deactivateRef.current();
6442
+ }, [pathname, isEditMode]);
6443
+ (0, import_react5.useEffect)(() => {
5165
6444
  if (!isEditMode) return;
5166
6445
  const measure = () => {
5167
6446
  const h = document.body.scrollHeight;
@@ -5185,7 +6464,7 @@ function OhhwellsBridge() {
5185
6464
  window.removeEventListener("resize", handleResize);
5186
6465
  };
5187
6466
  }, [pathname, isEditMode, postToParent]);
5188
- (0, import_react3.useEffect)(() => {
6467
+ (0, import_react5.useEffect)(() => {
5189
6468
  if (!subdomainFromQuery || isEditMode) return;
5190
6469
  const handleClick = (e) => {
5191
6470
  const anchor = e.target.closest("a");
@@ -5201,7 +6480,7 @@ function OhhwellsBridge() {
5201
6480
  document.addEventListener("click", handleClick, true);
5202
6481
  return () => document.removeEventListener("click", handleClick, true);
5203
6482
  }, [subdomainFromQuery, isEditMode, router]);
5204
- (0, import_react3.useEffect)(() => {
6483
+ (0, import_react5.useEffect)(() => {
5205
6484
  if (!isEditMode) {
5206
6485
  editStylesRef.current?.base.remove();
5207
6486
  editStylesRef.current?.forceHover.remove();
@@ -5271,12 +6550,12 @@ function OhhwellsBridge() {
5271
6550
  if (editable.dataset.ohwEditable === "link") {
5272
6551
  e.preventDefault();
5273
6552
  e.stopPropagation();
6553
+ deselectRef.current();
5274
6554
  deactivateRef.current();
5275
6555
  bumpLinkPopoverGrace();
5276
6556
  setLinkPopoverRef.current({
5277
6557
  key: editable.dataset.ohwKey ?? "",
5278
- target: getLinkHref(editable),
5279
- rect: editable.getBoundingClientRect()
6558
+ target: getLinkHref(editable)
5280
6559
  });
5281
6560
  return;
5282
6561
  }
@@ -5286,11 +6565,31 @@ function OhhwellsBridge() {
5286
6565
  postToParentRef.current({ type: "ow:image-pick", key: editable.dataset.ohwKey ?? "" });
5287
6566
  return;
5288
6567
  }
6568
+ const hrefCtx = getHrefKeyFromElement(editable);
6569
+ if (hrefCtx && isNavbarButton(hrefCtx.anchor)) {
6570
+ e.preventDefault();
6571
+ e.stopPropagation();
6572
+ const { anchor: anchor2 } = hrefCtx;
6573
+ if (selectedElRef.current === anchor2) {
6574
+ activateRef.current(editable);
6575
+ return;
6576
+ }
6577
+ selectRef.current(anchor2);
6578
+ return;
6579
+ }
5289
6580
  e.preventDefault();
5290
6581
  e.stopPropagation();
5291
6582
  activateRef.current(editable);
5292
6583
  return;
5293
6584
  }
6585
+ const hrefAnchor = target.closest("[data-ohw-href-key]");
6586
+ if (hrefAnchor && isNavbarButton(hrefAnchor)) {
6587
+ e.preventDefault();
6588
+ e.stopPropagation();
6589
+ if (selectedElRef.current === hrefAnchor) return;
6590
+ selectRef.current(hrefAnchor);
6591
+ return;
6592
+ }
5294
6593
  if (activeElRef.current) {
5295
6594
  const sel = window.getSelection();
5296
6595
  if (sel && !sel.isCollapsed) {
@@ -5305,15 +6604,22 @@ function OhhwellsBridge() {
5305
6604
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
5306
6605
  if (hrefCtx && (hrefCtx.anchor === target || hrefCtx.anchor.contains(target))) return;
5307
6606
  }
6607
+ if (linkPopoverOpenRef.current && selectedElRef.current) {
6608
+ const selected = selectedElRef.current;
6609
+ if (selected === target || selected.contains(target)) return;
6610
+ }
5308
6611
  if (linkPopoverOpenRef.current) {
5309
6612
  setLinkPopoverRef.current(null);
5310
6613
  return;
5311
6614
  }
6615
+ deselectRef.current();
5312
6616
  deactivateRef.current();
5313
6617
  };
5314
6618
  const handleMouseOver = (e) => {
5315
6619
  const editable = e.target.closest("[data-ohw-editable]");
5316
6620
  if (!editable) return;
6621
+ const selected = selectedElRef.current;
6622
+ if (selected && (selected === editable || selected.contains(editable))) return;
5317
6623
  if (editable.dataset.ohwEditable !== "image" && editable.dataset.ohwEditable !== "bg-image" && !editable.hasAttribute("contenteditable")) {
5318
6624
  editable.setAttribute("data-ohw-hovered", "");
5319
6625
  }
@@ -5449,7 +6755,7 @@ function OhhwellsBridge() {
5449
6755
  }
5450
6756
  return;
5451
6757
  }
5452
- if (topEl?.closest("[data-ohw-link-popover-root]") || topEl?.closest("[data-ohw-link-modal-root]")) {
6758
+ if (topEl?.closest("[data-ohw-link-popover-root]") || topEl?.closest("[data-ohw-link-modal-root]") || topEl?.closest("[data-ohw-link-page-dropdown]")) {
5453
6759
  if (hoveredImageRef.current) {
5454
6760
  hoveredImageRef.current = null;
5455
6761
  hoveredImageHasTextOverlapRef.current = false;
@@ -5502,6 +6808,15 @@ function OhhwellsBridge() {
5502
6808
  textEditable.setAttribute("data-ohw-hovered", "");
5503
6809
  return;
5504
6810
  }
6811
+ if (hoveredGapRef.current) {
6812
+ if (hoveredImageRef.current) {
6813
+ hoveredImageRef.current = null;
6814
+ resumeAnimTracks();
6815
+ postToParentRef.current({ type: "ow:image-unhover" });
6816
+ }
6817
+ document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
6818
+ return;
6819
+ }
5505
6820
  document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
5506
6821
  if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
5507
6822
  hoveredImageRef.current = imgEl;
@@ -5567,8 +6882,37 @@ function OhhwellsBridge() {
5567
6882
  }
5568
6883
  }
5569
6884
  };
6885
+ const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
6886
+ if (linkPopoverOpenRef.current) {
6887
+ if (hoveredGapRef.current) {
6888
+ hoveredGapRef.current = null;
6889
+ setSectionGap(null);
6890
+ }
6891
+ return;
6892
+ }
6893
+ const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
6894
+ const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
6895
+ const ZONE = 20;
6896
+ for (let i = 0; i < sections.length; i++) {
6897
+ const a = sections[i];
6898
+ const b = sections[i + 1] ?? null;
6899
+ const boundaryY = a.getBoundingClientRect().bottom;
6900
+ if (Math.abs(y - boundaryY) <= ZONE) {
6901
+ const insertAfter = a.dataset.ohwSection ?? "";
6902
+ const insertBefore = b?.dataset.ohwSection ?? null;
6903
+ hoveredGapRef.current = { insertAfter, insertBefore };
6904
+ setSectionGap({ insertAfter, insertBefore, y: boundaryY });
6905
+ return;
6906
+ }
6907
+ }
6908
+ if (hoveredGapRef.current) {
6909
+ hoveredGapRef.current = null;
6910
+ setSectionGap(null);
6911
+ }
6912
+ };
5570
6913
  const handleMouseMove = (e) => {
5571
6914
  const { clientX, clientY } = e;
6915
+ probeSectionGapAt(clientX, clientY);
5572
6916
  probeImageAt(clientX, clientY);
5573
6917
  probeHoverCardsAt(clientX, clientY);
5574
6918
  };
@@ -5576,6 +6920,7 @@ function OhhwellsBridge() {
5576
6920
  if (e.data?.type !== "ow:pointer-sync") return;
5577
6921
  const { clientX, clientY } = e.data;
5578
6922
  if (typeof clientX !== "number" || typeof clientY !== "number") return;
6923
+ probeSectionGapAt(clientX, clientY);
5579
6924
  probeImageAt(clientX, clientY);
5580
6925
  probeHoverCardsAt(clientX, clientY);
5581
6926
  };
@@ -5733,7 +7078,12 @@ function OhhwellsBridge() {
5733
7078
  if (e.data?.type !== "ow:hydrate") return;
5734
7079
  const content = e.data.content;
5735
7080
  if (!content) return;
7081
+ let sectionsJson = null;
5736
7082
  for (const [key, val] of Object.entries(content)) {
7083
+ if (key === "__ohw_sections") {
7084
+ sectionsJson = val;
7085
+ continue;
7086
+ }
5737
7087
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
5738
7088
  if (el.dataset.ohwEditable === "image") {
5739
7089
  const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
@@ -5748,6 +7098,11 @@ function OhhwellsBridge() {
5748
7098
  });
5749
7099
  applyLinkByKey(key, val);
5750
7100
  }
7101
+ if (sectionsJson) {
7102
+ initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
7103
+ sectionsLoadedRef.current = true;
7104
+ pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
7105
+ }
5751
7106
  postToParentRef.current({ type: "ow:hydrate-done" });
5752
7107
  };
5753
7108
  window.addEventListener("message", handleHydrate);
@@ -5758,6 +7113,7 @@ function OhhwellsBridge() {
5758
7113
  setLinkPopoverRef.current(null);
5759
7114
  return;
5760
7115
  }
7116
+ deselectRef.current();
5761
7117
  deactivateRef.current();
5762
7118
  };
5763
7119
  window.addEventListener("message", handleDeactivate);
@@ -5766,6 +7122,10 @@ function OhhwellsBridge() {
5766
7122
  setLinkPopoverRef.current(null);
5767
7123
  return;
5768
7124
  }
7125
+ if (e.key === "Escape" && selectedElRef.current && !activeElRef.current) {
7126
+ deselectRef.current();
7127
+ return;
7128
+ }
5769
7129
  if (e.key !== "Escape") return;
5770
7130
  const el = activeElRef.current;
5771
7131
  if (el && originalContentRef.current !== null) {
@@ -5775,13 +7135,16 @@ function OhhwellsBridge() {
5775
7135
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: originalContentRef.current }] });
5776
7136
  }
5777
7137
  }
7138
+ deselectRef.current();
5778
7139
  deactivateRef.current();
5779
7140
  };
5780
7141
  const handleScroll = () => {
5781
- if (activeElRef.current) {
5782
- const r2 = activeElRef.current.getBoundingClientRect();
7142
+ const focusEl = activeElRef.current ?? selectedElRef.current;
7143
+ if (focusEl) {
7144
+ const r2 = focusEl.getBoundingClientRect();
5783
7145
  applyToolbarPos(r2);
5784
- setMaxBadge((prev) => prev ? { ...prev, rect: r2 } : null);
7146
+ setToolbarRect(r2);
7147
+ setMaxBadge((prev) => prev && activeElRef.current ? { ...prev, rect: r2 } : prev);
5785
7148
  }
5786
7149
  if (activeStateElRef.current) {
5787
7150
  const rect = activeStateElRef.current.getBoundingClientRect();
@@ -5800,7 +7163,63 @@ function OhhwellsBridge() {
5800
7163
  };
5801
7164
  const handleSave = (e) => {
5802
7165
  if (e.data?.type !== "ow:save") return;
5803
- postToParentRef.current({ type: "ow:save-result", nodes: collectEditableNodes() });
7166
+ const nodes = collectEditableNodes();
7167
+ const tracker = document.querySelector("[data-ohw-sections-tracker]");
7168
+ if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
7169
+ postToParentRef.current({ type: "ow:save-result", nodes });
7170
+ };
7171
+ const handleInsertSection = (e) => {
7172
+ if (e.data?.type !== "ow:insert-section") return;
7173
+ const { widgetType, insertAfter, insertBefore } = e.data;
7174
+ if (widgetType !== "scheduling") return;
7175
+ const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
7176
+ if (inserted) {
7177
+ const tracker = getSectionsTracker();
7178
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
7179
+ const h = document.documentElement.scrollHeight;
7180
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
7181
+ }
7182
+ };
7183
+ const handleSwitchSchedule = (e) => {
7184
+ if (e.data?.type !== "ow:switch-schedule") return;
7185
+ const scheduleId = e.data.scheduleId;
7186
+ const insertAfter = e.data.insertAfter;
7187
+ if (!scheduleId || !insertAfter) return;
7188
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
7189
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
7190
+ };
7191
+ const handleScheduleLinked = (e) => {
7192
+ if (e.data?.type !== "ow:schedule-linked") return;
7193
+ const scheduleId = e.data.scheduleId;
7194
+ const insertAfter = e.data.insertAfter;
7195
+ if (!scheduleId || !insertAfter) return;
7196
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
7197
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
7198
+ };
7199
+ const handleClearSchedulingWidget = (e) => {
7200
+ if (e.data?.type !== "ow:clear-scheduling-widget") return;
7201
+ const insertAfter = e.data.insertAfter;
7202
+ if (!insertAfter) return;
7203
+ const text = updateSectionScheduleId(insertAfter, null);
7204
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
7205
+ };
7206
+ const handleRemoveSchedulingSection = (e) => {
7207
+ if (e.data?.type !== "ow:remove-scheduling-section") return;
7208
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
7209
+ el.remove();
7210
+ });
7211
+ const tracker = getSectionsTracker();
7212
+ let sections = [];
7213
+ try {
7214
+ sections = JSON.parse(tracker.textContent || "[]");
7215
+ } catch {
7216
+ }
7217
+ const currentPath = window.location.pathname;
7218
+ const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
7219
+ tracker.textContent = JSON.stringify(updated);
7220
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
7221
+ const h = document.documentElement.scrollHeight;
7222
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
5804
7223
  };
5805
7224
  const handleSelectionChange = () => {
5806
7225
  if (!activeElRef.current) return;
@@ -5850,8 +7269,9 @@ function OhhwellsBridge() {
5850
7269
  };
5851
7270
  const applyToolbarPos = (rect) => {
5852
7271
  const ps = parentScrollRef.current;
7272
+ const approxW = toolbarVariantRef.current === "link-action" ? 120 : 330;
5853
7273
  if (toolbarElRef.current) {
5854
- const { top, left, transform } = calcToolbarPos(rect, ps);
7274
+ const { top, left, transform } = calcToolbarPos(rect, ps, approxW);
5855
7275
  toolbarElRef.current.style.top = `${top}px`;
5856
7276
  toolbarElRef.current.style.left = `${left}px`;
5857
7277
  toolbarElRef.current.style.transform = transform;
@@ -5866,7 +7286,11 @@ function OhhwellsBridge() {
5866
7286
  if (e.data?.type !== "ow:parent-scroll") return;
5867
7287
  const { iframeOffsetTop, headerH, canvasH } = e.data;
5868
7288
  parentScrollRef.current = { iframeOffsetTop, headerH, canvasH };
5869
- if (activeElRef.current) applyToolbarPos(activeElRef.current.getBoundingClientRect());
7289
+ if (visibleViewportRef.current) {
7290
+ applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
7291
+ }
7292
+ const focusEl = activeElRef.current ?? selectedElRef.current;
7293
+ if (focusEl) applyToolbarPos(focusEl.getBoundingClientRect());
5870
7294
  };
5871
7295
  const handleClickAt = (e) => {
5872
7296
  if (e.data?.type !== "ow:click-at") return;
@@ -5905,12 +7329,23 @@ function OhhwellsBridge() {
5905
7329
  deactivateRef.current();
5906
7330
  };
5907
7331
  window.addEventListener("message", handleSave);
7332
+ window.addEventListener("message", handleInsertSection);
7333
+ window.addEventListener("message", handleSwitchSchedule);
7334
+ window.addEventListener("message", handleScheduleLinked);
7335
+ window.addEventListener("message", handleClearSchedulingWidget);
7336
+ window.addEventListener("message", handleRemoveSchedulingSection);
5908
7337
  window.addEventListener("message", handleImageUrl);
5909
7338
  window.addEventListener("message", handleAnimLock);
5910
7339
  window.addEventListener("message", handleCanvasHeight);
5911
7340
  window.addEventListener("message", handleParentScroll);
5912
7341
  window.addEventListener("message", handlePointerSync);
5913
7342
  window.addEventListener("message", handleClickAt);
7343
+ const handleViewportResize = () => {
7344
+ if (visibleViewportRef.current) {
7345
+ applyVisibleViewport(visibleViewportRef.current, parentScrollRef.current);
7346
+ }
7347
+ };
7348
+ window.addEventListener("resize", handleViewportResize, { passive: true });
5914
7349
  document.addEventListener("click", handleClick, true);
5915
7350
  document.addEventListener("paste", handlePaste, true);
5916
7351
  document.addEventListener("input", handleInput, true);
@@ -5939,10 +7374,16 @@ function OhhwellsBridge() {
5939
7374
  document.removeEventListener("selectionchange", handleSelectionChange);
5940
7375
  window.removeEventListener("scroll", handleScroll, true);
5941
7376
  window.removeEventListener("message", handleSave);
7377
+ window.removeEventListener("message", handleInsertSection);
7378
+ window.removeEventListener("message", handleSwitchSchedule);
7379
+ window.removeEventListener("message", handleScheduleLinked);
7380
+ window.removeEventListener("message", handleClearSchedulingWidget);
7381
+ window.removeEventListener("message", handleRemoveSchedulingSection);
5942
7382
  window.removeEventListener("message", handleImageUrl);
5943
7383
  window.removeEventListener("message", handleAnimLock);
5944
7384
  window.removeEventListener("message", handleCanvasHeight);
5945
7385
  window.removeEventListener("message", handleParentScroll);
7386
+ window.removeEventListener("resize", handleViewportResize);
5946
7387
  window.removeEventListener("message", handlePointerSync);
5947
7388
  window.removeEventListener("message", handleClickAt);
5948
7389
  window.removeEventListener("message", handleHydrate);
@@ -5953,7 +7394,23 @@ function OhhwellsBridge() {
5953
7394
  if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
5954
7395
  };
5955
7396
  }, [isEditMode, refreshStateRules]);
5956
- (0, import_react3.useEffect)(() => {
7397
+ (0, import_react5.useEffect)(() => {
7398
+ const handler = (e) => {
7399
+ if (e.data?.type !== "ow:request-schedule-config") return;
7400
+ const insertAfterVal = e.data.insertAfter;
7401
+ if (!insertAfterVal) return;
7402
+ if (!sectionsLoadedRef.current) {
7403
+ if (!pendingScheduleConfigRequests.current.includes(insertAfterVal)) {
7404
+ pendingScheduleConfigRequests.current.push(insertAfterVal);
7405
+ }
7406
+ return;
7407
+ }
7408
+ processConfigRequest(insertAfterVal);
7409
+ };
7410
+ window.addEventListener("message", handler);
7411
+ return () => window.removeEventListener("message", handler);
7412
+ }, [processConfigRequest]);
7413
+ (0, import_react5.useEffect)(() => {
5957
7414
  if (!isEditMode) return;
5958
7415
  document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
5959
7416
  el.removeAttribute("data-ohw-active-state");
@@ -5982,19 +7439,19 @@ function OhhwellsBridge() {
5982
7439
  clearTimeout(timer);
5983
7440
  };
5984
7441
  }, [pathname, isEditMode, refreshStateRules, postToParent]);
5985
- (0, import_react3.useEffect)(() => {
7442
+ (0, import_react5.useEffect)(() => {
5986
7443
  scrollToHashSectionWhenReady();
5987
7444
  const onHashChange = () => scrollToHashSectionWhenReady();
5988
7445
  window.addEventListener("hashchange", onHashChange);
5989
7446
  return () => window.removeEventListener("hashchange", onHashChange);
5990
7447
  }, [pathname]);
5991
- const handleCommand = (0, import_react3.useCallback)((cmd) => {
7448
+ const handleCommand = (0, import_react5.useCallback)((cmd) => {
5992
7449
  document.execCommand(cmd, false);
5993
7450
  activeElRef.current?.focus();
5994
7451
  if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
5995
7452
  refreshActiveCommandsRef.current();
5996
7453
  }, []);
5997
- const handleStateChange = (0, import_react3.useCallback)((state) => {
7454
+ const handleStateChange = (0, import_react5.useCallback)((state) => {
5998
7455
  if (!activeStateElRef.current) return;
5999
7456
  const el = activeStateElRef.current;
6000
7457
  if (state === "Default") {
@@ -6007,18 +7464,30 @@ function OhhwellsBridge() {
6007
7464
  }
6008
7465
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
6009
7466
  }, [deactivate]);
6010
- const closeLinkPopover = (0, import_react3.useCallback)(() => setLinkPopover(null), []);
6011
- const openLinkPopoverForActive = (0, import_react3.useCallback)(() => {
7467
+ const closeLinkPopover = (0, import_react5.useCallback)(() => setLinkPopover(null), []);
7468
+ const openLinkPopoverForActive = (0, import_react5.useCallback)(() => {
6012
7469
  const hrefCtx = getHrefKeyFromElement(activeElRef.current);
6013
7470
  if (!hrefCtx) return;
6014
7471
  bumpLinkPopoverGrace();
6015
7472
  setLinkPopover({
6016
7473
  key: hrefCtx.key,
6017
- target: getLinkHref(hrefCtx.anchor),
6018
- rect: hrefCtx.anchor.getBoundingClientRect()
7474
+ target: getLinkHref(hrefCtx.anchor)
6019
7475
  });
6020
- }, []);
6021
- const handleLinkPopoverSubmit = (0, import_react3.useCallback)(
7476
+ deactivate();
7477
+ }, [deactivate]);
7478
+ const openLinkPopoverForSelected = (0, import_react5.useCallback)(() => {
7479
+ const anchor = selectedElRef.current;
7480
+ if (!anchor) return;
7481
+ const key = anchor.getAttribute("data-ohw-href-key");
7482
+ if (!key) return;
7483
+ bumpLinkPopoverGrace();
7484
+ setLinkPopover({
7485
+ key,
7486
+ target: getLinkHref(anchor)
7487
+ });
7488
+ deselect();
7489
+ }, [deselect]);
7490
+ const handleLinkPopoverSubmit = (0, import_react5.useCallback)(
6022
7491
  (target) => {
6023
7492
  if (!linkPopover) return;
6024
7493
  const { key } = linkPopover;
@@ -6031,13 +7500,15 @@ function OhhwellsBridge() {
6031
7500
  const showEditLink = toolbarShowEditLink;
6032
7501
  const currentSections = sectionsByPath[pathname] ?? [];
6033
7502
  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)(
7503
+ return bridgeRoot ? (0, import_react_dom2.createPortal)(
7504
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
7505
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: attachVisibleViewport, "data-ohw-visible-viewport": "", "aria-hidden": true }),
7506
+ toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
7507
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
7508
+ toolbarVariant === "rich-text" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6039
7509
  FloatingToolbar,
6040
7510
  {
7511
+ variant: "rich-text",
6041
7512
  rect: toolbarRect,
6042
7513
  parentScroll: parentScrollRef.current,
6043
7514
  elRef: toolbarElRef,
@@ -6046,9 +7517,21 @@ function OhhwellsBridge() {
6046
7517
  showEditLink,
6047
7518
  onEditLink: openLinkPopoverForActive
6048
7519
  }
7520
+ ),
7521
+ toolbarVariant === "link-action" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7522
+ FloatingToolbar,
7523
+ {
7524
+ variant: "link-action",
7525
+ rect: toolbarRect,
7526
+ parentScroll: parentScrollRef.current,
7527
+ elRef: toolbarElRef,
7528
+ onCommand: handleCommand,
7529
+ activeCommands,
7530
+ onEditLink: openLinkPopoverForSelected
7531
+ }
6049
7532
  )
6050
7533
  ] }),
6051
- maxBadge && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
7534
+ maxBadge && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
6052
7535
  "div",
6053
7536
  {
6054
7537
  "data-ohw-max-badge": "",
@@ -6074,7 +7557,7 @@ function OhhwellsBridge() {
6074
7557
  ]
6075
7558
  }
6076
7559
  ),
6077
- toggleState && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7560
+ toggleState && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6078
7561
  StateToggle,
6079
7562
  {
6080
7563
  rect: toggleState.rect,
@@ -6083,12 +7566,36 @@ function OhhwellsBridge() {
6083
7566
  onStateChange: handleStateChange
6084
7567
  }
6085
7568
  ),
6086
- linkPopover ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7569
+ sectionGap && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
7570
+ "div",
7571
+ {
7572
+ "data-ohw-section-insert-line": "",
7573
+ className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
7574
+ style: { top: sectionGap.y, transform: "translateY(-50%)" },
7575
+ children: [
7576
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
7577
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
7578
+ Badge,
7579
+ {
7580
+ 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",
7581
+ onClick: () => {
7582
+ window.parent.postMessage(
7583
+ { type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
7584
+ "*"
7585
+ );
7586
+ },
7587
+ children: "Add Section"
7588
+ }
7589
+ ),
7590
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
7591
+ ]
7592
+ }
7593
+ ),
7594
+ linkPopover && dialogPortalContainer ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6087
7595
  LinkPopover,
6088
7596
  {
6089
- rect: linkPopover.rect,
6090
- parentScroll: parentScrollRef.current,
6091
7597
  panelRef: linkPopoverPanelRef,
7598
+ portalContainer: dialogPortalContainer,
6092
7599
  open: true,
6093
7600
  mode: "edit",
6094
7601
  pages: sitePages,
@@ -6097,7 +7604,8 @@ function OhhwellsBridge() {
6097
7604
  initialTarget: linkPopover.target,
6098
7605
  onClose: closeLinkPopover,
6099
7606
  onSubmit: handleLinkPopoverSubmit
6100
- }
7607
+ },
7608
+ `${linkPopover.key}-${pathname}`
6101
7609
  ) : null
6102
7610
  ] }),
6103
7611
  bridgeRoot
@@ -6108,6 +7616,7 @@ function OhhwellsBridge() {
6108
7616
  LinkEditorPanel,
6109
7617
  LinkPopover,
6110
7618
  OhhwellsBridge,
7619
+ SchedulingWidget,
6111
7620
  Toggle,
6112
7621
  ToggleGroup,
6113
7622
  ToggleGroupItem,