@gravionlabs/helix-zod 22.1.2 → 22.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,993 +1,1447 @@
1
- import { NgComponentOutlet } from '@angular/common';
2
- import * as i0 from '@angular/core';
3
- import { InjectionToken, inject, Injectable, input, computed, ChangeDetectionStrategy, Component, output, Injector, signal, untracked, Directive, model, makeEnvironmentProviders } from '@angular/core';
4
- import { validateStandardSchema, hidden, disabled, readonly, required, applyEach, applyWhenValue, form, submit, FormField, transformedValue } from '@angular/forms/signals';
5
- import { z, ZodIssueCode } from 'zod';
6
- import { ValidatorKey } from '@gravionlabs/helix-core/validators';
7
- import { HelixFormField, HelixSelect } from '@gravionlabs/helix-shell';
8
-
9
- /**
10
- * Multi-provider token holding widget registrations. Later registrations win,
11
- * so consumer widgets registered after the built-ins override them.
12
- */
13
- const HELIX_DYNAMIC_FIELD_WIDGETS = new InjectionToken('HELIX_DYNAMIC_FIELD_WIDGETS');
14
- /** Optional app-level error message resolver used by all built-in widgets. */
15
- const HELIX_ERROR_MESSAGE_RESOLVER = new InjectionToken('HELIX_ERROR_MESSAGE_RESOLVER');
16
- /** Resolves the widget component registered for a widget kind. */
17
- class HelixFieldWidgetResolver {
18
- #registrations = inject(HELIX_DYNAMIC_FIELD_WIDGETS, { optional: true }) ?? [];
19
- resolve(widget) {
20
- const flat = this.#registrations.flat();
21
- for (let i = flat.length - 1; i >= 0; i--) {
22
- if (flat[i].widget === widget)
23
- return flat[i].component;
24
- }
25
- throw new Error(`[HelixFieldWidgetResolver] No widget registered for kind "${widget}". ` +
26
- (typeof ngDevMode !== 'undefined' && ngDevMode
27
- ? `Did you forget provideHelixDynamicForms() or a custom { widgets: [...] } registration?`
28
- : ''));
29
- }
30
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixFieldWidgetResolver, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
31
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixFieldWidgetResolver, providedIn: 'root' });
32
- }
33
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixFieldWidgetResolver, decorators: [{
34
- type: Injectable,
35
- args: [{ providedIn: 'root' }]
36
- }] });
37
-
38
- /**
39
- * Dispatches one field descriptor to its registered widget component.
40
- *
41
- * Handles `hidden()` centrally: a hidden field renders nothing, per the
42
- * signal-forms guidance to `@if` on the hidden state.
43
- *
44
- * Imports no widget statically — composite widgets (object/array/union) import
45
- * this component, and built-in registrations live in
46
- * `provideHelixDynamicForms()`, which breaks the import cycle.
47
- */
48
- class HelixDynamicField {
49
- field = input.required(/* @ts-ignore */
50
- ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
51
- descriptor = input.required(/* @ts-ignore */
52
- ...(ngDevMode ? [{ debugName: "descriptor" }] : /* istanbul ignore next */ []));
53
- #resolver = inject(HelixFieldWidgetResolver);
54
- component = computed(() => this.#resolver.resolve(this.descriptor().widget), /* @ts-ignore */
55
- ...(ngDevMode ? [{ debugName: "component" }] : /* istanbul ignore next */ []));
56
- outletInputs = computed(() => ({
57
- field: this.field(),
58
- descriptor: this.descriptor(),
59
- }), /* @ts-ignore */
60
- ...(ngDevMode ? [{ debugName: "outletInputs" }] : /* istanbul ignore next */ []));
61
- hiddenState = computed(() => this.field()().hidden(), /* @ts-ignore */
62
- ...(ngDevMode ? [{ debugName: "hiddenState" }] : /* istanbul ignore next */ []));
63
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDynamicField, deps: [], target: i0.ɵɵFactoryTarget.Component });
64
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: HelixDynamicField, isStandalone: true, selector: "helix-dynamic-field", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, descriptor: { classPropertyName: "descriptor", publicName: "descriptor", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@if (!hiddenState()) {\n <ng-container [ngComponentOutlet]=\"component()\" [ngComponentOutletInputs]=\"outletInputs()\" />\n}\n", styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
65
- }
66
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDynamicField, decorators: [{
67
- type: Component,
68
- args: [{ selector: 'helix-dynamic-field', standalone: true, imports: [NgComponentOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (!hiddenState()) {\n <ng-container [ngComponentOutlet]=\"component()\" [ngComponentOutletInputs]=\"outletInputs()\" />\n}\n", styles: [":host{display:block;width:100%}\n"] }]
69
- }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], descriptor: [{ type: i0.Input, args: [{ isSignal: true, alias: "descriptor", required: true }] }] } });
70
-
1
+ import { NgComponentOutlet } from "@angular/common";
2
+ import * as i0 from "@angular/core";
3
+ import { ChangeDetectionStrategy, Component, Directive, Injectable, InjectionToken, Injector, computed, inject, input, makeEnvironmentProviders, model, output, signal, untracked } from "@angular/core";
4
+ import { FormField, applyEach, applyWhenValue, disabled, form, hidden, readonly, required, submit, transformedValue, validateStandardSchema } from "@angular/forms/signals";
5
+ import { ZodIssueCode, z } from "zod";
6
+ import { ValidatorKey } from "@gravionlabs/helix-core/validators";
7
+ import { HelixFormField, HelixSelect } from "@gravionlabs/helix-shell";
8
+ const HELIX_DYNAMIC_FIELD_WIDGETS = new InjectionToken("HELIX_DYNAMIC_FIELD_WIDGETS");
9
+ const HELIX_ERROR_MESSAGE_RESOLVER = new InjectionToken("HELIX_ERROR_MESSAGE_RESOLVER");
10
+ var HelixFieldWidgetResolver = class HelixFieldWidgetResolver {
11
+ #registrations = inject(HELIX_DYNAMIC_FIELD_WIDGETS, { optional: true }) ?? [];
12
+ resolve(widget) {
13
+ const flat = this.#registrations.flat();
14
+ for (let i = flat.length - 1; i >= 0; i--) if (flat[i].widget === widget) return flat[i].component;
15
+ throw new Error(`[HelixFieldWidgetResolver] No widget registered for kind "${widget}". ` + (typeof ngDevMode !== "undefined" && ngDevMode ? `Did you forget provideHelixDynamicForms() or a custom { widgets: [...] } registration?` : ""));
16
+ }
17
+ static ɵfac = i0.ɵɵngDeclareFactory({
18
+ minVersion: "12.0.0",
19
+ version: "22.2.1",
20
+ ngImport: i0,
21
+ type: HelixFieldWidgetResolver,
22
+ deps: [],
23
+ target: i0.ɵɵFactoryTarget.Injectable
24
+ });
25
+ static ɵprov = i0.ɵɵngDeclareInjectable({
26
+ minVersion: "12.0.0",
27
+ version: "22.2.1",
28
+ ngImport: i0,
29
+ type: HelixFieldWidgetResolver,
30
+ providedIn: "root"
31
+ });
32
+ };
33
+ i0.ɵɵngDeclareClassMetadata({
34
+ minVersion: "12.0.0",
35
+ version: "22.2.1",
36
+ ngImport: i0,
37
+ type: HelixFieldWidgetResolver,
38
+ decorators: [{
39
+ type: Injectable,
40
+ args: [{ providedIn: "root" }]
41
+ }]
42
+ });
43
+ var HelixDynamicField = class HelixDynamicField {
44
+ field = input.required(...ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []);
45
+ descriptor = input.required(...ngDevMode ? [{ debugName: "descriptor" }] : /* istanbul ignore next */ []);
46
+ #resolver = inject(HelixFieldWidgetResolver);
47
+ component = computed(() => this.#resolver.resolve(this.descriptor().widget), ...ngDevMode ? [{ debugName: "component" }] : /* istanbul ignore next */ []);
48
+ outletInputs = computed(() => ({
49
+ field: this.field(),
50
+ descriptor: this.descriptor()
51
+ }), ...ngDevMode ? [{ debugName: "outletInputs" }] : /* istanbul ignore next */ []);
52
+ hiddenState = computed(() => this.field()().hidden(), ...ngDevMode ? [{ debugName: "hiddenState" }] : /* istanbul ignore next */ []);
53
+ static ɵfac = i0.ɵɵngDeclareFactory({
54
+ minVersion: "12.0.0",
55
+ version: "22.2.1",
56
+ ngImport: i0,
57
+ type: HelixDynamicField,
58
+ deps: [],
59
+ target: i0.ɵɵFactoryTarget.Component
60
+ });
61
+ static ɵcmp = i0.ɵɵngDeclareComponent({
62
+ minVersion: "17.0.0",
63
+ version: "22.2.1",
64
+ type: HelixDynamicField,
65
+ isStandalone: true,
66
+ selector: "helix-dynamic-field",
67
+ inputs: {
68
+ field: {
69
+ classPropertyName: "field",
70
+ publicName: "field",
71
+ isSignal: true,
72
+ isRequired: true,
73
+ transformFunction: null
74
+ },
75
+ descriptor: {
76
+ classPropertyName: "descriptor",
77
+ publicName: "descriptor",
78
+ isSignal: true,
79
+ isRequired: true,
80
+ transformFunction: null
81
+ }
82
+ },
83
+ ngImport: i0,
84
+ template: "@if (!hiddenState()) {\n <ng-container [ngComponentOutlet]=\"component()\" [ngComponentOutletInputs]=\"outletInputs()\" />\n}\n",
85
+ styles: [":host{display:block;width:100%}\n"],
86
+ dependencies: [{
87
+ kind: "directive",
88
+ type: NgComponentOutlet,
89
+ selector: "[ngComponentOutlet]",
90
+ inputs: [
91
+ "ngComponentOutlet",
92
+ "ngComponentOutletInputs",
93
+ "ngComponentOutletInjector",
94
+ "ngComponentOutletEnvironmentInjector",
95
+ "ngComponentOutletContent",
96
+ "ngComponentOutletNgModule"
97
+ ],
98
+ exportAs: ["ngComponentOutlet"]
99
+ }],
100
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
101
+ });
102
+ };
103
+ i0.ɵɵngDeclareClassMetadata({
104
+ minVersion: "12.0.0",
105
+ version: "22.2.1",
106
+ ngImport: i0,
107
+ type: HelixDynamicField,
108
+ decorators: [{
109
+ type: Component,
110
+ args: [{
111
+ selector: "helix-dynamic-field",
112
+ standalone: true,
113
+ imports: [NgComponentOutlet],
114
+ changeDetection: ChangeDetectionStrategy.OnPush,
115
+ template: "@if (!hiddenState()) {\n <ng-container [ngComponentOutlet]=\"component()\" [ngComponentOutletInputs]=\"outletInputs()\" />\n}\n",
116
+ styles: [":host{display:block;width:100%}\n"]
117
+ }]
118
+ }],
119
+ propDecorators: {
120
+ field: [{
121
+ type: i0.Input,
122
+ args: [{
123
+ isSignal: true,
124
+ alias: "field",
125
+ required: true
126
+ }]
127
+ }],
128
+ descriptor: [{
129
+ type: i0.Input,
130
+ args: [{
131
+ isSignal: true,
132
+ alias: "descriptor",
133
+ required: true
134
+ }]
135
+ }]
136
+ }
137
+ });
71
138
  const defOf$1 = (schema) => schema._zod.def;
72
- /**
73
- * Builds a fully-populated initial model value for a Zod schema — signal forms
74
- * require a concrete model shape up front.
75
- *
76
- * Rules: `.default()` wins; strings → `''`; numbers/dates → `null` (paired
77
- * with parse-transforming widgets); booleans → `false`; enums → first option;
78
- * arrays → `[]`; objects → recursed shape; discriminated unions → default of
79
- * the first variant; `optional` unwraps to its inner default, `nullable` → `null`.
80
- */
81
139
  function buildDefaultValue(schema) {
82
- const def = defOf$1(schema);
83
- switch (def.type) {
84
- case 'default': {
85
- const dv = def['defaultValue'];
86
- return typeof dv === 'function' ? dv() : dv;
87
- }
88
- case 'optional': {
89
- // Signal forms create no child field for an `undefined` value, so optional
90
- // fields get their inner default (`''`, `null`, …) to stay navigable.
91
- return buildDefaultValue(schema.unwrap());
92
- }
93
- case 'readonly':
94
- case 'nonoptional':
95
- return buildDefaultValue(schema.unwrap());
96
- case 'nullable':
97
- return null;
98
- case 'string':
99
- return '';
100
- case 'number':
101
- case 'int':
102
- case 'date':
103
- return null;
104
- case 'boolean':
105
- return false;
106
- case 'enum': {
107
- const options = schema.options;
108
- return options[0];
109
- }
110
- case 'literal': {
111
- const values = def['values'];
112
- return values[0];
113
- }
114
- case 'array':
115
- return [];
116
- case 'object': {
117
- const shape = schema.shape;
118
- const value = {};
119
- for (const [key, child] of Object.entries(shape)) {
120
- value[key] = buildDefaultValue(child);
121
- }
122
- return value;
123
- }
124
- case 'union': {
125
- const options = schema.options;
126
- return buildDefaultValue(options[0]);
127
- }
128
- default:
129
- return undefined;
130
- }
140
+ const def = defOf$1(schema);
141
+ switch (def.type) {
142
+ case "default": {
143
+ const dv = def["defaultValue"];
144
+ return typeof dv === "function" ? dv() : dv;
145
+ }
146
+ case "optional": return buildDefaultValue(schema.unwrap());
147
+ case "readonly":
148
+ case "nonoptional": return buildDefaultValue(schema.unwrap());
149
+ case "nullable": return null;
150
+ case "string": return "";
151
+ case "number":
152
+ case "int":
153
+ case "date": return null;
154
+ case "boolean": return false;
155
+ case "enum": return schema.options[0];
156
+ case "literal": return def["values"][0];
157
+ case "array": return [];
158
+ case "object": {
159
+ const shape = schema.shape;
160
+ const value = {};
161
+ for (const [key, child] of Object.entries(shape)) value[key] = buildDefaultValue(child);
162
+ return value;
163
+ }
164
+ case "union": {
165
+ const options = schema.options;
166
+ return buildDefaultValue(options[0]);
167
+ }
168
+ default: return;
169
+ }
131
170
  }
132
- /**
133
- * Optional (non-nullable) leaves hold an empty model value (`null`) so signal
134
- * forms keep a child field for them; Zod expects them absent. Maps those
135
- * top-level `null`s to `undefined` before validating/parsing.
136
- */
137
171
  function nullsToAbsent(schema, value) {
138
- if (typeof value !== 'object' || value === null)
139
- return value;
140
- const shape = schema.shape;
141
- const result = { ...value };
142
- for (const [key, child] of Object.entries(shape)) {
143
- if (result[key] === null &&
144
- child.safeParse(undefined).success &&
145
- !child.safeParse(null).success) {
146
- result[key] = undefined;
147
- }
148
- }
149
- return result;
172
+ if (typeof value !== "object" || value === null) return value;
173
+ const shape = schema.shape;
174
+ const result = { ...value };
175
+ for (const [key, child] of Object.entries(shape)) if (result[key] === null && child.safeParse(void 0).success && !child.safeParse(null).success) result[key] = void 0;
176
+ return result;
150
177
  }
151
-
152
- /**
153
- * Builds the signal-forms schema for a Zod-driven dynamic form:
154
- *
155
- * 1. Root-level `validateStandardSchema(rootPath, zodSchema)` — Zod issue
156
- * paths route errors onto the matching child fields, including cross-field
157
- * `.refine()` errors with a `path`.
158
- * 2. Conditional field state from {@link HelixFieldMeta} predicates
159
- * (`hiddenWhen`/`disabledWhen`/`readonlyWhen`/`requiredWhen`), evaluated
160
- * against the root form value.
161
- * 3. `meta.extraSchema` escape-hatch rules per field.
162
- * 4. `applyEach` for array items and `applyWhenValue` for discriminated-union
163
- * variants, so variant/item rules stay dormant unless active.
164
- */
165
178
  function buildHelixSchema(zodSchema, root) {
166
- return (rootPath) => {
167
- const standard = zodSchema['~standard'];
168
- validateStandardSchema(rootPath, {
169
- '~standard': {
170
- ...standard,
171
- validate: (value) => standard.validate(nullsToAbsent(zodSchema, value)),
172
- },
173
- });
174
- applyDescriptorRules(rootPath, root, rootPath);
175
- };
179
+ return (rootPath) => {
180
+ const standard = zodSchema["~standard"];
181
+ validateStandardSchema(rootPath, { "~standard": {
182
+ ...standard,
183
+ validate: (value) => standard.validate(nullsToAbsent(zodSchema, value))
184
+ } });
185
+ applyDescriptorRules(rootPath, root, rootPath);
186
+ };
176
187
  }
177
- /** Reads the root form value from any nested logic context. */
178
188
  function rootValueOf(ctx, rootPath) {
179
- return ctx.valueOf(rootPath);
189
+ return ctx.valueOf(rootPath);
180
190
  }
181
191
  function applyConditionRules(path, descriptor, rootPath) {
182
- const meta = descriptor.meta;
183
- if (meta.hiddenWhen) {
184
- hidden(path, (ctx) => !!meta.hiddenWhen?.(rootValueOf(ctx, rootPath)));
185
- }
186
- if (meta.disabledWhen) {
187
- disabled(path, (ctx) => meta.disabledWhen?.(rootValueOf(ctx, rootPath)) ?? false);
188
- }
189
- if (meta.readonlyWhen) {
190
- readonly(path, (ctx) => !!meta.readonlyWhen?.(rootValueOf(ctx, rootPath)));
191
- }
192
- if (meta.requiredWhen) {
193
- required(path, {
194
- when: (ctx) => !!meta.requiredWhen?.(rootValueOf(ctx, rootPath)),
195
- });
196
- }
197
- meta.extraSchema?.(path);
192
+ const meta = descriptor.meta;
193
+ if (meta.hiddenWhen) hidden(path, (ctx) => !!meta.hiddenWhen?.(rootValueOf(ctx, rootPath)));
194
+ if (meta.disabledWhen) disabled(path, (ctx) => meta.disabledWhen?.(rootValueOf(ctx, rootPath)) ?? false);
195
+ if (meta.readonlyWhen) readonly(path, (ctx) => !!meta.readonlyWhen?.(rootValueOf(ctx, rootPath)));
196
+ if (meta.requiredWhen) required(path, { when: (ctx) => !!meta.requiredWhen?.(rootValueOf(ctx, rootPath)) });
197
+ meta.extraSchema?.(path);
198
198
  }
199
199
  function applyDescriptorRules(path, descriptor, rootPath) {
200
- // Root-level conditions make no sense on the form itself — only on fields.
201
- if (path !== rootPath)
202
- applyConditionRules(path, descriptor, rootPath);
203
- for (const child of descriptor.children ?? []) {
204
- applyDescriptorRules(path[child.key], child, rootPath);
205
- }
206
- if (descriptor.itemDescriptor) {
207
- const item = descriptor.itemDescriptor;
208
- applyEach(path, ((itemPath) => {
209
- applyDescriptorRules(itemPath, item, rootPath);
210
- }));
211
- }
212
- if (descriptor.union) {
213
- const { discriminator, variants } = descriptor.union;
214
- for (const [tag, variant] of variants) {
215
- applyWhenValue(path, ((value) => value?.[discriminator] === tag), ((variantPath) => {
216
- // The union field itself carries the variant's children.
217
- for (const child of variant.children ?? []) {
218
- applyDescriptorRules(variantPath[child.key], child, rootPath);
219
- }
220
- }));
221
- }
222
- }
200
+ if (path !== rootPath) applyConditionRules(path, descriptor, rootPath);
201
+ for (const child of descriptor.children ?? []) applyDescriptorRules(path[child.key], child, rootPath);
202
+ if (descriptor.itemDescriptor) {
203
+ const item = descriptor.itemDescriptor;
204
+ applyEach(path, ((itemPath) => {
205
+ applyDescriptorRules(itemPath, item, rootPath);
206
+ }));
207
+ }
208
+ if (descriptor.union) {
209
+ const { discriminator, variants } = descriptor.union;
210
+ for (const [tag, variant] of variants) applyWhenValue(path, ((value) => value?.[discriminator] === tag), ((variantPath) => {
211
+ for (const child of variant.children ?? []) applyDescriptorRules(variantPath[child.key], child, rootPath);
212
+ }));
213
+ }
223
214
  }
224
-
225
- /**
226
- * Typed registry backing {@link helixMeta}. Checked before the zod global
227
- * registry by {@link readHelixMeta}.
228
- */
229
215
  const helixFieldMetaRegistry = z.registry();
230
- /**
231
- * Attaches Helix UI metadata to a Zod schema and returns the **same** instance.
232
- *
233
- * Prefer this over `.meta()`, which clones the schema — metadata must live on
234
- * the exact instance composed into the `z.object()` shape for the walker to
235
- * find it.
236
- *
237
- * @example
238
- * const UserSchema = z.object({
239
- * email: helixMeta(z.email(), { label: 'E-mail', placeholder: 'you@example.com' }),
240
- * });
241
- */
242
216
  function helixMeta(schema, meta) {
243
- helixFieldMetaRegistry.add(schema, meta);
244
- return schema;
217
+ helixFieldMetaRegistry.add(schema, meta);
218
+ return schema;
245
219
  }
246
- const WRAPPER_TYPES$1 = new Set(['optional', 'nullable', 'default', 'readonly', 'nonoptional']);
220
+ const WRAPPER_TYPES$1 = /* @__PURE__ */ new Set([
221
+ "optional",
222
+ "nullable",
223
+ "default",
224
+ "readonly",
225
+ "nonoptional"
226
+ ]);
247
227
  function metaOf(schema) {
248
- const registered = helixFieldMetaRegistry.get(schema);
249
- if (registered)
250
- return registered;
251
- const globalMeta = schema.meta();
252
- if (!globalMeta)
253
- return undefined;
254
- const { title, description, helix, ...rest } = globalMeta;
255
- const mapped = { ...rest };
256
- if (title !== undefined)
257
- mapped.label = title;
258
- if (description !== undefined)
259
- mapped.hint = description;
260
- return { ...mapped, ...helix };
228
+ const registered = helixFieldMetaRegistry.get(schema);
229
+ if (registered) return registered;
230
+ const globalMeta = schema.meta();
231
+ if (!globalMeta) return void 0;
232
+ const { title, description, helix, ...rest } = globalMeta;
233
+ const mapped = { ...rest };
234
+ if (title !== void 0) mapped.label = title;
235
+ if (description !== void 0) mapped.hint = description;
236
+ return {
237
+ ...mapped,
238
+ ...helix
239
+ };
261
240
  }
262
- /**
263
- * Reads {@link HelixFieldMeta} from a Zod schema: the typed registry first,
264
- * then the zod global registry (`.meta()` — `title` → `label`,
265
- * `description` → `hint`, `helix` key merged on top).
266
- *
267
- * Wrapper types (`optional`, `nullable`, `default`, `readonly`) are unwrapped
268
- * and their metadata merged — outermost wins.
269
- */
270
241
  function readHelixMeta(schema) {
271
- const layers = [];
272
- let current = schema;
273
- while (current) {
274
- const meta = metaOf(current);
275
- if (meta)
276
- layers.push(meta);
277
- const def = current._zod.def;
278
- current = WRAPPER_TYPES$1.has(def.type) ? current.unwrap() : undefined;
279
- }
280
- // Innermost first so outer layers overwrite inner ones.
281
- return layers.reverse().reduce((acc, layer) => ({ ...acc, ...layer }), {});
242
+ const layers = [];
243
+ let current = schema;
244
+ while (current) {
245
+ const meta = metaOf(current);
246
+ if (meta) layers.push(meta);
247
+ const def = current._zod.def;
248
+ current = WRAPPER_TYPES$1.has(def.type) ? current.unwrap() : void 0;
249
+ }
250
+ return layers.reverse().reduce((acc, layer) => ({
251
+ ...acc,
252
+ ...layer
253
+ }), {});
282
254
  }
283
-
284
- const WRAPPER_TYPES = new Set(['optional', 'nullable', 'default', 'readonly', 'nonoptional']);
285
- /** Native `<input type>` per input-based widget kind. */
255
+ const WRAPPER_TYPES = /* @__PURE__ */ new Set([
256
+ "optional",
257
+ "nullable",
258
+ "default",
259
+ "readonly",
260
+ "nonoptional"
261
+ ]);
286
262
  const INPUT_TYPES = {
287
- text: 'text',
288
- email: 'email',
289
- password: 'password',
290
- number: 'number',
291
- date: 'date',
263
+ text: "text",
264
+ email: "email",
265
+ password: "password",
266
+ number: "number",
267
+ date: "date"
292
268
  };
293
269
  function defOf(schema) {
294
- return schema._zod.def;
270
+ return schema._zod.def;
295
271
  }
296
- /** Peels optional/nullable/default/readonly wrappers, tracking requiredness. */
297
272
  function unwrap(schema) {
298
- let inner = schema;
299
- let required = true;
300
- while (WRAPPER_TYPES.has(defOf(inner).type)) {
301
- const type = defOf(inner).type;
302
- if (type === 'optional' || type === 'nullable' || type === 'default')
303
- required = false;
304
- if (type === 'nonoptional')
305
- required = true;
306
- inner = inner.unwrap();
307
- }
308
- return { inner, required };
273
+ let inner = schema;
274
+ let required = true;
275
+ while (WRAPPER_TYPES.has(defOf(inner).type)) {
276
+ const type = defOf(inner).type;
277
+ if (type === "optional" || type === "nullable" || type === "default") required = false;
278
+ if (type === "nonoptional") required = true;
279
+ inner = inner.unwrap();
280
+ }
281
+ return {
282
+ inner,
283
+ required
284
+ };
309
285
  }
310
286
  function normalizeOptions(options) {
311
- if (!options)
312
- return undefined;
313
- return options.map((o) => typeof o === 'string' ? { label: o, value: o } : o);
287
+ if (!options) return void 0;
288
+ return options.map((o) => typeof o === "string" ? {
289
+ label: o,
290
+ value: o
291
+ } : o);
314
292
  }
315
293
  function inferWidget(inner) {
316
- const def = defOf(inner);
317
- switch (def.type) {
318
- case 'string':
319
- return def['format'] === 'email' ? 'email' : 'text';
320
- case 'number':
321
- case 'int':
322
- return 'number';
323
- case 'boolean':
324
- return 'checkbox';
325
- case 'date':
326
- return 'date';
327
- case 'enum':
328
- case 'literal':
329
- return 'select';
330
- case 'array':
331
- return 'array';
332
- case 'object':
333
- return 'object';
334
- case 'union':
335
- return def['discriminator'] !== undefined ? 'union' : null;
336
- default:
337
- return null;
338
- }
294
+ const def = defOf(inner);
295
+ switch (def.type) {
296
+ case "string": return def["format"] === "email" ? "email" : "text";
297
+ case "number":
298
+ case "int": return "number";
299
+ case "boolean": return "checkbox";
300
+ case "date": return "date";
301
+ case "enum":
302
+ case "literal": return "select";
303
+ case "array": return "array";
304
+ case "object": return "object";
305
+ case "union": return def["discriminator"] !== void 0 ? "union" : null;
306
+ default: return null;
307
+ }
339
308
  }
340
309
  function enumOptions(inner) {
341
- const def = defOf(inner);
342
- if (def.type === 'enum') {
343
- const values = inner.options;
344
- return values.map((v) => ({ label: String(v), value: v }));
345
- }
346
- if (def.type === 'literal') {
347
- const values = def['values'];
348
- return values.map((v) => ({ label: String(v), value: v }));
349
- }
350
- return undefined;
310
+ const def = defOf(inner);
311
+ if (def.type === "enum") return inner.options.map((v) => ({
312
+ label: String(v),
313
+ value: v
314
+ }));
315
+ if (def.type === "literal") return def["values"].map((v) => ({
316
+ label: String(v),
317
+ value: v
318
+ }));
351
319
  }
352
- /** Reads the discriminator literal of a union variant's tag field. */
353
320
  function discriminatorValue(variant, discriminator) {
354
- const shape = variant.shape;
355
- const tagDef = defOf(unwrap(shape[discriminator]).inner);
356
- const values = tagDef['values'];
357
- if (!values?.length) {
358
- throw new Error(`[zodToFieldDescriptors] Discriminated union variant has no literal value for ` +
359
- `discriminator "${discriminator}".`);
360
- }
361
- return values[0];
321
+ const shape = variant.shape;
322
+ const values = defOf(unwrap(shape[discriminator]).inner)["values"];
323
+ if (!values?.length) throw new Error(`[zodToFieldDescriptors] Discriminated union variant has no literal value for discriminator "${discriminator}".`);
324
+ return values[0];
362
325
  }
363
326
  function walk(schema, key, path) {
364
- const meta = readHelixMeta(schema);
365
- const { inner, required } = unwrap(schema);
366
- const def = defOf(inner);
367
- const inferred = inferWidget(inner);
368
- const widget = meta.widget ?? inferred;
369
- if (widget === null || widget === undefined) {
370
- if (typeof ngDevMode !== 'undefined' && ngDevMode) {
371
- throw new Error(`[zodToFieldDescriptors] Zod type "${def.type}" at path "${path.join('.')}" has no ` +
372
- `automatic widget mapping. Set { widget: '...' } via helixMeta() to render it with a ` +
373
- `registered (custom) widget.`);
374
- }
375
- // Production: render nothing rather than crash — fall through as plain text.
376
- return {
377
- key,
378
- path,
379
- widget: 'text',
380
- meta,
381
- zodType: inner,
382
- zodSource: schema,
383
- required,
384
- inputType: 'text',
385
- };
386
- }
387
- const descriptor = {
388
- key,
389
- path,
390
- widget,
391
- meta,
392
- zodType: inner,
393
- zodSource: schema,
394
- required,
395
- inputType: INPUT_TYPES[widget],
396
- options: normalizeOptions(meta.options) ?? enumOptions(inner),
397
- };
398
- if (def.type === 'object') {
399
- const shape = inner.shape;
400
- const children = Object.entries(shape).map(([childKey, childSchema], index) => ({
401
- index,
402
- descriptor: walk(childSchema, childKey, [...path, childKey]),
403
- }));
404
- children.sort((a, b) => (a.descriptor.meta.order ?? Number.MAX_SAFE_INTEGER) -
405
- (b.descriptor.meta.order ?? Number.MAX_SAFE_INTEGER) || a.index - b.index);
406
- descriptor.children = children.map((c) => c.descriptor);
407
- }
408
- if (def.type === 'array') {
409
- descriptor.itemDescriptor = walk(def['element'], '', path);
410
- }
411
- if (widget === 'union' && def.type === 'union') {
412
- const discriminator = def['discriminator'];
413
- const variants = new Map();
414
- for (const variant of inner.options) {
415
- variants.set(discriminatorValue(variant, discriminator), walk(variant, key, path));
416
- }
417
- descriptor.union = { discriminator, variants };
418
- }
419
- return descriptor;
327
+ const meta = readHelixMeta(schema);
328
+ const { inner, required } = unwrap(schema);
329
+ const def = defOf(inner);
330
+ const inferred = inferWidget(inner);
331
+ const widget = meta.widget ?? inferred;
332
+ if (widget === null || widget === void 0) {
333
+ if (typeof ngDevMode !== "undefined" && ngDevMode) throw new Error(`[zodToFieldDescriptors] Zod type "${def.type}" at path "${path.join(".")}" has no automatic widget mapping. Set { widget: '...' } via helixMeta() to render it with a registered (custom) widget.`);
334
+ return {
335
+ key,
336
+ path,
337
+ widget: "text",
338
+ meta,
339
+ zodType: inner,
340
+ zodSource: schema,
341
+ required,
342
+ inputType: "text"
343
+ };
344
+ }
345
+ const descriptor = {
346
+ key,
347
+ path,
348
+ widget,
349
+ meta,
350
+ zodType: inner,
351
+ zodSource: schema,
352
+ required,
353
+ inputType: INPUT_TYPES[widget],
354
+ options: normalizeOptions(meta.options) ?? enumOptions(inner)
355
+ };
356
+ if (def.type === "object") {
357
+ const shape = inner.shape;
358
+ const children = Object.entries(shape).map(([childKey, childSchema], index) => ({
359
+ index,
360
+ descriptor: walk(childSchema, childKey, [...path, childKey])
361
+ }));
362
+ children.sort((a, b) => (a.descriptor.meta.order ?? Number.MAX_SAFE_INTEGER) - (b.descriptor.meta.order ?? Number.MAX_SAFE_INTEGER) || a.index - b.index);
363
+ descriptor.children = children.map((c) => c.descriptor);
364
+ }
365
+ if (def.type === "array") descriptor.itemDescriptor = walk(def["element"], "", path);
366
+ if (widget === "union" && def.type === "union") {
367
+ const discriminator = def["discriminator"];
368
+ const variants = /* @__PURE__ */ new Map();
369
+ for (const variant of inner.options) variants.set(discriminatorValue(variant, discriminator), walk(variant, key, path));
370
+ descriptor.union = {
371
+ discriminator,
372
+ variants
373
+ };
374
+ }
375
+ return descriptor;
420
376
  }
421
- /**
422
- * Walks an annotated `z.object()` schema and produces the recursive
423
- * {@link HelixFieldDescriptor} tree consumed by the dynamic form renderer.
424
- *
425
- * Widget inference: string→text (`z.email()`→email), number→number,
426
- * boolean→checkbox, date→date, enum/literal→select, array→array,
427
- * object→object, discriminated union→union. Anything else requires a
428
- * `widget` override in {@link HelixFieldMeta} (throws in dev mode otherwise).
429
- */
430
377
  function zodToFieldDescriptors(schema) {
431
- return walk(schema, '', []);
432
- }
433
-
434
- /**
435
- * Renders a complete signal form from an annotated Zod object schema.
436
- *
437
- * - Validation: root-level `validateStandardSchema` — Zod issues are routed to
438
- * the matching fields.
439
- * - Model: pass your own `WritableSignal` via `model`, or let the component
440
- * derive an initial value from the schema (`buildDefaultValue`).
441
- * - Swapping the `schema` input recreates the form and resets its state.
442
- * - `submitted` emits the **parsed** (`schema.parse`) value on valid submit.
443
- *
444
- * Requires `provideHelixDynamicForms()` in the injector chain.
445
- */
446
- class HelixDynamicForm {
447
- schema = input.required(/* @ts-ignore */
448
- ...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
449
- /** Optional external model — omit to derive the initial value from the schema. */
450
- model = input(/* @ts-ignore */
451
- ...(ngDevMode ? [undefined, { debugName: "model" }] : /* istanbul ignore next */ []));
452
- submitLabel = input('Submit', /* @ts-ignore */
453
- ...(ngDevMode ? [{ debugName: "submitLabel" }] : /* istanbul ignore next */ []));
454
- showSubmit = input(true, /* @ts-ignore */
455
- ...(ngDevMode ? [{ debugName: "showSubmit" }] : /* istanbul ignore next */ []));
456
- submitted = output();
457
- #injector = inject(Injector);
458
- descriptors = computed(() => zodToFieldDescriptors(this.schema()), /* @ts-ignore */
459
- ...(ngDevMode ? [{ debugName: "descriptors" }] : /* istanbul ignore next */ []));
460
- #model = computed(() => this.model() ?? signal(buildDefaultValue(this.schema())), /* @ts-ignore */
461
- ...(ngDevMode ? [{ debugName: "#model" }] : /* istanbul ignore next */ []));
462
- /** The form's `FieldTree` — recreated (state reset) when the schema changes. */
463
- formTree = computed(() => {
464
- const model = this.#model();
465
- const schema = this.schema();
466
- const root = this.descriptors();
467
- return untracked(() => form(model, buildHelixSchema(schema, root), { injector: this.#injector }));
468
- }, /* @ts-ignore */
469
- ...(ngDevMode ? [{ debugName: "formTree" }] : /* istanbul ignore next */ []));
470
- childField(descriptor) {
471
- return this.formTree()[descriptor.key];
472
- }
473
- async handleSubmit(event) {
474
- event.preventDefault();
475
- await submit(this.formTree(), async (tree) => {
476
- this.submitted.emit(this.schema().parse(nullsToAbsent(this.schema(), tree().value())));
477
- return undefined;
478
- });
479
- }
480
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
481
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: HelixDynamicForm, isStandalone: true, selector: "helix-dynamic-form", inputs: { schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null }, model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, submitLabel: { classPropertyName: "submitLabel", publicName: "submitLabel", isSignal: true, isRequired: false, transformFunction: null }, showSubmit: { classPropertyName: "showSubmit", publicName: "showSubmit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { submitted: "submitted" }, ngImport: i0, template: "<form class=\"helix-dynamic-form\" novalidate (submit)=\"handleSubmit($event)\">\n @for (child of descriptors().children ?? []; track child.key) {\n <helix-dynamic-field [field]=\"childField(child)\" [descriptor]=\"child\" />\n }\n\n @if (showSubmit()) {\n <button\n type=\"submit\"\n class=\"helix-dynamic-form__submit\"\n [disabled]=\"formTree()().submitting()\"\n >\n {{ submitLabel() }}\n </button>\n }\n</form>\n", styles: [":host{display:block;width:100%}.helix-dynamic-form{display:flex;flex-direction:column;gap:1rem}.helix-dynamic-form__submit{align-self:flex-start;padding:.5rem 1.25rem;font-family:inherit;font-size:1rem;color:var(--h-primary-contrast-color);background-color:var(--h-primary-color);border:1px solid var(--h-primary-color);border-radius:var(--h-border-radius-md);cursor:pointer;transition:background-color .2s}.helix-dynamic-form__submit:hover:not(:disabled){background-color:var(--h-primary-hover-color)}.helix-dynamic-form__submit:disabled{cursor:default;opacity:.6}\n"], dependencies: [{ kind: "component", type: HelixDynamicField, selector: "helix-dynamic-field", inputs: ["field", "descriptor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
378
+ return walk(schema, "", []);
482
379
  }
483
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDynamicForm, decorators: [{
484
- type: Component,
485
- args: [{ selector: 'helix-dynamic-form', standalone: true, imports: [HelixDynamicField], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"helix-dynamic-form\" novalidate (submit)=\"handleSubmit($event)\">\n @for (child of descriptors().children ?? []; track child.key) {\n <helix-dynamic-field [field]=\"childField(child)\" [descriptor]=\"child\" />\n }\n\n @if (showSubmit()) {\n <button\n type=\"submit\"\n class=\"helix-dynamic-form__submit\"\n [disabled]=\"formTree()().submitting()\"\n >\n {{ submitLabel() }}\n </button>\n }\n</form>\n", styles: [":host{display:block;width:100%}.helix-dynamic-form{display:flex;flex-direction:column;gap:1rem}.helix-dynamic-form__submit{align-self:flex-start;padding:.5rem 1.25rem;font-family:inherit;font-size:1rem;color:var(--h-primary-contrast-color);background-color:var(--h-primary-color);border:1px solid var(--h-primary-color);border-radius:var(--h-border-radius-md);cursor:pointer;transition:background-color .2s}.helix-dynamic-form__submit:hover:not(:disabled){background-color:var(--h-primary-hover-color)}.helix-dynamic-form__submit:disabled{cursor:default;opacity:.6}\n"] }]
486
- }], propDecorators: { schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: true }] }], model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: false }] }], submitLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitLabel", required: false }] }], showSubmit: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSubmit", required: false }] }], submitted: [{ type: i0.Output, args: ["submitted"] }] } });
487
-
488
- // Mirrors Validators' internal EMPTY_VALUES — defined locally to avoid
489
- // depending on unexported internals of @gravionlabs/helix-core.
490
- const EMPTY_VALUES = ['', null, undefined];
380
+ var HelixDynamicForm = class HelixDynamicForm {
381
+ schema = input.required(...ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []);
382
+ model = input(...ngDevMode ? [void 0, { debugName: "model" }] : /* istanbul ignore next */ []);
383
+ submitLabel = input("Submit", ...ngDevMode ? [{ debugName: "submitLabel" }] : /* istanbul ignore next */ []);
384
+ showSubmit = input(true, ...ngDevMode ? [{ debugName: "showSubmit" }] : /* istanbul ignore next */ []);
385
+ submitted = output();
386
+ #injector = inject(Injector);
387
+ descriptors = computed(() => zodToFieldDescriptors(this.schema()), ...ngDevMode ? [{ debugName: "descriptors" }] : /* istanbul ignore next */ []);
388
+ #model = computed(() => this.model() ?? signal(buildDefaultValue(this.schema())), ...ngDevMode ? [{ debugName: "#model" }] : /* istanbul ignore next */ []);
389
+ formTree = computed(() => {
390
+ const model = this.#model();
391
+ const schema = this.schema();
392
+ const root = this.descriptors();
393
+ return untracked(() => form(model, buildHelixSchema(schema, root), { injector: this.#injector }));
394
+ }, ...ngDevMode ? [{ debugName: "formTree" }] : /* istanbul ignore next */ []);
395
+ childField(descriptor) {
396
+ return this.formTree()[descriptor.key];
397
+ }
398
+ async handleSubmit(event) {
399
+ event.preventDefault();
400
+ await submit(this.formTree(), async (tree) => {
401
+ this.submitted.emit(this.schema().parse(nullsToAbsent(this.schema(), tree().value())));
402
+ });
403
+ }
404
+ static ɵfac = i0.ɵɵngDeclareFactory({
405
+ minVersion: "12.0.0",
406
+ version: "22.2.1",
407
+ ngImport: i0,
408
+ type: HelixDynamicForm,
409
+ deps: [],
410
+ target: i0.ɵɵFactoryTarget.Component
411
+ });
412
+ static ɵcmp = i0.ɵɵngDeclareComponent({
413
+ minVersion: "17.0.0",
414
+ version: "22.2.1",
415
+ type: HelixDynamicForm,
416
+ isStandalone: true,
417
+ selector: "helix-dynamic-form",
418
+ inputs: {
419
+ schema: {
420
+ classPropertyName: "schema",
421
+ publicName: "schema",
422
+ isSignal: true,
423
+ isRequired: true,
424
+ transformFunction: null
425
+ },
426
+ model: {
427
+ classPropertyName: "model",
428
+ publicName: "model",
429
+ isSignal: true,
430
+ isRequired: false,
431
+ transformFunction: null
432
+ },
433
+ submitLabel: {
434
+ classPropertyName: "submitLabel",
435
+ publicName: "submitLabel",
436
+ isSignal: true,
437
+ isRequired: false,
438
+ transformFunction: null
439
+ },
440
+ showSubmit: {
441
+ classPropertyName: "showSubmit",
442
+ publicName: "showSubmit",
443
+ isSignal: true,
444
+ isRequired: false,
445
+ transformFunction: null
446
+ }
447
+ },
448
+ outputs: { submitted: "submitted" },
449
+ ngImport: i0,
450
+ template: "<form class=\"helix-dynamic-form\" novalidate (submit)=\"handleSubmit($event)\">\n @for (child of descriptors().children ?? []; track child.key) {\n <helix-dynamic-field [field]=\"childField(child)\" [descriptor]=\"child\" />\n }\n\n @if (showSubmit()) {\n <button\n type=\"submit\"\n class=\"helix-dynamic-form__submit\"\n [disabled]=\"formTree()().submitting()\"\n >\n {{ submitLabel() }}\n </button>\n }\n</form>\n",
451
+ styles: [":host{display:block;width:100%}.helix-dynamic-form{display:flex;flex-direction:column;gap:1rem}.helix-dynamic-form__submit{align-self:flex-start;padding:.5rem 1.25rem;font-family:inherit;font-size:1rem;color:var(--h-primary-contrast-color);background-color:var(--h-primary-color);border:1px solid var(--h-primary-color);border-radius:var(--h-border-radius-md);cursor:pointer;transition:background-color .2s}.helix-dynamic-form__submit:hover:not(:disabled){background-color:var(--h-primary-hover-color)}.helix-dynamic-form__submit:disabled{cursor:default;opacity:.6}\n"],
452
+ dependencies: [{
453
+ kind: "component",
454
+ type: HelixDynamicField,
455
+ selector: "helix-dynamic-field",
456
+ inputs: ["field", "descriptor"]
457
+ }],
458
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
459
+ });
460
+ };
461
+ i0.ɵɵngDeclareClassMetadata({
462
+ minVersion: "12.0.0",
463
+ version: "22.2.1",
464
+ ngImport: i0,
465
+ type: HelixDynamicForm,
466
+ decorators: [{
467
+ type: Component,
468
+ args: [{
469
+ selector: "helix-dynamic-form",
470
+ standalone: true,
471
+ imports: [HelixDynamicField],
472
+ changeDetection: ChangeDetectionStrategy.OnPush,
473
+ template: "<form class=\"helix-dynamic-form\" novalidate (submit)=\"handleSubmit($event)\">\n @for (child of descriptors().children ?? []; track child.key) {\n <helix-dynamic-field [field]=\"childField(child)\" [descriptor]=\"child\" />\n }\n\n @if (showSubmit()) {\n <button\n type=\"submit\"\n class=\"helix-dynamic-form__submit\"\n [disabled]=\"formTree()().submitting()\"\n >\n {{ submitLabel() }}\n </button>\n }\n</form>\n",
474
+ styles: [":host{display:block;width:100%}.helix-dynamic-form{display:flex;flex-direction:column;gap:1rem}.helix-dynamic-form__submit{align-self:flex-start;padding:.5rem 1.25rem;font-family:inherit;font-size:1rem;color:var(--h-primary-contrast-color);background-color:var(--h-primary-color);border:1px solid var(--h-primary-color);border-radius:var(--h-border-radius-md);cursor:pointer;transition:background-color .2s}.helix-dynamic-form__submit:hover:not(:disabled){background-color:var(--h-primary-hover-color)}.helix-dynamic-form__submit:disabled{cursor:default;opacity:.6}\n"]
475
+ }]
476
+ }],
477
+ propDecorators: {
478
+ schema: [{
479
+ type: i0.Input,
480
+ args: [{
481
+ isSignal: true,
482
+ alias: "schema",
483
+ required: true
484
+ }]
485
+ }],
486
+ model: [{
487
+ type: i0.Input,
488
+ args: [{
489
+ isSignal: true,
490
+ alias: "model",
491
+ required: false
492
+ }]
493
+ }],
494
+ submitLabel: [{
495
+ type: i0.Input,
496
+ args: [{
497
+ isSignal: true,
498
+ alias: "submitLabel",
499
+ required: false
500
+ }]
501
+ }],
502
+ showSubmit: [{
503
+ type: i0.Input,
504
+ args: [{
505
+ isSignal: true,
506
+ alias: "showSubmit",
507
+ required: false
508
+ }]
509
+ }],
510
+ submitted: [{
511
+ type: i0.Output,
512
+ args: ["submitted"]
513
+ }]
514
+ }
515
+ });
516
+ const EMPTY_VALUES = [
517
+ "",
518
+ null,
519
+ void 0
520
+ ];
491
521
  const isEmptyValue = (v) => EMPTY_VALUES.includes(v);
492
- /**
493
- * Maps a single `ZodIssue` to the corresponding `ValidatorKey`.
494
- *
495
- * NOTE: Zod v4 breaking changes accounted for here:
496
- * - `invalid_string` → `invalid_format` with `format` property (not `validation`)
497
- * - `too_small`/`too_big` use `origin` instead of `type`
498
- * - `not_integer` removed → `invalid_type` with `expected: 'int'`
499
- * - `invalid_type` for null/undefined no longer has a `received` field;
500
- * detected via the `value` parameter instead
501
- *
502
- * Returns `null` for issue codes with no automatic mapping — caller handles
503
- * these via `fallbackKey` (Option B).
504
- */
505
522
  function zodIssueToHelixKey(issue, value) {
506
- switch (issue.code) {
507
- case ZodIssueCode.invalid_type:
508
- // Zod v4 dropped the `received` field on invalid_type issues.
509
- // Detect required (null/undefined) errors by checking the control value directly.
510
- if (isEmptyValue(value))
511
- return ValidatorKey.Required;
512
- // integer: Zod v4 changed not_integer → invalid_type { expected: 'int' }
513
- if (issue.expected === 'int')
514
- return ValidatorKey.Integer;
515
- // numeric type mismatch
516
- if (issue.expected === 'number' ||
517
- issue.expected === 'float') {
518
- return ValidatorKey.Number;
519
- }
520
- return null;
521
- case ZodIssueCode.invalid_format:
522
- // Zod v4: replaces invalid_string for all format-based string validations.
523
- // The `format` property holds the validation name.
524
- {
525
- const fmt = issue.format;
526
- if (fmt === 'email')
527
- return ValidatorKey.Email;
528
- if (fmt === 'regex')
529
- return ValidatorKey.Pattern;
530
- if (fmt === 'datetime' || fmt === 'date' || fmt === 'time')
531
- return ValidatorKey.Date;
532
- }
533
- return null;
534
- case ZodIssueCode.too_small:
535
- // Zod v4: uses `origin` instead of `type` to identify the validated construct.
536
- {
537
- const origin = issue.origin;
538
- if (origin === 'string' || origin === 'array')
539
- return ValidatorKey.MinLength;
540
- if (origin === 'number')
541
- return ValidatorKey.Min;
542
- }
543
- return null;
544
- case ZodIssueCode.too_big:
545
- {
546
- const origin = issue.origin;
547
- if (origin === 'string' || origin === 'array')
548
- return ValidatorKey.MaxLength;
549
- if (origin === 'number')
550
- return ValidatorKey.Max;
551
- }
552
- return null;
553
- case ZodIssueCode.custom:
554
- return null; // must be handled via fallbackKey (Option B)
555
- default:
556
- return null;
557
- }
523
+ switch (issue.code) {
524
+ case ZodIssueCode.invalid_type:
525
+ if (isEmptyValue(value)) return ValidatorKey.Required;
526
+ if (issue.expected === "int") return ValidatorKey.Integer;
527
+ if (issue.expected === "number" || issue.expected === "float") return ValidatorKey.Number;
528
+ return null;
529
+ case ZodIssueCode.invalid_format:
530
+ {
531
+ const fmt = issue.format;
532
+ if (fmt === "email") return ValidatorKey.Email;
533
+ if (fmt === "regex") return ValidatorKey.Pattern;
534
+ if (fmt === "datetime" || fmt === "date" || fmt === "time") return ValidatorKey.Date;
535
+ }
536
+ return null;
537
+ case ZodIssueCode.too_small:
538
+ {
539
+ const origin = issue.origin;
540
+ if (origin === "string" || origin === "array") return ValidatorKey.MinLength;
541
+ if (origin === "number") return ValidatorKey.Min;
542
+ }
543
+ return null;
544
+ case ZodIssueCode.too_big:
545
+ {
546
+ const origin = issue.origin;
547
+ if (origin === "string" || origin === "array") return ValidatorKey.MaxLength;
548
+ if (origin === "number") return ValidatorKey.Max;
549
+ }
550
+ return null;
551
+ case ZodIssueCode.custom: return null;
552
+ default: return null;
553
+ }
558
554
  }
559
-
560
- /** Built-in signal-forms error kinds → ValidatorKey. */
561
555
  const NG_KIND_TO_HELIX = {
562
- required: ValidatorKey.Required,
563
- min: ValidatorKey.Min,
564
- max: ValidatorKey.Max,
565
- minLength: ValidatorKey.MinLength,
566
- maxLength: ValidatorKey.MaxLength,
567
- pattern: ValidatorKey.Pattern,
568
- email: ValidatorKey.Email,
556
+ required: ValidatorKey.Required,
557
+ min: ValidatorKey.Min,
558
+ max: ValidatorKey.Max,
559
+ minLength: ValidatorKey.MinLength,
560
+ maxLength: ValidatorKey.MaxLength,
561
+ pattern: ValidatorKey.Pattern,
562
+ email: ValidatorKey.Email
569
563
  };
570
- /**
571
- * Maps a signal-forms `ValidationError` to the `ValidatorKey` message
572
- * convention shared with `HelixZodValidators.fromZod`.
573
- *
574
- * @param error The signal-forms validation error.
575
- * @param value The current field value — used to distinguish Required from
576
- * type errors on Zod `invalid_type` issues (Zod v4 dropped the
577
- * `received` field).
578
- */
579
564
  function helixErrorKey(error, value) {
580
- if (error.kind === 'standardSchema') {
581
- const issue = error.issue;
582
- return zodIssueToHelixKey(issue, value);
583
- }
584
- return NG_KIND_TO_HELIX[error.kind] ?? null;
565
+ if (error.kind === "standardSchema") {
566
+ const issue = error.issue;
567
+ return zodIssueToHelixKey(issue, value);
568
+ }
569
+ return NG_KIND_TO_HELIX[error.kind] ?? null;
585
570
  }
586
571
  function defaultMessage(error) {
587
- if (error.kind === 'standardSchema') {
588
- const issue = error.issue;
589
- return issue.message ?? null;
590
- }
591
- return error.message ?? null;
572
+ if (error.kind === "standardSchema") return error.issue.message ?? null;
573
+ return error.message ?? null;
592
574
  }
593
- /**
594
- * Returns the message of the first displayable error, mirroring
595
- * `HelixFormField`'s first-error convention.
596
- *
597
- * Order of precedence per error: `resolver` result → Zod issue message /
598
- * signal-forms error message. Errors yielding no message are skipped.
599
- */
600
575
  function helixFirstErrorMessage(errors, options = {}) {
601
- for (const error of errors) {
602
- const resolved = options.resolver?.(error, helixErrorKey(error, options.value));
603
- const message = resolved ?? defaultMessage(error);
604
- if (message)
605
- return message;
606
- }
607
- return null;
608
- }
609
-
610
- /**
611
- * Base class for dynamic-form widgets. A widget is a standalone component with
612
- * the two inputs below; `HelixDynamicField` instantiates it via
613
- * `NgComponentOutlet` and feeds them in.
614
- */
615
- class HelixFieldWidgetBase {
616
- field = input.required(/* @ts-ignore */
617
- ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
618
- descriptor = input.required(/* @ts-ignore */
619
- ...(ngDevMode ? [{ debugName: "descriptor" }] : /* istanbul ignore next */ []));
620
- #errorResolver = inject(HELIX_ERROR_MESSAGE_RESOLVER, { optional: true });
621
- state = computed(() => this.field()(), /* @ts-ignore */
622
- ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
623
- /** Touched-gated first error message — mirrors `HelixFormField.activeError`. */
624
- firstError = computed(() => {
625
- const state = this.state();
626
- if (!state.touched())
627
- return null;
628
- return helixFirstErrorMessage(state.errors(), {
629
- value: state.value(),
630
- resolver: this.#errorResolver ?? undefined,
631
- });
632
- }, /* @ts-ignore */
633
- ...(ngDevMode ? [{ debugName: "firstError" }] : /* istanbul ignore next */ []));
634
- label = computed(() => this.descriptor().meta.label ?? humanize(this.descriptor().key), /* @ts-ignore */
635
- ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
636
- hint = computed(() => this.descriptor().meta.hint, /* @ts-ignore */
637
- ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
638
- placeholder = computed(() => this.descriptor().meta.placeholder ?? '', /* @ts-ignore */
639
- ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
640
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixFieldWidgetBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
641
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: HelixFieldWidgetBase, isStandalone: true, inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, descriptor: { classPropertyName: "descriptor", publicName: "descriptor", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
576
+ for (const error of errors) {
577
+ const message = options.resolver?.(error, helixErrorKey(error, options.value)) ?? defaultMessage(error);
578
+ if (message) return message;
579
+ }
580
+ return null;
642
581
  }
643
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixFieldWidgetBase, decorators: [{
644
- type: Directive
645
- }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], descriptor: [{ type: i0.Input, args: [{ isSignal: true, alias: "descriptor", required: true }] }] } });
646
- /** `firstName` → `First name` — fallback label when meta provides none. */
582
+ var HelixFieldWidgetBase = class HelixFieldWidgetBase {
583
+ field = input.required(...ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []);
584
+ descriptor = input.required(...ngDevMode ? [{ debugName: "descriptor" }] : /* istanbul ignore next */ []);
585
+ #errorResolver = inject(HELIX_ERROR_MESSAGE_RESOLVER, { optional: true });
586
+ state = computed(() => this.field()(), ...ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []);
587
+ firstError = computed(() => {
588
+ const state = this.state();
589
+ if (!state.touched()) return null;
590
+ return helixFirstErrorMessage(state.errors(), {
591
+ value: state.value(),
592
+ resolver: this.#errorResolver ?? void 0
593
+ });
594
+ }, ...ngDevMode ? [{ debugName: "firstError" }] : /* istanbul ignore next */ []);
595
+ label = computed(() => this.descriptor().meta.label ?? humanize(this.descriptor().key), ...ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []);
596
+ hint = computed(() => this.descriptor().meta.hint, ...ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []);
597
+ placeholder = computed(() => this.descriptor().meta.placeholder ?? "", ...ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []);
598
+ static ɵfac = i0.ɵɵngDeclareFactory({
599
+ minVersion: "12.0.0",
600
+ version: "22.2.1",
601
+ ngImport: i0,
602
+ type: HelixFieldWidgetBase,
603
+ deps: [],
604
+ target: i0.ɵɵFactoryTarget.Directive
605
+ });
606
+ static ɵdir = i0.ɵɵngDeclareDirective({
607
+ minVersion: "17.1.0",
608
+ version: "22.2.1",
609
+ type: HelixFieldWidgetBase,
610
+ isStandalone: true,
611
+ inputs: {
612
+ field: {
613
+ classPropertyName: "field",
614
+ publicName: "field",
615
+ isSignal: true,
616
+ isRequired: true,
617
+ transformFunction: null
618
+ },
619
+ descriptor: {
620
+ classPropertyName: "descriptor",
621
+ publicName: "descriptor",
622
+ isSignal: true,
623
+ isRequired: true,
624
+ transformFunction: null
625
+ }
626
+ },
627
+ ngImport: i0
628
+ });
629
+ };
630
+ i0.ɵɵngDeclareClassMetadata({
631
+ minVersion: "12.0.0",
632
+ version: "22.2.1",
633
+ ngImport: i0,
634
+ type: HelixFieldWidgetBase,
635
+ decorators: [{ type: Directive }],
636
+ propDecorators: {
637
+ field: [{
638
+ type: i0.Input,
639
+ args: [{
640
+ isSignal: true,
641
+ alias: "field",
642
+ required: true
643
+ }]
644
+ }],
645
+ descriptor: [{
646
+ type: i0.Input,
647
+ args: [{
648
+ isSignal: true,
649
+ alias: "descriptor",
650
+ required: true
651
+ }]
652
+ }]
653
+ }
654
+ });
647
655
  function humanize(key) {
648
- if (!key)
649
- return '';
650
- const words = key
651
- .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
652
- .replace(/[_-]+/g, ' ')
653
- .toLowerCase();
654
- return words.charAt(0).toUpperCase() + words.slice(1);
655
- }
656
-
657
- /** Built-in widget for `z.array()` fields — item list with add/remove. */
658
- class HelixArrayWidget extends HelixFieldWidgetBase {
659
- itemFields = computed(() => {
660
- const length = (this.state().value() ?? []).length;
661
- return Array.from({ length }, (_, index) => this.field()[index]);
662
- }, /* @ts-ignore */
663
- ...(ngDevMode ? [{ debugName: "itemFields" }] : /* istanbul ignore next */ []));
664
- addLabel = computed(() => this.descriptor().meta.addLabel ?? 'Add', /* @ts-ignore */
665
- ...(ngDevMode ? [{ debugName: "addLabel" }] : /* istanbul ignore next */ []));
666
- removeLabel = computed(() => this.descriptor().meta.removeLabel ?? 'Remove', /* @ts-ignore */
667
- ...(ngDevMode ? [{ debugName: "removeLabel" }] : /* istanbul ignore next */ []));
668
- add() {
669
- const item = this.descriptor().itemDescriptor;
670
- if (!item)
671
- return;
672
- const value = buildDefaultValue(item.zodSource);
673
- this.state().value.update((current) => [...(current ?? []), value]);
674
- }
675
- removeAt(index) {
676
- this.state().value.update((current) => (current ?? []).filter((_, i) => i !== index));
677
- }
678
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixArrayWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
679
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: HelixArrayWidget, isStandalone: true, selector: "helix-array-widget", usesInheritance: true, ngImport: i0, template: "<div class=\"helix-array-widget\">\n @if (label(); as l) {\n <div class=\"helix-array-widget__label\">{{ l }}</div>\n }\n\n @for (itemField of itemFields(); track $index) {\n <div class=\"helix-array-widget__item\">\n <helix-dynamic-field [field]=\"itemField\" [descriptor]=\"descriptor().itemDescriptor!\" />\n <button\n type=\"button\"\n class=\"helix-array-widget__remove\"\n (click)=\"removeAt($index)\"\n >\n {{ removeLabel() }}\n </button>\n </div>\n }\n\n <button type=\"button\" class=\"helix-array-widget__add\" (click)=\"add()\">{{ addLabel() }}</button>\n\n @if (firstError(); as err) {\n <small class=\"helix-array-widget__message helix-array-widget__message--error\">{{ err }}</small>\n } @else if (hint(); as h) {\n <small class=\"helix-array-widget__message helix-array-widget__message--hint\">{{ h }}</small>\n }\n</div>\n", styles: [":host{display:block;width:100%}.helix-array-widget{display:flex;flex-direction:column;gap:.75rem}.helix-array-widget__label{font-size:.875rem;font-weight:500;color:var(--h-text-color)}.helix-array-widget__item{display:flex;align-items:flex-start;gap:.5rem}.helix-array-widget__item helix-dynamic-field{flex:1}.helix-array-widget__add,.helix-array-widget__remove{padding:.375rem .75rem;font-family:inherit;font-size:.875rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);cursor:pointer;transition:border-color .2s}.helix-array-widget__add:hover,.helix-array-widget__remove:hover{border-color:var(--h-primary-color)}.helix-array-widget__add{align-self:flex-start}.helix-array-widget__message{font-size:.75rem}.helix-array-widget__message--hint{color:var(--h-text-muted-color)}.helix-array-widget__message--error{color:var(--h-red-500)}\n"], dependencies: [{ kind: "component", type: HelixDynamicField, selector: "helix-dynamic-field", inputs: ["field", "descriptor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
680
- }
681
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixArrayWidget, decorators: [{
682
- type: Component,
683
- args: [{ selector: 'helix-array-widget', standalone: true, imports: [HelixDynamicField], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"helix-array-widget\">\n @if (label(); as l) {\n <div class=\"helix-array-widget__label\">{{ l }}</div>\n }\n\n @for (itemField of itemFields(); track $index) {\n <div class=\"helix-array-widget__item\">\n <helix-dynamic-field [field]=\"itemField\" [descriptor]=\"descriptor().itemDescriptor!\" />\n <button\n type=\"button\"\n class=\"helix-array-widget__remove\"\n (click)=\"removeAt($index)\"\n >\n {{ removeLabel() }}\n </button>\n </div>\n }\n\n <button type=\"button\" class=\"helix-array-widget__add\" (click)=\"add()\">{{ addLabel() }}</button>\n\n @if (firstError(); as err) {\n <small class=\"helix-array-widget__message helix-array-widget__message--error\">{{ err }}</small>\n } @else if (hint(); as h) {\n <small class=\"helix-array-widget__message helix-array-widget__message--hint\">{{ h }}</small>\n }\n</div>\n", styles: [":host{display:block;width:100%}.helix-array-widget{display:flex;flex-direction:column;gap:.75rem}.helix-array-widget__label{font-size:.875rem;font-weight:500;color:var(--h-text-color)}.helix-array-widget__item{display:flex;align-items:flex-start;gap:.5rem}.helix-array-widget__item helix-dynamic-field{flex:1}.helix-array-widget__add,.helix-array-widget__remove{padding:.375rem .75rem;font-family:inherit;font-size:.875rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);cursor:pointer;transition:border-color .2s}.helix-array-widget__add:hover,.helix-array-widget__remove:hover{border-color:var(--h-primary-color)}.helix-array-widget__add{align-self:flex-start}.helix-array-widget__message{font-size:.75rem}.helix-array-widget__message--hint{color:var(--h-text-muted-color)}.helix-array-widget__message--error{color:var(--h-red-500)}\n"] }]
684
- }] });
685
-
686
- /** Built-in widget for boolean fields — checkbox with inline label. */
687
- class HelixCheckboxWidget extends HelixFieldWidgetBase {
688
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixCheckboxWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
689
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: HelixCheckboxWidget, isStandalone: true, selector: "helix-checkbox-widget", usesInheritance: true, ngImport: i0, template: "<div class=\"helix-checkbox-widget\">\n <label class=\"helix-checkbox-widget__label\">\n <input class=\"helix-checkbox-widget__input\" type=\"checkbox\" [formField]=\"field()\" />\n <span>{{ label() }}</span>\n </label>\n\n @if (firstError(); as err) {\n <small class=\"helix-checkbox-widget__message helix-checkbox-widget__message--error\">{{ err }}</small>\n } @else if (hint(); as h) {\n <small class=\"helix-checkbox-widget__message helix-checkbox-widget__message--hint\">{{ h }}</small>\n }\n</div>\n", styles: [":host{display:block;width:100%}.helix-checkbox-widget{display:flex;flex-direction:column;gap:.5rem}.helix-checkbox-widget__label{display:inline-flex;align-items:center;gap:.5rem;font-size:.875rem;color:var(--h-text-color);cursor:pointer}.helix-checkbox-widget__input{accent-color:var(--h-primary-color);width:1rem;height:1rem}.helix-checkbox-widget__message{font-size:.75rem}.helix-checkbox-widget__message--hint{color:var(--h-text-muted-color)}.helix-checkbox-widget__message--error{color:var(--h-red-500)}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
656
+ if (!key) return "";
657
+ const words = key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase();
658
+ return words.charAt(0).toUpperCase() + words.slice(1);
690
659
  }
691
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixCheckboxWidget, decorators: [{
692
- type: Component,
693
- args: [{ selector: 'helix-checkbox-widget', standalone: true, imports: [FormField], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"helix-checkbox-widget\">\n <label class=\"helix-checkbox-widget__label\">\n <input class=\"helix-checkbox-widget__input\" type=\"checkbox\" [formField]=\"field()\" />\n <span>{{ label() }}</span>\n </label>\n\n @if (firstError(); as err) {\n <small class=\"helix-checkbox-widget__message helix-checkbox-widget__message--error\">{{ err }}</small>\n } @else if (hint(); as h) {\n <small class=\"helix-checkbox-widget__message helix-checkbox-widget__message--hint\">{{ h }}</small>\n }\n</div>\n", styles: [":host{display:block;width:100%}.helix-checkbox-widget{display:flex;flex-direction:column;gap:.5rem}.helix-checkbox-widget__label{display:inline-flex;align-items:center;gap:.5rem;font-size:.875rem;color:var(--h-text-color);cursor:pointer}.helix-checkbox-widget__input{accent-color:var(--h-primary-color);width:1rem;height:1rem}.helix-checkbox-widget__message{font-size:.75rem}.helix-checkbox-widget__message--hint{color:var(--h-text-muted-color)}.helix-checkbox-widget__message--error{color:var(--h-red-500)}\n"] }]
694
- }] });
695
-
660
+ var HelixArrayWidget = class HelixArrayWidget extends HelixFieldWidgetBase {
661
+ itemFields = computed(() => {
662
+ const length = (this.state().value() ?? []).length;
663
+ return Array.from({ length }, (_, index) => this.field()[index]);
664
+ }, ...ngDevMode ? [{ debugName: "itemFields" }] : /* istanbul ignore next */ []);
665
+ addLabel = computed(() => this.descriptor().meta.addLabel ?? "Add", ...ngDevMode ? [{ debugName: "addLabel" }] : /* istanbul ignore next */ []);
666
+ removeLabel = computed(() => this.descriptor().meta.removeLabel ?? "Remove", ...ngDevMode ? [{ debugName: "removeLabel" }] : /* istanbul ignore next */ []);
667
+ add() {
668
+ const item = this.descriptor().itemDescriptor;
669
+ if (!item) return;
670
+ const value = buildDefaultValue(item.zodSource);
671
+ this.state().value.update((current) => [...current ?? [], value]);
672
+ }
673
+ removeAt(index) {
674
+ this.state().value.update((current) => (current ?? []).filter((_, i) => i !== index));
675
+ }
676
+ static ɵfac = i0.ɵɵngDeclareFactory({
677
+ minVersion: "12.0.0",
678
+ version: "22.2.1",
679
+ ngImport: i0,
680
+ type: HelixArrayWidget,
681
+ deps: null,
682
+ target: i0.ɵɵFactoryTarget.Component
683
+ });
684
+ static ɵcmp = i0.ɵɵngDeclareComponent({
685
+ minVersion: "17.0.0",
686
+ version: "22.2.1",
687
+ type: HelixArrayWidget,
688
+ isStandalone: true,
689
+ selector: "helix-array-widget",
690
+ usesInheritance: true,
691
+ ngImport: i0,
692
+ template: "<div class=\"helix-array-widget\">\n @if (label(); as l) {\n <div class=\"helix-array-widget__label\">{{ l }}</div>\n }\n\n @for (itemField of itemFields(); track $index) {\n <div class=\"helix-array-widget__item\">\n <helix-dynamic-field [field]=\"itemField\" [descriptor]=\"descriptor().itemDescriptor!\" />\n <button\n type=\"button\"\n class=\"helix-array-widget__remove\"\n (click)=\"removeAt($index)\"\n >\n {{ removeLabel() }}\n </button>\n </div>\n }\n\n <button type=\"button\" class=\"helix-array-widget__add\" (click)=\"add()\">{{ addLabel() }}</button>\n\n @if (firstError(); as err) {\n <small class=\"helix-array-widget__message helix-array-widget__message--error\">{{ err }}</small>\n } @else if (hint(); as h) {\n <small class=\"helix-array-widget__message helix-array-widget__message--hint\">{{ h }}</small>\n }\n</div>\n",
693
+ styles: [":host{display:block;width:100%}.helix-array-widget{display:flex;flex-direction:column;gap:.75rem}.helix-array-widget__label{font-size:.875rem;font-weight:500;color:var(--h-text-color)}.helix-array-widget__item{display:flex;align-items:flex-start;gap:.5rem}.helix-array-widget__item helix-dynamic-field{flex:1}.helix-array-widget__add,.helix-array-widget__remove{padding:.375rem .75rem;font-family:inherit;font-size:.875rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);cursor:pointer;transition:border-color .2s}.helix-array-widget__add:hover,.helix-array-widget__remove:hover{border-color:var(--h-primary-color)}.helix-array-widget__add{align-self:flex-start}.helix-array-widget__message{font-size:.75rem}.helix-array-widget__message--hint{color:var(--h-text-muted-color)}.helix-array-widget__message--error{color:var(--h-red-500)}\n"],
694
+ dependencies: [{
695
+ kind: "component",
696
+ type: HelixDynamicField,
697
+ selector: "helix-dynamic-field",
698
+ inputs: ["field", "descriptor"]
699
+ }],
700
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
701
+ });
702
+ };
703
+ i0.ɵɵngDeclareClassMetadata({
704
+ minVersion: "12.0.0",
705
+ version: "22.2.1",
706
+ ngImport: i0,
707
+ type: HelixArrayWidget,
708
+ decorators: [{
709
+ type: Component,
710
+ args: [{
711
+ selector: "helix-array-widget",
712
+ standalone: true,
713
+ imports: [HelixDynamicField],
714
+ changeDetection: ChangeDetectionStrategy.OnPush,
715
+ template: "<div class=\"helix-array-widget\">\n @if (label(); as l) {\n <div class=\"helix-array-widget__label\">{{ l }}</div>\n }\n\n @for (itemField of itemFields(); track $index) {\n <div class=\"helix-array-widget__item\">\n <helix-dynamic-field [field]=\"itemField\" [descriptor]=\"descriptor().itemDescriptor!\" />\n <button\n type=\"button\"\n class=\"helix-array-widget__remove\"\n (click)=\"removeAt($index)\"\n >\n {{ removeLabel() }}\n </button>\n </div>\n }\n\n <button type=\"button\" class=\"helix-array-widget__add\" (click)=\"add()\">{{ addLabel() }}</button>\n\n @if (firstError(); as err) {\n <small class=\"helix-array-widget__message helix-array-widget__message--error\">{{ err }}</small>\n } @else if (hint(); as h) {\n <small class=\"helix-array-widget__message helix-array-widget__message--hint\">{{ h }}</small>\n }\n</div>\n",
716
+ styles: [":host{display:block;width:100%}.helix-array-widget{display:flex;flex-direction:column;gap:.75rem}.helix-array-widget__label{font-size:.875rem;font-weight:500;color:var(--h-text-color)}.helix-array-widget__item{display:flex;align-items:flex-start;gap:.5rem}.helix-array-widget__item helix-dynamic-field{flex:1}.helix-array-widget__add,.helix-array-widget__remove{padding:.375rem .75rem;font-family:inherit;font-size:.875rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);cursor:pointer;transition:border-color .2s}.helix-array-widget__add:hover,.helix-array-widget__remove:hover{border-color:var(--h-primary-color)}.helix-array-widget__add{align-self:flex-start}.helix-array-widget__message{font-size:.75rem}.helix-array-widget__message--hint{color:var(--h-text-muted-color)}.helix-array-widget__message--error{color:var(--h-red-500)}\n"]
717
+ }]
718
+ }]
719
+ });
720
+ var HelixCheckboxWidget = class HelixCheckboxWidget extends HelixFieldWidgetBase {
721
+ static ɵfac = i0.ɵɵngDeclareFactory({
722
+ minVersion: "12.0.0",
723
+ version: "22.2.1",
724
+ ngImport: i0,
725
+ type: HelixCheckboxWidget,
726
+ deps: null,
727
+ target: i0.ɵɵFactoryTarget.Component
728
+ });
729
+ static ɵcmp = i0.ɵɵngDeclareComponent({
730
+ minVersion: "17.0.0",
731
+ version: "22.2.1",
732
+ type: HelixCheckboxWidget,
733
+ isStandalone: true,
734
+ selector: "helix-checkbox-widget",
735
+ usesInheritance: true,
736
+ ngImport: i0,
737
+ template: "<div class=\"helix-checkbox-widget\">\n <label class=\"helix-checkbox-widget__label\">\n <input class=\"helix-checkbox-widget__input\" type=\"checkbox\" [formField]=\"field()\" />\n <span>{{ label() }}</span>\n </label>\n\n @if (firstError(); as err) {\n <small class=\"helix-checkbox-widget__message helix-checkbox-widget__message--error\">{{ err }}</small>\n } @else if (hint(); as h) {\n <small class=\"helix-checkbox-widget__message helix-checkbox-widget__message--hint\">{{ h }}</small>\n }\n</div>\n",
738
+ styles: [":host{display:block;width:100%}.helix-checkbox-widget{display:flex;flex-direction:column;gap:.5rem}.helix-checkbox-widget__label{display:inline-flex;align-items:center;gap:.5rem;font-size:.875rem;color:var(--h-text-color);cursor:pointer}.helix-checkbox-widget__input{accent-color:var(--h-primary-color);width:1rem;height:1rem}.helix-checkbox-widget__message{font-size:.75rem}.helix-checkbox-widget__message--hint{color:var(--h-text-muted-color)}.helix-checkbox-widget__message--error{color:var(--h-red-500)}\n"],
739
+ dependencies: [{
740
+ kind: "directive",
741
+ type: FormField,
742
+ selector: "[formField]",
743
+ inputs: ["formField"],
744
+ exportAs: ["formField"]
745
+ }],
746
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
747
+ });
748
+ };
749
+ i0.ɵɵngDeclareClassMetadata({
750
+ minVersion: "12.0.0",
751
+ version: "22.2.1",
752
+ ngImport: i0,
753
+ type: HelixCheckboxWidget,
754
+ decorators: [{
755
+ type: Component,
756
+ args: [{
757
+ selector: "helix-checkbox-widget",
758
+ standalone: true,
759
+ imports: [FormField],
760
+ changeDetection: ChangeDetectionStrategy.OnPush,
761
+ template: "<div class=\"helix-checkbox-widget\">\n <label class=\"helix-checkbox-widget__label\">\n <input class=\"helix-checkbox-widget__input\" type=\"checkbox\" [formField]=\"field()\" />\n <span>{{ label() }}</span>\n </label>\n\n @if (firstError(); as err) {\n <small class=\"helix-checkbox-widget__message helix-checkbox-widget__message--error\">{{ err }}</small>\n } @else if (hint(); as h) {\n <small class=\"helix-checkbox-widget__message helix-checkbox-widget__message--hint\">{{ h }}</small>\n }\n</div>\n",
762
+ styles: [":host{display:block;width:100%}.helix-checkbox-widget{display:flex;flex-direction:column;gap:.5rem}.helix-checkbox-widget__label{display:inline-flex;align-items:center;gap:.5rem;font-size:.875rem;color:var(--h-text-color);cursor:pointer}.helix-checkbox-widget__input{accent-color:var(--h-primary-color);width:1rem;height:1rem}.helix-checkbox-widget__message{font-size:.75rem}.helix-checkbox-widget__message--hint{color:var(--h-text-muted-color)}.helix-checkbox-widget__message--error{color:var(--h-red-500)}\n"]
763
+ }]
764
+ }]
765
+ });
696
766
  function toIsoDate(value) {
697
- const y = value.getFullYear().toString().padStart(4, '0');
698
- const m = (value.getMonth() + 1).toString().padStart(2, '0');
699
- const d = value.getDate().toString().padStart(2, '0');
700
- return `${y}-${m}-${d}`;
701
- }
702
- /**
703
- * Native `<input type="date">` exposed as a signal-forms `FormValueControl`
704
- * with a `Date | null` model — string↔Date conversion via `transformedValue`.
705
- */
706
- class HelixDateInput {
707
- value = model.required(/* @ts-ignore */
708
- ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
709
- disabled = input(false, /* @ts-ignore */
710
- ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
711
- readonly = input(false, /* @ts-ignore */
712
- ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
713
- touched = model(false, /* @ts-ignore */
714
- ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
715
- rawValue = transformedValue(this.value, {
716
- parse: (raw) => {
717
- if (raw === '')
718
- return { value: null };
719
- const parsed = new Date(`${raw}T00:00:00`);
720
- if (Number.isNaN(parsed.getTime())) {
721
- return { error: { kind: 'parse', message: `"${raw}" is not a valid date` } };
722
- }
723
- return { value: parsed };
724
- },
725
- format: (value) => (value ? toIsoDate(value) : ''),
726
- });
727
- onInput(event) {
728
- this.rawValue.set(event.target.value);
729
- }
730
- onBlur() {
731
- this.touched.set(true);
732
- }
733
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDateInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
734
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: HelixDateInput, isStandalone: true, selector: "helix-date-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, ngImport: i0, template: "<input\n class=\"helix-date-input__input\"\n type=\"date\"\n [value]=\"rawValue()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n/>\n", styles: [":host{display:block;width:100%}.helix-date-input__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-date-input__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-date-input__input:disabled,.helix-date-input__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-date-input__input::placeholder{color:var(--h-text-muted-color)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
735
- }
736
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDateInput, decorators: [{
737
- type: Component,
738
- args: [{ selector: 'helix-date-input', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<input\n class=\"helix-date-input__input\"\n type=\"date\"\n [value]=\"rawValue()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n/>\n", styles: [":host{display:block;width:100%}.helix-date-input__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-date-input__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-date-input__input:disabled,.helix-date-input__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-date-input__input::placeholder{color:var(--h-text-muted-color)}\n"] }]
739
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }] } });
740
-
741
- /** Built-in widget for `z.date()` fields. */
742
- class HelixDateWidget extends HelixFieldWidgetBase {
743
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDateWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
744
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: HelixDateWidget, isStandalone: true, selector: "helix-date-widget", usesInheritance: true, ngImport: i0, template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <helix-date-input [formField]=\"field()\" />\n</helix-form-field>\n", styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "component", type: HelixFormField, selector: "helix-form-field", inputs: ["label", "control", "hint", "error", "showLabel", "showHint"] }, { kind: "component", type: HelixDateInput, selector: "helix-date-input", inputs: ["value", "disabled", "readonly", "touched"], outputs: ["valueChange", "touchedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
767
+ return `${value.getFullYear().toString().padStart(4, "0")}-${(value.getMonth() + 1).toString().padStart(2, "0")}-${value.getDate().toString().padStart(2, "0")}`;
745
768
  }
746
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDateWidget, decorators: [{
747
- type: Component,
748
- args: [{ selector: 'helix-date-widget', standalone: true, imports: [FormField, HelixFormField, HelixDateInput], changeDetection: ChangeDetectionStrategy.OnPush, template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <helix-date-input [formField]=\"field()\" />\n</helix-form-field>\n", styles: [":host{display:block;width:100%}\n"] }]
749
- }] });
750
-
751
- /** Built-in widget for numeric fields. */
752
- class HelixNumberWidget extends HelixFieldWidgetBase {
753
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixNumberWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
754
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: HelixNumberWidget, isStandalone: true, selector: "helix-number-widget", usesInheritance: true, ngImport: i0, template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <input\n class=\"helix-number-widget__input\"\n type=\"number\"\n [placeholder]=\"placeholder()\"\n [formField]=\"field()\"\n />\n</helix-form-field>\n", styles: [":host{display:block;width:100%}.helix-number-widget__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-number-widget__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-number-widget__input:disabled,.helix-number-widget__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-number-widget__input::placeholder{color:var(--h-text-muted-color)}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "component", type: HelixFormField, selector: "helix-form-field", inputs: ["label", "control", "hint", "error", "showLabel", "showHint"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
755
- }
756
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixNumberWidget, decorators: [{
757
- type: Component,
758
- args: [{ selector: 'helix-number-widget', standalone: true, imports: [FormField, HelixFormField], changeDetection: ChangeDetectionStrategy.OnPush, template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <input\n class=\"helix-number-widget__input\"\n type=\"number\"\n [placeholder]=\"placeholder()\"\n [formField]=\"field()\"\n />\n</helix-form-field>\n", styles: [":host{display:block;width:100%}.helix-number-widget__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-number-widget__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-number-widget__input:disabled,.helix-number-widget__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-number-widget__input::placeholder{color:var(--h-text-muted-color)}\n"] }]
759
- }] });
760
-
761
- /** Built-in widget for nested `z.object()` fields — fieldset + recursion. */
762
- class HelixObjectWidget extends HelixFieldWidgetBase {
763
- childField(key) {
764
- return this.field()[key];
765
- }
766
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixObjectWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
767
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: HelixObjectWidget, isStandalone: true, selector: "helix-object-widget", usesInheritance: true, ngImport: i0, template: "<fieldset class=\"helix-object-widget\">\n @if (label(); as l) {\n <legend class=\"helix-object-widget__legend\">{{ l }}</legend>\n }\n @if (hint(); as h) {\n <small class=\"helix-object-widget__hint\">{{ h }}</small>\n }\n\n @for (child of descriptor().children ?? []; track child.key) {\n <helix-dynamic-field [field]=\"childField(child.key)\" [descriptor]=\"child\" />\n }\n</fieldset>\n", styles: [":host{display:block;width:100%}.helix-object-widget{display:flex;flex-direction:column;gap:1rem;margin:0;padding:1rem;border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md)}.helix-object-widget__legend{padding:0 .5rem;font-size:.875rem;font-weight:600;color:var(--h-text-color)}.helix-object-widget__hint{font-size:.75rem;color:var(--h-text-muted-color)}\n"], dependencies: [{ kind: "component", type: HelixDynamicField, selector: "helix-dynamic-field", inputs: ["field", "descriptor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
768
- }
769
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixObjectWidget, decorators: [{
770
- type: Component,
771
- args: [{ selector: 'helix-object-widget', standalone: true, imports: [HelixDynamicField], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset class=\"helix-object-widget\">\n @if (label(); as l) {\n <legend class=\"helix-object-widget__legend\">{{ l }}</legend>\n }\n @if (hint(); as h) {\n <small class=\"helix-object-widget__hint\">{{ h }}</small>\n }\n\n @for (child of descriptor().children ?? []; track child.key) {\n <helix-dynamic-field [field]=\"childField(child.key)\" [descriptor]=\"child\" />\n }\n</fieldset>\n", styles: [":host{display:block;width:100%}.helix-object-widget{display:flex;flex-direction:column;gap:1rem;margin:0;padding:1rem;border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md)}.helix-object-widget__legend{padding:0 .5rem;font-size:.875rem;font-weight:600;color:var(--h-text-color)}.helix-object-widget__hint{font-size:.75rem;color:var(--h-text-muted-color)}\n"] }]
772
- }] });
773
-
774
- /**
775
- * Built-in widget for enum/select fields — wraps `HelixSelect`, which the
776
- * signal-forms `FormField` directive binds via its `ControlValueAccessor`.
777
- */
778
- class HelixSelectWidget extends HelixFieldWidgetBase {
779
- selectOptions = computed(() => [...(this.descriptor().options ?? [])], /* @ts-ignore */
780
- ...(ngDevMode ? [{ debugName: "selectOptions" }] : /* istanbul ignore next */ []));
781
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixSelectWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
782
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: HelixSelectWidget, isStandalone: true, selector: "helix-select-widget", usesInheritance: true, ngImport: i0, template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <helix-select\n [options]=\"selectOptions()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"state().disabled()\"\n [formField]=\"field()\"\n />\n</helix-form-field>\n", styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "component", type: HelixFormField, selector: "helix-form-field", inputs: ["label", "control", "hint", "error", "showLabel", "showHint"] }, { kind: "component", type: HelixSelect, selector: "helix-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
783
- }
784
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixSelectWidget, decorators: [{
785
- type: Component,
786
- args: [{ selector: 'helix-select-widget', standalone: true, imports: [FormField, HelixFormField, HelixSelect], changeDetection: ChangeDetectionStrategy.OnPush, template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <helix-select\n [options]=\"selectOptions()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"state().disabled()\"\n [formField]=\"field()\"\n />\n</helix-form-field>\n", styles: [":host{display:block;width:100%}\n"] }]
787
- }] });
788
-
789
- /** Built-in widget for `text`, `email` and `password` fields. */
790
- class HelixTextWidget extends HelixFieldWidgetBase {
791
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixTextWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
792
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: HelixTextWidget, isStandalone: true, selector: "helix-text-widget", usesInheritance: true, ngImport: i0, template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <input\n class=\"helix-text-widget__input\"\n [type]=\"descriptor().inputType ?? 'text'\"\n [placeholder]=\"placeholder()\"\n [formField]=\"field()\"\n />\n</helix-form-field>\n", styles: [":host{display:block;width:100%}.helix-text-widget__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-text-widget__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-text-widget__input:disabled,.helix-text-widget__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-text-widget__input::placeholder{color:var(--h-text-muted-color)}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "component", type: HelixFormField, selector: "helix-form-field", inputs: ["label", "control", "hint", "error", "showLabel", "showHint"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
793
- }
794
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixTextWidget, decorators: [{
795
- type: Component,
796
- args: [{ selector: 'helix-text-widget', standalone: true, imports: [FormField, HelixFormField], changeDetection: ChangeDetectionStrategy.OnPush, template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <input\n class=\"helix-text-widget__input\"\n [type]=\"descriptor().inputType ?? 'text'\"\n [placeholder]=\"placeholder()\"\n [formField]=\"field()\"\n />\n</helix-form-field>\n", styles: [":host{display:block;width:100%}.helix-text-widget__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-text-widget__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-text-widget__input:disabled,.helix-text-widget__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-text-widget__input::placeholder{color:var(--h-text-muted-color)}\n"] }]
797
- }] });
798
-
799
- /** Built-in widget for multiline text (`widget: 'textarea'`). */
800
- class HelixTextareaWidget extends HelixFieldWidgetBase {
801
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixTextareaWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
802
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: HelixTextareaWidget, isStandalone: true, selector: "helix-textarea-widget", usesInheritance: true, ngImport: i0, template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <textarea\n class=\"helix-textarea-widget__input\"\n rows=\"4\"\n [placeholder]=\"placeholder()\"\n [formField]=\"field()\"\n ></textarea>\n</helix-form-field>\n", styles: [":host{display:block;width:100%}.helix-textarea-widget__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-textarea-widget__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-textarea-widget__input:disabled,.helix-textarea-widget__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-textarea-widget__input::placeholder{color:var(--h-text-muted-color)}.helix-textarea-widget__input{resize:vertical}\n"], dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "component", type: HelixFormField, selector: "helix-form-field", inputs: ["label", "control", "hint", "error", "showLabel", "showHint"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
803
- }
804
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixTextareaWidget, decorators: [{
805
- type: Component,
806
- args: [{ selector: 'helix-textarea-widget', standalone: true, imports: [FormField, HelixFormField], changeDetection: ChangeDetectionStrategy.OnPush, template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <textarea\n class=\"helix-textarea-widget__input\"\n rows=\"4\"\n [placeholder]=\"placeholder()\"\n [formField]=\"field()\"\n ></textarea>\n</helix-form-field>\n", styles: [":host{display:block;width:100%}.helix-textarea-widget__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-textarea-widget__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-textarea-widget__input:disabled,.helix-textarea-widget__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-textarea-widget__input::placeholder{color:var(--h-text-muted-color)}.helix-textarea-widget__input{resize:vertical}\n"] }]
807
- }] });
808
-
809
- /**
810
- * Built-in widget for `z.discriminatedUnion()` fields: a discriminator select
811
- * plus the active variant's fields.
812
- *
813
- * Switching the discriminator **resets** the union value to the new variant's
814
- * default (preserving the discriminator) — mandatory so the model always
815
- * matches the active variant's shape and all field paths exist.
816
- */
817
- class HelixUnionWidget extends HelixFieldWidgetBase {
818
- discriminator = computed(() => this.descriptor().union?.discriminator ?? '', /* @ts-ignore */
819
- ...(ngDevMode ? [{ debugName: "discriminator" }] : /* istanbul ignore next */ []));
820
- tags = computed(() => [...(this.descriptor().union?.variants.keys() ?? [])], /* @ts-ignore */
821
- ...(ngDevMode ? [{ debugName: "tags" }] : /* istanbul ignore next */ []));
822
- activeTag = computed(() => this.state().value()?.[this.discriminator()], /* @ts-ignore */
823
- ...(ngDevMode ? [{ debugName: "activeTag" }] : /* istanbul ignore next */ []));
824
- variantChildren = computed(() => {
825
- const variant = this.descriptor().union?.variants.get(this.activeTag());
826
- return (variant?.children ?? []).filter((c) => c.key !== this.discriminator());
827
- }, /* @ts-ignore */
828
- ...(ngDevMode ? [{ debugName: "variantChildren" }] : /* istanbul ignore next */ []));
829
- childField(key) {
830
- return this.field()[key];
831
- }
832
- onTagChange(event) {
833
- const index = Number.parseInt(event.target.value, 10);
834
- const tag = this.tags()[index];
835
- const variant = this.descriptor().union?.variants.get(tag);
836
- if (variant === undefined || tag === this.activeTag())
837
- return;
838
- const next = buildDefaultValue(variant.zodSource);
839
- next[this.discriminator()] = tag;
840
- this.state().value.set(next);
841
- }
842
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixUnionWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
843
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: HelixUnionWidget, isStandalone: true, selector: "helix-union-widget", usesInheritance: true, ngImport: i0, template: "<div class=\"helix-union-widget\">\n <helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <div class=\"helix-union-widget__select-wrapper\">\n <select\n class=\"helix-union-widget__select\"\n [disabled]=\"state().disabled()\"\n (change)=\"onTagChange($event)\"\n >\n @for (tag of tags(); track tag) {\n <option [value]=\"$index\" [selected]=\"tag === activeTag()\">{{ tag }}</option>\n }\n </select>\n <i class=\"helix-union-widget__icon pi pi-chevron-down\"></i>\n </div>\n </helix-form-field>\n\n @for (child of variantChildren(); track child.key) {\n <helix-dynamic-field [field]=\"childField(child.key)\" [descriptor]=\"child\" />\n }\n</div>\n", styles: [":host{display:block;width:100%}.helix-union-widget{display:flex;flex-direction:column;gap:1rem}.helix-union-widget__select-wrapper{position:relative;display:flex;width:100%}.helix-union-widget__select{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-union-widget__select:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-union-widget__select:disabled,.helix-union-widget__select[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-union-widget__select::placeholder{color:var(--h-text-muted-color)}.helix-union-widget__select{padding-right:2.5rem;appearance:none;cursor:pointer}.helix-union-widget__icon{position:absolute;top:50%;right:.75rem;transform:translateY(-50%);pointer-events:none;color:var(--h-text-muted-color);font-size:.875rem}\n"], dependencies: [{ kind: "component", type: HelixDynamicField, selector: "helix-dynamic-field", inputs: ["field", "descriptor"] }, { kind: "component", type: HelixFormField, selector: "helix-form-field", inputs: ["label", "control", "hint", "error", "showLabel", "showHint"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
844
- }
845
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixUnionWidget, decorators: [{
846
- type: Component,
847
- args: [{ selector: 'helix-union-widget', standalone: true, imports: [HelixDynamicField, HelixFormField], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"helix-union-widget\">\n <helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <div class=\"helix-union-widget__select-wrapper\">\n <select\n class=\"helix-union-widget__select\"\n [disabled]=\"state().disabled()\"\n (change)=\"onTagChange($event)\"\n >\n @for (tag of tags(); track tag) {\n <option [value]=\"$index\" [selected]=\"tag === activeTag()\">{{ tag }}</option>\n }\n </select>\n <i class=\"helix-union-widget__icon pi pi-chevron-down\"></i>\n </div>\n </helix-form-field>\n\n @for (child of variantChildren(); track child.key) {\n <helix-dynamic-field [field]=\"childField(child.key)\" [descriptor]=\"child\" />\n }\n</div>\n", styles: [":host{display:block;width:100%}.helix-union-widget{display:flex;flex-direction:column;gap:1rem}.helix-union-widget__select-wrapper{position:relative;display:flex;width:100%}.helix-union-widget__select{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-union-widget__select:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-union-widget__select:disabled,.helix-union-widget__select[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-union-widget__select::placeholder{color:var(--h-text-muted-color)}.helix-union-widget__select{padding-right:2.5rem;appearance:none;cursor:pointer}.helix-union-widget__icon{position:absolute;top:50%;right:.75rem;transform:translateY(-50%);pointer-events:none;color:var(--h-text-muted-color);font-size:.875rem}\n"] }]
848
- }] });
849
-
769
+ var HelixDateInput = class HelixDateInput {
770
+ value = model.required(...ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []);
771
+ disabled = input(false, ...ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []);
772
+ readonly = input(false, ...ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []);
773
+ touched = model(false, ...ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []);
774
+ rawValue = transformedValue(this.value, {
775
+ parse: (raw) => {
776
+ if (raw === "") return { value: null };
777
+ const parsed = /* @__PURE__ */ new Date(`${raw}T00:00:00`);
778
+ if (Number.isNaN(parsed.getTime())) return { error: {
779
+ kind: "parse",
780
+ message: `"${raw}" is not a valid date`
781
+ } };
782
+ return { value: parsed };
783
+ },
784
+ format: (value) => value ? toIsoDate(value) : ""
785
+ });
786
+ onInput(event) {
787
+ this.rawValue.set(event.target.value);
788
+ }
789
+ onBlur() {
790
+ this.touched.set(true);
791
+ }
792
+ static ɵfac = i0.ɵɵngDeclareFactory({
793
+ minVersion: "12.0.0",
794
+ version: "22.2.1",
795
+ ngImport: i0,
796
+ type: HelixDateInput,
797
+ deps: [],
798
+ target: i0.ɵɵFactoryTarget.Component
799
+ });
800
+ static ɵcmp = i0.ɵɵngDeclareComponent({
801
+ minVersion: "17.1.0",
802
+ version: "22.2.1",
803
+ type: HelixDateInput,
804
+ isStandalone: true,
805
+ selector: "helix-date-input",
806
+ inputs: {
807
+ value: {
808
+ classPropertyName: "value",
809
+ publicName: "value",
810
+ isSignal: true,
811
+ isRequired: true,
812
+ transformFunction: null
813
+ },
814
+ disabled: {
815
+ classPropertyName: "disabled",
816
+ publicName: "disabled",
817
+ isSignal: true,
818
+ isRequired: false,
819
+ transformFunction: null
820
+ },
821
+ readonly: {
822
+ classPropertyName: "readonly",
823
+ publicName: "readonly",
824
+ isSignal: true,
825
+ isRequired: false,
826
+ transformFunction: null
827
+ },
828
+ touched: {
829
+ classPropertyName: "touched",
830
+ publicName: "touched",
831
+ isSignal: true,
832
+ isRequired: false,
833
+ transformFunction: null
834
+ }
835
+ },
836
+ outputs: {
837
+ value: "valueChange",
838
+ touched: "touchedChange"
839
+ },
840
+ ngImport: i0,
841
+ template: "<input\n class=\"helix-date-input__input\"\n type=\"date\"\n [value]=\"rawValue()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n/>\n",
842
+ styles: [":host{display:block;width:100%}.helix-date-input__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-date-input__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-date-input__input:disabled,.helix-date-input__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-date-input__input::placeholder{color:var(--h-text-muted-color)}\n"],
843
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
844
+ });
845
+ };
846
+ i0.ɵɵngDeclareClassMetadata({
847
+ minVersion: "12.0.0",
848
+ version: "22.2.1",
849
+ ngImport: i0,
850
+ type: HelixDateInput,
851
+ decorators: [{
852
+ type: Component,
853
+ args: [{
854
+ selector: "helix-date-input",
855
+ standalone: true,
856
+ changeDetection: ChangeDetectionStrategy.OnPush,
857
+ template: "<input\n class=\"helix-date-input__input\"\n type=\"date\"\n [value]=\"rawValue()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n/>\n",
858
+ styles: [":host{display:block;width:100%}.helix-date-input__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-date-input__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-date-input__input:disabled,.helix-date-input__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-date-input__input::placeholder{color:var(--h-text-muted-color)}\n"]
859
+ }]
860
+ }],
861
+ propDecorators: {
862
+ value: [{
863
+ type: i0.Input,
864
+ args: [{
865
+ isSignal: true,
866
+ alias: "value",
867
+ required: true
868
+ }]
869
+ }, {
870
+ type: i0.Output,
871
+ args: ["valueChange"]
872
+ }],
873
+ disabled: [{
874
+ type: i0.Input,
875
+ args: [{
876
+ isSignal: true,
877
+ alias: "disabled",
878
+ required: false
879
+ }]
880
+ }],
881
+ readonly: [{
882
+ type: i0.Input,
883
+ args: [{
884
+ isSignal: true,
885
+ alias: "readonly",
886
+ required: false
887
+ }]
888
+ }],
889
+ touched: [{
890
+ type: i0.Input,
891
+ args: [{
892
+ isSignal: true,
893
+ alias: "touched",
894
+ required: false
895
+ }]
896
+ }, {
897
+ type: i0.Output,
898
+ args: ["touchedChange"]
899
+ }]
900
+ }
901
+ });
902
+ var HelixDateWidget = class HelixDateWidget extends HelixFieldWidgetBase {
903
+ static ɵfac = i0.ɵɵngDeclareFactory({
904
+ minVersion: "12.0.0",
905
+ version: "22.2.1",
906
+ ngImport: i0,
907
+ type: HelixDateWidget,
908
+ deps: null,
909
+ target: i0.ɵɵFactoryTarget.Component
910
+ });
911
+ static ɵcmp = i0.ɵɵngDeclareComponent({
912
+ minVersion: "14.0.0",
913
+ version: "22.2.1",
914
+ type: HelixDateWidget,
915
+ isStandalone: true,
916
+ selector: "helix-date-widget",
917
+ usesInheritance: true,
918
+ ngImport: i0,
919
+ template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <helix-date-input [formField]=\"field()\" />\n</helix-form-field>\n",
920
+ styles: [":host{display:block;width:100%}\n"],
921
+ dependencies: [
922
+ {
923
+ kind: "directive",
924
+ type: FormField,
925
+ selector: "[formField]",
926
+ inputs: ["formField"],
927
+ exportAs: ["formField"]
928
+ },
929
+ {
930
+ kind: "component",
931
+ type: HelixFormField,
932
+ selector: "helix-form-field",
933
+ inputs: [
934
+ "label",
935
+ "control",
936
+ "hint",
937
+ "error",
938
+ "showLabel",
939
+ "showHint"
940
+ ]
941
+ },
942
+ {
943
+ kind: "component",
944
+ type: HelixDateInput,
945
+ selector: "helix-date-input",
946
+ inputs: [
947
+ "value",
948
+ "disabled",
949
+ "readonly",
950
+ "touched"
951
+ ],
952
+ outputs: ["valueChange", "touchedChange"]
953
+ }
954
+ ],
955
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
956
+ });
957
+ };
958
+ i0.ɵɵngDeclareClassMetadata({
959
+ minVersion: "12.0.0",
960
+ version: "22.2.1",
961
+ ngImport: i0,
962
+ type: HelixDateWidget,
963
+ decorators: [{
964
+ type: Component,
965
+ args: [{
966
+ selector: "helix-date-widget",
967
+ standalone: true,
968
+ imports: [
969
+ FormField,
970
+ HelixFormField,
971
+ HelixDateInput
972
+ ],
973
+ changeDetection: ChangeDetectionStrategy.OnPush,
974
+ template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <helix-date-input [formField]=\"field()\" />\n</helix-form-field>\n",
975
+ styles: [":host{display:block;width:100%}\n"]
976
+ }]
977
+ }]
978
+ });
979
+ var HelixNumberWidget = class HelixNumberWidget extends HelixFieldWidgetBase {
980
+ static ɵfac = i0.ɵɵngDeclareFactory({
981
+ minVersion: "12.0.0",
982
+ version: "22.2.1",
983
+ ngImport: i0,
984
+ type: HelixNumberWidget,
985
+ deps: null,
986
+ target: i0.ɵɵFactoryTarget.Component
987
+ });
988
+ static ɵcmp = i0.ɵɵngDeclareComponent({
989
+ minVersion: "14.0.0",
990
+ version: "22.2.1",
991
+ type: HelixNumberWidget,
992
+ isStandalone: true,
993
+ selector: "helix-number-widget",
994
+ usesInheritance: true,
995
+ ngImport: i0,
996
+ template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <input\n class=\"helix-number-widget__input\"\n type=\"number\"\n [placeholder]=\"placeholder()\"\n [formField]=\"field()\"\n />\n</helix-form-field>\n",
997
+ styles: [":host{display:block;width:100%}.helix-number-widget__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-number-widget__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-number-widget__input:disabled,.helix-number-widget__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-number-widget__input::placeholder{color:var(--h-text-muted-color)}\n"],
998
+ dependencies: [{
999
+ kind: "directive",
1000
+ type: FormField,
1001
+ selector: "[formField]",
1002
+ inputs: ["formField"],
1003
+ exportAs: ["formField"]
1004
+ }, {
1005
+ kind: "component",
1006
+ type: HelixFormField,
1007
+ selector: "helix-form-field",
1008
+ inputs: [
1009
+ "label",
1010
+ "control",
1011
+ "hint",
1012
+ "error",
1013
+ "showLabel",
1014
+ "showHint"
1015
+ ]
1016
+ }],
1017
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1018
+ });
1019
+ };
1020
+ i0.ɵɵngDeclareClassMetadata({
1021
+ minVersion: "12.0.0",
1022
+ version: "22.2.1",
1023
+ ngImport: i0,
1024
+ type: HelixNumberWidget,
1025
+ decorators: [{
1026
+ type: Component,
1027
+ args: [{
1028
+ selector: "helix-number-widget",
1029
+ standalone: true,
1030
+ imports: [FormField, HelixFormField],
1031
+ changeDetection: ChangeDetectionStrategy.OnPush,
1032
+ template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <input\n class=\"helix-number-widget__input\"\n type=\"number\"\n [placeholder]=\"placeholder()\"\n [formField]=\"field()\"\n />\n</helix-form-field>\n",
1033
+ styles: [":host{display:block;width:100%}.helix-number-widget__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-number-widget__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-number-widget__input:disabled,.helix-number-widget__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-number-widget__input::placeholder{color:var(--h-text-muted-color)}\n"]
1034
+ }]
1035
+ }]
1036
+ });
1037
+ var HelixObjectWidget = class HelixObjectWidget extends HelixFieldWidgetBase {
1038
+ childField(key) {
1039
+ return this.field()[key];
1040
+ }
1041
+ static ɵfac = i0.ɵɵngDeclareFactory({
1042
+ minVersion: "12.0.0",
1043
+ version: "22.2.1",
1044
+ ngImport: i0,
1045
+ type: HelixObjectWidget,
1046
+ deps: null,
1047
+ target: i0.ɵɵFactoryTarget.Component
1048
+ });
1049
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1050
+ minVersion: "17.0.0",
1051
+ version: "22.2.1",
1052
+ type: HelixObjectWidget,
1053
+ isStandalone: true,
1054
+ selector: "helix-object-widget",
1055
+ usesInheritance: true,
1056
+ ngImport: i0,
1057
+ template: "<fieldset class=\"helix-object-widget\">\n @if (label(); as l) {\n <legend class=\"helix-object-widget__legend\">{{ l }}</legend>\n }\n @if (hint(); as h) {\n <small class=\"helix-object-widget__hint\">{{ h }}</small>\n }\n\n @for (child of descriptor().children ?? []; track child.key) {\n <helix-dynamic-field [field]=\"childField(child.key)\" [descriptor]=\"child\" />\n }\n</fieldset>\n",
1058
+ styles: [":host{display:block;width:100%}.helix-object-widget{display:flex;flex-direction:column;gap:1rem;margin:0;padding:1rem;border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md)}.helix-object-widget__legend{padding:0 .5rem;font-size:.875rem;font-weight:600;color:var(--h-text-color)}.helix-object-widget__hint{font-size:.75rem;color:var(--h-text-muted-color)}\n"],
1059
+ dependencies: [{
1060
+ kind: "component",
1061
+ type: HelixDynamicField,
1062
+ selector: "helix-dynamic-field",
1063
+ inputs: ["field", "descriptor"]
1064
+ }],
1065
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1066
+ });
1067
+ };
1068
+ i0.ɵɵngDeclareClassMetadata({
1069
+ minVersion: "12.0.0",
1070
+ version: "22.2.1",
1071
+ ngImport: i0,
1072
+ type: HelixObjectWidget,
1073
+ decorators: [{
1074
+ type: Component,
1075
+ args: [{
1076
+ selector: "helix-object-widget",
1077
+ standalone: true,
1078
+ imports: [HelixDynamicField],
1079
+ changeDetection: ChangeDetectionStrategy.OnPush,
1080
+ template: "<fieldset class=\"helix-object-widget\">\n @if (label(); as l) {\n <legend class=\"helix-object-widget__legend\">{{ l }}</legend>\n }\n @if (hint(); as h) {\n <small class=\"helix-object-widget__hint\">{{ h }}</small>\n }\n\n @for (child of descriptor().children ?? []; track child.key) {\n <helix-dynamic-field [field]=\"childField(child.key)\" [descriptor]=\"child\" />\n }\n</fieldset>\n",
1081
+ styles: [":host{display:block;width:100%}.helix-object-widget{display:flex;flex-direction:column;gap:1rem;margin:0;padding:1rem;border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md)}.helix-object-widget__legend{padding:0 .5rem;font-size:.875rem;font-weight:600;color:var(--h-text-color)}.helix-object-widget__hint{font-size:.75rem;color:var(--h-text-muted-color)}\n"]
1082
+ }]
1083
+ }]
1084
+ });
1085
+ var HelixSelectWidget = class HelixSelectWidget extends HelixFieldWidgetBase {
1086
+ selectOptions = computed(() => [...this.descriptor().options ?? []], ...ngDevMode ? [{ debugName: "selectOptions" }] : /* istanbul ignore next */ []);
1087
+ static ɵfac = i0.ɵɵngDeclareFactory({
1088
+ minVersion: "12.0.0",
1089
+ version: "22.2.1",
1090
+ ngImport: i0,
1091
+ type: HelixSelectWidget,
1092
+ deps: null,
1093
+ target: i0.ɵɵFactoryTarget.Component
1094
+ });
1095
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1096
+ minVersion: "14.0.0",
1097
+ version: "22.2.1",
1098
+ type: HelixSelectWidget,
1099
+ isStandalone: true,
1100
+ selector: "helix-select-widget",
1101
+ usesInheritance: true,
1102
+ ngImport: i0,
1103
+ template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <helix-select\n [options]=\"selectOptions()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"state().disabled()\"\n [formField]=\"field()\"\n />\n</helix-form-field>\n",
1104
+ styles: [":host{display:block;width:100%}\n"],
1105
+ dependencies: [
1106
+ {
1107
+ kind: "directive",
1108
+ type: FormField,
1109
+ selector: "[formField]",
1110
+ inputs: ["formField"],
1111
+ exportAs: ["formField"]
1112
+ },
1113
+ {
1114
+ kind: "component",
1115
+ type: HelixFormField,
1116
+ selector: "helix-form-field",
1117
+ inputs: [
1118
+ "label",
1119
+ "control",
1120
+ "hint",
1121
+ "error",
1122
+ "showLabel",
1123
+ "showHint"
1124
+ ]
1125
+ },
1126
+ {
1127
+ kind: "component",
1128
+ type: HelixSelect,
1129
+ selector: "helix-select",
1130
+ inputs: [
1131
+ "options",
1132
+ "optionLabel",
1133
+ "optionValue",
1134
+ "placeholder",
1135
+ "disabled"
1136
+ ]
1137
+ }
1138
+ ],
1139
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1140
+ });
1141
+ };
1142
+ i0.ɵɵngDeclareClassMetadata({
1143
+ minVersion: "12.0.0",
1144
+ version: "22.2.1",
1145
+ ngImport: i0,
1146
+ type: HelixSelectWidget,
1147
+ decorators: [{
1148
+ type: Component,
1149
+ args: [{
1150
+ selector: "helix-select-widget",
1151
+ standalone: true,
1152
+ imports: [
1153
+ FormField,
1154
+ HelixFormField,
1155
+ HelixSelect
1156
+ ],
1157
+ changeDetection: ChangeDetectionStrategy.OnPush,
1158
+ template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <helix-select\n [options]=\"selectOptions()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"state().disabled()\"\n [formField]=\"field()\"\n />\n</helix-form-field>\n",
1159
+ styles: [":host{display:block;width:100%}\n"]
1160
+ }]
1161
+ }]
1162
+ });
1163
+ var HelixTextWidget = class HelixTextWidget extends HelixFieldWidgetBase {
1164
+ static ɵfac = i0.ɵɵngDeclareFactory({
1165
+ minVersion: "12.0.0",
1166
+ version: "22.2.1",
1167
+ ngImport: i0,
1168
+ type: HelixTextWidget,
1169
+ deps: null,
1170
+ target: i0.ɵɵFactoryTarget.Component
1171
+ });
1172
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1173
+ minVersion: "14.0.0",
1174
+ version: "22.2.1",
1175
+ type: HelixTextWidget,
1176
+ isStandalone: true,
1177
+ selector: "helix-text-widget",
1178
+ usesInheritance: true,
1179
+ ngImport: i0,
1180
+ template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <input\n class=\"helix-text-widget__input\"\n [type]=\"descriptor().inputType ?? 'text'\"\n [placeholder]=\"placeholder()\"\n [formField]=\"field()\"\n />\n</helix-form-field>\n",
1181
+ styles: [":host{display:block;width:100%}.helix-text-widget__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-text-widget__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-text-widget__input:disabled,.helix-text-widget__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-text-widget__input::placeholder{color:var(--h-text-muted-color)}\n"],
1182
+ dependencies: [{
1183
+ kind: "directive",
1184
+ type: FormField,
1185
+ selector: "[formField]",
1186
+ inputs: ["formField"],
1187
+ exportAs: ["formField"]
1188
+ }, {
1189
+ kind: "component",
1190
+ type: HelixFormField,
1191
+ selector: "helix-form-field",
1192
+ inputs: [
1193
+ "label",
1194
+ "control",
1195
+ "hint",
1196
+ "error",
1197
+ "showLabel",
1198
+ "showHint"
1199
+ ]
1200
+ }],
1201
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1202
+ });
1203
+ };
1204
+ i0.ɵɵngDeclareClassMetadata({
1205
+ minVersion: "12.0.0",
1206
+ version: "22.2.1",
1207
+ ngImport: i0,
1208
+ type: HelixTextWidget,
1209
+ decorators: [{
1210
+ type: Component,
1211
+ args: [{
1212
+ selector: "helix-text-widget",
1213
+ standalone: true,
1214
+ imports: [FormField, HelixFormField],
1215
+ changeDetection: ChangeDetectionStrategy.OnPush,
1216
+ template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <input\n class=\"helix-text-widget__input\"\n [type]=\"descriptor().inputType ?? 'text'\"\n [placeholder]=\"placeholder()\"\n [formField]=\"field()\"\n />\n</helix-form-field>\n",
1217
+ styles: [":host{display:block;width:100%}.helix-text-widget__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-text-widget__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-text-widget__input:disabled,.helix-text-widget__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-text-widget__input::placeholder{color:var(--h-text-muted-color)}\n"]
1218
+ }]
1219
+ }]
1220
+ });
1221
+ var HelixTextareaWidget = class HelixTextareaWidget extends HelixFieldWidgetBase {
1222
+ static ɵfac = i0.ɵɵngDeclareFactory({
1223
+ minVersion: "12.0.0",
1224
+ version: "22.2.1",
1225
+ ngImport: i0,
1226
+ type: HelixTextareaWidget,
1227
+ deps: null,
1228
+ target: i0.ɵɵFactoryTarget.Component
1229
+ });
1230
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1231
+ minVersion: "14.0.0",
1232
+ version: "22.2.1",
1233
+ type: HelixTextareaWidget,
1234
+ isStandalone: true,
1235
+ selector: "helix-textarea-widget",
1236
+ usesInheritance: true,
1237
+ ngImport: i0,
1238
+ template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <textarea\n class=\"helix-textarea-widget__input\"\n rows=\"4\"\n [placeholder]=\"placeholder()\"\n [formField]=\"field()\"\n ></textarea>\n</helix-form-field>\n",
1239
+ styles: [":host{display:block;width:100%}.helix-textarea-widget__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-textarea-widget__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-textarea-widget__input:disabled,.helix-textarea-widget__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-textarea-widget__input::placeholder{color:var(--h-text-muted-color)}.helix-textarea-widget__input{resize:vertical}\n"],
1240
+ dependencies: [{
1241
+ kind: "directive",
1242
+ type: FormField,
1243
+ selector: "[formField]",
1244
+ inputs: ["formField"],
1245
+ exportAs: ["formField"]
1246
+ }, {
1247
+ kind: "component",
1248
+ type: HelixFormField,
1249
+ selector: "helix-form-field",
1250
+ inputs: [
1251
+ "label",
1252
+ "control",
1253
+ "hint",
1254
+ "error",
1255
+ "showLabel",
1256
+ "showHint"
1257
+ ]
1258
+ }],
1259
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1260
+ });
1261
+ };
1262
+ i0.ɵɵngDeclareClassMetadata({
1263
+ minVersion: "12.0.0",
1264
+ version: "22.2.1",
1265
+ ngImport: i0,
1266
+ type: HelixTextareaWidget,
1267
+ decorators: [{
1268
+ type: Component,
1269
+ args: [{
1270
+ selector: "helix-textarea-widget",
1271
+ standalone: true,
1272
+ imports: [FormField, HelixFormField],
1273
+ changeDetection: ChangeDetectionStrategy.OnPush,
1274
+ template: "<helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <textarea\n class=\"helix-textarea-widget__input\"\n rows=\"4\"\n [placeholder]=\"placeholder()\"\n [formField]=\"field()\"\n ></textarea>\n</helix-form-field>\n",
1275
+ styles: [":host{display:block;width:100%}.helix-textarea-widget__input{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-textarea-widget__input:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-textarea-widget__input:disabled,.helix-textarea-widget__input[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-textarea-widget__input::placeholder{color:var(--h-text-muted-color)}.helix-textarea-widget__input{resize:vertical}\n"]
1276
+ }]
1277
+ }]
1278
+ });
1279
+ var HelixUnionWidget = class HelixUnionWidget extends HelixFieldWidgetBase {
1280
+ discriminator = computed(() => this.descriptor().union?.discriminator ?? "", ...ngDevMode ? [{ debugName: "discriminator" }] : /* istanbul ignore next */ []);
1281
+ tags = computed(() => [...this.descriptor().union?.variants.keys() ?? []], ...ngDevMode ? [{ debugName: "tags" }] : /* istanbul ignore next */ []);
1282
+ activeTag = computed(() => this.state().value()?.[this.discriminator()], ...ngDevMode ? [{ debugName: "activeTag" }] : /* istanbul ignore next */ []);
1283
+ variantChildren = computed(() => {
1284
+ return ((this.descriptor().union?.variants.get(this.activeTag()))?.children ?? []).filter((c) => c.key !== this.discriminator());
1285
+ }, ...ngDevMode ? [{ debugName: "variantChildren" }] : /* istanbul ignore next */ []);
1286
+ childField(key) {
1287
+ return this.field()[key];
1288
+ }
1289
+ onTagChange(event) {
1290
+ const index = Number.parseInt(event.target.value, 10);
1291
+ const tag = this.tags()[index];
1292
+ const variant = this.descriptor().union?.variants.get(tag);
1293
+ if (variant === void 0 || tag === this.activeTag()) return;
1294
+ const next = buildDefaultValue(variant.zodSource);
1295
+ next[this.discriminator()] = tag;
1296
+ this.state().value.set(next);
1297
+ }
1298
+ static ɵfac = i0.ɵɵngDeclareFactory({
1299
+ minVersion: "12.0.0",
1300
+ version: "22.2.1",
1301
+ ngImport: i0,
1302
+ type: HelixUnionWidget,
1303
+ deps: null,
1304
+ target: i0.ɵɵFactoryTarget.Component
1305
+ });
1306
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1307
+ minVersion: "17.0.0",
1308
+ version: "22.2.1",
1309
+ type: HelixUnionWidget,
1310
+ isStandalone: true,
1311
+ selector: "helix-union-widget",
1312
+ usesInheritance: true,
1313
+ ngImport: i0,
1314
+ template: "<div class=\"helix-union-widget\">\n <helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <div class=\"helix-union-widget__select-wrapper\">\n <select\n class=\"helix-union-widget__select\"\n [disabled]=\"state().disabled()\"\n (change)=\"onTagChange($event)\"\n >\n @for (tag of tags(); track tag) {\n <option [value]=\"$index\" [selected]=\"tag === activeTag()\">{{ tag }}</option>\n }\n </select>\n <i class=\"helix-union-widget__icon pi pi-chevron-down\"></i>\n </div>\n </helix-form-field>\n\n @for (child of variantChildren(); track child.key) {\n <helix-dynamic-field [field]=\"childField(child.key)\" [descriptor]=\"child\" />\n }\n</div>\n",
1315
+ styles: [":host{display:block;width:100%}.helix-union-widget{display:flex;flex-direction:column;gap:1rem}.helix-union-widget__select-wrapper{position:relative;display:flex;width:100%}.helix-union-widget__select{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-union-widget__select:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-union-widget__select:disabled,.helix-union-widget__select[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-union-widget__select::placeholder{color:var(--h-text-muted-color)}.helix-union-widget__select{padding-right:2.5rem;appearance:none;cursor:pointer}.helix-union-widget__icon{position:absolute;top:50%;right:.75rem;transform:translateY(-50%);pointer-events:none;color:var(--h-text-muted-color);font-size:.875rem}\n"],
1316
+ dependencies: [{
1317
+ kind: "component",
1318
+ type: HelixDynamicField,
1319
+ selector: "helix-dynamic-field",
1320
+ inputs: ["field", "descriptor"]
1321
+ }, {
1322
+ kind: "component",
1323
+ type: HelixFormField,
1324
+ selector: "helix-form-field",
1325
+ inputs: [
1326
+ "label",
1327
+ "control",
1328
+ "hint",
1329
+ "error",
1330
+ "showLabel",
1331
+ "showHint"
1332
+ ]
1333
+ }],
1334
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1335
+ });
1336
+ };
1337
+ i0.ɵɵngDeclareClassMetadata({
1338
+ minVersion: "12.0.0",
1339
+ version: "22.2.1",
1340
+ ngImport: i0,
1341
+ type: HelixUnionWidget,
1342
+ decorators: [{
1343
+ type: Component,
1344
+ args: [{
1345
+ selector: "helix-union-widget",
1346
+ standalone: true,
1347
+ imports: [HelixDynamicField, HelixFormField],
1348
+ changeDetection: ChangeDetectionStrategy.OnPush,
1349
+ template: "<div class=\"helix-union-widget\">\n <helix-form-field [label]=\"label()\" [hint]=\"hint()\" [error]=\"firstError()\">\n <div class=\"helix-union-widget__select-wrapper\">\n <select\n class=\"helix-union-widget__select\"\n [disabled]=\"state().disabled()\"\n (change)=\"onTagChange($event)\"\n >\n @for (tag of tags(); track tag) {\n <option [value]=\"$index\" [selected]=\"tag === activeTag()\">{{ tag }}</option>\n }\n </select>\n <i class=\"helix-union-widget__icon pi pi-chevron-down\"></i>\n </div>\n </helix-form-field>\n\n @for (child of variantChildren(); track child.key) {\n <helix-dynamic-field [field]=\"childField(child.key)\" [descriptor]=\"child\" />\n }\n</div>\n",
1350
+ styles: [":host{display:block;width:100%}.helix-union-widget{display:flex;flex-direction:column;gap:1rem}.helix-union-widget__select-wrapper{position:relative;display:flex;width:100%}.helix-union-widget__select{width:100%;padding:.5rem .75rem;font-family:inherit;font-size:1rem;color:var(--h-text-color);background-color:var(--h-content-background);border:1px solid var(--h-content-border-color);border-radius:var(--h-border-radius-md);transition:border-color .2s,box-shadow .2s}.helix-union-widget__select:focus{outline:0 none;outline-offset:0;border-color:var(--h-primary-color);box-shadow:0 0 0 .2rem var(--h-primary-100)}.helix-union-widget__select:disabled,.helix-union-widget__select[readonly]{cursor:default;background-color:var(--h-content-disabled-background);color:var(--h-text-disabled-color);opacity:1}.helix-union-widget__select::placeholder{color:var(--h-text-muted-color)}.helix-union-widget__select{padding-right:2.5rem;appearance:none;cursor:pointer}.helix-union-widget__icon{position:absolute;top:50%;right:.75rem;transform:translateY(-50%);pointer-events:none;color:var(--h-text-muted-color);font-size:.875rem}\n"]
1351
+ }]
1352
+ }]
1353
+ });
850
1354
  const BUILT_IN_WIDGETS = [
851
- { widget: 'text', component: HelixTextWidget },
852
- { widget: 'email', component: HelixTextWidget },
853
- { widget: 'password', component: HelixTextWidget },
854
- { widget: 'textarea', component: HelixTextareaWidget },
855
- { widget: 'number', component: HelixNumberWidget },
856
- { widget: 'checkbox', component: HelixCheckboxWidget },
857
- { widget: 'select', component: HelixSelectWidget },
858
- { widget: 'date', component: HelixDateWidget },
859
- { widget: 'object', component: HelixObjectWidget },
860
- { widget: 'array', component: HelixArrayWidget },
861
- { widget: 'union', component: HelixUnionWidget },
1355
+ {
1356
+ widget: "text",
1357
+ component: HelixTextWidget
1358
+ },
1359
+ {
1360
+ widget: "email",
1361
+ component: HelixTextWidget
1362
+ },
1363
+ {
1364
+ widget: "password",
1365
+ component: HelixTextWidget
1366
+ },
1367
+ {
1368
+ widget: "textarea",
1369
+ component: HelixTextareaWidget
1370
+ },
1371
+ {
1372
+ widget: "number",
1373
+ component: HelixNumberWidget
1374
+ },
1375
+ {
1376
+ widget: "checkbox",
1377
+ component: HelixCheckboxWidget
1378
+ },
1379
+ {
1380
+ widget: "select",
1381
+ component: HelixSelectWidget
1382
+ },
1383
+ {
1384
+ widget: "date",
1385
+ component: HelixDateWidget
1386
+ },
1387
+ {
1388
+ widget: "object",
1389
+ component: HelixObjectWidget
1390
+ },
1391
+ {
1392
+ widget: "array",
1393
+ component: HelixArrayWidget
1394
+ },
1395
+ {
1396
+ widget: "union",
1397
+ component: HelixUnionWidget
1398
+ }
862
1399
  ];
