layerchart 2.0.0-next.50 → 2.0.0-next.51

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 (49) hide show
  1. package/dist/components/Axis.svelte +25 -0
  2. package/dist/components/Axis.svelte.d.ts +10 -0
  3. package/dist/components/Circle.svelte +82 -59
  4. package/dist/components/Ellipse.svelte +83 -64
  5. package/dist/components/GeoRaster.svelte +311 -0
  6. package/dist/components/GeoRaster.svelte.d.ts +61 -0
  7. package/dist/components/Grid.svelte +15 -0
  8. package/dist/components/Grid.svelte.d.ts +5 -0
  9. package/dist/components/Image.svelte +2 -2
  10. package/dist/components/Line.svelte +82 -62
  11. package/dist/components/Points.svelte +2 -2
  12. package/dist/components/Polygon.svelte +92 -56
  13. package/dist/components/Rect.svelte +113 -64
  14. package/dist/components/Rule.svelte +2 -0
  15. package/dist/components/Sankey.svelte +0 -2
  16. package/dist/components/Text.svelte +83 -52
  17. package/dist/components/charts/PieChart.svelte +2 -2
  18. package/dist/components/index.d.ts +2 -0
  19. package/dist/components/index.js +2 -0
  20. package/dist/components/layers/Canvas.svelte +65 -48
  21. package/dist/components/layers/Canvas.svelte.d.ts +10 -0
  22. package/dist/contexts/canvas.d.ts +3 -0
  23. package/dist/server/ContextCapture.svelte +30 -0
  24. package/dist/server/ContextCapture.svelte.d.ts +8 -0
  25. package/dist/server/ServerChart.svelte +26 -0
  26. package/dist/server/ServerChart.svelte.d.ts +11 -0
  27. package/dist/server/TestBarChart.svelte +35 -0
  28. package/dist/server/TestBarChart.svelte.d.ts +14 -0
  29. package/dist/server/TestLineChart.svelte +35 -0
  30. package/dist/server/TestLineChart.svelte.d.ts +14 -0
  31. package/dist/server/captureStore.d.ts +8 -0
  32. package/dist/server/captureStore.js +18 -0
  33. package/dist/server/index.d.ts +137 -0
  34. package/dist/server/index.js +141 -0
  35. package/dist/server/renderChart.ssr.test.d.ts +1 -0
  36. package/dist/server/renderChart.ssr.test.js +205 -0
  37. package/dist/server/renderTree.d.ts +8 -0
  38. package/dist/server/renderTree.js +29 -0
  39. package/dist/states/__screenshots__/chart.svelte.test.ts/ChartState-geo-projection-skips-markInfo-should-not-derive-x-y-accessors-from-marks-when-geo-projection-is-active-1.png +0 -0
  40. package/dist/states/__screenshots__/chart.svelte.test.ts/ChartState-geo-projection-skips-markInfo-should-not-derive-x-y-accessors-from-marks-when-geo-projection-is-active-2.png +0 -0
  41. package/dist/states/chart.svelte.d.ts +5 -1
  42. package/dist/states/chart.svelte.js +18 -3
  43. package/dist/states/chart.svelte.test.js +110 -0
  44. package/dist/states/geo.svelte.d.ts +5 -1
  45. package/dist/states/geo.svelte.js +80 -68
  46. package/dist/utils/canvas.js +29 -10
  47. package/dist/utils/canvas.svelte.test.js +2 -2
  48. package/dist/utils/motion.svelte.js +14 -0
  49. package/package.json +7 -1
@@ -238,7 +238,12 @@
238
238
  import { createDataMotionMap } from '../utils/motion.svelte.js';
239
239
  import { getStringWidth, truncateText, type TruncateTextOptions } from '../utils/string.js';
240
240
  import { getComputedStyles, renderText, type ComputedStylesOptions } from '../utils/canvas.js';
