@grida/hud 0.2.0 → 0.2.2

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.
Files changed (38) hide show
  1. package/README.md +562 -37
  2. package/dist/core/index.d.mts +181 -0
  3. package/dist/core/index.d.ts +181 -0
  4. package/dist/core/index.js +301 -0
  5. package/dist/core/index.mjs +291 -0
  6. package/dist/cursors/index.d.mts +1 -1
  7. package/dist/cursors/index.d.ts +1 -1
  8. package/dist/cursors/index.js +1 -1
  9. package/dist/cursors/index.mjs +1 -1
  10. package/dist/index-BQtDtpHM.d.mts +3215 -0
  11. package/dist/index-BlfZbeEJ.d.ts +3215 -0
  12. package/dist/index.d.mts +4 -3
  13. package/dist/index.d.ts +4 -3
  14. package/dist/index.js +55 -2
  15. package/dist/index.mjs +3 -3
  16. package/dist/overlay-CVV4s3IL.d.ts +241 -0
  17. package/dist/overlay-dsG32baA.d.mts +241 -0
  18. package/dist/primitives/bedrock.d.mts +47 -0
  19. package/dist/primitives/bedrock.d.ts +47 -0
  20. package/dist/primitives/bedrock.js +71 -0
  21. package/dist/primitives/bedrock.mjs +65 -0
  22. package/dist/react.d.mts +3 -2
  23. package/dist/react.d.ts +2 -1
  24. package/dist/react.js +4 -1
  25. package/dist/react.mjs +4 -1
  26. package/dist/surface-BHDH6P6p.js +7383 -0
  27. package/dist/surface-B_8w6VWG.mjs +6929 -0
  28. package/dist/types-3wwFisZs.d.mts +296 -0
  29. package/dist/types-3wwFisZs.d.ts +296 -0
  30. package/package.json +16 -3
  31. package/dist/index-Cp0X4SV7.d.ts +0 -947
  32. package/dist/index-DhGdcuQz.d.mts +0 -947
  33. package/dist/surface-BvMmXoEl.mjs +0 -2471
  34. package/dist/surface-ofSNTJ8H.js +0 -2607
  35. /package/dist/{cursor-BFGUuD2M.d.mts → cursor-CxS8EMvm.d.mts} +0 -0
  36. /package/dist/{cursor-CIYvFshz.d.ts → cursor-CxS8EMvm.d.ts} +0 -0
  37. /package/dist/{cursor-BieMVb71.mjs → cursor-DW-uAPVE.mjs} +0 -0
  38. /package/dist/{cursor-DsP9qtN2.js → cursor-FGiJBdU-.js} +0 -0
