@particle-academy/fancy-slides 0.2.0 → 0.3.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.
package/dist/index.cjs CHANGED
@@ -161,6 +161,22 @@ function weight(w) {
161
161
  return void 0;
162
162
  }
163
163
  function ImageElementRenderer({ element }) {
164
+ const crop = element.crop;
165
+ const fit = element.fit ?? "contain";
166
+ if (crop && crop.w > 0 && crop.h > 0) {
167
+ const inner = {
168
+ position: "absolute",
169
+ left: 0,
170
+ top: 0,
171
+ width: `${1 / crop.w * 100}%`,
172
+ height: `${1 / crop.h * 100}%`,
173
+ transform: `translate(${-crop.x / crop.w * 100}%, ${-crop.y / crop.h * 100}%)`,
174
+ transformOrigin: "top left",
175
+ objectFit: fit,
176
+ display: "block"
177
+ };
178
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "relative", width: "100%", height: "100%", overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: element.src, alt: element.alt ?? "", style: inner, draggable: false }) });
179
+ }
164
180
  return /* @__PURE__ */ jsxRuntime.jsx(
165
181
  "img",
166
182
  {
@@ -169,7 +185,7 @@ function ImageElementRenderer({ element }) {
169
185
  style: {
170
186
  width: "100%",
171
187
  height: "100%",
172
- objectFit: element.fit ?? "contain",
188
+ objectFit: fit,
173
189
  display: "block"
174
190
  },
175
191
  draggable: false
@@ -1321,6 +1337,113 @@ function chartStarterOption(kind) {
1321
1337
  };
1322
1338
  }
1323
1339
  }
1340
+ var CHART_PALETTE = [
1341
+ "#8b5cf6",
1342
+ "#3b82f6",
1343
+ "#10b981",
1344
+ "#f59e0b",
1345
+ "#ef4444",
1346
+ "#ec4899",
1347
+ "#14b8a6",
1348
+ "#6366f1"
1349
+ ];
1350
+ function chartColorAt(index) {
1351
+ return CHART_PALETTE[index % CHART_PALETTE.length];
1352
+ }
1353
+ function isPlainObject(v) {
1354
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1355
+ }
1356
+ function toNumber(v) {
1357
+ const n = typeof v === "number" ? v : parseFloat(String(v));
1358
+ return Number.isFinite(n) ? n : 0;
1359
+ }
1360
+ function chartModelFromOption(option) {
1361
+ if (!isPlainObject(option)) return null;
1362
+ const seriesRaw = option.series;
1363
+ if (!Array.isArray(seriesRaw) || seriesRaw.length === 0) return null;
1364
+ if (!seriesRaw.every(isPlainObject)) return null;
1365
+ const types = seriesRaw.map((s) => String(s.type ?? ""));
1366
+ if (types[0] === "pie") {
1367
+ if (seriesRaw.length !== 1) return null;
1368
+ const data = seriesRaw[0].data;
1369
+ if (!Array.isArray(data)) return null;
1370
+ const slices = [];
1371
+ for (const d of data) {
1372
+ if (!isPlainObject(d)) return null;
1373
+ slices.push({ name: String(d.name ?? ""), value: toNumber(d.value) });
1374
+ }
1375
+ return { kind: "pie", categories: [], series: [], slices };
1376
+ }
1377
+ const cartesian = /* @__PURE__ */ new Set(["bar", "line", "scatter"]);
1378
+ if (!types.every((t) => cartesian.has(t))) return null;
1379
+ if (new Set(types).size !== 1) return null;
1380
+ const baseType = types[0];
1381
+ const isArea = baseType === "line" && seriesRaw.every((s) => isPlainObject(s.areaStyle) || s.areaStyle != null);
1382
+ const kind = baseType === "line" ? isArea ? "area" : "line" : baseType;
1383
+ const xAxis = option.xAxis;
1384
+ const axisData = isPlainObject(xAxis) ? xAxis.data : void 0;
1385
+ const categories = Array.isArray(axisData) ? axisData.map((c) => String(c)) : [];
1386
+ const firstData = seriesRaw[0].data;
1387
+ const valueCount = Array.isArray(firstData) ? firstData.length : 0;
1388
+ const cats = categories.length > 0 ? categories : Array.from({ length: valueCount }, (_, i) => String(i + 1));
1389
+ const series = [];
1390
+ for (const s of seriesRaw) {
1391
+ const data = s.data;
1392
+ if (!Array.isArray(data)) return null;
1393
+ const values = data.map((d) => {
1394
+ if (Array.isArray(d)) return toNumber(d[1]);
1395
+ if (isPlainObject(d)) return toNumber(d.value);
1396
+ return toNumber(d);
1397
+ });
1398
+ series.push({ name: String(s.name ?? "Series"), color: typeof s.itemStyle === "object" && s.itemStyle && isPlainObject(s.itemStyle) ? typeof s.itemStyle.color === "string" ? s.itemStyle.color : void 0 : typeof s.color === "string" ? s.color : void 0, values });
1399
+ }
1400
+ return { kind, categories: cats, series, slices: [] };
1401
+ }
1402
+ function chartOptionFromModel(model) {
1403
+ if (model.kind === "pie") {
1404
+ return {
1405
+ tooltip: { trigger: "item" },
1406
+ legend: { bottom: 0 },
1407
+ color: model.slices.map((_, i) => chartColorAt(i)),
1408
+ series: [
1409
+ {
1410
+ type: "pie",
1411
+ radius: ["40%", "70%"],
1412
+ name: "Segment",
1413
+ data: model.slices.map((s) => ({ name: s.name, value: s.value }))
1414
+ }
1415
+ ]
1416
+ };
1417
+ }
1418
+ const isScatter = model.kind === "scatter";
1419
+ const isArea = model.kind === "area";
1420
+ const seriesType = model.kind === "bar" ? "bar" : model.kind === "scatter" ? "scatter" : "line";
1421
+ const series = model.series.map((s, i) => {
1422
+ const color = s.color ?? chartColorAt(i);
1423
+ const base = {
1424
+ type: seriesType,
1425
+ name: s.name,
1426
+ itemStyle: { color }
1427
+ };
1428
+ if (isScatter) {
1429
+ base.symbolSize = 12;
1430
+ base.data = s.values.map((v, idx) => [idx, v]);
1431
+ } else {
1432
+ base.data = s.values;
1433
+ }
1434
+ if (seriesType === "line") base.smooth = true;
1435
+ if (isArea) base.areaStyle = { color };
1436
+ return base;
1437
+ });
1438
+ return {
1439
+ grid: { top: 24, left: 56, right: 16, bottom: isScatter ? 32 : 40 },
1440
+ tooltip: { trigger: isScatter ? "item" : "axis" },
1441
+ legend: model.series.length > 1 ? { bottom: 0 } : void 0,
1442
+ xAxis: isScatter ? { type: "value" } : { type: "category", data: [...model.categories] },
1443
+ yAxis: { type: "value" },
1444
+ series
1445
+ };
1446
+ }
1324
1447
  function SlideRail({
1325
1448
  slides,
1326
1449
  selectedId,
@@ -1674,7 +1797,35 @@ function TextStyleControls({ element, onPatch }) {
1674
1797
  ] });
1675
1798
  }