241
- import { resolveDataProp, resolveColorProp, resolveGeoDataPair, resolveStyleProp } from '../utils/dataProp.js';
241
+ import {
242
+ resolveDataProp,
243
+ resolveColorProp,
244
+ resolveGeoDataPair,
245
+ resolveStyleProp,
246
+ } from '../utils/dataProp.js';
242
247
  import { getGeoContext } from '../contexts/geo.js';
243
248
  import { get } from '@layerstack/utils';
244
249
  import { chartDataArray } from '../utils/common.js';
@@ -294,9 +299,7 @@
294
299
  const geo = getGeoContext();
295
300
 
296
301
  // Data to iterate over in data mode
297
- const resolvedData: any[] = $derived(
298
- dataMode ? (dataProp ?? chartDataArray(chartCtx.data)) : []
299
- );
302
+ const resolvedData: any[] = $derived(dataMode ? (dataProp ?? chartDataArray(chartCtx.data)) : []);
300
303
 
301
304
  // Resolve position for a data item
302
305
  function resolveTextPosition(d: any) {
@@ -401,7 +404,11 @@
401
404
  const motionValue = createMotion(
402
405
  typeof value === 'number' ? value : 0,
403
406
  () => (typeof value === 'number' ? value : 0),
404
- typeof value === 'number' && motion ? (typeof motion === 'object' && 'type' in motion ? motion : undefined) : undefined
407
+ typeof value === 'number' && motion
408
+ ? typeof motion === 'object' && 'type' in motion
409
+ ? motion
410
+ : undefined
411
+ : undefined
405
412
  );
406
413
 
407
414
  // Handle null and convert `\n` strings back to newline characters
@@ -549,6 +556,13 @@
549
556
  motion
550
557
  );
551
558
 
559
+ const staticFill = $derived(typeof fill === 'string' ? fill : undefined);
560
+ const staticFillOpacity = $derived(typeof fillOpacity === 'number' ? fillOpacity : undefined);
561
+ const staticStroke = $derived(typeof stroke === 'string' ? stroke : undefined);
562
+ const staticStrokeWidth = $derived(typeof strokeWidth === 'number' ? strokeWidth : undefined);
563
+ const staticOpacity = $derived(typeof opacity === 'number' ? opacity : undefined);
564
+ const staticClassName = $derived(typeof className === 'string' ? className : undefined);
565
+
552
566
  function render(
553
567
  ctx: CanvasRenderingContext2D,
554
568
  styleOverrides: ComputedStylesOptions | undefined
@@ -562,20 +576,33 @@
562
576
  itemClass?: string | undefined
563
577
  ) {
564
578
  return styleOverrides
565
- ? merge({ styles: { strokeWidth: itemStrokeWidth ?? (typeof strokeWidth === 'number' ? strokeWidth : undefined) } }, styleOverrides)
579
+ ? merge(
580
+ {
581
+ styles: {
582
+ strokeWidth:
583
+ itemStrokeWidth ?? (typeof strokeWidth === 'number' ? strokeWidth : undefined),
584
+ },
585
+ },
586
+ styleOverrides
587
+ )
566
588
  : {
567
589
  styles: {
568
590
  fill: itemFill ?? fill,
569
- fillOpacity: itemFillOpacity ?? (typeof fillOpacity === 'number' ? fillOpacity : undefined),
591
+ fillOpacity:
592
+ itemFillOpacity ?? (typeof fillOpacity === 'number' ? fillOpacity : undefined),
570
593
  stroke: itemStroke ?? stroke,
571
- strokeWidth: itemStrokeWidth ?? (typeof strokeWidth === 'number' ? strokeWidth : undefined),
594
+ strokeWidth:
595
+ itemStrokeWidth ?? (typeof strokeWidth === 'number' ? strokeWidth : undefined),
572
596
  opacity: itemOpacity ?? (typeof opacity === 'number' ? opacity : undefined),
573
597
  paintOrder: 'stroke',
574
598
  // Only include textAnchor in constantStyles when explicitly non-default,
575
599
  // so that CSS class-based text-anchor (e.g. [text-anchor:middle]) can take effect
576
600
  ...(textAnchor !== 'start' ? { textAnchor } : {}),
577
601
  },
578
- classes: cls('lc-text', itemClass ?? (typeof className === 'string' ? className : undefined)),
602
+ classes: cls(
603
+ 'lc-text',
604
+ itemClass ?? (typeof className === 'string' ? className : undefined)
605
+ ),
579
606
  style: restProps.style as string | undefined,
580
607
  };
581
608
  }
