stickyflow 0.9.3 → 0.11.0

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 (66) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/dist/binding/index.cjs +9 -9
  4. package/dist/binding/index.d.cts +4 -4
  5. package/dist/binding/index.d.ts +4 -4
  6. package/dist/binding/index.js +3 -3
  7. package/dist/{chunk-EURX7QT3.cjs → chunk-5G3BEXQU.cjs} +1300 -891
  8. package/dist/chunk-5G3BEXQU.cjs.map +1 -0
  9. package/dist/{chunk-AAP24GBG.js → chunk-BFQWCOEC.js} +3 -3
  10. package/dist/chunk-BFQWCOEC.js.map +1 -0
  11. package/dist/{chunk-3EOG34QN.cjs → chunk-F24JURN2.cjs} +561 -258
  12. package/dist/chunk-F24JURN2.cjs.map +1 -0
  13. package/dist/{chunk-2GKXPZRL.cjs → chunk-FXGXUWSI.cjs} +23 -23
  14. package/dist/chunk-FXGXUWSI.cjs.map +1 -0
  15. package/dist/{chunk-Q6FBISNP.js → chunk-G6IW2IX3.js} +601 -350
  16. package/dist/chunk-G6IW2IX3.js.map +1 -0
  17. package/dist/{chunk-G2N3KJYJ.js → chunk-Q6QLU6YQ.js} +662 -253
  18. package/dist/chunk-Q6QLU6YQ.js.map +1 -0
  19. package/dist/{chunk-C3LVKPWQ.js → chunk-RQYZNW2I.js} +532 -229
  20. package/dist/chunk-RQYZNW2I.js.map +1 -0
  21. package/dist/{chunk-XNNNIOJU.js → chunk-UB2ANTWF.js} +5 -5
  22. package/dist/{chunk-XNNNIOJU.js.map → chunk-UB2ANTWF.js.map} +1 -1
  23. package/dist/{chunk-C25NWWIH.cjs → chunk-UHKFJBY5.cjs} +605 -350
  24. package/dist/chunk-UHKFJBY5.cjs.map +1 -0
  25. package/dist/{chunk-MR32IJQO.cjs → chunk-WZA3DJJH.cjs} +15 -15
  26. package/dist/chunk-WZA3DJJH.cjs.map +1 -0
  27. package/dist/index.cjs +36 -36
  28. package/dist/index.d.cts +35 -30
  29. package/dist/index.d.ts +35 -30
  30. package/dist/index.js +5 -5
  31. package/dist/interaction/index.cjs +319 -352
  32. package/dist/interaction/index.cjs.map +1 -1
  33. package/dist/interaction/index.d.cts +3 -3
  34. package/dist/interaction/index.d.ts +3 -3
  35. package/dist/interaction/index.js +94 -127
  36. package/dist/interaction/index.js.map +1 -1
  37. package/dist/{render-DU4lYbRl.d.ts → render-BDO4S5uF.d.cts} +16 -0
  38. package/dist/{render-DU4lYbRl.d.cts → render-BDO4S5uF.d.ts} +16 -0
  39. package/dist/{runtime-DG8X3Fwf.d.cts → runtime-C-C5v9po.d.cts} +2 -2
  40. package/dist/{runtime-kqD1RS17.d.ts → runtime-lCAfU-1Q.d.ts} +2 -2
  41. package/dist/{runtimeTypes-vRaHOIHi.d.ts → runtimeTypes-B7p6b1Bi.d.cts} +2 -2
  42. package/dist/{runtimeTypes-CvjlBZds.d.cts → runtimeTypes-BDA_CfWh.d.ts} +2 -2
  43. package/dist/stickyflow-diagram.global.js +197 -44
  44. package/dist/stickyflow-diagram.global.js.map +1 -1
  45. package/dist/{types-B5jrEEcF.d.cts → types-CZJzycKw.d.cts} +1 -1
  46. package/dist/{types-B7IkZ0w4.d.ts → types-Dp8JVZLB.d.ts} +1 -1
  47. package/dist/viewer/index.cjs +9 -9
  48. package/dist/viewer/index.d.cts +4 -4
  49. package/dist/viewer/index.d.ts +4 -4
  50. package/dist/viewer/index.js +4 -4
  51. package/dist/web-component/index.cjs +6 -6
  52. package/dist/web-component/index.cjs.map +1 -1
  53. package/dist/web-component/index.d.cts +2 -2
  54. package/dist/web-component/index.d.ts +2 -2
  55. package/dist/web-component/index.js +4 -4
  56. package/dist/web-component/index.js.map +1 -1
  57. package/package.json +1 -1
  58. package/dist/chunk-2GKXPZRL.cjs.map +0 -1
  59. package/dist/chunk-3EOG34QN.cjs.map +0 -1
  60. package/dist/chunk-AAP24GBG.js.map +0 -1
  61. package/dist/chunk-C25NWWIH.cjs.map +0 -1
  62. package/dist/chunk-C3LVKPWQ.js.map +0 -1
  63. package/dist/chunk-EURX7QT3.cjs.map +0 -1
  64. package/dist/chunk-G2N3KJYJ.js.map +0 -1
  65. package/dist/chunk-MR32IJQO.cjs.map +0 -1
  66. package/dist/chunk-Q6FBISNP.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { componentRegistryVersion, isDimensionObject, isIsometricObject, sflToCanvasState, registerComponentDescriptor, isBuiltInComponentType, getComponentDescriptor, tag, EMPTY_BOUNDS, DOM_CONTRACT_VERSION, edgePointToward, elementCenter, VISIBLE_ISOMETRIC_FACES, isometricFaceCenter, finite, freeformPathData, rotatedBounds, DEFAULT_FONT, isGenericComponent, cleanFootprint, pointAtNormalizedPort, boxBounds, cornersRoundedRectPath, calculateLinkRoute, labelGapDash, getLineMidpoint, personLabelArea, wrapText, heuristicRichMeasure, shadeFill, extrudeFootprint, isometricFaces, roundedPolygonPath, stackSequence, stackRank, topRoundedRectPath, rightRoundedRectPath, leftRoundedRectPath, bottomRoundedRectPath, isHorizontalBar, paint, CHART_TITLE_PROP, chartText, chartTicks, mapChartValue, roundedOrthogonalPathData, localShapePath, pillCornerRadius, isometricBounds, AXIS_SIN, deriveFaceFills, labelTilt, tiltedLabelRoom, resolveChartShadow, CHART_COLORS, angleForAxisValue, horizontalRows, rowTop, categoryProp, isometricTopFaceCenter, ISOMETRIC_SIZE_ATTR, CORNER_RADIUS_ATTR, ISOMETRIC_LABEL_OFFSET_ATTR, PATH_POINTS_ATTR, PATH_TENSION_ATTR, PATH_CLOSED_ATTR, SYMBOL_BOUNDS_ATTR, SYMBOL_POINTS_ATTR, SYMBOL_TENSION_ATTR, SQUARE_CORNER_ATTR, resolvePieRadii, radarCategoryCount, radarRadius, createPieGeometry, DEFAULT_HEX_RADIUS, DEFAULT_HEATMAP_ZONES, axisBands, DEFAULT_ZONE_FILL, representativeColor, computeBoxplotStats, barCategoryCount, ruleAtProp, ruleTextProp, labelBelowFrame, seriesLabelProp, axisLabelProp, seriesColorProp, seriesValueProp, radarSeriesPath, radarRadiusForValue, createPieSlicePath, clipSegmentToRect, formatFieldValue, renderPieLabels, interpolateHexColorStops, contrastTextColor, sameHueTint, isLightColor, barShapePath, linePathData, colorWithAlpha, estimateTextWidth, segmentedBarPath, roundedBarPath, radarPointAt, gapInsetAngle, pieUsesInnerArc, maxPieCornerRadius, pieApexRadius, maxApexCornerRadius, pieArcPoint, sameHueContrastColor, zoneColorAtValue, ellipsizeToWidth, barPath, labelAboveFrame, seriesValueColorProp, stackedSegment, errorBarRange, createVerticalErrorWhiskerNode, formatBarLabel, barLabelSize, CATEGORY_LABEL_ROW, BAR_LABEL, pillPath, resolveRollingNumberStyle, formatRollingNumber } from './chunk-Q6FBISNP.js';
1
+ import { componentRegistryVersion, isDimensionObject, isIsometricObject, sflToCanvasState, registerComponentDescriptor, isBuiltInComponentType, getComponentDescriptor, tag, EMPTY_BOUNDS, DOM_CONTRACT_VERSION, edgePointToward, elementCenter, VISIBLE_ISOMETRIC_FACES, isometricFaceCenter, finite, freeformPathData, rotatedBounds, DEFAULT_FONT, isGenericComponent, cleanFootprint, pointAtNormalizedPort, boxBounds, cornersRoundedRectPath, calculateLinkRoute, labelGapDash, getLineMidpoint, personLabelArea, wrapText, heuristicRichMeasure, shadeFill, extrudeFootprint, isometricFaces, roundedPolygonPath, stackSequence, stackRank, topRoundedRectPath, rightRoundedRectPath, leftRoundedRectPath, bottomRoundedRectPath, paint, CHART_TITLE_PROP, chartText, isHorizontalBar, chartTicks, mapChartValue, roundedOrthogonalPathData, localShapePath, pillCornerRadius, isometricBounds, AXIS_SIN, deriveFaceFills, resolveChartShadow, CHART_COLORS, angleForAxisValue, horizontalRows, rowTop, categoryProp, isometricTopFaceCenter, ISOMETRIC_SIZE_ATTR, CORNER_RADIUS_ATTR, ISOMETRIC_LABEL_OFFSET_ATTR, PATH_POINTS_ATTR, PATH_TENSION_ATTR, PATH_CLOSED_ATTR, SYMBOL_BOUNDS_ATTR, SYMBOL_POINTS_ATTR, SYMBOL_TENSION_ATTR, SQUARE_CORNER_ATTR, labelTilt, tiltedLabelRoom, resolvePieRadii, radarCategoryCount, radarRadius, createPieGeometry, DEFAULT_HEX_RADIUS, DEFAULT_HEATMAP_ZONES, axisBands, DEFAULT_ZONE_FILL, representativeColor, computeBoxplotStats, barCategoryCount, ruleAtProp, ruleTextProp, labelBelowFrame, seriesLabelProp, axisLabelProp, seriesColorProp, seriesValueProp, radarSeriesPath, radarRadiusForValue, createPieSlicePath, renderPieLabels, interpolateHexColorStops, contrastTextColor, formatFieldValue, sameHueTint, isLightColor, barShapePath, linePathData, colorWithAlpha, estimateTextWidth, segmentedBarPath, roundedBarPath, radarPointAt, gapInsetAngle, pieUsesInnerArc, maxPieCornerRadius, pieApexRadius, maxApexCornerRadius, pieArcPoint, sameHueContrastColor, clipSegmentToRect, zoneColorAtValue, ellipsizeToWidth, barPath, labelAboveFrame, seriesValueColorProp, stackedSegment, errorBarRange, createVerticalErrorWhiskerNode, formatBarLabel, barLabelSize, CATEGORY_LABEL_ROW, BAR_LABEL, pillPath, resolveRollingNumberStyle, formatRollingNumber, parseHexRgb } from './chunk-G6IW2IX3.js';
2
2
 
3
3
  // src/renderInput/styleDecoding/colorToken.ts
4
4
  var COLOR_TOKEN_PATTERN = String.raw`rgba?\([^)]*\)|#[0-9a-fA-F]+|[a-zA-Z][\w-]*`;
@@ -117,8 +117,8 @@ function dashArray(value) {
117
117
  if (!Array.isArray(parsed)) {
118
118
  return void 0;
119
119
  }
120
- const numbers = parsed.filter((entry) => typeof entry === "number" && Number.isFinite(entry));
121
- return numbers.length === parsed.length ? numbers : void 0;
120
+ const numbers2 = parsed.filter((entry) => typeof entry === "number" && Number.isFinite(entry));
121
+ return numbers2.length === parsed.length ? numbers2 : void 0;
122
122
  }
