@ohhwells/bridge 0.1.141 → 0.1.143

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
@@ -1350,15 +1350,25 @@ function readAssignments(content) {
1350
1350
  function cssLength(style, prop) {
1351
1351
  return style.getPropertyValue(prop) || "0px";
1352
1352
  }
1353
+ function withTransitionsSettled(el, read) {
1354
+ const prior = el.style.getPropertyValue("transition");
1355
+ const priority = el.style.getPropertyPriority("transition");
1356
+ el.style.setProperty("transition", "none", "important");
1357
+ try {
1358
+ return read(getComputedStyle(el));
1359
+ } finally {
1360
+ if (prior) el.style.setProperty("transition", prior, priority);
1361
+ else el.style.removeProperty("transition");
1362
+ }
1363
+ }
1353
1364
  function readVariantSample(el) {
1354
- const style = getComputedStyle(el);
1355
- return {
1365
+ return withTransitionsSettled(el, (style) => ({
1356
1366
  background: style.backgroundColor,
1357
1367
  color: style.color,
1358
1368
  borderColor: style.borderTopColor,
1359
1369
  borderWidth: style.borderTopWidth,
1360
1370
  className: el.className || ""
1361
- };
1371
+ }));
1362
1372
  }
1363
1373
  function readSizeSample(el) {
1364
1374
  const style = getComputedStyle(el);
@@ -5023,10 +5033,9 @@ function getLinkHref3(el) {
5023
5033
  }
5024
5034
  function collectEditableNodes(extraContent, root = document) {
5025
5035
  const isScoped = root !== document;
5026
- const editableEls = Array.from(
5027
- root.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="form"])')
5028
- );
5029
- if (isScoped && root instanceof HTMLElement && root.matches('[data-ohw-editable]:not([data-ohw-editable="form"])')) {
5036
+ const EDITABLE_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="form"]):not([data-ai-icon])';
5037
+ const editableEls = Array.from(root.querySelectorAll(EDITABLE_SELECTOR));
5038
+ if (isScoped && root instanceof HTMLElement && root.matches(EDITABLE_SELECTOR)) {
5030
5039
  editableEls.unshift(root);
5031
5040
  }
