@webtides/element-js 0.7.4 → 1.0.0-alpha.10
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/README.md +2 -2
- package/docs/README.md +174 -0
- package/docs/concepts/classes.md +167 -0
- package/docs/concepts/hierarchy.md +61 -0
- package/docs/concepts/lifecycle.md +45 -0
- package/docs/concepts/reactivity.md +14 -0
- package/docs/features/computed-properties.md +28 -0
- package/docs/features/dependency-injection-context-protocol.md +132 -0
- package/docs/features/events.md +155 -0
- package/docs/features/global-styles.md +131 -0
- package/docs/features/methods.md +34 -0
- package/docs/features/properties.md +56 -0
- package/docs/features/refs.md +47 -0
- package/docs/features/renderless.md +70 -0
- package/docs/features/store.md +143 -0
- package/docs/features/styles.md +169 -0
- package/docs/features/templates.md +308 -0
- package/docs/features/watch.md +54 -0
- package/docs/getting-started/upgrade-to-v1.md +243 -0
- package/docs/guide/guide.md +112 -0
- package/index.js +9 -2
- package/package.json +19 -27
- package/src/BaseElement.js +176 -86
- package/src/StyledElement.js +66 -55
- package/src/TemplateElement.js +34 -8
- package/src/dom-parts/AttributePart.js +167 -0
- package/src/dom-parts/ChildNodePart.js +329 -0
- package/src/dom-parts/NodePart.js +38 -0
- package/src/dom-parts/Part.js +12 -0
- package/src/dom-parts/TemplatePart.js +134 -0
- package/src/dom-parts/TemplateResult.js +342 -0
- package/src/dom-parts/directives.js +111 -0
- package/src/dom-parts/html.js +12 -0
- package/src/dom-parts/render.js +10 -0
- package/src/util/AttributeParser.js +62 -29
- package/src/util/DOMHelper.js +253 -5
- package/src/util/Directive.js +42 -0
- package/src/util/GlobalStylesStore.js +66 -0
- package/src/util/SerializeStateHelper.js +103 -0
- package/src/util/Store.js +92 -9
- package/src/util/defineElement.js +2 -2
- package/src/util/toString.js +2 -2
- package/CHANGELOG.md +0 -154
- package/dist/index.js +0 -1673
- package/dist/index.js.map +0 -7
- package/src/renderer/vanilla/TemplateElement.js +0 -50
- package/src/renderer/vanilla/index.js +0 -18
- package/src/renderer/vanilla/util/directives.js +0 -78
- package/src/renderer/vanilla/util/html.js +0 -65
- package/src/renderer/vanilla/util/render.js +0 -149
- package/src/util/i18n.js +0 -20
package/src/BaseElement.js
CHANGED
|
@@ -1,11 +1,54 @@
|
|
|
1
1
|
import { parseAttribute, isNaN, dashToCamel, camelToDash, isObjectLike } from './util/AttributeParser.js';
|
|
2
2
|
import { getAllElementChildren, getClosestParentCustomElementNode, isOfSameNodeType } from './util/DOMHelper.js';
|
|
3
3
|
import { Store } from './util/Store.js';
|
|
4
|
-
|
|
4
|
+
import { serializeState, deserializeState, hasSerializedState } from './util/SerializeStateHelper.js';
|
|
5
5
|
export { defineElement } from './util/defineElement.js';
|
|
6
6
|
export { toString } from './util/toString.js';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Options object for the BaseElement
|
|
10
|
+
* @typedef {Object} BaseElementOptions
|
|
11
|
+
* @property {boolean} [autoUpdate] - When set to true the element will call the requestUpdate() method on the instance every time a property or attribute was changed. This will re-evaluate everything on the element and trigger a re-render (if a template is provided) and trigger the watchers for the affected properties/attributes. Default is `true`
|
|
12
|
+
* @property {boolean} [deferUpdate] - When set to true the element will not call the requestUpdate() method upon connecting and therefore will not render (if template was provided) initially. Default is `true`
|
|
13
|
+
* @property {boolean} [deferConnected] - When set to true the element will skip the initialization in connectedCallback() before connecting and therefore will not render (if template was provided) or hydrate initially. Default is `false`
|
|
14
|
+
* @property {MutationObserverOptions} [mutationObserverOptions]
|
|
15
|
+
* @property {PropertyOptions} [propertyOptions]
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Options object for the BaseElement mutationObserverOptions option
|
|
20
|
+
* @typedef {Object} MutationObserverOptions
|
|
21
|
+
* @property {boolean} [childList] - When childList is set to true the element will also call the requestUpdate method for childList modifications (eg. Adding or removing child elements). Default is `true`
|
|
22
|
+
* @property {boolean} [subtree] - Please note that observing subtree mutations might have performance implications and use it only if necessary. Default is `false`
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Options object for the BaseElement propertyOptions option
|
|
27
|
+
* @typedef {Object} PropertyOptions
|
|
28
|
+
* @property {undefined | boolean | function} [reflect] - When set to true the element will reflect property changes back to attributes if the attribute was not present when connecting the element. By default, all attributes that are present when connecting the element will be reflected anyway. Default is `undefined`
|
|
29
|
+
* @property {undefined | boolean | function} [parse] - When set to false the element will not automatically try to parse the attributes string value to a complex type (number, array, object). Default is `undefined`
|
|
30
|
+
* @property {undefined | boolean | function} [notify] - When set to true the element will dispatch CustomEvents for every property/attribute change. The event name will be the property name all lowercase and camel to dashes with a postfix of -changed. Default is `undefined`
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Options object for updating properties
|
|
35
|
+
* @typedef {Object} PropertyUpdateOptions
|
|
36
|
+
* @property {boolean} [notify] - Whether the update should trigger the lifecycle methods. The default is `true`
|
|
37
|
+
* @property {string} property - The property name
|
|
38
|
+
* @property {any} newValue - The new value for the property
|
|
39
|
+
* @property {string} newValueString - The new value as `string` for the property (for comparison reasons)
|
|
40
|
+
* @property {any} oldValue - The old value for the property
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @implements {Serializable}
|
|
45
|
+
*/
|
|
8
46
|
class BaseElement extends HTMLElement {
|
|
47
|
+
_serializationKey;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {BaseElementOptions} options
|
|
51
|
+
*/
|
|
9
52
|
constructor(options = {}) {
|
|
10
53
|
super();
|
|
11
54
|
this.$refs = {};
|
|
@@ -27,37 +70,40 @@ class BaseElement extends HTMLElement {
|
|
|
27
70
|
propertyOptions: {},
|
|
28
71
|
...options,
|
|
29
72
|
};
|
|
73
|
+
}
|
|
30
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Overrides the native `connectedCallback` of the HTMLElement to set up and initialize our element.
|
|
77
|
+
* This will define the attributes and properties as reactive getters and setter,
|
|
78
|
+
* register observers, refs and events.
|
|
79
|
+
*/
|
|
80
|
+
connectedCallback() {
|
|
31
81
|
if (this.hasAttribute('defer-update')) {
|
|
32
82
|
this._options.deferUpdate = true;
|
|
33
83
|
}
|
|
34
|
-
if (this.hasAttribute('defer-connected')) {
|
|
35
|
-
this._options.deferConnected = true;
|
|
36
|
-
}
|
|
37
84
|
|
|
38
|
-
if (
|
|
39
|
-
this._options.mutationObserverOptions.childList = options.childListUpdate;
|
|
40
|
-
console.warn(
|
|
41
|
-
`[${this.localName}] Using the "childListUpdate" option is deprecated and will be removed before 1.0! Please use the "mutationObserverOptions" dictionary instead. See the docs for more info`,
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
connectedCallback() {
|
|
47
|
-
if (this._options.deferConnected) {
|
|
85
|
+
if (this.hasAttribute('defer-connected') || this._options.deferConnected) {
|
|
48
86
|
this._options.deferConnected = false;
|
|
87
|
+
this.removeAttribute('defer-connected');
|
|
49
88
|
return;
|
|
50
89
|
}
|
|
51
90
|
|
|
91
|
+
if (this.hasAttribute('ejs:key')) {
|
|
92
|
+
this._serializationKey = this.getAttribute('ejs:key');
|
|
93
|
+
deserializeState(this);
|
|
94
|
+
} else {
|
|
95
|
+
this._serializationKey = globalThis.crypto.randomUUID();
|
|
96
|
+
if (globalThis.elementJsConfig?.serializeState) {
|
|
97
|
+
this.setAttribute('ejs:key', this._serializationKey);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
52
101
|
// define all attributes to "this" as properties
|
|
53
102
|
this.defineAttributesAsProperties();
|
|
54
103
|
|
|
55
104
|
// define all properties to "this"
|
|
56
105
|
this.defineProperties();
|
|
57
106
|
|
|
58
|
-
// define all computed properties to "this"
|
|
59
|
-
this.defineComputedProperties();
|
|
60
|
-
|
|
61
107
|
// define context
|
|
62
108
|
this.definePropertyInjection();
|
|
63
109
|
|
|
@@ -65,17 +111,21 @@ class BaseElement extends HTMLElement {
|
|
|
65
111
|
this.defineObserver();
|
|
66
112
|
|
|
67
113
|
if (this._options.deferUpdate) {
|
|
68
|
-
// don't
|
|
114
|
+
// don't update/render, but register refs and events
|
|
69
115
|
this.registerEventsAndRefs();
|
|
70
116
|
|
|
71
117
|
this.triggerHook('connected');
|
|
72
118
|
} else {
|
|
73
119
|
this.requestUpdate({ notify: false }).then(() => {
|
|
74
120
|
this.triggerHook('connected');
|
|
121
|
+
this.triggerHook('afterUpdate');
|
|
75
122
|
});
|
|
76
123
|
}
|
|
77
124
|
}
|
|
78
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Define a MutationObserver on the element to get notified on attribute changes and childList updates according to the options specified via the constructor options.
|
|
128
|
+
*/
|
|
79
129
|
defineObserver() {
|
|
80
130
|
// see: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#Example_usage
|
|
81
131
|
this._mutationObserver = new MutationObserver((mutations) => {
|
|
@@ -108,6 +158,9 @@ class BaseElement extends HTMLElement {
|
|
|
108
158
|
});
|
|
109
159
|
}
|
|
110
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Native `disconnectedCallback` of the HTMLElement
|
|
163
|
+
*/
|
|
111
164
|
disconnectedCallback() {
|
|
112
165
|
// remove events for elements in the component
|
|
113
166
|
this.removeEvents();
|
|
@@ -129,6 +182,11 @@ class BaseElement extends HTMLElement {
|
|
|
129
182
|
this.triggerHook('disconnected');
|
|
130
183
|
}
|
|
131
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Request and batch an asynchronous update cycle
|
|
187
|
+
* @param {PropertyUpdateOptions} options
|
|
188
|
+
* @returns {Promise<unknown>}
|
|
189
|
+
*/
|
|
132
190
|
requestUpdate(options = { notify: true }) {
|
|
133
191
|
if (options.notify === true) {
|
|
134
192
|
this.triggerHook('beforeUpdate');
|
|
@@ -158,6 +216,7 @@ class BaseElement extends HTMLElement {
|
|
|
158
216
|
* This should be called by the template component AFTER adding the template to
|
|
159
217
|
* the DOM.
|
|
160
218
|
* Here we will register the events and the refs for the element.
|
|
219
|
+
* @param {PropertyUpdateOptions} options
|
|
161
220
|
*/
|
|
162
221
|
update(options = { notify: true }) {
|
|
163
222
|
this.registerEventsAndRefs();
|
|
@@ -224,12 +283,11 @@ class BaseElement extends HTMLElement {
|
|
|
224
283
|
* Define a property on the element
|
|
225
284
|
* Will trigger watch() function when a property was changed
|
|
226
285
|
* Will trigger update() when a property was changed
|
|
286
|
+
* @param {string} property
|
|
287
|
+
* @param {any} value
|
|
288
|
+
* @param {boolean} reflectAttribute
|
|
227
289
|
*/
|
|
228
290
|
defineProperty(property, value, reflectAttribute = false) {
|
|
229
|
-
if (value instanceof Store) {
|
|
230
|
-
value.subscribe(this);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
291
|
if (this._state.hasOwnProperty(property)) {
|
|
234
292
|
// property has already been defined as an attribute nothing to do here
|
|
235
293
|
return;
|
|
@@ -245,8 +303,16 @@ class BaseElement extends HTMLElement {
|
|
|
245
303
|
this.removeAttribute(camelToDash(property));
|
|
246
304
|
}
|
|
247
305
|
|
|
306
|
+
if (hasSerializedState(this._serializationKey) && Object.keys(this.serializeState())?.includes(property)) {
|
|
307
|
+
value = this[property];
|
|
308
|
+
}
|
|
309
|
+
|
|
248
310
|
this._state[property] = value;
|
|
249
311
|
|
|
312
|
+
if (this._state[property] instanceof Store) {
|
|
313
|
+
this._state[property].subscribe(this);
|
|
314
|
+
}
|
|
315
|
+
|
|
250
316
|
Object.defineProperty(this, property, {
|
|
251
317
|
get: () => {
|
|
252
318
|
return this._state[property];
|
|
@@ -257,6 +323,8 @@ class BaseElement extends HTMLElement {
|
|
|
257
323
|
if (JSON.stringify(oldValue) !== newValueString) {
|
|
258
324
|
this._state[property] = newValue;
|
|
259
325
|
|
|
326
|
+
serializeState(this);
|
|
327
|
+
|
|
260
328
|
if (newValue instanceof Store) {
|
|
261
329
|
newValue.subscribe(this);
|
|
262
330
|
}
|
|
@@ -287,12 +355,10 @@ class BaseElement extends HTMLElement {
|
|
|
287
355
|
}
|
|
288
356
|
|
|
289
357
|
/**
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
* @param
|
|
293
|
-
* @param
|
|
294
|
-
* @param newValue
|
|
295
|
-
* @param oldValue
|
|
358
|
+
* Call the watch callbacks on property changes
|
|
359
|
+
* @param {string} property property that changes
|
|
360
|
+
* @param {any} newValue
|
|
361
|
+
* @param {any} oldValue
|
|
296
362
|
*/
|
|
297
363
|
callPropertyWatcher(property, newValue, oldValue) {
|
|
298
364
|
// notify watched properties (after update())
|
|
@@ -303,11 +369,9 @@ class BaseElement extends HTMLElement {
|
|
|
303
369
|
}
|
|
304
370
|
|
|
305
371
|
/**
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
* @param
|
|
309
|
-
* @param property property that changes
|
|
310
|
-
* @param newValue
|
|
372
|
+
* Notify property observer via change event
|
|
373
|
+
* @param {string} property property that changes
|
|
374
|
+
* @param {any} newValue
|
|
311
375
|
*/
|
|
312
376
|
notifyPropertyChange(property, newValue) {
|
|
313
377
|
// dispatch change event
|
|
@@ -315,10 +379,14 @@ class BaseElement extends HTMLElement {
|
|
|
315
379
|
property in this._options['propertyOptions'] &&
|
|
316
380
|
this._options['propertyOptions'][property]['notify'] === true
|
|
317
381
|
) {
|
|
318
|
-
this.dispatch(`${camelToDash(property)}-changed`, newValue
|
|
382
|
+
this.dispatch(`${camelToDash(property)}-changed`, newValue);
|
|
319
383
|
}
|
|
320
384
|
}
|
|
321
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Reflects a property as attribute on the element
|
|
388
|
+
* @param {PropertyUpdateOptions} options
|
|
389
|
+
*/
|
|
322
390
|
reflectProperty(options) {
|
|
323
391
|
const { property, newValue } = options;
|
|
324
392
|
const newValueString = options.newValueString || JSON.stringify(newValue);
|
|
@@ -380,20 +448,18 @@ class BaseElement extends HTMLElement {
|
|
|
380
448
|
|
|
381
449
|
/**
|
|
382
450
|
* Helper Function to initialize the context requests
|
|
383
|
-
*
|
|
384
|
-
* @param
|
|
385
|
-
* @param valueOrCallback
|
|
451
|
+
* @param {string} propertyName
|
|
452
|
+
* @param {any | function} valueOrCallback
|
|
386
453
|
*/
|
|
387
454
|
requestContext(propertyName, valueOrCallback) {
|
|
388
|
-
this.dispatch('request-context', { [propertyName]: valueOrCallback }
|
|
455
|
+
this.dispatch('request-context', { [propertyName]: valueOrCallback });
|
|
389
456
|
}
|
|
390
457
|
|
|
391
458
|
/**
|
|
392
459
|
* Internal Context Request Handler
|
|
393
460
|
* This will check if the current/receiving instance actually provides a value under the requested name.
|
|
394
461
|
* If there is something to provide it will either assign the value to the requesting element or call the callback.
|
|
395
|
-
*
|
|
396
|
-
* @param event
|
|
462
|
+
* @param {CustomEvent} event
|
|
397
463
|
*/
|
|
398
464
|
onRequestContext(event) {
|
|
399
465
|
const provideProperties = this.provideProperties();
|
|
@@ -416,33 +482,31 @@ class BaseElement extends HTMLElement {
|
|
|
416
482
|
});
|
|
417
483
|
}
|
|
418
484
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// Connected lifecycle hook
|
|
485
|
+
/**
|
|
486
|
+
* Connected lifecycle hook
|
|
487
|
+
*/
|
|
425
488
|
connected() {}
|
|
426
489
|
|
|
427
|
-
|
|
490
|
+
/**
|
|
491
|
+
* BeforeUpdate lifecycle hook
|
|
492
|
+
*/
|
|
428
493
|
beforeUpdate() {}
|
|
429
494
|
|
|
430
|
-
|
|
495
|
+
/**
|
|
496
|
+
* AfterUpdate lifecycle hook
|
|
497
|
+
*/
|
|
431
498
|
afterUpdate() {}
|
|
432
499
|
|
|
433
|
-
|
|
500
|
+
/**
|
|
501
|
+
* Disconnected lifecycle hook
|
|
502
|
+
*/
|
|
434
503
|
disconnected() {}
|
|
435
504
|
|
|
436
|
-
|
|
505
|
+
/**
|
|
506
|
+
* Triggers a lifecycle hook based on the name
|
|
507
|
+
* @param {string} name
|
|
508
|
+
*/
|
|
437
509
|
triggerHook(name) {
|
|
438
|
-
if (this.hooks && name in this.hooks()) {
|
|
439
|
-
console.warn(
|
|
440
|
-
`[${this.localName}] Using the hooks() map for lifecycle hooks is deprecated! Please overwrite the existing lifecycle hook functions. See the docs for more info`,
|
|
441
|
-
);
|
|
442
|
-
this.hooks()[name]();
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
510
|
if (name in this) {
|
|
447
511
|
this[name]();
|
|
448
512
|
}
|
|
@@ -459,25 +523,6 @@ class BaseElement extends HTMLElement {
|
|
|
459
523
|
return {};
|
|
460
524
|
}
|
|
461
525
|
|
|
462
|
-
// Deprecated
|
|
463
|
-
computed() {
|
|
464
|
-
return {};
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Deprecated
|
|
468
|
-
defineComputedProperties() {
|
|
469
|
-
Object.keys(this.computed()).forEach((prop) => {
|
|
470
|
-
if (!this.hasOwnProperty(prop)) {
|
|
471
|
-
console.warn(
|
|
472
|
-
`[${this.localName}] Using the computed() map for computed properties is deprecated! Please use regular JS getters and return the computed value. See the docs for more info`,
|
|
473
|
-
);
|
|
474
|
-
Object.defineProperty(this, prop, {
|
|
475
|
-
get: () => this.computed()[prop](),
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
|
|
481
526
|
/**
|
|
482
527
|
* Return a map of events that should be registered on the element.
|
|
483
528
|
* eg. { '.link': {
|
|
@@ -570,21 +615,66 @@ class BaseElement extends HTMLElement {
|
|
|
570
615
|
this.$refs = refsMap;
|
|
571
616
|
}
|
|
572
617
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
618
|
+
/**
|
|
619
|
+
* Helper function for dispatching custom events
|
|
620
|
+
* @param {string} name
|
|
621
|
+
* @param {Object} [data]
|
|
622
|
+
* @param {CustomEventInit} [options]
|
|
623
|
+
* @param {boolean} [cancelable] - DEPRECATED
|
|
624
|
+
* @param {boolean} [composed] - DEPRECATED
|
|
625
|
+
*/
|
|
626
|
+
dispatch(name, data, options, cancelable = false, composed = false) {
|
|
627
|
+
let eventInit = {};
|
|
628
|
+
|
|
629
|
+
if (options !== undefined && typeof options === 'boolean') {
|
|
630
|
+
eventInit['bubbles'] = options;
|
|
631
|
+
eventInit['cancelable'] = cancelable;
|
|
632
|
+
eventInit['composed'] = composed;
|
|
633
|
+
eventInit['detail'] = data;
|
|
634
|
+
console.warn(
|
|
635
|
+
`[${this.localName}] Using the old parameter options for the event is deprecated and will be removed in v2.0! Please use the new options object that is in line with the "CustomEventInit" options`,
|
|
636
|
+
);
|
|
637
|
+
} else {
|
|
638
|
+
eventInit = {
|
|
639
|
+
bubbles: true,
|
|
640
|
+
cancelable: true,
|
|
641
|
+
composed: true,
|
|
642
|
+
detail: data,
|
|
643
|
+
...options,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const event = new CustomEvent(name, eventInit);
|
|
581
648
|
this.dispatchEvent(event);
|
|
582
649
|
}
|
|
583
650
|
|
|
584
|
-
|
|
651
|
+
/**
|
|
652
|
+
* Get the root element
|
|
653
|
+
* @returns {HTMLElement}
|
|
654
|
+
*/
|
|
585
655
|
getRoot() {
|
|
586
656
|
return this;
|
|
587
657
|
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* This method will be used to serialize this elements state.
|
|
661
|
+
* By default, it will generate an object with all properties from this.properties().
|
|
662
|
+
* You can override this and return a different object with more/less/different properties.
|
|
663
|
+
* @return {{[string: any]: *}}
|
|
664
|
+
*/
|
|
665
|
+
serializeState() {
|
|
666
|
+
const keys = Object.keys(this.properties());
|
|
667
|
+
return Object.fromEntries(keys.map((key) => [key, this[key]]));
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* This method will be used to restore properties on the element from a previously serialized state.
|
|
672
|
+
* You can override this and handle the deserialization differently if the default is not enough.
|
|
673
|
+
* @param {{[string: any]: *}} serializedState
|
|
674
|
+
*/
|
|
675
|
+
restoreState(serializedState) {
|
|
676
|
+
Object.assign(this, serializedState);
|
|
677
|
+
}
|
|
588
678
|
}
|
|
589
679
|
|
|
590
680
|
export { BaseElement, getClosestParentCustomElementNode, isOfSameNodeType };
|
package/src/StyledElement.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import { BaseElement } from './BaseElement.js';
|
|
2
2
|
import { supportsAdoptingStyleSheets, getShadowParentOrBody } from './util/DOMHelper.js';
|
|
3
|
-
|
|
3
|
+
import { globalStylesStore } from './util/GlobalStylesStore.js';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (StyledElement.globalStyles && StyledElement['globalStyleSheet']) {
|
|
14
|
-
//updates already adopted global styles
|
|
15
|
-
StyledElement['globalStyleSheet'].replaceSync(StyledElement.globalStyles.textContent);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
5
|
+
/**
|
|
6
|
+
* Options object for the StyledElement
|
|
7
|
+
* @typedef {Object} StyledElementOptions
|
|
8
|
+
* @extends BaseElementOptions
|
|
9
|
+
* @property {boolean} [shadowRender] - When set to true the element will render the template (if provided) in the Shadow DOM and therefore encapsulate the element and styles from the rest of the document. Default is `false`
|
|
10
|
+
* @property {[]} [styles] - Via the styles option you can add multiple styles/stylesheets to your element. Default is `[]`
|
|
11
|
+
* @property {boolean | string | string[]} [adoptGlobalStyles] - When set to true element-js will look for all style elements in the global document with and apply them before any custom/element styles inside the shadow DOM. Default is `true`.
|
|
12
|
+
*/
|
|
18
13
|
|
|
14
|
+
class StyledElement extends BaseElement {
|
|
15
|
+
/**
|
|
16
|
+
* @param {StyledElementOptions} options
|
|
17
|
+
*/
|
|
19
18
|
constructor(options) {
|
|
20
19
|
super({
|
|
21
20
|
deferUpdate: false,
|
|
@@ -27,84 +26,96 @@ class StyledElement extends BaseElement {
|
|
|
27
26
|
this._styles = [...this._options.styles, ...this.styles()];
|
|
28
27
|
}
|
|
29
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Overrides the `connectedCallback` to adopt optional styles when the element is connected
|
|
31
|
+
*/
|
|
30
32
|
connectedCallback() {
|
|
31
33
|
super.connectedCallback();
|
|
32
34
|
|
|
35
|
+
if (!this.constructor['elementStyleSheets']) {
|
|
36
|
+
this.constructor['elementStyleSheets'] = this._styles.map((style) => {
|
|
37
|
+
const cssStyleSheet = new CSSStyleSheet();
|
|
38
|
+
cssStyleSheet.replaceSync(style);
|
|
39
|
+
return cssStyleSheet;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
if (supportsAdoptingStyleSheets() && this._options.shadowRender) {
|
|
34
44
|
// adopting does only make sense in shadow dom. Fall back to append for light elements
|
|
35
45
|
this.adoptStyleSheets();
|
|
36
|
-
} else if (this._options.shadowRender && window.ShadyCSS !== undefined) {
|
|
37
|
-
// if shadowRoot is polyfilled we use ShadyCSS to copy scoped styles to <head>
|
|
38
|
-
window.ShadyCSS.ScopingShim.prepareAdoptedCssText(this._styles, this.localName);
|
|
39
|
-
}
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
if (this._options.adoptGlobalStyles !== false) {
|
|
48
|
+
globalStylesStore.subscribe(() => {
|
|
49
|
+
this.adoptStyleSheets();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
/**
|
|
56
|
+
* The styles method is another way to return a list of styles to be adopted when the element is connected.
|
|
57
|
+
* You can either provide a list in the constructor options or return it here.
|
|
58
|
+
* @returns {string[]}
|
|
59
|
+
*/
|
|
47
60
|
styles() {
|
|
48
61
|
return [];
|
|
49
62
|
}
|
|
50
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Overrides the `update` method to adopt optional styles
|
|
66
|
+
* @param {PropertyUpdateOptions} options
|
|
67
|
+
*/
|
|
51
68
|
update(options) {
|
|
69
|
+
// We cannot do this in connectedCallback() since the whole template will be overridden in update/render afterward
|
|
52
70
|
if (!supportsAdoptingStyleSheets() || this._options.shadowRender === false) {
|
|
53
|
-
|
|
54
|
-
const appendableStyles = [...this._styles];
|
|
55
|
-
if (this._options.shadowRender && this._options.adoptGlobalStyles && !window.ShadyCSS) {
|
|
56
|
-
appendableStyles.unshift(StyledElement.globalStyles?.textContent ?? '');
|
|
57
|
-
}
|
|
58
|
-
this.appendStyleSheets(appendableStyles);
|
|
71
|
+
this.appendStyleSheets();
|
|
59
72
|
}
|
|
60
73
|
super.update(options);
|
|
61
74
|
}
|
|
62
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Adopt stylesheets
|
|
78
|
+
*/
|
|
63
79
|
adoptStyleSheets() {
|
|
64
|
-
|
|
65
|
-
this.constructor['cssStyleSheets'] = this._styles.map((style) => {
|
|
66
|
-
const sheet = new CSSStyleSheet();
|
|
67
|
-
if (sheet && sheet.cssRules.length === 0) {
|
|
68
|
-
sheet.replaceSync(style);
|
|
69
|
-
}
|
|
70
|
-
return sheet;
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (StyledElement.globalStyles && !StyledElement['globalStyleSheet']) {
|
|
75
|
-
StyledElement['globalStyleSheet'] = new CSSStyleSheet();
|
|
76
|
-
StyledElement['globalStyleSheet'].replaceSync(StyledElement.globalStyles.textContent);
|
|
77
|
-
}
|
|
80
|
+
const adoptGlobalStyleSheets = this._options.shadowRender && this._options.adoptGlobalStyles !== false;
|
|
78
81
|
|
|
79
|
-
// adopt styles
|
|
80
|
-
// uses proposed solution for constructable stylesheets
|
|
81
|
-
// see: https://wicg.github.io/construct-stylesheets/#proposed-solution
|
|
82
82
|
this.getRoot().adoptedStyleSheets = [
|
|
83
|
-
...(
|
|
84
|
-
|
|
85
|
-
: []),
|
|
86
|
-
...this.constructor['cssStyleSheets'],
|
|
83
|
+
...(adoptGlobalStyleSheets ? globalStylesStore.getGlobalStyleSheets(this._options.adoptGlobalStyles) : []),
|
|
84
|
+
...this.constructor['elementStyleSheets'],
|
|
87
85
|
];
|
|
88
86
|
}
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Custom polyfill for constructable stylesheets by appending styles to the end of an element
|
|
90
|
+
*/
|
|
91
|
+
appendStyleSheets() {
|
|
92
92
|
const parentDocument = getShadowParentOrBody(this.getRoot());
|
|
93
|
-
|
|
93
|
+
|
|
94
|
+
const adoptGlobalStyleSheets =
|
|
95
|
+
this._options.shadowRender &&
|
|
96
|
+
this._options.adoptGlobalStyles !== false &&
|
|
97
|
+
parentDocument !== globalThis.document.body;
|
|
98
|
+
|
|
99
|
+
const appendableStyles = [
|
|
100
|
+
...(adoptGlobalStyleSheets ? globalStylesStore.getGlobalStyleSheets(this._options.adoptGlobalStyles) : []),
|
|
101
|
+
...this.constructor['elementStyleSheets'],
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
appendableStyles.forEach((styleSheet, index) => {
|
|
94
105
|
const identifier = this.tagName + index;
|
|
95
106
|
|
|
96
107
|
// only append stylesheet if not already appended to shadowRoot or document
|
|
97
108
|
if (!parentDocument.querySelector(`#${identifier}`)) {
|
|
98
|
-
const styleElement = document
|
|
109
|
+
const styleElement = globalThis.document?.createElement('style');
|
|
99
110
|
styleElement.id = identifier;
|
|
100
111
|
styleElement.style.display = 'none';
|
|
101
|
-
styleElement.textContent =
|
|
112
|
+
styleElement.textContent = Array.from(styleSheet.cssRules)
|
|
113
|
+
.map((rule) => rule.cssText)
|
|
114
|
+
.join('');
|
|
102
115
|
parentDocument.appendChild(styleElement);
|
|
103
116
|
}
|
|
104
117
|
});
|
|
105
118
|
}
|
|
106
119
|
}
|
|
107
120
|
|
|
108
|
-
StyledElement.updateGlobalStyles();
|
|
109
|
-
|
|
110
121
|
export { StyledElement };
|
package/src/TemplateElement.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { StyledElement } from './StyledElement.js';
|
|
2
|
-
import { html } from '
|
|
3
|
-
import { render } from '
|
|
4
|
-
|
|
2
|
+
import { html } from './dom-parts/html.js';
|
|
3
|
+
import { render } from './dom-parts/render.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options object for the TemplateElement
|
|
7
|
+
* @typedef {Object} TemplateElementOptions
|
|
8
|
+
* @extends StyledElementOptions
|
|
9
|
+
*/
|
|
5
10
|
|
|
6
11
|
class TemplateElement extends StyledElement {
|
|
12
|
+
/**
|
|
13
|
+
* @param {TemplateElementOptions} options
|
|
14
|
+
*/
|
|
7
15
|
constructor(options) {
|
|
8
16
|
super({
|
|
9
17
|
deferUpdate: false,
|
|
@@ -16,33 +24,51 @@ class TemplateElement extends StyledElement {
|
|
|
16
24
|
...options,
|
|
17
25
|
});
|
|
18
26
|
this._template = this._options.template;
|
|
27
|
+
}
|
|
19
28
|
|
|
29
|
+
/**
|
|
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
|
+
*/
|
|
33
|
+
connectedCallback() {
|
|
20
34
|
if (this._options.shadowRender) this.attachShadow({ mode: 'open' });
|
|
35
|
+
super.connectedCallback();
|
|
21
36
|
}
|
|
22
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The template method should be overridden in extending elements and return the template to be rendered to the root
|
|
40
|
+
* @returns {TemplateResult}
|
|
41
|
+
*/
|
|
23
42
|
template() {
|
|
24
43
|
return html``;
|
|
25
44
|
}
|
|
26
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Override update method to render the template to the root
|
|
48
|
+
* @param {PropertyUpdateOptions} options
|
|
49
|
+
*/
|
|
27
50
|
update(options) {
|
|
28
51
|
this.renderTemplate();
|
|
29
52
|
super.update(options);
|
|
30
53
|
}
|
|
31
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Render the template to the root
|
|
57
|
+
*/
|
|
32
58
|
renderTemplate() {
|
|
33
59
|
const template = this._template || this.template();
|
|
34
60
|
if (typeof template === 'string') {
|
|
35
61
|
// just a plain string literal. no lit-html required
|
|
36
62
|
this.getRoot().innerHTML = `${template}`;
|
|
37
63
|
} else {
|
|
38
|
-
|
|
39
|
-
render(html` ${template} `, this.getRoot(), {
|
|
40
|
-
scopeName: this.localName,
|
|
41
|
-
eventContext: this,
|
|
42
|
-
});
|
|
64
|
+
render(template, this.getRoot());
|
|
43
65
|
}
|
|
44
66
|
}
|
|
45
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Get the root element - either the element or the shadow root
|
|
70
|
+
* @returns {ShadowRoot | HTMLElement}
|
|
71
|
+
*/
|
|
46
72
|
getRoot() {
|
|
47
73
|
return this.shadowRoot !== null ? this.shadowRoot : this;
|
|
48
74
|
}
|