@graview/react 0.1.10 → 0.1.14

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 (91) hide show
  1. package/dist/activity.d.ts +0 -30
  2. package/dist/activity.d.ts.map +1 -1
  3. package/dist/activity.js +0 -54
  4. package/dist/activity.js.map +1 -1
  5. package/dist/attention.d.ts +33 -0
  6. package/dist/attention.d.ts.map +1 -0
  7. package/dist/attention.js +60 -0
  8. package/dist/attention.js.map +1 -0
  9. package/dist/context.d.ts.map +1 -1
  10. package/dist/context.js +13 -4
  11. package/dist/context.js.map +1 -1
  12. package/dist/drawing.d.ts +25 -0
  13. package/dist/drawing.d.ts.map +1 -0
  14. package/dist/drawing.js +22 -0
  15. package/dist/drawing.js.map +1 -0
  16. package/dist/drawn.d.ts +21 -0
  17. package/dist/drawn.d.ts.map +1 -1
  18. package/dist/drawn.js +40 -1
  19. package/dist/drawn.js.map +1 -1
  20. package/dist/edge-signs.js +2 -1
  21. package/dist/edge-signs.js.map +1 -1
  22. package/dist/editable-fields.d.ts +16 -0
  23. package/dist/editable-fields.d.ts.map +1 -0
  24. package/dist/editable-fields.js +23 -0
  25. package/dist/editable-fields.js.map +1 -0
  26. package/dist/emphasis.d.ts +25 -0
  27. package/dist/emphasis.d.ts.map +1 -0
  28. package/dist/emphasis.js +84 -0
  29. package/dist/emphasis.js.map +1 -0
  30. package/dist/error-report.d.ts +15 -0
  31. package/dist/error-report.d.ts.map +1 -0
  32. package/dist/error-report.js +4 -0
  33. package/dist/error-report.js.map +1 -0
  34. package/dist/hooks.d.ts +7 -68
  35. package/dist/hooks.d.ts.map +1 -1
  36. package/dist/hooks.js +5 -281
  37. package/dist/hooks.js.map +1 -1
  38. package/dist/index.d.ts +16 -8
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +12 -6
  41. package/dist/index.js.map +1 -1
  42. package/dist/occupants.js +1 -1
  43. package/dist/occupants.js.map +1 -1
  44. package/dist/picked.d.ts +3 -0
  45. package/dist/picked.d.ts.map +1 -0
  46. package/dist/picked.js +9 -0
  47. package/dist/picked.js.map +1 -0
  48. package/dist/picking.d.ts +7 -2
  49. package/dist/picking.d.ts.map +1 -1
  50. package/dist/picking.js +17 -3
  51. package/dist/picking.js.map +1 -1
  52. package/dist/placement.d.ts +59 -0
  53. package/dist/placement.d.ts.map +1 -0
  54. package/dist/placement.js +85 -0
  55. package/dist/placement.js.map +1 -0
  56. package/dist/plots.d.ts +4 -3
  57. package/dist/plots.d.ts.map +1 -1
  58. package/dist/plots.js +4 -3
  59. package/dist/plots.js.map +1 -1
  60. package/dist/presence.d.ts +0 -57
  61. package/dist/presence.d.ts.map +1 -1
  62. package/dist/presence.js +2 -85
  63. package/dist/presence.js.map +1 -1
  64. package/dist/provider.d.ts +12 -9
  65. package/dist/provider.d.ts.map +1 -1
  66. package/dist/provider.js +10 -6
  67. package/dist/provider.js.map +1 -1
  68. package/dist/resolved-view.d.ts.map +1 -1
  69. package/dist/resolved-view.js +2 -1
  70. package/dist/resolved-view.js.map +1 -1
  71. package/dist/scene-camera.js +1 -1
  72. package/dist/scene-camera.js.map +1 -1
  73. package/dist/scene-lines.d.ts.map +1 -1
  74. package/dist/scene-lines.js +39 -0
  75. package/dist/scene-lines.js.map +1 -1
  76. package/dist/scene-root.d.ts.map +1 -1
  77. package/dist/scene-root.js +130 -117
  78. package/dist/scene-root.js.map +1 -1
  79. package/dist/url-sync.d.ts +32 -0
  80. package/dist/url-sync.d.ts.map +1 -0
  81. package/dist/url-sync.js +205 -0
  82. package/dist/url-sync.js.map +1 -0
  83. package/dist/view-boundary.d.ts +2 -12
  84. package/dist/view-boundary.d.ts.map +1 -1
  85. package/dist/view-boundary.js +3 -3
  86. package/dist/view-boundary.js.map +1 -1
  87. package/dist/view-host.d.ts +2 -2
  88. package/dist/view-host.d.ts.map +1 -1
  89. package/dist/view-host.js +11 -7
  90. package/dist/view-host.js.map +1 -1
  91. package/package.json +9 -5
