@elaraai/east-ui-components 1.0.12 → 1.0.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.
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ /**
6
+ * Pure camera maths for the Schematic renderer — the single source of truth for
7
+ * the pan/zoom transform, the interaction-mode machine, viewport culling
8
+ * geometry, and the `requestAnimationFrame` coalescer. Kept free of React,
9
+ * Chakra, and Canvas2D so every piece is unit-testable in a `node` environment
10
+ * and so the **same** world→screen projection is shared by the Canvas2D painter
11
+ * ({@link ../paint}) and the DOM card layer — the two surfaces can never use a
12
+ * different transform and drift apart (issue #57, P1).
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ /** The pan/zoom state: `screen = world × fit × zoom + t`. */
17
+ export interface Viewport {
18
+ zoom: number;
19
+ tx: number;
20
+ ty: number;
21
+ }
22
+ /** The identity camera — fully zoomed out, no pan. */
23
+ export declare const IDENTITY: Viewport;
24
+ /**
25
+ * The transform in CSS pixels with `ppu` (pixels-per-world-unit) already
26
+ * folded in: `screen = world × ppu + t`. Mirrors {@link ../paint!PaintCamera}.
27
+ */
28
+ export interface ScreenCamera {
29
+ ppu: number;
30
+ tx: number;
31
+ ty: number;
32
+ }
33
+ /** An axis-aligned bounding box (world or screen space). */
34
+ export interface Bbox {
35
+ minX: number;
36
+ minY: number;
37
+ maxX: number;
38
+ maxY: number;
39
+ }
40
+ /**
41
+ * Project a world point to screen space (`x × ppu + tx`, `y × ppu + ty`). The
42
+ * one projector shared by the painter and the card layer; the DOM cards apply
43
+ * the identical arithmetic in CSS via {@link cardTranslateCss}.
44
+ *
45
+ * @param x - world x
46
+ * @param y - world y
47
+ * @param cam - the screen camera (ppu + translation)
48
+ * @returns the screen-space point
49
+ */
50
+ export declare function project(x: number, y: number, cam: ScreenCamera): {
51
+ sx: number;
52
+ sy: number;
53
+ };
54
+ /**
55
+ * The CSS `translate(...)` a DOM card uses to sit on its world anchor, reading
56
+ * the live camera from the `--cam-ppu` / `--cam-tx` / `--cam-ty` custom
57
+ * properties set on the card layer. The arithmetic is identical to
58
+ * {@link project} (`ppu × coord + t`), so card and canvas-drawn footprint
59
+ * resolve to the same screen point. The string is **camera-independent** (it
60
+ * only embeds the item's world `x`/`y`), so a camera change is a single set of
61
+ * custom-property writes on the parent — no per-card React re-layout.
62
+ *
63
+ * @param x - world x of the item anchor
64
+ * @param y - world y of the item anchor
65
+ * @returns a CSS `translate()` expression referencing the `--cam-*` vars
66
+ */
67
+ export declare function cardTranslateCss(x: number, y: number): string;
68
+ /**
69
+ * The CSS length a DOM card uses for an explicit world-space `width`, reading
70
+ * the live `--cam-ppu`. Expressing the width through the var (not a
71
+ * render-time `width × ppu`) keeps an explicit-width card's box sized from the
72
+ * **same** live ppu the canvas footprint is drawn from, so box and footprint
73
+ * never diverge in size during a zoom (issue #57, Phase 2 invariant 6).
74
+ *
75
+ * @param width - the item's world-space width
76
+ * @returns a CSS `calc()` length referencing `--cam-ppu`
77
+ */
78
+ export declare function cardWidthCss(width: number): string;
79
+ /**
80
+ * The world-space rectangle currently visible — the inverse projection of the
81
+ * canvas corners. Used to cull off-screen zones / links (issue #57, P6).
82
+ *
83
+ * @param cam - the screen camera
84
+ * @param width - canvas width in CSS px
85
+ * @param height - canvas height in CSS px
86
+ * @returns the visible world bbox
87
+ */
88
+ export declare function viewportWorldBbox(cam: ScreenCamera, width: number, height: number): Bbox;
89
+ /**
90
+ * Whether two axis-aligned bounding boxes overlap (inclusive edges). Off-screen
91
+ * shapes whose bbox misses the viewport bbox are culled.
92
+ *
93
+ * @param a - first bbox
94
+ * @param b - second bbox
95
+ * @returns `true` when the boxes touch or overlap
96
+ */
97
+ export declare function bboxOverlaps(a: Bbox, b: Bbox): boolean;
98
+ /**
99
+ * The world-space AABB over a set of points. For a link this is taken over
100
+ * **all** anchors (endpoints + waypoints), so a trunk whose two ends sit
101
+ * off-screen but whose segment crosses the viewport is not wrongly culled
102
+ * (issue #57, 1e).
103
+ *
104
+ * @param pts - the points to bound (world coords)
105
+ * @returns the AABB, or `null` when the list is empty
106
+ */
107
+ export declare function pointsBbox(pts: readonly {
108
+ x: number;
109
+ y: number;
110
+ }[]): Bbox | null;
111
+ /**
112
+ * Apply a multiplicative zoom about a screen-space focal point, keeping that
113
+ * point fixed on screen (cursor-anchored wheel zoom, centre-anchored button
114
+ * zoom). Clamps to `[min, max]`; snapping to `min === 1` returns
115
+ * {@link IDENTITY} so a full zoom-out also recentres.
116
+ *
117
+ * @param prev - the current camera
118
+ * @param factor - multiply `zoom` by this
119
+ * @param px - screen-x to keep fixed
120
+ * @param py - screen-y to keep fixed
121
+ * @param min - minimum zoom
122
+ * @param max - maximum zoom
123
+ * @returns the next camera
124
+ */
125
+ export declare function zoomAbout(prev: Viewport, factor: number, px: number, py: number, min: number, max: number): Viewport;
126
+ /** The interaction modes the camera can be in at any instant. */
127
+ export type CameraMode = "idle" | "panning" | "wheeling" | "flying";
128
+ /** The inputs that drive {@link nextMode} transitions. */
129
+ export type CameraEvent = "pointerDown" | "wheel" | "zoom" | "flyStart" | "pointerUp" | "flyEnd" | "cancel";
130
+ /**
131
+ * The camera interaction-mode transition. Entering an input mode
132
+ * (`pointerDown` / `wheel` / `zoom`) supersedes a running fly — see
133
+ * {@link cancelsFly} for the companion side-effect predicate — so a user
134
+ * gesture always wins over an animation rather than racing it (issue #57, P4 /
135
+ * Phase 2 invariant 5).
136
+ *
137
+ * @param mode - the current mode
138
+ * @param event - the input event
139
+ * @returns the next mode
140
+ */
141
+ export declare function nextMode(mode: CameraMode, event: CameraEvent): CameraMode;
142
+ /**
143
+ * Whether processing `event` in `mode` must cancel a running fly animation.
144
+ * Pairs with {@link nextMode}: the reducer names the next mode, this names the
145
+ * side-effect (cancel the in-flight `flyTo` rAF) the transition implies. Every
146
+ * event that moves the machine OUT of `flying` (a user gesture, an explicit
147
+ * `cancel`/`reset`) must cancel the rAF, so the mode and the live `animRef` can
148
+ * never disagree — `flyEnd` is the sole exception (the rAF has already ended).
149
+ *
150
+ * @param mode - the current mode
151
+ * @param event - the input event
152
+ * @returns `true` when a fly animation is in progress and is being superseded
153
+ */
154
+ export declare function cancelsFly(mode: CameraMode, event: CameraEvent): boolean;
155
+ /** A frame-coalesced render scheduler — see {@link makeRafCoalescer}. */
156
+ export interface RafCoalescer {
157
+ /** Schedule `run` for the next frame; repeat calls before it fires are no-ops. */
158
+ request(run: () => void): void;
159
+ /** Cancel a pending frame (teardown). */
160
+ cancel(): void;
161
+ }
162
+ /**
163
+ * Coalesce many `request` calls within a single frame down to **one** scheduled
164
+ * callback. Pointer-move / wheel handlers mutate the live camera synchronously
165
+ * and call `request` on every event; this guarantees at most one paint +
166
+ * snapshot per frame, keeping the hot pan path off the per-event path (issue
167
+ * #57, Phase 2 invariant 2). Injecting `schedule` / `cancel` (rather than
168
+ * closing over `requestAnimationFrame`) keeps the coalescer unit-testable.
169
+ *
170
+ * @param schedule - schedules a callback for the next frame, returns a handle
171
+ * @param cancel - cancels a scheduled callback by handle
172
+ * @returns a {@link RafCoalescer}
173
+ */
174
+ export declare function makeRafCoalescer(schedule: (cb: () => void) => number, cancel: (handle: number) => void): RafCoalescer;
175
+ //# sourceMappingURL=camera.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../../../src/collections/schematic/camera.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;GAUG;AAEH,6DAA6D;AAC7D,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACd;AAED,sDAAsD;AACtD,eAAO,MAAM,QAAQ,EAAE,QAAoC,CAAC;AAE5D;;;GAGG;AACH,MAAM,WAAW,YAAY;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACd;AAED,4DAA4D;AAC5D,MAAM,WAAW,IAAI;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAE3F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAOxF;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,OAAO,CAEtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,SAAS;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,IAAI,GAAG,IAAI,CAUhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAKpH;AAED,iEAAiE;AACjE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEpE,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE5G;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,UAAU,CAUzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAGxE;AAED,yEAAyE;AACzE,MAAM,WAAW,YAAY;IACzB,kFAAkF;IAClF,OAAO,CAAC,GAAG,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC/B,yCAAyC;IACzC,MAAM,IAAI,IAAI,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,MAAM,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,YAAY,CAiBrH"}
@@ -6,6 +6,8 @@ export type SchematicValue = ValueTypeOf<typeof Schematic.Types.Schematic>;
6
6
  export type SchematicItemValue = ValueTypeOf<typeof Schematic.Types.Item>;
