mikuru 1.0.18 → 1.0.20

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 (60) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +19 -12
  3. package/dist/compiler/analyzeTemplate.js +116 -12
  4. package/dist/compiler/analyzeTemplate.js.map +1 -1
  5. package/dist/compiler/compile.js +2 -2
  6. package/dist/compiler/compile.js.map +1 -1
  7. package/dist/compiler/compileHydration.d.ts +2 -0
  8. package/dist/compiler/compileHydration.js +27 -0
  9. package/dist/compiler/compileHydration.js.map +1 -0
  10. package/dist/compiler/compileSsr.d.ts +2 -0
  11. package/dist/compiler/compileSsr.js +21 -0
  12. package/dist/compiler/compileSsr.js.map +1 -0
  13. package/dist/compiler/generate.d.ts +6 -1
  14. package/dist/compiler/generate.js +1473 -113
  15. package/dist/compiler/generate.js.map +1 -1
  16. package/dist/compiler/generateHydration.d.ts +2 -0
  17. package/dist/compiler/generateHydration.js +1281 -0
  18. package/dist/compiler/generateHydration.js.map +1 -0
  19. package/dist/compiler/generateSsr.d.ts +2 -0
  20. package/dist/compiler/generateSsr.js +764 -0
  21. package/dist/compiler/generateSsr.js.map +1 -0
  22. package/dist/compiler/index.d.ts +3 -1
  23. package/dist/compiler/index.js +2 -0
  24. package/dist/compiler/index.js.map +1 -1
  25. package/dist/compiler/parseTemplate.js +16 -1
  26. package/dist/compiler/parseTemplate.js.map +1 -1
  27. package/dist/compiler/sourceMap.d.ts +2 -2
  28. package/dist/compiler/sourceMap.js +110 -6
  29. package/dist/compiler/sourceMap.js.map +1 -1
  30. package/dist/compiler/types.d.ts +8 -0
  31. package/dist/index.d.ts +6 -3
  32. package/dist/index.js +3 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/router/index.js +16 -3
  35. package/dist/router/index.js.map +1 -1
  36. package/dist/runtime/asyncComponent.d.ts +26 -0
  37. package/dist/runtime/asyncComponent.js +65 -5
  38. package/dist/runtime/asyncComponent.js.map +1 -1
  39. package/dist/runtime/devtools.d.ts +51 -0
  40. package/dist/runtime/devtools.js +113 -0
  41. package/dist/runtime/devtools.js.map +1 -0
  42. package/dist/runtime/dom.d.ts +5 -1
  43. package/dist/runtime/dom.js +100 -3
  44. package/dist/runtime/dom.js.map +1 -1
  45. package/dist/runtime/index.d.ts +7 -5
  46. package/dist/runtime/index.js +3 -2
  47. package/dist/runtime/index.js.map +1 -1
  48. package/dist/runtime/lifecycle.d.ts +6 -0
  49. package/dist/runtime/lifecycle.js +66 -10
  50. package/dist/runtime/lifecycle.js.map +1 -1
  51. package/dist/runtime/reactivity.d.ts +33 -1
  52. package/dist/runtime/reactivity.js +254 -13
  53. package/dist/runtime/reactivity.js.map +1 -1
  54. package/dist/server.d.ts +31 -0
  55. package/dist/server.js +282 -0
  56. package/dist/server.js.map +1 -0
  57. package/dist/vite.d.ts +1 -0
  58. package/dist/vite.js +30 -13
  59. package/dist/vite.js.map +1 -1
  60. package/package.json +104 -100
@@ -1,29 +1,57 @@
1
- import { parse } from "acorn";
1
+ import { parse, parseExpressionAt } from "acorn";
2
2
  import { createCompileError } from "./errors.js";
3
3
  import { compileTemplateExpression, parseForExpression, validateAssignableExpression, validateTemplateExpression } from "./parseExpression.js";
