exodeui-react-native 1.0.5 → 1.0.6

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/engine.ts +107 -76
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exodeui-react-native",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "React Native runtime for ExodeUI animations",
5
5
  "main": "index.js",
6
6
  "files": [
package/src/engine.ts CHANGED
@@ -1630,13 +1630,13 @@ export class ExodeUIEngine {
1630
1630
  const paint = Skia.Paint();
1631
1631
 
1632
1632
  const isPressed = this.draggingSliderId === obj.id;
1633
- // Parse color safely
1634
- const bgNormal = opts.backgroundColor || opts.background || '#3b82f6';
1633
+ // The .exode file uses 'buttonBgColor', fall back to 'backgroundColor'
1634
+ const bgNormal = opts.buttonBgColor || opts.backgroundColor || opts.background || '#3b82f6';
1635
1635
  const bgActive = opts.activeBackgroundColor || opts.activeBackground || '#2563eb';
1636
- const bgStr = typeof bgNormal === 'string' ? bgNormal : '#3b82f6';
1637
- const bgActiveStr = typeof bgActive === 'string' ? bgActive : '#2563eb';
1636
+ const bgStr = (typeof bgNormal === 'string' ? bgNormal : '#3b82f6').replace(/\s+/g, '');
1637
+ const bgActiveStr = (typeof bgActive === 'string' ? bgActive : '#2563eb').replace(/\s+/g, '');
1638
1638
  try {
1639
- paint.setColor(Skia.Color((isPressed ? bgActiveStr : bgStr).replace(/\s+/g, '')));
1639
+ paint.setColor(Skia.Color(isPressed ? bgActiveStr : bgStr));
1640
1640
  } catch {
1641
1641
  paint.setColor(Skia.Color('#3b82f6'));
1642
1642
  }
@@ -1644,7 +1644,8 @@ export class ExodeUIEngine {
1644
1644
  const r = opts.cornerRadius ?? 8;
1645
1645
  canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -h/2, width: w, height: h }, r, r), paint);
1646
1646
 
1647
- const labelObj = opts.label || {};
1647
+ // label is an object {text, fontSize, color, ...}
1648
+ const labelObj = typeof opts.label === 'object' && opts.label !== null ? opts.label : {};
1648
1649
  const labelText = labelObj.text || opts.text || opts.title || 'Button';
1649
1650
  const fontSize = labelObj.fontSize || opts.fontSize || 14;
1650
1651
  const labelColor = labelObj.color || opts.color || '#ffffff';
@@ -1658,8 +1659,7 @@ export class ExodeUIEngine {
1658
1659
  }
1659
1660
  if (isPressed) textPaint.setAlphaf(0.85);
1660
1661
 
1661
- // Center text horizontally using getTextWidth
1662
- const textWidth = font.getTextWidth ? font.getTextWidth(labelText) : 0;
1662
+ const textWidth = font.getTextWidth ? font.getTextWidth(String(labelText)) : 0;
1663
1663
  canvas.save();
1664
1664
  canvas.translate(-textWidth / 2, fontSize / 3);
1665
1665
  canvas.drawText(String(labelText), 0, 0, textPaint, font);
@@ -1675,26 +1675,30 @@ export class ExodeUIEngine {
1675
1675
  const trackColor = checked ? (opts.activeColor || '#3b82f6') : (opts.inactiveColor || '#374151');
1676
1676
  try { paint.setColor(Skia.Color(trackColor.replace(/\s+/g, ''))); } catch { paint.setColor(Skia.Color('#374151')); }
1677
1677
 
1678
- const r = h / 2;
1678
+ const r = opts.cornerRadius ?? h / 2;
1679
1679
  canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -h/2, width: w, height: h }, r, r), paint);
1680
1680
 
1681
1681
  const thumbPaint = Skia.Paint();
