mikuru 1.0.19 → 1.0.21

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 (62) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/README.md +23 -13
  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 +1399 -93
  15. package/dist/compiler/generate.js.map +1 -1
  16. package/dist/compiler/generateHydration.d.ts +6 -0
  17. package/dist/compiler/generateHydration.js +1553 -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 +915 -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.d.ts +8 -0
  35. package/dist/router/index.js +133 -3
  36. package/dist/router/index.js.map +1 -1
  37. package/dist/runtime/asyncComponent.d.ts +22 -2
  38. package/dist/runtime/asyncComponent.js +84 -2
  39. package/dist/runtime/asyncComponent.js.map +1 -1
  40. package/dist/runtime/devtools.d.ts +51 -0
  41. package/dist/runtime/devtools.js +113 -0
  42. package/dist/runtime/devtools.js.map +1 -0
  43. package/dist/runtime/dom.d.ts +5 -1
  44. package/dist/runtime/dom.js +100 -3
  45. package/dist/runtime/dom.js.map +1 -1
  46. package/dist/runtime/index.d.ts +7 -5
  47. package/dist/runtime/index.js +3 -2
  48. package/dist/runtime/index.js.map +1 -1
  49. package/dist/runtime/lifecycle.d.ts +6 -0
  50. package/dist/runtime/lifecycle.js +66 -10
  51. package/dist/runtime/lifecycle.js.map +1 -1
  52. package/dist/runtime/reactivity.d.ts +33 -1
  53. package/dist/runtime/reactivity.js +254 -13
  54. package/dist/runtime/reactivity.js.map +1 -1
  55. package/dist/server.d.ts +33 -0
  56. package/dist/server.js +285 -0
  57. package/dist/server.js.map +1 -0
  58. package/dist/vite.d.ts +1 -0
  59. package/dist/vite.js +39 -18
  60. package/dist/vite.js.map +1 -1
  61. package/package.json +110 -100
  62. package/types/env.d.ts +58 -29
@@ -1,33 +1,53 @@
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 = {}) {");
20
33
  emit(context, 1, `const __mikuru_componentInfo = { component: ${quote(descriptor.filename ?? "anonymous.mikuru")}, filename: ${quote(descriptor.filename ?? "anonymous.mikuru")} };`);
34
+ emitDevtoolsRegistration(context, 1);
21
35
  emit(context, 1, "const __mikuru_cleanup = [];");
22
36
  emit(context, 1, "const __mikuru_afterUnmount = [];");
23
37
  emit(context, 1, "const __mikuru_mounted = [];");
24
- emit(context, 1, "const __mikuru_context = { parent: props.__mikuru_context, provides: new Map(), errorHandler: props.__mikuru_context?.errorHandler, ...__mikuru_componentInfo };");
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 };`);
25
41
  emit(context, 1, "const __mikuru_errorInfo = (phase) => ({ ...__mikuru_componentInfo, phase });");
26
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
+ }
27
46
  emit(context, 2, "if (typeof errorHandler === \"function\") { Promise.resolve().then(() => errorHandler(error, __mikuru_errorInfo(phase))); return; }");
28
47
  emit(context, 2, "setTimeout(() => { throw error; });");
29
48
  emit(context, 1, "};");
30
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]));");
31
51
  emit(context, 1, "const __mikuru_guardEventHandler = (fn, errorHandler = __mikuru_context.errorHandler) => (...args) => __mikuru_try(() => fn(...args), errorHandler, \"event\");");
32
52
  emit(context, 1, "const __mikuru_runCleanup = (cleanups) => {");
33
53
  emit(context, 2, "for (const cleanup of cleanups.splice(0).reverse()) {");
@@ -66,6 +86,12 @@ export function generate(descriptor, root) {
66
86
  emit(context, 3, "__mikuru_transitionDone(element, () => element.classList.remove(active, to));");
67
87
  emit(context, 2, "});");
68
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, "};");
69
95
  emit(context, 1, "const __mikuru_removeNode = (node) => {");
70
96
  emit(context, 2, "if (!node || !node.parentNode) { return; }");
71
97
  emit(context, 2, "if (node.nodeType !== 1 || !node.__mikuru_transition || node.__mikuru_transitionLeaving) { node.remove(); return; }");
@@ -102,6 +128,8 @@ export function generate(descriptor, root) {
102
128
  emit(context, 1, "const __mikuru_previousRegistrar = globalThis.__mikuru_currentRegistrar;");
103
129
  emit(context, 1, "globalThis.__mikuru_currentRegistrar = {");
104
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),");
105
133
  emit(context, 2, "registerBeforeUnmount: (fn) => __mikuru_cleanup.push(fn),");
106
134
  emit(context, 2, "registerUnmounted: (fn) => __mikuru_afterUnmount.push(fn),");
107
135
  emit(context, 2, "provide: (key, value) => __mikuru_context.provides.set(key, value),");
@@ -140,12 +168,23 @@ export function generate(descriptor, root) {
140
168
  emit(context, 1, "");
141
169
  }
142
170
  const rootVar = generateNode(context, root, "target", "__mikuru_cleanup", 1);
171
+ emitDevtoolsRootUpdate(context, rootVar, 1);
143
172
  emit(context, 1, "// call mounted callbacks registered during setup and remove registrar");
144
173
  emit(context, 1, "for (const cb of __mikuru_mounted.splice(0)) { __mikuru_try(cb, undefined, \"mounted\"); }");
145
174
  emit(context, 1, "if (__mikuru_previousRegistrar === undefined) { delete globalThis.__mikuru_currentRegistrar; } else { globalThis.__mikuru_currentRegistrar = __mikuru_previousRegistrar; }");
146
175
  emit(context, 1, "return {");
147
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, "},");
148
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
+ }
149
188
  emit(context, 3, "__mikuru_runCleanup(__mikuru_cleanup);");
150
189
  emit(context, 3, "for (const cb of __mikuru_afterUnmount.splice(0).reverse()) { __mikuru_try(cb, undefined, \"unmounted\"); }");
151
190
  emit(context, 3, `__mikuru_removeNode(${rootVar});`);
@@ -155,7 +194,10 @@ export function generate(descriptor, root) {
155
194
  emit(context, 0, "");
156
195
  emit(context, 0, `const __mikuru_component = { mount${script.inheritAttrs ? "" : ", inheritAttrs: false"} };`);
157
196
  emit(context, 0, "export default __mikuru_component;");
158
- 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`;
159
201
  }
