exodeui-react-native 1.0.3 → 1.0.4

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 +221 -45
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exodeui-react-native",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "React Native runtime for ExodeUI animations",
5
5
  "main": "index.js",
6
6
  "files": [
package/src/engine.ts CHANGED
@@ -1629,31 +1629,41 @@ export class ExodeUIEngine {
1629
1629
  const opts = state?.options || obj.options || {};
1630
1630
  const paint = Skia.Paint();
1631
1631
 
1632
- const isPressed = this.draggingSliderId === obj.id; // Reuse logic for now
1633
- paint.setColor(Skia.Color(isPressed ? (opts.activeBackgroundColor || '#2563eb') : (opts.backgroundColor || '#3b82f6')));
1632
+ const isPressed = this.draggingSliderId === obj.id;
1633
+ // Parse color safely
1634
+ const bgNormal = opts.backgroundColor || opts.background || '#3b82f6';
1635
+ const bgActive = opts.activeBackgroundColor || opts.activeBackground || '#2563eb';
1636
+ const bgStr = typeof bgNormal === 'string' ? bgNormal : '#3b82f6';
1637
+ const bgActiveStr = typeof bgActive === 'string' ? bgActive : '#2563eb';
1638
+ try {
1639
+ paint.setColor(Skia.Color((isPressed ? bgActiveStr : bgStr).replace(/\s+/g, '')));
1640
+ } catch {
1641
+ paint.setColor(Skia.Color('#3b82f6'));
1642
+ }
1634
1643
 
1635
1644
  const r = opts.cornerRadius ?? 8;
1636
1645
  canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -h/2, width: w, height: h }, r, r), paint);
1637
1646
 
1638
- const label = opts.label || { text: opts.text || 'Button', fontSize: 14, color: '#ffffff' };
1639
- if (label && label.text) {
1640
- const textPaint = Skia.Paint();
1641
- textPaint.setColor(Skia.Color(label.color || '#ffffff'));
1642
- const fontSize = label.fontSize || 14;
1643
- const font = this.getFont(fontSize);
1644
-
1645
- // DEBUG: Cyan marker for button text
1646
- const linePaint = Skia.Paint();
1647
- linePaint.setColor(Skia.Color('cyan'));
1648
- linePaint.setStrokeWidth(1);
1649
- canvas.drawLine(-w/4, fontSize/2, w/4, fontSize/2, linePaint);
1650
-
1651
- if (this._renderCount % 60 === 1) {
1652
- console.log(`[ExodeUIEngine] Button text: "${label.text}" at ${-w/4},${fontSize/2} font_size=${font.getSize()}`);
1653
- }
1647
+ const labelObj = opts.label || {};
1648
+ const labelText = labelObj.text || opts.text || opts.title || 'Button';
1649
+ const fontSize = labelObj.fontSize || opts.fontSize || 14;
1650
+ const labelColor = labelObj.color || opts.color || '#ffffff';
1651
+ const font = this.getFont(fontSize, 'Helvetica Neue');
1654
1652
 
1655
- canvas.drawText(label.text, -w/4, fontSize / 2, textPaint, font);
1653
+ const textPaint = Skia.Paint();
1654
+ try {
1655
+ textPaint.setColor(Skia.Color((typeof labelColor === 'string' ? labelColor : '#ffffff').replace(/\s+/g, '')));
1656
+ } catch {
1657
+ textPaint.setColor(Skia.Color('#ffffff'));
1656
1658
  }
1659
+ if (isPressed) textPaint.setAlphaf(0.85);
1660
+
1661
+ // Center text horizontally using getTextWidth
1662
+ const textWidth = font.getTextWidth ? font.getTextWidth(labelText) : 0;
1663
+ canvas.save();
1664
+ canvas.translate(-textWidth / 2, fontSize / 3);
1665
+ canvas.drawText(String(labelText), 0, 0, textPaint, font);
1666
+ canvas.restore();
1657
1667
  }
1658
1668
 
1659
1669
  private renderToggle(canvas: any, obj: any, w: number, h: number) {
@@ -1662,7 +1672,8 @@ export class ExodeUIEngine {
1662
1672
  const checked = opts.checked || false;
1663
1673
 
1664
1674
  const paint = Skia.Paint();
1665
- paint.setColor(Skia.Color(checked ? (opts.activeColor || '#3b82f6') : (opts.inactiveColor || '#374151')));
1675
+ const trackColor = checked ? (opts.activeColor || '#3b82f6') : (opts.inactiveColor || '#374151');
1676
+ try { paint.setColor(Skia.Color(trackColor.replace(/\s+/g, ''))); } catch { paint.setColor(Skia.Color('#374151')); }
1666
1677
 
1667
1678
  const r = h / 2;
1668
1679
  canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -h/2, width: w, height: h }, r, r), paint);
