@nextlyhq/plugin-page-builder 0.0.2-alpha.31
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/README.md +240 -0
- package/dist/admin/index.d.ts +56 -0
- package/dist/admin/index.js +2463 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/chunk-7VN7UNLK.js +45 -0
- package/dist/chunk-7VN7UNLK.js.map +1 -0
- package/dist/chunk-ABMSYCSD.js +355 -0
- package/dist/chunk-ABMSYCSD.js.map +1 -0
- package/dist/chunk-E4G7NNXW.js +21 -0
- package/dist/chunk-E4G7NNXW.js.map +1 -0
- package/dist/chunk-EGU6QGUC.js +119 -0
- package/dist/chunk-EGU6QGUC.js.map +1 -0
- package/dist/chunk-R7YHOSL2.js +483 -0
- package/dist/chunk-R7YHOSL2.js.map +1 -0
- package/dist/index.d.ts +196 -0
- package/dist/index.js +190 -0
- package/dist/index.js.map +1 -0
- package/dist/render/ErrorBoundary.d.ts +23 -0
- package/dist/render/ErrorBoundary.js +8 -0
- package/dist/render/ErrorBoundary.js.map +1 -0
- package/dist/render/index.d.ts +151 -0
- package/dist/render/index.js +64 -0
- package/dist/render/index.js.map +1 -0
- package/dist/style-compiler-hnWIDfko.d.ts +212 -0
- package/dist/styles/editor.css +412 -0
- package/package.json +109 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { B as BlockNode, R as ResponsiveStyle, a as BlockRegistry, b as BlockDocument } from './style-compiler-hnWIDfko.js';
|
|
2
|
+
export { c as Binding, d as BlockCategory, e as BlockDefinition, f as BlockDocumentKind, g as BlockRenderArgs, h as BoxSides, i as Breakpoint, j as BreakpointDef, C as CompileOptions, k as ControlDef, l as ControlRef, m as ControlRegistry, D as DEFAULT_BREAKPOINTS, n as DEFAULT_SLOT, o as DEFAULT_TOKENS, p as DocumentVersion, M as MAX_DEPTH, q as MAX_NODES, S as SlotSpec, r as StyleScalar, s as StyleValues, T as TokenRef, t as compileDocumentCss, u as compileNodeCss, v as compileTokensCss, w as createBlockRegistry, x as createControlRegistry, y as defaultBlockRegistry, z as defaultControlRegistry, A as defineBlock, E as nodeClass } from './style-compiler-hnWIDfko.js';
|
|
3
|
+
import * as nextly from 'nextly';
|
|
4
|
+
import 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Slot-aware, immutable operations over the block tree (spec §5/§6). Pure and
|
|
8
|
+
* React-free. Every mutation returns a new tree; the input is never modified.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Stable unique id. `crypto.randomUUID` is available in Node ≥18 and modern browsers. */
|
|
12
|
+
declare function newId(): string;
|
|
13
|
+
declare function makeNode(type: string, props?: Record<string, unknown>, style?: ResponsiveStyle, slots?: Record<string, BlockNode[]>): BlockNode;
|
|
14
|
+
/** Depth-first visit: the node, then each slot's children in order. */
|
|
15
|
+
declare function walk(node: BlockNode, fn: (n: BlockNode, parent?: BlockNode) => void, parent?: BlockNode): void;
|
|
16
|
+
declare function findNode(node: BlockNode, id: string): BlockNode | undefined;
|
|
17
|
+
declare function insertNode(root: BlockNode, parentId: string, slot: string, node: BlockNode, index: number): BlockNode;
|
|
18
|
+
declare function removeNode(root: BlockNode, id: string): BlockNode;
|
|
19
|
+
declare function moveNode(root: BlockNode, id: string, parentId: string, slot: string, index: number): BlockNode;
|
|
20
|
+
declare function duplicateNode(root: BlockNode, id: string): BlockNode;
|
|
21
|
+
declare function updateNode(root: BlockNode, id: string, patch: Partial<BlockNode>): BlockNode;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Document validation invariants (spec §14). Returns `true` when valid, else a
|
|
25
|
+
* human-readable error string. Used as the `pages.content` field validator (M3) and
|
|
26
|
+
* defensively in the editor. Pure and React-free.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
interface ValidateOptions {
|
|
30
|
+
/** Preserve/accept unknown block types (resilience, spec §12). Default false. */
|
|
31
|
+
allowUnknown?: boolean;
|
|
32
|
+
}
|
|
33
|
+
declare function validateDocument(doc: unknown, registry: BlockRegistry, opts?: ValidateOptions): true | string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Migration runner (spec §12). Upgrades a stored document to current block versions
|
|
37
|
+
* by running each block's `migrate()` when its instance is older than the registered
|
|
38
|
+
* definition. Unknown block types are PRESERVED as-is (Nextly's "retain and flag"
|
|
39
|
+
* philosophy) — never dropped, never fatal. Pure JSON→JSON; React-free.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/** Upgrade a stored document to current block versions. */
|
|
43
|
+
declare function migrateDocument(doc: BlockDocument, registry: BlockRegistry): BlockDocument;
|
|
44
|
+
|
|
45
|
+
declare function sanitizeCustomCss(css: string, scopeClass: string): string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Typed data-binding resolution for the Query Loop (spec §10). React-free.
|
|
49
|
+
*
|
|
50
|
+
* Bindings live in `node.bindings` (kept separate from literal props). At render time
|
|
51
|
+
* each binding is resolved from the current loop item via a dot-path, optionally
|
|
52
|
+
* transformed, and merged over the node's literal props.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/** Safe dotted read: getPath({a:{b:2}}, "a.b") === 2. */
|
|
56
|
+
declare function getPath(obj: unknown, path: string): unknown;
|
|
57
|
+
/**
|
|
58
|
+
* Return a props object with each `node.bindings[prop]` resolved from `item`
|
|
59
|
+
* (dot-path + optional transform) merged over `node.props`. Literal props untouched.
|
|
60
|
+
*/
|
|
61
|
+
declare function resolveBindings(node: BlockNode, item: Record<string, unknown>): Record<string, unknown>;
|
|
62
|
+
|
|
63
|
+
interface PageBuilderOptions {
|
|
64
|
+
/** Disable behavior while still applying schema. Default true. */
|
|
65
|
+
enabled?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The Page Builder plugin factory. Call it in a host app's
|
|
69
|
+
* `defineConfig({ plugins: [pageBuilder()] })`.
|
|
70
|
+
*/
|
|
71
|
+
declare const pageBuilder: (opts?: PageBuilderOptions) => nextly.PluginDefinition;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Registry path of the full-screen builder Edit view — still exported (and registered)
|
|
75
|
+
* for hosts that want a builder-only collection. The default `pages` collection below
|
|
76
|
+
* instead offers a per-entry CHOICE between the normal Nextly editor and the builder.
|
|
77
|
+
*/
|
|
78
|
+
declare const EDIT_VIEW_PATH = "@nextlyhq/plugin-page-builder/admin#PageBuilderEditView";
|
|
79
|
+
/**
|
|
80
|
+
* The plugin-owned `pages` collection. Each page CHOOSES its editor (Elementor-style):
|
|
81
|
+
* - "Page Builder" → the visual block tree (`content`, a `pageBuilderField`).
|
|
82
|
+
* - "Normal editor" → Nextly's default rich-text form (`body`).
|
|
83
|
+
* The front-end renders whichever was chosen. Using field conditions (not an Edit-view
|
|
84
|
+
* override) keeps the normal editor available — a single Edit view can't offer both.
|
|
85
|
+
*/
|
|
86
|
+
declare function pagesCollection(): nextly.CollectionConfig;
|
|
87
|
+
|
|
88
|
+
/** String path the host admin resolves to our field editor (registered on `./admin`). */
|
|
89
|
+
declare const FIELD_COMPONENT_PATH = "@nextlyhq/plugin-page-builder/admin#PageBuilderField";
|
|
90
|
+
interface PageBuilderFieldOptions {
|
|
91
|
+
/** Admin label for the field editor. */
|
|
92
|
+
label?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Show the editor only when a sibling field matches — enables an Elementor-style
|
|
95
|
+
* "choose your editor" workflow (e.g. show the builder only when a `mode` select is
|
|
96
|
+
* "page-builder"). Maps to the field's `admin.condition`.
|
|
97
|
+
*/
|
|
98
|
+
condition?: {
|
|
99
|
+
field: string;
|
|
100
|
+
equals?: unknown;
|
|
101
|
+
notEquals?: unknown;
|
|
102
|
+
exists?: boolean;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A page-builder editor as a custom field (spec §9/§11). Stores the BlockDocument as
|
|
107
|
+
* JSON and renders our editor via `admin.component` (D24, the plugin-supplied field
|
|
108
|
+
* editor). Works in BOTH collections and singles — the host form persists the value.
|
|
109
|
+
* Register the component with `@nextlyhq/plugin-page-builder/admin`.
|
|
110
|
+
*
|
|
111
|
+
* Node-side the block registry is empty at config-load (renderers live in `./render`),
|
|
112
|
+
* so the validator runs with `allowUnknown: true` — it still enforces the structural
|
|
113
|
+
* invariants (depth / node count / unique ids / namespaced types).
|
|
114
|
+
*/
|
|
115
|
+
declare function pageBuilderField(name: string, opts?: PageBuilderFieldOptions): nextly.JSONFieldConfig;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* A drop-in "choose your editor" field set (Elementor/WordPress-style). Spread it into
|
|
119
|
+
* ANY code-first collection to give each entry a choice between the visual Page Builder
|
|
120
|
+
* and Nextly's normal rich-text editor — the front-end renders whichever was chosen:
|
|
121
|
+
*
|
|
122
|
+
* ```ts
|
|
123
|
+
* defineCollection({
|
|
124
|
+
* slug: "landing-pages",
|
|
125
|
+
* fields: [text({ name: "title" }), text({ name: "slug" }), ...editorChoiceFields()],
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* On the front-end: `entry.editorMode === "builder"` → render `entry[builderField]` via
|
|
130
|
+
* `<PageRenderer>`; otherwise render `entry[normalField]` (rich text, fetched with
|
|
131
|
+
* `richTextFormat: "html"`).
|
|
132
|
+
*
|
|
133
|
+
* Note: this is for CODE-FIRST collections. Collections built in the admin schema-builder
|
|
134
|
+
* UI can't add these fields yet (that needs a UI-registered field type — a future door).
|
|
135
|
+
*/
|
|
136
|
+
interface EditorChoiceOptions {
|
|
137
|
+
/** Field name for the block tree (Page Builder mode). Default "content". */
|
|
138
|
+
builderField?: string;
|
|
139
|
+
/** Field name for the rich-text body (Normal mode). Default "body". */
|
|
140
|
+
normalField?: string;
|
|
141
|
+
/** Which editor is selected by default. Default "builder". */
|
|
142
|
+
defaultMode?: "builder" | "normal";
|
|
143
|
+
}
|
|
144
|
+
declare function editorChoiceFields(opts?: EditorChoiceOptions): (nextly.JSONFieldConfig | nextly.SelectFieldConfig | nextly.RichTextFieldConfig)[];
|
|
145
|
+
|
|
146
|
+
/** Reserved system field holding the BlockDocument JSON (spec §2). */
|
|
147
|
+
declare const PAGE_BUILDER_CONTENT_FIELD = "content";
|
|
148
|
+
/** Plugin-registered field type id (spec §4.1). */
|
|
149
|
+
declare const PAGE_BUILDER_TYPE = "page-builder";
|
|
150
|
+
type EditorMode = "default" | "builder";
|
|
151
|
+
/**
|
|
152
|
+
* @deprecated The editor-choice is now signalled purely by the presence of the
|
|
153
|
+
* `page-builder` field (a `layout: "takeover"` field type) — no collection-level
|
|
154
|
+
* flag is read anymore. Retained only for back-compat of existing type imports.
|
|
155
|
+
*/
|
|
156
|
+
interface PageBuilderAdminConfig {
|
|
157
|
+
enabled?: boolean;
|
|
158
|
+
defaultMode?: EditorMode;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Field-type descriptor registered via the plugin's `contributes.fieldTypes`.
|
|
162
|
+
* `layout: "takeover"` tells the admin entry form to show only this field (plus
|
|
163
|
+
* its editor-mode controller) when the field is visible — hiding the rest.
|
|
164
|
+
*/
|
|
165
|
+
declare const PAGE_BUILDER_FIELD_TYPE: {
|
|
166
|
+
readonly type: "page-builder";
|
|
167
|
+
readonly storage: "json";
|
|
168
|
+
readonly component: "@nextlyhq/plugin-page-builder/admin#PageBuilderField";
|
|
169
|
+
readonly layout: "takeover";
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* The two fields an entity needs to offer the Default / Page Builder choice: an `editorMode`
|
|
173
|
+
* select and the reserved `content` page-builder field (shown only in builder mode). The
|
|
174
|
+
* entity's OWN fields serve as the "Default" editor; the admin hides the non-essential ones
|
|
175
|
+
* in builder mode (a later stage). Works in both `defineCollection` and `defineSingle`.
|
|
176
|
+
*/
|
|
177
|
+
declare function pageBuilderFields(opts?: {
|
|
178
|
+
defaultMode?: EditorMode;
|
|
179
|
+
}): (nextly.JSONFieldConfig | nextly.SelectFieldConfig)[];
|
|
180
|
+
/**
|
|
181
|
+
* Opt a CODE-FIRST collection/single config into the Page Builder editor choice:
|
|
182
|
+
* appends `pageBuilderFields()`. The presence of the `page-builder` field is the
|
|
183
|
+
* signal — no collection-level flag needed. Wrap the config passed to
|
|
184
|
+
* `defineCollection`/`defineSingle`:
|
|
185
|
+
*
|
|
186
|
+
* ```ts
|
|
187
|
+
* defineCollection(withPageBuilder({ slug: "landing", fields: [text({ name: "title" })] }));
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
declare function withPageBuilder<T extends {
|
|
191
|
+
fields?: unknown[];
|
|
192
|
+
}>(config: T, opts?: {
|
|
193
|
+
defaultMode?: EditorMode;
|
|
194
|
+
}): T;
|
|
195
|
+
|
|
196
|
+
export { BlockDocument, BlockNode, BlockRegistry, EDIT_VIEW_PATH, type EditorChoiceOptions, type EditorMode, FIELD_COMPONENT_PATH, PAGE_BUILDER_CONTENT_FIELD, PAGE_BUILDER_FIELD_TYPE, PAGE_BUILDER_TYPE, type PageBuilderAdminConfig, type PageBuilderFieldOptions, type PageBuilderOptions, ResponsiveStyle, type ValidateOptions, duplicateNode, editorChoiceFields, findNode, getPath, insertNode, makeNode, migrateDocument, moveNode, newId, pageBuilder, pageBuilderField, pageBuilderFields, pagesCollection, removeNode, resolveBindings, sanitizeCustomCss, updateNode, validateDocument, walk, withPageBuilder };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FIELD_COMPONENT_PATH,
|
|
3
|
+
PAGE_BUILDER_CONTENT_FIELD,
|
|
4
|
+
PAGE_BUILDER_FIELD_TYPE,
|
|
5
|
+
PAGE_BUILDER_TYPE,
|
|
6
|
+
pageBuilderField,
|
|
7
|
+
pageBuilderFields,
|
|
8
|
+
validateDocument,
|
|
9
|
+
withPageBuilder
|
|
10
|
+
} from "./chunk-EGU6QGUC.js";
|
|
11
|
+
import {
|
|
12
|
+
sanitizeCustomCss
|
|
13
|
+
} from "./chunk-7VN7UNLK.js";
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_BREAKPOINTS,
|
|
16
|
+
DEFAULT_SLOT,
|
|
17
|
+
DEFAULT_TOKENS,
|
|
18
|
+
MAX_DEPTH,
|
|
19
|
+
MAX_NODES,
|
|
20
|
+
compileDocumentCss,
|
|
21
|
+
compileNodeCss,
|
|
22
|
+
compileTokensCss,
|
|
23
|
+
createBlockRegistry,
|
|
24
|
+
createControlRegistry,
|
|
25
|
+
defaultBlockRegistry,
|
|
26
|
+
defaultControlRegistry,
|
|
27
|
+
defineBlock,
|
|
28
|
+
duplicateNode,
|
|
29
|
+
findNode,
|
|
30
|
+
getPath,
|
|
31
|
+
insertNode,
|
|
32
|
+
makeNode,
|
|
33
|
+
moveNode,
|
|
34
|
+
newId,
|
|
35
|
+
nodeClass,
|
|
36
|
+
removeNode,
|
|
37
|
+
resolveBindings,
|
|
38
|
+
updateNode,
|
|
39
|
+
walk
|
|
40
|
+
} from "./chunk-ABMSYCSD.js";
|
|
41
|
+
|
|
42
|
+
// src/core/migrate.ts
|
|
43
|
+
function migrateNode(node, registry) {
|
|
44
|
+
const def = registry.get(node.type);
|
|
45
|
+
let next = node;
|
|
46
|
+
if (def) {
|
|
47
|
+
const from = node.definitionVersion ?? 1;
|
|
48
|
+
if (from < def.version && def.migrate) {
|
|
49
|
+
const { props, style } = def.migrate(node.props, from);
|
|
50
|
+
next = {
|
|
51
|
+
...node,
|
|
52
|
+
props,
|
|
53
|
+
...style ? { style } : {},
|
|
54
|
+
definitionVersion: def.version
|
|
55
|
+
};
|
|
56
|
+
} else if (from !== def.version) {
|
|
57
|
+
next = { ...node, definitionVersion: def.version };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!next.slots) return next;
|
|
61
|
+
const slots = {};
|
|
62
|
+
for (const [name, children] of Object.entries(next.slots)) {
|
|
63
|
+
slots[name] = children.map((c) => migrateNode(c, registry));
|
|
64
|
+
}
|
|
65
|
+
return { ...next, slots };
|
|
66
|
+
}
|
|
67
|
+
function migrateDocument(doc, registry) {
|
|
68
|
+
return { ...doc, root: migrateNode(doc.root, registry) };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/plugin.ts
|
|
72
|
+
import { definePlugin } from "@nextlyhq/plugin-sdk";
|
|
73
|
+
|
|
74
|
+
// src/collections/pages.ts
|
|
75
|
+
import { defineCollection, text, code } from "nextly/config";
|
|
76
|
+
|
|
77
|
+
// src/collections/editorChoice.ts
|
|
78
|
+
import { option, richText, select } from "nextly/config";
|
|
79
|
+
function editorChoiceFields(opts = {}) {
|
|
80
|
+
const builderField = opts.builderField ?? "content";
|
|
81
|
+
const normalField = opts.normalField ?? "body";
|
|
82
|
+
const defaultMode = opts.defaultMode ?? "builder";
|
|
83
|
+
return [
|
|
84
|
+
select({
|
|
85
|
+
name: "editorMode",
|
|
86
|
+
label: "Editor",
|
|
87
|
+
defaultValue: defaultMode,
|
|
88
|
+
options: [
|
|
89
|
+
option("Page Builder", "builder"),
|
|
90
|
+
option("Normal editor", "normal")
|
|
91
|
+
],
|
|
92
|
+
admin: { description: "Choose how to edit this entry." }
|
|
93
|
+
}),
|
|
94
|
+
pageBuilderField(builderField, {
|
|
95
|
+
label: "Page Builder",
|
|
96
|
+
condition: { field: "editorMode", equals: "builder" }
|
|
97
|
+
}),
|
|
98
|
+
richText({
|
|
99
|
+
name: normalField,
|
|
100
|
+
label: "Content",
|
|
101
|
+
admin: { condition: { field: "editorMode", equals: "normal" } }
|
|
102
|
+
})
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/collections/pages.ts
|
|
107
|
+
var EDIT_VIEW_PATH = "@nextlyhq/plugin-page-builder/admin#PageBuilderEditView";
|
|
108
|
+
function pagesCollection() {
|
|
109
|
+
return defineCollection({
|
|
110
|
+
slug: "pages",
|
|
111
|
+
labels: { singular: "Page", plural: "Pages" },
|
|
112
|
+
fields: [
|
|
113
|
+
text({ name: "title", required: true }),
|
|
114
|
+
text({ name: "slug", required: true, unique: true }),
|
|
115
|
+
// The Elementor-style editor choice (select + Page Builder + normal rich text).
|
|
116
|
+
...editorChoiceFields(),
|
|
117
|
+
code({ name: "customCss", admin: { language: "css" } })
|
|
118
|
+
],
|
|
119
|
+
status: true,
|
|
120
|
+
admin: { useAsTitle: "title" }
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/plugin.ts
|
|
125
|
+
var pageBuilder = (opts = {}) => definePlugin({
|
|
126
|
+
name: "@nextlyhq/plugin-page-builder",
|
|
127
|
+
version: "0.0.2-alpha.29",
|
|
128
|
+
nextly: ">=0.0.2-alpha.21",
|
|
129
|
+
enabled: opts.enabled,
|
|
130
|
+
contributes: {
|
|
131
|
+
collections: [pagesCollection()],
|
|
132
|
+
fieldTypes: [PAGE_BUILDER_FIELD_TYPE],
|
|
133
|
+
permissions: [
|
|
134
|
+
{ action: "publish", resource: "pages", label: "Publish Pages" }
|
|
135
|
+
],
|
|
136
|
+
admin: {
|
|
137
|
+
menu: [
|
|
138
|
+
{ label: "Pages", to: "/admin/collections/pages", icon: "Layout" }
|
|
139
|
+
],
|
|
140
|
+
// Schema-builder "Use Page Builder" toggle, rendered generically by the
|
|
141
|
+
// admin above the field list in the collection/single builders.
|
|
142
|
+
schemaBuilderSlot: "@nextlyhq/plugin-page-builder/admin#PageBuilderToggle",
|
|
143
|
+
// Per-entry Normal / Page Builder toggle, rendered in the entry/single
|
|
144
|
+
// form header toolbar (drives the hidden editor-mode field).
|
|
145
|
+
entryFormToolbarSlot: "@nextlyhq/plugin-page-builder/admin#PageBuilderModeToggle"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
export {
|
|
150
|
+
DEFAULT_BREAKPOINTS,
|
|
151
|
+
DEFAULT_SLOT,
|
|
152
|
+
DEFAULT_TOKENS,
|
|
153
|
+
EDIT_VIEW_PATH,
|
|
154
|
+
FIELD_COMPONENT_PATH,
|
|
155
|
+
MAX_DEPTH,
|
|
156
|
+
MAX_NODES,
|
|
157
|
+
PAGE_BUILDER_CONTENT_FIELD,
|
|
158
|
+
PAGE_BUILDER_FIELD_TYPE,
|
|
159
|
+
PAGE_BUILDER_TYPE,
|
|
160
|
+
compileDocumentCss,
|
|
161
|
+
compileNodeCss,
|
|
162
|
+
compileTokensCss,
|
|
163
|
+
createBlockRegistry,
|
|
164
|
+
createControlRegistry,
|
|
165
|
+
defaultBlockRegistry,
|
|
166
|
+
defaultControlRegistry,
|
|
167
|
+
defineBlock,
|
|
168
|
+
duplicateNode,
|
|
169
|
+
editorChoiceFields,
|
|
170
|
+
findNode,
|
|
171
|
+
getPath,
|
|
172
|
+
insertNode,
|
|
173
|
+
makeNode,
|
|
174
|
+
migrateDocument,
|
|
175
|
+
moveNode,
|
|
176
|
+
newId,
|
|
177
|
+
nodeClass,
|
|
178
|
+
pageBuilder,
|
|
179
|
+
pageBuilderField,
|
|
180
|
+
pageBuilderFields,
|
|
181
|
+
pagesCollection,
|
|
182
|
+
removeNode,
|
|
183
|
+
resolveBindings,
|
|
184
|
+
sanitizeCustomCss,
|
|
185
|
+
updateNode,
|
|
186
|
+
validateDocument,
|
|
187
|
+
walk,
|
|
188
|
+
withPageBuilder
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/migrate.ts","../src/plugin.ts","../src/collections/pages.ts","../src/collections/editorChoice.ts"],"sourcesContent":["/**\n * Migration runner (spec §12). Upgrades a stored document to current block versions\n * by running each block's `migrate()` when its instance is older than the registered\n * definition. Unknown block types are PRESERVED as-is (Nextly's \"retain and flag\"\n * philosophy) — never dropped, never fatal. Pure JSON→JSON; React-free.\n */\nimport type { BlockRegistry } from \"./registry\";\nimport type { BlockDocument, BlockNode } from \"./types\";\n\nfunction migrateNode(node: BlockNode, registry: BlockRegistry): BlockNode {\n const def = registry.get(node.type);\n let next: BlockNode = node;\n\n if (def) {\n const from = node.definitionVersion ?? 1;\n if (from < def.version && def.migrate) {\n const { props, style } = def.migrate(node.props, from);\n next = {\n ...node,\n props: props,\n ...(style ? { style } : {}),\n definitionVersion: def.version,\n };\n } else if (from !== def.version) {\n next = { ...node, definitionVersion: def.version };\n }\n }\n // Unknown blocks (def === undefined): preserved untouched.\n\n if (!next.slots) return next;\n const slots: Record<string, BlockNode[]> = {};\n for (const [name, children] of Object.entries(next.slots)) {\n slots[name] = children.map(c => migrateNode(c, registry));\n }\n return { ...next, slots };\n}\n\n/** Upgrade a stored document to current block versions. */\nexport function migrateDocument(\n doc: BlockDocument,\n registry: BlockRegistry\n): BlockDocument {\n return { ...doc, root: migrateNode(doc.root, registry) };\n}\n","import { definePlugin } from \"@nextlyhq/plugin-sdk\";\n\nimport { PAGE_BUILDER_FIELD_TYPE } from \"./collections/pageBuilderEntry\";\nimport { pagesCollection } from \"./collections/pages\";\n\nexport interface PageBuilderOptions {\n /** Disable behavior while still applying schema. Default true. */\n enabled?: boolean;\n}\n\n/**\n * The Page Builder plugin factory. Call it in a host app's\n * `defineConfig({ plugins: [pageBuilder()] })`.\n */\nexport const pageBuilder = (opts: PageBuilderOptions = {}) =>\n definePlugin({\n name: \"@nextlyhq/plugin-page-builder\",\n version: \"0.0.2-alpha.29\",\n nextly: \">=0.0.2-alpha.21\",\n enabled: opts.enabled,\n contributes: {\n collections: [pagesCollection()],\n fieldTypes: [PAGE_BUILDER_FIELD_TYPE],\n permissions: [\n { action: \"publish\", resource: \"pages\", label: \"Publish Pages\" },\n ],\n admin: {\n menu: [\n { label: \"Pages\", to: \"/admin/collections/pages\", icon: \"Layout\" },\n ],\n // Schema-builder \"Use Page Builder\" toggle, rendered generically by the\n // admin above the field list in the collection/single builders.\n schemaBuilderSlot:\n \"@nextlyhq/plugin-page-builder/admin#PageBuilderToggle\",\n // Per-entry Normal / Page Builder toggle, rendered in the entry/single\n // form header toolbar (drives the hidden editor-mode field).\n entryFormToolbarSlot:\n \"@nextlyhq/plugin-page-builder/admin#PageBuilderModeToggle\",\n },\n },\n });\n","import { defineCollection, text, code } from \"nextly/config\";\n\nimport { editorChoiceFields } from \"./editorChoice\";\n\n/**\n * Registry path of the full-screen builder Edit view — still exported (and registered)\n * for hosts that want a builder-only collection. The default `pages` collection below\n * instead offers a per-entry CHOICE between the normal Nextly editor and the builder.\n */\nexport const EDIT_VIEW_PATH =\n \"@nextlyhq/plugin-page-builder/admin#PageBuilderEditView\";\n\n/**\n * The plugin-owned `pages` collection. Each page CHOOSES its editor (Elementor-style):\n * - \"Page Builder\" → the visual block tree (`content`, a `pageBuilderField`).\n * - \"Normal editor\" → Nextly's default rich-text form (`body`).\n * The front-end renders whichever was chosen. Using field conditions (not an Edit-view\n * override) keeps the normal editor available — a single Edit view can't offer both.\n */\nexport function pagesCollection() {\n return defineCollection({\n slug: \"pages\",\n labels: { singular: \"Page\", plural: \"Pages\" },\n fields: [\n text({ name: \"title\", required: true }),\n text({ name: \"slug\", required: true, unique: true }),\n // The Elementor-style editor choice (select + Page Builder + normal rich text).\n ...editorChoiceFields(),\n code({ name: \"customCss\", admin: { language: \"css\" } }),\n ],\n status: true,\n admin: { useAsTitle: \"title\" },\n });\n}\n","import { option, richText, select } from \"nextly/config\";\n\nimport { pageBuilderField } from \"./pageBuilderField\";\n\n/**\n * A drop-in \"choose your editor\" field set (Elementor/WordPress-style). Spread it into\n * ANY code-first collection to give each entry a choice between the visual Page Builder\n * and Nextly's normal rich-text editor — the front-end renders whichever was chosen:\n *\n * ```ts\n * defineCollection({\n * slug: \"landing-pages\",\n * fields: [text({ name: \"title\" }), text({ name: \"slug\" }), ...editorChoiceFields()],\n * });\n * ```\n *\n * On the front-end: `entry.editorMode === \"builder\"` → render `entry[builderField]` via\n * `<PageRenderer>`; otherwise render `entry[normalField]` (rich text, fetched with\n * `richTextFormat: \"html\"`).\n *\n * Note: this is for CODE-FIRST collections. Collections built in the admin schema-builder\n * UI can't add these fields yet (that needs a UI-registered field type — a future door).\n */\nexport interface EditorChoiceOptions {\n /** Field name for the block tree (Page Builder mode). Default \"content\". */\n builderField?: string;\n /** Field name for the rich-text body (Normal mode). Default \"body\". */\n normalField?: string;\n /** Which editor is selected by default. Default \"builder\". */\n defaultMode?: \"builder\" | \"normal\";\n}\n\nexport function editorChoiceFields(opts: EditorChoiceOptions = {}) {\n const builderField = opts.builderField ?? \"content\";\n const normalField = opts.normalField ?? \"body\";\n const defaultMode = opts.defaultMode ?? \"builder\";\n\n return [\n select({\n name: \"editorMode\",\n label: \"Editor\",\n defaultValue: defaultMode,\n options: [\n option(\"Page Builder\", \"builder\"),\n option(\"Normal editor\", \"normal\"),\n ],\n admin: { description: \"Choose how to edit this entry.\" },\n }),\n pageBuilderField(builderField, {\n label: \"Page Builder\",\n condition: { field: \"editorMode\", equals: \"builder\" },\n }),\n richText({\n name: normalField,\n label: \"Content\",\n admin: { condition: { field: \"editorMode\", equals: \"normal\" } },\n }),\n ];\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Component, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-block error isolation (spec §10). The single intentional client island in
|
|
5
|
+
* `render/`: a throwing block renders a small fallback instead of taking down the
|
|
6
|
+
* whole page/canvas. `getDerivedStateFromError` also works under
|
|
7
|
+
* `renderToStaticMarkup`, so server output is protected too.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
fallback?: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
interface State {
|
|
15
|
+
failed: boolean;
|
|
16
|
+
}
|
|
17
|
+
declare class BlockErrorBoundary extends Component<Props, State> {
|
|
18
|
+
state: State;
|
|
19
|
+
static getDerivedStateFromError(): State;
|
|
20
|
+
render(): ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { BlockErrorBoundary };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { e as BlockDefinition, b as BlockDocument, a as BlockRegistry, j as BreakpointDef, B as BlockNode } from '../style-compiler-hnWIDfko.js';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
export { BlockErrorBoundary } from './ErrorBoundary.js';
|
|
4
|
+
|
|
5
|
+
declare const paragraph: BlockDefinition<{
|
|
6
|
+
text: string;
|
|
7
|
+
}>;
|
|
8
|
+
|
|
9
|
+
declare const heading: BlockDefinition<{
|
|
10
|
+
text: string;
|
|
11
|
+
level: string;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
interface MediaValue {
|
|
15
|
+
mediaId?: string;
|
|
16
|
+
url?: string;
|
|
17
|
+
alt?: string;
|
|
18
|
+
width?: number;
|
|
19
|
+
height?: number;
|
|
20
|
+
}
|
|
21
|
+
interface ImageProps extends MediaValue {
|
|
22
|
+
/** Editor-populated media object (from the media control). */
|
|
23
|
+
media?: MediaValue;
|
|
24
|
+
}
|
|
25
|
+
declare const image: BlockDefinition<ImageProps>;
|
|
26
|
+
|
|
27
|
+
declare const button: BlockDefinition<{
|
|
28
|
+
text: string;
|
|
29
|
+
link: {
|
|
30
|
+
href: string;
|
|
31
|
+
target: string;
|
|
32
|
+
};
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
declare const video: BlockDefinition<{
|
|
36
|
+
provider: string;
|
|
37
|
+
videoId: string;
|
|
38
|
+
}>;
|
|
39
|
+
|
|
40
|
+
declare const container: BlockDefinition<{
|
|
41
|
+
as: string;
|
|
42
|
+
}>;
|
|
43
|
+
|
|
44
|
+
interface GridProps {
|
|
45
|
+
columns?: number;
|
|
46
|
+
gap?: string;
|
|
47
|
+
}
|
|
48
|
+
declare const grid: BlockDefinition<GridProps>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Query Loop block (spec §10/§5). At production render, RenderNode intercepts this type and
|
|
52
|
+
* renders it data-driven via QueryLoop. The editor uses a dedicated settings panel + a live
|
|
53
|
+
* sample-data preview (see admin). This `render` is the plain design-time fallback: the
|
|
54
|
+
* template laid out in the configured column grid.
|
|
55
|
+
*
|
|
56
|
+
* Config lives in `props` (collection / sort / limit / columns / gap / where) and is driven
|
|
57
|
+
* by the admin's QueryLoopSettings panel rather than generic content fields.
|
|
58
|
+
*/
|
|
59
|
+
declare const queryLoop: BlockDefinition<{
|
|
60
|
+
collection: string;
|
|
61
|
+
sort: string;
|
|
62
|
+
limit: number;
|
|
63
|
+
columns: number;
|
|
64
|
+
gap: string;
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The renderer's data seam (spec §10). The host injects an implementation (e.g. one
|
|
69
|
+
* backed by `getNextly()`), so the renderer itself imports NO CMS runtime and stays
|
|
70
|
+
* import-safe + testable. Query Loop (M6) uses `find`; Image uses denormalized props
|
|
71
|
+
* in M3 and `resolveMedia` later.
|
|
72
|
+
*/
|
|
73
|
+
interface FindArgs {
|
|
74
|
+
collection: string;
|
|
75
|
+
where?: unknown;
|
|
76
|
+
sort?: string;
|
|
77
|
+
limit?: number;
|
|
78
|
+
populate?: unknown;
|
|
79
|
+
}
|
|
80
|
+
interface ResolvedMedia {
|
|
81
|
+
url: string;
|
|
82
|
+
alt?: string;
|
|
83
|
+
width?: number;
|
|
84
|
+
height?: number;
|
|
85
|
+
}
|
|
86
|
+
interface DataProvider {
|
|
87
|
+
find(args: FindArgs): Promise<{
|
|
88
|
+
items: Record<string, unknown>[];
|
|
89
|
+
}>;
|
|
90
|
+
findOne(args: {
|
|
91
|
+
collection: string;
|
|
92
|
+
id: string;
|
|
93
|
+
}): Promise<Record<string, unknown> | null>;
|
|
94
|
+
resolveMedia(id: string): Promise<ResolvedMedia | null>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Server-first page renderer (spec §10). Import-safe: NO `getNextly`, no browser
|
|
99
|
+
* globals. Emits one scoped `<style>` block (node styles + sanitized custom CSS) and
|
|
100
|
+
* renders the block tree. The host injects a `dataProvider`; the default registry holds
|
|
101
|
+
* the built-in blocks (populated by importing `./blocks`).
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
interface PageRendererProps {
|
|
105
|
+
document: BlockDocument;
|
|
106
|
+
registry?: BlockRegistry;
|
|
107
|
+
dataProvider?: DataProvider;
|
|
108
|
+
customCss?: string;
|
|
109
|
+
breakpoints?: BreakpointDef[];
|
|
110
|
+
/** Design-token overrides (`{ "color.primary": "#..." }`). Defaults ship a palette. */
|
|
111
|
+
tokens?: Record<string, string>;
|
|
112
|
+
/** Reserved (i18n, spec §13) — threaded through but ignored in the MVP. */
|
|
113
|
+
locale?: string;
|
|
114
|
+
}
|
|
115
|
+
declare function PageRenderer({ document, registry, dataProvider, customCss, breakpoints, tokens, }: PageRendererProps): ReactNode;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Async fetch orchestration for the Query Loop (spec §10). Isolated from React so it is
|
|
119
|
+
* unit-testable. Enforces the per-render query budget, skips when unconfigured, and
|
|
120
|
+
* converts provider failures into an error state instead of throwing.
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
interface QueryBudget {
|
|
124
|
+
n: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Recursively renders one block node to React (spec §10). Server-safe: no browser
|
|
129
|
+
* globals, no `getNextly`. The scoped class is applied to the block's OWN root element
|
|
130
|
+
* (the block spreads `className`) so there is no mandatory wrapper `<div>` — grid/flex
|
|
131
|
+
* stay correct. Unknown block types render a safe fallback; each node is isolated by an
|
|
132
|
+
* error boundary so one broken block never takes down the page.
|
|
133
|
+
*
|
|
134
|
+
* Query Loop (spec §10): the current loop `item` is threaded through recursion (NOT React
|
|
135
|
+
* context — Server Components can't consume context), so a bound prop on any nested block
|
|
136
|
+
* at any depth resolves via `resolveBindings`. `core/query-loop` is intercepted and
|
|
137
|
+
* rendered data-driven via `QueryLoop`.
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
interface RenderNodeProps {
|
|
141
|
+
node: BlockNode;
|
|
142
|
+
registry: BlockRegistry;
|
|
143
|
+
dataProvider?: DataProvider;
|
|
144
|
+
/** Current Query Loop item — threaded to resolve bindings at any depth. */
|
|
145
|
+
item?: Record<string, unknown>;
|
|
146
|
+
/** Remaining query budget shared across nested loops on this page render. */
|
|
147
|
+
budget?: QueryBudget;
|
|
148
|
+
}
|
|
149
|
+
declare function RenderNode({ node, registry, dataProvider, item, budget, }: RenderNodeProps): ReactNode;
|
|
150
|
+
|
|
151
|
+
export { type DataProvider, type FindArgs, PageRenderer, type PageRendererProps, RenderNode, type RenderNodeProps, type ResolvedMedia, button, container, grid, heading, image, paragraph, queryLoop, video };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sanitizeCustomCss
|
|
3
|
+
} from "../chunk-7VN7UNLK.js";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_QUERY_BUDGET,
|
|
6
|
+
RenderNode,
|
|
7
|
+
button,
|
|
8
|
+
container,
|
|
9
|
+
grid,
|
|
10
|
+
heading,
|
|
11
|
+
image,
|
|
12
|
+
paragraph,
|
|
13
|
+
queryLoop,
|
|
14
|
+
video
|
|
15
|
+
} from "../chunk-R7YHOSL2.js";
|
|
16
|
+
import {
|
|
17
|
+
compileDocumentCss,
|
|
18
|
+
compileTokensCss,
|
|
19
|
+
defaultBlockRegistry
|
|
20
|
+
} from "../chunk-ABMSYCSD.js";
|
|
21
|
+
import {
|
|
22
|
+
BlockErrorBoundary
|
|
23
|
+
} from "../chunk-E4G7NNXW.js";
|
|
24
|
+
|
|
25
|
+
// src/render/PageRenderer.tsx
|
|
26
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
27
|
+
var PAGE_ROOT_CLASS = "nx-pb-page";
|
|
28
|
+
function PageRenderer({
|
|
29
|
+
document,
|
|
30
|
+
registry = defaultBlockRegistry,
|
|
31
|
+
dataProvider,
|
|
32
|
+
customCss,
|
|
33
|
+
breakpoints,
|
|
34
|
+
tokens
|
|
35
|
+
}) {
|
|
36
|
+
if (!document?.root) return null;
|
|
37
|
+
const css = compileTokensCss(PAGE_ROOT_CLASS, tokens) + "\n" + compileDocumentCss(document, { breakpoints }) + "\n" + sanitizeCustomCss(customCss ?? "", PAGE_ROOT_CLASS);
|
|
38
|
+
return /* @__PURE__ */ jsxs("div", { className: PAGE_ROOT_CLASS, children: [
|
|
39
|
+
/* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: css } }),
|
|
40
|
+
/* @__PURE__ */ jsx(
|
|
41
|
+
RenderNode,
|
|
42
|
+
{
|
|
43
|
+
node: document.root,
|
|
44
|
+
registry,
|
|
45
|
+
dataProvider,
|
|
46
|
+
budget: { n: DEFAULT_QUERY_BUDGET }
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
] });
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
BlockErrorBoundary,
|
|
53
|
+
PageRenderer,
|
|
54
|
+
RenderNode,
|
|
55
|
+
button,
|
|
56
|
+
container,
|
|
57
|
+
grid,
|
|
58
|
+
heading,
|
|
59
|
+
image,
|
|
60
|
+
paragraph,
|
|
61
|
+
queryLoop,
|
|
62
|
+
video
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/render/PageRenderer.tsx"],"sourcesContent":["/**\n * Server-first page renderer (spec §10). Import-safe: NO `getNextly`, no browser\n * globals. Emits one scoped `<style>` block (node styles + sanitized custom CSS) and\n * renders the block tree. The host injects a `dataProvider`; the default registry holds\n * the built-in blocks (populated by importing `./blocks`).\n */\nimport type { ReactNode } from \"react\";\n\nimport { sanitizeCustomCss } from \"../core/css-sanitize\";\nimport { defaultBlockRegistry, type BlockRegistry } from \"../core/registry\";\nimport {\n compileDocumentCss,\n compileTokensCss,\n type BreakpointDef,\n} from \"../core/style-compiler\";\nimport type { BlockDocument } from \"../core/types\";\n\nimport type { DataProvider } from \"./dataProvider\";\nimport { DEFAULT_QUERY_BUDGET } from \"./query/types\";\nimport { RenderNode } from \"./RenderNode\";\n\nconst PAGE_ROOT_CLASS = \"nx-pb-page\";\n\nexport interface PageRendererProps {\n document: BlockDocument;\n registry?: BlockRegistry;\n dataProvider?: DataProvider;\n customCss?: string;\n breakpoints?: BreakpointDef[];\n /** Design-token overrides (`{ \"color.primary\": \"#...\" }`). Defaults ship a palette. */\n tokens?: Record<string, string>;\n /** Reserved (i18n, spec §13) — threaded through but ignored in the MVP. */\n locale?: string;\n}\n\nexport function PageRenderer({\n document,\n registry = defaultBlockRegistry,\n dataProvider,\n customCss,\n breakpoints,\n tokens,\n}: PageRendererProps): ReactNode {\n if (!document?.root) return null;\n\n const css =\n compileTokensCss(PAGE_ROOT_CLASS, tokens) +\n \"\\n\" +\n compileDocumentCss(document, { breakpoints }) +\n \"\\n\" +\n sanitizeCustomCss(customCss ?? \"\", PAGE_ROOT_CLASS);\n\n return (\n <div className={PAGE_ROOT_CLASS}>\n <style dangerouslySetInnerHTML={{ __html: css }} />\n <RenderNode\n node={document.root}\n registry={registry}\n dataProvider={dataProvider}\n budget={{ n: DEFAULT_QUERY_BUDGET }}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAqDI,SACE,KADF;AAhCJ,IAAM,kBAAkB;AAcjB,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiC;AAC/B,MAAI,CAAC,UAAU,KAAM,QAAO;AAE5B,QAAM,MACJ,iBAAiB,iBAAiB,MAAM,IACxC,OACA,mBAAmB,UAAU,EAAE,YAAY,CAAC,IAC5C,OACA,kBAAkB,aAAa,IAAI,eAAe;AAEpD,SACE,qBAAC,SAAI,WAAW,iBACd;AAAA,wBAAC,WAAM,yBAAyB,EAAE,QAAQ,IAAI,GAAG;AAAA,IACjD;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,SAAS;AAAA,QACf;AAAA,QACA;AAAA,QACA,QAAQ,EAAE,GAAG,qBAAqB;AAAA;AAAA,IACpC;AAAA,KACF;AAEJ;","names":[]}
|