@juspay/svelte-ui-components 4.51.0 → 4.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/Animations/tokenizedFly.js +9 -2
  2. package/dist/AreaChart/AreaChart.svelte +41 -13
  3. package/dist/AreaChart/properties.d.ts +12 -0
  4. package/dist/AttachmentChipRow/AttachmentChipRow.svelte +7 -6
  5. package/dist/BarChart/BarChart.svelte +54 -6
  6. package/dist/BarChart/properties.d.ts +12 -0
  7. package/dist/Calendar/Calendar.svelte +124 -51
  8. package/dist/Card/Card.svelte +5 -0
  9. package/dist/ChatSuggestions/ChatSuggestions.svelte +1 -0
  10. package/dist/CheckListItem/CheckListItem.svelte +27 -5
  11. package/dist/ColorPicker/ColorPicker.svelte +40 -9
  12. package/dist/ColorPicker/properties.d.ts +8 -0
  13. package/dist/Combobox/Combobox.svelte +34 -2
  14. package/dist/CommandMenu/CommandMenu.svelte +35 -8
  15. package/dist/CommandMenu/properties.d.ts +6 -0
  16. package/dist/DateRangePicker/DateRangePicker.svelte +5 -5
  17. package/dist/DualAxisBarChart/DualAxisBarChart.svelte +41 -7
  18. package/dist/DualAxisBarChart/properties.d.ts +4 -0
  19. package/dist/FileDropzoneTrigger/FileDropzoneTrigger.svelte +104 -10
  20. package/dist/FileDropzoneTrigger/properties.d.ts +14 -4
  21. package/dist/FileInput/FileInput.svelte +38 -9
  22. package/dist/FileInput/context.d.ts +20 -0
  23. package/dist/FileInput/context.js +8 -0
  24. package/dist/FileInput/properties.d.ts +25 -1
  25. package/dist/FunnelChart/FunnelChart.svelte +20 -1
  26. package/dist/FunnelChart/properties.d.ts +4 -0
  27. package/dist/Gallery/Gallery.svelte +72 -27
  28. package/dist/HITL/HITL.svelte +12 -3
  29. package/dist/LineChart/LineChart.svelte +105 -33
  30. package/dist/LineChart/properties.d.ts +12 -0
  31. package/dist/MediaPlayer/MediaPlayer.svelte +172 -2
  32. package/dist/MediaPlayer/MediaPlayer.svelte.d.ts +1 -1
  33. package/dist/MediaPlayer/properties.d.ts +31 -0
  34. package/dist/Menu/Menu.svelte +6 -4
  35. package/dist/Modal/Modal.svelte +15 -12
  36. package/dist/Modal/properties.d.ts +29 -1
  37. package/dist/Pagination/Pagination.svelte +1 -0
  38. package/dist/PieChart/PieChart.svelte +51 -29
  39. package/dist/PieChart/properties.d.ts +4 -0
  40. package/dist/Pill/Pill.svelte +5 -0
  41. package/dist/Radio/Radio.svelte +11 -2
  42. package/dist/Radio/properties.d.ts +7 -2
  43. package/dist/SankeyChart/SankeyChart.svelte +339 -211
  44. package/dist/SankeyChart/properties.d.ts +4 -0
  45. package/dist/Scroller/Scroller.svelte +236 -4
  46. package/dist/Scroller/properties.d.ts +8 -0
  47. package/dist/Select/Select.svelte +43 -5
  48. package/dist/Select/properties.d.ts +17 -1
  49. package/dist/Sheet/Sheet.svelte +31 -4
  50. package/dist/Sheet/properties.d.ts +6 -0
  51. package/dist/SplitInput/SplitInput.svelte +17 -0
  52. package/dist/SplitInput/field-name.d.ts +29 -0
  53. package/dist/SplitInput/field-name.js +50 -0
  54. package/dist/SplitInput/properties.d.ts +18 -4
  55. package/dist/Status/Status.svelte +24 -12
  56. package/dist/Status/properties.d.ts +11 -0
  57. package/dist/Table/BuiltinCell.svelte +13 -5
  58. package/dist/Table/BuiltinCell.svelte.d.ts +1 -0
  59. package/dist/Table/Table.svelte +78 -20
  60. package/dist/Table/a11y.d.ts +7 -0
  61. package/dist/Table/a11y.js +71 -0
  62. package/dist/Table/cellData.js +3 -0
  63. package/dist/Table/properties.d.ts +13 -5
  64. package/dist/ThemeSwitcher/ThemeSwitcher.svelte +4 -0
  65. package/dist/ThinkingIndicator/ThinkingIndicator.svelte +48 -9
  66. package/dist/Toast/Toast.svelte +40 -4
  67. package/dist/Toggle/Toggle.svelte +2 -0
  68. package/dist/Toggle/properties.d.ts +2 -0
  69. package/dist/_chart/Axis.svelte +10 -0
  70. package/dist/_chart/ChartContainer.svelte +32 -1
  71. package/dist/_chart/ChartTooltip.svelte +18 -3
  72. package/dist/_chart/LabelPlate.svelte +114 -0
  73. package/dist/_chart/LabelPlate.svelte.d.ts +7 -0
  74. package/dist/_chart/a11y.d.ts +29 -0
  75. package/dist/_chart/a11y.js +44 -0
  76. package/dist/_chart/measure.d.ts +2 -0
  77. package/dist/_chart/measure.js +4 -0
  78. package/dist/_chart/tooltipPosition.d.ts +6 -0
  79. package/dist/_chart/tooltipPosition.js +30 -2
  80. package/dist/_chart/types.d.ts +22 -0
  81. package/dist/_interaction/focusable.d.ts +47 -0
  82. package/dist/_interaction/focusable.js +139 -0
  83. package/dist/assets/captions-on.svg +3 -0
  84. package/dist/assets/captions.svg +5 -0
  85. package/dist/index.d.ts +1 -1
  86. package/dist/index.js +1 -1
  87. package/dist/styles/theme-dark.css +6 -1
  88. package/dist/types.d.ts +2 -0
  89. package/dist/utils.d.ts +18 -2
  90. package/dist/utils.js +73 -28
  91. package/dist-codemod/codemod/migrate-audit.js +9 -7
  92. package/dist-codemod/codemod/migration-assets/legacy-palette.css +9 -1
  93. package/dist-codemod/codemod/migration-assets-layout.js +61 -0
  94. package/dist-wc/index.js +5653 -4148
  95. package/package.json +4 -2
