feat: improved storybook demos

Co-authored-by: Joren Broekema <Joren.Broekema@ing.com>
Co-authored-by: Alex Ghiu <Alex.Ghiu@ing.com>
Co-authored-by: Thijs Louisse <Thijs.Louisse@ing.com>
This commit is contained in:
Thomas Allmer 2020-01-13 10:46:31 +01:00 committed by Thomas Allmer
parent d13672a226
commit 89b835a799
184 changed files with 10944 additions and 6569 deletions

View file

@ -11,7 +11,7 @@ trim_trailing_whitespace = true
[*.md]
indent_size = unset
[*.{html,js,md}]
[*.{html,js,md,mdx}]
block_comment_start = /**
block_comment = *
block_comment_end = */

View file

@ -0,0 +1,3 @@
module.exports = {
stories: './packages/**/stories/*.stories.{js,mdx}',
};

View file

@ -4,6 +4,7 @@ import {
setCustomElements,
withA11y,
} from '@open-wc/demoing-storybook';
import { sortEachDepth } from '../packages/helpers/index.js';
async function run() {
// const customElements = await (
@ -24,6 +25,14 @@ async function run() {
docs: {
iframeHeight: '200px',
},
options: {
showRoots: true,
storySort: sortEachDepth([
['Intro', 'Forms', 'Buttons', 'Overlays', 'Navigation', 'Localize', 'Icons', '...'],
['Intro', 'Features Overview', '...', 'System'],
['Overview', '...', '_internals'],
]),
},
});
}

View file

@ -0,0 +1,6 @@
module.exports = {
nodeResolve: true,
watch: true,
open: true,
stories: './packages/**/stories/*.stories.{js,mdx}',
};

View file