1676
1799
  function ImageStyleControls({ element, onPatch }) {
1800
+ const fileRef = react.useRef(null);
1801
+ const crop = element.crop;
1802
+ const onFile = (file) => {
1803
+ if (!file) return;
1804
+ const reader = new FileReader();
1805
+ reader.onload = () => {
1806
+ if (typeof reader.result === "string") onPatch({ src: reader.result });
1807
+ };
1808
+ reader.readAsDataURL(file);
1809
+ };
1810
+ const setCrop = (next) => {
1811
+ const base = crop ?? { x: 0, y: 0, w: 1, h: 1 };
1812
+ onPatch({ crop: { ...base, ...next } });
1813
+ };
1677
1814
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
1815
+ /* @__PURE__ */ jsxRuntime.jsx(
1816
+ "input",
1817
+ {
1818
+ ref: fileRef,
1819
+ type: "file",
1820
+ accept: "image/*",
1821
+ className: "hidden",
1822
+ onChange: (e) => {
1823
+ onFile(e.target.files?.[0]);
1824
+ e.target.value = "";
1825
+ }
1826
+ }
1827
+ ),
1828
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "sm", variant: "ghost", icon: "upload", onClick: () => fileRef.current?.click(), children: "Upload image" }),
1678
1829
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Textarea, { label: "Image URL", value: element.src, onValueChange: (v) => onPatch({ src: v }), rows: 2 }),
1679
1830
  /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Input, { label: "Alt text", value: element.alt ?? "", onChange: (e) => onPatch({ alt: e.target.value }) }),