@@ -584,8 +611,7 @@
584
611
  const baseStyles = getTextStyles();
585
612
  const computedStyles = getComputedStyles(ctx.canvas, baseStyles);
586
613
  ctx.font = `${computedStyles.fontSize} ${computedStyles.fontFamily}`;
587
- const textAlign =
588
- textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'end' : 'start';
614
+ const textAlign = textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'end' : 'start';
589
615
  ctx.textAlign = textAlign;
590
616
 
591
617
  for (const item of resolvedItems) {
@@ -596,7 +622,14 @@
596
622
  const resolvedStrokeWidth = resolveStyleProp(strokeWidth, item.d);
597
623
  const resolvedOpacity = resolveStyleProp(opacity, item.d);
598
624
  const resolvedClass = resolveStyleProp(className, item.d);
599
- const itemStyles = getTextStyles(resolvedFill, resolvedStroke, resolvedFillOpacity, resolvedStrokeWidth, resolvedOpacity, resolvedClass);
625
+ const itemStyles = getTextStyles(
626
+ resolvedFill,
627
+ resolvedStroke,
628
+ resolvedFillOpacity,
629
+ resolvedStrokeWidth,
630
+ resolvedOpacity,
631
+ resolvedClass
632
+ );
600
633
  ctx.save();
601
634
  if (rotate !== undefined) {
602
635
  const radians = degreesToRadians(rotate);
@@ -634,8 +667,7 @@
634
667
 
635
668
  ctx.font = `${computedStyles.fontSize} ${computedStyles.fontFamily}`;
636
669
 
637
- const textAlign =
638
- textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'end' : 'start';
670
+ const textAlign = textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'end' : 'start';
639
671
  ctx.textAlign = textAlign;
640
672
 
641
673
  for (let index = 0; index < wordsByLines.length; index++) {
@@ -668,26 +700,29 @@
668
700
  color: typeof fill === 'string' ? fill : undefined,
669
701
  };
670
702
  },
671
- canvasRender: layerCtx === 'canvas' ? {
672
- render,
673
- deps: () => [
674
- dataMode,
675
- dataMode ? resolvedItems : null,
676
- value,
677
- motionX.current,
678
- motionY.current,
679
- fillKey!.current,
680
- strokeKey!.current,
681
- strokeWidth,
682
- opacity,
683
- className,
684
- truncateConfig,
685
- rotate,
686
- lineHeight,
687
- textAnchor,
688
- verticalAnchor,
689
- ],
690
- } : undefined,
703
+ canvasRender:
704
+ layerCtx === 'canvas'
705
+ ? {
706
+ render,
707
+ deps: () => [
708
+ dataMode,
709
+ dataMode ? resolvedItems : null,
710
+ value,
711
+ motionX.current,
712
+ motionY.current,
713
+ fillKey!.current,
714
+ strokeKey!.current,
715
+ strokeWidth,
716
+ opacity,
717
+ className,
718
+ truncateConfig,
719
+ rotate,
720
+ lineHeight,
721
+ textAnchor,
722
+ verticalAnchor,
723
+ ],
724
+ }
725
+ : undefined,
691
726
  });
692
727
  </script>
693
728
 
@@ -717,11 +752,7 @@
717
752
  opacity={resolvedOpacity}
718
753
  class={['lc-text', resolvedClass]}
719
754
  >
