@ohhwells/bridge 0.1.142 → 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.js CHANGED
@@ -4939,10 +4939,9 @@ function getLinkHref3(el) {
4939
4939
  }
4940
4940
  function collectEditableNodes(extraContent, root = document) {
4941
4941
  const isScoped = root !== document;
4942
- const editableEls = Array.from(
4943
- root.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="form"])')
4944
- );
4945
- if (isScoped && root instanceof HTMLElement && root.matches('[data-ohw-editable]:not([data-ohw-editable="form"])')) {
4942
+ const EDITABLE_SELECTOR = '[data-ohw-editable]:not([data-ohw-editable="form"]):not([data-ai-icon])';
4943
+ const editableEls = Array.from(root.querySelectorAll(EDITABLE_SELECTOR));
4944
+ if (isScoped && root instanceof HTMLElement && root.matches(EDITABLE_SELECTOR)) {
4946
4945
  editableEls.unshift(root);
4947
4946
  }
4948
4947
  const nodes = editableEls.map((el) => {
@@ -6209,6 +6208,281 @@ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE
6209
6208
  return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
6210
6209
  }
6211
6210
 
6211
+ // src/lib/ai-pricing-tree.ts
6212
+ var PRICING_MIN_CARDS = 1;
6213
+ var PRICING_MAX_CARDS = 4;
6214
+ function isPricingCard(node) {
6215
+ return typeof node === "object" && node !== null && node.type === "card" && node.slots?.preset === "pricing";
6216
+ }
6217
+ function isPricingCollection(node) {
6218
+ return node.type === "collection" && Array.isArray(node.children) && node.children.length > 0 && isPricingCard(node.children[0]);
6219
+ }
6220
+ function findCollection(node) {
6221
+ if (isPricingCollection(node)) return node;
6222
+ for (const child of node.children ?? []) {
6223
+ const found = findCollection(child);
6224
+ if (found) return found;
6225
+ }
6226
+ return null;
6227
+ }
6228
+ function findPricingCollection(tree) {
6229
+ for (const row of tree.rows ?? []) {
6230
+ for (const block of row.blocks ?? []) {
6231
+ const found = findCollection(block);
6232
+ if (found) return found;
6233
+ }
6234
+ }
6235
+ return null;
6236
+ }
6237
+ function pricingCardCount(tree) {
6238
+ return findPricingCollection(tree)?.children?.length ?? 0;
6239
+ }
6240
+ function withPricingCollection(tree, mutate) {
6241
+ const next = JSON.parse(JSON.stringify(tree));
6242
+ const collection = findPricingCollection(next);
6243
+ if (!collection || !collection.children) return null;
6244
+ for (const card of collection.children) {
6245
+ if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
6246
+ }
6247
+ return mutate(collection) ? next : null;
6248
+ }
6249
+ function upgradeLegacyPricingSlots(card) {
6250
+ const slots = card.slots = card.slots ?? {};
6251
+ if (typeof slots.features === "string") {
6252
+ const rows = pricingFeatureList(card);
6253
+ if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
6254
+ }
6255
+ if (slots.featured === true) slots.featured = { enabled: true };
6256
+ else if (slots.featured === false) delete slots.featured;
6257
+ }
6258
+ function addPricingTier(tree) {
6259
+ return withPricingCollection(tree, (collection) => {
6260
+ const children = collection.children;
6261
+ if (children.length >= PRICING_MAX_CARDS) return false;
6262
+ const clone = JSON.parse(JSON.stringify(children[children.length - 1]));
6263
+ clone.slots = clone.slots ?? {};
6264
+ delete clone.slots.featured;
6265
+ clone.slots.heading = "New tier";
6266
+ children.push(clone);
6267
+ collection.slots = { ...collection.slots, itemsPerRow: children.length };
6268
+ return true;
6269
+ });
6270
+ }
6271
+ function removePricingTier(tree, index) {
6272
+ return withPricingCollection(tree, (collection) => {
6273
+ const children = collection.children;
6274
+ if (children.length <= PRICING_MIN_CARDS) return false;
6275
+ if (index < 0 || index >= children.length) return false;
6276
+ children.splice(index, 1);
6277
+ collection.slots = { ...collection.slots, itemsPerRow: children.length };
6278
+ return true;
6279
+ });
6280
+ }
6281
+ var PRICING_CARD_STYLES = [
6282
+ { value: "outline", label: "Outline", treatment: null },
6283
+ { value: "lift", label: "Lift", treatment: "lifted" },
6284
+ { value: "invert", label: "Invert", treatment: "inverted" },
6285
+ { value: "header", label: "Header", treatment: "accent-header" }
6286
+ ];
6287
+ var DEFAULT_FEATURED_BADGE = "Most popular";
6288
+ function featuredOf(target) {
6289
+ const raw = target?.slots?.featured;
6290
+ if (raw === true) return { enabled: true, treatment: "outlined" };
6291
+ return typeof raw === "object" && raw !== null ? raw : {};
6292
+ }
6293
+ function badgeOf(featured) {
6294
+ return typeof featured.badge === "string" ? featured.badge.trim() : "";
6295
+ }
6296
+ function pricingCardStyle(tree, index) {
6297
+ const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
6298
+ if (featured.enabled !== true) return "outline";
6299
+ return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
6300
+ }
6301
+ function pricingCardFeaturedTag(tree, index) {
6302
+ const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
6303
+ return featured.enabled === true && badgeOf(featured).length > 0;
6304
+ }
6305
+ function composeFeatured(style, tagOn, badgeText) {
6306
+ const treatment = PRICING_CARD_STYLES.find((s) => s.value === style)?.treatment ?? null;
6307
+ if (tagOn) return { enabled: true, badge: badgeText || DEFAULT_FEATURED_BADGE, treatment: treatment ?? "outlined" };
6308
+ if (treatment) return { enabled: true, treatment };
6309
+ return badgeText.length > 0 ? { enabled: false, badge: badgeText } : { enabled: false };
6310
+ }
6311
+ function withPricingCard(tree, index, mutate) {
6312
+ return withPricingCollection(tree, (collection) => {
6313
+ const children = collection.children;
6314
+ if (index < 0 || index >= children.length) return false;
6315
+ const target = children[index];
6316
+ target.slots = target.slots ?? {};
6317
+ mutate(target);
6318
+ return true;
6319
+ });
6320
+ }
6321
+ function setPricingCardStyle(tree, index, style) {
6322
+ if (!PRICING_CARD_STYLES.some((s) => s.value === style)) return null;
6323
+ return withPricingCard(tree, index, (target) => {
6324
+ const prev = featuredOf(target);
6325
+ const tagOn = prev.enabled === true && badgeOf(prev).length > 0;
6326
+ target.slots.featured = composeFeatured(style, tagOn, badgeOf(prev));
6327
+ });
6328
+ }
6329
+ function setPricingCardFeaturedTag(tree, index, on) {
6330
+ return withPricingCollection(tree, (collection) => {
6331
+ const children = collection.children;
6332
+ if (index < 0 || index >= children.length) return false;
6333
+ if (on) {
6334
+ children.forEach((card, i) => {
6335
+ if (i === index) return;
6336
+ const prev2 = featuredOf(card);
6337
+ if (prev2.enabled !== true || badgeOf(prev2).length === 0) return;
6338
+ card.slots = card.slots ?? {};
6339
+ card.slots.featured = composeFeatured(pricingCardStyleOf(prev2), false, badgeOf(prev2));
6340
+ });
6341
+ }
6342
+ const target = children[index];
6343
+ target.slots = target.slots ?? {};
6344
+ const prev = featuredOf(target);
6345
+ target.slots.featured = composeFeatured(pricingCardStyleOf(prev), on, badgeOf(prev));
6346
+ return true;
6347
+ });
6348
+ }
6349
+ function pricingCardStyleOf(featured) {
6350
+ if (featured.enabled !== true) return "outline";
6351
+ return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
6352
+ }
6353
+ function addPricingCardButton(tree, index, label) {
6354
+ return withPricingCollection(tree, (collection) => {
6355
+ const children = collection.children;
6356
+ if (index < 0 || index >= children.length) return false;
6357
+ const target = children[index];
6358
+ const buttons = (target.children ?? []).filter((c) => c.type === "button");
6359
+ if (buttons.length >= 2) return false;
6360
+ const hasPrimary = buttons.some((b) => b.slots?.variant === "primary");
6361
+ target.children = [
6362
+ ...target.children ?? [],
6363
+ { type: "button", span: 12, slots: { label, href: "#", variant: hasPrimary ? "secondary" : "primary" } }
6364
+ ];
6365
+ return true;
6366
+ });
6367
+ }
6368
+ function removePricingCardButton(tree, index) {
6369
+ return withPricingCollection(tree, (collection) => {
6370
+ const children = collection.children;
6371
+ if (index < 0 || index >= children.length) return false;
6372
+ const target = children[index];
6373
+ const buttons = (target.children ?? []).filter((c) => c.type === "button");
6374
+ if (buttons.length === 0) return false;
6375
+ const lastButton = buttons[buttons.length - 1];
6376
+ target.children = (target.children ?? []).filter((c) => c !== lastButton);
6377
+ return true;
6378
+ });
6379
+ }
6380
+ var PRICING_FEATURES_MIN = 1;
6381
+ var PRICING_FEATURES_MAX = 10;
6382
+ var PRICING_FEATURE_ICONS = [
6383
+ "check",
6384
+ "check-circle",
6385
+ "x",
6386
+ "star",
6387
+ "zap",
6388
+ "shield",
6389
+ "clock",
6390
+ "users",
6391
+ "infinity"
6392
+ ];
6393
+ function pricingFeatureList(target) {
6394
+ const raw = target.slots?.features;
6395
+ if (Array.isArray(raw)) {
6396
+ return raw.filter((item) => !!item && typeof item === "object" && typeof item.text === "string").map((item) => ({
6397
+ text: item.text,
6398
+ ...typeof item.icon === "string" ? { icon: item.icon } : {},
6399
+ ...typeof item.iconColor === "string" ? { iconColor: item.iconColor } : {}
6400
+ }));
6401
+ }
6402
+ if (typeof raw === "string") {
6403
+ return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
6404
+ }
6405
+ return [];
6406
+ }
6407
+ function pricingCardFeatureCount(tree, index) {
6408
+ const target = findPricingCollection(tree)?.children?.[index];
6409
+ return target ? pricingFeatureList(target).length : 0;
6410
+ }
6411
+ function addPricingCardFeature(tree, index, text) {
6412
+ return withPricingCollection(tree, (collection) => {
6413
+ const children = collection.children;
6414
+ if (index < 0 || index >= children.length) return false;
6415
+ const target = children[index];
6416
+ const features = pricingFeatureList(target);
6417
+ if (features.length >= PRICING_FEATURES_MAX) return false;
6418
+ target.slots = target.slots ?? {};
6419
+ target.slots.features = [...features, { text }];
6420
+ return true;
6421
+ });
6422
+ }
6423
+ function setPricingCardFeatureIcon(tree, index, featureIndex, icon) {
6424
+ if (!PRICING_FEATURE_ICONS.includes(icon)) return null;
6425
+ return withPricingCollection(tree, (collection) => {
6426
+ const children = collection.children;
6427
+ if (index < 0 || index >= children.length) return false;
6428
+ const target = children[index];
6429
+ const features = pricingFeatureList(target);
6430
+ if (featureIndex < 0 || featureIndex >= features.length) return false;
6431
+ target.slots = target.slots ?? {};
6432
+ target.slots.features = features.map((row, i) => i === featureIndex ? { ...row, icon } : row);
6433
+ return true;
6434
+ });
6435
+ }
6436
+ function removePricingCardFeature(tree, index) {
6437
+ return withPricingCollection(tree, (collection) => {
6438
+ const children = collection.children;
6439
+ if (index < 0 || index >= children.length) return false;
6440
+ const target = children[index];
6441
+ const features = pricingFeatureList(target);
6442
+ if (features.length <= PRICING_FEATURES_MIN) return false;
6443
+ target.slots = target.slots ?? {};
6444
+ target.slots.features = features.slice(0, -1);
6445
+ return true;
6446
+ });
6447
+ }
6448
+
6449
+ // src/lib/ai-icon-edit.ts
6450
+ var FEATURE_ICON_PATH = /\.c(\d+)\.features\.(\d+)\.icon$/;
6451
+ var NODE_SEGMENT = /^[rbc]\d+$/;
6452
+ function nodeAtPath(tree, segments) {
6453
+ if (segments.length < 2 || segments.some((s) => !NODE_SEGMENT.test(s))) return null;
6454
+ if (!segments[0].startsWith("r") || !segments[1].startsWith("b")) return null;
6455
+ let node = tree.rows?.[Number(segments[0].slice(1))]?.blocks?.[Number(segments[1].slice(1))];
6456
+ for (const segment of segments.slice(2)) {
6457
+ if (!segment.startsWith("c")) return null;
6458
+ node = node?.children?.[Number(segment.slice(1))];
6459
+ }
6460
+ return node ?? null;
6461
+ }
6462
+ function applyIconNameToTree(tree, path, icon) {
6463
+ const feature = path.match(FEATURE_ICON_PATH);
6464
+ if (feature) {
6465
+ return setPricingCardFeatureIcon(tree, Number(feature[1]), Number(feature[2]), icon);
6466
+ }
6467
+ return rewriteIconSlot(tree, path, `lucide:${icon}`);
6468
+ }
6469
+ function applyIconUploadToTree(tree, path, mediaRef) {
6470
+ if (FEATURE_ICON_PATH.test(path)) return null;
6471
+ return rewriteIconSlot(tree, path, `icon:${mediaRef}`);
6472
+ }
6473
+ function rewriteIconSlot(tree, path, ref) {
6474
+ const segments = path.split(".");
6475
+ const slot = segments.pop();
6476
+ if (!slot || NODE_SEGMENT.test(slot)) return null;
6477
+ const next = JSON.parse(JSON.stringify(tree));
6478
+ const node = nodeAtPath(next, segments);
6479
+ if (!node) return null;
6480
+ const current = node.slots?.[slot];
6481
+ if (typeof current !== "string" || !/^(lucide|simple|icon):/.test(current)) return null;
6482
+ node.slots = { ...node.slots, [slot]: ref };
6483
+ return next;
6484
+ }
6485
+
6212
6486
  // src/lib/ai-footer-tree.ts
6213
6487
  var FOOTER_COLUMN_SPANS = [12, 6, 4, 3];
6214
6488
  var FOOTER_COLUMN_BLOCKS = [
@@ -7565,7 +7839,7 @@ function MediaBox({
7565
7839
  onError
7566
7840
  }) {
7567
7841
  const url = refValue ? ctx.resolveMedia(refValue) : null;
7568
- const isIcon = /^(lucide|simple):/.test(refValue);
7842
+ const isIcon = /^(lucide|simple|icon):/.test(refValue);
7569
7843
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
7570
7844
  const editAttrs = ctx.keyFor && editPath && !isIcon ? {
7571
7845
  "data-ohw-key": ctx.keyFor(editPath),
@@ -7573,10 +7847,13 @@ function MediaBox({
7573
7847
  } : {};
7574
7848
  if (isIcon) {
7575
7849
  const Icon = refValue.startsWith("lucide:") ? lucideByName(refValue.slice(7)) : null;
7850
+ const uploadedIconUrl = refValue.startsWith("icon:") ? ctx.resolveMedia(refValue.slice(5)) : null;
7851
+ const iconEditAttrs = ctx.keyFor && editPath && genericImageEdit ? { "data-ohw-key": ctx.keyFor(editPath), "data-ohw-editable": "icon" } : {};
7576
7852
  return /* @__PURE__ */ jsx(
7577
7853
  "span",
7578
7854
  {
7579
7855
  "data-ai-icon": refValue,
7856
+ ...iconEditAttrs,
7580
7857
  style: {
7581
7858
  display: "inline-flex",
7582
7859
  width: 48,
@@ -7585,7 +7862,7 @@ function MediaBox({
7585
7862
  justifyContent: "center",
7586
7863
  color: ctx.brand.palette.dark
7587
7864
  },
7588
- children: Icon ? /* @__PURE__ */ jsx(Icon, { size: 48, color: ctx.brand.palette.dark, strokeWidth: 1.5 }) : /* @__PURE__ */ jsx("span", { style: { fontSize: 24 }, children: "\u25C6" })
7865
+ children: uploadedIconUrl ? /* @__PURE__ */ jsx("img", { src: uploadedIconUrl, alt: "", style: { width: 48, height: 48, objectFit: "contain" } }) : Icon ? /* @__PURE__ */ jsx(Icon, { size: 48, color: ctx.brand.palette.dark, strokeWidth: 1.5 }) : /* @__PURE__ */ jsx("span", { style: { fontSize: 24 }, children: "\u25C6" })
7589
7866
  }
7590
7867
  );
7591
7868
  }
@@ -8073,8 +8350,20 @@ function PricingCard({ node, ctx, path }) {
8073
8350
  /* @__PURE__ */ 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) => {
8074
8351
  const Icon = lucideByName(item.icon || "check");
8075
8352
  const iconColor = pricingIconColor(item.iconColor, ctx.brand, fg);
8353
+ const iconAttrs = ctx.keyFor && !item.html ? {
8354
+ "data-ohw-key": ctx.keyFor(`${path}.features.${i}.icon`),
8355
+ "data-ohw-editable": "icon"
8356
+ } : {};
8076
8357
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "flex-start", gap: AI_TREE_TOKENS.spacing3 }, children: [
8077
- Icon && /* @__PURE__ */ jsx(Icon, { size: 20, color: iconColor }),
8358
+ Icon && /* @__PURE__ */ jsx(
8359
+ "span",
8360
+ {
8361
+ "data-ai-icon": `lucide:${item.icon || "check"}`,
8362
+ ...iconAttrs,
8363
+ style: { display: "inline-flex", flexShrink: 0 },
8364
+ children: /* @__PURE__ */ jsx(Icon, { size: 20, color: iconColor })
8365
+ }
8366
+ ),
8078
8367
  item.html ? /* @__PURE__ */ jsx(
8079
8368
  "span",
8080
8369
  {
@@ -18317,222 +18606,6 @@ function AiMediaStripOverlay({
18317
18606
  // src/ui/ai-section/AiPricingCardOverlay.tsx
18318
18607
  import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useState as useState9 } from "react";
18319
18608
  import { Check as Check2, ChevronDown as ChevronDown3, Minus, Plus as Plus2, Star as Star2 } from "lucide-react";
18320
-
18321
- // src/lib/ai-pricing-tree.ts
18322
- var PRICING_MIN_CARDS = 1;
18323
- var PRICING_MAX_CARDS = 4;
18324
- function isPricingCard(node) {
18325
- return typeof node === "object" && node !== null && node.type === "card" && node.slots?.preset === "pricing";
18326
- }
18327
- function isPricingCollection(node) {
18328
- return node.type === "collection" && Array.isArray(node.children) && node.children.length > 0 && isPricingCard(node.children[0]);
18329
- }
18330
- function findCollection(node) {
18331
- if (isPricingCollection(node)) return node;
18332
- for (const child of node.children ?? []) {
18333
- const found = findCollection(child);
18334
- if (found) return found;
18335
- }
18336
- return null;
18337
- }
18338
- function findPricingCollection(tree) {
18339
- for (const row of tree.rows ?? []) {
18340
- for (const block of row.blocks ?? []) {
18341
- const found = findCollection(block);
18342
- if (found) return found;
18343
- }
18344
- }
18345
- return null;
18346
- }
18347
- function pricingCardCount(tree) {
18348
- return findPricingCollection(tree)?.children?.length ?? 0;
18349
- }
18350
- function withPricingCollection(tree, mutate) {
18351
- const next = JSON.parse(JSON.stringify(tree));
18352
- const collection = findPricingCollection(next);
18353
- if (!collection || !collection.children) return null;
18354
- for (const card of collection.children) {
18355
- if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
18356
- }
18357
- return mutate(collection) ? next : null;
18358
- }
18359
- function upgradeLegacyPricingSlots(card) {
18360
- const slots = card.slots = card.slots ?? {};
18361
- if (typeof slots.features === "string") {
18362
- const rows = pricingFeatureList(card);
18363
- if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
18364
- }
18365
- if (slots.featured === true) slots.featured = { enabled: true };
18366
- else if (slots.featured === false) delete slots.featured;
18367
- }
18368
- function addPricingTier(tree) {
18369
- return withPricingCollection(tree, (collection) => {
18370
- const children = collection.children;
18371
- if (children.length >= PRICING_MAX_CARDS) return false;
18372
- const clone = JSON.parse(JSON.stringify(children[children.length - 1]));
18373
- clone.slots = clone.slots ?? {};
18374
- delete clone.slots.featured;
18375
- clone.slots.heading = "New tier";
18376
- children.push(clone);
18377
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18378
- return true;
18379
- });
18380
- }
18381
- function removePricingTier(tree, index) {
18382
- return withPricingCollection(tree, (collection) => {
18383
- const children = collection.children;
18384
- if (children.length <= PRICING_MIN_CARDS) return false;
18385
- if (index < 0 || index >= children.length) return false;
18386
- children.splice(index, 1);
18387
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18388
- return true;
18389
- });
18390
- }
18391
- var PRICING_CARD_STYLES = [
18392
- { value: "outline", label: "Outline", treatment: null },
18393
- { value: "lift", label: "Lift", treatment: "lifted" },
18394
- { value: "invert", label: "Invert", treatment: "inverted" },
18395
- { value: "header", label: "Header", treatment: "accent-header" }
18396
- ];
18397
- var DEFAULT_FEATURED_BADGE = "Most popular";
18398
- function featuredOf(target) {
18399
- const raw = target?.slots?.featured;
18400
- if (raw === true) return { enabled: true, treatment: "outlined" };
18401
- return typeof raw === "object" && raw !== null ? raw : {};
18402
- }
18403
- function badgeOf(featured) {
18404
- return typeof featured.badge === "string" ? featured.badge.trim() : "";
18405
- }
18406
- function pricingCardStyle(tree, index) {
18407
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18408
- if (featured.enabled !== true) return "outline";
18409
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18410
- }
18411
- function pricingCardFeaturedTag(tree, index) {
18412
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18413
- return featured.enabled === true && badgeOf(featured).length > 0;
18414
- }
18415
- function composeFeatured(style, tagOn, badgeText) {
18416
- const treatment = PRICING_CARD_STYLES.find((s) => s.value === style)?.treatment ?? null;
18417
- if (tagOn) return { enabled: true, badge: badgeText || DEFAULT_FEATURED_BADGE, treatment: treatment ?? "outlined" };
18418
- if (treatment) return { enabled: true, treatment };
18419
- return badgeText.length > 0 ? { enabled: false, badge: badgeText } : { enabled: false };
18420
- }
18421
- function withPricingCard(tree, index, mutate) {
18422
- return withPricingCollection(tree, (collection) => {
18423
- const children = collection.children;
18424
- if (index < 0 || index >= children.length) return false;
18425
- const target = children[index];
18426
- target.slots = target.slots ?? {};
18427
- mutate(target);
18428
- return true;
18429
- });
18430
- }
18431
- function setPricingCardStyle(tree, index, style) {
18432
- if (!PRICING_CARD_STYLES.some((s) => s.value === style)) return null;
18433
- return withPricingCard(tree, index, (target) => {
18434
- const prev = featuredOf(target);
18435
- const tagOn = prev.enabled === true && badgeOf(prev).length > 0;
18436
- target.slots.featured = composeFeatured(style, tagOn, badgeOf(prev));
18437
- });
18438
- }
18439
- function setPricingCardFeaturedTag(tree, index, on) {
18440
- return withPricingCollection(tree, (collection) => {
18441
- const children = collection.children;
18442
- if (index < 0 || index >= children.length) return false;
18443
- if (on) {
18444
- children.forEach((card, i) => {
18445
- if (i === index) return;
18446
- const prev2 = featuredOf(card);
18447
- if (prev2.enabled !== true || badgeOf(prev2).length === 0) return;
18448
- card.slots = card.slots ?? {};
18449
- card.slots.featured = composeFeatured(pricingCardStyleOf(prev2), false, badgeOf(prev2));
18450
- });
18451
- }
18452
- const target = children[index];
18453
- target.slots = target.slots ?? {};
18454
- const prev = featuredOf(target);
18455
- target.slots.featured = composeFeatured(pricingCardStyleOf(prev), on, badgeOf(prev));
18456
- return true;
18457
- });
18458
- }
18459
- function pricingCardStyleOf(featured) {
18460
- if (featured.enabled !== true) return "outline";
18461
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18462
- }
18463
- function addPricingCardButton(tree, index, label) {
18464
- return withPricingCollection(tree, (collection) => {
18465
- const children = collection.children;
18466
- if (index < 0 || index >= children.length) return false;
18467
- const target = children[index];
18468
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18469
- if (buttons.length >= 2) return false;
18470
- const hasPrimary = buttons.some((b) => b.slots?.variant === "primary");
18471
- target.children = [
18472
- ...target.children ?? [],
18473
- { type: "button", span: 12, slots: { label, href: "#", variant: hasPrimary ? "secondary" : "primary" } }
18474
- ];
18475
- return true;
18476
- });
18477
- }
18478
- function removePricingCardButton(tree, index) {
18479
- return withPricingCollection(tree, (collection) => {
18480
- const children = collection.children;
18481
- if (index < 0 || index >= children.length) return false;
18482
- const target = children[index];
18483
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18484
- if (buttons.length === 0) return false;
18485
- const lastButton = buttons[buttons.length - 1];
18486
- target.children = (target.children ?? []).filter((c) => c !== lastButton);
18487
- return true;
18488
- });
18489
- }
18490
- var PRICING_FEATURES_MIN = 1;
18491
- var PRICING_FEATURES_MAX = 10;
18492
- function pricingFeatureList(target) {
18493
- const raw = target.slots?.features;
18494
- if (Array.isArray(raw)) {
18495
- return raw.filter((item) => !!item && typeof item === "object" && typeof item.text === "string").map((item) => ({
18496
- text: item.text,
18497
- ...typeof item.icon === "string" ? { icon: item.icon } : {},
18498
- ...typeof item.iconColor === "string" ? { iconColor: item.iconColor } : {}
18499
- }));
18500
- }
18501
- if (typeof raw === "string") {
18502
- return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
18503
- }
18504
- return [];
18505
- }
18506
- function pricingCardFeatureCount(tree, index) {
18507
- const target = findPricingCollection(tree)?.children?.[index];
18508
- return target ? pricingFeatureList(target).length : 0;
18509
- }
18510
- function addPricingCardFeature(tree, index, text) {
18511
- return withPricingCollection(tree, (collection) => {
18512
- const children = collection.children;
18513
- if (index < 0 || index >= children.length) return false;
18514
- const target = children[index];
18515
- const features = pricingFeatureList(target);
18516
- if (features.length >= PRICING_FEATURES_MAX) return false;
18517
- target.slots = target.slots ?? {};
18518
- target.slots.features = [...features, { text }];
18519
- return true;
18520
- });
18521
- }
18522
- function removePricingCardFeature(tree, index) {
18523
- return withPricingCollection(tree, (collection) => {
18524
- const children = collection.children;
18525
- if (index < 0 || index >= children.length) return false;
18526
- const target = children[index];
18527
- const features = pricingFeatureList(target);
18528
- if (features.length <= PRICING_FEATURES_MIN) return false;
18529
- target.slots = target.slots ?? {};
18530
- target.slots.features = features.slice(0, -1);
18531
- return true;
18532
- });
18533
- }
18534
-
18535
- // src/ui/ai-section/AiPricingCardOverlay.tsx
18536
18609
  import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
18537
18610
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
18538
18611
  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]";
@@ -23664,6 +23737,23 @@ function currentIconRef(el) {
23664
23737
  );
23665
23738
  return named ? `lucide:${named.slice("lucide-".length)}` : "";
23666
23739
  }
23740
+ function requestValidateTree5(postToParent2, tree) {
23741
+ return new Promise((resolve) => {
23742
+ const id = `ai-validate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
23743
+ const timeout = window.setTimeout(() => {
23744
+ window.removeEventListener("message", onMessage);
23745
+ resolve({ ok: false, reasons: ["Validation request timed out."] });
23746
+ }, 8e3);
23747
+ function onMessage(e) {
23748
+ if (e.data?.type !== "ow:ai-validate-tree-result" || e.data.id !== id) return;
23749
+ window.clearTimeout(timeout);
23750
+ window.removeEventListener("message", onMessage);
23751
+ resolve({ ok: e.data.ok === true, reasons: Array.isArray(e.data.reasons) ? e.data.reasons : [] });
23752
+ }
23753
+ window.addEventListener("message", onMessage);
23754
+ postToParent2({ type: "ow:ai-validate-tree-request", id, tree });
23755
+ });
23756
+ }
23667
23757
  function getEditMeasureEl(editable) {
23668
23758
  return editable.closest("[data-ohw-href-key]") ?? editable;
23669
23759
  }
@@ -28015,10 +28105,14 @@ function OhhwellsBridge() {
28015
28105
  e.preventDefault();
28016
28106
  e.stopPropagation();
28017
28107
  aiSectionApiRef.current?.selectFromElement(editable);
28108
+ const aiIcon = editable.hasAttribute("data-ai-icon");
28109
+ const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28018
28110
  postToParentRef.current({
28019
28111
  type: "ow:icon-pick",
28020
28112
  key: editable.dataset.ohwKey ?? "",
28021
- current: currentIconRef(editable)
28113
+ current: currentIconRef(editable),
28114
+ ...aiIcon ? { iconMode: "name" } : {},
28115
+ ...featureIcon ? { options: [...PRICING_FEATURE_ICONS] } : {}
28022
28116
  });
28023
28117
  return;
28024
28118
  }
@@ -29265,15 +29359,43 @@ function OhhwellsBridge() {
29265
29359
  if (e.data?.type !== "ow:icon-markup") return;
29266
29360
  const { key, markup } = e.data;
29267
29361
  if (!key || typeof markup !== "string") return;
29268
- const targets = document.querySelectorAll(
29269
- `[data-ohw-key="${key}"][data-ohw-editable="icon"]`
29270
- );
29362
+ const targets = Array.from(
29363
+ document.querySelectorAll(`[data-ohw-key="${key}"][data-ohw-editable="icon"]`)
29364
+ ).filter((el) => !el.hasAttribute("data-ai-icon"));
29271
29365
  if (!targets.length) return;
29272
29366
  targets.forEach((el) => {
29273
29367
  applyIconMarkup(el, markup);
29274
29368
  });
29275
29369
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: markup }], flush: true });
29276
29370
  };
29371
+ const handleIconName = (e) => {
29372
+ if (e.data?.type !== "ow:icon-name") return;
29373
+ const { key, icon, url } = e.data;
29374
+ const isName = typeof icon === "string" && /^[a-z0-9-]+$/.test(icon);
29375
+ const isUpload = !isName && typeof url === "string" && /^https?:\/\/\S+$/i.test(url);
29376
+ if (!key || !isName && !isUpload) return;
29377
+ const state = parseAiSectionsState(aiSectionsRef.current);
29378
+ const entry = state.sections.find((s) => key.startsWith(aiSlotKeyPrefixFor(s.id)));
29379
+ if (!entry) return;
29380
+ const path = key.slice(aiSlotKeyPrefixFor(entry.id).length);
29381
+ const uploadRef = isUpload ? `icon-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}` : null;
29382
+ const nextTree = uploadRef ? applyIconUploadToTree(entry.tree, path, uploadRef) : applyIconNameToTree(entry.tree, path, icon);
29383
+ if (!nextTree) return;
29384
+ void requestValidateTree5(postToParentRef.current, nextTree).then(({ ok }) => {
29385
+ if (!ok) return;
29386
+ applyTreeLocalRef.current?.({
29387
+ id: entry.id,
29388
+ mode: "replace",
29389
+ targetSectionId: entry.id,
29390
+ tree: nextTree,
29391
+ label: entry.label,
29392
+ media: uploadRef ? { ...entry.media, [uploadRef]: url } : entry.media,
29393
+ ...entry.brand ? { brand: entry.brand } : {},
29394
+ // The user is looking at the icon they just changed — no need to recenter on it.
29395
+ skipScrollIntoView: true
29396
+ });
29397
+ });
29398
+ };
29277
29399
  const handleImageUrl = (e) => {
29278
29400
  if (e.data?.type !== "ow:image-url") return;
29279
29401
  const { key, url } = e.data;
@@ -30722,6 +30844,7 @@ function OhhwellsBridge() {
30722
30844
  window.addEventListener("message", handleSocialCancel);
30723
30845
  window.addEventListener("message", handleSocialUpdate);
30724
30846
  window.addEventListener("message", handleIconMarkup);
30847
+ window.addEventListener("message", handleIconName);
30725
30848
  window.addEventListener("message", handleImageUrl);
30726
30849
  window.addEventListener("message", handleImageUploading);
30727
30850
  window.addEventListener("message", handleCarouselChange);
@@ -30806,6 +30929,7 @@ function OhhwellsBridge() {
30806
30929
  window.removeEventListener("message", handleSocialCancel);
30807
30930
  window.removeEventListener("message", handleSocialUpdate);
30808
30931
  window.removeEventListener("message", handleIconMarkup);
30932
+ window.removeEventListener("message", handleIconName);
30809
30933
  window.removeEventListener("message", handleImageUrl);
30810
30934
  window.removeEventListener("message", handleImageUploading);
30811
30935
  window.removeEventListener("message", handleCarouselChange);
@@ -31131,7 +31255,7 @@ function OhhwellsBridge() {
31131
31255
  postToParent2({
31132
31256
  type: "ow:ready",
31133
31257
  version: "1",
31134
- bridgeVersion: "0.1.141",
31258
+ bridgeVersion: "0.1.142",
31135
31259
  path: pathname,
31136
31260
  nodes: collectEditableNodes(editContentRef.current),
31137
31261
  sections