@ohhwells/bridge 0.1.139-next.379 → 0.1.139-next.381

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
@@ -5033,10 +5033,9 @@ function getLinkHref3(el) {
5033
5033
  }
5034
5034
  function collectEditableNodes(extraContent, root = document) {
5035
5035
  const isScoped = root !== document;
5036
- const editableEls = Array.from(
5037
- root.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="form"])')
5038
- );
5039
- 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)) {
5040
5039
  editableEls.unshift(root);
5041
5040
  }
5042
5041
  const nodes = editableEls.map((el) => {
@@ -6303,6 +6302,274 @@ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE
6303
6302
  return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
6304
6303
  }
6305
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
+
6306
6573
  // src/lib/ai-footer-tree.ts
6307
6574
  var FOOTER_COLUMN_SPANS = [12, 6, 4, 3];
6308
6575
  var FOOTER_COLUMN_BLOCKS = [
@@ -7688,17 +7955,18 @@ function MediaBox({
7688
7955
  const url = refValue ? ctx.resolveMedia(refValue) : null;
7689
7956
  const isIcon = /^(lucide|simple):/.test(refValue);
7690
7957
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
7691
- const editAttrs = ctx.keyFor && editPath ? {
7958
+ const editAttrs = ctx.keyFor && editPath && !isIcon ? {
7692
7959
  "data-ohw-key": ctx.keyFor(editPath),
7693
- ...isIcon ? { "data-ohw-editable": "icon" } : genericImageEdit ? { "data-ohw-editable": "image" } : {}
7960
+ ...genericImageEdit ? { "data-ohw-editable": "image" } : {}
7694
7961
  } : {};
7695
7962
  if (isIcon) {
7696
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" } : {};
7697
7965
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
7698
7966
  "span",
7699
7967
  {
7700
7968
  "data-ai-icon": refValue,
7701
- ...editAttrs,
7969
+ ...iconEditAttrs,
7702
7970
  style: {
7703
7971
  display: "inline-flex",
7704
7972
  width: 48,
@@ -8195,8 +8463,20 @@ function PricingCard({ node, ctx, path }) {
8195
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) => {
8196
8464
  const Icon = lucideByName(item.icon || "check");
8197
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
+ } : {};
8198
8470
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", gap: AI_TREE_TOKENS.spacing3 }, children: [
8199
- 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
+ ),
8200
8480
  item.html ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
8201
8481
  "span",
8202
8482
  {
@@ -18442,222 +18722,6 @@ function AiMediaStripOverlay({
18442
18722
  // src/ui/ai-section/AiPricingCardOverlay.tsx
18443
18723
  var import_react11 = require("react");
18444
18724
  var import_lucide_react9 = require("lucide-react");
18445
-
18446
- // src/lib/ai-pricing-tree.ts
18447
- var PRICING_MIN_CARDS = 1;
18448
- var PRICING_MAX_CARDS = 4;
18449
- function isPricingCard(node) {
18450
- return typeof node === "object" && node !== null && node.type === "card" && node.slots?.preset === "pricing";
18451
- }
18452
- function isPricingCollection(node) {
18453
- return node.type === "collection" && Array.isArray(node.children) && node.children.length > 0 && isPricingCard(node.children[0]);
18454
- }
18455
- function findCollection(node) {
18456
- if (isPricingCollection(node)) return node;
18457
- for (const child of node.children ?? []) {
18458
- const found = findCollection(child);
18459
- if (found) return found;
18460
- }
18461
- return null;
18462
- }
18463
- function findPricingCollection(tree) {
18464
- for (const row of tree.rows ?? []) {
18465
- for (const block of row.blocks ?? []) {
18466
- const found = findCollection(block);
18467
- if (found) return found;
18468
- }
18469
- }
18470
- return null;
18471
- }
18472
- function pricingCardCount(tree) {
18473
- return findPricingCollection(tree)?.children?.length ?? 0;
18474
- }
18475
- function withPricingCollection(tree, mutate) {
18476
- const next = JSON.parse(JSON.stringify(tree));
18477
- const collection = findPricingCollection(next);
18478
- if (!collection || !collection.children) return null;
18479
- for (const card of collection.children) {
18480
- if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
18481
- }
18482
- return mutate(collection) ? next : null;
18483
- }
18484
- function upgradeLegacyPricingSlots(card) {
18485
- const slots = card.slots = card.slots ?? {};
18486
- if (typeof slots.features === "string") {
18487
- const rows = pricingFeatureList(card);
18488
- if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
18489
- }
18490
- if (slots.featured === true) slots.featured = { enabled: true };
18491
- else if (slots.featured === false) delete slots.featured;
18492
- }
18493
- function addPricingTier(tree) {
18494
- return withPricingCollection(tree, (collection) => {
18495
- const children = collection.children;
18496
- if (children.length >= PRICING_MAX_CARDS) return false;
18497
- const clone = JSON.parse(JSON.stringify(children[children.length - 1]));
18498
- clone.slots = clone.slots ?? {};
18499
- delete clone.slots.featured;
18500
- clone.slots.heading = "New tier";
18501
- children.push(clone);
18502
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18503
- return true;
18504
- });
18505
- }
18506
- function removePricingTier(tree, index) {
18507
- return withPricingCollection(tree, (collection) => {
18508
- const children = collection.children;
18509
- if (children.length <= PRICING_MIN_CARDS) return false;
18510
- if (index < 0 || index >= children.length) return false;
18511
- children.splice(index, 1);
18512
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18513
- return true;
18514
- });
18515
- }
18516
- var PRICING_CARD_STYLES = [
18517
- { value: "outline", label: "Outline", treatment: null },
18518
- { value: "lift", label: "Lift", treatment: "lifted" },
18519
- { value: "invert", label: "Invert", treatment: "inverted" },
18520
- { value: "header", label: "Header", treatment: "accent-header" }
18521
- ];
18522
- var DEFAULT_FEATURED_BADGE = "Most popular";
18523
- function featuredOf(target) {
18524
- const raw = target?.slots?.featured;
18525
- if (raw === true) return { enabled: true, treatment: "outlined" };
18526
- return typeof raw === "object" && raw !== null ? raw : {};
18527
- }
18528
- function badgeOf(featured) {
18529
- return typeof featured.badge === "string" ? featured.badge.trim() : "";
18530
- }
18531
- function pricingCardStyle(tree, index) {
18532
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18533
- if (featured.enabled !== true) return "outline";
18534
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18535
- }
18536
- function pricingCardFeaturedTag(tree, index) {
18537
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18538
- return featured.enabled === true && badgeOf(featured).length > 0;
18539
- }
18540
- function composeFeatured(style, tagOn, badgeText) {
18541
- const treatment = PRICING_CARD_STYLES.find((s) => s.value === style)?.treatment ?? null;
18542
- if (tagOn) return { enabled: true, badge: badgeText || DEFAULT_FEATURED_BADGE, treatment: treatment ?? "outlined" };
18543
- if (treatment) return { enabled: true, treatment };
18544
- return badgeText.length > 0 ? { enabled: false, badge: badgeText } : { enabled: false };
18545
- }
18546
- function withPricingCard(tree, index, mutate) {
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
- target.slots = target.slots ?? {};
18552
- mutate(target);
18553
- return true;
18554
- });
18555
- }
18556
- function setPricingCardStyle(tree, index, style) {
18557
- if (!PRICING_CARD_STYLES.some((s) => s.value === style)) return null;
18558
- return withPricingCard(tree, index, (target) => {
18559
- const prev = featuredOf(target);
18560
- const tagOn = prev.enabled === true && badgeOf(prev).length > 0;
18561
- target.slots.featured = composeFeatured(style, tagOn, badgeOf(prev));
18562
- });
18563
- }
18564
- function setPricingCardFeaturedTag(tree, index, on) {
18565
- return withPricingCollection(tree, (collection) => {
18566
- const children = collection.children;
18567
- if (index < 0 || index >= children.length) return false;
18568
- if (on) {
18569
- children.forEach((card, i) => {
18570
- if (i === index) return;
18571
- const prev2 = featuredOf(card);
18572
- if (prev2.enabled !== true || badgeOf(prev2).length === 0) return;
18573
- card.slots = card.slots ?? {};
18574
- card.slots.featured = composeFeatured(pricingCardStyleOf(prev2), false, badgeOf(prev2));
18575
- });
18576
- }
18577
- const target = children[index];
18578
- target.slots = target.slots ?? {};
18579
- const prev = featuredOf(target);
18580
- target.slots.featured = composeFeatured(pricingCardStyleOf(prev), on, badgeOf(prev));
18581
- return true;
18582
- });
18583
- }
18584
- function pricingCardStyleOf(featured) {
18585
- if (featured.enabled !== true) return "outline";
18586
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18587
- }
18588
- function addPricingCardButton(tree, index, label) {
18589
- return withPricingCollection(tree, (collection) => {
18590
- const children = collection.children;
18591
- if (index < 0 || index >= children.length) return false;
18592
- const target = children[index];
18593
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18594
- if (buttons.length >= 2) return false;
18595
- const hasPrimary = buttons.some((b) => b.slots?.variant === "primary");
18596
- target.children = [
18597
- ...target.children ?? [],
18598
- { type: "button", span: 12, slots: { label, href: "#", variant: hasPrimary ? "secondary" : "primary" } }
18599
- ];
18600
- return true;
18601
- });
18602
- }
18603
- function removePricingCardButton(tree, index) {
18604
- return withPricingCollection(tree, (collection) => {
18605
- const children = collection.children;
18606
- if (index < 0 || index >= children.length) return false;
18607
- const target = children[index];
18608
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18609
- if (buttons.length === 0) return false;
18610
- const lastButton = buttons[buttons.length - 1];
18611
- target.children = (target.children ?? []).filter((c) => c !== lastButton);
18612
- return true;
18613
- });
18614
- }
18615
- var PRICING_FEATURES_MIN = 1;
18616
- var PRICING_FEATURES_MAX = 10;
18617
- function pricingFeatureList(target) {
18618
- const raw = target.slots?.features;
18619
- if (Array.isArray(raw)) {
18620
- return raw.filter((item) => !!item && typeof item === "object" && typeof item.text === "string").map((item) => ({
18621
- text: item.text,
18622
- ...typeof item.icon === "string" ? { icon: item.icon } : {},
18623
- ...typeof item.iconColor === "string" ? { iconColor: item.iconColor } : {}
18624
- }));
18625
- }
18626
- if (typeof raw === "string") {
18627
- return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
18628
- }
18629
- return [];
18630
- }
18631
- function pricingCardFeatureCount(tree, index) {
18632
- const target = findPricingCollection(tree)?.children?.[index];
18633
- return target ? pricingFeatureList(target).length : 0;
18634
- }
18635
- function addPricingCardFeature(tree, index, text) {
18636
- return withPricingCollection(tree, (collection) => {
18637
- const children = collection.children;
18638
- if (index < 0 || index >= children.length) return false;
18639
- const target = children[index];
18640
- const features = pricingFeatureList(target);
18641
- if (features.length >= PRICING_FEATURES_MAX) return false;
18642
- target.slots = target.slots ?? {};
18643
- target.slots.features = [...features, { text }];
18644
- return true;
18645
- });
18646
- }
18647
- function removePricingCardFeature(tree, index) {
18648
- return withPricingCollection(tree, (collection) => {
18649
- const children = collection.children;
18650
- if (index < 0 || index >= children.length) return false;
18651
- const target = children[index];
18652
- const features = pricingFeatureList(target);
18653
- if (features.length <= PRICING_FEATURES_MIN) return false;
18654
- target.slots = target.slots ?? {};
18655
- target.slots.features = features.slice(0, -1);
18656
- return true;
18657
- });
18658
- }
18659
-
18660
- // src/ui/ai-section/AiPricingCardOverlay.tsx
18661
18725
  var import_jsx_runtime20 = require("react/jsx-runtime");
18662
18726
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
18663
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]";
@@ -23784,6 +23848,23 @@ function currentIconRef(el) {
23784
23848
  );
23785
23849
  return named ? `lucide:${named.slice("lucide-".length)}` : "";
23786
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
+ }
23787
23868
  function getEditMeasureEl(editable) {
23788
23869
  return editable.closest("[data-ohw-href-key]") ?? editable;
23789
23870
  }
@@ -27781,8 +27862,11 @@ function OhhwellsBridge() {
27781
27862
  display: none !important;
27782
27863
  }
27783
27864
  /* Body text (no item-action toolbar) \u2014 first click enters text edit \u2192 I-beam.
27784
- Exclude select-first nav/footer labels so grab/default on [data-ohw-href-key] can win. */
27785
- [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="link"]):not(:is([data-ohw-href-key] *)) {
27865
+ Exclude select-first nav/footer labels so grab/default on [data-ohw-href-key] can win.
27866
+ Icons are excluded too: a glyph is replaced by a click, never typed into (OHH-623),
27867
+ so it takes the picker's pointer below, not an I-beam \u2014 this blanket !important used
27868
+ to beat the stylesheet's own non-!important icon rule (OHH-718). */
27869
+ [data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]):not([data-ohw-editable="video"]):not([data-ohw-editable="link"]):not([data-ohw-editable="icon"]):not(:is([data-ohw-href-key] *)) {
27786
27870
  cursor: text !important;
27787
27871
  }
27788
27872
  /* Select-first items: default arrow; grab only when drag-start is allowed (data-ohw-can-drag). */
@@ -27803,6 +27887,7 @@ function OhhwellsBridge() {
27803
27887
  [data-ohw-editable="video"], [data-ohw-editable="video"] *,
27804
27888
  [data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
27805
27889
  [data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
27890
+ [data-ohw-editable="icon"], [data-ohw-editable="icon"] * { cursor: pointer !important; }
27806
27891
  /* A text/plain editable nested inside a media editable (e.g. a placeholder's fallback
27807
27892
  label inside its bg-image box) is its own click target, not the media picker \u2014 the
27808
27893
  blanket media cursor rule above would otherwise show pointer where a click actually
@@ -28132,10 +28217,14 @@ function OhhwellsBridge() {
28132
28217
  e.preventDefault();
28133
28218
  e.stopPropagation();
28134
28219
  aiSectionApiRef.current?.selectFromElement(editable);
28220
+ const aiIcon = editable.hasAttribute("data-ai-icon");
28221
+ const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28135
28222
  postToParentRef.current({
28136
28223
  type: "ow:icon-pick",
28137
28224
  key: editable.dataset.ohwKey ?? "",
28138
- current: currentIconRef(editable)
28225
+ current: currentIconRef(editable),
28226
+ ...aiIcon ? { iconMode: "name" } : {},
28227
+ ...featureIcon ? { options: [...PRICING_FEATURE_ICONS] } : {}
28139
28228
  });
28140
28229
  return;
28141
28230
  }
@@ -29397,15 +29486,39 @@ function OhhwellsBridge() {
29397
29486
  if (e.data?.type !== "ow:icon-markup") return;
29398
29487
  const { key, markup } = e.data;
29399
29488
  if (!key || typeof markup !== "string") return;
29400
- const targets = document.querySelectorAll(
29401
- `[data-ohw-key="${key}"][data-ohw-editable="icon"]`
29402
- );
29489
+ const targets = Array.from(
29490
+ document.querySelectorAll(`[data-ohw-key="${key}"][data-ohw-editable="icon"]`)
29491
+ ).filter((el) => !el.hasAttribute("data-ai-icon"));
29403
29492
  if (!targets.length) return;
29404
29493
  targets.forEach((el) => {
29405
29494
  applyIconMarkup(el, markup);
29406
29495
  });
29407
29496
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: markup }], flush: true });
29408
29497
  };
29498
+ const handleIconName = (e) => {
29499
+ if (e.data?.type !== "ow:icon-name") return;
29500
+ const { key, icon } = e.data;
29501
+ if (!key || typeof icon !== "string" || !/^[a-z0-9-]+$/.test(icon)) return;
29502
+ const state = parseAiSectionsState(aiSectionsRef.current);
29503
+ const entry = state.sections.find((s) => key.startsWith(aiSlotKeyPrefixFor(s.id)));
29504
+ if (!entry) return;
29505
+ const nextTree = applyIconNameToTree(entry.tree, key.slice(aiSlotKeyPrefixFor(entry.id).length), icon);
29506
+ if (!nextTree) return;
29507
+ void requestValidateTree5(postToParentRef.current, nextTree).then(({ ok }) => {
29508
+ if (!ok) return;
29509
+ applyTreeLocalRef.current?.({
29510
+ id: entry.id,
29511
+ mode: "replace",
29512
+ targetSectionId: entry.id,
29513
+ tree: nextTree,
29514
+ label: entry.label,
29515
+ media: entry.media,
29516
+ ...entry.brand ? { brand: entry.brand } : {},
29517
+ // The user is looking at the icon they just changed — no need to recenter on it.
29518
+ skipScrollIntoView: true
29519
+ });
29520
+ });
29521
+ };
29409
29522
  const handleImageUrl = (e) => {
29410
29523
  if (e.data?.type !== "ow:image-url") return;
29411
29524
  const { key, url } = e.data;
@@ -30861,6 +30974,7 @@ function OhhwellsBridge() {
30861
30974
  window.addEventListener("message", handleSocialCancel);
30862
30975
  window.addEventListener("message", handleSocialUpdate);
30863
30976
  window.addEventListener("message", handleIconMarkup);
30977
+ window.addEventListener("message", handleIconName);
30864
30978
  window.addEventListener("message", handleImageUrl);
30865
30979
  window.addEventListener("message", handleImageUploading);
30866
30980
  window.addEventListener("message", handleCarouselChange);
@@ -30945,6 +31059,7 @@ function OhhwellsBridge() {
30945
31059
  window.removeEventListener("message", handleSocialCancel);
30946
31060
  window.removeEventListener("message", handleSocialUpdate);
30947
31061
  window.removeEventListener("message", handleIconMarkup);
31062
+ window.removeEventListener("message", handleIconName);
30948
31063
  window.removeEventListener("message", handleImageUrl);
30949
31064
  window.removeEventListener("message", handleImageUploading);
30950
31065
  window.removeEventListener("message", handleCarouselChange);