partforge 0.46.4 → 0.47.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/docs/AUTHORING-PARTS.md +18 -4
- package/package.json +1 -1
- package/src/framework/animation-controls.js +269 -48
- package/src/framework/app.css +111 -9
- package/src/framework/chrome.css +20 -0
- package/src/framework/controls.js +14 -363
- package/src/framework/lint/rules-animations.js +13 -12
- package/src/framework/lint/rules-schema.js +10 -21
- package/src/framework/panel/info.js +76 -0
- package/src/framework/panel/legacy.js +138 -0
- package/src/framework/panel/model.js +84 -0
- package/src/framework/panel/panel-state.js +70 -0
- package/src/framework/panel/render.js +249 -0
- package/src/framework/panel/widget-specs.js +36 -0
- package/src/framework/panel/widgets/checkbox.js +37 -0
- package/src/framework/panel/widgets/index.js +13 -0
- package/src/framework/panel/widgets/numeric.js +77 -0
- package/src/framework/panel/widgets/text.js +33 -0
- package/types/part.d.ts +19 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// slider + number: a range input (omitted for `number`) beside an editable value
|
|
2
|
+
// box. The box accepts exact values finer than `step`; typed values clamp to
|
|
3
|
+
// [min, max] on commit (blur/Enter).
|
|
4
|
+
import { attachInfo } from "../info.js";
|
|
5
|
+
|
|
6
|
+
// Short numeric string without float noise (4 dp max) for the value box.
|
|
7
|
+
const numStr = (v) => String(Math.round(v * 1e4) / 1e4);
|
|
8
|
+
|
|
9
|
+
export function clampToRange(raw, min, max) {
|
|
10
|
+
const v = parseFloat(raw);
|
|
11
|
+
if (!Number.isFinite(v)) return null;
|
|
12
|
+
return Math.min(max, Math.max(min, v));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function el(tag, className, text) {
|
|
16
|
+
const node = document.createElement(tag);
|
|
17
|
+
if (className) node.className = className;
|
|
18
|
+
if (text != null) node.textContent = text;
|
|
19
|
+
return node;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function makeNumeric(node, params, { onChange, info }) {
|
|
23
|
+
const numeric = node.type === "number";
|
|
24
|
+
const wrap = el("div", "slider");
|
|
25
|
+
const row = el("div", "row");
|
|
26
|
+
const label = el("label", "", node.label);
|
|
27
|
+
attachInfo(label, node.description, info);
|
|
28
|
+
row.append(label);
|
|
29
|
+
|
|
30
|
+
const val = el("div", "val");
|
|
31
|
+
const box = document.createElement("input");
|
|
32
|
+
box.type = "number";
|
|
33
|
+
box.className = "num";
|
|
34
|
+
box.min = node.min; box.max = node.max; box.step = node.step;
|
|
35
|
+
box.value = numStr(params[node.key]);
|
|
36
|
+
val.append(box);
|
|
37
|
+
if (node.unit) val.append(el("span", "unit", node.unit));
|
|
38
|
+
row.append(val);
|
|
39
|
+
wrap.append(row);
|
|
40
|
+
|
|
41
|
+
let slider = null;
|
|
42
|
+
if (!numeric) {
|
|
43
|
+
slider = document.createElement("input");
|
|
44
|
+
slider.type = "range";
|
|
45
|
+
slider.min = node.min; slider.max = node.max; slider.step = node.step;
|
|
46
|
+
slider.value = params[node.key];
|
|
47
|
+
slider.addEventListener("input", () => {
|
|
48
|
+
params[node.key] = +slider.value;
|
|
49
|
+
box.value = numStr(+slider.value);
|
|
50
|
+
onChange?.();
|
|
51
|
+
});
|
|
52
|
+
wrap.append(slider);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// live preview while typing (unclamped); clamp + reformat on commit
|
|
56
|
+
box.addEventListener("input", () => {
|
|
57
|
+
const v = parseFloat(box.value);
|
|
58
|
+
if (!Number.isFinite(v)) return;
|
|
59
|
+
params[node.key] = v;
|
|
60
|
+
if (slider) slider.value = v;
|
|
61
|
+
onChange?.();
|
|
62
|
+
});
|
|
63
|
+
box.addEventListener("change", () => {
|
|
64
|
+
const v = clampToRange(box.value, node.min, node.max);
|
|
65
|
+
if (v == null) { box.value = numStr(params[node.key]); return; } // revert invalid input
|
|
66
|
+
params[node.key] = v;
|
|
67
|
+
box.value = numStr(v);
|
|
68
|
+
if (slider) slider.value = v;
|
|
69
|
+
onChange?.();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const sync = () => {
|
|
73
|
+
box.value = numStr(params[node.key]);
|
|
74
|
+
if (slider) slider.value = params[node.key];
|
|
75
|
+
};
|
|
76
|
+
return { el: wrap, sync };
|
|
77
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// text / textarea: a live-updating string field. Every edit writes params
|
|
2
|
+
// immediately so the existing rebuild loop previews the new string.
|
|
3
|
+
import { attachInfo } from "../info.js";
|
|
4
|
+
|
|
5
|
+
function el(tag, className, text) {
|
|
6
|
+
const node = document.createElement(tag);
|
|
7
|
+
if (className) node.className = className;
|
|
8
|
+
if (text != null) node.textContent = text;
|
|
9
|
+
return node;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function makeText(node, params, { onChange, info }) {
|
|
13
|
+
const multiline = node.type === "textarea";
|
|
14
|
+
const wrap = el("div", "slider");
|
|
15
|
+
const row = el("div", "row");
|
|
16
|
+
const label = el("label", "", node.label);
|
|
17
|
+
attachInfo(label, node.description, info);
|
|
18
|
+
row.append(label);
|
|
19
|
+
wrap.append(row);
|
|
20
|
+
|
|
21
|
+
const field = document.createElement(multiline ? "textarea" : "input");
|
|
22
|
+
if (!multiline) field.type = "text";
|
|
23
|
+
field.className = "text-input";
|
|
24
|
+
field.value = String(params[node.key] ?? "");
|
|
25
|
+
field.addEventListener("input", () => {
|
|
26
|
+
params[node.key] = field.value;
|
|
27
|
+
onChange?.();
|
|
28
|
+
});
|
|
29
|
+
wrap.append(field);
|
|
30
|
+
|
|
31
|
+
const sync = () => { field.value = String(params[node.key] ?? ""); };
|
|
32
|
+
return { el: wrap, sync };
|
|
33
|
+
}
|
package/types/part.d.ts
CHANGED
|
@@ -57,10 +57,25 @@ export interface PartMeta {
|
|
|
57
57
|
/** Which input a control renders as. Omit for a slider + number box. */
|
|
58
58
|
export type ControlKind = "slider" | "number" | "text" | "textarea";
|
|
59
59
|
|
|
60
|
+
/** Every control type the panel can render. */
|
|
61
|
+
export type ControlType = "slider" | "number" | "text" | "textarea" | "checkbox";
|
|
62
|
+
|
|
63
|
+
/** A declarative visibility condition, evaluated against raw parameters. */
|
|
64
|
+
export type WhenCondition =
|
|
65
|
+
| { allOf: WhenCondition[] }
|
|
66
|
+
| { anyOf: WhenCondition[] }
|
|
67
|
+
| { not: WhenCondition }
|
|
68
|
+
| Record<string, ParamValue | {
|
|
69
|
+
gt?: number; gte?: number; lt?: number; lte?: number;
|
|
70
|
+
ne?: ParamValue; in?: ParamValue[];
|
|
71
|
+
}>;
|
|
72
|
+
|
|
60
73
|
/**
|
|
61
74
|
* One parameter control. The recognised field list mirrors
|
|
62
75
|
* `CONTROL_FIELDS` in src/framework/lint/rules-schema.js — anything else is
|
|
63
76
|
* ignored by the panel and warned about by `partforge lint`.
|
|
77
|
+
*
|
|
78
|
+
* @deprecated Prefer a `controls` array of control nodes. Still fully supported.
|
|
64
79
|
*/
|
|
65
80
|
export interface ControlDef {
|
|
66
81
|
/** Must exist in `defaults`, or the control is silently dead. */
|
|
@@ -82,6 +97,8 @@ export interface ControlDef {
|
|
|
82
97
|
* A feature: a checkbox that sets `key` to `on` (or `0`) and reveals its own
|
|
83
98
|
* controls. `sliders` is REQUIRED — the panel reads `feat.sliders.filter(...)`
|
|
84
99
|
* unguarded. A bare on/off control belongs in `toggles` instead.
|
|
100
|
+
*
|
|
101
|
+
* @deprecated Prefer a `controls` array of control nodes. Still fully supported.
|
|
85
102
|
*/
|
|
86
103
|
export interface FeatureDef {
|
|
87
104
|
key: string;
|
|
@@ -96,6 +113,8 @@ export interface FeatureDef {
|
|
|
96
113
|
/**
|
|
97
114
|
* A standalone on/off checkbox shown below the preset picker, outside the
|
|
98
115
|
* Advanced fold. Checked writes `on` (default `1`); unchecked writes `0`.
|
|
116
|
+
*
|
|
117
|
+
* @deprecated Prefer a `controls` array of control nodes. Still fully supported.
|
|
99
118
|
*/
|
|
100
119
|
export interface ToggleDef {
|
|
101
120
|
key: string;
|