@ohhwells/bridge 0.1.158 → 0.1.160
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 +203 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -1
- package/dist/index.d.ts +58 -1
- package/dist/index.js +203 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -348,6 +348,51 @@ interface AiFooterRendererProps {
|
|
|
348
348
|
*/
|
|
349
349
|
declare function AiFooterRenderer({ tree, brand, resolveMedia, editKeyPrefix, logoUrl, resolvePageHref, legal, surface, }: AiFooterRendererProps): react_jsx_runtime.JSX.Element | null;
|
|
350
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Relative headline sizing: "make the section heading bigger" as a MULTIPLIER on whatever the
|
|
353
|
+
* template already renders, not an absolute px token.
|
|
354
|
+
*
|
|
355
|
+
* Why this exists. The original `headlineScale` prop is an absolute 4-token px scale (md 24 / lg 32
|
|
356
|
+
* / xl 40 / display 56) painted by a stylesheet rule. Measuring two real templates at 1440px found
|
|
357
|
+
* 12 of 13 section headings rendering ABOVE the top of that scale (rebound: 148, 64, 72, 84;
|
|
358
|
+
* haven-realty: 86, 58, 48, 64, 58 x5), so every token the model could pick, `display` included,
|
|
359
|
+
* made the heading SMALLER. That is the bug users hit: "make it bigger" visibly shrank the heading.
|
|
360
|
+
*
|
|
361
|
+
* Why a multiplier rather than more tokens. The same headings render 30-56px at 390px wide, i.e.
|
|
362
|
+
* the templates have a working responsive type scale. Any absolute px override freezes one width's
|
|
363
|
+
* value at every width: a `96px` token chosen to beat a 1440px heading would still be 96px on a
|
|
364
|
+
* phone where the template wanted 36px. A multiplier rides the template's own scale instead, so it
|
|
365
|
+
* is correct at every breakpoint by construction.
|
|
366
|
+
*
|
|
367
|
+
* Why this is runtime JS and not CSS. CSS cannot multiply an element's OWN font-size: `em` and `%`
|
|
368
|
+
* both resolve against the PARENT's size, so `font-size: calc(1em * 1.2)` on an <h1> throws away
|
|
369
|
+
* the template's h1 sizing rather than scaling it. The only way to scale what the element actually
|
|
370
|
+
* renders is to read it and write it back, which is what this module does, re-measuring on resize
|
|
371
|
+
* so a breakpoint change re-reads the template's new base.
|
|
372
|
+
*/
|
|
373
|
+
/** Multiplier per token. `base` is the template's own size, which is also the reset target. */
|
|
374
|
+
declare const HEADLINE_SIZE_MULTIPLIERS: Record<string, number>;
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Per-section and per-node style overrides. The backend's update_style verb (and, later, toolbar
|
|
378
|
+
* UI) produces a delta {target, changed}; the editor merges it onto one reserved node,
|
|
379
|
+
* `__ohw_styles`, and hands the whole store to the bridge. The bridge applies it to the DOM:
|
|
380
|
+
*
|
|
381
|
+
* - Section props are written as `data-ohw-style-*` attributes on the `[data-ohw-section]` element
|
|
382
|
+
* plus one injected stylesheet of attribute-scoped rules. The rules are best-effort by design —
|
|
383
|
+
* they nudge presentation (background band, vertical distribution, headline scale, image fit,
|
|
384
|
+
* spacing) and a template whose structure doesn't tolerate one simply keeps its current look.
|
|
385
|
+
* - Node props (text colour/size, button colours) are written inline on every element carrying the
|
|
386
|
+
* node's `data-ohw-key`, which covers native template nodes and generated-section slots alike.
|
|
387
|
+
*
|
|
388
|
+
* Application is idempotent: each pass clears what it wrote before (tracked via the attributes and
|
|
389
|
+
* a wrote-marker) and re-applies from the store, so removing an override restores the original look
|
|
390
|
+
* and re-rendered generated sections just need a re-apply.
|
|
391
|
+
*
|
|
392
|
+
* The store shape, the node key, and the enums are a contract with the backend
|
|
393
|
+
* (`app/helpers/style-update.ts`) and the editor (which persists/undoes the node).
|
|
394
|
+
*/
|
|
395
|
+
|
|
351
396
|
declare const SECTION_BACKGROUNDS: readonly ["default", "surface", "accent", "accent-soft"];
|
|
352
397
|
declare const TEXT_DISTRIBUTIONS: readonly ["top", "center", "space-between"];
|
|
353
398
|
declare const HEADLINE_SCALES: readonly ["md", "lg", "xl", "display"];
|
|
@@ -369,8 +414,20 @@ interface SectionStyleOverride {
|
|
|
369
414
|
fontFamily?: string;
|
|
370
415
|
/** Section-wide heading colour — painted onto the section's h1-h6 editables, over textColor. */
|
|
371
416
|
headingColor?: string;
|
|
417
|
+
/** Section-wide ICON colour (OHH-875) — the glyphs in the section's icon slots, which are
|
|
418
|
+
* neither headings nor body text, so neither textColor nor headingColor could ever reach them.
|
|
419
|
+
* Painted as `color` on each `[data-ohw-editable="icon"]`, which a glyph drawn with
|
|
420
|
+
* stroke/fill="currentColor" (lucide and every set like it) follows. Best-effort, like every
|
|
421
|
+
* other section prop: a glyph with a hardcoded colour of its own keeps it. */
|
|
422
|
+
iconColor?: string;
|
|
372
423
|
textDistribution?: (typeof TEXT_DISTRIBUTIONS)[number];
|
|
424
|
+
/** @deprecated Absolute px headline tokens. Still rendered for stores saved before the switch,
|
|
425
|
+
* but nothing writes it any more: it could only ever name a size BELOW what most templates
|
|
426
|
+
* render, so "bigger" shrank the heading. New edits use `headlineSize`. */
|
|
373
427
|
headlineScale?: (typeof HEADLINE_SCALES)[number];
|
|
428
|
+
/** Headline size as a multiplier on the template's own size (`base` = untouched). Relative on
|
|
429
|
+
* purpose, and applied by measuring rather than by a CSS rule — see lib/headline-size.ts. */
|
|
430
|
+
headlineSize?: keyof typeof HEADLINE_SIZE_MULTIPLIERS;
|
|
374
431
|
imageAspect?: string;
|
|
375
432
|
/** @deprecated OHH-789 folds this into verticalPadding — a store saved before the fold may
|
|
376
433
|
* still carry it; legacyVerticalPaddingAlias resolves it at apply time. */
|
|
@@ -666,7 +723,7 @@ declare function filterAvailablePages(pages: LinkPage[], existingTargets: string
|
|
|
666
723
|
|
|
667
724
|
declare const toggleVariants: (props?: ({
|
|
668
725
|
variant?: "default" | "outline" | null | undefined;
|
|
669
|
-
size?: "default" | "
|
|
726
|
+
size?: "default" | "sm" | "lg" | null | undefined;
|
|
670
727
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
671
728
|
declare function Toggle({ className, variant, size, ...props }: React.ComponentProps<typeof Toggle$1.Root> & VariantProps<typeof toggleVariants>): react_jsx_runtime.JSX.Element;
|
|
672
729
|
|
package/dist/index.d.ts
CHANGED
|
@@ -348,6 +348,51 @@ interface AiFooterRendererProps {
|
|
|
348
348
|
*/
|
|
349
349
|
declare function AiFooterRenderer({ tree, brand, resolveMedia, editKeyPrefix, logoUrl, resolvePageHref, legal, surface, }: AiFooterRendererProps): react_jsx_runtime.JSX.Element | null;
|
|
350
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Relative headline sizing: "make the section heading bigger" as a MULTIPLIER on whatever the
|
|
353
|
+
* template already renders, not an absolute px token.
|
|
354
|
+
*
|
|
355
|
+
* Why this exists. The original `headlineScale` prop is an absolute 4-token px scale (md 24 / lg 32
|
|
356
|
+
* / xl 40 / display 56) painted by a stylesheet rule. Measuring two real templates at 1440px found
|
|
357
|
+
* 12 of 13 section headings rendering ABOVE the top of that scale (rebound: 148, 64, 72, 84;
|
|
358
|
+
* haven-realty: 86, 58, 48, 64, 58 x5), so every token the model could pick, `display` included,
|
|
359
|
+
* made the heading SMALLER. That is the bug users hit: "make it bigger" visibly shrank the heading.
|
|
360
|
+
*
|
|
361
|
+
* Why a multiplier rather than more tokens. The same headings render 30-56px at 390px wide, i.e.
|
|
362
|
+
* the templates have a working responsive type scale. Any absolute px override freezes one width's
|
|
363
|
+
* value at every width: a `96px` token chosen to beat a 1440px heading would still be 96px on a
|
|
364
|
+
* phone where the template wanted 36px. A multiplier rides the template's own scale instead, so it
|
|
365
|
+
* is correct at every breakpoint by construction.
|
|
366
|
+
*
|
|
367
|
+
* Why this is runtime JS and not CSS. CSS cannot multiply an element's OWN font-size: `em` and `%`
|
|
368
|
+
* both resolve against the PARENT's size, so `font-size: calc(1em * 1.2)` on an <h1> throws away
|
|
369
|
+
* the template's h1 sizing rather than scaling it. The only way to scale what the element actually
|
|
370
|
+
* renders is to read it and write it back, which is what this module does, re-measuring on resize
|
|
371
|
+
* so a breakpoint change re-reads the template's new base.
|
|
372
|
+
*/
|
|
373
|
+
/** Multiplier per token. `base` is the template's own size, which is also the reset target. */
|
|
374
|
+
declare const HEADLINE_SIZE_MULTIPLIERS: Record<string, number>;
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Per-section and per-node style overrides. The backend's update_style verb (and, later, toolbar
|
|
378
|
+
* UI) produces a delta {target, changed}; the editor merges it onto one reserved node,
|
|
379
|
+
* `__ohw_styles`, and hands the whole store to the bridge. The bridge applies it to the DOM:
|
|
380
|
+
*
|
|
381
|
+
* - Section props are written as `data-ohw-style-*` attributes on the `[data-ohw-section]` element
|
|
382
|
+
* plus one injected stylesheet of attribute-scoped rules. The rules are best-effort by design —
|
|
383
|
+
* they nudge presentation (background band, vertical distribution, headline scale, image fit,
|
|
384
|
+
* spacing) and a template whose structure doesn't tolerate one simply keeps its current look.
|
|
385
|
+
* - Node props (text colour/size, button colours) are written inline on every element carrying the
|
|
386
|
+
* node's `data-ohw-key`, which covers native template nodes and generated-section slots alike.
|
|
387
|
+
*
|
|
388
|
+
* Application is idempotent: each pass clears what it wrote before (tracked via the attributes and
|
|
389
|
+
* a wrote-marker) and re-applies from the store, so removing an override restores the original look
|
|
390
|
+
* and re-rendered generated sections just need a re-apply.
|
|
391
|
+
*
|
|
392
|
+
* The store shape, the node key, and the enums are a contract with the backend
|
|
393
|
+
* (`app/helpers/style-update.ts`) and the editor (which persists/undoes the node).
|
|
394
|
+
*/
|
|
395
|
+
|
|
351
396
|
declare const SECTION_BACKGROUNDS: readonly ["default", "surface", "accent", "accent-soft"];
|
|
352
397
|
declare const TEXT_DISTRIBUTIONS: readonly ["top", "center", "space-between"];
|
|
353
398
|
declare const HEADLINE_SCALES: readonly ["md", "lg", "xl", "display"];
|
|
@@ -369,8 +414,20 @@ interface SectionStyleOverride {
|
|
|
369
414
|
fontFamily?: string;
|
|
370
415
|
/** Section-wide heading colour — painted onto the section's h1-h6 editables, over textColor. */
|
|
371
416
|
headingColor?: string;
|
|
417
|
+
/** Section-wide ICON colour (OHH-875) — the glyphs in the section's icon slots, which are
|
|
418
|
+
* neither headings nor body text, so neither textColor nor headingColor could ever reach them.
|
|
419
|
+
* Painted as `color` on each `[data-ohw-editable="icon"]`, which a glyph drawn with
|
|
420
|
+
* stroke/fill="currentColor" (lucide and every set like it) follows. Best-effort, like every
|
|
421
|
+
* other section prop: a glyph with a hardcoded colour of its own keeps it. */
|
|
422
|
+
iconColor?: string;
|
|
372
423
|
textDistribution?: (typeof TEXT_DISTRIBUTIONS)[number];
|
|
424
|
+
/** @deprecated Absolute px headline tokens. Still rendered for stores saved before the switch,
|
|
425
|
+
* but nothing writes it any more: it could only ever name a size BELOW what most templates
|
|
426
|
+
* render, so "bigger" shrank the heading. New edits use `headlineSize`. */
|
|
373
427
|
headlineScale?: (typeof HEADLINE_SCALES)[number];
|
|
428
|
+
/** Headline size as a multiplier on the template's own size (`base` = untouched). Relative on
|
|
429
|
+
* purpose, and applied by measuring rather than by a CSS rule — see lib/headline-size.ts. */
|
|
430
|
+
headlineSize?: keyof typeof HEADLINE_SIZE_MULTIPLIERS;
|
|
374
431
|
imageAspect?: string;
|
|
375
432
|
/** @deprecated OHH-789 folds this into verticalPadding — a store saved before the fold may
|
|
376
433
|
* still carry it; legacyVerticalPaddingAlias resolves it at apply time. */
|
|
@@ -666,7 +723,7 @@ declare function filterAvailablePages(pages: LinkPage[], existingTargets: string
|
|
|
666
723
|
|
|
667
724
|
declare const toggleVariants: (props?: ({
|
|
668
725
|
variant?: "default" | "outline" | null | undefined;
|
|
669
|
-
size?: "default" | "
|
|
726
|
+
size?: "default" | "sm" | "lg" | null | undefined;
|
|
670
727
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
671
728
|
declare function Toggle({ className, variant, size, ...props }: React.ComponentProps<typeof Toggle$1.Root> & VariantProps<typeof toggleVariants>): react_jsx_runtime.JSX.Element;
|
|
672
729
|
|
package/dist/index.js
CHANGED
|
@@ -106,6 +106,56 @@ function isRenderableTree(value) {
|
|
|
106
106
|
return AI_TREE_SCHEMA_VERSIONS.includes(tree.schemaVersion) && tree.type === "section" && Array.isArray(tree.rows);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
// src/lib/headline-size.ts
|
|
110
|
+
var HEADLINE_SIZE_MULTIPLIERS = {
|
|
111
|
+
xs: 0.7,
|
|
112
|
+
sm: 0.85,
|
|
113
|
+
base: 1,
|
|
114
|
+
lg: 1.2,
|
|
115
|
+
xl: 1.45,
|
|
116
|
+
"2xl": 1.75
|
|
117
|
+
};
|
|
118
|
+
var HEADLINE_SIZE_ATTR = "data-ohw-style-headlinesize";
|
|
119
|
+
var PREV_ATTR = "data-ohw-style-prev-fsmul";
|
|
120
|
+
var NODE_FONT_SIZE_ATTR = "data-ohw-style-prev-font-size";
|
|
121
|
+
function headingsIn(section) {
|
|
122
|
+
return Array.from(section.querySelectorAll("h1, h2, h3"));
|
|
123
|
+
}
|
|
124
|
+
function clearHeadlineSizes(root = document) {
|
|
125
|
+
for (const el of Array.from(root.querySelectorAll(`[${PREV_ATTR}]`))) {
|
|
126
|
+
const prev = el.getAttribute(PREV_ATTR) ?? "";
|
|
127
|
+
if (prev) el.style.setProperty("font-size", prev);
|
|
128
|
+
else el.style.removeProperty("font-size");
|
|
129
|
+
el.removeAttribute(PREV_ATTR);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function applyHeadlineSizes(root = document) {
|
|
133
|
+
clearHeadlineSizes(root);
|
|
134
|
+
for (const section of Array.from(
|
|
135
|
+
root.querySelectorAll(`[${HEADLINE_SIZE_ATTR}]`)
|
|
136
|
+
)) {
|
|
137
|
+
const multiplier = HEADLINE_SIZE_MULTIPLIERS[section.getAttribute(HEADLINE_SIZE_ATTR) ?? ""];
|
|
138
|
+
if (!multiplier || multiplier === 1) continue;
|
|
139
|
+
for (const heading of headingsIn(section)) {
|
|
140
|
+
if (heading.hasAttribute(NODE_FONT_SIZE_ATTR)) continue;
|
|
141
|
+
const base = Number.parseFloat(window.getComputedStyle(heading).fontSize);
|
|
142
|
+
if (!Number.isFinite(base) || base <= 0) continue;
|
|
143
|
+
heading.setAttribute(PREV_ATTR, heading.style.getPropertyValue("font-size"));
|
|
144
|
+
heading.style.setProperty("font-size", `${Math.round(base * multiplier * 100) / 100}px`, "important");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
var resizeInstalled = false;
|
|
149
|
+
function installHeadlineSizeResize() {
|
|
150
|
+
if (resizeInstalled || typeof window === "undefined") return;
|
|
151
|
+
resizeInstalled = true;
|
|
152
|
+
let timer;
|
|
153
|
+
window.addEventListener("resize", () => {
|
|
154
|
+
if (timer) clearTimeout(timer);
|
|
155
|
+
timer = setTimeout(() => applyHeadlineSizes(), 120);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
109
159
|
// src/lib/sections.ts
|
|
110
160
|
var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
|
|
111
161
|
function isChromeSection(el) {
|
|
@@ -5897,6 +5947,7 @@ var SECTION_ATTRS = {
|
|
|
5897
5947
|
sectionBackground: "data-ohw-style-bg",
|
|
5898
5948
|
textDistribution: "data-ohw-style-distribution",
|
|
5899
5949
|
headlineScale: "data-ohw-style-headline",
|
|
5950
|
+
headlineSize: HEADLINE_SIZE_ATTR,
|
|
5900
5951
|
imageAspect: "data-ohw-style-aspect",
|
|
5901
5952
|
spacing: "data-ohw-style-spacing",
|
|
5902
5953
|
verticalPadding: "data-ohw-style-vpadding",
|
|
@@ -5965,6 +6016,15 @@ function clearSectionAttrs(root) {
|
|
|
5965
6016
|
}
|
|
5966
6017
|
}
|
|
5967
6018
|
var ITEM_WROTE_ATTR = "data-ohw-style-item";
|
|
6019
|
+
function paintIcon(slot, color) {
|
|
6020
|
+
for (const el of [slot, ...Array.from(slot.querySelectorAll("svg, svg *"))]) {
|
|
6021
|
+
const styled = el;
|
|
6022
|
+
if (!styled.style) continue;
|
|
6023
|
+
saveInline(styled, "color");
|
|
6024
|
+
styled.style.setProperty("color", color, "important");
|
|
6025
|
+
styled.setAttribute(NODE_WROTE_ATTR, "");
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
5968
6028
|
function clearNodeProps(root) {
|
|
5969
6029
|
for (const el of Array.from(root.querySelectorAll(`[${NODE_WROTE_ATTR}]`))) {
|
|
5970
6030
|
const h = el;
|
|
@@ -6002,6 +6062,7 @@ function applyStylesToDom(store) {
|
|
|
6002
6062
|
ensureStyleSheet();
|
|
6003
6063
|
clearSectionAttrs(document);
|
|
6004
6064
|
clearNodeProps(document);
|
|
6065
|
+
clearHeadlineSizes(document);
|
|
6005
6066
|
loadStyleFonts(
|
|
6006
6067
|
store ? [
|
|
6007
6068
|
...Object.values(store.nodes).flatMap((n) => n.fontFamily ? [n.fontFamily] : []),
|
|
@@ -6075,9 +6136,13 @@ function applyStylesToDom(store) {
|
|
|
6075
6136
|
const nodes = document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`);
|
|
6076
6137
|
for (const el of Array.from(nodes)) {
|
|
6077
6138
|
if (override.color !== void 0) {
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6139
|
+
if (el.dataset.ohwEditable === "icon") {
|
|
6140
|
+
paintIcon(el, override.color);
|
|
6141
|
+
} else {
|
|
6142
|
+
saveInline(el, "color");
|
|
6143
|
+
el.style.setProperty("color", override.color, "important");
|
|
6144
|
+
el.setAttribute(NODE_WROTE_ATTR, "");
|
|
6145
|
+
}
|
|
6081
6146
|
}
|
|
6082
6147
|
if (override.fontFamily !== void 0) {
|
|
6083
6148
|
saveInline(el, "font-family");
|
|
@@ -6134,6 +6199,18 @@ function applyStylesToDom(store) {
|
|
|
6134
6199
|
}
|
|
6135
6200
|
}
|
|
6136
6201
|
}
|
|
6202
|
+
for (const [sectionId, override] of Object.entries(store.sections)) {
|
|
6203
|
+
if (override.iconColor === void 0) continue;
|
|
6204
|
+
const escaped = CSS.escape(sectionId);
|
|
6205
|
+
const byInstance = document.querySelectorAll(`[data-ohw-instance="${escaped}"]`);
|
|
6206
|
+
const sections = byInstance.length ? byInstance : document.querySelectorAll(`[data-ohw-section="${escaped}"]`);
|
|
6207
|
+
for (const marker of Array.from(sections)) {
|
|
6208
|
+
const icons = marker.querySelectorAll('[data-ohw-editable="icon"]');
|
|
6209
|
+
for (const el of Array.from(icons)) paintIcon(el, override.iconColor);
|
|
6210
|
+
}
|
|
6211
|
+
}
|
|
6212
|
+
applyHeadlineSizes(document);
|
|
6213
|
+
installHeadlineSizeResize();
|
|
6137
6214
|
}
|
|
6138
6215
|
|
|
6139
6216
|
// src/lib/ai-sections-store.ts
|
|
@@ -6173,17 +6250,21 @@ function resolveSectionTree(sectionId, aiSectionsRaw) {
|
|
|
6173
6250
|
return parseAiSectionsState(aiSectionsRaw).sections.find((entry) => entry.id === sectionId)?.tree;
|
|
6174
6251
|
}
|
|
6175
6252
|
function resolveSectionSettingsContext(sectionId, aiSectionsRaw, stylesRaw) {
|
|
6253
|
+
const styleOverride = parseStyleStore(stylesRaw)?.sections[sectionId];
|
|
6254
|
+
const headlineSize = styleOverride?.headlineSize;
|
|
6176
6255
|
const genSettings = parseAiSectionsState(aiSectionsRaw).sections.find(
|
|
6177
6256
|
(entry) => entry.id === sectionId
|
|
6178
6257
|
)?.tree.settings;
|
|
6179
|
-
if (genSettings)
|
|
6180
|
-
|
|
6181
|
-
|
|
6258
|
+
if (genSettings) {
|
|
6259
|
+
return { ...genSettings, ...headlineSize ? { headlineSize } : {} };
|
|
6260
|
+
}
|
|
6261
|
+
if (!styleOverride?.verticalPadding && !styleOverride?.minHeight && !styleOverride?.spacing && !headlineSize) {
|
|
6182
6262
|
return void 0;
|
|
6183
6263
|
}
|
|
6184
6264
|
return {
|
|
6185
|
-
verticalPadding: styleOverride
|
|
6186
|
-
minHeight: styleOverride
|
|
6265
|
+
verticalPadding: styleOverride?.verticalPadding ?? legacyVerticalPaddingAlias(styleOverride?.spacing),
|
|
6266
|
+
minHeight: styleOverride?.minHeight,
|
|
6267
|
+
...headlineSize ? { headlineSize } : {}
|
|
6187
6268
|
};
|
|
6188
6269
|
}
|
|
6189
6270
|
function applyTreeToState(state, payload) {
|
|
@@ -7188,6 +7269,23 @@ function applyCustomCodeToHead(content) {
|
|
|
7188
7269
|
if (beforeNodes.length > 0) document.head.append(...beforeNodes);
|
|
7189
7270
|
}
|
|
7190
7271
|
|
|
7272
|
+
// src/lib/node-font-size.ts
|
|
7273
|
+
var TEXT_NODE_TYPES = /* @__PURE__ */ new Set(["text", "text/rich", "plain", "link"]);
|
|
7274
|
+
function escapeKey(key) {
|
|
7275
|
+
return typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(key) : key.replace(/["\\]/g, "\\$&");
|
|
7276
|
+
}
|
|
7277
|
+
function measureNodeFontSize(key, type, root) {
|
|
7278
|
+
if (!TEXT_NODE_TYPES.has(type)) return void 0;
|
|
7279
|
+
const el = root.querySelector(`[data-ohw-editable][data-ohw-key="${escapeKey(key)}"]`);
|
|
7280
|
+
if (!el) return void 0;
|
|
7281
|
+
const size = Number.parseFloat(window.getComputedStyle(el).fontSize);
|
|
7282
|
+
return Number.isFinite(size) && size > 0 ? Math.round(size) : void 0;
|
|
7283
|
+
}
|
|
7284
|
+
function withNodeFontSize(node, root) {
|
|
7285
|
+
const fontSize = measureNodeFontSize(node.key, node.type, root);
|
|
7286
|
+
return fontSize === void 0 ? node : { ...node, fontSize };
|
|
7287
|
+
}
|
|
7288
|
+
|
|
7191
7289
|
// src/lib/selectNavContainer.ts
|
|
7192
7290
|
function isOnScreen(el) {
|
|
7193
7291
|
const rect = el.getBoundingClientRect();
|
|
@@ -17127,7 +17225,7 @@ function apiBase() {
|
|
|
17127
17225
|
function bridgeVersion() {
|
|
17128
17226
|
try {
|
|
17129
17227
|
if (true) {
|
|
17130
|
-
return "0.1.
|
|
17228
|
+
return "0.1.160";
|
|
17131
17229
|
}
|
|
17132
17230
|
} catch {
|
|
17133
17231
|
}
|
|
@@ -17141,7 +17239,7 @@ function stampBridgeVersionOnDocument() {
|
|
|
17141
17239
|
return;
|
|
17142
17240
|
}
|
|
17143
17241
|
try {
|
|
17144
|
-
const v = true ? "0.1.
|
|
17242
|
+
const v = true ? "0.1.160" : "";
|
|
17145
17243
|
if (v) {
|
|
17146
17244
|
document.documentElement.setAttribute("data-ohw-bridge-version", v);
|
|
17147
17245
|
window.__OHW_BRIDGE_VERSION__ = v;
|
|
@@ -18050,6 +18148,89 @@ function CarouselOverlay({
|
|
|
18050
18148
|
// src/ui/ai-section/AiSectionOverlay.tsx
|
|
18051
18149
|
import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef4, useState as useState7 } from "react";
|
|
18052
18150
|
import { Check, X } from "lucide-react";
|
|
18151
|
+
|
|
18152
|
+
// src/lib/ai-change-highlight.ts
|
|
18153
|
+
var STYLE_ID = "ohw-ai-changed-style";
|
|
18154
|
+
var MARK_ATTR = "data-ohw-ai-changed";
|
|
18155
|
+
var HIGHLIGHT_MS = 2200;
|
|
18156
|
+
var HIGHLIGHT_CSS = `
|
|
18157
|
+
@keyframes ohw-ai-changed-pulse {
|
|
18158
|
+
0% { outline-color: rgba(0, 120, 229, 0.95); box-shadow: 0 0 0 5px rgba(0, 120, 229, 0.20); }
|
|
18159
|
+
72% { outline-color: rgba(0, 120, 229, 0.95); box-shadow: 0 0 0 5px rgba(0, 120, 229, 0.12); }
|
|
18160
|
+
100% { outline-color: rgba(0, 120, 229, 0); box-shadow: 0 0 0 5px rgba(0, 120, 229, 0); }
|
|
18161
|
+
}
|
|
18162
|
+
[${MARK_ATTR}] {
|
|
18163
|
+
outline: 2px solid rgba(0, 120, 229, 0.95) !important;
|
|
18164
|
+
outline-offset: 3px !important;
|
|
18165
|
+
animation: ohw-ai-changed-pulse ${HIGHLIGHT_MS}ms ease-out forwards !important;
|
|
18166
|
+
}
|
|
18167
|
+
@media (prefers-reduced-motion: reduce) {
|
|
18168
|
+
[${MARK_ATTR}] { animation: none !important; }
|
|
18169
|
+
}
|
|
18170
|
+
`;
|
|
18171
|
+
function ensureStyleSheet2() {
|
|
18172
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
18173
|
+
const el = document.createElement("style");
|
|
18174
|
+
el.id = STYLE_ID;
|
|
18175
|
+
el.textContent = HIGHLIGHT_CSS;
|
|
18176
|
+
document.head.appendChild(el);
|
|
18177
|
+
}
|
|
18178
|
+
function targetsFor(keys, item) {
|
|
18179
|
+
const out = /* @__PURE__ */ new Set();
|
|
18180
|
+
for (const key of keys) {
|
|
18181
|
+
if (!key) continue;
|
|
18182
|
+
for (const el of Array.from(
|
|
18183
|
+
document.querySelectorAll(`[data-ohw-key="${CSS.escape(key)}"]`)
|
|
18184
|
+
)) {
|
|
18185
|
+
out.add(el);
|
|
18186
|
+
}
|
|
18187
|
+
}
|
|
18188
|
+
if (item) {
|
|
18189
|
+
const section = document.querySelector(`[data-ohw-instance="${CSS.escape(item.sectionId)}"]`) ?? document.querySelector(`[data-ohw-section="${CSS.escape(item.sectionId)}"]`);
|
|
18190
|
+
const stamp = `${item.group}:${item.index}`;
|
|
18191
|
+
const el = section?.querySelector(`[data-ohw-item="${CSS.escape(stamp)}"]`);
|
|
18192
|
+
if (el) out.add(el);
|
|
18193
|
+
}
|
|
18194
|
+
return [...out];
|
|
18195
|
+
}
|
|
18196
|
+
var clearTimer = null;
|
|
18197
|
+
function clearChangeHighlight() {
|
|
18198
|
+
if (clearTimer !== null) {
|
|
18199
|
+
clearTimeout(clearTimer);
|
|
18200
|
+
clearTimer = null;
|
|
18201
|
+
}
|
|
18202
|
+
for (const el of Array.from(document.querySelectorAll(`[${MARK_ATTR}]`))) {
|
|
18203
|
+
el.removeAttribute(MARK_ATTR);
|
|
18204
|
+
}
|
|
18205
|
+
}
|
|
18206
|
+
function highlightChangedElements(keys, item) {
|
|
18207
|
+
clearChangeHighlight();
|
|
18208
|
+
const targets = targetsFor(keys, item);
|
|
18209
|
+
if (targets.length === 0) return 0;
|
|
18210
|
+
ensureStyleSheet2();
|
|
18211
|
+
for (const el of targets) el.setAttribute(MARK_ATTR, "");
|
|
18212
|
+
const rect = targets[0].getBoundingClientRect();
|
|
18213
|
+
if (rect.bottom < 0 || rect.top > window.innerHeight) {
|
|
18214
|
+
targets[0].scrollIntoView({ behavior: "smooth", block: "center" });
|
|
18215
|
+
}
|
|
18216
|
+
clearTimer = setTimeout(clearChangeHighlight, HIGHLIGHT_MS);
|
|
18217
|
+
return targets.length;
|
|
18218
|
+
}
|
|
18219
|
+
function installAiChangeHighlight() {
|
|
18220
|
+
const onMessage = (e) => {
|
|
18221
|
+
const data = e.data;
|
|
18222
|
+
if (data?.type !== "ow:ai-highlight") return;
|
|
18223
|
+
const keys = Array.isArray(data.keys) ? data.keys.filter((k) => typeof k === "string") : [];
|
|
18224
|
+
const raw = data.item;
|
|
18225
|
+
const item = raw && typeof raw.sectionId === "string" && typeof raw.group === "string" && typeof raw.index === "number" ? { sectionId: raw.sectionId, group: raw.group, index: raw.index } : void 0;
|
|
18226
|
+
const found = highlightChangedElements(keys, item);
|
|
18227
|
+
window.parent?.postMessage({ type: "ow:ai-highlight-done", found }, "*");
|
|
18228
|
+
};
|
|
18229
|
+
window.addEventListener("message", onMessage);
|
|
18230
|
+
return () => window.removeEventListener("message", onMessage);
|
|
18231
|
+
}
|
|
18232
|
+
|
|
18233
|
+
// src/ui/ai-section/AiSectionOverlay.tsx
|
|
18053
18234
|
import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
18054
18235
|
var findSectionElement = findByInstanceId;
|
|
18055
18236
|
function readRect(instanceId) {
|
|
@@ -18301,6 +18482,7 @@ function AiSectionOverlay({
|
|
|
18301
18482
|
window.addEventListener("message", onMessage);
|
|
18302
18483
|
return () => window.removeEventListener("message", onMessage);
|
|
18303
18484
|
}, [postToParent2]);
|
|
18485
|
+
useEffect5(() => installAiChangeHighlight(), []);
|
|
18304
18486
|
const [hoveredId, setHoveredId] = useState7(null);
|
|
18305
18487
|
useEffect5(() => {
|
|
18306
18488
|
const onMove = (e) => {
|
|
@@ -31220,13 +31402,15 @@ function OhhwellsBridge() {
|
|
|
31220
31402
|
if (e.data?.type !== "ow:collect-section") return;
|
|
31221
31403
|
const sectionId = e.data.sectionId;
|
|
31222
31404
|
if (typeof sectionId !== "string" || !sectionId) return;
|
|
31223
|
-
const
|
|
31224
|
-
|
|
31405
|
+
const escapedSectionId = CSS.escape(sectionId);
|
|
31406
|
+
const instanceEl = document.querySelector(
|
|
31407
|
+
`[data-ohw-instance="${escapedSectionId}"]`
|
|
31225
31408
|
);
|
|
31409
|
+
const foundEl = instanceEl ? sectionElementOf(instanceEl) : document.querySelector(`[data-ohw-section="${escapedSectionId}"]`);
|
|
31226
31410
|
const sectionEl = foundEl && !foundEl.hasAttribute("data-ohw-ai-template-hidden") && !foundEl.hasAttribute("data-ohw-ai-removed") && !foundEl.hasAttribute("data-ohw-ai-replaced-by") ? foundEl : null;
|
|
31227
31411
|
const nodes = sectionEl ? collectEditableNodes(void 0, sectionEl).filter(
|
|
31228
31412
|
(n) => n.key && !n.key.startsWith("__ohw_") && !["meta", "sections", "video-setting"].includes(n.type)
|
|
31229
|
-
).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })) : [];
|
|
31413
|
+
).map((n) => ({ ...n, text: n.text ?? "", type: normalizeAiNodeType(n.type) })).map((n) => withNodeFontSize(n, sectionEl)) : [];
|
|
31230
31414
|
const items = sectionEl ? collectItemGroups(sectionEl) : [];
|
|
31231
31415
|
const box = sectionEl?.getBoundingClientRect();
|
|
31232
31416
|
const rect = box ? {
|
|
@@ -31235,7 +31419,11 @@ function OhhwellsBridge() {
|
|
|
31235
31419
|
width: box.width,
|
|
31236
31420
|
height: box.height
|
|
31237
31421
|
} : null;
|
|
31238
|
-
const settings = resolveSectionSettingsContext(
|
|
31422
|
+
const settings = resolveSectionSettingsContext(
|
|
31423
|
+
sectionId,
|
|
31424
|
+
aiSectionsRef.current,
|
|
31425
|
+
stylesRef.current
|
|
31426
|
+
);
|
|
31239
31427
|
const tree = resolveSectionTree(sectionId, aiSectionsRef.current);
|
|
31240
31428
|
postToParentRef.current({
|
|
31241
31429
|
type: "ow:section-nodes",
|
|
@@ -31966,7 +32154,7 @@ function OhhwellsBridge() {
|
|
|
31966
32154
|
postToParent2({
|
|
31967
32155
|
type: "ow:ready",
|
|
31968
32156
|
version: "1",
|
|
31969
|
-
bridgeVersion: "0.1.
|
|
32157
|
+
bridgeVersion: "0.1.160",
|
|
31970
32158
|
path: pathname,
|
|
31971
32159
|
nodes: collectEditableNodes(editContentRef.current),
|
|
31972
32160
|
sections
|