@ohhwells/bridge 0.1.141 → 0.1.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1256,15 +1256,25 @@ function readAssignments(content) {
1256
1256
  function cssLength(style, prop) {
1257
1257
  return style.getPropertyValue(prop) || "0px";
1258
1258
  }
1259
+ function withTransitionsSettled(el, read) {
1260
+ const prior = el.style.getPropertyValue("transition");
1261
+ const priority = el.style.getPropertyPriority("transition");
1262
+ el.style.setProperty("transition", "none", "important");
1263
+ try {
1264
+ return read(getComputedStyle(el));
1265
+ } finally {
1266
+ if (prior) el.style.setProperty("transition", prior, priority);
1267
+ else el.style.removeProperty("transition");
1268
+ }
1269
+ }
1259
1270
  function readVariantSample(el) {
1260
- const style = getComputedStyle(el);
1261
- return {
1271
+ return withTransitionsSettled(el, (style) => ({
1262
1272
  background: style.backgroundColor,
1263
1273
  color: style.color,
1264
1274
  borderColor: style.borderTopColor,
1265
1275
  borderWidth: style.borderTopWidth,
1266
1276
  className: el.className || ""
1267
- };
1277
+ }));
1268
1278
  }
1269
1279
  function readSizeSample(el) {
1270
1280
  const style = getComputedStyle(el);
@@ -4929,10 +4939,9 @@ function getLinkHref3(el) {
4929
4939
  }
4930
4940
  function collectEditableNodes(extraContent, root = document) {
4931
4941
  const isScoped = root !== document;
4932
- const editableEls = Array.from(
4933
- root.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="form"])')
4934
- );
4935
- 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)) {
4936
4945
  editableEls.unshift(root);
4937
4946
  }
