bireactive 0.3.4 → 0.3.5
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/apply-patches.d.ts +4 -0
- package/dist/automerge/apply-patches.js +62 -0
- package/dist/automerge/doc-cell.js +8 -2
- package/dist/automerge/reconcile.d.ts +9 -8
- package/dist/automerge/reconcile.js +38 -25
- package/dist/core/_counts.d.ts +0 -2
- package/dist/core/_counts.js +0 -1
- package/dist/core/cell.d.ts +44 -87
- package/dist/core/cell.js +195 -301
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/core/lenses/memory.js +25 -21
- package/dist/core/lenses/point-cloud.js +66 -47
- package/dist/core/lenses/snap.js +8 -7
- package/dist/core/lenses/text.js +8 -6
- package/dist/core/optic.d.ts +0 -5
- package/dist/core/optic.js +16 -28
- package/dist/core/values/audio.js +6 -5
- package/dist/core/values/canvas.js +20 -12
- package/dist/core/values/field.d.ts +2 -0
- package/dist/core/values/field.js +25 -0
- package/dist/core/values/reg.d.ts +2 -3
- package/dist/core/values/reg.js +3 -4
- package/dist/core/values/str.js +6 -6
- package/dist/formats/lens.js +21 -13
- package/dist/jsx-runtime.js +88 -22
- package/dist/learn/lens-net.js +3 -5
- package/dist/schema/lens.d.ts +4 -1
- package/dist/schema/lens.js +14 -7
- package/package.json +1 -1
package/dist/core/values/str.js
CHANGED
|
@@ -56,23 +56,23 @@ export class Str extends Cell {
|
|
|
56
56
|
* write. A write's own edge whitespace is stripped first. */
|
|
57
57
|
trim() {
|
|
58
58
|
return Str.lens(this, {
|
|
59
|
-
|
|
59
|
+
complement: (s) => {
|
|
60
60
|
const lead = /^\s*/.exec(s)?.[0] ?? "";
|
|
61
61
|
// Slice lead off first so trail can't overlap it on all-whitespace.
|
|
62
62
|
const remain = s.slice(lead.length);
|
|
63
63
|
const trail = /\s*$/.exec(remain)?.[0] ?? "";
|
|
64
64
|
return { lead, trail };
|
|
65
65
|
},
|
|
66
|
-
|
|
66
|
+
// `get` is the sole refresh: re-capture the edge padding (pure ⇒ idempotent).
|
|
67
|
+
get: (s, c) => {
|
|
67
68
|
const lead = /^\s*/.exec(s)?.[0] ?? "";
|
|
68
69
|
const remain = s.slice(lead.length);
|
|
69
70
|
const trail = /\s*$/.exec(remain)?.[0] ?? "";
|
|
71
|
+
c.lead = lead;
|
|
72
|
+
c.trail = trail;
|
|
70
73
|
return remain.slice(0, remain.length - trail.length);
|
|
71
74
|
},
|
|
72
|
-
|
|
73
|
-
update: c.lead + target.replace(/^\s+/, "").replace(/\s+$/, "") + c.trail,
|
|
74
|
-
complement: c,
|
|
75
|
-
}),
|
|
75
|
+
put: (target, _s, c) => c.lead + target.replace(/^\s+/, "").replace(/\s+$/, "") + c.trail,
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
/** Windowed view `s.slice(start, end)` (JS semantics). A write splices the
|
package/dist/formats/lens.js
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
// value this text last agreed with (by identity — the hub only changes
|
|
7
7
|
// identity on a real change). The spoke:
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
// the hub. Errors ⇒ `
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
9
|
+
// put — parse the written text. Clean ⇒ push the recovered value to
|
|
10
|
+
// the hub. Errors ⇒ `SKIP` (hub untouched), but the complement
|
|
11
|
+
// keeps the broken text so the view echoes it back instead of
|
|
12
|
+
// trampling the editor.
|
|
13
|
+
// get — when the hub moved away from `synced`, absorb it by three-way
|
|
14
|
+
// surgical merge around any error regions (the sole refresh,
|
|
15
|
+
// idempotent once synced); then emit the complement's text.
|
|
16
16
|
import { cell, lens, SKIP } from "../core/cell.js";
|
|
17
17
|
import { deepEqual, mergeText, valueOf, } from "./cst.js";
|
|
18
18
|
function fromValue(adapter, v) {
|
|
@@ -36,16 +36,24 @@ export function valueHub(initial) {
|
|
|
36
36
|
* the error regions. */
|
|
37
37
|
export function formatSpoke(hub, adapter) {
|
|
38
38
|
return lens(hub, {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
complement: (v) => fromValue(adapter, v),
|
|
40
|
+
// `get` is the sole refresh: when the hub moved off `synced`, absorb it by
|
|
41
|
+
// surgical merge (idempotent — once `synced === v`, re-reading is a no-op).
|
|
42
|
+
get: (v, c) => {
|
|
43
|
+
if (v !== c.synced)
|
|
44
|
+
Object.assign(c, absorb(adapter, c, v));
|
|
45
|
+
return c.text;
|
|
46
|
+
},
|
|
47
|
+
put: (target, _v, c) => {
|
|
43
48
|
const { tree, errors } = adapter.parse(target);
|
|
44
49
|
if (errors.length === 0) {
|
|
45
50
|
const v = valueOf(tree);
|
|
46
|
-
|
|
51
|
+
Object.assign(c, { text: target, tree, errors, synced: v });
|
|
52
|
+
return v;
|
|
47
53
|
}
|
|
48
|
-
|
|
54
|
+
// Broken edit: keep the text (so the view echoes it) but hold `synced`.
|
|
55
|
+
Object.assign(c, { text: target, tree, errors });
|
|
56
|
+
return SKIP;
|
|
49
57
|
},
|
|
50
58
|
});
|
|
51
59
|
}
|
package/dist/jsx-runtime.js
CHANGED
|
@@ -118,30 +118,59 @@ function reactiveText(get) {
|
|
|
118
118
|
}));
|
|
119
119
|
return node;
|
|
120
120
|
}
|
|
121
|
-
/** Two-way bind a form control to a writable cell: read
|
|
122
|
-
* control, write back on input. The forward write is skipped
|
|
123
|
-
* control is focused, so a live edit is never clobbered mid-drag (the
|
|
121
|
+
/** Two-way bind a form control or `contenteditable` to a writable cell: read
|
|
122
|
+
* forward into the control, write back on input. The forward write is skipped
|
|
123
|
+
* while the control is focused, so a live edit is never clobbered mid-drag (the
|
|
124
124
|
* controlled-input focus guard, written once). */
|
|
125
125
|
function bindLens(el, lens) {
|
|
126
|
-
|
|
126
|
+
if (el.isContentEditable)
|
|
127
|
+
return bindContentEditable(el, lens);
|
|
128
|
+
const input = el;
|
|
129
|
+
const checkbox = input.type === "checkbox";
|
|
127
130
|
track(effect(() => {
|
|
128
131
|
const v = lens.value;
|
|
129
132
|
if (checkbox) {
|
|
130
|
-
|
|
133
|
+
input.checked = !!v;
|
|
131
134
|
return;
|
|
132
135
|
}
|
|
133
136
|
const next = v == null ? "" : String(v);
|
|
134
|
-
const root =
|
|
135
|
-
if (root.activeElement !==
|
|
136
|
-
|
|
137
|
+
const root = input.getRootNode();
|
|
138
|
+
if (root.activeElement !== input && input.value !== next)
|
|
139
|
+
input.value = next;
|
|
137
140
|
}));
|
|
138
|
-
const evt = checkbox ||
|
|
139
|
-
|
|
141
|
+
const evt = checkbox || input.tagName === "SELECT" ? "change" : "input";
|
|
142
|
+
input.addEventListener(evt, () => {
|
|
140
143
|
lens.value = checkbox
|
|
141
|
-
?
|
|
142
|
-
:
|
|
143
|
-
? Number(
|
|
144
|
-
:
|
|
144
|
+
? input.checked
|
|
145
|
+
: input.type === "range" || input.type === "number"
|
|
146
|
+
? Number(input.value)
|
|
147
|
+
: input.value;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** Bind a `contenteditable` element to a writable string cell via `textContent`.
|
|
151
|
+
* Forward writes are skipped while focused or mid IME composition; the back-write
|
|
152
|
+
* fires on input and at composition end. Assumes plaintext — prefer
|
|
153
|
+
* `contenteditable="plaintext-only"` where supported; rich markup is the caller's. */
|
|
154
|
+
function bindContentEditable(el, lens) {
|
|
155
|
+
let composing = false;
|
|
156
|
+
track(effect(() => {
|
|
157
|
+
const v = lens.value;
|
|
158
|
+
const next = v == null ? "" : String(v);
|
|
159
|
+
const root = el.getRootNode();
|
|
160
|
+
if (root.activeElement !== el && el.textContent !== next)
|
|
161
|
+
el.textContent = next;
|
|
162
|
+
}));
|
|
163
|
+
const writeBack = () => {
|
|
164
|
+
if (!composing)
|
|
165
|
+
lens.value = el.textContent ?? "";
|
|
166
|
+
};
|
|
167
|
+
el.addEventListener("input", writeBack);
|
|
168
|
+
el.addEventListener("compositionstart", () => {
|
|
169
|
+
composing = true;
|
|
170
|
+
});
|
|
171
|
+
el.addEventListener("compositionend", () => {
|
|
172
|
+
composing = false;
|
|
173
|
+
writeBack();
|
|
145
174
|
});
|
|
146
175
|
}
|
|
147
176
|
/** Render `component` into `host`, collecting reactive teardowns. The returned
|
|
@@ -172,6 +201,50 @@ export function scope(fn) {
|
|
|
172
201
|
currentOwner = prev;
|
|
173
202
|
}
|
|
174
203
|
}
|
|
204
|
+
/** Bring `parent`'s children to exactly `nodes` in order, touching the DOM only
|
|
205
|
+
* where it differs. Relocates existing nodes with `moveBefore` (preserves focus,
|
|
206
|
+
* selection, and IME composition on items that didn't move); inserts fresh nodes
|
|
207
|
+
* and falls back to `replaceChildren` where `moveBefore` is unavailable. */
|
|
208
|
+
function reorder(parent, nodes) {
|
|
209
|
+
// Identical order — re-inserting would steal focus and reset clicks mid-interaction.
|
|
210
|
+
const cur = parent.childNodes;
|
|
211
|
+
let same = cur.length === nodes.length;
|
|
212
|
+
for (let i = 0; same && i < nodes.length; i++)
|
|
213
|
+
same = cur[i] === nodes[i];
|
|
214
|
+
if (same)
|
|
215
|
+
return;
|
|
216
|
+
const move = parent.moveBefore;
|
|
217
|
+
if (typeof move !== "function") {
|
|
218
|
+
parent.replaceChildren(...nodes);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const wanted = new Set(nodes);
|
|
222
|
+
for (let i = cur.length - 1; i >= 0; i--) {
|
|
223
|
+
const n = cur[i];
|
|
224
|
+
if (!wanted.has(n))
|
|
225
|
+
n.remove();
|
|
226
|
+
}
|
|
227
|
+
let ref = parent.firstChild;
|
|
228
|
+
for (const node of nodes) {
|
|
229
|
+
if (node === ref) {
|
|
230
|
+
ref = ref.nextSibling;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
// moveBefore relocates an already-connected node; brand-new nodes (and the
|
|
234
|
+
// disconnected/cross-document cases that make moveBefore throw) use insertBefore.
|
|
235
|
+
if (node.parentNode === parent) {
|
|
236
|
+
try {
|
|
237
|
+
move.call(parent, node, ref);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
parent.insertBefore(node, ref);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
parent.insertBefore(node, ref);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
175
248
|
/** Keyed list rendering: keep `parent`'s children in sync with a reactive array,
|
|
176
249
|
* reusing and reordering nodes by key, disposing those that leave. Each item is
|
|
177
250
|
* rendered in its own `scope` (untracked from the list effect, so item-internal
|
|
@@ -201,14 +274,7 @@ export function each(parent, items, key, render) {
|
|
|
201
274
|
cache.delete(k);
|
|
202
275
|
}
|
|
203
276
|
}
|
|
204
|
-
|
|
205
|
-
// identical children mid-interaction would steal focus and reset clicks.
|
|
206
|
-
const cur = parent.childNodes;
|
|
207
|
-
let same = cur.length === nodes.length;
|
|
208
|
-
for (let i = 0; same && i < nodes.length; i++)
|
|
209
|
-
same = cur[i] === nodes[i];
|
|
210
|
-
if (!same)
|
|
211
|
-
parent.replaceChildren(...nodes);
|
|
277
|
+
reorder(parent, nodes);
|
|
212
278
|
});
|
|
213
279
|
track(() => {
|
|
214
280
|
stop();
|
package/dist/learn/lens-net.js
CHANGED
|
@@ -63,10 +63,8 @@ function denseBackward(p, inDim, outDim, act, x, dOut) {
|
|
|
63
63
|
function denseLens(params, input, inDim, outDim, act, cfg) {
|
|
64
64
|
const parents = [params, input];
|
|
65
65
|
return lens(parents, {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
fwd: (s) => denseForward(s[0], inDim, outDim, act, s[1]),
|
|
69
|
-
bwd: (cot, s, c) => {
|
|
66
|
+
get: (s) => denseForward(s[0], inDim, outDim, act, s[1]),
|
|
67
|
+
put: (cot, s) => {
|
|
70
68
|
const { dIn, gW, gb } = denseBackward(s[0], inDim, outDim, act, s[1], cot);
|
|
71
69
|
let pUpd;
|
|
72
70
|
if (cfg.frozen) {
|
|
@@ -82,7 +80,7 @@ function denseLens(params, input, inDim, outDim, act, cfg) {
|
|
|
82
80
|
b[o] = b[o] - lr * gb[o];
|
|
83
81
|
pUpd = { W, b };
|
|
84
82
|
}
|
|
85
|
-
return
|
|
83
|
+
return [pUpd, dIn];
|
|
86
84
|
},
|
|
87
85
|
});
|
|
88
86
|
}
|
package/dist/schema/lens.d.ts
CHANGED
|
@@ -91,7 +91,10 @@ export declare function each(elem: VLens<Obj, Obj, any>): VLens<Obj[], Obj[], un
|
|
|
91
91
|
* for nested occurrences — e.g. rename a field at every level of a subtask
|
|
92
92
|
* tree of arbitrary depth. Cambria's open "recursive schemas" case. */
|
|
93
93
|
export declare function recurse(build: (self: OLens) => OLens): OLens;
|
|
94
|
-
/** Lift a value-lens onto a reactive cell.
|
|
94
|
+
/** Lift a value-lens onto a reactive cell. The value-level complement `C` is
|
|
95
|
+
* functional (heterogeneous products, possibly `null`), so a small engine-side
|
|
96
|
+
* holder `{ c }` carries it: `get`/`put` swap `holder.c` in place. `get` is the
|
|
97
|
+
* sole refresh (folds `step`; idempotent when `step` is). */
|
|
95
98
|
export declare function toStep<C>(vl: VLens<Obj, Obj, C>): Step;
|
|
96
99
|
/** A migration step: a writable lens from one POJO schema to the next. */
|
|
97
100
|
export type Step = (src: Writable<Cell<Obj>>) => Writable<Cell<Obj>>;
|
package/dist/schema/lens.js
CHANGED
|
@@ -369,15 +369,22 @@ export function recurse(build) {
|
|
|
369
369
|
return self;
|
|
370
370
|
}
|
|
371
371
|
// ── reactive lifting (cells) ─────────────────────────────────────────
|
|
372
|
-
/** Lift a value-lens onto a reactive cell.
|
|
372
|
+
/** Lift a value-lens onto a reactive cell. The value-level complement `C` is
|
|
373
|
+
* functional (heterogeneous products, possibly `null`), so a small engine-side
|
|
374
|
+
* holder `{ c }` carries it: `get`/`put` swap `holder.c` in place. `get` is the
|
|
375
|
+
* sole refresh (folds `step`; idempotent when `step` is). */
|
|
373
376
|
export function toStep(vl) {
|
|
374
377
|
return src => lens(src, {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
378
|
+
complement: (v) => ({ c: vl.init(v) }),
|
|
379
|
+
get: (v, h) => {
|
|
380
|
+
if (vl.step)
|
|
381
|
+
h.c = vl.step(v, h.c);
|
|
382
|
+
return vl.fwd(v, h.c);
|
|
383
|
+
},
|
|
384
|
+
put: (t, v, h) => {
|
|
385
|
+
const r = vl.bwd(t, v, h.c);
|
|
386
|
+
h.c = r.c;
|
|
387
|
+
return r.s;
|
|
381
388
|
},
|
|
382
389
|
});
|
|
383
390
|
}
|