@samirdamle/calipers 0.0.0-stage → 0.1.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/dist/index.js ADDED
@@ -0,0 +1,524 @@
1
+ // src/internal/dom.ts
2
+ function isElement(v) {
3
+ return typeof Element !== "undefined" && v instanceof Element;
4
+ }
5
+ function px(value) {
6
+ const n = parseFloat(value);
7
+ return Number.isFinite(n) ? n : 0;
8
+ }
9
+ function parseTranslate(value) {
10
+ if (!value || value === "none") return { x: 0, y: 0 };
11
+ const [x = "0", y = "0"] = value.trim().split(/\s+/);
12
+ return { x: px(x), y: px(y) };
13
+ }
14
+ function getTranslate(el) {
15
+ return parseTranslate(getComputedStyle(el).translate);
16
+ }
17
+ function shiftTranslate(el, prev, dx, dy) {
18
+ if (dx === 0 && dy === 0) return;
19
+ el.style.setProperty("translate", `${prev.x + dx}px ${prev.y + dy}px`);
20
+ }
21
+
22
+ // src/internal/gap.ts
23
+ function normalizeGap(gap) {
24
+ if (gap === void 0) return { x: 0, y: 0 };
25
+ if (typeof gap === "number") return { x: gap, y: gap };
26
+ if ("x" in gap) return { x: gap.x, y: gap.y };
27
+ return { x: gap[0], y: gap[1] };
28
+ }
29
+
30
+ // src/rect/index.ts
31
+ function rectOf(target, options = {}) {
32
+ const { box = "border", space = "viewport" } = options;
33
+ const t = classify(target);
34
+ switch (t.kind) {
35
+ case "rect":
36
+ return { ...t.rect };
37
+ case "viewport":
38
+ return moveSpace(
39
+ {
40
+ x: 0,
41
+ y: 0,
42
+ width: window.innerWidth,
43
+ height: window.innerHeight
44
+ },
45
+ "viewport",
46
+ space
47
+ );
48
+ case "element":
49
+ return elementRect(t.el, box, space);
50
+ }
51
+ }
52
+ function classify(target) {
53
+ if (target === "viewport" || target === window) return { kind: "viewport" };
54
+ if (typeof target === "string") {
55
+ const el = document.querySelector(target);
56
+ if (!el)
57
+ throw new Error(`calipers: no element matches selector "${target}"`);
58
+ return { kind: "element", el };
59
+ }
60
+ if (isElement(target)) return { kind: "element", el: target };
61
+ if (isRectLike(target)) return { kind: "rect", rect: target };
62
+ if (isPointLike(target))
63
+ return {
64
+ kind: "rect",
65
+ rect: { x: target.x, y: target.y, width: 0, height: 0 }
66
+ };
67
+ const first = target[0];
68
+ if (first == null)
69
+ throw new Error("calipers: empty list has no measurable to resolve");
70
+ return classify(first);
71
+ }
72
+ function isPointLike(v) {
73
+ return typeof v === "object" && v !== null && typeof v.x === "number" && typeof v.y === "number" && typeof v.width !== "number";
74
+ }
75
+ function isRectLike(v) {
76
+ return typeof v === "object" && v !== null && typeof v.x === "number" && typeof v.y === "number" && typeof v.width === "number" && typeof v.height === "number";
77
+ }
78
+ function isSVGGraphics(el) {
79
+ return typeof SVGGraphicsElement !== "undefined" && el instanceof SVGGraphicsElement;
80
+ }
81
+ function elementRect(el, box, space) {
82
+ return isSVGGraphics(el) ? svgRect(el, box, space) : htmlRect(el, box, space);
83
+ }
84
+ function htmlRect(el, box, space) {
85
+ if (box === "visual") {
86
+ const r = el.getBoundingClientRect();
87
+ return moveSpace(
88
+ { x: r.x, y: r.y, width: r.width, height: r.height },
89
+ "viewport",
90
+ space
91
+ );
92
+ }
93
+ const border = layoutBorderBox(el);
94
+ const cs = getComputedStyle(el);
95
+ let rect;
96
+ if (box === "content") {
97
+ const bl = px(cs.borderLeftWidth);
98
+ const br = px(cs.borderRightWidth);
99
+ const bt = px(cs.borderTopWidth);
100
+ const bb = px(cs.borderBottomWidth);
101
+ const pl = px(cs.paddingLeft);
102
+ const pr = px(cs.paddingRight);
103
+ const pt = px(cs.paddingTop);
104
+ const pb = px(cs.paddingBottom);
105
+ rect = {
106
+ x: border.x + bl + pl,
107
+ y: border.y + bt + pt,
108
+ width: Math.max(0, border.width - bl - br - pl - pr),
109
+ height: Math.max(0, border.height - bt - bb - pt - pb)
110
+ };
111
+ } else if (box === "margin") {
112
+ const ml = px(cs.marginLeft);
113
+ const mr = px(cs.marginRight);
114
+ const mt = px(cs.marginTop);
115
+ const mb = px(cs.marginBottom);
116
+ rect = {
117
+ x: border.x - ml,
118
+ y: border.y - mt,
119
+ width: border.width + ml + mr,
120
+ height: border.height + mt + mb
121
+ };
122
+ } else {
123
+ rect = border;
124
+ }
125
+ return moveSpace(rect, "document", space);
126
+ }
127
+ function svgRect(el, box, space) {
128
+ if (box === "bbox") {
129
+ const b = el.getBBox();
130
+ return { x: b.x, y: b.y, width: b.width, height: b.height };
131
+ }
132
+ const r = el.getBoundingClientRect();
133
+ return moveSpace(
134
+ { x: r.x, y: r.y, width: r.width, height: r.height },
135
+ "viewport",
136
+ space
137
+ );
138
+ }
139
+ function layoutBorderBox(el) {
140
+ let x = 0;
141
+ let y = 0;
142
+ let node = el;
143
+ const fixed = getComputedStyle(el).position === "fixed";
144
+ while (node) {
145
+ x += node.offsetLeft;
146
+ y += node.offsetTop;
147
+ node = node.offsetParent;
148
+ }
149
+ if (fixed) {
150
+ x += window.scrollX;
151
+ y += window.scrollY;
152
+ }
153
+ return { x, y, width: el.offsetWidth, height: el.offsetHeight };
154
+ }
155
+ function moveSpace(rect, from, to) {
156
+ if (from === to) return rect;
157
+ const dx = from === "viewport" ? window.scrollX : -window.scrollX;
158
+ const dy = from === "viewport" ? window.scrollY : -window.scrollY;
159
+ return {
160
+ x: rect.x + dx,
161
+ y: rect.y + dy,
162
+ width: rect.width,
163
+ height: rect.height
164
+ };
165
+ }
166
+
167
+ // src/internal/rendered.ts
168
+ function rendered(el, box) {
169
+ const rect = rectOf(el, { box, space: "viewport" });
170
+ const translate = getTranslate(el);
171
+ if (box === "visual" || translate.x === 0 && translate.y === 0) {
172
+ return { rect, translate };
173
+ }
174
+ return {
175
+ rect: { ...rect, x: rect.x + translate.x, y: rect.y + translate.y },
176
+ translate
177
+ };
178
+ }
179
+
180
+ // src/align/index.ts
181
+ var pos = (r, ax) => ax === "x" ? r.x : r.y;
182
+ var size = (r, ax) => ax === "x" ? r.width : r.height;
183
+ var other = (ax) => ax === "x" ? "y" : "x";
184
+ function toRect(to, box) {
185
+ return isElement(to) ? rendered(to, box).rect : rectOf(to, { box, space: "viewport" });
186
+ }
187
+ function mean(vals) {
188
+ return vals.reduce((a, b) => a + b, 0) / vals.length;
189
+ }
190
+ function align(els, edge, options = {}) {
191
+ const list = Array.from(els);
192
+ if (list.length === 0) return;
193
+ const box = options.box ?? "border";
194
+ const ax = edge === "left" || edge === "right" || edge === "center-x" ? "x" : "y";
195
+ const items = list.map((el) => ({ el, ...rendered(el, box) }));
196
+ const ref = options.to === void 0 ? null : toRect(options.to, box);
197
+ const line = ref !== null ? edgePos(edge, ref) : aggregate(
198
+ edge,
199
+ items.map((it) => edgePos(edge, it.rect))
200
+ );
201
+ for (const { el, rect, translate } of items) {
202
+ const delta = line - edgePos(edge, rect);
203
+ shiftTranslate(
204
+ el,
205
+ translate,
206
+ ax === "x" ? delta : 0,
207
+ ax === "y" ? delta : 0
208
+ );
209
+ }
210
+ }
211
+ function edgePos(edge, r) {
212
+ switch (edge) {
213
+ case "left":
214
+ return r.x;
215
+ case "right":
216
+ return r.x + r.width;
217
+ case "center-x":
218
+ return r.x + r.width / 2;
219
+ case "top":
220
+ return r.y;
221
+ case "bottom":
222
+ return r.y + r.height;
223
+ case "center-y":
224
+ return r.y + r.height / 2;
225
+ }
226
+ }
227
+ function aggregate(edge, vals) {
228
+ if (edge === "center-x" || edge === "center-y") return mean(vals);
229
+ return edge === "left" || edge === "top" ? Math.min(...vals) : Math.max(...vals);
230
+ }
231
+ function distribute(els, axis, options = {}) {
232
+ const list = Array.from(els);
233
+ if (list.length < 2) return;
234
+ const box = options.box ?? "border";
235
+ const ax = axis;
236
+ const items = list.map((el) => ({ el, ...rendered(el, box) })).sort((a, b) => pos(a.rect, ax) - pos(b.rect, ax));
237
+ const first = items[0].rect;
238
+ const last = items[items.length - 1].rect;
239
+ const span = pos(last, ax) + size(last, ax) - pos(first, ax);
240
+ const sizes = items.reduce((sum, it) => sum + size(it.rect, ax), 0);
241
+ const gap = options.gap ?? (span - sizes) / (items.length - 1);
242
+ let cursor = pos(first, ax);
243
+ for (const { el, rect, translate } of items) {
244
+ const delta = cursor - pos(rect, ax);
245
+ shiftTranslate(
246
+ el,
247
+ translate,
248
+ ax === "x" ? delta : 0,
249
+ ax === "y" ? delta : 0
250
+ );
251
+ cursor += size(rect, ax) + gap;
252
+ }
253
+ }
254
+ function stack(els, options = {}) {
255
+ const list = Array.from(els);
256
+ if (list.length === 0) return;
257
+ const box = options.box ?? "border";
258
+ const direction = options.direction ?? "b";
259
+ const lateral = options.align ?? "c";
260
+ const ax = direction === "t" || direction === "b" ? "y" : "x";
261
+ const forward = direction === "b" || direction === "r";
262
+ const gap = normalizeGap(options.gap);
263
+ const gapAx = ax === "x" ? gap.x : gap.y;
264
+ const gapLat = typeof options.gap === "number" || options.gap === void 0 ? 0 : ax === "x" ? gap.y : gap.x;
265
+ const items = list.map((el) => ({ el, ...rendered(el, box) }));
266
+ let anchorEl;
267
+ let anchorRect;
268
+ if (options.to !== void 0) {
269
+ const to = options.to;
270
+ anchorEl = isElement(to) ? to : null;
271
+ anchorRect = toRect(to, box);
272
+ } else {
273
+ anchorEl = items[0].el;
274
+ anchorRect = items[0].rect;
275
+ for (const it of items) {
276
+ if (moreTrailing(it.rect, anchorRect, ax, forward)) {
277
+ anchorEl = it.el;
278
+ anchorRect = it.rect;
279
+ }
280
+ }
281
+ }
282
+ const targets = items.filter((it) => it.el !== anchorEl).sort(
283
+ (a, b) => forward ? pos(a.rect, ax) - pos(b.rect, ax) : pos(b.rect, ax) + size(b.rect, ax) - (pos(a.rect, ax) + size(a.rect, ax))
284
+ );
285
+ if (targets.length === 0) return;
286
+ let cursor = forward ? pos(anchorRect, ax) + size(anchorRect, ax) + gapAx : pos(anchorRect, ax) - gapAx;
287
+ let step = 0;
288
+ for (const { el, rect, translate } of targets) {
289
+ step += 1;
290
+ const s = size(rect, ax);
291
+ const slot = forward ? cursor : cursor - s;
292
+ const delta = slot - pos(rect, ax);
293
+ const lat = lateralOffset(lateral, anchorRect, rect, ax) + step * gapLat;
294
+ shiftTranslate(
295
+ el,
296
+ translate,
297
+ ax === "x" ? delta : lat,
298
+ ax === "y" ? delta : lat
299
+ );
300
+ cursor = forward ? slot + s + gapAx : slot - gapAx;
301
+ }
302
+ }
303
+ function moreTrailing(r, best, ax, forward) {
304
+ const e = forward ? pos(r, ax) : pos(r, ax) + size(r, ax);
305
+ const b = forward ? pos(best, ax) : pos(best, ax) + size(best, ax);
306
+ return forward ? e < b : e > b;
307
+ }
308
+ function lateralOffset(lateral, anchor, r, ax) {
309
+ const p = other(ax);
310
+ const leading = p === "x" ? "l" : "t";
311
+ const trailing = p === "x" ? "r" : "b";
312
+ const a0 = pos(anchor, p);
313
+ const a1 = a0 + size(anchor, p);
314
+ const r0 = pos(r, p);
315
+ const r1 = r0 + size(r, p);
316
+ if (lateral === leading) return a0 - r0;
317
+ if (lateral === trailing) return a1 - r1;
318
+ return (a0 + a1) / 2 - (r0 + r1) / 2;
319
+ }
320
+
321
+ // src/geom/index.ts
322
+ var ANCHOR_FRACTIONS = {
323
+ tl: [0, 0],
324
+ tc: [0.5, 0],
325
+ tr: [1, 0],
326
+ cl: [0, 0.5],
327
+ cc: [0.5, 0.5],
328
+ cr: [1, 0.5],
329
+ bl: [0, 1],
330
+ bc: [0.5, 1],
331
+ br: [1, 1],
332
+ t: [0.5, 0],
333
+ b: [0.5, 1],
334
+ l: [0, 0.5],
335
+ r: [1, 0.5]
336
+ };
337
+ function anchorPoint(rect, anchor = "cc") {
338
+ if (typeof anchor === "function") return anchor(rect);
339
+ const [fx, fy] = typeof anchor === "string" ? ANCHOR_FRACTIONS[anchor] : "x" in anchor ? [anchor.x, anchor.y] : anchor;
340
+ return { x: rect.x + fx * rect.width, y: rect.y + fy * rect.height };
341
+ }
342
+ function distanceBetween(a, b) {
343
+ return Math.hypot(a.x - b.x, a.y - b.y);
344
+ }
345
+ function angleBetween(from, to) {
346
+ return Math.atan2(to.y - from.y, to.x - from.x);
347
+ }
348
+ function gapBetween(a, b) {
349
+ return {
350
+ x: Math.max(0, Math.max(b.x - (a.x + a.width), a.x - (b.x + b.width))),
351
+ y: Math.max(0, Math.max(b.y - (a.y + a.height), a.y - (b.y + b.height)))
352
+ };
353
+ }
354
+ function overlapArea(a, b) {
355
+ const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
356
+ const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
357
+ return Math.max(0, w) * Math.max(0, h);
358
+ }
359
+ function overlaps(a, b) {
360
+ return overlapArea(a, b) > 0;
361
+ }
362
+ function containsRect(outer, inner) {
363
+ return outer.x <= inner.x && outer.y <= inner.y && outer.x + outer.width >= inner.x + inner.width && outer.y + outer.height >= inner.y + inner.height;
364
+ }
365
+
366
+ // src/measure/index.ts
367
+ var ALL_FIELDS = [
368
+ "dx",
369
+ "dy",
370
+ "distance",
371
+ "gapX",
372
+ "gapY",
373
+ "angleDeg",
374
+ "angleRad",
375
+ "width",
376
+ "height",
377
+ "overlaps",
378
+ "overlapArea",
379
+ "contains",
380
+ "visible"
381
+ ];
382
+ function measure(a, b, options = {}) {
383
+ const {
384
+ anchor = "cc",
385
+ ofAnchor = "cc",
386
+ fields = ALL_FIELDS,
387
+ box,
388
+ space
389
+ } = options;
390
+ const rectOptions = { box, space };
391
+ const ra = rectOf(a, rectOptions);
392
+ const rb = rectOf(b, rectOptions);
393
+ const pointA = anchorPoint(ra, anchor);
394
+ const pointB = anchorPoint(rb, ofAnchor);
395
+ const result = { target: a, source: b, pointA, pointB };
396
+ const want = new Set(fields);
397
+ if (want.has("dx") || want.has("dy") || want.has("distance") || want.has("angleDeg") || want.has("angleRad")) {
398
+ const dx = pointA.x - pointB.x;
399
+ const dy = pointA.y - pointB.y;
400
+ if (want.has("dx")) result.dx = dx;
401
+ if (want.has("dy")) result.dy = dy;
402
+ if (want.has("distance")) result.distance = Math.hypot(dx, dy);
403
+ if (want.has("angleDeg") || want.has("angleRad")) {
404
+ const rad = Math.atan2(dy, dx);
405
+ if (want.has("angleRad")) result.angleRad = rad;
406
+ if (want.has("angleDeg")) result.angleDeg = rad * 180 / Math.PI;
407
+ }
408
+ }
409
+ if (want.has("gapX") || want.has("gapY")) {
410
+ const gap = gapBetween(ra, rb);
411
+ if (want.has("gapX")) result.gapX = gap.x;
412
+ if (want.has("gapY")) result.gapY = gap.y;
413
+ }
414
+ if (want.has("width")) result.width = ra.width;
415
+ if (want.has("height")) result.height = ra.height;
416
+ if (want.has("overlaps") || want.has("overlapArea")) {
417
+ const area = overlapArea(ra, rb);
418
+ if (want.has("overlapArea")) result.overlapArea = area;
419
+ if (want.has("overlaps")) result.overlaps = area > 0;
420
+ }
421
+ if (want.has("contains")) result.contains = containsRect(ra, rb);
422
+ if (want.has("visible"))
423
+ result.visible = overlapArea(ra, rectOf("viewport", { space: space ?? "viewport" })) > 0;
424
+ return result;
425
+ }
426
+ function calipers(targets, source, options = {}) {
427
+ return expandTargets(targets).map((t) => measure(t, source, options));
428
+ }
429
+ function expandTargets(targets) {
430
+ if (typeof targets === "string")
431
+ return Array.from(document.querySelectorAll(targets));
432
+ if (targets === window || targets === "viewport") return [targets];
433
+ if (isList(targets)) {
434
+ const out = [];
435
+ for (const e of Array.from(targets)) {
436
+ if (typeof e === "string")
437
+ out.push(...Array.from(document.querySelectorAll(e)));
438
+ else out.push(e);
439
+ }
440
+ return out;
441
+ }
442
+ return [targets];
443
+ }
444
+ function isList(v) {
445
+ return typeof v === "object" && v !== null && typeof v.length === "number" && !(v instanceof Element);
446
+ }
447
+
448
+ // src/place/index.ts
449
+ function place(el, options = {}) {
450
+ const { anchor = "tl", offset = {}, using = "transform" } = options;
451
+ const ox = offset.x ?? 0;
452
+ const oy = offset.y ?? 0;
453
+ const gap = normalizeGap(options.gap);
454
+ if (using === "transform") {
455
+ const target = targetPoint(options.at, "viewport", "visual");
456
+ const { rect, translate } = rendered(el, "visual");
457
+ const cur = anchorPoint(rect, anchor);
458
+ const dir = gapDirection(rect, anchor);
459
+ shiftTranslate(
460
+ el,
461
+ translate,
462
+ target.x + ox + gap.x * dir.x - cur.x,
463
+ target.y + oy + gap.y * dir.y - cur.y
464
+ );
465
+ } else {
466
+ placeByPosition(
467
+ el,
468
+ anchor,
469
+ targetPoint(options.at, "document"),
470
+ ox,
471
+ oy,
472
+ gap
473
+ );
474
+ }
475
+ }
476
+ function gapDirection(rect, anchor) {
477
+ const c = anchorPoint(rect, "cc");
478
+ const p = anchorPoint(rect, anchor);
479
+ return { x: Math.sign(c.x - p.x), y: Math.sign(c.y - p.y) };
480
+ }
481
+ function targetPoint(at, space, box = "border") {
482
+ if (!at) return anchorPoint(rectOf("viewport", { space }), "cc");
483
+ if (isRawPoint(at)) {
484
+ return space === "document" ? { x: at.x + window.scrollX, y: at.y + window.scrollY } : { ...at };
485
+ }
486
+ const placeAt = at;
487
+ const of = typeof at.width === "number" ? at : placeAt.of ?? "viewport";
488
+ if (isRawPoint(of)) {
489
+ return space === "document" ? { x: of.x + window.scrollX, y: of.y + window.scrollY } : { ...of };
490
+ }
491
+ return anchorPoint(rectOf(of, { box, space }), placeAt.anchor ?? "cc");
492
+ }
493
+ function isRawPoint(v) {
494
+ return typeof v === "object" && v !== null && typeof v.x === "number" && typeof v.y === "number" && typeof v.width !== "number";
495
+ }
496
+ function placeByPosition(el, anchor, target, ox, oy, gap) {
497
+ const cs = getComputedStyle(el);
498
+ const positioned = cs.position === "static" ? "absolute" : cs.position;
499
+ if (cs.position === "static") el.style.position = "absolute";
500
+ const op = el.offsetParent;
501
+ const border = rectOf(el, { box: "border", space: "document" });
502
+ const a = anchorPoint(
503
+ { x: 0, y: 0, width: border.width, height: border.height },
504
+ anchor
505
+ );
506
+ const dir = gapDirection(border, anchor);
507
+ const ml = px(cs.marginLeft);
508
+ const mt = px(cs.marginTop);
509
+ let fx = target.x;
510
+ let fy = target.y;
511
+ if (op) {
512
+ const ob = rectOf(op, { box: "border", space: "document" });
513
+ const ocs = getComputedStyle(op);
514
+ fx -= ob.x + px(ocs.borderLeftWidth);
515
+ fy -= ob.y + px(ocs.borderTopWidth);
516
+ } else if (positioned === "fixed") {
517
+ fx -= window.scrollX;
518
+ fy -= window.scrollY;
519
+ }
520
+ el.style.left = `${fx + ox + gap.x * dir.x - a.x - ml}px`;
521
+ el.style.top = `${fy + oy + gap.y * dir.y - a.y - mt}px`;
522
+ }
523
+
524
+ export { align, anchorPoint, angleBetween, calipers, containsRect, distanceBetween, distribute, gapBetween, measure, overlapArea, overlaps, place, rectOf, stack };
@@ -0,0 +1,70 @@
1
+ import { R as Rect, A as Anchor, P as Point, M as Measurable } from './types-PJD4F9tg.js';
2
+ import { RectOptions } from './rect.js';
3
+
4
+ /** Resolve an anchor to a single point of a rect. Function anchors receive the rect and return an absolute point. */
5
+ declare function anchorPoint(rect: Rect, anchor?: Anchor): Point;
6
+ /** Euclidean distance between two points. */
7
+ declare function distanceBetween(a: Point, b: Point): number;
8
+ /**
9
+ * Direction from `from` to `to`, in radians.
10
+ * 0 = east, positive clockwise (screen coordinates, y down), range (−π, π].
11
+ */
12
+ declare function angleBetween(from: Point, to: Point): number;
13
+ /** Edge-to-edge separation per axis; 0 on an axis when the projections overlap. */
14
+ declare function gapBetween(a: Rect, b: Rect): {
15
+ x: number;
16
+ y: number;
17
+ };
18
+ /** Intersection area of two rects in px²; 0 when disjoint. */
19
+ declare function overlapArea(a: Rect, b: Rect): number;
20
+ /** Whether two rects intersect with non-zero area. */
21
+ declare function overlaps(a: Rect, b: Rect): boolean;
22
+ /** Whether `outer` fully encloses `inner`. */
23
+ declare function containsRect(outer: Rect, inner: Rect): boolean;
24
+
25
+ type Field = 'dx' | 'dy' | 'distance' | 'gapX' | 'gapY' | 'angleDeg' | 'angleRad' | 'width' | 'height' | 'overlaps' | 'overlapArea' | 'contains' | 'visible';
26
+ interface MeasureOptions extends RectOptions {
27
+ /** Anchor of the target. @default 'cc' */
28
+ anchor?: Anchor;
29
+ /** Anchor of the source. @default 'cc' */
30
+ ofAnchor?: Anchor;
31
+ /** Fields to compute. @default all */
32
+ fields?: readonly Field[];
33
+ }
34
+ interface MeasureResult {
35
+ target: Measurable;
36
+ source: Measurable;
37
+ /** Resolved target anchor — always present. */
38
+ pointA: Point;
39
+ /** Resolved source anchor — always present. */
40
+ pointB: Point;
41
+ /** Signed deltas, target − source. */
42
+ dx?: number;
43
+ dy?: number;
44
+ /** Euclidean distance between the anchor points. */
45
+ distance?: number;
46
+ /** Edge-to-edge separation per axis. */
47
+ gapX?: number;
48
+ gapY?: number;
49
+ /** Direction from source point to target point. */
50
+ angleDeg?: number;
51
+ angleRad?: number;
52
+ /** Target box size. */
53
+ width?: number;
54
+ height?: number;
55
+ overlaps?: boolean;
56
+ overlapArea?: number;
57
+ /** Whether the target box fully encloses the source box. */
58
+ contains?: boolean;
59
+ /** Whether any part of the target box intersects the viewport. */
60
+ visible?: boolean;
61
+ }
62
+ /** Measure one target against one source. */
63
+ declare function measure(a: Measurable, b: Measurable, options?: MeasureOptions): MeasureResult;
64
+ /**
65
+ * Measure many targets against one source.
66
+ * A selector string expands to *all* matches; array-likes expand per entry.
67
+ */
68
+ declare function calipers(targets: Measurable, source: Measurable, options?: MeasureOptions): MeasureResult[];
69
+
70
+ export { type Field, type MeasureOptions, type MeasureResult, anchorPoint, angleBetween, calipers, containsRect, distanceBetween, gapBetween, measure, overlapArea, overlaps };