@@ -9,6 +9,34 @@ export function computeTooltipPosition(opts) {
9
9
  const { tooltip, container } = opts;
10
10
  const maxLeft = container.width - tooltip.width;
11
11
  const maxTop = container.height - tooltip.height;
12
+ const finish = (left, top) => {
13
+ const preferred = { left: clamp(left, maxLeft), top: clamp(top, maxTop) };
14
+ const labels = opts.avoidRects ?? [];
15
+ const clear = (p) => labels.every((r) => p.left + tooltip.width <= r.left ||
16
+ p.left >= r.left + r.width ||
17
+ p.top + tooltip.height <= r.top ||
18
+ p.top >= r.top + r.height);
19
+ if (clear(preferred)) {
20
+ return preferred;
21
+ }
22
+ // Keep labels visible when the usual point/cursor offset intersects them.
23
+ // Try the nearby edges first, including combinations for crowded plots.
24
+ const candidates = [];
25
+ for (const r of labels) {
26
+ const xs = [preferred.left, r.left - tooltip.width - offset, r.left + r.width + offset];
27
+ const ys = [preferred.top, r.top - tooltip.height - offset, r.top + r.height + offset];
28
+ for (const x of xs) {
29
+ for (const y of ys) {
30
+ if (x >= 0 && y >= 0 && x <= maxLeft && y <= maxTop) {
31
+ candidates.push({ left: x, top: y });
32
+ }
33
+ }
34
+ }
35
+ }
36
+ const distance = (p) => (p.left - preferred.left) ** 2 + (p.top - preferred.top) ** 2;
37
+ candidates.sort((a, b) => distance(a) - distance(b));
38
+ return candidates.find(clear) ?? preferred;
39
+ };
12
40
  const a = opts.anchor ?? null;
13
41
  if (a !== null) {
14
42
  let left;
@@ -33,7 +61,7 @@ export function computeTooltipPosition(opts) {
33
61
  left = a.x + offset;
34
62
  }
35
63
  }
36
- return { left: clamp(left, maxLeft), top: clamp(top, maxTop) };
64
+ return finish(left, top);
37
65
  }
38
66
  const mouseX = opts.mouseX ?? 0;
39
67
  const mouseY = opts.mouseY ?? 0;
@@ -45,5 +73,5 @@ export function computeTooltipPosition(opts) {
45
73
  if (top + tooltip.height > container.height) {
46
74
  top = mouseY - tooltip.height - offset;
47
75
  }
48
- return { left: clamp(left, maxLeft), top: clamp(top, maxTop) };
76
+ return finish(left, top);
49
77
  }
@@ -149,6 +149,28 @@ export type ChartContainerProperties = {
149
149
  maxHeight?: number;
150
150
  testId?: string;
151
151
  classes?: string;
152
+ /**
153
+ * Accessible name of the chart drawing (`aria-label` on the `<svg>`). Without
154
+ * one the drawing is exposed with no name at all, which axe reports as
155
+ * `svg-img-alt` and which leaves a screen-reader user unable to tell which
156
+ * chart a data point belongs to.
157
+ */
158
+ ariaLabel?: string;
159
+ /**
160
+ * Optional longer description of the chart. Rendered as an SVG `<desc>` whose
161
+ * instance-scoped id is referenced by `aria-describedby`, so it is announced
162
+ * after the name wherever `aria-describedby` is honoured.
163
+ */
164
+ ariaDescription?: string;
165
+ /**
166
+ * True when the drawing contains focusable or clickable marks (bars, points,
167
+ * slices). An interactive drawing is exposed as `role="group"`, not
168
+ * `role="img"`: ARIA makes an image's children presentational, so marks left
169
+ * under an image either vanish from assistive technology or are announced
170
+ * inconsistently between browsers. A static drawing stays a named image.
171
+ * Defaults to false, which keeps every existing caller's `role="img"`.
172
+ */
173
+ interactive?: boolean;
152
174
  children: Snippet;
153
175
  };
