@tstdl/base 0.91.21 → 0.91.23

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 (77) hide show
  1. package/form/abstract-control.d.ts +72 -0
  2. package/form/abstract-control.js +82 -0
  3. package/form/controls/checkbox.control.d.ts +10 -0
  4. package/form/controls/checkbox.control.js +19 -0
  5. package/form/controls/chip-select.control.d.ts +36 -0
  6. package/form/controls/chip-select.control.js +56 -0
  7. package/form/controls/date.control.d.ts +26 -0
  8. package/form/controls/date.control.js +64 -0
  9. package/form/controls/hidden.control.d.ts +11 -0
  10. package/form/controls/hidden.control.js +22 -0
  11. package/form/controls/index.d.ts +10 -0
  12. package/form/controls/index.js +10 -0
  13. package/form/controls/items.control.d.ts +38 -0
  14. package/form/controls/items.control.js +41 -0
  15. package/form/controls/number.control.d.ts +33 -0
  16. package/form/controls/number.control.js +73 -0
  17. package/form/controls/radio-group.control.d.ts +22 -0
  18. package/form/controls/radio-group.control.js +28 -0
  19. package/form/controls/select.control.d.ts +17 -0
  20. package/form/controls/select.control.js +26 -0
  21. package/form/controls/text.control.d.ts +47 -0
  22. package/form/controls/text.control.js +94 -0
  23. package/form/controls/time.control.d.ts +24 -0
  24. package/form/controls/time.control.js +44 -0
  25. package/form/form-array.d.ts +71 -0
  26. package/form/form-array.js +188 -0
  27. package/form/form-button.d.ts +40 -0
  28. package/form/form-button.js +49 -0
  29. package/form/form-container.d.ts +24 -0
  30. package/form/form-container.js +33 -0
  31. package/form/form-control.d.ts +45 -0
  32. package/form/form-control.js +92 -0
  33. package/form/form-dialog.d.ts +22 -0
  34. package/form/form-dialog.js +24 -0
  35. package/form/form-element.d.ts +7 -0
  36. package/form/form-element.js +6 -0
  37. package/form/form-group.d.ts +70 -0
  38. package/form/form-group.js +134 -0
  39. package/form/form-header-footer-element.d.ts +14 -0
  40. package/form/form-header-footer-element.js +20 -0
  41. package/form/form-portal.d.ts +10 -0
  42. package/form/form-portal.js +13 -0
  43. package/form/form-text.d.ts +13 -0
  44. package/form/form-text.js +15 -0
  45. package/form/form-wrapper.d.ts +40 -0
  46. package/form/form-wrapper.js +64 -0
  47. package/form/index.d.ts +15 -0
  48. package/form/index.js +15 -0
  49. package/form/localization.d.ts +49 -0
  50. package/form/localization.js +56 -0
  51. package/form/types.d.ts +11 -0
  52. package/form/types.js +1 -0
  53. package/form/utils.d.ts +38 -0
  54. package/form/utils.js +47 -0
  55. package/form/validators/boolean.validator.d.ts +3 -0
  56. package/form/validators/boolean.validator.js +15 -0
  57. package/form/validators/date.validator.d.ts +4 -0
  58. package/form/validators/date.validator.js +26 -0
  59. package/form/validators/form.validator.d.ts +10 -0
  60. package/form/validators/form.validator.js +3 -0
  61. package/form/validators/index.d.ts +9 -0
  62. package/form/validators/index.js +9 -0
  63. package/form/validators/max-length.validator.d.ts +3 -0
  64. package/form/validators/max-length.validator.js +17 -0
  65. package/form/validators/number.validator.d.ts +4 -0
  66. package/form/validators/number.validator.js +22 -0
  67. package/form/validators/pattern.validator.d.ts +3 -0
  68. package/form/validators/pattern.validator.js +16 -0
  69. package/form/validators/required.validator.d.ts +3 -0
  70. package/form/validators/required.validator.js +16 -0
  71. package/form/validators/time.validator.d.ts +4 -0
  72. package/form/validators/time.validator.js +22 -0
  73. package/form/validators/unique.validator.d.ts +5 -0
  74. package/form/validators/unique.validator.js +22 -0
  75. package/package.json +1 -1
  76. package/text/common-localization.d.ts +2 -2
  77. package/text/common-localization.js +2 -2