@@ -0,0 +1,16 @@
1
+ import type { AnySchema } from "@graview/core";
2
+ import { type EditableField } from "@graview/tools/edit";
3
+ /**
4
+ * The fields of a node that can be changed where they are shown, and the way
5
+ * to change one.
6
+ *
7
+ * `commit` runs the mutation the framework found, with the author set to a
8
+ * person — so an in-place edit lands in the op log, can be undone, and is
9
+ * judged by the invariants exactly like an edit made from the actions strip.
10
+ * There is no second write path.
11
+ */
12
+ export declare function useEditableFields<S extends AnySchema>(id: string | null): {
13
+ readonly fields: readonly EditableField[];
14
+ commit: (field: EditableField, value: unknown, rest?: Record<string, unknown>) => void;
15
+ };
16
+ //# sourceMappingURL=editable-fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editable-fields.d.ts","sourceRoot":"","sources":["../src/editable-fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzE;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,SAAS,EACnD,EAAE,EAAE,MAAM,GAAG,IAAI,GAChB;IACD,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACxF,CAkBA"}
@@ -0,0 +1,23 @@
1
+ import { editableFields } from "@graview/tools/edit";
2
+ import { useCallback, useMemo } from "react";
3
+ import { useGraph, useGraview } from "./context.js";
4
+ /**
5
+ * The fields of a node that can be changed where they are shown, and the way
6
+ * to change one.
7
+ *
8
+ * `commit` runs the mutation the framework found, with the author set to a
9
+ * person — so an in-place edit lands in the op log, can be undone, and is
10
+ * judged by the invariants exactly like an edit made from the actions strip.
11
+ * There is no second write path.
12
+ */
13
+ export function useEditableFields(id) {
14
+ const { store, principal } = useGraview();
15
+ const nodes = useGraph();
16
+ const fields = useMemo(() => (id === null ? [] : editableFields(store, id)), [store, id, nodes]);
17
+ const commit = useCallback((field, value, rest = {}) => {
18
+ const call = field.call(value);
19
+ store.apply({ name: call.name, args: { ...call.args, ...rest } }, { author: principal });
20
+ }, [store, principal]);
21
+ return { fields, commit };
22
+ }
23
+ //# sourceMappingURL=editable-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editable-fields.js","sourceRoot":"","sources":["../src/editable-fields.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAsB,MAAM,qBAAqB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEpD;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,EAAiB;IAKjB,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,UAAU,EAAK,CAAC;IAC7C,MAAM,KAAK,GAAG,QAAQ,EAAK,CAAC;IAC5B,MAAM,MAAM,GAAG,OAAO,CACpB,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EACpD,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,CACnB,CAAC;IACF,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,KAAoB,EAAE,KAAc,EAAE,OAAgC,EAAE,EAAE,EAAE;QAC3E,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,CAAC,KAAK,CACT,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE,EACpD,EAAE,MAAM,EAAE,SAAS,EAAE,CACtB,CAAC;IACJ,CAAC,EACD,CAAC,KAAK,EAAE,SAAS,CAAC,CACnB,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,25 @@
1
+ /** Ids implicated in some current violation, for marking them in place. */
2
+ export declare function useFlagged(): readonly string[];
3
+ /**
4
+ * Everything the selection reaches: the selected nodes, plus everything one
5
+ * edge away from them.
6
+ *
7
+ * Selecting a person and having the week sit there unchanged is the same
8
+ * failure as clicking an event and getting an empty plane — the graph knows
9
+ * which five events that person is in, and the calendar is right there
10
+ * drawing all eighteen of them. A view that receives this can say so in its
11
+ * own idiom: a calendar lights up the spans, a roster rings the chips.
12
+ *
13
+ * Empty when nothing is selected, which every view must read as "no
14
+ * emphasis" rather than "nothing is related".
15
+ */
16
+ export declare function useImplicated(): readonly string[];
17
+ /**
18
+ * What the SELECTION alone reaches — `useImplicated` without the search.
19
+ * For the one reading that must not confuse the two: a district's "tied"
20
+ * count is about what you picked, not what you typed.
21
+ */
22
+ export declare function useReached(): readonly string[];
23
+ /** Stands in the implicated set when a search found nothing, so every view dims. */
24
+ export declare const NOTHING_FOUND = "search:nothing";
25
+ //# sourceMappingURL=emphasis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emphasis.d.ts","sourceRoot":"","sources":["../src/emphasis.ts"],"names":[],"mappings":"AAUA,2EAA2E;AAC3E,wBAAgB,UAAU,IAAI,SAAS,MAAM,EAAE,CAM9C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,IAAI,SAAS,MAAM,EAAE,CAgBjD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,SAAS,MAAM,EAAE,CA0B9C;AAED,oFAAoF;AACpF,eAAO,MAAM,aAAa,mBAAmB,CAAC"}
@@ -0,0 +1,84 @@
1
+ import { useMemo } from "react";
2
+ import { useFound, useGraph, useGraview } from "./context.js";
3
+ import { useViolations } from "./hooks.js";
4
+ /*
5
+ * WHAT A VIEW LIGHTS AND DIMS: the flagged, the reached and the implicated.
6
+ * A file of its own, apart from the hooks the frame reads before anything is
7
+ * drawn, so a page that has drawn no view yet carries none of it.
8
+ */
9
+ /** Ids implicated in some current violation, for marking them in place. */
10
+ export function useFlagged() {
11
+ const violations = useViolations();
12
+ return useMemo(() => [...new Set(violations.flatMap((violation) => violation.nodeIds))], [violations]);
13
+ }
14
+ /**
15
+ * Everything the selection reaches: the selected nodes, plus everything one
16
+ * edge away from them.
17
+ *
18
+ * Selecting a person and having the week sit there unchanged is the same
19
+ * failure as clicking an event and getting an empty plane — the graph knows
20
+ * which five events that person is in, and the calendar is right there
21
+ * drawing all eighteen of them. A view that receives this can say so in its
22
+ * own idiom: a calendar lights up the spans, a roster rings the chips.
23
+ *
24
+ * Empty when nothing is selected, which every view must read as "no
25
+ * emphasis" rather than "nothing is related".
26
+ */
27
+ export function useImplicated() {
28
+ const reachedBySelection = useReached();
29
+ const found = useFound();
30
+ /*
31
+ * AND WHAT THE WORDS FIND. A search lights its hits in whatever picture
32
+ * is open, through the same set every view already dims by — so a lens
33
+ * learns nothing new to take part. Words that find nothing dim it all:
34
+ * the one id nothing is called keeps the set non-empty.
35
+ */
36
+ return useMemo(() => {
37
+ if (!found)
38
+ return reachedBySelection;
39
+ // Every match, not the strip's capped index: a real hit is never drawn dimmed.
40
+ const hits = found.matched;
41
+ if (hits.length === 0 && reachedBySelection.length === 0)
42
+ return [NOTHING_FOUND];
43
+ return [...new Set([...reachedBySelection, ...hits])];
44
+ }, [found, reachedBySelection]);
45
+ }
46
+ /**
47
+ * What the SELECTION alone reaches — `useImplicated` without the search.
48
+ * For the one reading that must not confuse the two: a district's "tied"
49
+ * count is about what you picked, not what you typed.
50
+ */
51
+ export function useReached() {
52
+ const { store, selection } = useGraview();
53
+ const nodes = useGraph();
54
+ return useMemo(() => {
55
+ if (selection.length === 0)
56
+ return [];
57
+ const chosen = new Set(selection);
58
+ const reached = new Set(chosen);
59
+ for (const edge of store.graph.allEdges()) {
60
+ if (chosen.has(edge.from))
61
+ reached.add(edge.to);
62
+ if (chosen.has(edge.to))
63
+ reached.add(edge.from);
64
+ }
65
+ /*
66
+ * What a selected thing JUDGES, as well as what it touches.
67
+ *
68
+ * A rule has no edges, so selecting one reached nothing and the screen
69
+ * did not change — it read as broken. What a rule is about is derivable
70
+ * from its violations, which name the nodes they implicate, so selecting
71
+ * one now lights those wherever they are drawn.
72
+ */
73
+ for (const violation of store.violations()) {
74
+ if (violation.subjectId !== undefined && chosen.has(violation.subjectId)) {
75
+ for (const id of violation.nodeIds)
76
+ reached.add(id);
77
+ }
78
+ }
79
+ return [...reached];
80
+ }, [store, selection, nodes]);
81
+ }
82
+ /** Stands in the implicated set when a search found nothing, so every view dims. */
83
+ export const NOTHING_FOUND = "search:nothing";
84
+ //# sourceMappingURL=emphasis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emphasis.js","sourceRoot":"","sources":["../src/emphasis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;GAIG;AAEH,2EAA2E;AAC3E,MAAM,UAAU,UAAU;IACxB,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EACxE,CAAC,UAAU,CAAC,CACb,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,kBAAkB,GAAG,UAAU,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB;;;;;OAKG;IACH,OAAO,OAAO,CAAC,GAAG,EAAE;QAClB,IAAI,CAAC,KAAK;YAAE,OAAO,kBAAkB,CAAC;QACtC,+EAA+E;QAC/E,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC;QAC3B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,kBAAkB,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,aAAa,CAAC,CAAC;QACjF,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,kBAAkB,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAClC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,UAAU,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,OAAO,OAAO,CAAC,GAAG,EAAE;QAClB,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACtC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;QAClC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAChC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC1C,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAChD,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,CAAC;QACD;;;;;;;WAOG;QACH,KAAK,MAAM,SAAS,IAAI,KAAK,CAAC,UAAU,EAAE,EAAE,CAAC;YAC3C,IAAI,SAAS,CAAC,SAAS,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;gBACzE,KAAK,MAAM,EAAE,IAAI,SAAS,CAAC,OAAO;oBAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;QACD,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;IACtB,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;AAChC,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,MAAM,aAAa,GAAG,gBAAgB,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * WHERE A CAUGHT ERROR IS TOLD, beside the console (FR-24). A host that
3
+ * embeds an app observes its failures without reading what was on screen:
4
+ * a boundary tells this the error and the framework module it was caught
5
+ * in, and the host decides what of the error to keep.
6
+ *
7
+ * A file of its own: the frame provides the report before anything draws,
8
+ * and the boundary that tells it is drawn only with a view.
9
+ */
10
+ export type ErrorReport = (error: unknown, where: {
11
+ readonly module: string;
12
+ }) => void;
13
+ /** The report every boundary under it tells. Null outside any: the console alone. */
14
+ export declare const ErrorReportContext: import("react").Context<ErrorReport | null>;
15
+ //# sourceMappingURL=error-report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-report.d.ts","sourceRoot":"","sources":["../src/error-report.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAC;AAEvF,qFAAqF;AACrF,eAAO,MAAM,kBAAkB,6CAA0C,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { createContext } from "react";
2
+ /** The report every boundary under it tells. Null outside any: the console alone. */
3
+ export const ErrorReportContext = createContext(null);
4
+ //# sourceMappingURL=error-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-report.js","sourceRoot":"","sources":["../src/error-report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAatC,qFAAqF;AACrF,MAAM,CAAC,MAAM,kBAAkB,GAAG,aAAa,CAAqB,IAAI,CAAC,CAAC"}
package/dist/hooks.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { AnySchema } from "@graview/core";
2
2
  import { type ViewState } from "@graview/layout/view";
