layerchart 2.0.0-next.61 → 2.0.0-next.63

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 (182) hide show
  1. package/dist/canvas.d.ts +6 -2
  2. package/dist/canvas.js +6 -2
  3. package/dist/components/Arc/Arc.base.svelte +49 -11
  4. package/dist/components/Arc/Arc.shared.svelte.d.ts +2 -0
  5. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-full-circle--360-degree-range--1.png +0 -0
  6. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-full-circle--360-degree-range--2.png +0 -0
  7. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-innerRadius-of-0--pie-slice--1.png +0 -0
  8. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-innerRadius-of-0--pie-slice--2.png +0 -0
  9. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-negative-domain-values-1.png +0 -0
  10. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-negative-domain-values-2.png +0 -0
  11. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-partial-arc--e-g---180-degrees--1.png +0 -0
  12. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-partial-arc--e-g---180-degrees--2.png +0 -0
  13. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-at-max-domain-1.png +0 -0
  14. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-at-max-domain-2.png +0 -0
  15. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-below-domain-min-1.png +0 -0
  16. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-below-domain-min-2.png +0 -0
  17. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-exceeding-domain-max-1.png +0 -0
  18. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-exceeding-domain-max-2.png +0 -0
  19. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-of-0-1.png +0 -0
  20. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-of-0-2.png +0 -0
  21. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-pointer-enter-events-1.png +0 -0
  22. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-pointer-enter-events-2.png +0 -0
  23. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-pointer-move-events-1.png +0 -0
  24. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-pointer-move-events-2.png +0 -0
  25. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-touch-move-events-1.png +0 -0
  26. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-touch-move-events-2.png +0 -0
  27. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-custom-class-1.png +0 -0
  28. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-custom-class-2.png +0 -0
  29. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-fill-color-1.png +0 -0
  30. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-fill-color-2.png +0 -0
  31. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-fillOpacity-1.png +0 -0
  32. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-fillOpacity-2.png +0 -0
  33. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-offset-to-arc-position-1.png +0 -0
  34. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-offset-to-arc-position-2.png +0 -0
  35. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-opacity-1.png +0 -0
  36. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-opacity-2.png +0 -0
  37. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-stroke-color-1.png +0 -0
  38. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-stroke-color-2.png +0 -0
  39. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-strokeWidth-1.png +0 -0
  40. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-strokeWidth-2.png +0 -0
  41. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-zero-offset-by-default-1.png +0 -0
  42. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-zero-offset-by-default-2.png +0 -0
  43. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-call-tooltip-hide-on-pointer-leave-1.png +0 -0
  44. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-call-tooltip-hide-on-pointer-leave-2.png +0 -0
  45. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-call-tooltip-show-on-pointer-enter-with-data-1.png +0 -0
  46. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-call-tooltip-show-on-pointer-enter-with-data-2.png +0 -0
  47. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-handle-custom-start-angle-in-range-1.png +0 -0
  48. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-handle-custom-start-angle-in-range-2.png +0 -0
  49. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-have-stroke--none--by-default-1.png +0 -0
  50. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-have-stroke--none--by-default-2.png +0 -0
  51. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-an-arc-path-with-value-1.png +0 -0
  52. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-an-arc-path-with-value-2.png +0 -0
  53. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-when-track-prop-is-provided-1.png +0 -0
  54. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-when-track-prop-is-provided-2.png +0 -0
  55. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-custom-class-1.png +0 -0
  56. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-custom-class-2.png +0 -0
  57. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackCornerRadius-1.png +0 -0
  58. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackCornerRadius-2.png +0 -0
  59. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackEndAngle-1.png +0 -0
  60. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackEndAngle-2.png +0 -0
  61. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackInnerRadius-1.png +0 -0
  62. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackInnerRadius-2.png +0 -0
  63. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackInnerRadius-and-trackOuterRadius-1.png +0 -0
  64. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackInnerRadius-and-trackOuterRadius-2.png +0 -0
  65. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackOuterRadius-1.png +0 -0
  66. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackOuterRadius-2.png +0 -0
  67. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackPadAngle-1.png +0 -0
  68. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackPadAngle-2.png +0 -0
  69. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackStartAngle-1.png +0 -0
  70. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackStartAngle-2.png +0 -0
  71. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackStartAngle-and-trackEndAngle-1.png +0 -0
  72. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackStartAngle-and-trackEndAngle-2.png +0 -0
  73. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-both-startAngle-and-endAngle-1.png +0 -0
  74. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-both-startAngle-and-endAngle-2.png +0 -0
  75. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-cornerRadius-1.png +0 -0
  76. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-cornerRadius-2.png +0 -0
  77. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-domain-1.png +0 -0
  78. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-domain-2.png +0 -0
  79. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-domain-and-range-1.png +0 -0
  80. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-domain-and-range-2.png +0 -0
  81. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-range-1.png +0 -0
  82. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-range-2.png +0 -0
  83. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-endAngle-in-radians-1.png +0 -0
  84. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-endAngle-in-radians-2.png +0 -0
  85. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-innerRadius-1.png +0 -0
  86. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-innerRadius-2.png +0 -0
  87. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-innerRadius-and-outerRadius-1.png +0 -0
  88. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-innerRadius-and-outerRadius-2.png +0 -0
  89. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-outerRadius-1.png +0 -0
  90. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-outerRadius-2.png +0 -0
  91. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-padAngle-1.png +0 -0
  92. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-padAngle-2.png +0 -0
  93. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-startAngle-in-radians-1.png +0 -0
  94. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-startAngle-in-radians-2.png +0 -0
  95. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-should-render-Arc-element-1.png +0 -0
  96. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-should-render-Arc-element-2.png +0 -0
  97. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-should-render-track-1.png +0 -0
  98. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-should-render-track-2.png +0 -0
  99. package/dist/components/ArcLabel/ArcLabel.shared.svelte.d.ts +1 -0
  100. package/dist/components/{ArcLabel.svelte.test.js → ArcLabel/ArcLabel.svelte.test.js} +3 -3
  101. package/dist/components/ArcLabel/__screenshots__/ArcLabel.svelte.test.ts/ArcLabel-renders-a-text-element-with-the-supplied-value-at-the-centroid-1.png +0 -0
  102. package/dist/components/ArcLabel/__screenshots__/ArcLabel.svelte.test.ts/ArcLabel-renders-a-text-element-with-the-supplied-value-at-the-centroid-2.png +0 -0
  103. package/dist/components/Blur/Blur.canvas.svelte +25 -0
  104. package/dist/components/Blur/Blur.canvas.svelte.d.ts +4 -0
  105. package/dist/components/Blur/Blur.html.svelte +11 -0
  106. package/dist/components/Blur/Blur.html.svelte.d.ts +4 -0
  107. package/dist/components/{Blur.svelte.d.ts → Blur/Blur.shared.svelte.d.ts} +3 -5
  108. package/dist/components/Blur/Blur.svelte +23 -0
  109. package/dist/components/Blur/Blur.svelte.d.ts +4 -0
  110. package/dist/components/Blur/Blur.svg.svelte +24 -0
  111. package/dist/components/Blur/Blur.svg.svelte.d.ts +4 -0
  112. package/dist/components/Chart/Chart.base.svelte +13 -7
  113. package/dist/components/Chart/ChartCore.svelte.test.d.ts +1 -0
  114. package/dist/components/{ChartCore.svelte.test.js → Chart/ChartCore.svelte.test.js} +1 -1
  115. package/dist/components/Circle/Circle.shared.svelte.js +24 -5
  116. package/dist/components/Circle/Circle.svelte.test.js +70 -0
  117. package/dist/components/Dodge/Dodge.shared.svelte.d.ts +132 -0
  118. package/dist/components/Dodge/Dodge.shared.svelte.js +240 -0
  119. package/dist/components/Dodge/Dodge.svelte +88 -0
  120. package/dist/components/Dodge/Dodge.svelte.d.ts +27 -0
  121. package/dist/components/Dodge/Dodge.test.d.ts +1 -0
  122. package/dist/components/Dodge/Dodge.test.js +128 -0
  123. package/dist/components/Image/Image.html.svelte +0 -8
  124. package/dist/components/Image/Image.svg.svelte +1 -9
  125. package/dist/components/Link/Link.base.svelte +15 -9
  126. package/dist/components/Path/Path.canvas.svelte +5 -2
  127. package/dist/components/Path/Path.shared.svelte.d.ts +17 -4
  128. package/dist/components/Path/Path.shared.svelte.js +26 -8
  129. package/dist/components/Path/Path.svg.svelte +75 -60
  130. package/dist/components/Pattern/Pattern.canvas.svelte +4 -1
  131. package/dist/components/Pattern/Pattern.shared.svelte.d.ts +31 -2
  132. package/dist/components/Pattern/Pattern.shared.svelte.js +20 -1
  133. package/dist/components/Pattern/Pattern.svg.svelte +17 -1
  134. package/dist/components/Raster/Raster.base.svelte +2 -8
  135. package/dist/components/Rect/Rect.canvas.svelte +2 -4
  136. package/dist/components/Rect/Rect.canvas.svelte.d.ts +1 -1
  137. package/dist/components/Rect/Rect.html.svelte +3 -9
  138. package/dist/components/Rect/Rect.html.svelte.d.ts +1 -1
  139. package/dist/components/Rect/Rect.shared.svelte.d.ts +5 -2
  140. package/dist/components/Rect/Rect.shared.svelte.js +26 -13
  141. package/dist/components/Rect/Rect.svelte.test.js +45 -0
  142. package/dist/components/Rect/Rect.svg.svelte +36 -21
  143. package/dist/components/Rect/Rect.svg.svelte.d.ts +1 -1
  144. package/dist/components/RectClipPath/RectClipPath.base.svelte +25 -1
  145. package/dist/components/RectClipPath/RectClipPath.shared.svelte.d.ts +8 -0
  146. package/dist/components/Spline/Spline.base.svelte +3 -2
  147. package/dist/components/Text/Text.canvas.svelte +9 -0
  148. package/dist/components/Text/Text.html.svelte +6 -0
  149. package/dist/components/Text/Text.shared.svelte.d.ts +25 -2
  150. package/dist/components/Text/Text.shared.svelte.js +36 -5
  151. package/dist/components/Text/Text.svelte.test.js +40 -0
  152. package/dist/components/Text/Text.svg.svelte +7 -1
  153. package/dist/components/Trail/Trail.base.svelte +10 -7
  154. package/dist/components/Waffle/Waffle.shared.svelte.d.ts +182 -0
  155. package/dist/components/Waffle/Waffle.shared.svelte.js +300 -0
  156. package/dist/components/Waffle/Waffle.svelte +148 -0
  157. package/dist/components/Waffle/Waffle.svelte.d.ts +5 -0
  158. package/dist/components/charts/__screenshots__/ArcChart.svelte.test.ts/ArcChart-uses-the-chart-value-accessor-for-explicit-per-series-tooltip-values-1.png +0 -0
  159. package/dist/components/charts/__screenshots__/ArcChart.svelte.test.ts/ArcChart-uses-the-chart-value-accessor-for-explicit-per-series-tooltip-values-2.png +0 -0
  160. package/dist/components/charts/__screenshots__/BarChart.svelte.test.ts/BarChart-legend-series-toggle-adjusts-group-scale-should-adjust-grouped-bar-widths-when-series-are-toggled-via-legend-1.png +0 -0
  161. package/dist/components/charts/__screenshots__/PieChart.svelte.test.ts/PieChart-uses-hovered-slice-identity-for-implicit-tooltip-series-1.png +0 -0
  162. package/dist/components/charts/__screenshots__/PieChart.svelte.test.ts/PieChart-uses-hovered-slice-identity-for-implicit-tooltip-series-2.png +0 -0
  163. package/dist/components/index.d.ts +6 -2
  164. package/dist/components/index.js +6 -2
  165. package/dist/html.d.ts +6 -2
  166. package/dist/html.js +6 -2
  167. package/dist/states/chart.svelte.d.ts +4 -2
  168. package/dist/states/chart.svelte.js +53 -22
  169. package/dist/states/chart.svelte.test.js +54 -1
  170. package/dist/states/series.svelte.js +9 -13
  171. package/dist/states/series.svelte.test.js +5 -1
  172. package/dist/svg.d.ts +6 -2
  173. package/dist/svg.js +6 -2
  174. package/dist/utils/canvas.js +54 -13
  175. package/dist/utils/canvas.svelte.test.js +44 -0
  176. package/dist/utils/download.d.ts +5 -3
  177. package/dist/utils/download.js +36 -16
  178. package/dist/utils/stack.js +10 -2
  179. package/package.json +1 -1
  180. package/dist/components/Blur.svelte +0 -49
  181. /package/dist/components/{ArcLabel.svelte.test.d.ts → ArcLabel/ArcLabel.svelte.test.d.ts} +0 -0
  182. /package/dist/components/{ChartCore.svelte.test.d.ts → Blur/Blur.shared.svelte.js} +0 -0
