@gravionlabs/helix-zod 22.1.1 → 22.1.3

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,974 +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' });
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
+ });
138
+ const defOf$1 = (schema) => schema._zod.def;
139
+ function buildDefaultValue(schema) {
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
+ }
32
170
  }
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 });
171
+ function nullsToAbsent(schema, value) {
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;
65
177
  }
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
-
71
- /**
72
- * Builds the signal-forms schema for a Zod-driven dynamic form:
73
- *
74
- * 1. Root-level `validateStandardSchema(rootPath, zodSchema)` — Zod issue
75
- * paths route errors onto the matching child fields, including cross-field
76
- * `.refine()` errors with a `path`.
77
- * 2. Conditional field state from {@link HelixFieldMeta} predicates
78
- * (`hiddenWhen`/`disabledWhen`/`readonlyWhen`/`requiredWhen`), evaluated
79
- * against the root form value.
80
- * 3. `meta.extraSchema` escape-hatch rules per field.
81
- * 4. `applyEach` for array items and `applyWhenValue` for discriminated-union
82
- * variants, so variant/item rules stay dormant unless active.
83
- */
84
178
  function buildHelixSchema(zodSchema, root) {
85
- return (rootPath) => {
86
- validateStandardSchema(rootPath, zodSchema);
87
- applyDescriptorRules(rootPath, root, rootPath);
88
- };
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
+ };
89
187
  }
90
- /** Reads the root form value from any nested logic context. */
91
188
  function rootValueOf(ctx, rootPath) {
92
- return ctx.valueOf(rootPath);
189
+ return ctx.valueOf(rootPath);
93
190
  }
94
191
  function applyConditionRules(path, descriptor, rootPath) {
95
- const meta = descriptor.meta;
96
- if (meta.hiddenWhen) {
97
- hidden(path, (ctx) => !!meta.hiddenWhen?.(rootValueOf(ctx, rootPath)));
98
- }
99
- if (meta.disabledWhen) {
100
- disabled(path, (ctx) => meta.disabledWhen?.(rootValueOf(ctx, rootPath)) ?? false);
101
- }
102
- if (meta.readonlyWhen) {
103
- readonly(path, (ctx) => !!meta.readonlyWhen?.(rootValueOf(ctx, rootPath)));
104
- }
105
- if (meta.requiredWhen) {
106
- required(path, {
107
- when: (ctx) => !!meta.requiredWhen?.(rootValueOf(ctx, rootPath)),
108
- });
109
- }
110
- 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);
111
198
  }
112
199
  function applyDescriptorRules(path, descriptor, rootPath) {
113
- // Root-level conditions make no sense on the form itself — only on fields.
114
- if (path !== rootPath)
115
- applyConditionRules(path, descriptor, rootPath);
116
- for (const child of descriptor.children ?? []) {
117
- applyDescriptorRules(path[child.key], child, rootPath);
118
- }
119
- if (descriptor.itemDescriptor) {
120
- const item = descriptor.itemDescriptor;
121
- applyEach(path, ((itemPath) => {
122
- applyDescriptorRules(itemPath, item, rootPath);
123
- }));
124
- }
125
- if (descriptor.union) {
126
- const { discriminator, variants } = descriptor.union;
127
- for (const [tag, variant] of variants) {
128
- applyWhenValue(path, ((value) => value?.[discriminator] === tag), ((variantPath) => {
129
- // The union field itself carries the variant's children.
130
- for (const child of variant.children ?? []) {
131
- applyDescriptorRules(variantPath[child.key], child, rootPath);
132
- }
133
- }));
134
- }
135
- }
136
- }
137
-
138
- const defOf$1 = (schema) => schema._zod.def;
139
- /**
140
- * Builds a fully-populated initial model value for a Zod schema — signal forms
141
- * require a concrete model shape up front.
142
- *
143
- * Rules: `.default()` wins; strings → `''`; numbers/dates → `null` (paired
144
- * with parse-transforming widgets); booleans → `false`; enums → first option;
145
- * arrays → `[]`; objects → recursed shape; discriminated unions → default of
146
- * the first variant; `optional`/`nullable` leaves without a more specific
147
- * default → `undefined` / `null`.
148
- */
149
- function buildDefaultValue(schema) {
150
- const def = defOf$1(schema);
151
- switch (def.type) {
152
- case 'default': {
153
- const dv = def['defaultValue'];
154
- return typeof dv === 'function' ? dv() : dv;
155
- }
156
- case 'optional': {
157
- // Optional leaves default to undefined; optional containers still need a
158
- // concrete shape so the field tree has navigable paths.
159
- const inner = schema.unwrap();
160
- return isLeaf(inner) ? undefined : buildDefaultValue(inner);
161
- }
162
- case 'readonly':
163
- case 'nonoptional':
164
- return buildDefaultValue(schema.unwrap());
165
- case 'nullable':
166
- return null;
167
- case 'string':
168
- return '';
169
- case 'number':
170
- case 'int':
171
- case 'date':
172
- return null;
173
- case 'boolean':
174
- return false;
175
- case 'enum': {
176
- const options = schema.options;
177
- return options[0];
178
- }
179
- case 'literal': {
180
- const values = def['values'];
181
- return values[0];
182
- }
183
- case 'array':
184
- return [];
185
- case 'object': {
186
- const shape = schema.shape;
187
- const value = {};
188
- for (const [key, child] of Object.entries(shape)) {
189
- value[key] = buildDefaultValue(child);
190
- }
191
- return value;
192
- }
193
- case 'union': {
194
- const options = schema.options;
195
- return buildDefaultValue(options[0]);
196
- }
197
- default:
198
- return undefined;
199
- }
200
- }
201
- function isLeaf(schema) {
202
- const type = defOf$1(schema).type;
203
- return type !== 'object' && type !== 'array' && type !== 'union';
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
+ }
204
214
  }
