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/README.md +102 -0
- package/dist/cli.cjs +292 -0
- package/dist/index.cjs +2434 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +599 -0
- package/dist/index.d.ts +599 -0
- package/dist/index.js +2380 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +1236 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +184 -0
- package/dist/server.d.ts +184 -0
- package/dist/server.js +1195 -0
- package/dist/server.js.map +1 -0
- package/package.json +78 -0
- package/ui.config.schema.json +495 -0
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,1236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/server.ts
|
|
31
|
+
var server_exports = {};
|
|
32
|
+
__export(server_exports, {
|
|
33
|
+
addSSEWriter: () => addSSEWriter,
|
|
34
|
+
parseIdml: () => parseIdml,
|
|
35
|
+
startWatcher: () => startWatcher,
|
|
36
|
+
stopWatcher: () => stopWatcher,
|
|
37
|
+
withUIConfig: () => withUIConfig
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(server_exports);
|
|
40
|
+
|
|
41
|
+
// src/next-plugin/index.ts
|
|
42
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
43
|
+
function withUIConfig(options = {}) {
|
|
44
|
+
const {
|
|
45
|
+
configPath = "./ui.config.json",
|
|
46
|
+
editorEnabled = process.env.NODE_ENV === "development"
|
|
47
|
+
} = options;
|
|
48
|
+
const resolvedConfigPath = import_node_path.default.resolve(process.cwd(), configPath);
|
|
49
|
+
return function(nextConfig) {
|
|
50
|
+
return {
|
|
51
|
+
...nextConfig,
|
|
52
|
+
env: {
|
|
53
|
+
...nextConfig.env,
|
|
54
|
+
ISD_UI_CONFIG_PATH: resolvedConfigPath,
|
|
55
|
+
ISD_UI_EDITOR_ENABLED: String(editorEnabled)
|
|
56
|
+
},
|
|
57
|
+
async rewrites() {
|
|
58
|
+
const existing = await nextConfig.rewrites?.();
|
|
59
|
+
const isdRewrites = editorEnabled ? [
|
|
60
|
+
{
|
|
61
|
+
source: "/_isd-editor",
|
|
62
|
+
destination: "/isd/editor"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
source: "/_isd-editor/:path*",
|
|
66
|
+
destination: "/isd/editor/:path*"
|
|
67
|
+
}
|
|
68
|
+
] : [];
|
|
69
|
+
if (Array.isArray(existing)) {
|
|
70
|
+
return [...existing, ...isdRewrites];
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
beforeFiles: [...existing?.beforeFiles ?? []],
|
|
74
|
+
afterFiles: [...existing?.afterFiles ?? [], ...isdRewrites],
|
|
75
|
+
fallback: [...existing?.fallback ?? []]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/next-plugin/watcher.ts
|
|
83
|
+
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
84
|
+
var writers = /* @__PURE__ */ new Set();
|
|
85
|
+
var watcher = null;
|
|
86
|
+
function startWatcher(configPath) {
|
|
87
|
+
if (watcher) return;
|
|
88
|
+
watcher = import_chokidar.default.watch(configPath, {
|
|
89
|
+
persistent: true,
|
|
90
|
+
ignoreInitial: true,
|
|
91
|
+
awaitWriteFinish: { stabilityThreshold: 150, pollInterval: 50 }
|
|
92
|
+
});
|
|
93
|
+
watcher.on("change", () => {
|
|
94
|
+
broadcastConfigChange();
|
|
95
|
+
});
|
|
96
|
+
watcher.on("error", (err) => {
|
|
97
|
+
console.error("[idml] Watcher error:", err);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async function stopWatcher() {
|
|
101
|
+
if (!watcher) return;
|
|
102
|
+
await watcher.close();
|
|
103
|
+
watcher = null;
|
|
104
|
+
}
|
|
105
|
+
function addSSEWriter(writer) {
|
|
106
|
+
writers.add(writer);
|
|
107
|
+
return () => writers.delete(writer);
|
|
108
|
+
}
|
|
109
|
+
function broadcastConfigChange() {
|
|
110
|
+
const encoder = new TextEncoder();
|
|
111
|
+
const message = encoder.encode(`data: ${JSON.stringify({ type: "config:change", ts: Date.now() })}
|
|
112
|
+
|
|
113
|
+
`);
|
|
114
|
+
for (const writer of writers) {
|
|
115
|
+
writer.write(message).catch(() => {
|
|
116
|
+
writers.delete(writer);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/parser/idml-parser.ts
|
|
122
|
+
var isDimRef = (d) => typeof d === "object" && d !== null && "ref" in d;
|
|
123
|
+
var SINGLE_CHAR_TOKENS = {
|
|
124
|
+
"(": "LPAREN",
|
|
125
|
+
")": "RPAREN",
|
|
126
|
+
"[": "LBRACKET",
|
|
127
|
+
"]": "RBRACKET",
|
|
128
|
+
"{": "LBRACE",
|
|
129
|
+
"}": "RBRACE",
|
|
130
|
+
",": "COMMA",
|
|
131
|
+
":": "COLON",
|
|
132
|
+
"?": "QUESTION",
|
|
133
|
+
"!": "BANG"
|
|
134
|
+
};
|
|
135
|
+
var MAX_LINE_WIDTH = 80;
|
|
136
|
+
function validateSource(source) {
|
|
137
|
+
const lines = source.split("\n");
|
|
138
|
+
let codeStarted = false;
|
|
139
|
+
lines.forEach((line, idx) => {
|
|
140
|
+
const lineNo = idx + 1;
|
|
141
|
+
if (line.length > MAX_LINE_WIDTH) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`[idml] line ${lineNo} is ${line.length} columns; the limit is ${MAX_LINE_WIDTH}`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const trimmed = line.trim();
|
|
147
|
+
if (trimmed === "") return;
|
|
148
|
+
if (trimmed.startsWith("#")) {
|
|
149
|
+
if (codeStarted) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`[idml] line ${lineNo}: comments are only allowed in the header block at the very top of the file, before any code`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
codeStarted = true;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function tokenize(source) {
|
|
160
|
+
validateSource(source);
|
|
161
|
+
const stripped = source.split("\n").map((line) => line.trimStart().startsWith("#") ? "" : line).join("\n");
|
|
162
|
+
const tokens = [];
|
|
163
|
+
let i = 0;
|
|
164
|
+
while (i < stripped.length) {
|
|
165
|
+
if (/\s/.test(stripped[i])) {
|
|
166
|
+
i++;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (stripped[i] === "." && stripped[i + 1] === "/") {
|
|
170
|
+
let j = i + 2;
|
|
171
|
+
while (j < stripped.length && /[\w/-]/.test(stripped[j])) j++;
|
|
172
|
+
tokens.push({ type: "ROUTE", value: "/" + stripped.slice(i + 2, j) });
|
|
173
|
+
i = j;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (stripped[i] === "#" && /[0-9a-fA-F]/.test(stripped[i + 1] ?? "")) {
|
|
177
|
+
let j = i + 1;
|
|
178
|
+
while (j < stripped.length && /[0-9a-fA-F]/.test(stripped[j])) j++;
|
|
179
|
+
tokens.push({ type: "COLOR", value: stripped.slice(i, j) });
|
|
180
|
+
i = j;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (stripped[i] === "@" && /[a-zA-Z_]/.test(stripped[i + 1] ?? "")) {
|
|
184
|
+
let j = i + 1;
|
|
185
|
+
while (j < stripped.length && /[\w.-]/.test(stripped[j])) j++;
|
|
186
|
+
tokens.push({ type: "VALUE_REF", value: stripped.slice(i + 1, j) });
|
|
187
|
+
i = j;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (stripped[i] === "~" && /[a-zA-Z_]/.test(stripped[i + 1] ?? "")) {
|
|
191
|
+
let j = i + 1;
|
|
192
|
+
while (j < stripped.length && /[\w-]/.test(stripped[j])) j++;
|
|
193
|
+
tokens.push({ type: "MODEL_REF", value: stripped.slice(i + 1, j) });
|
|
194
|
+
i = j;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (stripped[i] === "<") {
|
|
198
|
+
throw new Error(
|
|
199
|
+
"[idml] inline `<...>` style blocks are no longer supported; declare a styled variant (Name:BaseType) and apply it instead"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (stripped[i] === "`") {
|
|
203
|
+
let j = i + 1;
|
|
204
|
+
while (j < stripped.length && stripped[j] !== "`") j++;
|
|
205
|
+
tokens.push({ type: "CLASS_BLOCK", value: stripped.slice(i + 1, j).trim() });
|
|
206
|
+
i = j + 1;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (stripped[i] in SINGLE_CHAR_TOKENS) {
|
|
210
|
+
tokens.push({ type: SINGLE_CHAR_TOKENS[stripped[i]] });
|
|
211
|
+
i++;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (stripped[i] === '"') {
|
|
215
|
+
let j = i + 1;
|
|
216
|
+
while (j < stripped.length && stripped[j] !== '"') {
|
|
217
|
+
if (stripped[j] === "\\") j++;
|
|
218
|
+
j++;
|
|
219
|
+
}
|
|
220
|
+
tokens.push({ type: "STRING", value: stripped.slice(i + 1, j) });
|
|
221
|
+
i = j + 1;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (/\d/.test(stripped[i])) {
|
|
225
|
+
let j = i;
|
|
226
|
+
while (j < stripped.length && /[\d.]/.test(stripped[j])) j++;
|
|
227
|
+
tokens.push({ type: "NUMBER", value: parseFloat(stripped.slice(i, j)) });
|
|
228
|
+
i = j;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (/[a-zA-Z_]/.test(stripped[i])) {
|
|
232
|
+
let j = i;
|
|
233
|
+
while (j < stripped.length && /[\w-]/.test(stripped[j])) j++;
|
|
234
|
+
tokens.push({ type: "IDENT", value: stripped.slice(i, j) });
|
|
235
|
+
i = j;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
throw new Error(`[idml] Unexpected character '${stripped[i]}' at position ${i}`);
|
|
239
|
+
}
|
|
240
|
+
return tokens;
|
|
241
|
+
}
|
|
242
|
+
function applyStyleProp(key, val, result) {
|
|
243
|
+
switch (key) {
|
|
244
|
+
case "bg":
|
|
245
|
+
result.backgroundColor = val;
|
|
246
|
+
break;
|
|
247
|
+
case "fg":
|
|
248
|
+
result.color = val;
|
|
249
|
+
break;
|
|
250
|
+
case "size":
|
|
251
|
+
result.fontSize = val.endsWith("vw") ? val : `${val}vw`;
|
|
252
|
+
break;
|
|
253
|
+
case "font":
|
|
254
|
+
result.fontFamily = val;
|
|
255
|
+
break;
|
|
256
|
+
case "weight":
|
|
257
|
+
result.fontWeight = val;
|
|
258
|
+
break;
|
|
259
|
+
case "style":
|
|
260
|
+
if (val === "bold") result.fontWeight = "700";
|
|
261
|
+
else if (val === "italic") result.fontStyle = "italic";
|
|
262
|
+
break;
|
|
263
|
+
case "pad":
|
|
264
|
+
result.padding = val.endsWith("%") ? val : `${val}%`;
|
|
265
|
+
break;
|
|
266
|
+
case "radius":
|
|
267
|
+
result.borderRadius = val.endsWith("px") ? val : `${val}px`;
|
|
268
|
+
break;
|
|
269
|
+
case "gap":
|
|
270
|
+
result.gap = val.endsWith("vw") ? val : `${val}vw`;
|
|
271
|
+
break;
|
|
272
|
+
case "align":
|
|
273
|
+
result.textAlign = val;
|
|
274
|
+
break;
|
|
275
|
+
case "overflow":
|
|
276
|
+
result.overflowY = val;
|
|
277
|
+
break;
|
|
278
|
+
case "h":
|
|
279
|
+
result.height = val;
|
|
280
|
+
break;
|
|
281
|
+
case "w":
|
|
282
|
+
result.width = val;
|
|
283
|
+
break;
|
|
284
|
+
default:
|
|
285
|
+
result[key] = val;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
var FN_REF_PREFIX = "\0fn:";
|
|
289
|
+
var VALUE_REF_PREFIX = "\0val:";
|
|
290
|
+
var MODEL_REF_PREFIX = "\0model:";
|
|
291
|
+
var BUILTIN_NAMES = /* @__PURE__ */ new Set([
|
|
292
|
+
"Text",
|
|
293
|
+
"Heading",
|
|
294
|
+
"Button",
|
|
295
|
+
"Image",
|
|
296
|
+
"List",
|
|
297
|
+
"Card",
|
|
298
|
+
"Divider",
|
|
299
|
+
"Spacer",
|
|
300
|
+
"Icon",
|
|
301
|
+
"Table",
|
|
302
|
+
"Children",
|
|
303
|
+
"Row",
|
|
304
|
+
"Col",
|
|
305
|
+
"Repeat",
|
|
306
|
+
"Form",
|
|
307
|
+
"Modal",
|
|
308
|
+
"Column",
|
|
309
|
+
"Overlay",
|
|
310
|
+
"Input",
|
|
311
|
+
"Textarea",
|
|
312
|
+
"Select",
|
|
313
|
+
"Option",
|
|
314
|
+
"Checkbox",
|
|
315
|
+
"Radio",
|
|
316
|
+
"Label"
|
|
317
|
+
]);
|
|
318
|
+
var IdmlParser = class _IdmlParser {
|
|
319
|
+
constructor(tokens) {
|
|
320
|
+
this.pos = 0;
|
|
321
|
+
this.styleRegistry = /* @__PURE__ */ new Map();
|
|
322
|
+
// Reusable component definitions: name -> body template (item list). A `define`
|
|
323
|
+
// block registers one; using the name as an item expands the body (macro-style),
|
|
324
|
+
// substituting the call's children at the `Children` marker. See convertItem.
|
|
325
|
+
this.defRegistry = /* @__PURE__ */ new Map();
|
|
326
|
+
// Parameter names per definition (e.g. `define TopBar(title)` -> ['title']).
|
|
327
|
+
// At expansion the call's positional args are bound to these names and any
|
|
328
|
+
// matching references inside the body are substituted. See convertItem.
|
|
329
|
+
this.defParamRegistry = /* @__PURE__ */ new Map();
|
|
330
|
+
this.tokens = tokens;
|
|
331
|
+
}
|
|
332
|
+
peek(offset = 0) {
|
|
333
|
+
return this.tokens[this.pos + offset];
|
|
334
|
+
}
|
|
335
|
+
consume(type) {
|
|
336
|
+
const t = this.tokens[this.pos++];
|
|
337
|
+
if (!t) throw new Error("[idml] Unexpected end of input");
|
|
338
|
+
if (type && t.type !== type) {
|
|
339
|
+
throw new Error(`[idml] Expected ${type}, got ${t.type} ("${t.value}")`);
|
|
340
|
+
}
|
|
341
|
+
return t;
|
|
342
|
+
}
|
|
343
|
+
// Entry point. resolve() is called to load imported .idml files.
|
|
344
|
+
parseFile(resolve) {
|
|
345
|
+
this.parseImports(resolve);
|
|
346
|
+
this.parseTopDecls();
|
|
347
|
+
const pages = [];
|
|
348
|
+
while (this.peek()) {
|
|
349
|
+
const route = this.consume("ROUTE").value;
|
|
350
|
+
let scroll = false;
|
|
351
|
+
if (this.peek()?.type === "LBRACKET") {
|
|
352
|
+
this.consume("LBRACKET");
|
|
353
|
+
const flag = this.consume("IDENT").value;
|
|
354
|
+
if (flag === "scroll") scroll = true;
|
|
355
|
+
this.consume("RBRACKET");
|
|
356
|
+
}
|
|
357
|
+
const items = [];
|
|
358
|
+
while (this.peek() && this.peek()?.type !== "ROUTE") {
|
|
359
|
+
const t = this.peek();
|
|
360
|
+
if (t?.type === "IDENT" && t.value === "import") {
|
|
361
|
+
this.parseImports(resolve);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (t?.type === "IDENT" && t.value === "define") {
|
|
365
|
+
this.parseDefinition();
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (t?.type === "IDENT" && this.peek(1)?.type === "COLON" && this.peek(2)?.type === "IDENT") {
|
|
369
|
+
this.parseStyleDefs();
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
items.push(this.parseItem());
|
|
373
|
+
}
|
|
374
|
+
pages.push({ route, scroll, items });
|
|
375
|
+
}
|
|
376
|
+
return pages;
|
|
377
|
+
}
|
|
378
|
+
// Consume import lines. Two forms:
|
|
379
|
+
// import "path" (whole-file style/def import)
|
|
380
|
+
// import Name, Name from "path" (named component/def import)
|
|
381
|
+
// In both cases the referenced .idml file is parsed and its definitions +
|
|
382
|
+
// style-defs are registered into the shared registries.
|
|
383
|
+
parseImports(resolve) {
|
|
384
|
+
while (this.peek(0)?.type === "IDENT" && this.peek(0)?.value === "import") {
|
|
385
|
+
const next = this.peek(1);
|
|
386
|
+
if (next?.type === "STRING") {
|
|
387
|
+
this.pos++;
|
|
388
|
+
const importPath = this.consume("STRING").value;
|
|
389
|
+
this.resolveImport(importPath, [], resolve);
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (next?.type === "IDENT") {
|
|
393
|
+
this.pos++;
|
|
394
|
+
const names = [this.consume("IDENT").value];
|
|
395
|
+
while (this.peek()?.type === "COMMA") {
|
|
396
|
+
this.pos++;
|
|
397
|
+
names.push(this.consume("IDENT").value);
|
|
398
|
+
}
|
|
399
|
+
const fromTok = this.consume("IDENT");
|
|
400
|
+
if (fromTok.value !== "from") {
|
|
401
|
+
throw new Error(`[idml] Expected 'from' in import, got "${fromTok.value}"`);
|
|
402
|
+
}
|
|
403
|
+
const importPath = this.consume("STRING").value;
|
|
404
|
+
this.resolveImport(importPath, names, resolve);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Resolve and parse an imported .idml file into the shared registries.
|
|
411
|
+
// `names`, when non-empty, are validated against what the file actually defines.
|
|
412
|
+
resolveImport(importPath, names, resolve) {
|
|
413
|
+
const afterLastSlash = importPath.slice(importPath.lastIndexOf("/") + 1);
|
|
414
|
+
const dotIdx = afterLastSlash.lastIndexOf(".");
|
|
415
|
+
const ext = dotIdx >= 0 ? afterLastSlash.slice(dotIdx) : "";
|
|
416
|
+
if (ext !== ".idml" && ext !== "") return;
|
|
417
|
+
if (!resolve) return;
|
|
418
|
+
const src = resolve(importPath);
|
|
419
|
+
const sub = new _IdmlParser(tokenize(src));
|
|
420
|
+
sub.styleRegistry = this.styleRegistry;
|
|
421
|
+
sub.defRegistry = this.defRegistry;
|
|
422
|
+
sub.defParamRegistry = this.defParamRegistry;
|
|
423
|
+
sub.parseImports(resolve);
|
|
424
|
+
sub.parseTopDecls();
|
|
425
|
+
for (const name of names) {
|
|
426
|
+
if (!this.defRegistry.has(name) && !this.styleRegistry.has(name) && !BUILTIN_NAMES.has(name)) {
|
|
427
|
+
console.warn(`[idml] import: "${name}" is not defined in ${importPath}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// Parse the top-of-file declarations: `define` component definitions and
|
|
432
|
+
// `Name:BaseType` style-defs, in any order, until a page route or EOF.
|
|
433
|
+
parseTopDecls() {
|
|
434
|
+
for (; ; ) {
|
|
435
|
+
const t = this.peek();
|
|
436
|
+
if (t?.type === "IDENT" && t.value === "define") {
|
|
437
|
+
this.parseDefinition();
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (t?.type === "IDENT" && this.peek(1)?.type === "COLON" && this.peek(2)?.type === "IDENT") {
|
|
441
|
+
this.parseStyleDefs();
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// Consume `define Name(params?) { ...items including Children()... }`.
|
|
448
|
+
parseDefinition() {
|
|
449
|
+
this.pos++;
|
|
450
|
+
const name = this.consume("IDENT").value;
|
|
451
|
+
this.consume("LPAREN");
|
|
452
|
+
const params = [];
|
|
453
|
+
if (this.peek()?.type !== "RPAREN") {
|
|
454
|
+
params.push(this.consume("IDENT").value);
|
|
455
|
+
while (this.peek()?.type === "COMMA") {
|
|
456
|
+
this.pos++;
|
|
457
|
+
params.push(this.consume("IDENT").value);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
this.consume("RPAREN");
|
|
461
|
+
this.consume("LBRACE");
|
|
462
|
+
const body = [];
|
|
463
|
+
while (this.peek()?.type !== "RBRACE") {
|
|
464
|
+
body.push(this.parseItem());
|
|
465
|
+
}
|
|
466
|
+
this.consume("RBRACE");
|
|
467
|
+
this.defRegistry.set(name, body);
|
|
468
|
+
this.defParamRegistry.set(name, params);
|
|
469
|
+
}
|
|
470
|
+
// Consume `Name:BaseType("arg"...) { prop: value }` definitions.
|
|
471
|
+
parseStyleDefs() {
|
|
472
|
+
while (this.peek(0)?.type === "IDENT" && this.peek(1)?.type === "COLON" && this.peek(2)?.type === "IDENT") {
|
|
473
|
+
const name = this.consume("IDENT").value;
|
|
474
|
+
this.consume("COLON");
|
|
475
|
+
const baseType = this.consume("IDENT").value;
|
|
476
|
+
const defaultArgs = [];
|
|
477
|
+
if (this.peek()?.type === "LPAREN") {
|
|
478
|
+
this.consume("LPAREN");
|
|
479
|
+
if (this.peek()?.type !== "RPAREN") defaultArgs.push(...this.parseArgList());
|
|
480
|
+
this.consume("RPAREN");
|
|
481
|
+
}
|
|
482
|
+
let className;
|
|
483
|
+
while (this.peek()?.type === "CLASS_BLOCK") {
|
|
484
|
+
const cls = this.consume("CLASS_BLOCK").value;
|
|
485
|
+
className = className ? `${className} ${cls}` : cls;
|
|
486
|
+
}
|
|
487
|
+
const style = this.peek()?.type === "LBRACE" ? this.parseStyleDefBody() : {};
|
|
488
|
+
this.styleRegistry.set(name, { baseType, defaultArgs, style, className });
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Parse `{ prop: value ... }` body of a style definition.
|
|
492
|
+
parseStyleDefBody() {
|
|
493
|
+
this.consume("LBRACE");
|
|
494
|
+
const result = {};
|
|
495
|
+
while (this.peek()?.type !== "RBRACE") {
|
|
496
|
+
const key = this.consume("IDENT").value;
|
|
497
|
+
this.consume("COLON");
|
|
498
|
+
const val = this.parseStyleValue();
|
|
499
|
+
applyStyleProp(key, val, result);
|
|
500
|
+
}
|
|
501
|
+
this.consume("RBRACE");
|
|
502
|
+
return result;
|
|
503
|
+
}
|
|
504
|
+
// Parse a value token inside a style def body.
|
|
505
|
+
parseStyleValue() {
|
|
506
|
+
const t = this.peek();
|
|
507
|
+
if (!t) throw new Error("[idml] Expected style value");
|
|
508
|
+
if (t.type === "COLOR") {
|
|
509
|
+
this.pos++;
|
|
510
|
+
return t.value;
|
|
511
|
+
}
|
|
512
|
+
if (t.type === "NUMBER") {
|
|
513
|
+
const n = this.consume("NUMBER").value;
|
|
514
|
+
const next = this.peek();
|
|
515
|
+
if (next?.type === "IDENT" && ["vh", "vw", "px", "rem", "em"].includes(next.value)) {
|
|
516
|
+
this.pos++;
|
|
517
|
+
return `${n}${next.value}`;
|
|
518
|
+
}
|
|
519
|
+
return String(n);
|
|
520
|
+
}
|
|
521
|
+
if (t.type === "IDENT") {
|
|
522
|
+
this.pos++;
|
|
523
|
+
return t.value;
|
|
524
|
+
}
|
|
525
|
+
throw new Error(`[idml] Unexpected token type ${t.type} as style value`);
|
|
526
|
+
}
|
|
527
|
+
parseItem() {
|
|
528
|
+
const rawName = this.consume("IDENT").value;
|
|
529
|
+
const regEntry = this.styleRegistry.get(rawName);
|
|
530
|
+
const name = regEntry ? regEntry.baseType : rawName;
|
|
531
|
+
const baseStyle = regEntry ? { ...regEntry.style } : {};
|
|
532
|
+
this.consume("LPAREN");
|
|
533
|
+
const parsedArgs = [];
|
|
534
|
+
if (this.peek()?.type !== "RPAREN") parsedArgs.push(...this.parseArgList());
|
|
535
|
+
this.consume("RPAREN");
|
|
536
|
+
const inlineChildren = [];
|
|
537
|
+
const valueArgs = [];
|
|
538
|
+
for (const a of parsedArgs) {
|
|
539
|
+
if (a && typeof a === "object" && Array.isArray(a.__idmlChildren)) {
|
|
540
|
+
inlineChildren.push(...a.__idmlChildren);
|
|
541
|
+
} else {
|
|
542
|
+
valueArgs.push(a);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const args = valueArgs.length > 0 ? valueArgs : regEntry?.defaultArgs ?? [];
|
|
546
|
+
if (this.peek()?.type !== "LBRACKET") {
|
|
547
|
+
throw new Error(
|
|
548
|
+
`[idml] "${rawName}" is missing its required [height,width,anchor] dimensions`
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
this.consume("LBRACKET");
|
|
552
|
+
const height = this.parseDimension();
|
|
553
|
+
this.consume("COMMA");
|
|
554
|
+
const width = this.parseDimension();
|
|
555
|
+
this.consume("COMMA");
|
|
556
|
+
const anchor = this.consume("IDENT").value;
|
|
557
|
+
let hug;
|
|
558
|
+
if (this.peek()?.type === "COMMA") {
|
|
559
|
+
this.consume("COMMA");
|
|
560
|
+
const kw = this.consume("IDENT").value;
|
|
561
|
+
if (kw === "hug") hug = { w: true, h: true };
|
|
562
|
+
else if (kw === "hug-w") hug = { w: true, h: false };
|
|
563
|
+
else if (kw === "hug-h") hug = { w: false, h: true };
|
|
564
|
+
else
|
|
565
|
+
throw new Error(
|
|
566
|
+
`[idml] unknown sizing keyword "${kw}" for "${rawName}"; expected hug, hug-w, or hug-h`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
this.consume("RBRACKET");
|
|
570
|
+
let visibility;
|
|
571
|
+
if (this.peek()?.type === "QUESTION") {
|
|
572
|
+
this.consume("QUESTION");
|
|
573
|
+
let negate = false;
|
|
574
|
+
if (this.peek()?.type === "BANG") {
|
|
575
|
+
this.consume("BANG");
|
|
576
|
+
negate = true;
|
|
577
|
+
}
|
|
578
|
+
const ref = this.consume("VALUE_REF").value;
|
|
579
|
+
visibility = { ref, negate };
|
|
580
|
+
}
|
|
581
|
+
let className = regEntry?.className;
|
|
582
|
+
const condClasses = [];
|
|
583
|
+
while (this.peek()?.type === "CLASS_BLOCK") {
|
|
584
|
+
const cls = this.consume("CLASS_BLOCK").value;
|
|
585
|
+
if (this.peek()?.type === "QUESTION") {
|
|
586
|
+
this.consume("QUESTION");
|
|
587
|
+
let negate = false;
|
|
588
|
+
if (this.peek()?.type === "BANG") {
|
|
589
|
+
this.consume("BANG");
|
|
590
|
+
negate = true;
|
|
591
|
+
}
|
|
592
|
+
const ref = this.consume("VALUE_REF").value;
|
|
593
|
+
condClasses.push({ classes: cls, ref, negate });
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
for (const tok of cls.split(/\s+/).filter(Boolean)) {
|
|
597
|
+
if (!tok.startsWith("@")) {
|
|
598
|
+
throw new Error(
|
|
599
|
+
`[idml] literal class "${tok}" is not allowed at a use site; declare a styled variant (Name:BaseType) instead`
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
className = className ? `${className} ${cls}` : cls;
|
|
604
|
+
}
|
|
605
|
+
const style = { ...baseStyle };
|
|
606
|
+
const classRefs = [];
|
|
607
|
+
if (className) {
|
|
608
|
+
const statics = [];
|
|
609
|
+
for (const tok of className.split(/\s+/).filter(Boolean)) {
|
|
610
|
+
if (tok.startsWith("@")) classRefs.push(tok.slice(1));
|
|
611
|
+
else statics.push(tok);
|
|
612
|
+
}
|
|
613
|
+
className = statics.length ? statics.join(" ") : void 0;
|
|
614
|
+
}
|
|
615
|
+
this.consume("LBRACE");
|
|
616
|
+
const children = [...inlineChildren];
|
|
617
|
+
while (this.peek()?.type !== "RBRACE") {
|
|
618
|
+
children.push(this.parseItem());
|
|
619
|
+
}
|
|
620
|
+
this.consume("RBRACE");
|
|
621
|
+
return { name, args, height, width, anchor, children, style, className, classRefs, condClasses, hug, visibility };
|
|
622
|
+
}
|
|
623
|
+
parseDimension() {
|
|
624
|
+
if (this.peek()?.type === "IDENT" && this.peek()?.value === "auto") {
|
|
625
|
+
throw new Error(
|
|
626
|
+
"[idml] the `auto` dimension is no longer supported; give an explicit percentage (use a Spacer for any intentional empty space)"
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
if (this.peek()?.type === "VALUE_REF") {
|
|
630
|
+
const ref = this.consume("VALUE_REF").value;
|
|
631
|
+
if (this.peek()?.type === "QUESTION") {
|
|
632
|
+
this.consume("QUESTION");
|
|
633
|
+
const whenTrue = this.parseDimLiteral();
|
|
634
|
+
this.consume("COLON");
|
|
635
|
+
const whenFalse = this.parseDimLiteral();
|
|
636
|
+
return { ref, whenTrue, whenFalse };
|
|
637
|
+
}
|
|
638
|
+
return { ref };
|
|
639
|
+
}
|
|
640
|
+
return this.consume("NUMBER").value;
|
|
641
|
+
}
|
|
642
|
+
/** A literal dimension value inside a `@ref ? A : B`: a number (→ `%`) or a
|
|
643
|
+
* number with a unit (`3.4vw`, `64px`). Returns the final CSS string. */
|
|
644
|
+
parseDimLiteral() {
|
|
645
|
+
const n = this.consume("NUMBER").value;
|
|
646
|
+
const next = this.peek();
|
|
647
|
+
if (next?.type === "IDENT" && ["vw", "vh", "px", "rem", "em"].includes(next.value)) {
|
|
648
|
+
this.pos++;
|
|
649
|
+
return `${n}${next.value}`;
|
|
650
|
+
}
|
|
651
|
+
return `${n}%`;
|
|
652
|
+
}
|
|
653
|
+
parseArgList() {
|
|
654
|
+
const args = [];
|
|
655
|
+
args.push(this.parseArg());
|
|
656
|
+
while (this.peek()?.type === "COMMA") {
|
|
657
|
+
this.pos++;
|
|
658
|
+
if (this.peek()?.type === "RPAREN") break;
|
|
659
|
+
args.push(this.parseArg());
|
|
660
|
+
}
|
|
661
|
+
return args;
|
|
662
|
+
}
|
|
663
|
+
parseArg() {
|
|
664
|
+
const t = this.peek();
|
|
665
|
+
if (!t) throw new Error("[idml] Expected argument");
|
|
666
|
+
if (t.type === "STRING") {
|
|
667
|
+
this.pos++;
|
|
668
|
+
return t.value;
|
|
669
|
+
}
|
|
670
|
+
if (t.type === "NUMBER") {
|
|
671
|
+
this.pos++;
|
|
672
|
+
return t.value;
|
|
673
|
+
}
|
|
674
|
+
if (t.type === "VALUE_REF") {
|
|
675
|
+
this.pos++;
|
|
676
|
+
return `${VALUE_REF_PREFIX}${t.value}`;
|
|
677
|
+
}
|
|
678
|
+
if (t.type === "MODEL_REF") {
|
|
679
|
+
this.pos++;
|
|
680
|
+
return `${MODEL_REF_PREFIX}${t.value}`;
|
|
681
|
+
}
|
|
682
|
+
if (t.type === "IDENT") {
|
|
683
|
+
this.pos++;
|
|
684
|
+
if (t.value === "null") return null;
|
|
685
|
+
if (t.value === "true") return true;
|
|
686
|
+
if (t.value === "false") return false;
|
|
687
|
+
return `${FN_REF_PREFIX}${t.value}`;
|
|
688
|
+
}
|
|
689
|
+
if (t.type === "LBRACE") {
|
|
690
|
+
this.pos++;
|
|
691
|
+
const childItems = [];
|
|
692
|
+
while (this.peek()?.type !== "RBRACE") {
|
|
693
|
+
childItems.push(this.parseItem());
|
|
694
|
+
}
|
|
695
|
+
this.consume("RBRACE");
|
|
696
|
+
return { __idmlChildren: childItems };
|
|
697
|
+
}
|
|
698
|
+
throw new Error(`[idml] Unexpected token type ${t.type} as argument`);
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
var LAYOUT_ITEMS = /* @__PURE__ */ new Set(["Row", "Col"]);
|
|
702
|
+
var ANCHOR_V = { top: "flex-start", center: "center", bottom: "flex-end" };
|
|
703
|
+
var ANCHOR_H = { left: "flex-start", center: "center", right: "flex-end" };
|
|
704
|
+
function anchorToAbsoluteInsets(anchor) {
|
|
705
|
+
const parts = anchor.split("-");
|
|
706
|
+
const v = parts.length === 1 ? parts[0] : parts[0] ?? "top";
|
|
707
|
+
const h = parts.length === 1 ? parts[0] : parts[1] ?? "left";
|
|
708
|
+
const css = {};
|
|
709
|
+
if (v === "bottom") css.bottom = "0";
|
|
710
|
+
else if (v === "center") css.top = "50%";
|
|
711
|
+
else css.top = "0";
|
|
712
|
+
if (h === "right") css.right = "0";
|
|
713
|
+
else if (h === "center") css.left = "50%";
|
|
714
|
+
else css.left = "0";
|
|
715
|
+
if (v === "center" || h === "center") {
|
|
716
|
+
css.transform = `translate(${h === "center" ? "-50%" : "0"}, ${v === "center" ? "-50%" : "0"})`;
|
|
717
|
+
}
|
|
718
|
+
return css;
|
|
719
|
+
}
|
|
720
|
+
function anchorToFlexProps(anchor, direction) {
|
|
721
|
+
const parts = anchor.split("-");
|
|
722
|
+
if (parts.length === 1) {
|
|
723
|
+
const val = ANCHOR_V[parts[0]] ?? ANCHOR_H[parts[0]] ?? "flex-start";
|
|
724
|
+
return { justifyContent: val, alignItems: val };
|
|
725
|
+
}
|
|
726
|
+
const [v = "top", h = "left"] = parts;
|
|
727
|
+
const vVal = ANCHOR_V[v] ?? "flex-start";
|
|
728
|
+
const hVal = ANCHOR_H[h] ?? "flex-start";
|
|
729
|
+
if (direction === "row") {
|
|
730
|
+
return { justifyContent: hVal, alignItems: vVal };
|
|
731
|
+
}
|
|
732
|
+
return { justifyContent: vVal, alignItems: hVal };
|
|
733
|
+
}
|
|
734
|
+
function pct(n) {
|
|
735
|
+
return `${n}%`;
|
|
736
|
+
}
|
|
737
|
+
var _idCounter = 0;
|
|
738
|
+
function genId(prefix) {
|
|
739
|
+
return `${prefix}-${++_idCounter}`;
|
|
740
|
+
}
|
|
741
|
+
function substituteParams(item, bindings) {
|
|
742
|
+
if (bindings.size === 0) return item;
|
|
743
|
+
const subArg = (a) => {
|
|
744
|
+
if (typeof a === "string") {
|
|
745
|
+
for (const prefix of [FN_REF_PREFIX, VALUE_REF_PREFIX, MODEL_REF_PREFIX]) {
|
|
746
|
+
if (a.startsWith(prefix)) {
|
|
747
|
+
const name = a.slice(prefix.length);
|
|
748
|
+
return bindings.has(name) ? bindings.get(name) : a;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return a;
|
|
752
|
+
}
|
|
753
|
+
if (a && typeof a === "object" && Array.isArray(a.__idmlChildren)) {
|
|
754
|
+
return {
|
|
755
|
+
__idmlChildren: a.__idmlChildren.map((c) => substituteParams(c, bindings))
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
return a;
|
|
759
|
+
};
|
|
760
|
+
return {
|
|
761
|
+
...item,
|
|
762
|
+
args: item.args.map(subArg),
|
|
763
|
+
children: item.children.map((c) => substituteParams(c, bindings))
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
function containerDirection(name, defs) {
|
|
767
|
+
if (name === "Row") return "row";
|
|
768
|
+
if (name === "Col" || name === "Form") return "column";
|
|
769
|
+
if (defs.has(name)) return "column";
|
|
770
|
+
return null;
|
|
771
|
+
}
|
|
772
|
+
var OUT_OF_FLOW = /* @__PURE__ */ new Set(["Overlay", "Modal"]);
|
|
773
|
+
function defIsOutOfFlow(name, defs, seen = /* @__PURE__ */ new Set()) {
|
|
774
|
+
const body = defs.get(name);
|
|
775
|
+
if (!body || seen.has(name)) return false;
|
|
776
|
+
seen.add(name);
|
|
777
|
+
return body.every(
|
|
778
|
+
(c) => OUT_OF_FLOW.has(c.name) || defs.has(c.name) && defIsOutOfFlow(c.name, defs, seen)
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
function makeOutOfFlowPredicate(defs) {
|
|
782
|
+
return (name) => OUT_OF_FLOW.has(name) || defs.has(name) && defIsOutOfFlow(name, defs);
|
|
783
|
+
}
|
|
784
|
+
function validateTiling(children, direction, where, isOutOfFlow, containerHug) {
|
|
785
|
+
children = children.filter((c) => !isOutOfFlow(c.name));
|
|
786
|
+
if (children.length === 0) return;
|
|
787
|
+
const main = direction === "row" ? "width" : "height";
|
|
788
|
+
const cross = direction === "row" ? "height" : "width";
|
|
789
|
+
const mainKey = direction === "row" ? "w" : "h";
|
|
790
|
+
const crossKey = direction === "row" ? "h" : "w";
|
|
791
|
+
const packsMain = !!containerHug?.[mainKey] || children.some((c) => c.hug?.[mainKey] || isDimRef(c[main]));
|
|
792
|
+
let sum = 0;
|
|
793
|
+
for (const c of children) {
|
|
794
|
+
if (typeof c[main] === "number") sum += c[main];
|
|
795
|
+
if (!c.hug?.[crossKey] && !isDimRef(c[cross]) && c[cross] !== 100) {
|
|
796
|
+
throw new Error(
|
|
797
|
+
`[idml] ${c.name} in ${where}: cross-axis ${cross} must be 100 (got ${c[cross]}); no vacant space is allowed`
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
if (!packsMain && sum !== 100) {
|
|
802
|
+
throw new Error(
|
|
803
|
+
`[idml] children of ${where} must tile to 100% along ${main}; got ${sum}. Add an explicit Spacer for any gap.`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
function walkTiling(item, defs, isOutOfFlow) {
|
|
808
|
+
const dir = containerDirection(item.name, defs);
|
|
809
|
+
if (dir) validateTiling(item.children, dir, `<${item.name}>`, isOutOfFlow, item.hug);
|
|
810
|
+
for (const child of item.children) walkTiling(child, defs, isOutOfFlow);
|
|
811
|
+
}
|
|
812
|
+
function sizeOf(item) {
|
|
813
|
+
const size = {};
|
|
814
|
+
if (typeof item.height === "number") size.height = pct(item.height);
|
|
815
|
+
if (typeof item.width === "number") size.width = pct(item.width);
|
|
816
|
+
return size;
|
|
817
|
+
}
|
|
818
|
+
function dynSizeOf(item) {
|
|
819
|
+
const dyn = {};
|
|
820
|
+
if (isDimRef(item.height)) dyn.height = dimRefToDynamic(item.height);
|
|
821
|
+
if (isDimRef(item.width)) dyn.width = dimRefToDynamic(item.width);
|
|
822
|
+
return dyn.width || dyn.height ? dyn : void 0;
|
|
823
|
+
}
|
|
824
|
+
function dimRefToDynamic(d) {
|
|
825
|
+
return d.whenTrue !== void 0 ? { ref: d.ref, whenTrue: d.whenTrue, whenFalse: d.whenFalse } : { ref: d.ref };
|
|
826
|
+
}
|
|
827
|
+
function hugStyles(hug) {
|
|
828
|
+
const s = {};
|
|
829
|
+
if (hug.w) {
|
|
830
|
+
s.width = "fit-content";
|
|
831
|
+
s.maxWidth = "100%";
|
|
832
|
+
s.minWidth = "0";
|
|
833
|
+
s.overflow = "hidden";
|
|
834
|
+
s.textOverflow = "ellipsis";
|
|
835
|
+
s.whiteSpace = "nowrap";
|
|
836
|
+
}
|
|
837
|
+
if (hug.h) {
|
|
838
|
+
s.height = "fit-content";
|
|
839
|
+
s.maxHeight = "100%";
|
|
840
|
+
}
|
|
841
|
+
return s;
|
|
842
|
+
}
|
|
843
|
+
function hugContainerStyles(hug) {
|
|
844
|
+
const s = {};
|
|
845
|
+
if (hug.w) s.width = "fit-content";
|
|
846
|
+
if (hug.h) s.height = "fit-content";
|
|
847
|
+
return s;
|
|
848
|
+
}
|
|
849
|
+
var HUG_INVALID_ON = /* @__PURE__ */ new Set(["Overlay", "Modal", "Children"]);
|
|
850
|
+
function assertHuggable(item, isDef) {
|
|
851
|
+
if (!item.hug) return;
|
|
852
|
+
if (HUG_INVALID_ON.has(item.name) || isDef) {
|
|
853
|
+
throw new Error(
|
|
854
|
+
`[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.`
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
function mkItem(name, args, height, width, anchor, children, style = {}) {
|
|
859
|
+
return { name, args, height, width, anchor, children, style };
|
|
860
|
+
}
|
|
861
|
+
function expandTable(item, ctx) {
|
|
862
|
+
const dataArg = item.args.find(
|
|
863
|
+
(a) => typeof a === "string" && a.startsWith(VALUE_REF_PREFIX)
|
|
864
|
+
);
|
|
865
|
+
const columns = item.children.filter((c) => c.name === "Column");
|
|
866
|
+
const headerCells = columns.map((col) => {
|
|
867
|
+
const label = mkItem(
|
|
868
|
+
"Text",
|
|
869
|
+
[String(col.args[0] ?? "")],
|
|
870
|
+
"auto",
|
|
871
|
+
100,
|
|
872
|
+
col.anchor,
|
|
873
|
+
[],
|
|
874
|
+
{ fontSize: "0.63vw" }
|
|
875
|
+
);
|
|
876
|
+
label.className = "font-medium text-gray-500 uppercase tracking-wider";
|
|
877
|
+
const cell = mkItem("Col", [], "auto", col.width, col.anchor, [label], {
|
|
878
|
+
paddingLeft: "1.6vw",
|
|
879
|
+
paddingRight: "1.6vw",
|
|
880
|
+
paddingTop: "0.8vw",
|
|
881
|
+
paddingBottom: "0.8vw"
|
|
882
|
+
});
|
|
883
|
+
return cell;
|
|
884
|
+
});
|
|
885
|
+
const headerRow = mkItem("Row", [], "auto", 100, "top-left", headerCells, {
|
|
886
|
+
borderBottom: "0.07vw solid #e5e7eb"
|
|
887
|
+
});
|
|
888
|
+
headerRow.className = "bg-gray-50";
|
|
889
|
+
const bodyCells = columns.map((col) => {
|
|
890
|
+
const cell = mkItem("Col", [], "auto", col.width, col.anchor, col.children, {
|
|
891
|
+
paddingLeft: "1.6vw",
|
|
892
|
+
paddingRight: "1.6vw",
|
|
893
|
+
paddingTop: "1vw",
|
|
894
|
+
paddingBottom: "1vw"
|
|
895
|
+
});
|
|
896
|
+
cell.className = "whitespace-nowrap";
|
|
897
|
+
return cell;
|
|
898
|
+
});
|
|
899
|
+
const bodyRowTemplate = mkItem("Row", [], "auto", 100, "top-left", bodyCells, {
|
|
900
|
+
borderBottom: "0.07vw solid #e5e7eb"
|
|
901
|
+
});
|
|
902
|
+
const repeat = mkItem("Repeat", dataArg ? [dataArg] : [], "auto", 100, "top-left", [bodyRowTemplate]);
|
|
903
|
+
const tableStyle = { ...item.style };
|
|
904
|
+
if (item.hug?.w) tableStyle.width = "fit-content";
|
|
905
|
+
const tableCol = mkItem(
|
|
906
|
+
"Col",
|
|
907
|
+
[],
|
|
908
|
+
item.hug?.h ? "auto" : item.height,
|
|
909
|
+
item.hug?.w ? "auto" : item.width,
|
|
910
|
+
item.anchor,
|
|
911
|
+
[headerRow, repeat],
|
|
912
|
+
tableStyle
|
|
913
|
+
);
|
|
914
|
+
tableCol.className = item.className;
|
|
915
|
+
return convertItem(tableCol, ctx);
|
|
916
|
+
}
|
|
917
|
+
function convertItem(item, ctx) {
|
|
918
|
+
const def = convertNode(item, ctx);
|
|
919
|
+
if (item.visibility) def.visibility = item.visibility;
|
|
920
|
+
const dyn = dynSizeOf(item);
|
|
921
|
+
if (dyn) def.dynamicSize = dyn;
|
|
922
|
+
if (item.classRefs?.length && !def.componentId) def.classRefs = item.classRefs;
|
|
923
|
+
if (item.condClasses?.length) def.condClasses = item.condClasses;
|
|
924
|
+
return def;
|
|
925
|
+
}
|
|
926
|
+
function convertNode(item, ctx) {
|
|
927
|
+
assertHuggable(item, ctx.defs.has(item.name));
|
|
928
|
+
const size = sizeOf(item);
|
|
929
|
+
const idmlStyle = Object.keys(item.style).length ? item.style : void 0;
|
|
930
|
+
const colAnchor = anchorToFlexProps(item.anchor, "column");
|
|
931
|
+
const outOfFlow = ctx.isOutOfFlow(item.name);
|
|
932
|
+
const cellStyle = outOfFlow ? { ...idmlStyle ?? {}, display: "contents" } : idmlStyle;
|
|
933
|
+
if (item.name === "Children") {
|
|
934
|
+
const slot = ctx.slotChildren ?? [];
|
|
935
|
+
const childCtx = { ...ctx, slotChildren: void 0 };
|
|
936
|
+
return {
|
|
937
|
+
type: "flex",
|
|
938
|
+
direction: "column",
|
|
939
|
+
...colAnchor,
|
|
940
|
+
size,
|
|
941
|
+
children: slot.map((child) => convertItem(child, childCtx)),
|
|
942
|
+
idmlStyle
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
const defBody = ctx.defs.get(item.name);
|
|
946
|
+
if (defBody && !ctx.expanding.has(item.name)) {
|
|
947
|
+
const params = ctx.defParams.get(item.name) ?? [];
|
|
948
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
949
|
+
params.forEach((p, i) => bindings.set(p, item.args[i] ?? ""));
|
|
950
|
+
const body = bindings.size ? defBody.map((t) => substituteParams(t, bindings)) : defBody;
|
|
951
|
+
const innerCtx = {
|
|
952
|
+
...ctx,
|
|
953
|
+
slotChildren: item.children,
|
|
954
|
+
expanding: new Set(ctx.expanding).add(item.name)
|
|
955
|
+
};
|
|
956
|
+
return {
|
|
957
|
+
type: "flex",
|
|
958
|
+
direction: "column",
|
|
959
|
+
...colAnchor,
|
|
960
|
+
size,
|
|
961
|
+
children: body.map((t) => convertItem(t, innerCtx)),
|
|
962
|
+
idmlStyle: cellStyle
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
if (item.name === "Overlay") {
|
|
966
|
+
const children2 = item.children.map((child) => {
|
|
967
|
+
const layout = convertItem(child, ctx);
|
|
968
|
+
const hugStyle = {};
|
|
969
|
+
if (child.hug?.w) {
|
|
970
|
+
hugStyle.width = "fit-content";
|
|
971
|
+
if (layout.size?.width) hugStyle.maxWidth = layout.size.width;
|
|
972
|
+
}
|
|
973
|
+
if (child.hug?.h) {
|
|
974
|
+
hugStyle.height = "fit-content";
|
|
975
|
+
if (layout.size?.height) hugStyle.maxHeight = layout.size.height;
|
|
976
|
+
}
|
|
977
|
+
return {
|
|
978
|
+
...layout,
|
|
979
|
+
idmlStyle: {
|
|
980
|
+
...layout.idmlStyle ?? {},
|
|
981
|
+
position: "absolute",
|
|
982
|
+
pointerEvents: "auto",
|
|
983
|
+
...anchorToAbsoluteInsets(child.anchor),
|
|
984
|
+
...hugStyle
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
});
|
|
988
|
+
return {
|
|
989
|
+
type: "flex",
|
|
990
|
+
direction: "column",
|
|
991
|
+
size: { width: "100%", height: "100%" },
|
|
992
|
+
children: children2,
|
|
993
|
+
idmlStyle: {
|
|
994
|
+
position: "fixed",
|
|
995
|
+
top: "0",
|
|
996
|
+
left: "0",
|
|
997
|
+
right: "0",
|
|
998
|
+
bottom: "0",
|
|
999
|
+
pointerEvents: "none",
|
|
1000
|
+
outline: "none",
|
|
1001
|
+
zIndex: "50",
|
|
1002
|
+
...idmlStyle ?? {}
|
|
1003
|
+
},
|
|
1004
|
+
...item.className ? { className: item.className } : {}
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
if (item.name === "Table") {
|
|
1008
|
+
return expandTable(item, ctx);
|
|
1009
|
+
}
|
|
1010
|
+
if (LAYOUT_ITEMS.has(item.name)) {
|
|
1011
|
+
const direction = item.name === "Row" ? "row" : "column";
|
|
1012
|
+
const { justifyContent, alignItems } = anchorToFlexProps(item.anchor, direction);
|
|
1013
|
+
const children2 = item.children.map((child) => convertItem(child, ctx));
|
|
1014
|
+
const mainHug = direction === "column" ? item.hug?.h : item.hug?.w;
|
|
1015
|
+
if (mainHug) {
|
|
1016
|
+
for (const ch of children2) {
|
|
1017
|
+
if (!ch.size) continue;
|
|
1018
|
+
if (direction === "column") delete ch.size.height;
|
|
1019
|
+
else delete ch.size.width;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
const crossHug = direction === "column" ? item.hug?.w : item.hug?.h;
|
|
1023
|
+
const containerStyle = crossHug ? { ...idmlStyle ?? {}, ...hugContainerStyles({ w: direction === "column", h: direction === "row" }) } : idmlStyle;
|
|
1024
|
+
return {
|
|
1025
|
+
type: "flex",
|
|
1026
|
+
direction,
|
|
1027
|
+
justifyContent,
|
|
1028
|
+
alignItems,
|
|
1029
|
+
size,
|
|
1030
|
+
children: children2,
|
|
1031
|
+
idmlStyle: containerStyle,
|
|
1032
|
+
...item.className ? { className: item.className } : {}
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
if (item.name === "Select" && item.children.some((c) => c.name === "Option")) {
|
|
1036
|
+
const id2 = genId("select");
|
|
1037
|
+
const options = item.children.filter((c) => c.name === "Option").map((c) => ({ value: c.args[0] ?? "", label: c.args[1] ?? c.args[0] ?? "" }));
|
|
1038
|
+
const def = buildComponentDef(item, id2);
|
|
1039
|
+
def.props = { ...def.props, options };
|
|
1040
|
+
ctx.components.push(def);
|
|
1041
|
+
return { type: "flex", direction: "column", ...colAnchor, size, children: [], componentId: id2 };
|
|
1042
|
+
}
|
|
1043
|
+
const id = genId(item.name.toLowerCase());
|
|
1044
|
+
ctx.components.push(buildComponentDef(item, id));
|
|
1045
|
+
const children = item.children.map((child) => convertItem(child, ctx));
|
|
1046
|
+
const hugCell = {};
|
|
1047
|
+
if (item.hug?.w) {
|
|
1048
|
+
hugCell.width = "fit-content";
|
|
1049
|
+
if (typeof item.width === "number") hugCell.maxWidth = `${item.width}%`;
|
|
1050
|
+
}
|
|
1051
|
+
if (item.hug?.h) {
|
|
1052
|
+
hugCell.height = "fit-content";
|
|
1053
|
+
if (typeof item.height === "number") hugCell.maxHeight = `${item.height}%`;
|
|
1054
|
+
}
|
|
1055
|
+
const cellIdml = outOfFlow ? { ...cellStyle ?? {}, ...hugCell } : Object.keys(hugCell).length ? hugCell : void 0;
|
|
1056
|
+
return {
|
|
1057
|
+
type: "flex",
|
|
1058
|
+
direction: "column",
|
|
1059
|
+
...colAnchor,
|
|
1060
|
+
size,
|
|
1061
|
+
children,
|
|
1062
|
+
componentId: id,
|
|
1063
|
+
...cellIdml ? { idmlStyle: cellIdml } : {}
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
function anchorToComponentStyle(anchor, componentType) {
|
|
1067
|
+
const parts = anchor.split("-");
|
|
1068
|
+
const h = parts.length === 1 ? parts[0] : parts[1] ?? parts[0];
|
|
1069
|
+
const v = parts.length === 1 ? parts[0] : parts[0];
|
|
1070
|
+
const css = {};
|
|
1071
|
+
if (componentType === "Text" || componentType === "Heading") {
|
|
1072
|
+
if (h === "center") css.textAlign = "center";
|
|
1073
|
+
else if (h === "right") css.textAlign = "right";
|
|
1074
|
+
}
|
|
1075
|
+
if (componentType === "Button") {
|
|
1076
|
+
css.display = "flex";
|
|
1077
|
+
css.justifyContent = h === "center" ? "center" : h === "right" ? "flex-end" : "flex-start";
|
|
1078
|
+
css.alignItems = v === "center" ? "center" : v === "bottom" ? "flex-end" : "flex-start";
|
|
1079
|
+
}
|
|
1080
|
+
return css;
|
|
1081
|
+
}
|
|
1082
|
+
function buildComponentDef(item, id) {
|
|
1083
|
+
const anchorStyle = anchorToComponentStyle(item.anchor, item.name);
|
|
1084
|
+
const hug = item.hug ? hugStyles(item.hug) : {};
|
|
1085
|
+
const merged = { ...anchorStyle, ...item.style, ...hug };
|
|
1086
|
+
const idmlStyle = Object.keys(merged).length ? merged : void 0;
|
|
1087
|
+
const valueRefs = [];
|
|
1088
|
+
const modelRefs = [];
|
|
1089
|
+
const handlerRefs = [];
|
|
1090
|
+
const literals = [];
|
|
1091
|
+
for (const a of item.args) {
|
|
1092
|
+
if (typeof a === "string" && a.startsWith(VALUE_REF_PREFIX)) valueRefs.push(a.slice(VALUE_REF_PREFIX.length));
|
|
1093
|
+
else if (typeof a === "string" && a.startsWith(MODEL_REF_PREFIX)) modelRefs.push(a.slice(MODEL_REF_PREFIX.length));
|
|
1094
|
+
else if (typeof a === "string" && a.startsWith(FN_REF_PREFIX)) handlerRefs.push(a.slice(FN_REF_PREFIX.length));
|
|
1095
|
+
else literals.push(a);
|
|
1096
|
+
}
|
|
1097
|
+
const primaryProp = PRIMARY_PROP[item.name] ?? "value";
|
|
1098
|
+
const bindings = [
|
|
1099
|
+
...valueRefs.map((methodId) => ({ prop: primaryProp, methodId, kind: "value" })),
|
|
1100
|
+
...modelRefs.map((methodId) => ({ prop: primaryProp, methodId, kind: "model" })),
|
|
1101
|
+
...handlerRefs.map((methodId) => ({ prop: "onClick", methodId })),
|
|
1102
|
+
// Dynamic classes (`@method` tokens in a class block) resolve to strings that
|
|
1103
|
+
// are appended to className per render.
|
|
1104
|
+
...(item.classRefs ?? []).map((methodId) => ({ prop: "className", methodId, kind: "value" }))
|
|
1105
|
+
];
|
|
1106
|
+
const withBindings = (def) => {
|
|
1107
|
+
const withB = bindings.length ? { ...def, bindings } : def;
|
|
1108
|
+
return item.className ? { ...withB, className: item.className } : withB;
|
|
1109
|
+
};
|
|
1110
|
+
const [first, second] = literals;
|
|
1111
|
+
switch (item.name) {
|
|
1112
|
+
case "Text":
|
|
1113
|
+
return withBindings({ id, type: "Text", props: { text: String(first ?? "") }, idmlStyle });
|
|
1114
|
+
case "Heading":
|
|
1115
|
+
return withBindings({
|
|
1116
|
+
id,
|
|
1117
|
+
type: "Heading",
|
|
1118
|
+
props: { text: String(first ?? ""), level: typeof second === "number" ? second : 1 },
|
|
1119
|
+
idmlStyle
|
|
1120
|
+
});
|
|
1121
|
+
case "Button": {
|
|
1122
|
+
const route = literals.find((a) => typeof a === "string" && a.startsWith("/"));
|
|
1123
|
+
const label = literals.find((a) => typeof a === "string" && !a.startsWith("/"));
|
|
1124
|
+
const props = { text: String(label ?? "") };
|
|
1125
|
+
if (route) props.href = route;
|
|
1126
|
+
return withBindings({ id, type: "Button", props, idmlStyle });
|
|
1127
|
+
}
|
|
1128
|
+
case "Image":
|
|
1129
|
+
return withBindings({ id, type: "Image", props: { src: String(first ?? ""), alt: String(second ?? "") }, idmlStyle });
|
|
1130
|
+
case "Label":
|
|
1131
|
+
return withBindings({ id, type: "Label", props: { text: String(first ?? "") }, idmlStyle });
|
|
1132
|
+
case "Icon": {
|
|
1133
|
+
const props = { name: String(first ?? "") };
|
|
1134
|
+
for (const rest of literals.slice(1)) {
|
|
1135
|
+
if (typeof rest === "number") props.size = rest;
|
|
1136
|
+
else if (typeof rest === "string") props.color = rest;
|
|
1137
|
+
}
|
|
1138
|
+
return withBindings({ id, type: "Icon", props, idmlStyle });
|
|
1139
|
+
}
|
|
1140
|
+
case "Option": {
|
|
1141
|
+
const value = first ?? "";
|
|
1142
|
+
const label = second ?? first ?? "";
|
|
1143
|
+
return withBindings({ id, type: "Option", props: { value, label }, idmlStyle });
|
|
1144
|
+
}
|
|
1145
|
+
case "Input":
|
|
1146
|
+
case "Textarea":
|
|
1147
|
+
return withBindings({
|
|
1148
|
+
id,
|
|
1149
|
+
type: item.name,
|
|
1150
|
+
props: first != null ? { placeholder: String(first) } : {},
|
|
1151
|
+
idmlStyle
|
|
1152
|
+
});
|
|
1153
|
+
default:
|
|
1154
|
+
return withBindings({
|
|
1155
|
+
id,
|
|
1156
|
+
type: item.name,
|
|
1157
|
+
props: Object.fromEntries(literals.map((v, i) => [`arg${i}`, v])),
|
|
1158
|
+
idmlStyle
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
var PRIMARY_PROP = {
|
|
1163
|
+
Text: "text",
|
|
1164
|
+
Heading: "text",
|
|
1165
|
+
Button: "text",
|
|
1166
|
+
Image: "src",
|
|
1167
|
+
Input: "value",
|
|
1168
|
+
Textarea: "value",
|
|
1169
|
+
Select: "value",
|
|
1170
|
+
Checkbox: "checked",
|
|
1171
|
+
Table: "data",
|
|
1172
|
+
Repeat: "data",
|
|
1173
|
+
Modal: "open",
|
|
1174
|
+
Icon: "name"
|
|
1175
|
+
};
|
|
1176
|
+
var DEFAULT_TOKENS = {
|
|
1177
|
+
colors: [
|
|
1178
|
+
{ name: "primary", value: "#1a56db", darkValue: "#60a5fa" },
|
|
1179
|
+
{ name: "surface", value: "#ffffff", darkValue: "#1e1e2e" },
|
|
1180
|
+
{ name: "on-surface", value: "#111827", darkValue: "#f9fafb" },
|
|
1181
|
+
{ name: "danger", value: "#dc2626", darkValue: "#f87171" }
|
|
1182
|
+
],
|
|
1183
|
+
typography: [
|
|
1184
|
+
{ name: "heading-xl", fontSize: "2.25rem", fontWeight: 700, lineHeight: "1.25" },
|
|
1185
|
+
{ name: "body-md", fontSize: "1rem", fontWeight: 400, lineHeight: "1.6" },
|
|
1186
|
+
{ name: "label-sm", fontSize: "0.75rem", fontWeight: 500, lineHeight: "1.4" }
|
|
1187
|
+
],
|
|
1188
|
+
spacing: [
|
|
1189
|
+
{ name: "gap-sm", value: "0.5rem" },
|
|
1190
|
+
{ name: "gap-md", value: "1rem" },
|
|
1191
|
+
{ name: "gap-lg", value: "2rem" }
|
|
1192
|
+
]
|
|
1193
|
+
};
|
|
1194
|
+
function parseIdml(source, options) {
|
|
1195
|
+
_idCounter = 0;
|
|
1196
|
+
const parser = new IdmlParser(tokenize(source));
|
|
1197
|
+
const parsedPages = parser.parseFile(options?.resolve);
|
|
1198
|
+
const isOutOfFlow = makeOutOfFlowPredicate(parser.defRegistry);
|
|
1199
|
+
for (const { route, items } of parsedPages) {
|
|
1200
|
+
validateTiling(items, "column", `page ${route}`, isOutOfFlow);
|
|
1201
|
+
items.forEach((it) => walkTiling(it, parser.defRegistry, isOutOfFlow));
|
|
1202
|
+
}
|
|
1203
|
+
for (const [name, body] of parser.defRegistry) {
|
|
1204
|
+
validateTiling(body, "column", `define ${name}`, isOutOfFlow);
|
|
1205
|
+
body.forEach((it) => walkTiling(it, parser.defRegistry, isOutOfFlow));
|
|
1206
|
+
}
|
|
1207
|
+
const pages = parsedPages.map(({ route, scroll, items }) => {
|
|
1208
|
+
const components = [];
|
|
1209
|
+
const ctx = {
|
|
1210
|
+
components,
|
|
1211
|
+
defs: parser.defRegistry,
|
|
1212
|
+
defParams: parser.defParamRegistry,
|
|
1213
|
+
expanding: /* @__PURE__ */ new Set(),
|
|
1214
|
+
isOutOfFlow
|
|
1215
|
+
};
|
|
1216
|
+
const layoutChildren = items.map((item) => convertItem(item, ctx));
|
|
1217
|
+
const rootLayout = {
|
|
1218
|
+
type: "flex",
|
|
1219
|
+
direction: "column",
|
|
1220
|
+
size: { width: "100%", height: "100%" },
|
|
1221
|
+
children: layoutChildren,
|
|
1222
|
+
...scroll ? { idmlStyle: { overflowY: "auto" } } : {}
|
|
1223
|
+
};
|
|
1224
|
+
return { route, layout: rootLayout, components };
|
|
1225
|
+
});
|
|
1226
|
+
return { version: "1", tokens: DEFAULT_TOKENS, pages };
|
|
1227
|
+
}
|
|
1228
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1229
|
+
0 && (module.exports = {
|
|
1230
|
+
addSSEWriter,
|
|
1231
|
+
parseIdml,
|
|
1232
|
+
startWatcher,
|
|
1233
|
+
stopWatcher,
|
|
1234
|
+
withUIConfig
|
|
1235
|
+
});
|
|
1236
|
+
//# sourceMappingURL=server.cjs.map
|