5032
5041
  const nodes = editableEls.map((el) => {
@@ -6293,6 +6302,281 @@ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE
6293
6302
  return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
6294
6303
  }
6295
6304
 
6305
+ // src/lib/ai-pricing-tree.ts
6306
+ var PRICING_MIN_CARDS = 1;
6307
+ var PRICING_MAX_CARDS = 4;
6308
+ function isPricingCard(node) {
6309
+ return typeof node === "object" && node !== null && node.type === "card" && node.slots?.preset === "pricing";
6310
+ }
6311
+ function isPricingCollection(node) {
6312
+ return node.type === "collection" && Array.isArray(node.children) && node.children.length > 0 && isPricingCard(node.children[0]);
6313
+ }
6314
+ function findCollection(node) {
6315
+ if (isPricingCollection(node)) return node;
6316
+ for (const child of node.children ?? []) {
6317
+ const found = findCollection(child);
6318
+ if (found) return found;
6319
+ }
6320
+ return null;
6321
+ }
6322
+ function findPricingCollection(tree) {
6323
+ for (const row of tree.rows ?? []) {
6324
+ for (const block of row.blocks ?? []) {
6325
+ const found = findCollection(block);
6326
+ if (found) return found;
6327
+ }
6328
+ }
6329
+ return null;
6330
+ }
6331
+ function pricingCardCount(tree) {
6332
+ return findPricingCollection(tree)?.children?.length ?? 0;
6333
+ }
6334
+ function withPricingCollection(tree, mutate) {
6335
+ const next = JSON.parse(JSON.stringify(tree));
6336
+ const collection = findPricingCollection(next);
6337
+ if (!collection || !collection.children) return null;
6338
+ for (const card of collection.children) {
6339
+ if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
6340
+ }
6341
+ return mutate(collection) ? next : null;
6342
+ }
6343
+ function upgradeLegacyPricingSlots(card) {
6344
+ const slots = card.slots = card.slots ?? {};
6345
+ if (typeof slots.features === "string") {
6346
+ const rows = pricingFeatureList(card);
6347
+ if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
6348
+ }
6349
+ if (slots.featured === true) slots.featured = { enabled: true };
6350
+ else if (slots.featured === false) delete slots.featured;
6351
+ }
6352
+ function addPricingTier(tree) {
6353
+ return withPricingCollection(tree, (collection) => {
6354
+ const children = collection.children;
6355
+ if (children.length >= PRICING_MAX_CARDS) return false;
6356
+ const clone = JSON.parse(JSON.stringify(children[children.length - 1]));
6357
+ clone.slots = clone.slots ?? {};
6358
+ delete clone.slots.featured;
6359
+ clone.slots.heading = "New tier";
6360
+ children.push(clone);
6361
+ collection.slots = { ...collection.slots, itemsPerRow: children.length };
6362
+ return true;
6363
+ });
6364
+ }
6365
+ function removePricingTier(tree, index) {
6366
+ return withPricingCollection(tree, (collection) => {
6367
+ const children = collection.children;
6368
+ if (children.length <= PRICING_MIN_CARDS) return false;
6369
+ if (index < 0 || index >= children.length) return false;
6370
+ children.splice(index, 1);
6371
+ collection.slots = { ...collection.slots, itemsPerRow: children.length };
6372
+ return true;
6373
+ });
6374
+ }
6375
+ var PRICING_CARD_STYLES = [
6376
+ { value: "outline", label: "Outline", treatment: null },
6377
+ { value: "lift", label: "Lift", treatment: "lifted" },
6378
+ { value: "invert", label: "Invert", treatment: "inverted" },
6379
+ { value: "header", label: "Header", treatment: "accent-header" }
6380
+ ];
6381
+ var DEFAULT_FEATURED_BADGE = "Most popular";
6382
+ function featuredOf(target) {
6383
+ const raw = target?.slots?.featured;
6384
+ if (raw === true) return { enabled: true, treatment: "outlined" };
6385
+ return typeof raw === "object" && raw !== null ? raw : {};
6386
+ }
6387
+ function badgeOf(featured) {
6388
+ return typeof featured.badge === "string" ? featured.badge.trim() : "";
6389
+ }
6390
+ function pricingCardStyle(tree, index) {
6391
+ const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
6392
+ if (featured.enabled !== true) return "outline";
6393
+ return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
6394
+ }
6395
+ function pricingCardFeaturedTag(tree, index) {
6396
+ const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
6397
+ return featured.enabled === true && badgeOf(featured).length > 0;
6398
+ }
6399
+ function composeFeatured(style, tagOn, badgeText) {
6400
+ const treatment = PRICING_CARD_STYLES.find((s) => s.value === style)?.treatment ?? null;
6401
+ if (tagOn) return { enabled: true, badge: badgeText || DEFAULT_FEATURED_BADGE, treatment: treatment ?? "outlined" };
6402
+ if (treatment) return { enabled: true, treatment };
6403
+ return badgeText.length > 0 ? { enabled: false, badge: badgeText } : { enabled: false };
6404
+ }
6405
+ function withPricingCard(tree, index, mutate) {
6406
+ return withPricingCollection(tree, (collection) => {
6407
+ const children = collection.children;
6408
+ if (index < 0 || index >= children.length) return false;
6409
+ const target = children[index];
6410
+ target.slots = target.slots ?? {};
6411
+ mutate(target);
6412
+ return true;
6413
+ });
6414
+ }
6415
+ function setPricingCardStyle(tree, index, style) {
6416
+ if (!PRICING_CARD_STYLES.some((s) => s.value === style)) return null;
6417
+ return withPricingCard(tree, index, (target) => {
6418
+ const prev = featuredOf(target);
6419
+ const tagOn = prev.enabled === true && badgeOf(prev).length > 0;
6420
+ target.slots.featured = composeFeatured(style, tagOn, badgeOf(prev));
6421
+ });
6422
+ }
6423
+ function setPricingCardFeaturedTag(tree, index, on) {
6424
+ return withPricingCollection(tree, (collection) => {
6425
+ const children = collection.children;
6426
+ if (index < 0 || index >= children.length) return false;
6427
+ if (on) {
6428
+ children.forEach((card, i) => {
6429
+ if (i === index) return;
6430
+ const prev2 = featuredOf(card);
6431
+ if (prev2.enabled !== true || badgeOf(prev2).length === 0) return;
6432
+ card.slots = card.slots ?? {};
6433
+ card.slots.featured = composeFeatured(pricingCardStyleOf(prev2), false, badgeOf(prev2));
6434
+ });
6435
+ }
6436
+ const target = children[index];
6437
+ target.slots = target.slots ?? {};
6438
+ const prev = featuredOf(target);
6439
+ target.slots.featured = composeFeatured(pricingCardStyleOf(prev), on, badgeOf(prev));
6440
+ return true;
6441
+ });
6442
+ }
6443
+ function pricingCardStyleOf(featured) {
6444
+ if (featured.enabled !== true) return "outline";
6445
+ return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
6446
+ }
6447
+ function addPricingCardButton(tree, index, label) {
6448
+ return withPricingCollection(tree, (collection) => {
6449
+ const children = collection.children;
6450
+ if (index < 0 || index >= children.length) return false;
6451
+ const target = children[index];
6452
+ const buttons = (target.children ?? []).filter((c) => c.type === "button");
6453
+ if (buttons.length >= 2) return false;
6454
+ const hasPrimary = buttons.some((b) => b.slots?.variant === "primary");
6455
+ target.children = [
6456
+ ...target.children ?? [],
6457
+ { type: "button", span: 12, slots: { label, href: "#", variant: hasPrimary ? "secondary" : "primary" } }
6458
+ ];
6459
+ return true;
6460
+ });
6461
+ }
6462
+ function removePricingCardButton(tree, index) {
6463
+ return withPricingCollection(tree, (collection) => {
6464
+ const children = collection.children;
6465
+ if (index < 0 || index >= children.length) return false;
6466
+ const target = children[index];
6467
+ const buttons = (target.children ?? []).filter((c) => c.type === "button");
6468
+ if (buttons.length === 0) return false;
6469
+ const lastButton = buttons[buttons.length - 1];
6470
+ target.children = (target.children ?? []).filter((c) => c !== lastButton);
6471
+ return true;
6472
+ });
6473
+ }
6474
+ var PRICING_FEATURES_MIN = 1;
6475
+ var PRICING_FEATURES_MAX = 10;
6476
+ var PRICING_FEATURE_ICONS = [
6477
+ "check",
6478
+ "check-circle",
6479
+ "x",
6480
+ "star",
6481
+ "zap",
6482
+ "shield",
6483
+ "clock",
6484
+ "users",
6485
+ "infinity"
6486
+ ];
6487
+ function pricingFeatureList(target) {
6488
+ const raw = target.slots?.features;
6489
+ if (Array.isArray(raw)) {
6490
+ return raw.filter((item) => !!item && typeof item === "object" && typeof item.text === "string").map((item) => ({
6491
+ text: item.text,
6492
+ ...typeof item.icon === "string" ? { icon: item.icon } : {},
6493
+ ...typeof item.iconColor === "string" ? { iconColor: item.iconColor } : {}
6494
+ }));
6495
+ }
6496
+ if (typeof raw === "string") {
6497
+ return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
6498
+ }
6499
+ return [];
6500
+ }
6501
+ function pricingCardFeatureCount(tree, index) {
6502
+ const target = findPricingCollection(tree)?.children?.[index];
6503
+ return target ? pricingFeatureList(target).length : 0;
6504
+ }
6505
+ function addPricingCardFeature(tree, index, text) {
6506
+ return withPricingCollection(tree, (collection) => {
6507
+ const children = collection.children;
6508
+ if (index < 0 || index >= children.length) return false;
6509
+ const target = children[index];
6510
+ const features = pricingFeatureList(target);
6511
+ if (features.length >= PRICING_FEATURES_MAX) return false;
6512
+ target.slots = target.slots ?? {};
6513
+ target.slots.features = [...features, { text }];
6514
+ return true;
6515
+ });
6516
+ }
6517
+ function setPricingCardFeatureIcon(tree, index, featureIndex, icon) {
6518
+ if (!PRICING_FEATURE_ICONS.includes(icon)) return null;
6519
+ return withPricingCollection(tree, (collection) => {
6520
+ const children = collection.children;
6521
+ if (index < 0 || index >= children.length) return false;
6522
+ const target = children[index];
6523
+ const features = pricingFeatureList(target);
6524
+ if (featureIndex < 0 || featureIndex >= features.length) return false;
6525
+ target.slots = target.slots ?? {};
6526
+ target.slots.features = features.map((row, i) => i === featureIndex ? { ...row, icon } : row);
6527
+ return true;
6528
+ });
6529
+ }
6530
+ function removePricingCardFeature(tree, index) {
6531
+ return withPricingCollection(tree, (collection) => {
6532
+ const children = collection.children;
6533
+ if (index < 0 || index >= children.length) return false;
6534
+ const target = children[index];
6535
+ const features = pricingFeatureList(target);
6536
+ if (features.length <= PRICING_FEATURES_MIN) return false;
6537
+ target.slots = target.slots ?? {};
6538
+ target.slots.features = features.slice(0, -1);
6539
+ return true;
6540
+ });
6541
+ }
6542
+
6543
+ // src/lib/ai-icon-edit.ts
6544
+ var FEATURE_ICON_PATH = /\.c(\d+)\.features\.(\d+)\.icon$/;
6545
+ var NODE_SEGMENT = /^[rbc]\d+$/;
6546
+ function nodeAtPath(tree, segments) {
6547
+ if (segments.length < 2 || segments.some((s) => !NODE_SEGMENT.test(s))) return null;
6548
+ if (!segments[0].startsWith("r") || !segments[1].startsWith("b")) return null;
6549
+ let node = tree.rows?.[Number(segments[0].slice(1))]?.blocks?.[Number(segments[1].slice(1))];
6550
+ for (const segment of segments.slice(2)) {
6551
+ if (!segment.startsWith("c")) return null;
6552
+ node = node?.children?.[Number(segment.slice(1))];
6553
+ }
6554
+ return node ?? null;
6555
+ }
6556
+ function applyIconNameToTree(tree, path, icon) {
6557
+ const feature = path.match(FEATURE_ICON_PATH);
6558
+ if (feature) {
6559
+ return setPricingCardFeatureIcon(tree, Number(feature[1]), Number(feature[2]), icon);
6560
+ }
6561
+ return rewriteIconSlot(tree, path, `lucide:${icon}`);
6562
+ }
6563
+ function applyIconUploadToTree(tree, path, mediaRef) {
6564
+ if (FEATURE_ICON_PATH.test(path)) return null;
6565
+ return rewriteIconSlot(tree, path, `icon:${mediaRef}`);
6566
+ }
6567
+ function rewriteIconSlot(tree, path, ref) {
6568
+ const segments = path.split(".");
6569
+ const slot = segments.pop();
6570
+ if (!slot || NODE_SEGMENT.test(slot)) return null;
6571
+ const next = JSON.parse(JSON.stringify(tree));
6572
+ const node = nodeAtPath(next, segments);
6573
+ if (!node) return null;
6574
+ const current = node.slots?.[slot];
6575
+ if (typeof current !== "string" || !/^(lucide|simple|icon):/.test(current)) return null;
6576
+ node.slots = { ...node.slots, [slot]: ref };
6577
+ return next;
6578
+ }
6579
+
6296
6580
  // src/lib/ai-footer-tree.ts
6297
6581
  var FOOTER_COLUMN_SPANS = [12, 6, 4, 3];
6298
6582
  var FOOTER_COLUMN_BLOCKS = [
@@ -7649,7 +7933,7 @@ function MediaBox({
7649
7933
  onError
7650
7934
  }) {
7651
7935
  const url = refValue ? ctx.resolveMedia(refValue) : null;
7652
- const isIcon = /^(lucide|simple):/.test(refValue);
7936
+ const isIcon = /^(lucide|simple|icon):/.test(refValue);
7653
7937
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
7654
7938
  const editAttrs = ctx.keyFor && editPath && !isIcon ? {
7655
7939
  "data-ohw-key": ctx.keyFor(editPath),
@@ -7657,10 +7941,13 @@ function MediaBox({
7657
7941
  } : {};
7658
7942
  if (isIcon) {
7659
7943
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
7944
+ const uploadedIconUrl = refValue.startsWith("icon:") ? ctx.resolveMedia(refValue.slice(5)) : null;
7945
+ const iconEditAttrs = ctx.keyFor && editPath && genericImageEdit ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "icon" } : {};
7660
7946
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
7661
7947
  "span",
7662
7948
  {
7663
7949
  "data-ai-icon": refValue,
7950
+ ...iconEditAttrs,
7664
7951
  style: {
7665
7952
  display: "inline-flex",
7666
7953
  width: 48,
@@ -7669,7 +7956,7 @@ function MediaBox({
7669
7956
  justifyContent: "center",
7670
7957
  color: ctx.brand.palette.dark
7671
7958
  },
7672
- children: Icon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 48, color: ctx.brand.palette.dark, strokeWidth: 1.5 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 24 }, children: "\u25C6" })
7959
+ children: uploadedIconUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: uploadedIconUrl, alt: "", style: { width: 48, height: 48, objectFit: "contain" } }) : Icon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 48, color: ctx.brand.palette.dark, strokeWidth: 1.5 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 24 }, children: "\u25C6" })
7673
7960
  }
