-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #344 from centrica-engineering/feature/form
- Loading branch information
Showing
12 changed files
with
870 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { Form } from './src/form-component.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import { html, MuonElement } from '@muonic/muon'; | ||
import scrollTo from '@muon/utils/scroll'; | ||
|
||
/** | ||
* A form. | ||
* | ||
* @element form | ||
*/ | ||
|
||
export class Form extends MuonElement { | ||
|
||
constructor() { | ||
super(); | ||
this._submit = this._submit.bind(this); | ||
this._reset = this._reset.bind(this); | ||
} | ||
|
||
connectedCallback() { | ||
super.connectedCallback(); | ||
|
||
queueMicrotask(() => { | ||
this.__checkForFormEl(); | ||
if (this._nativeForm) { | ||
this.__registerEvents(); | ||
// hack to stop browser validation pop up | ||
this._nativeForm.setAttribute('novalidate', true); | ||
// hack to force implicit submission (https://github.com/WICG/webcomponents/issues/187) | ||
const input = document.createElement('input'); | ||
input.type = 'submit'; | ||
input.hidden = true; | ||
this._nativeForm.appendChild(input); | ||
} | ||
}); | ||
} | ||
|
||
disconnectedCallback() { | ||
super.disconnectedCallback(); | ||
this.__teardownEvents(); | ||
} | ||
|
||
__registerEvents() { | ||
this._nativeForm?.addEventListener('submit', this._submit); | ||
this._submitButton?.addEventListener('click', this._submit); | ||
this._nativeForm?.addEventListener('reset', this._reset); | ||
} | ||
|
||
__teardownEvents() { | ||
this._nativeForm?.removeEventListener('submit', this._submit); | ||
this._submitButton?.removeEventListener('click', this._submit); | ||
this._nativeForm?.removeEventListener('reset', this._reset); | ||
} | ||
|
||
__checkForFormEl() { | ||
if (!this._nativeForm) { | ||
throw new Error( | ||
'No form node found. Did you put a <form> element inside?' | ||
); | ||
} | ||
} | ||
|
||
_reset() { | ||
this.__checkForFormEl(); | ||
|
||
if ( | ||
!this._resetButton.disabled || | ||
!this._resetButton.loading | ||
) { | ||
this._nativeForm.reset(); | ||
} | ||
} | ||
|
||
_submit(event) { | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
|
||
this.__checkForFormEl(); | ||
|
||
if ( | ||
!this._submitButton || | ||
this._submitButton.disabled || | ||
this._submitButton.loading | ||
) { | ||
return undefined; // should this be false? | ||
} | ||
|
||
const validity = this.validate(); | ||
|
||
if (validity.isValid) { | ||
this.dispatchEvent(new Event('submit', { cancelable: true })); | ||
} else { | ||
const invalidElements = validity.validationStates.filter((state) => { | ||
return !state.isValid; | ||
}); | ||
|
||
scrollTo({ element: invalidElements[0].formElement }); | ||
} | ||
|
||
return validity.isValid; | ||
} | ||
|
||
get _nativeForm() { | ||
return this.querySelector('form'); | ||
} | ||
|
||
get _submitButton() { | ||
return this.querySelector('button:not([hidden])[type="submit"]') || | ||
this.querySelector('input:not([hidden])[type="submit"]') || | ||
this.querySelector('*:not([hidden])[type="submit"]'); | ||
} | ||
|
||
get _resetButton() { | ||
return this.querySelector('button:not([hidden])[type="reset"]') || | ||
this.querySelector('input:not([hidden])[type="reset"]') || | ||
this.querySelector('*:not([hidden])[type="reset"]'); | ||
} | ||
|
||
_findInputElement(element) { | ||
if (element.parentElement._inputElement) { | ||
return element.parentElement; | ||
} | ||
// Due to any layout container elements - @TODO - need better logic | ||
if (element.parentElement.parentElement._inputElement) { | ||
return element.parentElement.parentElement; | ||
} | ||
|
||
return element; | ||
} | ||
|
||
validate() { | ||
let isValid = true; | ||
// @TODO: Check how this works with form associated | ||
const validationStates = Array.from(this._nativeForm.elements).reduce((acc, element) => { | ||
element = this._findInputElement(element); | ||
const { name } = element; | ||
const hasBeenSet = acc.filter((el) => el.name === name).length > 0; | ||
|
||
// For checkboxes and radio button - don't set multiple times (needs checking for native inputs) | ||
// Ignore buttons (including hidden reset) | ||
if ( | ||
hasBeenSet || | ||
element === this._submitButton || | ||
element === this._resetButton || | ||
element.type === 'submit' | ||
) { | ||
return acc; | ||
} | ||
|
||
if (element.reportValidity) { | ||
element.reportValidity(); | ||
} | ||
|
||
const { validity } = element; | ||
|
||
if (validity) { | ||
const { value } = element; | ||
const { valid, validationMessage } = validity; | ||
|
||
isValid = Boolean(isValid & validity.valid); | ||
|
||
acc.push({ | ||
name, | ||
value, | ||
isValid: valid, | ||
error: validationMessage, | ||
validity: validity, | ||
formElement: element | ||
}); | ||
} | ||
|
||
return acc; | ||
}, []); | ||
|
||
return { | ||
isValid, | ||
validationStates | ||
}; | ||
} | ||
|
||
get standardTemplate() { | ||
return html` | ||
<div class="form"> | ||
<slot></slot> | ||
</div> | ||
`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Form } from '@muonic/muon/components/form'; | ||
import setup from '@muonic/muon/storybook/stories'; | ||
|
||
const details = setup('form', Form); | ||
|
||
export default details.defaultValues; | ||
|
||
const innerDetail = () => ` | ||
<form> | ||
<muon-inputter helper="Useful information to help populate this field." validation='["isRequired"]' name="username"> | ||
<label slot="label">Name</label> | ||
<input type="text" placeholder="e.g. Placeholder" name="username"/> | ||
</muon-inputter> | ||
<muon-inputter value="" helper="How can we help you?" validation="["isRequired","isEmail"]" autocomplete="email"> | ||
<label slot="label">Email</label> | ||
<input type="email" placeholder="e.g. [email protected]" autocomplete="email" name="useremail"> | ||
<div slot="tip-details">By providing clarification on why this information is necessary.</div> | ||
</muon-inputter> | ||
<label for="user-id">User ID<label> | ||
<input type="text" id="user-id" name="user-id" required/> | ||
<muon-inputter heading="What options do you like?" helper="How can we help you?" validation='["isRequired"]' value="b"> | ||
<input type="checkbox" name="checkboxes" value="a" id="check-01"> | ||
<label for="check-01">Option A</label> | ||
<input type="checkbox" name="checkboxes" value="b" id="check-02"> | ||
<label for="check-02">Option B</label> | ||
<div slot="tip-details">By providing clarification on why this information is necessary.</div> | ||
</muon-inputter> | ||
<input type="reset" /> | ||
<muon-cta type="submit">Submit</muon-cta> | ||
<form>`; | ||
|
||
export const Standard = (args) => details.template(args, innerDetail); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { dedupeMixin } from '@muonic/muon'; | ||
|
||
/** | ||
* A mixin to associate the component to the enclosing native form. | ||
* | ||
* @mixin FormElementMixin | ||
*/ | ||
|
||
export const FormAssociateMixin = dedupeMixin((superClass) => | ||
class FormAssociateMixinClass extends superClass { | ||
|
||
static get properties() { | ||
return { | ||
_internals: { | ||
type: Object, | ||
state: true | ||
} | ||
}; | ||
} | ||
|
||
static get formAssociated() { | ||
return true; | ||
} | ||
|
||
constructor() { | ||
super(); | ||
this._internals = this.attachInternals(); | ||
} | ||
|
||
updated(changedProperties) { | ||
if (changedProperties.has('value')) { | ||
this._internals.setFormValue(this.value); | ||
} | ||
} | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.