layerchart 2.0.0-next.4 → 2.0.0-next.40

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 (179) hide show
  1. package/dist/components/AnnotationLine.svelte +15 -2
  2. package/dist/components/AnnotationPoint.svelte +29 -11
  3. package/dist/components/AnnotationRange.svelte +18 -4
  4. package/dist/components/Arc.svelte +5 -5
  5. package/dist/components/Area.svelte +10 -2
  6. package/dist/components/Axis.svelte +175 -58
  7. package/dist/components/Axis.svelte.d.ts +23 -6
  8. package/dist/components/Bar.svelte +20 -15
  9. package/dist/components/Bar.svelte.d.ts +2 -2
  10. package/dist/components/Bars.svelte +4 -4
  11. package/dist/components/Blur.svelte +7 -6
  12. package/dist/components/Blur.svelte.d.ts +2 -5
  13. package/dist/components/BrushContext.svelte +45 -45
  14. package/dist/components/Calendar.svelte +31 -10
  15. package/dist/components/Calendar.svelte.d.ts +2 -1
  16. package/dist/components/Chart.svelte +76 -27
  17. package/dist/components/Chart.svelte.d.ts +26 -8
  18. package/dist/components/ChartClipPath.svelte +1 -1
  19. package/dist/components/Circle.svelte +44 -3
  20. package/dist/components/CircleClipPath.svelte +8 -1
  21. package/dist/components/ClipPath.svelte +1 -2
  22. package/dist/components/ColorRamp.svelte +1 -1
  23. package/dist/components/ComputedStyles.svelte +9 -2
  24. package/dist/components/Connector.svelte +3 -3
  25. package/dist/components/Connector.svelte.d.ts +1 -1
  26. package/dist/components/Ellipse.svelte +228 -0
  27. package/dist/components/Ellipse.svelte.d.ts +64 -0
  28. package/dist/components/ForceSimulation.svelte +184 -50
  29. package/dist/components/ForceSimulation.svelte.d.ts +95 -21
  30. package/dist/components/Frame.svelte +1 -1
  31. package/dist/components/GeoCircle.svelte +1 -1
  32. package/dist/components/GeoEdgeFade.svelte +1 -1
  33. package/dist/components/GeoPath.svelte +30 -8
  34. package/dist/components/GeoPoint.svelte +4 -5
  35. package/dist/components/GeoSpline.svelte +5 -5
  36. package/dist/components/GeoSpline.svelte.d.ts +1 -1
  37. package/dist/components/GeoTile.svelte +1 -1
  38. package/dist/components/Graticule.svelte +5 -5
  39. package/dist/components/Grid.svelte +60 -63
  40. package/dist/components/Group.svelte +13 -8
  41. package/dist/components/Group.svelte.d.ts +10 -3
  42. package/dist/components/Highlight.svelte +55 -28
  43. package/dist/components/Highlight.svelte.d.ts +4 -0
  44. package/dist/components/Hull.svelte +12 -5
  45. package/dist/components/Labels.svelte +24 -13
  46. package/dist/components/Labels.svelte.d.ts +12 -5
  47. package/dist/components/Legend.svelte +143 -70
  48. package/dist/components/Legend.svelte.d.ts +12 -8
  49. package/dist/components/Line.svelte +40 -3
  50. package/dist/components/LinearGradient.svelte +35 -4
  51. package/dist/components/Link.svelte +1 -1
  52. package/dist/components/Marker.svelte +37 -26
  53. package/dist/components/MonthPath.svelte +26 -12
  54. package/dist/components/MonthPath.svelte.d.ts +4 -3
  55. package/dist/components/MotionPath.svelte +1 -1
  56. package/dist/components/Pack.svelte.d.ts +10 -3
  57. package/dist/components/Partition.svelte.d.ts +10 -3
  58. package/dist/components/Pattern.svelte +5 -5
  59. package/dist/components/Pie.svelte +1 -2
  60. package/dist/components/Points.svelte +11 -72
  61. package/dist/components/Points.svelte.d.ts +1 -8
  62. package/dist/components/Polygon.svelte +309 -0
  63. package/dist/components/Polygon.svelte.d.ts +115 -0
  64. package/dist/components/RadialGradient.svelte +4 -6
  65. package/dist/components/Rect.svelte +55 -5
  66. package/dist/components/Rect.svelte.d.ts +2 -2
  67. package/dist/components/RectClipPath.svelte +4 -3
  68. package/dist/components/RectClipPath.svelte.d.ts +2 -2
  69. package/dist/components/Rule.svelte +167 -77
  70. package/dist/components/Rule.svelte.d.ts +7 -2
  71. package/dist/components/Spline.svelte +59 -28
  72. package/dist/components/Spline.svelte.d.ts +12 -4
  73. package/dist/components/Text.svelte +121 -73
  74. package/dist/components/Text.svelte.d.ts +6 -0
  75. package/dist/components/TileImage.svelte +19 -4
  76. package/dist/components/TransformContext.svelte +9 -3
  77. package/dist/components/TransformControls.svelte +89 -38
  78. package/dist/components/Tree.svelte.d.ts +10 -3
  79. package/dist/components/Treemap.svelte +63 -26
  80. package/dist/components/Treemap.svelte.d.ts +21 -14
  81. package/dist/components/Voronoi.svelte +12 -13
  82. package/dist/components/charts/ArcChart.svelte +43 -71
  83. package/dist/components/charts/ArcChart.svelte.d.ts +10 -3
  84. package/dist/components/charts/AreaChart.svelte +29 -59
  85. package/dist/components/charts/AreaChart.svelte.d.ts +10 -3
  86. package/dist/components/charts/BarChart.svelte +79 -71
  87. package/dist/components/charts/BarChart.svelte.d.ts +10 -3
  88. package/dist/components/charts/DefaultTooltip.svelte +3 -3
  89. package/dist/components/charts/DefaultTooltip.svelte.d.ts +1 -1
  90. package/dist/components/charts/LineChart.svelte +69 -75
  91. package/dist/components/charts/LineChart.svelte.d.ts +21 -8
  92. package/dist/components/charts/PieChart.svelte +44 -71
  93. package/dist/components/charts/PieChart.svelte.d.ts +10 -3
  94. package/dist/components/charts/ScatterChart.svelte +10 -39
  95. package/dist/components/charts/ScatterChart.svelte.d.ts +10 -3
  96. package/dist/components/charts/utils.svelte.d.ts +1 -19
  97. package/dist/components/charts/utils.svelte.js +7 -35
  98. package/dist/components/index.d.ts +4 -0
  99. package/dist/components/index.js +5 -1
  100. package/dist/components/layout/Canvas.svelte +96 -69
  101. package/dist/components/layout/Canvas.svelte.d.ts +6 -0
  102. package/dist/components/layout/Html.svelte +15 -9
  103. package/dist/components/layout/Layer.svelte +6 -4
  104. package/dist/components/layout/Layer.svelte.d.ts +6 -4
  105. package/dist/components/layout/Svg.svelte +19 -11
  106. package/dist/components/layout/WebGL.svelte +26 -6
  107. package/dist/components/layout/WebGL.svelte.d.ts +5 -2
  108. package/dist/components/tooltip/Tooltip.svelte +65 -27
  109. package/dist/components/tooltip/Tooltip.svelte.d.ts +10 -3
  110. package/dist/components/tooltip/TooltipContext.svelte +167 -54
  111. package/dist/components/tooltip/TooltipContext.svelte.d.ts +19 -5
  112. package/dist/components/tooltip/TooltipHeader.svelte +32 -18
  113. package/dist/components/tooltip/TooltipHeader.svelte.d.ts +3 -3
  114. package/dist/components/tooltip/TooltipItem.svelte +46 -37
  115. package/dist/components/tooltip/TooltipItem.svelte.d.ts +3 -3
  116. package/dist/components/tooltip/TooltipList.svelte +12 -10
  117. package/dist/components/tooltip/TooltipSeparator.svelte +18 -10
  118. package/dist/components/tooltip/tooltipMetaContext.d.ts +2 -2
  119. package/dist/docs/Blockquote.svelte +6 -4
  120. package/dist/docs/Blockquote.svelte.d.ts +4 -19
  121. package/dist/docs/Code.svelte +70 -28
  122. package/dist/docs/Code.svelte.d.ts +9 -24
  123. package/dist/docs/Header1.svelte +4 -2
  124. package/dist/docs/Header1.svelte.d.ts +4 -28
  125. package/dist/docs/Json.svelte +11 -3
  126. package/dist/docs/Json.svelte.d.ts +9 -21
  127. package/dist/docs/Layout.svelte +10 -7
  128. package/dist/docs/Layout.svelte.d.ts +4 -19
  129. package/dist/docs/Link.svelte +7 -3
  130. package/dist/docs/Link.svelte.d.ts +4 -38
  131. package/dist/docs/Preview.svelte +22 -23
  132. package/dist/docs/Preview.svelte.d.ts +5 -6
  133. package/dist/docs/TilesetField.svelte +20 -19
  134. package/dist/docs/TilesetField.svelte.d.ts +5 -22
  135. package/dist/docs/ViewSourceButton.svelte +10 -7
  136. package/dist/docs/ViewSourceButton.svelte.d.ts +7 -21
  137. package/dist/states/series.svelte.d.ts +30 -0
  138. package/dist/states/series.svelte.js +54 -0
  139. package/dist/styles/daisyui-5.css +6 -0
  140. package/dist/styles/shadcn-svelte.css +11 -0
  141. package/dist/styles/skeleton-3.css +15 -0
  142. package/dist/utils/arcText.svelte.js +4 -4
  143. package/dist/utils/array.d.ts +11 -0
  144. package/dist/utils/array.js +23 -0
  145. package/dist/utils/array.test.d.ts +1 -0
  146. package/dist/utils/array.test.js +200 -0
  147. package/dist/utils/attributes.d.ts +3 -13
  148. package/dist/utils/attributes.js +4 -18
  149. package/dist/utils/canvas.d.ts +77 -0
  150. package/dist/utils/canvas.js +105 -41
  151. package/dist/utils/common.d.ts +9 -0
  152. package/dist/utils/common.js +18 -1
  153. package/dist/utils/common.test.js +26 -1
  154. package/dist/utils/genData.d.ts +22 -8
  155. package/dist/utils/genData.js +34 -14
  156. package/dist/utils/graph/dagre.d.ts +4 -4
  157. package/dist/utils/graph/dagre.js +5 -7
  158. package/dist/utils/index.d.ts +1 -0
  159. package/dist/utils/index.js +1 -0
  160. package/dist/utils/math.d.ts +17 -0
  161. package/dist/utils/math.js +17 -0
  162. package/dist/utils/path.d.ts +10 -0
  163. package/dist/utils/path.js +30 -0
  164. package/dist/utils/rect.svelte.d.ts +2 -2
  165. package/dist/utils/rect.svelte.js +73 -1
  166. package/dist/utils/scales.svelte.d.ts +9 -3
  167. package/dist/utils/scales.svelte.js +47 -4
  168. package/dist/utils/shape.d.ts +43 -0
  169. package/dist/utils/shape.js +59 -0
  170. package/dist/utils/stack.js +1 -1
  171. package/dist/utils/string.d.ts +49 -0
  172. package/dist/utils/string.js +4 -2
  173. package/dist/utils/ticks.d.ts +15 -4
  174. package/dist/utils/ticks.js +140 -159
  175. package/dist/utils/ticks.test.js +16 -26
  176. package/dist/utils/treemap.d.ts +1 -1
  177. package/dist/utils/types.d.ts +15 -2
  178. package/package.json +35 -35
  179. package/dist/utils/object.js +0 -2
