svelte 5.46.4 → 5.47.1

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.46.4",
5
+ "version": "5.47.1",
6
6
  "type": "module",
7
7
  "types": "./types/index.d.ts",
8
8
  "engines": {
@@ -770,7 +770,39 @@ function get_ancestor_elements(node, adjacent_only, seen = new Set()) {
770
770
  }
771
771
 
772
772
  if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
773
+ // Special handling for <option> inside <select>: elements inside <option> should
774
+ // also be considered descendants of <selectedcontent>, which clones the selected option's content
775
+ if (parent.type === 'RegularElement' && parent.name === 'option') {
776
+ const is_direct_child = ancestors.length === 0;
777
+
778
+ const select_element = path.findLast(
779
+ (element, j) => element.type === 'RegularElement' && element.name === 'select' && j < i
780
+ );
781
+
782
+ if (select_element && (!adjacent_only || is_direct_child)) {
783
+ /** @type {Compiler.AST.RegularElement | null} */
784
+ let selectedcontent_element = null;
785
+ walk(select_element, null, {
786
+ RegularElement(child, context) {
787
+ if (child.name === 'selectedcontent') {
788
+ selectedcontent_element = child;
789
+ context.stop();
790
+ return;
791
+ }
792
+ context.next();
793
+ }
794
+ });
795
+
796
+ if (adjacent_only && is_direct_child && selectedcontent_element) {
797
+ return [selectedcontent_element, parent];
798
+ } else if (selectedcontent_element) {
799
+ ancestors.push(selectedcontent_element);
800
+ }
801
+ }
802
+ }
803
+
773
804
  ancestors.push(parent);
805
+
774
806
  if (adjacent_only) {
775
807
  break;
776
808
  }
@@ -817,6 +849,34 @@ function get_descendant_elements(node, adjacent_only, seen = new Set()) {
817
849
 
818
850
  walk_children(node.type === 'RenderTag' ? node : node.fragment);
819
851
 
852
+ // Special handling for <selectedcontent>: it clones the content of the selected <option>,
853
+ // so descendants of <option> elements in the same <select> should also be considered descendants
854
+ if (node.type === 'RegularElement' && node.name === 'selectedcontent') {
855
+ const path = node.metadata.path;
856
+ const select_element = path.findLast(
857
+ (/** @type {Compiler.AST.SvelteNode} */ element) =>
858
+ element.type === 'RegularElement' && element.name === 'select'
859
+ );
860
+
861
+ if (select_element) {
862
+ walk(
863
+ select_element,
864
+ { inside_option: false },
865
+ {
866
+ _(child, context) {
867
+ if (child.type === 'RegularElement' && child.name === 'option') {
868
+ context.next({ inside_option: true });
869
+ } else if (context.state.inside_option) {
870
+ walk_children(child);
871
+ } else {
872
+ context.next();
873
+ }
874
+ }
875
+ }
876
+ );
877
+ }
878
+ }
879
+
820
880
  return descendants;
821
881
  }
822
882
 
@@ -7,7 +7,11 @@ import {
7
7
  } from '../../../../html-tree-validation.js';
8
8
  import * as e from '../../../errors.js';
9
9
  import * as w from '../../../warnings.js';
10
- import { create_attribute, is_custom_element_node } from '../../nodes.js';
10
+ import {
11
+ create_attribute,
12
+ is_custom_element_node,
13
+ is_customizable_select_element
14
+ } from '../../nodes.js';
11
15
  import { regex_starts_with_newline } from '../../patterns.js';
12
16
  import { check_element } from './shared/a11y/index.js';
13
17
  import { validate_element } from './shared/element.js';
@@ -74,6 +78,15 @@ export function RegularElement(node, context) {
74
78
  node.metadata.synthetic_value_node = child;
75
79
  }
76
80
 
81
+ // Special case: <select>, <option> or <optgroup> with rich content needs special hydration handling
82
+ // We mark the subtree as dynamic so parent elements properly include the child init code
83
+ if (is_customizable_select_element(node) || node.name === 'selectedcontent') {
84
+ // Mark the element's own fragment as dynamic so it's not treated as static
85
+ node.fragment.metadata.dynamic = true;
86
+ // Also mark ancestor fragments so parents properly include the child init code
87
+ mark_subtree_dynamic(context.path);
88
+ }
89
+
77
90
  const binding = context.state.scope.get(node.name);
78
91
  if (
79
92
  binding !== null &&
@@ -1,6 +1,7 @@
1
1
  /** @import { AST } from '#compiler' */
2
2
  /** @import { Context } from '../types' */
3
3
  import * as e from '../../../errors.js';
4
+ import { mark_subtree_dynamic } from './shared/fragment.js';
4
5
 
5
6
  const valid = ['onerror', 'failed', 'pending'];
6
7
 
@@ -23,5 +24,7 @@ export function SvelteBoundary(node, context) {
23
24
  }
24
25
  }
25
26
 
27
+ mark_subtree_dynamic(context.path);
28
+
26
29
  context.next();
27
30
  }