720
- <tspan
721
- x={item.x}
722
- dy={dataModeStartDy}
723
- class="lc-text-tspan"
724
- >
755
+ <tspan x={item.x} dy={dataModeStartDy} class="lc-text-tspan">
725
756
  {text}
726
757
  </tspan>
727
758
  </text>
@@ -741,13 +772,13 @@
741
772
  bind:this={ref}
742
773
  {dy}
743
774
  {...restProps}
744
- fill={fill as string}
745
- fill-opacity={fillOpacity as number}
746
- stroke={stroke as string}
747
- stroke-width={strokeWidth as number}
748
- opacity={opacity as number}
775
+ fill={staticFill}
776
+ fill-opacity={staticFillOpacity}
777
+ stroke={staticStroke}
778
+ stroke-width={staticStrokeWidth}
779
+ opacity={staticOpacity}
749
780
  transform={transformProp}
750
- class={['lc-text', className as string]}
781
+ class={['lc-text', staticClassName]}
751
782
  >
752
783
  <textPath
753
784
  style="text-anchor: {textAnchor};"
@@ -768,12 +799,12 @@
768
799
  text-anchor={textAnchor}
769
800
  dominant-baseline={dominantBaseline}
770
801
  {...restProps}
771
- fill={fill as string}
772
- fill-opacity={fillOpacity as number}
773
- stroke={stroke as string}
774
- stroke-width={strokeWidth as number}
775
- opacity={opacity as number}
776
- class={['lc-text', className as string]}
802
+ fill={staticFill}
803
+ fill-opacity={staticFillOpacity}
804
+ stroke={staticStroke}
805
+ stroke-width={staticStrokeWidth}
806
+ opacity={staticOpacity}
807
+ class={['lc-text', staticClassName]}
777
808
  >
