@ohhwells/bridge 0.1.54-next.146 → 0.1.54-next.148

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
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
+ EmptySection: () => EmptySection,
49
50
  ItemActionToolbar: () => ItemActionToolbar,
50
51
  ItemInteractionLayer: () => ItemInteractionLayer,
51
52
  LinkEditorPanel: () => LinkEditorPanel,
@@ -409,36 +410,53 @@ function RatingEl({ value, ctx }) {
409
410
  }
410
411
  );
411
412
  }
412
- function PersonEl({
413
+ function AvatarDisc({
413
414
  person,
414
415
  ctx,
415
- path
416
+ path,
417
+ size
416
418
  }) {
417
419
  const avatarUrl = person.avatar ? ctx.resolveMedia(person.avatar) : null;
418
420
  const avatarAttrs = ctx.keyFor ? { "data-ohw-key": ctx.keyFor(`${path}.person.avatar`), "data-ohw-editable": "image" } : {};
421
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
422
+ "span",
423
+ {
424
+ ...avatarAttrs,
425
+ style: {
426
+ width: size,
427
+ height: size,
428
+ borderRadius: "50%",
429
+ background: "#DBEAFE",
430
+ overflow: "hidden",
431
+ flexShrink: 0,
432
+ display: "block"
433
+ },
434
+ children: (avatarUrl || ctx.keyFor) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
435
+ "img",
436
+ {
437
+ src: avatarUrl ?? TRANSPARENT_PX,
438
+ alt: "",
439
+ style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
440
+ }
441
+ )
442
+ }
443
+ );
444
+ }
445
+ function avatarPosition(slots) {
446
+ const person = slots.person;
447
+ if (typeof person?.avatar !== "string" || !person.avatar) return null;
448
+ if (slots.mediaPosition === "left") return "left";
449
+ if (slots.mediaPosition === "bottom") return "bottom";
450
+ return "top";
451
+ }
452
+ function PersonEl({
453
+ person,
454
+ ctx,
455
+ path,
456
+ showAvatar = true
457
+ }) {
419
458
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 10, alignItems: "center" }, children: [
420
- person.avatar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
421
- "span",
422
- {
423
- ...avatarAttrs,
424
- style: {
425
- width: 56,
426
- height: 56,
427
- borderRadius: "50%",
428
- background: "#DBEAFE",
429
- overflow: "hidden",
430
- flexShrink: 0
431
- },
432
- children: (avatarUrl || ctx.keyFor) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
433
- "img",
434
- {
435
- src: avatarUrl ?? TRANSPARENT_PX,
436
- alt: "",
437
- style: { width: "100%", height: "100%", objectFit: "cover" }
438
- }
439
- )
440
- }
441
- ),
459
+ person.avatar && showAvatar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AvatarDisc, { person, ctx, path, size: 56 }),
442
460
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { flex: 1, minWidth: 0 }, children: [
443
461
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
444
462
  "span",
@@ -549,9 +567,35 @@ function TestimonialCard({ node, ctx, path }) {
549
567
  const slots = node.slots ?? {};
550
568
  const hasBg = slots.background === true;
551
569
  const person = slots.person;
570
+ const avatarPos = avatarPosition(slots);
571
+ const content = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
572
+ "div",
573
+ {
574
+ style: {
575
+ display: "flex",
576
+ flexDirection: "column",
577
+ gap: AI_TREE_TOKENS.spacing6,
578
+ minWidth: 0,
579
+ flex: avatarPos === "left" ? 1 : void 0
580
+ },
581
+ children: [
582
+ typeof slots.rating === "number" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RatingEl, { value: slots.rating, ctx }),
583
+ str(slots.quote) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
584
+ "div",
585
+ {
586
+ ...textAttrs(ctx, `${path}.quote`),
587
+ style: { ...typeStyle(AI_TREE_TOKENS.type.bodyM, ctx.brand.fonts.body), color: ctx.brand.palette.dark },
588
+ dangerouslySetInnerHTML: { __html: `"${str(slots.quote)}"` }
589
+ }
590
+ ),
591
+ person && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PersonEl, { person, ctx, path, showAvatar: avatarPos === "bottom" })
592
+ ]
593
+ }
594
+ );
552
595
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
553
596
  "div",
