Skip to content

Commit

Permalink
feat: add support for input types date, datetime-local and time (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomosterlund committed Jan 21, 2023
1 parent 0a83e03 commit d2fed45
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 19 deletions.
21 changes: 21 additions & 0 deletions development/Dev.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@ export default {
// lastName: 'empty'
// }
},
date: {
type: 'date',
label: 'Date',
hooks: [useRequired()],
min: '2023-01-01',
max: '2023-01-31',
},
datetimeLocal: {
type: 'datetime-local',
label: 'Datetime',
hooks: [useRequired()],
value: '2023-01-01T00:00',
min: '2023-01-01T00:00',
max: '2023-03-20T00:00',
},
time: {
type: 'time',
label: 'Time',
hooks: [useRequired()],
value: '00:00',
},
newsletter: {
type: 'checkbox',
label: 'Do you want to receive our newsletter?',
Expand Down
2 changes: 2 additions & 0 deletions development/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import CheckboxTests from "../tests/pages/CheckboxTests";
import RightToLeftTests from "../tests/pages/RightToLeft";
import ReactTestsInitializer from "../tests/pages/ReactTestsInitializer";
import DestroyInstanceTest from "../tests/pages/DestroyInstanceTest";
import DateAndTimeTests from "../tests/pages/DateAndTimeTests";

const app = createApp(App)

Expand All @@ -34,6 +35,7 @@ const router = createRouter({
{ path: '/e2e/right-to-left', component: RightToLeftTests },
{ path: '/e2e/react', component: ReactTestsInitializer },
{ path: '/e2e/destroy-instance', component: DestroyInstanceTest },
{ path: '/e2e/date-and-time', component: DateAndTimeTests },
]
})

Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/FormField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export class FormField implements FormFieldInterface {
row: undefined|string = undefined
inputDOMElement: HTMLInputElement | null = null
isHidden = false
min = null;
max = null;
_errorMessages = {}
_onClickHandlers: EventHandler[] = []
_onChangeHandlers: EventHandler[] = []
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/components/input-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ export default class InputElement implements MaquetteComponent {
if ((this.formField._form._config as FormConfig).theme === 'material') inputProps.placeholder = ' '
else if (this.formField.placeholder) inputProps.placeholder = this.formField.placeholder

if (this.formField.min) inputProps.min = this.formField.min
if (this.formField.max) inputProps.max = this.formField.max

return h(
'input',
inputProps
)
}
}
}
15 changes: 13 additions & 2 deletions packages/core/src/types/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@
import FormFieldInterface from './interfaces/FormField.interface'
import {FormFieldConfig} from "./interfaces/FormConfig.interface";

export const FormFieldTypes = ['email', 'password', 'select', 'checkbox', 'text', 'number', 'radiogroup']
export const FormFieldTypes = [
'email',
'password',
'select',
'checkbox',
'text',
'number',
'radiogroup',
'date',
'datetime-local',
'time'
]
export type FormFieldType = typeof FormFieldTypes[number] | string;

export type ValidationType = 'active' | 'passive';
Expand All @@ -23,4 +34,4 @@ export type ErrorMessageObject = { en: string } & Record<string, string>;
type FieldConditionValue = RegExp | boolean | string;
export type FieldCondition = Record<string, FieldConditionValue>;

export type GenericFunction = (...args: any[]) => void;
export type GenericFunction = (...args: any[]) => void;
5 changes: 5 additions & 0 deletions packages/core/src/types/interfaces/FormConfig.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default interface FormConfig {
}