7674
7961
  );
7675
7962
  }
@@ -8157,8 +8444,20 @@ function PricingCard({ node, ctx, path }) {
8157
8444
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-ai-features": "", style: { display: "flex", flexDirection: "column", gap: AI_TREE_TOKENS.spacing4, padding: `${AI_TREE_TOKENS.spacing2}px 0` }, children: features.map((item, i) => {
8158
8445
  const Icon = lucideByName(item.icon || "check");
8159
8446
  const iconColor = pricingIconColor(item.iconColor, ctx.brand, fg);
8447
+ const iconAttrs = ctx.keyFor && !item.html ? {
8448
+ "data-ohw-key": ctx.keyFor(`${path}.features.${i}.icon`),
8449
+ "data-ohw-editable": "icon"
8450
+ } : {};
8160
8451
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", gap: AI_TREE_TOKENS.spacing3 }, children: [
8161
- Icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 20, color: iconColor }),
8452
+ Icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
8453
+ "span",
8454
+ {
8455
+ "data-ai-icon": `lucide:${item.icon || "check"}`,
8456
+ ...iconAttrs,
8457
+ style: { display: "inline-flex", flexShrink: 0 },
8458
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 20, color: iconColor })
8459
+ }
8460
+ ),
8162
8461
  item.html ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