554
597
  {
598
+ "data-ai-avatar-pos": avatarPos ?? void 0,
555
599
  style: {
556
600
  background: hasBg ? ctx.cardSurface : "transparent",
557
601
  borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
@@ -564,22 +608,15 @@ function TestimonialCard({ node, ctx, path }) {
564
608
  {
565
609
  style: {
566
610
  display: "flex",
567
- flexDirection: "column",
611
+ flexDirection: avatarPos === "left" ? "row" : "column",
612
+ alignItems: avatarPos === "left" ? "flex-start" : void 0,
568
613
  gap: AI_TREE_TOKENS.spacing6,
569
614
  padding: hasBg ? AI_TREE_TOKENS.paddingBlock : 0,
570
615
  minWidth: 0
571
616
  },
572
617
  children: [
573
- typeof slots.rating === "number" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RatingEl, { value: slots.rating, ctx }),
574
- str(slots.quote) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
575
- "div",
576
- {
577
- ...textAttrs(ctx, `${path}.quote`),
578
- style: { ...typeStyle(AI_TREE_TOKENS.type.bodyM, ctx.brand.fonts.body), color: ctx.brand.palette.dark },
579
- dangerouslySetInnerHTML: { __html: `"${str(slots.quote)}"` }
580
- }
581
- ),
582
- person && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PersonEl, { person, ctx, path })
618
+ person && (avatarPos === "top" || avatarPos === "left") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AvatarDisc, { person, ctx, path, size: 72 }),
619
+ content
583
620
  ]
584
621
  }
585
622
  )
@@ -590,37 +627,26 @@ function TeamCard({ node, ctx, path }) {
590
627
  const slots = node.slots ?? {};
591
628
  const hasBg = slots.background === true;
592
629
  const person = slots.person ?? {};
593
- const avatarUrl = person.avatar ? ctx.resolveMedia(person.avatar) : null;
594
- const avatarAttrs = ctx.keyFor ? { "data-ohw-key": ctx.keyFor(`${path}.person.avatar`), "data-ohw-editable": "image" } : {};
630
+ const avatarPos = avatarPosition(slots);
595
631
  const buttonIdx = (node.children ?? []).findIndex((c) => c.type === "button");
596
632
  const button = buttonIdx >= 0 ? (node.children ?? [])[buttonIdx] : void 0;
597
633
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
598
634
  "div",
599
635
  {
636
+ "data-ai-avatar-pos": avatarPos ?? void 0,
600
637
  style: {
601
638
  background: hasBg ? ctx.cardSurface : "transparent",
602
639
  borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
603
640
  overflow: "hidden",
604
641
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
605
- minWidth: 0
642
+ minWidth: 0,
643
+ display: avatarPos === "left" ? "flex" : "block",
644
+ alignItems: avatarPos === "left" ? "flex-start" : void 0
606
645
  },
607
646
  children: [
608
- person.avatar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: `${AI_TREE_TOKENS.paddingBlock}px ${AI_TREE_TOKENS.paddingBlock}px 0` }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
609
- "span",
610
- {
611
- ...avatarAttrs,
612
- style: { width: 80, height: 80, borderRadius: "50%", overflow: "hidden", display: "block" },
613
- children: (avatarUrl || ctx.keyFor) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
614
- "img",
615
- {
616
- src: avatarUrl ?? TRANSPARENT_PX,
617
- alt: "",
618
- style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
619
- }
620
- )
621
- }
622
- ) }),
623
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing4, padding: AI_TREE_TOKENS.paddingBlock }, children: [
647
+ avatarPos === "top" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: `${AI_TREE_TOKENS.paddingBlock}px ${AI_TREE_TOKENS.paddingBlock}px 0` }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AvatarDisc, { person, ctx, path, size: 80 }) }),
648
+ avatarPos === "left" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: `${AI_TREE_TOKENS.paddingBlock}px 0 ${AI_TREE_TOKENS.paddingBlock}px ${AI_TREE_TOKENS.paddingBlock}px`, flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AvatarDisc, { person, ctx, path, size: 80 }) }),
649
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing4, padding: AI_TREE_TOKENS.paddingBlock, minWidth: 0, flex: avatarPos === "left" ? 1 : void 0 }, children: [
624
650
  person.name && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
625
651
  "div",
626
652
  {
@@ -653,7 +679,8 @@ function TeamCard({ node, ctx, path }) {
653
679
  },
654
680
  i
655
681
  )) }),
