@danxbot/ui 12.2.0 → 13.0.1

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.
@@ -76,13 +76,27 @@ interface InlineEditFieldProps {
76
76
  /** Box ramp. Matches `Input`'s names. */
77
77
  size?: "sm" | "md" | "lg";
78
78
  /**
79
- * Edit as a growing multi-line block instead of a single line.
79
+ * How the field wraps and how Enter behaves. Three shapes, not a boolean,
80
+ * because "wraps visually" and "Enter inserts a newline" are independent
81
+ * axes — DX-3388 needed a line that wraps but never contains "\n", which a
82
+ * single `multiline` flag could not express without a second bolted-on
83
+ * prop.
80
84
  *
81
- * THE COMMIT KEY MOVES, because Enter has to insert a newline instead: Enter
82
- * inserts "\n", Cmd/Ctrl+Enter commits, Escape still cancels, blur still
83
- * commits.
85
+ * - `"single"` (default) — no wrap; scrolls horizontally while editing,
86
+ * truncates at rest. Enter commits (Shift+Enter is a no-op — there is
87
+ * nowhere for a second line to go).
88
+ * - `"wrap"` — wraps visually like `"multiline"`, but Enter still commits
89
+ * exactly as `"single"` does. Every typed or pasted newline is normalised
90
+ * to a single space by the same `beforeinput` interception that
91
+ * `"single"` uses to strip them — the committed value can never contain
92
+ * "\n". For a title or summary: readable while long, never a literal
93
+ * line break in something that flows into a list row, Trello, or a
94
+ * commit message.
95
+ * - `"multiline"` — grows as a multi-line block. THE COMMIT KEY MOVES,
96
+ * because Enter has to insert a newline instead: Enter inserts "\n",
97
+ * Cmd/Ctrl+Enter commits, Escape still cancels, blur still commits.
84
98
  */
85
- multiline?: boolean;
99
+ lineMode?: "single" | "wrap" | "multiline";
86
100
  /**
87
101
  * Save WHILE the reader types: commit this many milliseconds after the last
88
102
  * keystroke, with the field still open. Enter (Shift+Enter is the newline in
@@ -33,6 +33,16 @@ export interface InlineMarkdownEditProps extends EditActivation {
33
33
  toolbar?: ReactNode;
34
34
  /** Stores a pasted/dropped/picked image — see `MarkdownEditorProps.uploadImage`. */
35
35
  uploadImage?: ImageUploader;
36
+ /**
37
+ * Fires whenever the open draft's image-upload state changes: `true` while
38
+ * any image in it is uploading, failed, or otherwise unstored; `false` once
39
+ * none are — INCLUDING when the field closes or unmounts with an upload
40
+ * still pending, so a caller wiring this straight into its own navigation
41
+ * guard can never get stuck armed after the field that armed it is gone.
42
+ * See the module comment ("NAVIGATION HOLD WHILE AN UPLOAD IS PENDING") for
43
+ * why this is a callback rather than a router hook.
44
+ */
45
+ onUploadStateChange?: (uploading: boolean) => void;
36
46
  /**
37
47
  * This value cannot be edited at all, AND WHY — `InlineEdit.readOnly`'s
38
48
  * shape and rule: asked to open, the field hands `onEditingChange(false)`
@@ -22,9 +22,9 @@ export interface GraphExplorerProps<Extra = unknown> {
22
22
  onNodeClick?: (node: CanvasNode<Extra>) => void;
23
23
  /** `variant="minimap"` only: the expand affordance's handler. Defaults to an in-place full-screen dialog. */
24
24
  onExpand?: () => void;
25
- /** Along-axis extent of a node that does not need more, in px. @default LAYOUT_DEFAULTS.nodeWidth */
25
+ /** Cross-axis extent of a node that does not need more, in px — the rank's OWN axis (ranks spread along y, DX-3401's vertical default) unless `orientation` is overridden. @default LAYOUT_DEFAULTS.nodeWidth */
26
26
  nodeWidth?: number;
27
- /** Cross-axis extent BEFORE a taller node's own content grows it. @default LAYOUT_DEFAULTS.nodeHeight */
27
+ /** Along-axis extent BEFORE a taller node's own content grows it — the axis ranks advance along. @default LAYOUT_DEFAULTS.nodeHeight */
28
28
  nodeHeight?: number;
29
29
  /** `variant="explorer"` only: height of the viewport. @default "32rem" */
30
30
  height?: string;
@@ -46,5 +46,12 @@ export interface FitOptions extends ScaleBounds {
46
46
  /** @default CAMERA_DEFAULTS.fitMaxScale — never zoom IN past this to fit a small box. */
47
47
  maxScale?: number;
48
48
  }
49
+ /**
50
+ * The smallest box containing every one of `boxes` — how a camera "frame"
51
+ * (e.g. a focused node plus its parent and children, DX-3401) is built from
52
+ * several already-placed boxes without re-deriving a bounding-box formula at
53
+ * every call site. `null` for an empty list — nothing to union.
54
+ */
55
+ export declare function unionBox(boxes: readonly Box[]): Box | null;
49
56
  /** A camera that fits the whole of `box` inside `viewport`, centred, padded. */
50
57
  export declare function fitBox(box: Box, viewport: Size, options?: FitOptions): CameraState;
@@ -1,5 +1,5 @@
1
1
  export { GraphExplorer, type GraphExplorerProps } from "./GraphExplorer";
2
- export { layoutExplorerTree, neighbourhoodTree, edgePath } from "./tree-layout";
3
- export { CAMERA_DEFAULTS, centerOn, clampScale, fitBox, toContentPoint, toScreenPoint, zoomAtPoint } from "./camera-math";
2
+ export { frameBoxFor, layoutExplorerTree, neighbourhoodTree, edgePath } from "./tree-layout";
3
+ export { CAMERA_DEFAULTS, centerOn, clampScale, fitBox, toContentPoint, toScreenPoint, unionBox, zoomAtPoint } from "./camera-math";
4
4
  export type { Box, CameraState, FitOptions, Point, ScaleBounds, Size } from "./camera-math";
5
5
  export type { CrossLink, ExplorerLayout, ExplorerNodeRenderContext, ExplorerNodeRenderer, ExplorerTree, GraphExplorerVariant, TreeNode, } from "./types";
@@ -1,10 +1,19 @@
1
1
  import { edgePath } from "../node-canvas/layout";
2
2
  import type { LayoutOptions } from "../node-canvas/types";
3
+ import { type Box } from "./camera-math";
3
4
  import type { CrossLink, ExplorerLayout, ExplorerTree } from "./types";
4
5
  /** Re-exported for a consumer drawing `edges`/`crossLinks` into an SVG. */
5
6
  export { edgePath };
6
7
  /**
7
- * Lay out a tree, left to right, from a parent backbone alone.
8
+ * Lay out a tree, top to bottom, from a parent backbone alone. Ranked
9
+ * vertically (dagre `rankdir: "TB"`) so SIBLINGS spread horizontally within a
10
+ * rank — the sprawl DX-3401's card asks for. A tree this shallow-but-wide (a
11
+ * handful of levels, many children per level) stacked its siblings into one
12
+ * tall column under the previous `"horizontal"` (`LR`) default, which is
13
+ * exactly backwards for that shape: `"horizontal"` ranks left to right, so
14
+ * within-rank siblings stack along the CROSS axis (vertically) instead.
15
+ * `options.orientation` still overrides this default for a caller with the
16
+ * opposite shape (deep and narrow).
8
17
  *
9
18
  * `crossLinks` are resolved against the ALREADY-FIXED node positions and
10
19
  * returned separately (`ExplorerLayout.crossLinks`) — they never reach
@@ -15,6 +24,18 @@ export { edgePath };
15
24
  * uses for a group boundary.
16
25
  */
17
26
  export declare function layoutExplorerTree<Extra>(tree: ExplorerTree<Extra>, crossLinks?: readonly CrossLink[], options?: LayoutOptions): ExplorerLayout<Extra>;
27
+ /**
28
+ * The box a camera should keep in frame around `focusId`: the focused node
29
+ * itself, its parent (one rank up) and every one of its direct children (one
30
+ * rank down) — "the minimap always frames the current node, its parent and
31
+ * its children" (DX-3401's fix for the minimap shrinking an entire tree to
32
+ * specks, and for the full-screen explorer's initial view not opening
33
+ * focused). Computed from an ALREADY-LAID-OUT tree, so it can never disagree
34
+ * with what is actually on screen the way fitting a separately-computed
35
+ * bounding box could. `null` when `focusId` isn't laid out at all — nothing
36
+ * to frame (a stale id, or data still loading).
37
+ */
38
+ export declare function frameBoxFor<Extra>(layout: ExplorerLayout<Extra>, focusId: string): Box | null;
18
39
  /**
19
40
  * The subtree a minimap draws: `focusId`'s ancestor chain up to its root,
20
41
  * `focusId`'s immediate siblings, and `focusId`'s immediate children — "the
@@ -6,6 +6,29 @@ export interface CameraClickEvent {
6
6
  export interface UseCameraOptions {
7
7
  /** Content-space bounding box of the whole drawing. Drives `fitAll`. */
8
8
  contentBox: Box;
9
+ /**
10
+ * The box the camera AUTOMATICALLY keeps in frame — on mount, and again
11
+ * whenever `frameKey` changes (DX-3401). Typically the focused node plus
12
+ * its parent and children, so the camera never zooms out further than the
13
+ * relevant neighbourhood just because the whole tree is big. `null`/
14
+ * `undefined` falls back to `contentBox` (fit the whole drawing) — the
15
+ * ordinary case when there is no focus yet.
16
+ */
17
+ frameBox?: Box | null;
18
+ /**
19
+ * A caller-chosen, STABLE value (e.g. the focused node's id) that changing
20
+ * re-runs the automatic frame fit even when `resetKey` hasn't changed —
21
+ * `frameBox` itself is deliberately NOT a dependency (see the frame-fit
22
+ * effect below for why), so this is the only reliable "the frame target
23
+ * moved" signal for a caller whose `resetKey` tracks something else (e.g.
24
+ * the explorer's own `resetKey` is the tree, not the focused node).
25
+ */
26
+ frameKey?: unknown;
27
+ /** Padding around the auto-fit target (`frameBox ?? contentBox`) — distinct
28
+ from `fitAll`'s always-`CAMERA_DEFAULTS.fitPadding`, since a compact
29
+ minimap strip wants much less breathing room than the full explorer.
30
+ @default CAMERA_DEFAULTS.fitPadding */
31
+ framePadding?: number;
9
32
  /** A node's content-space box, for `flyTo`. `null` for an id not laid out. */
10
33
  boxOf: (id: string) => Box | null;
11
34
  /** A press that released without travelling past `CLICK_DISTANCE`. */
@@ -129,7 +129,7 @@ export { ProvenanceDisclosure, ProvenanceDetail, ProvenanceBadge, ConfidenceRead
129
129
  export { confidenceLevel, CONFIDENCE_LEVEL_LABELS, type ConfidenceLevel } from "./lib/confidence";
130
130
  export { createSimStore, resetSimStores, useSimSettings, setSimSettings, getNetworkSpeed, getDataSource, simulateLatency, SimNetworkError, type SimStore, type SimStoreOptions, type SimSettings, type NetworkSpeed, type DataSource, type SimNamespace, } from "./lib/sim";
131
131
  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";
132
- export { GraphExplorer, layoutExplorerTree, neighbourhoodTree, CAMERA_DEFAULTS, centerOn, clampScale, fitBox, toContentPoint, toScreenPoint, zoomAtPoint, type GraphExplorerProps, type Box, type CameraState, type FitOptions, type Point, type ScaleBounds, type Size, type CrossLink, type ExplorerLayout, type ExplorerNodeRenderContext, type ExplorerNodeRenderer, type ExplorerTree, type GraphExplorerVariant, type TreeNode as ExplorerTreeNode, } from "./components/graph-explorer";
132
+ export { GraphExplorer, frameBoxFor, layoutExplorerTree, neighbourhoodTree, CAMERA_DEFAULTS, centerOn, clampScale, fitBox, toContentPoint, toScreenPoint, unionBox, zoomAtPoint, type GraphExplorerProps, type Box, type CameraState, type FitOptions, type Point, type ScaleBounds, type Size, type CrossLink, type ExplorerLayout, type ExplorerNodeRenderContext, type ExplorerNodeRenderer, type ExplorerTree, type GraphExplorerVariant, type TreeNode as ExplorerTreeNode, } from "./components/graph-explorer";
133
133
  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";
134
134
  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";
135
135
  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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danxbot/ui",
3
- "version": "12.2.0",
3
+ "version": "13.0.1",
4
4
  "type": "module",
5
5
  "description": "Danxbot — a domain-agnostic React design system with motion as a first-class primitive.",
6
6
  "license": "MIT",