layerchart 2.0.0-next.0 → 2.0.0-next.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.
Files changed (266) hide show
  1. package/dist/actions/movable.d.ts +28 -0
  2. package/dist/actions/movable.js +91 -0
  3. package/dist/components/AnnotationLine.svelte +155 -0
  4. package/dist/components/AnnotationLine.svelte.d.ts +28 -0
  5. package/dist/components/AnnotationPoint.svelte +121 -0
  6. package/dist/components/AnnotationPoint.svelte.d.ts +32 -0
  7. package/dist/components/AnnotationRange.svelte +147 -0
  8. package/dist/components/AnnotationRange.svelte.d.ts +40 -0
  9. package/dist/components/Arc.svelte +344 -151
  10. package/dist/components/Arc.svelte.d.ts +138 -0
  11. package/dist/components/Area.svelte +165 -149
  12. package/dist/components/Area.svelte.d.ts +45 -0
  13. package/dist/components/Axis.svelte +287 -174
  14. package/dist/components/Axis.svelte.d.ts +116 -0
  15. package/dist/components/Bar.svelte +163 -107
  16. package/dist/components/Bar.svelte.d.ts +48 -0
  17. package/dist/components/Bars.svelte +54 -68
  18. package/dist/components/Bars.svelte.d.ts +27 -0
  19. package/dist/components/Blur.svelte +31 -7
  20. package/dist/components/Blur.svelte.d.ts +23 -21
  21. package/dist/components/Bounds.svelte +49 -19
  22. package/dist/components/Bounds.svelte.d.ts +24 -50
  23. package/dist/components/BrushContext.svelte +296 -168
  24. package/dist/components/BrushContext.svelte.d.ts +97 -65
  25. package/dist/components/Calendar.svelte +116 -59
  26. package/dist/components/Calendar.svelte.d.ts +50 -31
  27. package/dist/components/Chart.svelte +1289 -398
  28. package/dist/components/Chart.svelte.d.ts +535 -410
  29. package/dist/components/ChartClipPath.svelte +37 -15
  30. package/dist/components/ChartClipPath.svelte.d.ts +21 -19
  31. package/dist/components/Circle.svelte +124 -85
  32. package/dist/components/Circle.svelte.d.ts +52 -0
  33. package/dist/components/CircleClipPath.svelte +76 -16
  34. package/dist/components/CircleClipPath.svelte.d.ts +46 -0
  35. package/dist/components/ClipPath.svelte +60 -15
  36. package/dist/components/ClipPath.svelte.d.ts +40 -27
  37. package/dist/components/ColorRamp.svelte +75 -9
  38. package/dist/components/ColorRamp.svelte.d.ts +37 -19
  39. package/dist/components/ComputedStyles.svelte +17 -5
  40. package/dist/components/ComputedStyles.svelte.d.ts +11 -19
  41. package/dist/components/Connector.svelte +149 -0
  42. package/dist/components/Connector.svelte.d.ts +51 -0
  43. package/dist/components/Dagre.svelte +211 -122
  44. package/dist/components/Dagre.svelte.d.ts +119 -56
  45. package/dist/components/ForceSimulation.svelte +215 -90
  46. package/dist/components/ForceSimulation.svelte.d.ts +82 -35
  47. package/dist/components/Frame.svelte +33 -13
  48. package/dist/components/Frame.svelte.d.ts +13 -17
  49. package/dist/components/GeoCircle.svelte +29 -16
  50. package/dist/components/GeoCircle.svelte.d.ts +22 -24
  51. package/dist/components/GeoContext.svelte +113 -72
  52. package/dist/components/GeoContext.svelte.d.ts +49 -41
  53. package/dist/components/GeoEdgeFade.svelte +47 -12
  54. package/dist/components/GeoEdgeFade.svelte.d.ts +17 -19
  55. package/dist/components/GeoPath.svelte +157 -127
  56. package/dist/components/GeoPath.svelte.d.ts +48 -36
  57. package/dist/components/GeoPoint.svelte +52 -20
  58. package/dist/components/GeoPoint.svelte.d.ts +25 -22
  59. package/dist/components/GeoSpline.svelte +75 -26
  60. package/dist/components/GeoSpline.svelte.d.ts +29 -20
  61. package/dist/components/GeoTile.svelte +100 -49
  62. package/dist/components/GeoTile.svelte.d.ts +38 -23
  63. package/dist/components/GeoVisible.svelte +17 -9
  64. package/dist/components/GeoVisible.svelte.d.ts +10 -18
  65. package/dist/components/Graticule.svelte +28 -13
  66. package/dist/components/Graticule.svelte.d.ts +11 -52
  67. package/dist/components/Grid.svelte +226 -114
  68. package/dist/components/Grid.svelte.d.ts +70 -0
  69. package/dist/components/Group.svelte +132 -105
  70. package/dist/components/Group.svelte.d.ts +53 -0
  71. package/dist/components/Highlight.svelte +409 -307
  72. package/dist/components/Highlight.svelte.d.ts +107 -0
  73. package/dist/components/Hull.svelte +96 -45
  74. package/dist/components/Hull.svelte.d.ts +40 -30
  75. package/dist/components/Labels.svelte +125 -46
  76. package/dist/components/Labels.svelte.d.ts +70 -27
  77. package/dist/components/Legend.svelte +374 -190
  78. package/dist/components/Legend.svelte.d.ts +95 -44
  79. package/dist/components/Line.svelte +163 -125
  80. package/dist/components/Line.svelte.d.ts +75 -0
  81. package/dist/components/LinearGradient.svelte +153 -78
  82. package/dist/components/LinearGradient.svelte.d.ts +66 -31
  83. package/dist/components/Link.svelte +160 -104
  84. package/dist/components/Link.svelte.d.ts +54 -0
  85. package/dist/components/Marker.svelte +100 -39
  86. package/dist/components/Marker.svelte.d.ts +59 -27
  87. package/dist/components/MarkerWrapper.svelte +35 -0
  88. package/dist/components/MarkerWrapper.svelte.d.ts +18 -0
  89. package/dist/components/MonthPath.svelte +65 -20
  90. package/dist/components/MonthPath.svelte.d.ts +23 -17
  91. package/dist/components/MotionPath.svelte +80 -24
  92. package/dist/components/MotionPath.svelte.d.ts +46 -27
  93. package/dist/components/Pack.svelte +53 -17
  94. package/dist/components/Pack.svelte.d.ts +42 -21
  95. package/dist/components/Partition.svelte +64 -22
  96. package/dist/components/Partition.svelte.d.ts +49 -26
  97. package/dist/components/Pattern.svelte +297 -11
  98. package/dist/components/Pattern.svelte.d.ts +103 -19
  99. package/dist/components/Pie.svelte +122 -76
  100. package/dist/components/Pie.svelte.d.ts +65 -51
  101. package/dist/components/Point.svelte +20 -9
  102. package/dist/components/Point.svelte.d.ts +16 -20
  103. package/dist/components/Points.svelte +148 -137
  104. package/dist/components/Points.svelte.d.ts +45 -34
  105. package/dist/components/RadialGradient.svelte +143 -70
  106. package/dist/components/RadialGradient.svelte.d.ts +69 -31
  107. package/dist/components/Rect.svelte +121 -102
  108. package/dist/components/Rect.svelte.d.ts +36 -0
  109. package/dist/components/RectClipPath.svelte +82 -18
  110. package/dist/components/RectClipPath.svelte.d.ts +55 -0
  111. package/dist/components/Rule.svelte +105 -62
  112. package/dist/components/Rule.svelte.d.ts +40 -19
  113. package/dist/components/Sankey.svelte +132 -55
  114. package/dist/components/Sankey.svelte.d.ts +61 -31
  115. package/dist/components/Spline.svelte +281 -218
  116. package/dist/components/Spline.svelte.d.ts +95 -0
  117. package/dist/components/Text.svelte +437 -176
  118. package/dist/components/Text.svelte.d.ts +130 -0
  119. package/dist/components/Threshold.svelte +48 -16
  120. package/dist/components/Threshold.svelte.d.ts +29 -31
  121. package/dist/components/TileImage.svelte +103 -30
  122. package/dist/components/TileImage.svelte.d.ts +48 -23
  123. package/dist/components/TransformContext.svelte +365 -171
  124. package/dist/components/TransformControls.svelte +50 -26
  125. package/dist/components/TransformControls.svelte.d.ts +27 -19
  126. package/dist/components/Tree.svelte +74 -33
  127. package/dist/components/Tree.svelte.d.ts +42 -30
  128. package/dist/components/Treemap.svelte +119 -42
  129. package/dist/components/Treemap.svelte.d.ts +75 -27
  130. package/dist/components/Voronoi.svelte +106 -75
  131. package/dist/components/Voronoi.svelte.d.ts +40 -41
  132. package/dist/components/charts/ArcChart.svelte +464 -0
  133. package/dist/components/charts/ArcChart.svelte.d.ts +90 -0
  134. package/dist/components/charts/AreaChart.svelte +450 -393
  135. package/dist/components/charts/AreaChart.svelte.d.ts +61 -0
  136. package/dist/components/charts/BarChart.svelte +454 -389
  137. package/dist/components/charts/BarChart.svelte.d.ts +76 -0
  138. package/dist/components/charts/ChartAnnotations.svelte +37 -0
  139. package/dist/components/charts/ChartAnnotations.svelte.d.ts +10 -0
  140. package/dist/components/charts/DefaultTooltip.svelte +60 -0
  141. package/dist/components/charts/DefaultTooltip.svelte.d.ts +10 -0
  142. package/dist/components/charts/LineChart.svelte +369 -314
  143. package/dist/components/charts/LineChart.svelte.d.ts +53 -0
  144. package/dist/components/charts/PieChart.svelte +458 -316
  145. package/dist/components/charts/PieChart.svelte.d.ts +137 -353
  146. package/dist/components/charts/ScatterChart.svelte +334 -296
  147. package/dist/components/charts/ScatterChart.svelte.d.ts +39 -0
  148. package/dist/components/charts/index.d.ts +8 -0
  149. package/dist/components/charts/index.js +7 -0
  150. package/dist/components/charts/types.d.ts +253 -0
  151. package/dist/components/charts/utils.svelte.d.ts +30 -0
  152. package/dist/components/charts/utils.svelte.js +55 -0
  153. package/dist/components/index.d.ts +76 -4
  154. package/dist/components/index.js +76 -5
  155. package/dist/components/layout/Canvas.svelte +321 -155
  156. package/dist/components/layout/Canvas.svelte.d.ts +104 -55
  157. package/dist/components/layout/Html.svelte +82 -42
  158. package/dist/components/layout/Html.svelte.d.ts +39 -28
  159. package/dist/components/layout/Layer.svelte +39 -0
  160. package/dist/components/layout/Layer.svelte.d.ts +17 -0
  161. package/dist/components/layout/Svg.svelte +122 -70
  162. package/dist/components/layout/Svg.svelte.d.ts +53 -34
  163. package/dist/components/layout/WebGL.svelte +135 -0
  164. package/dist/components/layout/WebGL.svelte.d.ts +50 -0
  165. package/dist/components/tooltip/Tooltip.svelte +246 -78
  166. package/dist/components/tooltip/Tooltip.svelte.d.ts +149 -31
  167. package/dist/components/tooltip/TooltipContext.svelte +409 -271
  168. package/dist/components/tooltip/TooltipContext.svelte.d.ts +86 -55
  169. package/dist/components/tooltip/TooltipHeader.svelte +100 -11
  170. package/dist/components/tooltip/TooltipHeader.svelte.d.ts +43 -23
  171. package/dist/components/tooltip/TooltipItem.svelte +167 -27
  172. package/dist/components/tooltip/TooltipItem.svelte.d.ts +63 -31
  173. package/dist/components/tooltip/TooltipList.svelte +22 -3
  174. package/dist/components/tooltip/TooltipList.svelte.d.ts +6 -17
  175. package/dist/components/tooltip/TooltipSeparator.svelte +27 -1
  176. package/dist/components/tooltip/TooltipSeparator.svelte.d.ts +6 -15
  177. package/dist/components/tooltip/index.d.ts +6 -0
  178. package/dist/components/tooltip/index.js +6 -0
  179. package/dist/components/tooltip/tooltipMetaContext.d.ts +79 -0
  180. package/dist/components/tooltip/tooltipMetaContext.js +139 -0
  181. package/dist/components/types.d.ts +1 -0
  182. package/dist/components/types.js +1 -0
  183. package/dist/docs/Blockquote.svelte.d.ts +18 -14
  184. package/dist/docs/Code.svelte.d.ts +26 -22
  185. package/dist/docs/ConnectorSweepMenuField.svelte +17 -0
  186. package/dist/docs/ConnectorSweepMenuField.svelte.d.ts +7 -0
  187. package/dist/docs/ConnectorTypeMenuField.svelte +17 -0
  188. package/dist/docs/ConnectorTypeMenuField.svelte.d.ts +7 -0
  189. package/dist/docs/CurveMenuField.svelte +14 -3
  190. package/dist/docs/CurveMenuField.svelte.d.ts +9 -18
  191. package/dist/docs/GeoDebug.svelte +47 -42
  192. package/dist/docs/GeoDebug.svelte.d.ts +4 -16
  193. package/dist/docs/Header1.svelte.d.ts +27 -16
  194. package/dist/docs/Json.svelte.d.ts +20 -16
  195. package/dist/docs/Layout.svelte.d.ts +18 -13
  196. package/dist/docs/Link.svelte.d.ts +33 -21
  197. package/dist/docs/PathDataMenuField.svelte +14 -10
  198. package/dist/docs/PathDataMenuField.svelte.d.ts +8 -18
  199. package/dist/docs/Preview.svelte +20 -7
  200. package/dist/docs/Preview.svelte.d.ts +12 -22
  201. package/dist/docs/TilesetField.svelte.d.ts +21 -17
  202. package/dist/docs/TransformDebug.svelte +5 -6
  203. package/dist/docs/TransformDebug.svelte.d.ts +18 -14
  204. package/dist/docs/ViewSourceButton.svelte.d.ts +21 -17
  205. package/dist/types/d3-shape-extentions.d.ts +7 -0
  206. package/dist/utils/afterTick.d.ts +5 -0
  207. package/dist/utils/afterTick.js +8 -0
  208. package/dist/utils/arcText.svelte.d.ts +57 -0
  209. package/dist/utils/arcText.svelte.js +262 -0
  210. package/dist/utils/array.d.ts +9 -1
  211. package/dist/utils/array.js +13 -0
  212. package/dist/utils/attributes.d.ts +29 -0
  213. package/dist/utils/attributes.js +40 -0
  214. package/dist/utils/canvas.js +47 -10
  215. package/dist/utils/chart.d.ts +78 -0
  216. package/dist/utils/chart.js +512 -0
  217. package/dist/utils/color.d.ts +1 -0
  218. package/dist/utils/color.js +8 -0
  219. package/dist/utils/common.d.ts +3 -5
  220. package/dist/utils/common.js +3 -2
  221. package/dist/utils/connectorUtils.d.ts +21 -0
  222. package/dist/utils/connectorUtils.js +111 -0
  223. package/dist/utils/createId.d.ts +7 -0
  224. package/dist/utils/createId.js +9 -0
  225. package/dist/utils/debug.d.ts +1 -0
  226. package/dist/utils/debug.js +84 -0
  227. package/dist/utils/filterObject.d.ts +9 -0
  228. package/dist/utils/filterObject.js +12 -0
  229. package/dist/utils/graph/dagre.d.ts +34 -0
  230. package/dist/utils/graph/dagre.js +78 -0
  231. package/dist/utils/graph/dagre.test.d.ts +1 -0
  232. package/dist/utils/{graph.test.js → graph/dagre.test.js} +19 -33
  233. package/dist/utils/graph/sankey.d.ts +28 -0
  234. package/dist/utils/{graph.js → graph/sankey.js} +13 -41
  235. package/dist/utils/index.d.ts +3 -1
  236. package/dist/utils/index.js +3 -1
  237. package/dist/utils/key.svelte.d.ts +3 -0
  238. package/dist/utils/key.svelte.js +11 -0
  239. package/dist/utils/legendPayload.d.ts +7 -0
  240. package/dist/utils/legendPayload.js +8 -0
  241. package/dist/utils/motion.svelte.d.ts +140 -0
  242. package/dist/utils/motion.svelte.js +180 -0
  243. package/dist/utils/motion.test.d.ts +1 -0
  244. package/dist/utils/motion.test.js +213 -0
  245. package/dist/utils/{rect.d.ts → rect.svelte.d.ts} +7 -4
  246. package/dist/utils/rect.svelte.js +105 -0
  247. package/dist/utils/scales.svelte.d.ts +90 -0
  248. package/dist/utils/{scales.js → scales.svelte.js} +100 -39
  249. package/dist/utils/stack.d.ts +2 -3
  250. package/dist/utils/stack.js +1 -1
  251. package/dist/utils/string.js +87 -0
  252. package/dist/utils/ticks.d.ts +8 -2
  253. package/dist/utils/ticks.js +28 -0
  254. package/dist/utils/ticks.test.d.ts +1 -0
  255. package/dist/utils/ticks.test.js +67 -0
  256. package/dist/utils/types.d.ts +81 -0
  257. package/package.json +25 -24
  258. package/dist/components/ChartContext.svelte +0 -295
  259. package/dist/components/ChartContext.svelte.d.ts +0 -139
  260. package/dist/components/TransformContext.svelte.d.ts +0 -158
  261. package/dist/stores/motionStore.d.ts +0 -30
  262. package/dist/stores/motionStore.js +0 -62
  263. package/dist/utils/graph.d.ts +0 -37
  264. package/dist/utils/rect.js +0 -107
  265. package/dist/utils/scales.d.ts +0 -66
  266. /package/dist/{utils/graph.test.d.ts → components/charts/types.js} +0 -0
@@ -1,15 +1,182 @@
1
+ <script lang="ts" module>
2
+ import type { CommonStyleProps, Without } from '../utils/types.js';
3
+ import type { SVGAttributes } from 'svelte/elements';
4
+ import { createMotion, type MotionProp } from '../utils/motion.svelte.js';
5
+
6
+ export type TextPropsWithoutHTML = {
7
+ /**
8
+ * text value
9
+ * @default 0
10
+ */
11
+ value?: string | number;
12
+
13
+ /**
14
+ * The fill color of the text
15
+ */
16
+ fill?: string;
17
+
18
+ /**
19
+ * Maximum width to occupy (approximate as words are not split)
20
+ */
21
+ width?: number;
22
+
23
+ /**
24
+ * x position of the text
25
+ *
26
+ * @default 0
27
+ */
28
+ x?: string | number;
29
+
30
+ /**
31
+ * Initial x position of the text
32
+ *
33
+ * @default x
34
+ */
35
+ initialX?: string | number;
36
+
37
+ /**
38
+ * y position of the text
39
+ *
40
+ * @default 0
41
+ */
42
+ y?: string | number;
43
+
44
+ /**
45
+ * Initial y position of the text
46
+ *
47
+ * @default y
48
+ */
49
+ initialY?: string | number;
50
+
51
+ /**
52
+ * dx offset of the text
53
+ *
54
+ * @default 0
55
+ */
56
+ dx?: string | number;
57
+
58
+ /**
59
+ * dy offset of the text
60
+ *
61
+ * @default 0
62
+ */
63
+ dy?: string | number;
64
+
65
+ /**
66
+ * Desired "line height" of the text, implemented as y offsets
67
+ *
68
+ * @default "1em"
69
+ */
70
+ lineHeight?: string;
71
+
72
+ /**
73
+ * Cap height of the text
74
+ * @default '0.71em'
75
+ */
76
+ capHeight?: string;
77
+
78
+ /**
79
+ * Whether to scale the fontSize to accommodate the specified width
80
+ *
81
+ * @default false
82
+ */
83
+
84
+ scaleToFit?: boolean;
85
+
86
+ /**
87
+ * Horizontal text anchor
88
+ *
89
+ * @default 'start'
90
+ */
91
+ textAnchor?: 'start' | 'middle' | 'end' | 'inherit';
92
+
93
+ /**
94
+ * Vertical text anchor
95
+ *
96
+ * @default 'end'
97
+ */
98
+ verticalAnchor?: 'start' | 'middle' | 'end' | 'inherit';
99
+
100
+ /**
101
+ * Rotational angle of the text
102
+ */
103
+ rotate?: number;
104
+
105
+ /**
106
+ * A bindable reference to the wrapping `<svg>` element.
107
+ *
108
+ * @bindable
109
+ */
110
+ svgRef?: SVGElement;
111
+
112
+ /**
113
+ * Props to pass to the wrapping `<svg>` element.
114
+ */
115
+ svgProps?: Omit<SVGAttributes<SVGElement>, 'children'>;
116
+
117
+ /**
118
+ * A bindable reference to the inner `<text>` element
119
+ *
120
+ * @bindable
121
+ */
122
+ ref?: SVGTextElement;
123
+ motion?: MotionProp;
124
+
125
+ /**
126
+ * Whether to enable text truncation
127
+ */
128
+ truncate?: boolean | TruncateTextOptions;
129
+
130
+ /**
131
+ * A unique identifier for the SVG path element.
132
+ * One is generated by default if not provided.
133
+ *
134
+ */
135
+ pathId?: string;
136
+
137
+ /**
138
+ * The path to render the text along.
139
+ */
140
+ path?: string | null;
141
+
142
+ /**
143
+ * Specify the offset for the start of the text along the path.
144
+ * Can be a percentage ('50%') or a length value.
145
+ *
146
+ * @default '0%'
147
+ */
148
+ startOffset?: string | number;
149
+ } & CommonStyleProps;
150
+
151
+ export type TextProps = TextPropsWithoutHTML &
152
+ Without<SVGAttributes<SVGTextElement>, TextPropsWithoutHTML>;
153
+
154
+ function getPathLength(pathRef: SVGPathElement | undefined) {
155
+ if (pathRef && typeof pathRef.getTotalLength === 'function') {
156
+ try {
157
+ return pathRef.getTotalLength();
158
+ } catch (e) {
159
+ console.error('Error getting path length:', e);
160
+ return 0;
161
+ }
162
+ }
163
+ return 0;
164
+ }
165
+ </script>
166
+
1
167
  <script lang="ts">