@@ -0,0 +1,128 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { dodge } from './Dodge.shared.svelte.js';
3
+ /** Build inputs for the circular case (`rx === ry === r`). */
4
+ function input(arr) {
5
+ return arr.map((d, index) => ({
6
+ x: d.x,
7
+ rx: d.r,
8
+ ry: d.r,
9
+ data: { id: d.id },
10
+ index,
11
+ }));
12
+ }
13
+ /** Build inputs for rectangular cases (per-axis half-extents). */
14
+ function rectInput(arr) {
15
+ return arr.map((d, index) => ({
16
+ x: d.x,
17
+ rx: d.rx,
18
+ ry: d.ry,
19
+ data: { id: d.id },
20
+ index,
21
+ }));
22
+ }
23
+ describe('dodge() — circular packing', () => {
24
+ it('places a single item at the natural anchor position', () => {
25
+ const out = dodge(input([{ id: 'a', x: 50, r: 10 }]), {
26
+ axis: 'y',
27
+ anchor: 'bottom',
28
+ padding: 0,
29
+ baseline: 100,
30
+ });
31
+ expect(out).toHaveLength(1);
32
+ expect(out[0].x).toBe(50);
33
+ expect(out[0].y).toBe(90); // baseline - r
34
+ });
35
+ it('places non-overlapping items both at the anchor', () => {
36
+ // Two items 100px apart, r=10 each — no horizontal overlap, both can sit at the bottom
37
+ const out = dodge(input([
38
+ { id: 'a', x: 0, r: 10 },
39
+ { id: 'b', x: 100, r: 10 },
40
+ ]), { axis: 'y', anchor: 'bottom', padding: 0, baseline: 100 });
41
+ expect(out[0].y).toBe(90);
42
+ expect(out[1].y).toBe(90);
43
+ });
44
+ it('stacks overlapping items vertically', () => {
45
+ // Two items at the same x — must stack with a gap ≥ r1 + r2 + padding = 21.
46
+ // padding is also applied to the chart edge, so the first item sits 1px
47
+ // above the bottom (y=89, not 90).
48
+ const out = dodge(input([
49
+ { id: 'a', x: 50, r: 10 },
50
+ { id: 'b', x: 50, r: 10 },
51
+ ]), { axis: 'y', anchor: 'bottom', padding: 1, baseline: 100 });
52
+ expect(out[0].y).toBe(89);
53
+ expect(out[1].y).toBeCloseTo(89 - 21, 5); // gap of sumR+padding
54
+ });
55
+ it('returns items in original input order', () => {
56
+ const out = dodge(input([
57
+ { id: 'small', x: 50, r: 5 },
58
+ { id: 'large', x: 50, r: 20 },
59
+ ]), { axis: 'y', anchor: 'bottom', padding: 0, baseline: 100 });
60
+ expect(out[0].data.id).toBe('small');
61
+ expect(out[1].data.id).toBe('large');
62
+ // Algorithm processes input order: small placed first at bottom (y=95),
63
+ // then large stacks above.
64
+ expect(out[0].y).toBe(95);
65
+ expect(out[1].y).toBeLessThan(95);
66
+ });
67
+ it('respects anchor=top (stacks downward)', () => {
68
+ const out = dodge(input([
69
+ { id: 'a', x: 50, r: 10 },
70
+ { id: 'b', x: 50, r: 10 },
71
+ ]), { axis: 'y', anchor: 'top', padding: 0, baseline: 0 });
72
+ expect(out[0].y).toBe(10); // first at top
73
+ expect(out[1].y).toBeCloseTo(30, 5); // second below, gap = r1+r2 = 20
74
+ });
75
+ it('swaps axes for axis=x', () => {
76
+ const out = dodge(input([{ id: 'a', x: 30, r: 5 }]), {
77
+ axis: 'x',
78
+ anchor: 'left',
79
+ padding: 0,
80
+ baseline: 0,
81
+ });
82
+ expect(out[0].x).toBe(5); // dodged: at left edge
83
+ expect(out[0].y).toBe(30); // anchor y preserved
84
+ });
85
+ });
86
+ describe('dodge() — rectangular packing (rx + ry)', () => {
87
+ // ry = 8 ⇒ row spacing = 2 * ry = 16 (matches the old `rowHeight: 16` cases).
88
+ it('places non-overlapping items in row 0', () => {
89
+ const out = dodge(rectInput([
90
+ { id: 'a', x: 0, rx: 20, ry: 8 }, // spans -20 to 20
91
+ { id: 'b', x: 100, rx: 20, ry: 8 }, // spans 80 to 120
92
+ ]), { axis: 'y', anchor: 'bottom', padding: 0, baseline: 200, rectangular: true });
93
+ // Row 0 center from bottom = baseline - ry = 192
94
+ expect(out[0].y).toBe(192);
95
+ expect(out[1].y).toBe(192);
96
+ });
97
+ it('stacks horizontally-overlapping items into separate rows', () => {
98
+ const out = dodge(rectInput([
99
+ { id: 'a', x: 50, rx: 30, ry: 8 },
100
+ { id: 'b', x: 60, rx: 30, ry: 8 },
101
+ { id: 'c', x: 70, rx: 30, ry: 8 },
102
+ ]), { axis: 'y', anchor: 'bottom', padding: 0, baseline: 200, rectangular: true });
103
+ // Input order: a → row 0, b overlaps a → row 1, c overlaps both → row 2
104
+ // Row centers from bottom: 192, 176, 160.
105
+ expect(out[0].y).toBe(192);
106
+ expect(out[1].y).toBe(176);
107
+ expect(out[2].y).toBe(160);
108
+ });
109
+ it("reuses row 0 when later items don't overlap earlier ones in that row", () => {
110
+ const out = dodge(rectInput([
111
+ { id: 'a', x: 0, rx: 10, ry: 8 },
112
+ { id: 'b', x: 5, rx: 10, ry: 8 }, // overlaps a → row 1
113
+ { id: 'c', x: 100, rx: 10, ry: 8 }, // far from both → row 0
114
+ ]), { axis: 'y', anchor: 'bottom', padding: 0, baseline: 200, rectangular: true });
115
+ expect(out[0].y).toBe(192);
116
+ expect(out[1].y).toBe(176);
117
+ expect(out[2].y).toBe(192);
118
+ });
119
+ it('respects anchor=top for rectangular mode', () => {
120
+ const out = dodge(rectInput([
121
+ { id: 'a', x: 50, rx: 30, ry: 8 },
122
+ { id: 'b', x: 60, rx: 30, ry: 8 },
123
+ ]), { axis: 'y', anchor: 'top', padding: 0, baseline: 0, rectangular: true });
124
+ // Row 0 from top: 0 + ry = 8. Row 1: 24.
125
+ expect(out[0].y).toBe(8);
126
+ expect(out[1].y).toBe(24);
127
+ });
128
+ });
@@ -104,11 +104,3 @@
104
104
  {...rest as any}
