@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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ExplorerDetails`: the drawer that shows the inspected node.
|
|
3
|
+
*
|
|
4
|
+
* It renders nothing while closed. Open, it is an overlay at every width,
|
|
5
|
+
* positioned against the nearest positioned ancestor (the viewport's stage
|
|
6
|
+
* when it is `ExplorerViewport`'s child, else the root or a host's own
|
|
7
|
+
* wrapper), so opening it never resizes the graph.
|
|
8
|
+
*
|
|
9
|
+
* The body is a keyboard scroll stop and scrolls to the top when the
|
|
10
|
+
* inspected node changes. Its content is the slot's, or by default the
|
|
11
|
+
* node's label and its connections as buttons, each inspecting the node at
|
|
12
|
+
* the other end. Those buttons keep the drawer's original opener, so
|
|
13
|
+
* `Escape` after following three edges still returns where the reader began.
|
|
14
|
+
*
|
|
15
|
+
* Closing restores focus, and the root does it, because every way the drawer
|
|
16
|
+
* closes (this part's `Escape` and close button, the api, the selected node
|
|
17
|
+
* leaving the data) ends in the same place. See `ExplorerRoot`.
|
|
18
|
+
*/
|
|
19
|
+
import type { CSSProperties, ReactElement, ReactNode } from 'react';
|
|
20
|
+
import type { ExplorerEdge, ExplorerNode } from './types.js';
|
|
21
|
+
/** An edge touching the inspected node, and the node at its other end. */
|
|
22
|
+
export interface ExplorerConnection<N extends ExplorerNode = ExplorerNode, E extends ExplorerEdge = ExplorerEdge> {
|
|
23
|
+
readonly edge: E;
|
|
24
|
+
/** The other end. For a self loop, the node itself. */
|
|
25
|
+
readonly node: N;
|
|
26
|
+
}
|
|
27
|
+
/** What the drawer's slot receives. */
|
|
28
|
+
export interface ExplorerDetailsContext<N extends ExplorerNode = ExplorerNode, E extends ExplorerEdge = ExplorerEdge> {
|
|
29
|
+
readonly node: N;
|
|
30
|
+
/** Every edge touching `node`, in data order. */
|
|
31
|
+
readonly connections: readonly ExplorerConnection<N, E>[];
|
|
32
|
+
/** Inspects another node, keeping the drawer's opener. */
|
|
33
|
+
readonly inspect: (id: string) => void;
|
|
34
|
+
}
|
|
35
|
+
export interface ExplorerDetailsProps<N extends ExplorerNode = ExplorerNode, E extends ExplorerEdge = ExplorerEdge> {
|
|
36
|
+
/** The drawer's content. Default: the label and the connection list. */
|
|
37
|
+
readonly children?: ((context: ExplorerDetailsContext<N, E>) => ReactNode) | undefined;
|
|
38
|
+
/** One connection's content in the default list. Default: the other node's label. */
|
|
39
|
+
readonly renderConnection?: ((edge: E, otherNode: N) => ReactNode) | undefined;
|
|
40
|
+
readonly className?: string | undefined;
|
|
41
|
+
readonly style?: CSSProperties | undefined;
|
|
42
|
+
}
|
|
43
|
+
export declare function ExplorerDetails<N extends ExplorerNode = ExplorerNode, E extends ExplorerEdge = ExplorerEdge>(props: ExplorerDetailsProps<N, E>): ReactElement | null;
|
|
44
|
+
//# sourceMappingURL=explorer-details.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-details.d.ts","sourceRoot":"","sources":["../src/explorer-details.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAiB,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEnF,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE7D,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,CAAC,SAAS,YAAY,GAAG,YAAY;IAC9G,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;CAClB;AAED,uCAAuC;AACvC,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,CAAC,SAAS,YAAY,GAAG,YAAY;IAClH,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,iDAAiD;IACjD,QAAQ,CAAC,WAAW,EAAE,SAAS,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IAC1D,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,CAAC,SAAS,YAAY,GAAG,YAAY;IAChH,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,sBAAsB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,GAAG,SAAS,CAAC;IACvF,qFAAqF;IACrF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,KAAK,SAAS,CAAC,GAAG,SAAS,CAAC;IAC/E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC5C;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,CAAC,SAAS,YAAY,GAAG,YAAY,EAC1G,KAAK,EAAE,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,GAChC,YAAY,GAAG,IAAI,CAwGrB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `ExplorerDetails`: the drawer that shows the inspected node.
|
|
4
|
+
*
|
|
5
|
+
* It renders nothing while closed. Open, it is an overlay at every width,
|
|
6
|
+
* positioned against the nearest positioned ancestor (the viewport's stage
|
|
7
|
+
* when it is `ExplorerViewport`'s child, else the root or a host's own
|
|
8
|
+
* wrapper), so opening it never resizes the graph.
|
|
9
|
+
*
|
|
10
|
+
* The body is a keyboard scroll stop and scrolls to the top when the
|
|
11
|
+
* inspected node changes. Its content is the slot's, or by default the
|
|
12
|
+
* node's label and its connections as buttons, each inspecting the node at
|
|
13
|
+
* the other end. Those buttons keep the drawer's original opener, so
|
|
14
|
+
* `Escape` after following three edges still returns where the reader began.
|
|
15
|
+
*
|
|
16
|
+
* Closing restores focus, and the root does it, because every way the drawer
|
|
17
|
+
* closes (this part's `Escape` and close button, the api, the selected node
|
|
18
|
+
* leaving the data) ends in the same place. See `ExplorerRoot`.
|
|
19
|
+
*/
|
|
20
|
+
import { useEffect, useId, useMemo, useRef } from 'react';
|
|
21
|
+
import { useExplorerContext } from './context.js';
|
|
22
|
+
export function ExplorerDetails(props) {
|
|
23
|
+
const { children, renderConnection, className, style } = props;
|
|
24
|
+
const { state } = useExplorerContext('ExplorerDetails');
|
|
25
|
+
const { labels, activeView, detailsOpen } = state;
|
|
26
|
+
// The part's type parameters are a claim about the root's data. See context.ts.
|
|
27
|
+
const node = detailsOpen ? state.selectedNode : null;
|
|
28
|
+
const nodeId = node?.id ?? null;
|
|
29
|
+
const titleId = useId();
|
|
30
|
+
const connectionsId = useId();
|
|
31
|
+
const bodyRef = useRef(null);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (nodeId !== null && bodyRef.current !== null)
|
|
34
|
+
bodyRef.current.scrollTop = 0;
|
|
35
|
+
}, [nodeId]);
|
|
36
|
+
const connections = useMemo(() => {
|
|
37
|
+
if (nodeId === null || activeView === null)
|
|
38
|
+
return [];
|
|
39
|
+
const byId = new Map(activeView.nodes.map((each) => [each.id, each]));
|
|
40
|
+
const list = [];
|
|
41
|
+
for (const edge of activeView.edges) {
|
|
42
|
+
if (edge.source !== nodeId && edge.target !== nodeId)
|
|
43
|
+
continue;
|
|
44
|
+
const other = byId.get(edge.source === nodeId ? edge.target : edge.source);
|
|
45
|
+
if (other !== undefined)
|
|
46
|
+
list.push({ edge, node: other });
|
|
47
|
+
}
|
|
48
|
+
return list;
|
|
49
|
+
}, [nodeId, activeView]);
|
|
50
|
+
if (node === null)
|
|
51
|
+
return null;
|
|
52
|
+
// No trigger, and the drawer is open: the root keeps the opener it has.
|
|
53
|
+
const inspect = (id) => state.inspect(id);
|
|
54
|
+
const onKeyDown = (event) => {
|
|
55
|
+
if (event.key !== 'Escape')
|
|
56
|
+
return;
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
state.closeDetails();
|
|
59
|
+
};
|
|
60
|
+
return (_jsxs("aside", { "data-dagr-explorer": "details", "aria-labelledby": titleId, className: className, onKeyDown: onKeyDown, style: {
|
|
61
|
+
position: 'absolute',
|
|
62
|
+
top: 0,
|
|
63
|
+
right: 0,
|
|
64
|
+
bottom: 0,
|
|
65
|
+
width: 'min(360px, 100%)',
|
|
66
|
+
display: 'flex',
|
|
67
|
+
flexDirection: 'column',
|
|
68
|
+
zIndex: 1,
|
|
69
|
+
// The stylesheet's fallback, so the drawer looks the same with or without it.
|
|
70
|
+
background: 'var(--dagr-explorer-bg, #ffffff)',
|
|
71
|
+
...style,
|
|
72
|
+
}, children: [_jsxs("div", { "data-dagr-explorer": "details-header", style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', flex: 'none' }, children: [_jsx("span", { id: titleId, children: labels.drawerTitle }), _jsx("button", { type: "button", "data-dagr-explorer": "details-close", onClick: () => state.closeDetails(), children: labels.close })] }), _jsx("div", { ref: bodyRef, "data-dagr-explorer": "details-body", tabIndex: 0,
|
|
73
|
+
// A keyboard scroll stop is announced, so it needs a name: the drawer's title.
|
|
74
|
+
role: "region", "aria-labelledby": titleId, style: { overflow: 'auto', flex: '1 1 auto', minHeight: 0 }, children: children !== undefined ? (children({ node, connections, inspect })) : (_jsxs(_Fragment, { children: [_jsx("p", { "data-dagr-explorer": "details-label", children: node.label }), connections.length === 0 ? null : (_jsxs(_Fragment, { children: [_jsx("p", { id: connectionsId, children: labels.connections }), _jsx("ul", { "data-dagr-explorer": "connections", "aria-labelledby": connectionsId, style: { listStyle: 'none', margin: 0, padding: 0 }, children: connections.map(({ edge, node: other }) => (_jsx("li", { children: _jsx("button", { type: "button", "data-node-id": other.id, onClick: () => inspect(other.id), children: renderConnection === undefined ? other.label : renderConnection(edge, other) }) }, edge.id))) })] }))] })) })] }));
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=explorer-details.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-details.js","sourceRoot":"","sources":["../src/explorer-details.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ExplorerSearch`: a labeled field, a live match count, and the matches as
|
|
3
|
+
* a list of buttons.
|
|
4
|
+
*
|
|
5
|
+
* Search is the complete way to every node. A virtualized node has no
|
|
6
|
+
* element, so the graph alone cannot reach it by keyboard or screen reader,
|
|
7
|
+
* and search can: every match is counted, and `Enter` reaches the first.
|
|
8
|
+
*
|
|
9
|
+
* **The list is capped,** at `maxResults` (default 50), in data order, with
|
|
10
|
+
* a plain line from `labels.moreMatches` for the rest, so a short query on a
|
|
11
|
+
* large graph does not mount thousands of buttons. A longer query narrows
|
|
12
|
+
* it. The list is memoized on the matches, which the root keeps by identity
|
|
13
|
+
* while the same nodes match.
|
|
14
|
+
*
|
|
15
|
+
* `Enter` inspects the first match and flies the camera to it, and choosing
|
|
16
|
+
* a result does the same for that result. The list stays mounted while a
|
|
17
|
+
* node is inspected, so its scroll position survives.
|
|
18
|
+
*
|
|
19
|
+
* `Escape` in the field closes the drawer if it is open, and only then
|
|
20
|
+
* clears the query, so a reader who opened a result from here can close it
|
|
21
|
+
* without losing the list.
|
|
22
|
+
*/
|
|
23
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
24
|
+
export interface ExplorerSearchProps {
|
|
25
|
+
/** The most results listed, in data order. Default 50. The count and `Enter` cover every match. */
|
|
26
|
+
readonly maxResults?: number | undefined;
|
|
27
|
+
readonly className?: string | undefined;
|
|
28
|
+
readonly style?: CSSProperties | undefined;
|
|
29
|
+
}
|
|
30
|
+
export declare function ExplorerSearch(props: ExplorerSearchProps): ReactElement;
|
|
31
|
+
//# sourceMappingURL=explorer-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-search.d.ts","sourceRoot":"","sources":["../src/explorer-search.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAiB,YAAY,EAAE,MAAM,OAAO,CAAC;AAMxE,MAAM,WAAW,mBAAmB;IAClC,mGAAmG;IACnG,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC5C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,YAAY,CAsGvE"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `ExplorerSearch`: a labeled field, a live match count, and the matches as
|
|
4
|
+
* a list of buttons.
|
|
5
|
+
*
|
|
6
|
+
* Search is the complete way to every node. A virtualized node has no
|
|
7
|
+
* element, so the graph alone cannot reach it by keyboard or screen reader,
|
|
8
|
+
* and search can: every match is counted, and `Enter` reaches the first.
|
|
9
|
+
*
|
|
10
|
+
* **The list is capped,** at `maxResults` (default 50), in data order, with
|
|
11
|
+
* a plain line from `labels.moreMatches` for the rest, so a short query on a
|
|
12
|
+
* large graph does not mount thousands of buttons. A longer query narrows
|
|
13
|
+
* it. The list is memoized on the matches, which the root keeps by identity
|
|
14
|
+
* while the same nodes match.
|
|
15
|
+
*
|
|
16
|
+
* `Enter` inspects the first match and flies the camera to it, and choosing
|
|
17
|
+
* a result does the same for that result. The list stays mounted while a
|
|
18
|
+
* node is inspected, so its scroll position survives.
|
|
19
|
+
*
|
|
20
|
+
* `Escape` in the field closes the drawer if it is open, and only then
|
|
21
|
+
* clears the query, so a reader who opened a result from here can close it
|
|
22
|
+
* without losing the list.
|
|
23
|
+
*/
|
|
24
|
+
import { useEffect, useId, useMemo, useRef } from 'react';
|
|
25
|
+
import { useExplorerContext } from './context.js';
|
|
26
|
+
/** How many results the list shows before it says how many more there are. */
|
|
27
|
+
const DEFAULT_MAX_RESULTS = 50;
|
|
28
|
+
export function ExplorerSearch(props) {
|
|
29
|
+
const { state, internals } = useExplorerContext('ExplorerSearch');
|
|
30
|
+
const { labels, query, matches, detailsOpen, selectedId, inspect, focusNode } = state;
|
|
31
|
+
const { maxResults } = props;
|
|
32
|
+
const cap = maxResults !== undefined && Number.isFinite(maxResults) && maxResults >= 0
|
|
33
|
+
? Math.floor(maxResults)
|
|
34
|
+
: DEFAULT_MAX_RESULTS;
|
|
35
|
+
const inputId = useId();
|
|
36
|
+
const countId = useId();
|
|
37
|
+
const inputRef = useRef(null);
|
|
38
|
+
// The field is where focus goes when the drawer closes and its opener is gone.
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const field = inputRef.current;
|
|
41
|
+
internals.searchInputRef.current = field;
|
|
42
|
+
return () => {
|
|
43
|
+
if (internals.searchInputRef.current === field)
|
|
44
|
+
internals.searchInputRef.current = null;
|
|
45
|
+
};
|
|
46
|
+
}, [internals]);
|
|
47
|
+
const blank = query.trim() === '';
|
|
48
|
+
// `inspect` and `focusNode` are the root's stable methods.
|
|
49
|
+
const pick = (id, trigger) => {
|
|
50
|
+
inspect(id, trigger);
|
|
51
|
+
focusNode(id);
|
|
52
|
+
};
|
|
53
|
+
const onKeyDown = (event) => {
|
|
54
|
+
if (event.key === 'Enter') {
|
|
55
|
+
const first = matches[0];
|
|
56
|
+
if (first === undefined)
|
|
57
|
+
return;
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
pick(first.id, event.currentTarget);
|
|
60
|
+
}
|
|
61
|
+
else if (event.key === 'Escape') {
|
|
62
|
+
if (detailsOpen)
|
|
63
|
+
state.closeDetails();
|
|
64
|
+
else if (query !== '')
|
|
65
|
+
state.setQuery('');
|
|
66
|
+
else
|
|
67
|
+
return;
|
|
68
|
+
// A search field clears itself on Escape in some browsers, which would
|
|
69
|
+
// skip the drawer's turn.
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const { searchResults, moreMatches } = labels;
|
|
74
|
+
const list = useMemo(() => {
|
|
75
|
+
if (matches.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
const shown = cap < matches.length ? matches.slice(0, cap) : matches;
|
|
78
|
+
const more = matches.length - shown.length;
|
|
79
|
+
return (_jsxs(_Fragment, { children: [shown.length === 0 ? null : (_jsx("ul", { "data-dagr-explorer": "search-results", "aria-label": searchResults, style: { listStyle: 'none', margin: 0, padding: 0, overflow: 'auto' }, children: shown.map((node) => (_jsx("li", { children: _jsx("button", { type: "button", "data-node-id": node.id, "data-selected": node.id === selectedId ? 'true' : undefined, "aria-current": node.id === selectedId ? 'true' : undefined, onClick: (event) => {
|
|
80
|
+
inspect(node.id, event.currentTarget);
|
|
81
|
+
focusNode(node.id);
|
|
82
|
+
}, children: node.label }) }, node.id))) })), more === 0 ? null : _jsx("p", { "data-dagr-explorer": "search-more", children: moreMatches(more) })] }));
|
|
83
|
+
}, [matches, cap, selectedId, searchResults, moreMatches, inspect, focusNode]);
|
|
84
|
+
return (_jsxs("div", { "data-dagr-explorer": "search", role: "search", className: props.className, style: props.style, children: [_jsx("label", { htmlFor: inputId, children: labels.search }), _jsx("input", { ref: inputRef, id: inputId, type: "search", value: query, placeholder: labels.searchPlaceholder, autoComplete: "off", spellCheck: false, "aria-describedby": countId, onChange: (event) => state.setQuery(event.currentTarget.value), onKeyDown: onKeyDown }), _jsx("p", { id: countId, "data-dagr-explorer": "search-count", "aria-live": "polite", children: blank ? '' : labels.matches(matches.length) }), list] }));
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=explorer-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-search.js","sourceRoot":"","sources":["../src/explorer-search.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ExplorerToolbar`: zoom out, the zoom readout, zoom in, fit, and zoom to
|
|
3
|
+
* the selected node.
|
|
4
|
+
*
|
|
5
|
+
* **The readout follows the camera without a render.** It subscribes to the
|
|
6
|
+
* root's camera source and writes its own text when the whole percent
|
|
7
|
+
* changes, so a zoom that runs for twenty frames re-renders nothing. React
|
|
8
|
+
* renders the element with no children, so it never overwrites that text.
|
|
9
|
+
* It is not a live region: announcing every frame of a zoom is noise.
|
|
10
|
+
*/
|
|
11
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
12
|
+
export interface ExplorerToolbarProps {
|
|
13
|
+
readonly className?: string | undefined;
|
|
14
|
+
readonly style?: CSSProperties | undefined;
|
|
15
|
+
}
|
|
16
|
+
export declare function ExplorerToolbar(props: ExplorerToolbarProps): ReactElement;
|
|
17
|
+
//# sourceMappingURL=explorer-toolbar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-toolbar.d.ts","sourceRoot":"","sources":["../src/explorer-toolbar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAQzD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC5C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,YAAY,CAmDzE"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `ExplorerToolbar`: zoom out, the zoom readout, zoom in, fit, and zoom to
|
|
4
|
+
* the selected node.
|
|
5
|
+
*
|
|
6
|
+
* **The readout follows the camera without a render.** It subscribes to the
|
|
7
|
+
* root's camera source and writes its own text when the whole percent
|
|
8
|
+
* changes, so a zoom that runs for twenty frames re-renders nothing. React
|
|
9
|
+
* renders the element with no children, so it never overwrites that text.
|
|
10
|
+
* It is not a live region: announcing every frame of a zoom is noise.
|
|
11
|
+
*/
|
|
12
|
+
import { useEffect, useRef } from 'react';
|
|
13
|
+
import { useExplorerContext } from './context.js';
|
|
14
|
+
/** The same steps as the `+` and `-` keys. */
|
|
15
|
+
const ZOOM_IN = 1.25;
|
|
16
|
+
const ZOOM_OUT = 0.8;
|
|
17
|
+
export function ExplorerToolbar(props) {
|
|
18
|
+
const { state } = useExplorerContext('ExplorerToolbar');
|
|
19
|
+
const { labels, selectedNode, camera } = state;
|
|
20
|
+
const readoutRef = useRef(null);
|
|
21
|
+
const { zoomLevel } = labels;
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const readout = readoutRef.current;
|
|
24
|
+
if (readout === null)
|
|
25
|
+
return undefined;
|
|
26
|
+
let shown = null;
|
|
27
|
+
const write = (now) => {
|
|
28
|
+
if (now === null)
|
|
29
|
+
return;
|
|
30
|
+
const percent = Math.round(now.scale * 100);
|
|
31
|
+
if (percent === shown)
|
|
32
|
+
return;
|
|
33
|
+
shown = percent;
|
|
34
|
+
readout.textContent = zoomLevel(percent);
|
|
35
|
+
};
|
|
36
|
+
write(camera.get());
|
|
37
|
+
return camera.subscribe(write);
|
|
38
|
+
}, [camera, zoomLevel]);
|
|
39
|
+
return (_jsxs("div", { "data-dagr-explorer": "toolbar", role: "group", "aria-label": labels.zoomControls, className: props.className, style: props.style, children: [_jsx("button", { type: "button", "data-action": "zoom-out", onClick: () => state.zoomBy(ZOOM_OUT), children: labels.zoomOut }), _jsx("span", { ref: readoutRef, "data-dagr-explorer": "zoom-level" }), _jsx("button", { type: "button", "data-action": "zoom-in", onClick: () => state.zoomBy(ZOOM_IN), children: labels.zoomIn }), _jsx("button", { type: "button", "data-action": "fit", onClick: () => state.fit(), children: labels.fit }), _jsx("button", { type: "button", "data-action": "zoom-to-selected", disabled: selectedNode === null, onClick: () => {
|
|
40
|
+
if (selectedNode !== null)
|
|
41
|
+
state.focusNode(selectedNode.id);
|
|
42
|
+
}, children: selectedNode === null ? labels.zoomToSelected : labels.zoomTo(selectedNode.label) })] }));
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=explorer-toolbar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-toolbar.js","sourceRoot":"","sources":["../src/explorer-toolbar.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAG1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,8CAA8C;AAC9C,MAAM,OAAO,GAAG,IAAI,CAAC;AACrB,MAAM,QAAQ,GAAG,GAAG,CAAC;AAOrB,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,MAAM,EAAE,KAAK,EAAE,GAAG,kBAAkB,CAAC,iBAAiB,CAAC,CAAC;IACxD,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAC/C,MAAM,UAAU,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAC;IACjD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAE7B,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;QACnC,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,SAAS,CAAC;QACvC,IAAI,KAAK,GAAkB,IAAI,CAAC;QAChC,MAAM,KAAK,GAAG,CAAC,GAA0B,EAAQ,EAAE;YACjD,IAAI,GAAG,KAAK,IAAI;gBAAE,OAAO;YACzB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC;YAC5C,IAAI,OAAO,KAAK,KAAK;gBAAE,OAAO;YAC9B,KAAK,GAAG,OAAO,CAAC;YAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QAC3C,CAAC,CAAC;QACF,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;QACpB,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IAExB,OAAO,CACL,qCACqB,SAAS,EAC5B,IAAI,EAAC,OAAO,gBACA,MAAM,CAAC,YAAY,EAC/B,SAAS,EAAE,KAAK,CAAC,SAAS,EAC1B,KAAK,EAAE,KAAK,CAAC,KAAK,aAElB,iBAAQ,IAAI,EAAC,QAAQ,iBAAa,UAAU,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,YAC/E,MAAM,CAAC,OAAO,GACR,EACT,eAAM,GAAG,EAAE,UAAU,wBAAqB,YAAY,GAAG,EACzD,iBAAQ,IAAI,EAAC,QAAQ,iBAAa,SAAS,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,YAC7E,MAAM,CAAC,MAAM,GACP,EACT,iBAAQ,IAAI,EAAC,QAAQ,iBAAa,KAAK,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,YAC/D,MAAM,CAAC,GAAG,GACJ,EACT,iBACE,IAAI,EAAC,QAAQ,iBACD,kBAAkB,EAC9B,QAAQ,EAAE,YAAY,KAAK,IAAI,EAC/B,OAAO,EAAE,GAAG,EAAE;oBACZ,IAAI,YAAY,KAAK,IAAI;wBAAE,KAAK,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;gBAC9D,CAAC,YAEA,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,GAC3E,IACL,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ExplorerTraceToggle`: trace on and off. With trace on and a node
|
|
3
|
+
* selected, every node not adjacent to it is dimmed.
|
|
4
|
+
*
|
|
5
|
+
* The text names what a press does (`labels.traceOn` while off), so the
|
|
6
|
+
* button is not also `aria-pressed`: a toggle whose name flips and that
|
|
7
|
+
* reports a pressed state reads its state twice. `data-active` is the
|
|
8
|
+
* styling hook.
|
|
9
|
+
*/
|
|
10
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
11
|
+
export interface ExplorerTraceToggleProps {
|
|
12
|
+
readonly className?: string | undefined;
|
|
13
|
+
readonly style?: CSSProperties | undefined;
|
|
14
|
+
}
|
|
15
|
+
export declare function ExplorerTraceToggle(props: ExplorerTraceToggleProps): ReactElement;
|
|
16
|
+
//# sourceMappingURL=explorer-trace-toggle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-trace-toggle.d.ts","sourceRoot":"","sources":["../src/explorer-trace-toggle.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAGzD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC5C;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,YAAY,CAejF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useExplorerContext } from './context.js';
|
|
3
|
+
export function ExplorerTraceToggle(props) {
|
|
4
|
+
const { state } = useExplorerContext('ExplorerTraceToggle');
|
|
5
|
+
const { trace, labels } = state;
|
|
6
|
+
return (_jsx("button", { type: "button", "data-dagr-explorer": "trace", "data-active": trace ? 'true' : undefined, className: props.className, style: props.style, onClick: () => state.setTrace(!trace), children: trace ? labels.traceOff : labels.traceOn }));
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=explorer-trace-toggle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-trace-toggle.js","sourceRoot":"","sources":["../src/explorer-trace-toggle.tsx"],"names":[],"mappings":";AAWA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAOlD,MAAM,UAAU,mBAAmB,CAAC,KAA+B;IACjE,MAAM,EAAE,KAAK,EAAE,GAAG,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;IAC5D,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAChC,OAAO,CACL,iBACE,IAAI,EAAC,QAAQ,wBACM,OAAO,iBACb,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EACvC,SAAS,EAAE,KAAK,CAAC,SAAS,EAC1B,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,YAEpC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,GAClC,CACV,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ExplorerViewport`: the graph surface, wired to the root.
|
|
3
|
+
*
|
|
4
|
+
* It is the internal `ViewportSurface` with its props filled from the root:
|
|
5
|
+
* the active view and its layout, the selection, the dimming, and the
|
|
6
|
+
* activation callbacks (a click inspects, a double click flies to the node).
|
|
7
|
+
* The camera controls and the camera source go back up to the root, so
|
|
8
|
+
* `ExplorerApi`'s camera methods and the toolbar's readout reach them.
|
|
9
|
+
*
|
|
10
|
+
* **One per root, counted in an effect.** The viewport registers with its
|
|
11
|
+
* root in an effect with cleanup, and the second registration throws.
|
|
12
|
+
* Counting in render would misfire under StrictMode's double render, and
|
|
13
|
+
* when Suspense keeps an old tree alive beside a new one.
|
|
14
|
+
*
|
|
15
|
+
* **A view switch remounts the surface,** keyed by view id. That is the
|
|
16
|
+
* camera reset the spec asks of a switch: a new surface fits its layout on
|
|
17
|
+
* its first measure, with no flight from a camera framed on other content.
|
|
18
|
+
*
|
|
19
|
+
* **The surface renders only for what it shows.** It is memoized, and given
|
|
20
|
+
* only the state it draws (the view, the layout, the selection, the dimming)
|
|
21
|
+
* and the root's stable methods, so a keystroke that changes neither the
|
|
22
|
+
* dimming nor the selection does not render the graph.
|
|
23
|
+
*
|
|
24
|
+
* **A stage for overlays.** The surface sits in a positioned element with
|
|
25
|
+
* the part's children, such as `ExplorerDetails`, which position against it.
|
|
26
|
+
* The hint, the graph's description, comes after the stage, so an overlay
|
|
27
|
+
* never covers it.
|
|
28
|
+
*/
|
|
29
|
+
import type { CSSProperties, ReactElement, ReactNode } from 'react';
|
|
30
|
+
import type { ExplorerBase } from './base.js';
|
|
31
|
+
import type { ExplorerEdge, ExplorerGroup, ExplorerNode } from './types.js';
|
|
32
|
+
import type { ExplorerTier, ExplorerTiers } from './visible-set.js';
|
|
33
|
+
/** Only the node type: nothing here takes an edge. */
|
|
34
|
+
export interface ExplorerViewportProps<N extends ExplorerNode = ExplorerNode> {
|
|
35
|
+
/**
|
|
36
|
+
* A node's content, for nodes large enough on screen to read. It sits
|
|
37
|
+
* inside the explorer's own node button, so it must not be interactive.
|
|
38
|
+
* Default: the label.
|
|
39
|
+
*/
|
|
40
|
+
readonly renderNode?: ((node: N, context: {
|
|
41
|
+
readonly tier: ExplorerTier;
|
|
42
|
+
readonly selected: boolean;
|
|
43
|
+
readonly dimmed: boolean;
|
|
44
|
+
}) => ReactNode) | undefined;
|
|
45
|
+
/** A node's accessible name. Default: its label, then `labels.inGroup(group)` for each group. */
|
|
46
|
+
readonly nodeAriaLabel?: ((node: N, context: {
|
|
47
|
+
readonly groups: readonly ExplorerGroup[];
|
|
48
|
+
}) => string) | undefined;
|
|
49
|
+
/** On-screen widths, in CSS pixels, at which a node becomes a summary and then rich. Default 56 and 200. */
|
|
50
|
+
readonly tiers?: ExplorerTiers | undefined;
|
|
51
|
+
/** The most node elements mounted at once, before pins. Default 200. */
|
|
52
|
+
readonly maxOverlayNodes?: number | undefined;
|
|
53
|
+
/** What draws the nodes that have no element. Default: the SVG base. Experimental. */
|
|
54
|
+
readonly base?: ExplorerBase | undefined;
|
|
55
|
+
readonly className?: string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Sizing and decoration pass through: `{ height: 600 }` replaces
|
|
58
|
+
* `--dagr-explorer-height`. `position` and `overflow` stay the viewport's
|
|
59
|
+
* own, because the graph needs them.
|
|
60
|
+
*/
|
|
61
|
+
readonly style?: CSSProperties | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Overlays positioned against the graph, such as `ExplorerDetails`. They
|
|
64
|
+
* share a positioned stage with the graph, and the hint comes after it.
|
|
65
|
+
*/
|
|
66
|
+
readonly children?: ReactNode;
|
|
67
|
+
}
|
|
68
|
+
export declare function ExplorerViewport<N extends ExplorerNode = ExplorerNode, E extends ExplorerEdge = ExplorerEdge>(props: ExplorerViewportProps<N>): ReactElement;
|
|
69
|
+
//# sourceMappingURL=explorer-viewport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-viewport.d.ts","sourceRoot":"","sources":["../src/explorer-viewport.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAwB,MAAM,WAAW,CAAC;AAIpE,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAgB,MAAM,YAAY,CAAC;AAE1F,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEpE,sDAAsD;AACtD,MAAM,WAAW,qBAAqB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY;IAC1E;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAChB,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,SAAS,CAAC,GACxH,SAAS,CAAC;IACd,iGAAiG;IACjG,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAA;KAAE,KAAK,MAAM,CAAC,GAC7E,SAAS,CAAC;IACd,4GAA4G;IAC5G,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC3C,wEAAwE;IACxE,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,sFAAsF;IACtF,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC3C;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;CAC/B;AA6CD,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,CAAC,SAAS,YAAY,GAAG,YAAY,EAC3G,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAC9B,YAAY,CA8Dd"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `ExplorerViewport`: the graph surface, wired to the root.
|
|
4
|
+
*
|
|
5
|
+
* It is the internal `ViewportSurface` with its props filled from the root:
|
|
6
|
+
* the active view and its layout, the selection, the dimming, and the
|
|
7
|
+
* activation callbacks (a click inspects, a double click flies to the node).
|
|
8
|
+
* The camera controls and the camera source go back up to the root, so
|
|
9
|
+
* `ExplorerApi`'s camera methods and the toolbar's readout reach them.
|
|
10
|
+
*
|
|
11
|
+
* **One per root, counted in an effect.** The viewport registers with its
|
|
12
|
+
* root in an effect with cleanup, and the second registration throws.
|
|
13
|
+
* Counting in render would misfire under StrictMode's double render, and
|
|
14
|
+
* when Suspense keeps an old tree alive beside a new one.
|
|
15
|
+
*
|
|
16
|
+
* **A view switch remounts the surface,** keyed by view id. That is the
|
|
17
|
+
* camera reset the spec asks of a switch: a new surface fits its layout on
|
|
18
|
+
* its first measure, with no flight from a camera framed on other content.
|
|
19
|
+
*
|
|
20
|
+
* **The surface renders only for what it shows.** It is memoized, and given
|
|
21
|
+
* only the state it draws (the view, the layout, the selection, the dimming)
|
|
22
|
+
* and the root's stable methods, so a keystroke that changes neither the
|
|
23
|
+
* dimming nor the selection does not render the graph.
|
|
24
|
+
*
|
|
25
|
+
* **A stage for overlays.** The surface sits in a positioned element with
|
|
26
|
+
* the part's children, such as `ExplorerDetails`, which position against it.
|
|
27
|
+
* The hint, the graph's description, comes after the stage, so an overlay
|
|
28
|
+
* never covers it.
|
|
29
|
+
*/
|
|
30
|
+
import { memo, useEffect, useId, useRef } from 'react';
|
|
31
|
+
import { useExplorerApiContext, useExplorerContext } from './context.js';
|
|
32
|
+
import { ViewportSurface } from './viewport-surface.js';
|
|
33
|
+
/**
|
|
34
|
+
* One surface for one view, attached to the root's camera hub while it lives.
|
|
35
|
+
* Memoized: every prop is stable unless what it draws changes.
|
|
36
|
+
*/
|
|
37
|
+
const ViewportPane = memo(function ViewportPane(props) {
|
|
38
|
+
const { api, internals, describedBy, ...rest } = props;
|
|
39
|
+
const sourceRef = useRef(null);
|
|
40
|
+
// The surface's own effects have run by now, so its source is in the ref.
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const source = sourceRef.current;
|
|
43
|
+
return source === null ? undefined : internals.camera.attach(source);
|
|
44
|
+
}, [internals]);
|
|
45
|
+
return (_jsx(ViewportSurface, { ...rest, onNodeActivate: (id, trigger) => api.inspect(id, trigger), onNodeZoom: (id) => api.focusNode(id), controlsRef: internals.controlsRef, cameraSourceRef: sourceRef, describedBy: describedBy }));
|
|
46
|
+
});
|
|
47
|
+
const STAGE = { position: 'relative' };
|
|
48
|
+
export function ExplorerViewport(props) {
|
|
49
|
+
const { children, ...rest } = props;
|
|
50
|
+
const { state, internals } = useExplorerContext('ExplorerViewport');
|
|
51
|
+
const api = useExplorerApiContext('ExplorerViewport');
|
|
52
|
+
const describedBy = useId();
|
|
53
|
+
useEffect(() => internals.registerViewport(), [internals]);
|
|
54
|
+
// The part's type parameters are a claim about the root's data. See context.ts.
|
|
55
|
+
const view = state.activeView;
|
|
56
|
+
const { layout, labels } = state;
|
|
57
|
+
const empty = view === null || layout === null || view.nodes.length === 0;
|
|
58
|
+
// One shape for both states, so the stage and its children survive a view
|
|
59
|
+
// that empties and fills again.
|
|
60
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { "data-dagr-explorer": "stage", style: STAGE, children: [empty ? (_jsx("div", { "data-dagr-explorer": "viewport", "data-empty": "true", role: "region", "aria-label": state.label, className: rest.className, style: {
|
|
61
|
+
position: 'relative',
|
|
62
|
+
height: 'var(--dagr-explorer-height, 480px)',
|
|
63
|
+
display: 'flex',
|
|
64
|
+
alignItems: 'center',
|
|
65
|
+
justifyContent: 'center',
|
|
66
|
+
...rest.style,
|
|
67
|
+
}, children: view === null ? labels.noViews : labels.emptyView })) : (_jsx(ViewportPane, { ...rest, label: state.label, inGroup: labels.inGroup, view: view, layout: layout, selectedId: state.selectedId, dimmed: state.dimmed, api: api, internals: internals, describedBy: describedBy }, view.id)), children] }), empty ? null : (_jsxs("p", { id: describedBy, "data-dagr-explorer": "hint", children: [_jsx("span", { children: labels.stats({ nodes: view.nodes.length, edges: view.edges.length }) }), ' ', _jsx("span", { children: labels.hint })] }))] }));
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=explorer-viewport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-viewport.js","sourceRoot":"","sources":["../src/explorer-viewport.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ExplorerViews`: the view switcher. It renders nothing for a single view,
|
|
3
|
+
* slot or not, because there is nothing to switch to.
|
|
4
|
+
*
|
|
5
|
+
* The default is a group of buttons, the active one `aria-pressed`. Buttons
|
|
6
|
+
* rather than tabs: tabs promise arrow-key movement and a tab panel, and the
|
|
7
|
+
* panel here is the whole explorer.
|
|
8
|
+
*/
|
|
9
|
+
import type { CSSProperties, ReactElement, ReactNode } from 'react';
|
|
10
|
+
import type { ExplorerEdge, ExplorerNode, ExplorerView } from './types.js';
|
|
11
|
+
/** What the switcher's slot receives. */
|
|
12
|
+
export interface ExplorerViewsContext<N extends ExplorerNode = ExplorerNode, E extends ExplorerEdge = ExplorerEdge> {
|
|
13
|
+
readonly views: readonly ExplorerView<N, E>[];
|
|
14
|
+
readonly activeView: ExplorerView<N, E>;
|
|
15
|
+
readonly selectView: (id: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface ExplorerViewsProps<N extends ExplorerNode = ExplorerNode, E extends ExplorerEdge = ExplorerEdge> {
|
|
18
|
+
/** The switcher's content. Default: a button per view. */
|
|
19
|
+
readonly children?: ((context: ExplorerViewsContext<N, E>) => ReactNode) | undefined;
|
|
20
|
+
readonly className?: string | undefined;
|
|
21
|
+
readonly style?: CSSProperties | undefined;
|
|
22
|
+
}
|
|
23
|
+
export declare function ExplorerViews<N extends ExplorerNode = ExplorerNode, E extends ExplorerEdge = ExplorerEdge>(props: ExplorerViewsProps<N, E>): ReactElement | null;
|
|
24
|
+
//# sourceMappingURL=explorer-views.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-views.d.ts","sourceRoot":"","sources":["../src/explorer-views.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3E,yCAAyC;AACzC,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,CAAC,SAAS,YAAY,GAAG,YAAY;IAChH,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,CAAC,SAAS,YAAY,GAAG,YAAY;IAC9G,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,GAAG,SAAS,CAAC;IACrF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC5C;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,CAAC,SAAS,YAAY,GAAG,YAAY,EACxG,KAAK,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,GAC9B,YAAY,GAAG,IAAI,CA+BrB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useExplorerContext } from './context.js';
|
|
3
|
+
export function ExplorerViews(props) {
|
|
4
|
+
const { children, className, style } = props;
|
|
5
|
+
const { state } = useExplorerContext('ExplorerViews');
|
|
6
|
+
// The part's type parameters are a claim about the root's data. See context.ts.
|
|
7
|
+
const views = state.views;
|
|
8
|
+
const activeView = state.activeView;
|
|
9
|
+
if (views.length < 2 || activeView === null)
|
|
10
|
+
return null;
|
|
11
|
+
if (children !== undefined) {
|
|
12
|
+
return (_jsx("div", { "data-dagr-explorer": "views", className: className, style: style, children: children({ views, activeView, selectView: state.selectView }) }));
|
|
13
|
+
}
|
|
14
|
+
return (_jsx("div", { "data-dagr-explorer": "views", role: "group", "aria-label": state.labels.views, className: className, style: style, children: views.map((view) => (_jsx("button", { type: "button", "data-view-id": view.id, "aria-pressed": view.id === activeView.id, onClick: () => state.selectView(view.id), children: view.label }, view.id))) }));
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=explorer-views.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer-views.js","sourceRoot":"","sources":["../src/explorer-views.tsx"],"names":[],"mappings":";AAUA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAiBlD,MAAM,UAAU,aAAa,CAC3B,KAA+B;IAE/B,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAC7C,MAAM,EAAE,KAAK,EAAE,GAAG,kBAAkB,CAAC,eAAe,CAAC,CAAC;IACtD,gFAAgF;IAChF,MAAM,KAAK,GAAG,KAAK,CAAC,KAAiD,CAAC;IACtE,MAAM,UAAU,GAAG,KAAK,CAAC,UAAuC,CAAC;IACjE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAEzD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,CACL,oCAAwB,OAAO,EAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,YAC/D,QAAQ,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,GAC1D,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,oCAAwB,OAAO,EAAC,IAAI,EAAC,OAAO,gBAAa,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,YAC5G,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,iBAEE,IAAI,EAAC,QAAQ,kBACC,IAAI,CAAC,EAAE,kBACP,IAAI,CAAC,EAAE,KAAK,UAAU,CAAC,EAAE,EACvC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,YAEvC,IAAI,CAAC,KAAK,IANN,IAAI,CAAC,EAAE,CAOL,CACV,CAAC,GACE,CACP,CAAC;AACJ,CAAC"}
|