@ship-it-ui/cytoscape 0.0.2
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/LICENSE +21 -0
- package/dist/index.cjs +362 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +177 -0
- package/dist/index.d.ts +177 -0
- package/dist/index.js +333 -0
- package/dist/index.js.map +1 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ship-it-ops
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/index.ts
|
|
23
|
+
var index_exports = {};
|
|
24
|
+
__export(index_exports, {
|
|
25
|
+
GRAPH_CANVAS_CLASS: () => GRAPH_CANVAS_CLASS,
|
|
26
|
+
GraphCanvas: () => GraphCanvas,
|
|
27
|
+
buildShipItStylesheet: () => buildShipItStylesheet,
|
|
28
|
+
readThemeTokens: () => readThemeTokens,
|
|
29
|
+
resolveColorReference: () => resolveColorReference,
|
|
30
|
+
resolveCssVar: () => resolveCssVar,
|
|
31
|
+
resolveEntityColor: () => resolveEntityColor,
|
|
32
|
+
useShipItStylesheet: () => useShipItStylesheet
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
|
|
36
|
+
// src/stylesheet.ts
|
|
37
|
+
var import_shipit2 = require("@ship-it-ui/shipit");
|
|
38
|
+
|
|
39
|
+
// src/theme-tokens.ts
|
|
40
|
+
var import_shipit = require("@ship-it-ui/shipit");
|
|
41
|
+
var DEFAULT_FALLBACK = {
|
|
42
|
+
bg: "#0a0a0a",
|
|
43
|
+
panel: "#0f0f0f",
|
|
44
|
+
"panel-2": "#161616",
|
|
45
|
+
border: "#262626",
|
|
46
|
+
"border-strong": "#383838",
|
|
47
|
+
text: "#fafafa",
|
|
48
|
+
"text-muted": "#a3a3a3",
|
|
49
|
+
"text-dim": "#737373",
|
|
50
|
+
accent: "#3b82f6",
|
|
51
|
+
ok: "#10b981",
|
|
52
|
+
warn: "#f59e0b",
|
|
53
|
+
err: "#ef4444",
|
|
54
|
+
purple: "#a855f7",
|
|
55
|
+
pink: "#ec4899"
|
|
56
|
+
};
|
|
57
|
+
function resolveCssVar(name, fallback = "") {
|
|
58
|
+
if (typeof document === "undefined") return fallback;
|
|
59
|
+
const raw = getComputedStyle(document.documentElement).getPropertyValue(name);
|
|
60
|
+
const trimmed = raw.trim();
|
|
61
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
62
|
+
}
|
|
63
|
+
function readThemeTokens() {
|
|
64
|
+
return {
|
|
65
|
+
bg: resolveCssVar("--color-bg", DEFAULT_FALLBACK.bg),
|
|
66
|
+
panel: resolveCssVar("--color-panel", DEFAULT_FALLBACK.panel),
|
|
67
|
+
panel2: resolveCssVar("--color-panel-2", DEFAULT_FALLBACK["panel-2"]),
|
|
68
|
+
border: resolveCssVar("--color-border", DEFAULT_FALLBACK.border),
|
|
69
|
+
borderStrong: resolveCssVar("--color-border-strong", DEFAULT_FALLBACK["border-strong"]),
|
|
70
|
+
text: resolveCssVar("--color-text", DEFAULT_FALLBACK.text),
|
|
71
|
+
textMuted: resolveCssVar("--color-text-muted", DEFAULT_FALLBACK["text-muted"]),
|
|
72
|
+
textDim: resolveCssVar("--color-text-dim", DEFAULT_FALLBACK["text-dim"]),
|
|
73
|
+
accent: resolveCssVar("--color-accent", DEFAULT_FALLBACK.accent),
|
|
74
|
+
ok: resolveCssVar("--color-ok", DEFAULT_FALLBACK.ok),
|
|
75
|
+
warn: resolveCssVar("--color-warn", DEFAULT_FALLBACK.warn),
|
|
76
|
+
err: resolveCssVar("--color-err", DEFAULT_FALLBACK.err),
|
|
77
|
+
purple: resolveCssVar("--color-purple", DEFAULT_FALLBACK.purple),
|
|
78
|
+
pink: resolveCssVar("--color-pink", DEFAULT_FALLBACK.pink)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function resolveEntityColor(type, palette) {
|
|
82
|
+
const meta = (0, import_shipit.getEntityTypeMeta)(type);
|
|
83
|
+
return resolveColorReference(meta.colorVar, palette);
|
|
84
|
+
}
|
|
85
|
+
function parseColorVarName(value) {
|
|
86
|
+
let i = 0;
|
|
87
|
+
while (i < value.length && isWhitespace(value.charCodeAt(i))) i++;
|
|
88
|
+
if (!value.startsWith("var(", i)) return void 0;
|
|
89
|
+
i += 4;
|
|
90
|
+
while (i < value.length && isWhitespace(value.charCodeAt(i))) i++;
|
|
91
|
+
if (!value.startsWith("--color-", i)) return void 0;
|
|
92
|
+
i += 8;
|
|
93
|
+
const start = i;
|
|
94
|
+
while (i < value.length) {
|
|
95
|
+
const c = value.charCodeAt(i);
|
|
96
|
+
if (c === CC_COMMA || c === CC_PAREN_CLOSE || isWhitespace(c)) break;
|
|
97
|
+
i++;
|
|
98
|
+
}
|
|
99
|
+
if (i === start) return void 0;
|
|
100
|
+
const close = value.indexOf(")", i);
|
|
101
|
+
if (close === -1) return void 0;
|
|
102
|
+
return value.slice(start, i);
|
|
103
|
+
}
|
|
104
|
+
var CC_COMMA = 44;
|
|
105
|
+
var CC_PAREN_CLOSE = 41;
|
|
106
|
+
function isWhitespace(cc) {
|
|
107
|
+
return cc === 32 || cc === 9 || cc === 10 || cc === 13 || cc === 12 || cc === 11;
|
|
108
|
+
}
|
|
109
|
+
function resolveColorReference(value, palette) {
|
|
110
|
+
const key = parseColorVarName(value);
|
|
111
|
+
if (key === void 0) return value;
|
|
112
|
+
switch (key) {
|
|
113
|
+
case "bg":
|
|
114
|
+
return palette.bg;
|
|
115
|
+
case "panel":
|
|
116
|
+
return palette.panel;
|
|
117
|
+
case "panel-2":
|
|
118
|
+
return palette.panel2;
|
|
119
|
+
case "border":
|
|
120
|
+
return palette.border;
|
|
121
|
+
case "border-strong":
|
|
122
|
+
return palette.borderStrong;
|
|
123
|
+
case "text":
|
|
124
|
+
return palette.text;
|
|
125
|
+
case "text-muted":
|
|
126
|
+
return palette.textMuted;
|
|
127
|
+
case "text-dim":
|
|
128
|
+
return palette.textDim;
|
|
129
|
+
case "accent":
|
|
130
|
+
return palette.accent;
|
|
131
|
+
case "ok":
|
|
132
|
+
return palette.ok;
|
|
133
|
+
case "warn":
|
|
134
|
+
return palette.warn;
|
|
135
|
+
case "err":
|
|
136
|
+
return palette.err;
|
|
137
|
+
case "purple":
|
|
138
|
+
return palette.purple;
|
|
139
|
+
case "pink":
|
|
140
|
+
return palette.pink;
|
|
141
|
+
default:
|
|
142
|
+
return palette.accent;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/stylesheet.ts
|
|
147
|
+
function buildShipItStylesheet(options = {}) {
|
|
148
|
+
const palette = options.palette ?? readThemeTokens();
|
|
149
|
+
const color = (cssVar) => resolveColorReference(cssVar, palette);
|
|
150
|
+
const base = [
|
|
151
|
+
{
|
|
152
|
+
selector: "node",
|
|
153
|
+
style: {
|
|
154
|
+
"background-color": palette.panel,
|
|
155
|
+
"border-width": 1.5,
|
|
156
|
+
"border-color": palette.accent,
|
|
157
|
+
"border-opacity": 1,
|
|
158
|
+
label: "data(label)",
|
|
159
|
+
color: palette.textMuted,
|
|
160
|
+
"font-family": "var(--font-mono, monospace)",
|
|
161
|
+
"font-size": 10,
|
|
162
|
+
"text-valign": "bottom",
|
|
163
|
+
"text-halign": "center",
|
|
164
|
+
"text-margin-y": 6,
|
|
165
|
+
width: 36,
|
|
166
|
+
height: 36,
|
|
167
|
+
shape: "round-rectangle"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
// One selector per entity type registered with @ship-it-ui/shipit. Built-in
|
|
171
|
+
// types are seeded automatically; custom types registered via
|
|
172
|
+
// `registerEntityType(...)` pick up their `colorVar` here without a docs
|
|
173
|
+
// patch or a forked stylesheet.
|
|
174
|
+
...(0, import_shipit2.listEntityTypes)().map(([type, meta]) => ({
|
|
175
|
+
selector: `node[entityType = "${escapeCytoscapeAttr(type)}"]`,
|
|
176
|
+
style: { "border-color": color(meta.colorVar) }
|
|
177
|
+
})),
|
|
178
|
+
{
|
|
179
|
+
selector: "node:selected",
|
|
180
|
+
style: {
|
|
181
|
+
"border-width": 3,
|
|
182
|
+
"overlay-color": palette.accent,
|
|
183
|
+
"overlay-opacity": 0.15,
|
|
184
|
+
"overlay-padding": 4
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
selector: "node.graph-canvas\\:path",
|
|
189
|
+
style: { "border-color": palette.purple }
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
selector: "node.graph-canvas\\:dim",
|
|
193
|
+
style: { opacity: 0.35 }
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
selector: "edge",
|
|
197
|
+
style: {
|
|
198
|
+
width: 1,
|
|
199
|
+
"line-color": palette.border,
|
|
200
|
+
"target-arrow-color": palette.border,
|
|
201
|
+
"target-arrow-shape": "triangle",
|
|
202
|
+
"arrow-scale": 0.8,
|
|
203
|
+
"curve-style": "bezier"
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
selector: "edge.graph-canvas\\:path",
|
|
208
|
+
style: {
|
|
209
|
+
"line-color": palette.purple,
|
|
210
|
+
"target-arrow-color": palette.purple,
|
|
211
|
+
width: 1.5
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
selector: "edge.graph-canvas\\:dim",
|
|
216
|
+
style: { opacity: 0.25 }
|
|
217
|
+
}
|
|
218
|
+
];
|
|
219
|
+
return [...base, ...options.extra ?? []];
|
|
220
|
+
}
|
|
221
|
+
var GRAPH_CANVAS_CLASS = {
|
|
222
|
+
path: "graph-canvas:path",
|
|
223
|
+
dim: "graph-canvas:dim"
|
|
224
|
+
};
|
|
225
|
+
function escapeCytoscapeAttr(value) {
|
|
226
|
+
return value.replace(/[\\"]/g, (c) => `\\${c}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/useShipItStylesheet.ts
|
|
230
|
+
var import_react = require("react");
|
|
231
|
+
function useShipItStylesheet(cyRef, options = {}) {
|
|
232
|
+
const { observe = true, palette, extra } = options;
|
|
233
|
+
const buildOptions = (0, import_react.useMemo)(
|
|
234
|
+
() => ({ palette, extra }),
|
|
235
|
+
[palette, extra]
|
|
236
|
+
);
|
|
237
|
+
const apply = (0, import_react.useCallback)(() => {
|
|
238
|
+
const cy = cyRef.current;
|
|
239
|
+
if (!cy) return;
|
|
240
|
+
cy.style(buildShipItStylesheet(buildOptions)).update();
|
|
241
|
+
}, [cyRef, buildOptions]);
|
|
242
|
+
(0, import_react.useEffect)(() => {
|
|
243
|
+
apply();
|
|
244
|
+
if (!observe || typeof document === "undefined") return void 0;
|
|
245
|
+
const observer = new MutationObserver(apply);
|
|
246
|
+
observer.observe(document.documentElement, {
|
|
247
|
+
attributes: true,
|
|
248
|
+
attributeFilter: ["data-theme"]
|
|
249
|
+
});
|
|
250
|
+
return () => observer.disconnect();
|
|
251
|
+
}, [apply, observe]);
|
|
252
|
+
return { refresh: apply };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/GraphCanvas.tsx
|
|
256
|
+
var import_react2 = require("react");
|
|
257
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
258
|
+
var DEFAULT_LAYOUT = { name: "preset" };
|
|
259
|
+
var GraphCanvas = (0, import_react2.forwardRef)(function GraphCanvas2({
|
|
260
|
+
engine,
|
|
261
|
+
elements,
|
|
262
|
+
layout = DEFAULT_LAYOUT,
|
|
263
|
+
onSelect,
|
|
264
|
+
onClearSelection,
|
|
265
|
+
onNodeHover,
|
|
266
|
+
onNodeLeave,
|
|
267
|
+
inspector,
|
|
268
|
+
styleOptions,
|
|
269
|
+
className,
|
|
270
|
+
"aria-label": ariaLabel = "Graph canvas",
|
|
271
|
+
...props
|
|
272
|
+
}, forwardedRef) {
|
|
273
|
+
const containerRef = (0, import_react2.useRef)(null);
|
|
274
|
+
const cyRef = (0, import_react2.useRef)(null);
|
|
275
|
+
(0, import_react2.useEffect)(() => {
|
|
276
|
+
if (!containerRef.current) return void 0;
|
|
277
|
+
const cy = engine({
|
|
278
|
+
container: containerRef.current,
|
|
279
|
+
elements,
|
|
280
|
+
layout,
|
|
281
|
+
style: buildShipItStylesheet(styleOptions)
|
|
282
|
+
});
|
|
283
|
+
cyRef.current = cy;
|
|
284
|
+
return () => {
|
|
285
|
+
cy.destroy();
|
|
286
|
+
cyRef.current = null;
|
|
287
|
+
};
|
|
288
|
+
}, [engine]);
|
|
289
|
+
(0, import_react2.useEffect)(() => {
|
|
290
|
+
const cy = cyRef.current;
|
|
291
|
+
if (!cy) return;
|
|
292
|
+
cy.json({ elements });
|
|
293
|
+
}, [elements]);
|
|
294
|
+
(0, import_react2.useEffect)(() => {
|
|
295
|
+
const cy = cyRef.current;
|
|
296
|
+
if (!cy) return;
|
|
297
|
+
cy.layout(layout).run();
|
|
298
|
+
}, [layout]);
|
|
299
|
+
const { refresh: refreshStyles } = useShipItStylesheet(cyRef, styleOptions);
|
|
300
|
+
(0, import_react2.useEffect)(() => {
|
|
301
|
+
const cy = cyRef.current;
|
|
302
|
+
if (!cy) return void 0;
|
|
303
|
+
const handleSelect = (event) => {
|
|
304
|
+
if (event.target?.isNode?.()) {
|
|
305
|
+
onSelect?.(event.target);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
const handleBackgroundTap = (event) => {
|
|
309
|
+
if (event.target === cy) onClearSelection?.();
|
|
310
|
+
};
|
|
311
|
+
const handleEnter = (event) => {
|
|
312
|
+
onNodeHover?.(event.target);
|
|
313
|
+
};
|
|
314
|
+
const handleLeave = () => onNodeLeave?.();
|
|
315
|
+
cy.on("tap", "node", handleSelect);
|
|
316
|
+
cy.on("tap", handleBackgroundTap);
|
|
317
|
+
cy.on("mouseover", "node", handleEnter);
|
|
318
|
+
cy.on("mouseout", "node", handleLeave);
|
|
319
|
+
return () => {
|
|
320
|
+
cy.off("tap", "node", handleSelect);
|
|
321
|
+
cy.off("tap", handleBackgroundTap);
|
|
322
|
+
cy.off("mouseover", "node", handleEnter);
|
|
323
|
+
cy.off("mouseout", "node", handleLeave);
|
|
324
|
+
};
|
|
325
|
+
}, [engine, onSelect, onClearSelection, onNodeHover, onNodeLeave]);
|
|
326
|
+
(0, import_react2.useImperativeHandle)(
|
|
327
|
+
forwardedRef,
|
|
328
|
+
() => ({
|
|
329
|
+
get cy() {
|
|
330
|
+
return cyRef.current;
|
|
331
|
+
},
|
|
332
|
+
refreshStyles
|
|
333
|
+
}),
|
|
334
|
+
[refreshStyles]
|
|
335
|
+
);
|
|
336
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
337
|
+
"div",
|
|
338
|
+
{
|
|
339
|
+
role: "region",
|
|
340
|
+
"aria-label": ariaLabel,
|
|
341
|
+
className: ["relative h-full w-full", className].filter(Boolean).join(" "),
|
|
342
|
+
...props,
|
|
343
|
+
children: [
|
|
344
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, className: "absolute inset-0" }),
|
|
345
|
+
inspector && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute top-4 right-4 z-10", children: inspector })
|
|
346
|
+
]
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
});
|
|
350
|
+
GraphCanvas.displayName = "GraphCanvas";
|
|
351
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
352
|
+
0 && (module.exports = {
|
|
353
|
+
GRAPH_CANVAS_CLASS,
|
|
354
|
+
GraphCanvas,
|
|
355
|
+
buildShipItStylesheet,
|
|
356
|
+
readThemeTokens,
|
|
357
|
+
resolveColorReference,
|
|
358
|
+
resolveCssVar,
|
|
359
|
+
resolveEntityColor,
|
|
360
|
+
useShipItStylesheet
|
|
361
|
+
});
|
|
362
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/stylesheet.ts","../src/theme-tokens.ts","../src/useShipItStylesheet.ts","../src/GraphCanvas.tsx"],"sourcesContent":["/**\n * @ship-it-ui/cytoscape — Cytoscape adapter for the Ship-It design system.\n *\n * Three layered exports:\n * - {@link buildShipItStylesheet} — token-driven stylesheet array.\n * - {@link useShipItStylesheet} — theme-aware re-resolver hook.\n * - {@link GraphCanvas} — `<GraphCanvas>` React wrapper that\n * owns the data-theme ↔ stylesheet ↔\n * inspector dance.\n *\n * Cytoscape is a peer dependency — pass `engine={cytoscape}` so the consumer\n * controls the version and any registered extensions.\n */\n\nexport {\n buildShipItStylesheet,\n GRAPH_CANVAS_CLASS,\n type BuildStylesheetOptions,\n type ShipItStylesheetBlock,\n} from './stylesheet';\n\nexport {\n useShipItStylesheet,\n type UseShipItStylesheetOptions,\n type UseShipItStylesheetReturn,\n} from './useShipItStylesheet';\n\nexport {\n GraphCanvas,\n type GraphCanvasProps,\n type GraphCanvasHandle,\n type CytoscapeEngine,\n} from './GraphCanvas';\n\nexport {\n readThemeTokens,\n resolveCssVar,\n resolveColorReference,\n resolveEntityColor,\n type ThemeTokenPalette,\n} from './theme-tokens';\n","import { listEntityTypes } from '@ship-it-ui/shipit';\nimport type cytoscape from 'cytoscape';\n\nimport { readThemeTokens, resolveColorReference, type ThemeTokenPalette } from './theme-tokens';\n\n/**\n * Build a Cytoscape stylesheet from the live design tokens. The result is a\n * plain JSON array suitable for `cytoscape({ style: ... })` — re-run after a\n * `data-theme` change to pick up the new palette.\n *\n * The stylesheet is opinionated:\n * - Nodes are square-ish rounded glyphs colored by `data(entityType)`.\n * - Edges are token-colored thin lines with arrowheads.\n * - Selected / on-path / dimmed states are driven by class names that the\n * consumer toggles (`graph-canvas:selected`, `graph-canvas:path`,\n * `graph-canvas:dim`).\n *\n * Pass `palette` to override the token read — useful for SSR or tests.\n */\n\nexport interface BuildStylesheetOptions {\n /** Pre-resolved palette. When omitted, tokens are read from the document. */\n palette?: ThemeTokenPalette;\n /**\n * Additional entries appended to the stylesheet — handy for app-specific\n * selectors without forking the builder.\n */\n extra?: ReadonlyArray<cytoscape.StylesheetJsonBlock>;\n}\n\n// Re-export the block type so consumers can declare typed `extra` entries.\nexport type ShipItStylesheetBlock = cytoscape.StylesheetJsonBlock;\n\nexport function buildShipItStylesheet(\n options: BuildStylesheetOptions = {},\n): cytoscape.StylesheetJson {\n const palette = options.palette ?? readThemeTokens();\n const color = (cssVar: string) => resolveColorReference(cssVar, palette);\n\n const base: cytoscape.StylesheetJsonBlock[] = [\n {\n selector: 'node',\n style: {\n 'background-color': palette.panel,\n 'border-width': 1.5,\n 'border-color': palette.accent,\n 'border-opacity': 1,\n label: 'data(label)',\n color: palette.textMuted,\n 'font-family': 'var(--font-mono, monospace)',\n 'font-size': 10,\n 'text-valign': 'bottom',\n 'text-halign': 'center',\n 'text-margin-y': 6,\n width: 36,\n height: 36,\n shape: 'round-rectangle',\n },\n },\n // One selector per entity type registered with @ship-it-ui/shipit. Built-in\n // types are seeded automatically; custom types registered via\n // `registerEntityType(...)` pick up their `colorVar` here without a docs\n // patch or a forked stylesheet.\n ...listEntityTypes().map<cytoscape.StylesheetJsonBlock>(([type, meta]) => ({\n selector: `node[entityType = \"${escapeCytoscapeAttr(type)}\"]`,\n style: { 'border-color': color(meta.colorVar) },\n })),\n {\n selector: 'node:selected',\n style: {\n 'border-width': 3,\n 'overlay-color': palette.accent,\n 'overlay-opacity': 0.15,\n 'overlay-padding': 4,\n },\n },\n {\n selector: 'node.graph-canvas\\\\:path',\n style: { 'border-color': palette.purple },\n },\n {\n selector: 'node.graph-canvas\\\\:dim',\n style: { opacity: 0.35 },\n },\n {\n selector: 'edge',\n style: {\n width: 1,\n 'line-color': palette.border,\n 'target-arrow-color': palette.border,\n 'target-arrow-shape': 'triangle',\n 'arrow-scale': 0.8,\n 'curve-style': 'bezier',\n },\n },\n {\n selector: 'edge.graph-canvas\\\\:path',\n style: {\n 'line-color': palette.purple,\n 'target-arrow-color': palette.purple,\n width: 1.5,\n },\n },\n {\n selector: 'edge.graph-canvas\\\\:dim',\n style: { opacity: 0.25 },\n },\n ];\n\n return [...base, ...(options.extra ?? [])] as cytoscape.StylesheetJson;\n}\n\n/**\n * Convenience class names the stylesheet recognizes. Toggle these on Cytoscape\n * nodes / edges to drive the on-path and dimmed visuals.\n */\nexport const GRAPH_CANVAS_CLASS = {\n path: 'graph-canvas:path',\n dim: 'graph-canvas:dim',\n} as const;\n\n// Cytoscape's attribute selector accepts a double-quoted string. Escape\n// embedded backslashes and double quotes so a malformed (or hostile)\n// registered key can't break out of the selector. The grammar\n// (https://js.cytoscape.org/#selectors/data) is more permissive than CSS, but\n// these two characters are the only ones that would change selector semantics.\nfunction escapeCytoscapeAttr(value: string): string {\n return value.replace(/[\\\\\"]/g, (c) => `\\\\${c}`);\n}\n","/**\n * Token resolution helpers. The Ship-It design system stores colors as CSS\n * custom properties on `<html>` (`--color-accent`, `--color-bg`, …). Cytoscape\n * renders to a canvas / SVG layer outside Tailwind, so those vars never\n * resolve — we read the *computed* values at runtime and feed them into the\n * stylesheet builder as concrete color strings.\n *\n * Two layers:\n * 1. `resolveCssVar` — single-var reader with a fallback.\n * 2. `readThemeTokens` — pulls every color the stylesheet uses, returning a\n * flat `Record<string, string>` keyed by short name (no `--color-`\n * prefix). Re-running this after a `data-theme` flip yields a fresh\n * palette.\n */\n\nimport { getEntityTypeMeta, type EntityType } from '@ship-it-ui/shipit';\n\nconst DEFAULT_FALLBACK: Record<string, string> = {\n bg: '#0a0a0a',\n panel: '#0f0f0f',\n 'panel-2': '#161616',\n border: '#262626',\n 'border-strong': '#383838',\n text: '#fafafa',\n 'text-muted': '#a3a3a3',\n 'text-dim': '#737373',\n accent: '#3b82f6',\n ok: '#10b981',\n warn: '#f59e0b',\n err: '#ef4444',\n purple: '#a855f7',\n pink: '#ec4899',\n};\n\n/**\n * Read a single CSS variable from the document root and return its trimmed\n * computed value, falling back to `fallback` when the document is missing\n * (SSR) or the variable is unset.\n */\nexport function resolveCssVar(name: string, fallback = ''): string {\n if (typeof document === 'undefined') return fallback;\n const raw = getComputedStyle(document.documentElement).getPropertyValue(name);\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : fallback;\n}\n\nexport interface ThemeTokenPalette {\n /** Surface backgrounds. */\n bg: string;\n panel: string;\n panel2: string;\n /** Hairline + emphasis border. */\n border: string;\n borderStrong: string;\n /** Foreground tiers. */\n text: string;\n textMuted: string;\n textDim: string;\n /** Brand + status. */\n accent: string;\n ok: string;\n warn: string;\n err: string;\n /** Extras the graph uses for entity-type ring colors. */\n purple: string;\n pink: string;\n}\n\n/** Read the canonical Ship-It color tokens from the document root. */\nexport function readThemeTokens(): ThemeTokenPalette {\n return {\n bg: resolveCssVar('--color-bg', DEFAULT_FALLBACK.bg),\n panel: resolveCssVar('--color-panel', DEFAULT_FALLBACK.panel),\n panel2: resolveCssVar('--color-panel-2', DEFAULT_FALLBACK['panel-2']),\n border: resolveCssVar('--color-border', DEFAULT_FALLBACK.border),\n borderStrong: resolveCssVar('--color-border-strong', DEFAULT_FALLBACK['border-strong']),\n text: resolveCssVar('--color-text', DEFAULT_FALLBACK.text),\n textMuted: resolveCssVar('--color-text-muted', DEFAULT_FALLBACK['text-muted']),\n textDim: resolveCssVar('--color-text-dim', DEFAULT_FALLBACK['text-dim']),\n accent: resolveCssVar('--color-accent', DEFAULT_FALLBACK.accent),\n ok: resolveCssVar('--color-ok', DEFAULT_FALLBACK.ok),\n warn: resolveCssVar('--color-warn', DEFAULT_FALLBACK.warn),\n err: resolveCssVar('--color-err', DEFAULT_FALLBACK.err),\n purple: resolveCssVar('--color-purple', DEFAULT_FALLBACK.purple),\n pink: resolveCssVar('--color-pink', DEFAULT_FALLBACK.pink),\n };\n}\n\n/**\n * Resolve the concrete color for a registered entity type. Reads the type's\n * `colorVar` (a `var(--color-…)` string) and looks the value up in the\n * palette. Falls back to the palette's `accent` color when the var is\n * malformed or unknown.\n */\nexport function resolveEntityColor(type: EntityType, palette: ThemeTokenPalette): string {\n const meta = getEntityTypeMeta(type);\n return resolveColorReference(meta.colorVar, palette);\n}\n\n/**\n * Extract `foo` from `var(--color-foo)` or `var(--color-foo, fallback)`.\n * Returns `undefined` if the input doesn't match. O(n), no backtracking.\n */\nfunction parseColorVarName(value: string): string | undefined {\n // Strip surrounding whitespace once. `String#trim` is linear, not a regex.\n let i = 0;\n while (i < value.length && isWhitespace(value.charCodeAt(i))) i++;\n if (!value.startsWith('var(', i)) return undefined;\n i += 4;\n // Skip whitespace inside the parens.\n while (i < value.length && isWhitespace(value.charCodeAt(i))) i++;\n if (!value.startsWith('--color-', i)) return undefined;\n i += 8;\n // Read the name: stop at the first whitespace, comma, or `)`.\n const start = i;\n while (i < value.length) {\n const c = value.charCodeAt(i);\n if (c === CC_COMMA || c === CC_PAREN_CLOSE || isWhitespace(c)) break;\n i++;\n }\n if (i === start) return undefined;\n // Require a closing `)` somewhere after, so we don't classify malformed\n // inputs (`var(--color-foo`) as valid references.\n const close = value.indexOf(')', i);\n if (close === -1) return undefined;\n return value.slice(start, i);\n}\n\nconst CC_COMMA = 0x2c;\nconst CC_PAREN_CLOSE = 0x29;\n\nfunction isWhitespace(cc: number): boolean {\n // ASCII whitespace: space, tab, LF, CR, FF, VT.\n return cc === 0x20 || cc === 0x09 || cc === 0x0a || cc === 0x0d || cc === 0x0c || cc === 0x0b;\n}\n\n/**\n * Map a `var(--color-foo)` reference or a raw color literal to a concrete\n * color string, using the supplied palette. Unrecognized references fall back\n * to `accent`.\n */\nexport function resolveColorReference(value: string, palette: ThemeTokenPalette): string {\n // Deterministic parser instead of a regex — regex variants with overlapping\n // quantifiers around the color name produced quadratic backtracking on\n // adversarial inputs (CodeQL js/polynomial-redos).\n const key = parseColorVarName(value);\n if (key === undefined) return value;\n switch (key) {\n case 'bg':\n return palette.bg;\n case 'panel':\n return palette.panel;\n case 'panel-2':\n return palette.panel2;\n case 'border':\n return palette.border;\n case 'border-strong':\n return palette.borderStrong;\n case 'text':\n return palette.text;\n case 'text-muted':\n return palette.textMuted;\n case 'text-dim':\n return palette.textDim;\n case 'accent':\n return palette.accent;\n case 'ok':\n return palette.ok;\n case 'warn':\n return palette.warn;\n case 'err':\n return palette.err;\n case 'purple':\n return palette.purple;\n case 'pink':\n return palette.pink;\n default:\n return palette.accent;\n }\n}\n","'use client';\n\nimport type cytoscape from 'cytoscape';\nimport { useCallback, useEffect, useMemo } from 'react';\n\nimport { buildShipItStylesheet, type BuildStylesheetOptions } from './stylesheet';\n\n/**\n * useShipItStylesheet — keeps a Cytoscape instance's stylesheet in sync with\n * the live design-token palette. Re-applies the stylesheet whenever\n * `<html data-theme>` flips, so toggling between dark and light propagates to\n * the graph without remounting.\n *\n * Returns a `refresh()` callback the consumer can invoke after any other\n * theme-affecting change (e.g., a `--color-accent` hue knob update).\n *\n * ```ts\n * const cyRef = useRef<cytoscape.Core | null>(null);\n * const { refresh } = useShipItStylesheet(cyRef);\n * ```\n */\n\nexport interface UseShipItStylesheetOptions extends BuildStylesheetOptions {\n /** Skip the MutationObserver wiring (e.g., when the host owns its own observer). */\n observe?: boolean;\n}\n\nexport interface UseShipItStylesheetReturn {\n /** Re-read tokens and re-apply the stylesheet. */\n refresh: () => void;\n}\n\nexport function useShipItStylesheet(\n cyRef: { current: cytoscape.Core | null },\n options: UseShipItStylesheetOptions = {},\n): UseShipItStylesheetReturn {\n const { observe = true, palette, extra } = options;\n // Memoize the build options against their flat constituents so callers that\n // pass `options` inline (a fresh object each render) don't churn `apply` /\n // disconnect+reconnect the MutationObserver on every render.\n const buildOptions = useMemo<BuildStylesheetOptions>(\n () => ({ palette, extra }),\n [palette, extra],\n );\n\n const apply = useCallback(() => {\n const cy = cyRef.current;\n if (!cy) return;\n cy.style(buildShipItStylesheet(buildOptions)).update();\n }, [cyRef, buildOptions]);\n\n useEffect(() => {\n apply();\n if (!observe || typeof document === 'undefined') return undefined;\n const observer = new MutationObserver(apply);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['data-theme'],\n });\n return () => observer.disconnect();\n }, [apply, observe]);\n\n return { refresh: apply };\n}\n","'use client';\n\nimport type cytoscape from 'cytoscape';\nimport {\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\n\nimport { buildShipItStylesheet, type BuildStylesheetOptions } from './stylesheet';\nimport { useShipItStylesheet } from './useShipItStylesheet';\n\n/**\n * GraphCanvas — high-level wrapper around a Cytoscape instance. Owns the\n * `data-theme` ↔ stylesheet sync (via {@link useShipItStylesheet}), the\n * cytoscape lifecycle (create / destroy on mount / unmount), and a thin\n * selection API on top of Cytoscape's events.\n *\n * The component never bundles Cytoscape itself — pass the engine factory in\n * via the `engine` prop so the consumer controls the Cytoscape version and\n * any registered extensions:\n *\n * ```tsx\n * import cytoscape from 'cytoscape';\n *\n * <GraphCanvas\n * engine={cytoscape}\n * elements={[...]}\n * layout={{ name: 'cose' }}\n * onSelect={(node) => setSelected(node.id())}\n * inspector={selected && <GraphInspector …/>}\n * />\n * ```\n */\n\nexport type CytoscapeEngine = (options: cytoscape.CytoscapeOptions) => cytoscape.Core;\n\nexport interface GraphCanvasHandle {\n /** Live Cytoscape instance. `null` until mount. */\n cy: cytoscape.Core | null;\n /** Re-read tokens and re-apply the stylesheet. */\n refreshStyles: () => void;\n}\n\nexport interface GraphCanvasProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'onSelect' | 'children'\n> {\n /** Cytoscape factory. Pass the imported `cytoscape` default export. */\n engine: CytoscapeEngine;\n /** Graph elements (nodes + edges). Passed straight through to Cytoscape. */\n elements: cytoscape.ElementDefinition[];\n /** Layout config. Defaults to a static `preset` layout (no auto-layout). */\n layout?: cytoscape.LayoutOptions;\n /** Fires when a node is tapped/selected. */\n onSelect?: (node: cytoscape.NodeSingular) => void;\n /** Fires when the selection is cleared (background tap). */\n onClearSelection?: () => void;\n /** Fires when the pointer enters a node. */\n onNodeHover?: (node: cytoscape.NodeSingular) => void;\n /** Fires when the pointer leaves a node. */\n onNodeLeave?: () => void;\n /** Slot rendered over the graph (e.g., a `<GraphInspector>`). Positioned top-right. */\n inspector?: ReactNode;\n /** Overrides for the stylesheet builder. */\n styleOptions?: BuildStylesheetOptions;\n /** Accessible label for the container. */\n 'aria-label'?: string;\n}\n\nconst DEFAULT_LAYOUT: cytoscape.LayoutOptions = { name: 'preset' };\n\nexport const GraphCanvas = forwardRef<GraphCanvasHandle, GraphCanvasProps>(function GraphCanvas(\n {\n engine,\n elements,\n layout = DEFAULT_LAYOUT,\n onSelect,\n onClearSelection,\n onNodeHover,\n onNodeLeave,\n inspector,\n styleOptions,\n className,\n 'aria-label': ariaLabel = 'Graph canvas',\n ...props\n },\n forwardedRef,\n) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const cyRef = useRef<cytoscape.Core | null>(null);\n\n // Create the cytoscape instance once on mount.\n useEffect(() => {\n if (!containerRef.current) return undefined;\n const cy = engine({\n container: containerRef.current,\n elements,\n layout,\n style: buildShipItStylesheet(styleOptions),\n });\n cyRef.current = cy;\n return () => {\n cy.destroy();\n cyRef.current = null;\n };\n // We intentionally re-create on engine swap or container remount only.\n // Elements / layout / style updates are handled in the effects below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [engine]);\n\n // Keep elements in sync.\n useEffect(() => {\n const cy = cyRef.current;\n if (!cy) return;\n cy.json({ elements });\n }, [elements]);\n\n // Re-run layout when the layout config changes.\n useEffect(() => {\n const cy = cyRef.current;\n if (!cy) return;\n cy.layout(layout).run();\n }, [layout]);\n\n const { refresh: refreshStyles } = useShipItStylesheet(cyRef, styleOptions);\n\n // Wire selection events. `engine` is in the deps so the listeners re-bind\n // whenever the upstream effect destroys + recreates the Cytoscape instance —\n // without it, an `engine` swap would leave the new `cy` without handlers.\n useEffect(() => {\n const cy = cyRef.current;\n if (!cy) return undefined;\n const handleSelect = (event: cytoscape.EventObject) => {\n if (event.target?.isNode?.()) {\n onSelect?.(event.target as cytoscape.NodeSingular);\n }\n };\n const handleBackgroundTap = (event: cytoscape.EventObject) => {\n if (event.target === cy) onClearSelection?.();\n };\n const handleEnter = (event: cytoscape.EventObject) => {\n onNodeHover?.(event.target as cytoscape.NodeSingular);\n };\n const handleLeave = () => onNodeLeave?.();\n cy.on('tap', 'node', handleSelect);\n cy.on('tap', handleBackgroundTap);\n cy.on('mouseover', 'node', handleEnter);\n cy.on('mouseout', 'node', handleLeave);\n return () => {\n cy.off('tap', 'node', handleSelect);\n cy.off('tap', handleBackgroundTap);\n cy.off('mouseover', 'node', handleEnter);\n cy.off('mouseout', 'node', handleLeave);\n };\n }, [engine, onSelect, onClearSelection, onNodeHover, onNodeLeave]);\n\n // Expose the imperative handle via getters so consumers always read the live\n // `cyRef.current`. Snapshotting `cyRef.current` here would freeze it at\n // `null` because the instance is assigned inside an effect that runs *after*\n // the initial render — Copilot/Claude both flagged this.\n useImperativeHandle(\n forwardedRef,\n (): GraphCanvasHandle => ({\n get cy() {\n return cyRef.current;\n },\n refreshStyles,\n }),\n [refreshStyles],\n );\n\n return (\n <div\n role=\"region\"\n aria-label={ariaLabel}\n className={['relative h-full w-full', className].filter(Boolean).join(' ')}\n {...props}\n >\n <div ref={containerRef} className=\"absolute inset-0\" />\n {inspector && <div className=\"absolute top-4 right-4 z-10\">{inspector}</div>}\n </div>\n );\n});\n\nGraphCanvas.displayName = 'GraphCanvas';\n"],"mappings":"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","names":["import_shipit","import_react","GraphCanvas"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import cytoscape from 'cytoscape';
|
|
2
|
+
import { EntityType } from '@ship-it-ui/shipit';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Token resolution helpers. The Ship-It design system stores colors as CSS
|
|
8
|
+
* custom properties on `<html>` (`--color-accent`, `--color-bg`, …). Cytoscape
|
|
9
|
+
* renders to a canvas / SVG layer outside Tailwind, so those vars never
|
|
10
|
+
* resolve — we read the *computed* values at runtime and feed them into the
|
|
11
|
+
* stylesheet builder as concrete color strings.
|
|
12
|
+
*
|
|
13
|
+
* Two layers:
|
|
14
|
+
* 1. `resolveCssVar` — single-var reader with a fallback.
|
|
15
|
+
* 2. `readThemeTokens` — pulls every color the stylesheet uses, returning a
|
|
16
|
+
* flat `Record<string, string>` keyed by short name (no `--color-`
|
|
17
|
+
* prefix). Re-running this after a `data-theme` flip yields a fresh
|
|
18
|
+
* palette.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Read a single CSS variable from the document root and return its trimmed
|
|
23
|
+
* computed value, falling back to `fallback` when the document is missing
|
|
24
|
+
* (SSR) or the variable is unset.
|
|
25
|
+
*/
|
|
26
|
+
declare function resolveCssVar(name: string, fallback?: string): string;
|
|
27
|
+
interface ThemeTokenPalette {
|
|
28
|
+
/** Surface backgrounds. */
|
|
29
|
+
bg: string;
|
|
30
|
+
panel: string;
|
|
31
|
+
panel2: string;
|
|
32
|
+
/** Hairline + emphasis border. */
|
|
33
|
+
border: string;
|
|
34
|
+
borderStrong: string;
|
|
35
|
+
/** Foreground tiers. */
|
|
36
|
+
text: string;
|
|
37
|
+
textMuted: string;
|
|
38
|
+
textDim: string;
|
|
39
|
+
/** Brand + status. */
|
|
40
|
+
accent: string;
|
|
41
|
+
ok: string;
|
|
42
|
+
warn: string;
|
|
43
|
+
err: string;
|
|
44
|
+
/** Extras the graph uses for entity-type ring colors. */
|
|
45
|
+
purple: string;
|
|
46
|
+
pink: string;
|
|
47
|
+
}
|
|
48
|
+
/** Read the canonical Ship-It color tokens from the document root. */
|
|
49
|
+
declare function readThemeTokens(): ThemeTokenPalette;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the concrete color for a registered entity type. Reads the type's
|
|
52
|
+
* `colorVar` (a `var(--color-…)` string) and looks the value up in the
|
|
53
|
+
* palette. Falls back to the palette's `accent` color when the var is
|
|
54
|
+
* malformed or unknown.
|
|
55
|
+
*/
|
|
56
|
+
declare function resolveEntityColor(type: EntityType, palette: ThemeTokenPalette): string;
|
|
57
|
+
/**
|
|
58
|
+
* Map a `var(--color-foo)` reference or a raw color literal to a concrete
|
|
59
|
+
* color string, using the supplied palette. Unrecognized references fall back
|
|
60
|
+
* to `accent`.
|
|
61
|
+
*/
|
|
62
|
+
declare function resolveColorReference(value: string, palette: ThemeTokenPalette): string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build a Cytoscape stylesheet from the live design tokens. The result is a
|
|
66
|
+
* plain JSON array suitable for `cytoscape({ style: ... })` — re-run after a
|
|
67
|
+
* `data-theme` change to pick up the new palette.
|
|
68
|
+
*
|
|
69
|
+
* The stylesheet is opinionated:
|
|
70
|
+
* - Nodes are square-ish rounded glyphs colored by `data(entityType)`.
|
|
71
|
+
* - Edges are token-colored thin lines with arrowheads.
|
|
72
|
+
* - Selected / on-path / dimmed states are driven by class names that the
|
|
73
|
+
* consumer toggles (`graph-canvas:selected`, `graph-canvas:path`,
|
|
74
|
+
* `graph-canvas:dim`).
|
|
75
|
+
*
|
|
76
|
+
* Pass `palette` to override the token read — useful for SSR or tests.
|
|
77
|
+
*/
|
|
78
|
+
interface BuildStylesheetOptions {
|
|
79
|
+
/** Pre-resolved palette. When omitted, tokens are read from the document. */
|
|
80
|
+
palette?: ThemeTokenPalette;
|
|
81
|
+
/**
|
|
82
|
+
* Additional entries appended to the stylesheet — handy for app-specific
|
|
83
|
+
* selectors without forking the builder.
|
|
84
|
+
*/
|
|
85
|
+
extra?: ReadonlyArray<cytoscape.StylesheetJsonBlock>;
|
|
86
|
+
}
|
|
87
|
+
type ShipItStylesheetBlock = cytoscape.StylesheetJsonBlock;
|
|
88
|
+
declare function buildShipItStylesheet(options?: BuildStylesheetOptions): cytoscape.StylesheetJson;
|
|
89
|
+
/**
|
|
90
|
+
* Convenience class names the stylesheet recognizes. Toggle these on Cytoscape
|
|
91
|
+
* nodes / edges to drive the on-path and dimmed visuals.
|
|
92
|
+
*/
|
|
93
|
+
declare const GRAPH_CANVAS_CLASS: {
|
|
94
|
+
readonly path: "graph-canvas:path";
|
|
95
|
+
readonly dim: "graph-canvas:dim";
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* useShipItStylesheet — keeps a Cytoscape instance's stylesheet in sync with
|
|
100
|
+
* the live design-token palette. Re-applies the stylesheet whenever
|
|
101
|
+
* `<html data-theme>` flips, so toggling between dark and light propagates to
|
|
102
|
+
* the graph without remounting.
|
|
103
|
+
*
|
|
104
|
+
* Returns a `refresh()` callback the consumer can invoke after any other
|
|
105
|
+
* theme-affecting change (e.g., a `--color-accent` hue knob update).
|
|
106
|
+
*
|
|
107
|
+
* ```ts
|
|
108
|
+
* const cyRef = useRef<cytoscape.Core | null>(null);
|
|
109
|
+
* const { refresh } = useShipItStylesheet(cyRef);
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
interface UseShipItStylesheetOptions extends BuildStylesheetOptions {
|
|
113
|
+
/** Skip the MutationObserver wiring (e.g., when the host owns its own observer). */
|
|
114
|
+
observe?: boolean;
|
|
115
|
+
}
|
|
116
|
+
interface UseShipItStylesheetReturn {
|
|
117
|
+
/** Re-read tokens and re-apply the stylesheet. */
|
|
118
|
+
refresh: () => void;
|
|
119
|
+
}
|
|
120
|
+
declare function useShipItStylesheet(cyRef: {
|
|
121
|
+
current: cytoscape.Core | null;
|
|
122
|
+
}, options?: UseShipItStylesheetOptions): UseShipItStylesheetReturn;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* GraphCanvas — high-level wrapper around a Cytoscape instance. Owns the
|
|
126
|
+
* `data-theme` ↔ stylesheet sync (via {@link useShipItStylesheet}), the
|
|
127
|
+
* cytoscape lifecycle (create / destroy on mount / unmount), and a thin
|
|
128
|
+
* selection API on top of Cytoscape's events.
|
|
129
|
+
*
|
|
130
|
+
* The component never bundles Cytoscape itself — pass the engine factory in
|
|
131
|
+
* via the `engine` prop so the consumer controls the Cytoscape version and
|
|
132
|
+
* any registered extensions:
|
|
133
|
+
*
|
|
134
|
+
* ```tsx
|
|
135
|
+
* import cytoscape from 'cytoscape';
|
|
136
|
+
*
|
|
137
|
+
* <GraphCanvas
|
|
138
|
+
* engine={cytoscape}
|
|
139
|
+
* elements={[...]}
|
|
140
|
+
* layout={{ name: 'cose' }}
|
|
141
|
+
* onSelect={(node) => setSelected(node.id())}
|
|
142
|
+
* inspector={selected && <GraphInspector …/>}
|
|
143
|
+
* />
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
type CytoscapeEngine = (options: cytoscape.CytoscapeOptions) => cytoscape.Core;
|
|
147
|
+
interface GraphCanvasHandle {
|
|
148
|
+
/** Live Cytoscape instance. `null` until mount. */
|
|
149
|
+
cy: cytoscape.Core | null;
|
|
150
|
+
/** Re-read tokens and re-apply the stylesheet. */
|
|
151
|
+
refreshStyles: () => void;
|
|
152
|
+
}
|
|
153
|
+
interface GraphCanvasProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect' | 'children'> {
|
|
154
|
+
/** Cytoscape factory. Pass the imported `cytoscape` default export. */
|
|
155
|
+
engine: CytoscapeEngine;
|
|
156
|
+
/** Graph elements (nodes + edges). Passed straight through to Cytoscape. */
|
|
157
|
+
elements: cytoscape.ElementDefinition[];
|
|
158
|
+
/** Layout config. Defaults to a static `preset` layout (no auto-layout). */
|
|
159
|
+
layout?: cytoscape.LayoutOptions;
|
|
160
|
+
/** Fires when a node is tapped/selected. */
|
|
161
|
+
onSelect?: (node: cytoscape.NodeSingular) => void;
|
|
162
|
+
/** Fires when the selection is cleared (background tap). */
|
|
163
|
+
onClearSelection?: () => void;
|
|
164
|
+
/** Fires when the pointer enters a node. */
|
|
165
|
+
onNodeHover?: (node: cytoscape.NodeSingular) => void;
|
|
166
|
+
/** Fires when the pointer leaves a node. */
|
|
167
|
+
onNodeLeave?: () => void;
|
|
168
|
+
/** Slot rendered over the graph (e.g., a `<GraphInspector>`). Positioned top-right. */
|
|
169
|
+
inspector?: ReactNode;
|
|
170
|
+
/** Overrides for the stylesheet builder. */
|
|
171
|
+
styleOptions?: BuildStylesheetOptions;
|
|
172
|
+
/** Accessible label for the container. */
|
|
173
|
+
'aria-label'?: string;
|
|
174
|
+
}
|
|
175
|
+
declare const GraphCanvas: react.ForwardRefExoticComponent<GraphCanvasProps & react.RefAttributes<GraphCanvasHandle>>;
|
|
176
|
+
|
|
177
|
+
export { type BuildStylesheetOptions, type CytoscapeEngine, GRAPH_CANVAS_CLASS, GraphCanvas, type GraphCanvasHandle, type GraphCanvasProps, type ShipItStylesheetBlock, type ThemeTokenPalette, type UseShipItStylesheetOptions, type UseShipItStylesheetReturn, buildShipItStylesheet, readThemeTokens, resolveColorReference, resolveCssVar, resolveEntityColor, useShipItStylesheet };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import cytoscape from 'cytoscape';
|
|
2
|
+
import { EntityType } from '@ship-it-ui/shipit';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Token resolution helpers. The Ship-It design system stores colors as CSS
|
|
8
|
+
* custom properties on `<html>` (`--color-accent`, `--color-bg`, …). Cytoscape
|
|
9
|
+
* renders to a canvas / SVG layer outside Tailwind, so those vars never
|
|
10
|
+
* resolve — we read the *computed* values at runtime and feed them into the
|
|
11
|
+
* stylesheet builder as concrete color strings.
|
|
12
|
+
*
|
|
13
|
+
* Two layers:
|
|
14
|
+
* 1. `resolveCssVar` — single-var reader with a fallback.
|
|
15
|
+
* 2. `readThemeTokens` — pulls every color the stylesheet uses, returning a
|
|
16
|
+
* flat `Record<string, string>` keyed by short name (no `--color-`
|
|
17
|
+
* prefix). Re-running this after a `data-theme` flip yields a fresh
|
|
18
|
+
* palette.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Read a single CSS variable from the document root and return its trimmed
|
|
23
|
+
* computed value, falling back to `fallback` when the document is missing
|
|
24
|
+
* (SSR) or the variable is unset.
|
|
25
|
+
*/
|
|
26
|
+
declare function resolveCssVar(name: string, fallback?: string): string;
|
|
27
|
+
interface ThemeTokenPalette {
|
|
28
|
+
/** Surface backgrounds. */
|
|
29
|
+
bg: string;
|
|
30
|
+
panel: string;
|
|
31
|
+
panel2: string;
|
|
32
|
+
/** Hairline + emphasis border. */
|
|
33
|
+
border: string;
|
|
34
|
+
borderStrong: string;
|
|
35
|
+
/** Foreground tiers. */
|
|
36
|
+
text: string;
|
|
37
|
+
textMuted: string;
|
|
38
|
+
textDim: string;
|
|
39
|
+
/** Brand + status. */
|
|
40
|
+
accent: string;
|
|
41
|
+
ok: string;
|
|
42
|
+
warn: string;
|
|
43
|
+
err: string;
|
|
44
|
+
/** Extras the graph uses for entity-type ring colors. */
|
|
45
|
+
purple: string;
|
|
46
|
+
pink: string;
|
|
47
|
+
}
|
|
48
|
+
/** Read the canonical Ship-It color tokens from the document root. */
|
|
49
|
+
declare function readThemeTokens(): ThemeTokenPalette;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the concrete color for a registered entity type. Reads the type's
|
|
52
|
+
* `colorVar` (a `var(--color-…)` string) and looks the value up in the
|
|
53
|
+
* palette. Falls back to the palette's `accent` color when the var is
|
|
54
|
+
* malformed or unknown.
|
|
55
|
+
*/
|
|
56
|
+
declare function resolveEntityColor(type: EntityType, palette: ThemeTokenPalette): string;
|
|
57
|
+
/**
|
|
58
|
+
* Map a `var(--color-foo)` reference or a raw color literal to a concrete
|
|
59
|
+
* color string, using the supplied palette. Unrecognized references fall back
|
|
60
|
+
* to `accent`.
|
|
61
|
+
*/
|
|
62
|
+
declare function resolveColorReference(value: string, palette: ThemeTokenPalette): string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build a Cytoscape stylesheet from the live design tokens. The result is a
|
|
66
|
+
* plain JSON array suitable for `cytoscape({ style: ... })` — re-run after a
|
|
67
|
+
* `data-theme` change to pick up the new palette.
|
|
68
|
+
*
|
|
69
|
+
* The stylesheet is opinionated:
|
|
70
|
+
* - Nodes are square-ish rounded glyphs colored by `data(entityType)`.
|
|
71
|
+
* - Edges are token-colored thin lines with arrowheads.
|
|
72
|
+
* - Selected / on-path / dimmed states are driven by class names that the
|
|
73
|
+
* consumer toggles (`graph-canvas:selected`, `graph-canvas:path`,
|
|
74
|
+
* `graph-canvas:dim`).
|
|
75
|
+
*
|
|
76
|
+
* Pass `palette` to override the token read — useful for SSR or tests.
|
|
77
|
+
*/
|
|
78
|
+
interface BuildStylesheetOptions {
|
|
79
|
+
/** Pre-resolved palette. When omitted, tokens are read from the document. */
|
|
80
|
+
palette?: ThemeTokenPalette;
|
|
81
|
+
/**
|
|
82
|
+
* Additional entries appended to the stylesheet — handy for app-specific
|
|
83
|
+
* selectors without forking the builder.
|
|
84
|
+
*/
|
|
85
|
+
extra?: ReadonlyArray<cytoscape.StylesheetJsonBlock>;
|
|
86
|
+
}
|
|
87
|
+
type ShipItStylesheetBlock = cytoscape.StylesheetJsonBlock;
|
|
88
|
+
declare function buildShipItStylesheet(options?: BuildStylesheetOptions): cytoscape.StylesheetJson;
|
|
89
|
+
/**
|
|
90
|
+
* Convenience class names the stylesheet recognizes. Toggle these on Cytoscape
|
|
91
|
+
* nodes / edges to drive the on-path and dimmed visuals.
|
|
92
|
+
*/
|
|
93
|
+
declare const GRAPH_CANVAS_CLASS: {
|
|
94
|
+
readonly path: "graph-canvas:path";
|
|
95
|
+
readonly dim: "graph-canvas:dim";
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* useShipItStylesheet — keeps a Cytoscape instance's stylesheet in sync with
|
|
100
|
+
* the live design-token palette. Re-applies the stylesheet whenever
|
|
101
|
+
* `<html data-theme>` flips, so toggling between dark and light propagates to
|
|
102
|
+
* the graph without remounting.
|
|
103
|
+
*
|
|
104
|
+
* Returns a `refresh()` callback the consumer can invoke after any other
|
|
105
|
+
* theme-affecting change (e.g., a `--color-accent` hue knob update).
|
|
106
|
+
*
|
|
107
|
+
* ```ts
|
|
108
|
+
* const cyRef = useRef<cytoscape.Core | null>(null);
|
|
109
|
+
* const { refresh } = useShipItStylesheet(cyRef);
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
interface UseShipItStylesheetOptions extends BuildStylesheetOptions {
|
|
113
|
+
/** Skip the MutationObserver wiring (e.g., when the host owns its own observer). */
|
|
114
|
+
observe?: boolean;
|
|
115
|
+
}
|
|
116
|
+
interface UseShipItStylesheetReturn {
|
|
117
|
+
/** Re-read tokens and re-apply the stylesheet. */
|
|
118
|
+
refresh: () => void;
|
|
119
|
+
}
|
|
120
|
+
declare function useShipItStylesheet(cyRef: {
|
|
121
|
+
current: cytoscape.Core | null;
|
|
122
|
+
}, options?: UseShipItStylesheetOptions): UseShipItStylesheetReturn;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* GraphCanvas — high-level wrapper around a Cytoscape instance. Owns the
|
|
126
|
+
* `data-theme` ↔ stylesheet sync (via {@link useShipItStylesheet}), the
|
|
127
|
+
* cytoscape lifecycle (create / destroy on mount / unmount), and a thin
|
|
128
|
+
* selection API on top of Cytoscape's events.
|
|
129
|
+
*
|
|
130
|
+
* The component never bundles Cytoscape itself — pass the engine factory in
|
|
131
|
+
* via the `engine` prop so the consumer controls the Cytoscape version and
|
|
132
|
+
* any registered extensions:
|
|
133
|
+
*
|
|
134
|
+
* ```tsx
|
|
135
|
+
* import cytoscape from 'cytoscape';
|
|
136
|
+
*
|
|
137
|
+
* <GraphCanvas
|
|
138
|
+
* engine={cytoscape}
|
|
139
|
+
* elements={[...]}
|
|
140
|
+
* layout={{ name: 'cose' }}
|
|
141
|
+
* onSelect={(node) => setSelected(node.id())}
|
|
142
|
+
* inspector={selected && <GraphInspector …/>}
|
|
143
|
+
* />
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
type CytoscapeEngine = (options: cytoscape.CytoscapeOptions) => cytoscape.Core;
|
|
147
|
+
interface GraphCanvasHandle {
|
|
148
|
+
/** Live Cytoscape instance. `null` until mount. */
|
|
149
|
+
cy: cytoscape.Core | null;
|
|
150
|
+
/** Re-read tokens and re-apply the stylesheet. */
|
|
151
|
+
refreshStyles: () => void;
|
|
152
|
+
}
|
|
153
|
+
interface GraphCanvasProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect' | 'children'> {
|
|
154
|
+
/** Cytoscape factory. Pass the imported `cytoscape` default export. */
|
|
155
|
+
engine: CytoscapeEngine;
|
|
156
|
+
/** Graph elements (nodes + edges). Passed straight through to Cytoscape. */
|
|
157
|
+
elements: cytoscape.ElementDefinition[];
|
|
158
|
+
/** Layout config. Defaults to a static `preset` layout (no auto-layout). */
|
|
159
|
+
layout?: cytoscape.LayoutOptions;
|
|
160
|
+
/** Fires when a node is tapped/selected. */
|
|
161
|
+
onSelect?: (node: cytoscape.NodeSingular) => void;
|
|
162
|
+
/** Fires when the selection is cleared (background tap). */
|
|
163
|
+
onClearSelection?: () => void;
|
|
164
|
+
/** Fires when the pointer enters a node. */
|
|
165
|
+
onNodeHover?: (node: cytoscape.NodeSingular) => void;
|
|
166
|
+
/** Fires when the pointer leaves a node. */
|
|
167
|
+
onNodeLeave?: () => void;
|
|
168
|
+
/** Slot rendered over the graph (e.g., a `<GraphInspector>`). Positioned top-right. */
|
|
169
|
+
inspector?: ReactNode;
|
|
170
|
+
/** Overrides for the stylesheet builder. */
|
|
171
|
+
styleOptions?: BuildStylesheetOptions;
|
|
172
|
+
/** Accessible label for the container. */
|
|
173
|
+
'aria-label'?: string;
|
|
174
|
+
}
|
|
175
|
+
declare const GraphCanvas: react.ForwardRefExoticComponent<GraphCanvasProps & react.RefAttributes<GraphCanvasHandle>>;
|
|
176
|
+
|
|
177
|
+
export { type BuildStylesheetOptions, type CytoscapeEngine, GRAPH_CANVAS_CLASS, GraphCanvas, type GraphCanvasHandle, type GraphCanvasProps, type ShipItStylesheetBlock, type ThemeTokenPalette, type UseShipItStylesheetOptions, type UseShipItStylesheetReturn, buildShipItStylesheet, readThemeTokens, resolveColorReference, resolveCssVar, resolveEntityColor, useShipItStylesheet };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// src/stylesheet.ts
|
|
4
|
+
import { listEntityTypes } from "@ship-it-ui/shipit";
|
|
5
|
+
|
|
6
|
+
// src/theme-tokens.ts
|
|
7
|
+
import { getEntityTypeMeta } from "@ship-it-ui/shipit";
|
|
8
|
+
var DEFAULT_FALLBACK = {
|
|
9
|
+
bg: "#0a0a0a",
|
|
10
|
+
panel: "#0f0f0f",
|
|
11
|
+
"panel-2": "#161616",
|
|
12
|
+
border: "#262626",
|
|
13
|
+
"border-strong": "#383838",
|
|
14
|
+
text: "#fafafa",
|
|
15
|
+
"text-muted": "#a3a3a3",
|
|
16
|
+
"text-dim": "#737373",
|
|
17
|
+
accent: "#3b82f6",
|
|
18
|
+
ok: "#10b981",
|
|
19
|
+
warn: "#f59e0b",
|
|
20
|
+
err: "#ef4444",
|
|
21
|
+
purple: "#a855f7",
|
|
22
|
+
pink: "#ec4899"
|
|
23
|
+
};
|
|
24
|
+
function resolveCssVar(name, fallback = "") {
|
|
25
|
+
if (typeof document === "undefined") return fallback;
|
|
26
|
+
const raw = getComputedStyle(document.documentElement).getPropertyValue(name);
|
|
27
|
+
const trimmed = raw.trim();
|
|
28
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
29
|
+
}
|
|
30
|
+
function readThemeTokens() {
|
|
31
|
+
return {
|
|
32
|
+
bg: resolveCssVar("--color-bg", DEFAULT_FALLBACK.bg),
|
|
33
|
+
panel: resolveCssVar("--color-panel", DEFAULT_FALLBACK.panel),
|
|
34
|
+
panel2: resolveCssVar("--color-panel-2", DEFAULT_FALLBACK["panel-2"]),
|
|
35
|
+
border: resolveCssVar("--color-border", DEFAULT_FALLBACK.border),
|
|
36
|
+
borderStrong: resolveCssVar("--color-border-strong", DEFAULT_FALLBACK["border-strong"]),
|
|
37
|
+
text: resolveCssVar("--color-text", DEFAULT_FALLBACK.text),
|
|
38
|
+
textMuted: resolveCssVar("--color-text-muted", DEFAULT_FALLBACK["text-muted"]),
|
|
39
|
+
textDim: resolveCssVar("--color-text-dim", DEFAULT_FALLBACK["text-dim"]),
|
|
40
|
+
accent: resolveCssVar("--color-accent", DEFAULT_FALLBACK.accent),
|
|
41
|
+
ok: resolveCssVar("--color-ok", DEFAULT_FALLBACK.ok),
|
|
42
|
+
warn: resolveCssVar("--color-warn", DEFAULT_FALLBACK.warn),
|
|
43
|
+
err: resolveCssVar("--color-err", DEFAULT_FALLBACK.err),
|
|
44
|
+
purple: resolveCssVar("--color-purple", DEFAULT_FALLBACK.purple),
|
|
45
|
+
pink: resolveCssVar("--color-pink", DEFAULT_FALLBACK.pink)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function resolveEntityColor(type, palette) {
|
|
49
|
+
const meta = getEntityTypeMeta(type);
|
|
50
|
+
return resolveColorReference(meta.colorVar, palette);
|
|
51
|
+
}
|
|
52
|
+
function parseColorVarName(value) {
|
|
53
|
+
let i = 0;
|
|
54
|
+
while (i < value.length && isWhitespace(value.charCodeAt(i))) i++;
|
|
55
|
+
if (!value.startsWith("var(", i)) return void 0;
|
|
56
|
+
i += 4;
|
|
57
|
+
while (i < value.length && isWhitespace(value.charCodeAt(i))) i++;
|
|
58
|
+
if (!value.startsWith("--color-", i)) return void 0;
|
|
59
|
+
i += 8;
|
|
60
|
+
const start = i;
|
|
61
|
+
while (i < value.length) {
|
|
62
|
+
const c = value.charCodeAt(i);
|
|
63
|
+
if (c === CC_COMMA || c === CC_PAREN_CLOSE || isWhitespace(c)) break;
|
|
64
|
+
i++;
|
|
65
|
+
}
|
|
66
|
+
if (i === start) return void 0;
|
|
67
|
+
const close = value.indexOf(")", i);
|
|
68
|
+
if (close === -1) return void 0;
|
|
69
|
+
return value.slice(start, i);
|
|
70
|
+
}
|
|
71
|
+
var CC_COMMA = 44;
|
|
72
|
+
var CC_PAREN_CLOSE = 41;
|
|
73
|
+
function isWhitespace(cc) {
|
|
74
|
+
return cc === 32 || cc === 9 || cc === 10 || cc === 13 || cc === 12 || cc === 11;
|
|
75
|
+
}
|
|
76
|
+
function resolveColorReference(value, palette) {
|
|
77
|
+
const key = parseColorVarName(value);
|
|
78
|
+
if (key === void 0) return value;
|
|
79
|
+
switch (key) {
|
|
80
|
+
case "bg":
|
|
81
|
+
return palette.bg;
|
|
82
|
+
case "panel":
|
|
83
|
+
return palette.panel;
|
|
84
|
+
case "panel-2":
|
|
85
|
+
return palette.panel2;
|
|
86
|
+
case "border":
|
|
87
|
+
return palette.border;
|
|
88
|
+
case "border-strong":
|
|
89
|
+
return palette.borderStrong;
|
|
90
|
+
case "text":
|
|
91
|
+
return palette.text;
|
|
92
|
+
case "text-muted":
|
|
93
|
+
return palette.textMuted;
|
|
94
|
+
case "text-dim":
|
|
95
|
+
return palette.textDim;
|
|
96
|
+
case "accent":
|
|
97
|
+
return palette.accent;
|
|
98
|
+
case "ok":
|
|
99
|
+
return palette.ok;
|
|
100
|
+
case "warn":
|
|
101
|
+
return palette.warn;
|
|
102
|
+
case "err":
|
|
103
|
+
return palette.err;
|
|
104
|
+
case "purple":
|
|
105
|
+
return palette.purple;
|
|
106
|
+
case "pink":
|
|
107
|
+
return palette.pink;
|
|
108
|
+
default:
|
|
109
|
+
return palette.accent;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/stylesheet.ts
|
|
114
|
+
function buildShipItStylesheet(options = {}) {
|
|
115
|
+
const palette = options.palette ?? readThemeTokens();
|
|
116
|
+
const color = (cssVar) => resolveColorReference(cssVar, palette);
|
|
117
|
+
const base = [
|
|
118
|
+
{
|
|
119
|
+
selector: "node",
|
|
120
|
+
style: {
|
|
121
|
+
"background-color": palette.panel,
|
|
122
|
+
"border-width": 1.5,
|
|
123
|
+
"border-color": palette.accent,
|
|
124
|
+
"border-opacity": 1,
|
|
125
|
+
label: "data(label)",
|
|
126
|
+
color: palette.textMuted,
|
|
127
|
+
"font-family": "var(--font-mono, monospace)",
|
|
128
|
+
"font-size": 10,
|
|
129
|
+
"text-valign": "bottom",
|
|
130
|
+
"text-halign": "center",
|
|
131
|
+
"text-margin-y": 6,
|
|
132
|
+
width: 36,
|
|
133
|
+
height: 36,
|
|
134
|
+
shape: "round-rectangle"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
// One selector per entity type registered with @ship-it-ui/shipit. Built-in
|
|
138
|
+
// types are seeded automatically; custom types registered via
|
|
139
|
+
// `registerEntityType(...)` pick up their `colorVar` here without a docs
|
|
140
|
+
// patch or a forked stylesheet.
|
|
141
|
+
...listEntityTypes().map(([type, meta]) => ({
|
|
142
|
+
selector: `node[entityType = "${escapeCytoscapeAttr(type)}"]`,
|
|
143
|
+
style: { "border-color": color(meta.colorVar) }
|
|
144
|
+
})),
|
|
145
|
+
{
|
|
146
|
+
selector: "node:selected",
|
|
147
|
+
style: {
|
|
148
|
+
"border-width": 3,
|
|
149
|
+
"overlay-color": palette.accent,
|
|
150
|
+
"overlay-opacity": 0.15,
|
|
151
|
+
"overlay-padding": 4
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
selector: "node.graph-canvas\\:path",
|
|
156
|
+
style: { "border-color": palette.purple }
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
selector: "node.graph-canvas\\:dim",
|
|
160
|
+
style: { opacity: 0.35 }
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
selector: "edge",
|
|
164
|
+
style: {
|
|
165
|
+
width: 1,
|
|
166
|
+
"line-color": palette.border,
|
|
167
|
+
"target-arrow-color": palette.border,
|
|
168
|
+
"target-arrow-shape": "triangle",
|
|
169
|
+
"arrow-scale": 0.8,
|
|
170
|
+
"curve-style": "bezier"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
selector: "edge.graph-canvas\\:path",
|
|
175
|
+
style: {
|
|
176
|
+
"line-color": palette.purple,
|
|
177
|
+
"target-arrow-color": palette.purple,
|
|
178
|
+
width: 1.5
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
selector: "edge.graph-canvas\\:dim",
|
|
183
|
+
style: { opacity: 0.25 }
|
|
184
|
+
}
|
|
185
|
+
];
|
|
186
|
+
return [...base, ...options.extra ?? []];
|
|
187
|
+
}
|
|
188
|
+
var GRAPH_CANVAS_CLASS = {
|
|
189
|
+
path: "graph-canvas:path",
|
|
190
|
+
dim: "graph-canvas:dim"
|
|
191
|
+
};
|
|
192
|
+
function escapeCytoscapeAttr(value) {
|
|
193
|
+
return value.replace(/[\\"]/g, (c) => `\\${c}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/useShipItStylesheet.ts
|
|
197
|
+
import { useCallback, useEffect, useMemo } from "react";
|
|
198
|
+
function useShipItStylesheet(cyRef, options = {}) {
|
|
199
|
+
const { observe = true, palette, extra } = options;
|
|
200
|
+
const buildOptions = useMemo(
|
|
201
|
+
() => ({ palette, extra }),
|
|
202
|
+
[palette, extra]
|
|
203
|
+
);
|
|
204
|
+
const apply = useCallback(() => {
|
|
205
|
+
const cy = cyRef.current;
|
|
206
|
+
if (!cy) return;
|
|
207
|
+
cy.style(buildShipItStylesheet(buildOptions)).update();
|
|
208
|
+
}, [cyRef, buildOptions]);
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
apply();
|
|
211
|
+
if (!observe || typeof document === "undefined") return void 0;
|
|
212
|
+
const observer = new MutationObserver(apply);
|
|
213
|
+
observer.observe(document.documentElement, {
|
|
214
|
+
attributes: true,
|
|
215
|
+
attributeFilter: ["data-theme"]
|
|
216
|
+
});
|
|
217
|
+
return () => observer.disconnect();
|
|
218
|
+
}, [apply, observe]);
|
|
219
|
+
return { refresh: apply };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/GraphCanvas.tsx
|
|
223
|
+
import {
|
|
224
|
+
forwardRef,
|
|
225
|
+
useEffect as useEffect2,
|
|
226
|
+
useImperativeHandle,
|
|
227
|
+
useRef
|
|
228
|
+
} from "react";
|
|
229
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
230
|
+
var DEFAULT_LAYOUT = { name: "preset" };
|
|
231
|
+
var GraphCanvas = forwardRef(function GraphCanvas2({
|
|
232
|
+
engine,
|
|
233
|
+
elements,
|
|
234
|
+
layout = DEFAULT_LAYOUT,
|
|
235
|
+
onSelect,
|
|
236
|
+
onClearSelection,
|
|
237
|
+
onNodeHover,
|
|
238
|
+
onNodeLeave,
|
|
239
|
+
inspector,
|
|
240
|
+
styleOptions,
|
|
241
|
+
className,
|
|
242
|
+
"aria-label": ariaLabel = "Graph canvas",
|
|
243
|
+
...props
|
|
244
|
+
}, forwardedRef) {
|
|
245
|
+
const containerRef = useRef(null);
|
|
246
|
+
const cyRef = useRef(null);
|
|
247
|
+
useEffect2(() => {
|
|
248
|
+
if (!containerRef.current) return void 0;
|
|
249
|
+
const cy = engine({
|
|
250
|
+
container: containerRef.current,
|
|
251
|
+
elements,
|
|
252
|
+
layout,
|
|
253
|
+
style: buildShipItStylesheet(styleOptions)
|
|
254
|
+
});
|
|
255
|
+
cyRef.current = cy;
|
|
256
|
+
return () => {
|
|
257
|
+
cy.destroy();
|
|
258
|
+
cyRef.current = null;
|
|
259
|
+
};
|
|
260
|
+
}, [engine]);
|
|
261
|
+
useEffect2(() => {
|
|
262
|
+
const cy = cyRef.current;
|
|
263
|
+
if (!cy) return;
|
|
264
|
+
cy.json({ elements });
|
|
265
|
+
}, [elements]);
|
|
266
|
+
useEffect2(() => {
|
|
267
|
+
const cy = cyRef.current;
|
|
268
|
+
if (!cy) return;
|
|
269
|
+
cy.layout(layout).run();
|
|
270
|
+
}, [layout]);
|
|
271
|
+
const { refresh: refreshStyles } = useShipItStylesheet(cyRef, styleOptions);
|
|
272
|
+
useEffect2(() => {
|
|
273
|
+
const cy = cyRef.current;
|
|
274
|
+
if (!cy) return void 0;
|
|
275
|
+
const handleSelect = (event) => {
|
|
276
|
+
if (event.target?.isNode?.()) {
|
|
277
|
+
onSelect?.(event.target);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const handleBackgroundTap = (event) => {
|
|
281
|
+
if (event.target === cy) onClearSelection?.();
|
|
282
|
+
};
|
|
283
|
+
const handleEnter = (event) => {
|
|
284
|
+
onNodeHover?.(event.target);
|
|
285
|
+
};
|
|
286
|
+
const handleLeave = () => onNodeLeave?.();
|
|
287
|
+
cy.on("tap", "node", handleSelect);
|
|
288
|
+
cy.on("tap", handleBackgroundTap);
|
|
289
|
+
cy.on("mouseover", "node", handleEnter);
|
|
290
|
+
cy.on("mouseout", "node", handleLeave);
|
|
291
|
+
return () => {
|
|
292
|
+
cy.off("tap", "node", handleSelect);
|
|
293
|
+
cy.off("tap", handleBackgroundTap);
|
|
294
|
+
cy.off("mouseover", "node", handleEnter);
|
|
295
|
+
cy.off("mouseout", "node", handleLeave);
|
|
296
|
+
};
|
|
297
|
+
}, [engine, onSelect, onClearSelection, onNodeHover, onNodeLeave]);
|
|
298
|
+
useImperativeHandle(
|
|
299
|
+
forwardedRef,
|
|
300
|
+
() => ({
|
|
301
|
+
get cy() {
|
|
302
|
+
return cyRef.current;
|
|
303
|
+
},
|
|
304
|
+
refreshStyles
|
|
305
|
+
}),
|
|
306
|
+
[refreshStyles]
|
|
307
|
+
);
|
|
308
|
+
return /* @__PURE__ */ jsxs(
|
|
309
|
+
"div",
|
|
310
|
+
{
|
|
311
|
+
role: "region",
|
|
312
|
+
"aria-label": ariaLabel,
|
|
313
|
+
className: ["relative h-full w-full", className].filter(Boolean).join(" "),
|
|
314
|
+
...props,
|
|
315
|
+
children: [
|
|
316
|
+
/* @__PURE__ */ jsx("div", { ref: containerRef, className: "absolute inset-0" }),
|
|
317
|
+
inspector && /* @__PURE__ */ jsx("div", { className: "absolute top-4 right-4 z-10", children: inspector })
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
});
|
|
322
|
+
GraphCanvas.displayName = "GraphCanvas";
|
|
323
|
+
export {
|
|
324
|
+
GRAPH_CANVAS_CLASS,
|
|
325
|
+
GraphCanvas,
|
|
326
|
+
buildShipItStylesheet,
|
|
327
|
+
readThemeTokens,
|
|
328
|
+
resolveColorReference,
|
|
329
|
+
resolveCssVar,
|
|
330
|
+
resolveEntityColor,
|
|
331
|
+
useShipItStylesheet
|
|
332
|
+
};
|
|
333
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stylesheet.ts","../src/theme-tokens.ts","../src/useShipItStylesheet.ts","../src/GraphCanvas.tsx"],"sourcesContent":["import { listEntityTypes } from '@ship-it-ui/shipit';\nimport type cytoscape from 'cytoscape';\n\nimport { readThemeTokens, resolveColorReference, type ThemeTokenPalette } from './theme-tokens';\n\n/**\n * Build a Cytoscape stylesheet from the live design tokens. The result is a\n * plain JSON array suitable for `cytoscape({ style: ... })` — re-run after a\n * `data-theme` change to pick up the new palette.\n *\n * The stylesheet is opinionated:\n * - Nodes are square-ish rounded glyphs colored by `data(entityType)`.\n * - Edges are token-colored thin lines with arrowheads.\n * - Selected / on-path / dimmed states are driven by class names that the\n * consumer toggles (`graph-canvas:selected`, `graph-canvas:path`,\n * `graph-canvas:dim`).\n *\n * Pass `palette` to override the token read — useful for SSR or tests.\n */\n\nexport interface BuildStylesheetOptions {\n /** Pre-resolved palette. When omitted, tokens are read from the document. */\n palette?: ThemeTokenPalette;\n /**\n * Additional entries appended to the stylesheet — handy for app-specific\n * selectors without forking the builder.\n */\n extra?: ReadonlyArray<cytoscape.StylesheetJsonBlock>;\n}\n\n// Re-export the block type so consumers can declare typed `extra` entries.\nexport type ShipItStylesheetBlock = cytoscape.StylesheetJsonBlock;\n\nexport function buildShipItStylesheet(\n options: BuildStylesheetOptions = {},\n): cytoscape.StylesheetJson {\n const palette = options.palette ?? readThemeTokens();\n const color = (cssVar: string) => resolveColorReference(cssVar, palette);\n\n const base: cytoscape.StylesheetJsonBlock[] = [\n {\n selector: 'node',\n style: {\n 'background-color': palette.panel,\n 'border-width': 1.5,\n 'border-color': palette.accent,\n 'border-opacity': 1,\n label: 'data(label)',\n color: palette.textMuted,\n 'font-family': 'var(--font-mono, monospace)',\n 'font-size': 10,\n 'text-valign': 'bottom',\n 'text-halign': 'center',\n 'text-margin-y': 6,\n width: 36,\n height: 36,\n shape: 'round-rectangle',\n },\n },\n // One selector per entity type registered with @ship-it-ui/shipit. Built-in\n // types are seeded automatically; custom types registered via\n // `registerEntityType(...)` pick up their `colorVar` here without a docs\n // patch or a forked stylesheet.\n ...listEntityTypes().map<cytoscape.StylesheetJsonBlock>(([type, meta]) => ({\n selector: `node[entityType = \"${escapeCytoscapeAttr(type)}\"]`,\n style: { 'border-color': color(meta.colorVar) },\n })),\n {\n selector: 'node:selected',\n style: {\n 'border-width': 3,\n 'overlay-color': palette.accent,\n 'overlay-opacity': 0.15,\n 'overlay-padding': 4,\n },\n },\n {\n selector: 'node.graph-canvas\\\\:path',\n style: { 'border-color': palette.purple },\n },\n {\n selector: 'node.graph-canvas\\\\:dim',\n style: { opacity: 0.35 },\n },\n {\n selector: 'edge',\n style: {\n width: 1,\n 'line-color': palette.border,\n 'target-arrow-color': palette.border,\n 'target-arrow-shape': 'triangle',\n 'arrow-scale': 0.8,\n 'curve-style': 'bezier',\n },\n },\n {\n selector: 'edge.graph-canvas\\\\:path',\n style: {\n 'line-color': palette.purple,\n 'target-arrow-color': palette.purple,\n width: 1.5,\n },\n },\n {\n selector: 'edge.graph-canvas\\\\:dim',\n style: { opacity: 0.25 },\n },\n ];\n\n return [...base, ...(options.extra ?? [])] as cytoscape.StylesheetJson;\n}\n\n/**\n * Convenience class names the stylesheet recognizes. Toggle these on Cytoscape\n * nodes / edges to drive the on-path and dimmed visuals.\n */\nexport const GRAPH_CANVAS_CLASS = {\n path: 'graph-canvas:path',\n dim: 'graph-canvas:dim',\n} as const;\n\n// Cytoscape's attribute selector accepts a double-quoted string. Escape\n// embedded backslashes and double quotes so a malformed (or hostile)\n// registered key can't break out of the selector. The grammar\n// (https://js.cytoscape.org/#selectors/data) is more permissive than CSS, but\n// these two characters are the only ones that would change selector semantics.\nfunction escapeCytoscapeAttr(value: string): string {\n return value.replace(/[\\\\\"]/g, (c) => `\\\\${c}`);\n}\n","/**\n * Token resolution helpers. The Ship-It design system stores colors as CSS\n * custom properties on `<html>` (`--color-accent`, `--color-bg`, …). Cytoscape\n * renders to a canvas / SVG layer outside Tailwind, so those vars never\n * resolve — we read the *computed* values at runtime and feed them into the\n * stylesheet builder as concrete color strings.\n *\n * Two layers:\n * 1. `resolveCssVar` — single-var reader with a fallback.\n * 2. `readThemeTokens` — pulls every color the stylesheet uses, returning a\n * flat `Record<string, string>` keyed by short name (no `--color-`\n * prefix). Re-running this after a `data-theme` flip yields a fresh\n * palette.\n */\n\nimport { getEntityTypeMeta, type EntityType } from '@ship-it-ui/shipit';\n\nconst DEFAULT_FALLBACK: Record<string, string> = {\n bg: '#0a0a0a',\n panel: '#0f0f0f',\n 'panel-2': '#161616',\n border: '#262626',\n 'border-strong': '#383838',\n text: '#fafafa',\n 'text-muted': '#a3a3a3',\n 'text-dim': '#737373',\n accent: '#3b82f6',\n ok: '#10b981',\n warn: '#f59e0b',\n err: '#ef4444',\n purple: '#a855f7',\n pink: '#ec4899',\n};\n\n/**\n * Read a single CSS variable from the document root and return its trimmed\n * computed value, falling back to `fallback` when the document is missing\n * (SSR) or the variable is unset.\n */\nexport function resolveCssVar(name: string, fallback = ''): string {\n if (typeof document === 'undefined') return fallback;\n const raw = getComputedStyle(document.documentElement).getPropertyValue(name);\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : fallback;\n}\n\nexport interface ThemeTokenPalette {\n /** Surface backgrounds. */\n bg: string;\n panel: string;\n panel2: string;\n /** Hairline + emphasis border. */\n border: string;\n borderStrong: string;\n /** Foreground tiers. */\n text: string;\n textMuted: string;\n textDim: string;\n /** Brand + status. */\n accent: string;\n ok: string;\n warn: string;\n err: string;\n /** Extras the graph uses for entity-type ring colors. */\n purple: string;\n pink: string;\n}\n\n/** Read the canonical Ship-It color tokens from the document root. */\nexport function readThemeTokens(): ThemeTokenPalette {\n return {\n bg: resolveCssVar('--color-bg', DEFAULT_FALLBACK.bg),\n panel: resolveCssVar('--color-panel', DEFAULT_FALLBACK.panel),\n panel2: resolveCssVar('--color-panel-2', DEFAULT_FALLBACK['panel-2']),\n border: resolveCssVar('--color-border', DEFAULT_FALLBACK.border),\n borderStrong: resolveCssVar('--color-border-strong', DEFAULT_FALLBACK['border-strong']),\n text: resolveCssVar('--color-text', DEFAULT_FALLBACK.text),\n textMuted: resolveCssVar('--color-text-muted', DEFAULT_FALLBACK['text-muted']),\n textDim: resolveCssVar('--color-text-dim', DEFAULT_FALLBACK['text-dim']),\n accent: resolveCssVar('--color-accent', DEFAULT_FALLBACK.accent),\n ok: resolveCssVar('--color-ok', DEFAULT_FALLBACK.ok),\n warn: resolveCssVar('--color-warn', DEFAULT_FALLBACK.warn),\n err: resolveCssVar('--color-err', DEFAULT_FALLBACK.err),\n purple: resolveCssVar('--color-purple', DEFAULT_FALLBACK.purple),\n pink: resolveCssVar('--color-pink', DEFAULT_FALLBACK.pink),\n };\n}\n\n/**\n * Resolve the concrete color for a registered entity type. Reads the type's\n * `colorVar` (a `var(--color-…)` string) and looks the value up in the\n * palette. Falls back to the palette's `accent` color when the var is\n * malformed or unknown.\n */\nexport function resolveEntityColor(type: EntityType, palette: ThemeTokenPalette): string {\n const meta = getEntityTypeMeta(type);\n return resolveColorReference(meta.colorVar, palette);\n}\n\n/**\n * Extract `foo` from `var(--color-foo)` or `var(--color-foo, fallback)`.\n * Returns `undefined` if the input doesn't match. O(n), no backtracking.\n */\nfunction parseColorVarName(value: string): string | undefined {\n // Strip surrounding whitespace once. `String#trim` is linear, not a regex.\n let i = 0;\n while (i < value.length && isWhitespace(value.charCodeAt(i))) i++;\n if (!value.startsWith('var(', i)) return undefined;\n i += 4;\n // Skip whitespace inside the parens.\n while (i < value.length && isWhitespace(value.charCodeAt(i))) i++;\n if (!value.startsWith('--color-', i)) return undefined;\n i += 8;\n // Read the name: stop at the first whitespace, comma, or `)`.\n const start = i;\n while (i < value.length) {\n const c = value.charCodeAt(i);\n if (c === CC_COMMA || c === CC_PAREN_CLOSE || isWhitespace(c)) break;\n i++;\n }\n if (i === start) return undefined;\n // Require a closing `)` somewhere after, so we don't classify malformed\n // inputs (`var(--color-foo`) as valid references.\n const close = value.indexOf(')', i);\n if (close === -1) return undefined;\n return value.slice(start, i);\n}\n\nconst CC_COMMA = 0x2c;\nconst CC_PAREN_CLOSE = 0x29;\n\nfunction isWhitespace(cc: number): boolean {\n // ASCII whitespace: space, tab, LF, CR, FF, VT.\n return cc === 0x20 || cc === 0x09 || cc === 0x0a || cc === 0x0d || cc === 0x0c || cc === 0x0b;\n}\n\n/**\n * Map a `var(--color-foo)` reference or a raw color literal to a concrete\n * color string, using the supplied palette. Unrecognized references fall back\n * to `accent`.\n */\nexport function resolveColorReference(value: string, palette: ThemeTokenPalette): string {\n // Deterministic parser instead of a regex — regex variants with overlapping\n // quantifiers around the color name produced quadratic backtracking on\n // adversarial inputs (CodeQL js/polynomial-redos).\n const key = parseColorVarName(value);\n if (key === undefined) return value;\n switch (key) {\n case 'bg':\n return palette.bg;\n case 'panel':\n return palette.panel;\n case 'panel-2':\n return palette.panel2;\n case 'border':\n return palette.border;\n case 'border-strong':\n return palette.borderStrong;\n case 'text':\n return palette.text;\n case 'text-muted':\n return palette.textMuted;\n case 'text-dim':\n return palette.textDim;\n case 'accent':\n return palette.accent;\n case 'ok':\n return palette.ok;\n case 'warn':\n return palette.warn;\n case 'err':\n return palette.err;\n case 'purple':\n return palette.purple;\n case 'pink':\n return palette.pink;\n default:\n return palette.accent;\n }\n}\n","'use client';\n\nimport type cytoscape from 'cytoscape';\nimport { useCallback, useEffect, useMemo } from 'react';\n\nimport { buildShipItStylesheet, type BuildStylesheetOptions } from './stylesheet';\n\n/**\n * useShipItStylesheet — keeps a Cytoscape instance's stylesheet in sync with\n * the live design-token palette. Re-applies the stylesheet whenever\n * `<html data-theme>` flips, so toggling between dark and light propagates to\n * the graph without remounting.\n *\n * Returns a `refresh()` callback the consumer can invoke after any other\n * theme-affecting change (e.g., a `--color-accent` hue knob update).\n *\n * ```ts\n * const cyRef = useRef<cytoscape.Core | null>(null);\n * const { refresh } = useShipItStylesheet(cyRef);\n * ```\n */\n\nexport interface UseShipItStylesheetOptions extends BuildStylesheetOptions {\n /** Skip the MutationObserver wiring (e.g., when the host owns its own observer). */\n observe?: boolean;\n}\n\nexport interface UseShipItStylesheetReturn {\n /** Re-read tokens and re-apply the stylesheet. */\n refresh: () => void;\n}\n\nexport function useShipItStylesheet(\n cyRef: { current: cytoscape.Core | null },\n options: UseShipItStylesheetOptions = {},\n): UseShipItStylesheetReturn {\n const { observe = true, palette, extra } = options;\n // Memoize the build options against their flat constituents so callers that\n // pass `options` inline (a fresh object each render) don't churn `apply` /\n // disconnect+reconnect the MutationObserver on every render.\n const buildOptions = useMemo<BuildStylesheetOptions>(\n () => ({ palette, extra }),\n [palette, extra],\n );\n\n const apply = useCallback(() => {\n const cy = cyRef.current;\n if (!cy) return;\n cy.style(buildShipItStylesheet(buildOptions)).update();\n }, [cyRef, buildOptions]);\n\n useEffect(() => {\n apply();\n if (!observe || typeof document === 'undefined') return undefined;\n const observer = new MutationObserver(apply);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['data-theme'],\n });\n return () => observer.disconnect();\n }, [apply, observe]);\n\n return { refresh: apply };\n}\n","'use client';\n\nimport type cytoscape from 'cytoscape';\nimport {\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\n\nimport { buildShipItStylesheet, type BuildStylesheetOptions } from './stylesheet';\nimport { useShipItStylesheet } from './useShipItStylesheet';\n\n/**\n * GraphCanvas — high-level wrapper around a Cytoscape instance. Owns the\n * `data-theme` ↔ stylesheet sync (via {@link useShipItStylesheet}), the\n * cytoscape lifecycle (create / destroy on mount / unmount), and a thin\n * selection API on top of Cytoscape's events.\n *\n * The component never bundles Cytoscape itself — pass the engine factory in\n * via the `engine` prop so the consumer controls the Cytoscape version and\n * any registered extensions:\n *\n * ```tsx\n * import cytoscape from 'cytoscape';\n *\n * <GraphCanvas\n * engine={cytoscape}\n * elements={[...]}\n * layout={{ name: 'cose' }}\n * onSelect={(node) => setSelected(node.id())}\n * inspector={selected && <GraphInspector …/>}\n * />\n * ```\n */\n\nexport type CytoscapeEngine = (options: cytoscape.CytoscapeOptions) => cytoscape.Core;\n\nexport interface GraphCanvasHandle {\n /** Live Cytoscape instance. `null` until mount. */\n cy: cytoscape.Core | null;\n /** Re-read tokens and re-apply the stylesheet. */\n refreshStyles: () => void;\n}\n\nexport interface GraphCanvasProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'onSelect' | 'children'\n> {\n /** Cytoscape factory. Pass the imported `cytoscape` default export. */\n engine: CytoscapeEngine;\n /** Graph elements (nodes + edges). Passed straight through to Cytoscape. */\n elements: cytoscape.ElementDefinition[];\n /** Layout config. Defaults to a static `preset` layout (no auto-layout). */\n layout?: cytoscape.LayoutOptions;\n /** Fires when a node is tapped/selected. */\n onSelect?: (node: cytoscape.NodeSingular) => void;\n /** Fires when the selection is cleared (background tap). */\n onClearSelection?: () => void;\n /** Fires when the pointer enters a node. */\n onNodeHover?: (node: cytoscape.NodeSingular) => void;\n /** Fires when the pointer leaves a node. */\n onNodeLeave?: () => void;\n /** Slot rendered over the graph (e.g., a `<GraphInspector>`). Positioned top-right. */\n inspector?: ReactNode;\n /** Overrides for the stylesheet builder. */\n styleOptions?: BuildStylesheetOptions;\n /** Accessible label for the container. */\n 'aria-label'?: string;\n}\n\nconst DEFAULT_LAYOUT: cytoscape.LayoutOptions = { name: 'preset' };\n\nexport const GraphCanvas = forwardRef<GraphCanvasHandle, GraphCanvasProps>(function GraphCanvas(\n {\n engine,\n elements,\n layout = DEFAULT_LAYOUT,\n onSelect,\n onClearSelection,\n onNodeHover,\n onNodeLeave,\n inspector,\n styleOptions,\n className,\n 'aria-label': ariaLabel = 'Graph canvas',\n ...props\n },\n forwardedRef,\n) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const cyRef = useRef<cytoscape.Core | null>(null);\n\n // Create the cytoscape instance once on mount.\n useEffect(() => {\n if (!containerRef.current) return undefined;\n const cy = engine({\n container: containerRef.current,\n elements,\n layout,\n style: buildShipItStylesheet(styleOptions),\n });\n cyRef.current = cy;\n return () => {\n cy.destroy();\n cyRef.current = null;\n };\n // We intentionally re-create on engine swap or container remount only.\n // Elements / layout / style updates are handled in the effects below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [engine]);\n\n // Keep elements in sync.\n useEffect(() => {\n const cy = cyRef.current;\n if (!cy) return;\n cy.json({ elements });\n }, [elements]);\n\n // Re-run layout when the layout config changes.\n useEffect(() => {\n const cy = cyRef.current;\n if (!cy) return;\n cy.layout(layout).run();\n }, [layout]);\n\n const { refresh: refreshStyles } = useShipItStylesheet(cyRef, styleOptions);\n\n // Wire selection events. `engine` is in the deps so the listeners re-bind\n // whenever the upstream effect destroys + recreates the Cytoscape instance —\n // without it, an `engine` swap would leave the new `cy` without handlers.\n useEffect(() => {\n const cy = cyRef.current;\n if (!cy) return undefined;\n const handleSelect = (event: cytoscape.EventObject) => {\n if (event.target?.isNode?.()) {\n onSelect?.(event.target as cytoscape.NodeSingular);\n }\n };\n const handleBackgroundTap = (event: cytoscape.EventObject) => {\n if (event.target === cy) onClearSelection?.();\n };\n const handleEnter = (event: cytoscape.EventObject) => {\n onNodeHover?.(event.target as cytoscape.NodeSingular);\n };\n const handleLeave = () => onNodeLeave?.();\n cy.on('tap', 'node', handleSelect);\n cy.on('tap', handleBackgroundTap);\n cy.on('mouseover', 'node', handleEnter);\n cy.on('mouseout', 'node', handleLeave);\n return () => {\n cy.off('tap', 'node', handleSelect);\n cy.off('tap', handleBackgroundTap);\n cy.off('mouseover', 'node', handleEnter);\n cy.off('mouseout', 'node', handleLeave);\n };\n }, [engine, onSelect, onClearSelection, onNodeHover, onNodeLeave]);\n\n // Expose the imperative handle via getters so consumers always read the live\n // `cyRef.current`. Snapshotting `cyRef.current` here would freeze it at\n // `null` because the instance is assigned inside an effect that runs *after*\n // the initial render — Copilot/Claude both flagged this.\n useImperativeHandle(\n forwardedRef,\n (): GraphCanvasHandle => ({\n get cy() {\n return cyRef.current;\n },\n refreshStyles,\n }),\n [refreshStyles],\n );\n\n return (\n <div\n role=\"region\"\n aria-label={ariaLabel}\n className={['relative h-full w-full', className].filter(Boolean).join(' ')}\n {...props}\n >\n <div ref={containerRef} className=\"absolute inset-0\" />\n {inspector && <div className=\"absolute top-4 right-4 z-10\">{inspector}</div>}\n </div>\n );\n});\n\nGraphCanvas.displayName = 'GraphCanvas';\n"],"mappings":"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","names":["useEffect","GraphCanvas","useEffect"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ship-it-ui/cytoscape",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Cytoscape adapter for the Ship-It design system. Token-driven stylesheet, theme-aware re-resolver hook, and a `<GraphCanvas>` wrapper that owns the data-theme ↔ stylesheet ↔ inspector dance.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://ship-it-ops.github.io/ship-it-design/",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/ship-it-ops/ship-it-design/issues"
|
|
9
|
+
},
|
|
10
|
+
"author": "Ship-It Ops",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"design-system",
|
|
13
|
+
"cytoscape",
|
|
14
|
+
"graph",
|
|
15
|
+
"knowledge-graph",
|
|
16
|
+
"shipit"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/ship-it-ops/ship-it-design.git",
|
|
21
|
+
"directory": "packages/cytoscape"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"module": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"require": "./dist/index.cjs"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"sideEffects": false,
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public",
|
|
41
|
+
"provenance": true
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"cytoscape": "^3.28.0",
|
|
45
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
46
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
47
|
+
"@ship-it-ui/shipit": "0.0.4",
|
|
48
|
+
"@ship-it-ui/ui": "0.0.4"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
52
|
+
"@testing-library/react": "^16.0.1",
|
|
53
|
+
"@types/react": "^18.3.12",
|
|
54
|
+
"@types/react-dom": "^18.3.1",
|
|
55
|
+
"axe-core": "^4.10.2",
|
|
56
|
+
"cytoscape": "^3.30.0",
|
|
57
|
+
"esbuild-plugin-preserve-directives": "^0.0.11",
|
|
58
|
+
"jsdom": "^29.1.1",
|
|
59
|
+
"react": "^18.3.1",
|
|
60
|
+
"react-dom": "^18.3.1",
|
|
61
|
+
"tsup": "^8.3.0",
|
|
62
|
+
"typescript": "^5.6.3",
|
|
63
|
+
"vitest": "^2.1.3",
|
|
64
|
+
"vitest-axe": "^0.1.0",
|
|
65
|
+
"@ship-it-ui/shipit": "0.0.4",
|
|
66
|
+
"@ship-it-ui/tokens": "0.0.4",
|
|
67
|
+
"@ship-it-ui/tsconfig": "0.0.1",
|
|
68
|
+
"@ship-it-ui/ui": "0.0.4",
|
|
69
|
+
"@ship-it-ui/eslint-config": "0.0.1"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "tsup",
|
|
73
|
+
"dev": "tsup --watch",
|
|
74
|
+
"lint": "eslint src",
|
|
75
|
+
"lint:fix": "eslint src --fix",
|
|
76
|
+
"test": "vitest run --passWithNoTests",
|
|
77
|
+
"test:watch": "vitest",
|
|
78
|
+
"typecheck": "tsc --noEmit",
|
|
79
|
+
"clean": "rm -rf dist .turbo"
|
|
80
|
+
}
|
|
81
|
+
}
|