@signaltree/ng-forms 4.0.9 → 4.0.12

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.
Files changed (41) hide show
  1. package/fesm2022/signaltree-ng-forms-src-audit.mjs +20 -0
  2. package/fesm2022/signaltree-ng-forms-src-audit.mjs.map +1 -0
  3. package/fesm2022/signaltree-ng-forms-src-history.mjs +113 -0
  4. package/fesm2022/signaltree-ng-forms-src-history.mjs.map +1 -0
  5. package/fesm2022/signaltree-ng-forms-src-rxjs.mjs +21 -0
  6. package/fesm2022/signaltree-ng-forms-src-rxjs.mjs.map +1 -0
  7. package/fesm2022/signaltree-ng-forms.mjs +1038 -0
  8. package/fesm2022/signaltree-ng-forms.mjs.map +1 -0
  9. package/index.d.ts +128 -0
  10. package/package.json +27 -6
  11. package/src/audit/index.d.ts +15 -0
  12. package/src/history/index.d.ts +24 -0
  13. package/src/rxjs/index.d.ts +6 -0
  14. package/LICENSE +0 -54
  15. package/eslint.config.mjs +0 -48
  16. package/jest.config.ts +0 -21
  17. package/ng-package.json +0 -8
  18. package/project.json +0 -48
  19. package/src/audit/audit.ts +0 -75
  20. package/src/audit/index.ts +0 -1
  21. package/src/audit/ng-package.json +0 -5
  22. package/src/core/async-validators.ts +0 -80
  23. package/src/core/ng-forms.spec.ts +0 -204
  24. package/src/core/ng-forms.ts +0 -1316
  25. package/src/core/validators.ts +0 -209
  26. package/src/history/history.ts +0 -169
  27. package/src/history/index.ts +0 -1
  28. package/src/history/ng-package.json +0 -5
  29. package/src/index.ts +0 -5
  30. package/src/rxjs/index.ts +0 -1
  31. package/src/rxjs/ng-package.json +0 -5
  32. package/src/rxjs/public-api.ts +0 -5
  33. package/src/rxjs/rxjs-bridge.ts +0 -75
  34. package/src/test-setup.ts +0 -6
  35. package/src/types/signaltree-core.d.ts +0 -11
  36. package/src/wizard/index.ts +0 -1
  37. package/src/wizard/wizard.ts +0 -145
  38. package/tsconfig.json +0 -33
  39. package/tsconfig.lib.json +0 -25
  40. package/tsconfig.lib.prod.json +0 -13
  41. package/tsconfig.spec.json +0 -17