205
-
206
- /**
207
- * Typed registry backing {@link helixMeta}. Checked before the zod global
208
- * registry by {@link readHelixMeta}.
209
- */
210
215
  const helixFieldMetaRegistry = z.registry();
211
- /**
212
- * Attaches Helix UI metadata to a Zod schema and returns the **same** instance.
213
- *
214
- * Prefer this over `.meta()`, which clones the schema — metadata must live on
215
- * the exact instance composed into the `z.object()` shape for the walker to
216
- * find it.
217
- *
218
- * @example
219
- * const UserSchema = z.object({
220
- * email: helixMeta(z.email(), { label: 'E-mail', placeholder: 'you@example.com' }),
221
- * });
222
- */
223
216
  function helixMeta(schema, meta) {
224
- helixFieldMetaRegistry.add(schema, meta);
225
- return schema;
217
+ helixFieldMetaRegistry.add(schema, meta);
218
+ return schema;
226
219
  }
227
- 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
+ ]);
228
227
  function metaOf(schema) {
229
- const registered = helixFieldMetaRegistry.get(schema);
230
- if (registered)
231
- return registered;
232
- const globalMeta = schema.meta();
233
- if (!globalMeta)
234
- return undefined;
235
- const { title, description, helix, ...rest } = globalMeta;
236
- const mapped = { ...rest };
237
- if (title !== undefined)
238
- mapped.label = title;
239
- if (description !== undefined)
240
- mapped.hint = description;
241
- 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
+ };
242
240
  }
243
- /**
244
- * Reads {@link HelixFieldMeta} from a Zod schema: the typed registry first,
245
- * then the zod global registry (`.meta()` — `title` → `label`,
246
- * `description` → `hint`, `helix` key merged on top).
247
- *
248
- * Wrapper types (`optional`, `nullable`, `default`, `readonly`) are unwrapped
249
- * and their metadata merged — outermost wins.
250
- */
251
241
  function readHelixMeta(schema) {
252
- const layers = [];
253
- let current = schema;
254
- while (current) {
255
- const meta = metaOf(current);
256
- if (meta)
257
- layers.push(meta);
258
- const def = current._zod.def;
259
- current = WRAPPER_TYPES$1.has(def.type) ? current.unwrap() : undefined;
260
- }
261
- // Innermost first so outer layers overwrite inner ones.
262
- 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
+ }), {});
263
254
  }
264
-
265
- const WRAPPER_TYPES = new Set(['optional', 'nullable', 'default', 'readonly', 'nonoptional']);
266
- /** 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
+ ]);
267
262
  const INPUT_TYPES = {
268
- text: 'text',
269
- email: 'email',
270
- password: 'password',
271
- number: 'number',
272
- date: 'date',
263
+ text: "text",
264
+ email: "email",
265
+ password: "password",
266
+ number: "number",
267
+ date: "date"
273
268
  };
274
269
  function defOf(schema) {
275
- return schema._zod.def;
270
+ return schema._zod.def;
276
271
  }
277
- /** Peels optional/nullable/default/readonly wrappers, tracking requiredness. */
278
272
  function unwrap(schema) {
279
- let inner = schema;
280
- let required = true;
281
- while (WRAPPER_TYPES.has(defOf(inner).type)) {
282
- const type = defOf(inner).type;
283
- if (type === 'optional' || type === 'nullable' || type === 'default')
284
- required = false;
285
- if (type === 'nonoptional')
286
- required = true;
287
- inner = inner.unwrap();
288
- }
289
- 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
+ };
290
285
  }
291
286
  function normalizeOptions(options) {
292
- if (!options)
293
- return undefined;
294
- 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);
295
292
  }