@@ -1672,6 +1683,20 @@ export class ExodeUIEngine {
1672
1683
  const thumbRadius = (h - 8) / 2;
1673
1684
  const thumbX = checked ? (w / 2 - thumbRadius - 4) : (-w / 2 + thumbRadius + 4);
1674
1685
  canvas.drawCircle(thumbX, 0, thumbRadius, thumbPaint);
1686
+
1687
+ // Label text
1688
+ const labelText = opts.label || opts.text || '';
1689
+ if (labelText) {
1690
+ const fontSize = opts.fontSize || 12;
1691
+ const font = this.getFont(fontSize, 'Helvetica Neue');
1692
+ const labelPaint = Skia.Paint();
1693
+ const labelColor = opts.labelColor || opts.color || '#ffffff';
1694
+ try { labelPaint.setColor(Skia.Color((typeof labelColor === 'string' ? labelColor : '#ffffff').replace(/\s+/g, ''))); } catch { labelPaint.setColor(Skia.Color('#ffffff')); }
1695
+ canvas.save();
1696
+ canvas.translate(w/2 + 8, fontSize / 3);
1697
+ canvas.drawText(String(labelText), 0, 0, labelPaint, font);
1698
+ canvas.restore();
1699
+ }
1675
1700
  }
1676
1701
 
1677
1702
  private renderSlider(canvas: any, obj: any, w: number, h: number) {
@@ -1680,23 +1705,40 @@ export class ExodeUIEngine {
1680
1705
  const value = opts.value ?? 50;
1681
1706
  const min = opts.min ?? 0;
1682
1707
  const max = opts.max ?? 100;
1683
- const percentage = (value - min) / (max - min);
1708
+ const percentage = Math.max(0, Math.min(1, (value - min) / (max - min)));
1684
1709
 
1685
1710
  const trackPaint = Skia.Paint();
1686
- trackPaint.setColor(Skia.Color(opts.inactiveColor || '#374151'));
1711
+ const inactiveColor = opts.inactiveColor || opts.trackColor || '#374151';
1712
+ try { trackPaint.setColor(Skia.Color((typeof inactiveColor === 'string' ? inactiveColor : '#374151').replace(/\s+/g, ''))); } catch { trackPaint.setColor(Skia.Color('#374151')); }
1687
1713
  const trackHeight = opts.trackHeight || 4;
1688
- canvas.drawRect({ x: -w/2, y: -trackHeight/2, width: w, height: trackHeight }, trackPaint);
1714
+ canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -trackHeight/2, width: w, height: trackHeight }, trackHeight/2, trackHeight/2), trackPaint);
1689
1715
 
1690
1716
  const activePaint = Skia.Paint();
1691
- activePaint.setColor(Skia.Color(opts.activeColor || '#3b82f6'));
1717
+ const activeColor = opts.activeColor || opts.thumbColor || '#3b82f6';
1718
+ try { activePaint.setColor(Skia.Color((typeof activeColor === 'string' ? activeColor : '#3b82f6').replace(/\s+/g, ''))); } catch { activePaint.setColor(Skia.Color('#3b82f6')); }
1692
1719
  const thumbWidth = opts.thumbWidth ?? 16;
1693
1720
  const travelW = w - thumbWidth;
1694
1721
  const thumbX = -w / 2 + (thumbWidth / 2) + (percentage * travelW);
1695
- canvas.drawRect({ x: -w/2, y: -trackHeight/2, width: thumbX + w/2, height: trackHeight }, activePaint);
1722
+ const activeW = thumbX + w/2;
1723
+ if (activeW > 0) canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -trackHeight/2, width: activeW, height: trackHeight }, trackHeight/2, trackHeight/2), activePaint);
1696
1724
 
1697
1725
  const thumbPaint = Skia.Paint();
1698
- thumbPaint.setColor(Skia.Color(opts.thumbColor || '#ffffff'));
1726
+ const thumbCol = opts.thumbColor || '#ffffff';
1727
+ try { thumbPaint.setColor(Skia.Color((typeof thumbCol === 'string' ? thumbCol : '#ffffff').replace(/\s+/g, ''))); } catch { thumbPaint.setColor(Skia.Color('#ffffff')); }
1699
1728
  canvas.drawCircle(thumbX, 0, thumbWidth / 2, thumbPaint);
1729
+
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));
1737
+ canvas.save();
1738
+ canvas.translate(thumbX, -thumbWidth / 2 - fontSize - 2);
1739
+ canvas.drawText(valText, 0, 0, valPaint, font);
1740
+ canvas.restore();
1741
+ }
1700
1742
  }
1701
1743
 
