@weasel-js/minimap 0.0.0-stage → 1.10.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 orochi235
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,46 @@
1
- # Temporary Holding Version
1
+ # @weasel-js/minimap
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A minimap with linked cursors, in two forms that share one set of actions.
4
+
5
+ Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
6
+ scene-graph canvas kit for React. See the
7
+ [API reference](https://orochi235.github.io/weasel/api/).
8
+
9
+ ```sh
10
+ npm install @weasel-js/minimap
11
+ ```
12
+
13
+ | Form | Where it paints | Install |
14
+ |---|---|---|
15
+ | `createMinimapContribution({ rect, fit })` | A `<CanvasView>` on the main canvas's own surface | One `ambient` entry |
16
+ | `<MinimapCanvas>` | A canvas of its own | A component beside the main canvas |
17
+
18
+ ## The contribution
19
+
20
+ It is the worked example of a `SurfaceContribution` that uses every role:
21
+
22
+ | Role | What it holds |
23
+ |---|---|
24
+ | `views` | The minimap: a `<CanvasView>` at `rect`, its camera a fit over the scene |
25
+ | `actions` | `minimap.center` (press) and `minimap.pan` (drag), which move `rootView` |
26
+ | `bindings` | `pointerDown` and `drag`, scoped with `views: [id]` so they outrank the active tool inside the minimap and never fire outside it |
27
+ | `overlay` | The visible-rect indicator, painted only in the minimap; the linked crosshair, painted in whichever view the pointer is not over |
28
+ | `attach` | Reads the `scene` and `pointer` deps, and repaints while the crosshair shows |
29
+
30
+ Build it once (`useMemo`, or module scope): each instance keeps the live state
31
+ its layers read, and `attach` runs once per instance.
32
+
33
+ ## The detached form
34
+
35
+ `<MinimapCanvas>` runs the same two actions on a dispatcher of its own, bound
36
+ unscoped since that dispatcher routes no views, with `rootView` answering the
37
+ `mainView` / `onMainViewChange` props. Put one `<PointerContextProvider>`
38
+ around it and the main canvas, and give the main canvas
39
+ `createLinkedCursorContribution()`, for crosshairs in both directions.
40
+
41
+ ## Why `rootView`
42
+
43
+ Inside the minimap's view, the `view` dep answers the minimap's camera — that
44
+ is what lets every other action work in a view. An action there that must move
45
+ the main camera reads `rootView`, never `view`; `viewport.dragPan` reads `view`
46
+ and so pans whichever view the drag lands in.
@@ -0,0 +1,256 @@
1
+ import { View, Bounds, Scene, ViewportDims, PoseDescriptor, PathDrawCommand, ViewRect, Dims, RenderLayer, SurfaceContribution, ViewApi, SceneViewDrawOne, SceneViewLayers } from '@weasel-js/core';
2
+ import { Action } from '@weasel-js/routing';
3
+ import { PointerWorldPos } from '@weasel-js/routing/react';
4
+ import * as react from 'react';
5
+ import { Ref } from 'react';
6
+ import { Animator } from '@weasel-js/animator';
7
+
8
+ /** Fallback view returned when there is nothing to fit (empty scene, etc.). */
9
+ declare const FALLBACK_FIT_VIEW: View;
10
+ /**
11
+ * Fit policy for `<MinimapCanvas>` / `computeFitView`.
12
+ *
13
+ * - `"scene"` — union of leaf-node pose AABBs (via the pose descriptor), read
14
+ * from the document: a pose override moves what is painted and not the frame.
15
+ * - `"live"` — the same union over the poses being shown, overrides included,
16
+ * so the frame follows a drag, a settle, or a simulation writing overrides.
17
+ * Refit on every override publish, which is a sweep of every leaf and
18
+ * rescales the whole minimap while one moves past the edge.
19
+ * - `{ kind: "world", rect }` — caller-supplied world rect.
20
+ * - `(scene, dims) => View` — caller derives the view directly.
21
+ */
22
+ type MinimapFit<TData, TLayer extends string, TPose> = 'scene' | 'live' | {
23
+ kind: 'world';
24
+ rect: Bounds;
25
+ } | ((scene: Scene<TData, TLayer, TPose>, dims: ViewportDims) => View);
26
+ /** Style for the visible-window indicator rect drawn over the minimap. */
27
+ interface IndicatorStyle {
28
+ /** Stroke color (any CSS color). Defaults to `'#ffffff'`. */
29
+ stroke?: string;
30
+ /** Stroke width in CSS px. Defaults to `1`. */
31
+ width?: number;
32
+ /** Dash pattern in CSS px. Defaults to `[2, 3]`. */
33
+ dash?: number[];
34
+ }
35
+ /** Options for {@link computeFitView}. */
36
+ interface ComputeFitViewOptions {
37
+ /** Inset (CSS px) around the fit rect on each side. Defaults to `8`. */
38
+ padding?: number;
39
+ }
40
+ /**
41
+ * Derive a `View` that fits the chosen world rect into a minimap of
42
+ * `dims.width × dims.height` CSS px. Uniform scale on both axes; the rect
43
+ * is centered with `padding` (default 8) on each side. The smaller-axis
44
+ * fit ratio wins, so the whole rect is visible. An empty scene gives
45
+ * {@link FALLBACK_FIT_VIEW}; a function `fit` is passed through unchanged.
46
+ *
47
+ * A rect with one zero axis (a line) is fitted along the other. One with no
48
+ * positive axis falls back to identity scale centered on its top-left.
49
+ */
50
+ declare function computeFitView<TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>, dims: ViewportDims, fit: MinimapFit<TData, TLayer, TPose>, descriptor: PoseDescriptor<TPose>, opts?: ComputeFitViewOptions): View;
51
+ /**
52
+ * Build the visible-window indicator `DrawCommand` for the minimap: the main
53
+ * canvas's visible world rect as a dashed-stroke `path` rect with no fill, in
54
+ * world coordinates. Pass the `minimapView` it will be drawn through, so the
55
+ * stroke width and dash come out in CSS px at any fit scale; without it they
56
+ * are in world units.
57
+ */
58
+ declare function computeIndicatorCommand(mainView: View, mainViewDims: ViewportDims, style?: IndicatorStyle, minimapView?: View): PathDrawCommand;
59
+
60
+ /**
61
+ * A minimap over a `<SceneCanvas>`, written as one `SurfaceContribution`: a
62
+ * view through a fit camera, the pan that moves the main camera from inside
63
+ * it, the visible-rect indicator, and a linked crosshair.
64
+ */
65
+
66
+ /** Options for {@link createLinkedCursorContribution}. */
67
+ interface LinkedCursorOptions {
68
+ /** Entry id. Default `'linked-cursor'`. */
69
+ id?: string;
70
+ /** Views on this surface the crosshair may paint in. Default `[null]`, the
71
+ * surface's own camera — the main canvas beside a detached minimap. */
72
+ views?: readonly (string | null)[];
73
+ }
74
+ /**
75
+ * The linked crosshair alone: this surface paints the pointer's position
76
+ * whenever the pointer is over some other view — another surface under the
77
+ * same `<PointerContextProvider>`, such as a detached `<MinimapCanvas>`.
78
+ *
79
+ * A surface carrying `createMinimapContribution` already has one, in both its
80
+ * views; this is for a surface without.
81
+ */
82
+ declare function createLinkedCursorContribution(opts?: LinkedCursorOptions): SurfaceContribution;
83
+ /** Options for {@link createMinimapContribution}. */
84
+ interface MinimapContributionOptions<TData = unknown, TLayer extends string = string, TPose = unknown> {
85
+ /** View and entry id. Default `'minimap'`. */
86
+ id?: string;
87
+ /** Where the minimap paints, in CSS pixels from the canvas top-left. A
88
+ * function is re-read every frame, so it can follow the canvas's size. */
89
+ rect: ViewRect | ((outer: View, dims: Dims) => ViewRect);
90
+ /** What the minimap frames. Default `'scene'`, the union of leaf poses. */
91
+ fit?: MinimapFit<TData, TLayer, TPose>;
92
+ /** The surface layers the minimap paints. Default: all of them. */
93
+ layers?: (surface: readonly RenderLayer<unknown>[]) => readonly RenderLayer<unknown>[];
94
+ /** Ground painted before the minimap's layers, so the main canvas does not
95
+ * show through. */
96
+ background?: string;
97
+ /** The visible-rect indicator's stroke. */
98
+ indicatorStyle?: IndicatorStyle;
99
+ }
100
+ /**
101
+ * A minimap inside a `<SceneCanvas>`, installed with one entry:
102
+ *
103
+ * ```tsx
104
+ * const minimap = useMemo(() => createMinimapContribution({ rect: { x: 8, y: 8, w: 160, h: 110 } }), []);
105
+ * <SceneCanvas ambient={[minimap]} … />
106
+ * ```
107
+ *
108
+ * Press in it to center the main camera on that point, drag to keep it there.
109
+ * It shows the main camera's visible rect, and a crosshair where the pointer is
110
+ * over the other view. Build it once — each instance keeps the live state its
111
+ * layers read.
112
+ */
113
+ declare function createMinimapContribution<TData = unknown, TLayer extends string = string, TPose = unknown>(opts: MinimapContributionOptions<TData, TLayer, TPose>): SurfaceContribution;
114
+
115
+ /**
116
+ * The minimap's input: press to recenter the main camera on a world point,
117
+ * drag to keep it there. Both read `rootView` — never `view`, which inside the
118
+ * minimap's own view answers the minimap's camera.
119
+ */
120
+
121
+ /** Action id for `minimapCenterAction`. */
122
+ declare const MINIMAP_CENTER = "minimap.center";
123
+ /** Action id for `minimapPanAction`. */
124
+ declare const MINIMAP_PAN = "minimap.pan";
125
+ /** Move `root` so world point (`x`, `y`) sits at the center of its host,
126
+ * keeping its scale. A no-op while the host is unmeasured. */
127
+ declare function centerRootOn(root: ViewApi, x: number, y: number): void;
128
+ /** Recenter the main camera on the pressed world point. */
129
+ declare function minimapCenterAction(): Action;
130
+ /** Keep the main camera centered on the pointer's world point while dragging.
131
+ * Pair it with `minimap.center` on `pointerDown`, which handles the press.
132
+ * A recenter lands the moment it is computed, so there is nothing to commit
133
+ * and a cancel ends where a release does. */
134
+ declare function minimapPanAction(): Action;
135
+
136
+ /**
137
+ * The minimap's chrome: the main camera's visible rect, drawn in the minimap,
138
+ * and the linked crosshair, drawn in every view the pointer is not over.
139
+ */
140
+
141
+ /**
142
+ * The main camera's visible world rect, painted only in view `viewId`. Its
143
+ * stroke and dash are screen pixels whatever the minimap's scale.
144
+ */
145
+ declare function createIndicatorLayer(opts: {
146
+ viewId: string;
147
+ root: () => ViewApi | undefined;
148
+ style?: IndicatorStyle;
149
+ }): RenderLayer<unknown>;
150
+ /** The halo's color: dark enough to separate the accent from any content. */
151
+ declare const CROSSHAIR_HALO = "rgba(0, 0, 0, 0.6)";
152
+ /** An axis-aligned rect, `{ x, y, w, h }`. */
153
+ interface CrosshairRect {
154
+ x: number;
155
+ y: number;
156
+ w: number;
157
+ h: number;
158
+ }
159
+ /**
160
+ * The linked crosshair at (`x`, `y`) as rects in the caller's units, where one
161
+ * screen pixel is `unitsPerPx` of them: four halo rects to paint first, then
162
+ * four bars. A painter with no weasel renderer — a 2D canvas — draws the same
163
+ * crosshair from these.
164
+ */
165
+ declare function crosshairRects(x: number, y: number, unitsPerPx: number): {
166
+ halo: CrosshairRect[];
167
+ bars: CrosshairRect[];
168
+ };
169
+ /**
170
+ * A crosshair at the pointer's world point, in each of `views` the pointer is
171
+ * not over — the other side of a linked cursor. A fixed screen size in every
172
+ * view. `views` omitted paints in every view.
173
+ */
174
+ declare function createLinkedCursorLayer(opts: {
175
+ id: string;
176
+ pointer: () => PointerWorldPos;
177
+ color: () => string;
178
+ views?: readonly (string | null)[];
179
+ }): RenderLayer<unknown>;
180
+
181
+ /** Props for `<MinimapCanvas>`. */
182
+ interface MinimapCanvasProps<TData, TLayer extends string, TPose> {
183
+ /** Same scene the main canvas is rendering. */
184
+ scene: Scene<TData, TLayer, TPose>;
185
+ /** Main canvas's view — used to position the visible-window indicator and
186
+ * to preserve `scale` when the minimap recenters. */
187
+ mainView: View;
188
+ /** Main canvas's CSS-pixel dims. Used to compute the visible-window rect
189
+ * and the recenter offset. */
190
+ mainViewDims: {
191
+ width: number;
192
+ height: number;
193
+ };
194
+ /** Setter called on click (single recenter) and during drag (continuous
195
+ * recenter). Receives a `View` with the same `scale` as `mainView` and
196
+ * `(x, y)` shifted so the pointer's world point lands at the main
197
+ * canvas's center. */
198
+ onMainViewChange: (next: View) => void;
199
+ /** Minimap's own CSS-pixel dims. */
200
+ width: number;
201
+ height: number;
202
+ /** Consumer's draw callback. Applied through the derived fit view; same
203
+ * signature as `<SceneCanvas>`'s scene-slot `drawOne` so the consumer
204
+ * can reuse it (typically simplified — just colored AABBs). */
205
+ drawOne: SceneViewDrawOne<TData, TLayer, TPose>;
206
+ /** Fit policy. Defaults to `"scene"` (AABB union of leaf poses). See
207
+ * `MinimapFit` for the full shape.
208
+ *
209
+ * `"scene"` frames **document** poses, not `scene.overrides` — a node
210
+ * moved by an override paints where the override puts it, outside the
211
+ * frame if it goes there, rather than making the whole minimap rescale on
212
+ * every frame of a drag or a settle. `"live"` frames the overrides too. */
213
+ fit?: MinimapFit<TData, TLayer, TPose>;
214
+ /** How to read poses. Default `AUTO_POSE_DESCRIPTOR`. */
215
+ poseDescriptor?: PoseDescriptor<TPose>;
216
+ /** Optional per-id alpha multiplier, mirroring `<SceneCanvas>`'s scene-slot
217
+ * `alphaFor`. Pass the same function the main canvas uses so a
218
+ * scoping-dim treatment shows up in the minimap too. */
219
+ alphaFor?: (id: string) => number;
220
+ /** Hide scene layers in the minimap, keyed `scene:<layerId>` as on
221
+ * `<SceneCanvas>` — pass the main canvas's map so both show the same set. */
222
+ layerVisibility?: SceneViewLayers['layerVisibility'];
223
+ /** Paint order for the minimap, keyed as `layerVisibility`. */
224
+ layerOrder?: SceneViewLayers['layerOrder'];
225
+ /** Optional animator: the minimap repaints on its ticks and paints its
226
+ * `colorOverrides`. Pass the main canvas's animator so both show the same
227
+ * colors. */
228
+ animator?: Animator;
229
+ /** Visual tuning of the indicator stroke. */
230
+ indicatorStyle?: IndicatorStyle;
231
+ /** CSS class for the canvas. Its size is `width` × `height` unless a rule
232
+ * on this class sizes it. */
233
+ className?: string;
234
+ /** Forwarded ref to the underlying `<canvas>`. */
235
+ canvasRef?: Ref<HTMLCanvasElement>;
236
+ /** The view id this minimap publishes its pointer under. Default
237
+ * `'minimap'`. */
238
+ id?: string;
239
+ }
240
+ /**
241
+ * Opinionated minimap on a canvas of its own. Renders the same `scene` the
242
+ * main canvas is showing through a derived "fit" view, and overlays a dashed
243
+ * indicator of where the main canvas is looking. Press anywhere in it to
244
+ * recenter the main view on that world point; drag to pan continuously.
245
+ *
246
+ * Its dispatcher, actions and deps are its own, so it never competes with
247
+ * the main canvas for input. Put one `<PointerContextProvider>` around it and
248
+ * the main canvas for linked cursors both ways.
249
+ *
250
+ * `forwardRef` is not used because the component is generic over
251
+ * `<TData, TLayer, TPose>` and React's `forwardRef` erases generics. The
252
+ * `canvasRef` prop is the plain-prop equivalent.
253
+ */
254
+ declare function MinimapCanvas<TData, TLayer extends string, TPose>(props: MinimapCanvasProps<TData, TLayer, TPose>): react.JSX.Element;
255
+
256
+ export { CROSSHAIR_HALO, type ComputeFitViewOptions, type CrosshairRect, FALLBACK_FIT_VIEW, type IndicatorStyle, type LinkedCursorOptions, MINIMAP_CENTER, MINIMAP_PAN, MinimapCanvas, type MinimapCanvasProps, type MinimapContributionOptions, type MinimapFit, centerRootOn, computeFitView, computeIndicatorCommand, createIndicatorLayer, createLinkedCursorContribution, createLinkedCursorLayer, createMinimapContribution, crosshairRects, minimapCenterAction, minimapPanAction };
package/dist/index.js ADDED
@@ -0,0 +1,425 @@
1
+ import { PointerProviderIfRoot, AUTO_POSE_DESCRIPTOR, poseDescriptorForNode, effectivePose, documentPose, FALLBACK_ACCENT, normalizeView, accentColorOf, SceneViewCanvas } from '@weasel-js/core';
2
+ import { useMemo, useRef, useEffect, useCallback, useSyncExternalStore } from 'react';
3
+ import { DepRegistryProvider, ActionsProvider, ActiveToolContextProvider, useDepSource, useActionsRegistry, useGestureDispatcher, usePointerContext, usePointerPosition } from '@weasel-js/routing/react';
4
+ import { useLatest, assignRef } from '@weasel-js/react';
5
+ import { jsx } from 'react/jsx-runtime';
6
+
7
+ // src/createMinimapContribution.ts
8
+ var DEFAULT_FIT_PADDING = 8;
9
+ var FALLBACK_FIT_VIEW = { x: 0, y: 0, scale: { x: 1, y: 1 } };
10
+ var DEFAULT_INDICATOR_STROKE = "#ffffff";
11
+ var DEFAULT_INDICATOR_WIDTH = 1;
12
+ var DEFAULT_INDICATOR_DASH = [2, 3];
13
+ function fitRectInto(rect, dims, padding) {
14
+ const availW = Math.max(1, dims.width - padding * 2);
15
+ const availH = Math.max(1, dims.height - padding * 2);
16
+ const hasW = rect.width > 0;
17
+ const hasH = rect.height > 0;
18
+ if (!hasW && !hasH) {
19
+ return {
20
+ x: rect.x - dims.width / 2,
21
+ y: rect.y - dims.height / 2,
22
+ scale: { x: 1, y: 1 }
23
+ };
24
+ }
25
+ const s = Math.min(
26
+ hasW ? availW / rect.width : Infinity,
27
+ hasH ? availH / rect.height : Infinity
28
+ );
29
+ const worldCx = rect.x + Math.max(0, rect.width) / 2;
30
+ const worldCy = rect.y + Math.max(0, rect.height) / 2;
31
+ return {
32
+ x: worldCx - dims.width / (2 * s),
33
+ y: worldCy - dims.height / (2 * s),
34
+ scale: { x: s, y: s }
35
+ };
36
+ }
37
+ function sceneLeafBounds(scene, descriptor, poseOf) {
38
+ let minX = Infinity;
39
+ let minY = Infinity;
40
+ let maxX = -Infinity;
41
+ let maxY = -Infinity;
42
+ let any = false;
43
+ for (const node of scene.nodes.values()) {
44
+ if (node.kind !== "leaf") continue;
45
+ const b = poseDescriptorForNode(descriptor, node).getBounds(poseOf(scene, node));
46
+ if (!Number.isFinite(b.x + b.y + b.width + b.height)) continue;
47
+ if (b.x < minX) minX = b.x;
48
+ if (b.y < minY) minY = b.y;
49
+ const right = b.x + b.width;
50
+ const bottom = b.y + b.height;
51
+ if (right > maxX) maxX = right;
52
+ if (bottom > maxY) maxY = bottom;
53
+ any = true;
54
+ }
55
+ if (!any) return null;
56
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
57
+ }
58
+ function computeFitView(scene, dims, fit, descriptor, opts = {}) {
59
+ const padding = opts.padding ?? DEFAULT_FIT_PADDING;
60
+ if (typeof fit === "function") {
61
+ return fit(scene, dims);
62
+ }
63
+ if (fit === "scene" || fit === "live") {
64
+ const rect = sceneLeafBounds(scene, descriptor, fit === "live" ? effectivePose : documentPose);
65
+ if (rect === null) return FALLBACK_FIT_VIEW;
66
+ return fitRectInto(rect, dims, padding);
67
+ }
68
+ return fitRectInto(fit.rect, dims, padding);
69
+ }
70
+ function computeIndicatorCommand(mainView, mainViewDims, style = {}, minimapView) {
71
+ const x = mainView.x;
72
+ const y = mainView.y;
73
+ const width = mainViewDims.width / mainView.scale.x;
74
+ const height = mainViewDims.height / mainView.scale.y;
75
+ const px = minimapView ? 1 / Math.abs(minimapView.scale.x) : 1;
76
+ return {
77
+ kind: "path",
78
+ path: { kind: "rect", x, y, width, height },
79
+ stroke: {
80
+ paint: { kind: "solid", color: style.stroke ?? DEFAULT_INDICATOR_STROKE },
81
+ width: (style.width ?? DEFAULT_INDICATOR_WIDTH) * px,
82
+ dash: (style.dash ?? DEFAULT_INDICATOR_DASH).map((d) => d * px)
83
+ }
84
+ };
85
+ }
86
+
87
+ // src/actions.ts
88
+ var MINIMAP_CENTER = "minimap.center";
89
+ var MINIMAP_PAN = "minimap.pan";
90
+ function centerRootOn(root, x, y) {
91
+ const size = root.hostSize?.();
92
+ if (!size) return;
93
+ const v = root.get();
94
+ root.set({
95
+ x: x - size.width / (2 * v.scale.x),
96
+ y: y - size.height / (2 * v.scale.y),
97
+ scale: v.scale
98
+ });
99
+ }
100
+ function rootOf(deps) {
101
+ return deps.rootView;
102
+ }
103
+ function minimapCenterAction() {
104
+ return {
105
+ id: MINIMAP_CENTER,
106
+ label: "Minimap \u2014 center the view here",
107
+ requires: ["rootView"],
108
+ invoker: {
109
+ timing: "immediate",
110
+ run: (deps, params) => {
111
+ const p = params;
112
+ const root = rootOf(deps);
113
+ if (!root || p?.worldX === void 0 || p.worldY === void 0) return;
114
+ centerRootOn(root, p.worldX, p.worldY);
115
+ }
116
+ }
117
+ };
118
+ }
119
+ function minimapPanAction() {
120
+ return {
121
+ id: MINIMAP_PAN,
122
+ label: "Minimap \u2014 pan the view",
123
+ requires: ["rootView"],
124
+ invoker: {
125
+ timing: "ongoing",
126
+ start(ctx) {
127
+ const root = rootOf(ctx.deps);
128
+ if (!root) return {};
129
+ return {
130
+ onMove: (m) => {
131
+ const p = m.drag?.current ?? m.world;
132
+ centerRootOn(root, p.x, p.y);
133
+ },
134
+ onEnd: () => {
135
+ }
136
+ };
137
+ }
138
+ }
139
+ };
140
+ }
141
+
142
+ // src/layers.ts
143
+ function viewIdOf(data) {
144
+ return data?.viewId ?? null;
145
+ }
146
+ function createIndicatorLayer(opts) {
147
+ const { viewId, root, style = {} } = opts;
148
+ return {
149
+ id: `${viewId}.indicator`,
150
+ label: "Minimap indicator",
151
+ draw: (data, view) => {
152
+ if (viewIdOf(data) !== viewId) return [];
153
+ const r = root();
154
+ const size = r?.hostSize?.();
155
+ if (!r || !size) return [];
156
+ return [computeIndicatorCommand(r.get(), size, style, view)];
157
+ }
158
+ };
159
+ }
160
+ var ARM = 9;
161
+ var GAP = 3;
162
+ var THICK = 2;
163
+ var HALO = 1;
164
+ var CROSSHAIR_HALO = "rgba(0, 0, 0, 0.6)";
165
+ function crosshairRects(x, y, unitsPerPx) {
166
+ const k = unitsPerPx;
167
+ const arm = ARM * k, gap = GAP * k, t = THICK * k, h = HALO * k;
168
+ const bars = [
169
+ { x: x - gap - arm, y: y - t / 2, w: arm, h: t },
170
+ { x: x + gap, y: y - t / 2, w: arm, h: t },
171
+ { x: x - t / 2, y: y - gap - arm, w: t, h: arm },
172
+ { x: x - t / 2, y: y + gap, w: t, h: arm }
173
+ ];
174
+ const halo = bars.map((b) => ({ x: b.x - h, y: b.y - h, w: b.w + 2 * h, h: b.h + 2 * h }));
175
+ return { halo, bars };
176
+ }
177
+ function createLinkedCursorLayer(opts) {
178
+ const { id, pointer, color, views } = opts;
179
+ return {
180
+ id,
181
+ label: "Linked cursor",
182
+ draw: (data, view) => {
183
+ const p = pointer();
184
+ const here = viewIdOf(data);
185
+ if (!p || p.viewId === here || views && !views.includes(here)) return [];
186
+ const { halo, bars } = crosshairRects(p.worldX, p.worldY, 1 / view.scale.x);
187
+ const rect = (r, c) => ({
188
+ kind: "path",
189
+ path: { kind: "rect", x: r.x, y: r.y, width: r.w, height: r.h },
190
+ fill: { paint: { kind: "solid", color: c } }
191
+ });
192
+ const accent = color();
193
+ return [
194
+ ...halo.map((r) => rect(r, CROSSHAIR_HALO)),
195
+ ...bars.map((r) => rect(r, accent))
196
+ ];
197
+ }
198
+ };
199
+ }
200
+ function linkedCursor(id, ownViews) {
201
+ let pointer = null;
202
+ let color = FALLBACK_ACCENT;
203
+ const visible = (p) => p !== null && ownViews.some((v) => v !== p.viewId);
204
+ const layer = createLinkedCursorLayer({ id, pointer: () => pointer, color: () => color, views: ownViews });
205
+ const attach = (api, deps) => {
206
+ const store = deps.get("pointer");
207
+ if (!store) return () => {
208
+ };
209
+ color = accentColorOf(api.element);
210
+ pointer = store.get();
211
+ const off = store.subscribe(() => {
212
+ const prev = pointer;
213
+ pointer = store.get();
214
+ if (visible(prev) || visible(pointer)) api.requestRedraw();
215
+ });
216
+ return () => {
217
+ off();
218
+ pointer = null;
219
+ api.requestRedraw();
220
+ };
221
+ };
222
+ return { layer, attach };
223
+ }
224
+ function createLinkedCursorContribution(opts = {}) {
225
+ const id = opts.id ?? "linked-cursor";
226
+ const cursor = linkedCursor(id, opts.views ?? [null]);
227
+ return { id, eligibility: { always: true }, overlay: cursor.layer, attach: cursor.attach };
228
+ }
229
+ var DEFAULT_BACKGROUND = "#18181c";
230
+ function createMinimapContribution(opts) {
231
+ const id = opts.id ?? "minimap";
232
+ const fit = opts.fit ?? "scene";
233
+ let deps = null;
234
+ let cached = null;
235
+ const fitCamera = (outer, dims) => {
236
+ const scene = deps?.get("scene");
237
+ if (!scene) return FALLBACK_FIT_VIEW;
238
+ const r = typeof opts.rect === "function" ? opts.rect(outer, dims) : opts.rect;
239
+ const version = scene.getVersion();
240
+ const shown = fit === "live" ? scene.overrides.getGeneration() : 0;
241
+ if (cached && cached.scene === scene && cached.version === version && cached.shown === shown && cached.w === r.w && cached.h === r.h) {
242
+ return cached.view;
243
+ }
244
+ const descriptor = deps?.get("poseDescriptor") ?? AUTO_POSE_DESCRIPTOR;
245
+ const view = computeFitView(scene, { width: r.w, height: r.h }, fit, descriptor);
246
+ cached = { scene, version, shown, w: r.w, h: r.h, view };
247
+ return view;
248
+ };
249
+ const cursor = linkedCursor(`${id}.cursor`, [null, id]);
250
+ const indicator = createIndicatorLayer({
251
+ viewId: id,
252
+ root: () => deps?.get("rootView"),
253
+ ...opts.indicatorStyle ? { style: opts.indicatorStyle } : {}
254
+ });
255
+ return {
256
+ id,
257
+ eligibility: { always: true },
258
+ views: [{
259
+ id,
260
+ bounds: opts.rect,
261
+ view: fitCamera,
262
+ interactive: true,
263
+ background: opts.background ?? DEFAULT_BACKGROUND,
264
+ ...opts.layers ? { layers: opts.layers } : {}
265
+ }],
266
+ actions: [minimapCenterAction(), minimapPanAction()],
267
+ bindings: [
268
+ { spec: { kind: "pointerDown" }, actionId: MINIMAP_CENTER, opts: { views: [id] } },
269
+ { spec: { kind: "drag" }, actionId: MINIMAP_PAN, opts: { views: [id] } }
270
+ ],
271
+ overlay: [indicator, cursor.layer],
272
+ attach: (api, reader) => {
273
+ deps = reader;
274
+ const detachCursor = cursor.attach(api, reader);
275
+ api.requestRedraw();
276
+ return () => {
277
+ detachCursor();
278
+ deps = null;
279
+ cached = null;
280
+ };
281
+ }
282
+ };
283
+ }
284
+ var NEVER = () => () => {
285
+ };
286
+ function useFitView(scene, width, height, fit, descriptor) {
287
+ const sceneVersion = useSyncExternalStore(scene.subscribe, scene.getVersion, scene.getVersion);
288
+ const overrideGeneration = useSyncExternalStore(
289
+ fit === "live" ? scene.overrides.subscribe : NEVER,
290
+ scene.overrides.getGeneration,
291
+ scene.overrides.getGeneration
292
+ );
293
+ return useMemo(() => {
294
+ return normalizeView(computeFitView(scene, { width, height }, fit, descriptor));
295
+ }, [scene, sceneVersion, overrideGeneration, width, height, fit, descriptor]);
296
+ }
297
+ var MINIMAP_INPUT = {
298
+ id: "minimap",
299
+ eligibility: { always: true },
300
+ bindings: [
301
+ { spec: { kind: "pointerDown" }, actionId: MINIMAP_CENTER },
302
+ { spec: { kind: "drag" }, actionId: MINIMAP_PAN }
303
+ ]
304
+ };
305
+ var TOOLS_BY_ID = /* @__PURE__ */ new Map([[MINIMAP_INPUT.id, MINIMAP_INPUT]]);
306
+ var CHANNELS = { wheel: false, pinch: false, contextMenu: false, ingest: false };
307
+ function MinimapCanvasInner(props) {
308
+ const {
309
+ scene,
310
+ mainView: mainViewProp,
311
+ mainViewDims,
312
+ onMainViewChange,
313
+ width,
314
+ height,
315
+ drawOne,
316
+ alphaFor,
317
+ layerVisibility,
318
+ layerOrder,
319
+ animator,
320
+ fit = "scene",
321
+ poseDescriptor,
322
+ indicatorStyle,
323
+ className,
324
+ canvasRef,
325
+ id = "minimap"
326
+ } = props;
327
+ const mainView = normalizeView(mainViewProp);
328
+ const descriptor = poseDescriptor ?? AUTO_POSE_DESCRIPTOR;
329
+ const fitView = useFitView(scene, width, height, fit, descriptor);
330
+ const indicatorCmd = useMemo(() => {
331
+ return computeIndicatorCommand(mainView, mainViewDims, indicatorStyle, fitView);
332
+ }, [mainView, mainViewDims, indicatorStyle, fitView]);
333
+ const localCanvasRef = useRef(null);
334
+ const mainViewRef = useLatest(mainView);
335
+ const mainViewDimsRef = useLatest(mainViewDims);
336
+ const onMainViewChangeRef = useLatest(onMainViewChange);
337
+ const fitViewRef = useLatest(fitView);
338
+ const rootView = useMemo(() => ({
339
+ get: () => mainViewRef.current,
340
+ set: (v) => onMainViewChangeRef.current(v),
341
+ hostSize: () => mainViewDimsRef.current
342
+ }), [mainViewRef, onMainViewChangeRef, mainViewDimsRef]);
343
+ useDepSource("rootView", () => rootView);
344
+ const actions = useActionsRegistry();
345
+ useEffect(() => {
346
+ if (!actions) return;
347
+ const off = [actions.register(minimapCenterAction()), actions.register(minimapPanAction())];
348
+ return () => {
349
+ for (const u of off) u();
350
+ };
351
+ }, [actions]);
352
+ const clientToWorld = useCallback((cx, cy) => {
353
+ const rect = localCanvasRef.current?.getBoundingClientRect();
354
+ const fv = fitViewRef.current;
355
+ return {
356
+ x: fv.x + (cx - (rect?.left ?? 0)) / fv.scale.x,
357
+ y: fv.y + (cy - (rect?.top ?? 0)) / fv.scale.y
358
+ };
359
+ }, [fitViewRef]);
360
+ useGestureDispatcher({
361
+ canvasRef: localCanvasRef,
362
+ actions,
363
+ entriesById: TOOLS_BY_ID,
364
+ clientToWorld,
365
+ keyboard: false,
366
+ channels: CHANNELS
367
+ });
368
+ const pointerStore = usePointerContext();
369
+ useEffect(() => {
370
+ const el = localCanvasRef.current;
371
+ if (!el || !pointerStore) return;
372
+ const onMove = (e) => {
373
+ const w = clientToWorld(e.clientX, e.clientY);
374
+ pointerStore.set({ worldX: w.x, worldY: w.y, viewId: id });
375
+ };
376
+ const onLeave = (e) => {
377
+ if (e.buttons !== 0) return;
378
+ if (pointerStore.get()?.viewId === id) pointerStore.set(null);
379
+ };
380
+ el.addEventListener("pointermove", onMove);
381
+ el.addEventListener("pointerleave", onLeave);
382
+ return () => {
383
+ el.removeEventListener("pointermove", onMove);
384
+ el.removeEventListener("pointerleave", onLeave);
385
+ };
386
+ }, [pointerStore, clientToWorld, id]);
387
+ const pointer = usePointerPosition();
388
+ const cursorLayer = useMemo(() => createLinkedCursorLayer({
389
+ id: `${id}.cursor`,
390
+ pointer: () => pointer,
391
+ color: () => accentColorOf(localCanvasRef.current)
392
+ }), [id, pointer]);
393
+ const extraCommands = useMemo(() => [
394
+ indicatorCmd,
395
+ ...cursorLayer.draw({ viewId: id }, fitView, { width, height })
396
+ ], [indicatorCmd, cursorLayer, id, fitView, width, height]);
397
+ const setCanvasRef = useCallback((el) => {
398
+ localCanvasRef.current = el;
399
+ assignRef(canvasRef, el);
400
+ }, [canvasRef]);
401
+ return /* @__PURE__ */ jsx(
402
+ SceneViewCanvas,
403
+ {
404
+ scene,
405
+ view: fitView,
406
+ width,
407
+ height,
408
+ drawOne,
409
+ extraCommands,
410
+ alphaFor,
411
+ layerVisibility,
412
+ layerOrder,
413
+ animator,
414
+ className,
415
+ canvasRef: setCanvasRef
416
+ }
417
+ );
418
+ }
419
+ function MinimapCanvas(props) {
420
+ return /* @__PURE__ */ jsx(DepRegistryProvider, { children: /* @__PURE__ */ jsx(ActionsProvider, { children: /* @__PURE__ */ jsx(ActiveToolContextProvider, { children: /* @__PURE__ */ jsx(PointerProviderIfRoot, { children: /* @__PURE__ */ jsx(MinimapCanvasInner, { ...props }) }) }) }) });
421
+ }
422
+
423
+ export { CROSSHAIR_HALO, FALLBACK_FIT_VIEW, MINIMAP_CENTER, MINIMAP_PAN, MinimapCanvas, centerRootOn, computeFitView, computeIndicatorCommand, createIndicatorLayer, createLinkedCursorContribution, createLinkedCursorLayer, createMinimapContribution, crosshairRects, minimapCenterAction, minimapPanAction };
424
+ //# sourceMappingURL=index.js.map
425
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/minimapMath.ts","../src/actions.ts","../src/layers.ts","../src/createMinimapContribution.ts","../src/useFitView.ts","../src/MinimapCanvas.tsx"],"names":["normalizeView","AUTO_POSE_DESCRIPTOR","useMemo","accentColorOf"],"mappings":"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","file":"index.js","sourcesContent":["/** Pure math helpers for `<MinimapCanvas>`: no GL, DOM, or renderer. */\nimport { documentPose, effectivePose } from '@weasel-js/core';\nimport type { Scene } from '@weasel-js/core';\nimport type { View } from '@weasel-js/core';\nimport type { Bounds, ViewportDims } from '@weasel-js/core';\nimport { poseDescriptorForNode, type PoseDescriptor } from '@weasel-js/core';\nimport type { PathDrawCommand } from '@weasel-js/core';\n\n/** Inset (CSS px) around the fit rect on each side. Matches the spec note. */\nconst DEFAULT_FIT_PADDING = 8;\n\n/** Fallback view returned when there is nothing to fit (empty scene, etc.). */\nexport const FALLBACK_FIT_VIEW: View = { x: 0, y: 0, scale: { x: 1, y: 1 } };\n\n/**\n * Fit policy for `<MinimapCanvas>` / `computeFitView`.\n *\n * - `\"scene\"` — union of leaf-node pose AABBs (via the pose descriptor), read\n * from the document: a pose override moves what is painted and not the frame.\n * - `\"live\"` — the same union over the poses being shown, overrides included,\n * so the frame follows a drag, a settle, or a simulation writing overrides.\n * Refit on every override publish, which is a sweep of every leaf and\n * rescales the whole minimap while one moves past the edge.\n * - `{ kind: \"world\", rect }` — caller-supplied world rect.\n * - `(scene, dims) => View` — caller derives the view directly.\n */\nexport type MinimapFit<TData, TLayer extends string, TPose> =\n | 'scene'\n | 'live'\n | { kind: 'world'; rect: Bounds }\n | ((scene: Scene<TData, TLayer, TPose>, dims: ViewportDims) => View);\n\n/** Style for the visible-window indicator rect drawn over the minimap. */\nexport interface IndicatorStyle {\n /** Stroke color (any CSS color). Defaults to `'#ffffff'`. */\n stroke?: string;\n /** Stroke width in CSS px. Defaults to `1`. */\n width?: number;\n /** Dash pattern in CSS px. Defaults to `[2, 3]`. */\n dash?: number[];\n}\n\nconst DEFAULT_INDICATOR_STROKE = '#ffffff';\nconst DEFAULT_INDICATOR_WIDTH = 1;\nconst DEFAULT_INDICATOR_DASH: readonly number[] = [2, 3];\n\n/** Internal: fit a world rect uniformly into dims with padding. */\nfunction fitRectInto(rect: Bounds, dims: ViewportDims, padding: number): View {\n const availW = Math.max(1, dims.width - padding * 2);\n const availH = Math.max(1, dims.height - padding * 2);\n const hasW = rect.width > 0;\n const hasH = rect.height > 0;\n if (!hasW && !hasH) {\n return {\n x: rect.x - dims.width / 2,\n y: rect.y - dims.height / 2,\n scale: { x: 1, y: 1 },\n };\n }\n const s = Math.min(\n hasW ? availW / rect.width : Infinity,\n hasH ? availH / rect.height : Infinity,\n );\n const worldCx = rect.x + Math.max(0, rect.width) / 2;\n const worldCy = rect.y + Math.max(0, rect.height) / 2;\n return {\n x: worldCx - dims.width / (2 * s),\n y: worldCy - dims.height / (2 * s),\n scale: { x: s, y: s },\n };\n}\n\n/**\n * Compute the AABB union of all leaf-node poses in the scene, via\n * `descriptor`. Container nodes are skipped — their effective bounds come\n * from their descendant leaves, and so is a leaf whose bounds are not finite,\n * which would otherwise make the whole union non-finite. Returns `null` when\n * the scene has no leaf with bounds.\n */\nfunction sceneLeafBounds<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n descriptor: PoseDescriptor<TPose>,\n poseOf: typeof documentPose,\n): Bounds | null {\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n let any = false;\n for (const node of scene.nodes.values()) {\n if (node.kind !== 'leaf') continue;\n const b = poseDescriptorForNode(descriptor, node).getBounds(poseOf(scene, node));\n if (!Number.isFinite(b.x + b.y + b.width + b.height)) continue;\n if (b.x < minX) minX = b.x;\n if (b.y < minY) minY = b.y;\n const right = b.x + b.width;\n const bottom = b.y + b.height;\n if (right > maxX) maxX = right;\n if (bottom > maxY) maxY = bottom;\n any = true;\n }\n if (!any) return null;\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\n/** Options for {@link computeFitView}. */\nexport interface ComputeFitViewOptions {\n /** Inset (CSS px) around the fit rect on each side. Defaults to `8`. */\n padding?: number;\n}\n\n/**\n * Derive a `View` that fits the chosen world rect into a minimap of\n * `dims.width × dims.height` CSS px. Uniform scale on both axes; the rect\n * is centered with `padding` (default 8) on each side. The smaller-axis\n * fit ratio wins, so the whole rect is visible. An empty scene gives\n * {@link FALLBACK_FIT_VIEW}; a function `fit` is passed through unchanged.\n *\n * A rect with one zero axis (a line) is fitted along the other. One with no\n * positive axis falls back to identity scale centered on its top-left.\n */\nexport function computeFitView<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n dims: ViewportDims,\n fit: MinimapFit<TData, TLayer, TPose>,\n descriptor: PoseDescriptor<TPose>,\n opts: ComputeFitViewOptions = {},\n): View {\n const padding = opts.padding ?? DEFAULT_FIT_PADDING;\n\n if (typeof fit === 'function') {\n return fit(scene, dims);\n }\n if (fit === 'scene' || fit === 'live') {\n const rect = sceneLeafBounds(scene, descriptor, fit === 'live' ? effectivePose : documentPose);\n if (rect === null) return FALLBACK_FIT_VIEW;\n return fitRectInto(rect, dims, padding);\n }\n // { kind: 'world', rect }\n return fitRectInto(fit.rect, dims, padding);\n}\n\n/**\n * Build the visible-window indicator `DrawCommand` for the minimap: the main\n * canvas's visible world rect as a dashed-stroke `path` rect with no fill, in\n * world coordinates. Pass the `minimapView` it will be drawn through, so the\n * stroke width and dash come out in CSS px at any fit scale; without it they\n * are in world units.\n */\nexport function computeIndicatorCommand(\n mainView: View,\n mainViewDims: ViewportDims,\n style: IndicatorStyle = {},\n minimapView?: View,\n): PathDrawCommand {\n // World-space rect, returned as-is: the caller wraps the command list in a\n // group transformed by the minimap's view, and screen coords here would get\n // that transform applied a second time.\n const x = mainView.x;\n const y = mainView.y;\n const width = mainViewDims.width / mainView.scale.x;\n const height = mainViewDims.height / mainView.scale.y;\n const px = minimapView ? 1 / Math.abs(minimapView.scale.x) : 1;\n return {\n kind: 'path',\n path: { kind: 'rect', x, y, width, height },\n stroke: {\n paint: { kind: 'solid', color: style.stroke ?? DEFAULT_INDICATOR_STROKE },\n width: (style.width ?? DEFAULT_INDICATOR_WIDTH) * px,\n dash: (style.dash ?? DEFAULT_INDICATOR_DASH).map((d) => d * px),\n },\n };\n}\n","/**\n * The minimap's input: press to recenter the main camera on a world point,\n * drag to keep it there. Both read `rootView` — never `view`, which inside the\n * minimap's own view answers the minimap's camera.\n */\nimport type { Action, InvocationCtx, OngoingHandle } from '@weasel-js/routing';\nimport type { ViewApi } from '@weasel-js/core';\n\n/** Action id for `minimapCenterAction`. */\nexport const MINIMAP_CENTER = 'minimap.center';\n/** Action id for `minimapPanAction`. */\nexport const MINIMAP_PAN = 'minimap.pan';\n\n/** Move `root` so world point (`x`, `y`) sits at the center of its host,\n * keeping its scale. A no-op while the host is unmeasured. */\nexport function centerRootOn(root: ViewApi, x: number, y: number): void {\n const size = root.hostSize?.();\n if (!size) return;\n const v = root.get();\n root.set({\n x: x - size.width / (2 * v.scale.x),\n y: y - size.height / (2 * v.scale.y),\n scale: v.scale,\n });\n}\n\nfunction rootOf(deps: InvocationCtx['deps']): ViewApi | undefined {\n return deps.rootView;\n}\n\n/** Recenter the main camera on the pressed world point. */\nexport function minimapCenterAction(): Action {\n return {\n id: MINIMAP_CENTER,\n label: 'Minimap — center the view here',\n requires: ['rootView'],\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const p = params as { worldX?: number; worldY?: number } | undefined;\n const root = rootOf(deps);\n if (!root || p?.worldX === undefined || p.worldY === undefined) return;\n centerRootOn(root, p.worldX, p.worldY);\n },\n },\n };\n}\n\n/** Keep the main camera centered on the pointer's world point while dragging.\n * Pair it with `minimap.center` on `pointerDown`, which handles the press.\n * A recenter lands the moment it is computed, so there is nothing to commit\n * and a cancel ends where a release does. */\nexport function minimapPanAction(): Action {\n return {\n id: MINIMAP_PAN,\n label: 'Minimap — pan the view',\n requires: ['rootView'],\n invoker: {\n timing: 'ongoing',\n start(ctx: InvocationCtx): OngoingHandle {\n const root = rootOf(ctx.deps);\n if (!root) return {};\n // The press already centered (`minimap.center`); the move that crossed\n // the drag threshold is pumped to `onMove` like every later one.\n return {\n onMove: (m) => {\n const p = m.drag?.current ?? m.world;\n centerRootOn(root, p.x, p.y);\n },\n onEnd: () => {},\n };\n },\n },\n };\n}\n","/**\n * The minimap's chrome: the main camera's visible rect, drawn in the minimap,\n * and the linked crosshair, drawn in every view the pointer is not over.\n */\nimport type { RenderLayer } from '@weasel-js/core';\nimport type { View } from '@weasel-js/core';\nimport type { DrawCommand } from '@weasel-js/core';\nimport type { ViewApi } from '@weasel-js/core';\nimport type { PointerWorldPos } from '@weasel-js/routing/react';\nimport { computeIndicatorCommand, type IndicatorStyle } from './minimapMath';\n\n/** What a layer's `data` carries for this feature: the view it draws for. */\nfunction viewIdOf(data: unknown): string | null {\n return (data as { viewId?: string | null } | null)?.viewId ?? null;\n}\n\n/**\n * The main camera's visible world rect, painted only in view `viewId`. Its\n * stroke and dash are screen pixels whatever the minimap's scale.\n */\nexport function createIndicatorLayer(opts: {\n viewId: string;\n root: () => ViewApi | undefined;\n style?: IndicatorStyle;\n}): RenderLayer<unknown> {\n const { viewId, root, style = {} } = opts;\n return {\n id: `${viewId}.indicator`,\n label: 'Minimap indicator',\n draw: (data, view: View): DrawCommand[] => {\n if (viewIdOf(data) !== viewId) return [];\n const r = root();\n const size = r?.hostSize?.();\n if (!r || !size) return [];\n return [computeIndicatorCommand(r.get(), size, style, view)];\n },\n };\n}\n\n/** Screen-pixel geometry of the crosshair: arm length, the gap at its center,\n * line thickness, and the dark halo that keeps it legible over any content. */\nconst ARM = 9;\nconst GAP = 3;\nconst THICK = 2;\nconst HALO = 1;\n/** The halo's color: dark enough to separate the accent from any content. */\nexport const CROSSHAIR_HALO = 'rgba(0, 0, 0, 0.6)';\n\n/** An axis-aligned rect, `{ x, y, w, h }`. */\nexport interface CrosshairRect { x: number; y: number; w: number; h: number }\n\n/**\n * The linked crosshair at (`x`, `y`) as rects in the caller's units, where one\n * screen pixel is `unitsPerPx` of them: four halo rects to paint first, then\n * four bars. A painter with no weasel renderer — a 2D canvas — draws the same\n * crosshair from these.\n */\nexport function crosshairRects(\n x: number, y: number, unitsPerPx: number,\n): { halo: CrosshairRect[]; bars: CrosshairRect[] } {\n const k = unitsPerPx;\n const arm = ARM * k, gap = GAP * k, t = THICK * k, h = HALO * k;\n const bars: CrosshairRect[] = [\n { x: x - gap - arm, y: y - t / 2, w: arm, h: t },\n { x: x + gap, y: y - t / 2, w: arm, h: t },\n { x: x - t / 2, y: y - gap - arm, w: t, h: arm },\n { x: x - t / 2, y: y + gap, w: t, h: arm },\n ];\n const halo = bars.map((b) => ({ x: b.x - h, y: b.y - h, w: b.w + 2 * h, h: b.h + 2 * h }));\n return { halo, bars };\n}\n\n/**\n * A crosshair at the pointer's world point, in each of `views` the pointer is\n * not over — the other side of a linked cursor. A fixed screen size in every\n * view. `views` omitted paints in every view.\n */\nexport function createLinkedCursorLayer(opts: {\n id: string;\n pointer: () => PointerWorldPos;\n color: () => string;\n views?: readonly (string | null)[];\n}): RenderLayer<unknown> {\n const { id, pointer, color, views } = opts;\n return {\n id,\n label: 'Linked cursor',\n draw: (data, view: View): DrawCommand[] => {\n const p = pointer();\n const here = viewIdOf(data);\n if (!p || p.viewId === here || (views && !views.includes(here))) return [];\n const { halo, bars } = crosshairRects(p.worldX, p.worldY, 1 / view.scale.x);\n const rect = (r: CrosshairRect, c: string): DrawCommand => ({\n kind: 'path',\n path: { kind: 'rect', x: r.x, y: r.y, width: r.w, height: r.h },\n fill: { paint: { kind: 'solid', color: c } },\n });\n const accent = color();\n return [\n ...halo.map((r) => rect(r, CROSSHAIR_HALO)),\n ...bars.map((r) => rect(r, accent)),\n ];\n },\n };\n}\n","/**\n * A minimap over a `<SceneCanvas>`, written as one `SurfaceContribution`: a\n * view through a fit camera, the pan that moves the main camera from inside\n * it, the visible-rect indicator, and a linked crosshair.\n */\nimport type { Dims, RenderLayer } from '@weasel-js/core';\nimport type { View } from '@weasel-js/core';\nimport type { Scene } from '@weasel-js/core';\nimport type { PoseDescriptor } from '@weasel-js/core';\nimport { AUTO_POSE_DESCRIPTOR } from '@weasel-js/core';\nimport type { PointerWorldPos } from '@weasel-js/routing/react';\nimport type { CanvasExtensionApi } from '@weasel-js/core';\nimport type { ViewRect } from '@weasel-js/core';\nimport type { ContributionDepReader, SurfaceContribution } from '@weasel-js/core';\nimport {\n computeFitView, FALLBACK_FIT_VIEW, type IndicatorStyle, type MinimapFit,\n} from './minimapMath';\nimport {\n MINIMAP_CENTER, MINIMAP_PAN, minimapCenterAction, minimapPanAction,\n} from './actions';\nimport { createIndicatorLayer, createLinkedCursorLayer } from './layers';\nimport { accentColorOf, FALLBACK_ACCENT } from '@weasel-js/core';\n\n\n/**\n * The linked crosshair's live half: follows the pointer store, and asks the\n * surface to repaint whenever the crosshair shows, moves, or has just hidden.\n * `ownViews` are the view ids this surface paints; the crosshair shows in any\n * of them the pointer is not over.\n */\nfunction linkedCursor(id: string, ownViews: readonly (string | null)[]) {\n let pointer: PointerWorldPos = null;\n let color = FALLBACK_ACCENT;\n const visible = (p: PointerWorldPos): boolean =>\n p !== null && ownViews.some((v) => v !== p.viewId);\n const layer = createLinkedCursorLayer({ id, pointer: () => pointer, color: () => color, views: ownViews });\n const attach = (api: CanvasExtensionApi, deps: ContributionDepReader): (() => void) => {\n const store = deps.get('pointer');\n if (!store) return () => {};\n color = accentColorOf(api.element);\n pointer = store.get();\n const off = store.subscribe(() => {\n const prev = pointer;\n pointer = store.get();\n if (visible(prev) || visible(pointer)) api.requestRedraw();\n });\n return () => {\n off();\n pointer = null;\n api.requestRedraw();\n };\n };\n return { layer, attach };\n}\n\n/** Options for {@link createLinkedCursorContribution}. */\nexport interface LinkedCursorOptions {\n /** Entry id. Default `'linked-cursor'`. */\n id?: string;\n /** Views on this surface the crosshair may paint in. Default `[null]`, the\n * surface's own camera — the main canvas beside a detached minimap. */\n views?: readonly (string | null)[];\n}\n\n/**\n * The linked crosshair alone: this surface paints the pointer's position\n * whenever the pointer is over some other view — another surface under the\n * same `<PointerContextProvider>`, such as a detached `<MinimapCanvas>`.\n *\n * A surface carrying `createMinimapContribution` already has one, in both its\n * views; this is for a surface without.\n */\nexport function createLinkedCursorContribution(opts: LinkedCursorOptions = {}): SurfaceContribution {\n const id = opts.id ?? 'linked-cursor';\n const cursor = linkedCursor(id, opts.views ?? [null]);\n return { id, eligibility: { always: true }, overlay: cursor.layer, attach: cursor.attach };\n}\n\n/** Options for {@link createMinimapContribution}. */\nexport interface MinimapContributionOptions<TData = unknown, TLayer extends string = string, TPose = unknown> {\n /** View and entry id. Default `'minimap'`. */\n id?: string;\n /** Where the minimap paints, in CSS pixels from the canvas top-left. A\n * function is re-read every frame, so it can follow the canvas's size. */\n rect: ViewRect | ((outer: View, dims: Dims) => ViewRect);\n /** What the minimap frames. Default `'scene'`, the union of leaf poses. */\n fit?: MinimapFit<TData, TLayer, TPose>;\n /** The surface layers the minimap paints. Default: all of them. */\n layers?: (surface: readonly RenderLayer<unknown>[]) => readonly RenderLayer<unknown>[];\n /** Ground painted before the minimap's layers, so the main canvas does not\n * show through. */\n background?: string;\n /** The visible-rect indicator's stroke. */\n indicatorStyle?: IndicatorStyle;\n}\n\nconst DEFAULT_BACKGROUND = '#18181c';\n\n/**\n * A minimap inside a `<SceneCanvas>`, installed with one entry:\n *\n * ```tsx\n * const minimap = useMemo(() => createMinimapContribution({ rect: { x: 8, y: 8, w: 160, h: 110 } }), []);\n * <SceneCanvas ambient={[minimap]} … />\n * ```\n *\n * Press in it to center the main camera on that point, drag to keep it there.\n * It shows the main camera's visible rect, and a crosshair where the pointer is\n * over the other view. Build it once — each instance keeps the live state its\n * layers read.\n */\nexport function createMinimapContribution<TData = unknown, TLayer extends string = string, TPose = unknown>(\n opts: MinimapContributionOptions<TData, TLayer, TPose>,\n): SurfaceContribution {\n const id = opts.id ?? 'minimap';\n const fit = (opts.fit ?? 'scene') as MinimapFit<unknown, string, unknown>;\n let deps: ContributionDepReader | null = null;\n\n // The fit follows the scene, so it is recomputed only when the scene's\n // version or the minimap's size changes — it is read on every frame and\n // every event.\n let cached: { scene: unknown; version: number; shown: number; w: number; h: number; view: View } | null = null;\n const fitCamera = (outer: View, dims: Dims): View => {\n const scene = deps?.get('scene') as Scene<unknown, string, unknown> | undefined;\n if (!scene) return FALLBACK_FIT_VIEW;\n const r = typeof opts.rect === 'function' ? opts.rect(outer, dims) : opts.rect;\n const version = scene.getVersion();\n // Only a live fit reads overrides, so only it refits when they publish.\n const shown = fit === 'live' ? scene.overrides.getGeneration() : 0;\n if (cached && cached.scene === scene && cached.version === version && cached.shown === shown\n && cached.w === r.w && cached.h === r.h) {\n return cached.view;\n }\n const descriptor = (deps?.get('poseDescriptor') ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<unknown>;\n const view = computeFitView(scene, { width: r.w, height: r.h }, fit, descriptor);\n cached = { scene, version, shown, w: r.w, h: r.h, view };\n return view;\n };\n\n const cursor = linkedCursor(`${id}.cursor`, [null, id]);\n const indicator = createIndicatorLayer({\n viewId: id,\n root: () => deps?.get('rootView'),\n ...(opts.indicatorStyle ? { style: opts.indicatorStyle } : {}),\n });\n\n return {\n id,\n eligibility: { always: true },\n views: [{\n id,\n bounds: opts.rect,\n view: fitCamera,\n interactive: true,\n background: opts.background ?? DEFAULT_BACKGROUND,\n ...(opts.layers ? { layers: opts.layers } : {}),\n }],\n actions: [minimapCenterAction(), minimapPanAction()],\n bindings: [\n { spec: { kind: 'pointerDown' }, actionId: MINIMAP_CENTER, opts: { views: [id] } },\n { spec: { kind: 'drag' }, actionId: MINIMAP_PAN, opts: { views: [id] } },\n ],\n overlay: [indicator, cursor.layer],\n attach: (api, reader) => {\n deps = reader;\n const detachCursor = cursor.attach(api, reader);\n // The fit camera had no scene until now.\n api.requestRedraw();\n return () => {\n detachCursor();\n deps = null;\n cached = null;\n };\n },\n };\n}\n","import { useMemo, useSyncExternalStore } from 'react';\nimport { normalizeView, type PoseDescriptor, type Scene, type View } from '@weasel-js/core';\nimport { computeFitView, type MinimapFit } from './minimapMath';\n\nconst NEVER = (): (() => void) => () => {};\n\n/**\n * The minimap's fit view, recomputed when what it frames changes: the scene's\n * version always, and the overrides only under a `\"live\"` fit, the one policy\n * that reads them.\n */\nexport function useFitView<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n width: number,\n height: number,\n fit: MinimapFit<TData, TLayer, TPose>,\n descriptor: PoseDescriptor<TPose>,\n): View {\n const sceneVersion = useSyncExternalStore(scene.subscribe, scene.getVersion, scene.getVersion);\n const overrideGeneration = useSyncExternalStore(\n fit === 'live' ? scene.overrides.subscribe : NEVER,\n scene.overrides.getGeneration,\n scene.overrides.getGeneration,\n );\n return useMemo<View>(() => {\n // A `fit` function's result is the caller's; pointer math divides by its scale.\n return normalizeView(computeFitView(scene, { width, height }, fit, descriptor));\n // The two counters are what say the scene's contents moved.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [scene, sceneVersion, overrideGeneration, width, height, fit, descriptor]);\n}\n","import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n} from 'react';\nimport type { Ref } from 'react';\nimport {\n ActionsProvider,\n ActiveToolContextProvider,\n DepRegistryProvider,\n useActionsRegistry,\n useDepSource,\n useGestureDispatcher,\n} from '@weasel-js/routing/react';\nimport { assignRef, useLatest } from '@weasel-js/react';\nimport type { Tool } from '@weasel-js/routing';\nimport { PointerProviderIfRoot } from '@weasel-js/core';\nimport { usePointerContext, usePointerPosition } from '@weasel-js/routing/react';\nimport {\n MINIMAP_CENTER, MINIMAP_PAN, minimapCenterAction, minimapPanAction,\n} from './actions';\nimport { createLinkedCursorLayer } from './layers';\nimport type { ViewApi } from '@weasel-js/core';\nimport { SceneViewCanvas } from '@weasel-js/core';\nimport { computeIndicatorCommand } from './minimapMath';\nimport { useFitView } from './useFitView';\nimport type { IndicatorStyle, MinimapFit } from './minimapMath';\nimport type { SceneViewDrawOne, SceneViewLayers } from '@weasel-js/core';\nimport type { DrawCommand } from '@weasel-js/core';\nimport { normalizeView, type View } from '@weasel-js/core';\nimport type { Scene } from '@weasel-js/core';\nimport type { PoseDescriptor } from '@weasel-js/core';\nimport type { Animator } from '@weasel-js/animator';\nimport { AUTO_POSE_DESCRIPTOR } from '@weasel-js/core';\nimport { accentColorOf } from '@weasel-js/core';\n\n/** Props for `<MinimapCanvas>`. */\nexport interface MinimapCanvasProps<TData, TLayer extends string, TPose> {\n /** Same scene the main canvas is rendering. */\n scene: Scene<TData, TLayer, TPose>;\n /** Main canvas's view — used to position the visible-window indicator and\n * to preserve `scale` when the minimap recenters. */\n mainView: View;\n /** Main canvas's CSS-pixel dims. Used to compute the visible-window rect\n * and the recenter offset. */\n mainViewDims: { width: number; height: number };\n /** Setter called on click (single recenter) and during drag (continuous\n * recenter). Receives a `View` with the same `scale` as `mainView` and\n * `(x, y)` shifted so the pointer's world point lands at the main\n * canvas's center. */\n onMainViewChange: (next: View) => void;\n /** Minimap's own CSS-pixel dims. */\n width: number;\n height: number;\n /** Consumer's draw callback. Applied through the derived fit view; same\n * signature as `<SceneCanvas>`'s scene-slot `drawOne` so the consumer\n * can reuse it (typically simplified — just colored AABBs). */\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>;\n /** Fit policy. Defaults to `\"scene\"` (AABB union of leaf poses). See\n * `MinimapFit` for the full shape.\n *\n * `\"scene\"` frames **document** poses, not `scene.overrides` — a node\n * moved by an override paints where the override puts it, outside the\n * frame if it goes there, rather than making the whole minimap rescale on\n * every frame of a drag or a settle. `\"live\"` frames the overrides too. */\n fit?: MinimapFit<TData, TLayer, TPose>;\n /** How to read poses. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<TPose>;\n /** Optional per-id alpha multiplier, mirroring `<SceneCanvas>`'s scene-slot\n * `alphaFor`. Pass the same function the main canvas uses so a\n * scoping-dim treatment shows up in the minimap too. */\n alphaFor?: (id: string) => number;\n /** Hide scene layers in the minimap, keyed `scene:<layerId>` as on\n * `<SceneCanvas>` — pass the main canvas's map so both show the same set. */\n layerVisibility?: SceneViewLayers['layerVisibility'];\n /** Paint order for the minimap, keyed as `layerVisibility`. */\n layerOrder?: SceneViewLayers['layerOrder'];\n /** Optional animator: the minimap repaints on its ticks and paints its\n * `colorOverrides`. Pass the main canvas's animator so both show the same\n * colors. */\n animator?: Animator;\n /** Visual tuning of the indicator stroke. */\n indicatorStyle?: IndicatorStyle;\n /** CSS class for the canvas. Its size is `width` × `height` unless a rule\n * on this class sizes it. */\n className?: string;\n /** Forwarded ref to the underlying `<canvas>`. */\n canvasRef?: Ref<HTMLCanvasElement>;\n /** The view id this minimap publishes its pointer under. Default\n * `'minimap'`. */\n id?: string;\n}\n\n/** The minimap's own bindings. Unscoped: this dispatcher routes no views, so\n * every input on it is the minimap's. */\nconst MINIMAP_INPUT: Tool = {\n id: 'minimap',\n eligibility: { always: true },\n bindings: [\n { spec: { kind: 'pointerDown' }, actionId: MINIMAP_CENTER },\n { spec: { kind: 'drag' }, actionId: MINIMAP_PAN },\n ],\n};\nconst TOOLS_BY_ID: ReadonlyMap<string, Tool> = new Map([[MINIMAP_INPUT.id, MINIMAP_INPUT]]);\nconst CHANNELS = { wheel: false, pinch: false, contextMenu: false, ingest: false } as const;\n\nfunction MinimapCanvasInner<TData, TLayer extends string, TPose>(\n props: MinimapCanvasProps<TData, TLayer, TPose>,\n) {\n const {\n scene,\n mainView: mainViewProp,\n mainViewDims,\n onMainViewChange,\n width,\n height,\n drawOne,\n alphaFor,\n layerVisibility,\n layerOrder,\n animator,\n fit = 'scene',\n poseDescriptor,\n indicatorStyle,\n className,\n canvasRef,\n id = 'minimap',\n } = props;\n const mainView = normalizeView(mainViewProp);\n\n const descriptor = (poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n const fitView = useFitView(scene, width, height, fit, descriptor);\n\n const indicatorCmd = useMemo<DrawCommand>(() => {\n return computeIndicatorCommand(mainView, mainViewDims, indicatorStyle, fitView);\n }, [mainView, mainViewDims, indicatorStyle, fitView]);\n\n const localCanvasRef = useRef<HTMLCanvasElement | null>(null);\n const mainViewRef = useLatest(mainView);\n const mainViewDimsRef = useLatest(mainViewDims);\n const onMainViewChangeRef = useLatest(onMainViewChange);\n const fitViewRef = useLatest(fitView);\n\n // The main canvas's camera, which is what the minimap's actions move.\n const rootView = useMemo<ViewApi>(() => ({\n get: () => mainViewRef.current,\n set: (v) => onMainViewChangeRef.current(v),\n hostSize: () => mainViewDimsRef.current,\n }), [mainViewRef, onMainViewChangeRef, mainViewDimsRef]);\n useDepSource('rootView', () => rootView);\n\n const actions = useActionsRegistry();\n useEffect(() => {\n if (!actions) return;\n const off = [actions.register(minimapCenterAction()), actions.register(minimapPanAction())];\n return () => { for (const u of off) u(); };\n }, [actions]);\n\n /** Client point → world, through the fit camera. */\n const clientToWorld = useCallback((cx: number, cy: number) => {\n const rect = localCanvasRef.current?.getBoundingClientRect();\n const fv = fitViewRef.current;\n return {\n x: fv.x + (cx - (rect?.left ?? 0)) / fv.scale.x,\n y: fv.y + (cy - (rect?.top ?? 0)) / fv.scale.y,\n };\n }, [fitViewRef]);\n\n useGestureDispatcher({\n canvasRef: localCanvasRef,\n actions: actions!,\n entriesById: TOOLS_BY_ID,\n clientToWorld,\n keyboard: false,\n channels: CHANNELS,\n });\n\n const pointerStore = usePointerContext();\n useEffect(() => {\n const el = localCanvasRef.current;\n if (!el || !pointerStore) return;\n const onMove = (e: PointerEvent): void => {\n const w = clientToWorld(e.clientX, e.clientY);\n pointerStore.set({ worldX: w.x, worldY: w.y, viewId: id });\n };\n const onLeave = (e: PointerEvent): void => {\n if (e.buttons !== 0) return;\n if (pointerStore.get()?.viewId === id) pointerStore.set(null);\n };\n el.addEventListener('pointermove', onMove);\n el.addEventListener('pointerleave', onLeave);\n return () => {\n el.removeEventListener('pointermove', onMove);\n el.removeEventListener('pointerleave', onLeave);\n };\n }, [pointerStore, clientToWorld, id]);\n\n const pointer = usePointerPosition();\n const cursorLayer = useMemo(() => createLinkedCursorLayer({\n id: `${id}.cursor`,\n pointer: () => pointer,\n color: () => accentColorOf(localCanvasRef.current),\n }), [id, pointer]);\n\n const extraCommands = useMemo<DrawCommand[]>(() => [\n indicatorCmd,\n ...cursorLayer.draw({ viewId: id }, fitView, { width, height }),\n ], [indicatorCmd, cursorLayer, id, fitView, width, height]);\n\n // `<SceneViewCanvas>` doesn't expose its element except via `canvasRef`, so\n // we route both our internal ref and the consumer's ref through one setter.\n const setCanvasRef = useCallback((el: HTMLCanvasElement | null) => {\n localCanvasRef.current = el;\n assignRef(canvasRef, el);\n }, [canvasRef]);\n\n return (\n <SceneViewCanvas\n scene={scene}\n view={fitView}\n width={width}\n height={height}\n drawOne={drawOne}\n extraCommands={extraCommands}\n alphaFor={alphaFor}\n layerVisibility={layerVisibility}\n layerOrder={layerOrder}\n animator={animator}\n className={className}\n canvasRef={setCanvasRef}\n />\n );\n}\n\n/**\n * Opinionated minimap on a canvas of its own. Renders the same `scene` the\n * main canvas is showing through a derived \"fit\" view, and overlays a dashed\n * indicator of where the main canvas is looking. Press anywhere in it to\n * recenter the main view on that world point; drag to pan continuously.\n *\n * Its dispatcher, actions and deps are its own, so it never competes with\n * the main canvas for input. Put one `<PointerContextProvider>` around it and\n * the main canvas for linked cursors both ways.\n *\n * `forwardRef` is not used because the component is generic over\n * `<TData, TLayer, TPose>` and React's `forwardRef` erases generics. The\n * `canvasRef` prop is the plain-prop equivalent.\n */\nexport function MinimapCanvas<TData, TLayer extends string, TPose>(\n props: MinimapCanvasProps<TData, TLayer, TPose>,\n) {\n return (\n <DepRegistryProvider>\n <ActionsProvider>\n <ActiveToolContextProvider>\n <PointerProviderIfRoot>\n <MinimapCanvasInner {...props} />\n </PointerProviderIfRoot>\n </ActiveToolContextProvider>\n </ActionsProvider>\n </DepRegistryProvider>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "@weasel-js/minimap",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.10.0",
4
+ "description": "A minimap for a weasel canvas: a read-only overview with a viewport indicator you drag to pan, fit math, and a linked crosshair between surfaces.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "author": "orochi235",
19
+ "homepage": "https://orochi235.github.io/weasel/",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/orochi235/weasel.git",
23
+ "directory": "packages/minimap"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/orochi235/weasel/issues"
27
+ },
28
+ "engines": {
29
+ "node": ">=22"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsup"
38
+ },
39
+ "dependencies": {
40
+ "@weasel-js/react": "1.10.0",
41
+ "@weasel-js/routing": "1.10.0"
42
+ },
43
+ "peerDependencies": {
44
+ "@weasel-js/animator": "1.10.0",
45
+ "@weasel-js/core": "1.10.0",
46
+ "react": ">=18"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public",
50
+ "provenance": true
51
+ }
52
+ }