@@ -87,23 +87,23 @@
87
87
  /**
88
88
  * Add additional content at the start of the line.
89
89
  *
90
- * Receives `{ point: DOMPoint }` as a snippet prop.
90
+ * Receives `{ point: DOMPoint; value: { x: number; y: number } }` as a snippet prop.
91
91
  */
92
- startContent?: Snippet<[{ point: DOMPoint }]>;
92
+ startContent?: Snippet<[{ point: DOMPoint; value: { x: number; y: number } }]>;
93
93
 
94
94
  /**
95
95
  * Add additional content at the end of the line.
96
96
  *
97
- * Receives `{ point: DOMPoint }` as a snippet prop.
97
+ * Receives `{ point: DOMPoint; value: { x: number; y: number } }` as a snippet prop.
98
98
  */
99
- endContent?: Snippet<[{ point: DOMPoint }]>;
99
+ endContent?: Snippet<[{ point: DOMPoint; value: { x: number; y: number } }]>;
100
100
 
101
101
  /**
102
102
  * A reference to the `<path>` element.
103
103
  *
104
104
  * @bindable
105
105
  */
106
- splineRef?: SVGPathElement;
106
+ pathRef?: SVGPathElement;
107
107
 
108
108
  motion?: MotionProp;
109
109
  } & CommonStyleProps;
