svelte 5.39.5 → 5.39.7

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "svelte",
3
3
  "description": "Cybernetically enhanced web apps",
4
4
  "license": "MIT",
5
- "version": "5.39.5",
5
+ "version": "5.39.7",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -147,7 +147,7 @@
147
147
  "@types/aria-query": "^5.0.4",
148
148
  "@types/node": "^20.11.5",
149
149
  "dts-buddy": "^0.5.5",
150
- "esbuild": "^0.21.5",
150
+ "esbuild": "^0.25.10",
151
151
  "rollup": "^4.22.4",
152
152
  "source-map": "^0.7.4",
153
153
  "tinyglobby": "^0.2.12",
@@ -382,7 +382,10 @@ export function check_element(node, context) {
382
382
  }
383
383
 
384
384
  // element-specific checks
385
- const is_labelled = attribute_map.has('aria-label') || attribute_map.has('aria-labelledby');
385
+ const is_labelled =
386
+ attribute_map.has('aria-label') ||
387
+ attribute_map.has('aria-labelledby') ||
388
+ attribute_map.has('title');
386
389
 
387
390
  switch (node.name) {
388
391
  case 'a':
@@ -12,8 +12,19 @@ export function TitleElement(node, context) {
12
12
  /** @type {any} */ (node.fragment.nodes),
13
13
  context
14
14
  );
15
+ const evaluated = context.state.scope.evaluate(value);
15
16
 
16
- const statement = b.stmt(b.assignment('=', b.id('$.document.title'), value));
17
+ const statement = b.stmt(
18
+ b.assignment(
19
+ '=',
20
+ b.id('$.document.title'),
21
+ evaluated.is_known
22
+ ? b.literal(evaluated.value)
23
+ : evaluated.is_defined
24
+ ? value
25
+ : b.logical('??', value, b.literal(''))
26
+ )
27
+ );
17
28
 
18
29
  if (has_state) {
19
30
  context.state.update.push(statement);
@@ -7,11 +7,10 @@ import { is_void } from '../../../../../utils.js';
7
7
  import { dev, locator } from '../../../../state.js';
8
8
  import * as b from '#compiler/builders';
9
9
  import { clean_nodes, determine_namespace_for_children } from '../../utils.js';
10
- import { build_element_attributes, build_spread_object } from './shared/element.js';
10
+ import { build_element_attributes, prepare_element_spread_object } from './shared/element.js';
11
11
  import {
12
12
  process_children,
13
13
  build_template,
14
- build_attribute_value,
15
14
  create_child_block,
16
15
  PromiseOptimiser
17
16
  } from './shared/utils.js';
@@ -37,9 +36,27 @@ export function RegularElement(node, context) {
37
36
 
38
37
  const optimiser = new PromiseOptimiser();
39
38
 
40
- state.template.push(b.literal(`<${node.name}`));
41
- const body = build_element_attributes(node, { ...context, state }, optimiser.transform);
42
- state.template.push(b.literal(node_is_void ? '/>' : '>')); // add `/>` for XHTML compliance
39
+ // If this element needs special handling (like <select value> / <option>),
40
+ // avoid calling build_element_attributes here to prevent evaluating/awaiting
41
+ // attribute expressions twice. We'll handle attributes in the special branch.
42
+ const is_select_special =
43
+ node.name === 'select' &&
44
+ node.attributes.some(
45
+ (attribute) =>
46
+ ((attribute.type === 'Attribute' || attribute.type === 'BindDirective') &&
47
+ attribute.name === 'value') ||
48
+ attribute.type === 'SpreadAttribute'
49
+ );
50
+ const is_option_special = node.name === 'option';
51
+ const is_special = is_select_special || is_option_special;
52
+
53
+ let body = /** @type {Expression | null} */ (null);
54
+ if (!is_special) {
55
+ // only open the tag in the non-special path
56
+ state.template.push(b.literal(`<${node.name}`));
57
+ body = build_element_attributes(node, { ...context, state }, optimiser.transform);
58
+ state.template.push(b.literal(node_is_void ? '/>' : '>')); // add `/>` for XHTML compliance
59
+ }
43
60
 
44
61
  if ((node.name === 'script' || node.name === 'style') && node.fragment.nodes.length === 1) {
45
62
  state.template.push(
@@ -95,27 +112,7 @@ export function RegularElement(node, context) {
95
112
  );
96
113
  }
97
114
 
98
- if (
99
- node.name === 'select' &&
100
- node.attributes.some(
101
- (attribute) =>
102
- ((attribute.type === 'Attribute' || attribute.type === 'BindDirective') &&
103
- attribute.name === 'value') ||
104
- attribute.type === 'SpreadAttribute'
105
- )
106
- ) {
107
- const attributes = build_spread_object(
108
- node,
109
- node.attributes.filter(
110
- (attribute) =>
111
- attribute.type === 'Attribute' ||
112
- attribute.type === 'BindDirective' ||
113
- attribute.type === 'SpreadAttribute'
114
- ),
115
- context,
116
- optimiser.transform
117
- );
118
-
115
+ if (is_select_special) {
119
116
  const inner_state = { ...state, template: [], init: [] };
120
117
  process_children(trimmed, { ...context, state: inner_state });
121
118
 
@@ -124,7 +121,9 @@ export function RegularElement(node, context) {
124
121
  b.block([...state.init, ...build_template(inner_state.template)])
125
122
  );
126
123
 
127
- const statement = b.stmt(b.call('$$renderer.select', attributes, fn));
124
+ const [attributes, ...rest] = prepare_element_spread_object(node, context, optimiser.transform);
125
+
126
+ const statement = b.stmt(b.call('$$renderer.select', attributes, fn, ...rest));
128
127
 
129
128
  if (optimiser.expressions.length > 0) {
130
129
  context.state.template.push(
@@ -137,19 +136,7 @@ export function RegularElement(node, context) {
137
136
  return;
138
137
  }
139
138
 
140
- if (node.name === 'option') {
141
- const attributes = build_spread_object(
142
- node,
143
- node.attributes.filter(
144
- (attribute) =>
145
- attribute.type === 'Attribute' ||
146
- attribute.type === 'BindDirective' ||
147
- attribute.type === 'SpreadAttribute'
148
- ),
149
- context,
150
- optimiser.transform
151
- );
152
-
139
+ if (is_option_special) {
153
140
  let body;
154
141
 
155
142
  if (node.metadata.synthetic_value_node) {
@@ -167,7 +154,9 @@ export function RegularElement(node, context) {
167
154
  );
168
155
  }
169
156
 
170
- const statement = b.stmt(b.call('$$renderer.option', attributes, body));
157
+ const [attributes, ...rest] = prepare_element_spread_object(node, context, optimiser.transform);
158
+
159
+ const statement = b.stmt(b.call('$$renderer.option', attributes, body, ...rest));
171
160
 
172
161
  if (optimiser.expressions.length > 0) {
173
162
  context.state.template.push(
@@ -242,7 +242,12 @@ export function build_inline_component(node, expression, context) {
242
242
  params.push(pattern);
243
243
  }
244
244
 
245
- const slot_fn = b.arrow(params, b.block(block.body));
245
+ const slot_fn = b.arrow(
246
+ params,
247
+ node.fragment.metadata.has_await
248
+ ? b.block([create_async_block(b.block(block.body))])
249
+ : b.block(block.body)
250
+ );
246
251
 
247
252
  if (slot_name === 'default' && !has_children_prop) {
248
253
  if (
@@ -358,39 +358,108 @@ function build_element_spread_attributes(
358
358
  context,
359
359
  transform
360
360
  ) {
361
+ const args = prepare_element_spread(
362
+ element,
363
+ /** @type {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective>} */ (attributes),
364
+ style_directives,
365
+ class_directives,
366
+ context,
367
+ transform
368
+ );
369
+
370
+ let call = b.call('$.attributes', ...args);
371
+
372
+ context.state.template.push(call);
373
+ }
374
+
375
+ /**
376
+ * Prepare args for $.attributes(...): compute object, css_hash, classes, styles and flags.
377
+ * @param {AST.RegularElement | AST.SvelteElement} element
378
+ * @param {ComponentContext} context
379
+ * @param {(expression: Expression, metadata: ExpressionMetadata) => Expression} transform
380
+ * @returns {[ObjectExpression,Literal | undefined, ObjectExpression | undefined, ObjectExpression | undefined, Literal | undefined]}
381
+ */
382
+ export function prepare_element_spread_object(element, context, transform) {
383
+ /** @type {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective>} */
384
+ const select_attributes = [];
385
+ /** @type {AST.ClassDirective[]} */
386
+ const class_directives = [];
387
+ /** @type {AST.StyleDirective[]} */
388
+ const style_directives = [];
389
+
390
+ for (const attribute of element.attributes) {
391
+ if (
392
+ attribute.type === 'Attribute' ||
393
+ attribute.type === 'BindDirective' ||
394
+ attribute.type === 'SpreadAttribute'
395
+ ) {
396
+ select_attributes.push(attribute);
397
+ } else if (attribute.type === 'ClassDirective') {
398
+ class_directives.push(attribute);
399
+ } else if (attribute.type === 'StyleDirective') {
400
+ style_directives.push(attribute);
401
+ }
402
+ }
403
+
404
+ return prepare_element_spread(
405
+ element,
406
+ select_attributes,
407
+ style_directives,
408
+ class_directives,
409
+ context,
410
+ transform
411
+ );
412
+ }
413
+
414
+ /**
415
+ * Prepare args for $.attributes(...): compute object, css_hash, classes, styles and flags.
416
+ * @param {AST.RegularElement | AST.SvelteElement} element
417
+ * @param {Array<AST.Attribute | AST.SpreadAttribute | AST.BindDirective>} attributes
418
+ * @param {AST.StyleDirective[]} style_directives
419
+ * @param {AST.ClassDirective[]} class_directives
420
+ * @param {ComponentContext} context
421
+ * @param {(expression: Expression, metadata: ExpressionMetadata) => Expression} transform
422
+ * @returns {[ObjectExpression,Literal | undefined, ObjectExpression | undefined, ObjectExpression | undefined, Literal | undefined]}
423
+ */
424
+ export function prepare_element_spread(
425
+ element,
426
+ attributes,
427
+ style_directives,
428
+ class_directives,
429
+ context,
430
+ transform
431
+ ) {
432
+ /** @type {ObjectExpression | undefined} */
361
433
  let classes;
434
+ /** @type {ObjectExpression | undefined} */
362
435
  let styles;
363
436
  let flags = 0;
364
437
 
365
- let has_await = false;
366
-
367
438
  if (class_directives.length) {
368
- const properties = class_directives.map((directive) => {
369
- has_await ||= directive.metadata.expression.has_await;
370
-
371
- return b.init(
439
+ const properties = class_directives.map((directive) =>
440
+ b.init(
372
441
  directive.name,
373
442
  directive.expression.type === 'Identifier' && directive.expression.name === directive.name
374
443
  ? b.id(directive.name)
375
- : /** @type {Expression} */ (context.visit(directive.expression))
376
- );
377
- });
444
+ : transform(
445
+ /** @type {Expression} */ (context.visit(directive.expression)),
446
+ directive.metadata.expression
447
+ )
448
+ )
449
+ );
378
450
 
379
451
  classes = b.object(properties);
380
452
  }
381
453
 
382
454
  if (style_directives.length > 0) {
383
- const properties = style_directives.map((directive) => {
384
- has_await ||= directive.metadata.expression.has_await;
385
-
386
- return b.init(
455
+ const properties = style_directives.map((directive) =>
456
+ b.init(
387
457
  directive.name,
388
458
  directive.value === true
389
459
  ? b.id(directive.name)
390
460
  : build_attribute_value(directive.value, context, transform, true)
391
- );
392
- });
393
-
461
+ )
462
+ );
394
463
  styles = b.object(properties);
395
464
  }
396
465
 
@@ -403,17 +472,12 @@ function build_element_spread_attributes(
403
472
  }
404
473
 
405
474
  const object = build_spread_object(element, attributes, context, transform);
406
-
407
475
  const css_hash =
408
476
  element.metadata.scoped && context.state.analysis.css.hash
409
477
  ? b.literal(context.state.analysis.css.hash)
410
478
  : undefined;
411
479
 
412
- const args = [object, css_hash, classes, styles, flags ? b.literal(flags) : undefined];
413
-
414
- let call = b.call('$.attributes', ...args);
415
-
416
- context.state.template.push(call);
480
+ return [object, css_hash, classes, styles, flags ? b.literal(flags) : undefined];
417
481
  }
418
482
 
419
483
  /**
@@ -174,11 +174,11 @@ export function a11y_click_events_have_key_events(node) {
174
174
  }
175
175
 
176
176
  /**
177
- * Buttons and links should either contain text or have an `aria-label` or `aria-labelledby` attribute
177
+ * Buttons and links should either contain text or have an `aria-label`, `aria-labelledby` or `title` attribute
178
178
  * @param {null | NodeLike} node
179
179
  */
180
180
  export function a11y_consider_explicit_label(node) {
181
- w(node, 'a11y_consider_explicit_label', `Buttons and links should either contain text or have an \`aria-label\` or \`aria-labelledby\` attribute\nhttps://svelte.dev/e/a11y_consider_explicit_label`);
181
+ w(node, 'a11y_consider_explicit_label', `Buttons and links should either contain text or have an \`aria-label\`, \`aria-labelledby\` or \`title\` attribute\nhttps://svelte.dev/e/a11y_consider_explicit_label`);
182
182
  }
183
183
 
184
184
  /**
@@ -22,7 +22,7 @@ import {
22
22
  set_dev_current_component_function,
23
23
  set_dev_stack
24
24
  } from '../../context.js';
25
- import { flushSync } from '../../reactivity/batch.js';
25
+ import { flushSync, is_flushing_sync } from '../../reactivity/batch.js';
26
26
 
27
27
  const PENDING = 0;
28
28
  const THEN = 1;
@@ -126,7 +126,7 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {
126
126
 
127
127
  // without this, the DOM does not update until two ticks after the promise
128
128
  // resolves, which is unexpected behaviour (and somewhat irksome to test)
129
- flushSync();
129
+ if (!is_flushing_sync) flushSync();
130
130
  }
131
131
  }
132
132
  }
@@ -6,7 +6,7 @@ import { create_event, delegate } from './events.js';
6
6
  import { add_form_reset_listener, autofocus } from './misc.js';
7
7
  import * as w from '../../warnings.js';
8
8
  import { LOADING_ATTR_SYMBOL } from '#client/constants';
9
- import { queue_idle_task } from '../task.js';
9
+ import { queue_micro_task } from '../task.js';
10
10
  import { is_capture_event, is_delegated, normalize_attribute } from '../../../../utils.js';
11
11
  import {
12
12
  active_effect,
@@ -65,7 +65,7 @@ export function remove_input_defaults(input) {
65
65
 
66
66
  // @ts-expect-error
67
67
  input.__on_r = remove_defaults;
68
- queue_idle_task(remove_defaults);
68
+ queue_micro_task(remove_defaults);
69
69
  add_form_reset_listener();
70
70
  }
71
71
 
@@ -1,34 +1,15 @@
1
1
  import { run_all } from '../../shared/utils.js';
2
2
  import { is_flushing_sync } from '../reactivity/batch.js';
3
3
 
4
- // Fallback for when requestIdleCallback is not available
5
- const request_idle_callback =
6
- typeof requestIdleCallback === 'undefined'
7
- ? (/** @type {() => void} */ cb) => setTimeout(cb, 1)
8
- : requestIdleCallback;
9
-
10
4
  /** @type {Array<() => void>} */
11
5
  let micro_tasks = [];
12
6
 
13
- /** @type {Array<() => void>} */
14
- let idle_tasks = [];
15
-
16
7
  function run_micro_tasks() {
17
8
  var tasks = micro_tasks;
18
9
  micro_tasks = [];
19
10
  run_all(tasks);
20
11
  }
21
12
 
22
- function run_idle_tasks() {
23
- var tasks = idle_tasks;
24
- idle_tasks = [];
25
- run_all(tasks);
26
- }
27
-
28
- export function has_pending_tasks() {
29
- return micro_tasks.length > 0 || idle_tasks.length > 0;
30
- }
31
-
32
13
  /**
33
14
  * @param {() => void} fn
34
15
  */
@@ -51,26 +32,11 @@ export function queue_micro_task(fn) {
51
32
  micro_tasks.push(fn);
52
33
  }
53
34
 
54
- /**
55
- * @param {() => void} fn
56
- */
57
- export function queue_idle_task(fn) {
58
- if (idle_tasks.length === 0) {
59
- request_idle_callback(run_idle_tasks);
60
- }
61
-
62
- idle_tasks.push(fn);
63
- }
64
-
65
35
  /**
66
36
  * Synchronously run any queued tasks.
67
37
  */
68
38
  export function flush_tasks() {
69
- if (micro_tasks.length > 0) {
39
+ while (micro_tasks.length > 0) {
70
40
  run_micro_tasks();
71
41
  }
72
-
73
- if (idle_tasks.length > 0) {
74
- run_idle_tasks();
75
- }
76
42
  }
@@ -244,7 +244,6 @@ export async function async_body(fn) {
244
244
  if (pending) {
245
245
  batch.flush();
246
246
  } else {
247
- batch.activate();
248
247
  batch.decrement();
249
248
  }
250
249