@bettercms-ai/types 1.5.0 → 1.6.0
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.d.ts +151 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,11 @@ type AlignToken = "left" | "center" | "right";
|
|
|
40
40
|
type FontSizeToken = "sm" | "base" | "lg" | "xl" | "2xl";
|
|
41
41
|
type WeightToken = "normal" | "medium" | "semibold" | "bold";
|
|
42
42
|
type RadiusToken = "none" | "sm" | "md" | "lg" | "full";
|
|
43
|
+
type ThemeToken = "inherit" | "light" | "dark";
|
|
44
|
+
type BgToken = "none" | "surface" | "muted" | "accent" | "dark" | "custom";
|
|
45
|
+
type ContentWidthToken = "narrow" | "default" | "wide" | "full";
|
|
46
|
+
type CornerToken = "0" | "s" | "m" | "l" | "xl" | "2xl";
|
|
47
|
+
type ShadowToken = "0" | "s" | "m" | "l" | "xl";
|
|
43
48
|
interface BlockStyle {
|
|
44
49
|
paddingY?: SpaceToken;
|
|
45
50
|
paddingX?: SpaceToken;
|
|
@@ -51,6 +56,24 @@ interface BlockStyle {
|
|
|
51
56
|
fontSize?: FontSizeToken;
|
|
52
57
|
weight?: WeightToken;
|
|
53
58
|
radius?: RadiusToken;
|
|
59
|
+
theme?: ThemeToken;
|
|
60
|
+
bg?: BgToken;
|
|
61
|
+
/** Only read when `bg === "custom"`. Hex only. */
|
|
62
|
+
bgCustom?: string;
|
|
63
|
+
/** Padding sliders, in px (0–240). */
|
|
64
|
+
paddingTop?: number;
|
|
65
|
+
paddingBottom?: number;
|
|
66
|
+
paddingSides?: number;
|
|
67
|
+
contentWidth?: ContentWidthToken;
|
|
68
|
+
fullHeight?: boolean;
|
|
69
|
+
/** 0–100. */
|
|
70
|
+
opacity?: number;
|
|
71
|
+
corner?: CornerToken;
|
|
72
|
+
shadow?: ShadowToken;
|
|
73
|
+
borderTop?: boolean;
|
|
74
|
+
borderBottom?: boolean;
|
|
75
|
+
/** 🔴 The one free-form string. MUST go through `safeCssUrl()` before reaching CSS. */
|
|
76
|
+
bgImage?: string;
|
|
54
77
|
}
|
|
55
78
|
interface HeadingProps {
|
|
56
79
|
text: string;
|
|
@@ -71,6 +94,67 @@ interface TextBlock {
|
|
|
71
94
|
style?: BlockStyle;
|
|
72
95
|
props: TextProps;
|
|
73
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* A rich-text block: the same HTML payload as `text`, authored through a real editor.
|
|
99
|
+
*
|
|
100
|
+
* WHY A SEPARATE TYPE RATHER THAN CHANGING `text`. Every `text` block already on a live page
|
|
101
|
+
* holds an HTML string that the delivery renderer injects as-is. Repurposing that type would
|
|
102
|
+
* mean migrating published content, and getting it wrong renders visible markup to visitors.
|
|
103
|
+
* A new type costs nothing at rest and leaves existing pages untouched.
|
|
104
|
+
*
|
|
105
|
+
* The payload is deliberately IDENTICAL to TextProps (an HTML string) so the two are
|
|
106
|
+
* interchangeable at the renderer: delivery treats them the same, and the difference is
|
|
107
|
+
* entirely in how an author edits them — `text` is a plain field, `richtext` gets the
|
|
108
|
+
* formatting surface.
|
|
109
|
+
*/
|
|
110
|
+
/**
|
|
111
|
+
* A Portable Text node — the structured form of rich text.
|
|
112
|
+
*
|
|
113
|
+
* Kept alongside `html` rather than replacing it, so nothing already published moves. The
|
|
114
|
+
* structure exists because an HTML string cannot carry INTENT: a colour is a hex with no
|
|
115
|
+
* notion of which brand token produced it, an embedded component is a div with no reference
|
|
116
|
+
* to resolve, and neither can be read back by tooling. Nodes can carry all of that.
|
|
117
|
+
*/
|
|
118
|
+
interface PortableTextNode {
|
|
119
|
+
_type: string;
|
|
120
|
+
_key?: string;
|
|
121
|
+
/** Block style — "normal", "h2", "blockquote". */
|
|
122
|
+
style?: string;
|
|
123
|
+
text?: string;
|
|
124
|
+
children?: PortableTextNode[];
|
|
125
|
+
/** Marks on a span: "strong", "em", or a token reference like "color:brand-accent". */
|
|
126
|
+
marks?: string[];
|
|
127
|
+
/** An embedded component or reference resolves through these. */
|
|
128
|
+
schemaKey?: string;
|
|
129
|
+
_ref?: string;
|
|
130
|
+
fields?: Record<string, unknown>;
|
|
131
|
+
}
|
|
132
|
+
interface PortableTextDocument {
|
|
133
|
+
version: 1;
|
|
134
|
+
content: PortableTextNode[];
|
|
135
|
+
}
|
|
136
|
+
interface RichTextProps {
|
|
137
|
+
/**
|
|
138
|
+
* The RENDERED CACHE. Delivery reads this, so the fast path stays a string interpolation
|
|
139
|
+
* and every page published before `content` existed keeps rendering byte-identically.
|
|
140
|
+
*/
|
|
141
|
+
html: string;
|
|
142
|
+
/**
|
|
143
|
+
* The SOURCE OF TRUTH, when present — `content` always wins over `html`.
|
|
144
|
+
*
|
|
145
|
+
* Optional on purpose: this is the same envelope the field values already use
|
|
146
|
+
* (`{ format, value, html }`), where `html` is likewise derived. Any writer that sets
|
|
147
|
+
* `html` alone on a block that HAS `content` must delete `content` in the same write, or
|
|
148
|
+
* the stale string silently shadows the newer structure.
|
|
149
|
+
*/
|
|
150
|
+
content?: PortableTextDocument;
|
|
151
|
+
}
|
|
152
|
+
interface RichTextBlock {
|
|
153
|
+
type: "richtext";
|
|
154
|
+
id: string;
|
|
155
|
+
style?: BlockStyle;
|
|
156
|
+
props: RichTextProps;
|
|
157
|
+
}
|
|
74
158
|
interface ImageProps {
|
|
75
159
|
src: string;
|
|
76
160
|
alt: string;
|
|
@@ -223,7 +307,7 @@ interface ComponentBlock {
|
|
|
223
307
|
style?: BlockStyle;
|
|
224
308
|
props: ComponentProps;
|
|
225
309
|
}
|
|
226
|
-
type ContentBlock = HeadingBlock | TextBlock | ImageBlock | ButtonBlock | SpacerBlock | ColumnsBlock | VideoBlock | FormBlock | SectionBlock | NavbarBlock | FooterBlock | SliderBlock | TabsBlock | ComponentBlock;
|
|
310
|
+
type ContentBlock = HeadingBlock | TextBlock | RichTextBlock | ImageBlock | ButtonBlock | SpacerBlock | ColumnsBlock | VideoBlock | FormBlock | SectionBlock | NavbarBlock | FooterBlock | SliderBlock | TabsBlock | ComponentBlock;
|
|
227
311
|
/** Literal union of all block type discriminators. */
|
|
228
312
|
type BlockType = ContentBlock["type"];
|
|
229
313
|
/**
|
|
@@ -249,7 +333,7 @@ declare function getBlockType(value: unknown): BlockType | null;
|
|
|
249
333
|
/**
|
|
250
334
|
* FLO-264 — bounded block-style tokens → CSS. Framework-agnostic (returns a plain
|
|
251
335
|
* declaration object, no React types) so every renderer shares ONE mapping: the
|
|
252
|
-
* headless @
|
|
336
|
+
* headless @bettercms-ai/next adapter, @bettercms-ai/ui, and (string form) the hosted
|
|
253
337
|
* render-page.ts. Unknown tokens are dropped — closed set, no arbitrary CSS.
|
|
254
338
|
*/
|
|
255
339
|
|
|
@@ -264,8 +348,12 @@ declare function blockStyleToCss(style: BlockStyle | undefined): Record<string,
|
|
|
264
348
|
* `props` declares a flat allowlist of values an instance may override.
|
|
265
349
|
*/
|
|
266
350
|
|
|
267
|
-
/**
|
|
268
|
-
|
|
351
|
+
/**
|
|
352
|
+
* The category a component belongs to (drives library grouping + icons). The first eight
|
|
353
|
+
* are structural (what the component IS); the last four are the "Add a section" library
|
|
354
|
+
* tabs (where it appears). One varchar column, not a PG enum — widening is code-only.
|
|
355
|
+
*/
|
|
356
|
+
type ComponentCategory = "navbar" | "footer" | "button" | "section" | "slider" | "tabs" | "form" | "custom" | "hero" | "content" | "social-proof" | "conversion";
|
|
269
357
|
/**
|
|
270
358
|
* A single overridable field on a component. `target` points at the block + JSON
|
|
271
359
|
* path inside `blockJson` the override writes to (e.g. blockId "cta", path
|
|
@@ -289,6 +377,12 @@ interface Component {
|
|
|
289
377
|
name: string;
|
|
290
378
|
slug: string;
|
|
291
379
|
category: ComponentCategory;
|
|
380
|
+
/**
|
|
381
|
+
* Section library: the family this component is a LAYOUT of, e.g. "Hero". `name` is the
|
|
382
|
+
* layout label ("Centered"), so components sharing a sectionType are selectable variants
|
|
383
|
+
* of one section. Null/absent = an ordinary component, not shown in the section library.
|
|
384
|
+
*/
|
|
385
|
+
sectionType?: string | null;
|
|
292
386
|
description?: string | null;
|
|
293
387
|
blockJson: ContentBlock[];
|
|
294
388
|
props: ComponentPropDef[];
|
|
@@ -302,6 +396,7 @@ interface DeliveryComponent {
|
|
|
302
396
|
name: string;
|
|
303
397
|
slug: string;
|
|
304
398
|
category: ComponentCategory;
|
|
399
|
+
sectionType?: string | null;
|
|
305
400
|
blockJson: ContentBlock[];
|
|
306
401
|
props: ComponentPropDef[];
|
|
307
402
|
}
|
|
@@ -338,7 +433,7 @@ interface ContentModel {
|
|
|
338
433
|
* by the API but normalized to `array` + zones on write, so stored/delivered
|
|
339
434
|
* content never contains them.
|
|
340
435
|
*/
|
|
341
|
-
type ContentModelFieldType = "text" | "richtext" | "image" | "boolean" | "number" | "select" | "reference" | "multi-reference" | "array" | "date" | "datetime";
|
|
436
|
+
type ContentModelFieldType = "text" | "richtext" | "image" | "boolean" | "number" | "select" | "reference" | "multi-reference" | "multiReference" | "array" | "date" | "datetime" | "longtext" | "slug" | "email" | "phone" | "link" | "color" | "json" | "file" | "component-ref";
|
|
342
437
|
/**
|
|
343
438
|
* Nested-content config for an `array` field. A zone holds ordinary fields, and a
|
|
344
439
|
* zone field may itself be an `array` with its own zones, so nesting is recursive
|
|
@@ -369,6 +464,31 @@ interface FieldConfig {
|
|
|
369
464
|
includeTime?: boolean;
|
|
370
465
|
[key: string]: unknown;
|
|
371
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Where a field renders in the entry editor.
|
|
469
|
+
*
|
|
470
|
+
* AUTHORING ONLY — this has no delivery or storage effect. It does not change the
|
|
471
|
+
* shape of `data`, is not read by any renderer in @bettercms-ai/next or /astro, and
|
|
472
|
+
* cannot move a value onto or off a published page. It decides one thing: where the
|
|
473
|
+
* control sits while somebody is writing.
|
|
474
|
+
*
|
|
475
|
+
* Granularity is REGION-level, never interleaved between body blocks: the canvas is
|
|
476
|
+
* `[hero slots] → [free block flow] → [inline fields] → [panel fields in the drawer]`.
|
|
477
|
+
* A field cannot be positioned "between two paragraphs" — there is no coordinate space
|
|
478
|
+
* for that, and inventing one would mean mounting form controls inside the Lexical tree.
|
|
479
|
+
*
|
|
480
|
+
* - `cover` / `title` / `excerpt` / `body` — the four hero slots, rendered unlabelled
|
|
481
|
+
* and typographically native. SCHEMA-LEVEL ONLY: a per-entry override may never
|
|
482
|
+
* claim one, or one author's click renders anonymous 40px text over anonymous text
|
|
483
|
+
* on that entry alone.
|
|
484
|
+
* - `inline` — in the canvas, below the block flow, labelled.
|
|
485
|
+
* - `panel` — in the drawer.
|
|
486
|
+
*
|
|
487
|
+
* ABSENT is meaningful and is the default for every model that predates this: the
|
|
488
|
+
* editor falls back to its title-gated heuristic. Only an explicit value promotes a
|
|
489
|
+
* field past that gate.
|
|
490
|
+
*/
|
|
491
|
+
type ContentModelFieldPlacement = "cover" | "title" | "excerpt" | "body" | "inline" | "panel";
|
|
372
492
|
interface ContentModelField {
|
|
373
493
|
key: string;
|
|
374
494
|
label: string;
|
|
@@ -378,6 +498,11 @@ interface ContentModelField {
|
|
|
378
498
|
options?: string[];
|
|
379
499
|
/** Per-type config. For `array`: either `itemType` (primitive) or `zones` (nested). */
|
|
380
500
|
config?: FieldConfig | null;
|
|
501
|
+
/**
|
|
502
|
+
* Authoring-only editor placement. See {@link ContentModelFieldPlacement}.
|
|
503
|
+
* Field ORDER is the array index — there is deliberately no `order` property.
|
|
504
|
+
*/
|
|
505
|
+
placement?: ContentModelFieldPlacement;
|
|
381
506
|
}
|
|
382
507
|
/**
|
|
383
508
|
* A field inside an array zone — identical to a top-level {@link ContentModelField}.
|
|
@@ -428,6 +553,26 @@ interface DeliveryEntry<T = Record<string, unknown>> {
|
|
|
428
553
|
depth: number;
|
|
429
554
|
cached?: boolean;
|
|
430
555
|
preview?: boolean;
|
|
556
|
+
/**
|
|
557
|
+
* The model's field list as an authoring HINT — optional, additive, and safe to
|
|
558
|
+
* ignore. Present so a consumer CAN lay an entry out generically instead of
|
|
559
|
+
* hand-writing every field; nothing requires it, and every existing template that
|
|
560
|
+
* reads `entry.data.title` keeps working untouched.
|
|
561
|
+
*
|
|
562
|
+
* `order` is the field's index in the model's own array — the schema has no separate
|
|
563
|
+
* order property, so there is nothing here that can drift out of sync with it.
|
|
564
|
+
* `slot` is present only where the model explicitly placed a field, and reflects
|
|
565
|
+
* where it sits in the BetterCMS editor. It does not describe, and must not be read
|
|
566
|
+
* as, where the value belongs on your page: placement is authoring-only.
|
|
567
|
+
*
|
|
568
|
+
* Treat this as changeable. It is a hint, not part of the payload contract.
|
|
569
|
+
*/
|
|
570
|
+
fields?: Array<{
|
|
571
|
+
key: string;
|
|
572
|
+
type: string;
|
|
573
|
+
order: number;
|
|
574
|
+
slot?: "cover" | "title" | "excerpt" | "body" | "inline" | "panel";
|
|
575
|
+
}>;
|
|
431
576
|
};
|
|
432
577
|
}
|
|
433
578
|
/**
|
|
@@ -649,4 +794,4 @@ type DeepReadonly<T> = T extends Function ? T : T extends object ? {
|
|
|
649
794
|
*/
|
|
650
795
|
type AssertEqual<L, R> = [L] extends [R] ? ([R] extends [L] ? true : false) : false;
|
|
651
796
|
|
|
652
|
-
export { type AlignToken, type ApiError, type ApiKey, type ApiKeyPermission, type ApiKeyTokenType, type ArrayZoneConfig, type AssertEqual, type AuthResponse, type AuthSession, type AuthUser, type BetterCMSErrorCode, type BlockStyle, type BlockType, type ButtonBlock, type ButtonProps, type ColorToken, type ColumnsBlock, type ColumnsProps, type Component, type ComponentBlock, type ComponentCategory, type ComponentPropDef, type ComponentProps, type Content, type ContentBlock, type ContentEntry, type ContentModel, type ContentModelField, type ContentModelFieldType, type ContentPageResult, type ContentResponse, type DeepReadonly, type DeliveryComponent, type DeliveryEntry, type DeliveryList, type DeliveryPage, type FieldConfig, type FontSizeToken, type FooterBlock, type FooterColumn, type FooterProps, type Form, type FormBlock, type FormField, type FormProps, type FormSubmission, type HeadingBlock, type HeadingProps, type ImageBlock, type ImageProps, type MediaAsset, type NavLink, type NavbarBlock, type NavbarProps, type Page, type PageMetaJson, type PaginatedResult, type Perspective, type RadiusToken, type Redirect, type SectionBlock, type SectionProps, type SignInInput, type SignUpInput, type SiteSeoDefaults, type SlideItem, type SliderBlock, type SliderProps, type SpaceToken, type SpacerBlock, type SpacerProps, type TabItem, type TabsBlock, type TabsProps, type TextBlock, type TextProps, type VideoBlock, type VideoProps, type WeightToken, type Workspace, type ZoneField, blockStyleToCss, getBlockType, isBlock };
|
|
797
|
+
export { type AlignToken, type ApiError, type ApiKey, type ApiKeyPermission, type ApiKeyTokenType, type ArrayZoneConfig, type AssertEqual, type AuthResponse, type AuthSession, type AuthUser, type BetterCMSErrorCode, type BlockStyle, type BlockType, type ButtonBlock, type ButtonProps, type ColorToken, type ColumnsBlock, type ColumnsProps, type Component, type ComponentBlock, type ComponentCategory, type ComponentPropDef, type ComponentProps, type Content, type ContentBlock, type ContentEntry, type ContentModel, type ContentModelField, type ContentModelFieldType, type ContentPageResult, type ContentResponse, type DeepReadonly, type DeliveryComponent, type DeliveryEntry, type DeliveryList, type DeliveryPage, type FieldConfig, type FontSizeToken, type FooterBlock, type FooterColumn, type FooterProps, type Form, type FormBlock, type FormField, type FormProps, type FormSubmission, type HeadingBlock, type HeadingProps, type ImageBlock, type ImageProps, type MediaAsset, type NavLink, type NavbarBlock, type NavbarProps, type Page, type PageMetaJson, type PaginatedResult, type Perspective, type PortableTextDocument, type PortableTextNode, type RadiusToken, type Redirect, type RichTextBlock, type RichTextProps, type SectionBlock, type SectionProps, type SignInInput, type SignUpInput, type SiteSeoDefaults, type SlideItem, type SliderBlock, type SliderProps, type SpaceToken, type SpacerBlock, type SpacerProps, type TabItem, type TabsBlock, type TabsProps, type TextBlock, type TextProps, type VideoBlock, type VideoProps, type WeightToken, type Workspace, type ZoneField, blockStyleToCss, getBlockType, isBlock };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/block.ts","../src/blockStyle.ts"],"sourcesContent":["/**\n * ContentBlock — a discriminated union of all CMS block types.\n * Each block has a `type` discriminator and a `props` object\n * whose shape matches the block type.\n */\n\n// ── Block style (FLO-264) ──────────────────────────────────────────────────────\n// A uniform, BOUNDED set of design tokens any block may carry (sibling to `props`).\n// Tokens map to fixed CSS values in the renderer (`styleTokensToCss`) so styling\n// reaches the live site WITHOUT arbitrary CSS. Additive + fully optional.\n\nexport type SpaceToken = \"none\" | \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\nexport type ColorToken = \"none\" | \"white\" | \"black\" | \"muted\" | \"mutedBg\" | \"primary\" | \"accent\";\nexport type AlignToken = \"left\" | \"center\" | \"right\";\nexport type FontSizeToken = \"sm\" | \"base\" | \"lg\" | \"xl\" | \"2xl\";\nexport type WeightToken = \"normal\" | \"medium\" | \"semibold\" | \"bold\";\nexport type RadiusToken = \"none\" | \"sm\" | \"md\" | \"lg\" | \"full\";\n\nexport interface BlockStyle {\n paddingY?: SpaceToken;\n paddingX?: SpaceToken;\n marginTop?: SpaceToken;\n marginBottom?: SpaceToken;\n background?: ColorToken;\n textColor?: ColorToken;\n align?: AlignToken;\n fontSize?: FontSizeToken;\n weight?: WeightToken;\n radius?: RadiusToken;\n}\n\n// ── Heading ──────────────────────────────────────────────────────────────────\n\nexport interface HeadingProps {\n text: string;\n level?: 1 | 2 | 3;\n}\n\nexport interface HeadingBlock {\n type: \"heading\";\n id: string;\n style?: BlockStyle;\n props: HeadingProps;\n}\n\n// ── Text ─────────────────────────────────────────────────────────────────────\n\nexport interface TextProps {\n html: string;\n}\n\nexport interface TextBlock {\n type: \"text\";\n id: string;\n style?: BlockStyle;\n props: TextProps;\n}\n\n// ── Image ────────────────────────────────────────────────────────────────────\n\nexport interface ImageProps {\n src: string;\n alt: string;\n width?: number;\n height?: number;\n caption?: string;\n}\n\nexport interface ImageBlock {\n type: \"image\";\n id: string;\n style?: BlockStyle;\n props: ImageProps;\n}\n\n// ── Button ───────────────────────────────────────────────────────────────────\n\nexport interface ButtonProps {\n text: string;\n href: string;\n variant?: \"primary\" | \"secondary\";\n}\n\nexport interface ButtonBlock {\n type: \"button\";\n id: string;\n style?: BlockStyle;\n props: ButtonProps;\n}\n\n// ── Spacer ───────────────────────────────────────────────────────────────────\n\nexport interface SpacerProps {\n height: number; // in px\n}\n\nexport interface SpacerBlock {\n type: \"spacer\";\n id: string;\n style?: BlockStyle;\n props: SpacerProps;\n}\n\n// ── Columns ───────────────────────────────────────────────────────────────────\n\nexport interface ColumnsProps {\n columns: ContentBlock[][]; // array of column slots, each slot is an array of blocks\n gap?: number; // in px\n}\n\nexport interface ColumnsBlock {\n type: \"columns\";\n id: string;\n style?: BlockStyle;\n props: ColumnsProps;\n}\n\n// ── Video ────────────────────────────────────────────────────────────────────\n\nexport interface VideoProps {\n url: string;\n poster?: string;\n autoplay?: boolean;\n loop?: boolean;\n}\n\nexport interface VideoBlock {\n type: \"video\";\n id: string;\n style?: BlockStyle;\n props: VideoProps;\n}\n\n// ── Form ─────────────────────────────────────────────────────────────────────\n\nexport interface FormProps {\n formId: string; // references forms.id (a nanoid). Resolved against delivered forms at render time.\n}\n\nexport interface FormBlock {\n type: \"form\";\n id: string;\n style?: BlockStyle;\n props: FormProps;\n}\n\n// ── Section (recursive container) ──────────────────────────────────────────────\n\nexport interface SectionProps {\n children: ContentBlock[];\n background?: string;\n paddingY?: number; // in px\n maxWidth?: number; // in px\n}\n\nexport interface SectionBlock {\n type: \"section\";\n id: string;\n style?: BlockStyle;\n props: SectionProps;\n}\n\n// ── Navbar ─────────────────────────────────────────────────────────────────────\n\nexport interface NavLink {\n label: string;\n href: string;\n}\n\nexport interface NavbarProps {\n logo?: { src: string; alt: string; href?: string };\n links: NavLink[];\n cta?: { text: string; href: string };\n}\n\nexport interface NavbarBlock {\n type: \"navbar\";\n id: string;\n style?: BlockStyle;\n props: NavbarProps;\n}\n\n// ── Footer ─────────────────────────────────────────────────────────────────────\n\nexport interface FooterColumn {\n heading?: string;\n links: NavLink[];\n}\n\nexport interface FooterProps {\n columns: FooterColumn[];\n copyright?: string;\n}\n\nexport interface FooterBlock {\n type: \"footer\";\n id: string;\n style?: BlockStyle;\n props: FooterProps;\n}\n\n// ── Slider / Carousel (recursive container, client-hydrated) ────────────────────\n\nexport interface SlideItem {\n id: string;\n children: ContentBlock[];\n}\n\nexport interface SliderProps {\n slides: SlideItem[];\n autoplay?: boolean;\n interval?: number; // in ms\n loop?: boolean;\n}\n\nexport interface SliderBlock {\n type: \"slider\";\n id: string;\n style?: BlockStyle;\n props: SliderProps;\n}\n\n// ── Tabs (recursive container, client-hydrated) ─────────────────────────────────\n\nexport interface TabItem {\n id: string;\n label: string;\n children: ContentBlock[];\n}\n\nexport interface TabsProps {\n tabs: TabItem[];\n}\n\nexport interface TabsBlock {\n type: \"tabs\";\n id: string;\n style?: BlockStyle;\n props: TabsProps;\n}\n\n// ── Component instance (Webflow-style reusable symbol) ──────────────────────────\n// References a reusable component by id. `overrides` maps a component prop key\n// (declared in the component's `props[]` allowlist) to a value, applied to the\n// component's block tree at render time. Resolved against delivered components.\n\nexport interface ComponentProps {\n componentId: string;\n overrides?: Record<string, unknown>;\n}\n\nexport interface ComponentBlock {\n type: \"component\";\n id: string;\n style?: BlockStyle;\n props: ComponentProps;\n}\n\n// ── Union ─────────────────────────────────────────────────────────────────────\n\nexport type ContentBlock =\n | HeadingBlock\n | TextBlock\n | ImageBlock\n | ButtonBlock\n | SpacerBlock\n | ColumnsBlock\n | VideoBlock\n | FormBlock\n | SectionBlock\n | NavbarBlock\n | FooterBlock\n | SliderBlock\n | TabsBlock\n | ComponentBlock;\n\n/** Literal union of all block type discriminators. */\nexport type BlockType = ContentBlock[\"type\"];\n\n/**\n * Type guard — narrows an unknown block to `ContentBlock`.\n *\n * @example\n * const block = maybeBlock as unknown;\n * if (isBlock(block)) {\n * // block is ContentBlock here\n * }\n */\nexport function isBlock(value: unknown): value is ContentBlock {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"type\" in value &&\n typeof (value as Record<string, unknown>)[\"type\"] === \"string\"\n );\n}\n\n/**\n * Extract the `type` discriminator from a block, or `null` if not a block.\n *\n * @example\n * const block = { type: \"heading\", id: \"1\", props: { text: \"Hi\" } };\n * getBlockType(block); // \"heading\"\n * getBlockType({ not: \"a block\" }); // null\n */\nexport function getBlockType(value: unknown): BlockType | null {\n if (isBlock(value)) return value.type;\n return null;\n}\n","/**\n * FLO-264 — bounded block-style tokens → CSS. Framework-agnostic (returns a plain\n * declaration object, no React types) so every renderer shares ONE mapping: the\n * headless @betttercms/next adapter, @betttercms/ui, and (string form) the hosted\n * render-page.ts. Unknown tokens are dropped — closed set, no arbitrary CSS.\n */\nimport type { BlockStyle } from \"./block.js\";\n\nconst SPACE_PX: Record<string, string> = { none: \"0\", xs: \"4px\", sm: \"8px\", md: \"16px\", lg: \"32px\", xl: \"64px\" };\nconst FONT_SIZE_REM: Record<string, string> = { sm: \"0.875rem\", base: \"1rem\", lg: \"1.25rem\", xl: \"1.5rem\", \"2xl\": \"2rem\" };\nconst WEIGHT: Record<string, number> = { normal: 400, medium: 500, semibold: 600, bold: 700 };\nconst RADIUS_PX: Record<string, string> = { none: \"0\", sm: \"4px\", md: \"8px\", lg: \"16px\", full: \"9999px\" };\nconst COLOR_HEX: Record<string, string> = { white: \"#ffffff\", black: \"#111111\", muted: \"#6b7280\", mutedBg: \"#f3f4f6\", primary: \"#111111\", accent: \"#4f46e5\" };\n\n/** A CSS declaration object (key → value) usable as a React `style` prop, or undefined when empty. */\nexport function blockStyleToCss(style: BlockStyle | undefined): Record<string, string | number> | undefined {\n if (!style || typeof style !== \"object\") return undefined;\n const s = style as Record<string, string>;\n const css: Record<string, string | number> = {};\n if (s.paddingY && SPACE_PX[s.paddingY] != null) { css.paddingTop = SPACE_PX[s.paddingY]!; css.paddingBottom = SPACE_PX[s.paddingY]!; }\n if (s.paddingX && SPACE_PX[s.paddingX] != null) { css.paddingLeft = SPACE_PX[s.paddingX]!; css.paddingRight = SPACE_PX[s.paddingX]!; }\n if (s.marginTop && SPACE_PX[s.marginTop] != null) css.marginTop = SPACE_PX[s.marginTop]!;\n if (s.marginBottom && SPACE_PX[s.marginBottom] != null) css.marginBottom = SPACE_PX[s.marginBottom]!;\n if (s.background && COLOR_HEX[s.background]) css.background = COLOR_HEX[s.background]!;\n if (s.textColor && COLOR_HEX[s.textColor]) css.color = COLOR_HEX[s.textColor]!;\n if (s.align === \"left\" || s.align === \"center\" || s.align === \"right\") css.textAlign = s.align;\n if (s.fontSize && FONT_SIZE_REM[s.fontSize]) css.fontSize = FONT_SIZE_REM[s.fontSize]!;\n if (s.weight && WEIGHT[s.weight] != null) css.fontWeight = WEIGHT[s.weight]!;\n if (s.radius && RADIUS_PX[s.radius] != null) css.borderRadius = RADIUS_PX[s.radius]!;\n return Object.keys(css).length ? css : undefined;\n}\n"],"mappings":";AAgSO,SAAS,QAAQ,OAAuC;AAC7D,SACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,OAAQ,MAAkC,MAAM,MAAM;AAE1D;AAUO,SAAS,aAAa,OAAkC;AAC7D,MAAI,QAAQ,KAAK,EAAG,QAAO,MAAM;AACjC,SAAO;AACT;;;AC5SA,IAAM,WAAmC,EAAE,MAAM,KAAK,IAAI,OAAO,IAAI,OAAO,IAAI,QAAQ,IAAI,QAAQ,IAAI,OAAO;AAC/G,IAAM,gBAAwC,EAAE,IAAI,YAAY,MAAM,QAAQ,IAAI,WAAW,IAAI,UAAU,OAAO,OAAO;AACzH,IAAM,SAAiC,EAAE,QAAQ,KAAK,QAAQ,KAAK,UAAU,KAAK,MAAM,IAAI;AAC5F,IAAM,YAAoC,EAAE,MAAM,KAAK,IAAI,OAAO,IAAI,OAAO,IAAI,QAAQ,MAAM,SAAS;AACxG,IAAM,YAAoC,EAAE,OAAO,WAAW,OAAO,WAAW,OAAO,WAAW,SAAS,WAAW,SAAS,WAAW,QAAQ,UAAU;AAGrJ,SAAS,gBAAgB,OAA4E;AAC1G,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,IAAI;AACV,QAAM,MAAuC,CAAC;AAC9C,MAAI,EAAE,YAAY,SAAS,EAAE,QAAQ,KAAK,MAAM;AAAE,QAAI,aAAa,SAAS,EAAE,QAAQ;AAAI,QAAI,gBAAgB,SAAS,EAAE,QAAQ;AAAA,EAAI;AACrI,MAAI,EAAE,YAAY,SAAS,EAAE,QAAQ,KAAK,MAAM;AAAE,QAAI,cAAc,SAAS,EAAE,QAAQ;AAAI,QAAI,eAAe,SAAS,EAAE,QAAQ;AAAA,EAAI;AACrI,MAAI,EAAE,aAAa,SAAS,EAAE,SAAS,KAAK,KAAM,KAAI,YAAY,SAAS,EAAE,SAAS;AACtF,MAAI,EAAE,gBAAgB,SAAS,EAAE,YAAY,KAAK,KAAM,KAAI,eAAe,SAAS,EAAE,YAAY;AAClG,MAAI,EAAE,cAAc,UAAU,EAAE,UAAU,EAAG,KAAI,aAAa,UAAU,EAAE,UAAU;AACpF,MAAI,EAAE,aAAa,UAAU,EAAE,SAAS,EAAG,KAAI,QAAQ,UAAU,EAAE,SAAS;AAC5E,MAAI,EAAE,UAAU,UAAU,EAAE,UAAU,YAAY,EAAE,UAAU,QAAS,KAAI,YAAY,EAAE;AACzF,MAAI,EAAE,YAAY,cAAc,EAAE,QAAQ,EAAG,KAAI,WAAW,cAAc,EAAE,QAAQ;AACpF,MAAI,EAAE,UAAU,OAAO,EAAE,MAAM,KAAK,KAAM,KAAI,aAAa,OAAO,EAAE,MAAM;AAC1E,MAAI,EAAE,UAAU,UAAU,EAAE,MAAM,KAAK,KAAM,KAAI,eAAe,UAAU,EAAE,MAAM;AAClF,SAAO,OAAO,KAAK,GAAG,EAAE,SAAS,MAAM;AACzC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/block.ts","../src/blockStyle.ts"],"sourcesContent":["/**\n * ContentBlock — a discriminated union of all CMS block types.\n * Each block has a `type` discriminator and a `props` object\n * whose shape matches the block type.\n */\n\n// ── Block style (FLO-264) ──────────────────────────────────────────────────────\n// A uniform, BOUNDED set of design tokens any block may carry (sibling to `props`).\n// Tokens map to fixed CSS values in the renderer (`styleTokensToCss`) so styling\n// reaches the live site WITHOUT arbitrary CSS. Additive + fully optional.\n\nexport type SpaceToken = \"none\" | \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\nexport type ColorToken = \"none\" | \"white\" | \"black\" | \"muted\" | \"mutedBg\" | \"primary\" | \"accent\";\nexport type AlignToken = \"left\" | \"center\" | \"right\";\nexport type FontSizeToken = \"sm\" | \"base\" | \"lg\" | \"xl\" | \"2xl\";\nexport type WeightToken = \"normal\" | \"medium\" | \"semibold\" | \"bold\";\nexport type RadiusToken = \"none\" | \"sm\" | \"md\" | \"lg\" | \"full\";\n\n// Section design (task 4) — the per-instance design a section carries on the page.\nexport type ThemeToken = \"inherit\" | \"light\" | \"dark\";\nexport type BgToken = \"none\" | \"surface\" | \"muted\" | \"accent\" | \"dark\" | \"custom\";\nexport type ContentWidthToken = \"narrow\" | \"default\" | \"wide\" | \"full\";\nexport type CornerToken = \"0\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\";\nexport type ShadowToken = \"0\" | \"s\" | \"m\" | \"l\" | \"xl\";\n\nexport interface BlockStyle {\n // Legacy token set (FLO-264). Where these overlap the section fields below\n // (background, radius, padding), the section field WINS in the renderer.\n paddingY?: SpaceToken;\n paddingX?: SpaceToken;\n marginTop?: SpaceToken;\n marginBottom?: SpaceToken;\n background?: ColorToken;\n textColor?: ColorToken;\n align?: AlignToken;\n fontSize?: FontSizeToken;\n weight?: WeightToken;\n radius?: RadiusToken;\n\n // Section design (task 4).\n theme?: ThemeToken;\n bg?: BgToken;\n /** Only read when `bg === \"custom\"`. Hex only. */\n bgCustom?: string;\n /** Padding sliders, in px (0–240). */\n paddingTop?: number;\n paddingBottom?: number;\n paddingSides?: number;\n contentWidth?: ContentWidthToken;\n fullHeight?: boolean;\n /** 0–100. */\n opacity?: number;\n corner?: CornerToken;\n shadow?: ShadowToken;\n borderTop?: boolean;\n borderBottom?: boolean;\n /** 🔴 The one free-form string. MUST go through `safeCssUrl()` before reaching CSS. */\n bgImage?: string;\n}\n\n// ── Heading ──────────────────────────────────────────────────────────────────\n\nexport interface HeadingProps {\n text: string;\n level?: 1 | 2 | 3;\n}\n\nexport interface HeadingBlock {\n type: \"heading\";\n id: string;\n style?: BlockStyle;\n props: HeadingProps;\n}\n\n// ── Text ─────────────────────────────────────────────────────────────────────\n\nexport interface TextProps {\n html: string;\n}\n\nexport interface TextBlock {\n type: \"text\";\n id: string;\n style?: BlockStyle;\n props: TextProps;\n}\n\n/**\n * A rich-text block: the same HTML payload as `text`, authored through a real editor.\n *\n * WHY A SEPARATE TYPE RATHER THAN CHANGING `text`. Every `text` block already on a live page\n * holds an HTML string that the delivery renderer injects as-is. Repurposing that type would\n * mean migrating published content, and getting it wrong renders visible markup to visitors.\n * A new type costs nothing at rest and leaves existing pages untouched.\n *\n * The payload is deliberately IDENTICAL to TextProps (an HTML string) so the two are\n * interchangeable at the renderer: delivery treats them the same, and the difference is\n * entirely in how an author edits them — `text` is a plain field, `richtext` gets the\n * formatting surface.\n */\n/**\n * A Portable Text node — the structured form of rich text.\n *\n * Kept alongside `html` rather than replacing it, so nothing already published moves. The\n * structure exists because an HTML string cannot carry INTENT: a colour is a hex with no\n * notion of which brand token produced it, an embedded component is a div with no reference\n * to resolve, and neither can be read back by tooling. Nodes can carry all of that.\n */\nexport interface PortableTextNode {\n _type: string;\n _key?: string;\n /** Block style — \"normal\", \"h2\", \"blockquote\". */\n style?: string;\n text?: string;\n children?: PortableTextNode[];\n /** Marks on a span: \"strong\", \"em\", or a token reference like \"color:brand-accent\". */\n marks?: string[];\n /** An embedded component or reference resolves through these. */\n schemaKey?: string;\n _ref?: string;\n fields?: Record<string, unknown>;\n}\n\nexport interface PortableTextDocument {\n version: 1;\n content: PortableTextNode[];\n}\n\nexport interface RichTextProps {\n /**\n * The RENDERED CACHE. Delivery reads this, so the fast path stays a string interpolation\n * and every page published before `content` existed keeps rendering byte-identically.\n */\n html: string;\n /**\n * The SOURCE OF TRUTH, when present — `content` always wins over `html`.\n *\n * Optional on purpose: this is the same envelope the field values already use\n * (`{ format, value, html }`), where `html` is likewise derived. Any writer that sets\n * `html` alone on a block that HAS `content` must delete `content` in the same write, or\n * the stale string silently shadows the newer structure.\n */\n content?: PortableTextDocument;\n}\n\nexport interface RichTextBlock {\n type: \"richtext\";\n id: string;\n style?: BlockStyle;\n props: RichTextProps;\n}\n\n// ── Image ────────────────────────────────────────────────────────────────────\n\nexport interface ImageProps {\n src: string;\n alt: string;\n width?: number;\n height?: number;\n caption?: string;\n}\n\nexport interface ImageBlock {\n type: \"image\";\n id: string;\n style?: BlockStyle;\n props: ImageProps;\n}\n\n// ── Button ───────────────────────────────────────────────────────────────────\n\nexport interface ButtonProps {\n text: string;\n href: string;\n variant?: \"primary\" | \"secondary\";\n}\n\nexport interface ButtonBlock {\n type: \"button\";\n id: string;\n style?: BlockStyle;\n props: ButtonProps;\n}\n\n// ── Spacer ───────────────────────────────────────────────────────────────────\n\nexport interface SpacerProps {\n height: number; // in px\n}\n\nexport interface SpacerBlock {\n type: \"spacer\";\n id: string;\n style?: BlockStyle;\n props: SpacerProps;\n}\n\n// ── Columns ───────────────────────────────────────────────────────────────────\n\nexport interface ColumnsProps {\n columns: ContentBlock[][]; // array of column slots, each slot is an array of blocks\n gap?: number; // in px\n}\n\nexport interface ColumnsBlock {\n type: \"columns\";\n id: string;\n style?: BlockStyle;\n props: ColumnsProps;\n}\n\n// ── Video ────────────────────────────────────────────────────────────────────\n\nexport interface VideoProps {\n url: string;\n poster?: string;\n autoplay?: boolean;\n loop?: boolean;\n}\n\nexport interface VideoBlock {\n type: \"video\";\n id: string;\n style?: BlockStyle;\n props: VideoProps;\n}\n\n// ── Form ─────────────────────────────────────────────────────────────────────\n\nexport interface FormProps {\n formId: string; // references forms.id (a nanoid). Resolved against delivered forms at render time.\n}\n\nexport interface FormBlock {\n type: \"form\";\n id: string;\n style?: BlockStyle;\n props: FormProps;\n}\n\n// ── Section (recursive container) ──────────────────────────────────────────────\n\nexport interface SectionProps {\n children: ContentBlock[];\n background?: string;\n paddingY?: number; // in px\n maxWidth?: number; // in px\n}\n\nexport interface SectionBlock {\n type: \"section\";\n id: string;\n style?: BlockStyle;\n props: SectionProps;\n}\n\n// ── Navbar ─────────────────────────────────────────────────────────────────────\n\nexport interface NavLink {\n label: string;\n href: string;\n}\n\nexport interface NavbarProps {\n logo?: { src: string; alt: string; href?: string };\n links: NavLink[];\n cta?: { text: string; href: string };\n}\n\nexport interface NavbarBlock {\n type: \"navbar\";\n id: string;\n style?: BlockStyle;\n props: NavbarProps;\n}\n\n// ── Footer ─────────────────────────────────────────────────────────────────────\n\nexport interface FooterColumn {\n heading?: string;\n links: NavLink[];\n}\n\nexport interface FooterProps {\n columns: FooterColumn[];\n copyright?: string;\n}\n\nexport interface FooterBlock {\n type: \"footer\";\n id: string;\n style?: BlockStyle;\n props: FooterProps;\n}\n\n// ── Slider / Carousel (recursive container, client-hydrated) ────────────────────\n\nexport interface SlideItem {\n id: string;\n children: ContentBlock[];\n}\n\nexport interface SliderProps {\n slides: SlideItem[];\n autoplay?: boolean;\n interval?: number; // in ms\n loop?: boolean;\n}\n\nexport interface SliderBlock {\n type: \"slider\";\n id: string;\n style?: BlockStyle;\n props: SliderProps;\n}\n\n// ── Tabs (recursive container, client-hydrated) ─────────────────────────────────\n\nexport interface TabItem {\n id: string;\n label: string;\n children: ContentBlock[];\n}\n\nexport interface TabsProps {\n tabs: TabItem[];\n}\n\nexport interface TabsBlock {\n type: \"tabs\";\n id: string;\n style?: BlockStyle;\n props: TabsProps;\n}\n\n// ── Component instance (Webflow-style reusable symbol) ──────────────────────────\n// References a reusable component by id. `overrides` maps a component prop key\n// (declared in the component's `props[]` allowlist) to a value, applied to the\n// component's block tree at render time. Resolved against delivered components.\n\nexport interface ComponentProps {\n componentId: string;\n overrides?: Record<string, unknown>;\n}\n\nexport interface ComponentBlock {\n type: \"component\";\n id: string;\n style?: BlockStyle;\n props: ComponentProps;\n}\n\n// ── Union ─────────────────────────────────────────────────────────────────────\n\nexport type ContentBlock =\n | HeadingBlock\n | TextBlock\n | RichTextBlock\n | ImageBlock\n | ButtonBlock\n | SpacerBlock\n | ColumnsBlock\n | VideoBlock\n | FormBlock\n | SectionBlock\n | NavbarBlock\n | FooterBlock\n | SliderBlock\n | TabsBlock\n | ComponentBlock;\n\n/** Literal union of all block type discriminators. */\nexport type BlockType = ContentBlock[\"type\"];\n\n/**\n * Type guard — narrows an unknown block to `ContentBlock`.\n *\n * @example\n * const block = maybeBlock as unknown;\n * if (isBlock(block)) {\n * // block is ContentBlock here\n * }\n */\nexport function isBlock(value: unknown): value is ContentBlock {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"type\" in value &&\n typeof (value as Record<string, unknown>)[\"type\"] === \"string\"\n );\n}\n\n/**\n * Extract the `type` discriminator from a block, or `null` if not a block.\n *\n * @example\n * const block = { type: \"heading\", id: \"1\", props: { text: \"Hi\" } };\n * getBlockType(block); // \"heading\"\n * getBlockType({ not: \"a block\" }); // null\n */\nexport function getBlockType(value: unknown): BlockType | null {\n if (isBlock(value)) return value.type;\n return null;\n}\n","/**\n * FLO-264 — bounded block-style tokens → CSS. Framework-agnostic (returns a plain\n * declaration object, no React types) so every renderer shares ONE mapping: the\n * headless @bettercms-ai/next adapter, @bettercms-ai/ui, and (string form) the hosted\n * render-page.ts. Unknown tokens are dropped — closed set, no arbitrary CSS.\n */\nimport type { BlockStyle } from \"./block.js\";\n\nconst SPACE_PX: Record<string, string> = { none: \"0\", xs: \"4px\", sm: \"8px\", md: \"16px\", lg: \"32px\", xl: \"64px\" };\nconst FONT_SIZE_REM: Record<string, string> = { sm: \"0.875rem\", base: \"1rem\", lg: \"1.25rem\", xl: \"1.5rem\", \"2xl\": \"2rem\" };\nconst WEIGHT: Record<string, number> = { normal: 400, medium: 500, semibold: 600, bold: 700 };\nconst RADIUS_PX: Record<string, string> = { none: \"0\", sm: \"4px\", md: \"8px\", lg: \"16px\", full: \"9999px\" };\nconst COLOR_HEX: Record<string, string> = { white: \"#ffffff\", black: \"#111111\", muted: \"#6b7280\", mutedBg: \"#f3f4f6\", primary: \"#111111\", accent: \"#4f46e5\" };\n\n/** A CSS declaration object (key → value) usable as a React `style` prop, or undefined when empty. */\nexport function blockStyleToCss(style: BlockStyle | undefined): Record<string, string | number> | undefined {\n if (!style || typeof style !== \"object\") return undefined;\n const s = style as Record<string, string>;\n const css: Record<string, string | number> = {};\n if (s.paddingY && SPACE_PX[s.paddingY] != null) { css.paddingTop = SPACE_PX[s.paddingY]!; css.paddingBottom = SPACE_PX[s.paddingY]!; }\n if (s.paddingX && SPACE_PX[s.paddingX] != null) { css.paddingLeft = SPACE_PX[s.paddingX]!; css.paddingRight = SPACE_PX[s.paddingX]!; }\n if (s.marginTop && SPACE_PX[s.marginTop] != null) css.marginTop = SPACE_PX[s.marginTop]!;\n if (s.marginBottom && SPACE_PX[s.marginBottom] != null) css.marginBottom = SPACE_PX[s.marginBottom]!;\n if (s.background && COLOR_HEX[s.background]) css.background = COLOR_HEX[s.background]!;\n if (s.textColor && COLOR_HEX[s.textColor]) css.color = COLOR_HEX[s.textColor]!;\n if (s.align === \"left\" || s.align === \"center\" || s.align === \"right\") css.textAlign = s.align;\n if (s.fontSize && FONT_SIZE_REM[s.fontSize]) css.fontSize = FONT_SIZE_REM[s.fontSize]!;\n if (s.weight && WEIGHT[s.weight] != null) css.fontWeight = WEIGHT[s.weight]!;\n if (s.radius && RADIUS_PX[s.radius] != null) css.borderRadius = RADIUS_PX[s.radius]!;\n return Object.keys(css).length ? css : undefined;\n}\n"],"mappings":";AA+XO,SAAS,QAAQ,OAAuC;AAC7D,SACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,OAAQ,MAAkC,MAAM,MAAM;AAE1D;AAUO,SAAS,aAAa,OAAkC;AAC7D,MAAI,QAAQ,KAAK,EAAG,QAAO,MAAM;AACjC,SAAO;AACT;;;AC3YA,IAAM,WAAmC,EAAE,MAAM,KAAK,IAAI,OAAO,IAAI,OAAO,IAAI,QAAQ,IAAI,QAAQ,IAAI,OAAO;AAC/G,IAAM,gBAAwC,EAAE,IAAI,YAAY,MAAM,QAAQ,IAAI,WAAW,IAAI,UAAU,OAAO,OAAO;AACzH,IAAM,SAAiC,EAAE,QAAQ,KAAK,QAAQ,KAAK,UAAU,KAAK,MAAM,IAAI;AAC5F,IAAM,YAAoC,EAAE,MAAM,KAAK,IAAI,OAAO,IAAI,OAAO,IAAI,QAAQ,MAAM,SAAS;AACxG,IAAM,YAAoC,EAAE,OAAO,WAAW,OAAO,WAAW,OAAO,WAAW,SAAS,WAAW,SAAS,WAAW,QAAQ,UAAU;AAGrJ,SAAS,gBAAgB,OAA4E;AAC1G,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,IAAI;AACV,QAAM,MAAuC,CAAC;AAC9C,MAAI,EAAE,YAAY,SAAS,EAAE,QAAQ,KAAK,MAAM;AAAE,QAAI,aAAa,SAAS,EAAE,QAAQ;AAAI,QAAI,gBAAgB,SAAS,EAAE,QAAQ;AAAA,EAAI;AACrI,MAAI,EAAE,YAAY,SAAS,EAAE,QAAQ,KAAK,MAAM;AAAE,QAAI,cAAc,SAAS,EAAE,QAAQ;AAAI,QAAI,eAAe,SAAS,EAAE,QAAQ;AAAA,EAAI;AACrI,MAAI,EAAE,aAAa,SAAS,EAAE,SAAS,KAAK,KAAM,KAAI,YAAY,SAAS,EAAE,SAAS;AACtF,MAAI,EAAE,gBAAgB,SAAS,EAAE,YAAY,KAAK,KAAM,KAAI,eAAe,SAAS,EAAE,YAAY;AAClG,MAAI,EAAE,cAAc,UAAU,EAAE,UAAU,EAAG,KAAI,aAAa,UAAU,EAAE,UAAU;AACpF,MAAI,EAAE,aAAa,UAAU,EAAE,SAAS,EAAG,KAAI,QAAQ,UAAU,EAAE,SAAS;AAC5E,MAAI,EAAE,UAAU,UAAU,EAAE,UAAU,YAAY,EAAE,UAAU,QAAS,KAAI,YAAY,EAAE;AACzF,MAAI,EAAE,YAAY,cAAc,EAAE,QAAQ,EAAG,KAAI,WAAW,cAAc,EAAE,QAAQ;AACpF,MAAI,EAAE,UAAU,OAAO,EAAE,MAAM,KAAK,KAAM,KAAI,aAAa,OAAO,EAAE,MAAM;AAC1E,MAAI,EAAE,UAAU,UAAU,EAAE,MAAM,KAAK,KAAM,KAAI,eAAe,UAAU,EAAE,MAAM;AAClF,SAAO,OAAO,KAAK,GAAG,EAAE,SAAS,MAAM;AACzC;","names":[]}
|