@@ -132,7 +132,6 @@
132
132
  import { getChartContext } from './Chart.svelte';
133
133
  import { createKey } from '../utils/key.svelte.js';
134
134
  import { createId } from '../utils/createId.js';
135
- import { layerClass } from '../utils/attributes.js';
136
135
 
137
136
  const ctx = getChartContext();
138
137
 
@@ -159,14 +158,14 @@
159
158
  startContent,
160
159
  endContent,
161
160
  opacity,
162
- splineRef: splineRefProp = $bindable(),
161
+ pathRef: pathRefProp = $bindable(),
163
162
  ...restProps
164
163
  }: SplineProps = $props();
165
164
 
166
- let splineRef = $state<SVGPathElement>();
165
+ let pathRef = $state<SVGPathElement>();
167
166
 
168
167
  $effect.pre(() => {
169
- splineRefProp = splineRef;
168
+ pathRefProp = pathRef;
170
169
  });
171
170
 
172
171
  const markerStart = $derived(markerStartProp ?? marker);
@@ -274,7 +273,7 @@
274
273
  ? merge({ styles: { strokeWidth } }, styleOverrides)
275
274
  : {
276
275
  styles: { fill, fillOpacity, stroke, strokeWidth, opacity },
277
- classes: className,
276
+ classes: cls('lc-spline-path', className),
278
277
  }
