@ohhwells/bridge 0.1.70-next.215 → 0.1.71-next.216

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
@@ -1293,13 +1293,44 @@ function AccordionBlock({ node, ctx, path }) {
1293
1293
  ) })
1294
1294
  ] }, i)) });
1295
1295
  }
1296
+ function useIsMobile() {
1297
+ const [mobile, setMobile] = import_react.default.useState(
1298
+ () => typeof window !== "undefined" && window.matchMedia("(max-width: 768px)").matches
1299
+ );
1300
+ import_react.default.useEffect(() => {
1301
+ const mq = window.matchMedia("(max-width: 768px)");
1302
+ const update = () => setMobile(mq.matches);
1303
+ update();
1304
+ mq.addEventListener("change", update);
1305
+ return () => mq.removeEventListener("change", update);
1306
+ }, []);
1307
+ return mobile;
1308
+ }
1296
1309
  function Carousel({ items, itemsPerRow, ctx }) {
1310
+ const isMobile = useIsMobile();
1311
+ const perPage = isMobile ? 1 : itemsPerRow;
1312
+ const pages = Math.max(1, Math.ceil(items.length / perPage));
1297
1313
  const [page, setPage] = import_react.default.useState(0);
1298
- const pages = Math.max(1, Math.ceil(items.length / itemsPerRow));
1299
1314
  const current = Math.min(page, pages - 1);
1315
+ if (pages <= 1) {
1316
+ const cols = Math.max(1, Math.min(items.length, itemsPerRow));
1317
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1318
+ "div",
1319
+ {
1320
+ "data-ai-grid": String(cols),
1321
+ style: {
1322
+ display: "grid",
1323
+ gridTemplateColumns: `repeat(${cols}, 1fr)`,
1324
+ gap: AI_TREE_TOKENS.spacing8,
1325
+ alignItems: "start"
1326
+ },
1327
+ children: items
1328
+ }
1329
+ );
1330
+ }
1300
1331
  const pageGroups = Array.from(
1301
1332
  { length: pages },
1302
- (_, p) => items.slice(p * itemsPerRow, (p + 1) * itemsPerRow)
1333
+ (_, p) => items.slice(p * perPage, (p + 1) * perPage)
1303
1334
  );