@ -30,38 +30,42 @@ npm i @lion/<package-name>
The accessibility column indicates whether the functionality is accessible in its core. Aspects like styling and content determine actual accessibility in usage.
| Package | Version | Description | Accessibility |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | -------------------------------------------------------------------- |
| [core](./packages/core) | [![core](https://img.shields.io/npm/v/@lion/core.svg)](https://www.npmjs.com/package/@lion/core) | Core System (exports LitElement, lit-html) | n/a |
| [localize](./packages/localize) | [![localize](https://img.shields.io/npm/v/@lion/localize.svg)](https://www.npmjs.com/package/@lion/localize) | Localize and translate your application/components | n/a |
| [ajax](./packages/ajax) | [![ajax](https://img.shields.io/npm/v/@lion/ajax.svg)](https://www.npmjs.com/package/@lion/ajax) | Fetching data via ajax request | n/a |
| [button](./packages/button) | [![button](https://img.shields.io/npm/v/@lion/button.svg)](https://www.npmjs.com/package/@lion/button) | Button | [#64][i64] |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | -------------------------- |
| **-- Buttons --** | | | |
| [button](./packages/button) | [![button](https://img.shields.io/npm/v/@lion/button.svg)](https://www.npmjs.com/package/@lion/button) | Button | ✔️ |
| [switch](./packages/switch) | [![switch](https://img.shields.io/npm/v/@lion/switch.svg)](https://www.npmjs.com/package/@lion/switch) | Switch | ✔️ |
| [calendar](./packages/calendar) | [![calendar](https://img.shields.io/npm/v/@lion/calendar.svg)](https://www.npmjs.com/package/@lion/calendar) | Standalone calendar | [#195][i195], [#194][i194], [#193][i193], [#191][i191] |
| [icon](./packages/icon) | [![icon](https://img.shields.io/npm/v/@lion/icon.svg)](https://www.npmjs.com/package/@lion/icon) | Display our svg icons | [#173][i173], [#172][i172] |
| [steps](./packages/steps) | [![steps](https://img.shields.io/npm/v/@lion/steps.svg)](https://www.npmjs.com/package/@lion/steps) | Multi Step System | n/a |
| [tabs](./packages/tabs) | [![tBS](https://img.shields.io/npm/v/@lion/tabs.svg)](https://www.npmjs.com/package/@lion/tabs) | Move between a small number of equally important views | n/a |
| **-- Forms --** | | | |
| [form](./packages/form) | [![form](https://img.shields.io/npm/v/@lion/form.svg)](https://www.npmjs.com/package/@lion/form) | Wrapper for multiple form elements | ✔️ |
| [field](./packages/field) | [![field](https://img.shields.io/npm/v/@lion/field.svg)](https://www.npmjs.com/package/@lion/field) | Base Class for all inputs | [#190][i190] |
| [field](./packages/field) | [![field](https://img.shields.io/npm/v/@lion/field.svg)](https://www.npmjs.com/package/@lion/field) | Base Class for all inputs | ✔️ |
| [fieldset](./packages/fieldset) | [![fieldset](https://img.shields.io/npm/v/@lion/fieldset.svg)](https://www.npmjs.com/package/@lion/fieldset) | Group for form inputs | ✔️ |
| [validate](./packages/validate) | [![validate](https://img.shields.io/npm/v/@lion/validate.svg)](https://www.npmjs.com/package/@lion/validate) | Validation for form components | n/a |
| [checkbox](./packages/checkbox) | [![checkbox](https://img.shields.io/npm/v/@lion/checkbox.svg)](https://www.npmjs.com/package/@lion/checkbox) | Checkbox form element | ✔️ |
| [checkbox-group](./packages/checkbox-group) | [![checkbox-group](https://img.shields.io/npm/v/@lion/checkbox-group.svg)](https://www.npmjs.com/package/@lion/checkbox-group) | Group of checkboxes | ✔️ |
| [input](./packages/input) | [![input](https://img.shields.io/npm/v/@lion/input.svg)](https://www.npmjs.com/package/@lion/input) | Input element for strings | ✔️ |
| [input-amount](./packages/input-amount) | [![input-amount](https://img.shields.io/npm/v/@lion/input-amount.svg)](https://www.npmjs.com/package/@lion/input-amount) | Input element for amounts | [#166][i166] |
| [input-amount](./packages/input-amount) | [![input-amount](https://img.shields.io/npm/v/@lion/input-amount.svg)](https://www.npmjs.com/package/@lion/input-amount) | Input element for amounts | ✔️ |
| [input-date](./packages/input-date) | [![input-date](https://img.shields.io/npm/v/@lion/input-date.svg)](https://www.npmjs.com/package/@lion/input-date) | Input element for dates | ✔️ |
| [input-datepicker](./packages/input-datepicker) | [![input-datepicker](https://img.shields.io/npm/v/@lion/input-datepicker.svg)](https://www.npmjs.com/package/@lion/input-datepicker) | Input element for dates with a datepicker | ✔️ |
| [input-email](./packages/input-email) | [![input-email](https://img.shields.io/npm/v/@lion/input-email.svg)](https://www.npmjs.com/package/@lion/input-email) | Input element for e-mails | [#169][i169] |
| [input-iban](./packages/input-iban) | [![input-iban](https://img.shields.io/npm/v/@lion/input-iban.svg)](https://www.npmjs.com/package/@lion/input-iban) | Input element for IBANs | [#169][i169] |
| [input-email](./packages/input-email) | [![input-email](https://img.shields.io/npm/v/@lion/input-email.svg)](https://www.npmjs.com/package/@lion/input-email) | Input element for e-mails | ✔️ |
| [input-iban](./packages/input-iban) | [![input-iban](https://img.shields.io/npm/v/@lion/input-iban.svg)](https://www.npmjs.com/package/@lion/input-iban) | Input element for IBANs | ✔️ |
| [input-range](./packages/input-range) | [![input-range](https://img.shields.io/npm/v/@lion/input-range.svg)](https://www.npmjs.com/package/@lion/input-range) | Input element for a range of values | ✔️ |
| [radio](./packages/radio) | [![radio](https://img.shields.io/npm/v/@lion/radio.svg)](https://www.npmjs.com/package/@lion/radio) | Radio from element | ✔️ |
| [radio-group](./packages/radio-group) | [![radio-group](https://img.shields.io/npm/v/@lion/radio-group.svg)](https://www.npmjs.com/package/@lion/radio-group) | Group of radios | ✔️ |
| [select](./packages/select) | [![select](https://img.shields.io/npm/v/@lion/select.svg)](https://www.npmjs.com/package/@lion/select) | Simple native dropdown element | ✔️ |
| [textarea](./packages/textarea) | [![textarea](https://img.shields.io/npm/v/@lion/textarea.svg)](https://www.npmjs.com/package/@lion/textarea) | Multiline text input | [#165][i165] |
| [textarea](./packages/textarea) | [![textarea](https://img.shields.io/npm/v/@lion/textarea.svg)](https://www.npmjs.com/package/@lion/textarea) | Multiline text input | ✔️ |
| **-- Overlays --** | | | |
| [overlays](./packages/overlays) | [![overlays](https://img.shields.io/npm/v/@lion/overlays.svg)](https://www.npmjs.com/package/@lion/overlays) | Overlay System | ✔️ |
| [dialog](./packages/dialog) | [![dialog](https://img.shields.io/npm/v/@lion/dialog.svg)](https://www.npmjs.com/package/@lion/dialog) | Dialog element | ✔️ |
| [tooltip](./packages/tooltip) | [![tooltip](https://img.shields.io/npm/v/@lion/tooltip.svg)](https://www.npmjs.com/package/@lion/tooltip) | Tooltip element | [#178][i178], [#177][i177], [#176][i176], [#175][i175], [#174][i174] |
| [tooltip](./packages/tooltip) | [![tooltip](https://img.shields.io/npm/v/@lion/tooltip.svg)](https://www.npmjs.com/package/@lion/tooltip) | Tooltip element | [#175][i175] |
| **-- Icons --** | | | |
| [icon](./packages/icon) | [![icon](https://img.shields.io/npm/v/@lion/icon.svg)](https://www.npmjs.com/package/@lion/icon) | Display our svg icons | [#173][i173], [#172][i172] |
| **-- Navigation --** | | | |
| [steps](./packages/steps) | [![steps](https://img.shields.io/npm/v/@lion/steps.svg)](https://www.npmjs.com/package/@lion/steps) | Multi Step System | n/a |
| [tabs](./packages/tabs) | [![tBS](https://img.shields.io/npm/v/@lion/tabs.svg)](https://www.npmjs.com/package/@lion/tabs) | Move between a small number of equally important views | n/a |
| **-- Others --** | | | |
| [core](./packages/core) | [![core](https://img.shields.io/npm/v/@lion/core.svg)](https://www.npmjs.com/package/@lion/core) | Core System (exports LitElement, lit-html) | n/a |
| [calendar](./packages/calendar) | [![calendar](https://img.shields.io/npm/v/@lion/calendar.svg)](https://www.npmjs.com/package/@lion/calendar) | Standalone calendar | [#195][i195], [#194][i194] |
| [localize](./packages/localize) | [![localize](https://img.shields.io/npm/v/@lion/localize.svg)](https://www.npmjs.com/package/@lion/localize) | Localize and translate your application/components | n/a |
| [ajax](./packages/ajax) | [![ajax](https://img.shields.io/npm/v/@lion/ajax.svg)](https://www.npmjs.com/package/@lion/ajax) | Fetching data via ajax request | n/a |
## How to use
@ -110,7 +114,6 @@ Lion Web Components aims to be future proof and use well-supported proven techno
- [npm](http://npmjs.com)
- [yarn](https://yarnpkg.com)
- [open-wc](https://open-wc.org)
- [webpack](https://webpack.js.org)
- [Karma](https://karma-runner.github.io)
- [Mocha](https://mochajs.org)
- [Chai](https://www.chaijs.com)
@ -150,19 +153,8 @@ You can join the Polymer slack by visiting [Polymer Slack Invite](https://join.s
As stated above "support and issues time" is currently rather limited: feel free to open a discussion.
However, we can not guarantee any response times.
[i64]: https://github.com/ing-bank/lion/issues/64
[i165]: https://github.com/ing-bank/lion/issues/165
[i166]: https://github.com/ing-bank/lion/issues/166
[i169]: https://github.com/ing-bank/lion/issues/169
[i172]: https://github.com/ing-bank/lion/issues/172
[i173]: https://github.com/ing-bank/lion/issues/173
[i174]: https://github.com/ing-bank/lion/issues/174
[i175]: https://github.com/ing-bank/lion/issues/175
[i176]: https://github.com/ing-bank/lion/issues/176
[i177]: https://github.com/ing-bank/lion/issues/177
[i178]: https://github.com/ing-bank/lion/issues/178
[i190]: https://github.com/ing-bank/lion/issues/190
[i191]: https://github.com/ing-bank/lion/issues/191
[i193]: https://github.com/ing-bank/lion/issues/193
[i194]: https://github.com/ing-bank/lion/issues/194
[i195]: https://github.com/ing-bank/lion/issues/195

View file

@ -1 +0,0 @@
root = true

View file

@ -1,5 +0,0 @@
{
"file-name": "max.json",
"name": "Max",
"age": "30"
}

View file

@ -1,5 +0,0 @@
{
"file-name": "peter.json",
"name": "Peter",
"age": "20"
}

View file

@ -8,7 +8,7 @@
"devDependencies": {
"@commitlint/cli": "^7.0.0",
"@commitlint/config-conventional": "^7.0.0",
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/eslint-config": "^1.0.0",
"@open-wc/prettier-config": "^0.1.0",
"@open-wc/testing": "^2.3.4",
@ -35,8 +35,8 @@
},
"scripts": {
"start": "npm run storybook",
"storybook": "start-storybook -p 9001 --stories \"./packages/*/stories/*.stories.{js,mdx}\" --node-resolve --watch --open",
"storybook:build": "build-storybook --stories \"./packages/*/stories/*.stories.{js,mdx}\" --output-dir ./storybook-static",
"storybook": "start-storybook -p 9001",
"storybook:build": "build-storybook",
"test": "karma start --coverage",
"test:watch": "karma start --auto-watch=true --single-run=false",
"test:compatibility": "karma start --compatibility all --coverage",

View file

@ -5,11 +5,9 @@
`ajax` is the global manager for handling all ajax requests.
It is a promise based system for fetching data, based on [axios](https://github.com/axios/axios)
## Features
## Live Demo/Documentation
- only JS functions, no (unnecessarily expensive) web components
- supports GET, POST, PUT, DELETE, REQUEST, PATCH and HEAD methods
- can be used with or without XSRF token
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/fetch-system-ajax) for a live demo and documentation
## How to use
@ -24,66 +22,5 @@ npm i --save @lion/ajax
```js
import { ajax } from '@lion/ajax';
ajax
.get('data.json')
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
ajax.get('data.json').then(response => console.log(response));
```
### Create own instances for custom options
#### Cancel
```js
import { AjaxClass } from '@lion/ajax';
const myAjax = AjaxClass.getNewInstance({ cancelable: true });
myAjax
.get('data.json')
.then(response => {
document.querySelector('#canceled').innerHTML = JSON.stringify(response.data);
})
.catch(error => {
document.querySelector('#canceled').innerHTML = `I got cancelled: ${error.message}`;
});
setTimeout(() => {
myAjax.cancel('too slow');
}, 1);
```
#### Cancel previous on new request
```js
import { AjaxClass } from '@lion/ajax';
const myAjax = AjaxClass.getNewInstance({ cancelPreviousOnNewRequest: true });
myAjax
.get('data.json')
.then(response => {
document.querySelector('#request1').innerHTML = 'Request 1: ' + JSON.stringify(response.data);
})
.catch(error => {
document.querySelector('#request1').innerHTML = `Request 1: I got cancelled: ${error.message}`;
});
myAjax
.get('data2.json')
.then(response => {
document.querySelector('#request2').innerHTML = 'Request 2: ' + JSON.stringify(response.data);
})
.catch(error => {
document.querySelector('#request2').innerHTML = `Request 2: I got cancelled: ${error.message}`;
});
```
## Considerations
> Due to a [bug in axios](https://github.com/axios/axios/issues/385) options may leak in to other instances. So please avoid setting global options in axios. Interceptors have no issues.
## Future plans
- Endplan is to remove axios and replace it with fetch
- This wrapper exist so that this switch should not mean any breaking changes for our users

View file

@ -36,7 +36,7 @@
"@lion/core": "0.3.0"
},
"devDependencies": {
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4",
"sinon": "^7.2.2"
}

View file

@ -0,0 +1,16 @@
{
"animals": {
"cow": {
"type": "mammal",
"limbs": 4
},
"frog": {
"type": "amphibian",
"limbs": 4
},
"snake": {
"type": "reptile",
"limbs": 0
}
}
}

View file

@ -1,77 +0,0 @@
import { html, storiesOf } from '@open-wc/demoing-storybook';
import { ajax, AjaxClass } from '../index.js';
storiesOf('Ajax system|Ajax')
.addParameters({ options: { selectedPanel: 'storybook/actions/actions-panel' } })
.add(
'Get',
() => html`
<button
@click=${() => {
ajax
.get('./dummy-jsons/peter.json')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
}}
>
Log Get Request to Action Logger
</button>
`,
)
.add(
'Cancelable',
() => html`
<button
@click=${() => {
const myAjax = AjaxClass.getNewInstance({ cancelable: true });
requestAnimationFrame(() => {
myAjax.cancel('too slow');
});
myAjax
.get('./dummy-jsons/peter.json')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
}}
>
Execute Request to Action Logger
</button>
`,
)
.add(
'CancelPreviousOnNewRequest',
() => html`
<button
@click=${() => {
const myAjax = AjaxClass.getNewInstance({ cancelPreviousOnNewRequest: true });
myAjax
.get('./dummy-jsons/peter.json')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error.message);
});
myAjax
.get('./dummy-jsons/max.json')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error.message);
});
}}
>
Execute 2 Request to Action Logger
</button>
`,
);

View file

@ -0,0 +1,166 @@
import {
Story,
Meta,
html,
} from '@open-wc/demoing-storybook';
import { ajax } from '../src/ajax.js';
import { AjaxClass } from '../src/AjaxClass.js';
<Meta title="Others/Fetch/Ajax" parameters={{ component: 'lion-ajax' }} />
# Ajax
`lion-ajax` is the global manager for handling all ajax requests.
It is a promise based system for fetching data, based on <a href="https://github.com/axios/axios" target="_blank">
axios</a>
<Story name="Default">
{html`
<button
@click=${() => {
ajax
.get('./packages/ajax/stories/data.json')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
}}
>
Execute Request to Action Logger
</button>
`}
</Story>
```js
ajax.get('./packages/ajax/stories/data.json').then(response => console.log(response.data));
```
## Features
- only JS functions, no (unnecessarily expensive) web components
- supports GET, POST, PUT, DELETE, REQUEST, PATCH and HEAD methods
- can be used with or without XSRF token
## How to use
### Installation
```sh
npm i --save @lion/ajax
```
```js
import { ajax, AjaxClass } from '@lion/ajax';
```
## Cancelable Request
It is possible to make an Ajax request cancelable, and then call `cancel()` to make the request provide a custom error once fired.
<Story name="Cancelable">
{html`
<button
@click=${() => {
const myAjax = AjaxClass.getNewInstance({ cancelable: true });
requestAnimationFrame(() => {
myAjax.cancel('too slow');
});
myAjax
.get('./packages/ajax/stories/data.json')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
}}
>
Execute Request to Action Logger
</button>
`}
</Story>
```js
const myAjax = AjaxClass.getNewInstance({ cancelable: true });
requestAnimationFrame(() => {
myAjax.cancel('too slow');
});
myAjax
.get('./packages/ajax/stories/data.json')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
```
## Cancel concurrent requests
You can cancel concurrent requests with the `cancelPreviousOnNewRequest` option.
<Story name="Cancel Previous on New Request">
{html`
<button
@click=${() => {
const myAjax = AjaxClass.getNewInstance({ cancelPreviousOnNewRequest: true });
myAjax
.get('./packages/ajax/stories/data.json')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error.message);
});
myAjax
.get('./packages/ajax/stories/data.json')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error.message);
});
}}
>
Execute Both Requests to Action Logger
</button>
`}
</Story>
```js
const myAjax = AjaxClass.getNewInstance({ cancelPreviousOnNewRequest: true });
myAjax
.get('./packages/ajax/stories/data.json')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error.message);
});
myAjax
.get('./packages/ajax/stories/data.json')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error.message);
});
```
## Considerations
Due to a <a href="https://github.com/axios/axios/issues/385" target="_blank"> bug in axios</a> options may leak in to other instances.
So please avoid setting global options in axios. Interceptors have no issues.
## Future plans
- Eventually we want to remove axios and replace it with <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" target="_blank">
Fetch</a>
- This wrapper exist to prevent this switch from causing breaking changes for our users

View file

@ -2,13 +2,11 @@
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
`lion-button` provides a component that is easily stylable and is accessible in all contexts.
`lion-button` provides a button component that is easily stylable and is accessible.
## Features
## Live Demo/Documentation
### Disabled
You can also set a button as disabled with the `disabled` property.
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/buttons-button) for a live demo and API documentation
## How to use
@ -27,39 +25,3 @@ import '@lion/button/lion-button.js';
```html
<lion-button>Button Text</lion-button>
```
## Considerations
### Why a Web Component?
There are multiple reasons why we used a Web Component as opposed to a CSS component.
- **Target size**: The minimum target size is 40 pixels, which makes even the small buttons easy to activate. A container element was needed to make this size possible.
- **Accessibility**: Our button is accessible because it uses the native button element. Having this native button element available in the light dom, preserves all platform accessibility features, like having it recognized by a native form.
- **Advanced styling**: There are advanced styling options regarding icons in buttons, where it is a lot more maintainable to handle icons in our button using slots. An example is that a sticky icon-only buttons may looks different from buttons which have both icons and text.
### Event target
We want to ensure that the event target returned to the user is `lion-button`, not `button`. Therefore, simply delegating the click to the native button immediately, is not desired. Instead, we catch the click event in the `lion-button`, and ensure delegation inside of there.
### Flashing a native button click as a direct child of form
By delegating the `click()` to the native button, it will bubble back up to `lion-button` which would cause duplicate actions. We have to simulate the full `.click()` however, otherwise form submission is not triggered. So this bubbling cannot be prevented.
Therefore, on click, we flash a `<button>` to the form as a direct child and fire the click on that button. We then immediately remove that button. This is a fully synchronous process; users or developers will not notice this, it should not cause problems.
### Native button & implicit form submission
Flashing the button in the way we do solves almost all issues except for one.
One of the specs of W3C is that when you have a form with multiple inputs, pressing enter while inside one of the inputs only triggers a form submit if that form has a button of type submit.
To get this particular implicit form submission to work, having a native button in our `lion-button` is a hard requirement. Therefore, not only do we flash a native button on the form to delegate `lion-button` trigger to `button` and thereby trigger form submission, we **also** add a native `button` inside the `lion-button` which `type` property is synchronized with the type of the `lion-button`.
### Preventing full page reloads
To prevent form submission full page reloads, add a **submit handler on the form** like so:
```html
<form @submit=${ev => ev.preventDefault()} >
```
Putting this on the `@click` of the `lion-button` is not enough.

View file

@ -35,10 +35,7 @@
"@lion/core": "0.3.0"
},
"devDependencies": {
"@lion/form": "0.3.14",
"@lion/icon": "0.2.10",
"@lion/input": "0.4.2",
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4",
"@polymer/iron-test-helpers": "^3.0.1",
"sinon": "^7.2.2"

View file

@ -0,0 +1,2 @@
export default tag =>
tag`<svg focusable="false" style="width: 24px; height: 24px;" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><g><path d="M44.8,44.2c-0.3-0.1-6.9-2.3-10-8c-0.9-1.7-1.7-3.6-2.4-5.4c-1.4-3.4-2.7-6.6-4.6-6.8l0.1-1.9c3.1,0.2,4.5,3.8,6.2,7.9 c0.7,1.7,1.4,3.5,2.3,5.2c2.7,5.1,8.9,7.1,8.9,7.1L44.8,44.2z"/></g><g><path d="M19.8,61.7v-1.9c4.7,0,10.7-6.6,13.4-10.7c2.6-4,11.2-2.1,12.2-1.9l-0.4,1.8c-2.3-0.5-8.6-1.4-10.2,1.1 C32.7,53.4,25.9,61.7,19.8,61.7z"/></g><g><path d="M25.1,86.5l-0.3-1.9c6-0.9,6.9-9.7,7.6-16.8c0.2-1.9,0.4-3.6,0.6-5.1c1.4-7.4,11.6-10.3,12.1-10.4l0.5,1.8 c-0.1,0-9.6,2.7-10.7,8.9c-0.3,1.4-0.4,3.1-0.6,5C33.5,75.3,32.5,85.4,25.1,86.5z"/></g><g><path d="M45.8,25.4c-0.1-1.4-0.1-4.7,0.9-5.7c0.3-0.3,0.6-0.4,1-0.4v1.9c0.2,0,0.3-0.1,0.4-0.2c-0.3,0.4-0.5,2.4-0.4,4.4 L45.8,25.4z"/></g><g><path d="M43.6,28c-0.3-0.3-6.8-6.2-6.8-11.4c0-5,1.4-8.7,1.5-8.8L40,8.5c0,0-1.3,3.6-1.3,8.1c0,4.4,6.2,10,6.2,10L43.6,28z"/></g><g><path d="M54.6,54.5c0-2.7,0.8-13-0.3-13c-1.1,0-1.8-2.2-1.8-3.4c0-1.1,4.1-3.4,4.1-8.1c0-4.8-6.6-5.6-6.6-5.6s-6.6,0.8-6.6,5.6 c0,4.8,4.1,7,4.1,8.1c0,1.1-0.7,3.4-1.8,3.4c-1.1,0-0.3,10.4-0.3,13c0,2.7-5.3,7.7-5.3,17.2s5.9,19.5,9.9,19.5 c4.1,0,9.9-9.9,9.9-19.5S54.6,57.2,54.6,54.5z"/></g><g><path d="M50,92.2c-5,0-10.9-11.1-10.9-20.4c0-6.5,2.4-11,4-14c0.7-1.4,1.4-2.6,1.4-3.3c0-0.6-0.1-1.8-0.1-3.1 c-0.3-7.6-0.2-9.8,0.5-10.5c0.2-0.2,0.5-0.4,0.8-0.4c0.3-0.1,0.8-1.4,0.9-2.3c-0.1-0.2-0.5-0.6-0.8-1c-1.2-1.4-3.3-3.7-3.3-7.3 c0-5.5,7.1-6.5,7.4-6.5c0.1,0,0.2,0,0.2,0c0.3,0,7.4,1,7.4,6.5c0,3.6-2.1,5.9-3.3,7.3c-0.3,0.3-0.7,0.8-0.8,1 c0,0.9,0.6,2.2,0.9,2.3c0.3,0,0.6,0.1,0.8,0.4c0.7,0.8,0.8,2.9,0.5,10.5c-0.1,1.3-0.1,2.4-0.1,3.1c0,0.7,0.6,1.9,1.4,3.3 c1.6,3,4,7.5,4,14C60.9,81.1,55,92.2,50,92.2z M46.2,42.4c-0.3,1.4-0.1,6.5,0,9c0.1,1.3,0.1,2.5,0.1,3.2c0,1.2-0.7,2.4-1.6,4.1 c-1.6,3-3.7,7.1-3.7,13.1c0,9.4,5.7,18.5,9,18.5s9-9.2,9-18.5c0-6-2.2-10.1-3.7-13.1c-0.9-1.7-1.6-3-1.6-4.1 c0-0.7,0.1-1.8,0.1-3.2c0.1-2.5,0.3-7.6,0-9c-1.6-0.5-2.2-3.1-2.2-4.2c0-0.7,0.5-1.3,1.3-2.1c1.2-1.3,2.8-3.1,2.8-6 c0-3.6-4.8-4.5-5.6-4.6c-0.8,0.1-5.6,1.1-5.6,4.6c0,2.8,1.6,4.7,2.8,6c0.7,0.8,1.3,1.4,1.3,2.1C48.4,39.3,47.8,41.9,46.2,42.4z"/></g><g><path d="M55.2,44.2l-0.6-1.8c0.1,0,6.2-2.1,9-7.1c0.9-1.7,1.6-3.4,2.3-5.2c1.7-4.2,3.2-7.7,6.2-7.9l0.1,1.9 c-1.9,0.1-3.2,3.3-4.6,6.8c-0.7,1.8-1.5,3.6-2.4,5.4C62.1,41.9,55.4,44.1,55.2,44.2z"/></g><g><path d="M80.2,61.7c-6.1,0-12.9-8.4-15-11.6c-1.6-2.5-8-1.6-10.2-1.1l-0.4-1.8c1-0.2,9.6-2.1,12.2,1.9c2.7,4.1,8.7,10.7,13.4,10.7 V61.7z"/></g><g><path d="M74.9,86.5c-7.4-1.1-8.5-11.2-9.2-18.5c-0.2-1.8-0.4-3.6-0.6-5c-1.1-6.3-10.6-8.9-10.7-8.9l0.5-1.8 c0.4,0.1,10.7,3,12.1,10.4c0.3,1.5,0.4,3.2,0.6,5.1c0.7,7.1,1.6,16,7.6,16.9L74.9,86.5z"/></g><g><path d="M54.2,25.4l-1.9-0.1c0.1-1.9-0.1-4-0.4-4.4c0,0,0.2,0.2,0.4,0.2v-1.9c0.4,0,0.7,0.2,1,0.4C54.3,20.7,54.3,24.1,54.2,25.4z "/></g><g><path d="M56.4,28l-1.3-1.4c0.1-0.1,6.2-5.7,6.2-10c0-4.6-1.3-8.1-1.3-8.1l1.8-0.7c0.1,0.2,1.5,3.9,1.5,8.8 C63.2,21.8,56.6,27.8,56.4,28z"/></g></g></svg>`;

View file

@ -1,90 +0,0 @@
import '@lion/form/lion-form.js';
import '@lion/icon/lion-icon.js';
import { bug12 } from '@lion/icon/stories/icons/bugs-collection.js';
import '@lion/input/lion-input.js';
import { html, storiesOf } from '@open-wc/demoing-storybook';
import '../lion-button.js';
storiesOf('Buttons|Button')
.add(
'Used on its own',
() => html`
<style>
.demo-box {
display: flex;
padding: 8px;
}
lion-button {
margin: 8px;
}
</style>
<div class="demo-box">
<lion-button>Default</lion-button>
<lion-button><lion-icon .svg="${bug12}"></lion-icon>Debug</lion-button>
<lion-button type="submit">Submit</lion-button>
<lion-button aria-label="Debug"><lion-icon .svg="${bug12}"></lion-icon></lion-button>
<lion-button @click="${e => console.log('clicked/spaced/entered', e)}">
click/space/enter me and see log
</lion-button>
<lion-button disabled>Disabled</lion-button>
</div>
`,
)
.add(
'Within a native form',
() => html`
<form
@submit=${ev => {
ev.preventDefault();
console.log('submit handler');
}}
>
<label for="firstNameId">First name</label>
<input id="firstNameId" name="firstName" />
<label for="lastNameId">Last name</label>
<input id="lastNameId" name="lastName" />
<lion-button @click=${() => console.log('click handler')}>Submit</lion-button>
</form>
<p>
Supports the following use cases:
</p>
<ul>
<li>
Submit on button click
</li>
<li>
Reset native form fields when using type="reset"
</li>
<li>
Submit on button enter or space keypress
</li>
<li>
Submit on enter keypress inside an input
</li>
</ul>
<p>Important notes:</p>
<ul>
<li>
A (lion)-button of type submit is mandatory for the last use case, if you have multiple
inputs. This is native behavior.
</li>
<li>
<span style="background-color: azure">
<code>@click</code> on <code>lion-button</code>
</span>
and
<span style="background-color: seashell">
<code>@submit</code> on <code>form</code>
</span>
are triggered by these use cases. We strongly encourage you to listen to the submit
handler if your goal is to do something on form-submit
</li>
<li>
To prevent form submission full page reloads, add a <b>submit handler on the form</b>
<code>@submit</code> with <code>event.preventDefault()</code>. Adding it on the
<code>lion-button</code> is not enough.
</li>
</ul>
`,
);

View file

@ -0,0 +1,173 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import '../lion-button.js';
import iconSvg from './icon.svg.js';
<Meta title="Buttons/Button" parameters={{ component: 'lion-button' }} />
# Button
`lion-button` provides a button component that is easily stylable and is accessible.
<Story name="Default">
{html`
<lion-button>Default</lion-button>
`}
</Story>
```html
<lion-button>Default</lion-button>
```
## Features
- Clickable area that is bigger than visual size
- Works with native form / inputs
- Has integration for implicit form submission similar to how native `<form>`, `<input>` and `<button>` work together.
## How to use
### Installation
```sh
npm i --save @lion/button
```
```js
import '@lion/button/lion-button.js';
```
### Example
```html
<lion-button>Button Text</lion-button>
```
## Buttons
### With click handler
<Story name="Handler">
{html`
<lion-button @click="${e => console.log('clicked/spaced/entered', e)}">
Click | Space | Enter me and see log
</lion-button>
`}
</Story>
```html
<lion-button @click="${e => console.log('clicked/spaced/entered', e)}">
Click | Space | Enter me and see log
</lion-button>
```
### Icon button
<Story name="Icon">
{html`<lion-button>${iconSvg(html)} Bug</lion-button>`}
</Story>
```html
<lion-button>${iconSvg(html)} Bug</lion-button>
```
### Icon only button
<Story name="Icon-only">{html`
<lion-button aria-label="Bug">
${iconSvg(html)}
</lion-button>
`}</Story>
```html
<lion-button aria-label="Bug">
${iconSvg(html)}
</lion-button>
```
### Disabled button
<Story name="Disabled">
{html`<lion-button disabled>Disabled</lion-button>`}
</Story>
```html
<lion-button disabled>Disabled</lion-button>
```
### Usage with native form
Supports the following use cases:
- Submit on button click
- Reset native form fields when using type="reset"
- Submit on button enter or space keypress
- Submit on enter keypress inside an input
<Story name="Within native form">
{html`
<form
@submit=${ev => {
ev.preventDefault();
console.log('submit handler');
}}
>
<label for="firstNameId">First name</label>
<input id="firstNameId" name="firstName" />
<label for="lastNameId">Last name</label>
<input id="lastNameId" name="lastName" />
<lion-button @click=${() => console.log('click handler')}>Submit</lion-button>
</form>
`}
</Story>
```html
<form
@submit=${ev => {
ev.preventDefault();
console.log('submit handler');
}}
>
<label for="firstNameId">First name</label>
<input id="firstNameId" name="firstName" />
<label for="lastNameId">Last name</label>
<input id="lastNameId" name="lastName" />
<lion-button @click=${() => console.log('click handler')}>Submit</lion-button>
</form>
```
Important notes:
- A (lion)-button of type submit is mandatory for the last use case, if you have multiple inputs. This is native behavior.
- `@click` on `<lion-button>` and `@submit` on `<form>` are triggered by these use cases. We strongly encourage you to listen to the submit handler if your goal is to do something on form-submit.
- To prevent form submission full page reloads, add a **submit handler on the form** `@submit` with `event.preventDefault()`. Adding it on the `<lion-button>` is not enough.
## Considerations
### Why a Web Component?
There are multiple reasons why we used a Web Component as opposed to a CSS component.
- **Target size**: The minimum target size is 40 pixels, which makes even the small buttons easy to activate. A container element was needed to make this size possible.
- **Advanced styling**: There are advanced styling options regarding icons in buttons, where it is a lot more maintainable to handle icons in our button using slots. An example is that a sticky icon-only buttons may looks different from buttons which have both icons and text.
- **Native form integration**: The lion button works with native `<form>` submission, and even implicit form submission on-enter. A lot of delegation logic had to be created for this to work.
### Event target
We want to ensure that the event target returned to the user is `<lion-button>`, not `button`. Therefore, simply delegating the click to the native button immediately, is not desired. Instead, we catch the click event in the `<lion-button>`, and ensure delegation inside of there.
### Flashing a native button click as a direct child of form
By delegating the `click()` to the native button, it will bubble back up to `<lion-button>` which would cause duplicate actions. We have to simulate the full `.click()` however, otherwise form submission is not triggered. So this bubbling cannot be prevented.
Therefore, on click, we flash a `<button>` to the form as a direct child and fire the click on that button. We then immediately remove that button. This is a fully synchronous process; users or developers will not notice this, it should not cause problems.
### Native button & implicit form submission
Flashing the button in the way we do solves almost all issues except for one.
One of the specs of W3C is that when you have a form with multiple inputs,
pressing enter while inside one of the inputs only triggers a form submit if that form has a button of type submit.
To get this particular implicit form submission to work, having a native button in our `<lion-button>` is a hard requirement.
Therefore, not only do we flash a native button on the form to delegate `<lion-button>` trigger to `<button>`
and thereby trigger form submission, we **also** add a native `button` inside the `<lion-button>`
whose `type` property is synchronized with the type of the `<lion-button>`.

View file

@ -4,21 +4,9 @@
`lion-calendar` is a reusable and accessible calendar view.
## Features
## Live Demo/Documentation
- fully accessible keyboard navigation (Arrow Keys, PgUp, PgDn, ALT+PgUp, ALT+PgDn)
- **minDate**: disables all dates before a given date
- **maxDate**: disables all dates after a given date
- **disableDates**: disables some dates within an available range
- **selectedDate**: currently selected date
- **centralDate**: date that determines the currently visible month and that will be focused when keyboard moves the focus to the month grid
- **focusedDate**: (getter only) currently focused date (if there is any with real focus)
- **focusDate(date)**: focus on a certain date
- **focusSelectedDate()**: focus on the current selected date
- **focusCentralDate()**: focus on the current central date
- **firstDayOfWeek**: typically Sunday (default) or Monday
- **weekdayHeaderNotation**: long/short/narrow for the current locale (e.g. Thursday/Thu/T)
- **locale**: different locale for the current component only
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/calendar-standalone) for a live demo and API documentation
## How to use
@ -35,10 +23,5 @@ import '@lion/calendar/lion-calendar.js';
### Example
```html
<lion-calendar
.minDate="${new Date()}"
.maxDate="${new Date('2019/12/09')}"
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
>
</lion-calendar>
<lion-calendar></lion-calendar>
```

View file

@ -38,7 +38,7 @@
},
"devDependencies": {
"@lion/button": "0.4.6",
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4",
"sinon": "^7.2.2"
}

View file

@ -1,135 +0,0 @@
import '@lion/button/lion-button.js';
import { css } from '@lion/core';
import { html, storiesOf } from '@open-wc/demoing-storybook';
import '../lion-calendar.js';
const calendarDemoStyle = css`
.demo-calendar {
border: 1px solid #adadad;
box-shadow: 0 0 16px #ccc;
max-width: 500px;
}
`;
storiesOf('Calendar|Standalone')
.add(
'default',
() => html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar class="demo-calendar"></lion-calendar>
`,
)
.add('selectedDate', () => {
const today = new Date();
const selectedDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
return html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar class="demo-calendar" .selectedDate="${selectedDate}"></lion-calendar>
`;
})
.add('centralDate', () => {
const today = new Date();
const centralDate = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
return html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar class="demo-calendar" .centralDate="${centralDate}"></lion-calendar>
<p>Use TAB to see which date will be focused first.</p>
`;
})
.add('control focus', () => {
const today = new Date();
const selectedDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
const centralDate = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
return html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar
id="js-demo-calendar"
class="demo-calendar"
.selectedDate="${selectedDate}"
.centralDate="${centralDate}"
></lion-calendar>
<p>
Focus:
<lion-button
@click="${() => document.querySelector('#js-demo-calendar').focusCentralDate()}"
>
Central date
</lion-button>
<lion-button
@click="${() => document.querySelector('#js-demo-calendar').focusSelectedDate()}"
>
Selected date
</lion-button>
<lion-button @click="${() => document.querySelector('#js-demo-calendar').focusDate(today)}">
Today
</lion-button>
</p>
<p>Be aware that the central date changes when a new date is focused.</p>
`;
})
.add('minDate', () => {
const today = new Date();
const minDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 2);
return html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar class="demo-calendar" .minDate="${minDate}"></lion-calendar>
`;
})
.add('maxDate', () => {
const today = new Date();
const maxDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 2);
return html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar class="demo-calendar" .maxDate="${maxDate}"></lion-calendar>
`;
})
.add(
'disableDates',
() => html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar
class="demo-calendar"
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
></lion-calendar>
`,
)
.add('combined disabled dates', () => {
const today = new Date();
const maxDate = new Date(today.getFullYear(), today.getMonth() + 2, today.getDate());
return html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar
class="demo-calendar"
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
.minDate="${new Date()}"
.maxDate="${maxDate}"
></lion-calendar>
`;
});

View file

@ -0,0 +1,257 @@
import {
Story,
Meta,
html,
} from '@open-wc/demoing-storybook';
import { css } from '@lion/core';
import '../lion-calendar.js';
<Meta title="Others/Calendar" parameters={{ component: 'lion-calendar' }} />
# Calendar
`lion-calendar` is a reusable and accessible calendar view.
export const calendarDemoStyle = css`
.demo-calendar {
border: 1px solid #adadad;
box-shadow: 0 0 16px #ccc;
max-width: 500px;
}
`;
<Story name="Default">
{html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar class="demo-calendar"></lion-calendar>
`}
</Story>
```html
<lion-calendar></lion-calendar>
```
## Features
- fully accessible keyboard navigation (Arrow Keys, PgUp, PgDn, ALT+PgUp, ALT+PgDn)
- **minDate**: disables all dates before a given date
- **maxDate**: disables all dates after a given date
- **disableDates**: disables some dates within an available range
- **selectedDate**: currently selected date
- **centralDate**: date that determines the currently visible month and that will be focused when keyboard moves the focus to the month grid
- **focusedDate**: (getter only) currently focused date (if there is any with real focus)
- **focusDate(date)**: focus on a certain date
- **focusSelectedDate()**: focus on the current selected date
- **focusCentralDate()**: focus on the current central date
- **firstDayOfWeek**: typically Sunday (default) or Monday
- **weekdayHeaderNotation**: long/short/narrow for the current locale (e.g. Thursday/Thu/T)
- **locale**: different locale for the current component only
## How to use
### Installation
```sh
npm i --save @lion/calendar
```
```js
import '@lion/calendar/lion-calendar.js';
```
## Move to the selected date
The `selectedDate` is the date which is currently marked as selected.
You usually select a date by clicking on it with the mouse or hitting Enter on the keyboard.
The `selectedDate` might not be within the dates in the current month view.
<Story name="Selected Date">
{html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar class="demo-calendar" .selectedDate=${new Date(1988, 2, 5)}></lion-calendar>
`}
</Story>
```html
<lion-calendar .selectedDate="${new Date(1988, 2, 5)}"></lion-calendar>
```
## Central Date
The `centralDate` defines which day will be focused when keyboard moves the focus to the current month grid.
By default it is set to today, or the enabled day of the current month view that is closest to today's date.
The next and previous months' buttons work by changing the `centralDate` with plus or minus one month.
Changing the `centralDate` may mean a different view will be displayed to your users if it is in a different month.
Usually if you change only the day, "nothing" happens as it's already currently in view.
The `centralDate` can be different from `selectedDate` as you can have today as actively selected but still look at date that is years ago.
When the `selectedDate` changes, it will sync its value to the `centralDate`.
<Story name="Central Date">
{() => {
const today = new Date();
const centralDate = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
return html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar class="demo-calendar" .centralDate="${centralDate}"></lion-calendar>
`;
}}
</Story>
```html
<lion-calendar .centralDate="${new Date(1988, 2, 5)}"></lion-calendar>
```
## Controlling focus
You can control the focus by calling the following methods
- `focusCentralDate()`
- `focusSelectedDate()`
- `focusDate(dateInstanceToFocus)`
Be aware that the central date changes when a new date is focused.
<Story name="Controlling focus">
{() => {
const today = new Date();
const selectedDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
const centralDate = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
return html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar
id="js-demo-calendar"
class="demo-calendar"
.selectedDate="${selectedDate}"
.centralDate="${centralDate}"
></lion-calendar>
<p>
Focus
<button @click="${() => document.querySelector('#js-demo-calendar').focusCentralDate()}">
Central date
</button>
<button @click="${() => document.querySelector('#js-demo-calendar').focusSelectedDate()}">
Selected date
</button>
<button @click="${() => document.querySelector('#js-demo-calendar').focusDate(today)}">
Today
</button>
</p>
`;
}}
</Story>
## Limiting selectable values
### Providing a lower limit
To give a lower limit you can bind a date to the `minDate` property.
<Story name="minDate">
{() => {
const minDate = new Date();
return html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar class="demo-calendar" .minDate="${minDate}"></lion-calendar>
`;
}}
</Story>
```html
<lion-calendar .minDate="${new Date()}"></lion-calendar>
```
### Provide a higher limit
To give a higher limit you can bind a date to the `maxDate` property. In this example, we show how to create an offset of + 2 days.
<Story name="maxDate">
{() => {
const today = new Date();
const maxDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 2);
return html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar class="demo-calendar" .maxDate="${maxDate}"></lion-calendar>
`;
}}
</Story>
```js
const today = new Date();
const maxDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 2);
```
```html
<lion-calendar .maxDate="${maxDate}"></lion-calendar>
```
### Provide a list of disabled dates
In some cases a specific date or day of the week needs to be disabled, supply those days to the `disableDates` property.
<Story name="disableDates">
{html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar
class="demo-calendar"
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
></lion-calendar>
`}
</Story>
```html
<lion-calendar
.disableDates="${day => day.getDay() === 6 || day.getDay() === 0}"
></lion-calendar>
```
### Combined disable dates
To limit the scope of possible dates further, combine the methods mentioned above.
<Story name="Combined disable dates">
{() => {
const today = new Date();
const maxDate = new Date(today.getFullYear(), today.getMonth() + 2, today.getDate());
return html`
<style>
${calendarDemoStyle}
</style>
<lion-calendar
class="demo-calendar"
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
.minDate="${new Date()}"
.maxDate="${maxDate}"
></lion-calendar>
`;
}}
</Story>
```js
const today = new Date();
const maxDate = new Date(today.getFullYear(), today.getMonth() + 2, today.getDate());
```
```html
<lion-calendar
.disableDates=${day => day.getDay() === 6 || day.getDay() === 0}
.minDate="${new Date()}"
.maxDate="${maxDate}"
></lion-calendar>
```

View file

@ -2,13 +2,13 @@
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
`lion-checkbox-group` component is webcomponent that enhances the functionality of the native `<input type="checkbox">` element. Its purpose is to provide a way for users to check **multiple** options amongst a set of choices, or to function as a single toggle.
`lion-checkbox-group` component enhances the functionality of the native `<input type="checkbox">` element. Its purpose is to provide a way for users to check **multiple** options amongst a set of choices, or to function as a single toggle.
You should use [lion-checkbox](../checkbox/)'s inside this element.
## Features
## Live Demo/Documentation
Since it extends from [lion-fieldset](../fieldset/), it has all the features a fieldset has.
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-checkbox-group) for a live demo and API documentation
## How to use
@ -21,24 +21,17 @@ npm i --save @lion/checkbox @lion/checkbox-group
```js
import '@lion/checkbox/lion-checkbox.js';
import '@lion/checkbox-group/lion-checkbox-group.js';
// validator import example
import { Required } from '@lion/validate';
```
### Example
```html
<lion-form><form>
<lion-checkbox-group
<lion-checkbox-group
name="scientistsGroup"
label="Favorite scientists"
.validators=${[new Required()]}
>
>
<lion-checkbox name="scientists[]" label="Archimedes" .choiceValue=${'Archimedes'}></lion-checkbox>
<lion-checkbox name="scientists[]" label="Francis Bacon" .choiceValue=${'Francis Bacon'}></lion-checkbox>
<lion-checkbox name="scientists[]" label="Marie Curie" .choiceValue=${'Marie Curie'}></lion-checkbox>
</lion-checkbox-group>
</form></lion-form>
</lion-checkbox-group>
```
- Make sure that it has a name attribute, this is necessary for the [lion-form](../form/)'s serialization result.

View file

@ -39,7 +39,7 @@
"@lion/checkbox": "0.2.13",
"@lion/localize": "0.7.2",
"@lion/validate": "0.5.4",
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4",
"sinon": "^7.2.2"
}

View file

@ -1,156 +0,0 @@
import '@lion/checkbox/lion-checkbox.js';
import { Required, Validator } from '@lion/validate';
import { html, storiesOf } from '@open-wc/demoing-storybook';
import '../lion-checkbox-group.js';
storiesOf('Forms|Checkbox Group')
.add(
'Default',
() => html`
<lion-checkbox-group name="scientistsGroup" label="Favorite scientists">
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.choiceValue=${'Marie Curie'}
></lion-checkbox>
</lion-checkbox-group>
`,
)
.add(
'Pre Select',
() => html`
<lion-checkbox-group name="scientistsGroup" label="Favorite scientists">
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
checked
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.modelValue=${{ value: 'Marie Curie', checked: true }}
></lion-checkbox>
</lion-checkbox-group>
`,
)
.add(
'Disabled',
() => html`
<lion-checkbox-group name="scientistsGroup" label="Favorite scientists" disabled>
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.modelValue=${{ value: 'Marie Curie', checked: true }}
></lion-checkbox>
</lion-checkbox-group>
`,
)
.add('Validation', () => {
const validate = () => {
const checkboxGroup = document.querySelector('#scientistsGroup');
checkboxGroup.submitted = !checkboxGroup.submitted;
};
return html`
<lion-checkbox-group
id="scientistsGroup"
name="scientistsGroup"
label="Favorite scientists"
.validators=${[new Required()]}
>
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.choiceValue=${'Marie Curie'}
></lion-checkbox>
</lion-checkbox-group>
<button @click="${() => validate()}">Validate</button>
`;
})
.add('Validation 2 checked', () => {
class HasMinTwoChecked extends Validator {
constructor(...args) {
super(...args);
this.name = 'HasMinTwoChecked';
}
execute(value) {
let hasError = false;
const selectedValues = value['scientists[]'].filter(v => v.checked === true);
if (!(selectedValues.length >= 2)) {
hasError = true;
}
return hasError;
}
static async getMessage() {
return 'You need to select at least 2 values.';
}
}
const validate = () => {
const checkboxGroup = document.querySelector('#scientistsGroup');
checkboxGroup.submitted = !checkboxGroup.submitted;
};
return html`
<lion-checkbox-group
id="scientistsGroup"
name="scientistsGroup"
label="Favorite scientists"
help-text="You should have at least 2 of those"
.validators=${[new Required(), new HasMinTwoChecked()]}
>
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.choiceValue=${'Marie Curie'}
></lion-checkbox>
</lion-checkbox-group>
<button @click="${() => validate()}">Validate</button>
`;
});

View file

@ -0,0 +1,342 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import { Required, Validator, loadDefaultFeedbackMessages } from '@lion/validate';
import '@lion/checkbox/lion-checkbox.js';
import '../lion-checkbox-group.js';
<Meta title="Forms/Checkbox Group" parameters={{ component: 'lion-checkbox-group' }} />
# Checkbox Group
`lion-checkbox-group` component enhances the functionality of the native `<input type="checkbox">` element.
Its purpose is to provide a way for users to check **multiple** options amongst a set of choices, or to function as a single toggle.
> You should use <a href="https://github.com/ing-bank/lion/tree/master/packages/checkbox" target="_blank">`<lion-checkbox>`</a> elements as the children of the `<lion-checkbox-group>`.
<Story name="Default">
{html`
<lion-checkbox-group
name="scientistsGroup"
label="Favorite scientists"
>
<lion-checkbox name="scientists[]" label="Archimedes" .choiceValue=${'Archimedes'}></lion-checkbox>
<lion-checkbox name="scientists[]" label="Francis Bacon" .choiceValue=${'Francis Bacon'}></lion-checkbox>
<lion-checkbox name="scientists[]" label="Marie Curie" .choiceValue=${'Marie Curie'}></lion-checkbox>
</lion-checkbox-group>
`}
</Story>
```html
<lion-checkbox-group
name="scientistsGroup"
label="Favourite scientists"
>
<lion-checkbox name="scientists[]" label="Archimedes" .choiceValue=${'Archimedes'}></lion-checkbox>
<lion-checkbox name="scientists[]" label="Francis Bacon" .choiceValue=${'Francis Bacon'}></lion-checkbox>
<lion-checkbox name="scientists[]" label="Marie Curie" .choiceValue=${'Marie Curie'}></lion-checkbox>
</lion-checkbox-group>
```
> Make sure that the checkbox-group also has a name attribute, this is necessary for the [lion-form](?path=/docs/forms-form)'s serialization result.
## Features
Since it extends from [lion-fieldset](?path=/docs/forms-fieldset), it has all the features a fieldset has.
## How to use
### Installation
```sh
npm i --save @lion/checkbox @lion/checkbox-group
```
```js
import '@lion/checkbox/lion-checkbox.js';
import '@lion/checkbox-group/lion-checkbox-group.js';
```
## Pre-select
You can pre-select options by targeting the `modelValue` object of the option and setting the `checked` property to `true`.
<Story name="Pre-select">
{html`
<lion-checkbox-group name="scientistsGroup" label="Favorite scientists">
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
checked
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.modelValue=${{ value: 'Marie Curie', checked: true }}
></lion-checkbox>
</lion-checkbox-group>
`}
</Story>
```html
<lion-checkbox-group name="scientistsGroup" label="Favorite scientists">
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
checked
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.modelValue=${{ value: 'Marie Curie', checked: true }}
></lion-checkbox>
</lion-checkbox-group>
```
## Disabled
You can disable the entire group by setting the `disabled` attribute on the `<lion-checkbox-group>`.
<Story name="Disabled">
{html`
<lion-checkbox-group name="scientistsGroup" label="Favorite scientists" disabled>
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.modelValue=${{ value: 'Marie Curie', checked: true }}
></lion-checkbox>
</lion-checkbox-group>
`}
</Story>
```html
<lion-checkbox-group name="scientistsGroup" label="Favorite scientists" disabled>
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.modelValue=${{ value: 'Marie Curie', checked: true }}
></lion-checkbox>
</lion-checkbox-group>
```
## Validation
You can apply validation to the `<lion-checkbox-group>`, similar to how you would do so in any fieldset.
The interaction states of the `<lion-checkbox-group>` are evaluated in order to hide or show feedback messages.
<Story name="Validation">
{() => {
loadDefaultFeedbackMessages();
const validate = () => {
const checkboxGroup = document.querySelector('#scientistsGroup');
checkboxGroup.submitted = !checkboxGroup.submitted;
};
return html`
<lion-checkbox-group
id="scientistsGroup"
name="scientistsGroup"
label="Favorite scientists"
.validators=${[new Required()]}
>
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.choiceValue=${'Marie Curie'}
></lion-checkbox>
</lion-checkbox-group>
<button @click="${() => validate()}">Validate</button>
`;
}}
</Story>
```js
import { Required, loadDefaultFeedbackMessages } from '@lion/validate';
loadDefaultFeedbackMessages();
const validate = () => {
const checkboxGroup = document.querySelector('#scientistsGroup');
checkboxGroup.submitted = !checkboxGroup.submitted;
};
```
```html
<lion-checkbox-group
id="scientistsGroup"
name="scientistsGroup"
label="Favorite scientists"
.validators=${[new Required()]}
>
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.choiceValue=${'Marie Curie'}
></lion-checkbox>
</lion-checkbox-group>
<button @click="${() => validate()}">Validate</button>
```
## Validation advanced
Below is a more advanced validator on the group that evaluates the children checkboxes' checked states.
<Story name="Validation Advanced">
{() => {
loadDefaultFeedbackMessages();
class HasMinTwoChecked extends Validator {
constructor(...args) {
super(...args);
this.name = 'HasMinTwoChecked';
}
execute(value) {
let hasError = false;
const selectedValues = value['scientists[]'].filter(v => v.checked === true);
if (!(selectedValues.length >= 2)) {
hasError = true;
}
return hasError;
}
static async getMessage() {
return 'You need to select at least 2 values.';
}
}
const validate = () => {
const checkboxGroup = document.querySelector('#scientistsGroup2');
checkboxGroup.submitted = !checkboxGroup.submitted;
};
return html`
<lion-checkbox-group
id="scientistsGroup2"
name="scientistsGroup"
label="Favorite scientists"
help-text="You should have at least 2 of those"
.validators=${[new Required(), new HasMinTwoChecked()]}
>
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.choiceValue=${'Marie Curie'}
></lion-checkbox>
</lion-checkbox-group>
<button @click="${() => validate()}">Validate</button>
`;
}}
</Story>
```js
import { Required, Validator, loadDefaultFeedbackMessages } from '@lion/validate';
loadDefaultFeedbackMessages();
class HasMinTwoChecked extends Validator {
constructor(...args) {
super(...args);
this.name = 'HasMinTwoChecked';
}
execute(value) {
let hasError = false;
const selectedValues = value['scientists[]'].filter(v => v.checked === true);
if (!(selectedValues.length >= 2)) {
hasError = true;
}
return hasError;
}
static async getMessage() {
return 'You need to select at least 2 values.';
}
}
const validate = () => {
const checkboxGroup = document.querySelector('#scientistsGroup');
checkboxGroup.submitted = !checkboxGroup.submitted;
};
```
```html
<lion-checkbox-group
id="scientistsGroup"
name="scientistsGroup"
label="Favorite scientists"
help-text="You should have at least 2 of those"
.validators=${[new Required(), new HasMinTwoChecked()]}
>
<lion-checkbox
name="scientists[]"
label="Archimedes"
.choiceValue=${'Archimedes'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Francis Bacon"
.choiceValue=${'Francis Bacon'}
></lion-checkbox>
<lion-checkbox
name="scientists[]"
label="Marie Curie"
.choiceValue=${'Marie Curie'}
></lion-checkbox>
</lion-checkbox-group>
<button @click="${() => validate()}">Validate</button>
```

View file

@ -37,7 +37,7 @@
"@lion/input": "0.4.2"
},
"devDependencies": {
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4"
}
}

View file

@ -0,0 +1,42 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import '../lion-checkbox.js';
<Meta title="Forms/Checkbox" parameters={{ component: 'lion-checkbox' }} />
# Checkbox
`lion-checkbox` component is a sub-element to be used in [lion-checkbox-group](?path=/docs/forms-checkbox-group--default-story) elements. Its purpose is to provide a way for users to check **multiple** options amongst a set of choices, or to function as a single toggle.
<Story name="Default">{html`
<lion-checkbox name="scientists[]" label="Archimedes" .choiceValue=${'Archimedes'}></lion-checkbox>
<lion-checkbox name="scientists[]" label="Francis Bacon" .choiceValue=${'Francis Bacon'}></lion-checkbox>
<lion-checkbox name="scientists[]" label="Marie Curie" .choiceValue=${'Marie Curie'}></lion-checkbox>
`}</Story>
```html
<lion-checkbox name="scientists[]" label="Archimedes" .choiceValue=${'Archimedes'}></lion-checkbox>
<lion-checkbox name="scientists[]" label="Francis Bacon" .choiceValue=${'Francis Bacon'}></lion-checkbox>
<lion-checkbox name="scientists[]" label="Marie Curie" .choiceValue=${'Marie Curie'}></lion-checkbox>
```
- Use this component inside a [lion-checkbox-group](?path=/docs/forms-checkbox-group--default-story)
- Make sure that it has a name attribute with appended `[]` for multiple choices.
## Features
- Get the checked state (boolean) - `checked` boolean attribute
- Pre-select an option by setting the `checked` boolean attribute
- Get or set the value of the choice - `choiceValue()`
## How to use
### Installation
```sh
npm i --save @lion/checkbox
```
```js
import '@lion/checkbox/lion-checkbox.js';
```

View file

@ -38,7 +38,7 @@
"devDependencies": {
"@lion/input": "0.4.2",
"@lion/validate": "0.5.4",
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4",
"sinon": "^7.2.2"
}

View file

@ -2,49 +2,28 @@
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
## Deprecations
`lion-input-amount` component is based on the generic text input field. Its purpose is to provide a way for users to fill in an amount.
Currently all deprecations are removed due to alpha state.
## Live Demo/Documentation
## Deduping of mixins
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/core) for a live demo and API documentation
### Why is deduping of mixins necessary?
## How to use
Imagine you are developing web components and creating ES classes for Custom Elements. You have two generic mixins (let's say `M1` and `M2`) which require independently the same even more generic mixin (`BaseMixin`). `M1` and `M2` can be used independently, that means they have to inherit from `BaseMixin` also independently. But they can be also used in combination. Sometimes `M1` and `M2` are used in the same component and can mess up the inheritance chain if `BaseMixin` is applied twice.
In other words, this may happen to the protoype chain `... -> M2 -> BaseMixin -> M1 -> BaseMixin -> ...`.
### Installation
An example of this may be a `LocalizeMixin` used across different components and mixins. Some mixins may need it and many components need it too and can not rely on other mixins to have it by default, so must inherit from it independently.
```sh
npm i --save @lion/core
```
The more generic the mixin is, the higher the chance of being appliend more than once. As a mixin author you can't control how it is used, and can't always predict it. So as a safety measure it is always recommended to create deduping mixins.
```js
import { dedupeMixin, LitElement } from '@lion/core';
```
### Usage of dedupeMixin()
This is an example of how to make a conventional ES mixin deduping.
### Example
```js
const BaseMixin = dedupeMixin((superClass) => {
return class extends superClass { ... };
});
// inherits from BaseMixin
const M1 = dedupeMixin((superClass) => {
return class extends BaseMixin(superClass) { ... };
});
// inherits from BaseMixin
const M2 = dedupeMixin((superClass) => {
return class extends BaseMixin(superClass) { ... };
});
// component inherits from M1
// MyCustomElement -> M1 -> BaseMixin -> BaseCustomElement;
class MyCustomElement extends M1(BaseCustomElement) { ... }
// component inherits from M2
// MyCustomElement -> M2 -> BaseMixin -> BaseCustomElement;
class MyCustomElement extends M2(BaseCustomElement) { ... }
// component inherits from both M1 and M2
// MyCustomElement -> M2 -> M1 -> BaseMixin -> BaseCustomElement;
class MyCustomElement extends M2(M1(BaseCustomElement)) { ... }
```

View file

@ -3,25 +3,43 @@
* For now please import types from lit-element and lit-html directly.
*/
// lit-element
export {
css,
CSSResult,
// decorators.js
customElement,
// updating-element.js
defaultConverter,
eventOptions,
LitElement,
notEqual,
property,
query,
queryAll,
// css-tag.js
supportsAdoptingStyleSheets,
unsafeCSS,
UpdatingElement,
} from 'lit-element';
// lit-html
export {
html,
svg,
render,
noChange,
nothing,
directive,
isDirective,
TemplateResult,
SVGTemplateResult,
AttributePart,
BooleanAttributePart,
directive,
EventPart,
NodePart,
PropertyPart,
html,
isDirective,
isPrimitive,
noChange,
NodePart,
nothing,
PropertyPart,
render,
svg,
SVGTemplateResult,
TemplateResult,
} from 'lit-html';
export { render as renderShady } from 'lit-html/lib/shady-render.js';
export { asyncAppend } from 'lit-html/directives/async-append.js';
export { asyncReplace } from 'lit-html/directives/async-replace.js';
export { cache } from 'lit-html/directives/cache.js';
@ -32,30 +50,12 @@ export { repeat } from 'lit-html/directives/repeat.js';
export { styleMap } from 'lit-html/directives/style-map.js';
export { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
export { until } from 'lit-html/directives/until.js';
// lit-element
export {
LitElement,
// css-tag.js
supportsAdoptingStyleSheets,
CSSResult,
unsafeCSS,
css,
// updating-element.js
defaultConverter,
notEqual,
UpdatingElement,
// decorators.js
customElement,
property,
query,
queryAll,
eventOptions,
} from 'lit-element';
export { render as renderShady } from 'lit-html/lib/shady-render.js';
// ours
export { dedupeMixin } from './src/dedupeMixin.js';
export { DelegateMixin } from './src/DelegateMixin.js';
export { LionSingleton } from './src/LionSingleton.js';
export { SlotMixin } from './src/SlotMixin.js';
export { DisabledMixin } from './src/DisabledMixin.js';
export { DisabledWithTabIndexMixin } from './src/DisabledWithTabIndexMixin.js';
export { LionSingleton } from './src/LionSingleton.js';
export { SlotMixin } from './src/SlotMixin.js';
export { UpdateStylesMixin } from './src/UpdateStylesMixin.js';

View file

@ -35,7 +35,7 @@
"lit-html": "^1.0.0"
},
"devDependencies": {
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4",
"sinon": "^7.2.2"
}

View file

@ -0,0 +1,67 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
<Meta title="Others/System/Core" />
# Core
The `@lion/core` package is mostly for in depth usage.
It handles the version of `lit-element` and `lit-html`.
In order to be sure a compatible version is used import it via this package.
```js
import { LitElement, html, render } from '@lion/core';
```
It offers low level functionality for
- [function to deduplicate mixins (dedupeMixin)](#deduping-of-mixins)
- Mixin to handle disabled (DisabledMixin)
- Mixin to handle disabled AND tabIndex (DisabledWithTabIndexMixin)
- Mixin to manage auto generated needed slot elements in light dom (SlotMixin)
> These features are not well documented - care to help out?
## Deduping of mixins
### Why is deduping of mixins necessary?
Imagine you are developing web components and creating ES classes for Custom Elements. You have two generic mixins (let's say `M1` and `M2`) which require independently the same even more generic mixin (`BaseMixin`). `M1` and `M2` can be used independently, that means they have to inherit from `BaseMixin` also independently. But they can be also used in combination. Sometimes `M1` and `M2` are used in the same component and can mess up the inheritance chain if `BaseMixin` is applied twice.
In other words, this may happen to the protoype chain `... -> M2 -> BaseMixin -> M1 -> BaseMixin -> ...`.
An example of this may be a `LocalizeMixin` used across different components and mixins. Some mixins may need it and many components need it too and can not rely on other mixins to have it by default, so must inherit from it independently.
The more generic the mixin is, the higher the chance of being appliend more than once. As a mixin author you can't control how it is used, and can't always predict it. So as a safety measure it is always recommended to create deduping mixins.
### Usage of dedupeMixin()
This is an example of how to make a conventional ES mixin deduping.
```js
const BaseMixin = dedupeMixin((superClass) => {
return class extends superClass { ... };
});
// inherits from BaseMixin
const M1 = dedupeMixin((superClass) => {
return class extends BaseMixin(superClass) { ... };
});
// inherits from BaseMixin
const M2 = dedupeMixin((superClass) => {
return class extends BaseMixin(superClass) { ... };
});
// component inherits from M1
// MyCustomElement -> M1 -> BaseMixin -> BaseCustomElement;
class MyCustomElement extends M1(BaseCustomElement) { ... }
// component inherits from M2
// MyCustomElement -> M2 -> BaseMixin -> BaseCustomElement;
class MyCustomElement extends M2(BaseCustomElement) { ... }
// component inherits from both M1 and M2
// MyCustomElement -> M2 -> M1 -> BaseMixin -> BaseCustomElement;
class MyCustomElement extends M2(M1(BaseCustomElement)) { ... }
```

View file

@ -2,15 +2,12 @@
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
`lion-dialog` is a component wrapping a modal dialog controller
Its purpose is to make it easy to use our Overlay System declaratively
With regards to modal dialogs, this is one of the more commonly used examples of overlays.
`lion-dialog` is a component wrapping a modal dialog controller.
Its purpose is to make it easy to use our Overlay System declaratively.
## Features
## Live Demo/Documentation
- Show content when clicking the invoker
- Respond to close event in the slot="content" element, to close the content
- Have a `.config` object to set or update the OverlayController's configuration
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/overlays-specific-wc-dialog) for a live demo and documentation
## How to use
@ -26,20 +23,12 @@ import '@lion/dialog/lion-dialog.js';
### Example
```js
html`
<lion-dialog.config=${{
viewportConfig: { placement: 'bottom-right' },
}}>
```html
<lion-dialog>
<div slot="content">
This is a dialog
<button
@click=${e => e.target.dispatchEvent(new Event('dialog-close', { bubbles: true }))}
>x</button>
<button @click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}>x</button>
<div>
<button slot="invoker">
Click me
</button>
</lion-dialog>
`;
<button slot="invoker">Click me</button>
</lion-dialog>
```

View file

@ -34,7 +34,7 @@
"@lion/overlays": "0.10.1"
},
"devDependencies": {
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4"
}
}

View file

@ -0,0 +1,44 @@
import { css } from '@lion/core';
export default css`
.demo-box {
width: 200px;
background-color: white;
border-radius: 2px;
border: 1px solid grey;
padding: 8px;
}
.demo-box_placements {
display: flex;
flex-direction: column;
margin-top: 20px;
}
lion-dialog {
display: block;
padding: 10px;
margin-bottom: 10px;
}
.close-button {
color: black;
font-size: 28px;
line-height: 28px;
}
.demo-box__column {
display: flex;
flex-direction: column;
}
.dialog {
display: block;
position: absolute;
font-size: 16px;
color: white;
background-color: black;
border-radius: 4px;
padding: 8px;
}
`;

View file

@ -1,142 +0,0 @@
import { css } from '@lion/core';
import { html, object, storiesOf, withKnobs } from '@open-wc/demoing-storybook';
import '../lion-dialog.js';
const dialogDemoStyle = css`
.demo-box {
width: 200px;
background-color: white;
border-radius: 2px;
border: 1px solid grey;
padding: 8px;
}
.demo-box_placements {
display: flex;
flex-direction: column;
width: 173px;
margin: 0 auto;
margin-top: 68px;
}
lion-dialog {
padding: 10px;
}
.close-button {
color: black;
font-size: 28px;
line-height: 28px;
}
.demo-box__column {
display: flex;
flex-direction: column;
}
.dialog {
display: block;
position: absolute;
font-size: 16px;
color: white;
background-color: black;
border-radius: 4px;
padding: 8px;
}
@media (max-width: 480px) {
.dialog {
display: none;
}
}
`;
storiesOf('Overlays Specific WC | Dialog')
.addDecorator(withKnobs)
.add(
'Button dialog',
() => html`
<style>
${dialogDemoStyle}
</style>
<p>
Important note: Your <code>slot="content"</code> gets moved to global overlay container.
After initialization it is no longer a child of <code>lion-dialog</code>
</p>
<p>
To close your dialog from some action performed inside the content slot, fire a
<code>hide</code> event.
</p>
<p>
For the dialog to close, it will need to bubble to the content slot (use
<code>bubbles: true</code>. If absolutely needed <code>composed: true</code> can be used to
traverse shadow boundaries)
</p>
<p>The demo below demonstrates this</p>
<div class="demo-box">
<lion-dialog>
<button slot="invoker">Dialog</button>
<div slot="content" class="dialog">
Hello! You can close this notification here:
<button
class="close-button"
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
>
</button>
</div>
</lion-dialog>
</div>
`,
)
.add('Custom configuration', () => {
const dialog = placement => html`
<lion-dialog .config=${{ viewportConfig: { placement } }}>
<button slot="invoker">Dialog ${placement}</button>
<div slot="content" class="dialog">
Hello! You can close this notification here:
<button
class="close-button"
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
>
</button>
</div>
</lion-dialog>
`;
return html`
<style>
${dialogDemoStyle}
</style>
<div class="demo-box_placements">
${dialog('center')} ${dialog('top-left')} ${dialog('top-right')} ${dialog('bottom-left')}
${dialog('bottom-right')}
</div>
`;
})
.add('Toggle placement with knobs', () => {
const dialog = html`
<lion-dialog .config=${object('config', { viewportConfig: { placement: 'center' } })}>
<button slot="invoker">Dialog</button>
<div slot="content" class="dialog">
Hello! You can close this notification here:
<button
class="close-button"
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
>
</button>
</div>
</lion-dialog>
`;
return html`
<style>
${dialogDemoStyle}
</style>
<div class="demo-box_placements">
${dialog}
</div>
`;
});

View file

@ -0,0 +1,182 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import demoStyle from './demo-dialog-style.js';
import '../lion-dialog.js';
<Meta title="Overlays/Dialog" parameters={{ component: 'lion-dialog' }} />
# Dialog
`lion-dialog` is a component wrapping a modal dialog controller.
Its purpose is to make it easy to use our Overlay System declaratively.
<Story name="Default">
{html`
<style>${demoStyle}</style>
<lion-dialog>
<button slot="invoker">Click me to open dialog</button>
<div slot="content" class="dialog">
Hello! You can close this dialog here:
<button
class="close-button"
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
>
</button>
</div>
</lion-dialog>
`}
</Story>
```html
<lion-dialog>
<button slot="invoker">Click me to open dialog</button>
<div slot="content" class="dialog">
Hello! You can close this dialog here:
<button
class="close-button"
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
>
</button>
</div>
</lion-dialog>
```
## Features
- Show content when clicking the invoker
- Respond to close event in the slot="content" element, to close the content
- Have a `.config` object to set or update the OverlayController's configuration
## How to use
### Installation
```sh
npm i --save @lion/dialog
```
```js
import '@lion/dialog/lion-dialog.js';
```
## Usage notes
- Your `slot="content"` node will be moved to the global overlay container during initialization.
After, your content node is no longer a child of `lion-dialog`.
If you still need to access it from the `lion-dialog` you can do so by using the `._overlayContentNode` property.
- To close the overlay from within the content node, you need to dispatch a `close-overlay` event that bubbles.
It has to be able to reach the content node.
- If you need to traverse shadow boundaries, you will have to add `composed: true` as well, although this is discouraged as a practice.
## Changing the configuration
You can use the `config` property on the dialog to change the configuration.
The documentation of the full config object can be found in the `lion/overlay` package.
The `config` property uses a setter to merge the passed configuration with the current, so you only **overwrite what you pass** when updating `config`.
### Placement overrides
<Story name="Placement overrides">
{() => {
const dialog = placement => html`
<lion-dialog .config=${{ viewportConfig: { placement } }}>
<button slot="invoker">Dialog ${placement}</button>
<div slot="content" class="dialog">
Hello! You can close this notification here:
<button
class="close-button"
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
>
</button>
</div>
</lion-dialog>
`;
return html`
<style>${demoStyle}</style>
<div class="demo-box_placements">
${dialog('center')} ${dialog('top-left')} ${dialog('top-right')} ${dialog('bottom-left')}
${dialog('bottom-right')}
</div>
`;
}}
</Story>
```html
<lion-dialog .config=${{ viewportConfig: { placement: 'top-left' } }}>
<button slot="invoker">Dialog top-left</button>
<div slot="content" class="dialog">
Hello! You can close this notification here:
<button
class="close-button"
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
>
</button>
</div>
</lion-dialog>
```
Configuration passed to `config` property:
```js
{
viewportConfig: {
placement: ... // <-- choose a position
}
}
```
### Other overrides
No backdrop, hides on escape, prevents scrolling while opened, and focuses the body when hiding.
<Story name="Other overrides">
{html`
<style>${demoStyle}</style>
<lion-dialog .config=${{
hasBackdrop: false,
hidesOnEscape: true,
preventsScroll: true,
elementToFocusAfterHide: document.body
}}>
<button slot="invoker">Click me to open dialog</button>
<div slot="content" class="dialog">
Hello! You can close this dialog here:
<button
class="close-button"
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
>
</button>
</div>
</lion-dialog>
`}
</Story>
```html
<lion-dialog .config="${{}}">
<button slot="invoker">Open dialog</button>
<div slot="content" class="dialog">
Hello! You can close this notification here:
<button
class="close-button"
@click=${e => e.target.dispatchEvent(new Event('close-overlay', { bubbles: true }))}
>
</button>
</div>
</lion-dialog>
```
Configuration passed to `config` property:
```js
{
hasBackdrop: false,
hidesOnEscape: true,
preventsScroll: true,
elementToFocusAfterHide: document.body
}
```

View file

@ -1,53 +0,0 @@
# Interaction States
`InteractionStateMixin` saves meta information about interaction states. It allows the
Application Developer to create advanced UX scenarios.
Examples of such scenarios are:
- show the validation message of an input only after the user has blurred the input field
- hide the validation message when an invalid value becomes valid
- show a red border around the input right after the input became invalid
The meta information that InteractionStateMixin collects, is stored in the properties:
- `touched` : the user blurred the field
- `dirty` : the value in the field has changed
- `prefilled` : a prepopulated field is non empty
## Listenening for changes
Application Developers can subscribe to the events `touched-changed` and `dirty-changed`.
## Advanced use cases
### Overriding interaction states
When creating a [custom wrapper or platform wrapper](./FormFundaments.md), it can be needed to
override the way prefilled values are 'computed'. The example below shows how this is done for
checkboxes/radio-inputs.
```js
/**
* @override
*/
static _isPrefilled(modelValue) {
return modelValue.checked;
}
```
## How interaction states are preconfigured
We show the validity feedback when one of the following conditions is met:
- prefilled:
The user already filled in something, or the value is prefilled
when the form is initially rendered.
- touched && dirty && !prefilled:
When a user starts typing for the first time in a field with for instance `required` validation,
error message should not be shown until a field becomes `touched` (a user leaves(blurs) a field).
When a user enters a field without altering the value (making it `dirty` but not `touched`),
an error message shouldn't be shown either.
- submitted:
If the form is submitted, always show the error message.

View file

@ -1,267 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.2" baseProfile="tiny" width="210mm" height="297mm" viewBox="0 0 21000 29700" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
<defs>
<font id="EmbeddedFont_1" horiz-adv-x="2048">
<font-face font-family="Liberation Sans embedded" units-per-em="2048" font-weight="normal" font-style="normal" ascent="1851" descent="433"/>
<missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
<glyph unicode="z" horiz-adv-x="927" d="M 40,0 L 40,146 716,922 C 639,918 572,916 513,916 L 80,916 80,1062 948,1062 948,943 373,269 262,146 C 343,152 418,155 489,155 L 980,155 980,0 Z"/>
<glyph unicode="y" horiz-adv-x="985" d="M 127,-409 L 107,-240 C 146,-251 181,-256 210,-256 250,-256 282,-249 306,-236 330,-223 350,-204 365,-180 376,-162 395,-117 420,-46 423,-36 429,-21 436,-2 L 33,1062 227,1062 448,447 C 477,369 502,287 525,201 546,284 570,364 599,443 L 826,1062 1006,1062 602,-18 C 559,-135 525,-215 501,-259 469,-318 432,-362 391,-390 350,-417 300,-431 243,-431 208,-431 170,-424 127,-409 Z"/>
<glyph unicode="w" horiz-adv-x="1458" d="M 331,0 L 6,1062 192,1062 361,449 424,221 C 427,232 445,305 479,440 L 648,1062 833,1062 992,446 1045,243 1106,448 1288,1062 1463,1062 1131,0 944,0 775,636 734,817 519,0 Z"/>
<glyph unicode="v" horiz-adv-x="965" d="M 430,0 L 26,1062 216,1062 444,426 C 469,357 491,286 512,212 528,268 550,335 579,414 L 815,1062 1000,1062 598,0 Z"/>
<glyph unicode="u" horiz-adv-x="867" d="M 831,0 L 831,156 C 748,36 636,-24 494,-24 431,-24 373,-12 319,12 264,36 224,66 198,103 171,139 153,183 142,236 135,271 131,327 131,404 L 131,1062 311,1062 311,473 C 311,379 315,316 322,283 333,236 357,199 394,172 431,145 476,131 530,131 584,131 635,145 682,173 729,200 763,238 783,286 802,333 812,402 812,493 L 812,1062 992,1062 992,0 Z"/>
<glyph unicode="t" horiz-adv-x="532" d="M 528,161 L 554,2 C 503,-9 458,-14 418,-14 353,-14 302,-4 266,17 230,38 205,65 190,99 175,132 168,203 168,311 L 168,922 36,922 36,1062 168,1062 168,1325 347,1433 347,1062 528,1062 528,922 347,922 347,301 C 347,250 350,217 357,202 363,187 373,176 388,167 402,158 422,154 449,154 469,154 495,156 528,161 Z"/>
<glyph unicode="s" horiz-adv-x="867" d="M 63,317 L 241,345 C 251,274 279,219 325,181 370,143 434,124 516,124 599,124 660,141 700,175 740,208 760,248 760,293 760,334 742,366 707,389 682,405 621,425 523,450 391,483 300,512 249,537 198,561 159,595 133,638 106,681 93,728 93,780 93,827 104,871 126,912 147,952 177,985 214,1012 242,1033 280,1050 329,1065 377,1079 429,1086 484,1086 567,1086 641,1074 704,1050 767,1026 813,994 843,953 873,912 894,857 905,788 L 729,764 C 721,819 698,861 660,892 621,923 567,938 497,938 414,938 355,924 320,897 285,870 267,838 267,801 267,778 274,757 289,738 304,719 327,703 358,690 376,683 429,668 517,644 644,610 733,582 784,561 834,539 873,507 902,466 931,425 945,373 945,312 945,252 928,196 893,143 858,90 807,49 741,20 675,-10 600,-24 517,-24 379,-24 274,5 202,62 129,119 83,204 63,317 Z"/>
<glyph unicode="r" horiz-adv-x="592" d="M 133,0 L 133,1062 295,1062 295,901 C 336,976 375,1026 410,1050 445,1074 483,1086 525,1086 586,1086 647,1067 710,1028 L 648,861 C 604,887 560,900 516,900 477,900 441,888 410,865 379,841 356,808 343,766 323,702 313,632 313,556 L 313,0 Z"/>
<glyph unicode="p" horiz-adv-x="927" d="M 135,-407 L 135,1062 299,1062 299,924 C 338,978 381,1019 430,1046 479,1073 538,1086 607,1086 698,1086 778,1063 847,1016 916,969 969,904 1004,819 1039,734 1057,640 1057,539 1057,430 1038,333 999,246 960,159 903,92 829,46 754,-1 676,-24 594,-24 534,-24 480,-11 433,14 385,39 346,71 315,110 L 315,-407 Z M 298,525 C 298,388 326,287 381,222 436,157 503,124 582,124 662,124 731,158 788,226 845,293 873,398 873,540 873,675 845,777 790,844 734,911 667,945 590,945 513,945 446,909 387,838 328,766 298,662 298,525 Z"/>
<glyph unicode="o" horiz-adv-x="986" d="M 68,531 C 68,728 123,873 232,968 323,1047 435,1086 566,1086 712,1086 831,1038 924,943 1017,847 1063,715 1063,546 1063,409 1043,302 1002,224 961,145 901,84 823,41 744,-2 659,-24 566,-24 417,-24 297,24 206,119 114,214 68,352 68,531 Z M 253,531 C 253,395 283,293 342,226 401,158 476,124 566,124 655,124 730,158 789,226 848,294 878,398 878,537 878,668 848,768 789,836 729,903 655,937 566,937 476,937 401,903 342,836 283,769 253,667 253,531 Z"/>
<glyph unicode="n" horiz-adv-x="867" d="M 135,0 L 135,1062 297,1062 297,911 C 375,1028 488,1086 635,1086 699,1086 758,1075 812,1052 865,1029 905,998 932,961 959,924 977,879 988,828 995,795 998,736 998,653 L 998,0 818,0 818,646 C 818,719 811,774 797,811 783,847 758,876 723,898 687,919 645,930 597,930 520,930 454,906 399,857 343,808 315,716 315,580 L 315,0 Z"/>
<glyph unicode="m" horiz-adv-x="1439" d="M 135,0 L 135,1062 296,1062 296,913 C 329,965 374,1007 429,1039 484,1070 547,1086 618,1086 697,1086 761,1070 812,1037 862,1004 897,959 918,900 1002,1024 1111,1086 1246,1086 1351,1086 1432,1057 1489,999 1546,940 1574,850 1574,729 L 1574,0 1395,0 1395,669 C 1395,741 1389,793 1378,825 1366,856 1345,882 1314,901 1283,920 1247,930 1206,930 1131,930 1069,905 1020,856 971,806 946,726 946,617 L 946,0 766,0 766,690 C 766,770 751,830 722,870 693,910 645,930 578,930 527,930 481,917 438,890 395,863 363,824 344,773 325,722 315,648 315,551 L 315,0 Z"/>
<glyph unicode="l" horiz-adv-x="178" d="M 131,0 L 131,1466 311,1466 311,0 Z"/>
<glyph unicode="k" horiz-adv-x="887" d="M 136,0 L 136,1466 316,1466 316,630 742,1062 975,1062 569,668 1016,0 794,0 443,543 316,421 316,0 Z"/>
<glyph unicode="i" horiz-adv-x="198" d="M 136,1259 L 136,1466 316,1466 316,1259 Z M 136,0 L 136,1062 316,1062 316,0 Z"/>
<glyph unicode="f" horiz-adv-x="631" d="M 178,0 L 178,922 19,922 19,1062 178,1062 178,1175 C 178,1246 184,1299 197,1334 214,1381 245,1419 289,1448 332,1477 393,1491 472,1491 523,1491 579,1485 640,1473 L 613,1316 C 576,1323 540,1326 507,1326 452,1326 414,1314 391,1291 368,1268 357,1224 357,1160 L 357,1062 564,1062 564,922 357,922 357,0 Z"/>
<glyph unicode="e" horiz-adv-x="986" d="M 862,342 L 1048,319 C 1019,210 964,126 885,66 806,6 704,-24 581,-24 426,-24 303,24 212,120 121,215 75,349 75,522 75,701 121,839 213,938 305,1037 424,1086 571,1086 713,1086 829,1038 919,941 1009,844 1054,708 1054,533 1054,522 1054,506 1053,485 L 261,485 C 268,368 301,279 360,217 419,155 493,124 582,124 648,124 704,141 751,176 798,211 835,266 862,342 Z M 271,633 L 864,633 C 856,722 833,789 796,834 739,903 664,938 573,938 490,938 421,910 365,855 308,800 277,726 271,633 Z"/>
<glyph unicode="d" horiz-adv-x="926" d="M 824,0 L 824,134 C 757,29 658,-24 527,-24 442,-24 365,-1 294,46 223,93 168,158 129,242 90,325 70,421 70,530 70,636 88,732 123,819 158,905 211,971 282,1017 353,1063 432,1086 519,1086 583,1086 640,1073 690,1046 740,1019 781,983 812,940 L 812,1466 991,1466 991,0 Z M 255,530 C 255,394 284,292 341,225 398,158 466,124 544,124 623,124 690,156 745,221 800,285 827,383 827,515 827,660 799,767 743,835 687,903 618,937 536,937 456,937 389,904 336,839 282,774 255,671 255,530 Z"/>
<glyph unicode="c" horiz-adv-x="926" d="M 828,389 L 1005,366 C 986,244 936,149 857,80 777,11 679,-24 563,-24 418,-24 301,24 213,119 124,214 80,350 80,527 80,642 99,742 137,828 175,914 233,979 311,1022 388,1065 473,1086 564,1086 679,1086 774,1057 847,999 920,940 967,857 988,750 L 813,723 C 796,794 767,848 725,884 682,920 631,938 571,938 480,938 407,906 350,841 293,776 265,673 265,532 265,389 292,286 347,221 402,156 473,124 561,124 632,124 691,146 738,189 785,232 815,299 828,389 Z"/>
<glyph unicode="b" horiz-adv-x="927" d="M 301,0 L 134,0 134,1466 314,1466 314,943 C 390,1038 487,1086 605,1086 670,1086 732,1073 791,1047 849,1020 897,983 935,936 972,888 1002,830 1023,763 1044,696 1055,624 1055,547 1055,365 1010,224 920,125 830,26 722,-24 596,-24 471,-24 372,28 301,133 Z M 299,539 C 299,412 316,320 351,263 408,170 484,124 581,124 660,124 728,158 785,227 842,295 871,397 871,532 871,671 844,773 789,839 734,905 667,938 589,938 510,938 442,904 385,836 328,767 299,668 299,539 Z"/>
<glyph unicode="a" horiz-adv-x="986" d="M 828,131 C 761,74 697,34 636,11 574,-12 508,-24 437,-24 320,-24 231,5 168,62 105,119 74,191 74,280 74,332 86,380 110,423 133,466 164,500 203,526 241,552 284,572 332,585 367,594 421,603 492,612 637,629 744,650 813,674 814,699 814,714 814,721 814,794 797,846 763,876 717,917 649,937 558,937 473,937 411,922 371,893 330,863 300,810 281,735 L 105,759 C 121,834 147,895 184,942 221,988 274,1024 343,1049 412,1074 493,1086 584,1086 675,1086 748,1075 805,1054 862,1033 903,1006 930,974 957,941 975,900 986,851 992,820 995,765 995,685 L 995,445 C 995,278 999,172 1007,128 1014,83 1029,41 1052,0 L 864,0 C 845,37 833,81 828,131 Z M 813,533 C 748,506 650,484 519,465 445,454 393,442 362,429 331,416 308,396 291,371 274,345 266,316 266,285 266,237 284,197 321,165 357,133 410,117 480,117 549,117 611,132 665,163 719,193 759,234 784,287 803,328 813,388 813,467 Z"/>
<glyph unicode="V" horiz-adv-x="1340" d="M 577,0 L 9,1466 219,1466 600,401 C 631,316 656,236 677,161 700,241 726,321 756,401 L 1152,1466 1350,1466 776,0 Z"/>
<glyph unicode="S" horiz-adv-x="1162" d="M 92,471 L 275,487 C 284,414 304,354 336,307 367,260 416,222 483,193 550,164 625,149 708,149 782,149 847,160 904,182 961,204 1003,234 1031,273 1058,311 1072,353 1072,398 1072,444 1059,484 1032,519 1005,553 961,582 900,605 861,620 774,644 639,677 504,709 410,739 356,768 286,805 234,850 200,905 165,959 148,1020 148,1087 148,1161 169,1230 211,1295 253,1359 314,1408 395,1441 476,1474 565,1491 664,1491 773,1491 869,1474 952,1439 1035,1404 1098,1352 1143,1284 1188,1216 1212,1139 1215,1053 L 1029,1039 C 1019,1132 985,1202 928,1249 870,1296 785,1320 672,1320 555,1320 469,1299 416,1256 362,1213 335,1161 335,1100 335,1047 354,1004 392,970 429,936 527,901 685,866 842,830 950,799 1009,772 1094,733 1157,683 1198,623 1239,562 1259,493 1259,414 1259,336 1237,263 1192,194 1147,125 1083,71 1000,33 916,-6 822,-25 717,-25 584,-25 473,-6 384,33 294,72 224,130 173,208 122,285 95,373 92,471 Z"/>
<glyph unicode="C" horiz-adv-x="1301" d="M 1204,514 L 1398,465 C 1357,306 1284,184 1179,101 1073,17 944,-25 791,-25 633,-25 505,7 406,72 307,136 231,229 180,351 128,473 102,604 102,744 102,897 131,1030 190,1144 248,1257 331,1344 439,1403 546,1462 665,1491 794,1491 941,1491 1064,1454 1164,1379 1264,1304 1334,1199 1373,1064 L 1182,1019 C 1148,1126 1099,1203 1034,1252 969,1301 888,1325 790,1325 677,1325 583,1298 508,1244 432,1190 379,1118 348,1027 317,936 302,842 302,745 302,620 320,512 357,419 393,326 449,256 526,210 603,164 686,141 775,141 884,141 976,172 1051,235 1126,298 1177,391 1204,514 Z"/>
<glyph unicode="&gt;" horiz-adv-x="986" d="M 1083,641 L 112,226 112,405 881,724 112,1040 112,1219 1083,809 Z"/>
<glyph unicode="&lt;" horiz-adv-x="986" d="M 112,641 L 112,809 1083,1219 1083,1040 313,724 1083,405 1083,226 Z"/>
<glyph unicode="-" horiz-adv-x="552" d="M 65,440 L 65,621 618,621 618,440 Z"/>
<glyph unicode=")" horiz-adv-x="474" d="M 253,-431 L 124,-431 C 323,-111 423,209 423,530 423,655 409,780 380,903 357,1003 326,1099 285,1191 259,1251 205,1351 124,1491 L 253,1491 C 378,1324 471,1156 531,987 582,842 608,690 608,531 608,351 574,177 505,9 436,-159 352,-306 253,-431 Z"/>
<glyph unicode="(" horiz-adv-x="474" d="M 479,-431 C 380,-306 296,-159 227,9 158,177 124,351 124,531 124,690 150,842 201,987 261,1156 354,1324 479,1491 L 608,1491 C 527,1352 474,1253 448,1194 407,1102 375,1006 352,906 323,781 309,656 309,530 309,209 409,-111 608,-431 Z"/>
<glyph unicode=" " horiz-adv-x="571"/>
</font>
</defs>
<defs>
<font id="EmbeddedFont_2" horiz-adv-x="2048">
<font-face font-family="Liberation Sans embedded" units-per-em="2048" font-weight="bold" font-style="normal" ascent="1851" descent="433"/>
<missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
<glyph unicode="v" horiz-adv-x="1104" d="M 439,0 L 11,1062 306,1062 506,520 564,339 C 579,385 589,415 593,430 602,460 612,490 623,520 L 825,1062 1114,1062 692,0 Z"/>
<glyph unicode="u" horiz-adv-x="966" d="M 846,0 L 846,159 C 807,102 757,58 694,25 631,-8 564,-24 494,-24 423,-24 359,-8 302,23 245,54 204,98 179,155 154,212 141,290 141,390 L 141,1062 422,1062 422,574 C 422,425 427,333 438,300 448,266 467,239 494,220 521,200 556,190 598,190 646,190 689,203 727,230 765,256 791,289 805,328 819,367 826,462 826,614 L 826,1062 1107,1062 1107,0 Z"/>
<glyph unicode="s" horiz-adv-x="986" d="M 48,303 L 330,346 C 342,291 366,250 403,222 440,193 491,179 557,179 630,179 684,192 721,219 746,238 758,263 758,294 758,315 751,333 738,347 724,360 693,373 644,384 417,434 274,480 213,521 129,578 87,658 87,760 87,852 123,929 196,992 269,1055 381,1086 534,1086 679,1086 787,1062 858,1015 929,968 977,898 1004,805 L 739,756 C 728,797 706,829 675,851 643,873 598,884 539,884 465,884 412,874 380,853 359,838 348,819 348,796 348,776 357,759 376,745 401,726 489,700 639,666 788,632 893,590 952,541 1011,491 1040,421 1040,332 1040,235 999,151 918,81 837,11 716,-24 557,-24 412,-24 298,5 214,64 129,123 74,202 48,303 Z"/>
<glyph unicode="r" horiz-adv-x="690" d="M 416,0 L 135,0 135,1062 396,1062 396,911 C 441,982 481,1029 517,1052 552,1075 593,1086 638,1086 702,1086 764,1068 823,1033 L 736,788 C 689,819 645,834 604,834 565,834 531,823 504,802 477,780 455,741 440,684 424,627 416,509 416,328 Z"/>
<glyph unicode="p" horiz-adv-x="1025" d="M 139,1062 L 401,1062 401,906 C 435,959 481,1003 539,1036 597,1069 661,1086 732,1086 855,1086 960,1038 1046,941 1132,844 1175,710 1175,537 1175,360 1132,222 1045,124 958,25 853,-24 730,-24 671,-24 618,-12 571,11 523,34 473,74 420,131 L 420,-404 139,-404 Z M 417,549 C 417,430 441,342 488,285 535,228 593,199 661,199 726,199 781,225 824,278 867,330 889,416 889,535 889,646 867,729 822,783 777,837 722,864 656,864 587,864 530,838 485,785 440,732 417,653 417,549 Z"/>
<glyph unicode="o" horiz-adv-x="1084" d="M 82,546 C 82,639 105,730 151,817 197,904 262,971 347,1017 431,1063 525,1086 629,1086 790,1086 921,1034 1024,930 1127,825 1178,693 1178,534 1178,373 1126,240 1023,135 919,29 788,-24 631,-24 534,-24 441,-2 353,42 264,86 197,151 151,236 105,321 82,424 82,546 Z M 370,531 C 370,426 395,345 445,289 495,233 557,205 630,205 703,205 765,233 815,289 864,345 889,426 889,533 889,637 864,717 815,773 765,829 703,857 630,857 557,857 495,829 445,773 395,717 370,636 370,531 Z"/>
<glyph unicode="n" horiz-adv-x="966" d="M 1113,0 L 832,0 832,542 C 832,657 826,731 814,765 802,798 783,824 756,843 729,862 696,871 658,871 609,871 566,858 527,831 488,804 462,769 448,725 433,681 426,600 426,481 L 426,0 145,0 145,1062 406,1062 406,906 C 499,1026 615,1086 756,1086 818,1086 875,1075 926,1053 977,1030 1016,1002 1043,967 1069,932 1087,893 1098,849 1108,805 1113,742 1113,660 Z"/>
<glyph unicode="l" horiz-adv-x="276" d="M 147,0 L 147,1466 428,1466 428,0 Z"/>
<glyph unicode="e" horiz-adv-x="986" d="M 762,338 L 1042,291 C 1006,188 949,110 872,57 794,3 697,-24 580,-24 395,-24 259,36 170,157 100,254 65,376 65,523 65,699 111,837 203,937 295,1036 411,1086 552,1086 710,1086 835,1034 926,930 1017,825 1061,665 1057,450 L 353,450 C 355,367 378,302 421,256 464,209 518,186 583,186 627,186 664,198 694,222 724,246 747,285 762,338 Z M 778,622 C 776,703 755,765 715,808 675,850 626,871 569,871 508,871 457,849 417,804 377,759 357,699 358,622 Z"/>
<glyph unicode="d" horiz-adv-x="1025" d="M 1121,0 L 860,0 860,156 C 817,95 766,50 707,21 648,-9 588,-24 528,-24 406,-24 302,25 215,124 128,222 84,359 84,535 84,715 126,852 211,946 296,1039 403,1086 532,1086 651,1086 753,1037 840,938 L 840,1466 1121,1466 Z M 371,554 C 371,441 387,359 418,308 463,235 527,198 608,198 673,198 728,226 773,281 818,336 841,418 841,527 841,649 819,737 775,791 731,844 675,871 606,871 539,871 484,845 439,792 394,739 371,659 371,554 Z"/>
<glyph unicode=" " horiz-adv-x="571"/>
</font>
</defs>
<g visibility="visible" id="MasterSlide_1_Default">
<desc>Master slide
</desc>
<rect fill="none" stroke="none" x="0" y="0" width="21000" height="29700"/>
</g>
<g visibility="visible" id="Slide_1_page1">
<g>
<path fill="rgb(238,238,238)" stroke="none" d="M 10500,29700 L 0,29700 0,0 21000,0 21000,29700 10500,29700 Z"/>
<path fill="none" stroke="rgb(52,101,164)" id="Drawing_1_0" stroke-linejoin="round" d="M 10500,29700 L 0,29700 0,0 21000,0 21000,29700 10500,29700 Z"/>
<rect fill="none" stroke="none" x="0" y="0" width="21001" height="29701"/>
</g>
<g>
<path fill="rgb(255,255,255)" stroke="none" d="M 10842,27162 L 2650,27162 2650,2224 19033,2224 19033,27162 10842,27162 Z"/>
<path fill="none" stroke="rgb(52,101,164)" id="Drawing_2_0" stroke-linejoin="round" d="M 10842,27162 L 2650,27162 2650,2224 19033,2224 19033,27162 10842,27162 Z"/>
<rect fill="none" stroke="none" x="2651" y="2224" width="16384" height="24939"/>
</g>
<g id="Drawing_3">
<path fill="rgb(255,255,255)" stroke="none" d="M 7245,1381 C 8613,1381 9658,1821 9658,2397 9658,2973 8613,3413 7245,3413 5877,3413 4832,2973 4832,2397 4832,1821 5877,1381 7245,1381 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-linejoin="round" d="M 7245,1381 C 8613,1381 9658,1821 9658,2397 9658,2973 8613,3413 7245,3413 5877,3413 4832,2973 4832,2397 4832,1821 5877,1381 7245,1381 Z"/>
<rect fill="none" stroke="none" x="4832" y="1381" width="4827" height="2033"/>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="494" font-style="normal" font-weight="400">
<text x="5473" y="2569">
<tspan x="5473 5719 5994 6133 6269 6680 6955 7230 7505 7615 7945 8220 8331 8601 8877">set modelValue </tspan></text>
</g>
</g>
<g id="Drawing_4">
<path fill="rgb(255,255,255)" stroke="none" d="M 10021,12655 C 9862,12655 9704,12813 9704,12972 L 9704,14243 C 9704,14402 9862,14561 10021,14561 L 14594,14561 C 14753,14561 14912,14402 14912,14243 L 14912,12972 C 14912,12813 14753,12655 14594,12655 L 10021,12655 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-linejoin="round" d="M 10021,12655 C 9862,12655 9704,12813 9704,12972 L 9704,14243 C 9704,14402 9862,14561 10021,14561 L 14594,14561 C 14753,14561 14912,14402 14912,14243 L 14912,12972 C 14912,12813 14753,12655 14594,12655 L 10021,12655 Z"/>
<rect fill="none" stroke="none" x="9705" y="12656" width="5208" height="1906"/>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="459" font-style="normal" font-weight="400">
<text x="10164" y="13255">
<tspan x="10164 10494 10752 11006 11235 11489 11646 11773 11900 12281 12535 12793 13047 13149 13453 13712 13813 14067 14321">Convert modelValue </tspan></text>
<text fill="rgb(0,0,0)" stroke="none" x="10432" y="13767">
<tspan x="10432 10559 10813 10944 11071 11325 11478 11859 12117 12244 12371 12625 12883 13188 13442 13544 13802 14056">to formattedValue </tspan></text>
<text fill="rgb(0,0,0)" stroke="none" x="10841" y="14279">
<tspan x="10841 10993 11146 11404 11658 11785 11912 12166 12318 12704 12958 13085 13212 13470 13622">(run formatter)</tspan></text>
</g>
</g>
<g id="Drawing_5">
<path fill="rgb(255,255,255)" stroke="none" d="M 9915,16509 C 9809,16509 9704,16614 9704,16720 L 9704,17568 C 9704,17674 9809,17780 9915,17780 L 14700,17780 C 14806,17780 14912,17674 14912,17568 L 14912,16720 C 14912,16614 14806,16509 14700,16509 L 9915,16509 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-linejoin="round" d="M 9915,16509 C 9809,16509 9704,16614 9704,16720 L 9704,17568 C 9704,17674 9809,17780 9915,17780 L 14700,17780 C 14806,17780 14912,17674 14912,17568 L 14912,16720 C 14912,16614 14806,16509 14700,16509 L 9915,16509 Z"/>
<rect fill="none" stroke="none" x="9705" y="16509" width="5208" height="1271"/>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="459" font-style="normal" font-weight="400">
<text x="10113" y="17047">
<tspan x="10113 10418 10651 10905 11133 11260 11387 11645 11798 12179 12433 12560 12691 12945 13199 13504 13762 13864 14118 14372">Sync formattedValue </tspan></text>
<text fill="rgb(0,0,0)" stroke="none" x="10674" y="17559">
<tspan x="10674 10801 11055 11186 11453 11555 11809 12067 12321 12448 12714 12841 13074 13328 13430 13684">to &lt;input&gt; value</tspan></text>
</g>
</g>
<g id="Drawing_6">
<path fill="rgb(255,255,255)" stroke="none" d="M 3276,8857 C 3095,8857 2914,9038 2914,9219 L 2914,10670 C 2914,10851 3095,11033 3276,11033 L 7759,11033 C 7940,11033 8121,10851 8121,10670 L 8121,9219 C 8121,9038 7940,8857 7759,8857 L 3276,8857 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-linejoin="round" d="M 3276,8857 C 3095,8857 2914,9038 2914,9219 L 2914,10670 C 2914,10851 3095,11033 3276,11033 L 7759,11033 C 7940,11033 8121,10851 8121,10670 L 8121,9219 C 8121,9038 7940,8857 7759,8857 L 3276,8857 Z"/>
<rect fill="none" stroke="none" x="2915" y="8858" width="5208" height="2176"/>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="459" font-style="normal" font-weight="400">
<text x="3374" y="9435">
<tspan x="3374 3704 3962 4216 4445 4699 4856 4983 5110 5491 5745 6003 6257 6359 6663 6922 7023 7277 7531">Convert modelValue </tspan></text>
<text fill="rgb(0,0,0)" stroke="none" x="3706" y="9947">
<tspan x="3706 3833 4087 4218 4447 4701 4853 4955 5213 5315 5416 5645 5899 6157 6462 6716 6818 7076">to serializedValue</tspan></text>
<text fill="rgb(0,0,0)" stroke="none" x="3987" y="10459"> </text>
<text fill="rgb(0,0,0)" stroke="none" x="4114" y="10459">
<tspan x="4114 4266 4419 4677 4931 5058 5287 5541 5697 5799 6053 6154 6256 6485 6743 6895">(run serializer)</tspan></text>
</g>
</g>
<g id="Drawing_7">
<path fill="rgb(255,255,255)" stroke="none" d="M 13722,1254 C 15090,1254 16135,1694 16135,2270 16135,2846 15090,3286 13722,3286 12354,3286 11309,2846 11309,2270 11309,1694 12354,1254 13722,1254 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-linejoin="round" d="M 13722,1254 C 15090,1254 16135,1694 16135,2270 16135,2846 15090,3286 13722,3286 12354,3286 11309,2846 11309,2270 11309,1694 12354,1254 13722,1254 Z"/>
<rect fill="none" stroke="none" x="11309" y="1254" width="4827" height="2033"/>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="494" font-style="normal" font-weight="400">
<text x="13324" y="2164">
<tspan x="13324 13570 13845 13984">set </tspan></text>
<text fill="rgb(0,0,0)" stroke="none" x="11978" y="2719">
<tspan x="11978 12224 12499 12664 12774 13049 13159 13269 13515 13790 14065 14395 14670 14780 15051 15327">serializedValue </tspan></text>
</g>
</g>
<g id="Drawing_8">
<path fill="rgb(255,255,255)" stroke="none" d="M 11438,5556 C 11246,5556 11055,5747 11055,5939 L 11055,7475 C 11055,7667 11246,7859 11438,7859 L 16022,7859 C 16214,7859 16406,7667 16406,7475 L 16406,5939 C 16406,5747 16214,5556 16022,5556 L 11438,5556 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-linejoin="round" d="M 11438,5556 C 11246,5556 11055,5747 11055,5939 L 11055,7475 C 11055,7667 11246,7859 11438,7859 L 16022,7859 C 16214,7859 16406,7667 16406,7475 L 16406,5939 C 16406,5747 16214,5556 16022,5556 L 11438,5556 Z"/>
<rect fill="none" stroke="none" x="11055" y="5556" width="5351" height="2303"/>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="459" font-style="normal" font-weight="400">
<text x="11187" y="6367">
<tspan x="11187 11517 11775 12029 12258 12512 12669 12796">Convert </tspan></text>
</g>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="494" font-style="normal" font-weight="400">
<text x="12923" y="6367">
<tspan x="12923 13169 13444 13609 13719 13994 14104 14214 14460 14735 15010 15340 15615 15725 15996">serializedValue</tspan></text>
</g>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="459" font-style="normal" font-weight="400">
<text x="12136" y="6888"> </text>
<text fill="rgb(0,0,0)" stroke="none" x="12263" y="6888">
<tspan x="12263 12390 12644 12775 13156 13410 13664 13922 14024 14329 14583 14689 14943 15197">to modelValue </tspan></text>
<text fill="rgb(0,0,0)" stroke="none" x="12009" y="7400">
<tspan x="12009 12161 12314 12572 12826 12953 13207 13465 13694 13948 14100 14202 14460 14562 14663 14892 15146 15298">(run deserializer)</tspan></text>
</g>
</g>
<g>
<path fill="rgb(114,159,207)" stroke="none" d="M 14821,12202 C 14647,12202 14503,12245 14503,12297 L 14503,12869 C 14503,12921 14647,12965 14821,12965 14994,12965 15139,12921 15139,12869 L 15139,12297 C 15139,12245 14994,12202 14821,12202 L 14821,12202 Z"/>
<path fill="none" stroke="rgb(52,101,164)" id="Drawing_9_0" stroke-linejoin="round" d="M 14821,12202 C 14647,12202 14503,12245 14503,12297 L 14503,12869 C 14503,12921 14647,12965 14821,12965 14994,12965 15139,12921 15139,12869 L 15139,12297 C 15139,12245 14994,12202 14821,12202 L 14821,12202 Z"/>
<path fill="rgb(165,195,226)" stroke="none" d="M 14821,12202 C 14647,12202 14503,12245 14503,12297 14503,12349 14647,12392 14821,12392 14994,12392 15139,12349 15139,12297 15139,12245 14994,12202 14821,12202 L 14821,12202 Z"/>
<path fill="none" stroke="rgb(52,101,164)" id="Drawing_9_1" stroke-linejoin="round" d="M 14821,12202 C 14647,12202 14503,12245 14503,12297 14503,12349 14647,12392 14821,12392 14994,12392 15139,12349 15139,12297 15139,12245 14994,12202 14821,12202 L 14821,12202 Z"/>
<rect fill="none" stroke="none" x="14504" y="12202" width="636" height="763"/>
</g>
<g>
<path fill="rgb(114,159,207)" stroke="none" d="M 16260,5148 C 16086,5148 15942,5191 15942,5243 L 15942,5815 C 15942,5867 16086,5911 16260,5911 16433,5911 16578,5867 16578,5815 L 16578,5243 C 16578,5191 16433,5148 16260,5148 L 16260,5148 Z"/>
<path fill="none" stroke="rgb(52,101,164)" id="Drawing_10_0" stroke-linejoin="round" d="M 16260,5148 C 16086,5148 15942,5191 15942,5243 L 15942,5815 C 15942,5867 16086,5911 16260,5911 16433,5911 16578,5867 16578,5815 L 16578,5243 C 16578,5191 16433,5148 16260,5148 L 16260,5148 Z"/>
<path fill="rgb(165,195,226)" stroke="none" d="M 16260,5148 C 16086,5148 15942,5191 15942,5243 15942,5295 16086,5338 16260,5338 16433,5338 16578,5295 16578,5243 16578,5191 16433,5148 16260,5148 L 16260,5148 Z"/>
<path fill="none" stroke="rgb(52,101,164)" id="Drawing_10_1" stroke-linejoin="round" d="M 16260,5148 C 16086,5148 15942,5191 15942,5243 15942,5295 16086,5338 16260,5338 16433,5338 16578,5295 16578,5243 16578,5191 16433,5148 16260,5148 L 16260,5148 Z"/>
<rect fill="none" stroke="none" x="15943" y="5148" width="636" height="763"/>
</g>
<g>
<path fill="rgb(114,159,207)" stroke="none" d="M 7931,8377 C 7757,8377 7613,8420 7613,8472 L 7613,9044 C 7613,9096 7757,9140 7931,9140 8104,9140 8249,9096 8249,9044 L 8249,8472 C 8249,8420 8104,8377 7931,8377 L 7931,8377 Z"/>
<path fill="none" stroke="rgb(52,101,164)" id="Drawing_11_0" stroke-linejoin="round" d="M 7931,8377 C 7757,8377 7613,8420 7613,8472 L 7613,9044 C 7613,9096 7757,9140 7931,9140 8104,9140 8249,9096 8249,9044 L 8249,8472 C 8249,8420 8104,8377 7931,8377 L 7931,8377 Z"/>
<path fill="rgb(165,195,226)" stroke="none" d="M 7931,8377 C 7757,8377 7613,8420 7613,8472 7613,8524 7757,8567 7931,8567 8104,8567 8249,8524 8249,8472 8249,8420 8104,8377 7931,8377 L 7931,8377 Z"/>
<path fill="none" stroke="rgb(52,101,164)" id="Drawing_11_1" stroke-linejoin="round" d="M 7931,8377 C 7757,8377 7613,8420 7613,8472 7613,8524 7757,8567 7931,8567 8104,8567 8249,8524 8249,8472 8249,8420 8104,8377 7931,8377 L 7931,8377 Z"/>
<rect fill="none" stroke="none" x="7614" y="8377" width="636" height="763"/>
</g>
<g>
<path fill="rgb(114,159,207)" stroke="none" d="M 14672,16134 C 14619,16134 14567,16165 14567,16197 L 14567,16245 14567,16292 14567,16357 14567,16404 14567,16452 C 14567,16484 14619,16516 14672,16516 L 14692,16929 14831,16516 14938,16516 15018,16516 15097,16516 C 15150,16516 15203,16484 15203,16452 L 15203,16404 15203,16357 15203,16292 15203,16245 15203,16197 C 15203,16165 15150,16134 15097,16134 L 15018,16134 14938,16134 14831,16134 14751,16134 14672,16134 Z"/>
<path fill="none" stroke="rgb(52,101,164)" id="Drawing_12_0" stroke-linejoin="round" d="M 14672,16134 C 14619,16134 14567,16165 14567,16197 L 14567,16245 14567,16292 14567,16357 14567,16404 14567,16452 C 14567,16484 14619,16516 14672,16516 L 14692,16929 14831,16516 14938,16516 15018,16516 15097,16516 C 15150,16516 15203,16484 15203,16452 L 15203,16404 15203,16357 15203,16292 15203,16245 15203,16197 C 15203,16165 15150,16134 15097,16134 L 15018,16134 14938,16134 14831,16134 14751,16134 14672,16134 Z"/>
<rect fill="none" stroke="none" x="14568" y="16135" width="636" height="382"/>
</g>
<g id="Drawing_13">
<rect fill="none" stroke="none" x="8820" y="619" width="3684" height="1266"/>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="635" font-style="normal" font-weight="700">
<text x="9070" y="1320">
<tspan x="9070 9459 9811 10162 10518 10696 11081 11470 11822 12071">developer </tspan></text>
</g>
</g>
<g id="Drawing_14">
<rect fill="none" stroke="none" x="9509" y="27996" width="3684" height="1801"/>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="635" font-style="normal" font-weight="700">
<text x="9759" y="28697">
<tspan x="9759 10110 10500 10889 11063 11452 11804 12159 12405">end user </tspan></text>
</g>
</g>
<g id="Drawing_15">
<path fill="rgb(255,255,255)" stroke="none" d="M 7669,26011 C 9037,26011 10082,26451 10082,27027 10082,27603 9037,28043 7669,28043 6301,28043 5256,27603 5256,27027 5256,26451 6301,26011 7669,26011 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-linejoin="round" d="M 7669,26011 C 9037,26011 10082,26451 10082,27027 10082,27603 9037,28043 7669,28043 6301,28043 5256,27603 5256,27027 5256,26451 6301,26011 7669,26011 Z"/>
<rect fill="none" stroke="none" x="5256" y="26011" width="4827" height="2033"/>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="494" font-style="normal" font-weight="400">
<text x="5732" y="27199">
<tspan x="5732 5978 6253 6502 6773 7049 7408 7684 7819 8107 8217 8492 8767 9042 9178 9466">keydown &lt;input&gt; </tspan></text>
</g>
</g>
<g id="Drawing_16">
<path fill="rgb(255,255,255)" stroke="none" d="M 5274,19796 C 5168,19796 5063,19901 5063,20007 L 5063,20855 C 5063,20961 5168,21067 5274,21067 L 10059,21067 C 10165,21067 10271,20961 10271,20855 L 10271,20007 C 10271,19901 10165,19796 10059,19796 L 5274,19796 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-linejoin="round" d="M 5274,19796 C 5168,19796 5063,19901 5063,20007 L 5063,20855 C 5063,20961 5168,21067 5274,21067 L 10059,21067 C 10165,21067 10271,20961 10271,20855 L 10271,20007 C 10271,19901 10165,19796 10059,19796 L 5274,19796 Z"/>
<rect fill="none" stroke="none" x="5064" y="19796" width="5208" height="1271"/>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="459" font-style="normal" font-weight="400">
<text x="5459" y="20334">
<tspan x="5459 5764 5997 6251 6479 6606 6873 6979 7233 7487 7741 7872 8139 8266 8494 8753 8854 9108 9362 9489 9616">Sync &lt;input&gt; value to</tspan></text>
<text fill="rgb(0,0,0)" stroke="none" x="6390" y="20846"> </text>
<text fill="rgb(0,0,0)" stroke="none" x="6517" y="20846">
<tspan x="6517 6898 7156 7410 7664 7766 8075 8329 8430 8684">modelValue</tspan></text>
</g>
</g>
<g id="Drawing_17">
<path fill="rgb(255,255,255)" stroke="none" d="M 14754,25811 C 16122,25811 17167,26251 17167,26827 17167,27403 16122,27843 14754,27843 13386,27843 12341,27403 12341,26827 12341,26251 13386,25811 14754,25811 Z"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-linejoin="round" d="M 14754,25811 C 16122,25811 17167,26251 17167,26827 17167,27403 16122,27843 14754,27843 13386,27843 12341,27403 12341,26827 12341,26251 13386,25811 14754,25811 Z"/>
<rect fill="none" stroke="none" x="12341" y="25811" width="4827" height="2033"/>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="494" font-style="normal" font-weight="400">
<text x="13381" y="26999">
<tspan x="13381 13656 13766 14041 14207 14342 14630 14740 15015 15290 15565 15701 15989">blur &lt;input&gt; </tspan></text>
</g>
</g>
<g>
<path fill="none" stroke="rgb(0,0,0)" id="Drawing_18_0" stroke-linejoin="round" d="M 13730,7858 L 13730,10652 12308,10652 12308,12206"/>
<path fill="rgb(0,0,0)" stroke="none" id="Drawing_18_1" d="M 12308,12656 L 12458,12206 12158,12206 12308,12656 Z"/>
</g>
<g>
<path fill="none" stroke="rgb(0,0,0)" id="Drawing_19_0" stroke-linejoin="round" d="M 13722,3286 L 13722,4421 13730,4421 13730,5106"/>
<path fill="rgb(0,0,0)" stroke="none" id="Drawing_19_1" d="M 13730,5556 L 13880,5106 13580,5106 13730,5556 Z"/>
</g>
<g>
<path fill="none" stroke="rgb(0,0,0)" id="Drawing_20_0" stroke-linejoin="round" d="M 17167,26827 L 17668,26827 17668,13608 15362,13608"/>
<path fill="rgb(0,0,0)" stroke="none" id="Drawing_20_1" d="M 14912,13608 L 15362,13758 15362,13458 14912,13608 Z"/>
</g>
<g>
<path fill="none" stroke="rgb(0,0,0)" id="Drawing_21_0" stroke-linejoin="round" d="M 7669,26011 L 7669,23539 7667,23539 7667,21516"/>
<path fill="rgb(0,0,0)" stroke="none" id="Drawing_21_1" d="M 7667,21066 L 7517,21516 7817,21516 7667,21066 Z"/>
</g>
<g>
<path fill="none" stroke="rgb(0,0,0)" id="Drawing_22_0" stroke-linejoin="round" d="M 8952,3116 L 8952,8620 12308,8620 12308,12206"/>
<path fill="rgb(0,0,0)" stroke="none" id="Drawing_22_1" d="M 12308,12656 L 12458,12206 12158,12206 12308,12656 Z"/>
</g>
<g>
<path fill="none" stroke="rgb(0,0,0)" id="Drawing_23_0" stroke-linejoin="round" d="M 5538,3116 L 5538,6135 5518,6135 5518,8408"/>
<path fill="rgb(0,0,0)" stroke="none" id="Drawing_23_1" d="M 5518,8858 L 5668,8408 5368,8408 5518,8858 Z"/>
</g>
<g>
<path fill="none" stroke="rgb(0,0,0)" id="Drawing_24_0" stroke-linejoin="round" d="M 12308,14561 L 12308,16059"/>
<path fill="rgb(0,0,0)" stroke="none" id="Drawing_24_1" d="M 12308,16509 L 12458,16059 12158,16059 12308,16509 Z"/>
</g>
<g>
<path fill="none" stroke="rgb(0,0,0)" id="Drawing_25_0" stroke-linejoin="round" d="M 7705,19796 L 7705,15665 5518,15665 5518,11483"/>
<path fill="rgb(0,0,0)" stroke="none" id="Drawing_25_1" d="M 5518,11033 L 5368,11483 5668,11483 5518,11033 Z"/>
</g>
<g id="Drawing_26">
<rect fill="none" stroke="none" x="2850" y="3031" width="1675" height="3812"/>
<g fill="rgb(0,0,0)" stroke="none" font-family="Liberation Sans embedded" font-size="635" font-style="normal" font-weight="400">
<g transform="translate(3552,6592) rotate(-90) translate(-3552,-6592)">
<text x="3552" y="6592">
<tspan x="3552 3925 4064 4204 4560 4911 5123 5300 5440 5791 5935 6287">&lt;lion-field&gt;</tspan></text>
</g>
</g>
</g>
<g>
<path fill="none" stroke="rgb(0,0,0)" id="Drawing_27_0" stroke-linejoin="round" d="M 7705,19796 L 7718,19796 7718,13608 9255,13608"/>
<path fill="rgb(0,0,0)" stroke="none" id="Drawing_27_1" d="M 9705,13608 L 9255,13458 9255,13758 9705,13608 Z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 35 KiB

View file

@ -1,69 +0,0 @@
# ModelValue
The modelValue or model can be considered as the aorta of our form system.
It is the single source of truth; not only for the current state
of the form, also for all derived states: interaction, validation, visibility and other states are
computed from a modelValue change.
## Single source of truth
ModelValues are designed to provide the Application Developer a single way of programmatical
interaction with the form for an Application Developer.
### One single concept for Application Developers
Application Developers need to only care about interacting with the modelValue on a form control
level, via:
- `.modelValue`
- `@model-value-changed`
> Internal/private concepts like viewValue, formattedValue, serializedValue are therefore not
> recommended as a means of interaction.
### One single concept for internals
Internally, all derived states are computed from model-value-changed events.
Since the modelValue is computed 'realtime' and reflects all user interaction, visibility and
validation states, we can guarantee a system that enables the best User Experience
(see Interaction States).
## Unparseable modelValues
A modelValue can demand a certain type (Date, Number, Iban etc.). A correct type will always be
translatable into a String representation (the value presented to the end user) via the `formatter`.
When the type is not valid (usually as a consequence of a user typing in an invalid or incomplete
viewValue), the current truth is captured in the `Unparseable` type.
For example: a viewValue can't be parsed (for instance 'foo' when the type should be Number).
The model(value) concept as implemented in lion-web is conceptually comparable to those found in
popular frameworks like Angular and Vue.
The Unparseable type is an addition on top of this that mainly is added for the following two
purposes:
- restoring user sessions
- realtime updated with all value changes
### Restoring user sessions
As a modelValue is always a single source of truth
### Realtime updated with all value changes
As an Application Developer, you will be notified when a user tries to write the correct type of
a value. This might be handy for giving feedback to the user.
In order to check whether the input is correct, an Application Developer can do the following:
```html
<lion-input @model-value-changed="${handleChange}"></lion-input>
```
```js
function handleChange({ target: { modelValue, hasFeedbackFor } }) {
if (!(modelValue instanceof Unparseable) && !(hasFeedbackFor.include('error))) {
// do my thing
}
}
```

View file

@ -38,7 +38,7 @@
},
"devDependencies": {
"@lion/localize": "0.7.2",
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4",
"sinon": "^7.2.2"
}

View file

@ -4,18 +4,9 @@
`lion-fieldset` groups multiple input fields or other fieldsets together.
Two specific types of fieldsets:
## Live Demo/Documentation
- [lion-checkbox-group](../checkbox-group/)
- [lion-radio-group](../radio-group/)
## Features
- easy retrieval of form data based on field names
- advanced user interaction scenarios via [interaction states](../field/docs/InteractionStates.md)
- can have [validate](../validate/) on fieldset level and shows the validation feedback below the fieldset
- can disable input fields on fieldset level
- accessible out of the box
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-fieldset) for a live demo and documentation
## How to use
@ -35,9 +26,5 @@ import '@lion/input/lion-input.js';
```html
<lion-fieldset name="personalia" label="personalia">
<lion-input name="title" label="Title"></lion-input>
<lion-fieldset name="fullName" label="Full name" .validations="${[new Required()]}">
<lion-input name="firstName" label="First name"></lion-input>
<lion-input name="lastName" label="Last name"></lion-input>
</lion-fieldset>
</lion-fieldset>
```

View file

@ -1,8 +1,8 @@
import { SlotMixin, html, LitElement } from '@lion/core';
import { html, LitElement, SlotMixin } from '@lion/core';
import { DisabledMixin } from '@lion/core/src/DisabledMixin.js';
import { ValidateMixin } from '@lion/validate';
import { FormControlMixin, FormRegistrarMixin } from '@lion/field';
import { getAriaElementsInRightDomOrder } from '@lion/field/src/utils/getAriaElementsInRightDomOrder.js';
import { ValidateMixin } from '@lion/validate';
import { FormElementsHaveNoError } from './FormElementsHaveNoError.js';
/**
@ -314,7 +314,7 @@ export class LionFieldset extends FormRegistrarMixin(
* Gets triggered by event 'validate-performed' which enabled us to handle 2 different situations
* - react on modelValue change, which says something about the validity as a whole
* (at least two checkboxes for instance) and nothing about the children's values
* - children validatity states have changed, so fieldset needs to update itself based on that
* - children validity states have changed, so fieldset needs to update itself based on that
*/
__validate(ev) {
if (ev && this.isRegisteredFormElement(ev.target)) {

View file

@ -0,0 +1,13 @@
import { LionField } from '@lion/field';
customElements.define(
'demo-fieldset-child',
class extends LionField {
get slots() {
return {
...super.slots,
input: () => document.createElement('input'),
};
}
},
);

View file

@ -1,205 +0,0 @@
import '@lion/input/lion-input.js';
import { localize } from '@lion/localize';
import { loadDefaultFeedbackMessages, MinLength, Validator } from '@lion/validate';
import { html, storiesOf } from '@open-wc/demoing-storybook';
import '../../form-system/stories/helper-wc/h-output.js';
import '../lion-fieldset.js';
localize.locale = 'en-GB';
loadDefaultFeedbackMessages();
storiesOf('Forms|Fieldset')
.add(
'Default',
() => html`
<p>
A native fieldset element should always have a legend-element for a11y purposes. Our
fieldset element is not native and should not have a legend-element. Our fieldset instead
has a label attribute or you can add a label with a div- or heading-element using the
slot="label". Please don't use the the label-element because that is reserved for
input-elements.
</p>
<lion-fieldset name="nameGroup" label="Name">
<lion-input name="FirstName" label="First Name"></lion-input>
<lion-input name="LastName" label="Last Name"></lion-input>
</lion-fieldset>
`,
)
.add(
'Data',
() => html`
<lion-fieldset name="nameGroup" label="Name">
<lion-input name="FirstName" label="First Name" .modelValue=${'Foo'}></lion-input>
<lion-input name="LastName" label="Last Name" .modelValue=${'Bar'}></lion-input>
<button @click=${ev => console.log(ev.target.parentElement.modelValue)}>
Log to Action Logger
</button>
</lion-fieldset>
`,
)
.add('Disabled', () => {
function toggleDisabled() {
const fieldset = document.querySelector('#fieldset');
fieldset.disabled = !fieldset.disabled;
}
return html`
<lion-fieldset name="nameGroup" label="Name" id="fieldset" disabled>
<lion-input name="FirstName" label="First Name" .modelValue=${'Foo'}></lion-input>
<lion-input name="LastName" label="Last Name" .modelValue=${'Bar'}></lion-input>
<lion-fieldset name="nameGroup2" label="Name">
<lion-input
name="FirstName2"
label="First Name"
.modelValue=${'Foo'}
disabled
></lion-input>
<lion-input name="LastName2" label="Last Name" .modelValue=${'Bar'}></lion-input>
</lion-fieldset>
</lion-fieldset>
<button @click=${toggleDisabled}>
Toggle disabled
</button>
`;
})
.add(
'Sub Fieldsets Data',
() => html`
<lion-fieldset>
<div slot="label">Personal data</div>
<lion-fieldset name="nameGroup" label="Name">
<lion-input name="FirstName" label="First Name" .modelValue=${'Foo'}></lion-input>
<lion-input name="LastName" label="Last Name" .modelValue=${'Bar'}></lion-input>
</lion-fieldset>
<lion-fieldset name="location" label="Location">
<lion-input name="country" label="Country" .modelValue=${'Netherlands'}></lion-input>
</lion-fieldset>
<lion-input name="age" label="Age" .modelValue=${21}></lion-input>
<button @click=${ev => console.log(ev.target.parentElement.modelValue)}>
Log to Action Logger
</button>
<br />
<button
@click=${ev => console.log(ev.target.parentElement.formElements.nameGroup.modelValue)}
>
Log nameGroup to Action Logger
</button>
</lion-fieldset>
`,
)
.add('Validation', () => {
const DemoValidator = class extends Validator {
constructor() {
super();
this.name = 'DemoValidator';
}
execute(value) {
if (value && value.input1) {
return true; // el.hasError = true
}
return false;
}
static async getMessage() {
return '[Fieldset Error] Demo error message';
}
};
return html`
<lion-fieldset id="someId" .validators="${[new DemoValidator()]}">
<lion-input name="input1" label="Label"> </lion-input>
</lion-fieldset>
`;
})
.add('Validation 2 inputs', () => {
const IsCatsAndDogs = class extends Validator {
constructor() {
super();
this.name = 'IsCatsAndDogs';
}
execute(value) {
if (!(value && value.input1 && value.input2)) {
return false;
}
return !(value.input1 === 'cats' && value.input2 === 'dogs');
}
static async getMessage() {
return '[Fieldset Error] Input 1 needs to be "cats" and Input 2 needs to be "dogs"';
}
};
return html`
<lion-fieldset .validators="${[new IsCatsAndDogs()]}">
<lion-input
label="An all time YouTube favorite"
name="input1"
help-text="longer than 2 characters"
.validators="${[new MinLength(3)]}"
>
</lion-input>
<lion-input
label="Another all time YouTube favorite"
name="input2"
help-text="longer than 2 characters"
.validators="${[new MinLength(3)]}"
>
</lion-input>
</lion-fieldset>
`;
})
.add('Validation 2 fields', () => {
const IsCats = class extends Validator {
constructor() {
super();
this.name = 'IsCats';
}
execute(value) {
return value.input1 !== 'cats';
}
static async getMessage() {
return '[Fieldset Nr. 1 Error] Input 1 needs to be "cats"';
}
};
const IsDogs = class extends Validator {
constructor() {
super();
this.name = 'IsDogs';
}
execute(value) {
return value.input1 !== 'dogs';
}
static async getMessage() {
return '[Fieldset Nr. 2 Error] Input 1 needs to be "dogs"';
}
};
return html`
<lion-fieldset .validators="${[new IsCats()]}">
<label slot="label">Fieldset no. 1</label>
<lion-input
label="An all time YouTube favorite"
name="input1"
help-text="longer than 2 characters"
.validators="${[new MinLength(3)]}"
>
</lion-input>
</lion-fieldset>
<hr />
<lion-fieldset .validators="${[new IsDogs()]}">
<label slot="label">Fieldset no. 2</label>
<lion-input
label="An all time YouTube favorite"
name="input1"
help-text="longer than 2 characters"
.validators="${[new MinLength(3)]}"
>
</lion-input>
</lion-fieldset>
`;
});

View file

@ -0,0 +1,417 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import '@lion/input/lion-input.js';
import { localize } from '@lion/localize';
import { loadDefaultFeedbackMessages, MinLength, Validator, Required } from '@lion/validate';
import '../lion-fieldset.js';
import './helpers/demo-fieldset-child.js';
<Meta title="Forms/Fieldset" parameters={{ component: 'lion-fieldset' }} />
# Fieldset
`lion-fieldset` groups multiple input fields or other fieldsets together.
We have three specific fieldset implementations:
- [lion-form](?path=/docs/forms-form)
- [lion-checkbox-group](?path=/docs/forms-checkbox-group)
- [lion-radio-group](?path=/docs/forms-radio-group)
<Story name="Default">
{html`
<style>
lion-fieldset {
margin-top: 20px;
}
</style>
<lion-fieldset name="nameGroup" label="Name">
<demo-fieldset-child name="FirstName" label="First Name"></demo-fieldset-child>
<demo-fieldset-child name="LastName" label="Last Name"></demo-fieldset-child>
</lion-fieldset>
`}
</Story>
```html
<lion-fieldset name="nameGroup" label="Name">
<demo-fieldset-child name="FirstName" label="First Name"></demo-fieldset-child>
<demo-fieldset-child name="LastName" label="Last Name"></demo-fieldset-child>
</lion-fieldset>
```
A native fieldset element should always have a legend-element for a11y purposes.
However, our fieldset element is not native and should not have a legend-element.
Our fieldset instead has a label attribute or you can add a label with a div- or heading-element using `slot="label"`.
## Features
- Easy retrieval of form data based on field names
- Advanced user interaction scenarios via [interaction states](?path=/docs/forms-system-interaction-states)
- Can have [validate](?path=/docs/forms-system-validate-system) on fieldset level and shows the validation feedback below the fieldset
- Can disable input fields on fieldset level
- Accessible out of the box
## Examples
### With Data
The fieldset's modelValue is an `Object` containing properties where the key is the `name` attribute of the field,
and the value is the `modelValue` of the field.
<Story name="Data">
{html`
<lion-fieldset name="nameGroup" label="Name">
<demo-fieldset-child name="firstName" label="First Name" .modelValue=${'Foo'}></demo-fieldset-child>
<demo-fieldset-child name="lastName" label="Last Name" .modelValue=${'Bar'}></demo-fieldset-child>
<button @click=${ev => console.log(ev.target.parentElement.modelValue)}>
Log to Action Logger
</button>
</lion-fieldset>
`}
</Story>
```html
<lion-fieldset name="nameGroup" label="Name">
<demo-fieldset-child name="firstName" label="First Name" .modelValue=${'Foo'}></demo-fieldset-child>
<demo-fieldset-child name="lastName" label="Last Name" .modelValue=${'Bar'}></demo-fieldset-child>
<button @click=${ev => console.log(ev.target.parentElement.modelValue)}>
Log to Action Logger
</button>
</lion-fieldset>
```
### Disabled
Disabling a fieldset disables all its child fields.
When enabling a fieldset, fields that have disabled explicitly set will stay disabled.
<Story name="Disabled">
{() => {
function toggleDisabled() {
const fieldset = document.querySelector('#fieldset');
fieldset.disabled = !fieldset.disabled;
}
return html`
<lion-fieldset name="nameGroup" label="Name" id="fieldset" disabled>
<demo-fieldset-child name="FirstName" label="First Name" .modelValue=${'Foo'}></demo-fieldset-child>
<demo-fieldset-child name="LastName" label="Last Name" .modelValue=${'Bar'}></demo-fieldset-child>
<lion-fieldset name="nameGroup2" label="Name">
<demo-fieldset-child
name="FirstName2"
label="First Name"
.modelValue=${'Foo'}
disabled
></demo-fieldset-child>
<demo-fieldset-child name="LastName2" label="Last Name" .modelValue=${'Bar'}></demo-fieldset-child>
</lion-fieldset>
</lion-fieldset>
<button @click=${toggleDisabled}>
Toggle disabled
</button>
`;
}}
</Story>
```js
function toggleDisabled() {
const fieldset = document.querySelector('#fieldset');
fieldset.disabled = !fieldset.disabled;
}
```
```html
<lion-fieldset name="nameGroup" label="Name" id="fieldset" disabled>
<demo-fieldset-child name="FirstName" label="First Name" .modelValue=${'Foo'}></demo-fieldset-child>
<demo-fieldset-child name="LastName" label="Last Name" .modelValue=${'Bar'}></demo-fieldset-child>
<lion-fieldset name="nameGroup2" label="Name">
<demo-fieldset-child
name="FirstName2"
label="First Name"
.modelValue=${'Foo'}
disabled
></demo-fieldset-child>
<demo-fieldset-child name="LastName2" label="Last Name" .modelValue=${'Bar'}></demo-fieldset-child>
</lion-fieldset>
</lion-fieldset>
<button @click=${toggleDisabled}>
Toggle disabled
</button>
```
### Nesting fieldsets
Fieldsets can also be nested. The level of nesting will correspond one to one with the `modelValue` object.
<Story name="Nesting fieldsets">
{html`
<lion-fieldset>
<div slot="label">Personal data</div>
<lion-fieldset name="nameGroup" label="Name">
<demo-fieldset-child name="FirstName" label="First Name" .modelValue=${'Foo'}></demo-fieldset-child>
<demo-fieldset-child name="LastName" label="Last Name" .modelValue=${'Bar'}></demo-fieldset-child>
</lion-fieldset>
<lion-fieldset name="location" label="Location">
<demo-fieldset-child name="country" label="Country" .modelValue=${'Netherlands'}></demo-fieldset-child>
</lion-fieldset>
<demo-fieldset-child name="age" label="Age" .modelValue=${21}></demo-fieldset-child>
<button @click=${ev => console.log(ev.target.parentElement.modelValue)}>
Log everything to Action Logger
</button>
<br />
<button
@click=${ev => console.log(ev.target.parentElement.formElements.nameGroup.modelValue)}
>
Log only Name fieldset to Action Logger
</button>
</lion-fieldset>
`}
</Story>
```html
<lion-fieldset>
<div slot="label">Personal data</div>
<lion-fieldset name="nameGroup" label="Name">
<demo-fieldset-child name="FirstName" label="First Name" .modelValue=${'Foo'}></demo-fieldset-child>
<demo-fieldset-child name="LastName" label="Last Name" .modelValue=${'Bar'}></demo-fieldset-child>
</lion-fieldset>
<lion-fieldset name="location" label="Location">
<demo-fieldset-child name="country" label="Country" .modelValue=${'Netherlands'}></demo-fieldset-child>
</lion-fieldset>
<demo-fieldset-child name="age" label="Age" .modelValue=${21}></demo-fieldset-child>
<button @click=${ev => console.log(ev.target.parentElement.modelValue)}>
Log everything to Action Logger
</button>
<br />
<button
@click=${ev => console.log(ev.target.parentElement.formElements.nameGroup.modelValue)}
>
Log only Name fieldset to Action Logger
</button>
</lion-fieldset>
```
## Validation
You can create validators that work on a fieldset level.
Below, we mimic a `required` validator, but on the fieldset.
Try it by typing something in the input, then removing it.
<Story name="Validation">
{() => {
const DemoValidator = class extends Validator {
constructor() {
super();
this.name = 'DemoValidator';
}
execute(value) {
if (value && value.input1) {
return false; // el.hasError = true
}
return true;
}
static async getMessage() {
return '[Fieldset Error] Demo error message';
}
};
return html`
<lion-fieldset id="someId" .validators="${[new DemoValidator()]}">
<demo-fieldset-child name="input1" label="Label"></demo-fieldset-child>
</lion-fieldset>
`;
}}
</Story>
```js
const DemoValidator = class extends Validator {
constructor() {
super();
this.name = 'DemoValidator';
}
execute(value) {
if (value && value.input1) {
return false;
}
return true;
}
static async getMessage() {
return '[Fieldset Error] Demo error message';
}
};
```
```html
<lion-fieldset id="someId" .validators="${[new DemoValidator()]}">
<demo-fieldset-child name="input1" label="Label"></demo-fieldset-child>
</lion-fieldset>
```
### Validating multiple inputs in a fieldset
You can have your fieldset validator take into consideration multiple fields.
<Story name="Validating 2 fields">
{() => {
const IsCatsAndDogs = class extends Validator {
constructor() {
super();
this.name = 'IsCatsAndDogs';
}
execute(value) {
return !(value.input1 === 'cats' && value.input2 === 'dogs');
}
static async getMessage() {
return '[Fieldset Error] Input 1 needs to be "cats" and Input 2 needs to be "dogs"';
}
};
return html`
<lion-fieldset .validators="${[new IsCatsAndDogs()]}">
<demo-fieldset-child
label="An all time YouTube favorite"
name="input1"
help-text="cats"
>
</demo-fieldset-child>
<demo-fieldset-child
label="Another all time YouTube favorite"
name="input2"
help-text="dogs"
>
</demo-fieldset-child>
</lion-fieldset>
`;
}}
</Story>
```js
const IsCatsAndDogs = class extends Validator {
constructor() {
super();
this.name = 'IsCatsAndDogs';
}
execute(value) {
return !(value.input1 === 'cats' && value.input2 === 'dogs');
}
static async getMessage() {
return '[Fieldset Error] Input 1 needs to be "cats" and Input 2 needs to be "dogs"';
}
};
```
```html
<lion-fieldset .validators="${[new IsCatsAndDogs()]}">
<demo-fieldset-child
label="An all time YouTube favorite"
name="input1"
help-text="cats"
>
</demo-fieldset-child>
<demo-fieldset-child
label="Another all time YouTube favorite"
name="input2"
help-text="dogs"
>
</demo-fieldset-child>
</lion-fieldset>
```
Alternatively you can also let the fieldset validator be dependent on the error states of its child fields.
Simply loop over the formElements inside your Validator's `execute` method:
```js
this.formElementsArray.some(el => el.hasFeedbackFor.includes('error'));
```
### Validating multiple fieldsets
You can have your fieldset validator take into accounts multiple nested fieldsets.
<Story name="Validating 2 fieldsets">
{() => {
const IsCatsDogs = class extends Validator {
constructor() {
super();
this.name = 'IsCatsDogs';
}
execute(value) {
if ((value.inner1 && value.inner1.input1 === 'cats') &&
(value.inner2 && value.inner2.input1 === 'dogs')
) {
return false;
}
return true;
}
static async getMessage() {
return 'There is a problem with one of your fieldsets';
}
};
return html`
<lion-fieldset name="outer" .validators=${[new IsCatsDogs()]}>
<lion-fieldset name="inner1">
<label slot="label">Fieldset no. 1</label>
<demo-fieldset-child
label="Write 'cats' here"
name="input1"
>
</demo-fieldset-child>
</lion-fieldset>
<hr />
<lion-fieldset name="inner2">
<label slot="label">Fieldset no. 2</label>
<demo-fieldset-child
label="Write 'dogs' here"
name="input1"
>
</demo-fieldset-child>
</lion-fieldset>
</lion-fieldset>
`;
}}
</Story>
```js
const IsCatsDogs = class extends Validator {
constructor() {
super();
this.name = 'IsCatsDogs';
}
execute(value) {
if ((value.inner1 && value.inner1.input1 === 'cats') &&
(value.inner2 && value.inner2.input1 === 'dogs')
) {
return false;
}
return true;
}
static async getMessage() {
return 'There is a problem with one of your fieldsets';
}
};
```
```html
<lion-fieldset name="outer" .validators="${[new IsCatsDogs()]}">
<lion-fieldset name="inner1">
<label slot="label">Fieldset no. 1</label>
<demo-fieldset-child
label="Write 'cats' here"
name="input1"
>
</demo-fieldset-child>
</lion-fieldset>
<hr />
<lion-fieldset name="inner2">
<label slot="label">Fieldset no. 2</label>
<demo-fieldset-child
label="Write 'dogs' here"
name="input1"
>
</demo-fieldset-child>
</lion-fieldset>
</lion-fieldset>
```

View file

@ -6,10 +6,9 @@ The Form System allows you to create complex forms with various validation in an
## Features
- built in [validate](../validate) for error/warning/info/success
- formatting of values
- accessible
- ...
- Built in [validate](../validate) for error/warning/info/success
- Formatting of values
- Accessible
## Packages

View file

@ -49,7 +49,7 @@
"@lion/radio-group": "0.4.5",
"@lion/textarea": "0.4.2",
"@lion/validate": "0.5.4",
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4"
}
}

View file

@ -0,0 +1,32 @@
<Meta title="Forms/Intro" />
# Form System
The Form System allows you to create complex forms with various validations in an easy way.
## Features
- Built in [validate](?path=/docs/forms-system-validate-system) for error/warning/info/success
- Formatting of values
- Accessible
For a more in depth description look into the [Form System Overview](?path=/docs/forms-system-overview--page).
## Packages
| Package | Description |
| ---------------------------------------------------- | ---------------------------------- |
| [checkbox](?path=/docs/forms-checkbox--default-story) | Checkbox form element |
| [checkbox-group](?path=/docs/forms-checkbox-group--default-story) | Group of checkboxes |
| [field](?path=/docs/forms-field--default-story) | Base class for all inputs |
| [form](?path=/docs/forms-form--default-story) | Wrapper for multiple form elements |
| [input](?path=/docs/forms-input--default-story) | Input element for strings |
| [input-amount](?path=/docs/forms-input-amount--default-story) | Input element for amounts |
| [input-date](?path=/docs/forms-input-date--default-story) | Input element for dates |
| [input-email](?path=/docs/forms-input-email--default-story) | Input element for e-mails |
| [input-iban](?path=/docs/forms-input-iban--default-story) | Input element for IBANs |
| [radio](?path=/docs/forms-radio--default-story) | Radio form element |
| [radio-group](?path=/docs/forms-radio-group--default-story) | Group of radios |
| [select](?path=/docs/forms-select--default-story) | Simple native dropdown element |
| [textarea](?path=/docs/forms-textarea--default-story) | Multiline text input |
| [validate](?path=/docs/forms-system-validate-system--default-story) | Validation for our form components |

View file

@ -0,0 +1,125 @@
import { Story, Preview, Meta, html } from '@open-wc/demoing-storybook';
import '@lion/checkbox-group/lion-checkbox-group.js';
import '@lion/checkbox/lion-checkbox.js';
import '@lion/fieldset/lion-fieldset.js';
import '@lion/form/lion-form.js';
import '@lion/input-amount/lion-input-amount.js';
import '@lion/input-date/lion-input-date.js';
import '@lion/input-email/lion-input-email.js';
import '@lion/input-iban/lion-input-iban.js';
import '@lion/input/lion-input.js';
import '@lion/radio-group/lion-radio-group.js';
import '@lion/radio/lion-radio.js';
import '@lion/textarea/lion-textarea.js';
import { MinLength, Required } from '@lion/validate';
<Meta title="Forms/Features Overview" />
# Features Overview
This is a meta package to show interaction between various form elements.
For usage and installation please see the appropriate packages.
## Umbrella Form
<Preview>
<Story name="Example">
{html`
<lion-form>
<form>
<lion-input
name="first_name"
label="First Name"
.validators="${[new Required()]}"
></lion-input>
<lion-input
name="last_name"
label="Last Name"
.validators="${[new Required()]}"
></lion-input>
<!-- TODO: lion-input-birthdate -->
<lion-input-date
name="date"
label="Date of application"
.modelValue="${'2000-12-12'}"
.validators="${[new Required()]}"
></lion-input-date>
<lion-textarea
name="bio"
label="Biography"
.validators="${[new Required(), new MinLength(10)]}"
help-text="Please enter at least 10 characters"
></lion-textarea>
<lion-input-amount name="money" label="Money"></lion-input-amount>
<lion-input-iban name="iban" label="Iban"></lion-input-iban>
<lion-input-email name="email" label="Email"></lion-input-email>
<lion-checkbox-group
label="What do you like?"
name="checkers"
.validators="${[new Required()]}"
>
<lion-checkbox name="checkers[]" value="foo" label="I like foo"></lion-checkbox>
<lion-checkbox name="checkers[]" value="bar" label="I like bar"></lion-checkbox>
<lion-checkbox name="checkers[]" value="baz" label="I like baz"></lion-checkbox>
</lion-checkbox-group>
<lion-radio-group
class="vertical"
name="dinosaurs"
label="Favorite dinosaur"
.validators="${[new Required()]}"
error-message="Dinosaurs error message"
>
<lion-radio name="dinosaurs[]" value="allosaurus" label="allosaurus"></lion-radio>
<lion-radio name="dinosaurs[]" value="brontosaurus" label="brontosaurus"></lion-radio>
<lion-radio name="dinosaurs[]" value="diplodocus" label="diplodocus"></lion-radio>
</lion-radio-group>
<!-- TODO: rich select -->
<lion-select
label="Make a selection (rich select)"
name="lyrics"
.validators="${[new Required()]}"
>
<select slot="input">
<option value="1">Fire up that loud</option>
<option value="2">Another round of shots...</option>
<option value="3">Drop down for what?</option>
</select>
</lion-select>
<lion-checkbox-group name="terms" .validators="${[new Required()]}">
<lion-checkbox
name="terms[]"
label="I blindly accept all terms and conditions"
></lion-checkbox>
</lion-checkbox-group>
<!-- TODO: slider -->
<lion-textarea name="comments" label="Comments"></lion-textarea>
<div class="buttons">
<lion-button raised>Submit</lion-button>
<lion-button
type="button"
raised
@click=${ev => ev.currentTarget.parentElement.parentElement.parentElement.resetGroup()}
>Reset</lion-button
>
</div>
</form>
</lion-form>
`}
</Story>
</Preview>
```js
import '@lion/checkbox-group/lion-checkbox-group.js';
import '@lion/checkbox/lion-checkbox.js';
import '@lion/fieldset/lion-fieldset.js';
import '@lion/form/lion-form.js';
import '@lion/input-amount/lion-input-amount.js';
import '@lion/input-date/lion-input-date.js';
import '@lion/input-email/lion-input-email.js';
import '@lion/input-iban/lion-input-iban.js';
import '@lion/input/lion-input.js';
import '@lion/radio-group/lion-radio-group.js';
import '@lion/radio/lion-radio.js';
import '@lion/textarea/lion-textarea.js';
import { MinLength, Required } from '@lion/validate';
```

View file

@ -0,0 +1,96 @@
import { Meta } from '@open-wc/demoing-storybook';
<Meta title="Forms/System/Overview" />
# Form System Overview
This page should be used as a starting point when first using the Form System.
It provides an overview of its essential building blocks and provides links to detailed explanations
of most of its core concepts.
## Building Blocks
Our Form System is built from a set of very fundamental building blocks: `form control`s, `field`s and `fieldset`s.
### Form Controls
`Form control`s are the most fundamental building blocks of our Form System.
They are the fundament of both `field`s, and `fieldset`s and provide a normalized, predictable api
throughout the whole form.
Every form element inherits from `FormControlMixin`.
`FormControlMixin` creates the default html structure
and accessibility is designed to be used in conjunction with the ValidateMixin and the FormatMixin.
### Fields
Fields (think of an input, texarea, select) are the actual form controls the end user interacts with.
They extend `LionField`, which in turn uses the `FormControlMixin`.
Fields provide a normalized api for both platform components and custom made form controls.
On top of this, they offer:
- [formatting/parsing/serializing](?path=/docs/forms-system-formatting-and-parsing) of view values
- advanced [validation](?path=/docs/forms-system-validate-system) possibilities
- creation of advanced user interaction scenarios via [interaction states](?path=/docs/forms-system-interaction-states--interaction-states)
- provision of labels and help texts in an easy, declarative manner
- accessibility out of the box <!-- TODO: write how our form system manages to deliver accessible components with all challenges that shadow dom provide -->
- advanced styling possibilities: map your own Design System to the internal HTML structure <!-- TODO: link to example of Material Design Datepicker-->
#### Platform fields (wrappers)
Platform field wrappers add all of the functionality described above to native form elements.
- [LionInput](?path=/docs/forms-input), a wrapper for `<input>`
- [LionTextarea](?path=/docs/forms-textarea), a wrapper for `<textarea>`
- [LionSelect](?path=/docs/forms-select), a wrapper for `<select>`
- [LionRadio](?path=/docs/forms-radio-group), a wrapper for `<input type="radio">`
- [LionCheckbox](?path=/docs/forms-checkbox-group), a wrapper for `<input type="checkbox">`
- [LionInputRange](?path=/docs/forms-input-range), a wrapper for `<input type="range">`
### Dedicated fields
Dedicated fields are less generic fields in a sense that they by default expect a certain type of
modelValue. This means that they have validators, parsers and formatters preconfigured.
- [`LionInputDate`](?path=/docs/forms-input-date)
- [`LionInputDatepicker`](?path=/docs/forms-input-datepicker)
- [`LionInputEmail`](?path=/docs/forms-input-email)
- [`LionInputAmount`](?path=/docs/forms-input-amount)
- [`LionInputIban`](?path=/docs/forms-input-iban)
### Custom fields
Instead of wrapping native elements, this category of fields contains custom built
form elements.
- [`LionSelectRich`](?path=/docs/forms-select-rich), an advanced (rich) version of `<select>`
One could also think of components like:
- listbox
- combobox
- autocomplete
- etc...
For more information about writing a custom field, please see [How to write a custom field](?path=/docs/forms-system-creating-a-custom-field--page)
## Fieldsets
Fieldsets are groups of fields. They can be considered fields on their own as well, since they
partly share the normalized api via `FormControlMixin`.
Fieldsets are at the base of:
- [`LionFieldset`](?path=/docs/forms-fieldset)
- [`LionForm`](?path=/docs/forms-form)
- [`LionRadioGroup`](?path=/docs/forms-radio-group)
- [`LionCheckboxGroup`](?path=/docs/forms-checkbox-group)
## Other Resources
<!-- TODO: - [`FormControlMixin`] () -->
<!-- TODO: - [`LionField`] () -->
- [Model Value](?path=/docs/forms-system-modelvalue)
- [Formatting and parsing](?path=/docs/forms-system-formatting-and-parsing)
- [Interaction states](?path=/docs/forms-system-interaction-states)
- [Validation System](?path=/docs/forms-system-validate-system)

View file

@ -0,0 +1,28 @@
import { Meta } from '@open-wc/demoing-storybook';
<Meta title="Forms/System/ModelValue" />
# ModelValue
The modelValue or model can be considered as the aorta of our form system.
It is the single source of truth; not only for the current state
of the form, also for all derived states: interaction, validation, visibility and other states are
computed from a modelValue change.
## Single source of truth
ModelValues are designed to provide the Application Developer a single way of programmatical
interaction with the form for an Application Developer.
### One single concept for Application Developers
Application Developers need to only care about interacting with the modelValue on a form control
level, via:
- `.modelValue`
- `@model-value-changed`
> Internal/private concepts like viewValue, formattedValue, serializedValue are therefore not
> recommended as a means of interaction.
For more information about parsing and the Unparseable type, see [Formatting and Parsing](?path=/docs/forms-system-formatting-and-parsing)

View file

@ -0,0 +1,245 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import '@lion/input/lion-input.js';
import { Unparseable } from '@lion/validate';
import './helper-wc/h-output.js';
<Meta title="Forms/System/Formatting and Parsing" />
# Formatting and Parsing
> For demo purposes, below we use `<lion-input>` which is a basic extension of `<lion-field>`.
> Almost all fields share the same functionality as `<lion-input>`.
## Different values
The `FormatMixin` which is used in all lion fields automatically keeps track of:
- `modelValue`, which is a result of parsing
- `formattedValue`, which is a result of formatting
- `serializedValue`, which is a result of serializing
Our fields can automatically format/parse/serialize user input or an input set imperatively by an Application Developer.
Below are some concrete examples of implementations of formatters and parsers mimicking a (basic) amount input.
> For an actual amount input, check out [lion-input-amount](?path=/docs/forms-lion-input-amount).
> This comes with its own formatter, parser, serializer.
### Parsers & modelValue
A parser should return a `modelValue`.
The `modelValue` is the result of the parser function.
It should be considered as the **internal value used for validation and reasoning/logic**.
The `modelValue` is 'ready for consumption' by the outside world (think of a Date object or a float).
The `modelValue` can (and is recommended to) be used as both input value and output value of the `<lion-input>`.
In essence, the `modelValue` acts as a Single Source of Truth in our form fields and therefore a single way of programmatical interaction.
Formatted values, serialized values and reflected-back view values are derived from it.
You can listen to `model-value-changed` event on all fields.
Internally this is also used as the main trigger for re-evaluating validation, visibility and interaction states.
Examples:
- For a `date input`: a String '20/01/1999' will be converted to new Date('1999/01/20')
- For an `amount input`: a formatted String '1.234,56' will be converted to a Number: 1234.56
You can set a parser function on the `<lion-input>` to set parsing behavior.
In this example, we parse the input and try to convert it to a `Number`.
<Story name="Parser">
{html`
<lion-input
label="Number Example"
help-text="Uses .parser to create model values from view values"
.parser="${viewValue => Number(viewValue)}"
.modelValue="${1234567890}"
@model-value-changed=${({ target }) => console.log(target) }
></lion-input>
<h-output .show="${['modelValue']}"></h-output>
`}
</Story>
```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>
```
#### Unparseable
If a parser tries to parse and it returns undefined, the `modelValue` will be an instance of
`Unparseable`.
This object contains a type `'unparseable'`, and a `viewValue` which contains the String value of
what the user tried to input.
The formatted result of this that is reflected to the user will be the `viewValue` of the
`Unparseable` instance, so basically nothing happens for the user.
<Story name="Unparseable">
{html`
<lion-input
label="Number Example"
help-text="Uses .parser and return undefined if Number() cannot parse"
.parser="${viewValue => Number(viewValue) || undefined}"
value="${'1234abc567890'}"
></lion-input>
<h-output .show="${['modelValue']}"></h-output>
`}
</Story>
```html
<lion-input
label="Number Example"
help-text="Uses .parser and return undefined if Number() cannot parse"
.parser="${viewValue => Number(viewValue) || undefined}"
value="${'1234abc567890'}"
></lion-input>
```
### Formatters
A formatter should return a `formattedValue`.
It accepts the current modelValue and an options object.
Below is a very naive and limited parser that ignores non-digits.
The formatter then uses `Intl.NumberFormat` to format it with thousand separators.
Formatted value is reflected back to the user `on-blur` of the field, but only if the field has no
errors (validation).
<Story name="Formatter">
{() => {
const formatDate = (modelValue, options) => {
if (!(typeof modelValue === 'number')) {
return options.formattedValue;
}
return new Intl.NumberFormat('en-GB').format(modelValue);
};
return html`
<lion-input
label="Number Example"
help-text="Uses .formatter to create view value"
.parser="${viewValue => Number(viewValue.replace(/[^0-9]/g, ''))}"
.formatter="${formatDate}"
.modelValue="${1234567890}"
>
</lion-input>
<h-output .show="${['modelValue', 'formattedValue']}"></h-output>
`;
}}
</Story>
> The options object holds a fallback value that shows what should be presented on
> screen when the user input resulted in an invalid `modelValue`.
```js
const formatDate = (modelValue, options) => {
if (!(typeof modelValue === 'number')) {
return options.formattedValue;
}
return new Intl.NumberFormat('en-GB').format(modelValue);
};
```
```html
<lion-input
label="Number Example"
help-text="Uses .formatter to create view value"
.parser="${viewValue => Number(viewValue.replace(/[^0-9]/g, ''))}"
.formatter="${formatDate}"
.modelValue="${1234567890}"
></lion-input>
```
### Serializers and deserializers
A serializer converts the `modelValue` to a `serializedValue`.
In this example, we decide we want to store the user input to our hypothetical database, but by parsing it with radix 8 first.
A deserializer converts a value, for example one received from an API, to a `modelValue`.
This can be useful for prefilling forms with data from APIs.
> There is no `.deserializedValue` property that stays in sync by default.
> You need to call `el.deserializer(el.modelValue)` manually yourself.
<Story name="(De)serializers">
{() => {
const mySerializer = (modelValue, options) => {
return parseInt(modelValue, 8);
};
const myDeserializer = (myValue, options) => {
return new Number(myValue);
};
return html`
<lion-input
label="Date Example"
help-text="Uses .(de)serializer to restore serialized modelValues"
.parser="${viewValue => Number(viewValue.replace(/[^0-9]/g, ''))}"
.serializer="${mySerializer}"
.deserializer="${myDeserializer}"
.modelValue="${1234567890}"
></lion-input>
<h-output .show="${['modelValue', 'serializedValue']}"></h-output>
`;
}}
</Story>
```js
const mySerializer = (modelValue, options) => {
return parseInt(modelValue, 8);
};
const myDeserializer = (myValue, options) => {
return parseInt(modelValue, 10);
};
```
```html
<lion-input
label="Date Example"
help-text="Uses .(de)serializer to restore serialized modelValues"
.parser="${viewValue => Number(viewValue.replace(/[^0-9]/g, ''))}"
.serializer="${mySerializer}"
.deserializer="${myDeserializer}"
.modelValue="${1234567890}"
></lion-input>
```
## Flow Diagrams
Below we show three flow diagrams to show the flow of formatting, serializing and parsing user input, with the example of a date input:
### Standard flow
Where a user changes the input with their keyboard
<Story name="Standard flow">
{html`
<img src="./packages/form-system/stories/diagram/FormatMixinDiagram-1.svg" />
`}
</Story>
### Unparseable flow
Where a user sets the input to something that is not parseable by the parser
<Story name="Unparseable flow">
{html`
<img src="./packages/form-system/stories/diagram/FormatMixinDiagram-2.svg" />
`}
</Story>
### Imperative / programmatic flow
Where the developer sets the modelValue of the input programmatically
<Story name="Imperative / programmatic flow">
{html`
<img src="./packages/form-system/stories/diagram/FormatMixinDiagram-3.svg" />
`}
</Story>

View file

@ -0,0 +1,181 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import { render } from '@lion/core';
import { renderLitAsNode } from '@lion/helpers';
import '@lion/input/lion-input.js';
import { Validator } from '@lion/validate';
import './helper-wc/h-output.js';
<Meta title="Forms/System/Interaction States" />
# Interaction States
`InteractionStateMixin` saves meta information about interaction states.
It allows for creating advanced UX scenarios.
Examples of such scenarios already in our fields:
- Show the validation message of an input only after the user has blurred the input field
- Hide the validation message when an invalid value becomes valid
Something our subclassers can implement:
- Show a red border around the input right after the input became invalid
The meta information that InteractionStateMixin collects, is stored in the Boolean properties on our fields:
- `touched`, the user blurred the field
- `dirty`, the value in the field has changed
- `prefilled`, a prepopulated field is not empty
> You can listen to the events `touched-changed` and `dirty-changed`.
<Story name="Interaction States">
{html`
<lion-input
label="Interaction States"
help-text="Interact with this field to see how dirty, touched and prefilled change"
.modelValue="${'myValue'}"
></lion-input>
<h-output .show="${['touched', 'dirty', 'prefilled', 'focused', 'submitted']}"></h-output>
`}
</Story>
```html
<lion-input
label="Interaction States"
help-text="Interact with this field to see how dirty, touched and prefilled change"
.modelValue="${'myValue'}"
></lion-input>
```
## Advanced use cases
### Overriding interaction states
When creating an extension of LionField or LionInput, it can be needed to override the way prefilled values are 'computed'.
The example below shows how this is done for checkboxes/radio-inputs.
```js
/**
* @override
*/
static _isPrefilled(modelValue) {
return modelValue.checked;
}
```
## When is feedback shown to the user
We show the validity feedback when one of the following conditions is met:
- **prefilled**,
the user already filled in something, or the value is prefilled
when the form is initially rendered.
- **touched** && **dirty**
- When a user starts typing for the first time in a field with for instance `required` validation,
error message should not be shown until a field becomes `touched` (a user leaves(blurs) a field).
- When a user enters a field without altering the value (making it `dirty` but not `touched`),
an error message shouldn't be shown either.
- **submitted**,
if the form is submitted, always show the error message.
### Changing the feedback show condition (Subclassers)
You can change the condition upon which feedback gets shown.
In order to override the feedback show conditions, you need to create a custom field and override `_showFeedbackConditionFor` method.
This method accepts the a `type` parameter which is a String representing the type of feedback (e.g. 'error').
Then, it returns true of false, depending on whether feedback for that type should be shown or not. That part, you can control.
In the following example we will demonstrate this with interaction states, the most common use case for feedback visibility conditions.
<Story name="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 })];
class OddValidator extends Validator {
constructor(...args) {
super(...args);
this.name = 'OddValidator';
}
// eslint-disable-next-line class-methods-use-this
execute(value) {
let hasError = false;
if (!(value.length % 2 !== 0)) {
hasError = true;
}
return hasError;
}
_getMessage() {
return '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 `.validators`
const fieldElement = renderLitAsNode(html`
<lion-input
name="interactionField"
label="Only an odd amount of characters allowed"
help-text="Change feedback condition"
.modelValue="${'notodd'}"
.validators="${[new OddValidator()]}"
>
<input slot="input" />
</lion-input>
`);
fieldElement._showFeedbackConditionFor = type => {
return conditions.every(condition => {
/**
* This here shows bug for focused state.
* Focused is set to true AFTER we already evaluate feedback conditions
* Bug report: https://github.com/ing-bank/lion/issues/455
*/
// setTimeout(() => console.log(fieldElement[condition])); //
return fieldElement[condition];
});
};
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();
}
fieldElement.addEventListener('focus', () => {
fieldElement.validate();
});
return html`
<lion-form>
<form>
${fieldElement}
<button>Submit</button>
</form>
</lion-form>
<h-output .field="${fieldElement}" .show="${[...props, 'hasFeedbackFor']}"> </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>
`;
}}
</Story>

View file

@ -0,0 +1,87 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import { render } from '@lion/core';
import '@lion/input/lion-input.js';
import { Validator } from '@lion/validate';
import './helper-wc/h-output.js';
<Meta title="Forms/System/Creating a Custom Field" />
# Creating a custom field
Custom fields can be created in just a few steps. All you need is an interaction element
(like for instance a slider, a listbox or a combobox) and connect it to the [Field](?path=/docs/forms-system-overview)
functionality.
## Prerequisite: an interaction element
An interaction element provides the means for the end user to enter a certain value, just like
native elements provide in this (think of `<input>`, `<textarea>` and `<select>`).
An example of a non native element is the <a href="https://www.w3.org/TR/wai-aria-practices-1.1/#slider" target="_blank">slider design pattern</a> described here.
For this tutorial, we assume we have a component `<my-slider>` that exposes its value via property
`mySliderValue` and sends an event `my-slider-changed` on every value change. To make it focusable,
it has a tabindex=“0” applied.
## Connecting the interaction element to the field
Now we want to integrate the slider in our form framework to enrich the user interface, get
validation support and get all the other [benefits of LionField](/?path=/docs/forms-system-overview).
We start of by creating a component `<lion-slider>` that extends from `LionField`.
Then we follow the steps below:
#### 1. Add your interaction element as input slot'
Here you return the element the user interacts with. By configuring it as a slot, it will end up
in light DOM, ensuring the best accessibility for the end user.
#### 2. Proxy event `my-slider-changed` to `user-input-changed` event
The `user-input-changed` event is listened to by the FormatMixin: it should be regarded as the
equivalent of the `input` event of the platform, but for custom built interaction elements.
#### 3. Proxy property `<my-slider>.mySliderValue` to `<lion-slider>.value`
Every time the `user-input-changed` fires, the value of `<my-slider>` is synchronized with the
[`modelValue`](?path=/docs/forms-system-modelvalue--page) of `<my-slider>`. Now the cycle is complete: the modelValue connects
your interaction element to all logic inside the LionField.
Steps as described can be implemented with the following javascript:
```js
import { LionField } from '@lion/field';
import './my-slider.js';
export class LionSlider extends LionField {
// 1. Add your interaction element as input slot'
get slots() {
return {
...super.slots,
input: () => document.createElement(my-slider),
};
}
// 2. Proxy event `my-slider-changed` to `user-input-changed` event
connectedCallback() {
super.connectedCallback();
this.addEventListener('my-slider-changed', this._proxyChangeEvent);
}
_proxyChangeEvent() {
this._inputNode.dispatchEvent(
new CustomEvent('user-input-changed', { bubbles: true, composed: true }),
);
}
// 3. Proxy property `<my-slider>.mySliderValue` to `<lion-slider>.value`
get value() {
return Array.from(this.children).find(child => child.slot === 'input').mySliderValue;
}
set value(newV) {
Array.from(this.children).find(child => child.slot === 'input').mySliderValue = newV;
}
}
```
That was all. Now you can enhance your slider by writing custom validators for it
or by writing a parser to get a custom modelValue type.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2000 879" style="enable-background:new 0 0 2000 879;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.st1{opacity:0.4;}
.st2{fill:#22B573;}
.st3{fill:#0071BC;}
.st4{opacity:0.4;fill:#0071BC;}
.st5{fill:#F7931E;}
.st6{opacity:0.65;}
.st7{opacity:0.65;fill:#0071BC;}
.st8{fill:#F2F2F2;}
.st9{font-family:'INGMe-Bold';}
.st10{font-size:36px;}
.st11{font-family:'INGMe';}
.st12{font-size:20px;}
.st13{font-size:19px;}
.st14{font-size:26px;}
.st15{fill:#4D4D4D;}
.st16{fill:#FFFFFF;}
.st17{font-size:28px;}
</style>
<g>
<text transform="matrix(1 0 0 1 908.4995 105.6836)"><tspan x="0" y="0" class="st9 st10">User flow 2</tspan><tspan x="30.1" y="24" class="st11 st12">(Unparseable)</tspan></text>
<text transform="matrix(1 0 0 1 633.6143 262.7981)"><tspan x="0" y="0" class="st11 st10">User</tspan><tspan x="-51.3" y="22.8" class="st11 st13">(inputElement.input)</tspan></text>
<text transform="matrix(1 0 0 1 626.7212 456.7981)" class="st11 st10">value</text>
<text transform="matrix(1 0 0 1 570.8491 643.7981)" class="st9 st10">modelValue</text>
<text transform="matrix(1 0 0 1 142.4126 643.7981)" class="st11 st10">serializedValue</text>
<g>
<g>
<line class="st0" x1="670.32" y1="388.02" x2="670.32" y2="314.42"/>
<g>
<polygon points="681.43,379.59 670.32,384.31 659.21,379.59 670.32,405.92 "/>
</g>
</g>
</g>
<g>
<g>
<line class="st0" x1="700.32" y1="405.92" x2="700.32" y2="332.33"/>
<g>
<polygon points="711.43,340.76 700.32,336.04 689.21,340.76 700.32,314.42 "/>
</g>
</g>
</g>
<g>
<g>
<line class="st0" x1="670.32" y1="567.02" x2="670.32" y2="493.42"/>
<g>
<polygon points="681.43,558.59 670.32,563.31 659.21,558.59 670.32,584.92 "/>
</g>
</g>
</g>
<g>
<g>
<path class="st0" d="M583.82,673.92c0,0-144.71,149.44-297.99,12.32"/>
<g>
<polygon points="299.63,683.84 288.58,688.71 284.42,700.04 272.82,673.92 "/>
</g>
</g>
</g>
<g>
<g>
<path class="st0" d="M760.06,452.77c37.11,16.52,116.78,67.59,35.44,179.73"/>
<g>
<polygon points="763.77,466.39 763.44,454.33 771.89,445.71 743.32,446.42 "/>
</g>
</g>
</g>
<g class="st1">
<g>
<path class="st0" d="M272.82,585.15c0,0,144.71-149.44,297.99-12.32"/>
<g>
<polygon points="557.01,575.23 568.05,570.36 572.21,559.03 583.82,585.15 "/>
</g>
</g>
</g>
<text transform="matrix(1 0 0 1 680.5942 538.8196)" class="st3 st11 st14">parser</text>
<g>
<text transform="matrix(1 0 0 1 369.5942 777.8196)" class="st3 st11 st14">serializer</text>
</g>
<g class="st6">
<text transform="matrix(1 0 0 1 369.5942 501.8196)" class="st3 st11 st14">deserializer</text>
</g>
<text transform="matrix(1 0 0 1 606.5942 351.8196)" class="st2 st11 st14">sync</text>
<text transform="matrix(1 0 0 1 717.5942 351.8196)" class="st2 st11 st14">delegate</text>
<text transform="matrix(1 0 0 1 898.5942 527.8196)" class="st2 st11 st14">sync</text>
<circle class="st2" cx="579.82" cy="351.92" r="16.97"/>
<circle class="st2" cx="842.82" cy="351.92" r="16.97"/>
<circle class="st3" cx="702.32" cy="491.42" r="16.97"/>
<circle class="st7" cx="344.32" cy="469.42" r="16.97"/>
<circle class="st3" cx="344.32" cy="782.42" r="16.97"/>
<text transform="matrix(1 0 0 1 555.5942 225.8196)" class="st5 st11 st14">interact</text>
<circle class="st5" cx="530.82" cy="219.92" r="16.97"/>
<text transform="matrix(1 0 0 1 524.8955 226.178)" class="st9 st12">1</text>
<text transform="matrix(1 0 0 1 573.8955 359.178)" class="st9 st12">2</text>
<text transform="matrix(1 0 0 1 695.8955 499.178)" class="st9 st12">3</text>
<text transform="matrix(1 0 0 1 337.8955 789.178)" class="st9 st12">4</text>
<text transform="matrix(1 0 0 1 338.8955 476.178)" class="st6 st9 st12">5</text>
<text transform="matrix(1 0 0 1 836.8955 359.178)" class="st9 st12">5</text>
<circle class="st2" cx="881.82" cy="490.92" r="16.97"/>
<text transform="matrix(1 0 0 1 875.8955 498.178)" class="st9 st12">4</text>
<text transform="matrix(1 0 0 1 1089.8506 262.6697)"><tspan x="0" y="0" class="st9 st12">1. User changes the input </tspan><tspan x="0" y="96" class="st9 st12">2. Native input syncs </tspan><tspan x="0" y="120" class="st9 st12"> with lion-input-date value </tspan><tspan x="0" y="192" class="st15 st9 st12">3. </tspan><tspan x="21.26" y="192" class="st9 st12">Value is parsed to modelValue</tspan><tspan x="0" y="216" class="st9 st12"> but it is unparseable </tspan><tspan x="0" y="240" class="st9 st12"> (parser returns undefined)</tspan><tspan x="0" y="312" class="st9 st12">4. modelValue.viewValue, </tspan><tspan x="0" y="336" class="st9 st12"> which is the value prior to </tspan><tspan x="0" y="360" class="st9 st12"> the parsing step, </tspan><tspan x="0" y="384" class="st9 st12"> is synced back to the user.</tspan><tspan x="0" y="408" class="st9 st12"> It is also serialized </tspan><tspan x="0" y="432" class="st9 st12"> </tspan><tspan x="0" y="504" class="st9 st12">5. Value is delegated back to </tspan><tspan x="0" y="528" class="st9 st12"> native input value </tspan><tspan x="0" y="552" class="st9 st12"> (what the user sees)</tspan></text>
<text transform="matrix(1 0 0 1 1458.8506 262.6697)"><tspan x="0" y="0" class="st3 st9 st12">“10%30%2010”</tspan><tspan x="0" y="96" class="st3 st9 st12">“10%30%2010”</tspan><tspan x="0" y="192" class="st3 st9 st12">[Unparseable]</tspan><tspan x="0" y="312" class="st3 st9 st12">“10%30%2010”</tspan><tspan x="0" y="336" class="st3 st9 st12"> </tspan><tspan x="0" y="504" class="st3 st9 st12">“10%30%2010”</tspan></text>
<text transform="matrix(1 0 0 1 1655.8506 262.6697)"><tspan x="0" y="0" class="st9 st12"> </tspan><tspan x="4.8" y="0" class="st15 st9 st12">@user-input-changed </tspan><tspan x="0" y="96" class="st9 st12"> </tspan><tspan x="0" y="192" class="st15 st9 st12">@model-value-changed </tspan><tspan x="0" y="384" class="st15 st9 st12"> </tspan></text>
<text transform="matrix(1 0 0 1 1365.3184 740.4246)" class="st16 st9 st17">!</text>
<text transform="matrix(1 0 0 1 110.1018 344.5281)" class="st6"><tspan x="0" y="0" class="st9 st12">5. On request: serializedValue</tspan><tspan x="0" y="24" class="st9 st12"> is deserialized to modelValue,</tspan><tspan x="0" y="48" class="st9 st12"> which will again be Unparseable</tspan></text>
<text transform="matrix(1 0 0 1 205.2695 418.5281)" class="st7 st9 st12">[Unparseable]</text>
<text transform="matrix(1 0 0 1 1374.6929 672.5281)" class="st3 st9 st12">&quot;{&quot;type&quot;:&quot;unparseable&quot;,&quot;viewValue&quot;:&quot;10%30%2010&quot;}&quot;</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

View file

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2000 807" style="enable-background:new 0 0 2000 807;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
.st1{opacity:0.4;}
.st2{fill:#22B573;}
.st3{fill:#0071BC;}
.st4{opacity:0.4;fill:#0071BC;}
.st5{fill:#F7931E;}
.st6{opacity:0.65;}
.st7{opacity:0.65;fill:#0071BC;}
.st8{fill:#F2F2F2;}
.st9{font-family:'INGMe-Bold';}
.st10{font-size:36px;}
.st11{font-family:'INGMe';}
.st12{font-size:20px;}
.st13{font-size:19px;}
.st14{font-size:26px;}
.st15{fill:#4D4D4D;}
.st16{fill:none;}
</style>
<g>
<text transform="matrix(1 0 0 1 875.9043 69.99)"><tspan x="0" y="0" class="st9 st10">Imperative</tspan><tspan x="19.22" y="24" class="st11 st12">(programmatic)</tspan></text>
<text transform="matrix(1 0 0 1 525.8647 763.1797)" class="st11 st10">formattedValue</text>
<text transform="matrix(1 0 0 1 607.5366 189.1799)"><tspan x="0" y="0" class="st11 st10">User</tspan><tspan x="-51.3" y="22.8" class="st11 st13">(inputElement.input)</tspan></text>
<text transform="matrix(1 0 0 1 600.6436 383.1797)" class="st11 st10">value</text>
<text transform="matrix(1 0 0 1 544.7715 570.1797)" class="st9 st10">modelValue</text>
<text transform="matrix(1 0 0 1 116.335 570.1797)" class="st11 st10">serializedValue</text>
<g>
<g>
<line class="st0" x1="646.24" y1="332.31" x2="646.24" y2="258.72"/>
<g>
<polygon points="657.35,267.14 646.24,262.42 635.13,267.14 646.24,240.81 "/>
</g>
</g>
</g>
<g>
<g>
<line class="st0" x1="642.24" y1="686.4" x2="642.24" y2="612.81"/>
<g>
<polygon points="653.35,677.97 642.24,682.69 631.13,677.97 642.24,704.31 "/>
</g>
</g>
</g>
<g>
<g>
<path class="st0" d="M557.74,600.31c0,0-144.71,149.44-297.99,12.32"/>
<g>
<polygon points="273.55,610.22 262.5,615.09 258.35,626.43 246.74,600.31 "/>
</g>
</g>
</g>
<g>
<g>
<path class="st0" d="M733.83,379.55c71.85,30.99,340.21,166.78,60.41,365.26"/>
<g>
<polygon points="737.35,393.12 737.22,381.05 745.82,372.58 717.24,372.81 "/>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 343.5166 704.2012)" class="st3 st11 st14">serializer</text>
</g>
<text transform="matrix(1 0 0 1 663.5166 278.2014)" class="st2 st11 st14">delegate</text>
<text transform="matrix(1 0 0 1 872.5166 454.2014)" class="st2 st11 st14">sync</text>
<text transform="matrix(1 0 0 1 654.5166 645.2012)" class="st3 st11 st14">formatter</text>
<circle class="st2" cx="791.74" cy="278.31" r="16.97"/>
<circle class="st3" cx="318.24" cy="708.81" r="16.97"/>
<circle class="st3" cx="676.24" cy="670.81" r="16.97"/>
<text transform="matrix(1 0 0 1 712.5166 526.2014)" class="st5 st11 st14">changed</text>
<circle class="st5" cx="687.74" cy="520.31" r="16.97"/>
<text transform="matrix(1 0 0 1 681.8179 527.5596)" class="st9 st12">1</text>
<text transform="matrix(1 0 0 1 670.8179 677.5596)" class="st9 st12">2</text>
<text transform="matrix(1 0 0 1 311.8179 714.5596)" class="st9 st12">2</text>
<text transform="matrix(1 0 0 1 785.8179 284.5598)" class="st9 st12">4</text>
<circle class="st2" cx="855.74" cy="417.31" r="16.97"/>
<text transform="matrix(1 0 0 1 849.8179 424.5598)" class="st9 st12">3</text>
<text transform="matrix(1 0 0 1 1063.7729 198.0515)"><tspan x="0" y="0" class="st9 st12">1. modelValue is changed during</tspan><tspan x="0" y="24" class="st9 st12"> runtime </tspan><tspan x="0" y="96" class="st9 st12">2. modelValue is formatted to </tspan><tspan x="0" y="120" class="st9 st12"> formattedValue and</tspan><tspan x="0" y="144" class="st9 st12"> modelValue is </tspan><tspan x="0" y="168" class="st9 st12"> serialized to serializedValue </tspan><tspan x="0" y="240" class="st15 st9 st12">3. </tspan><tspan x="21.26" y="240" class="st9 st12">formattedValue is reflected</tspan><tspan x="0" y="264" class="st9 st12"> back to the user through </tspan><tspan x="0" y="288" class="st9 st12"> the field value property</tspan><tspan x="0" y="336" class="st9 st12">4. Value is delegated back to </tspan><tspan x="0" y="360" class="st9 st12"> native input value </tspan><tspan x="0" y="384" class="st9 st12"> (what the user sees)</tspan><tspan x="0" y="432" class="st9 st12"> </tspan><tspan x="9.6" y="432" class="st15 st9 st12"> </tspan></text>
<text transform="matrix(1 0 0 1 1432.7729 189.0515)"><tspan x="0" y="0" class="st3 st9 st12"> [DateObject] </tspan><tspan x="0" y="24" class="st3 st9 st12"> (day + 1)</tspan><tspan x="0" y="96" class="st3 st9 st12">“30-11-2010”</tspan><tspan x="0" y="144" class="st3 st9 st12">“2010-10-30T00:00:00.000Z” </tspan><tspan x="0" y="240" class="st3 st9 st12">“30-11-2010”</tspan><tspan x="0" y="336" class="st3 st9 st12">“30-10-2010”</tspan></text>
<text transform="matrix(1 0 0 1 1629.7729 189.0515)"><tspan x="0" y="0" class="st9 st12"> </tspan><tspan x="4.8" y="0" class="st15 st9 st12">@model-value-changed </tspan><tspan x="0" y="96" class="st9 st12"> </tspan><tspan x="0" y="144" class="st15 st9 st12"> </tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

@ -1,132 +0,0 @@
import '@lion/input/lion-input.js';
import { Unparseable } from '@lion/validate';
import { html, storiesOf } from '@open-wc/demoing-storybook';
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')
.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
label="My 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>
`,
);

View file

@ -1,128 +0,0 @@
import '@lion/checkbox-group/lion-checkbox-group.js';
import '@lion/checkbox/lion-checkbox.js';
import '@lion/fieldset/lion-fieldset.js';
import '@lion/form/lion-form.js';
import '@lion/input-amount/lion-input-amount.js';
import '@lion/input-date/lion-input-date.js';
import '@lion/input-email/lion-input-email.js';
import '@lion/input-iban/lion-input-iban.js';
import '@lion/input/lion-input.js';
import '@lion/radio-group/lion-radio-group.js';
import '@lion/radio/lion-radio.js';
import '@lion/textarea/lion-textarea.js';
import { MinLength, Required } from '@lion/validate';
import { html, storiesOf } from '@open-wc/demoing-storybook';
storiesOf('Forms|Form').add(
'Umbrella form',
() => html`
<lion-form
><form>
<lion-input
name="first_name"
label="First Name"
.validators="${[new Required()]}"
></lion-input>
<lion-input
name="last_name"
label="Last Name"
.validators="${[new Required()]}"
></lion-input>
<!-- TODO: lion-input-birthdate -->
<lion-input-date
name="date"
label="Date of application"
.modelValue="${'2000-12-12'}"
.validators="${[new Required()]}"
></lion-input-date>
<lion-textarea
name="bio"
label="Biography"
.validators="${[new Required(), new MinLength(10)]}"
help-text="Please enter at least 10 characters"
></lion-textarea>
<lion-input-amount name="money" label="Money"></lion-input-amount>
<lion-input-iban name="iban" label="Iban"></lion-input-iban>
<lion-input-email name="email" label="Email"></lion-input-email>
<lion-checkbox-group
label="What do you like?"
name="checkers"
.validators="${[new Required()]}"
>
<lion-checkbox name="checkers[]" value="foo" label="I like foo"></lion-checkbox>
<lion-checkbox name="checkers[]" value="bar" label="I like bar"></lion-checkbox>
<lion-checkbox name="checkers[]" value="baz" label="I like baz"></lion-checkbox>
</lion-checkbox-group>
<lion-radio-group
name="dinosaurs"
label="Favorite dinosaur"
.validators="${[new Required()]}"
>
<lion-radio
name="dinosaurs[]"
.choiceValue="${'allosaurus'}"
label="allosaurus"
></lion-radio>
<lion-radio
name="dinosaurs[]"
.choiceValue="${'brontosaurus'}"
label="brontosaurus"
></lion-radio>
<lion-radio
name="dinosaurs[]"
.choiceValue="${'diplodocus'}"
label="diplodocus"
></lion-radio>
</lion-radio-group>
<!-- TODO: rich select -->
<lion-select
label="Make a selection (rich select)"
name="lyrics"
.validators="${[new Required()]}"
>
<select slot="input">
<option value="1">Fire up that loud</option>
<option value="2">Another round of shots...</option>
<option value="3">Drop down for what?</option>
</select>
</lion-select>
<lion-checkbox-group name="terms" .validators="${[new Required()]}">
<lion-checkbox
name="terms[]"
label="I blindly accept all terms and conditions"
></lion-checkbox>
</lion-checkbox-group>
<lion-input-range
name="interest"
label="Interest"
min="0"
max="5"
step="0.1"
unit="%"
></lion-input-range>
<lion-textarea name="comments" label="Comments"></lion-textarea>
<div class="buttons">
<lion-button raised>Submit</lion-button>
<lion-button
type="button"
raised
@click=${ev => ev.currentTarget.parentElement.parentElement.parentElement.resetGroup()}
>Reset</lion-button
>
</div>
</form></lion-form
>
`,
);

View file

@ -1,113 +0,0 @@
import '@lion/checkbox-group/lion-checkbox-group.js';
import '@lion/checkbox/lion-checkbox.js';
import { render } from '@lion/core';
import '@lion/form/lion-form.js';
import '@lion/input/lion-input.js';
import { Validator } from '@lion/validate';
import { html, storiesOf } from '@open-wc/demoing-storybook';
import './helper-wc/h-output.js';
function renderOffline(litHtmlTemplate) {
const offlineRenderContainer = document.createElement('div');
render(litHtmlTemplate, offlineRenderContainer);
return offlineRenderContainer.firstElementChild;
}
storiesOf('Form Fundaments|Interaction States')
.add(
'States',
() => html`
<lion-input
label="Interaction States"
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 })];
class OddValidator extends Validator {
constructor(...args) {
super(...args);
this.name = 'OddValidator';
}
// eslint-disable-next-line class-methods-use-this
execute(value) {
let hasError = false;
if (!(value.length % 2 !== 0)) {
hasError = true;
}
return hasError;
}
_getMessage() {
return '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 `.validators`
const fieldElement = renderOffline(html`
<lion-input
name="interactionField"
label="Only an odd amount of characters allowed"
help-text="Change feedback condition"
.modelValue="${'notodd'}"
.validators="${[new OddValidator()]}"
.showErrorCondition="${newStates =>
newStates.errorStates && 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, 'hasFeedbackFor']}"> </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>
`;
});

View file

@ -2,16 +2,11 @@
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
`lion-form` is a webcomponent that enhances the functionality of the native `form` component. It is designed to interact with (instances of) the [form controls](../field/docs/FormFundaments.md).
`lion-form` is a webcomponent that enhances the functionality of the native `form` component.
## Features
## Live Demo/Documentation
- data synchronization with models
- easy retrieval of form data based on field names
- advanced validation possibilities
- advanced user interaction scenarios via [interaction states](../field/docs/InteractionStates.md)
- registration mechanism for [form controls](../field/).
- accessible out of the box
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-form) for a live demo and documentation
## How to use
@ -31,19 +26,8 @@ import '@lion/form/lion-form.js';
<lion-form>
<form>
<lion-fieldset name="fullName">
<lion-input label="First Name" name="firstName" .modelValue="${model.firstName}"></lion-input>
<lion-input label="Last Name" name="lastName" .modelValue="${model.lastName}"></lion-input>
<lion-input label="First Name" name="firstName"></lion-input>
</lion-fieldset>
<lion-textarea
label="Description"
name="description"
.modelValue="${model.description}"
></lion-textarea>
</form>
</lion-form>
```
Note that the example above is rendered using [lit-html](https://github.com/Polymer/lit-html)
For more information about fields that are designed for lion-form, please read
[Forms](../../docs/forms.md).

View file

@ -36,7 +36,7 @@
"@lion/fieldset": "0.5.7"
},
"devDependencies": {
"@lion/field": "0.7.0",
"@lion/field": "0.7.1",
"@lion/validate": "0.5.4",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4",

View file

@ -0,0 +1,13 @@
import { LionField } from '@lion/field';
customElements.define(
'demo-form-child',
class extends LionField {
get slots() {
return {
...super.slots,
input: () => document.createElement('input'),
};
}
},
);

View file

@ -1,73 +0,0 @@
import '@lion/fieldset/lion-fieldset.js';
import '@lion/input-iban/lion-input-iban.js';
import '@lion/textarea/lion-textarea.js';
import { MaxLength, Required } from '@lion/validate';
import { html, storiesOf } from '@open-wc/demoing-storybook';
import '../lion-form.js';
storiesOf('Forms|Form')
.add(
'Basic form result',
() => html`
<lion-form id="form"
><form>
<lion-fieldset label="Personal data" name="personalData">
<lion-fieldset label="Full Name" name="fullName">
<lion-input name="firstName" label="First Name" .modelValue=${'Foo'}></lion-input>
<lion-input name="lastName" label="Last Name" .modelValue=${'Bar'}></lion-input>
</lion-fieldset>
<lion-fieldset label="Location" name="location">
<lion-input name="country" label="Country" .modelValue=${'Netherlands'}></lion-input>
<lion-input name="city" label="City" .modelValue=${'Amsterdam'}></lion-input>
</lion-fieldset>
<lion-input name="birthdate" label="Birthdate" .modelValue=${'23-04-1991'}></lion-input>
</lion-fieldset>
<lion-textarea
name="comments"
help-text="If none, leave empty"
label="Comments"
></lion-textarea>
<button @click=${() => console.log(document.querySelector('#form').serializeGroup())}>
Log to Action Logger
</button>
</form></lion-form
>
`,
)
.add('Form Submit/Reset', () => {
const submit = () => {
const form = document.querySelector('#form');
if (!form.hasFeedbackFor.includes('error')) {
console.log(form.serializeGroup());
form.resetGroup();
}
};
return html`
<lion-form id="form" @submit="${submit}"
><form>
<lion-fieldset label="Name" name="name">
<lion-input
name="firstName"
label="First Name"
.validators=${[new Required(), new MaxLength(15)]}
>
</lion-input>
<lion-input
name="lastName"
label="Last Name"
.validators=${[new Required(), new MaxLength(15)]}
>
</lion-input>
</lion-fieldset>
<button type="submit">Submit & Reset (if successfully submitted)</button>
<button type="button" @click=${() => document.querySelector('#form').resetGroup()}>
Reset
</button>
<p>
A reset button should never be offered to users. This button is only used here to
demonstrate the functionality.
</p>
</form></lion-form
>
`;
});

View file

@ -0,0 +1,157 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import { Required, MaxLength, loadDefaultFeedbackMessages } from '@lion/validate';
import '@lion/fieldset/lion-fieldset.js';
import '@lion/input/lion-input.js';
import '../lion-form.js';
import './helpers/demo-form-child.js';
<Meta title="Forms/Form" parameters={{ component: 'lion-form' }} />
# Form
`lion-form` is a webcomponent that enhances the functionality of the native `form` component.
It is designed to interact with (instances of) the [form controls](?path=/docs/forms-system-overview).
<Story name="Default">
{html`
<lion-form id="form">
<form>
<lion-fieldset label="Personal data" name="personalData">
<lion-fieldset label="Full Name" name="fullName">
<demo-form-child name="firstName" label="First Name" .modelValue=${'Foo'}></demo-form-child>
<demo-form-child name="lastName" label="Last Name" .modelValue=${'Bar'}></demo-form-child>
</lion-fieldset>
<lion-fieldset label="Location" name="location">
<demo-form-child name="country" label="Country" .modelValue=${'Netherlands'}></demo-form-child>
<demo-form-child name="city" label="City" .modelValue=${'Amsterdam'}></demo-form-child>
</lion-fieldset>
<demo-form-child name="birthdate" label="Birthdate" .modelValue=${'23-04-1991'}></demo-form-child>
</lion-fieldset>
<button type="button" @click=${() => console.log(document.querySelector('#form').serializeGroup())}>
Log to Action Logger
</button>
</form>
</lion-form>
`}
</Story>
```html
<lion-form id="form">
<form>
<lion-fieldset label="Personal data" name="personalData">
<lion-fieldset label="Full Name" name="fullName">
<demo-form-child name="firstName" label="First Name" .modelValue=${'Foo'}></demo-form-child>
<demo-form-child name="lastName" label="Last Name" .modelValue=${'Bar'}></demo-form-child>
</lion-fieldset>
<lion-fieldset label="Location" name="location">
<demo-form-child name="country" label="Country" .modelValue=${'Netherlands'}></demo-form-child>
<demo-form-child name="city" label="City" .modelValue=${'Amsterdam'}></demo-form-child>
</lion-fieldset>
<demo-form-child name="birthdate" label="Birthdate" .modelValue=${'23-04-1991'}></demo-form-child>
</lion-fieldset>
<button type="button" @click=${() => console.log(document.querySelector('#form').serializeGroup())}>
Log to Action Logger
</button>
</form>
</lion-form>
```
## Features
- Data synchronization with models
- Easy retrieval of form data based on field names
- Advanced validation possibilities
- Advanced user interaction scenarios via [interaction states](?path=/docs/forms-system-interaction-states)
- Registration mechanism for [form controls](?path=/docs/forms-system-overview)
- Accessible out of the box
For more information about fields that are designed for lion-form, please read
[Forms](?path=/docs/forms-form).
## Form Submit / Reset
You can control whether a form gets submitted based on validation states.
Same thing goes for resetting the inputs to the original state.
<Story name="Submit/reset">
{() => {
loadDefaultFeedbackMessages();
const submit = () => {
const form = document.querySelector('#form2');
if (!form.hasFeedbackFor.includes('error')) {
console.log(form.serializeGroup());
form.resetGroup();
}
};
return html`
<lion-form id="form2" @submit="${submit}"
><form>
<lion-fieldset label="Name" name="name">
<demo-form-child
name="firstName"
label="First Name"
.validators=${[new Required(), new MaxLength(15)]}
>
</demo-form-child>
<demo-form-child
name="lastName"
label="Last Name"
.validators=${[new Required(), new MaxLength(15)]}
>
</demo-form-child>
</lion-fieldset>
<button type="submit">Submit & Reset (if successfully submitted)</button>
<button type="button" @click=${() => document.querySelector('#form2').resetGroup()}>
Reset
</button>
<p>
A reset button should never be offered to users. This button is only used here to
demonstrate the functionality.
</p>
</form></lion-form
>
`;
}}
</Story>
```js
import { Required, MaxLength } from '@lion/validate'
const submit = () => {
const form = document.querySelector('#form2');
if (!form.hasFeedbackFor.includes('error')) {
console.log(form.serializeGroup());
form.resetGroup();
}
};
```
```html
<lion-form id="form2" @submit="${submit}">
<form>
<lion-fieldset label="Name" name="name">
<demo-form-child
name="firstName"
label="First Name"
.validators=${[new Required(), new MaxLength(15)]}
>
</demo-form-child>
<demo-form-child
name="lastName"
label="Last Name"
.validators=${[new Required(), new MaxLength(15)]}
>
</demo-form-child>
</lion-fieldset>
<button type="submit">Submit & Reset (if successfully submitted)</button>
<button type="button" @click=${() => console.log(document.querySelector('#form2'))}>
Reset
</button>
<p>
A reset button should never be offered to users. This button is only used here to
demonstrate the functionality.
</p>
</form>
</lion-form>
```

View file

@ -1,5 +1,6 @@
// Utilities
export { renderLitAsNode } from './renderLitAsNode/src/renderLitAsNode.js';
export { sortEachDepth } from './sortEachDepth/src/sortEachDepth.js';
// Components
export { SbActionLogger } from './sb-action-logger/src/SbActionLogger.js';

View file

@ -0,0 +1,83 @@
/**
* Sorts each depth level according to the provided array order.
*
* @example
* addParameters({
* options: {
* showRoots: true,
* storySort: sortEachDepth([
* ['Intro', 'Forms', 'Buttons', '...'] // 1. level - ordered like this rest default order
* ['Intro', '...', 'System'], // 2. level - Intro first, System last in between default order
* ['Overview', '...'] // 3. level - Intro first rest default order
* ]),
* });
*
* @param {array} orderPerDepth array of arrays giving the order of each level
*/
export function sortEachDepth(orderPerDepth) {
return (a, b) => {
// If the two stories have the same story kind, then use the default
// ordering, which is the order they are defined in the story file.
if (a[1].kind === b[1].kind) {
return 0;
}
const storyKindA = a[1].kind.split('/');
const storyKindB = b[1].kind.split('/');
let depth = 0;
let nameA;
let nameB;
let indexA;
let indexB;
let ordering = orderPerDepth[0] || [];
if (ordering.indexOf('...') !== -1 && ordering.indexOf('...abc') !== -1) {
throw new Error(
`Found ${ordering.join(',')} You need to use either "..." or "...abc" for each level.`,
);
}
// eslint-disable-next-line no-constant-condition
while (true) {
nameA = storyKindA[depth] ? storyKindA[depth] : '';
nameB = storyKindB[depth] ? storyKindB[depth] : '';
if (nameA === nameB) {
// We'll need to look at the next part of the name.
depth += 1;
ordering = orderPerDepth[depth] || [];
if (ordering.indexOf('...') !== -1 && ordering.indexOf('...abc') !== -1) {
throw new Error('You need to use either "..." or "...abc" for each level.');
}
// eslint-disable-next-line no-continue
continue;
} else {
// Look for the names in the given `ordering` array.
indexA = ordering.indexOf(nameA);
indexB = ordering.indexOf(nameB);
// If at least one of the names is found, sort by the `ordering` array.
if (indexA !== -1 || indexB !== -1) {
// If one of the names is not found in `ordering`, list it at the place of '...' or last.
let insertPosition = ordering.length;
if (ordering.indexOf('...') !== -1) {
insertPosition = ordering.indexOf('...');
}
if (ordering.indexOf('...abc') !== -1) {
insertPosition = ordering.indexOf('...abc');
}
if (indexA === -1) {
indexA = insertPosition;
}
if (indexB === -1) {
indexB = insertPosition;
}
return indexA - indexB;
}
}
if (ordering.indexOf('...abc') !== -1) {
return nameA.localeCompare(nameB);
}
// Otherwise, use source code order.
return 0;
}
};
}

View file

@ -0,0 +1,13 @@
import { Meta } from '@open-wc/demoing-storybook';
<Meta title="Helpers/Intro" />
# Helpers
Features that aid in the development process of web components.
## Packages
| Package | Version | Description |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ |
| [sb-action-logger](?path=/docs/helpers-storybook-action-logger--default-story) | [![sb-action-logger](https://img.shields.io/npm/v/sb-action-logger.svg)](https://www.npmjs.com/package/sb-action-logger) | sb-action-logger |

View file

@ -4,6 +4,10 @@
A web component for displaying icons.
## Live Demo/Documentation
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/icon-system-icon) for a live demo and documentation
## How to use
### Installation
@ -16,75 +20,13 @@ npm i --save @lion/icon
import '@lion/icon/lion-icon.js';
```
### Basic usage
### Example
Include the import for both the custom element the icons you want:
```html
<script type="module">
import 'lion-icon/lion-icon.js';
import bugSvg from '../icons/bug.svg.js';
</script>
```js
import 'lion-icon/lion-icon.js';
import bugSvg from './somewhere';
```
Use it in your lit-html template:
```html
<lion-icon .svg="${bugSvg}"></lion-icon>
```
### Icon format
Icon file is an ES module with an extension `.svg.js` which exports a function like this:
```js
// bug.svg.js
export default tag => tag`
<svg focusable="false" ...>...</svg>
`;
```
Make sure you have `focusable="false"` in the icon file to prevent bugs in IE/Edge when the icon appears in tab-order.
### Accessibiltiy
You may add an `aria-label` to provide information to visually impaired users:
```html
<lion-icon .svg="${arrowLeftSvg}" aria-label="Pointing left"></lion-icon>
```
A `lion-icon` without an `aria-label` attribute will be automatically be given an `aria-hidden` attribute.
### Styling
#### Dimensions
By default, a `lion-icon` will be `1em` × `1em` (the current line-height).
A `lion-icon` may be styled like a regular HTML element:
```html
<style>
lion-icon.big {
width: 3rem;
height: 3rem;
}
</style>
```
#### SVG Styling
`lion-icon` uses SVGs and may be styled with CSS, using CSS properties such as `fill` and `stroke`:
```html
<style>
lion-icon.strong {
fill: azure;
stroke: lightsteelblue;
}
</style>
<lion-icon .icon="${arrowSvg}" class="strong"></lion-icon>
```
See [SVG and CSS](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/SVG_and_CSS) on MDN web docs for more information.

View file

@ -35,7 +35,7 @@
"@lion/core": "0.3.0"
},
"devDependencies": {
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4"
}
}

View file

@ -0,0 +1,13 @@
import { html } from '@open-wc/demoing-storybook';
import '../lion-icon.js';
export default {
title: 'Icons/System/_internal',
};
export const dynamicIcon = () => html`
<lion-icon
.svg=${import('./icons/bugs/bug05.svg.js')}
aria-label="Skinny dung beatle"
></lion-icon>
`;

View file

@ -0,0 +1,6 @@
export default tag =>
tag`
<svg focusable="false" viewBox="0 0 100 100">
<path d="M0,50 L100,0 V100 z"/>
</svg>
`;

View file

@ -1,147 +0,0 @@
import { html, storiesOf } from '@open-wc/demoing-storybook';
import '../lion-icon.js';
import {
bug01,
bug02,
bug05,
bug06,
bug08,
bug12,
bug19,
bug23,
bug24,
} from './icons/bugs-collection.js';
import aliensSpaceship from './icons/space/aliens-spaceship.svg.js';
import moonFlag from './icons/space/moon-flag.svg.js';
import night from './icons/space/night.svg.js';
import sun from './icons/space/sun.svg.js';
storiesOf('Icon System|Icon')
.add(
'default',
() => html`
<style>
.icon {
width: 32px;
height: 32px;
}
</style>
<h2>Here are some bugs:</h2>
<lion-icon class="icon" .svg=${bug01}></lion-icon>
<lion-icon class="icon" .svg=${bug02}></lion-icon>
<h2>Here are some bugs with aria-label:</h2>
<lion-icon class="icon" .svg=${bug05} aria-label="Skinny dung beatle"></lion-icon>
<lion-icon class="icon" .svg=${bug06} aria-label="Butterfly"></lion-icon>
<lion-icon class="icon" .svg=${bug08} aria-label="Ant"></lion-icon>
<lion-icon class="icon" .svg=${bug12} aria-label="Striped beatle"></lion-icon>
<lion-icon class="icon" .svg=${bug19} aria-label="Beatle with long whiskers"></lion-icon>
<lion-icon class="icon" .svg=${bug23} aria-label="Swim beatle"></lion-icon>
<lion-icon class="icon" .svg=${bug24} aria-label="Big forrest ant"></lion-icon>
`,
)
.add(
'icons fit automatically',
() => html`
<style>
div {
font-size: 20px;
margin-bottom: 5px;
}
.big-para {
font-size: 1.5em;
}
.big-icon {
width: 70px;
height: 70px;
}
.medium-icon {
width: 48px;
height: 48px;
}
.styled-sun {
fill: gold;
}
.styled-sun:hover {
fill: purple;
}
dt,
dd {
display: inline-block;
vertical-align: middle;
min-width: 80px;
margin: 0;
}
</style>
<div>
<lion-icon .svg=${moonFlag}></lion-icon>
<span>A lion-icon will naturally fill its line height</span>
</div>
<br />
<dl>
<div>
<dt class="big-para"><lion-icon .svg=${night}></lion-icon></dt>
<dd class="big-para"><span>with font-size: 1.5em;</span></dd>
</div>
<div>
<dt><lion-icon .svg=${aliensSpaceship} class="big-icon"></lion-icon></dt>
<dd><span>with 70 &times; 70 pixels</span></dd>
</div>
<div>
<dt><lion-icon .svg=${sun} class="medium-icon"></lion-icon></dt>
<dd><span>unstyled icon</span></dd>
</div>
<div>
<dt><lion-icon .svg=${sun} class="styled-sun medium-icon"></lion-icon></dt>
<dd><span>with fill: gold; and :hover { fill: purple; }</span></dd>
</div>
</dl>
`,
)
.add(
'collections',
() => html`
<style>
.icon {
width: 32px;
height: 32px;
}
</style>
<code>
// load them like so <br />
import { bug05, bug06, bug08, bug12, bug19, bug23, bug24 } from
'./icons/bugs-collection.js'; </code
><br /><br />
<lion-icon class="icon" .svg=${bug05} aria-label="Skinny dung beatle"></lion-icon>
<lion-icon class="icon" .svg=${bug06} aria-label="Butterfly"></lion-icon>
<lion-icon class="icon" .svg=${bug08} aria-label="Ant"></lion-icon>
<lion-icon class="icon" .svg=${bug12} aria-label="Striped beatle"></lion-icon>
<lion-icon class="icon" .svg=${bug19} aria-label="Beatle with long whiskers"></lion-icon>
<lion-icon class="icon" .svg=${bug23} aria-label="Swim beatle"></lion-icon>
<lion-icon class="icon" .svg=${bug24} aria-label="Big forrest ant"></lion-icon>
`,
)
.add(
'dynamic icons',
() => html`
<style>
.icon {
width: 32px;
height: 32px;
}
</style>
<lion-icon
class="icon"
.svg=${import('./icons/bugs/bug05.svg.js')}
aria-label="Skinny dung beatle"
></lion-icon>
`,
);

View file

@ -0,0 +1,174 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import * as bugs from './icons/bugs-collection.js';
import arrowLeftSvg from './icons/arrowLeft.svg.js';
import '../lion-icon.js';
<Meta title="Icons/Icon" />
# Icon
A web component for displaying icons.
<Story name="Default">
{html`
<style>
lion-icon {
width: 50px;
height: 50px;
}
</style>
<lion-icon .svg="${bugs.bug01}"></lion-icon>
`}
</Story>
```html
<lion-icon .svg="${bug01}"></lion-icon>
```
## How to use
### Installation
```sh
npm i --save @lion/icon
```
```js
import '@lion/icon/lion-icon.js';
```
## Icon format
Icon file is an ES module with an extension `.svg.js` which exports a function like this:
```js
// bug.svg.js
export default tag => tag`
<svg focusable="false" ...>...</svg>
`;
```
Make sure you have `focusable="false"` in the icon file to prevent bugs in IE/Edge when the icon appears in tab-order.
### Accessibility
It is recommended to add an `aria-label` to provide information to visually impaired users:
A `lion-icon` without an `aria-label` attribute will be automatically be given an `aria-hidden` attribute.
<Story name="Accessible label">
{html`
<lion-icon .svg="${arrowLeftSvg}" aria-label="Pointing left"></lion-icon>
`}
</Story>
```html
<lion-icon .svg="${arrowLeftSvg}" aria-label="Pointing left"></lion-icon>
```
### Styling
By default, a `lion-icon` will be `1em` × `1em` (the current line-height).
`lion-icon` uses SVGs and may be styled with CSS, including using CSS properties such as `fill` and `stroke`:
<Story name="Styling">
{html`
<style>
lion-icon.custom {
width: 50px;
height: 50px;
fill: blue;
stroke: red;
}
</style>
<lion-icon class="custom" .svg="${bugs.bug02}" aria-label="Pointing left"></lion-icon>
`}
</Story>
```html
<style>
lion-icon {
fill: blue;
stroke: lightsteelblue;
}
</style>
<lion-icon .icon="${bug02}"></lion-icon>
```
See <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/SVG_and_CSS" target="_blank">SVG and CSS</a> on MDN web docs for more information.
### Collections
Due to the `.svg.js` format using ES Modules, it is very easy to compose and load your own icon collections.
You can bundle them like this:
```js
import bug01 from './bugs/bug01.svg.js';
import bug02 from './bugs/bug02.svg.js';
import bug05 from './bugs/bug05.svg.js';
import bug06 from './bugs/bug06.svg.js';
import bug08 from './bugs/bug08.svg.js';
import bug12 from './bugs/bug12.svg.js';
import bug19 from './bugs/bug19.svg.js';
import bug23 from './bugs/bug23.svg.js';
import bug24 from './bugs/bug24.svg.js';
export { bug01, bug02, bug05, bug06, bug08, bug12, bug19, bug23, bug24 };
```
<Story name="Collections">
{html`
<style>
.icon-collection lion-icon {
height: 50px;
width: 50px;
}
</style>
<div class="icon-collection">
<lion-icon .svg=${bugs.bug05} aria-label="Skinny dung beatle"></lion-icon>
<lion-icon .svg=${bugs.bug06} aria-label="Butterfly"></lion-icon>
<lion-icon .svg=${bugs.bug08} aria-label="Ant"></lion-icon>
<lion-icon .svg=${bugs.bug12} aria-label="Striped beatle"></lion-icon>
<lion-icon .svg=${bugs.bug19} aria-label="Beatle with long whiskers"></lion-icon>
<lion-icon .svg=${bugs.bug23} aria-label="Swim beatle"></lion-icon>
<lion-icon .svg=${bugs.bug24} aria-label="Big forrest ant"></lion-icon>
</div>
`}
</Story>
And then use them by either importing them all:
```js
import * as bugs from './icons/bugs-collection.js';
```
Or one by one:
```js
import {
bug01,
bug02,
bug05,
bug06,
bug08,
bug12,
bug19,
bug23,
bug24,
} from './icons/bugs-collection.js';
```
### Dynamic import
It is also possible to dynamically import the `.svg.js` file.
This will load the icon asynchronously.
<Story id="icons-system-internal--dynamic-icon" />
```js
<lion-icon .svg=${import('./icons/bugs/bug05.svg.js')} aria-label="Skinny dung beatle"></lion-icon>
```
The demo is currently disabled for this feature due to an issue with Storybook.

View file

@ -4,19 +4,9 @@
`lion-input-amount` component is based on the generic text input field. Its purpose is to provide a way for users to fill in an amount.
## Features
## Live Demo/Documentation
- based on [lion-input](../input)
- makes use of [formatNumber](../localize/docs/number.md) for formatting and parsing.
- option to show currency as a suffix
- option to override locale to change the formatting and parsing
- option to provide additional format options overrides
- default label in different languages
- can make use of number specific [validators](../validate/docs/ValidationSystem.md) with corresponding error messages in different languages
- IsNumber (default)
- MinNumber
- MaxNumber
- MinMaxNumber
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-input-amount) for a live demo and API documentation
## How to use
@ -28,17 +18,10 @@ npm i --save @lion/input-amount
```js
import '@lion/input-amount/lion-input-amount.js';
// validator import example
import { Required, MinNumber } from '@lion/validate';
```
### Example
```html
<lion-input-amount
name="amount"
currency="USD"
.validators="${[new Required(), new MinNumber(100)]}"
></lion-input-amount>
<lion-input-amount name="amount" currency="USD"></lion-input-amount>
```

View file

@ -39,7 +39,7 @@
"@lion/validate": "0.5.4"
},
"devDependencies": {
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4"
}
}

View file

@ -1,91 +0,0 @@
import { Required } from '@lion/validate';
import { html, storiesOf } from '@open-wc/demoing-storybook';
import '../lion-input-amount.js';
storiesOf('Forms|Input Amount')
.add(
'Default',
() => html`
<lion-input-amount label="Amount" .validators="${[new Required()]}" .modelValue=${123456.78}>
</lion-input-amount>
`,
)
.add(
'Negative number',
() => html`
<lion-input-amount label="Amount" .validators="${[new Required()]}" .modelValue=${-123456.78}>
</lion-input-amount>
`,
)
.add(
'Set USD as currency',
() => html`
<lion-input-amount label="Price" currency="USD" .modelValue=${123456.78}> </lion-input-amount>
`,
)
.add(
'Set JOD as currency',
() => html`
<lion-input-amount label="Price" currency="JOD" .modelValue=${123456.78}> </lion-input-amount>
`,
)
.add(
'Force locale to nl-NL',
() => html`
<lion-input-amount
label="Price"
currency="JOD"
.formatOptions="${{ locale: 'nl-NL' }}"
.modelValue=${123456.78}
>
</lion-input-amount>
`,
)
.add(
'Force locale to en-US',
() => html`
<lion-input-amount
label="Price"
currency="YEN"
.formatOptions="${{ locale: 'en-US' }}"
.modelValue=${123456.78}
>
</lion-input-amount>
`,
)
.add(
'Faulty prefilled',
() => html`
<lion-input-amount
label="Amount"
help-text="Faulty prefilled input will be cleared"
.modelValue=${'foo'}
>
</lion-input-amount>
`,
)
.add(
'Show no fractions',
() => html`
<lion-input-amount
label="Amount"
help-text="Prefilled and formatted"
.formatOptions=${{
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}}
.modelValue=${20}
>
</lion-input-amount>
<p>
Make sure to set the modelValue last as otherwise formatOptions will not be taken into
account
</p>
<p>
You can copy paste <input value="4000,0" /> and it will become 4000 independent of your
locale. <br />
If you write 4000,0 manually then it will become 4000 or 40000 dependent on your locale.
</p>
`,
);

View file

@ -0,0 +1,193 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import { loadDefaultFeedbackMessages } from '@lion/validate';
import '../lion-input-amount.js';
<Meta title="Forms/Input Amount" parameters={{ component: 'lion-input-amount' }} />
# Input Amount
`lion-input-amount` component is based on the generic text input field. Its purpose is to provide a way for users to fill in an amount.
For formatting, we use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat" target="_blank">Intl NumberFormat</a> with some overrides.
For parsing user input, we provide our own parser that takes into account a number of heuristics, locale and ignores invalid characters.
Valid characters are digits and separators. Formatting happens on-blur.
If there are no valid characters in the input whatsoever, it will provide an error feedback.
<Story name="Default">
{() => {
loadDefaultFeedbackMessages();
return html`
<lion-input-amount name="amount" currency="USD"></lion-input-amount>
`;
}}
</Story>
```html
<lion-input-amount name="amount"></lion-input-amount>
```
## Features
- Based on [lion-input](?path=/docs/forms-system-overview)
- Makes use of [formatNumber](?path=/docs/localize-numbers) for formatting and parsing.
- Option to show currency as a suffix
- Option to override locale to change the formatting and parsing
- Option to provide additional format options overrides
- Default label in different languages
- Can make use of number specific [validators](?path=/docs/forms-system-validate-system) with corresponding error messages in different languages
- IsNumber (default)
- MinNumber
- MaxNumber
- MinMaxNumber
## How to use
### Installation
```sh
npm i --save @lion/input-amount
```
```js
import '@lion/input-amount/lion-input-amount.js';
```
## Examples
### Negative Number
It will accept negative numbers with a minus symbol.
<Story name="Negative number">
{html`
<lion-input-amount label="Amount" .modelValue=${-123456.78}></lion-input-amount>
`}
</Story>
```html
<lion-input-amount label="Amount" .modelValue=${-123456.78}></lion-input-amount>
```
### Set currency suffix
You can optionally set a currency suffix with the `currency` attribute.
<Story name="Currency suffix">
{html`
<lion-input-amount label="Price" currency="USD" .modelValue=${123456.78}></lion-input-amount>
`}
</Story>
```html
<lion-input-amount label="Price" currency="USD" .modelValue=${123456.78}></lion-input-amount>
```
### Force locale
Locale can be forced for a specific `lion-input-amount`. It will format the amount according to this locale.
<Story name="Force locale">
{html`
<lion-input-amount
label="Price"
currency="JOD"
.formatOptions=${{ locale: 'nl-NL' }}
.modelValue=${123456.78}
></lion-input-amount>
`}
</Story>
> The separators are now flipped due to Dutch locale. On top of that, due to JOD currency, the minimum amount of decimals is 3 by default for this currency.
```html
<lion-input-amount
label="Price"
currency="JOD"
.formatOptions=${{ locale: 'nl-NL' }}
.modelValue=${123456.78}
></lion-input-amount>
```
### Faulty prefilled
This example will show the error message by prefilling it with a faulty `modelValue`.
> If there is 1 or more digit in the input, it will ignore invalid characters instead of showing an error feedback message.
<Story name="Faulty prefilled">
{html`
<lion-input-amount
label="Amount"
help-text="Faulty prefilled input will cause error feedback"
.modelValue=${'foo'}
></lion-input-amount>
`}
</Story>
```js
import { loadDefaultFeedbackMessages } from '@lion/validate';
loadDefaultFeedbackMessages();
```
```html
<lion-input-amount
label="Amount"
help-text="Faulty prefilled input will cause error feedback"
.modelValue=${'foo'}
></lion-input-amount>
```
### Show no decimals
You can override certain formatting options similar to how you would do this when using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat" target="_blank">Intl NumberFormat</a>.
This example shows formatting to whole numbers.
<Story name="No decimals">
{html`
<lion-input-amount
label="Amount"
help-text="Prefilled and formatted"
.formatOptions=${{
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}}
.modelValue=${20}
>
</lion-input-amount>
`}
</Story>
```html
<lion-input-amount
label="Amount"
help-text="Prefilled and formatted"
.modelValue=${20}
.formatOptions=${{
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}}
>
</lion-input-amount>
```
### Paste behavior
For copy pasting numbers into the input-amount, there is slightly different parsing behavior.
Normally, when it receives an input with only 1 separator character, we check the locale to determine whether this character is a thousand separator, or a decimal separator.
When a user pastes the input from a different source, we find this approach (locale-based) quite unreliable, because it may have been copied from somewhere with a different locale.
Therefore, we use the heuristics based method to parse the input when it is pasted by the user.
#### What this means
If the user in an English locale types `400,0` it will become `4,000.00`
because the locale determines that the comma is a thousand separator, not a decimal separator.
If the user in an English locale pastes `400,0` instead, it will become `400.00` because we cannot rely on locale.
Therefore, instead, we determine that the comma cannot be a thousand separator because it is not followed by 3 digits after.
It is more likely to be a decimal separator.

View file

@ -4,17 +4,9 @@
`lion-input-date` component is based on the generic text input field. Its purpose is to provide a way for users to fill in a date.
## Features
## Live Demo/Documentation
- based on [lion-input](../input)
- makes use of [formatDate](../localize/docs/date.md) for formatting and parsing.
- option to overwrite locale to change the formatting and parsing
- default label in different languages
- can make use of date specific [validators](../validate/docs/ValidationSystem.md) with corresponding error messages in different languages
- IsDate (default)
- MinDate
- MaxDate
- MinMaxDate
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-input-date) for a live demo and API documentation
## How to use
@ -26,16 +18,10 @@ npm i --save @lion/input-date
```js
import '@lion/input-date/lion-input-date.js';
// validator import example
import { Required, MinDate } from '@lion/validate';
```
### Example
```html
<lion-input-date
name="date"
.validators="${[new Required(), new MinDate(new Date('2018/05/24'))]}"
></lion-input-date>
<lion-input-date></lion-input-date>
```

View file

@ -39,7 +39,7 @@
"@lion/validate": "0.5.4"
},
"devDependencies": {
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4"
}
}

View file

@ -1,58 +0,0 @@
import { formatDate } from '@lion/localize';
import { MaxDate, MinDate, MinMaxDate } from '@lion/validate';
import { html, storiesOf } from '@open-wc/demoing-storybook';
import '../lion-input-date.js';
storiesOf('Forms|Input Date')
.add(
'Default',
() => html`
<lion-input-date label="Date" .modelValue=${new Date('2017/06/15')}> </lion-input-date>
`,
)
.add(
'Validation',
() => html`
<lion-input-date label="IsDate" .modelValue=${new Date('foo')}> </lion-input-date>
<lion-input-date
label="MinDate"
help-text="Enter a date greater than or equal to today."
.modelValue=${new Date('2018/05/30')}
.validators=${[new MinDate(new Date())]}
>
</lion-input-date>
<lion-input-date
label="MaxDate"
help-text="Enter a date smaller than or equal to today."
.modelValue=${new Date('2100/05/30')}
.validators=${[new MaxDate(new Date())]}
>
</lion-input-date>
<lion-input-date
label="MinMaxDate"
.modelValue=${new Date('2018/05/30')}
.validators=${[
new MinMaxDate({ min: new Date('2018/05/24'), max: new Date('2018/06/24') }),
]}
>
<div slot="help-text">
Enter a date between ${formatDate(new Date('2018/05/24'))} and
${formatDate(new Date('2018/06/24'))}.
</div>
</lion-input-date>
`,
)
.add(
'Faulty prefilled',
() => html`
<lion-input-date
label="Date"
help-text="Faulty prefilled input will be cleared"
.modelValue=${'foo'}
>
</lion-input-date>
`,
);

View file

@ -0,0 +1,155 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import { loadDefaultFeedbackMessages, MinDate, MinMaxDate, MaxDate } from '@lion/validate';
import { formatDate } from '@lion/localize';
import '../lion-input-date.js';
<Meta title="Forms/Input Date" parameters={{ component: 'lion-input-date' }} />
# Input Date
`lion-input-date` component is based on the generic text input field. Its purpose is to provide a way for users to fill in a date.
<Story name="Default">
{() => {
loadDefaultFeedbackMessages();
return html`
<lion-input-date label="Date"></lion-input-date>
`;
}}
</Story>
```html
<lion-input-date label="Date"></lion-input-date>
```
## Features
- Based on [lion-input](?path=/docs/forms-input)
- Makes use of [formatDate](?path=/docs/localize-dates) for formatting and parsing.
- Option to override locale to change the formatting and parsing
- Default label in different languages
- Can make use of date specific [validators](?path=/docs/forms-system-validate-system) with corresponding error messages in different languages
- IsDate (default)
- MinDate
- MaxDate
- MinMaxDate
## How to use
### Installation
```sh
npm i --save @lion/input-date
```
```js
import '@lion/input-date/lion-input-date.js';
```
## Examples
### Validation
<Story name="Validation">
{html`
<style>
lion-input-date {
margin-bottom: 30px;
}
</style>
<lion-input-date label="IsDate" .modelValue=${new Date('foo')}> </lion-input-date>
<lion-input-date
label="MinDate"
help-text="Enter a date greater than or equal to today."
.modelValue=${new Date('2018/05/30')}
.validators=${[new MinDate(new Date())]}
>
</lion-input-date>
<lion-input-date
label="MaxDate"
help-text="Enter a date smaller than or equal to today."
.modelValue=${new Date('2100/05/30')}
.validators=${[new MaxDate(new Date())]}
>
</lion-input-date>
<lion-input-date
label="MinMaxDate"
.modelValue=${new Date('2018/05/30')}
.validators=${[
new MinMaxDate({ min: new Date('2018/05/24'), max: new Date('2018/06/24') }),
]}
>
<div slot="help-text">
Enter a date between ${formatDate(new Date('2018/05/24'))} and
${formatDate(new Date('2018/06/24'))}.
</div>
</lion-input-date>
`}
</Story>
```js
import { loadDefaultFeedbackMessages, MinDate, MinMaxDate, MaxDate } from '@lion/validate';
import { formatDate } from '@lion/localize';
```
IsDate
```html
<lion-input-date label="IsDate" .modelValue=${new Date('foo')}> </lion-input-date>
```
MinDate
```html
<lion-input-date
label="MinDate"
help-text="Enter a date greater than or equal to today."
.modelValue=${new Date('2018/05/30')}
.validators=${[new MinDate(new Date())]}
></lion-input-date>
```
MaxDate
```html
<lion-input-date
label="MaxDate"
help-text="Enter a date smaller than or equal to today."
.modelValue=${new Date('2100/05/30')}
.validators=${[new MaxDate(new Date())]}
></lion-input-date>
```
MinMaxDate
```html
<lion-input-date
label="MinMaxDate"
.modelValue=${new Date('2018/05/30')}
.validators=${[
new MinMaxDate({ min: new Date('2018/05/24'), max: new Date('2018/06/24') }),
]}
>
<div slot="help-text">
Enter a date between ${formatDate(new Date('2018/05/24'))} and
${formatDate(new Date('2018/06/24'))}.
</div>
</lion-input-date>
```
### Faulty prefilled
<Story name="Faulty prefilled">
{html`
<lion-input-date
label="Date"
help-text="Faulty prefilled input will be cleared"
.modelValue=${'foo'}
></lion-input-date>
`}
</Story>
```html
<lion-input-date
label="Date"
help-text="Faulty prefilled input will be cleared"
.modelValue=${'foo'}
></lion-input-date>
```

View file

@ -3,19 +3,9 @@
`lion-input-datepicker` component is based on the date text input field. Its purpose is to provide a way for users to fill in a date with a datepicker.
For an input field with a big range, such as `birthday-input`, a datepicker is not the ultimate tool, so use the standard [lion-input-date](../input-date).
## Features
## Live Demo/Documentation
- input field with a datepicker to help to choose a date
- based on [lion-input-date](../input-date)
- makes use of [lion-calendar](../calendar) inside the datepicker
- makes use of [formatDate](../localize/docs/date.md) for formatting and parsing.
- option to overwrite locale to change the formatting and parsing
- can make use of date specific [validators](../validate/docs/DefaultVaValidationSystemlidators.md) with corresponding error messages in different languages
- IsDate (default)
- MinDate
- MaxDate
- MinMaxDate
- IsDateDisabled
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-input-datepicker) for a live demo and API documentation
## How to use
@ -27,16 +17,10 @@ npm i --save @lion/input-datepicker
```js
import '@lion/input-datepicker/lion-input-datepicker.js';
// validator import example
import { Required, MinDate } from '@lion/validate';
```
### Example
```html
<lion-input-datepicker
name="date"
.validators="${[new Required(), new MinDate(new Date('2018/05/24'))]}"
></lion-input-datepicker>
<lion-input-datepicker label="Date" name="date"></lion-input-datepicker>
```

View file

@ -46,7 +46,7 @@
},
"devDependencies": {
"@lion/button": "0.4.6",
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4",
"sinon": "^7.2.2"
}

View file

@ -1,64 +0,0 @@
import { formatDate } from '@lion/localize';
import { IsDateDisabled, MinMaxDate } from '@lion/validate';
import { html, storiesOf } from '@open-wc/demoing-storybook';
import '../lion-input-datepicker.js';
storiesOf('Forms|Input Datepicker')
.add(
'Default',
() => html`
<lion-input-datepicker label="Date"> </lion-input-datepicker>
`,
)
.add(
'Validation',
() => html`
<lion-input-datepicker
label="MinMaxDate"
.modelValue=${new Date('2018/05/30')}
.validators=${[
new MinMaxDate({ min: new Date('2018/05/24'), max: new Date('2018/06/24') }),
]}
>
<div slot="help-text">
Enter a date between ${formatDate(new Date('2018/05/24'))} and
${formatDate(new Date('2018/06/24'))}.
</div>
</lion-input-datepicker>
<lion-input-datepicker
label="IsDateDisabled"
help-text="You're not allowed to choose any 15th."
.validators=${[new IsDateDisabled(d => d.getDate() === 15)]}
>
</lion-input-datepicker>
`,
)
.add(
'With calendar-heading',
() => html`
<lion-input-datepicker
label="Date"
.calendarHeading="${'Custom heading'}"
.modelValue=${new Date()}
>
</lion-input-datepicker>
`,
)
.add(
'Disabled',
() => html`
<lion-input-datepicker label="Disabled" disabled></lion-input-datepicker>
`,
)
.add(
'Readonly',
() => html`
<lion-input-datepicker
help-text="Notice that it's not possible to open the calendar on readonly inputs"
label="Readonly"
readonly
.modelValue="${new Date()}"
></lion-input-datepicker>
`,
);

View file

@ -0,0 +1,169 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import { loadDefaultFeedbackMessages, MinMaxDate, IsDateDisabled } from '@lion/validate';
import { formatDate } from '@lion/localize';
import '../lion-input-datepicker.js';
<Meta title="Forms/Input Datepicker" parameters={{ component: 'lion-input-datepicker' }} />
# Input Datepicker
`lion-input-datepicker` component is based on the date text input field. Its purpose is to provide a way for users to fill in a date with a datepicker.
For an input field with a big range, such as `birthday-input`, a datepicker is not the best choice due to the high variance between possible inputs.
We encourage using the standard [lion-input-date](?path=/docs/form-component-input-date) for this.
<Story name="Default">
{() => {
loadDefaultFeedbackMessages();
return html`
<lion-input-datepicker label="Date" name="date"></lion-input-datepicker>
`;
}}
</Story>
```html
<lion-input-datepicker label="Date" name="date"></lion-input-datepicker>
```
## Features
- Input field with a datepicker to help to choose a date
- Based on [lion-input-date](?path=/docs/form-component-input-date)
- Makes use of [lion-calendar](?path=/docs/calendar-standalone) inside the datepicker
- Makes use of [formatDate](?path=/docs/localize-dates) for formatting and parsing.
- Option to overwrite locale to change the formatting and parsing
- Can make use of date specific [validators](?path=/docs/forms-system-validate-system) with corresponding error messages in different languages
- IsDate (default)
- MinDate
- MaxDate
- MinMaxDate
- IsDateDisabled
## How to use
### Installation
```sh
npm i --save @lion/input-datepicker
```
```js
import '@lion/input-datepicker/lion-input-datepicker.js';
```
## Examples
### Validation
Below are examples of different validators for dates.
<Story name="Validation">
{html`
<style>
lion-input-datepicker {
margin-bottom: 30px;
}
</style>
<lion-input-datepicker
label="MinMaxDate"
.modelValue=${new Date('2018/05/30')}
.validators=${[
new MinMaxDate({ min: new Date('2018/05/24'), max: new Date('2018/06/24') }),
]}
>
<div slot="help-text">
Enter a date between ${formatDate(new Date('2018/05/24'))} and
${formatDate(new Date('2018/06/24'))}.
</div>
</lion-input-datepicker>
<lion-input-datepicker
label="IsDateDisabled"
help-text="You're not allowed to choose any 15th."
.validators=${[new IsDateDisabled(d => d.getDate() === 15)]}
></lion-input-datepicker>
`}
</Story>
```js
import { loadDefaultFeedbackMessages, MinMaxDate, IsDateDisabled } from '@lion/validate';
import { formatDate } from '@lion/localize';
loadDefaultFeedbackMessages();
```
```html
<lion-input-datepicker
label="MinMaxDate"
.modelValue=${new Date('2018/05/30')}
.validators=${[
new MinMaxDate({ min: new Date('2018/05/24'), max: new Date('2018/06/24') }),
]}
>
<div slot="help-text">
Enter a date between ${formatDate(new Date('2018/05/24'))} and
${formatDate(new Date('2018/06/24'))}.
</div>
</lion-input-datepicker>
```
```html
<lion-input-datepicker
label="IsDateDisabled"
help-text="You're not allowed to choose any 15th."
.validators=${[new IsDateDisabled(d => d.getDate() === 15)]}
></lion-input-datepicker>
```
### Calendar heading
You can modify the heading of the calendar with the `.calendarHeading` property or `calendar-heading` attribute for simple values.
By default, it will take the label value.
<Story name="Calendar heading">
{html`
<lion-input-datepicker
label="Date"
.calendarHeading="${'Custom heading'}"
.modelValue=${new Date()}
></lion-input-datepicker>
`}
</Story>
```html
<lion-input-datepicker
label="Date"
.calendarHeading="${'Custom heading'}"
.modelValue=${new Date()}
></lion-input-datepicker>
```
### Disabled
You can disable datepicker inputs.
<Story name="Disabled">
{html`
<lion-input-datepicker label="Disabled" disabled></lion-input-datepicker>
`}
</Story>
```html
<lion-input-datepicker label="Disabled" disabled></lion-input-datepicker>
```
### Read only
You can set datepicker inputs to `readonly`, which will prevent the user from opening the calendar popup.
<Story name="Read only">
{html`
<lion-input-datepicker label="Readonly" readonly .modelValue="${new Date()}">
</lion-input-datepicker>
`}
</Story>
```html
<lion-input-datepicker label="Readonly" readonly .modelValue="${new Date()}">
</lion-input-datepicker>
```

View file

@ -4,11 +4,9 @@
`lion-input-email` component is based on the generic text input field. Its purpose is to provide a way for users to fill in an email.
## Features
## Live Demo/Documentation
- based on [lion-input](../input)
- makes use of email [validators](../validate/docs/ValidationSystem.md) with corresponding error messages in different languages
- IsEmail (default)
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-input-email) for a live demo and API documentation
## How to use
@ -20,13 +18,10 @@ npm i --save @lion/input-email
```js
import '@lion/input-email/lion-input-email.js';
// validator import example
import { Required } from '@lion/validate';
```
### Example
```html
<lion-input-email label="email" name="email" .validators="${['new Required()]}"></lion-input-email>
<lion-input-email label="Email" name="email"></lion-input-email>
```

View file

@ -39,7 +39,7 @@
"@lion/validate": "0.5.4"
},
"devDependencies": {
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4"
}
}

View file

@ -1,46 +0,0 @@
import { Validator } from '@lion/validate';
import { html, storiesOf } from '@open-wc/demoing-storybook';
import '../../fieldset/lion-fieldset.js';
import '../lion-input-email.js';
storiesOf('Forms|Input Email')
.add(
'Default',
() => html`
<lion-input-email name="email" label="Label"></lion-input-email>
`,
)
.add(
'Faulty prefilled',
() => html`
<lion-input-email .modelValue=${'foo'} label="Email"></lion-input-email>
`,
)
.add('Custom validator', () => {
class GmailOnly extends Validator {
constructor(...args) {
super(...args);
this.name = 'GmailOnly';
}
execute(value) {
let hasError = false;
if (!(value.indexOf('gmail.com') !== -1)) {
hasError = true;
}
return hasError;
}
static async getMessage() {
return 'You can only use gmail.com email addresses.';
}
}
return html`
<lion-input-email
.modelValue=${'foo@bar.com'}
.validators=${[new GmailOnly()]}
label="Email"
></lion-input-email>
`;
});

View file

@ -0,0 +1,126 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import { loadDefaultFeedbackMessages, Validator } from '@lion/validate';
import '../lion-input-email.js';
<Meta title="Forms/Input Email" parameters={{ component: 'lion-input-email' }} />
# Input Email
`lion-input-email` component is based on the generic text input field. Its purpose is to provide a way for users to fill in an email.
<Story name="Default">
{() => {
loadDefaultFeedbackMessages();
return html`
<lion-input-email label="Email" name="email"></lion-input-email>
`;
}}
</Story>
```html
<lion-input-email label="Email" name="email"></lion-input-email>
```
## Features
- Based on [lion-input](?path=/docs/forms-input)
- Makes use of email [validators](?path=/docs/forms-system-validate-system) with corresponding error messages in different languages
- IsEmail (default)
## How to use
### Installation
```sh
npm i --save @lion/input-email
```
```js
import '@lion/input-email/lion-input-email.js';
```
## Examples
### Faulty Prefilled
When prefilling with a faulty input, an error feedback message will show.
Use `loadDefaultFeedbackMessages` to get our default feedback messages displayed on it.
<Story name="Faulty prefilled">
{html`
<lion-input-email .modelValue=${'foo'} label="Email"></lion-input-email>
`}
</Story>
```js
import { loadDefaultFeedbackMessages } from 'lion/validate';
loadDefaultFeedbackMessages();
```
```html
<lion-input-email .modelValue=${'foo'} label="Email"></lion-input-email>
```
### Custom Validator
<Story name="Custom Validator">
{() => {
class GmailOnly extends Validator {
constructor(...args) {
super(...args);
this.name = 'GmailOnly';
}
execute(value) {
let hasError = false;
if (!(value.indexOf('gmail.com') !== -1)) {
hasError = true;
}
return hasError;
}
static async getMessage() {
return 'You can only use gmail.com email addresses.';
}
}
return html`
<lion-input-email
.modelValue=${'foo@bar.com'}
.validators=${[new GmailOnly()]}
label="Email"
></lion-input-email>
`;
}}
</Story>
```js
import { Validator } from '@lion/validate';
class GmailOnly extends Validator {
constructor(...args) {
super(...args);
this.name = 'GmailOnly';
}
execute(value) {
let hasError = false;
if (!(value.indexOf('gmail.com') !== -1)) {
hasError = true;
}
return hasError;
}
static async getMessage() {
return 'You can only use gmail.com email addresses.';
}
}
```
```html
<lion-input-email
.modelValue=${'foo@bar.com'}
.validators=${[new GmailOnly()]}
label="Email"
></lion-input-email>
```

View file

@ -4,13 +4,9 @@
`lion-input-iban` component is based on the generic text input field. Its purpose is to provide a way for users to fill in an iban.
## Features
## Live Demo/Documentation
- based on [lion-input](../input)
- default label in different languages
- makes use of IBAN specific [validate](../validate) with corresponding error messages in different languages
- IsIBAN (default)
- IsCountryIBAN
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-input-iban) for a live demo and API documentation
## How to use
@ -22,16 +18,10 @@ npm i --save @lion/input-amount
```js
import '@lion/input-amount/lion-input-amount.js';
// validator import example
import { Required, IsCountryIBAN } from '@lion/validate';
```
### Example
```html
<lion-input-iban
name="account"
.validators="${[new Required(), new IsCountryIBAN('BE')]}"
></lion-input-iban>
<lion-input-iban label="Account" name="account"></lion-input-iban>
```

View file

@ -40,7 +40,7 @@
"@lion/validate": "0.5.4"
},
"devDependencies": {
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4"
}
}

View file

@ -1,42 +0,0 @@
import { html, storiesOf } from '@open-wc/demoing-storybook';
import { IsCountryIBAN } from '../index.js';
import '../lion-input-iban.js';
storiesOf('Forms|Input IBAN')
.add(
'Default',
() => html`
<lion-input-iban name="iban" label="IBAN"></lion-input-iban>
`,
)
.add(
'Prefilled',
() => html`
<lion-input-iban
.modelValue=${'NL20INGB0001234567'}
name="iban"
label="IBAN"
></lion-input-iban>
`,
)
.add(
'Faulty prefilled',
() => html`
<lion-input-iban
.modelValue=${'NL20INGB0001234567XXXX'}
name="iban"
label="IBAN"
></lion-input-iban>
`,
)
.add(
'Country restrictions',
() => html`
<lion-input-iban
.modelValue=${'DE89370400440532013000'}
.validators=${[new IsCountryIBAN('NL')]}
name="iban"
label="IBAN"
></lion-input-iban>
`,
);

View file

@ -0,0 +1,120 @@
import { Story, Meta, html } from '@open-wc/demoing-storybook';
import { loadDefaultFeedbackMessages } from '@lion/validate';
import { IsCountryIBAN } from '../src/validators.js';
import '../lion-input-iban.js';
<Meta title="Forms/Input Iban" parameters={{ component: 'lion-input-iban' }} />
# Input IBAN
`lion-input-iban` component is based on the generic text input field.
Its purpose is to provide a way for users to fill in an IBAN (International Bank Account Number).
<Story name="Default">
{() => {
loadDefaultFeedbackMessages();
return html`
<lion-input-iban label="Account" name="account"></lion-input-iban>
`;
}}
</Story>
```html
<lion-input-iban label="Account" name="account"></lion-input-iban>
```
## Features
- Based on [lion-input](?path=/docs/forms-input)
- Default label in different languages
- Makes use of IBAN specific [validate](?path=/docs/forms-system-validate-system) with corresponding error messages in different languages
- IsIBAN (default)
- IsCountryIBAN
- Parses IBANs automatically
- Formats IBANs automatically
## How to use
### Installation
```sh
npm i --save @lion/input-amount
```
```js
import '@lion/input-amount/lion-input-amount.js';
```
## Examples
### Prefilled
<Story name="Prefilled">
{html`
<lion-input-iban
.modelValue=${'NL20INGB0001234567'}
name="iban"
label="IBAN"
></lion-input-iban>
`}
</Story>
```html
<lion-input-iban
.modelValue=${'NL20INGB0001234567'}
name="iban"
label="IBAN"
></lion-input-iban>
```
### Faulty Prefilled
<Story name="Faulty prefilled">
{html`
<lion-input-iban
.modelValue=${'NL20INGB0001234567XXXX'}
name="iban"
label="IBAN"
></lion-input-iban>
`}
</Story>
```html
<lion-input-iban
.modelValue=${'NL20INGB0001234567XXXX'}
name="iban"
label="IBAN"
></lion-input-iban>
```
### Country Restrictions
By default, we validate the input to ensure the IBAN is valid.
To get the default feedback message for this default validator, use `loadDefaultFeedbackMessages` from `@lion/validate`.
In the example below, we show how to use an additional validator that restricts the `input-iban` to IBANs from only certain countries.
<Story name="Country restrictions">
{html`
<lion-input-iban
.modelValue=${'DE89370400440532013000'}
.validators=${[new IsCountryIBAN('NL')]}
name="iban"
label="IBAN"
></lion-input-iban>
`}
</Story>
```js
import { IsCountryIBAN } from '@lion/input-iban';
```
```html
<lion-input-iban
.modelValue=${'DE89370400440532013000'}
.validators=${[new IsCountryIBAN('NL')]}
name="iban"
label="IBAN"
></lion-input-iban>
```

View file

@ -5,13 +5,9 @@
`lion-input-range` component is based on the native range input.
Its purpose is to provide a way for users to select one value from a range of values.
## Features
## Live Demo/Documentation
- Based on [lion-input](../input).
- Shows `modelValue` and `unit` above the range input.
- Shows `min` and `max` value after the range input.
- Can hide the `min` and `max` value via `no-min-max-labels`.
- Makes use of [formatNumber](../localize/docs/number.md) for formatting and parsing.
> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-input-range) for a live demo and API documentation
## How to use

View file

@ -37,7 +37,7 @@
"@lion/localize": "0.7.2"
},
"devDependencies": {
"@open-wc/demoing-storybook": "^1.1.1",
"@open-wc/demoing-storybook": "^1.6.1",
"@open-wc/testing": "^2.3.4"
}
}

Some files were not shown because too many files have changed in this diff Show more