279
278
  );
280
279
  }
@@ -331,8 +330,8 @@
331
330
  easing: typeof draw === 'object' && draw.easing ? draw.easing : cubicInOut,
332
331
  interpolate() {
333
332
  return (t: number) => {
334
- const totalLength = splineRef?.getTotalLength() ?? 0;
335
- const point = splineRef?.getPointAtLength(totalLength * t);
333
+ const totalLength = pathRef?.getTotalLength() ?? 0;
334
+ const point = pathRef?.getPointAtLength(totalLength * t);
336
335
  return point;
337
336
  };
338
337
  },
@@ -343,16 +342,16 @@
343
342
  $effect(() => {
344
343
  if (!startContent && !endContent) return;
345
344
  d;
346
- if (!splineRef || !splineRef.getTotalLength()) return;
347
- startPoint = splineRef.getPointAtLength(0);
348
- const totalLength = splineRef.getTotalLength();
349
- endPoint.target = splineRef.getPointAtLength(totalLength);
345
+ if (!pathRef || !pathRef.getTotalLength()) return;
346
+ startPoint = pathRef.getPointAtLength(0);
347
+ const totalLength = pathRef.getTotalLength();
348
+ endPoint.target = pathRef.getPointAtLength(totalLength);
350
349
  });
351
350
 
352
351
  $effect(() => {
353
352
  if (!draw) return;
354
- [tweenedState.current];
355
353
  // Anytime the path data changes, redraw
354
+ [pathData, data, ctx.data];
356
355
  key = Symbol();
357
356
  });
358
357
  </script>
@@ -362,12 +361,7 @@
362
361
  <path
363
362
  d={tweenedState.current}
364
363
  {...restProps}
365
- class={cls(
366
- layerClass('spline-path'),
367
- !fill && 'fill-none',
368
- !stroke && 'stroke-surface-content',
369
- className
370
- )}
364
+ class={cls('lc-spline-path', className)}
371
365
  {fill}
372
366
  fill-opacity={fillOpacity}
373
367
  {stroke}
@@ -377,22 +371,59 @@
377
371
  marker-mid={markerMidId ? `url(#${markerMidId})` : undefined}
378
372
  marker-end={markerEndId ? `url(#${markerEndId})` : undefined}
379
373
  in:drawTransition|global={typeof draw === 'object' ? draw : undefined}
380
- bind:this={splineRef}
374
+ bind:this={pathRef}
381
375
  />
382
376
  <MarkerWrapper id={markerStartId} marker={markerStart} />
383
377
  <MarkerWrapper id={markerMidId} marker={markerMid} />
384
378
  <MarkerWrapper id={markerEndId} marker={markerEnd} />
385
379
 