1304
1335
  const chrome = (enabled) => ({
1305
1336
  border: `1px solid ${ctx.brand.palette.dark}`,
@@ -1324,55 +1355,69 @@ function Carousel({ items, itemsPerRow, ctx }) {
1324
1355
  cursor: "pointer",
1325
1356
  padding: 0
1326
1357
  });
1327
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing8, alignItems: "center" }, children: [
1328
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: AI_TREE_TOKENS.spacing6, width: "100%" }, children: [
1329
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1330
- "button",
1331
- {
1332
- type: "button",
1333
- "aria-label": "Previous",
1334
- onClick: () => setPage((p) => Math.max(0, p - 1)),
1335
- style: chrome(current > 0),
1336
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
1337
- }
1338
- ),
1339
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1358
+ const viewport = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: isMobile ? "0 0 auto" : 1, minWidth: 0, width: "100%", overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1359
+ "div",
1360
+ {
1361
+ style: {
1362
+ display: "flex",
1363
+ transform: `translateX(-${current * 100}%)`,
1364
+ transition: "transform 0.4s ease"
1365
+ },
1366
+ children: pageGroups.map((group, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1340
1367
  "div",
1341
1368
  {
1369
+ "data-ai-grid": String(perPage),
1342
1370
  style: {
1343
- display: "flex",
1344
- transform: `translateX(-${current * 100}%)`,
1345
- transition: "transform 0.4s ease"
1371
+ flex: "0 0 100%",
1372
+ display: "grid",
1373
+ gridTemplateColumns: `repeat(${perPage}, 1fr)`,
1374
+ gap: AI_TREE_TOKENS.spacing8,
1375
+ alignItems: "start"
1346
1376
  },
1347
- children: pageGroups.map((group, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1348
- "div",
1349
- {
1350
- "data-ai-grid": String(itemsPerRow),
1351
- style: {
1352
- flex: "0 0 100%",
1353
- display: "grid",
1354
- gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`,
1355
- gap: AI_TREE_TOKENS.spacing8,
1356
- alignItems: "start"
1357
- },
1358
- children: group
1359
- },
1360
- p
1361
- ))
1362
- }
1363
- ) }),
1364
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1365
- "button",
1366
- {
1367
- type: "button",
1368
- "aria-label": "Next",
1369
- onClick: () => setPage((p) => Math.min(pages - 1, p + 1)),
1370
- style: chrome(current < pages - 1),
1371
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRight, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
1372
- }
1373
- )
1377
+ children: group
1378
+ },
1379
+ p
1380
+ ))
1381
+ }
1382
+ ) });
1383
+ const prevBtn = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1384
+ "button",
1385
+ {
1386
+ type: "button",
1387
+ "aria-label": "Previous",
1388
+ onClick: () => setPage((p) => Math.max(0, p - 1)),
1389
+ style: chrome(current > 0),
1390
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowLeft, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
1391
+ }
1392
+ );
1393
+ const nextBtn = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1394
+ "button",
1395
+ {
1396
+ type: "button",
1397
+ "aria-label": "Next",
1398
+ onClick: () => setPage((p) => Math.min(pages - 1, p + 1)),
1399
+ style: chrome(current < pages - 1),
1400
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRight, { size: 24, strokeWidth: 1.5, "aria-hidden": true })
1401
+ }
1402
+ );
1403
+ const dots = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: 9, justifyContent: "center" }, children: pageGroups.map((_, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": `Page ${p + 1}`, onClick: () => setPage(p), style: dot(p === current) }, p)) });
1404
+ if (isMobile) {
1405
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing6, alignItems: "center" }, children: [
1406
+ viewport,
1407
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: AI_TREE_TOKENS.spacing6, justifyContent: "center" }, children: [
1408
+ prevBtn,
1409
+ nextBtn
1410
+ ] }),
1411
+ dots
1412
+ ] });
1413
+ }
1414
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-ai-carousel": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing8, alignItems: "center" }, children: [
1415
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: AI_TREE_TOKENS.spacing6, width: "100%" }, children: [
1416
+ prevBtn,
1417
+ viewport,
1418
+ nextBtn
1374
1419
  ] }),
1375
- pages > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: 9, justifyContent: "center" }, children: pageGroups.map((_, p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", "aria-label": `Page ${p + 1}`, onClick: () => setPage(p), style: dot(p === current) }, p)) })
1420
+ dots
1376
1421
  ] });
1377
1422
  }
1378
1423
  function CollectionBlock({ node, ctx, path }) {
@@ -1589,33 +1634,81 @@ function renderNode(node, ctx, path) {
1589
1634
  }
1590
1635
  );
1591
1636
  }
1592
- case "form":
1593
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing4 }, children: (node.children ?? []).map((child, i) => {
1637
+ case "form": {
1638
+ const formAttrs = ctx.keyFor ? {
1639
+ "data-ohw-editable": "form",
1640
+ "data-ohw-key": ctx.keyFor(`${path}.form`),
1641
+ "data-ohw-success-text": "Thanks \u2014 we'll be in touch shortly."
1642
+ } : {};
1643
+ const fieldStyle = {
1644
+ width: "100%",
1645
+ boxSizing: "border-box",
1646
+ border: `1px solid ${ctx.brand.palette.accent}`,
1647
+ borderRadius: AI_TREE_TOKENS.radiusButton,
1648
+ padding: "12px 14px",
1649
+ background: "#fff",
1650
+ color: ctx.brand.palette.dark,
1651
+ outline: "none",
1652
+ ...typeStyle(AI_TREE_TOKENS.type.bodyM, ctx.brand.fonts.body)
1653
+ };
1654
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("form", { ...formAttrs, style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing4 }, children: (node.children ?? []).map((child, i) => {
1594
1655
  if (child.type === "input") {
1595
- const cs = child.slots ?? {};
1596
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1656
+ const cs2 = child.slots ?? {};
1657
+ const kind = str(cs2.kind);
1658
+ const label = str(cs2.label);
1659
+ const placeholder = str(cs2.placeholder);
1660
+ const name = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || `field-${i}`;
1661
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
1597
1662
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1598
- "div",
1663
+ "label",
1599
1664
  {
1600
1665
  ...textAttrs(ctx, `${path}.c${i}.label`),
1601
- style: { ...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body), color: ctx.brand.palette.dark, marginBottom: 6 },
1602
- children: str(cs.label)
1666
+ style: {
1667
+ ...typeStyle(AI_TREE_TOKENS.type.bodyMBold, ctx.brand.fonts.body),
1668
+ color: ctx.brand.palette.dark
1669
+ },
1670
+ children: label
1603
1671
  }
1604
1672
  ),
1605
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1606
- "div",
1673
+ kind === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1674
+ "textarea",
1607
1675
  {
1608
- style: {
1609
- border: `1px solid ${ctx.brand.palette.accent}`,
1610
- borderRadius: AI_TREE_TOKENS.radiusButton,
1611
- height: cs.kind === "textarea" ? 96 : 42
1612
- }
1676
+ name,
1677
+ placeholder,
1678
+ style: { ...fieldStyle, height: 140, resize: "vertical" }
1679
+ }
1680
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1681
+ "input",
1682
+ {
1683
+ name,
1684
+ type: kind === "email" ? "email" : "text",
1685
+ placeholder,
1686
+ style: { ...fieldStyle, height: 48 }
1613
1687
  }
1614
1688
  )
1615
1689
  ] }, i);
1616
1690
  }
1617
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonEl, { slots: child.slots ?? {}, ctx, path: `${path}.c${i}` }) }, i);
1691
+ const cs = child.slots ?? {};
1692
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1693
+ "button",
1694
+ {
1695
+ type: "submit",
1696
+ style: {
1697
+ alignSelf: "flex-start",
1698
+ border: "none",
1699
+ cursor: "pointer",
1700
+ padding: `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
1701
+ borderRadius: AI_TREE_TOKENS.radiusButton,
1702
+ background: ctx.brand.palette.primary,
1703
+ color: AI_TREE_TOKENS.textPrimaryForeground,
1704
+ ...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
1705
+ },
1706
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
1707
+ },
1708
+ i
1709
+ );
1618
1710
  }) });
1711
+ }
1619
1712
  case "schedule-widget":
