@ohhwells/bridge 0.1.139-next.376 → 0.1.139-next.380

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,274 @@ 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
+ const segments = path.split(".");
6562
+ const slot = segments.pop();
6563
+ if (!slot || NODE_SEGMENT.test(slot)) return null;
6564
+ const next = JSON.parse(JSON.stringify(tree));
6565
+ const node = nodeAtPath(next, segments);
6566
+ if (!node) return null;
6567
+ const current = node.slots?.[slot];
6568
+ if (typeof current !== "string" || !/^(lucide|simple):/.test(current)) return null;
6569
+ node.slots = { ...node.slots, [slot]: `lucide:${icon}` };
6570
+ return next;
6571
+ }
6572
+
6296
6573
  // src/lib/ai-footer-tree.ts
6297
6574
  var FOOTER_COLUMN_SPANS = [12, 6, 4, 3];
6298
6575
  var FOOTER_COLUMN_BLOCKS = [
@@ -7678,17 +7955,18 @@ function MediaBox({
7678
7955
  const url = refValue ? ctx.resolveMedia(refValue) : null;
7679
7956
  const isIcon = /^(lucide|simple):/.test(refValue);
7680
7957
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
7681
- const editAttrs = ctx.keyFor && editPath ? {
7958
+ const editAttrs = ctx.keyFor && editPath && !isIcon ? {
7682
7959
  "data-ohw-key": ctx.keyFor(editPath),
7683
- ...isIcon ? { "data-ohw-editable": "icon" } : genericImageEdit ? { "data-ohw-editable": "image" } : {}
7960
+ ...genericImageEdit ? { "data-ohw-editable": "image" } : {}
7684
7961
  } : {};
7685
7962
  if (isIcon) {
7686
7963
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
7964
+ const iconEditAttrs = ctx.keyFor && editPath && genericImageEdit ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "icon" } : {};
7687
7965
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
7688
7966
  "span",
7689
7967
  {
7690
7968
  "data-ai-icon": refValue,
7691
- ...editAttrs,
7969
+ ...iconEditAttrs,
7692
7970
  style: {
7693
7971
  display: "inline-flex",
7694
7972
  width: 48,
@@ -8185,8 +8463,20 @@ function PricingCard({ node, ctx, path }) {
8185
8463
  /* @__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) => {
8186
8464
  const Icon = lucideByName(item.icon || "check");
8187
8465
  const iconColor = pricingIconColor(item.iconColor, ctx.brand, fg);
8466
+ const iconAttrs = ctx.keyFor && !item.html ? {
8467
+ "data-ohw-key": ctx.keyFor(`${path}.features.${i}.icon`),
8468
+ "data-ohw-editable": "icon"
8469
+ } : {};
8188
8470
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", gap: AI_TREE_TOKENS.spacing3 }, children: [
8189
- Icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 20, color: iconColor }),
8471
+ Icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
8472
+ "span",
8473
+ {
8474
+ "data-ai-icon": `lucide:${item.icon || "check"}`,
8475
+ ...iconAttrs,
8476
+ style: { display: "inline-flex", flexShrink: 0 },
8477
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 20, color: iconColor })
8478
+ }
8479
+ ),
8190
8480
  item.html ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
8191
8481
  "span",
8192
8482
  {
@@ -10311,38 +10601,39 @@ function deriveTemplateButtonStyle() {
10311
10601
  el.querySelectorAll("[data-ohw-editable]").length <= 1
10312
10602
  );
10313
10603
  const isFilled = (el) => {
10314
- const bg = getComputedStyle(el).backgroundColor;
10604
+ const bg = withTransitionsSettled(el, (s) => s.backgroundColor);
10315
10605
  if (!bg || bg === "transparent") return false;
10316
10606
  const alpha = bg.match(/rgba?\([^)]*,\s*([\d.]+)\)$/);
10317
10607
  return !alpha || parseFloat(alpha[1]) > 0;
10318
10608
  };
10319
10609
  const btn = candidates.find(isFilled) ?? candidates[0];
10320
10610
  if (!btn) return null;
10321
- const cs = getComputedStyle(btn);
10322
- const filled = isFilled(btn);
10323
- const corners = [
10324
- cs.borderTopLeftRadius,
10325
- cs.borderTopRightRadius,
10326
- cs.borderBottomRightRadius,
10327
- cs.borderBottomLeftRadius
10328
- ].map((v) => v || "0px");
10329
- const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
10330
- const px2 = (v) => parseFloat(v) || 0;
10331
- const lineHeight = px2(cs.lineHeight) || px2(cs.fontSize) * 1.2;
10332
- const contentH = btn.getBoundingClientRect().height - px2(cs.borderTopWidth) - px2(cs.borderBottomWidth);
10333
- const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
10334
- const padY = Math.max(px2(cs.paddingTop), px2(cs.paddingBottom), impliedY);
10335
- const padX = Math.max(px2(cs.paddingLeft), px2(cs.paddingRight));
10336
- return {
10337
- radius: radius || "10px",
10338
- padding: `${padY}px ${padX}px`,
10339
- ...filled ? { background: cs.backgroundColor, color: cs.color } : {},
10340
- fontFamily: cs.fontFamily || "",
10341
- fontSize: cs.fontSize || "",
10342
- fontWeight: cs.fontWeight || "",
10343
- letterSpacing: cs.letterSpacing || "",
10344
- textTransform: cs.textTransform || ""
10345
- };
10611
+ return withTransitionsSettled(btn, (cs) => {
10612
+ const filled = isFilled(btn);
10613
+ const corners = [
10614
+ cs.borderTopLeftRadius,
10615
+ cs.borderTopRightRadius,
10616
+ cs.borderBottomRightRadius,
10617
+ cs.borderBottomLeftRadius
10618
+ ].map((v) => v || "0px");
10619
+ const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
10620
+ const px2 = (v) => parseFloat(v) || 0;
10621
+ const lineHeight = px2(cs.lineHeight) || px2(cs.fontSize) * 1.2;
10622
+ const contentH = btn.getBoundingClientRect().height - px2(cs.borderTopWidth) - px2(cs.borderBottomWidth);
10623
+ const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
10624
+ const padY = Math.max(px2(cs.paddingTop), px2(cs.paddingBottom), impliedY);
10625
+ const padX = Math.max(px2(cs.paddingLeft), px2(cs.paddingRight));
10626
+ return {
10627
+ radius: radius || "10px",
10628
+ padding: `${padY}px ${padX}px`,
10629
+ ...filled ? { background: cs.backgroundColor, color: cs.color } : {},
10630
+ fontFamily: cs.fontFamily || "",
10631
+ fontSize: cs.fontSize || "",
10632
+ fontWeight: cs.fontWeight || "",
10633
+ letterSpacing: cs.letterSpacing || "",
10634
+ textTransform: cs.textTransform || ""
10635
+ };
10636
+ });
10346
10637
  }
10347
10638
  var mounted = /* @__PURE__ */ new Map();
10348
10639
  function findTemplateSection(id) {
@@ -18431,222 +18722,6 @@ function AiMediaStripOverlay({
18431
18722
  // src/ui/ai-section/AiPricingCardOverlay.tsx
18432
18723
  var import_react11 = require("react");
18433
18724
  var import_lucide_react9 = require("lucide-react");
18434
-
18435
- // src/lib/ai-pricing-tree.ts
18436
- var PRICING_MIN_CARDS = 1;
18437
- var PRICING_MAX_CARDS = 4;
18438
- function isPricingCard(node) {
18439
- return typeof node === "object" && node !== null && node.type === "card" && node.slots?.preset === "pricing";
18440
- }
18441
- function isPricingCollection(node) {
18442
- return node.type === "collection" && Array.isArray(node.children) && node.children.length > 0 && isPricingCard(node.children[0]);
18443
- }
18444
- function findCollection(node) {
18445
- if (isPricingCollection(node)) return node;
18446
- for (const child of node.children ?? []) {
18447
- const found = findCollection(child);
18448
- if (found) return found;
18449
- }
18450
- return null;
18451
- }
18452
- function findPricingCollection(tree) {
18453
- for (const row of tree.rows ?? []) {
18454
- for (const block of row.blocks ?? []) {
18455
- const found = findCollection(block);
18456
- if (found) return found;
18457
- }
18458
- }
18459
- return null;
18460
- }
18461
- function pricingCardCount(tree) {
18462
- return findPricingCollection(tree)?.children?.length ?? 0;
18463
- }
18464
- function withPricingCollection(tree, mutate) {
18465
- const next = JSON.parse(JSON.stringify(tree));
18466
- const collection = findPricingCollection(next);
18467
- if (!collection || !collection.children) return null;
18468
- for (const card of collection.children) {
18469
- if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
18470
- }
18471
- return mutate(collection) ? next : null;
18472
- }
18473
- function upgradeLegacyPricingSlots(card) {
18474
- const slots = card.slots = card.slots ?? {};
18475
- if (typeof slots.features === "string") {
18476
- const rows = pricingFeatureList(card);
18477
- if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
18478
- }
18479
- if (slots.featured === true) slots.featured = { enabled: true };
18480
- else if (slots.featured === false) delete slots.featured;
18481
- }
18482
- function addPricingTier(tree) {
18483
- return withPricingCollection(tree, (collection) => {
18484
- const children = collection.children;
18485
- if (children.length >= PRICING_MAX_CARDS) return false;
18486
- const clone = JSON.parse(JSON.stringify(children[children.length - 1]));
18487
- clone.slots = clone.slots ?? {};
18488
- delete clone.slots.featured;
18489
- clone.slots.heading = "New tier";
18490
- children.push(clone);
18491
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18492
- return true;
18493
- });
18494
- }
18495
- function removePricingTier(tree, index) {
18496
- return withPricingCollection(tree, (collection) => {
18497
- const children = collection.children;
18498
- if (children.length <= PRICING_MIN_CARDS) return false;
18499
- if (index < 0 || index >= children.length) return false;
18500
- children.splice(index, 1);
18501
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18502
- return true;
18503
- });
18504
- }
18505
- var PRICING_CARD_STYLES = [
18506
- { value: "outline", label: "Outline", treatment: null },
18507
- { value: "lift", label: "Lift", treatment: "lifted" },
18508
- { value: "invert", label: "Invert", treatment: "inverted" },
18509
- { value: "header", label: "Header", treatment: "accent-header" }
18510
- ];
18511
- var DEFAULT_FEATURED_BADGE = "Most popular";
18512
- function featuredOf(target) {
18513
- const raw = target?.slots?.featured;
18514
- if (raw === true) return { enabled: true, treatment: "outlined" };
18515
- return typeof raw === "object" && raw !== null ? raw : {};
18516
- }
18517
- function badgeOf(featured) {
18518
- return typeof featured.badge === "string" ? featured.badge.trim() : "";
18519
- }
18520
- function pricingCardStyle(tree, index) {
18521
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18522
- if (featured.enabled !== true) return "outline";
18523
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18524
- }
18525
- function pricingCardFeaturedTag(tree, index) {
18526
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18527
- return featured.enabled === true && badgeOf(featured).length > 0;
18528
- }
18529
- function composeFeatured(style, tagOn, badgeText) {
18530
- const treatment = PRICING_CARD_STYLES.find((s) => s.value === style)?.treatment ?? null;
18531
- if (tagOn) return { enabled: true, badge: badgeText || DEFAULT_FEATURED_BADGE, treatment: treatment ?? "outlined" };
18532
- if (treatment) return { enabled: true, treatment };
18533
- return badgeText.length > 0 ? { enabled: false, badge: badgeText } : { enabled: false };
18534
- }
18535
- function withPricingCard(tree, index, mutate) {
18536
- return withPricingCollection(tree, (collection) => {
18537
- const children = collection.children;
18538
- if (index < 0 || index >= children.length) return false;
18539
- const target = children[index];
18540
- target.slots = target.slots ?? {};
18541
- mutate(target);
18542
- return true;
18543
- });
18544
- }
18545
- function setPricingCardStyle(tree, index, style) {
18546
- if (!PRICING_CARD_STYLES.some((s) => s.value === style)) return null;
18547
- return withPricingCard(tree, index, (target) => {
18548
- const prev = featuredOf(target);
18549
- const tagOn = prev.enabled === true && badgeOf(prev).length > 0;
18550
- target.slots.featured = composeFeatured(style, tagOn, badgeOf(prev));
18551
- });
18552
- }
18553
- function setPricingCardFeaturedTag(tree, index, on) {
18554
- return withPricingCollection(tree, (collection) => {
18555
- const children = collection.children;
18556
- if (index < 0 || index >= children.length) return false;
18557
- if (on) {
18558
- children.forEach((card, i) => {
18559
- if (i === index) return;
18560
- const prev2 = featuredOf(card);
18561
- if (prev2.enabled !== true || badgeOf(prev2).length === 0) return;
18562
- card.slots = card.slots ?? {};
18563
- card.slots.featured = composeFeatured(pricingCardStyleOf(prev2), false, badgeOf(prev2));
18564
- });
18565
- }
18566
- const target = children[index];
18567
- target.slots = target.slots ?? {};
18568
- const prev = featuredOf(target);
18569
- target.slots.featured = composeFeatured(pricingCardStyleOf(prev), on, badgeOf(prev));
18570
- return true;
18571
- });
18572
- }
18573
- function pricingCardStyleOf(featured) {
18574
- if (featured.enabled !== true) return "outline";
18575
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18576
- }
18577
- function addPricingCardButton(tree, index, label) {
18578
- return withPricingCollection(tree, (collection) => {
18579
- const children = collection.children;
18580
- if (index < 0 || index >= children.length) return false;
18581
- const target = children[index];
18582
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18583
- if (buttons.length >= 2) return false;
18584
- const hasPrimary = buttons.some((b) => b.slots?.variant === "primary");
18585
- target.children = [
18586
- ...target.children ?? [],
18587
- { type: "button", span: 12, slots: { label, href: "#", variant: hasPrimary ? "secondary" : "primary" } }
18588
- ];
18589
- return true;
18590
- });
18591
- }
18592
- function removePricingCardButton(tree, index) {
18593
- return withPricingCollection(tree, (collection) => {
18594
- const children = collection.children;
18595
- if (index < 0 || index >= children.length) return false;
18596
- const target = children[index];
18597
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18598
- if (buttons.length === 0) return false;
18599
- const lastButton = buttons[buttons.length - 1];
18600
- target.children = (target.children ?? []).filter((c) => c !== lastButton);
18601
- return true;
18602
- });
18603
- }
18604
- var PRICING_FEATURES_MIN = 1;
18605
- var PRICING_FEATURES_MAX = 10;
18606
- function pricingFeatureList(target) {
18607
- const raw = target.slots?.features;
18608
- if (Array.isArray(raw)) {
18609
- return raw.filter((item) => !!item && typeof item === "object" && typeof item.text === "string").map((item) => ({
18610
- text: item.text,
18611
- ...typeof item.icon === "string" ? { icon: item.icon } : {},
18612
- ...typeof item.iconColor === "string" ? { iconColor: item.iconColor } : {}
18613
- }));
18614
- }
18615
- if (typeof raw === "string") {
18616
- return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
18617
- }
18618
- return [];
18619
- }
18620
- function pricingCardFeatureCount(tree, index) {
18621
- const target = findPricingCollection(tree)?.children?.[index];
18622
- return target ? pricingFeatureList(target).length : 0;
18623
- }
18624
- function addPricingCardFeature(tree, index, text) {
18625
- return withPricingCollection(tree, (collection) => {
18626
- const children = collection.children;
18627
- if (index < 0 || index >= children.length) return false;
18628
- const target = children[index];
18629
- const features = pricingFeatureList(target);
18630
- if (features.length >= PRICING_FEATURES_MAX) return false;
18631
- target.slots = target.slots ?? {};
18632
- target.slots.features = [...features, { text }];
18633
- return true;
18634
- });
18635
- }
18636
- function removePricingCardFeature(tree, index) {
18637
- return withPricingCollection(tree, (collection) => {
18638
- const children = collection.children;
18639
- if (index < 0 || index >= children.length) return false;
18640
- const target = children[index];
18641
- const features = pricingFeatureList(target);
18642
- if (features.length <= PRICING_FEATURES_MIN) return false;
18643
- target.slots = target.slots ?? {};
18644
- target.slots.features = features.slice(0, -1);
18645
- return true;
18646
- });
18647
- }
18648
-
18649
- // src/ui/ai-section/AiPricingCardOverlay.tsx
18650
18725
  var import_jsx_runtime20 = require("react/jsx-runtime");
18651
18726
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
18652
18727
  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]";
@@ -23773,6 +23848,23 @@ function currentIconRef(el) {
23773
23848
  );
23774
23849
  return named ? `lucide:${named.slice("lucide-".length)}` : "";
23775
23850
  }
23851
+ function requestValidateTree5(postToParent2, tree) {
23852
+ return new Promise((resolve) => {
23853
+ const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
23854
+ const timeout = window.setTimeout(() => {
23855
+ window.removeEventListener("message", onMessage);
23856
+ resolve({ ok: false, reasons: ["Validation request timed out."] });
23857
+ }, 8e3);
23858
+ function onMessage(e) {
23859
+ if (e.data?.type !== "ow:ai-validate-tree-result" || e.data.id !== id) return;
23860
+ window.clearTimeout(timeout);
23861
+ window.removeEventListener("message", onMessage);
23862
+ resolve({ ok: e.data.ok === true, reasons: Array.isArray(e.data.reasons) ? e.data.reasons : [] });
23863
+ }
23864
+ window.addEventListener("message", onMessage);
23865
+ postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
23866
+ });
23867
+ }
23776
23868
  function getEditMeasureEl(editable) {
23777
23869
  return editable.closest("[data-ohw-href-key]") ?? editable;
23778
23870
  }
@@ -28121,10 +28213,14 @@ function OhhwellsBridge() {
28121
28213
  e.preventDefault();
28122
28214
  e.stopPropagation();
28123
28215
  aiSectionApiRef.current?.selectFromElement(editable);
28216
+ const aiIcon = editable.hasAttribute("data-ai-icon");
28217
+ const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28124
28218
  postToParentRef.current({
28125
28219
  type: "ow:icon-pick",
28126
28220
  key: editable.dataset.ohwKey ?? "",
28127
- current: currentIconRef(editable)
28221
+ current: currentIconRef(editable),
28222
+ ...aiIcon ? { iconMode: "name" } : {},
28223
+ ...featureIcon ? { options: [...PRICING_FEATURE_ICONS] } : {}
28128
28224
  });
28129
28225
  return;
28130
28226
  }
@@ -29386,15 +29482,39 @@ function OhhwellsBridge() {
29386
29482
  if (e.data?.type !== "ow:icon-markup") return;
29387
29483
  const { key, markup } = e.data;
29388
29484
  if (!key || typeof markup !== "string") return;
29389
- const targets = document.querySelectorAll(
29390
- `[data-ohw-key="${key}"][data-ohw-editable="icon"]`
29391
- );
29485
+ const targets = Array.from(
29486
+ document.querySelectorAll(`[data-ohw-key="${key}"][data-ohw-editable="icon"]`)
29487
+ ).filter((el) => !el.hasAttribute("data-ai-icon"));
29392
29488
  if (!targets.length) return;
29393
29489
  targets.forEach((el) => {
29394
29490
  applyIconMarkup(el, markup);
29395
29491
  });
29396
29492
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: markup }], flush: true });
29397
29493
  };
29494
+ const handleIconName = (e) => {
29495
+ if (e.data?.type !== "ow:icon-name") return;
29496
+ const { key, icon } = e.data;
29497
+ if (!key || typeof icon !== "string" || !/^[a-z0-9-]+$/.test(icon)) return;
29498
+ const state = parseAiSectionsState(aiSectionsRef.current);
29499
+ const entry = state.sections.find((s) => key.startsWith(aiSlotKeyPrefixFor(s.id)));
29500
+ if (!entry) return;
29501
+ const nextTree = applyIconNameToTree(entry.tree, key.slice(aiSlotKeyPrefixFor(entry.id).length), icon);
29502
+ if (!nextTree) return;
29503
+ void requestValidateTree5(postToParentRef.current, nextTree).then(({ ok }) => {
29504
+ if (!ok) return;
29505
+ applyTreeLocalRef.current?.({
29506
+ id: entry.id,
29507
+ mode: "replace",
29508
+ targetSectionId: entry.id,
29509
+ tree: nextTree,
29510
+ label: entry.label,
29511
+ media: entry.media,
29512
+ ...entry.brand ? { brand: entry.brand } : {},
29513
+ // The user is looking at the icon they just changed — no need to recenter on it.
29514
+ skipScrollIntoView: true
29515
+ });
29516
+ });
29517
+ };
29398
29518
  const handleImageUrl = (e) => {
29399
29519
  if (e.data?.type !== "ow:image-url") return;
29400
29520
  const { key, url } = e.data;
@@ -30850,6 +30970,7 @@ function OhhwellsBridge() {
30850
30970
  window.addEventListener("message", handleSocialCancel);
30851
30971
  window.addEventListener("message", handleSocialUpdate);
30852
30972
  window.addEventListener("message", handleIconMarkup);
30973
+ window.addEventListener("message", handleIconName);
30853
30974
  window.addEventListener("message", handleImageUrl);
30854
30975
  window.addEventListener("message", handleImageUploading);
30855
30976
  window.addEventListener("message", handleCarouselChange);
@@ -30934,6 +31055,7 @@ function OhhwellsBridge() {
30934
31055
  window.removeEventListener("message", handleSocialCancel);
30935
31056
  window.removeEventListener("message", handleSocialUpdate);
30936
31057
  window.removeEventListener("message", handleIconMarkup);
31058
+ window.removeEventListener("message", handleIconName);
30937
31059
  window.removeEventListener("message", handleImageUrl);
30938
31060
  window.removeEventListener("message", handleImageUploading);
30939
31061
  window.removeEventListener("message", handleCarouselChange);