@weasel-js/minimap 0.0.0-stage → 1.9.4

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,264 @@
1
+ import { View, Bounds, Scene, ViewportDims, PoseDescriptor, PathDrawCommand, ViewRect, Dims, RenderLayer, SurfaceContribution, ViewApi, SceneViewDrawOne, SceneViewLayers, Animator } 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
+
7
+ /**
8
+ * Fallback view returned when there is nothing to fit (empty scene, etc.).
9
+ * Identity view at the origin — produces a 1:1 mapping centered on (0, 0).
10
+ */
11
+ declare const FALLBACK_FIT_VIEW: View;
12
+ /**
13
+ * Fit policy for `<MinimapCanvas>` / `computeFitView`.
14
+ *
15
+ * - `"scene"` — union of leaf-node pose AABBs (via the pose descriptor).
16
+ * - `{ kind: "world", rect }` — caller-supplied world rect.
17
+ * - `(scene, dims) => View` — caller derives the view directly.
18
+ */
19
+ type MinimapFit<TData, TLayer extends string, TPose> = 'scene' | {
20
+ kind: 'world';
21
+ rect: Bounds;
22
+ } | ((scene: Scene<TData, TLayer, TPose>, dims: ViewportDims) => View);
23
+ /** Style for the visible-window indicator rect drawn over the minimap. */
24
+ interface IndicatorStyle {
25
+ /** Stroke color (any CSS color). Defaults to `'#ffffff'`. */
26
+ stroke?: string;
27
+ /** Stroke width in CSS px. Defaults to `1`. */
28
+ width?: number;
29
+ /** Dash pattern in CSS px. Defaults to `[2, 3]`. */
30
+ dash?: number[];
31
+ }
32
+ /** Options for {@link computeFitView}. */
33
+ interface ComputeFitViewOptions {
34
+ /** Inset (CSS px) around the fit rect on each side. Defaults to `8`. */
35
+ padding?: number;
36
+ }
37
+ /**
38
+ * Derive a `View` that fits the chosen world rect into a minimap of
39
+ * `dims.width × dims.height` CSS px. Uniform scale on both axes; the rect
40
+ * is centered with `padding` (default 8) on each side. The smaller-axis
41
+ * fit ratio wins, so the whole rect is visible.
42
+ *
43
+ * - `fit === "scene"`: union AABB of leaf-node poses via `descriptor`.
44
+ * Empty scene → {@link FALLBACK_FIT_VIEW}.
45
+ * - `fit` is `{ kind: "world", rect }`: fit `rect`.
46
+ * - `fit` is a function: called with `(scene, dims)`; its return is
47
+ * passed through unchanged.
48
+ *
49
+ * A rect with one zero axis (a line) is fitted along the other. One with no
50
+ * positive axis falls back to identity scale centered on its top-left.
51
+ */
52
+ 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;
53
+ /**
54
+ * Build the visible-window indicator `DrawCommand` for the minimap.
55
+ *
56
+ * The main canvas's visible world rect is:
57
+ * ```
58
+ * worldX = mainView.x
59
+ * worldY = mainView.y
60
+ * worldW = mainViewDims.width / mainView.scale.x
61
+ * worldH = mainViewDims.height / mainView.scale.y
62
+ * ```
63
+ * That world rect is then transformed into the minimap's screen
64
+ * coordinates through `minimapView`.
65
+ *
66
+ * The returned command is a dashed-stroke `path` rect with no fill. Pass the
67
+ * `minimapView` it will be drawn through, so the stroke width and dash come
68
+ * out in CSS px at any fit scale; without it they are in world units.
69
+ */
70
+ declare function computeIndicatorCommand(mainView: View, mainViewDims: ViewportDims, style?: IndicatorStyle, minimapView?: View): PathDrawCommand;
71
+
72
+ /**
73
+ * A minimap over a `<SceneCanvas>`, written as one `SurfaceContribution`: a
74
+ * view through a fit camera, the pan that moves the main camera from inside
75
+ * it, the visible-rect indicator, and a linked crosshair.
76
+ */
77
+
78
+ /** Options for {@link createLinkedCursorContribution}. */
79
+ interface LinkedCursorOptions {
80
+ /** Entry id. Default `'linked-cursor'`. */
81
+ id?: string;
82
+ /** Views on this surface the crosshair may paint in. Default `[null]`, the
83
+ * surface's own camera — the main canvas beside a detached minimap. */
84
+ views?: readonly (string | null)[];
85
+ }
86
+ /**
87
+ * The linked crosshair alone: this surface paints the pointer's position
88
+ * whenever the pointer is over some other view — another surface under the
89
+ * same `<PointerContextProvider>`, such as a detached `<MinimapCanvas>`.
90
+ *
91
+ * A surface carrying `createMinimapContribution` already has one, in both its
92
+ * views; this is for a surface without.
93
+ */
94
+ declare function createLinkedCursorContribution(opts?: LinkedCursorOptions): SurfaceContribution;
95
+ /** Options for {@link createMinimapContribution}. */
96
+ interface MinimapContributionOptions<TData = unknown, TLayer extends string = string, TPose = unknown> {
97
+ /** View and entry id. Default `'minimap'`. */
98
+ id?: string;
99
+ /** Where the minimap paints, in CSS pixels from the canvas top-left. A
100
+ * function is re-read every frame, so it can follow the canvas's size. */
101
+ rect: ViewRect | ((outer: View, dims: Dims) => ViewRect);
102
+ /** What the minimap frames. Default `'scene'`, the union of leaf poses. */
103
+ fit?: MinimapFit<TData, TLayer, TPose>;
104
+ /** The surface layers the minimap paints. Default: all of them. */
105
+ layers?: (surface: readonly RenderLayer<unknown>[]) => readonly RenderLayer<unknown>[];
106
+ /** Ground painted before the minimap's layers, so the main canvas does not
107
+ * show through. */
108
+ background?: string;
109
+ /** The visible-rect indicator's stroke. */
110
+ indicatorStyle?: IndicatorStyle;
111
+ }
112
+ /**
113
+ * A minimap inside a `<SceneCanvas>`, installed with one entry:
114
+ *
115
+ * ```tsx
116
+ * const minimap = useMemo(() => createMinimapContribution({ rect: { x: 8, y: 8, w: 160, h: 110 } }), []);
117
+ * <SceneCanvas ambient={[minimap]} … />
118
+ * ```
119
+ *
120
+ * Press in it to center the main camera on that point, drag to keep it there.
121
+ * It shows the main camera's visible rect, and a crosshair where the pointer is
122
+ * over the other view. Build it once — each instance keeps the live state its
123
+ * layers read.
124
+ */
125
+ declare function createMinimapContribution<TData = unknown, TLayer extends string = string, TPose = unknown>(opts: MinimapContributionOptions<TData, TLayer, TPose>): SurfaceContribution;
126
+
127
+ /**
128
+ * The minimap's input: press to recenter the main camera on a world point,
129
+ * drag to keep it there. Both read `rootView` — never `view`, which inside the
130
+ * minimap's own view answers the minimap's camera.
131
+ */
132
+
133
+ /** Action id for `minimapCenterAction`. */
134
+ declare const MINIMAP_CENTER = "minimap.center";
135
+ /** Action id for `minimapPanAction`. */
136
+ declare const MINIMAP_PAN = "minimap.pan";
137
+ /** Move `root` so world point (`x`, `y`) sits at the center of its host,
138
+ * keeping its scale. A no-op while the host is unmeasured. */
139
+ declare function centerRootOn(root: ViewApi, x: number, y: number): void;
140
+ /** Recenter the main camera on the pressed world point. */
141
+ declare function minimapCenterAction(): Action;
142
+ /** Keep the main camera centered on the pointer's world point while dragging.
143
+ * Pair it with `minimap.center` on `pointerDown`, which handles the press.
144
+ * A recenter lands the moment it is computed, so there is nothing to commit
145
+ * and a cancel ends where a release does. */
146
+ declare function minimapPanAction(): Action;
147
+
148
+ /**
149
+ * The minimap's chrome: the main camera's visible rect, drawn in the minimap,
150
+ * and the linked crosshair, drawn in every view the pointer is not over.
151
+ */
152
+
153
+ /**
154
+ * The main camera's visible world rect, painted only in view `viewId`. Its
155
+ * stroke and dash are screen pixels whatever the minimap's scale.
156
+ */
157
+ declare function createIndicatorLayer(opts: {
158
+ viewId: string;
159
+ root: () => ViewApi | undefined;
160
+ style?: IndicatorStyle;
161
+ }): RenderLayer<unknown>;
162
+ /** The halo's color: dark enough to separate the accent from any content. */
163
+ declare const CROSSHAIR_HALO = "rgba(0, 0, 0, 0.6)";
164
+ /** An axis-aligned rect, `{ x, y, w, h }`. */
165
+ interface CrosshairRect {
166
+ x: number;
167
+ y: number;
168
+ w: number;
169
+ h: number;
170
+ }
171
+ /**
172
+ * The linked crosshair at (`x`, `y`) as rects in the caller's units, where one
173
+ * screen pixel is `unitsPerPx` of them: four halo rects to paint first, then
174
+ * four bars. A painter with no weasel renderer — a 2D canvas — draws the same
175
+ * crosshair from these.
176
+ */
177
+ declare function crosshairRects(x: number, y: number, unitsPerPx: number): {
178
+ halo: CrosshairRect[];
179
+ bars: CrosshairRect[];
180
+ };
181
+ /**
182
+ * A crosshair at the pointer's world point, in each of `views` the pointer is
183
+ * not over — the other side of a linked cursor. A fixed screen size in every
184
+ * view. `views` omitted paints in every view.
185
+ */
186
+ declare function createLinkedCursorLayer(opts: {
187
+ id: string;
188
+ pointer: () => PointerWorldPos;
189
+ color: () => string;
190
+ views?: readonly (string | null)[];
191
+ }): RenderLayer<unknown>;
192
+
193
+ /** Props for `<MinimapCanvas>`. */
194
+ interface MinimapCanvasProps<TData, TLayer extends string, TPose> {
195
+ /** Same scene the main canvas is rendering. */
196
+ scene: Scene<TData, TLayer, TPose>;
197
+ /** Main canvas's view — used to position the visible-window indicator and
198
+ * to preserve `scale` when the minimap recenters. */
199
+ mainView: View;
200
+ /** Main canvas's CSS-pixel dims. Used to compute the visible-window rect
201
+ * and the recenter offset. */
202
+ mainViewDims: {
203
+ width: number;
204
+ height: number;
205
+ };
206
+ /** Setter called on click (single recenter) and during drag (continuous
207
+ * recenter). Receives a `View` with the same `scale` as `mainView` and
208
+ * `(x, y)` shifted so the pointer's world point lands at the main
209
+ * canvas's center. */
210
+ onMainViewChange: (next: View) => void;
211
+ /** Minimap's own CSS-pixel dims. */
212
+ width: number;
213
+ height: number;
214
+ /** Consumer's draw callback. Applied through the derived fit view; same
215
+ * signature as `<SceneCanvas>`'s scene-slot `drawOne` so the consumer
216
+ * can reuse it (typically simplified — just colored AABBs). */
217
+ drawOne: SceneViewDrawOne<TData, TLayer, TPose>;
218
+ /** Fit policy. Defaults to `"scene"` (AABB union of leaf poses). See
219
+ * `MinimapFit` for the full shape.
220
+ *
221
+ * Framing is derived from **document** poses, not `scene.overrides` — a
222
+ * node moved by an override paints where the override puts it, outside the
223
+ * frame if it goes there, rather than making the whole minimap rescale on
224
+ * every frame of a drag or a settle. */
225
+ fit?: MinimapFit<TData, TLayer, TPose>;
226
+ /** How to read poses. Default `AUTO_POSE_DESCRIPTOR`. */
227
+ poseDescriptor?: PoseDescriptor<TPose>;
228
+ /** Optional per-id alpha multiplier, mirroring `<SceneCanvas>`'s scene-slot
229
+ * `alphaFor`. Pass the same function the main canvas uses so a
230
+ * scoping-dim treatment shows up in the minimap too. */
231
+ alphaFor?: (id: string) => number;
232
+ /** Hide scene layers in the minimap, keyed `scene:<layerId>` as on
233
+ * `<SceneCanvas>` — pass the main canvas's map so both show the same set. */
234
+ layerVisibility?: SceneViewLayers['layerVisibility'];
235
+ /** Paint order for the minimap, keyed as `layerVisibility`. */
236
+ layerOrder?: SceneViewLayers['layerOrder'];
237
+ /** Optional animator: the minimap repaints on its ticks and paints its
238
+ * `colorOverrides`. Pass the main canvas's animator so both show the same
239
+ * colors. */
240
+ animator?: Animator;
241
+ /** Visual tuning of the indicator stroke. */
242
+ indicatorStyle?: IndicatorStyle;
243
+ /** CSS class for positioning the canvas. Its size is `width` × `height`. */
244
+ className?: string;
245
+ /** Forwarded ref to the underlying `<canvas>`. */
246
+ canvasRef?: Ref<HTMLCanvasElement>;
247
+ /** The view id this minimap publishes its pointer under. Default
248
+ * `'minimap'`. */
249
+ id?: string;
250
+ }
251
+ /**
252
+ * Opinionated minimap. See `MinimapCanvasProps` for the full surface and
253
+ * the module docstring for the rendering / input model.
254
+ *
255
+ * Its dispatcher, actions and deps are its own, so it never competes with
256
+ * the main canvas for input. The pointer store is shared when one is in scope.
257
+ *
258
+ * `forwardRef` is not used because the component is generic over
259
+ * `<TData, TLayer, TPose>` and React's `forwardRef` erases generics. The
260
+ * `canvasRef` prop is the plain-prop equivalent.
261
+ */
262
+ declare function MinimapCanvas<TData, TLayer extends string, TPose>(props: MinimapCanvasProps<TData, TLayer, TPose>): react.JSX.Element;
263
+
264
+ 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,418 @@
1
+ import { PointerProviderIfRoot, AUTO_POSE_DESCRIPTOR, poseDescriptorForNode, documentPose, FALLBACK_ACCENT, normalizeView, accentColorOf, SceneViewCanvas } from '@weasel-js/core';
2
+ import { useSyncExternalStore, useMemo, useRef, useEffect, useCallback } 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) {
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(documentPose(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") {
64
+ const rect = sceneLeafBounds(scene, descriptor);
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: { fill: "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: { fill: "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
+ if (cached && cached.scene === scene && cached.version === version && cached.w === r.w && cached.h === r.h) {
241
+ return cached.view;
242
+ }
243
+ const descriptor = deps?.get("poseDescriptor") ?? AUTO_POSE_DESCRIPTOR;
244
+ const view = computeFitView(scene, { width: r.w, height: r.h }, fit, descriptor);
245
+ cached = { scene, version, w: r.w, h: r.h, view };
246
+ return view;
247
+ };
248
+ const cursor = linkedCursor(`${id}.cursor`, [null, id]);
249
+ const indicator = createIndicatorLayer({
250
+ viewId: id,
251
+ root: () => deps?.get("rootView"),
252
+ ...opts.indicatorStyle ? { style: opts.indicatorStyle } : {}
253
+ });
254
+ return {
255
+ id,
256
+ eligibility: { always: true },
257
+ views: [{
258
+ id,
259
+ bounds: opts.rect,
260
+ view: fitCamera,
261
+ interactive: true,
262
+ background: opts.background ?? DEFAULT_BACKGROUND,
263
+ ...opts.layers ? { layers: opts.layers } : {}
264
+ }],
265
+ actions: [minimapCenterAction(), minimapPanAction()],
266
+ bindings: [
267
+ { spec: { kind: "pointerDown" }, actionId: MINIMAP_CENTER, opts: { views: [id] } },
268
+ { spec: { kind: "drag" }, actionId: MINIMAP_PAN, opts: { views: [id] } }
269
+ ],
270
+ overlay: [indicator, cursor.layer],
271
+ attach: (api, reader) => {
272
+ deps = reader;
273
+ const detachCursor = cursor.attach(api, reader);
274
+ api.requestRedraw();
275
+ return () => {
276
+ detachCursor();
277
+ deps = null;
278
+ cached = null;
279
+ };
280
+ }
281
+ };
282
+ }
283
+ var MINIMAP_INPUT = {
284
+ id: "minimap",
285
+ eligibility: { always: true },
286
+ bindings: [
287
+ { spec: { kind: "pointerDown" }, actionId: MINIMAP_CENTER },
288
+ { spec: { kind: "drag" }, actionId: MINIMAP_PAN }
289
+ ]
290
+ };
291
+ var TOOLS_BY_ID = /* @__PURE__ */ new Map([[MINIMAP_INPUT.id, MINIMAP_INPUT]]);
292
+ var CHANNELS = { wheel: false, pinch: false, contextMenu: false, ingest: false };
293
+ function MinimapCanvasInner(props) {
294
+ const {
295
+ scene,
296
+ mainView: mainViewProp,
297
+ mainViewDims,
298
+ onMainViewChange,
299
+ width,
300
+ height,
301
+ drawOne,
302
+ alphaFor,
303
+ layerVisibility,
304
+ layerOrder,
305
+ animator,
306
+ fit = "scene",
307
+ poseDescriptor,
308
+ indicatorStyle,
309
+ className,
310
+ canvasRef,
311
+ id = "minimap"
312
+ } = props;
313
+ const mainView = normalizeView(mainViewProp);
314
+ const sceneVersion = useSyncExternalStore(
315
+ scene.subscribe,
316
+ scene.getVersion,
317
+ scene.getVersion
318
+ );
319
+ const descriptor = poseDescriptor ?? AUTO_POSE_DESCRIPTOR;
320
+ const fitView = useMemo(() => {
321
+ return normalizeView(computeFitView(scene, { width, height }, fit, descriptor));
322
+ }, [scene, sceneVersion, width, height, fit, descriptor]);
323
+ const indicatorCmd = useMemo(() => {
324
+ return computeIndicatorCommand(mainView, mainViewDims, indicatorStyle, fitView);
325
+ }, [mainView, mainViewDims, indicatorStyle, fitView]);
326
+ const localCanvasRef = useRef(null);
327
+ const mainViewRef = useLatest(mainView);
328
+ const mainViewDimsRef = useLatest(mainViewDims);
329
+ const onMainViewChangeRef = useLatest(onMainViewChange);
330
+ const fitViewRef = useLatest(fitView);
331
+ const rootView = useMemo(() => ({
332
+ get: () => mainViewRef.current,
333
+ set: (v) => onMainViewChangeRef.current(v),
334
+ hostSize: () => mainViewDimsRef.current
335
+ }), [mainViewRef, onMainViewChangeRef, mainViewDimsRef]);
336
+ useDepSource("rootView", () => rootView);
337
+ const actions = useActionsRegistry();
338
+ useEffect(() => {
339
+ if (!actions) return;
340
+ const off = [actions.register(minimapCenterAction()), actions.register(minimapPanAction())];
341
+ return () => {
342
+ for (const u of off) u();
343
+ };
344
+ }, [actions]);
345
+ const clientToWorld = useCallback((cx, cy) => {
346
+ const rect = localCanvasRef.current?.getBoundingClientRect();
347
+ const fv = fitViewRef.current;
348
+ return {
349
+ x: fv.x + (cx - (rect?.left ?? 0)) / fv.scale.x,
350
+ y: fv.y + (cy - (rect?.top ?? 0)) / fv.scale.y
351
+ };
352
+ }, [fitViewRef]);
353
+ useGestureDispatcher({
354
+ canvasRef: localCanvasRef,
355
+ actions,
356
+ entriesById: TOOLS_BY_ID,
357
+ clientToWorld,
358
+ keyboard: false,
359
+ channels: CHANNELS
360
+ });
361
+ const pointerStore = usePointerContext();
362
+ useEffect(() => {
363
+ const el = localCanvasRef.current;
364
+ if (!el || !pointerStore) return;
365
+ const onMove = (e) => {
366
+ const w = clientToWorld(e.clientX, e.clientY);
367
+ pointerStore.set({ worldX: w.x, worldY: w.y, viewId: id });
368
+ };
369
+ const onLeave = (e) => {
370
+ if (e.buttons !== 0) return;
371
+ if (pointerStore.get()?.viewId === id) pointerStore.set(null);
372
+ };
373
+ el.addEventListener("pointermove", onMove);
374
+ el.addEventListener("pointerleave", onLeave);
375
+ return () => {
376
+ el.removeEventListener("pointermove", onMove);
377
+ el.removeEventListener("pointerleave", onLeave);
378
+ };
379
+ }, [pointerStore, clientToWorld, id]);
380
+ const pointer = usePointerPosition();
381
+ const cursorLayer = useMemo(() => createLinkedCursorLayer({
382
+ id: `${id}.cursor`,
383
+ pointer: () => pointer,
384
+ color: () => accentColorOf(localCanvasRef.current)
385
+ }), [id, pointer]);
386
+ const extraCommands = useMemo(() => [
387
+ indicatorCmd,
388
+ ...cursorLayer.draw({ viewId: id }, fitView, { width, height })
389
+ ], [indicatorCmd, cursorLayer, id, fitView, width, height]);
390
+ const setCanvasRef = useCallback((el) => {
391
+ localCanvasRef.current = el;
392
+ assignRef(canvasRef, el);
393
+ }, [canvasRef]);
394
+ return /* @__PURE__ */ jsx(
395
+ SceneViewCanvas,
396
+ {
397
+ scene,
398
+ view: fitView,
399
+ width,
400
+ height,
401
+ drawOne,
402
+ extraCommands,
403
+ alphaFor,
404
+ layerVisibility,
405
+ layerOrder,
406
+ animator,
407
+ className,
408
+ canvasRef: setCanvasRef
409
+ }
410
+ );
411
+ }
412
+ function MinimapCanvas(props) {
413
+ return /* @__PURE__ */ jsx(DepRegistryProvider, { children: /* @__PURE__ */ jsx(ActionsProvider, { children: /* @__PURE__ */ jsx(ActiveToolContextProvider, { children: /* @__PURE__ */ jsx(PointerProviderIfRoot, { children: /* @__PURE__ */ jsx(MinimapCanvasInner, { ...props }) }) }) }) });
414
+ }
415
+
416
+ export { CROSSHAIR_HALO, FALLBACK_FIT_VIEW, MINIMAP_CENTER, MINIMAP_PAN, MinimapCanvas, centerRootOn, computeFitView, computeIndicatorCommand, createIndicatorLayer, createLinkedCursorContribution, createLinkedCursorLayer, createMinimapContribution, crosshairRects, minimapCenterAction, minimapPanAction };
417
+ //# sourceMappingURL=index.js.map
418
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/minimapMath.ts","../src/actions.ts","../src/layers.ts","../src/createMinimapContribution.ts","../src/MinimapCanvas.tsx"],"names":["AUTO_POSE_DESCRIPTOR","accentColorOf"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Pure math helpers for `<MinimapCanvas>`.\n *\n * `computeFitView` derives a `View` that fits a chosen world rect into a\n * minimap canvas of given pixel dims. `computeIndicatorCommand` builds the\n * dashed \"visible window\" rect — the main canvas's screen window projected\n * into the minimap's own screen coordinates.\n *\n * Neither helper touches GL, the DOM, or the renderer; they're pure\n * functions, unit-testable with `expect`.\n */\nimport { documentPose } 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/**\n * Fallback view returned when there is nothing to fit (empty scene, etc.).\n * Identity view at the origin — produces a 1:1 mapping centered on (0, 0).\n */\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).\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 | { 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): 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(documentPose(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.\n *\n * - `fit === \"scene\"`: union AABB of leaf-node poses via `descriptor`.\n * Empty scene → {@link FALLBACK_FIT_VIEW}.\n * - `fit` is `{ kind: \"world\", rect }`: fit `rect`.\n * - `fit` is a function: called with `(scene, dims)`; its return is\n * 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') {\n const rect = sceneLeafBounds(scene, descriptor);\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.\n *\n * The main canvas's visible world rect is:\n * ```\n * worldX = mainView.x\n * worldY = mainView.y\n * worldW = mainViewDims.width / mainView.scale.x\n * worldH = mainViewDims.height / mainView.scale.y\n * ```\n * That world rect is then transformed into the minimap's screen\n * coordinates through `minimapView`.\n *\n * The returned command is a dashed-stroke `path` rect with no fill. Pass the\n * `minimapView` it will be drawn through, so the stroke width and dash come\n * out in CSS px at any fit scale; without it they 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\n // (scene + extras) in a group transformed by the minimap's view, so the\n // indicator lands in screen coords automatically. Returning screen coords\n // here would get the view transform applied a second time — a bug that\n // manifested as the indicator drifting at non-identity minimap scale.\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: { fill: '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: { fill: '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; 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 if (cached && cached.scene === scene && cached.version === version && 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, 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","/**\n * `<MinimapCanvas>` — opinionated minimap on a canvas of its own, built on\n * `<SceneViewCanvas>`.\n *\n * Renders the same `scene` the main canvas is showing, through a derived\n * \"fit\" view, and overlays a dashed visible-window indicator showing where\n * the main canvas is currently looking. Press anywhere in the minimap to\n * recenter the main view on that world point; drag to pan continuously.\n *\n * Input runs on a dispatcher of its own, through the same `minimap.center` /\n * `minimap.pan` actions the in-surface minimap (`createMinimapContribution`)\n * binds. It publishes its pointer into the pointer store in scope, and draws a\n * crosshair wherever that store says the pointer is on another surface — put\n * one `<PointerContextProvider>` around it and the main canvas for linked\n * cursors both ways.\n */\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\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 {\n computeFitView,\n computeIndicatorCommand,\n} from './minimapMath';\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/core';\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 * Framing is derived from **document** poses, not `scene.overrides` — a\n * node 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. */\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 positioning the canvas. Its size is `width` × `height`. */\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 // Subscribe to scene version so the fit view recomputes when the scene\n // mutates. `<SceneViewCanvas>` also subscribes, but it owns its own\n // rendering; we need the version *here* to invalidate the memoized fit.\n const sceneVersion = useSyncExternalStore(\n scene.subscribe,\n scene.getVersion,\n scene.getVersion,\n );\n\n const descriptor = (poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n\n const fitView = 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 // `sceneVersion` participates so the memo re-evaluates after scene mutations.\n // `descriptor` is stable when the prop is omitted.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [scene, sceneVersion, width, height, fit, descriptor]);\n\n const indicatorCmd = useMemo<DrawCommand>(() => {\n return computeIndicatorCommand(mainView, mainViewDims, indicatorStyle, fitView);\n }, [mainView, mainViewDims, indicatorStyle, fitView]);\n\n // -------------------------------------------------------------------------\n // Input\n // -------------------------------------------------------------------------\n //\n // The \"latest\" pattern (refs holding the current mainView / dims / setter\n // / fitView) lets the dep and the listeners read up-to-date values without\n // re-binding every frame.\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 // -------------------------------------------------------------------------\n // Linked cursor\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. See `MinimapCanvasProps` for the full surface and\n * the module docstring for the rendering / input model.\n *\n * Its dispatcher, actions and deps are its own, so it never competes with\n * the main canvas for input. The pointer store is shared when one is in scope.\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,51 @@
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.9.4",
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.9.4",
41
+ "@weasel-js/routing": "1.9.4"
42
+ },
43
+ "peerDependencies": {
44
+ "@weasel-js/core": "1.9.4",
45
+ "react": ">=18"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public",
49
+ "provenance": true
50
+ }
51
+ }