3
- import { type EditableField } from "@graview/tools/frame";
4
3
  /** The current selection, and the ways an interface changes it. */
5
4
  export declare function useSelection(): {
6
5
  selection: readonly string[];
@@ -19,30 +18,6 @@ export declare function useSelection(): {
19
18
  * week, and it should never require hunting for it.
20
19
  */
21
20
  export declare function useViolations<S extends AnySchema>(): import("@graview/core").Violation[];
22
- /** Ids implicated in some current violation, for marking them in place. */
23
- export declare function useFlagged(): readonly string[];
24
- /**
25
- * Everything the selection reaches: the selected nodes, plus everything one
26
- * edge away from them.
27
- *
28
- * Selecting a person and having the week sit there unchanged is the same
29
- * failure as clicking an event and getting an empty plane — the graph knows
30
- * which five events that person is in, and the calendar is right there
31
- * drawing all eighteen of them. A view that receives this can say so in its
32
- * own idiom: a calendar lights up the spans, a roster rings the chips.
33
- *
34
- * Empty when nothing is selected, which every view must read as "no
35
- * emphasis" rather than "nothing is related".
36
- */
37
- export declare function useImplicated(): readonly string[];
38
- /**
39
- * What the SELECTION alone reaches — `useImplicated` without the search.
40
- * For the one reading that must not confuse the two: a district's "tied"
41
- * count is about what you picked, not what you typed.
42
- */
43
- export declare function useReached(): readonly string[];
44
- /** Stands in the implicated set when a search found nothing, so every view dims. */
45
- export declare const NOTHING_FOUND = "search:nothing";
46
21
  /** Navigation: every stop is a view, and every view is a URL. */
47
22
  export declare function useNavigation(): {
48
23
  view: ViewState;
@@ -52,20 +27,6 @@ export declare function useNavigation(): {
52
27
  toggle: (aggregateId: string) => void;
53
28
  go: (next: ViewState) => void;
54
29
  };
55
- /**
56
- * Keeps the view in the URL fragment and honours the back button.
57
- *
58
- * The back button returning to the exact prior view is not a nicety here: it
59
- * is the thing that makes a spatial interface navigable at all, because it
60
- * means no arrangement is ever unreachable once you have left it.
61
- */
62
- /**
63
- * `useUrlSync` as a component, for shells that make syncing conditional —
64
- * a hook inside `if (syncUrl)` is a Rules-of-Hooks trap the moment the
65
- * flag ever changes; a conditionally RENDERED component is not.
66
- */
67
- export declare function UrlSync(): null;
68
- export declare function useUrlSync(): void;
69
30
  /**
70
31
  * Where you are in your own history.
71
32
  *
@@ -79,22 +40,13 @@ export declare function useUrlSync(): void;
79
40
  * that re-created it per component would count each navigation once per
80
41
  * mounted consumer.
81
42
  */
82
- /**
83
- * Whether one view differs from another only in what the user MOVED.
84
- *
85
- * Where you are is the focus, the relation, what is expanded and whether you
86
- * have risen above the stack. Where you dragged things to is not a different
87
- * place; it is the same place, rearranged.
88
- */
89
- /**
90
- * WHETHER A CHANGE IS A STOP OR JUST A NUDGE OF THE ONE YOU ARE ON.
91
- *
92
- * Exported so it can be read and held to: what belongs in here is the
93
- * difference between the back button meaning something and meaning "one
94
- * pixel ago", and the two things that went wrong went wrong silently — the
95
- * address changed, no entry was pushed, and the arrows stayed grey.
96
- */
97
- export declare function adjustment(before: ViewState | null, after: ViewState): boolean;
43
+ /** Where you are in your own history: the pushes `useUrlSync` makes and the pops the browser reports. */
44
+ export declare const trail: {
45
+ at: number;
46
+ depth: number;
47
+ listeners: Set<() => void>;
48
+ tell(): void;
49
+ };
98
50
  /**
99
51
  * Whether there is anywhere to go, and the way to go there.
100
52
  *
@@ -123,17 +75,4 @@ export declare function useJackIn(): {
123
75
  exit: () => void;
124
76
  isJackedIn: boolean;
125
77
  };
126
- /**
127
- * The fields of a node that can be changed where they are shown, and the way
128
- * to change one.
129
- *
130
- * `commit` runs the mutation the framework found, with the author set to a
131
- * person — so an in-place edit lands in the op log, can be undone, and is
132
- * judged by the invariants exactly like an edit made from the actions strip.
133
- * There is no second write path.
134
- */
135
- export declare function useEditableFields<S extends AnySchema>(id: string | null): {
136
- readonly fields: readonly EditableField[];
137
- commit: (field: EditableField, value: unknown, rest?: Record<string, unknown>) => void;
138
- };
139
78
  //# sourceMappingURL=hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAQL,KAAK,SAAS,EAIf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1E,mEAAmE;AACnE,wBAAgB,YAAY;;eAKX,SAAS,MAAM,EAAE;;iBAEf,MAAM;qBAIF,MAAM;EAI5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,SAAS,yCAKhD;AA4BD,2EAA2E;AAC3E,wBAAgB,UAAU,IAAI,SAAS,MAAM,EAAE,CAM9C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,IAAI,SAAS,MAAM,EAAE,CAgBjD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,SAAS,MAAM,EAAE,CA0B9C;AAED,oFAAoF;AACpF,eAAO,MAAM,aAAa,mBAAmB,CAAC;AAE9C,iEAAiE;AACjE,wBAAgB,aAAa;;;gBAMX,MAAM,GAAG,IAAI;qBACR,MAAM,GAAG,IAAI;0BACR,MAAM;eAEjB,SAAS;EAIzB;AAED;;;;;;GAMG;AACH;;;;GAIG;AACH,wBAAgB,OAAO,IAAI,IAAI,CAG9B;AAED,wBAAgB,UAAU,IAAI,IAAI,CAwGjC;AAED;;;;;;;;;;;;GAYG;AACH;;;;;;GAMG;AACH;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CA8B9E;AA+CD;;;;;;;GAOG;AACH,wBAAgB,YAAY,IAAI;IAC9B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAwBA;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS;;gBASQ,MAAM;;;EAOtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,SAAS,EACnD,EAAE,EAAE,MAAM,GAAG,IAAI,GAChB;IACD,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACxF,CAkBA"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAwE,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAI5H,mEAAmE;AACnE,wBAAgB,YAAY;;eAKX,SAAS,MAAM,EAAE;;iBAEf,MAAM;qBAIF,MAAM;EAI5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,SAAS,yCAKhD;AA4BD,iEAAiE;AACjE,wBAAgB,aAAa;;;gBAMX,MAAM,GAAG,IAAI;qBACR,MAAM,GAAG,IAAI;0BACR,MAAM;eAEjB,SAAS;EAIzB;AAGD;;;;;;;;;;;;GAYG;AAEH,yGAAyG;AACzG,eAAO,MAAM,KAAK;;;yBAGS,IAAI;;CAI9B,CAAC;AAcF;;;;;;;GAOG;AACH,wBAAgB,YAAY,IAAI;IAC9B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAwBA;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS;;gBASQ,MAAM;;;EAOtC"}
package/dist/hooks.js CHANGED
@@ -1,7 +1,6 @@
1
- import { aggregateId, fromUrl, toggleExpanded, toUrl, withFocus, withRelation, withZoom, sameView, EMPTY_VIEW, withJackIn, } from "@graview/layout/view";
2
- import { editableFields } from "@graview/tools/frame";
3
- import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
4
- import { useFound, useGraph, useGraview } from "./context.js";
1
+ import { toggleExpanded, toUrl, withFocus, withRelation, withZoom, withJackIn } from "@graview/layout/view";
2
+ import { useCallback, useMemo, useSyncExternalStore } from "react";
3
+ import { useGraph, useGraview } from "./context.js";
5
4
  /** The current selection, and the ways an interface changes it. */
6
5
  export function useSelection() {
7
6
  const { selection, setSelection } = useGraview();
@@ -43,81 +42,6 @@ function violationsOf(store) {
43
42
  entry.violations ??= store.violations();
44
43
  return entry.violations;
45
44
  }
46
- /** Ids implicated in some current violation, for marking them in place. */
47
- export function useFlagged() {
48
- const violations = useViolations();
49
- return useMemo(() => [...new Set(violations.flatMap((violation) => violation.nodeIds))], [violations]);
50
- }
51
- /**
52
- * Everything the selection reaches: the selected nodes, plus everything one
53
- * edge away from them.
54
- *
55
- * Selecting a person and having the week sit there unchanged is the same
56
- * failure as clicking an event and getting an empty plane — the graph knows
57
- * which five events that person is in, and the calendar is right there
58
- * drawing all eighteen of them. A view that receives this can say so in its
59
- * own idiom: a calendar lights up the spans, a roster rings the chips.
60
- *
61
- * Empty when nothing is selected, which every view must read as "no
62
- * emphasis" rather than "nothing is related".
63
- */
64
- export function useImplicated() {
65
- const reachedBySelection = useReached();
66
- const found = useFound();
67
- /*
68
- * AND WHAT THE WORDS FIND. A search lights its hits in whatever picture
69
- * is open, through the same set every view already dims by — so a lens
70
- * learns nothing new to take part. Words that find nothing dim it all:
71
- * the one id nothing is called keeps the set non-empty.
72
- */
73
- return useMemo(() => {
74
- if (!found)
75
- return reachedBySelection;
76
- // Every match, not the strip's capped index: a real hit is never drawn dimmed.
77
- const hits = found.matched;
78
- if (hits.length === 0 && reachedBySelection.length === 0)
79
- return [NOTHING_FOUND];
80
- return [...new Set([...reachedBySelection, ...hits])];
81
- }, [found, reachedBySelection]);
82
- }
83
- /**
84
- * What the SELECTION alone reaches — `useImplicated` without the search.
85
- * For the one reading that must not confuse the two: a district's "tied"
86
- * count is about what you picked, not what you typed.
87
- */
88
- export function useReached() {
89
- const { store, selection } = useGraview();
90
- const nodes = useGraph();
91
- return useMemo(() => {
92
- if (selection.length === 0)
93
- return [];
94
- const chosen = new Set(selection);
95
- const reached = new Set(chosen);
96
- for (const edge of store.graph.allEdges()) {
97
- if (chosen.has(edge.from))
98
- reached.add(edge.to);
99
- if (chosen.has(edge.to))
100
- reached.add(edge.from);
101
- }
102
- /*
103
- * What a selected thing JUDGES, as well as what it touches.
104
- *
105
- * A rule has no edges, so selecting one reached nothing and the screen
106
- * did not change — it read as broken. What a rule is about is derivable
107
- * from its violations, which name the nodes they implicate, so selecting
108
- * one now lights those wherever they are drawn.
109
- */
110
- for (const violation of store.violations()) {
111
- if (violation.subjectId !== undefined && chosen.has(violation.subjectId)) {
112
- for (const id of violation.nodeIds)
113
- reached.add(id);
114
- }
115
- }
116
- return [...reached];
117
- }, [store, selection, nodes]);
118
- }
119
- /** Stands in the implicated set when a search found nothing, so every view dims. */
120
- export const NOTHING_FOUND = "search:nothing";
121
45
  /** Navigation: every stop is a view, and every view is a URL. */
122
46
  export function useNavigation() {
123
47
  const { view, setView } = useGraview();
@@ -130,124 +54,6 @@ export function useNavigation() {
130
54
  go: (next) => setView(next),
131
55
  }), [view, setView]);
132
56
  }
133
- /**
134
- * Keeps the view in the URL fragment and honours the back button.
135
- *
136
- * The back button returning to the exact prior view is not a nicety here: it
137
- * is the thing that makes a spatial interface navigable at all, because it
138
- * means no arrangement is ever unreachable once you have left it.
139
- */
140
- /**
141
- * `useUrlSync` as a component, for shells that make syncing conditional —
142
- * a hook inside `if (syncUrl)` is a Rules-of-Hooks trap the moment the
143
- * flag ever changes; a conditionally RENDERED component is not.
144
- */
145
- export function UrlSync() {
146
- useUrlSync();
147
- return null;
148
- }
149
- export function useUrlSync() {
150
- const { view, setView, views } = useGraview();
151
- /*
152
- * A STOP THAT NAMES A PLACE AND NOT ITS GROUP STILL GOES THERE.
153
- *
154
- * `#view=grounds-map` is the link a page can actually write: the page knows
155
- * the picture it is talking about, not how the layout spells the aggregate
156
- * id of the kind behind it. The registry does know — a named place carries
157
- * its kind — so the place is looked up here and the group it is a picture
158
- * of becomes the focus. Without this the short form lands on the default
159
- * view, which is the pasted-link problem one level up.
160
- */
161
- const settled = useCallback((state) => {
162
- const asked = state.within?.["view"];
163
- if (asked === undefined || state.focusId)
164
- return state;
165
- const place = views.places().find((candidate) => candidate.as === asked);
166
- return place ? { ...state, focusId: aggregateId(place.kind) } : state;
167
- }, [views]);
168
- /*
169
- * Adopt the fragment on FIRST load, not only on navigation.
170
- *
171
- * "Every stop is a URL" was only half true: the view was written to the
172
- * fragment and the back button honoured it, but opening a shared link
173
- * landed on the default view and silently overwrote the address bar. A
174
- * pasted link that does not go where it says is worse than no link.
175
- */
176
- useEffect(() => {
177
- if (typeof window === "undefined")
178
- return;
179
- const initial = settled(fromUrl(window.location.hash));
180
- // Adopt ANY address that says something — overview, zoom, a selection —
181
- // not only the focus-shaped ones. A pasted link that does not go where
182
- // it says is worse than no link.
183
- if (!sameView(initial, EMPTY_VIEW)) {
184
- setView(initial);
185
- }
186
- // Once, on mount: later changes are this hook's own writes.
187
- }, []);
188
- useEffect(() => {
189
- if (typeof window === "undefined")
190
- return;
191
- const onPop = () => setView(settled(fromUrl(window.location.hash)));
192
- window.addEventListener("popstate", onPop);
193
- window.addEventListener("hashchange", onPop);
194
- return () => {
195
- window.removeEventListener("popstate", onPop);
196
- window.removeEventListener("hashchange", onPop);
197
- };
198
- }, [setView, settled]);
199
- const landed = useRef(false);
200
- const written = useRef(null);
201
- useEffect(() => {
202
- if (typeof window === "undefined")
203
- return;
204
- const next = toUrl(view);
205
- // Only write when the view actually changed, or the back stack fills with
206
- // duplicates and the back button stops meaning anything. The baseline
207
- // still moves: after a popstate re-syncs the view, a stale baseline made
208
- // the next drag read as travel and push a phantom stop.
209
- if (window.location.hash === next) {
210
- written.current = view;
211
- return;
212
- }
213
- /*
214
- * ARRIVING is not a navigation.
215
- *
216
- * The first write is the app adopting its own default view, and pushing it
217
- * put a stop in the history that nobody went to — so the back control was
218
- * offered the moment the page loaded, and pressing it went to the blank
219
- * URL the app had just left. Replacing is what a page does when it tidies
220
- * its own address.
221
- */
222
- if (!landed.current) {
223
- landed.current = true;
224
- written.current = view;
225
- window.history.replaceState({ graview: trail.at }, "", next);
226
- return;
227
- }
228
- /*
229
- * MOVING THE FURNITURE IS NOT TRAVELLING.
230
- *
231
- * A drag writes the view state on every pointer move, because that is what
232
- * makes the connectors follow the card. Pushed, one drag would be sixty
233
- * history entries and Back would mean "one pixel ago". A change that
234
- * touches only the camera or where things were dragged is an adjustment of
235
- * the stop you are on, so it replaces: the address stays shareable and
236
- * Back still means the place you were before you started fiddling.
237
- */
238
- if (adjustment(written.current, view)) {
239
- window.history.replaceState({ graview: trail.at }, "", next);
240
- written.current = view;
241
- return;
242
- }
243
- written.current = view;
244
- window.history.pushState({ graview: ++trail.at }, "", next);
245
- // A new stop discards anything that was ahead of it, exactly as the
246
- // browser does.
247
- trail.depth = trail.at;
248
- trail.tell();
249
- }, [view]);
250
- }
251
57
  /**
252
58
  * Where you are in your own history.
253
59
  *
@@ -261,71 +67,8 @@ export function useUrlSync() {
261
67
  * that re-created it per component would count each navigation once per
262
68
  * mounted consumer.
263
69
  */
264
- /**
265
- * Whether one view differs from another only in what the user MOVED.
266
- *
267
- * Where you are is the focus, the relation, what is expanded and whether you
268
- * have risen above the stack. Where you dragged things to is not a different
269
- * place; it is the same place, rearranged.
270
- */
271
- /**
272
- * WHETHER A CHANGE IS A STOP OR JUST A NUDGE OF THE ONE YOU ARE ON.
273
- *
274
- * Exported so it can be read and held to: what belongs in here is the
275
- * difference between the back button meaning something and meaning "one
276
- * pixel ago", and the two things that went wrong went wrong silently — the
277
- * address changed, no entry was pushed, and the arrows stayed grey.
278
- */
279
- export function adjustment(before, after) {
280
- if (!before)
281
- return false;
282
- return (before.focusId === after.focusId &&
283
- before.relation === after.relation &&
284
- (before.overview ?? false) === (after.overview ?? false) &&
285
- (before.zoom ?? false) === (after.zoom ?? false) &&
286
- (before.past ?? false) === (after.past ?? false) &&
287
- before.expanded.join(",") === after.expanded.join(",") &&
288
- /*
289
- * SHOWING A MODULE IS TRAVELLING. "Show the installation" raises whole
290
- * districts into the scene — who is here, who has been invited — and
291
- * its own comment always said it was a stop Back knew the way out of.
292
- * It was not: `shown` was missing from this comparison, so the address
293
- * gained `show=installation` and the entry was REPLACED, the arrows
294
- * stayed grey, and one Back from the installation left the app.
295
- */
296
- (before.shown ?? []).join(",") === (after.shown ?? []).join(",") &&
297
- /*
298
- * A VIEW MOVING ALONG ITS OWN DIMENSION IS TRAVELLING.
299
- *
300
- * Turning the calendar to October is going somewhere: a URL to send
301
- * somebody, a place to come back to, a Back that means "the month I was
302
- * looking at". Left out of this comparison it read as an adjustment of
303
- * the stop — the address updated, no entry was pushed, and one Back
304
- * from October landed past the calendar entirely, at the place before
305
- * anyone opened it. That is the exact failure `within` exists to fix.
306
- */
307
- sameWithin(before.within, after.within));
308
- }
309
- function sameWithin(before, after) {
310
- const say = (held) => Object.entries(held ?? {})
311
- .sort(([a], [b]) => a.localeCompare(b))
312
- .map(([key, value]) => `${key}=${value}`)
313
- .join("&");
314
- return say(withoutWords(before)) === say(withoutWords(after));
315
- }
316
- /*
317
- * TYPING IS NOT TRAVELLING. The words a row narrows by (`in.q`) change on
318
- * every keystroke, and each one pushed a history entry — five letters, five
319
- * Backs to leave a list. The scene's own `q` was never compared; the row's
320
- * words are the same act and read the same way.
321
- */
322
- function withoutWords(held) {
323
- if (!held || !("q" in held))
324
- return held;
325
- const { q: _words, ...rest } = held;
326
- return rest;
327
- }
328
- const trail = {
70
+ /** Where you are in your own history: the pushes `useUrlSync` makes and the pops the browser reports. */
71
+ export const trail = {
329
72
  at: 0,
330
73
  depth: 0,
331
74
  listeners: new Set(),
@@ -390,23 +133,4 @@ export function useJackIn() {
390
133
  const jackedIn = view.zoom ? view.focusId : null;
391
134
  return useMemo(() => ({ jackedIn, enter, exit, isJackedIn: view.zoom === true }), [jackedIn, enter, exit, view.zoom]);
392
135
  }
393
- /**
394
- * The fields of a node that can be changed where they are shown, and the way
395
- * to change one.
396
- *
397
- * `commit` runs the mutation the framework found, with the author set to a
398
- * person — so an in-place edit lands in the op log, can be undone, and is
399
- * judged by the invariants exactly like an edit made from the actions strip.
400
- * There is no second write path.
401
- */
402
- export function useEditableFields(id) {
403
- const { store, principal } = useGraview();
404
- const nodes = useGraph();
405
- const fields = useMemo(() => (id === null ? [] : editableFields(store, id)), [store, id, nodes]);
406
- const commit = useCallback((field, value, rest = {}) => {
407
- const call = field.call(value);
408
- store.apply({ name: call.name, args: { ...call.args, ...rest } }, { author: principal });
409
- }, [store, principal]);
410
- return { fields, commit };
411
- }
412
136
  //# sourceMappingURL=hooks.js.map