296
293
  function inferWidget(inner) {
297
- const def = defOf(inner);
298
- switch (def.type) {
299
- case 'string':
300
- return def['format'] === 'email' ? 'email' : 'text';
301
- case 'number':
302
- case 'int':
303
- return 'number';
304
- case 'boolean':
305
- return 'checkbox';
306
- case 'date':
307
- return 'date';
308
- case 'enum':
309
- case 'literal':
310
- return 'select';
311
- case 'array':
312
- return 'array';
313
- case 'object':
314
- return 'object';
315
- case 'union':
316
- return def['discriminator'] !== undefined ? 'union' : null;
317
- default:
318
- return null;
319
- }
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
+ }
320
308
  }
321
309
  function enumOptions(inner) {
322
- const def = defOf(inner);
323
- if (def.type === 'enum') {
324
- const values = inner.options;
325
- return values.map((v) => ({ label: String(v), value: v }));
326
- }
327
- if (def.type === 'literal') {
328
- const values = def['values'];
329
- return values.map((v) => ({ label: String(v), value: v }));
330
- }
331
- 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
+ }));
332
319
  }
333
- /** Reads the discriminator literal of a union variant's tag field. */
334
320
  function discriminatorValue(variant, discriminator) {
335
- const shape = variant.shape;
336
- const tagDef = defOf(unwrap(shape[discriminator]).inner);
337
- const values = tagDef['values'];
338
- if (!values?.length) {
339
- throw new Error(`[zodToFieldDescriptors] Discriminated union variant has no literal value for ` +
340
- `discriminator "${discriminator}".`);
341
- }
342
- 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];
343
325
  }
344
326
  function walk(schema, key, path) {
345
- const meta = readHelixMeta(schema);
346
- const { inner, required } = unwrap(schema);
347
- const def = defOf(inner);
348
- const inferred = inferWidget(inner);
349
- const widget = meta.widget ?? inferred;
350
- if (widget === null || widget === undefined) {
351
- if (typeof ngDevMode !== 'undefined' && ngDevMode) {
352
- throw new Error(`[zodToFieldDescriptors] Zod type "${def.type}" at path "${path.join('.')}" has no ` +
353
- `automatic widget mapping. Set { widget: '...' } via helixMeta() to render it with a ` +
354
- `registered (custom) widget.`);
355
- }
356
- // Production: render nothing rather than crash — fall through as plain text.
357
- return {
358
- key,
359
- path,
360
- widget: 'text',
361
- meta,
362
- zodType: inner,
363
- zodSource: schema,
364
- required,
365
- inputType: 'text',
366
- };
367
- }
368
- const descriptor = {
369
- key,
370
- path,
371
- widget,
372
- meta,
373
- zodType: inner,
374
- zodSource: schema,
375
- required,
376
- inputType: INPUT_TYPES[widget],
377
- options: normalizeOptions(meta.options) ?? enumOptions(inner),
378
- };
379
- if (def.type === 'object') {
380
- const shape = inner.shape;
381
- const children = Object.entries(shape).map(([childKey, childSchema], index) => ({
382
- index,
383
- descriptor: walk(childSchema, childKey, [...path, childKey]),
384
- }));
385
- children.sort((a, b) => (a.descriptor.meta.order ?? Number.MAX_SAFE_INTEGER) -
386
- (b.descriptor.meta.order ?? Number.MAX_SAFE_INTEGER) || a.index - b.index);
387
- descriptor.children = children.map((c) => c.descriptor);
388
- }
389
- if (def.type === 'array') {
390
- descriptor.itemDescriptor = walk(def['element'], '', path);
391
- }
392
- if (widget === 'union' && def.type === 'union') {
393
- const discriminator = def['discriminator'];
394
- const variants = new Map();
395
- for (const variant of inner.options) {
396
- variants.set(discriminatorValue(variant, discriminator), walk(variant, key, path));
397
- }
398
- descriptor.union = { discriminator, variants };
399
- }
400
- 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;
401
376
  }
