@juspay/svelte-ui-components 4.52.1 → 4.53.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 (39) hide show
  1. package/README.md +14 -0
  2. package/dist/AttachmentChipRow/AttachmentChipRow.svelte +3 -2
  3. package/dist/AttachmentChipRow/properties.d.ts +2 -0
  4. package/dist/Book/Book.svelte +2 -0
  5. package/dist/Book/properties.d.ts +2 -0
  6. package/dist/Chat/Chat.svelte +2 -0
  7. package/dist/Chat/properties.d.ts +2 -0
  8. package/dist/Chat/types.d.ts +2 -0
  9. package/dist/ChatMessage/ChatMessage.svelte +18 -3
  10. package/dist/ChatMessage/properties.d.ts +6 -0
  11. package/dist/ChatMessageList/ChatMessageList.svelte +31 -7
  12. package/dist/ChatMessageList/properties.d.ts +2 -0
  13. package/dist/Choicebox/Choicebox.svelte +33 -23
  14. package/dist/DateRangePicker/DateRangePicker.svelte +82 -33
  15. package/dist/DateRangePicker/properties.d.ts +18 -2
  16. package/dist/Draggable/Draggable.svelte +4 -2
  17. package/dist/MediaPlayer/MediaPlayer.svelte +28 -13
  18. package/dist/MediaPlayer/properties.d.ts +2 -0
  19. package/dist/Modal/Modal.svelte +30 -43
  20. package/dist/Modal/properties.d.ts +5 -4
  21. package/dist/Pagination/Pagination.svelte +2 -0
  22. package/dist/Pagination/properties.d.ts +2 -0
  23. package/dist/Pill/Pill.svelte +61 -4
  24. package/dist/Pill/properties.d.ts +8 -16
  25. package/dist/Radio/Radio.svelte +6 -2
  26. package/dist/RatingGroup/RatingGroup.svelte +38 -26
  27. package/dist/Select/Select.svelte +8 -0
  28. package/dist/StatCard/StatCard.svelte +68 -17
  29. package/dist/StatCard/properties.d.ts +1 -1
  30. package/dist/Stepper/Step.svelte +2 -2
  31. package/dist/Table/Table.svelte +32 -2
  32. package/dist/Table/properties.d.ts +11 -5
  33. package/dist/Tabs/Tabs.svelte +187 -12
  34. package/dist/ToolCallLog/ToolCallLog.svelte +4 -4
  35. package/dist/utils.js +20 -2
  36. package/dist-codemod/codemod/cli.js +4 -4
  37. package/dist-wc/index.js +3134 -2717
  38. package/package.json +3 -2
  39. package/scripts/postinstall.mjs +13 -15
@@ -183,7 +183,7 @@ export type OptionalStatCardProperties = {
183
183
  classes?: string;
184
184
  };