1680
1831
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1690,7 +1841,17 @@ function ImageStyleControls({ element, onPatch }) {
1690
1841
  value: element.fit ?? "contain",
1691
1842
  onValueChange: (v) => onPatch({ fit: v })
1692
1843
  }
1693
- )
1844
+ ),
1845
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Separator, {}),
1846
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
1847
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Heading, { as: "h4", size: "xs", className: "!uppercase !tracking-wider !text-zinc-500", children: "Crop" }),
1848
+ crop && /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "xs", variant: "ghost", onClick: () => onPatch({ crop: void 0 }), children: "Clear crop" })
1849
+ ] }),
1850
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Slider, { label: "X", value: crop?.x ?? 0, onValueChange: (v) => setCrop({ x: Number(v) }), min: 0, max: 1, step: 0.01, showValue: true }),
1851
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Slider, { label: "Y", value: crop?.y ?? 0, onValueChange: (v) => setCrop({ y: Number(v) }), min: 0, max: 1, step: 0.01, showValue: true }),
1852
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Slider, { label: "Width", value: crop?.w ?? 1, onValueChange: (v) => setCrop({ w: Number(v) }), min: 0.01, max: 1, step: 0.01, showValue: true }),
1853
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Slider, { label: "Height", value: crop?.h ?? 1, onValueChange: (v) => setCrop({ h: Number(v) }), min: 0.01, max: 1, step: 0.01, showValue: true }),
1854
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Text, { size: "xs", className: "!text-zinc-500", children: "Crop is a window into the source image (0..1). Width/height shrink the visible region; X/Y pan it." })
1694
1855
  ] });
1695
1856
  }
1696
1857
  function ShapeStyleControls({ element, onPatch }) {
@@ -1737,54 +1898,254 @@ function CodeStyleControls({ element, onPatch }) {
1737
1898
  ] });
1738
1899
  }
1739
1900
  function ChartStyleControls({ element, onPatch }) {
1901
+ const model = chartModelFromOption(element.option);
1902
+ const writeModel = (m) => onPatch({ option: chartOptionFromModel(m) });
1903
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
1904
+ model ? /* @__PURE__ */ jsxRuntime.jsx(ChartModelEditor, { model, onChange: writeModel }) : /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Text, { size: "sm", className: "rounded-md bg-amber-50 p-2 !text-amber-700 dark:bg-amber-950/40 dark:!text-amber-400", children: "This chart's option is too custom for the visual editor. Edit it as JSON below." }),
1905
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "rounded-md border border-zinc-200 dark:border-zinc-800", children: [
1906
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { className: "cursor-pointer select-none px-2 py-1.5 text-xs font-medium text-zinc-600 dark:text-zinc-400", children: "Advanced \u2014 edit option JSON" }),
1907
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 pt-0", children: /* @__PURE__ */ jsxRuntime.jsx(
1908
+ reactFancy.Textarea,
1909
+ {
1910
+ label: "ECharts option (JSON)",
1911
+ value: JSON.stringify(element.option, null, 2),
1912
+ onValueChange: (v) => {
1913
+ try {
1914
+ onPatch({ option: JSON.parse(v) });
1915
+ } catch {
1916
+ }
1917
+ },
1918
+ rows: 10
1919
+ }
1920
+ ) })
1921
+ ] })
1922
+ ] });
1923
+ }
1924
+ var CHART_TYPE_OPTIONS = [
1925
+ { value: "bar", label: "Bar" },
1926
+ { value: "line", label: "Line" },
1927
+ { value: "area", label: "Area" },
1928
+ { value: "pie", label: "Pie" },
1929
+ { value: "scatter", label: "Scatter" }
1930
+ ];
1931
+ function ChartModelEditor({ model, onChange }) {
1932
+ const setKind = (kind) => {
1933
+ if (kind === model.kind) return;
1934
+ if (kind === "pie") {
1935
+ const first = model.series[0];
1936
+ const slices = model.slices.length ? model.slices : model.categories.length ? model.categories.map((name, i) => ({ name, value: first?.values[i] ?? 0 })) : [{ name: "Slice 1", value: 1 }];
1937
+ onChange({ ...model, kind, slices });
1938
+ return;
1939
+ }
1940
+ if (model.kind === "pie") {
1941
+ const categories = model.slices.length ? model.slices.map((s) => s.name) : ["A", "B", "C"];
1942
+ const values = model.slices.length ? model.slices.map((s) => s.value) : [1, 2, 3];
1943
+ onChange({ ...model, kind, categories, series: [{ name: "Series 1", color: chartColorAt(0), values }] });
1944
+ return;
1945
+ }
1946
+ onChange({ ...model, kind });
1947
+ };
1740
1948
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
1741
- /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Text, { size: "sm", className: "!text-zinc-500", children: "Chart option is JSON \u2014 paste any ECharts option here." }),
1742
1949
  /* @__PURE__ */ jsxRuntime.jsx(
1743
- reactFancy.Textarea,
1950
+ reactFancy.Select,
1744
1951
  {
1745
- label: "ECharts option (JSON)",
1746
- value: JSON.stringify(element.option, null, 2),
1747
- onValueChange: (v) => {
1748
- try {
1749
- onPatch({ option: JSON.parse(v) });
1750
- } catch {
1751
- }
1752
- },
1753
- rows: 10
1952
+ label: "Chart type",
1953
+ list: CHART_TYPE_OPTIONS,
1954
+ value: model.kind,
1955
+ onValueChange: (v) => setKind(v)
1754
1956
  }
1755
- )
1957
+ ),
1958
+ model.kind === "pie" ? /* @__PURE__ */ jsxRuntime.jsx(PieSliceEditor, { model, onChange }) : /* @__PURE__ */ jsxRuntime.jsx(CartesianChartEditor, { model, onChange })
1959
+ ] });
1960
+ }
1961
+ function PieSliceEditor({ model, onChange }) {
1962
+ const slices = model.slices;
1963
+ const update = (i, next) => {
1964
+ const copy = slices.map((s, idx) => idx === i ? { ...s, ...next } : s);
1965
+ onChange({ ...model, slices: copy });
1966
+ };
1967
+ const remove = (i) => onChange({ ...model, slices: slices.filter((_, idx) => idx !== i) });
1968
+ const add = () => onChange({ ...model, slices: [...slices, { name: `Slice ${slices.length + 1}`, value: 0 }] });
1969
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1970
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Heading, { as: "h4", size: "xs", className: "!uppercase !tracking-wider !text-zinc-500", children: "Slices" }),
1971
+ slices.map((s, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2", children: [
1972
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Input, { label: i === 0 ? "Name" : void 0, value: s.name, onChange: (e) => update(i, { name: e.target.value }) }) }),
1973
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-20", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Input, { label: i === 0 ? "Value" : void 0, type: "number", value: String(s.value), onChange: (e) => update(i, { value: parseFloat(e.target.value) || 0 }) }) }),
1974
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "xs", variant: "ghost", color: "red", icon: "x", onClick: () => remove(i), "aria-label": "Remove slice" })
1975
+ ] }, i)),
1976
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "xs", variant: "ghost", icon: "plus", onClick: add, children: "Add slice" })
1977
+ ] });
1978
+ }
1979
+ function CartesianChartEditor({ model, onChange }) {
1980
+ const { categories, series } = model;
1981
+ const updateCategory = (i, label) => {
1982
+ onChange({ ...model, categories: categories.map((c, idx) => idx === i ? label : c) });
1983
+ };
1984
+ const removeCategory = (i) => {
1985
+ onChange({
1986
+ ...model,
1987
+ categories: categories.filter((_, idx) => idx !== i),
1988
+ series: series.map((s) => ({ ...s, values: s.values.filter((_, idx) => idx !== i) }))
1989
+ });
1990
+ };
1991
+ const addCategory = () => {
1992
+ onChange({
1993
+ ...model,
1994
+ categories: [...categories, `Cat ${categories.length + 1}`],
1995
+ series: series.map((s) => ({ ...s, values: [...s.values, 0] }))
1996
+ });
1997
+ };
1998
+ const updateSeries = (si, next) => {
1999
+ onChange({ ...model, series: series.map((s, idx) => idx === si ? { ...s, ...next } : s) });
2000
+ };
2001
+ const updateValue = (si, ci, value) => {
2002
+ onChange({
2003
+ ...model,
2004
+ series: series.map(
2005
+ (s, idx) => idx === si ? { ...s, values: s.values.map((v, vi) => vi === ci ? value : v) } : s
2006
+ )
2007
+ });
2008
+ };
2009
+ const removeSeries = (si) => onChange({ ...model, series: series.filter((_, idx) => idx !== si) });
2010
+ const addSeries = () => onChange({
2011
+ ...model,
2012
+ series: [
2013
+ ...series,
2014
+ { name: `Series ${series.length + 1}`, color: chartColorAt(series.length), values: categories.map(() => 0) }
2015
+ ]
2016
+ });
2017
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
2018
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2019
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Heading, { as: "h4", size: "xs", className: "!uppercase !tracking-wider !text-zinc-500", children: "Categories" }),
2020
+ categories.map((c, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2021
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Input, { value: c, onChange: (e) => updateCategory(i, e.target.value) }) }),
2022
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "xs", variant: "ghost", color: "red", icon: "x", onClick: () => removeCategory(i), "aria-label": "Remove category" })
2023
+ ] }, i)),
2024
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "xs", variant: "ghost", icon: "plus", onClick: addCategory, children: "Add category" })
2025
+ ] }),
2026
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Separator, {}),
2027
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
2028
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Heading, { as: "h4", size: "xs", className: "!uppercase !tracking-wider !text-zinc-500", children: "Series" }),
2029
+ series.map((s, si) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 rounded-md border border-zinc-200 p-2 dark:border-zinc-800", children: [
2030
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2", children: [
2031
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Input, { label: "Name", value: s.name, onChange: (e) => updateSeries(si, { name: e.target.value }) }) }),
2032
+ /* @__PURE__ */ jsxRuntime.jsx(FieldLabel, { label: "Color", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.ColorPicker, { value: s.color ?? chartColorAt(si), onChange: (c) => updateSeries(si, { color: c }) }) }),
2033
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "xs", variant: "ghost", color: "red", icon: "x", onClick: () => removeSeries(si), "aria-label": "Remove series" })
2034
+ ] }),
2035
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2", children: categories.map((c, ci) => /* @__PURE__ */ jsxRuntime.jsx(
2036
+ reactFancy.Input,
2037
+ {
2038
+ label: c,
2039
+ type: "number",
2040
+ value: String(s.values[ci] ?? 0),
2041
+ onChange: (e) => updateValue(si, ci, parseFloat(e.target.value) || 0)
2042
+ },
2043
+ ci
2044
+ )) })
2045
+ ] }, si)),
2046
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "xs", variant: "ghost", icon: "plus", onClick: addSeries, children: "Add series" })
2047
+ ] })
1756
2048
  ] });
1757
2049
  }
1758
2050
  function TableStyleControls({ element, onPatch }) {
2051
+ const columns = element.columns;
2052
+ const rows = element.rows;
2053
+ const nextColKey = () => {
2054
+ const existing = new Set(columns.map((c) => c.key));
2055
+ let n = columns.length + 1;
2056
+ while (existing.has(`col${n}`)) n++;
2057
+ return `col${n}`;
2058
+ };
2059
+ const setColumnLabel = (i, label) => {
2060
+ onPatch({ columns: columns.map((c, idx) => idx === i ? { ...c, label } : c) });
2061
+ };
2062
+ const removeColumn = (i) => {
2063
+ const key = columns[i]?.key;
2064
+ const nextCols = columns.filter((_, idx) => idx !== i);
2065
+ const nextRows = key ? rows.map((r) => {
2066
+ const { [key]: _drop, ...rest } = r;
2067
+ return rest;
2068
+ }) : rows;
2069
+ onPatch({ columns: nextCols, rows: nextRows });
2070
+ };
2071
+ const addColumn = () => {
2072
+ const key = nextColKey();
2073
+ onPatch({
2074
+ columns: [...columns, { key, label: `Column ${columns.length + 1}` }],
2075
+ rows: rows.map((r) => ({ ...r, [key]: "" }))
2076
+ });
2077
+ };
2078
+ const setCell = (rowIdx, key, value) => {
2079
+ onPatch({ rows: rows.map((r, idx) => idx === rowIdx ? { ...r, [key]: value } : r) });
2080
+ };
2081
+ const removeRow = (rowIdx) => onPatch({ rows: rows.filter((_, idx) => idx !== rowIdx) });
2082
+ const addRow = () => {
2083
+ const blank = {};
2084
+ for (const c of columns) blank[c.key] = "";
2085
+ onPatch({ rows: [...rows, blank] });
2086
+ };
1759
2087
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
1760
- /* @__PURE__ */ jsxRuntime.jsx(
1761
- reactFancy.Textarea,
1762
- {
1763
- label: "Columns (JSON)",
1764
- value: JSON.stringify(element.columns, null, 2),
1765
- onValueChange: (v) => {
1766
- try {
1767
- onPatch({ columns: JSON.parse(v) });
1768
- } catch {
2088
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2089
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Heading, { as: "h4", size: "xs", className: "!uppercase !tracking-wider !text-zinc-500", children: "Columns" }),
2090
+ columns.map((c, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2091
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Input, { value: c.label, onChange: (e) => setColumnLabel(i, e.target.value), "aria-label": `Column ${i + 1} label` }) }),
2092
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Text, { size: "xs", className: "!font-mono !text-zinc-400", children: c.key }),
2093
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "xs", variant: "ghost", color: "red", icon: "x", onClick: () => removeColumn(i), "aria-label": "Remove column" })
2094
+ ] }, c.key)),
2095
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "xs", variant: "ghost", icon: "plus", onClick: addColumn, children: "Add column" })
2096
+ ] }),
2097
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Separator, {}),
2098
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2099
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Heading, { as: "h4", size: "xs", className: "!uppercase !tracking-wider !text-zinc-500", children: "Rows" }),
2100
+ columns.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Text, { size: "xs", className: "!text-zinc-500", children: "Add a column to start adding rows." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2101
+ rows.map((r, rowIdx) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 border-b border-zinc-100 pb-2 dark:border-zinc-800", children: [
2102
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid flex-1 grid-cols-1 gap-1", children: columns.map((c) => /* @__PURE__ */ jsxRuntime.jsx(
2103
+ reactFancy.Input,
2104
+ {
2105
+ label: c.label,
2106
+ value: r[c.key] == null ? "" : String(r[c.key]),
2107
+ onChange: (e) => setCell(rowIdx, c.key, e.target.value)
2108
+ },
2109
+ c.key
2110
+ )) }),
2111
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "xs", variant: "ghost", color: "red", icon: "x", onClick: () => removeRow(rowIdx), "aria-label": "Remove row" })
2112
+ ] }, rowIdx)),
2113
+ /* @__PURE__ */ jsxRuntime.jsx(reactFancy.Action, { size: "xs", variant: "ghost", icon: "plus", onClick: addRow, children: "Add row" })
2114
+ ] })
2115
+ ] }),
2116
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "rounded-md border border-zinc-200 dark:border-zinc-800", children: [
2117
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { className: "cursor-pointer select-none px-2 py-1.5 text-xs font-medium text-zinc-600 dark:text-zinc-400", children: "Edit as JSON" }),
2118
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 p-2 pt-0", children: [
2119
+ /* @__PURE__ */ jsxRuntime.jsx(
2120
+ reactFancy.Textarea,
2121
+ {
2122
+ label: "Columns (JSON)",
2123
+ value: JSON.stringify(columns, null, 2),
2124
+ onValueChange: (v) => {
2125
+ try {
2126
+ onPatch({ columns: JSON.parse(v) });
2127
+ } catch {
2128
+ }
2129
+ },
2130
+ rows: 5
1769
2131
  }
1770
- },
1771
- rows: 5
1772
- }
1773
- ),
1774
- /* @__PURE__ */ jsxRuntime.jsx(
1775
- reactFancy.Textarea,
1776
- {
1777
- label: "Rows (JSON)",
1778
- value: JSON.stringify(element.rows, null, 2),
1779
- onValueChange: (v) => {
1780
- try {
1781
- onPatch({ rows: JSON.parse(v) });
1782
- } catch {
2132
+ ),
2133
+ /* @__PURE__ */ jsxRuntime.jsx(
2134
+ reactFancy.Textarea,
2135
+ {
2136
+ label: "Rows (JSON)",
2137
+ value: JSON.stringify(rows, null, 2),
2138
+ onValueChange: (v) => {
2139
+ try {
2140
+ onPatch({ rows: JSON.parse(v) });
2141
+ } catch {
2142
+ }
2143
+ },
2144
+ rows: 8
1783
2145
  }
1784
- },
1785
- rows: 8
1786
- }
1787
- )
2146
+ )
2147
+ ] })
2148
+ ] })
1788
2149
  ] });
1789
2150
  }
1790
2151
  function EmbedStyleControls({ element, onPatch }) {