feat(form): readOnly flag (#166)
* feat: add readOnly flag on form * fix: resolve PR coments * fix: add flag disabled to fields that dont have effect on readonly attr
This commit is contained in:
parent
640e945959
commit
f502e6ca24
11 changed files with 2334 additions and 5635 deletions
7810
package-lock.json
generated
7810
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -14,9 +14,10 @@ export interface Props {
|
||||||
control: FormControl;
|
control: FormControl;
|
||||||
showValidationHints: boolean;
|
showValidationHints: boolean;
|
||||||
showErrors?: boolean; // feature flag for showing validation errors
|
showErrors?: boolean; // feature flag for showing validation errors
|
||||||
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { control, showValidationHints, showErrors = false } = Astro.props;
|
const { control, showValidationHints, showErrors = false, readOnly = false } = Astro.props;
|
||||||
|
|
||||||
const hasErrors: boolean | null = !!control.errors?.length;
|
const hasErrors: boolean | null = !!control.errors?.length;
|
||||||
---
|
---
|
||||||
|
@ -25,12 +26,12 @@ const hasErrors: boolean | null = !!control.errors?.length;
|
||||||
<Label control={control} showValidationHints={showValidationHints}>
|
<Label control={control} showValidationHints={showValidationHints}>
|
||||||
{
|
{
|
||||||
control.type === 'radio' ? (
|
control.type === 'radio' ? (
|
||||||
<RadioGroup control={control as Radio} />
|
<RadioGroup control={control as Radio} readOnly={readOnly} />
|
||||||
) :
|
) :
|
||||||
control.type === 'dropdown' ? (
|
control.type === 'dropdown' ? (
|
||||||
<DropdownControl control={control as Dropdown} />
|
<DropdownControl control={control as Dropdown} readOnly={readOnly} />
|
||||||
) : (
|
) : (
|
||||||
<Input control={control} />
|
<Input control={control} readOnly={readOnly} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{showErrors && <Errors errors={control.errors} />}
|
{showErrors && <Errors errors={control.errors} />}
|
||||||
|
|
|
@ -5,16 +5,17 @@ import Field from './Field.astro';
|
||||||
export interface Props {
|
export interface Props {
|
||||||
group: FormGroup;
|
group: FormGroup;
|
||||||
showValidationHints: boolean;
|
showValidationHints: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { group, showValidationHints } = Astro.props;
|
const { group, showValidationHints, readOnly = false } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<fieldset id={group.name} name={group.name}>
|
<fieldset id={group.name} name={group.name}>
|
||||||
{group.name && <legend>{group.name}</legend>}
|
{group.name && <legend>{group.name}</legend>}
|
||||||
{
|
{
|
||||||
group?.controls?.map((control) => (
|
group?.controls?.map((control) => (
|
||||||
<Field showValidationHints={showValidationHints} control={control} />
|
<Field showValidationHints={showValidationHints} control={control} readOnly={readOnly} />
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
|
@ -9,9 +9,10 @@ export interface Props {
|
||||||
submitControl?: Submit;
|
submitControl?: Submit;
|
||||||
theme?: 'light' | 'dark';
|
theme?: 'light' | 'dark';
|
||||||
showValidationHints?: boolean;
|
showValidationHints?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { submitControl, formGroups = [], theme, showValidationHints = false } = Astro.props;
|
const { submitControl, formGroups = [], theme, showValidationHints = false, readOnly = false } = Astro.props;
|
||||||
|
|
||||||
const formTheme = theme ?? 'light';
|
const formTheme = theme ?? 'light';
|
||||||
const formName = Array.isArray(formGroups) ? null : formGroups?.name || null;
|
const formName = Array.isArray(formGroups) ? null : formGroups?.name || null;
|
||||||
|
@ -26,10 +27,10 @@ const formName = Array.isArray(formGroups) ? null : formGroups?.name || null;
|
||||||
{
|
{
|
||||||
Array.isArray(formGroups)
|
Array.isArray(formGroups)
|
||||||
? formGroups?.map((group) => (
|
? formGroups?.map((group) => (
|
||||||
<FieldSet showValidationHints={showValidationHints} group={group} />
|
<FieldSet showValidationHints={showValidationHints} group={group} readOnly={readOnly}/>
|
||||||
))
|
))
|
||||||
: formGroups?.controls.map((control) => (
|
: formGroups?.controls.map((control) => (
|
||||||
<Field showValidationHints={showValidationHints} control={control} />
|
<Field showValidationHints={showValidationHints} control={control} readOnly={readOnly}/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,9 +6,10 @@ import type { Dropdown, ControlOption } from '@astro-reactive/common';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
control: Dropdown;
|
control: Dropdown;
|
||||||
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { control } = Astro.props;
|
const { control, readOnly } = Astro.props;
|
||||||
|
|
||||||
const options = control.options.map((option: string | ControlOption) => {
|
const options = control.options.map((option: string | ControlOption) => {
|
||||||
if (typeof option === 'string') {
|
if (typeof option === 'string') {
|
||||||
|
@ -21,7 +22,11 @@ const options = control.options.map((option: string | ControlOption) => {
|
||||||
});
|
});
|
||||||
---
|
---
|
||||||
|
|
||||||
<select name={control.name} id={control.name}>
|
<select
|
||||||
|
name={control.name}
|
||||||
|
id={control.name}
|
||||||
|
disabled={readOnly || null}
|
||||||
|
>
|
||||||
{
|
{
|
||||||
control?.placeholder && (
|
control?.placeholder && (
|
||||||
<option value="" disabled selected={!control?.value}>
|
<option value="" disabled selected={!control?.value}>
|
||||||
|
|
|
@ -7,9 +7,10 @@ import type { FormControl } from '../../core/form-control';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
control: FormControl;
|
control: FormControl;
|
||||||
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { control } = Astro.props;
|
const { control, readOnly } = Astro.props;
|
||||||
|
|
||||||
const { validators = [] } = control;
|
const { validators = [] } = control;
|
||||||
|
|
||||||
|
@ -37,5 +38,7 @@ const validatorAttributes: Record<string, string> = validators?.reduce((prev, va
|
||||||
data-label={control.label}
|
data-label={control.label}
|
||||||
data-label-position={control.labelPosition}
|
data-label-position={control.labelPosition}
|
||||||
data-validator-haserrors={hasErrors.toString()}
|
data-validator-haserrors={hasErrors.toString()}
|
||||||
|
readonly={readOnly || null}
|
||||||
|
disabled={(readOnly || null) && control.type === 'checkbox'}
|
||||||
{...validatorAttributes}
|
{...validatorAttributes}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,9 +6,10 @@ import type { Radio, ControlOption } from '@astro-reactive/common';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
control: Radio;
|
control: Radio;
|
||||||
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { control } = Astro.props;
|
const { control, readOnly = false } = Astro.props;
|
||||||
|
|
||||||
const options = control.options.map((option: string | ControlOption) => {
|
const options = control.options.map((option: string | ControlOption) => {
|
||||||
if (typeof option === 'string') {
|
if (typeof option === 'string') {
|
||||||
|
@ -29,6 +30,8 @@ const options = control.options.map((option: string | ControlOption) => {
|
||||||
name={control.name}
|
name={control.name}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
checked={option.value === control.value}
|
checked={option.value === control.value}
|
||||||
|
readonly={readOnly || null}
|
||||||
|
disabled={readOnly || null}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
{option.label}
|
{option.label}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -149,4 +149,27 @@ describe('Dropdown.astro test', () => {
|
||||||
// assert
|
// assert
|
||||||
expect(actualResult).to.contain(expectedResult);
|
expect(actualResult).to.contain(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should render disabled attribute if the prop readOnly is passed as true', async () => {
|
||||||
|
// arrange
|
||||||
|
const expectedOptions = 1;
|
||||||
|
const element = /disabled/g;
|
||||||
|
const props = {
|
||||||
|
control: {
|
||||||
|
label: 'FAKE LABEL',
|
||||||
|
name: 'FAKE NAME',
|
||||||
|
type: 'dropdown',
|
||||||
|
options: ['one', 'two', 'three'],
|
||||||
|
},
|
||||||
|
readOnly: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// act
|
||||||
|
component = await getComponentOutput('./components/controls/Dropdown.astro', props);
|
||||||
|
const actualResult = cleanString(component.html);
|
||||||
|
const matches = actualResult.match(element) || [];
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(matches.length).to.equal(expectedOptions);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -79,4 +79,27 @@ describe('Field.astro test', () => {
|
||||||
// assert
|
// assert
|
||||||
expect(actualResult).to.contain(expectedResult);
|
expect(actualResult).to.contain(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should render field with readOnly attribute when readOnly is true', async () => {
|
||||||
|
// arrange
|
||||||
|
const expectedLabel = 'TestLabel';
|
||||||
|
const expectedAttribute = 'readonly';
|
||||||
|
const props = {
|
||||||
|
control: {
|
||||||
|
label: expectedLabel,
|
||||||
|
name: 'username',
|
||||||
|
labelPosition: 'left',
|
||||||
|
validators: ['validator-required'],
|
||||||
|
},
|
||||||
|
readOnly: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// act
|
||||||
|
component = await getComponentOutput('./components/Field.astro', props);
|
||||||
|
const actualResult = cleanString(component.html);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(actualResult).to.contain(expectedLabel);
|
||||||
|
expect(actualResult).to.contain(expectedAttribute);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -85,6 +85,29 @@ describe('Form.astro test', () => {
|
||||||
const actualResult = cleanString(component.html);
|
const actualResult = cleanString(component.html);
|
||||||
const matches = actualResult.match(element) || [];
|
const matches = actualResult.match(element) || [];
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(matches.length).to.equal(expectedCount);
|
||||||
|
});
|
||||||
|
it('Should render readOnly fields if the flag is passed as true', async () => {
|
||||||
|
// arrange
|
||||||
|
const expectedCount = 3;
|
||||||
|
const element = /readonly/g;
|
||||||
|
const fakeFormGroup = {
|
||||||
|
controls: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'fake-checkbox',
|
||||||
|
label: 'FAKE CHECKBOX',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const props = { formGroups: Array(expectedCount).fill(fakeFormGroup), readOnly: true };
|
||||||
|
component = await getComponentOutput('./components/Form.astro', props);
|
||||||
|
|
||||||
|
// act
|
||||||
|
const actualResult = cleanString(component.html);
|
||||||
|
const matches = actualResult.match(element) || [];
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
expect(matches.length).to.equal(expectedCount);
|
expect(matches.length).to.equal(expectedCount);
|
||||||
});
|
});
|
||||||
|
|
|
@ -87,4 +87,52 @@ describe('RadioGroup.astro test', () => {
|
||||||
// assert
|
// assert
|
||||||
expect(actualResult).to.contain(expectedResult);
|
expect(actualResult).to.contain(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should render readonly attribute if the prop readOnly is passed as true', async () => {
|
||||||
|
// arrange
|
||||||
|
const expectedOptions = 3;
|
||||||
|
const element = /readonly/g;
|
||||||
|
const props = {
|
||||||
|
control: {
|
||||||
|
label: 'FAKE LABEL',
|
||||||
|
name: 'FAKE NAME',
|
||||||
|
type: 'radio',
|
||||||
|
options: ['one', 'two', 'three'],
|
||||||
|
},
|
||||||
|
showValidationHints: true,
|
||||||
|
readOnly: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// act
|
||||||
|
component = await getComponentOutput('./components/controls/RadioGroup.astro', props);
|
||||||
|
const actualResult = cleanString(component.html);
|
||||||
|
const matches = actualResult.match(element) || [];
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(matches.length).to.equal(expectedOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should render disabled attribute if the prop readOnly is passed as true', async () => {
|
||||||
|
// arrange
|
||||||
|
const expectedOptions = 3;
|
||||||
|
const element = /disabled/g;
|
||||||
|
const props = {
|
||||||
|
control: {
|
||||||
|
label: 'FAKE LABEL',
|
||||||
|
name: 'FAKE NAME',
|
||||||
|
type: 'radio',
|
||||||
|
options: ['one', 'two', 'three'],
|
||||||
|
},
|
||||||
|
showValidationHints: true,
|
||||||
|
readOnly: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// act
|
||||||
|
component = await getComponentOutput('./components/controls/RadioGroup.astro', props);
|
||||||
|
const actualResult = cleanString(component.html);
|
||||||
|
const matches = actualResult.match(element) || [];
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(matches.length).to.equal(expectedOptions);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue