layerchart 2.0.0-next.52 → 2.0.0-next.54

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 (50) hide show
  1. package/dist/components/Arc.svelte +8 -7
  2. package/dist/components/Arc.svelte.test.js +1 -1
  3. package/dist/components/ArcLabel.svelte +1 -1
  4. package/dist/components/ClipPath.svelte +1 -1
  5. package/dist/components/GeoClipPath.svelte +75 -0
  6. package/dist/components/GeoClipPath.svelte.d.ts +29 -0
  7. package/dist/components/GeoLegend.svelte +44 -13
  8. package/dist/components/GeoLegend.svelte.d.ts +11 -0
  9. package/dist/components/Hull.svelte +20 -2
  10. package/dist/components/Hull.svelte.d.ts +2 -2
  11. package/dist/components/Pie.svelte +8 -2
  12. package/dist/components/Text.svelte +63 -16
  13. package/dist/components/Text.svelte.d.ts +10 -0
  14. package/dist/components/charts/BarChart.svelte.test.js +1 -1
  15. package/dist/components/charts/DefaultTooltip.svelte.test.js +18 -18
  16. package/dist/components/charts/LineChart.svelte.test.js +1 -1
  17. package/dist/components/charts/PieChart.svelte.test.js +2 -2
  18. package/dist/components/charts/__screenshots__/BarChart.svelte.test.ts/BarChart-series-tooltip-should-use-explicit-series-colors--not-color-scale-1.png +0 -0
  19. package/dist/components/charts/__screenshots__/BarChart.svelte.test.ts/BarChart-series-tooltip-should-use-explicit-series-colors--not-color-scale-2.png +0 -0
  20. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-fade-non-highlighted-tooltip-series-items-on-hover-1.png +0 -0
  21. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-fade-non-highlighted-tooltip-series-items-on-hover-2.png +0 -0
  22. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-header-and-all-series-items-1.png +0 -0
  23. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-header-and-all-series-items-2.png +0 -0
  24. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-series-colors-in-tooltip-items-1.png +0 -0
  25. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-series-colors-in-tooltip-items-2.png +0 -0
  26. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-single-series-without-total-1.png +0 -0
  27. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-single-series-without-total-2.png +0 -0
  28. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-LineChart--multi-series--quadtree-x-mode--should-show-header-and-all-series-items-1.png +0 -0
  29. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-LineChart--multi-series--quadtree-x-mode--should-show-header-and-all-series-items-2.png +0 -0
  30. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-series-header-for-multi-series-1.png +0 -0
  31. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-series-header-for-multi-series-2.png +0 -0
  32. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-x--y--and-r-items-when-r-is-configured-1.png +0 -0
  33. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-x--y--and-r-items-when-r-is-configured-2.png +0 -0
  34. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-x-and-y-items-in-tooltip-1.png +0 -0
  35. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-x-and-y-items-in-tooltip-2.png +0 -0
  36. package/dist/components/charts/__screenshots__/LineChart.svelte.test.ts/LineChart-tooltip-should-prefer-cScale-color-over-default-series-color-when-cScale-is-explicitly-provided-1.png +0 -0
  37. package/dist/components/charts/__screenshots__/LineChart.svelte.test.ts/LineChart-tooltip-should-prefer-cScale-color-over-default-series-color-when-cScale-is-explicitly-provided-2.png +0 -0
  38. package/dist/components/charts/__screenshots__/PieChart.svelte.test.ts/PieChart-uses-hovered-slice-identity-for-implicit-tooltip-series-1.png +0 -0
  39. package/dist/components/charts/__screenshots__/PieChart.svelte.test.ts/PieChart-uses-hovered-slice-identity-for-implicit-tooltip-series-2.png +0 -0
  40. package/dist/components/index.d.ts +2 -0
  41. package/dist/components/index.js +2 -0
  42. package/dist/components/tooltip/Tooltip.svelte +145 -29
  43. package/dist/components/tooltip/Tooltip.svelte.d.ts +16 -0
  44. package/dist/components/tooltip/Tooltip.svelte.test.d.ts +1 -0
  45. package/dist/components/tooltip/Tooltip.svelte.test.js +294 -0
  46. package/dist/components/tooltip/__screenshots__/Tooltip.svelte.test.ts/Tooltip-portal-should-portal-tooltip-to-a-custom-selector-target-1.png +0 -0
  47. package/dist/components/tooltip/__screenshots__/Tooltip.svelte.test.ts/Tooltip-portal-should-portal-tooltip-to-a-custom-selector-target-2.png +0 -0
  48. package/dist/components/tooltip/__screenshots__/Tooltip.svelte.test.ts/Tooltip-portal-should-render-tooltip-inline-when-portal-is-false-1.png +0 -0
  49. package/dist/components/tooltip/__screenshots__/Tooltip.svelte.test.ts/Tooltip-portal-should-render-tooltip-inline-when-portal-is-false-2.png +0 -0
  50. package/package.json +13 -13
@@ -66,6 +66,8 @@ export { default as GeoProjection } from './GeoProjection.svelte';
66
66
  export * from './GeoProjection.svelte';
67
67
  export { default as GeoEdgeFade } from './GeoEdgeFade.svelte';
68
68
  export * from './GeoEdgeFade.svelte';
69
+ export { default as GeoClipPath } from './GeoClipPath.svelte';
70
+ export * from './GeoClipPath.svelte';
69
71
  export { default as GeoPath } from './GeoPath.svelte';
70
72
  export * from './GeoPath.svelte';
71
73
  export { default as GeoPoint } from './GeoPoint.svelte';
@@ -66,6 +66,8 @@ export { default as GeoProjection } from './GeoProjection.svelte';
66
66
  export * from './GeoProjection.svelte';
67
67
  export { default as GeoEdgeFade } from './GeoEdgeFade.svelte';
68
68
  export * from './GeoEdgeFade.svelte';
69
+ export { default as GeoClipPath } from './GeoClipPath.svelte';
70
+ export * from './GeoClipPath.svelte';
69
71
  export { default as GeoPath } from './GeoPath.svelte';
70
72
  export * from './GeoPath.svelte';
71
73
  export { default as GeoPoint } from './GeoPoint.svelte';
@@ -1,5 +1,6 @@
1
1
  <script lang="ts" module>
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { PortalOptions } from '@layerstack/svelte-actions';
3
4
  import type { Without } from '../../utils/types.js';
4
5
  import type { Placement } from '../types.js';
5
6
 
@@ -49,6 +50,14 @@
49
50
  */
50
51
  motion?: MotionProp;
51
52
 
53
+ /**
54
+ * Duration of the fade in/out transition in milliseconds.
55
+ * Set to `0` to disable the fade transition.
56
+ *
57
+ * @default 100
58
+ */
59
+ fadeDuration?: number;
60
+
52
61
  /**
53
62
  * Allow pointer events. Disabled by default to reduce accidental selection, but useful to
54
63
  * enable to allow interactive tooltips (using `locked`)
@@ -132,6 +141,15 @@
132
141
  content?: HTMLAttributes<HTMLElement>;
133
142
  };
134
143
 
144
+ /**
145
+ * Portal the tooltip outside the chart DOM hierarchy to avoid overflow clipping.
146
+ * Pass `true` to portal to `.PortalTarget` or `document.body`, a string CSS selector,
147
+ * an HTMLElement, or `false` to disable.
148
+ *
149
+ * @default true
150
+ */
151
+ portal?: PortalOptions;
152
+
135
153
  /**
136
154
  * Optionally pass the chart's context to the tooltip to get
137
155
  * type inference for the data.
@@ -146,6 +164,7 @@
146
164
  <script lang="ts" generics="T = any">
147
165
  import { fade } from 'svelte/transition';
148
166
  import { cls } from '@layerstack/tailwind';
167
+ import { portal as portalAction } from '@layerstack/svelte-actions';
149
168
 
150
169
  import { isScaleBand } from '../../utils/scales.svelte.js';
151
170
  import { getChartContext } from '../../contexts/chart.js';
@@ -157,8 +176,10 @@
157
176
  anchor = 'top-left',
158
177
  classes = {},
159
178
  contained = 'container',
179
+ fadeDuration = 100,
160
180
  motion = 'spring',
161
181
  pointerEvents = false,
182
+ portal: portalProp = true,
162
183
  variant = 'default',
163
184
  x = 'pointer',
164
185
  xOffset = x === 'pointer' ? 10 : 0,
@@ -189,12 +210,23 @@
189
210
  return value + (align === 'end' ? -additionalOffset : additionalOffset) - alignOffset;
190
211
  }
191
212
 
213
+ const isPortaled = $derived(
214
+ typeof portalProp === 'boolean' ? portalProp : portalProp?.enabled !== false
215
+ );
216
+
192
217
  const positions = $derived.by(() => {
193
218
  // if no data or tooltip size is not known yet, return null
194
219
  if (!ctx.tooltip.data || tooltipWidth === null || tooltipHeight === null) {
195
220
  return { x: null, y: null };
196
221
  }
197
222
 
223
+ // When portaled, we need the container's viewport rect to convert coordinates
224
+ const containerRect = isPortaled ? ctx.containerRef?.getBoundingClientRect() : null;
225
+ // If portaled but container rect not available yet, bail
226
+ if (isPortaled && !containerRect) {
227
+ return { x: null, y: null };
228
+ }
229
+
198
230
  const xBandOffset = isScaleBand(ctx.xScale)
199
231
  ? ctx.xScale.step() / 2 - (ctx.xScale.padding() * ctx.xScale.step()) / 2
200
232
  : 0;
@@ -279,44 +311,75 @@
279
311
  rect.right = rect.left + tooltipWidth;
280
312
 
281
313
  if (contained === 'container') {
282
- // Only attempt repositioning if not fixed (ie. `pointer`/`data`)
283
- if (typeof x !== 'number') {
284
- // Check if outside of container and swap align side accordingly
285
- if ((xAlign === 'start' || xAlign === 'center') && rect.right > ctx.containerWidth) {
286
- rect.left = alignValue(xValue, 'end', xOffset, tooltipWidth);
287
- }
288
- if ((xAlign === 'end' || xAlign === 'center') && rect.left < ctx.padding.left) {
289
- rect.left = alignValue(xValue, 'start', xOffset, tooltipWidth);
314
+ if (isPortaled && containerRect) {
315
+ // Containment in viewport coordinates
316
+ if (typeof x !== 'number') {
317
+ if (
318
+ (xAlign === 'start' || xAlign === 'center') &&
319
+ containerRect.left + rect.right > containerRect.right
320
+ ) {
321
+ rect.left = alignValue(xValue, 'end', xOffset, tooltipWidth);
322
+ }
323
+ if (
324
+ (xAlign === 'end' || xAlign === 'center') &&
325
+ containerRect.left + rect.left < containerRect.left + ctx.padding.left
326
+ ) {
327
+ rect.left = alignValue(xValue, 'start', xOffset, tooltipWidth);
328
+ }
290
329
  }
291
- }
292
- rect.right = rect.left + tooltipWidth;
330
+ rect.right = rect.left + tooltipWidth;
293
331
 
294
- if (typeof y !== 'number') {
295
- if ((yAlign === 'start' || yAlign === 'center') && rect.bottom > ctx.containerHeight) {
296
- rect.top = alignValue(yValue, 'end', yOffset, tooltipHeight);
332
+ if (typeof y !== 'number') {
333
+ if (
334
+ (yAlign === 'start' || yAlign === 'center') &&
335
+ containerRect.top + rect.bottom > containerRect.bottom
336
+ ) {
337
+ rect.top = alignValue(yValue, 'end', yOffset, tooltipHeight);
338
+ }
339
+ if (
340
+ (yAlign === 'end' || yAlign === 'center') &&
341
+ containerRect.top + rect.top < containerRect.top + ctx.padding.top
342
+ ) {
343
+ rect.top = alignValue(yValue, 'start', yOffset, tooltipHeight);
344
+ }
297
345
  }
298
- if ((yAlign === 'end' || yAlign === 'center') && rect.top < ctx.padding.top) {
299
- rect.top = alignValue(yValue, 'start', yOffset, tooltipHeight);
346
+ rect.bottom = rect.top + tooltipHeight;
347
+ } else {
348
+ // Original non-portaled container containment
349
+ if (typeof x !== 'number') {
350
+ // Check if outside of container and swap align side accordingly
351
+ if ((xAlign === 'start' || xAlign === 'center') && rect.right > ctx.containerWidth) {
352
+ rect.left = alignValue(xValue, 'end', xOffset, tooltipWidth);
353
+ }
354
+ if ((xAlign === 'end' || xAlign === 'center') && rect.left < ctx.padding.left) {
355
+ rect.left = alignValue(xValue, 'start', xOffset, tooltipWidth);
356
+ }
357
+ }
358
+ rect.right = rect.left + tooltipWidth;
359
+
360
+ if (typeof y !== 'number') {
361
+ if ((yAlign === 'start' || yAlign === 'center') && rect.bottom > ctx.containerHeight) {
362
+ rect.top = alignValue(yValue, 'end', yOffset, tooltipHeight);
363
+ }
364
+ if ((yAlign === 'end' || yAlign === 'center') && rect.top < ctx.padding.top) {
365
+ rect.top = alignValue(yValue, 'start', yOffset, tooltipHeight);
366
+ }
300
367
  }
368
+ rect.bottom = rect.top + tooltipHeight;
301
369
  }
302
- rect.bottom = rect.top + tooltipHeight;
303
370
  } else if (contained === 'window') {
304
- // Check if outside of window / viewport and swap align side accordingly
305
- // Root <div> won't be available on initial mount
306
- if (rootRef?.parentElement) {
307
- const parentViewportRect = rootRef.parentElement.getBoundingClientRect();
308
-
309
- // Only attempt repositioning if not fixed (ie. `pointer`/`data`)
371
+ if (isPortaled && containerRect) {
372
+ // Already in viewport coordinates, just clamp to window
310
373
  if (typeof x !== 'number') {
311
374
  if (
312
375
  (xAlign === 'start' || xAlign === 'center') &&
313
- parentViewportRect.left + rect.right > window.innerWidth
376
+ containerRect.left + rect.right > window.innerWidth
314
377
  ) {
315
378
  rect.left = alignValue(xValue, 'end', xOffset, tooltipWidth);
316
379
  }
317
380
  if (
318
381
  (xAlign === 'end' || xAlign === 'center') &&
319
- parentViewportRect.left + rect.left < 0
382
+ containerRect.left + rect.left < 0
320
383
  ) {
321
384
  rect.left = alignValue(xValue, 'start', xOffset, tooltipWidth);
322
385
  }
@@ -326,20 +389,67 @@
326
389
  if (typeof y !== 'number') {
327
390
  if (
328
391
  (yAlign === 'start' || yAlign === 'center') &&
329
- parentViewportRect.top + rect.bottom > window.innerHeight
392
+ containerRect.top + rect.bottom > window.innerHeight
330
393
  ) {
331
394
  rect.top = alignValue(yValue, 'end', yOffset, tooltipHeight);
332
395
  }
333
- if ((yAlign === 'end' || yAlign === 'center') && parentViewportRect.top + rect.top < 0) {
396
+ if (
397
+ (yAlign === 'end' || yAlign === 'center') &&
398
+ containerRect.top + rect.top < 0
399
+ ) {
334
400
  rect.top = alignValue(yValue, 'start', yOffset, tooltipHeight);
335
401
  }
336
402
  }
337
403
  rect.bottom = rect.top + tooltipHeight;
404
+ } else {
405
+ // Original non-portaled window containment
406
+ // Root <div> won't be available on initial mount
407
+ if (rootRef?.parentElement) {
408
+ const parentViewportRect = rootRef.parentElement.getBoundingClientRect();
409
+
410
+ // Only attempt repositioning if not fixed (ie. `pointer`/`data`)
411
+ if (typeof x !== 'number') {
412
+ if (
413
+ (xAlign === 'start' || xAlign === 'center') &&
414
+ parentViewportRect.left + rect.right > window.innerWidth
415
+ ) {
416
+ rect.left = alignValue(xValue, 'end', xOffset, tooltipWidth);
417
+ }
418
+ if (
419
+ (xAlign === 'end' || xAlign === 'center') &&
420
+ parentViewportRect.left + rect.left < 0
421
+ ) {
422
+ rect.left = alignValue(xValue, 'start', xOffset, tooltipWidth);
423
+ }
424
+ }
425
+ rect.right = rect.left + tooltipWidth;
426
+
427
+ if (typeof y !== 'number') {
428
+ if (
429
+ (yAlign === 'start' || yAlign === 'center') &&
430
+ parentViewportRect.top + rect.bottom > window.innerHeight
431
+ ) {
432
+ rect.top = alignValue(yValue, 'end', yOffset, tooltipHeight);
433
+ }
434
+ if (
435
+ (yAlign === 'end' || yAlign === 'center') &&
436
+ parentViewportRect.top + rect.top < 0
437
+ ) {
438
+ rect.top = alignValue(yValue, 'start', yOffset, tooltipHeight);
439
+ }
440
+ }
441
+ rect.bottom = rect.top + tooltipHeight;
442
+ }
338
443
  }
339
444
  }
445
+
446
+ // When portaled, convert from container-relative to viewport-relative coordinates
447
+ const offsetX = isPortaled && containerRect ? containerRect.left : 0;
448
+ const offsetY = isPortaled && containerRect ? containerRect.top : 0;
449
+
340
450
  return {
341
- x: rect.left,
342
- y: rect.top,
451
+ x: rect.left + offsetX,
452
+ y: rect.top + offsetY,
343
453
  };
344
454
  });
345
455
 
@@ -356,11 +466,13 @@
356
466
  {#if ctx.tooltip.data}
357
467
  <div
358
468
  {...props.root}
469
+ use:portalAction={portalProp}
359
470
  class={cls('lc-tooltip-root', classes.root, props.root?.class)}
360
471
  class:disablePointerEvents={pointerEvents === false}
472
+ class:portaled={isPortaled}
361
473
  style:top="{motionY.current}px"
362
474
  style:left="{motionX.current}px"
363
- transition:fade={{ duration: 100 }}
475
+ transition:fade={{ duration: fadeDuration }}
364
476
  bind:clientWidth={tooltipWidth}
365
477
  bind:clientHeight={tooltipHeight}
366
478
  bind:this={rootRef}
@@ -392,6 +504,10 @@
392
504
  z-index: 50;
393
505
  user-select: none;
394
506
 
507
+ &.portaled {
508
+ position: fixed;
509
+ }
510
+
395
511
  &.disablePointerEvents {
396
512
  pointer-events: none;
397
513
  }
@@ -1,4 +1,5 @@
1
1
  import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { PortalOptions } from '@layerstack/svelte-actions';
2
3
  import type { Without } from '../../utils/types.js';
3
4
  import type { Placement } from '../types.js';
4
5
  export type Align = 'start' | 'center' | 'end';
@@ -41,6 +42,13 @@ export type TooltipPropsWithoutHTML<T = any> = {
41
42
  * @default "spring"
42
43
  */
43
44
  motion?: MotionProp;
45
+ /**
46
+ * Duration of the fade in/out transition in milliseconds.
47
+ * Set to `0` to disable the fade transition.
48
+ *
49
+ * @default 100
50
+ */
51
+ fadeDuration?: number;
44
52
  /**
45
53
  * Allow pointer events. Disabled by default to reduce accidental selection, but useful to
46
54
  * enable to allow interactive tooltips (using `locked`)
@@ -115,6 +123,14 @@ export type TooltipPropsWithoutHTML<T = any> = {
115
123
  */
116
124
  content?: HTMLAttributes<HTMLElement>;
117
125
  };
126
+ /**
127
+ * Portal the tooltip outside the chart DOM hierarchy to avoid overflow clipping.
128
+ * Pass `true` to portal to `.PortalTarget` or `document.body`, a string CSS selector,
129
+ * an HTMLElement, or `false` to disable.
130
+ *
131
+ * @default true
132
+ */
133
+ portal?: PortalOptions;
118
134
  /**
119
135
  * Optionally pass the chart's context to the tooltip to get
120
136
  * type inference for the data.
@@ -0,0 +1 @@
1
+ export {};