@tsrx/core 0.1.9 → 0.1.11

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.
package/src/index.js CHANGED
@@ -146,6 +146,7 @@ export {
146
146
  clone_switch_helper_invocation as cloneSwitchHelperInvocation,
147
147
  collect_param_bindings as collectParamBindings,
148
148
  collect_statement_bindings as collectStatementBindings,
149
+ create_hook_safe_helper as createHookSafeHelper,
149
150
  create_host_html_attribute as createHostHtmlAttribute,
150
151
  create_host_html_conflict_error as createHostHtmlConflictError,
151
152
  createJsxTransform,
@@ -236,6 +237,7 @@ export {
236
237
 
237
238
  // Analyze
238
239
  export { analyze_css as analyzeCss } from './analyze/css-analyze.js';
240
+ export { prune_css as pruneCss } from './analyze/prune.js';
239
241
  export {
240
242
  CLASS_COMPONENT_AS_NON_ARROW_PROPERTY_ERROR,
241
243
  COMPONENT_DO_WHILE_STATEMENT_ERROR,
@@ -5,6 +5,8 @@
5
5
  import { walk } from 'zimmerframe';
6
6
  import { print } from 'esrap';
7
7
  import { error } from '../../errors.js';
8
+ import { analyze_css } from '../../analyze/css-analyze.js';
9
+ import { prune_css } from '../../analyze/prune.js';
8
10
  import {
9
11
  ensure_function_metadata,
10
12
  in_jsx_child_context,
@@ -36,7 +38,11 @@ import {
36
38
  import * as b from '../../utils/builders.js';
37
39
  import { apply_lazy_transforms, preallocate_lazy_ids } from '../lazy.js';
38
40
  import { find_first_top_level_await_in_component_body } from '../await.js';
39
- import { prepare_stylesheet_for_render, annotate_component_with_hash } from '../scoping.js';
41
+ import {
42
+ prepare_stylesheet_for_render,
43
+ annotate_component_with_hash,
44
+ is_style_element,
45
+ } from '../scoping.js';
40
46
  import {
41
47
  validate_class_component_declarations,
42
48
  validate_component_loop_break_statement,
@@ -426,12 +432,14 @@ export function createJsxTransform(platform) {
426
432
 
427
433
  const css = as_any.css;
428
434
  if (css) {
435
+ apply_css_definition_metadata(as_any, css);
429
436
  stylesheets.push(css);
430
437
  const hash = css.hash;
431
438
  annotate_component_with_hash(
432
439
  as_any,
433
440
  hash,
434
441
  platform.jsx.rewriteClassAttr ? 'className' : 'class',
442
+ transform_context.typeOnly,
435
443
  );
436
444
  }
437
445
  return next(state);
@@ -609,6 +617,73 @@ export function createJsxTransform(platform) {
609
617
  return transform;
610
618
  }
611
619
 
620
+ /**
621
+ * Attach selector-location metadata used by editor definitions/hover before
622
+ * the shared scoping pass mutates class attributes with the component hash.
623
+ *
624
+ * @param {any} component
625
+ * @param {any} css
626
+ * @returns {void}
627
+ */
628
+ function apply_css_definition_metadata(component, css) {
629
+ analyze_css(css);
630
+
631
+ const metadata = component.metadata || (component.metadata = { path: [] });
632
+ const style_classes = metadata.styleClasses || (metadata.styleClasses = new Map());
633
+ const top_scoped_classes = metadata.topScopedClasses || new Map();
634
+ const elements = collect_css_prunable_elements(component.body || []);
635
+
636
+ for (const element of elements) {
637
+ prune_css(css, element, style_classes, top_scoped_classes);
638
+ }
639
+
640
+ if (top_scoped_classes.size > 0) {
641
+ metadata.topScopedClasses = top_scoped_classes;
642
+ }
643
+ }
644
+
645
+ /**
646
+ * @param {any} value
647
+ * @param {any[]} [elements]
648
+ * @returns {any[]}
649
+ */
650
+ function collect_css_prunable_elements(value, elements = []) {
651
+ if (!value || typeof value !== 'object') {
652
+ return elements;
653
+ }
654
+
655
+ if (Array.isArray(value)) {
656
+ for (const child of value) {
657
+ collect_css_prunable_elements(child, elements);
658
+ }
659
+ return elements;
660
+ }
661
+
662
+ if (
663
+ value.type === 'FunctionDeclaration' ||
664
+ value.type === 'FunctionExpression' ||
665
+ value.type === 'ArrowFunctionExpression' ||
666
+ value.type === 'Component'
667
+ ) {
668
+ return elements;
669
+ }
670
+
671
+ if (value.type === 'Element') {
672
+ if (!is_style_element(value)) {
673
+ elements.push(value);
674
+ }
675
+ }
676
+
677
+ for (const key of Object.keys(value)) {
678
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata' || key === 'css') {
679
+ continue;
680
+ }
681
+ collect_css_prunable_elements(value[key], elements);
682
+ }
683
+
684
+ return elements;
685
+ }
686
+
612
687
  /**
613
688
  * Detect a top-level `"use server"` directive. Used by platforms whose
614
689
  * validation rule requires the directive to enable top-level `await`
@@ -1140,23 +1215,27 @@ function is_hook_callee(callee) {
1140
1215
 
1141
1216
  /**
1142
1217
  * @param {AST.Identifier[]} bindings
1218
+ * @param {Set<string>} [mapped_bindings]
1143
1219
  * @returns {AST.ObjectPattern}
1144
1220
  */
1145
- function create_helper_props_pattern(bindings) {
1221
+ function create_helper_props_pattern(bindings, mapped_bindings = new Set()) {
1146
1222
  return /** @type {any} */ ({
1147
1223
  type: 'ObjectPattern',
1148
- properties: bindings.map((binding) => create_helper_props_property(binding)),
1224
+ properties: bindings.map((binding) =>
1225
+ create_helper_props_property(binding, mapped_bindings.has(binding.name)),
1226
+ ),
1149
1227
  metadata: { path: [] },
1150
1228
  });
1151
1229
  }
1152
1230
 
1153
1231
  /**
1154
1232
  * @param {AST.Identifier} binding
1233
+ * @param {boolean} [map_binding]
1155
1234
  * @returns {AST.Property}
1156
1235
  */
1157
- function create_helper_props_property(binding) {
1158
- const key = create_generated_identifier(binding.name);
1159
- const value = create_generated_identifier(binding.name);
1236
+ function create_helper_props_property(binding, map_binding = false) {
1237
+ const key = map_binding ? clone_identifier(binding) : create_generated_identifier(binding.name);
1238
+ const value = map_binding ? clone_identifier(binding) : create_generated_identifier(binding.name);
1160
1239
 
1161
1240
  return b.prop('init', key, value, false, true);
1162
1241
  }
@@ -3336,14 +3415,16 @@ function get_hook_callee_name(callee) {
3336
3415
  * Used by the switch lift's chained-call build, which allocates ids in
3337
3416
  * source order in a forward pass and then constructs helpers in reverse so
3338
3417
  * each fall-through case can reference the next case's component element.
3418
+ * @param {{ transientBindings?: Set<string> }} [options]
3339
3419
  * @returns {{ setup_statements: any[], component_element: ESTreeJSX.JSXElement }}
3340
3420
  */
3341
- function create_hook_safe_helper(
3421
+ export function create_hook_safe_helper(
3342
3422
  body_nodes,
3343
3423
  key_expression,
3344
3424
  source_node,
3345
3425
  transform_context,
3346
3426
  preallocated_helper_id,
3427
+ options = {},
3347
3428
  ) {
3348
3429
  validate_hook_safe_body_does_not_assign_hook_results_to_outer_bindings(
3349
3430
  body_nodes,
@@ -3361,9 +3442,14 @@ function create_hook_safe_helper(
3361
3442
  body_nodes,
3362
3443
  transform_context.available_bindings,
3363
3444
  );
3445
+ const transient_bindings = options.transientBindings ?? new Set();
3364
3446
  const aliases = use_module_scoped_component
3365
3447
  ? []
3366
- : helper_bindings.map((binding) => create_helper_type_alias_declaration(helper_id, binding));
3448
+ : helper_bindings.map((binding) =>
3449
+ transient_bindings.has(binding.name)
3450
+ ? null
3451
+ : create_helper_type_alias_declaration(helper_id, binding),
3452
+ );
3367
3453
  const props_type =
3368
3454
  helper_bindings.length > 0 && !use_module_scoped_component
3369
3455
  ? create_helper_props_type_literal(helper_bindings, aliases)
@@ -3372,8 +3458,8 @@ function create_hook_safe_helper(
3372
3458
  helper_bindings.length > 0
3373
3459
  ? [
3374
3460
  props_type !== null
3375
- ? create_typed_helper_props_pattern(helper_bindings, props_type)
3376
- : create_helper_props_pattern(helper_bindings),
3461
+ ? create_typed_helper_props_pattern(helper_bindings, props_type, transient_bindings)
3462
+ : create_helper_props_pattern(helper_bindings, transient_bindings),
3377
3463
  ]
3378
3464
  : [];
3379
3465
 
@@ -3415,7 +3501,7 @@ function create_hook_safe_helper(
3415
3501
  if (!transform_context.helper_state) {
3416
3502
  return {
3417
3503
  setup_statements: [
3418
- ...aliases.map((alias) => alias.declaration),
3504
+ ...aliases.flatMap((alias) => (alias ? [alias.declaration] : [])),
3419
3505
  create_helper_declaration(helper_id, helper_fn, source_node, transform_context),
3420
3506
  ],
3421
3507
  component_element,
@@ -3439,7 +3525,7 @@ function create_hook_safe_helper(
3439
3525
 
3440
3526
  return {
3441
3527
  setup_statements: [
3442
- ...aliases.map((alias) => alias.declaration),
3528
+ ...aliases.flatMap((alias) => (alias ? [alias.declaration] : [])),
3443
3529
  create_cached_helper_declaration(
3444
3530
  helper_id,
3445
3531
  cache_id,
@@ -3513,7 +3599,7 @@ function create_helper_type_alias_declaration(helper_id, binding) {
3513
3599
 
3514
3600
  /**
3515
3601
  * @param {AST.Identifier[]} bindings
3516
- * @param {{ id: AST.Identifier }[]} aliases
3602
+ * @param {({ id: AST.Identifier } | null)[]} aliases
3517
3603
  * @returns {any}
3518
3604
  */
3519
3605
  function create_helper_props_type_literal(bindings, aliases) {
@@ -3521,7 +3607,13 @@ function create_helper_props_type_literal(bindings, aliases) {
3521
3607
  bindings.map((binding, i) =>
3522
3608
  b.ts_property_signature(
3523
3609
  create_generated_identifier(binding.name),
3524
- b.ts_type_annotation(b.ts_type_query(clone_identifier(aliases[i].id))),
3610
+ b.ts_type_annotation(
3611
+ aliases[i]
3612
+ ? b.ts_type_query(
3613
+ clone_identifier(/** @type {{ id: AST.Identifier }} */ (aliases[i]).id),
3614
+ )
3615
+ : b.ts_keyword_type('any'),
3616
+ ),
3525
3617
  ),
3526
3618
  ),
3527
3619
  );
@@ -3530,10 +3622,11 @@ function create_helper_props_type_literal(bindings, aliases) {
3530
3622
  /**
3531
3623
  * @param {AST.Identifier[]} bindings
3532
3624
  * @param {any} props_type
3625
+ * @param {Set<string>} [mapped_bindings]
3533
3626
  * @returns {AST.ObjectPattern}
3534
3627
  */
3535
- function create_typed_helper_props_pattern(bindings, props_type) {
3536
- const pattern = create_helper_props_pattern(bindings);
3628
+ function create_typed_helper_props_pattern(bindings, props_type, mapped_bindings = new Set()) {
3629
+ const pattern = create_helper_props_pattern(bindings, mapped_bindings);
3537
3630
  /** @type {any} */ (pattern).typeAnnotation = b.ts_type_annotation(props_type);
3538
3631
  return pattern;
3539
3632
  }
@@ -4427,18 +4520,25 @@ function try_statement_to_jsx_child(node, transform_context) {
4427
4520
  ? to_jsx_expression_container(create_null_literal())
4428
4521
  : statement_body_to_jsx_child(pending_body_nodes, transform_context);
4429
4522
 
4430
- result = create_jsx_element(
4431
- 'Suspense',
4432
- [
4433
- {
4434
- type: 'JSXAttribute',
4435
- name: { type: 'JSXIdentifier', name: 'fallback', metadata: { path: [] } },
4436
- value: fallback_content,
4437
- metadata: { path: [] },
4438
- },
4439
- ],
4440
- [result],
4441
- );
4523
+ result =
4524
+ transform_context.platform.hooks?.createPendingBoundary?.(
4525
+ result,
4526
+ fallback_content,
4527
+ transform_context,
4528
+ node,
4529
+ ) ??
4530
+ create_jsx_element(
4531
+ 'Suspense',
4532
+ [
4533
+ {
4534
+ type: 'JSXAttribute',
4535
+ name: { type: 'JSXIdentifier', name: 'fallback', metadata: { path: [] } },
4536
+ value: fallback_content,
4537
+ metadata: { path: [] },
4538
+ },
4539
+ ],
4540
+ [result],
4541
+ );
4442
4542
  }
4443
4543
 
4444
4544
  // Wrap in <TsrxErrorBoundary> if catch block exists
@@ -4476,19 +4576,42 @@ function try_statement_to_jsx_child(node, transform_context) {
4476
4576
 
4477
4577
  const fallback_fn = b.arrow(
4478
4578
  catch_params,
4479
- b.block(build_render_statements(catch_body_nodes, true, transform_context)),
4579
+ b.block(build_render_statements(catch_body_nodes, true, transform_context), handler.body),
4580
+ false,
4581
+ undefined,
4582
+ handler,
4480
4583
  );
4481
4584
 
4585
+ const fallback_component =
4586
+ transform_context.platform.hooks?.createErrorFallbackComponent?.(
4587
+ catch_body_nodes,
4588
+ catch_params,
4589
+ transform_context,
4590
+ node,
4591
+ ) ?? null;
4592
+
4482
4593
  transform_context.available_bindings = saved_catch_bindings;
4483
4594
 
4484
4595
  const boundary_content =
4485
4596
  transform_context.platform.hooks?.createErrorBoundaryContent?.(
4597
+ result,
4598
+ transform_context,
4599
+ node,
4600
+ ) ?? null;
4601
+
4602
+ const custom_boundary =
4603
+ transform_context.platform.hooks?.createErrorBoundary?.(
4604
+ result,
4486
4605
  try_content,
4606
+ fallback_fn,
4487
4607
  transform_context,
4488
4608
  node,
4609
+ { fallbackComponent: fallback_component },
4489
4610
  ) ?? null;
4490
4611
 
4491
- if (boundary_content && transform_context.inside_element_child) {
4612
+ if (custom_boundary) {
4613
+ result = custom_boundary;
4614
+ } else if (boundary_content && transform_context.inside_element_child) {
4492
4615
  result = to_jsx_expression_container(
4493
4616
  b.call(
4494
4617
  'TsrxErrorBoundary',
@@ -4497,21 +4620,21 @@ function try_statement_to_jsx_child(node, transform_context) {
4497
4620
  );
4498
4621
 
4499
4622
  return result;
4623
+ } else {
4624
+ result = create_jsx_element(
4625
+ 'TsrxErrorBoundary',
4626
+ [
4627
+ b.jsx_attribute(
4628
+ b.jsx_id('fallback'),
4629
+ to_jsx_expression_container(/** @type {any} */ (fallback_fn)),
4630
+ ),
4631
+ ...(boundary_content
4632
+ ? [b.jsx_attribute(b.jsx_id('content'), to_jsx_expression_container(boundary_content))]
4633
+ : []),
4634
+ ],
4635
+ boundary_content ? [] : [result],
4636
+ );
4500
4637
  }
4501
-
4502
- result = create_jsx_element(
4503
- 'TsrxErrorBoundary',
4504
- [
4505
- b.jsx_attribute(
4506
- b.jsx_id('fallback'),
4507
- to_jsx_expression_container(/** @type {any} */ (fallback_fn)),
4508
- ),
4509
- ...(boundary_content
4510
- ? [b.jsx_attribute(b.jsx_id('content'), to_jsx_expression_container(boundary_content))]
4511
- : []),
4512
- ],
4513
- boundary_content ? [] : [result],
4514
- );
4515
4638
  }
4516
4639
 
4517
4640
  // result is a JSXElement, but we need to return a JSXExpressionContainer
@@ -5547,6 +5670,8 @@ export function to_jsx_attribute(attr, transform_context) {
5547
5670
  attr_name.name === 'class'
5548
5671
  ) {
5549
5672
  attr_name = set_loc(b.id('className'), attr.name);
5673
+ attr_name.metadata.source_name = 'class';
5674
+ attr_name.metadata.source_length = 'class'.length;
5550
5675
  }
5551
5676
 
5552
5677
  const name =
@@ -95,9 +95,15 @@ function is_composite_jsx_element(node) {
95
95
  * @param {any} node
96
96
  * @param {string} hash
97
97
  * @param {'class' | 'className'} [jsx_class_attr_name='class']
98
+ * @param {boolean} [preserve_style_elements=false]
98
99
  * @returns {any}
99
100
  */
100
- export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
101
+ export function annotate_with_hash(
102
+ node,
103
+ hash,
104
+ jsx_class_attr_name = 'class',
105
+ preserve_style_elements = false,
106
+ ) {
101
107
  if (!node || typeof node !== 'object') return node;
102
108
  if (
103
109
  node.type === 'Component' ||
@@ -109,13 +115,19 @@ export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
109
115
  }
110
116
 
111
117
  if (node.type === 'Element') {
118
+ if (preserve_style_elements && is_style_element(node)) {
119
+ node.children = [];
120
+ return node;
121
+ }
112
122
  if (!is_style_element(node) && !is_composite_element(node)) {
113
123
  add_hash_class(node, hash);
114
124
  }
115
125
  if (Array.isArray(node.children)) {
116
126
  node.children = node.children
117
- .filter((/** @type {any} */ child) => !is_style_element(child))
118
- .map((/** @type {any} */ child) => annotate_with_hash(child, hash, jsx_class_attr_name));
127
+ .filter((/** @type {any} */ child) => preserve_style_elements || !is_style_element(child))
128
+ .map((/** @type {any} */ child) =>
129
+ annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
130
+ );
119
131
  }
120
132
  return node;
121
133
  }
@@ -126,7 +138,7 @@ export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
126
138
  }
127
139
  if (Array.isArray(node.children)) {
128
140
  node.children = node.children.map((/** @type {any} */ child) =>
129
- annotate_with_hash(child, hash, jsx_class_attr_name),
141
+ annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
130
142
  );
131
143
  }
132
144
  return node;
@@ -140,10 +152,10 @@ export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
140
152
  const value = node[key];
141
153
  if (Array.isArray(value)) {
142
154
  node[key] = value.map((/** @type {any} */ child) =>
143
- annotate_with_hash(child, hash, jsx_class_attr_name),
155
+ annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
144
156
  );
145
157
  } else if (value && typeof value === 'object') {
146
- node[key] = annotate_with_hash(value, hash, jsx_class_attr_name);
158
+ node[key] = annotate_with_hash(value, hash, jsx_class_attr_name, preserve_style_elements);
147
159
  }
148
160
  }
149
161
 
@@ -154,14 +166,22 @@ export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
154
166
  * @param {any} component
155
167
  * @param {string} hash
156
168
  * @param {'class' | 'className'} [jsx_class_attr_name='class']
169
+ * @param {boolean} [preserve_style_elements=false]
157
170
  * @returns {void}
158
171
  */
159
- export function annotate_component_with_hash(component, hash, jsx_class_attr_name = 'class') {
172
+ export function annotate_component_with_hash(
173
+ component,
174
+ hash,
175
+ jsx_class_attr_name = 'class',
176
+ preserve_style_elements = false,
177
+ ) {
160
178
  /** @type {any[]} */
161
179
  const body = component.body;
162
180
  component.body = body
163
- .filter((/** @type {any} */ child) => !is_style_element(child))
164
- .map((/** @type {any} */ child) => annotate_with_hash(child, hash, jsx_class_attr_name));
181
+ .filter((/** @type {any} */ child) => preserve_style_elements || !is_style_element(child))
182
+ .map((/** @type {any} */ child) =>
183
+ annotate_with_hash(child, hash, jsx_class_attr_name, preserve_style_elements),
184
+ );
165
185
  }
166
186
 
167
187
  /**
@@ -198,7 +218,8 @@ export function add_hash_class(element, hash) {
198
218
 
199
219
  if (value.type === 'Literal' && typeof value.value === 'string') {
200
220
  const merged = `${value.value} ${hash}`;
201
- existing.value = { type: 'Literal', value: merged, raw: JSON.stringify(merged) };
221
+ value.value = merged;
222
+ value.raw = JSON.stringify(merged);
202
223
  return;
203
224
  }
204
225
 
@@ -233,7 +254,11 @@ function add_hash_class_to_jsx_element(element, hash, jsx_class_attr_name) {
233
254
  existing.name = {
234
255
  type: 'JSXIdentifier',
235
256
  name: jsx_class_attr_name,
236
- metadata: existing.name.metadata || { path: [] },
257
+ metadata: {
258
+ ...(existing.name.metadata || { path: [] }),
259
+ source_name: existing.name.name,
260
+ source_length: existing.name.name.length,
261
+ },
237
262
  };
238
263
  }
239
264
 
@@ -245,7 +270,8 @@ function add_hash_class_to_jsx_element(element, hash, jsx_class_attr_name) {
245
270
 
246
271
  if (value.type === 'Literal' && typeof value.value === 'string') {
247
272
  const merged = `${value.value} ${hash}`;
248
- existing.value = { type: 'Literal', value: merged, raw: JSON.stringify(merged) };
273
+ value.value = merged;
274
+ value.raw = JSON.stringify(merged);
249
275
  return;
250
276
  }
251
277
 
@@ -162,14 +162,14 @@ function visit_source_ast(ast, src_line_offsets, { regions, css_element_info })
162
162
  start: cssStart,
163
163
  end: cssEnd,
164
164
  content: node.css,
165
- id: get_style_region_id(node.metadata.styleScopeHash, `head-${region_id}`),
165
+ id: get_style_region_id(node.metadata.styleScopeHash, `head-${region_id++}`),
166
166
  });
167
167
  }
168
168
 
169
169
  context.next();
170
170
  },
171
171
  Attribute(node, context) {
172
- const element = context.path?.find((n) => n.type === 'Element');
172
+ const element = context.path?.findLast((n) => n.type === 'Element');
173
173
  if (element?.metadata?.css?.scopedClasses) {
174
174
  // we don't need to check is_element_dom_element(node)
175
175
  // since scopedClasses are added during pruning only to DOM elements
@@ -568,10 +568,11 @@ export function convert_source_map_to_mappings(
568
568
  if (node.loc && node.name) {
569
569
  /** @type {Token} */
570
570
  const token = {
571
- source: node.metadata?.is_capitalized ? node.metadata.source_name : node.name,
571
+ source: node.metadata?.source_name ?? node.name,
572
572
  generated: node.name,
573
573
  loc: node.loc,
574
574
  metadata: {},
575
+ sourceLength: node.metadata?.source_length,
575
576
  };
576
577
  if (node.metadata?.disable_verification) {
577
578
  token.mappingData = { ...mapping_data, verification: false };
@@ -708,16 +709,24 @@ export function convert_source_map_to_mappings(
708
709
  visit(node.value);
709
710
  }
710
711
  } else {
712
+ const is_class_attribute =
713
+ node.name?.type === 'JSXIdentifier' &&
714
+ (node.name.name === 'class' || node.name.name === 'className');
711
715
  const attr =
712
- node.name.name === 'class' && node.value?.type === 'JSXExpressionContainer'
716
+ is_class_attribute && node.value?.type === 'JSXExpressionContainer'
713
717
  ? node.value.expression
714
718
  : node.value;
715
719
 
716
- const css = attr
717
- ? css_element_info.get(`${attr.loc?.start.line}:${attr.loc?.start.column}`)
718
- : null;
720
+ const css =
721
+ is_class_attribute && attr
722
+ ? css_element_info.get(`${attr.loc?.start.line}:${attr.loc?.start.column}`)
723
+ : null;
719
724
 
720
725
  if (attr && css) {
726
+ if (node.name) {
727
+ visit(node.name);
728
+ }
729
+
721
730
  // Extract class names from the attribute value
722
731
  const classes = extract_classes(
723
732
  attr,
@@ -861,7 +870,7 @@ export function convert_source_map_to_mappings(
861
870
  target_node,
862
871
  src_to_gen_map,
863
872
  gen_line_offsets,
864
- mapping_data_verify_only,
873
+ closing ? mapping_data_verify_only : mapping_data_verify_complete,
865
874
  );
866
875
  // The generated code includes a semicolon after the closing or self-closed tag
867
876
  // We're extending the mapping to include the semicolon
@@ -200,11 +200,49 @@ export interface JsxPlatformHooks {
200
200
  * the loop to the downstream Vapor JSX compiler as a typed `VaporFor` component.
201
201
  */
202
202
  renderForOf?: (node: any, loopParams: any[], bodyStatements: any[], ctx: any) => any | null;
203
+ /**
204
+ * Optionally replace the default React-style pending lowering for
205
+ * `try { ... } pending { ... }`. The default emits
206
+ * `<Suspense fallback={fallbackContent}>tryContent</Suspense>`.
207
+ * Vue Vapor uses this to provide `default` and `fallback` slots via
208
+ * `v-slots`.
209
+ */
210
+ createPendingBoundary?: (
211
+ tryContent: any,
212
+ fallbackContent: any,
213
+ ctx: any,
214
+ node: any,
215
+ ) => any | null;
216
+ /**
217
+ * Optionally create a generated component for a catch fallback body while
218
+ * the catch parameters are still in scope. Platforms can use this to reuse
219
+ * one mapped catch-body component from multiple runtime catch sites.
220
+ */
221
+ createErrorFallbackComponent?: (
222
+ catchBodyNodes: any[],
223
+ catchParams: any[],
224
+ ctx: any,
225
+ node: any,
226
+ ) => any | null;
227
+ /**
228
+ * Optionally replace the default `try/catch` boundary wrapper. The hook
229
+ * receives the current render content, the original try-body content before
230
+ * any pending wrapper, and the generated catch fallback function.
231
+ */
232
+ createErrorBoundary?: (
233
+ tryContent: any,
234
+ rawTryContent: any,
235
+ fallbackFn: any,
236
+ ctx: any,
237
+ node: any,
238
+ info?: { fallbackComponent?: any },
239
+ ) => any | null;
203
240
  /**
204
241
  * Optionally move the primary `try { ... }` render content into an explicit
205
242
  * error-boundary prop instead of rendering it as the boundary's JSX children.
206
243
  * Vue Vapor uses this because boundary content must execute lazily from a
207
- * zero-argument function.
244
+ * zero-argument function. If a `pending` block exists, `tryContent` is the
245
+ * already-created pending boundary so catch wrappers still enclose it.
208
246
  */
209
247
  createErrorBoundaryContent?: (tryContent: any, ctx: any, node: any) => any | null;
210
248
  /**