402
- /**
403
- * Walks an annotated `z.object()` schema and produces the recursive
404
- * {@link HelixFieldDescriptor} tree consumed by the dynamic form renderer.
405
- *
406
- * Widget inference: string→text (`z.email()`→email), number→number,
407
- * boolean→checkbox, date→date, enum/literal→select, array→array,
408
- * object→object, discriminated union→union. Anything else requires a
409
- * `widget` override in {@link HelixFieldMeta} (throws in dev mode otherwise).
410
- */
411
377
  function zodToFieldDescriptors(schema) {
412
- return walk(schema, '', []);
413
- }
414
-
415
- /**
416
- * Renders a complete signal form from an annotated Zod object schema.
417
- *
418
- * - Validation: root-level `validateStandardSchema` — Zod issues are routed to
419
- * the matching fields.
420
- * - Model: pass your own `WritableSignal` via `model`, or let the component
421
- * derive an initial value from the schema (`buildDefaultValue`).
422
- * - Swapping the `schema` input recreates the form and resets its state.
423
- * - `submitted` emits the **parsed** (`schema.parse`) value on valid submit.
424
- *
425
- * Requires `provideHelixDynamicForms()` in the injector chain.
426
- */
427
- class HelixDynamicForm {
428
- schema = input.required(/* @ts-ignore */
429
- ...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
430
- /** Optional external model — omit to derive the initial value from the schema. */
431
- model = input(/* @ts-ignore */
432
- ...(ngDevMode ? [undefined, { debugName: "model" }] : /* istanbul ignore next */ []));
433
- submitLabel = input('Submit', /* @ts-ignore */
434
- ...(ngDevMode ? [{ debugName: "submitLabel" }] : /* istanbul ignore next */ []));
435
- showSubmit = input(true, /* @ts-ignore */
436
- ...(ngDevMode ? [{ debugName: "showSubmit" }] : /* istanbul ignore next */ []));
437
- submitted = output();
438
- #injector = inject(Injector);
439
- descriptors = computed(() => zodToFieldDescriptors(this.schema()), /* @ts-ignore */
440
- ...(ngDevMode ? [{ debugName: "descriptors" }] : /* istanbul ignore next */ []));
441
- #model = computed(() => this.model() ?? signal(buildDefaultValue(this.schema())), /* @ts-ignore */
442
- ...(ngDevMode ? [{ debugName: "#model" }] : /* istanbul ignore next */ []));
443
- /** The form's `FieldTree` — recreated (state reset) when the schema changes. */
444
- formTree = computed(() => {
445
- const model = this.#model();
446
- const schema = this.schema();
447
- const root = this.descriptors();
448
- return untracked(() => form(model, buildHelixSchema(schema, root), { injector: this.#injector }));
449
- }, /* @ts-ignore */
450
- ...(ngDevMode ? [{ debugName: "formTree" }] : /* istanbul ignore next */ []));
451
- childField(descriptor) {
452
- return this.formTree()[descriptor.key];
453
- }
454
- async handleSubmit(event) {
455
- event.preventDefault();
456
- await submit(this.formTree(), async (tree) => {
457
- this.submitted.emit(this.schema().parse(tree().value()));
458
- return undefined;
459
- });
460
- }
461
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDynamicForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
462
- 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, "", []);
463
379
  }
464
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDynamicForm, decorators: [{
465
- type: Component,
466
- 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"] }]
467
- }], 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"] }] } });
468
-
469
- // Mirrors Validators' internal EMPTY_VALUES — defined locally to avoid
470
- // depending on unexported internals of @gravionlabs/helix-core.
471
- 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
+ ];
472
521
  const isEmptyValue = (v) => EMPTY_VALUES.includes(v);
473
- /**
474
- * Maps a single `ZodIssue` to the corresponding `ValidatorKey`.
475
- *
476
- * NOTE: Zod v4 breaking changes accounted for here:
477
- * - `invalid_string` → `invalid_format` with `format` property (not `validation`)
478
- * - `too_small`/`too_big` use `origin` instead of `type`
479
- * - `not_integer` removed → `invalid_type` with `expected: 'int'`
480
- * - `invalid_type` for null/undefined no longer has a `received` field;
481
- * detected via the `value` parameter instead
482
- *
483
- * Returns `null` for issue codes with no automatic mapping — caller handles
484
- * these via `fallbackKey` (Option B).
485
- */
486
522
  function zodIssueToHelixKey(issue, value) {
487
- switch (issue.code) {
488
- case ZodIssueCode.invalid_type:
489
- // Zod v4 dropped the `received` field on invalid_type issues.
490
- // Detect required (null/undefined) errors by checking the control value directly.
491
- if (isEmptyValue(value))
492
- return ValidatorKey.Required;
493
- // integer: Zod v4 changed not_integer → invalid_type { expected: 'int' }
494
- if (issue.expected === 'int')
495
- return ValidatorKey.Integer;
496
- // numeric type mismatch
497
- if (issue.expected === 'number' ||
498
- issue.expected === 'float') {
499
- return ValidatorKey.Number;
500
- }
501
- return null;
502
- case ZodIssueCode.invalid_format:
503
- // Zod v4: replaces invalid_string for all format-based string validations.
504
- // The `format` property holds the validation name.
505
- {
506
- const fmt = issue.format;
507
- if (fmt === 'email')
508
- return ValidatorKey.Email;
509
- if (fmt === 'regex')
510
- return ValidatorKey.Pattern;
511
- if (fmt === 'datetime' || fmt === 'date' || fmt === 'time')
512
- return ValidatorKey.Date;
513
- }
514
- return null;
515
- case ZodIssueCode.too_small:
516
- // Zod v4: uses `origin` instead of `type` to identify the validated construct.
517
- {
518
- const origin = issue.origin;
519
- if (origin === 'string' || origin === 'array')
520
- return ValidatorKey.MinLength;
521
- if (origin === 'number')
522
- return ValidatorKey.Min;
523
- }
524
- return null;
525
- case ZodIssueCode.too_big:
526
- {
527
- const origin = issue.origin;
528
- if (origin === 'string' || origin === 'array')
529
- return ValidatorKey.MaxLength;
530
- if (origin === 'number')
531
- return ValidatorKey.Max;
532
- }
533
- return null;
534
- case ZodIssueCode.custom:
535
- return null; // must be handled via fallbackKey (Option B)
536
- default:
537
- return null;
538
- }
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
+ }
539
554
  }
