@ohhwells/bridge 0.1.96 → 0.1.97

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.js CHANGED
@@ -418,6 +418,10 @@ function styleSheetCss() {
418
418
  `[data-ohw-style-spacing="${spacing}"] { padding-top: ${px}px !important; padding-bottom: ${px}px !important; }`
419
419
  );
420
420
  }
421
+ rules.push(
422
+ `[data-ohw-style-corners="sharp"] :is(.card, [data-ohw-card], img, picture, figure, [data-ohw-editable="bg-image"]) { border-radius: 0 !important; }`,
423
+ `[data-ohw-style-corners="sharp"] :has(> img) { border-radius: 0 !important; }`
424
+ );
421
425
  for (const align of ["left", "center", "right"]) {
422
426
  rules.push(`[data-ohw-style-align="${align}"] { text-align: ${align} !important; }`);
423
427
  }
@@ -448,6 +452,7 @@ var SECTION_ATTRS = {
448
452
  headlineScale: "data-ohw-style-headline",
449
453
  imageAspect: "data-ohw-style-aspect",
450
454
  spacing: "data-ohw-style-spacing",
455
+ cornerStyle: "data-ohw-style-corners",
451
456
  align: "data-ohw-style-align"
452
457
  };
453
458
  var NODE_WROTE_ATTR = "data-ohw-style-node";