@@ -831,6 +831,10 @@ function has_content(element) {
831
831
  return true;
832
832
  }
833
833
 
834
+ if (node.name === 'selectedcontent') {
835
+ return true;
836
+ }
837
+
834
838
  if (!has_content(node)) {
835
839
  continue;
836
840
  }
@@ -11,7 +11,12 @@ import {
11
11
  import { is_ignored } from '../../../../state.js';
12
12
  import { is_event_attribute, is_text_attribute } from '../../../../utils/ast.js';
13
13
  import * as b from '#compiler/builders';
14
- import { create_attribute, ExpressionMetadata, is_custom_element_node } from '../../../nodes.js';
14
+ import {
15
+ create_attribute,
16
+ ExpressionMetadata,
17
+ is_custom_element_node,
18
+ is_customizable_select_element
19
+ } from '../../../nodes.js';
15
20
  import { clean_nodes, determine_namespace_for_children } from '../../utils.js';
16
21
  import { build_getter } from '../utils.js';
17
22
  import {
@@ -21,9 +26,12 @@ import {
21
26
  build_set_class,
22
27
  build_set_style
23
28
  } from './shared/element.js';
24
- import { process_children } from './shared/fragment.js';
29
+ import { process_children, is_static_element } from './shared/fragment.js';
25
30
  import { build_render_statement, build_template_chunk, Memoizer } from './shared/utils.js';
26
31
  import { visit_event_attribute } from './shared/events.js';
32
+ import { Template } from '../transform-template/template.js';
33
+ import { transform_template } from '../transform-template/index.js';
34
+ import { TEMPLATE_FRAGMENT } from '../../../../../constants.js';
27
35
 
28
36
  /**
29
37
  * @param {AST.RegularElement} node
@@ -351,13 +359,65 @@ export function RegularElement(node, context) {
351
359
  b.stmt(b.assignment('=', b.member(context.state.node, 'textContent'), value))
352
360
  );
353
361
  }
362
+ } else if (is_customizable_select_element(node)) {
363
+ // For <option>, <optgroup>, or <select> elements with rich content, we need to branch based on browser support.
364
+ // Modern browsers preserve rich HTML in options, older browsers strip it to text only.
365
+ // We create a separate template for the rich content and append it to the element.
366
+
367
+ const element_node = context.state.node;
368
+
369
+ // Add a hydration marker inside the option element so $.child() has an anchor to find
370
+ context.state.template.push_comment();
371
+
372
+ // Create a separate template for the rich content
373
+ const template_name = context.state.scope.root.unique(`${node.name}_content`);
374
+ const fragment_id = b.id(context.state.scope.generate('fragment'));
375
+ const anchor_id = b.id(context.state.scope.generate('anchor'));
376
+
377
+ // Create state with a new template for the rich content
378
+ /** @type {typeof state} */
379
+ const select_state = {
380
+ ...state,
381
+ init: [],
382
+ update: [],
383
+ after_update: [],
384
+ template: new Template()
385
+ };
386
+
387
+ process_children(
388
+ trimmed,
389
+ (is_text) => b.call('$.first_child', fragment_id, is_text && b.true),
390
+ false,
391
+ {
392
+ ...context,
393
+ state: select_state
394
+ }
395
+ );
396
+
397
+ // Transform the template to $.from_html(...) and hoist it
398
+ const template = transform_template(select_state, metadata.namespace, TEMPLATE_FRAGMENT);
399
+ context.state.hoisted.push(b.var(template_name, template));
400
+
401
+ // Build the rich content function body
402
+ // The anchor is the child of the element (a hydration marker during hydration)
403
+ const body = b.block([
404
+ b.var(anchor_id, b.call('$.child', element_node)),
405
+ b.var(fragment_id, b.call(template_name)),
406
+ ...select_state.init,
407
+ ...(select_state.update.length > 0 ? [build_render_statement(select_state)] : []),
408
+ ...select_state.after_update,
409
+ b.stmt(b.call('$.append', anchor_id, fragment_id))
410
+ ]);
411
+
412
+ child_state.init.push(b.stmt(b.call('$.customizable_select', element_node, b.arrow([], body))));
354
413
  } else {
355
414
  /** @type {Expression} */
356
415
  let arg = context.state.node;
357
416
 
358
417
  // If `hydrate_node` is set inside the element, we need to reset it
359
- // after the element has been hydrated
360
- let needs_reset = trimmed.some((node) => node.type !== 'Text');
418
+ // after the element has been hydrated. We need to check if any child
419
+ // would actually advance the hydrate_node cursor - static elements don't.
420
+ let needs_reset = trimmed.some((node) => node.type !== 'Text' && !is_static_element(node));
361
421
 
362
422
  // The same applies if it's a `<template>` element, since we need to
363
423
  // set the value of `hydrate_node` to `node.content`
@@ -397,6 +457,18 @@ export function RegularElement(node, context) {
397
457
  context.state.after_update.push(...element_state.after_update);
398
458
  }
399
459
 
460
+ if (node.name === 'selectedcontent') {
461
+ context.state.init.push(
462
+ b.stmt(
463
+ b.call(
464
+ '$.selectedcontent',
465
+ context.state.node,
466
+ b.arrow([b.id('$$element')], b.assignment('=', context.state.node, b.id('$$element')))
467
+ )
468
+ )
469
+ );
470
+ }
471
+
400
472
  if (lookup.has('dir')) {
401
473
  // This fixes an issue with Chromium where updates to text content within an element
402
474
  // does not update the direction when set to auto. If we just re-assign the dir, this fixes it.
@@ -98,7 +98,7 @@ export function process_children(nodes, initial, is_element, context) {
98
98
 
99
99
  let child_state = context.state;
100
100
 
101
- if (is_static_element(node, context.state)) {
101
+ if (is_static_element(node)) {
102
102
  skipped += 1;
103
103
  } else if (
104
104
  node.type === 'EachBlock' &&
@@ -137,9 +137,8 @@ export function process_children(nodes, initial, is_element, context) {
137
137
 
138
138
  /**
139
139
  * @param {AST.SvelteNode} node
140
- * @param {ComponentContext["state"]} state
141
140
  */
142
- function is_static_element(node, state) {
141
+ export function is_static_element(node) {
143
142
  if (node.type !== 'RegularElement') return false;
144
143
  if (node.fragment.metadata.dynamic) return false;
145
144
  if (is_custom_element_node(node)) return false; // we're setting all attributes on custom elements through properties
@@ -15,6 +15,7 @@ import {
15
15
  PromiseOptimiser,
16
16
  create_async_block
17
17
  } from './shared/utils.js';
18
+ import { is_customizable_select_element } from '../../../nodes.js';
18
19
 
19
20
  /**
20
21
  * @param {AST.RegularElement} node
@@ -124,6 +125,10 @@ export function RegularElement(node, context) {
124
125
 
125
126
  const [attributes, ...rest] = prepare_element_spread_object(node, context, optimiser.transform);
126
127
 
128
+ if (is_customizable_select_element(node)) {
129
+ rest.push(b.true);
130
+ }
131
+
127
132
  const statement = b.stmt(b.call('$$renderer.select', attributes, fn, ...rest));
128
133
 
129
134
  if (optimiser.expressions.length > 0) {
@@ -149,14 +154,34 @@ export function RegularElement(node, context) {
149
154
  const inner_state = { ...state, template: [], init: [] };
150
155
  process_children(trimmed, { ...context, state: inner_state });
151
156
 
152
- body = b.arrow(
153
- [b.id('$$renderer')],
154
- b.block([...state.init, ...build_template(inner_state.template)])
155
- );
157
+ /** @type {import('estree').Statement[]} */
158
+ const body_statements = [...state.init, ...build_template(inner_state.template)];
159
+
160
+ if (dev) {
161
+ const location = locator(node.start);
162
+ body_statements.unshift(
163
+ b.stmt(
164
+ b.call(
165
+ '$.push_element',
166
+ b.id('$$renderer'),
167
+ b.literal(node.name),
168
+ b.literal(location.line),
169
+ b.literal(location.column)
170
+ )
171
+ )
172
+ );
173
+ body_statements.push(b.stmt(b.call('$.pop_element')));
174
+ }
175
+
176
+ body = b.arrow([b.id('$$renderer')], b.block(body_statements));
156
177
  }
157
178
 
158
179
  const [attributes, ...rest] = prepare_element_spread_object(node, context, optimiser.transform);
159
180
 
181
+ if (is_customizable_select_element(node)) {
182
+ rest.push(b.true);
183
+ }
184
+
160
185
  const statement = b.stmt(b.call('$$renderer.option', attributes, body, ...rest));
161
186
 
162
187
  if (optimiser.expressions.length > 0) {
@@ -192,7 +217,14 @@ export function RegularElement(node, context) {
192
217
  )
193
218
  );
194
219
  } else {
220
+ // For optgroup or select with rich content, add hydration marker at the start
195
221
  process_children(trimmed, { ...context, state });
222
+ if (
223
+ (node.name === 'optgroup' || node.name === 'select') &&
224
+ is_customizable_select_element(node)
225
+ ) {
226
+ state.template.push(b.literal('<!>'));
227
+ }
196
228
  }
197
229
 
198
230
  if (!node_is_void) {
@@ -148,3 +148,98 @@ export function get_name(node) {
148
148
 
149
149
  return null;
150
150
  }
151
+
152
+ /**
153
+ * Checks if an <option>, <optgroup>, or <select> element has rich content that requires special hydration handling.
154
+ * Rich content is anything beyond simple text, expressions, and comments for <option>,
155
+ * anything beyond <option> children for <optgroup>,
156
+ * or anything beyond <option>, <optgroup>, and empty text for <select>.
157
+ * Control flow blocks are recursively checked - they only count as rich content if they contain rich content.
158
+ * @param {AST.RegularElement} node
159
+ * @returns {boolean}
160
+ */
161
+ export function is_customizable_select_element(node) {
162
+ if (node.name === 'select' || node.name === 'optgroup' || node.name === 'option') {
163
+ for (const child of find_descendants(node.fragment)) {
164
+ if (child.type === 'RegularElement') {
165
+ if (node.name === 'select' && child.name !== 'option' && child.name !== 'optgroup') {
166
+ return true;
167
+ }
168
+
169
+ if (node.name === 'optgroup' && child.name !== 'option') {
170
+ return true;
171
+ }
172
+
173
+ if (node.name === 'option') {
174
+ return true;
175
+ }
176
+ }
177
+
178
+ // Text nodes directly in <select> or <optgroup> are rich content
179
+ else if (child.type === 'Text') {
180
+ if (node.name === 'select' || node.name === 'optgroup') {
181
+ return true;
182
+ }
183
+ }
184
+
185
+ // Any non-RegularElement, non-Text node is rich content
186
+ else {
187
+ return true;
188
+ }
189
+ }
190
+ }
191
+
192
+ return false;
193
+ }
194
+
195
+ /**
196
+ * @param {AST.Fragment | null} fragment
197
+ * @returns {Iterable<AST.SvelteNode>}
198
+ */
199
+ function* find_descendants(fragment) {
200
+ if (fragment === null) return;
201
+
202
+ for (const node of fragment.nodes) {
203
+ switch (node.type) {
204
+ case 'SnippetBlock':
205
+ case 'DebugTag':
206
+ case 'ConstTag':
207
+ case 'Comment':
208
+ case 'ExpressionTag':
209
+ break;
210
+
211
+ case 'Text':
212
+ if (node.data.trim() !== '') {
213
+ yield node;
214
+ }
215
+ break;
216
+
217
+ case 'IfBlock':
218
+ yield* find_descendants(node.consequent);
219
+ yield* find_descendants(node.alternate);
220
+ break;
221
+
222
+ case 'EachBlock':
223
+ yield* find_descendants(node.body);
224
+ yield* find_descendants(node.fallback ?? null);
225
+ break;
226
+
227
+ case 'KeyBlock':
228
+ yield* find_descendants(node.fragment);
229
+ break;
230
+
231
+ case 'AwaitBlock':
232
+ yield* find_descendants(node.pending);
233
+ yield* find_descendants(node.then);
234
+ yield* find_descendants(node.catch);
235
+ break;
236
+
237
+ case 'SvelteBoundary':
238
+ yield* find_descendants(node.fragment);
239
+ break;
240
+
241
+ default:
242
+ yield node;
243
+ }
244
+ }
245
+ }
@@ -80,9 +80,9 @@ export function closing_tag_omitted(current, next) {
80
80
  */
81
81
  const disallowed_children = {
82
82
  ...autoclosing_children,
83
- optgroup: { only: ['option', '#text'] },
84
83
  // Strictly speaking, seeing an <option> doesn't mean we're in a <select>, but we assume it here
85
- option: { only: ['#text'] },
84
+ // option or optgroup does not have an `only` restriction because newer browsers support rich HTML content
85
+ // inside option elements. For older browsers, hydration will handle the mismatch.
86
86
  form: { descendant: ['form'] },
87
87
  a: { descendant: ['a'] },
88
88
  button: { descendant: ['button'] },
@@ -92,8 +92,6 @@ const disallowed_children = {
92
92
  h4: { descendant: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] },
93
93
  h5: { descendant: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] },
94
94
  h6: { descendant: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] },
95
- // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect
96
- select: { only: ['option', 'optgroup', '#text', 'hr', 'script', 'template'] },
97
95
 
98
96
  // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd
99
97
  // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption
@@ -0,0 +1,98 @@
1
+ import { hydrating, reset, set_hydrate_node, set_hydrating } from '../hydration.js';
2
+ import { create_comment } from '../operations.js';
3
+ import { attach } from './attachments.js';
4
+
5
+ /** @type {boolean | null} */
6
+ let supported = null;
7
+
8
+ /**
9
+ * Checks if the browser supports rich HTML content inside `<option>` elements.
10
+ * Modern browsers preserve HTML elements inside options, while older browsers
11
+ * strip them during parsing, leaving only text content.
12
+ * @returns {boolean}
13
+ */
14
+ function is_supported() {
15
+ if (supported === null) {
16
+ var select = document.createElement('select');
17
+ select.innerHTML = '<option><span>t</span></option>';
18
+ supported = /** @type {Element} */ (select.firstChild)?.firstChild?.nodeType === 1;
19
+ }
20
+
21
+ return supported;
22
+ }
23
+
24
+ /**
25
+ *
26
+ * @param {HTMLElement} element
27
+ * @param {(new_element: HTMLElement) => void} update_element
28
+ */
29
+ export function selectedcontent(element, update_element) {
30
+ // if it's not supported no need for special logic
31
+ if (!is_supported()) return;
32
+
33
+ // we use the attach function directly just to make sure is executed when is mounted to the dom
34
+ attach(element, () => () => {
35
+ const select = element.closest('select');
36
+ if (!select) return;
37
+
38
+ const observer = new MutationObserver((entries) => {
39
+ var selected = false;
40
+
41
+ for (const entry of entries) {
42
+ if (entry.target === element) {
43
+ // the `<selectedcontent>` already changed, no need to replace it
44
+ return;
45
+ }
46
+
47
+ // if the changes doesn't include the selected `<option>` we don't need to do anything
48
+ selected ||= !!entry.target.parentElement?.closest('option')?.selected;
49
+ }
50
+
51
+ if (selected) {
52
+ // replace the `<selectedcontent>` with a clone
53
+ element.replaceWith((element = /** @type {HTMLElement} */ (element.cloneNode(true))));
54
+ update_element(element);
55
+ }
56
+ });
57
+
58
+ observer.observe(select, {
59
+ childList: true,
60
+ characterData: true,
61
+ subtree: true
62
+ });
63
+
64
+ return () => {
65
+ observer.disconnect();
66
+ };
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Handles rich HTML content inside `<option>`, `<optgroup>`, or `<select>` elements with browser-specific branching.
72
+ * Modern browsers preserve HTML inside options, while older browsers strip it to text only.
73
+ *
74
+ * @param {HTMLOptionElement | HTMLOptGroupElement | HTMLSelectElement} element The element to process
75
+ * @param {() => void} rich_fn Function to process rich HTML content (modern browsers)
76
+ */
77
+ export function customizable_select(element, rich_fn) {
78
+ var was_hydrating = hydrating;
79
+
80
+ if (!is_supported()) {
81
+ set_hydrating(false);
82
+ element.textContent = '';
83
+ element.append(create_comment(''));
84
+ }
85
+
86
+ try {
87
+ rich_fn();
88
+ } finally {
89
+ if (was_hydrating) {
90
+ if (hydrating) {
91
+ reset(element);
92
+ } else {
93
+ set_hydrating(true);
94
+ set_hydrate_node(element);
95
+ }
96
+ }
97
+ }
98
+ }
@@ -42,6 +42,7 @@ export {
42
42
  export { set_class } from './dom/elements/class.js';
43
43
  export { apply, event, delegate, replay_events } from './dom/elements/events.js';
44
44
  export { autofocus, remove_textarea_child } from './dom/elements/misc.js';
45
+ export { customizable_select, selectedcontent } from './dom/elements/customizable-select.js';
45
46
  export { set_style } from './dom/elements/style.js';
46
47
  export { animation, transition } from './dom/elements/transitions.js';
47
48
  export { bind_active_element } from './dom/elements/bindings/document.js';
@@ -220,9 +220,10 @@ export class Renderer {
220
220
  * @param {Record<string, boolean> | undefined} [classes]
221
221
  * @param {Record<string, string> | undefined} [styles]
222
222
  * @param {number | undefined} [flags]
223
+ * @param {boolean | undefined} [is_rich]
223
224
  * @returns {void}
224
225
  */
225
- select(attrs, fn, css_hash, classes, styles, flags) {
226
+ select(attrs, fn, css_hash, classes, styles, flags, is_rich) {
226
227
  const { value, ...select_attrs } = attrs;
227
228
 
228
229
  this.push(`<select${attributes(select_attrs, css_hash, classes, styles, flags)}>`);
@@ -230,7 +231,7 @@ export class Renderer {
230
231
  renderer.local.select_value = value;
231
232
  fn(renderer);
232
233
  });
233
- this.push('</select>');
234
+ this.push(`${is_rich ? '<!>' : ''}</select>`);
234
235
  }
235
236
 
236
237
  /**
@@ -240,8 +241,9 @@ export class Renderer {
240
241
  * @param {Record<string, boolean> | undefined} [classes]
241
242
  * @param {Record<string, string> | undefined} [styles]
242
243
  * @param {number | undefined} [flags]
244
+ * @param {boolean | undefined} [is_rich]
243
245
  */
244
- option(attrs, body, css_hash, classes, styles, flags) {
246
+ option(attrs, body, css_hash, classes, styles, flags, is_rich) {
245
247
  this.#out.push(`<option${attributes(attrs, css_hash, classes, styles, flags)}`);
246
248
 
247
249
  /**
@@ -258,7 +260,7 @@ export class Renderer {
258
260
  renderer.#out.push(' selected');
259
261
  }
260
262
 
261
- renderer.#out.push(`>${body}</option>`);
263
+ renderer.#out.push(`>${body}${is_rich ? '<!>' : ''}</option>`);
262
264
 
263
265
  // super edge case, but may as well handle it
264
266
  if (head) {
package/src/version.js CHANGED
@@ -4,5 +4,5 @@
4
4
  * The current version, as set in package.json.
5
5
  * @type {string}
6
6
  */
7
- export const VERSION = '5.46.4';
7
+ export const VERSION = '5.47.1';
8
8
  export const PUBLIC_VERSION = '5';