@danxbot/ui 13.0.1 → 13.0.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.
@@ -26,10 +26,17 @@ export interface GraphExplorerProps<Extra = unknown> {
26
26
  nodeWidth?: number;
27
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
+ /** Floor on the automatic frame-fit's scale (mount / focus change, both
30
+ `variant`s) — below this, the camera holds the floor and centres on the
31
+ focused node instead of shrinking the whole frame to fit; `fitAll`
32
+ stays unaffected (DX-3404). See `MIN_READABLE_TITLE_SCALE`'s own
33
+ comment for the derivation; override when `renderNode` uses a
34
+ different title font size. @default MIN_READABLE_TITLE_SCALE */
35
+ minReadableScale?: number;
29
36
  /** `variant="explorer"` only: height of the viewport. @default "32rem" */
30
37
  height?: string;
31
38
  /** Nothing pans, zooms or clicks. Reading is unaffected. */
32
39
  disabled?: boolean;
33
40
  className?: string;
34
41
  }
35
- export declare function GraphExplorer<Extra = unknown>({ tree, crossLinks, label, renderNode, variant, focusId: controlledFocusId, defaultFocusId, onFocusIdChange, visitedIds, onNodeClick, onExpand, nodeWidth, nodeHeight, height, disabled, className, }: GraphExplorerProps<Extra>): import("react").JSX.Element;
42
+ export declare function GraphExplorer<Extra = unknown>({ tree, crossLinks, label, renderNode, variant, focusId: controlledFocusId, defaultFocusId, onFocusIdChange, visitedIds, onNodeClick, onExpand, nodeWidth, nodeHeight, height, minReadableScale, disabled, className, }: GraphExplorerProps<Extra>): import("react").JSX.Element;
@@ -55,3 +55,55 @@ export interface FitOptions extends ScaleBounds {
55
55
  export declare function unionBox(boxes: readonly Box[]): Box | null;
56
56
  /** A camera that fits the whole of `box` inside `viewport`, centred, padded. */
57
57
  export declare function fitBox(box: Box, viewport: Size, options?: FitOptions): CameraState;
58
+ /**
59
+ * Which viewport edges have `box`'s content extending past them at `camera`'s
60
+ * current position/scale — the input `fitBoxWithFloor` (below) needs to
61
+ * report an overflow cue, factored out because a caller re-deriving it from
62
+ * raw camera state would be a second, easily-drifting copy of this same
63
+ * arithmetic (DX-3404). A small epsilon absorbs floating-point noise at an
64
+ * edge that lands (by construction) exactly on the viewport boundary.
65
+ */
66
+ export interface FrameOverflow {
67
+ left: boolean;
68
+ right: boolean;
69
+ top: boolean;
70
+ bottom: boolean;
71
+ }
72
+ export interface FitWithFloorOptions extends FitOptions {
73
+ /**
74
+ * A SECOND, stricter lower bound on top of `minScale`/`fitBounds` (DX-3404)
75
+ * — readability-driven ("titles at least ~13px on screen"), not
76
+ * graph-legibility-driven the way `CAMERA_DEFAULTS.minScale` (0.15) is.
77
+ * `undefined` (the default) makes this behave exactly like `fitBox` — the
78
+ * floor is opt-in per call site, so `fitAll` (which never passes it) stays
79
+ * a manual control that may zoom out past readable text on purpose.
80
+ * When the ordinary fit-to-`box` scale would land BELOW this floor, the
81
+ * camera clamps to the floor and centres on `focusBox` instead of `box` —
82
+ * showing the focused target at a readable size and reaching the rest of
83
+ * `box` by panning, rather than shrinking everything to fit.
84
+ */
85
+ minReadableScale?: number;
86
+ /**
87
+ * What to centre on once `minReadableScale` clamps the scale — normally
88
+ * the focused node's own (small) box, not the whole (wide) `box` being
89
+ * framed. Defaults to `box` itself (so an omitted `focusBox` degrades to
90
+ * "clamp the scale but still centre the frame", never a crash).
91
+ */
92
+ focusBox?: Box;
93
+ }
94
+ export interface FitWithFloorResult {
95
+ camera: CameraState;
96
+ /** Which sides of `box` extend past the viewport at the returned camera's
97
+ scale/position — all `false` whenever the floor didn't clamp, since an
98
+ ordinary `fitBox` result always contains the whole of `box` by
99
+ construction. */
100
+ overflow: FrameOverflow;
101
+ }
102
+ /**
103
+ * `fitBox`, with an optional readable-text floor (DX-3404) — see
104
+ * `FitWithFloorOptions.minReadableScale`'s own doc for the behaviour. Used
105
+ * ONLY by `use-camera.ts`'s automatic frame-fit effect; `fitAll` keeps
106
+ * calling `fitBox` directly so the manual "fit everything" control is never
107
+ * affected by this floor (AC #2).
108
+ */
109
+ export declare function fitBoxWithFloor(box: Box, viewport: Size, options?: FitWithFloorOptions): FitWithFloorResult;
@@ -1,5 +1,5 @@
1
1
  export { GraphExplorer, type GraphExplorerProps } from "./GraphExplorer";
2
2
  export { frameBoxFor, layoutExplorerTree, neighbourhoodTree, edgePath } from "./tree-layout";
3
- export { CAMERA_DEFAULTS, centerOn, clampScale, fitBox, toContentPoint, toScreenPoint, unionBox, zoomAtPoint } from "./camera-math";
4
- export type { Box, CameraState, FitOptions, Point, ScaleBounds, Size } from "./camera-math";
3
+ export { CAMERA_DEFAULTS, centerOn, clampScale, fitBox, fitBoxWithFloor, toContentPoint, toScreenPoint, unionBox, zoomAtPoint, } from "./camera-math";
4
+ export type { Box, CameraState, FitOptions, FitWithFloorOptions, FitWithFloorResult, FrameOverflow, Point, ScaleBounds, Size, } from "./camera-math";
5
5
  export type { CrossLink, ExplorerLayout, ExplorerNodeRenderContext, ExplorerNodeRenderer, ExplorerTree, GraphExplorerVariant, TreeNode, } from "./types";
@@ -26,14 +26,31 @@ export { edgePath };
26
26
  export declare function layoutExplorerTree<Extra>(tree: ExplorerTree<Extra>, crossLinks?: readonly CrossLink[], options?: LayoutOptions): ExplorerLayout<Extra>;
27
27
  /**
28
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
29
+ * itself and every one of its direct children (one rank down), with the
30
+ * frame's vertical extent alone stretched to reach its parent's rank (one
31
+ * rank up) — "the minimap always frames the current node, its parent and its
32
+ * children" (DX-3401's fix for the minimap shrinking an entire tree to
32
33
  * specks, and for the full-screen explorer's initial view not opening
33
34
  * focused). Computed from an ALREADY-LAID-OUT tree, so it can never disagree
34
35
  * with what is actually on screen the way fitting a separately-computed
35
36
  * bounding box could. `null` when `focusId` isn't laid out at all — nothing
36
37
  * to frame (a stale id, or data still loading).
38
+ *
39
+ * THE PARENT'S X/WIDTH ARE DELIBERATELY NEVER UNIONED IN (DX-3401 follow-up).
40
+ * An earlier version unioned the parent's whole box in, same as a child's.
41
+ * dagre centres a parent horizontally over the barycenter of ALL its
42
+ * children, not just the focused one — so a parent with many other children
43
+ * spread wide (exactly the "one system, ~15 areas" shape this card exists to
44
+ * fix) can sit far to one side of the union of `focus` + its own children.
45
+ * Pulling that parent's x/width into the frame then stretches it to cover the
46
+ * real horizontal gap between the parent and this one child, reproducing a
47
+ * milder version of the original "whole tree crushed to a strip" bug for
48
+ * this specific shape (confirmed live against the Ringlet demo: a `frameBox`
49
+ * nearly as wide as the entire tree's `contentBox`). Only the parent's Y
50
+ * range is folded in, so the frame reaches up far enough for the connecting
51
+ * edge into the parent to be visible — a parent off to the side is reached by
52
+ * panning, the same trade-off this function already makes for siblings
53
+ * (drawn by `neighbourhoodTree` but never pulled into the frame's own box).
37
54
  */
38
55
  export declare function frameBoxFor<Extra>(layout: ExplorerLayout<Extra>, focusId: string): Box | null;
39
56
  /**
@@ -1,4 +1,4 @@
1
- import { type Box, type CameraState } from "./camera-math";
1
+ import { type Box, type CameraState, type FrameOverflow } from "./camera-math";
2
2
  export interface CameraClickEvent {
3
3
  /** Client coordinates, for the caller to hit-test against its own elements. */
4
4
  target: HTMLElement;
@@ -29,6 +29,27 @@ export interface UseCameraOptions {
29
29
  minimap strip wants much less breathing room than the full explorer.
30
30
  @default CAMERA_DEFAULTS.fitPadding */
31
31
  framePadding?: number;
32
+ /**
33
+ * A THIRD, stricter lower bound layered on top of `fitBounds`'s own
34
+ * `minScale`, applied ONLY to the automatic frame-fit effect below — NEVER
35
+ * to `fitAll` (which keeps calling `fitBox` directly with `fitBounds`) and
36
+ * NEVER to `zoomBounds` (wheel/pinch/+/-, which stay `CAMERA_DEFAULTS
37
+ * .minScale`..`maxScale`) (DX-3404). See `camera-math.ts`'s
38
+ * `FitWithFloorOptions.minReadableScale` for the full behaviour: below
39
+ * this scale, the automatic fit clamps here and centres on `focusBox`
40
+ * instead of shrinking `frameBox ?? contentBox` to fit — the rest is
41
+ * reached by panning. `undefined` (the default) reproduces the pre-DX-3404
42
+ * behaviour exactly (plain `fitBox`, no floor).
43
+ */
44
+ minReadableScale?: number;
45
+ /**
46
+ * What the automatic fit centres on once `minReadableScale` clamps the
47
+ * scale — normally the focused node's own (small) box, distinct from the
48
+ * wider `frameBox` (focus + parent + children) being framed. Defaults to
49
+ * `frameBox ?? contentBox` when omitted, matching `fitBoxWithFloor`'s own
50
+ * default. Ignored when `minReadableScale` is unset.
51
+ */
52
+ focusBox?: Box | null;
32
53
  /** A node's content-space box, for `flyTo`. `null` for an id not laid out. */
33
54
  boxOf: (id: string) => Box | null;
34
55
  /** A press that released without travelling past `CLICK_DISTANCE`. */
@@ -44,6 +65,14 @@ export interface UseCameraResult {
44
65
  panning: boolean;
45
66
  /** True for the duration of a programmatic move — see the module doc. */
46
67
  animating: boolean;
68
+ /**
69
+ * Which sides of the automatic frame target (`frameBox ?? contentBox`)
70
+ * extend past the viewport at the CURRENT camera — all `false` unless
71
+ * `minReadableScale` actually clamped the last automatic fit (DX-3404).
72
+ * The one source of truth for an "edge cue" overlay, so a caller never
73
+ * re-derives this from raw `camera` state itself.
74
+ */
75
+ frameOverflow: FrameOverflow;
47
76
  bindViewport: (node: HTMLDivElement | null) => void;
48
77
  zoomIn: () => void;
49
78
  zoomOut: () => void;
@@ -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, 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";
132
+ export { GraphExplorer, frameBoxFor, layoutExplorerTree, neighbourhoodTree, CAMERA_DEFAULTS, centerOn, clampScale, fitBox, fitBoxWithFloor, toContentPoint, toScreenPoint, unionBox, zoomAtPoint, type GraphExplorerProps, type Box, type CameraState, type FitOptions, type FitWithFloorOptions, type FitWithFloorResult, type FrameOverflow, 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": "13.0.1",
3
+ "version": "13.0.3",
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",