mikuru 1.0.14 → 1.0.16

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.
@@ -26,6 +26,68 @@ export function generate(descriptor, root) {
26
26
  emit(context, 3, "cleanup();");
27
27
  emit(context, 2, "}");
28
28
  emit(context, 1, "};");
29
+ emit(context, 1, "const __mikuru_transitionFrame = (fn) => {");
30
+ emit(context, 2, "const raf = globalThis.requestAnimationFrame;");
31
+ emit(context, 2, "if (typeof raf === \"function\") { raf(() => raf(fn)); } else { setTimeout(fn, 0); }");
32
+ emit(context, 1, "};");
33
+ emit(context, 1, "const __mikuru_transitionDone = (element, cleanup) => {");
34
+ emit(context, 2, "let done = false;");
35
+ emit(context, 2, "const finish = () => {");
36
+ emit(context, 3, "if (done) { return; }");
37
+ emit(context, 3, "done = true;");
38
+ emit(context, 3, "element.removeEventListener(\"transitionend\", finish);");
39
+ emit(context, 3, "element.removeEventListener(\"animationend\", finish);");
40
+ emit(context, 3, "cleanup();");
41
+ emit(context, 2, "};");
42
+ emit(context, 2, "element.addEventListener(\"transitionend\", finish);");
43
+ emit(context, 2, "element.addEventListener(\"animationend\", finish);");
44
+ emit(context, 2, "setTimeout(finish, 50);");
45
+ emit(context, 1, "};");
46
+ emit(context, 1, "const __mikuru_applyTransitionEnter = (element, name) => {");
47
+ emit(context, 2, "const transition = typeof name === \"object\" ? name : { name };");
48
+ emit(context, 2, "if (!element || element.nodeType !== 1 || !transition.name) { return; }");
49
+ emit(context, 2, "const from = transition.enterFromClass || `${transition.name}-enter-from`;");
50
+ emit(context, 2, "const active = transition.enterActiveClass || `${transition.name}-enter-active`;");
51
+ emit(context, 2, "const to = transition.enterToClass || `${transition.name}-enter-to`;");
52
+ emit(context, 2, "element.classList.add(from, active);");
53
+ emit(context, 2, "__mikuru_transitionFrame(() => {");
54
+ emit(context, 3, "element.classList.remove(from);");
55
+ emit(context, 3, "element.classList.add(to);");
56
+ emit(context, 3, "__mikuru_transitionDone(element, () => element.classList.remove(active, to));");
57
+ emit(context, 2, "});");
58
+ emit(context, 1, "};");
59
+ emit(context, 1, "const __mikuru_removeNode = (node) => {");
60
+ emit(context, 2, "if (!node || !node.parentNode) { return; }");
61
+ emit(context, 2, "if (node.nodeType !== 1 || !node.__mikuru_transition || node.__mikuru_transitionLeaving) { node.remove(); return; }");
62
+ emit(context, 2, "node.__mikuru_transitionLeaving = true;");
63
+ emit(context, 2, "const transition = node.__mikuru_transition;");
64
+ emit(context, 2, "const from = transition.leaveFromClass || `${transition.name}-leave-from`;");
65
+ emit(context, 2, "const active = transition.leaveActiveClass || `${transition.name}-leave-active`;");
66
+ emit(context, 2, "const to = transition.leaveToClass || `${transition.name}-leave-to`;");
67
+ emit(context, 2, "node.classList.add(from, active);");
68
+ emit(context, 2, "__mikuru_transitionFrame(() => {");
69
+ emit(context, 3, "node.classList.remove(from);");
70
+ emit(context, 3, "node.classList.add(to);");
71
+ emit(context, 3, "__mikuru_transitionDone(node, () => { node.classList.remove(active, to); node.remove(); });");
72
+ emit(context, 2, "});");
73
+ emit(context, 1, "};");
74
+ emit(context, 1, "const __mikuru_setRef = (target, value, multiple = false) => {");
75
+ emit(context, 2, "if (typeof target === \"function\") {");
76
+ emit(context, 3, "target(value);");
77
+ emit(context, 3, "return () => target(null);");
78
+ emit(context, 2, "}");
79
+ emit(context, 2, "if (!target || typeof target !== \"object\" || !(\"value\" in target)) {");
80
+ emit(context, 3, "return () => {};");
81
+ emit(context, 2, "}");
82
+ emit(context, 2, "if (multiple) {");
83
+ emit(context, 3, "const list = Array.isArray(target.value) ? target.value : [];");
84
+ emit(context, 3, "if (target.value !== list) { target.value = list; }");
85
+ emit(context, 3, "list.push(value);");
86
+ emit(context, 3, "return () => { const index = list.indexOf(value); if (index >= 0) { list.splice(index, 1); } };");
87
+ emit(context, 2, "}");
88
+ emit(context, 2, "target.value = value;");
89
+ emit(context, 2, "return () => { if (target.value === value) { target.value = null; } };");
90
+ emit(context, 1, "};");
29
91
  emit(context, 1, "// expose a lightweight registrar for runtime lifecycle and provide/inject helpers");
30
92
  emit(context, 1, "const __mikuru_previousRegistrar = globalThis.__mikuru_currentRegistrar;");
31
93
  emit(context, 1, "globalThis.__mikuru_currentRegistrar = {");
@@ -52,6 +114,9 @@ export function generate(descriptor, root) {
52
114
  if (script.usesPropsAlias) {
53
115
  emit(context, 1, "const __mikuru_props = props;");
54
116
  }
117
+ if (script.usesAttrsAlias) {
118
+ emit(context, 1, "const __mikuru_attrs = props.__mikuru_attrs ?? {};");
119
+ }
55
120
  if (script.usesEmitAlias) {
56
121
  emit(context, 1, "const __mikuru_emit = (name, ...args) => {");
57
122
  emit(context, 2, "const handlerName = \"on\" + String(name).split(/[-:]/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(\"\");");
@@ -73,12 +138,12 @@ export function generate(descriptor, root) {
73
138
  emit(context, 2, "unmount() {");
74
139
  emit(context, 3, "__mikuru_runCleanup(__mikuru_cleanup);");
75
140
  emit(context, 3, "for (const cb of __mikuru_afterUnmount.splice(0).reverse()) { try { cb(); } catch (e) { setTimeout(() => { throw e; }); } }");
76
- emit(context, 3, `${rootVar}.remove();`);
141
+ emit(context, 3, `__mikuru_removeNode(${rootVar});`);
77
142
  emit(context, 2, "}");
78
143
  emit(context, 1, "};");
79
144
  emit(context, 0, "}");
80
145
  emit(context, 0, "");
81
- emit(context, 0, "const __mikuru_component = { mount };");
146
+ emit(context, 0, `const __mikuru_component = { mount${script.inheritAttrs ? "" : ", inheritAttrs: false"} };`);
82
147
  emit(context, 0, "export default __mikuru_component;");
83
148
  return `${context.lines.join("\n")}\n`;
84
149
  }
@@ -110,6 +175,12 @@ function generateNode(context, node, parentVar, cleanupVar, indent, beforeVar) {
110
175
  if (orphanElseAttr) {
111
176
  throwTemplateError(`${orphanElseAttr.name} must follow v-if or v-else-if`, context, orphanElseAttr.loc);
112
177
  }
178
+ if (node.tag === "component") {
179
+ return generateDynamicComponent(context, node, parentVar, cleanupVar, indent, beforeVar);
180
+ }
181
+ if (node.tag === "Transition") {
182
+ return generateTransition(context, node, parentVar, cleanupVar, indent, beforeVar);
183
+ }
113
184
  if (isComponentTag(node.tag)) {
114
185
  return generateComponent(context, node, parentVar, cleanupVar, indent, beforeVar);
115
186
  }
@@ -118,14 +189,64 @@ function generateNode(context, node, parentVar, cleanupVar, indent, beforeVar) {
118
189
  }
119
190
  return generateElement(context, node, parentVar, cleanupVar, indent, beforeVar);
120
191
  }
192
+ function generateTransition(context, node, parentVar, cleanupVar, indent, beforeVar) {
193
+ validateTransitionAttributes(context, node);
194
+ const children = getTransitionChildren(context, node);
195
+ const transitionVar = nextVar(context, "transition");
196
+ emit(context, indent, `const ${transitionVar} = ${getTransitionOptionsExpression(context, node)};`);
197
+ const child = children[0];
198
+ if (getStringAttr(child, "v-if")) {
199
+ return generateIfChain(context, getTransitionBranches(context, children), parentVar, cleanupVar, indent, beforeVar, transitionVar);
200
+ }
201
+ if (child.tag === "component") {
202
+ return generateDynamicComponent(context, child, parentVar, cleanupVar, indent, beforeVar, transitionVar);
203
+ }
204
+ const childVar = generateNode(context, child, parentVar, cleanupVar, indent, beforeVar);
205
+ emitTransitionRegistration(context, childVar, transitionVar, indent);
206
+ return childVar;
207
+ }
121
208
  function generateSlot(context, node, parentVar, cleanupVar, indent, beforeVar) {
122
- const slotVar = nextVar(context, "slot");
123
209
  const slotCleanupVar = nextVar(context, "slotCleanup");
124
210
  const slotCleanupResultVar = nextVar(context, "slotCleanup");
125
211
  const slotFnVar = nextVar(context, "slot");
126
212
  const slotPropsVar = nextVar(context, "slotProps");
127
- const slotName = getSlotOutletName(node, context);
128
- emit(context, indent, `const ${slotFnVar} = ${slotName === "default" ? "props.slots?.default ?? props.children" : `props.slots?.[${quote(slotName)}]`};`);
213
+ const slotName = getSlotOutletNameExpression(node, context);
214
+ if (slotName.dynamic) {
215
+ const startVar = nextVar(context, "slotStart");
216
+ const endVar = nextVar(context, "slotEnd");
217
+ const slotFragmentVar = nextVar(context, "slot");
218
+ const slotNameVar = nextVar(context, "slotName");
219
+ const stopVar = nextVar(context, "stop");
220
+ emit(context, indent, `const ${startVar} = document.createComment("slot");`);
221
+ emit(context, indent, `const ${endVar} = document.createComment("/slot");`);
222
+ appendNode(context, parentVar, startVar, indent, beforeVar);
223
+ appendNode(context, parentVar, endVar, indent, beforeVar);
224
+ emit(context, indent, `const ${slotCleanupVar} = [];`);
225
+ emit(context, indent, `const ${stopVar} = effect(() => {`);
226
+ emit(context, indent + 1, `__mikuru_runCleanup(${slotCleanupVar});`);
227
+ emitRemoveBetween(context, indent + 1, startVar, endVar);
228
+ emit(context, indent + 1, `const ${slotNameVar} = String(unwrap(${slotName.expression}) ?? "default");`);
229
+ emit(context, indent + 1, `const ${slotFnVar} = ${slotNameVar} === "default" ? props.slots?.default ?? props.children : props.slots?.[${slotNameVar}];`);
230
+ emitSlotOutletProps(context, node, slotPropsVar, indent + 1);
231
+ emit(context, indent + 1, `const ${slotFragmentVar} = document.createDocumentFragment();`);
232
+ emit(context, indent + 1, `if (${slotFnVar}) {`);
233
+ emit(context, indent + 2, `const ${slotCleanupResultVar} = ${slotFnVar}(${slotFragmentVar}, ${slotPropsVar});`);
234
+ emit(context, indent + 2, `if (${slotCleanupResultVar}) {`);
235
+ emit(context, indent + 3, `${slotCleanupVar}.push(${slotCleanupResultVar});`);
236
+ emit(context, indent + 2, "}");
237
+ emit(context, indent + 1, "} else {");
238
+ generateChildren(context, node.children, slotFragmentVar, slotCleanupVar, indent + 2);
239
+ emit(context, indent + 1, "}");
240
+ appendNode(context, parentVar, slotFragmentVar, indent + 1, endVar);
241
+ emit(context, indent, "});");
242
+ emit(context, indent, `${cleanupVar}.push(() => {`);
243
+ emit(context, indent + 1, `${stopVar}();`);
244
+ emit(context, indent + 1, `__mikuru_runCleanup(${slotCleanupVar});`);
245
+ emit(context, indent, "});");
246
+ return startVar;
247
+ }
248
+ const slotVar = nextVar(context, "slot");
249
+ emit(context, indent, `const ${slotFnVar} = ${slotName.name === "default" ? "props.slots?.default ?? props.children" : `props.slots?.[${quote(slotName.name)}]`};`);
129
250
  emitSlotOutletProps(context, node, slotPropsVar, indent);
130
251
  emit(context, indent, `const ${slotVar} = document.createDocumentFragment();`);
131
252
  emit(context, indent, `const ${slotCleanupVar} = [];`);
@@ -143,20 +264,318 @@ function generateSlot(context, node, parentVar, cleanupVar, indent, beforeVar) {
143
264
  }
144
265
  function generateComponent(context, node, parentVar, cleanupVar, indent, beforeVar) {
145
266
  const fragmentVar = nextVar(context, "fragment");
267
+ const attrsVar = nextVar(context, "attrs");
146
268
  const propsVar = nextVar(context, "props");
147
269
  const componentVar = nextVar(context, "component");
148
270
  emit(context, indent, `const ${fragmentVar} = document.createDocumentFragment();`);
149
- emitComponentProps(context, node, propsVar, indent);
271
+ emitComponentAttrs(context, node, attrsVar, indent);
272
+ emitComponentProps(context, node, propsVar, attrsVar, indent);
150
273
  emit(context, indent, `const ${componentVar} = ${node.tag}.mount(${fragmentVar}, ${propsVar});`);
151
274
  if (context.scopeAttr) {
152
- emit(context, indent, `if (${componentVar}.element instanceof Element) {`);
275
+ emit(context, indent, `if (${componentVar}.element?.nodeType === 1) {`);
153
276
  emit(context, indent + 1, `${componentVar}.element.setAttribute(${quote(context.scopeAttr)}, "");`);
154
277
  emit(context, indent, "}");
155
278
  }
279
+ emit(context, indent, `if (${node.tag}.inheritAttrs !== false) {`);
280
+ emitComponentFallthrough(context, node, componentVar, cleanupVar, indent + 1);
281
+ emit(context, indent, "}");
282
+ emitComponentShow(context, node, componentVar, cleanupVar, indent);
156
283
  emit(context, indent, `${cleanupVar}.push(() => ${componentVar}.unmount());`);
284
+ emitTemplateRef(context, node, componentVar, cleanupVar, indent);
157
285
  appendNode(context, parentVar, fragmentVar, indent, beforeVar);
158
286
  return `${componentVar}.element`;
159
287
  }
288
+ function generateDynamicComponent(context, node, parentVar, cleanupVar, indent, beforeVar, transitionVar) {
289
+ const isAttr = node.attrs.find((attr) => getBindingName(attr.name) === "is");
290
+ if (!isAttr) {
291
+ throwTemplateError("Dynamic component requires :is to resolve to a component object", context, node.loc);
292
+ }
293
+ const expression = compileTemplateExpression(requireAttrValue(isAttr), isAttr.name, toExpressionContext(context, isAttr.valueLoc));
294
+ const dynamicNode = withoutAttrs(node, ["is", ":is", "v-bind:is"]);
295
+ const startVar = nextVar(context, "componentStart");
296
+ const endVar = nextVar(context, "componentEnd");
297
+ const branchCleanupVar = nextVar(context, "componentCleanup");
298
+ const componentTypeVar = nextVar(context, "componentType");
299
+ const currentTypeVar = nextVar(context, "currentComponent");
300
+ const currentInstanceVar = nextVar(context, "currentComponent");
301
+ const stopVar = nextVar(context, "stop");
302
+ emit(context, indent, `const ${startVar} = document.createComment("component");`);
303
+ emit(context, indent, `const ${endVar} = document.createComment("/component");`);
304
+ appendNode(context, parentVar, startVar, indent, beforeVar);
305
+ appendNode(context, parentVar, endVar, indent, beforeVar);
306
+ emit(context, indent, `const ${branchCleanupVar} = [];`);
307
+ emit(context, indent, `let ${currentTypeVar};`);
308
+ emit(context, indent, `let ${currentInstanceVar};`);
309
+ emit(context, indent, `const ${stopVar} = effect(() => {`);
310
+ emit(context, indent + 1, `const ${componentTypeVar} = unwrap(${expression});`);
311
+ emit(context, indent + 1, `if (${componentTypeVar} === ${currentTypeVar}) { return; }`);
312
+ emit(context, indent + 1, `__mikuru_runCleanup(${branchCleanupVar});`);
313
+ emitRemoveBetween(context, indent + 1, startVar, endVar);
314
+ emit(context, indent + 1, `if (!${componentTypeVar}) {`);
315
+ emit(context, indent + 2, `${currentTypeVar} = ${componentTypeVar};`);
316
+ emit(context, indent + 2, `${currentInstanceVar} = null;`);
317
+ emit(context, indent + 2, "return;");
318
+ emit(context, indent + 1, "}");
319
+ emit(context, indent + 1, `if (typeof ${componentTypeVar} !== "object" || typeof ${componentTypeVar}.mount !== "function") {`);
320
+ emit(context, indent + 2, `throw new Error("Dynamic component :is must resolve to a component object with mount()");`);
321
+ emit(context, indent + 1, "}");
322
+ const fragmentVar = nextVar(context, "fragment");
323
+ const attrsVar = nextVar(context, "attrs");
324
+ const propsVar = nextVar(context, "props");
325
+ const componentVar = nextVar(context, "component");
326
+ emit(context, indent + 1, `const ${fragmentVar} = document.createDocumentFragment();`);
327
+ emitComponentAttrs(context, dynamicNode, attrsVar, indent + 1);
328
+ emitComponentProps(context, dynamicNode, propsVar, attrsVar, indent + 1);
329
+ emit(context, indent + 1, `const ${componentVar} = ${componentTypeVar}.mount(${fragmentVar}, ${propsVar});`);
330
+ if (context.scopeAttr) {
331
+ emit(context, indent + 1, `if (${componentVar}.element?.nodeType === 1) {`);
332
+ emit(context, indent + 2, `${componentVar}.element.setAttribute(${quote(context.scopeAttr)}, "");`);
333
+ emit(context, indent + 1, "}");
334
+ }
335
+ emit(context, indent + 1, `if (${componentTypeVar}.inheritAttrs !== false) {`);
336
+ emitComponentFallthrough(context, dynamicNode, componentVar, branchCleanupVar, indent + 2);
337
+ emit(context, indent + 1, "}");
338
+ emitComponentShow(context, dynamicNode, componentVar, branchCleanupVar, indent + 1);
339
+ emit(context, indent + 1, `${branchCleanupVar}.push(() => ${componentVar}.unmount());`);
340
+ emitTemplateRef(context, dynamicNode, componentVar, branchCleanupVar, indent + 1);
341
+ appendNode(context, parentVar, fragmentVar, indent + 1, endVar);
342
+ if (transitionVar) {
343
+ emitTransitionRegistration(context, `${componentVar}.element`, transitionVar, indent + 1);
344
+ }
345
+ emit(context, indent + 1, `${currentTypeVar} = ${componentTypeVar};`);
346
+ emit(context, indent + 1, `${currentInstanceVar} = ${componentVar};`);
347
+ emit(context, indent, "});");
348
+ emit(context, indent, `${cleanupVar}.push(() => {`);
349
+ emit(context, indent + 1, `${stopVar}();`);
350
+ emit(context, indent + 1, `__mikuru_runCleanup(${branchCleanupVar});`);
351
+ emit(context, indent, "});");
352
+ return startVar;
353
+ }
354
+ function emitComponentShow(context, node, componentVar, cleanupVar, indent) {
355
+ const showAttr = node.attrs.find((attr) => attr.name === "v-show");
356
+ if (!showAttr) {
357
+ return;
358
+ }
359
+ const expression = compileTemplateExpression(requireAttrValue(showAttr), showAttr.name, toExpressionContext(context, showAttr.valueLoc));
360
+ const elementVar = nextVar(context, "componentEl");
361
+ const baseDisplayVar = nextVar(context, "baseDisplay");
362
+ const stopVar = nextVar(context, "stop");
363
+ emit(context, indent, `const ${elementVar} = ${componentVar}.element;`);
364
+ emit(context, indent, `if (${elementVar}?.nodeType === 1) {`);
365
+ emit(context, indent + 1, `const ${baseDisplayVar} = ${elementVar}.style.display;`);
366
+ emit(context, indent + 1, `const ${stopVar} = effect(() => {`);
367
+ emit(context, indent + 2, `${elementVar}.style.display = unwrap(${expression}) ? ${baseDisplayVar} : "none";`);
368
+ emit(context, indent + 1, "});");
369
+ emit(context, indent + 1, `${cleanupVar}.push(${stopVar});`);
370
+ emit(context, indent, "}");
371
+ }
372
+ function emitComponentAttrs(context, node, attrsVar, indent) {
373
+ const objectBindExpressions = node.attrs
374
+ .filter((attr) => isObjectBindAttr(attr))
375
+ .map((attr) => compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc)));
376
+ const classParts = componentFallthroughExpressions(context, node, "class", objectBindExpressions);
377
+ const styleParts = componentFallthroughExpressions(context, node, "style", objectBindExpressions);
378
+ const directAttrs = componentDirectAttributeFallthroughs(context, node);
379
+ const baseVar = objectBindExpressions.length > 0 ? nextVar(context, "attrsBase") : attrsVar;
380
+ emit(context, indent, `const ${baseVar} = {`);
381
+ if (classParts.length > 0) {
382
+ emit(context, indent + 1, `get class() { return [${classParts.join(", ")}]; },`);
383
+ }
384
+ if (styleParts.length > 0) {
385
+ emit(context, indent + 1, `get style() { return [${styleParts.join(", ")}]; },`);
386
+ }
387
+ for (const attr of directAttrs) {
388
+ if (attr.dynamic) {
389
+ emit(context, indent + 1, `get ${quotePropertyName(attr.name)}() { return unwrap(${attr.expression}); },`);
390
+ }
391
+ else {
392
+ emit(context, indent + 1, `${quotePropertyName(attr.name)}: ${attr.expression},`);
393
+ }
394
+ }
395
+ emit(context, indent, "};");
396
+ if (objectBindExpressions.length > 0) {
397
+ emitComponentAttrsProxy(context, attrsVar, baseVar, objectBindExpressions, indent);
398
+ }
399
+ }
400
+ function emitComponentAttrsProxy(context, attrsVar, baseVar, objectBindExpressions, indent) {
401
+ const keyVar = nextVar(context, "key");
402
+ const sourceVar = nextVar(context, "source");
403
+ const keysVar = nextVar(context, "keys");
404
+ const descriptorVar = nextVar(context, "descriptor");
405
+ emit(context, indent, `const ${attrsVar} = new Proxy(${baseVar}, {`);
406
+ emit(context, indent + 1, `get(target, ${keyVar}) {`);
407
+ emit(context, indent + 2, `if (typeof ${keyVar} === "symbol" || ${keyVar} in target) {`);
408
+ emit(context, indent + 3, `return target[${keyVar}];`);
409
+ emit(context, indent + 2, "}");
410
+ for (const expression of objectBindExpressions) {
411
+ emit(context, indent + 2, `{`);
412
+ emit(context, indent + 3, `const ${sourceVar} = unwrap(${expression}) ?? {};`);
413
+ emit(context, indent + 3, `if (${sourceVar} && typeof ${sourceVar} === "object" && ${keyVar} in ${sourceVar} && (${keyVar} === "class" || ${keyVar} === "style" || ${componentFallthroughAttributeNameExpression(keyVar)})) {`);
414
+ emit(context, indent + 4, `return unwrap(${sourceVar}[${keyVar}]);`);
415
+ emit(context, indent + 3, "}");
416
+ emit(context, indent + 2, `}`);
417
+ }
418
+ emit(context, indent + 2, "return undefined;");
419
+ emit(context, indent + 1, "},");
420
+ emit(context, indent + 1, "ownKeys(target) {");
421
+ emit(context, indent + 2, `const ${keysVar} = new Set(Reflect.ownKeys(target));`);
422
+ for (const expression of objectBindExpressions) {
423
+ emit(context, indent + 2, `{`);
424
+ emit(context, indent + 3, `const ${sourceVar} = unwrap(${expression}) ?? {};`);
425
+ emit(context, indent + 3, `if (${sourceVar} && typeof ${sourceVar} === "object") {`);
426
+ emit(context, indent + 4, `for (const ${keyVar} of Object.keys(${sourceVar})) {`);
427
+ emit(context, indent + 5, `if (${keyVar} === "class" || ${keyVar} === "style" || ${componentFallthroughAttributeNameExpression(keyVar)}) {`);
428
+ emit(context, indent + 6, `${keysVar}.add(${keyVar});`);
429
+ emit(context, indent + 5, "}");
430
+ emit(context, indent + 4, "}");
431
+ emit(context, indent + 3, "}");
432
+ emit(context, indent + 2, `}`);
433
+ }
434
+ emit(context, indent + 2, `return Array.from(${keysVar});`);
435
+ emit(context, indent + 1, "},");
436
+ emit(context, indent + 1, `getOwnPropertyDescriptor(target, ${keyVar}) {`);
437
+ emit(context, indent + 2, `const ${descriptorVar} = Reflect.getOwnPropertyDescriptor(target, ${keyVar});`);
438
+ emit(context, indent + 2, `if (${descriptorVar}) { return ${descriptorVar}; }`);
439
+ emit(context, indent + 2, `if (typeof ${keyVar} === "string" && this.get(target, ${keyVar}) !== undefined) {`);
440
+ emit(context, indent + 3, "return { enumerable: true, configurable: true };");
441
+ emit(context, indent + 2, "}");
442
+ emit(context, indent + 2, "return undefined;");
443
+ emit(context, indent + 1, "}");
444
+ emit(context, indent, "});");
445
+ }
446
+ function emitComponentFallthrough(context, node, componentVar, cleanupVar, indent) {
447
+ const objectBindExpressions = node.attrs
448
+ .filter((attr) => isObjectBindAttr(attr))
449
+ .map((attr) => compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc)));
450
+ const classParts = componentFallthroughExpressions(context, node, "class", objectBindExpressions);
451
+ const styleParts = componentFallthroughExpressions(context, node, "style", objectBindExpressions);
452
+ const directAttrs = componentDirectAttributeFallthroughs(context, node);
453
+ if (classParts.length === 0 && styleParts.length === 0 && directAttrs.length === 0 && objectBindExpressions.length === 0) {
454
+ return;
455
+ }
456
+ const elementVar = nextVar(context, "componentEl");
457
+ emit(context, indent, `const ${elementVar} = ${componentVar}.element;`);
458
+ emit(context, indent, `if (${elementVar}?.nodeType === 1) {`);
459
+ if (classParts.length > 0) {
460
+ emitComponentFallthroughAttribute(context, elementVar, cleanupVar, "class", classParts, indent + 1);
461
+ }
462
+ if (styleParts.length > 0) {
463
+ emitComponentFallthroughAttribute(context, elementVar, cleanupVar, "style", styleParts, indent + 1);
464
+ }
465
+ for (const attr of directAttrs) {
466
+ if (attr.dynamic) {
467
+ const stopVar = nextVar(context, "stop");
468
+ emit(context, indent + 1, `const ${stopVar} = effect(() => {`);
469
+ emit(context, indent + 2, `setAttribute(${elementVar}, ${quote(attr.name)}, unwrap(${attr.expression}));`);
470
+ emit(context, indent + 1, "});");
471
+ emit(context, indent + 1, `${cleanupVar}.push(${stopVar});`);
472
+ }
473
+ else {
474
+ emit(context, indent + 1, `setAttribute(${elementVar}, ${quote(attr.name)}, ${attr.expression});`);
475
+ }
476
+ }
477
+ for (const expression of objectBindExpressions) {
478
+ emitComponentObjectAttributeFallthrough(context, elementVar, cleanupVar, expression, indent + 1);
479
+ }
480
+ emit(context, indent, "}");
481
+ }
482
+ function emitComponentFallthroughAttribute(context, elementVar, cleanupVar, attributeName, parts, indent) {
483
+ const baseVar = nextVar(context, `base${attributeName === "class" ? "Class" : "Style"}`);
484
+ const stopVar = nextVar(context, "stop");
485
+ emit(context, indent, `const ${baseVar} = ${elementVar}.getAttribute(${quote(attributeName)});`);
486
+ emit(context, indent, `const ${stopVar} = effect(() => {`);
487
+ emit(context, indent + 1, `setAttribute(${elementVar}, ${quote(attributeName)}, [${baseVar}, ${parts.join(", ")}]);`);
488
+ emit(context, indent, "});");
489
+ emit(context, indent, `${cleanupVar}.push(${stopVar});`);
490
+ }
491
+ function componentFallthroughExpressions(context, node, attributeName, objectBindExpressions) {
492
+ return [
493
+ ...node.attrs.flatMap((attr) => componentDirectFallthroughExpression(context, attr, attributeName)),
494
+ ...objectBindExpressions.map((expression) => objectBindFallthroughExpression(expression, attributeName))
495
+ ];
496
+ }
497
+ function componentDirectFallthroughExpression(context, attr, attributeName) {
498
+ if (attr.name === attributeName) {
499
+ return [quote(attr.value === true ? "" : attr.value)];
500
+ }
501
+ if (getBindingName(attr.name) === attributeName) {
502
+ return [compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc))];
503
+ }
504
+ return [];
505
+ }
506
+ function objectBindFallthroughExpression(expression, attributeName) {
507
+ const key = quote(attributeName);
508
+ return `(() => { const source = unwrap(${expression}) ?? {}; return source && typeof source === "object" && ${key} in source ? unwrap(source[${key}]) : null; })()`;
509
+ }
510
+ function componentDirectAttributeFallthroughs(context, node) {
511
+ return node.attrs.flatMap((attr) => {
512
+ if (attr.name === "class" || attr.name === "style") {
513
+ return [];
514
+ }
515
+ if (!isDirectiveAttr(attr) && isComponentFallthroughAttributeName(attr.name)) {
516
+ return [
517
+ {
518
+ dynamic: false,
519
+ expression: quote(attr.value === true ? true : attr.value),
520
+ name: attr.name
521
+ }
522
+ ];
523
+ }
524
+ const bindingName = getBindingName(attr.name);
525
+ if (bindingName && bindingName !== "class" && bindingName !== "style" && isComponentFallthroughAttributeName(bindingName)) {
526
+ return [
527
+ {
528
+ dynamic: true,
529
+ expression: compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc)),
530
+ name: bindingName
531
+ }
532
+ ];
533
+ }
534
+ return [];
535
+ });
536
+ }
537
+ function emitComponentObjectAttributeFallthrough(context, elementVar, cleanupVar, expression, indent) {
538
+ const previousVar = nextVar(context, "fallthroughKeys");
539
+ const stopVar = nextVar(context, "stop");
540
+ const sourceVar = nextVar(context, "source");
541
+ const nextVarName = nextVar(context, "nextKeys");
542
+ const keyVar = nextVar(context, "key");
543
+ const valueVar = nextVar(context, "value");
544
+ const staleKeyVar = nextVar(context, "staleKey");
545
+ emit(context, indent, `let ${previousVar} = new Set();`);
546
+ emit(context, indent, `const ${stopVar} = effect(() => {`);
547
+ emit(context, indent + 1, `const ${sourceVar} = unwrap(${expression}) ?? {};`);
548
+ emit(context, indent + 1, `const ${nextVarName} = new Set();`);
549
+ emit(context, indent + 1, `if (${sourceVar} && typeof ${sourceVar} === "object") {`);
550
+ emit(context, indent + 2, `for (const [${keyVar}, ${valueVar}] of Object.entries(${sourceVar})) {`);
551
+ emit(context, indent + 3, `if (${keyVar} === "class" || ${keyVar} === "style" || !${componentFallthroughAttributeNameExpression(keyVar)}) { continue; }`);
552
+ emit(context, indent + 3, `${nextVarName}.add(${keyVar});`);
553
+ emit(context, indent + 3, `setAttribute(${elementVar}, ${keyVar}, unwrap(${valueVar}));`);
554
+ emit(context, indent + 2, "}");
555
+ emit(context, indent + 1, "}");
556
+ emit(context, indent + 1, `for (const ${staleKeyVar} of ${previousVar}) {`);
557
+ emit(context, indent + 2, `if (!${nextVarName}.has(${staleKeyVar})) {`);
558
+ emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, null);`);
559
+ emit(context, indent + 2, "}");
560
+ emit(context, indent + 1, "}");
561
+ emit(context, indent + 1, `${previousVar} = ${nextVarName};`);
562
+ emit(context, indent, "});");
563
+ emit(context, indent, `${cleanupVar}.push(${stopVar});`);
564
+ }
565
+ function isComponentFallthroughAttributeName(name) {
566
+ return (name === "id" ||
567
+ name === "title" ||
568
+ name === "role" ||
569
+ name === "tabindex" ||
570
+ name === "lang" ||
571
+ name === "dir" ||
572
+ name === "hidden" ||
573
+ name.startsWith("aria-") ||
574
+ name.startsWith("data-"));
575
+ }
576
+ function componentFallthroughAttributeNameExpression(keyExpression) {
577
+ return `(${keyExpression} === "id" || ${keyExpression} === "title" || ${keyExpression} === "role" || ${keyExpression} === "tabindex" || ${keyExpression} === "lang" || ${keyExpression} === "dir" || ${keyExpression} === "hidden" || ${keyExpression}.startsWith("aria-") || ${keyExpression}.startsWith("data-"))`;
578
+ }
160
579
  function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar) {
161
580
  const slotDirectiveAttr = node.attrs.find((attr) => isSlotDirectiveAttr(attr));
162
581
  if (slotDirectiveAttr) {
@@ -169,26 +588,53 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
169
588
  emit(context, indent, `${elementVar}.setAttribute(${quote(context.scopeAttr)}, "");`);
170
589
  }
171
590
  for (const attr of node.attrs) {
591
+ if (attr.name === "ref" || getBindingName(attr.name) === "ref") {
592
+ continue;
593
+ }
172
594
  if (isDirectiveAttr(attr)) {
173
595
  continue;
174
596
  }
175
597
  emit(context, indent, `setAttribute(${elementVar}, ${quote(attr.name)}, ${quote(attr.value === true ? "" : attr.value)});`);
176
598
  }
599
+ emitTemplateRef(context, node, elementVar, cleanupVar, indent);
600
+ generateChildren(context, node.children, elementVar, cleanupVar, indent);
177
601
  for (const attr of node.attrs) {
178
- if (attr.name === "v-model") {
602
+ const modelDirective = parseModelDirective(attr.name);
603
+ if (modelDirective) {
604
+ validateModelModifiers(modelDirective, attr, context);
179
605
  const expression = validateAssignableExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
180
606
  const stopVar = nextVar(context, "stop");
181
607
  const handlerVar = nextVar(context, "handler");
182
608
  const inputType = getStaticAttrValue(node, "type")?.toLowerCase();
183
- const modelMode = node.tag === "input" && inputType === "checkbox" ? "checkbox" : node.tag === "select" ? "select" : "text";
184
- const eventName = modelMode === "text" ? "input" : "change";
185
- const propertyName = modelMode === "checkbox" ? "checked" : "value";
186
- const renderedValue = modelMode === "checkbox" ? `Boolean(unwrap(${expression}))` : `String(unwrap(${expression}) ?? "")`;
187
- const assignedValue = modelMode === "checkbox" ? `$event.target.checked` : `$event.target.value`;
609
+ const multiple = node.tag === "select" && hasStaticBooleanAttr(node, "multiple");
610
+ const modelMode = node.tag === "input" && inputType === "checkbox"
611
+ ? "checkbox"
612
+ : node.tag === "input" && inputType === "radio"
613
+ ? "radio"
614
+ : node.tag === "select" && multiple
615
+ ? "select-multiple"
616
+ : node.tag === "select"
617
+ ? "select"
618
+ : "text";
619
+ const eventName = modelMode === "text" && !modelDirective.modifiers.includes("lazy") ? "input" : "change";
620
+ const propertyName = modelMode === "checkbox" || modelMode === "radio" ? "checked" : "value";
621
+ const renderedValue = modelMode === "checkbox"
622
+ ? `Boolean(unwrap(${expression}))`
623
+ : modelMode === "radio"
624
+ ? `(${modelDirective.modifiers.includes("number") ? `Number(${elementVar}.getAttribute("value") ?? "on")` : `(${elementVar}.getAttribute("value") ?? "on")`} === unwrap(${expression}))`
625
+ : modelMode === "select-multiple"
626
+ ? `Array.from(${elementVar}.options).forEach((option) => { option.selected = (unwrap(${expression}) ?? []).map(String).includes(option.getAttribute("value") ?? option.textContent ?? ""); })`
627
+ : `String(unwrap(${expression}) ?? "")`;
628
+ const assignedValue = modelAssignedValue(modelMode, modelDirective.modifiers);
188
629
  emit(context, indent, `const ${stopVar} = effect(() => {`);
189
- emit(context, indent + 1, `if (${elementVar}.${propertyName} !== ${renderedValue}) {`);
190
- emit(context, indent + 2, `${elementVar}.${propertyName} = ${renderedValue};`);
191
- emit(context, indent + 1, "}");
630
+ if (modelMode === "select-multiple") {
631
+ emit(context, indent + 1, renderedValue);
632
+ }
633
+ else {
634
+ emit(context, indent + 1, `if (${elementVar}.${propertyName} !== ${renderedValue}) {`);
635
+ emit(context, indent + 2, `${elementVar}.${propertyName} = ${renderedValue};`);
636
+ emit(context, indent + 1, "}");
637
+ }
192
638
  emit(context, indent, "});");
193
639
  emit(context, indent, `${cleanupVar}.push(${stopVar});`);
194
640
  emit(context, indent, `const ${handlerVar} = ($event) => {`);
@@ -208,7 +654,7 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
208
654
  continue;
209
655
  }
210
656
  if (isObjectBindAttr(attr)) {
211
- emitObjectBind(context, elementVar, attr, cleanupVar, indent);
657
+ emitObjectBind(context, node, elementVar, attr, cleanupVar, indent);
212
658
  continue;
213
659
  }
214
660
  if (isObjectOnAttr(attr)) {
@@ -225,6 +671,9 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
225
671
  const baseHandlerVar = nextVar(context, "handler");
226
672
  emit(context, indent, `const ${baseHandlerVar} = ${handlerExpression};`);
227
673
  emit(context, indent, `const ${handlerVar} = ($event) => {`);
674
+ if (event.modifiers.includes("self")) {
675
+ emit(context, indent + 1, `if ($event.target !== ${elementVar}) { return; }`);
676
+ }
228
677
  if (event.modifiers.includes("prevent")) {
229
678
  emit(context, indent + 1, "$event.preventDefault();");
230
679
  }
@@ -237,12 +686,16 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
237
686
  else {
238
687
  emit(context, indent, `const ${handlerVar} = ${handlerExpression};`);
239
688
  }
240
- emit(context, indent, `${elementVar}.addEventListener(${quote(event.name)}, ${handlerVar});`);
241
- emit(context, indent, `${cleanupVar}.push(() => ${elementVar}.removeEventListener(${quote(event.name)}, ${handlerVar}));`);
689
+ const eventOptions = eventListenerOptions(event);
690
+ emit(context, indent, `${elementVar}.addEventListener(${quote(event.name)}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""});`);
691
+ emit(context, indent, `${cleanupVar}.push(() => ${elementVar}.removeEventListener(${quote(event.name)}, ${handlerVar}${eventOptions ? `, ${eventOptions}` : ""}));`);
242
692
  continue;
243
693
  }
244
694
  const bindingName = getBindingName(attr.name);
245
695
  if (bindingName) {
696
+ if (bindingName === "ref") {
697
+ continue;
698
+ }
246
699
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
247
700
  const stopVar = nextVar(context, "stop");
248
701
  const valueExpression = bindingName === "class" && getStaticAttrValue(node, "class")
@@ -254,10 +707,36 @@ function generateElement(context, node, parentVar, cleanupVar, indent, beforeVar
254
707
  emit(context, indent, `${cleanupVar}.push(${stopVar});`);
255
708
  }
256
709
  }
257
- generateChildren(context, node.children, elementVar, cleanupVar, indent);
258
710
  appendNode(context, parentVar, elementVar, indent, beforeVar);
259
711
  return elementVar;
260
712
  }
713
+ function emitTemplateRef(context, node, valueExpression, cleanupVar, indent) {
714
+ const refAttr = getTemplateRefAttr(node);
715
+ if (!refAttr) {
716
+ return;
717
+ }
718
+ const targetExpression = templateRefTargetExpression(context, refAttr);
719
+ const cleanupRefVar = nextVar(context, "cleanupRef");
720
+ const multiple = context.templateRefMode === "array";
721
+ emit(context, indent, `const ${cleanupRefVar} = __mikuru_setRef(${targetExpression}, ${valueExpression}, ${multiple ? "true" : "false"});`);
722
+ emit(context, indent, `${cleanupVar}.push(${cleanupRefVar});`);
723
+ }
724
+ function getTemplateRefAttr(node) {
725
+ return node.attrs.find((attr) => attr.name === "ref" || getBindingName(attr.name) === "ref");
726
+ }
727
+ function templateRefTargetExpression(context, attr) {
728
+ if (getBindingName(attr.name) === "ref") {
729
+ return compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
730
+ }
731
+ if (attr.value === true || !attr.value.trim()) {
732
+ throwTemplateError("Template ref requires a ref object name, for example ref=\"inputEl\"", context, attr.loc);
733
+ }
734
+ const name = attr.value.trim();
735
+ if (!isIdentifier(name)) {
736
+ throwTemplateError("Template ref must be a simple identifier that points to a ref object", context, attr.valueLoc ?? attr.loc);
737
+ }
738
+ return name;
739
+ }
261
740
  function generateText(context, node, parentVar, cleanupVar, indent, beforeVar) {
262
741
  const textVar = nextVar(context, "text");
263
742
  emit(context, indent, `const ${textVar} = document.createTextNode("");`);
@@ -274,7 +753,7 @@ function generateText(context, node, parentVar, cleanupVar, indent, beforeVar) {
274
753
  appendNode(context, parentVar, textVar, indent, beforeVar);
275
754
  return textVar;
276
755
  }
277
- function emitObjectBind(context, elementVar, attr, cleanupVar, indent) {
756
+ function emitObjectBind(context, node, elementVar, attr, cleanupVar, indent) {
278
757
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
279
758
  const prevKeysVar = nextVar(context, "boundKeys");
280
759
  const stopVar = nextVar(context, "stop");
@@ -283,6 +762,7 @@ function emitObjectBind(context, elementVar, attr, cleanupVar, indent) {
283
762
  const keyVar = nextVar(context, "key");
284
763
  const valueVar = nextVar(context, "value");
285
764
  const staleKeyVar = nextVar(context, "key");
765
+ const staticClass = getStaticAttrValue(node, "class");
286
766
  emit(context, indent, `const ${prevKeysVar} = new Set();`);
287
767
  emit(context, indent, `const ${stopVar} = effect(() => {`);
288
768
  emit(context, indent + 1, `const ${attrsVar} = unwrap(${expression}) ?? {};`);
@@ -290,12 +770,22 @@ function emitObjectBind(context, elementVar, attr, cleanupVar, indent) {
290
770
  emit(context, indent + 1, `if (${attrsVar} && typeof ${attrsVar} === "object") {`);
291
771
  emit(context, indent + 2, `for (const [${keyVar}, ${valueVar}] of Object.entries(${attrsVar})) {`);
292
772
  emit(context, indent + 3, `${nextKeysVar}.add(${keyVar});`);
293
- emit(context, indent + 3, `setAttribute(${elementVar}, ${keyVar}, unwrap(${valueVar}));`);
773
+ if (staticClass) {
774
+ emit(context, indent + 3, `setAttribute(${elementVar}, ${keyVar}, ${keyVar} === "class" ? [${quote(staticClass)}, unwrap(${valueVar})] : unwrap(${valueVar}));`);
775
+ }
776
+ else {
777
+ emit(context, indent + 3, `setAttribute(${elementVar}, ${keyVar}, unwrap(${valueVar}));`);
778
+ }
294
779
  emit(context, indent + 2, "}");
295
780
  emit(context, indent + 1, "}");
296
781
  emit(context, indent + 1, `for (const ${staleKeyVar} of ${prevKeysVar}) {`);
297
782
  emit(context, indent + 2, `if (!${nextKeysVar}.has(${staleKeyVar})) {`);
298
- emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, null);`);
783
+ if (staticClass) {
784
+ emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, ${staleKeyVar} === "class" ? ${quote(staticClass)} : null);`);
785
+ }
786
+ else {
787
+ emit(context, indent + 3, `setAttribute(${elementVar}, ${staleKeyVar}, null);`);
788
+ }
299
789
  emit(context, indent + 2, "}");
300
790
  emit(context, indent + 1, "}");
301
791
  emit(context, indent + 1, `${prevKeysVar}.clear();`);
@@ -390,7 +880,7 @@ function generateChildren(context, children, parentVar, cleanupVar, indent, befo
390
880
  index += 1;
391
881
  }
392
882
  }
393
- function generateIfChain(context, branches, parentVar, cleanupVar, indent, beforeVar) {
883
+ function generateIfChain(context, branches, parentVar, cleanupVar, indent, beforeVar, transitionVar) {
394
884
  const startVar = nextVar(context, "ifStart");
395
885
  const endVar = nextVar(context, "ifEnd");
396
886
  const branchCleanupVar = nextVar(context, "ifCleanup");
@@ -411,7 +901,10 @@ function generateIfChain(context, branches, parentVar, cleanupVar, indent, befor
411
901
  const condition = compileTemplateExpression(branch.condition ?? "", branch.directive, toExpressionContext(context, getStringAttrLocation(branch.node, branch.directive)));
412
902
  emit(context, indent + 1, `${branchIndex === 0 ? "if" : "else if"} (unwrap(${condition})) {`);
413
903
  }
414
- generateNode(context, withoutAttrs(branch.node, ["v-if", "v-else-if", "v-else"]), parentVar, branchCleanupVar, indent + 2, endVar);
904
+ const branchVar = generateNode(context, withoutAttrs(branch.node, ["v-if", "v-else-if", "v-else"]), parentVar, branchCleanupVar, indent + 2, endVar);
905
+ if (transitionVar) {
906
+ emitTransitionRegistration(context, branchVar, transitionVar, indent + 2);
907
+ }
415
908
  emit(context, indent + 1, "}");
416
909
  });
417
910
  emit(context, indent, "});");
@@ -446,12 +939,16 @@ function generateFor(context, node, parentVar, cleanupVar, indent, expression, b
446
939
  emit(context, indent + 1, `for (let ${indexVar} = 0; ${indexVar} < ${sourceVar}.length; ${indexVar} += 1) {`);
447
940
  emit(context, indent + 2, `const ${itemName} = ${sourceVar}[${indexVar}];`);
448
941
  emit(context, indent + 2, `const ${indexName} = ${indexVar};`);
449
- generateNode(context, withoutForAttrs(node), parentVar, branchCleanupVar, indent + 2, endVar);
942
+ withTemplateRefMode(context, "array", () => {
943
+ generateNode(context, withoutForAttrs(node), parentVar, branchCleanupVar, indent + 2, endVar);
944
+ });
450
945
  emit(context, indent + 1, "}");
451
946
  }
452
947
  else {
453
948
  emit(context, indent + 1, `for (const ${itemName} of unwrap(${compileTemplateExpression(sourceExpression, "v-for source", toExpressionContext(context, getStringAttrLocation(node, "v-for")))}) ?? []) {`);
454
- generateNode(context, withoutForAttrs(node), parentVar, branchCleanupVar, indent + 2, endVar);
949
+ withTemplateRefMode(context, "array", () => {
950
+ generateNode(context, withoutForAttrs(node), parentVar, branchCleanupVar, indent + 2, endVar);
951
+ });
455
952
  emit(context, indent + 1, "}");
456
953
  }
457
954
  emit(context, indent, "});");
@@ -506,7 +1003,7 @@ function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName
506
1003
  if (indexName) {
507
1004
  emit(context, indent + 4, `const ${indexName} = ${indexRefVar};`);
508
1005
  }
509
- const elementVar = generateNode(context, withoutForAttrs(node), parentVar, recordCleanupVar, indent + 4, endVar);
1006
+ const elementVar = withTemplateRefMode(context, "array", () => generateNode(context, withoutForAttrs(node), parentVar, recordCleanupVar, indent + 4, endVar));
510
1007
  emit(context, indent + 4, `${recordVar} = { element: ${elementVar}, cleanups: ${recordCleanupVar}, item: ${itemRefVar}${indexName ? `, index: ${indexRefVar}` : ""} };`);
511
1008
  emit(context, indent + 3, `}`);
512
1009
  emit(context, indent + 2, `} else {`);
@@ -521,7 +1018,7 @@ function generateKeyedFor(context, node, parentVar, cleanupVar, indent, itemName
521
1018
  emit(context, indent + 1, `for (const [${keyVar}, ${recordVar}] of ${recordsVar}) {`);
522
1019
  emit(context, indent + 2, `if (!${nextRecordsVar}.has(${keyVar})) {`);
523
1020
  emit(context, indent + 3, `__mikuru_runCleanup(${recordVar}.cleanups);`);
524
- emit(context, indent + 3, `${recordVar}.element.remove();`);
1021
+ emit(context, indent + 3, `__mikuru_removeNode(${recordVar}.element);`);
525
1022
  emit(context, indent + 2, `}`);
526
1023
  emit(context, indent + 1, `}`);
527
1024
  emit(context, indent + 1, `${recordsVar}.clear();`);
@@ -544,10 +1041,91 @@ function emitRemoveBetween(context, indent, startVar, endVar) {
544
1041
  emit(context, indent, `let ${currentVar} = ${startVar}.nextSibling;`);
545
1042
  emit(context, indent, `while (${currentVar} && ${currentVar} !== ${endVar}) {`);
546
1043
  emit(context, indent + 1, `const ${nextVarName} = ${currentVar}.nextSibling;`);
547
- emit(context, indent + 1, `${currentVar}.remove();`);
1044
+ emit(context, indent + 1, `__mikuru_removeNode(${currentVar});`);
548
1045
  emit(context, indent + 1, `${currentVar} = ${nextVarName};`);
549
1046
  emit(context, indent, "}");
550
1047
  }
1048
+ function emitTransitionRegistration(context, nodeVar, transitionVar, indent) {
1049
+ emit(context, indent, `if (${nodeVar}?.nodeType === 1) {`);
1050
+ emit(context, indent + 1, `${nodeVar}.__mikuru_transition = ${transitionVar};`);
1051
+ emit(context, indent + 1, `${nodeVar}.__mikuru_transitionLeaving = false;`);
1052
+ emit(context, indent + 1, `__mikuru_applyTransitionEnter(${nodeVar}, ${transitionVar});`);
1053
+ emit(context, indent, "}");
1054
+ }
1055
+ function withTemplateRefMode(context, mode, callback) {
1056
+ const previousMode = context.templateRefMode;
1057
+ context.templateRefMode = mode;
1058
+ try {
1059
+ return callback();
1060
+ }
1061
+ finally {
1062
+ context.templateRefMode = previousMode;
1063
+ }
1064
+ }
1065
+ function splitTopLevel(source, delimiter) {
1066
+ const parts = [];
1067
+ let depth = 0;
1068
+ let quoteChar = "";
1069
+ let start = 0;
1070
+ for (let index = 0; index < source.length; index += 1) {
1071
+ const char = source[index];
1072
+ const previous = source[index - 1];
1073
+ if (quoteChar) {
1074
+ if (char === quoteChar && previous !== "\\") {
1075
+ quoteChar = "";
1076
+ }
1077
+ continue;
1078
+ }
1079
+ if (char === "\"" || char === "'" || char === "`") {
1080
+ quoteChar = char;
1081
+ continue;
1082
+ }
1083
+ if (char === "{" || char === "[" || char === "(") {
1084
+ depth += 1;
1085
+ continue;
1086
+ }
1087
+ if (char === "}" || char === "]" || char === ")") {
1088
+ depth -= 1;
1089
+ continue;
1090
+ }
1091
+ if (depth === 0 && char === delimiter) {
1092
+ parts.push(source.slice(start, index));
1093
+ start = index + 1;
1094
+ }
1095
+ }
1096
+ parts.push(source.slice(start));
1097
+ return parts;
1098
+ }
1099
+ function findTopLevelToken(source, token) {
1100
+ let depth = 0;
1101
+ let quoteChar = "";
1102
+ for (let index = 0; index < source.length; index += 1) {
1103
+ const char = source[index];
1104
+ const previous = source[index - 1];
1105
+ if (quoteChar) {
1106
+ if (char === quoteChar && previous !== "\\") {
1107
+ quoteChar = "";
1108
+ }
1109
+ continue;
1110
+ }
1111
+ if (char === "\"" || char === "'" || char === "`") {
1112
+ quoteChar = char;
1113
+ continue;
1114
+ }
1115
+ if (char === "{" || char === "[" || char === "(") {
1116
+ depth += 1;
1117
+ continue;
1118
+ }
1119
+ if (char === "}" || char === "]" || char === ")") {
1120
+ depth -= 1;
1121
+ continue;
1122
+ }
1123
+ if (depth === 0 && char === token) {
1124
+ return index;
1125
+ }
1126
+ }
1127
+ return -1;
1128
+ }
551
1129
  function appendNode(context, parentVar, nodeVar, indent, beforeVar) {
552
1130
  if (beforeVar) {
553
1131
  emit(context, indent, `${parentVar}.insertBefore(${nodeVar}, ${beforeVar});`);
@@ -574,7 +1152,9 @@ function normalizeScript(descriptor) {
574
1152
  const edits = [];
575
1153
  const transformedMacroStarts = new Set();
576
1154
  const emitsDeclarations = [];
1155
+ let inheritAttrs = true;
577
1156
  let usesPropsAlias = false;
1157
+ let usesAttrsAlias = false;
578
1158
  let usesEmitAlias = false;
579
1159
  let ast;
580
1160
  try {
@@ -596,17 +1176,26 @@ function normalizeScript(descriptor) {
596
1176
  continue;
597
1177
  }
598
1178
  if (statement.type !== "VariableDeclaration") {
1179
+ if (isMacroCall(statement.expression, "defineOptions")) {
1180
+ inheritAttrs = parseDefineOptionsDeclaration(statement.expression, descriptor).inheritAttrs;
1181
+ edits.push({ start: statement.start, end: statement.end, replacement: "" });
1182
+ transformedMacroStarts.add(statement.expression?.start ?? statement.start);
1183
+ }
599
1184
  continue;
600
1185
  }
601
- const macroDeclarations = (statement.declarations ?? []).filter((declaration) => isMacroCall(declaration.init, "defineProps") || isMacroCall(declaration.init, "defineEmits"));
1186
+ const macroDeclarations = (statement.declarations ?? []).filter((declaration) => isMacroCall(declaration.init, "defineProps") ||
1187
+ isMacroCall(declaration.init, "defineEmits") ||
1188
+ isMacroCall(declaration.init, "useAttrs"));
602
1189
  if (macroDeclarations.length > 0 && (statement.declarations ?? []).length !== 1) {
603
- throwUnsupportedMacro("defineProps() and defineEmits() cannot share a variable declaration with other bindings", macroDeclarations[0], descriptor);
1190
+ throwUnsupportedMacro("defineProps(), defineEmits(), and useAttrs() cannot share a variable declaration with other bindings", macroDeclarations[0], descriptor);
604
1191
  }
605
1192
  if (macroDeclarations.length > 0 && statement.kind !== "const") {
606
- throwUnsupportedMacro("defineProps() and defineEmits() must use const declarations", macroDeclarations[0], descriptor);
1193
+ throwUnsupportedMacro("defineProps(), defineEmits(), and useAttrs() must use const declarations", macroDeclarations[0], descriptor);
607
1194
  }
608
1195
  for (const declaration of statement.declarations ?? []) {
609
- if (!isMacroCall(declaration.init, "defineProps") && !isMacroCall(declaration.init, "defineEmits")) {
1196
+ if (!isMacroCall(declaration.init, "defineProps") &&
1197
+ !isMacroCall(declaration.init, "defineEmits") &&
1198
+ !isMacroCall(declaration.init, "useAttrs")) {
610
1199
  continue;
611
1200
  }
612
1201
  if (isMacroCall(declaration.init, "defineProps")) {
@@ -616,6 +1205,13 @@ function normalizeScript(descriptor) {
616
1205
  usesPropsAlias = usesPropsAlias || replacement.includes("__mikuru_props");
617
1206
  continue;
618
1207
  }
1208
+ if (isMacroCall(declaration.init, "useAttrs")) {
1209
+ const replacement = transformUseAttrsDeclaration(declaration, descriptor);
1210
+ edits.push({ start: statement.start, end: statement.end, replacement });
1211
+ transformedMacroStarts.add(declaration.init?.start ?? declaration.start);
1212
+ usesAttrsAlias = true;
1213
+ continue;
1214
+ }
619
1215
  const emitDeclaration = transformDefineEmitsDeclaration(declaration, descriptor);
620
1216
  edits.push({ start: statement.start, end: statement.end, replacement: emitDeclaration.replacement });
621
1217
  transformedMacroStarts.add(declaration.init?.start ?? declaration.start);
@@ -633,7 +1229,9 @@ function normalizeScript(descriptor) {
633
1229
  imports,
634
1230
  runtimeImports,
635
1231
  body,
1232
+ inheritAttrs,
636
1233
  usesPropsAlias,
1234
+ usesAttrsAlias,
637
1235
  usesEmitAlias
638
1236
  };
639
1237
  }
@@ -718,6 +1316,14 @@ function transformDefineEmitsDeclaration(declaration, descriptor) {
718
1316
  events: parseDefineEmitsDeclaration(declaration.init, descriptor)
719
1317
  };
720
1318
  }
1319
+ function transformUseAttrsDeclaration(declaration, descriptor) {
1320
+ validateNoMacroArguments(declaration.init, "useAttrs", descriptor);
1321
+ if (declaration.id?.type !== "Identifier" || !isIdentifier(declaration.id.name ?? "")) {
1322
+ throwUnsupportedMacro("useAttrs() only supports identifier bindings", declaration.id ?? declaration, descriptor);
1323
+ }
1324
+ const localName = declaration.id.name ?? "";
1325
+ return `const ${localName} = ${localName === "__mikuru_attrs" ? "props.__mikuru_attrs ?? {}" : "__mikuru_attrs"};`;
1326
+ }
721
1327
  function applyScriptEdits(script, edits) {
722
1328
  const ordered = [...edits].sort((left, right) => left.start - right.start);
723
1329
  let output = "";
@@ -736,6 +1342,34 @@ function applyScriptEdits(script, edits) {
736
1342
  function isMacroCall(node, name) {
737
1343
  return node?.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === name;
738
1344
  }
1345
+ function validateNoMacroArguments(node, name, descriptor) {
1346
+ const args = node?.arguments ?? [];
1347
+ if (args.length > 0) {
1348
+ throwUnsupportedMacro(`${name}() does not accept arguments`, args[0] ?? node ?? undefined, descriptor);
1349
+ }
1350
+ }
1351
+ function parseDefineOptionsDeclaration(node, descriptor) {
1352
+ const args = node?.arguments ?? [];
1353
+ if (args.length !== 1 || args[0]?.type !== "ObjectExpression") {
1354
+ throwUnsupportedMacro("defineOptions() only supports an object declaration argument", args[0] ?? node ?? undefined, descriptor);
1355
+ }
1356
+ let inheritAttrs = true;
1357
+ for (const property of args[0].properties ?? []) {
1358
+ if (property.type !== "Property" || property.computed) {
1359
+ throwUnsupportedMacro("Unsupported defineOptions() declaration entry", property, descriptor);
1360
+ }
1361
+ const keyName = getPropertyKeyName(property.key);
1362
+ if (keyName !== "inheritAttrs") {
1363
+ throwUnsupportedMacro(`Unsupported defineOptions() option "${keyName ?? ""}"`, property.key ?? property, descriptor);
1364
+ }
1365
+ const valueNode = property.value;
1366
+ if (valueNode?.type !== "Literal" || typeof valueNode.value !== "boolean") {
1367
+ throwUnsupportedMacro("defineOptions({ inheritAttrs }) must use a boolean literal", valueNode ?? property, descriptor);
1368
+ }
1369
+ inheritAttrs = valueNode.value;
1370
+ }
1371
+ return { inheritAttrs };
1372
+ }
739
1373
  function validateDefinePropsDeclaration(node, descriptor) {
740
1374
  const args = node?.arguments ?? [];
741
1375
  if (args.length === 0) {
@@ -815,9 +1449,12 @@ function assertNoUnsupportedMacroCalls(node, transformedMacroStarts, descriptor)
815
1449
  walkScriptNode(node, (candidate) => {
816
1450
  if (candidate.type === "CallExpression" &&
817
1451
  candidate.callee?.type === "Identifier" &&
818
- (candidate.callee.name === "defineProps" || candidate.callee.name === "defineEmits") &&
1452
+ (candidate.callee.name === "defineProps" ||
1453
+ candidate.callee.name === "defineEmits" ||
1454
+ candidate.callee.name === "useAttrs" ||
1455
+ candidate.callee.name === "defineOptions") &&
819
1456
  !transformedMacroStarts.has(candidate.start)) {
820
- throwUnsupportedMacro("defineProps() and defineEmits() must be used in top-level const declarations", candidate, descriptor);
1457
+ throwUnsupportedMacro("defineProps(), defineEmits(), useAttrs(), and defineOptions() must be used in supported top-level declarations", candidate, descriptor);
821
1458
  }
822
1459
  });
823
1460
  }
@@ -879,6 +1516,9 @@ function getStaticAttrValue(node, name) {
879
1516
  }
880
1517
  return attr.value;
881
1518
  }
1519
+ function hasStaticBooleanAttr(node, name) {
1520
+ return node.attrs.some((candidate) => candidate.name === name && candidate.value === true);
1521
+ }
882
1522
  function getKeyExpression(node) {
883
1523
  return getStringAttr(node, ":key") ?? getStringAttr(node, "v-bind:key");
884
1524
  }
@@ -925,18 +1565,19 @@ function throwTemplateError(message, context, location) {
925
1565
  }
926
1566
  throw new Error(message);
927
1567
  }
928
- function emitComponentProps(context, node, propsVar, indent) {
1568
+ function emitComponentProps(context, node, propsVar, attrsVar, indent) {
929
1569
  const props = node.attrs
930
1570
  .filter((attr) => !isStructuralAttr(attr))
931
1571
  .flatMap((attr) => componentPropEntries(context, attr));
932
1572
  const objectBindAttrs = node.attrs.filter((attr) => isObjectBindAttr(attr));
933
1573
  const objectOnAttrs = node.attrs.filter((attr) => isObjectOnAttr(attr));
934
1574
  const slots = collectComponentSlots(context, node);
935
- const defaultSlot = slots.find((slot) => slot.name === "default");
1575
+ const defaultSlot = slots.find((slot) => !slot.nameExpression && slot.name === "default");
936
1576
  const needsProxy = objectBindAttrs.length > 0 || objectOnAttrs.length > 0;
937
1577
  const propsTargetVar = needsProxy ? nextVar(context, "propsBase") : propsVar;
938
1578
  emit(context, indent, `const ${propsTargetVar} = {`);
939
1579
  emit(context, indent + 1, "__mikuru_context,");
1580
+ emit(context, indent + 1, `__mikuru_attrs: ${attrsVar},`);
940
1581
  for (const prop of props) {
941
1582
  emit(context, indent + 1, `${prop},`);
942
1583
  }
@@ -946,7 +1587,7 @@ function emitComponentProps(context, node, propsVar, indent) {
946
1587
  if (slots.length > 0) {
947
1588
  emit(context, indent + 1, "slots: {");
948
1589
  for (const slot of slots) {
949
- emitSlotFunction(context, quotePropertyName(slot.name), slot, indent + 2);
1590
+ emitSlotFunction(context, slot.nameExpression ? `[${slot.nameExpression}]` : quotePropertyName(slot.name), slot, indent + 2);
950
1591
  }
951
1592
  emit(context, indent + 1, "},");
952
1593
  }
@@ -1010,9 +1651,24 @@ function emitSlotScopeBindings(context, slot, slotPropsVar, indent) {
1010
1651
  emit(context, indent, `const ${binding.alias} = ${slotPropsVar};`);
1011
1652
  continue;
1012
1653
  }
1013
- emit(context, indent, `const ${binding.alias} = { get value() { return ${slotPropsVar}.${binding.source}; } };`);
1654
+ if (binding.kind === "rest") {
1655
+ emit(context, indent, `const ${binding.alias} = { get value() { const rest = { ...${slotPropsVar} }; ${binding.exclude.map((key) => `delete rest[${quote(key)}];`).join(" ")} return rest; } };`);
1656
+ continue;
1657
+ }
1658
+ const valueExpression = slotScopePathExpression(slotPropsVar, binding.path);
1659
+ if (binding.defaultValue) {
1660
+ emit(context, indent, `const ${binding.alias} = { get value() { const value = ${valueExpression}; return value === undefined ? (${binding.defaultValue}) : value; } };`);
1661
+ continue;
1662
+ }
1663
+ emit(context, indent, `const ${binding.alias} = { get value() { return ${valueExpression}; } };`);
1014
1664
  }
1015
1665
  }
1666
+ function slotScopePathExpression(rootVar, path) {
1667
+ return path.reduce((expression, key, index) => {
1668
+ const property = /^[A-Za-z_$][\w$]*$/.test(key) ? `.${key}` : `[${quote(key)}]`;
1669
+ return `${expression}${index === 0 ? property : `?.${property.slice(1)}`}`;
1670
+ }, rootVar);
1671
+ }
1016
1672
  function collectComponentSlots(context, node) {
1017
1673
  const slots = [];
1018
1674
  const usedNames = new Set();
@@ -1027,6 +1683,7 @@ function collectComponentSlots(context, node) {
1027
1683
  usedNames.add(slotDirective.name);
1028
1684
  slots.push({
1029
1685
  name: slotDirective.name,
1686
+ nameExpression: slotDirective.nameExpression,
1030
1687
  children: child.children,
1031
1688
  scope: slotDirective.scope,
1032
1689
  loc: child.loc,
@@ -1062,25 +1719,41 @@ function getSlotTemplateDirective(node, context) {
1062
1719
  throwTemplateError("A slot template can only declare one slot target", context, slotAttrs[1]?.loc);
1063
1720
  }
1064
1721
  const attr = slotAttrs[0];
1065
- const name = getSlotTemplateName(attr);
1722
+ const name = getSlotTemplateName(attr, context);
1066
1723
  return {
1067
- name,
1724
+ ...name,
1068
1725
  scope: attr.value,
1069
1726
  loc: attr.loc,
1070
1727
  scopeLoc: attr.valueLoc
1071
1728
  };
1072
1729
  }
1073
- function getSlotTemplateName(attr) {
1730
+ function getSlotTemplateName(attr, context) {
1074
1731
  if (attr.name === "v-slot") {
1075
- return "default";
1732
+ return { name: "default" };
1076
1733
  }
1077
1734
  if (attr.name.startsWith("v-slot:")) {
1078
- return attr.name.slice("v-slot:".length) || "default";
1735
+ return parseSlotTemplateName(attr.name.slice("v-slot:".length) || "default", attr, context);
1079
1736
  }
1080
1737
  if (attr.name.startsWith("#")) {
1081
- return attr.name.slice(1) || "default";
1738
+ return parseSlotTemplateName(attr.name.slice(1) || "default", attr, context);
1739
+ }
1740
+ return { name: "default" };
1741
+ }
1742
+ function parseSlotTemplateName(rawName, attr, context) {
1743
+ const name = rawName.trim();
1744
+ const dynamicStart = name.indexOf("[");
1745
+ const dynamicEnd = name.lastIndexOf("]");
1746
+ if (dynamicStart >= 0 && dynamicEnd > dynamicStart) {
1747
+ const expression = name.slice(dynamicStart + 1, dynamicEnd).trim();
1748
+ if (!expression) {
1749
+ throwTemplateError("Dynamic slot name requires an expression", context, attr.loc);
1750
+ }
1751
+ return {
1752
+ name: `[${expression}]`,
1753
+ nameExpression: compileTemplateExpression(expression, attr.name, toExpressionContext(context, attr.loc))
1754
+ };
1082
1755
  }
1083
- return "default";
1756
+ return { name };
1084
1757
  }
1085
1758
  function parseSlotScopeBindings(scope, context, location) {
1086
1759
  if (scope === true || !scope.trim()) {
@@ -1097,22 +1770,90 @@ function parseSlotScopeBindings(scope, context, location) {
1097
1770
  if (!body) {
1098
1771
  return [];
1099
1772
  }
1100
- return body.split(",").map((part) => {
1773
+ return parseSlotScopeObjectPattern(body, context, location, []);
1774
+ }
1775
+ function parseSlotScopeObjectPattern(body, context, location, pathPrefix) {
1776
+ const bindings = [];
1777
+ const excludedTopLevelKeys = [];
1778
+ for (const part of splitTopLevel(body, ",")) {
1101
1779
  const sourcePart = part.trim();
1102
- const match = /^([A-Za-z_$][\w$]*)(?:\s*:\s*([A-Za-z_$][\w$]*))?$/.exec(sourcePart);
1103
- if (!match) {
1104
- throwTemplateError("Slot scope destructuring only supports identifiers and simple aliases", context, location);
1780
+ if (!sourcePart) {
1781
+ continue;
1782
+ }
1783
+ if (sourcePart.startsWith("...")) {
1784
+ const alias = sourcePart.slice(3).trim();
1785
+ if (pathPrefix.length > 0) {
1786
+ throwTemplateError("Slot scope rest destructuring is only supported at the top level", context, location);
1787
+ }
1788
+ if (!isIdentifier(alias)) {
1789
+ throwTemplateError("Slot scope rest destructuring must use a simple identifier like ...rest", context, location);
1790
+ }
1791
+ bindings.push({ kind: "rest", alias, exclude: excludedTopLevelKeys });
1792
+ continue;
1105
1793
  }
1794
+ const { left, right } = splitSlotScopeEntry(sourcePart, context, location);
1795
+ if (!isIdentifier(left)) {
1796
+ throwTemplateError(`Unsupported slot scope key "${left}". Use identifier keys in slot scope destructuring`, context, location);
1797
+ }
1798
+ if (pathPrefix.length === 0) {
1799
+ excludedTopLevelKeys.push(left);
1800
+ }
1801
+ const path = [...pathPrefix, left];
1802
+ if (right === undefined) {
1803
+ bindings.push(...slotScopeLeafBindings(left, path, context, location));
1804
+ continue;
1805
+ }
1806
+ const value = right.trim();
1807
+ if (value.startsWith("{") && value.endsWith("}")) {
1808
+ bindings.push(...parseSlotScopeObjectPattern(value.slice(1, -1), context, location, path));
1809
+ continue;
1810
+ }
1811
+ if (value.startsWith("[") || value.includes("{")) {
1812
+ throwTemplateError("Slot scope destructuring supports nested object patterns only; array and mixed patterns are not supported", context, location);
1813
+ }
1814
+ bindings.push(...slotScopeLeafBindings(value, path, context, location));
1815
+ }
1816
+ return bindings;
1817
+ }
1818
+ function splitSlotScopeEntry(source, context, location) {
1819
+ const colonIndex = findTopLevelToken(source, ":");
1820
+ if (colonIndex >= 0) {
1106
1821
  return {
1107
- kind: "property",
1108
- source: match[1],
1109
- alias: match[2] ?? match[1]
1822
+ left: source.slice(0, colonIndex).trim(),
1823
+ right: source.slice(colonIndex + 1).trim()
1110
1824
  };
1111
- });
1825
+ }
1826
+ const equalsIndex = findTopLevelToken(source, "=");
1827
+ if (equalsIndex >= 0) {
1828
+ const left = source.slice(0, equalsIndex).trim();
1829
+ if (!isIdentifier(left)) {
1830
+ throwTemplateError("Slot scope default values can only be assigned to simple identifiers", context, location);
1831
+ }
1832
+ return { left, right: source };
1833
+ }
1834
+ return { left: source.trim() };
1835
+ }
1836
+ function slotScopeLeafBindings(source, path, context, location) {
1837
+ const equalsIndex = findTopLevelToken(source, "=");
1838
+ const localSource = equalsIndex >= 0 ? source.slice(0, equalsIndex).trim() : source.trim();
1839
+ const defaultSource = equalsIndex >= 0 ? source.slice(equalsIndex + 1).trim() : undefined;
1840
+ if (!isIdentifier(localSource)) {
1841
+ throwTemplateError(`Unsupported slot scope binding "${source}". Use identifiers, aliases, defaults, nested objects, or top-level ...rest`, context, location);
1842
+ }
1843
+ return [
1844
+ {
1845
+ kind: "property",
1846
+ path,
1847
+ alias: localSource,
1848
+ defaultValue: defaultSource
1849
+ ? compileTemplateExpression(defaultSource, "slot scope default", toExpressionContext(context, location))
1850
+ : undefined
1851
+ }
1852
+ ];
1112
1853
  }
1113
1854
  function emitSlotOutletProps(context, node, propsVar, indent) {
1114
1855
  const entries = node.attrs
1115
- .filter((attr) => attr.name !== "name")
1856
+ .filter((attr) => attr.name !== "name" && getBindingName(attr.name) !== "name")
1116
1857
  .map((attr) => slotOutletPropEntry(context, attr));
1117
1858
  emit(context, indent, `const ${propsVar} = {`);
1118
1859
  for (const entry of entries) {
@@ -1123,9 +1864,6 @@ function emitSlotOutletProps(context, node, propsVar, indent) {
1123
1864
  function slotOutletPropEntry(context, attr) {
1124
1865
  const bindingName = getBindingName(attr.name);
1125
1866
  if (bindingName) {
1126
- if (bindingName === "name") {
1127
- throwTemplateError("Dynamic slot names are not supported", context, attr.loc);
1128
- }
1129
1867
  const expression = compileTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
1130
1868
  return `get ${quotePropertyName(bindingName)}() { return unwrap(${expression}); }`;
1131
1869
  }
@@ -1134,35 +1872,50 @@ function slotOutletPropEntry(context, attr) {
1134
1872
  }
1135
1873
  return `${quotePropertyName(attr.name)}: ${quote(attr.value === true ? true : attr.value)}`;
1136
1874
  }
1137
- function getSlotOutletName(node, context) {
1138
- if (hasAttr(node, ":name") || hasAttr(node, "v-bind:name")) {
1139
- const attr = node.attrs.find((candidate) => candidate.name === ":name" || candidate.name === "v-bind:name");
1140
- throwTemplateError("Dynamic slot names are not supported", context, attr?.loc);
1875
+ function getSlotOutletNameExpression(node, context) {
1876
+ const dynamicNameAttr = node.attrs.find((candidate) => getBindingName(candidate.name) === "name");
1877
+ if (dynamicNameAttr) {
1878
+ return {
1879
+ dynamic: true,
1880
+ expression: compileTemplateExpression(requireAttrValue(dynamicNameAttr), dynamicNameAttr.name, toExpressionContext(context, dynamicNameAttr.valueLoc))
1881
+ };
1141
1882
  }
1142
- return getStaticAttrValue(node, "name") ?? "default";
1883
+ return { dynamic: false, name: getStaticAttrValue(node, "name") ?? "default" };
1143
1884
  }
1144
1885
  function componentPropEntries(context, attr) {
1145
1886
  if (isSlotDirectiveAttr(attr)) {
1146
1887
  throwTemplateError("v-slot must be used on a <template> child in Mikuru", context, attr.loc);
1147
1888
  }
1889
+ if (attr.name === "ref") {
1890
+ return [];
1891
+ }
1892
+ if (getBindingName(attr.name) === "ref") {
1893
+ return [];
1894
+ }
1148
1895
  if (isObjectBindAttr(attr) || isObjectOnAttr(attr)) {
1149
1896
  return [];
1150
1897
  }
1151
- if (attr.name === "v-model") {
1898
+ const modelDirective = parseModelDirective(attr.name);
1899
+ if (modelDirective) {
1900
+ validateModelModifiers(modelDirective, attr, context);
1152
1901
  const expression = validateAssignableExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
1153
1902
  const valueExpression = compileTemplateExpression(expression, attr.name, toExpressionContext(context, attr.valueLoc));
1154
- return [
1903
+ const entries = [
1155
1904
  `get modelValue() { return unwrap(${valueExpression}); }`,
1156
1905
  `onUpdateModelValue: ($value) => { ${expression}.value = $value; }`
1157
1906
  ];
1907
+ if (modelDirective.modifiers.length > 0) {
1908
+ entries.push(`modelModifiers: { ${modelDirective.modifiers.map((modifier) => `${quotePropertyName(modifier)}: true`).join(", ")} }`);
1909
+ }
1910
+ return entries;
1158
1911
  }
1159
1912
  const event = parseEventDirective(attr.name);
1160
1913
  if (event) {
1161
- if (event.modifiers.length) {
1162
- throwTemplateError("Event modifiers are not supported on component events yet", context, attr.loc);
1163
- }
1914
+ validateComponentEventModifiers(event, attr, context);
1164
1915
  const handler = validateTemplateExpression(requireAttrValue(attr), attr.name, toExpressionContext(context, attr.valueLoc));
1165
- return [`${quotePropertyName(toComponentEventProp(event.name))}: ${eventHandlerExpression(handler, context, attr.valueLoc)}`];
1916
+ return [
1917
+ `${quotePropertyName(toComponentEventProp(event.name))}: ${componentEventHandlerExpression(event, eventHandlerExpression(handler, context, attr.valueLoc), context)}`
1918
+ ];
1166
1919
  }
1167
1920
  const bindingName = getBindingName(attr.name);
1168
1921
  if (bindingName) {
@@ -1170,13 +1923,94 @@ function componentPropEntries(context, attr) {
1170
1923
  return [`get ${quotePropertyName(bindingName)}() { return unwrap(${expression}); }`];
1171
1924
  }
1172
1925
  if (attr.name === "v-show") {
1173
- throwTemplateError(`Unsupported component directive ${attr.name}`, context, attr.loc);
1926
+ return [];
1174
1927
  }
1175
1928
  return [`${quotePropertyName(attr.name)}: ${quote(attr.value === true ? true : attr.value)}`];
1176
1929
  }
1177
1930
  function hasMeaningfulTemplateChildren(children) {
1178
1931
  return children.some((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
1179
1932
  }
1933
+ function getTransitionChildren(context, node) {
1934
+ const meaningful = node.children.filter((child) => child.type === "element" || child.parts.some((part) => part.value.trim()));
1935
+ if (meaningful.length === 0 || meaningful.some((child) => child.type !== "element")) {
1936
+ throwTemplateError("<Transition> requires exactly one element/component child or one v-if chain", context, node.loc);
1937
+ }
1938
+ const children = meaningful;
1939
+ const first = children[0];
1940
+ if (children.length === 1) {
1941
+ return children;
1942
+ }
1943
+ if (!getStringAttr(first, "v-if")) {
1944
+ throwTemplateError("<Transition> requires exactly one element/component child or one v-if chain", context, node.loc);
1945
+ }
1946
+ for (const child of children.slice(1)) {
1947
+ if (!getStringAttr(child, "v-else-if") && !hasAttr(child, "v-else")) {
1948
+ throwTemplateError("<Transition> only accepts multiple children when they form a v-if chain", context, child.loc);
1949
+ }
1950
+ }
1951
+ return children;
1952
+ }
1953
+ function getTransitionBranches(context, children) {
1954
+ return children.map((child, index) => {
1955
+ if (index === 0) {
1956
+ return { node: child, condition: getStringAttr(child, "v-if"), directive: "v-if" };
1957
+ }
1958
+ const elseIfExpression = getStringAttr(child, "v-else-if");
1959
+ if (elseIfExpression) {
1960
+ return { node: child, condition: elseIfExpression, directive: "v-else-if" };
1961
+ }
1962
+ validateElseAttribute(child, context);
1963
+ return { node: child, directive: "v-else" };
1964
+ });
1965
+ }
1966
+ function getTransitionOptionsExpression(context, node) {
1967
+ const entries = [`name: String(unwrap(${getTransitionAttrExpression(context, node, "name", "v")}) ?? "v")`];
1968
+ const classAttrs = [
1969
+ ["enter-from-class", "enterFromClass"],
1970
+ ["enter-active-class", "enterActiveClass"],
1971
+ ["enter-to-class", "enterToClass"],
1972
+ ["leave-from-class", "leaveFromClass"],
1973
+ ["leave-active-class", "leaveActiveClass"],
1974
+ ["leave-to-class", "leaveToClass"]
1975
+ ];
1976
+ for (const [attrName, optionName] of classAttrs) {
1977
+ const expression = getTransitionAttrExpression(context, node, attrName);
1978
+ if (expression) {
1979
+ entries.push(`${optionName}: String(unwrap(${expression}) ?? "")`);
1980
+ }
1981
+ }
1982
+ return `{ ${entries.join(", ")} }`;
1983
+ }
1984
+ function getTransitionAttrExpression(context, node, name, fallback) {
1985
+ const dynamicAttr = node.attrs.find((attr) => getBindingName(attr.name) === name);
1986
+ if (dynamicAttr) {
1987
+ return compileTemplateExpression(requireAttrValue(dynamicAttr), dynamicAttr.name, toExpressionContext(context, dynamicAttr.valueLoc));
1988
+ }
1989
+ const staticValue = getStaticAttrValue(node, name);
1990
+ if (staticValue !== undefined) {
1991
+ return quote(staticValue);
1992
+ }
1993
+ return fallback === undefined ? undefined : quote(fallback);
1994
+ }
1995
+ function validateTransitionAttributes(context, node) {
1996
+ const supported = new Set([
1997
+ "name",
1998
+ "appear",
1999
+ "enter-from-class",
2000
+ "enter-active-class",
2001
+ "enter-to-class",
2002
+ "leave-from-class",
2003
+ "leave-active-class",
2004
+ "leave-to-class"
2005
+ ]);
2006
+ for (const attr of node.attrs) {
2007
+ const name = getBindingName(attr.name) ?? attr.name;
2008
+ if (supported.has(name)) {
2009
+ continue;
2010
+ }
2011
+ throwTemplateError(`<Transition> only supports name, appear, and CSS class override attributes in v1`, context, attr.loc);
2012
+ }
2013
+ }
1180
2014
  function toComponentEventProp(eventName) {
1181
2015
  return `on${eventName
1182
2016
  .split(/[-:]/)
@@ -1206,7 +2040,7 @@ function isSupportedDirectiveAttr(attr) {
1206
2040
  attr.name === "v-else" ||
1207
2041
  attr.name === "v-for" ||
1208
2042
  attr.name === "v-show" ||
1209
- attr.name === "v-model" ||
2043
+ Boolean(parseModelDirective(attr.name)) ||
1210
2044
  isObjectBindAttr(attr) ||
1211
2045
  isObjectOnAttr(attr) ||
1212
2046
  Boolean(parseEventDirective(attr.name)) ||
@@ -1229,12 +2063,77 @@ function parseEventDirective(name) {
1229
2063
  modifiers
1230
2064
  };
1231
2065
  }
2066
+ function parseModelDirective(name) {
2067
+ if (name === "v-model") {
2068
+ return { modifiers: [] };
2069
+ }
2070
+ if (!name.startsWith("v-model.")) {
2071
+ return undefined;
2072
+ }
2073
+ return { modifiers: name.slice("v-model.".length).split(".").filter(Boolean) };
2074
+ }
2075
+ function validateModelModifiers(model, attr, context) {
2076
+ const supportedModifiers = new Set(["trim", "number", "lazy"]);
2077
+ for (const modifier of model.modifiers) {
2078
+ if (!supportedModifiers.has(modifier)) {
2079
+ throwTemplateError(`Unsupported v-model modifier .${modifier}`, context, attr.loc);
2080
+ }
2081
+ }
2082
+ }
2083
+ function modelAssignedValue(modelMode, modifiers) {
2084
+ if (modelMode === "checkbox") {
2085
+ return "$event.target.checked";
2086
+ }
2087
+ if (modelMode === "radio") {
2088
+ const valueExpression = `($event.target.getAttribute("value") ?? "on")`;
2089
+ return modifiers.includes("number") ? `Number(${valueExpression})` : valueExpression;
2090
+ }
2091
+ if (modelMode === "select-multiple") {
2092
+ const valueExpression = `Array.from($event.target.options).filter((option) => option.selected).map((option) => option.getAttribute("value") ?? option.textContent ?? "")`;
2093
+ return modifiers.includes("number") ? `${valueExpression}.map((value) => Number(value))` : valueExpression;
2094
+ }
2095
+ let valueExpression = "$event.target.value";
2096
+ if (modifiers.includes("trim")) {
2097
+ valueExpression = `${valueExpression}.trim()`;
2098
+ }
2099
+ if (modifiers.includes("number")) {
2100
+ valueExpression = `Number(${valueExpression})`;
2101
+ }
2102
+ return valueExpression;
2103
+ }
1232
2104
  function validateEventModifiers(event, attr, context) {
2105
+ const supportedModifiers = new Set(["prevent", "stop", "self", "once", "capture", "passive"]);
1233
2106
  for (const modifier of event.modifiers) {
1234
- if (modifier !== "prevent" && modifier !== "stop") {
2107
+ if (!supportedModifiers.has(modifier)) {
1235
2108
  throwTemplateError(`Unsupported event modifier .${modifier}`, context, attr.loc);
1236
2109
  }
1237
2110
  }
2111
+ if (event.modifiers.includes("passive") && event.modifiers.includes("prevent")) {
2112
+ throwTemplateError("Event modifiers .passive and .prevent cannot be combined", context, attr.loc);
2113
+ }
2114
+ }
2115
+ function validateComponentEventModifiers(event, attr, context) {
2116
+ for (const modifier of event.modifiers) {
2117
+ if (modifier !== "once") {
2118
+ throwTemplateError(`Event modifier .${modifier} is only supported on DOM events`, context, attr.loc);
2119
+ }
2120
+ }
2121
+ }
2122
+ function eventListenerOptions(event) {
2123
+ const options = [
2124
+ event.modifiers.includes("capture") ? "capture: true" : undefined,
2125
+ event.modifiers.includes("once") ? "once: true" : undefined,
2126
+ event.modifiers.includes("passive") ? "passive: true" : undefined
2127
+ ].filter(Boolean);
2128
+ return options.length ? `{ ${options.join(", ")} }` : undefined;
2129
+ }
2130
+ function componentEventHandlerExpression(event, handlerExpression, context) {
2131
+ if (!event.modifiers.includes("once")) {
2132
+ return handlerExpression;
2133
+ }
2134
+ const calledVar = nextVar(context, "called");
2135
+ const handlerVar = nextVar(context, "handler");
2136
+ return `(() => { let ${calledVar} = false; const ${handlerVar} = ${handlerExpression}; return (...$args) => { if (${calledVar}) { return; } ${calledVar} = true; return ${handlerVar}(...$args); }; })()`;
1238
2137
  }
1239
2138
  function getEventName(name) {
1240
2139
  if (name.startsWith("@")) {