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