@@ -1,209 +0,0 @@
1
- /**
2
- * @fileoverview Tree-shakeable validator functions for form fields
3
- *
4
- * These validators are exported as individual functions to enable
5
- * tree-shaking. Only the validators you actually use will be included
6
- * in your bundle.
7
- */
8
-
9
- import type { FieldValidator } from './ng-forms';
10
-
11
- /**
12
- * Creates a required field validator
13
- *
14
- * @param message - Custom error message (default: "Required")
15
- * @returns Validator function that returns error message if value is falsy
16
- *
17
- * @example
18
- * ```typescript
19
- * createFormTree(data, {
20
- * validators: {
21
- * name: required(),
22
- * email: required('Email is required')
23
- * }
24
- * });
25
- * ```
26
- */
27
- export function required(message = 'Required'): FieldValidator {
28
- return (value: unknown) => (!value ? message : null);
29
- }
30
-
31
- /**
32
- * Creates an email format validator
33
- *
34
- * @param message - Custom error message (default: "Invalid email")
35
- * @returns Validator function that checks for @ symbol
36
- *
37
- * @example
38
- * ```typescript
39
- * createFormTree(data, {
40
- * validators: {
41
- * email: email('Please enter a valid email address')
42
- * }
43
- * });
44
- * ```
45
- */
46
- export function email(message = 'Invalid email'): FieldValidator {
47
- return (value: unknown) => {
48
- const strValue = value as string;
49
- return strValue && !strValue.includes('@') ? message : null;
50
- };
51
- }
52
-
53
- /**
54
- * Creates a minimum length validator for strings
55
- *
56
- * @param min - Minimum required length
57
- * @param message - Optional custom error message
58
- * @returns Validator function that checks string length
59
- *
60
- * @example
61
- * ```typescript
62
- * createFormTree(data, {
63
- * validators: {
64
- * password: minLength(8),
65
- * description: minLength(10, 'Description must be at least 10 characters')
66
- * }
67
- * });
68
- * ```
69
- */
70
- export function minLength(min: number, message?: string): FieldValidator {
71
- return (value: unknown) => {
72
- const strValue = value as string;
73
- const errorMsg = message ?? `Min ${min} characters`;
74
- return strValue && strValue.length < min ? errorMsg : null;
75
- };
76
- }
77
-
78
- /**
79
- * Creates a maximum length validator for strings
80
- *
81
- * @param max - Maximum allowed length
82
- * @param message - Optional custom error message
83
- * @returns Validator function that checks string length
84
- *
85
- * @example
86
- * ```typescript
87
- * createFormTree(data, {
88
- * validators: {
89
- * username: maxLength(20),
90
- * bio: maxLength(500, 'Bio must be under 500 characters')
91
- * }
92
- * });
93
- * ```
94
- */
95
- export function maxLength(max: number, message?: string): FieldValidator {
96
- return (value: unknown) => {
97
- const strValue = value as string;
98
- const errorMsg = message ?? `Max ${max} characters`;
99
- return strValue && strValue.length > max ? errorMsg : null;
100
- };
101
- }
102
-
103
- /**
104
- * Creates a regex pattern validator
105
- *
106
- * @param regex - Regular expression to test against
107
- * @param message - Custom error message (default: "Invalid format")
108
- * @returns Validator function that tests value against regex
109
- *
110
- * @example
111
- * ```typescript
112
- * createFormTree(data, {
113
- * validators: {
114
- * phone: pattern(/^\d{3}-\d{3}-\d{4}$/, 'Phone must be in format: 123-456-7890'),
115
- * zipCode: pattern(/^\d{5}$/, 'Zip code must be 5 digits')
116
- * }
117
- * });
118
- * ```
119
- */
120
- export function pattern(
121
- regex: RegExp,
122
- message = 'Invalid format'
123
- ): FieldValidator {
124
- return (value: unknown) => {
125
- const strValue = value as string;
126
- return strValue && !regex.test(strValue) ? message : null;
127
- };
128
- }
129
-
130
- /**
131
- * Creates a minimum value validator for numbers
132
- *
133
- * @param min - Minimum allowed value
134
- * @param message - Optional custom error message
135
- * @returns Validator function that checks numeric minimum
136
- *
137
- * @example
138
- * ```typescript
139
- * createFormTree(data, {
140
- * validators: {
141
- * age: min(18, 'Must be at least 18 years old'),
142
- * quantity: min(1)
143
- * }
144
- * });
145
- * ```
146
- */
147
- export function min(min: number, message?: string): FieldValidator {
148
- return (value: unknown) => {
149
- const numValue = value as number;
150
- const errorMsg = message ?? `Must be at least ${min}`;
151
- return numValue < min ? errorMsg : null;
152
- };
153
- }
154
-
155
- /**
156
- * Creates a maximum value validator for numbers
157
- *
158
- * @param max - Maximum allowed value
159
- * @param message - Optional custom error message
160
- * @returns Validator function that checks numeric maximum
161
- *
162
- * @example
163
- * ```typescript
164
- * createFormTree(data, {
165
- * validators: {
166
- * age: max(120, 'Must be under 120 years old'),
167
- * quantity: max(100)
168
- * }
169
- * });
170
- * ```
171
- */
172
- export function max(max: number, message?: string): FieldValidator {
173
- return (value: unknown) => {
174
- const numValue = value as number;
175
- const errorMsg = message ?? `Must be at most ${max}`;
176
- return numValue > max ? errorMsg : null;
177
- };
178
- }
179
-
180
- /**
181
- * Combines multiple validators into a single validator function
182
- *
183
- * @param validators - Array of validator functions to combine
184
- * @returns Combined validator that returns the first error encountered
185
- *
186
- * @example
187
- * ```typescript
188
- * createFormTree(data, {
189
- * validators: {
190
- * email: compose([
191
- * required('Email is required'),
192
- * email('Invalid email format'),
193
- * pattern(/@company\.com$/, 'Must be a company email')
194
- * ])
195
- * }
196
- * });
197
- * ```
198
- */
199
- export function compose(validators: FieldValidator[]): FieldValidator {
200
- return (value: unknown) => {
201
- for (const validator of validators) {
202
- const error = validator(value);
203
- if (error) {
204
- return error;
205
- }
206
- }
207
- return null;
208
- };
209
- }
@@ -1,169 +0,0 @@
1
- import { Signal, signal } from '@angular/core';
2
- import { deepClone, snapshotsEqual } from '@signaltree/shared';
3
-
4
- // Local type definition to avoid circular imports in secondary entry points
5
- interface FormTree<T extends Record<string, unknown>> {
6
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
- form: any; // FormGroup
8
- unwrap(): T;
9
- destroy(): void;
10
- setValues(values: Partial<T>): void;
11
- }
12
-
13
- /**
14
- * Form history state structure
15
- */
16
- export interface FormHistory<T> {
17
- past: T[];
18
- present: T;
19
- future: T[];
20
- }
21
-
22
- /**
23
- * Enhances a FormTree with undo/redo capabilities.
24
- * Provides form-specific history management with capacity limits and change tracking.
25
- *
26
- * @param formTree - The form tree to enhance
27
- * @param options - Configuration options
28
- * @param options.capacity - Maximum number of history entries (default: 10)
29
- * @returns Extended FormTree with undo, redo, and history access
30
- *
31
- * @example
32
- * ```typescript
33
- * const form = createFormTree({ name: '', email: '' });
34
- * const formWithHistory = withFormHistory(form, { capacity: 20 });
35
- *
36
- * form.$.name.set('John');
37
- * form.$.email.set('john@example.com');
38
- *
39
- * formWithHistory.undo(); // Reverts email change
40
- * formWithHistory.undo(); // Reverts name change
41
- * formWithHistory.redo(); // Reapplies name change
42
- *
43
- * console.log(formWithHistory.history().past.length); // 1
44
- * ```
45
- */
46
- export function withFormHistory<T extends Record<string, unknown>>(
47
- formTree: FormTree<T>,
48
- options: { capacity?: number } = {}
49
- ): FormTree<T> & {
50
- undo: () => void;
51
- redo: () => void;
52
- clearHistory: () => void;
53
- history: Signal<FormHistory<T>>;
54
- } {
55
- const capacity = Math.max(1, options.capacity ?? 10);
56
- const historySignal = signal<FormHistory<T>>({
57
- past: [],
58
- present: deepClone(formTree.form.getRawValue() as T),
59
- future: [],
60
- });
61
-
62
- let recording = true;
63
- let suppressUpdates = 0;
64
- let internalHistory: FormHistory<T> = {
65
- past: [],
66
- present: deepClone(formTree.form.getRawValue() as T),
67
- future: [],
68
- };
69
-
70
- const subscription = formTree.form.valueChanges.subscribe(() => {
71
- if (suppressUpdates > 0) {
72
- suppressUpdates--;
73
- return;
74
- }
75
-
76
- if (!recording) {
77
- internalHistory = {
78
- ...internalHistory,
79
- present: deepClone(formTree.form.getRawValue() as T),
80
- };
81
- return;
82
- }
83
- const snapshot = deepClone(formTree.form.getRawValue() as T);
84
- if (snapshotsEqual(internalHistory.present, snapshot)) {
85
- internalHistory = {
86
- ...internalHistory,
87
- present: snapshot,
88
- };
89
- historySignal.set(cloneHistory(internalHistory));
90
- return;
91
- }
92
- const updatedPast = [...internalHistory.past, internalHistory.present];
93
- if (updatedPast.length > capacity) {
94
- updatedPast.shift();
95
- }
96
- internalHistory = {
97
- past: updatedPast,
98
- present: snapshot,
99
- future: [],
100
- };
101
- historySignal.set(cloneHistory(internalHistory));
102
- });
103
-
104
- const originalDestroy = formTree.destroy;
105
- formTree.destroy = () => {
106
- subscription.unsubscribe();
107
- originalDestroy();
108
- };
109
-
110
- const undo = () => {
111
- const history = historySignal();
112
- if (history.past.length === 0) {
113
- return;
114
- }
115
- const previous = deepClone(history.past[history.past.length - 1]);
116
- recording = false;
117
- suppressUpdates++;
118
- formTree.setValues(previous);
119
- recording = true;
120
- internalHistory = {
121
- past: history.past.slice(0, -1),
122
- present: previous,
123
- future: [history.present, ...history.future],
124
- };
125
- historySignal.set(cloneHistory(internalHistory));
126
- };
127
-
128
- const redo = () => {
129
- const history = historySignal();
130
- if (history.future.length === 0) {
131
- return;
132
- }
133
- const next = deepClone(history.future[0]);
134
- recording = false;
135
- suppressUpdates++;
136
- formTree.setValues(next);
137
- recording = true;
138
- internalHistory = {
139
- past: [...history.past, history.present],
140
- present: next,
141
- future: history.future.slice(1),
142
- };
143
- historySignal.set(cloneHistory(internalHistory));
144
- };
145
-
146
- const clearHistory = () => {
147
- internalHistory = {
148
- past: [],
149
- present: deepClone(formTree.form.getRawValue() as T),
150
- future: [],
151
- };
152
- historySignal.set(cloneHistory(internalHistory));
153
- };
154
-
155
- function cloneHistory(state: FormHistory<T>): FormHistory<T> {
156
- return {
157
- past: state.past.map((entry) => deepClone(entry)),
158
- present: deepClone(state.present),
159
- future: state.future.map((entry) => deepClone(entry)),
160
- };
161
- }
162
-
163
- return Object.assign(formTree, {
164
- undo,
165
- redo,
166
- clearHistory,
167
- history: historySignal.asReadonly(),
168
- });
169
- }
@@ -1 +0,0 @@
1
- export { withFormHistory, type FormHistory } from './history';
@@ -1,5 +0,0 @@
1
- {
2
- "lib": {
3
- "entryFile": "index.ts"
4
- }
5
- }
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- /// <reference path="./types/signaltree-core.d.ts" />
2
- export * from './core/ng-forms';
3
- export * from './core/validators';
4
- export * from './core/async-validators';
5
- export * from './history';
package/src/rxjs/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from './public-api';
@@ -1,5 +0,0 @@
1
- {
2
- "lib": {
3
- "entryFile": "public-api.ts"
4
- }
5
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Public API Surface of @signaltree/ng-forms/rxjs
3
- */
4
-
5
- export * from './rxjs-bridge';
@@ -1,75 +0,0 @@
1
- import { effect, Signal } from '@angular/core';
2
- import { Observable } from 'rxjs';
3
-
4
- /**
5
- * @fileoverview RxJS bridge for converting Angular signals to observables
6
- *
7
- * This module provides utilities for integrating SignalTree forms with
8
- * RxJS-based reactive patterns.
9
- */
10
-
11
- /**
12
- * Converts an Angular signal to an RxJS Observable.
13
- *
14
- * Creates an Observable that emits the signal's value whenever it changes.
15
- * The effect is automatically destroyed when the Observable is unsubscribed.
16
- *
17
- * @template T - The type of value emitted by the signal
18
- * @param signal - The Angular signal to convert
19
- * @returns Observable that mirrors the signal's values
20
- *
21
- * @example
22
- * ```typescript
23
- * import { toObservable } from '@signaltree/ng-forms/rxjs';
24
- *
25
- * const form = createFormTree({ name: '' });
26
- *
27
- * // Convert form signals to observables
28
- * const name$ = toObservable(form.$.name);
29
- * const errors$ = toObservable(form.errors);
30
- *
31
- * // Use with RxJS operators
32
- * name$.pipe(
33
- * debounceTime(300),
34
- * distinctUntilChanged()
35
- * ).subscribe(value => {
36
- * console.log('Name changed:', value);
37
- * });
38
- * ```
39
- *
40
- * @example
41
- * ```typescript
42
- * // Combine multiple form signals
43
- * import { combineLatest } from 'rxjs';
44
- *
45
- * const form = createFormTree({
46
- * firstName: '',
47
- * lastName: ''
48
- * });
49
- *
50
- * combineLatest([
51
- * toObservable(form.$.firstName),
52
- * toObservable(form.$.lastName)
53
- * ]).pipe(
54
- * map(([first, last]) => `${first} ${last}`)
55
- * ).subscribe(fullName => {
56
- * console.log('Full name:', fullName);
57
- * });
58
- * ```
59
- */
60
- export function toObservable<T>(signal: Signal<T>): Observable<T> {
61
- return new Observable((subscriber) => {
62
- try {
63
- const effectRef = effect(() => {
64
- subscriber.next(signal());
65
- });
66
- return () => effectRef.destroy();
67
- } catch {
68
- // Fallback for test environment without injection context
69
- subscriber.next(signal());
70
- return () => {
71
- // No cleanup needed for single emission
72
- };
73
- }
74
- });
75
- }
package/src/test-setup.ts DELETED
@@ -1,6 +0,0 @@
1
- import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
2
-
3
- setupZoneTestEnv({
4
- errorOnUnknownElements: true,
5
- errorOnUnknownProperties: true,
6
- });
@@ -1,11 +0,0 @@
1
- declare module '@signaltree/core' {
2
- export { signalTree } from '../../../core/src/lib/signal-tree';
3
- export type {
4
- SignalTree,
5
- TreeConfig,
6
- TreeNode,
7
- Enhancer,
8
- Middleware
9
- } from '../../../core/src/lib/types';
10
- export * from '../../../core/src/index';
11
- }
@@ -1 +0,0 @@
1
- export { createWizardForm, type FormStep } from './wizard';
@@ -1,145 +0,0 @@
1
- import { computed, Signal, signal } from '@angular/core';
2
-
3
- import { createFormTree } from '..';
4
-
5
- // Local type definitions to avoid circular imports in secondary entry points
6
- type FormTreeOptions = Record<string, unknown>;
7
-
8
- interface FormTree<T extends Record<string, unknown>> {
9
- unwrap(): T;
10
- }
11
- /**
12
- * Defines a step in a wizard form
13
- */
14
- export interface FormStep<T extends Record<string, unknown>> {
15
- /** Fields visible in this step */
16
- fields: Array<keyof T | string>;
17
- /** Optional validation function to run before proceeding to next step */
18
- validate?: (form: FormTree<T>) => Promise<boolean> | boolean;
19
- /** Optional function to determine if this step can be skipped */
20
- canSkip?: (values: T) => boolean;
21
- }
22
-
23
- /**
24
- * Creates a wizard form with multi-step navigation.
25
- * Manages step visibility, navigation, and per-step validation.
26
- *
27
- * @param steps - Array of form steps defining fields and validation
28
- * @param initialValues - Initial form values
29
- * @param config - Optional form tree configuration
30
- * @returns Extended FormTree with wizard navigation methods
31
- *
32
- * @example
33
- * ```typescript
34
- * const wizard = createWizardForm(
35
- * [
36
- * { fields: ['email', 'password'] },
37
- * { fields: ['firstName', 'lastName'], validate: async (form) => form.valid() },
38
- * { fields: ['address', 'city', 'zip'] }
39
- * ],
40
- * { email: '', password: '', firstName: '', lastName: '', address: '', city: '', zip: '' }
41
- * );
42
- *
43
- * await wizard.nextStep(); // Move to step 2
44
- * wizard.previousStep(); // Back to step 1
45
- * await wizard.goToStep(2); // Jump to step 3
46
- * ```
47
- */
48
- export function createWizardForm<T extends Record<string, unknown>>(
49
- steps: FormStep<T>[],
50
- initialValues: T,
51
- config: FormTreeOptions = {}
52
- ): FormTree<T> & {
53
- currentStep: Signal<number>;
54
- nextStep: () => Promise<boolean>;
55
- previousStep: () => void;
56
- goToStep: (index: number) => Promise<boolean>;
57
- canGoToStep: (index: number) => boolean;
58
- isFieldVisible: (field: keyof T | string) => Signal<boolean>;
59
- } {
60
- const formTree = createFormTree(initialValues, config);
61
- const currentStepSignal = signal(0);
62
-
63
- const visibleFields = computed(() => {
64
- const step = steps[currentStepSignal()];
65
- if (!step) {
66
- return new Set<string>();
67
- }
68
- return new Set(step.fields.map((field) => String(field)));
69
- });
70
-
71
- const canGoToStep = (index: number) => index >= 0 && index < steps.length;
72
-
73
- const goToStep = async (index: number) => {
74
- if (!canGoToStep(index)) {
75
- return false;
76
- }
77
- if (index === currentStepSignal()) {
78
- return true;
79
- }
80
- currentStepSignal.set(index);
81
- return true;
82
- };
83
-
84
- const findNextStep = (startIndex: number) => {
85
- let candidate = startIndex;
86
- while (candidate < steps.length) {
87
- const candidateStep = steps[candidate];
88
- if (
89
- !candidateStep?.canSkip ||
90
- !candidateStep.canSkip(formTree.unwrap())
91
- ) {
92
- break;
93
- }
94
- candidate++;
95
- }
96
- return candidate;
97
- };
98
-
99
- const nextStep = async () => {
100
- const currentIndex = currentStepSignal();
101
- const currentStep = steps[currentIndex];
102
- if (!currentStep) {
103
- return false;
104
- }
105
-
106
- if (currentStep.validate) {
107
- const result = await currentStep.validate(formTree);
108
- if (!result) {
109
- return false;
110
- }
111
- }
112
-
113
- const nextIndex = findNextStep(currentIndex + 1);
114
- if (!canGoToStep(nextIndex) || nextIndex <= currentIndex) {
115
- return false;
116
- }
117
-
118
- currentStepSignal.set(nextIndex);
119
- return true;
120
- };
121
-
122
- const previousStep = () => {
123
- const currentIndex = currentStepSignal();
124
- currentStepSignal.set(Math.max(0, currentIndex - 1));
125
- };
126
-
127
- const isFieldVisible = (field: keyof T | string) =>
128
- computed(() => {
129
- const fields = visibleFields();
130
- if (fields.size === 0) {
131
- return true;
132
- }
133
- return fields.has(String(field));
134
- });
135
-
136
- return {
137
- ...formTree,
138
- currentStep: currentStepSignal.asReadonly(),
139
- nextStep,
140
- previousStep,
141
- goToStep,
142
- canGoToStep,
143
- isFieldVisible,
144
- };
145
- }
package/tsconfig.json DELETED
@@ -1,33 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "target": "es2022",
5
- "moduleResolution": "node",
6
- "strict": true,
7
- "noImplicitOverride": true,
8
- "noPropertyAccessFromIndexSignature": true,
9
- "noImplicitReturns": true,
10
- "noFallthroughCasesInSwitch": true,
11
- "module": "preserve"
12
- },
13
- "angularCompilerOptions": {
14
- "enableI18nLegacyMessageIdFormat": false,
15
- "strictInjectionParameters": true,
16
- "strictInputAccessModifiers": true,
17
- "typeCheckHostBindings": true,
18
- "strictTemplates": true
19
- },
20
- "files": [],
21
- "include": [],
22
- "references": [
23
- {
24
- "path": "../core"
25
- },
26
- {
27
- "path": "./tsconfig.lib.json"
28
- },
29
- {
30
- "path": "./tsconfig.spec.json"
31
- }
32
- ]
33
- }