@ohhwells/bridge 0.1.20 → 0.1.21

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
@@ -43,6 +43,7 @@ module.exports = __toCommonJS(index_exports);
43
43
  // src/OhhwellsBridge.tsx
44
44
  var import_react3 = __toESM(require("react"), 1);
45
45
  var import_client = require("react-dom/client");
46
+ var import_react_dom = require("react-dom");
46
47
 
47
48
  // src/ui/SchedulingWidget.tsx
48
49
  var import_react2 = require("react");
@@ -310,7 +311,19 @@ function LoadingSkeleton() {
310
311
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
311
312
  ] }, i)) }),
312
313
  [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
313
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-[60px] py-4", children: [
314
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:hidden", children: [
315
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0", children: [
316
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100px", height: "16px", borderRadius: "4px" } }),
317
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "52px", height: "20px", borderRadius: "999px" } }),
318
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "60px", height: "14px", borderRadius: "4px" } })
319
+ ] }),
320
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2", children: [
321
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "75%", height: "16px", borderRadius: "4px" } }),
322
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "50%", height: "14px", borderRadius: "4px" } }),
323
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100%", height: "44px", borderRadius: "8px" } })
324
+ ] })
325
+ ] }),
326
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hidden sm:flex items-center gap-[60px] py-4", children: [
314
327
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
315
328
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2", children: [
316
329
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
@@ -353,6 +366,28 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
353
366
  const scroll = (dir) => {
354
367
  scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
355
368
  };