386
380
  {#if startContent && startPoint}
387
- <Group x={startPoint.x} y={startPoint.y} class={layerClass('spline-g-start')}>
388
- {@render startContent({ point: startPoint })}
381
+ <Group x={startPoint.x} y={startPoint.y} class="lc-spline-g-start">
382
+ {@render startContent({
383
+ point: startPoint,
384
+ value: {
385
+ x: ctx.xScale?.invert?.(startPoint.x),
386
+ y: ctx.yScale?.invert?.(startPoint.y),
387
+ },
388
+ })}
389
389
  </Group>
390
390
  {/if}
391
391
 
392
392
  {#if endContent && endPoint.current}
393
- <Group x={endPoint.current.x} y={endPoint.current.y} class={layerClass('spline-g-end')}>
394
- {@render endContent({ point: endPoint.current })}
393
+ <Group x={endPoint.current.x} y={endPoint.current.y} class="lc-spline-g-end">
394
+ {@render endContent({
395
+ point: endPoint.current,
396
+ value: {
397
+ x: ctx.xScale?.invert?.(endPoint.current.x),
398
+ y: ctx.yScale?.invert?.(endPoint.current.y),
399
+ },
400
+ })}
395
401
  </Group>
396
402
  {/if}
397
403
  {/key}
398
404
  {/if}
405
+
406
+ <style>
407
+ @layer base {
408
+ :global(:where(.lc-spline-path)) {
409
+ --fill-color: none;
410
+ --stroke-color: var(--color-surface-content, currentColor);
411
+ }
412
+
413
+ /* Svg | Canvas layers */
414
+ :global(:where(.lc-layout-svg .lc-spline-path, svg.lc-spline-path):not([fill])) {
415
+ fill: var(--fill-color);
416
+ }
417
+ :global(:where(.lc-layout-svg .lc-spline-path, svg.lc-spline-path):not([stroke])) {
418
+ stroke: var(--stroke-color);
419
+ }
420
+
421
+ /* Html layers */
422
+ :global(:where(.lc-layout-html .lc-spline-path):not([background-color])) {
423
+ background-color: var(--fill-color);
424
+ }
425
+ :global(:where(.lc-layout-html .lc-spline-path):not([border-color])) {
426
+ border-color: var(--stroke-color);
427
+ }
428
+ }
429
+ </style>
@@ -67,29 +67,37 @@ export type SplinePropsWithoutHTML = {
67
67
  /**
68
68
  * Add additional content at the start of the line.
69
69
  *
70
- * Receives `{ point: DOMPoint }` as a snippet prop.
70
+ * Receives `{ point: DOMPoint; value: { x: number; y: number } }` as a snippet prop.
71
71
  */
72
72
  startContent?: Snippet<[{
73
73
  point: DOMPoint;
74
+ value: {
75
+ x: number;
76
+ y: number;
77
+ };
74
78
  }]>;
75
79
  /**
76
80
  * Add additional content at the end of the line.
77
81
  *
78
- * Receives `{ point: DOMPoint }` as a snippet prop.
82
+ * Receives `{ point: DOMPoint; value: { x: number; y: number } }` as a snippet prop.
79
83
  */
80
84
  endContent?: Snippet<[{
81
85
  point: DOMPoint;
86
+ value: {
87
+ x: number;
88
+ y: number;
89
+ };
82
90
  }]>;
83
91
  /**
84
92
  * A reference to the `<path>` element.
85
93
  *
86
94
  * @bindable
87
95
  */
88
- splineRef?: SVGPathElement;
96
+ pathRef?: SVGPathElement;
89
97
  motion?: MotionProp;
90
98
  } & CommonStyleProps;
91
99
  export type SplineProps = SplinePropsWithoutHTML & Without<SVGAttributes<SVGPathElement>, SplinePropsWithoutHTML>;
92
100
  import { draw as _drawTransition } from 'svelte/transition';
93
- declare const Spline: import("svelte").Component<SplineProps, {}, "splineRef">;
101
+ declare const Spline: import("svelte").Component<SplineProps, {}, "pathRef">;
94
102
  type Spline = ReturnType<typeof Spline>;
95
103
  export default Spline;
@@ -97,6 +97,20 @@
97
97
  */
98
98
  verticalAnchor?: 'start' | 'middle' | 'end' | 'inherit';
99
99
 
100
+ /**
101
+ * The dominant baseline of the text. Useful for aligning text to the baseline of the axis.
102
+ *
103
+ * @default 'auto'
104
+ */
105
+ dominantBaseline?:
106
+ | 'auto'
107
+ | 'text-before-edge'
108
+ | 'text-after-edge'
109
+ | 'middle'
110
+ | 'hanging'
111
+ | 'ideographic'
112
+ | 'mathematical';
113
+
100
114
  /**
101
115
  * Rotational angle of the text
102
116
  */
@@ -174,24 +188,9 @@
174
188
  import { getComputedStyles, renderText, type ComputedStylesOptions } from '../utils/canvas.js';
175
189
 
176
190
  import { createKey } from '../utils/key.svelte.js';
177
- import { layerClass } from '../utils/attributes.js';
178
191
  import { degreesToRadians } from '../utils/math.js';
179
192
  import { createId } from '../utils/createId.js';
180
193
 
181
- /*
182
- TODO:
183
- - [ ] Handle styled text (use <slot /> to measure?)
184
- - [ ] Simplify by using `alignment-baseline` / `dominant-baseline`, rework multiline or drop support, etc
185
- - https://svelte.dev/repl/f12d3003313a43ba8a0be53e5786f1c7?version=3.44.3
186
- - https://observablehq.com/@neocartocnrs/cheat-sheet-on-texts-in-svg
187
-
188
- Reference:
189
- - https://bl.ocks.org/mbostock/7555321
190
- - https://github.com/airbnb/visx/blob/master/packages/visx-text/src/Text.tsx
191
- - https://airbnb.io/visx/text
192
- - https://github.com/airbnb/visx/blob/master/packages/visx-demo/src/pages/text.tsx
193
- */
194
-
195
194
  const uid = $props.id();
196
195
 
197
196
  let {
@@ -208,6 +207,7 @@
208
207
  scaleToFit = false,
209
208
  textAnchor = 'start',
210
209
  verticalAnchor = 'end',
210
+ dominantBaseline = 'auto',
211
211
  rotate,
212
212
  opacity = 1,
213
213
  strokeWidth = 0,
@@ -227,6 +227,8 @@
227
227
  ...restProps
228
228
  }: TextProps = $props();
229
229
 
230
+ const renderCtx = getRenderContext();
231
+
230
232
  let ref = $state<SVGTextElement>();
231
233
  let svgRef = $state<SVGElement>();
232
234
  let pathRef = $state<SVGPathElement>();
@@ -260,49 +262,56 @@
260
262
  };
261
263
  });