863
- /**
864
- * Provides the dynamic-form widget registry (built-in widgets included) and
865
- * optional configuration. Add it to `ApplicationConfig.providers` or a route's
866
- * `providers`.
867
- */
868
1400
  function provideHelixDynamicForms(config = {}) {
869
- const providers = [
870
- { provide: HELIX_DYNAMIC_FIELD_WIDGETS, multi: true, useValue: BUILT_IN_WIDGETS },
871
- // Re-provide the resolver here so it is created in THIS environment
872
- // injector and sees the registrations above. The `providedIn: 'root'`
873
- // fallback instance cannot see route-scoped providers.
874
- HelixFieldWidgetResolver,
875
- ];
876
- if (config.widgets?.length) {
877
- providers.push({ provide: HELIX_DYNAMIC_FIELD_WIDGETS, multi: true, useValue: config.widgets });
878
- }
879
- if (config.errorMessageResolver) {
880
- providers.push({
881
- provide: HELIX_ERROR_MESSAGE_RESOLVER,
882
- useValue: config.errorMessageResolver,
883
- });
884
- }
885
- return makeEnvironmentProviders(providers);
1401
+ const providers = [{
1402
+ provide: HELIX_DYNAMIC_FIELD_WIDGETS,
1403
+ multi: true,
1404
+ useValue: BUILT_IN_WIDGETS
1405
+ }, HelixFieldWidgetResolver];
1406
+ if (config.widgets?.length) providers.push({
1407
+ provide: HELIX_DYNAMIC_FIELD_WIDGETS,
1408
+ multi: true,
1409
+ useValue: config.widgets
1410
+ });
1411
+ if (config.errorMessageResolver) providers.push({
1412
+ provide: HELIX_ERROR_MESSAGE_RESOLVER,
1413
+ useValue: config.errorMessageResolver
1414
+ });
1415
+ return makeEnvironmentProviders(providers);
886
1416
  }