2
- import { onDestroy, tick } from 'svelte';
3
- import type { spring as springStore, tweened as tweenedStore } from 'svelte/motion';
4
168
  import { cls } from '@layerstack/tailwind';
5
- import { objectId } from '@layerstack/utils/object';
6
169
  import { merge } from 'lodash-es';
7
170
 
8
171
  import { getRenderContext } from './Chart.svelte';
9
- import { getCanvasContext } from './layout/Canvas.svelte';
10
- import { getStringWidth } from '../utils/string.js';
11
- import { motionStore } from '../stores/motionStore.js';
12
- import { renderText, type ComputedStylesOptions } from '../utils/canvas.js';
172
+ import { registerCanvasComponent } from './layout/Canvas.svelte';
173
+ import { getStringWidth, truncateText, type TruncateTextOptions } from '../utils/string.js';
174
+ import { getComputedStyles, renderText, type ComputedStylesOptions } from '../utils/canvas.js';
175
+
176
+ import { createKey } from '../utils/key.svelte.js';
177
+ import { layerClass } from '../utils/attributes.js';
178
+ import { degreesToRadians } from '../utils/math.js';
179
+ import { createId } from '../utils/createId.js';
13
180
 
14
181
  /*
15
182
  TODO:
@@ -25,88 +192,117 @@
25
192
  - https://github.com/airbnb/visx/blob/master/packages/visx-demo/src/pages/text.tsx
26
193
  */