123
123
  function solidColor(value) {
124
124
  const decoded = decodeColor(value);
@@ -630,7 +630,7 @@ function readProperty(descriptor, raw) {
630
630
  case "boolean":
631
631
  return raw === void 0 ? descriptor.default : raw === "true";
632
632
  case "color":
633
- return cssColor(raw) ?? descriptor.default;
633
+ return descriptor.optional && raw === "none" ? "none" : cssColor(raw) ?? descriptor.default;
634
634
  case "enum":
635
635
  return raw !== void 0 && descriptor.options.includes(raw) ? raw : descriptor.default;
636
636
  case "string":
@@ -1010,12 +1010,29 @@ function pathObject(e) {
1010
1010
  }
1011
1011
 
1012
1012
  // src/renderInput/fromSfl/canvasObjects/shape.ts
1013
+ var IMAGE_FITS = ["cover", "contain", "fill"];
1013
1014
  function shapeTypeOf(value) {
1014
1015
  return value || "rectangle";
1015
1016
  }
1017
+ function numbers(value, count) {
1018
+ const parts = value?.split(",").map(Number);
1019
+ return parts?.length === count && parts.every(Number.isFinite) ? parts : void 0;
1020
+ }
1021
+ function imageOf(p) {
1022
+ const size = numbers(p.imageSize, 2);
1023
+ const crop = numbers(p.crop, 4);
1024
+ return {
1025
+ src: p.src,
1026
+ fit: oneOf(p.fit, IMAGE_FITS, "cover"),
1027
+ pixels: size && size[0] > 0 && size[1] > 0 ? [size[0], size[1]] : void 0,
1028
+ crop: crop && crop[2] > 0 && crop[3] > 0 ? { x: crop[0], y: crop[1], width: crop[2], height: crop[3] } : void 0
1029
+ };
1030
+ }
1016
1031
  function shapeObject(e, shaders) {
1017
1032
  const p = e.props;
1018
1033
  const isText = e.kind === "text";
1034
+ const isImage = e.kind === "image";
1035
+ const unpainted = isText || isImage;
1019
1036
  return {
1020
1037
  id: e.id,
1021
1038
  type: "shape",
@@ -1029,8 +1046,8 @@ function shapeObject(e, shaders) {
1029
1046
  w: e.width,
1030
1047
  h: e.height,
1031
1048
  rotation: e.rotation ?? 0,
1032
- fillColor: decodeColor(p.fill ?? (isText ? "none" : void 0)),
1033
- strokeColor: strokePaint(p.stroke ?? (isText ? "none" : void 0)),
1049
+ fillColor: decodeColor(p.fill ?? (unpainted ? "none" : void 0)),
1050
+ strokeColor: strokePaint(p.stroke ?? (unpainted ? "none" : void 0)),
1034
1051
  isText: isText || void 0,
1035
1052
  strokeWidth: numOf(p.strokeWidth),
1036
1053
  textColor: p.textColor,
@@ -1051,7 +1068,8 @@ function shapeObject(e, shaders) {
1051
1068
  parentFrameId: e.parentFrameId,
1052
1069
  order: e.order,
1053
1070
  sourceIndex: e.sourceIndex,
1054
- shader: p.shader ? shaders.get(p.shader) : void 0
1071
+ shader: p.shader ? shaders.get(p.shader) : void 0,
1072
+ image: isImage && p.src ? imageOf(p) : void 0
1055
1073
  };
1056
1074
  }
1057
1075
 
@@ -1482,7 +1500,7 @@ function fade(component, style) {
1482
1500
  const shown = component.disabled ? { ...style, opacity: 0.4 } : style;
1483
1501
  return animate > 0 ? { ...shown, transition: animate } : shown;
1484
1502
  }
1485
- function hitArea(component, box) {
1503
+ function hitArea(component, box, takesClicks = true) {
1486
1504
  return {
1487
1505
  id: `widget-${component.id}`,
1488
1506
  type: "rect",
@@ -1492,7 +1510,7 @@ function hitArea(component, box) {
1492
1510
  y: box.y,
1493
1511
  width: box.width,
1494
1512
  height: box.height,
1495
- style: { fill: { kind: "none" }, stroke: { kind: "none" }, pointerEvents: "all" }
1513
+ style: { fill: { kind: "none" }, stroke: { kind: "none" }, pointerEvents: takesClicks ? "all" : "none" }
1496
1514
  };
1497
1515
  }
1498
1516
  function labelText(component, part, area, label, order) {
@@ -1613,6 +1631,268 @@ function buildCheckbox(component, index) {
1613
1631
  ];
1614
1632
  }
1615
1633
 
1634
+ // src/renderScene/artifacts/component/hand.ts
1635
+ var MIN_TIP = 0.25;
1636
+ function withAlpha(color, alpha) {
1637
+ const rgb = parseHexRgb(color);
1638
+ return rgb && alpha < 1 ? `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${Number(alpha.toFixed(3))})` : color;
1639
+ }
1640
+ var num = (value) => String(Number(value.toFixed(3)));
1641
+ var isOff = (component, key) => textProperty(component, key) === "none";
1642
+ var colorOr = (component, key, fallback) => isOff(component, key) ? fallback : textProperty(component, key) || fallback;
1643
+ function handOf(component, box) {
1644
+ const radius = Math.min(box.width, box.height) / 2;
1645
+ const root = Math.max(0.5, numberProperty2(component, "width"));
1646
+ const taper = Math.min(1, Math.max(0, numberProperty2(component, "taper")));
1647
+ return {
1648
+ cx: box.x + box.width / 2,
1649
+ cy: box.y + box.height / 2,
1650
+ radius,
1651
+ length: Math.max(1, numberProperty2(component, "length") * radius),
1652
+ tail: Math.max(0, numberProperty2(component, "tail") * radius),
1653
+ gap: Math.max(0, numberProperty2(component, "gap") * radius),
1654
+ rootHalf: root / 2,
1655
+ tipHalf: Math.max(MIN_TIP, root * taper / 2)
1656
+ };
1657
+ }
1658
+ function outline({ cx, cy, length, tail, gap, rootHalf, tipHalf }) {
1659
+ const root = gap > 0 ? cy - gap : cy;
1660
+ const back = gap > 0 ? 0 : tail;
1661
+ const tipLeft = `L${num(cx - tipHalf)} ${num(cy - length)}`;
1662
+ const tip = `A${num(tipHalf)} ${num(tipHalf)} 0 0 1 ${num(cx + tipHalf)} ${num(cy - length)}`;
1663
+ const round2 = `A${num(rootHalf)} ${num(rootHalf)} 0 0 1 ${num(cx - rootHalf)} ${num(root + back)}`;
1664
+ if (gap > 0) {
1665
+ return `M${num(cx - rootHalf)} ${num(root)}${tipLeft}${tip}L${num(cx + rootHalf)} ${num(root)}${round2}Z`;
1666
+ }
1667
+ return [
1668
+ `M${num(cx - rootHalf)} ${num(root + back)}`,
1669
+ `L${num(cx - rootHalf)} ${num(root)}`,
1670
+ tipLeft,
1671
+ tip,
1672
+ `L${num(cx + rootHalf)} ${num(root)}`,
1673
+ `L${num(cx + rootHalf)} ${num(root + back)}`,
1674
+ back > 0 ? round2 : `L${num(cx - rootHalf)} ${num(root)}`,
1675
+ "Z"
1676
+ ].join("");
1677
+ }
1678
+ function shaped(hand, layer) {
1679
+ const rootHalf = hand.rootHalf * layer.scale;
1680
+ const tipHalf = layer.taper === void 0 ? hand.tipHalf * layer.scale : rootHalf * layer.taper;
1681
+ return { ...hand, length: hand.length * (layer.length ?? 1), rootHalf, tipHalf: Math.max(MIN_TIP, tipHalf) };
1682
+ }
1683
+ function stopsPaint(hand, stops, fadeOut, clear) {
1684
+ const tail = hand.gap > 0 ? 0 : hand.tail;
1685
+ const offset = (along2) => (tail + along2 * hand.length) / (hand.length + tail) * 100;
1686
+ const [, firstColor, firstAlpha] = stops[0];
1687
+ return {
1688
+ kind: "linear-gradient",
1689
+ angle: -90,
1690
+ stops: [
1691
+ ...tail > 0 ? [{ offset: 0, color: withAlpha(firstColor, firstAlpha * (clear ? 0 : 1 - fadeOut)) }] : [],
1692
+ ...stops.map(([along2, color, alpha], position) => ({
1693
+ offset: offset(along2),
1694
+ color: withAlpha(color, clear && position === 0 ? 0 : alpha)
1695
+ }))
1696
+ ]
1697
+ };
1698
+ }
1699
+ function plainPaint(hand, layer, fadeOut, tipFade) {
1700
+ if (fadeOut <= 0 && tipFade <= 0 && !layer.clear) {
1701
+ return solid(layer.color);
1702
+ }
1703
+ return {
1704
+ kind: "linear-gradient",
1705
+ angle: -90,
1706
+ stops: [
1707
+ { offset: 0, color: withAlpha(layer.root, layer.clear ? 0 : 1 - fadeOut) },
1708
+ // A tail is lit at the pivot and dissolves at its end.
1709
+ ...hand.gap === 0 && hand.tail > 0 ? [{ offset: hand.tail / (hand.length + hand.tail) * 100, color: layer.root }] : [],
1710
+ { offset: 100, color: withAlpha(layer.color, 1 - tipFade) }
1711
+ ]
1712
+ };
1713
+ }
1714
+ function handLayer(component, box, base, angle, layer, order) {
1715
+ const hand = shaped(base, layer);
1716
+ const fadeOut = Math.min(1, Math.max(0, numberProperty2(component, "fade")));
1717
+ const tipFade = Math.min(1, Math.max(0, layer.tipFade ?? numberProperty2(component, "tipFade")));
1718
+ const glow = numberProperty2(component, "glow");
1719
+ const soft = layer.blur > 0;
1720
+ const reach = Math.max(hand.rootHalf, hand.tipHalf);
1721
+ const near = hand.gap > 0 ? -hand.gap : hand.tail;
1722
+ const extent = { x: hand.cx - reach, y: hand.cy - hand.length, width: reach * 2, height: hand.length + near };
1723
+ return {
1724
+ id: `widget-${component.id}-${layer.part}`,
1725
+ type: "path",
1726
+ order: box.order + order,
1727
+ bounds: rotatedBounds(extent, 0, 0),
1728
+ d: outline(hand),
1729
+ rotation: angle,
1730
+ rotationCenter: { x: hand.cx, y: hand.cy },
1731
+ style: fade(component, {
1732
+ // The gradient runs from the tail to the tip, and turns with the hand.
1733
+ fill: layer.stops ? stopsPaint(hand, layer.stops, fadeOut, Boolean(layer.clear)) : plainPaint(hand, layer, fadeOut, tipFade),
1734
+ stroke: { kind: "none" },
1735
+ ...soft ? { blur: layer.blur, opacity: layer.opacity } : {},
1736
+ ...!soft && glow > 0 && !isOff(component, "glowColor") ? {
1737
+ shadow: {
1738
+ color: colorOr(component, "glowColor", layer.color),
1739
+ opacity: 1,
1740
+ blur: glow,
1741
+ dx: 0,
1742
+ dy: 0
1743
+ }
1744
+ } : {}
1745
+ })
1746
+ };
1747
+ }
1748
+ function trailLayers(color, root, light, soft, clear, lit) {
1749
+ const trail = [
1750
+ {
1751
+ part: "hand-light",
1752
+ scale: 4.5,
1753
+ length: 0.55,
1754
+ taper: 0.15,
1755
+ blur: soft * 3,
1756
+ opacity: 1,
1757
+ color: light,
1758
+ root: light,
1759
+ stops: [
1760
+ [0, light, 0.95],
1761
+ [0.35, light, 0.45],
1762
+ [1, light, 0]
1763
+ ]
1764
+ },
1765
+ {
1766
+ part: "hand-body",
1767
+ scale: 1.6,
1768
+ taper: 0.25,
1769
+ blur: soft,
1770
+ opacity: 1,
1771
+ color,
1772
+ root,
1773
+ clear,
1774
+ stops: [
1775
+ [0, root, 1],
1776
+ [0.18, color, 1],
1777
+ [0.6, color, 0.55],
1778
+ [1, color, 0]
1779
+ ]
1780
+ },
1781
+ {
1782
+ part: "hand",
1783
+ scale: 0.55,
1784
+ length: 0.8,
1785
+ taper: 0.3,
1786
+ blur: soft * 0.3,
1787
+ opacity: 1,
1788
+ color: root,
1789
+ root,
1790
+ clear,
1791
+ stops: [
1792
+ [0, root, 1],
1793
+ [0.45, root, 0.6],
1794
+ [1, root, 0]
1795
+ ]
1796
+ }
1797
+ ];
1798
+ return lit ? trail : trail.slice(1);
1799
+ }
1800
+ function handLayers(component, box, hand, angle) {
1801
+ const color = textProperty(component, "color");
1802
+ const root = colorOr(component, "root", color);
1803
+ const light = colorOr(component, "glowColor", color);
1804
+ const lit = !isOff(component, "glowColor");
1805
+ const clear = isOff(component, "root");
1806
+ const soft = numberProperty2(component, "soft");
1807
+ const bloom = numberProperty2(component, "bloom");
1808
+ if (soft > 0) {
1809
+ return trailLayers(color, root, light, soft, clear, lit).map(
1810
+ (layer, position) => handLayer(component, box, hand, angle, layer, 0.26 + position * 0.02)
1811
+ );
1812
+ }
1813
+ return [
1814
+ // A blurred light under the hand, bright at the pivot and dark at the tip.
1815
+ ...bloom > 0 && lit ? [
1816
+ handLayer(
1817
+ component,
1818
+ box,
1819
+ hand,
1820
+ angle,
1821
+ { part: "hand-bloom", scale: 4, blur: bloom, opacity: 0.9, color: light, root: light, tipFade: 1 },
1822
+ 0.26
1823
+ )
1824
+ ] : [],
1825
+ handLayer(component, box, hand, angle, { part: "hand", scale: 1, blur: 0, opacity: 1, color, root, clear }, 0.3)
1826
+ ];
1827
+ }
1828
+ function hubNode(component, box, hand, radius) {
1829
+ const ringWidth = numberProperty2(component, "hubRing");
1830
+ return {
1831
+ id: `widget-${component.id}-hub`,
1832
+ type: "ellipse",
1833
+ order: box.order + 0.4,
1834
+ bounds: rotatedBounds(
1835
+ { x: hand.cx - radius, y: hand.cy - radius, width: radius * 2, height: radius * 2 },
1836
+ 0,
1837
+ ringWidth
1838
+ ),
1839
+ cx: hand.cx,
1840
+ cy: hand.cy,
1841
+ rx: Math.max(0.5, radius - ringWidth / 2),
1842
+ ry: Math.max(0.5, radius - ringWidth / 2),
1843
+ style: fade(component, {
1844
+ fill: isOff(component, "hubFill") ? { kind: "none" } : solid(textProperty(component, "hubFill")),
1845
+ stroke: ringWidth > 0 ? solid(textProperty(component, "color")) : { kind: "none" },
1846
+ strokeWidth: ringWidth,
1847
+ ...numberProperty2(component, "glow") > 0 && !isOff(component, "glowColor") ? {
1848
+ shadow: {
1849
+ color: colorOr(component, "glowColor", textProperty(component, "color")),
1850
+ opacity: 1,
1851
+ blur: numberProperty2(component, "glow"),
1852
+ dx: 0,
1853
+ dy: 0
1854
+ }
1855
+ } : {}
1856
+ })
1857
+ };
1858
+ }
1859
+ var HIT_HALF = 8;
1860
+ function pickNodes(component, box, hand, angle) {
1861
+ const reach = { ...hand, rootHalf: Math.max(hand.rootHalf, HIT_HALF), tipHalf: Math.max(hand.tipHalf, HIT_HALF) };
1862
+ const near = reach.gap > 0 ? -reach.gap : reach.tail;
1863
+ const extent = {
1864
+ x: reach.cx - HIT_HALF,
1865
+ y: reach.cy - reach.length,
1866
+ width: HIT_HALF * 2,
1867
+ height: reach.length + near
1868
+ };
1869
+ return [
1870
+ hitArea(component, box, false),
1871
+ {
1872
+ id: `widget-${component.id}-pick`,
1873
+ type: "path",
1874
+ order: box.order + 0.5,
1875
+ bounds: rotatedBounds(extent, 0, 0),
1876
+ d: outline(reach),
1877
+ rotation: angle,
1878
+ rotationCenter: { x: reach.cx, y: reach.cy },
1879
+ style: { fill: { kind: "none" }, stroke: { kind: "none" }, pointerEvents: "all" }
1880
+ }
1881
+ ];
1882
+ }
1883
+ function buildHand(component, index) {
1884
+ const box = controlBox(component, index, { width: 200, height: 200 });
1885
+ const hand = handOf(component, box);
1886
+ const max = numberProperty2(component, "max") || 1;
1887
+ const angle = numberProperty2(component, "start") + Number(component.value) / max * 360;
1888
+ const hub = numberProperty2(component, "hub");
1889
+ return [
1890
+ ...pickNodes(component, box, hand, angle),
1891
+ ...handLayers(component, box, hand, angle),
1892
+ ...hub > 0 ? [hubNode(component, box, hand, hub)] : []
1893
+ ];
1894
+ }
1895
+
1616
1896
  // src/renderScene/artifacts/component/knob.ts
1617
1897
  var DOT_RATIO = 0.62;
1618
1898
  function decimals(step) {
@@ -1625,21 +1905,26 @@ function readout(component, fraction) {
1625
1905
  }
1626
1906
  return display === "value" ? Number(component.value).toFixed(decimals(numberProperty2(component, "step"))) : "";
1627
1907
  }
1628
- function buildKnob(component, index) {
1629
- const box = controlBox(component, index, { width: 96, height: 96 });
1908
+ function dialOf(component, box) {
1630
1909
  const min = numberProperty2(component, "min");
1631
1910
  const max = numberProperty2(component, "max");
1632
- const fraction = Math.min(1, Math.max(0, (Number(component.value) - min) / (max - min || 1)));
1633
- const start = numberProperty2(component, "start");
1634
- const sweep = Math.min(360, Math.max(1, numberProperty2(component, "sweep")));
1635
1911
  const thickness = Math.min(numberProperty2(component, "thickness"), Math.min(box.width, box.height) / 3);
1636
- const cx = box.x + box.width / 2;
1637
- const cy = box.y + box.height / 2;
1638
1912
  const radius = Math.max(1, Math.min(box.width, box.height) / 2 - thickness / 2);
1639
- const circumference = 2 * Math.PI * radius;
1640
- const round2 = textProperty(component, "rounded") !== "false";
1641
- const angle = (start - fraction * sweep) * Math.PI / 180;
1642
- const arc = (part, order, color, length, extra = {}) => ({
1913
+ return {
1914
+ cx: box.x + box.width / 2,
1915
+ cy: box.y + box.height / 2,
1916
+ radius,
1917
+ thickness,
1918
+ fraction: Math.min(1, Math.max(0, (Number(component.value) - min) / (max - min || 1))),
1919
+ start: numberProperty2(component, "start"),
1920
+ sweep: Math.min(360, Math.max(1, numberProperty2(component, "sweep"))),
1921
+ circumference: 2 * Math.PI * radius,
1922
+ rounded: textProperty(component, "rounded") !== "false"
1923
+ };
1924
+ }
1925
+ function arcNode(component, box, dial, part, order, color, length, extra = {}) {
1926
+ const { cx, cy, radius, thickness } = dial;
1927
+ return {
1643
1928
  id: `widget-${component.id}-${part}`,
1644
1929
  type: "ellipse",
1645
1930
  order: box.order + order,
@@ -1648,59 +1933,62 @@ function buildKnob(component, index) {
1648
1933
  cy,
1649
1934
  rx: radius,
1650
1935
  ry: radius,
1651
- rotation: -start,
1936
+ rotation: -dial.start,
1652
1937
  style: fade(component, {
1653
1938
  fill: { kind: "none" },
1654
1939
  stroke: solid(color),
1655
1940
  strokeWidth: thickness,
1656
- dash: [Math.max(1e-3, length), circumference],
1657
- lineCap: round2 ? "round" : "butt",
1941
+ dash: [Math.max(1e-3, length), dial.circumference],
1942
+ lineCap: dial.rounded ? "round" : "butt",
1658
1943
  ...extra
1659
1944
  })
1660
- });
1945
+ };
1946
+ }
1947
+ function discNode(component, box, part, order, center, radius, fill2) {
1948
+ return {
1949
+ id: `widget-${component.id}-${part}`,
1950
+ type: "ellipse",
1951
+ order: box.order + order,
1952
+ bounds: rotatedBounds({ x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2 }, 0, 0),
1953
+ cx: center.x,
1954
+ cy: center.y,
1955
+ rx: radius,
1956
+ ry: radius,
1957
+ style: fade(component, { fill: solid(fill2), stroke: { kind: "none" } })
1958
+ };
1959
+ }
1960
+ function buildKnob(component, index) {
1961
+ const box = controlBox(component, index, { width: 96, height: 96 });
1962
+ const dial = dialOf(component, box);
1963
+ const { cx, cy, radius, thickness, fraction, sweep, circumference } = dial;
1964
+ const angle = (dial.start - fraction * sweep) * Math.PI / 180;
1661
1965
  const faceRadius = Math.max(1, radius - thickness / 2 - Math.max(2, thickness * 0.4));
1662
1966
  const dotRadius = thickness / 2 * DOT_RATIO + 1;
1663
1967
  const nodes = [
1664
1968
  hitArea(component, box),
1665
- arc("track", 0.1, textProperty(component, "track"), sweep / 360 * circumference),
1666
- arc("fill", 0.2, textProperty(component, "accent"), fraction * sweep / 360 * circumference, {
1667
- opacity: fraction > 0 ? 1 : 0
1668
- }),
1669
- {
1670
- id: `widget-${component.id}-face`,
1671
- type: "ellipse",
1672
- order: box.order + 0.3,
1673
- bounds: rotatedBounds(
1674
- { x: cx - faceRadius, y: cy - faceRadius, width: faceRadius * 2, height: faceRadius * 2 },
1675
- 0,
1676
- 0
1677
- ),
1678
- cx,
1679
- cy,
1680
- rx: faceRadius,
1681
- ry: faceRadius,
1682
- style: fade(component, { fill: solid(textProperty(component, "face")), stroke: { kind: "none" } })
1683
- },
1684
- {
1685
- id: `widget-${component.id}-dot`,
1686
- type: "ellipse",
1687
- order: box.order + 0.4,
1688
- bounds: rotatedBounds(
1689
- {
1690
- x: cx + radius * Math.cos(angle) - dotRadius,
1691
- y: cy - radius * Math.sin(angle) - dotRadius,
1692
- width: dotRadius * 2,
1693
- height: dotRadius * 2
1694
- },
1695
- 0,
1696
- 0
1697
- ),
1698
- cx: cx + radius * Math.cos(angle),
1699
- cy: cy - radius * Math.sin(angle),
1700
- rx: dotRadius,
1701
- ry: dotRadius,
1702
- style: fade(component, { fill: solid(textProperty(component, "dot")), stroke: { kind: "none" } })
1703
- }
1969
+ arcNode(component, box, dial, "track", 0.1, textProperty(component, "track"), sweep / 360 * circumference),
1970
+ arcNode(
1971
+ component,
1972
+ box,
1973
+ dial,
1974
+ "fill",
1975
+ 0.2,
1976
+ textProperty(component, "accent"),
1977
+ fraction * sweep / 360 * circumference,
1978
+ {
1979
+ opacity: fraction > 0 ? 1 : 0
1980
+ }
1981
+ ),
1982
+ discNode(component, box, "face", 0.3, { x: cx, y: cy }, faceRadius, textProperty(component, "face")),
1983
+ discNode(
1984
+ component,
1985
+ box,
1986
+ "dot",
1987
+ 0.4,
1988
+ { x: cx + radius * Math.cos(angle), y: cy - radius * Math.sin(angle) },
1989
+ dotRadius,
1990
+ textProperty(component, "dot")
1991
+ )
1704
1992
  ];
1705
1993
  const text = readout(component, fraction);
1706
1994
  if (text) {
@@ -1731,62 +2019,68 @@ function readout2(component, fraction) {
1731
2019
  }
1732
2020
  return display === "value" ? String(Number(component.value)) : "";
1733
2021
  }
2022
+ function buildRing(component, box, fraction, round2) {
2023
+ const thickness = Math.min(numberProperty2(component, "thickness"), Math.min(box.width, box.height) / 2);
2024
+ const radius = Math.max(1, Math.min(box.width, box.height) / 2 - thickness / 2);
2025
+ const circumference = 2 * Math.PI * radius;
2026
+ const cx = box.x + box.width / 2;
2027
+ const cy = box.y + box.height / 2;
2028
+ const ring = (part, order, color, extra) => ({
2029
+ id: `widget-${component.id}-${part}`,
2030
+ type: "ellipse",
2031
+ order: box.order + order,
2032
+ bounds: rotatedBounds({ x: cx - radius, y: cy - radius, width: radius * 2, height: radius * 2 }, 0, thickness),
2033
+ cx,
2034
+ cy,
2035
+ rx: radius,
2036
+ ry: radius,
2037
+ rotation: -90,
2038
+ style: fade(component, { fill: { kind: "none" }, stroke: solid(color), strokeWidth: thickness, ...extra })
2039
+ });
2040
+ return [
2041
+ ring("track", 0.1, textProperty(component, "track"), {}),
2042
+ ring("fill", 0.2, textProperty(component, "accent"), {
2043
+ dash: [Math.max(1e-3, fraction * circumference), circumference],
2044
+ lineCap: round2 ? "round" : "butt",
2045
+ opacity: fraction > 0 ? 1 : 0
2046
+ })
2047
+ ];
2048
+ }
2049
+ function buildBar(component, box, fraction, round2) {
2050
+ const vertical = textProperty(component, "direction") === "vertical";
2051
+ const radius = round2 ? (vertical ? box.width : box.height) / 2 : 0;
2052
+ const bar = (part, order, length, color, opacity) => {
2053
+ const shape = vertical ? { x: box.x, y: box.y + box.height - length, width: box.width, height: length } : { x: box.x, y: box.y, width: length, height: box.height };
2054
+ return {
2055
+ id: `widget-${component.id}-${part}`,
2056
+ type: "rect",
2057
+ order: box.order + order,
2058
+ bounds: rotatedBounds(shape, 0, 0),
2059
+ ...shape,
2060
+ rx: radius,
2061
+ style: fade(component, {
2062
+ fill: solid(color),
2063
+ stroke: { kind: "none" },
2064
+ ...opacity === void 0 ? {} : { opacity }
2065
+ })
2066
+ };
2067
+ };
2068
+ const full = vertical ? box.height : box.width;
2069
+ return [
2070
+ bar("track", 0.1, full, textProperty(component, "track")),
2071
+ bar("fill", 0.2, Math.max(0, fraction * full), textProperty(component, "accent"), fraction > 0 ? 1 : 0)
2072
+ ];
2073
+ }
1734
2074
  function buildProgress(component, index) {
1735
2075
  const box = controlBox(component, index, { width: 220, height: 12 });
1736
2076
  const min = numberProperty2(component, "min");
1737
2077
  const max = numberProperty2(component, "max");
1738
2078
  const fraction = Math.min(1, Math.max(0, (Number(component.value) - min) / (max - min || 1)));
1739
2079
  const round2 = textProperty(component, "rounded") !== "false";
1740
- const nodes = [hitArea(component, box)];
1741
- if (textProperty(component, "variant") === "ring") {
1742
- const thickness = Math.min(numberProperty2(component, "thickness"), Math.min(box.width, box.height) / 2);
1743
- const radius = Math.max(1, Math.min(box.width, box.height) / 2 - thickness / 2);
1744
- const circumference = 2 * Math.PI * radius;
1745
- const cx = box.x + box.width / 2;
1746
- const cy = box.y + box.height / 2;
1747
- const ring = (part, order, color, extra) => ({
1748
- id: `widget-${component.id}-${part}`,
1749
- type: "ellipse",
1750
- order: box.order + order,
1751
- bounds: rotatedBounds({ x: cx - radius, y: cy - radius, width: radius * 2, height: radius * 2 }, 0, thickness),
1752
- cx,
1753
- cy,
1754
- rx: radius,
1755
- ry: radius,
1756
- rotation: -90,
1757
- style: fade(component, { fill: { kind: "none" }, stroke: solid(color), strokeWidth: thickness, ...extra })
1758
- });
1759
- nodes.push(ring("track", 0.1, textProperty(component, "track"), {}));
1760
- nodes.push(
1761
- ring("fill", 0.2, textProperty(component, "accent"), {
1762
- dash: [Math.max(1e-3, fraction * circumference), circumference],
1763
- lineCap: round2 ? "round" : "butt",
1764
- opacity: fraction > 0 ? 1 : 0
1765
- })
1766
- );
1767
- } else {
1768
- const vertical = textProperty(component, "direction") === "vertical";
1769
- const radius = round2 ? (vertical ? box.width : box.height) / 2 : 0;
1770
- const bar = (part, order, length, color, opacity) => {
1771
- const shape = vertical ? { x: box.x, y: box.y + box.height - length, width: box.width, height: length } : { x: box.x, y: box.y, width: length, height: box.height };
1772
- return {
1773
- id: `widget-${component.id}-${part}`,
1774
- type: "rect",
1775
- order: box.order + order,
1776
- bounds: rotatedBounds(shape, 0, 0),
1777
- ...shape,
1778
- rx: radius,
1779
- style: fade(component, {
1780
- fill: solid(color),
1781
- stroke: { kind: "none" },
1782
- ...opacity === void 0 ? {} : { opacity }
1783
- })
1784
- };
1785
- };
1786
- const full = vertical ? box.height : box.width;
1787
- nodes.push(bar("track", 0.1, full, textProperty(component, "track")));
1788
- nodes.push(bar("fill", 0.2, Math.max(0, fraction * full), textProperty(component, "accent"), fraction > 0 ? 1 : 0));
1789
- }
2080
+ const nodes = [
2081
+ hitArea(component, box),
2082
+ ...textProperty(component, "variant") === "ring" ? buildRing(component, box, fraction, round2) : buildBar(component, box, fraction, round2)
2083
+ ];
1790
2084
  const text = readout2(component, fraction);
1791
2085
  if (text) {
1792
2086
  const label = {
@@ -1905,12 +2199,15 @@ function ticks(component, box, vertical, reach, offset) {
1905
2199
  return rect(component, box, `tick-${index}`, 0.15, shape, color);
1906
2200
  });
1907
2201
  }
2202
+ function alongOf(box, reach, vertical) {
2203
+ return (fraction) => vertical ? box.y + box.height - reach - fraction * (box.height - reach * 2) : box.x + reach + fraction * (box.width - reach * 2);
2204
+ }
1908
2205
  function buildLine(component, box, thumbs, vertical) {
1909
2206
  const reach = sliderReach("line", vertical ? box.width : box.height);
1910
2207
  const radius = reach * numberProperty2(component, "thumbSize") / 100;
1911
2208
  const thickness = Math.min(numberProperty2(component, "trackHeight"), vertical ? box.width : box.height);
1912
2209
  const mid = vertical ? box.x + box.width / 2 : box.y + box.height / 2;
1913
- const along2 = (fraction) => vertical ? box.y + box.height - reach - fraction * (box.height - reach * 2) : box.x + reach + fraction * (box.width - reach * 2);
2210
+ const along2 = alongOf(box, reach, vertical);
1914
2211
  const line = (start, end) => vertical ? {
1915
2212
  x: mid - thickness / 2,
1916
2213
  y: Math.min(start, end),
@@ -1959,69 +2256,57 @@ function buildLine(component, box, thumbs, vertical) {
1959
2256
  thumb(thumbs.two ? "thumb-end" : "thumb", thumbs.to, 0.31)
1960
2257
  ];
1961
2258
  }
1962
- function buildField(component, box, thumbs, vertical) {
1963
- const reach = sliderReach("field", vertical ? box.width : box.height);
1964
- const radius = Math.min(numberProperty2(component, "radius"), Math.min(box.width, box.height) / 2);
1965
- const whole = { x: box.x, y: box.y, width: box.width, height: box.height, rx: radius };
1966
- const along2 = (fraction) => vertical ? box.y + box.height - reach - fraction * (box.height - reach * 2) : box.x + reach + fraction * (box.width - reach * 2);
1967
- const start = thumbs.two ? along2(thumbs.from) : vertical ? box.y + box.height : box.x;
1968
- const end = along2(thumbs.to);
2259
+ function fieldFill(whole, box, thumbs, vertical, start, end) {
1969
2260
  const first = Math.min(start, end) - (thumbs.two ? HANDLE_WIDTH / 2 : 0);
1970
2261
  const last = Math.max(start, end) + HANDLE_WIDTH / 2;
1971
- const filled = vertical ? {
2262
+ return vertical ? {
1972
2263
  ...whole,
1973
2264
  y: thumbs.two ? first : end - HANDLE_WIDTH / 2,
1974
2265
  height: thumbs.two ? last - first : Math.max(0, box.y + box.height - end + HANDLE_WIDTH / 2),
1975
- rx: thumbs.two ? 0 : radius
2266
+ rx: thumbs.two ? 0 : whole.rx
1976
2267
  } : {
1977
2268
  ...whole,
1978
2269
  x: thumbs.two ? first : box.x,
1979
2270
  width: thumbs.two ? last - first : Math.max(0, end + HANDLE_WIDTH / 2 - box.x),
1980
- rx: thumbs.two ? 0 : radius
2271
+ rx: thumbs.two ? 0 : whole.rx
1981
2272
  };
2273
+ }
2274
+ function fieldHandle(component, box, vertical, part, at) {
1982
2275
  const handleLength = (vertical ? box.width : box.height) * 0.5;
1983
- const handle = (part, at) => {
1984
- const shape = vertical ? {
1985
- x: box.x + (box.width - handleLength) / 2,
1986
- y: at - HANDLE_WIDTH / 2,
1987
- width: handleLength,
1988
- height: HANDLE_WIDTH,
1989
- rx: HANDLE_WIDTH / 2
1990
- } : {
1991
- x: at - HANDLE_WIDTH / 2,
1992
- y: box.y + (box.height - handleLength) / 2,
1993
- width: HANDLE_WIDTH,
1994
- height: handleLength,
1995
- rx: HANDLE_WIDTH / 2
1996
- };
1997
- return rect(component, box, part, 0.3, shape, textProperty(component, "accent"));
2276
+ const shape = vertical ? {
2277
+ x: box.x + (box.width - handleLength) / 2,
2278
+ y: at - HANDLE_WIDTH / 2,
2279
+ width: handleLength,
2280
+ height: HANDLE_WIDTH,
2281
+ rx: HANDLE_WIDTH / 2
2282
+ } : {
2283
+ x: at - HANDLE_WIDTH / 2,
2284
+ y: box.y + (box.height - handleLength) / 2,
2285
+ width: HANDLE_WIDTH,
2286
+ height: handleLength,
2287
+ rx: HANDLE_WIDTH / 2
1998
2288
  };
1999
- const nodes = [
2000
- rect(component, box, "track", 0.1, whole, textProperty(component, "surface")),
2001
- rect(component, box, "fill", 0.2, filled, textProperty(component, "fill")),
2002
- ...thumbs.two ? [handle("handle-start", start)] : [],
2003
- handle(thumbs.two ? "handle-end" : "handle", end)
2004
- ];
2289
+ return rect(component, box, part, 0.3, shape, textProperty(component, "accent"));
2290
+ }
2291
+ function fieldTexts(component, box, thumbs, vertical) {
2005
2292
  const text = {
2006
2293
  color: ink(component, textProperty(component, "textColor")),
2007
2294
  fontSize: numberProperty2(component, "fontSize")
2008
2295
  };
2009
2296
  const value = readout3(component, thumbs);
2010
2297
  if (vertical) {
2011
- if (value) {
2012
- nodes.push(
2013
- labelText(
2014
- component,
2015
- "value",
2016
- { x: box.x, y: box.y + 6, width: box.width, height: 20 },
2017
- { ...text, text: value, align: "center" },
2018
- box.order + 0.4
2019
- )
2020
- );
2021
- }
2022
- return nodes;
2298
+ return value ? [
2299
+ labelText(
2300
+ component,
2301
+ "value",
2302
+ { x: box.x, y: box.y + 6, width: box.width, height: 20 },
2303
+ { ...text, text: value, align: "center" },
2304
+ box.order + 0.4
2305
+ )
2306
+ ] : [];
2023
2307
  }
2024
2308
  const inner = { y: box.y, height: box.height, width: Math.max(0, box.width / 2 - TEXT_PADDING) };
2309
+ const nodes = [];
2025
2310
  if (component.label) {
2026
2311
  nodes.push(
2027
2312
  labelText(
@@ -2044,9 +2329,31 @@ function buildField(component, box, thumbs, vertical) {
2044
2329
  )
2045
2330
  );
2046
2331
  }
2047
- nodes.push(...ticks(component, box, false, reach, box.height - TICK_LENGTH - 5));
2048
2332
  return nodes;
2049
2333
  }
2334
+ function buildField(component, box, thumbs, vertical) {
2335
+ const reach = sliderReach("field", vertical ? box.width : box.height);
2336
+ const radius = Math.min(numberProperty2(component, "radius"), Math.min(box.width, box.height) / 2);
2337
+ const whole = { x: box.x, y: box.y, width: box.width, height: box.height, rx: radius };
2338
+ const along2 = alongOf(box, reach, vertical);
2339
+ const start = thumbs.two ? along2(thumbs.from) : vertical ? box.y + box.height : box.x;
2340
+ const end = along2(thumbs.to);
2341
+ return [
2342
+ rect(component, box, "track", 0.1, whole, textProperty(component, "surface")),
2343
+ rect(
2344
+ component,
2345
+ box,
2346
+ "fill",
2347
+ 0.2,
2348
+ fieldFill(whole, box, thumbs, vertical, start, end),
2349
+ textProperty(component, "fill")
2350
+ ),
2351
+ ...thumbs.two ? [fieldHandle(component, box, vertical, "handle-start", start)] : [],
2352
+ fieldHandle(component, box, vertical, thumbs.two ? "handle-end" : "handle", end),
2353
+ ...fieldTexts(component, box, thumbs, vertical),
2354
+ ...vertical ? [] : ticks(component, box, false, reach, box.height - TICK_LENGTH - 5)
2355
+ ];
2356
+ }
2050
2357
  function buildSlider(component, index) {
2051
2358
  const field = textProperty(component, "variant") === "field";
2052
2359
  const vertical = textProperty(component, "direction") === "vertical";
@@ -2163,6 +2470,7 @@ var builders = /* @__PURE__ */ new Map([
2163
2470
  ["progress", asComponentBuilder(buildProgress)],
2164
2471
  ["segmented", asComponentBuilder(buildSegmented)],
2165
2472
  ["knob", asComponentBuilder(buildKnob)],
2473
+ ["hand", asComponentBuilder(buildHand)],
2166
2474
  ["range", asComponentBuilder(buildSlider)]
2167
2475
  ]);
2168
2476
  function registerComponentBuilder(type, builder) {
@@ -3568,6 +3876,68 @@ function renderShapeGeometry(context) {
3568
3876
  return renderer(context);
3569
3877
  }
3570
3878
 
3879
+ // src/renderScene/artifacts/shape/image.ts
3880
+ function clipOf(outline2, box) {
3881
+ if (outline2?.type === "path") {
3882
+ return { type: "path", d: outline2.d };
3883
+ }
3884
+ if (outline2?.type === "ellipse") {
3885
+ const { cx, cy, rx, ry } = outline2;
3886
+ return {
3887
+ type: "path",
3888
+ d: `M${cx - rx} ${cy}A${rx} ${ry} 0 1 0 ${cx + rx} ${cy}A${rx} ${ry} 0 1 0 ${cx - rx} ${cy}Z`
3889
+ };
3890
+ }
3891
+ return { type: "rect", ...box, rx: outline2?.type === "rect" ? outline2.rx ?? 0 : 0 };
3892
+ }
3893
+ function croppedBox(image, crop, pixels, box) {
3894
+ const scaleX = box.width / crop.width;
3895
+ const scaleY = box.height / crop.height;
3896
+ const scale = image.fit === "contain" ? Math.min(scaleX, scaleY) : Math.max(scaleX, scaleY);
3897
+ const [sx, sy] = image.fit === "fill" ? [scaleX, scaleY] : [scale, scale];
3898
+ return {
3899
+ x: box.x + (box.width - crop.width * sx) / 2 - crop.x * sx,
3900
+ y: box.y + (box.height - crop.height * sy) / 2 - crop.y * sy,
3901
+ width: pixels[0] * sx,
3902
+ height: pixels[1] * sy
3903
+ };
3904
+ }
3905
+ var ASPECTS = { cover: "slice", contain: "meet", fill: "none" };
3906
+ function renderShapeImage(context, outline2, diagnostics) {
3907
+ const image = context.shape.image;
3908
+ if (!image) {
3909
+ return void 0;
3910
+ }
3911
+ if (image.crop && !image.pixels) {
3912
+ diagnostics.push({
3913
+ severity: "warning",
3914
+ code: "image-crop-without-size",
3915
+ ownerId: context.shape.id,
3916
+ message: `The image '${context.shape.id}' has a crop but no imageSize, so it is shown uncropped. Give it the size of the file, as imageSize: width, height.`
3917
+ });
3918
+ }
3919
+ const box = { x: context.x, y: context.y, width: context.width, height: context.height };
3920
+ const cropped = image.crop && image.pixels;
3921
+ return {
3922
+ type: "image",
3923
+ id: `${context.id}-image`,
3924
+ order: context.order,
3925
+ bounds: context.bounds,
3926
+ ...cropped ? croppedBox(image, image.crop, image.pixels, box) : box,
3927
+ href: image.src,
3928
+ aspect: cropped ? "none" : ASPECTS[image.fit],
3929
+ rotation: context.rotation,
3930
+ rotationCenter: context.rotationCenter,
3931
+ style: {
3932
+ opacity: context.style.opacity,
3933
+ blur: context.style.blur,
3934
+ blend: context.style.blend,
3935
+ clip: clipOf(outline2, box),
3936
+ pointerEvents: "none"
3937
+ }
3938
+ };
3939
+ }
3940
+
3571
3941
  // src/renderScene/artifacts/shape/shaderSurface.ts
3572
3942
  var RECT_PROGRAMS = /* @__PURE__ */ new Set([
3573
3943
  "reactivePanel",
@@ -3874,6 +4244,15 @@ function buildShape(shape, index, diagnostics, measureText) {
3874
4244
  }
3875
4245
  nodes.push(shader);
3876
4246
  }
4247
+ const image = renderShapeImage(context, nodes[0], diagnostics);
4248
+ if (image) {
4249
+ for (const node of nodes) {
4250
+ if (node.type === "ellipse" || node.type === "path" || node.type === "rect") {
4251
+ node.style = { ...node.style, pointerEvents: "all" };
4252
+ }
4253
+ }
4254
+ nodes.push(image);
4255
+ }
3877
4256
  const text = renderShapeText(context, strokeWidth, measureText);
3878
4257
  if (text) {
3879
4258
  nodes.push(text);
@@ -4146,27 +4525,46 @@ function tiltedBottomRoom(chart) {
4146
4525
  const labels = chart.chartType === "bar" && !isHorizontalBar(chart) ? chart.config?.categories ?? [] : chart.chartType === "boxplot" && !isHorizontalBoxplot(chart) ? chart.series.map((series) => series.label || series.id) : [];
4147
4526
  return labels.length > 0 ? Math.max(0, tiltedLabelRoom(chart, labels, tilt) - 18) : 0;
4148
4527
  }
4149
- function decorations(chart) {
4150
- const showLabels = chart.showAxisLabels !== false;
4528
+ function legendPlaceOf(chart) {
4151
4529
  const showLegend = chart.showLegend !== false;
4152
- const legendPosition = chart.legendPosition ?? "top";
4153
- const legendAtTop = legendPosition === "top";
4154
- const hasLegendItems = showLegend && chart.series.length > 0;
4155
- const legendStacked = hasLegendItems && isStackedLegendPosition(legendPosition);
4156
- const sideColumnWidth = legendStacked ? stackedLegendColumnWidth(chart) + LEGEND_COLUMN_GAP : 0;
4157
- const treemapSide = treemapSideLegendSide(chart, showLegend);
4158
- const tickX = xLabelOffset(chart);
4159
- const tickY = yLabelOffset(chart);
4160
- const padLeft = tickY + (showLabels ? leftLabelWidth(chart) : 12) + (legendPosition === "bottomLeft" ? sideColumnWidth : 0) + (treemapSide === "left" ? TREEMAP_SIDE_LEGEND_WIDTH : 0);
4161
- const padRight = 24 + (legendPosition === "bottomRight" ? sideColumnWidth : 0) + (treemapSide === "right" ? TREEMAP_SIDE_LEGEND_WIDTH : 0);
4530
+ const position = chart.legendPosition ?? "top";
4531
+ const hasItems = showLegend && chart.series.length > 0;
4532
+ const stacked = hasItems && isStackedLegendPosition(position);
4533
+ return {
4534
+ showLegend,
4535
+ position,
4536
+ atTop: position === "top",
4537
+ hasItems,
4538
+ stacked,
4539
+ sideColumnWidth: stacked ? stackedLegendColumnWidth(chart) + LEGEND_COLUMN_GAP : 0,
4540
+ treemapSide: treemapSideLegendSide(chart, showLegend)
4541
+ };
4542
+ }
4543
+ function padLeftOf(chart, showLabels, legend) {
4544
+ return yLabelOffset(chart) + (showLabels ? leftLabelWidth(chart) : 12) + (legend.position === "bottomLeft" ? legend.sideColumnWidth : 0) + (legend.treemapSide === "left" ? TREEMAP_SIDE_LEGEND_WIDTH : 0);
4545
+ }
4546
+ function padRightOf(legend) {
4547
+ return 24 + (legend.position === "bottomRight" ? legend.sideColumnWidth : 0) + (legend.treemapSide === "right" ? TREEMAP_SIDE_LEGEND_WIDTH : 0);
4548
+ }
4549
+ function padBottomOf(chart, showLabels, legend) {
4162
4550
  const valueRow = chart.chartType === "bar" && chart.config?.showBarLabels === true && chart.config.barLabelPosition === "axis";
4163
4551
  const horizontalValueAxis = isHorizontalBar(chart) && chart.showValueAxis !== false;
4164
- const basePadBottom = showLabels ? isHorizontalBar(chart) ? horizontalValueAxis ? 34 : 12 : (chart.axisX.label ? 52 : 34) + (valueRow ? 18 : 0) : 12;
4165
- const hasColorBar = chart.chartType === "field" && showLegend && Boolean(chart.config?.scale?.stops.length);
4166
- const legendAtBottomRow = hasLegendItems && !legendAtTop && !legendStacked;
4167
- const padBottom = tickX + basePadBottom + (showLabels ? tiltedBottomRoom(chart) : 0) + (hasColorBar ? 44 : 0) + (legendAtBottomRow ? 16 + LEGEND_ROW_HEIGHT : 0);
4552
+ const base = showLabels ? isHorizontalBar(chart) ? horizontalValueAxis ? 34 : 12 : (chart.axisX.label ? 52 : 34) + (valueRow ? 18 : 0) : 12;
4553
+ const hasColorBar = chart.chartType === "field" && legend.showLegend && Boolean(chart.config?.scale?.stops.length);
4554
+ const legendInRow = legend.hasItems && !legend.atTop && !legend.stacked;
4555
+ return xLabelOffset(chart) + base + (showLabels ? tiltedBottomRoom(chart) : 0) + (hasColorBar ? 44 : 0) + (legendInRow ? 16 + LEGEND_ROW_HEIGHT : 0);
4556
+ }
4557
+ function padTopOf(chart, legend) {
4168
4558
  const titleHeight = chart.title ? Math.max(44, 20 + finite(chart.titleSize, 14) * 1.6) : 0;
4169
- const padTop = titleHeight || (legendAtTop && hasLegendItems ? 44 : 24);
4559
+ return titleHeight || (legend.atTop && legend.hasItems ? 44 : 24);
4560
+ }
4561
+ function decorations(chart) {
4562
+ const showLabels = chart.showAxisLabels !== false;
4563
+ const legend = legendPlaceOf(chart);
4564
+ const padLeft = padLeftOf(chart, showLabels, legend);
4565
+ const padRight = padRightOf(legend);
4566
+ const padBottom = padBottomOf(chart, showLabels, legend);
4567
+ const padTop = padTopOf(chart, legend);
4170
4568
  return {
4171
4569
  bounds: boxBounds(
4172
4570
  { x: chart.x, y: chart.y, width: chart.width, height: chart.height },
@@ -4179,7 +4577,7 @@ function decorations(chart) {
4179
4577
  height: Math.max(1, chart.height - padTop - padBottom)
4180
4578
  },
4181
4579
  showLabels,
4182
- showLegend,
4580
+ showLegend: legend.showLegend,
4183
4581
  padLeft
4184
4582
  };
4185
4583
  }
@@ -8084,25 +8482,23 @@ function labelPlacement2(context, geometry, angles, series, fillColor) {
8084
8482
  );
8085
8483
  return { point, color, outside, mid, tickStart };
8086
8484
  }
8087
- function createWedgeLabel(context, geometry, angles, series, value, fillColor) {
8088
- const placement = labelPlacement2(context, geometry, angles, series, fillColor);
8089
- if (!placement) {
8090
- return [];
8091
- }
8092
- const { chart, bounds, nextOrder } = context;
8093
- const { point, color, outside, mid, tickStart } = placement;
8094
- const { nameSize, valueSize } = labelSizes(chart);
8095
- const lineHeight = Math.max(nameSize, valueSize) * 1.2;
8485
+ function labelTick(context, series, placement, lineHeight) {
8486
+ const { point, color, outside, tickStart } = placement;
8096
8487
  const tickEnd = outside ? clipSegmentToRect(tickStart, point, LABEL_WIDTH2 / 2, lineHeight) : tickStart;
8097
- const tick = {
8488
+ return {
8098
8489
  type: "path",
8099
- id: `chart-polar-area-label-tick-${chart.id}-${series.id}`,
8100
- order: nextOrder(),
8101
- bounds,
8490
+ id: `chart-polar-area-label-tick-${context.chart.id}-${series.id}`,
8491
+ order: context.nextOrder(),
8492
+ bounds: context.bounds,
8102
8493
  d: `M ${tickStart.x} ${tickStart.y} L ${tickEnd.x} ${tickEnd.y}`,
8103
8494
  detail: { of: "chartSeries", series: series.id, mark: "polar-area-label-tick" },
8104
8495
  style: outside ? { stroke: { kind: "solid", color }, strokeWidth: 1.5 } : { stroke: { kind: "none" } }
8105
8496
  };
8497
+ }
8498
+ function labelTexts(context, geometry, series, value, placement) {
8499
+ const { chart, bounds, nextOrder } = context;
8500
+ const { point, color, outside, mid } = placement;
8501
+ const { nameSize, valueSize } = labelSizes(chart);
8106
8502
  const detail = {
8107
8503
  of: "chartSeries",
8108
8504
  series: series.id,
@@ -8154,7 +8550,16 @@ function createWedgeLabel(context, geometry, angles, series, value, fillColor) {
8154
8550
  prop: seriesValueProp(series.id, 0),
8155
8551
  propKind: "value"
8156
8552
  };
8157
- return [tick, nameText, valueText];
8553
+ return [nameText, valueText];
8554
+ }
8555
+ function createWedgeLabel(context, geometry, angles, series, value, fillColor) {
8556
+ const placement = labelPlacement2(context, geometry, angles, series, fillColor);
8557
+ if (!placement) {
8558
+ return [];
8559
+ }
8560
+ const { nameSize, valueSize } = labelSizes(context.chart);
8561
+ const tick = labelTick(context, series, placement, Math.max(nameSize, valueSize) * 1.2);
8562
+ return [tick, ...labelTexts(context, geometry, series, value, placement)];
8158
8563
  }
8159
8564
  function addOffset(point, dx, dy) {
8160
8565
  return { x: point.x + dx, y: point.y + dy };
@@ -9660,7 +10065,7 @@ function buildRenderScene(input, options = {}) {
9660
10065
 
9661
10066
  // src/svg/format.ts
9662
10067
  var xml = (value) => String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
9663
- var num = (value) => Number(value.toFixed(3)).toString();
10068
+ var num2 = (value) => Number(value.toFixed(3)).toString();
9664
10069
  var safeId = (value) => `sf-${value.replace(/[^A-Za-z0-9_.:-]/g, "-")}`;
9665
10070
 
9666
10071
  // src/svg/clipDefinitions.ts
@@ -9682,7 +10087,7 @@ var SvgClipDefinitions = class {
9682
10087
  const outer = clip.within ? this.define(clip.within) : void 0;
9683
10088
  id = safeId(`${this.prefix}clip-${this.ids.size}`);
9684
10089
  this.ids.set(key, id);
9685
- const shape = clip.type === "path" ? `<path clip-rule="evenodd" d="${xml(clip.d)}"/>` : `<rect x="${num(clip.x)}" y="${num(clip.y)}" width="${num(clip.width)}" height="${num(clip.height)}" rx="${num(clip.rx)}"/>`;
10090
+ const shape = clip.type === "path" ? `<path clip-rule="evenodd" d="${xml(clip.d)}"/>` : `<rect x="${num2(clip.x)}" y="${num2(clip.y)}" width="${num2(clip.width)}" height="${num2(clip.height)}" rx="${num2(clip.rx)}"/>`;
9686
10091
  this.addDefinition(`<clipPath id="${id}"${outer ? ` clip-path="url(#${outer})"` : ""}>${shape}</clipPath>`);
9687
10092
  }
9688
10093
  return id;
@@ -9724,18 +10129,18 @@ var SvgFilterDefinitions = class {
9724
10129
  id = safeId(`${this.prefix}shadow-${nodeId}-${this.ids.size}`);
9725
10130
  this.ids.set(key, id);
9726
10131
  this.addDefinition(
9727
- `<filter id="${id}" x="${num(-margin * 100)}%" y="${num(-margin * 100)}%" width="${num((1 + margin * 2) * 100)}%" height="${num((1 + margin * 2) * 100)}%">${primitives(value)}</filter>`
10132
+ `<filter id="${id}" x="${num2(-margin * 100)}%" y="${num2(-margin * 100)}%" width="${num2((1 + margin * 2) * 100)}%" height="${num2((1 + margin * 2) * 100)}%">${primitives(value)}</filter>`
9728
10133
  );
9729
10134
  }
9730
10135
  return id;
9731
10136
  }
9732
10137
  };
9733
10138
  function outerShadowPrimitives(shadow) {
9734
- return `<feDropShadow dx="${num(shadow.dx)}" dy="${num(shadow.dy)}" stdDeviation="${num(shadow.blur / 2)}" flood-color="${xml(shadow.color)}" flood-opacity="${num(shadow.opacity)}"/>`;
10139
+ return `<feDropShadow dx="${num2(shadow.dx)}" dy="${num2(shadow.dy)}" stdDeviation="${num2(shadow.blur / 2)}" flood-color="${xml(shadow.color)}" flood-opacity="${num2(shadow.opacity)}"/>`;
9735
10140
  }
9736
10141
  function blurPrimitives(effect) {
9737
- const blur = effect.blur > 0 ? `<feGaussianBlur stdDeviation="${num(effect.blur / 2)}"/>` : "";
9738
- const saturate = effect.saturate === 1 ? "" : `<feColorMatrix type="saturate" values="${num(effect.saturate)}"/>`;
10142
+ const blur = effect.blur > 0 ? `<feGaussianBlur stdDeviation="${num2(effect.blur / 2)}"/>` : "";
10143
+ const saturate = effect.saturate === 1 ? "" : `<feColorMatrix type="saturate" values="${num2(effect.saturate)}"/>`;
9739
10144
  return blur + saturate;
9740
10145
  }
9741
10146
  function innerShadowPrimitives(shadow) {
@@ -9743,9 +10148,9 @@ function innerShadowPrimitives(shadow) {
9743
10148
  '<feComponentTransfer in="SourceAlpha" result="sf-inverted-alpha">',
9744
10149
  '<feFuncA type="table" tableValues="1 0"/>',
9745
10150
  "</feComponentTransfer>",
9746
- `<feGaussianBlur in="sf-inverted-alpha" stdDeviation="${num(shadow.blur / 2)}" result="sf-blurred"/>`,
9747
- `<feOffset in="sf-blurred" dx="${num(shadow.dx)}" dy="${num(shadow.dy)}" result="sf-offset"/>`,
9748
- `<feFlood flood-color="${xml(shadow.color)}" flood-opacity="${num(shadow.opacity)}" result="sf-color"/>`,
10151
+ `<feGaussianBlur in="sf-inverted-alpha" stdDeviation="${num2(shadow.blur / 2)}" result="sf-blurred"/>`,
10152
+ `<feOffset in="sf-blurred" dx="${num2(shadow.dx)}" dy="${num2(shadow.dy)}" result="sf-offset"/>`,
10153
+ `<feFlood flood-color="${xml(shadow.color)}" flood-opacity="${num2(shadow.opacity)}" result="sf-color"/>`,
9749
10154
  '<feComposite in="sf-color" in2="sf-offset" operator="in" result="sf-halo"/>',
9750
10155
  '<feComposite in="sf-halo" in2="SourceGraphic" operator="in" result="sf-inset"/>',
9751
10156
  '<feComposite in="sf-inset" in2="SourceGraphic" operator="over"/>'
@@ -9755,19 +10160,19 @@ function innerShadowPrimitives(shadow) {
9755
10160
  // src/svg/markerBodies.ts
9756
10161
  function markerBody(type, size, color, strokeWidth) {
9757
10162
  const bodies = {
9758
- arrow: `<path d="M ${num(-size)} ${num(-size * 0.6)} L 0 0 L ${num(-size)} ${num(size * 0.6)}" fill="none" stroke="${color}" stroke-width="${num(strokeWidth)}" stroke-linecap="round" stroke-linejoin="round"/>`,
9759
- filled_arrow: `<path d="M ${num(-size)} ${num(-size * 0.6)} L 0 0 L ${num(-size)} ${num(size * 0.6)} Z" fill="${color}" stroke="${color}" stroke-width="${num(strokeWidth * 0.5)}" stroke-linejoin="round"/>`,
9760
- circle: `<circle cx="0" cy="0" r="${num(size * 0.5)}" fill="white" stroke="${color}" stroke-width="${num(strokeWidth)}"/>`,
9761
- filled_circle: `<circle cx="0" cy="0" r="${num(size * 0.5)}" fill="${color}"/>`,
9762
- diamond: `<path d="M ${num(-size * 0.6)} 0 L 0 ${num(-size * 0.6)} L ${num(size * 0.6)} 0 L 0 ${num(size * 0.6)} Z" fill="white" stroke="${color}" stroke-width="${num(strokeWidth)}"/>`,
9763
- filled_diamond: `<path d="M ${num(-size * 0.6)} 0 L 0 ${num(-size * 0.6)} L ${num(size * 0.6)} 0 L 0 ${num(size * 0.6)} Z" fill="${color}" stroke="${color}" stroke-width="${num(strokeWidth * 0.5)}"/>`,
9764
- bar: `<path d="M 0 ${num(-size * 0.6)} V ${num(size * 0.6)}" stroke="${color}" stroke-width="${num(strokeWidth)}" stroke-linecap="round"/>`,
9765
- triangle: `<path d="M ${num(-size)} ${num(-size * 0.72)} L 0 0 L ${num(-size)} ${num(size * 0.72)} Z" fill="white" stroke="${color}" stroke-width="${num(strokeWidth)}" stroke-linejoin="round"/>`,
9766
- one: `<path d="M 0 ${num(-size * 0.62)} V ${num(size * 0.62)} M ${num(-size * 0.72)} ${num(-size * 0.62)} V ${num(size * 0.62)}" stroke="${color}" stroke-width="${num(strokeWidth)}"/>`,
9767
- zero_one: `<circle cx="${num(-size * 0.8)}" cy="0" r="${num(size * 0.38)}" fill="white" stroke="${color}" stroke-width="${num(strokeWidth)}"/><path d="M 0 ${num(-size * 0.62)} V ${num(size * 0.62)}" stroke="${color}" stroke-width="${num(strokeWidth)}"/>`,
9768
- many: `<path d="M ${num(-size)} ${num(-size * 0.72)} L 0 0 M ${num(-size)} 0 L 0 0 M ${num(-size)} ${num(size * 0.72)} L 0 0" stroke="${color}" stroke-width="${num(strokeWidth)}" stroke-linecap="round"/>`,
9769
- one_many: `<path d="M ${num(-size)} ${num(-size * 0.72)} L 0 0 M ${num(-size)} 0 L 0 0 M ${num(-size)} ${num(size * 0.72)} L 0 0 M ${num(-size * 1.45)} ${num(-size * 0.62)} V ${num(size * 0.62)}" stroke="${color}" stroke-width="${num(strokeWidth)}" stroke-linecap="round"/>`,
9770
- zero_many: `<circle cx="${num(-size * 1.45)}" cy="0" r="${num(size * 0.38)}" fill="white" stroke="${color}" stroke-width="${num(strokeWidth)}"/><path d="M ${num(-size)} ${num(-size * 0.72)} L 0 0 M ${num(-size)} 0 L 0 0 M ${num(-size)} ${num(size * 0.72)} L 0 0" stroke="${color}" stroke-width="${num(strokeWidth)}" stroke-linecap="round"/>`
10163
+ arrow: `<path d="M ${num2(-size)} ${num2(-size * 0.6)} L 0 0 L ${num2(-size)} ${num2(size * 0.6)}" fill="none" stroke="${color}" stroke-width="${num2(strokeWidth)}" stroke-linecap="round" stroke-linejoin="round"/>`,
10164
+ filled_arrow: `<path d="M ${num2(-size)} ${num2(-size * 0.6)} L 0 0 L ${num2(-size)} ${num2(size * 0.6)} Z" fill="${color}" stroke="${color}" stroke-width="${num2(strokeWidth * 0.5)}" stroke-linejoin="round"/>`,
10165
+ circle: `<circle cx="0" cy="0" r="${num2(size * 0.5)}" fill="white" stroke="${color}" stroke-width="${num2(strokeWidth)}"/>`,
10166
+ filled_circle: `<circle cx="0" cy="0" r="${num2(size * 0.5)}" fill="${color}"/>`,
10167
+ diamond: `<path d="M ${num2(-size * 0.6)} 0 L 0 ${num2(-size * 0.6)} L ${num2(size * 0.6)} 0 L 0 ${num2(size * 0.6)} Z" fill="white" stroke="${color}" stroke-width="${num2(strokeWidth)}"/>`,
10168
+ filled_diamond: `<path d="M ${num2(-size * 0.6)} 0 L 0 ${num2(-size * 0.6)} L ${num2(size * 0.6)} 0 L 0 ${num2(size * 0.6)} Z" fill="${color}" stroke="${color}" stroke-width="${num2(strokeWidth * 0.5)}"/>`,
10169
+ bar: `<path d="M 0 ${num2(-size * 0.6)} V ${num2(size * 0.6)}" stroke="${color}" stroke-width="${num2(strokeWidth)}" stroke-linecap="round"/>`,
10170
+ triangle: `<path d="M ${num2(-size)} ${num2(-size * 0.72)} L 0 0 L ${num2(-size)} ${num2(size * 0.72)} Z" fill="white" stroke="${color}" stroke-width="${num2(strokeWidth)}" stroke-linejoin="round"/>`,
10171
+ one: `<path d="M 0 ${num2(-size * 0.62)} V ${num2(size * 0.62)} M ${num2(-size * 0.72)} ${num2(-size * 0.62)} V ${num2(size * 0.62)}" stroke="${color}" stroke-width="${num2(strokeWidth)}"/>`,
10172
+ zero_one: `<circle cx="${num2(-size * 0.8)}" cy="0" r="${num2(size * 0.38)}" fill="white" stroke="${color}" stroke-width="${num2(strokeWidth)}"/><path d="M 0 ${num2(-size * 0.62)} V ${num2(size * 0.62)}" stroke="${color}" stroke-width="${num2(strokeWidth)}"/>`,
10173
+ many: `<path d="M ${num2(-size)} ${num2(-size * 0.72)} L 0 0 M ${num2(-size)} 0 L 0 0 M ${num2(-size)} ${num2(size * 0.72)} L 0 0" stroke="${color}" stroke-width="${num2(strokeWidth)}" stroke-linecap="round"/>`,
10174
+ one_many: `<path d="M ${num2(-size)} ${num2(-size * 0.72)} L 0 0 M ${num2(-size)} 0 L 0 0 M ${num2(-size)} ${num2(size * 0.72)} L 0 0 M ${num2(-size * 1.45)} ${num2(-size * 0.62)} V ${num2(size * 0.62)}" stroke="${color}" stroke-width="${num2(strokeWidth)}" stroke-linecap="round"/>`,
10175
+ zero_many: `<circle cx="${num2(-size * 1.45)}" cy="0" r="${num2(size * 0.38)}" fill="white" stroke="${color}" stroke-width="${num2(strokeWidth)}"/><path d="M ${num2(-size)} ${num2(-size * 0.72)} L 0 0 M ${num2(-size)} 0 L 0 0 M ${num2(-size)} ${num2(size * 0.72)} L 0 0" stroke="${color}" stroke-width="${num2(strokeWidth)}" stroke-linecap="round"/>`
9771
10176
  };
9772
10177
  return bodies[type];
9773
10178
  }
@@ -9778,7 +10183,7 @@ function markerExtent(type, size, strokeWidth) {
9778
10183
  return Math.max(size * (isCompoundErMarker ? 2 : 1.4), strokeWidth * 2);
9779
10184
  }
9780
10185
  function markerAttributes(id, extent) {
9781
- return `id="${id}" markerWidth="${num(extent * 2)}" markerHeight="${num(extent * 2)}" viewBox="${num(-extent)} ${num(-extent)} ${num(extent * 2)} ${num(extent * 2)}" refX="0" refY="0" orient="auto-start-reverse" markerUnits="userSpaceOnUse" overflow="visible"`;
10186
+ return `id="${id}" markerWidth="${num2(extent * 2)}" markerHeight="${num2(extent * 2)}" viewBox="${num2(-extent)} ${num2(-extent)} ${num2(extent * 2)} ${num2(extent * 2)}" refX="0" refY="0" orient="auto-start-reverse" markerUnits="userSpaceOnUse" overflow="visible"`;
9782
10187
  }
9783
10188
  var SvgMarkerDefinitions = class {
9784
10189
  constructor(addDefinition, prefix) {
@@ -9813,7 +10218,7 @@ function meshGradientDefinition(id, paint2) {
9813
10218
  const radialDefinitions = paint2.points.map((point, index) => {
9814
10219
  const centerX = Math.max(0, Math.min(100, point.x));
9815
10220
  const centerY = Math.max(0, Math.min(100, point.y));
9816
- return `<radialGradient id="${radialIds[index]}" gradientUnits="objectBoundingBox" cx="${num(centerX)}%" cy="${num(centerY)}%" r="75%"><stop offset="0%" stop-color="${xml(point.color)}" stop-opacity="1"/><stop offset="100%" stop-color="${xml(point.color)}" stop-opacity="0"/></radialGradient>`;
10221
+ return `<radialGradient id="${radialIds[index]}" gradientUnits="objectBoundingBox" cx="${num2(centerX)}%" cy="${num2(centerY)}%" r="75%"><stop offset="0%" stop-color="${xml(point.color)}" stop-opacity="1"/><stop offset="100%" stop-color="${xml(point.color)}" stop-opacity="0"/></radialGradient>`;
9817
10222
  }).join("");
9818
10223
  const layers = paint2.points.map((_point, index) => `<rect width="1" height="1" fill="url(#${radialIds[index]})"/>`).join("");
9819
10224
  return `${radialDefinitions}<pattern id="${id}" patternUnits="objectBoundingBox" patternContentUnits="objectBoundingBox" width="1" height="1"><rect width="1" height="1" fill="${xml(paint2.points[0]?.color ?? "transparent")}"/>${layers}</pattern>`;
@@ -9825,8 +10230,8 @@ function linearGradientDefinition(id, paint2, bounds) {
9825
10230
  const radius = Math.max(bounds.maxX - bounds.minX, bounds.maxY - bounds.minY) / 2;
9826
10231
  const deltaX = Math.cos(angle) * radius;
9827
10232
  const deltaY = Math.sin(angle) * radius;
9828
- const stops = paint2.stops.map((stop) => `<stop offset="${num(Math.max(0, Math.min(100, stop.offset)))}%" stop-color="${xml(stop.color)}"/>`).join("");
9829
- return `<linearGradient id="${id}" gradientUnits="userSpaceOnUse" x1="${num(centerX - deltaX)}" y1="${num(centerY - deltaY)}" x2="${num(centerX + deltaX)}" y2="${num(centerY + deltaY)}">${stops}</linearGradient>`;
10233
+ const stops = paint2.stops.map((stop) => `<stop offset="${num2(Math.max(0, Math.min(100, stop.offset)))}%" stop-color="${xml(stop.color)}"/>`).join("");
10234
+ return `<linearGradient id="${id}" gradientUnits="userSpaceOnUse" x1="${num2(centerX - deltaX)}" y1="${num2(centerY - deltaY)}" x2="${num2(centerX + deltaX)}" y2="${num2(centerY + deltaY)}">${stops}</linearGradient>`;
9830
10235
  }
9831
10236
  var SvgPaintDefinitions = class {
9832
10237
  constructor(addDefinition, prefix) {
@@ -9943,10 +10348,10 @@ function styleAttrs(node, defs) {
9943
10348
  }
9944
10349
  function addStrokeAttrs(attrs, style) {
9945
10350
  if (style.strokeWidth !== void 0) {
9946
- attrs.push(`stroke-width="${num(style.strokeWidth)}"`);
10351
+ attrs.push(`stroke-width="${num2(style.strokeWidth)}"`);
9947
10352
  }
9948
10353
  if (style.dash?.length) {
9949
- attrs.push(`stroke-dasharray="${style.dash.map(num).join(" ")}"`);
10354
+ attrs.push(`stroke-dasharray="${style.dash.map(num2).join(" ")}"`);
9950
10355
  }
9951
10356
  if (style.lineCap) {
9952
10357
  attrs.push(`stroke-linecap="${style.lineCap}"`);
@@ -9955,14 +10360,14 @@ function addStrokeAttrs(attrs, style) {
9955
10360
  attrs.push(`stroke-linejoin="${style.lineJoin}"`);
9956
10361
  }
9957
10362
  if (style.opacity !== void 0) {
9958
- attrs.push(`opacity="${num(style.opacity)}"`);
10363
+ attrs.push(`opacity="${num2(style.opacity)}"`);
9959
10364
  }
9960
10365
  if (style.pointerEvents) {
9961
10366
  attrs.push(`pointer-events="${style.pointerEvents}"`);
9962
10367
  }
9963
10368
  const css = [];
9964
10369
  if (style.transition && style.transition > 0) {
9965
- const time = `${num(style.transition)}ms ease`;
10370
+ const time = `${num2(style.transition)}ms ease`;
9966
10371
  css.push(
9967
10372
  `transition: x ${time}, y ${time}, width ${time}, height ${time}, cx ${time}, cy ${time}, rx ${time}, ry ${time}, fill ${time}, stroke ${time}, stroke-width ${time}, stroke-dasharray ${time}, opacity ${time}`
9968
10373
  );
@@ -9982,6 +10387,10 @@ function elementSize(node) {
9982
10387
  if (shape.type === "rect" && shape.width !== void 0 && shape.height !== void 0) {
9983
10388
  return Math.min(shape.width, shape.height);
9984
10389
  }
10390
+ if (shape.type === "path" && shape.bounds) {
10391
+ const side = Math.min(shape.bounds.maxX - shape.bounds.minX, shape.bounds.maxY - shape.bounds.minY);
10392
+ return side > 0 ? side : void 0;
10393
+ }
9985
10394
  return void 0;
9986
10395
  }
9987
10396
  function addVisualEffectAttrs(attrs, node, defs) {
@@ -10008,12 +10417,12 @@ function addPathMarkerAttrs(attrs, node, defs) {
10008
10417
  }
10009
10418
  }
10010
10419
  function transform(rotation, center) {
10011
- return rotation && center ? ` transform="rotate(${num(rotation)} ${num(center.x)} ${num(center.y)})"` : "";
10420
+ return rotation && center ? ` transform="rotate(${num2(rotation)} ${num2(center.x)} ${num2(center.y)})"` : "";
10012
10421
  }
10013
10422
  function imageAttrs(node, defs) {
10014
10423
  const attrs = [];
10015
10424
  if (node.style.opacity !== void 0) {
10016
- attrs.push(`opacity="${num(node.style.opacity)}"`);
10425
+ attrs.push(`opacity="${num2(node.style.opacity)}"`);
10017
10426
  }
10018
10427
  if (node.style.pointerEvents) {
10019
10428
  attrs.push(`pointer-events="${node.style.pointerEvents}"`);
@@ -10075,12 +10484,12 @@ function chartValueAttrs(detail) {
10075
10484
  `data-sf-series="${xml(detail.series)}"`,
10076
10485
  `data-sf-value-index="${detail.valueIndex}"`,
10077
10486
  detail.mark ? `data-sf-mark="${detail.mark}"` : void 0,
10078
- detail.radius !== void 0 ? `data-sf-bar-radius="${num(detail.radius)}"` : void 0,
10487
+ detail.radius !== void 0 ? `data-sf-bar-radius="${num2(detail.radius)}"` : void 0,
10079
10488
  detail.colorProp ? `data-sf-color-prop="${xml(detail.colorProp)}"` : void 0,
10080
10489
  detail.hasColorOverride ? 'data-sf-color-override="true"' : void 0,
10081
- detail.pieStart !== void 0 ? `data-sf-pie-start="${num(detail.pieStart)}"` : void 0,
10082
- detail.pieEnd !== void 0 ? `data-sf-pie-end="${num(detail.pieEnd)}"` : void 0,
10083
- detail.pieValue !== void 0 ? `data-sf-pie-value="${num(detail.pieValue)}"` : void 0
10490
+ detail.pieStart !== void 0 ? `data-sf-pie-start="${num2(detail.pieStart)}"` : void 0,
10491
+ detail.pieEnd !== void 0 ? `data-sf-pie-end="${num2(detail.pieEnd)}"` : void 0,
10492
+ detail.pieValue !== void 0 ? `data-sf-pie-value="${num2(detail.pieValue)}"` : void 0
10084
10493
  ];
10085
10494
  }
10086
10495
  function pieGeometryAttrs(detail) {
@@ -10095,39 +10504,39 @@ function chartScaleAttrs(detail) {
10095
10504
  function chartSeriesAttrs(detail) {
10096
10505
  return [
10097
10506
  `data-sf-series="${xml(detail.series)}"`,
10098
- detail.lineTension !== void 0 ? `data-sf-line-tension="${num(detail.lineTension)}"` : void 0,
10507
+ detail.lineTension !== void 0 ? `data-sf-line-tension="${num2(detail.lineTension)}"` : void 0,
10099
10508
  detail.mark ? `data-sf-mark="${xml(detail.mark)}"` : void 0,
10100
10509
  detail.pulled ? 'data-sf-label-pulled="true"' : void 0,
10101
- detail.midAngle !== void 0 ? `data-sf-pie-mid-angle="${num(detail.midAngle)}"` : void 0,
10102
- detail.wedgeRadius !== void 0 ? `data-sf-pie-wedge-radius="${num(detail.wedgeRadius)}"` : void 0
10510
+ detail.midAngle !== void 0 ? `data-sf-pie-mid-angle="${num2(detail.midAngle)}"` : void 0,
10511
+ detail.wedgeRadius !== void 0 ? `data-sf-pie-wedge-radius="${num2(detail.wedgeRadius)}"` : void 0
10103
10512
  ];
10104
10513
  }
10105
10514
  function frameBoundaryAttrs(detail) {
10106
- return [`${CORNER_RADIUS_ATTR}="${num(detail.cornerRadius)}"`, `${SQUARE_CORNER_ATTR}="${detail.squareCorner}"`];
10515
+ return [`${CORNER_RADIUS_ATTR}="${num2(detail.cornerRadius)}"`, `${SQUARE_CORNER_ATTR}="${detail.squareCorner}"`];
10107
10516
  }
10108
10517
  function declaredBoundsAttr(bounds) {
10109
- return `${SYMBOL_BOUNDS_ATTR}="${num(bounds.x)},${num(bounds.y)},${num(bounds.width)},${num(bounds.height)}"`;
10518
+ return `${SYMBOL_BOUNDS_ATTR}="${num2(bounds.x)},${num2(bounds.y)},${num2(bounds.width)},${num2(bounds.height)}"`;
10110
10519
  }
10111
10520
  function symbolGeometryAttrs(detail) {
10112
10521
  return [
10113
10522
  `${SYMBOL_POINTS_ATTR}="${xml(JSON.stringify(detail.points))}"`,
10114
- `${SYMBOL_TENSION_ATTR}="${num(detail.tension)}"`,
10523
+ `${SYMBOL_TENSION_ATTR}="${num2(detail.tension)}"`,
10115
10524
  declaredBoundsAttr(detail.declaredBounds)
10116
10525
  ];
10117
10526
  }
10118
10527
  function pathGeometryAttrs(detail) {
10119
10528
  return [
10120
10529
  `${PATH_POINTS_ATTR}="${xml(JSON.stringify(detail.points))}"`,
10121
- `${PATH_TENSION_ATTR}="${num(detail.tension)}"`,
10530
+ `${PATH_TENSION_ATTR}="${num2(detail.tension)}"`,
10122
10531
  `${PATH_CLOSED_ATTR}="${detail.closed}"`
10123
10532
  ];
10124
10533
  }
10125
10534
  function isometricGeometryAttrs(detail) {
10126
10535
  return [
10127
- `${ISOMETRIC_SIZE_ATTR}="${num(detail.width)},${num(detail.height)},${num(detail.depth)}"`,
10128
- `${CORNER_RADIUS_ATTR}="${num(detail.cornerRadius)}"`,
10536
+ `${ISOMETRIC_SIZE_ATTR}="${num2(detail.width)},${num2(detail.height)},${num2(detail.depth)}"`,
10537
+ `${CORNER_RADIUS_ATTR}="${num2(detail.cornerRadius)}"`,
10129
10538
  declaredBoundsAttr(detail.bounds),
10130
- `${ISOMETRIC_LABEL_OFFSET_ATTR}="${num(detail.labelOffsetX)},${num(detail.labelOffsetY)}"`
10539
+ `${ISOMETRIC_LABEL_OFFSET_ATTR}="${num2(detail.labelOffsetX)},${num2(detail.labelOffsetY)}"`
10131
10540
  ];
10132
10541
  }
10133
10542
  function linkDetailAttrs(detail) {
@@ -10153,8 +10562,8 @@ function contractAttrs(node) {
10153
10562
  node.propSolidOnly ? 'data-sf-prop-solid-only="true"' : void 0,
10154
10563
  node.mark ? `data-sf-mark="${xml(node.mark)}"` : void 0,
10155
10564
  node.zoneGradient ? 'data-sf-zone-gradient="true"' : void 0,
10156
- node.zoneGradient ? `data-sf-zone-axis-min="${num(node.zoneGradient.min)}"` : void 0,
10157
- node.zoneGradient ? `data-sf-zone-axis-max="${num(node.zoneGradient.max)}"` : void 0,
10565
+ node.zoneGradient ? `data-sf-zone-axis-min="${num2(node.zoneGradient.min)}"` : void 0,
10566
+ node.zoneGradient ? `data-sf-zone-axis-max="${num2(node.zoneGradient.max)}"` : void 0,
10158
10567
  label ? `tabindex="0" aria-label="${xml(label.replace(/\s+/g, " ").trim())}"` : void 0
10159
10568
  ].filter((attribute) => attribute !== void 0);
10160
10569
  return attributes.length ? ` ${attributes.join(" ")}` : "";
@@ -10163,7 +10572,7 @@ function contractAttrs(node) {
10163
10572
  // src/svg/text.ts
10164
10573
  function richLineTspans(words, x, dy, fontSize) {
10165
10574
  if (!words.length) {
10166
- return `<tspan x="${num(x)}"${dy} font-size="${fontSize}"></tspan>`;
10575
+ return `<tspan x="${num2(x)}"${dy} font-size="${fontSize}"></tspan>`;
10167
10576
  }
10168
10577
  const groups = [];
10169
10578
  for (const word of words) {
@@ -10178,15 +10587,15 @@ function richLineTspans(words, x, dy, fontSize) {
10178
10587
  }
10179
10588
  }
10180
10589
  return groups.map(
10181
- (group, i) => `<tspan${i === 0 ? ` x="${num(x)}"${dy}` : ""} font-size="${fontSize}" font-weight="${group.bold ? "bold" : "normal"}">${xml(group.text)}</tspan>`
10590
+ (group, i) => `<tspan${i === 0 ? ` x="${num2(x)}"${dy}` : ""} font-size="${fontSize}" font-weight="${group.bold ? "bold" : "normal"}">${xml(group.text)}</tspan>`
10182
10591
  ).join("");
10183
10592
  }
10184
10593
  function textSvg(node, defs) {
10185
10594
  const position = textPosition(node);
10186
- const fontSize = `${num(node.fontSize)}px`;
10595
+ const fontSize = `${num2(node.fontSize)}px`;
10187
10596
  const content = textContent(node, position, fontSize);
10188
10597
  const preserve = node.richLines ? ' xml:space="preserve"' : "";
10189
- return `<text id="${defs.elementId(node.id)}"${contractAttrs(node)} x="${num(position.x)}" y="${num(position.firstY)}" font-family="${xml(node.fontFamily)}" font-size="${fontSize}" font-weight="${node.fontWeight}"${node.letterSpacing ? ` letter-spacing="${num(node.letterSpacing)}"` : ""} fill="${xml(node.color)}" text-anchor="${position.anchor}" data-sf-valign="${node.verticalAlign}" data-sf-line-height="${num(node.lineHeight)}"${preserve}${transform(node.rotation, node.rotationCenter)}>${content}</text>`;
10598
+ return `<text id="${defs.elementId(node.id)}"${contractAttrs(node)} x="${num2(position.x)}" y="${num2(position.firstY)}" font-family="${xml(node.fontFamily)}" font-size="${fontSize}" font-weight="${node.fontWeight}"${node.letterSpacing ? ` letter-spacing="${num2(node.letterSpacing)}"` : ""} fill="${xml(node.color)}" text-anchor="${position.anchor}" data-sf-valign="${node.verticalAlign}" data-sf-line-height="${num2(node.lineHeight)}"${preserve}${transform(node.rotation, node.rotationCenter)}>${content}</text>`;
10190
10599
  }
10191
10600
  function textPosition(node) {
10192
10601
  const linePx = node.fontSize * node.lineHeight;
@@ -10212,10 +10621,10 @@ function textContent(node, position, fontSize) {
10212
10621
  return node.lines.map((line, index) => lineTspan(line, index, position, fontSize)).join("");
10213
10622
  }
10214
10623
  function lineTspan(line, index, position, fontSize) {
10215
- return `<tspan x="${num(position.x)}" font-size="${fontSize}"${lineOffset(index, position.linePx)}>${xml(line)}</tspan>`;
10624
+ return `<tspan x="${num2(position.x)}" font-size="${fontSize}"${lineOffset(index, position.linePx)}>${xml(line)}</tspan>`;
10216
10625
  }
10217
10626
  function lineOffset(index, linePx) {
10218
- return index ? ` dy="${num(linePx)}px"` : "";
10627
+ return index ? ` dy="${num2(linePx)}px"` : "";
10219
10628
  }
10220
10629
 
10221
10630
  // src/svg/nodes.ts
@@ -10224,7 +10633,7 @@ function shaderSurfaceSvg(node, defs) {
10224
10633
  const parameters = xml(JSON.stringify(node.shader.parameters));
10225
10634
  const surfacePadding = node.shader.surfacePadding ?? 0;
10226
10635
  const fillColor = node.fillColor ? ` data-sf-shader-fill="${xml(node.fillColor)}"` : "";
10227
- return `<foreignObject id="${defs.elementId(node.id)}"${contractAttrs(node)} data-sf-shader="${xml(node.shader.program)}" data-sf-shader-name="${xml(node.shader.name)}" data-sf-shader-params="${parameters}" data-sf-shader-mask="${node.mask}" data-sf-shader-padding="${num(surfacePadding)}"${fillColor} x="${num(node.x)}" y="${num(node.y)}" width="${num(node.width)}" height="${num(node.height)}"${rotation} pointer-events="${node.shader.program === "ruler" || node.identity?.role === "widget" ? "all" : "none"}"><canvas xmlns="http://www.w3.org/1999/xhtml" data-sf-shader-canvas="true" style="display:block;width:100%;height:100%;pointer-events:none"></canvas></foreignObject>`;
10636
+ return `<foreignObject id="${defs.elementId(node.id)}"${contractAttrs(node)} data-sf-shader="${xml(node.shader.program)}" data-sf-shader-name="${xml(node.shader.name)}" data-sf-shader-params="${parameters}" data-sf-shader-mask="${node.mask}" data-sf-shader-padding="${num2(surfacePadding)}"${fillColor} x="${num2(node.x)}" y="${num2(node.y)}" width="${num2(node.width)}" height="${num2(node.height)}"${rotation} pointer-events="${node.shader.program === "ruler" || node.identity?.role === "widget" ? "all" : "none"}"><canvas xmlns="http://www.w3.org/1999/xhtml" data-sf-shader-canvas="true" style="display:block;width:100%;height:100%;pointer-events:none"></canvas></foreignObject>`;
10228
10637
  }
10229
10638
  function rollingDigitSvg(character, node) {
10230
10639
  const style = node.componentStyle;
@@ -10239,7 +10648,7 @@ function rollingNumberSvg(node, defs) {
10239
10648
  const label = node.label ? `<span xmlns="http://www.w3.org/1999/xhtml" style="margin-bottom:.14em;color:${xml(node.componentStyle.labelColor)};font-size:.16em;font-weight:600;letter-spacing:.24em;line-height:1">${xml(node.label)}</span>` : "";
10240
10649
  const digits = [...node.value].map((character) => rollingDigitSvg(character, node)).join("");
10241
10650
  const componentStyle = xml(JSON.stringify(node.componentStyle));
10242
- return `<foreignObject id="${defs.elementId(node.id)}"${contractAttrs(node)} data-sf-widget="rollingNumber" data-sf-widget-value="${xml(node.value)}" data-sf-widget-digits="${node.digits ?? ""}" data-sf-widget-duration="${num(node.duration)}" data-sf-widget-style="${componentStyle}" x="${num(node.x)}" y="${num(node.y)}" width="${num(node.width)}" height="${num(node.height)}"${rotation}><div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;font-family:${xml(node.componentStyle.fontFamily)};font-size:${num(fontSize)}px">${label}<div data-sf-rolling-digits="true" aria-label="${xml(node.value)}" style="display:flex;gap:${node.componentStyle.variant === "plain" ? ".02em" : ".1em"};font-size:1em">${digits}</div></div></foreignObject>`;
10651
+ return `<foreignObject id="${defs.elementId(node.id)}"${contractAttrs(node)} data-sf-widget="rollingNumber" data-sf-widget-value="${xml(node.value)}" data-sf-widget-digits="${node.digits ?? ""}" data-sf-widget-duration="${num2(node.duration)}" data-sf-widget-style="${componentStyle}" x="${num2(node.x)}" y="${num2(node.y)}" width="${num2(node.width)}" height="${num2(node.height)}"${rotation}><div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;font-family:${xml(node.componentStyle.fontFamily)};font-size:${num2(fontSize)}px">${label}<div data-sf-rolling-digits="true" aria-label="${xml(node.value)}" style="display:flex;gap:${node.componentStyle.variant === "plain" ? ".02em" : ".1em"};font-size:1em">${digits}</div></div></foreignObject>`;
10243
10652
  }
10244
10653
  function nodeSvg(node, defs) {
10245
10654
  if (node.type === "text") {
@@ -10252,14 +10661,14 @@ function nodeSvg(node, defs) {
10252
10661
  return rollingNumberSvg(node, defs);
10253
10662
  }
10254
10663
  if (node.type === "image") {
10255
- return `<image id="${defs.elementId(node.id)}"${contractAttrs(node)} ${imageAttrs(node, defs)} href="${xml(node.href)}" x="${num(node.x)}" y="${num(node.y)}" width="${num(node.width)}" height="${num(node.height)}" preserveAspectRatio="xMidYMid slice"/>`;
10664
+ return `<image id="${defs.elementId(node.id)}"${contractAttrs(node)} ${imageAttrs(node, defs)} href="${xml(node.href)}" x="${num2(node.x)}" y="${num2(node.y)}" width="${num2(node.width)}" height="${num2(node.height)}" preserveAspectRatio="${node.aspect === "none" ? "none" : `xMidYMid ${node.aspect ?? "slice"}`}"${transform(node.rotation, node.rotationCenter)}/>`;
10256
10665
  }
10257
10666
  const attrs = `id="${defs.elementId(node.id)}"${contractAttrs(node)} ${styleAttrs(node, defs)}`;
10258
10667
  if (node.type === "rect") {
10259
- return `<rect ${attrs} x="${num(node.x)}" y="${num(node.y)}" width="${num(node.width)}" height="${num(node.height)}"${node.rx ? ` rx="${num(node.rx)}"` : ""}${transform(node.rotation, { x: node.x + node.width / 2, y: node.y + node.height / 2 })}/>`;
10668
+ return `<rect ${attrs} x="${num2(node.x)}" y="${num2(node.y)}" width="${num2(node.width)}" height="${num2(node.height)}"${node.rx ? ` rx="${num2(node.rx)}"` : ""}${transform(node.rotation, { x: node.x + node.width / 2, y: node.y + node.height / 2 })}/>`;
10260
10669
  }
10261
10670
  if (node.type === "ellipse") {
10262
- return `<ellipse ${attrs} cx="${num(node.cx)}" cy="${num(node.cy)}" rx="${num(node.rx)}" ry="${num(node.ry)}"${transform(node.rotation, { x: node.cx, y: node.cy })}/>`;
10671
+ return `<ellipse ${attrs} cx="${num2(node.cx)}" cy="${num2(node.cy)}" rx="${num2(node.rx)}" ry="${num2(node.ry)}"${transform(node.rotation, { x: node.cx, y: node.cy })}/>`;
10263
10672
  }
10264
10673
  return `<path ${attrs} d="${xml(node.d)}"${transform(node.rotation, node.rotationCenter)}/>`;
10265
10674
  }
@@ -10269,12 +10678,12 @@ function renderSvgDocument(scene, options = {}) {
10269
10678
  const padding = options.padding ?? 16;
10270
10679
  const width = Math.max(1, scene.bounds.maxX - scene.bounds.minX + padding * 2);
10271
10680
  const height = Math.max(1, scene.bounds.maxY - scene.bounds.minY + padding * 2);
10272
- const viewBox = `${num(scene.bounds.minX - padding)} ${num(scene.bounds.minY - padding)} ${num(width)} ${num(height)}`;
10681
+ const viewBox = `${num2(scene.bounds.minX - padding)} ${num2(scene.bounds.minY - padding)} ${num2(width)} ${num2(height)}`;
10273
10682
  const defs = new SvgDefinitionRegistry(options.idPrefix ? `${options.idPrefix}-` : "");
10274
10683
  const nodes = options.linkHitAreas ? withLinkHitAreas(scene.nodes) : scene.nodes;
10275
10684
  const body = nodes.map((node) => nodeSvg(node, defs)).join("\n ");
10276
10685
  const declaration = options.includeXmlDeclaration === false ? "" : '<?xml version="1.0" encoding="UTF-8"?>\n';
10277
- const svg = `${declaration}<svg xmlns="http://www.w3.org/2000/svg" width="${num(width)}px" height="${num(height)}px" viewBox="${viewBox}" role="img" data-sf-contract="${DOM_CONTRACT_VERSION}">
10686
+ const svg = `${declaration}<svg xmlns="http://www.w3.org/2000/svg" width="${num2(width)}px" height="${num2(height)}px" viewBox="${viewBox}" role="img" data-sf-contract="${DOM_CONTRACT_VERSION}">
10278
10687
  <defs>${defs.entries.join("")}</defs>
10279
10688
  ${body}
10280
10689
  </svg>`;
@@ -10340,5 +10749,5 @@ function defineComponent(definition) {
10340
10749
  }
10341
10750
 
10342
10751
  export { collectCanvasObjects, decodeColor, decodeShadow, defineComponent, render, renderCanvas, sflToRenderInput, sliderReach };
10343
- //# sourceMappingURL=chunk-G2N3KJYJ.js.map
10344
- //# sourceMappingURL=chunk-G2N3KJYJ.js.map
10752
+ //# sourceMappingURL=chunk-Q6QLU6YQ.js.map
10753
+ //# sourceMappingURL=chunk-Q6QLU6YQ.js.map