@@ -0,0 +1,291 @@
1
+ //#region core/event.ts
2
+ const NO_MODS = {
3
+ shift: false,
4
+ alt: false,
5
+ meta: false,
6
+ ctrl: false
7
+ };
8
+ //#endregion
9
+ //#region core/click-tracker.ts
10
+ var ClickTracker = class {
11
+ constructor(opts = {}) {
12
+ this.last_time = 0;
13
+ this.last_x = 0;
14
+ this.last_y = 0;
15
+ this.count = 0;
16
+ this.window_ms = opts.windowMs ?? 250;
17
+ this.distance_px = opts.distancePx ?? 5;
18
+ }
19
+ /**
20
+ * Register a click at `(x, y)` and return the current consecutive-
21
+ * click count (1 = single, 2 = double, etc.). `now` is in ms.
22
+ */
23
+ register(x, y, now = nowMs()) {
24
+ const dt = now - this.last_time;
25
+ const dx = x - this.last_x;
26
+ const dy = y - this.last_y;
27
+ const dist2 = dx * dx + dy * dy;
28
+ const max2 = this.distance_px * this.distance_px;
29
+ if (dt <= this.window_ms && dist2 <= max2 && this.count > 0) this.count += 1;
30
+ else this.count = 1;
31
+ this.last_time = now;
32
+ this.last_x = x;
33
+ this.last_y = y;
34
+ return this.count;
35
+ }
36
+ reset() {
37
+ this.count = 0;
38
+ this.last_time = 0;
39
+ }
40
+ };
41
+ function nowMs() {
42
+ if (typeof performance !== "undefined" && performance.now) return performance.now();
43
+ return Date.now();
44
+ }
45
+ //#endregion
46
+ //#region core/transform.ts
47
+ const IDENTITY = [[
48
+ 1,
49
+ 0,
50
+ 0
51
+ ], [
52
+ 0,
53
+ 1,
54
+ 0
55
+ ]];
56
+ /** Project a screen-space point into document-space. */
57
+ function screenToDoc(t, x, y) {
58
+ const [[sx, , tx], [, sy, ty]] = t;
59
+ return [(x - tx) / (sx || 1), (y - ty) / (sy || 1)];
60
+ }
61
+ /** Project a document-space point into screen-space. */
62
+ function docToScreen(t, x, y) {
63
+ const [[sx, , tx], [, sy, ty]] = t;
64
+ return [sx * x + tx, sy * y + ty];
65
+ }
66
+ /** Current uniform zoom. Reads `sx`. */
67
+ function zoomOf(t) {
68
+ return t[0][0];
69
+ }
70
+ //#endregion
71
+ //#region core/registry.ts
72
+ /**
73
+ * Thrown by `register` when an entity with the same `name` is already
74
+ * registered. Consumers should `unregister` the existing one first.
75
+ */
76
+ var RegistrationError = class extends Error {
77
+ constructor(message) {
78
+ super(message);
79
+ this.name = "RegistrationError";
80
+ }
81
+ };
82
+ /**
83
+ * Generic name-keyed registry.
84
+ *
85
+ * @typeParam K — key type, must extend `string`.
86
+ * @typeParam T — entity type. Must expose `readonly name: K` and an
87
+ * optional `detach()` method.
88
+ */
89
+ var NamedRegistry = class {
90
+ constructor(label) {
91
+ this.label = label;
92
+ this.byName = /* @__PURE__ */ new Map();
93
+ this.order = [];
94
+ }
95
+ register(e) {
96
+ if (this.byName.has(e.name)) throw new RegistrationError(`${this.label} "${e.name}" already registered; unregister it first.`);
97
+ this.byName.set(e.name, e);
98
+ this.order.push(e);
99
+ }
100
+ unregister(e) {
101
+ if (this.byName.get(e.name) !== e) return;
102
+ this.byName.delete(e.name);
103
+ const i = this.order.indexOf(e);
104
+ if (i >= 0) this.order.splice(i, 1);
105
+ try {
106
+ e.detach?.();
107
+ } catch (err) {
108
+ console.error(`[hud] ${this.label} "${e.name}" detach() threw:`, err);
109
+ }
110
+ }
111
+ has(name) {
112
+ return this.byName.has(name);
113
+ }
114
+ get(name) {
115
+ return this.byName.get(name);
116
+ }
117
+ *entries() {
118
+ for (const e of this.order) yield e;
119
+ }
120
+ size() {
121
+ return this.order.length;
122
+ }
123
+ clear() {
124
+ for (let i = this.order.length - 1; i >= 0; i--) {
125
+ const e = this.order[i];
126
+ try {
127
+ e.detach?.();
128
+ } catch (err) {
129
+ console.error(`[hud] ${this.label} "${e.name}" detach() threw:`, err);
130
+ }
131
+ }
132
+ this.order = [];
133
+ this.byName.clear();
134
+ }
135
+ };
136
+ //#endregion
137
+ //#region core/hit-registry.ts
138
+ /**
139
+ * Generic hit registry. One per frame (or persistent — bedrock is
140
+ * agnostic). Add objects with `add`, clear with `clear`, query with
141
+ * `queryPoint` / `queryAll`.
142
+ */
143
+ var HitRegistry = class {
144
+ constructor() {
145
+ this.items = [];
146
+ }
147
+ /**
148
+ * Add an object. Objects without `hit` are accepted (they may carry
149
+ * paint information the consumer still wants in the registry for
150
+ * uniform iteration) but are filtered from point queries.
151
+ */
152
+ add(obj) {
153
+ this.items.push(obj);
154
+ }
155
+ /** Drop all entries. */
156
+ clear() {
157
+ this.items = [];
158
+ }
159
+ /** Total number of stored objects (paint-only + hit-testable). */
160
+ size() {
161
+ return this.items.length;
162
+ }
163
+ /** Iterate every stored object in insertion order. */
164
+ *entries() {
165
+ for (const o of this.items) yield o;
166
+ }
167
+ /**
168
+ * Return the hit-testable object whose hit shape contains the
169
+ * given screen-space point with the lowest `priority` value
170
+ * (lower wins). Paint-only objects (no `hit`) are skipped.
171
+ */
172
+ queryPoint(point_screen, transform) {
173
+ let best = null;
174
+ let best_priority = Number.POSITIVE_INFINITY;
175
+ for (const obj of this.items) {
176
+ if (!obj.hit) continue;
177
+ if (obj.priority > best_priority) continue;
178
+ if (!shapeContains(obj.hit, point_screen, transform)) continue;
179
+ if (obj.refine && !obj.refine(point_screen)) continue;
180
+ best = obj;
181
+ best_priority = obj.priority;
182
+ }
183
+ return best;
184
+ }
185
+ /**
186
+ * Return all hit-testable objects whose hit shape contains the point,
187
+ * winner first. Ordering is identical to `queryPoint`'s arbitration:
188
+ * priority ascending (lower wins), and on EQUAL priority the
189
+ * later-added object comes first ("later push wins on tie"). This makes
190
+ * `queryAll(p)[0]` always equal `queryPoint(p)` — the two query paths
191
+ * never disagree on the winner.
192
+ */
193
+ queryAll(point_screen, transform) {
194
+ const matches = [];
195
+ for (let i = 0; i < this.items.length; i++) {
196
+ const obj = this.items[i];
197
+ if (!obj.hit) continue;
198
+ if (!shapeContains(obj.hit, point_screen, transform)) continue;
199
+ if (obj.refine && !obj.refine(point_screen)) continue;
200
+ matches.push({
201
+ obj,
202
+ i
203
+ });
204
+ }
205
+ matches.sort((a, b) => a.obj.priority - b.obj.priority || b.i - a.i);
206
+ return matches.map((m) => m.obj);
207
+ }
208
+ };
209
+ /**
210
+ * Test whether a screen-space point lies inside a `HitShape`.
211
+ *
212
+ * The camera `transform` is needed for doc-anchored shapes
213
+ * (`screen_rect_at_doc`, `screen_circle_at_doc`); pre-projected
214
+ * shapes (`screen_aabb`, `screen_polygon`) ignore it. `screen_obb`
215
+ * applies its own `inverse_transform`, which maps screen → shadow
216
+ * (independent of the camera).
217
+ */
218
+ function shapeContains(shape, point_screen, transform) {
219
+ const [px, py] = point_screen;
220
+ switch (shape.kind) {
221
+ case "screen_rect_at_doc": {
222
+ const [ax, ay] = docToScreen(transform, shape.anchor_doc[0], shape.anchor_doc[1]);
223
+ const { x, y } = anchorOrigin(ax, ay, shape.width, shape.height, shape.placement ?? "center");
224
+ return px >= x && px <= x + shape.width && py >= y && py <= y + shape.height;
225
+ }
226
+ case "screen_aabb": {
227
+ const r = shape.rect;
228
+ return px >= r.x && px <= r.x + r.width && py >= r.y && py <= r.y + r.height;
229
+ }
230
+ case "screen_obb": {
231
+ const [[a, b, e], [c, d, f]] = shape.inverse_transform;
232
+ const sx = a * px + b * py + e;
233
+ const sy = c * px + d * py + f;
234
+ const r = shape.rect;
235
+ return sx >= r.x && sx <= r.x + r.width && sy >= r.y && sy <= r.y + r.height;
236
+ }
237
+ case "screen_circle_at_doc": {
238
+ const [ax, ay] = docToScreen(transform, shape.anchor_doc[0], shape.anchor_doc[1]);
239
+ const dx = px - ax;
240
+ const dy = py - ay;
241
+ return dx * dx + dy * dy <= shape.radius * shape.radius;
242
+ }
243
+ case "screen_polygon": return pointInPolygon(point_screen, shape.points);
244
+ }
245
+ }
246
+ function anchorOrigin(ax, ay, w, h, placement) {
247
+ switch (placement) {
248
+ case "center": return {
249
+ x: ax - w / 2,
250
+ y: ay - h / 2
251
+ };
252
+ case "tl": return {
253
+ x: ax,
254
+ y: ay
255
+ };
256
+ case "tr": return {
257
+ x: ax - w,
258
+ y: ay
259
+ };
260
+ case "bl": return {
261
+ x: ax,
262
+ y: ay - h
263
+ };
264
+ case "br": return {
265
+ x: ax - w,
266
+ y: ay - h
267
+ };
268
+ }
269
+ }
270
+ /**
271
+ * Standard even-odd ray-cast point-in-polygon.
272
+ *
273
+ * Horizontal edges (`yi === yj`) are skipped — the standard idiom for
274
+ * the even-odd algorithm. (The previous form used `(yj - yi ||
275
+ * Number.EPSILON)` as a divide-by-zero guard, but the guard was dead
276
+ * code: the `yi > py !== yj > py` clause already short-circuits to
277
+ * `false` when the edge is horizontal, so the division never runs.)
278
+ */
279
+ function pointInPolygon(point, poly) {
280
+ const [px, py] = point;
281
+ let inside = false;
282
+ for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
283
+ const [xi, yi] = poly[i];
284
+ const [xj, yj] = poly[j];
285
+ if (yi === yj) continue;
286
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi) inside = !inside;
287
+ }
288
+ return inside;
289
+ }
290
+ //#endregion
291
+ export { ClickTracker, HitRegistry, IDENTITY, NO_MODS, NamedRegistry, RegistrationError, docToScreen, screenToDoc, shapeContains, zoomOf };
@@ -1,4 +1,4 @@
1
- import { n as CursorRenderer } from "../cursor-BFGUuD2M.mjs";
1
+ import { n as CursorRenderer } from "../cursor-CxS8EMvm.mjs";
2
2
 
3
3
  //#region cursors/renderer.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { n as CursorRenderer } from "../cursor-CIYvFshz.js";
1
+ import { n as CursorRenderer } from "../cursor-CxS8EMvm.js";
2
2
 
3
3
  //#region cursors/renderer.d.ts
4
4
  /**
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_cursor = require("../cursor-DsP9qtN2.js");
2
+ const require_cursor = require("../cursor-FGiJBdU-.js");
3
3
  //#region cursors/encode.ts
4
4
  /**
5
5
  * SVG → `data:` URL encoding for CSS `cursor:` values.
@@ -1,4 +1,4 @@
1
- import { i as cursorToCss, n as angleBucket, t as CURSOR_ANGLE_BUCKET_RAD } from "../cursor-BieMVb71.mjs";
1
+ import { i as cursorToCss, n as angleBucket, t as CURSOR_ANGLE_BUCKET_RAD } from "../cursor-DW-uAPVE.mjs";
2
2
  //#region cursors/encode.ts
3
3
  /**
4
4
  * SVG → `data:` URL encoding for CSS `cursor:` values.