27
194
 
28
- /** text value */
29
- export let value: string | number = 0;
30
-
31
- /** Maximum width to occupy (approximate as words are not split) */
32
- export let width: number | undefined = undefined;
33
-
34
- /** x position of the text */
35
- export let x: string | number = 0;
36
- export let initialX = x;
37
-
38
- /** y position of the text */
39
- export let y: string | number = 0;
40
- export let initialY = y;
41
-
42
- /** dx offset of the text */
43
- export let dx: string | number = 0;
195
+ const uid = $props.id();
196
+
197
+ let {
198
+ value,
199
+ x = 0,
200
+ initialX = x,
201
+ y = 0,
202
+ initialY = y,
203
+ dx = 0,
204
+ dy = 0,
205
+ lineHeight = '1em',
206
+ capHeight = '0.71em',
207
+ width,
208
+ scaleToFit = false,
209
+ textAnchor = 'start',
210
+ verticalAnchor = 'end',
211
+ rotate,
212
+ opacity = 1,
213
+ strokeWidth = 0,
214
+ stroke,
215
+ fill,
216
+ fillOpacity,
217
+ motion,
218
+ svgRef: svgRefProp = $bindable(),
219
+ ref: refProp = $bindable(),
220
+ class: className,
221
+ svgProps = {},
222
+ truncate = false,
223
+ path,
224
+ pathId = createId('text-path', uid),
225
+ startOffset = '0%',
226
+ transform: transformProp,
227
+ ...restProps
228
+ }: TextProps = $props();
229
+
230
+ let ref = $state<SVGTextElement>();
231
+ let svgRef = $state<SVGElement>();
232
+ let pathRef = $state<SVGPathElement>();
233
+
234
+ $effect.pre(() => {
235
+ refProp = ref;
236
+ });
44
237
 