369
+ const isDragging = (0, import_react2.useRef)(false);
370
+ const dragStartX = (0, import_react2.useRef)(0);
371
+ const dragStartScrollLeft = (0, import_react2.useRef)(0);
372
+ const onDragStart = (e) => {
373
+ if (!scrollRef.current) return;
374
+ isDragging.current = true;
375
+ dragStartX.current = e.clientX;
376
+ dragStartScrollLeft.current = scrollRef.current.scrollLeft;
377
+ scrollRef.current.style.cursor = "grabbing";
378
+ scrollRef.current.style.userSelect = "none";
379
+ };
380
+ const onDragMove = (e) => {
381
+ if (!isDragging.current || !scrollRef.current) return;
382
+ const delta = e.clientX - dragStartX.current;
383
+ scrollRef.current.scrollLeft = dragStartScrollLeft.current - delta;
384
+ };
385
+ const onDragEnd = () => {
386
+ if (!scrollRef.current) return;
387
+ isDragging.current = false;
388
+ scrollRef.current.style.cursor = "grab";
389
+ scrollRef.current.style.userSelect = "";
390
+ };
356
391
  const datesWithClasses = (0, import_react2.useMemo)(
357
392
  () => new Set(dates.map(
358
393
  (d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
@@ -370,7 +405,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
370
405
  "button",
371
406
  {
372
407
  onClick: () => scroll("left"),
373
- className: "absolute left-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 flex items-center justify-center rounded-full border-none cursor-pointer",
408
+ 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",
374
409
  style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
375
410
  "aria-label": "Scroll left",
376
411
  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" }) })
@@ -380,23 +415,36 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
380
415
  "button",
381
416
  {
382
417
  onClick: () => scroll("right"),
383
- className: "absolute right-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 flex items-center justify-center rounded-full border-none cursor-pointer",
418
+ 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",
384
419
  style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
385
420
  "aria-label": "Scroll right",
386
421
  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" }) })
387
422
  }
388
423
  ),
424
+ canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
425
+ "div",
426
+ {
427
+ className: "sm:hidden absolute left-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
428
+ style: { background: "linear-gradient(to right, var(--color-light,#FEFFF0), transparent)" }
429
+ }
430
+ ),
431
+ canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
432
+ "div",
433
+ {
434
+ className: "sm:hidden absolute right-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
435
+ style: { background: "linear-gradient(to left, var(--color-light,#FEFFF0), transparent)" }
436
+ }
437
+ ),
389
438
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
390
439
  "div",
391
440
  {
392
441
  ref: scrollRef,
393
442
  className: "overflow-x-auto w-full",
394
- style: {
395
- scrollbarWidth: "none",
396
- msOverflowStyle: "none",
397
- paddingLeft: canScrollLeft ? "36px" : 0,
398
- paddingRight: canScrollRight ? "36px" : 0
399
- },
443
+ style: { scrollbarWidth: "none", msOverflowStyle: "none", touchAction: "pan-x", cursor: "grab" },
444
+ onMouseDown: onDragStart,
445
+ onMouseMove: onDragMove,
446
+ onMouseUp: onDragEnd,
447
+ onMouseLeave: onDragEnd,
400
448
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
401
449
  const isToday = date.getTime() === todayMs;
402
450
  const isSelected = i === selectedIdx;
@@ -437,40 +485,60 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
437
485
  const available = cls.maxParticipants - booked;
438
486
  const isFull = available <= 0;
439
487
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
440
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-[60px] py-4 box-border", children: [
441
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-[120px] shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
442
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-3 min-w-0", children: [
443
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
444
- 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 })
488
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
489
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
490
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:w-[120px] sm:shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
491
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
492
+ "span",
493
+ {
494
+ className: "inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium sm:hidden",
495
+ style: { borderColor: "#0885FE", color: "#0885FE" },
496
+ children: "GROUP"
497
+ }
498
+ ),
499
+ /* @__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: [
500
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
501
+ available,
502
+ "/",
503
+ cls.maxParticipants
504
+ ] }),
505
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
506
+ ] }) })
445
507
  ] }),
446
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-14 shrink-0 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: [
447
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
448
- available,
449
- "/",
450
- cls.maxParticipants
508
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
509
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
510
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
511
+ 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 })
451
512
  ] }),
452
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
453
- ] }) }),
454
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
455
- "button",
456
- {
457
- className: "shrink-0 w-[200px] px-5 py-[10px] flex items-center justify-center rounded-lg font-body text-base font-bold cursor-pointer box-border",
458
- style: isFull ? {
459
- background: "transparent",
460
- border: "1px solid var(--color-dark, #200C02)",
461
- color: "var(--color-dark, #200C02)"
462
- } : {
463
- background: "var(--color-primary, #3D312B)",
464
- border: "none",
465
- color: "var(--color-light, #FEFFF0)"
466
- },
467
- onClick: () => {
468
- if (!cls.id) return;
469
- onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
470
- },
471
- children: isFull ? "Join Waitlist" : "Book Now"
472
- }
473
- )
513
+ /* @__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: [
514
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
515
+ available,
516
+ "/",
517
+ cls.maxParticipants
518
+ ] }),
519
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
520
+ ] }) }),
521
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
522
+ "button",
523
+ {
524
+ 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",
525
+ style: isFull ? {
526
+ background: "transparent",
527
+ border: "1px solid var(--color-dark, #200C02)",
528
+ color: "var(--color-dark, #200C02)"
529
+ } : {
530
+ background: "var(--color-primary, #3D312B)",
531
+ border: "none",
532
+ color: "var(--color-light, #FEFFF0)"
533
+ },
534
+ onClick: () => {
535
+ if (!cls.id) return;
536
+ onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
537
+ },
538
+ children: isFull ? "Join Waitlist" : "Book Now"
539
+ }
540
+ )
541
+ ] })
474
542
  ] }),
475
543
  i < selectedClasses.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
476
544
  ] }, cls.id ?? i);
@@ -500,7 +568,7 @@ function CalendarFoldIcon() {
500
568
  }
501
569
  );
502
570
  }
503
- function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
571
+ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
504
572
  const [schedule, setSchedule] = (0, import_react2.useState)(null);
505
573
  const [loading, setLoading] = (0, import_react2.useState)(true);
506
574
  const [inEditor, setInEditor] = (0, import_react2.useState)(false);
@@ -542,18 +610,21 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
542
610
  }, 5e3) : null;
543
611
  const handler = (e) => {
544
612
  if (e.data?.type === "ow:clear-scheduling-widget") {
613
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
545
614
  setSchedule(null);
546
615
  setLoading(false);
547
616
  return;
548
617
  }
549
618
  if (e.data?.type === "ow:switch-schedule") {
619
+ if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
550
620
  switchScheduleIdRef.current = e.data.scheduleId ?? null;
551
621
  initialized = false;
552
622
  setLoading(true);
553
- window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
623
+ window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
554
624
  return;
555
625
  }
556
626
  if (e.data?.type !== "ow:user-schedules-response") return;
627
+ if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
557
628
  if (initialized && switchScheduleIdRef.current === null) return;
558
629
  initialized = true;
559
630
  if (timer) clearTimeout(timer);
@@ -564,11 +635,16 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
564
635
  setSchedule(target);
565
636
  setLoading(false);
566
637
  if (notifyOnConnect && target && !isSwitching) {
567
- window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: target.id, name: target.name } }, "*");
638
+ window.parent.postMessage({
639
+ type: "ow:schedule-connected",
640
+ schedule: { id: target.id, name: target.name },
641
+ insertAfter
642
+ }, "*");
643
+ window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
568
644
  }
569
645
  };
570
646
  window.addEventListener("message", handler);
571
- if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules" }, "*");
647
+ if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
572
648
  return () => {
573
649
  window.removeEventListener("message", handler);
574
650
  if (timer) clearTimeout(timer);
@@ -629,16 +705,22 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
629
705
  const handleReplaceSchedule = () => {
630
706
  setIsHovered(false);
631
707
  if (schedule) {
632
- window.parent.postMessage({ type: "ow:schedule-connected", schedule: { id: schedule.id, name: schedule.name } }, "*");
708
+ window.parent.postMessage({
709
+ type: "ow:schedule-connected",
710
+ schedule: { id: schedule.id, name: schedule.name },
711
+ insertAfter
712
+ }, "*");
633
713
  } else {
634
- window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
714
+ window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
635
715
  }
636
716
  };
717
+ const sectionId = `scheduling-${insertAfter}`;
637
718
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
638
719
  "section",
639
720
  {
640
- "data-ohw-section": "scheduling",
641
- className: "w-full box-border py-20 px-16 font-body bg-(--color-light,#FEFFF0) relative",
721
+ "data-ohw-section": sectionId,
722
+ "data-ohw-scheduling-anchor": insertAfter,
723
+ className: "w-full box-border py-10 px-5 sm:py-20 sm:px-16 font-body bg-(--color-light,#FEFFF0) relative",
642
724
  onMouseEnter: () => inEditor && setIsHovered(true),
643
725
  onMouseLeave: () => setIsHovered(false),
644
726
  children: [
@@ -672,12 +754,12 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId }) {
672
754
  ),
673
755
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
674
756
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
675
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "font-display text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
757
+ /* @__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" }),
676
758
  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 })
677
759
  ] }),
678
760
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
679
761
  timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
680
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyState, { inEditor }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
762
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyState, { inEditor }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
681
763
  ScheduleView,
682
764
  {
683
765
  schedule,
@@ -4112,7 +4194,7 @@ function ToggleGroupItem({
4112
4194
  }
4113
4195
 
4114
4196
  // src/OhhwellsBridge.tsx
4115
- var import_react_dom = require("react-dom");
4197
+ var import_react_dom2 = require("react-dom");
4116
4198
  var import_navigation = require("next/navigation");
4117
4199
 
4118
4200
  // src/lib/session-search.ts
@@ -4217,24 +4299,135 @@ function getSectionsTracker() {
4217
4299
  }
4218
4300
  return el;
4219
4301
  }
4220
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId) {
4221
- const anchor = document.querySelector(`[data-ohw-section="${insertAfter}"]`);
4222
- if (!anchor) return false;
4223
- if (anchor.nextElementSibling?.dataset.ohwSectionContainer) return false;
4302
+ function updateSectionScheduleId(insertAfter, scheduleId) {
4303
+ const tracker = getSectionsTracker();
4304
+ let sections = [];
4305
+ try {
4306
+ sections = JSON.parse(tracker.textContent || "[]");
4307
+ } catch {
4308
+ }
4309
+ const currentPath = window.location.pathname;
4310
+ const updated = sections.map((s) => {
4311
+ if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
4312
+ if (s.pagePath && s.pagePath !== currentPath) return s;
4313
+ return { ...s, scheduleId };
4314
+ });
4315
+ tracker.textContent = JSON.stringify(updated);
4316
+ return tracker.textContent ?? "[]";
4317
+ }
4318
+ function schedulingSectionId(insertAfter) {
4319
+ return `scheduling-${insertAfter}`;
4320
+ }
4321
+ var INSERT_BEFORE_MARKER = ":before:";
4322
+ function parseSchedulingInsertAfter(insertAfter) {
4323
+ const idx = insertAfter.indexOf(INSERT_BEFORE_MARKER);
4324
+ if (idx < 0) return { anchor: insertAfter, insertBefore: null };
4325
+ return {
4326
+ anchor: insertAfter.slice(0, idx),
4327
+ insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
4328
+ };
4329
+ }
4330
+ function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
4331
+ const parsed = parseSchedulingInsertAfter(insertAfter);
4332
+ const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
4333
+ const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
4334
+ return { effectiveInsertAfter, insertBefore };
4335
+ }
4336
+ function getSchedulingMountPoint(insertAfter) {
4337
+ const { anchor } = parseSchedulingInsertAfter(insertAfter);
4338
+ let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
4339
+ if (!anchorEl && anchor === "scheduling") {
4340
+ const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
4341
+ anchorEl = widgets.at(-1) ?? null;
4342
+ }
4343
+ if (!anchorEl) return null;
4344
+ return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
4345
+ }
4346
+ function schedulingMountDepth(insertAfter) {
4347
+ if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
4348
+ return insertAfter.split(INSERT_BEFORE_MARKER).length - 1;
4349
+ }
4350
+ function getPageSchedulingEntries(raw) {
4351
+ if (!raw) return [];
4352
+ try {
4353
+ const entries = JSON.parse(raw);
4354
+ const currentPath = window.location.pathname;
4355
+ return entries.filter((e) => e.type === "scheduling" && (!e.pagePath || e.pagePath === currentPath));
4356
+ } catch {
4357
+ return [];
4358
+ }
4359
+ }
4360
+ function isSchedulingWidgetMissing(entry) {
4361
+ const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
4362
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
4363
+ }
4364
+ function hasMissingSchedulingWidgets(entries) {
4365
+ return entries.some(isSchedulingWidgetMissing);
4366
+ }
4367
+ function retryMissingSchedulingMounts(entries, notifyOnConnect = false) {
4368
+ if (!hasMissingSchedulingWidgets(entries)) return;
4369
+ mountSchedulingEntries(entries, notifyOnConnect);
4370
+ }
4371
+ function initSectionsFromContent(content, removeExisting = false) {
4372
+ const raw = content["__ohw_sections"];
4373
+ if (!raw) return;
4374
+ try {
4375
+ if (removeExisting) {
4376
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => el.remove());
4377
+ }
4378
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
4379
+ if (inEditor) getSectionsTracker().textContent = raw;
4380
+ const pageEntries = getPageSchedulingEntries(raw);
4381
+ mountSchedulingEntries(pageEntries, false);
4382
+ requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, false));
4383
+ setTimeout(() => retryMissingSchedulingMounts(pageEntries, false), 250);
4384
+ } catch {
4385
+ }
4386
+ }
4387
+ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
4388
+ const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
4389
+ const sectionId = schedulingSectionId(effectiveInsertAfter);
4390
+ if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
4391
+ const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
4392
+ if (!mountPoint) return false;
4224
4393
  const container = document.createElement("div");
4225
4394
  container.dataset.ohwSectionContainer = "scheduling";
4226
- anchor.insertAdjacentElement("afterend", container);
4227
- (0, import_client.createRoot)(container).render(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SchedulingWidget, { notifyOnConnect, initialScheduleId: scheduleId }));
4395
+ if (insertBefore) {
4396
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
4397
+ const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
4398
+ if (!beforePoint) return false;
4399
+ beforePoint.insertAdjacentElement("beforebegin", container);
4400
+ } else {
4401
+ let tail = mountPoint;
4402
+ while (tail.nextElementSibling?.dataset.ohwSectionContainer === "scheduling") {
4403
+ tail = tail.nextElementSibling;
4404
+ }
4405
+ tail.insertAdjacentElement("afterend", container);
4406
+ }
4407
+ const root = (0, import_client.createRoot)(container);
4408
+ (0, import_react_dom.flushSync)(() => {
4409
+ root.render(
4410
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4411
+ SchedulingWidget,
4412
+ {
4413
+ notifyOnConnect,
4414
+ initialScheduleId: scheduleId,
4415
+ insertAfter: effectiveInsertAfter
4416
+ }
4417
+ )
4418
+ );
4419
+ });
4228
4420
  const tracker = getSectionsTracker();
4229
4421
  let sections = [];
4230
4422
  try {
4231
4423
  sections = JSON.parse(tracker.textContent || "[]");
4232
4424
  } catch {
4233
4425
  }
4234
- if (!sections.find((s) => s.type === "scheduling" && s.insertAfter === insertAfter)) {
4426
+ const inEditor = typeof window !== "undefined" && window.self !== window.top;
4427
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
4235
4428
  sections.push({
4236
4429
  type: "scheduling",
4237
- insertAfter,
4430
+ insertAfter: effectiveInsertAfter,
4238
4431
  pagePath: window.location.pathname,
4239
4432
  ...scheduleId ? { scheduleId } : {}
4240
4433
  });
@@ -4242,6 +4435,17 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId)
4242
4435
  }
4243
4436
  return true;
4244
4437
  }
4438
+ function mountSchedulingEntries(entries, notifyOnConnect = false) {
4439
+ const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
4440
+ for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
4441
+ for (let i = pending.length - 1; i >= 0; i--) {
4442
+ const { insertAfter, scheduleId } = pending[i];
4443
+ if (mountSchedulingWidget(insertAfter, notifyOnConnect, scheduleId)) {
4444
+ pending.splice(i, 1);
4445
+ }
4446
+ }
4447
+ }
4448
+ }
4245
4449
  function collectEditableNodes() {
4246
4450
  return Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
4247
4451
  if (el.dataset.ohwEditable === "image") {
@@ -4555,6 +4759,23 @@ function StateToggle({
4555
4759
  );
4556
4760
  }
4557
4761
  var contentCache = /* @__PURE__ */ new Map();