1620
1713
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1621
1714
  "div",
@@ -1877,6 +1970,31 @@ function syncReplacedOriginals(state) {
1877
1970
  }
1878
1971
  }
1879
1972
  }
1973
+ function orderByChain(sections) {
1974
+ const ids = new Set(sections.map((entry) => entry.id));
1975
+ const after = /* @__PURE__ */ new Map();
1976
+ const roots = [];
1977
+ for (const entry of sections) {
1978
+ const anchor = entry.replaces ?? entry.beforeSection ?? entry.afterSection ?? null;
1979
+ if (anchor && ids.has(anchor)) {
1980
+ const bucket = after.get(anchor);
1981
+ if (bucket) bucket.push(entry);
1982
+ else after.set(anchor, [entry]);
1983
+ } else {
1984
+ roots.push(entry);
1985
+ }
1986
+ }
1987
+ const out = [];
1988
+ const seen = /* @__PURE__ */ new Set();
1989
+ const visit = (entry) => {
1990
+ if (seen.has(entry.id)) return;
1991
+ seen.add(entry.id);
1992
+ out.push(entry);
1993
+ for (const child of after.get(entry.id) ?? []) visit(child);
1994
+ };
1995
+ for (const root of roots) visit(root);
1996
+ return out.length === sections.length ? out : sections;
1997
+ }
1880
1998
  function applyAiSectionsToDom(state, options) {
1881
1999
  if (typeof document === "undefined") return;
1882
2000
  const brandOverride = deriveBrandOverride();
@@ -1885,6 +2003,7 @@ function applyAiSectionsToDom(state, options) {
1885
2003
  const pagePath = window.location.pathname;
1886
2004
  const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
1887
2005
  const activeIds = new Set(pageSections.map((entry) => entry.id));
2006
+ const ordered = state.hideTemplate === true ? orderByChain(pageSections) : pageSections;
1888
2007
  for (const [id, section] of mounted) {
1889
2008
  if (!activeIds.has(id)) {
1890
2009
  section.root.unmount();
@@ -1892,7 +2011,7 @@ function applyAiSectionsToDom(state, options) {
1892
2011
  mounted.delete(id);
1893
2012
  }
1894
2013
  }
1895
- for (const entry of pageSections) {
2014
+ for (const entry of ordered) {
1896
2015
  const serialized = JSON.stringify(entry) + brandKey;
1897
2016
  const existing = mounted.get(entry.id);
1898
2017
  if (existing && existing.serialized === serialized && existing.container.isConnected) {
@@ -1929,7 +2048,7 @@ function applyAiSectionsToDom(state, options) {
1929
2048
  }
1930
2049
  if (state.hideTemplate === true) {
1931
2050
  let prev = null;
1932
- for (const entry of pageSections) {
2051
+ for (const entry of ordered) {
1933
2052
  const el = mounted.get(entry.id)?.container;
1934
2053
  if (!el) continue;
1935
2054
  if (prev && !(prev.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING)) {
@@ -20804,7 +20923,7 @@ function OhhwellsBridge() {
20804
20923
  postToParent2({
20805
20924
  type: "ow:ready",
20806
20925
  version: "1",
20807
- bridgeVersion: "0.1.70",
20926
+ bridgeVersion: "0.1.71",
20808
20927
  path: pathname,
20809
20928
  nodes: collectEditableNodes(editContentRef.current),
20810
20929
  sections