105
105
  />
106
106
  {/if}
107
-
108
- <style>
109
- @layer base {
110
- :global(:where(.lc-image)) {
111
- pointer-events: none;
112
- }
113
- }
114
- </style>
@@ -127,12 +127,4 @@
127
127
  {opacity}
128
128
  class={cls('lc-image', className)}
129
129
  />
130
- {/if}
131
-
132
- <style>
133
- @layer base {
134
- :global(:where(.lc-image)) {
135
- pointer-events: none;
136
- }
137
- }
138
- </style>
130
+ {/if}
@@ -18,7 +18,6 @@
18
18
  getLinkRadialPresetPath,
19
19
  } from '../../utils/linkUtils.js';
20
20
  import { getChartContext } from '../../contexts/chart.js';
21
- import { extractLayerProps } from '../../utils/attributes.js';
22
21
  import { accessor, type Accessor } from '../../utils/common.js';
23
22
  import { cls } from '@layerstack/tailwind';
24
23
  import {
@@ -203,11 +202,20 @@
203
202
  }
204
203
  : undefined;
205
204
 
206
- const motionPath = createMotion(
207
- '',
208
- () => singlePathData,
209
- tweenOptions ? tweenOptions : { type: 'none' }
210
- );
205
+ // Pass `tweenOptions` (possibly undefined) so `createMotion` takes its
206
+ // fast-path passthrough when no tween is configured — avoids allocating
207
+ // a MotionNone container + per-instance `$effect` that fires on every
208
+ // x1/y1/x2/y2 change. Critical for force-simulation graphs which can
209
+ // have hundreds of links updating on every tick.
210
+ const motionPath = createMotion('', () => singlePathData, tweenOptions);
211
+
212
+ // Stable getter handed to `<Path>` instead of `motionPath.current`.
213
+ // Reading `motionPath.current` directly in the template would subscribe
214
+ // *this* component's template to per-tick updates, forcing the entire
215
+ // `<Path>` block to re-evaluate (and re-spread props) on every change.
216
+ // By passing a function reference, the per-tick `current` read happens
217
+ // inside `<Path>`'s own template — the parent stays stable.
218
+ const getPathData = () => motionPath.current;
211
219
 
