Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions src/components/checkbox/checkbox-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ import { blazorDeepImport } from '../common/decorators/blazorDeepImport.js';
import type { Constructor } from '../common/mixins/constructor.js';
import { EventEmitterMixin } from '../common/mixins/event-emitter.js';
import { FormAssociatedCheckboxRequiredMixin } from '../common/mixins/forms/associated-required.js';
import {
createFormValueState,
defaultBooleanTransformers,
type FormValueOf,
} from '../common/mixins/forms/form-value.js';
import { FormValueBooleanTransformers } from '../common/mixins/forms/form-transformers.js';
import { createFormValueState } from '../common/mixins/forms/form-value.js';
import type { ToggleLabelPosition } from '../types.js';
import { checkBoxValidators } from './validators.js';

Expand Down Expand Up @@ -44,11 +41,10 @@ export class IgcCheckboxBaseComponent extends FormAssociatedCheckboxRequiredMixi
});

protected readonly _focusRingManager = addKeyboardFocusRing(this);
protected override readonly _formValue: FormValueOf<boolean> =
createFormValueState(this, {
initialValue: false,
transformers: defaultBooleanTransformers,
});
protected override readonly _formValue = createFormValueState(this, {
initialValue: false,
transformers: FormValueBooleanTransformers,
});
protected _value!: string;

@query('input', true)
Expand All @@ -65,7 +61,7 @@ export class IgcCheckboxBaseComponent extends FormAssociatedCheckboxRequiredMixi
public set value(value: string) {
this._value = value;
if (this.checked) {
this._setFormValue(this._value || 'on');
this._formValue.setValueAndFormState(this.checked);
}
}

Expand Down
22 changes: 10 additions & 12 deletions src/components/combo/combo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ import { registerComponent } from '../common/definitions/register.js';
import type { Constructor } from '../common/mixins/constructor.js';
import { EventEmitterMixin } from '../common/mixins/event-emitter.js';
import { FormAssociatedRequiredMixin } from '../common/mixins/forms/associated-required.js';
import {
createFormValueState,
type FormValueOf,
} from '../common/mixins/forms/form-value.js';
import { createFormValueState } from '../common/mixins/forms/form-value.js';
import { partMap } from '../common/part-map.js';
import {
addSafeEventListener,
Expand Down Expand Up @@ -147,14 +144,15 @@ export default class IgcComboComponent<
},
});

protected override readonly _formValue: FormValueOf<ComboValue<T>[]> =
createFormValueState<ComboValue<T>[]>(this, {
initialValue: [],
transformers: {
setValue: asArray,
setDefaultValue: asArray,
},
});
protected override readonly _formValue = createFormValueState<
ComboValue<T>[]
>(this, {
initialValue: [],
transformers: {
setValue: asArray,
setDefaultValue: asArray,
},
});
private _data: T[] = [];