540
-
541
- /** Built-in signal-forms error kinds → ValidatorKey. */
542
555
  const NG_KIND_TO_HELIX = {
543
- required: ValidatorKey.Required,
544
- min: ValidatorKey.Min,
545
- max: ValidatorKey.Max,
546
- minLength: ValidatorKey.MinLength,
547
- maxLength: ValidatorKey.MaxLength,
548
- pattern: ValidatorKey.Pattern,
549
- 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
550
563
  };
551
- /**
552
- * Maps a signal-forms `ValidationError` to the `ValidatorKey` message
553
- * convention shared with `HelixZodValidators.fromZod`.
554
- *
555
- * @param error The signal-forms validation error.
556
- * @param value The current field value — used to distinguish Required from
557
- * type errors on Zod `invalid_type` issues (Zod v4 dropped the
558
- * `received` field).
559
- */
560
564
  function helixErrorKey(error, value) {
561
- if (error.kind === 'standardSchema') {
562
- const issue = error.issue;
563
- return zodIssueToHelixKey(issue, value);
564
- }
565
- 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;
566
570
  }
567
571
  function defaultMessage(error) {
568
- if (error.kind === 'standardSchema') {
569
- const issue = error.issue;
570
- return issue.message ?? null;
571
- }
572
- return error.message ?? null;
572
+ if (error.kind === "standardSchema") return error.issue.message ?? null;
573
+ return error.message ?? null;
573
574
  }
574
- /**
575
- * Returns the message of the first displayable error, mirroring
576
- * `HelixFormField`'s first-error convention.
577
- *
578
- * Order of precedence per error: `resolver` result → Zod issue message /
579
- * signal-forms error message. Errors yielding no message are skipped.
580
- */
581
575
  function helixFirstErrorMessage(errors, options = {}) {
582
- for (const error of errors) {
583
- const resolved = options.resolver?.(error, helixErrorKey(error, options.value));
584
- const message = resolved ?? defaultMessage(error);
585
- if (message)
586
- return message;
587
- }
588
- return null;
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;
589
581
  }
