@dvirus-js/angular-signals 0.0.16 → 0.0.18

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.
@@ -77,111 +77,6 @@ function signalOrFunction(value, ...fnArgs) {
77
77
  // console.log(plainObj2); // { a: 10, b: 'Test' }
78
78
  // }
79
79
 
80
- /**
81
- * Normalizes a validation error object by filtering out empty/null values.
82
- *
83
- * Converts a raw validation result into a clean error map containing only
84
- * non-empty string messages. Filters out undefined, null, and empty strings.
85
- *
86
- * @param error - Raw validation error object or null
87
- * @returns Clean error map with only valid error messages
88
- *
89
- * @example
90
- * ```typescript
91
- * normalizeValidationError({ required: 'Error', empty: '', invalid: null });
92
- * // Returns: { required: 'Error' }
93
- * ```
94
- */
95
- function normalizeValidationError(error) {
96
- if (!error)
97
- return {};
98
- return Object.entries(error).reduce((acc, [key, val]) => {
99
- if (typeof val === 'string' && val.length > 0) {
100
- acc[key] = val;
101
- }
102
- return acc;
103
- }, {});
104
- }
105
- /**
106
- * Executes an array of validators and collects all error messages.
107
- *
108
- * Runs each validator function with the provided context and merges all
109
- * error messages into a single error map. Empty/null results are filtered out.
110
- *
111
- * @template TControls - Object type defining available sibling controls
112
- * @template TValue - The type of value being validated
113
- *
114
- * @param validators - Array of validator functions to execute
115
- * @param ctx - Validation context with current value and control accessor
116
- * @returns Combined error map from all validators
117
- *
118
- * @example
119
- * ```typescript
120
- * const errors = collectValidationErrors(
121
- * [signalFormValidators.required, signalFormValidators.minLength(3)],
122
- * { item: { value: '' }, getControl: () => {} }
123
- * );
124
- * // Returns: { required: 'This field is required' }
125
- * ```
126
- */
127
- function collectValidationErrors(validators, ctx) {
128
- if (!validators?.length)
129
- return {};
130
- return validators.reduce((acc, validator) => {
131
- const normalized = normalizeValidationError(validator(ctx));
132
- Object.entries(normalized).forEach(([key, val]) => {
133
- acc[key] = val;
134
- });
135
- return acc;
136
- }, {});
137
- }
138
- /**
139
- * Checks if an error map contains any errors.
140
- *
141
- * Simple utility to determine if a control has validation errors by
142
- * checking if the error map object has any keys.
143
- *
144
- * @param errorMap - Error map to check
145
- * @returns True if there are any errors, false if empty
146
- *
147
- * @example
148
- * ```typescript
149
- * hasErrors({ required: 'Error' }); // true
150
- * hasErrors({}); // false
151
- * ```
152
- */
153
- function hasErrors(errorMap) {
154
- return Object.keys(errorMap).length > 0;
155
- }
156
- /**
157
- * Recursively checks if an error tree structure is completely empty.
158
- *
159
- * Traverses nested error structures (objects and arrays) to determine if
160
- * there are any actual error messages anywhere in the tree. Returns true
161
- * only when the entire structure contains no errors.
162
- *
163
- * @param value - Error tree to check (can be nested objects/arrays)
164
- * @returns True if no errors exist anywhere in the tree
165
- *
166
- * @example
167
- * ```typescript
168
- * isEmptyErrorTree({ name: undefined, address: { street: undefined } }); // true
169
- * isEmptyErrorTree({ name: { required: 'Error' } }); // false
170
- * isEmptyErrorTree([undefined, undefined]); // true
171
- * ```
172
- */
173
- function isEmptyErrorTree(value) {
174
- if (value === null || value === undefined)
175
- return true;
176
- if (Array.isArray(value)) {
177
- return value.every(isEmptyErrorTree);
178
- }
179
- if (typeof value === 'object') {
180
- return Object.values(value).every(isEmptyErrorTree);
181
- }
182
- return false;
183
- }
184
-
185
80
  /**
186
81
  * Creates a signal-based notifier function.
187
82
  *
@@ -919,1304 +814,9 @@ function nestedControlSignal(control, options) {
919
814
  return controlSignal(control, options);
920
815
  }
921
816
 
922
- /**
923
- * Shared empty error map object to avoid creating new objects.
924
- *
925
- * @internal
926
- */
927
- const EMPTY_ERROR_MAP = {};
928
- /**
929
- * Checks if a value is a plain JavaScript object (not array, not null, not class instance).
930
- *
931
- * @internal
932
- * @param value - Value to check
933
- * @returns True if value is a plain object
934
- */
935
- function isPlainObject(value) {
936
- if (!value || typeof value !== 'object')
937
- return false;
938
- const proto = Object.getPrototypeOf(value);
939
- return proto === Object.prototype || proto === null;
940
- }
941
- /**
942
- * Checks if an object contains any Angular signal values.
943
- *
944
- * @internal
945
- * @param obj - Object to check for signal values
946
- * @returns True if any property value is a signal
947
- */
948
- function hasSignalValues(obj) {
949
- return Object.values(obj).some((value) => isSignal(value));
950
- }
951
- /**
952
- * Resolves a SignalOrValue to its actual value, handling nested signal objects.
953
- *
954
- * If the resolved value is a plain object containing signals, it converts
955
- * all signal properties to their values using fromSignalObj.
956
- *
957
- * @internal
958
- * @template TValue - The type of value to resolve
959
- * @param value - Signal or static value to resolve
960
- * @returns The resolved value
961
- */
962
- function resolveControlValue(value) {
963
- const resolved = signalOrValue(value);
964
- if (isPlainObject(resolved) && hasSignalValues(resolved)) {
965
- return fromSignalObj(resolved);
966
- }
967
- return resolved;
968
- }
969
- /**
970
- * Normalizes various control input formats to a standard SignalFormControlConfig.
971
- *
972
- * Handles three input types:
973
- * - SignalFormControl: extracts current value
974
- * - Config object with 'value' property: uses as-is
975
- * - Raw value: wraps in config object
976
- *
977
- * @internal
978
- * @template TValue - The type of control value
979
- * @template TControls - Object type defining available sibling controls
980
- * @param input - Input in any accepted format
981
- * @returns Normalized control configuration
982
- */
983
- function normalizeControlInput(input) {
984
- if (isSignalFormControl(input)) {
985
- return { value: input.value() };
986
- }
987
- if (typeof input === 'object' && input !== null && 'value' in input) {
988
- return input;
989
- }
990
- return { value: input };
991
- }
992
- /**
993
- * Type guard to check if an object is a SignalFormControl.
994
- *
995
- * @template TValue - The type of value the control manages
996
- * @param obj - Object to check
997
- * @returns True if obj is a SignalFormControl
998
- *
999
- * @example
1000
- * ```typescript
1001
- * if (isSignalFormControl(value)) {
1002
- * console.log(value.value()); // TypeScript knows this is a control
1003
- * }
1004
- * ```
1005
- */
1006
- function isSignalFormControl(obj) {
1007
- return (!!obj &&
1008
- typeof obj === 'object' &&
1009
- obj.kind === 'control');
1010
- }
1011
- /**
1012
- * Creates a reactive form control with signal-based state management.
1013
- *
1014
- * Builds a control that wraps a primitive value (string, number, boolean, etc.)
1015
- * with validation, state tracking, and reactive updates using Angular signals.
1016
- *
1017
- * Features:
1018
- * - Reactive value updates through signals
1019
- * - Validators for errors (mark control as invalid)
1020
- * - Warnings (validation messages without invalidating)
1021
- * - Dynamic disabled state based on form context
1022
- * - State tracking (touched, dirty)
1023
- * - Manual error/warning management
1024
- *
1025
- * @template TControls - Object type defining available sibling controls for cross-field validation
1026
- * @template TValue - The type of value this control manages
1027
- *
1028
- * @param input - Control input (raw value, config object, or existing control)
1029
- * @param getControl - Optional accessor function for sibling controls
1030
- * @returns Fully configured SignalFormControl instance
1031
- *
1032
- * @example
1033
- * ```typescript
1034
- * // Simple control
1035
- * const nameControl = createSignalFormControl('John');
1036
- *
1037
- * // With validators
1038
- * const ageControl = createSignalFormControl({
1039
- * value: 25,
1040
- * validators: [signalFormValidators.required, signalFormValidators.min(0)],
1041
- * warnings: [signalFormValidators.max(120)]
1042
- * });
1043
- *
1044
- * // With dynamic disabled
1045
- * const emailControl = createSignalFormControl({
1046
- * value: '',
1047
- * disabled: (ctx) => ctx.getControl('accountType').value() === 'guest'
1048
- * });
1049
- * ```
1050
- */
1051
- function createSignalFormControl(input, getControl) {
1052
- if (isSignalFormControl(input)) {
1053
- return input;
1054
- }
1055
- const config = normalizeControlInput(input);
1056
- const accessControl = getControl ??
1057
- (() => {
1058
- throw new Error('getControl is not available for this control.');
1059
- });
1060
- const initialValue = resolveControlValue(config.value);
1061
- const value = writableSignal(() => resolveControlValue(config.value));
1062
- const manualErrors = signal({}, ...(ngDevMode ? [{ debugName: "manualErrors" }] : []));
1063
- const manualWarnings = signal({}, ...(ngDevMode ? [{ debugName: "manualWarnings" }] : []));
1064
- const selfTouched = signal(false, ...(ngDevMode ? [{ debugName: "selfTouched" }] : []));
1065
- const selfDirty = signal(false, ...(ngDevMode ? [{ debugName: "selfDirty" }] : []));
1066
- const selfDisabled = signal(false, ...(ngDevMode ? [{ debugName: "selfDisabled" }] : []));
1067
- const disabledResolver = config.disabled;
1068
- const disabled = computed(() => {
1069
- const derived = typeof disabledResolver === 'function'
1070
- ? disabledResolver({
1071
- item: { value: value() },
1072
- getControl: accessControl,
1073
- })
1074
- : disabledResolver
1075
- ? signalOrValue(disabledResolver)
1076
- : false;
1077
- return selfDisabled() || derived;
1078
- }, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
1079
- const errors = computed(() => {
1080
- if (disabled())
1081
- return EMPTY_ERROR_MAP;
1082
- const ctx = {
1083
- item: { value: value() },
1084
- getControl: accessControl,
1085
- };
1086
- return {
1087
- ...collectValidationErrors(config.validators, ctx),
1088
- ...manualErrors(),
1089
- };
1090
- }, ...(ngDevMode ? [{ debugName: "errors" }] : []));
1091
- const warnings = computed(() => {
1092
- if (disabled())
1093
- return EMPTY_ERROR_MAP;
1094
- const ctx = {
1095
- item: { value: value() },
1096
- getControl: accessControl,
1097
- };
1098
- return {
1099
- ...collectValidationErrors(config.warnings, ctx),
1100
- ...manualWarnings(),
1101
- };
1102
- }, ...(ngDevMode ? [{ debugName: "warnings" }] : []));
1103
- const invalid = computed(() => !disabled() && hasErrors(errors()), ...(ngDevMode ? [{ debugName: "invalid" }] : []));
1104
- const valid = computed(() => !invalid(), ...(ngDevMode ? [{ debugName: "valid" }] : []));
1105
- const firstError = computed(() => {
1106
- const entries = Object.entries(errors());
1107
- if (!entries.length)
1108
- return undefined;
1109
- const [name, message] = entries[0] ?? ['', ''];
1110
- return { name, message, type: 'error' };
1111
- }, ...(ngDevMode ? [{ debugName: "firstError" }] : []));
1112
- const firstWarning = computed(() => {
1113
- const entries = Object.entries(warnings());
1114
- if (!entries.length)
1115
- return undefined;
1116
- const [name, message] = entries[0] ?? ['', ''];
1117
- return { name, message, type: 'warning' };
1118
- }, ...(ngDevMode ? [{ debugName: "firstWarning" }] : []));
1119
- const firstErrorOrWarning = computed(() => {
1120
- return firstError() ?? firstWarning();
1121
- }, ...(ngDevMode ? [{ debugName: "firstErrorOrWarning" }] : []));
1122
- const setValue = (next, options) => {
1123
- value.set(next);
1124
- if (options?.markDirty ?? true)
1125
- selfDirty.set(true);
1126
- if (options?.markTouched)
1127
- selfTouched.set(true);
1128
- };
1129
- const reset = (next) => {
1130
- value.set(next ?? initialValue);
1131
- selfDirty.set(false);
1132
- selfTouched.set(false);
1133
- manualErrors.set({});
1134
- manualWarnings.set({});
1135
- };
1136
- return {
1137
- kind: 'control',
1138
- value,
1139
- disabled,
1140
- touched: computed(() => selfTouched()),
1141
- dirty: computed(() => selfDirty()),
1142
- errors,
1143
- warnings,
1144
- selfErrors: errors,
1145
- selfWarnings: warnings,
1146
- invalid,
1147
- valid,
1148
- firstError,
1149
- firstWarning,
1150
- firstErrorOrWarning,
1151
- setValue,
1152
- reset,
1153
- markTouched: () => selfTouched.set(true),
1154
- markUntouched: () => selfTouched.set(false),
1155
- markDirty: () => selfDirty.set(true),
1156
- markPristine: () => selfDirty.set(false),
1157
- setDisabled: (next) => selfDisabled.set(next),
1158
- setError: (key, message) => manualErrors.update((current) => ({ ...current, [key]: message })),
1159
- clearError: (key) => manualErrors.update((current) => {
1160
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1161
- const { [key]: _removed, ...rest } = current;
1162
- return rest;
1163
- }),
1164
- clearErrors: () => manualErrors.set({}),
1165
- setWarning: (key, message) => manualWarnings.update((current) => ({ ...current, [key]: message })),
1166
- clearWarning: (key) => manualWarnings.update((current) => {
1167
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1168
- const { [key]: _removed, ...rest } = current;
1169
- return rest;
1170
- }),
1171
- clearWarnings: () => manualWarnings.set({}),
1172
- };
1173
- }
1174
-
1175
- /**
1176
- * Set of valid keys for control configuration objects.
1177
- * Used to distinguish config objects from plain value objects.
1178
- *
1179
- * @internal
1180
- */
1181
- const CONTROL_CONFIG_KEYS$1 = new Set([
1182
- 'value',
1183
- 'validators',
1184
- 'warnings',
1185
- 'disabled',
1186
- ]);
1187
- /**
1188
- * Checks if a value is a control configuration object.
1189
- *
1190
- * Determines if the value has a 'value' property and only contains
1191
- * valid control config keys (value, validators, warnings, disabled).
1192
- *
1193
- * @internal
1194
- * @param value - Value to check
1195
- * @returns True if value is a control config object
1196
- */
1197
- function isControlConfig$1(value) {
1198
- if (!value || typeof value !== 'object')
1199
- return false;
1200
- if (!('value' in value))
1201
- return false;
1202
- return Object.keys(value).every(key => CONTROL_CONFIG_KEYS$1.has(key));
1203
- }
1204
- /**
1205
- * Creates the appropriate control node from various input types.
1206
- *
1207
- * Recursively determines and creates the correct control type:
1208
- * - Existing controls are returned as-is
1209
- * - Arrays become SignalFormArray
1210
- * - Objects (non-config) become SignalForm (group)
1211
- * - Primitives/configs become SignalFormControl
1212
- *
1213
- * @internal
1214
- * @template TValue - The type of value to create a control for
1215
- * @template TControls - Object type defining available sibling controls
1216
- * @param input - Input value in any accepted format
1217
- * @param getControl - Accessor for sibling controls
1218
- * @returns Appropriate control type for the input
1219
- */
1220
- function createNodeFromInput$1(input, getControl) {
1221
- if (isSignalFormControl(input)) {
1222
- return input;
1223
- }
1224
- if (isSignalFormGroup(input)) {
1225
- return input;
1226
- }
1227
- if (isSignalFormArray(input)) {
1228
- return input;
1229
- }
1230
- if (Array.isArray(input)) {
1231
- return createSignalFormArray(input, getControl);
1232
- }
1233
- if (typeof input === 'object' && input !== null && !isControlConfig$1(input)) {
1234
- return createSignalFormGroup(input);
1235
- }
1236
- return createSignalFormControl(input, getControl);
1237
- }
1238
- /**
1239
- * Type guard to check if an object is a SignalForm (form group).
1240
- *
1241
- * @template TData - Object type defining the form structure
1242
- * @param obj - Object to check
1243
- * @returns True if obj is a SignalForm
1244
- *
1245
- * @example
1246
- * ```typescript
1247
- * if (isSignalFormGroup(value)) {
1248
- * console.log(value.controls.name); // TypeScript knows this is a form group
1249
- * }
1250
- * ```
1251
- */
1252
- function isSignalFormGroup(obj) {
1253
- return (!!obj &&
1254
- typeof obj === 'object' &&
1255
- obj.kind === 'group');
1256
- }
1257
- /**
1258
- * Creates a reactive form group for managing structured form data.
1259
- *
1260
- * Builds a typed form container that holds multiple named controls, groups, or arrays.
1261
- * Each property in the input object becomes a control with full signal-based reactivity.
1262
- * Provides type-safe access to controls and tracks collective validation state.
1263
- *
1264
- * Features:
1265
- * - Type-safe control access via `.controls` property
1266
- * - Reactive value and error tracking across all controls
1267
- * - Collective state management (touched, dirty, valid)
1268
- * - Manual error/warning management at group level
1269
- * - Support for nested groups and arrays
1270
- * - Cross-field validation via getControl accessor
1271
- *
1272
- * @template TData - Object type defining the structure and types of all controls
1273
- *
1274
- * @param inputs - Object mapping property names to their control inputs
1275
- * @returns Fully configured SignalForm instance
1276
- *
1277
- * @example
1278
- * ```typescript
1279
- * // Simple form
1280
- * const form = createSignalFormGroup({
1281
- * name: 'John',
1282
- * age: 25
1283
- * });
1284
- *
1285
- * // With validators and nested structure
1286
- * const form = createSignalFormGroup<User>({
1287
- * email: {
1288
- * value: '',
1289
- * validators: [signalFormValidators.required, signalFormValidators.email]
1290
- * },
1291
- * age: {
1292
- * value: 25,
1293
- * validators: [signalFormValidators.min(0)],
1294
- * warnings: [signalFormValidators.max(120)]
1295
- * },
1296
- * address: {
1297
- * street: '123 Main St',
1298
- * city: 'NYC'
1299
- * },
1300
- * hobbies: ['coding', 'gaming']
1301
- * });
1302
- *
1303
- * // Access controls
1304
- * form.controls.email.value(); // Type-safe access
1305
- * form.getControl('age').setValue(30);
1306
- * ```
1307
- */
1308
- function createSignalFormGroup(inputs) {
1309
- const controls = {};
1310
- const getControl = key => controls[key];
1311
- Object.entries(inputs).forEach(([key, value]) => {
1312
- controls[key] = createNodeFromInput$1(value, getControl);
1313
- });
1314
- const selfDirty = signal(false, ...(ngDevMode ? [{ debugName: "selfDirty" }] : []));
1315
- const selfTouched = signal(false, ...(ngDevMode ? [{ debugName: "selfTouched" }] : []));
1316
- const selfDisabled = signal(false, ...(ngDevMode ? [{ debugName: "selfDisabled" }] : []));
1317
- const manualErrors = signal({}, ...(ngDevMode ? [{ debugName: "manualErrors" }] : []));
1318
- const manualWarnings = signal({}, ...(ngDevMode ? [{ debugName: "manualWarnings" }] : []));
1319
- const value = computed(() => {
1320
- return Object.entries(controls).reduce((acc, [key, control]) => {
1321
- acc[key] = control.value();
1322
- return acc;
1323
- }, {});
1324
- }, ...(ngDevMode ? [{ debugName: "value" }] : []));
1325
- const errors = computed(() => {
1326
- const all = {};
1327
- Object.entries(controls).forEach(([key, control]) => {
1328
- if (control.kind === 'control') {
1329
- const map = control.errors();
1330
- all[key] = (hasErrors(map) ? map : undefined);
1331
- return;
1332
- }
1333
- const childErrors = control.errors();
1334
- all[key] = (isEmptyErrorTree(childErrors) ? undefined : childErrors);
1335
- });
1336
- return all;
1337
- }, ...(ngDevMode ? [{ debugName: "errors" }] : []));
1338
- const warnings = computed(() => {
1339
- const all = {};
1340
- Object.entries(controls).forEach(([key, control]) => {
1341
- if (control.kind === 'control') {
1342
- const map = control.warnings();
1343
- all[key] = (hasErrors(map) ? map : undefined);
1344
- return;
1345
- }
1346
- const childWarnings = control.warnings();
1347
- all[key] = (isEmptyErrorTree(childWarnings) ? undefined : childWarnings);
1348
- });
1349
- return all;
1350
- }, ...(ngDevMode ? [{ debugName: "warnings" }] : []));
1351
- const selfErrors = computed(() => {
1352
- if (selfDisabled())
1353
- return {};
1354
- return { ...manualErrors() };
1355
- }, ...(ngDevMode ? [{ debugName: "selfErrors" }] : []));
1356
- const selfWarnings = computed(() => {
1357
- if (selfDisabled())
1358
- return {};
1359
- return { ...manualWarnings() };
1360
- }, ...(ngDevMode ? [{ debugName: "selfWarnings" }] : []));
1361
- const invalid = computed(() => {
1362
- if (selfDisabled())
1363
- return false;
1364
- return (hasErrors(selfErrors()) ||
1365
- Object.values(controls).some(control => control.invalid()));
1366
- }, ...(ngDevMode ? [{ debugName: "invalid" }] : []));
1367
- const valid = computed(() => !invalid(), ...(ngDevMode ? [{ debugName: "valid" }] : []));
1368
- const touched = computed(() => selfTouched() ||
1369
- Object.values(controls).some(control => control.touched()), ...(ngDevMode ? [{ debugName: "touched" }] : []));
1370
- const dirty = computed(() => selfDirty() ||
1371
- Object.values(controls).some(control => control.dirty()), ...(ngDevMode ? [{ debugName: "dirty" }] : []));
1372
- const setValue = (nextValues, options) => {
1373
- Object.entries(nextValues).forEach(([key, nextValue]) => {
1374
- const control = controls[key];
1375
- control?.setValue(nextValue, options);
1376
- });
1377
- if (options?.markDirty ?? true)
1378
- selfDirty.set(true);
1379
- if (options?.markTouched)
1380
- selfTouched.set(true);
1381
- };
1382
- const reset = (nextValues) => {
1383
- if (nextValues) {
1384
- setValue(nextValues, { markDirty: false });
1385
- }
1386
- else {
1387
- Object.values(controls).forEach(control => control.reset());
1388
- }
1389
- selfDirty.set(false);
1390
- selfTouched.set(false);
1391
- manualErrors.set({});
1392
- manualWarnings.set({});
1393
- };
1394
- const setDisabled = (disabled, options) => {
1395
- selfDisabled.set(disabled);
1396
- if (!options?.onlySelf) {
1397
- Object.values(controls).forEach(control => control.setDisabled(disabled));
1398
- }
1399
- };
1400
- return {
1401
- kind: 'group',
1402
- controls,
1403
- value,
1404
- errors,
1405
- warnings,
1406
- selfErrors,
1407
- selfWarnings,
1408
- disabled: computed(() => selfDisabled()),
1409
- touched,
1410
- dirty,
1411
- invalid,
1412
- valid,
1413
- getControl,
1414
- setValue,
1415
- reset,
1416
- markTouched: () => selfTouched.set(true),
1417
- markUntouched: () => selfTouched.set(false),
1418
- markDirty: () => selfDirty.set(true),
1419
- markPristine: () => selfDirty.set(false),
1420
- markAllTouched: () => Object.values(controls).forEach(control => control.markTouched()),
1421
- setDisabled,
1422
- setError: (key, message) => manualErrors.update(current => ({ ...current, [key]: message })),
1423
- clearError: (key) => manualErrors.update(current => {
1424
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1425
- const { [key]: _removed, ...rest } = current;
1426
- return rest;
1427
- }),
1428
- clearErrors: () => manualErrors.set({}),
1429
- setWarning: (key, message) => manualWarnings.update(current => ({ ...current, [key]: message })),
1430
- clearWarning: (key) => manualWarnings.update(current => {
1431
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1432
- const { [key]: _removed, ...rest } = current;
1433
- return rest;
1434
- }),
1435
- clearWarnings: () => manualWarnings.set({}),
1436
- };
1437
- }
1438
- /**
1439
- * Helper function to create a standalone form control.
1440
- *
1441
- * Creates a control without sibling control access. Useful for creating
1442
- * individual controls outside of a form group context.
1443
- *
1444
- * @template TValue - The type of value the control manages
1445
- * @param input - Control input (raw value, config object, or existing control)
1446
- * @returns SignalFormControl instance
1447
- *
1448
- * @example
1449
- * ```typescript
1450
- * const nameControl = formControl('John');
1451
- * const ageControl = formControl({
1452
- * value: 25,
1453
- * validators: [signalFormValidators.min(0)]
1454
- * });
1455
- * ```
1456
- */
1457
- function formControl(input) {
1458
- return createSignalFormControl(input, undefined);
1459
- }
1460
- /**
1461
- * Helper function to create a standalone form array.
1462
- *
1463
- * Creates an array without sibling control access. Useful for creating
1464
- * array controls outside of a form group context.
1465
- *
1466
- * @template TValue - The type of each item in the array
1467
- * @param input - Array of initial items
1468
- * @returns SignalFormArray instance
1469
- *
1470
- * @example
1471
- * ```typescript
1472
- * const tagsArray = formArray(['tag1', 'tag2', 'tag3']);
1473
- * const addressesArray = formArray<Address>([
1474
- * { street: '123 Main', city: 'NYC' }
1475
- * ]);
1476
- * ```
1477
- */
1478
- function formArray(input) {
1479
- return createSignalFormArray(input, undefined);
1480
- }
1481
- /**
1482
- * Helper function to create a form group.
1483
- *
1484
- * Alias for createSignalFormGroup. Creates a typed form with multiple controls.
1485
- *
1486
- * @template TData - Object type defining the form structure
1487
- * @param input - Object mapping property names to control inputs
1488
- * @returns SignalForm instance
1489
- *
1490
- * @example
1491
- * ```typescript
1492
- * const form = formGroup({
1493
- * name: 'John',
1494
- * email: {
1495
- * value: 'john@example.com',
1496
- * validators: [signalFormValidators.email]
1497
- * }
1498
- * });
1499
- * ```
1500
- */
1501
- function formGroup(input) {
1502
- return createSignalFormGroup(input);
1503
- }
1504
- /**
1505
- * Primary API for creating signal-based reactive forms.
1506
- *
1507
- * Alias for `formGroup`. This is the main entry point for creating forms.
1508
- * Provides type-safe, signal-based form state management with built-in validation.
1509
- *
1510
- * @example
1511
- * ```typescript
1512
- * // Basic form
1513
- * const form = signalForm({ name: 'John', age: 25 });
1514
- *
1515
- * // Complex form with validation
1516
- * const form = signalForm<Person>({
1517
- * name: {
1518
- * value: '',
1519
- * validators: [signalFormValidators.required, signalFormValidators.minLength(2)]
1520
- * },
1521
- * age: {
1522
- * value: 30,
1523
- * validators: [signalFormValidators.min(0)],
1524
- * warnings: [signalFormValidators.max(120)],
1525
- * disabled: (ctx) => ctx.getControl('name').value() === 'admin'
1526
- * },
1527
- * address: {
1528
- * street: '123 Main St',
1529
- * city: 'NYC'
1530
- * },
1531
- * hobbies: ['coding', 'gaming']
1532
- * });
1533
- *
1534
- * // Access form state
1535
- * console.log(form.value()); // { name: '', age: 30, address: {...}, hobbies: [...] }
1536
- * console.log(form.valid()); // boolean
1537
- * console.log(form.controls.name.errors()); // { required: 'This field is required' }
1538
- * ```
1539
- */
1540
- const signalForm = formGroup;
1541
-
1542
- /**
1543
- * Set of valid keys for control configuration objects.
1544
- * Used to distinguish config objects from plain value objects.
1545
- *
1546
- * @internal
1547
- */
1548
- const CONTROL_CONFIG_KEYS = new Set([
1549
- 'value',
1550
- 'validators',
1551
- 'warnings',
1552
- 'disabled',
1553
- ]);
1554
- /**
1555
- * Checks if a value is a control configuration object.
1556
- *
1557
- * Determines if the value has a 'value' property and only contains
1558
- * valid control config keys (value, validators, warnings, disabled).
1559
- *
1560
- * @internal
1561
- * @param value - Value to check
1562
- * @returns True if value is a control config object
1563
- */
1564
- function isControlConfig(value) {
1565
- if (!value || typeof value !== 'object')
1566
- return false;
1567
- if (!('value' in value))
1568
- return false;
1569
- return Object.keys(value).every(key => CONTROL_CONFIG_KEYS.has(key));
1570
- }
1571
- /**
1572
- * Creates the appropriate control node from various input types.
1573
- *
1574
- * Recursively determines and creates the correct control type:
1575
- * - Existing controls are returned as-is
1576
- * - Arrays become SignalFormArray
1577
- * - Objects (non-config) become SignalForm (group)
1578
- * - Primitives/configs become SignalFormControl
1579
- *
1580
- * @internal
1581
- * @template TItem - The type of item to create a control for
1582
- * @template TControls - Object type defining available sibling controls
1583
- * @param input - Input value in any accepted format
1584
- * @param getControl - Accessor for sibling controls
1585
- * @returns Appropriate control type for the input
1586
- */
1587
- function createNodeFromInput(input, getControl) {
1588
- if (isSignalFormControl(input)) {
1589
- return input;
1590
- }
1591
- if (isSignalFormGroup(input)) {
1592
- return input;
1593
- }
1594
- if (isSignalFormArray(input)) {
1595
- return input;
1596
- }
1597
- if (Array.isArray(input)) {
1598
- return createSignalFormArray(input, getControl);
1599
- }
1600
- if (typeof input === 'object' && input !== null && !isControlConfig(input)) {
1601
- return createSignalFormGroup(input);
1602
- }
1603
- return createSignalFormControl(input, getControl);
1604
- }
1605
- /**
1606
- * Type guard to check if an object is a SignalFormArray.
1607
- *
1608
- * @template TValue - The type of items in the array
1609
- * @param obj - Object to check
1610
- * @returns True if obj is a SignalFormArray
1611
- *
1612
- * @example
1613
- * ```typescript
1614
- * if (isSignalFormArray(value)) {
1615
- * console.log(value.controls().length); // TypeScript knows this is an array
1616
- * }
1617
- * ```
1618
- */
1619
- function isSignalFormArray(obj) {
1620
- return (!!obj &&
1621
- typeof obj === 'object' &&
1622
- obj.kind === 'array');
1623
- }
1624
- /**
1625
- * Creates a reactive form array for managing dynamic collections of controls.
1626
- *
1627
- * Builds an array container that can hold multiple controls (primitives, groups, or nested arrays)
1628
- * with full signal-based reactivity. Provides methods for dynamic addition/removal of items
1629
- * and tracks collective validation state.
1630
- *
1631
- * Features:
1632
- * - Dynamic array operations (push, insert, remove, clear)
1633
- * - Reactive value and error tracking across all items
1634
- * - Collective state management (touched, dirty, valid)
1635
- * - Manual error/warning management at array level
1636
- * - Type-safe access to individual controls
1637
- *
1638
- * @template TItem - The type of each item in the array
1639
- * @template TControls - Object type defining available sibling controls
1640
- *
1641
- * @param inputItems - Array of initial items (values, configs, or controls)
1642
- * @param getControl - Optional accessor for sibling controls
1643
- * @returns Fully configured SignalFormArray instance
1644
- *
1645
- * @example
1646
- * ```typescript
1647
- * // Array of primitives
1648
- * const tagsArray = createSignalFormArray(['tag1', 'tag2']);
1649
- * tagsArray.push('tag3');
1650
- *
1651
- * // Array of objects
1652
- * const addressesArray = createSignalFormArray<Address>([
1653
- * { street: '123 Main', city: 'NYC' },
1654
- * { street: '456 Oak', city: 'LA' }
1655
- * ]);
1656
- *
1657
- * // Array with validators
1658
- * const hobbiesArray = createSignalFormArray([
1659
- * { value: 'coding', validators: [signalFormValidators.minLength(3)] },
1660
- * 'gaming'
1661
- * ]);
1662
- * ```
1663
- */
1664
- function createSignalFormArray(inputItems, getControl) {
1665
- const accessControl = getControl ??
1666
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1667
- ((_) => {
1668
- throw new Error('getControl is not available for this array.');
1669
- });
1670
- const controls = signal(inputItems.map(item => createNodeFromInput(item, accessControl)), ...(ngDevMode ? [{ debugName: "controls" }] : []));
1671
- const selfDirty = signal(false, ...(ngDevMode ? [{ debugName: "selfDirty" }] : []));
1672
- const selfTouched = signal(false, ...(ngDevMode ? [{ debugName: "selfTouched" }] : []));
1673
- const selfDisabled = signal(false, ...(ngDevMode ? [{ debugName: "selfDisabled" }] : []));
1674
- const manualErrors = signal({}, ...(ngDevMode ? [{ debugName: "manualErrors" }] : []));
1675
- const manualWarnings = signal({}, ...(ngDevMode ? [{ debugName: "manualWarnings" }] : []));
1676
- const value = computed(() => controls().map(control => control.value()), ...(ngDevMode ? [{ debugName: "value" }] : []));
1677
- const errors = computed(() => controls().map(control => {
1678
- if (control.kind === 'control') {
1679
- const map = control.errors();
1680
- return hasErrors(map) ? map : undefined;
1681
- }
1682
- const childErrors = control.errors();
1683
- return isEmptyErrorTree(childErrors) ? undefined : childErrors;
1684
- }), ...(ngDevMode ? [{ debugName: "errors" }] : []));
1685
- const warnings = computed(() => controls().map(control => {
1686
- if (control.kind === 'control') {
1687
- const map = control.warnings();
1688
- return hasErrors(map) ? map : undefined;
1689
- }
1690
- const childWarnings = control.warnings();
1691
- return isEmptyErrorTree(childWarnings) ? undefined : childWarnings;
1692
- }), ...(ngDevMode ? [{ debugName: "warnings" }] : []));
1693
- const selfErrors = computed(() => {
1694
- if (selfDisabled())
1695
- return {};
1696
- return { ...manualErrors() };
1697
- }, ...(ngDevMode ? [{ debugName: "selfErrors" }] : []));
1698
- const selfWarnings = computed(() => {
1699
- if (selfDisabled())
1700
- return {};
1701
- return { ...manualWarnings() };
1702
- }, ...(ngDevMode ? [{ debugName: "selfWarnings" }] : []));
1703
- const invalid = computed(() => {
1704
- if (selfDisabled())
1705
- return false;
1706
- return (hasErrors(selfErrors()) || controls().some(control => control.invalid()));
1707
- }, ...(ngDevMode ? [{ debugName: "invalid" }] : []));
1708
- const valid = computed(() => !invalid(), ...(ngDevMode ? [{ debugName: "valid" }] : []));
1709
- const touched = computed(() => selfTouched() || controls().some(control => control.touched()), ...(ngDevMode ? [{ debugName: "touched" }] : []));
1710
- const dirty = computed(() => selfDirty() || controls().some(control => control.dirty()), ...(ngDevMode ? [{ debugName: "dirty" }] : []));
1711
- const insert = (item, index) => {
1712
- const node = createNodeFromInput(item, accessControl);
1713
- const position = index ?? controls().length;
1714
- controls.update(old => {
1715
- const newArray = [...old];
1716
- newArray.splice(position, 0, node);
1717
- return newArray;
1718
- });
1719
- selfDirty.set(true);
1720
- return position;
1721
- };
1722
- const push = (item) => insert(item);
1723
- const removeAt = (index) => {
1724
- controls.update(old => {
1725
- const newArray = [...old];
1726
- newArray.splice(index, 1);
1727
- return newArray;
1728
- });
1729
- selfDirty.set(true);
1730
- };
1731
- const clear = () => {
1732
- controls.set([]);
1733
- selfDirty.set(true);
1734
- };
1735
- const at = (index) => controls()[index];
1736
- const setValue = (nextValues, options) => {
1737
- controls.update(old => {
1738
- if (nextValues.length !== old.length) {
1739
- return nextValues.map(_value => createNodeFromInput(_value, accessControl));
1740
- }
1741
- old.forEach((control, index) => {
1742
- control.setValue(nextValues[index], options);
1743
- });
1744
- return old;
1745
- });
1746
- if (options?.markDirty ?? true)
1747
- selfDirty.set(true);
1748
- if (options?.markTouched)
1749
- selfTouched.set(true);
1750
- };
1751
- const reset = (nextValues) => {
1752
- if (nextValues) {
1753
- setValue(nextValues, { markDirty: false });
1754
- }
1755
- else {
1756
- controls().forEach(control => control.reset());
1757
- }
1758
- selfDirty.set(false);
1759
- selfTouched.set(false);
1760
- manualErrors.set({});
1761
- manualWarnings.set({});
1762
- };
1763
- const setDisabled = (disabled, options) => {
1764
- selfDisabled.set(disabled);
1765
- if (!options?.onlySelf) {
1766
- controls().forEach(control => control.setDisabled(disabled));
1767
- }
1768
- };
1769
- return {
1770
- kind: 'array',
1771
- controls,
1772
- value,
1773
- errors,
1774
- warnings,
1775
- selfErrors,
1776
- selfWarnings,
1777
- disabled: computed(() => selfDisabled()),
1778
- touched,
1779
- dirty,
1780
- invalid,
1781
- valid,
1782
- insert,
1783
- push,
1784
- removeAt,
1785
- clear,
1786
- at,
1787
- setValue,
1788
- reset,
1789
- markTouched: () => selfTouched.set(true),
1790
- markUntouched: () => selfTouched.set(false),
1791
- markDirty: () => selfDirty.set(true),
1792
- markPristine: () => selfDirty.set(false),
1793
- markAllTouched: () => controls().forEach(control => control.markTouched()),
1794
- setDisabled,
1795
- setError: (key, message) => manualErrors.update(current => ({ ...current, [key]: message })),
1796
- clearError: (key) => manualErrors.update(current => {
1797
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1798
- const { [key]: _removed, ...rest } = current;
1799
- return rest;
1800
- }),
1801
- clearErrors: () => manualErrors.set({}),
1802
- setWarning: (key, message) => manualWarnings.update(current => ({ ...current, [key]: message })),
1803
- clearWarning: (key) => manualWarnings.update(current => {
1804
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1805
- const { [key]: _removed, ...rest } = current;
1806
- return rest;
1807
- }),
1808
- clearWarnings: () => manualWarnings.set({}),
1809
- };
1810
- }
1811
-
1812
- /**
1813
- * Regular expression for email validation.
1814
- *
1815
- * Aligned with Angular's built-in email validator. Validates standard email formats
1816
- * with proper domain and local parts.
1817
- *
1818
- * Pattern requirements:
1819
- * - Total length: 1-254 characters
1820
- * - Local part (before @): 1-64 characters
1821
- * - Allows alphanumeric and special characters: !#$%&'*+/=?^_`{|}~-
1822
- * - Domain must have valid format with optional subdomains
1823
- */
1824
- const EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
1825
- /**
1826
- * Determines if a value is considered empty for validation purposes.
1827
- *
1828
- * Checks various types:
1829
- * - null/undefined: always empty
1830
- * - Numbers: empty when equals 0
1831
- * - Strings/Arrays: empty when length is 0
1832
- * - Sets: empty when size is 0
1833
- *
1834
- * @param value - Value to check for emptiness
1835
- * @returns True if the value is considered empty
1836
- *
1837
- * @example
1838
- * ```typescript
1839
- * isEmptyInputValue(null); // true
1840
- * isEmptyInputValue(''); // true
1841
- * isEmptyInputValue(0); // true
1842
- * isEmptyInputValue('hello'); // false
1843
- * ```
1844
- */
1845
- function isEmptyInputValue(value) {
1846
- if (value === null || value === undefined)
1847
- return true;
1848
- // TODO 1: check if number==0 is empty or number.length==0 is empty
1849
- if (typeof value === 'number' /* or - isNumber(value) */) {
1850
- return value == 0;
1851
- }
1852
- if (Array.isArray(value) || typeof value === 'string') {
1853
- return value.length === 0;
1854
- }
1855
- if (value instanceof Set) {
1856
- return value.size === 0;
1857
- }
1858
- return false;
1859
- }
1860
- /**
1861
- * Type guard that checks if a value can be coerced to a valid number.
1862
- *
1863
- * Uses JavaScript's Number coercion and checks for NaN to determine
1864
- * if a value represents a valid numeric value.
1865
- *
1866
- * @param value - Value to check
1867
- * @returns True if value is or can be coerced to a number
1868
- *
1869
- * @example
1870
- * ```typescript
1871
- * isNumber(42); // true
1872
- * isNumber('42'); // true
1873
- * isNumber('hello'); // false
1874
- * ```
1875
- */
1876
- function isNumber(value) {
1877
- return !Number.isNaN(Number(value));
1878
- }
1879
- /**
1880
- * Validator that requires a non-empty value.
1881
- *
1882
- * Fails when the value is null, undefined, empty string, empty array,
1883
- * zero (for numbers), or empty Set.
1884
- *
1885
- * @param ctx - Validation context with the value to check
1886
- * @returns Error object with 'required' key if validation fails, null otherwise
1887
- *
1888
- * @example
1889
- * ```typescript
1890
- * const control = signalForm({ name: { value: '', validators: [signalFormValidators.required] } });
1891
- * // control.controls.name.errors() => { required: 'This field is required' }
1892
- * ```
1893
- */
1894
- const required = ({ item: { value } }) => isEmptyInputValue(value) ? { required: 'This field is required' } : null;
1895
- /**
1896
- * Validator that enforces a maximum string or number length.
1897
- *
1898
- * Converts the value to string and checks if its length exceeds the specified maximum.
1899
- * Works with both string and number types.
1900
- *
1901
- * @param num - Maximum allowed length (inclusive)
1902
- * @returns Validator function
1903
- *
1904
- * @example
1905
- * ```typescript
1906
- * const control = signalForm({
1907
- * username: { value: 'verylongusername', validators: [signalFormValidators.maxLength(10)] }
1908
- * });
1909
- * // control.controls.username.errors() => { maxLength: 'To long' }
1910
- * ```
1911
- */
1912
- function maxLength(num) {
1913
- return ({ item: { value } }) => (typeof value == 'string' || typeof value == 'number') &&
1914
- value.toString().length > num
1915
- ? { maxLength: 'To long' }
1916
- : null;
1917
- }
1918
- /**
1919
- * Validator that enforces a minimum string or number length.
1920
- *
1921
- * Converts the value to string and checks if its length is less than or equal to the specified minimum.
1922
- * Works with both string and number types.
1923
- *
1924
- * @param num - Minimum required length (inclusive)
1925
- * @returns Validator function
1926
- *
1927
- * @example
1928
- * ```typescript
1929
- * const control = signalForm({
1930
- * code: { value: 'ab', validators: [signalFormValidators.minLength(3)] }
1931
- * });
1932
- * // control.controls.code.errors() => { minLength: 'To short' }
1933
- * ```
1934
- */
1935
- function minLength(num) {
1936
- return ({ item: { value } }) => (typeof value == 'string' || typeof value == 'number') &&
1937
- value.toString().length <= num
1938
- ? { minLength: 'To short' }
1939
- : null;
1940
- }
1941
- /**
1942
- * Validator that enforces a minimum numeric value.
1943
- *
1944
- * Checks if a numeric value is less than or equal to the specified minimum.
1945
- * Value is coerced to a number for comparison.
1946
- *
1947
- * @param num - Minimum allowed value (exclusive - value must be greater than this)
1948
- * @returns Validator function
1949
- *
1950
- * @example
1951
- * ```typescript
1952
- * const control = signalForm({
1953
- * age: { value: -5, validators: [signalFormValidators.min(0)] }
1954
- * });
1955
- * // control.controls.age.errors() => { minLength: 'To small' }
1956
- * ```
1957
- */
1958
- function min(num) {
1959
- return ({ item: { value } }) => isNumber(value) && value <= num ? { minLength: 'To small' } : null;
1960
- }
1961
- /**
1962
- * Validator that enforces a maximum numeric value.
1963
- *
1964
- * Checks if a numeric value exceeds the specified maximum.
1965
- * Value is coerced to a number for comparison.
1966
- *
1967
- * @param num - Maximum allowed value (inclusive)
1968
- * @returns Validator function
1969
- *
1970
- * @example
1971
- * ```typescript
1972
- * const control = signalForm({
1973
- * age: { value: 150, validators: [signalFormValidators.max(120)] }
1974
- * });
1975
- * // control.controls.age.errors() => { minLength: 'To big' }
1976
- * ```
1977
- */
1978
- function max(num) {
1979
- return ({ item: { value } }) => isNumber(value) && value > num ? { minLength: 'To big' } : null;
1980
- }
1981
- /**
1982
- * Validator that checks if a value is a valid email address.
1983
- *
1984
- * Uses Angular-compatible email regex pattern to validate email format.
1985
- * Skips validation for empty values (use with `required` if needed).
1986
- *
1987
- * @param ctx - Validation context with the email value to check
1988
- * @returns Error object with 'email' key if validation fails, null otherwise
1989
- *
1990
- * @example
1991
- * ```typescript
1992
- * const control = signalForm({
1993
- * email: { value: 'invalid-email', validators: [signalFormValidators.email] }
1994
- * });
1995
- * // control.controls.email.errors() => { email: 'Invalid email' }
1996
- * ```
1997
- */
1998
- const email = ({ item: { value } }) => {
1999
- if (isEmptyInputValue(value))
2000
- return null;
2001
- return EMAIL_REGEXP.test(String(value)) ? null : { email: 'Invalid email' };
2002
- };
2003
- /**
2004
- * Validator that checks if a value matches a specified regular expression pattern.
2005
- *
2006
- * Accepts either a RegExp object or a string pattern. String patterns are automatically
2007
- * wrapped with ^ and $ anchors to match the entire value.
2008
- *
2009
- * Skips validation for empty values (use with `required` if needed).
2010
- *
2011
- * @param valuePattern - Regular expression or pattern string to match against
2012
- * @returns Validator function
2013
- *
2014
- * @example
2015
- * ```typescript
2016
- * // Using regex
2017
- * const control1 = signalForm({
2018
- * code: { value: 'abc', validators: [signalFormValidators.pattern(/^[0-9]+$/)] }
2019
- * });
2020
- * // control1.controls.code.errors() => { pattern: 'RequiredPattern: ^[0-9]+$, ActualValue: abc' }
2021
- *
2022
- * // Using string pattern
2023
- * const control2 = signalForm({
2024
- * zipCode: { value: 'ABC', validators: [signalFormValidators.pattern('[0-9]{5}')] }
2025
- * });
2026
- * ```
2027
- */
2028
- function pattern(valuePattern) {
2029
- if (!valuePattern)
2030
- return () => null;
2031
- let regex;
2032
- let regexStr;
2033
- if (typeof valuePattern === 'string') {
2034
- regexStr = '';
2035
- if (valuePattern.charAt(0) !== '^')
2036
- regexStr += '^';
2037
- regexStr += valuePattern;
2038
- if (valuePattern.charAt(valuePattern.length - 1) !== '$')
2039
- regexStr += '$';
2040
- regex = new RegExp(regexStr);
2041
- }
2042
- else {
2043
- regexStr = valuePattern.toString();
2044
- regex = valuePattern;
2045
- }
2046
- return ({ item: { value } }) => {
2047
- if (isEmptyInputValue(value))
2048
- return null;
2049
- const valueStr = String(value);
2050
- return regex.test(valueStr)
2051
- ? null
2052
- : {
2053
- pattern: `RequiredPattern: ${regexStr}, ActualValue: ${valueStr}`,
2054
- };
2055
- };
2056
- }
2057
- /**
2058
- * Collection of built-in validators for signal-form controls.
2059
- *
2060
- * Provides common validation functions that can be used in the `validators` or `warnings`
2061
- * arrays of form controls. All validators skip empty values except `required`.
2062
- *
2063
- * @property required - Ensures the value is not empty (null, undefined, '', [], 0, empty Set)
2064
- * @property maxLength - Ensures string/number length doesn't exceed maximum
2065
- * @property minLength - Ensures string/number length meets minimum requirement
2066
- * @property min - Ensures numeric value is greater than minimum (exclusive)
2067
- * @property max - Ensures numeric value doesn't exceed maximum (inclusive)
2068
- * @property email - Validates email address format using Angular-compatible regex
2069
- * @property pattern - Validates value matches a regular expression pattern
2070
- *
2071
- * @example
2072
- * ```typescript
2073
- * const form = signalForm({
2074
- * email: {
2075
- * value: '',
2076
- * validators: [signalFormValidators.required, signalFormValidators.email]
2077
- * },
2078
- * age: {
2079
- * value: 25,
2080
- * validators: [signalFormValidators.min(0), signalFormValidators.max(120)],
2081
- * warnings: [signalFormValidators.max(100)] // Warning but doesn't invalidate
2082
- * },
2083
- * username: {
2084
- * value: '',
2085
- * validators: [
2086
- * signalFormValidators.required,
2087
- * signalFormValidators.minLength(3),
2088
- * signalFormValidators.maxLength(20),
2089
- * signalFormValidators.pattern(/^[a-zA-Z0-9_]+$/)
2090
- * ]
2091
- * }
2092
- * });
2093
- * ```
2094
- */
2095
- const signalFormValidators = {
2096
- required,
2097
- maxLength,
2098
- minLength,
2099
- min,
2100
- max,
2101
- email,
2102
- pattern,
2103
- };
2104
-
2105
- /**
2106
- * Example demonstrating the usage of signal-based reactive forms.
2107
- *
2108
- * Shows various features including:
2109
- * - Simple value initialization
2110
- * - Validators that mark controls as invalid
2111
- * - Warnings that don't affect validity
2112
- * - Dynamic disabled state based on other controls
2113
- * - Nested objects and arrays
2114
- * - Type-safe control access
2115
- *
2116
- * This function is exported as an example and reference implementation.
2117
- * It's not meant to be called in production code.
2118
- *
2119
- * @example
2120
- * ```typescript
2121
- * // Basic usage pattern from the example
2122
- * const form = signalForm<Person>({
2123
- * name: 'dvirus',
2124
- * age: {
2125
- * value: 130,
2126
- * validators: [signalFormValidators.required, signalFormValidators.min(0)],
2127
- * warnings: [signalFormValidators.max(120)],
2128
- * disabled: (ctx) => ctx.getControl('name').value() === 'admin'
2129
- * },
2130
- * address: {
2131
- * street: { value: '123 Main St', validators: [signalFormValidators.required] }
2132
- * },
2133
- * hobbies: [
2134
- * { value: 'coding', validators: [signalFormValidators.minLength(3)] },
2135
- * 'programming'
2136
- * ]
2137
- * });
2138
- *
2139
- * // Access form state
2140
- * form.getControl('address').value(); // { street: '123 Main St' }
2141
- * form.controls.hobbies.controls()[0].errors(); // {}
2142
- * form.controls.age.firstErrorOrWarning(); // { name: 'max', message: 'To big', type: 'warning' }
2143
- * ```
2144
- */
2145
- // function main() {
2146
- // type Person = {
2147
- // name: string;
2148
- // age: number;
2149
- // address: {
2150
- // street: string;
2151
- // city: string;
2152
- // };
2153
- // hobbies: string[];
2154
- // };
2155
- // /**
2156
- // * Example 1: Simple form with direct value initialization.
2157
- // *
2158
- // * Creates a form from a plain object. Each property becomes
2159
- // * a control with the provided value.
2160
- // */
2161
- // const model1: Person = {
2162
- // name: 'dvirus',
2163
- // age: 30,
2164
- // address: {
2165
- // street: '123 Main St',
2166
- // city: 'Any-town',
2167
- // },
2168
- // hobbies: ['coding', 'gaming'],
2169
- // };
2170
- // const form1 = signalForm(model1);
2171
- // /**
2172
- // * Example 2: Advanced form with validators, warnings, and dynamic disabled state.
2173
- // *
2174
- // * Demonstrates:
2175
- // * - Simple value for name field
2176
- // * - Age control with validators (required, min) and warnings (max)
2177
- // * - Dynamic disabled logic based on name value
2178
- // * - Nested address object with validated street field
2179
- // * - Array of hobbies with mixed control configs and simple values
2180
- // */
2181
- // const form2 = signalForm<Person>({
2182
- // name: 'dvirus',
2183
- // age: {
2184
- // // initial value
2185
- // value: 130,
2186
- // // example of validators, they add error messages and mark the control as invalid if the validation fails
2187
- // validators: [signalFormValidators.required, signalFormValidators.min(0)],
2188
- // // warning are like validators but they don't make the control invalid, just add a warning message
2189
- // warnings: [signalFormValidators.max(120)],
2190
- // // example of dynamic disabling based on another control's value
2191
- // disabled: (ctx) => ctx.getControl('name').value() === 'admin',
2192
- // },
2193
- // address: {
2194
- // street: {
2195
- // value: '123 Main St',
2196
- // validators: [signalFormValidators.required],
2197
- // disabled: false,
2198
- // },
2199
- // },
2200
- // hobbies: [
2201
- // {
2202
- // value: 'coding',
2203
- // validators: [signalFormValidators.minLength(3)],
2204
- // disabled: computed(() => false),
2205
- // },
2206
- // 'programming',
2207
- // 'Typing',
2208
- // ],
2209
- // });
2210
- // // Example outputs demonstrating form access patterns
2211
- // console.log(form2.getControl('address').value()); // { street: '123 Main St' }
2212
- // console.log(form2.controls.hobbies.controls()[0].errors()); // {}
2213
- // console.log(form2.controls.age.firstErrorOrWarning()); // {name: 'minLength', message: 'To big', type: 'warning'}
2214
- // console.log(form2.controls.age.warnings()); // { minLength: 'To big' }
2215
- // }
2216
-
2217
817
  /**
2218
818
  * Generated bundle index. Do not edit.
2219
819
  */
2220
820
 
2221
- export { controlSignal, createSignalFormArray, createSignalFormControl, createSignalFormGroup, formArray, formArraySignal, formControl, formGroup, formGroupSignal, fromSignalObj, isSignalFormArray, isSignalFormControl, isSignalFormGroup, isSignalObject, mergeSignalObjects, signalDebounce, signalForm, signalFormValidators, signalMap, signalNotifier, signalObject, signalOrFunction, signalOrValue, signalSet, toSignalObj, tryCatch, writableSignal };
821
+ export { controlSignal, formArraySignal, formGroupSignal, fromSignalObj, isSignalObject, mergeSignalObjects, signalDebounce, signalMap, signalNotifier, signalObject, signalOrFunction, signalOrValue, signalSet, toSignalObj, tryCatch, writableSignal };
2222
822
  //# sourceMappingURL=dvirus-js-angular-signals.mjs.map