@lightspeed-js/core 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.
Files changed (142) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +75 -0
  3. package/dist/compile.js +1494 -0
  4. package/dist/index.js +3060 -0
  5. package/dist/ssg.js +3620 -0
  6. package/dist/types/build.d.ts +1 -0
  7. package/dist/types/index.d.ts +8 -0
  8. package/dist/types/src/agent/index.d.ts +175 -0
  9. package/dist/types/src/cli/commands/agent.d.ts +9 -0
  10. package/dist/types/src/cli/commands/build.d.ts +2 -0
  11. package/dist/types/src/cli/commands/create.d.ts +2 -0
  12. package/dist/types/src/cli/commands/dev.d.ts +2 -0
  13. package/dist/types/src/cli/commands/index.d.ts +3 -0
  14. package/dist/types/src/cli/index.d.ts +2 -0
  15. package/dist/types/src/cli/types/commands.d.ts +11 -0
  16. package/dist/types/src/cli/types/index.d.ts +1 -0
  17. package/dist/types/src/cli/utils/commands.d.ts +5 -0
  18. package/dist/types/src/cli/utils/index.d.ts +1 -0
  19. package/dist/types/src/compile/expressions.d.ts +76 -0
  20. package/dist/types/src/compile/index.d.ts +44 -0
  21. package/dist/types/src/compile/logic.d.ts +101 -0
  22. package/dist/types/src/manifest.d.ts +148 -0
  23. package/dist/types/src/marker.d.ts +85 -0
  24. package/dist/types/src/parser/index.d.ts +2 -0
  25. package/dist/types/src/parser/layout.d.ts +32 -0
  26. package/dist/types/src/parser/lsp.d.ts +56 -0
  27. package/dist/types/src/parser/route.d.ts +87 -0
  28. package/dist/types/src/parser/style-source-node.d.ts +19 -0
  29. package/dist/types/src/parser/style.d.ts +20 -0
  30. package/dist/types/src/parser/template.d.ts +32 -0
  31. package/dist/types/src/parser/test.d.ts +1 -0
  32. package/dist/types/src/runtime/bindings/BindingEvaluator.d.ts +174 -0
  33. package/dist/types/src/runtime/bindings/index.d.ts +2 -0
  34. package/dist/types/src/runtime/compiler/ImportTransformer.d.ts +11 -0
  35. package/dist/types/src/runtime/compiler/index.d.ts +1 -0
  36. package/dist/types/src/runtime/components/ComponentInstance.d.ts +110 -0
  37. package/dist/types/src/runtime/components/ComponentRegistry.d.ts +11 -0
  38. package/dist/types/src/runtime/components/index.d.ts +3 -0
  39. package/dist/types/src/runtime/components/types/index.d.ts +34 -0
  40. package/dist/types/src/runtime/core.d.ts +251 -0
  41. package/dist/types/src/runtime/diagnostics.d.ts +33 -0
  42. package/dist/types/src/runtime/dom/ForBlock.d.ts +127 -0
  43. package/dist/types/src/runtime/dom/Mounter.d.ts +223 -0
  44. package/dist/types/src/runtime/dom/StyleManager.d.ts +57 -0
  45. package/dist/types/src/runtime/dom/hydration.d.ts +35 -0
  46. package/dist/types/src/runtime/dom/index.d.ts +7 -0
  47. package/dist/types/src/runtime/events/EventBinder.d.ts +18 -0
  48. package/dist/types/src/runtime/events/index.d.ts +1 -0
  49. package/dist/types/src/runtime/globals/GlobalFunctions.d.ts +24 -0
  50. package/dist/types/src/runtime/globals/index.d.ts +1 -0
  51. package/dist/types/src/runtime/helpers.d.ts +141 -0
  52. package/dist/types/src/runtime/index.d.ts +16 -0
  53. package/dist/types/src/runtime/reactive/ReactiveProxy.d.ts +63 -0
  54. package/dist/types/src/runtime/reactive/UpdateScheduler.d.ts +9 -0
  55. package/dist/types/src/runtime/reactive/index.d.ts +2 -0
  56. package/dist/types/src/runtime/signals/SignalManager.d.ts +10 -0
  57. package/dist/types/src/runtime/signals/index.d.ts +3 -0
  58. package/dist/types/src/runtime/signals/signals.d.ts +16 -0
  59. package/dist/types/src/runtime/signals/types/index.d.ts +1 -0
  60. package/dist/types/src/runtime/signals/types/signals.d.ts +7 -0
  61. package/dist/types/src/runtime/state/Cell.d.ts +68 -0
  62. package/dist/types/src/runtime/state/CellManager.d.ts +101 -0
  63. package/dist/types/src/runtime/state/index.d.ts +4 -0
  64. package/dist/types/src/specifiers.d.ts +16 -0
  65. package/dist/types/src/ssg/index.d.ts +21 -0
  66. package/dist/types/src/ssg/rebuild.d.ts +49 -0
  67. package/dist/types/src/ssg/render.d.ts +104 -0
  68. package/dist/types/src/types/lsp.d.ts +22 -0
  69. package/dist/types/src/types/optional.d.ts +14 -0
  70. package/dist/types/src/types/virtual.d.ts +4 -0
  71. package/dist/types/src/vite/csp.d.ts +12 -0
  72. package/dist/types/src/vite/index.d.ts +2 -0
  73. package/dist/types/src/vite/layout.d.ts +17 -0
  74. package/dist/types/src/vite/plugin.d.ts +69 -0
  75. package/dist/types/src/vite/routes.d.ts +139 -0
  76. package/dist/types/test/adopted-styles.test.d.ts +1 -0
  77. package/dist/types/test/adoption.test.d.ts +1 -0
  78. package/dist/types/test/agent.test.d.ts +1 -0
  79. package/dist/types/test/audit.test.d.ts +1 -0
  80. package/dist/types/test/bindings.test.d.ts +1 -0
  81. package/dist/types/test/branches-in-loops.test.d.ts +1 -0
  82. package/dist/types/test/build.test.d.ts +1 -0
  83. package/dist/types/test/buildtime.test.d.ts +1 -0
  84. package/dist/types/test/built.test.d.ts +1 -0
  85. package/dist/types/test/cell-props.test.d.ts +1 -0
  86. package/dist/types/test/comments.test.d.ts +1 -0
  87. package/dist/types/test/compile-expressions.test.d.ts +1 -0
  88. package/dist/types/test/compile-logic.test.d.ts +1 -0
  89. package/dist/types/test/components.test.d.ts +1 -0
  90. package/dist/types/test/composition.test.d.ts +1 -0
  91. package/dist/types/test/declared-needs.test.d.ts +1 -0
  92. package/dist/types/test/diagnostics.test.d.ts +1 -0
  93. package/dist/types/test/dynamic.test.d.ts +1 -0
  94. package/dist/types/test/example.test.d.ts +1 -0
  95. package/dist/types/test/faq.test.d.ts +1 -0
  96. package/dist/types/test/flatten-nested.test.d.ts +1 -0
  97. package/dist/types/test/html-binding.test.d.ts +1 -0
  98. package/dist/types/test/hydration-report.test.d.ts +1 -0
  99. package/dist/types/test/hydration.test.d.ts +1 -0
  100. package/dist/types/test/layout-resolution.test.d.ts +1 -0
  101. package/dist/types/test/layout.test.d.ts +1 -0
  102. package/dist/types/test/lazy.test.d.ts +1 -0
  103. package/dist/types/test/manifest.test.d.ts +1 -0
  104. package/dist/types/test/model-props.test.d.ts +1 -0
  105. package/dist/types/test/model.test.d.ts +1 -0
  106. package/dist/types/test/mounting.test.d.ts +1 -0
  107. package/dist/types/test/needs.test.d.ts +1 -0
  108. package/dist/types/test/nested-rows.test.d.ts +1 -0
  109. package/dist/types/test/on-mounted.test.d.ts +1 -0
  110. package/dist/types/test/on-update.test.d.ts +1 -0
  111. package/dist/types/test/page-island.test.d.ts +1 -0
  112. package/dist/types/test/pages.test.d.ts +1 -0
  113. package/dist/types/test/reactivity.test.d.ts +1 -0
  114. package/dist/types/test/rebuild-keeps-logic.test.d.ts +1 -0
  115. package/dist/types/test/rebuild.test.d.ts +1 -0
  116. package/dist/types/test/reenumerate.test.d.ts +1 -0
  117. package/dist/types/test/refresh.test.d.ts +1 -0
  118. package/dist/types/test/refs.test.d.ts +1 -0
  119. package/dist/types/test/reorder.test.d.ts +1 -0
  120. package/dist/types/test/route.test.d.ts +1 -0
  121. package/dist/types/test/routes-build.test.d.ts +1 -0
  122. package/dist/types/test/routes-dev.test.d.ts +1 -0
  123. package/dist/types/test/routes-emit.test.d.ts +1 -0
  124. package/dist/types/test/routes-runtime.test.d.ts +1 -0
  125. package/dist/types/test/setup.d.ts +59 -0
  126. package/dist/types/test/show.test.d.ts +1 -0
  127. package/dist/types/test/skeleton-adoption.test.d.ts +1 -0
  128. package/dist/types/test/slots.test.d.ts +1 -0
  129. package/dist/types/test/specifiers.test.d.ts +1 -0
  130. package/dist/types/test/ssg.test.d.ts +1 -0
  131. package/dist/types/test/state.test.d.ts +1 -0
  132. package/dist/types/test/static-islands.test.d.ts +1 -0
  133. package/dist/types/test/stats.test.d.ts +1 -0
  134. package/dist/types/test/structural.test.d.ts +1 -0
  135. package/dist/types/test/style-sources.test.d.ts +1 -0
  136. package/dist/types/test/styles.test.d.ts +1 -0
  137. package/dist/types/test/surgical-rebuild.test.d.ts +1 -0
  138. package/dist/types/test/tables.test.d.ts +1 -0
  139. package/dist/types/test/unmount.test.d.ts +1 -0
  140. package/dist/types/test/verbatim.test.d.ts +1 -0
  141. package/dist/vite.js +5655 -0
  142. package/package.json +64 -0