45
- /** dy offset of the text */
46
- export let dy: string | number = 0;
238
+ $effect.pre(() => {
239
+ svgRefProp = svgRef;
240
+ });
47
241
 
48
- /** Desired "line height" of the text, implemented as y offsets */
49
- export let lineHeight = '1em';
242
+ let style = $state<CSSStyleDeclaration>(); // TODO: read from DOM?
50
243
 
51
- /** Cap height of the text */
52
- export let capHeight = '0.71em'; // Magic number from d3
244
+ const resolvedWidth = $derived(path ? getPathLength(pathRef) : width);
53
245
 
54
- /** Whether to scale the fontSize to accommodate the specified width */
55
- export let scaleToFit: boolean = false;
246
+ const defaultTruncateOptions: TruncateTextOptions = $derived({
247
+ maxChars: undefined,
248
+ position: 'end',
249
+ maxWidth: resolvedWidth,
250
+ });
56
251
 
57
- /** Horizontal text anchor */
58
- export let textAnchor: 'start' | 'middle' | 'end' | 'inherit' = 'start';
252
+ const truncateConfig: TruncateTextOptions | boolean = $derived.by(() => {
253
+ if (typeof truncate === 'boolean') {
254
+ if (truncate) return defaultTruncateOptions;
255
+ return false;
256
+ }
257
+ return {
258
+ ...defaultTruncateOptions,
259
+ ...truncate,
260
+ };
261
+ });
59
262
 
