bireactive 0.3.0 → 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/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/core/_counts.d.ts +48 -0
- package/dist/core/_counts.js +58 -0
- package/dist/core/cell.d.ts +148 -112
- package/dist/core/cell.js +946 -768
- package/dist/core/debug.d.ts +25 -0
- package/dist/core/debug.js +121 -0
- package/dist/core/index.d.ts +6 -1
- package/dist/core/index.js +5 -0
- package/dist/core/lenses/closed-form-policies.js +8 -3
- package/dist/core/lenses/index.d.ts +1 -0
- package/dist/core/lenses/index.js +1 -0
- package/dist/core/lenses/snap.d.ts +18 -0
- package/dist/core/lenses/snap.js +145 -0
- 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/values/audio.js +4 -5
- package/dist/core/values/canvas.js +15 -18
- package/dist/core/values/str.js +8 -8
- package/dist/formats/lens.js +6 -9
- 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/schema/lens.js +5 -5
- 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/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 +6 -0
- package/dist/shapes/layout.d.ts +47 -1
- package/dist/shapes/layout.js +59 -1
- package/package.json +24 -2
|
@@ -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/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
|
@@ -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, boxExpand, reader, transformBox } from "../core/index.js";
|
|
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. */
|
|
@@ -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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bireactive",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Bi-directional reactive programming.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,6 +11,18 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js"
|
|
13
13
|
},
|
|
14
|
+
"./automerge": {
|
|
15
|
+
"types": "./dist/automerge/index.d.ts",
|
|
16
|
+
"import": "./dist/automerge/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./jsx-runtime": {
|
|
19
|
+
"types": "./dist/jsx-runtime.d.ts",
|
|
20
|
+
"import": "./dist/jsx-runtime.js"
|
|
21
|
+
},
|
|
22
|
+
"./jsx-dev-runtime": {
|
|
23
|
+
"types": "./dist/jsx-dev-runtime.d.ts",
|
|
24
|
+
"import": "./dist/jsx-dev-runtime.js"
|
|
25
|
+
},
|
|
14
26
|
"./package.json": "./package.json"
|
|
15
27
|
},
|
|
16
28
|
"sideEffects": false,
|
|
@@ -21,7 +33,9 @@
|
|
|
21
33
|
],
|
|
22
34
|
"scripts": {
|
|
23
35
|
"dev": "vite --host",
|
|
24
|
-
"site": "vite build && typedoc",
|
|
36
|
+
"site": "vite build && typedoc && vitepress build docs",
|
|
37
|
+
"docs": "typedoc && vitepress build docs",
|
|
38
|
+
"docs:dev": "typedoc && vitepress dev docs --port 5556",
|
|
25
39
|
"preview": "vite preview",
|
|
26
40
|
"prebuild": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
27
41
|
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json --resolve-full-paths",
|
|
@@ -38,6 +52,9 @@
|
|
|
38
52
|
"postversion": "npm publish && git push --follow-tags"
|
|
39
53
|
},
|
|
40
54
|
"dependencies": {
|
|
55
|
+
"@automerge/automerge-repo": "^2.6.0-subduction.34",
|
|
56
|
+
"@automerge/automerge-repo-network-broadcastchannel": "^2.6.0-subduction.34",
|
|
57
|
+
"@automerge/automerge-repo-storage-indexeddb": "^2.6.0-subduction.34",
|
|
41
58
|
"prism-esm": "^1.29.0-fix.6",
|
|
42
59
|
"temml": "^0.13.3"
|
|
43
60
|
},
|
|
@@ -54,9 +71,14 @@
|
|
|
54
71
|
"reactive-framework-test-suite": "^0.0.2",
|
|
55
72
|
"tsc-alias": "^1.8.17",
|
|
56
73
|
"typedoc": "^0.28.19",
|
|
74
|
+
"typedoc-plugin-markdown": "^4.12.0",
|
|
75
|
+
"typedoc-vitepress-theme": "^1.1.3",
|
|
57
76
|
"typescript": "^6.0.3",
|
|
58
77
|
"vite": "^7.3.3",
|
|
59
78
|
"vite-node": "^5.3.0",
|
|
79
|
+
"vite-plugin-top-level-await": "^1.6.0",
|
|
80
|
+
"vite-plugin-wasm": "^3.6.0",
|
|
81
|
+
"vitepress": "^1.6.4",
|
|
60
82
|
"vitest": "^4.1.7"
|
|
61
83
|
},
|
|
62
84
|
"keywords": [
|