@ohhwells/bridge 0.1.126 → 0.1.127-next.351
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 +15337 -13005
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -18
- package/dist/index.d.ts +165 -18
- package/dist/index.js +15239 -12922
- package/dist/index.js.map +1 -1
- package/dist/pages.cjs +1 -1
- package/dist/pages.cjs.map +1 -1
- package/dist/pages.js +1 -1
- package/dist/pages.js.map +1 -1
- package/dist/styles.css +43 -0
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -20,14 +20,7 @@ declare function OhhwellsBridge(): react_jsx_runtime.JSX.Element;
|
|
|
20
20
|
*/
|
|
21
21
|
declare const AI_TREE_SCHEMA_VERSIONS: readonly [1];
|
|
22
22
|
type SectionScaffold = 'stacked' | 'split' | 'overlay';
|
|
23
|
-
type AiBlockType = 'text' | 'button' | 'media' | 'media-list' | 'rating' | 'divider' | 'input' | 'section-header' | 'button-row' | 'card' | 'collection' | 'accordion' | 'media-strip' | 'form' | 'embed' | 'schedule-widget' | 'group';
|
|
24
|
-
/** media-strip's `items[]` entry shape (rn-mobile-backend's `itemFields` for that block). */
|
|
25
|
-
interface MediaStripItem {
|
|
26
|
-
assetId: string;
|
|
27
|
-
alt: string;
|
|
28
|
-
href?: string;
|
|
29
|
-
linkTarget?: 'new-tab' | 'same-tab';
|
|
30
|
-
}
|
|
23
|
+
type AiBlockType = 'text' | 'button' | 'media' | 'media-list' | 'rating' | 'divider' | 'input' | 'section-header' | 'button-row' | 'card' | 'collection' | 'accordion' | 'media-strip' | 'form' | 'embed' | 'schedule-widget' | 'group' | 'link-list' | 'hours' | 'logo' | 'social-row' | 'video';
|
|
31
24
|
interface AiBlockNode {
|
|
32
25
|
type: AiBlockType;
|
|
33
26
|
span: number;
|
|
@@ -36,11 +29,8 @@ interface AiBlockNode {
|
|
|
36
29
|
slots?: Record<string, unknown>;
|
|
37
30
|
children?: AiBlockNode[];
|
|
38
31
|
/** Shape is per-block-type, defined by the backend's `BlockDef.itemFields` (accordion's
|
|
39
|
-
* question/answer, media-strip's assetId/alt/href/linkTarget). */
|
|
40
|
-
items?: Array<
|
|
41
|
-
question: string;
|
|
42
|
-
answer: string;
|
|
43
|
-
} | MediaStripItem>;
|
|
32
|
+
* question/answer, media-strip's assetId/alt/href/linkTarget, footer link-list/hours/social-row). */
|
|
33
|
+
items?: Array<Record<string, unknown>>;
|
|
44
34
|
animation?: {
|
|
45
35
|
effect: 'fade-in' | 'fade-in-up' | 'slide-in-left' | 'slide-in-right' | 'zoom-in';
|
|
46
36
|
trigger: 'on-scroll' | 'on-load' | 'on-hover';
|
|
@@ -49,7 +39,15 @@ interface AiBlockNode {
|
|
|
49
39
|
};
|
|
50
40
|
}
|
|
51
41
|
interface AiSectionSettings {
|
|
42
|
+
/** @deprecated OHH-789 folds this into verticalPadding — kept readable so already-persisted
|
|
43
|
+
* trees carrying tight/balanced/airy still render; see legacyVerticalPaddingAlias below. */
|
|
52
44
|
spacing?: 'tight' | 'balanced' | 'airy';
|
|
45
|
+
/** Section-node vertical padding (OHH-789), any scaffold. Default 'm'. 'none' is a true 0px
|
|
46
|
+
* floor, below 'xs', for an explicit "remove the padding" ask. */
|
|
47
|
+
verticalPadding?: 'none' | 'xs' | 's' | 'm' | 'l' | 'xl';
|
|
48
|
+
/** Section-node minimum-height floor (OHH-789), any scaffold. Default 'auto' — a floor, never
|
|
49
|
+
* a fixed height, so content taller than 'half'/'full' always extends the section. */
|
|
50
|
+
minHeight?: 'auto' | 'half' | 'full';
|
|
53
51
|
alignment?: 'left' | 'center';
|
|
54
52
|
ratio?: '1:1' | '5:3' | '3:5';
|
|
55
53
|
verticalPosition?: 'top' | 'center';
|
|
@@ -168,10 +166,16 @@ declare const AI_TREE_TOKENS: {
|
|
|
168
166
|
readonly radiusButton: 10;
|
|
169
167
|
readonly radiusCard: 18;
|
|
170
168
|
readonly sectionMaxWidth: 1280;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
169
|
+
/** 6-step vertical-padding scale (OHH-789) — replaces the deprecated tight/balanced/airy
|
|
170
|
+
* map; `m` keeps the old `balanced` value (64) so already-generated sites render unchanged.
|
|
171
|
+
* `none` is the true zero floor for an explicit "remove the padding" ask. */
|
|
172
|
+
readonly sectionPaddingByScale: {
|
|
173
|
+
readonly none: 0;
|
|
174
|
+
readonly xs: 24;
|
|
175
|
+
readonly s: 40;
|
|
176
|
+
readonly m: 64;
|
|
177
|
+
readonly l: 96;
|
|
178
|
+
readonly xl: 128;
|
|
175
179
|
};
|
|
176
180
|
readonly type: {
|
|
177
181
|
readonly display: {
|
|
@@ -218,6 +222,37 @@ declare const AI_TREE_TOKENS: {
|
|
|
218
222
|
};
|
|
219
223
|
declare function isRenderableTree(value: unknown): value is AiLayoutTree;
|
|
220
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Footer layout-tree contract — bridge-side mirror of
|
|
227
|
+
* `rn-mobile-backend/app/helpers/ai-footer-tree.ts`.
|
|
228
|
+
*
|
|
229
|
+
* The server validates every footer tree fail-closed before it reaches the bridge.
|
|
230
|
+
* Legal links are never on this tree; the renderer injects them from site legal config.
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
declare const FOOTER_COLUMN_SPANS: readonly [12, 6, 4, 3];
|
|
234
|
+
type FooterColumnSpan = (typeof FOOTER_COLUMN_SPANS)[number];
|
|
235
|
+
declare const FOOTER_COLUMN_BLOCKS: readonly ["link-list", "text", "hours", "logo", "social-row", "media-strip"];
|
|
236
|
+
declare const FOOTER_SOCIAL_PLATFORMS: readonly ["facebook", "instagram", "youtube", "linkedin", "twitter", "tiktok", "threads", "pinterest", "whatsapp"];
|
|
237
|
+
interface AiFooterColumn {
|
|
238
|
+
span: FooterColumnSpan;
|
|
239
|
+
blocks: AiBlockNode[];
|
|
240
|
+
}
|
|
241
|
+
/** Protected bottom bar — copyright only; legal links come from site config at render. */
|
|
242
|
+
interface AiFooterBottomBar {
|
|
243
|
+
/** May include `{year}` — resolved at render time. */
|
|
244
|
+
copyright: string;
|
|
245
|
+
}
|
|
246
|
+
interface AiFooterLayoutTree {
|
|
247
|
+
schemaVersion: number;
|
|
248
|
+
type: 'footer';
|
|
249
|
+
columns: AiFooterColumn[];
|
|
250
|
+
bottomBar: AiFooterBottomBar;
|
|
251
|
+
}
|
|
252
|
+
/** Resolve `{year}` in copyright copy — never ship a stale year. */
|
|
253
|
+
declare function resolveFooterCopyright(copyright: string, year?: number): string;
|
|
254
|
+
declare function isRenderableFooterTree(value: unknown): value is AiFooterLayoutTree;
|
|
255
|
+
|
|
221
256
|
interface AiTreeRendererProps {
|
|
222
257
|
tree: AiLayoutTree;
|
|
223
258
|
brand?: AiBrandKit;
|
|
@@ -233,6 +268,118 @@ interface AiTreeRendererProps {
|
|
|
233
268
|
editKeyPrefix?: string;
|
|
234
269
|
}
|
|
235
270
|
declare function AiTreeRenderer({ tree, brand, buttonStyle, resolveMedia, editKeyPrefix, }: AiTreeRendererProps): react_jsx_runtime.JSX.Element | null;
|
|
271
|
+
/** Tier 1 legal links — always rendered from site config, never from the footer tree. */
|
|
272
|
+
interface SiteLegalLinks {
|
|
273
|
+
privacy: {
|
|
274
|
+
label: string;
|
|
275
|
+
href: string;
|
|
276
|
+
};
|
|
277
|
+
terms: {
|
|
278
|
+
label: string;
|
|
279
|
+
href: string;
|
|
280
|
+
};
|
|
281
|
+
/** Cookie-settings control; omit to hide. Trigger only — no editable href. */
|
|
282
|
+
cookieSettings?: {
|
|
283
|
+
label: string;
|
|
284
|
+
onClick?: () => void;
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
interface AiFooterRendererProps {
|
|
288
|
+
tree: AiFooterLayoutTree;
|
|
289
|
+
brand?: AiBrandKit;
|
|
290
|
+
resolveMedia?: (ref: string) => string | null;
|
|
291
|
+
editKeyPrefix?: string;
|
|
292
|
+
/** Brand logo URL for `logo` blocks. */
|
|
293
|
+
logoUrl?: string | null;
|
|
294
|
+
/** Map page/anchor ids → navigable hrefs for link-list internals. */
|
|
295
|
+
resolvePageHref?: (pageOrAnchorId: string) => string | null;
|
|
296
|
+
/** Always painted in the bottom bar (Tier 1). */
|
|
297
|
+
legal: SiteLegalLinks;
|
|
298
|
+
/** Template footer's own surface — retrofit paints on the real band, not the brand's dark. */
|
|
299
|
+
surface?: {
|
|
300
|
+
background: string;
|
|
301
|
+
text: string;
|
|
302
|
+
} | null;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Site-wide footer from a validated footer layout tree .
|
|
306
|
+
* Bottom bar always includes copyright (`{year}` resolved) + legal links from `legal`.
|
|
307
|
+
*/
|
|
308
|
+
declare function AiFooterRenderer({ tree, brand, resolveMedia, editKeyPrefix, logoUrl, resolvePageHref, legal, surface, }: AiFooterRendererProps): react_jsx_runtime.JSX.Element | null;
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Site-wide AI footer persistence .
|
|
312
|
+
*
|
|
313
|
+
* One tree per site under `__ohw_ai_footer` — same Tier 1 guarded namespace as
|
|
314
|
+
* `__ohw_ai_sections`. Absent / empty = keep the template React footer
|
|
315
|
+
* (pixel-identical retrofit). Present = mount AiFooterRenderer and hide the
|
|
316
|
+
* template footer chrome.
|
|
317
|
+
*/
|
|
318
|
+
|
|
319
|
+
declare const AI_FOOTER_KEY = "__ohw_ai_footer";
|
|
320
|
+
interface AiFooterState {
|
|
321
|
+
v: 1;
|
|
322
|
+
tree: AiFooterLayoutTree;
|
|
323
|
+
media?: Record<string, string>;
|
|
324
|
+
brand?: AiBrandKit;
|
|
325
|
+
}
|
|
326
|
+
/** Parse a persisted footer value — malformed state means "no custom footer". */
|
|
327
|
+
declare function parseAiFooterState(raw: string | null | undefined): AiFooterState | null;
|
|
328
|
+
declare function serializeAiFooterState(state: AiFooterState): string;
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Default footer layout tree — bridge mirror of
|
|
332
|
+
* `rn-mobile-backend/app/helpers/ai-footer-default.ts`.
|
|
333
|
+
*
|
|
334
|
+
* Used when an AI edit first materialises a footer tree from the house default,
|
|
335
|
+
* and as the seed for prompt structured edits. Template footers stay until then.
|
|
336
|
+
*/
|
|
337
|
+
|
|
338
|
+
declare function buildDefaultFooterTree(opts?: {
|
|
339
|
+
businessName?: string;
|
|
340
|
+
about?: string;
|
|
341
|
+
pageLinks?: Array<{
|
|
342
|
+
label: string;
|
|
343
|
+
href: string;
|
|
344
|
+
}>;
|
|
345
|
+
}): AiFooterLayoutTree;
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Pure structured edits for the site-wide footer tree .
|
|
349
|
+
* Prompt intents map here; the bridge/persist layer validates via the backend
|
|
350
|
+
* (or client-side shape checks) before applying.
|
|
351
|
+
*/
|
|
352
|
+
|
|
353
|
+
/** Ensure a footer tree exists — seed from the house default when first editing. */
|
|
354
|
+
declare function ensureFooterTree(tree: AiFooterLayoutTree | null | undefined, opts?: {
|
|
355
|
+
businessName?: string;
|
|
356
|
+
}): AiFooterLayoutTree;
|
|
357
|
+
/**
|
|
358
|
+
* Add or update a social platform link. Same platform → update href (no duplicate icons);
|
|
359
|
+
* missing row → create `social-row` in the emptiest column.
|
|
360
|
+
*/
|
|
361
|
+
declare function appendFooterSocial(tree: AiFooterLayoutTree, entry: {
|
|
362
|
+
platform: string;
|
|
363
|
+
href: string;
|
|
364
|
+
}): AiFooterLayoutTree;
|
|
365
|
+
/** Insert an hours block into the emptiest/last column. */
|
|
366
|
+
declare function insertFooterHours(tree: AiFooterLayoutTree, hours: Array<{
|
|
367
|
+
day: string;
|
|
368
|
+
hours: string;
|
|
369
|
+
}>, title?: string): AiFooterLayoutTree;
|
|
370
|
+
/**
|
|
371
|
+
* Add a link-list column (≤4 link columns). Packs brand first so it does not
|
|
372
|
+
* burn a slot. Rejects when already at 4 link-lists (returns original).
|
|
373
|
+
*/
|
|
374
|
+
declare function addFooterLinkColumn(tree: AiFooterLayoutTree, column: {
|
|
375
|
+
title?: string;
|
|
376
|
+
links: Array<{
|
|
377
|
+
label: string;
|
|
378
|
+
href: string;
|
|
379
|
+
}>;
|
|
380
|
+
}): AiFooterLayoutTree;
|
|
381
|
+
/** Remove the first block matching `predicate` (e.g. about text). */
|
|
382
|
+
declare function removeFooterBlock(tree: AiFooterLayoutTree, predicate: (block: AiBlockNode, columnIndex: number, blockIndex: number) => boolean): AiFooterLayoutTree;
|
|
236
383
|
|
|
237
384
|
type LinkPage = {
|
|
238
385
|
path: string;
|
|
@@ -545,4 +692,4 @@ declare function useOhwCarousel(key: string, initial: CarouselSlide[]): {
|
|
|
545
692
|
bind: Record<string, string>;
|
|
546
693
|
};
|
|
547
694
|
|
|
548
|
-
export { AI_DEFAULT_BRAND, AI_TREE_SCHEMA_VERSIONS, AI_TREE_TOKENS, type AiBlockNode, type AiBrandKit, type AiLayoutTree, AiTreeRenderer, type AiTreeRendererProps, type CarouselSlide, CustomToolbar, CustomToolbarButton, CustomToolbarDivider, DragHandle, DropIndicator, type DropIndicatorProps, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptySection, type EmptySectionProps, ItemActionToolbar, type ItemActionToolbarProps, ItemInteractionLayer, type ItemInteractionLayerProps, type ItemInteractionState, LinkEditorPanel, type LinkModalMode, type LinkModalProps, type LinkPage, LinkPopover, type LinkSection, OhhwellsBridge, SchedulingWidget, type SectionScaffold, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, buildTarget, dropIndicatorVariants, filterAvailablePages, getEditModeInitialState, isEditSessionActive, isRenderableTree, isValidUrl, parseTarget, toggleVariants, useOhwCarousel, validateUrlInput };
|
|
695
|
+
export { AI_DEFAULT_BRAND, AI_FOOTER_KEY, AI_TREE_SCHEMA_VERSIONS, AI_TREE_TOKENS, type AiBlockNode, type AiBrandKit, type AiFooterBottomBar, type AiFooterColumn, type AiFooterLayoutTree, AiFooterRenderer, type AiFooterRendererProps, type AiLayoutTree, AiTreeRenderer, type AiTreeRendererProps, type CarouselSlide, CustomToolbar, CustomToolbarButton, CustomToolbarDivider, DragHandle, DropIndicator, type DropIndicatorProps, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptySection, type EmptySectionProps, FOOTER_COLUMN_BLOCKS, FOOTER_COLUMN_SPANS, FOOTER_SOCIAL_PLATFORMS, ItemActionToolbar, type ItemActionToolbarProps, ItemInteractionLayer, type ItemInteractionLayerProps, type ItemInteractionState, LinkEditorPanel, type LinkModalMode, type LinkModalProps, type LinkPage, LinkPopover, type LinkSection, OhhwellsBridge, SchedulingWidget, type SectionScaffold, type SiteLegalLinks, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, addFooterLinkColumn, appendFooterSocial, buildDefaultFooterTree, buildTarget, dropIndicatorVariants, ensureFooterTree, filterAvailablePages, getEditModeInitialState, insertFooterHours, isEditSessionActive, isRenderableFooterTree, isRenderableTree, isValidUrl, parseAiFooterState, parseTarget, removeFooterBlock, resolveFooterCopyright, serializeAiFooterState, toggleVariants, useOhwCarousel, validateUrlInput };
|
package/dist/index.d.ts
CHANGED
|
@@ -20,14 +20,7 @@ declare function OhhwellsBridge(): react_jsx_runtime.JSX.Element;
|
|
|
20
20
|
*/
|
|
21
21
|
declare const AI_TREE_SCHEMA_VERSIONS: readonly [1];
|
|
22
22
|
type SectionScaffold = 'stacked' | 'split' | 'overlay';
|
|
23
|
-
type AiBlockType = 'text' | 'button' | 'media' | 'media-list' | 'rating' | 'divider' | 'input' | 'section-header' | 'button-row' | 'card' | 'collection' | 'accordion' | 'media-strip' | 'form' | 'embed' | 'schedule-widget' | 'group';
|
|
24
|
-
/** media-strip's `items[]` entry shape (rn-mobile-backend's `itemFields` for that block). */
|
|
25
|
-
interface MediaStripItem {
|
|
26
|
-
assetId: string;
|
|
27
|
-
alt: string;
|
|
28
|
-
href?: string;
|
|
29
|
-
linkTarget?: 'new-tab' | 'same-tab';
|
|
30
|
-
}
|
|
23
|
+
type AiBlockType = 'text' | 'button' | 'media' | 'media-list' | 'rating' | 'divider' | 'input' | 'section-header' | 'button-row' | 'card' | 'collection' | 'accordion' | 'media-strip' | 'form' | 'embed' | 'schedule-widget' | 'group' | 'link-list' | 'hours' | 'logo' | 'social-row' | 'video';
|
|
31
24
|
interface AiBlockNode {
|
|
32
25
|
type: AiBlockType;
|
|
33
26
|
span: number;
|
|
@@ -36,11 +29,8 @@ interface AiBlockNode {
|
|
|
36
29
|
slots?: Record<string, unknown>;
|
|
37
30
|
children?: AiBlockNode[];
|
|
38
31
|
/** Shape is per-block-type, defined by the backend's `BlockDef.itemFields` (accordion's
|
|
39
|
-
* question/answer, media-strip's assetId/alt/href/linkTarget). */
|
|
40
|
-
items?: Array<
|
|
41
|
-
question: string;
|
|
42
|
-
answer: string;
|
|
43
|
-
} | MediaStripItem>;
|
|
32
|
+
* question/answer, media-strip's assetId/alt/href/linkTarget, footer link-list/hours/social-row). */
|
|
33
|
+
items?: Array<Record<string, unknown>>;
|
|
44
34
|
animation?: {
|
|
45
35
|
effect: 'fade-in' | 'fade-in-up' | 'slide-in-left' | 'slide-in-right' | 'zoom-in';
|
|
46
36
|
trigger: 'on-scroll' | 'on-load' | 'on-hover';
|
|
@@ -49,7 +39,15 @@ interface AiBlockNode {
|
|
|
49
39
|
};
|
|
50
40
|
}
|
|
51
41
|
interface AiSectionSettings {
|
|
42
|
+
/** @deprecated OHH-789 folds this into verticalPadding — kept readable so already-persisted
|
|
43
|
+
* trees carrying tight/balanced/airy still render; see legacyVerticalPaddingAlias below. */
|
|
52
44
|
spacing?: 'tight' | 'balanced' | 'airy';
|
|
45
|
+
/** Section-node vertical padding (OHH-789), any scaffold. Default 'm'. 'none' is a true 0px
|
|
46
|
+
* floor, below 'xs', for an explicit "remove the padding" ask. */
|
|
47
|
+
verticalPadding?: 'none' | 'xs' | 's' | 'm' | 'l' | 'xl';
|
|
48
|
+
/** Section-node minimum-height floor (OHH-789), any scaffold. Default 'auto' — a floor, never
|
|
49
|
+
* a fixed height, so content taller than 'half'/'full' always extends the section. */
|
|
50
|
+
minHeight?: 'auto' | 'half' | 'full';
|
|
53
51
|
alignment?: 'left' | 'center';
|
|
54
52
|
ratio?: '1:1' | '5:3' | '3:5';
|
|
55
53
|
verticalPosition?: 'top' | 'center';
|
|
@@ -168,10 +166,16 @@ declare const AI_TREE_TOKENS: {
|
|
|
168
166
|
readonly radiusButton: 10;
|
|
169
167
|
readonly radiusCard: 18;
|
|
170
168
|
readonly sectionMaxWidth: 1280;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
169
|
+
/** 6-step vertical-padding scale (OHH-789) — replaces the deprecated tight/balanced/airy
|
|
170
|
+
* map; `m` keeps the old `balanced` value (64) so already-generated sites render unchanged.
|
|
171
|
+
* `none` is the true zero floor for an explicit "remove the padding" ask. */
|
|
172
|
+
readonly sectionPaddingByScale: {
|
|
173
|
+
readonly none: 0;
|
|
174
|
+
readonly xs: 24;
|
|
175
|
+
readonly s: 40;
|
|
176
|
+
readonly m: 64;
|
|
177
|
+
readonly l: 96;
|
|
178
|
+
readonly xl: 128;
|
|
175
179
|
};
|
|
176
180
|
readonly type: {
|
|
177
181
|
readonly display: {
|
|
@@ -218,6 +222,37 @@ declare const AI_TREE_TOKENS: {
|
|
|
218
222
|
};
|
|
219
223
|
declare function isRenderableTree(value: unknown): value is AiLayoutTree;
|
|
220
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Footer layout-tree contract — bridge-side mirror of
|
|
227
|
+
* `rn-mobile-backend/app/helpers/ai-footer-tree.ts`.
|
|
228
|
+
*
|
|
229
|
+
* The server validates every footer tree fail-closed before it reaches the bridge.
|
|
230
|
+
* Legal links are never on this tree; the renderer injects them from site legal config.
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
declare const FOOTER_COLUMN_SPANS: readonly [12, 6, 4, 3];
|
|
234
|
+
type FooterColumnSpan = (typeof FOOTER_COLUMN_SPANS)[number];
|
|
235
|
+
declare const FOOTER_COLUMN_BLOCKS: readonly ["link-list", "text", "hours", "logo", "social-row", "media-strip"];
|
|
236
|
+
declare const FOOTER_SOCIAL_PLATFORMS: readonly ["facebook", "instagram", "youtube", "linkedin", "twitter", "tiktok", "threads", "pinterest", "whatsapp"];
|
|
237
|
+
interface AiFooterColumn {
|
|
238
|
+
span: FooterColumnSpan;
|
|
239
|
+
blocks: AiBlockNode[];
|
|
240
|
+
}
|
|
241
|
+
/** Protected bottom bar — copyright only; legal links come from site config at render. */
|
|
242
|
+
interface AiFooterBottomBar {
|
|
243
|
+
/** May include `{year}` — resolved at render time. */
|
|
244
|
+
copyright: string;
|
|
245
|
+
}
|
|
246
|
+
interface AiFooterLayoutTree {
|
|
247
|
+
schemaVersion: number;
|
|
248
|
+
type: 'footer';
|
|
249
|
+
columns: AiFooterColumn[];
|
|
250
|
+
bottomBar: AiFooterBottomBar;
|
|
251
|
+
}
|
|
252
|
+
/** Resolve `{year}` in copyright copy — never ship a stale year. */
|
|
253
|
+
declare function resolveFooterCopyright(copyright: string, year?: number): string;
|
|
254
|
+
declare function isRenderableFooterTree(value: unknown): value is AiFooterLayoutTree;
|
|
255
|
+
|
|
221
256
|
interface AiTreeRendererProps {
|
|
222
257
|
tree: AiLayoutTree;
|
|
223
258
|
brand?: AiBrandKit;
|
|
@@ -233,6 +268,118 @@ interface AiTreeRendererProps {
|
|
|
233
268
|
editKeyPrefix?: string;
|
|
234
269
|
}
|
|
235
270
|
declare function AiTreeRenderer({ tree, brand, buttonStyle, resolveMedia, editKeyPrefix, }: AiTreeRendererProps): react_jsx_runtime.JSX.Element | null;
|
|
271
|
+
/** Tier 1 legal links — always rendered from site config, never from the footer tree. */
|
|
272
|
+
interface SiteLegalLinks {
|
|
273
|
+
privacy: {
|
|
274
|
+
label: string;
|
|
275
|
+
href: string;
|
|
276
|
+
};
|
|
277
|
+
terms: {
|
|
278
|
+
label: string;
|
|
279
|
+
href: string;
|
|
280
|
+
};
|
|
281
|
+
/** Cookie-settings control; omit to hide. Trigger only — no editable href. */
|
|
282
|
+
cookieSettings?: {
|
|
283
|
+
label: string;
|
|
284
|
+
onClick?: () => void;
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
interface AiFooterRendererProps {
|
|
288
|
+
tree: AiFooterLayoutTree;
|
|
289
|
+
brand?: AiBrandKit;
|
|
290
|
+
resolveMedia?: (ref: string) => string | null;
|
|
291
|
+
editKeyPrefix?: string;
|
|
292
|
+
/** Brand logo URL for `logo` blocks. */
|
|
293
|
+
logoUrl?: string | null;
|
|
294
|
+
/** Map page/anchor ids → navigable hrefs for link-list internals. */
|
|
295
|
+
resolvePageHref?: (pageOrAnchorId: string) => string | null;
|
|
296
|
+
/** Always painted in the bottom bar (Tier 1). */
|
|
297
|
+
legal: SiteLegalLinks;
|
|
298
|
+
/** Template footer's own surface — retrofit paints on the real band, not the brand's dark. */
|
|
299
|
+
surface?: {
|
|
300
|
+
background: string;
|
|
301
|
+
text: string;
|
|
302
|
+
} | null;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Site-wide footer from a validated footer layout tree .
|
|
306
|
+
* Bottom bar always includes copyright (`{year}` resolved) + legal links from `legal`.
|
|
307
|
+
*/
|
|
308
|
+
declare function AiFooterRenderer({ tree, brand, resolveMedia, editKeyPrefix, logoUrl, resolvePageHref, legal, surface, }: AiFooterRendererProps): react_jsx_runtime.JSX.Element | null;
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Site-wide AI footer persistence .
|
|
312
|
+
*
|
|
313
|
+
* One tree per site under `__ohw_ai_footer` — same Tier 1 guarded namespace as
|
|
314
|
+
* `__ohw_ai_sections`. Absent / empty = keep the template React footer
|
|
315
|
+
* (pixel-identical retrofit). Present = mount AiFooterRenderer and hide the
|
|
316
|
+
* template footer chrome.
|
|
317
|
+
*/
|
|
318
|
+
|
|
319
|
+
declare const AI_FOOTER_KEY = "__ohw_ai_footer";
|
|
320
|
+
interface AiFooterState {
|
|
321
|
+
v: 1;
|
|
322
|
+
tree: AiFooterLayoutTree;
|
|
323
|
+
media?: Record<string, string>;
|
|
324
|
+
brand?: AiBrandKit;
|
|
325
|
+
}
|
|
326
|
+
/** Parse a persisted footer value — malformed state means "no custom footer". */
|
|
327
|
+
declare function parseAiFooterState(raw: string | null | undefined): AiFooterState | null;
|
|
328
|
+
declare function serializeAiFooterState(state: AiFooterState): string;
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Default footer layout tree — bridge mirror of
|
|
332
|
+
* `rn-mobile-backend/app/helpers/ai-footer-default.ts`.
|
|
333
|
+
*
|
|
334
|
+
* Used when an AI edit first materialises a footer tree from the house default,
|
|
335
|
+
* and as the seed for prompt structured edits. Template footers stay until then.
|
|
336
|
+
*/
|
|
337
|
+
|
|
338
|
+
declare function buildDefaultFooterTree(opts?: {
|
|
339
|
+
businessName?: string;
|
|
340
|
+
about?: string;
|
|
341
|
+
pageLinks?: Array<{
|
|
342
|
+
label: string;
|
|
343
|
+
href: string;
|
|
344
|
+
}>;
|
|
345
|
+
}): AiFooterLayoutTree;
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Pure structured edits for the site-wide footer tree .
|
|
349
|
+
* Prompt intents map here; the bridge/persist layer validates via the backend
|
|
350
|
+
* (or client-side shape checks) before applying.
|
|
351
|
+
*/
|
|
352
|
+
|
|
353
|
+
/** Ensure a footer tree exists — seed from the house default when first editing. */
|
|
354
|
+
declare function ensureFooterTree(tree: AiFooterLayoutTree | null | undefined, opts?: {
|
|
355
|
+
businessName?: string;
|
|
356
|
+
}): AiFooterLayoutTree;
|
|
357
|
+
/**
|
|
358
|
+
* Add or update a social platform link. Same platform → update href (no duplicate icons);
|
|
359
|
+
* missing row → create `social-row` in the emptiest column.
|
|
360
|
+
*/
|
|
361
|
+
declare function appendFooterSocial(tree: AiFooterLayoutTree, entry: {
|
|
362
|
+
platform: string;
|
|
363
|
+
href: string;
|
|
364
|
+
}): AiFooterLayoutTree;
|
|
365
|
+
/** Insert an hours block into the emptiest/last column. */
|
|
366
|
+
declare function insertFooterHours(tree: AiFooterLayoutTree, hours: Array<{
|
|
367
|
+
day: string;
|
|
368
|
+
hours: string;
|
|
369
|
+
}>, title?: string): AiFooterLayoutTree;
|
|
370
|
+
/**
|
|
371
|
+
* Add a link-list column (≤4 link columns). Packs brand first so it does not
|
|
372
|
+
* burn a slot. Rejects when already at 4 link-lists (returns original).
|
|
373
|
+
*/
|
|
374
|
+
declare function addFooterLinkColumn(tree: AiFooterLayoutTree, column: {
|
|
375
|
+
title?: string;
|
|
376
|
+
links: Array<{
|
|
377
|
+
label: string;
|
|
378
|
+
href: string;
|
|
379
|
+
}>;
|
|
380
|
+
}): AiFooterLayoutTree;
|
|
381
|
+
/** Remove the first block matching `predicate` (e.g. about text). */
|
|
382
|
+
declare function removeFooterBlock(tree: AiFooterLayoutTree, predicate: (block: AiBlockNode, columnIndex: number, blockIndex: number) => boolean): AiFooterLayoutTree;
|
|
236
383
|
|
|
237
384
|
type LinkPage = {
|
|
238
385
|
path: string;
|
|
@@ -545,4 +692,4 @@ declare function useOhwCarousel(key: string, initial: CarouselSlide[]): {
|
|
|
545
692
|
bind: Record<string, string>;
|
|
546
693
|
};
|
|
547
694
|
|
|
548
|
-
export { AI_DEFAULT_BRAND, AI_TREE_SCHEMA_VERSIONS, AI_TREE_TOKENS, type AiBlockNode, type AiBrandKit, type AiLayoutTree, AiTreeRenderer, type AiTreeRendererProps, type CarouselSlide, CustomToolbar, CustomToolbarButton, CustomToolbarDivider, DragHandle, DropIndicator, type DropIndicatorProps, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptySection, type EmptySectionProps, ItemActionToolbar, type ItemActionToolbarProps, ItemInteractionLayer, type ItemInteractionLayerProps, type ItemInteractionState, LinkEditorPanel, type LinkModalMode, type LinkModalProps, type LinkPage, LinkPopover, type LinkSection, OhhwellsBridge, SchedulingWidget, type SectionScaffold, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, buildTarget, dropIndicatorVariants, filterAvailablePages, getEditModeInitialState, isEditSessionActive, isRenderableTree, isValidUrl, parseTarget, toggleVariants, useOhwCarousel, validateUrlInput };
|
|
695
|
+
export { AI_DEFAULT_BRAND, AI_FOOTER_KEY, AI_TREE_SCHEMA_VERSIONS, AI_TREE_TOKENS, type AiBlockNode, type AiBrandKit, type AiFooterBottomBar, type AiFooterColumn, type AiFooterLayoutTree, AiFooterRenderer, type AiFooterRendererProps, type AiLayoutTree, AiTreeRenderer, type AiTreeRendererProps, type CarouselSlide, CustomToolbar, CustomToolbarButton, CustomToolbarDivider, DragHandle, DropIndicator, type DropIndicatorProps, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptySection, type EmptySectionProps, FOOTER_COLUMN_BLOCKS, FOOTER_COLUMN_SPANS, FOOTER_SOCIAL_PLATFORMS, ItemActionToolbar, type ItemActionToolbarProps, ItemInteractionLayer, type ItemInteractionLayerProps, type ItemInteractionState, LinkEditorPanel, type LinkModalMode, type LinkModalProps, type LinkPage, LinkPopover, type LinkSection, OhhwellsBridge, SchedulingWidget, type SectionScaffold, type SiteLegalLinks, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, addFooterLinkColumn, appendFooterSocial, buildDefaultFooterTree, buildTarget, dropIndicatorVariants, ensureFooterTree, filterAvailablePages, getEditModeInitialState, insertFooterHours, isEditSessionActive, isRenderableFooterTree, isRenderableTree, isValidUrl, parseAiFooterState, parseTarget, removeFooterBlock, resolveFooterCopyright, serializeAiFooterState, toggleVariants, useOhwCarousel, validateUrlInput };
|