60
- /** Vertical text anchor */
61
- export let verticalAnchor: 'start' | 'middle' | 'end' | 'inherit' = 'end'; // default SVG behavior
263
+ const rawText = $derived(value != null ? value.toString() : '');
62
264
 
63
- /** Rotational angle of the text */
64
- export let rotate: number | undefined = undefined;
265
+ const textValue = $derived.by(() => {
266
+ if (!truncateConfig) return rawText;
267
+ return truncateText(rawText, truncateConfig);
268
+ });
65
269
 
66
- export let fill: string | undefined = undefined;
67
- export let fillOpacity: number | undefined = undefined;
68
- export let stroke: string | undefined = undefined;
69
- export let strokeWidth: number | undefined = undefined;
70
- export let opacity: number | undefined = undefined;
270
+ const renderCtx = getRenderContext();
71
271
 
72
- let className: string | undefined = undefined;
73
- export { className as class };
272
+ const words = $derived(textValue ? textValue.split(/(?:(?!\u00A0+)\s+)/) : []);
74
273
 
75
- let wordsByLines: { words: string[]; width?: number }[] = [];
76
- let wordsWithWidth: { word: string; width: number }[] = [];
77
- let spaceWidth: number = 0;
274
+ const wordsWithWidth = $derived(
275
+ words.map((word) => ({
276
+ word,
277
+ width: getStringWidth(word, style) || 0,
278
+ }))
279
+ );
78
280
 
79
- let style: CSSStyleDeclaration | undefined = undefined; // TODO: read from DOM?
281
+ const spaceWidth = $derived(getStringWidth('\u00A0', style) || 0);
80
282
 
81
- $: words = value != null ? value.toString().split(/(?:(?!\u00A0+)\s+)/) : [];
283
+ const wordsByLines = $derived(
284
+ wordsWithWidth.reduce((result: { words: string[]; width?: number }[], item) => {
285
+ const currentLine = result[result.length - 1];
82
286
 
83
- $: wordsWithWidth = words.map((word) => ({
84
- word,
85
- width: getStringWidth(word, style) || 0,
86
- }));
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;
295
+ } 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);
299
+ }
87
300
 
88
- $: spaceWidth = getStringWidth('\u00A0', style) || 0;
301
+ return result;
302
+ }, [])
303
+ );
89
304
 
90
- $: wordsByLines = wordsWithWidth.reduce((result: typeof wordsByLines, item) => {
91
- const currentLine = result[result.length - 1];
92
-
93
- if (
94
- currentLine &&
95
- (width == null || scaleToFit || (currentLine.width || 0) + item.width + spaceWidth < width)
96
- ) {
97
- // Word can be added to an existing line
98
- currentLine.words.push(item.word);
99
- currentLine.width = currentLine.width || 0;
100
- currentLine.width += item.width + spaceWidth;
101
- } else {
102
- // Add first word to line or word is too long to scaleToFit on existing line
103
- const newLine = { words: [item.word], width: item.width };
104
- result.push(newLine);
105
- }
106
-
107
- return result;
108
- }, []);
109
- $: lines = wordsByLines.length;
305
+ const lines = $derived(wordsByLines.length);
110
306
 
