@nesso-how/graph 0.1.0-alpha.33 → 0.1.0-alpha.35

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/README.md CHANGED
@@ -1,13 +1,15 @@
1
1
  # @nesso-how/graph
2
2
 
3
- Embeddable read-only Nesso knowledge graph React component, built on [React Flow](https://reactflow.dev).
3
+ Embeddable Nesso knowledge graph React component, built on [React Flow](https://reactflow.dev). Read-only by default; enable drag, connect, and selection when you need an interactive canvas.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install @nesso-how/graph
8
+ npm install @nesso-how/graph @xyflow/react react react-dom
9
9
  ```
10
10
 
11
+ `@xyflow/react`, `react`, and `react-dom` are peer dependencies — match the ranges in this package's `peerDependencies`.
12
+
11
13
  ## Usage
12
14
 
13
15
  ```tsx
@@ -21,27 +23,29 @@ import '@xyflow/react/dist/style.css'
21
23
  />
22
24
  ```
23
25
 
24
- `nodes`/`edges` (or a full `graph: NessoGraphFile`) render read-only by default —
25
- no drag, connect, or selection. Default `nodeTypes`/`edgeTypes` use `ConceptNode` and
26
- `NessoEdge` from this package (display settings via `GraphDisplayContext`).
26
+ Pass a full `NessoGraphDocument` (from `@nesso-how/vocab-learning`) via `graph` instead of separate `nodes`/`edges`:
27
+
28
+ ```tsx
29
+ import { deserialize } from '@nesso-how/vocab-learning'
30
+
31
+ const doc = deserialize(json)
32
+ ;<NessoGraph graph={doc} style={{ width: '100%', height: 400 }} />
33
+ ```
34
+
35
+ Or build React Flow props yourself with `documentToRenderGraph(doc)` when you need the raw `nodes`/`edges` arrays.
36
+
37
+ `nodes`/`edges` render read-only by default — no drag, connect, or selection. Default `nodeTypes`/`edgeTypes` use `ConceptNode` and `NessoEdge` from this package (display settings via `GraphDisplayContext`).
38
+
39
+ Pass `display`/`palette` to control categories, glyphs, and curves. Use `categoryColorMode: 'css'` when `--cat-*` CSS variables are set on the page (the main app); embeds default to `palette` (hex from `PALETTES` in `@nesso-how/vocab-learning`). Optional `getRelationLabel` and `isItemSelected` customize labels and selection. Pass any other [`ReactFlow`](https://reactflow.dev/api-reference/react-flow) prop through `reactFlowProps`.
27
40
 
28
- Pass `display`/`palette` to control categories, glyphs, and curves. Use
29
- `categoryColorMode: 'css'` when `--cat-*` CSS variables are set on the page (the main
30
- app); embeds default to `palette` (hex from `PALETTES`). Optional `getRelationLabel`
31
- and `isItemSelected` customize labels and selection. Pass any other
32
- [`ReactFlow`](https://reactflow.dev/api-reference/react-flow) prop through `reactFlowProps`.
41
+ Full guide: [Embedding graphs](https://nesso.how/docs/guides/embedding-graphs/).
33
42
 
34
43
  ### Interactivity
35
44
 
36
- Turn on `nodesDraggable`/`nodesConnectable`/`elementsSelectable` as needed. How you
37
- provide nodes determines who owns their state:
45
+ Turn on `nodesDraggable`/`nodesConnectable`/`elementsSelectable` as needed. How you provide nodes determines who owns their state:
38
46
 
39
- - `nodes`/`edges` — _controlled_: you own the state and must also pass
40
- `onNodesChange`/`onEdgesChange`/`onConnect` to apply updates (e.g. the main app,
41
- where positions live in its own store).
42
- - `defaultNodes`/`defaultEdges` — _uncontrolled_: React Flow seeds its internal
43
- state once and manages drag/connect/selection itself — no wiring needed, the
44
- right choice for decorative or one-off embeds.
47
+ - `nodes`/`edges` — _controlled_: you own the state and must also pass `onNodesChange`/`onEdgesChange`/`onConnect` to apply updates (e.g. the main app, where positions live in its own store).
48
+ - `defaultNodes`/`defaultEdges` _uncontrolled_: React Flow seeds its internal state once and manages drag/connect/selection itself — no wiring needed, the right choice for decorative or one-off embeds.
45
49
 
46
50
  ```tsx
47
51
  <NessoGraph
@@ -1,5 +1,5 @@
1
1
  import type { Node, NodeProps } from '@xyflow/react';
2
- import type { ConceptNodeData } from '@nesso-how/types';
2
+ import type { ConceptNodeData } from '@nesso-how/vocab-learning';
3
3
  type ConceptNodeType = Node<ConceptNodeData>;
4
4
  export declare function ConceptNode({ data, selected }: NodeProps<ConceptNodeType>): import("react/jsx-runtime").JSX.Element;
5
5
  export {};
@@ -1,9 +1,9 @@
1
- import type { GlyphKind } from '@nesso-how/relation-types';
1
+ import type { GlyphKind } from '@nesso-how/vocab-learning';
2
2
  interface Props {
3
3
  kind: GlyphKind;
4
4
  color?: string;
5
5
  size?: number;
6
6
  }
7
- /** Renders a relation glyph from `@nesso-how/relation-types`' framework-agnostic SVG data. */
7
+ /** Renders a relation glyph from `@nesso-how/vocab-learning`' framework-agnostic SVG data. */
8
8
  export declare function GlyphSVG({ kind, color, size }: Props): import("react/jsx-runtime").JSX.Element;
9
9
  export {};
package/dist/GlyphSVG.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  // SPDX-License-Identifier: MIT
3
- import { GLYPH_PATHS } from '@nesso-how/relation-types';
4
- /** Renders a relation glyph from `@nesso-how/relation-types`' framework-agnostic SVG data. */
3
+ import { GLYPH_PATHS } from '@nesso-how/vocab-learning';
4
+ /** Renders a relation glyph from `@nesso-how/vocab-learning`' framework-agnostic SVG data. */
5
5
  export function GlyphSVG({ kind, color = 'currentColor', size = 14 }) {
6
6
  return (_jsx("svg", { width: size, height: size, viewBox: "0 0 14 14", style: { color }, strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round", fill: "none", stroke: "currentColor", dangerouslySetInnerHTML: { __html: GLYPH_PATHS[kind] } }));
7
7
  }
@@ -1,5 +1,5 @@
1
1
  import type { Edge, EdgeProps } from '@xyflow/react';
2
- import type { NessoEdgeData } from '@nesso-how/types';
2
+ import type { NessoEdgeData } from './display.js';
3
3
  type NessoFlowEdge = Edge<NessoEdgeData, 'nesso'>;
4
4
  export declare function NessoEdge({ id, source, target, data, selected }: EdgeProps<NessoFlowEdge>): import("react/jsx-runtime").JSX.Element | null;
5
5
  export {};
package/dist/NessoEdge.js CHANGED
@@ -2,12 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // SPDX-License-Identifier: MIT
3
3
  import { useState } from 'react';
4
4
  import { useStore } from '@xyflow/react';
5
- import { PALETTES, RELATION_TYPES } from '@nesso-how/relation-types';
5
+ import { PALETTES, RELATION_TYPES } from '@nesso-how/vocab-learning';
6
6
  import { GlyphSVG } from './GlyphSVG.js';
7
7
  import { useGraphDisplay } from './context.js';
8
8
  import { arcControlPoint, effectiveCurveFlip, flowNodeCenterX, flowNodeCenterY, nessoArcPath, rectExit, } from './geometry.js';
9
- function asEdgeTypeName(value, fallback = 'causes') {
10
- return typeof value === 'string' && value in RELATION_TYPES ? value : fallback;
9
+ function asRelationTypeName(value, fallback = 'causes') {
10
+ return typeof value === 'string' && value in RELATION_TYPES
11
+ ? value
12
+ : fallback;
11
13
  }
12
14
  function categoryColor(cat, mode, palette) {
13
15
  if (mode === 'css')
@@ -39,7 +41,7 @@ export function NessoEdge({ id, source, target, data, selected }) {
39
41
  const { edgeEncoding, curveStyle, autoCurveFlip, palette, categoryColorMode, getRelationLabel, isItemSelected, } = useGraphDisplay();
40
42
  const sourceNode = useStore((s) => s.nodeLookup.get(source));
41
43
  const targetNode = useStore((s) => s.nodeLookup.get(target));
42
- const edgeType = asEdgeTypeName(data?.type);
44
+ const edgeType = asRelationTypeName(data?.type);
43
45
  const T = RELATION_TYPES[edgeType];
44
46
  const color = edgeEncoding === 'minimal'
45
47
  ? 'var(--ink-3, #888888)'
@@ -1,11 +1,10 @@
1
1
  import type { Node, Edge, NodeTypes, EdgeTypes, ReactFlowProps, Viewport, OnNodesChange, OnEdgesChange, OnConnect, OnConnectStart, OnConnectEnd, OnMoveEnd } from '@xyflow/react';
2
- import type { NessoGraphFile } from '@nesso-how/formats';
3
- import type { EdgeTypeName } from '@nesso-how/relation-types';
4
- import type { ConceptNodeData, NessoEdgeData, GraphDisplaySettings, CategoryPalette } from '@nesso-how/types';
2
+ import type { RelationTypeName, ConceptNodeData, NessoGraphDocument, NessoGraphDocumentInput, CategoryPalette } from '@nesso-how/vocab-learning';
3
+ import type { NessoEdgeData, GraphDisplaySettings } from './display.js';
5
4
  import { type CategoryColorMode } from './context.js';
6
5
  type PassthroughKeys = 'nodes' | 'defaultNodes' | 'edges' | 'defaultEdges' | 'nodeTypes' | 'edgeTypes' | 'nodesDraggable' | 'nodesConnectable' | 'elementsSelectable' | 'onNodesChange' | 'onEdgesChange' | 'onConnect' | 'onConnectStart' | 'onConnectEnd' | 'onSelectionChange' | 'onMoveEnd' | 'onNodeClick' | 'onEdgeClick' | 'fitView' | 'defaultViewport' | 'minZoom' | 'maxZoom' | 'panOnDrag' | 'zoomOnScroll';
7
6
  export interface NessoGraphProps {
8
- graph?: NessoGraphFile;
7
+ graph?: NessoGraphDocument | NessoGraphDocumentInput;
9
8
  nodes?: Node[];
10
9
  defaultNodes?: Node[];
11
10
  edges?: Edge[];
@@ -14,7 +13,7 @@ export interface NessoGraphProps {
14
13
  palette?: CategoryPalette;
15
14
  /** `palette` (default) for embeds; `css` when `--cat-*` vars are set on the page. */
16
15
  categoryColorMode?: CategoryColorMode;
17
- getRelationLabel?: (type: EdgeTypeName) => string;
16
+ getRelationLabel?: (type: RelationTypeName) => string;
18
17
  isItemSelected?: (kind: 'node' | 'edge', id: string) => boolean;
19
18
  nodeTypes?: NodeTypes;
20
19
  edgeTypes?: EdgeTypes;
@@ -3,31 +3,34 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
3
3
  import { useMemo } from 'react';
4
4
  import { ReactFlow, Background, Controls } from '@xyflow/react';
5
5
  import { GraphDisplayContext } from './context.js';
6
+ import { documentToRenderGraph } from './documentToRenderGraph.js';
6
7
  import { ConceptNode } from './ConceptNode.js';
7
8
  import { NessoEdge } from './NessoEdge.js';
8
9
  const DEFAULT_NODE_TYPES = { concept: ConceptNode };
9
10
  const DEFAULT_EDGE_TYPES = { nesso: NessoEdge };
10
11
  export function NessoGraph({ graph, nodes: nodesProp, defaultNodes, edges: edgesProp, defaultEdges, display, palette = 'default', categoryColorMode = 'palette', getRelationLabel, isItemSelected, nodeTypes = DEFAULT_NODE_TYPES, edgeTypes = DEFAULT_EDGE_TYPES, nodesDraggable = false, nodesConnectable = false, elementsSelectable = true, panOnDrag = true, zoomOnScroll = true, onNodesChange, onEdgesChange, onConnect, onConnectStart, onConnectEnd, onSelectionChange, onMoveEnd, onNodeClick, onEdgeClick, fitView = true, defaultViewport, minZoom, maxZoom, reactFlowProps, style, className, onDoubleClick, children, }) {
11
- const controlledNodes = nodesProp ?? graph?.nodes;
12
- const controlledEdges = (edgesProp ?? graph?.edges);
13
- // Controlled (`nodes`/`graph`) takes precedence; otherwise fall back to ReactFlow's
14
- // own uncontrolled mode via `defaultNodes`/`defaultEdges` see prop docs above.
15
- const nodesProps = controlledNodes !== undefined
16
- ? { nodes: controlledNodes }
17
- : { defaultNodes: defaultNodes ?? [] };
18
- const edgesProps = controlledEdges !== undefined
19
- ? { edges: controlledEdges }
20
- : { defaultEdges: defaultEdges ?? [] };
12
+ const rendered = graph ? documentToRenderGraph(graph) : undefined;
13
+ const graphDisplay = rendered?.display;
14
+ // `graph` alone uncontrolled (React Flow owns drag/selection). With `onNodesChange` /
15
+ // explicit `nodes`, or `nodesProp` without `graph`, use controlled mode instead.
16
+ const nodeControlled = nodesProp !== undefined || (rendered !== undefined && onNodesChange !== undefined);
17
+ const edgeControlled = edgesProp !== undefined || (rendered !== undefined && onEdgesChange !== undefined);
18
+ const nodesProps = nodeControlled
19
+ ? { nodes: nodesProp ?? rendered?.nodes ?? [] }
20
+ : { defaultNodes: defaultNodes ?? rendered?.nodes ?? [] };
21
+ const edgesProps = edgeControlled
22
+ ? { edges: (edgesProp ?? rendered?.edges ?? []) }
23
+ : { defaultEdges: defaultEdges ?? rendered?.edges ?? [] };
21
24
  const ctx = useMemo(() => ({
22
- edgeEncoding: display?.edgeEncoding ?? graph?.display?.edgeEncoding ?? 'full',
23
- showHeatmap: display?.showHeatmap ?? graph?.display?.showHeatmap ?? true,
24
- curveStyle: display?.curveStyle ?? graph?.display?.curveStyle ?? 'arc',
25
- autoCurveFlip: display?.autoCurveFlip ?? graph?.display?.autoCurveFlip ?? true,
25
+ edgeEncoding: display?.edgeEncoding ?? graphDisplay?.edgeEncoding ?? 'full',
26
+ showHeatmap: display?.showHeatmap ?? graphDisplay?.showHeatmap ?? true,
27
+ curveStyle: display?.curveStyle ?? graphDisplay?.curveStyle ?? 'arc',
28
+ autoCurveFlip: display?.autoCurveFlip ?? graphDisplay?.autoCurveFlip ?? true,
26
29
  palette,
27
30
  categoryColorMode,
28
31
  getRelationLabel,
29
32
  isItemSelected,
30
- }), [display, graph?.display, palette, categoryColorMode, getRelationLabel, isItemSelected]);
33
+ }), [display, graphDisplay, palette, categoryColorMode, getRelationLabel, isItemSelected]);
31
34
  return (_jsx("div", { style: { width: '100%', height: '100%', ...style }, className: className, onDoubleClick: onDoubleClick, children: _jsx(GraphDisplayContext.Provider, { value: ctx, children: _jsx(ReactFlow, { ...nodesProps, ...edgesProps, nodeTypes: nodeTypes, edgeTypes: edgeTypes, nodesDraggable: nodesDraggable, nodesConnectable: nodesConnectable, elementsSelectable: elementsSelectable, panOnDrag: panOnDrag, zoomOnScroll: zoomOnScroll, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, onConnect: onConnect, onConnectStart: onConnectStart, onConnectEnd: onConnectEnd, onSelectionChange: onSelectionChange, onMoveEnd: onMoveEnd, fitView: fitView, defaultViewport: defaultViewport, minZoom: minZoom, maxZoom: maxZoom, onNodeClick: onNodeClick
32
35
  ? (_, node) => onNodeClick(node.id, node.data)
33
36
  : undefined, onEdgeClick: onEdgeClick ? (_, edge) => onEdgeClick(edge.id, edge.data) : undefined, ...reactFlowProps, children: children ?? (_jsxs(_Fragment, { children: [_jsx(Background, {}), _jsx(Controls, {})] })) }) }) }));
package/dist/context.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { EdgeTypeName } from '@nesso-how/relation-types';
2
- import type { EdgeEncoding, CurveStyle, CategoryPalette } from '@nesso-how/types';
1
+ import type { RelationTypeName, CategoryPalette } from '@nesso-how/vocab-learning';
2
+ import type { EdgeEncoding, CurveStyle } from './display.js';
3
3
  export type CategoryColorMode = 'palette' | 'css';
4
4
  export interface NessoGraphDisplayContext {
5
5
  edgeEncoding: EdgeEncoding;
@@ -9,7 +9,7 @@ export interface NessoGraphDisplayContext {
9
9
  palette: CategoryPalette;
10
10
  /** `palette` uses hex from PALETTES; `css` uses `var(--cat-*)` (app with live palette switching). */
11
11
  categoryColorMode: CategoryColorMode;
12
- getRelationLabel?: (type: EdgeTypeName) => string;
12
+ getRelationLabel?: (type: RelationTypeName) => string;
13
13
  isItemSelected?: (kind: 'node' | 'edge', id: string) => boolean;
14
14
  }
15
15
  export declare const GraphDisplayContext: import("react").Context<NessoGraphDisplayContext>;
@@ -0,0 +1,15 @@
1
+ import type { RelationTypeName } from '@nesso-how/vocab-learning';
2
+ export type EdgeEncoding = 'full' | 'category' | 'minimal';
3
+ export type CurveStyle = 'arc' | 'straight';
4
+ export interface GraphDisplaySettings {
5
+ edgeEncoding: EdgeEncoding;
6
+ showHeatmap: boolean;
7
+ curveStyle: CurveStyle;
8
+ autoCurveFlip: boolean;
9
+ }
10
+ export interface NessoEdgeData extends Record<string, unknown> {
11
+ type: RelationTypeName;
12
+ siblingIdx?: number;
13
+ curveFlip?: boolean;
14
+ curveFlipPinned?: boolean;
15
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { Node, Edge } from '@xyflow/react';
2
+ import type { ConceptNodeData, NessoGraphDocumentInput } from '@nesso-how/vocab-learning';
3
+ import type { NessoEdgeData, GraphDisplaySettings } from './display.js';
4
+ /** Convert a Nesso graph document to React Flow nodes/edges for rendering (no FSRS). */
5
+ export declare function documentToRenderGraph(doc: NessoGraphDocumentInput): {
6
+ nodes: Node<ConceptNodeData>[];
7
+ edges: Edge<NessoEdgeData>[];
8
+ display?: Partial<GraphDisplaySettings>;
9
+ };
@@ -0,0 +1,36 @@
1
+ import { defaultConceptReviewFields } from '@nesso-how/vocab-learning';
2
+ function edgeDataFromRelation(r) {
3
+ return {
4
+ type: r.type,
5
+ ...(r.data?.curveFlip !== undefined ? { curveFlip: r.data.curveFlip } : {}),
6
+ ...(r.data?.curveFlipPinned !== undefined ? { curveFlipPinned: r.data.curveFlipPinned } : {}),
7
+ };
8
+ }
9
+ function relationToEdge(r) {
10
+ return {
11
+ id: r.id,
12
+ source: r.source,
13
+ target: r.target,
14
+ sourceHandle: 'out',
15
+ targetHandle: 'in',
16
+ type: 'nesso',
17
+ data: edgeDataFromRelation(r),
18
+ };
19
+ }
20
+ /** Convert a Nesso graph document to React Flow nodes/edges for rendering (no FSRS). */
21
+ export function documentToRenderGraph(doc) {
22
+ const fsrsDefaults = defaultConceptReviewFields();
23
+ const nodes = doc.concepts.map((c) => ({
24
+ id: c.id,
25
+ type: 'concept',
26
+ position: { x: c.x, y: c.y },
27
+ data: {
28
+ ...fsrsDefaults,
29
+ text: c.label,
30
+ ...(c.data?.elaboration !== undefined && { elaboration: c.data.elaboration }),
31
+ },
32
+ }));
33
+ const edges = doc.relations.map(relationToEdge);
34
+ const meta = doc.meta;
35
+ return { nodes, edges, display: meta?.display };
36
+ }
package/dist/index.d.ts CHANGED
@@ -6,6 +6,8 @@ export type { ConceptNodeBodyProps } from './ConceptNodeBody.js';
6
6
  export { NessoEdge } from './NessoEdge.js';
7
7
  export { useGraphDisplay, GraphDisplayContext } from './context.js';
8
8
  export type { NessoGraphDisplayContext, CategoryColorMode } from './context.js';
9
+ export type { CurveStyle, EdgeEncoding, GraphDisplaySettings, NessoEdgeData, } from './display.js';
10
+ export { documentToRenderGraph } from './documentToRenderGraph.js';
9
11
  export { GlyphSVG } from './GlyphSVG.js';
10
12
  export { ratingColor } from './ratingColor.js';
11
13
  export { arcControlPoint, defaultCurveFlip, nodeCenterX, nodeCenterY, flowNodeCenterX, flowNodeCenterY, effectiveCurveFlip, rectExit, nessoArcPath, } from './geometry.js';
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export { ConceptNode } from './ConceptNode.js';
4
4
  export { ConceptNodeBody } from './ConceptNodeBody.js';
5
5
  export { NessoEdge } from './NessoEdge.js';
6
6
  export { useGraphDisplay, GraphDisplayContext } from './context.js';
7
+ export { documentToRenderGraph } from './documentToRenderGraph.js';
7
8
  // Shared canvas utilities — import from here to avoid duplication with the main app.
8
9
  export { GlyphSVG } from './GlyphSVG.js';
9
10
  export { ratingColor } from './ratingColor.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nesso-how/graph",
3
- "version": "0.1.0-alpha.33",
4
- "description": "Embeddable read-only Nesso knowledge graph React component",
3
+ "version": "0.1.0-alpha.35",
4
+ "description": "Embeddable Nesso knowledge graph React component (read-only by default)",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -22,9 +22,7 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@nesso-how/formats": "0.1.0-alpha.33",
26
- "@nesso-how/relation-types": "0.1.0-alpha.33",
27
- "@nesso-how/types": "0.1.0-alpha.33"
25
+ "@nesso-how/vocab-learning": "0.1.0-alpha.35"
28
26
  },
29
27
  "peerDependencies": {
30
28
  "@xyflow/react": "^12.6.4",