590
-
591
- /**
592
- * Base class for dynamic-form widgets. A widget is a standalone component with
593
- * the two inputs below; `HelixDynamicField` instantiates it via
594
- * `NgComponentOutlet` and feeds them in.
595
- */
596
- class HelixFieldWidgetBase {
597
- field = input.required(/* @ts-ignore */
598
- ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
599
- descriptor = input.required(/* @ts-ignore */
600
- ...(ngDevMode ? [{ debugName: "descriptor" }] : /* istanbul ignore next */ []));
601
- #errorResolver = inject(HELIX_ERROR_MESSAGE_RESOLVER, { optional: true });
602
- state = computed(() => this.field()(), /* @ts-ignore */
603
- ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
604
- /** Touched-gated first error message — mirrors `HelixFormField.activeError`. */
605
- firstError = computed(() => {
606
- const state = this.state();
607
- if (!state.touched())
608
- return null;
609
- return helixFirstErrorMessage(state.errors(), {
610
- value: state.value(),
611
- resolver: this.#errorResolver ?? undefined,
612
- });
613
- }, /* @ts-ignore */
614
- ...(ngDevMode ? [{ debugName: "firstError" }] : /* istanbul ignore next */ []));
615
- label = computed(() => this.descriptor().meta.label ?? humanize(this.descriptor().key), /* @ts-ignore */
616
- ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
617
- hint = computed(() => this.descriptor().meta.hint, /* @ts-ignore */
618
- ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
619
- placeholder = computed(() => this.descriptor().meta.placeholder ?? '', /* @ts-ignore */
620
- ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
621
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixFieldWidgetBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
622
- 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 });
623
- }
624
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixFieldWidgetBase, decorators: [{
625
- type: Directive
626
- }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], descriptor: [{ type: i0.Input, args: [{ isSignal: true, alias: "descriptor", required: true }] }] } });
627
- /** `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
+ });
628
655
  function humanize(key) {
629
- if (!key)
630
- return '';
631
- const words = key
632
- .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
633
- .replace(/[_-]+/g, ' ')
634
- .toLowerCase();
635
- return words.charAt(0).toUpperCase() + words.slice(1);
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);
636
659
  }
637
-
638
- /** Built-in widget for `z.array()` fields — item list with add/remove. */
639
- class HelixArrayWidget extends HelixFieldWidgetBase {
640
- itemFields = computed(() => {
641
- const length = (this.state().value() ?? []).length;
642
- return Array.from({ length }, (_, index) => this.field()[index]);
643
- }, /* @ts-ignore */
644
- ...(ngDevMode ? [{ debugName: "itemFields" }] : /* istanbul ignore next */ []));
645
- addLabel = computed(() => this.descriptor().meta.addLabel ?? 'Add', /* @ts-ignore */
646
- ...(ngDevMode ? [{ debugName: "addLabel" }] : /* istanbul ignore next */ []));
647
- removeLabel = computed(() => this.descriptor().meta.removeLabel ?? 'Remove', /* @ts-ignore */
648
- ...(ngDevMode ? [{ debugName: "removeLabel" }] : /* istanbul ignore next */ []));
649
- add() {
650
- const item = this.descriptor().itemDescriptor;
651
- if (!item)
652
- return;
653
- const value = buildDefaultValue(item.zodSource);
654
- this.state().value.update((current) => [...(current ?? []), value]);
655
- }
656
- removeAt(index) {
657
- this.state().value.update((current) => (current ?? []).filter((_, i) => i !== index));
658
- }
659
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixArrayWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
660
- 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 });
661
- }
662
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixArrayWidget, decorators: [{
663
- type: Component,
664
- 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"] }]
665
- }] });
666
-
667
- /** Built-in widget for boolean fields — checkbox with inline label. */
668
- class HelixCheckboxWidget extends HelixFieldWidgetBase {
669
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixCheckboxWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
670
- 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 });
671
- }
672
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixCheckboxWidget, decorators: [{
673
- type: Component,
674
- 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"] }]
675
- }] });
676
-
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
+ });
677
766
  function toIsoDate(value) {
678
- const y = value.getFullYear().toString().padStart(4, '0');
679
- const m = (value.getMonth() + 1).toString().padStart(2, '0');
680
- const d = value.getDate().toString().padStart(2, '0');
681
- return `${y}-${m}-${d}`;
682
- }
683
- /**
684
- * Native `<input type="date">` exposed as a signal-forms `FormValueControl`
685
- * with a `Date | null` model — string↔Date conversion via `transformedValue`.
686
- */
687
- class HelixDateInput {
688
- value = model.required(/* @ts-ignore */
689
- ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
690
- disabled = input(false, /* @ts-ignore */
691
- ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
692
- readonly = input(false, /* @ts-ignore */
693
- ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
694
- touched = model(false, /* @ts-ignore */
695
- ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
696
- rawValue = transformedValue(this.value, {
697
- parse: (raw) => {
698
- if (raw === '')
699
- return { value: null };
700
- const parsed = new Date(`${raw}T00:00:00`);
701
- if (Number.isNaN(parsed.getTime())) {
702
- return { error: { kind: 'parse', message: `"${raw}" is not a valid date` } };
703
- }
704
- return { value: parsed };
705
- },
706
- format: (value) => (value ? toIsoDate(value) : ''),
707
- });
708
- onInput(event) {
709
- this.rawValue.set(event.target.value);
710
- }
711
- onBlur() {
712
- this.touched.set(true);
713
- }
714
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDateInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
715
- 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 });
767
+ return `${value.getFullYear().toString().padStart(4, "0")}-${(value.getMonth() + 1).toString().padStart(2, "0")}-${value.getDate().toString().padStart(2, "0")}`;
716
768
  }
717
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDateInput, decorators: [{
718
- type: Component,
719
- 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"] }]
720
- }], 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"] }] } });
721
-
722
- /** Built-in widget for `z.date()` fields. */
723
- class HelixDateWidget extends HelixFieldWidgetBase {
724
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDateWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
725
- 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 });
726
- }
727
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixDateWidget, decorators: [{
728
- type: Component,
729
- 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"] }]
730
- }] });
731
-
732
- /** Built-in widget for numeric fields. */
733
- class HelixNumberWidget extends HelixFieldWidgetBase {
734
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixNumberWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
735
- 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 });
736
- }
737
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixNumberWidget, decorators: [{
738
- type: Component,
739
- 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"] }]
740
- }] });
741
-
742
- /** Built-in widget for nested `z.object()` fields — fieldset + recursion. */
743
- class HelixObjectWidget extends HelixFieldWidgetBase {
744
- childField(key) {
745
- return this.field()[key];
746
- }
747
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixObjectWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
748
- 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 });
749
- }
750
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixObjectWidget, decorators: [{
751
- type: Component,
752
- 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"] }]
753
- }] });
754
-
755
- /**
756
- * Built-in widget for enum/select fields — wraps `HelixSelect`, which the
757
- * signal-forms `FormField` directive binds via its `ControlValueAccessor`.
758
- */
759
- class HelixSelectWidget extends HelixFieldWidgetBase {
760
- selectOptions = computed(() => [...(this.descriptor().options ?? [])], /* @ts-ignore */
761
- ...(ngDevMode ? [{ debugName: "selectOptions" }] : /* istanbul ignore next */ []));
762
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixSelectWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
763
- 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 });
764
- }
765
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixSelectWidget, decorators: [{
766
- type: Component,
767
- 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"] }]
768
- }] });
769
-
770
- /** Built-in widget for `text`, `email` and `password` fields. */
771
- class HelixTextWidget extends HelixFieldWidgetBase {
772
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixTextWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
773
- 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 });
774
- }
775
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixTextWidget, decorators: [{
776
- type: Component,
777
- 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"] }]
778
- }] });
779
-
780
- /** Built-in widget for multiline text (`widget: 'textarea'`). */
781
- class HelixTextareaWidget extends HelixFieldWidgetBase {
782
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixTextareaWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
783
- 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 });
784
- }
785
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixTextareaWidget, decorators: [{
786
- type: Component,
787
- 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"] }]
788
- }] });
789
-
790
- /**
791
- * Built-in widget for `z.discriminatedUnion()` fields: a discriminator select
792
- * plus the active variant's fields.
793
- *
794
- * Switching the discriminator **resets** the union value to the new variant's
795
- * default (preserving the discriminator) — mandatory so the model always
796
- * matches the active variant's shape and all field paths exist.
797
- */
798
- class HelixUnionWidget extends HelixFieldWidgetBase {
799
- discriminator = computed(() => this.descriptor().union?.discriminator ?? '', /* @ts-ignore */
800
- ...(ngDevMode ? [{ debugName: "discriminator" }] : /* istanbul ignore next */ []));
801
- tags = computed(() => [...(this.descriptor().union?.variants.keys() ?? [])], /* @ts-ignore */
802
- ...(ngDevMode ? [{ debugName: "tags" }] : /* istanbul ignore next */ []));
803
- activeTag = computed(() => this.state().value()?.[this.discriminator()], /* @ts-ignore */
804
- ...(ngDevMode ? [{ debugName: "activeTag" }] : /* istanbul ignore next */ []));
805
- variantChildren = computed(() => {
806
- const variant = this.descriptor().union?.variants.get(this.activeTag());
807
- return (variant?.children ?? []).filter((c) => c.key !== this.discriminator());
808
- }, /* @ts-ignore */
809
- ...(ngDevMode ? [{ debugName: "variantChildren" }] : /* istanbul ignore next */ []));
810
- childField(key) {
811
- return this.field()[key];
812
- }
813
- onTagChange(event) {
814
- const index = Number.parseInt(event.target.value, 10);
815
- const tag = this.tags()[index];
816
- const variant = this.descriptor().union?.variants.get(tag);
817
- if (variant === undefined || tag === this.activeTag())
818
- return;
819
- const next = buildDefaultValue(variant.zodSource);
820
- next[this.discriminator()] = tag;
821
- this.state().value.set(next);
822
- }
823
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixUnionWidget, deps: null, target: i0.ɵɵFactoryTarget.Component });
824
- 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 });
825
- }
826
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: HelixUnionWidget, decorators: [{
827
- type: Component,
828
- 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"] }]
829
- }] });
830
-
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
+ });
831
1354
  const BUILT_IN_WIDGETS = [
832
- { widget: 'text', component: HelixTextWidget },
833
- { widget: 'email', component: HelixTextWidget },
834
- { widget: 'password', component: HelixTextWidget },
835
- { widget: 'textarea', component: HelixTextareaWidget },
836
- { widget: 'number', component: HelixNumberWidget },
837
- { widget: 'checkbox', component: HelixCheckboxWidget },
838
- { widget: 'select', component: HelixSelectWidget },
839
- { widget: 'date', component: HelixDateWidget },
840
- { widget: 'object', component: HelixObjectWidget },
841
- { widget: 'array', component: HelixArrayWidget },
842
- { 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
+ }
843
1399
  ];
