├── .editorconfig
├── .gitignore
├── README.md
├── docs
└── comparison.md
├── index.d.ts
├── index.js
├── notify.d.ts
├── notify.js
├── package-lock.json
├── package.json
├── sync.d.ts
├── sync.js
└── test
└── index.html
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = LF
5 | indent_style = space
6 | indent_size = 4
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Change notification helpers for LitElement
2 |
3 | [](https://www.npmjs.com/package/@morbidick/lit-element-notify)
4 |
5 | Small helpers for LitElement to dispatch change notifications and two-way binding. For a comparison to PolymerElement and pure LitElement see [comparison section](docs/comparison.md) in the docs.
6 |
7 | ## Install
8 |
9 | ```bash
10 | npm install @morbidick/lit-element-notify
11 | ```
12 |
13 | ## Notify mixin
14 |
15 | Small mixin for LitElement to get easy change events via the `properties` getter.
16 |
17 | This mixin adds the `notify` option to the property definition. Similar to the LitElement `attribute` option (which reflects a property to the dom) it fires an event as soon as the property value changes. The event name depends on the following conditions:
18 |
19 | 1. `notify: true`: the property gets lowercased and `-changed` is appended (note: contrary to PolymerElement and similar to LitElements attribute handling no camelCase to kebap-case conversion is done).
20 | 2. the notify option contains a string: `notify: 'success-event'` fires an event named `success-event`.
21 | 3. `notify: true` is set and the attribute option is a string (`attribute: 'attribute-name'`): the attribute name will be suffixed with `-changed`.
22 |
23 | The updated value of the property is available in `event.detail.value`.
24 |
25 | ```javascript
26 | import { LitElement, html } from 'lit-element';
27 | import LitNotify from '@morbidick/lit-element-notify/notify.js';
28 |
29 | class NotifyingElement extends LitNotify(LitElement) {
30 | static get properties() {
31 | return {
32 |
33 | // property names get lowercased and the -changed suffix is added
34 | token: {
35 | type: String,
36 | notify: true, // fires token-changed
37 | },
38 | camelCase: {
39 | type: String,
40 | notify: true, // fires camelcase-changed
41 | },
42 |
43 | // an explicit event name can be set
44 | thing: {
45 | type: String,
46 | notify: 'success-event', // fires success-event
47 | },
48 |
49 | // if an attribute value is set, -changed is appended
50 | myMessage: {
51 | type: String,
52 | attribute: 'my-message',
53 | notify: true, // fires my-message-changed
54 | },
55 |
56 | };
57 | }
58 | }
59 | ```
60 |
61 | ## Sync directive
62 |
63 | lit-html directive to synchronize an element property to a childs property, adding two-way binding to lit-element.
64 | The directive takes two parameters, the property name and an optional event name on which to sync.
65 |
66 | ### Usage
67 |
68 | ```javascript
69 | import { LitElement, html } from 'lit-element';
70 | import LitSync from '@morbidick/lit-element-notify/sync.js';
71 |
72 | class SyncElement extends LitSync(LitElement) {
73 |
74 | // Syncing the child property `token` with the parent property `myProperty` when `token-changed`
75 | // is fired or `myProperty` set.
76 | render() { return html`
77 |
78 | `}
79 |
80 | // Syncing the child property `myMessage` with the event explicitly set to `my-message-changed`
81 | // (mainly used to map from the camelCase property to the kebap-case event as PolymerElement does).
82 | render() { return html`
83 |
84 | `}
85 |
86 | }
87 | ```
88 |
--------------------------------------------------------------------------------
/docs/comparison.md:
--------------------------------------------------------------------------------
1 | # Comparison
2 |
3 | This section tries to make the individual use cases clearer.
4 |
5 | ## Firing an event on property change
6 |
7 | ### PolymerElement
8 |
9 | ```js
10 | class NotifyingElement extends PolymerElement {
11 | static get properties() {
12 | return {
13 | message: {
14 | type: String,
15 | notify: true
16 | }}}}
17 | ```
18 |
19 | ### LitElement
20 |
21 | ```js
22 | class NotifyingElement extends LitElement {
23 | static get properties() {
24 | return {
25 | message: {
26 | type: String
27 | }}}
28 | update(props) {
29 | super.update(props);
30 | if (props.has('message')) {
31 | this.dispatchEvent(new CustomEvent('message-changed', {
32 | detail: {
33 | value: this.message,
34 | },
35 | bubbles: false,
36 | composed: true
37 | }));
38 | }
39 | }
40 | ```
41 |
42 | ### LitElement with LitNotify mixin
43 |
44 | ```js
45 | class NotifyingElement extends LitNotify(LitElement) {
46 | static get properties() {
47 | return {
48 | message: {
49 | type: String,
50 | notify: true
51 | }}}}
52 | ```
53 |
54 | mapping a camelCase property to a kebap-case event (as PolymerElement does automaticly)
55 |
56 | ```js
57 | class NotifyingElement extends LitNotify(LitElement) {
58 | static get properties() {
59 | return {
60 | myMessage: {
61 | type: String,
62 | notify: 'my-message-changed'
63 | }}}}
64 | ```
65 |
66 | ## Two-way data binding
67 |
68 | Synchronizing a parent property with a childs property.
69 |
70 | ### PolymerElement*
71 |
72 | ```js
73 | html``
74 | ```
75 |
76 | ### Lit Element - upwards binding only
77 |
78 | ```js
79 | html` this.myProperty = e.detail.value}>`
80 | ```
81 |
82 | ### LitElement*
83 |
84 | ```js
85 | html` this.myProperty = e.detail.value}>`
86 | ```
87 |
88 | ### LitElement with sync directive*
89 |
90 | ```js
91 | html``
92 | ```
93 |
94 | * two-way binding so also updating the child when the parent property changes
95 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | export { LitNotify } from './notify';
2 | export { LitSync } from './sync';
3 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | export {LitNotify} from "./notify.js";
2 | export {LitSync} from "./sync.js";
3 |
--------------------------------------------------------------------------------
/notify.d.ts:
--------------------------------------------------------------------------------
1 | import { PropertyDeclaration, LitElement, UpdatingElement } from 'lit-element';
2 |
3 | type Constructor = new (...args: any[]) => T;
4 |
5 | interface AugmentedPropertyDeclaration extends PropertyDeclaration {
6 | /** When true will notify. Pass a string to define the event name to fire. */
7 | notify: string|Boolean
8 | }
9 |
10 | declare class NotifyingElement {
11 | static createProperty(name: string, options: AugmentedPropertyDeclaration): void
12 | }
13 |
14 | export function LitNotify(baseElement: Constructor): T & NotifyingElement
15 |
--------------------------------------------------------------------------------
/notify.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns the event name for the given property.
3 | * @param {string} name property name
4 | * @param {PropertyDeclaration} options property declaration
5 | * @return event name to fire
6 | */
7 | export function eventNameForProperty(name, { notify, attribute } = {}) {
8 | if (notify && typeof notify === 'string') {
9 | return notify;
10 | } else if (attribute && typeof attribute === 'string') {
11 | return `${attribute}-changed`;
12 | } else {
13 | return `${name.toLowerCase()}-changed`;
14 | }
15 | }
16 |
17 | // eslint-disable-next-line valid-jsdoc
18 | /**
19 | * Enables the nofity option for properties to fire change notification events
20 | *
21 | * @template TBase
22 | * @param {Constructor} baseElement
23 | */
24 | export const LitNotify = (baseElement) => class NotifyingElement extends baseElement {
25 | /**
26 | * check for changed properties with notify option and fire the events
27 | */
28 | update(changedProps) {
29 | super.update(changedProps);
30 |
31 | for (const prop of changedProps.keys()) {
32 | const declaration = this.constructor._classProperties.get(prop)
33 | if (!declaration || !declaration.notify) continue;
34 | const type = eventNameForProperty(prop, declaration)
35 | const value = this[prop]
36 | this.dispatchEvent(new CustomEvent(type, { detail: { value } }));
37 | }
38 | }
39 | };
40 |
41 | export default LitNotify;
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@morbidick/lit-element-notify",
3 | "version": "1.1.1",
4 | "description": "Small helpers for LitElement to dispatch change notifications and two-way binding",
5 | "license": "MIT",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/morbidick/lit-element-notify"
9 | },
10 | "main": "index.js",
11 | "module": "index.js",
12 | "peerDependencies": {
13 | "lit-element": "^2.0.1",
14 | "lit-html": "^1.0.0"
15 | },
16 | "devDependencies": {
17 | "@webcomponents/webcomponentsjs": "^2.2.6",
18 | "lit-element": "^2.0.1",
19 | "lit-html": "^1.0.0",
20 | "polyserve": "^0.27.15"
21 | },
22 | "scripts": {
23 | "start": "npm run serve",
24 | "serve": "polyserve --npm --module-resolution=node"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/sync.d.ts:
--------------------------------------------------------------------------------
1 | import { LitElement, UpdatingElement } from 'lit-element';
2 |
3 | type Constructor = new (...args: any[]) => T;
4 |
5 | declare class SyncElement {
6 | sync(property: string, eventName?: string): void
7 | }
8 |
9 | export function LitSync(baseElement: Constructor): T & SyncElement
10 |
--------------------------------------------------------------------------------
/sync.js:
--------------------------------------------------------------------------------
1 | import {directive} from "lit-html/lib/directive.js";
2 | import {eventNameForProperty} from "./notify.js";
3 |
4 | // eslint-disable-next-line valid-jsdoc
5 | /**
6 | * Mixin that provides a lit-html directive to sync a property to a child property
7 | *
8 | * @template TBase
9 | * @param {Constructor} baseElement
10 | */
11 | export const LitSync = (baseElement) => class extends baseElement {
12 | constructor() {
13 | super();
14 |
15 | /**
16 | * lit-html directive to sync a property to a child property
17 | *
18 | * @param {string} property - The property name
19 | * @param {string} [eventName] - Optional event name to sync on, defaults to propertyname-changed
20 | */
21 | this.sync = directive((property, eventName) => (part) => {
22 | part.setValue(this[property]);
23 |
24 | // mark the part so the listener is only attached once
25 | if (!part.syncInitialized) {
26 | part.syncInitialized = true;
27 |
28 | const notifyingElement = part.committer.element;
29 | const notifyingProperty = part.committer.name;
30 | const notifyingEvent = eventName || eventNameForProperty(notifyingProperty);
31 |
32 | notifyingElement.addEventListener(notifyingEvent, (e) => {
33 | const oldValue = this[property];
34 | this[property] = e.detail.value;
35 | if (this.__lookupSetter__(property) === undefined) {
36 | this.updated(new Map([[property, oldValue]]));
37 | }
38 | });
39 | }
40 | });
41 | }
42 | }
43 |
44 | export default LitSync;
45 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
58 |
59 |
--------------------------------------------------------------------------------