@ohhwells/bridge 0.1.122 → 0.1.123
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 +278 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +278 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -72,6 +72,21 @@ interface AiLayoutTree {
|
|
|
72
72
|
align?: 'top' | 'center' | 'bottom' | 'stretch';
|
|
73
73
|
}>;
|
|
74
74
|
}
|
|
75
|
+
declare const BUTTON_FILL_TOKENS: readonly ["dark", "primary", "accent", "light"];
|
|
76
|
+
declare const BUTTON_STYLES: readonly ["solid", "outline", "ghost"];
|
|
77
|
+
declare const BUTTON_RADII: readonly ["square", "rounded", "pill"];
|
|
78
|
+
declare const BUTTON_SIZES: readonly ["s", "m", "l"];
|
|
79
|
+
interface ButtonStyle {
|
|
80
|
+
fill: (typeof BUTTON_FILL_TOKENS)[number];
|
|
81
|
+
textColor: (typeof BUTTON_FILL_TOKENS)[number];
|
|
82
|
+
style: (typeof BUTTON_STYLES)[number];
|
|
83
|
+
radius: (typeof BUTTON_RADII)[number];
|
|
84
|
+
size: (typeof BUTTON_SIZES)[number];
|
|
85
|
+
}
|
|
86
|
+
interface ButtonStyles {
|
|
87
|
+
primary: ButtonStyle;
|
|
88
|
+
secondary: ButtonStyle;
|
|
89
|
+
}
|
|
75
90
|
/** One uploaded weight file for a custom font family. */
|
|
76
91
|
interface AiCustomFontWeight {
|
|
77
92
|
weight: number;
|
|
@@ -105,7 +120,20 @@ interface AiBrandKit {
|
|
|
105
120
|
heading: string;
|
|
106
121
|
body: string;
|
|
107
122
|
};
|
|
123
|
+
/** Absent on a kit saved before OHH-791 — renderer falls back to today's hardcoded button look. */
|
|
124
|
+
buttons?: ButtonStyles;
|
|
108
125
|
customFonts?: AiCustomFont[];
|
|
126
|
+
/** Which parts the user actually set. Absent (kits saved before this field) = both, as ever;
|
|
127
|
+
* an unset part is a carried-over template snapshot and must not be written onto the page. */
|
|
128
|
+
touched?: {
|
|
129
|
+
palette?: boolean;
|
|
130
|
+
fonts?: boolean;
|
|
131
|
+
/** Per variant, the button fields the user set (fill, textColor, style, radius, size). */
|
|
132
|
+
buttons?: {
|
|
133
|
+
primary?: (keyof ButtonStyle)[];
|
|
134
|
+
secondary?: (keyof ButtonStyle)[];
|
|
135
|
+
};
|
|
136
|
+
};
|
|
109
137
|
}
|
|
110
138
|
/**
|
|
111
139
|
* Shape, padding and typography measured off a host-template CTA, so generated buttons read as the
|
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,21 @@ interface AiLayoutTree {
|
|
|
72
72
|
align?: 'top' | 'center' | 'bottom' | 'stretch';
|
|
73
73
|
}>;
|
|
74
74
|
}
|
|
75
|
+
declare const BUTTON_FILL_TOKENS: readonly ["dark", "primary", "accent", "light"];
|
|
76
|
+
declare const BUTTON_STYLES: readonly ["solid", "outline", "ghost"];
|
|
77
|
+
declare const BUTTON_RADII: readonly ["square", "rounded", "pill"];
|
|
78
|
+
declare const BUTTON_SIZES: readonly ["s", "m", "l"];
|
|
79
|
+
interface ButtonStyle {
|
|
80
|
+
fill: (typeof BUTTON_FILL_TOKENS)[number];
|
|
81
|
+
textColor: (typeof BUTTON_FILL_TOKENS)[number];
|
|
82
|
+
style: (typeof BUTTON_STYLES)[number];
|
|
83
|
+
radius: (typeof BUTTON_RADII)[number];
|
|
84
|
+
size: (typeof BUTTON_SIZES)[number];
|
|
85
|
+
}
|
|
86
|
+
interface ButtonStyles {
|
|
87
|
+
primary: ButtonStyle;
|
|
88
|
+
secondary: ButtonStyle;
|
|
89
|
+
}
|
|
75
90
|
/** One uploaded weight file for a custom font family. */
|
|
76
91
|
interface AiCustomFontWeight {
|
|
77
92
|
weight: number;
|
|
@@ -105,7 +120,20 @@ interface AiBrandKit {
|
|
|
105
120
|
heading: string;
|
|
106
121
|
body: string;
|
|
107
122
|
};
|
|
123
|
+
/** Absent on a kit saved before OHH-791 — renderer falls back to today's hardcoded button look. */
|
|
124
|
+
buttons?: ButtonStyles;
|
|
108
125
|
customFonts?: AiCustomFont[];
|
|
126
|
+
/** Which parts the user actually set. Absent (kits saved before this field) = both, as ever;
|
|
127
|
+
* an unset part is a carried-over template snapshot and must not be written onto the page. */
|
|
128
|
+
touched?: {
|
|
129
|
+
palette?: boolean;
|
|
130
|
+
fonts?: boolean;
|
|
131
|
+
/** Per variant, the button fields the user set (fill, textColor, style, radius, size). */
|
|
132
|
+
buttons?: {
|
|
133
|
+
primary?: (keyof ButtonStyle)[];
|
|
134
|
+
secondary?: (keyof ButtonStyle)[];
|
|
135
|
+
};
|
|
136
|
+
};
|
|
109
137
|
}
|
|
110
138
|
/**
|
|
111
139
|
* Shape, padding and typography measured off a host-template CTA, so generated buttons read as the
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,10 @@ function enforceLinkHrefs() {
|
|
|
31
31
|
|
|
32
32
|
// src/lib/ai-tree.ts
|
|
33
33
|
var AI_TREE_SCHEMA_VERSIONS = [1];
|
|
34
|
+
var BUTTON_FILL_TOKENS = ["dark", "primary", "accent", "light"];
|
|
35
|
+
var BUTTON_STYLES = ["solid", "outline", "ghost"];
|
|
36
|
+
var BUTTON_RADII = ["square", "rounded", "pill"];
|
|
37
|
+
var BUTTON_SIZES = ["s", "m", "l"];
|
|
34
38
|
var AI_DEFAULT_BRAND = {
|
|
35
39
|
palette: { dark: "#0C0A09", primary: "#0078E5", accent: "#747271", light: "#ECECEB" },
|
|
36
40
|
fonts: {
|
|
@@ -290,6 +294,118 @@ var FONT_VARS = {
|
|
|
290
294
|
var BRAND_FONT_LINK_ID = "ohw-brand-fonts";
|
|
291
295
|
var CUSTOM_FONT_STYLE_ID = "ohw-brand-custom-fonts";
|
|
292
296
|
var CUSTOM_FONT_FORMATS = /* @__PURE__ */ new Set(["woff2", "woff", "truetype", "opentype"]);
|
|
297
|
+
var BUTTON_RADIUS_PX = {
|
|
298
|
+
square: 0,
|
|
299
|
+
rounded: AI_TREE_TOKENS.radiusButton,
|
|
300
|
+
pill: 999
|
|
301
|
+
};
|
|
302
|
+
var BUTTON_SIZE_SPEC = {
|
|
303
|
+
s: {
|
|
304
|
+
paddingX: `${AI_TREE_TOKENS.spacing4}px`,
|
|
305
|
+
paddingY: `${AI_TREE_TOKENS.spacing2}px`,
|
|
306
|
+
fontSize: "14px"
|
|
307
|
+
},
|
|
308
|
+
m: {
|
|
309
|
+
paddingX: `${AI_TREE_TOKENS.spacing6}px`,
|
|
310
|
+
paddingY: `${AI_TREE_TOKENS.spacing3}px`,
|
|
311
|
+
fontSize: `${AI_TREE_TOKENS.type.button.size}px`
|
|
312
|
+
},
|
|
313
|
+
l: {
|
|
314
|
+
paddingX: `${AI_TREE_TOKENS.spacing8}px`,
|
|
315
|
+
paddingY: `${AI_TREE_TOKENS.spacing4}px`,
|
|
316
|
+
fontSize: "18px"
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
var BUTTON_SIZE_SCALE = { s: 0.85, m: 1, l: 1.25 };
|
|
320
|
+
var roundHalf = (n) => Math.round(n * 2) / 2;
|
|
321
|
+
function measureTemplateButton(variant) {
|
|
322
|
+
const root = document.documentElement;
|
|
323
|
+
const el = document.querySelector(`.btn--${variant}, .btn-${variant}`);
|
|
324
|
+
if (!el) return null;
|
|
325
|
+
const sheet = document.getElementById(BUTTON_RULES_STYLE_ID);
|
|
326
|
+
const rules = sheet?.textContent ?? "";
|
|
327
|
+
const saved = BUTTON_VAR_NAMES.map((name) => [name, root.style.getPropertyValue(name)]);
|
|
328
|
+
if (sheet) sheet.textContent = "";
|
|
329
|
+
for (const [name] of saved) root.style.removeProperty(name);
|
|
330
|
+
const cs = getComputedStyle(el);
|
|
331
|
+
const base = {
|
|
332
|
+
fontSize: parseFloat(cs.fontSize),
|
|
333
|
+
top: parseFloat(cs.paddingTop),
|
|
334
|
+
right: parseFloat(cs.paddingRight),
|
|
335
|
+
bottom: parseFloat(cs.paddingBottom),
|
|
336
|
+
left: parseFloat(cs.paddingLeft)
|
|
337
|
+
};
|
|
338
|
+
if (sheet) sheet.textContent = rules;
|
|
339
|
+
for (const [name, value] of saved) if (value) root.style.setProperty(name, value);
|
|
340
|
+
return Object.values(base).every(Number.isFinite) && base.fontSize > 0 ? base : null;
|
|
341
|
+
}
|
|
342
|
+
function scaledButtonSize(size, base) {
|
|
343
|
+
if (!base) return BUTTON_SIZE_SPEC[size];
|
|
344
|
+
const k = BUTTON_SIZE_SCALE[size];
|
|
345
|
+
const px2 = (n) => `${roundHalf(n * k)}px`;
|
|
346
|
+
return {
|
|
347
|
+
// Each side scaled on its own, so a button with uneven padding (say, room for an icon on one
|
|
348
|
+
// side) keeps its shape; the x/y vars are the averages for templates that read those instead.
|
|
349
|
+
padding: `${px2(base.top)} ${px2(base.right)} ${px2(base.bottom)} ${px2(base.left)}`,
|
|
350
|
+
paddingX: px2((base.left + base.right) / 2),
|
|
351
|
+
paddingY: px2((base.top + base.bottom) / 2),
|
|
352
|
+
fontSize: `${roundHalf(base.fontSize * k)}px`
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
var BUTTON_VAR_ROLES = ["bg", "text", "border", "radius", "padding-x", "padding-y", "padding", "font-size"];
|
|
356
|
+
var BUTTON_VAR_NAMES = ["primary", "secondary"].flatMap(
|
|
357
|
+
(variant) => BUTTON_VAR_ROLES.map((role) => `--brand-button-${variant}-${role}`)
|
|
358
|
+
);
|
|
359
|
+
var ROLES_BY_FIELD = {
|
|
360
|
+
fill: ["bg"],
|
|
361
|
+
style: ["bg", "border"],
|
|
362
|
+
textColor: ["text"],
|
|
363
|
+
radius: ["radius"],
|
|
364
|
+
size: ["padding-x", "padding-y", "padding", "font-size"]
|
|
365
|
+
};
|
|
366
|
+
function touchedButtonRoles(variant, kit) {
|
|
367
|
+
if (!kit.touched) return new Set(BUTTON_VAR_ROLES);
|
|
368
|
+
const fields = kit.touched.buttons?.[variant] ?? [];
|
|
369
|
+
const outline = fields.includes("style") && kit.buttons?.[variant]?.style === "outline";
|
|
370
|
+
return new Set(
|
|
371
|
+
fields.flatMap((f) => {
|
|
372
|
+
if (outline && (f === "fill" || f === "style")) return ["border"];
|
|
373
|
+
return ROLES_BY_FIELD[f] ?? [];
|
|
374
|
+
})
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
function buttonPalette(kit) {
|
|
378
|
+
return kit.palette ?? {
|
|
379
|
+
dark: "var(--brand-text)",
|
|
380
|
+
primary: "var(--brand-primary)",
|
|
381
|
+
accent: "var(--brand-accent)",
|
|
382
|
+
light: "var(--brand-background)"
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function buttonVariantVars(variant, style, roles, palette) {
|
|
386
|
+
const fill = palette[style.fill];
|
|
387
|
+
const text = palette[style.textColor];
|
|
388
|
+
const size = scaledButtonSize(style.size, roles.has("font-size") ? measureTemplateButton(variant) : null);
|
|
389
|
+
const { bg, border } = !roles.has("border") ? { bg: fill, border: "transparent" } : style.style === "outline" ? { bg: "transparent", border: fill } : style.style === "ghost" ? { bg: "transparent", border: "transparent" } : { bg: fill, border: "transparent" };
|
|
390
|
+
const all = {
|
|
391
|
+
[`--brand-button-${variant}-bg`]: bg,
|
|
392
|
+
[`--brand-button-${variant}-text`]: text,
|
|
393
|
+
[`--brand-button-${variant}-border`]: border,
|
|
394
|
+
[`--brand-button-${variant}-radius`]: `${BUTTON_RADIUS_PX[style.radius]}px`,
|
|
395
|
+
[`--brand-button-${variant}-padding-x`]: size.paddingX,
|
|
396
|
+
[`--brand-button-${variant}-padding-y`]: size.paddingY,
|
|
397
|
+
...size.padding ? { [`--brand-button-${variant}-padding`]: size.padding } : {},
|
|
398
|
+
[`--brand-button-${variant}-font-size`]: size.fontSize
|
|
399
|
+
};
|
|
400
|
+
const prefix = `--brand-button-${variant}-`;
|
|
401
|
+
return Object.fromEntries(Object.entries(all).filter(([name]) => roles.has(name.slice(prefix.length))));
|
|
402
|
+
}
|
|
403
|
+
function buttonStyleVars(kit) {
|
|
404
|
+
return {
|
|
405
|
+
...buttonVariantVars("primary", kit.buttons.primary, touchedButtonRoles("primary", kit), buttonPalette(kit)),
|
|
406
|
+
...buttonVariantVars("secondary", kit.buttons.secondary, touchedButtonRoles("secondary", kit), buttonPalette(kit))
|
|
407
|
+
};
|
|
408
|
+
}
|
|
293
409
|
function brandColorVars(palette) {
|
|
294
410
|
const { dark, primary, accent, light } = palette;
|
|
295
411
|
const mix = (a, aPct, b) => `color-mix(in srgb, ${a} ${aPct}%, ${b})`;
|
|
@@ -317,6 +433,11 @@ function brandColorVars(palette) {
|
|
|
317
433
|
"--brand-on-primary": light
|
|
318
434
|
};
|
|
319
435
|
}
|
|
436
|
+
function isButtonStyle(value) {
|
|
437
|
+
if (!value || typeof value !== "object") return false;
|
|
438
|
+
const v = value;
|
|
439
|
+
return BUTTON_FILL_TOKENS.includes(v.fill) && BUTTON_FILL_TOKENS.includes(v.textColor) && BUTTON_STYLES.includes(v.style) && BUTTON_RADII.includes(v.radius) && BUTTON_SIZES.includes(v.size);
|
|
440
|
+
}
|
|
320
441
|
function parseCustomFontWeight(raw) {
|
|
321
442
|
if (typeof raw !== "object" || raw === null) return null;
|
|
322
443
|
const w = raw;
|
|
@@ -336,6 +457,15 @@ function parseCustomFont(raw) {
|
|
|
336
457
|
if (weights.length === 0) return null;
|
|
337
458
|
return { family: f.family, label: f.label, weights };
|
|
338
459
|
}
|
|
460
|
+
var BUTTON_FIELDS = Object.keys(ROLES_BY_FIELD);
|
|
461
|
+
function parseTouchedButtons(raw) {
|
|
462
|
+
const out = {};
|
|
463
|
+
for (const variant of ["primary", "secondary"]) {
|
|
464
|
+
const list = raw?.[variant];
|
|
465
|
+
if (Array.isArray(list)) out[variant] = BUTTON_FIELDS.filter((f) => list.includes(f));
|
|
466
|
+
}
|
|
467
|
+
return out;
|
|
468
|
+
}
|
|
339
469
|
function parseBrandKit(raw) {
|
|
340
470
|
if (!raw) return null;
|
|
341
471
|
try {
|
|
@@ -345,11 +475,21 @@ function parseBrandKit(raw) {
|
|
|
345
475
|
const palette = p && typeof p.dark === "string" && typeof p.primary === "string" && typeof p.accent === "string" && typeof p.light === "string" ? { dark: p.dark, primary: p.primary, accent: p.accent, light: p.light } : void 0;
|
|
346
476
|
const fonts = f && typeof f.heading === "string" && typeof f.body === "string" ? { heading: f.heading, body: f.body } : void 0;
|
|
347
477
|
const customFonts = Array.isArray(parsed?.customFonts) ? parsed.customFonts.map(parseCustomFont).filter((c) => c !== null) : void 0;
|
|
348
|
-
|
|
478
|
+
const b = parsed?.buttons;
|
|
479
|
+
const buttons = b && isButtonStyle(b.primary) && isButtonStyle(b.secondary) ? { primary: b.primary, secondary: b.secondary } : void 0;
|
|
480
|
+
if (!palette && !fonts && !customFonts && !buttons) return null;
|
|
349
481
|
return {
|
|
350
482
|
...palette ? { palette } : {},
|
|
351
483
|
...fonts ? { fonts } : {},
|
|
352
|
-
...customFonts ? { customFonts } : {}
|
|
484
|
+
...customFonts ? { customFonts } : {},
|
|
485
|
+
...buttons ? { buttons } : {},
|
|
486
|
+
...parsed.touched && typeof parsed.touched === "object" ? {
|
|
487
|
+
touched: {
|
|
488
|
+
palette: parsed.touched.palette === true,
|
|
489
|
+
fonts: parsed.touched.fonts === true,
|
|
490
|
+
buttons: parseTouchedButtons(parsed.touched.buttons)
|
|
491
|
+
}
|
|
492
|
+
} : {}
|
|
353
493
|
};
|
|
354
494
|
} catch {
|
|
355
495
|
return null;
|
|
@@ -378,6 +518,39 @@ function loadBrandFonts(families) {
|
|
|
378
518
|
}
|
|
379
519
|
if (link.href !== href) link.href = href;
|
|
380
520
|
}
|
|
521
|
+
var BUTTON_RULES_STYLE_ID = "ohw-brand-button-rules";
|
|
522
|
+
function buttonRulesCss(kit) {
|
|
523
|
+
const rule = (variant) => {
|
|
524
|
+
const roles = touchedButtonRoles(variant, kit);
|
|
525
|
+
const v = (role) => `var(--brand-button-${variant}-${role})`;
|
|
526
|
+
const decls = [];
|
|
527
|
+
if (roles.has("bg")) decls.push(`background:${v("bg")} !important`);
|
|
528
|
+
if (roles.has("text")) decls.push(`color:${v("text")} !important`);
|
|
529
|
+
if (roles.has("border")) decls.push(`border:1.5px solid ${v("border")} !important`);
|
|
530
|
+
if (roles.has("radius")) decls.push(`border-radius:${v("radius")} !important`);
|
|
531
|
+
if (roles.has("padding-x")) decls.push(`padding:var(--brand-button-${variant}-padding, ${v("padding-y")} ${v("padding-x")}) !important`);
|
|
532
|
+
if (roles.has("font-size")) decls.push(`font-size:${v("font-size")} !important`);
|
|
533
|
+
if (decls.length === 0) return "";
|
|
534
|
+
const selectors = [`html .btn--${variant}`, `html .btn-${variant}`];
|
|
535
|
+
const labelRule = roles.has("text") ? `${selectors.map((x) => `${x} *`).join(",")}{color:inherit !important}` : "";
|
|
536
|
+
return `${selectors.join(",")}{${decls.join(";")}}` + labelRule + (roles.has("bg") ? `${selectors.map((x) => `${x}:hover`).join(",")}{opacity:0.88}` : "");
|
|
537
|
+
};
|
|
538
|
+
return rule("primary") + rule("secondary");
|
|
539
|
+
}
|
|
540
|
+
function syncButtonRules(kit) {
|
|
541
|
+
let el = document.getElementById(BUTTON_RULES_STYLE_ID);
|
|
542
|
+
const css = kit ? buttonRulesCss(kit) : "";
|
|
543
|
+
if (!css) {
|
|
544
|
+
el?.remove();
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (!el) {
|
|
548
|
+
el = document.createElement("style");
|
|
549
|
+
el.id = BUTTON_RULES_STYLE_ID;
|
|
550
|
+
document.head.appendChild(el);
|
|
551
|
+
}
|
|
552
|
+
if (el.textContent !== css) el.textContent = css;
|
|
553
|
+
}
|
|
381
554
|
function escapeCssString(value) {
|
|
382
555
|
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
383
556
|
}
|
|
@@ -403,28 +576,47 @@ function loadCustomFonts(fonts) {
|
|
|
403
576
|
function applyBrandToDom(kit) {
|
|
404
577
|
const root = document.documentElement;
|
|
405
578
|
if (!kit) {
|
|
406
|
-
for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES])
|
|
579
|
+
for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES, ...BUTTON_VAR_NAMES]) {
|
|
580
|
+
root.style.removeProperty(name);
|
|
581
|
+
}
|
|
407
582
|
for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
|
|
408
583
|
document.getElementById(BRAND_FONT_LINK_ID)?.remove();
|
|
409
584
|
document.getElementById(CUSTOM_FONT_STYLE_ID)?.remove();
|
|
585
|
+
syncButtonRules(null);
|
|
410
586
|
return;
|
|
411
587
|
}
|
|
588
|
+
const applyPalette = kit.touched ? kit.touched.palette === true : true;
|
|
589
|
+
const applyFonts = kit.touched ? kit.touched.fonts === true : true;
|
|
412
590
|
if (kit.palette) {
|
|
413
|
-
|
|
591
|
+
if (applyPalette) {
|
|
592
|
+
for (const [name, value] of Object.entries(brandColorVars(kit.palette))) root.style.setProperty(name, value);
|
|
593
|
+
} else {
|
|
594
|
+
for (const name of [...BRAND_VAR_NAMES, ...LEGACY_BRAND_VAR_NAMES]) root.style.removeProperty(name);
|
|
595
|
+
}
|
|
414
596
|
}
|
|
597
|
+
if (kit.buttons) {
|
|
598
|
+
for (const name of BUTTON_VAR_NAMES) root.style.removeProperty(name);
|
|
599
|
+
for (const [name, value] of Object.entries(buttonStyleVars({ ...kit, buttons: kit.buttons }))) root.style.setProperty(name, value);
|
|
600
|
+
}
|
|
601
|
+
syncButtonRules(kit.buttons ? kit : null);
|
|
415
602
|
if (kit.customFonts) {
|
|
416
603
|
loadCustomFonts(kit.customFonts);
|
|
417
604
|
}
|
|
418
605
|
if (kit.fonts) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
(
|
|
426
|
-
|
|
427
|
-
|
|
606
|
+
if (applyFonts) {
|
|
607
|
+
const heading = quoteFamily(kit.fonts.heading);
|
|
608
|
+
const body = quoteFamily(kit.fonts.body);
|
|
609
|
+
for (const name of FONT_VARS.heading) root.style.setProperty(name, heading);
|
|
610
|
+
for (const name of FONT_VARS.body) root.style.setProperty(name, body);
|
|
611
|
+
const customFamilies = new Set((kit.customFonts ?? []).map((f) => f.family));
|
|
612
|
+
const googleFamilies = [familyOf(kit.fonts.heading), familyOf(kit.fonts.body)].filter(
|
|
613
|
+
(f) => !customFamilies.has(f)
|
|
614
|
+
);
|
|
615
|
+
loadBrandFonts(googleFamilies);
|
|
616
|
+
} else {
|
|
617
|
+
for (const name of [...FONT_VARS.heading, ...FONT_VARS.body]) root.style.removeProperty(name);
|
|
618
|
+
document.getElementById(BRAND_FONT_LINK_ID)?.remove();
|
|
619
|
+
}
|
|
428
620
|
}
|
|
429
621
|
}
|
|
430
622
|
|
|
@@ -574,7 +766,6 @@ var NODE_PROPS = [
|
|
|
574
766
|
"color",
|
|
575
767
|
"font-family",
|
|
576
768
|
"font-size",
|
|
577
|
-
"background",
|
|
578
769
|
"text-align",
|
|
579
770
|
"justify-content",
|
|
580
771
|
"align-items"
|
|
@@ -624,9 +815,6 @@ function clearNodeProps(root) {
|
|
|
624
815
|
h.removeAttribute(NODE_WROTE_ATTR);
|
|
625
816
|
}
|
|
626
817
|
}
|
|
627
|
-
function buttonSurfaceOf(el) {
|
|
628
|
-
return el.closest("a, button") ?? el;
|
|
629
|
-
}
|
|
630
818
|
function alignSubjectOf(el) {
|
|
631
819
|
const button = el.closest('[data-ohw-role="button"]');
|
|
632
820
|
if (button?.parentElement) return button.parentElement;
|
|
@@ -744,18 +932,6 @@ function applyStylesToDom(store) {
|
|
|
744
932
|
);
|
|
745
933
|
subject.setAttribute(NODE_WROTE_ATTR, "");
|
|
746
934
|
}
|
|
747
|
-
if (override.buttonBackground !== void 0 || override.buttonText !== void 0) {
|
|
748
|
-
const surface = buttonSurfaceOf(el);
|
|
749
|
-
if (override.buttonBackground !== void 0) {
|
|
750
|
-
saveInline(surface, "background");
|
|
751
|
-
surface.style.setProperty("background", override.buttonBackground, "important");
|
|
752
|
-
}
|
|
753
|
-
if (override.buttonText !== void 0) {
|
|
754
|
-
saveInline(surface, "color");
|
|
755
|
-
surface.style.setProperty("color", override.buttonText, "important");
|
|
756
|
-
}
|
|
757
|
-
surface.setAttribute(NODE_WROTE_ATTR, "");
|
|
758
|
-
}
|
|
759
935
|
}
|
|
760
936
|
}
|
|
761
937
|
for (const [sectionId, override] of Object.entries(store.sections)) {
|
|
@@ -1341,7 +1517,59 @@ var AI_MOBILE_CSS = [
|
|
|
1341
1517
|
"[data-ai-grid]{grid-template-columns:repeat(2, 1fr) !important}",
|
|
1342
1518
|
"}"
|
|
1343
1519
|
].join("");
|
|
1344
|
-
function
|
|
1520
|
+
function buttonSizeSpec(size) {
|
|
1521
|
+
switch (size) {
|
|
1522
|
+
case "s":
|
|
1523
|
+
return { padding: `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px`, fontSize: 14 };
|
|
1524
|
+
case "l":
|
|
1525
|
+
return { padding: `${AI_TREE_TOKENS.spacing4}px ${AI_TREE_TOKENS.spacing8}px`, fontSize: 18 };
|
|
1526
|
+
case "m":
|
|
1527
|
+
default:
|
|
1528
|
+
return { padding: `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`, fontSize: AI_TREE_TOKENS.type.button.size };
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
function buttonRadiusPx(radius) {
|
|
1532
|
+
switch (radius) {
|
|
1533
|
+
case "square":
|
|
1534
|
+
return 0;
|
|
1535
|
+
case "pill":
|
|
1536
|
+
return 999;
|
|
1537
|
+
case "rounded":
|
|
1538
|
+
default:
|
|
1539
|
+
return AI_TREE_TOKENS.radiusButton;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
function resolveButtonToken(brand, token) {
|
|
1543
|
+
return brand.palette[token];
|
|
1544
|
+
}
|
|
1545
|
+
function buttonColorStyle(ctx, variant) {
|
|
1546
|
+
const token = ctx.brand.buttons?.[variant];
|
|
1547
|
+
if (!token) return null;
|
|
1548
|
+
const fill = resolveButtonToken(ctx.brand, token.fill);
|
|
1549
|
+
const text = resolveButtonToken(ctx.brand, token.textColor);
|
|
1550
|
+
switch (token.style) {
|
|
1551
|
+
case "outline":
|
|
1552
|
+
return { background: "transparent", border: `1px solid ${fill}`, color: text };
|
|
1553
|
+
case "ghost":
|
|
1554
|
+
return { background: "transparent", border: "1px solid transparent", color: text };
|
|
1555
|
+
case "solid":
|
|
1556
|
+
default:
|
|
1557
|
+
return { background: fill, color: text };
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
function buttonShellStyle(ctx, variant, fullWidth) {
|
|
1561
|
+
const buttonToken = ctx.brand.buttons?.[variant];
|
|
1562
|
+
if (buttonToken) {
|
|
1563
|
+
const { padding, fontSize } = buttonSizeSpec(buttonToken.size);
|
|
1564
|
+
return {
|
|
1565
|
+
borderRadius: buttonRadiusPx(buttonToken.radius),
|
|
1566
|
+
padding,
|
|
1567
|
+
fontFamily: ctx.brand.fonts.body,
|
|
1568
|
+
fontSize,
|
|
1569
|
+
lineHeight: AI_TREE_TOKENS.type.button.line,
|
|
1570
|
+
fontWeight: AI_TREE_TOKENS.type.button.weight
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1345
1573
|
const bs = ctx.buttonStyle;
|
|
1346
1574
|
if (bs) {
|
|
1347
1575
|
return {
|
|
@@ -1537,12 +1765,16 @@ function ButtonEl({
|
|
|
1537
1765
|
secondaryColor
|
|
1538
1766
|
}) {
|
|
1539
1767
|
const secondary = slots.variant === "secondary";
|
|
1768
|
+
const variant = secondary ? "secondary" : "primary";
|
|
1769
|
+
const tokenColorStyle = buttonColorStyle(ctx, variant);
|
|
1540
1770
|
const href = str(slots.href);
|
|
1541
1771
|
const linkAttrs = ctx.keyFor && path ? { "data-ohw-href-key": ctx.keyFor(`${path}.href`), "data-ohw-role": "button" } : {};
|
|
1542
1772
|
return /* @__PURE__ */ jsx(
|
|
1543
1773
|
"a",
|
|
1544
1774
|
{
|
|
1545
1775
|
href: href || void 0,
|
|
1776
|
+
className: `btn btn--${variant}`,
|
|
1777
|
+
"data-ohw-btn-variant": variant,
|
|
1546
1778
|
...linkAttrs,
|
|
1547
1779
|
style: {
|
|
1548
1780
|
display: fullWidth ? "flex" : "inline-flex",
|
|
@@ -1552,13 +1784,13 @@ function ButtonEl({
|
|
|
1552
1784
|
textDecoration: "none",
|
|
1553
1785
|
cursor: "pointer",
|
|
1554
1786
|
whiteSpace: "nowrap",
|
|
1555
|
-
...buttonShellStyle(ctx, fullWidth),
|
|
1556
|
-
...secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : (
|
|
1787
|
+
...buttonShellStyle(ctx, variant, fullWidth),
|
|
1788
|
+
...tokenColorStyle ?? (secondary ? { border: `1px solid ${secondaryColor ?? ctx.brand.palette.dark}`, color: secondaryColor ?? ctx.brand.palette.dark } : (
|
|
1557
1789
|
// Off-band, prefer the template button's measured fill/label pair — a template may
|
|
1558
1790
|
// fill its CTAs with any token (hvac: accent bg, primary text). On an accent band
|
|
1559
1791
|
// the flipped palette keeps contrast, so the brand-driven colours stay.
|
|
1560
1792
|
!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
|
|
1561
|
-
)
|
|
1793
|
+
))
|
|
1562
1794
|
},
|
|
1563
1795
|
children: /* @__PURE__ */ jsx("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
|
|
1564
1796
|
}
|
|
@@ -2780,18 +3012,21 @@ function renderNode(node, ctx, path) {
|
|
|
2780
3012
|
"button",
|
|
2781
3013
|
{
|
|
2782
3014
|
type: "submit",
|
|
3015
|
+
className: "btn btn--primary",
|
|
3016
|
+
"data-ohw-btn-variant": "primary",
|
|
3017
|
+
"data-ohw-role": "button",
|
|
2783
3018
|
style: {
|
|
2784
3019
|
alignSelf: submitAlign,
|
|
2785
3020
|
border: "none",
|
|
2786
3021
|
cursor: "pointer",
|
|
2787
3022
|
// Shape/padding/typography follow the host template's own buttons.
|
|
2788
|
-
...buttonShellStyle(ctx),
|
|
3023
|
+
...buttonShellStyle(ctx, "primary"),
|
|
2789
3024
|
// Brand-styled: the template button's measured fill/label pair off-band, else
|
|
2790
3025
|
// primary fill with a brand-derived label so it reads on custom palettes.
|
|
2791
|
-
|
|
3026
|
+
...buttonColorStyle(ctx, "primary") ?? (!ctx.buttonLabel && ctx.buttonStyle?.background ? { background: ctx.buttonStyle.background, color: ctx.buttonStyle.color } : {
|
|
2792
3027
|
background: ctx.brand.palette.primary,
|
|
2793
3028
|
color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
|
|
2794
|
-
}
|
|
3029
|
+
})
|
|
2795
3030
|
},
|
|
2796
3031
|
children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
|
|
2797
3032
|
},
|
|
@@ -16775,6 +17010,9 @@ function guardTemplateHover(el) {
|
|
|
16775
17010
|
observer.observe(el, { attributes: true, attributeFilter: ["style", "class"] });
|
|
16776
17011
|
paintObserverByEl.set(el, observer);
|
|
16777
17012
|
}
|
|
17013
|
+
function paintedTokensOf(el) {
|
|
17014
|
+
return paintLockByEl.get(el)?.tokens ?? null;
|
|
17015
|
+
}
|
|
16778
17016
|
function applyButtonStyle(el, assignment, tokens) {
|
|
16779
17017
|
paintLockByEl.set(el, { assignment, tokens });
|
|
16780
17018
|
paintingButton = true;
|
|
@@ -17113,12 +17351,9 @@ function duplicateButton(source, content) {
|
|
|
17113
17351
|
const label = buttonLabel(source);
|
|
17114
17352
|
if (href) copy.setAttribute("href", href);
|
|
17115
17353
|
const sourceAssignment = readAssignments(content)[sourceId] ?? assignmentFor(source, content, tokens);
|
|
17116
|
-
const copyAssignment = {
|
|
17117
|
-
|
|
17118
|
-
|
|
17119
|
-
};
|
|
17120
|
-
stampVariantClass(copy, "secondary");
|
|
17121
|
-
applyButtonStyle(copy, copyAssignment, tokens);
|
|
17354
|
+
const copyAssignment = { ...sourceAssignment };
|
|
17355
|
+
stampVariantClass(copy, copyAssignment.variant);
|
|
17356
|
+
applyButtonStyle(copy, copyAssignment, paintedTokensOf(source) ?? tokens);
|
|
17122
17357
|
const copyLabelEl = labelElement2(copy);
|
|
17123
17358
|
if (copyLabelEl) copyLabelEl.textContent = label;
|
|
17124
17359
|
let collection = buttonCollectionOf(source);
|
|
@@ -26569,7 +26804,7 @@ function OhhwellsBridge() {
|
|
|
26569
26804
|
postToParent2({
|
|
26570
26805
|
type: "ow:ready",
|
|
26571
26806
|
version: "1",
|
|
26572
|
-
bridgeVersion: "0.1.
|
|
26807
|
+
bridgeVersion: "0.1.122",
|
|
26573
26808
|
path: pathname,
|
|
26574
26809
|
nodes: collectEditableNodes(editContentRef.current),
|
|
26575
26810
|
sections
|