@zodal/dials-ui-vanilla 0.1.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/dist/index.cjs +328 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +72 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +286 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createVanillaSettingsRegistry: () => createVanillaSettingsRegistry,
|
|
24
|
+
el: () => el,
|
|
25
|
+
renderArray: () => renderArray,
|
|
26
|
+
renderField: () => renderField,
|
|
27
|
+
renderNumber: () => renderNumber,
|
|
28
|
+
renderObject: () => renderObject,
|
|
29
|
+
renderRadio: () => renderRadio,
|
|
30
|
+
renderRawJson: () => renderRawJson,
|
|
31
|
+
renderSecret: () => renderSecret,
|
|
32
|
+
renderSelect: () => renderSelect,
|
|
33
|
+
renderSettingsPanel: () => renderSettingsPanel,
|
|
34
|
+
renderSlider: () => renderSlider,
|
|
35
|
+
renderSwitch: () => renderSwitch,
|
|
36
|
+
renderText: () => renderText,
|
|
37
|
+
renderTextarea: () => renderTextarea,
|
|
38
|
+
widgetIs: () => widgetIs
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// src/dom.ts
|
|
43
|
+
var PROP_KEYS = /* @__PURE__ */ new Set(["value", "checked", "disabled", "selected"]);
|
|
44
|
+
function el(tag, attrs, ...children) {
|
|
45
|
+
const element = document.createElement(tag);
|
|
46
|
+
if (attrs) {
|
|
47
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
48
|
+
if (value === void 0 || value === null || value === false) continue;
|
|
49
|
+
if (key.startsWith("on") && typeof value === "function") {
|
|
50
|
+
element.addEventListener(key.slice(2).toLowerCase(), value);
|
|
51
|
+
} else if (key === "class") {
|
|
52
|
+
element.className = String(value);
|
|
53
|
+
} else if (PROP_KEYS.has(key)) {
|
|
54
|
+
element[key] = value;
|
|
55
|
+
} else {
|
|
56
|
+
element.setAttribute(key, String(value));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const child of children) {
|
|
61
|
+
if (child == null) continue;
|
|
62
|
+
element.appendChild(typeof child === "string" ? document.createTextNode(child) : child);
|
|
63
|
+
}
|
|
64
|
+
return element;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/widgets.ts
|
|
68
|
+
var import_dials_core = require("@zodal/dials-core");
|
|
69
|
+
var isDisabled = (field, state) => field.readOnly || state.managed;
|
|
70
|
+
var asText = (v) => v == null ? "" : String(v);
|
|
71
|
+
function decodeNumber(raw) {
|
|
72
|
+
if (raw.trim() === "") return void 0;
|
|
73
|
+
const n = Number(raw);
|
|
74
|
+
return Number.isNaN(n) ? void 0 : n;
|
|
75
|
+
}
|
|
76
|
+
var jsonString = (v) => {
|
|
77
|
+
try {
|
|
78
|
+
return JSON.stringify(v ?? null, null, 2);
|
|
79
|
+
} catch {
|
|
80
|
+
return String(v);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var renderSwitch = (field, state, h) => el("input", {
|
|
84
|
+
type: "checkbox",
|
|
85
|
+
class: "zodal-dials-switch",
|
|
86
|
+
checked: state.value === true,
|
|
87
|
+
disabled: isDisabled(field, state),
|
|
88
|
+
onchange: (e) => h.onChange?.(field.key, e.target.checked)
|
|
89
|
+
});
|
|
90
|
+
var renderText = (field, state, h) => el("input", {
|
|
91
|
+
type: "text",
|
|
92
|
+
class: "zodal-dials-text",
|
|
93
|
+
value: asText(state.value),
|
|
94
|
+
placeholder: asText(field.defaultValue),
|
|
95
|
+
disabled: isDisabled(field, state),
|
|
96
|
+
onchange: (e) => h.onChange?.(field.key, e.target.value)
|
|
97
|
+
});
|
|
98
|
+
var renderTextarea = (field, state, h) => el(
|
|
99
|
+
"textarea",
|
|
100
|
+
{
|
|
101
|
+
class: "zodal-dials-textarea",
|
|
102
|
+
disabled: isDisabled(field, state),
|
|
103
|
+
onchange: (e) => h.onChange?.(field.key, e.target.value)
|
|
104
|
+
},
|
|
105
|
+
asText(state.value)
|
|
106
|
+
);
|
|
107
|
+
var renderNumber = (field, state, h) => el("input", {
|
|
108
|
+
type: "number",
|
|
109
|
+
class: "zodal-dials-number",
|
|
110
|
+
value: asText(state.value),
|
|
111
|
+
min: field.bounds?.min,
|
|
112
|
+
max: field.bounds?.max,
|
|
113
|
+
disabled: isDisabled(field, state),
|
|
114
|
+
onchange: (e) => {
|
|
115
|
+
const v = decodeNumber(e.target.value);
|
|
116
|
+
if (v !== void 0) h.onChange?.(field.key, v);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
var renderSlider = (field, state, h) => {
|
|
120
|
+
const output = el("output", { class: "zodal-dials-slider-value" }, asText(state.value));
|
|
121
|
+
const input = el("input", {
|
|
122
|
+
type: "range",
|
|
123
|
+
class: "zodal-dials-slider",
|
|
124
|
+
value: asText(state.value),
|
|
125
|
+
min: field.bounds?.min,
|
|
126
|
+
max: field.bounds?.max,
|
|
127
|
+
disabled: isDisabled(field, state),
|
|
128
|
+
oninput: (e) => {
|
|
129
|
+
output.textContent = e.target.value;
|
|
130
|
+
},
|
|
131
|
+
onchange: (e) => {
|
|
132
|
+
const v = decodeNumber(e.target.value);
|
|
133
|
+
if (v !== void 0) h.onChange?.(field.key, v);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return el("span", { class: "zodal-dials-slider-wrap" }, input, output);
|
|
137
|
+
};
|
|
138
|
+
var renderSelect = (field, state, h) => {
|
|
139
|
+
const select = el(
|
|
140
|
+
"select",
|
|
141
|
+
{
|
|
142
|
+
class: "zodal-dials-select",
|
|
143
|
+
disabled: isDisabled(field, state),
|
|
144
|
+
onchange: (e) => h.onChange?.(field.key, e.target.value)
|
|
145
|
+
},
|
|
146
|
+
...(field.enumValues ?? []).map((v) => el("option", { value: v }, v))
|
|
147
|
+
);
|
|
148
|
+
select.value = asText(state.value);
|
|
149
|
+
return select;
|
|
150
|
+
};
|
|
151
|
+
var renderRadio = (field, state, h) => el(
|
|
152
|
+
"fieldset",
|
|
153
|
+
{ class: "zodal-dials-radio" },
|
|
154
|
+
...(field.enumValues ?? []).map(
|
|
155
|
+
(v) => el(
|
|
156
|
+
"label",
|
|
157
|
+
null,
|
|
158
|
+
el("input", {
|
|
159
|
+
type: "radio",
|
|
160
|
+
name: field.key,
|
|
161
|
+
value: v,
|
|
162
|
+
checked: state.value === v,
|
|
163
|
+
disabled: isDisabled(field, state),
|
|
164
|
+
onchange: () => h.onChange?.(field.key, v)
|
|
165
|
+
}),
|
|
166
|
+
v
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
);
|
|
170
|
+
var renderSecret = (field, state, h) => {
|
|
171
|
+
const ref = (0, import_dials_core.isSecretRef)(state.value) ? state.value : void 0;
|
|
172
|
+
const status = el("span", { class: "zodal-dials-secret-status" }, ref ? ref.masked : "not set");
|
|
173
|
+
const input = el("input", {
|
|
174
|
+
type: "password",
|
|
175
|
+
class: "zodal-dials-secret",
|
|
176
|
+
placeholder: "Enter new value",
|
|
177
|
+
disabled: isDisabled(field, state),
|
|
178
|
+
onchange: (e) => h.onChange?.(field.key, e.target.value)
|
|
179
|
+
});
|
|
180
|
+
return el("span", { class: "zodal-dials-secret-wrap" }, status, input);
|
|
181
|
+
};
|
|
182
|
+
function renderJsonish(cssClass, note) {
|
|
183
|
+
return (field, state, h) => {
|
|
184
|
+
if (field.sensitivity === "secret" || (0, import_dials_core.isSecretRef)(state.value)) return renderSecret(field, state, h);
|
|
185
|
+
const textarea = el(
|
|
186
|
+
"textarea",
|
|
187
|
+
{
|
|
188
|
+
class: cssClass,
|
|
189
|
+
disabled: isDisabled(field, state),
|
|
190
|
+
onchange: (e) => {
|
|
191
|
+
try {
|
|
192
|
+
h.onChange?.(field.key, JSON.parse(e.target.value));
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
jsonString(state.value)
|
|
198
|
+
);
|
|
199
|
+
return note ? el("span", { class: "zodal-dials-json-wrap" }, el("small", { class: "zodal-dials-json-note" }, note), textarea) : textarea;
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
var renderObject = renderJsonish("zodal-dials-object");
|
|
203
|
+
var renderArray = renderJsonish("zodal-dials-array");
|
|
204
|
+
var renderRawJson = renderJsonish("zodal-dials-rawjson", "rendered as raw JSON (no structured editor for this type)");
|
|
205
|
+
|
|
206
|
+
// src/registry.ts
|
|
207
|
+
var import_dials_ui = require("@zodal/dials-ui");
|
|
208
|
+
function widgetIs(kind) {
|
|
209
|
+
return (_field, ctx) => ctx.widget === kind ? import_dials_ui.PRIORITY.LIBRARY : -1;
|
|
210
|
+
}
|
|
211
|
+
var WIDGET_RENDERERS = [
|
|
212
|
+
["switch", renderSwitch],
|
|
213
|
+
["select", renderSelect],
|
|
214
|
+
["radio", renderRadio],
|
|
215
|
+
["slider", renderSlider],
|
|
216
|
+
["number", renderNumber],
|
|
217
|
+
["text", renderText],
|
|
218
|
+
["textarea", renderTextarea],
|
|
219
|
+
["color", renderText],
|
|
220
|
+
["date", renderText],
|
|
221
|
+
["path", renderText],
|
|
222
|
+
["object", renderObject],
|
|
223
|
+
["array", renderArray],
|
|
224
|
+
// Honor a resolved `secret` widget kind directly, so secret masking does not depend solely on the
|
|
225
|
+
// sensitivity context staying in lockstep (secretRoleIs at OVERRIDE remains the primary signal).
|
|
226
|
+
["secret", renderSecret]
|
|
227
|
+
];
|
|
228
|
+
function createVanillaSettingsRegistry() {
|
|
229
|
+
const registry = (0, import_dials_ui.createSettingsRendererRegistry)();
|
|
230
|
+
registry.register({ tester: (0, import_dials_ui.alwaysMatch)(), renderer: renderRawJson, name: "rawJson" });
|
|
231
|
+
for (const [kind, renderer] of WIDGET_RENDERERS) registry.register({ tester: widgetIs(kind), renderer, name: kind });
|
|
232
|
+
registry.register({ tester: (0, import_dials_ui.secretRoleIs)(), renderer: renderSecret, name: "secret" });
|
|
233
|
+
return registry;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/panel.ts
|
|
237
|
+
function humanize(id) {
|
|
238
|
+
return id.replace(/^[@_]/, "").replace(/[._-]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) || "Other";
|
|
239
|
+
}
|
|
240
|
+
function emptyState() {
|
|
241
|
+
return { value: void 0, managed: false, shadowed: false, dirty: false };
|
|
242
|
+
}
|
|
243
|
+
function renderField(field, state, options = {}) {
|
|
244
|
+
const registry = options.registry ?? createVanillaSettingsRegistry();
|
|
245
|
+
const render = registry.resolve({ zodType: field.zodType }, {
|
|
246
|
+
mode: "form",
|
|
247
|
+
widget: field.widget,
|
|
248
|
+
sensitivity: field.sensitivity
|
|
249
|
+
});
|
|
250
|
+
const control = render ? render(field, state, options) : el("span", null, "(no renderer)");
|
|
251
|
+
const badges = [];
|
|
252
|
+
if (state.source && state.source !== "default") {
|
|
253
|
+
badges.push(el("span", { class: "zodal-dials-badge zodal-dials-source", title: `set by ${state.source}` }, state.source));
|
|
254
|
+
}
|
|
255
|
+
if (state.managed) badges.push(el("span", { class: "zodal-dials-badge zodal-dials-managed", title: "managed by policy" }, "policy"));
|
|
256
|
+
if (state.dirty) badges.push(el("span", { class: "zodal-dials-badge zodal-dials-dirty" }, "modified"));
|
|
257
|
+
const reset = state.source && state.source !== "default" && !state.managed ? el("button", { type: "button", class: "zodal-dials-reset", onclick: () => options.onReset?.(field.key) }, "Reset") : null;
|
|
258
|
+
return el(
|
|
259
|
+
"div",
|
|
260
|
+
{ class: "zodal-dials-field", "data-key": field.key, "data-widget": field.widget },
|
|
261
|
+
el("label", { class: "zodal-dials-label", for: field.key }, field.label, ...badges),
|
|
262
|
+
field.description ? el("p", { class: "zodal-dials-description" }, field.description) : null,
|
|
263
|
+
el("div", { class: "zodal-dials-control" }, control, reset)
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
function filterRows(panel, query) {
|
|
267
|
+
const q = query.trim().toLowerCase();
|
|
268
|
+
for (const row of Array.from(panel.querySelectorAll(".zodal-dials-field"))) {
|
|
269
|
+
const key = (row.getAttribute("data-key") ?? "").toLowerCase();
|
|
270
|
+
const label = (row.querySelector(".zodal-dials-label")?.textContent ?? "").toLowerCase();
|
|
271
|
+
const description = (row.querySelector(".zodal-dials-description")?.textContent ?? "").toLowerCase();
|
|
272
|
+
row.style.display = !q || key.includes(q) || label.includes(q) || description.includes(q) ? "" : "none";
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function renderSettingsPanel(form, states, options = {}) {
|
|
276
|
+
const registry = options.registry ?? createVanillaSettingsRegistry();
|
|
277
|
+
const opts = { ...options, registry };
|
|
278
|
+
const panel = el("div", { class: "zodal-dials-panel" });
|
|
279
|
+
if (options.search !== false) {
|
|
280
|
+
panel.appendChild(
|
|
281
|
+
el("input", {
|
|
282
|
+
type: "search",
|
|
283
|
+
class: "zodal-dials-search",
|
|
284
|
+
placeholder: "Search settings\u2026",
|
|
285
|
+
oninput: (e) => filterRows(panel, e.target.value)
|
|
286
|
+
})
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
const primary = (f) => f.facets?.find((x) => !x.startsWith("@")) ?? "_ungrouped";
|
|
290
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
291
|
+
for (const f of form.fields) {
|
|
292
|
+
const id = primary(f);
|
|
293
|
+
const list = buckets.get(id);
|
|
294
|
+
if (list) list.push(f);
|
|
295
|
+
else buckets.set(id, [f]);
|
|
296
|
+
}
|
|
297
|
+
const groupMeta = new Map(form.groups.map((g) => [g.id, g]));
|
|
298
|
+
const sectionIds = [...buckets.keys()].sort(
|
|
299
|
+
(a, b) => (groupMeta.get(a)?.order ?? 500) - (groupMeta.get(b)?.order ?? 500) || a.localeCompare(b)
|
|
300
|
+
);
|
|
301
|
+
for (const id of sectionIds) {
|
|
302
|
+
const title = groupMeta.get(id)?.title ?? humanize(id);
|
|
303
|
+
const section = el("section", { class: "zodal-dials-group", "data-group": id }, el("h3", { class: "zodal-dials-group-title" }, title));
|
|
304
|
+
for (const f of buckets.get(id) ?? []) section.appendChild(renderField(f, states[f.key] ?? emptyState(), opts));
|
|
305
|
+
panel.appendChild(section);
|
|
306
|
+
}
|
|
307
|
+
return panel;
|
|
308
|
+
}
|
|
309
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
310
|
+
0 && (module.exports = {
|
|
311
|
+
createVanillaSettingsRegistry,
|
|
312
|
+
el,
|
|
313
|
+
renderArray,
|
|
314
|
+
renderField,
|
|
315
|
+
renderNumber,
|
|
316
|
+
renderObject,
|
|
317
|
+
renderRadio,
|
|
318
|
+
renderRawJson,
|
|
319
|
+
renderSecret,
|
|
320
|
+
renderSelect,
|
|
321
|
+
renderSettingsPanel,
|
|
322
|
+
renderSlider,
|
|
323
|
+
renderSwitch,
|
|
324
|
+
renderText,
|
|
325
|
+
renderTextarea,
|
|
326
|
+
widgetIs
|
|
327
|
+
});
|
|
328
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/dom.ts","../src/widgets.ts","../src/registry.ts","../src/panel.ts"],"sourcesContent":["/**\n * @zodal/dials-ui-vanilla — the vanilla HTML/JS reference renderer for @zodal/dials-ui.\n *\n * Turns the headless settings config (`SettingsForm` + field states from `@zodal/dials-ui`) into real\n * DOM, with no framework: `renderSettingsPanel` builds the whole panel (search + grouped fields),\n * `renderField` builds one row, and `createVanillaSettingsRegistry` is the per-widget DOM renderer\n * registry (with a terminal rawJson fallback). It proves the headless contract end-to-end.\n */\n\nexport { el } from './dom.js';\nexport type { Attrs } from './dom.js';\n\nexport type { FieldHandlers, SettingRenderFn } from './types.js';\n\nexport {\n renderSwitch,\n renderSelect,\n renderRadio,\n renderSlider,\n renderNumber,\n renderText,\n renderTextarea,\n renderObject,\n renderArray,\n renderRawJson,\n renderSecret,\n} from './widgets.js';\n\nexport { createVanillaSettingsRegistry, widgetIs } from './registry.js';\n\nexport { renderField, renderSettingsPanel } from './panel.js';\nexport type { RenderOptions } from './panel.js';\n","/**\n * Internal DOM element factory (no framework). Sets value/checked/disabled/selected as PROPERTIES\n * (so form controls reflect state), `on*` keys as event listeners, and everything else as attributes.\n */\n\nexport type Attrs = Record<string, unknown>;\n\nconst PROP_KEYS = new Set(['value', 'checked', 'disabled', 'selected']);\n\nexport function el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n attrs?: Attrs | null,\n ...children: Array<Node | string | null | undefined>\n): HTMLElementTagNameMap[K] {\n const element = document.createElement(tag);\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) {\n if (value === undefined || value === null || value === false) continue;\n if (key.startsWith('on') && typeof value === 'function') {\n element.addEventListener(key.slice(2).toLowerCase(), value as EventListener);\n } else if (key === 'class') {\n element.className = String(value);\n } else if (PROP_KEYS.has(key)) {\n (element as unknown as Record<string, unknown>)[key] = value;\n } else {\n element.setAttribute(key, String(value));\n }\n }\n }\n for (const child of children) {\n if (child == null) continue;\n element.appendChild(typeof child === 'string' ? document.createTextNode(child) : child);\n }\n return element;\n}\n","/**\n * Vanilla widget renderers — each produces the control element for a setting from its config +\n * current state, wiring change events to the handlers. A `secret` value (a masked `SecretRef`) is\n * shown as a status + a write-only field. Structured/unknown values fall back to a raw JSON editor\n * (the rawJson renderer states WHY). No framework; pure DOM.\n */\n\nimport { el } from './dom.js';\nimport { isSecretRef } from '@zodal/dials-core';\nimport type { SettingFieldConfig, SettingFieldState } from '@zodal/dials-ui';\nimport type { SettingRenderFn } from './types.js';\n\nconst isDisabled = (field: SettingFieldConfig, state: SettingFieldState): boolean => field.readOnly || state.managed;\nconst asText = (v: unknown): string => (v == null ? '' : String(v));\n\n/** Decode a numeric input, returning undefined for empty/non-numeric so clearing the box is treated\n * as \"no change\" (use Reset to unset) rather than silently writing 0 / NaN. */\nfunction decodeNumber(raw: string): number | undefined {\n if (raw.trim() === '') return undefined;\n const n = Number(raw);\n return Number.isNaN(n) ? undefined : n;\n}\nconst jsonString = (v: unknown): string => {\n try {\n return JSON.stringify(v ?? null, null, 2);\n } catch {\n return String(v);\n }\n};\n\nexport const renderSwitch: SettingRenderFn = (field, state, h) =>\n el('input', {\n type: 'checkbox',\n class: 'zodal-dials-switch',\n checked: state.value === true,\n disabled: isDisabled(field, state),\n onchange: (e: Event) => h.onChange?.(field.key, (e.target as HTMLInputElement).checked),\n });\n\nexport const renderText: SettingRenderFn = (field, state, h) =>\n el('input', {\n type: 'text',\n class: 'zodal-dials-text',\n value: asText(state.value),\n placeholder: asText(field.defaultValue),\n disabled: isDisabled(field, state),\n onchange: (e: Event) => h.onChange?.(field.key, (e.target as HTMLInputElement).value),\n });\n\nexport const renderTextarea: SettingRenderFn = (field, state, h) =>\n el(\n 'textarea',\n {\n class: 'zodal-dials-textarea',\n disabled: isDisabled(field, state),\n onchange: (e: Event) => h.onChange?.(field.key, (e.target as HTMLTextAreaElement).value),\n },\n asText(state.value),\n );\n\nexport const renderNumber: SettingRenderFn = (field, state, h) =>\n el('input', {\n type: 'number',\n class: 'zodal-dials-number',\n value: asText(state.value),\n min: field.bounds?.min,\n max: field.bounds?.max,\n disabled: isDisabled(field, state),\n onchange: (e: Event) => {\n const v = decodeNumber((e.target as HTMLInputElement).value);\n if (v !== undefined) h.onChange?.(field.key, v);\n },\n });\n\nexport const renderSlider: SettingRenderFn = (field, state, h) => {\n const output = el('output', { class: 'zodal-dials-slider-value' }, asText(state.value));\n const input = el('input', {\n type: 'range',\n class: 'zodal-dials-slider',\n value: asText(state.value),\n min: field.bounds?.min,\n max: field.bounds?.max,\n disabled: isDisabled(field, state),\n oninput: (e: Event) => {\n output.textContent = (e.target as HTMLInputElement).value;\n },\n onchange: (e: Event) => {\n const v = decodeNumber((e.target as HTMLInputElement).value);\n if (v !== undefined) h.onChange?.(field.key, v);\n },\n });\n return el('span', { class: 'zodal-dials-slider-wrap' }, input, output);\n};\n\nexport const renderSelect: SettingRenderFn = (field, state, h) => {\n const select = el(\n 'select',\n {\n class: 'zodal-dials-select',\n disabled: isDisabled(field, state),\n onchange: (e: Event) => h.onChange?.(field.key, (e.target as HTMLSelectElement).value),\n },\n ...(field.enumValues ?? []).map((v) => el('option', { value: v }, v)),\n );\n select.value = asText(state.value);\n return select;\n};\n\nexport const renderRadio: SettingRenderFn = (field, state, h) =>\n el(\n 'fieldset',\n { class: 'zodal-dials-radio' },\n ...(field.enumValues ?? []).map((v) =>\n el(\n 'label',\n null,\n el('input', {\n type: 'radio',\n name: field.key,\n value: v,\n checked: state.value === v,\n disabled: isDisabled(field, state),\n onchange: () => h.onChange?.(field.key, v),\n }),\n v,\n ),\n ),\n );\n\nexport const renderSecret: SettingRenderFn = (field, state, h) => {\n const ref = isSecretRef(state.value) ? state.value : undefined;\n const status = el('span', { class: 'zodal-dials-secret-status' }, ref ? ref.masked : 'not set');\n const input = el('input', {\n type: 'password',\n class: 'zodal-dials-secret',\n placeholder: 'Enter new value',\n disabled: isDisabled(field, state),\n onchange: (e: Event) => h.onChange?.(field.key, (e.target as HTMLInputElement).value),\n });\n return el('span', { class: 'zodal-dials-secret-wrap' }, status, input);\n};\n\nfunction renderJsonish(cssClass: string, note?: string): SettingRenderFn {\n return (field, state, h) => {\n // Defense in depth: a JSON/raw editor must never serialize a secret, even if reached by a\n // widget↔sensitivity mismatch — always fall back to the masked secret widget.\n if (field.sensitivity === 'secret' || isSecretRef(state.value)) return renderSecret(field, state, h);\n const textarea = el(\n 'textarea',\n {\n class: cssClass,\n disabled: isDisabled(field, state),\n onchange: (e: Event) => {\n try {\n h.onChange?.(field.key, JSON.parse((e.target as HTMLTextAreaElement).value));\n } catch {\n // invalid JSON: a real renderer would surface an error; the reference renderer ignores it.\n }\n },\n },\n jsonString(state.value),\n );\n return note\n ? el('span', { class: 'zodal-dials-json-wrap' }, el('small', { class: 'zodal-dials-json-note' }, note), textarea)\n : textarea;\n };\n}\n\nexport const renderObject = renderJsonish('zodal-dials-object');\nexport const renderArray = renderJsonish('zodal-dials-array');\nexport const renderRawJson = renderJsonish('zodal-dials-rawjson', 'rendered as raw JSON (no structured editor for this type)');\n","/**\n * The vanilla settings renderer registry — populates a `@zodal/dials-ui` `RendererRegistry` with one\n * DOM renderer per widget kind, the masked secret widget at the OVERRIDE band, and a terminal rawJson\n * renderer (via `alwaysMatch`) so every setting renders to something. The widget kind is supplied via\n * the render context, so selection is open-closed (a consumer can register a higher-priority override).\n */\n\nimport { createSettingsRendererRegistry, secretRoleIs, alwaysMatch, PRIORITY } from '@zodal/dials-ui';\nimport type { RendererRegistry, RendererTester, WidgetKind } from '@zodal/dials-ui';\nimport type { SettingRenderFn } from './types.js';\nimport {\n renderSwitch,\n renderSelect,\n renderRadio,\n renderSlider,\n renderNumber,\n renderText,\n renderTextarea,\n renderObject,\n renderArray,\n renderRawJson,\n renderSecret,\n} from './widgets.js';\n\n/** Tester matching a field's resolved widget kind (supplied via the render context as `widget`). */\nexport function widgetIs(kind: WidgetKind): RendererTester {\n return (_field, ctx) => (ctx.widget === kind ? PRIORITY.LIBRARY : -1);\n}\n\nconst WIDGET_RENDERERS: Array<[WidgetKind, SettingRenderFn]> = [\n ['switch', renderSwitch],\n ['select', renderSelect],\n ['radio', renderRadio],\n ['slider', renderSlider],\n ['number', renderNumber],\n ['text', renderText],\n ['textarea', renderTextarea],\n ['color', renderText],\n ['date', renderText],\n ['path', renderText],\n ['object', renderObject],\n ['array', renderArray],\n // Honor a resolved `secret` widget kind directly, so secret masking does not depend solely on the\n // sensitivity context staying in lockstep (secretRoleIs at OVERRIDE remains the primary signal).\n ['secret', renderSecret],\n];\n\n/** Create a vanilla settings renderer registry (a `@zodal/dials-ui` registry of DOM renderers). */\nexport function createVanillaSettingsRegistry(): RendererRegistry<SettingRenderFn> {\n const registry = createSettingsRendererRegistry<SettingRenderFn>();\n registry.register({ tester: alwaysMatch(), renderer: renderRawJson, name: 'rawJson' });\n for (const [kind, renderer] of WIDGET_RENDERERS) registry.register({ tester: widgetIs(kind), renderer, name: kind });\n registry.register({ tester: secretRoleIs(), renderer: renderSecret, name: 'secret' });\n return registry;\n}\n","/**\n * Field and panel assembly. `renderField` builds one labeled row (label + provenance badges +\n * description + control via the registry + a reset button when overridden). `renderSettingsPanel`\n * builds the whole panel: a client-side search/filter box + a section per primary facet. Each setting\n * renders ONCE (under its primary facet); multi-membership facets and computed groups drive filtering,\n * not duplicate controls.\n */\n\nimport { el } from './dom.js';\nimport type { RendererRegistry, SettingFieldConfig, SettingFieldState, SettingsForm } from '@zodal/dials-ui';\nimport type { ResolvedFieldAffordance } from '@zodal/core';\nimport type { SettingKey } from '@zodal/dials-core';\nimport type { FieldHandlers, SettingRenderFn } from './types.js';\nimport { createVanillaSettingsRegistry } from './registry.js';\n\nexport interface RenderOptions extends FieldHandlers {\n /** Override the renderer registry (e.g. to register custom widgets). */\n registry?: RendererRegistry<SettingRenderFn>;\n /** Include the client-side search/filter box. Default: true. */\n search?: boolean;\n}\n\nfunction humanize(id: string): string {\n return id.replace(/^[@_]/, '').replace(/[._-]+/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase()) || 'Other';\n}\n\nfunction emptyState(): SettingFieldState {\n return { value: undefined, managed: false, shadowed: false, dirty: false };\n}\n\n/** Render one setting as a labeled field row. */\nexport function renderField(\n field: SettingFieldConfig,\n state: SettingFieldState,\n options: RenderOptions = {},\n): HTMLElement {\n const registry = options.registry ?? createVanillaSettingsRegistry();\n const render = registry.resolve({ zodType: field.zodType } as unknown as ResolvedFieldAffordance, {\n mode: 'form',\n widget: field.widget,\n sensitivity: field.sensitivity,\n });\n const control = render ? render(field, state, options) : el('span', null, '(no renderer)');\n\n const badges: HTMLElement[] = [];\n if (state.source && state.source !== 'default') {\n badges.push(el('span', { class: 'zodal-dials-badge zodal-dials-source', title: `set by ${state.source}` }, state.source));\n }\n if (state.managed) badges.push(el('span', { class: 'zodal-dials-badge zodal-dials-managed', title: 'managed by policy' }, 'policy'));\n if (state.dirty) badges.push(el('span', { class: 'zodal-dials-badge zodal-dials-dirty' }, 'modified'));\n\n const reset =\n state.source && state.source !== 'default' && !state.managed\n ? el('button', { type: 'button', class: 'zodal-dials-reset', onclick: () => options.onReset?.(field.key) }, 'Reset')\n : null;\n\n return el(\n 'div',\n { class: 'zodal-dials-field', 'data-key': field.key, 'data-widget': field.widget },\n el('label', { class: 'zodal-dials-label', for: field.key }, field.label, ...badges),\n field.description ? el('p', { class: 'zodal-dials-description' }, field.description) : null,\n el('div', { class: 'zodal-dials-control' }, control, reset),\n );\n}\n\nfunction filterRows(panel: HTMLElement, query: string): void {\n const q = query.trim().toLowerCase();\n for (const row of Array.from(panel.querySelectorAll<HTMLElement>('.zodal-dials-field'))) {\n const key = (row.getAttribute('data-key') ?? '').toLowerCase();\n const label = (row.querySelector('.zodal-dials-label')?.textContent ?? '').toLowerCase();\n const description = (row.querySelector('.zodal-dials-description')?.textContent ?? '').toLowerCase();\n row.style.display = !q || key.includes(q) || label.includes(q) || description.includes(q) ? '' : 'none';\n }\n}\n\n/** Render the full settings panel (search box + a section per primary facet). */\nexport function renderSettingsPanel(\n form: SettingsForm,\n states: Record<SettingKey, SettingFieldState>,\n options: RenderOptions = {},\n): HTMLElement {\n const registry = options.registry ?? createVanillaSettingsRegistry();\n const opts: RenderOptions = { ...options, registry };\n const panel = el('div', { class: 'zodal-dials-panel' });\n\n if (options.search !== false) {\n panel.appendChild(\n el('input', {\n type: 'search',\n class: 'zodal-dials-search',\n placeholder: 'Search settings…',\n oninput: (e: Event) => filterRows(panel, (e.target as HTMLInputElement).value),\n }),\n );\n }\n\n // Bucket each field under its primary (first non-computed) facet, so every field renders once.\n const primary = (f: SettingFieldConfig): string => f.facets?.find((x) => !x.startsWith('@')) ?? '_ungrouped';\n const buckets = new Map<string, SettingFieldConfig[]>();\n for (const f of form.fields) {\n const id = primary(f);\n const list = buckets.get(id);\n if (list) list.push(f);\n else buckets.set(id, [f]);\n }\n\n const groupMeta = new Map(form.groups.map((g) => [g.id, g]));\n const sectionIds = [...buckets.keys()].sort(\n (a, b) => (groupMeta.get(a)?.order ?? 500) - (groupMeta.get(b)?.order ?? 500) || a.localeCompare(b),\n );\n\n for (const id of sectionIds) {\n const title = groupMeta.get(id)?.title ?? humanize(id);\n const section = el('section', { class: 'zodal-dials-group', 'data-group': id }, el('h3', { class: 'zodal-dials-group-title' }, title));\n for (const f of buckets.get(id) ?? []) section.appendChild(renderField(f, states[f.key] ?? emptyState(), opts));\n panel.appendChild(section);\n }\n\n return panel;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { SettingFieldConfig, SettingFieldState, RendererRegistry, WidgetKind, RendererTester, SettingsForm } from '@zodal/dials-ui';
|
|
2
|
+
import { SettingKey } from '@zodal/dials-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Internal DOM element factory (no framework). Sets value/checked/disabled/selected as PROPERTIES
|
|
6
|
+
* (so form controls reflect state), `on*` keys as event listeners, and everything else as attributes.
|
|
7
|
+
*/
|
|
8
|
+
type Attrs = Record<string, unknown>;
|
|
9
|
+
declare function el<K extends keyof HTMLElementTagNameMap>(tag: K, attrs?: Attrs | null, ...children: Array<Node | string | null | undefined>): HTMLElementTagNameMap[K];
|
|
10
|
+
|
|
11
|
+
/** Render contract for the vanilla settings renderer. */
|
|
12
|
+
|
|
13
|
+
interface FieldHandlers {
|
|
14
|
+
/** Called when the user changes a setting's value (the decoded value, not the raw event). */
|
|
15
|
+
onChange?: (key: SettingKey, value: unknown) => void;
|
|
16
|
+
/** Called when the user clicks reset on a setting. */
|
|
17
|
+
onReset?: (key: SettingKey) => void;
|
|
18
|
+
}
|
|
19
|
+
/** A widget renderer: produce the control element for a setting from its config + current state. */
|
|
20
|
+
type SettingRenderFn = (field: SettingFieldConfig, state: SettingFieldState, handlers: FieldHandlers) => HTMLElement;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Vanilla widget renderers — each produces the control element for a setting from its config +
|
|
24
|
+
* current state, wiring change events to the handlers. A `secret` value (a masked `SecretRef`) is
|
|
25
|
+
* shown as a status + a write-only field. Structured/unknown values fall back to a raw JSON editor
|
|
26
|
+
* (the rawJson renderer states WHY). No framework; pure DOM.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
declare const renderSwitch: SettingRenderFn;
|
|
30
|
+
declare const renderText: SettingRenderFn;
|
|
31
|
+
declare const renderTextarea: SettingRenderFn;
|
|
32
|
+
declare const renderNumber: SettingRenderFn;
|
|
33
|
+
declare const renderSlider: SettingRenderFn;
|
|
34
|
+
declare const renderSelect: SettingRenderFn;
|
|
35
|
+
declare const renderRadio: SettingRenderFn;
|
|
36
|
+
declare const renderSecret: SettingRenderFn;
|
|
37
|
+
declare const renderObject: SettingRenderFn;
|
|
38
|
+
declare const renderArray: SettingRenderFn;
|
|
39
|
+
declare const renderRawJson: SettingRenderFn;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The vanilla settings renderer registry — populates a `@zodal/dials-ui` `RendererRegistry` with one
|
|
43
|
+
* DOM renderer per widget kind, the masked secret widget at the OVERRIDE band, and a terminal rawJson
|
|
44
|
+
* renderer (via `alwaysMatch`) so every setting renders to something. The widget kind is supplied via
|
|
45
|
+
* the render context, so selection is open-closed (a consumer can register a higher-priority override).
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/** Tester matching a field's resolved widget kind (supplied via the render context as `widget`). */
|
|
49
|
+
declare function widgetIs(kind: WidgetKind): RendererTester;
|
|
50
|
+
/** Create a vanilla settings renderer registry (a `@zodal/dials-ui` registry of DOM renderers). */
|
|
51
|
+
declare function createVanillaSettingsRegistry(): RendererRegistry<SettingRenderFn>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Field and panel assembly. `renderField` builds one labeled row (label + provenance badges +
|
|
55
|
+
* description + control via the registry + a reset button when overridden). `renderSettingsPanel`
|
|
56
|
+
* builds the whole panel: a client-side search/filter box + a section per primary facet. Each setting
|
|
57
|
+
* renders ONCE (under its primary facet); multi-membership facets and computed groups drive filtering,
|
|
58
|
+
* not duplicate controls.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
interface RenderOptions extends FieldHandlers {
|
|
62
|
+
/** Override the renderer registry (e.g. to register custom widgets). */
|
|
63
|
+
registry?: RendererRegistry<SettingRenderFn>;
|
|
64
|
+
/** Include the client-side search/filter box. Default: true. */
|
|
65
|
+
search?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** Render one setting as a labeled field row. */
|
|
68
|
+
declare function renderField(field: SettingFieldConfig, state: SettingFieldState, options?: RenderOptions): HTMLElement;
|
|
69
|
+
/** Render the full settings panel (search box + a section per primary facet). */
|
|
70
|
+
declare function renderSettingsPanel(form: SettingsForm, states: Record<SettingKey, SettingFieldState>, options?: RenderOptions): HTMLElement;
|
|
71
|
+
|
|
72
|
+
export { type Attrs, type FieldHandlers, type RenderOptions, type SettingRenderFn, createVanillaSettingsRegistry, el, renderArray, renderField, renderNumber, renderObject, renderRadio, renderRawJson, renderSecret, renderSelect, renderSettingsPanel, renderSlider, renderSwitch, renderText, renderTextarea, widgetIs };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { SettingFieldConfig, SettingFieldState, RendererRegistry, WidgetKind, RendererTester, SettingsForm } from '@zodal/dials-ui';
|
|
2
|
+
import { SettingKey } from '@zodal/dials-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Internal DOM element factory (no framework). Sets value/checked/disabled/selected as PROPERTIES
|
|
6
|
+
* (so form controls reflect state), `on*` keys as event listeners, and everything else as attributes.
|
|
7
|
+
*/
|
|
8
|
+
type Attrs = Record<string, unknown>;
|
|
9
|
+
declare function el<K extends keyof HTMLElementTagNameMap>(tag: K, attrs?: Attrs | null, ...children: Array<Node | string | null | undefined>): HTMLElementTagNameMap[K];
|
|
10
|
+
|
|
11
|
+
/** Render contract for the vanilla settings renderer. */
|
|
12
|
+
|
|
13
|
+
interface FieldHandlers {
|
|
14
|
+
/** Called when the user changes a setting's value (the decoded value, not the raw event). */
|
|
15
|
+
onChange?: (key: SettingKey, value: unknown) => void;
|
|
16
|
+
/** Called when the user clicks reset on a setting. */
|
|
17
|
+
onReset?: (key: SettingKey) => void;
|
|
18
|
+
}
|
|
19
|
+
/** A widget renderer: produce the control element for a setting from its config + current state. */
|
|
20
|
+
type SettingRenderFn = (field: SettingFieldConfig, state: SettingFieldState, handlers: FieldHandlers) => HTMLElement;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Vanilla widget renderers — each produces the control element for a setting from its config +
|
|
24
|
+
* current state, wiring change events to the handlers. A `secret` value (a masked `SecretRef`) is
|
|
25
|
+
* shown as a status + a write-only field. Structured/unknown values fall back to a raw JSON editor
|
|
26
|
+
* (the rawJson renderer states WHY). No framework; pure DOM.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
declare const renderSwitch: SettingRenderFn;
|
|
30
|
+
declare const renderText: SettingRenderFn;
|
|
31
|
+
declare const renderTextarea: SettingRenderFn;
|
|
32
|
+
declare const renderNumber: SettingRenderFn;
|
|
33
|
+
declare const renderSlider: SettingRenderFn;
|
|
34
|
+
declare const renderSelect: SettingRenderFn;
|
|
35
|
+
declare const renderRadio: SettingRenderFn;
|
|
36
|
+
declare const renderSecret: SettingRenderFn;
|
|
37
|
+
declare const renderObject: SettingRenderFn;
|
|
38
|
+
declare const renderArray: SettingRenderFn;
|
|
39
|
+
declare const renderRawJson: SettingRenderFn;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The vanilla settings renderer registry — populates a `@zodal/dials-ui` `RendererRegistry` with one
|
|
43
|
+
* DOM renderer per widget kind, the masked secret widget at the OVERRIDE band, and a terminal rawJson
|
|
44
|
+
* renderer (via `alwaysMatch`) so every setting renders to something. The widget kind is supplied via
|
|
45
|
+
* the render context, so selection is open-closed (a consumer can register a higher-priority override).
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/** Tester matching a field's resolved widget kind (supplied via the render context as `widget`). */
|
|
49
|
+
declare function widgetIs(kind: WidgetKind): RendererTester;
|
|
50
|
+
/** Create a vanilla settings renderer registry (a `@zodal/dials-ui` registry of DOM renderers). */
|
|
51
|
+
declare function createVanillaSettingsRegistry(): RendererRegistry<SettingRenderFn>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Field and panel assembly. `renderField` builds one labeled row (label + provenance badges +
|
|
55
|
+
* description + control via the registry + a reset button when overridden). `renderSettingsPanel`
|
|
56
|
+
* builds the whole panel: a client-side search/filter box + a section per primary facet. Each setting
|
|
57
|
+
* renders ONCE (under its primary facet); multi-membership facets and computed groups drive filtering,
|
|
58
|
+
* not duplicate controls.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
interface RenderOptions extends FieldHandlers {
|
|
62
|
+
/** Override the renderer registry (e.g. to register custom widgets). */
|
|
63
|
+
registry?: RendererRegistry<SettingRenderFn>;
|
|
64
|
+
/** Include the client-side search/filter box. Default: true. */
|
|
65
|
+
search?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** Render one setting as a labeled field row. */
|
|
68
|
+
declare function renderField(field: SettingFieldConfig, state: SettingFieldState, options?: RenderOptions): HTMLElement;
|
|
69
|
+
/** Render the full settings panel (search box + a section per primary facet). */
|
|
70
|
+
declare function renderSettingsPanel(form: SettingsForm, states: Record<SettingKey, SettingFieldState>, options?: RenderOptions): HTMLElement;
|
|
71
|
+
|
|
72
|
+
export { type Attrs, type FieldHandlers, type RenderOptions, type SettingRenderFn, createVanillaSettingsRegistry, el, renderArray, renderField, renderNumber, renderObject, renderRadio, renderRawJson, renderSecret, renderSelect, renderSettingsPanel, renderSlider, renderSwitch, renderText, renderTextarea, widgetIs };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// src/dom.ts
|
|
2
|
+
var PROP_KEYS = /* @__PURE__ */ new Set(["value", "checked", "disabled", "selected"]);
|
|
3
|
+
function el(tag, attrs, ...children) {
|
|
4
|
+
const element = document.createElement(tag);
|
|
5
|
+
if (attrs) {
|
|
6
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
7
|
+
if (value === void 0 || value === null || value === false) continue;
|
|
8
|
+
if (key.startsWith("on") && typeof value === "function") {
|
|
9
|
+
element.addEventListener(key.slice(2).toLowerCase(), value);
|
|
10
|
+
} else if (key === "class") {
|
|
11
|
+
element.className = String(value);
|
|
12
|
+
} else if (PROP_KEYS.has(key)) {
|
|
13
|
+
element[key] = value;
|
|
14
|
+
} else {
|
|
15
|
+
element.setAttribute(key, String(value));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
for (const child of children) {
|
|
20
|
+
if (child == null) continue;
|
|
21
|
+
element.appendChild(typeof child === "string" ? document.createTextNode(child) : child);
|
|
22
|
+
}
|
|
23
|
+
return element;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/widgets.ts
|
|
27
|
+
import { isSecretRef } from "@zodal/dials-core";
|
|
28
|
+
var isDisabled = (field, state) => field.readOnly || state.managed;
|
|
29
|
+
var asText = (v) => v == null ? "" : String(v);
|
|
30
|
+
function decodeNumber(raw) {
|
|
31
|
+
if (raw.trim() === "") return void 0;
|
|
32
|
+
const n = Number(raw);
|
|
33
|
+
return Number.isNaN(n) ? void 0 : n;
|
|
34
|
+
}
|
|
35
|
+
var jsonString = (v) => {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.stringify(v ?? null, null, 2);
|
|
38
|
+
} catch {
|
|
39
|
+
return String(v);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var renderSwitch = (field, state, h) => el("input", {
|
|
43
|
+
type: "checkbox",
|
|
44
|
+
class: "zodal-dials-switch",
|
|
45
|
+
checked: state.value === true,
|
|
46
|
+
disabled: isDisabled(field, state),
|
|
47
|
+
onchange: (e) => h.onChange?.(field.key, e.target.checked)
|
|
48
|
+
});
|
|
49
|
+
var renderText = (field, state, h) => el("input", {
|
|
50
|
+
type: "text",
|
|
51
|
+
class: "zodal-dials-text",
|
|
52
|
+
value: asText(state.value),
|
|
53
|
+
placeholder: asText(field.defaultValue),
|
|
54
|
+
disabled: isDisabled(field, state),
|
|
55
|
+
onchange: (e) => h.onChange?.(field.key, e.target.value)
|
|
56
|
+
});
|
|
57
|
+
var renderTextarea = (field, state, h) => el(
|
|
58
|
+
"textarea",
|
|
59
|
+
{
|
|
60
|
+
class: "zodal-dials-textarea",
|
|
61
|
+
disabled: isDisabled(field, state),
|
|
62
|
+
onchange: (e) => h.onChange?.(field.key, e.target.value)
|
|
63
|
+
},
|
|
64
|
+
asText(state.value)
|
|
65
|
+
);
|
|
66
|
+
var renderNumber = (field, state, h) => el("input", {
|
|
67
|
+
type: "number",
|
|
68
|
+
class: "zodal-dials-number",
|
|
69
|
+
value: asText(state.value),
|
|
70
|
+
min: field.bounds?.min,
|
|
71
|
+
max: field.bounds?.max,
|
|
72
|
+
disabled: isDisabled(field, state),
|
|
73
|
+
onchange: (e) => {
|
|
74
|
+
const v = decodeNumber(e.target.value);
|
|
75
|
+
if (v !== void 0) h.onChange?.(field.key, v);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
var renderSlider = (field, state, h) => {
|
|
79
|
+
const output = el("output", { class: "zodal-dials-slider-value" }, asText(state.value));
|
|
80
|
+
const input = el("input", {
|
|
81
|
+
type: "range",
|
|
82
|
+
class: "zodal-dials-slider",
|
|
83
|
+
value: asText(state.value),
|
|
84
|
+
min: field.bounds?.min,
|
|
85
|
+
max: field.bounds?.max,
|
|
86
|
+
disabled: isDisabled(field, state),
|
|
87
|
+
oninput: (e) => {
|
|
88
|
+
output.textContent = e.target.value;
|
|
89
|
+
},
|
|
90
|
+
onchange: (e) => {
|
|
91
|
+
const v = decodeNumber(e.target.value);
|
|
92
|
+
if (v !== void 0) h.onChange?.(field.key, v);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return el("span", { class: "zodal-dials-slider-wrap" }, input, output);
|
|
96
|
+
};
|
|
97
|
+
var renderSelect = (field, state, h) => {
|
|
98
|
+
const select = el(
|
|
99
|
+
"select",
|
|
100
|
+
{
|
|
101
|
+
class: "zodal-dials-select",
|
|
102
|
+
disabled: isDisabled(field, state),
|
|
103
|
+
onchange: (e) => h.onChange?.(field.key, e.target.value)
|
|
104
|
+
},
|
|
105
|
+
...(field.enumValues ?? []).map((v) => el("option", { value: v }, v))
|
|
106
|
+
);
|
|
107
|
+
select.value = asText(state.value);
|
|
108
|
+
return select;
|
|
109
|
+
};
|
|
110
|
+
var renderRadio = (field, state, h) => el(
|
|
111
|
+
"fieldset",
|
|
112
|
+
{ class: "zodal-dials-radio" },
|
|
113
|
+
...(field.enumValues ?? []).map(
|
|
114
|
+
(v) => el(
|
|
115
|
+
"label",
|
|
116
|
+
null,
|
|
117
|
+
el("input", {
|
|
118
|
+
type: "radio",
|
|
119
|
+
name: field.key,
|
|
120
|
+
value: v,
|
|
121
|
+
checked: state.value === v,
|
|
122
|
+
disabled: isDisabled(field, state),
|
|
123
|
+
onchange: () => h.onChange?.(field.key, v)
|
|
124
|
+
}),
|
|
125
|
+
v
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
var renderSecret = (field, state, h) => {
|
|
130
|
+
const ref = isSecretRef(state.value) ? state.value : void 0;
|
|
131
|
+
const status = el("span", { class: "zodal-dials-secret-status" }, ref ? ref.masked : "not set");
|
|
132
|
+
const input = el("input", {
|
|
133
|
+
type: "password",
|
|
134
|
+
class: "zodal-dials-secret",
|
|
135
|
+
placeholder: "Enter new value",
|
|
136
|
+
disabled: isDisabled(field, state),
|
|
137
|
+
onchange: (e) => h.onChange?.(field.key, e.target.value)
|
|
138
|
+
});
|
|
139
|
+
return el("span", { class: "zodal-dials-secret-wrap" }, status, input);
|
|
140
|
+
};
|
|
141
|
+
function renderJsonish(cssClass, note) {
|
|
142
|
+
return (field, state, h) => {
|
|
143
|
+
if (field.sensitivity === "secret" || isSecretRef(state.value)) return renderSecret(field, state, h);
|
|
144
|
+
const textarea = el(
|
|
145
|
+
"textarea",
|
|
146
|
+
{
|
|
147
|
+
class: cssClass,
|
|
148
|
+
disabled: isDisabled(field, state),
|
|
149
|
+
onchange: (e) => {
|
|
150
|
+
try {
|
|
151
|
+
h.onChange?.(field.key, JSON.parse(e.target.value));
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
jsonString(state.value)
|
|
157
|
+
);
|
|
158
|
+
return note ? el("span", { class: "zodal-dials-json-wrap" }, el("small", { class: "zodal-dials-json-note" }, note), textarea) : textarea;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
var renderObject = renderJsonish("zodal-dials-object");
|
|
162
|
+
var renderArray = renderJsonish("zodal-dials-array");
|
|
163
|
+
var renderRawJson = renderJsonish("zodal-dials-rawjson", "rendered as raw JSON (no structured editor for this type)");
|
|
164
|
+
|
|
165
|
+
// src/registry.ts
|
|
166
|
+
import { createSettingsRendererRegistry, secretRoleIs, alwaysMatch, PRIORITY } from "@zodal/dials-ui";
|
|
167
|
+
function widgetIs(kind) {
|
|
168
|
+
return (_field, ctx) => ctx.widget === kind ? PRIORITY.LIBRARY : -1;
|
|
169
|
+
}
|
|
170
|
+
var WIDGET_RENDERERS = [
|
|
171
|
+
["switch", renderSwitch],
|
|
172
|
+
["select", renderSelect],
|
|
173
|
+
["radio", renderRadio],
|
|
174
|
+
["slider", renderSlider],
|
|
175
|
+
["number", renderNumber],
|
|
176
|
+
["text", renderText],
|
|
177
|
+
["textarea", renderTextarea],
|
|
178
|
+
["color", renderText],
|
|
179
|
+
["date", renderText],
|
|
180
|
+
["path", renderText],
|
|
181
|
+
["object", renderObject],
|
|
182
|
+
["array", renderArray],
|
|
183
|
+
// Honor a resolved `secret` widget kind directly, so secret masking does not depend solely on the
|
|
184
|
+
// sensitivity context staying in lockstep (secretRoleIs at OVERRIDE remains the primary signal).
|
|
185
|
+
["secret", renderSecret]
|
|
186
|
+
];
|
|
187
|
+
function createVanillaSettingsRegistry() {
|
|
188
|
+
const registry = createSettingsRendererRegistry();
|
|
189
|
+
registry.register({ tester: alwaysMatch(), renderer: renderRawJson, name: "rawJson" });
|
|
190
|
+
for (const [kind, renderer] of WIDGET_RENDERERS) registry.register({ tester: widgetIs(kind), renderer, name: kind });
|
|
191
|
+
registry.register({ tester: secretRoleIs(), renderer: renderSecret, name: "secret" });
|
|
192
|
+
return registry;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/panel.ts
|
|
196
|
+
function humanize(id) {
|
|
197
|
+
return id.replace(/^[@_]/, "").replace(/[._-]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) || "Other";
|
|
198
|
+
}
|
|
199
|
+
function emptyState() {
|
|
200
|
+
return { value: void 0, managed: false, shadowed: false, dirty: false };
|
|
201
|
+
}
|
|
202
|
+
function renderField(field, state, options = {}) {
|
|
203
|
+
const registry = options.registry ?? createVanillaSettingsRegistry();
|
|
204
|
+
const render = registry.resolve({ zodType: field.zodType }, {
|
|
205
|
+
mode: "form",
|
|
206
|
+
widget: field.widget,
|
|
207
|
+
sensitivity: field.sensitivity
|
|
208
|
+
});
|
|
209
|
+
const control = render ? render(field, state, options) : el("span", null, "(no renderer)");
|
|
210
|
+
const badges = [];
|
|
211
|
+
if (state.source && state.source !== "default") {
|
|
212
|
+
badges.push(el("span", { class: "zodal-dials-badge zodal-dials-source", title: `set by ${state.source}` }, state.source));
|
|
213
|
+
}
|
|
214
|
+
if (state.managed) badges.push(el("span", { class: "zodal-dials-badge zodal-dials-managed", title: "managed by policy" }, "policy"));
|
|
215
|
+
if (state.dirty) badges.push(el("span", { class: "zodal-dials-badge zodal-dials-dirty" }, "modified"));
|
|
216
|
+
const reset = state.source && state.source !== "default" && !state.managed ? el("button", { type: "button", class: "zodal-dials-reset", onclick: () => options.onReset?.(field.key) }, "Reset") : null;
|
|
217
|
+
return el(
|
|
218
|
+
"div",
|
|
219
|
+
{ class: "zodal-dials-field", "data-key": field.key, "data-widget": field.widget },
|
|
220
|
+
el("label", { class: "zodal-dials-label", for: field.key }, field.label, ...badges),
|
|
221
|
+
field.description ? el("p", { class: "zodal-dials-description" }, field.description) : null,
|
|
222
|
+
el("div", { class: "zodal-dials-control" }, control, reset)
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
function filterRows(panel, query) {
|
|
226
|
+
const q = query.trim().toLowerCase();
|
|
227
|
+
for (const row of Array.from(panel.querySelectorAll(".zodal-dials-field"))) {
|
|
228
|
+
const key = (row.getAttribute("data-key") ?? "").toLowerCase();
|
|
229
|
+
const label = (row.querySelector(".zodal-dials-label")?.textContent ?? "").toLowerCase();
|
|
230
|
+
const description = (row.querySelector(".zodal-dials-description")?.textContent ?? "").toLowerCase();
|
|
231
|
+
row.style.display = !q || key.includes(q) || label.includes(q) || description.includes(q) ? "" : "none";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function renderSettingsPanel(form, states, options = {}) {
|
|
235
|
+
const registry = options.registry ?? createVanillaSettingsRegistry();
|
|
236
|
+
const opts = { ...options, registry };
|
|
237
|
+
const panel = el("div", { class: "zodal-dials-panel" });
|
|
238
|
+
if (options.search !== false) {
|
|
239
|
+
panel.appendChild(
|
|
240
|
+
el("input", {
|
|
241
|
+
type: "search",
|
|
242
|
+
class: "zodal-dials-search",
|
|
243
|
+
placeholder: "Search settings\u2026",
|
|
244
|
+
oninput: (e) => filterRows(panel, e.target.value)
|
|
245
|
+
})
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
const primary = (f) => f.facets?.find((x) => !x.startsWith("@")) ?? "_ungrouped";
|
|
249
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
250
|
+
for (const f of form.fields) {
|
|
251
|
+
const id = primary(f);
|
|
252
|
+
const list = buckets.get(id);
|
|
253
|
+
if (list) list.push(f);
|
|
254
|
+
else buckets.set(id, [f]);
|
|
255
|
+
}
|
|
256
|
+
const groupMeta = new Map(form.groups.map((g) => [g.id, g]));
|
|
257
|
+
const sectionIds = [...buckets.keys()].sort(
|
|
258
|
+
(a, b) => (groupMeta.get(a)?.order ?? 500) - (groupMeta.get(b)?.order ?? 500) || a.localeCompare(b)
|
|
259
|
+
);
|
|
260
|
+
for (const id of sectionIds) {
|
|
261
|
+
const title = groupMeta.get(id)?.title ?? humanize(id);
|
|
262
|
+
const section = el("section", { class: "zodal-dials-group", "data-group": id }, el("h3", { class: "zodal-dials-group-title" }, title));
|
|
263
|
+
for (const f of buckets.get(id) ?? []) section.appendChild(renderField(f, states[f.key] ?? emptyState(), opts));
|
|
264
|
+
panel.appendChild(section);
|
|
265
|
+
}
|
|
266
|
+
return panel;
|
|
267
|
+
}
|
|
268
|
+
export {
|
|
269
|
+
createVanillaSettingsRegistry,
|
|
270
|
+
el,
|
|
271
|
+
renderArray,
|
|
272
|
+
renderField,
|
|
273
|
+
renderNumber,
|
|
274
|
+
renderObject,
|
|
275
|
+
renderRadio,
|
|
276
|
+
renderRawJson,
|
|
277
|
+
renderSecret,
|
|
278
|
+
renderSelect,
|
|
279
|
+
renderSettingsPanel,
|
|
280
|
+
renderSlider,
|
|
281
|
+
renderSwitch,
|
|
282
|
+
renderText,
|
|
283
|
+
renderTextarea,
|
|
284
|
+
widgetIs
|
|
285
|
+
};
|
|
286
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dom.ts","../src/widgets.ts","../src/registry.ts","../src/panel.ts"],"sourcesContent":["/**\n * Internal DOM element factory (no framework). Sets value/checked/disabled/selected as PROPERTIES\n * (so form controls reflect state), `on*` keys as event listeners, and everything else as attributes.\n */\n\nexport type Attrs = Record<string, unknown>;\n\nconst PROP_KEYS = new Set(['value', 'checked', 'disabled', 'selected']);\n\nexport function el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n attrs?: Attrs | null,\n ...children: Array<Node | string | null | undefined>\n): HTMLElementTagNameMap[K] {\n const element = document.createElement(tag);\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) {\n if (value === undefined || value === null || value === false) continue;\n if (key.startsWith('on') && typeof value === 'function') {\n element.addEventListener(key.slice(2).toLowerCase(), value as EventListener);\n } else if (key === 'class') {\n element.className = String(value);\n } else if (PROP_KEYS.has(key)) {\n (element as unknown as Record<string, unknown>)[key] = value;\n } else {\n element.setAttribute(key, String(value));\n }\n }\n }\n for (const child of children) {\n if (child == null) continue;\n element.appendChild(typeof child === 'string' ? document.createTextNode(child) : child);\n }\n return element;\n}\n","/**\n * Vanilla widget renderers — each produces the control element for a setting from its config +\n * current state, wiring change events to the handlers. A `secret` value (a masked `SecretRef`) is\n * shown as a status + a write-only field. Structured/unknown values fall back to a raw JSON editor\n * (the rawJson renderer states WHY). No framework; pure DOM.\n */\n\nimport { el } from './dom.js';\nimport { isSecretRef } from '@zodal/dials-core';\nimport type { SettingFieldConfig, SettingFieldState } from '@zodal/dials-ui';\nimport type { SettingRenderFn } from './types.js';\n\nconst isDisabled = (field: SettingFieldConfig, state: SettingFieldState): boolean => field.readOnly || state.managed;\nconst asText = (v: unknown): string => (v == null ? '' : String(v));\n\n/** Decode a numeric input, returning undefined for empty/non-numeric so clearing the box is treated\n * as \"no change\" (use Reset to unset) rather than silently writing 0 / NaN. */\nfunction decodeNumber(raw: string): number | undefined {\n if (raw.trim() === '') return undefined;\n const n = Number(raw);\n return Number.isNaN(n) ? undefined : n;\n}\nconst jsonString = (v: unknown): string => {\n try {\n return JSON.stringify(v ?? null, null, 2);\n } catch {\n return String(v);\n }\n};\n\nexport const renderSwitch: SettingRenderFn = (field, state, h) =>\n el('input', {\n type: 'checkbox',\n class: 'zodal-dials-switch',\n checked: state.value === true,\n disabled: isDisabled(field, state),\n onchange: (e: Event) => h.onChange?.(field.key, (e.target as HTMLInputElement).checked),\n });\n\nexport const renderText: SettingRenderFn = (field, state, h) =>\n el('input', {\n type: 'text',\n class: 'zodal-dials-text',\n value: asText(state.value),\n placeholder: asText(field.defaultValue),\n disabled: isDisabled(field, state),\n onchange: (e: Event) => h.onChange?.(field.key, (e.target as HTMLInputElement).value),\n });\n\nexport const renderTextarea: SettingRenderFn = (field, state, h) =>\n el(\n 'textarea',\n {\n class: 'zodal-dials-textarea',\n disabled: isDisabled(field, state),\n onchange: (e: Event) => h.onChange?.(field.key, (e.target as HTMLTextAreaElement).value),\n },\n asText(state.value),\n );\n\nexport const renderNumber: SettingRenderFn = (field, state, h) =>\n el('input', {\n type: 'number',\n class: 'zodal-dials-number',\n value: asText(state.value),\n min: field.bounds?.min,\n max: field.bounds?.max,\n disabled: isDisabled(field, state),\n onchange: (e: Event) => {\n const v = decodeNumber((e.target as HTMLInputElement).value);\n if (v !== undefined) h.onChange?.(field.key, v);\n },\n });\n\nexport const renderSlider: SettingRenderFn = (field, state, h) => {\n const output = el('output', { class: 'zodal-dials-slider-value' }, asText(state.value));\n const input = el('input', {\n type: 'range',\n class: 'zodal-dials-slider',\n value: asText(state.value),\n min: field.bounds?.min,\n max: field.bounds?.max,\n disabled: isDisabled(field, state),\n oninput: (e: Event) => {\n output.textContent = (e.target as HTMLInputElement).value;\n },\n onchange: (e: Event) => {\n const v = decodeNumber((e.target as HTMLInputElement).value);\n if (v !== undefined) h.onChange?.(field.key, v);\n },\n });\n return el('span', { class: 'zodal-dials-slider-wrap' }, input, output);\n};\n\nexport const renderSelect: SettingRenderFn = (field, state, h) => {\n const select = el(\n 'select',\n {\n class: 'zodal-dials-select',\n disabled: isDisabled(field, state),\n onchange: (e: Event) => h.onChange?.(field.key, (e.target as HTMLSelectElement).value),\n },\n ...(field.enumValues ?? []).map((v) => el('option', { value: v }, v)),\n );\n select.value = asText(state.value);\n return select;\n};\n\nexport const renderRadio: SettingRenderFn = (field, state, h) =>\n el(\n 'fieldset',\n { class: 'zodal-dials-radio' },\n ...(field.enumValues ?? []).map((v) =>\n el(\n 'label',\n null,\n el('input', {\n type: 'radio',\n name: field.key,\n value: v,\n checked: state.value === v,\n disabled: isDisabled(field, state),\n onchange: () => h.onChange?.(field.key, v),\n }),\n v,\n ),\n ),\n );\n\nexport const renderSecret: SettingRenderFn = (field, state, h) => {\n const ref = isSecretRef(state.value) ? state.value : undefined;\n const status = el('span', { class: 'zodal-dials-secret-status' }, ref ? ref.masked : 'not set');\n const input = el('input', {\n type: 'password',\n class: 'zodal-dials-secret',\n placeholder: 'Enter new value',\n disabled: isDisabled(field, state),\n onchange: (e: Event) => h.onChange?.(field.key, (e.target as HTMLInputElement).value),\n });\n return el('span', { class: 'zodal-dials-secret-wrap' }, status, input);\n};\n\nfunction renderJsonish(cssClass: string, note?: string): SettingRenderFn {\n return (field, state, h) => {\n // Defense in depth: a JSON/raw editor must never serialize a secret, even if reached by a\n // widget↔sensitivity mismatch — always fall back to the masked secret widget.\n if (field.sensitivity === 'secret' || isSecretRef(state.value)) return renderSecret(field, state, h);\n const textarea = el(\n 'textarea',\n {\n class: cssClass,\n disabled: isDisabled(field, state),\n onchange: (e: Event) => {\n try {\n h.onChange?.(field.key, JSON.parse((e.target as HTMLTextAreaElement).value));\n } catch {\n // invalid JSON: a real renderer would surface an error; the reference renderer ignores it.\n }\n },\n },\n jsonString(state.value),\n );\n return note\n ? el('span', { class: 'zodal-dials-json-wrap' }, el('small', { class: 'zodal-dials-json-note' }, note), textarea)\n : textarea;\n };\n}\n\nexport const renderObject = renderJsonish('zodal-dials-object');\nexport const renderArray = renderJsonish('zodal-dials-array');\nexport const renderRawJson = renderJsonish('zodal-dials-rawjson', 'rendered as raw JSON (no structured editor for this type)');\n","/**\n * The vanilla settings renderer registry — populates a `@zodal/dials-ui` `RendererRegistry` with one\n * DOM renderer per widget kind, the masked secret widget at the OVERRIDE band, and a terminal rawJson\n * renderer (via `alwaysMatch`) so every setting renders to something. The widget kind is supplied via\n * the render context, so selection is open-closed (a consumer can register a higher-priority override).\n */\n\nimport { createSettingsRendererRegistry, secretRoleIs, alwaysMatch, PRIORITY } from '@zodal/dials-ui';\nimport type { RendererRegistry, RendererTester, WidgetKind } from '@zodal/dials-ui';\nimport type { SettingRenderFn } from './types.js';\nimport {\n renderSwitch,\n renderSelect,\n renderRadio,\n renderSlider,\n renderNumber,\n renderText,\n renderTextarea,\n renderObject,\n renderArray,\n renderRawJson,\n renderSecret,\n} from './widgets.js';\n\n/** Tester matching a field's resolved widget kind (supplied via the render context as `widget`). */\nexport function widgetIs(kind: WidgetKind): RendererTester {\n return (_field, ctx) => (ctx.widget === kind ? PRIORITY.LIBRARY : -1);\n}\n\nconst WIDGET_RENDERERS: Array<[WidgetKind, SettingRenderFn]> = [\n ['switch', renderSwitch],\n ['select', renderSelect],\n ['radio', renderRadio],\n ['slider', renderSlider],\n ['number', renderNumber],\n ['text', renderText],\n ['textarea', renderTextarea],\n ['color', renderText],\n ['date', renderText],\n ['path', renderText],\n ['object', renderObject],\n ['array', renderArray],\n // Honor a resolved `secret` widget kind directly, so secret masking does not depend solely on the\n // sensitivity context staying in lockstep (secretRoleIs at OVERRIDE remains the primary signal).\n ['secret', renderSecret],\n];\n\n/** Create a vanilla settings renderer registry (a `@zodal/dials-ui` registry of DOM renderers). */\nexport function createVanillaSettingsRegistry(): RendererRegistry<SettingRenderFn> {\n const registry = createSettingsRendererRegistry<SettingRenderFn>();\n registry.register({ tester: alwaysMatch(), renderer: renderRawJson, name: 'rawJson' });\n for (const [kind, renderer] of WIDGET_RENDERERS) registry.register({ tester: widgetIs(kind), renderer, name: kind });\n registry.register({ tester: secretRoleIs(), renderer: renderSecret, name: 'secret' });\n return registry;\n}\n","/**\n * Field and panel assembly. `renderField` builds one labeled row (label + provenance badges +\n * description + control via the registry + a reset button when overridden). `renderSettingsPanel`\n * builds the whole panel: a client-side search/filter box + a section per primary facet. Each setting\n * renders ONCE (under its primary facet); multi-membership facets and computed groups drive filtering,\n * not duplicate controls.\n */\n\nimport { el } from './dom.js';\nimport type { RendererRegistry, SettingFieldConfig, SettingFieldState, SettingsForm } from '@zodal/dials-ui';\nimport type { ResolvedFieldAffordance } from '@zodal/core';\nimport type { SettingKey } from '@zodal/dials-core';\nimport type { FieldHandlers, SettingRenderFn } from './types.js';\nimport { createVanillaSettingsRegistry } from './registry.js';\n\nexport interface RenderOptions extends FieldHandlers {\n /** Override the renderer registry (e.g. to register custom widgets). */\n registry?: RendererRegistry<SettingRenderFn>;\n /** Include the client-side search/filter box. Default: true. */\n search?: boolean;\n}\n\nfunction humanize(id: string): string {\n return id.replace(/^[@_]/, '').replace(/[._-]+/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase()) || 'Other';\n}\n\nfunction emptyState(): SettingFieldState {\n return { value: undefined, managed: false, shadowed: false, dirty: false };\n}\n\n/** Render one setting as a labeled field row. */\nexport function renderField(\n field: SettingFieldConfig,\n state: SettingFieldState,\n options: RenderOptions = {},\n): HTMLElement {\n const registry = options.registry ?? createVanillaSettingsRegistry();\n const render = registry.resolve({ zodType: field.zodType } as unknown as ResolvedFieldAffordance, {\n mode: 'form',\n widget: field.widget,\n sensitivity: field.sensitivity,\n });\n const control = render ? render(field, state, options) : el('span', null, '(no renderer)');\n\n const badges: HTMLElement[] = [];\n if (state.source && state.source !== 'default') {\n badges.push(el('span', { class: 'zodal-dials-badge zodal-dials-source', title: `set by ${state.source}` }, state.source));\n }\n if (state.managed) badges.push(el('span', { class: 'zodal-dials-badge zodal-dials-managed', title: 'managed by policy' }, 'policy'));\n if (state.dirty) badges.push(el('span', { class: 'zodal-dials-badge zodal-dials-dirty' }, 'modified'));\n\n const reset =\n state.source && state.source !== 'default' && !state.managed\n ? el('button', { type: 'button', class: 'zodal-dials-reset', onclick: () => options.onReset?.(field.key) }, 'Reset')\n : null;\n\n return el(\n 'div',\n { class: 'zodal-dials-field', 'data-key': field.key, 'data-widget': field.widget },\n el('label', { class: 'zodal-dials-label', for: field.key }, field.label, ...badges),\n field.description ? el('p', { class: 'zodal-dials-description' }, field.description) : null,\n el('div', { class: 'zodal-dials-control' }, control, reset),\n );\n}\n\nfunction filterRows(panel: HTMLElement, query: string): void {\n const q = query.trim().toLowerCase();\n for (const row of Array.from(panel.querySelectorAll<HTMLElement>('.zodal-dials-field'))) {\n const key = (row.getAttribute('data-key') ?? '').toLowerCase();\n const label = (row.querySelector('.zodal-dials-label')?.textContent ?? '').toLowerCase();\n const description = (row.querySelector('.zodal-dials-description')?.textContent ?? '').toLowerCase();\n row.style.display = !q || key.includes(q) || label.includes(q) || description.includes(q) ? '' : 'none';\n }\n}\n\n/** Render the full settings panel (search box + a section per primary facet). */\nexport function renderSettingsPanel(\n form: SettingsForm,\n states: Record<SettingKey, SettingFieldState>,\n options: RenderOptions = {},\n): HTMLElement {\n const registry = options.registry ?? createVanillaSettingsRegistry();\n const opts: RenderOptions = { ...options, registry };\n const panel = el('div', { class: 'zodal-dials-panel' });\n\n if (options.search !== false) {\n panel.appendChild(\n el('input', {\n type: 'search',\n class: 'zodal-dials-search',\n placeholder: 'Search settings…',\n oninput: (e: Event) => filterRows(panel, (e.target as HTMLInputElement).value),\n }),\n );\n }\n\n // Bucket each field under its primary (first non-computed) facet, so every field renders once.\n const primary = (f: SettingFieldConfig): string => f.facets?.find((x) => !x.startsWith('@')) ?? '_ungrouped';\n const buckets = new Map<string, SettingFieldConfig[]>();\n for (const f of form.fields) {\n const id = primary(f);\n const list = buckets.get(id);\n if (list) list.push(f);\n else buckets.set(id, [f]);\n }\n\n const groupMeta = new Map(form.groups.map((g) => [g.id, g]));\n const sectionIds = [...buckets.keys()].sort(\n (a, b) => (groupMeta.get(a)?.order ?? 500) - (groupMeta.get(b)?.order ?? 500) || a.localeCompare(b),\n );\n\n for (const id of sectionIds) {\n const title = groupMeta.get(id)?.title ?? humanize(id);\n const section = el('section', { class: 'zodal-dials-group', 'data-group': id }, el('h3', { class: 'zodal-dials-group-title' }, title));\n for (const f of buckets.get(id) ?? []) section.appendChild(renderField(f, states[f.key] ?? emptyState(), opts));\n panel.appendChild(section);\n }\n\n return panel;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zodal/dials-ui-vanilla",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vanilla HTML/JS reference renderer for @zodal/dials-ui — no framework, produces DOM from the headless settings config",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@zodal/core": "^0.1.2",
|
|
29
|
+
"@zodal/dials-core": "^0.1.0",
|
|
30
|
+
"@zodal/dials-ui": "^0.1.0",
|
|
31
|
+
"zod": ">=4.1.13"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@zodal/core": "^0.1.2",
|
|
35
|
+
"jsdom": "^25.0.0",
|
|
36
|
+
"tsup": "^8.0.0",
|
|
37
|
+
"typescript": "^5.7.0",
|
|
38
|
+
"vitest": "^3.0.0",
|
|
39
|
+
"zod": "^4.4.0",
|
|
40
|
+
"@zodal/dials-core": "0.1.0",
|
|
41
|
+
"@zodal/dials-ui": "0.1.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"typecheck": "tsc --noEmit"
|
|
47
|
+
}
|
|
48
|
+
}
|