111
307
  /**
112
308
  * Convert css value to pixel value (ex. 0.71em => 11.36)
@@ -114,14 +310,11 @@
114
310
  function getPixelValue(cssValue: number | string) {
115
311
  // TODO: Properly measure pixel values using DOM (handle inherited font size, zoom, etc)
116
312
 
117
- if (typeof cssValue === 'number') {
118
- return cssValue;
119
- }
313
+ if (typeof cssValue === 'number') return cssValue;
120
314
 
121
- // @ts-expect-error
122
- const [match, value, units] = cssValue.match(/([\d.]+)(\D+)/);
123
- const number = Number(value);
124
- switch (units) {
315
+ const result = cssValue.match(/([\d.]+)(\D+)/);
316
+ const number = Number(result?.[1]);
317
+ switch (result?.[2]) {
125
318
  case 'px':
126
319
  return number;
127
320
  case 'em':
@@ -132,35 +325,47 @@
132
325
  }
133
326
  }
134
327
 
135
- let startDy = 0;
136
- $: if (verticalAnchor === 'start') {
137
- startDy = getPixelValue(capHeight);
138
- } else if (verticalAnchor === 'middle') {
139
- startDy = ((lines - 1) / 2) * -getPixelValue(lineHeight) + getPixelValue(capHeight) / 2;
140
- } else {
141
- startDy = (lines - 1) * -getPixelValue(lineHeight);
142
- }
328
+ const startDy = $derived.by(() => {
329
+ 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
+ });
143
337
 
144
- let scaleTransform = '';
145
- $: if (
146
- scaleToFit &&
147
- lines > 0 &&
148
- typeof x == 'number' &&
149
- typeof y == 'number' &&
150
- typeof width == 'number'
151
- ) {
152
- const lineWidth = wordsByLines[0].width || 1;
153
- const sx = width / lineWidth;
154
- const sy = sx;
155
- const originX = x - sx * x;
156
- const originY = y - sy * y;
157
- scaleTransform = `matrix(${sx}, 0, 0, ${sy}, ${originX}, ${originY})`;
158
- } else {
159
- scaleTransform = '';
160
- }
161
- $: rotateTransform = rotate ? `rotate(${rotate}, ${x}, ${y})` : '';
338
+ const pathStartDy = $derived.by(() => {
339
+ if (verticalAnchor === 'start') {
340
+ return getPixelValue(capHeight);
341
+ } else if (verticalAnchor === 'middle') {
342
+ return (0 / 2) * -getPixelValue(lineHeight) + getPixelValue(capHeight) / 2;
343
+ } else {
344
+ return 0 * -getPixelValue(lineHeight);
345
+ }
346
+ });
347
+
348
+ const scaleTransform = $derived.by(() => {
349
+ if (
350
+ scaleToFit &&
351
+ lines > 0 &&
352
+ typeof x == 'number' &&
353
+ typeof y == 'number' &&
354
+ typeof width == 'number'
355
+ ) {
356
+ const lineWidth = wordsByLines[0].width || 1;
357
+ const sx = width / lineWidth;
358
+ const sy = sx;
359
+ const originX = x - sx * x;
360
+ const originY = y - sy * y;
361
+ return `matrix(${sx}, 0, 0, ${sy}, ${originX}, ${originY})`;
362
+ } else {
363
+ return '';
364
+ }
365
+ });
162
366
 
163
- $: transform = `${scaleTransform} ${rotateTransform}`;
367
+ const rotateTransform = $derived(rotate ? `rotate(${rotate}, ${x}, ${y})` : '');
368
+ const transform = $derived(transformProp ?? `${scaleTransform} ${rotateTransform}`);
164
369
 
165
370
  function isValidXOrY(xOrY: string | number | undefined) {
166
371
  return (
@@ -171,102 +376,158 @@
171
376
  );
172
377
  }
173
378
 
174
- export let spring: boolean | Parameters<typeof springStore>[1] = undefined;
175
- export let tweened: boolean | Parameters<typeof tweenedStore>[1] = undefined;
176
-
177
- let tweened_x = motionStore(initialX, { spring, tweened });
178
- let tweened_y = motionStore(initialY, { spring, tweened });
179
-
180
- $: tick().then(() => {
181
- tweened_x.set(x);
182
- tweened_y.set(y);
183
- });
184
-
185
- const renderContext = getRenderContext();
186
- const canvasContext = getCanvasContext();
379
+ const motionX = createMotion(initialX, () => x, motion);
380
+ const motionY = createMotion(initialY, () => y, motion);
187
381
 
188
382
  function render(
189
383
  ctx: CanvasRenderingContext2D,
190
384
  styleOverrides: ComputedStylesOptions | undefined
191
385
  ) {
192
- wordsByLines.forEach((line, index) => {
386
+ const effectiveLineHeight = getPixelValue(lineHeight);
387
+ const baseY = getPixelValue(motionY.current) + getPixelValue(dy) + getPixelValue(startDy);
388
+ const baseX = getPixelValue(motionX.current) + getPixelValue(dx);
389
+
390
+ ctx.save();
391
+
392
+ if (rotate !== undefined) {
393
+ const centerX = getPixelValue(x);
394
+ const centerY = getPixelValue(y);
395
+ const radians = degreesToRadians(rotate);
396
+
397
+ ctx.translate(centerX, centerY);
398
+ ctx.rotate(radians);
399
+ ctx.translate(-centerX, -centerY);
400
+ }
401
+
402
+ const styles = styleOverrides
403
+ ? merge({ styles: { strokeWidth } }, styleOverrides)
404
+ : {
405
+ styles: {
406
+ fill,
407
+ fillOpacity,
408
+ stroke,
409
+ strokeWidth,
410
+ opacity,
411
+ paintOrder: 'stroke',
412
+ textAnchor,
413
+ },
414
+ classes: cls(fill === undefined && 'fill-surface-content', className),
415
+ };
416
+
417
+ const computedStyles = getComputedStyles(ctx.canvas, styles);
418
+
419
+ ctx.font = `${computedStyles.fontSize} ${computedStyles.fontFamily}`;
420
+
421
+ const textAlign = textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'end' : 'start';
422
+ ctx.textAlign = textAlign;
423
+
424
+ for (let index = 0; index < wordsByLines.length; index++) {
425
+ const line = wordsByLines[index];
426
+ const text = line.words.join(' ');
427
+
428
+ // no need to manually adjust x for textAnchor since ctx.textAlign handles it
429
+ const xPos = baseX;
430
+ const yPos = baseY + index * effectiveLineHeight;
431
+
193
432
  renderText(
194
433
  ctx,
195
- line.words.join(' '),
434
+ text,
196
435
  {
197
- x: getPixelValue($tweened_x) + getPixelValue(dx),
198
- y:
199
- getPixelValue($tweened_y) +
200
- getPixelValue(dy) +
201
- (index === 0 ? startDy : getPixelValue(lineHeight)),
436
+ x: xPos,
437
+ y: yPos,
202
438
  },
203
- styleOverrides
204
- ? merge({ styles: { strokeWidth } }, styleOverrides)
205
- : {
206
- styles: {
207
- fill,
208
- fillOpacity,
209
- stroke,
210
- strokeWidth,
211
- opacity,
212
- paintOrder: 'stroke',
213
- textAnchor,
214
- },
215
- classes: cls(fill === undefined && 'fill-surface-content', className),
216
- }
439
+ styles
217
440
  );
218
- });
219
- }
441
+ }
220
442
 
221
- // TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
222
- $: fillKey = fill && typeof fill === 'object' ? objectId(fill) : fill;
223
- $: strokeKey = stroke && typeof stroke === 'object' ? objectId(stroke) : stroke;
224
-
225
- $: if (renderContext === 'canvas') {
226
- // Redraw when props change
227
- value &&
228
- $tweened_x &&
229
- $tweened_y &&
230
- fillKey &&
231
- strokeKey &&
232
- strokeWidth &&
233
- opacity &&
234
- className;
235
- canvasContext.invalidate();
443
+ ctx.restore();
236
444
  }
237
445
 
238
- let canvasUnregister: ReturnType<typeof canvasContext.register>;
239
- $: if (renderContext === 'canvas') {
240
- canvasUnregister = canvasContext.register({ name: 'Text', render });
446
+ // TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
447
+ const fillKey = createKey(() => fill);
448
+ const strokeKey = createKey(() => stroke);
449
+
450
+ if (renderCtx === 'canvas') {
451
+ registerCanvasComponent({
452
+ name: 'Text',
453
+ render,
454
+ deps: () => [
455
+ value,
456
+ motionX.current,
457
+ motionY.current,
458
+ fillKey.current,
459
+ strokeKey.current,
460
+ strokeWidth,
461
+ opacity,
462
+ className,
463
+ truncateConfig,
464
+ rotate,
465
+ lineHeight,
466
+ textAnchor,
467
+ verticalAnchor,
468
+ ],
469
+ });
241
470
  }
242
-
243
- onDestroy(() => {
244
- if (renderContext === 'canvas') {
245
- canvasUnregister();
246
- }
247
- });
248
471
  </script>
249
472
 
250
- {#if renderContext === 'svg'}
473
+ {#if renderCtx === 'svg'}
251
474
  <!-- `overflow: visible` allow contents to be shown outside element -->
252
475
  <!-- `paint-order: stroke` supports stroke outlining text -->
253
- <svg x={dx} y={dy} class="overflow-visible [paint-order:stroke]">
254
- {#if isValidXOrY(x) && isValidXOrY(y)}
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
+ >
483
+ {#if path}
484
+ <defs>
485
+ {#key path}
486
+ <path bind:this={pathRef} id={pathId} d={path} />
487
+ {/key}
488
+ </defs>
489
+ <text
490
+ bind:this={ref}
491
+ {dy}
492
+ {...restProps}
493
+ {fill}
494
+ fill-opacity={fillOpacity}
495
+ {stroke}
496
+ stroke-width={strokeWidth}
497
+ {opacity}
498
+ transform={transformProp}
499
+ class={cls(layerClass('text'), fill === undefined && 'fill-surface-content', className)}
500
+ >
501
+ <textPath
502
+ style="text-anchor: {textAnchor};"
503
+ href="#{pathId}"
504
+ {startOffset}
505
+ class={cls(layerClass('text-path'))}
506
+ >
507
+ {wordsByLines.map((line) => line.words.join(' ')).join()}
508
+ </textPath>
509
+ </text>
510
+ {:else if isValidXOrY(x) && isValidXOrY(y)}
255
511
  <text
256
- x={$tweened_x}
257
- y={$tweened_y}
512
+ bind:this={ref}
513
+ x={motionX.current}
514
+ y={motionY.current}
258
515
  {transform}
259
516
  text-anchor={textAnchor}
260
- {...$$restProps}
517
+ {...restProps}
261
518
  {fill}
262
519
  fill-opacity={fillOpacity}
263
520
  {stroke}
264
521
  stroke-width={strokeWidth}
265
522
  {opacity}
266
- class={cls(fill === undefined && 'fill-surface-content', className)}
523
+ class={cls(layerClass('text'), fill === undefined && 'fill-surface-content', className)}
267
524
  >
268
525
  {#each wordsByLines as line, index}
269
- <tspan x={$tweened_x} dy={index === 0 ? startDy : lineHeight}>
526
+ <tspan
527
+ x={motionX.current}
528
+ dy={index === 0 ? startDy : lineHeight}
529
+ class={layerClass('text-tspan')}
530
+ >
270
531
  {line.words.join(' ')}
271
532
  </tspan>
272
533
  {/each}