@@ -0,0 +1,28 @@
1
+ import { tstdlCommonLocalizationKeys } from '../../text/common-localization.js';
2
+ import { enumAsItems, reactiveOptionToSignal } from '../utils.js';
3
+ import { ItemsFormControl } from './items.control.js';
4
+ export class RadioGroupFormControl extends ItemsFormControl {
5
+ direction;
6
+ constructor(initialValue, options) {
7
+ super(initialValue, options);
8
+ this.direction = reactiveOptionToSignal(options.direction ?? 'column', { initialValue: 'column' });
9
+ }
10
+ }
11
+ export function radioGroupFormControl(options) {
12
+ return new RadioGroupFormControl(null, options);
13
+ }
14
+ export function booleanRadioGroupFormControl(options = {}) {
15
+ return radioGroupFormControl({
16
+ ...options,
17
+ items: [{ label: tstdlCommonLocalizationKeys.yes, value: true }, { label: tstdlCommonLocalizationKeys.no, value: false }]
18
+ });
19
+ }
20
+ export function enumRadioGroupFormControl(enumeration, options = {}) {
21
+ return radioGroupFormControl({
22
+ ...options,
23
+ items: enumAsItems(enumeration, options.allowedValues)
24
+ });
25
+ }
26
+ export function isRadioGroupFormControl(value) {
27
+ return (value instanceof RadioGroupFormControl);
28
+ }
@@ -0,0 +1,17 @@
1
+ import type { EnumerationObject, FilledReadonlyArray, TypedOmit } from '../../types.js';
2
+ import type { InitialValue, InitialValueOption } from '../form-control.js';
3
+ import { ItemsFormControl, type ItemsFormControlOptions } from './items.control.js';
4
+ export type SelectFormControlOptions<T, I extends InitialValue<T>> = ItemsFormControlOptions<T, I>;
5
+ export declare class SelectFormControl<T, I extends InitialValue<T> = T | null> extends ItemsFormControl<T, I> {
6
+ constructor(initialValue: I, options: SelectFormControlOptions<T, I>);
7
+ }
8
+ export declare function selectFormControl<T, I extends InitialValue<T>>(options: SelectFormControlOptions<T, I> & InitialValueOption<I>): SelectFormControl<T, I>;
9
+ export declare function selectFormControl<T, I extends InitialValue<T>>(options: SelectFormControlOptions<T, I>): SelectFormControl<T, T | null>;
10
+ export declare function booleanSelectFormControl<I extends InitialValue<boolean>>(options: TypedOmit<SelectFormControlOptions<boolean, I>, 'items'> & InitialValueOption<I>): SelectFormControl<boolean, I>;
11
+ export declare function booleanSelectFormControl<I extends InitialValue<boolean>>(options?: TypedOmit<SelectFormControlOptions<boolean, I>, 'items'>): SelectFormControl<boolean>;
12
+ export type EnumSelectFormControlOptions<T extends EnumerationObject, I extends InitialValue<T[keyof T]>> = TypedOmit<SelectFormControlOptions<T[keyof T], I>, 'items'> & {
13
+ allowedValues?: FilledReadonlyArray<T[keyof T]>;
14
+ };
15
+ export declare function enumSelectFormControl<T extends EnumerationObject, I extends InitialValue<T[keyof T]>>(enumeration: T, options: EnumSelectFormControlOptions<T, I> & InitialValueOption<I>): SelectFormControl<T[keyof T], I>;
16
+ export declare function enumSelectFormControl<T extends EnumerationObject, I extends InitialValue<T[keyof T]>>(enumeration: T, options?: EnumSelectFormControlOptions<T, I>): SelectFormControl<T[keyof T]>;
17
+ export declare function isSelectFormControl(value: any): value is SelectFormControl<any, any>;
@@ -0,0 +1,26 @@
1
+ import { tstdlCommonLocalizationKeys } from '../../text/common-localization.js';
2
+ import { enumAsItems } from '../utils.js';
3
+ import { ItemsFormControl } from './items.control.js';
4
+ export class SelectFormControl extends ItemsFormControl {
5
+ constructor(initialValue, options) {
6
+ super(initialValue, options);
7
+ }
8
+ }
9
+ export function selectFormControl(options) {
10
+ return new SelectFormControl(null, options);
11
+ }
12
+ export function booleanSelectFormControl(options = {}) {
13
+ return selectFormControl({
14
+ ...options,
15
+ items: [{ label: tstdlCommonLocalizationKeys.yes, value: true }, { label: tstdlCommonLocalizationKeys.no, value: false }]
16
+ });
17
+ }
18
+ export function enumSelectFormControl(enumeration, options = {}) {
19
+ return selectFormControl({
20
+ ...options,
21
+ items: enumAsItems(enumeration, options.allowedValues)
22
+ });
23
+ }
24
+ export function isSelectFormControl(value) {
25
+ return (value instanceof SelectFormControl);
26
+ }
@@ -0,0 +1,47 @@
1
+ import { type Signal } from '../../signals/index.js';
2
+ import type { ReactiveValue, TypedOmit } from '../../types.js';
3
+ import type { InputAutocomplete } from '../../web-types.js';
4
+ import { FormControl, type FormControlOptions, type InitialValue, type InitialValueOption } from '../form-control.js';
5
+ import type { DynamicTextOption } from '../types.js';
6
+ export type TextFormControlType = 'text' | 'password' | 'email';
7
+ export type AutoFillSuggestionSelectHandler<T = any> = (suggestion: AutoFillSuggestion<T>) => any;
8
+ export type AutoFillSuggestion<T = any> = {
9
+ display: ReactiveValue<string>;
10
+ selectValue?: string;
11
+ data?: T;
12
+ onSelect?: AutoFillSuggestionSelectHandler<T>;
13
+ };
14
+ export type NormalizedAutoFillSuggestion<T = any> = TypedOmit<AutoFillSuggestion<T>, 'display'> & {
15
+ display: Signal<string>;
16
+ searchText: Signal<string>;
17
+ };
18
+ export type TextFormControlOptions<I extends InitialValue<string> = InitialValue<string>> = FormControlOptions<string | I> & {
19
+ type?: ReactiveValue<TextFormControlType>;
20
+ autocomplete?: ReactiveValue<InputAutocomplete>;
21
+ placeholder?: ReactiveValue<DynamicTextOption>;
22
+ pattern?: ReactiveValue<RegExp | null>;
23
+ inputPattern?: ReactiveValue<RegExp | null>;
24
+ trim?: ReactiveValue<boolean>;
25
+ maxLength?: ReactiveValue<number>;
26
+ autoFillSuggestions?: ReactiveValue<(string | AutoFillSuggestion)[] | undefined>;
27
+ filterAutoFillSuggestions?: ReactiveValue<boolean>;
28
+ };
29
+ export declare class TextFormControl<I extends InitialValue<string> = string | null> extends FormControl<string | I, string> {
30
+ protected readonly internalValidators: Signal<readonly import("../validators/form.validator.js").FormValidator<any>[]>;
31
+ readonly type: Signal<TextFormControlType>;
32
+ readonly autocomplete: Signal<InputAutocomplete>;
33
+ readonly placeholder: Signal<DynamicTextOption>;
34
+ readonly pattern: Signal<RegExp | null>;
35
+ readonly inputPattern: Signal<RegExp | null>;
36
+ readonly trim: Signal<boolean>;
37
+ readonly maxLength: Signal<number | null>;
38
+ readonly autoFillSuggestions: Signal<readonly NormalizedAutoFillSuggestion[] | null>;
39
+ readonly filterAutoFillSuggestions: Signal<boolean>;
40
+ readonly filteredAutoFillSuggestions: Signal<readonly NormalizedAutoFillSuggestion[] | null>;
41
+ constructor(initialValue: I, options?: TextFormControlOptions<I>);
42
+ parseInputValue(value: string | null): string | I | null;
43
+ formatRawValue(value: string | null): string;
44
+ }
45
+ export declare function textFormControl<I extends InitialValue<string>>(options: TextFormControlOptions<I> & InitialValueOption<I>): TextFormControl<I>;
46
+ export declare function textFormControl<I extends InitialValue<string>>(options?: TextFormControlOptions<I>): TextFormControl;
47
+ export declare function isTextFormControl(value: any): value is TextFormControl<any>;
@@ -0,0 +1,94 @@
1
+ import { computed, getCurrentSignalsInjector, runInSignalsInjectionContext } from '../../signals/index.js';
2
+ import { normalizeText } from '../../utils/helpers.js';
3
+ import { mailPattern } from '../../utils/patterns.js';
4
+ import { isNotNull, isNull, isNumber, isString } from '../../utils/type-guards.js';
5
+ import { computedInternalValidators } from '../abstract-control.js';
6
+ import { FormControl } from '../form-control.js';
7
+ import { reactiveOptionToSignal } from '../utils.js';
8
+ import { maxLengthValidator, patternValidator } from '../validators/index.js';
9
+ export class TextFormControl extends FormControl {
10
+ internalValidators = computedInternalValidators(() => {
11
+ const pattern = this.pattern();
12
+ const maxLength = this.maxLength();
13
+ const type = this.type();
14
+ return [
15
+ isNumber(maxLength) ? maxLengthValidator(maxLength) : null,
16
+ (type == 'email') ? patternValidator(mailPattern) : null,
17
+ isNotNull(pattern) ? patternValidator(pattern) : null
18
+ ];
19
+ });
20
+ type;
21
+ autocomplete;
22
+ placeholder;
23
+ pattern;
24
+ inputPattern;
25
+ trim;
26
+ maxLength;
27
+ autoFillSuggestions;
28
+ filterAutoFillSuggestions;
29
+ filteredAutoFillSuggestions;
30
+ constructor(initialValue, options = {}) {
31
+ super(initialValue, options);
32
+ this.type = reactiveOptionToSignal(options.type ?? 'text', { initialValue: 'text' });
33
+ this.autocomplete = reactiveOptionToSignal(options.autocomplete ?? 'off', { initialValue: 'off' });
34
+ this.placeholder = reactiveOptionToSignal(options.placeholder ?? null, { initialValue: null });
35
+ this.pattern = reactiveOptionToSignal(options.pattern ?? null, { initialValue: null });
36
+ this.inputPattern = reactiveOptionToSignal(options.inputPattern ?? null, { initialValue: null });
37
+ this.trim = reactiveOptionToSignal(options.trim ?? true, { initialValue: true });
38
+ this.maxLength = reactiveOptionToSignal(options.maxLength ?? null, { initialValue: null });
39
+ const rawAutoFillSuggestions = reactiveOptionToSignal(options.autoFillSuggestions);
40
+ const signalsInjector = getCurrentSignalsInjector();
41
+ this.autoFillSuggestions = computed(() => runInSignalsInjectionContext(signalsInjector, () => normalizeAutoFillSuggestions(rawAutoFillSuggestions() ?? null)));
42
+ this.filterAutoFillSuggestions = reactiveOptionToSignal(options.filterAutoFillSuggestions ?? true, { initialValue: true });
43
+ this.filteredAutoFillSuggestions = computed(() => {
44
+ const searchValue = normalizeText(this.value() ?? '');
45
+ const autoFillSuggestions = this.autoFillSuggestions();
46
+ if (isNull(autoFillSuggestions)) {
47
+ return null;
48
+ }
49
+ if (!this.filterAutoFillSuggestions() || (searchValue.length == 0)) {
50
+ return autoFillSuggestions;
51
+ }
52
+ return autoFillSuggestions.filter((suggestion) => suggestion.searchText().includes(searchValue));
53
+ });
54
+ }
55
+ parseInputValue(value) {
56
+ if (isNull(value)) {
57
+ return null;
58
+ }
59
+ const normalized = this.trim() ? value.trim() : value;
60
+ if (normalized.length == 0) {
61
+ return null;
62
+ }
63
+ return normalized;
64
+ }
65
+ formatRawValue(value) {
66
+ return value ?? '';
67
+ }
68
+ }
69
+ export function textFormControl(options) {
70
+ return new TextFormControl(null, options);
71
+ }
72
+ export function isTextFormControl(value) {
73
+ return (value instanceof TextFormControl);
74
+ }
75
+ function normalizeAutoFillSuggestions(suggestions) {
76
+ if (isNull(suggestions)) {
77
+ return null;
78
+ }
79
+ return suggestions.map((suggestion) => {
80
+ if (isString(suggestion)) {
81
+ return {
82
+ display: computed(() => suggestion),
83
+ searchText: computed(() => normalizeText(suggestion)),
84
+ data: suggestion
85
+ };
86
+ }
87
+ const displaySignal = reactiveOptionToSignal(suggestion.display, { requireSync: true });
88
+ return {
89
+ ...suggestion,
90
+ display: displaySignal,
91
+ searchText: computed(() => normalizeText(displaySignal()))
92
+ };
93
+ });
94
+ }
@@ -0,0 +1,24 @@
1
+ import type { Signal } from '../../signals/api.js';
2
+ import type { ReactiveValue } from '../../types.js';
3
+ import type { InputAutocomplete } from '../../web-types.js';
4
+ import { FormControl, type FormControlOptions, type InitialValue, type InitialValueOption } from '../form-control.js';
5
+ import type { DynamicTextOption } from '../types.js';
6
+ export type TimeFormControlOptions<I extends InitialValue<number> = InitialValue<number>> = FormControlOptions<number | I> & {
7
+ autocomplete?: InputAutocomplete;
8
+ placeholder?: ReactiveValue<DynamicTextOption>;
9
+ minimum?: ReactiveValue<number | null>;
10
+ maximum?: ReactiveValue<number | null>;
11
+ };
12
+ export declare class TimeFormControl<I extends InitialValue<number> = number | null> extends FormControl<number | I, string> {
13
+ protected readonly internalValidators: Signal<readonly import("../index.js").FormValidator<any>[]>;
14
+ readonly autocomplete: Signal<InputAutocomplete>;
15
+ readonly placeholder: Signal<DynamicTextOption>;
16
+ readonly minimum: Signal<number | null>;
17
+ readonly maximum: Signal<number | null>;
18
+ constructor(initialValue: I, options?: TimeFormControlOptions<I>);
19
+ parseInputValue(value: string | null): number | null;
20
+ formatRawValue(value: number | null): string;
21
+ }
22
+ export declare function timeFormControl<I extends InitialValue<number>>(options: TimeFormControlOptions<I> & InitialValueOption<I>): TimeFormControl<I>;
23
+ export declare function timeFormControl<I extends InitialValue<number>>(options?: TimeFormControlOptions<I>): TimeFormControl;
24
+ export declare function isTimeFormControl(value: any): value is TimeFormControl<any>;
@@ -0,0 +1,44 @@
1
+ import { Duration } from 'luxon';
2
+ import { isDefined, isNull } from '../../utils/type-guards.js';
3
+ import { millisecondsPerHour, millisecondsPerMinute, millisecondsPerSecond } from '../../utils/units.js';
4
+ import { computedInternalValidators } from '../abstract-control.js';
5
+ import { FormControl } from '../form-control.js';
6
+ import { reactiveOptionToSignal } from '../utils.js';
7
+ import { timeValidator } from '../validators/time.validator.js';
8
+ export class TimeFormControl extends FormControl {
9
+ internalValidators = computedInternalValidators(() => {
10
+ const minimum = this.minimum();
11
+ const maximum = this.maximum();
12
+ return [(isDefined(minimum) || isDefined(maximum)) ? timeValidator(minimum, maximum) : undefined];
13
+ });
14
+ autocomplete;
15
+ placeholder;
16
+ minimum;
17
+ maximum;
18
+ constructor(initialValue, options = {}) {
19
+ super(initialValue, options);
20
+ this.autocomplete = reactiveOptionToSignal(options.autocomplete ?? 'off', { initialValue: 'off' });
21
+ this.placeholder = reactiveOptionToSignal(options.placeholder ?? null, { initialValue: null });
22
+ this.minimum = reactiveOptionToSignal(options.minimum ?? null, { initialValue: null });
23
+ this.maximum = reactiveOptionToSignal(options.maximum ?? null, { initialValue: null });
24
+ }
25
+ parseInputValue(value) {
26
+ if (isNull(value)) {
27
+ return null;
28
+ }
29
+ const [hours, minutes, seconds] = value.split(':').map((part) => Number(part));
30
+ return ((hours ?? 0) * millisecondsPerHour) + ((minutes ?? 0) * millisecondsPerMinute) + ((seconds ?? 0) * millisecondsPerSecond);
31
+ }
32
+ formatRawValue(value) {
33
+ if (isNull(value) || Number.isNaN(value)) {
34
+ return '';
35
+ }
36
+ return Duration.fromMillis(value).toFormat('hh:mm');
37
+ }
38
+ }
39
+ export function timeFormControl(options) {
40
+ return new TimeFormControl(null, options);
41
+ }
42
+ export function isTimeFormControl(value) {
43
+ return (value instanceof TimeFormControl);
44
+ }
@@ -0,0 +1,71 @@
1
+ import type { JsonPath } from '../json-path/json-path.js';
2
+ import { type Signal } from '../signals/api.js';
3
+ import type { ReactiveValue } from '../types.js';
4
+ import { type AbstractControl, type AbstractControlParent, type AbstractControlRawValueType, type AbstractControlValueType, type MarkOptions } from './abstract-control.js';
5
+ import { FormContainer, type FormContainerOptions } from './form-container.js';
6
+ import type { FormControlRemoveHandler } from './types.js';
7
+ import type { FormValidatorError } from './validators/form.validator.js';
8
+ export type FormArrayDisplayType = 'simple' | 'tabs';
9
+ export type FormArrayChildBuilder<T = unknown, TRaw = unknown, C extends AbstractControl<T, TRaw> = AbstractControl<T, TRaw>> = (value: T) => C;
10
+ export type FormArrayOptions<T, TRaw, C extends AbstractControl<T, TRaw>> = FormContainerOptions<T[]> & {
11
+ children?: C[];
12
+ displayType?: FormArrayDisplayType;
13
+ allowRemove?: ReactiveValue<boolean>;
14
+ allowRemoveAll?: ReactiveValue<boolean>;
15
+ removeHandler?: FormControlRemoveHandler<C & {}>;
16
+ builder?: FormArrayChildBuilder<T, TRaw, C>;
17
+ };
18
+ export type FormArrayByControl<C extends AbstractControl = AbstractControl> = FormArray<AbstractControlValueType<C>, AbstractControlRawValueType<C>, C>;
19
+ export type FormArrayChild<T extends FormArray> = ReturnType<T['children']>[number];
20
+ export declare class FormArray<T = any, TRaw = any, C extends AbstractControl<T, TRaw> = AbstractControl<T, TRaw>> extends FormContainer<T[], TRaw[]> implements Iterable<C> {
21
+ #private;
22
+ protected readonly internalValidators: null;
23
+ readonly children: Signal<readonly C[]>;
24
+ readonly enabledChildren: Signal<readonly C[]>;
25
+ readonly size: Signal<number>;
26
+ readonly displayType: Signal<FormArrayDisplayType>;
27
+ readonly allowRemove: Signal<boolean>;
28
+ readonly allowRemoveAll: Signal<boolean>;
29
+ readonly removedChildren: Signal<C[]>;
30
+ readonly removeHandler: FormControlRemoveHandler<any>;
31
+ readonly builder: FormArrayChildBuilder<T, TRaw, C>;
32
+ /** Raw values of all children whether disabled or not */
33
+ readonly rawValue: Signal<TRaw[]>;
34
+ /** Values of all enabled children */
35
+ readonly value: Signal<T[]>;
36
+ readonly dirty: Signal<boolean>;
37
+ readonly touched: Signal<boolean>;
38
+ readonly localErrors: Signal<readonly FormValidatorError[]>;
39
+ readonly childrenErrors: Signal<FormValidatorError[]>;
40
+ readonly errors: Signal<FormValidatorError[]>;
41
+ readonly errorDebug: Signal<{
42
+ _self: readonly FormValidatorError[];
43
+ } | null>;
44
+ constructor(options?: FormArrayOptions<T, TRaw, C>);
45
+ get(index: number): C | undefined;
46
+ getValue(index: number): T | undefined;
47
+ getRawValue(index: number): TRaw | undefined;
48
+ reset(): void;
49
+ indexOf(child: C): number | null;
50
+ add(...children: C[]): void;
51
+ addValue(...values: T[]): void;
52
+ removeAll(): void;
53
+ remove(child: C): void;
54
+ removeAt(index: number, count?: number): void;
55
+ setValue(values: T[]): void;
56
+ markAsDirty({ onlySelf }?: MarkOptions): void;
57
+ markAsTouched({ onlySelf }?: MarkOptions): void;
58
+ setParent(parent: AbstractControlParent, force?: boolean): void;
59
+ setParent(parent: null): void;
60
+ [Symbol.iterator](): Iterator<C>;
61
+ getChildPath(child: C): Signal<JsonPath>;
62
+ private updateParents;
63
+ }
64
+ export declare function formArray<C extends AbstractControl>(options: FormArrayOptions<AbstractControlValueType<C>, AbstractControlRawValueType<C>, C> & {
65
+ builder: FormArrayChildBuilder<AbstractControlValueType<C>, AbstractControlRawValueType<C>, C>;
66
+ }): FormArray<AbstractControlValueType<C>, AbstractControlRawValueType<C>, C>;
67
+ export declare function formArray<C extends AbstractControl>(options: FormArrayOptions<AbstractControlValueType<C>, AbstractControlRawValueType<C>, C> & {
68
+ children: C[];
69
+ }): FormArray<AbstractControlValueType<C>, AbstractControlRawValueType<C>, C>;
70
+ export declare function formArray<T, TRaw, C extends AbstractControl>(options: FormArrayOptions<T, TRaw, C>): FormArray<T, TRaw, C>;
71
+ export declare function isFormArray(value: any): value is FormArray;
@@ -0,0 +1,188 @@
1
+ import { computed, signal, untracked } from '../signals/api.js';
2
+ import { switchAll } from '../signals/index.js';
3
+ import { createArray } from '../utils/array/array.js';
4
+ import { fromEntries } from '../utils/object/object.js';
5
+ import { deferThrow } from '../utils/throw.js';
6
+ import { isDefined, isNotNull } from '../utils/type-guards.js';
7
+ import { ignoreFormValue } from './abstract-control.js';
8
+ import { FormContainer } from './form-container.js';
9
+ import { bindReactiveOption } from './utils.js';
10
+ export class FormArray extends FormContainer {
11
+ #children = signal([]);
12
+ #selfTouched = signal(false);
13
+ #selfDirty = signal(false);
14
+ #displayType = signal('tabs');
15
+ #allowRemove = signal(true);
16
+ #allowRemoveAll = signal(true);
17
+ #removedChildren = signal(new Set());
18
+ internalValidators = null;
19
+ children = this.#children.asReadonly();
20
+ enabledChildren = computed(() => this.#children().filter((child) => child.enabled()));
21
+ size = computed(() => this.children().length);
22
+ displayType = this.#displayType.asReadonly();
23
+ allowRemove = this.#allowRemove.asReadonly();
24
+ allowRemoveAll = this.#allowRemoveAll.asReadonly();
25
+ removedChildren = computed(() => [...this.#removedChildren()]);
26
+ removeHandler;
27
+ builder;
28
+ /** Raw values of all children whether disabled or not */
29
+ rawValue = computed(() => this.#children().map((child) => child.rawValue()));
30
+ /** Values of all enabled children */
31
+ value = computed(() => this.enabledChildren().map((child) => child.value()));
32
+ dirty = computed(() => this.#selfDirty() || this.children().some((child) => child.dirty()));
33
+ touched = computed(() => this.#selfTouched() || this.children().some((child) => child.touched()));
34
+ localErrors = switchAll(() => {
35
+ const validations = this.validators().map((validator) => validator(this));
36
+ return computed(() => validations.map((validation) => validation()).filter(isNotNull));
37
+ });
38
+ childrenErrors = computed(() => this.children().flatMap((child) => child.errors()));
39
+ errors = computed(() => [
40
+ ...this.localErrors(),
41
+ ...this.childrenErrors()
42
+ ]);
43
+ errorDebug = computed(() => this.valid()
44
+ ? null
45
+ : ({
46
+ _self: this.localErrors(),
47
+ ...fromEntries(this.children().map((child, index) => [index, child.errorDebug()]).filter(([, errorDebug]) => isNotNull(errorDebug)))
48
+ }));
49
+ constructor(options = {}) {
50
+ super(options);
51
+ this.builder = options.builder ?? deferThrow(() => new Error('Builder required to add children by value'));
52
+ this.removeHandler = options.removeHandler ?? (() => true);
53
+ if (isDefined(options.children)) {
54
+ this.add(...options.children);
55
+ }
56
+ bindReactiveOption(options.displayType, this.#displayType);
57
+ bindReactiveOption(options.allowRemove, this.#allowRemove);
58
+ bindReactiveOption(options.allowRemoveAll, this.#allowRemoveAll);
59
+ }
60
+ get(index) {
61
+ return this.children()[index];
62
+ }
63
+ getValue(index) {
64
+ return this.get(index)?.value();
65
+ }
66
+ getRawValue(index) {
67
+ return this.get(index)?.rawValue();
68
+ }
69
+ reset() {
70
+ for (const child of untracked(this.#children)) {
71
+ child.reset();
72
+ }
73
+ this.#removedChildren.set(new Set());
74
+ }
75
+ indexOf(child) {
76
+ const index = untracked(this.children).indexOf(child);
77
+ return (index == -1) ? null : index;
78
+ }
79
+ add(...children) {
80
+ this.#children.set([...untracked(this.children), ...children]);
81
+ for (const child of children) {
82
+ child.setParent(this);
83
+ }
84
+ }
85
+ addValue(...values) {
86
+ const children = values.map((value) => this.builder(value));
87
+ this.add(...children);
88
+ }
89
+ removeAll() {
90
+ const newlyRemovedChildren = [];
91
+ for (const child of untracked(this.children)) {
92
+ if (untracked(child.parent) == this) {
93
+ child.setParent(null);
94
+ }
95
+ newlyRemovedChildren.push(child);
96
+ }
97
+ this.#removedChildren.update((removedChildren) => new Set([...removedChildren, ...newlyRemovedChildren]));
98
+ this.#children.set([]);
99
+ }
100
+ remove(child) {
101
+ const index = this.indexOf(child);
102
+ if (isNotNull(index)) {
103
+ this.removeAt(index);
104
+ }
105
+ }
106
+ removeAt(index, count = 1) {
107
+ const children = untracked(this.children);
108
+ const newlyRemovedChildren = [];
109
+ const end = Math.min(index + count, children.length);
110
+ if (end <= index) {
111
+ return;
112
+ }
113
+ for (let i = index; i < end; i++) {
114
+ const child = children[i];
115
+ child.setParent(null);
116
+ newlyRemovedChildren.push(child);
117
+ }
118
+ const spliced = children.toSpliced(index, 1);
119
+ this.#removedChildren.update((removedChildren) => new Set([...removedChildren, ...newlyRemovedChildren]));
120
+ this.#children.set(spliced);
121
+ }
122
+ setValue(values) {
123
+ if (values == ignoreFormValue) {
124
+ return;
125
+ }
126
+ let children = untracked(this.#children);
127
+ const newlyRemovedChildren = [];
128
+ if (values.length < children.length) {
129
+ for (let i = values.length; i < children.length - 1; i++) {
130
+ const child = children[i];
131
+ child.setParent(null);
132
+ newlyRemovedChildren.push(child);
133
+ }
134
+ children = children.slice(0, values.length);
135
+ }
136
+ else if (values.length > children.length) {
137
+ const newChildren = createArray(values.length - children.length, (index) => this.builder(values[children.length + index]));
138
+ children = [...children, ...newChildren];
139
+ }
140
+ for (let i = 0; i < values.length; i++) {
141
+ children[i].setValue(values[i]);
142
+ }
143
+ this.#removedChildren.update((removedChildren) => new Set([...removedChildren, ...newlyRemovedChildren]));
144
+ this.#children.set(children);
145
+ }
146
+ markAsDirty({ onlySelf = false } = {}) {
147
+ this.#selfDirty.set(true);
148
+ if (onlySelf) {
149
+ return;
150
+ }
151
+ for (const child of untracked(this.children)) {
152
+ child.markAsDirty();
153
+ }
154
+ }
155
+ markAsTouched({ onlySelf = false } = {}) {
156
+ this.#selfTouched.set(true);
157
+ if (onlySelf) {
158
+ return;
159
+ }
160
+ for (const child of untracked(this.children)) {
161
+ child.markAsTouched();
162
+ }
163
+ }
164
+ setParent(parent, force) {
165
+ super.setParent(parent, force);
166
+ this.updateParents(true);
167
+ }
168
+ *[Symbol.iterator]() {
169
+ yield* untracked(this.children);
170
+ }
171
+ getChildPath(child) {
172
+ return computed(() => {
173
+ const key = this.#children().indexOf(child);
174
+ return this.path().add((key >= 0) ? key : 'CHILD_NOT_FOUND');
175
+ });
176
+ }
177
+ updateParents(force) {
178
+ for (const child of untracked(this.#children)) {
179
+ child.setParent(this, force);
180
+ }
181
+ }
182
+ }
183
+ export function formArray(options) {
184
+ return new FormArray(options); // eslint-disable-line @typescript-eslint/no-unsafe-return
185
+ }
186
+ export function isFormArray(value) {
187
+ return (value instanceof FormArray);
188
+ }
@@ -0,0 +1,40 @@
1
+ import type { PartialProperty, ReactiveValue, TypedOmit } from '../types.js';
2
+ import type { FormDialog } from './form-dialog.js';
3
+ import { FormHeaderFooterElement, type FormHeaderFooterElementOptions } from './form-header-footer-element.js';
4
+ import type { DynamicTextOption } from './types.js';
5
+ export type FormButtonType = 'button' | 'submit' | 'reset';
6
+ export type FormButtonStyle = 'basic' | 'flat' | 'raised' | 'stroked' | 'icon' | 'fab';
7
+ export type FormButtonColor = 'primary' | 'basic' | 'secondary' | 'warn';
8
+ export type IconLocation = 'before' | 'after';
9
+ export type IconSize = 'small' | 'normal';
10
+ export type FormButtonHandler = () => any;
11
+ export type FormButtonOptions = FormHeaderFooterElementOptions & {
12
+ handler: FormButtonHandler;
13
+ label?: ReactiveValue<DynamicTextOption>;
14
+ type?: ReactiveValue<FormButtonType | null>;
15
+ disabled?: ReactiveValue<boolean | null>;
16
+ style?: ReactiveValue<FormButtonStyle | null>;
17
+ color?: ReactiveValue<FormButtonColor | null>;
18
+ icon?: ReactiveValue<string | null>;
19
+ iconLocation?: ReactiveValue<IconLocation | null>;
20
+ iconSize?: ReactiveValue<IconSize | null>;
21
+ };
22
+ export declare class FormButton extends FormHeaderFooterElement {
23
+ #private;
24
+ readonly handler: FormButtonHandler;
25
+ readonly label: import("../signals/api.js").Signal<DynamicTextOption>;
26
+ readonly type: import("../signals/api.js").Signal<FormButtonType>;
27
+ readonly disabled: import("../signals/api.js").Signal<boolean>;
28
+ readonly style: import("../signals/api.js").Signal<FormButtonStyle>;
29
+ readonly color: import("../signals/api.js").Signal<FormButtonColor>;
30
+ readonly icon: import("../signals/api.js").Signal<string | null>;
31
+ readonly iconLocation: import("../signals/api.js").Signal<IconLocation>;
32
+ readonly iconSize: import("../signals/api.js").Signal<IconSize>;
33
+ constructor(options: FormButtonOptions);
34
+ }
35
+ export declare function formButton(options: FormButtonOptions): FormButton;
36
+ export declare function dialogFormButton(options: {
37
+ dialog: FormDialog;
38
+ showErrors?: boolean;
39
+ } & TypedOmit<PartialProperty<FormButtonOptions, 'handler'>, 'icon' | 'color'>): FormButton;
40
+ export declare function isFormButton(value: any): value is FormButton;
@@ -0,0 +1,49 @@
1
+ import { computed, signal } from '../signals/api.js';
2
+ import { FormHeaderFooterElement } from './form-header-footer-element.js';
3
+ import { bindReactiveOption } from './utils.js';
4
+ export class FormButton extends FormHeaderFooterElement {
5
+ #label = signal(null);
6
+ #type = signal('button');
7
+ #disabled = signal(false);
8
+ #style = signal('flat');
9
+ #color = signal('primary');
10
+ #icon = signal(null);
11
+ #iconLocation = signal('before');
12
+ #iconSize = signal('normal');
13
+ handler;
14
+ label = this.#label.asReadonly();
15
+ type = this.#type.asReadonly();
16
+ disabled = this.#disabled.asReadonly();
17
+ style = this.#style.asReadonly();
18
+ color = this.#color.asReadonly();
19
+ icon = this.#icon.asReadonly();
20
+ iconLocation = this.#iconLocation.asReadonly();
21
+ iconSize = this.#iconSize.asReadonly();
22
+ constructor(options) {
23
+ super(options);
24
+ this.handler = options.handler;
25
+ bindReactiveOption(options.label, this.#label, { defaultValue: null });
26
+ bindReactiveOption(options.type, this.#type, { defaultValue: 'button' });
27
+ bindReactiveOption(options.disabled, this.#disabled, { defaultValue: false });
28
+ bindReactiveOption(options.style, this.#style, { defaultValue: 'flat' });
29
+ bindReactiveOption(options.color, this.#color, { defaultValue: 'primary' });
30
+ bindReactiveOption(options.icon, this.#icon, { defaultValue: null });
31
+ bindReactiveOption(options.iconLocation, this.#iconLocation, { defaultValue: 'before' });
32
+ bindReactiveOption(options.iconSize, this.#iconSize, { defaultValue: 'normal' });
33
+ }
34
+ }
35
+ export function formButton(options) {
36
+ return new FormButton(options);
37
+ }
38
+ export function dialogFormButton(options) {
39
+ const showError = computed(() => ((options.showErrors ?? true) ? options.dialog.touched() ? options.dialog.invalid() : false : false));
40
+ return formButton({
41
+ handler: () => options.dialog.openDialog(),
42
+ ...options,
43
+ icon: computed(() => (showError() ? 'warning' : null)),
44
+ color: computed(() => (showError() ? 'warn' : null))
45
+ });
46
+ }
47
+ export function isFormButton(value) {
48
+ return (value instanceof FormButton);
49
+ }
@@ -0,0 +1,24 @@
1
+ import type { OneOrMany, ReactiveValue } from '../types.js';
2
+ import { AbstractControl, type AbstractControlOptions } from './abstract-control.js';
3
+ import type { FormHeaderFooterElement } from './form-header-footer-element.js';
4
+ import type { ClassesOption, ClassesOptions } from './types.js';
5
+ export type FormContainerOptions<T> = AbstractControlOptions<T> & ClassesOptions & {
6
+ displayLabel?: ReactiveValue<boolean>;
7
+ displayLine?: ReactiveValue<boolean>;
8
+ headerContainerClasses?: ReactiveValue<ClassesOption>;
9
+ footerContainerClasses?: ReactiveValue<ClassesOption>;
10
+ headerElements?: ReactiveValue<OneOrMany<FormHeaderFooterElement>>;
11
+ footerElements?: ReactiveValue<OneOrMany<FormHeaderFooterElement>>;
12
+ };
13
+ export declare abstract class FormContainer<T, TRaw> extends AbstractControl<T, TRaw> {
14
+ #private;
15
+ readonly displayLabel: import("../signals/api.js").Signal<boolean>;
16
+ readonly displayLine: import("../signals/api.js").Signal<boolean>;
17
+ readonly headerElements: import("../signals/api.js").Signal<readonly FormHeaderFooterElement[]>;
18
+ readonly footerElements: import("../signals/api.js").Signal<readonly FormHeaderFooterElement[]>;
19
+ readonly headerContainerClasses: import("../signals/api.js").Signal<readonly string[]>;
20
+ readonly footerContainerClasses: import("../signals/api.js").Signal<readonly string[]>;
21
+ readonly classes: import("../signals/api.js").Signal<readonly string[]>;
22
+ constructor(options: FormContainerOptions<T>);
23
+ }
24
+ export declare function isFormContainer(value: any): value is FormContainer<any, any>;