844
- /**
845
- * Provides the dynamic-form widget registry (built-in widgets included) and
846
- * optional configuration. Add it to `ApplicationConfig.providers` or a route's
847
- * `providers`.
848
- */
849
1400
  function provideHelixDynamicForms(config = {}) {
850
- const providers = [
851
- { provide: HELIX_DYNAMIC_FIELD_WIDGETS, multi: true, useValue: BUILT_IN_WIDGETS },
852
- // Re-provide the resolver here so it is created in THIS environment
853
- // injector and sees the registrations above. The `providedIn: 'root'`
854
- // fallback instance cannot see route-scoped providers.
855
- HelixFieldWidgetResolver,
856
- ];
857
- if (config.widgets?.length) {
858
- providers.push({ provide: HELIX_DYNAMIC_FIELD_WIDGETS, multi: true, useValue: config.widgets });
859
- }
860
- if (config.errorMessageResolver) {
861
- providers.push({
862
- provide: HELIX_ERROR_MESSAGE_RESOLVER,
863
- useValue: config.errorMessageResolver,
864
- });
865
- }
866
- 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);
867
1416
  }
868
-
869
- /**
870
- * Navigates a `FieldTree` by a root-relative property path
871
- * (e.g. `['address', 'city']`).
872
- */
873
1417
  function fieldAtPath(tree, path) {
874
- let current = tree;
875
- for (const key of path) {
876
- current = current[key];
877
- if (current === undefined) {
878
- throw new Error(`[fieldAtPath] No field at path "${path.join('.')}" (missing key "${key}").`);
879
- }
880
- }
881
- 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;
882
1424
  }