private _valueKey?: Keys<T>;
Expand Down
7 changes: 4 additions & 3 deletions src/components/common/mixins/form-associated.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
type Validator,
} from '../validators.js';
import { FormAssociatedRequiredMixin } from './forms/associated-required.js';
import { createFormValueState, type FormValueOf } from './forms/form-value.js';
import { createFormValueState } from './forms/form-value.js';
import type {
FormAssociatedElementInterface,
FormRequiredInterface,
Expand Down Expand Up @@ -52,8 +52,9 @@ describe('Form associated mixin tests', () => {
return [requiredValidator, minLengthValidator, maxLengthValidator];
}

protected override _formValue: FormValueOf<string> =
createFormValueState(this, { initialValue: '' });
protected override _formValue = createFormValueState(this, {
initialValue: '',
});

private _minLength!: number;
private _maxLength!: number;
Expand Down
109 changes: 109 additions & 0 deletions src/components/common/mixins/forms/form-transformers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
convertToDate,
convertToDateRange,
getDateFormValue,
} from '../../../calendar/helpers.js';
import type { DateRangeValue } from '../../../date-range-picker/date-range-picker.js';
import { asNumber } from '../../util.js';
import type { FormValueType, IgcFormControl } from './types.js';

export type FormValueTransformers<T> = {
setValue: (value: T) => T;
getValue: (value: T) => T;
setDefaultValue: (value: T) => T;
getDefaultValue: (value: T) => T;
setFormValue: (value: T, host: IgcFormControl) => FormValueType;
};

export type FormValueConfig<T> = {
initialValue: T;
initialDefaultValue?: T;
transformers?: Partial<FormValueTransformers<T>>;
};

// Transformers

export const FormValueDefaultTransformers: FormValueTransformers<string> = {
setValue: (value) => value || '',
getValue: (value) => value,
setDefaultValue: (value) => value || '',
getDefaultValue: (value) => value,
setFormValue: (value, _) => value || null,
};

export const FormValueBooleanTransformers: Partial<
FormValueTransformers<boolean>
> = {
setValue: Boolean,
setDefaultValue: Boolean,
setFormValue: (checked, host) =>
checked && 'value' in host ? (host.value as string) || 'on' : null,
};

export const FormValueNumberTransformers: Partial<
FormValueTransformers<number>
> = {
setValue: asNumber,
setDefaultValue: asNumber,
setFormValue: (value) => value.toString(),
};

export const FormValueDateTimeTransformers: Partial<
FormValueTransformers<Date | null>
> = {
setValue: convertToDate,
setDefaultValue: convertToDate,
setFormValue: getDateFormValue,
};

export const FormValueDateRangeTransformers: Partial<
FormValueTransformers<DateRangeValue | null>
> = {
setValue: convertToDateRange,
setDefaultValue: convertToDateRange,
setFormValue: (value, host) => {
if (!host.name) {
return null;
}
const start = value?.start?.toISOString();
const end = value?.end?.toISOString();

const formData = new FormData();

if (start) {
formData.append(`${host.name}-start`, start);
}
if (end) {
formData.append(`${host.name}-end`, end);
}

return formData;
},
};

export const FormValueFileListTransformers: FormValueTransformers<FileList | null> =
{
setValue: (value) => value || null,
getValue: (value) => value,
setDefaultValue: (value) => value || null,
getDefaultValue: (value) => value,
setFormValue: (files, host) => {
if (!(host.name && files)) {
return null;
}

const formData = new FormData();
for (const file of files) {
formData.append(host.name, file);
}

return formData;
},
};

export const FormValueSelectTransformers: Partial<
FormValueTransformers<string | undefined>
> = {
setValue: (value) => value || undefined,
setDefaultValue: (value) => value || undefined,
};
139 changes: 13 additions & 126 deletions src/components/common/mixins/forms/form-value.ts
Original file line number Diff line number Diff line change
@@ -1,132 +1,21 @@
import type { LitElement } from 'lit';
import {
convertToDate,
convertToDateRange,
getDateFormValue,
} from '../../../calendar/helpers.js';
import type { DateRangeValue } from '../../../date-range-picker/date-range-picker.js';
import { asNumber } from '../../util.js';
import type { FormValueType, IgcFormControl } from './types.js';

type FormValueTransformers<T> = {
setValue: (value: T) => T;
getValue: (value: T) => T;
setDefaultValue: (value: T) => T;
getDefaultValue: (value: T) => T;
setFormValue: (value: T, host: IgcFormControl) => FormValueType;
};

type FormValueConfig<T> = {
initialValue: T;
initialDefaultValue?: T;
transformers?: Partial<FormValueTransformers<T>>;
};

const defaultTransformers: FormValueTransformers<string> = {
setValue: (value) => value || '',
getValue: (value) => value,
setDefaultValue: (value) => value || '',
getDefaultValue: (value) => value,
setFormValue: (value, _: IgcFormControl) => value || null,
};

export const defaultBooleanTransformers: Partial<
FormValueTransformers<boolean>
> = {
setValue: Boolean,
setDefaultValue: Boolean,
setFormValue: (checked, host) => {
return checked && 'value' in host ? (host.value as string) || 'on' : null;
},
};

export const defaultNumberTransformers: Partial<FormValueTransformers<number>> =
{
setValue: asNumber,
setDefaultValue: asNumber,
setFormValue: (value) => value.toString(),
};

export const defaultDateTimeTransformers: Partial<
FormValueTransformers<Date | null>
> = {
setValue: convertToDate,
setDefaultValue: convertToDate,
setFormValue: getDateFormValue,
};

export const defaultFileListTransformer: Partial<
FormValueTransformers<FileList | null>
> = {
setValue: (value) => value || null,
getValue: (value) => value,
setDefaultValue: (value) => value || null,
getDefaultValue: (value) => value,
setFormValue: (files: FileList | null, host: IgcFormControl) => {
if (!host.name || !files) {
return null;
}

const data = new FormData();

for (const file of Array.from(files)) {
data.append(host.name, file);
}

return data;
},
};

/**
* Converts a DateDateRangeValue object to FormData with
* start and end Date values as ISO 8601 strings.
* The keys are prefixed with the host name
* and suffixed with 'start' or 'end' accordingly.
* In case the host does not have a name, it does not participate in form submission.
*
* If the date values are null or undefined, the form data values
* are empty strings ''.
*/
export function getDateRangeFormValue(
value: DateRangeValue | null,
host: IgcFormControl
): FormValueType {
if (!host.name) {
return null;
}

const start = value?.start?.toISOString();
const end = value?.end?.toISOString();

const fd = new FormData();
const prefix = `${host.name}-`;

if (start) {
fd.append(`${prefix}start`, start);
}
if (end) {
fd.append(`${prefix}end`, end);
}

return fd;
}

export const defaultDateRangeTransformers: Partial<
FormValueTransformers<DateRangeValue | null>
> = {
setValue: convertToDateRange,
setDefaultValue: convertToDateRange,
setFormValue: getDateRangeFormValue,
};
type FormValueConfig,
FormValueDefaultTransformers,
type FormValueTransformers,
} from './form-transformers.js';
import type { IgcFormControl } from './types.js';

/* blazorSuppress */
export class FormValue<T> {
private static readonly setFormValueKey = '_setFormValue' as const;

private _host: IgcFormControl;
private readonly _host: IgcFormControl;
private readonly _transformers: FormValueTransformers<T>;
private readonly _setFormValue: IgcFormControl[typeof FormValue.setFormValueKey];

private _value: T;
private _defaultValue: T;
private _transformers: FormValueTransformers<T>;
private _setFormValue: IgcFormControl[typeof FormValue.setFormValueKey];

constructor(host: IgcFormControl, config: FormValueConfig<T>) {
this._host = host;
Expand All @@ -135,7 +24,7 @@ export class FormValue<T> {
this._setFormValue = host[FormValue.setFormValueKey];

this._transformers = {
...defaultTransformers,
...FormValueDefaultTransformers,
...config.transformers,
} as FormValueTransformers<T>;
}
Expand Down Expand Up @@ -166,10 +55,8 @@ export class FormValue<T> {
}

export function createFormValueState<T>(
host: IgcFormControl,
host: LitElement,
config: FormValueConfig<T>
): FormValue<T> {
return new FormValue(host, config);
return new FormValue(host as IgcFormControl, config);
}

export type FormValueOf<T> = ReturnType<typeof createFormValueState<T>>;
Loading