Skip to content

Commit

Permalink
Merge pull request #23 from CrowdStrike/fix-value
Browse files Browse the repository at this point in the history
Fix rendering `name` attribute, reorganize internal types
  • Loading branch information
simonihmig authored Jan 27, 2023
2 parents 0f00aca + d1a2e71 commit 3e2e007
Show file tree
Hide file tree
Showing 17 changed files with 174 additions and 86 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<input
name={{@name}}
type='checkbox'
checked={{@value}}
id={{@fieldId}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface HeadlessFormControlCheckboxComponentSignature {
Element: HTMLInputElement;
Args: {
value: boolean;
name: string;
fieldId: string;
setValue: (value: boolean) => void;
invalid: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<input
name={{@name}}
type={{@type}}
value={{@value}}
id={{@fieldId}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface HeadlessFormControlInputComponentSignature {
Element: HTMLInputElement;
Args: {
value: string;
name: string;
type?: InputType;
fieldId: string;
setValue: (value: string) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
label=(component (ensure-safe-component this.LabelComponent) fieldId=uuid)
input=(component
(ensure-safe-component this.RadioInputComponent)
name=@name
fieldId=uuid
value=@value
checked=this.isChecked
Expand Down
3 changes: 2 additions & 1 deletion ember-headless-form/src/components/-private/control/radio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { ComponentLike, WithBoundArgs } from '@glint/template';
export interface HeadlessFormControlRadioComponentSignature {
Args: {
value: string;
name: string;
selected: string;
setValue: (value: string) => void;
};
Expand All @@ -19,7 +20,7 @@ export interface HeadlessFormControlRadioComponentSignature {
label: WithBoundArgs<typeof LabelComponent, 'fieldId'>;
input: WithBoundArgs<
typeof RadioInputComponent,
'fieldId' | 'value' | 'setValue' | 'checked'
'fieldId' | 'value' | 'setValue' | 'checked' | 'name'
>;
}
];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<input
name={{@name}}
type='radio'
value={{@value}}
checked={{@checked}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface HeadlessFormControlRadioInputComponentSignature {
Element: HTMLInputElement;
Args: {
value: string;
name: string;
checked: boolean;
fieldId: string;
setValue: (value: string) => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<textarea
name={{@name}}
id={{@fieldId}}
aria-invalid={{if @invalid 'true'}}
aria-errormessage={{if @invalid @errorId}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface HeadlessFormControlTextareaComponentSignature {
Element: HTMLTextAreaElement;
Args: {
value: string;
name: string;
fieldId: string;
setValue: (value: string) => void;
invalid: boolean;
Expand Down
2 changes: 1 addition & 1 deletion ember-headless-form/src/components/-private/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Component from '@glimmer/component';

import type { ValidationError } from '../headless-form';
import type { ValidationError } from './types';

export interface HeadlessFormErrorsComponentSignature<VALUE> {
Element: HTMLDivElement;
Expand Down
4 changes: 4 additions & 0 deletions ember-headless-form/src/components/-private/field.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
)
input=(component
(ensure-safe-component this.InputComponent)
name=@name
fieldId=fieldId
errorId=errorId
value=this.valueAsString
Expand All @@ -14,6 +15,7 @@
)
checkbox=(component
(ensure-safe-component this.CheckboxComponent)
name=@name
fieldId=fieldId
errorId=errorId
value=this.valueAsBoolean
Expand All @@ -22,6 +24,7 @@
)
textarea=(component
(ensure-safe-component this.TextareaComponent)
name=@name
fieldId=fieldId
errorId=errorId
value=this.valueAsString
Expand All @@ -30,6 +33,7 @@
)
radio=(component
(ensure-safe-component this.RadioComponent)
name=@name
selected=this.valueAsString
setValue=this.setValue
)
Expand Down
43 changes: 23 additions & 20 deletions ember-headless-form/src/components/-private/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,54 @@ import TextareaComponent from './control/textarea';
import ErrorsComponent from './errors';
import LabelComponent from './label';

import type {
ErrorRecord,
FieldValidateCallback,
HeadlessFormData,
RegisterFieldCallback,
UnregisterFieldCallback,
ValidationError,
} from '../headless-form';
import type { HeadlessFormControlCheckboxComponentSignature } from './control/checkbox';
import type { HeadlessFormControlInputComponentSignature } from './control/input';
import type { HeadlessFormControlRadioComponentSignature } from './control/radio';
import type { HeadlessFormControlTextareaComponentSignature } from './control/textarea';
import type { HeadlessFormErrorsComponentSignature } from './errors';
import type { HeadlessFormLabelComponentSignature } from './label';
import type {
ErrorRecord,
FieldValidateCallback,
FormData,
RegisterFieldCallback,
UnregisterFieldCallback,
} from './types';
import type { FormKey, UserData, ValidationError } from './types';
import type { ComponentLike, WithBoundArgs } from '@glint/template';

export interface HeadlessFormFieldComponentSignature<
DATA extends HeadlessFormData,
KEY extends keyof DATA = keyof DATA
DATA extends UserData,
KEY extends FormKey<FormData<DATA>> = FormKey<FormData<DATA>>
> {
Args: {
data: DATA;
data: FormData<DATA>;
name: KEY;
set: (key: KEY, value: DATA[KEY]) => void;
validate?: FieldValidateCallback<DATA, KEY>;
validate?: FieldValidateCallback<FormData<DATA>, KEY>;
errors?: ErrorRecord<DATA, KEY>;
registerField: RegisterFieldCallback<DATA, KEY>;
unregisterField: UnregisterFieldCallback<DATA, KEY>;
registerField: RegisterFieldCallback<FormData<DATA>, KEY>;
unregisterField: UnregisterFieldCallback<FormData<DATA>, KEY>;
};
Blocks: {
default: [
{
label: WithBoundArgs<typeof LabelComponent, 'fieldId'>;
input: WithBoundArgs<
typeof InputComponent,
'fieldId' | 'value' | 'setValue' | 'invalid' | 'errorId'
'name' | 'fieldId' | 'value' | 'setValue' | 'invalid' | 'errorId'
>;
checkbox: WithBoundArgs<
typeof CheckboxComponent,
'fieldId' | 'value' | 'setValue' | 'invalid' | 'errorId'
'name' | 'fieldId' | 'value' | 'setValue' | 'invalid' | 'errorId'
>;
radio: WithBoundArgs<
typeof RadioComponent,
'name' | 'selected' | 'setValue'
>;
radio: WithBoundArgs<typeof RadioComponent, 'selected' | 'setValue'>;
textarea: WithBoundArgs<
typeof TextareaComponent,
'fieldId' | 'value' | 'setValue' | 'invalid' | 'errorId'
'name' | 'fieldId' | 'value' | 'setValue' | 'invalid' | 'errorId'
>;
value: DATA[KEY];
id: string;
Expand All @@ -68,8 +71,8 @@ export interface HeadlessFormFieldComponentSignature<
}

export default class HeadlessFormFieldComponent<
DATA extends HeadlessFormData,
KEY extends keyof DATA = keyof DATA
DATA extends FormData,
KEY extends FormKey<FormData<DATA>> = FormKey<FormData<DATA>>
> extends Component<HeadlessFormFieldComponentSignature<DATA, KEY>> {
LabelComponent: ComponentLike<HeadlessFormLabelComponentSignature> =
LabelComponent;
Expand Down
84 changes: 84 additions & 0 deletions ember-headless-form/src/components/-private/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* What the user can pass as @data
*/
export type UserData = object;

/**
* The subset of properties of DATA, whose keys are strings (and not number or symbol)
* Only this data is useable in the form
*/
export type FormData<DATA extends UserData = UserData> = OnlyStringKeys<DATA>;

/**
* Returns the type of all keys of DATA, that are also strings. Only strings can be used as field @name
*/
export type FormKey<DATA extends UserData> = keyof DATA & string;

/**
* Generic interface for all validation errors
*/
export interface ValidationError<T = unknown> {
type: string;
// @todo does a validator need to add this? we already have the value internally
value: T;
message?: string;
}

export type ErrorRecord<
DATA extends FormData,
KEY extends FormKey<DATA> = FormKey<DATA>
> = Partial<Record<KEY, ValidationError<DATA[KEY]>[]>>;

/**
* Callback used for form level validation
*/
export type FormValidateCallback<DATA extends FormData> = (
formData: DATA
) => undefined | ErrorRecord<DATA> | Promise<undefined | ErrorRecord<DATA>>;

/**
* Callback used for field level validation
*/
export type FieldValidateCallback<
DATA extends FormData,
KEY extends FormKey<DATA> = FormKey<DATA>
> = (
fieldValue: DATA[KEY],
fieldName: KEY,
formData: DATA
) =>
| undefined
| ValidationError<DATA[KEY]>[]
| Promise<undefined | ValidationError<DATA[KEY]>[]>;

/**
* Internal structure to track used fields
* @private
*/
export interface FieldData<
DATA extends FormData,
KEY extends FormKey<DATA> = FormKey<DATA>
> {
validate?: FieldValidateCallback<DATA, KEY>;
}

/**
* For internal field registration
* @private
*/
export type RegisterFieldCallback<
DATA extends FormData,
KEY extends FormKey<DATA> = FormKey<DATA>
> = (name: KEY, field: FieldData<DATA, KEY>) => void;

export type UnregisterFieldCallback<
DATA extends FormData,
KEY extends FormKey<DATA> = FormKey<DATA>
> = (name: KEY) => void;

/**
* Mapper type to construct subset of objects, whose keys are only strings (and not number or symbol)
*/
export type OnlyStringKeys<T extends object> = {
[P in Extract<keyof T, string>]: T[P];
};
Loading

0 comments on commit 3e2e007

Please sign in to comment.