778
809
  {#each wordsByLines as line, index}
779
810
  <tspan
@@ -831,7 +862,7 @@
831
862
  {textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'right' : 'left'}"
832
863
  style:white-space="pre-wrap"
833
864
  style:line-height={lineHeight}
834
- class={['lc-text', className as string]}
865
+ class={['lc-text', staticClassName]}
835
866
  >
836
867
  {textValue}
837
868
  </div>
@@ -231,7 +231,7 @@
231
231
  // Reading context.series.allSeriesData here would create a derived_references_self cycle:
232
232
  // SeriesState.#series → ChartState.props → data={visibleData} → chartData → context.series.allSeriesData → #series
233
233
  const chartData = $derived.by(() => {
234
- const seriesData = series.flatMap((s) => s.data ?? []);
234
+ const seriesData = series.flatMap((s) => ('data' in s ? s.data : undefined) ?? []);
235
235
  return (seriesData.length > 0 ? seriesData : chartDataArray(data)) as Array<TData>;
236
236
  });
237
237
 
@@ -245,7 +245,7 @@
245
245
 
246
246
  // Compute series colors locally to avoid derived_references_self cycle through context.series.allSeriesColors
247
247
  const allSeriesColors = $derived(
248
- series.map((s) => s.color).filter((c) => c != null) as string[]
248
+ series.map((s) => ('color' in s ? s.color : undefined)).filter((c) => c != null) as string[]
249
249
  );
250
250
 
251
251
  // Custom tickFormat for PieChart legends - uses data labels instead of series labels
@@ -68,6 +68,8 @@ export { default as GeoPath } from './GeoPath.svelte';
68
68
  export * from './GeoPath.svelte';
69
69
  export { default as GeoPoint } from './GeoPoint.svelte';
70
70
  export * from './GeoPoint.svelte';
71
+ export { default as GeoRaster } from './GeoRaster.svelte';
72
+ export * from './GeoRaster.svelte';
71
73
  export { default as GeoSpline } from './GeoSpline.svelte';
72
74
  export * from './GeoSpline.svelte';
73
75
  export { default as GeoTile } from './GeoTile.svelte';
@@ -68,6 +68,8 @@ export { default as GeoPath } from './GeoPath.svelte';
68
68
  export * from './GeoPath.svelte';
69
69
  export { default as GeoPoint } from './GeoPoint.svelte';
70
70
  export * from './GeoPoint.svelte';
71
+ export { default as GeoRaster } from './GeoRaster.svelte';
72
+ export * from './GeoRaster.svelte';
71
73
  export { default as GeoSpline } from './GeoSpline.svelte';
72
74
  export * from './GeoSpline.svelte';
73
75
  export { default as GeoTile } from './GeoTile.svelte';
@@ -1,5 +1,11 @@
1
1
  <script lang="ts" module>
2
2
  import type { ComponentNode } from '../../contexts/chart.js';
3
+ import type { ChartState } from '../../states/chart.svelte.js';
4
+
5
+ type SSRCaptureTarget = {
6
+ chartState?: ChartState<any, any, any>;
7
+ rootNode?: ComponentNode;
8
+ };
3
9
 
4
10
  export type CanvasPropsWithoutHTML = {
5
11
  /**
@@ -80,6 +86,12 @@
80
86
  */
81
87
  debug?: boolean;
82
88
 
89
+ /** @internal Server-side capture target used by layerchart/server. */
90
+ ssrCapture?: SSRCaptureTarget;
91
+
92
+ /** @internal Server-side capture callback used by layerchart/server. */
93
+ ssrCaptureCallback?: (data: SSRCaptureTarget) => void;
94
+
83
95
  children?: Snippet<
84
96
  [{ ref: HTMLCanvasElement; canvasContext: CanvasRenderingContext2D | undefined }]
85
97
  >;
@@ -87,7 +99,6 @@
87
99
 
88
100
  export type CanvasProps = CanvasPropsWithoutHTML &
89
101
  Without<HTMLCanvasAttributes, CanvasPropsWithoutHTML>;
90
-
91
102
  </script>
92
103
 
93
104
  <script lang="ts">
@@ -107,10 +118,13 @@
107
118
  type CanvasContextValue,
108
119
  type ComponentRender,
109
120
  } from '../../contexts/canvas.js';
121
+ import { renderTree } from '../../server/renderTree.js';
110
122
 
111
123
  let {
112
124
  ref: refProp = $bindable(),
113
125
  canvasContext: canvasContextProp = $bindable(),
126
+ ssrCapture,
127
+ ssrCaptureCallback,
114
128
  willReadFrequently = false,
115
129
  debug = false,
116
130
  zIndex = 0,
@@ -183,9 +197,10 @@
183
197
  // Bubble up to ancestor groups
184
198
  let ancestor = node?.parent;
185
199
  while (ancestor) {
186
- const handler = ancestor.kind === 'group'
187
- ? ancestor.canvasRender?.events?.[eventName] as ((e: Event) => void) | null | undefined
188
- : undefined;
200
+ const handler =
201
+ ancestor.kind === 'group'
202
+ ? (ancestor.canvasRender?.events?.[eventName] as ((e: Event) => void) | null | undefined)
203
+ : undefined;
189
204
  handler?.(e);
190
205
  ancestor = ancestor.parent;
191
206
  }
@@ -225,21 +240,23 @@
225
240
  */
226
241
 
227
242
  // Invalidate/redraw if color scheme changes, either via browser `prefers-color-scheme` (including emulation) or by changing `<html class="dark">` or `<html data-theme="...">`
228
- const { dark } = new MediaQueryPresets();
229
- watch(
230
- () => dark.current,
231
- () => {
232
- canvasContext.invalidate();
233
- }
234
- );
235
- useMutationObserver(
236
- () => document.documentElement,
237
- () => canvasContext.invalidate(),
238
- {
239
- attributes: true,
240
- attributeFilter: ['class', 'data-theme'],
241
- }
242
- );
243
+ if (typeof window !== 'undefined') {
244
+ const { dark } = new MediaQueryPresets();
245
+ watch(
246
+ () => dark.current,
247
+ () => {
248
+ canvasContext.invalidate();
249
+ }
250
+ );
251
+ useMutationObserver(
252
+ () => document.documentElement,
253
+ () => canvasContext.invalidate(),
254
+ {
255
+ attributes: true,
256
+ attributeFilter: ['class', 'data-theme'],
257
+ }
258
+ );
259
+ }
243
260
 
244
261
  onMount(() => {
245
262
  context = ref?.getContext('2d', { willReadFrequently }) as CanvasRenderingContext2D;
@@ -310,33 +327,6 @@
310
327
  pendingInvalidation = false;
311
328
  }
312
329
 
313
- /**
314
- * Recursively render the component tree.
315
- * Group nodes: save → render (translate/opacity) → recurse children → restore
316
- * Leaf nodes: save → render → restore
317
- */
318
- function renderTree(canvasCtx: CanvasRenderingContext2D, node: ComponentNode) {
319
- if (node.kind === 'group' && node.canvasRender) {
320
- // Group: save state, apply transform, render children, restore
321
- canvasCtx.save();
322
- node.canvasRender.render(canvasCtx);
323
- for (const child of node.children) {
324
- renderTree(canvasCtx, child);
325
- }
326
- canvasCtx.restore();
327
- } else if (node.canvasRender) {
328
- // Leaf mark: save, render, restore
329
- canvasCtx.save();
330
- node.canvasRender.render(canvasCtx);
331
- canvasCtx.restore();
332
- } else {
333
- // Non-rendering node (e.g. root, composite-mark): just recurse children
334
- for (const child of node.children) {
335
- renderTree(canvasCtx, child);
336
- }
337
- }
338
- }
339
-
340
330
  function nodeHasEvents(node: ComponentNode) {
341
331
  return node.canvasRender?.events && Object.values(node.canvasRender.events).some((d) => d);
342
332
  }
@@ -345,7 +335,11 @@
345
335
  * Recursively render the hit canvas tree for pointer event detection.
346
336
  * Renders components that have event handlers (or whose ancestor group does), using unique colors.
347
337
  */
348
- function renderHitTree(hitCtx: CanvasRenderingContext2D, node: ComponentNode, ancestorHasEvents = false) {
338
+ function renderHitTree(
339
+ hitCtx: CanvasRenderingContext2D,
340
+ node: ComponentNode,
341
+ ancestorHasEvents = false
342
+ ) {
349
343
  if (node.kind === 'group' && node.canvasRender) {
350
344
  const groupHasEvents = ancestorHasEvents || nodeHasEvents(node);
351
345
  // Group: apply transform, recurse children (scoped by save/restore)
@@ -383,11 +377,33 @@
383
377
 
384
378
  function invalidate() {
385
379
  if (pendingInvalidation) return;
380
+ if (typeof requestAnimationFrame === 'undefined') return;
386
381
  pendingInvalidation = true;
387
382
  frameId = requestAnimationFrame(update);
388
383
  }
389
384
 
390
- return { register, invalidate };
385
+ function getRootNode() {
386
+ return rootNode;
387
+ }
388
+
389
+ return { register, invalidate, getRootNode };
390
+ }
391
+
392
+ function captureSSR() {
393
+ if (typeof window !== 'undefined') return '';
394
+ if (!ssrCapture && !ssrCaptureCallback) return '';
395
+
396
+ const captured: SSRCaptureTarget = {
397
+ chartState: ctx,
398
+ rootNode,
399
+ };
400
+
401
+ if (ssrCapture) {
402
+ Object.assign(ssrCapture, captured);
403
+ }
404
+
405
+ ssrCaptureCallback?.(captured);
406
+ return '';
391
407
  }
392
408
 
393
409
  const canvasContext = createCanvasContext();
@@ -457,6 +473,7 @@
457
473
  <canvas bind:this={hitCanvasElement} class="lc-hit-canvas" class:debug></canvas>
458
474
 
459
475
  {@render children?.({ ref, canvasContext: context })}
476
+ {captureSSR()}
460
477
 
461
478
  <style>
462
479
  @layer base {
@@ -1,3 +1,9 @@
1
+ import type { ComponentNode } from '../../contexts/chart.js';
2
+ import type { ChartState } from '../../states/chart.svelte.js';
3
+ type SSRCaptureTarget = {
4
+ chartState?: ChartState<any, any, any>;
5
+ rootNode?: ComponentNode;
6
+ };
1
7
  export type CanvasPropsWithoutHTML = {
2
8
  /**
3
9
  * The `<canvas>` tag. Useful for bindings.
@@ -67,6 +73,10 @@ export type CanvasPropsWithoutHTML = {
67
73
  * @default false
68
74
  */
69
75
  debug?: boolean;
76
+ /** @internal Server-side capture target used by layerchart/server. */
77
+ ssrCapture?: SSRCaptureTarget;
78
+ /** @internal Server-side capture callback used by layerchart/server. */
79
+ ssrCaptureCallback?: (data: SSRCaptureTarget) => void;
70
80
  children?: Snippet<[
71
81
  {
72
82
  ref: HTMLCanvasElement;
@@ -1,5 +1,6 @@
1
1
  import type { MouseEventHandler, PointerEventHandler, TouchEventHandler } from 'svelte/elements';
2
2
  import type { ComputedStylesOptions } from '../utils/canvas.js';
3
+ import type { ComponentNode } from '../states/chart.svelte.js';
3
4
  export type ComponentRender<T extends Element = Element> = {
4
5
  render: (ctx: CanvasRenderingContext2D, styleOverrides?: ComputedStylesOptions) => any;
5
6
  events?: {
@@ -26,6 +27,8 @@ export type CanvasContextValue = {
26
27
  */
27
28
  register<T extends Element>(component: ComponentRender<T>): () => void;
28
29
  invalidate(): void;
30
+ /** Get the root ComponentNode of the canvas render tree. Used for server-side rendering. */
31
+ getRootNode?: () => ComponentNode;
29
32
  };
30
33
  export declare function getCanvasContext(): CanvasContextValue;
31
34
  export declare function setCanvasContext(context: CanvasContextValue): CanvasContextValue;
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import { getChartContext } from '../contexts/chart.js';
3
+ import { getCanvasContext } from '../contexts/canvas.js';
4
+ import { setSSRCapture, type CaptureTarget } from './captureStore.js';
5
+
6
+ let {
7
+ capture,
8
+ onCapture,
9
+ }: {
10
+ capture?: CaptureTarget;
11
+ onCapture?: (data: CaptureTarget) => void;
12
+ } = $props();
13
+
14
+ const chartState = getChartContext();
15
+ const canvasCtx = getCanvasContext();
16
+
17
+ const captured = {
18
+ chartState,
19
+ rootNode: canvasCtx.getRootNode?.(),
20
+ };
21
+
22
+ if (typeof window === 'undefined') {
23
+ if (capture) {
24
+ Object.assign(capture, captured);
25
+ }
26
+
27
+ setSSRCapture(captured);
28
+ onCapture?.(captured);
29
+ }
30
+ </script>
@@ -0,0 +1,8 @@
1
+ import { type CaptureTarget } from './captureStore.js';
2
+ type $$ComponentProps = {
3
+ capture?: CaptureTarget;
4
+ onCapture?: (data: CaptureTarget) => void;
5
+ };
6
+ declare const ContextCapture: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type ContextCapture = ReturnType<typeof ContextCapture>;
8
+ export default ContextCapture;
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import Chart from '../components/Chart.svelte';
4
+ import type { ChartProps } from '../components/Chart.svelte';
5
+ import Canvas from '../components/layers/Canvas.svelte';
6
+ import type { CaptureTarget } from './captureStore.js';
7
+
8
+ let {
9
+ children,
10
+ capture,
11
+ onCapture,
12
+ ...chartProps
13
+ }: {
14
+ children?: Snippet;
15
+ capture?: CaptureTarget;
16
+ onCapture?: (data: CaptureTarget) => void;
17
+ } & Omit<ChartProps<any>, 'children'> = $props();
18
+ </script>
19
+
20
+ <Chart ssr={true} {...chartProps}>
21
+ <Canvas ssrCapture={capture} ssrCaptureCallback={onCapture}>
22
+ {#if children}
23
+ {@render children()}
24
+ {/if}
25
+ </Canvas>
26
+ </Chart>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { ChartProps } from '../components/Chart.svelte';
3
+ import type { CaptureTarget } from './captureStore.js';
4
+ type $$ComponentProps = {
5
+ children?: Snippet;
6
+ capture?: CaptureTarget;
7
+ onCapture?: (data: CaptureTarget) => void;
8
+ } & Omit<ChartProps<any>, 'children'>;
9
+ declare const ServerChart: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type ServerChart = ReturnType<typeof ServerChart>;
11
+ export default ServerChart;
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ import { scaleBand } from 'd3-scale';
3
+ import ServerChart from './ServerChart.svelte';
4
+ import type { CaptureTarget } from './captureStore.js';
5
+ import Bars from '../components/Bars.svelte';
6
+
7
+ let {
8
+ data,
9
+ width,
10
+ height,
11
+ capture,
12
+ onCapture
13
+ }: {
14
+ data: { category: string; value: number }[];
15
+ width: number;
16
+ height: number;
17
+ capture?: CaptureTarget;
18
+ onCapture?: (data: CaptureTarget) => void;
19
+ } = $props();
20
+ </script>
21
+
22
+ <ServerChart
23
+ {capture}
24
+ {onCapture}
25
+ {width}
26
+ {height}
27
+ {data}
28
+ x="category"
29
+ xScale={scaleBand().paddingInner(0.2).paddingOuter(0.1)}
30
+ y="value"
31
+ yDomain={[0, null]}
32
+ padding={{ top: 20, right: 20, bottom: 30, left: 40 }}
33
+ >
34
+ <Bars fill="rgb(59, 130, 246)" radius={4} />
35
+ </ServerChart>
@@ -0,0 +1,14 @@
1
+ import type { CaptureTarget } from './captureStore.js';
2
+ type $$ComponentProps = {
3
+ data: {
4
+ category: string;
5
+ value: number;
6
+ }[];
7
+ width: number;
8
+ height: number;
9
+ capture?: CaptureTarget;
10
+ onCapture?: (data: CaptureTarget) => void;
11
+ };
12
+ declare const TestBarChart: import("svelte").Component<$$ComponentProps, {}, "">;
13
+ type TestBarChart = ReturnType<typeof TestBarChart>;
14
+ export default TestBarChart;
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ import ServerChart from './ServerChart.svelte';
3
+ import type { CaptureTarget } from './captureStore.js';
4
+ import Area from '../components/Area.svelte';
5
+ import Spline from '../components/Spline.svelte';
6
+
7
+ let {
8
+ data,
9
+ width,
10
+ height,
11
+ capture,
12
+ onCapture
13
+ }: {
14
+ data: { date: number; value: number }[];
15
+ width: number;
16
+ height: number;
17
+ capture?: CaptureTarget;
18
+ onCapture?: (data: CaptureTarget) => void;
19
+ } = $props();
20
+ </script>
21
+
22
+ <ServerChart
23
+ {capture}
24
+ {onCapture}
25
+ {width}
26
+ {height}
27
+ {data}
28
+ x="date"
29
+ y="value"
30
+ yDomain={[0, null]}
31
+ padding={{ top: 20, right: 20, bottom: 20, left: 20 }}
32
+ >
33
+ <Area fill="rgba(59, 130, 246, 0.15)" stroke="none" />
34
+ <Spline stroke="rgb(59, 130, 246)" strokeWidth={2} />
35
+ </ServerChart>