656
- button && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonEl, { slots: button.slots ?? {}, ctx, path: `${path}.c${buttonIdx}` }) })
682
+ button && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", justifyContent: "center", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonEl, { slots: button.slots ?? {}, ctx, path: `${path}.c${buttonIdx}` }) }),
683
+ avatarPos === "bottom" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AvatarDisc, { person, ctx, path, size: 80 })
657
684
  ] })
658
685
  ]
659
686
  }
@@ -808,56 +835,78 @@ function CardBlock({ node, ctx, path }) {
808
835
  }
809
836
  );
810
837
  }
838
+ function toggleAccordionItem(button) {
839
+ const wasOpen = button.getAttribute("aria-expanded") === "true";
840
+ const list = button.closest("[data-ai-accordion]");
841
+ for (const other of list?.querySelectorAll("button[aria-expanded]") ?? []) {
842
+ other.setAttribute("aria-expanded", "false");
843
+ const panel = other.nextElementSibling;
844
+ if (panel) panel.style.display = "none";
845
+ const chevron = other.querySelector("[data-ai-chevron]");
846
+ if (chevron) chevron.style.transform = "";
847
+ }
848
+ if (!wasOpen) {
849
+ button.setAttribute("aria-expanded", "true");
850
+ const panel = button.nextElementSibling;
851
+ if (panel) panel.style.display = "block";
852
+ const chevron = button.querySelector("[data-ai-chevron]");
853
+ if (chevron) chevron.style.transform = "rotate(180deg)";
854
+ }
855
+ }
811
856
  function AccordionBlock({ node, ctx, path }) {
812
- const [open, setOpen] = import_react.default.useState(null);
813
857
  const rule = `1px solid ${ctx.brand.palette.dark}33`;
814
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { borderTop: rule }, children: (node.items ?? []).map((item, i) => {
815
- const isOpen = open === i;
816
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { borderBottom: rule }, children: [
817
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
818
- "button",
819
- {
820
- type: "button",
821
- "aria-expanded": isOpen,
822
- onClick: () => setOpen(isOpen ? null : i),
823
- style: {
824
- ...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body),
825
- color: ctx.brand.palette.dark,
826
- display: "flex",
827
- justifyContent: "space-between",
828
- alignItems: "center",
829
- width: "100%",
830
- padding: "14px 0",
831
- background: "transparent",
832
- border: "none",
833
- cursor: "pointer",
834
- textAlign: "left",
835
- gap: AI_TREE_TOKENS.spacing3
836
- },
837
- children: [
838
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.items.${i}.question`), children: item.question }),
839
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
840
- import_lucide_react.ChevronDown,
841
- {
842
- size: 20,
843
- strokeWidth: 1.5,
844
- "aria-hidden": true,
845
- style: { flexShrink: 0, transform: isOpen ? "rotate(180deg)" : void 0, transition: "transform 0.2s" }
846
- }
847
- )
848
- ]
849
- }
850
- ),
851
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: isOpen ? "block" : "none", paddingBottom: 14 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
852
- "div",
853
- {
854
- ...textAttrs(ctx, `${path}.items.${i}.answer`),
855
- style: { ...typeStyle(AI_TREE_TOKENS.type.bodyM, ctx.brand.fonts.body), color: ctx.brand.palette.accent },
856
- dangerouslySetInnerHTML: { __html: item.answer }
857
- }
858
- ) })
859
- ] }, i);
860
- }) });
858
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-ai-accordion": "", style: { borderTop: rule }, children: (node.items ?? []).map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { borderBottom: rule }, children: [
859
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
860
+ "button",
861
+ {
862
+ type: "button",
863
+ "aria-expanded": false,
864
+ onClick: (e) => toggleAccordionItem(e.currentTarget),
865
+ style: {
866
+ ...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body),
867
+ color: ctx.brand.palette.dark,
868
+ display: "flex",
869
+ justifyContent: "space-between",
870
+ alignItems: "center",
871
+ width: "100%",
872
+ padding: "14px 0",
873
+ background: "transparent",
874
+ border: "none",
875
+ cursor: "pointer",
876
+ textAlign: "left",
877
+ gap: AI_TREE_TOKENS.spacing3
878
+ },
879
+ children: [
880
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
881
+ "span",
882
+ {
883
+ ...textAttrs(ctx, `${path}.items.${i}.question`),
884
+ style: { flex: 1, minWidth: 0 },
885
+ dangerouslySetInnerHTML: { __html: item.question }
886
+ }
887
+ ),
888
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
889
+ import_lucide_react.ChevronDown,
890
+ {
891
+ "data-ai-chevron": "",
892
+ size: 20,
893
+ strokeWidth: 1.5,
894
+ "aria-hidden": true,
895
+ style: { flexShrink: 0, transition: "transform 0.2s" }
896
+ }
897
+ )
898
+ ]
899
+ }
900
+ ),
901
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "none", paddingBottom: 14 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
902
+ "div",
903
+ {
904
+ ...textAttrs(ctx, `${path}.items.${i}.answer`),
905
+ style: { ...typeStyle(AI_TREE_TOKENS.type.bodyM, ctx.brand.fonts.body), color: ctx.brand.palette.accent },
906
+ dangerouslySetInnerHTML: { __html: item.answer }
907
+ }
908
+ ) })
909
+ ] }, i)) });
861
910
  }
862
911
  function Carousel({ items, itemsPerRow, ctx }) {
863
912
  const [page, setPage] = import_react.default.useState(0);
@@ -1978,7 +2027,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
1978
2027
  const autoId = (0, import_react5.useId)();
1979
2028
  const insertAfter = insertAfterProp ?? autoId;
1980
2029
  const [schedule, setSchedule] = (0, import_react5.useState)(null);
1981
- const [loading, setLoading] = (0, import_react5.useState)(true);
2030
+ const [loading, setLoading] = (0, import_react5.useState)(initialScheduleId !== null);
1982
2031
  const [inEditor, setInEditor] = (0, import_react5.useState)(false);
1983
2032
  const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
1984
2033
  const [modalState, setModalState] = (0, import_react5.useState)(null);
@@ -2152,8 +2201,10 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
2152
2201
  "*"
2153
2202
  );
2154
2203
  };
2155
- if (!inEditor && !loading && !schedule) return null;
2156
2204
  const sectionId = `scheduling-${insertAfter}`;
2205
+ if (!inEditor && !loading && !schedule) {
2206
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { "data-ohw-section": sectionId, "data-ohw-scheduling-anchor": insertAfter, style: { display: "none" } });
2207
+ }
2157
2208
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2158
2209
  "section",
2159
2210
  {
@@ -12200,21 +12251,10 @@ function parseSchedulingInsertAfter(insertAfter) {
12200
12251
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
12201
12252
  };
12202
12253
  }
12203
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
12204
- const parsed = parseSchedulingInsertAfter(insertAfter);
12205
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
12206
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
12207
- return { effectiveInsertAfter, insertBefore };
12208
- }
12209
- function getSchedulingMountPoint(insertAfter) {
12210
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
12211
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
12212
- if (!anchorEl && anchor === "scheduling") {
12213
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
12214
- anchorEl = widgets.at(-1) ?? null;
12215
- }
12216
- if (!anchorEl) return null;
12217
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
12254
+ function resolveEntryAnchor(entry) {
12255
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
12256
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
12257
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
12218
12258
  }
12219
12259
  function schedulingMountDepth(insertAfter) {
12220
12260
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -12231,8 +12271,7 @@ function getPageSchedulingEntries(raw) {
12231
12271
  }
12232
12272
  }
12233
12273
  function isSchedulingWidgetMissing(entry) {
12234
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
12235
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
12274
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
12236
12275
  }
12237
12276
  function hasMissingSchedulingWidgets(entries) {
12238
12277
  return entries.some(isSchedulingWidgetMissing);
@@ -12262,16 +12301,17 @@ function initSectionsFromContent(content, removeExisting = false) {
12262
12301
  } catch {
12263
12302
  }
12264
12303
  }
12265
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
12266
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
12267
- const sectionId = schedulingSectionId(effectiveInsertAfter);
12304
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
12305
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
12306
+ const sectionId = schedulingSectionId(widgetId);
12268
12307
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
12269
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
12270
- if (!mountPoint) return false;
12308
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
12309
+ if (!anchorEl) return false;
12310
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
12271
12311
  const container = document.createElement("div");
12272
12312
  container.dataset.ohwSectionContainer = "scheduling";
12273
- if (insertBefore) {
12274
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
12313
+ if (beforeId) {
12314
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
12275
12315
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
12276
12316
  if (!beforePoint) return false;
12277
12317
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -12282,19 +12322,25 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
12282
12322
  }
12283
12323
  tail.insertAdjacentElement("afterend", container);
12284
12324
  }
12285
- const root = (0, import_client2.createRoot)(container);
12286
- (0, import_react_dom3.flushSync)(() => {
12287
- root.render(
12288
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
12289
- SchedulingWidget,
12290
- {
12291
- notifyOnConnect,
12292
- initialScheduleId: scheduleId,
12293
- insertAfter: effectiveInsertAfter
12294
- }
12295
- )
12296
- );
12297
- });
12325
+ try {
12326
+ const root = (0, import_client2.createRoot)(container);
12327
+ (0, import_react_dom3.flushSync)(() => {
12328
+ root.render(
12329
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
12330
+ SchedulingWidget,
12331
+ {
12332
+ notifyOnConnect,
12333
+ initialScheduleId: scheduleId,
12334
+ insertAfter: widgetId
12335
+ }
12336
+ )
12337
+ );
12338
+ });
12339
+ } catch (err) {
12340
+ console.error("[ow:scheduling] render threw", err);
12341
+ container.remove();
12342
+ return false;
12343
+ }
12298
12344
  const tracker = getSectionsTracker();
12299
12345
  let sections = [];
12300
12346
  try {
@@ -12302,10 +12348,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
12302
12348
  } catch {
12303
12349
  }
12304
12350
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
12305
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
12351
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
12306
12352
  sections.push({
12307
12353
  type: "scheduling",
12308
- insertAfter: effectiveInsertAfter,
12354
+ insertAfter: widgetId,
12355
+ anchorId,
12356
+ beforeId: beforeId ?? null,
12309
12357
  pagePath: window.location.pathname,
12310
12358
  ...scheduleId ? { scheduleId } : {}
12311
12359
  });
@@ -12319,7 +12367,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
12319
12367
  for (let i = pending.length - 1; i >= 0; i--) {
12320
12368
  const entry = pending[i];
12321
12369
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
12322
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
12370
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
12371
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
12323
12372
  pending.splice(i, 1);
12324
12373
  }
12325
12374
  }
@@ -13255,6 +13304,7 @@ function StateToggle({
13255
13304
  );
13256
13305
  }
13257
13306
  var contentCache = /* @__PURE__ */ new Map();
13307
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
13258
13308
  function resolveSubdomain(subdomainFromQuery) {
13259
13309
  if (subdomainFromQuery) return subdomainFromQuery;
13260
13310
  if (typeof window !== "undefined") {
@@ -14593,7 +14643,9 @@ function OhhwellsBridge() {
14593
14643
  let cancelled = false;
14594
14644
  setFetchState("loading");
14595
14645
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
14596
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
14646
+ const initialPath = pathname;
14647
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
14648
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
14597
14649
  if (cancelled) return;
14598
14650
  const content = data?.content ?? {};
14599
14651
  contentCache.set(subdomain, content);
@@ -14653,6 +14705,17 @@ function OhhwellsBridge() {
14653
14705
  debounceTimer = setTimeout(applyFromCache, 150);
14654
14706
  };
14655
14707
  applyFromCache();
14708
+ const pathCacheKey = `${subdomain}::${pathname}`;
14709
+ if (!fetchedContentPaths.has(pathCacheKey)) {
14710
+ fetchedContentPaths.add(pathCacheKey);
14711
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
14712
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
14713
+ if (!data?.content) return;
14714
+ contentCache.set(subdomain, data.content);
14715
+ applyFromCache();
14716
+ }).catch(() => {
14717
+ });
14718
+ }
14656
14719
  observer = new MutationObserver(scheduleApply);
14657
14720
  observer.observe(document.body, { childList: true, subtree: true });
14658
14721
  return () => {
@@ -16481,8 +16544,12 @@ function OhhwellsBridge() {
16481
16544
  if (inserted) {
16482
16545
  const tracker = getSectionsTracker();
16483
16546
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
16484
- const h = document.documentElement.scrollHeight;
16485
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
16547
+ const reportHeight = () => {
16548
+ const h = document.body.scrollHeight;
16549
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
16550
+ };
16551
+ reportHeight();
16552
+ setTimeout(reportHeight, 500);
16486
16553
  }
16487
16554
  };
16488
16555
  const handleSwitchSchedule = (e) => {
@@ -17711,6 +17778,59 @@ function OhhwellsBridge() {
17711
17778
  bridgeRoot
17712
17779
  ) : null;
17713
17780
  }
17781
+
17782
+ // src/ui/EmptySection.tsx
17783
+ var import_link = __toESM(require("next/link"), 1);
17784
+ var import_jsx_runtime33 = require("react/jsx-runtime");
17785
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
17786
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
17787
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17788
+ "p",
17789
+ {
17790
+ style: {
17791
+ fontFamily: "var(--brand-font-body)",
17792
+ fontSize: "0.75rem",
17793
+ fontWeight: 500,
17794
+ letterSpacing: "0.15em",
17795
+ textTransform: "uppercase",
17796
+ color: "var(--brand-accent)",
17797
+ marginBottom: "1.5rem"
17798
+ },
17799
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_link.default, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
17800
+ }
17801
+ ),
17802
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17803
+ "h1",
17804
+ {
17805
+ style: {
17806
+ fontFamily: "var(--brand-font-heading)",
17807
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
17808
+ lineHeight: 1.1,
17809
+ letterSpacing: "-0.025em",
17810
+ color: "var(--brand-text)",
17811
+ marginBottom: "1rem"
17812
+ },
17813
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
17814
+ children: title
17815
+ }
17816
+ ),
17817
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
17818
+ "p",
17819
+ {
17820
+ style: {
17821
+ fontFamily: "var(--brand-font-body)",
17822
+ fontSize: "1rem",
17823
+ lineHeight: 1.7,
17824
+ fontWeight: 300,
17825
+ color: "var(--brand-text-muted)",
17826
+ maxWidth: "340px"
17827
+ },
17828
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
17829
+ children: "This page doesn't have any content yet."
17830
+ }
17831
+ )
17832
+ ] });
17833
+ }
17714
17834
  // Annotate the CommonJS export names for ESM import in node:
17715
17835
  0 && (module.exports = {
17716
17836
  AI_DEFAULT_BRAND,
@@ -17728,6 +17848,7 @@ function OhhwellsBridge() {
17728
17848
  DropdownMenuItem,
17729
17849
  DropdownMenuSeparator,
17730
17850
  DropdownMenuTrigger,
17851
+ EmptySection,
17731
17852
  ItemActionToolbar,
17732
17853
  ItemInteractionLayer,
17733
17854
  LinkEditorPanel,