@prnt/dagr-explorer 0.1.3
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/CHANGELOG.md +93 -0
- package/LICENSE +21 -0
- package/README.md +441 -0
- package/dist/base.d.ts +63 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/base.js +20 -0
- package/dist/base.js.map +1 -0
- package/dist/camera.d.ts +61 -0
- package/dist/camera.d.ts.map +1 -0
- package/dist/camera.js +142 -0
- package/dist/camera.js.map +1 -0
- package/dist/context.d.ts +110 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +71 -0
- package/dist/context.js.map +1 -0
- package/dist/dagr-explorer.d.ts +41 -0
- package/dist/dagr-explorer.d.ts.map +1 -0
- package/dist/dagr-explorer.js +13 -0
- package/dist/dagr-explorer.js.map +1 -0
- package/dist/errors.d.ts +55 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +57 -0
- package/dist/errors.js.map +1 -0
- package/dist/explorer-details.d.ts +44 -0
- package/dist/explorer-details.d.ts.map +1 -0
- package/dist/explorer-details.js +76 -0
- package/dist/explorer-details.js.map +1 -0
- package/dist/explorer-search.d.ts +31 -0
- package/dist/explorer-search.d.ts.map +1 -0
- package/dist/explorer-search.js +86 -0
- package/dist/explorer-search.js.map +1 -0
- package/dist/explorer-toolbar.d.ts +17 -0
- package/dist/explorer-toolbar.d.ts.map +1 -0
- package/dist/explorer-toolbar.js +44 -0
- package/dist/explorer-toolbar.js.map +1 -0
- package/dist/explorer-trace-toggle.d.ts +16 -0
- package/dist/explorer-trace-toggle.d.ts.map +1 -0
- package/dist/explorer-trace-toggle.js +8 -0
- package/dist/explorer-trace-toggle.js.map +1 -0
- package/dist/explorer-viewport.d.ts +69 -0
- package/dist/explorer-viewport.d.ts.map +1 -0
- package/dist/explorer-viewport.js +69 -0
- package/dist/explorer-viewport.js.map +1 -0
- package/dist/explorer-views.d.ts +24 -0
- package/dist/explorer-views.d.ts.map +1 -0
- package/dist/explorer-views.js +16 -0
- package/dist/explorer-views.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/isomorphic-layout-effect.d.ts +7 -0
- package/dist/isomorphic-layout-effect.d.ts.map +1 -0
- package/dist/isomorphic-layout-effect.js +7 -0
- package/dist/isomorphic-layout-effect.js.map +1 -0
- package/dist/labels.d.ts +67 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/labels.js +50 -0
- package/dist/labels.js.map +1 -0
- package/dist/layout.d.ts +82 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +244 -0
- package/dist/layout.js.map +1 -0
- package/dist/navigation.d.ts +20 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +44 -0
- package/dist/navigation.js.map +1 -0
- package/dist/root.d.ts +89 -0
- package/dist/root.d.ts.map +1 -0
- package/dist/root.js +401 -0
- package/dist/root.js.map +1 -0
- package/dist/search.d.ts +19 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +31 -0
- package/dist/search.js.map +1 -0
- package/dist/size.d.ts +12 -0
- package/dist/size.d.ts.map +1 -0
- package/dist/size.js +20 -0
- package/dist/size.js.map +1 -0
- package/dist/svg-base.d.ts +33 -0
- package/dist/svg-base.d.ts.map +1 -0
- package/dist/svg-base.js +89 -0
- package/dist/svg-base.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/use-explorer-camera.d.ts +56 -0
- package/dist/use-explorer-camera.d.ts.map +1 -0
- package/dist/use-explorer-camera.js +641 -0
- package/dist/use-explorer-camera.js.map +1 -0
- package/dist/use-explorer.d.ts +23 -0
- package/dist/use-explorer.d.ts.map +1 -0
- package/dist/use-explorer.js +26 -0
- package/dist/use-explorer.js.map +1 -0
- package/dist/validate.d.ts +20 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +99 -0
- package/dist/validate.js.map +1 -0
- package/dist/viewport-surface.d.ts +90 -0
- package/dist/viewport-surface.d.ts.map +1 -0
- package/dist/viewport-surface.js +485 -0
- package/dist/viewport-surface.js.map +1 -0
- package/dist/visible-set.d.ts +65 -0
- package/dist/visible-set.d.ts.map +1 -0
- package/dist/visible-set.js +168 -0
- package/dist/visible-set.js.map +1 -0
- package/package.json +62 -0
- package/src/base.ts +67 -0
- package/src/camera.ts +206 -0
- package/src/context.ts +168 -0
- package/src/dagr-explorer.tsx +85 -0
- package/src/errors.ts +85 -0
- package/src/explorer-details.tsx +156 -0
- package/src/explorer-search.tsx +140 -0
- package/src/explorer-toolbar.tsx +77 -0
- package/src/explorer-trace-toggle.tsx +34 -0
- package/src/explorer-viewport.tsx +181 -0
- package/src/explorer-views.tsx +61 -0
- package/src/index.ts +76 -0
- package/src/isomorphic-layout-effect.ts +7 -0
- package/src/labels.ts +109 -0
- package/src/layout.ts +298 -0
- package/src/navigation.ts +51 -0
- package/src/root.tsx +544 -0
- package/src/search.ts +36 -0
- package/src/size.ts +23 -0
- package/src/svg-base.tsx +173 -0
- package/src/types.ts +69 -0
- package/src/use-explorer-camera.ts +675 -0
- package/src/use-explorer.ts +32 -0
- package/src/validate.ts +173 -0
- package/src/viewport-surface.tsx +650 -0
- package/src/visible-set.ts +225 -0
- package/styles.css +253 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @prnt/dagr-explorer
|
|
2
|
+
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
The first release. Built in slices M5.6a to M5.6f; the entries below are in
|
|
6
|
+
the order they landed.
|
|
7
|
+
|
|
8
|
+
- Add the headless core (M5.6b): the `ExplorerView` data model, `validateView`
|
|
9
|
+
and `validateViews` with `ExplorerDataError`, `layoutView`, and
|
|
10
|
+
`searchNodes`.
|
|
11
|
+
- Layout flows `'right'` by default or `'down'`, in y-down world pixels padded
|
|
12
|
+
40 off the origin. Parallel edges that span one rank bow 16 apart. A self
|
|
13
|
+
loop has an empty route. A group is the padded hull of its members and
|
|
14
|
+
moves no node.
|
|
15
|
+
- Add the viewport's pure core (M5.6c-1), internal for now: camera arithmetic
|
|
16
|
+
with limits from `Camera2D`, and the visible set that decides which nodes
|
|
17
|
+
get a DOM element at which tier, capped, with pinned nodes always mounted.
|
|
18
|
+
- Add the viewport (M5.6c-2), internal for now: a pannable, zoomable surface
|
|
19
|
+
with an SVG base layer and a windowed DOM overlay. Pan and zoom by wheel,
|
|
20
|
+
keys, drag, trackpad pinch and two-finger touch. A drag tracks the pointer
|
|
21
|
+
exactly, and a resize or a relayout keeps the user's place. The base layer
|
|
22
|
+
is a seam that is told of every drawn camera, for a native base later.
|
|
23
|
+
- Add the React parts (M5.6d), the first public exports beyond the core:
|
|
24
|
+
`DagrExplorer`, preassembled, and the parts it is built from,
|
|
25
|
+
`ExplorerRoot`, `ExplorerViews`, `ExplorerSearch`, `ExplorerTraceToggle`,
|
|
26
|
+
`ExplorerViewport`, `ExplorerDetails` and `ExplorerToolbar`, with
|
|
27
|
+
`useExplorer` and an `apiRef` for the same methods, and `useExplorerApi`
|
|
28
|
+
for the methods alone, which never re-renders its caller. The methods see
|
|
29
|
+
each other's writes, so two calls in one tick end where the last asked.
|
|
30
|
+
- `ExplorerRoot` validates every view and lays out the active one, memoized
|
|
31
|
+
by shape. The view and the selection are controllable, and a controlled
|
|
32
|
+
value is never rendered past: the explorer calls back and waits for the
|
|
33
|
+
prop. The query, trace, drawer and camera are internal, and a view switch
|
|
34
|
+
resets them. Data that arrives after mount is not a switch, so a
|
|
35
|
+
deep-linked selection survives data that starts empty.
|
|
36
|
+
- `ExplorerViewport` renders its children, such as `ExplorerDetails`, in a
|
|
37
|
+
positioned stage with the graph, and the graph's hint after it. Its
|
|
38
|
+
surface renders only when what it draws changes. Every part's `style`
|
|
39
|
+
wins over its own, so `style={{ height: 600 }}` sizes the graph.
|
|
40
|
+
- `ExplorerSearch` lists at most `maxResults` matches (default 50), then a
|
|
41
|
+
line saying how many more. The count and Enter cover every match.
|
|
42
|
+
- The drawer returns focus to what opened it, or to the search field, or to
|
|
43
|
+
the root, never to the page. Escape closes it from anywhere in the root.
|
|
44
|
+
In the search field it closes the drawer before it clears the query, and
|
|
45
|
+
on a node or the graph's surface it closes the drawer before it leaves
|
|
46
|
+
the graph (see the M5.6f-2 entry below).
|
|
47
|
+
- Every string comes from `labels`, with neutral English defaults in
|
|
48
|
+
`DEFAULT_EXPLORER_LABELS`, including the `moreMatches` and `inGroup`
|
|
49
|
+
formatters. An inline `labels` object is kept by value. A part outside a
|
|
50
|
+
root, or a second viewport in one, throws `ExplorerContextError`.
|
|
51
|
+
- Add `@prnt/dagr-explorer/styles.css`, the optional default look, themed by
|
|
52
|
+
eight `--dagr-explorer-*` variables. The parts work without it.
|
|
53
|
+
- Export the base-layer seam types (`ExplorerBase`, `ExplorerBaseProps`,
|
|
54
|
+
`ExplorerCameraSource`, `ExplorerEmphasis`, `ExplorerVisibleSet`) as
|
|
55
|
+
experimental, until a native base confirms them, and `ExplorerCamera` as a
|
|
56
|
+
type.
|
|
57
|
+
- The test suite runs under React 18 and React 19. The React 18 run needs
|
|
58
|
+
Node 22.15 or later, for `module.registerHooks`.
|
|
59
|
+
- Add keyboard navigation (M5.6e). The graph is one tab stop: the selected
|
|
60
|
+
node, else the last node focused from the keyboard, else the node nearest
|
|
61
|
+
the center, always mounted. Arrow keys on a focused node move focus to the
|
|
62
|
+
nearest node in that direction, mounted or not; Shift with an arrow pans,
|
|
63
|
+
and Enter and Space inspect. A node focused from the keyboard is revealed
|
|
64
|
+
by the least pan at the current zoom; a click moves nothing. The default
|
|
65
|
+
`hint` now names the arrow keys. The tab target and the focused node are
|
|
66
|
+
pinned outside `maxOverlayNodes`, so the page can hold a few more node
|
|
67
|
+
elements than the cap.
|
|
68
|
+
- Every part renders on a server (M5.6e): the shell and the base layer, with
|
|
69
|
+
every node as a mark, the plane hidden and no node elements until the
|
|
70
|
+
client measures the graph. It hydrates without a mismatch. No part needed
|
|
71
|
+
a change for it.
|
|
72
|
+
- Add the docs page, `/docs/explorer`, with two live demos, an
|
|
73
|
+
architecture graph and a 2,000 node synthetic graph, and Node benches
|
|
74
|
+
for the visible set at 1,000 and 10,000 nodes and for layout at 1,000
|
|
75
|
+
(M5.6f-1). No package code changed.
|
|
76
|
+
- Validate the explorer in real browsers (M5.6f-2): the docs demos in
|
|
77
|
+
Chromium 153 and WebKit 26.6, at 1440 by 900 and at 390 by 844 with
|
|
78
|
+
touch, with `bench/browser/explorer-check.mjs`. Every check passes in
|
|
79
|
+
both. The README's "Known browser differences" records what differs.
|
|
80
|
+
- Fix: `Escape` on a node or the graph's surface with the drawer open
|
|
81
|
+
closes the drawer and keeps focus where it is, and a second `Escape`
|
|
82
|
+
leaves the graph. One `Escape` used to do both, which left focus on the
|
|
83
|
+
page body. An `Escape` that content inside a node handles is left to it.
|
|
84
|
+
- Fix: the graph holds its wheel listener only while it has focus. WebKit
|
|
85
|
+
does not scroll a page whose root sets `overscroll-behavior: none` while
|
|
86
|
+
the pointer is over any non-passive wheel listener, so the page would not
|
|
87
|
+
scroll past an unfocused graph there.
|
|
88
|
+
- Measure the SVG base's ceiling: smooth (a 95th percentile frame within
|
|
89
|
+
one 16.7 ms frame) to about 4,000 nodes and 5,400 edges, panned at one and
|
|
90
|
+
a half times the fit zoom in Chromium on an Apple M4. The table is in the
|
|
91
|
+
README.
|
|
92
|
+
- This is the first published version. The umbrella package re-exports it
|
|
93
|
+
as `@prnt/dagr/explorer`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 prnt.design
|
|
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,441 @@
|
|
|
1
|
+
# @prnt/dagr-explorer
|
|
2
|
+
|
|
3
|
+
An interactive graph explorer for [Dagr](https://dagr.prnt.design): views,
|
|
4
|
+
search, connection tracing, groups and a details drawer, with node content
|
|
5
|
+
virtualized by on-screen size.
|
|
6
|
+
|
|
7
|
+
## Install and requirements
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @prnt/dagr-explorer
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The package is browser-validated in Chromium and WebKit (see "Known browser
|
|
14
|
+
differences" below).
|
|
15
|
+
|
|
16
|
+
The explorer never loads three.js at runtime: from `@prnt/dagr-render` it
|
|
17
|
+
imports only the three-free `core` entry, and `@prnt/dagr-render` declares
|
|
18
|
+
`three` an optional peer, so it is not installed either.
|
|
19
|
+
|
|
20
|
+
It runs on React 18 and React 19 (`react` and `react-dom` `>=18.2.0 <20.0.0`).
|
|
21
|
+
The umbrella package re-exports it as `@prnt/dagr/explorer`, but the umbrella
|
|
22
|
+
requires React 19, so a React 18 site installs this package directly.
|
|
23
|
+
|
|
24
|
+
## The explorer
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { DagrExplorer } from '@prnt/dagr-explorer';
|
|
28
|
+
import '@prnt/dagr-explorer/styles.css'; // optional
|
|
29
|
+
|
|
30
|
+
<DagrExplorer
|
|
31
|
+
label="Architecture"
|
|
32
|
+
views={[
|
|
33
|
+
{
|
|
34
|
+
id: 'overview',
|
|
35
|
+
label: 'Overview',
|
|
36
|
+
nodes: [
|
|
37
|
+
{ id: 'app', label: 'Application', team: 'web' },
|
|
38
|
+
{ id: 'store', label: 'Store', team: 'data' },
|
|
39
|
+
],
|
|
40
|
+
edges: [{ id: 'read', source: 'app', target: 'store' }],
|
|
41
|
+
},
|
|
42
|
+
]}
|
|
43
|
+
renderNode={(node) => `${node.label} (${node.team})`}
|
|
44
|
+
/>;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`label` is required: it is the accessible name of the graph, and the parts
|
|
48
|
+
derive theirs from it. For one graph, pass `nodes`, `edges` and optionally
|
|
49
|
+
`groups` and `layout` instead of `views`. That is one view with the id
|
|
50
|
+
`'default'` and `label` as its label. Passing both shapes is a type error.
|
|
51
|
+
|
|
52
|
+
The node type is inferred from your data, so `renderNode` above sees `team`.
|
|
53
|
+
`DagrExplorer` takes the root's props (below) plus `renderNode`,
|
|
54
|
+
`renderDetails`, `renderConnection`, `renderViews`, `tiers`,
|
|
55
|
+
`maxOverlayNodes`, `base` and `nodeAriaLabel`, which it forwards to the parts.
|
|
56
|
+
|
|
57
|
+
### The parts
|
|
58
|
+
|
|
59
|
+
`DagrExplorer` is built only from these, with no private access, so a host
|
|
60
|
+
that owns its layout can rebuild it, or arrange them differently:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import {
|
|
64
|
+
ExplorerDetails,
|
|
65
|
+
ExplorerRoot,
|
|
66
|
+
ExplorerSearch,
|
|
67
|
+
ExplorerToolbar,
|
|
68
|
+
ExplorerViewport,
|
|
69
|
+
type ExplorerNode,
|
|
70
|
+
type ExplorerView,
|
|
71
|
+
} from '@prnt/dagr-explorer';
|
|
72
|
+
|
|
73
|
+
interface MyNode extends ExplorerNode {
|
|
74
|
+
readonly title: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const views: ExplorerView<MyNode>[] = [
|
|
78
|
+
{
|
|
79
|
+
id: 'overview',
|
|
80
|
+
label: 'Overview',
|
|
81
|
+
nodes: [
|
|
82
|
+
{ id: 'app', label: 'Application', title: 'App' },
|
|
83
|
+
{ id: 'store', label: 'Store', title: 'Store' },
|
|
84
|
+
],
|
|
85
|
+
edges: [{ id: 'read', source: 'app', target: 'store' }],
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
export function Architecture() {
|
|
90
|
+
return (
|
|
91
|
+
<ExplorerRoot label="Architecture" views={views}>
|
|
92
|
+
<ExplorerSearch />
|
|
93
|
+
<ExplorerViewport<MyNode> renderNode={(node) => node.title}>
|
|
94
|
+
<ExplorerDetails />
|
|
95
|
+
</ExplorerViewport>
|
|
96
|
+
<ExplorerToolbar />
|
|
97
|
+
</ExplorerRoot>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| Part | Owns |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| `ExplorerRoot` | the data, its validation and layout, and the state below. Renders one element around its children |
|
|
105
|
+
| `ExplorerViews` | the view switcher. Renders nothing for a single view. Children `({ views, activeView, selectView })` replace it |
|
|
106
|
+
| `ExplorerSearch` | the search field, a live match count, and the matches as buttons, at most `maxResults` (default 50) |
|
|
107
|
+
| `ExplorerTraceToggle` | trace on and off |
|
|
108
|
+
| `ExplorerViewport` | the graph: pan and zoom, the SVG base, node elements for nodes large enough to read. Takes `renderNode`, `nodeAriaLabel`, `tiers`, `maxOverlayNodes`, `base`. Its children, such as `ExplorerDetails`, share a positioned stage with the graph, and the graph's hint comes after the stage, where an overlay cannot cover it |
|
|
109
|
+
| `ExplorerDetails` | the drawer: an overlay with a close button and a scrolling body. Children `({ node, connections, inspect })` replace the body, and `renderConnection(edge, otherNode)` draws one connection in the default body |
|
|
110
|
+
| `ExplorerToolbar` | zoom out, the zoom readout, zoom in, fit, and zoom to the selected node |
|
|
111
|
+
|
|
112
|
+
Every part takes `className` and `style`, and your `style` wins over the
|
|
113
|
+
part's own: `<ExplorerViewport style={{ height: 600 }} />` sets the graph's
|
|
114
|
+
height, which is otherwise `--dagr-explorer-height`. The one exception is
|
|
115
|
+
the viewport's `position` and `overflow`: they stay the viewport's own
|
|
116
|
+
(`relative` and `hidden`), because the graph's nodes are positioned against
|
|
117
|
+
it and clipped by it.
|
|
118
|
+
|
|
119
|
+
One `ExplorerViewport` per root: a second throws `ExplorerContextError` with
|
|
120
|
+
the code `SECOND_VIEWPORT`. A part outside a root throws it with
|
|
121
|
+
`OUTSIDE_EXPLORER`, naming the part.
|
|
122
|
+
|
|
123
|
+
**A part's type parameters are a claim, not a check.** The parts talk through
|
|
124
|
+
a context, which erases them, so `ExplorerViewport<MyNode>` asserts the node
|
|
125
|
+
type and nothing verifies it against the root's data. Unannotated, a part
|
|
126
|
+
sees only `id` and `label`. `DagrExplorer` has no such gap: it infers the
|
|
127
|
+
types from `views`.
|
|
128
|
+
|
|
129
|
+
### The root's props and state
|
|
130
|
+
|
|
131
|
+
`viewId` and `selectedId` are controllable (with `defaultViewId`,
|
|
132
|
+
`defaultSelectedId`, `onViewChange` and `onSelectedChange`), because they are
|
|
133
|
+
what a host syncs to a URL. Under a controlled value, every change the
|
|
134
|
+
explorer starts calls the callback and changes nothing on screen until the
|
|
135
|
+
prop does. A controlled id the data lacks renders as no selection, or as the
|
|
136
|
+
first view, with no corrective callback.
|
|
137
|
+
|
|
138
|
+
The query, trace, the drawer and the camera are internal. Changing view
|
|
139
|
+
resets all four, and sets the selection to what `selectOnViewChange(view)`
|
|
140
|
+
returns, or clears it. If the selected node leaves the data, the selection
|
|
141
|
+
clears and the drawer closes.
|
|
142
|
+
|
|
143
|
+
The other props: `searchText` (what search reads from a node, default the id
|
|
144
|
+
and label), `strictGroups`, `labels`, `apiRef`, `className` and `style`.
|
|
145
|
+
|
|
146
|
+
### `useExplorer`, `useExplorerApi` and `apiRef`
|
|
147
|
+
|
|
148
|
+
`useExplorer()` returns the explorer's state (`views`, `activeView`,
|
|
149
|
+
`layout`, `selectedId`, `selectedNode`, `query`, `matches`, `trace`,
|
|
150
|
+
`detailsOpen`, `dimmed`, `labels`, and `camera` for a readout of your own) and
|
|
151
|
+
the same methods `apiRef` hands out. It re-renders its caller on every change
|
|
152
|
+
of state.
|
|
153
|
+
|
|
154
|
+
`useExplorerApi()` returns the methods alone, the same stable functions, and
|
|
155
|
+
never re-renders its caller: use it in a component that only calls them, such
|
|
156
|
+
as a button of your own. Called twice in one tick, the methods see each other,
|
|
157
|
+
so `select('a'); select(null)` ends with nothing selected.
|
|
158
|
+
|
|
159
|
+
| Method | Does |
|
|
160
|
+
| --- | --- |
|
|
161
|
+
| `fit()`, `zoomBy(factor)` | the camera. No-ops before the viewport has a size |
|
|
162
|
+
| `focusNode(id)` | flies the camera to fit the node |
|
|
163
|
+
| `reveal(id)` | pans the least distance that brings the node into view |
|
|
164
|
+
| `select(id \| null)` | sets the current node, without opening the drawer |
|
|
165
|
+
| `inspect(id, trigger?)` | selects and opens the drawer. Focus returns to `trigger` when it closes |
|
|
166
|
+
| `closeDetails()` | closes the drawer |
|
|
167
|
+
| `selectView(id)` | switches view |
|
|
168
|
+
| `setQuery(query)`, `setTrace(on)` | search and trace |
|
|
169
|
+
|
|
170
|
+
### Search, drawer and Escape
|
|
171
|
+
|
|
172
|
+
Search matches nodes whose text contains every whitespace-separated token,
|
|
173
|
+
case ignored. `Enter` inspects the first match and flies to it, and choosing a
|
|
174
|
+
result does the same. The result list stays mounted while a node is
|
|
175
|
+
inspected. Search is the complete way to every node: a node too small on
|
|
176
|
+
screen to read has no element, and the graph's accessible description says
|
|
177
|
+
so. The list shows the first `maxResults` matches in data order, then a line
|
|
178
|
+
saying how many more there are. The count and `Enter` cover every match, and
|
|
179
|
+
a longer query narrows the list.
|
|
180
|
+
|
|
181
|
+
When the drawer closes with focus inside it, focus returns to what opened
|
|
182
|
+
it, or to the search field if that element is gone, or to the root element
|
|
183
|
+
if there is no search field. A control of yours that closes the drawer keeps
|
|
184
|
+
its focus. A connection button in the drawer inspects its neighbor and keeps
|
|
185
|
+
the original opener. `Escape` closes the drawer from anywhere in the root,
|
|
186
|
+
with two places that keep their own order. In the search field it closes
|
|
187
|
+
the drawer first and clears the query second. On a node or the graph's
|
|
188
|
+
surface it closes the drawer first, keeping focus where it is, and releases
|
|
189
|
+
graph focus second. A control of yours that handles `Escape` and calls
|
|
190
|
+
`preventDefault()` keeps it.
|
|
191
|
+
|
|
192
|
+
### Keyboard
|
|
193
|
+
|
|
194
|
+
The graph is one tab stop. Exactly one node is in the tab order: the
|
|
195
|
+
selected node, else the last node you focused from the keyboard, else the
|
|
196
|
+
node nearest the center of the view. That node always has an element, at
|
|
197
|
+
any zoom, so once the graph has been measured Tab always lands on a node.
|
|
198
|
+
Tab again leaves the graph. The graph's surface takes focus when you click
|
|
199
|
+
it, and is not in the tab order.
|
|
200
|
+
|
|
201
|
+
With a node focused:
|
|
202
|
+
|
|
203
|
+
| Key | Does |
|
|
204
|
+
| --- | --- |
|
|
205
|
+
| Arrow keys | move focus to the nearest node in that direction, on screen or not |
|
|
206
|
+
| `Shift` with an arrow | pans |
|
|
207
|
+
| `Enter`, `Space` | inspect the node |
|
|
208
|
+
|
|
209
|
+
With the surface focused, the arrow keys pan. In both cases `+` and `=` zoom
|
|
210
|
+
in, `-` zooms out and `0` fits. `Escape` closes the drawer if it is open,
|
|
211
|
+
and otherwise leaves the graph. A key with `Ctrl`, `Command` or `Alt` is left to the browser.
|
|
212
|
+
|
|
213
|
+
A node that takes focus from the keyboard is brought into view by the
|
|
214
|
+
least pan, at the current zoom. A node you click is not moved to.
|
|
215
|
+
"Keyboard focus" means focus that followed a key press: a pointer press ends
|
|
216
|
+
it, and only keyboard focus moves the camera.
|
|
217
|
+
|
|
218
|
+
Keys typed into content you render inside a node are left to that content.
|
|
219
|
+
|
|
220
|
+
Only the nodes on screen and large enough to read have elements, so a
|
|
221
|
+
screen reader finds only those in the graph. Search is the way to every
|
|
222
|
+
node: it reaches all of them whatever is on screen, and the graph's
|
|
223
|
+
accessible description says so and gives the node and edge counts.
|
|
224
|
+
|
|
225
|
+
The node in the tab order, the focused node and the selected node are
|
|
226
|
+
mounted outside `maxOverlayNodes`, so the page can hold a few more node
|
|
227
|
+
elements than the cap.
|
|
228
|
+
|
|
229
|
+
### Server rendering
|
|
230
|
+
|
|
231
|
+
Every part renders on a server, with no DOM: layout is pure, so the HTML
|
|
232
|
+
carries the shell and the base layer, every node as a mark and every routed
|
|
233
|
+
edge. The camera and the node elements start on the client, once the
|
|
234
|
+
viewport is measured. Until then the plane is hidden, so no unscaled frame
|
|
235
|
+
is painted, and the graph has no tab stop. The viewport's height is fixed by
|
|
236
|
+
`--dagr-explorer-height` (or your `style`), so nothing shifts when it fits.
|
|
237
|
+
A data error throws `ExplorerDataError` on the server as it does on the
|
|
238
|
+
client.
|
|
239
|
+
|
|
240
|
+
### Labels
|
|
241
|
+
|
|
242
|
+
Every string the parts show comes from `labels`, an `ExplorerLabels` object
|
|
243
|
+
whose neutral English defaults are `DEFAULT_EXPLORER_LABELS`. Pass any subset
|
|
244
|
+
to `ExplorerRoot` or `DagrExplorer`. Counts and names are formatters, such as
|
|
245
|
+
`matches(count)`, `moreMatches(count)` (the matches the capped list does not
|
|
246
|
+
show), `stats({ nodes, edges })`, `zoomLevel(percent)`, `zoomTo(label)` and
|
|
247
|
+
`inGroup(groupLabel)` (one group in a node's default accessible name, as in
|
|
248
|
+
"Store, in Data tier").
|
|
249
|
+
|
|
250
|
+
An inline object is fine: `labels={{ search: 'Find' }}` is kept by value,
|
|
251
|
+
so re-creating it on every render with the same contents changes nothing.
|
|
252
|
+
An inline formatter is a new function each time, and so a change; define it
|
|
253
|
+
outside the component to keep it stable.
|
|
254
|
+
|
|
255
|
+
### Styling
|
|
256
|
+
|
|
257
|
+
The parts work with no stylesheet: what they need to function is inline. Each
|
|
258
|
+
carries a `data-dagr-explorer` hook (`root`, `views`, `search`, `trace`,
|
|
259
|
+
`viewport`, `node`, `details`, `toolbar` and others), and state hooks
|
|
260
|
+
`data-tier`, `data-selected`, `data-dimmed`, `data-dragging` and
|
|
261
|
+
`data-active`. Nothing names a host framework.
|
|
262
|
+
|
|
263
|
+
`@prnt/dagr-explorer/styles.css` is the optional default look, for a light
|
|
264
|
+
page. No module imports it. It reads these variables, which you set anywhere
|
|
265
|
+
above the explorer:
|
|
266
|
+
|
|
267
|
+
| Variable | For |
|
|
268
|
+
| --- | --- |
|
|
269
|
+
| `--dagr-explorer-accent` | selection and pressed controls |
|
|
270
|
+
| `--dagr-explorer-fg` | text |
|
|
271
|
+
| `--dagr-explorer-fg-muted` | secondary text, marks and edges |
|
|
272
|
+
| `--dagr-explorer-border` | borders |
|
|
273
|
+
| `--dagr-explorer-bg` | nodes, controls and the drawer |
|
|
274
|
+
| `--dagr-explorer-bg-subtle` | the graph's background, hover |
|
|
275
|
+
| `--dagr-explorer-focus` | focus rings |
|
|
276
|
+
| `--dagr-explorer-font-mono` | the zoom readout |
|
|
277
|
+
|
|
278
|
+
Its selectors are wrapped in `:where()`, so they have no specificity and any
|
|
279
|
+
rule of yours wins. The exceptions are the `:hover`, `:focus` and
|
|
280
|
+
`:focus-visible` rules, whose pseudo-class sits outside the `:where()`: each
|
|
281
|
+
weighs as one class, so override it with a class that comes later or with a
|
|
282
|
+
more specific selector.
|
|
283
|
+
`--dagr-explorer-height` sets the graph's height, 480px by default, with or
|
|
284
|
+
without the stylesheet.
|
|
285
|
+
|
|
286
|
+
### The base layer is experimental
|
|
287
|
+
|
|
288
|
+
Nodes too small to read, edges and group outlines are drawn by a base layer,
|
|
289
|
+
SVG by default. `ExplorerViewport`'s `base` swaps it, through the
|
|
290
|
+
`ExplorerBase`, `ExplorerBaseProps`, `ExplorerCameraSource`,
|
|
291
|
+
`ExplorerEmphasis` and `ExplorerVisibleSet` types. They are exported and
|
|
292
|
+
experimental: a seam with one implementation is a guess, and they may change
|
|
293
|
+
when a native base over `DagrCanvas` lands and confirms or corrects them.
|
|
294
|
+
`ExplorerCamera` (`{ x, y, scale }`), what `camera.get()` returns, is
|
|
295
|
+
exported as a type too.
|
|
296
|
+
|
|
297
|
+
**The SVG base is smooth to about 4,000 nodes.** Panned at one and a half
|
|
298
|
+
times the fit zoom, where every node and edge is in the base, in Chromium
|
|
299
|
+
153 on an Apple M4 (macOS, 16 GB, device pixel ratio 1), on 2026-10-04:
|
|
300
|
+
|
|
301
|
+
| Nodes | Edges | Median frame | 95th percentile | Frames dropped |
|
|
302
|
+
| --- | --- | --- | --- | --- |
|
|
303
|
+
| 500 | 643 | 16.7 ms | 18.3 ms (1 frame) | 0% |
|
|
304
|
+
| 1,000 | 1,314 | 16.7 ms | 18.3 ms (1 frame) | 0% |
|
|
305
|
+
| 2,000 | 2,661 | 16.7 ms | 18.1 ms (1 frame) | 0% |
|
|
306
|
+
| 4,000 | 5,407 | 16.7 ms | 18.4 ms (1 frame) | 1% |
|
|
307
|
+
| 8,000 | 10,826 | 16.7 ms | 33.4 ms (2 frames) | 11% |
|
|
308
|
+
|
|
309
|
+
A size is smooth when its 95th percentile frame stays within one 16.7 ms
|
|
310
|
+
frame, counted in frames because timestamps jitter by a millisecond or two.
|
|
311
|
+
The method and the harness are in the repository's `bench/browser`. Above
|
|
312
|
+
the ceiling the answer is a native base, which is not built yet.
|
|
313
|
+
|
|
314
|
+
### Known browser differences
|
|
315
|
+
|
|
316
|
+
Checked on 2026-10-04 in Chromium 153 and WebKit 26.6 (Playwright 1.63), at
|
|
317
|
+
1440 by 900 and at 390 by 844 with touch, against the docs demos. Every
|
|
318
|
+
check passed in both. What differs is the browsers, not the explorer:
|
|
319
|
+
|
|
320
|
+
- **WebKit, with macOS's default keyboard setting, leaves buttons out of the
|
|
321
|
+
Tab order,** as it does on every page: the view switcher, the trace toggle,
|
|
322
|
+
the search results and the toolbar. The graph's tab stop is still reached,
|
|
323
|
+
because the explorer gives that node `tabIndex` 0 explicitly.
|
|
324
|
+
- **WebKit stops a page from scrolling under a non-passive wheel listener
|
|
325
|
+
when the page's root sets `overscroll-behavior: none`.** The explorer holds
|
|
326
|
+
its wheel listener only while the graph has focus, so the page scrolls past
|
|
327
|
+
an unfocused graph, and a host's own non-passive wheel listener on an
|
|
328
|
+
ancestor would bring the problem back.
|
|
329
|
+
- **A phone has no wheel, so neither does the check.** The wheel and
|
|
330
|
+
`Ctrl` wheel checks were run on the 390 profile in Chromium and not in
|
|
331
|
+
WebKit, where Playwright has no wheel for a mobile page. The touch pinch
|
|
332
|
+
was checked in both, synthesized as pointer events, and in Chromium also
|
|
333
|
+
as real touch input.
|
|
334
|
+
|
|
335
|
+
## The core
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
import { layoutView, searchNodes } from '@prnt/dagr-explorer';
|
|
339
|
+
import type { ExplorerView } from '@prnt/dagr-explorer';
|
|
340
|
+
|
|
341
|
+
const view: ExplorerView = {
|
|
342
|
+
id: 'overview',
|
|
343
|
+
label: 'Overview',
|
|
344
|
+
nodes: [
|
|
345
|
+
{ id: 'app', label: 'Application' },
|
|
346
|
+
{ id: 'store', label: 'Store' },
|
|
347
|
+
],
|
|
348
|
+
edges: [{ id: 'read', source: 'app', target: 'store' }],
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const layout = layoutView(view);
|
|
352
|
+
layout.boxes.get('app'); // { x: 40, y: 40, width: 240, height: 120 }
|
|
353
|
+
layout.routes.get('read'); // [{ x: 280, y: 100 }, { x: 400, y: 100 }]
|
|
354
|
+
|
|
355
|
+
searchNodes(view.nodes, 'sto'); // [{ id: 'store', label: 'Store' }]
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
These functions touch no DOM and render nothing, so they run on a server.
|
|
359
|
+
|
|
360
|
+
## A node is an id and a label
|
|
361
|
+
|
|
362
|
+
Those two fields are all the explorer reads. Everything else about a node is
|
|
363
|
+
your own fields on a type that extends `ExplorerNode`, and that type flows
|
|
364
|
+
through to every function and every slot.
|
|
365
|
+
|
|
366
|
+
## Sizes are declared, never measured
|
|
367
|
+
|
|
368
|
+
A node's size is its own `size`, else the view's `layout.nodeSize` (a value or
|
|
369
|
+
a function of the node), else 240 by 120. The explorer virtualizes node
|
|
370
|
+
content, and a node with no element cannot be measured.
|
|
371
|
+
|
|
372
|
+
A width or height that is not finite and greater than zero throws
|
|
373
|
+
`INVALID_NODE_SIZE`, naming the node.
|
|
374
|
+
|
|
375
|
+
## Layout
|
|
376
|
+
|
|
377
|
+
`layoutView(view, options)` returns boxes, routes and group rectangles in world
|
|
378
|
+
space: y-down CSS pixels at zoom 1, padded 40 off the origin.
|
|
379
|
+
|
|
380
|
+
| `view.layout` | Default | |
|
|
381
|
+
| --- | --- | --- |
|
|
382
|
+
| `direction` | `'right'` | or `'down'` |
|
|
383
|
+
| `nodeSize` | 240 by 120 | a size, or a function of the node |
|
|
384
|
+
| `nodeSep` | 40 | gap between neighbors across the flow |
|
|
385
|
+
| `rankSep` | 120 | gap between ranks along the flow |
|
|
386
|
+
| `edgeStyle` | `'smooth'` | or `'orthogonal'` |
|
|
387
|
+
|
|
388
|
+
Three behaviors are the explorer's, not the layout engine's:
|
|
389
|
+
|
|
390
|
+
- **Parallel edges on one line bow apart.** Edges joining the same two nodes,
|
|
391
|
+
in either direction, are drawn on one line when they span a single rank.
|
|
392
|
+
Those are separated by 16, with both ends left on their nodes. A pair that
|
|
393
|
+
spans more ranks is left as the layout engine routed it, which is already
|
|
394
|
+
apart.
|
|
395
|
+
- **A self loop is not drawn.** An edge from a node to itself stays in your
|
|
396
|
+
data and has an empty route. It moves nothing.
|
|
397
|
+
- **A group moves no node.** It is the padded hull of its members with a band
|
|
398
|
+
above for its label. Pass `{ strictGroups: true }` to throw
|
|
399
|
+
`GROUP_ENCLOSES_NON_MEMBER` when an outline would overlap a node that is not
|
|
400
|
+
a member, even partly, for a diagram where that would be a false statement.
|
|
401
|
+
An outline that only touches a neighbor is not an overlap.
|
|
402
|
+
|
|
403
|
+
## Search
|
|
404
|
+
|
|
405
|
+
`searchNodes(nodes, query, searchText)` returns the nodes whose text contains
|
|
406
|
+
every whitespace-separated token of the query, case ignored, in data order. The
|
|
407
|
+
text is the id and label unless you pass `searchText`, an accessor over your
|
|
408
|
+
own node type. An empty query matches nothing. The query is literal text, not a
|
|
409
|
+
pattern.
|
|
410
|
+
|
|
411
|
+
## Errors
|
|
412
|
+
|
|
413
|
+
A malformed view throws `ExplorerDataError`. `ExplorerRoot` validates every
|
|
414
|
+
view and lays out the active one during render, so an error boundary
|
|
415
|
+
catches it. Switch on its `code`. Its `id` is
|
|
416
|
+
the view, node, edge or group the error is about, and its `viewId` is the view
|
|
417
|
+
that was found in (`undefined` when the error is about a view itself), so a
|
|
418
|
+
host can point at the offender without parsing the message.
|
|
419
|
+
|
|
420
|
+
`layoutView` validates its view first, so it throws these too. `validateView`
|
|
421
|
+
and `validateViews` run the same checks without laying out. Only
|
|
422
|
+
`validateViews` can raise `DUPLICATE_VIEW_ID`, since it is the only one that
|
|
423
|
+
sees more than one view.
|
|
424
|
+
|
|
425
|
+
| `code` | When |
|
|
426
|
+
| --- | --- |
|
|
427
|
+
| `INVALID_ID` | a view, node, edge or group has an empty id |
|
|
428
|
+
| `DUPLICATE_VIEW_ID` | two views share an id |
|
|
429
|
+
| `DUPLICATE_NODE_ID` | two nodes in one view share an id |
|
|
430
|
+
| `DUPLICATE_EDGE_ID` | two edges in one view share an id |
|
|
431
|
+
| `DUPLICATE_GROUP_ID` | two groups in one view share an id |
|
|
432
|
+
| `INVALID_NODE_SIZE` | a node's width or height is not finite and greater than zero |
|
|
433
|
+
| `INVALID_LAYOUT_OPTION` | a view's `nodeSep` or `rankSep` is not finite and zero or greater, or its `direction` or `edgeStyle` is not one of the allowed values |
|
|
434
|
+
| `MISSING_EDGE_ENDPOINT` | an edge names a node its view lacks |
|
|
435
|
+
| `MISSING_GROUP_MEMBER` | a group names a node its view lacks |
|
|
436
|
+
| `EMPTY_GROUP` | a group has no members |
|
|
437
|
+
| `GROUP_ENCLOSES_NON_MEMBER` | `strictGroups` only |
|
|
438
|
+
|
|
439
|
+
Ids may repeat across views.
|
|
440
|
+
|
|
441
|
+
MIT © prnt.design
|
package/dist/base.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The base-layer seam: what draws every node the overlay does not mount.
|
|
3
|
+
*
|
|
4
|
+
* The viewport renders the overlay, capped, as DOM. Everything else in view
|
|
5
|
+
* (nodes as marks, edges, group outlines) is the base layer's, and the base
|
|
6
|
+
* is swappable: the SVG base is the default, and a native renderer can take
|
|
7
|
+
* its place for graphs past the SVG ceiling.
|
|
8
|
+
*
|
|
9
|
+
* `space` is the one fact the viewport needs about a base. A `'plane'` base
|
|
10
|
+
* renders inside the transformed plane in world coordinates, so a camera
|
|
11
|
+
* frame costs it nothing. A `'viewport'` base sits outside the plane and
|
|
12
|
+
* drives its own camera from `camera`, which tells it of every frame in the
|
|
13
|
+
* same frame the overlay moves, so the two never drift apart.
|
|
14
|
+
*
|
|
15
|
+
* **Experimental.** These types are exported, and may change until the
|
|
16
|
+
* native base lands and confirms or corrects them: a seam with one
|
|
17
|
+
* implementation is a guess.
|
|
18
|
+
*/
|
|
19
|
+
import type { ComponentType } from 'react';
|
|
20
|
+
import type { ExplorerCamera } from './camera.js';
|
|
21
|
+
import type { ExplorerLayout } from './layout.js';
|
|
22
|
+
import type { ExplorerEdge, ExplorerNode, ExplorerView } from './types.js';
|
|
23
|
+
import type { ExplorerVisibleSet } from './visible-set.js';
|
|
24
|
+
/** What the base draws emphasized. Experimental, like the seam. */
|
|
25
|
+
export interface ExplorerEmphasis {
|
|
26
|
+
readonly selectedId: string | null;
|
|
27
|
+
/** Node ids drawn dimmed. Edges with a dimmed end are dimmed. */
|
|
28
|
+
readonly dimmed: ReadonlySet<string>;
|
|
29
|
+
}
|
|
30
|
+
/** The camera on screen, and every frame of it. Experimental, like the seam. */
|
|
31
|
+
export interface ExplorerCameraSource {
|
|
32
|
+
/** The camera on screen, or `null` before the first fit. */
|
|
33
|
+
get(): ExplorerCamera | null;
|
|
34
|
+
/**
|
|
35
|
+
* Calls `listener` on each drawn frame, right after the plane's transform
|
|
36
|
+
* is written. Returns the unsubscribe.
|
|
37
|
+
*/
|
|
38
|
+
subscribe(listener: (camera: ExplorerCamera) => void): () => void;
|
|
39
|
+
}
|
|
40
|
+
/** What a base layer is given. Experimental: may change until a native base confirms it. */
|
|
41
|
+
export interface ExplorerBaseProps<N extends ExplorerNode, E extends ExplorerEdge> {
|
|
42
|
+
/**
|
|
43
|
+
* The view as given, less its layout options: `nodeSize` there is a
|
|
44
|
+
* function of `N`, which a base typed over the base node cannot accept,
|
|
45
|
+
* and the layout is already done.
|
|
46
|
+
*/
|
|
47
|
+
readonly view: Omit<ExplorerView<N, E>, 'layout'>;
|
|
48
|
+
readonly layout: ExplorerLayout;
|
|
49
|
+
readonly visible: ExplorerVisibleSet;
|
|
50
|
+
readonly emphasis: ExplorerEmphasis;
|
|
51
|
+
readonly camera: ExplorerCameraSource;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A base layer: what draws every node the overlay does not mount, every
|
|
55
|
+
* edge and every group outline. Experimental: may change until a native
|
|
56
|
+
* base confirms it.
|
|
57
|
+
*/
|
|
58
|
+
export interface ExplorerBase {
|
|
59
|
+
readonly Layer: ComponentType<ExplorerBaseProps<ExplorerNode, ExplorerEdge>>;
|
|
60
|
+
/** 'plane': rendered inside the transformed plane. 'viewport': handles the camera itself. */
|
|
61
|
+
readonly space: 'plane' | 'viewport';
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../src/base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACtC;AAED,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,GAAG,IAAI,cAAc,GAAG,IAAI,CAAC;IAC7B;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACnE;AAED,4FAA4F;AAC5F,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,YAAY;IAC/E;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;CACvC;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,iBAAiB,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;IAC7E,6FAA6F;IAC7F,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAAC;CACtC"}
|
package/dist/base.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The base-layer seam: what draws every node the overlay does not mount.
|
|
3
|
+
*
|
|
4
|
+
* The viewport renders the overlay, capped, as DOM. Everything else in view
|
|
5
|
+
* (nodes as marks, edges, group outlines) is the base layer's, and the base
|
|
6
|
+
* is swappable: the SVG base is the default, and a native renderer can take
|
|
7
|
+
* its place for graphs past the SVG ceiling.
|
|
8
|
+
*
|
|
9
|
+
* `space` is the one fact the viewport needs about a base. A `'plane'` base
|
|
10
|
+
* renders inside the transformed plane in world coordinates, so a camera
|
|
11
|
+
* frame costs it nothing. A `'viewport'` base sits outside the plane and
|
|
12
|
+
* drives its own camera from `camera`, which tells it of every frame in the
|
|
13
|
+
* same frame the overlay moves, so the two never drift apart.
|
|
14
|
+
*
|
|
15
|
+
* **Experimental.** These types are exported, and may change until the
|
|
16
|
+
* native base lands and confirms or corrects them: a seam with one
|
|
17
|
+
* implementation is a guess.
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=base.js.map
|
package/dist/base.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../src/base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
|