@webtides/element-js 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +140 -90
- package/README.md +3 -3
- package/docs/README.md +70 -89
- package/docs/concepts/classes.md +37 -66
- package/docs/concepts/hierarchy.md +29 -31
- package/docs/concepts/lifecycle.md +9 -23
- package/docs/concepts/reactivity.md +1 -3
- package/docs/features/computed-properties.md +13 -16
- package/docs/features/dependency-injection-context-protocol.md +75 -87
- package/docs/features/global-styles.md +1 -1
- package/docs/features/methods.md +14 -15
- package/docs/features/properties.md +21 -33
- package/docs/features/refs.md +23 -31
- package/docs/features/renderless.md +31 -37
- package/docs/features/store.md +63 -78
- package/docs/features/templates.md +4 -1
- package/docs/features/watch.md +28 -35
- package/docs/getting-started/upgrade-to-v1.md +16 -0
- package/docs/guide/guide.md +10 -22
- package/index.js +29 -2
- package/package.json +62 -62
- package/src/BaseElement.js +631 -631
- package/src/StyledElement.js +104 -104
- package/src/TemplateElement.js +53 -53
- package/src/dom-parts/AttributePart.js +116 -116
- package/src/dom-parts/ChildNodePart.js +288 -288
- package/src/dom-parts/NodePart.js +27 -27
- package/src/dom-parts/Part.js +6 -6
- package/src/dom-parts/TemplatePart.js +119 -119
- package/src/dom-parts/TemplateResult.js +270 -270
- package/src/dom-parts/directives.js +105 -48
- package/src/dom-parts/html.js +1 -1
- package/src/dom-parts/render.js +6 -6
- package/src/util/AttributeParser.js +47 -47
- package/src/util/DOMHelper.js +190 -190
- package/src/util/Directive.js +31 -31
- package/src/util/GlobalStylesStore.js +56 -53
- package/src/util/SerializeStateHelper.js +56 -56
- package/src/util/Store.js +174 -174
- package/src/util/defineElement.js +5 -5
- package/src/util/toString.js +5 -5
|
@@ -19,16 +19,16 @@ let globalElementJsState;
|
|
|
19
19
|
* Initializes the global script element that holds the state for all `Serializable` objects.
|
|
20
20
|
*/
|
|
21
21
|
function initGlobalStateObject() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
if (!globalElementJsState) {
|
|
23
|
+
globalElementJsState = Array.from(globalThis.document.scripts).find((script) => script.type === 'ejs/json');
|
|
24
|
+
if (!globalElementJsState) {
|
|
25
|
+
const script = document.createElement('script');
|
|
26
|
+
script.setAttribute('type', 'ejs/json');
|
|
27
|
+
script.textContent = '{}';
|
|
28
|
+
document.body.appendChild(script);
|
|
29
|
+
globalElementJsState = script;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -36,23 +36,23 @@ function initGlobalStateObject() {
|
|
|
36
36
|
* @param {Serializable} serializableObject
|
|
37
37
|
*/
|
|
38
38
|
export function serializeState(serializableObject) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
if (!globalThis.elementJsConfig?.serializeState) return;
|
|
40
|
+
|
|
41
|
+
if (!serializableObject._serializationKey && !serializableObject.serializeState) {
|
|
42
|
+
throw new Error('serializableObject does not implement the Serializable interface');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
initGlobalStateObject();
|
|
46
|
+
|
|
47
|
+
const currentState = JSON.parse(globalElementJsState.textContent);
|
|
48
|
+
currentState[serializableObject._serializationKey] = serializableObject.serializeState();
|
|
49
|
+
globalElementJsState.textContent = JSON.stringify(currentState, (key, value) => {
|
|
50
|
+
if (value instanceof Store) {
|
|
51
|
+
return 'Store/' + value._serializationKey;
|
|
52
|
+
} else {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
@@ -61,31 +61,31 @@ export function serializeState(serializableObject) {
|
|
|
61
61
|
* @param {{[string: any]: *}} [serializedState]
|
|
62
62
|
*/
|
|
63
63
|
export function deserializeState(serializableObject, serializedState) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
64
|
+
if (!globalThis.elementJsConfig?.serializeState) return;
|
|
65
|
+
|
|
66
|
+
if (!serializableObject._serializationKey && !serializableObject.restoreState) {
|
|
67
|
+
throw new Error('serializableObject does not implement the Serializable interface');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (serializedState) {
|
|
71
|
+
serializableObject.restoreState(serializedState);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
initGlobalStateObject();
|
|
76
|
+
|
|
77
|
+
const unresolvedState = JSON.parse(globalElementJsState.textContent);
|
|
78
|
+
const currentState = JSON.parse(globalElementJsState.textContent, (key, value) => {
|
|
79
|
+
if (typeof value === 'string' && value.startsWith('Store/')) {
|
|
80
|
+
const [_, storeUuid] = value.split('/');
|
|
81
|
+
const serializedState = unresolvedState[storeUuid];
|
|
82
|
+
return new Store(serializedState, { key: storeUuid, serializedState });
|
|
83
|
+
} else {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
serializableObject.restoreState(currentState[serializableObject._serializationKey]);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
@@ -94,10 +94,10 @@ export function deserializeState(serializableObject, serializedState) {
|
|
|
94
94
|
* @return {boolean}
|
|
95
95
|
*/
|
|
96
96
|
export function hasSerializedState(key) {
|
|
97
|
-
|
|
97
|
+
if (!globalThis.elementJsConfig?.serializeState) return false;
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
initGlobalStateObject();
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
const serializedState = JSON.parse(globalElementJsState.textContent);
|
|
102
|
+
return serializedState.hasOwnProperty(key);
|
|
103
103
|
}
|
package/src/util/Store.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { deepEquals, isObjectLike } from './AttributeParser.js';
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
hasSerializedState,
|
|
4
|
+
serializeState,
|
|
5
|
+
deserializeState,
|
|
6
|
+
serializableObjectsCache,
|
|
7
7
|
} from './SerializeStateHelper.js';
|
|
8
8
|
import { BaseElement } from '../BaseElement.js';
|
|
9
9
|
|
|
@@ -17,174 +17,174 @@ import { BaseElement } from '../BaseElement.js';
|
|
|
17
17
|
* @implements {Serializable}
|
|
18
18
|
*/
|
|
19
19
|
export class Store {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
20
|
+
_serializationKey;
|
|
21
|
+
_observer = new Set([]);
|
|
22
|
+
_singlePropertyMode = false;
|
|
23
|
+
_state = {};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {* | object} [value]
|
|
27
|
+
* @param {StoreOptions} [options]
|
|
28
|
+
*/
|
|
29
|
+
constructor(value, options) {
|
|
30
|
+
if (options?.key && serializableObjectsCache.has(options?.key)) {
|
|
31
|
+
return serializableObjectsCache.get(options?.key);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this._singlePropertyMode = arguments.length > 0 && !isObjectLike(value);
|
|
35
|
+
|
|
36
|
+
if (options?.key) {
|
|
37
|
+
this._serializationKey = options?.key;
|
|
38
|
+
} else {
|
|
39
|
+
this._serializationKey = globalThis.crypto.randomUUID();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (options?.serializedState) {
|
|
43
|
+
deserializeState(this, options.serializedState);
|
|
44
|
+
} else if (hasSerializedState(this._serializationKey)) {
|
|
45
|
+
deserializeState(this);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// wrap primitives with a generic "value" field to generate getter and setter
|
|
49
|
+
const specificValues = this._singlePropertyMode ? { value } : value;
|
|
50
|
+
const properties = { ...(!this._singlePropertyMode && this.properties()), ...specificValues };
|
|
51
|
+
|
|
52
|
+
const serializationKeys = Object.keys(this.serializeState());
|
|
53
|
+
Object.entries(properties).map(([key, value]) => {
|
|
54
|
+
this._state[key] =
|
|
55
|
+
hasSerializedState(this._serializationKey) && serializationKeys.includes(key) ? this[key] : value;
|
|
56
|
+
|
|
57
|
+
if (value instanceof Store) {
|
|
58
|
+
value.subscribe(this);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Object.defineProperty(this, key, {
|
|
62
|
+
get: () => {
|
|
63
|
+
return this._state[key];
|
|
64
|
+
},
|
|
65
|
+
set: (newValue) => {
|
|
66
|
+
const oldValue = this._state[key];
|
|
67
|
+
this._state[key] = newValue;
|
|
68
|
+
if (value instanceof Store) {
|
|
69
|
+
value.subscribe(this);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
serializeState(this);
|
|
73
|
+
|
|
74
|
+
if (!deepEquals(newValue, oldValue)) {
|
|
75
|
+
const watch = this.watch();
|
|
76
|
+
if (watch.hasOwnProperty(key) && typeof watch[key] === 'function') {
|
|
77
|
+
watch[key](newValue, oldValue);
|
|
78
|
+
}
|
|
79
|
+
this.requestUpdate();
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
serializableObjectsCache.set(this._serializationKey, this);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Properties to be defined and assigned on the store. Can be overridden in extended classes.
|
|
90
|
+
* Properties defined here will also trigger an update cycle when changed.
|
|
91
|
+
* @return {{}}
|
|
92
|
+
*/
|
|
93
|
+
properties() {
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* When a property as the key was changed in the store, the callback function defined as value
|
|
99
|
+
* will be called with `newValue` and `oldValue`
|
|
100
|
+
* @return {{}}
|
|
101
|
+
*/
|
|
102
|
+
watch() {
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Overrides `valueOf` to automatically return .value
|
|
108
|
+
* @returns {* | {}}
|
|
109
|
+
*/
|
|
110
|
+
valueOf() {
|
|
111
|
+
return this._singlePropertyMode ? this._state.value : this._state;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Overrides `toString` to automatically return .value
|
|
116
|
+
* @returns {string}
|
|
117
|
+
*/
|
|
118
|
+
toString() {
|
|
119
|
+
return `${this._singlePropertyMode ? this._state.value : this._state}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Add an observer
|
|
124
|
+
* @param {BaseElement|Function|Store} observer
|
|
125
|
+
*/
|
|
126
|
+
subscribe(observer) {
|
|
127
|
+
this._observer.add(observer);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Remove an observer
|
|
132
|
+
* @param {BaseElement | Function} observer
|
|
133
|
+
*/
|
|
134
|
+
unsubscribe(observer) {
|
|
135
|
+
this._observer.delete(observer);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Request and batch an asynchronous update cycle
|
|
140
|
+
*/
|
|
141
|
+
requestUpdate() {
|
|
142
|
+
this._observer.forEach(async (observer) => {
|
|
143
|
+
if (observer instanceof BaseElement || observer instanceof Store) {
|
|
144
|
+
if (observer?._options?.autoUpdate !== false) {
|
|
145
|
+
await observer.requestUpdate();
|
|
146
|
+
}
|
|
147
|
+
// check if store is watched by an observer
|
|
148
|
+
if (Object.keys(observer.watch() ?? {}).length > 0) {
|
|
149
|
+
// observer actually has watched properties
|
|
150
|
+
Object.keys(observer.watch() ?? {}).forEach((key) => {
|
|
151
|
+
if (observer._state[key] === this) {
|
|
152
|
+
// observer is actually watching store changes provide new and old values
|
|
153
|
+
if (observer instanceof Store) {
|
|
154
|
+
// observer is a foreign store / call watcher
|
|
155
|
+
observer.watch?.()[key](this, this);
|
|
156
|
+
} else {
|
|
157
|
+
observer.callPropertyWatcher(key, this, this);
|
|
158
|
+
observer.notifyPropertyChange(key, this);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
} else if (typeof observer === 'function') {
|
|
164
|
+
observer();
|
|
165
|
+
} else {
|
|
166
|
+
console.error('Store: observer type is not supported.', observer);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* This method will be used to serialize this stores state.
|
|
173
|
+
* By default, it will generate an object with all properties from this.properties().
|
|
174
|
+
* You can override this and return a different object with more/less/different properties.
|
|
175
|
+
* @return {{[string: any]: *}}
|
|
176
|
+
*/
|
|
177
|
+
serializeState() {
|
|
178
|
+
const keys = Object.keys(this.properties());
|
|
179
|
+
return Object.fromEntries(keys.map((key) => [key, this[key]]));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* This method will be used to restore properties on the store from a previously serialized state.
|
|
184
|
+
* You can override this and handle the deserialization differently if the default is not enough.
|
|
185
|
+
* @param {{[string: any]: *}} serializedState
|
|
186
|
+
*/
|
|
187
|
+
restoreState(serializedState) {
|
|
188
|
+
Object.assign(this, serializedState);
|
|
189
|
+
}
|
|
190
190
|
}
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* @param {CustomElementConstructor} constructor for the custom element
|
|
5
5
|
*/
|
|
6
6
|
export function defineElement(name, constructor) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
try {
|
|
8
|
+
customElements.define(name, constructor);
|
|
9
|
+
} catch (e) {
|
|
10
|
+
// console.log('error defining custom element', e);
|
|
11
|
+
}
|
|
12
12
|
}
|
package/src/util/toString.js
CHANGED