@juspay/svelte-ui-components 2.61.0 → 2.62.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.
@@ -413,10 +413,7 @@
413
413
  return null;
414
414
  }
415
415
  const title = isMulti ? `${bar.dataPoint.label} — ${bar.seriesName}` : bar.dataPoint.label;
416
- const displayValue =
417
- isNormalized && bar.normalizedValue != null
418
- ? normalizedFormat(bar.normalizedValue)
419
- : format(bar.dataPoint.value);
416
+ const displayValue = getDisplayValue(bar);
420
417
  return {
421
418
  title,
422
419
  items: [{ label: bar.dataPoint.label, value: displayValue, color: bar.color }]
@@ -574,7 +571,7 @@
574
571
  y2={isHoriz ? entry.bar.y : entry.bar.y + entry.bar.height}
575
572
  gradientUnits="userSpaceOnUse"
576
573
  >
577
- {#each grad.stops as stop (stop.offset)}
574
+ {#each grad.stops as stop, stopIndex (`${stopIndex}-${stop.offset}`)}
578
575
  <stop
579
576
  offset="{stop.offset * 100}%"
580
577
  stop-color={stop.color}
@@ -15,7 +15,7 @@
15
15
  ariaSelected,
16
16
  role,
17
17
  onclick,
18
- onkeydown,
18
+ onkeydown = () => {},
19
19
  onkeyup = () => {},
20
20
  onmousedown,
21
21
  onmouseup,
@@ -117,7 +117,10 @@
117
117
  font-weight: var(--disabled-font-weight);
118
118
  border: var(--disabled-border);
119
119
  background: var(--disabled-background-color, var(--button-color, #3a4550));
120
- box-shadow: var(--button-disabled-box-shadow, var(--button-box-shadow, none));
120
+ box-shadow: var(
121
+ --button-disabled-box-shadow,
122
+ var(--disabled-box-shadow, var(--button-box-shadow, none))
123
+ );
121
124
  }
122
125
 
123
126
  .button-loader {
@@ -20,11 +20,18 @@ export type OptionalButtonProperties = {
20
20
  };
21
21
  export type ButtonEventProperties = {
22
22
  onclick?: (event: MouseEvent) => void;
23
+ /** Fires when a key is pressed down while the button has focus. */
23
24
  onkeydown?: (event: KeyboardEvent) => void;
25
+ /** Fires when a key is released while the button has focus. */
24
26
  onkeyup?: (event: KeyboardEvent) => void;
27
+ /** Fires when a mouse button is pressed down on the button. */
25
28
  onmousedown?: (event: MouseEvent) => void;
29
+ /** Fires when a mouse button is released over the button. */
26
30
  onmouseup?: (event: MouseEvent) => void;
31
+ /** Fires when the pointer leaves the button area. */
27
32
  onmouseleave?: (event: MouseEvent) => void;
33
+ /** Fires when a touch point is placed on the button. */
28
34
  ontouchstart?: (event: TouchEvent) => void;
35
+ /** Fires when a touch point is removed from the button. */
29
36
  ontouchend?: (event: TouchEvent) => void;
30
37
  };
@@ -177,7 +177,7 @@
177
177
  .card-content-scrollable {
178
178
  overflow-y: auto;
179
179
  max-height: var(--card-content-max-height, 400px);
180
- scrollbar-width: thin;
180
+ scrollbar-width: var(--card-content-scrollbar-width, thin);
181
181
  }
182
182
 
183
183
  /* footer */
@@ -110,6 +110,32 @@
110
110
 
111
111
  let activePresetLabel: string | null = $state(resolvedInitialPresetLabel);
112
112
 
113
+ // Seed draft from initialPresetLabel whenever presets become available so
114
+ // the calendar shows the preset's date selection when the picker is first
115
+ // opened — even if presets arrive asynchronously after initial render.
116
+ // The draftStart/draftEnd/draftValue guards prevent re-seeding once the
117
+ // user has made a custom selection.
118
+ $effect.pre(() => {
119
+ if (
120
+ resolvedInitialPresetLabel !== null &&
121
+ presets !== null &&
122
+ draftStart === null &&
123
+ draftEnd === null &&
124
+ draftValue === null
125
+ ) {
126
+ const matchedPreset = presets.find((p) => p.label === resolvedInitialPresetLabel) ?? null;
127
+ if (matchedPreset !== null) {
128
+ const { start, end } = matchedPreset.getValue();
129
+ if (mode === 'single') {
130
+ draftValue = start;
131
+ } else {
132
+ draftStart = start;
133
+ draftEnd = end;
134
+ }
135
+ }
136
+ }
137
+ });
138
+
113
139
  const now = new SvelteDate();
114
140
 
115
141
  // Dual-month navigation: track the year+month of the left calendar
@@ -160,11 +186,11 @@
160
186
 
161
187
  function openPicker(): void {
162
188
  // Seed draft from current committed values
163
- draftStart = rangeStart !== null ? rangeStart : null;
164
- draftEnd = rangeEnd !== null ? rangeEnd : null;
165
- draftValue = value !== null ? value : null;
166
- draftCompareStart = compareStart !== null ? compareStart : null;
167
- draftCompareEnd = compareEnd !== null ? compareEnd : null;
189
+ draftStart = rangeStart;
190
+ draftEnd = rangeEnd;
191
+ draftValue = value;
192
+ draftCompareStart = compareStart;
193
+ draftCompareEnd = compareEnd;
168
194
 
169
195
  // Reset preset tracking so each picker session starts clean.
170
196
  selectedPresetLabel = null;
@@ -499,9 +525,7 @@
499
525
  {#each presets as preset, presetIndex (preset.label)}
500
526
  {@const previousPreset = presetIndex > 0 ? presets[presetIndex - 1] : null}
501
527
  {@const groupChanged =
502
- presetIndex > 0 &&
503
- 'group' in preset &&
504
- (previousPreset === null || previousPreset.group !== preset.group)}
528
+ presetIndex > 0 && (previousPreset?.group ?? '') !== (preset.group ?? '')}
505
529
  {#if groupChanged}
506
530
  <div class="drp-preset-divider" role="separator" aria-hidden="true">
507
531
  {#if preset.group !== ''}
@@ -6,6 +6,7 @@
6
6
  max = 100,
7
7
  showLabel = true,
8
8
  labelFormatter,
9
+ ariaLabel,
9
10
  testId,
10
11
  classes
11
12
  }: GaugeProperties = $props();
@@ -33,7 +34,7 @@
33
34
  aria-valuenow={clamped}
34
35
  aria-valuemin={0}
35
36
  aria-valuemax={100}
36
- aria-label="gauge"
37
+ aria-label={ariaLabel ?? labelText}
37
38
  >
38
39
  <svg viewBox="0 0 {VIEW_BOX} {VIEW_BOX}">
39
40
  <circle class="track" cx={CENTER} cy={CENTER} r={RADIUS} fill="none" />
@@ -11,6 +11,7 @@ export type OptionalGaugeProperties = {
11
11
  * @default 100
12
12
  */
13
13
  max?: number;
14
+ /** Whether to show the centered percentage label. @default true */
14
15
  showLabel?: boolean;
15
16
  /**
16
17
  * Custom label renderer called with the raw `(value, max)` pair. Return any
@@ -18,6 +19,14 @@ export type OptionalGaugeProperties = {
18
19
  * Example: `labelFormatter={(v, m) => \`${v} / ${m}\`}` shows "50 / 200".
19
20
  */
20
21
  labelFormatter?: (value: number, max: number) => string;
22
+ /**
23
+ * Accessible label for the gauge element (`role="progressbar"`). Falls back
24
+ * to the computed `labelText` (e.g. `"75%"`) when not provided, so assistive
25
+ * technology announces the current percentage by default.
26
+ */
27
+ ariaLabel?: string;
28
+ /** Value for the `data-pw` attribute on the root element for E2E test selection. */
21
29
  testId?: string;
30
+ /** Extra CSS classes applied to the root element for consumer theming. */
22
31
  classes?: string;
23
32
  };
@@ -272,7 +272,7 @@
272
272
  <ChartContainer bind:width={chartWidth} bind:height={chartHeight} {aspectRatio}>
273
273
  <g transform="translate({MARGIN}, {MARGIN})">
274
274
  {#if columnLabels != null && columnLabels.length > 0}
275
- {#each columnLabels as label, ci (ci)}
275
+ {#each columnLabels.slice(0, columnCount) as label, ci (ci)}
276
276
  <text
277
277
  class="sankey-col-label"
278
278
  x={ci * colWidth + nodeWidth / 2}
@@ -364,7 +364,7 @@
364
364
  id={`${listboxId}-option-${index}`}
365
365
  aria-selected={value.includes(item.id)}
366
366
  tabindex="-1"
367
- {...item.testId
367
+ {...typeof item.testId === 'string'
368
368
  ? { 'data-pw': item.testId }
369
369
  : typeof itemTestId === 'string'
370
370
  ? { 'data-pw': `${itemTestId}-${item.id}` }
@@ -1,18 +1,77 @@
1
1
  <script lang="ts">
2
2
  import type { StepProperties } from './properties';
3
3
 
4
- let { stepIndex, label, icon, classes, onclick, onkeydown }: StepProperties = $props();
4
+ let {
5
+ stepIndex,
6
+ label,
7
+ icon,
8
+ status,
9
+ badge,
10
+ orientation = 'horizontal',
11
+ classes,
12
+ ariaLabel,
13
+ onclick,
14
+ onkeydown
15
+ }: StepProperties = $props();
5
16
 
6
- function handleStepClick() {
17
+ const handleStepClick = (): void => {
7
18
  onclick?.({ selectedIndex: stepIndex });
8
- }
19
+ };
20
+
21
+ const handleKeydown = (event: KeyboardEvent): void => {
22
+ if (event.key === 'Enter' || event.key === ' ') {
23
+ event.preventDefault();
24
+ handleStepClick();
25
+ }
26
+ onkeydown?.(event);
27
+ };
28
+
29
+ let isVertical = $derived(orientation === 'vertical');
30
+
31
+ let stepClass = $derived(
32
+ ['step', isVertical ? 'step-vertical' : '', classes ?? ''].filter((c) => c.length > 0).join(' ')
33
+ );
9
34
  </script>
10
35
 
11
- <div class="step {classes ?? ''}" onclick={handleStepClick} {onkeydown} role="button" tabindex="0">
36
+ <div
37
+ class={stepClass}
38
+ onclick={handleStepClick}
39
+ onkeydown={handleKeydown}
40
+ role="button"
41
+ tabindex="0"
42
+ aria-label={ariaLabel ?? null}
43
+ aria-labelledby={ariaLabel ? null : `step-label-${stepIndex}`}
44
+ aria-current={status === 'active' ? 'step' : null}
45
+ >
12
46
  {#if typeof icon === 'string' && icon.length > 0}
13
47
  <div class="step-icon-container">
14
48
  <img class="step-icon" src={icon} alt="" />
15
49
  </div>
50
+ {:else if status === 'in-progress'}
51
+ <div class="step-index-container">
52
+ <svg
53
+ class="step-spinner"
54
+ viewBox="0 0 24 24"
55
+ fill="none"
56
+ xmlns="http://www.w3.org/2000/svg"
57
+ aria-hidden="true"
58
+ >
59
+ <circle
60
+ cx="12"
61
+ cy="12"
62
+ r="9"
63
+ stroke="currentColor"
64
+ stroke-opacity="0.25"
65
+ stroke-width="3"
66
+ />
67
+ <path
68
+ d="M12 3a9 9 0 0 1 9 9"
69
+ stroke="currentColor"
70
+ stroke-width="3"
71
+ stroke-linecap="round"
72
+ />
73
+ </svg>
74
+ </div>
16
75
  {:else}
17
76
  <div class="step-index-container">
18
77
  <div class="step-index-text">
@@ -20,9 +79,17 @@
20
79
  </div>
21
80
  </div>
22
81
  {/if}
23
- <div class="step-text">
82
+
83
+ <div class="step-text" id="step-label-{stepIndex}">
24
84
  {label}
25
85
  </div>
86
+
87
+ {#if typeof badge === 'function'}
88
+ <div class="step-badge">
89
+ {@render badge()}
90
+ </div>
91
+ {/if}
92
+
26
93
  <div class="separator"></div>
27
94
  </div>
28
95
 
@@ -33,6 +100,11 @@
33
100
  align-items: center;
34
101
  }
35
102
 
103
+ .step-vertical {
104
+ --step-flex-direction: column;
105
+ align-items: flex-start;
106
+ }
107
+
36
108
  .step-index-container {
37
109
  display: flex;
38
110
  justify-content: center;
@@ -41,33 +113,110 @@
41
113
  width: var(--step-index-container-width, 30px);
42
114
  border-radius: var(--step-index-container-radius, 50%);
43
115
  background-color: var(--step-index-container-background-color, #798fa5cc);
116
+ color: var(--step-index-color, white);
117
+ flex-shrink: 0;
118
+ }
119
+
120
+ .step-icon-container {
121
+ display: flex;
122
+ justify-content: center;
123
+ align-items: center;
124
+ height: var(--step-index-container-height, 30px);
125
+ width: var(--step-index-container-width, 30px);
126
+ border-radius: var(--step-index-container-radius, 50%);
127
+ background-color: var(--step-index-container-background-color, #798fa5cc);
128
+ flex-shrink: 0;
129
+ overflow: hidden;
130
+ }
131
+
132
+ .step-icon {
133
+ width: var(--step-icon-size, 18px);
134
+ height: var(--step-icon-size, 18px);
135
+ object-fit: contain;
136
+ }
137
+
138
+ .step-spinner {
139
+ width: var(--step-spinner-size, 18px);
140
+ height: var(--step-spinner-size, 18px);
141
+ animation: stepper-spin 0.8s linear infinite;
142
+ }
143
+
144
+ @keyframes stepper-spin {
145
+ from {
146
+ transform: rotate(0deg);
147
+ }
148
+ to {
149
+ transform: rotate(360deg);
150
+ }
44
151
  }
45
152
 
46
153
  .separator {
47
- display: var(--separator-display, block);
48
- height: var(--separator-height, 1px);
49
- width: var(--separator-width, 50px);
50
- margin: var(--separator-margin, 0px 12px 0px 12px);
154
+ display: var(--stepper-separator-display, var(--separator-display, block));
155
+ height: var(--stepper-separator-height, var(--separator-height, 1px));
156
+ width: var(--stepper-separator-width, var(--separator-width, 50px));
157
+ margin: var(--stepper-separator-margin, var(--separator-margin, 0px 12px 0px 12px));
51
158
  background-image: var(
52
- --separator-background-image,
53
- repeating-linear-gradient(
54
- to right,
55
- var(--separator-background-image-color, #798fa5cc),
56
- var(--separator-background-image-color, #798fa5cc) 6px,
57
- transparent 6px,
58
- transparent 10px
159
+ --stepper-separator-background-image,
160
+ var(
161
+ --separator-background-image,
162
+ repeating-linear-gradient(
163
+ to right,
164
+ var(
165
+ --stepper-separator-background-image-color,
166
+ var(--separator-background-image-color, #798fa5cc)
167
+ ),
168
+ var(
169
+ --stepper-separator-background-image-color,
170
+ var(--separator-background-image-color, #798fa5cc)
171
+ )
172
+ 6px,
173
+ transparent 6px,
174
+ transparent 10px
175
+ )
59
176
  )
60
177
  );
61
178
  }
62
179
 
180
+ .step-vertical .separator {
181
+ height: var(--stepper-separator-vertical-height, 32px);
182
+ width: var(--stepper-separator-vertical-width, 1px);
183
+ margin: var(--stepper-separator-vertical-margin, 4px 0px 4px 14px);
184
+ background-image: repeating-linear-gradient(
185
+ to bottom,
186
+ var(
187
+ --stepper-separator-background-image-color,
188
+ var(--separator-background-image-color, #798fa5cc)
189
+ ),
190
+ var(
191
+ --stepper-separator-background-image-color,
192
+ var(--separator-background-image-color, #798fa5cc)
193
+ )
194
+ 6px,
195
+ transparent 6px,
196
+ transparent 10px
197
+ );
198
+ }
199
+
63
200
  .step-text {
64
201
  margin: var(--step-text-margin, 0px 0px 0px 12px);
65
202
  font-size: var(--step-text-font-size, 12px);
66
203
  color: var(--step-text-color, #798fa5cc);
67
204
  }
68
205
 
206
+ .step-vertical .step-text {
207
+ margin: var(--step-text-vertical-margin, 4px 0px 0px 0px);
208
+ }
209
+
69
210
  .step-index-text {
70
211
  font-size: var(--step-index-font-size, 14px);
71
212
  color: var(--step-index-color, white);
72
213
  }
214
+
215
+ .step-badge {
216
+ margin: var(--step-badge-margin, 0 0 0 4px);
217
+ }
218
+
219
+ .step-vertical .step-badge {
220
+ margin: var(--step-badge-vertical-margin, 4px 0 0 0);
221
+ }
73
222
  </style>
@@ -1,22 +1,57 @@
1
1
  <script lang="ts">
2
- import type { StepperProperties } from './properties';
2
+ import type { StepperProperties, StepStatus } from './properties';
3
3
  import Step from './Step.svelte';
4
4
 
5
- let { steps, currentStepIndex, classes, onhandleStepClick }: StepperProperties = $props();
5
+ let {
6
+ steps,
7
+ currentStepIndex,
8
+ orientation = 'horizontal',
9
+ classes,
10
+ testId,
11
+ onstepclick,
12
+ onhandleStepClick
13
+ }: StepperProperties = $props();
14
+
15
+ const resolveStatus = (stepIndex: number, explicitStatus: StepStatus | null): StepStatus => {
16
+ if (explicitStatus !== null) {
17
+ return explicitStatus;
18
+ }
19
+ if (stepIndex < currentStepIndex) {
20
+ return 'completed';
21
+ }
22
+ if (stepIndex === currentStepIndex) {
23
+ return 'active';
24
+ }
25
+ return 'pending';
26
+ };
27
+
28
+ // Support the deprecated onhandleStepClick alias — onstepclick takes priority.
29
+ const effectiveStepClick = $derived(onstepclick ?? onhandleStepClick);
30
+
31
+ let containerClass = $derived(
32
+ ['container', orientation === 'vertical' ? 'container-vertical' : '', classes ?? '']
33
+ .filter((c) => c.length > 0)
34
+ .join(' ')
35
+ );
6
36
  </script>
7
37
 
8
- <div class="container {classes ?? ''}">
38
+ <div class={containerClass} data-pw={typeof testId === 'string' ? testId : null} role="list">
9
39
  {#each steps as currentStep, stepIndex (stepIndex)}
40
+ {@const effectiveStatus = resolveStatus(stepIndex, currentStep.status ?? null)}
10
41
  <div
11
- class:active-step={currentStepIndex === stepIndex}
12
- class:completed-step={currentStepIndex > stepIndex}
13
- class="step-container"
42
+ role="listitem"
43
+ class="step-container status-{effectiveStatus} {effectiveStatus === 'active'
44
+ ? 'active-step'
45
+ : ''} {effectiveStatus === 'completed' ? 'completed-step' : ''}"
14
46
  >
15
47
  <Step
16
- onclick={onhandleStepClick}
48
+ onclick={effectiveStepClick}
17
49
  label={currentStep.label}
18
50
  icon={currentStep.icon}
19
51
  stepIndex={stepIndex + 1}
52
+ status={effectiveStatus}
53
+ badge={currentStep.badge}
54
+ {orientation}
20
55
  />
21
56
  </div>
22
57
  {/each}
@@ -29,8 +64,13 @@
29
64
  align-items: center;
30
65
  }
31
66
 
67
+ .container-vertical {
68
+ --container-flex-direction: column;
69
+ align-items: flex-start;
70
+ }
71
+
32
72
  .step-container:last-child {
33
- --separator-display: none;
73
+ --stepper-separator-display: none;
34
74
  }
35
75
 
36
76
  .step-container {
@@ -38,21 +78,81 @@
38
78
  align-items: center;
39
79
  }
40
80
 
41
- .active-step {
81
+ /* status-completed */
82
+ .status-completed {
83
+ --step-text-color: var(--step-text-completed-color, #24aa5a);
84
+ --stepper-separator-background-image-color: var(
85
+ --stepper-separator-background-image-completed-color,
86
+ #24aa5a
87
+ );
88
+ --step-index-container-background-color: var(
89
+ --step-index-container-completed-background-color,
90
+ #24aa5a
91
+ );
92
+ }
93
+
94
+ /* status-active */
95
+ .status-active {
42
96
  --step-text-color: var(--step-text-active-color, #2f3841);
43
- --separator-background-image-color: var(--separator-background-image-active-color, #2f3841);
97
+ --stepper-separator-background-image-color: var(
98
+ --stepper-separator-background-image-active-color,
99
+ #2f3841
100
+ );
44
101
  --step-index-container-background-color: var(
45
102
  --step-index-container-active-background-color,
46
103
  #2f3841
47
104
  );
48
105
  }
49
106
 
50
- .completed-step {
51
- --step-text-color: var(--step-text-completed-color, #24aa5a);
52
- --separator-background-image-color: var(--separator-background-image-completed-color, #24aa5a);
107
+ /* status-pending: no override — falls through to #798fa5cc grey default in Step.svelte */
108
+
109
+ /* status-failure */
110
+ .status-failure {
111
+ --step-text-color: var(--step-text-failure-color, #e53935);
112
+ --stepper-separator-background-image-color: var(
113
+ --stepper-separator-background-image-failure-color,
114
+ #e53935
115
+ );
53
116
  --step-index-container-background-color: var(
54
- --step-index-container-completed-background-color,
55
- #24aa5a
117
+ --step-index-container-failure-background-color,
118
+ var(--stepper-status-failure-color, #e53935)
119
+ );
120
+ }
121
+
122
+ /* status-in-progress */
123
+ .status-in-progress {
124
+ --step-text-color: var(--step-text-in-progress-color, #f59e0b);
125
+ --stepper-separator-background-image-color: var(
126
+ --stepper-separator-background-image-in-progress-color,
127
+ #f59e0b
128
+ );
129
+ --step-index-container-background-color: var(
130
+ --step-index-container-in-progress-background-color,
131
+ var(--stepper-status-in-progress-color, #f59e0b)
132
+ );
133
+ }
134
+
135
+ :global([theme='dark']) .status-failure {
136
+ --step-text-color: var(--step-text-failure-color, #ef5350);
137
+ --stepper-separator-background-image-color: var(
138
+ --stepper-separator-background-image-failure-color,
139
+ #ef5350
140
+ );
141
+ --step-index-container-background-color: var(
142
+ --step-index-container-failure-background-color,
143
+ var(--stepper-status-failure-color, #ef5350)
144
+ );
145
+ }
146
+
147
+ :global([theme='dark']) .status-in-progress {
148
+ --step-text-color: var(--step-text-in-progress-color, #fbbf24);
149
+ --stepper-separator-background-image-color: var(
150
+ --stepper-separator-background-image-in-progress-color,
151
+ #fbbf24
152
+ );
153
+ --step-index-container-background-color: var(
154
+ --step-index-container-in-progress-background-color,
155
+ var(--stepper-status-in-progress-color, #fbbf24)
56
156
  );
57
157
  }
58
158
  </style>
@@ -1,21 +1,44 @@
1
- export type StepperProperties = {
1
+ import type { Snippet } from 'svelte';
2
+ export type StepStatus = 'completed' | 'active' | 'pending' | 'failure' | 'in-progress';
3
+ export type Step = {
4
+ label: string;
5
+ /**
6
+ * URL of a custom icon image. When provided, the icon takes precedence over
7
+ * any `status`-driven rendering (including the `in-progress` spinner).
8
+ */
9
+ icon?: string;
10
+ status?: StepStatus;
11
+ /**
12
+ * Optional Svelte snippet rendered inline after the step label (to its right
13
+ * in horizontal layout, below it in vertical layout). Use it for badges,
14
+ * tags, or other inline metadata — e.g. a count pill, a status chip, or a
15
+ * "New" label.
16
+ */
17
+ badge?: Snippet;
18
+ };
19
+ export type MandatoryStepperProperties = {
2
20
  steps: Array<Step>;
3
21
  currentStepIndex: number;
22
+ };
23
+ export type OptionalStepperProperties = {
24
+ orientation?: 'horizontal' | 'vertical';
4
25
  classes?: string;
26
+ testId?: string;
27
+ };
28
+ export type StepperEventProperties = {
29
+ onstepclick?: (event: {
30
+ selectedIndex: number;
31
+ }) => void;
32
+ /** @deprecated Use `onstepclick` instead. */
5
33
  onhandleStepClick?: (event: {
6
34
  selectedIndex: number;
7
35
  }) => void;
8
36
  };
9
- export type Step = {
10
- label: string;
11
- icon?: string;
12
- };
13
- export type StepProperties = OptionalStepProperties & StepEventProperties & {
14
- stepIndex: number;
15
- label: string;
16
- };
37
+ export type StepperProperties = MandatoryStepperProperties & OptionalStepperProperties & StepperEventProperties;
17
38
  export type OptionalStepProperties = {
18
39
  icon?: string;
40
+ status?: StepStatus;
41
+ badge?: Snippet;
19
42
  classes?: string;
20
43
  };
21
44
  export type StepEventProperties = {
@@ -24,3 +47,16 @@ export type StepEventProperties = {
24
47
  }) => void;
25
48
  onkeydown?: (event: KeyboardEvent) => void;
26
49
  };
50
+ export type StepProperties = OptionalStepProperties & StepEventProperties & {
51
+ /** 1-based display index — Stepper passes `stepIndex + 1` so that clicking step 1 returns `selectedIndex: 1`. */
52
+ stepIndex: number;
53
+ label: string;
54
+ orientation?: 'horizontal' | 'vertical';
55
+ /**
56
+ * Accessible label for the step button. When provided, set as `aria-label` on
57
+ * the `role="button"` element, overriding the default `aria-labelledby` association
58
+ * with the visible label text. Use when the visible label alone is insufficient
59
+ * context for screen reader users (e.g. "Step 1 — Cart (completed)").
60
+ */
61
+ ariaLabel?: string;
62
+ };
@@ -189,10 +189,16 @@
189
189
  * stable even when the visible set shrinks or expands as the search term
190
190
  * changes — so a row that was "row 2" in the full set keeps ID `"2"` even
191
191
  * when it becomes the only visible result.
192
+ *
193
+ * The Map lookup uses object reference equality, so row objects in
194
+ * `filteredTableData` must be the same references as those in
195
+ * `sortedTableData`. When `originalIndex` cannot be found (reference mismatch
196
+ * after a transform), the fallback positional index is used instead.
192
197
  */
193
198
  let filteredRowIds = $derived.by((): string[] => {
199
+ const indexMap = new Map(sortedTableData.map((row, index) => [row, index]));
194
200
  return filteredTableData.map((row, fallbackIndex) => {
195
- const originalIndex = sortedTableData.indexOf(row);
201
+ const originalIndex = indexMap.get(row) ?? -1;
196
202
  const stableIndex = originalIndex === -1 ? fallbackIndex : originalIndex;
197
203
  if (checkboxSelection && checkboxSelection.enabled !== false) {
198
204
  return resolveRowId(checkboxSelection, row, stableIndex);
@@ -314,6 +320,7 @@
314
320
  oninput={handleSearchInput}
315
321
  data-pw={searchConfig?.testId ?? null}
316
322
  aria-label={searchConfig?.placeholder ?? 'Search'}
323
+ autocomplete="off"
317
324
  />
318
325
  {#if searchTerm.length > 0}
319
326
  <button
@@ -353,6 +360,7 @@
353
360
  ? 'mixed'
354
361
  : headerCheckboxState === 'all'}
355
362
  aria-label="Select all rows"
363
+ aria-controls={selectableRowIds.map((rowId) => `row-checkbox-${rowId}`).join(' ')}
356
364
  onclick={toggleAllSelection}
357
365
  onkeydown={(keyboardEvent) =>
358
366
  handleCheckboxKeydown(keyboardEvent, toggleAllSelection)}
@@ -445,6 +453,7 @@
445
453
  class:checked={rowSelected}
446
454
  class:disabled={rowDisabled}
447
455
  role="checkbox"
456
+ id={`row-checkbox-${rowId}`}
448
457
  tabindex={rowDisabled ? -1 : 0}
449
458
  aria-checked={rowSelected}
450
459
  aria-disabled={rowDisabled}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/svelte-ui-components",
3
- "version": "2.61.0",
3
+ "version": "2.62.1",
4
4
  "description": "A themeable Svelte 5 UI component library with CSS custom property driven styling",
5
5
  "keywords": [
6
6
  "svelte",