@juspay/svelte-ui-components 2.106.0 → 2.106.2

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.
@@ -131,9 +131,12 @@
131
131
  if (xDomain) {
132
132
  return xDomain;
133
133
  }
134
+ // Non-finite coordinates mark gap points in sparse series; they must not
135
+ // poison the auto-domain (Math.min/max propagate NaN).
134
136
  const allX = series
135
137
  .filter((_, si) => !hiddenSeries.has(si))
136
- .flatMap((s) => s.data.map((d) => d.x));
138
+ .flatMap((s) => s.data.map((d) => d.x))
139
+ .filter((x) => Number.isFinite(x));
137
140
  if (allX.length === 0) {
138
141
  return [0, 1];
139
142
  }
@@ -145,7 +148,8 @@
145
148
  }
146
149
  const allY = series
147
150
  .filter((_, si) => !hiddenSeries.has(si))
148
- .flatMap((s) => s.data.map((d) => d.y));
151
+ .flatMap((s) => s.data.map((d) => d.y))
152
+ .filter((y) => Number.isFinite(y));
149
153
  if (allY.length === 0) {
150
154
  return [0, 1];
151
155
  }
@@ -170,7 +174,8 @@
170
174
  });
171
175
 
172
176
  let yTickCount = $derived(Math.max(2, Math.min(6, Math.floor(chartHeight / 70))));
173
- let xTickCount = $derived(Math.max(2, Math.min(8, Math.floor(chartWidth / 90))));
177
+ // Capped at 6 per the design-system line-chart spec ("Max no. of ticks should be 6").
178
+ let xTickCount = $derived(Math.max(2, Math.min(6, Math.floor(chartWidth / 90))));
174
179
  // Category positions are whole numbers — fractional tick steps would repeat labels.
175
180
  let xIntegerTicks = $derived(Boolean(xAxisCategories && xAxisCategories.length > 0));
176
181
 
@@ -222,9 +227,14 @@
222
227
  hovered === null ? null : (series[hovered.si]?.data[hovered.pi] ?? null)
223
228
  );
224
229
 
225
- let hoverLineX = $derived(
226
- hovered === null ? null : (lines[hovered.si]?.points[hovered.pi]?.x ?? null)
227
- );
230
+ let hoverLineX = $derived.by<number | null>(() => {
231
+ if (hovered === null) {
232
+ return null;
233
+ }
234
+ const x = lines[hovered.si]?.points[hovered.pi]?.x ?? null;
235
+ // A gap point (non-finite) anchors no crosshair.
236
+ return x !== null && Number.isFinite(x) ? x : null;
237
+ });
228
238
 
229
239
  // When a highlight index is active (imperative or prop), show the vertical
230
240
  // crosshair at that point even without a mouse hover.
@@ -238,7 +248,7 @@
238
248
  continue;
239
249
  }
240
250
  const point = line.points[effectiveHighlight];
241
- if (point) {
251
+ if (point && Number.isFinite(point.x)) {
242
252
  return point.x;
243
253
  }
244
254
  }
@@ -280,7 +290,9 @@
280
290
  ({ si }) =>
281
291
  !hiddenSeries.has(si) &&
282
292
  (shared || si === hovered?.si) &&
283
- series[si]?.data[hovered!.pi] != null
293
+ series[si]?.data[hovered!.pi] != null &&
294
+ // A gap point (non-finite y) has nothing to report in the tooltip.
295
+ Number.isFinite(series[si].data[hovered!.pi].y)
284
296
  )
285
297
  .map(({ p }) => ({ label: p.name, value: formatNumber(p.y), color: p.color }))
286
298
  };
@@ -296,7 +308,10 @@
296
308
  return [];
297
309
  }
298
310
  const p = line.points[hovered!.pi];
299
- return p ? [{ x: p.x, y: p.y, color: line.color }] : [];
311
+ // A gap point (non-finite) has no marker, so it gets no halo either.
312
+ return p && Number.isFinite(p.x) && Number.isFinite(p.y)
313
+ ? [{ x: p.x, y: p.y, color: line.color }]
314
+ : [];
300
315
  });
301
316
  });
302
317
 