1682
- thumbPaint.setColor(Skia.Color('#ffffff'));
1682
+ const thumbCol = opts.knobColor || opts.thumbColor || '#ffffff';
1683
+ try { thumbPaint.setColor(Skia.Color(thumbCol.replace(/\s+/g, ''))); } catch { thumbPaint.setColor(Skia.Color('#ffffff')); }
1683
1684
  const thumbRadius = (h - 8) / 2;
1684
1685
  const thumbX = checked ? (w / 2 - thumbRadius - 4) : (-w / 2 + thumbRadius + 4);
1685
1686
  canvas.drawCircle(thumbX, 0, thumbRadius, thumbPaint);
1686
1687
 
1687
- // Label text
1688
- const labelText = opts.label || opts.text || '';
1689
- if (labelText) {
1690
- const fontSize = opts.fontSize || 12;
1688
+ // label is an object: {text, position, fontSize, color, gap}
1689
+ const labelObj = typeof opts.label === 'object' && opts.label !== null ? opts.label : null;
1690
+ if (labelObj && labelObj.text) {
1691
+ const fontSize = labelObj.fontSize || 12;
1691
1692
  const font = this.getFont(fontSize, 'Helvetica Neue');
1692
1693
  const labelPaint = Skia.Paint();
1693
- const labelColor = opts.labelColor || opts.color || '#ffffff';
1694
+ const labelColor = labelObj.color || '#ffffff';
1694
1695
  try { labelPaint.setColor(Skia.Color((typeof labelColor === 'string' ? labelColor : '#ffffff').replace(/\s+/g, ''))); } catch { labelPaint.setColor(Skia.Color('#ffffff')); }
1696
+ const gap = labelObj.gap || 8;
1697
+ const position = labelObj.position || 'right';
1698
+ const offsetX = position === 'left' ? -(w/2 + gap + font.getTextWidth(String(labelObj.text))) : w/2 + gap;
1695
1699
  canvas.save();
1696
- canvas.translate(w/2 + 8, fontSize / 3);
1697
- canvas.drawText(String(labelText), 0, 0, labelPaint, font);
1700
+ canvas.translate(offsetX, fontSize / 3);
1701
+ canvas.drawText(String(labelObj.text), 0, 0, labelPaint, font);
1698
1702
  canvas.restore();
1699
1703
  }
1700
1704
  }
@@ -1714,7 +1718,7 @@ export class ExodeUIEngine {
1714
1718
  canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -trackHeight/2, width: w, height: trackHeight }, trackHeight/2, trackHeight/2), trackPaint);
1715
1719
 
1716
1720
  const activePaint = Skia.Paint();
1717
- const activeColor = opts.activeColor || opts.thumbColor || '#3b82f6';
1721
+ const activeColor = opts.activeColor || '#3b82f6';
1718
1722
  try { activePaint.setColor(Skia.Color((typeof activeColor === 'string' ? activeColor : '#3b82f6').replace(/\s+/g, ''))); } catch { activePaint.setColor(Skia.Color('#3b82f6')); }
1719
1723
  const thumbWidth = opts.thumbWidth ?? 16;
1720
1724
  const travelW = w - thumbWidth;
