@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.
Files changed (135) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/LICENSE +21 -0
  3. package/README.md +441 -0
  4. package/dist/base.d.ts +63 -0
  5. package/dist/base.d.ts.map +1 -0
  6. package/dist/base.js +20 -0
  7. package/dist/base.js.map +1 -0
  8. package/dist/camera.d.ts +61 -0
  9. package/dist/camera.d.ts.map +1 -0
  10. package/dist/camera.js +142 -0
  11. package/dist/camera.js.map +1 -0
  12. package/dist/context.d.ts +110 -0
  13. package/dist/context.d.ts.map +1 -0
  14. package/dist/context.js +71 -0
  15. package/dist/context.js.map +1 -0
  16. package/dist/dagr-explorer.d.ts +41 -0
  17. package/dist/dagr-explorer.d.ts.map +1 -0
  18. package/dist/dagr-explorer.js +13 -0
  19. package/dist/dagr-explorer.js.map +1 -0
  20. package/dist/errors.d.ts +55 -0
  21. package/dist/errors.d.ts.map +1 -0
  22. package/dist/errors.js +57 -0
  23. package/dist/errors.js.map +1 -0
  24. package/dist/explorer-details.d.ts +44 -0
  25. package/dist/explorer-details.d.ts.map +1 -0
  26. package/dist/explorer-details.js +76 -0
  27. package/dist/explorer-details.js.map +1 -0
  28. package/dist/explorer-search.d.ts +31 -0
  29. package/dist/explorer-search.d.ts.map +1 -0
  30. package/dist/explorer-search.js +86 -0
  31. package/dist/explorer-search.js.map +1 -0
  32. package/dist/explorer-toolbar.d.ts +17 -0
  33. package/dist/explorer-toolbar.d.ts.map +1 -0
  34. package/dist/explorer-toolbar.js +44 -0
  35. package/dist/explorer-toolbar.js.map +1 -0
  36. package/dist/explorer-trace-toggle.d.ts +16 -0
  37. package/dist/explorer-trace-toggle.d.ts.map +1 -0
  38. package/dist/explorer-trace-toggle.js +8 -0
  39. package/dist/explorer-trace-toggle.js.map +1 -0
  40. package/dist/explorer-viewport.d.ts +69 -0
  41. package/dist/explorer-viewport.d.ts.map +1 -0
  42. package/dist/explorer-viewport.js +69 -0
  43. package/dist/explorer-viewport.js.map +1 -0
  44. package/dist/explorer-views.d.ts +24 -0
  45. package/dist/explorer-views.d.ts.map +1 -0
  46. package/dist/explorer-views.js +16 -0
  47. package/dist/explorer-views.js.map +1 -0
  48. package/dist/index.d.ts +48 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +33 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/isomorphic-layout-effect.d.ts +7 -0
  53. package/dist/isomorphic-layout-effect.d.ts.map +1 -0
  54. package/dist/isomorphic-layout-effect.js +7 -0
  55. package/dist/isomorphic-layout-effect.js.map +1 -0
  56. package/dist/labels.d.ts +67 -0
  57. package/dist/labels.d.ts.map +1 -0
  58. package/dist/labels.js +50 -0
  59. package/dist/labels.js.map +1 -0
  60. package/dist/layout.d.ts +82 -0
  61. package/dist/layout.d.ts.map +1 -0
  62. package/dist/layout.js +244 -0
  63. package/dist/layout.js.map +1 -0
  64. package/dist/navigation.d.ts +20 -0
  65. package/dist/navigation.d.ts.map +1 -0
  66. package/dist/navigation.js +44 -0
  67. package/dist/navigation.js.map +1 -0
  68. package/dist/root.d.ts +89 -0
  69. package/dist/root.d.ts.map +1 -0
  70. package/dist/root.js +401 -0
  71. package/dist/root.js.map +1 -0
  72. package/dist/search.d.ts +19 -0
  73. package/dist/search.d.ts.map +1 -0
  74. package/dist/search.js +31 -0
  75. package/dist/search.js.map +1 -0
  76. package/dist/size.d.ts +12 -0
  77. package/dist/size.d.ts.map +1 -0
  78. package/dist/size.js +20 -0
  79. package/dist/size.js.map +1 -0
  80. package/dist/svg-base.d.ts +33 -0
  81. package/dist/svg-base.d.ts.map +1 -0
  82. package/dist/svg-base.js +89 -0
  83. package/dist/svg-base.js.map +1 -0
  84. package/dist/types.d.ts +61 -0
  85. package/dist/types.d.ts.map +1 -0
  86. package/dist/types.js +2 -0
  87. package/dist/types.js.map +1 -0
  88. package/dist/use-explorer-camera.d.ts +56 -0
  89. package/dist/use-explorer-camera.d.ts.map +1 -0
  90. package/dist/use-explorer-camera.js +641 -0
  91. package/dist/use-explorer-camera.js.map +1 -0
  92. package/dist/use-explorer.d.ts +23 -0
  93. package/dist/use-explorer.d.ts.map +1 -0
  94. package/dist/use-explorer.js +26 -0
  95. package/dist/use-explorer.js.map +1 -0
  96. package/dist/validate.d.ts +20 -0
  97. package/dist/validate.d.ts.map +1 -0
  98. package/dist/validate.js +99 -0
  99. package/dist/validate.js.map +1 -0
  100. package/dist/viewport-surface.d.ts +90 -0
  101. package/dist/viewport-surface.d.ts.map +1 -0
  102. package/dist/viewport-surface.js +485 -0
  103. package/dist/viewport-surface.js.map +1 -0
  104. package/dist/visible-set.d.ts +65 -0
  105. package/dist/visible-set.d.ts.map +1 -0
  106. package/dist/visible-set.js +168 -0
  107. package/dist/visible-set.js.map +1 -0
  108. package/package.json +62 -0
  109. package/src/base.ts +67 -0
  110. package/src/camera.ts +206 -0
  111. package/src/context.ts +168 -0
  112. package/src/dagr-explorer.tsx +85 -0
  113. package/src/errors.ts +85 -0
  114. package/src/explorer-details.tsx +156 -0
  115. package/src/explorer-search.tsx +140 -0
  116. package/src/explorer-toolbar.tsx +77 -0
  117. package/src/explorer-trace-toggle.tsx +34 -0
  118. package/src/explorer-viewport.tsx +181 -0
  119. package/src/explorer-views.tsx +61 -0
  120. package/src/index.ts +76 -0
  121. package/src/isomorphic-layout-effect.ts +7 -0
  122. package/src/labels.ts +109 -0
  123. package/src/layout.ts +298 -0
  124. package/src/navigation.ts +51 -0
  125. package/src/root.tsx +544 -0
  126. package/src/search.ts +36 -0
  127. package/src/size.ts +23 -0
  128. package/src/svg-base.tsx +173 -0
  129. package/src/types.ts +69 -0
  130. package/src/use-explorer-camera.ts +675 -0
  131. package/src/use-explorer.ts +32 -0
  132. package/src/validate.ts +173 -0
  133. package/src/viewport-surface.tsx +650 -0
  134. package/src/visible-set.ts +225 -0
  135. package/styles.css +253 -0
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `@prnt/dagr-explorer`: an interactive graph explorer.
3
+ *
4
+ * Two ways in, from one implementation: `DagrExplorer`, preassembled, and
5
+ * the named parts it is built from (`ExplorerRoot`, `ExplorerViewport`,
6
+ * `ExplorerSearch` and the rest) with `useExplorer()`, for a host that owns
7
+ * the layout. Beside them, the headless core: the data model, validation,
8
+ * layout into world coordinates, and search, none of which touches the DOM.
9
+ *
10
+ * The optional stylesheet is its own entry, `@prnt/dagr-explorer/styles.css`.
11
+ * Nothing here imports it.
12
+ *
13
+ * From `@prnt/dagr-render` this package imports only the `core` entry, which
14
+ * never loads three.js. `test/imports.test.ts` holds that.
15
+ */
16
+ export { DagrExplorer } from './dagr-explorer.js';
17
+ export type { DagrExplorerProps } from './dagr-explorer.js';
18
+ export { ExplorerRoot } from './root.js';
19
+ export type { ExplorerRootProps } from './root.js';
20
+ export { ExplorerViews } from './explorer-views.js';
21
+ export type { ExplorerViewsContext, ExplorerViewsProps } from './explorer-views.js';
22
+ export { ExplorerSearch } from './explorer-search.js';
23
+ export type { ExplorerSearchProps } from './explorer-search.js';
24
+ export { ExplorerViewport } from './explorer-viewport.js';
25
+ export type { ExplorerViewportProps } from './explorer-viewport.js';
26
+ export { ExplorerDetails } from './explorer-details.js';
27
+ export type { ExplorerConnection, ExplorerDetailsContext, ExplorerDetailsProps, } from './explorer-details.js';
28
+ export { ExplorerToolbar } from './explorer-toolbar.js';
29
+ export type { ExplorerToolbarProps } from './explorer-toolbar.js';
30
+ export { ExplorerTraceToggle } from './explorer-trace-toggle.js';
31
+ export type { ExplorerTraceToggleProps } from './explorer-trace-toggle.js';
32
+ export { useExplorer, useExplorerApi } from './use-explorer.js';
33
+ export type { ExplorerApi, ExplorerState } from './context.js';
34
+ export { DEFAULT_EXPLORER_LABELS } from './labels.js';
35
+ export type { ExplorerLabels } from './labels.js';
36
+ export type { ExplorerBase, ExplorerBaseProps, ExplorerCameraSource, ExplorerEmphasis, } from './base.js';
37
+ export type { ExplorerCamera } from './camera.js';
38
+ export type { ExplorerTier, ExplorerTiers, ExplorerVisibleSet } from './visible-set.js';
39
+ export { ExplorerContextError, ExplorerDataError } from './errors.js';
40
+ export type { DagrExplorerErrorCode, ExplorerContextErrorCode } from './errors.js';
41
+ export { DEFAULT_NODE_SEP, DEFAULT_RANK_SEP, layoutView } from './layout.js';
42
+ export type { ExplorerBox, ExplorerLayout, LayoutViewOptions, } from './layout.js';
43
+ export { defaultSearchText, searchNodes } from './search.js';
44
+ export { DEFAULT_NODE_SIZE, resolveNodeSize } from './size.js';
45
+ export { validateView, validateViews } from './validate.js';
46
+ export type { ExplorerEdge, ExplorerGroup, ExplorerLayoutOptions, ExplorerNode, ExplorerView, Size, } from './types.js';
47
+ export type { Vec2 } from '@prnt/dagr-render/core';
48
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAChE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AAKnB,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAExF,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACtE,YAAY,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAGnF,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC7E,YAAY,EACV,WAAW,EACX,cAAc,EACd,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC5D,YAAY,EACV,YAAY,EACZ,aAAa,EACb,qBAAqB,EACrB,YAAY,EACZ,YAAY,EACZ,IAAI,GACL,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `@prnt/dagr-explorer`: an interactive graph explorer.
3
+ *
4
+ * Two ways in, from one implementation: `DagrExplorer`, preassembled, and
5
+ * the named parts it is built from (`ExplorerRoot`, `ExplorerViewport`,
6
+ * `ExplorerSearch` and the rest) with `useExplorer()`, for a host that owns
7
+ * the layout. Beside them, the headless core: the data model, validation,
8
+ * layout into world coordinates, and search, none of which touches the DOM.
9
+ *
10
+ * The optional stylesheet is its own entry, `@prnt/dagr-explorer/styles.css`.
11
+ * Nothing here imports it.
12
+ *
13
+ * From `@prnt/dagr-render` this package imports only the `core` entry, which
14
+ * never loads three.js. `test/imports.test.ts` holds that.
15
+ */
16
+ export { DagrExplorer } from './dagr-explorer.js';
17
+ export { ExplorerRoot } from './root.js';
18
+ export { ExplorerViews } from './explorer-views.js';
19
+ export { ExplorerSearch } from './explorer-search.js';
20
+ export { ExplorerViewport } from './explorer-viewport.js';
21
+ export { ExplorerDetails } from './explorer-details.js';
22
+ export { ExplorerToolbar } from './explorer-toolbar.js';
23
+ export { ExplorerTraceToggle } from './explorer-trace-toggle.js';
24
+ export { useExplorer, useExplorerApi } from './use-explorer.js';
25
+ export { DEFAULT_EXPLORER_LABELS } from './labels.js';
26
+ export { ExplorerContextError, ExplorerDataError } from './errors.js';
27
+ // `layoutKey` and the fixed spacing constants stay internal on purpose. A
28
+ // public constant cannot change value, or become an option, without a break.
29
+ export { DEFAULT_NODE_SEP, DEFAULT_RANK_SEP, layoutView } from './layout.js';
30
+ export { defaultSearchText, searchNodes } from './search.js';
31
+ export { DEFAULT_NODE_SIZE, resolveNodeSize } from './size.js';
32
+ export { validateView, validateViews } from './validate.js';
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAMxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAgBtD,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEtE,0EAA0E;AAC1E,6EAA6E;AAC7E,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAM7E,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { useEffect } from 'react';
2
+ /**
3
+ * A layout effect in the browser and a plain one on a server, where React 18
4
+ * warns that a layout effect does nothing.
5
+ */
6
+ export declare const useIsomorphicLayoutEffect: typeof useEffect;
7
+ //# sourceMappingURL=isomorphic-layout-effect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isomorphic-layout-effect.d.ts","sourceRoot":"","sources":["../src/isomorphic-layout-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAmB,MAAM,OAAO,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,yBAAyB,kBAAgE,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { useEffect, useLayoutEffect } from 'react';
2
+ /**
3
+ * A layout effect in the browser and a plain one on a server, where React 18
4
+ * warns that a layout effect does nothing.
5
+ */
6
+ export const useIsomorphicLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
7
+ //# sourceMappingURL=isomorphic-layout-effect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isomorphic-layout-effect.js","sourceRoot":"","sources":["../src/isomorphic-layout-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAEnD;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Every string the parts show or announce, in one object.
3
+ *
4
+ * No part writes copy of its own: a host that translates, or names things in
5
+ * its own voice, passes `labels` to `ExplorerRoot` and replaces any subset.
6
+ * Counts and names arrive through formatters, so a language with other plural
7
+ * rules, or another word order, is a function and not a template.
8
+ *
9
+ * The defaults are neutral English and name no host framework.
10
+ */
11
+ export interface ExplorerLabels {
12
+ /** The search field's label. */
13
+ readonly search: string;
14
+ readonly searchPlaceholder: string;
15
+ /** The accessible name of the search result list. */
16
+ readonly searchResults: string;
17
+ /** The live match count, shown while the query is not blank. */
18
+ readonly matches: (count: number) => string;
19
+ /** The line under a capped result list: how many matches it does not show. */
20
+ readonly moreMatches: (count: number) => string;
21
+ /** The graph's node and edge counts, part of its accessible description. */
22
+ readonly stats: (counts: {
23
+ readonly nodes: number;
24
+ readonly edges: number;
25
+ }) => string;
26
+ /** How to use the graph, the rest of its accessible description. */
27
+ readonly hint: string;
28
+ /** The accessible name of the view switcher. */
29
+ readonly views: string;
30
+ /** One group a node is in, as part of the node's default accessible name. */
31
+ readonly inGroup: (groupLabel: string) => string;
32
+ /** The trace toggle while trace is off, so pressing it turns trace on. */
33
+ readonly traceOn: string;
34
+ /** The trace toggle while trace is on. */
35
+ readonly traceOff: string;
36
+ /** The accessible name of the zoom toolbar. */
37
+ readonly zoomControls: string;
38
+ readonly zoomIn: string;
39
+ readonly zoomOut: string;
40
+ /** The zoom readout, from the zoom as a whole percent. */
41
+ readonly zoomLevel: (percent: number) => string;
42
+ readonly fit: string;
43
+ /** The zoom-to-selected button, with a node selected. */
44
+ readonly zoomTo: (label: string) => string;
45
+ /** The zoom-to-selected button, disabled, with nothing selected. */
46
+ readonly zoomToSelected: string;
47
+ /** Shown in place of the graph for a view with no nodes. */
48
+ readonly emptyView: string;
49
+ /** Shown in place of the graph when there are no views. */
50
+ readonly noViews: string;
51
+ /** The drawer's title and accessible name. */
52
+ readonly drawerTitle: string;
53
+ /** The drawer's close button. */
54
+ readonly close: string;
55
+ /** The heading of the drawer's default connection list. */
56
+ readonly connections: string;
57
+ }
58
+ export declare const DEFAULT_EXPLORER_LABELS: ExplorerLabels;
59
+ /**
60
+ * The defaults with the caller's overrides on top. A key the caller set to
61
+ * `undefined`, which a JavaScript caller can do, keeps its default rather
62
+ * than rendering nothing.
63
+ */
64
+ export declare function resolveLabels(overrides: Partial<ExplorerLabels> | undefined): ExplorerLabels;
65
+ /** Whether two label objects hold the same value under every key, by `Object.is`. */
66
+ export declare function sameLabels(a: ExplorerLabels, b: ExplorerLabels): boolean;
67
+ //# sourceMappingURL=labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../src/labels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B,gCAAgC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,qDAAqD;IACrD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;IACvF,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IACjD,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+CAA+C;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,iCAAiC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAKD,eAAO,MAAM,uBAAuB,EAAE,cAyBpC,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,SAAS,GAAG,cAAc,CAM5F;AAED,qFAAqF;AACrF,wBAAgB,UAAU,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,cAAc,GAAG,OAAO,CASxE"}
package/dist/labels.js ADDED
@@ -0,0 +1,50 @@
1
+ const plural = (count, one, many) => `${String(count)} ${count === 1 ? one : many}`;
2
+ export const DEFAULT_EXPLORER_LABELS = Object.freeze({
3
+ search: 'Search nodes',
4
+ searchPlaceholder: 'Name or id',
5
+ searchResults: 'Search results',
6
+ matches: (count) => (count === 0 ? 'No matches' : plural(count, 'match', 'matches')),
7
+ moreMatches: (count) => plural(count, 'more match', 'more matches'),
8
+ stats: ({ nodes, edges }) => `${plural(nodes, 'node', 'nodes')}, ${plural(edges, 'edge', 'edges')}.`,
9
+ hint: 'Search reaches every node. In the graph, the arrow keys move between nodes and Shift with an arrow pans. Click the graph to zoom with the wheel, drag to pan, and press Escape to leave it.',
10
+ views: 'Views',
11
+ inGroup: (groupLabel) => `in ${groupLabel}`,
12
+ traceOn: 'Trace connections',
13
+ traceOff: 'Stop tracing',
14
+ zoomControls: 'Zoom',
15
+ zoomIn: 'Zoom in',
16
+ zoomOut: 'Zoom out',
17
+ zoomLevel: (percent) => `${String(percent)}%`,
18
+ fit: 'Fit',
19
+ zoomTo: (label) => `Zoom to ${label}`,
20
+ zoomToSelected: 'Zoom to selection',
21
+ emptyView: 'This view has no nodes.',
22
+ noViews: 'Nothing to show.',
23
+ drawerTitle: 'Details',
24
+ close: 'Close',
25
+ connections: 'Connections',
26
+ });
27
+ /**
28
+ * The defaults with the caller's overrides on top. A key the caller set to
29
+ * `undefined`, which a JavaScript caller can do, keeps its default rather
30
+ * than rendering nothing.
31
+ */
32
+ export function resolveLabels(overrides) {
33
+ if (overrides === undefined)
34
+ return DEFAULT_EXPLORER_LABELS;
35
+ const defined = Object.fromEntries(Object.entries(overrides).filter(([, value]) => value !== undefined));
36
+ return { ...DEFAULT_EXPLORER_LABELS, ...defined };
37
+ }
38
+ /** Whether two label objects hold the same value under every key, by `Object.is`. */
39
+ export function sameLabels(a, b) {
40
+ if (a === b)
41
+ return true;
42
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
43
+ for (const key of keys) {
44
+ if (!Object.is(a[key], b[key])) {
45
+ return false;
46
+ }
47
+ }
48
+ return true;
49
+ }
50
+ //# sourceMappingURL=labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.js","sourceRoot":"","sources":["../src/labels.ts"],"names":[],"mappings":"AAuDA,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,GAAW,EAAE,IAAY,EAAU,EAAE,CAClE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAEjD,MAAM,CAAC,MAAM,uBAAuB,GAAmB,MAAM,CAAC,MAAM,CAAC;IACnE,MAAM,EAAE,cAAc;IACtB,iBAAiB,EAAE,YAAY;IAC/B,aAAa,EAAE,gBAAgB;IAC/B,OAAO,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IAC5F,WAAW,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,cAAc,CAAC;IAC3E,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAsD,EAAE,EAAE,CAC9E,GAAG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG;IACzE,IAAI,EAAE,6LAA6L;IACnM,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,CAAC,UAAkB,EAAE,EAAE,CAAC,MAAM,UAAU,EAAE;IACnD,OAAO,EAAE,mBAAmB;IAC5B,QAAQ,EAAE,cAAc;IACxB,YAAY,EAAE,MAAM;IACpB,MAAM,EAAE,SAAS;IACjB,OAAO,EAAE,UAAU;IACnB,SAAS,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG;IACrD,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,WAAW,KAAK,EAAE;IAC7C,cAAc,EAAE,mBAAmB;IACnC,SAAS,EAAE,yBAAyB;IACpC,OAAO,EAAE,kBAAkB;IAC3B,WAAW,EAAE,SAAS;IACtB,KAAK,EAAE,OAAO;IACd,WAAW,EAAE,aAAa;CAC3B,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,SAA8C;IAC1E,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,uBAAuB,CAAC;IAC5D,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAChC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAC1C,CAAC;IAC7B,OAAO,EAAE,GAAG,uBAAuB,EAAE,GAAG,OAAO,EAAE,CAAC;AACpD,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,UAAU,CAAC,CAAiB,EAAE,CAAiB;IAC7D,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAE,CAAwC,CAAC,GAAG,CAAC,EAAG,CAAwC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC/G,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,82 @@
1
+ import type { Vec2 } from '@prnt/dagr-render/core';
2
+ import type { ExplorerEdge, ExplorerNode, ExplorerView } from './types.js';
3
+ /** Gap between neighbors across the flow, when the view does not say. */
4
+ export declare const DEFAULT_NODE_SEP = 40;
5
+ /** Gap between ranks along the flow, when the view does not say. */
6
+ export declare const DEFAULT_RANK_SEP = 120;
7
+ /** How far the content sits from the world origin, and from the far edges. */
8
+ export declare const WORLD_PADDING = 40;
9
+ /** Space between a group's outline and its members. `NodeGroup`'s default. */
10
+ export declare const GROUP_PADDING = 24;
11
+ /** Extra space above a group's members, for its label. */
12
+ export declare const GROUP_LABEL_BAND = 24;
13
+ /** Distance between neighboring edges that join the same pair of nodes. */
14
+ export declare const PARALLEL_EDGE_GAP = 16;
15
+ /** An axis-aligned rectangle in world space. `x`, `y` is its top-left corner. */
16
+ export interface ExplorerBox {
17
+ readonly x: number;
18
+ readonly y: number;
19
+ readonly width: number;
20
+ readonly height: number;
21
+ }
22
+ /**
23
+ * A view, placed. World space is y-down CSS pixels at zoom 1, and the content
24
+ * is padded 40 off the origin, so `width` and `height`
25
+ * describe a plane that starts at (0, 0).
26
+ */
27
+ export interface ExplorerLayout {
28
+ /** One box per node, in the order the view lists its nodes. */
29
+ readonly boxes: ReadonlyMap<string, ExplorerBox>;
30
+ /**
31
+ * One route per edge, in the order the view lists its edges, running from
32
+ * the edge's source to its target. A self loop has an empty route.
33
+ */
34
+ readonly routes: ReadonlyMap<string, readonly Vec2[]>;
35
+ /** One rectangle per group, in the order the view lists its groups. */
36
+ readonly groups: ReadonlyMap<string, ExplorerBox>;
37
+ readonly width: number;
38
+ readonly height: number;
39
+ }
40
+ export interface LayoutViewOptions {
41
+ /** Throw when a group's outline would enclose a node that is not a member. */
42
+ readonly strictGroups?: boolean | undefined;
43
+ }
44
+ /**
45
+ * Places a view: a box per node, a route per edge, a rectangle per group.
46
+ *
47
+ * `@prnt/dagr-layout` lays out top-down and has no direction option, so
48
+ * flowing right is a transpose done here, once: sizes go in with width and
49
+ * height swapped, and every coordinate comes out with x and y swapped.
50
+ *
51
+ * Three things the layout engine does not do are done here:
52
+ *
53
+ * - **Parallel edges.** The router draws edges between one pair of nodes on
54
+ * the same line only when they span one rank. Those are given a middle point
55
+ * and moved apart, symmetrically about that line, so each can be seen. Both
56
+ * ends stay on their nodes. A pair that spans more ranks is left alone: the
57
+ * engine already routes each through its own dummy nodes.
58
+ * - **Self loops.** The router gives an edge from a node to itself a
59
+ * zero-length line. Such an edge is left out of layout and has an empty
60
+ * route. It stays in the data, and drawing a loop is a later slice.
61
+ * - **Groups.** A group is an annotation over the finished layout: the hull
62
+ * of its members, padded, with a band above for its label. It moves no node.
63
+ *
64
+ * @throws {ExplorerDataError} for a malformed view, and under `strictGroups`
65
+ * when a group's outline would enclose a node that is not a member.
66
+ */
67
+ export declare function layoutView<N extends ExplorerNode, E extends ExplorerEdge>(view: ExplorerView<N, E>, options?: LayoutViewOptions): ExplorerLayout;
68
+ /**
69
+ * A string that is equal for two views exactly when they lay out the same.
70
+ *
71
+ * It holds what moves the layout and nothing else: direction, edge style and
72
+ * spacing, each node's id and resolved size, each edge's id and endpoints,
73
+ * each group's id and members. Labels, colors and descriptions are absent, so
74
+ * changing one never relayouts, and data re-created on every render with the
75
+ * same shape keeps its layout.
76
+ *
77
+ * JSON, for the reason `pairKey` gives: ids are the caller's.
78
+ *
79
+ * Internal: the root uses it to memoize, and the entry does not export it.
80
+ */
81
+ export declare function layoutKey<N extends ExplorerNode, E extends ExplorerEdge>(view: ExplorerView<N, E>): string;
82
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAInD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAQ,MAAM,YAAY,CAAC;AAGjF,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAKpC,8EAA8E;AAC9E,eAAO,MAAM,aAAa,KAAK,CAAC;AAChC,8EAA8E;AAC9E,eAAO,MAAM,aAAa,KAAK,CAAC;AAChC,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACjD;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;IACtD,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC7C;AAuBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,YAAY,EACvE,IAAI,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EACxB,OAAO,GAAE,iBAAsB,GAC9B,cAAc,CAiKhB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,YAAY,EACtE,IAAI,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,GACvB,MAAM,CAaR"}
package/dist/layout.js ADDED
@@ -0,0 +1,244 @@
1
+ import { Graph } from '@prnt/dagr-graph';
2
+ import { layout } from '@prnt/dagr-layout';
3
+ import { shapeEdgePath } from '@prnt/dagr-render/core';
4
+ import { ExplorerDataError } from './errors.js';
5
+ import { resolveNodeSize } from './size.js';
6
+ import { validateView } from './validate.js';
7
+ /** Gap between neighbors across the flow, when the view does not say. */
8
+ export const DEFAULT_NODE_SEP = 40;
9
+ /** Gap between ranks along the flow, when the view does not say. */
10
+ export const DEFAULT_RANK_SEP = 120;
11
+ // The four constants below are exported for this package's tests and are NOT
12
+ // re-exported from the entry. They are fixed values today. Publishing them
13
+ // would make a change to any one a silent behavior break for whoever read it.
14
+ /** How far the content sits from the world origin, and from the far edges. */
15
+ export const WORLD_PADDING = 40;
16
+ /** Space between a group's outline and its members. `NodeGroup`'s default. */
17
+ export const GROUP_PADDING = 24;
18
+ /** Extra space above a group's members, for its label. */
19
+ export const GROUP_LABEL_BAND = 24;
20
+ /** Distance between neighboring edges that join the same pair of nodes. */
21
+ export const PARALLEL_EDGE_GAP = 16;
22
+ /**
23
+ * The key two edges share when they join the same two nodes, whichever way
24
+ * each one points. JSON rather than a joined string, because ids are the
25
+ * caller's and may contain any separator: joined with a comma, 'a,b' with 'c'
26
+ * and 'a' with 'b,c' are one pair.
27
+ */
28
+ const pairKey = (a, b) => JSON.stringify(a < b ? [a, b] : [b, a]);
29
+ /**
30
+ * Gives a two-point route a middle point, moved sideways across the flow, so
31
+ * two edges the router drew on one line can be told apart. Both ends stay
32
+ * where they are.
33
+ */
34
+ function bow(points, offset, right) {
35
+ const first = points[0];
36
+ const last = points[1];
37
+ if (first === undefined || last === undefined)
38
+ return [...points];
39
+ const mid = { x: (first.x + last.x) / 2, y: (first.y + last.y) / 2 };
40
+ return [first, right ? { x: mid.x, y: mid.y + offset } : { x: mid.x + offset, y: mid.y }, last];
41
+ }
42
+ /**
43
+ * Places a view: a box per node, a route per edge, a rectangle per group.
44
+ *
45
+ * `@prnt/dagr-layout` lays out top-down and has no direction option, so
46
+ * flowing right is a transpose done here, once: sizes go in with width and
47
+ * height swapped, and every coordinate comes out with x and y swapped.
48
+ *
49
+ * Three things the layout engine does not do are done here:
50
+ *
51
+ * - **Parallel edges.** The router draws edges between one pair of nodes on
52
+ * the same line only when they span one rank. Those are given a middle point
53
+ * and moved apart, symmetrically about that line, so each can be seen. Both
54
+ * ends stay on their nodes. A pair that spans more ranks is left alone: the
55
+ * engine already routes each through its own dummy nodes.
56
+ * - **Self loops.** The router gives an edge from a node to itself a
57
+ * zero-length line. Such an edge is left out of layout and has an empty
58
+ * route. It stays in the data, and drawing a loop is a later slice.
59
+ * - **Groups.** A group is an annotation over the finished layout: the hull
60
+ * of its members, padded, with a band above for its label. It moves no node.
61
+ *
62
+ * @throws {ExplorerDataError} for a malformed view, and under `strictGroups`
63
+ * when a group's outline would enclose a node that is not a member.
64
+ */
65
+ export function layoutView(view, options = {}) {
66
+ validateView(view);
67
+ const right = (view.layout?.direction ?? 'right') === 'right';
68
+ const sizes = new Map();
69
+ const graph = new Graph();
70
+ for (const node of view.nodes) {
71
+ sizes.set(node.id, resolveNodeSize(view.layout, node));
72
+ graph.addNode({ id: node.id });
73
+ }
74
+ const siblings = new Map();
75
+ for (const edge of view.edges) {
76
+ if (edge.source === edge.target)
77
+ continue;
78
+ graph.addEdge({ id: edge.id, source: edge.source, target: edge.target });
79
+ const key = pairKey(edge.source, edge.target);
80
+ const list = siblings.get(key);
81
+ if (list === undefined)
82
+ siblings.set(key, [edge.id]);
83
+ else
84
+ list.push(edge.id);
85
+ }
86
+ // Each edge's place among the edges that join its pair, looked up once here
87
+ // so the route loop below does not search a list per edge.
88
+ const siblingOf = new Map();
89
+ for (const list of siblings.values()) {
90
+ list.forEach((id, index) => siblingOf.set(id, { index, count: list.length }));
91
+ }
92
+ const sizeOf = (id) => {
93
+ const size = sizes.get(id);
94
+ if (size === undefined)
95
+ throw new Error(`layoutView lost the size of node "${id}"`);
96
+ return size;
97
+ };
98
+ const result = layout({
99
+ graph,
100
+ config: {
101
+ nodeSep: view.layout?.nodeSep ?? DEFAULT_NODE_SEP,
102
+ rankSep: view.layout?.rankSep ?? DEFAULT_RANK_SEP,
103
+ nodeSize: (node) => {
104
+ const size = sizeOf(node.id);
105
+ return right ? { width: size.height, height: size.width } : size;
106
+ },
107
+ },
108
+ });
109
+ const toWorld = (p) => (right ? { x: p.y, y: p.x } : { x: p.x, y: p.y });
110
+ const boxes = new Map();
111
+ for (const node of view.nodes) {
112
+ const placed = result.nodes.get(node.id);
113
+ if (placed === undefined)
114
+ throw new Error(`layout returned no position for node "${node.id}"`);
115
+ const size = sizeOf(node.id);
116
+ const center = toWorld(placed);
117
+ boxes.set(node.id, {
118
+ x: center.x - size.width / 2,
119
+ y: center.y - size.height / 2,
120
+ width: size.width,
121
+ height: size.height,
122
+ });
123
+ }
124
+ const style = view.layout?.edgeStyle ?? 'smooth';
125
+ const routes = new Map();
126
+ for (const edge of view.edges) {
127
+ if (edge.source === edge.target) {
128
+ routes.set(edge.id, []);
129
+ continue;
130
+ }
131
+ const routed = result.edges.get(edge.id);
132
+ if (routed === undefined)
133
+ throw new Error(`layout returned no route for edge "${edge.id}"`);
134
+ let points = routed.points.map(toWorld);
135
+ // Only a route of two points needs separating. That is a pair spanning one
136
+ // rank, which the router draws on one line. A longer route already has its
137
+ // own dummy nodes, a nodeSep from its siblings', and moving those would
138
+ // undo an ordering the engine chose. See `route.ts` in `@prnt/dagr-layout`.
139
+ const place = siblingOf.get(edge.id);
140
+ if (place !== undefined && points.length === 2) {
141
+ const offset = (place.index - (place.count - 1) / 2) * PARALLEL_EDGE_GAP;
142
+ if (offset !== 0)
143
+ points = bow(points, offset, right);
144
+ }
145
+ routes.set(edge.id, shapeEdgePath(points, { style, direction: right ? 'horizontal' : 'vertical' }));
146
+ }
147
+ const groups = new Map();
148
+ for (const group of view.groups ?? []) {
149
+ const members = new Set(group.nodeIds);
150
+ let minX = Infinity;
151
+ let minY = Infinity;
152
+ let maxX = -Infinity;
153
+ let maxY = -Infinity;
154
+ for (const id of members) {
155
+ const member = boxes.get(id);
156
+ if (member === undefined)
157
+ continue;
158
+ minX = Math.min(minX, member.x);
159
+ minY = Math.min(minY, member.y);
160
+ maxX = Math.max(maxX, member.x + member.width);
161
+ maxY = Math.max(maxY, member.y + member.height);
162
+ }
163
+ const rect = {
164
+ x: minX - GROUP_PADDING,
165
+ y: minY - GROUP_PADDING - GROUP_LABEL_BAND,
166
+ width: maxX - minX + GROUP_PADDING * 2,
167
+ height: maxY - minY + GROUP_PADDING * 2 + GROUP_LABEL_BAND,
168
+ };
169
+ if (options.strictGroups === true) {
170
+ for (const [id, other] of boxes) {
171
+ if (members.has(id))
172
+ continue;
173
+ const overlaps = other.x < rect.x + rect.width &&
174
+ other.x + other.width > rect.x &&
175
+ other.y < rect.y + rect.height &&
176
+ other.y + other.height > rect.y;
177
+ if (overlaps) {
178
+ throw new ExplorerDataError('GROUP_ENCLOSES_NON_MEMBER', `Group "${group.id}" in view "${view.id}" would enclose non-member node "${id}"`, group.id, view.id);
179
+ }
180
+ }
181
+ }
182
+ groups.set(group.id, rect);
183
+ }
184
+ // Everything above is in the layout engine's frame, which is centered on
185
+ // nothing in particular. Move it so the hull of every box, group and route
186
+ // point starts WORLD_PADDING in from the origin.
187
+ let minX = Infinity;
188
+ let minY = Infinity;
189
+ let maxX = -Infinity;
190
+ let maxY = -Infinity;
191
+ const grow = (x0, y0, x1, y1) => {
192
+ minX = Math.min(minX, x0);
193
+ minY = Math.min(minY, y0);
194
+ maxX = Math.max(maxX, x1);
195
+ maxY = Math.max(maxY, y1);
196
+ };
197
+ for (const b of boxes.values())
198
+ grow(b.x, b.y, b.x + b.width, b.y + b.height);
199
+ for (const g of groups.values())
200
+ grow(g.x, g.y, g.x + g.width, g.y + g.height);
201
+ for (const points of routes.values())
202
+ for (const p of points)
203
+ grow(p.x, p.y, p.x, p.y);
204
+ if (minX === Infinity) {
205
+ return { boxes: new Map(), routes: new Map(), groups: new Map(), width: 0, height: 0 };
206
+ }
207
+ const dx = WORLD_PADDING - minX;
208
+ const dy = WORLD_PADDING - minY;
209
+ return {
210
+ boxes: new Map([...boxes].map(([id, b]) => [id, { ...b, x: b.x + dx, y: b.y + dy }])),
211
+ routes: new Map([...routes].map(([id, points]) => [id, points.map((p) => ({ x: p.x + dx, y: p.y + dy }))])),
212
+ groups: new Map([...groups].map(([id, g]) => [id, { ...g, x: g.x + dx, y: g.y + dy }])),
213
+ width: maxX - minX + WORLD_PADDING * 2,
214
+ height: maxY - minY + WORLD_PADDING * 2,
215
+ };
216
+ }
217
+ /**
218
+ * A string that is equal for two views exactly when they lay out the same.
219
+ *
220
+ * It holds what moves the layout and nothing else: direction, edge style and
221
+ * spacing, each node's id and resolved size, each edge's id and endpoints,
222
+ * each group's id and members. Labels, colors and descriptions are absent, so
223
+ * changing one never relayouts, and data re-created on every render with the
224
+ * same shape keeps its layout.
225
+ *
226
+ * JSON, for the reason `pairKey` gives: ids are the caller's.
227
+ *
228
+ * Internal: the root uses it to memoize, and the entry does not export it.
229
+ */
230
+ export function layoutKey(view) {
231
+ return JSON.stringify([
232
+ view.layout?.direction ?? 'right',
233
+ view.layout?.edgeStyle ?? 'smooth',
234
+ view.layout?.nodeSep ?? DEFAULT_NODE_SEP,
235
+ view.layout?.rankSep ?? DEFAULT_RANK_SEP,
236
+ view.nodes.map((node) => {
237
+ const size = resolveNodeSize(view.layout, node);
238
+ return [node.id, size.width, size.height];
239
+ }),
240
+ view.edges.map((edge) => [edge.id, edge.source, edge.target]),
241
+ (view.groups ?? []).map((group) => [group.id, group.nodeIds]),
242
+ ]);
243
+ }
244
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Which node an arrow key moves focus to, as one pure function.
3
+ *
4
+ * Candidates are the nodes whose center lies strictly in the arrow's
5
+ * half-plane from the focused node's center. The score is the distance along
6
+ * the arrow's axis plus twice the distance across it, lowest wins, and a tie
7
+ * goes to the lower id. Weighing the cross axis keeps a press on a row or a
8
+ * column rather than letting it drift onto a nearer diagonal.
9
+ *
10
+ * It scans every node in the layout, mounted or not: an unmounted node is a
11
+ * mark with no element, and focus has to be able to reach it. Linear, like
12
+ * the visible set's scan, and for the same reason.
13
+ *
14
+ * Internal to the package.
15
+ */
16
+ import type { LayoutIndex } from './visible-set.js';
17
+ /** In screen terms, which are world terms: the world is y-down. */
18
+ export type ExplorerDirection = 'up' | 'down' | 'left' | 'right';
19
+ export declare function nearestInDirection(index: LayoutIndex, fromId: string, direction: ExplorerDirection): string | null;
20
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,mEAAmE;AACnE,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjE,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,iBAAiB,GAC3B,MAAM,GAAG,IAAI,CAyBf"}