partforge 0.47.0 → 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.
@@ -0,0 +1,37 @@
1
+ // A bare on/off checkbox, writing `on` when ticked and 0 when cleared.
2
+ //
3
+ // `preserveOn` is the one behavioral difference between the two legacy shapes it
4
+ // replaces. A `features` checkbox only wrote `on` when the value wasn't already
5
+ // positive (controls.js:352), so re-ticking a feature restored the magnitude the
6
+ // user had dialled in. A `toggles` checkbox always wrote it (controls.js:286),
7
+ // because its `on` is a flag, not a magnitude.
8
+ import { attachInfo } from "../info.js";
9
+
10
+ function el(tag, className, text) {
11
+ const node = document.createElement(tag);
12
+ if (className) node.className = className;
13
+ if (text != null) node.textContent = text;
14
+ return node;
15
+ }
16
+
17
+ export function makeCheckbox(node, params, { onChange, info }) {
18
+ const row = el("label", "feat");
19
+ const box = document.createElement("input");
20
+ box.type = "checkbox";
21
+ box.checked = params[node.key] > 0;
22
+ const lbl = el("span", "", node.label);
23
+ attachInfo(lbl, node.description, info);
24
+ row.append(box, lbl);
25
+
26
+ box.addEventListener("change", () => {
27
+ if (box.checked) {
28
+ if (!node.preserveOn || !(params[node.key] > 0)) params[node.key] = node.on ?? 1;
29
+ } else {
30
+ params[node.key] = 0;
31
+ }
32
+ onChange?.();
33
+ });
34
+
35
+ const sync = () => { box.checked = params[node.key] > 0; };
36
+ return { el: row, sync };
37
+ }
@@ -0,0 +1,13 @@
1
+ // The DOM half of the widget registry. Its keys must match widget-specs.js
2
+ // exactly — test/framework/panel/registry.test.js proves they do.
3
+ import { makeNumeric } from "./numeric.js";
4
+ import { makeText } from "./text.js";
5
+ import { makeCheckbox } from "./checkbox.js";
6
+
7
+ export const WIDGET_FACTORIES = {
8
+ slider: makeNumeric,
9
+ number: makeNumeric,
10
+ text: makeText,
11
+ textarea: makeText,
12
+ checkbox: makeCheckbox,
13
+ };
@@ -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;