@xenolithengine/graph-react 0.7.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/components.d.ts +43 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +87 -0
- package/dist/components.js.map +1 -0
- package/dist/context.d.ts +15 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +23 -0
- package/dist/context.js.map +1 -0
- package/dist/events-map.d.ts +33 -0
- package/dist/events-map.d.ts.map +1 -0
- package/dist/events-map.js +28 -0
- package/dist/events-map.js.map +1 -0
- package/dist/hooks.d.ts +37 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +112 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -0
- package/dist/react-widget.d.ts +18 -0
- package/dist/react-widget.d.ts.map +1 -0
- package/dist/react-widget.js +21 -0
- package/dist/react-widget.js.map +1 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 strelok2012
|
|
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/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @xenolithengine/graph-react
|
|
2
|
+
|
|
3
|
+
[](https://github.com/XenolithEngine/xenolith-graph#status)
|
|
4
|
+
[](https://github.com/XenolithEngine/xenolith-graph/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
React adapter for XenolithGraph — `<XenolithGraph>` component, `useEditor` / `useNodes` / `useEdges` hooks, in-editor panels.
|
|
7
|
+
|
|
8
|
+
> **Beta** — public API in `STABLE-API.md` is the surface we plan to freeze, but it is **NOT frozen yet** — breaking changes can land at any point before v1.0. If you adopt now, pin an exact version.
|
|
9
|
+
|
|
10
|
+
Part of [XenolithGraph](https://github.com/XenolithEngine/xenolith-graph) — an AI-native, embeddable node-graph editor for the web with its own visual design language (Xen).
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @xenolithengine/graph-react pixi.js
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Peer deps: `react >= 18`, `react-dom >= 18`, `pixi.js@^8.6.0`. WebGL/client-only — render the component only in the browser (the entry is marked `'use client'` for Next.js App Router).
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { XenolithGraph, XenolithControls, XenolithMiniMap } from '@xenolithengine/graph-react'
|
|
24
|
+
|
|
25
|
+
export function Editor() {
|
|
26
|
+
return (
|
|
27
|
+
<XenolithGraph
|
|
28
|
+
style={{ width: '100%', height: '100vh' }}
|
|
29
|
+
graph={savedGraph}
|
|
30
|
+
minimap
|
|
31
|
+
onNodeClick={({ nodeId }) => console.log(nodeId)}
|
|
32
|
+
onSelectionChange={({ nodeIds }) => console.log(nodeIds)}
|
|
33
|
+
onReady={(editor) => editor.fitView()}
|
|
34
|
+
>
|
|
35
|
+
<XenolithControls position="bottom-left" />
|
|
36
|
+
<XenolithMiniMap position="bottom-right" />
|
|
37
|
+
</XenolithGraph>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## What's exported
|
|
43
|
+
|
|
44
|
+
- `<XenolithGraph>` — main component (props extend `XenolithProps` + `EventCallbacks`)
|
|
45
|
+
- In-editor panels: `<XenolithPanel>`, `<XenolithButton>`, `<XenolithControls>`, `<XenolithMiniMap>`
|
|
46
|
+
- Hooks: `useEditor`, `useXenolithEditor`, `useNodes`, `useEdges`, `useSelection`, `useViewport`, `useGraphJSON`, `useEditorEvent`, `useUndoRedo`
|
|
47
|
+
- Lower-level: `useXenolith(hostRef, props)`, `XenolithContext`, `EVENT_PROP`
|
|
48
|
+
- Custom widgets: `reactWidget`, `WidgetProps`
|
|
49
|
+
|
|
50
|
+
## Docs
|
|
51
|
+
|
|
52
|
+
- [React guide](https://xenolithengine.github.io/xenolith-graph/guides/react/)
|
|
53
|
+
- [API reference](https://xenolithengine.github.io/xenolith-graph/guides/api/) — every method exposed by `XenolithEditor`
|
|
54
|
+
- [GitHub](https://github.com/XenolithEngine/xenolith-graph)
|
|
55
|
+
|
|
56
|
+
MIT © XenolithEngine
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode, ReactElement, ReactPortal, ButtonHTMLAttributes } from 'react';
|
|
2
|
+
import type { MinimapPosition, ControlsOptions } from '@xenolithengine/graph-editor';
|
|
3
|
+
export type PanelPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
4
|
+
export interface XenolithPanelProps {
|
|
5
|
+
position?: PanelPosition;
|
|
6
|
+
/** Drop the default frosted card chrome and only anchor the children. */
|
|
7
|
+
bare?: boolean;
|
|
8
|
+
className?: string;
|
|
9
|
+
style?: CSSProperties;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* An absolutely-positioned overlay anchored inside the editor (React Flow's `<Panel>` analogue).
|
|
14
|
+
* Renders into the editor's screen-anchored overlay layer via a portal, so debug readouts, controls,
|
|
15
|
+
* legends — any React UI — live *inside* the graph. Inherits the theme's `--xeno-*` vars from the host.
|
|
16
|
+
*/
|
|
17
|
+
export declare function XenolithPanel({ position, bare, className, style, children }: XenolithPanelProps): ReactPortal | null;
|
|
18
|
+
export interface XenolithButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
19
|
+
/** Render in the accent colour (filled) instead of the neutral control surface. */
|
|
20
|
+
active?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A control button themed from the editor's `--xeno-*` vars, so it tracks the active theme (gold in
|
|
24
|
+
* Xen, cyan in Liquid Glass) for free. Use inside a `<XenolithPanel>` / `<XenolithControls>`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function XenolithButton({ active, style, children, disabled, ...rest }: XenolithButtonProps): ReactElement;
|
|
27
|
+
export type XenolithControlsProps = ControlsOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Built-in viewport controls (zoom / fit / reset / undo·redo / save / lock). Declarative wrapper over
|
|
30
|
+
* the editor's vanilla `setControls` — the toolbar itself is rendered by the CORE in `overlayRoot`
|
|
31
|
+
* (shared by every framework, themed via `--xeno-*`), so this component renders no DOM of its own.
|
|
32
|
+
*/
|
|
33
|
+
export declare function XenolithControls(props: XenolithControlsProps): null;
|
|
34
|
+
export interface XenolithMiniMapProps {
|
|
35
|
+
position?: MinimapPosition;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Declarative minimap toggle (React Flow's `<MiniMap>` analogue). The minimap itself is rendered in
|
|
39
|
+
* WebGL by the editor; mounting this component enables it and sets its anchor, and unmounting hides
|
|
40
|
+
* it. Renders no DOM of its own.
|
|
41
|
+
*/
|
|
42
|
+
export declare function XenolithMiniMap({ position }: XenolithMiniMapProps): null;
|
|
43
|
+
//# sourceMappingURL=components.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/components.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AACtG,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAGpF,MAAM,MAAM,aAAa,GACrB,UAAU,GAAG,YAAY,GAAG,WAAW,GACvC,aAAa,GAAG,eAAe,GAAG,cAAc,CAAA;AAWpD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,yEAAyE;IACzE,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAqB,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,kBAAkB,GAAG,WAAW,GAAG,IAAI,CAuBjI;AAED,MAAM,WAAW,mBAAoB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IAClF,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,mBAAmB,GAAG,YAAY,CAwBhH;AAED,MAAM,MAAM,qBAAqB,GAAG,eAAe,CAAA;AAEnD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,IAAI,CAUnE;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,eAAe,CAAA;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,QAAyB,EAAE,EAAE,oBAAoB,GAAG,IAAI,CASzF"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { useXenolithEditor } from './context.js';
|
|
6
|
+
const POS = {
|
|
7
|
+
'top-left': { top: 12, left: 12 },
|
|
8
|
+
'top-center': { top: 12, left: '50%', transform: 'translateX(-50%)' },
|
|
9
|
+
'top-right': { top: 12, right: 12 },
|
|
10
|
+
'bottom-left': { bottom: 12, left: 12 },
|
|
11
|
+
'bottom-center': { bottom: 12, left: '50%', transform: 'translateX(-50%)' },
|
|
12
|
+
'bottom-right': { bottom: 12, right: 12 },
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* An absolutely-positioned overlay anchored inside the editor (React Flow's `<Panel>` analogue).
|
|
16
|
+
* Renders into the editor's screen-anchored overlay layer via a portal, so debug readouts, controls,
|
|
17
|
+
* legends — any React UI — live *inside* the graph. Inherits the theme's `--xeno-*` vars from the host.
|
|
18
|
+
*/
|
|
19
|
+
export function XenolithPanel({ position = 'top-left', bare, className, style, children }) {
|
|
20
|
+
const editor = useXenolithEditor();
|
|
21
|
+
if (!editor)
|
|
22
|
+
return null;
|
|
23
|
+
const chrome = bare ? {} : {
|
|
24
|
+
background: 'var(--xeno-panel)',
|
|
25
|
+
border: '1px solid var(--xeno-border)',
|
|
26
|
+
borderRadius: 10,
|
|
27
|
+
padding: 10,
|
|
28
|
+
color: 'var(--xeno-text)',
|
|
29
|
+
boxShadow: '0 6px 24px rgba(0,0,0,0.35)',
|
|
30
|
+
fontFamily: 'system-ui, sans-serif',
|
|
31
|
+
fontSize: 13,
|
|
32
|
+
};
|
|
33
|
+
return createPortal(_jsx("div", { "data-xeno-panel": "", className: className, style: { position: 'absolute', pointerEvents: 'auto', ...POS[position], ...chrome, ...style }, children: children }), editor.overlayRoot);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A control button themed from the editor's `--xeno-*` vars, so it tracks the active theme (gold in
|
|
37
|
+
* Xen, cyan in Liquid Glass) for free. Use inside a `<XenolithPanel>` / `<XenolithControls>`.
|
|
38
|
+
*/
|
|
39
|
+
export function XenolithButton({ active, style, children, disabled, ...rest }) {
|
|
40
|
+
return (_jsx("button", { type: "button", "data-xeno-button": "", disabled: disabled, style: {
|
|
41
|
+
font: 'inherit',
|
|
42
|
+
fontSize: 13,
|
|
43
|
+
lineHeight: 1,
|
|
44
|
+
padding: '7px 12px',
|
|
45
|
+
cursor: disabled ? 'default' : 'pointer',
|
|
46
|
+
opacity: disabled ? 0.4 : 1,
|
|
47
|
+
borderRadius: 8,
|
|
48
|
+
border: `1px solid ${active ? 'var(--xeno-accent)' : 'var(--xeno-border)'}`,
|
|
49
|
+
background: active ? 'var(--xeno-accent)' : 'var(--xeno-elevated)',
|
|
50
|
+
color: active ? 'var(--xeno-canvas)' : 'var(--xeno-text)',
|
|
51
|
+
...style,
|
|
52
|
+
}, ...rest, children: children }));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Built-in viewport controls (zoom / fit / reset / undo·redo / save / lock). Declarative wrapper over
|
|
56
|
+
* the editor's vanilla `setControls` — the toolbar itself is rendered by the CORE in `overlayRoot`
|
|
57
|
+
* (shared by every framework, themed via `--xeno-*`), so this component renders no DOM of its own.
|
|
58
|
+
*/
|
|
59
|
+
export function XenolithControls(props) {
|
|
60
|
+
const editor = useXenolithEditor();
|
|
61
|
+
const key = JSON.stringify(props);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!editor)
|
|
64
|
+
return;
|
|
65
|
+
editor.setControls(props);
|
|
66
|
+
return () => editor.setControls(false);
|
|
67
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
68
|
+
}, [editor, key]);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Declarative minimap toggle (React Flow's `<MiniMap>` analogue). The minimap itself is rendered in
|
|
73
|
+
* WebGL by the editor; mounting this component enables it and sets its anchor, and unmounting hides
|
|
74
|
+
* it. Renders no DOM of its own.
|
|
75
|
+
*/
|
|
76
|
+
export function XenolithMiniMap({ position = 'bottom-right' }) {
|
|
77
|
+
const editor = useXenolithEditor();
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!editor)
|
|
80
|
+
return;
|
|
81
|
+
editor.setMinimapVisible(true);
|
|
82
|
+
editor.setMinimapPosition(position);
|
|
83
|
+
return () => { editor.setMinimapVisible(false); };
|
|
84
|
+
}, [editor, position]);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=components.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.js","sourceRoot":"","sources":["../src/components.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { XenolithEditor } from '@xenolithengine/graph-editor';
|
|
2
|
+
/** The live editor for the enclosing `<XenolithGraph>`, or `null` until it has mounted. In-editor
|
|
3
|
+
* components (`<XenolithPanel>`, `<XenolithControls>`, hooks) read it from here. */
|
|
4
|
+
export declare const XenolithContext: import("react").Context<XenolithEditor | null>;
|
|
5
|
+
/** The editor instance from the nearest `<XenolithGraph>` ancestor — `null` until it has mounted
|
|
6
|
+
* OR if the component is rendered outside any `<XenolithGraph>`. Use `useEditor()` when the
|
|
7
|
+
* component is GUARANTEED to live inside an editor — that variant throws on misuse rather than
|
|
8
|
+
* leaking nulls into your render. */
|
|
9
|
+
export declare function useXenolithEditor(): XenolithEditor | null;
|
|
10
|
+
/** Strict variant: returns the live editor, throwing if the component is rendered outside a
|
|
11
|
+
* `<XenolithGraph>` ancestor or before the editor has finished mounting. Prefer this when
|
|
12
|
+
* writing in-editor components — removes null-check noise at every call site and turns a
|
|
13
|
+
* misuse from a silent bug into a clear error. */
|
|
14
|
+
export declare function useEditor(): XenolithEditor;
|
|
15
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAElE;qFACqF;AACrF,eAAO,MAAM,eAAe,gDAA6C,CAAA;AAEzE;;;sCAGsC;AACtC,wBAAgB,iBAAiB,IAAI,cAAc,GAAG,IAAI,CAEzD;AAED;;;mDAGmD;AACnD,wBAAgB,SAAS,IAAI,cAAc,CAI1C"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
/** The live editor for the enclosing `<XenolithGraph>`, or `null` until it has mounted. In-editor
|
|
4
|
+
* components (`<XenolithPanel>`, `<XenolithControls>`, hooks) read it from here. */
|
|
5
|
+
export const XenolithContext = createContext(null);
|
|
6
|
+
/** The editor instance from the nearest `<XenolithGraph>` ancestor — `null` until it has mounted
|
|
7
|
+
* OR if the component is rendered outside any `<XenolithGraph>`. Use `useEditor()` when the
|
|
8
|
+
* component is GUARANTEED to live inside an editor — that variant throws on misuse rather than
|
|
9
|
+
* leaking nulls into your render. */
|
|
10
|
+
export function useXenolithEditor() {
|
|
11
|
+
return useContext(XenolithContext);
|
|
12
|
+
}
|
|
13
|
+
/** Strict variant: returns the live editor, throwing if the component is rendered outside a
|
|
14
|
+
* `<XenolithGraph>` ancestor or before the editor has finished mounting. Prefer this when
|
|
15
|
+
* writing in-editor components — removes null-check noise at every call site and turns a
|
|
16
|
+
* misuse from a silent bug into a clear error. */
|
|
17
|
+
export function useEditor() {
|
|
18
|
+
const editor = useContext(XenolithContext);
|
|
19
|
+
if (!editor)
|
|
20
|
+
throw new Error('useEditor() called outside <XenolithGraph> or before the editor mounted. Use useXenolithEditor() if a null result is expected here.');
|
|
21
|
+
return editor;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AACZ,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAGjD;qFACqF;AACrF,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAwB,IAAI,CAAC,CAAA;AAEzE;;;sCAGsC;AACtC,MAAM,UAAU,iBAAiB;IAC/B,OAAO,UAAU,CAAC,eAAe,CAAC,CAAA;AACpC,CAAC;AAED;;;mDAGmD;AACnD,MAAM,UAAU,SAAS;IACvB,MAAM,MAAM,GAAG,UAAU,CAAC,eAAe,CAAC,CAAA;IAC1C,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,qIAAqI,CAAC,CAAA;IACnK,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { EditorEvents } from '@xenolithengine/graph-editor';
|
|
2
|
+
/** Maps each public editor event to its idiomatic React callback prop name. */
|
|
3
|
+
export declare const EVENT_PROP: {
|
|
4
|
+
readonly 'node:added': "onNodeAdded";
|
|
5
|
+
readonly 'node:removed': "onNodeRemoved";
|
|
6
|
+
readonly 'node:removing': "onNodeRemoving";
|
|
7
|
+
readonly 'node:moved': "onNodeMoved";
|
|
8
|
+
readonly 'node:click': "onNodeClick";
|
|
9
|
+
readonly 'node:clicking': "onNodeClicking";
|
|
10
|
+
readonly 'node:drop': "onNodeDrop";
|
|
11
|
+
readonly 'edge:connected': "onEdgeConnected";
|
|
12
|
+
readonly 'edge:disconnected': "onEdgeDisconnected";
|
|
13
|
+
readonly 'edge:connecting': "onEdgeConnecting";
|
|
14
|
+
readonly 'edge:disconnecting': "onEdgeDisconnecting";
|
|
15
|
+
readonly 'selection:changed': "onSelectionChange";
|
|
16
|
+
readonly 'viewport:changed': "onViewportChange";
|
|
17
|
+
readonly 'widget:changed': "onWidgetChange";
|
|
18
|
+
readonly 'widget:action': "onWidgetAction";
|
|
19
|
+
readonly 'graph:loaded': "onGraphLoad";
|
|
20
|
+
readonly 'history:changed': "onHistoryChange";
|
|
21
|
+
readonly 'dive:changed': "onDiveChange";
|
|
22
|
+
readonly 'sidebar:opened': "onSidebarOpen";
|
|
23
|
+
readonly 'sidebar:closed': "onSidebarClose";
|
|
24
|
+
readonly 'livemode:changed': "onLiveModeChange";
|
|
25
|
+
readonly 'node:contextmenu': "onNodeContextMenu";
|
|
26
|
+
readonly 'edge:contextmenu': "onEdgeContextMenu";
|
|
27
|
+
readonly 'canvas:contextmenu': "onCanvasContextMenu";
|
|
28
|
+
};
|
|
29
|
+
/** The React callback props derived from {@link EVENT_PROP} — one optional handler per event. */
|
|
30
|
+
export type EventCallbacks = {
|
|
31
|
+
[E in keyof EditorEvents as (typeof EVENT_PROP)[E]]?: (payload: EditorEvents[E]) => void;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=events-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events-map.d.ts","sourceRoot":"","sources":["../src/events-map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAEhE,+EAA+E;AAC/E,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;CAyBgC,CAAA;AAEvD,iGAAiG;AACjG,MAAM,MAAM,cAAc,GAAG;KAC1B,CAAC,IAAI,MAAM,YAAY,IAAI,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI;CACzF,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Maps each public editor event to its idiomatic React callback prop name. */
|
|
2
|
+
export const EVENT_PROP = {
|
|
3
|
+
'node:added': 'onNodeAdded',
|
|
4
|
+
'node:removed': 'onNodeRemoved',
|
|
5
|
+
'node:removing': 'onNodeRemoving',
|
|
6
|
+
'node:moved': 'onNodeMoved',
|
|
7
|
+
'node:click': 'onNodeClick',
|
|
8
|
+
'node:clicking': 'onNodeClicking',
|
|
9
|
+
'node:drop': 'onNodeDrop',
|
|
10
|
+
'edge:connected': 'onEdgeConnected',
|
|
11
|
+
'edge:disconnected': 'onEdgeDisconnected',
|
|
12
|
+
'edge:connecting': 'onEdgeConnecting',
|
|
13
|
+
'edge:disconnecting': 'onEdgeDisconnecting',
|
|
14
|
+
'selection:changed': 'onSelectionChange',
|
|
15
|
+
'viewport:changed': 'onViewportChange',
|
|
16
|
+
'widget:changed': 'onWidgetChange',
|
|
17
|
+
'widget:action': 'onWidgetAction',
|
|
18
|
+
'graph:loaded': 'onGraphLoad',
|
|
19
|
+
'history:changed': 'onHistoryChange',
|
|
20
|
+
'dive:changed': 'onDiveChange',
|
|
21
|
+
'sidebar:opened': 'onSidebarOpen',
|
|
22
|
+
'sidebar:closed': 'onSidebarClose',
|
|
23
|
+
'livemode:changed': 'onLiveModeChange',
|
|
24
|
+
'node:contextmenu': 'onNodeContextMenu',
|
|
25
|
+
'edge:contextmenu': 'onEdgeContextMenu',
|
|
26
|
+
'canvas:contextmenu': 'onCanvasContextMenu',
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=events-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events-map.js","sourceRoot":"","sources":["../src/events-map.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,YAAY,EAAE,aAAa;IAC3B,cAAc,EAAE,eAAe;IAC/B,eAAe,EAAE,gBAAgB;IACjC,YAAY,EAAE,aAAa;IAC3B,YAAY,EAAE,aAAa;IAC3B,eAAe,EAAE,gBAAgB;IACjC,WAAW,EAAE,YAAY;IACzB,gBAAgB,EAAE,iBAAiB;IACnC,mBAAmB,EAAE,oBAAoB;IACzC,iBAAiB,EAAE,kBAAkB;IACrC,oBAAoB,EAAE,qBAAqB;IAC3C,mBAAmB,EAAE,mBAAmB;IACxC,kBAAkB,EAAE,kBAAkB;IACtC,gBAAgB,EAAE,gBAAgB;IAClC,eAAe,EAAE,gBAAgB;IACjC,cAAc,EAAE,aAAa;IAC7B,iBAAiB,EAAE,iBAAiB;IACpC,cAAc,EAAE,cAAc;IAC9B,gBAAgB,EAAE,eAAe;IACjC,gBAAgB,EAAE,gBAAgB;IAClC,kBAAkB,EAAE,kBAAkB;IACtC,kBAAkB,EAAI,mBAAmB;IACzC,kBAAkB,EAAI,mBAAmB;IACzC,oBAAoB,EAAE,qBAAqB;CACU,CAAA"}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { EditorEvents, ViewportState, Node, Edge, NodeId, XenolithGraphV1 } from '@xenolithengine/graph-editor';
|
|
2
|
+
/** Live array of nodes; re-renders on add/remove/move, load and undo/redo. */
|
|
3
|
+
export declare const useNodes: () => readonly Node[];
|
|
4
|
+
/** Live array of edges; re-renders on connect/disconnect, node removal, load and undo/redo. */
|
|
5
|
+
export declare const useEdges: () => readonly Edge[];
|
|
6
|
+
/** Live selection (node ids); re-renders on selection change. */
|
|
7
|
+
export declare const useSelection: () => readonly NodeId[];
|
|
8
|
+
/** Live viewport (`x`, `y`, `zoom`); re-renders on pan/zoom. */
|
|
9
|
+
export declare const useViewport: () => ViewportState;
|
|
10
|
+
/** Live serialized graph (xenolith.v1); recomputes on any graph mutation, load and undo/redo. */
|
|
11
|
+
export declare const useGraphJSON: () => XenolithGraphV1 | null;
|
|
12
|
+
/**
|
|
13
|
+
* Subscribe to a single editor event from a React component. Handler is auto-rebound when the
|
|
14
|
+
* editor changes; unsubscribed on unmount. Use this for events that don't have a dedicated hook —
|
|
15
|
+
* `node:click`, `edge:disconnected`, `sidebar:opened`, `livemode:changed`, `node:drop`, the four
|
|
16
|
+
* preventable `*-ing` events, etc.
|
|
17
|
+
*
|
|
18
|
+
* The handler is captured by closure each render — no deps array needed; the latest closure is
|
|
19
|
+
* always called. This matches `useSyncExternalStore` semantics and avoids stale-closure footguns.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* useEditorEvent('node:removing', (p) => { if (p.nodeId === lockedId) p.cancel() })
|
|
23
|
+
* useEditorEvent('widget:changed', (p) => setLocalCopy(p.value))
|
|
24
|
+
*/
|
|
25
|
+
export declare function useEditorEvent<E extends keyof EditorEvents>(event: E, handler: (payload: EditorEvents[E]) => void): void;
|
|
26
|
+
/**
|
|
27
|
+
* `{ canUndo, canRedo, undo, redo }` — live undo/redo state + stable callable handles. Use for
|
|
28
|
+
* toolbars: `<button disabled={!canUndo} onClick={undo}>Undo</button>`. Re-renders whenever the
|
|
29
|
+
* editor's undo/redo log changes.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useUndoRedo(): {
|
|
32
|
+
canUndo: boolean;
|
|
33
|
+
canRedo: boolean;
|
|
34
|
+
undo: () => boolean;
|
|
35
|
+
redo: () => boolean;
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAkB,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAoEpI,8EAA8E;AAC9E,eAAO,MAAM,QAAQ,EAAE,MAAM,SAAS,IAAI,EAIzC,CAAA;AAED,+FAA+F;AAC/F,eAAO,MAAM,QAAQ,EAAE,MAAM,SAAS,IAAI,EAIzC,CAAA;AAED,iEAAiE;AACjE,eAAO,MAAM,YAAY,EAAE,MAAM,SAAS,MAAM,EAI/C,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,WAAW,EAAE,MAAM,aAI/B,CAAA;AAED,iGAAiG;AACjG,eAAO,MAAM,YAAY,EAAE,MAAM,eAAe,GAAG,IAIlD,CAAA;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,YAAY,EACzD,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,GAC1C,IAAI,CAQN;AAID;;;;GAIG;AACH,wBAAgB,WAAW,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAA;CAAE,CAa9G"}
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useRef, useSyncExternalStore } from 'react';
|
|
3
|
+
import { useXenolithEditor } from './context.js';
|
|
4
|
+
// Canonical pattern: subscribe to an external (non-React) mutable store via `useSyncExternalStore`.
|
|
5
|
+
// Replaces the prior `useState + useEffect + subscribe` shape, which the React docs flag as the
|
|
6
|
+
// anti-pattern it explicitly fixes. See:
|
|
7
|
+
// - https://react.dev/reference/react/useSyncExternalStore
|
|
8
|
+
// - https://www.epicreact.dev/use-sync-external-store-demystified-for-practical-react-development-w5ac0
|
|
9
|
+
// Contract: `getSnapshot` MUST return the same reference if the underlying data hasn't changed —
|
|
10
|
+
// otherwise React tears down and re-renders forever. We cache by editor identity in a ref and
|
|
11
|
+
// only mutate the cache when an editor event actually fires.
|
|
12
|
+
function makeEditorStoreHook(events, compute, fallback) {
|
|
13
|
+
return function useEditorStore() {
|
|
14
|
+
const editor = useXenolithEditor();
|
|
15
|
+
const cacheRef = useRef({ editor: null, value: fallback });
|
|
16
|
+
const subscribe = useCallback((onChange) => {
|
|
17
|
+
if (!editor)
|
|
18
|
+
return () => { };
|
|
19
|
+
// Refresh the cache on subscribe so the first getSnapshot read returns up-to-date data.
|
|
20
|
+
cacheRef.current = { editor, value: compute(editor) };
|
|
21
|
+
// Coalesce a burst of events (e.g. a 1000-node transaction firing node:added 1000×) into
|
|
22
|
+
// ONE recompute + ONE React notification. Without this, the hook re-walks the graph on every
|
|
23
|
+
// event and a `+1000` button costs ~1M ops just for the snapshot, before React even renders.
|
|
24
|
+
let scheduled = false;
|
|
25
|
+
const update = () => {
|
|
26
|
+
if (scheduled)
|
|
27
|
+
return;
|
|
28
|
+
scheduled = true;
|
|
29
|
+
queueMicrotask(() => {
|
|
30
|
+
scheduled = false;
|
|
31
|
+
if (cacheRef.current.editor !== editor)
|
|
32
|
+
return; // unsubscribed mid-flight
|
|
33
|
+
cacheRef.current = { editor, value: compute(editor) };
|
|
34
|
+
onChange();
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const offs = events.map((ev) => editor.on(ev, update));
|
|
38
|
+
return () => { for (const off of offs)
|
|
39
|
+
off(); };
|
|
40
|
+
}, [editor]);
|
|
41
|
+
const getSnapshot = useCallback(() => {
|
|
42
|
+
// Rebuild the cache only when the editor identity itself changes (null → instance, swap),
|
|
43
|
+
// OR (rare) the editor cleared its cache mid-flight. Identity-stable otherwise.
|
|
44
|
+
if (cacheRef.current.editor !== editor) {
|
|
45
|
+
cacheRef.current = { editor, value: editor ? compute(editor) : fallback };
|
|
46
|
+
}
|
|
47
|
+
return cacheRef.current.value;
|
|
48
|
+
}, [editor]);
|
|
49
|
+
return useSyncExternalStore(subscribe, getSnapshot, () => fallback);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const NODE_EVENTS = ['node:added', 'node:removed', 'node:moved', 'graph:loaded', 'history:changed'];
|
|
53
|
+
const EDGE_EVENTS = ['edge:connected', 'edge:disconnected', 'node:removed', 'graph:loaded', 'history:changed'];
|
|
54
|
+
const GRAPH_EVENTS = [
|
|
55
|
+
'node:added', 'node:removed', 'node:moved', 'edge:connected', 'edge:disconnected',
|
|
56
|
+
'widget:changed', 'graph:loaded', 'history:changed',
|
|
57
|
+
];
|
|
58
|
+
const EMPTY_NODES = Object.freeze([]);
|
|
59
|
+
const EMPTY_EDGES = Object.freeze([]);
|
|
60
|
+
const EMPTY_SELECTION = Object.freeze([]);
|
|
61
|
+
const DEFAULT_VIEWPORT = Object.freeze({ x: 0, y: 0, zoom: 1 });
|
|
62
|
+
/** Live array of nodes; re-renders on add/remove/move, load and undo/redo. */
|
|
63
|
+
export const useNodes = makeEditorStoreHook(NODE_EVENTS, (e) => Object.freeze(Array.from(e.graph.nodes())), EMPTY_NODES);
|
|
64
|
+
/** Live array of edges; re-renders on connect/disconnect, node removal, load and undo/redo. */
|
|
65
|
+
export const useEdges = makeEditorStoreHook(EDGE_EVENTS, (e) => Object.freeze(Array.from(e.graph.edges())), EMPTY_EDGES);
|
|
66
|
+
/** Live selection (node ids); re-renders on selection change. */
|
|
67
|
+
export const useSelection = makeEditorStoreHook(['selection:changed'], (e) => Object.freeze([...e.selection.ids()]), EMPTY_SELECTION);
|
|
68
|
+
/** Live viewport (`x`, `y`, `zoom`); re-renders on pan/zoom. */
|
|
69
|
+
export const useViewport = makeEditorStoreHook(['viewport:changed'], (e) => e.viewport, DEFAULT_VIEWPORT);
|
|
70
|
+
/** Live serialized graph (xenolith.v1); recomputes on any graph mutation, load and undo/redo. */
|
|
71
|
+
export const useGraphJSON = makeEditorStoreHook(GRAPH_EVENTS, (e) => e.toJSON(), null);
|
|
72
|
+
import { useEffect } from 'react';
|
|
73
|
+
/**
|
|
74
|
+
* Subscribe to a single editor event from a React component. Handler is auto-rebound when the
|
|
75
|
+
* editor changes; unsubscribed on unmount. Use this for events that don't have a dedicated hook —
|
|
76
|
+
* `node:click`, `edge:disconnected`, `sidebar:opened`, `livemode:changed`, `node:drop`, the four
|
|
77
|
+
* preventable `*-ing` events, etc.
|
|
78
|
+
*
|
|
79
|
+
* The handler is captured by closure each render — no deps array needed; the latest closure is
|
|
80
|
+
* always called. This matches `useSyncExternalStore` semantics and avoids stale-closure footguns.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* useEditorEvent('node:removing', (p) => { if (p.nodeId === lockedId) p.cancel() })
|
|
84
|
+
* useEditorEvent('widget:changed', (p) => setLocalCopy(p.value))
|
|
85
|
+
*/
|
|
86
|
+
export function useEditorEvent(event, handler) {
|
|
87
|
+
const editor = useXenolithEditor();
|
|
88
|
+
const handlerRef = useRef(handler);
|
|
89
|
+
handlerRef.current = handler;
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!editor)
|
|
92
|
+
return;
|
|
93
|
+
return editor.on(event, (payload) => handlerRef.current(payload));
|
|
94
|
+
}, [editor, event]);
|
|
95
|
+
}
|
|
96
|
+
const UNDO_REDO_EVENTS = ['history:changed'];
|
|
97
|
+
/**
|
|
98
|
+
* `{ canUndo, canRedo, undo, redo }` — live undo/redo state + stable callable handles. Use for
|
|
99
|
+
* toolbars: `<button disabled={!canUndo} onClick={undo}>Undo</button>`. Re-renders whenever the
|
|
100
|
+
* editor's undo/redo log changes.
|
|
101
|
+
*/
|
|
102
|
+
export function useUndoRedo() {
|
|
103
|
+
const editor = useXenolithEditor();
|
|
104
|
+
const state = makeEditorStoreHook(UNDO_REDO_EVENTS, (e) => ({ canUndo: e.history.canUndo, canRedo: e.history.canRedo }), { canUndo: false, canRedo: false })();
|
|
105
|
+
return {
|
|
106
|
+
canUndo: state.canUndo,
|
|
107
|
+
canRedo: state.canRedo,
|
|
108
|
+
undo: useCallback(() => editor?.history.undo() ?? false, [editor]),
|
|
109
|
+
redo: useCallback(() => editor?.history.redo() ?? false, [editor]),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactElement, type ReactNode, type Ref } from 'react';
|
|
2
|
+
import { type XenolithProps } from '@xenolithengine/graph-adapter-core';
|
|
3
|
+
import type { XenolithEditor } from '@xenolithengine/graph-editor';
|
|
4
|
+
import { type EventCallbacks } from './events-map.js';
|
|
5
|
+
export type { EventCallbacks } from './events-map.js';
|
|
6
|
+
export { EVENT_PROP } from './events-map.js';
|
|
7
|
+
export { XenolithContext, useXenolithEditor, useEditor } from './context.js';
|
|
8
|
+
export { XenolithPanel, XenolithButton, XenolithControls, XenolithMiniMap, type PanelPosition, type XenolithPanelProps, type XenolithButtonProps, type XenolithControlsProps, type XenolithMiniMapProps, } from './components.js';
|
|
9
|
+
export { useNodes, useEdges, useSelection, useViewport, useGraphJSON, useEditorEvent, useUndoRedo } from './hooks.js';
|
|
10
|
+
export { reactWidget, type WidgetProps } from './react-widget.js';
|
|
11
|
+
export interface XenolithGraphProps extends XenolithProps, EventCallbacks {
|
|
12
|
+
className?: string;
|
|
13
|
+
style?: CSSProperties;
|
|
14
|
+
/** Called once the editor is mounted and ready. */
|
|
15
|
+
onReady?: (editor: XenolithEditor) => void;
|
|
16
|
+
/** Forwarded to the host div, e.g. to grab the editor imperatively. */
|
|
17
|
+
containerRef?: Ref<HTMLDivElement>;
|
|
18
|
+
/** In-editor overlay UI: `<XenolithPanel>`, `<XenolithControls>`, or any component using the hooks. */
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* `<XenolithGraph>` — idiomatic React wrapper. Pass editor props (`theme`, `graph`, `minimap`, …)
|
|
23
|
+
* and `on*` event callbacks; the component mounts the editor into its div, keeps props in sync, and
|
|
24
|
+
* tears it down on unmount. The editor is WebGL/client-only — render it only in the browser.
|
|
25
|
+
*/
|
|
26
|
+
export declare function XenolithGraph(props: XenolithGraphProps): ReactElement;
|
|
27
|
+
/** Lower-level hook: mount a XenolithGraph editor into `hostRef` and return the binding ref. */
|
|
28
|
+
export declare function useXenolith(hostRef: Ref<HTMLDivElement>, props?: XenolithProps): {
|
|
29
|
+
editorRef: {
|
|
30
|
+
current: XenolithEditor | null;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAIA,OAAO,EAA+B,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,GAAG,EAAE,MAAM,OAAO,CAAA;AACpH,OAAO,EAA2C,KAAK,aAAa,EAAE,MAAM,oCAAoC,CAAA;AAChH,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAChF,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGjE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC5E,OAAO,EACL,aAAa,EAAE,cAAc,EAAE,gBAAgB,EAAE,eAAe,EAChE,KAAK,aAAa,EAAE,KAAK,kBAAkB,EAAE,KAAK,mBAAmB,EACrE,KAAK,qBAAqB,EAAE,KAAK,oBAAoB,GACtD,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACrH,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEjE,MAAM,WAAW,kBAAmB,SAAQ,aAAa,EAAE,cAAc;IACvE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,mDAAmD;IACnD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAA;IAC1C,uEAAuE;IACvE,YAAY,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAA;IAClC,uGAAuG;IACvG,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAUD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,YAAY,CAyDrE;AAED,gGAAgG;AAChG,wBAAgB,WAAW,CACzB,OAAO,EAAE,GAAG,CAAC,cAAc,CAAC,EAC5B,KAAK,GAAE,aAAkB,GACxB;IAAE,SAAS,EAAE;QAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;KAAE,CAAA;CAAE,CAqBnD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
// `'use client'` is REQUIRED — Xenolith is WebGL/PIXI; cannot run as a React Server Component. Without
|
|
4
|
+
// this directive, Next.js App Router fails at the first import. Same applies to react-widget.tsx,
|
|
5
|
+
// hooks.ts, components.tsx, context.ts.
|
|
6
|
+
import { useEffect, useRef, useState } from 'react';
|
|
7
|
+
import { createEditorBinding } from '@xenolithengine/graph-adapter-core';
|
|
8
|
+
import { EVENT_PROP } from './events-map.js';
|
|
9
|
+
import { XenolithContext } from './context.js';
|
|
10
|
+
export { EVENT_PROP } from './events-map.js';
|
|
11
|
+
export { XenolithContext, useXenolithEditor, useEditor } from './context.js';
|
|
12
|
+
export { XenolithPanel, XenolithButton, XenolithControls, XenolithMiniMap, } from './components.js';
|
|
13
|
+
export { useNodes, useEdges, useSelection, useViewport, useGraphJSON, useEditorEvent, useUndoRedo } from './hooks.js';
|
|
14
|
+
export { reactWidget } from './react-widget.js';
|
|
15
|
+
const EDITOR_KEYS = ['theme', 'graph', 'zoomBounds', 'minimap', 'disableGrid', 'snap', 'resizeToWindow', 'fitOnLoad', 'isValidConnection'];
|
|
16
|
+
function pickEditorProps(props) {
|
|
17
|
+
const out = {};
|
|
18
|
+
for (const k of EDITOR_KEYS)
|
|
19
|
+
if (props[k] !== undefined)
|
|
20
|
+
out[k] = props[k];
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `<XenolithGraph>` — idiomatic React wrapper. Pass editor props (`theme`, `graph`, `minimap`, …)
|
|
25
|
+
* and `on*` event callbacks; the component mounts the editor into its div, keeps props in sync, and
|
|
26
|
+
* tears it down on unmount. The editor is WebGL/client-only — render it only in the browser.
|
|
27
|
+
*/
|
|
28
|
+
export function XenolithGraph(props) {
|
|
29
|
+
const hostRef = useRef(null);
|
|
30
|
+
const bindingRef = useRef(null);
|
|
31
|
+
// Editor lives in state so overlay children (panels/controls/hooks) render once it's ready.
|
|
32
|
+
const [editor, setEditor] = useState(null);
|
|
33
|
+
// Latest callbacks, read by the stable subscriptions so changing a handler never re-subscribes.
|
|
34
|
+
const cbRef = useRef(props);
|
|
35
|
+
cbRef.current = props;
|
|
36
|
+
// Mount once. Subscriptions read the live cbRef, so they survive prop changes.
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const host = hostRef.current;
|
|
39
|
+
if (!host)
|
|
40
|
+
return;
|
|
41
|
+
let disposed = false;
|
|
42
|
+
const offs = [];
|
|
43
|
+
void createEditorBinding(host, pickEditorProps(cbRef.current)).then((binding) => {
|
|
44
|
+
if (disposed) {
|
|
45
|
+
binding.destroy();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
bindingRef.current = binding;
|
|
49
|
+
cbRef.current.onReady?.(binding.editor);
|
|
50
|
+
setEditor(binding.editor);
|
|
51
|
+
for (const [event, prop] of Object.entries(EVENT_PROP)) {
|
|
52
|
+
offs.push(binding.on(event, (payload) => {
|
|
53
|
+
;
|
|
54
|
+
cbRef.current[prop]?.(payload);
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
return () => {
|
|
59
|
+
disposed = true;
|
|
60
|
+
for (const off of offs)
|
|
61
|
+
off();
|
|
62
|
+
bindingRef.current?.destroy();
|
|
63
|
+
bindingRef.current = null;
|
|
64
|
+
setEditor(null);
|
|
65
|
+
};
|
|
66
|
+
}, []);
|
|
67
|
+
// Sync editor props on every render (reference-diffed inside the binding).
|
|
68
|
+
useEffect(() => { bindingRef.current?.setProps(pickEditorProps(props)); });
|
|
69
|
+
return (_jsx("div", { ref: (el) => {
|
|
70
|
+
hostRef.current = el;
|
|
71
|
+
if (typeof props.containerRef === 'function')
|
|
72
|
+
props.containerRef(el);
|
|
73
|
+
else if (props.containerRef)
|
|
74
|
+
props.containerRef.current = el;
|
|
75
|
+
}, className: props.className, style: props.style, children: _jsx(XenolithContext.Provider, { value: editor, children: editor ? props.children : null }) }));
|
|
76
|
+
}
|
|
77
|
+
/** Lower-level hook: mount a XenolithGraph editor into `hostRef` and return the binding ref. */
|
|
78
|
+
export function useXenolith(hostRef, props = {}) {
|
|
79
|
+
const editorRef = useRef(null);
|
|
80
|
+
const propsRef = useRef(props);
|
|
81
|
+
propsRef.current = props;
|
|
82
|
+
const bindingRef = useRef(null);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const el = hostRef.current;
|
|
85
|
+
if (!el)
|
|
86
|
+
return;
|
|
87
|
+
let disposed = false;
|
|
88
|
+
void createEditorBinding(el, propsRef.current).then((binding) => {
|
|
89
|
+
if (disposed) {
|
|
90
|
+
binding.destroy();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
bindingRef.current = binding;
|
|
94
|
+
editorRef.current = binding.editor;
|
|
95
|
+
});
|
|
96
|
+
return () => { disposed = true; bindingRef.current?.destroy(); bindingRef.current = null; editorRef.current = null; };
|
|
97
|
+
}, []);
|
|
98
|
+
useEffect(() => { bindingRef.current?.setProps(props); });
|
|
99
|
+
return { editorRef };
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { DomWidgetController } from '@xenolithengine/graph-editor';
|
|
3
|
+
/** Props every React-component widget receives. `value` is the widget's stored value (node.state),
|
|
4
|
+
* `setValue` commits a change (undoable). `accent`/`text`/`muted` come from the active theme. */
|
|
5
|
+
export interface WidgetProps {
|
|
6
|
+
value: unknown;
|
|
7
|
+
setValue: (v: unknown) => void;
|
|
8
|
+
accent: string;
|
|
9
|
+
text: string;
|
|
10
|
+
muted: string;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
14
|
+
/** Bridge a React component into a XenolithGraph custom widget. This is the heart of "bring your
|
|
15
|
+
* own UI": `editor.registerWidget('name', reactWidget(MyComponent))` renders `MyComponent` as the
|
|
16
|
+
* node's widget, with full React state/hooks/libraries available. */
|
|
17
|
+
export declare function reactWidget(Component: (props: WidgetProps) => ReactNode): DomWidgetController;
|
|
18
|
+
//# sourceMappingURL=react-widget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-widget.d.ts","sourceRoot":"","sources":["../src/react-widget.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAA;AAEvE;kGACkG;AAClG,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;sEAEsE;AACtE,wBAAgB,WAAW,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,SAAS,GAAG,mBAAmB,CAe7F"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createRoot } from 'react-dom/client';
|
|
4
|
+
/** Bridge a React component into a XenolithGraph custom widget. This is the heart of "bring your
|
|
5
|
+
* own UI": `editor.registerWidget('name', reactWidget(MyComponent))` renders `MyComponent` as the
|
|
6
|
+
* node's widget, with full React state/hooks/libraries available. */
|
|
7
|
+
export function reactWidget(Component) {
|
|
8
|
+
let root = null;
|
|
9
|
+
let setValue = () => { };
|
|
10
|
+
const toProps = (c) => ({ value: c.value, setValue, accent: c.accent, text: c.text, muted: c.muted, width: c.width, height: c.height });
|
|
11
|
+
return {
|
|
12
|
+
mount(el, c) {
|
|
13
|
+
setValue = c.setValue;
|
|
14
|
+
root = createRoot(el);
|
|
15
|
+
root.render(_jsx(Component, { ...toProps(c) }));
|
|
16
|
+
return () => { root?.unmount(); root = null; };
|
|
17
|
+
},
|
|
18
|
+
update(c) { root?.render(_jsx(Component, { ...toProps(c) })); },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=react-widget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-widget.js","sourceRoot":"","sources":["../src/react-widget.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AACZ,OAAO,EAAE,UAAU,EAAa,MAAM,kBAAkB,CAAA;AAgBxD;;sEAEsE;AACtE,MAAM,UAAU,WAAW,CAAC,SAA4C;IACtE,IAAI,IAAI,GAAgB,IAAI,CAAA;IAC5B,IAAI,QAAQ,GAAyB,GAAG,EAAE,GAAE,CAAC,CAAA;IAC7C,MAAM,OAAO,GAAG,CAAC,CAAiG,EAAe,EAAE,CACjI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAA;IAElH,OAAO;QACL,KAAK,CAAC,EAAE,EAAE,CAAC;YACT,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAA;YACrB,IAAI,GAAG,UAAU,CAAC,EAAE,CAAC,CAAA;YACrB,IAAI,CAAC,MAAM,CAAC,KAAC,SAAS,OAAK,OAAO,CAAC,CAAC,CAAC,GAAI,CAAC,CAAA;YAC1C,OAAO,GAAG,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,GAAG,IAAI,CAAA,CAAC,CAAC,CAAA;QAC/C,CAAC;QACD,MAAM,CAAC,CAAC,IAAI,IAAI,EAAE,MAAM,CAAC,KAAC,SAAS,OAAK,OAAO,CAAC,CAAC,CAAC,GAAI,CAAC,CAAA,CAAC,CAAC;KAC1D,CAAA;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xenolithengine/graph-react",
|
|
3
|
+
"version": "0.7.0-beta.0",
|
|
4
|
+
"description": "React adapter for XenolithGraph — <XenolithGraph> component + useXenolith hook.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@xenolithengine/graph-adapter-core": "0.7.0-beta.0",
|
|
23
|
+
"@xenolithengine/graph-editor": "0.7.0-beta.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"pixi.js": "^8.6.0",
|
|
27
|
+
"react": ">=18",
|
|
28
|
+
"react-dom": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@testing-library/react": "^16.1.0",
|
|
32
|
+
"@types/react": "^19.0.0",
|
|
33
|
+
"@types/react-dom": "^19",
|
|
34
|
+
"jsdom": "^25.0.0",
|
|
35
|
+
"pixi.js": "^8.6.0",
|
|
36
|
+
"react": "^19.0.0",
|
|
37
|
+
"react-dom": "^19.0.0",
|
|
38
|
+
"typescript": "^5.7.0",
|
|
39
|
+
"vitest": "^3.0.0"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"react",
|
|
43
|
+
"node-editor",
|
|
44
|
+
"node-graph",
|
|
45
|
+
"react-flow-alternative",
|
|
46
|
+
"blueprint",
|
|
47
|
+
"flow",
|
|
48
|
+
"graph-editor"
|
|
49
|
+
],
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/XenolithEngine/xenolith-graph.git",
|
|
56
|
+
"directory": "packages/react"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://xenolithengine.github.io/xenolith-graph/",
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/XenolithEngine/xenolith-graph/issues"
|
|
61
|
+
},
|
|
62
|
+
"author": "XenolithEngine",
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "tsc -b",
|
|
65
|
+
"dev": "tsc -b --watch",
|
|
66
|
+
"test": "vitest run --passWithNoTests",
|
|
67
|
+
"lint": "eslint src",
|
|
68
|
+
"typecheck": "tsc --noEmit"
|
|
69
|
+
}
|
|
70
|
+
}
|