@@ -840,6 +845,284 @@ function initSectionInstancesFromContent(content, currentPath) {
840
845
  // src/ui/ai-tree/AiTreeRenderer.tsx
841
846
  import React from "react";
842
847
  import { ArrowLeft, ArrowRight, ChevronDown, icons as lucideIcons } from "lucide-react";
848
+
849
+ // src/lib/placeholder-imagery.ts
850
+ var U = (id) => `https://images.unsplash.com/photo-${id}?auto=format&fit=crop&w=1600&q=80`;
851
+ var GENERIC = [
852
+ U("1441986300917-64674bd600d8"),
853
+ U("1486406146926-c627a92ad1ab"),
854
+ U("1497032628192-86f99bcd76bc"),
855
+ U("1521737604893-d14cc237f11d"),
856
+ U("1522071820081-009f0129c71c"),
857
+ U("1519389950473-47ba0277781c"),
858
+ U("1460925895917-afdab827c52f"),
859
+ U("1504384308090-c894fdcc538d")
860
+ ];
861
+ var PEOPLE = [
862
+ U("1500648767791-00dcc994a43e"),
863
+ U("1494790108377-be9c29b29330"),
864
+ U("1507003211169-0a1dd7228f2d"),
865
+ U("1438761681033-6461ffad8d80"),
866
+ U("1544005313-94ddf0286df2"),
867
+ U("1472099645785-5658abf4ff4e"),
868
+ U("1519085360753-af0119f7cbe7"),
869
+ U("1534528741775-53994a69daeb")
870
+ ];
871
+ var THEMED = [
872
+ {
873
+ keywords: ["portrait", "headshot", "person", "people", "team", "staff", "avatar", "founder", "face"],
874
+ pool: PEOPLE
875
+ },
876
+ {
877
+ keywords: ["pet", "dog", "cat", "puppy", "kitten", "vet", "animal"],
878
+ pool: [
879
+ U("1548199973-03cce0bbc87b"),
880
+ U("1450778869180-41d0601e046e"),
881
+ U("1583511655857-d19b40a7a54e"),
882
+ U("1587300003388-59208cc962cb"),
883
+ U("1517849845537-4d257902454a"),
884
+ U("1601758228041-f3b2795255f1")
885
+ ]
886
+ },
887
+ {
888
+ keywords: [
889
+ "baker",
890
+ "bakery",
891
+ "cafe",
892
+ "coffee",
893
+ "latte",
894
+ "restaurant",
895
+ "pastr",
896
+ "bread",
897
+ "cake",
898
+ "cater",
899
+ "chef",
900
+ "kitchen",
901
+ "food",
902
+ "pizza",
903
+ "dessert",
904
+ "brunch",
905
+ "bistro",
906
+ "deli",
907
+ "dish",
908
+ "menu"
909
+ ],
910
+ pool: [
911
+ U("1509440159596-0249088772ff"),
912
+ U("1555507036-ab1f4038808a"),
913
+ U("1517433670267-08bbd4be890f"),
914
+ U("1486427944299-d1955d23e34d"),
915
+ U("1504754524776-8f4f37790ca0"),
916
+ U("1495474472287-4d71bcdd2085"),
917
+ U("1521017432531-fbd92d768814"),
918
+ U("1556909114-f6e7ad7d3136")
919
+ ]
920
+ },
921
+ {
922
+ keywords: [
923
+ "shop",
924
+ "store",
925
+ "boutique",
926
+ "retail",
927
+ "clothing",
928
+ "fashion",
929
+ "jewel",
930
+ "gift",
931
+ "florist",
932
+ "market",
933
+ "grocer",
934
+ "product",
935
+ "storefront"
936
+ ],
937
+ pool: [
938
+ U("1441984904996-e0b6ba687e04"),
939
+ U("1472851294608-062f824d29cc"),
940
+ U("1523381210434-271e8be1f52b"),
941
+ U("1534452203293-494d7ddbf7e0"),
942
+ U("1445205170230-053b83016050"),
943
+ U("1560243563-062bfc001d68")
944
+ ]
945
+ },
946
+ {
947
+ keywords: [
948
+ "yoga",
949
+ "pilates",
950
+ "fitness",
951
+ "gym",
952
+ "workout",
953
+ "trainer",
954
+ "wellness",
955
+ "meditat",
956
+ "massage",
957
+ "therap",
958
+ "physio",
959
+ "chiro",
960
+ "nutrition",
961
+ "spa",
962
+ "studio"
963
+ ],
964
+ pool: [
965
+ U("1544367567-0f2fcb009e0b"),
966
+ U("1506126613408-eca07ce68773"),
967
+ U("1545205597-3d9d02c29597"),
968
+ U("1552196563-55cd4e45efb3"),
969
+ U("1518611012118-696072aa579a"),
970
+ U("1571019613454-1cb2f99b2d8b"),
971
+ U("1540555700478-4be289fbecef"),
972
+ U("1519824145371-296894a0daa9")
973
+ ]
974
+ },
975
+ {
976
+ keywords: [
977
+ "salon",
978
+ "hairdress",
979
+ "haircut",
980
+ "barber",
981
+ "manicure",
982
+ "pedicure",
983
+ "nails",
984
+ "beauty",
985
+ "makeup",
986
+ "cosmetic",
987
+ "eyelash",
988
+ "eyebrow",
989
+ "skincare",
990
+ "esthetic",
991
+ "waxing",
992
+ "hair"
993
+ ],
994
+ pool: [
995
+ U("1560066984-138dadb4c035"),
996
+ U("1522337660859-02fbefca4702"),
997
+ U("1562322140-8baeececf3df"),
998
+ U("1521590832167-7bcbfaa6381f"),
999
+ U("1487412947147-5cebf100ffc2"),
1000
+ U("1526045478516-99145907023c")
1001
+ ]
1002
+ },
1003
+ {
1004
+ keywords: [
1005
+ "cleaning",
1006
+ "plumb",
1007
+ "electric",
1008
+ "landscap",
1009
+ "contractor",
1010
+ "handyman",
1011
+ "renov",
1012
+ "hvac",
1013
+ "roofing",
1014
+ "painting",
1015
+ "carpentry",
1016
+ "flooring",
1017
+ "movers",
1018
+ "construction",
1019
+ "tools"
1020
+ ],
1021
+ pool: [
1022
+ U("1581578731548-c64695cc6952"),
1023
+ U("1504307651254-35680f356dfd"),
1024
+ U("1581092160562-40aa08e78837"),
1025
+ U("1621905251189-08b45d6a269e"),
1026
+ U("1558618666-fcd25c85cd64"),
1027
+ U("1585128792020-803d29415281")
1028
+ ]
1029
+ },
1030
+ {
1031
+ keywords: [
1032
+ "legal",
1033
+ "attorney",
1034
+ "lawyer",
1035
+ "account",
1036
+ "bookkeep",
1037
+ "consult",
1038
+ "coaching",
1039
+ "financ",
1040
+ "insurance",
1041
+ "realtor",
1042
+ "estate",
1043
+ "marketing",
1044
+ "agency",
1045
+ "office",
1046
+ "business",
1047
+ "desk"
1048
+ ],
1049
+ pool: [
1050
+ U("1497366216548-37526070297c"),
1051
+ U("1497366811353-6870744d04b2"),
1052
+ U("1454165804606-c3d57bc86b40"),
1053
+ U("1521791136064-7986c2920216"),
1054
+ U("1556761175-b413da4baf72"),
1055
+ U("1542744173-8e7e53415bb0")
1056
+ ]
1057
+ },
1058
+ {
1059
+ keywords: [
1060
+ "wedding",
1061
+ "event",
1062
+ "party",
1063
+ "celebrat",
1064
+ "venue",
1065
+ "community",
1066
+ "nonprofit",
1067
+ "charity",
1068
+ "workshop",
1069
+ "photograph",
1070
+ "concert"
1071
+ ],
1072
+ pool: [
1073
+ U("1511578314322-379afb476865"),
1074
+ U("1501281668745-f7f57925c3b4"),
1075
+ U("1523580494863-6f3031224c94"),
1076
+ U("1540575467063-178a50c2df87"),
1077
+ U("1505236858219-8359eb29e329"),
1078
+ U("1528605248644-14dd04022da1")
1079
+ ]
1080
+ }
1081
+ ];
1082
+ function poolForSubject(subject) {
1083
+ for (const theme of THEMED) {
1084
+ if (theme.keywords.some((k) => subject.includes(k))) {
1085
+ return theme.pool;
1086
+ }
1087
+ }
1088
+ return GENERIC;
1089
+ }
1090
+ function mixedHash(text) {
1091
+ let hash = 2166136261;
1092
+ for (let i = 0; i < text.length; i++) {
1093
+ hash ^= text.charCodeAt(i);
1094
+ hash = Math.imul(hash, 16777619);
1095
+ }
1096
+ return hash >>> 16 & 65535;
1097
+ }
1098
+ function resolvePlaceholderRef(ref) {
1099
+ const match = /^placeholder:([a-z0-9-]+)$/.exec(ref);
1100
+ if (!match) return null;
1101
+ const subject = match[1];
1102
+ const pool = poolForSubject(subject.replace(/-\d+$/, ""));
1103
+ return pool[mixedHash(ref) % pool.length];
1104
+ }
1105
+ function collectPlaceholderRefs(tree) {
1106
+ const seen = /* @__PURE__ */ new Set();
1107
+ for (const match of JSON.stringify(tree ?? null).matchAll(/"(placeholder:[a-z0-9-]+)"/gu)) {
1108
+ seen.add(match[1]);
1109
+ }
1110
+ return [...seen];
1111
+ }
1112
+ function buildPlaceholderMap(tree) {
1113
+ const map = {};
1114
+ const cursor = /* @__PURE__ */ new Map();
1115
+ for (const ref of collectPlaceholderRefs(tree)) {
1116
+ const subject = ref.slice("placeholder:".length).replace(/-\d+$/, "");
1117
+ const pool = poolForSubject(subject);
1118
+ const start = cursor.get(pool) ?? mixedHash(ref) % pool.length;
1119
+ map[ref] = pool[start % pool.length];
1120
+ cursor.set(pool, start + 1);
1121
+ }
1122
+ return map;
1123
+ }
1124
+
1125
+ // src/ui/ai-tree/AiTreeRenderer.tsx
843
1126
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
844
1127
  function lucideByName(name) {
845
1128
  const pascal = name.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -853,6 +1136,7 @@ var typeStyle = (spec, font) => ({
853
1136
  fontWeight: spec.weight
854
1137
  });
855
1138
  var str = (value) => typeof value === "string" ? value : "";
1139
+ var cardRadius = (slots) => slots.cornerStyle === "sharp" ? 0 : AI_TREE_TOKENS.radiusCard;
856
1140
  var featureLines = (value) => value.split(/\n|<br\s*\/?>/i).map((line) => line.trim()).filter(Boolean);
857
1141
  var CHECK_MASK = `data:image/svg+xml,${encodeURIComponent(
858
1142
  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>'
@@ -1198,10 +1482,11 @@ function PricingCard({ node, ctx, path }) {
1198
1482
  return /* @__PURE__ */ jsxs(
1199
1483
  "div",
1200
1484
  {
1485
+ "data-ohw-card": "",
1201
1486
  style: {
1202
1487
  background: hasBg ? ctx.brand.palette.light : "transparent",
1203
1488
  border: `1px solid ${dark}`,
1204
- borderRadius: AI_TREE_TOKENS.radiusCard,
1489
+ borderRadius: cardRadius(slots),
1205
1490
  padding: AI_TREE_TOKENS.paddingBlock,
1206
1491
  display: "flex",
1207
1492
  flexDirection: "column",
@@ -1306,10 +1591,11 @@ function TestimonialCard({ node, ctx, path }) {
1306
1591
  return /* @__PURE__ */ jsx(
1307
1592
  "div",
1308
1593
  {
1594
+ "data-ohw-card": "",
1309
1595
  "data-ai-avatar-pos": avatarPos ?? void 0,
1310
1596
  style: {
1311
1597
  background: hasBg ? ctx.cardSurface : "transparent",
1312
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1598
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1313
1599
  overflow: "hidden",
1314
1600
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1315
1601
  minWidth: 0
@@ -1344,10 +1630,11 @@ function TeamCard({ node, ctx, path }) {
1344
1630
  return /* @__PURE__ */ jsxs(
1345
1631
  "div",
1346
1632
  {
1633
+ "data-ohw-card": "",
1347
1634
  "data-ai-avatar-pos": avatarPos ?? void 0,
1348
1635
  style: {
1349
1636
  background: hasBg ? ctx.cardSurface : "transparent",
1350
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1637
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1351
1638
  overflow: "hidden",
1352
1639
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1353
1640
  minWidth: 0,
@@ -1518,9 +1805,10 @@ function CardBlock({ node, ctx, path }) {
1518
1805
  return /* @__PURE__ */ jsxs(
1519
1806
  "div",
1520
1807
  {
1808
+ "data-ohw-card": "",
1521
1809
  style: {
1522
1810
  background: hasBg ? ctx.cardSurface : "transparent",
1523
- borderRadius: hasBg ? AI_TREE_TOKENS.radiusCard : 0,
1811
+ borderRadius: hasBg ? cardRadius(slots) : 0,
1524
1812
  overflow: "hidden",
1525
1813
  outline: slots.featured === true ? `2px solid ${ctx.brand.palette.primary}` : void 0,
1526
1814
  display: horizontal ? "flex" : "block",
@@ -1548,7 +1836,7 @@ function CardBlock({ node, ctx, path }) {
1548
1836
  ) : /* @__PURE__ */ jsx(
1549
1837
  "div",
1550
1838
  {
1551
- style: /^(lucide|simple):/.test(mediaRef) ? { padding: `${AI_TREE_TOKENS.paddingBlock}px ${AI_TREE_TOKENS.paddingBlock}px 0` } : hasBg ? void 0 : { borderRadius: AI_TREE_TOKENS.radiusCard, overflow: "hidden" },
1839
+ style: /^(lucide|simple):/.test(mediaRef) ? { padding: `${AI_TREE_TOKENS.paddingBlock}px ${AI_TREE_TOKENS.paddingBlock}px 0` } : hasBg ? void 0 : { borderRadius: cardRadius(slots), overflow: "hidden" },
1552
1840
  children: media
1553
1841
  }
1554
1842
  )),
@@ -1959,6 +2247,32 @@ function renderNode(node, ctx, path) {
1959
2247
  if (child) {
1960
2248
  return renderNode(child, ctx, `${path}.c0`);
1961
2249
  }
2250
+ if (str(slots.provider) === "map" && str(slots.query)) {
2251
+ const query = str(slots.query);
2252
+ const mapAttrs = ctx.keyFor ? {
2253
+ "data-ohw-key": ctx.keyFor(`${path}.query`),
2254
+ "data-ohw-editable": "map",
2255
+ "data-ohw-map-query": query
2256
+ } : {};
2257
+ return /* @__PURE__ */ jsx(
2258
+ "iframe",
2259
+ {
2260
+ ...mapAttrs,
2261
+ "data-ai-embed": "map",
2262
+ title: str(slots.title) || "Map",
2263
+ src: `https://www.google.com/maps?q=${encodeURIComponent(query)}&output=embed`,
2264
+ loading: "lazy",
2265
+ referrerPolicy: "no-referrer-when-downgrade",
2266
+ style: {
2267
+ width: "100%",
2268
+ minHeight: 320,
2269
+ border: 0,
2270
+ borderRadius: AI_TREE_TOKENS.radiusCard,
2271
+ display: "block"
2272
+ }
2273
+ }
2274
+ );
2275
+ }
1962
2276
  return /* @__PURE__ */ jsx(
1963
2277
  "div",
1964
2278
  {
@@ -2112,9 +2426,14 @@ function AiTreeRenderer({
2112
2426
  const resolvedBrand = brand ?? AI_DEFAULT_BRAND;
2113
2427
  const band = (tree.settings ?? {}).sectionBackground === "accent" ? accentBandContext(resolvedBrand) : null;
2114
2428
  const blockBrand = band?.brand ?? resolvedBrand;
2429
+ const placeholderMap = buildPlaceholderMap(tree);
2115
2430
  const ctx = {
2116
2431
  brand: blockBrand,
2117
- resolveMedia: resolveMedia ?? (() => null),
2432
+ // An owner/library ref resolves through the host resolver; a `placeholder:<subject>` ref the
2433
+ // host cannot resolve falls back to real stock photography (the per-section map first, then a
2434
+ // standalone resolve), so generated galleries, image rows, and overlay backgrounds arrive with
2435
+ // photos instead of grey boxes.
2436
+ resolveMedia: (ref) => resolveMedia?.(ref) ?? placeholderMap[ref] ?? resolvePlaceholderRef(ref),
2118
2437
  cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
2119
2438
  keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
2120
2439
  sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
@@ -14598,6 +14917,9 @@ function collectEditableNodes(extraContent, root = document) {
14598
14917
  if (el.dataset.ohwEditable === "link") {
14599
14918
  return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref3(el) };
14600
14919
  }
14920
+ if (el.dataset.ohwEditable === "map") {
14921
+ return { key: el.dataset.ohwKey ?? "", type: "map", text: el.dataset.ohwMapQuery ?? "" };
14922
+ }
14601
14923
  return {
14602
14924
  key: el.dataset.ohwKey ?? "",
14603
14925
  type: el.dataset.ohwEditable ?? "text",
@@ -15381,7 +15703,7 @@ var EDITOR_CHROME_SELECTOR = '[data-ohw-toolbar], [data-ohw-form-toolbar], [data
15381
15703
  function isOverEditorChrome(x, y) {
15382
15704
  return document.elementsFromPoint(x, y).some((el) => el instanceof HTMLElement && el.matches(EDITOR_CHROME_SELECTOR));
15383
15705
  }
15384
- var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="icon"]):not([data-ohw-editable="form"])';
15706
+ var NON_MEDIA_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="icon"]):not([data-ohw-editable="form"]):not([data-ohw-editable="map"])';
15385
15707
  function getVideoEl2(el) {
15386
15708
  return el instanceof HTMLVideoElement ? el : el.querySelector("video");
15387
15709
  }
@@ -15437,6 +15759,12 @@ function applyVideoSettingNode(key, val) {
15437
15759
  });
15438
15760
  return true;
15439
15761
  }