8163
8462
  "span",
8164
8463
  {
@@ -10281,38 +10580,39 @@ function deriveTemplateButtonStyle() {
10281
10580
  el.querySelectorAll("[data-ohw-editable]").length <= 1
10282
10581
  );
10283
10582
  const isFilled = (el) => {
10284
- const bg = getComputedStyle(el).backgroundColor;
10583
+ const bg = withTransitionsSettled(el, (s) => s.backgroundColor);
10285
10584
  if (!bg || bg === "transparent") return false;
10286
10585
  const alpha = bg.match(/rgba?\([^)]*,\s*([\d.]+)\)$/);
10287
10586
  return !alpha || parseFloat(alpha[1]) > 0;
10288
10587
  };
10289
10588
  const btn = candidates.find(isFilled) ?? candidates[0];
10290
10589
  if (!btn) return null;
10291
- const cs = getComputedStyle(btn);
10292
- const filled = isFilled(btn);
10293
- const corners = [
10294
- cs.borderTopLeftRadius,
10295
- cs.borderTopRightRadius,
10296
- cs.borderBottomRightRadius,
10297
- cs.borderBottomLeftRadius
10298
- ].map((v) => v || "0px");
10299
- const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
10300
- const px2 = (v) => parseFloat(v) || 0;
10301
- const lineHeight = px2(cs.lineHeight) || px2(cs.fontSize) * 1.2;
10302
- const contentH = btn.getBoundingClientRect().height - px2(cs.borderTopWidth) - px2(cs.borderBottomWidth);
10303
- const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
10304
- const padY = Math.max(px2(cs.paddingTop), px2(cs.paddingBottom), impliedY);
10305
- const padX = Math.max(px2(cs.paddingLeft), px2(cs.paddingRight));
10306
- return {
10307
- radius: radius || "10px",
10308
- padding: `${padY}px ${padX}px`,
10309
- ...filled ? { background: cs.backgroundColor, color: cs.color } : {},
10310
- fontFamily: cs.fontFamily || "",
10311
- fontSize: cs.fontSize || "",
10312
- fontWeight: cs.fontWeight || "",
10313
- letterSpacing: cs.letterSpacing || "",
10314
- textTransform: cs.textTransform || ""
10315
- };
10590
+ return withTransitionsSettled(btn, (cs) => {
10591
+ const filled = isFilled(btn);
10592
+ const corners = [
10593
+ cs.borderTopLeftRadius,
10594
+ cs.borderTopRightRadius,
10595
+ cs.borderBottomRightRadius,
10596
+ cs.borderBottomLeftRadius
10597
+ ].map((v) => v || "0px");
10598
+ const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
10599
+ const px2 = (v) => parseFloat(v) || 0;
10600
+ const lineHeight = px2(cs.lineHeight) || px2(cs.fontSize) * 1.2;
10601
+ const contentH = btn.getBoundingClientRect().height - px2(cs.borderTopWidth) - px2(cs.borderBottomWidth);
10602
+ const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
10603
+ const padY = Math.max(px2(cs.paddingTop), px2(cs.paddingBottom), impliedY);
10604
+ const padX = Math.max(px2(cs.paddingLeft), px2(cs.paddingRight));
10605
+ return {
10606
+ radius: radius || "10px",
10607
+ padding: `${padY}px ${padX}px`,
10608
+ ...filled ? { background: cs.backgroundColor, color: cs.color } : {},
10609
+ fontFamily: cs.fontFamily || "",
10610
+ fontSize: cs.fontSize || "",
10611
+ fontWeight: cs.fontWeight || "",
10612
+ letterSpacing: cs.letterSpacing || "",
10613
+ textTransform: cs.textTransform || ""
10614
+ };
10615
+ });
10316
10616
  }