4762
+ function resolveSubdomain(subdomainFromQuery) {
4763
+ if (subdomainFromQuery) return subdomainFromQuery;
4764
+ if (typeof window !== "undefined") {
4765
+ const parts = window.location.hostname.split(".");
4766
+ if (parts.length >= 3 && parts[0] !== "www") return parts[0];
4767
+ }
4768
+ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
4769
+ if (siteUrl) {
4770
+ try {
4771
+ const host = new URL(siteUrl).hostname;
4772
+ const siteParts = host.split(".");
4773
+ if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
4774
+ } catch {
4775
+ }
4776
+ }
4777
+ return "";
4778
+ }
4558
4779
  function OhhwellsBridge() {
4559
4780
  const pathname = (0, import_navigation.usePathname)();
4560
4781
  const router = (0, import_navigation.useRouter)();
@@ -4584,11 +4805,7 @@ function OhhwellsBridge() {
4584
4805
  };
4585
4806
  }, []);
4586
4807
  const subdomainFromQuery = searchParams.get("subdomain");
4587
- const subdomain = subdomainFromQuery ?? (() => {
4588
- if (typeof window === "undefined") return "";
4589
- const parts = window.location.hostname.split(".");
4590
- return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
4591
- })();
4808
+ const subdomain = resolveSubdomain(subdomainFromQuery);
4592
4809
  const postToParent = (0, import_react3.useCallback)((data) => {
4593
4810
  if (typeof window !== "undefined" && window.parent !== window) {
4594
4811
  window.parent.postMessage(data, "*");
@@ -4698,18 +4915,6 @@ function OhhwellsBridge() {
4698
4915
  }
4699
4916
  const applyContent = (content) => {
4700
4917
  const imageLoads = [];
4701
- document.querySelectorAll("[data-ohw-section-container]").forEach((el) => el.remove());
4702
- if (content["__ohw_sections"]) {
4703
- try {
4704
- const entries = JSON.parse(content["__ohw_sections"]);
4705
- const currentPath = window.location.pathname;
4706
- entries.forEach(({ type, insertAfter, scheduleId, pagePath }) => {
4707
- if (pagePath && pagePath !== currentPath) return;
4708
- if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
4709
- });
4710
- } catch {
4711
- }
4712
- }
4713
4918
  for (const [key, val] of Object.entries(content)) {
4714
4919
  if (key === "__ohw_sections") continue;
4715
4920
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -4730,6 +4935,7 @@ function OhhwellsBridge() {
4730
4935
  }
4731
4936
  });
4732
4937
  }
4938
+ initSectionsFromContent(content, true);
4733
4939
  return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
4734
4940
  }) : Promise.resolve();
