@tsrx/core 0.0.27 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -169,6 +169,8 @@ export function createJsxTransform(platform) {
169
169
  needs_error_boundary: false,
170
170
  needs_suspense: false,
171
171
  needs_merge_refs: false,
172
+ needs_ref_prop: false,
173
+ needs_normalize_spread_props: false,
172
174
  needs_fragment: false,
173
175
  module_scoped_hook_components:
174
176
  options?.moduleScopedHookComponents ?? !!platform.hooks?.moduleScopedHookComponents,
@@ -382,13 +384,31 @@ export function createJsxTransform(platform) {
382
384
 
383
385
  Tsx(node, { next, path }) {
384
386
  const inner = /** @type {any} */ (next() ?? node);
385
- return /** @type {any} */ (tsx_node_to_jsx_expression(inner, in_jsx_child_context(path)));
387
+ const in_jsx_child = in_jsx_child_context(path);
388
+ return /** @type {any} */ (
389
+ wrap_jsx_setup_declarations(tsx_node_to_jsx_expression(inner, in_jsx_child), in_jsx_child)
390
+ );
391
+ },
392
+
393
+ Tsrx(node, { next, path, state }) {
394
+ const inner = /** @type {any} */ (next() ?? node);
395
+ const in_jsx_child = in_jsx_child_context(path);
396
+ return /** @type {any} */ (
397
+ wrap_jsx_setup_declarations(
398
+ tsrx_node_to_jsx_expression(inner, state, in_jsx_child),
399
+ in_jsx_child,
400
+ )
401
+ );
386
402
  },
387
403
 
388
404
  TsxCompat(node, { next, path, state }) {
389
405
  const inner = /** @type {any} */ (next() ?? node);
406
+ const in_jsx_child = in_jsx_child_context(path);
390
407
  return /** @type {any} */ (
391
- tsx_compat_node_to_jsx_expression(inner, state, in_jsx_child_context(path))
408
+ wrap_jsx_setup_declarations(
409
+ tsx_compat_node_to_jsx_expression(inner, state, in_jsx_child),
410
+ in_jsx_child,
411
+ )
392
412
  );
393
413
  },
394
414
 
@@ -432,6 +452,27 @@ export function createJsxTransform(platform) {
432
452
  FunctionDeclaration: ensure_function_metadata,
433
453
  FunctionExpression: ensure_function_metadata,
434
454
  ArrowFunctionExpression: ensure_function_metadata,
455
+
456
+ RefExpression(node) {
457
+ return create_ref_prop_call(node, transform_context);
458
+ },
459
+
460
+ JSXOpeningElement(node, { next }) {
461
+ const visited = next() || node;
462
+ const is_component = is_component_like_jsx_name(visited.name);
463
+ const attrs = normalize_named_ref_attributes(
464
+ visited.attributes || [],
465
+ !is_component,
466
+ transform_context,
467
+ );
468
+ return {
469
+ ...visited,
470
+ attributes: merge_duplicate_refs(
471
+ normalize_host_ref_spreads(attrs, !is_component, transform_context),
472
+ transform_context,
473
+ ),
474
+ };
475
+ },
435
476
  });
436
477
 
437
478
  const expanded = expand_component_helpers(/** @type {AST.Program} */ (transformed));
@@ -906,6 +947,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
906
947
 
907
948
  if (is_jsx_child(child)) {
908
949
  const jsx = to_jsx_child(child, transform_context);
950
+ statements.push(...extract_jsx_setup_declarations(jsx));
909
951
  if (interleaved && is_capturable_jsx_child(jsx)) {
910
952
  const { declaration, reference } = captureJsxChild(jsx, capture_index++);
911
953
  statements.push(declaration);
@@ -1576,9 +1618,7 @@ function create_component_return_statement(
1576
1618
  map_render_node_locations = true,
1577
1619
  ) {
1578
1620
  const cloned = render_nodes.map((node) =>
1579
- map_render_node_locations
1580
- ? clone_expression_node(node)
1581
- : clone_expression_node_without_locations(node),
1621
+ map_render_node_locations ? clone_expression_node(node) : clone_expression_node(node, false),
1582
1622
  );
1583
1623
 
1584
1624
  return set_loc(b.return(build_return_expression(cloned) || create_null_literal()), source_node);
@@ -1645,7 +1685,7 @@ function build_tail_helper(continuation_body, source_node, transform_context) {
1645
1685
  * @returns {any}
1646
1686
  */
1647
1687
  function clone_tail_invocation(tail_helper) {
1648
- return clone_expression_node_without_locations(tail_helper.component_element);
1688
+ return clone_expression_node(tail_helper.component_element, false);
1649
1689
  }
1650
1690
 
1651
1691
  /**
@@ -2553,7 +2593,7 @@ function prepend_render_nodes_to_return_statement(node, render_nodes, inside_nes
2553
2593
  * @returns {any}
2554
2594
  */
2555
2595
  function combine_render_return_argument(render_nodes, return_argument) {
2556
- const combined = render_nodes.map((node) => clone_expression_node_without_locations(node));
2596
+ const combined = render_nodes.map((node) => clone_expression_node(node, false));
2557
2597
 
2558
2598
  if (return_argument != null && !is_null_literal(return_argument)) {
2559
2599
  combined.push(return_argument_to_render_node(return_argument));
@@ -2586,30 +2626,6 @@ function is_null_literal(node) {
2586
2626
  return node?.type === 'Literal' && node.value == null;
2587
2627
  }
2588
2628
 
2589
- /**
2590
- * @param {any} node
2591
- * @returns {any}
2592
- */
2593
- function clone_expression_node_without_locations(node) {
2594
- if (!node || typeof node !== 'object') return node;
2595
- if (Array.isArray(node)) return node.map(clone_expression_node_without_locations);
2596
-
2597
- const clone = { ...node };
2598
- delete clone.loc;
2599
- delete clone.start;
2600
- delete clone.end;
2601
-
2602
- for (const key of Object.keys(clone)) {
2603
- if (key === 'metadata') {
2604
- clone.metadata = clone.metadata ? { ...clone.metadata } : { path: [] };
2605
- continue;
2606
- }
2607
- clone[key] = clone_expression_node_without_locations(clone[key]);
2608
- }
2609
-
2610
- return clone;
2611
- }
2612
-
2613
2629
  const TEMPLATE_FRAGMENT_ERROR =
2614
2630
  'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only within <tsx>...</tsx>.';
2615
2631
 
@@ -3374,6 +3390,8 @@ function to_jsx_child(node, transform_context) {
3374
3390
  // We're inside a JSX child position by construction, so keep a
3375
3391
  // JSXExpressionContainer wrapper for bare `{expr}` children.
3376
3392
  return tsx_node_to_jsx_expression(node, true);
3393
+ case 'Tsrx':
3394
+ return tsrx_node_to_jsx_expression(node, transform_context, true);
3377
3395
  case 'TsxCompat':
3378
3396
  return tsx_compat_node_to_jsx_expression(node, transform_context, true);
3379
3397
  case 'Element':
@@ -3408,6 +3426,59 @@ function to_jsx_child(node, transform_context) {
3408
3426
  }
3409
3427
  }
3410
3428
 
3429
+ /**
3430
+ * Lower a `<tsrx>` node's native TSRX template body to a JSX expression.
3431
+ * Unlike `<tsx>`, children have already been parsed and transformed through
3432
+ * the normal TSRX Element/Text/control-flow visitors.
3433
+ *
3434
+ * @param {any} node
3435
+ * @param {TransformContext} transform_context
3436
+ * @param {boolean} [in_jsx_child]
3437
+ * @returns {any}
3438
+ */
3439
+ function tsrx_node_to_jsx_expression(node, transform_context, in_jsx_child = false) {
3440
+ const children = (node.children || []).filter(
3441
+ (/** @type {any} */ child) =>
3442
+ child &&
3443
+ child.type !== 'EmptyStatement' &&
3444
+ (child.type !== 'JSXText' || child.value.trim() !== ''),
3445
+ );
3446
+
3447
+ /** @type {any} */
3448
+ let expression;
3449
+ if (children.length === 0) {
3450
+ expression = create_null_literal();
3451
+ } else if (
3452
+ children.every(is_inline_element_child) &&
3453
+ !children_contain_return_semantics(children)
3454
+ ) {
3455
+ const saved_inside_element_child = transform_context.inside_element_child;
3456
+ transform_context.inside_element_child = true;
3457
+ try {
3458
+ const render_nodes = children.map((/** @type {any} */ child) =>
3459
+ to_jsx_child(child, transform_context),
3460
+ );
3461
+ expression = build_return_expression(render_nodes) || create_null_literal();
3462
+ } finally {
3463
+ transform_context.inside_element_child = saved_inside_element_child;
3464
+ }
3465
+ } else {
3466
+ expression = statement_body_to_jsx_child(children, transform_context).expression;
3467
+ }
3468
+
3469
+ if (
3470
+ in_jsx_child &&
3471
+ expression.type !== 'JSXElement' &&
3472
+ expression.type !== 'JSXFragment' &&
3473
+ expression.type !== 'JSXText' &&
3474
+ expression.type !== 'JSXExpressionContainer'
3475
+ ) {
3476
+ return to_jsx_expression_container(expression, node);
3477
+ }
3478
+
3479
+ return expression;
3480
+ }
3481
+
3411
3482
  /**
3412
3483
  * @param {any} node
3413
3484
  * @param {TransformContext} transform_context
@@ -4201,30 +4272,79 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
4201
4272
  });
4202
4273
  }
4203
4274
 
4204
- if (transform_context.needs_merge_refs && platform.imports.mergeRefs) {
4205
- const merge_refs_source = platform.imports.mergeRefs;
4275
+ const merge_refs_source =
4276
+ transform_context.needs_merge_refs && platform.imports.mergeRefs
4277
+ ? platform.imports.mergeRefs
4278
+ : null;
4279
+ const ref_prop_source =
4280
+ transform_context.needs_ref_prop && platform.imports.refProp ? platform.imports.refProp : null;
4281
+ const normalize_spread_props_source =
4282
+ transform_context.needs_normalize_spread_props && platform.imports.refProp
4283
+ ? platform.imports.refProp
4284
+ : null;
4285
+
4286
+ /** @type {Map<string, any[]>} */
4287
+ const ref_imports = new Map();
4288
+
4289
+ if (merge_refs_source !== null) {
4290
+ add_ref_import_specifier(ref_imports, merge_refs_source, {
4291
+ type: 'ImportSpecifier',
4292
+ imported: {
4293
+ type: 'Identifier',
4294
+ name: 'mergeRefs',
4295
+ metadata: { path: [] },
4296
+ },
4297
+ local: {
4298
+ type: 'Identifier',
4299
+ name: MERGE_REFS_INTERNAL_NAME,
4300
+ metadata: { path: [] },
4301
+ },
4302
+ metadata: { path: [] },
4303
+ });
4304
+ }
4305
+
4306
+ if (ref_prop_source !== null) {
4307
+ add_ref_import_specifier(ref_imports, ref_prop_source, {
4308
+ type: 'ImportSpecifier',
4309
+ imported: {
4310
+ type: 'Identifier',
4311
+ name: 'create_ref_prop',
4312
+ metadata: { path: [] },
4313
+ },
4314
+ local: {
4315
+ type: 'Identifier',
4316
+ name: CREATE_REF_PROP_INTERNAL_NAME,
4317
+ metadata: { path: [] },
4318
+ },
4319
+ metadata: { path: [] },
4320
+ });
4321
+ }
4322
+
4323
+ if (normalize_spread_props_source !== null) {
4324
+ add_ref_import_specifier(ref_imports, normalize_spread_props_source, {
4325
+ type: 'ImportSpecifier',
4326
+ imported: {
4327
+ type: 'Identifier',
4328
+ name: 'normalize_spread_props',
4329
+ metadata: { path: [] },
4330
+ },
4331
+ local: {
4332
+ type: 'Identifier',
4333
+ name: NORMALIZE_SPREAD_PROPS_INTERNAL_NAME,
4334
+ metadata: { path: [] },
4335
+ },
4336
+ metadata: { path: [] },
4337
+ });
4338
+ }
4339
+
4340
+ for (const [source, ref_specifiers] of ref_imports) {
4206
4341
  imports.push({
4207
4342
  type: 'ImportDeclaration',
4208
- specifiers: [
4209
- {
4210
- type: 'ImportSpecifier',
4211
- imported: {
4212
- type: 'Identifier',
4213
- name: 'mergeRefs',
4214
- metadata: { path: [] },
4215
- },
4216
- local: {
4217
- type: 'Identifier',
4218
- name: MERGE_REFS_LOCAL_NAME,
4219
- metadata: { path: [] },
4220
- },
4221
- metadata: { path: [] },
4222
- },
4223
- ],
4343
+ specifiers: ref_specifiers,
4224
4344
  source: {
4225
4345
  type: 'Literal',
4226
- value: merge_refs_source,
4227
- raw: `'${merge_refs_source}'`,
4346
+ value: source,
4347
+ raw: `'${source}'`,
4228
4348
  },
4229
4349
  metadata: { path: [] },
4230
4350
  });
@@ -4235,6 +4355,20 @@ function inject_try_imports(program, transform_context, platform, suspense_sourc
4235
4355
  }
4236
4356
  }
4237
4357
 
4358
+ /**
4359
+ * @param {Map<string, any[]>} imports
4360
+ * @param {string} source
4361
+ * @param {any} specifier
4362
+ */
4363
+ function add_ref_import_specifier(imports, source, specifier) {
4364
+ const specifiers = imports.get(source);
4365
+ if (specifiers) {
4366
+ specifiers.push(specifier);
4367
+ } else {
4368
+ imports.set(source, [specifier]);
4369
+ }
4370
+ }
4371
+
4238
4372
  /**
4239
4373
  * @param {any} node
4240
4374
  * @param {TransformContext} transform_context
@@ -4421,6 +4555,8 @@ function to_jsx_expression_container(expression, source_node = expression) {
4421
4555
  */
4422
4556
  function transform_element_attributes_dispatch(attrs, transform_context, element) {
4423
4557
  validate_at_most_one_ref_attribute(attrs, transform_context);
4558
+ const is_component = is_component_like_element(element);
4559
+ attrs = normalize_named_ref_attributes(attrs, !is_component, transform_context);
4424
4560
  const preprocess = transform_context.platform.hooks?.preprocessElementAttributes;
4425
4561
  if (preprocess) {
4426
4562
  attrs = preprocess(attrs, transform_context, element);
@@ -4429,7 +4565,238 @@ function transform_element_attributes_dispatch(attrs, transform_context, element
4429
4565
  const result = hook
4430
4566
  ? hook(attrs, transform_context, element)
4431
4567
  : attrs.map((/** @type {any} */ a) => to_jsx_attribute(a, transform_context));
4432
- return merge_duplicate_refs(result, transform_context);
4568
+ return merge_duplicate_refs(
4569
+ normalize_host_ref_spreads(result, !is_component, transform_context),
4570
+ transform_context,
4571
+ );
4572
+ }
4573
+
4574
+ /**
4575
+ * @param {any} element
4576
+ * @returns {boolean}
4577
+ */
4578
+ function is_component_like_element(element) {
4579
+ const id = element?.id;
4580
+ if (!id) return false;
4581
+ if (id.type === 'Identifier') return /^[A-Z]/.test(id.name);
4582
+ if (id.type === 'JSXIdentifier') return /^[A-Z]/.test(id.name);
4583
+ if (id.type === 'MemberExpression') return true;
4584
+ if (id.type === 'JSXMemberExpression') return true;
4585
+ return false;
4586
+ }
4587
+
4588
+ /**
4589
+ * @param {any} name
4590
+ * @returns {boolean}
4591
+ */
4592
+ function is_component_like_jsx_name(name) {
4593
+ if (!name) return false;
4594
+ if (name.type === 'JSXIdentifier') return /^[A-Z]/.test(name.name);
4595
+ if (name.type === 'JSXMemberExpression') return true;
4596
+ return false;
4597
+ }
4598
+
4599
+ /**
4600
+ * @param {any[]} attrs
4601
+ * @param {boolean} is_host
4602
+ * @param {TransformContext} transform_context
4603
+ * @returns {any[]}
4604
+ */
4605
+ function normalize_named_ref_attributes(attrs, is_host, transform_context) {
4606
+ if (!is_host) return attrs;
4607
+
4608
+ return attrs.map((attr) => {
4609
+ if (!is_named_ref_attribute(attr)) {
4610
+ return attr;
4611
+ }
4612
+
4613
+ if (transform_context.typeOnly) {
4614
+ return mark_type_only_named_ref_attribute(attr);
4615
+ }
4616
+
4617
+ return {
4618
+ ...attr,
4619
+ metadata: { ...(attr.metadata || {}), from_ref_keyword: true },
4620
+ name:
4621
+ attr.name?.type === 'JSXIdentifier'
4622
+ ? { ...attr.name, name: 'ref' }
4623
+ : { type: 'Identifier', name: 'ref', metadata: { path: [] } },
4624
+ };
4625
+ });
4626
+ }
4627
+
4628
+ /**
4629
+ * @param {any} attr
4630
+ * @returns {any}
4631
+ */
4632
+ function mark_type_only_named_ref_attribute(attr) {
4633
+ return {
4634
+ ...attr,
4635
+ name: attr.name
4636
+ ? {
4637
+ ...attr.name,
4638
+ metadata: { ...(attr.name.metadata || {}), disable_verification: true },
4639
+ }
4640
+ : attr.name,
4641
+ };
4642
+ }
4643
+
4644
+ /**
4645
+ * @param {any[]} attrs
4646
+ * @param {boolean} is_host
4647
+ * @param {TransformContext} transform_context
4648
+ * @returns {any[]}
4649
+ */
4650
+ function normalize_host_ref_spreads(attrs, is_host, transform_context) {
4651
+ if (!is_host) return attrs;
4652
+
4653
+ const needs_explicit_spread_ref =
4654
+ transform_context.platform.jsx?.hostSpreadRefStrategy === 'explicit-ref-attr';
4655
+ const ref_exprs = attrs
4656
+ .filter((attr) => is_jsx_ref_attribute(attr))
4657
+ .map((attr) => attr.value.expression);
4658
+ const needs_synthetic_spread_ref = needs_explicit_spread_ref || ref_exprs.length > 0;
4659
+
4660
+ return attrs.flatMap((attr) => {
4661
+ if (!attr || attr.type !== 'JSXSpreadAttribute') {
4662
+ return [attr];
4663
+ }
4664
+
4665
+ transform_context.needs_normalize_spread_props = true;
4666
+ const normalized = b.call(NORMALIZE_SPREAD_PROPS_INTERNAL_NAME, attr.argument);
4667
+
4668
+ if (needs_synthetic_spread_ref) {
4669
+ const normalized_id = create_generated_identifier(
4670
+ create_spread_props_name(transform_context),
4671
+ );
4672
+ const spread = {
4673
+ ...attr,
4674
+ argument: clone_identifier(normalized_id),
4675
+ };
4676
+ const ref_attr = b.jsx_attribute(
4677
+ b.jsx_id('ref'),
4678
+ to_jsx_expression_container(b.member(clone_identifier(normalized_id), 'ref'), attr),
4679
+ false,
4680
+ attr,
4681
+ );
4682
+ ref_attr.metadata = { ...(ref_attr.metadata || {}) };
4683
+ /** @type {any} */ (ref_attr.metadata).from_ref_keyword = true;
4684
+ add_jsx_setup_declaration(spread, b.let(clone_identifier(normalized_id), normalized));
4685
+
4686
+ return [spread, ref_attr];
4687
+ }
4688
+
4689
+ return [
4690
+ {
4691
+ ...attr,
4692
+ argument: normalized,
4693
+ },
4694
+ ];
4695
+ });
4696
+ }
4697
+
4698
+ /**
4699
+ * @param {TransformContext} transform_context
4700
+ * @returns {string}
4701
+ */
4702
+ function create_spread_props_name(transform_context) {
4703
+ if (transform_context.helper_state) {
4704
+ return create_helper_name(transform_context.helper_state, 'spread_props');
4705
+ }
4706
+
4707
+ transform_context.local_statement_component_index += 1;
4708
+ return `_tsrx_spread_props_${transform_context.local_statement_component_index}`;
4709
+ }
4710
+
4711
+ /**
4712
+ * @param {any} node
4713
+ * @param {any} declaration
4714
+ */
4715
+ export function add_jsx_setup_declaration(node, declaration) {
4716
+ node.metadata ??= { path: [] };
4717
+ (node.metadata.generated_setup_declarations ??= []).push(declaration);
4718
+ }
4719
+
4720
+ /**
4721
+ * @param {any} node
4722
+ * @param {Set<any>} [seen]
4723
+ * @returns {any[]}
4724
+ */
4725
+ export function extract_jsx_setup_declarations(node, seen = new Set()) {
4726
+ if (node == null || typeof node !== 'object' || seen.has(node)) {
4727
+ return [];
4728
+ }
4729
+ seen.add(node);
4730
+
4731
+ const declarations = node.metadata?.generated_setup_declarations ?? [];
4732
+ if (node.metadata?.generated_setup_declarations) {
4733
+ delete node.metadata.generated_setup_declarations;
4734
+ }
4735
+
4736
+ for (const key of Object.keys(node)) {
4737
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
4738
+ continue;
4739
+ }
4740
+ declarations.push(...extract_jsx_setup_declarations(node[key], seen));
4741
+ }
4742
+
4743
+ return declarations;
4744
+ }
4745
+
4746
+ /**
4747
+ * @param {any} expression
4748
+ * @param {boolean} in_jsx_child
4749
+ * @returns {any}
4750
+ */
4751
+ function wrap_jsx_setup_declarations(expression, in_jsx_child) {
4752
+ const declarations = extract_jsx_setup_declarations(expression);
4753
+ if (declarations.length === 0) {
4754
+ return expression;
4755
+ }
4756
+
4757
+ const return_expression =
4758
+ expression?.type === 'JSXExpressionContainer' ? expression.expression : expression;
4759
+ const call = b.call(
4760
+ b.arrow(
4761
+ [],
4762
+ b.block([...declarations, b.return(return_expression)], expression),
4763
+ false,
4764
+ expression,
4765
+ ),
4766
+ );
4767
+
4768
+ return in_jsx_child ? to_jsx_expression_container(call, expression) : call;
4769
+ }
4770
+
4771
+ /**
4772
+ * @param {any} attr
4773
+ * @returns {boolean}
4774
+ */
4775
+ function is_named_ref_attribute(attr) {
4776
+ return !!(
4777
+ attr &&
4778
+ (attr.type === 'Attribute' || attr.type === 'JSXAttribute') &&
4779
+ attr.name &&
4780
+ ((attr.name.type === 'Identifier' && attr.name.name !== 'ref') ||
4781
+ (attr.name.type === 'JSXIdentifier' && attr.name.name !== 'ref')) &&
4782
+ (attr.value?.type === 'RefExpression' ||
4783
+ is_ref_prop_expression(attr.value) ||
4784
+ (attr.value?.type === 'JSXExpressionContainer' &&
4785
+ is_ref_prop_expression(attr.value.expression)))
4786
+ );
4787
+ }
4788
+
4789
+ /**
4790
+ * @param {any} expression
4791
+ * @returns {boolean}
4792
+ */
4793
+ export function is_ref_prop_expression(expression) {
4794
+ return (
4795
+ expression?.type === 'RefExpression' ||
4796
+ (expression?.type === 'CallExpression' &&
4797
+ expression.callee?.type === 'Identifier' &&
4798
+ expression.callee.name === CREATE_REF_PROP_INTERNAL_NAME)
4799
+ );
4433
4800
  }
4434
4801
 
4435
4802
  /**
@@ -4549,7 +4916,7 @@ export function merge_duplicate_refs(jsx_attrs, transform_context) {
4549
4916
  type: 'CallExpression',
4550
4917
  callee: {
4551
4918
  type: 'Identifier',
4552
- name: MERGE_REFS_LOCAL_NAME,
4919
+ name: MERGE_REFS_INTERNAL_NAME,
4553
4920
  metadata: { path: [] },
4554
4921
  },
4555
4922
  arguments: ref_exprs,
@@ -4610,7 +4977,9 @@ function is_jsx_ref_attribute(attr) {
4610
4977
  * double-underscore matches the convention for compiler-generated
4611
4978
  * identifiers and avoids shadowing user-declared `mergeRefs` symbols.
4612
4979
  */
4613
- const MERGE_REFS_LOCAL_NAME = '__mergeRefs';
4980
+ export const MERGE_REFS_INTERNAL_NAME = '__mergeRefs';
4981
+ export const CREATE_REF_PROP_INTERNAL_NAME = '__create_ref_prop';
4982
+ export const NORMALIZE_SPREAD_PROPS_INTERNAL_NAME = '__normalize_spread_props';
4614
4983
 
4615
4984
  /**
4616
4985
  * @param {any} attr
@@ -4619,7 +4988,31 @@ const MERGE_REFS_LOCAL_NAME = '__mergeRefs';
4619
4988
  */
4620
4989
  export function to_jsx_attribute(attr, transform_context) {
4621
4990
  if (!attr) return attr;
4622
- if (attr.type === 'JSXAttribute' || attr.type === 'JSXSpreadAttribute') {
4991
+ if (attr.type === 'JSXAttribute') {
4992
+ if (
4993
+ attr.value?.type === 'JSXExpressionContainer' &&
4994
+ attr.value.expression?.type === 'RefExpression'
4995
+ ) {
4996
+ return {
4997
+ ...attr,
4998
+ value: to_jsx_expression_container(
4999
+ create_ref_prop_call(attr.value.expression, transform_context),
5000
+ ),
5001
+ metadata: { ...(attr.metadata || {}), from_ref_keyword: true },
5002
+ };
5003
+ }
5004
+ if (
5005
+ attr.value?.type === 'JSXExpressionContainer' &&
5006
+ is_ref_prop_expression(attr.value.expression)
5007
+ ) {
5008
+ return {
5009
+ ...attr,
5010
+ metadata: { ...(attr.metadata || {}), from_ref_keyword: true },
5011
+ };
5012
+ }
5013
+ return attr;
5014
+ }
5015
+ if (attr.type === 'JSXSpreadAttribute') {
4623
5016
  return attr;
4624
5017
  }
4625
5018
  if (attr.type === 'SpreadAttribute') {
@@ -4670,15 +5063,28 @@ export function to_jsx_attribute(attr, transform_context) {
4670
5063
  attr_name && attr_name.type === 'Identifier' ? identifier_to_jsx_name(attr_name) : attr_name;
4671
5064
 
4672
5065
  let value = attr.value;
5066
+ const is_ref_expression_value =
5067
+ value?.type === 'RefExpression' ||
5068
+ is_ref_prop_expression(value) ||
5069
+ (value?.type === 'JSXExpressionContainer' && is_ref_prop_expression(value.expression));
4673
5070
  if (value) {
4674
5071
  if (value.type === 'Literal' && typeof value.value === 'string') {
4675
5072
  // Keep string literal as attribute string.
5073
+ } else if (value.type === 'RefExpression') {
5074
+ value = to_jsx_expression_container(create_ref_prop_call(value, transform_context));
4676
5075
  } else if (value.type !== 'JSXExpressionContainer') {
4677
5076
  value = to_jsx_expression_container(value);
5077
+ } else if (value.expression?.type === 'RefExpression') {
5078
+ value = to_jsx_expression_container(
5079
+ create_ref_prop_call(value.expression, transform_context),
5080
+ );
4678
5081
  }
4679
5082
  }
4680
5083
 
4681
5084
  const jsx_attribute = build_jsx_attribute(name, value || null, attr.shorthand === true);
5085
+ if (is_ref_expression_value) {
5086
+ /** @type {any} */ (jsx_attribute.metadata).from_ref_keyword = true;
5087
+ }
4682
5088
 
4683
5089
  if (value_has_unmappable_jsx_loc(value)) {
4684
5090
  /** @type {any} */ (jsx_attribute.metadata).has_unmappable_value = true;
@@ -4700,6 +5106,35 @@ function value_has_unmappable_jsx_loc(value) {
4700
5106
  );
4701
5107
  }
4702
5108
 
5109
+ /**
5110
+ * @param {any} node
5111
+ * @param {TransformContext} transform_context
5112
+ * @returns {any}
5113
+ */
5114
+ function create_ref_prop_call(node, transform_context) {
5115
+ transform_context.needs_ref_prop = true;
5116
+
5117
+ const argument = node.argument;
5118
+ const args = [b.thunk(argument)];
5119
+
5120
+ if (argument.type === 'Identifier' || argument.type === 'MemberExpression') {
5121
+ args.push(
5122
+ b.arrow(
5123
+ [b.id('v')],
5124
+ /** @type {any} */ ({
5125
+ type: 'AssignmentExpression',
5126
+ operator: '=',
5127
+ left: clone_expression_node(argument, false),
5128
+ right: b.id('v'),
5129
+ metadata: { path: [] },
5130
+ }),
5131
+ ),
5132
+ );
5133
+ }
5134
+
5135
+ return b.call(CREATE_REF_PROP_INTERNAL_NAME, ...args);
5136
+ }
5137
+
4703
5138
  /**
4704
5139
  * @param {any} node
4705
5140
  * @param {TransformContext} transform_context