chore(form-system): add form fundament demos
This commit is contained in:
parent
11ee5e99ec
commit
9d64cb36f5
5 changed files with 347 additions and 0 deletions
|
|
@ -35,6 +35,7 @@
|
||||||
"@lion/checkbox": "^0.1.51",
|
"@lion/checkbox": "^0.1.51",
|
||||||
"@lion/checkbox-group": "^0.1.57",
|
"@lion/checkbox-group": "^0.1.57",
|
||||||
"@lion/core": "^0.1.13",
|
"@lion/core": "^0.1.13",
|
||||||
|
"@lion/field": "^0.2.3",
|
||||||
"@lion/fieldset": "^0.1.50",
|
"@lion/fieldset": "^0.1.50",
|
||||||
"@lion/form": "^0.1.56",
|
"@lion/form": "^0.1.56",
|
||||||
"@lion/input": "^0.1.50",
|
"@lion/input": "^0.1.50",
|
||||||
|
|
@ -42,9 +43,11 @@
|
||||||
"@lion/input-date": "^0.1.51",
|
"@lion/input-date": "^0.1.51",
|
||||||
"@lion/input-email": "^0.1.50",
|
"@lion/input-email": "^0.1.50",
|
||||||
"@lion/input-iban": "^0.1.52",
|
"@lion/input-iban": "^0.1.52",
|
||||||
|
"@lion/localize": "^0.4.14",
|
||||||
"@lion/radio": "^0.1.51",
|
"@lion/radio": "^0.1.51",
|
||||||
"@lion/radio-group": "^0.1.57",
|
"@lion/radio-group": "^0.1.57",
|
||||||
"@lion/textarea": "^0.1.53",
|
"@lion/textarea": "^0.1.53",
|
||||||
|
"@lion/validate": "^0.2.29",
|
||||||
"@open-wc/demoing-storybook": "^0.2.0",
|
"@open-wc/demoing-storybook": "^0.2.0",
|
||||||
"@open-wc/testing": "^2.0.6"
|
"@open-wc/testing": "^2.0.6"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
131
packages/form-system/stories/formatting.stories.js
Normal file
131
packages/form-system/stories/formatting.stories.js
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
import { storiesOf, html } from '@open-wc/demoing-storybook';
|
||||||
|
import { Unparseable } from '@lion/validate';
|
||||||
|
import '@lion/input/lion-input.js';
|
||||||
|
import './helper-wc/h-output.js';
|
||||||
|
|
||||||
|
function newDateValid(d) {
|
||||||
|
const result = d ? new Date(d) : new Date();
|
||||||
|
return !isNaN(result.getTime()) ? result : null; // eslint-disable-line no-restricted-globals
|
||||||
|
}
|
||||||
|
|
||||||
|
storiesOf('Form Fundaments|Formatting and Parsing', module)
|
||||||
|
.add(
|
||||||
|
'model value',
|
||||||
|
() => html`
|
||||||
|
<p>
|
||||||
|
Note: we always use lion-input to demonstrate, but all things that extend lion-input have
|
||||||
|
this functionality!
|
||||||
|
</p>
|
||||||
|
<lion-input
|
||||||
|
help-text="Uses model value for data synchronisation"
|
||||||
|
.modelValue="${'myValue'}"
|
||||||
|
@model-value-changed="${({ target }) => {
|
||||||
|
console.log(target);
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
</lion-input>
|
||||||
|
<h-output .show="${['modelValue']}"></h-output>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
'parser',
|
||||||
|
() => html`
|
||||||
|
<lion-input
|
||||||
|
label="Number Example"
|
||||||
|
help-text="Uses .parser to create model values from view values"
|
||||||
|
.parser="${viewValue => Number(viewValue)}"
|
||||||
|
.modelValue="${1234567890}"
|
||||||
|
>
|
||||||
|
</lion-input>
|
||||||
|
<h-output .show="${['modelValue']}"></h-output>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
'formatter',
|
||||||
|
() => html`
|
||||||
|
<lion-input
|
||||||
|
label="Number Example"
|
||||||
|
help-text="Uses .formatter to create view value"
|
||||||
|
.parser="${viewValue => Number(viewValue)}"
|
||||||
|
.formatter="${modelValue => new Intl.NumberFormat('en-GB').format(modelValue)}"
|
||||||
|
.modelValue="${1234567890}"
|
||||||
|
>
|
||||||
|
</lion-input>
|
||||||
|
<h-output .show="${['modelValue']}"></h-output>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
/* .add(
|
||||||
|
'preprocessor',
|
||||||
|
() => html`
|
||||||
|
<lion-input
|
||||||
|
label="Date Example"
|
||||||
|
help-text="Uses .preprocessor to enhance user experience"
|
||||||
|
.parser="${viewValue => newDateValid(viewValue) || undefined}"
|
||||||
|
.formatter="${modelValue => new Intl.DateTimeFormat('en-GB').format(modelValue)}"
|
||||||
|
.preprocessor="${viewValue => viewValue.replace('/', '-')}"
|
||||||
|
.modelValue="${new Date()}"
|
||||||
|
>
|
||||||
|
</lion-input>
|
||||||
|
<h-output .show="${['modelValue']}"></h-output>
|
||||||
|
`,
|
||||||
|
) */
|
||||||
|
.add(
|
||||||
|
'(de)serializer',
|
||||||
|
() => html`
|
||||||
|
<lion-input
|
||||||
|
label="Date Example"
|
||||||
|
help-text="Uses .(de)serializer to restore serialized modelValues"
|
||||||
|
.parser="${viewValue => newDateValid(viewValue) || undefined}"
|
||||||
|
.formatter="${modelValue => new Intl.DateTimeFormat('en-GB').format(modelValue)}"
|
||||||
|
.serializer="${modelValue => modelValue.toISOString().slice(0, 10)}"
|
||||||
|
.deserializer="${serializedMv => newDateValid(serializedMv) || undefined}"
|
||||||
|
.modelValue="${new Date(Date.UTC(2012, 11, 20, 3, 0, 0))}"
|
||||||
|
>
|
||||||
|
</lion-input>
|
||||||
|
<h-output .show="${['modelValue']}"></h-output>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
'Unparseable',
|
||||||
|
() => html`
|
||||||
|
<div
|
||||||
|
@model-value-changed="${({ target: { modelValue, errorState } }) => {
|
||||||
|
if (modelValue instanceof Unparseable) {
|
||||||
|
console.log(`End user attempted to create a valid entry and most likely is in
|
||||||
|
the process of doing so. We can retrieve the intermediate state via modelValue.viewValue`);
|
||||||
|
} else if (errorState) {
|
||||||
|
console.log(`We know now end user entered a valid type, but some constraints
|
||||||
|
(for instance min date) were not met`);
|
||||||
|
} else {
|
||||||
|
console.log(`Now we know end user entered a valid input: the field is valid
|
||||||
|
and modelValue can be used in Application context for further processing`);
|
||||||
|
}
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
<lion-input
|
||||||
|
label="Date Example"
|
||||||
|
help-text="Creates modelValue of 'Unparseable' when modelValue can't be created. Please check the action logger for additional details."
|
||||||
|
.parser="${viewValue => newDateValid(viewValue) || undefined}"
|
||||||
|
.formatter="${modelValue => new Intl.DateTimeFormat('en-GB').format(modelValue)}"
|
||||||
|
.deserializer="${serializedMv => newDateValid(serializedMv) || undefined}"
|
||||||
|
.modelValue="${new Unparseable('2000/12f')}"
|
||||||
|
>
|
||||||
|
</lion-input>
|
||||||
|
<h-output .show="${['modelValue']}"></h-output>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
'Unparseable restore',
|
||||||
|
() => html`
|
||||||
|
<lion-input
|
||||||
|
label="Date Example"
|
||||||
|
help-text="Restored 'Unparseable' state"
|
||||||
|
.parser="${viewValue => new Date(viewValue)}"
|
||||||
|
.formatter="${modelValue => new Intl.DateTimeFormat('en-GB').format(modelValue)}"
|
||||||
|
.modelValue="${new Unparseable('2000/12f')}"
|
||||||
|
>
|
||||||
|
</lion-input>
|
||||||
|
<h-output .show="${['modelValue']}"></h-output>
|
||||||
|
`,
|
||||||
|
);
|
||||||
105
packages/form-system/stories/helper-wc/h-output.js
Normal file
105
packages/form-system/stories/helper-wc/h-output.js
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
import { LitElement, html, css } from '@lion/core';
|
||||||
|
import { LionField } from '@lion/field';
|
||||||
|
|
||||||
|
export class HelperOutput extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
field: Object,
|
||||||
|
show: Array,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return [
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table,
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated(c) {
|
||||||
|
super.firstUpdated(c);
|
||||||
|
if (!this.field) {
|
||||||
|
// Fuzzy logic, but... practical
|
||||||
|
const prev = this.previousElementSibling;
|
||||||
|
if (prev instanceof LionField) {
|
||||||
|
this.field = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.__rerender = this.__rerender.bind(this);
|
||||||
|
this.field.addEventListener('model-value-changed', this.__rerender);
|
||||||
|
this.field.addEventListener('mousemove', this.__rerender);
|
||||||
|
this.field.addEventListener('blur', this.__rerender);
|
||||||
|
this.field.addEventListener('focusin', this.__rerender);
|
||||||
|
this.field.addEventListener('focusout', this.__rerender);
|
||||||
|
|
||||||
|
if (this.field.inputElement.form) {
|
||||||
|
this.field.inputElement.form.addEventListener('submit', this.__rerender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__rerender() {
|
||||||
|
setTimeout(() => {
|
||||||
|
const f = this.field;
|
||||||
|
this.field = null;
|
||||||
|
this.field = f;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
__renderProp(p) {
|
||||||
|
if (typeof p === 'boolean') {
|
||||||
|
return p === true ? '✓' : '';
|
||||||
|
}
|
||||||
|
if (typeof p === 'undefined') {
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const field = this.field || {};
|
||||||
|
return html`
|
||||||
|
<table>
|
||||||
|
<caption>
|
||||||
|
Interaction States
|
||||||
|
</caption>
|
||||||
|
<tr>
|
||||||
|
${this.show.map(
|
||||||
|
prop => html`
|
||||||
|
<th>${prop}</th>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
<tr></tr>
|
||||||
|
<tr>
|
||||||
|
${this.show.map(
|
||||||
|
prop => html`
|
||||||
|
<td>${this.__renderProp(field[prop])}</td>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('h-output', HelperOutput);
|
||||||
105
packages/form-system/stories/interactionStates.stories.js
Normal file
105
packages/form-system/stories/interactionStates.stories.js
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
import { storiesOf, html } from '@open-wc/demoing-storybook';
|
||||||
|
import { render } from '@lion/core';
|
||||||
|
import { localize } from '@lion/localize';
|
||||||
|
import '@lion/checkbox/lion-checkbox.js';
|
||||||
|
import '@lion/checkbox-group/lion-checkbox-group.js';
|
||||||
|
import '@lion/form/lion-form.js';
|
||||||
|
import '@lion/input/lion-input.js';
|
||||||
|
import './helper-wc/h-output.js';
|
||||||
|
|
||||||
|
function renderOffline(litHtmlTemplate) {
|
||||||
|
const offlineRenderContainer = document.createElement('div');
|
||||||
|
render(litHtmlTemplate, offlineRenderContainer);
|
||||||
|
return offlineRenderContainer.firstElementChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTranslations(ns, data) {
|
||||||
|
if (!localize._isNamespaceInCache('en-GB', ns)) {
|
||||||
|
localize.addData('en-GB', ns, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storiesOf('Form Fundaments|Interaction States', module)
|
||||||
|
.add(
|
||||||
|
'States',
|
||||||
|
() => html`
|
||||||
|
<lion-input
|
||||||
|
help-text="Interact with this field to see how dirty, touched and prefilled change"
|
||||||
|
.modelValue="${'myValue'}"
|
||||||
|
>
|
||||||
|
<input slot="input" />
|
||||||
|
</lion-input>
|
||||||
|
|
||||||
|
<h-output .show="${['touched', 'dirty', 'prefilled', 'focused', 'filled', 'submitted']}">
|
||||||
|
</h-output>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.add('Feedback condition', () => {
|
||||||
|
// 1. Initialize variables...
|
||||||
|
// properties on InteractionStateMixin we want to check conditions for
|
||||||
|
const props = ['touched', 'dirty', 'prefilled', 'focused', 'filled', 'submitted'];
|
||||||
|
|
||||||
|
// Here we will store the conditions (trigger for validation feedback)
|
||||||
|
// provided via the UI of the demo
|
||||||
|
let conditions = [];
|
||||||
|
|
||||||
|
// 2. Create a validator...
|
||||||
|
// Define a demo validator that should only be visible on an odd amount of characters
|
||||||
|
const oddValidator = [modelValue => ({ odd: modelValue.length % 2 !== 0 })];
|
||||||
|
|
||||||
|
addTranslations('lion-validate+odd', {
|
||||||
|
error: {
|
||||||
|
odd: '[ Error feedback ] : Add or remove one character',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Create field overriding .showErrorCondition...
|
||||||
|
// Here we will store a reference to the Field element that overrides the default condition
|
||||||
|
// (function `showErrorCondition`) for triggering validation feedback of `.errorValidators`
|
||||||
|
const fieldElement = renderOffline(html`
|
||||||
|
<lion-input
|
||||||
|
name="interactionField"
|
||||||
|
label="Only an odd amount of characters allowed"
|
||||||
|
help-text="Change feedback condition"
|
||||||
|
.modelValue="${'notodd'}"
|
||||||
|
.errorValidators="${[oddValidator]}"
|
||||||
|
.showErrorCondition="${newStates =>
|
||||||
|
newStates.error && conditions.every(p => fieldElement[p])}"
|
||||||
|
>
|
||||||
|
<input slot="input" />
|
||||||
|
</lion-input>
|
||||||
|
`);
|
||||||
|
|
||||||
|
function fetchConditionsAndReevaluate({ currentTarget: { modelValue } }) {
|
||||||
|
if (!modelValue['props[]']) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Create props list like: ['touched', 'submitted']
|
||||||
|
conditions = modelValue['props[]'].filter(p => p.checked).map(p => p.value);
|
||||||
|
// Reevaluate
|
||||||
|
fieldElement.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<lion-form>
|
||||||
|
<form>
|
||||||
|
${fieldElement}
|
||||||
|
<button>Submit</button>
|
||||||
|
</form>
|
||||||
|
</lion-form>
|
||||||
|
|
||||||
|
<h-output .field="${fieldElement}" .show="${[...props, 'errorState']}"> </h-output>
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
Set conditions for validation feedback visibility
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<lion-checkbox-group name="props" @model-value-changed="${fetchConditionsAndReevaluate}">
|
||||||
|
${props.map(
|
||||||
|
p => html`
|
||||||
|
<lion-checkbox name="props[]" .label="${p}" .choiceValue="${p}"> </lion-checkbox>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</lion-checkbox-group>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
@ -14,7 +14,10 @@ import '../packages/fieldset/stories/index.stories.js';
|
||||||
import '../packages/checkbox-group/stories/index.stories.js';
|
import '../packages/checkbox-group/stories/index.stories.js';
|
||||||
import '../packages/radio-group/stories/index.stories.js';
|
import '../packages/radio-group/stories/index.stories.js';
|
||||||
import '../packages/form/stories/index.stories.js';
|
import '../packages/form/stories/index.stories.js';
|
||||||
|
|
||||||
import '../packages/form-system/stories/index.stories.js';
|
import '../packages/form-system/stories/index.stories.js';
|
||||||
|
import '../packages/form-system/stories/formatting.stories.js';
|
||||||
|
import '../packages/form-system/stories/interactionStates.stories.js';
|
||||||
|
|
||||||
import '../packages/icon/stories/index.stories.js';
|
import '../packages/icon/stories/index.stories.js';
|
||||||
import '../packages/ajax/stories/index.stories.js';
|
import '../packages/ajax/stories/index.stories.js';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue