@bian-womp/spark-workbench 0.1.24 → 0.1.26

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/lib/cjs/index.cjs CHANGED
@@ -1756,8 +1756,13 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
1756
1756
  const { registry } = useWorkbenchContext();
1757
1757
  const rf = ReactFlow.useReactFlow();
1758
1758
  const ids = Array.from(registry.nodes.keys());
1759
+ const [query, setQuery] = React.useState("");
1760
+ const q = query.trim().toLowerCase();
1761
+ const filteredIds = q
1762
+ ? ids.filter((id) => id.toLowerCase().includes(q))
1763
+ : ids;
1759
1764
  const root = { __children: {} };
1760
- for (const id of ids) {
1765
+ for (const id of filteredIds) {
1761
1766
  const parts = id.split(".");
1762
1767
  let node = root;
1763
1768
  for (let i = 0; i < parts.length; i++) {
@@ -1768,7 +1773,7 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
1768
1773
  node.__self = id;
1769
1774
  }
1770
1775
  }
1771
- const totalCount = ids.length;
1776
+ const totalCount = filteredIds.length;
1772
1777
  // Ref for focus/outside click handling
1773
1778
  const ref = React.useRef(null);
1774
1779
  // Close on outside click and on ESC
@@ -1792,10 +1797,12 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
1792
1797
  window.removeEventListener("keydown", onKey);
1793
1798
  };
1794
1799
  }, [open, onClose]);
1795
- // Focus for keyboard accessibility
1800
+ // Focus search input when menu opens
1801
+ const inputRef = React.useRef(null);
1796
1802
  React.useEffect(() => {
1797
- if (open)
1798
- ref.current?.focus();
1803
+ if (open) {
1804
+ setTimeout(() => inputRef.current?.focus(), 0);
1805
+ }
1799
1806
  }, [open]);
1800
1807
  if (!open || !clientPos)
1801
1808
  return null;
@@ -1816,14 +1823,19 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
1816
1823
  return (jsxRuntime.jsx("div", { children: entries.map(([key, child]) => {
1817
1824
  const label = key;
1818
1825
  const hasChildren = Object.keys(child.__children).length > 0;
1819
- !!child.__self && !hasChildren;
1820
- return (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "px-2 py-1 text-[11px] uppercase tracking-wide text-gray-400", children: label }), child.__self && (jsxRuntime.jsx("button", { onClick: () => handleClick(child.__self), className: "block w-full text-left px-3 py-1 hover:bg-gray-100 cursor-pointer", title: child.__self, children: child.__self.split(".").slice(-1)[0] })), hasChildren && (jsxRuntime.jsx("div", { className: "pl-2 border-l border-gray-200 ml-2", children: renderTree(child, [...path, key]) }))] }, [...path, key].join(".")));
1826
+ const idKey = [...path, key].join(".");
1827
+ if (!hasChildren) {
1828
+ // Leaf: render only the action button, no group header
1829
+ return child.__self ? (jsxRuntime.jsx("button", { onClick: () => handleClick(child.__self), className: "block w-full text-left px-3 py-1 hover:bg-gray-100 cursor-pointer", title: child.__self, children: label }, idKey)) : null;
1830
+ }
1831
+ // Group: show label, optional action for id at this level, and children
1832
+ return (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "px-2 py-1 text-[11px] uppercase tracking-wide text-gray-400", children: label }), child.__self && (jsxRuntime.jsx("button", { onClick: () => handleClick(child.__self), className: "block w-full text-left px-3 py-1 hover:bg-gray-100 cursor-pointer", title: child.__self, children: label })), jsxRuntime.jsx("div", { className: "pl-2 border-l border-gray-200 ml-2", children: renderTree(child, [...path, key]) })] }, idKey));
1821
1833
  }) }));
1822
1834
  };
1823
1835
  return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-none shadow-lg p-1 min-w-[180px] text-sm text-gray-700", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
1824
1836
  e.preventDefault();
1825
1837
  e.stopPropagation();
1826
- }, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Add Node ", jsxRuntime.jsxs("span", { className: "text-gray-500 font-normal", children: ["(", totalCount, ")"] })] }), jsxRuntime.jsx("div", { className: "max-h-60 overflow-auto", children: renderTree(root) })] }));
1838
+ }, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Add Node", " ", jsxRuntime.jsxs("span", { className: "text-gray-500 font-normal", children: ["(", totalCount, ")"] })] }), jsxRuntime.jsx("div", { className: "px-2 pb-1", children: jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Filter nodes...", className: "w-full border border-gray-300 px-2 py-1 text-sm outline-none focus:border-gray-400", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation() }) }), jsxRuntime.jsx("div", { className: "max-h-60 overflow-auto", children: totalCount > 0 ? (renderTree(root)) : (jsxRuntime.jsx("div", { className: "px-3 py-2 text-gray-400", children: "No matches" })) })] }));
1827
1839
  }
1828
1840
 
1829
1841
  function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
@@ -2113,7 +2125,9 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2113
2125
  const downloadGraph = React.useCallback(() => {
2114
2126
  try {
2115
2127
  const def = wb.export();
2116
- const pretty = JSON.stringify(def, null, 2);
2128
+ const inputs = runner.getInputs(def);
2129
+ const payload = { def, inputs };
2130
+ const pretty = JSON.stringify(payload, null, 2);
2117
2131
  const blob = new Blob([pretty], { type: "application/json" });
2118
2132
  const url = URL.createObjectURL(blob);
2119
2133
  const a = document.createElement("a");
@@ -2130,7 +2144,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2130
2144
  catch (err) {
2131
2145
  alert(String(err?.message ?? err));
2132
2146
  }
2133
- }, [wb]);
2147
+ }, [wb, runner]);
2134
2148
  const hydrateFromBackend = React.useCallback(async (kind, base) => {
2135
2149
  try {
2136
2150
  const transport = kind === "remote-http"
@@ -2280,20 +2294,24 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2280
2294
  value = Number.isFinite(n) ? n : 0;
2281
2295
  break;
2282
2296
  }
2297
+ case "base.float[]": {
2298
+ value = parseArray(String(raw), (x) => Number(x));
2299
+ break;
2300
+ }
2283
2301
  case "base.bool": {
2284
2302
  value = raw === "true" || raw === "1" ? true : false;
2285
2303
  break;
2286
2304
  }
2287
- case "base.string": {
2288
- value = String(raw);
2305
+ case "base.bool[]": {
2306
+ value = parseArray(String(raw), (x) => /^(true|1)$/i.test(x));
2289
2307
  break;
2290
2308
  }
2291
- case "base.float[]": {
2292
- value = parseArray(String(raw), (x) => Number(x));
2309
+ case "base.string": {
2310
+ value = String(raw);
2293
2311
  break;
2294
2312
  }
2295
- case "base.bool[]": {
2296
- value = parseArray(String(raw), (x) => /^(true|1)$/i.test(x));
2313
+ case "base.string[]": {
2314
+ value = parseArray(String(raw), (x) => String(x));
2297
2315
  break;
2298
2316
  }
2299
2317
  case "base.vec3": {