883
-
884
- const HelixZodValidators = {
885
- /**
886
- * Converts a Zod field schema into a Helix-compatible Angular `ValidatorFn`.
887
- *
888
- * Each `ZodIssue` is mapped to its `ValidatorKey`. `HelixFormField` reads
889
- * those keys directly — no template changes required.
890
- *
891
- * All issues from a single `safeParse` are processed simultaneously, producing
892
- * one `ValidationErrors` key per issue — equivalent to stacking multiple
893
- * `Validators` calls.
894
- *
895
- * ### Known gaps
896
- * - `ValidatorKey.OneOf` / `AllOf` — `z.enum()` produces `invalid_value`,
897
- * which has no automatic mapping. Use `fallbackKey` or continue using
898
- * `Validators.oneOf` / `Validators.allOf`.
899
- * - `invalid_type` for `boolean` expected — Helix has no `Boolean` key.
900
- * Use `fallbackKey` or `Validators.pattern`.
901
- *
902
- * @param schema A Zod field schema (e.g. `UserSchema.shape.email`).
903
- * Do NOT pass schemas with `.transform()` — use the
904
- * input-only (pre-transform) variant for form controls.
905
- * @param options `allowEmpty` (default `true`) and optional `fallbackKey`
906
- * for `.refine()` / `custom` validators.
907
- *
908
- * @example
909
- * // Standard field — no fallbackKey needed
910
- * HelixZodValidators.fromZod(UserSchema.shape.email)
911
- *
912
- * @example
913
- * // Schema with .refine() — fallbackKey required (Option B)
914
- * HelixZodValidators.fromZod(
915
- * z.string().refine(v => !banned.includes(v), 'Username not allowed'),
916
- * { fallbackKey: ValidatorKey.Pattern },
917
- * )
918
- *
919
- * @example
920
- * // Required field — disable allowEmpty so empty value triggers Required / MinLength
921
- * HelixZodValidators.fromZod(UserSchema.shape.name, { allowEmpty: false })
922
- */
923
- fromZod(schema, options = {}) {
924
- const { fallbackKey, allowEmpty = true } = options;
925
- return (control) => {
926
- const value = control.value;
927
- // Replicate Validators' allowEmpty = true default:
928
- // skip Zod entirely for empty values unless the caller opts out.
929
- if (allowEmpty && isEmptyValue(value)) {
930
- return null;
931
- }
932
- const result = schema.safeParse(value);
933
- if (result.success)
934
- return null;
935
- const errors = {};
936
- for (const issue of result.error.issues) {
937
- const key = zodIssueToHelixKey(issue, value);
938
- if (key !== null) {
939
- errors[key] = issue.message;
940
- continue;
941
- }
942
- // No automatic mapping — apply Option B logic.
943
- if (fallbackKey !== undefined) {
944
- errors[fallbackKey] = issue.message;
945
- }
946
- else {
947
- // ngDevMode is set by Angular's build toolchain:
948
- // true in development builds, false (tree-shaken) in production.
949
- if (typeof ngDevMode !== 'undefined' && ngDevMode) {
950
- throw new Error(`[HelixZodValidators.fromZod] Zod issue code "${issue.code}" has no ` +
951
- `automatic ValidatorKey mapping.\n` +
952
- `Pass { fallbackKey: ValidatorKey.xxx } as the second argument.\n` +
953
- `Failing issue: ${JSON.stringify(issue, null, 2)}`);
954
- }
955
- // Production: skip the unmapped issue silently.
956
- // The control remains invalid if other issues produce mapped errors.
957
- }
958
- }
959
- return Object.keys(errors).length ? errors : null;
960
- };
961
- },
962
- };
963
-
964
- /*
965
- * Public API Surface of @gravionlabs/helix-zod
966
- */
967
- // Dynamic forms — components
968
-
969
- /**
970
- * Generated bundle index. Do not edit.
971
- */
972
-
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
+ } };
973
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 };
974
- //# sourceMappingURL=gravionlabs-helix-zod.mjs.map
1446
+
1447
+ //# sourceMappingURL=gravionlabs-helix-zod.mjs.map