154
176
  export type AxisProperties = {
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Finds interactive elements the way a keyboard user meets them: through `<slot>`
3
+ * projection and into open shadow roots.
4
+ *
5
+ * `querySelector` cannot do either. Inside a `sui-*` element the content a consumer
6
+ * slotted in lives in the light DOM, so a query rooted in the shadow tree never sees
7
+ * it; and a control rendered by a nested `sui-*` element sits one shadow root further
8
+ * down. Menu needs this to return focus to a slotted trigger, and Scroller needs it to
9
+ * decide whether its content already offers a Tab stop. Both answered "none" through the
10
+ * custom-element build while answering correctly in the Svelte one.
11
+ *
12
+ * Pure DOM reads with no Svelte dependency, so it stays unit-testable under jsdom --
13
+ * which is also why visibility uses `checkVisibility` only when the engine has it
14
+ * rather than layout measurements jsdom cannot provide.
15
+ */
16
+ export type FindInteractiveOptions = {
17
+ /** Also test `root` itself, not only what it contains. */
18
+ readonly includeRoot?: boolean;
19
+ /**
20
+ * Only elements a Tab press reaches. Off by default so a control carrying
21
+ * `tabindex="-1"` still counts as somewhere focus can be put programmatically.
22
+ */
23
+ readonly tabbableOnly?: boolean;
24
+ /**
25
+ * Also yield elements that are not rendered right now (`display: none`, `hidden`, inert).
26
+ * For a caller that wants to be told when one of them is shown or hidden again.
27
+ */
28
+ readonly includeHidden?: boolean;
29
+ };
30
+ /** Inert applies through shadow hosts and slot projection, beyond `closest()`'s tree. */
31
+ export declare function composedParent(el: Element): Element | null;
32
+ /**
33
+ * Every interactive element at or below `root`, in tree order. When `root` is itself a
34
+ * host, that includes the controls its own shadow tree renders -- the case of a
35
+ * `<sui-button>` handed over as a trigger.
36
+ */
37
+ export declare function interactiveElements(root: Element | ShadowRoot | DocumentFragment, options?: FindInteractiveOptions): Generator<HTMLElement>;
38
+ /** The first interactive element at or below `root` in tree order, or `null`. */
39
+ export declare function findInteractive(root: Element | ShadowRoot | DocumentFragment, options?: FindInteractiveOptions): HTMLElement | null;
40
+ /** Whether anything beneath `root` is reachable with Tab. */
41
+ export declare function hasTabbableContent(root: Element | ShadowRoot | DocumentFragment): boolean;
42
+ /**
43
+ * The direct content of `root` as the layout sees it: a `<slot>` stands in for the
44
+ * elements assigned to it. These are the nodes whose size and attributes decide whether
45
+ * `root`'s scrollable area, or its Tab order, changes.
46
+ */
47
+ export declare function contentElements(root: Element): Element[];
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Finds interactive elements the way a keyboard user meets them: through `<slot>`
3
+ * projection and into open shadow roots.
4
+ *
5
+ * `querySelector` cannot do either. Inside a `sui-*` element the content a consumer
6
+ * slotted in lives in the light DOM, so a query rooted in the shadow tree never sees
7
+ * it; and a control rendered by a nested `sui-*` element sits one shadow root further
8
+ * down. Menu needs this to return focus to a slotted trigger, and Scroller needs it to
9
+ * decide whether its content already offers a Tab stop. Both answered "none" through the
10
+ * custom-element build while answering correctly in the Svelte one.
11
+ *
12
+ * Pure DOM reads with no Svelte dependency, so it stays unit-testable under jsdom --
13
+ * which is also why visibility uses `checkVisibility` only when the engine has it
14
+ * rather than layout measurements jsdom cannot provide.
15
+ */
16
+ const NATIVE_CONTROL_SELECTOR = [
17
+ 'a[href]',
18
+ 'button:not([disabled])',
19
+ 'input:not([disabled]):not([type="hidden"])',
20
+ 'select:not([disabled])',
21
+ 'textarea:not([disabled])',
22
+ 'summary',
23
+ 'audio[controls]',
24
+ 'video[controls]',
25
+ 'iframe',
26
+ '[contenteditable=""]',
27
+ '[contenteditable="true"]'
28
+ ].join(', ');
29
+ /** Inert applies through shadow hosts and slot projection, beyond `closest()`'s tree. */
30
+ export function composedParent(el) {
31
+ const slot = el instanceof HTMLElement || el instanceof SVGElement ? el.assignedSlot : null;
32
+ if (slot !== null) {
33
+ return slot;
34
+ }
35
+ const root = el.getRootNode();
36
+ return el.parentElement ?? (root instanceof ShadowRoot ? root.host : null);
37
+ }
38
+ function isRendered(el) {
39
+ if (el.hasAttribute('hidden')) {
40
+ return false;
41
+ }
42
+ for (let ancestor = el; ancestor !== null; ancestor = composedParent(ancestor)) {
43
+ if (ancestor.hasAttribute('inert')) {
44
+ return false;
45
+ }
46
+ }
47
+ return typeof el.checkVisibility === 'function'
48
+ ? el.checkVisibility({ visibilityProperty: true })
49
+ : true;
50
+ }
51
+ function interactiveElement(el, tabbableOnly, includeHidden) {
52
+ if (!(el instanceof HTMLElement)) {
53
+ return null;
54
+ }
55
+ // Native disabledness includes a disabled fieldset, except its first legend.
56
+ // Neither the own `disabled` property nor authored tabindex describes that
57
+ // inherited state. The browser's predicate also handles the legend exception.
58
+ if (el.matches(':disabled')) {
59
+ return null;
60
+ }
61
+ const tabbable = el.tabIndex >= 0;
62
+ // A native control keeps `tabindex="-1"` focusable from script. A bare `[tabindex]`
63
+ // is only a control because of that attribute, so a negative value takes it out.
64
+ const interactive = el.matches(NATIVE_CONTROL_SELECTOR)
65
+ ? !tabbableOnly || tabbable
66
+ : el.hasAttribute('tabindex') && tabbable;
67
+ return interactive && (includeHidden || isRendered(el)) ? el : null;
68
+ }
69
+ function* childrenOf(node) {
70
+ if (node instanceof HTMLSlotElement) {
71
+ const assigned = node.assignedElements({ flatten: true });
72
+ // An unassigned slot renders its fallback content, which is its own children.
73
+ yield* assigned.length > 0 ? assigned : Array.from(node.children);
74
+ return;
75
+ }
76
+ yield* Array.from(node.children);
77
+ }
78
+ /**
79
+ * What an element renders inside itself: its shadow tree when it hosts one, otherwise
80
+ * its own children. A host's light children are not rendered directly -- they appear
81
+ * wherever the shadow tree's `<slot>`s place them, which `childrenOf` resolves -- so
82
+ * walking the shadow tree alone visits each of them once and in the order they render.
83
+ */
84
+ function renderedTreeOf(node) {
85
+ return node instanceof Element && node.shadowRoot !== null ? node.shadowRoot : node;
86
+ }
87
+ function* walk(node) {
88
+ for (const child of childrenOf(node)) {
89
+ yield child;
90
+ yield* walk(renderedTreeOf(child));
91
+ }
92
+ }
93
+ /**
94
+ * Every interactive element at or below `root`, in tree order. When `root` is itself a
95
+ * host, that includes the controls its own shadow tree renders -- the case of a
96
+ * `<sui-button>` handed over as a trigger.
97
+ */
98
+ export function* interactiveElements(root, options = {}) {
99
+ const tabbableOnly = options.tabbableOnly === true;
100
+ const includeHidden = options.includeHidden === true;
101
+ if (options.includeRoot === true && root instanceof Element) {
102
+ const self = interactiveElement(root, tabbableOnly, includeHidden);
103
+ if (self !== null) {
104
+ yield self;
105
+ }
106
+ }
107
+ for (const el of walk(renderedTreeOf(root))) {
108
+ const found = interactiveElement(el, tabbableOnly, includeHidden);
109
+ if (found !== null) {
110
+ yield found;
111
+ }
112
+ }
113
+ }
114
+ /** The first interactive element at or below `root` in tree order, or `null`. */
115
+ export function findInteractive(root, options = {}) {
116
+ const first = interactiveElements(root, options).next();
117
+ return first.done === true ? null : first.value;
118
+ }
119
+ /** Whether anything beneath `root` is reachable with Tab. */
120
+ export function hasTabbableContent(root) {
121
+ return findInteractive(root, { tabbableOnly: true }) !== null;
122
+ }
123
+ /**
124
+ * The direct content of `root` as the layout sees it: a `<slot>` stands in for the
125
+ * elements assigned to it. These are the nodes whose size and attributes decide whether
126
+ * `root`'s scrollable area, or its Tab order, changes.
127
+ */
128
+ export function contentElements(root) {
129
+ const out = [];
130
+ for (const child of Array.from(root.children)) {
131
+ if (child instanceof HTMLSlotElement) {
132
+ out.push(...childrenOf(child));
133
+ }
134
+ else {
135
+ out.push(child);
136
+ }
137
+ }
138
+ return out;
139
+ }
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M5 5h14a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2zM11.121 9.879A3 3 0 1 0 11.121 14.121L9.99 12.99A1.4 1.4 0 1 1 9.99 11.01zM17.121 9.879A3 3 0 1 0 17.121 14.121L15.99 12.99A1.4 1.4 0 1 1 15.99 11.01z" />
3
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
2
+ <rect x="3" y="5" width="18" height="14" rx="2" />
3
+ <path d="M10.5 10.5a2 2 0 1 0 0 3" />
4
+ <path d="M16.5 10.5a2 2 0 1 0 0 3" />
5
+ </svg>
package/dist/index.d.ts CHANGED
@@ -228,7 +228,7 @@ export type * from './MediaPlayer/properties';
228
228
  export type * from './MediaUpload/properties';
229
229
  export type * from './Gallery/properties';
230
230
  export { createSoundKit } from './SoundKit/SoundKit';
231
- export { validateInput, lockBodyScroll, unlockBodyScroll, prefersReducedMotion } from './utils';
231
+ export { validateInput, lockBodyScroll, unlockBodyScroll, acquireScrollLock, prefersReducedMotion } from './utils';
232
232
  export { reducedMotion } from './reduced-motion.svelte';
233
233
  export type { ReducedMotionPreference } from './reduced-motion.svelte';
234
234
  export { TEL_PRESETS, TEL_PRESET_IN_MOBILE } from './utils';
package/dist/index.js CHANGED
@@ -123,7 +123,7 @@ export { createSoundKit } from './SoundKit/SoundKit';
123
123
  // applies just as much across the boundary. A consumer driving motion from JavaScript
124
124
  // -- a canvas, a chart, an imperative animation -- cannot reach the preference through
125
125
  // a stylesheet, so leaving this unexported made every such consumer write it again.
126
- export { validateInput, lockBodyScroll, unlockBodyScroll, prefersReducedMotion } from './utils';
126
+ export { validateInput, lockBodyScroll, unlockBodyScroll, acquireScrollLock, prefersReducedMotion } from './utils';
127
127
  // The reactive counterpart to `prefersReducedMotion()` above: a consumer
128
128
  // building their own component with runes needs a value that stays current
129
129
  // inside a `$derived`, which a plain function call cannot give them, for the
@@ -201,8 +201,11 @@
201
201
  --hitl-halted-color: #fbbf24;
202
202
  --hitl-countdown-filter: brightness(1.6);
203
203
 
204
- /* ThinkingIndicator */
204
+ /* ThinkingIndicator. The shimmer gradient is built from the label colour plus this
205
+ highlight, so both stops of the sweep stay readable on the dark grounds
206
+ (#9ca3af / #e5e7eb are 5.1-7.5:1 and 10.5-15.4:1 on #0f0f17..#2f2f45). */
205
207
  --thinking-indicator-label-color: #9ca3af;
208
+ --thinking-indicator-shimmer-highlight: #e5e7eb;
206
209
  --thinking-indicator-detail-color: #9ca3af;
207
210
  --thinking-indicator-arrow-color: #9ca3af;
208
211
  --thinking-indicator-border-bottom: 1px solid #2a2a3c;
@@ -212,6 +215,7 @@
212
215
  --thinking-indicator-chip-background: #1e1e2e;
213
216
  --thinking-indicator-chip-border: 1px solid #2a2a3c;
214
217
  --thinking-indicator-chip-color: #9ca3af;
218
+ --thinking-indicator-chip-shimmer-highlight: #f3f4f6;
215
219
  --thinking-indicator-chip-box-shadow: 0 6px 20px rgb(0 0 0 / 40%);
216
220
 
217
221
  /* AttachmentChipRow */
@@ -710,6 +714,7 @@
710
714
  --sankey-label-color: #d1d5db;
711
715
 
712
716
  /* Scroller */
717
+ --scroller-focus-outline: 2px solid #818cf8;
713
718
  --scroller-arrow-background: #1e1e2e;
714
719
  --scroller-arrow-color: #d1d5db;
715
720
  --scroller-arrow-hover-background: #252535;
package/dist/types.d.ts CHANGED
@@ -19,6 +19,8 @@ import type { HTMLAttributes } from 'svelte/elements';
19
19
  */
20
20
  export type InputDataType = 'text' | 'tel' | 'password' | 'email' | 'number' | 'time' | 'date' | 'search' | 'url';
21
21
  export type ModalTransition = 'IN' | 'ALL';
22
+ /** An overlay's owned scroll element. A null value/getter result disables locking. */
23
+ export type ScrollContainer = HTMLElement | (() => HTMLElement | null) | null;
22
24
  /**
23
25
  * @name CustomValidator
24
26
  * @description Function type for taking input parameter and returning a boolean denoting if the value is valid or not
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CustomValidator, Hsv, Hsl, InputDataType, Rgb, ValidationState } from './types';
1
+ import type { CustomValidator, Hsv, Hsl, InputDataType, Rgb, ValidationState, ScrollContainer } from './types';
2
2
  /**
3
3
  * A phone-number shape a consumer opts into, as the pair of patterns the
4
4
  * in-progress/committed distinction needs.
@@ -93,9 +93,25 @@ export declare function prefersReducedMotion(): boolean;
93
93
  * dismissal must be acted on at once, and the repeats it guards against are the
94
94
  * ones to discard.
95
95
  *
96
+ * `delay` may be a function, read on every call, for a caller whose delay can
97
+ * change after the debouncer exists. The window is then measured against the
98
+ * delay in force now, while `lastCallTime` stays the one debouncer's own: a
99
+ * caller rebuilding the debouncer to pick up a new delay would also forget when
100
+ * it last fired, and let through the very repeat it exists to drop.
101
+ *
96
102
  * Not exported from the package; renaming it is a free change whenever the
97
103
  * mismatch stops being worth the note.
98
104
  */
99
- export declare function createDebouncer(delay: number): <T extends unknown[]>(callback: (...args: T) => void, ...args: T) => void;
105
+ export declare function createDebouncer(delay: number | (() => number)): <T extends unknown[]>(callback: (...args: T) => void, ...args: T) => void;
106
+ /**
107
+ * Acquire one hold and return its idempotent release function. Only overflow
108
+ * declarations are touched; their original values, priorities and order are
109
+ * restored after the final hold. Scroll position and unrelated styles stay intact.
110
+ * A null container is a no-op, including during SSR.
111
+ */
112
+ export declare function acquireScrollLock(container?: HTMLElement | null): () => void;
113
+ /** Explicit ownership never falls back to body, even if the getter returns null. */
114
+ export declare function resolveScrollContainer(node: HTMLElement | null, container?: ScrollContainer): HTMLElement | null;
115
+ /** Backwards-compatible body helpers; every acquired hold needs one unlock. */
100
116
  export declare function lockBodyScroll(): void;
101
117
  export declare function unlockBodyScroll(): void;
package/dist/utils.js CHANGED
@@ -352,6 +352,12 @@ export function prefersReducedMotion() {
352
352
  * dismissal must be acted on at once, and the repeats it guards against are the
353
353
  * ones to discard.
354
354
  *
355
+ * `delay` may be a function, read on every call, for a caller whose delay can
356
+ * change after the debouncer exists. The window is then measured against the
357
+ * delay in force now, while `lastCallTime` stays the one debouncer's own: a
358
+ * caller rebuilding the debouncer to pick up a new delay would also forget when
359
+ * it last fired, and let through the very repeat it exists to drop.
360
+ *
355
361
  * Not exported from the package; renaming it is a free change whenever the
356
362
  * mismatch stops being worth the note.
357
363
  */
@@ -359,44 +365,83 @@ export function createDebouncer(delay) {
359
365
  let lastCallTime = 0;
360
366
  return function (callback, ...args) {
361
367
  const now = Date.now();
362
- if (now - lastCallTime > delay) {
368
+ const currentDelay = typeof delay === 'function' ? delay() : delay;
369
+ if (now - lastCallTime > currentDelay) {
363
370
  lastCallTime = now;
364
371
  callback(...args);
365
372
  }
366
373
  };
367
374
  }
375
+ /** Per-container locks shared by overlays and the legacy body helpers. */
376
+ const scrollLocks = new WeakMap();
377
+ const bodyScrollReleases = [];
368
378
  /**
369
- * Body scroll lock, reference counted.
370
- *
371
- * Modal, Sheet and CommandMenu each hide body overflow while they are open. Doing
372
- * that with a bare assignment means the FIRST of them to unmount restores
373
- * scrolling for all of them, so closing a confirmation dialog inside a still-open
374
- * modal silently lets the page behind it scroll again. Counting the holders keeps
375
- * the lock until the last one releases it.
376
- *
377
- * Callers must pair every lock with exactly one unlock.
379
+ * Acquire one hold and return its idempotent release function. Only overflow
380
+ * declarations are touched; their original values, priorities and order are
381
+ * restored after the final hold. Scroll position and unrelated styles stay intact.
382
+ * A null container is a no-op, including during SSR.
378
383
  */
379
- let bodyScrollLockCount = 0;
380
- // Whatever inline overflow the host page had set before the first lock, so the last unlock
381
- // hands it back instead of clearing it: a host running `overflow: clip` on its body keeps it.
382
- let bodyScrollPreviousOverflow = '';
383
- export function lockBodyScroll() {
384
- if (typeof document === 'undefined') {
385
- return;
384
+ export function acquireScrollLock(container = typeof document === 'undefined' ? null : document.body) {
385
+ if (container === null) {
386
+ return () => { };
386
387
  }
387
- if (bodyScrollLockCount === 0) {
388
- bodyScrollPreviousOverflow = document.body.style.overflow;
389
- document.body.style.overflow = 'hidden';
388
+ let lock = scrollLocks.get(container);
389
+ if (!lock) {
390
+ const overflow = [];
391
+ for (let index = 0; index < container.style.length; index += 1) {
392
+ const name = container.style.item(index);
393
+ if (name === 'overflow' || name === 'overflow-x' || name === 'overflow-y') {
394
+ overflow.push([
395
+ name,
396
+ container.style.getPropertyValue(name),
397
+ container.style.getPropertyPriority(name)
398
+ ]);
399
+ }
400
+ }
401
+ lock = { count: 0, overflow };
402
+ scrollLocks.set(container, lock);
403
+ container.style.setProperty('overflow', 'hidden', 'important');
390
404
  }
391
- bodyScrollLockCount += 1;
405
+ lock.count += 1;
406
+ const heldLock = lock;
407
+ let released = false;
408
+ return () => {
409
+ if (released) {
410
+ return;
411
+ }
412
+ released = true;
413
+ heldLock.count -= 1;
414
+ if (heldLock.count === 0) {
415
+ container.style.removeProperty('overflow');
416
+ container.style.removeProperty('overflow-x');
417
+ container.style.removeProperty('overflow-y');
418
+ for (const [name, value, priority] of heldLock.overflow) {
419
+ container.style.setProperty(name, value, priority);
420
+ }
421
+ scrollLocks.delete(container);
422
+ }
423
+ };
392
424
  }
393
- export function unlockBodyScroll() {
394
- if (typeof document === 'undefined') {
395
- return;
425
+ /** Explicit ownership never falls back to body, even if the getter returns null. */
426
+ export function resolveScrollContainer(node, container) {
427
+ const resolved = typeof container === 'function' ? container() : container;
428
+ if (typeof resolved === 'object') {
429
+ return resolved !== null && resolved.nodeType === 1 && typeof resolved.style === 'object'
430
+ ? resolved
431
+ : null;
396
432
  }
397
- bodyScrollLockCount = Math.max(0, bodyScrollLockCount - 1);
398
- if (bodyScrollLockCount === 0) {
399
- document.body.style.overflow = bodyScrollPreviousOverflow;
400
- bodyScrollPreviousOverflow = '';
433
+ // Invalid JavaScript/custom-element values are never permission to lock body.
434
+ if (typeof container !== 'undefined') {
435
+ return null;
401
436
  }
437
+ return node?.ownerDocument.body ?? (typeof document === 'undefined' ? null : document.body);
438
+ }
439
+ /** Backwards-compatible body helpers; every acquired hold needs one unlock. */
440
+ export function lockBodyScroll() {
441
+ if (typeof document !== 'undefined') {
442
+ bodyScrollReleases.push(acquireScrollLock(document.body));
443
+ }
444
+ }
445
+ export function unlockBodyScroll() {
446
+ bodyScrollReleases.pop()?.();
402
447
  }
@@ -5,6 +5,8 @@ import { parseArgs } from 'node:util';
5
5
  import { analyzeStylesheet, analyzeSvelte, LIBRARY } from "../migrate/analyze.js";
6
6
  import { planSelectorRewrites } from "../migrate/chart-tooltip-selector.js";
7
7
  import { SKIPPED_DIRECTORIES } from "./cli.js";
8
+ import { LEGACY_DISPLAY_CSS_FILENAME, LEGACY_PALETTE_CSS_FILENAME, WC_DISPLAY_FILENAME, migrationAssetsDirBeside } from "./migration-assets-layout.js";
9
+ export { LEGACY_DISPLAY_CSS_FILENAME, LEGACY_PALETTE_CSS_FILENAME };
8
10
  /**
9
11
  * Runs the 4.28.0 breaking-change audit `npm run migrate` runs, from the
10
12
  * PUBLISHED `sui-codemod` binary against a consumer project that only has
@@ -28,15 +30,15 @@ import { SKIPPED_DIRECTORIES } from "./cli.js";
28
30
  * never otherwise reach.
29
31
  */
30
32
  // ------------------------------------------------------------- wc-display.json
31
- const MIGRATION_ASSETS_DIRNAME = 'migration-assets';
32
- const WC_DISPLAY_FILENAME = 'wc-display.json';
33
- export const LEGACY_DISPLAY_CSS_FILENAME = 'legacy-display.css';
34
- export const LEGACY_PALETTE_CSS_FILENAME = 'legacy-palette.css';
35
33
  /**
36
34
  * The directory `generate-migration-assets.ts` writes into, resolved off
37
35
  * THIS module's own compiled location rather than `process.cwd()` -- so it
38
- * still finds `dist-codemod/migration-assets` when this module has been
39
- * compiled to `dist-codemod/migrate-audit.js` and invoked from anywhere.
36
+ * still finds `dist-codemod/codemod/migration-assets` when this module has
37
+ * been compiled to `dist-codemod/codemod/migrate-audit.js` and invoked from
38
+ * anywhere. The directory name and the "beside the compiled module" rule come
39
+ * from `migration-assets-layout.ts`, the one place the generator, this lookup
40
+ * and the real-artifact tests all read it from.
41
+ *
40
42
  * Run against the uncompiled `.ts` source (as the tests below do without an
41
43
  * explicit `assetsDir` override) it resolves to a sibling directory that
42
44
  * does not exist -- which is the correct answer, not a bug: this module
@@ -45,7 +47,7 @@ export const LEGACY_PALETTE_CSS_FILENAME = 'legacy-palette.css';
45
47
  * than a guess.
46
48
  */
47
49
  function defaultAssetsDir() {
48
- return join(dirname(fileURLToPath(import.meta.url)), MIGRATION_ASSETS_DIRNAME);
50
+ return migrationAssetsDirBeside(dirname(fileURLToPath(import.meta.url)));
49
51
  }
50
52
  /** Same no-assertion narrowing `analyze.ts`'s own `asRecord` uses: copies known-present keys onto a fresh object rather than casting. */
51
53
  function asRecord(value) {
@@ -12,11 +12,12 @@
12
12
  * one property at a time, not to be a permanent fixture in a consumer
13
13
  * build.
14
14
  *
15
- * 54 properties pinned below. 9 more could not be -- see the note at the end.
15
+ * 62 properties pinned below. 9 more could not be -- see the note at the end.
16
16
  *
17
17
  * Generated by scripts/migrate/legacy-palette.ts -- do not hand edit.
18
18
  */
19
19
  :root { --banner-link-color: #0099ff; } /* was the 4.27.x default */
20
+ :root { --button-width: fit-content; } /* was the 4.27.x default */
20
21
  :root { --calendar-day-name-color: #999999; } /* was the 4.27.x default */
21
22
  :root { --calendar-disabled-color: #cccccc; } /* was the 4.27.x default */
22
23
  :root { --calendar-outside-month-color: #cccccc; } /* was the 4.27.x default */
@@ -26,6 +27,7 @@
26
27
  :root { --delta-indicator-negative-color: #e5484d; } /* was the 4.27.x default */
27
28
  :root { --delta-indicator-neutral-color: #8a8a8a; } /* was the 4.27.x default */
28
29
  :root { --delta-indicator-positive-color: #1a9d6f; } /* was the 4.27.x default */
30
+ :root { --drp-apply-background: currentColor; } /* was the 4.27.x default */
29
31
  :root { --drp-panel-background: inherit; } /* was the 4.27.x default */
30
32
  :root { --empty-state-description-opacity: 0.6; } /* was the 4.27.x default */
31
33
  :root { --field-group-label-color: #9ca3af; } /* was the 4.27.x default */
@@ -45,6 +47,8 @@
45
47
  :root { --pagination-button-color: #3a4550; } /* was the 4.27.x default */
46
48
  :root { --pagination-load-more-color: #3a4550; } /* was the 4.27.x default */
47
49
  :root { --pill-tone-muted-color: #6b7280; } /* was the 4.27.x default */
50
+ :root { --sankey-col-label-color: #666; } /* was the 4.27.x default */
51
+ :root { --sankey-label-color: #333; } /* was the 4.27.x default */
48
52
  :root { --select-placeholder-color: #999999; } /* was the 4.27.x default */
49
53
  :root { --separator-margin: 0px 12px 0px 12px; } /* was the 4.27.x default */
50
54
  :root { --statcard-breakdown-label-color: #9ca3af; } /* was the 4.27.x default */
@@ -66,6 +70,10 @@
66
70
  :root { --task-list-secondary-color: #9a9a9a; } /* was the 4.27.x default */
67
71
  :root { --thinking-indicator-detail-color: #bebebe; } /* was the 4.27.x default */
68
72
  :root { --thinking-indicator-elapsed-color: #9a9a9a; } /* was the 4.27.x default */
73
+ :root { --thinking-indicator-label-color: #858585; } /* was the 4.27.x default */
74
+ :root { --thinking-indicator-trace-added-color: #1f7a5f; } /* was the 4.27.x default */
75
+ :root { --thinking-indicator-trace-more-color: #9a9a9a; } /* was the 4.27.x default */
76
+ :root { --thinking-indicator-trace-removed-color: #c93f38; } /* was the 4.27.x default */
69
77
  :root { --thinking-indicator-trace-secondary-color: #9a9a9a; } /* was the 4.27.x default */
70
78
  :root { --tool-call-log-error-color: #c93f38; } /* was the 4.27.x default */
71
79
  :root { --tool-call-log-meta-color: #9a9a9a; } /* was the 4.27.x default */
@@ -0,0 +1,61 @@
1
+ import { join } from 'node:path';
2
+ /**
3
+ * Where the migration assets live in the BUILT package, in one place.
4
+ *
5
+ * Three different pieces of code have to agree on this directory and, before
6
+ * this module, each spelled it out on its own: the generator that writes it
7
+ * (`generate-migration-assets.ts`), the compiled reader that finds it
8
+ * (`migrate-audit.ts`) and the unit tests that check the real artifact. When
9
+ * `tsconfig.build.json` gained a second source tree the compiled layout moved
10
+ * from `dist-codemod/migration-assets` to `dist-codemod/codemod/migration-assets`;
11
+ * the generator and the reader were updated and the test's hand-written copy of
12
+ * the old path was not, so its "run build:codemod first" skip kept firing after
13
+ * a complete build and the real-artifact checks silently stopped running.
14
+ *
15
+ * The assets sit BESIDE the compiled `migrate-audit.js` and are resolved off
16
+ * its own `import.meta.url`, so the layout is "a `migration-assets` directory
17
+ * next to the compiled module", not an absolute location:
18
+ *
19
+ * dist-codemod/ <- tsconfig.build.json `outDir`
20
+ * codemod/ <- `scripts/codemod`, relative to its `rootDir` (`scripts`)
21
+ * bin.js migrate-audit.js ...
22
+ * migration-assets/ <- written by generate-migration-assets.ts
23
+ * wc-display.json legacy-display.css legacy-palette.css
24
+ *
25
+ * `migration-assets-layout.test.ts` pins these constants to `tsconfig.build.json`
26
+ * (`outDir`, `rootDir`) and to package.json (`bin`, `files`) so a change to any
27
+ * of them fails there instead of being discovered as a skipped test.
28
+ *
29
+ * Compiled into `dist-codemod` (it is imported by `migrate-audit.ts`) and also
30
+ * imported by the build-only generator, which node runs with
31
+ * `--experimental-strip-types`: keep it to erasable TypeScript.
32
+ */
33
+ /** Package-root directory `build:codemod` emits into; `tsconfig.build.json` `outDir`. */
34
+ export const DIST_CODEMOD_DIRNAME = 'dist-codemod';
35
+ /** `scripts/codemod` as it appears under `DIST_CODEMOD_DIRNAME`, because `rootDir` is `scripts`. */
36
+ export const COMPILED_CODEMOD_DIRNAME = 'codemod';
37
+ export const MIGRATION_ASSETS_DIRNAME = 'migration-assets';
38
+ export const WC_DISPLAY_FILENAME = 'wc-display.json';
39
+ export const LEGACY_DISPLAY_CSS_FILENAME = 'legacy-display.css';
40
+ export const LEGACY_PALETTE_CSS_FILENAME = 'legacy-palette.css';
41
+ /** Everything the generator writes into the assets directory. */
42
+ export const MIGRATION_ASSET_FILENAMES = [
43
+ WC_DISPLAY_FILENAME,
44
+ LEGACY_DISPLAY_CSS_FILENAME,
45
+ LEGACY_PALETTE_CSS_FILENAME
46
+ ];
47
+ /**
48
+ * The assets directory that sits beside a compiled module. The runtime lookup:
49
+ * `migrate-audit.ts` passes its own directory.
50
+ */
51
+ export function migrationAssetsDirBeside(compiledModuleDir) {
52
+ return join(compiledModuleDir, MIGRATION_ASSETS_DIRNAME);
53
+ }
54
+ /**
55
+ * The assets directory a build of the package rooted at `packageRoot` produces
56
+ * and the published tarball ships. The generator's default `--out`, and what
57
+ * the real-artifact tests read.
58
+ */
59
+ export function builtMigrationAssetsDir(packageRoot) {
60
+ return migrationAssetsDirBeside(join(packageRoot, DIST_CODEMOD_DIRNAME, COMPILED_CODEMOD_DIRNAME));
61
+ }