10317
10617
  var mounted = /* @__PURE__ */ new Map();
10318
10618
  function findTemplateSection(id) {
@@ -18400,222 +18700,6 @@ function AiMediaStripOverlay({
18400
18700
  // src/ui/ai-section/AiPricingCardOverlay.tsx
18401
18701
  var import_react11 = require("react");
18402
18702
  var import_lucide_react9 = require("lucide-react");
18403
-
18404
- // src/lib/ai-pricing-tree.ts
18405
- var PRICING_MIN_CARDS = 1;
18406
- var PRICING_MAX_CARDS = 4;
18407
- function isPricingCard(node) {
18408
- return typeof node === "object" && node !== null && node.type === "card" && node.slots?.preset === "pricing";
18409
- }
18410
- function isPricingCollection(node) {
18411
- return node.type === "collection" && Array.isArray(node.children) && node.children.length > 0 && isPricingCard(node.children[0]);
18412
- }
18413
- function findCollection(node) {
18414
- if (isPricingCollection(node)) return node;
18415
- for (const child of node.children ?? []) {
18416
- const found = findCollection(child);
18417
- if (found) return found;
18418
- }
18419
- return null;
18420
- }
18421
- function findPricingCollection(tree) {
18422
- for (const row of tree.rows ?? []) {
18423
- for (const block of row.blocks ?? []) {
18424
- const found = findCollection(block);
18425
- if (found) return found;
18426
- }
18427
- }
18428
- return null;
18429
- }
18430
- function pricingCardCount(tree) {
18431
- return findPricingCollection(tree)?.children?.length ?? 0;
18432
- }
18433
- function withPricingCollection(tree, mutate) {
18434
- const next = JSON.parse(JSON.stringify(tree));
18435
- const collection = findPricingCollection(next);
18436
- if (!collection || !collection.children) return null;
18437
- for (const card of collection.children) {
18438
- if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
18439
- }
18440
- return mutate(collection) ? next : null;
18441
- }
18442
- function upgradeLegacyPricingSlots(card) {
18443
- const slots = card.slots = card.slots ?? {};
18444
- if (typeof slots.features === "string") {
18445
- const rows = pricingFeatureList(card);
18446
- if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
18447
- }
18448
- if (slots.featured === true) slots.featured = { enabled: true };
18449
- else if (slots.featured === false) delete slots.featured;
18450
- }
18451
- function addPricingTier(tree) {
18452
- return withPricingCollection(tree, (collection) => {
18453
- const children = collection.children;
18454
- if (children.length >= PRICING_MAX_CARDS) return false;
18455
- const clone = JSON.parse(JSON.stringify(children[children.length - 1]));
18456
- clone.slots = clone.slots ?? {};
18457
- delete clone.slots.featured;
18458
- clone.slots.heading = "New tier";
18459
- children.push(clone);
18460
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18461
- return true;
18462
- });
18463
- }
18464
- function removePricingTier(tree, index) {
18465
- return withPricingCollection(tree, (collection) => {
18466
- const children = collection.children;
18467
- if (children.length <= PRICING_MIN_CARDS) return false;
18468
- if (index < 0 || index >= children.length) return false;
18469
- children.splice(index, 1);
18470
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18471
- return true;
18472
- });
18473
- }
18474
- var PRICING_CARD_STYLES = [
18475
- { value: "outline", label: "Outline", treatment: null },
18476
- { value: "lift", label: "Lift", treatment: "lifted" },
18477
- { value: "invert", label: "Invert", treatment: "inverted" },
18478
- { value: "header", label: "Header", treatment: "accent-header" }
18479
- ];
18480
- var DEFAULT_FEATURED_BADGE = "Most popular";
18481
- function featuredOf(target) {
18482
- const raw = target?.slots?.featured;
18483
- if (raw === true) return { enabled: true, treatment: "outlined" };
18484
- return typeof raw === "object" && raw !== null ? raw : {};
18485
- }
18486
- function badgeOf(featured) {
18487
- return typeof featured.badge === "string" ? featured.badge.trim() : "";
18488
- }
18489
- function pricingCardStyle(tree, index) {
18490
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18491
- if (featured.enabled !== true) return "outline";
18492
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18493
- }
18494
- function pricingCardFeaturedTag(tree, index) {
18495
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18496
- return featured.enabled === true && badgeOf(featured).length > 0;
18497
- }
18498
- function composeFeatured(style, tagOn, badgeText) {
18499
- const treatment = PRICING_CARD_STYLES.find((s) => s.value === style)?.treatment ?? null;
18500
- if (tagOn) return { enabled: true, badge: badgeText || DEFAULT_FEATURED_BADGE, treatment: treatment ?? "outlined" };
18501
- if (treatment) return { enabled: true, treatment };
18502
- return badgeText.length > 0 ? { enabled: false, badge: badgeText } : { enabled: false };
18503
- }
18504
- function withPricingCard(tree, index, mutate) {
18505
- return withPricingCollection(tree, (collection) => {
18506
- const children = collection.children;
18507
- if (index < 0 || index >= children.length) return false;
18508
- const target = children[index];
18509
- target.slots = target.slots ?? {};
18510
- mutate(target);
18511
- return true;
18512
- });
18513
- }
18514
- function setPricingCardStyle(tree, index, style) {
18515
- if (!PRICING_CARD_STYLES.some((s) => s.value === style)) return null;
18516
- return withPricingCard(tree, index, (target) => {
18517
- const prev = featuredOf(target);
18518
- const tagOn = prev.enabled === true && badgeOf(prev).length > 0;
18519
- target.slots.featured = composeFeatured(style, tagOn, badgeOf(prev));
18520
- });
18521
- }
18522
- function setPricingCardFeaturedTag(tree, index, on) {
18523
- return withPricingCollection(tree, (collection) => {
18524
- const children = collection.children;
18525
- if (index < 0 || index >= children.length) return false;
18526
- if (on) {
18527
- children.forEach((card, i) => {
18528
- if (i === index) return;
18529
- const prev2 = featuredOf(card);
18530
- if (prev2.enabled !== true || badgeOf(prev2).length === 0) return;
18531
- card.slots = card.slots ?? {};
18532
- card.slots.featured = composeFeatured(pricingCardStyleOf(prev2), false, badgeOf(prev2));
18533
- });
18534
- }
18535
- const target = children[index];
18536
- target.slots = target.slots ?? {};
18537
- const prev = featuredOf(target);
18538
- target.slots.featured = composeFeatured(pricingCardStyleOf(prev), on, badgeOf(prev));
18539
- return true;
18540
- });
18541
- }
18542
- function pricingCardStyleOf(featured) {
18543
- if (featured.enabled !== true) return "outline";
18544
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18545
- }
18546
- function addPricingCardButton(tree, index, label) {
18547
- return withPricingCollection(tree, (collection) => {
18548
- const children = collection.children;
18549
- if (index < 0 || index >= children.length) return false;
18550
- const target = children[index];
18551
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18552
- if (buttons.length >= 2) return false;
18553
- const hasPrimary = buttons.some((b) => b.slots?.variant === "primary");
18554
- target.children = [
18555
- ...target.children ?? [],
18556
- { type: "button", span: 12, slots: { label, href: "#", variant: hasPrimary ? "secondary" : "primary" } }
18557
- ];
18558
- return true;
18559
- });
18560
- }
18561
- function removePricingCardButton(tree, index) {
18562
- return withPricingCollection(tree, (collection) => {
18563
- const children = collection.children;
18564
- if (index < 0 || index >= children.length) return false;
18565
- const target = children[index];
18566
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18567
- if (buttons.length === 0) return false;
18568
- const lastButton = buttons[buttons.length - 1];
18569
- target.children = (target.children ?? []).filter((c) => c !== lastButton);
18570
- return true;
18571
- });
18572
- }
18573
- var PRICING_FEATURES_MIN = 1;
18574
- var PRICING_FEATURES_MAX = 10;
18575
- function pricingFeatureList(target) {
18576
- const raw = target.slots?.features;
18577
- if (Array.isArray(raw)) {
18578
- return raw.filter((item) => !!item && typeof item === "object" && typeof item.text === "string").map((item) => ({
18579
- text: item.text,
18580
- ...typeof item.icon === "string" ? { icon: item.icon } : {},
18581
- ...typeof item.iconColor === "string" ? { iconColor: item.iconColor } : {}
18582
- }));
18583
- }
18584
- if (typeof raw === "string") {
18585
- return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
18586
- }
18587
- return [];
18588
- }
18589
- function pricingCardFeatureCount(tree, index) {
18590
- const target = findPricingCollection(tree)?.children?.[index];
18591
- return target ? pricingFeatureList(target).length : 0;
18592
- }
18593
- function addPricingCardFeature(tree, index, text) {
18594
- return withPricingCollection(tree, (collection) => {
18595
- const children = collection.children;
18596
- if (index < 0 || index >= children.length) return false;
18597
- const target = children[index];
18598
- const features = pricingFeatureList(target);
18599
- if (features.length >= PRICING_FEATURES_MAX) return false;
18600
- target.slots = target.slots ?? {};
18601
- target.slots.features = [...features, { text }];
18602
- return true;
18603
- });
18604
- }
18605
- function removePricingCardFeature(tree, index) {
18606
- return withPricingCollection(tree, (collection) => {
18607
- const children = collection.children;
18608
- if (index < 0 || index >= children.length) return false;
18609
- const target = children[index];
18610
- const features = pricingFeatureList(target);
18611
- if (features.length <= PRICING_FEATURES_MIN) return false;
18612
- target.slots = target.slots ?? {};
18613
- target.slots.features = features.slice(0, -1);
18614
- return true;
18615
- });
18616
- }
18617
-
18618
- // src/ui/ai-section/AiPricingCardOverlay.tsx
18619
18703
  var import_jsx_runtime20 = require("react/jsx-runtime");
18620
18704
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
18621
18705
  var CHROME_SELECTOR = "[data-ohw-toolbar], [data-ohw-edit-chrome], [data-ohw-item-interaction], [data-ohw-link-modal-root], [data-ohw-section-picker], [data-ohw-ai-review], [data-ohw-more-menu]";
@@ -23741,6 +23825,23 @@ function currentIconRef(el) {
23741
23825
  );
23742
23826
  return named ? `lucide:${named.slice("lucide-".length)}` : "";
23743
23827
  }
23828
+ function requestValidateTree5(postToParent2, tree) {
23829
+ return new Promise((resolve) => {
23830
+ const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
23831
+ const timeout = window.setTimeout(() => {
23832
+ window.removeEventListener("message", onMessage);
23833
+ resolve({ ok: false, reasons: ["Validation request timed out."] });
23834
+ }, 8e3);
23835
+ function onMessage(e) {
23836
+ if (e.data?.type !== "ow:ai-validate-tree-result" || e.data.id !== id) return;
23837
+ window.clearTimeout(timeout);
23838
+ window.removeEventListener("message", onMessage);
23839
+ resolve({ ok: e.data.ok === true, reasons: Array.isArray(e.data.reasons) ? e.data.reasons : [] });
23840
+ }
23841
+ window.addEventListener("message", onMessage);
23842
+ postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
23843
+ });
23844
+ }
23744
23845
  function getEditMeasureEl(editable) {
23745
23846
  return editable.closest("[data-ohw-href-key]") ?? editable;
23746
23847
  }
@@ -28092,10 +28193,14 @@ function OhhwellsBridge() {
28092
28193
  e.preventDefault();
28093
28194
  e.stopPropagation();
28094
28195
  aiSectionApiRef.current?.selectFromElement(editable);
28196
+ const aiIcon = editable.hasAttribute("data-ai-icon");
28197
+ const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28095
28198
  postToParentRef.current({
28096
28199
  type: "ow:icon-pick",
28097
28200
  key: editable.dataset.ohwKey ?? "",
28098
- current: currentIconRef(editable)
28201
+ current: currentIconRef(editable),
28202
+ ...aiIcon ? { iconMode: "name" } : {},
28203
+ ...featureIcon ? { options: [...PRICING_FEATURE_ICONS] } : {}
28099
28204
  });
28100
28205
  return;
28101
28206
  }
@@ -29342,15 +29447,43 @@ function OhhwellsBridge() {
29342
29447
  if (e.data?.type !== "ow:icon-markup") return;
29343
29448
  const { key, markup } = e.data;
29344
29449
  if (!key || typeof markup !== "string") return;
29345
- const targets = document.querySelectorAll(
29346
- `[data-ohw-key="${key}"][data-ohw-editable="icon"]`
29347
- );
29450
+ const targets = Array.from(
29451
+ document.querySelectorAll(`[data-ohw-key="${key}"][data-ohw-editable="icon"]`)
29452
+ ).filter((el) => !el.hasAttribute("data-ai-icon"));
29348
29453
  if (!targets.length) return;
29349
29454
  targets.forEach((el) => {
29350
29455
  applyIconMarkup(el, markup);
29351
29456
  });
29352
29457
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: markup }], flush: true });
29353
29458
  };