4735
4941
  };
@@ -4756,20 +4962,11 @@ function OhhwellsBridge() {
4756
4962
  }, [subdomain, isEditMode, pathname]);
4757
4963
  (0, import_react3.useEffect)(() => {
4758
4964
  if (!subdomain || isEditMode) return;
4965
+ let debounceTimer = null;
4759
4966
  const applyFromCache = () => {
4760
4967
  const content = contentCache.get(subdomain);
4761
4968
  if (!content) return;
4762
- if (content["__ohw_sections"]) {
4763
- try {
4764
- const entries = JSON.parse(content["__ohw_sections"]);
4765
- const currentPath = window.location.pathname;
4766
- entries.forEach(({ type, insertAfter, scheduleId, pagePath }) => {
4767
- if (pagePath && pagePath !== currentPath) return;
4768
- if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
4769
- });
4770
- } catch {
4771
- }
4772
- }
4969
+ retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
4773
4970
  for (const [key, val] of Object.entries(content)) {
4774
4971
  if (key === "__ohw_sections") continue;
4775
4972
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -4785,11 +4982,18 @@ function OhhwellsBridge() {
4785
4982
  });
4786
4983
  }
4787
4984
  };
4788
- applyFromCache();
4789
- const observer = new MutationObserver(applyFromCache);
4985
+ const scheduleApply = () => {
4986
+ if (debounceTimer) clearTimeout(debounceTimer);
4987
+ debounceTimer = setTimeout(applyFromCache, 150);
4988
+ };
4989
+ scheduleApply();
4990
+ const observer = new MutationObserver(scheduleApply);
4790
4991
  observer.observe(document.body, { childList: true, subtree: true });
4791
- return () => observer.disconnect();
4792
- }, [subdomain, isEditMode]);
4992
+ return () => {
4993
+ observer.disconnect();
4994
+ if (debounceTimer) clearTimeout(debounceTimer);
4995
+ };
4996
+ }, [subdomain, isEditMode, pathname]);
4793
4997
  (0, import_react3.useLayoutEffect)(() => {
4794
4998
  const el = document.getElementById("ohw-loader");
4795
4999
  if (!el) return;
@@ -5362,17 +5566,10 @@ function OhhwellsBridge() {
5362
5566
  if (e.data?.type !== "ow:hydrate") return;
5363
5567
  const content = e.data.content;
5364
5568
  if (!content) return;
5569
+ let sectionsJson = null;
5365
5570
  for (const [key, val] of Object.entries(content)) {
5366
5571
  if (key === "__ohw_sections") {
5367
- try {
5368
- const entries = JSON.parse(val);
5369
- const tracker = getSectionsTracker();
5370
- tracker.textContent = val;
5371
- entries.forEach(({ type, insertAfter, scheduleId }) => {
5372
- if (type === "scheduling") mountSchedulingWidget(insertAfter, false, scheduleId);
5373
- });
5374
- } catch {
5375
- }
5572
+ sectionsJson = val;
5376
5573
  continue;
5377
5574
  }
5378
5575
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
@@ -5386,6 +5583,9 @@ function OhhwellsBridge() {
5386
5583
  }
5387
5584
  });
5388
5585
  }
5586
+ if (sectionsJson) {
5587
+ initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
5588
+ }
5389
5589
  postToParentRef.current({ type: "ow:hydrate-done" });
5390
5590
  };
