@webtides/element-js 1.2.5 → 1.2.11
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/docs/README.md +1 -1
- package/package.json +5 -2
- package/src/BaseElement.js +37 -14
- package/src/StyledElement.js +22 -13
- package/src/TemplateElement.js +8 -2
- package/src/util/DOMHelper.js +16 -2
package/docs/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webtides/element-js",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.11",
|
|
4
4
|
"module": "index.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
"./src/util/*": "./src/util/*.js",
|
|
20
20
|
"./src/dom-parts/*": "./src/dom-parts/*.js"
|
|
21
21
|
},
|
|
22
|
-
"repository":
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/webtides/element-js.git"
|
|
25
|
+
},
|
|
23
26
|
"author": "@webtides",
|
|
24
27
|
"license": "MIT",
|
|
25
28
|
"keywords": [
|
package/src/BaseElement.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { parseAttribute, isNaN, dashToCamel, camelToDash, isObjectLike } from './util/AttributeParser.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getAllComposedElementChildren,
|
|
4
|
+
getAllElementChildren,
|
|
5
|
+
getClosestParentCustomElementNode,
|
|
6
|
+
isOfSameNodeType,
|
|
7
|
+
} from './util/DOMHelper.js';
|
|
3
8
|
import { Store } from './util/Store.js';
|
|
4
9
|
import { serializeState, deserializeState, hasSerializedState } from './util/SerializeStateHelper.js';
|
|
5
10
|
import { randomUUID } from './util/crypto.js';
|
|
@@ -126,11 +131,15 @@ class BaseElement extends HTMLElement {
|
|
|
126
131
|
|
|
127
132
|
this.triggerHook('connected');
|
|
128
133
|
this._internals.states.add('connected');
|
|
134
|
+
// verification must happen after connection
|
|
135
|
+
this.verifyProvidedProperties();
|
|
129
136
|
} else {
|
|
130
137
|
this.requestUpdate({ notify: false }).then(() => {
|
|
131
138
|
this.triggerHook('connected');
|
|
132
139
|
this._internals.states.add('connected');
|
|
133
140
|
this.triggerHook('afterUpdate');
|
|
141
|
+
// verification must happen after connection
|
|
142
|
+
this.verifyProvidedProperties();
|
|
134
143
|
});
|
|
135
144
|
}
|
|
136
145
|
}
|
|
@@ -194,9 +203,6 @@ class BaseElement extends HTMLElement {
|
|
|
194
203
|
// release dom references
|
|
195
204
|
this.$refs = {};
|
|
196
205
|
|
|
197
|
-
// clear state
|
|
198
|
-
this._state = {};
|
|
199
|
-
|
|
200
206
|
// cancel pending updates
|
|
201
207
|
if (this._batchUpdate) {
|
|
202
208
|
cancelAnimationFrame(this._batchUpdate);
|
|
@@ -292,13 +298,14 @@ class BaseElement extends HTMLElement {
|
|
|
292
298
|
* Will trigger update() when a property was changed
|
|
293
299
|
*/
|
|
294
300
|
defineProperties() {
|
|
295
|
-
|
|
301
|
+
const properties = this.properties();
|
|
302
|
+
Object.keys({ ...this.injectProperties(), ...properties }).forEach((prop) => {
|
|
296
303
|
// when mixing shadow dom elements with light dom elements and nesting custom elements
|
|
297
304
|
// it might occur that properties where set on an element before it was
|
|
298
305
|
// registered or connected. To avoid such timing issues we check
|
|
299
306
|
// if a value was set for that specific property on the
|
|
300
307
|
// prototype before assigning a default value
|
|
301
|
-
const value = this[prop]
|
|
308
|
+
const value = this[prop] ?? properties[prop];
|
|
302
309
|
this.defineProperty(prop, value);
|
|
303
310
|
});
|
|
304
311
|
}
|
|
@@ -312,11 +319,6 @@ class BaseElement extends HTMLElement {
|
|
|
312
319
|
* @param {boolean} isAttribute
|
|
313
320
|
*/
|
|
314
321
|
defineProperty(property, value, isAttribute = false) {
|
|
315
|
-
if (this._state.hasOwnProperty(property)) {
|
|
316
|
-
// property has already been defined as an attribute nothing to do here
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
322
|
// if property did not come from an attribute but has the option to reflect // enabled or custom fn
|
|
321
323
|
if (!isAttribute && this._options.propertyOptions[property]?.reflect) {
|
|
322
324
|
this.reflectProperty({ property: property, newValue: value });
|
|
@@ -460,14 +462,35 @@ class BaseElement extends HTMLElement {
|
|
|
460
462
|
const providedKeys = Object.keys(this.provideProperties());
|
|
461
463
|
if (providedKeys.length > 0) {
|
|
462
464
|
this.addEventListener('request-context', this.onRequestContext);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Verifies provided context after the element has finished connecting.
|
|
470
|
+
*
|
|
471
|
+
* This is used as a failover step for late providers and lifecycle race conditions,
|
|
472
|
+
* especially when a provided value is assigned inside `connected()` and the initial
|
|
473
|
+
* context request was resolved before that mutation happened.
|
|
474
|
+
*
|
|
475
|
+
* By replaying matching requests here we ensure children receive the final provider
|
|
476
|
+
* value instead of being stuck with an early placeholder like `null`.
|
|
477
|
+
*/
|
|
478
|
+
|
|
479
|
+
verifyProvidedProperties() {
|
|
480
|
+
// define context provider
|
|
481
|
+
const providedProperties = this.provideProperties();
|
|
482
|
+
const providedKeys = Object.keys(providedProperties);
|
|
483
|
+
if (providedKeys.length > 0) {
|
|
484
|
+
// take both light and shadow dom into account.
|
|
485
|
+
const composedChildren = getAllComposedElementChildren(this, BaseElement);
|
|
463
486
|
// check if there are already connected elements in child dom and restart requests
|
|
464
|
-
|
|
487
|
+
composedChildren.forEach((customChild) => {
|
|
465
488
|
// if injectProperties?.() is defined it means that the child got connected BEFORE the parent (Runtime Issue)
|
|
466
489
|
const requestedProperties = customChild.injectProperties?.() ?? {};
|
|
467
490
|
Object.entries(requestedProperties).forEach(([key, value]) => {
|
|
468
491
|
// iterate over requested properties to find missed context requests
|
|
469
|
-
if (providedKeys.includes(key)) {
|
|
470
|
-
// owns the provided
|
|
492
|
+
if (providedKeys.includes(key) && customChild[key] !== providedProperties[key]) {
|
|
493
|
+
// owns the provided key -> replay request
|
|
471
494
|
customChild.requestContext(key, value);
|
|
472
495
|
}
|
|
473
496
|
});
|
package/src/StyledElement.js
CHANGED
|
@@ -39,17 +39,6 @@ class StyledElement extends BaseElement {
|
|
|
39
39
|
return cssStyleSheet;
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
if (supportsAdoptingStyleSheets() && this._options.shadowRender) {
|
|
44
|
-
// adopting does only make sense in shadow dom. Fall back to append for light elements
|
|
45
|
-
this.adoptStyleSheets();
|
|
46
|
-
|
|
47
|
-
if (this._options.adoptGlobalStyles !== false) {
|
|
48
|
-
globalStylesStore.subscribe(() => {
|
|
49
|
-
this.adoptStyleSheets();
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
42
|
}
|
|
54
43
|
|
|
55
44
|
/**
|
|
@@ -73,6 +62,26 @@ class StyledElement extends BaseElement {
|
|
|
73
62
|
super.update(options);
|
|
74
63
|
}
|
|
75
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Start the adoption of external stylesheets
|
|
67
|
+
* adopt global styles from document
|
|
68
|
+
* subscribe to globalStylesStore for updates
|
|
69
|
+
* @returns { void }
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
initStyleAdoption() {
|
|
73
|
+
if (this._options.shadowRender && supportsAdoptingStyleSheets()) {
|
|
74
|
+
// adopting does only make sense in shadow dom. Fall back to append for light elements
|
|
75
|
+
this.adoptStyleSheets();
|
|
76
|
+
|
|
77
|
+
if (this._options.adoptGlobalStyles !== false) {
|
|
78
|
+
globalStylesStore.subscribe(() => {
|
|
79
|
+
this.adoptStyleSheets();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
76
85
|
/**
|
|
77
86
|
* Adopt stylesheets
|
|
78
87
|
*/
|
|
@@ -81,7 +90,7 @@ class StyledElement extends BaseElement {
|
|
|
81
90
|
|
|
82
91
|
this.getRoot().adoptedStyleSheets = [
|
|
83
92
|
...(adoptGlobalStyleSheets ? globalStylesStore.getGlobalStyleSheets(this._options.adoptGlobalStyles) : []),
|
|
84
|
-
...this.constructor['elementStyleSheets'],
|
|
93
|
+
...(this.constructor?.['elementStyleSheets'] ?? []),
|
|
85
94
|
];
|
|
86
95
|
}
|
|
87
96
|
|
|
@@ -98,7 +107,7 @@ class StyledElement extends BaseElement {
|
|
|
98
107
|
|
|
99
108
|
const appendableStyles = [
|
|
100
109
|
...(adoptGlobalStyleSheets ? globalStylesStore.getGlobalStyleSheets(this._options.adoptGlobalStyles) : []),
|
|
101
|
-
...this.constructor['elementStyleSheets'],
|
|
110
|
+
...(this.constructor?.['elementStyleSheets'] ?? []),
|
|
102
111
|
];
|
|
103
112
|
|
|
104
113
|
appendableStyles.forEach((styleSheet, index) => {
|
package/src/TemplateElement.js
CHANGED
|
@@ -28,10 +28,8 @@ class TemplateElement extends StyledElement {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Overrides the native `connectedCallback` of the HTMLElement to set up and initialize our element.
|
|
31
|
-
* This will attach a shadow DOM if the element is supposed to render in shadow DOM.
|
|
32
31
|
*/
|
|
33
32
|
connectedCallback() {
|
|
34
|
-
if (this._options.shadowRender && !this.shadowRoot) this.attachShadow({ mode: 'open' });
|
|
35
33
|
super.connectedCallback();
|
|
36
34
|
}
|
|
37
35
|
|
|
@@ -54,8 +52,16 @@ class TemplateElement extends StyledElement {
|
|
|
54
52
|
|
|
55
53
|
/**
|
|
56
54
|
* Render the template to the root
|
|
55
|
+
* This will attach a shadow DOM if the element is supposed to render in shadow DOM.
|
|
57
56
|
*/
|
|
58
57
|
renderTemplate() {
|
|
58
|
+
const firstRender = this._options.shadowRender && !this.shadowRoot;
|
|
59
|
+
if (firstRender) {
|
|
60
|
+
this.attachShadow({ mode: 'open' });
|
|
61
|
+
// init external stylesheets
|
|
62
|
+
this.initStyleAdoption();
|
|
63
|
+
}
|
|
64
|
+
|
|
59
65
|
const template = this._template || this.template();
|
|
60
66
|
render(template, this.getRoot());
|
|
61
67
|
}
|
package/src/util/DOMHelper.js
CHANGED
|
@@ -60,7 +60,7 @@ export function getShadowParentOrBody(element) {
|
|
|
60
60
|
return document.body;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
// TODO: add function for
|
|
63
|
+
// TODO: add function for getClosestParentOfNo deType('custom-element')
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Get the closest/first parent node that is a custom element
|
|
@@ -76,7 +76,7 @@ export function getClosestParentCustomElementNode(domNode) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
* Get all
|
|
79
|
+
* Get all custom element children of a DOM node
|
|
80
80
|
* @param {Node} domNode
|
|
81
81
|
* @param {{prototype: HTMLElement, new()}} instanceOfFilter
|
|
82
82
|
* @return {Node[]}
|
|
@@ -95,6 +95,20 @@ export function getAllElementChildren(domNode, instanceOfFilter = HTMLElement) {
|
|
|
95
95
|
return customElements;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Get all custom element children of a DOM node, including children inside the node's shadow root.
|
|
100
|
+
*
|
|
101
|
+
* @param {Node} domNode - The root node to start the traversal from.
|
|
102
|
+
* @param {{prototype: HTMLElement, new(): HTMLElement}} [instanceOfFilter=HTMLElement] - Constructor used to filter matching custom elements.
|
|
103
|
+
* @return {Set<HTMLElement>} A set of matching custom element descendants.
|
|
104
|
+
*/
|
|
105
|
+
export function getAllComposedElementChildren(domNode, instanceOfFilter = HTMLElement) {
|
|
106
|
+
return new Set([
|
|
107
|
+
...getAllElementChildren(domNode, instanceOfFilter),
|
|
108
|
+
...(domNode.shadowRoot ? getAllElementChildren(domNode.shadowRoot, instanceOfFilter) : []),
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
|
|
98
112
|
/**
|
|
99
113
|
* Checks whether two nodes are of the same node type
|
|
100
114
|
* @param {Node} nodeA
|