15762
+ function applyMapQuery(el, val) {
15763
+ if (!(el instanceof HTMLIFrameElement)) return;
15764
+ const nextSrc = `https://www.google.com/maps?q=${encodeURIComponent(val)}&output=embed`;
15765
+ if (el.src !== nextSrc) el.src = nextSrc;
15766
+ el.setAttribute("data-ohw-map-query", val);
15767
+ }
15440
15768
  function applyLinkByKey(key, val) {
15441
15769
  document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
15442
15770
  if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
@@ -18237,6 +18565,8 @@ function OhhwellsBridge() {
18237
18565
  }
18238
18566
  } else if (el.dataset.ohwEditable === "link") {
18239
18567
  applyLinkHref(el, val);
18568
+ } else if (el.dataset.ohwEditable === "map") {
18569
+ applyMapQuery(el, val);
18240
18570
  } else if (el.dataset.ohwEditable === "icon") {
18241
18571
  applyIconMarkup(el, val);
18242
18572
  } else if (el.dataset.ohwEditable === "form") {
@@ -18433,6 +18763,8 @@ function OhhwellsBridge() {
18433
18763
  if (video && video.src !== val) applyVideoSrc(video, val);
18434
18764
  } else if (el.dataset.ohwEditable === "link") {
18435
18765
  applyLinkHref(el, val);
18766
+ } else if (el.dataset.ohwEditable === "map") {
18767
+ applyMapQuery(el, val);
18436
18768
  } else if (el.dataset.ohwEditable === "form") {
18437
18769
  } else if (isIconMarkupValue(val)) {
18438
18770
  } else if (el.innerHTML !== val) {
@@ -20406,6 +20738,8 @@ function OhhwellsBridge() {
20406
20738
  if (video && video.src !== val) applyVideoSrc(video, val);
20407
20739
  } else if (el.dataset.ohwEditable === "link") {
20408
20740
  applyLinkHref(el, val);
20741
+ } else if (el.dataset.ohwEditable === "map") {
20742
+ applyMapQuery(el, val);
20409
20743
  } else if (el.dataset.ohwEditable === "icon") {
20410
20744
  applyIconMarkup(el, val);
20411
20745
  } else if (isIconMarkupValue(val)) {
@@ -21642,7 +21976,7 @@ function OhhwellsBridge() {
21642
21976
  postToParent2({
21643
21977
  type: "ow:ready",
21644
21978
  version: "1",
21645
- bridgeVersion: "0.1.95",
21979
+ bridgeVersion: "0.1.96",
21646
21980
  path: pathname,
21647
21981
  nodes: collectEditableNodes(editContentRef.current),
21648
21982
  sections