type FormFieldConfigBase = {
// For all fields
type?: FormFieldType;
value?: string|boolean;
label?: string;
Expand All @@ -26,6 +27,10 @@ type FormFieldConfigBase = {
// For select fields
options?: MultiSelectOption[] | RadioButtonOption[];

// For date & time fields
min?: string;
max?: string;

hooks?: HookReturnValue[];
}

Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/types/interfaces/FormField.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export default interface FormFieldInterface {
dependants?: string[];
isHidden?: boolean;

// date & time attributes
min?: string|null;
max?: string|null;

render(mountingEl: HTMLElement): void;
runAllValidators(): void;
getValue(): string | number | undefined | boolean;
Expand All @@ -44,4 +48,4 @@ export default interface FormFieldInterface {
// Allow extensibility
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}
}
8 changes: 5 additions & 3 deletions packages/core/src/util/form-field-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ export class FormFieldResolver {
this.resolveField()
if (formFieldConfig.focus) this.focusOnMount()
}

validateFieldConfig() {
if (![...FormFieldTypes, undefined].includes(this.formFieldConfig.type)) {
throw new FormFieldError(
'Invalid field type. The following are allowed: ' + FormFieldTypes.join(', ')
)
}
}

resolveField() {
this.field.placeholder = this.formFieldConfig.placeholder ? this.formFieldConfig.placeholder : ''
this.field.label = this.formFieldConfig.label ? this.formFieldConfig.label : ''
Expand All @@ -33,6 +33,8 @@ export class FormFieldResolver {
this.field.disabledIf = this.formFieldConfig.disabledIf ? this.formFieldConfig.disabledIf : {}
this.field.hideIf = this.formFieldConfig.hideIf ? this.formFieldConfig.hideIf : {}
this.field.row = this.formFieldConfig.row ? this.formFieldConfig.row : ''
this.field.min = this.formFieldConfig.min ? this.formFieldConfig.min : undefined
this.field.max = this.formFieldConfig.max ? this.formFieldConfig.max : undefined

if (this.formFieldConfig.handleOnClick) this.field._onClickHandlers.push(this.formFieldConfig.handleOnClick)
if (this.formFieldConfig.handleOnChange) this.field._onChangeHandlers.push(this.formFieldConfig.handleOnChange)
Expand Down Expand Up @@ -66,4 +68,4 @@ export class FormFieldResolver {
const targetNode = document.querySelector(((this.field._form as Phormal)._config as FormConfig).el as string) as HTMLElement
observer.observe(targetNode, config);
}
}
}
7 changes: 5 additions & 2 deletions packages/theme-basic/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@

.phlib__input-text,
.phlib__input-textarea,
.phlib__input-select, {
display: block;
.phlib__input-date,
.phlib__input-select,
.phlib__input-datetime-local,
.phlib__input-time, {
font-family: inherit;
width: 100%;
font-size: 1rem;
padding: 0 0.75rem;
Expand Down
19 changes: 9 additions & 10 deletions packages/theme-material/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -198,18 +198,14 @@
}
}

.phlib__input-text {
padding: 0 0.75rem;
}

.phlib__input-select {
padding: 0 0.75rem;
}

.phlib__input-text,
.phlib__input-select, {
.phlib__input-select,
.phlib__input-date,
.phlib__input-datetime-local,
.phlib__input-time, {
appearance: none;
width: 100%;
padding: 0 0.75em;
border: 0;
font-family: inherit;
height: 3.5rem;
Expand Down Expand Up @@ -261,7 +257,10 @@

.phlib__has-label {
.phlib__input-text,
.phlib__input-select {
.phlib__input-select,
.phlib__input-date,
.phlib__input-datetime-local,
.phlib__input-time {
padding-top: 1rem;
}
}
Expand Down
8 changes: 8 additions & 0 deletions tests/e2e/01-smoke.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ const COUNTRY_SELECT = 'input[id="phormal-field-input-country"][role="button"]'
const ZIP_CODE_FIELD = 'input[id="phormal-field-input-zip"]'
const FIRST_NAME_ERROR = 'div[id="phormal-field-error-firstName"]'
const ZIP_CODE_ERROR = 'div[id="phormal-field-error-zip"]'
const DATE_FIELD = 'input[id="phormal-field-input-date"]'
const DATETIME_LOCAL_FIELD = 'input[id="phormal-field-input-datetimeLocal"]'
const TIME_FIELD = 'input[id="phormal-field-input-time"]'

const testRenderingAllFields = () => {
it('should render all expected fields', () => {
cy.get(FIRST_NAME_FIELD).should('exist')
cy.get(LAST_NAME_FIELD).should('exist')
cy.get(COUNTRY_SELECT).should('exist')
cy.get(DATETIME_LOCAL_FIELD).should('exist')
cy.get(TIME_FIELD).should('exist')
})
}

Expand All @@ -21,6 +26,9 @@ const testSettingDefaultValues = () => {
cy.get(LAST_NAME_FIELD).should('have.value', 'Doe')
cy.get(COUNTRY_SELECT).should('have.value', 'United States')
cy.get(ZIP_CODE_FIELD).should('have.value', '51378')
cy.get(DATE_FIELD).should('have.value', '2020-01-01')
cy.get(DATETIME_LOCAL_FIELD).should('have.value', '2020-01-01T12:00')
cy.get(TIME_FIELD).should('have.value', '12:00')
})
}

Expand Down
26 changes: 26 additions & 0 deletions tests/e2e/12-date-and-time.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
describe('Date and Time', () => {
beforeEach(() => {
cy.visit('/#/e2e/date-and-time')
})

it('Displays a date field with min and max values', () => {
cy
.get('#phormal-field-input-date')
.should('have.attr', 'min', '2023-01-01')
.should('have.attr', 'max', '2023-12-31')
})

it('Displays a datetime field with min and max values', () => {
cy
.get('#phormal-field-input-datetimeLocal')
.should('have.attr', 'min', '2023-01-01T00:00')
.should('have.attr', 'max', '2023-03-20T00:00')
})

it('Displays a time field with min and max values', () => {
cy
.get('#phormal-field-input-time')
.should('have.attr', 'min', '01:00')
.should('have.attr', 'max', '23:00')
})
})
53 changes: 53 additions & 0 deletions tests/pages/DateAndTimeTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {defineComponent, h} from "vue";
import {Phormal, useRequired} from "../../packages/core/src";
import CodeElement from "../components/code-element";

export default defineComponent({
name: "DateAndTimeTests",

data() {
return {
form: undefined as undefined|Phormal,

formFields: {
date: {
type: 'date',
label: 'Date',
min: '2023-01-01',
max: '2023-12-31',
},
datetimeLocal: {
type: 'datetime-local',
label: 'Datetime',
hooks: [useRequired()],
value: '2023-01-01T00:00',
min: '2023-01-01T00:00',
max: '2023-03-20T00:00',
},
time: {
type: 'time',
label: 'Time',
hooks: [useRequired()],
min: '01:00',
max: '23:00',
},
},

formValues: {} as Record<string, any>|undefined,
}
},

mounted() {
this.form = new Phormal(this.formFields, { el: '#phormal' })
},

render() {
const phormal = h('div', { id: 'phormal' })

return h("div",[
phormal,
h(CodeElement, { code: this.formValues || {} }),
h('button', { onClick: () => this.form!.$values(), id: 'getValues' }, 'Get values')
])
}
})
15 changes: 15 additions & 0 deletions tests/pages/Smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ export default defineComponent({
form: undefined as undefined|Phormal,

formFields: {
date: {
type: 'date',
label: 'Date',
value: '2020-01-01',
},
datetimeLocal: {
type: 'datetime-local',
label: 'Datetime',
value: '2020-01-01T12:00',
},
time: {
type: 'time',
label: 'Time',
value: '12:00',
},
delivery: {
type: 'radiogroup',
label: 'Delivery to',
Expand Down

0 comments on commit d2fed45

Please sign in to comment.