4938
4947
  const nodes = editableEls.map((el) => {
@@ -6199,6 +6208,281 @@ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE
6199
6208
  return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
6200
6209
  }
6201
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
+
6202
6486
  // src/lib/ai-footer-tree.ts
6203
6487
  var FOOTER_COLUMN_SPANS = [12, 6, 4, 3];
6204
6488
  var FOOTER_COLUMN_BLOCKS = [
@@ -7555,7 +7839,7 @@ function MediaBox({
7555
7839
  onError
7556
7840
  }) {
7557
7841
  const url = refValue ? ctx.resolveMedia(refValue) : null;
7558
- const isIcon = /^(lucide|simple):/.test(refValue);
7842
+ const isIcon = /^(lucide|simple|icon):/.test(refValue);
7559
7843
  const aspectRatio = aspect && /^\d+:\d+$/.test(aspect) ? aspect.replace(":", " / ") : void 0;
7560
7844
  const editAttrs = ctx.keyFor && editPath && !isIcon ? {
7561
7845
  "data-ohw-key": ctx.keyFor(editPath),
@@ -7563,10 +7847,13 @@ function MediaBox({
7563
7847
  } : {};
7564
7848
  if (isIcon) {
7565
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" } : {};
7566
7852
  return /* @__PURE__ */ jsx(
7567
7853
  "span",
7568
7854
  {
7569
7855
  "data-ai-icon": refValue,
7856
+ ...iconEditAttrs,
7570
7857
  style: {
7571
7858
  display: "inline-flex",
7572
7859
  width: 48,
@@ -7575,7 +7862,7 @@ function MediaBox({
7575
7862
  justifyContent: "center",
7576
7863
  color: ctx.brand.palette.dark
7577
7864
  },
7578
- 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" })
7579
7866
  }
7580
7867
  );
7581
7868
  }
@@ -8063,8 +8350,20 @@ function PricingCard({ node, ctx, path }) {
8063
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) => {
8064
8351
  const Icon = lucideByName(item.icon || "check");
8065
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
+ } : {};
8066
8357
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "flex-start", gap: AI_TREE_TOKENS.spacing3 }, children: [
8067
- 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
+ ),
8068
8367
  item.html ? /* @__PURE__ */ jsx(
8069
8368
  "span",
8070
8369
  {
@@ -10187,38 +10486,39 @@ function deriveTemplateButtonStyle() {
10187
10486
  el.querySelectorAll("[data-ohw-editable]").length <= 1
10188
10487
  );
10189
10488
  const isFilled = (el) => {
10190
- const bg = getComputedStyle(el).backgroundColor;
10489
+ const bg = withTransitionsSettled(el, (s) => s.backgroundColor);
10191
10490
  if (!bg || bg === "transparent") return false;
10192
10491
  const alpha = bg.match(/rgba?\([^)]*,\s*([\d.]+)\)$/);
10193
10492
  return !alpha || parseFloat(alpha[1]) > 0;
10194
10493
  };
10195
10494
  const btn = candidates.find(isFilled) ?? candidates[0];
10196
10495
  if (!btn) return null;
10197
- const cs = getComputedStyle(btn);
10198
- const filled = isFilled(btn);
10199
- const corners = [
10200
- cs.borderTopLeftRadius,
10201
- cs.borderTopRightRadius,
10202
- cs.borderBottomRightRadius,
10203
- cs.borderBottomLeftRadius
10204
- ].map((v) => v || "0px");
10205
- const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
10206
- const px2 = (v) => parseFloat(v) || 0;
10207
- const lineHeight = px2(cs.lineHeight) || px2(cs.fontSize) * 1.2;
10208
- const contentH = btn.getBoundingClientRect().height - px2(cs.borderTopWidth) - px2(cs.borderBottomWidth);
10209
- const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
10210
- const padY = Math.max(px2(cs.paddingTop), px2(cs.paddingBottom), impliedY);
10211
- const padX = Math.max(px2(cs.paddingLeft), px2(cs.paddingRight));
10212
- return {
10213
- radius: radius || "10px",
10214
- padding: `${padY}px ${padX}px`,
10215
- ...filled ? { background: cs.backgroundColor, color: cs.color } : {},
10216
- fontFamily: cs.fontFamily || "",
10217
- fontSize: cs.fontSize || "",
10218
- fontWeight: cs.fontWeight || "",
10219
- letterSpacing: cs.letterSpacing || "",
10220
- textTransform: cs.textTransform || ""
10221
- };
10496
+ return withTransitionsSettled(btn, (cs) => {
10497
+ const filled = isFilled(btn);
10498
+ const corners = [
10499
+ cs.borderTopLeftRadius,
10500
+ cs.borderTopRightRadius,
10501
+ cs.borderBottomRightRadius,
10502
+ cs.borderBottomLeftRadius
10503
+ ].map((v) => v || "0px");
10504
+ const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
10505
+ const px2 = (v) => parseFloat(v) || 0;
10506
+ const lineHeight = px2(cs.lineHeight) || px2(cs.fontSize) * 1.2;
10507
+ const contentH = btn.getBoundingClientRect().height - px2(cs.borderTopWidth) - px2(cs.borderBottomWidth);
10508
+ const impliedY = Math.round(Math.max(0, (contentH - lineHeight) / 2));
10509
+ const padY = Math.max(px2(cs.paddingTop), px2(cs.paddingBottom), impliedY);
10510
+ const padX = Math.max(px2(cs.paddingLeft), px2(cs.paddingRight));
10511
+ return {
10512
+ radius: radius || "10px",
10513
+ padding: `${padY}px ${padX}px`,
10514
+ ...filled ? { background: cs.backgroundColor, color: cs.color } : {},
10515
+ fontFamily: cs.fontFamily || "",
10516
+ fontSize: cs.fontSize || "",
10517
+ fontWeight: cs.fontWeight || "",
10518
+ letterSpacing: cs.letterSpacing || "",
10519
+ textTransform: cs.textTransform || ""
10520
+ };
10521
+ });
10222
10522
  }
10223
10523
  var mounted = /* @__PURE__ */ new Map();
10224
10524
  function findTemplateSection(id) {
@@ -18306,222 +18606,6 @@ function AiMediaStripOverlay({
18306
18606
  // src/ui/ai-section/AiPricingCardOverlay.tsx
18307
18607
  import { useCallback as useCallback4, useEffect as useEffect7, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useState as useState9 } from "react";
18308
18608
  import { Check as Check2, ChevronDown as ChevronDown3, Minus, Plus as Plus2, Star as Star2 } from "lucide-react";
18309
-
18310
- // src/lib/ai-pricing-tree.ts
18311
- var PRICING_MIN_CARDS = 1;
18312
- var PRICING_MAX_CARDS = 4;
18313
- function isPricingCard(node) {
18314
- return typeof node === "object" && node !== null && node.type === "card" && node.slots?.preset === "pricing";
18315
- }
18316
- function isPricingCollection(node) {
18317
- return node.type === "collection" && Array.isArray(node.children) && node.children.length > 0 && isPricingCard(node.children[0]);
18318
- }
18319
- function findCollection(node) {
18320
- if (isPricingCollection(node)) return node;
18321
- for (const child of node.children ?? []) {
18322
- const found = findCollection(child);
18323
- if (found) return found;
18324
- }
18325
- return null;
18326
- }
18327
- function findPricingCollection(tree) {
18328
- for (const row of tree.rows ?? []) {
18329
- for (const block of row.blocks ?? []) {
18330
- const found = findCollection(block);
18331
- if (found) return found;
18332
- }
18333
- }
18334
- return null;
18335
- }
18336
- function pricingCardCount(tree) {
18337
- return findPricingCollection(tree)?.children?.length ?? 0;
18338
- }
18339
- function withPricingCollection(tree, mutate) {
18340
- const next = JSON.parse(JSON.stringify(tree));
18341
- const collection = findPricingCollection(next);
18342
- if (!collection || !collection.children) return null;
18343
- for (const card of collection.children) {
18344
- if (isPricingCard(card)) upgradeLegacyPricingSlots(card);
18345
- }
18346
- return mutate(collection) ? next : null;
18347
- }
18348
- function upgradeLegacyPricingSlots(card) {
18349
- const slots = card.slots = card.slots ?? {};
18350
- if (typeof slots.features === "string") {
18351
- const rows = pricingFeatureList(card);
18352
- if (rows.length > 0) slots.features = rows.slice(0, PRICING_FEATURES_MAX);
18353
- }
18354
- if (slots.featured === true) slots.featured = { enabled: true };
18355
- else if (slots.featured === false) delete slots.featured;
18356
- }
18357
- function addPricingTier(tree) {
18358
- return withPricingCollection(tree, (collection) => {
18359
- const children = collection.children;
18360
- if (children.length >= PRICING_MAX_CARDS) return false;
18361
- const clone = JSON.parse(JSON.stringify(children[children.length - 1]));
18362
- clone.slots = clone.slots ?? {};
18363
- delete clone.slots.featured;
18364
- clone.slots.heading = "New tier";
18365
- children.push(clone);
18366
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18367
- return true;
18368
- });
18369
- }
18370
- function removePricingTier(tree, index) {
18371
- return withPricingCollection(tree, (collection) => {
18372
- const children = collection.children;
18373
- if (children.length <= PRICING_MIN_CARDS) return false;
18374
- if (index < 0 || index >= children.length) return false;
18375
- children.splice(index, 1);
18376
- collection.slots = { ...collection.slots, itemsPerRow: children.length };
18377
- return true;
18378
- });
18379
- }
18380
- var PRICING_CARD_STYLES = [
18381
- { value: "outline", label: "Outline", treatment: null },
18382
- { value: "lift", label: "Lift", treatment: "lifted" },
18383
- { value: "invert", label: "Invert", treatment: "inverted" },
18384
- { value: "header", label: "Header", treatment: "accent-header" }
18385
- ];
18386
- var DEFAULT_FEATURED_BADGE = "Most popular";
18387
- function featuredOf(target) {
18388
- const raw = target?.slots?.featured;
18389
- if (raw === true) return { enabled: true, treatment: "outlined" };
18390
- return typeof raw === "object" && raw !== null ? raw : {};
18391
- }
18392
- function badgeOf(featured) {
18393
- return typeof featured.badge === "string" ? featured.badge.trim() : "";
18394
- }
18395
- function pricingCardStyle(tree, index) {
18396
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18397
- if (featured.enabled !== true) return "outline";
18398
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18399
- }
18400
- function pricingCardFeaturedTag(tree, index) {
18401
- const featured = featuredOf(findPricingCollection(tree)?.children?.[index]);
18402
- return featured.enabled === true && badgeOf(featured).length > 0;
18403
- }
18404
- function composeFeatured(style, tagOn, badgeText) {
18405
- const treatment = PRICING_CARD_STYLES.find((s) => s.value === style)?.treatment ?? null;
18406
- if (tagOn) return { enabled: true, badge: badgeText || DEFAULT_FEATURED_BADGE, treatment: treatment ?? "outlined" };
18407
- if (treatment) return { enabled: true, treatment };
18408
- return badgeText.length > 0 ? { enabled: false, badge: badgeText } : { enabled: false };
18409
- }
18410
- function withPricingCard(tree, index, mutate) {
18411
- return withPricingCollection(tree, (collection) => {
18412
- const children = collection.children;
18413
- if (index < 0 || index >= children.length) return false;
18414
- const target = children[index];
18415
- target.slots = target.slots ?? {};
18416
- mutate(target);
18417
- return true;
18418
- });
18419
- }
18420
- function setPricingCardStyle(tree, index, style) {
18421
- if (!PRICING_CARD_STYLES.some((s) => s.value === style)) return null;
18422
- return withPricingCard(tree, index, (target) => {
18423
- const prev = featuredOf(target);
18424
- const tagOn = prev.enabled === true && badgeOf(prev).length > 0;
18425
- target.slots.featured = composeFeatured(style, tagOn, badgeOf(prev));
18426
- });
18427
- }
18428
- function setPricingCardFeaturedTag(tree, index, on) {
18429
- return withPricingCollection(tree, (collection) => {
18430
- const children = collection.children;
18431
- if (index < 0 || index >= children.length) return false;
18432
- if (on) {
18433
- children.forEach((card, i) => {
18434
- if (i === index) return;
18435
- const prev2 = featuredOf(card);
18436
- if (prev2.enabled !== true || badgeOf(prev2).length === 0) return;
18437
- card.slots = card.slots ?? {};
18438
- card.slots.featured = composeFeatured(pricingCardStyleOf(prev2), false, badgeOf(prev2));
18439
- });
18440
- }
18441
- const target = children[index];
18442
- target.slots = target.slots ?? {};
18443
- const prev = featuredOf(target);
18444
- target.slots.featured = composeFeatured(pricingCardStyleOf(prev), on, badgeOf(prev));
18445
- return true;
18446
- });
18447
- }
18448
- function pricingCardStyleOf(featured) {
18449
- if (featured.enabled !== true) return "outline";
18450
- return PRICING_CARD_STYLES.find((s) => s.treatment === featured.treatment)?.value ?? "outline";
18451
- }
18452
- function addPricingCardButton(tree, index, label) {
18453
- return withPricingCollection(tree, (collection) => {
18454
- const children = collection.children;
18455
- if (index < 0 || index >= children.length) return false;
18456
- const target = children[index];
18457
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18458
- if (buttons.length >= 2) return false;
18459
- const hasPrimary = buttons.some((b) => b.slots?.variant === "primary");
18460
- target.children = [
18461
- ...target.children ?? [],
18462
- { type: "button", span: 12, slots: { label, href: "#", variant: hasPrimary ? "secondary" : "primary" } }
18463
- ];
18464
- return true;
18465
- });
18466
- }
18467
- function removePricingCardButton(tree, index) {
18468
- return withPricingCollection(tree, (collection) => {
18469
- const children = collection.children;
18470
- if (index < 0 || index >= children.length) return false;
18471
- const target = children[index];
18472
- const buttons = (target.children ?? []).filter((c) => c.type === "button");
18473
- if (buttons.length === 0) return false;
18474
- const lastButton = buttons[buttons.length - 1];
18475
- target.children = (target.children ?? []).filter((c) => c !== lastButton);
18476
- return true;
18477
- });
18478
- }
18479
- var PRICING_FEATURES_MIN = 1;
18480
- var PRICING_FEATURES_MAX = 10;
18481
- function pricingFeatureList(target) {
18482
- const raw = target.slots?.features;
18483
- if (Array.isArray(raw)) {
18484
- return raw.filter((item) => !!item && typeof item === "object" && typeof item.text === "string").map((item) => ({
18485
- text: item.text,
18486
- ...typeof item.icon === "string" ? { icon: item.icon } : {},
18487
- ...typeof item.iconColor === "string" ? { iconColor: item.iconColor } : {}
18488
- }));
18489
- }
18490
- if (typeof raw === "string") {
18491
- return raw.split(/\n|<br\s*\/?>/i).map((line) => line.replace(/<[^>]+>/g, "").replace(/^[-*•]\s*/, "").trim().slice(0, 120)).filter(Boolean).map((text) => ({ text }));
18492
- }
18493
- return [];
18494
- }
18495
- function pricingCardFeatureCount(tree, index) {
18496
- const target = findPricingCollection(tree)?.children?.[index];
18497
- return target ? pricingFeatureList(target).length : 0;
18498
- }
18499
- function addPricingCardFeature(tree, index, text) {
18500
- return withPricingCollection(tree, (collection) => {
18501
- const children = collection.children;
18502
- if (index < 0 || index >= children.length) return false;
18503
- const target = children[index];
18504
- const features = pricingFeatureList(target);
18505
- if (features.length >= PRICING_FEATURES_MAX) return false;
18506
- target.slots = target.slots ?? {};
18507
- target.slots.features = [...features, { text }];
18508
- return true;
18509
- });
18510
- }
18511
- function removePricingCardFeature(tree, index) {
18512
- return withPricingCollection(tree, (collection) => {
18513
- const children = collection.children;
18514
- if (index < 0 || index >= children.length) return false;
18515
- const target = children[index];
18516
- const features = pricingFeatureList(target);
18517
- if (features.length <= PRICING_FEATURES_MIN) return false;
18518
- target.slots = target.slots ?? {};
18519
- target.slots.features = features.slice(0, -1);
18520
- return true;
18521
- });
18522
- }
18523
-
18524
- // src/ui/ai-section/AiPricingCardOverlay.tsx
18525
18609
  import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
18526
18610
  var PRIMARY4 = "var(--ohw-primary, #0885FE)";
18527
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]";
@@ -23653,6 +23737,23 @@ function currentIconRef(el) {
23653
23737
  );
23654
23738
  return named ? `lucide:${named.slice("lucide-".length)}` : "";
23655
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
+ }
23656
23757
  function getEditMeasureEl(editable) {
23657
23758
  return editable.closest("[data-ohw-href-key]") ?? editable;
23658
23759
  }
@@ -28004,10 +28105,14 @@ function OhhwellsBridge() {
28004
28105
  e.preventDefault();
28005
28106
  e.stopPropagation();
28006
28107
  aiSectionApiRef.current?.selectFromElement(editable);
28108
+ const aiIcon = editable.hasAttribute("data-ai-icon");
28109
+ const featureIcon = aiIcon && /\.features\.\d+\.icon$/.test(editable.dataset.ohwKey ?? "");
28007
28110
  postToParentRef.current({
28008
28111
  type: "ow:icon-pick",
28009
28112
  key: editable.dataset.ohwKey ?? "",
28010
- current: currentIconRef(editable)
28113
+ current: currentIconRef(editable),
28114
+ ...aiIcon ? { iconMode: "name" } : {},
28115
+ ...featureIcon ? { options: [...PRICING_FEATURE_ICONS] } : {}
28011
28116
  });
28012
28117
  return;
28013
28118
  }
@@ -29254,15 +29359,43 @@ function OhhwellsBridge() {
29254
29359
  if (e.data?.type !== "ow:icon-markup") return;
29255
29360
  const { key, markup } = e.data;
29256
29361
  if (!key || typeof markup !== "string") return;
29257
- const targets = document.querySelectorAll(
29258
- `[data-ohw-key="${key}"][data-ohw-editable="icon"]`
29259
- );
29362
+ const targets = Array.from(
29363
+ document.querySelectorAll(`[data-ohw-key="${key}"][data-ohw-editable="icon"]`)
29364
+ ).filter((el) => !el.hasAttribute("data-ai-icon"));
29260
29365
  if (!targets.length) return;
29261
29366
  targets.forEach((el) => {
29262
29367
  applyIconMarkup(el, markup);
29263
29368
  });
29264
29369
  postToParentRef.current({ type: "ow:change", nodes: [{ key, text: markup }], flush: true });
29265
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
+ };
29266
29399
  const handleImageUrl = (e) => {
29267
29400
  if (e.data?.type !== "ow:image-url") return;
29268
29401
  const { key, url } = e.data;
@@ -30711,6 +30844,7 @@ function OhhwellsBridge() {
30711
30844
  window.addEventListener("message", handleSocialCancel);
30712
30845
  window.addEventListener("message", handleSocialUpdate);
30713
30846
  window.addEventListener("message", handleIconMarkup);
30847
+ window.addEventListener("message", handleIconName);
30714
30848
  window.addEventListener("message", handleImageUrl);
30715
30849
  window.addEventListener("message", handleImageUploading);
30716
30850
  window.addEventListener("message", handleCarouselChange);
@@ -30795,6 +30929,7 @@ function OhhwellsBridge() {
30795
30929
  window.removeEventListener("message", handleSocialCancel);
30796
30930
  window.removeEventListener("message", handleSocialUpdate);
30797
30931
  window.removeEventListener("message", handleIconMarkup);
30932
+ window.removeEventListener("message", handleIconName);
30798
30933
  window.removeEventListener("message", handleImageUrl);
30799
30934
  window.removeEventListener("message", handleImageUploading);
30800
30935
  window.removeEventListener("message", handleCarouselChange);
@@ -31120,7 +31255,7 @@ function OhhwellsBridge() {
31120
31255
  postToParent2({
31121
31256
  type: "ow:ready",
31122
31257
  version: "1",
31123
- bridgeVersion: "0.1.140",
31258
+ bridgeVersion: "0.1.142",
31124
31259
  path: pathname,
31125
31260
  nodes: collectEditableNodes(editContentRef.current),
31126
31261
  sections