262
264
 
263
- const rawText = $derived(value != null ? value.toString() : '');
265
+ // Handle null and convert `\n` strings back to newline characters
266
+ const rawText = $derived(value != null ? value.toString().replace(/\\n/g, '\n') : '');
264
267
 
265
268
  const textValue = $derived.by(() => {
266
269
  if (!truncateConfig) return rawText;
267
270
  return truncateText(rawText, truncateConfig);
268
271
  });
269
272
 
270
- const renderCtx = getRenderContext();
271
-
272
- const words = $derived(textValue ? textValue.split(/(?:(?!\u00A0+)\s+)/) : []);
273
+ const spaceWidth = $derived(getStringWidth('\u00A0', style) || 0);
273
274
 
274
- const wordsWithWidth = $derived(
275
- words.map((word) => ({
276
- word,
277
- width: getStringWidth(word, style) || 0,
278
- }))
279
- );
275
+ const wordsByLines = $derived.by(() => {
276
+ // Split by newlines to preserve explicit line breaks
277
+ const lines = textValue.split('\n');
280
278
 
281
- const spaceWidth = $derived(getStringWidth('\u00A0', style) || 0);
279
+ return lines.flatMap((line) => {
280
+ // Split each line into words
281
+ const words = line.split(/(?:(?!\u00A0+)\s+)/);
282
282
 
283
- const wordsByLines = $derived(
284
- wordsWithWidth.reduce((result: { words: string[]; width?: number }[], item) => {
285
- const currentLine = result[result.length - 1];
286
-
287
- if (
288
- currentLine &&
289
- (width == null || scaleToFit || (currentLine.width || 0) + item.width + spaceWidth < width)
290
- ) {
291
- // Word can be added to an existing line
292
- currentLine.words.push(item.word);
293
- currentLine.width = currentLine.width || 0;
294
- currentLine.width += item.width + spaceWidth;
283
+ if (width == null) {
284
+ // No width specified, only use explicit line breaks (if used)
285
+ return [{ words }];
295
286
  } else {
296
- // Add first word to line or word is too long to scaleToFit on existing line
297
- const newLine = { words: [item.word], width: item.width };
298
- result.push(newLine);
287
+ // Handle word wrapping within each line
288
+ return words.reduce((result: { words: string[]; width?: number }[], item) => {
289
+ const currentLine = result[result.length - 1];
290
+ const itemWidth = getStringWidth(item, style) || 0;
291
+
292
+ if (
293
+ currentLine &&
294
+ (width == null ||
295
+ scaleToFit ||
296
+ (currentLine.width || 0) + itemWidth + spaceWidth < width)
297
+ ) {
298
+ // Word can be added to an existing line
299
+ currentLine.words.push(item);
300
+ currentLine.width = currentLine.width || 0;
301
+ currentLine.width += itemWidth + spaceWidth;
302
+ } else {
303
+ // Add first word to line or word is too long to scaleToFit on existing line
304
+ const newLine = { words: [item], width: itemWidth };
305
+ result.push(newLine);
306
+ }
307
+
308
+ return result;
309
+ }, []);
299
310
  }
311
+ });
312
+ });
300
313
 
301
- return result;
302
- }, [])
303
- );
304
-
305
- const lines = $derived(wordsByLines.length);
314
+ const lineCount = $derived(wordsByLines.length);
306
315
 
307
316
  /**
308
317
  * Convert css value to pixel value (ex. 0.71em => 11.36)
@@ -327,28 +336,18 @@
327
336
 
328
337
  const startDy = $derived.by(() => {
329
338
  if (verticalAnchor === 'start') {
330
- return getPixelValue(capHeight);
331
- } else if (verticalAnchor === 'middle') {
332
- return ((lines - 1) / 2) * -getPixelValue(lineHeight) + getPixelValue(capHeight) / 2;
333
- } else {
334
- return (lines - 1) * -getPixelValue(lineHeight);
335
- }
336
- });
337
-
338
- const pathStartDy = $derived.by(() => {
339
- if (verticalAnchor === 'start') {
340
- return getPixelValue(capHeight);
339
+ return getPixelValue(lineHeight);
341
340
  } else if (verticalAnchor === 'middle') {
342
- return (0 / 2) * -getPixelValue(lineHeight) + getPixelValue(capHeight) / 2;
341
+ return ((lineCount - 1) / 2) * -getPixelValue(lineHeight) + getPixelValue(capHeight) / 2;
343
342
  } else {
344
- return 0 * -getPixelValue(lineHeight);
343
+ return (lineCount - 1) * -getPixelValue(lineHeight) - getPixelValue(capHeight) / 2;
345
344
  }
346
345
  });
347
346
 
348
347
  const scaleTransform = $derived.by(() => {
349
348
  if (
350
349
  scaleToFit &&
351
- lines > 0 &&
350
+ lineCount > 0 &&
352
351
  typeof x == 'number' &&
353
352
  typeof y == 'number' &&
354
353
  typeof width == 'number'
@@ -411,7 +410,7 @@
411
410
  paintOrder: 'stroke',
412
411
  textAnchor,
413
412
  },
414
- classes: cls(fill === undefined && 'fill-surface-content', className),
413
+ classes: cls('lc-text', className),
415
414
  };
416
415
 
417
416
  const computedStyles = getComputedStyles(ctx.canvas, styles);
@@ -473,13 +472,7 @@
473
472
  {#if renderCtx === 'svg'}
474
473
  <!-- `overflow: visible` allow contents to be shown outside element -->
475
474
  <!-- `paint-order: stroke` supports stroke outlining text -->
476
- <svg
477
- x={dx}
478
- y={dy}
479
- {...svgProps}
480
- class={cls(layerClass('text-svg'), 'overflow-visible [paint-order:stroke]', svgProps?.class)}
481
- bind:this={svgRef}
482
- >
475
+ <svg x={dx} y={dy} {...svgProps} class={['lc-text-svg', svgProps?.class]} bind:this={svgRef}>
483
476
  {#if path}
484
477
  <defs>
485
478
  {#key path}
@@ -496,13 +489,14 @@
496
489
  stroke-width={strokeWidth}
497
490
  {opacity}
498
491
  transform={transformProp}
499
- class={cls(layerClass('text'), fill === undefined && 'fill-surface-content', className)}
492
+ class={['lc-text', className]}
500
493
  >
501
494
  <textPath
502
495
  style="text-anchor: {textAnchor};"
496
+ dominant-baseline={dominantBaseline}
503
497
  href="#{pathId}"
504
498
  {startOffset}
505
- class={cls(layerClass('text-path'))}
499
+ class="lc-text-path"
506
500
  >
507
501
  {wordsByLines.map((line) => line.words.join(' ')).join()}
508
502
  </textPath>
@@ -514,19 +508,20 @@
514
508
  y={motionY.current}
515
509
  {transform}
516
510
  text-anchor={textAnchor}
511
+ dominant-baseline={dominantBaseline}
517
512
  {...restProps}
518
513
  {fill}
519
514
  fill-opacity={fillOpacity}
520
515
  {stroke}
521
516
  stroke-width={strokeWidth}
522
517
  {opacity}
523
- class={cls(layerClass('text'), fill === undefined && 'fill-surface-content', className)}
518
+ class={['lc-text', className]}
524
519
  >
525
520
  {#each wordsByLines as line, index}
526
521
  <tspan
527
522
  x={motionX.current}
528
- dy={index === 0 ? startDy : lineHeight}
529
- class={layerClass('text-tspan')}
523
+ dy={index === 0 ? startDy : getPixelValue(lineHeight)}
524
+ class="lc-text-tspan"
530
525
  >
531
526
  {line.words.join(' ')}
532
527
  </tspan>
@@ -534,4 +529,57 @@
534
529
  </text>
535
530
  {/if}
536
531
  </svg>
532
+ {:else if renderCtx === 'html'}
533
+ {@const translateX = textAnchor === 'middle' ? '-50%' : textAnchor === 'end' ? '-100%' : '0%'}
534
+ {@const translateY =
535
+ verticalAnchor === 'middle' ? '-50%' : verticalAnchor === 'end' ? '-100%' : '0%'}
536
+ <!-- TODO: How best to handle dx/dy when adjusted for svg style issues? -->
537
+ <!-- style:line-height={lineHeight} -->
538
+ <!-- TODO: How to handle fill-/stroke- vs bg-/text-/border- colors? -->
539
+ <div
540
+ style:position="absolute"
541
+ style:left="{dx + motionX.current}px"
542
+ style:top="{dy + motionY.current}px"
543
+ style:transform="translate({translateX}, {translateY}) rotate({rotate ?? 0}deg)"
544
+ style:transform-origin="{verticalAnchor === 'middle'
545
+ ? 'center'
546
+ : verticalAnchor === 'end'
547
+ ? 'bottom'
548
+ : 'top'}
549
+ {textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'right' : 'left'}"
550
+ class={['lc-text', className]}
551
+ >
552
+ {textValue}
553
+ </div>
537
554
  {/if}
555
+
556
+ <style>
557
+ @layer base {
558
+ :global(:where(.lc-text)) {
559
+ --fill-color: var(--color-surface-content, currentColor);
560
+ --stroke-color: initial;
561
+ }
562
+
563
+ :global(:where(.lc-text-svg)) {
564
+ overflow: visible;
565
+ paint-order: stroke;
566
+ }
567
+
568
+ /* Svg | Canvas layers */
569
+ :global(:where(.lc-layout-svg .lc-text, svg.lc-text):not([fill])) {
570
+ color: var(--fill-color);
571
+ fill: currentColor;
572
+ }
573
+ :global(:where(.lc-layout-svg .lc-text, svg.lc-text):not([stroke])) {
574
+ stroke: var(--stroke-color);
575
+ }
576
+
577
+ /* Html layers */
578
+ :global(:where(.lc-layout-html .lc-text):not([background-color])) {
579
+ color: var(--fill-color);
580
+ }
581
+ :global(:where(.lc-layout-html .lc-text):not([border-color])) {
582
+ border-color: var(--stroke-color);
583
+ }
584
+ }
585
+ </style>
@@ -80,6 +80,12 @@ export type TextPropsWithoutHTML = {
80
80
  * @default 'end'
81
81
  */
82
82
  verticalAnchor?: 'start' | 'middle' | 'end' | 'inherit';
83
+ /**
84
+ * The dominant baseline of the text. Useful for aligning text to the baseline of the axis.
85
+ *
86
+ * @default 'auto'
87
+ */
88
+ dominantBaseline?: 'auto' | 'text-before-edge' | 'text-after-edge' | 'middle' | 'hanging' | 'ideographic' | 'mathematical';
83
89
  /**
84
90
  * Rotational angle of the text
85
91
  */
@@ -135,7 +135,7 @@
135
135
  y={(y + ty) * scale - 0.5}
136
136
  width={scale + 1}
137
137
  height={scale + 1}
138
- {...extractLayerProps(restProps, 'tile-image-lower')}
138
+ {...extractLayerProps(restProps, 'lc-tile-image-lower')}
139
139
  />
140
140
  <image
141
141
  {href}
@@ -143,7 +143,7 @@
143
143
  y={(y + ty) * scale}
144
144
  width={scale}
145
145
  height={scale}
146
- {...extractLayerProps(restProps, 'tile-image')}
146
+ {...extractLayerProps(restProps, 'lc-tile-image')}
147
147
  />
148
148
  {/key}
149
149
  {#if debug}
@@ -152,7 +152,7 @@
152
152
  y={(y + ty) * scale}
153
153
  width={scale}
154
154
  height={scale}
155
- class="stroke-danger/50 fill-none"
155
+ class="lc-tile-image-debug-rect"
156
156
  />
157
157
  <Text
158
158
  x={(x + tx) * scale}
@@ -161,6 +161,21 @@
161
161
  dx={2}
162
162
  dy={-2}
163
163
  value="{x}-{y}-{z}"
164
- class="text-[8px] fill-black/50"
164
+ class="lc-tile-image-debug-text"
165
165
  />
166
166
  {/if}
167
+
168
+ <style>
169
+ @layer components {
170
+ :global(:where(.lc-tile-image-debug-rect)) {
171
+ fill: none;
172
+ stroke: var(--color-danger, red);
173
+ }
174
+
175
+ :global(:where(.lc-tile-image-debug-text)) {
176
+ --fill-color: var(--color-danger, red);
177
+ font-size: 14px;
178
+ font-weight: 500;
179
+ }
180
+ }
181
+ </style>
@@ -216,8 +216,6 @@
216
216
  import type { Without } from '../utils/types.js';
217
217
  import { getChartContext } from './Chart.svelte';
218
218
  import type { Snippet } from 'svelte';
219
- import { cls } from '@layerstack/tailwind';
220
- import { layerClass } from '../utils/attributes.js';
221
219
  import {
222
220
  createControlledMotion,
223
221
  createMotionTracker,
@@ -519,9 +517,17 @@
519
517
  onpointerup={onPointerUp}
520
518
  ondblclick={onDoubleClick}
521
519
  onclickcapture={onClick}
522
- class={cls(layerClass('transform-context'), 'h-full', className)}
520
+ class={['lc-transform-context', className]}
523
521
  bind:this={ref}
524
522
  {...restProps}
525
523
  >
526
524
  {@render children?.({ transformContext: transformContext })}
527
525
  </div>
526
+
527
+ <style>
528
+ @layer base {
529
+ :where(.lc-transform-context) {
530
+ height: 100%;
531
+ }
532
+ }
533
+ </style>