185
185
  export type StatCardEventProperties = {
186
- /** Makes the card interactive: adds `role="button"`, `tabindex=0`, and wires click/Enter/Space. */
186
+ /** Makes the card interactive with a native action button; child controls remain independent. */
187
187
  onclick?: (event: MouseEvent) => void;
188
188
  /** Fired when the header checkbox changes. */
189
189
  oncheckboxchange?: (checked: boolean) => void;
@@ -64,8 +64,8 @@
64
64
  onkeydown={handleKeydown}
65
65
  role={suppressRoleAndTabindex ? null : 'button'}
66
66
  tabindex={suppressRoleAndTabindex ? null : 0}
67
- aria-label={ariaLabel ?? null}
68
- aria-labelledby={ariaLabel ? null : labelId}
67
+ aria-label={suppressRoleAndTabindex ? null : (ariaLabel ?? null)}
68
+ aria-labelledby={suppressRoleAndTabindex || ariaLabel ? null : labelId}
69
69
  aria-current={status === 'active' ? 'step' : null}
70
70
  >
71
71
  {#if typeof icon === 'string' && icon.length > 0}
@@ -225,11 +225,24 @@
225
225
  // viewports, but a bare scroll container gives no visual hint that more
226
226
  // columns exist. Track whether either edge has hidden content and surface
227
227
  // it as edge scrims (see .table-scroll-shell styles).
228
+ // Caption identity must reach the actual keyboard scroll owner, not only its
229
+ // descendant table. Groups keep nested axes out of the landmark namespace and
230
+ // add no tab stop or keyboard handler; native overflow/fit focus stays intact.
231
+ let scrollCaption = $derived(
232
+ typeof caption === 'string' && caption.trim().length > 0 ? caption : null
233
+ );
234
+ let hasVerticalScrollOwner = $state(false);
228
235
  let canScrollLeft = $state(false);
229
236
  let canScrollRight = $state(false);
230
237
 
231
238
  const trackHorizontalScroll = (scrollNode: HTMLElement) => {
239
+ const containerNode = scrollNode.closest<HTMLElement>('.table-container');
232
240
  const updateScrollHints = () => {
241
+ const verticalOverflow = containerNode ? getComputedStyle(containerNode).overflowY : '';
242
+ hasVerticalScrollOwner =
243
+ containerNode !== null &&
244
+ (verticalOverflow === 'auto' || verticalOverflow === 'scroll') &&
245
+ containerNode.scrollHeight > containerNode.clientHeight;
233
246
  canScrollLeft = scrollNode.scrollLeft > 2;
234
247
  canScrollRight = scrollNode.scrollLeft + scrollNode.clientWidth < scrollNode.scrollWidth - 2;
235
248
  };
@@ -239,6 +252,9 @@
239
252
  // (async rows/columns arriving) change scrollWidth, so observe both.
240
253
  const hintResizeObserver = new ResizeObserver(updateScrollHints);
241
254
  hintResizeObserver.observe(scrollNode);
255
+ if (containerNode !== null) {
256
+ hintResizeObserver.observe(containerNode);
257
+ }
242
258
  const tableElement = scrollNode.querySelector('table');
243
259
  if (tableElement) {
244
260
  hintResizeObserver.observe(tableElement);
@@ -836,6 +852,8 @@
836
852
  <div
837
853
  class="table-container {isTableScrollable ? 'scrollable-table' : ''} {classes ?? ''}"
838
854
  class:table-mobile-cards={mobileCardLayout}
855
+ role={scrollCaption !== null && hasVerticalScrollOwner ? 'group' : null}
856
+ aria-label={hasVerticalScrollOwner ? scrollCaption : null}
839
857
  data-pw={testId}
840
858
  testID={testId}
841
859
  >
@@ -844,7 +862,12 @@
844
862
  class:scrollable-left={canScrollLeft}
845
863
  class:scrollable-right={canScrollRight}
846
864
  >
847
- <div class="table-scroll" use:trackHorizontalScroll>
865
+ <div
866
+ class="table-scroll"
867
+ role={scrollCaption !== null ? 'group' : null}
868
+ aria-label={scrollCaption}
869
+ use:trackHorizontalScroll
870
+ >
848
871
  <table role={mobileRole('table')}>
849
872
  {#if caption}
850
873
  <caption class="sr-only">{caption}</caption>
@@ -878,8 +901,9 @@
878
901
  />
879
902
  </th>
880
903
  {:else if isCheckboxMode && isSingleSelect}
881
- <!-- In single-select mode the header cell is an empty spacer -->
904
+ <!-- Selection is a real column even without a select-all action. -->
882
905
  <th
906
+ aria-label={labels?.selectionColumn ?? 'Row selection'}
883
907
  class="table-header table-checkbox-col"
884
908
  class:table-header-sticky={isStickyHeader}
885
909
  role={mobileRole('columnheader')}
@@ -1084,6 +1108,7 @@
1084
1108
  {#each paginatedTableData as row, pageRowIndex (rowIdByRow.get(row) ?? pageRowIndex)}
1085
1109
  {@const rowIndex = pageRowIndex + rowIndexOffset}
1086
1110
  {@const originalIndex = originalIndexByRow.get(row) ?? rowIndex}
1111
+ {@const rowContext = rowLabels.get(row) ?? `row ${originalIndex + 1}`}
1087
1112
  {@const rowId = rowIdByRow.get(row) ?? String(rowIndex)}
1088
1113
  {@const rowDisabled = isCheckboxMode && isRowDisabled(rowId)}
1089
1114
  {@const rowSelected = isCheckboxMode && isRowSelected(rowId)}
@@ -1094,6 +1119,9 @@
1094
1119
  class:table-summary-row={summaryRowIndex !== null &&
1095
1120
  originalIndex === summaryRowIndex}
1096
1121
  role={mobileRole('row')}
1122
+ aria-label={isRowClickable
1123
+ ? (labels?.rowAction?.(rowContext) ?? rowContext)
1124
+ : null}
1097
1125
  data-pw={typeof getRowTestId === 'function' ? getRowTestId(row, rowIndex) : null}
1098
1126
  testID={typeof getRowTestId === 'function' ? getRowTestId(row, rowIndex) : null}
1099
1127
  onclick={isRowClickable
@@ -1280,6 +1308,8 @@
1280
1308
  {/if}
1281
1309
  {#if !hideSteppers}
1282
1310
  <Pagination
1311
+ ariaLabel={labels?.pageNavigation ??
1312
+ (tableTitle ? `${tableTitle} pagination` : 'Pagination')}
1283
1313
  totalPages={paginationTotalPages}
1284
1314
  currentPage={effectivePage}
1285
1315
  hasMore={pagination.hasMore ?? false}
@@ -524,12 +524,11 @@ export type TableProperties = OptionalTableProperties & TableEventProperties;
524
524
  * Accessible names Table generates for its own controls, for callers who do
525
525
  * not ship an English UI.
526
526
  *
527
- * Every member is optional and falls back to an English default. All but
528
- * `rowsPerPage` and `cellEditor` keep the string Table has always used; those
529
- * two add a name to a control that previously had none.
527
+ * Every member is optional. Control labels use English defaults; rowAction
528
+ * uses the readable source-row caption.
530
529
  *
531
- * The two that depend on a column, and the one that depends on a row, are
532
- * functions rather than templates with a placeholder: a translator needs to
530
+ * Members that depend on a column or row are functions rather than templates
531
+ * with a placeholder: a translator needs to
533
532
  * put the header where their grammar wants it, not where an English sentence
534
533
  * happens to leave a slot.
535
534
  *
@@ -546,6 +545,8 @@ export type TableLabels = {
546
545
  * naming an empty row. Default: `Select row ${rowId || 'non-selectable'}`. */
547
546
  selectRow?: (rowId: string) => string;
548
547
  /** Header checkbox that selects every row on the page. Default: `Select all rows`. */
548
+ /** Accessible header name for the row-selection column, including single-selection mode. */
549
+ selectionColumn?: string;
549
550
  selectAllRows?: string;
550
551
  /** Button that empties the search box. Default: `Clear search`. */
551
552
  clearSearch?: string;
@@ -553,8 +554,12 @@ export type TableLabels = {
553
554
  closeSearch?: string;
554
555
  /** Built-in editor name. Default: `${columnLabel} for ${rowLabel}`. */
555
556
  cellEditor?: (columnLabel: string, rowLabel: string) => string;
557
+ /** Clickable row name. Receives stable readable source-row context. Default: rowLabel. */
558
+ rowAction?: (rowLabel: string) => string;
556
559
  /** Page-size selector in the built-in paginator footer. Default: `Rows per page`. */
557
560
  rowsPerPage?: string;
561
+ /** Page-navigation landmark name. Default: `Pagination`. */
562
+ pageNavigation?: string;
558
563
  };
559
564
  export type OptionalTableProperties = {
560
565
  labels?: TableLabels;
@@ -669,6 +674,7 @@ export type OptionalTableProperties = {
669
674
  isTableScrollable?: boolean;
670
675
  isContentScrollable?: boolean;
671
676
  testId?: string;
677
+ /** Visually hidden native table caption; non-empty also names existing scroll groups. */
672
678
  caption?: string;
673
679
  sortAscIcon?: Snippet;
674
680
  sortDescIcon?: Snippet;
@@ -156,19 +156,137 @@
156
156
  return getComputedStyle(scrollContainer).direction === 'rtl';
157
157
  }
158
158
 
159
+ function availableWithoutArrows(node: HTMLElement, vertical: boolean): number {
160
+ const owner = node.parentElement;
161
+ const current = vertical ? node.clientHeight : node.clientWidth;
162
+ if (owner === null) {
163
+ return current;
164
+ }
165
+ const ownerStyle = getComputedStyle(owner);
166
+ const barStyle = getComputedStyle(node);
167
+ const flexLayout = ownerStyle.display === 'flex' || ownerStyle.display === 'inline-flex';
168
+ const matchingAxis = vertical
169
+ ? ownerStyle.flexDirection === 'column' || ownerStyle.flexDirection === 'column-reverse'
170
+ : ownerStyle.flexDirection === 'row' || ownerStyle.flexDirection === 'row-reverse';
171
+ if (!flexLayout || !matchingAxis || Number.parseFloat(barStyle.flexGrow) <= 0) {
172
+ return current;
173
+ }
174
+ const pixels = (value: string): number => Number.parseFloat(value) || 0;
175
+ const padding = vertical
176
+ ? pixels(ownerStyle.paddingTop) + pixels(ownerStyle.paddingBottom)
177
+ : pixels(ownerStyle.paddingLeft) + pixels(ownerStyle.paddingRight);
178
+ const budget = (vertical ? owner.clientHeight : owner.clientWidth) - padding;
179
+ const arrows = Array.from(owner.querySelectorAll<HTMLElement>(':scope > .tabs-arrow'));
180
+ const gap = pixels(vertical ? ownerStyle.rowGap : ownerStyle.columnGap);
181
+ const gutter = arrows.reduce(
182
+ (total, arrow) => total + (vertical ? arrow.offsetHeight : arrow.offsetWidth),
183
+ gap * arrows.length
184
+ );
185
+ const border = vertical
186
+ ? pixels(barStyle.borderTopWidth) + pixels(barStyle.borderBottomWidth)
187
+ : pixels(barStyle.borderLeftWidth) + pixels(barStyle.borderRightWidth);
188
+ let available = Math.min(current + gutter, Math.max(0, budget - border));
189
+ const maximum = vertical ? barStyle.maxHeight : barStyle.maxWidth;
190
+ if (maximum !== 'none') {
191
+ const limit = maximum.endsWith('%')
192
+ ? (pixels(maximum) * budget) / 100
193
+ : maximum.endsWith('px')
194
+ ? pixels(maximum)
195
+ : Number.NaN;
196
+ if (!Number.isFinite(limit)) {
197
+ // Keep the measured constraint when an authored CSS expression cannot
198
+ // be resolved here without changing that layout.
199
+ return current;
200
+ }
201
+ const barPadding = vertical
202
+ ? pixels(barStyle.paddingTop) + pixels(barStyle.paddingBottom)
203
+ : pixels(barStyle.paddingLeft) + pixels(barStyle.paddingRight);
204
+ available = Math.min(
205
+ available,
206
+ barStyle.boxSizing === 'border-box' ? Math.max(0, limit - border) : limit + barPadding
207
+ );
208
+ }
209
+ return available;
210
+ }
211
+
212
+ function inFlowContentExtent(node: HTMLElement, vertical: boolean): number {
213
+ let minimum = Number.POSITIVE_INFINITY;
214
+ let maximum = Number.NEGATIVE_INFINITY;
215
+ const pixels = (value: string): number => Number.parseFloat(value) || 0;
216
+ for (const child of Array.from(node.children)) {
217
+ if (
218
+ !(child instanceof HTMLElement) ||
219
+ child.classList.contains('tabs-indicator') ||
220
+ child.getClientRects().length === 0
221
+ ) {
222
+ continue;
223
+ }
224
+ if (child.offsetParent !== node) {
225
+ // An authored offset parent uses another coordinate system.
226
+ return vertical ? node.scrollHeight : node.scrollWidth;
227
+ }
228
+ const childStyle = getComputedStyle(child);
229
+ const start = vertical ? child.offsetTop : child.offsetLeft;
230
+ const end = start + (vertical ? child.offsetHeight : child.offsetWidth);
231
+ const before = pixels(vertical ? childStyle.marginTop : childStyle.marginLeft);
232
+ const after = pixels(vertical ? childStyle.marginBottom : childStyle.marginRight);
233
+ minimum = Math.min(minimum, start, start - before);
234
+ maximum = Math.max(maximum, end, end + after);
235
+ }
236
+ const style = getComputedStyle(node);
237
+ const padding = vertical
238
+ ? pixels(style.paddingTop) + pixels(style.paddingBottom)
239
+ : pixels(style.paddingLeft) + pixels(style.paddingRight);
240
+ // The absolute indicator can temporarily extend during its transition;
241
+ // it is decoration rather than additional tab content.
242
+ return (Number.isFinite(minimum) ? Math.max(0, maximum - minimum) : 0) + padding;
243
+ }
244
+
159
245
  function updateOverflow(): void {
160
246
  if (scrollContainer === null) {
161
247
  return;
162
248
  }
163
249
  if (isVertical) {
164
250
  const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
251
+ if (clientHeight <= 0) {
252
+ return;
253
+ }
254
+ if (
255
+ inFlowContentExtent(scrollContainer, true) <=
256
+ availableWithoutArrows(scrollContainer, true) + 1
257
+ ) {
258
+ canScrollUp = false;
259
+ canScrollDown = false;
260
+ return;
261
+ }
165
262
  canScrollUp = scrollTop > 1;
166
263
  canScrollDown = scrollTop + clientHeight < scrollHeight - 1;
167
264
  return;
168
265
  }
169
266
  const { scrollLeft, scrollWidth, clientWidth } = scrollContainer;
170
- canScrollLeft = scrollLeft > 1;
171
- canScrollRight = scrollLeft + clientWidth < scrollWidth - 1;
267
+ if (clientWidth <= 0) {
268
+ return;
269
+ }
270
+ if (
271
+ inFlowContentExtent(scrollContainer, false) <=
272
+ availableWithoutArrows(scrollContainer, false) + 1
273
+ ) {
274
+ canScrollLeft = false;
275
+ canScrollRight = false;
276
+ return;
277
+ }
278
+ const maxScroll = Math.max(0, scrollWidth - clientWidth);
279
+ // RTL starts at zero at the physical right edge and moves negatively to
280
+ // the left. Express both directions as distance from the physical left.
281
+ const physicalLeft = Math.min(
282
+ maxScroll,
283
+ Math.max(
284
+ 0,
285
+ getComputedStyle(scrollContainer).direction === 'rtl' ? maxScroll + scrollLeft : scrollLeft
286
+ )
287
+ );
288
+ canScrollLeft = physicalLeft > 1;
289
+ canScrollRight = physicalLeft < maxScroll - 1;
172
290
  }
173
291
 
174
292
  function updateIndicator(): void {
@@ -176,6 +294,11 @@
176
294
  indicatorReady = false;
177
295
  return;
178
296
  }
297
+ // A hidden or temporarily zero-sized viewport cannot provide label offsets.
298
+ // Keep the last measured indicator until this axis can be laid out again.
299
+ if ((isVertical ? scrollContainer.clientHeight : scrollContainer.clientWidth) <= 0) {
300
+ return;
301
+ }
179
302
  const activeEl = scrollContainer.querySelector<HTMLElement>('.tabs-item.active');
180
303
  if (activeEl === null) {
181
304
  indicatorReady = false;
@@ -359,6 +482,21 @@
359
482
  if (next instanceof HTMLElement && tabElements().includes(next)) {
360
483
  return;
361
484
  }
485
+ if (next === null && event.target instanceof HTMLElement) {
486
+ const target = event.target;
487
+ const previousKey = focusedKey;
488
+ focusedKey = null;
489
+ // Chromium fires focusout before removing the focused node. Let that
490
+ // mutation finish before restoring removal focus. An intentional blur
491
+ // releases ownership immediately, including during a selection update.
492
+ queueMicrotask(() => {
493
+ if (!target.isConnected && focusedKey === null && previousKey !== null) {
494
+ focusedKey = previousKey;
495
+ reconcileFocus();
496
+ }
497
+ });
498
+ return;
499
+ }
362
500
  focusedKey = null;
363
501
  }
364
502
 
@@ -387,8 +525,20 @@
387
525
  focusedKey = null;
388
526
  return;
389
527
  }
390
- if (!isInside && focusedKey === null) {
391
- return;
528
+ if (!isInside) {
529
+ // A removed tab leaves focus on the body; restore that lost focus. An
530
+ // external control (including this strip's arrow) owns its own focus.
531
+ const root = scrollContainer?.getRootNode();
532
+ const hostActive = root instanceof ShadowRoot ? getActiveElement(root.host) : null;
533
+ const lostToBody =
534
+ active === scrollContainer?.ownerDocument.body ||
535
+ (root instanceof ShadowRoot &&
536
+ active === null &&
537
+ (hostActive === root.host || hostActive === scrollContainer?.ownerDocument.body));
538
+ if (focusedKey === null || !lostToBody) {
539
+ focusedKey = null;
540
+ return;
541
+ }
392
542
  }
393
543
  // Focus follows a selection made elsewhere, so a parent switching tabs does not
394
544
  // strand the user on a tab that is no longer the selected one.
@@ -414,7 +564,32 @@
414
564
  updateOverflow();
415
565
  updateIndicator();
416
566
  reconcileFocus();
567
+ const resizeObserver = new ResizeObserver(() => {
568
+ updateOverflow();
569
+ updateIndicator();
570
+ });
571
+ let resizeTargets: Element[] = [];
572
+ const syncResizeTargets = (): void => {
573
+ const targets: Element[] = [
574
+ node,
575
+ ...node.querySelectorAll('.tabs-item, .tabs-section-label')
576
+ ];
577
+ for (const previous of resizeTargets) {
578
+ if (!targets.includes(previous)) {
579
+ resizeObserver.unobserve(previous);
580
+ }
581
+ }
582
+ for (const target of targets) {
583
+ if (!resizeTargets.includes(target)) {
584
+ resizeObserver.observe(target, {
585
+ box: target === node ? 'content-box' : 'border-box'
586
+ });
587
+ }
588
+ }
589
+ resizeTargets = targets;
590
+ };
417
591
  const observer = new MutationObserver(() => {
592
+ syncResizeTargets();
418
593
  updateOverflow();
419
594
  updateIndicator();
420
595
  reconcileFocus();
@@ -425,11 +600,7 @@
425
600
  attributes: true,
426
601
  attributeFilter: ['class', 'tabindex', 'aria-selected', 'aria-disabled']
427
602
  });
428
- const resizeObserver = new ResizeObserver(() => {
429
- updateOverflow();
430
- updateIndicator();
431
- });
432
- resizeObserver.observe(node);
603
+ syncResizeTargets();
433
604
  return () => {
434
605
  observer.disconnect();
435
606
  resizeObserver.disconnect();
@@ -521,7 +692,11 @@
521
692
  {#if typeof tabItem?.icon === 'string' && tabItem.icon.length > 0}
522
693
  <Img inlineSvg src={tabItem.icon} alt="" fallback="" classes="tabs-item-icon" />
523
694
  {/if}
524
- <span class="tabs-item-label" data-text={label}>{label}</span>
695
+ <!-- Refresh only the label node: WebKit can retain the old attr() ghost
696
+ after a reused label changes, while the tab and its focus stay stable. -->
697
+ {#key label}
698
+ <span class="tabs-item-label" data-text={label}>{label}</span>
699
+ {/key}
525
700
  {#if tabItem?.status && tabItem.status !== 'none'}
526
701
  <span class="tabs-item-status status-{tabItem.status}" aria-hidden="true"></span>
527
702
  {/if}
@@ -735,12 +910,12 @@
735
910
  with height:0/visibility:hidden -- invisible, but its width still sets
736
911
  the shrink-to-fit width of the inline-block label, so the box is already
737
912
  as wide as the active state needs even while showing the lighter weight. */
738
- .tabs-item-label {
913
+ .tabs-item :global(.tabs-item-label) {
739
914
  position: relative;
740
915
  display: inline-block;
741
916
  }
742
917
 
743
- .tabs-item-label::after {
918
+ .tabs-item :global(.tabs-item-label)::after {
744
919
  content: attr(data-text);
745
920
  display: block;
746
921
  height: 0;
@@ -289,10 +289,10 @@
289
289
  gap: var(--tool-call-log-gap, 8px);
290
290
  }
291
291
 
292
- /* Bespoke <button> markup (Pill's root is a non-interactive <div> and can't carry
293
- aria-expanded/button semantics — see docs), but its sizing recipe is deliberately
294
- pulled from Pill's own tokens (line-height, cursor) so it reads as the same family
295
- of control as the diffstat Pill badges nested inside it. */
292
+ /* Bespoke <button> markup (the chip holds mixed content -- spinner, label, meta and nested
293
+ diffstat Pill badges -- where Pill renders one `text` string; see docs), but its sizing
294
+ recipe is deliberately pulled from Pill's own tokens (line-height, cursor) so it reads as
295
+ the same family of control as the diffstat Pill badges nested inside it. */
296
296
  .chip {
297
297
  display: inline-flex;
298
298
  align-items: center;
package/dist/utils.js CHANGED
@@ -372,8 +372,24 @@ export function createDebouncer(delay) {
372
372
  }
373
373
  };
374
374
  }
375
- /** Per-container locks shared by overlays and the legacy body helpers. */
376
- const scrollLocks = new WeakMap();
375
+ const scrollLocksKey = Symbol.for('@juspay/svelte-ui-components/scroll-locks/v1');
376
+ const activeScrollLocksKey = Symbol.for('@juspay/svelte-ui-components/active-scroll-locks/v1');
377
+ /** Independent package and custom-element bundles share the actual owner's holds. */
378
+ function scrollLocksFor(container) {
379
+ const activeOwner = container;
380
+ if (activeOwner[activeScrollLocksKey]) {
381
+ return activeOwner[activeScrollLocksKey];
382
+ }
383
+ const owner = container.ownerDocument;
384
+ let locks = owner[scrollLocksKey];
385
+ if (!locks) {
386
+ locks = new WeakMap();
387
+ Object.defineProperty(owner, scrollLocksKey, { value: locks });
388
+ }
389
+ // The same real owner can be adopted into another document while held.
390
+ Object.defineProperty(activeOwner, activeScrollLocksKey, { value: locks, configurable: true });
391
+ return locks;
392
+ }
377
393
  const bodyScrollReleases = [];
378
394
  /**
379
395
  * Acquire one hold and return its idempotent release function. Only overflow
@@ -385,6 +401,7 @@ export function acquireScrollLock(container = typeof document === 'undefined' ?
385
401
  if (container === null) {
386
402
  return () => { };
387
403
  }
404
+ const scrollLocks = scrollLocksFor(container);
388
405
  let lock = scrollLocks.get(container);
389
406
  if (!lock) {
390
407
  const overflow = [];
@@ -419,6 +436,7 @@ export function acquireScrollLock(container = typeof document === 'undefined' ?
419
436
  container.style.setProperty(name, value, priority);
420
437
  }
421
438
  scrollLocks.delete(container);
439
+ Reflect.deleteProperty(container, activeScrollLocksKey);
422
440
  }
423
441
  };
424
442
  }
@@ -9,10 +9,10 @@ const USAGE = [
9
9
  ' npx sui-codemod migrate [--dry-run] <path>',
10
10
  ' (inside this repository: node scripts/codemod/cli.ts ...)',
11
11
  '',
12
- 'Prepares a @juspay/svelte-ui-components consumer for 4.0.0: renames every',
13
- 'deprecated event-prop spelling on library components to its corrected',
14
- 'spelling (see legacy-pairs.ts) and reports `children` assignments on the',
15
- 'custom elements that stop declaring it.',
12
+ 'Migrates older @juspay/svelte-ui-components usage to the current API:',
13
+ 'renames removed event-prop spellings on library component tags to their',
14
+ 'lowercase replacements (see legacy-pairs.ts) and reports unsupported',
15
+ '`children` assignments on affected custom elements.',
16
16
  '',
17
17
  ' --dry-run print every change as a diff without writing any file',
18
18
  ' --help show this help',