5391
5591
  window.addEventListener("message", handleHydrate);
@@ -5436,9 +5636,9 @@ function OhhwellsBridge() {
5436
5636
  };
5437
5637
  const handleInsertSection = (e) => {
5438
5638
  if (e.data?.type !== "ow:insert-section") return;
5439
- const { widgetType, insertAfter } = e.data;
5639
+ const { widgetType, insertAfter, insertBefore } = e.data;
5440
5640
  if (widgetType !== "scheduling") return;
5441
- const inserted = mountSchedulingWidget(insertAfter, true);
5641
+ const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
5442
5642
  if (inserted) {
5443
5643
  const tracker = getSectionsTracker();
5444
5644
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
@@ -5449,39 +5649,31 @@ function OhhwellsBridge() {
5449
5649
  const handleSwitchSchedule = (e) => {
5450
5650
  if (e.data?.type !== "ow:switch-schedule") return;
5451
5651
  const scheduleId = e.data.scheduleId;
5452
- if (!scheduleId) return;
5453
- const tracker = getSectionsTracker();
5454
- let sections = [];
5455
- try {
5456
- sections = JSON.parse(tracker.textContent || "[]");
5457
- } catch {
5458
- }
5459
- const currentPath = window.location.pathname;
5460
- const updated = sections.map(
5461
- (s) => s.type === "scheduling" && s.pagePath === currentPath ? { ...s, scheduleId } : s
5462
- );
5463
- tracker.textContent = JSON.stringify(updated);
5464
- postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
5652
+ const insertAfter = e.data.insertAfter;
5653
+ if (!scheduleId || !insertAfter) return;
5654
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
5655
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
5656
+ };
5657
+ const handleScheduleLinked = (e) => {
5658
+ if (e.data?.type !== "ow:schedule-linked") return;
5659
+ const scheduleId = e.data.scheduleId;
5660
+ const insertAfter = e.data.insertAfter;
5661
+ if (!scheduleId || !insertAfter) return;
5662
+ const text = updateSectionScheduleId(insertAfter, scheduleId);
5663
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
5465
5664
  };
5466
5665
  const handleClearSchedulingWidget = (e) => {
5467
5666
  if (e.data?.type !== "ow:clear-scheduling-widget") return;
5468
- const tracker = getSectionsTracker();
5469
- let sections = [];
5470
- try {
5471
- sections = JSON.parse(tracker.textContent || "[]");
5472
- } catch {
5473
- }
5474
- const currentPath = window.location.pathname;
5475
- const updated = sections.map(
5476
- (s) => s.type === "scheduling" && s.pagePath === currentPath ? { ...s, scheduleId: null } : s
5477
- );
5478
- tracker.textContent = JSON.stringify(updated);
5479
- postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
5667
+ const insertAfter = e.data.insertAfter;
5668
+ if (!insertAfter) return;
5669
+ const text = updateSectionScheduleId(insertAfter, null);
5670
+ postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
5480
5671
  };
5481
5672
  const handleRemoveSchedulingSection = (e) => {
5482
5673
  if (e.data?.type !== "ow:remove-scheduling-section") return;
5483
- const els = document.querySelectorAll('[data-ohw-section="scheduling"]');
5484
- els.forEach((el) => el.parentElement?.removeChild(el));
5674
+ document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
5675
+ el.remove();
5676
+ });
5485
5677
  const tracker = getSectionsTracker();
5486
5678
  let sections = [];
5487
5679
  try {
@@ -5600,6 +5792,7 @@ function OhhwellsBridge() {
5600
5792
  window.addEventListener("message", handleSave);
5601
5793
  window.addEventListener("message", handleInsertSection);
5602
5794
  window.addEventListener("message", handleSwitchSchedule);
5795
+ window.addEventListener("message", handleScheduleLinked);
5603
5796
  window.addEventListener("message", handleClearSchedulingWidget);
5604
5797
  window.addEventListener("message", handleRemoveSchedulingSection);
5605
5798
  window.addEventListener("message", handleImageUrl);
@@ -5638,6 +5831,7 @@ function OhhwellsBridge() {
5638
5831
  window.removeEventListener("message", handleSave);
5639
5832
  window.removeEventListener("message", handleInsertSection);
5640
5833
  window.removeEventListener("message", handleSwitchSchedule);
5834
+ window.removeEventListener("message", handleScheduleLinked);
5641
5835
  window.removeEventListener("message", handleClearSchedulingWidget);
5642
5836
  window.removeEventListener("message", handleRemoveSchedulingSection);
5643
5837
  window.removeEventListener("message", handleImageUrl);
@@ -5695,7 +5889,7 @@ function OhhwellsBridge() {
5695
5889
  }
5696
5890
  setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
5697
5891
  }, [deactivate]);
5698
- return bridgeRoot ? (0, import_react_dom.createPortal)(
5892
+ return bridgeRoot ? (0, import_react_dom2.createPortal)(
5699
5893
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5700
5894
  toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5701
5895
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),