4
- export function generate(descriptor, root) {
4
+ export function generate(descriptor, root, options = {}) {
5
5
  const context = {
6
6
  lines: [],
7
7
  index: 0,
8
8
  source: descriptor.source,
9
9
  filename: descriptor.filename,
10
- scopeAttr: descriptor.styleScoped ? createScopeAttr(descriptor) : undefined
10
+ scopeAttr: descriptor.styleScoped ? createScopeAttr(descriptor) : undefined,
11
+ debug: options.debug === true,
12
+ batchedUpdates: options.batchedUpdates === true
11
13
  };
12
14
  const script = normalizeScript(descriptor);
13
15
  for (const importLine of script.imports) {
14
16
  emit(context, 0, importLine);
15
17
  }
16
- const runtimeImports = mergeRuntimeImports(["computed", "effect", "ref", "setAttribute", "unwrap"], script.runtimeImports);
18
+ const runtimeBaseImports = ["computed", "effect", "ref", "setAttribute", "unwrap"];
19
+ if (context.batchedUpdates) {
20
+ runtimeBaseImports.push("queueJob");
21
+ }
22
+ if (context.debug) {
23
+ runtimeBaseImports.push("emitDebugEvent", "registerDebugComponent");
24
+ }
25
+ const runtimeImports = mergeRuntimeImports(runtimeBaseImports, script.runtimeImports);
17
26
  emit(context, 0, `import { ${runtimeImports.join(", ")} } from "mikuru/runtime";`);
18
27
  emit(context, 0, "");
28
+ if (context.batchedUpdates) {
29
+ emit(context, 0, "const __mikuru_effect = (fn) => effect(fn, { scheduler: queueJob });");
30
+ emit(context, 0, "");
31
+ }
19
32
  emit(context, 0, "export function mount(target, props = {}) {");
33
+ emit(context, 1, `const __mikuru_componentInfo = { component: ${quote(descriptor.filename ?? "anonymous.mikuru")}, filename: ${quote(descriptor.filename ?? "anonymous.mikuru")} };`);
34
+ emitDevtoolsRegistration(context, 1);
20
35
  emit(context, 1, "const __mikuru_cleanup = [];");
21
36
  emit(context, 1, "const __mikuru_afterUnmount = [];");
22
37
  emit(context, 1, "const __mikuru_mounted = [];");
23
- emit(context, 1, "const __mikuru_context = { parent: props.__mikuru_context, provides: new Map() };");
38
+ emit(context, 1, "const __mikuru_activated = [];");
39
+ emit(context, 1, "const __mikuru_deactivated = [];");
40
+ emit(context, 1, `const __mikuru_context = { parent: props.__mikuru_context, provides: new Map(), errorHandler: props.__mikuru_context?.errorHandler${context.debug ? ", debugId: __mikuru_debug.id" : ""}, ...__mikuru_componentInfo };`);
41
+ emit(context, 1, "const __mikuru_errorInfo = (phase) => ({ ...__mikuru_componentInfo, phase });");
42
+ emit(context, 1, "const __mikuru_reportError = (error, errorHandler = __mikuru_context.errorHandler, phase = \"runtime\") => {");
43
+ if (context.debug) {
44
+ emit(context, 2, "emitDebugEvent(\"component:error\", { component: __mikuru_componentInfo, error, errorInfo: __mikuru_errorInfo(phase), componentId: __mikuru_debug.id });");
45
+ }
46
+ emit(context, 2, "if (typeof errorHandler === \"function\") { Promise.resolve().then(() => errorHandler(error, __mikuru_errorInfo(phase))); return; }");
47
+ emit(context, 2, "setTimeout(() => { throw error; });");
48
+ emit(context, 1, "};");
49
+ emit(context, 1, "const __mikuru_try = (fn, errorHandler, phase) => { try { return fn(); } catch (error) { __mikuru_reportError(error, errorHandler, phase); } };");
50
+ emit(context, 1, "const __mikuru_memoEqual = (previous, next) => Array.isArray(previous) && Array.isArray(next) && previous.length === next.length && previous.every((value, index) => Object.is(value, next[index]));");
51
+ emit(context, 1, "const __mikuru_guardEventHandler = (fn, errorHandler = __mikuru_context.errorHandler) => (...args) => __mikuru_try(() => fn(...args), errorHandler, \"event\");");
24
52
  emit(context, 1, "const __mikuru_runCleanup = (cleanups) => {");
25
53
  emit(context, 2, "for (const cleanup of cleanups.splice(0).reverse()) {");
26
- emit(context, 3, "cleanup();");
54
+ emit(context, 3, "__mikuru_try(cleanup, undefined, \"cleanup\");");
27
55
  emit(context, 2, "}");
28
56
  emit(context, 1, "};");
29
57
  emit(context, 1, "const __mikuru_transitionFrame = (fn) => {");
@@ -58,6 +86,12 @@ export function generate(descriptor, root) {
58
86
  emit(context, 3, "__mikuru_transitionDone(element, () => element.classList.remove(active, to));");
59
87
  emit(context, 2, "});");
60
88
  emit(context, 1, "};");
89
+ emit(context, 1, "const __mikuru_applyTransitionMove = (element, transition) => {");
90
+ emit(context, 2, "if (!element || element.nodeType !== 1 || !transition?.name) { return; }");
91
+ emit(context, 2, "const move = transition.moveClass || `${transition.name}-move`;");
92
+ emit(context, 2, "element.classList.add(move);");
93
+ emit(context, 2, "__mikuru_transitionFrame(() => __mikuru_transitionDone(element, () => element.classList.remove(move)));");
94
+ emit(context, 1, "};");
61
95
  emit(context, 1, "const __mikuru_removeNode = (node) => {");
62
96
  emit(context, 2, "if (!node || !node.parentNode) { return; }");
63
97
  emit(context, 2, "if (node.nodeType !== 1 || !node.__mikuru_transition || node.__mikuru_transitionLeaving) { node.remove(); return; }");
@@ -94,6 +128,8 @@ export function generate(descriptor, root) {
94
128
  emit(context, 1, "const __mikuru_previousRegistrar = globalThis.__mikuru_currentRegistrar;");
95
129
  emit(context, 1, "globalThis.__mikuru_currentRegistrar = {");
96
130
  emit(context, 2, "registerMounted: (fn) => __mikuru_mounted.push(fn),");
131
+ emit(context, 2, "registerActivated: (fn) => __mikuru_activated.push(fn),");
132
+ emit(context, 2, "registerDeactivated: (fn) => __mikuru_deactivated.push(fn),");
97
133
  emit(context, 2, "registerBeforeUnmount: (fn) => __mikuru_cleanup.push(fn),");
98
134
  emit(context, 2, "registerUnmounted: (fn) => __mikuru_afterUnmount.push(fn),");
99
135
  emit(context, 2, "provide: (key, value) => __mikuru_context.provides.set(key, value),");
@@ -124,7 +160,7 @@ export function generate(descriptor, root) {
124
160
  emit(context, 2, "const handlerName = \"on\" + String(name).split(/[-:]/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(\"\");");
125
161
  emit(context, 2, "const handler = props[handlerName];");
126
162
  emit(context, 2, "if (handler) {");
127
- emit(context, 3, "handler(...args);");
163
+ emit(context, 3, "__mikuru_try(() => handler(...args), undefined, \"emit\");");
128
164
  emit(context, 2, "}");
129
165
  emit(context, 1, "};");
130
166
  }
@@ -132,14 +168,25 @@ export function generate(descriptor, root) {
132
168
  emit(context, 1, "");
133
169
  }
134
170
  const rootVar = generateNode(context, root, "target", "__mikuru_cleanup", 1);
171
+ emitDevtoolsRootUpdate(context, rootVar, 1);
135
172
  emit(context, 1, "// call mounted callbacks registered during setup and remove registrar");
136
- emit(context, 1, "for (const cb of __mikuru_mounted.splice(0)) { try { cb(); } catch (e) { setTimeout(() => { throw e; }); } }");
173
+ emit(context, 1, "for (const cb of __mikuru_mounted.splice(0)) { __mikuru_try(cb, undefined, \"mounted\"); }");
137
174
  emit(context, 1, "if (__mikuru_previousRegistrar === undefined) { delete globalThis.__mikuru_currentRegistrar; } else { globalThis.__mikuru_currentRegistrar = __mikuru_previousRegistrar; }");
138
175
  emit(context, 1, "return {");
139
176
  emit(context, 2, `element: ${rootVar},`);
177
+ emit(context, 2, "activate() {");
178
+ emit(context, 3, "for (const cb of __mikuru_activated) { __mikuru_try(cb, undefined, \"activated\"); }");
179
+ emit(context, 2, "},");
180
+ emit(context, 2, "deactivate() {");
181
+ emit(context, 3, "for (const cb of __mikuru_deactivated) { __mikuru_try(cb, undefined, \"deactivated\"); }");
182
+ emit(context, 2, "},");
140
183
  emit(context, 2, "unmount() {");
184
+ if (context.debug) {
185
+ emit(context, 3, "emitDebugEvent(\"component:unmount\", { component: __mikuru_componentInfo, componentId: __mikuru_debug.id });");
186
+ emit(context, 3, "__mikuru_unregisterDevtools();");
187
+ }
141
188
  emit(context, 3, "__mikuru_runCleanup(__mikuru_cleanup);");
142
- emit(context, 3, "for (const cb of __mikuru_afterUnmount.splice(0).reverse()) { try { cb(); } catch (e) { setTimeout(() => { throw e; }); } }");
189
+ emit(context, 3, "for (const cb of __mikuru_afterUnmount.splice(0).reverse()) { __mikuru_try(cb, undefined, \"unmounted\"); }");
143
190
  emit(context, 3, `__mikuru_removeNode(${rootVar});`);
144
191
  emit(context, 2, "}");
145
192
  emit(context, 1, "};");
@@ -147,7 +194,10 @@ export function generate(descriptor, root) {
147
194
  emit(context, 0, "");
148
195
  emit(context, 0, `const __mikuru_component = { mount${script.inheritAttrs ? "" : ", inheritAttrs: false"} };`);
149
196
  emit(context, 0, "export default __mikuru_component;");
150
- return `${context.lines.join("\n")}\n`;
197
+ const lines = context.batchedUpdates
198
+ ? context.lines.map((line) => line.replace("= effect(() => {", "= __mikuru_effect(() => {"))
199
+ : context.lines;
200
+ return `${lines.join("\n")}\n`;
151
201
  }
152
202
  function emitStyleInjection(context, descriptor, indent) {
153
203
  const styleId = `mikuru-${hash(`${descriptor.filename ?? ""}\n${descriptor.style ?? ""}`)}`;
@@ -161,10 +211,42 @@ function emitStyleInjection(context, descriptor, indent) {
161
211
  emit(context, indent + 1, "document.head.appendChild(style);");
162
212
  emit(context, indent, "}");
163
213
  }
214
+ function emitDevtoolsRegistration(context, indent) {
215
+ if (!context.debug) {
216
+ return;
217
+ }
218
+ emit(context, indent, "const __mikuru_publicProps = Object.fromEntries(Object.entries(props).filter(([key]) => !key.startsWith(\"__mikuru_\")));");
219
+ emit(context, indent, "const __mikuru_debugAttrs = props.__mikuru_attrs && typeof props.__mikuru_attrs === \"object\" ? { ...props.__mikuru_attrs } : {};");
220
+ emit(context, indent, "const __mikuru_debug = registerDebugComponent({");
221
+ emit(context, indent + 1, "name: __mikuru_componentInfo.component,");
222
+ emit(context, indent + 1, "filename: __mikuru_componentInfo.filename,");
223
+ emit(context, indent + 1, "parentId: props.__mikuru_context?.debugId,");
224
+ emit(context, indent + 1, "props: __mikuru_publicProps,");
225
+ emit(context, indent + 1, "propKeys: Object.keys(__mikuru_publicProps),");
226
+ emit(context, indent + 1, "attrs: __mikuru_debugAttrs,");
227
+ emit(context, indent + 1, "attrKeys: Object.keys(__mikuru_debugAttrs)");
228
+ emit(context, indent, "});");
229
+ emit(context, indent, "const __mikuru_unregisterDevtools = () => __mikuru_debug.unregister();");
230
+ }
231
+ function emitDevtoolsRootUpdate(context, rootVar, indent) {
232
+ if (!context.debug) {
233
+ return;
234
+ }
235
+ emit(context, indent, `__mikuru_debug.update({ root: ${rootVar} });`);
236
+ emit(context, indent, "emitDebugEvent(\"component:mount\", { component: __mikuru_componentInfo, componentId: __mikuru_debug.id, root: __mikuru_debug.metadata.root, props: __mikuru_debug.metadata.props, attrs: __mikuru_debug.metadata.attrs });");
237
+ }
164
238
  function generateNode(context, node, parentVar, cleanupVar, indent, beforeVar) {
165
239
  if (node.type === "text") {
166
240
  return generateText(context, node, parentVar, cleanupVar, indent, beforeVar);
167
241
  }
242
+ if (hasAttr(node, "v-pre")) {
243
+ validatePreAttribute(context, node);
244
+ return generatePreElement(context, node, parentVar, indent, beforeVar);
245
+ }
246
+ if (hasAttr(node, "v-once") && !hasAttr(node, "v-for")) {
247
+ validateOnceAttribute(context, node);
248
+ return withOnceMode(context, () => generateNode(context, withoutAttrs(node, ["v-once"]), parentVar, cleanupVar, indent, beforeVar));
249
+ }
168
250
  const forExpression = getStringAttr(node, "v-for");
169
251
  if (forExpression) {
170
252
  return generateFor(context, node, parentVar, cleanupVar, indent, forExpression, beforeVar);
@@ -183,12 +265,21 @@ function generateNode(context, node, parentVar, cleanupVar, indent, beforeVar) {
183
265
  if (node.tag === "Transition") {
184
266
  return generateTransition(context, node, parentVar, cleanupVar, indent, beforeVar);
185
267
  }
268
+ if (node.tag === "TransitionGroup") {
269
+ return generateTransitionGroup(context, node, parentVar, cleanupVar, indent, beforeVar);
270
+ }
186
271
  if (node.tag === "Teleport") {
187
272
  return generateTeleport(context, node, parentVar, cleanupVar, indent, beforeVar);
188
273
  }
274
+ if (node.tag === "AsyncBoundary") {
275
+ return generateAsyncBoundary(context, node, parentVar, cleanupVar, indent, beforeVar);
276
+ }
189
277
  if (node.tag === "ErrorBoundary") {
190
278
  return generateErrorBoundary(context, node, parentVar, cleanupVar, indent, beforeVar);
191
279
  }
280
+ if (node.tag === "KeepAlive") {
281
+ return generateKeepAlive(context, node, parentVar, cleanupVar, indent, beforeVar);
282
+ }
192
283
  if (isComponentTag(node.tag)) {
193
284
  return generateComponent(context, node, parentVar, cleanupVar, indent, beforeVar);
194
285
  }
@@ -213,6 +304,30 @@ function generateTransition(context, node, parentVar, cleanupVar, indent, before
213
304
  emitTransitionRegistration(context, childVar, transitionVar, indent);
214
305
  return childVar;
215
306
  }
307
+ function generateTransitionGroup(context, node, parentVar, cleanupVar, indent, beforeVar) {
308
+ validateTransitionGroupAttributes(context, node);
309
+ const children = getSingleElementChild(context, node, "<TransitionGroup>");
310
+ const child = children[0];
311
+ const forExpression = getStringAttr(child, "v-for");
312
+ const keyExpression = getKeyExpression(child);
313
+ if (!forExpression || !keyExpression) {
314
+ throwTemplateError("<TransitionGroup> requires a single keyed v-for child in v1", context, child.loc);
315
+ }
316
+ const { item: itemName, index: indexName, source: sourceExpression } = parseForExpression(forExpression, toExpressionContext(context, getStringAttrLocation(child, "v-for")));
317
+ const groupVar = nextVar(context, "transitionGroup");
318
+ const groupCleanupVar = nextVar(context, "transitionGroupCleanup");
319
+ const transitionVar = nextVar(context, "transition");
320
+ emit(context, indent, `const ${transitionVar} = ${getTransitionOptionsExpression(context, node)};`);
321
+ emit(context, indent, `const ${groupVar} = document.createElement(String(unwrap(${getTransitionAttrExpression(context, node, "tag", "span")}) ?? "span"));`);
322
+ appendNode(context, parentVar, groupVar, indent, beforeVar);
323
+ emit(context, indent, `const ${groupCleanupVar} = [];`);
324
+ generateKeyedFor(context, child, groupVar, groupCleanupVar, indent, itemName, indexName, sourceExpression, keyExpression, undefined, transitionVar);
325
+ emit(context, indent, `${cleanupVar}.push(() => {`);
326
+ emit(context, indent + 1, `__mikuru_runCleanup(${groupCleanupVar});`);
327
+ emit(context, indent + 1, `__mikuru_removeNode(${groupVar});`);
328
+ emit(context, indent, "});");
329
+ return groupVar;
330
+ }
216
331
  function generateTeleport(context, node, parentVar, cleanupVar, indent, beforeVar) {
217
332
  validateTeleportAttributes(context, node);
218
333
  const toExpression = getTeleportToExpression(context, node);
@@ -274,37 +389,239 @@ function generateErrorBoundary(context, node, parentVar, cleanupVar, indent, bef
274
389
  validateErrorBoundaryAttributes(context, node);
275
390
  const children = getSingleElementChild(context, node, "<ErrorBoundary>");
276
391
  const fallbackExpression = getErrorBoundaryFallbackExpression(context, node);
392
+ const resetKeyExpression = getErrorBoundaryResetKeyExpression(context, node);
277
393
  const startVar = nextVar(context, "errorBoundaryStart");
278
394
  const endVar = nextVar(context, "errorBoundaryEnd");
279
395
  const boundaryCleanupVar = nextVar(context, "errorBoundaryCleanup");
280
396
  const renderVar = nextVar(context, "renderErrorBoundary");
397
+ const fallbackRenderVar = nextVar(context, "renderErrorBoundaryFallback");
281
398
  const errorVar = nextVar(context, "error");
399
+ const errorInfoVar = nextVar(context, "errorInfo");
400
+ const normalizedErrorInfoVar = nextVar(context, "errorInfo");
282
401
  const fallbackVar = nextVar(context, "errorFallback");
283
402
  const fallbackFragmentVar = nextVar(context, "errorFallback");
284
403
  const fallbackInstanceVar = nextVar(context, "errorFallback");
404
+ const previousErrorHandlerVar = nextVar(context, "previousErrorHandler");
405
+ const boundaryContextVar = nextVar(context, "errorBoundaryContext");
406
+ const previousComponentContextVar = context.componentContextVar;
407
+ const boundaryResetInitializedVar = resetKeyExpression ? nextVar(context, "errorBoundaryResetInitialized") : undefined;
408
+ const boundaryResetValueVar = resetKeyExpression ? nextVar(context, "errorBoundaryResetValue") : undefined;
409
+ const boundaryResetNextVar = resetKeyExpression ? nextVar(context, "errorBoundaryResetValue") : undefined;
410
+ const boundaryResetStopVar = resetKeyExpression ? nextVar(context, "stop") : undefined;
285
411
  emit(context, indent, `const ${startVar} = document.createComment("error-boundary");`);
286
412
  emit(context, indent, `const ${endVar} = document.createComment("/error-boundary");`);
287
413
  appendNode(context, parentVar, startVar, indent, beforeVar);
288
414
  appendNode(context, parentVar, endVar, indent, beforeVar);
289
415
  emit(context, indent, `const ${boundaryCleanupVar} = [];`);
416
+ emit(context, indent, `const ${fallbackRenderVar} = (${errorVar}, ${errorInfoVar} = __mikuru_errorInfo("runtime")) => {`);
417
+ emit(context, indent + 1, `__mikuru_runCleanup(${boundaryCleanupVar});`);
418
+ emitRemoveBetween(context, indent + 1, startVar, endVar);
419
+ emit(context, indent + 1, `const ${fallbackVar} = unwrap(${fallbackExpression});`);
420
+ emit(context, indent + 1, `if (!${fallbackVar} || typeof ${fallbackVar}.mount !== "function") { throw ${errorVar}; }`);
421
+ emit(context, indent + 1, `const ${normalizedErrorInfoVar} = ${errorInfoVar} && typeof ${errorInfoVar} === "object" ? ${errorInfoVar} : {};`);
422
+ if (context.debug) {
423
+ emit(context, indent + 1, `emitDebugEvent("component:error", { component: __mikuru_componentInfo, componentId: __mikuru_debug.id, error: ${errorVar}, errorInfo: { ...${normalizedErrorInfoVar}, boundary: __mikuru_componentInfo } });`);
424
+ }
425
+ emit(context, indent + 1, `const ${fallbackFragmentVar} = document.createDocumentFragment();`);
426
+ emit(context, indent + 1, `const ${fallbackInstanceVar} = ${fallbackVar}.mount(${fallbackFragmentVar}, { error: ${errorVar}, errorInfo: { ...${normalizedErrorInfoVar}, boundary: __mikuru_componentInfo }, retry: ${renderVar}, reset: ${renderVar}, __mikuru_context });`);
427
+ emit(context, indent + 1, `${boundaryCleanupVar}.push(() => ${fallbackInstanceVar}.unmount());`);
428
+ appendNode(context, parentVar, fallbackFragmentVar, indent + 1, endVar);
429
+ emit(context, indent, "};");
290
430
  emit(context, indent, `const ${renderVar} = () => {`);
291
431
  emit(context, indent + 1, `__mikuru_runCleanup(${boundaryCleanupVar});`);
292
432
  emitRemoveBetween(context, indent + 1, startVar, endVar);
433
+ emit(context, indent + 1, `const ${previousErrorHandlerVar} = __mikuru_context.errorHandler;`);
434
+ emit(context, indent + 1, `const ${boundaryContextVar} = { parent: __mikuru_context, provides: new Map(), errorHandler: ${fallbackRenderVar}, ...__mikuru_componentInfo };`);
435
+ emit(context, indent + 1, `__mikuru_context.errorHandler = ${fallbackRenderVar};`);
293
436
  emit(context, indent + 1, "try {");
437
+ context.componentContextVar = boundaryContextVar;
294
438
  generateNode(context, children[0], parentVar, boundaryCleanupVar, indent + 2, endVar);
439
+ context.componentContextVar = previousComponentContextVar;
295
440
  emit(context, indent + 1, `} catch (${errorVar}) {`);
296
- emit(context, indent + 2, `__mikuru_runCleanup(${boundaryCleanupVar});`);
297
- emitRemoveBetween(context, indent + 2, startVar, endVar);
298
- emit(context, indent + 2, `const ${fallbackVar} = unwrap(${fallbackExpression});`);
299
- emit(context, indent + 2, `if (!${fallbackVar} || typeof ${fallbackVar}.mount !== "function") { throw ${errorVar}; }`);
300
- emit(context, indent + 2, `const ${fallbackFragmentVar} = document.createDocumentFragment();`);
301
- emit(context, indent + 2, `const ${fallbackInstanceVar} = ${fallbackVar}.mount(${fallbackFragmentVar}, { error: ${errorVar}, retry: ${renderVar}, __mikuru_context: props.__mikuru_context });`);
302
- emit(context, indent + 2, `${boundaryCleanupVar}.push(() => ${fallbackInstanceVar}.unmount());`);
303
- appendNode(context, parentVar, fallbackFragmentVar, indent + 2, endVar);
441
+ emit(context, indent + 2, `${fallbackRenderVar}(${errorVar}, __mikuru_errorInfo("mount"));`);
442
+ emit(context, indent + 1, "} finally {");
443
+ emit(context, indent + 2, `__mikuru_context.errorHandler = ${previousErrorHandlerVar};`);
444
+ emit(context, indent + 1, "}");
445
+ emit(context, indent, "};");
446
+ emit(context, indent, `${renderVar}();`);
447
+ if (resetKeyExpression && boundaryResetInitializedVar && boundaryResetValueVar && boundaryResetNextVar && boundaryResetStopVar) {
448
+ emit(context, indent, `let ${boundaryResetInitializedVar} = false;`);
449
+ emit(context, indent, `let ${boundaryResetValueVar};`);
450
+ emit(context, indent, `const ${boundaryResetStopVar} = effect(() => {`);
451
+ emit(context, indent + 1, `const ${boundaryResetNextVar} = unwrap(${resetKeyExpression});`);
452
+ emit(context, indent + 1, `if (!${boundaryResetInitializedVar}) { ${boundaryResetInitializedVar} = true; ${boundaryResetValueVar} = ${boundaryResetNextVar}; return; }`);
453
+ emit(context, indent + 1, `if (Object.is(${boundaryResetValueVar}, ${boundaryResetNextVar})) { return; }`);
454
+ emit(context, indent + 1, `${boundaryResetValueVar} = ${boundaryResetNextVar};`);
455
+ emit(context, indent + 1, `${renderVar}();`);
456
+ emit(context, indent, "});");
457
+ emit(context, indent, `${cleanupVar}.push(${boundaryResetStopVar});`);
458
+ }
459
+ emit(context, indent, `${cleanupVar}.push(() => {`);
460
+ emit(context, indent + 1, `__mikuru_runCleanup(${boundaryCleanupVar});`);
461
+ emitRemoveBetween(context, indent + 1, startVar, endVar);
462
+ emit(context, indent + 1, `${startVar}.remove();`);
463
+ emit(context, indent + 1, `${endVar}.remove();`);
464
+ emit(context, indent, "});");
465
+ return startVar;
466
+ }
467
+ function generateAsyncBoundary(context, node, parentVar, cleanupVar, indent, beforeVar) {
468
+ validateAsyncBoundaryAttributes(context, node);
469
+ const children = getAsyncBoundaryChildren(context, node);
470
+ const loadingExpression = getAsyncBoundaryLoadingExpression(context, node);
471
+ const fallbackExpression = getAsyncBoundaryFallbackExpression(context, node);
472
+ const delayExpression = getAsyncBoundaryDelayExpression(context, node);
473
+ const timeoutExpression = getAsyncBoundaryTimeoutExpression(context, node);
474
+ const startVar = nextVar(context, "asyncBoundaryStart");
475
+ const endVar = nextVar(context, "asyncBoundaryEnd");
476
+ const boundaryCleanupVar = nextVar(context, "asyncBoundaryCleanup");
477
+ const loadingCleanupVar = nextVar(context, "asyncBoundaryLoadingCleanup");
478
+ const fallbackCleanupVar = nextVar(context, "asyncBoundaryFallbackCleanup");
479
+ const pendingVar = nextVar(context, "asyncBoundaryPending");
480
+ const failedVar = nextVar(context, "asyncBoundaryFailed");
481
+ const lastRetryVar = nextVar(context, "asyncBoundaryRetry");
482
+ const renderLoadingVar = nextVar(context, "renderAsyncBoundaryLoading");
483
+ const clearLoadingVar = nextVar(context, "clearAsyncBoundaryLoading");
484
+ const renderFallbackVar = nextVar(context, "renderAsyncBoundaryFallback");
485
+ const clearFallbackVar = nextVar(context, "clearAsyncBoundaryFallback");
486
+ const scheduleLoadingVar = nextVar(context, "scheduleAsyncBoundaryLoading");
487
+ const scheduleTimeoutVar = nextVar(context, "scheduleAsyncBoundaryTimeout");
488
+ const clearTimersVar = nextVar(context, "clearAsyncBoundaryTimers");
489
+ const renderVar = nextVar(context, "renderAsyncBoundary");
490
+ const loadingVar = nextVar(context, "asyncBoundaryLoading");
491
+ const loadingFragmentVar = nextVar(context, "asyncBoundaryLoading");
492
+ const loadingInstanceVar = nextVar(context, "asyncBoundaryLoading");
493
+ const fallbackVar = nextVar(context, "asyncBoundaryFallback");
494
+ const fallbackFragmentVar = nextVar(context, "asyncBoundaryFallback");
495
+ const fallbackInstanceVar = nextVar(context, "asyncBoundaryFallback");
496
+ const errorVar = nextVar(context, "error");
497
+ const errorInfoVar = nextVar(context, "errorInfo");
498
+ const errorsVar = nextVar(context, "asyncBoundaryErrors");
499
+ const normalizedErrorInfoVar = nextVar(context, "errorInfo");
500
+ const retryVar = nextVar(context, "retry");
501
+ const settledVar = nextVar(context, "settled");
502
+ const delayVar = nextVar(context, "asyncBoundaryDelay");
503
+ const timeoutVar = nextVar(context, "asyncBoundaryTimeout");
504
+ const delayTimerVar = nextVar(context, "asyncBoundaryDelayTimer");
505
+ const timeoutTimerVar = nextVar(context, "asyncBoundaryTimeoutTimer");
506
+ const timeoutErrorVar = nextVar(context, "asyncBoundaryTimeoutError");
507
+ const asyncContextVar = nextVar(context, "asyncBoundaryContext");
508
+ const previousComponentContextVar = context.componentContextVar;
509
+ emit(context, indent, `const ${startVar} = document.createComment("async-boundary");`);
510
+ emit(context, indent, `const ${endVar} = document.createComment("/async-boundary");`);
511
+ appendNode(context, parentVar, startVar, indent, beforeVar);
512
+ appendNode(context, parentVar, endVar, indent, beforeVar);
513
+ emit(context, indent, `const ${boundaryCleanupVar} = [];`);
514
+ emit(context, indent, `const ${loadingCleanupVar} = [];`);
515
+ emit(context, indent, `const ${fallbackCleanupVar} = [];`);
516
+ emit(context, indent, `let ${pendingVar} = 0;`);
517
+ emit(context, indent, `let ${failedVar} = false;`);
518
+ emit(context, indent, `let ${errorsVar} = [];`);
519
+ emit(context, indent, `let ${lastRetryVar} = () => {};`);
520
+ emit(context, indent, `let ${delayTimerVar};`);
521
+ emit(context, indent, `let ${timeoutTimerVar};`);
522
+ emit(context, indent, `const ${clearLoadingVar} = () => __mikuru_runCleanup(${loadingCleanupVar});`);
523
+ emit(context, indent, `const ${clearFallbackVar} = () => __mikuru_runCleanup(${fallbackCleanupVar});`);
524
+ emit(context, indent, `const ${clearTimersVar} = () => {`);
525
+ emit(context, indent + 1, `if (${delayTimerVar}) { clearTimeout(${delayTimerVar}); ${delayTimerVar} = undefined; }`);
526
+ emit(context, indent + 1, `if (${timeoutTimerVar}) { clearTimeout(${timeoutTimerVar}); ${timeoutTimerVar} = undefined; }`);
527
+ emit(context, indent, "};");
528
+ emit(context, indent, `const ${renderLoadingVar} = () => {`);
529
+ emit(context, indent + 1, `if (${failedVar}) { return; }`);
530
+ emit(context, indent + 1, `${clearLoadingVar}();`);
531
+ emit(context, indent + 1, `const ${loadingVar} = unwrap(${loadingExpression});`);
532
+ emit(context, indent + 1, `if (!${loadingVar} || typeof ${loadingVar}.mount !== "function") { return; }`);
533
+ emit(context, indent + 1, `const ${loadingFragmentVar} = document.createDocumentFragment();`);
534
+ emit(context, indent + 1, `const ${loadingInstanceVar} = ${loadingVar}.mount(${loadingFragmentVar}, { pending: ${pendingVar}, __mikuru_context });`);
535
+ emit(context, indent + 1, `${loadingCleanupVar}.push(() => ${loadingInstanceVar}.unmount());`);
536
+ appendNode(context, parentVar, loadingFragmentVar, indent + 1, endVar);
537
+ emit(context, indent, "};");
538
+ emit(context, indent, `const ${renderFallbackVar} = (${errorVar}, ${errorInfoVar} = __mikuru_errorInfo("async-loader")) => {`);
539
+ emit(context, indent + 1, `${failedVar} = true;`);
540
+ emit(context, indent + 1, `${clearTimersVar}();`);
541
+ emit(context, indent + 1, `${clearLoadingVar}();`);
542
+ emit(context, indent + 1, `${clearFallbackVar}();`);
543
+ emit(context, indent + 1, `__mikuru_runCleanup(${boundaryCleanupVar});`);
544
+ emitRemoveBetween(context, indent + 1, startVar, endVar);
545
+ emit(context, indent + 1, `const ${fallbackVar} = unwrap(${fallbackExpression});`);
546
+ emit(context, indent + 1, `if (!${fallbackVar} || typeof ${fallbackVar}.mount !== "function") { throw ${errorVar}; }`);
547
+ emit(context, indent + 1, `const ${normalizedErrorInfoVar} = ${errorInfoVar} && typeof ${errorInfoVar} === "object" ? ${errorInfoVar} : {};`);
548
+ if (context.debug) {
549
+ emit(context, indent + 1, `emitDebugEvent("async:rejected", { component: __mikuru_componentInfo, componentId: __mikuru_debug.id, error: ${errorVar}, errors: [...${errorsVar}], pending: ${pendingVar}, errorInfo: { ...${normalizedErrorInfoVar}, boundary: __mikuru_componentInfo } });`);
550
+ }
551
+ emit(context, indent + 1, `const ${fallbackFragmentVar} = document.createDocumentFragment();`);
552
+ emit(context, indent + 1, `const ${fallbackInstanceVar} = ${fallbackVar}.mount(${fallbackFragmentVar}, { error: ${errorVar}, errors: [...${errorsVar}], errorInfo: { ...${normalizedErrorInfoVar}, boundary: __mikuru_componentInfo }, pending: ${pendingVar}, retry: ${lastRetryVar}, reset: ${lastRetryVar}, __mikuru_context });`);
553
+ emit(context, indent + 1, `${fallbackCleanupVar}.push(() => ${fallbackInstanceVar}.unmount());`);
554
+ appendNode(context, parentVar, fallbackFragmentVar, indent + 1, endVar);
555
+ emit(context, indent, "};");
556
+ emit(context, indent, `const ${scheduleLoadingVar} = () => {`);
557
+ emit(context, indent + 1, `const ${delayVar} = Number(unwrap(${delayExpression}) ?? 0);`);
558
+ emit(context, indent + 1, `if (${delayVar} <= 0) { ${renderLoadingVar}(); return; }`);
559
+ emit(context, indent + 1, `if (${delayTimerVar}) { return; }`);
560
+ emit(context, indent + 1, `${delayTimerVar} = setTimeout(() => {`);
561
+ emit(context, indent + 2, `${delayTimerVar} = undefined;`);
562
+ emit(context, indent + 2, `if (${pendingVar} > 0 && !${failedVar}) { ${renderLoadingVar}(); }`);
563
+ emit(context, indent + 1, `}, ${delayVar});`);
564
+ emit(context, indent, "};");
565
+ emit(context, indent, `const ${scheduleTimeoutVar} = () => {`);
566
+ emit(context, indent + 1, `const ${timeoutVar} = unwrap(${timeoutExpression});`);
567
+ emit(context, indent + 1, `if (${timeoutVar} == null || Number(${timeoutVar}) <= 0 || ${timeoutTimerVar}) { return; }`);
568
+ emit(context, indent + 1, `${timeoutTimerVar} = setTimeout(() => {`);
569
+ emit(context, indent + 2, `${timeoutTimerVar} = undefined;`);
570
+ emit(context, indent + 2, `if (${pendingVar} <= 0 || ${failedVar}) { return; }`);
571
+ emit(context, indent + 2, `const ${timeoutErrorVar} = new Error("Async boundary timed out");`);
572
+ emit(context, indent + 2, `${errorsVar}.push(${timeoutErrorVar});`);
573
+ emit(context, indent + 2, `${renderFallbackVar}(${timeoutErrorVar}, __mikuru_errorInfo("async-timeout"));`);
574
+ emit(context, indent + 1, `}, Number(${timeoutVar}));`);
575
+ emit(context, indent, "};");
576
+ emit(context, indent, `const ${asyncContextVar} = { parent: __mikuru_context, provides: new Map(), errorHandler: __mikuru_context.errorHandler, asyncBoundary: {`);
577
+ emit(context, indent + 1, `start({ retry: ${retryVar} }) {`);
578
+ emit(context, indent + 2, `${pendingVar} += 1;`);
579
+ emit(context, indent + 2, `${lastRetryVar} = ${renderVar};`);
580
+ if (context.debug) {
581
+ emit(context, indent + 2, `emitDebugEvent("async:pending", { component: __mikuru_componentInfo, componentId: __mikuru_debug.id, pending: ${pendingVar} });`);
582
+ }
583
+ emit(context, indent + 2, `if (${pendingVar} === 1) { ${scheduleLoadingVar}(); ${scheduleTimeoutVar}(); } else if (${loadingCleanupVar}.length > 0) { ${renderLoadingVar}(); }`);
584
+ emit(context, indent + 2, `let ${settledVar} = false;`);
585
+ emit(context, indent + 2, "return {");
586
+ emit(context, indent + 3, "resolve() {");
587
+ emit(context, indent + 4, `if (${settledVar}) { return; }`);
588
+ emit(context, indent + 4, `${settledVar} = true;`);
589
+ emit(context, indent + 4, `${pendingVar} = Math.max(0, ${pendingVar} - 1);`);
590
+ if (context.debug) {
591
+ emit(context, indent + 4, `emitDebugEvent("async:resolved", { component: __mikuru_componentInfo, componentId: __mikuru_debug.id, pending: ${pendingVar} });`);
592
+ }
593
+ emit(context, indent + 4, `if (${pendingVar} === 0) { ${clearTimersVar}(); ${clearLoadingVar}(); } else if (${loadingCleanupVar}.length > 0) { ${renderLoadingVar}(); }`);
594
+ emit(context, indent + 3, "},");
595
+ emit(context, indent + 3, `reject(${errorVar}, ${errorInfoVar}) {`);
596
+ emit(context, indent + 4, `if (${settledVar}) { return; }`);
597
+ emit(context, indent + 4, `${settledVar} = true;`);
598
+ emit(context, indent + 4, `${pendingVar} = Math.max(0, ${pendingVar} - 1);`);
599
+ emit(context, indent + 4, `${errorsVar}.push(${errorVar});`);
600
+ emit(context, indent + 4, `${clearTimersVar}();`);
601
+ emit(context, indent + 4, `${renderFallbackVar}(${errorVar}, ${errorInfoVar});`);
602
+ emit(context, indent + 3, "}");
603
+ emit(context, indent + 2, "};");
304
604
  emit(context, indent + 1, "}");
605
+ emit(context, indent, `}, ...__mikuru_componentInfo };`);
606
+ emit(context, indent, `const ${renderVar} = () => {`);
607
+ emit(context, indent + 1, `${failedVar} = false;`);
608
+ emit(context, indent + 1, `${pendingVar} = 0;`);
609
+ emit(context, indent + 1, `${errorsVar} = [];`);
610
+ emit(context, indent + 1, `${lastRetryVar} = ${renderVar};`);
611
+ emit(context, indent + 1, `${clearTimersVar}();`);
612
+ emit(context, indent + 1, `${clearLoadingVar}();`);
613
+ emit(context, indent + 1, `${clearFallbackVar}();`);
614
+ emit(context, indent + 1, `__mikuru_runCleanup(${boundaryCleanupVar});`);
615
+ emitRemoveBetween(context, indent + 1, startVar, endVar);
616
+ context.componentContextVar = asyncContextVar;
617
+ generateChildren(context, children, parentVar, boundaryCleanupVar, indent + 1, endVar);
618
+ context.componentContextVar = previousComponentContextVar;
305
619
  emit(context, indent, "};");
306
620
  emit(context, indent, `${renderVar}();`);
307
621
  emit(context, indent, `${cleanupVar}.push(() => {`);
622
+ emit(context, indent + 1, `${clearTimersVar}();`);
623
+ emit(context, indent + 1, `${clearLoadingVar}();`);
624
+ emit(context, indent + 1, `${clearFallbackVar}();`);
308
625
  emit(context, indent + 1, `__mikuru_runCleanup(${boundaryCleanupVar});`);
309
626
  emitRemoveBetween(context, indent + 1, startVar, endVar);
310
627
  emit(context, indent + 1, `${startVar}.remove();`);
@@ -458,6 +775,149 @@ function generateDynamicComponent(context, node, parentVar, cleanupVar, indent,
458
775
  emit(context, indent, "});");
459
776
  return startVar;
460
777
  }
778
+ function generateKeepAlive(context, node, parentVar, cleanupVar, indent, beforeVar) {
779
+ validateKeepAliveAttributes(context, node);
780
+ const children = getSingleElementChild(context, node, "<KeepAlive>");
781
+ const child = children[0];
782
+ if (child.tag !== "component") {
783
+ throwTemplateError("<KeepAlive> requires a single <component :is=\"...\" /> child in v1", context, child.loc);
784
+ }
785
+ const isAttr = child.attrs.find((attr) => getBindingName(attr.name) === "is");
786
+ if (!isAttr) {
787
+ throwTemplateError("<KeepAlive> dynamic child requires :is to resolve to a component object", context, child.loc);
788
+ }
789
+ const expression = compileTemplateExpression(requireAttrValue(isAttr), isAttr.name, toExpressionContext(context, isAttr.valueLoc));
790
+ const includeExpression = getKeepAliveOptionExpression(context, node, "include");
791
+ const excludeExpression = getKeepAliveOptionExpression(context, node, "exclude");
792
+ const maxExpression = getKeepAliveOptionExpression(context, node, "max");
793
+ const dynamicNode = withoutAttrs(child, ["is", ":is", "v-bind:is"]);
794
+ const startVar = nextVar(context, "keepAliveStart");
795
+ const endVar = nextVar(context, "keepAliveEnd");
796
+ const cacheVar = nextVar(context, "keepAliveCache");
797
+ const currentTypeVar = nextVar(context, "keepAliveCurrent");
798
+ const currentRecordVar = nextVar(context, "keepAliveCurrent");
799
+ const componentTypeVar = nextVar(context, "keepAliveType");
800
+ const componentNameVar = nextVar(context, "keepAliveName");
801
+ const componentNameHelperVar = nextVar(context, "keepAliveName");
802
+ const matchHelperVar = nextVar(context, "keepAliveMatches");
803
+ const includeVar = nextVar(context, "keepAliveInclude");
804
+ const excludeVar = nextVar(context, "keepAliveExclude");
805
+ const maxVar = nextVar(context, "keepAliveMax");
806
+ const cacheableVar = nextVar(context, "keepAliveCacheable");
807
+ const recordVar = nextVar(context, "keepAliveRecord");
808
+ const recordNodesVar = nextVar(context, "keepAliveNodes");
809
+ const activateRecordVar = nextVar(context, "keepAliveActivate");
810
+ const deactivateRecordVar = nextVar(context, "keepAliveDeactivate");
811
+ const collectNodesVar = nextVar(context, "keepAliveCollectNodes");
812
+ const collectNodeVar = nextVar(context, "keepAliveNode");
813
+ const staleKeyVar = nextVar(context, "keepAliveStaleKey");
814
+ const staleRecordVar = nextVar(context, "keepAliveStaleRecord");
815
+ const fragmentVar = nextVar(context, "fragment");
816
+ const attrsVar = nextVar(context, "attrs");
817
+ const propsVar = nextVar(context, "props");
818
+ const componentVar = nextVar(context, "component");
819
+ const recordCleanupVar = nextVar(context, "keepAliveCleanup");
820
+ const stopVar = nextVar(context, "stop");
821
+ emit(context, indent, `const ${startVar} = document.createComment("keep-alive");`);
822
+ emit(context, indent, `const ${endVar} = document.createComment("/keep-alive");`);
823
+ appendNode(context, parentVar, startVar, indent, beforeVar);
824
+ appendNode(context, parentVar, endVar, indent, beforeVar);
825
+ emit(context, indent, `const ${cacheVar} = new Map();`);
826
+ emit(context, indent, `let ${currentTypeVar};`);
827
+ emit(context, indent, `let ${currentRecordVar};`);
828
+ emit(context, indent, `const ${componentNameHelperVar} = (component) => component?.name ?? component?.displayName ?? component?.__name ?? component?.constructor?.name ?? "";`);
829
+ emit(context, indent, `const ${matchHelperVar} = (pattern, name) => pattern == null ? false : typeof pattern === "string" ? pattern.split(",").map((part) => part.trim()).filter(Boolean).includes(name) : Array.isArray(pattern) ? pattern.some((entry) => ${matchHelperVar}(entry, name)) : pattern instanceof RegExp ? pattern.test(name) : false;`);
830
+ emit(context, indent, `const ${activateRecordVar} = (record) => { if (record && !record.active) { record.active = true; record.instance.activate?.(); } };`);
831
+ emit(context, indent, `const ${deactivateRecordVar} = (record) => { if (record?.active) { record.active = false; record.instance.deactivate?.(); } };`);
832
+ emit(context, indent, `const ${collectNodesVar} = () => { const nodes = []; for (let ${collectNodeVar} = ${startVar}.nextSibling; ${collectNodeVar} && ${collectNodeVar} !== ${endVar}; ${collectNodeVar} = ${collectNodeVar}.nextSibling) { nodes.push(${collectNodeVar}); } return nodes; };`);
833
+ emit(context, indent, `const ${stopVar} = effect(() => {`);
834
+ emit(context, indent + 1, `const ${componentTypeVar} = unwrap(${expression});`);
835
+ emit(context, indent + 1, `if (${componentTypeVar} === ${currentTypeVar}) { return; }`);
836
+ emit(context, indent + 1, `if (${currentRecordVar}) { ${currentRecordVar}.nodes = ${collectNodesVar}(); }`);
837
+ emit(context, indent + 1, `if (${currentRecordVar}?.transient) { __mikuru_runCleanup(${currentRecordVar}.cleanups); } else { ${deactivateRecordVar}(${currentRecordVar}); }`);
838
+ emitRemoveBetween(context, indent + 1, startVar, endVar);
839
+ emit(context, indent + 1, `if (!${componentTypeVar}) { ${currentTypeVar} = ${componentTypeVar}; ${currentRecordVar} = undefined; return; }`);
840
+ emit(context, indent + 1, `if (typeof ${componentTypeVar} !== "object" || typeof ${componentTypeVar}.mount !== "function") {`);
841
+ emit(context, indent + 2, `throw new Error("KeepAlive child :is must resolve to a component object with mount()");`);
842
+ emit(context, indent + 1, "}");
843
+ emit(context, indent + 1, `const ${componentNameVar} = ${componentNameHelperVar}(${componentTypeVar});`);
844
+ emit(context, indent + 1, `const ${includeVar} = unwrap(${includeExpression ?? "undefined"});`);
845
+ emit(context, indent + 1, `const ${excludeVar} = unwrap(${excludeExpression ?? "undefined"});`);
846
+ emit(context, indent + 1, `const ${maxVar} = Number(unwrap(${maxExpression ?? "undefined"}));`);
847
+ emit(context, indent + 1, `const ${cacheableVar} = (${includeVar} == null || ${matchHelperVar}(${includeVar}, ${componentNameVar})) && !${matchHelperVar}(${excludeVar}, ${componentNameVar});`);
848
+ emit(context, indent + 1, `if (!${cacheableVar}) {`);
849
+ emit(context, indent + 2, `const ${fragmentVar} = document.createDocumentFragment();`);
850
+ emit(context, indent + 2, `const ${recordCleanupVar} = [];`);
851
+ emitComponentAttrs(context, dynamicNode, attrsVar, indent + 2);
852
+ emitComponentProps(context, dynamicNode, propsVar, attrsVar, indent + 2);
853
+ emit(context, indent + 2, `const ${componentVar} = ${componentTypeVar}.mount(${fragmentVar}, ${propsVar});`);
854
+ if (context.scopeAttr) {
855
+ emit(context, indent + 2, `if (${componentVar}.element?.nodeType === 1) {`);
856
+ emit(context, indent + 3, `${componentVar}.element.setAttribute(${quote(context.scopeAttr)}, "");`);
857
+ emit(context, indent + 2, "}");
858
+ }
859
+ emit(context, indent + 2, `if (${componentTypeVar}.inheritAttrs !== false) {`);
860
+ emitComponentFallthrough(context, dynamicNode, componentVar, recordCleanupVar, indent + 3);
861
+ emit(context, indent + 2, "}");
862
+ emitComponentShow(context, dynamicNode, componentVar, recordCleanupVar, indent + 2);
863
+ emitTemplateRef(context, dynamicNode, componentVar, recordCleanupVar, indent + 2);
864
+ emit(context, indent + 2, `const ${recordNodesVar} = Array.from(${fragmentVar}.childNodes);`);
865
+ appendNode(context, parentVar, fragmentVar, indent + 2, endVar);
866
+ emit(context, indent + 2, `${currentTypeVar} = ${componentTypeVar};`);
867
+ emit(context, indent + 2, `${currentRecordVar} = { instance: ${componentVar}, element: ${componentVar}.element, nodes: ${recordNodesVar}, cleanups: ${recordCleanupVar}, transient: true };`);
868
+ emit(context, indent + 2, `${recordCleanupVar}.push(() => ${componentVar}.unmount());`);
869
+ emit(context, indent + 2, "return;");
870
+ emit(context, indent + 1, "}");
871
+ emit(context, indent + 1, `let ${recordVar} = ${cacheVar}.get(${componentTypeVar});`);
872
+ emit(context, indent + 1, `if (!${recordVar}) {`);
873
+ emit(context, indent + 2, `const ${fragmentVar} = document.createDocumentFragment();`);
874
+ emit(context, indent + 2, `const ${recordCleanupVar} = [];`);
875
+ emitComponentAttrs(context, dynamicNode, attrsVar, indent + 2);
876
+ emitComponentProps(context, dynamicNode, propsVar, attrsVar, indent + 2);
877
+ emit(context, indent + 2, `const ${componentVar} = ${componentTypeVar}.mount(${fragmentVar}, ${propsVar});`);
878
+ if (context.scopeAttr) {
879
+ emit(context, indent + 2, `if (${componentVar}.element?.nodeType === 1) {`);
880
+ emit(context, indent + 3, `${componentVar}.element.setAttribute(${quote(context.scopeAttr)}, "");`);
881
+ emit(context, indent + 2, "}");
882
+ }
883
+ emit(context, indent + 2, `if (${componentTypeVar}.inheritAttrs !== false) {`);
884
+ emitComponentFallthrough(context, dynamicNode, componentVar, recordCleanupVar, indent + 3);
885
+ emit(context, indent + 2, "}");
886
+ emitComponentShow(context, dynamicNode, componentVar, recordCleanupVar, indent + 2);
887
+ emitTemplateRef(context, dynamicNode, componentVar, recordCleanupVar, indent + 2);
888
+ emit(context, indent + 2, `const ${recordNodesVar} = Array.from(${fragmentVar}.childNodes);`);
889
+ emit(context, indent + 2, `${recordVar} = { instance: ${componentVar}, element: ${componentVar}.element, nodes: ${recordNodesVar}, cleanups: ${recordCleanupVar}, active: false };`);
890
+ emit(context, indent + 2, `${cacheVar}.set(${componentTypeVar}, ${recordVar});`);
891
+ emit(context, indent + 2, `if (Number.isFinite(${maxVar}) && ${maxVar} > 0) {`);
892
+ emit(context, indent + 3, `while (${cacheVar}.size > ${maxVar}) {`);
893
+ emit(context, indent + 4, `const ${staleKeyVar} = ${cacheVar}.keys().next().value;`);
894
+ emit(context, indent + 4, `if (${staleKeyVar} === ${componentTypeVar}) { break; }`);
895
+ emit(context, indent + 4, `const ${staleRecordVar} = ${cacheVar}.get(${staleKeyVar});`);
896
+ emit(context, indent + 4, `${cacheVar}.delete(${staleKeyVar});`);
897
+ emit(context, indent + 4, `if (${staleRecordVar}) { ${deactivateRecordVar}(${staleRecordVar}); __mikuru_runCleanup(${staleRecordVar}.cleanups); ${staleRecordVar}.instance.unmount(); }`);
898
+ emit(context, indent + 3, "}");
899
+ emit(context, indent + 2, "}");
900
+ appendNode(context, parentVar, fragmentVar, indent + 2, endVar);
901
+ emit(context, indent + 1, "} else {");
902
+ emit(context, indent + 2, `${cacheVar}.delete(${componentTypeVar});`);
903
+ emit(context, indent + 2, `${cacheVar}.set(${componentTypeVar}, ${recordVar});`);
904
+ emit(context, indent + 2, `for (const node of ${recordVar}.nodes) { ${parentVar}.insertBefore(node, ${endVar}); }`);
905
+ emit(context, indent + 1, "}");
906
+ emit(context, indent + 1, `${currentTypeVar} = ${componentTypeVar};`);
907
+ emit(context, indent + 1, `${currentRecordVar} = ${recordVar};`);
908
+ emit(context, indent + 1, `${activateRecordVar}(${recordVar});`);
909
+ emit(context, indent, "});");
910
+ emit(context, indent, `${cleanupVar}.push(() => {`);
911
+ emit(context, indent + 1, `${stopVar}();`);
912
+ emit(context, indent + 1, `if (${currentRecordVar}?.transient) { __mikuru_runCleanup(${currentRecordVar}.cleanups); } else { ${deactivateRecordVar}(${currentRecordVar}); }`);
913
+ emit(context, indent + 1, `for (const ${recordVar} of ${cacheVar}.values()) { __mikuru_runCleanup(${recordVar}.cleanups); ${recordVar}.instance.unmount(); }`);
914
+ emit(context, indent + 1, `${cacheVar}.clear();`);
915
+ emitRemoveBetween(context, indent + 1, startVar, endVar);
916
+ emit(context, indent + 1, `${startVar}.remove();`);
917
+ emit(context, indent + 1, `${endVar}.remove();`);
918
+ emit(context, indent, "});");
919
+ return startVar;
920
+ }
461
921
  function emitComponentShow(context, node, componentVar, cleanupVar, indent) {
462
922
  const showAttr = node.attrs.find((attr) => attr.name === "v-show");
463
923
  if (!showAttr) {
@@ -479,7 +939,10 @@ function emitComponentShow(context, node, componentVar, cleanupVar, indent) {
479
939
  function emitComponentAttrs(context, node, attrsVar, indent) {
480
940
  const objectBindExpressions = node.attrs
481
941
  .filter((attr) => isObjectBindAttr(attr))
482
- .map((attr) => compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc)));
942
+ .map((attr) => {
943
+ validateObjectBindModifiers(parseObjectBindDirective(attr.name) ?? { modifiers: [] }, attr, context, "component");
944
+ return compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
945
+ });
483
946
  const classParts = componentFallthroughExpressions(context, node, "class", objectBindExpressions);
484
947
  const styleParts = componentFallthroughExpressions(context, node, "style", objectBindExpressions);
485
948
  const directAttrs = componentDirectAttributeFallthroughs(context, node);
@@ -553,7 +1016,10 @@ function emitComponentAttrsProxy(context, attrsVar, baseVar, objectBindExpressio
553
1016
  function emitComponentFallthrough(context, node, componentVar, cleanupVar, indent) {
554
1017
  const objectBindExpressions = node.attrs
555
1018
  .filter((attr) => isObjectBindAttr(attr))
556
- .map((attr) => compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc)));
1019
+ .map((attr) => {
1020
+ validateObjectBindModifiers(parseObjectBindDirective(attr.name) ?? { modifiers: [] }, attr, context, "component");
1021
+ return compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
1022
+ });
557
1023
  const classParts = componentFallthroughExpressions(context, node, "class", objectBindExpressions);
558
1024
  const styleParts = componentFallthroughExpressions(context, node, "style", objectBindExpressions);
559
1025
  const directAttrs = componentDirectAttributeFallthroughs(context, node);
@@ -688,7 +1154,7 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
688
1154
  if (slotDirectiveAttr) {
689
1155
  throwTemplateError("v-slot must be used on a <template> child in Mikuru", context, slotDirectiveAttr.loc);
690
1156
  }
691
- validateAttributes(node);
1157
+ validateAttributes(context, node);
692
1158
  const elementVar = nextVar(context, "el");
693
1159
  emit(context, indent, `const ${elementVar} = document.createElement(${quote(node.tag)});`);
694
1160
  if (context.scopeAttr) {
@@ -704,11 +1170,20 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
704
1170
  emit(context, indent, `setAttribute(${elementVar}, ${quote(attr.name)}, ${quote(attr.value === true ? "" : attr.value)});`);
705
1171
  }
706
1172
  emitTemplateRef(context, node, elementVar, cleanupVar, indent);
707
- generateChildren(context, node.children, elementVar, cleanupVar, indent);
1173
+ const contentDirective = getContentDirectiveAttr(node);
1174
+ if (contentDirective) {
1175
+ emitContentDirective(context, node, elementVar, contentDirective, cleanupVar, indent);
1176
+ }
1177
+ else {
1178
+ generateChildren(context, node.children, elementVar, cleanupVar, indent);
1179
+ }
708
1180
  for (const attr of node.attrs) {
709
1181
  const modelDirective = parseModelDirective(attr.name);
710
1182
  if (modelDirective) {
711
1183
  validateModelModifiers(modelDirective, attr, context);
1184
+ if (modelDirective.argument) {
1185
+ throwTemplateError("v-model arguments are only supported on components in v1", context, attr.loc);
1186
+ }
712
1187
  const expression = validateAssignableExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
713
1188
  const stopVar = nextVar(context, "stop");
714
1189
  const handlerVar = nextVar(context, "handler");
@@ -726,15 +1201,17 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
726
1201
  const eventName = modelMode === "text" && !modelDirective.modifiers.includes("lazy") ? "input" : "change";
727
1202
  const propertyName = modelMode === "checkbox" || modelMode === "radio" ? "checked" : "value";
728
1203
  const renderedValue = modelMode === "checkbox"
729
- ? `Boolean(unwrap(${expression}))`
1204
+ ? `(() => { const value = unwrap(${expression}); const checkboxValue = ${modelElementValueExpression(`${elementVar}`, modelDirective.modifiers)}; return Array.isArray(value) ? value.some((item) => Object.is(item, checkboxValue)) : Boolean(value); })()`
730
1205
  : modelMode === "radio"
731
- ? `(${modelDirective.modifiers.includes("number") ? `Number(${elementVar}.getAttribute("value") ?? "on")` : `(${elementVar}.getAttribute("value") ?? "on")`} === unwrap(${expression}))`
1206
+ ? `Object.is(${modelElementValueExpression(`${elementVar}`, modelDirective.modifiers)}, unwrap(${expression}))`
732
1207
  : modelMode === "select-multiple"
733
- ? `Array.from(${elementVar}.options).forEach((option) => { option.selected = (unwrap(${expression}) ?? []).map(String).includes(option.getAttribute("value") ?? option.textContent ?? ""); })`
734
- : `String(unwrap(${expression}) ?? "")`;
735
- const assignedValue = modelAssignedValue(modelMode, modelDirective.modifiers);
1208
+ ? `Array.from(${elementVar}.options).forEach((option) => { const optionValue = ${modelElementValueExpression("option", modelDirective.modifiers)}; option.selected = (unwrap(${expression}) ?? []).some((item) => Object.is(item, optionValue)); })`
1209
+ : modelMode === "select"
1210
+ ? `Array.from(${elementVar}.options).forEach((option) => { option.selected = Object.is(${modelElementValueExpression("option", modelDirective.modifiers)}, unwrap(${expression})); })`
1211
+ : `String(unwrap(${expression}) ?? "")`;
1212
+ const assignedValue = modelAssignedValue(modelMode, modelDirective.modifiers, expression);
736
1213
  emit(context, indent, `const ${stopVar} = effect(() => {`);
737
- if (modelMode === "select-multiple") {
1214
+ if (modelMode === "select-multiple" || modelMode === "select") {
738
1215
  emit(context, indent + 1, renderedValue);
739
1216
  }
740
1217
  else {
@@ -744,20 +1221,25 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
744
1221
  }
745
1222
  emit(context, indent, "});");
746
1223
  emit(context, indent, `${cleanupVar}.push(${stopVar});`);
747
- emit(context, indent, `const ${handlerVar} = ($event) => {`);
1224
+ emit(context, indent, `const ${handlerVar} = __mikuru_guardEventHandler(($event) => {`);
748
1225
  emit(context, indent + 1, `${expression}.value = ${assignedValue};`);
749
- emit(context, indent, "};");
1226
+ emit(context, indent, "});");
750
1227
  emit(context, indent, `${elementVar}.addEventListener(${quote(eventName)}, ${handlerVar});`);
751
1228
  emit(context, indent, `${cleanupVar}.push(() => ${elementVar}.removeEventListener(${quote(eventName)}, ${handlerVar}));`);
752
1229
  continue;
753
1230
  }
754
1231
  if (attr.name === "v-show") {
755
1232
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
756
- const stopVar = nextVar(context, "stop");
757
- emit(context, indent, `const ${stopVar} = effect(() => {`);
758
- emit(context, indent + 1, `${elementVar}.style.display = unwrap(${expression}) ? "" : "none";`);
759
- emit(context, indent, "});");
760
- emit(context, indent, `${cleanupVar}.push(${stopVar});`);
1233
+ if (context.once) {
1234
+ emit(context, indent, `${elementVar}.style.display = unwrap(${expression}) ? "" : "none";`);
1235
+ }
1236
+ else {
1237
+ const stopVar = nextVar(context, "stop");
1238
+ emit(context, indent, `const ${stopVar} = effect(() => {`);
1239
+ emit(context, indent + 1, `${elementVar}.style.display = unwrap(${expression}) ? "" : "none";`);
1240
+ emit(context, indent, "});");
1241
+ emit(context, indent, `${cleanupVar}.push(${stopVar});`);
1242
+ }
761
1243
  continue;
762
1244
  }
763
1245
  if (isObjectBindAttr(attr)) {
@@ -771,16 +1253,22 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
771
1253
  const event = parseEventDirective(attr.name);
772
1254
  if (event) {
773
1255
  validateEventModifiers(event, attr, context);
774
- const handler = validateTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
1256
+ const handler = validateEventHandlerExpression(requireAttrValue(attr), context, attr.valueLoc);
775
1257
  const handlerVar = nextVar(context, "handler");
776
1258
  const handlerExpression = eventHandlerExpression(handler, context, attr.valueLoc);
777
1259
  if (event.modifiers.length) {
778
1260
  const baseHandlerVar = nextVar(context, "handler");
1261
+ const errorHandlerVar = nextVar(context, "errorHandler");
779
1262
  emit(context, indent, `const ${baseHandlerVar} = ${handlerExpression};`);
780
- emit(context, indent, `const ${handlerVar} = ($event) => {`);
1263
+ emit(context, indent, `const ${errorHandlerVar} = __mikuru_context.errorHandler;`);
1264
+ emit(context, indent, `const ${handlerVar} = ($event) => __mikuru_try(() => {`);
781
1265
  if (event.modifiers.includes("self")) {
782
1266
  emit(context, indent + 1, `if ($event.target !== ${elementVar}) { return; }`);
783
1267
  }
1268
+ const modifierGuard = eventModifierGuardExpression(event);
1269
+ if (modifierGuard) {
1270
+ emit(context, indent + 1, `if (${modifierGuard}) { return; }`);
1271
+ }
784
1272
  if (event.modifiers.includes("prevent")) {
785
1273
  emit(context, indent + 1, "$event.preventDefault();");
786
1274
  }
@@ -788,35 +1276,138 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
788
1276
  emit(context, indent + 1, "$event.stopPropagation();");
789
1277
  }
790
1278
  emit(context, indent + 1, `return ${baseHandlerVar}($event);`);
791
- emit(context, indent, "};");
1279
+ emit(context, indent, `}, ${errorHandlerVar}, "event");`);
792
1280
  }
793
1281
  else {
794
- emit(context, indent, `const ${handlerVar} = ${handlerExpression};`);
1282
+ emit(context, indent, `const ${handlerVar} = __mikuru_guardEventHandler(${handlerExpression});`);
1283
+ }
1284
+ if (event.nameExpression) {
1285
+ emitDynamicEventListener(context, elementVar, event, handlerVar, attr, cleanupVar, indent);
1286
+ }
1287
+ else {
1288
+ const eventOptions = eventListenerOptions(event);
1289
+ emit(context, indent, `${elementVar}.addEventListener(${quote(event.name ?? "")}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
1290
+ emit(context, indent, `${cleanupVar}.push(() => ${elementVar}.removeEventListener(${quote(event.name ?? "")}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""}));`);
795
1291
  }
796
- const eventOptions = eventListenerOptions(event);
797
- emit(context, indent, `${elementVar}.addEventListener(${quote(event.name)}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
798
- emit(context, indent, `${cleanupVar}.push(() => ${elementVar}.removeEventListener(${quote(event.name)}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""}));`);
799
1292
  continue;
800
1293
  }
801
- const bindingName = getBindingName(attr.name);
802
- if (bindingName) {
1294
+ const bindDirective = parseBindDirective(attr.name);
1295
+ const dynamicBinding = bindDirective?.nameExpression ? bindDirective : undefined;
1296
+ if (dynamicBinding) {
1297
+ validateBindModifiers(dynamicBinding, attr, context, "element");
1298
+ emitDynamicAttributeBinding(context, node, elementVar, attr, dynamicBinding, cleanupVar, indent);
1299
+ continue;
1300
+ }
1301
+ const bindingName = bindDirective?.name;
1302
+ if (bindingName && bindDirective) {
1303
+ validateBindModifiers(bindDirective, attr, context, "element");
803
1304
  if (bindingName === "ref") {
804
1305
  continue;
805
1306
  }
806
1307
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
807
1308
  const stopVar = nextVar(context, "stop");
808
- const valueExpression = bindingName === "class" && getStaticAttrValue(node, "class")
809
- ? `[${quote(getStaticAttrValue(node, "class"))}, ${expression}]`
810
- : expression;
811
- emit(context, indent, `const ${stopVar} = effect(() => {`);
812
- emit(context, indent + 1, `setAttribute(${elementVar}, ${quote(bindingName)}, unwrap(${valueExpression}));`);
813
- emit(context, indent, "});");
814
- emit(context, indent, `${cleanupVar}.push(${stopVar});`);
1309
+ const staticClass = getStaticAttrValue(node, "class");
1310
+ const staticStyle = getStaticAttrValue(node, "style");
1311
+ const valueExpression = bindingName === "class" && staticClass
1312
+ ? `[${quote(staticClass)}, ${expression}]`
1313
+ : bindingName === "style" && staticStyle
1314
+ ? `[${quote(staticStyle)}, ${expression}]`
1315
+ : expression;
1316
+ if (context.once) {
1317
+ emit(context, indent, `setAttribute(${elementVar}, ${quote(bindingName)}, unwrap(${valueExpression})${bindOptionsExpression(bindDirective)});`);
1318
+ }
1319
+ else {
1320
+ emit(context, indent, `const ${stopVar} = effect(() => {`);
1321
+ emit(context, indent + 1, `setAttribute(${elementVar}, ${quote(bindingName)}, unwrap(${valueExpression})${bindOptionsExpression(bindDirective)});`);
1322
+ emit(context, indent, "});");
1323
+ emit(context, indent, `${cleanupVar}.push(${stopVar});`);
1324
+ }
815
1325
  }
816
1326
  }
817
1327
  appendNode(context, parentVar, elementVar, indent, beforeVar);
818
1328
  return elementVar;
819
1329
  }
1330
+ function emitDynamicAttributeBinding(context, node, elementVar, attr, binding, cleanupVar, indent) {
1331
+ const compiledName = compileTemplateExpression(binding.nameExpression ?? "", attr.name, toExpressionContext(context, attr.loc));
1332
+ const compiledValue = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
1333
+ const staticClass = getStaticAttrValue(node, "class");
1334
+ const staticStyle = getStaticAttrValue(node, "style");
1335
+ const previousNameVar = nextVar(context, "attrName");
1336
+ const nameVar = nextVar(context, "attrName");
1337
+ const valueVar = nextVar(context, "attrValue");
1338
+ const stopVar = nextVar(context, "stop");
1339
+ emit(context, indent, `let ${previousNameVar};`);
1340
+ emit(context, indent, `const ${stopVar} = effect(() => {`);
1341
+ emit(context, indent + 1, `const ${nameVar} = ${bindNameExpression(`String(unwrap(${compiledName}) ?? "")`, binding)};`);
1342
+ emit(context, indent + 1, `if (!${nameVar}) { if (${previousNameVar}) setAttribute(${elementVar}, ${previousNameVar}, null); ${previousNameVar} = undefined; return; }`);
1343
+ emit(context, indent + 1, `if (${previousNameVar} && ${previousNameVar} !== ${nameVar}) { setAttribute(${elementVar}, ${previousNameVar}, null); }`);
1344
+ emit(context, indent + 1, `const ${valueVar} = unwrap(${compiledValue});`);
1345
+ if (staticClass || staticStyle) {
1346
+ emit(context, indent + 1, `setAttribute(${elementVar}, ${nameVar}, ${nameVar} === "class" && ${staticClass ? "true" : "false"} ? [${quote(staticClass ?? "")}, ${valueVar}] : ${nameVar} === "style" && ${staticStyle ? "true" : "false"} ? [${quote(staticStyle ?? "")}, ${valueVar}] : ${valueVar}${bindOptionsExpression(binding)});`);
1347
+ }
1348
+ else {
1349
+ emit(context, indent + 1, `setAttribute(${elementVar}, ${nameVar}, ${valueVar}${bindOptionsExpression(binding)});`);
1350
+ }
1351
+ emit(context, indent + 1, `${previousNameVar} = ${nameVar};`);
1352
+ emit(context, indent, "});");
1353
+ emit(context, indent, `${cleanupVar}.push(() => { ${stopVar}(); if (${previousNameVar}) setAttribute(${elementVar}, ${previousNameVar}, null); });`);
1354
+ }
1355
+ function emitDynamicEventListener(context, elementVar, event, handlerVar, attr, cleanupVar, indent) {
1356
+ const expression = compileTemplateExpression(event.nameExpression ?? "\"\"", attr.name, toExpressionContext(context, attr.loc));
1357
+ const currentEventVar = nextVar(context, "eventName");
1358
+ const nextEventVar = nextVar(context, "eventName");
1359
+ const stopVar = nextVar(context, "stop");
1360
+ const eventOptions = eventListenerOptions(event);
1361
+ emit(context, indent, `let ${currentEventVar};`);
1362
+ emit(context, indent, `const ${stopVar} = effect(() => {`);
1363
+ emit(context, indent + 1, `const ${nextEventVar} = String(unwrap(${expression}) ?? "");`);
1364
+ emit(context, indent + 1, `if (${nextEventVar} === ${currentEventVar}) { return; }`);
1365
+ emit(context, indent + 1, `if (${currentEventVar}) { ${elementVar}.removeEventListener(${currentEventVar}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""}); }`);
1366
+ emit(context, indent + 1, `${currentEventVar} = ${nextEventVar};`);
1367
+ emit(context, indent + 1, `if (${currentEventVar}) { ${elementVar}.addEventListener(${currentEventVar}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""}); }`);
1368
+ emit(context, indent, "});");
1369
+ emit(context, indent, `${cleanupVar}.push(() => { ${stopVar}(); if (${currentEventVar}) { ${elementVar}.removeEventListener(${currentEventVar}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""}); } });`);
1370
+ }
1371
+ function emitContentDirective(context, node, elementVar, attr, cleanupVar, indent) {
1372
+ const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
1373
+ const property = attr.name === "v-html" ? "innerHTML" : "textContent";
1374
+ if (context.once) {
1375
+ emit(context, indent, `${elementVar}.${property} = String(unwrap(${expression}) ?? "");`);
1376
+ return;
1377
+ }
1378
+ const stopVar = nextVar(context, "stop");
1379
+ emit(context, indent, `const ${stopVar} = effect(() => {`);
1380
+ emit(context, indent + 1, `${elementVar}.${property} = String(unwrap(${expression}) ?? "");`);
1381
+ emit(context, indent, "});");
1382
+ emit(context, indent, `${cleanupVar}.push(${stopVar});`);
1383
+ }
1384
+ function generatePreNode(context, node, parentVar, indent, beforeVar) {
1385
+ if (node.type === "text") {
1386
+ const textVar = nextVar(context, "text");
1387
+ emit(context, indent, `const ${textVar} = document.createTextNode(${quote(node.parts.map((part) => part.value).join(""))});`);
1388
+ appendNode(context, parentVar, textVar, indent, beforeVar);
1389
+ return textVar;
1390
+ }
1391
+ return generatePreElement(context, node, parentVar, indent, beforeVar);
1392
+ }
1393
+ function generatePreElement(context, node, parentVar, indent, beforeVar) {
1394
+ const elementVar = nextVar(context, "el");
1395
+ emit(context, indent, `const ${elementVar} = document.createElement(${quote(node.tag)});`);
1396
+ if (context.scopeAttr) {
1397
+ emit(context, indent, `${elementVar}.setAttribute(${quote(context.scopeAttr)}, "");`);
1398
+ }
1399
+ for (const attr of node.attrs) {
1400
+ if (attr.name === "v-pre") {
1401
+ continue;
1402
+ }
1403
+ emit(context, indent, `setAttribute(${elementVar}, ${quote(attr.name)}, ${quote(attr.value === true ? "" : attr.value)});`);
1404
+ }
1405
+ for (const child of node.children) {
1406
+ generatePreNode(context, child, elementVar, indent);
1407
+ }
1408
+ appendNode(context, parentVar, elementVar, indent, beforeVar);
1409
+ return elementVar;
1410
+ }
820
1411
  function emitTemplateRef(context, node, valueExpression, cleanupVar, indent) {
821
1412
  const refAttr = getTemplateRefAttr(node);
822
1413
  if (!refAttr) {
@@ -848,11 +1439,16 @@ function generateText(context, node, parentVar, cleanupVar, indent, beforeVar) {
848
1439
  const textVar = nextVar(context, "text");
849
1440
  emit(context, indent, `const ${textVar} = document.createTextNode("");`);
850
1441
  if (node.parts.some((part) => part.type === "expression")) {
851
- const stopVar = nextVar(context, "stop");
852
- emit(context, indent, `const ${stopVar} = effect(() => {`);
853
- emit(context, indent + 1, `${textVar}.textContent = ${textExpression(node.parts, context)};`);
854
- emit(context, indent, "});");
855
- emit(context, indent, `${cleanupVar}.push(${stopVar});`);
1442
+ if (context.once) {
1443
+ emit(context, indent, `${textVar}.textContent = ${textExpression(node.parts, context)};`);
1444
+ }
1445
+ else {
1446
+ const stopVar = nextVar(context, "stop");
1447
+ emit(context, indent, `const ${stopVar} = effect(() => {`);
1448
+ emit(context, indent + 1, `${textVar}.textContent = ${textExpression(node.parts, context)};`);
1449
+ emit(context, indent, "});");
1450
+ emit(context, indent, `${cleanupVar}.push(${stopVar});`);
1451
+ }
856
1452
  }
857
1453
  else {
858
1454
  emit(context, indent, `${textVar}.textContent = ${quote(node.parts.map((part) => part.value).join(""))};`);
@@ -861,37 +1457,45 @@ function generateText(context, node, parentVar, cleanupVar, indent, beforeVar) {
861
1457
  return textVar;
862
1458
  }
863
1459
  function emitObjectBind(context, node, elementVar, attr, cleanupVar, indent) {
1460
+ const binding = parseObjectBindDirective(attr.name);
1461
+ if (!binding) {
1462
+ return;
1463
+ }
1464
+ validateObjectBindModifiers(binding, attr, context, "element");
864
1465
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
865
1466
  const prevKeysVar = nextVar(context, "boundKeys");
866
1467
  const stopVar = nextVar(context, "stop");
867
1468
  const attrsVar = nextVar(context, "attrs");
868
1469
  const nextKeysVar = nextVar(context, "boundKeys");
869
1470
  const keyVar = nextVar(context, "key");
1471
+ const boundKeyVar = nextVar(context, "key");
870
1472
  const valueVar = nextVar(context, "value");
871
1473
  const staleKeyVar = nextVar(context, "key");
872
1474
  const staticClass = getStaticAttrValue(node, "class");
1475
+ const staticStyle = getStaticAttrValue(node, "style");
873
1476
  emit(context, indent, `const ${prevKeysVar} = new Set();`);
874
1477
  emit(context, indent, `const ${stopVar} = effect(() => {`);
875
1478
  emit(context, indent + 1, `const ${attrsVar} = unwrap(${expression}) ?? {};`);
876
1479
  emit(context, indent + 1, `const ${nextKeysVar} = new Set();`);
877
1480
  emit(context, indent + 1, `if (${attrsVar} && typeof ${attrsVar} === "object") {`);
878
1481
  emit(context, indent + 2, `for (const [${keyVar}, ${valueVar}] of Object.entries(${attrsVar})) {`);
879
- emit(context, indent + 3, `${nextKeysVar}.add(${keyVar});`);
880
- if (staticClass) {
881
- emit(context, indent + 3, `setAttribute(${elementVar}, ${keyVar}, ${keyVar} === "class" ? [${quote(staticClass)}, unwrap(${valueVar})] : unwrap(${valueVar}));`);
1482
+ emit(context, indent + 3, `const ${boundKeyVar} = ${objectBindKeyExpression(keyVar, binding)};`);
1483
+ emit(context, indent + 3, `${nextKeysVar}.add(${boundKeyVar});`);
1484
+ if (staticClass || staticStyle) {
1485
+ emit(context, indent + 3, `setAttribute(${elementVar}, ${boundKeyVar}, ${boundKeyVar} === "class" && ${staticClass ? "true" : "false"} ? [${quote(staticClass ?? "")}, unwrap(${valueVar})] : ${boundKeyVar} === "style" && ${staticStyle ? "true" : "false"} ? [${quote(staticStyle ?? "")}, unwrap(${valueVar})] : unwrap(${valueVar})${bindOptionsExpression(binding)});`);
882
1486
  }
883
1487
  else {
884
- emit(context, indent + 3, `setAttribute(${elementVar}, ${keyVar}, unwrap(${valueVar}));`);
1488
+ emit(context, indent + 3, `setAttribute(${elementVar}, ${boundKeyVar}, unwrap(${valueVar})${bindOptionsExpression(binding)});`);
885
1489
  }
886
1490
  emit(context, indent + 2, "}");
887
1491
  emit(context, indent + 1, "}");
888
1492
  emit(context, indent + 1, `for (const ${staleKeyVar} of ${prevKeysVar}) {`);
889
1493
  emit(context, indent + 2, `if (!${nextKeysVar}.has(${staleKeyVar})) {`);
890
- if (staticClass) {
891
- emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, ${staleKeyVar} === "class" ? ${quote(staticClass)} : null);`);
1494
+ if (staticClass || staticStyle) {
1495
+ emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, ${staleKeyVar} === "class" && ${staticClass ? "true" : "false"} ? ${quote(staticClass ?? "")} : ${staleKeyVar} === "style" && ${staticStyle ? "true" : "false"} ? ${quote(staticStyle ?? "")} : null${bindOptionsExpression(binding)});`);
892
1496
  }
893
1497
  else {
894
- emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, null);`);
1498
+ emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, null${bindOptionsExpression(binding)});`);
895
1499
  }
896
1500
  emit(context, indent + 2, "}");
897
1501
  emit(context, indent + 1, "}");
@@ -903,24 +1507,32 @@ function emitObjectBind(context, node, elementVar, attr, cleanupVar, indent) {
903
1507
  emit(context, indent, `${cleanupVar}.push(${stopVar});`);
904
1508
  }
905
1509
  function emitObjectListeners(context, elementVar, attr, cleanupVar, indent) {
1510
+ const event = parseObjectOnDirective(attr.name);
1511
+ if (!event) {
1512
+ return;
1513
+ }
1514
+ validateObjectOnModifiers(event, attr, context, "element");
1515
+ const eventOptions = eventListenerOptions(event);
906
1516
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
907
1517
  const listenersVar = nextVar(context, "listeners");
908
1518
  const stopVar = nextVar(context, "stop");
909
1519
  const sourceVar = nextVar(context, "listeners");
910
1520
  const eventVar = nextVar(context, "event");
911
1521
  const handlerVar = nextVar(context, "handler");
1522
+ const wrappedHandlerVar = nextVar(context, "handler");
912
1523
  emit(context, indent, `const ${listenersVar} = new Map();`);
913
1524
  emit(context, indent, `const ${stopVar} = effect(() => {`);
914
1525
  emit(context, indent + 1, `for (const [${eventVar}, ${handlerVar}] of ${listenersVar}) {`);
915
- emit(context, indent + 2, `${elementVar}.removeEventListener(${eventVar}, ${handlerVar});`);
1526
+ emit(context, indent + 2, `${elementVar}.removeEventListener(${eventVar}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
916
1527
  emit(context, indent + 1, "}");
917
1528
  emit(context, indent + 1, `${listenersVar}.clear();`);
918
1529
  emit(context, indent + 1, `const ${sourceVar} = unwrap(${expression}) ?? {};`);
919
1530
  emit(context, indent + 1, `if (${sourceVar} && typeof ${sourceVar} === "object") {`);
920
1531
  emit(context, indent + 2, `for (const [${eventVar}, ${handlerVar}] of Object.entries(${sourceVar})) {`);
921
1532
  emit(context, indent + 3, `if (typeof ${handlerVar} === "function") {`);
922
- emit(context, indent + 4, `${elementVar}.addEventListener(${eventVar}, ${handlerVar});`);
923
- emit(context, indent + 4, `${listenersVar}.set(${eventVar}, ${handlerVar});`);
1533
+ emit(context, indent + 4, `const ${wrappedHandlerVar} = __mikuru_guardEventHandler(${handlerVar});`);
1534
+ emit(context, indent + 4, `${elementVar}.addEventListener(${eventVar}, ${wrappedHandlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
1535
+ emit(context, indent + 4, `${listenersVar}.set(${eventVar}, ${wrappedHandlerVar});`);
924
1536
  emit(context, indent + 3, "}");
925
1537
  emit(context, indent + 2, "}");
926
1538
  emit(context, indent + 1, "}");
@@ -928,7 +1540,7 @@ function emitObjectListeners(context, elementVar, attr, cleanupVar, indent) {
928
1540
  emit(context, indent, `${cleanupVar}.push(() => {`);
929
1541
  emit(context, indent + 1, `${stopVar}();`);
930
1542
  emit(context, indent + 1, `for (const [${eventVar}, ${handlerVar}] of ${listenersVar}) {`);
931
- emit(context, indent + 2, `${elementVar}.removeEventListener(${eventVar}, ${handlerVar});`);
1543
+ emit(context, indent + 2, `${elementVar}.removeEventListener(${eventVar}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
932
1544
  emit(context, indent + 1, "}");
933
1545
  emit(context, indent + 1, `${listenersVar}.clear();`);
934
1546
  emit(context, indent, "});");
@@ -1084,13 +1696,14 @@ function generateFor(context, node, parentVar, cleanupVar, indent, expression, b
1084
1696
  emit(context, indent, "});");
1085
1697
  return startVar;
1086
1698
  }
1087
- function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName, indexName, sourceExpression, keyExpression, beforeVar) {
1699
+ function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName, indexName, sourceExpression, keyExpression, beforeVar, transitionVar) {
1088
1700
  const startVar = nextVar(context, "forStart");
1089
1701
  const endVar = nextVar(context, "forEnd");
1090
1702
  const recordsVar = nextVar(context, "forRecords");
1091
1703
  const stopVar = nextVar(context, "stop");
1092
1704
  const compiledSource = compileTemplateExpression(sourceExpression, "v-for source", toExpressionContext(context, getStringAttrLocation(node, "v-for")));
1093
1705
  const compiledKey = compileTemplateExpression(keyExpression, "v-for key", toExpressionContext(context, getKeyAttrLocation(node)));
1706
+ const compiledMemo = getMemoExpression(context, node) ?? getOnceMemoExpression(context, node);
1094
1707
  emit(context, indent, `const ${recordsVar} = new Map();`);
1095
1708
  emit(context, indent, `const ${startVar} = document.createComment("for");`);
1096
1709
  emit(context, indent, `const ${endVar} = document.createComment("/for");`);
@@ -1107,6 +1720,8 @@ function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName
1107
1720
  const recordCleanupVar = nextVar(context, "forRecordCleanup");
1108
1721
  const itemRefVar = nextVar(context, "forItemRef");
1109
1722
  const indexRefVar = nextVar(context, "forIndexRef");
1723
+ const memoVar = compiledMemo ? nextVar(context, "forMemo") : undefined;
1724
+ const memoChangedVar = compiledMemo ? nextVar(context, "forMemoChanged") : undefined;
1110
1725
  emit(context, indent + 1, `const ${sourceVar} = unwrap(${compiledSource}) ?? [];`);
1111
1726
  emit(context, indent + 1, `const ${nextRecordsVar} = new Map();`);
1112
1727
  emit(context, indent + 1, `for (let ${indexVar} = 0; ${indexVar} < ${sourceVar}.length; ${indexVar} += 1) {`);
@@ -1117,6 +1732,9 @@ function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName
1117
1732
  emit(context, indent + 2, `const ${indexName} = ${rawIndexVar};`);
1118
1733
  }
1119
1734
  emit(context, indent + 2, `const ${keyVar} = unwrap(${compiledKey});`);
1735
+ if (compiledMemo && memoVar) {
1736
+ emit(context, indent + 2, `const ${memoVar} = ${compiledMemo};`);
1737
+ }
1120
1738
  emit(context, indent + 2, `let ${recordVar} = ${recordsVar}.get(${keyVar});`);
1121
1739
  emit(context, indent + 2, `if (!${recordVar}) {`);
1122
1740
  emit(context, indent + 3, `const ${recordCleanupVar} = [];`);
@@ -1130,14 +1748,32 @@ function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName
1130
1748
  emit(context, indent + 4, `const ${indexName} = ${indexRefVar};`);
1131
1749
  }
1132
1750
  const elementVar = withTemplateRefMode(context, "array", () => generateNode(context, withoutForAttrs(node), parentVar, recordCleanupVar, indent + 4, endVar));
1133
- emit(context, indent + 4, `${recordVar} = { element: ${elementVar}, cleanups: ${recordCleanupVar}, item: ${itemRefVar}${indexName ? `, index: ${indexRefVar}` : ""} };`);
1751
+ if (transitionVar) {
1752
+ emitTransitionRegistration(context, elementVar, transitionVar, indent + 4);
1753
+ }
1754
+ emit(context, indent + 4, `${recordVar} = { element: ${elementVar}, cleanups: ${recordCleanupVar}, item: ${itemRefVar}${indexName ? `, index: ${indexRefVar}` : ""}${compiledMemo && memoVar ? `, memo: ${memoVar}` : ""} };`);
1134
1755
  emit(context, indent + 3, `}`);
1135
1756
  emit(context, indent + 2, `} else {`);
1136
- emit(context, indent + 3, `${recordVar}.item.value = ${rawItemVar};`);
1137
- if (indexName) {
1138
- emit(context, indent + 3, `${recordVar}.index.value = ${rawIndexVar};`);
1757
+ if (compiledMemo && memoVar && memoChangedVar) {
1758
+ emit(context, indent + 3, `const ${memoChangedVar} = !__mikuru_memoEqual(${recordVar}.memo, ${memoVar});`);
1759
+ emit(context, indent + 3, `if (${memoChangedVar}) {`);
1760
+ emit(context, indent + 4, `${recordVar}.memo = ${memoVar};`);
1761
+ emit(context, indent + 4, `${recordVar}.item.value = ${rawItemVar};`);
1762
+ if (indexName) {
1763
+ emit(context, indent + 4, `${recordVar}.index.value = ${rawIndexVar};`);
1764
+ }
1765
+ emit(context, indent + 3, "}");
1766
+ }
1767
+ else {
1768
+ emit(context, indent + 3, `${recordVar}.item.value = ${rawItemVar};`);
1769
+ if (indexName) {
1770
+ emit(context, indent + 3, `${recordVar}.index.value = ${rawIndexVar};`);
1771
+ }
1139
1772
  }
1140
1773
  emit(context, indent + 3, `${parentVar}.insertBefore(${recordVar}.element, ${endVar});`);
1774
+ if (transitionVar) {
1775
+ emit(context, indent + 3, `__mikuru_applyTransitionMove(${recordVar}.element, ${transitionVar});`);
1776
+ }
1141
1777
  emit(context, indent + 2, `}`);
1142
1778
  emit(context, indent + 2, `${nextRecordsVar}.set(${keyVar}, ${recordVar});`);
1143
1779
  emit(context, indent + 1, `}`);
@@ -1188,6 +1824,16 @@ function withTemplateRefMode(context, mode, callback) {
1188
1824
  context.templateRefMode = previousMode;
1189
1825
  }
1190
1826
  }
1827
+ function withOnceMode(context, callback) {
1828
+ const previousOnce = context.once;
1829
+ context.once = true;
1830
+ try {
1831
+ return callback();
1832
+ }
1833
+ finally {
1834
+ context.once = previousOnce;
1835
+ }
1836
+ }
1191
1837
  function splitTopLevel(source, delimiter) {
1192
1838
  const parts = [];
1193
1839
  let depth = 0;
@@ -1619,11 +2265,241 @@ function isIdentifier(value) {
1619
2265
  return /^[A-Za-z_$][\w$]*$/.test(value);
1620
2266
  }
1621
2267
  function eventHandlerExpression(expression, context, location) {
1622
- const validatedExpression = validateTemplateExpression(expression, "event handler", toExpressionContext(context, location));
2268
+ const validatedExpression = validateEventHandlerExpression(expression, context, location);
1623
2269
  if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(validatedExpression)) {
1624
2270
  return validatedExpression;
1625
2271
  }
1626
- return `($event) => (${compileTemplateExpression(validatedExpression, "event handler", toExpressionContext(context, location))})`;
2272
+ return `($event) => { return (${compileEventHandlerExpression(validatedExpression, context, location)}); }`;
2273
+ }
2274
+ function validateEventHandlerExpression(expression, context, location) {
2275
+ const source = expression.trim().replace(/;\s*$/, "");
2276
+ if (!source) {
2277
+ throwTemplateError("Invalid template expression for event handler: expression is empty", context, location);
2278
+ }
2279
+ try {
2280
+ const ast = parseExpressionAt(source, 0, { ecmaVersion: "latest" });
2281
+ if (ast.end !== source.length) {
2282
+ throw new Error("Unexpected trailing content");
2283
+ }
2284
+ validateEventHandlerNode(ast, context, source, location);
2285
+ return source;
2286
+ }
2287
+ catch (error) {
2288
+ if (error instanceof Error && error.message.startsWith("Unsupported event handler")) {
2289
+ throwTemplateError(error.message, context, location);
2290
+ }
2291
+ try {
2292
+ return validateTemplateExpression(source, "event handler", toExpressionContext(context, location));
2293
+ }
2294
+ catch {
2295
+ const message = error instanceof Error ? error.message : String(error);
2296
+ throwTemplateError(`Invalid template expression for event handler: ${source} (${message})`, context, location);
2297
+ }
2298
+ }
2299
+ }
2300
+ function compileEventHandlerExpression(expression, context, location) {
2301
+ const ast = parseExpressionAt(expression, 0, { ecmaVersion: "latest" });
2302
+ const edits = [];
2303
+ collectEventHandlerEdits(ast, expression, edits, "read");
2304
+ return applyScriptEdits(expression, edits);
2305
+ }
2306
+ function validateEventHandlerNode(node, context, source, location) {
2307
+ switch (node.type) {
2308
+ case "Identifier":
2309
+ case "Literal":
2310
+ case "TemplateElement":
2311
+ return;
2312
+ case "ThisExpression":
2313
+ case "NewExpression":
2314
+ throw new Error(`Unsupported event handler expression: ${source} (${node.type})`);
2315
+ case "AssignmentExpression":
2316
+ validateEventAssignmentTarget(node.left, context, source, location);
2317
+ validateEventHandlerNode(node.right, context, source, location);
2318
+ return;
2319
+ case "UpdateExpression":
2320
+ validateEventAssignmentTarget(node.argument, context, source, location);
2321
+ return;
2322
+ case "CallExpression":
2323
+ validateEventCallExpression(node, context, source, location);
2324
+ return;
2325
+ case "MemberExpression":
2326
+ validateEventMemberExpression(node, context, source, location);
2327
+ return;
2328
+ case "ChainExpression":
2329
+ validateEventHandlerNode(node.expression, context, source, location);
2330
+ return;
2331
+ case "UnaryExpression":
2332
+ validateEventHandlerNode(node.argument, context, source, location);
2333
+ return;
2334
+ case "BinaryExpression":
2335
+ case "LogicalExpression":
2336
+ validateEventHandlerNode(node.left, context, source, location);
2337
+ validateEventHandlerNode(node.right, context, source, location);
2338
+ return;
2339
+ case "ConditionalExpression":
2340
+ validateEventHandlerNode(node.test, context, source, location);
2341
+ validateEventHandlerNode(node.consequent, context, source, location);
2342
+ validateEventHandlerNode(node.alternate, context, source, location);
2343
+ return;
2344
+ case "ArrayExpression":
2345
+ for (const element of node.elements ?? []) {
2346
+ if (element) {
2347
+ validateEventHandlerNode(element, context, source, location);
2348
+ }
2349
+ }
2350
+ return;
2351
+ case "ObjectExpression":
2352
+ for (const property of node.properties ?? []) {
2353
+ validateEventHandlerNode(property, context, source, location);
2354
+ }
2355
+ return;
2356
+ case "Property":
2357
+ if (node.computed) {
2358
+ validateEventHandlerNode(node.key, context, source, location);
2359
+ }
2360
+ validateEventHandlerNode(node.value, context, source, location);
2361
+ return;
2362
+ case "TemplateLiteral":
2363
+ for (const quasi of node.quasis ?? []) {
2364
+ validateEventHandlerNode(quasi, context, source, location);
2365
+ }
2366
+ for (const part of node.expressions ?? []) {
2367
+ validateEventHandlerNode(part, context, source, location);
2368
+ }
2369
+ return;
2370
+ default:
2371
+ throw new Error(`Unsupported event handler expression: ${source} (${node.type})`);
2372
+ }
2373
+ }
2374
+ function validateEventAssignmentTarget(node, context, source, location) {
2375
+ if (node.type === "Identifier") {
2376
+ return;
2377
+ }
2378
+ if (node.type === "MemberExpression") {
2379
+ validateEventMemberExpression(node, context, source, location);
2380
+ return;
2381
+ }
2382
+ throw new Error(`Unsupported event handler assignment target: ${source} (${node.type})`);
2383
+ }
2384
+ function validateEventCallExpression(node, context, source, location) {
2385
+ const callee = node.callee;
2386
+ const calleeName = getEventStaticCalleeName(callee, source);
2387
+ if (calleeName === "eval" || calleeName === "Function") {
2388
+ throw new Error(`Unsupported event handler expression: ${source} (${calleeName})`);
2389
+ }
2390
+ validateEventHandlerNode(callee, context, source, location);
2391
+ for (const argument of node.arguments ?? []) {
2392
+ validateEventHandlerNode(argument, context, source, location);
2393
+ }
2394
+ }
2395
+ function validateEventMemberExpression(node, context, source, location) {
2396
+ validateEventHandlerNode(node.object, context, source, location);
2397
+ if (node.computed) {
2398
+ validateEventHandlerNode(node.property, context, source, location);
2399
+ return;
2400
+ }
2401
+ const propertyName = getEventStaticPropertyName(node.property, source);
2402
+ if (propertyName === "constructor" || propertyName === "__proto__" || propertyName === "prototype") {
2403
+ throw new Error(`Unsupported event handler expression: ${source} (${propertyName})`);
2404
+ }
2405
+ }
2406
+ function collectEventHandlerEdits(node, source, edits, mode) {
2407
+ if (!node) {
2408
+ return;
2409
+ }
2410
+ switch (node.type) {
2411
+ case "Identifier":
2412
+ if (node.name === "$event") {
2413
+ return;
2414
+ }
2415
+ if (mode === "write") {
2416
+ edits.push({ start: node.start, end: node.end, replacement: `${node.name}.value` });
2417
+ return;
2418
+ }
2419
+ if (mode === "callee") {
2420
+ return;
2421
+ }
2422
+ edits.push({ start: node.start, end: node.end, replacement: `unwrap(${node.name})` });
2423
+ return;
2424
+ case "Literal":
2425
+ case "TemplateElement":
2426
+ case "ThisExpression":
2427
+ return;
2428
+ case "AssignmentExpression":
2429
+ collectEventHandlerEdits(node.left, source, edits, "write");
2430
+ collectEventHandlerEdits(node.right, source, edits, "read");
2431
+ return;
2432
+ case "UpdateExpression":
2433
+ collectEventHandlerEdits(node.argument, source, edits, "write");
2434
+ return;
2435
+ case "CallExpression":
2436
+ collectEventHandlerEdits(node.callee, source, edits, "callee");
2437
+ for (const argument of node.arguments ?? []) {
2438
+ collectEventHandlerEdits(argument, source, edits, "read");
2439
+ }
2440
+ return;
2441
+ case "MemberExpression":
2442
+ collectEventHandlerEdits(node.object, source, edits, mode === "callee" ? "read" : mode);
2443
+ if (node.computed) {
2444
+ collectEventHandlerEdits(node.property, source, edits, "read");
2445
+ }
2446
+ return;
2447
+ case "ChainExpression":
2448
+ collectEventHandlerEdits(node.expression, source, edits, mode);
2449
+ return;
2450
+ case "UnaryExpression":
2451
+ collectEventHandlerEdits(node.argument, source, edits, "read");
2452
+ return;
2453
+ case "BinaryExpression":
2454
+ case "LogicalExpression":
2455
+ collectEventHandlerEdits(node.left, source, edits, "read");
2456
+ collectEventHandlerEdits(node.right, source, edits, "read");
2457
+ return;
2458
+ case "ConditionalExpression":
2459
+ collectEventHandlerEdits(node.test, source, edits, "read");
2460
+ collectEventHandlerEdits(node.consequent, source, edits, "read");
2461
+ collectEventHandlerEdits(node.alternate, source, edits, "read");
2462
+ return;
2463
+ case "ArrayExpression":
2464
+ for (const element of node.elements ?? []) {
2465
+ collectEventHandlerEdits(element, source, edits, "read");
2466
+ }
2467
+ return;
2468
+ case "ObjectExpression":
2469
+ for (const property of node.properties ?? []) {
2470
+ collectEventHandlerEdits(property, source, edits, "read");
2471
+ }
2472
+ return;
2473
+ case "Property":
2474
+ if (node.computed) {
2475
+ collectEventHandlerEdits(node.key, source, edits, "read");
2476
+ }
2477
+ collectEventHandlerEdits(node.value, source, edits, "read");
2478
+ return;
2479
+ case "TemplateLiteral":
2480
+ for (const part of node.expressions ?? []) {
2481
+ collectEventHandlerEdits(part, source, edits, "read");
2482
+ }
2483
+ return;
2484
+ }
2485
+ }
2486
+ function getEventStaticCalleeName(node, source) {
2487
+ if (node.type === "Identifier") {
2488
+ return source.slice(node.start, node.end);
2489
+ }
2490
+ if (node.type === "MemberExpression") {
2491
+ return getEventStaticPropertyName(node.property, source);
2492
+ }
2493
+ return undefined;
2494
+ }
2495
+ function getEventStaticPropertyName(node, source) {
2496
+ if (node.type === "Identifier") {
2497
+ return source.slice(node.start, node.end);
2498
+ }
2499
+ if (node.type === "Literal") {
2500
+ return String(node.value);
2501
+ }
2502
+ return undefined;
1627
2503
  }
1628
2504
  function getStringAttr(node, name) {
1629
2505
  const attr = node.attrs.find((candidate) => candidate.name === name);
@@ -1651,6 +2527,24 @@ function getKeyExpression(node) {
1651
2527
  function getKeyAttrLocation(node) {
1652
2528
  return getStringAttrLocation(node, ":key") ?? getStringAttrLocation(node, "v-bind:key");
1653
2529
  }
2530
+ function getMemoExpression(context, node) {
2531
+ const attr = node.attrs.find((candidate) => candidate.name === "v-memo");
2532
+ if (!attr) {
2533
+ return undefined;
2534
+ }
2535
+ const expression = validateTemplateExpression(requireAttrValue(attr), "v-memo", toExpressionContext(context, attr.valueLoc));
2536
+ if (!expression.trim().startsWith("[") || !expression.trim().endsWith("]")) {
2537
+ throwTemplateError("v-memo requires an array expression", context, attr.valueLoc ?? attr.loc);
2538
+ }
2539
+ return compileTemplateExpression(expression, "v-memo", toExpressionContext(context, attr.valueLoc));
2540
+ }
2541
+ function getOnceMemoExpression(context, node) {
2542
+ if (!hasAttr(node, "v-once")) {
2543
+ return undefined;
2544
+ }
2545
+ validateOnceAttribute(context, node);
2546
+ return "[]";
2547
+ }
1654
2548
  function toExpressionContext(context, location) {
1655
2549
  if (!context.source || !location) {
1656
2550
  return undefined;
@@ -1665,7 +2559,7 @@ function withoutAttr(node, name) {
1665
2559
  return withoutAttrs(node, [name]);
1666
2560
  }
1667
2561
  function withoutForAttrs(node) {
1668
- return withoutAttrs(node, ["v-for", "key", ":key", "v-bind:key"]);
2562
+ return withoutAttrs(node, ["v-for", "key", ":key", "v-bind:key", "v-memo", "v-once"]);
1669
2563
  }
1670
2564
  function withoutAttrs(node, names) {
1671
2565
  return {
@@ -1697,12 +2591,18 @@ function emitComponentProps(context, node, propsVar, attrsVar, indent) {
1697
2591
  .flatMap((attr) => componentPropEntries(context, attr));
1698
2592
  const objectBindAttrs = node.attrs.filter((attr) => isObjectBindAttr(attr));
1699
2593
  const objectOnAttrs = node.attrs.filter((attr) => isObjectOnAttr(attr));
2594
+ for (const attr of objectBindAttrs) {
2595
+ validateObjectBindModifiers(parseObjectBindDirective(attr.name) ?? { modifiers: [] }, attr, context, "component");
2596
+ }
2597
+ for (const attr of objectOnAttrs) {
2598
+ validateObjectOnModifiers(parseObjectOnDirective(attr.name) ?? { modifiers: [] }, attr, context, "component");
2599
+ }
1700
2600
  const slots = collectComponentSlots(context, node);
1701
2601
  const defaultSlot = slots.find((slot) => !slot.nameExpression && slot.name === "default");
1702
2602
  const needsProxy = objectBindAttrs.length > 0 || objectOnAttrs.length > 0;
1703
2603
  const propsTargetVar = needsProxy ? nextVar(context, "propsBase") : propsVar;
1704
2604
  emit(context, indent, `const ${propsTargetVar} = {`);
1705
- emit(context, indent + 1, "__mikuru_context,");
2605
+ emit(context, indent + 1, `__mikuru_context: ${context.componentContextVar ?? "__mikuru_context"},`);
1706
2606
  emit(context, indent + 1, `__mikuru_attrs: ${attrsVar},`);
1707
2607
  for (const prop of props) {
1708
2608
  emit(context, indent + 1, `${prop},`);
@@ -2026,12 +2926,15 @@ function componentPropEntries(context, attr) {
2026
2926
  validateModelModifiers(modelDirective, attr, context);
2027
2927
  const expression = validateAssignableExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
2028
2928
  const valueExpression = compileTemplateExpression(expression, attr.name, toExpressionContext(context, attr.valueLoc));
2929
+ const propName = modelDirective.argument ?? "modelValue";
2930
+ const updatePropName = toComponentEventProp(`update:${propName}`);
2931
+ const modifiersPropName = modelDirective.argument ? `${propName}Modifiers` : "modelModifiers";
2029
2932
  const entries = [
2030
- `get modelValue() { return unwrap(${valueExpression}); }`,
2031
- `onUpdateModelValue: ($value) => { ${expression}.value = $value; }`
2933
+ `get ${quotePropertyName(propName)}() { return unwrap(${valueExpression}); }`,
2934
+ `${quotePropertyName(updatePropName)}: __mikuru_guardEventHandler(($value) => { ${expression}.value = $value; })`
2032
2935
  ];
2033
2936
  if (modelDirective.modifiers.length > 0) {
2034
- entries.push(`modelModifiers: { ${modelDirective.modifiers.map((modifier) => `${quotePropertyName(modifier)}: true`).join(", ")} }`);
2937
+ entries.push(`${quotePropertyName(modifiersPropName)}: { ${modelDirective.modifiers.map((modifier) => `${quotePropertyName(modifier)}: true`).join(", ")} }`);
2035
2938
  }
2036
2939
  return entries;
2037
2940
  }
@@ -2039,13 +2942,36 @@ function componentPropEntries(context, attr) {
2039
2942
  if (event) {
2040
2943
  validateComponentEventModifiers(event, attr, context);
2041
2944
  const handler = validateTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
2945
+ const handlerExpression = componentEventHandlerExpression(event, eventHandlerExpression(handler, context, attr.valueLoc), context);
2946
+ if (event.nameExpression) {
2947
+ const eventNameExpression = compileTemplateExpression(event.nameExpression, attr.name, toExpressionContext(context, attr.loc));
2948
+ return [
2949
+ `[${componentEventPropRuntimeExpression(`String(unwrap(${eventNameExpression}) ?? "")`)}]: __mikuru_guardEventHandler(${handlerExpression})`
2950
+ ];
2951
+ }
2042
2952
  return [
2043
- `${quotePropertyName(toComponentEventProp(event.name))}: ${componentEventHandlerExpression(event, eventHandlerExpression(handler, context, attr.valueLoc), context)}`
2953
+ `${quotePropertyName(toComponentEventProp(event.name ?? ""))}: __mikuru_guardEventHandler(${componentEventHandlerExpression(event, eventHandlerExpression(handler, context, attr.valueLoc), context)})`
2044
2954
  ];
2045
2955
  }
2046
- const bindingName = getBindingName(attr.name);
2047
- if (bindingName) {
2956
+ const bindDirective = parseBindDirective(attr.name);
2957
+ const dynamicBinding = bindDirective?.nameExpression ? bindDirective : undefined;
2958
+ if (dynamicBinding) {
2959
+ validateBindModifiers(dynamicBinding, attr, context, "component");
2960
+ const nameExpression = compileTemplateExpression(dynamicBinding.nameExpression ?? "", attr.name, toExpressionContext(context, attr.loc));
2961
+ const valueExpression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
2962
+ const propertyName = bindNameExpression(`String(unwrap(${nameExpression}) ?? "")`, dynamicBinding);
2963
+ if (context.once) {
2964
+ return [`[${propertyName}]: unwrap(${valueExpression})`];
2965
+ }
2966
+ return [`get [${propertyName}]() { return unwrap(${valueExpression}); }`];
2967
+ }
2968
+ const bindingName = bindDirective?.name;
2969
+ if (bindingName && bindDirective) {
2970
+ validateBindModifiers(bindDirective, attr, context, "component");
2048
2971
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
2972
+ if (context.once) {
2973
+ return [`${quotePropertyName(bindingName)}: unwrap(${expression})`];
2974
+ }
2049
2975
  return [`get ${quotePropertyName(bindingName)}() { return unwrap(${expression}); }`];
2050
2976
  }
2051
2977
  if (attr.name === "v-show") {
@@ -2105,7 +3031,8 @@ function getTransitionOptionsExpression(context, node) {
2105
3031
  ["enter-to-class", "enterToClass"],
2106
3032
  ["leave-from-class", "leaveFromClass"],
2107
3033
  ["leave-active-class", "leaveActiveClass"],
2108
- ["leave-to-class", "leaveToClass"]
3034
+ ["leave-to-class", "leaveToClass"],
3035
+ ["move-class", "moveClass"]
2109
3036
  ];
2110
3037
  for (const [attrName, optionName] of classAttrs) {
2111
3038
  const expression = getTransitionAttrExpression(context, node, attrName);
@@ -2130,7 +3057,7 @@ function getTransitionAttrExpression(context, node, name, fallback) {
2130
3057
  return fallback === undefined ? undefined : quote(fallback);
2131
3058
  }
2132
3059
  function validateTransitionAttributes(context, node) {
2133
- const supported = new Set([
3060
+ const supported = [
2134
3061
  "name",
2135
3062
  "appear",
2136
3063
  "mode",
@@ -2140,13 +3067,33 @@ function validateTransitionAttributes(context, node) {
2140
3067
  "leave-from-class",
2141
3068
  "leave-active-class",
2142
3069
  "leave-to-class"
2143
- ]);
3070
+ ].map((name) => ({ name, display: name }));
2144
3071
  for (const attr of node.attrs) {
2145
3072
  const name = getBindingName(attr.name) ?? attr.name;
2146
- if (supported.has(name)) {
3073
+ if (supported.some((candidate) => candidate.name === name)) {
2147
3074
  continue;
2148
3075
  }
2149
- throwTemplateError(`<Transition> only supports name, appear, and CSS class override attributes in v1`, context, attr.loc);
3076
+ throwUnsupportedSpecialAttribute(context, "Transition", attr, supported, "name, appear, mode, and CSS class override attributes");
3077
+ }
3078
+ }
3079
+ function validateTransitionGroupAttributes(context, node) {
3080
+ const supported = [
3081
+ "name",
3082
+ "tag",
3083
+ "enter-from-class",
3084
+ "enter-active-class",
3085
+ "enter-to-class",
3086
+ "leave-from-class",
3087
+ "leave-active-class",
3088
+ "leave-to-class",
3089
+ "move-class"
3090
+ ].map((name) => ({ name, display: name }));
3091
+ for (const attr of node.attrs) {
3092
+ const name = getBindingName(attr.name) ?? attr.name;
3093
+ if (supported.some((candidate) => candidate.name === name)) {
3094
+ continue;
3095
+ }
3096
+ throwUnsupportedSpecialAttribute(context, "TransitionGroup", attr, supported, "name, tag, and CSS class override attributes");
2150
3097
  }
2151
3098
  }
2152
3099
  function getSingleElementChild(context, node, label) {
@@ -2156,6 +3103,13 @@ function getSingleElementChild(context, node, label) {
2156
3103
  }
2157
3104
  return [meaningful[0]];
2158
3105
  }
3106
+ function getAsyncBoundaryChildren(context, node) {
3107
+ const meaningful = node.children.filter((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
3108
+ if (meaningful.length === 0) {
3109
+ throwTemplateError("<AsyncBoundary> requires at least one child", context, node.loc);
3110
+ }
3111
+ return node.children;
3112
+ }
2159
3113
  function getErrorBoundaryFallbackExpression(context, node) {
2160
3114
  const fallbackAttr = node.attrs.find((attr) => getBindingName(attr.name) === "fallback");
2161
3115
  if (!fallbackAttr) {
@@ -2163,12 +3117,89 @@ function getErrorBoundaryFallbackExpression(context, node) {
2163
3117
  }
2164
3118
  return compileTemplateExpression(requireAttrValue(fallbackAttr), fallbackAttr.name, toExpressionContext(context, fallbackAttr.valueLoc));
2165
3119
  }
3120
+ function getErrorBoundaryResetKeyExpression(context, node) {
3121
+ const resetKeyAttr = node.attrs.find((attr) => getBindingName(attr.name) === "reset-key");
3122
+ if (!resetKeyAttr) {
3123
+ return undefined;
3124
+ }
3125
+ return compileTemplateExpression(requireAttrValue(resetKeyAttr), resetKeyAttr.name, toExpressionContext(context, resetKeyAttr.valueLoc));
3126
+ }
3127
+ function getAsyncBoundaryLoadingExpression(context, node) {
3128
+ const loadingAttr = node.attrs.find((attr) => getBindingName(attr.name) === "loading");
3129
+ if (!loadingAttr) {
3130
+ throwTemplateError("<AsyncBoundary> requires :loading to resolve to a component object", context, node.loc);
3131
+ }
3132
+ return compileTemplateExpression(requireAttrValue(loadingAttr), loadingAttr.name, toExpressionContext(context, loadingAttr.valueLoc));
3133
+ }
3134
+ function getAsyncBoundaryFallbackExpression(context, node) {
3135
+ const fallbackAttr = node.attrs.find((attr) => getBindingName(attr.name) === "fallback");
3136
+ if (!fallbackAttr) {
3137
+ throwTemplateError("<AsyncBoundary> requires :fallback to resolve to a component object", context, node.loc);
3138
+ }
3139
+ return compileTemplateExpression(requireAttrValue(fallbackAttr), fallbackAttr.name, toExpressionContext(context, fallbackAttr.valueLoc));
3140
+ }
3141
+ function getAsyncBoundaryDelayExpression(context, node) {
3142
+ const delayAttr = node.attrs.find((attr) => getBindingName(attr.name) === "delay");
3143
+ if (!delayAttr) {
3144
+ return "0";
3145
+ }
3146
+ return compileTemplateExpression(requireAttrValue(delayAttr), delayAttr.name, toExpressionContext(context, delayAttr.valueLoc));
3147
+ }
3148
+ function getAsyncBoundaryTimeoutExpression(context, node) {
3149
+ const timeoutAttr = node.attrs.find((attr) => getBindingName(attr.name) === "timeout");
3150
+ if (!timeoutAttr) {
3151
+ return "undefined";
3152
+ }
3153
+ return compileTemplateExpression(requireAttrValue(timeoutAttr), timeoutAttr.name, toExpressionContext(context, timeoutAttr.valueLoc));
3154
+ }
3155
+ function getKeepAliveOptionExpression(context, node, name) {
3156
+ const dynamicAttr = node.attrs.find((attr) => getBindingName(attr.name) === name);
3157
+ if (dynamicAttr) {
3158
+ return compileTemplateExpression(requireAttrValue(dynamicAttr), dynamicAttr.name, toExpressionContext(context, dynamicAttr.valueLoc));
3159
+ }
3160
+ const staticValue = getStaticAttrValue(node, name);
3161
+ return staticValue === undefined ? undefined : quote(staticValue);
3162
+ }
2166
3163
  function validateErrorBoundaryAttributes(context, node) {
3164
+ const supported = [
3165
+ { name: "fallback", display: ":fallback" },
3166
+ { name: "reset-key", display: ":reset-key" }
3167
+ ];
3168
+ for (const attr of node.attrs) {
3169
+ const bindingName = getBindingName(attr.name);
3170
+ if (bindingName === "fallback" || bindingName === "reset-key") {
3171
+ continue;
3172
+ }
3173
+ throwUnsupportedSpecialAttribute(context, "ErrorBoundary", attr, supported);
3174
+ }
3175
+ }
3176
+ function validateAsyncBoundaryAttributes(context, node) {
3177
+ const supported = [
3178
+ { name: "loading", display: ":loading" },
3179
+ { name: "fallback", display: ":fallback" },
3180
+ { name: "delay", display: ":delay" },
3181
+ { name: "timeout", display: ":timeout" }
3182
+ ];
2167
3183
  for (const attr of node.attrs) {
2168
- if (getBindingName(attr.name) === "fallback") {
3184
+ const bindingName = getBindingName(attr.name);
3185
+ if (bindingName === "loading" || bindingName === "fallback" || bindingName === "delay" || bindingName === "timeout") {
2169
3186
  continue;
2170
3187
  }
2171
- throwTemplateError("<ErrorBoundary> only supports :fallback in v1", context, attr.loc);
3188
+ throwUnsupportedSpecialAttribute(context, "AsyncBoundary", attr, supported);
3189
+ }
3190
+ }
3191
+ function validateKeepAliveAttributes(context, node) {
3192
+ const supported = [
3193
+ { name: "include", display: ":include" },
3194
+ { name: "exclude", display: ":exclude" },
3195
+ { name: "max", display: ":max" }
3196
+ ];
3197
+ for (const attr of node.attrs) {
3198
+ const name = getBindingName(attr.name) ?? attr.name;
3199
+ if (name === "include" || name === "exclude" || name === "max") {
3200
+ continue;
3201
+ }
3202
+ throwUnsupportedSpecialAttribute(context, "KeepAlive", attr, supported, "include, exclude, and max");
2172
3203
  }
2173
3204
  }
2174
3205
  function getTeleportToExpression(context, node) {
@@ -2190,14 +3221,56 @@ function getTeleportDisabledExpression(context, node) {
2190
3221
  return hasStaticBooleanAttr(node, "disabled") ? "true" : "false";
2191
3222
  }
2192
3223
  function validateTeleportAttributes(context, node) {
3224
+ const supported = [
3225
+ { name: "to", display: "to" },
3226
+ { name: "disabled", display: "disabled" }
3227
+ ];
2193
3228
  for (const attr of node.attrs) {
2194
3229
  const name = getBindingName(attr.name) ?? attr.name;
2195
3230
  if (name === "to" || name === "disabled") {
2196
3231
  continue;
2197
3232
  }
2198
- throwTemplateError("<Teleport> only supports to and disabled in v1", context, attr.loc);
3233
+ throwUnsupportedSpecialAttribute(context, "Teleport", attr, supported);
2199
3234
  }
2200
3235
  }
3236
+ function throwUnsupportedSpecialAttribute(context, tagName, attr, supported, supportedLabel = supported.map((candidate) => candidate.display).join(", ")) {
3237
+ const normalizedName = getBindingName(attr.name) ?? attr.name;
3238
+ const suggestion = suggestAttributeName(normalizedName, supported);
3239
+ const suggestionMessage = suggestion ? ` Did you mean ${suggestion.display}?` : "";
3240
+ throwTemplateError(`Unsupported attribute ${quote(attr.name)} on <${tagName}>.${suggestionMessage} <${tagName}> only supports ${supportedLabel} in v1.`, context, attr.loc);
3241
+ }
3242
+ function suggestAttributeName(name, supported) {
3243
+ return suggestName(name, supported);
3244
+ }
3245
+ function suggestName(name, supported) {
3246
+ let best;
3247
+ for (const candidate of supported) {
3248
+ const distance = editDistance(name, candidate.name);
3249
+ if (!best || distance < best.distance) {
3250
+ best = { candidate, distance };
3251
+ }
3252
+ }
3253
+ if (!best) {
3254
+ return undefined;
3255
+ }
3256
+ const maxDistance = Math.max(1, Math.floor(best.candidate.name.length / 3));
3257
+ return best.distance <= maxDistance ? best.candidate : undefined;
3258
+ }
3259
+ function editDistance(left, right) {
3260
+ const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
3261
+ const current = Array.from({ length: right.length + 1 }, () => 0);
3262
+ for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
3263
+ current[0] = leftIndex;
3264
+ for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
3265
+ const cost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
3266
+ current[rightIndex] = Math.min(previous[rightIndex] + 1, current[rightIndex - 1] + 1, previous[rightIndex - 1] + cost);
3267
+ }
3268
+ for (let index = 0; index < previous.length; index += 1) {
3269
+ previous[index] = current[index];
3270
+ }
3271
+ }
3272
+ return previous[right.length] ?? 0;
3273
+ }
2201
3274
  function toComponentEventProp(eventName) {
2202
3275
  return `on${eventName
2203
3276
  .split(/[-:]/)
@@ -2205,18 +3278,67 @@ function toComponentEventProp(eventName) {
2205
3278
  .map((part) => part[0]?.toUpperCase() + part.slice(1))
2206
3279
  .join("")}`;
2207
3280
  }
2208
- function validateAttributes(node) {
3281
+ function validateAttributes(context, node) {
3282
+ const htmlAttr = getAttr(node, "v-html");
3283
+ const textAttr = getAttr(node, "v-text");
3284
+ if (htmlAttr && textAttr) {
3285
+ throwTemplateError("v-html and v-text cannot be used on the same element", context, textAttr.loc);
3286
+ }
2209
3287
  for (const attr of node.attrs) {
2210
3288
  if (attr.name.startsWith("v-") && !isSupportedDirectiveAttr(attr)) {
2211
- throw new Error(`Unsupported directive ${attr.name}`);
3289
+ throwUnsupportedDirective(context, attr);
3290
+ }
3291
+ if ((attr.name === "v-html" || attr.name === "v-text") && attr.value === true) {
3292
+ throwTemplateError(`${attr.name} requires a value`, context, attr.loc);
3293
+ }
3294
+ if ((attr.name === "v-pre" || attr.name === "v-cloak") && attr.value !== true) {
3295
+ throwTemplateError(`${attr.name} does not accept a value`, context, attr.valueLoc ?? attr.loc);
3296
+ }
3297
+ if (attr.name === "v-once") {
3298
+ validateOnceAttribute(context, node);
2212
3299
  }
3300
+ if (attr.name === "v-memo") {
3301
+ getMemoExpression(context, node);
3302
+ }
3303
+ }
3304
+ }
3305
+ function validateOnceAttribute(context, node) {
3306
+ const attr = node.attrs.find((candidate) => candidate.name === "v-once");
3307
+ if (attr && attr.value !== true) {
3308
+ throwTemplateError("v-once does not accept a value", context, attr.valueLoc ?? attr.loc);
3309
+ }
3310
+ }
3311
+ function validatePreAttribute(context, node) {
3312
+ const attr = node.attrs.find((candidate) => candidate.name === "v-pre");
3313
+ if (attr && attr.value !== true) {
3314
+ throwTemplateError("v-pre does not accept a value", context, attr.valueLoc ?? attr.loc);
2213
3315
  }
2214
3316
  }
3317
+ function throwUnsupportedDirective(context, attr) {
3318
+ const suggestion = suggestDirectiveName(attr.name);
3319
+ const suggestionMessage = suggestion ? ` Did you mean ${suggestion}?` : "";
3320
+ throwTemplateError(`Unsupported directive ${quote(attr.name)}.${suggestionMessage}`, context, attr.loc);
3321
+ }
3322
+ function suggestDirectiveName(name) {
3323
+ const supported = ["v-if", "v-else-if", "v-else", "v-for", "v-show", "v-html", "v-text", "v-pre", "v-cloak", "v-once", "v-memo", "v-model", "v-bind", "v-on", "v-slot"];
3324
+ const directiveName = name.includes(":") ? name.slice(0, name.indexOf(":")) : name.includes(".") ? name.slice(0, name.indexOf(".")) : name;
3325
+ const suggestion = suggestName(directiveName, supported.map((candidate) => ({ name: candidate, display: candidate })));
3326
+ if (!suggestion) {
3327
+ return undefined;
3328
+ }
3329
+ if (suggestion.name === "v-bind" && name.includes(":")) {
3330
+ return `v-bind:${name.slice(name.indexOf(":") + 1)}`;
3331
+ }
3332
+ if (suggestion.name === "v-on" && name.includes(":")) {
3333
+ return `v-on:${name.slice(name.indexOf(":") + 1)}`;
3334
+ }
3335
+ return suggestion.display;
3336
+ }
2215
3337
  function isDirectiveAttr(attr) {
2216
3338
  return isSupportedDirectiveAttr(attr) || attr.name.startsWith("@") || attr.name.startsWith(":");
2217
3339
  }
2218
3340
  function isStructuralAttr(attr) {
2219
- return attr.name === "v-if" || attr.name === "v-else-if" || attr.name === "v-else" || attr.name === "v-for";
3341
+ return attr.name === "v-if" || attr.name === "v-else-if" || attr.name === "v-else" || attr.name === "v-for" || attr.name === "v-once" || attr.name === "v-memo";
2220
3342
  }
2221
3343
  function isSlotDirectiveAttr(attr) {
2222
3344
  return attr.name === "v-slot" || attr.name.startsWith("v-slot:") || attr.name.startsWith("#");
@@ -2227,19 +3349,55 @@ function isSupportedDirectiveAttr(attr) {
2227
3349
  attr.name === "v-else" ||
2228
3350
  attr.name === "v-for" ||
2229
3351
  attr.name === "v-show" ||
3352
+ attr.name === "v-html" ||
3353
+ attr.name === "v-text" ||
3354
+ attr.name === "v-pre" ||
3355
+ attr.name === "v-cloak" ||
3356
+ attr.name === "v-once" ||
3357
+ attr.name === "v-memo" ||
2230
3358
  Boolean(parseModelDirective(attr.name)) ||
2231
3359
  isObjectBindAttr(attr) ||
2232
3360
  isObjectOnAttr(attr) ||
3361
+ Boolean(getDynamicBindingArgument(attr.name)) ||
3362
+ Boolean(getDynamicEventArgument(attr.name)) ||
2233
3363
  Boolean(parseEventDirective(attr.name)) ||
2234
3364
  Boolean(getBindingName(attr.name)));
2235
3365
  }
2236
3366
  function isObjectBindAttr(attr) {
2237
- return attr.name === "v-bind";
3367
+ return Boolean(parseObjectBindDirective(attr.name));
3368
+ }
3369
+ function parseObjectBindDirective(name) {
3370
+ if (name === "v-bind") {
3371
+ return { modifiers: [] };
3372
+ }
3373
+ if (!name.startsWith("v-bind.")) {
3374
+ return undefined;
3375
+ }
3376
+ return { modifiers: name.slice("v-bind.".length).split(".").filter(Boolean) };
3377
+ }
3378
+ function getContentDirectiveAttr(node) {
3379
+ return getAttr(node, "v-html") ?? getAttr(node, "v-text");
3380
+ }
3381
+ function getAttr(node, name) {
3382
+ return node.attrs.find((attr) => attr.name === name);
2238
3383
  }
2239
3384
  function isObjectOnAttr(attr) {
2240
- return attr.name === "v-on";
3385
+ return Boolean(parseObjectOnDirective(attr.name));
3386
+ }
3387
+ function parseObjectOnDirective(name) {
3388
+ if (name === "v-on") {
3389
+ return { modifiers: [] };
3390
+ }
3391
+ if (!name.startsWith("v-on.")) {
3392
+ return undefined;
3393
+ }
3394
+ return { modifiers: name.slice("v-on.".length).split(".").filter(Boolean) };
2241
3395
  }
2242
3396
  function parseEventDirective(name) {
3397
+ const dynamic = getDynamicEventArgument(name);
3398
+ if (dynamic) {
3399
+ return dynamic;
3400
+ }
2243
3401
  const rawName = getEventName(name);
2244
3402
  if (!rawName) {
2245
3403
  return undefined;
@@ -2254,30 +3412,49 @@ function parseModelDirective(name) {
2254
3412
  if (name === "v-model") {
2255
3413
  return { modifiers: [] };
2256
3414
  }
2257
- if (!name.startsWith("v-model.")) {
3415
+ if (!name.startsWith("v-model.") && !name.startsWith("v-model:")) {
2258
3416
  return undefined;
2259
3417
  }
3418
+ if (name.startsWith("v-model:")) {
3419
+ const raw = name.slice("v-model:".length);
3420
+ const [argument = "", ...modifiers] = raw.split(".");
3421
+ return { argument, modifiers: modifiers.filter(Boolean) };
3422
+ }
2260
3423
  return { modifiers: name.slice("v-model.".length).split(".").filter(Boolean) };
2261
3424
  }
2262
3425
  function validateModelModifiers(model, attr, context) {
2263
- const supportedModifiers = new Set(["trim", "number", "lazy"]);
3426
+ const supportedModifiers = ["trim", "number", "lazy"];
3427
+ if (model.argument !== undefined && !model.argument) {
3428
+ throwTemplateError("v-model argument must not be empty", context, attr.loc);
3429
+ }
2264
3430
  for (const modifier of model.modifiers) {
2265
- if (!supportedModifiers.has(modifier)) {
2266
- throwTemplateError(`Unsupported v-model modifier .${modifier}`, context, attr.loc);
3431
+ if (!supportedModifiers.includes(modifier)) {
3432
+ const suggestion = suggestModifierName(modifier, supportedModifiers);
3433
+ const suggestionMessage = suggestion ? ` Did you mean .${suggestion}?` : "";
3434
+ throwTemplateError(`Unsupported v-model modifier .${modifier}.${suggestionMessage}`, context, attr.loc);
2267
3435
  }
2268
3436
  }
2269
3437
  }
2270
- function modelAssignedValue(modelMode, modifiers) {
3438
+ const modelValueProperty = "__mikuruModelValue";
3439
+ function modelElementValueExpression(targetExpression, modifiers) {
3440
+ const valueExpression = `(${quote(modelValueProperty)} in ${targetExpression} ? ${targetExpression}[${quote(modelValueProperty)}] : ${targetExpression}.getAttribute("value") ?? (${targetExpression}.tagName === "OPTION" ? (${targetExpression}.textContent ?? "") : "on"))`;
3441
+ return modifiers.includes("number") ? `Number(${valueExpression})` : valueExpression;
3442
+ }
3443
+ function modelAssignedValue(modelMode, modifiers, expression) {
2271
3444
  if (modelMode === "checkbox") {
2272
- return "$event.target.checked";
3445
+ const valueExpression = modelElementValueExpression("$event.target", modifiers);
3446
+ return `(() => { const checked = $event.target.checked; const current = unwrap(${expression}); const value = ${valueExpression}; if (Array.isArray(current)) { const hasValue = current.some((item) => Object.is(item, value)); return checked ? (hasValue ? current : [...current, value]) : current.filter((item) => !Object.is(item, value)); } return checked; })()`;
2273
3447
  }
2274
3448
  if (modelMode === "radio") {
2275
- const valueExpression = `($event.target.getAttribute("value") ?? "on")`;
2276
- return modifiers.includes("number") ? `Number(${valueExpression})` : valueExpression;
3449
+ return modelElementValueExpression("$event.target", modifiers);
2277
3450
  }
2278
3451
  if (modelMode === "select-multiple") {
2279
- const valueExpression = `Array.from($event.target.options).filter((option) => option.selected).map((option) => option.getAttribute("value") ?? option.textContent ?? "")`;
2280
- return modifiers.includes("number") ? `${valueExpression}.map((value) => Number(value))` : valueExpression;
3452
+ const valueExpression = `Array.from($event.target.options).filter((option) => option.selected).map((option) => ${modelElementValueExpression("option", modifiers)})`;
3453
+ return valueExpression;
3454
+ }
3455
+ if (modelMode === "select") {
3456
+ const valueExpression = `(() => { const option = $event.target.selectedOptions[0]; return option ? ${modelElementValueExpression("option", modifiers)} : ""; })()`;
3457
+ return valueExpression;
2281
3458
  }
2282
3459
  let valueExpression = "$event.target.value";
2283
3460
  if (modifiers.includes("trim")) {
@@ -2289,10 +3466,12 @@ function modelAssignedValue(modelMode, modifiers) {
2289
3466
  return valueExpression;
2290
3467
  }
2291
3468
  function validateEventModifiers(event, attr, context) {
2292
- const supportedModifiers = new Set(["prevent", "stop", "self", "once", "capture", "passive"]);
3469
+ const supportedModifiers = [...eventControlModifiers, ...eventOptionModifiers, ...eventSystemModifiers, ...eventMouseModifiers, ...eventKeyModifiers, "exact"];
2293
3470
  for (const modifier of event.modifiers) {
2294
- if (!supportedModifiers.has(modifier)) {
2295
- throwTemplateError(`Unsupported event modifier .${modifier}`, context, attr.loc);
3471
+ if (!supportedModifiers.includes(modifier)) {
3472
+ const suggestion = suggestModifierName(modifier, supportedModifiers);
3473
+ const suggestionMessage = suggestion ? ` Did you mean .${suggestion}?` : "";
3474
+ throwTemplateError(`Unsupported event modifier .${modifier}.${suggestionMessage}`, context, attr.loc);
2296
3475
  }
2297
3476
  }
2298
3477
  if (event.modifiers.includes("passive") && event.modifiers.includes("prevent")) {
@@ -2300,12 +3479,30 @@ function validateEventModifiers(event, attr, context) {
2300
3479
  }
2301
3480
  }
2302
3481
  function validateComponentEventModifiers(event, attr, context) {
3482
+ const supportedModifiers = ["once"];
2303
3483
  for (const modifier of event.modifiers) {
2304
3484
  if (modifier !== "once") {
2305
- throwTemplateError(`Event modifier .${modifier} is only supported on DOM events`, context, attr.loc);
3485
+ const suggestion = suggestModifierName(modifier, supportedModifiers);
3486
+ const suggestionMessage = suggestion ? ` Did you mean .${suggestion}?` : "";
3487
+ throwTemplateError(`Event modifier .${modifier} is only supported on DOM events.${suggestionMessage}`, context, attr.loc);
3488
+ }
3489
+ }
3490
+ }
3491
+ function validateObjectOnModifiers(event, attr, context, target) {
3492
+ if (target === "component" && event.modifiers.length > 0) {
3493
+ throwTemplateError("Object v-on modifiers are only supported on native elements", context, attr.loc);
3494
+ }
3495
+ for (const modifier of event.modifiers) {
3496
+ if (!eventOptionModifiers.includes(modifier)) {
3497
+ const suggestion = suggestModifierName(modifier, eventOptionModifiers);
3498
+ const suggestionMessage = suggestion ? ` Did you mean .${suggestion}?` : "";
3499
+ throwTemplateError(`Object v-on modifier .${modifier} is not supported. Use .once, .capture, or .passive.${suggestionMessage}`, context, attr.loc);
2306
3500
  }
2307
3501
  }
2308
3502
  }
3503
+ function suggestModifierName(name, supported) {
3504
+ return suggestName(name, supported.map((candidate) => ({ name: candidate, display: candidate })))?.name;
3505
+ }
2309
3506
  function eventListenerOptions(event) {
2310
3507
  const options = [
2311
3508
  event.modifiers.includes("capture") ? "capture: true" : undefined,
@@ -2314,6 +3511,66 @@ function eventListenerOptions(event) {
2314
3511
  ].filter(Boolean);
2315
3512
  return options.length ? `{ ${options.join(", ")} }` : undefined;
2316
3513
  }
3514
+ const eventControlModifiers = ["prevent", "stop", "self"];
3515
+ const eventOptionModifiers = ["once", "capture", "passive"];
3516
+ const eventSystemModifiers = ["ctrl", "shift", "alt", "meta"];
3517
+ const eventMouseModifiers = ["left", "right", "middle"];
3518
+ const eventKeyModifiers = ["enter", "escape", "esc", "space", "tab", "delete", "backspace", "up", "down", "left", "right"];
3519
+ function eventModifierGuardExpression(event) {
3520
+ const checks = [];
3521
+ const mouseEvent = isMouseEventName(event.name);
3522
+ for (const modifier of event.modifiers) {
3523
+ if (eventSystemModifiers.includes(modifier)) {
3524
+ checks.push(`!$event.${modifier}Key`);
3525
+ continue;
3526
+ }
3527
+ const mouseExpression = mouseEvent ? eventMouseButtonExpression(modifier) : undefined;
3528
+ if (mouseExpression) {
3529
+ checks.push(`$event.button !== ${mouseExpression}`);
3530
+ continue;
3531
+ }
3532
+ const keyExpression = eventKeyExpression(modifier);
3533
+ if (keyExpression) {
3534
+ checks.push(`!${keyExpression}.includes($event.key)`);
3535
+ }
3536
+ }
3537
+ if (event.modifiers.includes("exact")) {
3538
+ for (const modifier of eventSystemModifiers) {
3539
+ if (!event.modifiers.includes(modifier)) {
3540
+ checks.push(`$event.${modifier}Key`);
3541
+ }
3542
+ }
3543
+ }
3544
+ return checks.length ? checks.join(" || ") : undefined;
3545
+ }
3546
+ function isMouseEventName(name) {
3547
+ return !!name && /^(?:click|dblclick|auxclick|contextmenu|mousedown|mouseup|mousemove|mouseover|mouseout|mouseenter|mouseleave|pointerdown|pointerup|pointermove|pointerover|pointerout|pointerenter|pointerleave)$/.test(name);
3548
+ }
3549
+ function eventMouseButtonExpression(modifier) {
3550
+ const mouseButtons = {
3551
+ left: "0",
3552
+ middle: "1",
3553
+ right: "2"
3554
+ };
3555
+ return mouseButtons[modifier];
3556
+ }
3557
+ function eventKeyExpression(modifier) {
3558
+ const keyAliases = {
3559
+ enter: ["Enter"],
3560
+ escape: ["Escape"],
3561
+ esc: ["Escape"],
3562
+ space: [" ", "Spacebar"],
3563
+ tab: ["Tab"],
3564
+ delete: ["Delete"],
3565
+ backspace: ["Backspace"],
3566
+ up: ["ArrowUp", "Up"],
3567
+ down: ["ArrowDown", "Down"],
3568
+ left: ["ArrowLeft", "Left"],
3569
+ right: ["ArrowRight", "Right"]
3570
+ };
3571
+ const keys = keyAliases[modifier];
3572
+ return keys ? JSON.stringify(keys) : undefined;
3573
+ }
2317
3574
  function componentEventHandlerExpression(event, handlerExpression, context) {
2318
3575
  if (!event.modifiers.includes("once")) {
2319
3576
  return handlerExpression;
@@ -2322,7 +3579,13 @@ function componentEventHandlerExpression(event, handlerExpression, context) {
2322
3579
  const handlerVar = nextVar(context, "handler");
2323
3580
  return `(() => { let ${calledVar} = false; const ${handlerVar} = ${handlerExpression}; return (...$args) => { if (${calledVar}) { return; } ${calledVar} = true; return ${handlerVar}(...$args); }; })()`;
2324
3581
  }
3582
+ function componentEventPropRuntimeExpression(eventNameExpression) {
3583
+ return `"on" + ${eventNameExpression}.split(/[-:]/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join("")`;
3584
+ }
2325
3585
  function getEventName(name) {
3586
+ if (name.startsWith("@[") || name.startsWith("v-on:[")) {
3587
+ return undefined;
3588
+ }
2326
3589
  if (name.startsWith("@")) {
2327
3590
  return name.slice(1);
2328
3591
  }
@@ -2332,11 +3595,108 @@ function getEventName(name) {
2332
3595
  return undefined;
2333
3596
  }
2334
3597
  function getBindingName(name) {
2335
- if (name.startsWith(":")) {
2336
- return name.slice(1);
3598
+ const binding = parseBindDirective(name);
3599
+ if (!binding || binding.nameExpression) {
3600
+ return undefined;
3601
+ }
3602
+ return binding.name;
3603
+ }
3604
+ function getDynamicBindingArgument(name) {
3605
+ const binding = parseBindDirective(name);
3606
+ if (!binding?.nameExpression) {
3607
+ return undefined;
3608
+ }
3609
+ return { expression: binding.nameExpression };
3610
+ }
3611
+ function parseBindDirective(name) {
3612
+ const dynamic = parseDynamicArgument(name, [":", "v-bind:"]);
3613
+ if (dynamic) {
3614
+ return { nameExpression: dynamic.expression, modifiers: dynamic.modifiers };
3615
+ }
3616
+ const rawName = name.startsWith(":")
3617
+ ? name.slice(1)
3618
+ : name.startsWith("v-bind:")
3619
+ ? name.slice("v-bind:".length)
3620
+ : undefined;
3621
+ if (!rawName) {
3622
+ return undefined;
3623
+ }
3624
+ const [bindingName, ...modifiers] = rawName.split(".");
3625
+ if (!bindingName) {
3626
+ return undefined;
3627
+ }
3628
+ return { name: modifiers.includes("camel") ? camelize(bindingName) : bindingName, modifiers };
3629
+ }
3630
+ function validateBindModifiers(binding, attr, context, target) {
3631
+ const allowed = new Set(["camel", "prop", "attr"]);
3632
+ for (const modifier of binding.modifiers) {
3633
+ if (!allowed.has(modifier)) {
3634
+ throwTemplateError(`Unsupported v-bind modifier ".${modifier}". Use .camel, .prop, or .attr.`, context, attr.loc);
3635
+ }
3636
+ }
3637
+ if (binding.modifiers.includes("prop") && binding.modifiers.includes("attr")) {
3638
+ throwTemplateError("v-bind modifiers .prop and .attr cannot be used together", context, attr.loc);
3639
+ }
3640
+ if (target === "component" && (binding.modifiers.includes("prop") || binding.modifiers.includes("attr"))) {
3641
+ throwTemplateError("v-bind .prop and .attr modifiers are only supported on native elements", context, attr.loc);
3642
+ }
3643
+ }
3644
+ function validateObjectBindModifiers(binding, attr, context, target) {
3645
+ const allowed = new Set(["camel", "prop", "attr"]);
3646
+ for (const modifier of binding.modifiers) {
3647
+ if (!allowed.has(modifier)) {
3648
+ throwTemplateError(`Unsupported object v-bind modifier ".${modifier}". Use .camel, .prop, or .attr.`, context, attr.loc);
3649
+ }
3650
+ }
3651
+ if (binding.modifiers.includes("prop") && binding.modifiers.includes("attr")) {
3652
+ throwTemplateError("Object v-bind modifiers .prop and .attr cannot be used together", context, attr.loc);
3653
+ }
3654
+ if (target === "component" && binding.modifiers.length > 0) {
3655
+ throwTemplateError("Object v-bind modifiers are only supported on native elements", context, attr.loc);
3656
+ }
3657
+ }
3658
+ function bindOptionsExpression(binding) {
3659
+ if (binding.modifiers.includes("prop")) {
3660
+ return ", { property: true }";
3661
+ }
3662
+ if (binding.modifiers.includes("attr")) {
3663
+ return ", { attribute: true }";
3664
+ }
3665
+ return "";
3666
+ }
3667
+ function bindNameExpression(expression, binding) {
3668
+ return binding.modifiers.includes("camel") ? `(${expression}).replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase())` : expression;
3669
+ }
3670
+ function objectBindKeyExpression(keyExpression, binding) {
3671
+ return bindNameExpression(`String(${keyExpression})`, binding);
3672
+ }
3673
+ function camelize(value) {
3674
+ return value.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
3675
+ }
3676
+ function getDynamicEventArgument(name) {
3677
+ const dynamic = parseDynamicArgument(name, ["@", "v-on:"]);
3678
+ if (!dynamic) {
3679
+ return undefined;
2337
3680
  }
2338
- if (name.startsWith("v-bind:")) {
2339
- return name.slice("v-bind:".length);
3681
+ return { nameExpression: dynamic.expression, modifiers: dynamic.modifiers };
3682
+ }
3683
+ function parseDynamicArgument(name, prefixes) {
3684
+ for (const prefix of prefixes) {
3685
+ if (!name.startsWith(`${prefix}[`)) {
3686
+ continue;
3687
+ }
3688
+ const argumentStart = prefix.length + 1;
3689
+ const argumentEnd = name.indexOf("]", argumentStart);
3690
+ if (argumentEnd === -1) {
3691
+ return undefined;
3692
+ }
3693
+ const expression = name.slice(argumentStart, argumentEnd).trim();
3694
+ if (!expression) {
3695
+ return undefined;
3696
+ }
3697
+ const rest = name.slice(argumentEnd + 1);
3698
+ const modifiers = rest.startsWith(".") ? rest.slice(1).split(".").filter(Boolean) : [];
3699
+ return { expression, modifiers };
2340
3700
  }
2341
3701
  return undefined;
2342
3702
  }