212
220
  const arrayRows = $derived(isArrayMode ? data ?? ctx.data ?? [] : []);
213
221
 
@@ -237,7 +245,6 @@
237
245
  {markerStart}
238
246
  {markerMid}
239
247
  {markerEnd}
240
- {...extractLayerProps(restProps, 'lc-link')}
241
248
  {...restProps}
242
249
  stroke={resolvedStroke}
243
250
  fill={resolvePerDatum(fillProp, d)}
@@ -247,13 +254,12 @@
247
254
  {/each}
248
255
  {:else}
249
256
  <Path
250
- pathData={motionPath.current}
257
+ pathData={getPathData}
251
258
  bind:pathRef
252
259
  {marker}
253
260
  {markerStart}
254
261
  {markerMid}
255
262
  {markerEnd}
256
- {...extractLayerProps(restProps, 'lc-link')}
257
263
  {...restProps}
258
264
  class={cls('lc-link', typeof classProp === 'string' ? classProp : undefined)}
259
265
  />
@@ -9,9 +9,12 @@
9
9
  import { createKey } from '../../utils/key.svelte.js';
10
10
  import { PathState, type PathProps } from './Path.shared.svelte.js';
11
11
 
12
- let { ...rest }: PathProps = $props();
12
+ let { pathData, ...rest }: PathProps = $props();
13
13
 
