bireactive 0.2.4 → 0.3.1
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/animation/anim.js +4 -0
- package/dist/automerge/doc-cell.d.ts +20 -0
- package/dist/automerge/doc-cell.js +80 -0
- package/dist/automerge/index.d.ts +3 -0
- package/dist/automerge/index.js +12 -0
- package/dist/automerge/reconcile.d.ts +5 -0
- package/dist/automerge/reconcile.js +63 -0
- package/dist/coll.d.ts +7 -7
- package/dist/core/_counts.d.ts +48 -0
- package/dist/core/_counts.js +58 -0
- package/dist/core/cell.d.ts +182 -123
- package/dist/core/cell.js +1140 -721
- package/dist/core/debug.d.ts +25 -0
- package/dist/core/debug.js +121 -0
- package/dist/core/index.d.ts +9 -14
- package/dist/core/index.js +9 -14
- package/dist/core/lenses/aggregates.d.ts +1 -1
- package/dist/core/lenses/aggregates.js +4 -3
- package/dist/core/lenses/closed-form-policies.js +14 -9
- package/dist/core/lenses/decompositions.js +3 -3
- package/dist/core/lenses/domain-aggregates.js +5 -5
- package/dist/core/lenses/geometry.d.ts +1 -1
- package/dist/core/lenses/geometry.js +6 -7
- package/dist/core/lenses/index.d.ts +1 -0
- package/dist/core/lenses/index.js +1 -0
- package/dist/core/lenses/memory.d.ts +2 -2
- package/dist/core/lenses/memory.js +3 -3
- package/dist/core/lenses/snap.d.ts +18 -0
- package/dist/core/lenses/snap.js +145 -0
- package/dist/core/lenses/typed-factor.js +4 -3
- package/dist/core/optic.d.ts +13 -0
- package/dist/core/optic.js +44 -0
- package/dist/core/optics.d.ts +10 -0
- package/dist/core/optics.js +30 -0
- package/dist/core/store.d.ts +10 -0
- package/dist/core/store.js +85 -0
- package/dist/core/traits.d.ts +1 -0
- package/dist/core/values/audio.js +4 -5
- package/dist/core/values/box.js +7 -7
- package/dist/core/values/canvas.js +15 -18
- package/dist/core/values/color.js +5 -5
- package/dist/core/values/field.d.ts +70 -0
- package/dist/core/values/field.js +230 -0
- package/dist/core/values/gpu.d.ts +4 -2
- package/dist/core/values/gpu.js +11 -4
- package/dist/core/values/matrix.js +7 -7
- package/dist/core/values/num.d.ts +1 -1
- package/dist/core/values/num.js +1 -1
- package/dist/core/values/pose.js +4 -4
- package/dist/core/values/range.js +6 -6
- package/dist/core/values/str.js +8 -8
- package/dist/core/values/template.d.ts +1 -1
- package/dist/core/values/template.js +2 -1
- package/dist/core/values/transform.js +7 -7
- package/dist/core/values/tri.js +3 -3
- package/dist/core/values/vec.js +8 -12
- package/dist/ext/timeline.js +2 -2
- package/dist/formats/cst.d.ts +127 -0
- package/dist/formats/cst.js +280 -0
- package/dist/formats/edn.d.ts +2 -0
- package/dist/formats/edn.js +301 -0
- package/dist/formats/index.d.ts +6 -0
- package/dist/formats/index.js +8 -0
- package/dist/formats/json.d.ts +2 -0
- package/dist/formats/json.js +332 -0
- package/dist/formats/lens.d.ts +8 -0
- package/dist/formats/lens.js +51 -0
- package/dist/formats/toml.d.ts +2 -0
- package/dist/formats/toml.js +526 -0
- package/dist/formats/yaml.d.ts +2 -0
- package/dist/formats/yaml.js +661 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/jsx-dev-runtime.d.ts +2 -0
- package/dist/jsx-dev-runtime.js +5 -0
- package/dist/jsx-runtime.d.ts +54 -0
- package/dist/jsx-runtime.js +219 -0
- package/dist/learn/data.d.ts +49 -0
- package/dist/learn/data.js +181 -0
- package/dist/learn/index.d.ts +3 -0
- package/dist/learn/index.js +6 -0
- package/dist/learn/lens-net.d.ts +63 -0
- package/dist/learn/lens-net.js +219 -0
- package/dist/learn/mlp.d.ts +77 -0
- package/dist/learn/mlp.js +292 -0
- package/dist/propagators/csp.d.ts +13 -0
- package/dist/propagators/csp.js +52 -0
- package/dist/propagators/flex.d.ts +31 -0
- package/dist/propagators/flex.js +189 -0
- package/dist/propagators/graph.d.ts +73 -0
- package/dist/propagators/graph.js +543 -0
- package/dist/propagators/index.d.ts +8 -6
- package/dist/propagators/index.js +15 -6
- package/dist/propagators/lattice.d.ts +45 -0
- package/dist/propagators/lattice.js +113 -0
- package/dist/propagators/layout.d.ts +1 -27
- package/dist/propagators/layout.js +6 -175
- package/dist/propagators/numeric.d.ts +17 -0
- package/dist/propagators/numeric.js +93 -0
- package/dist/propagators/solver.d.ts +51 -0
- package/dist/propagators/solver.js +175 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/lens.d.ts +121 -0
- package/dist/schema/lens.js +429 -0
- package/dist/shapes/annular-sector.js +4 -4
- package/dist/shapes/button.js +1 -1
- package/dist/shapes/circle.js +1 -1
- package/dist/shapes/drag-behaviors.d.ts +56 -0
- package/dist/shapes/drag-behaviors.js +102 -0
- package/dist/shapes/drag-spec.d.ts +52 -0
- package/dist/shapes/drag-spec.js +112 -0
- package/dist/shapes/handle.js +2 -2
- package/dist/shapes/index.d.ts +3 -1
- package/dist/shapes/index.js +3 -1
- package/dist/shapes/interaction.d.ts +2 -3
- package/dist/shapes/interaction.js +77 -56
- package/dist/shapes/label.js +7 -1
- package/dist/shapes/layout.d.ts +47 -1
- package/dist/shapes/layout.js +60 -2
- package/dist/shapes/rect.js +7 -7
- package/dist/shapes/shape.js +8 -8
- package/dist/web/diagram.js +2 -2
- package/package.json +24 -2
- package/dist/propagators/network.d.ts +0 -52
- package/dist/propagators/network.js +0 -185
- package/dist/propagators/propagator.d.ts +0 -12
- package/dist/propagators/propagator.js +0 -16
- package/dist/propagators/range.d.ts +0 -45
- package/dist/propagators/range.js +0 -147
- package/dist/propagators/relations.d.ts +0 -60
- package/dist/propagators/relations.js +0 -343
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// drag-behaviors.ts — Dragology-style drag *modifiers* layered over the
|
|
2
|
+
// scene graph. The model-driven cores (`closest`, `between`, `whenFar`)
|
|
3
|
+
// live in `core/lenses/snap.ts`; these wire them to pointer input and the
|
|
4
|
+
// animation clock.
|
|
5
|
+
//
|
|
6
|
+
// The key idea, and the answer to "drag-and-drop complicates state": the
|
|
7
|
+
// floating offset and the spring-settle are TRANSIENT drag state, held in
|
|
8
|
+
// the animator, never written to the model. The model only ever sees the
|
|
9
|
+
// committed drop.
|
|
10
|
+
import { spring } from "../animation/index.js";
|
|
11
|
+
import { cell, derive, effect, Vec, vec, } from "../core/index.js";
|
|
12
|
+
import { drag } from "./interaction.js";
|
|
13
|
+
/** Dragology's `withFloating`: while held, `pos` follows the pointer
|
|
14
|
+
* directly (via the robust `drag` wiring — grab offset, touch, capture);
|
|
15
|
+
* on release it springs to `home` (the resolved target, e.g. a `closest`
|
|
16
|
+
* snap position or a layout slot). `pos` is the caller-owned display cell
|
|
17
|
+
* the shape renders from.
|
|
18
|
+
*
|
|
19
|
+
* const pos = vec(home.peek());
|
|
20
|
+
* const dot = s(circle(pos, 10));
|
|
21
|
+
* const { anim } = floating(dot, pos, home);
|
|
22
|
+
* this.anim.start(anim);
|
|
23
|
+
*
|
|
24
|
+
* While dragging, the settle spring is frozen (rate 0) so it never fights
|
|
25
|
+
* the pointer; on release it re-engages and eases `pos` home. */
|
|
26
|
+
export function floating(shape, pos, home, opts = {}) {
|
|
27
|
+
const dragging = cell(false);
|
|
28
|
+
const dispose = drag(shape, pos, dragging);
|
|
29
|
+
const anim = spring(pos, home, {
|
|
30
|
+
omega: opts.omega ?? 24,
|
|
31
|
+
zeta: opts.zeta ?? 0.9,
|
|
32
|
+
...opts,
|
|
33
|
+
// Never completes (re-engages every release) and yields to the pointer
|
|
34
|
+
// while held.
|
|
35
|
+
precision: 0,
|
|
36
|
+
rate: () => (dragging.value ? 0 : (opts.rate?.() ?? 1)),
|
|
37
|
+
});
|
|
38
|
+
return { dragging, anim, dispose };
|
|
39
|
+
}
|
|
40
|
+
// ── drag lifecycle ──────────────────────────────────────────────────
|
|
41
|
+
// The `was`-flag edge and z-raise every demo hand-rolls, factored out, plus a
|
|
42
|
+
// model-driven driver that ties a `Drag<M>` spec (drag-spec.ts) to the
|
|
43
|
+
// grab→preview→commit lifecycle — the spec is built once per grab (like
|
|
44
|
+
// Dragology's `dragologyOnDrag`), so candidate states are enumerated then.
|
|
45
|
+
/** Run `grab`/`drop` on the rising/falling edge of `active`. */
|
|
46
|
+
export function onGesture(active, edges) {
|
|
47
|
+
let was = false;
|
|
48
|
+
return effect(() => {
|
|
49
|
+
const now = active.value;
|
|
50
|
+
if (now && !was)
|
|
51
|
+
edges.grab?.();
|
|
52
|
+
else if (!now && was)
|
|
53
|
+
edges.drop?.();
|
|
54
|
+
was = now;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** Re-append shapes to raise them above siblings (z-order). */
|
|
58
|
+
export function raise(...shapes) {
|
|
59
|
+
for (const s of shapes)
|
|
60
|
+
s.el.parentElement?.appendChild(s.el);
|
|
61
|
+
}
|
|
62
|
+
/** Bind a committed `model` cell to a `Drag<M>` spec built at grab time. Owns
|
|
63
|
+
* the transient drag state (which element, the free pointer, the live preview)
|
|
64
|
+
* and commits the spec's drop on release — the demo only renders `preview`/`at`. */
|
|
65
|
+
export function dragModel(model, spec) {
|
|
66
|
+
const active = cell(null);
|
|
67
|
+
const pointer = vec(0, 0);
|
|
68
|
+
const live = cell(null);
|
|
69
|
+
const preview = derive(() => {
|
|
70
|
+
const s = live.value;
|
|
71
|
+
return s ? s.preview.value : model.value;
|
|
72
|
+
});
|
|
73
|
+
const at = Vec.derive(() => {
|
|
74
|
+
const s = live.value;
|
|
75
|
+
return s ? s.at.value : pointer.value;
|
|
76
|
+
});
|
|
77
|
+
const grip = (handle, id, seed, onGrab) => {
|
|
78
|
+
const dragging = cell(false);
|
|
79
|
+
const offDown = handle.on("pointerdown", () => {
|
|
80
|
+
pointer.value = seed();
|
|
81
|
+
active.value = id;
|
|
82
|
+
live.value = spec(id, pointer);
|
|
83
|
+
onGrab?.();
|
|
84
|
+
});
|
|
85
|
+
const offDrag = drag(handle, pointer, dragging);
|
|
86
|
+
const offEdge = onGesture(dragging, {
|
|
87
|
+
drop: () => {
|
|
88
|
+
const s = live.peek();
|
|
89
|
+
if (s)
|
|
90
|
+
model.value = s.drop.peek();
|
|
91
|
+
active.value = null;
|
|
92
|
+
live.value = null;
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
return () => {
|
|
96
|
+
offDown();
|
|
97
|
+
offDrag();
|
|
98
|
+
offEdge();
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
return { active, pointer, preview, at, grip };
|
|
102
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type Read } from "../core/index.js";
|
|
2
|
+
type V = {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
};
|
|
6
|
+
/** A drag behavior: Dragology's `DragBehavior`, reactive and parametric in the
|
|
7
|
+
* MODEL `M` (positions are just `M = Vec`). */
|
|
8
|
+
export interface Drag<M> {
|
|
9
|
+
/** Model rendered this frame (non-dragged elements reflow toward this). */
|
|
10
|
+
preview: Read<M>;
|
|
11
|
+
/** Model committed on release. */
|
|
12
|
+
drop: Read<M>;
|
|
13
|
+
/** Where the dragged handle sits this frame (the renderer floats it here). */
|
|
14
|
+
at: Read<V>;
|
|
15
|
+
/** Residual |pointer − achievable|; combinators arbitrate on this. */
|
|
16
|
+
gap: Read<number>;
|
|
17
|
+
}
|
|
18
|
+
/** `d.fixed`: a single reachable model; `locate` reads where the dragged handle
|
|
19
|
+
* lands in it (a layout cell, or a pure layout fn). */
|
|
20
|
+
declare function fixed<M>(pointer: Read<V>, state: M, locate: (m: M) => V): Drag<M>;
|
|
21
|
+
/** `d.vary`: a continuous family; `place` is the BACKWARD map pointer→model (a
|
|
22
|
+
* lens / `argminVec`, not numerical search), `gap` the residual off the family. */
|
|
23
|
+
declare function vary<M>(pointer: Read<V>, place: (p: V) => M, locate: (m: M) => V): Drag<M>;
|
|
24
|
+
/** `d.closest`: the behavior with the smallest `gap` (discrete snapping and
|
|
25
|
+
* continuous tracks both pick with it). */
|
|
26
|
+
declare function closest<M>(bs: readonly Drag<M>[]): Drag<M>;
|
|
27
|
+
/** `d.between`: free motion in the candidates' convex hull; preview is their
|
|
28
|
+
* barycentric blend (`mix` any `Lerp`/`Linear` model). Unlike `closest` it does
|
|
29
|
+
* NOT snap — it rests at the blend, so `drop` is the previewed mix. */
|
|
30
|
+
declare function between<M>(pointer: Read<V>, bs: readonly Drag<M>[], mix: (ms: readonly M[], ws: readonly number[]) => M): Drag<M>;
|
|
31
|
+
/** `d.whenFar`: use `near` unless its gap exceeds `radius`, then `far` (snap
|
|
32
|
+
* into a port, else float free). */
|
|
33
|
+
declare function whenFar<M>(near: Drag<M>, far: Drag<M>, radius: number): Drag<M>;
|
|
34
|
+
/** `d.withFloating`: the dragged handle follows the pointer while the rest
|
|
35
|
+
* reflow (they already do — `preview` is reactive); just an `at` override. */
|
|
36
|
+
declare function withFloating<M>(pointer: Read<V>, b: Drag<M>): Drag<M>;
|
|
37
|
+
/** `d.onDrop`: transform the committed model (create/destroy, snap-to-grid),
|
|
38
|
+
* the escape hatch beyond repositional drags. */
|
|
39
|
+
declare function onDrop<M>(b: Drag<M>, f: (m: M) => M): Drag<M>;
|
|
40
|
+
/** The drag-behavior DSL (Dragology's `d.`): primitives `fixed`/`vary`,
|
|
41
|
+
* combinators `closest`/`between`/`whenFar`, modifiers `withFloating`/`onDrop`.
|
|
42
|
+
* Build a `Drag<M>` once at grab; the renderer reads `preview`/`at`/`drop`. */
|
|
43
|
+
export declare const d: {
|
|
44
|
+
readonly fixed: typeof fixed;
|
|
45
|
+
readonly vary: typeof vary;
|
|
46
|
+
readonly closest: typeof closest;
|
|
47
|
+
readonly between: typeof between;
|
|
48
|
+
readonly whenFar: typeof whenFar;
|
|
49
|
+
readonly withFloating: typeof withFloating;
|
|
50
|
+
readonly onDrop: typeof onDrop;
|
|
51
|
+
};
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// drag-spec.ts — the general drag algebra (Dragology's `d.` DSL), model-driven.
|
|
2
|
+
//
|
|
3
|
+
// Dragology's closed object isn't "snap"; it's a behavior
|
|
4
|
+
//
|
|
5
|
+
// DragBehavior = (pointer) → { preview, dropState, gap }
|
|
6
|
+
//
|
|
7
|
+
// — the whole previewed MODEL this frame, the model to commit on release, and
|
|
8
|
+
// the residual the combinators arbitrate on. Snapping is one primitive of it.
|
|
9
|
+
//
|
|
10
|
+
// Reactive form: the result is cells already tracking a shared pointer, so
|
|
11
|
+
// nothing recomputes per frame by hand. And the deep bit — "drawing knows
|
|
12
|
+
// state→drawing, computing knows drawing→state" (a lens) — is FREE here:
|
|
13
|
+
// Dragology synthesizes drawing→state by speculative rendering (render every
|
|
14
|
+
// candidate, extract positions; quadratic), whereas bireactive already has it
|
|
15
|
+
// as the backward lens, so `vary` is a lens write and `preview` is a reactive
|
|
16
|
+
// previewed model (springs interpolate it — no view-diffing, no re-render).
|
|
17
|
+
//
|
|
18
|
+
// Exposed as `d` to mirror the paper. It builds on core's pointer math
|
|
19
|
+
// (`hullWeights` for `between`, `nearestIndex`'s sticky selection mirrored in
|
|
20
|
+
// `closest`). Renderer contract: render non-dragged elements from `preview`,
|
|
21
|
+
// the dragged one at `at`, and commit `drop` on release.
|
|
22
|
+
import { derive, hullWeights } from "../core/index.js";
|
|
23
|
+
const dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
|
24
|
+
/** `d.fixed`: a single reachable model; `locate` reads where the dragged handle
|
|
25
|
+
* lands in it (a layout cell, or a pure layout fn). */
|
|
26
|
+
function fixed(pointer, state, locate) {
|
|
27
|
+
const s = derive(() => state);
|
|
28
|
+
const at = derive(() => locate(state));
|
|
29
|
+
return { preview: s, drop: s, at, gap: derive(() => dist(pointer.value, at.value)) };
|
|
30
|
+
}
|
|
31
|
+
/** `d.vary`: a continuous family; `place` is the BACKWARD map pointer→model (a
|
|
32
|
+
* lens / `argminVec`, not numerical search), `gap` the residual off the family. */
|
|
33
|
+
function vary(pointer, place, locate) {
|
|
34
|
+
const preview = derive(() => place(pointer.value));
|
|
35
|
+
const at = derive(() => locate(preview.value));
|
|
36
|
+
return { preview, drop: preview, at, gap: derive(() => dist(pointer.value, at.value)) };
|
|
37
|
+
}
|
|
38
|
+
/** `d.closest`: the behavior with the smallest `gap` (discrete snapping and
|
|
39
|
+
* continuous tracks both pick with it). */
|
|
40
|
+
function closest(bs) {
|
|
41
|
+
const idx = derive(() => {
|
|
42
|
+
let best = 0;
|
|
43
|
+
let bg = Number.POSITIVE_INFINITY;
|
|
44
|
+
for (let i = 0; i < bs.length; i++) {
|
|
45
|
+
const g = bs[i].gap.value;
|
|
46
|
+
if (g < bg) {
|
|
47
|
+
bg = g;
|
|
48
|
+
best = i;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return best;
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
preview: derive(() => bs[idx.value].preview.value),
|
|
55
|
+
drop: derive(() => bs[idx.value].drop.value),
|
|
56
|
+
at: derive(() => bs[idx.value].at.value),
|
|
57
|
+
gap: derive(() => bs[idx.value].gap.value),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** `d.between`: free motion in the candidates' convex hull; preview is their
|
|
61
|
+
* barycentric blend (`mix` any `Lerp`/`Linear` model). Unlike `closest` it does
|
|
62
|
+
* NOT snap — it rests at the blend, so `drop` is the previewed mix. */
|
|
63
|
+
function between(pointer, bs, mix) {
|
|
64
|
+
const ws = derive(() => hullWeights(pointer.value, bs.map(b => b.at.value)));
|
|
65
|
+
const at = derive(() => {
|
|
66
|
+
const w = ws.value;
|
|
67
|
+
let x = 0;
|
|
68
|
+
let y = 0;
|
|
69
|
+
bs.forEach((b, i) => {
|
|
70
|
+
const a = b.at.value;
|
|
71
|
+
x += w[i] * a.x;
|
|
72
|
+
y += w[i] * a.y;
|
|
73
|
+
});
|
|
74
|
+
return { x, y };
|
|
75
|
+
});
|
|
76
|
+
const blend = derive(() => mix(bs.map(b => b.preview.value), ws.value));
|
|
77
|
+
return { preview: blend, drop: blend, at, gap: derive(() => dist(pointer.value, at.value)) };
|
|
78
|
+
}
|
|
79
|
+
/** `d.whenFar`: use `near` unless its gap exceeds `radius`, then `far` (snap
|
|
80
|
+
* into a port, else float free). */
|
|
81
|
+
function whenFar(near, far, radius) {
|
|
82
|
+
const pickFar = derive(() => near.gap.value > radius);
|
|
83
|
+
const sel = (f) => derive(() => f(pickFar.value ? far : near).value);
|
|
84
|
+
return {
|
|
85
|
+
preview: sel(b => b.preview),
|
|
86
|
+
drop: sel(b => b.drop),
|
|
87
|
+
at: sel(b => b.at),
|
|
88
|
+
gap: sel(b => b.gap),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** `d.withFloating`: the dragged handle follows the pointer while the rest
|
|
92
|
+
* reflow (they already do — `preview` is reactive); just an `at` override. */
|
|
93
|
+
function withFloating(pointer, b) {
|
|
94
|
+
return { preview: b.preview, drop: b.drop, gap: b.gap, at: pointer };
|
|
95
|
+
}
|
|
96
|
+
/** `d.onDrop`: transform the committed model (create/destroy, snap-to-grid),
|
|
97
|
+
* the escape hatch beyond repositional drags. */
|
|
98
|
+
function onDrop(b, f) {
|
|
99
|
+
return { preview: b.preview, at: b.at, gap: b.gap, drop: derive(() => f(b.drop.value)) };
|
|
100
|
+
}
|
|
101
|
+
/** The drag-behavior DSL (Dragology's `d.`): primitives `fixed`/`vary`,
|
|
102
|
+
* combinators `closest`/`between`/`whenFar`, modifiers `withFloating`/`onDrop`.
|
|
103
|
+
* Build a `Drag<M>` once at grab; the renderer reads `preview`/`at`/`drop`. */
|
|
104
|
+
export const d = {
|
|
105
|
+
fixed,
|
|
106
|
+
vary,
|
|
107
|
+
closest,
|
|
108
|
+
between,
|
|
109
|
+
whenFar,
|
|
110
|
+
withFloating,
|
|
111
|
+
onDrop,
|
|
112
|
+
};
|
package/dist/shapes/handle.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// handle.* — writable derived shapes (draggable circles wired to a Vec).
|
|
2
|
-
import { cell, mean, polar as polarLens, Vec, } from "../core/index.js";
|
|
2
|
+
import { cell, mean, polar as polarLens, SKIP, Vec, } from "../core/index.js";
|
|
3
3
|
import { Circle } from "./circle.js";
|
|
4
4
|
import { drag } from "./interaction.js";
|
|
5
5
|
const COLOR = "var(--bireactive-handle, #2563eb)";
|
|
@@ -51,7 +51,7 @@ const scaleHandle = (shape, radius = 40, opts) => {
|
|
|
51
51
|
// Reads center and scale; writes only scale.
|
|
52
52
|
const pos = Vec.lens([shape.center, shape.scale], vals => ({ x: vals[0].x + radius * vals[1].x, y: vals[0].y }), (target, vals) => {
|
|
53
53
|
const k = Math.max(0.05, Math.abs(target.x - vals[0].x) / radius);
|
|
54
|
-
return [
|
|
54
|
+
return [SKIP, { x: k, y: k }];
|
|
55
55
|
});
|
|
56
56
|
return handleFn(pos, { cursor: "ew-resize", ...opts });
|
|
57
57
|
};
|
package/dist/shapes/index.d.ts
CHANGED
|
@@ -7,11 +7,13 @@ export { type ArrowOpts, arrow, connect, ensureArrowMarker } from "./connect.js"
|
|
|
7
7
|
export { Curve, type CurveOpts, type CurveSegment, curve, ellipse } from "./curve.js";
|
|
8
8
|
export { dashedPath } from "./dashed.js";
|
|
9
9
|
export { debug } from "./debug.js";
|
|
10
|
+
export { type DragModel, dragModel, type FloatingOpts, type FloatingResult, floating, onGesture, raise, } from "./drag-behaviors.js";
|
|
11
|
+
export { type Drag, d } from "./drag-spec.js";
|
|
10
12
|
export { group } from "./group.js";
|
|
11
13
|
export { type HandleOpts, handle } from "./handle.js";
|
|
12
14
|
export { cursor, drag, draggable, dragRotate, dragWithState, hoverSignal } from "./interaction.js";
|
|
13
15
|
export { Label, type LabelOpts, label } from "./label.js";
|
|
14
|
-
export { type ArrangeOpts, arrange, expand, grid, split } from "./layout.js";
|
|
16
|
+
export { type ArrangeOpts, arrange, expand, grid, split, type TreeStack, type TreeStackBox, type TreeStackOpts, treeStack, } from "./layout.js";
|
|
15
17
|
export { Line, type LineOpts, line } from "./line.js";
|
|
16
18
|
export { type ForEachOptions, forEach } from "./list.js";
|
|
17
19
|
export { type Mount, mount } from "./mount.js";
|
package/dist/shapes/index.js
CHANGED
|
@@ -7,11 +7,13 @@ export { arrow, connect, ensureArrowMarker } from "./connect.js";
|
|
|
7
7
|
export { Curve, curve, ellipse } from "./curve.js";
|
|
8
8
|
export { dashedPath } from "./dashed.js";
|
|
9
9
|
export { debug } from "./debug.js";
|
|
10
|
+
export { dragModel, floating, onGesture, raise, } from "./drag-behaviors.js";
|
|
11
|
+
export { d } from "./drag-spec.js";
|
|
10
12
|
export { group } from "./group.js";
|
|
11
13
|
export { handle } from "./handle.js";
|
|
12
14
|
export { cursor, drag, draggable, dragRotate, dragWithState, hoverSignal } from "./interaction.js";
|
|
13
15
|
export { Label, label } from "./label.js";
|
|
14
|
-
export { arrange, expand, grid, split } from "./layout.js";
|
|
16
|
+
export { arrange, expand, grid, split, treeStack, } from "./layout.js";
|
|
15
17
|
export { Line, line } from "./line.js";
|
|
16
18
|
export { forEach } from "./list.js";
|
|
17
19
|
export { mount } from "./mount.js";
|
|
@@ -19,9 +19,8 @@ export declare function draggable(handle: AnyShape, onDrag: (local: Inner<Vec>)
|
|
|
19
19
|
* `shape.translate`. Grab offset is captured on pointerdown; optional
|
|
20
20
|
* `dragging` reports active state. Defaults `cursor` to `"grab"`. */
|
|
21
21
|
export declare function drag(shape: AnyShape, target: Writable<Vec>, dragging?: Writable<Cell<boolean>>): () => void;
|
|
22
|
-
/** Wrap a `drag(shape, target)` call and return a local `dragging`
|
|
23
|
-
*
|
|
24
|
-
* own state." */
|
|
22
|
+
/** Wrap a `drag(shape, target)` call and return a local `dragging` `Cell<boolean>`.
|
|
23
|
+
* Sugar for "give me a drag handle that exposes its own state." */
|
|
25
24
|
export declare function dragWithState(shape: AnyShape, target: Writable<Vec>): {
|
|
26
25
|
dragging: Cell<boolean>;
|
|
27
26
|
dispose: () => void;
|
|
@@ -63,43 +63,51 @@ export function cursor(shape, init) {
|
|
|
63
63
|
* `onState(active)` callback fires `true` on pointerdown and `false`
|
|
64
64
|
* on pointerup/cancel — `Handle` uses it to drive `.dragging`. */
|
|
65
65
|
export function draggable(handle, onDrag, onState) {
|
|
66
|
-
let dragging = false;
|
|
67
66
|
let pointerId = -1;
|
|
68
67
|
let unblock = null;
|
|
69
68
|
ownTouchGesture(handle);
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
const pe = e;
|
|
73
|
-
dragging = true;
|
|
74
|
-
pointerId = pe.pointerId;
|
|
75
|
-
handle.el.setPointerCapture(pointerId);
|
|
76
|
-
unblock = blockPageScroll();
|
|
77
|
-
onState?.(true);
|
|
78
|
-
onDrag(handle.toLocal(pe));
|
|
79
|
-
}));
|
|
80
|
-
offs.push(handle.on("pointermove", e => {
|
|
81
|
-
if (!dragging)
|
|
69
|
+
const onMove = (e) => {
|
|
70
|
+
if (pointerId === -1 || e.pointerId !== pointerId)
|
|
82
71
|
return;
|
|
83
72
|
onDrag(handle.toLocal(e));
|
|
84
|
-
}
|
|
85
|
-
const stop = () => {
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
73
|
+
};
|
|
74
|
+
const stop = (e) => {
|
|
75
|
+
if (pointerId === -1 || (e && e.pointerId !== pointerId))
|
|
76
|
+
return;
|
|
77
|
+
try {
|
|
78
|
+
handle.el.releasePointerCapture(pointerId);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* ok */
|
|
93
82
|
}
|
|
94
|
-
dragging = false;
|
|
95
83
|
pointerId = -1;
|
|
84
|
+
window.removeEventListener("pointermove", onMove);
|
|
85
|
+
window.removeEventListener("pointerup", stop);
|
|
86
|
+
window.removeEventListener("pointercancel", stop);
|
|
96
87
|
unblock?.();
|
|
97
88
|
unblock = null;
|
|
98
89
|
onState?.(false);
|
|
99
90
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
91
|
+
const offDown = handle.on("pointerdown", e => {
|
|
92
|
+
const pe = e;
|
|
93
|
+
pointerId = pe.pointerId;
|
|
94
|
+
try {
|
|
95
|
+
handle.el.setPointerCapture(pointerId);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
/* ok */
|
|
99
|
+
}
|
|
100
|
+
unblock = blockPageScroll();
|
|
101
|
+
window.addEventListener("pointermove", onMove);
|
|
102
|
+
window.addEventListener("pointerup", stop);
|
|
103
|
+
window.addEventListener("pointercancel", stop);
|
|
104
|
+
onState?.(true);
|
|
105
|
+
onDrag(handle.toLocal(pe));
|
|
106
|
+
});
|
|
107
|
+
return () => {
|
|
108
|
+
offDown();
|
|
109
|
+
stop();
|
|
110
|
+
};
|
|
103
111
|
}
|
|
104
112
|
/** Bind pointer drag on `shape` directly to a writable `Vec` (no handle dot);
|
|
105
113
|
* returns a disposer. `target` is in the SVG-root frame and coords are read
|
|
@@ -114,47 +122,60 @@ export function drag(shape, target, dragging) {
|
|
|
114
122
|
let dy = 0;
|
|
115
123
|
let pointerId = -1;
|
|
116
124
|
let unblock = null;
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
// Moves/ups are tracked on `window`, not the shape: pointer capture alone
|
|
126
|
+
// drops the gesture when the element is re-parented (z-raising) or the
|
|
127
|
+
// pointer outruns the shape, so a window listener is the reliable path.
|
|
128
|
+
const onMove = (e) => {
|
|
129
|
+
if (pointerId === -1 || e.pointerId !== pointerId)
|
|
130
|
+
return;
|
|
131
|
+
const world = shape.toWorld(e);
|
|
132
|
+
target.value = { x: world.x - dx, y: world.y - dy };
|
|
133
|
+
};
|
|
134
|
+
const stop = (e) => {
|
|
135
|
+
if (pointerId === -1 || (e && e.pointerId !== pointerId))
|
|
136
|
+
return;
|
|
137
|
+
try {
|
|
138
|
+
shape.el.releasePointerCapture(pointerId);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
/* ok */
|
|
142
|
+
}
|
|
143
|
+
pointerId = -1;
|
|
144
|
+
window.removeEventListener("pointermove", onMove);
|
|
145
|
+
window.removeEventListener("pointerup", stop);
|
|
146
|
+
window.removeEventListener("pointercancel", stop);
|
|
147
|
+
unblock?.();
|
|
148
|
+
unblock = null;
|
|
149
|
+
if (dragging)
|
|
150
|
+
dragging.value = false;
|
|
151
|
+
};
|
|
152
|
+
const offDown = shape.on("pointerdown", e => {
|
|
119
153
|
const pe = e;
|
|
120
154
|
pointerId = pe.pointerId;
|
|
121
|
-
|
|
155
|
+
try {
|
|
156
|
+
shape.el.setPointerCapture(pointerId);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
/* ok */
|
|
160
|
+
}
|
|
122
161
|
unblock = blockPageScroll();
|
|
123
162
|
const world = shape.toWorld(pe);
|
|
124
163
|
const v = target.value;
|
|
125
164
|
dx = world.x - v.x;
|
|
126
165
|
dy = world.y - v.y;
|
|
166
|
+
window.addEventListener("pointermove", onMove);
|
|
167
|
+
window.addEventListener("pointerup", stop);
|
|
168
|
+
window.addEventListener("pointercancel", stop);
|
|
127
169
|
if (dragging)
|
|
128
170
|
dragging.value = true;
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const world = shape.toWorld(e);
|
|
134
|
-
target.value = { x: world.x - dx, y: world.y - dy };
|
|
135
|
-
}));
|
|
136
|
-
const stop = () => {
|
|
137
|
-
if (pointerId !== -1) {
|
|
138
|
-
try {
|
|
139
|
-
shape.el.releasePointerCapture(pointerId);
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
/* ok */
|
|
143
|
-
}
|
|
144
|
-
pointerId = -1;
|
|
145
|
-
}
|
|
146
|
-
unblock?.();
|
|
147
|
-
unblock = null;
|
|
148
|
-
if (dragging)
|
|
149
|
-
dragging.value = false;
|
|
171
|
+
});
|
|
172
|
+
return () => {
|
|
173
|
+
offDown();
|
|
174
|
+
stop();
|
|
150
175
|
};
|
|
151
|
-
offs.push(shape.on("pointerup", stop));
|
|
152
|
-
offs.push(shape.on("pointercancel", stop));
|
|
153
|
-
return () => offs.forEach(d => d());
|
|
154
176
|
}
|
|
155
|
-
/** Wrap a `drag(shape, target)` call and return a local `dragging`
|
|
156
|
-
*
|
|
157
|
-
* own state." */
|
|
177
|
+
/** Wrap a `drag(shape, target)` call and return a local `dragging` `Cell<boolean>`.
|
|
178
|
+
* Sugar for "give me a drag handle that exposes its own state." */
|
|
158
179
|
export function dragWithState(shape, target) {
|
|
159
180
|
const dragging = cell(false);
|
|
160
181
|
const dispose = drag(shape, target, dragging);
|
package/dist/shapes/label.js
CHANGED
|
@@ -14,7 +14,7 @@ export class Label extends Shape {
|
|
|
14
14
|
: typeof content === "function"
|
|
15
15
|
? derive(content)
|
|
16
16
|
: cell(content);
|
|
17
|
-
const sizeSig = Num.
|
|
17
|
+
const sizeSig = Num.coerce(opts.size ?? tokens.fontSize);
|
|
18
18
|
const a = opts.align ?? { x: 0.5, y: 0.5 };
|
|
19
19
|
super("text", () => {
|
|
20
20
|
const text = flattenText(contentSig.value);
|
|
@@ -34,6 +34,12 @@ export class Label extends Shape {
|
|
|
34
34
|
this.attr("dominant-baseline", yAttr(a.y));
|
|
35
35
|
if (opts.bold)
|
|
36
36
|
this.attr("font-weight", 700);
|
|
37
|
+
// Labels are decorative: never select on drag, never steal a pointer from
|
|
38
|
+
// the shape underneath (so text over a draggable doesn't break its grab).
|
|
39
|
+
const style = this.intrinsic.style;
|
|
40
|
+
style.userSelect = "none";
|
|
41
|
+
style.setProperty("-webkit-user-select", "none");
|
|
42
|
+
style.pointerEvents = "none";
|
|
37
43
|
this.effect(() => {
|
|
38
44
|
this.intrinsic.innerHTML = renderContent(contentSig.value);
|
|
39
45
|
});
|
package/dist/shapes/layout.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box, type Val } from "../core/index.js";
|
|
1
|
+
import { Box, type Read, type Val } from "../core/index.js";
|
|
2
2
|
import type { Shape } from "./shape.js";
|
|
3
3
|
export interface ArrangeOpts {
|
|
4
4
|
/** Spacing between adjacent bounding boxes. Default 0. */
|
|
@@ -27,3 +27,49 @@ export declare function split(source: Box, axis: "x" | "y", parts: number | numb
|
|
|
27
27
|
export declare function grid(source: Box, rows: number, cols: number, opts?: {
|
|
28
28
|
gap?: Val<number>;
|
|
29
29
|
}): Box[][];
|
|
30
|
+
export interface TreeStackBox {
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
w: number;
|
|
34
|
+
h: number;
|
|
35
|
+
}
|
|
36
|
+
export interface TreeStackOpts<Id> {
|
|
37
|
+
/** Top-level node ids, in order (read inside a derive; cell reads track). */
|
|
38
|
+
roots: () => readonly Id[];
|
|
39
|
+
/** A container's children, in order (read inside a derive). */
|
|
40
|
+
kids: (id: Id) => readonly Id[];
|
|
41
|
+
/** Containers stack their `kids`; non-containers are sized by `leaf`. */
|
|
42
|
+
container: (id: Id) => boolean;
|
|
43
|
+
/** Intrinsic size of a non-container node. */
|
|
44
|
+
leaf: (id: Id) => {
|
|
45
|
+
w: number;
|
|
46
|
+
h: number;
|
|
47
|
+
};
|
|
48
|
+
/** Top-left of a root node. */
|
|
49
|
+
origin: (id: Id) => {
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
};
|
|
53
|
+
/** Space reserved above a container's children (a title bar). Default 0. */
|
|
54
|
+
header?: number;
|
|
55
|
+
/** Inset around a container's children. Default 0. */
|
|
56
|
+
pad?: number;
|
|
57
|
+
/** Space between adjacent children. Default 0. */
|
|
58
|
+
gap?: number;
|
|
59
|
+
/** Minimum container width. Default 0. */
|
|
60
|
+
minWidth?: number;
|
|
61
|
+
/** Height of an empty container. Default `header + 2·pad`. */
|
|
62
|
+
emptyHeight?: number;
|
|
63
|
+
}
|
|
64
|
+
export interface TreeStack<Id> {
|
|
65
|
+
/** Reactive placement of every reachable node. */
|
|
66
|
+
readonly boxes: Read<Map<Id, TreeStackBox>>;
|
|
67
|
+
/** A node's box as a reactive `Box` (zero box when absent). */
|
|
68
|
+
box(id: Id): Box;
|
|
69
|
+
}
|
|
70
|
+
/** Intrinsic ("hug-contents") layout of a tree as nested vertical stacks:
|
|
71
|
+
* each container's size is the bottom-up sum of its children, each child is
|
|
72
|
+
* placed top-down from its container. Unlike `row`/`col` (which fit items
|
|
73
|
+
* into a fixed container), the containers grow to fit. Pure function of the
|
|
74
|
+
* inputs, so feeding it a *previewed* tree yields a previewed layout. */
|
|
75
|
+
export declare function treeStack<Id>(opts: TreeStackOpts<Id>): TreeStack<Id>;
|
package/dist/shapes/layout.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Spatial composition primitives.
|
|
2
|
-
import { Box,
|
|
2
|
+
import { Box, boxExpand, derive, reader, transformBox, } from "../core/index.js";
|
|
3
3
|
/** Lay out `shapes` in a row/column. First stays put; the rest bind
|
|
4
4
|
* their `translate` reactively to sit `gap` past the previous.
|
|
5
5
|
* Reflows on size or anchor change. */
|
|
@@ -40,7 +40,7 @@ export function arrange(shapes, axis, opts = {}) {
|
|
|
40
40
|
/** Inflate a Box on each side by `by`. */
|
|
41
41
|
export function expand(b, by) {
|
|
42
42
|
const byFn = reader(by);
|
|
43
|
-
return Box.derive(() =>
|
|
43
|
+
return Box.derive(() => boxExpand(b.value, byFn()));
|
|
44
44
|
}
|
|
45
45
|
/** Split a Box along an axis into N reactive sub-Boxes.
|
|
46
46
|
*
|
|
@@ -72,3 +72,61 @@ export function split(source, axis, parts, opts = {}) {
|
|
|
72
72
|
export function grid(source, rows, cols, opts = {}) {
|
|
73
73
|
return split(source, "y", rows, opts).map(row => split(row, "x", cols, opts));
|
|
74
74
|
}
|
|
75
|
+
/** Intrinsic ("hug-contents") layout of a tree as nested vertical stacks:
|
|
76
|
+
* each container's size is the bottom-up sum of its children, each child is
|
|
77
|
+
* placed top-down from its container. Unlike `row`/`col` (which fit items
|
|
78
|
+
* into a fixed container), the containers grow to fit. Pure function of the
|
|
79
|
+
* inputs, so feeding it a *previewed* tree yields a previewed layout. */
|
|
80
|
+
export function treeStack(opts) {
|
|
81
|
+
const header = opts.header ?? 0;
|
|
82
|
+
const pad = opts.pad ?? 0;
|
|
83
|
+
const gap = opts.gap ?? 0;
|
|
84
|
+
const minW = opts.minWidth ?? 0;
|
|
85
|
+
const emptyH = opts.emptyHeight ?? header + 2 * pad;
|
|
86
|
+
const measure = (id) => {
|
|
87
|
+
if (!opts.container(id))
|
|
88
|
+
return opts.leaf(id);
|
|
89
|
+
const ks = opts.kids(id);
|
|
90
|
+
if (ks.length === 0)
|
|
91
|
+
return { w: minW, h: emptyH };
|
|
92
|
+
let maxw = 0;
|
|
93
|
+
let h = header + pad;
|
|
94
|
+
for (const c of ks) {
|
|
95
|
+
const m = measure(c);
|
|
96
|
+
maxw = Math.max(maxw, m.w);
|
|
97
|
+
h += m.h + gap;
|
|
98
|
+
}
|
|
99
|
+
return { w: Math.max(minW, maxw + 2 * pad), h: h + pad - gap };
|
|
100
|
+
};
|
|
101
|
+
const place = (id, x, y, out) => {
|
|
102
|
+
const m = measure(id);
|
|
103
|
+
out.set(id, { x, y, w: m.w, h: m.h });
|
|
104
|
+
if (opts.container(id)) {
|
|
105
|
+
let cy = y + header + pad;
|
|
106
|
+
for (const c of opts.kids(id)) {
|
|
107
|
+
place(c, x + pad, cy, out);
|
|
108
|
+
cy += measure(c).h + gap;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const boxes = derive(() => {
|
|
113
|
+
const out = new Map();
|
|
114
|
+
for (const r of opts.roots()) {
|
|
115
|
+
const o = opts.origin(r);
|
|
116
|
+
place(r, o.x, o.y, out);
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
});
|
|
120
|
+
const cache = new Map();
|
|
121
|
+
return {
|
|
122
|
+
boxes,
|
|
123
|
+
box(id) {
|
|
124
|
+
let b = cache.get(id);
|
|
125
|
+
if (!b) {
|
|
126
|
+
b = Box.derive(() => boxes.value.get(id) ?? { x: 0, y: 0, w: 0, h: 0 });
|
|
127
|
+
cache.set(id, b);
|
|
128
|
+
}
|
|
129
|
+
return b;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|