partforge 0.74.0 → 0.76.0
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
|
@@ -609,7 +609,12 @@ parameters: [
|
|
|
609
609
|
```
|
|
610
610
|
|
|
611
611
|
Every control `key` must exist in `defaults`, or the control is silently dead —
|
|
612
|
-
`control-key-not-in-defaults` is an error for exactly that reason.
|
|
612
|
+
`control-key-not-in-defaults` is an error for exactly that reason. Its value there must
|
|
613
|
+
be a **number, string or boolean** — a control writes one scalar, so a control bound to
|
|
614
|
+
an array, an object, `null` or `NaN` is dead in the same silent way
|
|
615
|
+
(`control-default-not-primitive`), and a host that saves panel settings back into
|
|
616
|
+
`defaults` cannot write that value either. A non-primitive `defaults` entry that no
|
|
617
|
+
control is bound to is fine: `defaults` also seeds `p` for `build()`.
|
|
613
618
|
|
|
614
619
|
**Ids.** A section, a group and a preset may carry an `id`; the renderer keys its
|
|
615
620
|
element, state and disclosure maps on ids, so they must be unique across the whole
|
|
@@ -1896,7 +1901,8 @@ about the definition as a whole use `""`.
|
|
|
1896
1901
|
behave as authored — whether or not that shows up as a thrown exception. Some error
|
|
1897
1902
|
findings do correspond to a runtime throw (`build-throws`, `verify-expect-throws`),
|
|
1898
1903
|
but others catch **silent** wrongness: `missing-meta-title`, `part-view-unknown`,
|
|
1899
|
-
`control-key-not-in-defaults`, `
|
|
1904
|
+
`control-key-not-in-defaults`, `control-default-not-primitive`,
|
|
1905
|
+
`preset-key-not-in-defaults`, and
|
|
1900
1906
|
`verify-unknown-subpart` all fire on parts that build, measure, and verify cleanly —
|
|
1901
1907
|
a dead control that's silently unreachable, a view that renders nothing, or a
|
|
1902
1908
|
`verify` expectation that's silently dropped so its gate never runs. That's still an
|
|
@@ -1913,7 +1919,8 @@ previously didn't; that's the fix working as intended, not a regression.
|
|
|
1913
1919
|
`default-view-ambiguous` (warnings).
|
|
1914
1920
|
|
|
1915
1921
|
**Parameter schema** — `features-requires-sliders`, `features-requires-on`,
|
|
1916
|
-
`control-key-not-in-defaults`, `
|
|
1922
|
+
`control-key-not-in-defaults`, `control-default-not-primitive`,
|
|
1923
|
+
`preset-key-not-in-defaults`, `mixed-section-shape`,
|
|
1917
1924
|
`duplicate-preset-name`, `duplicate-node-id`, `select-options-missing`,
|
|
1918
1925
|
`select-default-not-in-options`, `log-scale-needs-positive-min`,
|
|
1919
1926
|
`when-key-not-in-defaults`, `when-unknown-operator`, `unknown-control-type` (errors);
|
package/package.json
CHANGED
|
@@ -350,11 +350,20 @@ export function attachAnimationControls(viewer, part, {
|
|
|
350
350
|
// even where ResizeObserver is absent.
|
|
351
351
|
let onStructureChanged = null;
|
|
352
352
|
|
|
353
|
+
// Sketch mode takes this slot over: the app floats its own composer where the
|
|
354
|
+
// bar sits, and a frozen sketch over a playing animation is meaningless
|
|
355
|
+
// anyway. Its own flag rather than a straight write to `display`, for the
|
|
356
|
+
// reason mount.js spells out for the view cube's two hide reasons: with one
|
|
357
|
+
// assignment shared between causes, whichever fires last wins, and a view
|
|
358
|
+
// switch back to an animated view would reveal a bar that sketch mode still
|
|
359
|
+
// wants gone.
|
|
360
|
+
let hiddenForSketch = false;
|
|
361
|
+
|
|
353
362
|
// Per-view + per-animation chrome: which chooser shows, the picker's options,
|
|
354
363
|
// title, ⓘ description, pager labels, scrubber ticks. A view with no
|
|
355
364
|
// animations hides the whole bar rather than showing an empty transport.
|
|
356
365
|
function syncStructure() {
|
|
357
|
-
bar.style.display = current ? "" : "none";
|
|
366
|
+
bar.style.display = current && !hiddenForSketch ? "" : "none";
|
|
358
367
|
onStructureChanged?.();
|
|
359
368
|
hideChapterBubble();
|
|
360
369
|
if (!current) return;
|
|
@@ -791,6 +800,15 @@ export function attachAnimationControls(viewer, part, {
|
|
|
791
800
|
playback?.userEdited();
|
|
792
801
|
syncUi();
|
|
793
802
|
},
|
|
803
|
+
// Hide/reveal for SKETCH mode only. `--pf-anim-clear` follows for free:
|
|
804
|
+
// syncStructure notifies the placement pass, which derives the clearance
|
|
805
|
+
// from this same `display` value and publishes 0px for a hidden bar.
|
|
806
|
+
setHidden(hidden) {
|
|
807
|
+
const next = hidden === true;
|
|
808
|
+
if (next === hiddenForSketch) return;
|
|
809
|
+
hiddenForSketch = next;
|
|
810
|
+
syncStructure();
|
|
811
|
+
},
|
|
794
812
|
// Mount calls this from the view-tab onChange, BEFORE it refreshes the
|
|
795
813
|
// view: the outgoing animation's params and opacity overrides must be
|
|
796
814
|
// restored before the incoming view composes its assembly.
|
|
@@ -104,6 +104,22 @@ function collectPresetBundles(part) {
|
|
|
104
104
|
|
|
105
105
|
const defaultKeys = (part) => new Set(Object.keys(part?.defaults ?? {}));
|
|
106
106
|
|
|
107
|
+
// What a control can actually edit: one finite scalar. Non-finite numbers are out
|
|
108
|
+
// with the rest — a slider cannot show NaN, and neither NaN nor Infinity survives
|
|
109
|
+
// a round trip through JSON or a source rewrite.
|
|
110
|
+
const isEditableValue = (v) =>
|
|
111
|
+
typeof v === "string" || typeof v === "boolean" || (typeof v === "number" && Number.isFinite(v));
|
|
112
|
+
|
|
113
|
+
// Name a value for a finding's message: the reader needs to know WHICH default is
|
|
114
|
+
// wrong and what it is, and `typeof []` ("object") tells them neither.
|
|
115
|
+
const describeValue = (v) => {
|
|
116
|
+
if (Array.isArray(v)) return "an array";
|
|
117
|
+
if (v === null) return "a null";
|
|
118
|
+
if (typeof v === "number") return `a ${Number.isNaN(v) ? "NaN" : String(v)}`;
|
|
119
|
+
if (v === undefined) return "an undefined";
|
|
120
|
+
return `a ${typeof v}`;
|
|
121
|
+
};
|
|
122
|
+
|
|
107
123
|
// Walk one WhenCondition, calling onKey(key) for every param key it reads and
|
|
108
124
|
// onOp(op) for every operator name it uses. allOf/anyOf/not recurse; a bare
|
|
109
125
|
// `{ key: value }` entry counts as reading `key` with no operator to check.
|
|
@@ -191,6 +207,32 @@ export const SCHEMA_RULES = [
|
|
|
191
207
|
`${path}.key`));
|
|
192
208
|
},
|
|
193
209
|
},
|
|
210
|
+
{
|
|
211
|
+
// A control edits ONE value, and the widgets read and write it as a scalar:
|
|
212
|
+
// a slider over an array renders NaN, a checkbox over an object writes
|
|
213
|
+
// `true` over it. It is dead in the same silent way control-key-not-in-defaults
|
|
214
|
+
// is — the part builds, the panel renders, the control does nothing usable —
|
|
215
|
+
// which is why this is an error rather than a warning.
|
|
216
|
+
//
|
|
217
|
+
// Only a key a CONTROL is bound to. `defaults` also seeds `p` for build(), so
|
|
218
|
+
// parking a table or a point list there for the build to read is legitimate
|
|
219
|
+
// authoring and none of lint's business.
|
|
220
|
+
id: "control-default-not-primitive",
|
|
221
|
+
run: ({ part }) => {
|
|
222
|
+
// Same guard as control-key-not-in-defaults: bail only when `defaults`
|
|
223
|
+
// isn't a plain object at all (missing-defaults already reports that).
|
|
224
|
+
if (!isPlainObject(part?.defaults)) return [];
|
|
225
|
+
const defaults = part.defaults;
|
|
226
|
+
return collectDescriptors(part)
|
|
227
|
+
.filter(({ container }) => !container)
|
|
228
|
+
.filter(({ d }) => typeof d.key === "string" && Object.hasOwn(defaults, d.key))
|
|
229
|
+
.filter(({ d }) => !isEditableValue(defaults[d.key]))
|
|
230
|
+
.map(({ d, path }) => err("control-default-not-primitive",
|
|
231
|
+
`control "${d.key}" is bound to ${describeValue(defaults[d.key])} default`,
|
|
232
|
+
`Give "${d.key}" a number, string or boolean default — a control writes one scalar, so it cannot edit this, and a host that saves panel settings back into \`defaults\` cannot write it either.`,
|
|
233
|
+
`${path}.key`));
|
|
234
|
+
},
|
|
235
|
+
},
|
|
194
236
|
{
|
|
195
237
|
id: "preset-key-not-in-defaults",
|
|
196
238
|
run: ({ part }) => {
|
package/src/framework/mount.js
CHANGED
|
@@ -912,6 +912,21 @@ export function mount(part, { createWorker, elements = {}, onBuild, onPick, onDo
|
|
|
912
912
|
},
|
|
913
913
|
});
|
|
914
914
|
if (animCtl) cleanup.defer(() => animCtl.detach());
|
|
915
|
+
// Sketch mode takes the transport bar's slot: the host floats its own
|
|
916
|
+
// composer there (partforge-cloud's sketch composer), and ink is stored in
|
|
917
|
+
// screen space against the pose it was drawn over — a playing animation
|
|
918
|
+
// under it is meaningless. STOP first, then hide: hiding first leaves a
|
|
919
|
+
// frame where an animation still drives the model beneath a bar that is
|
|
920
|
+
// already gone. Playback does not resume on the way out; "stop" is the
|
|
921
|
+
// contract, and notifyUserEdit is the same pause a user's own control edit
|
|
922
|
+
// performs.
|
|
923
|
+
if (annotateMode && animCtl) {
|
|
924
|
+
cleanup.defer(annotateMode.onModeChange(() => {
|
|
925
|
+
const on = annotateMode.isEnabled();
|
|
926
|
+
if (on) animCtl.notifyUserEdit();
|
|
927
|
+
animCtl.setHidden(on);
|
|
928
|
+
}));
|
|
929
|
+
}
|
|
915
930
|
|
|
916
931
|
// Re-run the active view under the current caching setting, so toggling the
|
|
917
932
|
// ?debug switch updates the readout for the same design without a param change.
|