@@ -0,0 +1,1494 @@
1
+ // src/parser/lsp.ts
2
+ function parseLSP(content, filename) {
3
+ const view = extractSection(content, "view");
4
+ const skeleton = extractSection(content, "skeleton");
5
+ const logic = extractSection(content, "logic");
6
+ const styles = extractStyles(content);
7
+ return {
8
+ view,
9
+ skeleton,
10
+ logic,
11
+ needs: extractSection(content, "needs").split(/[,\n]/).map((key) => key.trim()).filter(Boolean),
12
+ ...styles,
13
+ filename
14
+ };
15
+ }
16
+ function inlineStyleSources(component, read) {
17
+ const join = (sources, own) => {
18
+ if (!sources.length)
19
+ return own;
20
+ const shared = sources.map((source) => read(source, component.filename));
21
+ return [...shared, own].filter((block) => block.trim()).join(`
22
+
23
+ `);
24
+ };
25
+ return {
26
+ ...component,
27
+ style: join(component.styleSources, component.style),
28
+ styleDeferred: join(component.styleDeferredSources, component.styleDeferred)
29
+ };
30
+ }
31
+ function extractSection(content, sectionName) {
32
+ const regex = new RegExp(`<${sectionName}[^>]*>([\\s\\S]*?)<\\/${sectionName}>`, "i");
33
+ const match = content.match(regex);
34
+ return match ? match[1].trim() : "";
35
+ }
36
+ function extractStyles(content) {
37
+ const blocks = content.matchAll(/<style([^>]*)>([\s\S]*?)<\/style>/gi);
38
+ const critical = [];
39
+ const deferred = [];
40
+ const criticalSources = [];
41
+ const deferredSources = [];
42
+ for (const [, attributes, body] of blocks) {
43
+ const isDeferred = /\bdeferred\b/i.test(attributes);
44
+ const src = attributes.match(/\bsrc\s*=\s*["']([^"']+)["']/i);
45
+ if (src)
46
+ (isDeferred ? deferredSources : criticalSources).push(src[1]);
47
+ (isDeferred ? deferred : critical).push(body.trim());
48
+ }
49
+ return {
50
+ style: critical.join(`
51
+
52
+ `),
53
+ styleDeferred: deferred.join(`
54
+
55
+ `),
56
+ styleSources: criticalSources,
57
+ styleDeferredSources: deferredSources
58
+ };
59
+ }
60
+ // src/runtime/diagnostics.ts
61
+ var PREFIX = "[lightspeed]";
62
+ function warn(message, ...detail) {
63
+ console.warn(`${PREFIX} ${message}`, ...detail);
64
+ }
65
+ function fail(message, ...detail) {
66
+ console.error(`${PREFIX} ${message}`, ...detail);
67
+ }
68
+
69
+ // src/marker.ts
70
+ var START = "lsp ";
71
+ var END = "/lsp";
72
+ var MARKER_END = `<!--${END}-->`;
73
+ function serializeMarker(payload) {
74
+ const json = JSON.stringify(payload).replace(/-(?=-)/g, "\\u002d");
75
+ return `<!--${START}${json}-->`;
76
+ }
77
+
78
+ // src/parser/template.ts
79
+ function transformTemplate(template, componentId, componentRegistry) {
80
+ const comments = protectComments(template);
81
+ const verbatim = protectVerbatim(comments.text, componentRegistry);
82
+ let transformed = verbatim.text;
83
+ transformed = hoistBlockDirectives(transformed);
84
+ transformed = emitMountPlaceholders(transformed, componentRegistry);
85
+ for (const [stray] of transformed.matchAll(/\$cell:[\w.-]*="[^"]*"/g)) {
86
+ warn(`${stray} is only meaningful on a component tag - ` + `an element reads a cell with {{ shared['key'] }} or $attr="shared['key']".`);
87
+ }
88
+ transformed = transformed.replace(/@(\w+)="([^"]+)"/g, 'data-on-$1="$2"');
89
+ transformed = transformed.replace(/\$needs="([^"]*)"/g, 'data-lsp-needs="$1"');
90
+ transformed = transformed.replace(/\$shimmer="([^"]*)"/g, 'data-lsp-shimmer="$1"');
91
+ transformed = transformed.replace(/\$shimmer(?=[\s/>])/g, 'data-lsp-shimmer=""');
92
+ transformed = transformed.replace(/\$needs(?=[\s/>])/g, 'data-lsp-needs=""');
93
+ transformed = transformed.replace(/\$ref="([^"]+)"/g, 'data-lsp-ref="$1"');
94
+ transformed = transformed.replace(/\$text="([^"]+)"/g, 'data-text="$1"');
95
+ transformed = transformed.replace(/\$([\w-]+)="([^"]+)"/g, 'data-bind-$1="$2"');
96
+ transformed = transformed.replace(/\$else(?=[\s/>])/g, "data-bind-else");
97
+ transformed = transformed.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_match, expression) => `<span data-text="${expression.trim()}"></span>`);
98
+ return comments.restore(verbatim.restore(transformed));
99
+ }
100
+ function protectComments(template) {
101
+ const stash = [];
102
+ const text = template.replace(/<!--[\s\S]*?-->/g, (comment) => {
103
+ stash.push(comment);
104
+ return `<!--lsp-comment-${stash.length - 1}-->`;
105
+ });
106
+ return {
107
+ text,
108
+ restore: (html) => html.replace(/<!--lsp-comment-(\d+)-->/g, (_match, index) => stash[Number(index)])
109
+ };
110
+ }
111
+ var VERBATIM_TAGS = /<(pre|code)\b[^>]*>[\s\S]*?<\/\1\s*>/gi;
112
+ var RAW_SLOT = /<slot\b[^>]*\braw\b[^>]*>/i;
113
+ function protectVerbatim(template, componentRegistry) {
114
+ const stash = [];
115
+ const hold = (value) => {
116
+ stash.push(value);
117
+ return `<!--lsp-verbatim-${stash.length - 1}-->`;
118
+ };
119
+ let text = template.replace(VERBATIM_TAGS, hold);
120
+ if (componentRegistry) {
121
+ text = text.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*>([\s\S]*?)<\/\1\s*>/g, (match, name, attributes, content) => {
122
+ const definition = componentRegistry[name.toLowerCase()];
123
+ if (!definition || !RAW_SLOT.test(definition.template ?? "")) {
124
+ return match;
125
+ }
126
+ return `<${name}${attributes}>${hold(content)}</${name}>`;
127
+ });
128
+ }
129
+ const restore = (html) => {
130
+ let result = html;
131
+ for (let pass = 0;pass < 10; pass++) {
132
+ const next = result.replace(/<!--lsp-verbatim-(\d+)-->/g, (_match, index) => stash[Number(index)] ?? "");
133
+ if (next === result)
134
+ return result;
135
+ result = next;
136
+ }
137
+ return result;
138
+ };
139
+ return { text, restore };
140
+ }
141
+ function hoistBlockDirectives(template) {
142
+ const wrap = (tag, attributes, rebuild) => {
143
+ const { block, rest } = splitBlockDirectives(attributes);
144
+ if (!block.length)
145
+ return tag;
146
+ return `<template ${block.join(" ")}>${rebuild(rest)}</template>`;
147
+ };
148
+ let result = template.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*\/>/g, (match, name, attributes) => wrap(match, attributes || "", (rest) => `<${name}${rest} />`));
149
+ result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*>([\s\S]*?)<\/\1\s*>/g, (match, name, attributes, content) => wrap(match, attributes || "", (rest) => `<${name}${rest}>${content}</${name}>`));
150
+ return result;
151
+ }
152
+ function splitBlockDirectives(attributes) {
153
+ const block = [];
154
+ let rest = attributes;
155
+ for (const name of ["for", "if", "else-if"]) {
156
+ const pattern = new RegExp(`\\s\\$${name}="([^"]*)"`);
157
+ const match = rest.match(pattern);
158
+ if (!match)
159
+ continue;
160
+ block.push(`data-bind-${name}="${match[1]}"`);
161
+ rest = rest.replace(pattern, "");
162
+ }
163
+ const valueless = /\s\$else(?=[\s/>])/;
164
+ if (valueless.test(rest)) {
165
+ block.push("data-bind-else");
166
+ rest = rest.replace(valueless, "");
167
+ }
168
+ const key = /\s\$key="([^"]*)"/;
169
+ const keyMatch = rest.match(key);
170
+ if (keyMatch && block.length) {
171
+ block.push(`data-bind-key="${keyMatch[1]}"`);
172
+ rest = rest.replace(key, "");
173
+ }
174
+ return { block, rest };
175
+ }
176
+ function emitMountPlaceholders(template, componentRegistry, depth = 0) {
177
+ if (depth > 10) {
178
+ console.warn("Max nesting reached in component placeholders - possible circular reference");
179
+ return template;
180
+ }
181
+ const warnUnknown = (name) => {
182
+ if (name.toLowerCase() === "dynamic")
183
+ return;
184
+ if (componentRegistry && !componentRegistry[name.toLowerCase()]) {
185
+ console.warn(`Component not found: ${name}`);
186
+ }
187
+ };
188
+ let result = template;
189
+ result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*\/>/g, (_match, name, attributes) => {
190
+ warnUnknown(name);
191
+ return buildPlaceholder(name, attributes || "", "", componentRegistry?.[name.toLowerCase()]?.needs);
192
+ });
193
+ result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*>([\s\S]*?)<\/\1\s*>/g, (_match, name, attributes, content) => {
194
+ warnUnknown(name);
195
+ const inner = emitMountPlaceholders(content, componentRegistry, depth + 1);
196
+ return buildPlaceholder(name, attributes || "", inner, componentRegistry?.[name.toLowerCase()]?.needs);
197
+ });
198
+ return result;
199
+ }
200
+ function splitCellKeys(value) {
201
+ if (!value)
202
+ return [];
203
+ return value.split(",").map((key) => key.trim()).filter(Boolean);
204
+ }
205
+ var DYNAMIC_TAG = "dynamic";
206
+ var CELL_PROP = "cell:";
207
+ var BARE_CELL_READ = /^shared\s*(?:\[\s*(['"])([^'"]+)\1\s*\]|\.([A-Za-z_$][\w$]*))$/;
208
+ var nudged = new Set;
209
+ function nudgeCellProps(componentId, payload) {
210
+ for (const [name, expr] of Object.entries(payload.bind ?? {})) {
211
+ if (name === "class" || name === "style")
212
+ continue;
213
+ const match = BARE_CELL_READ.exec(expr.trim());
214
+ if (!match)
215
+ continue;
216
+ const key = match[2] ?? match[3];
217
+ const said = `${componentId}|${name}|${expr}`;
218
+ if (nudged.has(said))
219
+ continue;
220
+ nudged.add(said);
221
+ warn(`<${componentId} $${name}="${expr}"> is delivered by whoever mounts it. ` + `Write $cell:${name}="${key}" and the child subscribes to the cell itself.`);
222
+ }
223
+ }
224
+ function buildPlaceholder(name, attributes, content, declared) {
225
+ const payload = splitAttributes(name.toLowerCase(), attributes);
226
+ if (declared?.length) {
227
+ const seen = new Set(declared);
228
+ payload.needs = [
229
+ ...declared,
230
+ ...(payload.needs ?? []).filter((key) => !seen.has(key))
231
+ ];
232
+ }
233
+ nudgeCellProps(name.toLowerCase(), payload);
234
+ if (payload.c === DYNAMIC_TAG) {
235
+ const is = payload.bind?.is;
236
+ delete payload.bind?.is;
237
+ if (payload.bind && !Object.keys(payload.bind).length)
238
+ delete payload.bind;
239
+ if (!is) {
240
+ warn('<Dynamic> needs $is, e.g. <Dynamic $is="field.type" />');
241
+ }
242
+ payload.c = "";
243
+ payload.dyn = is ?? "";
244
+ }
245
+ return serializeMarker(payload) + content + MARKER_END;
246
+ }
247
+ function splitAttributes(componentId, attrString) {
248
+ const payload = { c: componentId };
249
+ if (!attrString.trim())
250
+ return payload;
251
+ const attrRegex = /([@$]?[A-Za-z_:][\w:.-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
252
+ let match;
253
+ const put = (bag, key, value) => {
254
+ const target = payload[bag] ??= {};
255
+ target[key] = value;
256
+ };
257
+ while ((match = attrRegex.exec(attrString)) !== null) {
258
+ const [, name, doubleQuoted, singleQuoted, unquoted] = match;
259
+ const value = doubleQuoted ?? singleQuoted ?? unquoted;
260
+ if (name.startsWith("@")) {
261
+ put("on", name.slice(1), value ?? "");
262
+ continue;
263
+ }
264
+ if (name.startsWith("$")) {
265
+ const directive = name.slice(1);
266
+ if (directive === "hydrate")
267
+ payload.hydrate = value ?? "load";
268
+ else if (directive === "needs")
269
+ payload.needs = splitCellKeys(value);
270
+ else if (directive.startsWith(CELL_PROP)) {
271
+ const propName = directive.slice(CELL_PROP.length).trim();
272
+ const key = (value ?? "").trim();
273
+ if (!propName) {
274
+ warn(`<${componentId}> has $cell: with no prop name - e.g. $cell:count="ui:clicks"`);
275
+ } else if (!key) {
276
+ warn(`<${componentId}> $cell:${propName} needs a cell key, e.g. $cell:${propName}="ui:clicks"`);
277
+ } else {
278
+ put("cells", propName, key);
279
+ }
280
+ } else
281
+ put("bind", directive, value ?? "");
282
+ continue;
283
+ }
284
+ if (name === "class" || name === "style") {
285
+ put("attrs", name, value ?? "");
286
+ continue;
287
+ }
288
+ put("props", name, value === undefined ? true : value);
289
+ }
290
+ return payload;
291
+ }
292
+
293
+ // src/parser/style.ts
294
+ function scopeIdFor(componentId) {
295
+ let hash = 5381;
296
+ for (let i = 0;i < componentId.length; i++) {
297
+ hash = (hash << 5) + hash + componentId.charCodeAt(i) >>> 0;
298
+ }
299
+ return `lsp-${hash.toString(36)}`;
300
+ }
301
+ function scopeStyles(css, scopeId) {
302
+ if (!css.trim())
303
+ return css;
304
+ const attribute = `[data-lsp~="${scopeId}"]`;
305
+ const scopes = {
306
+ normal: `:where(${attribute})`,
307
+ deep: `${attribute}${attribute}`
308
+ };
309
+ const renames = collectKeyframeRenames(css, scopeId);
310
+ return transformRules(css, scopes, scopeId, renames);
311
+ }
312
+ function collectKeyframeRenames(css, scopeId) {
313
+ const renames = new Map;
314
+ const pattern = /@[-\w]*keyframes\s+([\w-]+)/gi;
315
+ let match;
316
+ while ((match = pattern.exec(css)) !== null) {
317
+ renames.set(match[1], `${match[1]}-${scopeId}`);
318
+ }
319
+ return renames;
320
+ }
321
+ var NESTED_AT_RULES = new Set([
322
+ "@media",
323
+ "@supports",
324
+ "@container",
325
+ "@layer",
326
+ "@scope"
327
+ ]);
328
+ function transformRules(css, scopes, scopeId, renames) {
329
+ let out = "";
330
+ let i = 0;
331
+ while (i < css.length) {
332
+ const prelude = readPrelude(css, i);
333
+ i = prelude.end;
334
+ if (i >= css.length) {
335
+ out += prelude.text;
336
+ break;
337
+ }
338
+ if (css[i] === ";") {
339
+ out += `${prelude.text};`;
340
+ i++;
341
+ continue;
342
+ }
343
+ const bodyEnd = matchBrace(css, i);
344
+ const body = css.slice(i + 1, bodyEnd);
345
+ i = bodyEnd + 1;
346
+ const trimmed = prelude.text.trim();
347
+ if (trimmed.startsWith("@")) {
348
+ const name = trimmed.split(/[\s({]/)[0].toLowerCase();
349
+ if (name.endsWith("keyframes")) {
350
+ out += `${renameKeyframe(prelude.text, scopeId)}{${body}}`;
351
+ } else if (NESTED_AT_RULES.has(name)) {
352
+ out += `${prelude.text}{${transformRules(body, scopes, scopeId, renames)}}`;
353
+ } else {
354
+ out += `${prelude.text}{${body}}`;
355
+ }
356
+ continue;
357
+ }
358
+ out += `${scopeSelectorList(prelude.text, scopes)}{${rewriteAnimations(body, renames)}}`;
359
+ }
360
+ return out;
361
+ }
362
+ function renameKeyframe(prelude, scopeId) {
363
+ return prelude.replace(/(@[-\w]*keyframes\s+)([\w-]+)/i, (_match, keyword, name) => `${keyword}${name}-${scopeId}`);
364
+ }
365
+ function rewriteAnimations(body, renames) {
366
+ if (renames.size === 0)
367
+ return body;
368
+ return body.replace(/(animation(?:-name)?\s*:)([^;}]*)/gi, (_match, property, value) => {
369
+ let next = value;
370
+ for (const [from, to] of renames) {
371
+ next = next.replace(new RegExp(`\\b${escapeRegExp(from)}\\b`, "g"), to);
372
+ }
373
+ return property + next;
374
+ });
375
+ }
376
+ function readPrelude(css, start) {
377
+ let text = "";
378
+ let i = start;
379
+ let depth = 0;
380
+ while (i < css.length) {
381
+ const char = css[i];
382
+ if (char === "/" && css[i + 1] === "*") {
383
+ const close = css.indexOf("*/", i + 2);
384
+ const stop = close < 0 ? css.length : close + 2;
385
+ text += css.slice(i, stop);
386
+ i = stop;
387
+ continue;
388
+ }
389
+ if (char === '"' || char === "'") {
390
+ const stop = skipString(css, i);
391
+ text += css.slice(i, stop);
392
+ i = stop;
393
+ continue;
394
+ }
395
+ if (char === "(")
396
+ depth++;
397
+ if (char === ")")
398
+ depth--;
399
+ if (depth === 0 && (char === "{" || char === ";"))
400
+ break;
401
+ text += char;
402
+ i++;
403
+ }
404
+ return { text, end: i };
405
+ }
406
+ function skipString(css, start) {
407
+ const quote = css[start];
408
+ let i = start + 1;
409
+ while (i < css.length) {
410
+ if (css[i] === "\\") {
411
+ i += 2;
412
+ continue;
413
+ }
414
+ if (css[i] === quote)
415
+ return i + 1;
416
+ i++;
417
+ }
418
+ return i;
419
+ }
420
+ function matchBrace(css, openIndex) {
421
+ let depth = 0;
422
+ let i = openIndex;
423
+ while (i < css.length) {
424
+ const char = css[i];
425
+ if (char === "/" && css[i + 1] === "*") {
426
+ const close = css.indexOf("*/", i + 2);
427
+ i = close < 0 ? css.length : close + 2;
428
+ continue;
429
+ }
430
+ if (char === '"' || char === "'") {
431
+ i = skipString(css, i);
432
+ continue;
433
+ }
434
+ if (char === "{")
435
+ depth++;
436
+ if (char === "}") {
437
+ depth--;
438
+ if (depth === 0)
439
+ return i;
440
+ }
441
+ i++;
442
+ }
443
+ return css.length - 1;
444
+ }
445
+ function scopeSelectorList(selectors, scopes) {
446
+ const comments = [];
447
+ const bare = selectors.replace(/\/\*[\s\S]*?\*\//g, (comment) => {
448
+ comments.push(comment);
449
+ return "";
450
+ });
451
+ const scoped = splitTopLevel(bare, ",").map((selector) => scopeSelector(selector, scopes)).join(",");
452
+ return comments.join("") + scoped;
453
+ }
454
+ function scopeSelector(selector, scopes) {
455
+ const leading = selector.match(/^\s*/)?.[0] ?? "";
456
+ const trailing = selector.match(/\s*$/)?.[0] ?? "";
457
+ const trimmed = selector.trim();
458
+ if (!trimmed)
459
+ return selector;
460
+ const parts = splitCompounds(trimmed);
461
+ const deepIndex = parts.findIndex((part) => part.deep);
462
+ if (deepIndex >= 0) {
463
+ if (deepIndex === 0) {
464
+ const rendered3 = parts.map((part) => part.combinator + unwrap(part.text)).join("").trimStart();
465
+ return `${leading}${scopes.deep} ${rendered3}${trailing}`;
466
+ }
467
+ let anchor = -1;
468
+ for (let i = deepIndex - 1;i >= 0; i--) {
469
+ if (!parts[i].global) {
470
+ anchor = i;
471
+ break;
472
+ }
473
+ }
474
+ const rendered2 = parts.map((part, index) => {
475
+ const text = unwrap(part.text);
476
+ return part.combinator + (index === anchor ? insertScope(text, scopes.deep) : text);
477
+ });
478
+ return leading + rendered2.join("") + trailing;
479
+ }
480
+ let targetIndex = -1;
481
+ for (let i = parts.length - 1;i >= 0; i--) {
482
+ if (!parts[i].global) {
483
+ targetIndex = i;
484
+ break;
485
+ }
486
+ }
487
+ const rendered = parts.map((part, index) => {
488
+ const text = unwrap(part.text);
489
+ const scoped = index === targetIndex ? insertScope(text, scopes.normal) : text;
490
+ return part.combinator + scoped;
491
+ });
492
+ return leading + rendered.join("") + trailing;
493
+ }
494
+ function unwrap(text) {
495
+ return text.replace(/:(?:global|deep)\(\s*([\s\S]*?)\s*\)/g, "$1");
496
+ }
497
+ function insertScope(compound, attribute) {
498
+ let depth = 0;
499
+ for (let i = 0;i < compound.length; i++) {
500
+ const char = compound[i];
501
+ if (char === "(" || char === "[")
502
+ depth++;
503
+ if (char === ")" || char === "]")
504
+ depth--;
505
+ if (char === ":" && depth === 0) {
506
+ return compound.slice(0, i) + attribute + compound.slice(i);
507
+ }
508
+ }
509
+ return compound + attribute;
510
+ }
511
+ function splitCompounds(selector) {
512
+ const parts = [];
513
+ let depth = 0;
514
+ let current = "";
515
+ let combinator = "";
516
+ let i = 0;
517
+ const push = () => {
518
+ if (!current)
519
+ return;
520
+ parts.push({
521
+ combinator,
522
+ text: current,
523
+ global: /^:global\(/.test(current.trim()),
524
+ deep: /^:deep\(/.test(current.trim())
525
+ });
526
+ current = "";
527
+ combinator = "";
528
+ };
529
+ while (i < selector.length) {
530
+ const char = selector[i];
531
+ if (char === "(" || char === "[")
532
+ depth++;
533
+ if (char === ")" || char === "]")
534
+ depth--;
535
+ if (depth === 0 && /[\s>+~]/.test(char)) {
536
+ let separator = "";
537
+ while (i < selector.length && /[\s>+~]/.test(selector[i]) && depth === 0) {
538
+ separator += selector[i];
539
+ i++;
540
+ }
541
+ push();
542
+ combinator = separator;
543
+ continue;
544
+ }
545
+ current += char;
546
+ i++;
547
+ }
548
+ push();
549
+ return parts;
550
+ }
551
+ function splitTopLevel(input, delimiter) {
552
+ const parts = [];
553
+ let depth = 0;
554
+ let current = "";
555
+ let i = 0;
556
+ while (i < input.length) {
557
+ const char = input[i];
558
+ if (char === '"' || char === "'") {
559
+ const stop = skipString(input, i);
560
+ current += input.slice(i, stop);
561
+ i = stop;
562
+ continue;
563
+ }
564
+ if (char === "(" || char === "[")
565
+ depth++;
566
+ if (char === ")" || char === "]")
567
+ depth--;
568
+ if (char === delimiter && depth === 0) {
569
+ parts.push(current);
570
+ current = "";
571
+ i++;
572
+ continue;
573
+ }
574
+ current += char;
575
+ i++;
576
+ }
577
+ parts.push(current);
578
+ return parts;
579
+ }
580
+ function escapeRegExp(value) {
581
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
582
+ }
583
+
584
+ // src/specifiers.ts
585
+ var SPECIFIER_PATTERN = "(?:@lightspeed-js\\/core|lightspeed)";
586
+
587
+ // src/compile/logic.ts
588
+ var CONTEXT_KEYS = [
589
+ "_componentInstance",
590
+ "__lightspeedRuntime",
591
+ "_getComponent",
592
+ "_addHandler"
593
+ ];
594
+ var LIGHTSPEED_NAMED = new RegExp(`^import\\s*\\{([^}]+)\\}\\s*from\\s*['"]${SPECIFIER_PATTERN}['"]\\s*;?\\s*$`);
595
+ var DEFAULT_IMPORT = /^import\s+([A-Za-z_$][\w$]*)\s+from\s*['"]([^'"]+)['"]\s*;?\s*$/;
596
+ var ANY_IMPORT = /^import\s.+from\s*['"][^'"]+['"]\s*;?\s*$|^import\s*['"][^'"]+['"]\s*;?\s*$/;
597
+ function componentIdFrom(path) {
598
+ const file = path.split("/").pop() ?? path;
599
+ return file.replace(/\.lsp$/, "").toLowerCase();
600
+ }
601
+ function joinWrappedImports(logic) {
602
+ const out = [];
603
+ let pending = [];
604
+ const STARTS = /^import\s+[^(]/;
605
+ const ENDS = /from\s*['"][^'"]*['"]\s*;?$|^import\s*['"][^'"]*['"]\s*;?$/;
606
+ for (const line of logic.split(`
607
+ `)) {
608
+ const trimmed = line.trim();
609
+ if (pending.length) {
610
+ pending.push(trimmed);
611
+ if (ENDS.test(trimmed)) {
612
+ out.push(pending.join(" "), ...pending.slice(1).map(() => ""));
613
+ pending = [];
614
+ }
615
+ continue;
616
+ }
617
+ if (STARTS.test(trimmed) && !ANY_IMPORT.test(trimmed)) {
618
+ pending.push(trimmed);
619
+ continue;
620
+ }
621
+ out.push(line);
622
+ }
623
+ out.push(...pending);
624
+ return out.join(`
625
+ `);
626
+ }
627
+ function compileLogic(logic) {
628
+ const imports = [];
629
+ const bindings = [];
630
+ const components = [];
631
+ const prelude = [];
632
+ const rest = [];
633
+ for (const line of joinWrappedImports(logic).split(`
634
+ `)) {
635
+ const trimmed = line.trim();
636
+ const named = trimmed.match(LIGHTSPEED_NAMED);
637
+ if (named) {
638
+ for (const raw of named[1].split(",")) {
639
+ const name = raw.trim();
640
+ if (!name)
641
+ continue;
642
+ const [source, local = source] = name.split(/\s+as\s+/).map((s) => s.trim());
643
+ prelude.push(`const ${local} = __lightspeedRuntime.${source};`);
644
+ }
645
+ continue;
646
+ }
647
+ const asDefault = trimmed.match(DEFAULT_IMPORT);
648
+ if (asDefault && asDefault[2].endsWith(".lsp")) {
649
+ const id = componentIdFrom(asDefault[2]);
650
+ components.push(id);
651
+ prelude.push(`const ${asDefault[1]} = _getComponent(${JSON.stringify(id)});`);
652
+ continue;
653
+ }
654
+ if (ANY_IMPORT.test(trimmed)) {
655
+ imports.push(trimmed);
656
+ bindings.push(parseImport(trimmed));
657
+ continue;
658
+ }
659
+ rest.push(line);
660
+ }
661
+ const body = [
662
+ `const { ${CONTEXT_KEYS.join(", ")} } = __ctx;`,
663
+ ...prelude,
664
+ "",
665
+ ...rest,
666
+ "",
667
+ captureEpilogue(declaredNames(logic))
668
+ ].join(`
669
+ `);
670
+ return { imports, bindings, body, components };
671
+ }
672
+ function declaredNames(logic) {
673
+ const names = new Set;
674
+ const patterns = [
675
+ /^(?:export\s+)?(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)/gm,
676
+ /^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/gm
677
+ ];
678
+ for (const pattern of patterns) {
679
+ let match;
680
+ while ((match = pattern.exec(logic)) !== null)
681
+ names.add(match[1]);
682
+ }
683
+ return [...names];
684
+ }
685
+ function captureEpilogue(names) {
686
+ const handlers = names.map((name) => ` if (typeof ${name} === "function") _addHandler(${JSON.stringify(name)}, ${name});`).join(`
687
+ `);
688
+ return [
689
+ "// Hand the component's state and handlers to the runtime. Captured after",
690
+ "// the body runs rather than rewritten in place: a regex over an",
691
+ "// initialiser could not survive nested braces, which is exactly what a",
692
+ "// list of objects is.",
693
+ 'if (typeof state !== "undefined") _componentInstance.state = state;',
694
+ handlers
695
+ ].join(`
696
+ `);
697
+ }
698
+ function setupSource(compiled) {
699
+ return `export function setup(__ctx) {
700
+ ${compiled.body}
701
+ }`;
702
+ }
703
+ function parseImport(statement) {
704
+ const source = statement.match(/from\s*['"]([^'"]+)['"]/)?.[1] ?? statement.match(/^import\s*['"]([^'"]+)['"]/)?.[1] ?? "";
705
+ const names = [];
706
+ const clause = statement.match(/^import\s+([\s\S]+?)\s+from/)?.[1]?.trim();
707
+ if (clause) {
708
+ const star = clause.match(/^\*\s+as\s+([A-Za-z_$][\w$]*)$/);
709
+ if (star) {
710
+ names.push({ local: star[1], imported: "*" });
711
+ } else {
712
+ const braced = clause.match(/\{([^}]*)\}/)?.[1];
713
+ const bare = clause.replace(/\{[^}]*\}/, "").replace(/,/g, "").trim();
714
+ if (bare)
715
+ names.push({ local: bare, imported: "default" });
716
+ for (const raw of (braced ?? "").split(",")) {
717
+ const entry = raw.trim();
718
+ if (!entry)
719
+ continue;
720
+ const [imported, local = imported] = entry.split(/\s+as\s+/).map((s) => s.trim());
721
+ names.push({ local, imported });
722
+ }
723
+ }
724
+ }
725
+ return { source, names, statement };
726
+ }
727
+ async function buildSetup(logic, options = {}) {
728
+ if (!logic.trim())
729
+ return;
730
+ const compiled = compileLogic(options.transpile ? options.transpile(logic) : logic);
731
+ const locals = [];
732
+ const values = [];
733
+ for (const binding of compiled.bindings) {
734
+ if (!binding.names.length)
735
+ continue;
736
+ if (!options.load) {
737
+ throw new Error(`[lightspeed] Component logic imports "${binding.source}", but this ` + `build has no way to load project modules.`);
738
+ }
739
+ const namespace = await options.load(binding.source);
740
+ for (const name of binding.names) {
741
+ locals.push(name.local);
742
+ values.push(name.imported === "*" ? namespace : namespace[name.imported]);
743
+ }
744
+ }
745
+ const fn = new Function(...locals, "__ctx", compiled.body);
746
+ return (ctx) => fn(...values, ctx);
747
+ }
748
+ var RESERVED = new Set(["state", "props", "shared", "pending"]);
749
+ function handlerParams(logic) {
750
+ return declaredNames(logic).filter((name) => !RESERVED.has(name));
751
+ }
752
+
753
+ // src/runtime/bindings/BindingEvaluator.ts
754
+ class BindingScope {
755
+ instance;
756
+ locals;
757
+ cleanup;
758
+ constructor(instance, locals, cleanup) {
759
+ this.instance = instance;
760
+ this.locals = locals;
761
+ this.cleanup = cleanup;
762
+ }
763
+ get id() {
764
+ return this.instance.id;
765
+ }
766
+ get state() {
767
+ return this.instance.state;
768
+ }
769
+ get props() {
770
+ return this.instance.props;
771
+ }
772
+ }
773
+ var BOOLEAN_ATTRIBUTES = new Set([
774
+ "allowfullscreen",
775
+ "async",
776
+ "autofocus",
777
+ "autoplay",
778
+ "checked",
779
+ "controls",
780
+ "default",
781
+ "defer",
782
+ "disabled",
783
+ "formnovalidate",
784
+ "hidden",
785
+ "inert",
786
+ "ismap",
787
+ "itemscope",
788
+ "loop",
789
+ "multiple",
790
+ "muted",
791
+ "novalidate",
792
+ "open",
793
+ "playsinline",
794
+ "readonly",
795
+ "required",
796
+ "reversed",
797
+ "selected"
798
+ ]);
799
+ var NO_LOCALS = [];
800
+ var SKIP = Symbol("skip");
801
+ var IDENTITY = (raw) => raw;
802
+ var TO_NUMBER = (raw) => raw === "" || raw == null ? undefined : Number(raw);
803
+ function resolveModelTarget(el) {
804
+ const tag = el.tagName;
805
+ if (tag === "SELECT")
806
+ return { prop: "value", event: "change", coerce: IDENTITY };
807
+ if (tag === "TEXTAREA")
808
+ return { prop: "value", event: "input", coerce: IDENTITY };
809
+ if (tag === "INPUT") {
810
+ const type = el.type;
811
+ if (type === "checkbox")
812
+ return { prop: "checked", event: "change", coerce: Boolean };
813
+ if (type === "radio")
814
+ return { prop: "value", event: "change", coerce: IDENTITY, radio: true };
815
+ if (type === "number" || type === "range")
816
+ return { prop: "value", event: "input", coerce: TO_NUMBER };
817
+ }
818
+ return { prop: "value", event: "input", coerce: IDENTITY };
819
+ }
820
+
821
+ class BindingEvaluator {
822
+ cells;
823
+ onPropChanged;
824
+ compiledCache = new Map;
825
+ writerCache = new Map;
826
+ constructor(cells, onPropChanged = () => {
827
+ }) {
828
+ this.cells = cells;
829
+ this.onPropChanged = onPropChanged;
830
+ }
831
+ expressions = new Map;
832
+ writers = new Map;
833
+ registerExpressions(expressions = {}, writers = {}) {
834
+ for (const [key, fn] of Object.entries(expressions)) {
835
+ this.expressions.set(key, fn);
836
+ }
837
+ for (const [key, fn] of Object.entries(writers)) {
838
+ this.writers.set(key, fn);
839
+ }
840
+ }
841
+ compile(expr, params) {
842
+ const compiled = this.expressions.get(`${params.join(",")}|${expr}`);
843
+ if (compiled)
844
+ return compiled;
845
+ fail(`No compiled expression for ${JSON.stringify(expr)}` + (params.length ? ` in scope [${params.join(", ")}]` : ""));
846
+ return () => {
847
+ return;
848
+ };
849
+ }
850
+ compileWriter(expr, params) {
851
+ const writer = this.writers.get(`${params.join(",")}|${expr}`);
852
+ if (writer)
853
+ return writer;
854
+ warn(`$model needs an assignable expression, got "${expr}" - it will render but not write back.`);
855
+ return () => {
856
+ return;
857
+ };
858
+ }
859
+ createBinding(el, kind, expr, attr, params = []) {
860
+ return { el, kind, attr, expr, compiled: this.compile(expr, params) };
861
+ }
862
+ createPropBinding(target, name, expr, params = []) {
863
+ return {
864
+ kind: "prop",
865
+ target,
866
+ name,
867
+ expr,
868
+ compiled: this.compile(expr, params)
869
+ };
870
+ }
871
+ bindCellProp(target, name, key) {
872
+ const cell = this.cells.get(key);
873
+ if (!cell.pending)
874
+ target.props[name] = cell.value;
875
+ target.runtimeCells.add(key);
876
+ const unsubscribe = cell.subscribe((value) => {
877
+ if (Object.is(target.props[name], value))
878
+ return;
879
+ target.props[name] = value;
880
+ this.onPropChanged(target);
881
+ });
882
+ target.addLogicCleanup(unsubscribe);
883
+ }
884
+ createModelBinding(el, expr, params = []) {
885
+ return {
886
+ kind: "model",
887
+ el,
888
+ expr,
889
+ target: resolveModelTarget(el),
890
+ compiled: this.compile(expr, params),
891
+ write: this.compileWriter(expr, params)
892
+ };
893
+ }
894
+ createForBinding(block, expr, params = []) {
895
+ return { kind: "for", block, expr, compiled: this.compile(expr, params) };
896
+ }
897
+ readProxy() {
898
+ return this.cells.createReadProxy();
899
+ }
900
+ pendingProbe(onRead) {
901
+ return (key) => {
902
+ onRead?.(key);
903
+ return this.cells.get(key).pending;
904
+ };
905
+ }
906
+ get cellManager() {
907
+ return this.cells;
908
+ }
909
+ compileExpression(expr, params = []) {
910
+ return this.compile(expr, params);
911
+ }
912
+ evaluate(expr, instance, locals = []) {
913
+ try {
914
+ return this.compile(expr, [])(instance.state, instance.props, this.cells.createReadProxy(), this.pendingProbe(), ...locals);
915
+ } catch (error) {
916
+ warn(`Error evaluating "${expr}" in ${instance.id}:`, error);
917
+ return;
918
+ }
919
+ }
920
+ applyBinding(binding, scope) {
921
+ this.apply(binding, scope);
922
+ }
923
+ updateInstance(instance) {
924
+ const scope = new BindingScope(instance, NO_LOCALS, instance);
925
+ for (const binding of instance.bindings) {
926
+ this.apply(binding, scope);
927
+ }
928
+ for (const block of instance.blocks) {
929
+ this.updateBlock(block);
930
+ }
931
+ }
932
+ updateRow(instance, row) {
933
+ const scope = new BindingScope(instance, row.locals, row);
934
+ for (const binding of row.bindings) {
935
+ this.apply(binding, scope);
936
+ }
937
+ for (const block of row.blocks) {
938
+ this.updateBlock(block);
939
+ }
940
+ }
941
+ updateBlock(block) {
942
+ block.rows.forEach((row) => {
943
+ for (let i = 0;i < block.parentLocals.length; i++) {
944
+ row.locals[i] = block.parentLocals[i];
945
+ }
946
+ this.updateRow(block.owner, row);
947
+ });
948
+ }
949
+ updateTree(instance) {
950
+ this.updateInstance(instance);
951
+ for (const child of instance.children) {
952
+ this.updateTree(child);
953
+ }
954
+ }
955
+ attachModel(binding, scope) {
956
+ const listener = () => {
957
+ const next = this.readFromDom(binding);
958
+ if (next === SKIP)
959
+ return;
960
+ try {
961
+ binding.write(scope.state, scope.props, this.cells.createReadProxy(), this.pendingProbe(), next, ...scope.locals);
962
+ } catch (error) {
963
+ warn(`Failed writing $model="${binding.expr}":`, error);
964
+ }
965
+ };
966
+ binding.el.addEventListener(binding.target.event, listener);
967
+ scope.cleanup.addCleanup(() => binding.el.removeEventListener(binding.target.event, listener));
968
+ }
969
+ readFromDom(binding) {
970
+ const el = binding.el;
971
+ if (binding.target.radio) {
972
+ return el.checked ? el.value : SKIP;
973
+ }
974
+ return binding.target.coerce(el[binding.target.prop]);
975
+ }
976
+ apply(binding, scope) {
977
+ const reads = new Set;
978
+ const shared = this.cells.createReadProxy((key) => reads.add(key));
979
+ const pending = this.pendingProbe((key) => reads.add(key));
980
+ let value;
981
+ try {
982
+ value = binding.compiled(scope.state, scope.props, shared, pending, ...scope.locals);
983
+ this.syncCellSubscriptions(binding, scope, reads);
984
+ } catch (error) {
985
+ warn(`Error evaluating "${binding.expr}" in ${scope.id}:`, error);
986
+ return;
987
+ }
988
+ if (binding.kind === "for") {
989
+ binding.block.schedule(binding.block.toItems(value));
990
+ return;
991
+ }
992
+ if (binding.kind === "model") {
993
+ this.applyModel(binding, value);
994
+ return;
995
+ }
996
+ if (binding.kind === "prop") {
997
+ const target = binding.target;
998
+ if (Object.is(target.props[binding.name], value))
999
+ return;
1000
+ target.props[binding.name] = value;
1001
+ this.onPropChanged(target);
1002
+ return;
1003
+ }
1004
+ switch (binding.kind) {
1005
+ case "text":
1006
+ binding.el.textContent = value == null ? "" : String(value);
1007
+ break;
1008
+ case "class":
1009
+ this.updateClasses(binding.el, value);
1010
+ break;
1011
+ case "show":
1012
+ setHidden(binding.el, !value);
1013
+ break;
1014
+ case "html":
1015
+ binding.el.innerHTML = value == null ? "" : String(value);
1016
+ break;
1017
+ case "attr": {
1018
+ const attr = binding.attr;
1019
+ const aria = attr.startsWith("aria-");
1020
+ if (BOOLEAN_ATTRIBUTES.has(attr) && !aria) {
1021
+ if (value)
1022
+ binding.el.setAttribute(attr, "");
1023
+ else
1024
+ binding.el.removeAttribute(attr);
1025
+ break;
1026
+ }
1027
+ if (value == null || value === false && !aria) {
1028
+ binding.el.removeAttribute(attr);
1029
+ } else if (aria) {
1030
+ binding.el.setAttribute(attr, String(value));
1031
+ } else {
1032
+ binding.el.setAttribute(attr, value === true ? "" : String(value));
1033
+ }
1034
+ break;
1035
+ }
1036
+ }
1037
+ }
1038
+ applyModel(binding, value) {
1039
+ const el = binding.el;
1040
+ const { prop, radio, coerce } = binding.target;
1041
+ if (radio) {
1042
+ const shouldCheck = String(value) === el.value;
1043
+ if (el.checked !== shouldCheck)
1044
+ el.checked = shouldCheck;
1045
+ return;
1046
+ }
1047
+ if (Object.is(coerce(el[prop]), value))
1048
+ return;
1049
+ if (prop === "checked") {
1050
+ el.checked = Boolean(value);
1051
+ } else {
1052
+ el.value = value == null ? "" : String(value);
1053
+ }
1054
+ }
1055
+ syncCellSubscriptions(binding, scope, reads) {
1056
+ const previous = binding.cellKeys;
1057
+ if (previous && sameKeys(previous, reads))
1058
+ return;
1059
+ binding.cellUnsubscribes?.forEach((unsubscribe) => unsubscribe());
1060
+ binding.cellKeys = reads;
1061
+ binding.cellUnsubscribes = [...reads].map((key) => this.cells.get(key).subscribe(() => this.apply(binding, scope)));
1062
+ if (!binding.cleanupRegistered) {
1063
+ binding.cleanupRegistered = true;
1064
+ scope.cleanup.addCleanup(() => {
1065
+ binding.cellUnsubscribes?.forEach((unsubscribe) => unsubscribe());
1066
+ binding.cellUnsubscribes = [];
1067
+ binding.cellKeys = undefined;
1068
+ });
1069
+ }
1070
+ }
1071
+ appliedClasses = new WeakMap;
1072
+ updateClasses(element, classObj) {
1073
+ if (typeof classObj === "string") {
1074
+ const previous = this.appliedClasses.get(element) ?? [];
1075
+ const next = classObj.split(/\s+/).filter(Boolean);
1076
+ for (const name of previous) {
1077
+ if (!next.includes(name))
1078
+ element.classList.remove(name);
1079
+ }
1080
+ for (const name of next)
1081
+ element.classList.add(name);
1082
+ this.appliedClasses.set(element, next);
1083
+ return;
1084
+ }
1085
+ if (typeof classObj === "object" && classObj !== null) {
1086
+ Object.entries(classObj).forEach(([className, shouldAdd]) => {
1087
+ if (shouldAdd) {
1088
+ element.classList.add(className);
1089
+ } else {
1090
+ element.classList.remove(className);
1091
+ }
1092
+ });
1093
+ }
1094
+ }
1095
+ }
1096
+ function sameKeys(a, b) {
1097
+ if (a.size !== b.size)
1098
+ return false;
1099
+ for (const key of a)
1100
+ if (!b.has(key))
1101
+ return false;
1102
+ return true;
1103
+ }
1104
+ var HIDDEN_ATTR = "data-lsp-hidden";
1105
+ function setHidden(el, hidden) {
1106
+ if (hidden)
1107
+ el.setAttribute(HIDDEN_ATTR, "");
1108
+ else
1109
+ el.removeAttribute(HIDDEN_ATTR);
1110
+ }
1111
+ // src/runtime/dom/ForBlock.ts
1112
+ function toIterable(value) {
1113
+ if (value == null)
1114
+ return [];
1115
+ if (Array.isArray(value))
1116
+ return value;
1117
+ if (typeof value === "number") {
1118
+ return Array.from({ length: Math.max(0, Math.floor(value)) }, (_, i) => i);
1119
+ }
1120
+ if (typeof value[Symbol.iterator] === "function")
1121
+ return [...value];
1122
+ if (typeof value === "object")
1123
+ return Object.values(value);
1124
+ return [];
1125
+ }
1126
+ var nextObjectId = 1;
1127
+ var objectIds = new WeakMap;
1128
+ function identityOf(value) {
1129
+ if (typeof value === "symbol")
1130
+ return value.toString();
1131
+ if (value !== null && typeof value === "object") {
1132
+ let id = objectIds.get(value);
1133
+ if (id === undefined) {
1134
+ id = nextObjectId++;
1135
+ objectIds.set(value, id);
1136
+ }
1137
+ return `o${id}`;
1138
+ }
1139
+ return `${typeof value}:${String(value)}`;
1140
+ }
1141
+
1142
+ class ForBlock {
1143
+ owner;
1144
+ anchor;
1145
+ templates;
1146
+ alias;
1147
+ params;
1148
+ parentLocals;
1149
+ keyFor;
1150
+ renderer;
1151
+ conditional;
1152
+ itemsExpr;
1153
+ rows = new Map;
1154
+ order = [];
1155
+ pendingItems = null;
1156
+ running = false;
1157
+ warnedDuplicates = false;
1158
+ constructor(owner, anchor, templates, alias, params, parentLocals, keyFor, renderer, conditional = false, itemsExpr = "") {
1159
+ this.owner = owner;
1160
+ this.anchor = anchor;
1161
+ this.templates = templates;
1162
+ this.alias = alias;
1163
+ this.params = params;
1164
+ this.parentLocals = parentLocals;
1165
+ this.keyFor = keyFor;
1166
+ this.renderer = renderer;
1167
+ this.conditional = conditional;
1168
+ this.itemsExpr = itemsExpr;
1169
+ }
1170
+ toItems(value) {
1171
+ if (this.conditional) {
1172
+ return Number.isInteger(value) && value >= 0 ? [value] : [];
1173
+ }
1174
+ return toIterable(value);
1175
+ }
1176
+ templateFor(item) {
1177
+ return this.conditional ? this.templates[item] : this.templates[0];
1178
+ }
1179
+ schedule(items) {
1180
+ this.pendingItems = items;
1181
+ if (this.running)
1182
+ return;
1183
+ this.running = true;
1184
+ this.drain();
1185
+ }
1186
+ async drain() {
1187
+ try {
1188
+ while (this.pendingItems !== null) {
1189
+ const items = this.pendingItems;
1190
+ this.pendingItems = null;
1191
+ await this.reconcile(items);
1192
+ }
1193
+ } finally {
1194
+ this.running = false;
1195
+ }
1196
+ }
1197
+ keysFor(items) {
1198
+ const occurrences = new Map;
1199
+ return items.map((item) => {
1200
+ const locals = [...this.parentLocals, item];
1201
+ const base = identityOf(this.keyFor ? this.keyFor(locals) : item);
1202
+ const seen = occurrences.get(base) ?? 0;
1203
+ occurrences.set(base, seen + 1);
1204
+ if (seen > 0 && !this.keyFor && !this.warnedDuplicates) {
1205
+ this.warnedDuplicates = true;
1206
+ {
1207
+ warn(`$for over duplicate values in "${this.alias}" - rows are matched by position among equal items. Add $key for exact identity.`);
1208
+ }
1209
+ }
1210
+ return `${base}#${seen}`;
1211
+ });
1212
+ }
1213
+ async reconcile(items) {
1214
+ const keys = this.keysFor(items);
1215
+ const next = new Map;
1216
+ for (let i = 0;i < items.length; i++) {
1217
+ const key = keys[i];
1218
+ const existing = this.rows.get(key);
1219
+ if (existing) {
1220
+ this.rows.delete(key);
1221
+ if (this.alias)
1222
+ existing.item = items[i];
1223
+ this.renderer.refreshRow(this, existing);
1224
+ next.set(key, existing);
1225
+ } else {
1226
+ const row = await this.renderer.createRow(this, items[i]);
1227
+ row.key = key;
1228
+ next.set(key, row);
1229
+ }
1230
+ }
1231
+ this.rows.forEach((row) => this.renderer.destroyRow(this, row));
1232
+ this.rows = next;
1233
+ this.order = keys;
1234
+ this.position();
1235
+ }
1236
+ position() {
1237
+ const parent = this.anchor.parentNode;
1238
+ if (!parent)
1239
+ return;
1240
+ let reference = this.anchor;
1241
+ for (let i = this.order.length - 1;i >= 0; i--) {
1242
+ const row = this.rows.get(this.order[i]);
1243
+ if (!row)
1244
+ continue;
1245
+ for (let j = row.nodes.length - 1;j >= 0; j--) {
1246
+ const node = row.nodes[j];
1247
+ if (node.nextSibling !== reference || node.parentNode !== parent) {
1248
+ parent.insertBefore(node, reference);
1249
+ }
1250
+ reference = node;
1251
+ }
1252
+ }
1253
+ if (parent.isConnected)
1254
+ this.renderer.connected?.();
1255
+ }
1256
+ destroy() {
1257
+ this.pendingItems = null;
1258
+ this.rows.forEach((row) => this.renderer.destroyRow(this, row));
1259
+ this.rows.clear();
1260
+ this.order = [];
1261
+ this.anchor.parentNode?.removeChild(this.anchor);
1262
+ }
1263
+ }
1264
+
1265
+ // src/runtime/dom/hydration.ts
1266
+ var TRIGGERS = new Set([
1267
+ "load",
1268
+ "idle",
1269
+ "visible",
1270
+ "interaction",
1271
+ "never",
1272
+ "always"
1273
+ ]);
1274
+
1275
+ // src/runtime/dom/Mounter.ts
1276
+ function composeBranchExpression(branches) {
1277
+ const elseIndex = branches.findIndex((branch) => branch.condition === null);
1278
+ let expression = elseIndex >= 0 ? String(elseIndex) : "-1";
1279
+ for (let i = branches.length - 1;i >= 0; i--) {
1280
+ const { condition } = branches[i];
1281
+ if (condition === null)
1282
+ continue;
1283
+ expression = `((${condition}) ? ${i} : ${expression})`;
1284
+ }
1285
+ return expression;
1286
+ }
1287
+
1288
+ // src/compile/expressions.ts
1289
+ function expressionKey(expr, params) {
1290
+ return `${params.join(",")}|${expr}`;
1291
+ }
1292
+ var VOID_ELEMENTS = new Set([
1293
+ "area",
1294
+ "base",
1295
+ "br",
1296
+ "col",
1297
+ "embed",
1298
+ "hr",
1299
+ "img",
1300
+ "input",
1301
+ "link",
1302
+ "meta",
1303
+ "param",
1304
+ "source",
1305
+ "track",
1306
+ "wbr"
1307
+ ]);
1308
+ var NODE = /<!--lsp ([\s\S]*?)-->|(<!--[\s\S]*?-->)|<\/?([a-zA-Z][\w-]*)((?:"[^"]*"|'[^']*'|[^>"'])*)\/?>/g;
1309
+ var ATTR = /([a-zA-Z@:$][\w:@.-]*)\s*=\s*"([^"]*)"/g;
1310
+ function parseForExpression(value) {
1311
+ const match = value.match(/^\s*(?:\(([^)]*)\)|([A-Za-z_$][\w$]*))\s+in\s+([\s\S]+)$/);
1312
+ if (!match)
1313
+ return { items: value.trim() };
1314
+ const alias = (match[1] ?? match[2] ?? "").split(",")[0].trim();
1315
+ return { alias: alias || undefined, items: match[3].trim() };
1316
+ }
1317
+ function extractExpressions(template, options = {}) {
1318
+ const chains = new Map;
1319
+ const found = [];
1320
+ const seen = new Set;
1321
+ const stack = [];
1322
+ const add = (expr, params, kind, writable = false) => {
1323
+ const trimmed = expr.trim();
1324
+ if (!trimmed)
1325
+ return;
1326
+ const key = `${kind}|${expressionKey(trimmed, params)}`;
1327
+ if (seen.has(key))
1328
+ return;
1329
+ seen.add(key);
1330
+ found.push({ expr: trimmed, params: [...params], kind, writable });
1331
+ };
1332
+ const aliases = () => stack.map((frame) => frame.alias).filter((alias) => !!alias);
1333
+ const flushChain = (depth) => {
1334
+ const chain = chains.get(depth);
1335
+ if (!chain)
1336
+ return;
1337
+ chains.delete(depth);
1338
+ add(composeBranchExpression(chain.branches), chain.params, "branch");
1339
+ };
1340
+ NODE.lastIndex = 0;
1341
+ let match;
1342
+ while ((match = NODE.exec(template)) !== null) {
1343
+ const [raw, markerPayload, comment, tag, attrText = ""] = match;
1344
+ if (markerPayload !== undefined) {
1345
+ addMarkerBinds(markerPayload, aliases(), add);
1346
+ continue;
1347
+ }
1348
+ if (comment !== undefined)
1349
+ continue;
1350
+ if (raw.startsWith("</")) {
1351
+ for (let i = stack.length - 1;i >= 0; i--) {
1352
+ if (stack[i].tag === tag) {
1353
+ stack.length = i;
1354
+ break;
1355
+ }
1356
+ }
1357
+ continue;
1358
+ }
1359
+ const outer = aliases();
1360
+ let alias;
1361
+ const forMatch = attrText.match(/\sdata-bind-for\s*=\s*"([^"]*)"/);
1362
+ if (forMatch) {
1363
+ const parsed = parseForExpression(forMatch[1]);
1364
+ add(parsed.items, outer, "for");
1365
+ alias = parsed.alias;
1366
+ }
1367
+ const inner = alias ? [...outer, alias] : outer;
1368
+ ATTR.lastIndex = 0;
1369
+ let attr;
1370
+ while ((attr = ATTR.exec(attrText)) !== null) {
1371
+ const [, name, value] = attr;
1372
+ if (name === "data-bind-for")
1373
+ continue;
1374
+ if (name === "data-text")
1375
+ add(value, inner, "text");
1376
+ else if (name === "data-bind-model")
1377
+ add(value, inner, "model", true);
1378
+ else if (name === "data-bind-key")
1379
+ add(value, inner, "key");
1380
+ else if (name.startsWith("data-on-")) {
1381
+ if (/^[A-Za-z_$][\w$]*$/.test(value.trim()))
1382
+ continue;
1383
+ const handlers = (options.handlers ?? []).filter((handler) => !inner.includes(handler));
1384
+ add(value, [...inner, ...handlers, "$event"], "event");
1385
+ } else if (name.startsWith("data-bind-")) {
1386
+ const kind = name.slice("data-bind-".length);
1387
+ if (kind === "else" || kind === "hydrate" || kind === "needs")
1388
+ continue;
1389
+ add(value, inner, kind);
1390
+ }
1391
+ }
1392
+ const depth = stack.length;
1393
+ const ifAttr = attrText.match(/\sdata-bind-if\s*=\s*"([^"]*)"/);
1394
+ const elseIf = attrText.match(/\sdata-bind-else-if\s*=\s*"([^"]*)"/);
1395
+ const hasElse = /\sdata-bind-else(?![-\w])/.test(attrText);
1396
+ if (ifAttr) {
1397
+ flushChain(depth);
1398
+ chains.set(depth, { params: outer, branches: [{ condition: ifAttr[1] }] });
1399
+ } else if (elseIf && chains.has(depth)) {
1400
+ chains.get(depth).branches.push({ condition: elseIf[1] });
1401
+ } else if (hasElse && chains.has(depth)) {
1402
+ chains.get(depth).branches.push({ condition: null });
1403
+ flushChain(depth);
1404
+ } else {
1405
+ flushChain(depth);
1406
+ }
1407
+ const selfClosing = raw.endsWith("/>") || VOID_ELEMENTS.has(tag.toLowerCase());
1408
+ if (!selfClosing)
1409
+ stack.push({ tag, alias });
1410
+ }
1411
+ for (const depth of [...chains.keys()])
1412
+ flushChain(depth);
1413
+ return found;
1414
+ }
1415
+ function buildExpressionTable(found) {
1416
+ const expressions = {};
1417
+ const writers = {};
1418
+ for (const one of found) {
1419
+ const signature = ["state", "props", "shared", "pending", ...one.params];
1420
+ const key = expressionKey(one.expr, one.params);
1421
+ try {
1422
+ expressions[key] = new Function(...signature, `return (${one.expr});`);
1423
+ if (one.writable) {
1424
+ writers[key] = new Function("state", "props", "shared", "pending", "$value", ...one.params, `${one.expr} = $value;`);
1425
+ }
1426
+ } catch {
1427
+ }
1428
+ }
1429
+ return { expressions, writers };
1430
+ }
1431
+ function addMarkerBinds(payload, params, add) {
1432
+ let parsed;
1433
+ try {
1434
+ parsed = JSON.parse(payload.trim());
1435
+ } catch {
1436
+ return;
1437
+ }
1438
+ for (const expr of Object.values(parsed?.bind ?? {})) {
1439
+ if (typeof expr === "string")
1440
+ add(expr, params, "prop");
1441
+ }
1442
+ if (typeof parsed?.dyn === "string")
1443
+ add(parsed.dyn, params, "dynamic");
1444
+ }
1445
+
1446
+ // src/compile/index.ts
1447
+ async function compileComponents(sources, transpile = (logic) => logic, load, readStyle) {
1448
+ const parsed = new Map(sources.map(({ id, source }) => {
1449
+ const block = parseLSP(source, id);
1450
+ const shares = block.styleSources.length || block.styleDeferredSources.length;
1451
+ if (shares && !readStyle) {
1452
+ throw new Error(`${id}: <style src="…"> needs a reader - pass one to compileComponents.`);
1453
+ }
1454
+ return [
1455
+ id.toLowerCase(),
1456
+ shares ? inlineStyleSources(block, readStyle) : block
1457
+ ];
1458
+ }));
1459
+ const registry = Object.fromEntries([...parsed].map(([id, block]) => [
1460
+ id,
1461
+ { template: block.view, style: block.style, logic: block.logic }
1462
+ ]));
1463
+ const definitions = {};
1464
+ for (const [id, block] of parsed) {
1465
+ const scopeId = scopeIdFor(id);
1466
+ definitions[id] = {
1467
+ id,
1468
+ template: transformTemplate(block.view, id, registry),
1469
+ skeleton: block.skeleton ? transformTemplate(block.skeleton, id, registry) : "",
1470
+ style: scopeStyles(block.style ?? "", scopeId),
1471
+ styleDeferred: scopeStyles(block.styleDeferred ?? "", scopeId),
1472
+ setup: await buildSetup(block.logic ?? "", { transpile, load }),
1473
+ handlers: handlerParams(block.logic ?? ""),
1474
+ ...buildExpressionTable([
1475
+ ...extractExpressions(transformTemplate(block.view, id, registry), {
1476
+ handlers: handlerParams(block.logic ?? "")
1477
+ }),
1478
+ ...extractExpressions(block.skeleton ? transformTemplate(block.skeleton, id, registry) : "", { handlers: handlerParams(block.logic ?? "") })
1479
+ ]),
1480
+ scopeId
1481
+ };
1482
+ }
1483
+ return definitions;
1484
+ }
1485
+ export {
1486
+ transformTemplate,
1487
+ setupSource,
1488
+ scopeStyles,
1489
+ scopeIdFor,
1490
+ parseLSP,
1491
+ compileLogic,
1492
+ compileComponents,
1493
+ buildSetup
1494
+ };