├── .eslintrc.js
├── .nojekyll
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── babel.config.json
├── build
│ ├── dist
│ │ ├── advanced-usage
│ │ │ ├── manually-observe-state
│ │ │ │ ├── index.js
│ │ │ │ ├── observe-all-state-vars
│ │ │ │ │ ├── automatic-component.js
│ │ │ │ │ ├── manual-component.js
│ │ │ │ │ └── state.js
│ │ │ │ └── observe-specific-state-vars
│ │ │ │ │ ├── specific-automatic-component.js
│ │ │ │ │ ├── specific-manual-component.js
│ │ │ │ │ └── state.js
│ │ │ ├── state-recorder-usage.js
│ │ │ └── state-var-handler
│ │ │ │ ├── custom-state-var-handler-component.js
│ │ │ │ ├── index.js
│ │ │ │ └── state.js
│ │ ├── api
│ │ │ ├── lit-state.js
│ │ │ ├── observe-state-mixin.js
│ │ │ ├── state-recorder.js
│ │ │ ├── state-var-decorator.js
│ │ │ └── state-var-handler.js
│ │ ├── basic-usage
│ │ │ ├── index.js
│ │ │ ├── no-decorator-usage
│ │ │ │ ├── index.js
│ │ │ │ ├── no-deco-component-1.js
│ │ │ │ ├── no-deco-component-2.js
│ │ │ │ └── state.js
│ │ │ ├── state-var-component-1.js
│ │ │ ├── state-var-component-2.js
│ │ │ └── state.js
│ │ ├── demo-component.js
│ │ ├── index.js
│ │ ├── intro-page.js
│ │ ├── lit-state.js
│ │ ├── state-handling
│ │ │ ├── computed-values
│ │ │ │ ├── computed-value-component.js
│ │ │ │ ├── index.js
│ │ │ │ └── state.js
│ │ │ ├── index.js
│ │ │ └── nested-states
│ │ │ │ ├── index.js
│ │ │ │ ├── nested-state-component.js
│ │ │ │ └── states.js
│ │ └── use-cases
│ │ │ ├── different-vars-on-rerender
│ │ │ ├── changing-component.js
│ │ │ ├── control-component.js
│ │ │ ├── index.js
│ │ │ └── state.js
│ │ │ ├── index.js
│ │ │ └── reconnected-components
│ │ │ ├── index.js
│ │ │ ├── reconnect-control-component.js
│ │ │ ├── reconnecting-component.js
│ │ │ └── state.js
│ ├── favicon.png
│ ├── index.html
│ ├── snowpack
│ │ └── env.js
│ └── web_modules
│ │ ├── common
│ │ ├── lit-element-336867a2.js
│ │ └── lit-style-ca615be5.js
│ │ ├── import-map.json
│ │ ├── lit-docs.js
│ │ ├── lit-element-style.js
│ │ └── lit-element.js
├── package-lock.json
├── package.json
├── public
│ ├── favicon.png
│ └── index.html
├── snowpack.config.js
└── src
│ ├── advanced-usage
│ ├── manually-observe-state
│ │ ├── index.js
│ │ ├── observe-all-state-vars
│ │ │ ├── automatic-component.js
│ │ │ ├── manual-component.js
│ │ │ └── state.js
│ │ └── observe-specific-state-vars
│ │ │ ├── specific-automatic-component.js
│ │ │ ├── specific-manual-component.js
│ │ │ └── state.js
│ ├── state-recorder-usage.js
│ └── state-var-handler
│ │ ├── custom-state-var-handler-component.js
│ │ ├── index.js
│ │ └── state.js
│ ├── api
│ ├── lit-state.js
│ ├── observe-state-mixin.js
│ ├── state-recorder.js
│ ├── state-var-decorator.js
│ └── state-var-handler.js
│ ├── basic-usage
│ ├── index.js
│ ├── no-decorator-usage
│ │ ├── index.js
│ │ ├── no-deco-component-1.js
│ │ ├── no-deco-component-2.js
│ │ └── state.js
│ ├── state-var-component-1.js
│ ├── state-var-component-2.js
│ └── state.js
│ ├── demo-component.js
│ ├── index.js
│ ├── intro-page.js
│ ├── lit-state.js
│ ├── state-handling
│ ├── computed-values
│ │ ├── computed-value-component.js
│ │ ├── index.js
│ │ └── state.js
│ ├── index.js
│ └── nested-states
│ │ ├── index.js
│ │ ├── nested-state-component.js
│ │ └── states.js
│ └── use-cases
│ ├── different-vars-on-rerender
│ ├── changing-component.js
│ ├── control-component.js
│ ├── index.js
│ └── state.js
│ ├── index.js
│ └── reconnected-components
│ ├── index.js
│ ├── reconnect-control-component.js
│ ├── reconnecting-component.js
│ └── state.js
├── lit-state.js
├── package-lock.json
└── package.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'env': {
3 | 'browser': true,
4 | 'es6': true
5 | },
6 | 'extends': 'eslint:recommended',
7 | 'globals': {
8 | 'Atomics': 'readonly',
9 | 'SharedArrayBuffer': 'readonly'
10 | },
11 | "parser": "babel-eslint",
12 | 'parserOptions': {
13 | 'ecmaVersion': 2018,
14 | 'sourceType': 'module'
15 | },
16 | 'rules': {
17 | 'indent': [
18 | 'error',
19 | 4,
20 | {
21 | 'SwitchCase': 1
22 | }
23 | ],
24 | 'linebreak-style': [
25 | 'error',
26 | 'unix'
27 | ],
28 | 'semi': [
29 | 'error',
30 | 'always'
31 | ]
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitaarik/lit-state/8cd66223612c3b115c0275f58f6cee5e900ee534/.nojekyll
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # LitState Changelog
2 |
3 |
4 | ### 1.7.0 - Non-decorator usage simplified
5 |
6 | - The static `stateVars()` getter method should now return an object with in
7 | the keys the name of the stateVar and in the values the initial value of the
8 | stateVar.
9 | - The stateVar options were previously set with the static `stateVars()` getter
10 | method, but should now be specified with the static `stateVarOptions()`
11 | getter method.
12 | - Docs updated accordingly.
13 | - Added some more descriptive comments in the code.
14 |
15 | #### To upgrade from 1.6.x
16 |
17 | For non-decorator usage, update the static `stateVar()` getter methods.
18 |
19 | Change this:
20 |
21 | ```javascript
22 | class MyState extends LitState {
23 |
24 | static get stateVars() {
25 | return {
26 | myStateVar: { option: 'value' }
27 | };
28 | }
29 |
30 | constructor() {
31 | super();
32 | this.myStateVar = 'initial value';
33 | }
34 |
35 | }
36 | ```
37 |
38 | To this:
39 |
40 | ```javascript
41 | class MyState extends LitState {
42 |
43 | static get stateVars() {
44 | return {
45 | myStateVar: 'initial value'
46 | };
47 | }
48 |
49 | static get stateVarOptions() {
50 | return {
51 | myStateVar: { option: 'value' }
52 | };
53 | }
54 |
55 | }
56 | ```
57 |
58 | If the options object is empty, you don't need to specify the static
59 | `stateVarOptions()` getter method.
60 |
61 |
62 | ### 1.6.1 - Support reconnecting components
63 |
64 | - When a component gets dynamically removed from the DOM and then added again,
65 | call `this.requestUpdate()` so that the observers get-readded again. Fixes
66 | [issue #5](https://github.com/gitaarik/lit-state/issues/5).
67 | - Added a docs page for this use case.
68 | - Reordered docs menu.
69 | - Added some comments in the code.
70 |
71 |
72 | ### 1.6.0 - Added JavaScript decorator support
73 |
74 | - The way `stateVar` variables are defined has changed. You have to update this
75 | coming from 1.5.0.
76 | - Now you can use decorators to specify your stateVars, use `@stateVar() myVar
77 | = 'initial value';` in your state class.
78 | - You can also still define `stateVar` variables without decorators. It is done
79 | in a similar way LitElement does. Check the docs for this.
80 | - Updated LitDocs and documentation content.
81 | - Overall refactors.
82 |
83 | #### To upgrade from 1.5.x
84 |
85 | Update the `stateVar` definitions, instead of:
86 |
87 | ```javascript
88 | myData = stateVar('value');
89 | ```
90 |
91 | Do this:
92 |
93 | ```javascript
94 | @stateVar() myData = 'value';
95 | ```
96 |
97 | For no-decorator usage, [look in the docs](https://gitaarik.github.io/lit-state/build/#basic-usage/no-decorator-usage/).
98 |
99 |
100 | ### 1.5.0 - Moved `asyncStateVar` to separate repository
101 |
102 | - The `asyncStateVar` is now available [here](https://github.com/gitaarik/lit-state-async-state-var)
103 | so that LitState is tiny for projects that don't use `asyncStateVar`.
104 | - Updated docs and demo app.
105 |
106 |
107 | ### 1.4.0 - Mixin primary means of making components state-aware
108 |
109 | - Making mixin usage the primary means for making your components state-aware.
110 | - Renamed the mixin class `LitStateElementMixin` to `observeState`, reads
111 | nicer, looks better, is shorter.
112 | - Updated docs and demo app to use the new mixin class.
113 |
114 |
115 | ### 1.3.0 - LitState independent from StateVar classes
116 |
117 | - `LitState` is now not dependend on the `StateVar` and `AsyncStateVar`
118 | classes. There's one `BaseStateVar` class which all types of stateVar classes
119 | should extend from. `LitState` recognizes this as a stateVar, and then
120 | handles the handling of the `get` and `set` traps to the stateVar. This
121 | allows for the possibility to factor out `AsyncStateVar` to a separate
122 | library, making LitState smaller. Because not all apps always need
123 | `AsyncStateVar`. It also allows for easily adding more (custom) types of
124 | stateVars, to extend LitState.
125 | - Some more general refactors to make things simpler.
126 | - Reordered some texts in README
127 |
128 |
129 | ### 1.2.3
130 |
131 | - Fixed bug where `isRejectedGet()` still returned `true` when a `setValue()`
132 | failed after it, and `isRejectedSet()` still returned `true` when a
133 | `getValue()` failed after it.
134 |
135 |
136 | ### 1.2.2
137 |
138 | - Updated Readme
139 |
140 |
141 | ### 1.2.1
142 |
143 | - Updated Readme
144 |
145 |
146 | ### 1.2.0
147 |
148 | - Assigning to an `asyncStateVar` now throws an error, you have to use
149 | `setValue()`. This to make usage consistent.
150 | - `setValue()` doesn't return a promise anymore.
151 | - `_pendingCache` is always set to `false` when `setValue()` succeeds, so
152 | `pushCache()` doesn't need `setValue()` to return a promise anymore.
153 | - Removed unnecessary `logStateVar()` calls for `asyncStateVar`. Only
154 | components that call `getValue()` need to re-render when the var changes.
155 | - Added demo page for components that show different state variables on
156 | re-renders.
157 | - Overall demo app refactors and UI/text improvements.
158 |
159 |
160 | ### 1.1.2
161 |
162 | - Added `setCache(value)` and `pushCache()` functionality to `asyncStateVar`.
163 | - Added demo page for `setCache(value)` and `pushCache()`.
164 | - Updated readme.
165 |
166 |
167 | ### 1.1.1
168 |
169 | - Created mixin `LitStateElementMixin`.
170 | - Added demo page for mixin `LitStateElementMixin`.
171 | - Updated demo app internals.
172 |
173 |
174 | ### 1.1.0
175 |
176 | - Created changelog.
177 | - Changed behavior of `asyncStateVar()`:
178 | - You can now have a **getter** and **setter**.
179 | - `getValue()` won't return the error from the promise's reject callback in
180 | case of an error anymore. You can get the error with the method
181 | `getError()` (for the error of the last get or set), `getErrorGet()` (for
182 | the error of the last get) or `getErrorSet()` (for the error of the last
183 | set).
184 | - Updated demo app to showcase `asyncStateVar` better.
185 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LitState
2 |
3 | ### Simple shared app state management for LitElement.
4 |
5 | LitState automatically re-renders your LitElement components, when a shared app
6 | state variable they use changes. It's like LitElement's
7 | [properties](https://lit-element.polymer-project.org/guide/properties), but
8 | then shared over multiple components.
9 |
10 | It's tiny, simple but still powerful, just like
11 | [LitElement](https://lit-element.polymer-project.org/) and
12 | [lit-html](https://lit-html.polymer-project.org/).
13 |
14 |
15 | ## Installation
16 |
17 | ```
18 | npm install lit-element-state
19 | ```
20 |
21 | *The name `lit-state` is unfortunately already taken on npm, so therefore the
22 | slightly awkward package name.*
23 |
24 |
25 | ### Current version: v1.7.0
26 |
27 | For more information and update instructions, [See the changelog](CHANGELOG.md).
28 |
29 |
30 | ## Basic idea
31 |
32 | You keep your shared state in a `LitState` derived class. This class contains
33 | `stateVar` variables that contain the state. This class can also contain helper
34 | functions that modify the state. Decorate your `LitElement` classes with the
35 | `observeState()` mixin. This makes your components automatically re-render
36 | whenever a `stateVar` they use changes.
37 |
38 |
39 | ## Usage
40 |
41 | ### 1. Create a `LitState` object:
42 |
43 | ```javascript
44 | import { LitState, stateVar } from 'lit-element-state';
45 |
46 | class MyState extends LitState {
47 | @stateVar() counter = 0;
48 | }
49 |
50 | export const myState = new MyState();
51 | ```
52 |
53 | #### Or without [javascript decorators](https://github.com/tc39/proposal-decorators):
54 |
55 | ```javascript
56 | import { LitState, stateVar } from 'lit-element-state';
57 |
58 | class MyState extends LitState {
59 | static get stateVars() {
60 | return {
61 | counter: 0
62 | };
63 | }
64 | }
65 |
66 | export const myState = new MyState();
67 | ```
68 |
69 | ### 2. Make your component aware of your state:
70 |
71 | By using the `observeState()` mixin on your `LitElement` class and then just
72 | using the `stateVar` variables in your render method:
73 |
74 | ```javascript
75 | import { LitElement, html } from 'lit-element';
76 | import { observeState } from 'lit-element-state';
77 | import { myState } from './my-state.js';
78 |
79 | class MyComponent extends observeState(LitElement) {
80 |
81 | render() {
82 | return html`
83 |
Counter: ${myState.counter}
84 |
85 | `;
86 | }
87 |
88 | }
89 | ```
90 |
91 | The components that read `myState.counter` will automatically re-render when
92 | any (other) component updates it.
93 |
94 | In more technical words:
95 |
96 | A component using the `observeState()` mixin will re-render when any
97 | `stateVar` - which it read in the last render cycle - changes.
98 |
99 |
100 | ## Docs
101 |
102 | For more information about how to use LitState, check the
103 | [docs](https://gitaarik.github.io/lit-state/build/).
104 |
105 |
106 | ## How does this work?
107 |
108 |
109 | ### Basics
110 |
111 | When you define a `stateVar` variable, LitState will observe those variables
112 | whenever they're get or set. When using the `observeState()` mixin on a
113 | component, during the render of that component, there is a recorder active that
114 | records any `stateVar` that is accessed during the render of that component. At
115 | the end of the render, the recorded `stateVar` variables are collected and
116 | whenever one of them changes, the component will be re-rendered. If the
117 | re-render uses different `stateVar` variables, they are again recorded and
118 | observed for possible rerenders.
119 |
120 |
121 | ### Implementation details
122 |
123 | To re-render the component, the `observeState()` mixin calls LitElement's
124 | [`this.requestUpdate()`](https://lit-element.polymer-project.org/api/classes/_lit_element_.litelement.html#requestupdate)
125 | (with no arguments). This will enqueue an update request for the component. The
126 | component will re-render at the end of the execution queue.
127 | `this.requestUpdate()` can be called multiple times during a particular
128 | JavaScript event (like a click), and it will only update the component once, at
129 | the end of the execution queue. So it doesn't matter when it is called multiple
130 | times when multiple `stateVar` variables are changed during a JavaScript event.
131 | This is an optimization feature built-in in LitElement. LitElement uses this
132 | optimization for it's own
133 | [properties](https://lit-element.polymer-project.org/guide/properties). This
134 | optimization works in the same way for LitState's `stateVar` variables.
135 |
136 | Also, LitElement uses lit-html, which sees which parts of the template are
137 | changed or not. And it will only re-render the HTML elements that have changes.
138 |
139 |
140 | ## Notes
141 |
142 | ### You can create and use multiple `LitState` classes at the same time.
143 |
144 | It is even encouraged to keep things separate. You can of course have one big
145 | `LitState` derived class which contains all global app state variables. But it
146 | is probably cleaner if you categorize it into multiple smaller `LitState`
147 | derived classes. For example, you can put each state class in a separate file,
148 | collected in a `state/` folder, and import them at the places you need.
149 |
150 |
151 | ### You can nest states
152 |
153 | If your state requires more hierarchy, you can also nest states. It doesn't
154 | matter to your components how your state is structured, as long as it uses the
155 | correct references to your `stateVar` variables. Refer to the
156 | [docs](https://gitaarik.github.io/lit-state/build/#state-handling/nested-states/)
157 | for more information about nesting states.
158 |
159 |
160 | ### Only new assigns trigger a re-render. Updating an object/array won't trigger a re-render.
161 |
162 | Just like LitElement's
163 | [properties](https://lit-element.polymer-project.org/guide/properties), only a
164 | new assign of the `stateVar` triggers a re-render. For example if you have a
165 | state like this:
166 |
167 | ```javascript
168 | MyState extends LitState {
169 | @stateVar() myObj = {myKey: 'myValue'};
170 | @stateVar() myArray = ['one', 'two', 'three'];
171 | }
172 | ```
173 |
174 | Then this won't trigger a re-render:
175 |
176 | ```javascript
177 | myState = new MyState();
178 | myState.myObj.mykey = 'newValue';
179 | myState.myArray.push('four');
180 | ```
181 |
182 | You'll instead need to assign a new object to the `stateVar`:
183 |
184 | ```javascript
185 | myState.myObj = {...myState.myObj, myKey: 'newValue'};
186 | myState.myArray = [...myState.myArray, 'four'];
187 | ```
188 |
189 | Watching for changes inside objects is very complex matter and would make
190 | LitState way more complicated than desirable. If you are interested in this
191 | kind of thing, check out
192 | [observable-slim](https://github.com/ElliotNB/observable-slim).
193 |
194 |
195 | ## Extra features
196 |
197 | ### Custom `stateVar` variables
198 |
199 | You can easily extend LitState with a custom `stateVar` handler. An example of
200 | this is the [asyncStateVar](https://github.com/gitaarik/lit-state-async-state-var),
201 | which is a `stateVar` variation that makes handling with asynchronous data
202 | easy. To make a custom `stateVar` yourself, create a class that extends from
203 | `StateVar`, exported by LitState.
204 | [Check out the documentation on this.](https://gitaarik.github.io/lit-state/build/#advanced-usage/state-var-handler/)
205 |
206 |
207 | ## FAQ
208 |
209 |
210 | ### Why should I use shared state for my components? Doesn't that oppose the concept of web components?
211 |
212 | The big feature of web components is that they are encapsulated through the
213 | [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
214 | That means that their internal state isn't affected by state from the outside.
215 | And also that the component's internal state doesn't affect other elements on
216 | the page. This makes web components great for creating reusable elements.
217 | Reusable elements should have no side-effects, meaning that they shouldn't
218 | change state outside of themselves.
219 |
220 | Reusable elements are great and we should use them a lot. When you're building
221 | a full application however, it is also desirable to have application-specific
222 | components that have application-specific side-effects. For example, changing
223 | the global app state. And it is of course desirable, that when this global app
224 | state changes, the components that use this global app state are synchronized
225 | with it.
226 |
227 | And you can also have a reusable component that has several internal
228 | sub-components. They all might need to share some common internal state.
229 |
230 | LitState is created for these use cases, and is meant to make it as simple as
231 | possible for the developer.
232 |
233 | ### Why not use Redux or MobX? Any benefits by using this?
234 |
235 | To use [Redux](https://github.com/reduxjs/redux) (or something similar, like
236 | [unistore](https://github.com/developit/unistore)) you need a lot of of boiler
237 | plate code: *Actions*, *Action Creators* and *Reducers*, you need to dispatch
238 | created actions and you need to map the state to your props inside your
239 | components. You have helper libraries that make it a bit easier, but I still
240 | think it is unnecessarily complicated.
241 |
242 | I think [MobX](https://github.com/mobxjs/mobx) is much easier to use, because
243 | you don't need to write any boilerplate. However, MobX is a quite large library
244 | with a lot of whistles and bells. And for more advanced use-cases it can become
245 | relatively complicated to use.
246 |
247 | I think a lot of features from MobX are not really necessary when you use
248 | LitElement. MobX is mainly created for React. Therefore MobX has optimizations
249 | aimed at how React works. LitState is specifically aimed at LitElement. And
250 | most of the optimizations MobX created for React are not required for
251 | LitElement.
252 |
253 | See the section [How does this work?](#how-does-this-work) to see how LitState
254 | works together with LitElement.
255 |
256 | Also LitState doesn't try to track changes inside objects, like MobX does. That
257 | is also a reason why MobX is complicated. It's nice that you can modify objects
258 | and MobX detects that, but it's not very hard to just set a new object. That
259 | makes the source code of LitState a lot smaller and simpler, and therefore also
260 | easier to understand what is happening.
261 | [Look here](#only-new-assigns-trigger-a-re-render-updating-a-objectarray-wont-trigger-a-re-render)
262 | for more details on this.
263 |
264 | Basically it comes down to the fact that LitState is written for, and with the
265 | same philosophy as, LitElement and lit-html. Which makes it more suitable for
266 | developers that like this philosophy.
267 |
268 | If you however do want to use MobX with LitElement for some reason, check out
269 | [lit-mobx](https://github.com/adobe/lit-mobx).
270 |
271 |
272 | ## Development
273 |
274 | LitState is brand-new. I created it because I wanted an easy way to deal with
275 | shared app state in LitElement, for my own projects. I hope it can make the
276 | lives of other developers easier too.
277 |
278 | I want to keep LitState small and simple, just like LitElement. So I don't
279 | expect to add a lot of features. Only things that are a very common patterns
280 | for shared app state management would be suitable to include.
281 |
282 | In any case, I will keep expanding the documentation to make the library more
283 | accessible. Also I would like to add unit tests, to automatically test the
284 | library. I don't have much experience with unit testing in JavaScript, so I
285 | need to dive into that.
286 |
287 | If you have comments, suggestions, questions, any kind of feedback, or you want
288 | to contribute, I would be pleased to hear from you. Feel free to open an issue.
289 |
290 |
291 | ## Also see
292 |
293 | - [asyncStateVar](https://github.com/gitaarik/lit-state-async-state-var) - asyncStateVar for LitState, easy handing of async data
294 | - [LitStyle](https://github.com/gitaarik/lit-style) - Shared component styles for LitElement
295 | - [LitDocumentEvent](https://github.com/gitaarik/lit-document-event) - Easily add listeners to the document object
296 | - [LitDocs](https://github.com/gitaarik/lit-docs) - Utilities to create documentation for LitElement related projects
297 |
--------------------------------------------------------------------------------
/docs/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'env': {
3 | 'browser': true,
4 | 'es6': true
5 | },
6 | 'extends': 'eslint:recommended',
7 | 'globals': {
8 | 'Atomics': 'readonly',
9 | 'SharedArrayBuffer': 'readonly'
10 | },
11 | "parser": "babel-eslint",
12 | 'parserOptions': {
13 | 'ecmaVersion': 2018,
14 | 'sourceType': 'module',
15 | 'ecmaFeatures': {
16 | 'legacyDecorators': true
17 | }
18 | },
19 | 'rules': {
20 | 'indent': [
21 | 'error',
22 | 4,
23 | {
24 | 'SwitchCase': 1
25 | }
26 | ],
27 | 'linebreak-style': [
28 | 'error',
29 | 'unix'
30 | ],
31 | 'semi': [
32 | 'error',
33 | 'always'
34 | ]
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | .build
2 | node_modules
3 |
--------------------------------------------------------------------------------
/docs/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
5 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # New Project
2 |
3 | > ✨ Bootstrapped with Create Snowpack App (CSA).
4 |
5 | ## Available Scripts
6 |
7 | ### npm start
8 |
9 | Runs the app in the development mode.
10 | Open http://localhost:8080 to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### npm run build
16 |
17 | Builds the app for production to the `dist/` folder.
18 | It correctly bundles React in production mode and optimizes the build for the best performance.
19 |
20 | ## Directives
21 |
22 | In case you need to add a directive like `classMap` you should add the extension to the import:
23 |
24 | ```
25 | import { classMap } from "lit-html/directives/class-map.js";
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | ["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }],
4 | ["@babel/plugin-proposal-class-properties", { "loose": true }]
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/docs/build/dist/advanced-usage/manually-observe-state/observe-all-state-vars/state.js:
--------------------------------------------------------------------------------
1 | function _decorate(decorators, factory, superClass, mixins) { var api = _getDecoratorsApi(); if (mixins) { for (var i = 0; i < mixins.length; i++) { api = mixins[i](api); } } var r = factory(function initialize(O) { api.initializeInstanceElements(O, decorated.elements); }, superClass); var decorated = api.decorateClass(_coalesceClassElements(r.d.map(_createElementDescriptor)), decorators); api.initializeClassElements(r.F, decorated.elements); return api.runClassFinishers(r.F, decorated.finishers); }
2 |
3 | function _getDecoratorsApi() { _getDecoratorsApi = function () { return api; }; var api = { elementsDefinitionOrder: [["method"], ["field"]], initializeInstanceElements: function (O, elements) { ["method", "field"].forEach(function (kind) { elements.forEach(function (element) { if (element.kind === kind && element.placement === "own") { this.defineClassElement(O, element); } }, this); }, this); }, initializeClassElements: function (F, elements) { var proto = F.prototype; ["method", "field"].forEach(function (kind) { elements.forEach(function (element) { var placement = element.placement; if (element.kind === kind && (placement === "static" || placement === "prototype")) { var receiver = placement === "static" ? F : proto; this.defineClassElement(receiver, element); } }, this); }, this); }, defineClassElement: function (receiver, element) { var descriptor = element.descriptor; if (element.kind === "field") { var initializer = element.initializer; descriptor = { enumerable: descriptor.enumerable, writable: descriptor.writable, configurable: descriptor.configurable, value: initializer === void 0 ? void 0 : initializer.call(receiver) }; } Object.defineProperty(receiver, element.key, descriptor); }, decorateClass: function (elements, decorators) { var newElements = []; var finishers = []; var placements = { static: [], prototype: [], own: [] }; elements.forEach(function (element) { this.addElementPlacement(element, placements); }, this); elements.forEach(function (element) { if (!_hasDecorators(element)) return newElements.push(element); var elementFinishersExtras = this.decorateElement(element, placements); newElements.push(elementFinishersExtras.element); newElements.push.apply(newElements, elementFinishersExtras.extras); finishers.push.apply(finishers, elementFinishersExtras.finishers); }, this); if (!decorators) { return { elements: newElements, finishers: finishers }; } var result = this.decorateConstructor(newElements, decorators); finishers.push.apply(finishers, result.finishers); result.finishers = finishers; return result; }, addElementPlacement: function (element, placements, silent) { var keys = placements[element.placement]; if (!silent && keys.indexOf(element.key) !== -1) { throw new TypeError("Duplicated element (" + element.key + ")"); } keys.push(element.key); }, decorateElement: function (element, placements) { var extras = []; var finishers = []; for (var decorators = element.decorators, i = decorators.length - 1; i >= 0; i--) { var keys = placements[element.placement]; keys.splice(keys.indexOf(element.key), 1); var elementObject = this.fromElementDescriptor(element); var elementFinisherExtras = this.toElementFinisherExtras((0, decorators[i])(elementObject) || elementObject); element = elementFinisherExtras.element; this.addElementPlacement(element, placements); if (elementFinisherExtras.finisher) { finishers.push(elementFinisherExtras.finisher); } var newExtras = elementFinisherExtras.extras; if (newExtras) { for (var j = 0; j < newExtras.length; j++) { this.addElementPlacement(newExtras[j], placements); } extras.push.apply(extras, newExtras); } } return { element: element, finishers: finishers, extras: extras }; }, decorateConstructor: function (elements, decorators) { var finishers = []; for (var i = decorators.length - 1; i >= 0; i--) { var obj = this.fromClassDescriptor(elements); var elementsAndFinisher = this.toClassDescriptor((0, decorators[i])(obj) || obj); if (elementsAndFinisher.finisher !== undefined) { finishers.push(elementsAndFinisher.finisher); } if (elementsAndFinisher.elements !== undefined) { elements = elementsAndFinisher.elements; for (var j = 0; j < elements.length - 1; j++) { for (var k = j + 1; k < elements.length; k++) { if (elements[j].key === elements[k].key && elements[j].placement === elements[k].placement) { throw new TypeError("Duplicated element (" + elements[j].key + ")"); } } } } } return { elements: elements, finishers: finishers }; }, fromElementDescriptor: function (element) { var obj = { kind: element.kind, key: element.key, placement: element.placement, descriptor: element.descriptor }; var desc = { value: "Descriptor", configurable: true }; Object.defineProperty(obj, Symbol.toStringTag, desc); if (element.kind === "field") obj.initializer = element.initializer; return obj; }, toElementDescriptors: function (elementObjects) { if (elementObjects === undefined) return; return _toArray(elementObjects).map(function (elementObject) { var element = this.toElementDescriptor(elementObject); this.disallowProperty(elementObject, "finisher", "An element descriptor"); this.disallowProperty(elementObject, "extras", "An element descriptor"); return element; }, this); }, toElementDescriptor: function (elementObject) { var kind = String(elementObject.kind); if (kind !== "method" && kind !== "field") { throw new TypeError('An element descriptor\'s .kind property must be either "method" or' + ' "field", but a decorator created an element descriptor with' + ' .kind "' + kind + '"'); } var key = _toPropertyKey(elementObject.key); var placement = String(elementObject.placement); if (placement !== "static" && placement !== "prototype" && placement !== "own") { throw new TypeError('An element descriptor\'s .placement property must be one of "static",' + ' "prototype" or "own", but a decorator created an element descriptor' + ' with .placement "' + placement + '"'); } var descriptor = elementObject.descriptor; this.disallowProperty(elementObject, "elements", "An element descriptor"); var element = { kind: kind, key: key, placement: placement, descriptor: Object.assign({}, descriptor) }; if (kind !== "field") { this.disallowProperty(elementObject, "initializer", "A method descriptor"); } else { this.disallowProperty(descriptor, "get", "The property descriptor of a field descriptor"); this.disallowProperty(descriptor, "set", "The property descriptor of a field descriptor"); this.disallowProperty(descriptor, "value", "The property descriptor of a field descriptor"); element.initializer = elementObject.initializer; } return element; }, toElementFinisherExtras: function (elementObject) { var element = this.toElementDescriptor(elementObject); var finisher = _optionalCallableProperty(elementObject, "finisher"); var extras = this.toElementDescriptors(elementObject.extras); return { element: element, finisher: finisher, extras: extras }; }, fromClassDescriptor: function (elements) { var obj = { kind: "class", elements: elements.map(this.fromElementDescriptor, this) }; var desc = { value: "Descriptor", configurable: true }; Object.defineProperty(obj, Symbol.toStringTag, desc); return obj; }, toClassDescriptor: function (obj) { var kind = String(obj.kind); if (kind !== "class") { throw new TypeError('A class descriptor\'s .kind property must be "class", but a decorator' + ' created a class descriptor with .kind "' + kind + '"'); } this.disallowProperty(obj, "key", "A class descriptor"); this.disallowProperty(obj, "placement", "A class descriptor"); this.disallowProperty(obj, "descriptor", "A class descriptor"); this.disallowProperty(obj, "initializer", "A class descriptor"); this.disallowProperty(obj, "extras", "A class descriptor"); var finisher = _optionalCallableProperty(obj, "finisher"); var elements = this.toElementDescriptors(obj.elements); return { elements: elements, finisher: finisher }; }, runClassFinishers: function (constructor, finishers) { for (var i = 0; i < finishers.length; i++) { var newConstructor = (0, finishers[i])(constructor); if (newConstructor !== undefined) { if (typeof newConstructor !== "function") { throw new TypeError("Finishers must return a constructor."); } constructor = newConstructor; } } return constructor; }, disallowProperty: function (obj, name, objectType) { if (obj[name] !== undefined) { throw new TypeError(objectType + " can't have a ." + name + " property."); } } }; return api; }
4 |
5 | function _createElementDescriptor(def) { var key = _toPropertyKey(def.key); var descriptor; if (def.kind === "method") { descriptor = { value: def.value, writable: true, configurable: true, enumerable: false }; } else if (def.kind === "get") { descriptor = { get: def.value, configurable: true, enumerable: false }; } else if (def.kind === "set") { descriptor = { set: def.value, configurable: true, enumerable: false }; } else if (def.kind === "field") { descriptor = { configurable: true, writable: true, enumerable: true }; } var element = { kind: def.kind === "field" ? "field" : "method", key: key, placement: def.static ? "static" : def.kind === "field" ? "own" : "prototype", descriptor: descriptor }; if (def.decorators) element.decorators = def.decorators; if (def.kind === "field") element.initializer = def.value; return element; }
6 |
7 | function _coalesceGetterSetter(element, other) { if (element.descriptor.get !== undefined) { other.descriptor.get = element.descriptor.get; } else { other.descriptor.set = element.descriptor.set; } }
8 |
9 | function _coalesceClassElements(elements) { var newElements = []; var isSameElement = function (other) { return other.kind === "method" && other.key === element.key && other.placement === element.placement; }; for (var i = 0; i < elements.length; i++) { var element = elements[i]; var other; if (element.kind === "method" && (other = newElements.find(isSameElement))) { if (_isDataDescriptor(element.descriptor) || _isDataDescriptor(other.descriptor)) { if (_hasDecorators(element) || _hasDecorators(other)) { throw new ReferenceError("Duplicated methods (" + element.key + ") can't be decorated."); } other.descriptor = element.descriptor; } else { if (_hasDecorators(element)) { if (_hasDecorators(other)) { throw new ReferenceError("Decorators can't be placed on different accessors with for " + "the same property (" + element.key + ")."); } other.decorators = element.decorators; } _coalesceGetterSetter(element, other); } } else { newElements.push(element); } } return newElements; }
10 |
11 | function _hasDecorators(element) { return element.decorators && element.decorators.length; }
12 |
13 | function _isDataDescriptor(desc) { return desc !== undefined && !(desc.value === undefined && desc.writable === undefined); }
14 |
15 | function _optionalCallableProperty(obj, name) { var value = obj[name]; if (value !== undefined && typeof value !== "function") { throw new TypeError("Expected '" + name + "' to be a function"); } return value; }
16 |
17 | function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
18 |
19 | function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
20 |
21 | function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest(); }
22 |
23 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
24 |
25 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
26 |
27 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
28 |
29 | function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
30 |
31 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
32 |
33 | import { LitState, stateVar } from '../../../lit-state.js';
34 |
35 | let DemoState = _decorate(null, function (_initialize, _LitState) {
36 | class DemoState extends _LitState {
37 | constructor(...args) {
38 | super(...args);
39 |
40 | _initialize(this);
41 | }
42 |
43 | }
44 |
45 | return {
46 | F: DemoState,
47 | d: [{
48 | kind: "field",
49 | decorators: [stateVar()],
50 | key: "counter",
51 |
52 | value() {
53 | return 0;
54 | }
55 |
56 | }]
57 | };
58 | }, LitState);
59 |
60 | export const demoState = new DemoState();
--------------------------------------------------------------------------------
/docs/build/dist/advanced-usage/manually-observe-state/observe-specific-state-vars/state.js:
--------------------------------------------------------------------------------
1 | function _decorate(decorators, factory, superClass, mixins) { var api = _getDecoratorsApi(); if (mixins) { for (var i = 0; i < mixins.length; i++) { api = mixins[i](api); } } var r = factory(function initialize(O) { api.initializeInstanceElements(O, decorated.elements); }, superClass); var decorated = api.decorateClass(_coalesceClassElements(r.d.map(_createElementDescriptor)), decorators); api.initializeClassElements(r.F, decorated.elements); return api.runClassFinishers(r.F, decorated.finishers); }
2 |
3 | function _getDecoratorsApi() { _getDecoratorsApi = function () { return api; }; var api = { elementsDefinitionOrder: [["method"], ["field"]], initializeInstanceElements: function (O, elements) { ["method", "field"].forEach(function (kind) { elements.forEach(function (element) { if (element.kind === kind && element.placement === "own") { this.defineClassElement(O, element); } }, this); }, this); }, initializeClassElements: function (F, elements) { var proto = F.prototype; ["method", "field"].forEach(function (kind) { elements.forEach(function (element) { var placement = element.placement; if (element.kind === kind && (placement === "static" || placement === "prototype")) { var receiver = placement === "static" ? F : proto; this.defineClassElement(receiver, element); } }, this); }, this); }, defineClassElement: function (receiver, element) { var descriptor = element.descriptor; if (element.kind === "field") { var initializer = element.initializer; descriptor = { enumerable: descriptor.enumerable, writable: descriptor.writable, configurable: descriptor.configurable, value: initializer === void 0 ? void 0 : initializer.call(receiver) }; } Object.defineProperty(receiver, element.key, descriptor); }, decorateClass: function (elements, decorators) { var newElements = []; var finishers = []; var placements = { static: [], prototype: [], own: [] }; elements.forEach(function (element) { this.addElementPlacement(element, placements); }, this); elements.forEach(function (element) { if (!_hasDecorators(element)) return newElements.push(element); var elementFinishersExtras = this.decorateElement(element, placements); newElements.push(elementFinishersExtras.element); newElements.push.apply(newElements, elementFinishersExtras.extras); finishers.push.apply(finishers, elementFinishersExtras.finishers); }, this); if (!decorators) { return { elements: newElements, finishers: finishers }; } var result = this.decorateConstructor(newElements, decorators); finishers.push.apply(finishers, result.finishers); result.finishers = finishers; return result; }, addElementPlacement: function (element, placements, silent) { var keys = placements[element.placement]; if (!silent && keys.indexOf(element.key) !== -1) { throw new TypeError("Duplicated element (" + element.key + ")"); } keys.push(element.key); }, decorateElement: function (element, placements) { var extras = []; var finishers = []; for (var decorators = element.decorators, i = decorators.length - 1; i >= 0; i--) { var keys = placements[element.placement]; keys.splice(keys.indexOf(element.key), 1); var elementObject = this.fromElementDescriptor(element); var elementFinisherExtras = this.toElementFinisherExtras((0, decorators[i])(elementObject) || elementObject); element = elementFinisherExtras.element; this.addElementPlacement(element, placements); if (elementFinisherExtras.finisher) { finishers.push(elementFinisherExtras.finisher); } var newExtras = elementFinisherExtras.extras; if (newExtras) { for (var j = 0; j < newExtras.length; j++) { this.addElementPlacement(newExtras[j], placements); } extras.push.apply(extras, newExtras); } } return { element: element, finishers: finishers, extras: extras }; }, decorateConstructor: function (elements, decorators) { var finishers = []; for (var i = decorators.length - 1; i >= 0; i--) { var obj = this.fromClassDescriptor(elements); var elementsAndFinisher = this.toClassDescriptor((0, decorators[i])(obj) || obj); if (elementsAndFinisher.finisher !== undefined) { finishers.push(elementsAndFinisher.finisher); } if (elementsAndFinisher.elements !== undefined) { elements = elementsAndFinisher.elements; for (var j = 0; j < elements.length - 1; j++) { for (var k = j + 1; k < elements.length; k++) { if (elements[j].key === elements[k].key && elements[j].placement === elements[k].placement) { throw new TypeError("Duplicated element (" + elements[j].key + ")"); } } } } } return { elements: elements, finishers: finishers }; }, fromElementDescriptor: function (element) { var obj = { kind: element.kind, key: element.key, placement: element.placement, descriptor: element.descriptor }; var desc = { value: "Descriptor", configurable: true }; Object.defineProperty(obj, Symbol.toStringTag, desc); if (element.kind === "field") obj.initializer = element.initializer; return obj; }, toElementDescriptors: function (elementObjects) { if (elementObjects === undefined) return; return _toArray(elementObjects).map(function (elementObject) { var element = this.toElementDescriptor(elementObject); this.disallowProperty(elementObject, "finisher", "An element descriptor"); this.disallowProperty(elementObject, "extras", "An element descriptor"); return element; }, this); }, toElementDescriptor: function (elementObject) { var kind = String(elementObject.kind); if (kind !== "method" && kind !== "field") { throw new TypeError('An element descriptor\'s .kind property must be either "method" or' + ' "field", but a decorator created an element descriptor with' + ' .kind "' + kind + '"'); } var key = _toPropertyKey(elementObject.key); var placement = String(elementObject.placement); if (placement !== "static" && placement !== "prototype" && placement !== "own") { throw new TypeError('An element descriptor\'s .placement property must be one of "static",' + ' "prototype" or "own", but a decorator created an element descriptor' + ' with .placement "' + placement + '"'); } var descriptor = elementObject.descriptor; this.disallowProperty(elementObject, "elements", "An element descriptor"); var element = { kind: kind, key: key, placement: placement, descriptor: Object.assign({}, descriptor) }; if (kind !== "field") { this.disallowProperty(elementObject, "initializer", "A method descriptor"); } else { this.disallowProperty(descriptor, "get", "The property descriptor of a field descriptor"); this.disallowProperty(descriptor, "set", "The property descriptor of a field descriptor"); this.disallowProperty(descriptor, "value", "The property descriptor of a field descriptor"); element.initializer = elementObject.initializer; } return element; }, toElementFinisherExtras: function (elementObject) { var element = this.toElementDescriptor(elementObject); var finisher = _optionalCallableProperty(elementObject, "finisher"); var extras = this.toElementDescriptors(elementObject.extras); return { element: element, finisher: finisher, extras: extras }; }, fromClassDescriptor: function (elements) { var obj = { kind: "class", elements: elements.map(this.fromElementDescriptor, this) }; var desc = { value: "Descriptor", configurable: true }; Object.defineProperty(obj, Symbol.toStringTag, desc); return obj; }, toClassDescriptor: function (obj) { var kind = String(obj.kind); if (kind !== "class") { throw new TypeError('A class descriptor\'s .kind property must be "class", but a decorator' + ' created a class descriptor with .kind "' + kind + '"'); } this.disallowProperty(obj, "key", "A class descriptor"); this.disallowProperty(obj, "placement", "A class descriptor"); this.disallowProperty(obj, "descriptor", "A class descriptor"); this.disallowProperty(obj, "initializer", "A class descriptor"); this.disallowProperty(obj, "extras", "A class descriptor"); var finisher = _optionalCallableProperty(obj, "finisher"); var elements = this.toElementDescriptors(obj.elements); return { elements: elements, finisher: finisher }; }, runClassFinishers: function (constructor, finishers) { for (var i = 0; i < finishers.length; i++) { var newConstructor = (0, finishers[i])(constructor); if (newConstructor !== undefined) { if (typeof newConstructor !== "function") { throw new TypeError("Finishers must return a constructor."); } constructor = newConstructor; } } return constructor; }, disallowProperty: function (obj, name, objectType) { if (obj[name] !== undefined) { throw new TypeError(objectType + " can't have a ." + name + " property."); } } }; return api; }
4 |
5 | function _createElementDescriptor(def) { var key = _toPropertyKey(def.key); var descriptor; if (def.kind === "method") { descriptor = { value: def.value, writable: true, configurable: true, enumerable: false }; } else if (def.kind === "get") { descriptor = { get: def.value, configurable: true, enumerable: false }; } else if (def.kind === "set") { descriptor = { set: def.value, configurable: true, enumerable: false }; } else if (def.kind === "field") { descriptor = { configurable: true, writable: true, enumerable: true }; } var element = { kind: def.kind === "field" ? "field" : "method", key: key, placement: def.static ? "static" : def.kind === "field" ? "own" : "prototype", descriptor: descriptor }; if (def.decorators) element.decorators = def.decorators; if (def.kind === "field") element.initializer = def.value; return element; }
6 |
7 | function _coalesceGetterSetter(element, other) { if (element.descriptor.get !== undefined) { other.descriptor.get = element.descriptor.get; } else { other.descriptor.set = element.descriptor.set; } }
8 |
9 | function _coalesceClassElements(elements) { var newElements = []; var isSameElement = function (other) { return other.kind === "method" && other.key === element.key && other.placement === element.placement; }; for (var i = 0; i < elements.length; i++) { var element = elements[i]; var other; if (element.kind === "method" && (other = newElements.find(isSameElement))) { if (_isDataDescriptor(element.descriptor) || _isDataDescriptor(other.descriptor)) { if (_hasDecorators(element) || _hasDecorators(other)) { throw new ReferenceError("Duplicated methods (" + element.key + ") can't be decorated."); } other.descriptor = element.descriptor; } else { if (_hasDecorators(element)) { if (_hasDecorators(other)) { throw new ReferenceError("Decorators can't be placed on different accessors with for " + "the same property (" + element.key + ")."); } other.decorators = element.decorators; } _coalesceGetterSetter(element, other); } } else { newElements.push(element); } } return newElements; }
10 |
11 | function _hasDecorators(element) { return element.decorators && element.decorators.length; }
12 |
13 | function _isDataDescriptor(desc) { return desc !== undefined && !(desc.value === undefined && desc.writable === undefined); }
14 |
15 | function _optionalCallableProperty(obj, name) { var value = obj[name]; if (value !== undefined && typeof value !== "function") { throw new TypeError("Expected '" + name + "' to be a function"); } return value; }
16 |
17 | function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
18 |
19 | function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
20 |
21 | function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest(); }
22 |
23 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
24 |
25 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
26 |
27 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
28 |
29 | function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
30 |
31 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
32 |
33 | import { LitState, stateVar } from '../../../lit-state.js';
34 |
35 | let DemoState = _decorate(null, function (_initialize, _LitState) {
36 | class DemoState extends _LitState {
37 | constructor(...args) {
38 | super(...args);
39 |
40 | _initialize(this);
41 | }
42 |
43 | }
44 |
45 | return {
46 | F: DemoState,
47 | d: [{
48 | kind: "field",
49 | decorators: [stateVar()],
50 | key: "counter1",
51 |
52 | value() {
53 | return 0;
54 | }
55 |
56 | }, {
57 | kind: "field",
58 | decorators: [stateVar()],
59 | key: "counter2",
60 |
61 | value() {
62 | return 0;
63 | }
64 |
65 | }]
66 | };
67 | }, LitState);
68 |
69 | export const demoState = new DemoState();
--------------------------------------------------------------------------------
/docs/build/dist/basic-usage/no-decorator-usage/state.js:
--------------------------------------------------------------------------------
1 | import { LitState } from '../../lit-state.js';
2 |
3 | class DemoState extends LitState {
4 | static get stateVars() {
5 | return {
6 | counter: 0
7 | };
8 | }
9 |
10 | }
11 |
12 | export const demoState = new DemoState();
--------------------------------------------------------------------------------
/docs/build/dist/basic-usage/state.js:
--------------------------------------------------------------------------------
1 | function _decorate(decorators, factory, superClass, mixins) { var api = _getDecoratorsApi(); if (mixins) { for (var i = 0; i < mixins.length; i++) { api = mixins[i](api); } } var r = factory(function initialize(O) { api.initializeInstanceElements(O, decorated.elements); }, superClass); var decorated = api.decorateClass(_coalesceClassElements(r.d.map(_createElementDescriptor)), decorators); api.initializeClassElements(r.F, decorated.elements); return api.runClassFinishers(r.F, decorated.finishers); }
2 |
3 | function _getDecoratorsApi() { _getDecoratorsApi = function () { return api; }; var api = { elementsDefinitionOrder: [["method"], ["field"]], initializeInstanceElements: function (O, elements) { ["method", "field"].forEach(function (kind) { elements.forEach(function (element) { if (element.kind === kind && element.placement === "own") { this.defineClassElement(O, element); } }, this); }, this); }, initializeClassElements: function (F, elements) { var proto = F.prototype; ["method", "field"].forEach(function (kind) { elements.forEach(function (element) { var placement = element.placement; if (element.kind === kind && (placement === "static" || placement === "prototype")) { var receiver = placement === "static" ? F : proto; this.defineClassElement(receiver, element); } }, this); }, this); }, defineClassElement: function (receiver, element) { var descriptor = element.descriptor; if (element.kind === "field") { var initializer = element.initializer; descriptor = { enumerable: descriptor.enumerable, writable: descriptor.writable, configurable: descriptor.configurable, value: initializer === void 0 ? void 0 : initializer.call(receiver) }; } Object.defineProperty(receiver, element.key, descriptor); }, decorateClass: function (elements, decorators) { var newElements = []; var finishers = []; var placements = { static: [], prototype: [], own: [] }; elements.forEach(function (element) { this.addElementPlacement(element, placements); }, this); elements.forEach(function (element) { if (!_hasDecorators(element)) return newElements.push(element); var elementFinishersExtras = this.decorateElement(element, placements); newElements.push(elementFinishersExtras.element); newElements.push.apply(newElements, elementFinishersExtras.extras); finishers.push.apply(finishers, elementFinishersExtras.finishers); }, this); if (!decorators) { return { elements: newElements, finishers: finishers }; } var result = this.decorateConstructor(newElements, decorators); finishers.push.apply(finishers, result.finishers); result.finishers = finishers; return result; }, addElementPlacement: function (element, placements, silent) { var keys = placements[element.placement]; if (!silent && keys.indexOf(element.key) !== -1) { throw new TypeError("Duplicated element (" + element.key + ")"); } keys.push(element.key); }, decorateElement: function (element, placements) { var extras = []; var finishers = []; for (var decorators = element.decorators, i = decorators.length - 1; i >= 0; i--) { var keys = placements[element.placement]; keys.splice(keys.indexOf(element.key), 1); var elementObject = this.fromElementDescriptor(element); var elementFinisherExtras = this.toElementFinisherExtras((0, decorators[i])(elementObject) || elementObject); element = elementFinisherExtras.element; this.addElementPlacement(element, placements); if (elementFinisherExtras.finisher) { finishers.push(elementFinisherExtras.finisher); } var newExtras = elementFinisherExtras.extras; if (newExtras) { for (var j = 0; j < newExtras.length; j++) { this.addElementPlacement(newExtras[j], placements); } extras.push.apply(extras, newExtras); } } return { element: element, finishers: finishers, extras: extras }; }, decorateConstructor: function (elements, decorators) { var finishers = []; for (var i = decorators.length - 1; i >= 0; i--) { var obj = this.fromClassDescriptor(elements); var elementsAndFinisher = this.toClassDescriptor((0, decorators[i])(obj) || obj); if (elementsAndFinisher.finisher !== undefined) { finishers.push(elementsAndFinisher.finisher); } if (elementsAndFinisher.elements !== undefined) { elements = elementsAndFinisher.elements; for (var j = 0; j < elements.length - 1; j++) { for (var k = j + 1; k < elements.length; k++) { if (elements[j].key === elements[k].key && elements[j].placement === elements[k].placement) { throw new TypeError("Duplicated element (" + elements[j].key + ")"); } } } } } return { elements: elements, finishers: finishers }; }, fromElementDescriptor: function (element) { var obj = { kind: element.kind, key: element.key, placement: element.placement, descriptor: element.descriptor }; var desc = { value: "Descriptor", configurable: true }; Object.defineProperty(obj, Symbol.toStringTag, desc); if (element.kind === "field") obj.initializer = element.initializer; return obj; }, toElementDescriptors: function (elementObjects) { if (elementObjects === undefined) return; return _toArray(elementObjects).map(function (elementObject) { var element = this.toElementDescriptor(elementObject); this.disallowProperty(elementObject, "finisher", "An element descriptor"); this.disallowProperty(elementObject, "extras", "An element descriptor"); return element; }, this); }, toElementDescriptor: function (elementObject) { var kind = String(elementObject.kind); if (kind !== "method" && kind !== "field") { throw new TypeError('An element descriptor\'s .kind property must be either "method" or' + ' "field", but a decorator created an element descriptor with' + ' .kind "' + kind + '"'); } var key = _toPropertyKey(elementObject.key); var placement = String(elementObject.placement); if (placement !== "static" && placement !== "prototype" && placement !== "own") { throw new TypeError('An element descriptor\'s .placement property must be one of "static",' + ' "prototype" or "own", but a decorator created an element descriptor' + ' with .placement "' + placement + '"'); } var descriptor = elementObject.descriptor; this.disallowProperty(elementObject, "elements", "An element descriptor"); var element = { kind: kind, key: key, placement: placement, descriptor: Object.assign({}, descriptor) }; if (kind !== "field") { this.disallowProperty(elementObject, "initializer", "A method descriptor"); } else { this.disallowProperty(descriptor, "get", "The property descriptor of a field descriptor"); this.disallowProperty(descriptor, "set", "The property descriptor of a field descriptor"); this.disallowProperty(descriptor, "value", "The property descriptor of a field descriptor"); element.initializer = elementObject.initializer; } return element; }, toElementFinisherExtras: function (elementObject) { var element = this.toElementDescriptor(elementObject); var finisher = _optionalCallableProperty(elementObject, "finisher"); var extras = this.toElementDescriptors(elementObject.extras); return { element: element, finisher: finisher, extras: extras }; }, fromClassDescriptor: function (elements) { var obj = { kind: "class", elements: elements.map(this.fromElementDescriptor, this) }; var desc = { value: "Descriptor", configurable: true }; Object.defineProperty(obj, Symbol.toStringTag, desc); return obj; }, toClassDescriptor: function (obj) { var kind = String(obj.kind); if (kind !== "class") { throw new TypeError('A class descriptor\'s .kind property must be "class", but a decorator' + ' created a class descriptor with .kind "' + kind + '"'); } this.disallowProperty(obj, "key", "A class descriptor"); this.disallowProperty(obj, "placement", "A class descriptor"); this.disallowProperty(obj, "descriptor", "A class descriptor"); this.disallowProperty(obj, "initializer", "A class descriptor"); this.disallowProperty(obj, "extras", "A class descriptor"); var finisher = _optionalCallableProperty(obj, "finisher"); var elements = this.toElementDescriptors(obj.elements); return { elements: elements, finisher: finisher }; }, runClassFinishers: function (constructor, finishers) { for (var i = 0; i < finishers.length; i++) { var newConstructor = (0, finishers[i])(constructor); if (newConstructor !== undefined) { if (typeof newConstructor !== "function") { throw new TypeError("Finishers must return a constructor."); } constructor = newConstructor; } } return constructor; }, disallowProperty: function (obj, name, objectType) { if (obj[name] !== undefined) { throw new TypeError(objectType + " can't have a ." + name + " property."); } } }; return api; }
4 |
5 | function _createElementDescriptor(def) { var key = _toPropertyKey(def.key); var descriptor; if (def.kind === "method") { descriptor = { value: def.value, writable: true, configurable: true, enumerable: false }; } else if (def.kind === "get") { descriptor = { get: def.value, configurable: true, enumerable: false }; } else if (def.kind === "set") { descriptor = { set: def.value, configurable: true, enumerable: false }; } else if (def.kind === "field") { descriptor = { configurable: true, writable: true, enumerable: true }; } var element = { kind: def.kind === "field" ? "field" : "method", key: key, placement: def.static ? "static" : def.kind === "field" ? "own" : "prototype", descriptor: descriptor }; if (def.decorators) element.decorators = def.decorators; if (def.kind === "field") element.initializer = def.value; return element; }
6 |
7 | function _coalesceGetterSetter(element, other) { if (element.descriptor.get !== undefined) { other.descriptor.get = element.descriptor.get; } else { other.descriptor.set = element.descriptor.set; } }
8 |
9 | function _coalesceClassElements(elements) { var newElements = []; var isSameElement = function (other) { return other.kind === "method" && other.key === element.key && other.placement === element.placement; }; for (var i = 0; i < elements.length; i++) { var element = elements[i]; var other; if (element.kind === "method" && (other = newElements.find(isSameElement))) { if (_isDataDescriptor(element.descriptor) || _isDataDescriptor(other.descriptor)) { if (_hasDecorators(element) || _hasDecorators(other)) { throw new ReferenceError("Duplicated methods (" + element.key + ") can't be decorated."); } other.descriptor = element.descriptor; } else { if (_hasDecorators(element)) { if (_hasDecorators(other)) { throw new ReferenceError("Decorators can't be placed on different accessors with for " + "the same property (" + element.key + ")."); } other.decorators = element.decorators; } _coalesceGetterSetter(element, other); } } else { newElements.push(element); } } return newElements; }
10 |
11 | function _hasDecorators(element) { return element.decorators && element.decorators.length; }
12 |
13 | function _isDataDescriptor(desc) { return desc !== undefined && !(desc.value === undefined && desc.writable === undefined); }
14 |
15 | function _optionalCallableProperty(obj, name) { var value = obj[name]; if (value !== undefined && typeof value !== "function") { throw new TypeError("Expected '" + name + "' to be a function"); } return value; }
16 |
17 | function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
18 |
19 | function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
20 |
21 | function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest(); }
22 |
23 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
24 |
25 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
26 |
27 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
28 |
29 | function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
30 |
31 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
32 |
33 | import { LitState, stateVar } from '../lit-state.js';
34 |
35 | let DemoState = _decorate(null, function (_initialize, _LitState) {
36 | class DemoState extends _LitState {
37 | constructor(...args) {
38 | super(...args);
39 |
40 | _initialize(this);
41 | }
42 |
43 | }
44 |
45 | return {
46 | F: DemoState,
47 | d: [{
48 | kind: "field",
49 | decorators: [stateVar()],
50 | key: "counter",
51 |
52 | value() {
53 | return 0;
54 | }
55 |
56 | }]
57 | };
58 | }, LitState);
59 |
60 | export const demoState = new DemoState();
--------------------------------------------------------------------------------
/docs/build/dist/demo-component.js:
--------------------------------------------------------------------------------
1 | import { css } from '../web_modules/lit-element.js';
2 | import { litStyle } from '../web_modules/lit-element-style.js';
3 | import '../web_modules/lit-docs.js';
4 | export const demoComponentStyle = litStyle(css`
5 |
6 | h2 {
7 | margin-top: 0;
8 | font-size: 20px;
9 | color: green;
10 | }
11 |
12 | h3 {
13 | margin: 20px 0;
14 | font-weight: 600;
15 | font-size: 16px;
16 | }
17 |
18 | .status {
19 | color: blue;
20 | }
21 |
22 | .value {
23 | color: #cb2828;
24 | }
25 |
26 | .buttons {
27 | display: flex;
28 | flex-wrap: wrap;
29 | margin: -5px 0 0 -5px;
30 | }
31 |
32 | .buttons > * {
33 | margin: 5px 0 0 5px;
34 | }
35 |
36 | `);
--------------------------------------------------------------------------------
/docs/build/dist/lit-state.js:
--------------------------------------------------------------------------------
1 | export const observeState = superclass => class extends superclass {
2 | constructor() {
3 | super();
4 | this._observers = [];
5 | }
6 |
7 | update(changedProperties) {
8 | stateRecorder.start();
9 | super.update(changedProperties);
10 |
11 | this._initStateObservers();
12 | }
13 |
14 | connectedCallback() {
15 | super.connectedCallback();
16 |
17 | if (this._wasConnected) {
18 | this.requestUpdate();
19 | delete this._wasConnected;
20 | }
21 | }
22 |
23 | disconnectedCallback() {
24 | super.disconnectedCallback();
25 | this._wasConnected = true;
26 |
27 | this._clearStateObservers();
28 | }
29 |
30 | _initStateObservers() {
31 | this._clearStateObservers();
32 |
33 | if (!this.isConnected) return;
34 |
35 | this._addStateObservers(stateRecorder.finish());
36 | }
37 |
38 | _addStateObservers(stateVars) {
39 | for (let [state, keys] of stateVars) {
40 | const observer = () => this.requestUpdate();
41 |
42 | this._observers.push([state, observer]);
43 |
44 | state.addObserver(observer, keys);
45 | }
46 | }
47 |
48 | _clearStateObservers() {
49 | for (let [state, observer] of this._observers) {
50 | state.removeObserver(observer);
51 | }
52 |
53 | this._observers = [];
54 | }
55 |
56 | };
57 | export class LitState {
58 | constructor() {
59 | this._observers = [];
60 |
61 | this._initStateVars();
62 | }
63 |
64 | addObserver(observer, keys) {
65 | this._observers.push({
66 | observer,
67 | keys
68 | });
69 | }
70 |
71 | removeObserver(observer) {
72 | this._observers = this._observers.filter(observerObj => observerObj.observer !== observer);
73 | }
74 |
75 | _initStateVars() {
76 | if (this.constructor.stateVarOptions) {
77 | for (let [key, options] of Object.entries(this.constructor.stateVarOptions)) {
78 | this._initStateVar(key, options);
79 | }
80 | }
81 |
82 | if (this.constructor.stateVars) {
83 | for (let [key, value] of Object.entries(this.constructor.stateVars)) {
84 | this._initStateVar(key, {});
85 |
86 | this[key] = value;
87 | }
88 | }
89 | }
90 |
91 | _initStateVar(key, options) {
92 | if (this.hasOwnProperty(key)) {
93 | // Property already defined, so don't re-define.
94 | return;
95 | }
96 |
97 | options = this._parseOptions(options);
98 | const stateVar = new options.handler({
99 | options: options,
100 | recordRead: () => this._recordRead(key),
101 | notifyChange: () => this._notifyChange(key)
102 | });
103 | Object.defineProperty(this, key, {
104 | get() {
105 | return stateVar.get();
106 | },
107 |
108 | set(value) {
109 | if (stateVar.shouldSetValue(value)) {
110 | stateVar.set(value);
111 | }
112 | },
113 |
114 | configurable: true,
115 | enumerable: true
116 | });
117 | }
118 |
119 | _parseOptions(options) {
120 | if (!options.handler) {
121 | options.handler = StateVar;
122 | } else {
123 | // In case of a custom `StateVar` handler is provided, we offer a
124 | // second way of providing options to your custom handler class.
125 | //
126 | // You can decorate a *method* with `@stateVar()` instead of a
127 | // variable. The method must return an object, and that object will
128 | // be assigned to the `options` object.
129 | //
130 | // Within the method you have access to the `this` context. So you
131 | // can access other properties and methods from your state class.
132 | // And you can add arrow function callbacks where you can access
133 | // `this`. This provides a lot of possibilities for a custom
134 | // handler class.
135 | if (options.propertyMethod && options.propertyMethod.kind === 'method') {
136 | Object.assign(options, options.propertyMethod.descriptor.value.call(this));
137 | }
138 | }
139 |
140 | return options;
141 | }
142 |
143 | _recordRead(key) {
144 | stateRecorder.recordRead(this, key);
145 | }
146 |
147 | _notifyChange(key) {
148 | for (const observerObj of this._observers) {
149 | if (!observerObj.keys || observerObj.keys.includes(key)) {
150 | observerObj.observer(key);
151 | }
152 | }
153 |
154 | ;
155 | }
156 |
157 | }
158 | export class StateVar {
159 | constructor(args) {
160 | this.options = args.options; // The options given in the `stateVar` declaration
161 |
162 | this.recordRead = args.recordRead; // Callback to indicate the `stateVar` is read
163 |
164 | this.notifyChange = args.notifyChange; // Callback to indicate the `stateVar` value has changed
165 |
166 | this.value = undefined; // The initial value
167 | } // Called when the `stateVar` on the `LitState` class is read (for example:
168 | // `myState.myStateVar`). Should return the value of the `stateVar`.
169 |
170 |
171 | get() {
172 | this.recordRead();
173 | return this.value;
174 | } // Called before the `set()` method is called. If this method returns
175 | // `false`, the `set()` method won't be called. This can be used for
176 | // validation and/or optimization.
177 |
178 |
179 | shouldSetValue(value) {
180 | return this.value !== value;
181 | } // Called when the `stateVar` on the `LitState` class is set (for example:
182 | // `myState.myStateVar = 'value'`.
183 |
184 |
185 | set(value) {
186 | this.value = value;
187 | this.notifyChange();
188 | }
189 |
190 | }
191 | export function stateVar(options = {}) {
192 | return element => {
193 | return {
194 | kind: 'field',
195 | key: Symbol(),
196 | placement: 'own',
197 | descriptor: {},
198 |
199 | initializer() {
200 | if (typeof element.initializer === 'function') {
201 | this[element.key] = element.initializer.call(this);
202 | }
203 | },
204 |
205 | finisher(litStateClass) {
206 | if (element.kind === 'method') {
207 | // You can decorate a *method* with `@stateVar()` instead
208 | // of a variable. When the state class is constructed, this
209 | // method will be called, and it's return value must be an
210 | // object that will be added to the options the stateVar
211 | // handler will receive.
212 | options.propertyMethod = element;
213 | }
214 |
215 | if (litStateClass.stateVarOptions === undefined) {
216 | litStateClass.stateVarOptions = {};
217 | }
218 |
219 | litStateClass.stateVarOptions[element.key] = options;
220 | }
221 |
222 | };
223 | };
224 | }
225 |
226 | class StateRecorder {
227 | constructor() {
228 | this._log = null;
229 | }
230 |
231 | start() {
232 | this._log = new Map();
233 | }
234 |
235 | recordRead(stateObj, key) {
236 | if (this._log === null) return;
237 | const keys = this._log.get(stateObj) || [];
238 | if (!keys.includes(key)) keys.push(key);
239 |
240 | this._log.set(stateObj, keys);
241 | }
242 |
243 | finish() {
244 | const stateVars = this._log;
245 | this._log = null;
246 | return stateVars;
247 | }
248 |
249 | }
250 |
251 | export const stateRecorder = new StateRecorder();
--------------------------------------------------------------------------------
/docs/build/dist/use-cases/different-vars-on-rerender/state.js:
--------------------------------------------------------------------------------
1 | function _decorate(decorators, factory, superClass, mixins) { var api = _getDecoratorsApi(); if (mixins) { for (var i = 0; i < mixins.length; i++) { api = mixins[i](api); } } var r = factory(function initialize(O) { api.initializeInstanceElements(O, decorated.elements); }, superClass); var decorated = api.decorateClass(_coalesceClassElements(r.d.map(_createElementDescriptor)), decorators); api.initializeClassElements(r.F, decorated.elements); return api.runClassFinishers(r.F, decorated.finishers); }
2 |
3 | function _getDecoratorsApi() { _getDecoratorsApi = function () { return api; }; var api = { elementsDefinitionOrder: [["method"], ["field"]], initializeInstanceElements: function (O, elements) { ["method", "field"].forEach(function (kind) { elements.forEach(function (element) { if (element.kind === kind && element.placement === "own") { this.defineClassElement(O, element); } }, this); }, this); }, initializeClassElements: function (F, elements) { var proto = F.prototype; ["method", "field"].forEach(function (kind) { elements.forEach(function (element) { var placement = element.placement; if (element.kind === kind && (placement === "static" || placement === "prototype")) { var receiver = placement === "static" ? F : proto; this.defineClassElement(receiver, element); } }, this); }, this); }, defineClassElement: function (receiver, element) { var descriptor = element.descriptor; if (element.kind === "field") { var initializer = element.initializer; descriptor = { enumerable: descriptor.enumerable, writable: descriptor.writable, configurable: descriptor.configurable, value: initializer === void 0 ? void 0 : initializer.call(receiver) }; } Object.defineProperty(receiver, element.key, descriptor); }, decorateClass: function (elements, decorators) { var newElements = []; var finishers = []; var placements = { static: [], prototype: [], own: [] }; elements.forEach(function (element) { this.addElementPlacement(element, placements); }, this); elements.forEach(function (element) { if (!_hasDecorators(element)) return newElements.push(element); var elementFinishersExtras = this.decorateElement(element, placements); newElements.push(elementFinishersExtras.element); newElements.push.apply(newElements, elementFinishersExtras.extras); finishers.push.apply(finishers, elementFinishersExtras.finishers); }, this); if (!decorators) { return { elements: newElements, finishers: finishers }; } var result = this.decorateConstructor(newElements, decorators); finishers.push.apply(finishers, result.finishers); result.finishers = finishers; return result; }, addElementPlacement: function (element, placements, silent) { var keys = placements[element.placement]; if (!silent && keys.indexOf(element.key) !== -1) { throw new TypeError("Duplicated element (" + element.key + ")"); } keys.push(element.key); }, decorateElement: function (element, placements) { var extras = []; var finishers = []; for (var decorators = element.decorators, i = decorators.length - 1; i >= 0; i--) { var keys = placements[element.placement]; keys.splice(keys.indexOf(element.key), 1); var elementObject = this.fromElementDescriptor(element); var elementFinisherExtras = this.toElementFinisherExtras((0, decorators[i])(elementObject) || elementObject); element = elementFinisherExtras.element; this.addElementPlacement(element, placements); if (elementFinisherExtras.finisher) { finishers.push(elementFinisherExtras.finisher); } var newExtras = elementFinisherExtras.extras; if (newExtras) { for (var j = 0; j < newExtras.length; j++) { this.addElementPlacement(newExtras[j], placements); } extras.push.apply(extras, newExtras); } } return { element: element, finishers: finishers, extras: extras }; }, decorateConstructor: function (elements, decorators) { var finishers = []; for (var i = decorators.length - 1; i >= 0; i--) { var obj = this.fromClassDescriptor(elements); var elementsAndFinisher = this.toClassDescriptor((0, decorators[i])(obj) || obj); if (elementsAndFinisher.finisher !== undefined) { finishers.push(elementsAndFinisher.finisher); } if (elementsAndFinisher.elements !== undefined) { elements = elementsAndFinisher.elements; for (var j = 0; j < elements.length - 1; j++) { for (var k = j + 1; k < elements.length; k++) { if (elements[j].key === elements[k].key && elements[j].placement === elements[k].placement) { throw new TypeError("Duplicated element (" + elements[j].key + ")"); } } } } } return { elements: elements, finishers: finishers }; }, fromElementDescriptor: function (element) { var obj = { kind: element.kind, key: element.key, placement: element.placement, descriptor: element.descriptor }; var desc = { value: "Descriptor", configurable: true }; Object.defineProperty(obj, Symbol.toStringTag, desc); if (element.kind === "field") obj.initializer = element.initializer; return obj; }, toElementDescriptors: function (elementObjects) { if (elementObjects === undefined) return; return _toArray(elementObjects).map(function (elementObject) { var element = this.toElementDescriptor(elementObject); this.disallowProperty(elementObject, "finisher", "An element descriptor"); this.disallowProperty(elementObject, "extras", "An element descriptor"); return element; }, this); }, toElementDescriptor: function (elementObject) { var kind = String(elementObject.kind); if (kind !== "method" && kind !== "field") { throw new TypeError('An element descriptor\'s .kind property must be either "method" or' + ' "field", but a decorator created an element descriptor with' + ' .kind "' + kind + '"'); } var key = _toPropertyKey(elementObject.key); var placement = String(elementObject.placement); if (placement !== "static" && placement !== "prototype" && placement !== "own") { throw new TypeError('An element descriptor\'s .placement property must be one of "static",' + ' "prototype" or "own", but a decorator created an element descriptor' + ' with .placement "' + placement + '"'); } var descriptor = elementObject.descriptor; this.disallowProperty(elementObject, "elements", "An element descriptor"); var element = { kind: kind, key: key, placement: placement, descriptor: Object.assign({}, descriptor) }; if (kind !== "field") { this.disallowProperty(elementObject, "initializer", "A method descriptor"); } else { this.disallowProperty(descriptor, "get", "The property descriptor of a field descriptor"); this.disallowProperty(descriptor, "set", "The property descriptor of a field descriptor"); this.disallowProperty(descriptor, "value", "The property descriptor of a field descriptor"); element.initializer = elementObject.initializer; } return element; }, toElementFinisherExtras: function (elementObject) { var element = this.toElementDescriptor(elementObject); var finisher = _optionalCallableProperty(elementObject, "finisher"); var extras = this.toElementDescriptors(elementObject.extras); return { element: element, finisher: finisher, extras: extras }; }, fromClassDescriptor: function (elements) { var obj = { kind: "class", elements: elements.map(this.fromElementDescriptor, this) }; var desc = { value: "Descriptor", configurable: true }; Object.defineProperty(obj, Symbol.toStringTag, desc); return obj; }, toClassDescriptor: function (obj) { var kind = String(obj.kind); if (kind !== "class") { throw new TypeError('A class descriptor\'s .kind property must be "class", but a decorator' + ' created a class descriptor with .kind "' + kind + '"'); } this.disallowProperty(obj, "key", "A class descriptor"); this.disallowProperty(obj, "placement", "A class descriptor"); this.disallowProperty(obj, "descriptor", "A class descriptor"); this.disallowProperty(obj, "initializer", "A class descriptor"); this.disallowProperty(obj, "extras", "A class descriptor"); var finisher = _optionalCallableProperty(obj, "finisher"); var elements = this.toElementDescriptors(obj.elements); return { elements: elements, finisher: finisher }; }, runClassFinishers: function (constructor, finishers) { for (var i = 0; i < finishers.length; i++) { var newConstructor = (0, finishers[i])(constructor); if (newConstructor !== undefined) { if (typeof newConstructor !== "function") { throw new TypeError("Finishers must return a constructor."); } constructor = newConstructor; } } return constructor; }, disallowProperty: function (obj, name, objectType) { if (obj[name] !== undefined) { throw new TypeError(objectType + " can't have a ." + name + " property."); } } }; return api; }
4 |
5 | function _createElementDescriptor(def) { var key = _toPropertyKey(def.key); var descriptor; if (def.kind === "method") { descriptor = { value: def.value, writable: true, configurable: true, enumerable: false }; } else if (def.kind === "get") { descriptor = { get: def.value, configurable: true, enumerable: false }; } else if (def.kind === "set") { descriptor = { set: def.value, configurable: true, enumerable: false }; } else if (def.kind === "field") { descriptor = { configurable: true, writable: true, enumerable: true }; } var element = { kind: def.kind === "field" ? "field" : "method", key: key, placement: def.static ? "static" : def.kind === "field" ? "own" : "prototype", descriptor: descriptor }; if (def.decorators) element.decorators = def.decorators; if (def.kind === "field") element.initializer = def.value; return element; }
6 |
7 | function _coalesceGetterSetter(element, other) { if (element.descriptor.get !== undefined) { other.descriptor.get = element.descriptor.get; } else { other.descriptor.set = element.descriptor.set; } }
8 |
9 | function _coalesceClassElements(elements) { var newElements = []; var isSameElement = function (other) { return other.kind === "method" && other.key === element.key && other.placement === element.placement; }; for (var i = 0; i < elements.length; i++) { var element = elements[i]; var other; if (element.kind === "method" && (other = newElements.find(isSameElement))) { if (_isDataDescriptor(element.descriptor) || _isDataDescriptor(other.descriptor)) { if (_hasDecorators(element) || _hasDecorators(other)) { throw new ReferenceError("Duplicated methods (" + element.key + ") can't be decorated."); } other.descriptor = element.descriptor; } else { if (_hasDecorators(element)) { if (_hasDecorators(other)) { throw new ReferenceError("Decorators can't be placed on different accessors with for " + "the same property (" + element.key + ")."); } other.decorators = element.decorators; } _coalesceGetterSetter(element, other); } } else { newElements.push(element); } } return newElements; }
10 |
11 | function _hasDecorators(element) { return element.decorators && element.decorators.length; }
12 |
13 | function _isDataDescriptor(desc) { return desc !== undefined && !(desc.value === undefined && desc.writable === undefined); }
14 |
15 | function _optionalCallableProperty(obj, name) { var value = obj[name]; if (value !== undefined && typeof value !== "function") { throw new TypeError("Expected '" + name + "' to be a function"); } return value; }
16 |
17 | function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
18 |
19 | function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
20 |
21 | function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest(); }
22 |
23 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
24 |
25 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
26 |
27 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
28 |
29 | function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
30 |
31 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
32 |
33 | import { LitState, stateVar } from '../../lit-state.js';
34 |
35 | let DemoState = _decorate(null, function (_initialize, _LitState) {
36 | class DemoState extends _LitState {
37 | constructor(...args) {
38 | super(...args);
39 |
40 | _initialize(this);
41 | }
42 |
43 | }
44 |
45 | return {
46 | F: DemoState,
47 | d: [{
48 | kind: "field",
49 | decorators: [stateVar()],
50 | key: "showCounter",
51 |
52 | value() {
53 | return 1;
54 | }
55 |
56 | }, {
57 | kind: "field",
58 | decorators: [stateVar()],
59 | key: "counter1",
60 |
61 | value() {
62 | return 0;
63 | }
64 |
65 | }, {
66 | kind: "field",
67 | decorators: [stateVar()],
68 | key: "counter2",
69 |
70 | value() {
71 | return 0;
72 | }
73 |
74 | }]
75 | };
76 | }, LitState);
77 |
78 | export const demoState = new DemoState();
--------------------------------------------------------------------------------
/docs/build/dist/use-cases/reconnected-components/state.js:
--------------------------------------------------------------------------------
1 | function _decorate(decorators, factory, superClass, mixins) { var api = _getDecoratorsApi(); if (mixins) { for (var i = 0; i < mixins.length; i++) { api = mixins[i](api); } } var r = factory(function initialize(O) { api.initializeInstanceElements(O, decorated.elements); }, superClass); var decorated = api.decorateClass(_coalesceClassElements(r.d.map(_createElementDescriptor)), decorators); api.initializeClassElements(r.F, decorated.elements); return api.runClassFinishers(r.F, decorated.finishers); }
2 |
3 | function _getDecoratorsApi() { _getDecoratorsApi = function () { return api; }; var api = { elementsDefinitionOrder: [["method"], ["field"]], initializeInstanceElements: function (O, elements) { ["method", "field"].forEach(function (kind) { elements.forEach(function (element) { if (element.kind === kind && element.placement === "own") { this.defineClassElement(O, element); } }, this); }, this); }, initializeClassElements: function (F, elements) { var proto = F.prototype; ["method", "field"].forEach(function (kind) { elements.forEach(function (element) { var placement = element.placement; if (element.kind === kind && (placement === "static" || placement === "prototype")) { var receiver = placement === "static" ? F : proto; this.defineClassElement(receiver, element); } }, this); }, this); }, defineClassElement: function (receiver, element) { var descriptor = element.descriptor; if (element.kind === "field") { var initializer = element.initializer; descriptor = { enumerable: descriptor.enumerable, writable: descriptor.writable, configurable: descriptor.configurable, value: initializer === void 0 ? void 0 : initializer.call(receiver) }; } Object.defineProperty(receiver, element.key, descriptor); }, decorateClass: function (elements, decorators) { var newElements = []; var finishers = []; var placements = { static: [], prototype: [], own: [] }; elements.forEach(function (element) { this.addElementPlacement(element, placements); }, this); elements.forEach(function (element) { if (!_hasDecorators(element)) return newElements.push(element); var elementFinishersExtras = this.decorateElement(element, placements); newElements.push(elementFinishersExtras.element); newElements.push.apply(newElements, elementFinishersExtras.extras); finishers.push.apply(finishers, elementFinishersExtras.finishers); }, this); if (!decorators) { return { elements: newElements, finishers: finishers }; } var result = this.decorateConstructor(newElements, decorators); finishers.push.apply(finishers, result.finishers); result.finishers = finishers; return result; }, addElementPlacement: function (element, placements, silent) { var keys = placements[element.placement]; if (!silent && keys.indexOf(element.key) !== -1) { throw new TypeError("Duplicated element (" + element.key + ")"); } keys.push(element.key); }, decorateElement: function (element, placements) { var extras = []; var finishers = []; for (var decorators = element.decorators, i = decorators.length - 1; i >= 0; i--) { var keys = placements[element.placement]; keys.splice(keys.indexOf(element.key), 1); var elementObject = this.fromElementDescriptor(element); var elementFinisherExtras = this.toElementFinisherExtras((0, decorators[i])(elementObject) || elementObject); element = elementFinisherExtras.element; this.addElementPlacement(element, placements); if (elementFinisherExtras.finisher) { finishers.push(elementFinisherExtras.finisher); } var newExtras = elementFinisherExtras.extras; if (newExtras) { for (var j = 0; j < newExtras.length; j++) { this.addElementPlacement(newExtras[j], placements); } extras.push.apply(extras, newExtras); } } return { element: element, finishers: finishers, extras: extras }; }, decorateConstructor: function (elements, decorators) { var finishers = []; for (var i = decorators.length - 1; i >= 0; i--) { var obj = this.fromClassDescriptor(elements); var elementsAndFinisher = this.toClassDescriptor((0, decorators[i])(obj) || obj); if (elementsAndFinisher.finisher !== undefined) { finishers.push(elementsAndFinisher.finisher); } if (elementsAndFinisher.elements !== undefined) { elements = elementsAndFinisher.elements; for (var j = 0; j < elements.length - 1; j++) { for (var k = j + 1; k < elements.length; k++) { if (elements[j].key === elements[k].key && elements[j].placement === elements[k].placement) { throw new TypeError("Duplicated element (" + elements[j].key + ")"); } } } } } return { elements: elements, finishers: finishers }; }, fromElementDescriptor: function (element) { var obj = { kind: element.kind, key: element.key, placement: element.placement, descriptor: element.descriptor }; var desc = { value: "Descriptor", configurable: true }; Object.defineProperty(obj, Symbol.toStringTag, desc); if (element.kind === "field") obj.initializer = element.initializer; return obj; }, toElementDescriptors: function (elementObjects) { if (elementObjects === undefined) return; return _toArray(elementObjects).map(function (elementObject) { var element = this.toElementDescriptor(elementObject); this.disallowProperty(elementObject, "finisher", "An element descriptor"); this.disallowProperty(elementObject, "extras", "An element descriptor"); return element; }, this); }, toElementDescriptor: function (elementObject) { var kind = String(elementObject.kind); if (kind !== "method" && kind !== "field") { throw new TypeError('An element descriptor\'s .kind property must be either "method" or' + ' "field", but a decorator created an element descriptor with' + ' .kind "' + kind + '"'); } var key = _toPropertyKey(elementObject.key); var placement = String(elementObject.placement); if (placement !== "static" && placement !== "prototype" && placement !== "own") { throw new TypeError('An element descriptor\'s .placement property must be one of "static",' + ' "prototype" or "own", but a decorator created an element descriptor' + ' with .placement "' + placement + '"'); } var descriptor = elementObject.descriptor; this.disallowProperty(elementObject, "elements", "An element descriptor"); var element = { kind: kind, key: key, placement: placement, descriptor: Object.assign({}, descriptor) }; if (kind !== "field") { this.disallowProperty(elementObject, "initializer", "A method descriptor"); } else { this.disallowProperty(descriptor, "get", "The property descriptor of a field descriptor"); this.disallowProperty(descriptor, "set", "The property descriptor of a field descriptor"); this.disallowProperty(descriptor, "value", "The property descriptor of a field descriptor"); element.initializer = elementObject.initializer; } return element; }, toElementFinisherExtras: function (elementObject) { var element = this.toElementDescriptor(elementObject); var finisher = _optionalCallableProperty(elementObject, "finisher"); var extras = this.toElementDescriptors(elementObject.extras); return { element: element, finisher: finisher, extras: extras }; }, fromClassDescriptor: function (elements) { var obj = { kind: "class", elements: elements.map(this.fromElementDescriptor, this) }; var desc = { value: "Descriptor", configurable: true }; Object.defineProperty(obj, Symbol.toStringTag, desc); return obj; }, toClassDescriptor: function (obj) { var kind = String(obj.kind); if (kind !== "class") { throw new TypeError('A class descriptor\'s .kind property must be "class", but a decorator' + ' created a class descriptor with .kind "' + kind + '"'); } this.disallowProperty(obj, "key", "A class descriptor"); this.disallowProperty(obj, "placement", "A class descriptor"); this.disallowProperty(obj, "descriptor", "A class descriptor"); this.disallowProperty(obj, "initializer", "A class descriptor"); this.disallowProperty(obj, "extras", "A class descriptor"); var finisher = _optionalCallableProperty(obj, "finisher"); var elements = this.toElementDescriptors(obj.elements); return { elements: elements, finisher: finisher }; }, runClassFinishers: function (constructor, finishers) { for (var i = 0; i < finishers.length; i++) { var newConstructor = (0, finishers[i])(constructor); if (newConstructor !== undefined) { if (typeof newConstructor !== "function") { throw new TypeError("Finishers must return a constructor."); } constructor = newConstructor; } } return constructor; }, disallowProperty: function (obj, name, objectType) { if (obj[name] !== undefined) { throw new TypeError(objectType + " can't have a ." + name + " property."); } } }; return api; }
4 |
5 | function _createElementDescriptor(def) { var key = _toPropertyKey(def.key); var descriptor; if (def.kind === "method") { descriptor = { value: def.value, writable: true, configurable: true, enumerable: false }; } else if (def.kind === "get") { descriptor = { get: def.value, configurable: true, enumerable: false }; } else if (def.kind === "set") { descriptor = { set: def.value, configurable: true, enumerable: false }; } else if (def.kind === "field") { descriptor = { configurable: true, writable: true, enumerable: true }; } var element = { kind: def.kind === "field" ? "field" : "method", key: key, placement: def.static ? "static" : def.kind === "field" ? "own" : "prototype", descriptor: descriptor }; if (def.decorators) element.decorators = def.decorators; if (def.kind === "field") element.initializer = def.value; return element; }
6 |
7 | function _coalesceGetterSetter(element, other) { if (element.descriptor.get !== undefined) { other.descriptor.get = element.descriptor.get; } else { other.descriptor.set = element.descriptor.set; } }
8 |
9 | function _coalesceClassElements(elements) { var newElements = []; var isSameElement = function (other) { return other.kind === "method" && other.key === element.key && other.placement === element.placement; }; for (var i = 0; i < elements.length; i++) { var element = elements[i]; var other; if (element.kind === "method" && (other = newElements.find(isSameElement))) { if (_isDataDescriptor(element.descriptor) || _isDataDescriptor(other.descriptor)) { if (_hasDecorators(element) || _hasDecorators(other)) { throw new ReferenceError("Duplicated methods (" + element.key + ") can't be decorated."); } other.descriptor = element.descriptor; } else { if (_hasDecorators(element)) { if (_hasDecorators(other)) { throw new ReferenceError("Decorators can't be placed on different accessors with for " + "the same property (" + element.key + ")."); } other.decorators = element.decorators; } _coalesceGetterSetter(element, other); } } else { newElements.push(element); } } return newElements; }
10 |
11 | function _hasDecorators(element) { return element.decorators && element.decorators.length; }
12 |
13 | function _isDataDescriptor(desc) { return desc !== undefined && !(desc.value === undefined && desc.writable === undefined); }
14 |
15 | function _optionalCallableProperty(obj, name) { var value = obj[name]; if (value !== undefined && typeof value !== "function") { throw new TypeError("Expected '" + name + "' to be a function"); } return value; }
16 |
17 | function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
18 |
19 | function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
20 |
21 | function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest(); }
22 |
23 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
24 |
25 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
26 |
27 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
28 |
29 | function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
30 |
31 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
32 |
33 | import { LitState, stateVar } from '../../lit-state.js';
34 |
35 | let DemoState = _decorate(null, function (_initialize, _LitState) {
36 | class DemoState extends _LitState {
37 | constructor(...args) {
38 | super(...args);
39 |
40 | _initialize(this);
41 | }
42 |
43 | }
44 |
45 | return {
46 | F: DemoState,
47 | d: [{
48 | kind: "field",
49 | decorators: [stateVar()],
50 | key: "counter",
51 |
52 | value() {
53 | return 0;
54 | }
55 |
56 | }, {
57 | kind: "field",
58 | decorators: [stateVar()],
59 | key: "connected",
60 |
61 | value() {
62 | return true;
63 | }
64 |
65 | }]
66 | };
67 | }, LitState);
68 |
69 | export const demoState = new DemoState();
--------------------------------------------------------------------------------
/docs/build/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitaarik/lit-state/8cd66223612c3b115c0275f58f6cee5e900ee534/docs/build/favicon.png
--------------------------------------------------------------------------------
/docs/build/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | LitState Docs
9 |
10 |
11 |
12 |
13 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/docs/build/snowpack/env.js:
--------------------------------------------------------------------------------
1 | export default {"MODE":"production","NODE_ENV":"production","SSR":false};
--------------------------------------------------------------------------------
/docs/build/web_modules/common/lit-style-ca615be5.js:
--------------------------------------------------------------------------------
1 | import './lit-element-336867a2.js';
2 |
3 | function litStyle(myStyles) {
4 |
5 | return superclass => class extends superclass {
6 |
7 | static getStyles() {
8 |
9 | const styles = super.getStyles();
10 |
11 | if (!styles) {
12 | return myStyles;
13 | } else if (Array.isArray(styles)) {
14 | return [myStyles, ...styles];
15 | } else {
16 | return [myStyles, styles];
17 | }
18 |
19 | }
20 |
21 | }
22 |
23 | }
24 |
25 | export { litStyle as l };
26 |
--------------------------------------------------------------------------------
/docs/build/web_modules/import-map.json:
--------------------------------------------------------------------------------
1 | {
2 | "imports": {
3 | "lit-docs": "./lit-docs.js",
4 | "lit-element": "./lit-element.js",
5 | "lit-element-style": "./lit-element-style.js"
6 | }
7 | }
--------------------------------------------------------------------------------
/docs/build/web_modules/lit-element-style.js:
--------------------------------------------------------------------------------
1 | import './common/lit-element-336867a2.js';
2 | export { l as litStyle } from './common/lit-style-ca615be5.js';
3 |
--------------------------------------------------------------------------------
/docs/build/web_modules/lit-element.js:
--------------------------------------------------------------------------------
1 | export { L as LitElement, c as css, h as html } from './common/lit-element-336867a2.js';
2 |
3 | /**
4 | * @license
5 | * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
6 | * This code may only be used under the BSD style license found at
7 | * http://polymer.github.io/LICENSE.txt
8 | * The complete set of authors may be found at
9 | * http://polymer.github.io/AUTHORS.txt
10 | * The complete set of contributors may be found at
11 | * http://polymer.github.io/CONTRIBUTORS.txt
12 | * Code distributed by Google as part of the polymer project is also
13 | * subject to an additional IP rights grant found at
14 | * http://polymer.github.io/PATENTS.txt
15 | */
16 | const legacyCustomElement = (tagName, clazz) => {
17 | window.customElements.define(tagName, clazz);
18 | // Cast as any because TS doesn't recognize the return type as being a
19 | // subtype of the decorated class when clazz is typed as
20 | // `Constructor` for some reason.
21 | // `Constructor` is helpful to make sure the decorator is
22 | // applied to elements however.
23 | // tslint:disable-next-line:no-any
24 | return clazz;
25 | };
26 | const standardCustomElement = (tagName, descriptor) => {
27 | const { kind, elements } = descriptor;
28 | return {
29 | kind,
30 | elements,
31 | // This callback is called once the class is otherwise fully defined
32 | finisher(clazz) {
33 | window.customElements.define(tagName, clazz);
34 | }
35 | };
36 | };
37 | /**
38 | * Class decorator factory that defines the decorated class as a custom element.
39 | *
40 | * ```
41 | * @customElement('my-element')
42 | * class MyElement {
43 | * render() {
44 | * return html``;
45 | * }
46 | * }
47 | * ```
48 | * @category Decorator
49 | * @param tagName The name of the custom element to define.
50 | */
51 | const customElement = (tagName) => (classOrDescriptor) => (typeof classOrDescriptor === 'function') ?
52 | legacyCustomElement(tagName, classOrDescriptor) :
53 | standardCustomElement(tagName, classOrDescriptor);
54 | const standardProperty = (options, element) => {
55 | // When decorating an accessor, pass it through and add property metadata.
56 | // Note, the `hasOwnProperty` check in `createProperty` ensures we don't
57 | // stomp over the user's accessor.
58 | if (element.kind === 'method' && element.descriptor &&
59 | !('value' in element.descriptor)) {
60 | return Object.assign(Object.assign({}, element), { finisher(clazz) {
61 | clazz.createProperty(element.key, options);
62 | } });
63 | }
64 | else {
65 | // createProperty() takes care of defining the property, but we still
66 | // must return some kind of descriptor, so return a descriptor for an
67 | // unused prototype field. The finisher calls createProperty().
68 | return {
69 | kind: 'field',
70 | key: Symbol(),
71 | placement: 'own',
72 | descriptor: {},
73 | // When @babel/plugin-proposal-decorators implements initializers,
74 | // do this instead of the initializer below. See:
75 | // https://github.com/babel/babel/issues/9260 extras: [
76 | // {
77 | // kind: 'initializer',
78 | // placement: 'own',
79 | // initializer: descriptor.initializer,
80 | // }
81 | // ],
82 | initializer() {
83 | if (typeof element.initializer === 'function') {
84 | this[element.key] = element.initializer.call(this);
85 | }
86 | },
87 | finisher(clazz) {
88 | clazz.createProperty(element.key, options);
89 | }
90 | };
91 | }
92 | };
93 | const legacyProperty = (options, proto, name) => {
94 | proto.constructor
95 | .createProperty(name, options);
96 | };
97 | /**
98 | * A property decorator which creates a LitElement property which reflects a
99 | * corresponding attribute value. A [[`PropertyDeclaration`]] may optionally be
100 | * supplied to configure property features.
101 | *
102 | * This decorator should only be used for public fields. Private or protected
103 | * fields should use the [[`internalProperty`]] decorator.
104 | *
105 | * @example
106 | * ```ts
107 | * class MyElement {
108 | * @property({ type: Boolean })
109 | * clicked = false;
110 | * }
111 | * ```
112 | * @category Decorator
113 | * @ExportDecoratedItems
114 | */
115 | function property(options) {
116 | // tslint:disable-next-line:no-any decorator
117 | return (protoOrDescriptor, name) => (name !== undefined) ?
118 | legacyProperty(options, protoOrDescriptor, name) :
119 | standardProperty(options, protoOrDescriptor);
120 | }
121 |
122 | export { customElement, property };
123 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "start": "snowpack dev",
4 | "build": "snowpack build",
5 | "test": "echo \"This template does not include a test runner by default.\" && exit 1",
6 | "format": "prettier --write \"src/**/*.js\"",
7 | "lint": "prettier --check \"src/**/*.js\""
8 | },
9 | "dependencies": {
10 | "lit-docs": "^0.0.33",
11 | "lit-element": "^2.3.1",
12 | "lit-html": "^1.2.1"
13 | },
14 | "devDependencies": {
15 | "@babel/plugin-proposal-class-properties": "^7.10.4",
16 | "@babel/plugin-proposal-decorators": "^7.10.5",
17 | "@snowpack/plugin-babel": "^2.1.4",
18 | "@snowpack/plugin-dotenv": "^2.0.4",
19 | "prettier": "^2.0.5",
20 | "snowpack": "^2.17.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/docs/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitaarik/lit-state/8cd66223612c3b115c0275f58f6cee5e900ee534/docs/public/favicon.png
--------------------------------------------------------------------------------
/docs/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | LitState Docs
9 |
10 |
11 |
12 |
13 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/docs/snowpack.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import("snowpack").SnowpackUserConfig } */
2 | module.exports = {
3 | mount: {
4 | public: '/',
5 | src: '/dist',
6 | },
7 | plugins: ['@snowpack/plugin-babel', '@snowpack/plugin-dotenv'],
8 | install: [
9 | /* ... */
10 | ],
11 | installOptions: {
12 | /* ... */
13 | },
14 | devOptions: {
15 | /* ... */
16 | },
17 | buildOptions: {
18 | // The build is meant for GitHub pages, so we need to set the path to the
19 | // GitHub pages URL as `baseUrl`
20 | baseUrl: '/lit-state/build/',
21 | metaDir: 'snowpack'
22 | },
23 | proxy: {
24 | /* ... */
25 | },
26 | alias: {
27 | "@app": "./src"
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/docs/src/advanced-usage/manually-observe-state/index.js:
--------------------------------------------------------------------------------
1 | import { customElement, LitElement, property, html } from 'lit-element';
2 | import { LitDocsContent } from 'lit-docs';
3 | import 'lit-docs';
4 | import './observe-all-state-vars/automatic-component.js';
5 | import './observe-all-state-vars/manual-component.js';
6 | import './observe-specific-state-vars/specific-automatic-component.js';
7 | import './observe-specific-state-vars/specific-manual-component.js';
8 |
9 |
10 | @customElement('manually-observe-state')
11 | export class ManuallyObserveState extends LitDocsContent(LitElement) {
12 |
13 | render() {
14 |
15 | return html`
16 |
17 |
Manually observe the state
18 |
19 |
20 | When you use the observeState() mixin,
21 | your LitState Element automatically observes the states you use
22 | in that component, and re-renders when the state changes. You
23 | can also manually add more observers that will be notified when
24 | your state changes. This can be handy if you have other parts
25 | of your app (different from your LitElement components) that
26 | need to know when a state has changed.
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | To add observers to a certain state instance, you just call
36 | myState.addObserver(myCallback). The
37 | callback will be called any time a
38 | stateVar changes. The callback will
39 | get as a first argument the name of the
40 | stateVar that changed. To stop
41 | observing, call
42 | myState.removeObserver(myCallback). It
43 | is similar to the way
44 | document.addEventListener() and
45 | document.removeEventListener() work.
46 |
47 |
48 |
49 | To keep the example simple, we still use a LitElement
50 | component. We just don't use the
51 | observeState() mixin. There are
52 | buttons to manually add and remove the observer:
53 |
54 |
55 |
56 |
57 |
58 |
Observe specific stateVars
59 |
60 |
61 | As an optional second argument to
62 | addObserver(), you can provide an
63 | array with stateVar names that you
64 | want to observe. The callback will then only be called when any
65 | of those stateVar variables change.
66 |
16 | LitState needs to know which stateVar variables
17 | are being used by your components. That's why you need to add
18 | the observeState() mixin to your
19 | LitElement components. The
20 | observeState() mixin uses the
21 | stateRecorder object to record which
22 | stateVar variables have been accessed during it's
23 | last render cycle.
24 |
25 |
26 |
Automatic usage with the observeState() mixin
27 |
28 |
29 | The observeState() mixin automatically uses the
30 | stateRecorder to record which
31 | stateVar variables are used by your component.
32 | Just before the component renders,
33 | stateRecorder.start() is being called to start
34 | recording. When a stateVar is being read, it's
35 | handler
36 | will record it to the stateRecorder.
37 |
38 |
39 |
40 | When the component has finished rendering, the
41 | stateRecorder.finish() method is called. This
42 | stops recording stateVar variables, and returns
43 | the recorded ones. These collected stateVar
44 | variables are then observed, and when one of them changes, the
45 | component re-renders itself.
46 |
47 |
48 |
49 | So when you use the observeState() mixin on your
50 | LitElement components, this is all being taken
51 | care of for you.
52 |
53 |
54 |
Manual usage
55 |
56 |
57 | For custom implementations, you might want to use the
58 | stateRecorder manually. You could for example,
59 | create a observeState() variation for React. That
60 | would re-render a React component when a stateVar
61 | that is used inside the component changes. See the
62 | API reference
63 | for more information on how to use stateRecorder.
64 |
17 | You can also define your own stateVar handler
18 | class. This allows you to have custom functionality when you
19 | read/write your stateVars. An example of a custom state var is
20 | asyncStateVar.
21 | This is a type of stateVar that makes it easy to deal with
22 | asynchronous data, and the state of the data (loading,
23 | updating, loaded, updated, etc).
24 |
25 |
26 |
27 | On this page we'll make a custom stateVar handler ourselves.
28 | We'll create a LocalStorageHandler that saves your
29 | stateVar values to localStorage. So
30 | that your state is retained when you refresh your page.
31 |
32 |
33 |
34 | Here is the component already. Later we'll explain how it is
35 | made. To try it out, change the value and refresh the page.
36 |
37 |
38 |
39 |
40 |
41 |
42 |
Default StateVar handler
43 |
44 |
45 | We implement this handler by creating a new class that extends
46 | from the default handler class
47 | StateVar. This default handler class
48 | looks like this:
49 |
50 |
51 |
52 |
53 |
54 |
55 |
Custom StateVar handler
56 |
57 |
58 | We will extend this class and add some functionality. You see
59 | that the constructor of the default handler sets some default
60 | variables. We will use some of these in our custom handler.
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | Like this, when the stateVar is
69 | created, the initial value is set to any previously set
70 | localStorage value, or else to
71 | options.initialValue. And whenever a
72 | new value is set, it is saved to
73 | localStorage. The option
74 | localStorageKey is used as the key for
75 | the localStorage.
76 |
77 |
78 |
Usage in state class
79 |
80 |
81 | Now let's see how we use this custom
82 | stateVar handler in our
83 | demoState class:
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | You see that we tell LitState to use a different handler by
92 | specifying the handler option. The
93 | other options are options of our own custom handler. The
94 | initialValue should be set through an
95 | option. It can't be set like counter = 0,
96 | because that would be seen as a regular assignment, and would
97 | override any previously value in
98 | localStorage.
99 |
100 |
101 |
Custom decorator
102 |
103 |
104 | If you use your custom stateVar
105 | handler a lot, it could be useful to also make a custom
106 | decorator function:
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | This allows you to define the stateVar
115 | like this:
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | Custom stateVar handlers give you a
124 | lot of power for customizing what happens when your
125 | stateVar variables are being get/set.
126 |
127 |
128 |
No-decorator usage
129 |
130 |
131 | Without decorators, use the static stateVarOptions()
132 | getter method to specify the options:
133 |
134 |
135 |
136 |
137 |
138 |
139 |
Providing options from a method
140 |
141 |
142 | To give you access to the this objects on your
143 | state instance, you can additionally add options to your
144 | handler through a method.
145 |
16 | This is the API reference for advanced usage. See
17 | Basic usage
18 | on how to use this class.
19 |
20 |
21 |
class LitState
22 |
23 |
24 | Property stateVars
25 |
26 |
27 |
28 | Contains the stateVar variables that have been
29 | defined (either through decorators or not). It's an object with
30 | the name in the key and the options in the value:
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | The default stateVar handler doesn't require any
39 | options. But the options can be used to specify a
40 | custom
41 | stateVar handler.
42 | A custom handler might also accept extra options:
43 |
44 |
45 |
46 |
47 |
48 |
49 |
Method addObserver(observer, keys)
50 |
51 |
52 | Add a observer to this state class. This is for when you need
53 | to observe the state outside of a LitElement component, see
54 | Manually observe state.
55 | For usage with LitElement, use the
56 | observeState() mixin.
57 |
58 |
59 |
observer
60 |
61 |
62 | Function that will be called when the state changes. This
63 | function will get as a first argument the name of the
64 | stateVar that has been changed.
65 |
66 |
67 |
keys
68 |
69 |
70 | An Array of names of stateVar variables to
71 | observe. Only if those stateVar variables change,
72 | the observer will be called. If not provided, the
73 | observer will be called when any
74 | stateVar variable changes.
75 |
76 |
77 |
78 |
79 |
80 |
81 |
Method removeObserver(observer)
82 |
83 |
84 | Removes a previously added observer. The observer
85 | argument should be the same as the observer
86 | argument given to addObserver():
87 |
16 | This mixin is meant to be used on your components that extend
17 | from LitElement. You apply the mixin by wrapping
18 | the LitElement class with this mixin function:
19 | observeState(LitElement).
20 |
21 |
22 |
23 | This makes your LitElement component aware of any
24 | stateVar variables. When stateVar
25 | variables are being read by the component during render, it
26 | will record this. Then when any of the recorded
27 | stateVar variables are being changed, the
28 | component re-renders itself.
29 |
30 |
31 |
observeState(litElementClass)
32 |
33 |
34 | Add "stateVar awareness" to the given
35 | litElementClass, which should be a class
36 | definition of, or an extend from, LitElement's
37 | LitElement class.
38 |
16 | This decorator function is used to define stateVar
17 | variables on a LitState derived class. When these
18 | variables are set or get from the LitState class,
19 | for example myState.myStateVar = 'value', the
20 | observers of the LitState class will be notified.
21 |
22 |
23 |
@stateVar(options)
24 |
25 |
options
26 |
27 |
28 | Optional parameter that should be a object containing the
29 | options for the StateVar handler class. The
30 | default StateVar handler class doesn't take any
31 | options. You can use the option handler to specify
32 | a custom
33 | StateVar handler class. Other
34 | options you specify will be passed on to this custom
35 | StateVar handler class.
36 |
16 | This is the default handler class for the stateVar
17 | variables. When you define a stateVar and you
18 | don't specify a custom handler class, this class will be used.
19 |
20 |
21 |
22 | A handler class controls what happens when a
23 | stateVar is being set or get. For more information
24 | on how to create a custom stateVar handler class,
25 | see stateVar handler.
26 |
27 |
28 |
29 | You shouldn't use a StateVar handler class
30 | directly as a user. You only need to know about it when
31 | defining a custom StateVar handler class. The
32 | handler class is called by LitState internally, so you don't
33 | need to call it yourself.
34 |
35 |
36 |
class StateVar
37 |
38 |
Methods
39 |
40 |
constructor(args)
41 |
42 |
43 | The constructor takes a single argument args which
44 | is an object containing the following properties:
45 |
46 |
47 |
48 |
49 |
options
50 |
The options given in the stateVar declaration.
51 |
52 |
53 |
recordRead
54 |
Callback to indicate the stateVar is read.
55 |
56 |
57 |
notifyChange
58 |
Callback to indicate the stateVar value has changed.
59 |
60 |
61 |
value
62 |
The initial value.
63 |
64 |
65 |
66 |
get()
67 |
68 |
69 | Called when the stateVar on the
70 | LitState class is read (for example:
71 | myState.myStateVar). Should return the value of
72 | the stateVar. Typically, this method should call
73 | the recordRead() callback, set in the
74 | constructor(), to indicate the observers that the
75 | stateVar is being read.
76 |
77 |
78 |
shouldSetValue(value)
79 |
80 |
81 | Called before the set() method is called. If this
82 | method returns false, the set()
83 | method won't be called. This can be used for validation and/or
84 | optimization.
85 |
86 |
87 |
set(value)
88 |
89 |
90 | Called when the stateVar on the
91 | LitState class is set (for example:
92 | myState.myStateVar = 'value'.
93 |
94 |
95 |
96 | Should set the value of the stateVar. Typically,
97 | this method would call the notifyChange()
98 | callback, set in the constructor(), to indicate
99 | the observers that the stateVar has changed.
100 |
18 | Below are 2 components with a shared state
19 | demoState. When you change the state
20 | from one component, the other component automatically
21 | synchronizes:
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
The state class
30 |
31 |
32 | The shared state demoState contains a
33 | stateVar called
34 | counter. It holds an integer that has
35 | an initial value of 0:
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Look here
45 | for usage without
46 | decorators.
47 |
48 |
49 |
50 |
Usage in component
51 |
52 |
53 | The components that use the state use the mixin
54 | observeState. This makes them
55 | automatically re-render when a
56 | stateVar they use changes:
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | That's all. How simple do you want to have it?
65 |
18 | In case you can't or don't want to use
19 | decorators,
20 | you can define the stateVars with a static
21 | stateVars getter method. This method should return
22 | an object with in the keys the name of the
23 | stateVar and in the value the initial value for
24 | it.
25 |
18 | LitState automatically re-renders your LitElement
19 | components, when a shared app state variable they use
20 | changes. It's like LitElement's
21 | properties,
22 | but then shared over multiple components.
23 |
24 |
25 |
Installation
26 |
27 |
28 |
29 |
30 |
31 |
Usage
32 |
33 |
1. Create a LitState object:
34 |
35 |
36 | Use the stateVar() decorator to define
37 | the variables that should be observed by LitState.
38 |
39 |
40 |
41 |
42 |
43 |
44 |
2. Make your component aware of your state:
45 |
46 |
47 | Use the observeState() mixin on your
48 | LitElement components to make them
49 | re-render when any stateVar variables
50 | they use changes.
51 |
17 | You can have helper methods on your
18 | LitState class that return computed
19 | values, or update multiple values at the same time.
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | In our state class, we have a getter function that returns the
28 | sum of both numbers. Also there is a method that increases both
29 | numbers.
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Our component makes use of these helper methods, so that it
38 | doesn't have to do this work itself.
39 |
16 | Typically you would keep your state class (which extends from
17 | LitState) in a separate file, where the created
18 | instance of your state class is exported:
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Then you import the state in the component file where you need
27 | it. And use observeState() to decorate your components:
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | But you could also have your state and all your components in
36 | one file if you like. Or do it any other way that works for
37 | you.
38 |
17 | You can also easily nest states.
18 | stateVar variables can contain
19 | instances of other states. They will be recognized by LitState
20 | just the same.
21 |
22 |
23 |
Example:
24 |
25 |
26 |
27 |
28 |
29 |
30 | Note that there's only 1
31 | ParentState instance created, which is exported.
32 | There are 2ChildState instances
33 | created, which are set on the parentState
34 | instance. The component imports the parentState,
35 | and accesses the child states from there.
36 |
20 | A component could render different stateVar
21 | variables at every re-render. Possible new
22 | stateVar variables should be observed so that the
23 | component re-renders when they change.
24 |
25 |
26 |
Execution
27 |
28 |
29 | LitState records the stateVar variables that are
30 | used in the component every time it re-renders.
31 |
32 |
33 |
Demonstration
34 |
35 |
36 | In this example, <changing-component> only
37 | shows 1 counter at a time, depending on the value of
38 | showCounter.
39 | <control-component> shows them both, and you
40 | can modify them there.
41 |
16 | This subsection contains demonstrations of specific use cases.
17 | And the way LitState covers those use cases. These pages also
18 | act as a way to test these use cases.
19 |