887
-
888
- /**
889
- * Navigates a `FieldTree` by a root-relative property path
890
- * (e.g. `['address', 'city']`).
891
- */
892
1417
  function fieldAtPath(tree, path) {
893
- let current = tree;
894
- for (const key of path) {
895
- current = current[key];
896
- if (current === undefined) {
897
- throw new Error(`[fieldAtPath] No field at path "${path.join('.')}" (missing key "${key}").`);
898
- }
899
- }
900
- return current;
1418
+ let current = tree;
1419
+ for (const key of path) {
1420
+ current = current[key];
1421
+ if (current === void 0) throw new Error(`[fieldAtPath] No field at path "${path.join(".")}" (missing key "${key}").`);
1422
+ }
1423
+ return current;
901
1424
  }
902
-
903
- const HelixZodValidators = {
904
- /**
905
- * Converts a Zod field schema into a Helix-compatible Angular `ValidatorFn`.
906
- *
907
- * Each `ZodIssue` is mapped to its `ValidatorKey`. `HelixFormField` reads
908
- * those keys directly — no template changes required.
909
- *
910
- * All issues from a single `safeParse` are processed simultaneously, producing
911
- * one `ValidationErrors` key per issue — equivalent to stacking multiple
912
- * `Validators` calls.
913
- *
914
- * ### Known gaps
915
- * - `ValidatorKey.OneOf` / `AllOf` — `z.enum()` produces `invalid_value`,
916
- * which has no automatic mapping. Use `fallbackKey` or continue using
917
- * `Validators.oneOf` / `Validators.allOf`.
918
- * - `invalid_type` for `boolean` expected — Helix has no `Boolean` key.
919
- * Use `fallbackKey` or `Validators.pattern`.
920
- *
921
- * @param schema A Zod field schema (e.g. `UserSchema.shape.email`).
922
- * Do NOT pass schemas with `.transform()` — use the
923
- * input-only (pre-transform) variant for form controls.
924
- * @param options `allowEmpty` (default `true`) and optional `fallbackKey`
925
- * for `.refine()` / `custom` validators.
926
- *
927
- * @example
928
- * // Standard field — no fallbackKey needed
929
- * HelixZodValidators.fromZod(UserSchema.shape.email)
930
- *
931
- * @example
932
- * // Schema with .refine() — fallbackKey required (Option B)
933
- * HelixZodValidators.fromZod(
934
- * z.string().refine(v => !banned.includes(v), 'Username not allowed'),
935
- * { fallbackKey: ValidatorKey.Pattern },
936
- * )
937
- *
938
- * @example
939
- * // Required field — disable allowEmpty so empty value triggers Required / MinLength
940
- * HelixZodValidators.fromZod(UserSchema.shape.name, { allowEmpty: false })
941
- */
942
- fromZod(schema, options = {}) {
943
- const { fallbackKey, allowEmpty = true } = options;
944
- return (control) => {
945
- const value = control.value;
946
- // Replicate Validators' allowEmpty = true default:
947
- // skip Zod entirely for empty values unless the caller opts out.
948
- if (allowEmpty && isEmptyValue(value)) {
949
- return null;
950
- }
951
- const result = schema.safeParse(value);
952
- if (result.success)
953
- return null;
954
- const errors = {};
955
- for (const issue of result.error.issues) {
956
- const key = zodIssueToHelixKey(issue, value);
957
- if (key !== null) {
958
- errors[key] = issue.message;
959
- continue;
960
- }
961
- // No automatic mapping — apply Option B logic.
962
- if (fallbackKey !== undefined) {
963
- errors[fallbackKey] = issue.message;
964
- }
965
- else {
966
- // ngDevMode is set by Angular's build toolchain:
967
- // true in development builds, false (tree-shaken) in production.
968
- if (typeof ngDevMode !== 'undefined' && ngDevMode) {
969
- throw new Error(`[HelixZodValidators.fromZod] Zod issue code "${issue.code}" has no ` +
970
- `automatic ValidatorKey mapping.\n` +
971
- `Pass { fallbackKey: ValidatorKey.xxx } as the second argument.\n` +
972
- `Failing issue: ${JSON.stringify(issue, null, 2)}`);
973
- }
974
- // Production: skip the unmapped issue silently.
975
- // The control remains invalid if other issues produce mapped errors.
976
- }
977
- }
978
- return Object.keys(errors).length ? errors : null;
979
- };
980
- },
981
- };
982
-
983
- /*
984
- * Public API Surface of @gravionlabs/helix-zod
985
- */
986
- // Dynamic forms — components
987
-
988
- /**
989
- * Generated bundle index. Do not edit.
990
- */
991
-
1425
+ const HelixZodValidators = { fromZod(schema, options = {}) {
1426
+ const { fallbackKey, allowEmpty = true } = options;
1427
+ return (control) => {
1428
+ const value = control.value;
1429
+ if (allowEmpty && isEmptyValue(value)) return null;
1430
+ const result = schema.safeParse(value);
1431
+ if (result.success) return null;
1432
+ const errors = {};
1433
+ for (const issue of result.error.issues) {
1434
+ const key = zodIssueToHelixKey(issue, value);
1435
+ if (key !== null) {
1436
+ errors[key] = issue.message;
1437
+ continue;
1438
+ }
1439
+ if (fallbackKey !== void 0) errors[fallbackKey] = issue.message;
1440
+ else if (typeof ngDevMode !== "undefined" && ngDevMode) throw new Error(`[HelixZodValidators.fromZod] Zod issue code "${issue.code}" has no automatic ValidatorKey mapping.\nPass { fallbackKey: ValidatorKey.xxx } as the second argument.\nFailing issue: ${JSON.stringify(issue, null, 2)}`);
1441
+ }
1442
+ return Object.keys(errors).length ? errors : null;
1443
+ };
1444
+ } };
992
1445
  export { HELIX_DYNAMIC_FIELD_WIDGETS, HELIX_ERROR_MESSAGE_RESOLVER, HelixArrayWidget, HelixCheckboxWidget, HelixDateInput, HelixDateWidget, HelixDynamicField, HelixDynamicForm, HelixFieldWidgetBase, HelixFieldWidgetResolver, HelixNumberWidget, HelixObjectWidget, HelixSelectWidget, HelixTextWidget, HelixTextareaWidget, HelixUnionWidget, HelixZodValidators, buildDefaultValue, buildHelixSchema, fieldAtPath, helixErrorKey, helixFieldMetaRegistry, helixFirstErrorMessage, helixMeta, provideHelixDynamicForms, readHelixMeta, zodToFieldDescriptors };
993
- //# sourceMappingURL=gravionlabs-helix-zod.mjs.map
1446
+
1447
+ //# sourceMappingURL=gravionlabs-helix-zod.mjs.map