@@ -408,6 +423,11 @@
408
423
  let nearestPi = 0;
409
424
  let nearestXDist = Infinity;
410
425
  for (let i = 0; i < reference.length; i++) {
426
+ // Gap points (non-finite) are not hoverable — NaN distances would never
427
+ // win the comparison, but skipping keeps nearestPi from defaulting to one.
428
+ if (!Number.isFinite(reference[i].x) || !Number.isFinite(reference[i].y)) {
429
+ continue;
430
+ }
411
431
  const px = xScale(reference[i].x);
412
432
  const dist = Math.abs(px - plotX);
413
433
  if (dist < nearestXDist) {
@@ -619,7 +639,7 @@
619
639
  stroke-width={strokeWidth}
620
640
  fill="none"
621
641
  />
622
- {#if line.points.length === 1 && !showDots}
642
+ {#if line.points.length === 1 && !showDots && Number.isFinite(line.points[0].x) && Number.isFinite(line.points[0].y)}
623
643
  <circle
624
644
  class="single-point"
625
645
  class:dimmed={isLineDimmed(si)}
@@ -631,21 +651,24 @@
631
651
  {/if}
632
652
  {#if showDots}
633
653
  {#each line.points as point, pi (pi)}
634
- <circle
635
- class="dot"
636
- class:dimmed={isDotDimmed(si, pi)}
637
- class:highlighted={isHighlightedDot(si, pi)}
638
- cx={point.x}
639
- cy={point.y}
640
- r={isHighlightedDot(si, pi) ? dotRadius * 1.5 : dotRadius}
641
- fill={line.color}
642
- />
654
+ <!-- Gap points (non-finite) render no marker. -->
655
+ {#if Number.isFinite(point.x) && Number.isFinite(point.y)}
656
+ <circle
657
+ class="dot"
658
+ class:dimmed={isDotDimmed(si, pi)}
659
+ class:highlighted={isHighlightedDot(si, pi)}
660
+ cx={point.x}
661
+ cy={point.y}
662
+ r={isHighlightedDot(si, pi) ? dotRadius * 1.5 : dotRadius}
663
+ fill={line.color}
664
+ />
665
+ {/if}
643
666
  {/each}
644
667
  {/if}
645
668
  {#if showValues && pointLabelPlacements[si]}
646
669
  {#each line.points as _point, pi (pi)}
647
670
  {@const pl = pointLabelPlacements[si][pi]}
648
- {#if pl?.visible}
671
+ {#if pl?.visible && Number.isFinite(pl.x) && Number.isFinite(pl.y)}
649
672
  <text
650
673
  class="point-value"
651
674
  x={pl.x}
@@ -30,6 +30,31 @@ export function arcPath(cx, cy, innerR, outerR, startAngle, endAngle) {
30
30
  }
31
31
  return `M ${cx} ${cy}` + `L ${x1} ${y1}` + `A ${outerR} ${outerR} 0 ${largeArc} 1 ${x2} ${y2}Z`;
32
32
  }
33
+ /**
34
+ * Splits a point run into finite sub-runs. A non-finite coordinate (NaN /
35
+ * Infinity — typically a missing value in a sparse series) acts as a break:
36
+ * the line renders as separate segments with a gap at the missing point
37
+ * instead of one poisoned path — a single NaN coordinate in `d` makes the
38
+ * browser drop everything from that command onward, visually cutting the
39
+ * line mid-chart.
40
+ */
41
+ function finiteSegments(points) {
42
+ const segments = [];
43
+ let current = [];
44
+ for (const point of points) {
45
+ if (Number.isFinite(point.x) && Number.isFinite(point.y)) {
46
+ current.push(point);
47
+ }
48
+ else if (current.length > 0) {
49
+ segments.push(current);
50
+ current = [];
51
+ }
52
+ }
53
+ if (current.length > 0) {
54
+ segments.push(current);
55
+ }
56
+ return segments;
57
+ }
33
58
  function linearPath(points) {
34
59
  if (points.length === 0) {
35
60
  return '';
@@ -113,7 +138,7 @@ function monotonePath(points) {
113
138
  }
114
139
  return d;
115
140
  }
116
- export function linePath(points, curve = 'linear') {
141
+ function linePathSegment(points, curve) {
117
142
  if (points.length < 2) {
118
143
  return points.length === 1 ? `M ${points[0].x} ${points[0].y}` : '';
119
144
  }
@@ -128,15 +153,27 @@ export function linePath(points, curve = 'linear') {
128
153
  return linearPath(points);
129
154
  }
130
155
  }
131
- export function areaPath(points, baseline, curve = 'linear') {
156
+ export function linePath(points, curve = 'linear') {
157
+ return finiteSegments(points)
158
+ .map((segment) => linePathSegment(segment, curve))
159
+ .filter((d) => d !== '')
160
+ .join(' ');
161
+ }
162
+ function areaPathSegment(points, baseline, curve) {
132
163
  if (points.length === 0) {
133
164
  return '';
134
165
  }
135
- const topPath = linePath(points, curve);
166
+ const topPath = linePathSegment(points, curve);
136
167
  const lastPoint = points[points.length - 1];
137
168
  const firstPoint = points[0];
138
169
  return topPath + ` L ${lastPoint.x} ${baseline} L ${firstPoint.x} ${baseline} Z`;
139
170
  }
171
+ export function areaPath(points, baseline, curve = 'linear') {
172
+ return finiteSegments(points)
173
+ .map((segment) => areaPathSegment(segment, baseline, curve))
174
+ .filter((d) => d !== '')
175
+ .join(' ');
176
+ }
140
177
  /**
141
178
  * Builds an SVG path string for a rectangle with independently-controlled
142
179
  * corner radii. Each radius is clamped to Math.min(r, w/2, h/2) so the
@@ -36,11 +36,39 @@ export function computeLinearTicks(domain, count = 5, integer = false) {
36
36
  if (integer && step < 1) {
37
37
  step = 1;
38
38
  }
39
- const ticks = [];
40
- let tick = Math.ceil(min / step) * step;
41
- while (tick <= max + step * 0.001) {
42
- ticks.push(Math.round(tick * 1e10) / 1e10);
43
- tick += step;
39
+ const buildTicks = (tickStep) => {
40
+ const ticks = [];
41
+ let tick = Math.ceil(min / tickStep) * tickStep;
42
+ while (tick <= max + tickStep * 0.001) {
43
+ ticks.push(Math.round(tick * 1e10) / 1e10);
44
+ tick += tickStep;
45
+ }
46
+ return ticks;
47
+ };
48
+ // The next rung up the 1-2-5-10 ladder, e.g. 0.2 → 0.5, 2 → 5, 5 → 10.
49
+ const nextNiceStep = (current) => {
50
+ const stepExp = Math.floor(Math.log10(current) + 1e-10);
51
+ const stepBase = Math.pow(10, stepExp);
52
+ const mantissa = current / stepBase;
53
+ if (mantissa < 1.5) {
54
+ return stepBase * 2;
55
+ }
56
+ if (mantissa < 3.5) {
57
+ return stepBase * 5;
58
+ }
59
+ return stepBase * 10;
60
+ };
61
+ // `count` is a hard maximum (design-system chart spec: "max 6 ticks"), not a
62
+ // hint — the nice-step ladder can overshoot it (range 14 at count 6 picks
63
+ // step 2 → 7 ticks). Escalate the step until the ticks fit: category axes
64
+ // step through whole numbers (any integer stride is a valid category step,
65
+ // so 15 days at max 6 lands on the natural every-3rd-day), numeric axes
66
+ // climb the ladder so tick values stay round.
67
+ let ticks = buildTicks(step);
68
+ const maxTicks = Math.max(2, count);
69
+ while (ticks.length > maxTicks) {
70
+ step = integer ? Math.floor(step) + 1 : nextNiceStep(step);
71
+ ticks = buildTicks(step);
44
72
  }
45
73
  return ticks;
46
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/svelte-ui-components",
3
- "version": "2.106.0",
3
+ "version": "2.106.2",
4
4
  "description": "A themeable Svelte 5 UI component library with CSS custom property driven styling",
5
5
  "keywords": [
6
6
  "svelte",