14
- const c = new PathState(() => rest as PathProps);
14
+ const c = new PathState(
15
+ () => pathData,
16
+ () => rest as PathProps
17
+ );
15
18
 
16
19
  function render(
17
20
  ctx: CanvasRenderingContext2D,
@@ -7,10 +7,15 @@ import type { ChartState } from '../../states/chart.svelte.js';
7
7
  import type { draw as _drawTransition } from 'svelte/transition';
8
8
  export type PathPropsWithoutHTML = {
9
9
  /**
10
- * Pass `<path d={...} />` explicitly instead of calculating
11
- * from data / context
10
+ * The `d` attribute of the rendered `<path>`.
11
+ *
12
+ * Accepts either a value (resolved at call site) or a function that
13
+ * returns the current value. Passing a function lets the parent avoid
14
+ * re-rendering its own template on every change to the path data —
15
+ * useful when a parent like `Link` / `Spline` / `Area` updates the
16
+ * path on every animation tick across hundreds of instances.
12
17
  */
13
- pathData?: string | undefined | null;
18
+ pathData?: string | undefined | null | (() => string | undefined | null);
14
19
  /**
15
20
  * Whether to animate the drawing of the path over time.
16
21
  * Pass either `true` or an object with transition options to
@@ -66,5 +71,13 @@ export declare class PathState {
66
71
  chartCtx: ChartState;
67
72
  get tweenedPathData(): any;
68
73
  drawKey: symbol;
69
- constructor(getProps: () => PathProps);
74
+ /**
75
+ * @param getPathData Hot-path getter — reads only `pathData`. Kept separate from
76
+ * `getProps` so the `<path d=...>` updater (and the canvas
77
+ * `tweenedPathData` consumer) does not subscribe to every
78
+ * Path prop on every tick.
79
+ * @param getProps Full-props getter — used for one-time / cold-path config
80
+ * (motion, draw).
81
+ */
82
+ constructor(getPathData: () => PathProps['pathData'], getProps?: () => PathProps);
70
83
  }
@@ -2,11 +2,20 @@ import { interpolatePath } from 'd3-interpolate-path';
2
2
  import { flattenPathData } from '../../utils/path.js';
3
3
  import { createMotion, extractTweenConfig, } from '../../utils/motion.svelte.js';
4
4
  import { getChartContext } from '../../contexts/chart.js';
5
+ /** Resolve `pathData` whether it was passed as a value or a getter function. */
6
+ function resolvePathData(v) {
7
+ return typeof v === 'function' ? v() : v;
8
+ }
5
9
  /**
6
10
  * Reactive state shared by every per-layer Path variant.
7
11
  */
8
12
  export class PathState {
9
- #getProps = () => ({});
13
+ // Hot-path getter: reads only `pathData` (or invokes the function-getter form).
14
+ // Kept separate from the full-props getter so that the `<path d=...>` template
15
+ // updater does not subscribe to every Path prop on every read — critical for
16
+ // mark-heavy scenes (force-simulation graphs with hundreds of links updating
17
+ // per tick) where pre-fix each tween read re-evaluated all 15+ props.
18
+ #getPathData;
10
19
  // Contexts
11
20
  chartCtx = getChartContext();
12
21
  // Path data tween source — the actual `d` attribute / canvas render input
@@ -16,8 +25,16 @@ export class PathState {
16
25
  }
17
26
  // Re-key trigger for draw transitions
18
27
  drawKey = $state(Symbol());
19
- constructor(getProps) {
20
- this.#getProps = getProps;
28
+ /**
29
+ * @param getPathData Hot-path getter — reads only `pathData`. Kept separate from
30
+ * `getProps` so the `<path d=...>` updater (and the canvas
31
+ * `tweenedPathData` consumer) does not subscribe to every
32
+ * Path prop on every tick.
33
+ * @param getProps Full-props getter — used for one-time / cold-path config
34
+ * (motion, draw).
35
+ */
36
+ constructor(getPathData, getProps = () => ({})) {
37
+ this.#getPathData = () => resolvePathData(getPathData());
21
38
  const initial = getProps();
22
39
  const extractedTween = extractTweenConfig(initial.motion);
23
40
  const tweenedOptions = extractedTween
@@ -32,18 +49,19 @@ export class PathState {
32
49
  // Fast initial render when not tweened
33
50
  return '';
34
51
  }
35
- else if (initial.pathData) {
36
- return flattenPathData(initial.pathData, Math.min(this.chartCtx.yScale(0) ?? this.chartCtx.yRange[0], this.chartCtx.yRange[0]));
52
+ const resolved = resolvePathData(getPathData());
53
+ if (resolved) {
54
+ return flattenPathData(resolved, Math.min(this.chartCtx.yScale(0) ?? this.chartCtx.yRange[0], this.chartCtx.yRange[0]));
37
55
  }
38
56
  return '';
39
57
  })();
40
- this.#tweenedState = createMotion(defaultPathData, () => getProps().pathData, tweenedOptions);
58
+ this.#tweenedState = createMotion(defaultPathData, this.#getPathData, tweenedOptions);
41
59
  // Re-trigger draw transition when path data changes
42
60
  $effect(() => {
43
61
  if (!getProps().draw)
44
62
  return;
45
- // Touch dependency
46
- void getProps().pathData;
63
+ // Touch dependency (resolves getter form too)
64
+ void this.#getPathData();
47
65
  this.drawKey = Symbol();
48
66
  });
49
67
  }
@@ -16,7 +16,7 @@
16
16
  const uid = $props.id();
17
17
 
18
18
  let {
19
- pathRef: pathRefProp = $bindable(),
19
+ pathRef = $bindable(),
20
20
  marker,
21
21
  markerStart: markerStartProp,
22
22
  markerMid: markerMidProp,
@@ -24,29 +24,35 @@
24
24
  startContent,
25
25
  endContent,
26
26
  draw,
27
+ motion,
28
+ // Extracted out of `rest` so the `<path>` element's `{...rest}`
29
+ // spread doesn't re-evaluate on every frame in mark-heavy scenes
30
+ // (force-simulation graphs with hundreds of links updating per tick).
31
+ // - `pathData`: changes every frame
32
+ // - `class`: parents typically pass `cls(...)` which produces a new
33
+ // string reference per parent render
34
+ // - styling props: explicit on the <path> element below, no need to
35
+ // leak them through the spread
36
+ pathData: _pathData,
37
+ class: classProp,
38
+ fill: fillProp,
39
+ fillOpacity: fillOpacityProp,
40
+ stroke: strokeProp,
41
+ strokeOpacity: strokeOpacityProp,
42
+ strokeWidth: strokeWidthProp,
43
+ opacity: opacityProp,
27
44
  ...rest
28
45
  }: PathProps = $props();
29
46
 
47
+ // Pass `pathData` as its own getter so the hot-path tween read only subscribes
48
+ // to `pathData` (which changes per tick on force sims) and not to every other
49
+ // Path prop. Pre-fix the per-tick `<path d=...>` updater re-read all 15+ props
50
+ // through `getProps()` on each force-sim tick × hundreds of paths.
30
51
  const c = new PathState(
31
- () =>
32
- ({
33
- marker,
34
- markerStart: markerStartProp,
35
- markerMid: markerMidProp,
36
- markerEnd: markerEndProp,
37
- startContent,
38
- endContent,
39
- draw,
40
- ...rest,
41
- }) as PathProps
52
+ () => _pathData,
53
+ () => ({ draw, motion }) as PathProps
42
54
  );
43
55
 
44
- let pathRef = $state<SVGPathElement>();
45
-
46
- $effect.pre(() => {
47
- pathRefProp = pathRef;
48
- });
49
-
50
56
  const markerStart = $derived(markerStartProp ?? marker);
51
57
  const markerMid = $derived(markerMidProp ?? marker);
52
58
  const markerEnd = $derived(markerEndProp ?? marker);
@@ -56,7 +62,6 @@
56
62
  const markerEndId = $derived(markerEnd ? createId('marker-end', uid) : '');
57
63
 
58
64
  const drawTransition = $derived(draw ? _drawTransition : () => ({}));
59
-
60
65
  let startPoint = $state<DOMPoint | undefined>();
61
66
 
62
67
  const endPointDuration = $derived.by(() => {
@@ -70,60 +75,70 @@
70
75
  return 800;
71
76
  });
72
77
 
73
- const endPoint = createControlledMotion<DOMPoint | undefined>(
74
- undefined,
75
- draw
76
- ? {
77
- type: 'tween',
78
- duration: () => endPointDuration,
79
- easing: typeof draw === 'object' && draw.easing ? draw.easing : cubicInOut,
80
- interpolate() {
81
- return (t: number) => {
82
- const totalLength = pathRef?.getTotalLength() ?? 0;
83
- const point = pathRef?.getPointAtLength(totalLength * t);
84
- return point;
85
- };
86
- },
87
- }
88
- : { type: 'none' }
89
- );
90
-
91
- $effect(() => {
92
- if (!startContent && !endContent) return;
93
- // Track path data changes
94
- void c.tweenedPathData;
95
- if (!pathRef) return;
96
-
97
- tick().then(() => {
78
+ // Only allocate the controlled motion container when `draw` is configured;
79
+ // otherwise the per-Path `MotionNone` × hundreds of paths was a measurable
80
+ // mount-time cost in mark-heavy scenes.
81
+ const endPoint = draw
82
+ ? createControlledMotion<DOMPoint | undefined>(undefined, {
83
+ type: 'tween',
84
+ duration: () => endPointDuration,
85
+ easing: typeof draw === 'object' && draw.easing ? draw.easing : cubicInOut,
86
+ interpolate() {
87
+ return (t: number) => {
88
+ const totalLength = pathRef?.getTotalLength() ?? 0;
89
+ const point = pathRef?.getPointAtLength(totalLength * t);
90
+ return point;
91
+ };
92
+ },
93
+ })
94
+ : null;
95
+
96
+ // Only set up path-end tracking when startContent/endContent require it.
97
+ if (startContent || endContent) {
98
+ $effect(() => {
99
+ // Track path data changes
100
+ void c.tweenedPathData;
98
101
  if (!pathRef) return;
99
- const totalLength = pathRef.getTotalLength();
100
- if (!totalLength) return;
101
- startPoint = pathRef.getPointAtLength(0);
102
- endPoint.target = pathRef.getPointAtLength(totalLength);
102
+
103
+ tick().then(() => {
104
+ if (!pathRef) return;
105
+ const totalLength = pathRef.getTotalLength();
106
+ if (!totalLength) return;
107
+ startPoint = pathRef.getPointAtLength(0);
108
+ if (endPoint) {
109
+ endPoint.target = pathRef.getPointAtLength(totalLength);
110
+ }
111
+ });
103
112
  });
104
- });
113
+ }
105
114
  </script>
106
115
 
107
116
  {#key c.drawKey}
108
117
  <path
109
118
  {...rest as any}
110
119
  d={c.tweenedPathData}
111
- fill={rest.fill}
112
- fill-opacity={rest.fillOpacity}
113
- stroke={rest.stroke}
114
- stroke-opacity={rest.strokeOpacity}
115
- stroke-width={rest.strokeWidth}
116
- opacity={rest.opacity}
117
- class={cls('lc-path', rest.class as string | undefined)}
120
+ fill={fillProp}
121
+ fill-opacity={fillOpacityProp}
122
+ stroke={strokeProp}
123
+ stroke-opacity={strokeOpacityProp}
124
+ stroke-width={strokeWidthProp}
125
+ opacity={opacityProp}
126
+ class={cls('lc-path', classProp as string | undefined)}
118
127
  marker-start={markerStartId ? `url(#${markerStartId})` : undefined}
119
128
  marker-mid={markerMidId ? `url(#${markerMidId})` : undefined}
120
129
  marker-end={markerEndId ? `url(#${markerEndId})` : undefined}
121
130
  in:drawTransition|global={typeof draw === 'object' ? draw : undefined}
122
131
  bind:this={pathRef}
123
132
  />
124
- <MarkerWrapper id={markerStartId} marker={markerStart} />
125
- <MarkerWrapper id={markerMidId} marker={markerMid} />
126
- <MarkerWrapper id={markerEndId} marker={markerEnd} />
133
+ {#if markerStart}
134
+ <MarkerWrapper id={markerStartId} marker={markerStart} />
135
+ {/if}
136
+ {#if markerMid}
137
+ <MarkerWrapper id={markerMidId} marker={markerMid} />
138
+ {/if}
139
+ {#if markerEnd}
140
+ <MarkerWrapper id={markerEndId} marker={markerEnd} />
141
+ {/if}
127
142
 
128
143
  {#if startContent && startPoint}
129
144
  <Group x={startPoint.x} y={startPoint.y} class="lc-path-g-start">
@@ -137,7 +152,7 @@
137
152
  </Group>
138
153
  {/if}
139
154
 
140
- {#if endContent && endPoint.current}
155
+ {#if endContent && endPoint?.current}
141
156
  <Group x={endPoint.current.x} y={endPoint.current.y} class="lc-path-g-end">
142
157
  {@render endContent({
143
158
  point: endPoint.current,
@@ -22,11 +22,14 @@
22
22
  height = size,
23
23
  lines: linesProp,
24
24
  circles: circlesProp,
25
+ rects: rectsProp,
25
26
  background,
26
27
  children,
27
28
  }: PatternProps = $props();
28
29
 
29
- const shapes = $derived(buildPatternShapes(linesProp, circlesProp, size, width, height));
30
+ const shapes = $derived(
31
+ buildPatternShapes(linesProp, circlesProp, size, width, height, rectsProp)
32
+ );
30
33
 
31
34
  let canvasPattern = $state<CanvasPattern | null>(null);
32
35
 
@@ -21,6 +21,22 @@ export type PatternCircleDef = {
21
21
  /** The opacity of the circle @default 1 */
22
22
  opacity?: number;
23
23
  };
24
+ export type PatternRectDef = {
25
+ /**
26
+ * Inset from each edge of the pattern tile, in pixels. Useful for cell
27
+ * grids — set to half the desired gap between cells.
28
+ * @default 0
29
+ */
30
+ inset?: number;
31
+ /** Horizontal corner radius, or `"100%"` for a full ellipse / circle. */
32
+ rx?: number | string;
33
+ /** Vertical corner radius. Defaults to `rx` if not provided. */
34
+ ry?: number | string;
35
+ /** Fill color @default 'var(--color-surface-content)' */
36
+ color?: string;
37
+ /** Opacity @default 1 */
38
+ opacity?: number;
39
+ };
24
40
  export type PatternPropsWithoutHTML = {
25
41
  /** The id of the pattern */
26
42
  id?: string;
@@ -34,6 +50,8 @@ export type PatternPropsWithoutHTML = {
34
50
  lines?: boolean | PatternLineDef | PatternLineDef[];
35
51
  /** The number of circles to render */
36
52
  circles?: boolean | PatternCircleDef | PatternCircleDef[];
53
+ /** Rect(s) to render in each pattern tile */
54
+ rects?: boolean | PatternRectDef | PatternRectDef[];
37
55
  /** The background color of the pattern */
38
56
  background?: string;
39
57
  /** Render as a child of the pattern. Note: only supported on the `<Svg>` layer. */
@@ -59,9 +77,20 @@ export type LineShape = {
59
77
  strokeWidth: string | number;
60
78
  opacity: number;
61
79
  };
62
- export type PatternShape = CircleShape | LineShape;
80
+ export type RectShape = {
81
+ type: 'rect';
82
+ x: number;
83
+ y: number;
84
+ width: number;
85
+ height: number;
86
+ rx?: number | string;
87
+ ry?: number | string;
88
+ fill: string;
89
+ opacity: number;
90
+ };
91
+ export type PatternShape = CircleShape | LineShape | RectShape;
63
92
  /**
64
93
  * Build the SVG/canvas shape descriptors for a pattern's lines/circles.
65
94
  * Pure function — no reactivity.
66
95
  */
67
- export declare function buildPatternShapes(linesProp: PatternPropsWithoutHTML['lines'], circlesProp: PatternPropsWithoutHTML['circles'], size: number, width: number, height: number): PatternShape[];
96
+ export declare function buildPatternShapes(linesProp: PatternPropsWithoutHTML['lines'], circlesProp: PatternPropsWithoutHTML['circles'], size: number, width: number, height: number, rectsProp?: PatternPropsWithoutHTML['rects']): PatternShape[];
@@ -2,7 +2,7 @@
2
2
  * Build the SVG/canvas shape descriptors for a pattern's lines/circles.
3
3
  * Pure function — no reactivity.
4
4
  */
5
- export function buildPatternShapes(linesProp, circlesProp, size, width, height) {
5
+ export function buildPatternShapes(linesProp, circlesProp, size, width, height, rectsProp) {
6
6
  const shapes = [];
7
7
  if (linesProp) {
8
8
  const lineDefs = Array.isArray(linesProp) ? linesProp : linesProp === true ? [{}] : [linesProp];
@@ -67,5 +67,24 @@ export function buildPatternShapes(linesProp, circlesProp, size, width, height)
67
67
  }
68
68
  }
69
69
  }
70
+ if (rectsProp) {
71
+ const rectDefs = Array.isArray(rectsProp) ? rectsProp : rectsProp === true ? [{}] : [rectsProp];
72
+ for (const rect of rectDefs) {
73
+ const inset = rect.inset ?? 0;
74
+ const fill = rect.color ?? 'var(--color-surface-content, currentColor)';
75
+ const opacity = rect.opacity ?? 1;
76
+ shapes.push({
77
+ type: 'rect',
78
+ x: inset,
79
+ y: inset,
80
+ width: Math.max(0, width - 2 * inset),
81
+ height: Math.max(0, height - 2 * inset),
82
+ rx: rect.rx,
83
+ ry: rect.ry,
84
+ fill,
85
+ opacity,
86
+ });
87
+ }
88
+ }
70
89
  return shapes;
71
90
  }
@@ -19,13 +19,16 @@
19
19
  height = size,
20
20
  lines: linesProp,
21
21
  circles: circlesProp,
22
+ rects: rectsProp,
22
23
  background,
23
24
  patternContent,
24
25
  children,
25
26
  ...rest
26
27
  }: PatternProps = $props();
27
28
 
28
- const shapes = $derived(buildPatternShapes(linesProp, circlesProp, size, width, height));
29
+ const shapes = $derived(
30
+ buildPatternShapes(linesProp, circlesProp, size, width, height, rectsProp)
31
+ );
29
32
  </script>
30
33
 
31
34
  <defs>
@@ -62,6 +65,19 @@
62
65
  opacity={circle.opacity}
63
66
  />
64
67
  {/each}
68
+
69
+ {#each shapes.filter((s) => s.type === 'rect') as rect}
70
+ <rect
71
+ x={rect.x}
72
+ y={rect.y}
73
+ width={rect.width}
74
+ height={rect.height}
75
+ rx={rect.rx as number | string | undefined}
76
+ ry={rect.ry as number | string | undefined}
77
+ fill={rect.fill}
78
+ opacity={rect.opacity}
79
+ />
80
+ {/each}
65
81
  {/if}
66
82
  </pattern>
67
83
  </defs>