bireactive 0.3.0 → 0.3.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.
- package/README.md +14 -7
- 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 +51 -0
- package/dist/core/cell.d.ts +148 -112
- package/dist/core/cell.js +945 -768
- package/dist/core/debug.d.ts +25 -0
- package/dist/core/debug.js +121 -0
- package/dist/core/derived-geometry.js +4 -7
- package/dist/core/index.d.ts +9 -2
- package/dist/core/index.js +8 -1
- package/dist/core/lenses/aggregates.d.ts +42 -52
- package/dist/core/lenses/aggregates.js +225 -116
- package/dist/core/lenses/geometry.d.ts +22 -4
- package/dist/core/lenses/geometry.js +59 -27
- package/dist/core/lenses/index.d.ts +6 -6
- package/dist/core/lenses/index.js +6 -6
- package/dist/core/lenses/memory.js +4 -17
- package/dist/core/lenses/numerical.d.ts +100 -0
- package/dist/core/lenses/{typed-factor.js → numerical.js} +136 -34
- package/dist/core/lenses/point-cloud.d.ts +67 -0
- package/dist/core/lenses/{closed-form-policies.js → point-cloud.js} +226 -84
- package/dist/core/lenses/snap.d.ts +18 -0
- package/dist/core/lenses/snap.js +138 -0
- package/dist/core/lenses/text.d.ts +40 -0
- package/dist/core/lenses/text.js +202 -0
- package/dist/core/lifecycle.js +3 -6
- package/dist/core/linalg.js +5 -11
- package/dist/core/optic.d.ts +13 -0
- package/dist/core/optic.js +39 -0
- package/dist/core/optics.d.ts +10 -0
- package/dist/core/optics.js +26 -0
- package/dist/core/store.d.ts +9 -0
- package/dist/core/store.js +77 -0
- package/dist/core/traits.d.ts +4 -7
- package/dist/core/traits.js +8 -12
- package/dist/core/values/anchor.js +0 -4
- package/dist/core/values/arr.d.ts +110 -0
- package/dist/core/values/arr.js +336 -0
- package/dist/core/values/audio.d.ts +8 -9
- package/dist/core/values/audio.js +11 -28
- package/dist/core/values/bool.d.ts +11 -11
- package/dist/core/values/bool.js +12 -22
- package/dist/core/values/box.d.ts +15 -20
- package/dist/core/values/box.js +20 -33
- package/dist/core/values/canvas.d.ts +18 -25
- package/dist/core/values/canvas.js +32 -66
- package/dist/core/values/color.d.ts +5 -7
- package/dist/core/values/color.js +5 -11
- package/dist/core/values/field.d.ts +6 -7
- package/dist/core/values/field.js +10 -35
- package/dist/core/values/flags.d.ts +1 -2
- package/dist/core/values/flags.js +1 -17
- package/dist/core/values/gpu.d.ts +6 -10
- package/dist/core/values/gpu.js +8 -22
- package/dist/core/values/matrix.d.ts +2 -4
- package/dist/core/values/matrix.js +2 -12
- package/dist/core/values/num.d.ts +19 -28
- package/dist/core/values/num.js +23 -41
- package/dist/core/values/pose.d.ts +2 -4
- package/dist/core/values/pose.js +3 -12
- package/dist/core/values/range.d.ts +18 -26
- package/dist/core/values/range.js +22 -39
- package/dist/core/values/reg/ambiguity.d.ts +8 -0
- package/dist/core/values/reg/ambiguity.js +131 -0
- package/dist/core/values/reg/engine.d.ts +91 -0
- package/dist/core/values/reg/engine.js +373 -0
- package/dist/core/values/reg/nfa.d.ts +42 -0
- package/dist/core/values/reg/nfa.js +391 -0
- package/dist/core/values/reg/regex.d.ts +7 -0
- package/dist/core/values/reg/regex.js +318 -0
- package/dist/core/values/reg/types.d.ts +60 -0
- package/dist/core/values/reg/types.js +3 -0
- package/dist/core/values/reg.d.ts +250 -0
- package/dist/core/values/reg.js +649 -0
- package/dist/core/values/str.d.ts +16 -60
- package/dist/core/values/str.js +133 -315
- package/dist/core/values/template.js +1 -24
- package/dist/core/values/transform.d.ts +3 -5
- package/dist/core/values/transform.js +3 -12
- package/dist/core/values/tri.d.ts +9 -10
- package/dist/core/values/tri.js +9 -15
- package/dist/core/values/vec.d.ts +9 -24
- package/dist/core/values/vec.js +9 -64
- package/dist/formats/lens.js +6 -9
- package/dist/index.d.ts +0 -11
- package/dist/index.js +1 -11
- 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 +22 -1
- package/dist/coll.d.ts +0 -74
- package/dist/coll.js +0 -210
- package/dist/core/lenses/closed-form-policies.d.ts +0 -57
- package/dist/core/lenses/decompositions.d.ts +0 -14
- package/dist/core/lenses/decompositions.js +0 -224
- package/dist/core/lenses/domain-aggregates.d.ts +0 -42
- package/dist/core/lenses/domain-aggregates.js +0 -245
- package/dist/core/lenses/typed-factor.d.ts +0 -40
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { Str } from "../values/str.js";
|
|
2
|
+
// ── word parsing ─────────────────────────────────────────────────────
|
|
3
|
+
/** A "word" character: letters, digits, underscore, apostrophe, hyphen
|
|
4
|
+
* (handles "don't", "co-op"). Everything else is a separator. */
|
|
5
|
+
const WORD_CHAR = /[\p{L}\p{N}_'-]/u;
|
|
6
|
+
/** Split `s` into words and separators. Returns:
|
|
7
|
+
*
|
|
8
|
+
* words[i] — the i-th run of word characters
|
|
9
|
+
* seps[0] — leading non-word characters (possibly empty)
|
|
10
|
+
* seps[i] — for 1 ≤ i ≤ words.length-1, the separator BETWEEN
|
|
11
|
+
* `words[i-1]` and `words[i]`
|
|
12
|
+
* seps[words.length] — trailing non-word characters
|
|
13
|
+
*
|
|
14
|
+
* Always satisfies `seps.length === words.length + 1`. */
|
|
15
|
+
export function parseWords(s) {
|
|
16
|
+
const words = [];
|
|
17
|
+
const seps = [];
|
|
18
|
+
let cur = "";
|
|
19
|
+
let inWord = false;
|
|
20
|
+
for (let i = 0; i < s.length; i++) {
|
|
21
|
+
const c = s[i];
|
|
22
|
+
if (WORD_CHAR.test(c)) {
|
|
23
|
+
if (!inWord) {
|
|
24
|
+
seps.push(cur);
|
|
25
|
+
cur = "";
|
|
26
|
+
inWord = true;
|
|
27
|
+
}
|
|
28
|
+
cur += c;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
if (inWord) {
|
|
32
|
+
words.push(cur);
|
|
33
|
+
cur = "";
|
|
34
|
+
inWord = false;
|
|
35
|
+
}
|
|
36
|
+
cur += c;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (inWord) {
|
|
40
|
+
words.push(cur);
|
|
41
|
+
seps.push("");
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
seps.push(cur);
|
|
45
|
+
}
|
|
46
|
+
return { words, seps };
|
|
47
|
+
}
|
|
48
|
+
/** Inverse of `parseWords`. Interleaves words with `seps`; added words
|
|
49
|
+
* get `" "` gaps, removed words keep the original trailing separator.
|
|
50
|
+
* A zero-word original (`seps.length === 1`) treats its one entry as
|
|
51
|
+
* lead only, so words append after it without double-counting as trail. */
|
|
52
|
+
export function rebuildWords(words, seps) {
|
|
53
|
+
const n = words.length;
|
|
54
|
+
if (n === 0)
|
|
55
|
+
return seps[0] ?? "";
|
|
56
|
+
const lead = seps[0] ?? "";
|
|
57
|
+
const trail = seps.length > 1 ? (seps[seps.length - 1] ?? "") : "";
|
|
58
|
+
let out = lead;
|
|
59
|
+
for (let i = 0; i < n; i++) {
|
|
60
|
+
out += words[i];
|
|
61
|
+
if (i < n - 1) {
|
|
62
|
+
const idx = i + 1;
|
|
63
|
+
// Interior separators only; the final `seps` entry is the trail.
|
|
64
|
+
const sep = idx < seps.length - 1 ? seps[idx] : undefined;
|
|
65
|
+
out += sep !== undefined ? sep : " ";
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
out += trail;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
// ── case masks ───────────────────────────────────────────────────────
|
|
74
|
+
/** Per-character case mask: `U` upper letter, `L` lower letter,
|
|
75
|
+
* `" "` non-letter. Length matches the source. */
|
|
76
|
+
export function caseMaskOf(s) {
|
|
77
|
+
let mask = "";
|
|
78
|
+
for (let i = 0; i < s.length; i++) {
|
|
79
|
+
const c = s[i];
|
|
80
|
+
if (c >= "A" && c <= "Z")
|
|
81
|
+
mask += "U";
|
|
82
|
+
else if (c >= "a" && c <= "z")
|
|
83
|
+
mask += "L";
|
|
84
|
+
else
|
|
85
|
+
mask += " ";
|
|
86
|
+
}
|
|
87
|
+
return mask;
|
|
88
|
+
}
|
|
89
|
+
/** Apply a case mask to `target`, position by position. Mask positions
|
|
90
|
+
* beyond `target.length` are ignored; target positions beyond the
|
|
91
|
+
* mask keep their native case (e.g. user appended a longer word). */
|
|
92
|
+
export function applyCaseMask(target, mask) {
|
|
93
|
+
let out = "";
|
|
94
|
+
for (let i = 0; i < target.length; i++) {
|
|
95
|
+
const c = target[i];
|
|
96
|
+
const m = i < mask.length ? mask[i] : " ";
|
|
97
|
+
if (m === "U")
|
|
98
|
+
out += c.toUpperCase();
|
|
99
|
+
else if (m === "L")
|
|
100
|
+
out += c.toLowerCase();
|
|
101
|
+
else
|
|
102
|
+
out += c;
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
const ASCII_LETTER = (c) => (c >= "a" && c <= "z") || (c >= "A" && c <= "Z");
|
|
107
|
+
/** Apply the case pattern of a source word to a target word. Detects
|
|
108
|
+
* all-upper / all-lower / title case, else falls back to position-wise
|
|
109
|
+
* `applyCaseMask`. Non-letter target chars always pass through unchanged
|
|
110
|
+
* (title-casing "-gng" → "-Gng"). */
|
|
111
|
+
export function applyCasePattern(target, mask) {
|
|
112
|
+
if (target.length === 0 || mask.length === 0)
|
|
113
|
+
return target;
|
|
114
|
+
const letters = [...mask].filter(c => c === "U" || c === "L");
|
|
115
|
+
if (letters.length === 0)
|
|
116
|
+
return target;
|
|
117
|
+
if (letters.every(c => c === "U"))
|
|
118
|
+
return target.toUpperCase();
|
|
119
|
+
if (letters.every(c => c === "L"))
|
|
120
|
+
return target.toLowerCase();
|
|
121
|
+
if (letters[0] === "U" && letters.slice(1).every(c => c === "L")) {
|
|
122
|
+
// Title case: uppercase the first letter (skipping leading
|
|
123
|
+
// non-letters), lowercase the rest, pass non-letters through.
|
|
124
|
+
let out = "";
|
|
125
|
+
let firstLetterDone = false;
|
|
126
|
+
for (let i = 0; i < target.length; i++) {
|
|
127
|
+
const c = target[i];
|
|
128
|
+
if (ASCII_LETTER(c)) {
|
|
129
|
+
out += firstLetterDone ? c.toLowerCase() : c.toUpperCase();
|
|
130
|
+
firstLetterDone = true;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
out += c;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
return applyCaseMask(target, mask);
|
|
139
|
+
}
|
|
140
|
+
/** (Re)build the case complement: positional `wordMasks` and content-keyed
|
|
141
|
+
* `byContent` in one pass. `byContent` lists stay in source order for FIFO
|
|
142
|
+
* consumption on write-back. */
|
|
143
|
+
function refreshCaseComplement(s, c) {
|
|
144
|
+
const { words } = parseWords(s);
|
|
145
|
+
const wordMasks = words.map(caseMaskOf);
|
|
146
|
+
const byContent = new Map();
|
|
147
|
+
for (let i = 0; i < words.length; i++) {
|
|
148
|
+
const key = words[i].toLowerCase();
|
|
149
|
+
let list = byContent.get(key);
|
|
150
|
+
if (list === undefined) {
|
|
151
|
+
list = [];
|
|
152
|
+
byContent.set(key, list);
|
|
153
|
+
}
|
|
154
|
+
list.push(wordMasks[i]);
|
|
155
|
+
}
|
|
156
|
+
c.wordMasks = wordMasks;
|
|
157
|
+
c.byContent = byContent;
|
|
158
|
+
}
|
|
159
|
+
/** Apply the case complement to a target string and rebuild. Each
|
|
160
|
+
* target word goes through three lookup tiers — content match
|
|
161
|
+
* (FIFO-consumed from a per-call clone), positional fallback, then
|
|
162
|
+
* native pass-through. */
|
|
163
|
+
function applyCaseComplement(target, c) {
|
|
164
|
+
const { words, seps } = parseWords(target);
|
|
165
|
+
// Per-call clone: consume FIFO without mutating the stored map, so
|
|
166
|
+
// repeated writes start from the same state.
|
|
167
|
+
const remaining = new Map();
|
|
168
|
+
for (const [k, list] of c.byContent)
|
|
169
|
+
remaining.set(k, list.slice());
|
|
170
|
+
const cased = words.map((w, i) => {
|
|
171
|
+
const key = w.toLowerCase();
|
|
172
|
+
const matches = remaining.get(key);
|
|
173
|
+
if (matches !== undefined && matches.length > 0) {
|
|
174
|
+
return applyCasePattern(w, matches.shift());
|
|
175
|
+
}
|
|
176
|
+
const mask = i < c.wordMasks.length ? c.wordMasks[i] : "";
|
|
177
|
+
return mask.length === 0 ? w : applyCasePattern(w, mask);
|
|
178
|
+
});
|
|
179
|
+
return rebuildWords(cased, seps);
|
|
180
|
+
}
|
|
181
|
+
function buildCaseComplement(s) {
|
|
182
|
+
const c = { wordMasks: [], byContent: new Map() };
|
|
183
|
+
refreshCaseComplement(s, c);
|
|
184
|
+
return c;
|
|
185
|
+
}
|
|
186
|
+
// ── caseFold ─────────────────────────────────────────────────────────
|
|
187
|
+
/** Case-folded view of a string cell with word-aware case recovery on
|
|
188
|
+
* write. Read folds to lower (default) or upper; write recovers the
|
|
189
|
+
* source's per-word case — lookup priority: (1) content match (FIFO
|
|
190
|
+
* across duplicates); (2) per-position fallback for new content; (3)
|
|
191
|
+
* native for content beyond the source structure. */
|
|
192
|
+
export function caseFold(parent, to = "lower") {
|
|
193
|
+
const fold = to === "upper" ? (s) => s.toUpperCase() : (s) => s.toLowerCase();
|
|
194
|
+
return Str.lens(parent, {
|
|
195
|
+
init: (s) => buildCaseComplement(s),
|
|
196
|
+
fwd: (s) => fold(s),
|
|
197
|
+
bwd: (target, _s, c) => ({
|
|
198
|
+
update: applyCaseComplement(target, c),
|
|
199
|
+
complement: c,
|
|
200
|
+
}),
|
|
201
|
+
});
|
|
202
|
+
}
|
package/dist/core/lifecycle.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// `each(source, body)` — body runs per element (keyed by reference
|
|
5
|
-
// identity); cleanup on removal.
|
|
6
|
-
// `when(source, body)` — body runs while truthy; cleanup on falsy.
|
|
1
|
+
// Reactive-collection lifecycle helpers, implemented via `effect`: `each` runs a
|
|
2
|
+
// body per element (keyed by reference identity) with cleanup on removal; `when`
|
|
3
|
+
// runs a body while truthy with cleanup on falsy.
|
|
7
4
|
import { effect } from "./cell.js";
|
|
8
5
|
/** Run `body(item)` per element on first sight (storing its cleanup) and
|
|
9
6
|
* run that cleanup when the element leaves. Identity is element
|
package/dist/core/linalg.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// Systems are tiny (n ≤ ~12), so unrolled direct solves (n = 2, 3) and
|
|
8
|
-
// in-place LDLᵀ beat anything general-purpose AND allocate nothing — the
|
|
9
|
-
// matrix is destroyed, the solution is written into `b`. This replaces the
|
|
10
|
-
// old full-inverse-then-multiply path (1.9–5.3× slower; allocated an
|
|
11
|
-
// `n×2n` augmented buffer per write).
|
|
1
|
+
// Small dense linear algebra for the numerical backward directions (the
|
|
2
|
+
// factor/argmin pseudoinverse `A k = δy` with `A = J W Jᵀ + λI` SPD, and the
|
|
3
|
+
// per-cell Newton solves in constraints). Systems are tiny (n ≤ ~12), so
|
|
4
|
+
// unrolled solves (n = 2, 3) and in-place LDLᵀ beat anything general-purpose and
|
|
5
|
+
// allocate nothing: the matrix is destroyed, the solution written into `b`.
|
|
12
6
|
//
|
|
13
7
|
// Convention: matrices are row-major, length `n*n`.
|
|
14
8
|
const TINY = 1e-14;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Optic } from "./cell.js";
|
|
2
|
+
/** Build an optic from a forward and a backward. A 2-arg `put(b, a)` reads the
|
|
3
|
+
* source; a 1-arg `put(b)` reconstructs it (and is treated as an `iso`). */
|
|
4
|
+
export declare function optic<A, B>(get: (a: A) => B, put: (b: B, a: A) => A): Optic<A, B>;
|
|
5
|
+
/** A lossless, source-independent optic (an isomorphism): `to`/`from` invert. */
|
|
6
|
+
export declare function iso<A, B>(to: (a: A) => B, from: (b: B) => A): Optic<A, B>;
|
|
7
|
+
/** Field optic: project key `K`, putting back with a spread-replace. */
|
|
8
|
+
export declare function atKey<T, K extends keyof T>(key: K): Optic<T, T[K]>;
|
|
9
|
+
/** Compose optics left-to-right into one: `compose(a, b, c)` is `a` then `b` then
|
|
10
|
+
* `c`. Typed for up to three; falls back to `Optic<unknown, unknown>` beyond. */
|
|
11
|
+
export declare function compose<A, B>(a: Optic<A, B>): Optic<A, B>;
|
|
12
|
+
export declare function compose<A, B, C>(a: Optic<A, B>, b: Optic<B, C>): Optic<A, C>;
|
|
13
|
+
export declare function compose<A, B, C, D>(a: Optic<A, B>, b: Optic<B, C>, c: Optic<C, D>): Optic<A, D>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Lenses as first-class values, independent of any `Cell`. An `Optic<A, B>` is a
|
|
2
|
+
// lens transform *unbound* — a `get`/`put` pair you compose, store, and apply to
|
|
3
|
+
// a source with `cell.through(optic)` (≡ `lens(cell, o.get, o.put)`). Composition
|
|
4
|
+
// is ordinary lens composition: `(f ∘ g).put(c, a) = f.put(g.put(c, f.get(a)),
|
|
5
|
+
// a)`, reconstructing the inner source from `a` each back-write. An `iso` is the
|
|
6
|
+
// lossless case whose `put` ignores the source (`readsSource = false`), so
|
|
7
|
+
// `through` can bind a cheaper 1-arg backward. No complement here — use
|
|
8
|
+
// `lens(parent, spec)` when you need one.
|
|
9
|
+
//
|
|
10
|
+
// The `Optic` type lives in cell.ts so that file stays import-free and its
|
|
11
|
+
// `Cell.through` can name it; this module is the constructor/algebra surface.
|
|
12
|
+
function make(get, put, readsSource) {
|
|
13
|
+
return {
|
|
14
|
+
get,
|
|
15
|
+
put,
|
|
16
|
+
readsSource,
|
|
17
|
+
through(next) {
|
|
18
|
+
// Composed backward reconstructs the inner B from the outer A, so it always
|
|
19
|
+
// reads the source regardless of either side's own `readsSource`.
|
|
20
|
+
return make(a => next.get(get(a)), (c, a) => put(next.put(c, get(a)), a), true);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Build an optic from a forward and a backward. A 2-arg `put(b, a)` reads the
|
|
25
|
+
* source; a 1-arg `put(b)` reconstructs it (and is treated as an `iso`). */
|
|
26
|
+
export function optic(get, put) {
|
|
27
|
+
return make(get, put, put.length >= 2);
|
|
28
|
+
}
|
|
29
|
+
/** A lossless, source-independent optic (an isomorphism): `to`/`from` invert. */
|
|
30
|
+
export function iso(to, from) {
|
|
31
|
+
return make(to, b => from(b), false);
|
|
32
|
+
}
|
|
33
|
+
/** Field optic: project key `K`, putting back with a spread-replace. */
|
|
34
|
+
export function atKey(key) {
|
|
35
|
+
return make(t => t[key], (v, t) => ({ ...t, [key]: v }), true);
|
|
36
|
+
}
|
|
37
|
+
export function compose(...optics) {
|
|
38
|
+
return optics.reduce((a, b) => a.through(b));
|
|
39
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Cell, type Read, type Writable } from "./cell.js";
|
|
2
|
+
/** Writable field view of `c.value[key]` (spread-replace put). A read-only
|
|
3
|
+
* parent yields a read-only view. */
|
|
4
|
+
export declare function at<T, K extends keyof T>(c: Writable<Cell<T>>, key: K): Writable<Cell<T[K]>>;
|
|
5
|
+
export declare function at<T, K extends keyof T>(c: Read<T>, key: K): Cell<T[K]>;
|
|
6
|
+
/** Lens view of every field, lazily and memoized — `const { r, g, b } =
|
|
7
|
+
* fields(rgb)` yields one writable `at` per key. */
|
|
8
|
+
export declare function fields<T extends object>(c: Writable<Cell<T>>): {
|
|
9
|
+
[K in keyof T]-?: Writable<Cell<T[K]>>;
|
|
10
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Plain-record field optics over a `Cell<T>`. Unlike `fieldOf`/`fieldLens`
|
|
2
|
+
// (cell.ts), which need the field's Cell constructor to return a typed value
|
|
3
|
+
// class, `at`/`fields` return a base `Cell<T[K]>` with full key inference and no
|
|
4
|
+
// constructor argument — thin sugar over `fieldOf` for plain records.
|
|
5
|
+
import { Cell, fieldOf } from "./cell.js";
|
|
6
|
+
export function at(c, key) {
|
|
7
|
+
const ctor = Cell;
|
|
8
|
+
return fieldOf(c, key, ctor);
|
|
9
|
+
}
|
|
10
|
+
/** Lens view of every field, lazily and memoized — `const { r, g, b } =
|
|
11
|
+
* fields(rgb)` yields one writable `at` per key. */
|
|
12
|
+
export function fields(c) {
|
|
13
|
+
const cache = new Map();
|
|
14
|
+
return new Proxy(Object.create(null), {
|
|
15
|
+
get(_t, key) {
|
|
16
|
+
if (typeof key === "symbol")
|
|
17
|
+
return undefined;
|
|
18
|
+
let v = cache.get(key);
|
|
19
|
+
if (v === undefined) {
|
|
20
|
+
v = at(c, key);
|
|
21
|
+
cache.set(key, v);
|
|
22
|
+
}
|
|
23
|
+
return v;
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Cell, Writable } from "./cell.js";
|
|
2
|
+
/** Deep store view: the cell itself, plus a `Store` per object field. Primitives
|
|
3
|
+
* and functions bottom out at the plain `Writable<Cell<T>>`. */
|
|
4
|
+
export type Store<T> = Writable<Cell<T>> & (T extends readonly any[] ? unknown : T extends (...args: any[]) => any ? unknown : T extends object ? {
|
|
5
|
+
[K in keyof T]-?: Store<T[K]>;
|
|
6
|
+
} : unknown);
|
|
7
|
+
/** Deep, lens-backed store view of `cell`. Field access returns a nested `Store`;
|
|
8
|
+
* write through `.value` at any depth. */
|
|
9
|
+
export declare function store<T>(cell: Writable<Cell<T>>): Store<T>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Deep store proxy over a `Cell`: `store(cell).a.b` is a chain of `at` field
|
|
2
|
+
// lenses, so it's an ordinary `Cell` (read/write `.value`, bind in JSX, compose
|
|
3
|
+
// with the graph). Writes are spread-replace puts back to the root.
|
|
4
|
+
//
|
|
5
|
+
// A field whose name collides with the cell surface (`value`, `peek`, `lens`,
|
|
6
|
+
// `derive`, `merge`, `through`) resolves to the cell member — use `at(cell, key)`
|
|
7
|
+
// to reach such a field.
|
|
8
|
+
import { at } from "./optics.js";
|
|
9
|
+
// Cell members forwarded to the underlying cell rather than treated as a field,
|
|
10
|
+
// plus the object protocol methods JS itself may touch.
|
|
11
|
+
const FORWARD = new Set([
|
|
12
|
+
"value",
|
|
13
|
+
"peek",
|
|
14
|
+
"lens",
|
|
15
|
+
"derive",
|
|
16
|
+
"merge",
|
|
17
|
+
"through",
|
|
18
|
+
"toString",
|
|
19
|
+
"valueOf",
|
|
20
|
+
"toJSON",
|
|
21
|
+
"constructor",
|
|
22
|
+
]);
|
|
23
|
+
// One proxy per cell, so `store(c).a === store(c).a` and child stores are stable.
|
|
24
|
+
const wrapped = new WeakMap();
|
|
25
|
+
function wrap(cell) {
|
|
26
|
+
const hit = wrapped.get(cell);
|
|
27
|
+
if (hit !== undefined)
|
|
28
|
+
return hit;
|
|
29
|
+
// Per-key field lenses and their child stores, both memoized.
|
|
30
|
+
const lensFor = new Map();
|
|
31
|
+
const fieldLens = (key) => {
|
|
32
|
+
let l = lensFor.get(key);
|
|
33
|
+
if (l === undefined) {
|
|
34
|
+
l = at(cell, key);
|
|
35
|
+
lensFor.set(key, l);
|
|
36
|
+
}
|
|
37
|
+
return l;
|
|
38
|
+
};
|
|
39
|
+
const childStores = new Map();
|
|
40
|
+
const proxy = new Proxy(cell, {
|
|
41
|
+
get(target, key) {
|
|
42
|
+
if (typeof key === "symbol" || FORWARD.has(key)) {
|
|
43
|
+
const v = target[key];
|
|
44
|
+
return typeof v === "function" ? v.bind(target) : v;
|
|
45
|
+
}
|
|
46
|
+
if (key === "then")
|
|
47
|
+
return undefined; // never a thenable
|
|
48
|
+
let s = childStores.get(key);
|
|
49
|
+
if (s === undefined) {
|
|
50
|
+
s = wrap(fieldLens(key));
|
|
51
|
+
childStores.set(key, s);
|
|
52
|
+
}
|
|
53
|
+
return s;
|
|
54
|
+
},
|
|
55
|
+
set(target, key, value) {
|
|
56
|
+
if (typeof key === "symbol" || FORWARD.has(key)) {
|
|
57
|
+
target[key] = value;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
fieldLens(key).value = value;
|
|
61
|
+
return true;
|
|
62
|
+
},
|
|
63
|
+
has(target, key) {
|
|
64
|
+
if (typeof key === "symbol" || FORWARD.has(key))
|
|
65
|
+
return true;
|
|
66
|
+
const v = target.peek();
|
|
67
|
+
return typeof v === "object" && v !== null && key in v;
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
wrapped.set(cell, proxy);
|
|
71
|
+
return proxy;
|
|
72
|
+
}
|
|
73
|
+
/** Deep, lens-backed store view of `cell`. Field access returns a nested `Store`;
|
|
74
|
+
* write through `.value` at any depth. */
|
|
75
|
+
export function store(cell) {
|
|
76
|
+
return wrap(cell);
|
|
77
|
+
}
|
package/dist/core/traits.d.ts
CHANGED
|
@@ -41,13 +41,10 @@ export interface TraitDict<T> {
|
|
|
41
41
|
}
|
|
42
42
|
/** Valid keys of `TraitDict`. The set of declarable traits. */
|
|
43
43
|
export type TraitKey = keyof TraitDict<unknown>;
|
|
44
|
-
/** "A reactive whose class declares the listed traits." `_t` is a
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* function spring<T>(sig: Traits<T, "linear" | "metric">, target: Val<T>)
|
|
50
|
-
* function tween<T>(sig: Traits<T, "lerp">, target: T, dur: Val<number>) */
|
|
44
|
+
/** "A reactive whose class declares the listed traits." `_t` is a phantom slot
|
|
45
|
+
* typed against `typeof Cls.traits`; listed keys must resolve to non-null. Pure
|
|
46
|
+
* constraint — doesn't imply `Cell<T>`; intersect with `Writable<Cell<T>>` /
|
|
47
|
+
* `Read<T>` for capability. */
|
|
51
48
|
export type Traits<T, K extends TraitKey = never> = {
|
|
52
49
|
/** @internal Phantom slot; never accessed at runtime. */
|
|
53
50
|
readonly _t: {
|
package/dist/core/traits.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
// Traits — polymorphic interfaces declared on
|
|
2
|
-
// `static traits = { … }` dictionary. One
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Traits — polymorphic interfaces declared on a class via a single
|
|
2
|
+
// `static traits = { … }` dictionary. One constraint type `Traits<T, K>` covers
|
|
3
|
+
// any combination of required traits via a union of keys (no per-trait alias
|
|
4
|
+
// proliferation):
|
|
5
5
|
//
|
|
6
6
|
// function spring<T>(sig: Traits<T, "linear" | "metric">, target: Val<T>) …
|
|
7
7
|
// function tween<T>(sig: Traits<T, "lerp">, target: T, dur: Val<number>) …
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// - Runtime: `requireLinear` & siblings walk `s.constructor.traits.*`.
|
|
14
|
-
//
|
|
15
|
-
// The engine (signal.ts) is trait-ignorant; subclasses thread equality
|
|
16
|
-
// through `super(v, { equals })` themselves.
|
|
9
|
+
// Two axes: at the type level `Traits<T, K>` requires a phantom `_t` slot typed
|
|
10
|
+
// against the class's static traits; at runtime `requireLinear` & siblings walk
|
|
11
|
+
// `s.constructor.traits.*`. The engine itself is trait-ignorant — subclasses
|
|
12
|
+
// thread equality through `super(v, { equals })`.
|
|
17
13
|
/** Class-level traits dictionary for any Cell subclass. */
|
|
18
14
|
const dictOf = (s) => s.constructor?.traits ?? {};
|
|
19
15
|
const className = (s) => s.constructor?.name ?? "?";
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
// anchor.ts — points on the unit box `[0,1]²` (registration on a shape).
|
|
2
|
-
// dir.ts — unit direction vectors in `[-1,1]²` (displacement from rest).
|
|
3
|
-
//
|
|
4
|
-
// Plain constants — no reactive wrapping. Use as defaults / arguments.
|
|
5
1
|
/** Anchor points on the unit box (`Center = {0.5, 0.5}`). */
|
|
6
2
|
export const Anchor = {
|
|
7
3
|
TopLeft: { x: 0, y: 0 },
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Cell, type Read, type Writable } from "../cell.js";
|
|
2
|
+
import { Num } from "./num.js";
|
|
3
|
+
/** The value of an `Arr<T>`: its element cells, in order. */
|
|
4
|
+
type Elems<T> = readonly Cell<T>[];
|
|
5
|
+
/** A predicate over an element cell; optional `assert(c)` makes the test pass
|
|
6
|
+
* by writing the cell. */
|
|
7
|
+
export interface CellPred<T> {
|
|
8
|
+
(c: Cell<T>): boolean;
|
|
9
|
+
assert?: (c: Cell<T>) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare class Arr<T> extends Cell<Elems<T>> {
|
|
12
|
+
#private;
|
|
13
|
+
static traits: {
|
|
14
|
+
equals: <E>(a: readonly E[], b: readonly E[]) => boolean;
|
|
15
|
+
};
|
|
16
|
+
readonly _t: typeof Arr.traits;
|
|
17
|
+
constructor(items?: Elems<T>);
|
|
18
|
+
/** An `Arr` whose elements are derived from an external `source` and whose
|
|
19
|
+
* structural edits rewrite that source via `ops`. */
|
|
20
|
+
static fromSource<S, R>(source: Read<S>, getter: (s: S) => Elems<R>, ops: {
|
|
21
|
+
insert?: (v: R | Cell<R>, at?: number) => Cell<R>;
|
|
22
|
+
remove?: (e: Cell<R>) => void;
|
|
23
|
+
moveBefore?: (e: Cell<R>, anchor: Cell<R> | null) => void;
|
|
24
|
+
}): Arr<R>;
|
|
25
|
+
/** The element cells in order; tracked when read in an effect/derive. */
|
|
26
|
+
get cells(): Elems<T>;
|
|
27
|
+
/** Snapshot of element values, `readonly T[]`; re-derives on any value or
|
|
28
|
+
* structural change. Memoized per instance. */
|
|
29
|
+
get values(): Read<readonly T[]>;
|
|
30
|
+
/** Element count as a reactive `Num` (structural changes only). */
|
|
31
|
+
get length(): Num;
|
|
32
|
+
/** This element's index as a writable `Num`: reading gives its position,
|
|
33
|
+
* writing reorders (splices the reference to the rounded, clamped target).
|
|
34
|
+
* Writable on a base `Arr`; read-only over a derived view. */
|
|
35
|
+
indexOf(e: Cell<T>): Writable<Num>;
|
|
36
|
+
/** Append `v` (a value is wrapped in a fresh cell; a cell passes through).
|
|
37
|
+
* Returns the element cell. */
|
|
38
|
+
push(v: T | Cell<T>): Cell<T>;
|
|
39
|
+
/** Insert at `at` (default: end). On a view, delegates through the view's
|
|
40
|
+
* `insert` (a filter asserts its predicate; `map` has none, so it throws). */
|
|
41
|
+
insert(v: T | Cell<T>, at?: number): Cell<T>;
|
|
42
|
+
/** Remove element `e` (by reference). On a view, delegates through the
|
|
43
|
+
* view's `remove`. */
|
|
44
|
+
remove(e: Cell<T>): void;
|
|
45
|
+
/** Remove the element at index `i` in this (view's) order. */
|
|
46
|
+
removeAt(i: number): void;
|
|
47
|
+
/** Move `e` to index `to` (rounded, clamped) in this (view's) order. */
|
|
48
|
+
move(e: Cell<T>, to: number): void;
|
|
49
|
+
/** Splice `e` to just before `anchor` (or to the end when `anchor` is null).
|
|
50
|
+
* On a base `Arr` this rewrites the reference order; on a view it delegates
|
|
51
|
+
* through the view's `moveBefore`, where the shared `anchor` cell stays
|
|
52
|
+
* meaningful in the base order. No-op if `e` isn't present. */
|
|
53
|
+
moveBefore(e: Cell<T>, anchor: Cell<T> | null): void;
|
|
54
|
+
/** Make `e` appear in this view: insert into the base if absent, then assert
|
|
55
|
+
* each view's constraint up the chain. */
|
|
56
|
+
assertContains(e: Cell<T>): void;
|
|
57
|
+
/** Empty the collection (a view removes only its visible elements). */
|
|
58
|
+
clear(): void;
|
|
59
|
+
/** Subset whose elements pass `pred` (which reads element values, so the
|
|
60
|
+
* view re-derives reactively). Shares the element cells. `insert` adds to
|
|
61
|
+
* the parent and asserts the predicate. */
|
|
62
|
+
filter(pred: CellPred<T>): Arr<T>;
|
|
63
|
+
/** Projection ordered by `key` (read off each element, so it re-sorts
|
|
64
|
+
* reactively). Shares the element cells; structural edits delegate to the
|
|
65
|
+
* base (a `move`/`insert` repositions in the base order, not the view's). */
|
|
66
|
+
sortBy(key: (c: Cell<T>) => number): Arr<T>;
|
|
67
|
+
/** Per-element map. `f` projects each element value; with the inverse `g`
|
|
68
|
+
* the mapped elements are writable lenses (editing one writes the source
|
|
69
|
+
* cell), else they're read-only. Element identity is stable: one lens per
|
|
70
|
+
* source cell, memoized. */
|
|
71
|
+
map<U>(f: (v: T) => U, g?: (u: U, v: T) => T): Arr<U>;
|
|
72
|
+
/** Partition by a per-element key field into derived sub-`Arr`s, one writable
|
|
73
|
+
* filter view per bucket. `move(e, key, index)` writes the key field and
|
|
74
|
+
* splices the base so `e` lands at `index` in its new group. `order` seeds
|
|
75
|
+
* empty buckets and pins their order. */
|
|
76
|
+
groupBy<K>(field: (e: Cell<T>) => Writable<Cell<K>>, opts?: {
|
|
77
|
+
order?: readonly K[];
|
|
78
|
+
}): GroupArr<K, T>;
|
|
79
|
+
}
|
|
80
|
+
/** One bucket of a `groupBy`: its key and the derived sub-`Arr` of members. */
|
|
81
|
+
export interface Group<K, T> {
|
|
82
|
+
key: K;
|
|
83
|
+
items: Arr<T>;
|
|
84
|
+
}
|
|
85
|
+
/** The result of `Arr.groupBy`: derived buckets plus a grouped `move`/`insert`
|
|
86
|
+
* whose backward pass writes the group field and reorders the base. */
|
|
87
|
+
export declare class GroupArr<K, T> {
|
|
88
|
+
#private;
|
|
89
|
+
/** The buckets in order; tracked when read in an effect/derive. */
|
|
90
|
+
readonly groups: Read<readonly Group<K, T>[]>;
|
|
91
|
+
constructor(parent: Arr<T>, field: (e: Cell<T>) => Writable<Cell<K>>, order: readonly K[]);
|
|
92
|
+
get value(): readonly Group<K, T>[];
|
|
93
|
+
map<F>(f: (g: Group<K, T>) => F): Read<readonly F[]>;
|
|
94
|
+
/** Place `e` in group `key` at `index` (within that group's order). Asserts
|
|
95
|
+
* every upstream filter, writes the group field, and splices the base — one
|
|
96
|
+
* batch, so every view re-flows once. Omit `index` to keep base order. */
|
|
97
|
+
move(e: Cell<T>, key: K, index?: number): void;
|
|
98
|
+
/** Alias of `move`. */
|
|
99
|
+
insert(e: Cell<T>, key: K, index?: number): void;
|
|
100
|
+
/** Remove `e` from the source through the chain. */
|
|
101
|
+
remove(e: Cell<T>): void;
|
|
102
|
+
}
|
|
103
|
+
/** `field === value`, assertable by writing the field; for `filter` predicates. */
|
|
104
|
+
export declare function is<T, V>(field: (e: Cell<T>) => Writable<Cell<V>>, value: V): CellPred<T>;
|
|
105
|
+
/** Conjunction of cell predicates; asserts every clause. */
|
|
106
|
+
export declare function allPass<T>(...preds: readonly CellPred<T>[]): CellPred<T>;
|
|
107
|
+
/** Writable `Arr<T>` from values and/or cells (values are wrapped in fresh
|
|
108
|
+
* cells; cells pass through by identity). */
|
|
109
|
+
export declare function arr<T>(items?: Iterable<T | Cell<T>>): Writable<Arr<T>>;
|
|
110
|
+
export {};
|