160
202
  function emitStyleInjection(context, descriptor, indent) {
161
203
  const styleId = `mikuru-${hash(`${descriptor.filename ?? ""}\n${descriptor.style ?? ""}`)}`;
@@ -169,10 +211,42 @@ function emitStyleInjection(context, descriptor, indent) {
169
211
  emit(context, indent + 1, "document.head.appendChild(style);");
170
212
  emit(context, indent, "}");
171
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
+ }
172
238
  function generateNode(context, node, parentVar, cleanupVar, indent, beforeVar) {
173
239
  if (node.type === "text") {
174
240
  return generateText(context, node, parentVar, cleanupVar, indent, beforeVar);
175
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
+ }
176
250
  const forExpression = getStringAttr(node, "v-for");
177
251
  if (forExpression) {
178
252
  return generateFor(context, node, parentVar, cleanupVar, indent, forExpression, beforeVar);
@@ -191,12 +265,21 @@ function generateNode(context, node, parentVar, cleanupVar, indent, beforeVar) {
191
265
  if (node.tag === "Transition") {
192
266
  return generateTransition(context, node, parentVar, cleanupVar, indent, beforeVar);
193
267
  }
268
+ if (node.tag === "TransitionGroup") {
269
+ return generateTransitionGroup(context, node, parentVar, cleanupVar, indent, beforeVar);
270
+ }
194
271
  if (node.tag === "Teleport") {
195
272
  return generateTeleport(context, node, parentVar, cleanupVar, indent, beforeVar);
196
273
  }
274
+ if (node.tag === "AsyncBoundary") {
275
+ return generateAsyncBoundary(context, node, parentVar, cleanupVar, indent, beforeVar);
276
+ }
197
277
  if (node.tag === "ErrorBoundary") {
198
278
  return generateErrorBoundary(context, node, parentVar, cleanupVar, indent, beforeVar);
199
279
  }
280
+ if (node.tag === "KeepAlive") {
281
+ return generateKeepAlive(context, node, parentVar, cleanupVar, indent, beforeVar);
282
+ }
200
283
  if (isComponentTag(node.tag)) {
201
284
  return generateComponent(context, node, parentVar, cleanupVar, indent, beforeVar);
202
285
  }
@@ -221,6 +304,30 @@ function generateTransition(context, node, parentVar, cleanupVar, indent, before
221
304
  emitTransitionRegistration(context, childVar, transitionVar, indent);
222
305
  return childVar;
223
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
+ }
224
331
  function generateTeleport(context, node, parentVar, cleanupVar, indent, beforeVar) {
225
332
  validateTeleportAttributes(context, node);
226
333
  const toExpression = getTeleportToExpression(context, node);
@@ -312,6 +419,9 @@ function generateErrorBoundary(context, node, parentVar, cleanupVar, indent, bef
312
419
  emit(context, indent + 1, `const ${fallbackVar} = unwrap(${fallbackExpression});`);
313
420
  emit(context, indent + 1, `if (!${fallbackVar} || typeof ${fallbackVar}.mount !== "function") { throw ${errorVar}; }`);
314
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
+ }
315
425
  emit(context, indent + 1, `const ${fallbackFragmentVar} = document.createDocumentFragment();`);
316
426
  emit(context, indent + 1, `const ${fallbackInstanceVar} = ${fallbackVar}.mount(${fallbackFragmentVar}, { error: ${errorVar}, errorInfo: { ...${normalizedErrorInfoVar}, boundary: __mikuru_componentInfo }, retry: ${renderVar}, reset: ${renderVar}, __mikuru_context });`);
317
427
  emit(context, indent + 1, `${boundaryCleanupVar}.push(() => ${fallbackInstanceVar}.unmount());`);
@@ -354,6 +464,171 @@ function generateErrorBoundary(context, node, parentVar, cleanupVar, indent, bef
354
464
  emit(context, indent, "});");
355
465
  return startVar;
356
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, "};");
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;
619
+ emit(context, indent, "};");
620
+ emit(context, indent, `${renderVar}();`);
621
+ emit(context, indent, `${cleanupVar}.push(() => {`);
622
+ emit(context, indent + 1, `${clearTimersVar}();`);
623
+ emit(context, indent + 1, `${clearLoadingVar}();`);
624
+ emit(context, indent + 1, `${clearFallbackVar}();`);
625
+ emit(context, indent + 1, `__mikuru_runCleanup(${boundaryCleanupVar});`);
626
+ emitRemoveBetween(context, indent + 1, startVar, endVar);
627
+ emit(context, indent + 1, `${startVar}.remove();`);
628
+ emit(context, indent + 1, `${endVar}.remove();`);
629
+ emit(context, indent, "});");
630
+ return startVar;
631
+ }
357
632
  function generateSlot(context, node, parentVar, cleanupVar, indent, beforeVar) {
358
633
  const slotCleanupVar = nextVar(context, "slotCleanup");
359
634
  const slotCleanupResultVar = nextVar(context, "slotCleanup");
@@ -500,6 +775,149 @@ function generateDynamicComponent(context, node, parentVar, cleanupVar, indent,
500
775
  emit(context, indent, "});");
501
776
  return startVar;
502
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
+ }
503
921
  function emitComponentShow(context, node, componentVar, cleanupVar, indent) {
504
922
  const showAttr = node.attrs.find((attr) => attr.name === "v-show");
505
923
  if (!showAttr) {
@@ -521,7 +939,10 @@ function emitComponentShow(context, node, componentVar, cleanupVar, indent) {
521
939
  function emitComponentAttrs(context, node, attrsVar, indent) {
522
940
  const objectBindExpressions = node.attrs
523
941
  .filter((attr) => isObjectBindAttr(attr))
524
- .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
+ });
525
946
  const classParts = componentFallthroughExpressions(context, node, "class", objectBindExpressions);
526
947
  const styleParts = componentFallthroughExpressions(context, node, "style", objectBindExpressions);
527
948
  const directAttrs = componentDirectAttributeFallthroughs(context, node);
@@ -595,7 +1016,10 @@ function emitComponentAttrsProxy(context, attrsVar, baseVar, objectBindExpressio
595
1016
  function emitComponentFallthrough(context, node, componentVar, cleanupVar, indent) {
596
1017
  const objectBindExpressions = node.attrs
597
1018
  .filter((attr) => isObjectBindAttr(attr))
598
- .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
+ });
599
1023
  const classParts = componentFallthroughExpressions(context, node, "class", objectBindExpressions);
600
1024
  const styleParts = componentFallthroughExpressions(context, node, "style", objectBindExpressions);
601
1025
  const directAttrs = componentDirectAttributeFallthroughs(context, node);
@@ -730,7 +1154,7 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
730
1154
  if (slotDirectiveAttr) {
731
1155
  throwTemplateError("v-slot must be used on a <template> child in Mikuru", context, slotDirectiveAttr.loc);
732
1156
  }
733
- validateAttributes(node);
1157
+ validateAttributes(context, node);
734
1158
  const elementVar = nextVar(context, "el");
735
1159
  emit(context, indent, `const ${elementVar} = document.createElement(${quote(node.tag)});`);
736
1160
  if (context.scopeAttr) {
@@ -746,11 +1170,20 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
746
1170
  emit(context, indent, `setAttribute(${elementVar}, ${quote(attr.name)}, ${quote(attr.value === true ? "" : attr.value)});`);
747
1171
  }
748
1172
  emitTemplateRef(context, node, elementVar, cleanupVar, indent);
749
- 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
+ }
750
1180
  for (const attr of node.attrs) {
751
1181
  const modelDirective = parseModelDirective(attr.name);
752
1182
  if (modelDirective) {
753
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
+ }
754
1187
  const expression = validateAssignableExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
755
1188
  const stopVar = nextVar(context, "stop");
756
1189
  const handlerVar = nextVar(context, "handler");
@@ -768,15 +1201,17 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
768
1201
  const eventName = modelMode === "text" && !modelDirective.modifiers.includes("lazy") ? "input" : "change";
769
1202
  const propertyName = modelMode === "checkbox" || modelMode === "radio" ? "checked" : "value";
770
1203
  const renderedValue = modelMode === "checkbox"
771
- ? `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); })()`
772
1205
  : modelMode === "radio"
773
- ? `(${modelDirective.modifiers.includes("number") ? `Number(${elementVar}.getAttribute("value") ?? "on")` : `(${elementVar}.getAttribute("value") ?? "on")`} === unwrap(${expression}))`
1206
+ ? `Object.is(${modelElementValueExpression(`${elementVar}`, modelDirective.modifiers)}, unwrap(${expression}))`
774
1207
  : modelMode === "select-multiple"
775
- ? `Array.from(${elementVar}.options).forEach((option) => { option.selected = (unwrap(${expression}) ?? []).map(String).includes(option.getAttribute("value") ?? option.textContent ?? ""); })`
776
- : `String(unwrap(${expression}) ?? "")`;
777
- 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);
778
1213
  emit(context, indent, `const ${stopVar} = effect(() => {`);
779
- if (modelMode === "select-multiple") {
1214
+ if (modelMode === "select-multiple" || modelMode === "select") {
780
1215
  emit(context, indent + 1, renderedValue);
781
1216
  }
782
1217
  else {
@@ -795,11 +1230,16 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
795
1230
  }
796
1231
  if (attr.name === "v-show") {
797
1232
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
798
- const stopVar = nextVar(context, "stop");
799
- emit(context, indent, `const ${stopVar} = effect(() => {`);
800
- emit(context, indent + 1, `${elementVar}.style.display = unwrap(${expression}) ? "" : "none";`);
801
- emit(context, indent, "});");
802
- 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
+ }
803
1243
  continue;
804
1244
  }
805
1245
  if (isObjectBindAttr(attr)) {
@@ -813,7 +1253,7 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
813
1253
  const event = parseEventDirective(attr.name);
814
1254
  if (event) {
815
1255
  validateEventModifiers(event, attr, context);
816
- const handler = validateTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
1256
+ const handler = validateEventHandlerExpression(requireAttrValue(attr), context, attr.valueLoc);
817
1257
  const handlerVar = nextVar(context, "handler");
818
1258
  const handlerExpression = eventHandlerExpression(handler, context, attr.valueLoc);
819
1259
  if (event.modifiers.length) {
@@ -825,6 +1265,10 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
825
1265
  if (event.modifiers.includes("self")) {
826
1266
  emit(context, indent + 1, `if ($event.target !== ${elementVar}) { return; }`);
827
1267
  }
1268
+ const modifierGuard = eventModifierGuardExpression(event);
1269
+ if (modifierGuard) {
1270
+ emit(context, indent + 1, `if (${modifierGuard}) { return; }`);
1271
+ }
828
1272
  if (event.modifiers.includes("prevent")) {
829
1273
  emit(context, indent + 1, "$event.preventDefault();");
830
1274
  }
@@ -837,30 +1281,133 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
837
1281
  else {
838
1282
  emit(context, indent, `const ${handlerVar} = __mikuru_guardEventHandler(${handlerExpression});`);
839
1283
  }
840
- const eventOptions = eventListenerOptions(event);
841
- emit(context, indent, `${elementVar}.addEventListener(${quote(event.name)}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
842
- emit(context, indent, `${cleanupVar}.push(() => ${elementVar}.removeEventListener(${quote(event.name)}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""}));`);
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}` : ""}));`);
1291
+ }
843
1292
  continue;
844
1293
  }
845
- const bindingName = getBindingName(attr.name);
846
- 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");
847
1304
  if (bindingName === "ref") {
848
1305
  continue;
849
1306
  }
850
1307
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
851
1308
  const stopVar = nextVar(context, "stop");
852
- const valueExpression = bindingName === "class" && getStaticAttrValue(node, "class")
853
- ? `[${quote(getStaticAttrValue(node, "class"))}, ${expression}]`
854
- : expression;
855
- emit(context, indent, `const ${stopVar} = effect(() => {`);
856
- emit(context, indent + 1, `setAttribute(${elementVar}, ${quote(bindingName)}, unwrap(${valueExpression}));`);
857
- emit(context, indent, "});");
858
- 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
+ }
859
1325
  }
860
1326
  }
861
1327
  appendNode(context, parentVar, elementVar, indent, beforeVar);
862
1328
  return elementVar;
863
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
+ }
864
1411
  function emitTemplateRef(context, node, valueExpression, cleanupVar, indent) {
865
1412
  const refAttr = getTemplateRefAttr(node);
866
1413
  if (!refAttr) {
@@ -892,11 +1439,16 @@ function generateText(context, node, parentVar, cleanupVar, indent, beforeVar) {
892
1439
  const textVar = nextVar(context, "text");
893
1440
  emit(context, indent, `const ${textVar} = document.createTextNode("");`);
894
1441
  if (node.parts.some((part) => part.type === "expression")) {
895
- const stopVar = nextVar(context, "stop");
896
- emit(context, indent, `const ${stopVar} = effect(() => {`);
897
- emit(context, indent + 1, `${textVar}.textContent = ${textExpression(node.parts, context)};`);
898
- emit(context, indent, "});");
899
- 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
+ }
900
1452
  }
901
1453
  else {
902
1454
  emit(context, indent, `${textVar}.textContent = ${quote(node.parts.map((part) => part.value).join(""))};`);
@@ -905,37 +1457,45 @@ function generateText(context, node, parentVar, cleanupVar, indent, beforeVar) {
905
1457
  return textVar;
906
1458
  }
907
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");
908
1465
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
909
1466
  const prevKeysVar = nextVar(context, "boundKeys");
910
1467
  const stopVar = nextVar(context, "stop");
911
1468
  const attrsVar = nextVar(context, "attrs");
912
1469
  const nextKeysVar = nextVar(context, "boundKeys");
913
1470
  const keyVar = nextVar(context, "key");
1471
+ const boundKeyVar = nextVar(context, "key");
914
1472
  const valueVar = nextVar(context, "value");
915
1473
  const staleKeyVar = nextVar(context, "key");
916
1474
  const staticClass = getStaticAttrValue(node, "class");
1475
+ const staticStyle = getStaticAttrValue(node, "style");
917
1476
  emit(context, indent, `const ${prevKeysVar} = new Set();`);
918
1477
  emit(context, indent, `const ${stopVar} = effect(() => {`);
919
1478
  emit(context, indent + 1, `const ${attrsVar} = unwrap(${expression}) ?? {};`);
920
1479
  emit(context, indent + 1, `const ${nextKeysVar} = new Set();`);
921
1480
  emit(context, indent + 1, `if (${attrsVar} && typeof ${attrsVar} === "object") {`);
922
1481
  emit(context, indent + 2, `for (const [${keyVar}, ${valueVar}] of Object.entries(${attrsVar})) {`);
923
- emit(context, indent + 3, `${nextKeysVar}.add(${keyVar});`);
924
- if (staticClass) {
925
- 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)});`);
926
1486
  }
927
1487
  else {
928
- emit(context, indent + 3, `setAttribute(${elementVar}, ${keyVar}, unwrap(${valueVar}));`);
1488
+ emit(context, indent + 3, `setAttribute(${elementVar}, ${boundKeyVar}, unwrap(${valueVar})${bindOptionsExpression(binding)});`);
929
1489
  }
930
1490
  emit(context, indent + 2, "}");
931
1491
  emit(context, indent + 1, "}");
932
1492
  emit(context, indent + 1, `for (const ${staleKeyVar} of ${prevKeysVar}) {`);
933
1493
  emit(context, indent + 2, `if (!${nextKeysVar}.has(${staleKeyVar})) {`);
934
- if (staticClass) {
935
- 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)});`);
936
1496
  }
937
1497
  else {
938
- emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, null);`);
1498
+ emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, null${bindOptionsExpression(binding)});`);
939
1499
  }
940
1500
  emit(context, indent + 2, "}");
941
1501
  emit(context, indent + 1, "}");
@@ -947,6 +1507,12 @@ function emitObjectBind(context, node, elementVar, attr, cleanupVar, indent) {
947
1507
  emit(context, indent, `${cleanupVar}.push(${stopVar});`);
948
1508
  }
949
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);
950
1516
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
951
1517
  const listenersVar = nextVar(context, "listeners");
952
1518
  const stopVar = nextVar(context, "stop");
@@ -957,7 +1523,7 @@ function emitObjectListeners(context, elementVar, attr, cleanupVar, indent) {
957
1523
  emit(context, indent, `const ${listenersVar} = new Map();`);
958
1524
  emit(context, indent, `const ${stopVar} = effect(() => {`);
959
1525
  emit(context, indent + 1, `for (const [${eventVar}, ${handlerVar}] of ${listenersVar}) {`);
960
- emit(context, indent + 2, `${elementVar}.removeEventListener(${eventVar}, ${handlerVar});`);
1526
+ emit(context, indent + 2, `${elementVar}.removeEventListener(${eventVar}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
961
1527
  emit(context, indent + 1, "}");
962
1528
  emit(context, indent + 1, `${listenersVar}.clear();`);
963
1529
  emit(context, indent + 1, `const ${sourceVar} = unwrap(${expression}) ?? {};`);
@@ -965,7 +1531,7 @@ function emitObjectListeners(context, elementVar, attr, cleanupVar, indent) {
965
1531
  emit(context, indent + 2, `for (const [${eventVar}, ${handlerVar}] of Object.entries(${sourceVar})) {`);
966
1532
  emit(context, indent + 3, `if (typeof ${handlerVar} === "function") {`);
967
1533
  emit(context, indent + 4, `const ${wrappedHandlerVar} = __mikuru_guardEventHandler(${handlerVar});`);
968
- emit(context, indent + 4, `${elementVar}.addEventListener(${eventVar}, ${wrappedHandlerVar});`);
1534
+ emit(context, indent + 4, `${elementVar}.addEventListener(${eventVar}, ${wrappedHandlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
969
1535
  emit(context, indent + 4, `${listenersVar}.set(${eventVar}, ${wrappedHandlerVar});`);
970
1536
  emit(context, indent + 3, "}");
971
1537
  emit(context, indent + 2, "}");
@@ -974,7 +1540,7 @@ function emitObjectListeners(context, elementVar, attr, cleanupVar, indent) {
974
1540
  emit(context, indent, `${cleanupVar}.push(() => {`);
975
1541
  emit(context, indent + 1, `${stopVar}();`);
976
1542
  emit(context, indent + 1, `for (const [${eventVar}, ${handlerVar}] of ${listenersVar}) {`);
977
- emit(context, indent + 2, `${elementVar}.removeEventListener(${eventVar}, ${handlerVar});`);
1543
+ emit(context, indent + 2, `${elementVar}.removeEventListener(${eventVar}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
978
1544
  emit(context, indent + 1, "}");
979
1545
  emit(context, indent + 1, `${listenersVar}.clear();`);
980
1546
  emit(context, indent, "});");
@@ -1130,13 +1696,14 @@ function generateFor(context, node, parentVar, cleanupVar, indent, expression, b
1130
1696
  emit(context, indent, "});");
1131
1697
  return startVar;
1132
1698
  }
1133
- 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) {
1134
1700
  const startVar = nextVar(context, "forStart");
1135
1701
  const endVar = nextVar(context, "forEnd");
1136
1702
  const recordsVar = nextVar(context, "forRecords");
1137
1703
  const stopVar = nextVar(context, "stop");
1138
1704
  const compiledSource = compileTemplateExpression(sourceExpression, "v-for source", toExpressionContext(context, getStringAttrLocation(node, "v-for")));
1139
1705
  const compiledKey = compileTemplateExpression(keyExpression, "v-for key", toExpressionContext(context, getKeyAttrLocation(node)));
1706
+ const compiledMemo = getMemoExpression(context, node) ?? getOnceMemoExpression(context, node);
1140
1707
  emit(context, indent, `const ${recordsVar} = new Map();`);
1141
1708
  emit(context, indent, `const ${startVar} = document.createComment("for");`);
1142
1709
  emit(context, indent, `const ${endVar} = document.createComment("/for");`);
@@ -1153,6 +1720,8 @@ function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName
1153
1720
  const recordCleanupVar = nextVar(context, "forRecordCleanup");
1154
1721
  const itemRefVar = nextVar(context, "forItemRef");
1155
1722
  const indexRefVar = nextVar(context, "forIndexRef");
1723
+ const memoVar = compiledMemo ? nextVar(context, "forMemo") : undefined;
1724
+ const memoChangedVar = compiledMemo ? nextVar(context, "forMemoChanged") : undefined;
1156
1725
  emit(context, indent + 1, `const ${sourceVar} = unwrap(${compiledSource}) ?? [];`);
1157
1726
  emit(context, indent + 1, `const ${nextRecordsVar} = new Map();`);
1158
1727
  emit(context, indent + 1, `for (let ${indexVar} = 0; ${indexVar} < ${sourceVar}.length; ${indexVar} += 1) {`);
@@ -1163,6 +1732,9 @@ function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName
1163
1732
  emit(context, indent + 2, `const ${indexName} = ${rawIndexVar};`);
1164
1733
  }
1165
1734
  emit(context, indent + 2, `const ${keyVar} = unwrap(${compiledKey});`);
1735
+ if (compiledMemo && memoVar) {
1736
+ emit(context, indent + 2, `const ${memoVar} = ${compiledMemo};`);
1737
+ }
1166
1738
  emit(context, indent + 2, `let ${recordVar} = ${recordsVar}.get(${keyVar});`);
1167
1739
  emit(context, indent + 2, `if (!${recordVar}) {`);
1168
1740
  emit(context, indent + 3, `const ${recordCleanupVar} = [];`);
@@ -1176,14 +1748,32 @@ function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName
1176
1748
  emit(context, indent + 4, `const ${indexName} = ${indexRefVar};`);
1177
1749
  }
1178
1750
  const elementVar = withTemplateRefMode(context, "array", () => generateNode(context, withoutForAttrs(node), parentVar, recordCleanupVar, indent + 4, endVar));
1179
- 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}` : ""} };`);
1180
1755
  emit(context, indent + 3, `}`);
1181
1756
  emit(context, indent + 2, `} else {`);
1182
- emit(context, indent + 3, `${recordVar}.item.value = ${rawItemVar};`);
1183
- if (indexName) {
1184
- 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
+ }
1185
1772
  }
1186
1773
  emit(context, indent + 3, `${parentVar}.insertBefore(${recordVar}.element, ${endVar});`);
1774
+ if (transitionVar) {
1775
+ emit(context, indent + 3, `__mikuru_applyTransitionMove(${recordVar}.element, ${transitionVar});`);
1776
+ }
1187
1777
  emit(context, indent + 2, `}`);
1188
1778
  emit(context, indent + 2, `${nextRecordsVar}.set(${keyVar}, ${recordVar});`);
1189
1779
  emit(context, indent + 1, `}`);
@@ -1234,6 +1824,16 @@ function withTemplateRefMode(context, mode, callback) {
1234
1824
  context.templateRefMode = previousMode;
1235
1825
  }
1236
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
+ }
1237
1837
  function splitTopLevel(source, delimiter) {
1238
1838
  const parts = [];
1239
1839
  let depth = 0;
@@ -1665,11 +2265,241 @@ function isIdentifier(value) {
1665
2265
  return /^[A-Za-z_$][\w$]*$/.test(value);
1666
2266
  }
1667
2267
  function eventHandlerExpression(expression, context, location) {
1668
- const validatedExpression = validateTemplateExpression(expression, "event handler", toExpressionContext(context, location));
2268
+ const validatedExpression = validateEventHandlerExpression(expression, context, location);
1669
2269
  if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(validatedExpression)) {
1670
2270
  return validatedExpression;
1671
2271
  }
1672
- 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;
1673
2503
  }
1674
2504
  function getStringAttr(node, name) {
1675
2505
  const attr = node.attrs.find((candidate) => candidate.name === name);
@@ -1697,6 +2527,24 @@ function getKeyExpression(node) {
1697
2527
  function getKeyAttrLocation(node) {
1698
2528
  return getStringAttrLocation(node, ":key") ?? getStringAttrLocation(node, "v-bind:key");
1699
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
+ }
1700
2548
  function toExpressionContext(context, location) {
1701
2549
  if (!context.source || !location) {
1702
2550
  return undefined;
@@ -1711,7 +2559,7 @@ function withoutAttr(node, name) {
1711
2559
  return withoutAttrs(node, [name]);
1712
2560
  }
1713
2561
  function withoutForAttrs(node) {
1714
- 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"]);
1715
2563
  }
1716
2564
  function withoutAttrs(node, names) {
1717
2565
  return {
@@ -1743,6 +2591,12 @@ function emitComponentProps(context, node, propsVar, attrsVar, indent) {
1743
2591
  .flatMap((attr) => componentPropEntries(context, attr));
1744
2592
  const objectBindAttrs = node.attrs.filter((attr) => isObjectBindAttr(attr));
1745
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
+ }
1746
2600
  const slots = collectComponentSlots(context, node);
1747
2601
  const defaultSlot = slots.find((slot) => !slot.nameExpression && slot.name === "default");
1748
2602
  const needsProxy = objectBindAttrs.length > 0 || objectOnAttrs.length > 0;
@@ -2072,12 +2926,15 @@ function componentPropEntries(context, attr) {
2072
2926
  validateModelModifiers(modelDirective, attr, context);
2073
2927
  const expression = validateAssignableExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
2074
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";
2075
2932
  const entries = [
2076
- `get modelValue() { return unwrap(${valueExpression}); }`,
2077
- `onUpdateModelValue: __mikuru_guardEventHandler(($value) => { ${expression}.value = $value; })`
2933
+ `get ${quotePropertyName(propName)}() { return unwrap(${valueExpression}); }`,
2934
+ `${quotePropertyName(updatePropName)}: __mikuru_guardEventHandler(($value) => { ${expression}.value = $value; })`
2078
2935
  ];
2079
2936
  if (modelDirective.modifiers.length > 0) {
2080
- entries.push(`modelModifiers: { ${modelDirective.modifiers.map((modifier) => `${quotePropertyName(modifier)}: true`).join(", ")} }`);
2937
+ entries.push(`${quotePropertyName(modifiersPropName)}: { ${modelDirective.modifiers.map((modifier) => `${quotePropertyName(modifier)}: true`).join(", ")} }`);
2081
2938
  }
2082
2939
  return entries;
2083
2940
  }
@@ -2085,13 +2942,36 @@ function componentPropEntries(context, attr) {
2085
2942
  if (event) {
2086
2943
  validateComponentEventModifiers(event, attr, context);
2087
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
+ }
2088
2952
  return [
2089
- `${quotePropertyName(toComponentEventProp(event.name))}: __mikuru_guardEventHandler(${componentEventHandlerExpression(event, eventHandlerExpression(handler, context, attr.valueLoc), context)})`
2953
+ `${quotePropertyName(toComponentEventProp(event.name ?? ""))}: __mikuru_guardEventHandler(${componentEventHandlerExpression(event, eventHandlerExpression(handler, context, attr.valueLoc), context)})`
2090
2954
  ];
2091
2955
  }
2092
- const bindingName = getBindingName(attr.name);
2093
- 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");
2094
2971
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
2972
+ if (context.once) {
2973
+ return [`${quotePropertyName(bindingName)}: unwrap(${expression})`];
2974
+ }
2095
2975
  return [`get ${quotePropertyName(bindingName)}() { return unwrap(${expression}); }`];
2096
2976
  }
2097
2977
  if (attr.name === "v-show") {
@@ -2151,7 +3031,8 @@ function getTransitionOptionsExpression(context, node) {
2151
3031
  ["enter-to-class", "enterToClass"],
2152
3032
  ["leave-from-class", "leaveFromClass"],
2153
3033
  ["leave-active-class", "leaveActiveClass"],
2154
- ["leave-to-class", "leaveToClass"]
3034
+ ["leave-to-class", "leaveToClass"],
3035
+ ["move-class", "moveClass"]
2155
3036
  ];
2156
3037
  for (const [attrName, optionName] of classAttrs) {
2157
3038
  const expression = getTransitionAttrExpression(context, node, attrName);
@@ -2176,7 +3057,7 @@ function getTransitionAttrExpression(context, node, name, fallback) {
2176
3057
  return fallback === undefined ? undefined : quote(fallback);
2177
3058
  }
2178
3059
  function validateTransitionAttributes(context, node) {
2179
- const supported = new Set([
3060
+ const supported = [
2180
3061
  "name",
2181
3062
  "appear",
2182
3063
  "mode",
@@ -2186,13 +3067,33 @@ function validateTransitionAttributes(context, node) {
2186
3067
  "leave-from-class",
2187
3068
  "leave-active-class",
2188
3069
  "leave-to-class"
2189
- ]);
3070
+ ].map((name) => ({ name, display: name }));
3071
+ for (const attr of node.attrs) {
3072
+ const name = getBindingName(attr.name) ?? attr.name;
3073
+ if (supported.some((candidate) => candidate.name === name)) {
3074
+ continue;
3075
+ }
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 }));
2190
3091
  for (const attr of node.attrs) {
2191
3092
  const name = getBindingName(attr.name) ?? attr.name;
2192
- if (supported.has(name)) {
3093
+ if (supported.some((candidate) => candidate.name === name)) {
2193
3094
  continue;
2194
3095
  }
2195
- throwTemplateError(`<Transition> only supports name, appear, and CSS class override attributes in v1`, context, attr.loc);
3096
+ throwUnsupportedSpecialAttribute(context, "TransitionGroup", attr, supported, "name, tag, and CSS class override attributes");
2196
3097
  }
2197
3098
  }
2198
3099
  function getSingleElementChild(context, node, label) {
@@ -2202,6 +3103,13 @@ function getSingleElementChild(context, node, label) {
2202
3103
  }
2203
3104
  return [meaningful[0]];
2204
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
+ }
2205
3113
  function getErrorBoundaryFallbackExpression(context, node) {
2206
3114
  const fallbackAttr = node.attrs.find((attr) => getBindingName(attr.name) === "fallback");
2207
3115
  if (!fallbackAttr) {
@@ -2216,13 +3124,82 @@ function getErrorBoundaryResetKeyExpression(context, node) {
2216
3124
  }
2217
3125
  return compileTemplateExpression(requireAttrValue(resetKeyAttr), resetKeyAttr.name, toExpressionContext(context, resetKeyAttr.valueLoc));
2218
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
+ }
2219
3163
  function validateErrorBoundaryAttributes(context, node) {
3164
+ const supported = [
3165
+ { name: "fallback", display: ":fallback" },
3166
+ { name: "reset-key", display: ":reset-key" }
3167
+ ];
2220
3168
  for (const attr of node.attrs) {
2221
3169
  const bindingName = getBindingName(attr.name);
2222
3170
  if (bindingName === "fallback" || bindingName === "reset-key") {
2223
3171
  continue;
2224
3172
  }
2225
- throwTemplateError("<ErrorBoundary> only supports :fallback and :reset-key in v1", context, attr.loc);
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
+ ];
3183
+ for (const attr of node.attrs) {
3184
+ const bindingName = getBindingName(attr.name);
3185
+ if (bindingName === "loading" || bindingName === "fallback" || bindingName === "delay" || bindingName === "timeout") {
3186
+ continue;
3187
+ }
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");
2226
3203
  }
2227
3204
  }
2228
3205
  function getTeleportToExpression(context, node) {
@@ -2244,14 +3221,56 @@ function getTeleportDisabledExpression(context, node) {
2244
3221
  return hasStaticBooleanAttr(node, "disabled") ? "true" : "false";
2245
3222
  }
2246
3223
  function validateTeleportAttributes(context, node) {
3224
+ const supported = [
3225
+ { name: "to", display: "to" },
3226
+ { name: "disabled", display: "disabled" }
3227
+ ];
2247
3228
  for (const attr of node.attrs) {
2248
3229
  const name = getBindingName(attr.name) ?? attr.name;
2249
3230
  if (name === "to" || name === "disabled") {
2250
3231
  continue;
2251
3232
  }
2252
- throwTemplateError("<Teleport> only supports to and disabled in v1", context, attr.loc);
3233
+ throwUnsupportedSpecialAttribute(context, "Teleport", attr, supported);
2253
3234
  }
2254
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
+ }
2255
3274
  function toComponentEventProp(eventName) {
2256
3275
  return `on${eventName
2257
3276
  .split(/[-:]/)
@@ -2259,18 +3278,67 @@ function toComponentEventProp(eventName) {
2259
3278
  .map((part) => part[0]?.toUpperCase() + part.slice(1))
2260
3279
  .join("")}`;
2261
3280
  }
2262
- 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
+ }
2263
3287
  for (const attr of node.attrs) {
2264
3288
  if (attr.name.startsWith("v-") && !isSupportedDirectiveAttr(attr)) {
2265
- 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);
2266
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);
2267
3315
  }
2268
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
+ }
2269
3337
  function isDirectiveAttr(attr) {
2270
3338
  return isSupportedDirectiveAttr(attr) || attr.name.startsWith("@") || attr.name.startsWith(":");
2271
3339
  }
2272
3340
  function isStructuralAttr(attr) {
2273
- 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";
2274
3342
  }
2275
3343
  function isSlotDirectiveAttr(attr) {
2276
3344
  return attr.name === "v-slot" || attr.name.startsWith("v-slot:") || attr.name.startsWith("#");
@@ -2281,19 +3349,55 @@ function isSupportedDirectiveAttr(attr) {
2281
3349
  attr.name === "v-else" ||
2282
3350
  attr.name === "v-for" ||
2283
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" ||
2284
3358
  Boolean(parseModelDirective(attr.name)) ||
2285
3359
  isObjectBindAttr(attr) ||
2286
3360
  isObjectOnAttr(attr) ||
3361
+ Boolean(getDynamicBindingArgument(attr.name)) ||
3362
+ Boolean(getDynamicEventArgument(attr.name)) ||
2287
3363
  Boolean(parseEventDirective(attr.name)) ||
2288
3364
  Boolean(getBindingName(attr.name)));
2289
3365
  }
2290
3366
  function isObjectBindAttr(attr) {
2291
- 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);
2292
3383
  }
2293
3384
  function isObjectOnAttr(attr) {
2294
- 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) };
2295
3395
  }
2296
3396
  function parseEventDirective(name) {
3397
+ const dynamic = getDynamicEventArgument(name);
3398
+ if (dynamic) {
3399
+ return dynamic;
3400
+ }
2297
3401
  const rawName = getEventName(name);
2298
3402
  if (!rawName) {
2299
3403
  return undefined;
@@ -2308,30 +3412,49 @@ function parseModelDirective(name) {
2308
3412
  if (name === "v-model") {
2309
3413
  return { modifiers: [] };
2310
3414
  }
2311
- if (!name.startsWith("v-model.")) {
3415
+ if (!name.startsWith("v-model.") && !name.startsWith("v-model:")) {
2312
3416
  return undefined;
2313
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
+ }
2314
3423
  return { modifiers: name.slice("v-model.".length).split(".").filter(Boolean) };
2315
3424
  }
2316
3425
  function validateModelModifiers(model, attr, context) {
2317
- 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
+ }
2318
3430
  for (const modifier of model.modifiers) {
2319
- if (!supportedModifiers.has(modifier)) {
2320
- 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);
2321
3435
  }
2322
3436
  }
2323
3437
  }
2324
- 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) {
2325
3444
  if (modelMode === "checkbox") {
2326
- 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; })()`;
2327
3447
  }
2328
3448
  if (modelMode === "radio") {
2329
- const valueExpression = `($event.target.getAttribute("value") ?? "on")`;
2330
- return modifiers.includes("number") ? `Number(${valueExpression})` : valueExpression;
3449
+ return modelElementValueExpression("$event.target", modifiers);
2331
3450
  }
2332
3451
  if (modelMode === "select-multiple") {
2333
- const valueExpression = `Array.from($event.target.options).filter((option) => option.selected).map((option) => option.getAttribute("value") ?? option.textContent ?? "")`;
2334
- 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;
2335
3458
  }
2336
3459
  let valueExpression = "$event.target.value";
2337
3460
  if (modifiers.includes("trim")) {
@@ -2343,10 +3466,12 @@ function modelAssignedValue(modelMode, modifiers) {
2343
3466
  return valueExpression;
2344
3467
  }
2345
3468
  function validateEventModifiers(event, attr, context) {
2346
- const supportedModifiers = new Set(["prevent", "stop", "self", "once", "capture", "passive"]);
3469
+ const supportedModifiers = [...eventControlModifiers, ...eventOptionModifiers, ...eventSystemModifiers, ...eventMouseModifiers, ...eventKeyModifiers, "exact"];
2347
3470
  for (const modifier of event.modifiers) {
2348
- if (!supportedModifiers.has(modifier)) {
2349
- 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);
2350
3475
  }
2351
3476
  }
2352
3477
  if (event.modifiers.includes("passive") && event.modifiers.includes("prevent")) {
@@ -2354,12 +3479,30 @@ function validateEventModifiers(event, attr, context) {
2354
3479
  }
2355
3480
  }
2356
3481
  function validateComponentEventModifiers(event, attr, context) {
3482
+ const supportedModifiers = ["once"];
2357
3483
  for (const modifier of event.modifiers) {
2358
3484
  if (modifier !== "once") {
2359
- 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);
2360
3500
  }
2361
3501
  }
2362
3502
  }
3503
+ function suggestModifierName(name, supported) {
3504
+ return suggestName(name, supported.map((candidate) => ({ name: candidate, display: candidate })))?.name;
3505
+ }
2363
3506
  function eventListenerOptions(event) {
2364
3507
  const options = [
2365
3508
  event.modifiers.includes("capture") ? "capture: true" : undefined,
@@ -2368,6 +3511,66 @@ function eventListenerOptions(event) {
2368
3511
  ].filter(Boolean);
2369
3512
  return options.length ? `{ ${options.join(", ")} }` : undefined;
2370
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
+ }
2371
3574
  function componentEventHandlerExpression(event, handlerExpression, context) {
2372
3575
  if (!event.modifiers.includes("once")) {
2373
3576
  return handlerExpression;
@@ -2376,7 +3579,13 @@ function componentEventHandlerExpression(event, handlerExpression, context) {
2376
3579
  const handlerVar = nextVar(context, "handler");
2377
3580
  return `(() => { let ${calledVar} = false; const ${handlerVar} = ${handlerExpression}; return (...$args) => { if (${calledVar}) { return; } ${calledVar} = true; return ${handlerVar}(...$args); }; })()`;
2378
3581
  }
3582
+ function componentEventPropRuntimeExpression(eventNameExpression) {
3583
+ return `"on" + ${eventNameExpression}.split(/[-:]/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join("")`;
3584
+ }
2379
3585
  function getEventName(name) {
3586
+ if (name.startsWith("@[") || name.startsWith("v-on:[")) {
3587
+ return undefined;
3588
+ }
2380
3589
  if (name.startsWith("@")) {
2381
3590
  return name.slice(1);
2382
3591
  }
@@ -2386,11 +3595,108 @@ function getEventName(name) {
2386
3595
  return undefined;
2387
3596
  }
2388
3597
  function getBindingName(name) {
2389
- if (name.startsWith(":")) {
2390
- 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;
2391
3680
  }
2392
- if (name.startsWith("v-bind:")) {
2393
- 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 };
2394
3700
  }
2395
3701
  return undefined;
2396
3702
  }