1702
1744
  private renderDropdown(canvas: any, obj: any, w: number, h: number) {
@@ -1892,19 +1934,33 @@ export class ExodeUIEngine {
1892
1934
  const opts = state?.options || obj.options || {};
1893
1935
 
1894
1936
  const paint = Skia.Paint();
1895
- paint.setColor(Skia.Color(opts.backgroundColor || '#1f2937'));
1937
+ const bgColor = typeof (opts.backgroundColor || opts.background) === 'string' ? (opts.backgroundColor || opts.background || '#1f2937') : '#1f2937';
1938
+ try { paint.setColor(Skia.Color(bgColor.replace(/\s+/g, ''))); } catch { paint.setColor(Skia.Color('#1f2937')); }
1896
1939
  const r = opts.cornerRadius ?? 8;
1897
1940
  canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -h/2, width: w, height: h }, r, r), paint);
1898
1941
 
1942
+ // Border
1943
+ if (opts.borderColor || opts.borderWidth) {
1944
+ const borderP = Skia.Paint();
1945
+ borderP.setStyle(PaintStyle.Stroke);
1946
+ borderP.setStrokeWidth(opts.borderWidth || 1);
1947
+ try { borderP.setColor(Skia.Color((opts.borderColor || '#4b5563').replace(/\s+/g, ''))); } catch { borderP.setColor(Skia.Color('#4b5563')); }
1948
+ canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -h/2, width: w, height: h }, r, r), borderP);
1949
+ }
1950
+
1899
1951
  const textPaint = Skia.Paint();
1900
- textPaint.setColor(Skia.Color(opts.color || '#ffffff'));
1952
+ const textColor = typeof (opts.color || opts.textColor) === 'string' ? (opts.color || opts.textColor || '#ffffff') : '#ffffff';
1953
+ try { textPaint.setColor(Skia.Color(textColor.replace(/\s+/g, ''))); } catch { textPaint.setColor(Skia.Color('#ffffff')); }
1901
1954
  const fontSize = opts.fontSize || 14;
1902
- const font = this.getFont(fontSize);
1903
- const text = opts.text || '';
1955
+ const font = this.getFont(fontSize, 'Helvetica Neue');
1956
+ const text = opts.text || opts.value || '';
1904
1957
  const display = text || opts.placeholder || 'Enter text...';
1905
1958
  if (!text) textPaint.setAlphaf(0.5);
1906
1959
 
1907
- canvas.drawText(display, -w/2 + 15, 5, textPaint, font);
1960
+ canvas.save();
1961
+ canvas.translate(-w/2 + 15, fontSize / 3);
1962
+ canvas.drawText(String(display), 0, 0, textPaint, font);
1963
+ canvas.restore();
1908
1964
  }
1909
1965
 
1910
1966
  private renderSVG(canvas: any, obj: any, w: number, h: number) {
@@ -1923,30 +1979,150 @@ export class ExodeUIEngine {
1923
1979
  private renderLineGraph(canvas: any, geom: any, w: number, h: number) {
1924
1980
  const state = this.objectStates.get(geom.id);
1925
1981
  const datasets = geom.datasets || [];
1982
+ const opts = geom.options || {};
1926
1983
  if (datasets.length === 0) return;
1927
1984
 
1928
- const paint = Skia.Paint();
1929
- paint.setStyle(PaintStyle.Stroke);
1930
- paint.setStrokeWidth(geom.lineWidth || 2);
1985
+ const font = this.getFont(10, 'Helvetica Neue');
1986
+ const axisMarginL = 30; // left margin for y-axis labels
1987
+ const axisMarginB = 20; // bottom margin for x-axis labels
1988
+ const plotW = w - axisMarginL;
1989
+ const plotH = h - axisMarginB;
1990
+
1991
+ // Axis lines
1992
+ const axisPaint = Skia.Paint();
1993
+ axisPaint.setColor(Skia.Color(opts.axisColor || '#4b5563'));
1994
+ axisPaint.setStrokeWidth(1);
1995
+ axisPaint.setStyle(PaintStyle.Stroke);
1996
+ canvas.drawLine(-w/2 + axisMarginL, -h/2, -w/2 + axisMarginL, h/2 - axisMarginB, axisPaint); // Y axis
1997
+ canvas.drawLine(-w/2 + axisMarginL, h/2 - axisMarginB, w/2, h/2 - axisMarginB, axisPaint); // X axis
1998
+
1999
+ // Get global max across all datasets
2000
+ let globalMax = 1;
2001
+ datasets.forEach((ds: any) => {
2002
+ const m = Math.max(...(ds.data || [0]));
2003
+ if (m > globalMax) globalMax = m;
2004
+ });
1931
2005
 
1932
2006
  datasets.forEach((ds: any) => {
1933
2007
  const data = ds.data || [];
1934
2008
  if (data.length < 2) return;
1935
2009
 
1936
- paint.setColor(Skia.Color(ds.lineColor || '#3b82f6'));
1937
- const path = Skia.Path.Make();
1938
-
1939
- const stepX = w / (data.length - 1);
1940
- const max = Math.max(...data, 1);
1941
-
2010
+ const lineColorStr = (typeof ds.lineColor === 'string' ? ds.lineColor : '#3b82f6').replace(/\s+/g, '');
2011
+ let lineColor;
2012
+ try { lineColor = Skia.Color(lineColorStr); } catch { lineColor = Skia.Color('#3b82f6'); }
2013
+
2014
+ const stepX = plotW / (data.length - 1);
2015
+ const getX = (i: number) => -w/2 + axisMarginL + i * stepX;
2016
+ const getY = (val: number) => h/2 - axisMarginB - (val / globalMax) * plotH;
2017
+
2018
+ const smooth = opts.smooth !== false && (ds.smooth !== false);
2019
+
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);
2045
+ }
2046
+
2047
+ // Line
2048
+ const linePaint = Skia.Paint();
2049
+ linePaint.setStyle(PaintStyle.Stroke);
2050
+ linePaint.setStrokeWidth(ds.lineWidth || geom.lineWidth || 2);
2051
+ try { linePaint.setColor(lineColor); } catch { linePaint.setColor(Skia.Color('#3b82f6')); }
2052
+
2053
+ const linePath = Skia.Path.Make();
1942
2054
  data.forEach((val: number, i: number) => {
1943
- const x = -w/2 + i * stepX;
1944
- const y = h/2 - (val / max) * h;
1945
- if (i === 0) path.moveTo(x, y);
1946
- else path.lineTo(x, y);
2055
+ const x = getX(i);
2056
+ const y = getY(val);
2057
+ 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
+ }
1947
2066
  });
1948
- canvas.drawPath(path, paint);
2067
+ canvas.drawPath(linePath, linePaint);
2068
+
2069
+ // Data points
2070
+ if (ds.showPoints !== false && data.length <= 20) {
2071
+ data.forEach((val: number, i: number) => {
2072
+ const dotPaint = Skia.Paint();
2073
+ try { dotPaint.setColor(lineColor); } catch { dotPaint.setColor(Skia.Color('#3b82f6')); }
2074
+ canvas.drawCircle(getX(i), getY(val), 3, dotPaint);
2075
+ });
2076
+ }
1949
2077
  });
2078
+
2079
+ // X-axis labels
2080
+ const xLabels = opts.xLabels || geom.xLabels || [];
2081
+ const labelPaint = Skia.Paint();
2082
+ labelPaint.setColor(Skia.Color(opts.labelColor || '#9ca3af'));
2083
+ const firstDs = datasets[0];
2084
+ const dataLen = (firstDs?.data || []).length;
2085
+ if (xLabels.length > 0 && dataLen > 0) {
2086
+ const stepX = plotW / (dataLen - 1);
2087
+ xLabels.forEach((label: string, i: number) => {
2088
+ const x = -w/2 + axisMarginL + i * stepX;
2089
+ canvas.save();
2090
+ canvas.translate(x, h/2 - axisMarginB + 12);
2091
+ canvas.drawText(String(label), 0, 0, labelPaint, font);
2092
+ canvas.restore();
2093
+ });
2094
+ }
2095
+
2096
+ // Y-axis labels
2097
+ const yLabels = opts.yLabels || geom.yLabels || [];
2098
+ if (yLabels.length > 0) {
2099
+ yLabels.forEach((label: string, i: number) => {
2100
+ const y = h/2 - axisMarginB - (i / (yLabels.length - 1)) * plotH;
2101
+ canvas.save();
2102
+ canvas.translate(-w/2 + axisMarginL - 4, y);
2103
+ const lp = Skia.Paint();
2104
+ lp.setColor(Skia.Color(opts.labelColor || '#9ca3af'));
2105
+ canvas.drawText(String(label), -(font.getTextWidth ? font.getTextWidth(String(label)) : 24), 0, lp, font);
2106
+ canvas.restore();
2107
+ });
2108
+ }
2109
+
2110
+ // Legend
2111
+ if (datasets.length > 1 || datasets[0]?.label) {
2112
+ datasets.forEach((ds: any, i: number) => {
2113
+ if (!ds.label) return;
2114
+ const legX = -w/2 + axisMarginL + i * 80;
2115
+ const legY = -h/2 + 10;
2116
+ 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')); }
2119
+ canvas.drawRect({ x: legX, y: legY - 5, width: 16, height: 3 }, legPaint);
2120
+ canvas.save();
2121
+ canvas.translate(legX + 20, legY);
2122
+ canvas.drawText(String(ds.label), 0, 0, legPaint, font);
2123
+ canvas.restore();
2124
+ });
2125
+ }
1950
2126
  }
1951
2127
  private renderShape(canvas: SkCanvas, obj: ShapeObject, w: number, h: number) {
1952
2128
  const state = this.objectStates.get(obj.id);