@juspay/svelte-ui-components 4.27.6 → 4.28.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.
Files changed (164) hide show
  1. package/README.md +140 -36
  2. package/dist/Animations/ModalAnimation.svelte +15 -1
  3. package/dist/Animations/OverlayAnimation.svelte +7 -1
  4. package/dist/AreaChart/AreaChart.svelte +366 -85
  5. package/dist/AspectRatio/AspectRatio.svelte +51 -0
  6. package/dist/AspectRatio/AspectRatio.svelte.d.ts +3 -0
  7. package/dist/AspectRatio/properties.d.ts +27 -0
  8. package/dist/AspectRatio/properties.js +1 -0
  9. package/dist/Banner/Banner.svelte +10 -4
  10. package/dist/Banner/properties.d.ts +8 -0
  11. package/dist/BarChart/BarChart.svelte +156 -49
  12. package/dist/Book/Book.svelte +14 -1
  13. package/dist/BrandLoader/BrandLoader.svelte +76 -12
  14. package/dist/Button/Button.svelte +56 -11
  15. package/dist/Calendar/Calendar.svelte +88 -61
  16. package/dist/Card/Card.svelte +1 -1
  17. package/dist/Carousel/Carousel.svelte +6 -3
  18. package/dist/ChatComposer/ChatComposer.svelte +54 -4
  19. package/dist/ChatComposer/dictationState.d.ts +15 -0
  20. package/dist/ChatComposer/dictationState.js +9 -0
  21. package/dist/ChatComposer/properties.d.ts +28 -1
  22. package/dist/ChatMessage/ChatMessage.svelte +27 -1
  23. package/dist/CheckListItem/CheckListItem.svelte +3 -3
  24. package/dist/Checkbox/Checkbox.svelte +84 -3
  25. package/dist/Checkbox/properties.d.ts +45 -13
  26. package/dist/ChipInput/ChipInput.svelte +70 -3
  27. package/dist/ChipInput/properties.d.ts +16 -0
  28. package/dist/Choicebox/Choicebox.svelte +156 -1
  29. package/dist/Choicebox/group.d.ts +63 -0
  30. package/dist/Choicebox/group.js +127 -0
  31. package/dist/Choicebox/properties.d.ts +46 -0
  32. package/dist/ColorPicker/ColorPicker.svelte +169 -20
  33. package/dist/ColorPicker/properties.d.ts +16 -0
  34. package/dist/Combobox/Combobox.svelte +87 -25
  35. package/dist/Combobox/properties.d.ts +23 -0
  36. package/dist/CommandMenu/CommandMenu.svelte +27 -11
  37. package/dist/ContextMenu/ContextMenu.svelte +31 -37
  38. package/dist/DateRangePicker/DateRangePicker.svelte +171 -73
  39. package/dist/DeltaIndicator/DeltaIndicator.svelte +3 -3
  40. package/dist/Draggable/Draggable.svelte +12 -7
  41. package/dist/DualAxisBarChart/DualAxisBarChart.svelte +11 -4
  42. package/dist/EmptyState/EmptyState.svelte +1 -1
  43. package/dist/FileDropzoneTrigger/FileDropzoneTrigger.svelte +1 -1
  44. package/dist/FileInput/FileInput.svelte +50 -1
  45. package/dist/FileInput/properties.d.ts +7 -0
  46. package/dist/FunnelChart/FunnelChart.svelte +40 -12
  47. package/dist/FunnelChart/properties.d.ts +7 -1
  48. package/dist/Gallery/Gallery.svelte +20 -13
  49. package/dist/Gallery/properties.d.ts +8 -0
  50. package/dist/GridItem/GridItem.svelte +31 -1
  51. package/dist/HITL/HITL.svelte +35 -9
  52. package/dist/Icon/Icon.svelte +19 -3
  53. package/dist/Input/Input.svelte +24 -10
  54. package/dist/Input/properties.d.ts +37 -1
  55. package/dist/InputButton/InputButton.svelte +86 -11
  56. package/dist/InputButton/properties.d.ts +10 -1
  57. package/dist/Label/Label.svelte +54 -0
  58. package/dist/Label/Label.svelte.d.ts +3 -0
  59. package/dist/Label/properties.d.ts +26 -0
  60. package/dist/Label/properties.js +1 -0
  61. package/dist/LineChart/LineChart.svelte +385 -94
  62. package/dist/ListItem/ListItem.svelte +121 -15
  63. package/dist/Loader/Loader.svelte +22 -2
  64. package/dist/LoadingDots/LoadingDots.svelte +21 -0
  65. package/dist/MediaUpload/MediaUpload.svelte +13 -3
  66. package/dist/Menu/Menu.svelte +56 -28
  67. package/dist/Modal/Modal.svelte +77 -42
  68. package/dist/Pagination/Pagination.svelte +9 -2
  69. package/dist/Phone/Phone.svelte +4 -0
  70. package/dist/PieChart/PieChart.svelte +282 -38
  71. package/dist/PieChart/properties.d.ts +9 -0
  72. package/dist/Pill/Pill.svelte +14 -5
  73. package/dist/Progress/Progress.svelte +20 -0
  74. package/dist/Radio/Radio.svelte +58 -1
  75. package/dist/Radio/properties.d.ts +23 -0
  76. package/dist/RatingGroup/RatingGroup.svelte +283 -0
  77. package/dist/RatingGroup/RatingGroup.svelte.d.ts +4 -0
  78. package/dist/RatingGroup/properties.d.ts +73 -0
  79. package/dist/RatingGroup/properties.js +1 -0
  80. package/dist/RelativeTime/RelativeTime.svelte +1 -1
  81. package/dist/SankeyChart/SankeyChart.svelte +272 -50
  82. package/dist/SankeyChart/properties.d.ts +9 -0
  83. package/dist/Scroller/Scroller.svelte +37 -2
  84. package/dist/Select/Select.svelte +166 -46
  85. package/dist/Select/properties.d.ts +7 -0
  86. package/dist/Separator/Separator.svelte +48 -0
  87. package/dist/Separator/Separator.svelte.d.ts +3 -0
  88. package/dist/Separator/properties.d.ts +22 -0
  89. package/dist/Separator/properties.js +1 -0
  90. package/dist/Sheet/Sheet.svelte +60 -21
  91. package/dist/Shimmer/Shimmer.svelte +15 -0
  92. package/dist/Slider/Slider.svelte +87 -6
  93. package/dist/Slider/properties.d.ts +26 -0
  94. package/dist/Snippet/Snippet.svelte +4 -0
  95. package/dist/Snippet/copyState.svelte.d.ts +13 -1
  96. package/dist/Snippet/copyState.svelte.js +27 -1
  97. package/dist/Snippet/properties.d.ts +7 -0
  98. package/dist/SoundKit/SoundKit.js +9 -8
  99. package/dist/SplitInput/SplitInput.svelte +68 -2
  100. package/dist/SplitInput/properties.d.ts +23 -0
  101. package/dist/StatCard/StatCard.svelte +6 -6
  102. package/dist/Status/Status.svelte +2 -2
  103. package/dist/Stepper/Step.svelte +19 -6
  104. package/dist/Stepper/Stepper.svelte +108 -35
  105. package/dist/Table/BuiltinCell.svelte +2 -2
  106. package/dist/Table/Table.svelte +373 -55
  107. package/dist/Table/properties.d.ts +134 -3
  108. package/dist/Tabs/Tabs.svelte +263 -65
  109. package/dist/Tabs/properties.d.ts +23 -0
  110. package/dist/TaskList/TaskList.svelte +14 -5
  111. package/dist/ThemeSwitcher/ThemeSwitcher.svelte +23 -0
  112. package/dist/ThinkingIndicator/ThinkingIndicator.svelte +13 -5
  113. package/dist/Toast/Toast.svelte +27 -2
  114. package/dist/Toggle/Toggle.svelte +79 -7
  115. package/dist/Toggle/Toggle.svelte.d.ts +1 -1
  116. package/dist/Toggle/properties.d.ts +30 -7
  117. package/dist/ToolCallLog/ToolCallLog.svelte +59 -21
  118. package/dist/ToolCallLog/properties.d.ts +10 -0
  119. package/dist/Toolbar/Toolbar.svelte +1 -1
  120. package/dist/Tooltip/Tooltip.svelte +20 -2
  121. package/dist/Tooltip/tooltip-action.js +39 -4
  122. package/dist/TypewriterText/TypewriterText.svelte +34 -2
  123. package/dist/TypewriterText/properties.d.ts +18 -5
  124. package/dist/_chart/ChartContainer.svelte +12 -0
  125. package/dist/_chart/ChartTooltip.svelte +18 -4
  126. package/dist/_chart/Legend.svelte +21 -0
  127. package/dist/_chart/Legend.svelte.d.ts +15 -0
  128. package/dist/_chart/geometry.d.ts +54 -1
  129. package/dist/_chart/geometry.js +122 -14
  130. package/dist/_chart/interactions.d.ts +5 -1
  131. package/dist/_chart/interactions.js +9 -5
  132. package/dist/_chart/scales.js +16 -0
  133. package/dist/_chart/temporal.d.ts +45 -0
  134. package/dist/_chart/temporal.js +116 -0
  135. package/dist/_field/description.d.ts +50 -0
  136. package/dist/_field/description.js +51 -0
  137. package/dist/_interaction/dismissal.d.ts +67 -0
  138. package/dist/_interaction/dismissal.js +137 -0
  139. package/dist/_interaction/focus.d.ts +73 -0
  140. package/dist/_interaction/focus.js +143 -0
  141. package/dist/assets/star.svg +5 -0
  142. package/dist/index.d.ts +13 -1
  143. package/dist/index.js +21 -1
  144. package/dist/reduced-motion.svelte.d.ts +37 -0
  145. package/dist/reduced-motion.svelte.js +44 -0
  146. package/dist/styles/theme-dark.css +778 -0
  147. package/dist/types.d.ts +4 -2
  148. package/dist/utils.d.ts +62 -1
  149. package/dist/utils.js +80 -21
  150. package/dist-codemod/{bin.js → codemod/bin.js} +13 -2
  151. package/dist-codemod/{cli.js → codemod/cli.js} +48 -3
  152. package/dist-codemod/codemod/migrate-audit.js +395 -0
  153. package/dist-codemod/codemod/migration-assets/legacy-display.css +120 -0
  154. package/dist-codemod/codemod/migration-assets/legacy-palette.css +106 -0
  155. package/dist-codemod/codemod/migration-assets/wc-display.json +492 -0
  156. package/dist-codemod/migrate/analyze.js +804 -0
  157. package/dist-codemod/migrate/chart-tooltip-selector.js +301 -0
  158. package/dist-wc/index.js +13992 -4323
  159. package/package.json +13 -6
  160. package/dist/Modal/focus-trap.d.ts +0 -42
  161. package/dist/Modal/focus-trap.js +0 -57
  162. /package/dist-codemod/{legacy-pairs.js → codemod/legacy-pairs.js} +0 -0
  163. /package/dist-codemod/{transform.js → codemod/transform.js} +0 -0
  164. /package/dist-codemod/{wc-children.js → codemod/wc-children.js} +0 -0
