partforge 0.146.3 → 0.146.4
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/docs/AUTHORING-PARTS.md
CHANGED
|
@@ -1248,7 +1248,12 @@ export function tilePicker(host) {
|
|
|
1248
1248
|
`update({reason, disabled})` is called when your key changes from outside the widget
|
|
1249
1249
|
(`reason: "sync"` — a preset, undo, `setParams`), when `derived` changes
|
|
1250
1250
|
(`"derived"`), and once after `host.state` was restored on mount (`"restore"`). It is
|
|
1251
|
-
**not** called for your own `set
|
|
1251
|
+
**not** called for your own `set`, and a `"derived"` update is **held while a gesture is
|
|
1252
|
+
in flight** — a `set` with `commit: false`, or a `host.controls` sub-control being
|
|
1253
|
+
edited — and delivered once `host.commit()` ends it (`host.derived` is current
|
|
1254
|
+
throughout). That is what lets the pattern above rebuild its sub-panel on every
|
|
1255
|
+
`update` without pulling the slider or number box out from under the user: every
|
|
1256
|
+
build the drag itself triggers ends in a derived update. `dispose()` runs on teardown.
|
|
1252
1257
|
|
|
1253
1258
|
**Rules that keep it working.**
|
|
1254
1259
|
|
package/package.json
CHANGED
|
@@ -69,6 +69,10 @@ export function makeCustom(node, params, { onChange, onCommit, info, custom = {}
|
|
|
69
69
|
let instance = null;
|
|
70
70
|
let retired = false;
|
|
71
71
|
let lastDerived = "{}";
|
|
72
|
+
// A derived update that arrived while a deferred gesture was in flight
|
|
73
|
+
// (`pending` non-empty): held, delivered once the gesture commits. See
|
|
74
|
+
// `update` below.
|
|
75
|
+
let heldUpdate = false;
|
|
72
76
|
|
|
73
77
|
const report = (phase, message) => onError?.({ key: node.key, label, phase, message });
|
|
74
78
|
|
|
@@ -131,6 +135,9 @@ export function makeCustom(node, params, { onChange, onCommit, info, custom = {}
|
|
|
131
135
|
if (!changed.length) return;
|
|
132
136
|
for (const k of changed) pending.delete(k);
|
|
133
137
|
onCommit?.(changed);
|
|
138
|
+
// The gesture is over: deliver the derived update it held back (once,
|
|
139
|
+
// for the latest derived — host.derived already carries it).
|
|
140
|
+
if (heldUpdate && pending.size === 0) { heldUpdate = false; update("derived"); }
|
|
134
141
|
},
|
|
135
142
|
|
|
136
143
|
setState(patch) { if (retired) return; Object.assign(host.state, patch); },
|
|
@@ -210,7 +217,21 @@ export function makeCustom(node, params, { onChange, onCommit, info, custom = {}
|
|
|
210
217
|
instance = r && typeof r === "object" ? r : null;
|
|
211
218
|
});
|
|
212
219
|
|
|
213
|
-
|
|
220
|
+
// A derived update is HELD while a deferred gesture is in flight — a
|
|
221
|
+
// `host.set(…, {commit: false})` or a host.controls sub-control edit that
|
|
222
|
+
// has not committed yet. Every build the gesture itself triggers (a slider
|
|
223
|
+
// drag's live preview, each keystroke in a number box) ends in a derived
|
|
224
|
+
// refresh, and the documented widget pattern answers update() by rebuilding
|
|
225
|
+
// its sub-panel — which tore the input being edited out of the DOM before
|
|
226
|
+
// its `change` could fire. Chromium dispatches `change` on detach; WebKit
|
|
227
|
+
// does not, so on Safari the edit reached the viewer and was never
|
|
228
|
+
// committed (partforge-cloud feedback #172). host.derived is still updated
|
|
229
|
+
// at once; only the call is deferred, to the commit that ends the gesture.
|
|
230
|
+
// `sync` is never held: an outside write supersedes the gesture.
|
|
231
|
+
const update = (reason) => {
|
|
232
|
+
if (reason === "derived" && pending.size > 0) { heldUpdate = true; return; }
|
|
233
|
+
guarded("update", () => instance?.update?.({ reason, disabled: host.disabled }));
|
|
234
|
+
};
|
|
214
235
|
|
|
215
236
|
// Restoring panel state is not part of creation: a throw here is a bad
|
|
216
237
|
// `update`, not a bad `create` — the widget did construct successfully.
|
|
@@ -67,6 +67,10 @@ export function makeNumeric(node, params, { onChange, onCommit, info }) {
|
|
|
67
67
|
// the log mapping don't, so be explicit).
|
|
68
68
|
const thumbFor = (v) => log ? toPosSafe(v) : Math.max(node.min, Math.min(node.max, v));
|
|
69
69
|
|
|
70
|
+
// Edited (an `input`) since the last `change`. A widget torn down mid-edit
|
|
71
|
+
// commits what it holds (dispose below): the DOM fires no `change` for a
|
|
72
|
+
// detached input on WebKit, and the value is already in params either way.
|
|
73
|
+
let dirty = false;
|
|
70
74
|
let slider = null;
|
|
71
75
|
if (!numeric) {
|
|
72
76
|
slider = document.createElement("input");
|
|
@@ -78,9 +82,10 @@ export function makeNumeric(node, params, { onChange, onCommit, info }) {
|
|
|
78
82
|
params[node.key] = v;
|
|
79
83
|
box.value = numStr(v);
|
|
80
84
|
paintWarn();
|
|
85
|
+
dirty = true;
|
|
81
86
|
onChange?.();
|
|
82
87
|
});
|
|
83
|
-
slider.addEventListener("change", () => onCommit?.());
|
|
88
|
+
slider.addEventListener("change", () => { dirty = false; onCommit?.(); });
|
|
84
89
|
wrap.append(slider);
|
|
85
90
|
}
|
|
86
91
|
|
|
@@ -131,6 +136,7 @@ export function makeNumeric(node, params, { onChange, onCommit, info }) {
|
|
|
131
136
|
params[node.key] = v;
|
|
132
137
|
if (slider) slider.value = thumbFor(v);
|
|
133
138
|
paintWarn();
|
|
139
|
+
dirty = true;
|
|
134
140
|
onChange?.();
|
|
135
141
|
});
|
|
136
142
|
box.addEventListener("change", () => {
|
|
@@ -140,6 +146,7 @@ export function makeNumeric(node, params, { onChange, onCommit, info }) {
|
|
|
140
146
|
box.value = numStr(v);
|
|
141
147
|
if (slider) slider.value = thumbFor(v);
|
|
142
148
|
paintWarn();
|
|
149
|
+
dirty = false;
|
|
143
150
|
onChange?.();
|
|
144
151
|
onCommit?.();
|
|
145
152
|
});
|
|
@@ -150,5 +157,8 @@ export function makeNumeric(node, params, { onChange, onCommit, info }) {
|
|
|
150
157
|
paintWarn();
|
|
151
158
|
};
|
|
152
159
|
paintWarn();
|
|
153
|
-
|
|
160
|
+
// Torn down with an edit still uncommitted (a sub-panel rebuilt under the
|
|
161
|
+
// pointer, a remount): commit it, since no `change` is coming.
|
|
162
|
+
const dispose = () => { if (dirty) { dirty = false; onCommit?.(); } };
|
|
163
|
+
return { el: wrap, sync, dispose };
|
|
154
164
|
}
|