idml-ui 0.1.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.cjs ADDED
@@ -0,0 +1,2434 @@
1
+ "use client";
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var src_exports = {};
33
+ __export(src_exports, {
34
+ BUILTIN_COMPONENTS: () => BUILTIN_COMPONENTS,
35
+ ComponentRenderer: () => ComponentRenderer,
36
+ ConfigProvider: () => ConfigProvider,
37
+ ConfigRenderer: () => ConfigRenderer,
38
+ EditorPage: () => EditorPage,
39
+ LayoutRenderer: () => LayoutRenderer,
40
+ UIConfigSchema: () => UIConfigSchema,
41
+ clearComponentRegistry: () => clearComponentRegistry,
42
+ clearRegistry: () => clearRegistry,
43
+ getComponent: () => getComponent,
44
+ getMethod: () => getMethod,
45
+ parseIdml: () => parseIdml,
46
+ registerComponent: () => registerComponent,
47
+ registerMethod: () => registerMethod,
48
+ safeValidateConfig: () => safeValidateConfig,
49
+ useConfigContext: () => useConfigContext,
50
+ useRegisteredMethod: () => useRegisteredMethod,
51
+ useVisibility: () => useVisibility,
52
+ validateConfig: () => validateConfig
53
+ });
54
+ module.exports = __toCommonJS(src_exports);
55
+
56
+ // src/schema/config.schema.ts
57
+ var import_zod6 = require("zod");
58
+
59
+ // src/schema/tokens.schema.ts
60
+ var import_zod = require("zod");
61
+ var ColorTokenSchema = import_zod.z.object({
62
+ name: import_zod.z.string().min(1),
63
+ value: import_zod.z.string().min(1),
64
+ darkValue: import_zod.z.string().optional()
65
+ }).strict();
66
+ var TypographyTokenSchema = import_zod.z.object({
67
+ name: import_zod.z.string().min(1),
68
+ fontFamily: import_zod.z.string().optional(),
69
+ fontSize: import_zod.z.string().min(1),
70
+ fontWeight: import_zod.z.union([import_zod.z.number(), import_zod.z.string()]).optional(),
71
+ lineHeight: import_zod.z.string().optional(),
72
+ letterSpacing: import_zod.z.string().optional()
73
+ }).strict();
74
+ var SpacingTokenSchema = import_zod.z.object({
75
+ name: import_zod.z.string().min(1),
76
+ value: import_zod.z.string().min(1)
77
+ }).strict();
78
+ var TokensDefSchema = import_zod.z.object({
79
+ colors: import_zod.z.array(ColorTokenSchema),
80
+ typography: import_zod.z.array(TypographyTokenSchema),
81
+ spacing: import_zod.z.array(SpacingTokenSchema)
82
+ }).strict();
83
+
84
+ // src/schema/page.schema.ts
85
+ var import_zod5 = require("zod");
86
+
87
+ // src/schema/layout.schema.ts
88
+ var import_zod2 = require("zod");
89
+ var percentageString = import_zod2.z.string().regex(/^\d+(\.\d+)?%$/, {
90
+ message: 'All size values must be expressed as percentages (e.g. "100%", "33.3%"). px, rem, em, vw, vh are not allowed.'
91
+ });
92
+ var SizeDefSchema = import_zod2.z.object({
93
+ width: percentageString.optional(),
94
+ height: percentageString.optional(),
95
+ minWidth: percentageString.optional(),
96
+ minHeight: percentageString.optional(),
97
+ maxWidth: percentageString.optional(),
98
+ maxHeight: percentageString.optional()
99
+ }).strict();
100
+ var VisibilitySchema = import_zod2.z.object({ ref: import_zod2.z.string(), negate: import_zod2.z.boolean().optional() }).strict();
101
+ var ConditionalClassSchema = import_zod2.z.object({ classes: import_zod2.z.string(), ref: import_zod2.z.string(), negate: import_zod2.z.boolean().optional() }).strict();
102
+ var DynamicDimSchema = import_zod2.z.object({
103
+ ref: import_zod2.z.string(),
104
+ whenTrue: import_zod2.z.string().optional(),
105
+ whenFalse: import_zod2.z.string().optional()
106
+ }).strict();
107
+ var DynamicSizeSchema = import_zod2.z.object({ width: DynamicDimSchema.optional(), height: DynamicDimSchema.optional() }).strict();
108
+ var LayoutDefSchema = import_zod2.z.lazy(
109
+ () => import_zod2.z.discriminatedUnion("type", [
110
+ import_zod2.z.object({
111
+ type: import_zod2.z.literal("flex"),
112
+ direction: import_zod2.z.enum(["row", "column", "row-reverse", "column-reverse"]),
113
+ wrap: import_zod2.z.enum(["nowrap", "wrap", "wrap-reverse"]).optional(),
114
+ justifyContent: import_zod2.z.enum(["flex-start", "flex-end", "center", "space-between", "space-around", "space-evenly"]).optional(),
115
+ alignItems: import_zod2.z.enum(["flex-start", "flex-end", "center", "stretch", "baseline"]).optional(),
116
+ gap: import_zod2.z.string().optional(),
117
+ size: SizeDefSchema.optional(),
118
+ children: import_zod2.z.array(LayoutDefSchema),
119
+ componentId: import_zod2.z.string().optional(),
120
+ idmlStyle: import_zod2.z.record(import_zod2.z.string()).optional(),
121
+ className: import_zod2.z.string().optional(),
122
+ classRefs: import_zod2.z.array(import_zod2.z.string()).optional(),
123
+ condClasses: import_zod2.z.array(ConditionalClassSchema).optional(),
124
+ visibility: VisibilitySchema.optional(),
125
+ dynamicSize: DynamicSizeSchema.optional()
126
+ }).strict(),
127
+ import_zod2.z.object({
128
+ type: import_zod2.z.literal("grid"),
129
+ columns: import_zod2.z.number().int().min(1).max(24),
130
+ rows: import_zod2.z.number().int().min(1).optional(),
131
+ gap: import_zod2.z.string().optional(),
132
+ size: SizeDefSchema.optional(),
133
+ children: import_zod2.z.array(LayoutDefSchema),
134
+ componentId: import_zod2.z.string().optional(),
135
+ idmlStyle: import_zod2.z.record(import_zod2.z.string()).optional(),
136
+ className: import_zod2.z.string().optional(),
137
+ classRefs: import_zod2.z.array(import_zod2.z.string()).optional(),
138
+ condClasses: import_zod2.z.array(ConditionalClassSchema).optional(),
139
+ visibility: VisibilitySchema.optional(),
140
+ dynamicSize: DynamicSizeSchema.optional()
141
+ }).strict()
142
+ ])
143
+ );
144
+
145
+ // src/schema/component.schema.ts
146
+ var import_zod4 = require("zod");
147
+
148
+ // src/schema/binding.schema.ts
149
+ var import_zod3 = require("zod");
150
+ var DataBindingDefSchema = import_zod3.z.object({
151
+ prop: import_zod3.z.string().min(1),
152
+ methodId: import_zod3.z.string().min(1),
153
+ kind: import_zod3.z.enum(["handler", "value", "model"]).optional()
154
+ }).strict();
155
+ var VisibilityDefSchema = import_zod3.z.object({
156
+ methodId: import_zod3.z.string().min(1),
157
+ negate: import_zod3.z.boolean().optional()
158
+ }).strict();
159
+
160
+ // src/schema/component.schema.ts
161
+ var ComponentDefSchema = import_zod4.z.lazy(
162
+ () => import_zod4.z.object({
163
+ id: import_zod4.z.string().min(1),
164
+ type: import_zod4.z.string().min(1),
165
+ props: import_zod4.z.record(import_zod4.z.unknown()).optional(),
166
+ tokenProps: import_zod4.z.object({
167
+ color: import_zod4.z.string().optional(),
168
+ background: import_zod4.z.string().optional(),
169
+ typography: import_zod4.z.string().optional(),
170
+ padding: import_zod4.z.string().optional()
171
+ }).strict().optional(),
172
+ bindings: import_zod4.z.array(DataBindingDefSchema).optional(),
173
+ visibility: VisibilityDefSchema.optional(),
174
+ children: import_zod4.z.array(ComponentDefSchema).optional(),
175
+ idmlStyle: import_zod4.z.record(import_zod4.z.string()).optional(),
176
+ className: import_zod4.z.string().optional()
177
+ }).strict()
178
+ );
179
+
180
+ // src/schema/page.schema.ts
181
+ var PageDefSchema = import_zod5.z.object({
182
+ route: import_zod5.z.string().min(1),
183
+ title: import_zod5.z.string().optional(),
184
+ layout: LayoutDefSchema,
185
+ components: import_zod5.z.array(ComponentDefSchema),
186
+ meta: import_zod5.z.object({
187
+ description: import_zod5.z.string().optional(),
188
+ ogTitle: import_zod5.z.string().optional()
189
+ }).strict().optional()
190
+ }).strict();
191
+
192
+ // src/schema/config.schema.ts
193
+ var UIConfigSchema = import_zod6.z.object({
194
+ version: import_zod6.z.literal("1"),
195
+ tokens: TokensDefSchema,
196
+ pages: import_zod6.z.array(PageDefSchema).min(1),
197
+ userComponents: import_zod6.z.array(import_zod6.z.string()).optional()
198
+ }).strict();
199
+ function validateConfig(raw) {
200
+ return UIConfigSchema.parse(raw);
201
+ }
202
+ function safeValidateConfig(raw) {
203
+ return UIConfigSchema.safeParse(raw);
204
+ }
205
+
206
+ // src/renderer/ConfigProvider.tsx
207
+ var import_react = require("react");
208
+
209
+ // src/renderer/tokens/token-resolver.ts
210
+ function injectTokenVars(tokens, darkMode) {
211
+ const vars = {};
212
+ for (const color of tokens.colors) {
213
+ const value = darkMode && color.darkValue ? color.darkValue : color.value;
214
+ vars[`--isd-color-${color.name}`] = value;
215
+ }
216
+ for (const spacing of tokens.spacing) {
217
+ vars[`--isd-spacing-${spacing.name}`] = spacing.value;
218
+ }
219
+ return vars;
220
+ }
221
+ function resolveTokenProps(tokenProps, tokens) {
222
+ if (!tokenProps) return {};
223
+ const style = {};
224
+ if (tokenProps.color) {
225
+ style.color = `var(--isd-color-${tokenProps.color})`;
226
+ }
227
+ if (tokenProps.background) {
228
+ style.backgroundColor = `var(--isd-color-${tokenProps.background})`;
229
+ }
230
+ if (tokenProps.typography) {
231
+ const token = tokens.typography.find((t) => t.name === tokenProps.typography);
232
+ if (token) {
233
+ style.fontSize = token.fontSize;
234
+ if (token.fontWeight) style.fontWeight = token.fontWeight;
235
+ if (token.lineHeight) style.lineHeight = token.lineHeight;
236
+ if (token.letterSpacing) style.letterSpacing = token.letterSpacing;
237
+ if (token.fontFamily) style.fontFamily = token.fontFamily;
238
+ }
239
+ }
240
+ if (tokenProps.padding) {
241
+ style.padding = `var(--isd-spacing-${tokenProps.padding})`;
242
+ }
243
+ return style;
244
+ }
245
+
246
+ // src/renderer/registry/method-registry.ts
247
+ var registry = /* @__PURE__ */ new Map();
248
+ function registerMethod(id, fn) {
249
+ if (registry.has(id)) {
250
+ console.warn(`[idml] registerMethod: overwriting existing method "${id}"`);
251
+ }
252
+ registry.set(id, fn);
253
+ }
254
+ function getMethod(id) {
255
+ return registry.get(id);
256
+ }
257
+ function clearRegistry() {
258
+ registry.clear();
259
+ }
260
+
261
+ // src/renderer/registry/component-registry.ts
262
+ var registry2 = /* @__PURE__ */ new Map();
263
+ function registerComponent(name, component) {
264
+ if (registry2.has(name)) {
265
+ console.warn(`[idml] registerComponent: overwriting existing component "${name}"`);
266
+ }
267
+ registry2.set(name, component);
268
+ }
269
+ function getComponent(name) {
270
+ return registry2.get(name);
271
+ }
272
+ function clearComponentRegistry() {
273
+ registry2.clear();
274
+ }
275
+
276
+ // src/renderer/ConfigProvider.tsx
277
+ var import_jsx_runtime = require("react/jsx-runtime");
278
+ var ConfigContext = (0, import_react.createContext)(void 0);
279
+ function useConfigContext() {
280
+ const ctx = (0, import_react.useContext)(ConfigContext);
281
+ if (!ctx) throw new Error("useConfigContext must be used inside <ConfigProvider>");
282
+ return ctx;
283
+ }
284
+ function ConfigProvider({
285
+ config: rawConfig,
286
+ methods = [],
287
+ components = [],
288
+ children,
289
+ onConfigInvalid,
290
+ debug = false
291
+ }) {
292
+ const [validConfig, setValidConfig] = (0, import_react.useState)(null);
293
+ const [darkMode, setDarkMode] = (0, import_react.useState)(false);
294
+ (0, import_react.useEffect)(() => {
295
+ try {
296
+ const parsed = validateConfig(rawConfig);
297
+ setValidConfig(parsed);
298
+ } catch (err) {
299
+ const error = err instanceof Error ? err : new Error(String(err));
300
+ onConfigInvalid?.(error);
301
+ }
302
+ }, [rawConfig, onConfigInvalid]);
303
+ (0, import_react.useEffect)(() => {
304
+ clearRegistry();
305
+ for (const { id, fn } of methods) {
306
+ registerMethod(id, fn);
307
+ }
308
+ }, [methods]);
309
+ (0, import_react.useEffect)(() => {
310
+ clearComponentRegistry();
311
+ for (const { name, component } of components) {
312
+ registerComponent(name, component);
313
+ }
314
+ }, [components]);
315
+ (0, import_react.useEffect)(() => {
316
+ if (process.env.NODE_ENV !== "development") return;
317
+ const handler = (e) => {
318
+ const target = e.target.closest("[data-isd-id]");
319
+ if (target) {
320
+ const id = target.getAttribute("data-isd-id");
321
+ window.parent.postMessage({ type: "isd:select", componentId: id }, window.location.origin);
322
+ }
323
+ };
324
+ document.addEventListener("click", handler);
325
+ return () => document.removeEventListener("click", handler);
326
+ }, []);
327
+ if (!validConfig) return null;
328
+ const tokenVars = injectTokenVars(validConfig.tokens, darkMode);
329
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConfigContext.Provider, { value: { config: validConfig, darkMode, setDarkMode, tokenVars, debug }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: tokenVars, children }) });
330
+ }
331
+
332
+ // src/renderer/LayoutRenderer.tsx
333
+ var import_react6 = __toESM(require("react"), 1);
334
+
335
+ // src/renderer/ComponentRenderer.tsx
336
+ var import_react5 = __toESM(require("react"), 1);
337
+
338
+ // src/renderer/hooks/useVisibility.ts
339
+ function useVisibility(rule) {
340
+ if (!rule) return true;
341
+ const method = getMethod(rule.methodId);
342
+ if (!method) {
343
+ console.warn(
344
+ `[idml] Visibility method "${rule.methodId}" not registered \u2014 defaulting to visible`
345
+ );
346
+ return true;
347
+ }
348
+ const result = Boolean(method());
349
+ return rule.negate ? !result : result;
350
+ }
351
+
352
+ // src/renderer/builtins/index.ts
353
+ var import_react4 = __toESM(require("react"), 1);
354
+ var import_react_dom = require("react-dom");
355
+ var import_link = __toESM(require("next/link"), 1);
356
+
357
+ // src/renderer/repeat-context.ts
358
+ var import_react2 = require("react");
359
+ var RepeatItemContext = (0, import_react2.createContext)(void 0);
360
+
361
+ // src/renderer/form-context.tsx
362
+ var import_react3 = require("react");
363
+ var import_jsx_runtime2 = require("react/jsx-runtime");
364
+ var FormStateContext = (0, import_react3.createContext)(void 0);
365
+ function FormStateProvider({
366
+ initial,
367
+ children
368
+ }) {
369
+ const [values, setValues] = (0, import_react3.useState)(initial ?? {});
370
+ const setValue = (0, import_react3.useCallback)((name, value) => {
371
+ setValues((prev) => ({ ...prev, [name]: value }));
372
+ }, []);
373
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FormStateContext.Provider, { value: { values, setValue }, children });
374
+ }
375
+
376
+ // src/renderer/builtins/index.ts
377
+ var Text = ({ text, children, ...props }) => import_react4.default.createElement("span", props, text || children);
378
+ var Heading = ({ level = 1, text, children, ...props }) => {
379
+ const Tag = `h${level}`;
380
+ return import_react4.default.createElement(Tag, props, text || children);
381
+ };
382
+ var BUTTON_BASE = {
383
+ border: "none",
384
+ outline: "none",
385
+ textDecoration: "none",
386
+ cursor: "pointer",
387
+ fontFamily: "inherit",
388
+ fontSize: "inherit"
389
+ };
390
+ var Button = ({ text, children, onClick, href, style, type = "button", ...props }) => {
391
+ const merged = { ...BUTTON_BASE, ...style };
392
+ const content = [text, children];
393
+ return href ? import_react4.default.createElement(import_link.default, { href, style: merged, ...props }, content) : import_react4.default.createElement("button", { type, onClick, style: merged, ...props }, content);
394
+ };
395
+ var Image = ({ src, alt, ...props }) => import_react4.default.createElement("img", { src, alt: alt || "", ...props });
396
+ var List = ({ items = [], children, ...props }) => import_react4.default.createElement(
397
+ "ul",
398
+ props,
399
+ items.map(
400
+ (item, i) => import_react4.default.createElement("li", { key: i }, typeof item === "string" ? item : JSON.stringify(item))
401
+ ),
402
+ children
403
+ );
404
+ var Card = ({ children, ...props }) => import_react4.default.createElement("div", props, children);
405
+ var Divider = (props) => import_react4.default.createElement("hr", props);
406
+ var Spacer = (props) => import_react4.default.createElement("div", props);
407
+ var Icon = ({ name, ...props }) => import_react4.default.createElement("span", props, name || "\u25CF");
408
+ var Table = ({ children, ...props }) => import_react4.default.createElement("div", { "data-isd-table": "", ...props }, children ?? "Table");
409
+ var Input = ({ type = "text", value, onChange, placeholder, name, disabled, style, ...props }) => import_react4.default.createElement("input", { type, value, onChange, placeholder, name, disabled, style, ...props });
410
+ var Textarea = ({ value, onChange, placeholder, name, rows, style, ...props }) => import_react4.default.createElement("textarea", { value, onChange, placeholder, name, rows, style, ...props });
411
+ var Option = ({ value, label, children, ...props }) => import_react4.default.createElement("option", { value, ...props }, label ?? children);
412
+ var Select = ({ value, onChange, name, options, children, style, ...props }) => import_react4.default.createElement(
413
+ "select",
414
+ { value, onChange, name, style, ...props },
415
+ Array.isArray(options) ? options.map(
416
+ (opt, i) => import_react4.default.createElement("option", { key: i, value: opt.value }, opt.label)
417
+ ) : children
418
+ );
419
+ var Checkbox = ({ checked, onChange, name, disabled, style, ...props }) => import_react4.default.createElement("input", { type: "checkbox", checked, onChange, name, disabled, style, ...props });
420
+ var Radio = ({ checked, value, name, onChange, style, ...props }) => import_react4.default.createElement("input", { type: "radio", checked, value, name, onChange, style, ...props });
421
+ var Label = ({ htmlFor, text, children, style, ...props }) => import_react4.default.createElement("label", { htmlFor, style, ...props }, text ?? children);
422
+ var Children = ({ slot, children, ...props }) => import_react4.default.createElement(import_react4.default.Fragment, null, slot ?? children);
423
+ var Repeat = ({ data, children, style, ...props }) => {
424
+ const items = Array.isArray(data) ? data : [];
425
+ return import_react4.default.createElement(
426
+ "div",
427
+ { "data-isd-repeat": "", style, ...props },
428
+ items.map(
429
+ (item, i) => import_react4.default.createElement(RepeatItemContext.Provider, { key: i, value: item }, children)
430
+ )
431
+ );
432
+ };
433
+ var Form = ({ children, style, ...props }) => import_react4.default.createElement(
434
+ FormStateProvider,
435
+ null,
436
+ import_react4.default.createElement("form", { style, ...props }, children)
437
+ );
438
+ var MODAL_BACKDROP = {
439
+ position: "fixed",
440
+ inset: 0,
441
+ background: "rgba(0,0,0,0.5)",
442
+ display: "flex",
443
+ alignItems: "center",
444
+ justifyContent: "center",
445
+ zIndex: 1e3
446
+ };
447
+ var MODAL_PANEL = {
448
+ maxHeight: "90vh",
449
+ overflow: "auto"
450
+ };
451
+ var Modal = ({ open, onClose, children, style, ...props }) => {
452
+ if (!open) return null;
453
+ if (typeof document === "undefined") return null;
454
+ const panel = import_react4.default.createElement(
455
+ "div",
456
+ {
457
+ "data-isd-modal": "",
458
+ style: { ...MODAL_PANEL, ...style },
459
+ onClick: (e) => e.stopPropagation(),
460
+ ...props
461
+ },
462
+ children
463
+ );
464
+ return (0, import_react_dom.createPortal)(
465
+ import_react4.default.createElement("div", { "data-isd-modal-backdrop": "", style: MODAL_BACKDROP, onClick: onClose }, panel),
466
+ document.body
467
+ );
468
+ };
469
+ var BUILTIN_COMPONENTS = {
470
+ Text,
471
+ Heading,
472
+ Button,
473
+ Image,
474
+ List,
475
+ Card,
476
+ Divider,
477
+ Spacer,
478
+ Icon,
479
+ Table,
480
+ Input,
481
+ Textarea,
482
+ Select,
483
+ Option,
484
+ Checkbox,
485
+ Radio,
486
+ Label,
487
+ Children,
488
+ Repeat,
489
+ Form,
490
+ Modal
491
+ };
492
+
493
+ // src/renderer/ComponentRenderer.tsx
494
+ var import_jsx_runtime3 = require("react/jsx-runtime");
495
+ function ComponentRenderer({
496
+ component,
497
+ components,
498
+ slot
499
+ }) {
500
+ const { config } = useConfigContext();
501
+ const isVisible = useVisibility(component.visibility);
502
+ const boundProps = useBoundProps(component.bindings ?? []);
503
+ if (!isVisible) return null;
504
+ const FILL_HEIGHT = /* @__PURE__ */ new Set(["Button", "Image", "Card", "Divider", "Spacer"]);
505
+ const tokenStyle = {
506
+ width: "100%",
507
+ ...FILL_HEIGHT.has(component.type) ? { height: "100%" } : {},
508
+ boxSizing: "border-box",
509
+ ...resolveTokenProps(component.tokenProps, config.tokens),
510
+ ...component.idmlStyle ?? {}
511
+ };
512
+ const childElements = (component.children ?? []).map((child) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ComponentRenderer, { component: child, components }, child.id));
513
+ const Component = getComponent(component.type) ?? BUILTIN_COMPONENTS[component.type];
514
+ if (!Component) {
515
+ console.warn(`[idml] Unknown component type "${component.type}"`);
516
+ return null;
517
+ }
518
+ const content = slot ?? childElements;
519
+ const hasContent = Array.isArray(content) ? content.length > 0 : content != null;
520
+ const props = {
521
+ ...component.props,
522
+ ...boundProps,
523
+ style: tokenStyle,
524
+ "data-isd-id": component.id
525
+ };
526
+ const mergedClass = [component.className, boundProps.className].filter(Boolean).join(" ");
527
+ if (mergedClass) props.className = mergedClass;
528
+ else delete props.className;
529
+ return hasContent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { ...props, children: content }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { ...props });
530
+ }
531
+ function useBoundProps(bindings) {
532
+ const item = import_react5.default.useContext(RepeatItemContext);
533
+ const formStore = import_react5.default.useContext(FormStateContext);
534
+ const props = {};
535
+ for (const binding of bindings) {
536
+ if (binding.kind === "value") {
537
+ const resolved = resolveValueRef(binding.methodId, item, formStore?.values);
538
+ if (binding.prop === "className") {
539
+ props.className = [props.className, resolved].filter(Boolean).join(" ");
540
+ } else {
541
+ props[binding.prop] = resolved;
542
+ }
543
+ } else if (binding.kind === "model") {
544
+ const name = binding.methodId;
545
+ const isChecked = binding.prop === "checked";
546
+ const current = formStore?.values[name];
547
+ props[binding.prop] = isChecked ? Boolean(current) : current ?? "";
548
+ props.onChange = (e) => formStore?.setValue(name, isChecked ? e?.target?.checked : e?.target?.value);
549
+ } else {
550
+ const method = getMethod(binding.methodId);
551
+ if (typeof method === "function") {
552
+ const fn = method;
553
+ props[binding.prop] = (event) => fn(formStore?.values ?? {}, {
554
+ set: (name, value) => formStore?.setValue(name, value),
555
+ event,
556
+ item
557
+ });
558
+ }
559
+ }
560
+ }
561
+ return props;
562
+ }
563
+ function resolveValueRef(ref, item, state) {
564
+ const segments = ref.split(".");
565
+ let base;
566
+ if (segments[0] === "item") {
567
+ base = item;
568
+ } else if (segments[0] === "state") {
569
+ base = state;
570
+ } else {
571
+ const method = getMethod(segments[0]);
572
+ base = typeof method === "function" ? method(item, state) : void 0;
573
+ }
574
+ for (let i = 1; i < segments.length; i++) {
575
+ if (base == null) return void 0;
576
+ base = base[segments[i]];
577
+ }
578
+ return base;
579
+ }
580
+
581
+ // src/renderer/LayoutRenderer.tsx
582
+ var import_jsx_runtime4 = require("react/jsx-runtime");
583
+ var DIM_ANIM = { duration: 300, easing: "ease-in-out" };
584
+ var toDimStr = (v) => v == null ? void 0 : typeof v === "number" ? `${v}%` : String(v);
585
+ function LayoutRenderer({ layout, components }) {
586
+ const { config, debug } = useConfigContext();
587
+ const item = import_react6.default.useContext(RepeatItemContext);
588
+ const formStore = import_react6.default.useContext(FormStateContext);
589
+ const resolveDyn = (d) => {
590
+ const v = resolveValueRef(d.ref, item, formStore?.values);
591
+ if (d.whenTrue !== void 0) return v ? d.whenTrue : d.whenFalse;
592
+ return toDimStr(v);
593
+ };
594
+ const dynW = layout.dynamicSize?.width ? resolveDyn(layout.dynamicSize.width) : void 0;
595
+ const dynH = layout.dynamicSize?.height ? resolveDyn(layout.dynamicSize.height) : void 0;
596
+ const elRef = import_react6.default.useRef(null);
597
+ const prevDimsRef = import_react6.default.useRef({ w: dynW, h: dynH });
598
+ import_react6.default.useLayoutEffect(() => {
599
+ const el = elRef.current;
600
+ const prev = prevDimsRef.current;
601
+ if (el && layout.dynamicSize) {
602
+ if (dynW !== void 0 && prev.w !== void 0 && prev.w !== dynW) {
603
+ el.animate([{ width: prev.w }, { width: dynW }], DIM_ANIM);
604
+ }
605
+ if (dynH !== void 0 && prev.h !== void 0 && prev.h !== dynH) {
606
+ el.animate([{ height: prev.h }, { height: dynH }], DIM_ANIM);
607
+ }
608
+ }
609
+ prevDimsRef.current = { w: dynW, h: dynH };
610
+ if (process.env.NODE_ENV !== "production" && el) {
611
+ const cs = getComputedStyle(el);
612
+ if (cs.overflowY === "hidden" && el.scrollHeight - el.clientHeight > 1) {
613
+ console.error(
614
+ `[idml] content overflows its box and is clipped \u2014 ${el.scrollHeight - el.clientHeight}px taller than its height. Reduce the content or its spacing.`,
615
+ el
616
+ );
617
+ }
618
+ }
619
+ }, [dynW, dynH]);
620
+ if (layout.visibility) {
621
+ const value = resolveValueRef(layout.visibility.ref, item, formStore?.values);
622
+ const visible = layout.visibility.negate ? !value : Boolean(value);
623
+ if (!visible) return null;
624
+ }
625
+ const sizeStyle = resolveSizeStyle(layout.size);
626
+ const gapValue = layout.gap ? resolveGap(layout.gap, config.tokens.spacing) : void 0;
627
+ const containerStyle = {
628
+ ...sizeStyle,
629
+ // Debug aid (opt-in): show bounding boxes for structural Row/Col containers
630
+ // (not component-bound leaf cells). Off by default so pages render cleanly.
631
+ ...debug && !layout.componentId ? { outline: "1px solid rgba(100,100,100,0.35)" } : {},
632
+ // Apply .idml inline styles (can override sizeStyle values, e.g. height: '30vh' for scroll pages)
633
+ ...layout.idmlStyle ?? {},
634
+ // Prevent flex children from shrinking so percentage/vh heights are respected and scroll works
635
+ flexShrink: 0
636
+ };
637
+ let containerClass = "";
638
+ if (layout.type === "flex") {
639
+ containerClass = "flex";
640
+ containerStyle.flexDirection = layout.direction;
641
+ if (layout.wrap) containerStyle.flexWrap = layout.wrap;
642
+ if (layout.justifyContent) containerStyle.justifyContent = layout.justifyContent;
643
+ if (layout.alignItems) containerStyle.alignItems = layout.alignItems;
644
+ if (gapValue) containerStyle.gap = gapValue;
645
+ } else {
646
+ containerClass = "grid";
647
+ containerStyle.gridTemplateColumns = `repeat(${layout.columns}, minmax(0, 1fr))`;
648
+ if (layout.rows) containerStyle.gridTemplateRows = `repeat(${layout.rows}, minmax(0, 1fr))`;
649
+ if (gapValue) containerStyle.gap = gapValue;
650
+ }
651
+ if (layout.dynamicSize) {
652
+ if (dynW !== void 0) {
653
+ containerStyle.width = dynW;
654
+ containerStyle.minWidth = 0;
655
+ }
656
+ if (dynH !== void 0) {
657
+ containerStyle.height = dynH;
658
+ containerStyle.minHeight = 0;
659
+ }
660
+ }
661
+ if (layout.className) containerClass += ` ${layout.className}`;
662
+ if (layout.classRefs?.length) {
663
+ for (const ref of layout.classRefs) {
664
+ const cls = resolveValueRef(ref, item, formStore?.values);
665
+ if (cls) containerClass += ` ${cls}`;
666
+ }
667
+ }
668
+ if (layout.condClasses?.length) {
669
+ for (const c of layout.condClasses) {
670
+ const v = resolveValueRef(c.ref, item, formStore?.values);
671
+ if (c.negate ? !v : Boolean(v)) containerClass += ` ${c.classes}`;
672
+ }
673
+ }
674
+ const boundComponent = layout.componentId ? components.find((c) => c.id === layout.componentId) : void 0;
675
+ const children = layout.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LayoutRenderer, { layout: child, components }, i));
676
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: elRef, className: containerClass, style: containerStyle, "data-isd-layout": true, children: boundComponent ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
677
+ ComponentRenderer,
678
+ {
679
+ component: boundComponent,
680
+ components,
681
+ slot: layout.children.length > 0 ? children : void 0
682
+ }
683
+ ) : children });
684
+ }
685
+ function resolveSizeStyle(size) {
686
+ if (!size) return {};
687
+ const s = {};
688
+ if (size.width) s.width = size.width;
689
+ if (size.height) s.height = size.height;
690
+ if (size.minWidth) s.minWidth = size.minWidth;
691
+ if (size.minHeight) s.minHeight = size.minHeight;
692
+ if (size.maxWidth) s.maxWidth = size.maxWidth;
693
+ if (size.maxHeight) s.maxHeight = size.maxHeight;
694
+ return s;
695
+ }
696
+ function resolveGap(tokenName, spacingTokens) {
697
+ return spacingTokens.find((t) => t.name === tokenName)?.value;
698
+ }
699
+
700
+ // src/renderer/ConfigRenderer.tsx
701
+ var import_jsx_runtime5 = require("react/jsx-runtime");
702
+ function ConfigRenderer({ page }) {
703
+ const { config } = useConfigContext();
704
+ const pageDef = config.pages.find((p) => p.route === page);
705
+ if (!pageDef) {
706
+ console.warn(`[idml] No page found for route "${page}"`);
707
+ return null;
708
+ }
709
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FormStateProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
710
+ "div",
711
+ {
712
+ "data-isd-page": page,
713
+ style: { width: "100%", height: "100vh", display: "flex", flexDirection: "column" },
714
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LayoutRenderer, { layout: pageDef.layout, components: pageDef.components })
715
+ }
716
+ ) });
717
+ }
718
+
719
+ // src/renderer/hooks/useRegisteredMethod.ts
720
+ function useRegisteredMethod(id) {
721
+ return getMethod(id);
722
+ }
723
+
724
+ // src/editor/EditorPage.tsx
725
+ var import_react12 = require("react");
726
+
727
+ // src/editor/hooks/useEditorState.ts
728
+ var import_react7 = require("react");
729
+ function useEditorState(initialConfig) {
730
+ const [state, setState] = (0, import_react7.useState)({
731
+ config: initialConfig,
732
+ selectedComponentId: null,
733
+ selectedPageRoute: initialConfig.pages[0]?.route ?? "/",
734
+ history: [],
735
+ future: []
736
+ });
737
+ const selectComponent = (0, import_react7.useCallback)((id) => {
738
+ setState((s) => ({ ...s, selectedComponentId: id }));
739
+ }, []);
740
+ const selectPage = (0, import_react7.useCallback)((route) => {
741
+ setState((s) => ({ ...s, selectedPageRoute: route, selectedComponentId: null }));
742
+ }, []);
743
+ const updateConfig = (0, import_react7.useCallback)((next) => {
744
+ setState((s) => ({
745
+ ...s,
746
+ config: next,
747
+ history: [...s.history, s.config].slice(-50),
748
+ future: []
749
+ }));
750
+ }, []);
751
+ const undo = (0, import_react7.useCallback)(() => {
752
+ setState((s) => {
753
+ if (s.history.length === 0) return s;
754
+ const prev = s.history[s.history.length - 1];
755
+ return {
756
+ ...s,
757
+ config: prev,
758
+ history: s.history.slice(0, -1),
759
+ future: [s.config, ...s.future]
760
+ };
761
+ });
762
+ }, []);
763
+ const redo = (0, import_react7.useCallback)(() => {
764
+ setState((s) => {
765
+ if (s.future.length === 0) return s;
766
+ const next = s.future[0];
767
+ return {
768
+ ...s,
769
+ config: next,
770
+ history: [...s.history, s.config],
771
+ future: s.future.slice(1)
772
+ };
773
+ });
774
+ }, []);
775
+ return { state, selectComponent, selectPage, updateConfig, undo, redo };
776
+ }
777
+
778
+ // src/editor/hooks/useSSEConfig.ts
779
+ var import_react8 = require("react");
780
+ function useSSEConfig(onConfigChange) {
781
+ const handleChange = (0, import_react8.useCallback)(() => {
782
+ fetch("/api/_isd/config").then((r) => r.json()).then((raw) => onConfigChange(raw)).catch((err) => console.error("[idml editor] Failed to reload config:", err));
783
+ }, [onConfigChange]);
784
+ (0, import_react8.useEffect)(() => {
785
+ const es = new EventSource("/api/_isd/events");
786
+ es.onmessage = (event) => {
787
+ try {
788
+ const data = JSON.parse(event.data);
789
+ if (data.type === "config:change") {
790
+ handleChange();
791
+ }
792
+ } catch {
793
+ }
794
+ };
795
+ es.onerror = () => {
796
+ console.warn("[idml editor] SSE connection lost, will retry automatically");
797
+ };
798
+ return () => es.close();
799
+ }, [handleChange]);
800
+ }
801
+
802
+ // src/editor/hooks/useSaveConfig.ts
803
+ var import_react9 = require("react");
804
+ function useSaveConfig() {
805
+ const [saving, setSaving] = (0, import_react9.useState)(false);
806
+ const [error, setError] = (0, import_react9.useState)(null);
807
+ const save = (0, import_react9.useCallback)(async (config) => {
808
+ setSaving(true);
809
+ setError(null);
810
+ try {
811
+ const res = await fetch("/api/_isd/config", {
812
+ method: "POST",
813
+ headers: { "Content-Type": "application/json" },
814
+ body: JSON.stringify(config)
815
+ });
816
+ if (!res.ok) {
817
+ const data = await res.json();
818
+ throw new Error(data.error || `Save failed: ${res.status}`);
819
+ }
820
+ } catch (err) {
821
+ setError(err instanceof Error ? err.message : "Unknown error");
822
+ } finally {
823
+ setSaving(false);
824
+ }
825
+ }, []);
826
+ return { save, saving, error };
827
+ }
828
+
829
+ // src/editor/panels/ComponentTree.tsx
830
+ var import_jsx_runtime6 = require("react/jsx-runtime");
831
+ function ComponentTree({
832
+ pages,
833
+ selectedId,
834
+ onSelect,
835
+ onPageChange
836
+ }) {
837
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { padding: "12px", overflow: "auto", height: "100%" }, children: [
838
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: "12px", fontWeight: 600, marginBottom: "8px" }, children: "Pages" }),
839
+ pages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { marginBottom: "12px" }, children: [
840
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
841
+ "button",
842
+ {
843
+ onClick: () => onPageChange(page.route),
844
+ style: {
845
+ display: "block",
846
+ width: "100%",
847
+ padding: "6px 8px",
848
+ textAlign: "left",
849
+ border: "1px solid #d1d5db",
850
+ background: "#f9fafb",
851
+ cursor: "pointer",
852
+ fontSize: "12px",
853
+ fontWeight: 500
854
+ },
855
+ children: page.route
856
+ }
857
+ ),
858
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { paddingLeft: "8px", marginTop: "4px" }, children: page.components.map((comp) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
859
+ ComponentTreeItem,
860
+ {
861
+ component: comp,
862
+ selected: selectedId === comp.id,
863
+ onSelect
864
+ },
865
+ comp.id
866
+ )) })
867
+ ] }, page.route))
868
+ ] });
869
+ }
870
+ function ComponentTreeItem({
871
+ component,
872
+ selected,
873
+ onSelect
874
+ }) {
875
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
876
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
877
+ "button",
878
+ {
879
+ onClick: () => onSelect(component.id),
880
+ style: {
881
+ display: "block",
882
+ width: "100%",
883
+ padding: "4px 6px",
884
+ textAlign: "left",
885
+ border: selected ? "1px solid #1a56db" : "1px solid transparent",
886
+ background: selected ? "#eff6ff" : "transparent",
887
+ cursor: "pointer",
888
+ fontSize: "11px",
889
+ marginBottom: "2px"
890
+ },
891
+ children: [
892
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontFamily: "monospace" }, children: component.type }),
893
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontSize: "10px", color: "#6b7280" }, children: [
894
+ "#",
895
+ component.id
896
+ ] })
897
+ ]
898
+ }
899
+ ),
900
+ component.children?.map((child) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { paddingLeft: "12px" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ComponentTreeItem, { component: child, selected, onSelect }) }, child.id))
901
+ ] });
902
+ }
903
+
904
+ // src/editor/panels/LivePreview.tsx
905
+ var import_react10 = require("react");
906
+ var import_jsx_runtime7 = require("react/jsx-runtime");
907
+ function LivePreview({
908
+ pageRoute,
909
+ onComponentSelect
910
+ }) {
911
+ const iframeRef = (0, import_react10.useRef)(null);
912
+ (0, import_react10.useEffect)(() => {
913
+ const handler = (event) => {
914
+ if (event.data?.type === "isd:select" && typeof event.data.componentId === "string") {
915
+ onComponentSelect(event.data.componentId);
916
+ }
917
+ };
918
+ window.addEventListener("message", handler);
919
+ return () => window.removeEventListener("message", handler);
920
+ }, [onComponentSelect]);
921
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { height: "100%", display: "flex", flexDirection: "column", background: "#fff" }, children: [
922
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { padding: "8px", borderBottom: "1px solid #e5e7eb", fontSize: "12px" }, children: [
923
+ "Preview: ",
924
+ pageRoute
925
+ ] }),
926
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
927
+ "iframe",
928
+ {
929
+ ref: iframeRef,
930
+ src: pageRoute,
931
+ style: {
932
+ flex: 1,
933
+ border: "none",
934
+ width: "100%"
935
+ },
936
+ title: "Live preview"
937
+ }
938
+ )
939
+ ] });
940
+ }
941
+
942
+ // src/editor/panels/PropertyPanel.tsx
943
+ var import_react11 = require("react");
944
+ var import_jsx_runtime8 = require("react/jsx-runtime");
945
+ function PropertyPanel({
946
+ component,
947
+ tokens,
948
+ onChange
949
+ }) {
950
+ const [expandedSection, setExpandedSection] = (0, import_react11.useState)("props");
951
+ const updateComponent = (updates) => {
952
+ onChange({ ...component, ...updates });
953
+ };
954
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { padding: "12px", overflow: "auto", height: "100%", fontSize: "12px" }, children: [
955
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { marginBottom: "16px" }, children: [
956
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "11px", color: "#6b7280", marginBottom: "4px" }, children: "ID" }),
957
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
958
+ "div",
959
+ {
960
+ style: {
961
+ fontFamily: "monospace",
962
+ padding: "4px 6px",
963
+ background: "#f3f4f6",
964
+ borderRadius: "2px"
965
+ },
966
+ children: component.id
967
+ }
968
+ )
969
+ ] }),
970
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
971
+ Section,
972
+ {
973
+ title: "Type",
974
+ expanded: expandedSection === "type",
975
+ onToggle: () => setExpandedSection(expandedSection === "type" ? null : "type"),
976
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
977
+ "input",
978
+ {
979
+ type: "text",
980
+ value: component.type,
981
+ onChange: (e) => updateComponent({ type: e.target.value }),
982
+ style: { width: "100%", padding: "4px", fontSize: "12px" }
983
+ }
984
+ )
985
+ }
986
+ ),
987
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
988
+ Section,
989
+ {
990
+ title: "Token Props",
991
+ expanded: expandedSection === "tokenProps",
992
+ onToggle: () => setExpandedSection(expandedSection === "tokenProps" ? null : "tokenProps"),
993
+ children: [
994
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
995
+ PropertyInput,
996
+ {
997
+ label: "Color",
998
+ value: component.tokenProps?.color ?? "",
999
+ onChange: (value) => updateComponent({
1000
+ tokenProps: { ...component.tokenProps, color: value || void 0 }
1001
+ }),
1002
+ options: tokens.colors.map((c) => c.name)
1003
+ }
1004
+ ),
1005
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1006
+ PropertyInput,
1007
+ {
1008
+ label: "Background",
1009
+ value: component.tokenProps?.background ?? "",
1010
+ onChange: (value) => updateComponent({
1011
+ tokenProps: { ...component.tokenProps, background: value || void 0 }
1012
+ }),
1013
+ options: tokens.colors.map((c) => c.name)
1014
+ }
1015
+ ),
1016
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1017
+ PropertyInput,
1018
+ {
1019
+ label: "Typography",
1020
+ value: component.tokenProps?.typography ?? "",
1021
+ onChange: (value) => updateComponent({
1022
+ tokenProps: { ...component.tokenProps, typography: value || void 0 }
1023
+ }),
1024
+ options: tokens.typography.map((t) => t.name)
1025
+ }
1026
+ )
1027
+ ]
1028
+ }
1029
+ ),
1030
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1031
+ Section,
1032
+ {
1033
+ title: "Visibility",
1034
+ expanded: expandedSection === "visibility",
1035
+ onToggle: () => setExpandedSection(expandedSection === "visibility" ? null : "visibility"),
1036
+ children: component.visibility ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1037
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { marginBottom: "8px" }, children: [
1038
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "10px", color: "#6b7280" }, children: "Method" }),
1039
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1040
+ "input",
1041
+ {
1042
+ type: "text",
1043
+ value: component.visibility.methodId,
1044
+ onChange: (e) => updateComponent({
1045
+ visibility: { ...component.visibility, methodId: e.target.value }
1046
+ }),
1047
+ style: { width: "100%", padding: "4px", fontSize: "12px" }
1048
+ }
1049
+ )
1050
+ ] }),
1051
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "flex", alignItems: "center", gap: "4px", fontSize: "11px" }, children: [
1052
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1053
+ "input",
1054
+ {
1055
+ type: "checkbox",
1056
+ checked: component.visibility.negate ?? false,
1057
+ onChange: (e) => updateComponent({
1058
+ visibility: { ...component.visibility, negate: e.target.checked }
1059
+ })
1060
+ }
1061
+ ),
1062
+ "Negate (show when false)"
1063
+ ] }),
1064
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1065
+ "button",
1066
+ {
1067
+ onClick: () => updateComponent({ visibility: void 0 }),
1068
+ style: {
1069
+ marginTop: "8px",
1070
+ padding: "4px 8px",
1071
+ fontSize: "11px",
1072
+ background: "#fee2e2",
1073
+ color: "#dc2626",
1074
+ border: "none",
1075
+ cursor: "pointer"
1076
+ },
1077
+ children: "Remove visibility"
1078
+ }
1079
+ )
1080
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1081
+ "button",
1082
+ {
1083
+ onClick: () => updateComponent({ visibility: { methodId: "", negate: false } }),
1084
+ style: {
1085
+ padding: "4px 8px",
1086
+ fontSize: "11px",
1087
+ background: "#dbeafe",
1088
+ color: "#1a56db",
1089
+ border: "none",
1090
+ cursor: "pointer"
1091
+ },
1092
+ children: "Add visibility rule"
1093
+ }
1094
+ )
1095
+ }
1096
+ )
1097
+ ] });
1098
+ }
1099
+ function Section({
1100
+ title,
1101
+ expanded,
1102
+ onToggle,
1103
+ children
1104
+ }) {
1105
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { marginBottom: "12px" }, children: [
1106
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1107
+ "button",
1108
+ {
1109
+ onClick: onToggle,
1110
+ style: {
1111
+ width: "100%",
1112
+ padding: "6px 8px",
1113
+ textAlign: "left",
1114
+ background: "#f3f4f6",
1115
+ border: "1px solid #e5e7eb",
1116
+ cursor: "pointer",
1117
+ fontSize: "11px",
1118
+ fontWeight: 600
1119
+ },
1120
+ children: [
1121
+ expanded ? "\u25BC" : "\u25B6",
1122
+ " ",
1123
+ title
1124
+ ]
1125
+ }
1126
+ ),
1127
+ expanded && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { padding: "8px", background: "#fafafa" }, children })
1128
+ ] });
1129
+ }
1130
+ function PropertyInput({
1131
+ label,
1132
+ value,
1133
+ onChange,
1134
+ options
1135
+ }) {
1136
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { marginBottom: "8px" }, children: [
1137
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { style: { display: "block", fontSize: "10px", color: "#6b7280", marginBottom: "2px" }, children: label }),
1138
+ options.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1139
+ "select",
1140
+ {
1141
+ value,
1142
+ onChange: (e) => onChange(e.target.value),
1143
+ style: {
1144
+ width: "100%",
1145
+ padding: "4px",
1146
+ fontSize: "12px",
1147
+ border: "1px solid #d1d5db"
1148
+ },
1149
+ children: [
1150
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "", children: "None" }),
1151
+ options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: opt, children: opt }, opt))
1152
+ ]
1153
+ }
1154
+ ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1155
+ "input",
1156
+ {
1157
+ type: "text",
1158
+ value,
1159
+ onChange: (e) => onChange(e.target.value),
1160
+ style: {
1161
+ width: "100%",
1162
+ padding: "4px",
1163
+ fontSize: "12px",
1164
+ border: "1px solid #d1d5db"
1165
+ }
1166
+ }
1167
+ )
1168
+ ] });
1169
+ }
1170
+
1171
+ // src/editor/EditorPage.tsx
1172
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1173
+ function EditorPage() {
1174
+ const [initialConfig, setInitialConfig] = (0, import_react12.useState)(null);
1175
+ const [loading, setLoading] = (0, import_react12.useState)(true);
1176
+ (0, import_react12.useEffect)(() => {
1177
+ fetch("/api/_isd/config").then((r) => r.json()).then((config) => {
1178
+ setInitialConfig(config);
1179
+ setLoading(false);
1180
+ }).catch((err) => {
1181
+ console.error("[idml editor] Failed to load config:", err);
1182
+ setLoading(false);
1183
+ });
1184
+ }, []);
1185
+ if (loading) {
1186
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "16px" }, children: "Loading editor..." });
1187
+ }
1188
+ if (!initialConfig) {
1189
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "16px", color: "#dc2626" }, children: "Failed to load configuration" });
1190
+ }
1191
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EditorShell, { initialConfig });
1192
+ }
1193
+ function EditorShell({ initialConfig }) {
1194
+ const { state, selectComponent, selectPage, updateConfig, undo, redo } = useEditorState(initialConfig);
1195
+ const { save, saving, error } = useSaveConfig();
1196
+ useSSEConfig((fresh) => {
1197
+ updateConfig(fresh);
1198
+ });
1199
+ const activePage = state.config.pages.find((p) => p.route === state.selectedPageRoute);
1200
+ const selectedComponent = activePage?.components.find((c) => c.id === state.selectedComponentId);
1201
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", width: "100vw", height: "100vh", fontFamily: "system-ui" }, children: [
1202
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { width: "20%", borderRight: "1px solid #e5e7eb", display: "flex", flexDirection: "column" }, children: [
1203
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "8px", borderBottom: "1px solid #e5e7eb", fontSize: "12px", fontWeight: 600 }, children: "Structure" }),
1204
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1205
+ ComponentTree,
1206
+ {
1207
+ pages: state.config.pages,
1208
+ selectedId: state.selectedComponentId,
1209
+ onSelect: selectComponent,
1210
+ onPageChange: selectPage
1211
+ }
1212
+ )
1213
+ ] }),
1214
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { flex: 1, display: "flex", flexDirection: "column" }, children: [
1215
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1216
+ "div",
1217
+ {
1218
+ style: {
1219
+ padding: "8px",
1220
+ borderBottom: "1px solid #e5e7eb",
1221
+ display: "flex",
1222
+ gap: "8px",
1223
+ alignItems: "center",
1224
+ fontSize: "12px"
1225
+ },
1226
+ children: [
1227
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1228
+ "button",
1229
+ {
1230
+ onClick: undo,
1231
+ disabled: state.history.length === 0,
1232
+ style: {
1233
+ padding: "4px 8px",
1234
+ fontSize: "11px",
1235
+ cursor: state.history.length === 0 ? "not-allowed" : "pointer",
1236
+ opacity: state.history.length === 0 ? 0.5 : 1
1237
+ },
1238
+ children: "\u21B6 Undo"
1239
+ }
1240
+ ),
1241
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1242
+ "button",
1243
+ {
1244
+ onClick: redo,
1245
+ disabled: state.future.length === 0,
1246
+ style: {
1247
+ padding: "4px 8px",
1248
+ fontSize: "11px",
1249
+ cursor: state.future.length === 0 ? "not-allowed" : "pointer",
1250
+ opacity: state.future.length === 0 ? 0.5 : 1
1251
+ },
1252
+ children: "\u21B7 Redo"
1253
+ }
1254
+ ),
1255
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1256
+ "button",
1257
+ {
1258
+ onClick: () => save(state.config),
1259
+ disabled: saving,
1260
+ style: {
1261
+ padding: "4px 8px",
1262
+ fontSize: "11px",
1263
+ background: "#dbeafe",
1264
+ color: "#1a56db",
1265
+ border: "none",
1266
+ cursor: saving ? "not-allowed" : "pointer",
1267
+ opacity: saving ? 0.7 : 1
1268
+ },
1269
+ children: saving ? "\u22EF Saving" : "\u{1F4BE} Save"
1270
+ }
1271
+ ),
1272
+ error && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { fontSize: "10px", color: "#dc2626", flex: 1 }, children: [
1273
+ "Error: ",
1274
+ error
1275
+ ] })
1276
+ ]
1277
+ }
1278
+ ),
1279
+ activePage && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LivePreview, { pageRoute: state.selectedPageRoute, onComponentSelect: selectComponent })
1280
+ ] }),
1281
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { width: "25%", borderLeft: "1px solid #e5e7eb", display: "flex", flexDirection: "column" }, children: [
1282
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "8px", borderBottom: "1px solid #e5e7eb", fontSize: "12px", fontWeight: 600 }, children: "Properties" }),
1283
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { flex: 1, overflow: "auto" }, children: selectedComponent && activePage ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1284
+ PropertyPanel,
1285
+ {
1286
+ component: selectedComponent,
1287
+ tokens: state.config.tokens,
1288
+ onChange: (updated) => {
1289
+ const newPages = state.config.pages.map(
1290
+ (p) => p.route === state.selectedPageRoute ? {
1291
+ ...p,
1292
+ components: p.components.map(
1293
+ (c) => c.id === updated.id ? updated : c
1294
+ )
1295
+ } : p
1296
+ );
1297
+ updateConfig({ ...state.config, pages: newPages });
1298
+ }
1299
+ }
1300
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "12px", fontSize: "12px", color: "#6b7280" }, children: "Select a component to edit its properties" }) })
1301
+ ] })
1302
+ ] });
1303
+ }
1304
+
1305
+ // src/parser/idml-parser.ts
1306
+ var isDimRef = (d) => typeof d === "object" && d !== null && "ref" in d;
1307
+ var SINGLE_CHAR_TOKENS = {
1308
+ "(": "LPAREN",
1309
+ ")": "RPAREN",
1310
+ "[": "LBRACKET",
1311
+ "]": "RBRACKET",
1312
+ "{": "LBRACE",
1313
+ "}": "RBRACE",
1314
+ ",": "COMMA",
1315
+ ":": "COLON",
1316
+ "?": "QUESTION",
1317
+ "!": "BANG"
1318
+ };
1319
+ var MAX_LINE_WIDTH = 80;
1320
+ function validateSource(source) {
1321
+ const lines = source.split("\n");
1322
+ let codeStarted = false;
1323
+ lines.forEach((line, idx) => {
1324
+ const lineNo = idx + 1;
1325
+ if (line.length > MAX_LINE_WIDTH) {
1326
+ throw new Error(
1327
+ `[idml] line ${lineNo} is ${line.length} columns; the limit is ${MAX_LINE_WIDTH}`
1328
+ );
1329
+ }
1330
+ const trimmed = line.trim();
1331
+ if (trimmed === "") return;
1332
+ if (trimmed.startsWith("#")) {
1333
+ if (codeStarted) {
1334
+ throw new Error(
1335
+ `[idml] line ${lineNo}: comments are only allowed in the header block at the very top of the file, before any code`
1336
+ );
1337
+ }
1338
+ return;
1339
+ }
1340
+ codeStarted = true;
1341
+ });
1342
+ }
1343
+ function tokenize(source) {
1344
+ validateSource(source);
1345
+ const stripped = source.split("\n").map((line) => line.trimStart().startsWith("#") ? "" : line).join("\n");
1346
+ const tokens = [];
1347
+ let i = 0;
1348
+ while (i < stripped.length) {
1349
+ if (/\s/.test(stripped[i])) {
1350
+ i++;
1351
+ continue;
1352
+ }
1353
+ if (stripped[i] === "." && stripped[i + 1] === "/") {
1354
+ let j = i + 2;
1355
+ while (j < stripped.length && /[\w/-]/.test(stripped[j])) j++;
1356
+ tokens.push({ type: "ROUTE", value: "/" + stripped.slice(i + 2, j) });
1357
+ i = j;
1358
+ continue;
1359
+ }
1360
+ if (stripped[i] === "#" && /[0-9a-fA-F]/.test(stripped[i + 1] ?? "")) {
1361
+ let j = i + 1;
1362
+ while (j < stripped.length && /[0-9a-fA-F]/.test(stripped[j])) j++;
1363
+ tokens.push({ type: "COLOR", value: stripped.slice(i, j) });
1364
+ i = j;
1365
+ continue;
1366
+ }
1367
+ if (stripped[i] === "@" && /[a-zA-Z_]/.test(stripped[i + 1] ?? "")) {
1368
+ let j = i + 1;
1369
+ while (j < stripped.length && /[\w.-]/.test(stripped[j])) j++;
1370
+ tokens.push({ type: "VALUE_REF", value: stripped.slice(i + 1, j) });
1371
+ i = j;
1372
+ continue;
1373
+ }
1374
+ if (stripped[i] === "~" && /[a-zA-Z_]/.test(stripped[i + 1] ?? "")) {
1375
+ let j = i + 1;
1376
+ while (j < stripped.length && /[\w-]/.test(stripped[j])) j++;
1377
+ tokens.push({ type: "MODEL_REF", value: stripped.slice(i + 1, j) });
1378
+ i = j;
1379
+ continue;
1380
+ }
1381
+ if (stripped[i] === "<") {
1382
+ throw new Error(
1383
+ "[idml] inline `<...>` style blocks are no longer supported; declare a styled variant (Name:BaseType) and apply it instead"
1384
+ );
1385
+ }
1386
+ if (stripped[i] === "`") {
1387
+ let j = i + 1;
1388
+ while (j < stripped.length && stripped[j] !== "`") j++;
1389
+ tokens.push({ type: "CLASS_BLOCK", value: stripped.slice(i + 1, j).trim() });
1390
+ i = j + 1;
1391
+ continue;
1392
+ }
1393
+ if (stripped[i] in SINGLE_CHAR_TOKENS) {
1394
+ tokens.push({ type: SINGLE_CHAR_TOKENS[stripped[i]] });
1395
+ i++;
1396
+ continue;
1397
+ }
1398
+ if (stripped[i] === '"') {
1399
+ let j = i + 1;
1400
+ while (j < stripped.length && stripped[j] !== '"') {
1401
+ if (stripped[j] === "\\") j++;
1402
+ j++;
1403
+ }
1404
+ tokens.push({ type: "STRING", value: stripped.slice(i + 1, j) });
1405
+ i = j + 1;
1406
+ continue;
1407
+ }
1408
+ if (/\d/.test(stripped[i])) {
1409
+ let j = i;
1410
+ while (j < stripped.length && /[\d.]/.test(stripped[j])) j++;
1411
+ tokens.push({ type: "NUMBER", value: parseFloat(stripped.slice(i, j)) });
1412
+ i = j;
1413
+ continue;
1414
+ }
1415
+ if (/[a-zA-Z_]/.test(stripped[i])) {
1416
+ let j = i;
1417
+ while (j < stripped.length && /[\w-]/.test(stripped[j])) j++;
1418
+ tokens.push({ type: "IDENT", value: stripped.slice(i, j) });
1419
+ i = j;
1420
+ continue;
1421
+ }
1422
+ throw new Error(`[idml] Unexpected character '${stripped[i]}' at position ${i}`);
1423
+ }
1424
+ return tokens;
1425
+ }
1426
+ function applyStyleProp(key, val, result) {
1427
+ switch (key) {
1428
+ case "bg":
1429
+ result.backgroundColor = val;
1430
+ break;
1431
+ case "fg":
1432
+ result.color = val;
1433
+ break;
1434
+ case "size":
1435
+ result.fontSize = val.endsWith("vw") ? val : `${val}vw`;
1436
+ break;
1437
+ case "font":
1438
+ result.fontFamily = val;
1439
+ break;
1440
+ case "weight":
1441
+ result.fontWeight = val;
1442
+ break;
1443
+ case "style":
1444
+ if (val === "bold") result.fontWeight = "700";
1445
+ else if (val === "italic") result.fontStyle = "italic";
1446
+ break;
1447
+ case "pad":
1448
+ result.padding = val.endsWith("%") ? val : `${val}%`;
1449
+ break;
1450
+ case "radius":
1451
+ result.borderRadius = val.endsWith("px") ? val : `${val}px`;
1452
+ break;
1453
+ case "gap":
1454
+ result.gap = val.endsWith("vw") ? val : `${val}vw`;
1455
+ break;
1456
+ case "align":
1457
+ result.textAlign = val;
1458
+ break;
1459
+ case "overflow":
1460
+ result.overflowY = val;
1461
+ break;
1462
+ case "h":
1463
+ result.height = val;
1464
+ break;
1465
+ case "w":
1466
+ result.width = val;
1467
+ break;
1468
+ default:
1469
+ result[key] = val;
1470
+ }
1471
+ }
1472
+ var FN_REF_PREFIX = "\0fn:";
1473
+ var VALUE_REF_PREFIX = "\0val:";
1474
+ var MODEL_REF_PREFIX = "\0model:";
1475
+ var BUILTIN_NAMES = /* @__PURE__ */ new Set([
1476
+ "Text",
1477
+ "Heading",
1478
+ "Button",
1479
+ "Image",
1480
+ "List",
1481
+ "Card",
1482
+ "Divider",
1483
+ "Spacer",
1484
+ "Icon",
1485
+ "Table",
1486
+ "Children",
1487
+ "Row",
1488
+ "Col",
1489
+ "Repeat",
1490
+ "Form",
1491
+ "Modal",
1492
+ "Column",
1493
+ "Overlay",
1494
+ "Input",
1495
+ "Textarea",
1496
+ "Select",
1497
+ "Option",
1498
+ "Checkbox",
1499
+ "Radio",
1500
+ "Label"
1501
+ ]);
1502
+ var IdmlParser = class _IdmlParser {
1503
+ constructor(tokens) {
1504
+ this.pos = 0;
1505
+ this.styleRegistry = /* @__PURE__ */ new Map();
1506
+ // Reusable component definitions: name -> body template (item list). A `define`
1507
+ // block registers one; using the name as an item expands the body (macro-style),
1508
+ // substituting the call's children at the `Children` marker. See convertItem.
1509
+ this.defRegistry = /* @__PURE__ */ new Map();
1510
+ // Parameter names per definition (e.g. `define TopBar(title)` -> ['title']).
1511
+ // At expansion the call's positional args are bound to these names and any
1512
+ // matching references inside the body are substituted. See convertItem.
1513
+ this.defParamRegistry = /* @__PURE__ */ new Map();
1514
+ this.tokens = tokens;
1515
+ }
1516
+ peek(offset = 0) {
1517
+ return this.tokens[this.pos + offset];
1518
+ }
1519
+ consume(type) {
1520
+ const t = this.tokens[this.pos++];
1521
+ if (!t) throw new Error("[idml] Unexpected end of input");
1522
+ if (type && t.type !== type) {
1523
+ throw new Error(`[idml] Expected ${type}, got ${t.type} ("${t.value}")`);
1524
+ }
1525
+ return t;
1526
+ }
1527
+ // Entry point. resolve() is called to load imported .idml files.
1528
+ parseFile(resolve) {
1529
+ this.parseImports(resolve);
1530
+ this.parseTopDecls();
1531
+ const pages = [];
1532
+ while (this.peek()) {
1533
+ const route = this.consume("ROUTE").value;
1534
+ let scroll = false;
1535
+ if (this.peek()?.type === "LBRACKET") {
1536
+ this.consume("LBRACKET");
1537
+ const flag = this.consume("IDENT").value;
1538
+ if (flag === "scroll") scroll = true;
1539
+ this.consume("RBRACKET");
1540
+ }
1541
+ const items = [];
1542
+ while (this.peek() && this.peek()?.type !== "ROUTE") {
1543
+ const t = this.peek();
1544
+ if (t?.type === "IDENT" && t.value === "import") {
1545
+ this.parseImports(resolve);
1546
+ continue;
1547
+ }
1548
+ if (t?.type === "IDENT" && t.value === "define") {
1549
+ this.parseDefinition();
1550
+ continue;
1551
+ }
1552
+ if (t?.type === "IDENT" && this.peek(1)?.type === "COLON" && this.peek(2)?.type === "IDENT") {
1553
+ this.parseStyleDefs();
1554
+ continue;
1555
+ }
1556
+ items.push(this.parseItem());
1557
+ }
1558
+ pages.push({ route, scroll, items });
1559
+ }
1560
+ return pages;
1561
+ }
1562
+ // Consume import lines. Two forms:
1563
+ // import "path" (whole-file style/def import)
1564
+ // import Name, Name from "path" (named component/def import)
1565
+ // In both cases the referenced .idml file is parsed and its definitions +
1566
+ // style-defs are registered into the shared registries.
1567
+ parseImports(resolve) {
1568
+ while (this.peek(0)?.type === "IDENT" && this.peek(0)?.value === "import") {
1569
+ const next = this.peek(1);
1570
+ if (next?.type === "STRING") {
1571
+ this.pos++;
1572
+ const importPath = this.consume("STRING").value;
1573
+ this.resolveImport(importPath, [], resolve);
1574
+ continue;
1575
+ }
1576
+ if (next?.type === "IDENT") {
1577
+ this.pos++;
1578
+ const names = [this.consume("IDENT").value];
1579
+ while (this.peek()?.type === "COMMA") {
1580
+ this.pos++;
1581
+ names.push(this.consume("IDENT").value);
1582
+ }
1583
+ const fromTok = this.consume("IDENT");
1584
+ if (fromTok.value !== "from") {
1585
+ throw new Error(`[idml] Expected 'from' in import, got "${fromTok.value}"`);
1586
+ }
1587
+ const importPath = this.consume("STRING").value;
1588
+ this.resolveImport(importPath, names, resolve);
1589
+ continue;
1590
+ }
1591
+ break;
1592
+ }
1593
+ }
1594
+ // Resolve and parse an imported .idml file into the shared registries.
1595
+ // `names`, when non-empty, are validated against what the file actually defines.
1596
+ resolveImport(importPath, names, resolve) {
1597
+ const afterLastSlash = importPath.slice(importPath.lastIndexOf("/") + 1);
1598
+ const dotIdx = afterLastSlash.lastIndexOf(".");
1599
+ const ext = dotIdx >= 0 ? afterLastSlash.slice(dotIdx) : "";
1600
+ if (ext !== ".idml" && ext !== "") return;
1601
+ if (!resolve) return;
1602
+ const src = resolve(importPath);
1603
+ const sub = new _IdmlParser(tokenize(src));
1604
+ sub.styleRegistry = this.styleRegistry;
1605
+ sub.defRegistry = this.defRegistry;
1606
+ sub.defParamRegistry = this.defParamRegistry;
1607
+ sub.parseImports(resolve);
1608
+ sub.parseTopDecls();
1609
+ for (const name of names) {
1610
+ if (!this.defRegistry.has(name) && !this.styleRegistry.has(name) && !BUILTIN_NAMES.has(name)) {
1611
+ console.warn(`[idml] import: "${name}" is not defined in ${importPath}`);
1612
+ }
1613
+ }
1614
+ }
1615
+ // Parse the top-of-file declarations: `define` component definitions and
1616
+ // `Name:BaseType` style-defs, in any order, until a page route or EOF.
1617
+ parseTopDecls() {
1618
+ for (; ; ) {
1619
+ const t = this.peek();
1620
+ if (t?.type === "IDENT" && t.value === "define") {
1621
+ this.parseDefinition();
1622
+ continue;
1623
+ }
1624
+ if (t?.type === "IDENT" && this.peek(1)?.type === "COLON" && this.peek(2)?.type === "IDENT") {
1625
+ this.parseStyleDefs();
1626
+ continue;
1627
+ }
1628
+ break;
1629
+ }
1630
+ }
1631
+ // Consume `define Name(params?) { ...items including Children()... }`.
1632
+ parseDefinition() {
1633
+ this.pos++;
1634
+ const name = this.consume("IDENT").value;
1635
+ this.consume("LPAREN");
1636
+ const params = [];
1637
+ if (this.peek()?.type !== "RPAREN") {
1638
+ params.push(this.consume("IDENT").value);
1639
+ while (this.peek()?.type === "COMMA") {
1640
+ this.pos++;
1641
+ params.push(this.consume("IDENT").value);
1642
+ }
1643
+ }
1644
+ this.consume("RPAREN");
1645
+ this.consume("LBRACE");
1646
+ const body = [];
1647
+ while (this.peek()?.type !== "RBRACE") {
1648
+ body.push(this.parseItem());
1649
+ }
1650
+ this.consume("RBRACE");
1651
+ this.defRegistry.set(name, body);
1652
+ this.defParamRegistry.set(name, params);
1653
+ }
1654
+ // Consume `Name:BaseType("arg"...) { prop: value }` definitions.
1655
+ parseStyleDefs() {
1656
+ while (this.peek(0)?.type === "IDENT" && this.peek(1)?.type === "COLON" && this.peek(2)?.type === "IDENT") {
1657
+ const name = this.consume("IDENT").value;
1658
+ this.consume("COLON");
1659
+ const baseType = this.consume("IDENT").value;
1660
+ const defaultArgs = [];
1661
+ if (this.peek()?.type === "LPAREN") {
1662
+ this.consume("LPAREN");
1663
+ if (this.peek()?.type !== "RPAREN") defaultArgs.push(...this.parseArgList());
1664
+ this.consume("RPAREN");
1665
+ }
1666
+ let className;
1667
+ while (this.peek()?.type === "CLASS_BLOCK") {
1668
+ const cls = this.consume("CLASS_BLOCK").value;
1669
+ className = className ? `${className} ${cls}` : cls;
1670
+ }
1671
+ const style = this.peek()?.type === "LBRACE" ? this.parseStyleDefBody() : {};
1672
+ this.styleRegistry.set(name, { baseType, defaultArgs, style, className });
1673
+ }
1674
+ }
1675
+ // Parse `{ prop: value ... }` body of a style definition.
1676
+ parseStyleDefBody() {
1677
+ this.consume("LBRACE");
1678
+ const result = {};
1679
+ while (this.peek()?.type !== "RBRACE") {
1680
+ const key = this.consume("IDENT").value;
1681
+ this.consume("COLON");
1682
+ const val = this.parseStyleValue();
1683
+ applyStyleProp(key, val, result);
1684
+ }
1685
+ this.consume("RBRACE");
1686
+ return result;
1687
+ }
1688
+ // Parse a value token inside a style def body.
1689
+ parseStyleValue() {
1690
+ const t = this.peek();
1691
+ if (!t) throw new Error("[idml] Expected style value");
1692
+ if (t.type === "COLOR") {
1693
+ this.pos++;
1694
+ return t.value;
1695
+ }
1696
+ if (t.type === "NUMBER") {
1697
+ const n = this.consume("NUMBER").value;
1698
+ const next = this.peek();
1699
+ if (next?.type === "IDENT" && ["vh", "vw", "px", "rem", "em"].includes(next.value)) {
1700
+ this.pos++;
1701
+ return `${n}${next.value}`;
1702
+ }
1703
+ return String(n);
1704
+ }
1705
+ if (t.type === "IDENT") {
1706
+ this.pos++;
1707
+ return t.value;
1708
+ }
1709
+ throw new Error(`[idml] Unexpected token type ${t.type} as style value`);
1710
+ }
1711
+ parseItem() {
1712
+ const rawName = this.consume("IDENT").value;
1713
+ const regEntry = this.styleRegistry.get(rawName);
1714
+ const name = regEntry ? regEntry.baseType : rawName;
1715
+ const baseStyle = regEntry ? { ...regEntry.style } : {};
1716
+ this.consume("LPAREN");
1717
+ const parsedArgs = [];
1718
+ if (this.peek()?.type !== "RPAREN") parsedArgs.push(...this.parseArgList());
1719
+ this.consume("RPAREN");
1720
+ const inlineChildren = [];
1721
+ const valueArgs = [];
1722
+ for (const a of parsedArgs) {
1723
+ if (a && typeof a === "object" && Array.isArray(a.__idmlChildren)) {
1724
+ inlineChildren.push(...a.__idmlChildren);
1725
+ } else {
1726
+ valueArgs.push(a);
1727
+ }
1728
+ }
1729
+ const args = valueArgs.length > 0 ? valueArgs : regEntry?.defaultArgs ?? [];
1730
+ if (this.peek()?.type !== "LBRACKET") {
1731
+ throw new Error(
1732
+ `[idml] "${rawName}" is missing its required [height,width,anchor] dimensions`
1733
+ );
1734
+ }
1735
+ this.consume("LBRACKET");
1736
+ const height = this.parseDimension();
1737
+ this.consume("COMMA");
1738
+ const width = this.parseDimension();
1739
+ this.consume("COMMA");
1740
+ const anchor = this.consume("IDENT").value;
1741
+ let hug;
1742
+ if (this.peek()?.type === "COMMA") {
1743
+ this.consume("COMMA");
1744
+ const kw = this.consume("IDENT").value;
1745
+ if (kw === "hug") hug = { w: true, h: true };
1746
+ else if (kw === "hug-w") hug = { w: true, h: false };
1747
+ else if (kw === "hug-h") hug = { w: false, h: true };
1748
+ else
1749
+ throw new Error(
1750
+ `[idml] unknown sizing keyword "${kw}" for "${rawName}"; expected hug, hug-w, or hug-h`
1751
+ );
1752
+ }
1753
+ this.consume("RBRACKET");
1754
+ let visibility;
1755
+ if (this.peek()?.type === "QUESTION") {
1756
+ this.consume("QUESTION");
1757
+ let negate = false;
1758
+ if (this.peek()?.type === "BANG") {
1759
+ this.consume("BANG");
1760
+ negate = true;
1761
+ }
1762
+ const ref = this.consume("VALUE_REF").value;
1763
+ visibility = { ref, negate };
1764
+ }
1765
+ let className = regEntry?.className;
1766
+ const condClasses = [];
1767
+ while (this.peek()?.type === "CLASS_BLOCK") {
1768
+ const cls = this.consume("CLASS_BLOCK").value;
1769
+ if (this.peek()?.type === "QUESTION") {
1770
+ this.consume("QUESTION");
1771
+ let negate = false;
1772
+ if (this.peek()?.type === "BANG") {
1773
+ this.consume("BANG");
1774
+ negate = true;
1775
+ }
1776
+ const ref = this.consume("VALUE_REF").value;
1777
+ condClasses.push({ classes: cls, ref, negate });
1778
+ continue;
1779
+ }
1780
+ for (const tok of cls.split(/\s+/).filter(Boolean)) {
1781
+ if (!tok.startsWith("@")) {
1782
+ throw new Error(
1783
+ `[idml] literal class "${tok}" is not allowed at a use site; declare a styled variant (Name:BaseType) instead`
1784
+ );
1785
+ }
1786
+ }
1787
+ className = className ? `${className} ${cls}` : cls;
1788
+ }
1789
+ const style = { ...baseStyle };
1790
+ const classRefs = [];
1791
+ if (className) {
1792
+ const statics = [];
1793
+ for (const tok of className.split(/\s+/).filter(Boolean)) {
1794
+ if (tok.startsWith("@")) classRefs.push(tok.slice(1));
1795
+ else statics.push(tok);
1796
+ }
1797
+ className = statics.length ? statics.join(" ") : void 0;
1798
+ }
1799
+ this.consume("LBRACE");
1800
+ const children = [...inlineChildren];
1801
+ while (this.peek()?.type !== "RBRACE") {
1802
+ children.push(this.parseItem());
1803
+ }
1804
+ this.consume("RBRACE");
1805
+ return { name, args, height, width, anchor, children, style, className, classRefs, condClasses, hug, visibility };
1806
+ }
1807
+ parseDimension() {
1808
+ if (this.peek()?.type === "IDENT" && this.peek()?.value === "auto") {
1809
+ throw new Error(
1810
+ "[idml] the `auto` dimension is no longer supported; give an explicit percentage (use a Spacer for any intentional empty space)"
1811
+ );
1812
+ }
1813
+ if (this.peek()?.type === "VALUE_REF") {
1814
+ const ref = this.consume("VALUE_REF").value;
1815
+ if (this.peek()?.type === "QUESTION") {
1816
+ this.consume("QUESTION");
1817
+ const whenTrue = this.parseDimLiteral();
1818
+ this.consume("COLON");
1819
+ const whenFalse = this.parseDimLiteral();
1820
+ return { ref, whenTrue, whenFalse };
1821
+ }
1822
+ return { ref };
1823
+ }
1824
+ return this.consume("NUMBER").value;
1825
+ }
1826
+ /** A literal dimension value inside a `@ref ? A : B`: a number (→ `%`) or a
1827
+ * number with a unit (`3.4vw`, `64px`). Returns the final CSS string. */
1828
+ parseDimLiteral() {
1829
+ const n = this.consume("NUMBER").value;
1830
+ const next = this.peek();
1831
+ if (next?.type === "IDENT" && ["vw", "vh", "px", "rem", "em"].includes(next.value)) {
1832
+ this.pos++;
1833
+ return `${n}${next.value}`;
1834
+ }
1835
+ return `${n}%`;
1836
+ }
1837
+ parseArgList() {
1838
+ const args = [];
1839
+ args.push(this.parseArg());
1840
+ while (this.peek()?.type === "COMMA") {
1841
+ this.pos++;
1842
+ if (this.peek()?.type === "RPAREN") break;
1843
+ args.push(this.parseArg());
1844
+ }
1845
+ return args;
1846
+ }
1847
+ parseArg() {
1848
+ const t = this.peek();
1849
+ if (!t) throw new Error("[idml] Expected argument");
1850
+ if (t.type === "STRING") {
1851
+ this.pos++;
1852
+ return t.value;
1853
+ }
1854
+ if (t.type === "NUMBER") {
1855
+ this.pos++;
1856
+ return t.value;
1857
+ }
1858
+ if (t.type === "VALUE_REF") {
1859
+ this.pos++;
1860
+ return `${VALUE_REF_PREFIX}${t.value}`;
1861
+ }
1862
+ if (t.type === "MODEL_REF") {
1863
+ this.pos++;
1864
+ return `${MODEL_REF_PREFIX}${t.value}`;
1865
+ }
1866
+ if (t.type === "IDENT") {
1867
+ this.pos++;
1868
+ if (t.value === "null") return null;
1869
+ if (t.value === "true") return true;
1870
+ if (t.value === "false") return false;
1871
+ return `${FN_REF_PREFIX}${t.value}`;
1872
+ }
1873
+ if (t.type === "LBRACE") {
1874
+ this.pos++;
1875
+ const childItems = [];
1876
+ while (this.peek()?.type !== "RBRACE") {
1877
+ childItems.push(this.parseItem());
1878
+ }
1879
+ this.consume("RBRACE");
1880
+ return { __idmlChildren: childItems };
1881
+ }
1882
+ throw new Error(`[idml] Unexpected token type ${t.type} as argument`);
1883
+ }
1884
+ };
1885
+ var LAYOUT_ITEMS = /* @__PURE__ */ new Set(["Row", "Col"]);
1886
+ var ANCHOR_V = { top: "flex-start", center: "center", bottom: "flex-end" };
1887
+ var ANCHOR_H = { left: "flex-start", center: "center", right: "flex-end" };
1888
+ function anchorToAbsoluteInsets(anchor) {
1889
+ const parts = anchor.split("-");
1890
+ const v = parts.length === 1 ? parts[0] : parts[0] ?? "top";
1891
+ const h = parts.length === 1 ? parts[0] : parts[1] ?? "left";
1892
+ const css = {};
1893
+ if (v === "bottom") css.bottom = "0";
1894
+ else if (v === "center") css.top = "50%";
1895
+ else css.top = "0";
1896
+ if (h === "right") css.right = "0";
1897
+ else if (h === "center") css.left = "50%";
1898
+ else css.left = "0";
1899
+ if (v === "center" || h === "center") {
1900
+ css.transform = `translate(${h === "center" ? "-50%" : "0"}, ${v === "center" ? "-50%" : "0"})`;
1901
+ }
1902
+ return css;
1903
+ }
1904
+ function anchorToFlexProps(anchor, direction) {
1905
+ const parts = anchor.split("-");
1906
+ if (parts.length === 1) {
1907
+ const val = ANCHOR_V[parts[0]] ?? ANCHOR_H[parts[0]] ?? "flex-start";
1908
+ return { justifyContent: val, alignItems: val };
1909
+ }
1910
+ const [v = "top", h = "left"] = parts;
1911
+ const vVal = ANCHOR_V[v] ?? "flex-start";
1912
+ const hVal = ANCHOR_H[h] ?? "flex-start";
1913
+ if (direction === "row") {
1914
+ return { justifyContent: hVal, alignItems: vVal };
1915
+ }
1916
+ return { justifyContent: vVal, alignItems: hVal };
1917
+ }
1918
+ function pct(n) {
1919
+ return `${n}%`;
1920
+ }
1921
+ var _idCounter = 0;
1922
+ function genId(prefix) {
1923
+ return `${prefix}-${++_idCounter}`;
1924
+ }
1925
+ function substituteParams(item, bindings) {
1926
+ if (bindings.size === 0) return item;
1927
+ const subArg = (a) => {
1928
+ if (typeof a === "string") {
1929
+ for (const prefix of [FN_REF_PREFIX, VALUE_REF_PREFIX, MODEL_REF_PREFIX]) {
1930
+ if (a.startsWith(prefix)) {
1931
+ const name = a.slice(prefix.length);
1932
+ return bindings.has(name) ? bindings.get(name) : a;
1933
+ }
1934
+ }
1935
+ return a;
1936
+ }
1937
+ if (a && typeof a === "object" && Array.isArray(a.__idmlChildren)) {
1938
+ return {
1939
+ __idmlChildren: a.__idmlChildren.map((c) => substituteParams(c, bindings))
1940
+ };
1941
+ }
1942
+ return a;
1943
+ };
1944
+ return {
1945
+ ...item,
1946
+ args: item.args.map(subArg),
1947
+ children: item.children.map((c) => substituteParams(c, bindings))
1948
+ };
1949
+ }
1950
+ function containerDirection(name, defs) {
1951
+ if (name === "Row") return "row";
1952
+ if (name === "Col" || name === "Form") return "column";
1953
+ if (defs.has(name)) return "column";
1954
+ return null;
1955
+ }
1956
+ var OUT_OF_FLOW = /* @__PURE__ */ new Set(["Overlay", "Modal"]);
1957
+ function defIsOutOfFlow(name, defs, seen = /* @__PURE__ */ new Set()) {
1958
+ const body = defs.get(name);
1959
+ if (!body || seen.has(name)) return false;
1960
+ seen.add(name);
1961
+ return body.every(
1962
+ (c) => OUT_OF_FLOW.has(c.name) || defs.has(c.name) && defIsOutOfFlow(c.name, defs, seen)
1963
+ );
1964
+ }
1965
+ function makeOutOfFlowPredicate(defs) {
1966
+ return (name) => OUT_OF_FLOW.has(name) || defs.has(name) && defIsOutOfFlow(name, defs);
1967
+ }
1968
+ function validateTiling(children, direction, where, isOutOfFlow, containerHug) {
1969
+ children = children.filter((c) => !isOutOfFlow(c.name));
1970
+ if (children.length === 0) return;
1971
+ const main = direction === "row" ? "width" : "height";
1972
+ const cross = direction === "row" ? "height" : "width";
1973
+ const mainKey = direction === "row" ? "w" : "h";
1974
+ const crossKey = direction === "row" ? "h" : "w";
1975
+ const packsMain = !!containerHug?.[mainKey] || children.some((c) => c.hug?.[mainKey] || isDimRef(c[main]));
1976
+ let sum = 0;
1977
+ for (const c of children) {
1978
+ if (typeof c[main] === "number") sum += c[main];
1979
+ if (!c.hug?.[crossKey] && !isDimRef(c[cross]) && c[cross] !== 100) {
1980
+ throw new Error(
1981
+ `[idml] ${c.name} in ${where}: cross-axis ${cross} must be 100 (got ${c[cross]}); no vacant space is allowed`
1982
+ );
1983
+ }
1984
+ }
1985
+ if (!packsMain && sum !== 100) {
1986
+ throw new Error(
1987
+ `[idml] children of ${where} must tile to 100% along ${main}; got ${sum}. Add an explicit Spacer for any gap.`
1988
+ );
1989
+ }
1990
+ }
1991
+ function walkTiling(item, defs, isOutOfFlow) {
1992
+ const dir = containerDirection(item.name, defs);
1993
+ if (dir) validateTiling(item.children, dir, `<${item.name}>`, isOutOfFlow, item.hug);
1994
+ for (const child of item.children) walkTiling(child, defs, isOutOfFlow);
1995
+ }
1996
+ function sizeOf(item) {
1997
+ const size = {};
1998
+ if (typeof item.height === "number") size.height = pct(item.height);
1999
+ if (typeof item.width === "number") size.width = pct(item.width);
2000
+ return size;
2001
+ }
2002
+ function dynSizeOf(item) {
2003
+ const dyn = {};
2004
+ if (isDimRef(item.height)) dyn.height = dimRefToDynamic(item.height);
2005
+ if (isDimRef(item.width)) dyn.width = dimRefToDynamic(item.width);
2006
+ return dyn.width || dyn.height ? dyn : void 0;
2007
+ }
2008
+ function dimRefToDynamic(d) {
2009
+ return d.whenTrue !== void 0 ? { ref: d.ref, whenTrue: d.whenTrue, whenFalse: d.whenFalse } : { ref: d.ref };
2010
+ }
2011
+ function hugStyles(hug) {
2012
+ const s = {};
2013
+ if (hug.w) {
2014
+ s.width = "fit-content";
2015
+ s.maxWidth = "100%";
2016
+ s.minWidth = "0";
2017
+ s.overflow = "hidden";
2018
+ s.textOverflow = "ellipsis";
2019
+ s.whiteSpace = "nowrap";
2020
+ }
2021
+ if (hug.h) {
2022
+ s.height = "fit-content";
2023
+ s.maxHeight = "100%";
2024
+ }
2025
+ return s;
2026
+ }
2027
+ function hugContainerStyles(hug) {
2028
+ const s = {};
2029
+ if (hug.w) s.width = "fit-content";
2030
+ if (hug.h) s.height = "fit-content";
2031
+ return s;
2032
+ }
2033
+ var HUG_INVALID_ON = /* @__PURE__ */ new Set(["Overlay", "Modal", "Children"]);
2034
+ function assertHuggable(item, isDef) {
2035
+ if (!item.hug) return;
2036
+ if (HUG_INVALID_ON.has(item.name) || isDef) {
2037
+ throw new Error(
2038
+ `[idml] "${item.name}" cannot use hug \u2014 nothing to content-size here. hug applies to components (e.g. Button/Text) and layout containers (Row/Col/Form), not definitions, slots, tables, or out-of-flow layers.`
2039
+ );
2040
+ }
2041
+ }
2042
+ function mkItem(name, args, height, width, anchor, children, style = {}) {
2043
+ return { name, args, height, width, anchor, children, style };
2044
+ }
2045
+ function expandTable(item, ctx) {
2046
+ const dataArg = item.args.find(
2047
+ (a) => typeof a === "string" && a.startsWith(VALUE_REF_PREFIX)
2048
+ );
2049
+ const columns = item.children.filter((c) => c.name === "Column");
2050
+ const headerCells = columns.map((col) => {
2051
+ const label = mkItem(
2052
+ "Text",
2053
+ [String(col.args[0] ?? "")],
2054
+ "auto",
2055
+ 100,
2056
+ col.anchor,
2057
+ [],
2058
+ { fontSize: "0.63vw" }
2059
+ );
2060
+ label.className = "font-medium text-gray-500 uppercase tracking-wider";
2061
+ const cell = mkItem("Col", [], "auto", col.width, col.anchor, [label], {
2062
+ paddingLeft: "1.6vw",
2063
+ paddingRight: "1.6vw",
2064
+ paddingTop: "0.8vw",
2065
+ paddingBottom: "0.8vw"
2066
+ });
2067
+ return cell;
2068
+ });
2069
+ const headerRow = mkItem("Row", [], "auto", 100, "top-left", headerCells, {
2070
+ borderBottom: "0.07vw solid #e5e7eb"
2071
+ });
2072
+ headerRow.className = "bg-gray-50";
2073
+ const bodyCells = columns.map((col) => {
2074
+ const cell = mkItem("Col", [], "auto", col.width, col.anchor, col.children, {
2075
+ paddingLeft: "1.6vw",
2076
+ paddingRight: "1.6vw",
2077
+ paddingTop: "1vw",
2078
+ paddingBottom: "1vw"
2079
+ });
2080
+ cell.className = "whitespace-nowrap";
2081
+ return cell;
2082
+ });
2083
+ const bodyRowTemplate = mkItem("Row", [], "auto", 100, "top-left", bodyCells, {
2084
+ borderBottom: "0.07vw solid #e5e7eb"
2085
+ });
2086
+ const repeat = mkItem("Repeat", dataArg ? [dataArg] : [], "auto", 100, "top-left", [bodyRowTemplate]);
2087
+ const tableStyle = { ...item.style };
2088
+ if (item.hug?.w) tableStyle.width = "fit-content";
2089
+ const tableCol = mkItem(
2090
+ "Col",
2091
+ [],
2092
+ item.hug?.h ? "auto" : item.height,
2093
+ item.hug?.w ? "auto" : item.width,
2094
+ item.anchor,
2095
+ [headerRow, repeat],
2096
+ tableStyle
2097
+ );
2098
+ tableCol.className = item.className;
2099
+ return convertItem(tableCol, ctx);
2100
+ }
2101
+ function convertItem(item, ctx) {
2102
+ const def = convertNode(item, ctx);
2103
+ if (item.visibility) def.visibility = item.visibility;
2104
+ const dyn = dynSizeOf(item);
2105
+ if (dyn) def.dynamicSize = dyn;
2106
+ if (item.classRefs?.length && !def.componentId) def.classRefs = item.classRefs;
2107
+ if (item.condClasses?.length) def.condClasses = item.condClasses;
2108
+ return def;
2109
+ }
2110
+ function convertNode(item, ctx) {
2111
+ assertHuggable(item, ctx.defs.has(item.name));
2112
+ const size = sizeOf(item);
2113
+ const idmlStyle = Object.keys(item.style).length ? item.style : void 0;
2114
+ const colAnchor = anchorToFlexProps(item.anchor, "column");
2115
+ const outOfFlow = ctx.isOutOfFlow(item.name);
2116
+ const cellStyle = outOfFlow ? { ...idmlStyle ?? {}, display: "contents" } : idmlStyle;
2117
+ if (item.name === "Children") {
2118
+ const slot = ctx.slotChildren ?? [];
2119
+ const childCtx = { ...ctx, slotChildren: void 0 };
2120
+ return {
2121
+ type: "flex",
2122
+ direction: "column",
2123
+ ...colAnchor,
2124
+ size,
2125
+ children: slot.map((child) => convertItem(child, childCtx)),
2126
+ idmlStyle
2127
+ };
2128
+ }
2129
+ const defBody = ctx.defs.get(item.name);
2130
+ if (defBody && !ctx.expanding.has(item.name)) {
2131
+ const params = ctx.defParams.get(item.name) ?? [];
2132
+ const bindings = /* @__PURE__ */ new Map();
2133
+ params.forEach((p, i) => bindings.set(p, item.args[i] ?? ""));
2134
+ const body = bindings.size ? defBody.map((t) => substituteParams(t, bindings)) : defBody;
2135
+ const innerCtx = {
2136
+ ...ctx,
2137
+ slotChildren: item.children,
2138
+ expanding: new Set(ctx.expanding).add(item.name)
2139
+ };
2140
+ return {
2141
+ type: "flex",
2142
+ direction: "column",
2143
+ ...colAnchor,
2144
+ size,
2145
+ children: body.map((t) => convertItem(t, innerCtx)),
2146
+ idmlStyle: cellStyle
2147
+ };
2148
+ }
2149
+ if (item.name === "Overlay") {
2150
+ const children2 = item.children.map((child) => {
2151
+ const layout = convertItem(child, ctx);
2152
+ const hugStyle = {};
2153
+ if (child.hug?.w) {
2154
+ hugStyle.width = "fit-content";
2155
+ if (layout.size?.width) hugStyle.maxWidth = layout.size.width;
2156
+ }
2157
+ if (child.hug?.h) {
2158
+ hugStyle.height = "fit-content";
2159
+ if (layout.size?.height) hugStyle.maxHeight = layout.size.height;
2160
+ }
2161
+ return {
2162
+ ...layout,
2163
+ idmlStyle: {
2164
+ ...layout.idmlStyle ?? {},
2165
+ position: "absolute",
2166
+ pointerEvents: "auto",
2167
+ ...anchorToAbsoluteInsets(child.anchor),
2168
+ ...hugStyle
2169
+ }
2170
+ };
2171
+ });
2172
+ return {
2173
+ type: "flex",
2174
+ direction: "column",
2175
+ size: { width: "100%", height: "100%" },
2176
+ children: children2,
2177
+ idmlStyle: {
2178
+ position: "fixed",
2179
+ top: "0",
2180
+ left: "0",
2181
+ right: "0",
2182
+ bottom: "0",
2183
+ pointerEvents: "none",
2184
+ outline: "none",
2185
+ zIndex: "50",
2186
+ ...idmlStyle ?? {}
2187
+ },
2188
+ ...item.className ? { className: item.className } : {}
2189
+ };
2190
+ }
2191
+ if (item.name === "Table") {
2192
+ return expandTable(item, ctx);
2193
+ }
2194
+ if (LAYOUT_ITEMS.has(item.name)) {
2195
+ const direction = item.name === "Row" ? "row" : "column";
2196
+ const { justifyContent, alignItems } = anchorToFlexProps(item.anchor, direction);
2197
+ const children2 = item.children.map((child) => convertItem(child, ctx));
2198
+ const mainHug = direction === "column" ? item.hug?.h : item.hug?.w;
2199
+ if (mainHug) {
2200
+ for (const ch of children2) {
2201
+ if (!ch.size) continue;
2202
+ if (direction === "column") delete ch.size.height;
2203
+ else delete ch.size.width;
2204
+ }
2205
+ }
2206
+ const crossHug = direction === "column" ? item.hug?.w : item.hug?.h;
2207
+ const containerStyle = crossHug ? { ...idmlStyle ?? {}, ...hugContainerStyles({ w: direction === "column", h: direction === "row" }) } : idmlStyle;
2208
+ return {
2209
+ type: "flex",
2210
+ direction,
2211
+ justifyContent,
2212
+ alignItems,
2213
+ size,
2214
+ children: children2,
2215
+ idmlStyle: containerStyle,
2216
+ ...item.className ? { className: item.className } : {}
2217
+ };
2218
+ }
2219
+ if (item.name === "Select" && item.children.some((c) => c.name === "Option")) {
2220
+ const id2 = genId("select");
2221
+ const options = item.children.filter((c) => c.name === "Option").map((c) => ({ value: c.args[0] ?? "", label: c.args[1] ?? c.args[0] ?? "" }));
2222
+ const def = buildComponentDef(item, id2);
2223
+ def.props = { ...def.props, options };
2224
+ ctx.components.push(def);
2225
+ return { type: "flex", direction: "column", ...colAnchor, size, children: [], componentId: id2 };
2226
+ }
2227
+ const id = genId(item.name.toLowerCase());
2228
+ ctx.components.push(buildComponentDef(item, id));
2229
+ const children = item.children.map((child) => convertItem(child, ctx));
2230
+ const hugCell = {};
2231
+ if (item.hug?.w) {
2232
+ hugCell.width = "fit-content";
2233
+ if (typeof item.width === "number") hugCell.maxWidth = `${item.width}%`;
2234
+ }
2235
+ if (item.hug?.h) {
2236
+ hugCell.height = "fit-content";
2237
+ if (typeof item.height === "number") hugCell.maxHeight = `${item.height}%`;
2238
+ }
2239
+ const cellIdml = outOfFlow ? { ...cellStyle ?? {}, ...hugCell } : Object.keys(hugCell).length ? hugCell : void 0;
2240
+ return {
2241
+ type: "flex",
2242
+ direction: "column",
2243
+ ...colAnchor,
2244
+ size,
2245
+ children,
2246
+ componentId: id,
2247
+ ...cellIdml ? { idmlStyle: cellIdml } : {}
2248
+ };
2249
+ }
2250
+ function anchorToComponentStyle(anchor, componentType) {
2251
+ const parts = anchor.split("-");
2252
+ const h = parts.length === 1 ? parts[0] : parts[1] ?? parts[0];
2253
+ const v = parts.length === 1 ? parts[0] : parts[0];
2254
+ const css = {};
2255
+ if (componentType === "Text" || componentType === "Heading") {
2256
+ if (h === "center") css.textAlign = "center";
2257
+ else if (h === "right") css.textAlign = "right";
2258
+ }
2259
+ if (componentType === "Button") {
2260
+ css.display = "flex";
2261
+ css.justifyContent = h === "center" ? "center" : h === "right" ? "flex-end" : "flex-start";
2262
+ css.alignItems = v === "center" ? "center" : v === "bottom" ? "flex-end" : "flex-start";
2263
+ }
2264
+ return css;
2265
+ }
2266
+ function buildComponentDef(item, id) {
2267
+ const anchorStyle = anchorToComponentStyle(item.anchor, item.name);
2268
+ const hug = item.hug ? hugStyles(item.hug) : {};
2269
+ const merged = { ...anchorStyle, ...item.style, ...hug };
2270
+ const idmlStyle = Object.keys(merged).length ? merged : void 0;
2271
+ const valueRefs = [];
2272
+ const modelRefs = [];
2273
+ const handlerRefs = [];
2274
+ const literals = [];
2275
+ for (const a of item.args) {
2276
+ if (typeof a === "string" && a.startsWith(VALUE_REF_PREFIX)) valueRefs.push(a.slice(VALUE_REF_PREFIX.length));
2277
+ else if (typeof a === "string" && a.startsWith(MODEL_REF_PREFIX)) modelRefs.push(a.slice(MODEL_REF_PREFIX.length));
2278
+ else if (typeof a === "string" && a.startsWith(FN_REF_PREFIX)) handlerRefs.push(a.slice(FN_REF_PREFIX.length));
2279
+ else literals.push(a);
2280
+ }
2281
+ const primaryProp = PRIMARY_PROP[item.name] ?? "value";
2282
+ const bindings = [
2283
+ ...valueRefs.map((methodId) => ({ prop: primaryProp, methodId, kind: "value" })),
2284
+ ...modelRefs.map((methodId) => ({ prop: primaryProp, methodId, kind: "model" })),
2285
+ ...handlerRefs.map((methodId) => ({ prop: "onClick", methodId })),
2286
+ // Dynamic classes (`@method` tokens in a class block) resolve to strings that
2287
+ // are appended to className per render.
2288
+ ...(item.classRefs ?? []).map((methodId) => ({ prop: "className", methodId, kind: "value" }))
2289
+ ];
2290
+ const withBindings = (def) => {
2291
+ const withB = bindings.length ? { ...def, bindings } : def;
2292
+ return item.className ? { ...withB, className: item.className } : withB;
2293
+ };
2294
+ const [first, second] = literals;
2295
+ switch (item.name) {
2296
+ case "Text":
2297
+ return withBindings({ id, type: "Text", props: { text: String(first ?? "") }, idmlStyle });
2298
+ case "Heading":
2299
+ return withBindings({
2300
+ id,
2301
+ type: "Heading",
2302
+ props: { text: String(first ?? ""), level: typeof second === "number" ? second : 1 },
2303
+ idmlStyle
2304
+ });
2305
+ case "Button": {
2306
+ const route = literals.find((a) => typeof a === "string" && a.startsWith("/"));
2307
+ const label = literals.find((a) => typeof a === "string" && !a.startsWith("/"));
2308
+ const props = { text: String(label ?? "") };
2309
+ if (route) props.href = route;
2310
+ return withBindings({ id, type: "Button", props, idmlStyle });
2311
+ }
2312
+ case "Image":
2313
+ return withBindings({ id, type: "Image", props: { src: String(first ?? ""), alt: String(second ?? "") }, idmlStyle });
2314
+ case "Label":
2315
+ return withBindings({ id, type: "Label", props: { text: String(first ?? "") }, idmlStyle });
2316
+ case "Icon": {
2317
+ const props = { name: String(first ?? "") };
2318
+ for (const rest of literals.slice(1)) {
2319
+ if (typeof rest === "number") props.size = rest;
2320
+ else if (typeof rest === "string") props.color = rest;
2321
+ }
2322
+ return withBindings({ id, type: "Icon", props, idmlStyle });
2323
+ }
2324
+ case "Option": {
2325
+ const value = first ?? "";
2326
+ const label = second ?? first ?? "";
2327
+ return withBindings({ id, type: "Option", props: { value, label }, idmlStyle });
2328
+ }
2329
+ case "Input":
2330
+ case "Textarea":
2331
+ return withBindings({
2332
+ id,
2333
+ type: item.name,
2334
+ props: first != null ? { placeholder: String(first) } : {},
2335
+ idmlStyle
2336
+ });
2337
+ default:
2338
+ return withBindings({
2339
+ id,
2340
+ type: item.name,
2341
+ props: Object.fromEntries(literals.map((v, i) => [`arg${i}`, v])),
2342
+ idmlStyle
2343
+ });
2344
+ }
2345
+ }
2346
+ var PRIMARY_PROP = {
2347
+ Text: "text",
2348
+ Heading: "text",
2349
+ Button: "text",
2350
+ Image: "src",
2351
+ Input: "value",
2352
+ Textarea: "value",
2353
+ Select: "value",
2354
+ Checkbox: "checked",
2355
+ Table: "data",
2356
+ Repeat: "data",
2357
+ Modal: "open",
2358
+ Icon: "name"
2359
+ };
2360
+ var DEFAULT_TOKENS = {
2361
+ colors: [
2362
+ { name: "primary", value: "#1a56db", darkValue: "#60a5fa" },
2363
+ { name: "surface", value: "#ffffff", darkValue: "#1e1e2e" },
2364
+ { name: "on-surface", value: "#111827", darkValue: "#f9fafb" },
2365
+ { name: "danger", value: "#dc2626", darkValue: "#f87171" }
2366
+ ],
2367
+ typography: [
2368
+ { name: "heading-xl", fontSize: "2.25rem", fontWeight: 700, lineHeight: "1.25" },
2369
+ { name: "body-md", fontSize: "1rem", fontWeight: 400, lineHeight: "1.6" },
2370
+ { name: "label-sm", fontSize: "0.75rem", fontWeight: 500, lineHeight: "1.4" }
2371
+ ],
2372
+ spacing: [
2373
+ { name: "gap-sm", value: "0.5rem" },
2374
+ { name: "gap-md", value: "1rem" },
2375
+ { name: "gap-lg", value: "2rem" }
2376
+ ]
2377
+ };
2378
+ function parseIdml(source, options) {
2379
+ _idCounter = 0;
2380
+ const parser = new IdmlParser(tokenize(source));
2381
+ const parsedPages = parser.parseFile(options?.resolve);
2382
+ const isOutOfFlow = makeOutOfFlowPredicate(parser.defRegistry);
2383
+ for (const { route, items } of parsedPages) {
2384
+ validateTiling(items, "column", `page ${route}`, isOutOfFlow);
2385
+ items.forEach((it) => walkTiling(it, parser.defRegistry, isOutOfFlow));
2386
+ }
2387
+ for (const [name, body] of parser.defRegistry) {
2388
+ validateTiling(body, "column", `define ${name}`, isOutOfFlow);
2389
+ body.forEach((it) => walkTiling(it, parser.defRegistry, isOutOfFlow));
2390
+ }
2391
+ const pages = parsedPages.map(({ route, scroll, items }) => {
2392
+ const components = [];
2393
+ const ctx = {
2394
+ components,
2395
+ defs: parser.defRegistry,
2396
+ defParams: parser.defParamRegistry,
2397
+ expanding: /* @__PURE__ */ new Set(),
2398
+ isOutOfFlow
2399
+ };
2400
+ const layoutChildren = items.map((item) => convertItem(item, ctx));
2401
+ const rootLayout = {
2402
+ type: "flex",
2403
+ direction: "column",
2404
+ size: { width: "100%", height: "100%" },
2405
+ children: layoutChildren,
2406
+ ...scroll ? { idmlStyle: { overflowY: "auto" } } : {}
2407
+ };
2408
+ return { route, layout: rootLayout, components };
2409
+ });
2410
+ return { version: "1", tokens: DEFAULT_TOKENS, pages };
2411
+ }
2412
+ // Annotate the CommonJS export names for ESM import in node:
2413
+ 0 && (module.exports = {
2414
+ BUILTIN_COMPONENTS,
2415
+ ComponentRenderer,
2416
+ ConfigProvider,
2417
+ ConfigRenderer,
2418
+ EditorPage,
2419
+ LayoutRenderer,
2420
+ UIConfigSchema,
2421
+ clearComponentRegistry,
2422
+ clearRegistry,
2423
+ getComponent,
2424
+ getMethod,
2425
+ parseIdml,
2426
+ registerComponent,
2427
+ registerMethod,
2428
+ safeValidateConfig,
2429
+ useConfigContext,
2430
+ useRegisteredMethod,
2431
+ useVisibility,
2432
+ validateConfig
2433
+ });
2434
+ //# sourceMappingURL=index.cjs.map