@@ -0,0 +1,804 @@
1
+ import { readdirSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { parse } from 'svelte/compiler';
4
+ export const LIBRARY = '@juspay/svelte-ui-components';
5
+ /** Peer requirement of the 3.x line, from its published package.json. */
6
+ export const SVELTE_PEER_RANGE = '^5.41.2';
7
+ /**
8
+ * Reads an unknown value as a record of unknown fields.
9
+ *
10
+ * Copies the own enumerable keys rather than asserting the type. Assertions are
11
+ * banned repo-wide and so are type predicates, so there is no way to tell the
12
+ * compiler that a narrowed `object` really is keyed by string. The copy is
13
+ * shallow, so nested nodes keep their identity and traversal is unaffected.
14
+ *
15
+ * `Reflect.get` rather than `value[key]`, because indexing a value typed
16
+ * `object` is itself an error without an assertion — the very thing being
17
+ * avoided. `defineProperty` rather than assignment, because one input here is a
18
+ * consumer's `package.json`: `JSON.parse` turns `__proto__` into an ordinary
19
+ * own key, and assigning it would re-point this copy's prototype instead of
20
+ * adding a field, so a manifest declaring nothing could still answer for
21
+ * `dependencies` through the chain.
22
+ *
23
+ * Only string keys are copied. The Svelte AST and `package.json` have no
24
+ * symbol-keyed fields, and reading one is never the question being asked here.
25
+ */
26
+ function asRecord(value) {
27
+ if (typeof value !== 'object' || value === null) {
28
+ return {};
29
+ }
30
+ const record = {};
31
+ for (const key of Object.keys(value)) {
32
+ Object.defineProperty(record, key, {
33
+ value: Reflect.get(value, key),
34
+ enumerable: true,
35
+ writable: true,
36
+ configurable: true
37
+ });
38
+ }
39
+ return record;
40
+ }
41
+ function readDependency(manifest, name) {
42
+ const root = asRecord(manifest);
43
+ for (const field of ['dependencies', 'devDependencies', 'peerDependencies']) {
44
+ const value = asRecord(root[field])[name];
45
+ if (typeof value === 'string') {
46
+ return value;
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+ /**
52
+ * Whether a declared range permits any version satisfying the 3.x peer.
53
+ *
54
+ * Comparing major numbers is not enough in either direction: an exact `5.0.0`
55
+ * shares the major but does not satisfy `^5.41.2`, and `^4 || ^5` looks like a
56
+ * 4 to a first-number read while genuinely intersecting. Range intersection is
57
+ * the actual question being asked.
58
+ */
59
+ /**
60
+ * Injected rather than imported, because this module ships.
61
+ *
62
+ * `semver` is a devDependency, so a `import { intersects } from 'semver'` here
63
+ * loaded fine from a repo checkout and threw ERR_MODULE_NOT_FOUND the moment
64
+ * the compiled module was run from a consumer's node_modules -- where the
65
+ * detectors below are exactly what `sui-codemod migrate` needs and the peer
66
+ * check is not used at all. `npm run check:codemod` could not see it either:
67
+ * type-checking resolves devDependencies happily. Only installing the packed
68
+ * tarball and running the binary surfaced it.
69
+ *
70
+ * `scripts/migrate/cli.ts` passes the real `semver` implementation; the
71
+ * shipped audit never calls the manifest path and so never needs one.
72
+ */
73
+ let rangesIntersect = () => {
74
+ // Throws rather than defaulting to `true`. A permissive default would make a
75
+ // forgotten injection look like "every peer range is fine", which is the
76
+ // silent-pass shape this tool exists to catch in other people's code. The
77
+ // shipped audit never reaches `analyzeManifest`, so it never trips this.
78
+ throw new Error('analyzeManifest needs a range intersector: call setRangeIntersector() first ' +
79
+ '(scripts/migrate/cli.ts does). The shipped audit does not use this path.');
80
+ };
81
+ export function setRangeIntersector(fn) {
82
+ rangesIntersect = fn;
83
+ }
84
+ function satisfiesPeer(range) {
85
+ try {
86
+ // `loose` changes the answer for exactly one shape worth having: a version
87
+ // written with a leading zero (`^05.41.2`), which strict parsing throws on
88
+ // and would therefore report as a blocker it is not. It does not loosen
89
+ // prerelease handling -- `^5.41.2-alpha` and `>=5.0.0-0` resolve the same
90
+ // either way -- and genuinely unparseable ranges still throw.
91
+ return rangesIntersect(range, SVELTE_PEER_RANGE);
92
+ }
93
+ catch {
94
+ // An unparseable range (a git URL, a workspace protocol) cannot be shown to
95
+ // satisfy the peer, and silently passing it would be the wrong default.
96
+ return false;
97
+ }
98
+ }
99
+ /**
100
+ * Checks a consumer's manifest for anything that would prevent taking 3.x.
101
+ *
102
+ * Deliberately reads the manifest rather than node_modules: an installed tree
103
+ * can be stale relative to what the project declares, and reading the wrong one
104
+ * produces a confidently wrong answer about which Svelte a project is on.
105
+ */
106
+ export function analyzeManifest(manifest) {
107
+ const currentRange = readDependency(manifest, LIBRARY);
108
+ const svelteRange = readDependency(manifest, 'svelte');
109
+ const blockers = [];
110
+ if (currentRange === null) {
111
+ blockers.push(`${LIBRARY} is not a dependency of this project`);
112
+ }
113
+ if (svelteRange === null) {
114
+ blockers.push('svelte is not a dependency; 3.x requires svelte ^5.41.2');
115
+ }
116
+ else {
117
+ if (!satisfiesPeer(svelteRange)) {
118
+ blockers.push(`svelte ${svelteRange} does not satisfy the 3.x peer requirement of ${SVELTE_PEER_RANGE}`);
119
+ }
120
+ }
121
+ return { currentRange, svelteRange, blockers };
122
+ }
123
+ /**
124
+ * Reads the {component, tag, display} of every `sui-*` wrapper straight from
125
+ * `src/wc/components`, rather than hardcoding the 98 -- a hardcoded list drifts
126
+ * the moment a component is added, renamed, or its display default changes,
127
+ * and would then silently under- or over-report `host-display-inline`,
128
+ * `chart-min-width`, and the `<sui-input-button>` spelling below it.
129
+ *
130
+ * `repoRoot` is this library's OWN repo root, not the consumer root the CLI is
131
+ * pointed at -- the wrapper sources this reads exist only here. A root that
132
+ * does not resolve to a checkout with these sources (a published tree with no
133
+ * `src/`, say) yields `[]` rather than a guess: the detectors that depend on
134
+ * this list then simply find nothing, which is what "the source of truth
135
+ * that used to answer this question is not present" ought to mean here.
136
+ */
137
+ export function readWcComponents(repoRoot) {
138
+ const dir = join(repoRoot, 'src/wc/components');
139
+ let entries;
140
+ try {
141
+ entries = readdirSync(dir).filter((name) => name.endsWith('.wc.svelte'));
142
+ }
143
+ catch {
144
+ return [];
145
+ }
146
+ const components = [];
147
+ for (const file of entries) {
148
+ const component = file.slice(0, -'.wc.svelte'.length);
149
+ const source = readFileSync(join(dir, file), 'utf8');
150
+ // The wrapped component's own name, read off the import that brings it in
151
+ // -- `Card.wc.svelte` always imports `Card` from `$lib/Card/Card.svelte` --
152
+ // rather than assumed equal to the filename, so a wrapper that diverges
153
+ // from that convention is skipped instead of silently mismatched.
154
+ const importPattern = new RegExp(`import\\s+\\w+\\s+from\\s+'\\$lib/${component}/${component}\\.svelte'`);
155
+ const tagMatch = /tag:\s*'([^']+)'/.exec(source);
156
+ const displayMatch = /display:\s*var\(--sui-[\w-]+-display,\s*(block|inline-block)\)/.exec(source);
157
+ if (!importPattern.test(source) || tagMatch === null || displayMatch === null) {
158
+ continue;
159
+ }
160
+ components.push({
161
+ component,
162
+ tag: tagMatch[1],
163
+ display: displayMatch[1] === 'inline-block' ? 'inline-block' : 'block'
164
+ });
165
+ }
166
+ return components;
167
+ }
168
+ /**
169
+ * Default context for callers that only care about the Toolbar/InputButton
170
+ * checks reachable through a named import: those never need the wrapper list,
171
+ * and every existing caller of `analyzeSvelte` predates its existence.
172
+ */
173
+ const EMPTY_CONTEXT = { wcComponents: [] };
174
+ const EMPTY_NAMES = new Set();
175
+ /** Local names this file binds to each of the library's named imports. */
176
+ function importedNames(source) {
177
+ const byExport = new Map();
178
+ const importPattern = new RegExp(`import\\s*\\{([^}]*)\\}\\s*from\\s*['"]${LIBRARY.replace('/', '\\/')}['"]`, 'g');
179
+ for (const match of source.matchAll(importPattern)) {
180
+ for (const clause of match[1].split(',')) {
181
+ const trimmed = clause.trim();
182
+ if (trimmed.length === 0) {
183
+ continue;
184
+ }
185
+ const [imported, local] = trimmed.split(/\s+as\s+/).map((part) => part.trim());
186
+ const set = byExport.get(imported) ?? new Set();
187
+ set.add(local ?? imported);
188
+ byExport.set(imported, set);
189
+ }
190
+ }
191
+ return byExport;
192
+ }
193
+ function lineOf(source, offset) {
194
+ return source.slice(0, offset).split('\n').length;
195
+ }
196
+ /** True only for `{false}` — not for a bound expression or the string "false". */
197
+ function isFalseLiteral(value) {
198
+ // Svelte types an attribute value as `true | ExpressionTag | Array<Text |
199
+ // ExpressionTag>`, and quoting a single expression (`="{false}"`) produces
200
+ // the one-element array rather than the bare tag. Reading only the bare shape
201
+ // reports a Toolbar whose control genuinely never renders.
202
+ const single = Array.isArray(value) && value.length === 1 ? value[0] : value;
203
+ const tag = asRecord(single);
204
+ if (tag.type !== 'ExpressionTag') {
205
+ return false;
206
+ }
207
+ const expression = asRecord(tag.expression);
208
+ return expression.type === 'Literal' && expression.value === false;
209
+ }
210
+ function summarize(node) {
211
+ const attributes = Array.isArray(node.attributes) ? node.attributes : [];
212
+ let explicitlyDisabled = false;
213
+ let hasOwnIcon = false;
214
+ let hasSpread = false;
215
+ for (const raw of attributes) {
216
+ const attribute = asRecord(raw);
217
+ if (attribute.type === 'SpreadAttribute') {
218
+ hasSpread = true;
219
+ continue;
220
+ }
221
+ if (attribute.name === 'showBackButton') {
222
+ // Only the Boolean literal `false` disables the control. This used to
223
+ // test `JSON.stringify(value).includes('false')`, which matched unrelated
224
+ // AST properties -- a MemberExpression carries `"computed":false`, so
225
+ // `showBackButton={cfg.showBack}` read as disabled and its affected
226
+ // Toolbar went unreported. A false negative is the worst outcome here.
227
+ explicitlyDisabled = isFalseLiteral(attribute.value);
228
+ }
229
+ if (attribute.name === 'backIcon') {
230
+ hasOwnIcon = true;
231
+ }
232
+ }
233
+ return { explicitlyDisabled, hasOwnIcon, hasSpread };
234
+ }
235
+ function walk(node, visit) {
236
+ if (Array.isArray(node)) {
237
+ for (const child of node) {
238
+ walk(child, visit);
239
+ }
240
+ return;
241
+ }
242
+ const record = asRecord(node);
243
+ if (typeof record.type === 'string') {
244
+ visit(record);
245
+ }
246
+ for (const value of Object.values(record)) {
247
+ if (typeof value === 'object' && value !== null) {
248
+ walk(value, visit);
249
+ }
250
+ }
251
+ }
252
+ /** True for a `Text` node whose content is only formatting whitespace -- it collapses in rendered HTML, so it does not itself break an inline run. */
253
+ function isWhitespaceOnlyText(raw) {
254
+ const record = asRecord(raw);
255
+ return (record.type === 'Text' && typeof record.data === 'string' && record.data.trim().length === 0);
256
+ }
257
+ /**
258
+ * Standard HTML tags whose UA-stylesheet default is inline or inline-block --
259
+ * the shapes that genuinely sit *beside* a neighbour rather than stacking
260
+ * with it. Deliberately narrower than `TEXT_FLOW_ELEMENTS`: that set answers
261
+ * "is this tag's own content model text-flow" (so it includes block-level
262
+ * `p`/`li`/`td`/`h1`-`h6`), while this one answers "does this tag occupy a
263
+ * line the way an inline box does" -- a `<div>` sibling never counts, however
264
+ * text-flow-ish its own contents are.
265
+ */
266
+ const INLINE_HTML_ELEMENTS = new Set([
267
+ 'a',
268
+ 'abbr',
269
+ 'b',
270
+ 'bdi',
271
+ 'bdo',
272
+ 'br',
273
+ 'button',
274
+ 'cite',
275
+ 'code',
276
+ 'data',
277
+ 'dfn',
278
+ 'em',
279
+ 'i',
280
+ 'img',
281
+ 'input',
282
+ 'kbd',
283
+ 'label',
284
+ 'mark',
285
+ 'output',
286
+ 'q',
287
+ 'rp',
288
+ 'rt',
289
+ 'ruby',
290
+ 's',
291
+ 'samp',
292
+ 'select',
293
+ 'small',
294
+ 'span',
295
+ 'strong',
296
+ 'sub',
297
+ 'sup',
298
+ 'textarea',
299
+ 'time',
300
+ 'u',
301
+ 'var',
302
+ 'wbr'
303
+ ]);
304
+ /** True for a raw sibling node that itself sits inline: another `sui-*` element, non-whitespace text, or a known inline HTML tag. */
305
+ function isInlineLevelSibling(raw) {
306
+ const record = asRecord(raw);
307
+ if (record.type === 'Text') {
308
+ return typeof record.data === 'string' && record.data.trim().length > 0;
309
+ }
310
+ if (record.type === 'RegularElement' && typeof record.name === 'string') {
311
+ return record.name.startsWith('sui-') || INLINE_HTML_ELEMENTS.has(record.name);
312
+ }
313
+ // Everything else (an ExpressionTag, a Comment, an IfBlock/EachBlock, a
314
+ // Component) is neither skippable whitespace nor a known inline shape, so
315
+ // scanning stops here without a verdict -- a false negative rather than a
316
+ // guess at what that construct renders.
317
+ return false;
318
+ }
319
+ /**
320
+ * Walks outward from `siblings[index]` in one direction, skipping only
321
+ * whitespace-only text (it collapses, so it does not separate two elements
322
+ * that are otherwise adjacent), and reports whether the first real neighbour
323
+ * found is itself inline-level.
324
+ */
325
+ function nearestNeighbourIsInline(siblings, index, step) {
326
+ for (let i = index + step; i >= 0 && i < siblings.length; i += step) {
327
+ if (!isWhitespaceOnlyText(siblings[i])) {
328
+ return isInlineLevelSibling(siblings[i]);
329
+ }
330
+ }
331
+ return false;
332
+ }
333
+ /** Whether the sibling at `index` has an inline-level neighbour on either side, formatting whitespace aside. */
334
+ function hasAdjacentInlineContent(siblings, index) {
335
+ return (nearestNeighbourIsInline(siblings, index, -1) || nearestNeighbourIsInline(siblings, index, 1));
336
+ }
337
+ /**
338
+ * Same traversal as `walk`, threading the nearest enclosing `RegularElement`,
339
+ * and (for a node positioned directly in some Fragment's own child list)
340
+ * whether it has an inline-level sibling there. `host-display-inline` and
341
+ * `chart-min-width` both turn on "what does the immediate parent/neighbours
342
+ * look like", which a parent-blind walk cannot answer -- kept separate from
343
+ * `walk` itself rather than adding parameters there, so the existing Toolbar
344
+ * traversal is untouched.
345
+ *
346
+ * Non-element wrapper nodes (`IfBlock`, `EachBlock`, a `Component`) pass the
347
+ * ancestor through unchanged: an `{#if}` or another component between a chart
348
+ * and its styled `<div>` doesn't change which DOM element the chart actually
349
+ * lands inside.
350
+ *
351
+ * `Fragment` nodes (every element's `.fragment`, and the file's own root one)
352
+ * are intercepted rather than walked generically: only there is the ordered,
353
+ * same-processing-pass sibling list available to compute adjacency against.
354
+ * Once inside an `{#if}`/`{#each}`'s own nested fragment, siblings are that
355
+ * construct's, not the surrounding DOM's -- a sui-* element that only sits
356
+ * beside inline content through a conditional branch is a false negative,
357
+ * not a guess.
358
+ */
359
+ function walkWithParent(node, parent, visit, hasInlineSibling = false) {
360
+ if (Array.isArray(node)) {
361
+ for (const child of node) {
362
+ walkWithParent(child, parent, visit, hasInlineSibling);
363
+ }
364
+ return;
365
+ }
366
+ const record = asRecord(node);
367
+ if (typeof record.type !== 'string') {
368
+ return;
369
+ }
370
+ visit(record, parent, hasInlineSibling);
371
+ const nextParent = record.type === 'RegularElement' && typeof record.name === 'string'
372
+ ? { name: record.name, node: record }
373
+ : parent;
374
+ if (record.type === 'Fragment' && Array.isArray(record.nodes)) {
375
+ // Bound to a local rather than read back off `record.nodes` inside the
376
+ // callback below: narrowing a property access does not survive into a
377
+ // nested closure, and re-reading it there would need an assertion to
378
+ // recover the array type -- banned repo-wide. A local const keeps the
379
+ // narrowing without one.
380
+ const siblings = record.nodes;
381
+ siblings.forEach((child, index) => {
382
+ walkWithParent(child, nextParent, visit, hasAdjacentInlineContent(siblings, index));
383
+ });
384
+ return;
385
+ }
386
+ for (const value of Object.values(record)) {
387
+ if (typeof value === 'object' && value !== null) {
388
+ walkWithParent(value, nextParent, visit, false);
389
+ }
390
+ }
391
+ }
392
+ const CHART_TOOLTIP_SLOT_DETAIL = "selector targets '.chart-tooltip-slot'; PieChart/SankeyChart's custom-tooltip " +
393
+ "wrapper is now '.chart-tooltip.unstyled' (plus '.portal' when tooltipPortal is set)";
394
+ /**
395
+ * `.chart-tooltip-slot` findings inside one block of CSS text (a `.css` file's
396
+ * whole contents, or one `<style>` block's contents). `cssOffset` bridges CSS
397
+ * offsets back to `source` offsets for a `<style>` block, where the two differ
398
+ * by the length of everything before the block; it is `0` for a standalone
399
+ * `.css` file, where they are the same text.
400
+ */
401
+ function chartTooltipSlotFindings(css, file, source, cssOffset) {
402
+ const findings = [];
403
+ for (const match of css.matchAll(/\.chart-tooltip-slot\b/g)) {
404
+ findings.push({
405
+ file,
406
+ line: lineOf(source, cssOffset + match.index),
407
+ reason: 'chart-tooltip-slot-selector',
408
+ detail: CHART_TOOLTIP_SLOT_DETAIL
409
+ });
410
+ }
411
+ return findings;
412
+ }
413
+ const CHAT_COMPOSER_RECORDING_CSS_DETAIL = "selector targets 'sui-chat-composer[recording]'; recording lost reflect: true (it is now " +
414
+ "{ type: 'String' }), so the attribute is never set by the property and this selector will " +
415
+ 'never match — setting el.recording still works, and a presence attribute you set yourself ' +
416
+ 'still means true, so read el.recording rather than chasing this as a real regression';
417
+ /**
418
+ * `sui-chat-composer[recording]` findings inside one block of CSS text, same
419
+ * split as `chartTooltipSlotFindings` above and for the same reason: a `.css`
420
+ * file's whole contents, or one `<style>` block's contents, with `cssOffset`
421
+ * bridging back to `source` offsets. The tag is a literal here rather than
422
+ * read off `readWcComponents`: unlike the 98-wrapper detectors, this reason
423
+ * targets exactly one named component with a tag that cannot rename itself
424
+ * out from under a hardcoded string any more than `.chart-tooltip-slot` above
425
+ * can, so there is no drift risk to design around.
426
+ */
427
+ function chatComposerRecordingSelectorFindings(css, file, source, cssOffset) {
428
+ const findings = [];
429
+ for (const match of css.matchAll(/sui-chat-composer\[\s*recording\b[^\]]*\]/g)) {
430
+ findings.push({
431
+ file,
432
+ line: lineOf(source, cssOffset + match.index),
433
+ reason: 'chat-composer-recording-reflect',
434
+ detail: CHAT_COMPOSER_RECORDING_CSS_DETAIL
435
+ });
436
+ }
437
+ return findings;
438
+ }
439
+ const CHAT_COMPOSER_RECORDING_JS_DETAIL = "reads 'recording' back with getAttribute/hasAttribute on what looks like a chat-composer — it " +
440
+ 'lost reflect: true, so this now returns null/false even while recording is set; read the ' +
441
+ 'el.recording PROPERTY instead. Setting the property still works, and a presence attribute you ' +
442
+ 'set yourself still means true';
443
+ /**
444
+ * `getAttribute('recording')` / `hasAttribute('recording')`, gated on the
445
+ * file also spelling out the literal tag somewhere (markup, or a
446
+ * `querySelector('sui-chat-composer')` string) -- the same same-file textual
447
+ * co-occurrence bound `legacy-back-selector` already relies on rather than
448
+ * tracing which element a variable actually holds, which this format cannot
449
+ * do without a real type checker. A file that reaches a chat-composer through
450
+ * a differently-named variable with no literal tag anywhere in the same file
451
+ * is a false negative this cannot see; a file that mentions the tag once and
452
+ * reads an unrelated element's `recording` attribute elsewhere is the false
453
+ * positive this bound accepts in exchange.
454
+ */
455
+ function chatComposerRecordingAttributeFindings(source, file) {
456
+ if (!source.includes('sui-chat-composer')) {
457
+ return [];
458
+ }
459
+ const findings = [];
460
+ for (const match of source.matchAll(/\.(?:getAttribute|hasAttribute)\(\s*(['"])recording\1\s*\)/g)) {
461
+ findings.push({
462
+ file,
463
+ line: lineOf(source, match.index),
464
+ reason: 'chat-composer-recording-reflect',
465
+ detail: CHAT_COMPOSER_RECORDING_JS_DETAIL
466
+ });
467
+ }
468
+ return findings;
469
+ }
470
+ function styleBlocks(source) {
471
+ const blocks = [];
472
+ for (const match of source.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/g)) {
473
+ // match.index points at the opening <style> tag, while the content starts
474
+ // further in by the tag's own (variable) length -- bridged here or a
475
+ // multiline block reports the tag's line instead of the content's.
476
+ const contentStart = match.index + match[0].indexOf(match[1]);
477
+ blocks.push({ content: match[1], contentStart });
478
+ }
479
+ return blocks;
480
+ }
481
+ /**
482
+ * Checks a `<InputButton>` / `<sui-input-button>` node for the 4.0.0 defect:
483
+ * `mandatory` used to draw only a decorative asterisk, and now also sets
484
+ * native `required`/`aria-required` (`typeof required === 'boolean' ?
485
+ * required : mandatory === true` -- `required` wins whenever it is passed at
486
+ * all, matching its declared `boolean` prop type). A form that relied on
487
+ * submitting this field empty now fails client-side validation.
488
+ */
489
+ function inputButtonFinding(node, displayName, file, source) {
490
+ const attributes = Array.isArray(node.attributes) ? node.attributes : [];
491
+ let hasRequired = false;
492
+ let mandatoryTruthy = false;
493
+ for (const raw of attributes) {
494
+ const attribute = asRecord(raw);
495
+ if (attribute.type === 'SpreadAttribute') {
496
+ const line = lineOf(source, typeof node.start === 'number' ? node.start : 0);
497
+ return {
498
+ file,
499
+ line,
500
+ reason: 'indeterminate-spread',
501
+ detail: `<${displayName}> spreads props, so mandatory/required cannot be read statically — confirm by hand`
502
+ };
503
+ }
504
+ if (attribute.name === 'required') {
505
+ hasRequired = true;
506
+ }
507
+ if (attribute.name === 'mandatory') {
508
+ // A literal `mandatory={false}` behaves identically before and after
509
+ // 4.0.0 (no asterisk then, no `required` now), so it is not a defect.
510
+ mandatoryTruthy = !isFalseLiteral(attribute.value);
511
+ }
512
+ }
513
+ if (hasRequired || !mandatoryTruthy) {
514
+ return null;
515
+ }
516
+ const line = lineOf(source, typeof node.start === 'number' ? node.start : 0);
517
+ return {
518
+ file,
519
+ line,
520
+ reason: 'inputbutton-mandatory',
521
+ detail: `<${displayName}> passes mandatory without required — mandatory now also sets native required/aria-required on the underlying Input, so a form that used to submit this field empty will fail client-side validation`
522
+ };
523
+ }
524
+ const TEXT_FLOW_ELEMENTS = new Set([
525
+ 'p',
526
+ 'span',
527
+ 'li',
528
+ 'td',
529
+ 'label',
530
+ 'h1',
531
+ 'h2',
532
+ 'h3',
533
+ 'h4',
534
+ 'h5',
535
+ 'h6',
536
+ 'a',
537
+ 'button'
538
+ ]);
539
+ function hostDisplayInlineFinding(node, wc, parent, file, source, trigger) {
540
+ const line = lineOf(source, typeof node.start === 'number' ? node.start : 0);
541
+ // The text-flow-parent wording is unchanged from before this reason grew a
542
+ // second trigger, so an existing report naming this shape reads exactly as
543
+ // it always has.
544
+ const detail = trigger === 'text-flow-parent'
545
+ ? `<${wc.tag}> is a direct child of <${parent.name}>, a text-flow element — the wrapper ` +
546
+ `now declares display: ${wc.display} (previously the browser's implicit inline for an ` +
547
+ `unknown element), which can change this line's layout; override with --${wc.tag}-display ` +
548
+ 'if inline is still wanted'
549
+ : `<${wc.tag}> sits beside other inline-level content inside <${parent.name}> — the wrapper ` +
550
+ `now declares display: ${wc.display} (previously the browser's implicit inline for an ` +
551
+ 'unknown element), which can pull it onto its own line instead of alongside its neighbour; ' +
552
+ `override with --${wc.tag}-display if inline is still wanted`;
553
+ return { file, line, reason: 'host-display-inline', detail };
554
+ }
555
+ /**
556
+ * A parent's inline `style` attribute text, when it is expressible as a single
557
+ * plain string literal (`style="..."`) rather than a bound expression or a
558
+ * `style:` directive. Those aren't readable from source alone, so this
559
+ * returns `null` for them -- "unknown", not "absent" -- and callers must
560
+ * treat a `null` as "cannot tell", never as "no style".
561
+ */
562
+ function inlineStyleText(parent) {
563
+ const attributes = Array.isArray(parent.attributes) ? parent.attributes : [];
564
+ for (const raw of attributes) {
565
+ const attribute = asRecord(raw);
566
+ if (attribute.type !== 'Attribute' || attribute.name !== 'style') {
567
+ continue;
568
+ }
569
+ const value = attribute.value;
570
+ if (!Array.isArray(value) || value.length !== 1) {
571
+ continue;
572
+ }
573
+ const text = asRecord(value[0]);
574
+ if (text.type !== 'Text' || typeof text.data !== 'string') {
575
+ continue;
576
+ }
577
+ return text.data;
578
+ }
579
+ return null;
580
+ }
581
+ /**
582
+ * A parent's inline `width`/`flex-basis`, when it is a plain literal string
583
+ * (`style="width: 120px"`) below the chart's new 160px floor. Anything else --
584
+ * a bound style, a `style:width` directive, a percentage or `rem` value, a
585
+ * class-based width -- is not readable from this file alone and is not
586
+ * reported, rather than guessed.
587
+ */
588
+ function inlineNarrowWidth(parent) {
589
+ const style = inlineStyleText(parent);
590
+ if (style === null) {
591
+ return null;
592
+ }
593
+ const match = /(width|flex-basis)\s*:\s*(\d+(?:\.\d+)?)px/.exec(style);
594
+ if (match === null) {
595
+ return null;
596
+ }
597
+ const px = Number(match[2]);
598
+ return px < 160 ? { property: match[1], px } : null;
599
+ }
600
+ /**
601
+ * True when the parent's inline style makes it a flex or grid container. A
602
+ * flex/grid item's OWN `display` is overridden by the layout mode regardless
603
+ * of what it declares (the used value becomes `block` either way per the
604
+ * flex/grid box spec), so a `sui-*` child's new block default changes
605
+ * nothing about whether it shares a line with its siblings there -- the
606
+ * container decides that, not the child. Anything not readable as a plain
607
+ * literal (a class, a bound style, a `style:display` directive) is
608
+ * "unknown", not "false", and this fails open the same way `inlineNarrowWidth`
609
+ * does for a width it cannot read: it returns false, so the sibling check
610
+ * below still runs rather than silently skipping a real positive.
611
+ */
612
+ function parentIsFlexOrGridContainer(parent) {
613
+ const style = inlineStyleText(parent);
614
+ return style !== null && /display\s*:\s*(inline-)?(flex|grid)\b/.test(style);
615
+ }
616
+ function chartMinWidthFinding(node, componentName, parent, file, source) {
617
+ if (parent === null) {
618
+ return null;
619
+ }
620
+ const narrow = inlineNarrowWidth(parent.node);
621
+ if (narrow === null) {
622
+ return null;
623
+ }
624
+ const line = lineOf(source, typeof node.start === 'number' ? node.start : 0);
625
+ return {
626
+ file,
627
+ line,
628
+ reason: 'chart-min-width',
629
+ detail: `<${componentName}> renders inside a <${parent.name}> with an inline ${narrow.property} ` +
630
+ `of ${narrow.px}px — the chart's new 160px min-width (--chart-min-width) will overflow it ` +
631
+ 'instead of shrinking to fit'
632
+ };
633
+ }
634
+ /**
635
+ * Finds everything in one `.svelte` file that a 3.x-or-later change affects,
636
+ * across every reason this module knows about.
637
+ *
638
+ * `context.wcComponents` (from `readWcComponents`, over THIS library's own
639
+ * `src/wc/components`) is what lets `host-display-inline`, `chart-min-width`'s
640
+ * `<sui-*-chart>` form, and the `<sui-input-button>` spelling of
641
+ * `inputbutton-mandatory` recognise a raw custom-element tag; without it
642
+ * (the default) only the named-import forms — `<Toolbar>`, `<InputButton>`,
643
+ * `<PieChart>` and friends — are reachable, since only a resolved import name
644
+ * is available for free.
645
+ */
646
+ export function analyzeSvelte(source, file, context = EMPTY_CONTEXT) {
647
+ const findings = [];
648
+ // Textual and independent of every other check: a stylesheet can select
649
+ // `.chart-tooltip-slot` without this file rendering PieChart/SankeyChart
650
+ // itself (a shared global stylesheet, say), so this must not be gated behind
651
+ // a chart import the way legacy-back-selector below is gated behind Toolbar.
652
+ const blocks = styleBlocks(source);
653
+ for (const block of blocks) {
654
+ findings.push(...chartTooltipSlotFindings(block.content, file, source, block.contentStart));
655
+ findings.push(...chatComposerRecordingSelectorFindings(block.content, file, source, block.contentStart));
656
+ }
657
+ // Same independence, for the JS half of the same defect: a script can read
658
+ // `getAttribute('recording')` off a chat-composer without this file ever
659
+ // importing ChatComposer as a named export (a raw custom element, or an
660
+ // element obtained via `document.querySelector`), so this is not gated
661
+ // behind `context.wcComponents` the way the AST-based checks below are.
662
+ findings.push(...chatComposerRecordingAttributeFindings(source, file));
663
+ const byExport = importedNames(source);
664
+ const toolbarNames = byExport.get('Toolbar') ?? EMPTY_NAMES;
665
+ const inputButtonNames = byExport.get('InputButton') ?? EMPTY_NAMES;
666
+ const chartComponents = context.wcComponents.filter((wc) => wc.component.endsWith('Chart'));
667
+ const chartLocalNames = new Map();
668
+ for (const chart of chartComponents) {
669
+ for (const local of byExport.get(chart.component) ?? EMPTY_NAMES) {
670
+ chartLocalNames.set(local, chart.component);
671
+ }
672
+ }
673
+ const chartTagToComponent = new Map(chartComponents.map((wc) => [wc.tag, wc.component]));
674
+ const hostTagMap = new Map(context.wcComponents.map((wc) => [wc.tag, wc]));
675
+ const inputButtonTag = context.wcComponents.find((wc) => wc.component === 'InputButton')?.tag ?? null;
676
+ const mightUseCustomElement = hostTagMap.size > 0 && source.includes('sui-');
677
+ const needsAst = toolbarNames.size > 0 ||
678
+ inputButtonNames.size > 0 ||
679
+ chartLocalNames.size > 0 ||
680
+ mightUseCustomElement;
681
+ if (!needsAst) {
682
+ return findings;
683
+ }
684
+ let ast;
685
+ try {
686
+ ast = parse(source, { modern: true });
687
+ }
688
+ catch {
689
+ findings.push({
690
+ file,
691
+ line: 1,
692
+ reason: 'indeterminate-spread',
693
+ detail: 'file could not be parsed; review this usage by hand'
694
+ });
695
+ return findings;
696
+ }
697
+ // Toolbar: unchanged traversal, kept on the original parent-blind `walk` --
698
+ // this check never needed to know its parent, and reusing the newer,
699
+ // ancestor-tracking walker here would be a change with nothing to show for it.
700
+ if (toolbarNames.size > 0) {
701
+ walk(ast, (node) => {
702
+ if (node.type !== 'Component' ||
703
+ typeof node.name !== 'string' ||
704
+ !toolbarNames.has(node.name)) {
705
+ return;
706
+ }
707
+ const { explicitlyDisabled, hasOwnIcon, hasSpread } = summarize(node);
708
+ const line = lineOf(source, typeof node.start === 'number' ? node.start : 0);
709
+ if (explicitlyDisabled || hasOwnIcon) {
710
+ return;
711
+ }
712
+ if (hasSpread) {
713
+ findings.push({
714
+ file,
715
+ line,
716
+ reason: 'indeterminate-spread',
717
+ detail: `<${node.name}> spreads props, so showBackButton/backIcon cannot be read statically — confirm by hand`
718
+ });
719
+ return;
720
+ }
721
+ findings.push({
722
+ file,
723
+ line,
724
+ reason: 'default-back-control',
725
+ detail: `<${node.name}> renders the default back control, whose markup changed from <div><img></div> to <button><svg></button>`
726
+ });
727
+ });
728
+ // Styles are checked textually: a selector reaching into the back control's
729
+ // markup breaks regardless of which Toolbar instance it was written for.
730
+ for (const block of blocks) {
731
+ const legacy = /\.back[^{}]*\bimg\b/.exec(block.content);
732
+ if (legacy !== null) {
733
+ findings.push({
734
+ file,
735
+ line: lineOf(source, block.contentStart + legacy.index),
736
+ reason: 'legacy-back-selector',
737
+ detail: 'selector targets an <img> inside the back control; the default control now renders an inline <svg>'
738
+ });
739
+ }
740
+ }
741
+ }
742
+ walkWithParent(ast, null, (node, parent, hasInlineSibling) => {
743
+ if (node.type === 'Component' && typeof node.name === 'string') {
744
+ if (inputButtonNames.has(node.name)) {
745
+ const finding = inputButtonFinding(node, node.name, file, source);
746
+ if (finding !== null) {
747
+ findings.push(finding);
748
+ }
749
+ }
750
+ const chartComponent = chartLocalNames.get(node.name);
751
+ if (typeof chartComponent !== 'undefined') {
752
+ const finding = chartMinWidthFinding(node, node.name, parent, file, source);
753
+ if (finding !== null) {
754
+ findings.push(finding);
755
+ }
756
+ }
757
+ return;
758
+ }
759
+ if (node.type === 'RegularElement' && typeof node.name === 'string') {
760
+ if (node.name === inputButtonTag) {
761
+ const finding = inputButtonFinding(node, node.name, file, source);
762
+ if (finding !== null) {
763
+ findings.push(finding);
764
+ }
765
+ }
766
+ const chartTagComponent = chartTagToComponent.get(node.name);
767
+ if (typeof chartTagComponent !== 'undefined') {
768
+ const finding = chartMinWidthFinding(node, node.name, parent, file, source);
769
+ if (finding !== null) {
770
+ findings.push(finding);
771
+ }
772
+ }
773
+ const hostDefault = hostTagMap.get(node.name);
774
+ if (typeof hostDefault !== 'undefined' && parent !== null) {
775
+ // Two independent triggers, checked in this order so the more
776
+ // specific (and pre-existing) text-flow-parent wording wins when a
777
+ // usage happens to satisfy both, e.g. `<p>Status: <sui-badge/></p>`.
778
+ // The flex/grid guard applies only to the newer sibling trigger --
779
+ // widening it to also suppress the text-flow-parent path would be a
780
+ // behaviour change to a rule this pass was told to leave alone.
781
+ if (TEXT_FLOW_ELEMENTS.has(parent.name)) {
782
+ findings.push(hostDisplayInlineFinding(node, hostDefault, parent, file, source, 'text-flow-parent'));
783
+ }
784
+ else if (hasInlineSibling && !parentIsFlexOrGridContainer(parent.node)) {
785
+ findings.push(hostDisplayInlineFinding(node, hostDefault, parent, file, source, 'inline-sibling'));
786
+ }
787
+ }
788
+ }
789
+ });
790
+ return findings;
791
+ }
792
+ /**
793
+ * `chart-tooltip-slot-selector` and `chat-composer-recording-reflect`'s CSS
794
+ * shape in a standalone `.css` file. Split out from `analyzeSvelte` because a
795
+ * `.css` file has no `<style>` tags to locate first — its whole content
796
+ * already is the CSS. Does not also run `chatComposerRecordingAttributeFindings`:
797
+ * that one is JS-shaped, and a `.css` file has none.
798
+ */
799
+ export function analyzeStylesheet(source, file) {
800
+ return [
801
+ ...chartTooltipSlotFindings(source, file, source, 0),
802
+ ...chatComposerRecordingSelectorFindings(source, file, source, 0)
803
+ ];
804
+ }