29459
+ const handleIconName = (e) => {
29460
+ if (e.data?.type !== "ow:icon-name") return;
29461
+ const { key, icon, url } = e.data;
29462
+ const isName = typeof icon === "string" && /^[a-z0-9-]+$/.test(icon);
29463
+ const isUpload = !isName && typeof url === "string" && /^https?:\/\/\S+$/i.test(url);
29464
+ if (!key || !isName && !isUpload) return;
29465
+ const state = parseAiSectionsState(aiSectionsRef.current);
29466
+ const entry = state.sections.find((s) => key.startsWith(aiSlotKeyPrefixFor(s.id)));
29467
+ if (!entry) return;
29468
+ const path = key.slice(aiSlotKeyPrefixFor(entry.id).length);
29469
+ const uploadRef = isUpload ? `icon-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}` : null;
29470
+ const nextTree = uploadRef ? applyIconUploadToTree(entry.tree, path, uploadRef) : applyIconNameToTree(entry.tree, path, icon);
29471
+ if (!nextTree) return;
29472
+ void requestValidateTree5(postToParentRef.current, nextTree).then(({ ok }) => {
29473
+ if (!ok) return;
29474
+ applyTreeLocalRef.current?.({
29475
+ id: entry.id,
29476
+ mode: "replace",
29477
+ targetSectionId: entry.id,
29478
+ tree: nextTree,
29479
+ label: entry.label,
29480
+ media: uploadRef ? { ...entry.media, [uploadRef]: url } : entry.media,
29481
+ ...entry.brand ? { brand: entry.brand } : {},
29482
+ // The user is looking at the icon they just changed — no need to recenter on it.
29483
+ skipScrollIntoView: true
29484
+ });
29485
+ });
29486
+ };
29354
29487
  const handleImageUrl = (e) => {
29355
29488
  if (e.data?.type !== "ow:image-url") return;
29356
29489
  const { key, url } = e.data;
@@ -30799,6 +30932,7 @@ function OhhwellsBridge() {
30799
30932
  window.addEventListener("message", handleSocialCancel);
30800
30933
  window.addEventListener("message", handleSocialUpdate);
30801
30934
  window.addEventListener("message", handleIconMarkup);
30935
+ window.addEventListener("message", handleIconName);
30802
30936
  window.addEventListener("message", handleImageUrl);
30803
30937
  window.addEventListener("message", handleImageUploading);
30804
30938
  window.addEventListener("message", handleCarouselChange);
@@ -30883,6 +31017,7 @@ function OhhwellsBridge() {
30883
31017
  window.removeEventListener("message", handleSocialCancel);
30884
31018
  window.removeEventListener("message", handleSocialUpdate);
30885
31019
  window.removeEventListener("message", handleIconMarkup);
31020
+ window.removeEventListener("message", handleIconName);
30886
31021
  window.removeEventListener("message", handleImageUrl);
30887
31022
  window.removeEventListener("message", handleImageUploading);
30888
31023
  window.removeEventListener("message", handleCarouselChange);
@@ -31208,7 +31343,7 @@ function OhhwellsBridge() {
31208
31343
  postToParent2({
31209
31344
  type: "ow:ready",
31210
31345
  version: "1",
31211
- bridgeVersion: "0.1.140",
31346
+ bridgeVersion: "0.1.142",
31212
31347
  path: pathname,
31213
31348
  nodes: collectEditableNodes(editContentRef.current),
31214
31349
  sections