@danxbot/ui 10.0.1 → 11.1.0

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.
@@ -0,0 +1,20 @@
1
+ import { type ReactNode } from "react";
2
+ /** The controlled activation pair every pencil-activated field takes. */
3
+ export interface EditActivation {
4
+ editing: boolean;
5
+ onEditingChange: (editing: boolean) => void;
6
+ }
7
+ export interface EditGutterProps {
8
+ /**
9
+ * The pencil's accessible name — say WHAT it edits ("Edit title"), because
10
+ * a screen reader lists it among every other pencil on the page.
11
+ */
12
+ label: string;
13
+ /**
14
+ * Render the field, spreading the activation onto it:
15
+ * `{(edit) => <InlineEdit {...edit} … />}`.
16
+ */
17
+ children: (activation: EditActivation) => ReactNode;
18
+ className?: string;
19
+ }
20
+ export declare function EditGutter({ label, children, className }: EditGutterProps): import("react").JSX.Element;
@@ -1,14 +1,7 @@
1
1
  import { type Ref } from "react";
2
2
  import { useRender } from "@base-ui/react/use-render";
3
- /**
4
- * The caller's refusal of a value, with the reason that will be shown.
5
- * Unchanged from 5.2.1 — see `InlineEditOutcome`.
6
- */
7
- export interface InlineEditRefusal {
8
- refusal: string;
9
- }
10
- /** What `onCommit` may report. Returning nothing accepts the value. */
11
- export type InlineEditOutcome = void | InlineEditRefusal;
3
+ import { type InlineEditCommit } from "./edit/outcome";
4
+ export type { InlineEditCommit, InlineEditOutcome, InlineEditRefusal } from "./edit/outcome";
12
5
  export interface InlineEditProps {
13
6
  /**
14
7
  * The committed value. Controlled — this component never holds the truth,
@@ -30,7 +23,7 @@ export interface InlineEditProps {
30
23
  * it settles. Resolve/return `{ refusal }` to reject the value with a
31
24
  * reason; throw or reject to report that the save itself failed.
32
25
  */
33
- onCommit: (value: string, base: string) => InlineEditOutcome | Promise<InlineEditOutcome>;
26
+ onCommit: InlineEditCommit;
34
27
  /**
35
28
  * The refusals that are knowable WITHOUT a request. Return the reason, or
36
29
  * `null` to accept. Runs after the unchanged check and before `onCommit`.
@@ -115,10 +108,25 @@ export interface InlineEditProps {
115
108
  /** The rejected error, handed on so it can be logged or reported. */
116
109
  onError?: (error: unknown) => void;
117
110
  /**
118
- * Fires exactly when editing starts and exactly when it ends — never while a
119
- * read-only control merely declines to open. Use it to suspend any
120
- * re-render-from-server logic of your own for as long as someone is
121
- * actually looking at the field; see the module comment.
111
+ * CONTROLLED ACTIVATION (DX-3372). Pass it and the control never opens
112
+ * ITSELF — no click, Enter or F2 — because something else (the pencil of an
113
+ * `EditGutter`, a menu item, a shortcut) is the way in. Setting it `true`
114
+ * opens the field, focused, caret at the end; the control asks to close
115
+ * through `onEditingChange(false)` once a commit lands or Escape cancels.
116
+ * Setting it `false` yourself while it is open abandons the draft exactly as
117
+ * Escape would — nothing is written. At rest the element leaves the tab
118
+ * order and drops its hover affordance, so the trigger is the one stop.
119
+ *
120
+ * Omit it for the self-opening control (click, Enter or F2).
121
+ */
122
+ editing?: boolean;
123
+ /**
124
+ * Fires exactly when the control itself starts or ends editing — never while
125
+ * a read-only control merely declines to open, and never for a change the
126
+ * caller made through `editing`. Use it to suspend any re-render-from-server
127
+ * logic of your own for as long as someone is actually looking at the
128
+ * field; see the module comment. With `editing` controlled, this is how the
129
+ * control hands the closed state back.
122
130
  */
123
131
  onEditingChange?: (editing: boolean) => void;
124
132
  /** Render as a different element — see `Button`'s own `render` for the shape. */
@@ -135,4 +143,4 @@ export interface InlineEditProps {
135
143
  messageClassName?: string;
136
144
  ref?: Ref<HTMLElement>;
137
145
  }
138
- export declare function InlineEdit({ value, onCommit, validate, liveValidate, label, placeholder, size, multiline, autosaveMs, readOnly, failureMessage, onError, onEditingChange, render, className, messageClassName, inputMode, ref, }: InlineEditProps): import("react").JSX.Element;
146
+ export declare function InlineEdit({ value, onCommit, validate, liveValidate, label, placeholder, size, multiline, autosaveMs, readOnly, failureMessage, onError, editing: editingProp, onEditingChange, render, className, messageClassName, inputMode, ref, }: InlineEditProps): import("react").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { type ReactNode } from "react";
2
+ import type { ImageUploader } from "../lib/markdown/image-pipeline";
3
+ import { type InlineEditCommit } from "./edit/outcome";
4
+ export interface InlineMarkdownEditProps {
5
+ /** The committed markdown. Controlled — the field only ever holds a draft. */
6
+ value: string;
7
+ /**
8
+ * Commit a new value — the same contract as `InlineEdit.onCommit`: called
9
+ * only when the draft differs from `value`, with `base` = the value editing
10
+ * began from; return/resolve `{ refusal }` to refuse with a reason, throw or
11
+ * reject to report a failed save.
12
+ */
13
+ onCommit: InlineEditCommit;
14
+ /** Whether the editor is open. REQUIRED — see the module comment. */
15
+ editing: boolean;
16
+ /** The field asks to close through this, once a commit lands or Escape cancels. */
17
+ onEditingChange: (editing: boolean) => void;
18
+ /** The accessible name of the field and of its editor. */
19
+ label: string;
20
+ /** Shown at rest and in the editor when the value is empty. @default "Empty" */
21
+ placeholder?: string;
22
+ /** Collapse the rendered view to this many lines behind a `ReadMore`. */
23
+ collapsedLines?: number;
24
+ /** Extra toolbar content for the editor, after its built-in groups. */
25
+ toolbar?: ReactNode;
26
+ /** Stores a pasted/dropped/picked image — see `MarkdownEditorProps.uploadImage`. */
27
+ uploadImage?: ImageUploader;
28
+ /**
29
+ * What is shown when the commit REJECTS (never a `{ refusal }`, which
30
+ * renders its own text).
31
+ */
32
+ failureMessage?: string;
33
+ /** The rejected error, handed on so it can be logged or reported. */
34
+ onError?: (error: unknown) => void;
35
+ className?: string;
36
+ }
37
+ export declare function InlineMarkdownEdit({ value, onCommit, editing, onEditingChange, label, placeholder, collapsedLines, toolbar, uploadImage, failureMessage, onError, className, }: InlineMarkdownEditProps): import("react").JSX.Element;
@@ -10,7 +10,13 @@ import { type ImageUploadState, type ImageUploader } from "../lib/markdown/image
10
10
  * contenteditable host, never a proxy element.
11
11
  */
12
12
  export interface MarkdownEditorHandle {
13
- focus: () => void;
13
+ /**
14
+ * Focus the surface. `caret` places the caret at the document's `"start"` or
15
+ * `"end"` (snapped to a real caret stop); omit it to leave the caret wherever
16
+ * the browser puts it. DX-3372: `InlineMarkdownEdit` opens with the caret at
17
+ * the end, the same place `InlineEdit` puts it.
18
+ */
19
+ focus: (caret?: "start" | "end") => void;
14
20
  }
15
21
  export interface MarkdownEditorProps {
16
22
  /** The markdown. Controlled when `onChange` is given. */
@@ -9,11 +9,11 @@ export declare const code: import("tailwind-variants").TVReturnType<{
9
9
  footer?: import("tailwind-variants").ClassValue;
10
10
  hint?: import("tailwind-variants").ClassValue;
11
11
  root?: import("tailwind-variants").ClassValue;
12
- control?: import("tailwind-variants").ClassValue;
13
12
  rendered?: import("tailwind-variants").ClassValue;
14
13
  layers?: import("tailwind-variants").ClassValue;
15
14
  gutter?: import("tailwind-variants").ClassValue;
16
15
  fade?: import("tailwind-variants").ClassValue;
16
+ control?: import("tailwind-variants").ClassValue;
17
17
  scroller?: import("tailwind-variants").ClassValue;
18
18
  expand?: import("tailwind-variants").ClassValue;
19
19
  };
@@ -29,11 +29,11 @@ export declare const code: import("tailwind-variants").TVReturnType<{
29
29
  footer?: import("tailwind-variants").ClassValue;
30
30
  hint?: import("tailwind-variants").ClassValue;
31
31
  root?: import("tailwind-variants").ClassValue;
32
- control?: import("tailwind-variants").ClassValue;
33
32
  rendered?: import("tailwind-variants").ClassValue;
34
33
  layers?: import("tailwind-variants").ClassValue;
35
34
  gutter?: import("tailwind-variants").ClassValue;
36
35
  fade?: import("tailwind-variants").ClassValue;
36
+ control?: import("tailwind-variants").ClassValue;
37
37
  scroller?: import("tailwind-variants").ClassValue;
38
38
  expand?: import("tailwind-variants").ClassValue;
39
39
  };
@@ -65,11 +65,11 @@ export declare const code: import("tailwind-variants").TVReturnType<{
65
65
  footer?: import("tailwind-variants").ClassValue;
66
66
  hint?: import("tailwind-variants").ClassValue;
67
67
  root?: import("tailwind-variants").ClassValue;
68
- control?: import("tailwind-variants").ClassValue;
69
68
  rendered?: import("tailwind-variants").ClassValue;
70
69
  layers?: import("tailwind-variants").ClassValue;
71
70
  gutter?: import("tailwind-variants").ClassValue;
72
71
  fade?: import("tailwind-variants").ClassValue;
72
+ control?: import("tailwind-variants").ClassValue;
73
73
  scroller?: import("tailwind-variants").ClassValue;
74
74
  expand?: import("tailwind-variants").ClassValue;
75
75
  };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The caller's refusal of a value, with the reason that will be shown.
3
+ */
4
+ export interface InlineEditRefusal {
5
+ refusal: string;
6
+ }
7
+ /** What `onCommit` may report. Returning nothing accepts the value. */
8
+ export type InlineEditOutcome = void | InlineEditRefusal;
9
+ /** Every in-place editor's `onCommit`: the new value and the value editing began from. */
10
+ export type InlineEditCommit = (value: string, base: string) => InlineEditOutcome | Promise<InlineEditOutcome>;
11
+ export declare function isRefusal(outcome: InlineEditOutcome): outcome is InlineEditRefusal;
12
+ export declare function isPromise(outcome: InlineEditOutcome | Promise<InlineEditOutcome>): outcome is Promise<InlineEditOutcome>;
13
+ export declare const DEFAULT_FAILURE = "Could not save. Your text is still here \u2014 press Enter to try again.";
14
+ /**
15
+ * A refusal nobody can read leaves the field open with nothing beside it — the
16
+ * exact silent failure the outcome contract exists to rule out — so an empty
17
+ * reason is a programming error, thrown rather than rendered.
18
+ */
19
+ export declare function assertReadableRefusal(component: string, reason: string, source: "validate" | "onCommit"): void;
20
+ /**
21
+ * THE MESSAGE IS AN OVERLAY, for the reason `InlineEdit` measured: in normal
22
+ * flow a refusal grows the control's own wrapper, which inside a fixed-height
23
+ * row shoves everything beside it. Absolutely positioned, it cannot contribute
24
+ * height. The parent must be `relative`.
25
+ */
26
+ export declare function OutcomeMessage({ id, children, className, }: {
27
+ id?: string;
28
+ children: string;
29
+ className?: string;
30
+ }): import("react").JSX.Element;
@@ -1,48 +1,56 @@
1
- import type { CanvasNode, CanvasOrientation, NodeRenderer } from "./types";
1
+ import type { CanvasGraph, CanvasGroup, CanvasNode, CanvasOrientation, CanvasPlacedEdge, NodeRenderer } from "./types";
2
2
  export interface NodeCanvasProps<Extra = unknown> {
3
- /** The whole tree. The canvas walks into it; it never mutates it. */
4
- root: CanvasNode<Extra>;
3
+ /** The whole graph — every node and edge, however many scopes it spans. Never mutated. */
4
+ graph: CanvasGraph<Extra>;
5
+ /** Named clusters nodes can belong to via `CanvasNode.groupId`. Flat — see `CanvasGroup`. */
6
+ groups?: readonly CanvasGroup[];
5
7
  /**
6
- * What this canvas IS — "Org chart", "Dependency graph for the March build".
8
+ * What this canvas IS — "Import graph for the docs map", "Service dependency graph".
7
9
  *
8
- * Required for the same reason `StatusChecklist.label` and
9
- * `KanbanBoard.label` are: it is the accessible name of the whole structure,
10
- * and somebody landing on it by keyboard has nothing else to tell them what
10
+ * Required for the same reason `StatusChecklist.label` and `KanbanBoard.label`
11
+ * are: it is the accessible name of the whole structure, and somebody landing
12
+ * on it by keyboard or in the list view has nothing else to tell them what
11
13
  * they are inside.
12
14
  */
13
15
  label: string;
14
16
  /**
15
17
  * The node body. Receives the consumer's own node type and a context of
16
- * purely structural facts.
18
+ * purely structural facts — see `NodeRenderContext`.
17
19
  *
18
- * A RENDER PROP RATHER THAN CHILDREN-AS-A-FUNCTION, because a canvas draws
19
- * MANY nodes and `children` reads as one. `<NodeCanvas>{(node) => …}` looks
20
- * like a single child and is really a template invoked N times, which is the
21
- * ambiguity this library already avoids elsewhere (`ProvenanceDisclosure`
22
- * takes `actions`, `Chat` takes a `components` map). It is optional: the
23
- * default draws the `label` the node already declares and nothing else, so
24
- * the component is usable with no wiring and the default introduces no
25
- * vocabulary of its own.
20
+ * A RENDER PROP RATHER THAN CHILDREN-AS-A-FUNCTION, for the same reason
21
+ * `ProvenanceDisclosure` takes `actions` and `Chat` takes a `components` map:
22
+ * a canvas draws MANY nodes and `children` reads as one. Optional: the
23
+ * default draws the label, and a member count when this box stands in for a
24
+ * group or a condensed cycle.
25
+ *
26
+ * Called with a SYNTHETIC node for a collapsed group or cycle — `{id, label}`
27
+ * only, `Extra`'s own fields absent — since no single real node's `Extra`
28
+ * would be honest to hand back for a box standing in for several.
26
29
  */
27
30
  renderNode?: NodeRenderer<Extra>;
31
+ /**
32
+ * The hover summary's content. Defaults to the node's `label`. Return
33
+ * `null`/`undefined` to suppress the tooltip for a particular node.
34
+ */
35
+ renderSummary?: (node: CanvasNode<Extra>) => React.ReactNode;
36
+ /** A class name for one drawn edge, keyed on its (possibly aggregated) `kind`. */
37
+ edgeClassName?: (edge: CanvasPlacedEdge) => string | undefined;
28
38
  /** Which way the chart grows. See `CanvasOrientation`. */
29
39
  orientation?: CanvasOrientation;
30
- /** Levels below the current root to draw. `1` is the root and its children. */
31
- depth?: number;
32
40
  /** Along-axis extent of a node that does not declare its own, in px. */
33
41
  nodeWidth?: number;
34
42
  /** Cross-axis extent of a node that does not declare its own, in px. */
35
43
  nodeHeight?: number;
36
44
  /** Height of the scrolling viewport. Any CSS length. */
37
45
  height?: string;
38
- /**
39
- * Fired after the canvas re-roots, with the trail from the ORIGINAL root.
40
- *
41
- * Observation, not control: the canvas owns where it is. A consumer that
42
- * needs to restore a position across a reload stores this and remounts with
43
- * a different `root`.
44
- */
45
- onRootChange?: (node: CanvasNode<Extra>, path: CanvasNode<Extra>[]) => void;
46
+ /** Which group is currently drilled into. `null` is the top level. Controlled. */
47
+ expandedGroupId?: string | null;
48
+ /** Initial drilled-into group for uncontrolled use. Default `null` (top level). */
49
+ defaultExpandedGroupId?: string | null;
50
+ /** Fired whenever the drilled-into group changes, from either a click or the keyboard. */
51
+ onExpandedGroupIdChange?: (groupId: string | null) => void;
52
+ /** Fired when an ORDINARY node (not a group, not a cycle) is clicked. Navigation is the consumer's. */
53
+ onNodeClick?: (node: CanvasNode<Extra>) => void;
46
54
  /** Fired when a node drag commits. The offset is from its laid-out position. */
47
55
  onNodeDragEnd?: (event: {
48
56
  id: string;
@@ -55,4 +63,4 @@ export interface NodeCanvasProps<Extra = unknown> {
55
63
  disabled?: boolean;
56
64
  className?: string;
57
65
  }
58
- export declare function NodeCanvas<Extra = unknown>({ root, label, renderNode, orientation, depth, nodeWidth, nodeHeight, height, onRootChange, onNodeDragEnd, disabled, className, }: NodeCanvasProps<Extra>): import("react").JSX.Element;
66
+ export declare function NodeCanvas<Extra = unknown>({ graph, groups, label, renderNode, renderSummary, edgeClassName, orientation, nodeWidth, nodeHeight, height, expandedGroupId, defaultExpandedGroupId, onExpandedGroupIdChange, onNodeClick, onNodeDragEnd, disabled, className, }: NodeCanvasProps<Extra>): import("react").JSX.Element;
@@ -1,3 +1,3 @@
1
1
  export { NodeCanvas, type NodeCanvasProps } from "./NodeCanvas";
2
- export { LAYOUT_DEFAULTS, layoutTree, pathToNode } from "./layout";
3
- export type { CanvasEdge, CanvasLayout, CanvasNode, CanvasOrientation, LayoutOptions, NodeRenderContext, NodeRenderer, PlacedNode, } from "./types";
2
+ export { LAYOUT_DEFAULTS, edgePath, layoutGraph, neighbourhood } from "./layout";
3
+ export type { CanvasCycleScope, CanvasEdge, CanvasGraph, CanvasGroup, CanvasLayout, CanvasNode, CanvasOrientation, CanvasPlacedEdge, CanvasScope, LayoutOptions, NodeRenderContext, NodeRenderer, PlacedNode, } from "./types";
@@ -1,17 +1,29 @@
1
- import type { CanvasLayout, CanvasNode, LayoutOptions } from "./types";
1
+ import type { CanvasGraph, CanvasGroup, CanvasLayout, CanvasPlacedEdge, CanvasScope, LayoutOptions } from "./types";
2
2
  export declare const LAYOUT_DEFAULTS: Required<LayoutOptions>;
3
3
  /**
4
- * Lay a tree out.
4
+ * Lay out one scope of a graph.
5
5
  *
6
- * Coordinates are plane-local and start at 0 on both axes: the caller decides
7
- * where the plane sits and how much padding surrounds it.
6
+ * `scope` is `null` for the top level, a `CanvasGroup.id` to draw that
7
+ * group's own members, or a `CanvasCycleScope` to draw a condensed cycle's
8
+ * own members (see that type's doc for why it is not just "another group
9
+ * id" — it skips this function's own condensing pre-pass on purpose).
10
+ * Coordinates are plane-local and start at 0 on both axes: the caller
11
+ * decides where the plane sits and how much padding surrounds it.
8
12
  */
9
- export declare function layoutTree<Extra>(root: CanvasNode<Extra>, options?: LayoutOptions): CanvasLayout<Extra>;
13
+ export declare function layoutGraph<Extra>(graph: CanvasGraph<Extra>, groups: readonly CanvasGroup[], scope: CanvasScope, options?: LayoutOptions): CanvasLayout<Extra>;
14
+ /** The SVG `d` attribute for one drawn edge. See `smoothPath` above. */
15
+ export declare function edgePath(edge: CanvasPlacedEdge): string;
10
16
  /**
11
- * The ids from the ORIGINAL root down to `targetId`, inclusive.
17
+ * Every real node id reachable from `id` within `hops` steps, along either
18
+ * edge direction — the "1–2-hop neighbourhood" `NodeRenderContext.focusDistance`
19
+ * reports. Undirected on purpose: a node's neighbourhood, for the purpose of
20
+ * "what should stay legible while I'm reading this one", is what points at it
21
+ * as much as what it points at.
12
22
  *
13
- * `null` when the id is not in the tree, which the canvas treats as a
14
- * programming error rather than as an empty trail — a breadcrumb that silently
15
- * renders nothing is how a broken `rootId` looks like a styling bug.
23
+ * Keyed by the drawn (possibly condensed) ids, not raw graph node ids — the
24
+ * canvas calls this with whatever `CanvasPlacedEdge` already uses.
16
25
  */
17
- export declare function pathToNode<Extra>(root: CanvasNode<Extra>, targetId: string): CanvasNode<Extra>[] | null;
26
+ export declare function neighbourhood(id: string, edges: readonly {
27
+ from: string;
28
+ to: string;
29
+ }[], hops: number): Map<string, number>;
@@ -1,76 +1,165 @@
1
1
  import type { ReactNode } from "react";
2
2
  /**
3
- * One node of the tree.
3
+ * One node of the graph.
4
4
  *
5
- * `Extra` carries the consumer's own fields DOWN THE WHOLE TREE, so
6
- * `renderNode` receives their type rather than ours plus a cast:
5
+ * `Extra` carries the consumer's own fields, so `renderNode` receives their
6
+ * type rather than ours plus a cast:
7
7
  *
8
8
  * ```ts
9
- * type Task = { owner: string; done: boolean };
10
- * const root: CanvasNode<Task> = { id: "r", label: "Release", owner: "ada", done: false, children: [...] };
11
- * <NodeCanvas root={root} renderNode={(node) => <span>{node.owner}</span>} />
9
+ * type Service = { owner: string; deployed: boolean };
10
+ * const nodes: CanvasNode<Service>[] = [
11
+ * { id: "api", label: "API", owner: "ada", deployed: true },
12
+ * ];
13
+ * <NodeCanvas graph={{ nodes, edges }} renderNode={(node) => <span>{node.owner}</span>} />
12
14
  * ```
13
- *
14
- * Written as an intersection rather than an interface with a type parameter
15
- * because the recursion has to carry `Extra` into `children` — an interface
16
- * cannot refer to its own instantiation that way without a self-referencing
17
- * constraint that every consumer then has to spell out.
18
15
  */
19
16
  export type CanvasNode<Extra = unknown> = Extra & {
20
17
  /** Stable across renders. Node positions, focus and drag offsets are keyed on it. */
21
18
  id: string;
22
19
  /**
23
- * The accessible name, the breadcrumb text, and the default body.
20
+ * The accessible name, the list-view row text, and the default body.
24
21
  *
25
22
  * A plain string, not a `ReactNode`, and that is deliberate: it is read
26
- * aloud and printed into a trail, and neither of those can do anything with
23
+ * aloud and printed into a row, and neither of those can do anything with
27
24
  * an element tree. Rich content belongs in `renderNode`.
28
25
  */
29
26
  label: string;
30
- children?: readonly CanvasNode<Extra>[];
27
+ /**
28
+ * Which `CanvasGroup` this node belongs to, if any. A node with no group
29
+ * sits at the top level next to every declared group's own aggregate node.
30
+ * Groups are flat — a group cannot itself belong to another group — which
31
+ * matches every real case this shipped with (a domain, a package, a team):
32
+ * one level of clustering is what "collapse the noisy part" needs, and nested
33
+ * clustering is a UI nobody asked for yet. Add it if a real consumer needs it.
34
+ */
35
+ groupId?: string;
31
36
  /** Along-axis extent for this node only, when it differs from the canvas default. */
32
37
  width?: number;
33
38
  /** Cross-axis extent for this node only, when it differs from the canvas default. */
34
39
  height?: number;
35
40
  };
41
+ /**
42
+ * A directed connection between two node ids.
43
+ *
44
+ * `kind` is a free-form string the library assigns NO meaning to — same rule
45
+ * as `label`'s absence of a `type` field. A consumer distinguishing "imports"
46
+ * from "calls" reads `kind` back out in `edgeClassName` or in its own styling;
47
+ * the library never brands one kind as more important than another.
48
+ */
49
+ export interface CanvasEdge {
50
+ from: string;
51
+ to: string;
52
+ kind?: string;
53
+ }
54
+ /**
55
+ * A named cluster of nodes, collapsed to one box by default.
56
+ *
57
+ * COLLAPSED IS THE DEFAULT because a real graph worth drawing (the spike's
58
+ * 402-node/2,099-edge import graph) is unreadable — and, for a graph with
59
+ * enough cross-cluster cycles, catastrophically slow to lay out — at full
60
+ * detail. A group's aggregate node stands in for every member until a
61
+ * consumer drills into it; see `NodeCanvasProps.expandedGroupId`.
62
+ */
63
+ export interface CanvasGroup {
64
+ id: string;
65
+ label: string;
66
+ /** Along-axis extent of this group's collapsed box, when it differs from the default. */
67
+ width?: number;
68
+ /** Cross-axis extent of this group's collapsed box, when it differs from the default. */
69
+ height?: number;
70
+ }
71
+ /** The whole graph. Never mutated by the canvas. */
72
+ export interface CanvasGraph<Extra = unknown> {
73
+ nodes: readonly CanvasNode<Extra>[];
74
+ edges: readonly CanvasEdge[];
75
+ }
76
+ /**
77
+ * The scope drilled into when a consumer clicks a CONDENSED CYCLE rather than
78
+ * a declared group — captured directly from the `PlacedNode.memberIds` the
79
+ * click landed on, since a cycle has no stable, externally-nameable id of its
80
+ * own the way a `CanvasGroup` does.
81
+ *
82
+ * `layoutGraph` treats this shape as an instruction to skip its own
83
+ * cycle-condensing pre-pass for this call: every member of a genuine strongly
84
+ * connected component is, by definition, reachable from every other member
85
+ * using ONLY edges between members of that same component (that is what
86
+ * "strongly connected" means) — so re-running Tarjan on exactly this member
87
+ * set would immediately re-discover the identical single component and
88
+ * collapse it right back to one box, drilling in would show nothing new, and
89
+ * a naive implementation of "drill into a cycle" is a no-op wearing a
90
+ * breadcrumb. Skipping condensation here hands the (now small, already
91
+ * isolated) subgraph to dagre's OWN cycle-breaking pass directly — the exact
92
+ * cost/quality tradeoff this card's condensing pre-pass exists to avoid at
93
+ * the FULL graph's scale is perfectly acceptable once bounded to one cycle's
94
+ * membership.
95
+ */
96
+ export interface CanvasCycleScope {
97
+ cycleMemberIds: readonly string[];
98
+ }
99
+ /**
100
+ * What `layoutGraph`'s third argument names: the top level (`null`), a
101
+ * declared group's own members (its `CanvasGroup.id`), or a condensed
102
+ * cycle's own members (`CanvasCycleScope`). See `CanvasCycleScope` for why
103
+ * the last case is not just "another group id".
104
+ */
105
+ export type CanvasScope = string | null | CanvasCycleScope;
36
106
  /**
37
107
  * Which way the chart grows.
38
108
  *
39
- * `vertical` puts the root at the top with children below it — the shape most
40
- * people picture. `horizontal` puts the root at the left with children to the
41
- * right, which is what deep trees with long labels actually want: label text
42
- * runs along the level axis in one and across it in the other, so a horizontal
43
- * chart of the same tree is narrower and reads without truncation.
109
+ * `vertical` ranks top to bottom — the shape most people picture. `horizontal`
110
+ * ranks left to right, which is what a graph with long labels and few ranks
111
+ * actually wants: label text runs along the rank axis in one and across it in
112
+ * the other, so a horizontal chart of the same graph is narrower and reads
113
+ * without truncation.
44
114
  */
45
115
  export type CanvasOrientation = "vertical" | "horizontal";
46
- /** A node with its box resolved, in plane coordinates. `x`/`y` are its TOP-LEFT. */
116
+ /**
117
+ * A node with its box resolved, in plane coordinates. `x`/`y` are its
118
+ * TOP-LEFT. This is what the layout actually drew — which is not always one
119
+ * real node: a collapsed group or a condensed cycle draws as ONE placed node
120
+ * standing in for several.
121
+ */
47
122
  export interface PlacedNode<Extra = unknown> {
48
123
  id: string;
49
- node: CanvasNode<Extra>;
50
- /** 0 for the node the canvas is currently rooted on. */
51
- depth: number;
52
- /** Null for the current root. Ids of nodes outside this view never appear. */
53
- parentId: string | null;
124
+ /** Null for a placed node standing in for a group or a condensed cycle. */
125
+ node: CanvasNode<Extra> | null;
126
+ /** Set when this placed node IS a collapsed group's aggregate box. */
127
+ group: CanvasGroup | null;
128
+ /**
129
+ * Set when this placed node is a condensed strongly-connected cluster —
130
+ * nodes that mutually reach each other with no acyclic rank to give them.
131
+ * Collapsing a cycle is not a user-facing "group" (nobody named it), but it
132
+ * renders and drills in exactly like one: see the module doc in `layout.ts`.
133
+ */
134
+ cycle: boolean;
135
+ /**
136
+ * How many real graph nodes this placed node stands for. `1` for an
137
+ * ordinary node; more than one for a collapsed group or a condensed cycle.
138
+ */
139
+ memberCount: number;
140
+ /** Ids of the real nodes this placed node stands for. `[id]` for an ordinary node. */
141
+ memberIds: readonly string[];
54
142
  x: number;
55
143
  y: number;
56
144
  width: number;
57
145
  height: number;
58
- /** How many children it has, whether or not this view is showing them. */
59
- childCount: number;
60
- /**
61
- * It has children and this view is not drawing them — the node sits on the
62
- * bottom row of the visible depth. The affordance to descend belongs here.
63
- */
64
- hasHiddenChildren: boolean;
65
146
  }
66
- /** Parent → child, by id. Both ends are always present in `nodes`. */
67
- export interface CanvasEdge {
147
+ /** One drawn connection. Both ends are always present in the returned `nodes`. */
148
+ export interface CanvasPlacedEdge {
68
149
  from: string;
69
150
  to: string;
151
+ kind?: string;
152
+ /** How many raw edges this drawn edge aggregates. `1` for an ordinary edge. */
153
+ weight: number;
154
+ /** The route dagre computed, in plane coordinates, at least two points. */
155
+ points: readonly {
156
+ x: number;
157
+ y: number;
158
+ }[];
70
159
  }
71
160
  export interface CanvasLayout<Extra = unknown> {
72
161
  nodes: PlacedNode<Extra>[];
73
- edges: CanvasEdge[];
162
+ edges: CanvasPlacedEdge[];
74
163
  /** Extent of the laid-out nodes. The plane is this plus padding on each side. */
75
164
  width: number;
76
165
  height: number;
@@ -80,34 +169,39 @@ export interface LayoutOptions {
80
169
  nodeWidth?: number;
81
170
  /** Cross-axis extent of a node that does not declare its own. */
82
171
  nodeHeight?: number;
83
- /** Clear space between two adjacent nodes on the same level. */
172
+ /** Clear space between two adjacent nodes on the same rank. */
84
173
  siblingGap?: number;
85
- /** Clear space between one level and the next. */
174
+ /** Clear space between one rank and the next. */
86
175
  levelGap?: number;
87
176
  orientation?: CanvasOrientation;
88
- /**
89
- * How many levels BELOW the root to draw. `1` shows the root and its
90
- * children; `0` shows the root alone.
91
- */
92
- depth?: number;
93
177
  }
94
178
  /**
95
179
  * What `renderNode` is told about the node it is drawing.
96
180
  *
97
- * Every field is structural — a fact about the node's place in the tree or in
98
- * the current interaction. None of them is a fact about what the node is.
181
+ * Every field is structural — a fact about the node's place in the graph or
182
+ * in the current interaction. None of them is a fact about what the node is.
99
183
  */
100
184
  export interface NodeRenderContext {
101
- /** 0 for the node the canvas is rooted on. */
102
- depth: number;
103
- isRoot: boolean;
104
- childCount: number;
105
- hasChildren: boolean;
106
- /** Children exist and this view stops short of them — clicking descends. */
107
- hasHiddenChildren: boolean;
185
+ /**
186
+ * This placed node stands in for a whole group or a condensed cycle rather
187
+ * than one real node. `renderNode` still receives a `CanvasNode`-shaped
188
+ * value for it (see `NodeCanvas`'s synthetic node), but `memberCount` is
189
+ * what says there is more inside.
190
+ */
191
+ isGroup: boolean;
192
+ /** How many real nodes this placed node represents. `1` for an ordinary node. */
193
+ memberCount: number;
108
194
  /** This node carries the canvas's single tab stop. */
109
195
  isFocused: boolean;
110
196
  /** A pointer is carrying this node right now. */
111
197
  isDragging: boolean;
198
+ /**
199
+ * Hops from the focused node, along either edge direction: `0` is the
200
+ * focused node itself, `1`/`2` its one- and two-hop neighbours. `null` when
201
+ * nothing is focused, or this node is further away than the canvas tracks.
202
+ * Lets `renderNode` dim everything outside the neighbourhood a keyboard or
203
+ * screen-reader user is currently reading.
204
+ */
205
+ focusDistance: number | null;
112
206
  }
113
207
  export type NodeRenderer<Extra = unknown> = (node: CanvasNode<Extra>, context: NodeRenderContext) => ReactNode;
@@ -21,7 +21,9 @@ export { Skeleton, SkeletonText, LoadingOverlay, type SkeletonProps, type Skelet
21
21
  export { ErrorBoundary, type ErrorBoundaryProps } from "./components/ErrorBoundary";
22
22
  export { Field, FieldSet, type FieldProps, type FieldSetProps } from "./components/Field";
23
23
  export { Input, Textarea, type InputProps, type TextareaProps } from "./components/Input";
24
- export { InlineEdit, type InlineEditProps, type InlineEditRefusal, type InlineEditOutcome, } from "./components/InlineEdit";
24
+ export { InlineEdit, type InlineEditProps, type InlineEditRefusal, type InlineEditOutcome, type InlineEditCommit, } from "./components/InlineEdit";
25
+ export { EditGutter, type EditGutterProps, type EditActivation } from "./components/EditGutter";
26
+ export { InlineMarkdownEdit, type InlineMarkdownEditProps } from "./components/InlineMarkdownEdit";
25
27
  export { SecretField, type SecretFieldProps, type SecretValue } from "./components/SecretField";
26
28
  export { SettingRow, SettingGroup, type SettingRowProps, type SettingGroupProps, type SettingRowControlRenderProps, } from "./components/SettingRow";
27
29
  export { DescriptionList, DescriptionListItem, type DescriptionListProps, type DescriptionListItemProps, type DescriptionListEntry, } from "./components/DescriptionList";
@@ -124,7 +126,7 @@ export { UseCaseCard, RoadmapItemCard, RoadmapSwimlaneBoard, UseCaseLedgerPage,
124
126
  export { ProvenanceDisclosure, ProvenanceDetail, ProvenanceBadge, ConfidenceReadout, ConfidenceMeter, ConfidenceBar, provenanceStateLabel, confidenceBand, confidenceScore, scoredConfidence, ratedConfidence, type ProvenanceRecord, type ProvenanceState, type ProvenanceSource, type ProvenanceCheck, type ProvenanceCall, type ProvenanceSignoff, type ConfidenceBand, type ConfidenceScore, type ProvenanceConfidence, type ConfidenceReadoutProps, type ConfidenceBarProps, type ConfidenceBarSize, type ProvenanceDisclosureProps, type ProvenanceDetailProps, type ProvenanceDetailTitleAs, type ProvenanceTriggerBadge, type ProvenanceBadgeProps, type ProvenanceBadgeAppearance, type ConfidenceMeterProps, } from "./components/provenance";
125
127
  export { confidenceLevel, CONFIDENCE_LEVEL_LABELS, type ConfidenceLevel } from "./lib/confidence";
126
128
  export { createSimStore, resetSimStores, useSimSettings, setSimSettings, getNetworkSpeed, getDataSource, simulateLatency, SimNetworkError, type SimStore, type SimStoreOptions, type SimSettings, type NetworkSpeed, type DataSource, type SimNamespace, } from "./lib/sim";
127
- export { NodeCanvas, LAYOUT_DEFAULTS, layoutTree, pathToNode, type NodeCanvasProps, type CanvasEdge, type CanvasLayout, type CanvasNode, type CanvasOrientation, type LayoutOptions, type NodeRenderContext, type NodeRenderer, type PlacedNode, } from "./components/node-canvas";
129
+ export { NodeCanvas, LAYOUT_DEFAULTS, edgePath, layoutGraph, neighbourhood, type NodeCanvasProps, type CanvasCycleScope, type CanvasEdge, type CanvasGraph, type CanvasGroup, type CanvasLayout, type CanvasNode, type CanvasOrientation, type CanvasPlacedEdge, type CanvasScope, type LayoutOptions, type NodeRenderContext, type NodeRenderer, type PlacedNode, } from "./components/node-canvas";
128
130
  export { defineRecordType, resetRecordTypes, useRecord, useRecords, useStaleness, write, writeAll, peekRecord, readState, readStaleness, subscribeToKey, subscribeToStaleness, retainKeys, sweepRecords, setRecordStoreTenant, getRecordStoreTenant, flushRecordNotifications, resetRecordStore, recordStoreStats, UNKNOWN_STATE, normalizeTypeName, normalizeTimestamp, recordKey, isRecordRef, type RecordRef, type ReadOptions, type RecordPayload, type RecordState, type RecordTypeOptions, type RecordTypeToken, type StalenessEntry, type WriteAllResult, type WriteOptions, type WriteResult, type WriteSource, type WriteVerdict, } from "./lib/records";
129
131
  export { RealtimeProvider, useRealtime, useRealtimeEpoch, useRealtimeHealth, useRealtimeRegistry, SubscriptionRegistry, normalizeScope, scopeKey, subscriptionKey, readRealtimeEpoch, subscribeToRealtimeEpoch, subscribeToRealtimeEvents, resetRealtimeSignals, type RealtimeChannel, type RealtimeConnectionState, type RealtimeDiagnostic, type RealtimeEvent, type RealtimeHealth, type RealtimeProviderProps, type RealtimeScope, type RealtimeScopeInput, type RealtimeStatus, type RealtimeTransport, type RegistryOptions, type SubscriptionHealth, type SubscriptionLease, type SubscriptionRequest, } from "./lib/realtime";
130
132
  export { useRecordRead, useRecordListRead, useRecordStoreSweep, applyMutationResult, optimisticWrite, beginOptimisticWrite, resetOptimisticWrites, type OptimisticHandle, type OptimisticWriteOptions, type OptimisticWriteSpec, type SlotDescriptor, acquireSlot, appendCreated, emptySlotSnapshot, readSlot, resetSlots, slotKeys, subscribeToSlot, writeSlot, RefetchRunner, registerActiveRead, resetActiveReads, type ActiveRead, type ListFetch, type ListFetchResult, type ListReadOptions, type ListReadResult, type ReadError, type RecordFetch, type RecordReadOptions, type RecordReadResult, type RunnerOptions, type SlotSnapshot, } from "./lib/reads";