@@ -1727,16 +1731,39 @@ export class ExodeUIEngine {
1727
1731
  try { thumbPaint.setColor(Skia.Color((typeof thumbCol === 'string' ? thumbCol : '#ffffff').replace(/\s+/g, ''))); } catch { thumbPaint.setColor(Skia.Color('#ffffff')); }
1728
1732
  canvas.drawCircle(thumbX, 0, thumbWidth / 2, thumbPaint);
1729
1733
 
1730
- // Value label above thumb
1731
- if (opts.showValue !== false) {
1732
- const fontSize = opts.fontSize || 11;
1733
- const font = this.getFont(fontSize, 'Helvetica Neue');
1734
- const valPaint = Skia.Paint();
1735
- valPaint.setColor(Skia.Color('#ffffff'));
1736
- const valText = String(Math.round(value));
1734
+ // Label: opts.label is an object {text, visible, fontSize, color}
1735
+ const labelObj = typeof opts.label === 'object' && opts.label !== null ? opts.label : null;
1736
+ if (labelObj && labelObj.text && labelObj.visible !== false) {
1737
+ const lFontSize = labelObj.fontSize || 12;
1738
+ const lFont = this.getFont(lFontSize, 'Helvetica Neue');
1739
+ const lPaint = Skia.Paint();
1740
+ const lColor = labelObj.color || '#ffffff';
1741
+ try { lPaint.setColor(Skia.Color((typeof lColor === 'string' ? lColor : '#9ca3af').replace(/\s+/g, ''))); } catch { lPaint.setColor(Skia.Color('#9ca3af')); }
1737
1742
  canvas.save();
1738
- canvas.translate(thumbX, -thumbWidth / 2 - fontSize - 2);
1739
- canvas.drawText(valText, 0, 0, valPaint, font);
1743
+ canvas.translate(-w/2, -h/2 - lFontSize - 2);
1744
+ canvas.drawText(String(labelObj.text), 0, 0, lPaint, lFont);
1745
+ canvas.restore();
1746
+ }
1747
+
1748
+ // Value tooltip: opts.showValueTooltip + tooltipOffsetY
1749
+ if (opts.showValueTooltip) {
1750
+ const tFontSize = 11;
1751
+ const tFont = this.getFont(tFontSize, 'Helvetica Neue');
1752
+ const tooltipBg = opts.tooltipBgColor || '#333333';
1753
+ const tooltipText = opts.tooltipColor || '#ffffff';
1754
+ const offsetY = opts.tooltipOffsetY ?? -25;
1755
+ const valStr = String(Math.round(value));
1756
+ const tWidth = tFont.getTextWidth ? tFont.getTextWidth(valStr) + 8 : 24;
1757
+
1758
+ const bgP = Skia.Paint();
1759
+ try { bgP.setColor(Skia.Color((typeof tooltipBg === 'string' ? tooltipBg : '#333333').replace(/\s+/g, ''))); } catch { bgP.setColor(Skia.Color('#333333')); }
1760
+ canvas.drawRRect(Skia.RRectXY({ x: thumbX - tWidth/2, y: offsetY - tFontSize - 2, width: tWidth, height: tFontSize + 6 }, 3, 3), bgP);
1761
+
1762
+ const tPaint = Skia.Paint();
1763
+ try { tPaint.setColor(Skia.Color((typeof tooltipText === 'string' ? tooltipText : '#ffffff').replace(/\s+/g, ''))); } catch { tPaint.setColor(Skia.Color('#ffffff')); }
1764
+ canvas.save();
1765
+ canvas.translate(thumbX - tWidth/2 + 4, offsetY - 4);
1766
+ canvas.drawText(valStr, 0, 0, tPaint, tFont);
1740
1767
  canvas.restore();
1741
1768
  }
1742
1769
  }
@@ -2007,41 +2034,42 @@ export class ExodeUIEngine {
2007
2034
  const data = ds.data || [];
2008
2035
  if (data.length < 2) return;
2009
2036
 
2010
- const lineColorStr = (typeof ds.lineColor === 'string' ? ds.lineColor : '#3b82f6').replace(/\s+/g, '');
2037
+ const lineColorStr = typeof ds.lineColor === 'string' ? ds.lineColor : '#3b82f6';
2038
+ // hsl/hsla colors need to be converted; Skia may not accept them directly
2039
+ // We'll try Skia.Color, and if it throws, fall back to a safe default
2011
2040
  let lineColor;
2012
- try { lineColor = Skia.Color(lineColorStr); } catch { lineColor = Skia.Color('#3b82f6'); }
2041
+ try { lineColor = Skia.Color(lineColorStr.replace(/\s+/g, '')); } catch { lineColor = Skia.Color('#3b82f6'); }
2013
2042
 
2014
- const stepX = plotW / (data.length - 1);
2043
+ const stepX = plotW / (Math.max(data.length - 1, 1));
2015
2044
  const getX = (i: number) => -w/2 + axisMarginL + i * stepX;
2016
2045
  const getY = (val: number) => h/2 - axisMarginB - (val / globalMax) * plotH;
2017
2046
 
2018
- const smooth = opts.smooth !== false && (ds.smooth !== false);
2047
+ // Per-dataset smoothing from the .exode file
2048
+ const smooth = ds.smoothing === true;
2019
2049
 
2020
- // Gradient fill
2021
- if (ds.fill !== false && opts.fill !== false) {
2022
- const fillPath = Skia.Path.Make();
2023
- fillPath.moveTo(getX(0), h/2 - axisMarginB);
2024
- data.forEach((val: number, i: number) => {
2025
- const x = getX(i);
2026
- const y = getY(val);
2027
- if (i === 0) fillPath.lineTo(x, y);
2028
- else if (smooth && i < data.length) {
2029
- const prevX = getX(i - 1);
2030
- const prevY = getY(data[i - 1]);
2031
- const cpX = (prevX + x) / 2;
2032
- fillPath.cubicTo(cpX, prevY, cpX, y, x, y);
2033
- } else {
2034
- fillPath.lineTo(x, y);
2035
- }
2036
- });
2037
- fillPath.lineTo(getX(data.length - 1), h/2 - axisMarginB);
2038
- fillPath.close();
2039
-
2040
- const fillPaint = Skia.Paint();
2041
- fillPaint.setStyle(PaintStyle.Fill);
2042
- fillPaint.setAlphaf(0.15);
2043
- try { fillPaint.setColor(lineColor); } catch { fillPaint.setColor(Skia.Color('#3b82f6')); }
2044
- canvas.drawPath(fillPath, fillPaint);
2050
+ // Per-dataset area fill from .exode field 'showArea'
2051
+ if (ds.showArea === true && ds.areaColor) {
2052
+ const areaColorStr = typeof ds.areaColor === 'string' ? ds.areaColor : null;
2053
+ let areaColor;
2054
+ if (areaColorStr) {
2055
+ try { areaColor = Skia.Color(areaColorStr.replace(/\s+/g, '')); } catch { areaColor = null; }
2056
+ }
2057
+ if (areaColor) {
2058
+ const fillPath = Skia.Path.Make();
2059
+ fillPath.moveTo(getX(0), h/2 - axisMarginB);
2060
+ data.forEach((val: number, i: number) => {
2061
+ const x = getX(i); const y = getY(val);
2062
+ if (i === 0) fillPath.lineTo(x, y);
2063
+ else if (smooth) { const px = getX(i-1); const py = getY(data[i-1]); const cpX = (px+x)/2; fillPath.cubicTo(cpX, py, cpX, y, x, y); }
2064
+ else fillPath.lineTo(x, y);
2065
+ });
2066
+ fillPath.lineTo(getX(data.length - 1), h/2 - axisMarginB);
2067
+ fillPath.close();
2068
+ const fillPaint = Skia.Paint();
2069
+ fillPaint.setStyle(PaintStyle.Fill);
2070
+ fillPaint.setColor(areaColor);
2071
+ canvas.drawPath(fillPath, fillPaint);
2072
+ }
2045
2073
  }
2046
2074
 
2047
2075
  // Line
@@ -2052,26 +2080,29 @@ export class ExodeUIEngine {
2052
2080
 
2053
2081
  const linePath = Skia.Path.Make();
2054
2082
  data.forEach((val: number, i: number) => {
2055
- const x = getX(i);
2056
- const y = getY(val);
2083
+ const x = getX(i); const y = getY(val);
2057
2084
  if (i === 0) linePath.moveTo(x, y);
2058
- else if (smooth && i < data.length) {
2059
- const prevX = getX(i - 1);
2060
- const prevY = getY(data[i - 1]);
2061
- const cpX = (prevX + x) / 2;
2062
- linePath.cubicTo(cpX, prevY, cpX, y, x, y);
2063
- } else {
2064
- linePath.lineTo(x, y);
2065
- }
2085
+ else if (smooth) { const px = getX(i-1); const py = getY(data[i-1]); const cpX = (px+x)/2; linePath.cubicTo(cpX, py, cpX, y, x, y); }
2086
+ else linePath.lineTo(x, y);
2066
2087
  });
2067
2088
  canvas.drawPath(linePath, linePaint);
2068
2089
 
2069
- // Data points
2070
- if (ds.showPoints !== false && data.length <= 20) {
2090
+ // Data points with custom point styling
2091
+ if (ds.showPoints !== false && data.length <= 30 && ds.pointSize) {
2092
+ const pr = ds.pointSize / 2;
2071
2093
  data.forEach((val: number, i: number) => {
2094
+ // Outer stroke
2095
+ if (ds.pointStrokeColor) {
2096
+ const strokeP = Skia.Paint();
2097
+ strokeP.setStyle(PaintStyle.Stroke);
2098
+ strokeP.setStrokeWidth(ds.pointStrokeWidth || 2);
2099
+ try { strokeP.setColor(Skia.Color((ds.pointStrokeColor || '#3b82f6').replace(/\s+/g, ''))); } catch { strokeP.setColor(lineColor); }
2100
+ canvas.drawCircle(getX(i), getY(val), pr, strokeP);
2101
+ }
2102
+ // Fill
2072
2103
  const dotPaint = Skia.Paint();
2073
- try { dotPaint.setColor(lineColor); } catch { dotPaint.setColor(Skia.Color('#3b82f6')); }
2074
- canvas.drawCircle(getX(i), getY(val), 3, dotPaint);
2104
+ try { dotPaint.setColor(Skia.Color((ds.pointFill || '#ffffff').replace(/\s+/g, ''))); } catch { dotPaint.setColor(Skia.Color('#ffffff')); }
2105
+ canvas.drawCircle(getX(i), getY(val), pr - (ds.pointStrokeWidth || 2)/2, dotPaint);
2075
2106
  });
2076
2107
  }
2077
2108
  });
@@ -2107,15 +2138,15 @@ export class ExodeUIEngine {
2107
2138
  });
2108
2139
  }
2109
2140
 
2110
- // Legend
2111
- if (datasets.length > 1 || datasets[0]?.label) {
2112
- datasets.forEach((ds: any, i: number) => {
2113
- if (!ds.label) return;
2141
+ // Legend — only if datasets have labels
2142
+ const datasetsWithLabels = datasets.filter((ds: any) => ds.label);
2143
+ if (datasetsWithLabels.length > 0) {
2144
+ datasetsWithLabels.forEach((ds: any, i: number) => {
2114
2145
  const legX = -w/2 + axisMarginL + i * 80;
2115
2146
  const legY = -h/2 + 10;
2116
2147
  const legPaint = Skia.Paint();
2117
- const lcStr = (typeof ds.lineColor === 'string' ? ds.lineColor : '#3b82f6').replace(/\s+/g, '');
2118
- try { legPaint.setColor(Skia.Color(lcStr)); } catch { legPaint.setColor(Skia.Color('#3b82f6')); }
2148
+ const lcStr = typeof ds.lineColor === 'string' ? ds.lineColor : '#3b82f6';
2149
+ try { legPaint.setColor(Skia.Color(lcStr.replace(/\s+/g, ''))); } catch { legPaint.setColor(Skia.Color('#3b82f6')); }
2119
2150
  canvas.drawRect({ x: legX, y: legY - 5, width: 16, height: 3 }, legPaint);
2120
2151
  canvas.save();
2121
2152
  canvas.translate(legX + 20, legY);