7
7
  /** East Schematic zone value type. */
8
8
  export type SchematicZoneValue = ValueTypeOf<typeof Schematic.Types.Zone>;
9
+ /** East Schematic shape-geometry value type (`rect` / `circle` / `polyline` / `polygon`). */
10
+ export type SchematicGeometryValue = ValueTypeOf<typeof Schematic.Types.Geometry>;
9
11
  export interface EastChakraSchematicProps {
10
12
  value: SchematicValue;
11
13
  storageKey: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/schematic/index.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAQtD,iCAAiC;AACjC,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,OAAO,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;AAE3E,sCAAsC;AACtC,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,OAAO,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAE1E,sCAAsC;AACtC,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,OAAO,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAE1E,MAAM,WAAW,wBAAwB;IACrC,KAAK,EAAE,cAAc,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACtB;AA6LD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,gEAolBiE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/collections/schematic/index.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAetD,iCAAiC;AACjC,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,OAAO,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;AAE3E,sCAAsC;AACtC,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,OAAO,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAE1E,sCAAsC;AACtC,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,OAAO,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAE1E,6FAA6F;AAC7F,MAAM,MAAM,sBAAsB,GAAG,WAAW,CAAC,OAAO,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;AAElF,MAAM,WAAW,wBAAwB;IACrC,KAAK,EAAE,cAAc,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACtB;AAgKD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,gEA4zBiE,CAAC"}
@@ -0,0 +1,174 @@
1
+ import { ValueTypeOf } from '@elaraai/east';
2
+ import { Schematic } from '@elaraai/east-ui/internal';
3
+ import { ScreenCamera } from './camera';
4
+ type SchematicValue = ValueTypeOf<typeof Schematic.Types.Schematic>;
5
+ type SchematicItemValue = ValueTypeOf<typeof Schematic.Types.Item>;
6
+ type Pt = {
7
+ x: number;
8
+ y: number;
9
+ };
10
+ /** Per-item LOD tier (mirrors the React layer). */
11
+ export type LodTier = "card" | "label" | "dot";
12
+ /** An `[r, g, b]` triple in 0–255. */
13
+ export type RGB = [number, number, number];
14
+ /**
15
+ * Theme-resolved colours the paint layer needs. The React layer resolves these
16
+ * from Chakra semantic tokens (CSS custom properties) once per theme; the paint
17
+ * layer is colour-system-agnostic and just consumes RGB.
18
+ */
19
+ export interface SchematicPalette {
20
+ /** Brand teal (links, `info` status). */
21
+ brand600: RGB;
22
+ brand500: RGB;
23
+ /** Foreground ink (`ink` tone). */
24
+ fg: RGB;
25
+ fgMuted: RGB;
26
+ fgSubtle: RGB;
27
+ /** Zone `muted` stroke. */
28
+ borderStrong: RGB;
29
+ borderSubtle: RGB;
30
+ /** Card / pin background, and the eyebrow-label halo. */
31
+ bgSurface: RGB;
32
+ bgPanel: RGB;
33
+ /** Status tones. */
34
+ statusOk: RGB;
35
+ statusWarn: RGB;
36
+ statusBad: RGB;
37
+ /** Dot ring. */
38
+ white: RGB;
39
+ }
40
+ /** The pan/zoom transform in CSS pixels: `screen = world × ppu + t`. */
41
+ export interface PaintCamera {
42
+ ppu: number;
43
+ tx: number;
44
+ ty: number;
45
+ }
46
+ /** Everything {@link paintSchematic} needs for one frame. */
47
+ export interface PaintInput {
48
+ ctx: CanvasRenderingContext2D;
49
+ value: SchematicValue;
50
+ cam: PaintCamera;
51
+ /** Canvas size in CSS px (the ctx is pre-scaled by devicePixelRatio). */
52
+ width: number;
53
+ height: number;
54
+ /** Viewport-culled items (the React layer's `visibleItems`). */
55
+ visibleItems: readonly SchematicItemValue[];
56
+ /** Per-item LOD tier after declutter. */
57
+ tiers: ReadonlyMap<string, LodTier>;
58
+ selected: string | null;
59
+ /** Item key → world centre, for link endpoints. */
60
+ centers: ReadonlyMap<string, Pt>;
61
+ palette: SchematicPalette;
62
+ }
63
+ /** Centre / radius / sweep of a DXF bulge arc, ready for `CanvasRenderingContext2D.arc`. */
64
+ export interface BulgeArc {
65
+ cx: number;
66
+ cy: number;
67
+ radius: number;
68
+ startAngle: number;
69
+ endAngle: number;
70
+ anticlockwise: boolean;
71
+ }
72
+ /**
73
+ * Pure geometry of the circular arc a DXF `bulge` encodes for the edge
74
+ * `p1`→`p2` (`bulge = tan(includedAngle / 4)`; sign = turn direction). Returns
75
+ * `null` for a straight or degenerate edge (`|bulge|` ~ 0, or a zero-length
76
+ * chord). Screen space: the caller has already transformed the vertices, and
77
+ * the uniform camera scale preserves the bulge (it is the tangent of an angle).
78
+ * Exported so the arc maths is unit-testable without a Canvas2D context.
79
+ */
80
+ export declare function arcFromBulge(p1: Pt, p2: Pt, bulge: number): BulgeArc | null;
81
+ /** Font the labelled-pin marker is drawn with; the pick must measure label
82
+ * widths with the SAME font so its hitbox matches the drawn pill. */
83
+ export declare const MARKER_LABEL_FONT = "600 10px ui-monospace, \"SF Mono\", Menlo, monospace";
84
+ /** Drawn radius of a `dot`-tier marker, in screen px. */
85
+ export declare const MARKER_DOT_RADIUS = 5;
86
+ /** Hit radius of a `dot`-tier marker, in screen px — comfortably over the
87
+ * drawn dot (5) and its selected ring (7.5) for touch slop (issue #57, P11). */
88
+ export declare const MARKER_DOT_HIT_RADIUS = 10;
89
+ /** The screen-space hitbox of a non-card LOD marker — a circle for a `dot`, the
90
+ * rounded-pill rect for a `label`. */
91
+ export type MarkerHitbox = {
92
+ kind: "circle";
93
+ cx: number;
94
+ cy: number;
95
+ r: number;
96
+ } | {
97
+ kind: "rect";
98
+ left: number;
99
+ top: number;
100
+ w: number;
101
+ h: number;
102
+ };
103
+ /**
104
+ * The screen-space hitbox of an item's LOD marker at `tier`. The single source
105
+ * of truth used by **both** {@link paintSchematic} (to draw the pill) and the
106
+ * React layer's pick (to hit-test), so the clickable area always equals the
107
+ * drawn area and the two cannot silently drift (issue #57, P11). A `dot` is a
108
+ * circle of {@link MARKER_DOT_HIT_RADIUS}; a `label` is the rounded pill whose
109
+ * width is `pad + dot + gap + textWidth + pad` — the text measured by `measure`
110
+ * (the caller sets the context font to {@link MARKER_LABEL_FONT} first).
111
+ *
112
+ * @param item - the item's world anchor and label
113
+ * @param tier - the LOD tier (`dot` or `label`; `card` markers are DOM)
114
+ * @param cam - the screen camera
115
+ * @param measure - measures a string's width in px under the pin font
116
+ * @returns the marker's screen-space hitbox
117
+ */
118
+ export declare function markerHitbox(item: {
119
+ x: number;
120
+ y: number;
121
+ label: string;
122
+ }, tier: "dot" | "label", cam: ScreenCamera, measure: (text: string) => number): MarkerHitbox;
123
+ /**
124
+ * Whether the screen point `(sx, sy)` falls inside `box` (see
125
+ * {@link markerHitbox}).
126
+ *
127
+ * @param box - a marker hitbox
128
+ * @param sx - screen x
129
+ * @param sy - screen y
130
+ * @returns `true` when the point is inside the hitbox
131
+ */
132
+ export declare function markerHit(box: MarkerHitbox, sx: number, sy: number): boolean;
133
+ /** Clamp a hatch `spacing` to a positive, finite minimum so the line sweep
134
+ * always advances and terminates — a `0` / negative / NaN spacing would
135
+ * otherwise hard-hang or stall the render thread (issue #57, P3). */
136
+ export declare function hatchSpacing(rawSpacing: number): number;
137
+ /**
138
+ * The bounded offset range `[oStart, oEnd)` for the diagonal hatch sweep of a
139
+ * zone whose screen rect is `(x, y, w, h)` on an `width × height` canvas, for a
140
+ * line family of direction `(dx, dy)` spaced `spacing` apart, with `diag` the
141
+ * sweep half-length. The full sweep is `-diag … diag`; this narrows it to the
142
+ * offsets whose line can reach the on-screen portion of the zone, so the step
143
+ * count is O(visible area / spacing) not O(zone area / spacing) (issue #57,
144
+ * 1e). `oStart` is snapped onto the SAME `{ -diag + k·spacing }` phase grid the
145
+ * full sweep uses, so the bounded sweep draws the identical lines — fewer of
146
+ * them, never phase-shifted. A horizontal family (`dy ≈ 0`, so the line normal
147
+ * has no x-component) can't be solved this way and falls back to the full
148
+ * `-diag … diag` range: fully off-screen zones are already culled upstream, so
149
+ * the only cost case is an on-screen zone far taller than the viewport, which
150
+ * then pays the full-height sweep (it still terminates — `spacing ≥ 1`; this
151
+ * degenerate orientation renders as collinear lines and is rarely authored).
152
+ *
153
+ * @returns the inclusive-exclusive offset range to sweep
154
+ */
155
+ export declare function hatchSweepBounds(x: number, y: number, w: number, h: number, width: number, height: number, dx: number, dy: number, diag: number, spacing: number): {
156
+ oStart: number;
157
+ oEnd: number;
158
+ };
159
+ /**
160
+ * An upper bound on the number of hatch lines swept across an `w × h` (screen
161
+ * px) region at `rawSpacing`. Finite for any input because {@link hatchSpacing}
162
+ * floors the spacing at 1 (issue #57, P3); the painter further bounds the sweep
163
+ * to the on-screen portion of the zone (1e).
164
+ *
165
+ * @param w - region width in screen px
166
+ * @param h - region height in screen px
167
+ * @param rawSpacing - the raw (possibly zero/negative) spacing
168
+ * @returns a finite line-count upper bound
169
+ */
170
+ export declare function hatchLineCount(w: number, h: number, rawSpacing: number): number;
171
+ /** Draw the schematic's bulk-shape layer for one frame. Clears first. */
172
+ export declare function paintSchematic(input: PaintInput): void;
173
+ export {};
174
+ //# sourceMappingURL=paint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paint.d.ts","sourceRoot":"","sources":["../../../src/collections/schematic/paint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { SchematicPalette } from './paint';
2
+ /**
3
+ * Renders a visually-hidden set of themed probes and reports the resolved
4
+ * {@link SchematicPalette} to `onResolve` on mount and on every theme change.
5
+ */
6
+ export declare const SchematicPaletteProbe: import('react').NamedExoticComponent<{
7
+ onResolve: (palette: SchematicPalette) => void;
8
+ }>;
9
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../src/collections/schematic/theme.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAcH,OAAO,EAAE,KAAK,gBAAgB,EAAY,MAAM,SAAS,CAAC;AA2B1D;;;GAGG;AACH,eAAO,MAAM,qBAAqB;eACF,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI;EAyBjE,CAAC"}