@webtides/element-js 0.6.2 → 0.7.1
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 +24 -0
- package/dist/index.js +86 -17
- package/dist/index.js.map +2 -2
- package/index.js +7 -7
- package/package.json +2 -2
- package/src/BaseElement.js +116 -22
- package/src/StyledElement.js +3 -3
- package/src/TemplateElement.js +2 -2
- package/src/renderer/vanilla/TemplateElement.js +4 -6
- package/src/renderer/vanilla/index.js +6 -6
- package/src/renderer/vanilla/util/html.js +1 -1
- package/src/renderer/vanilla/util/render.js +7 -16
- package/src/util/AttributeParser.js +12 -0
- package/src/util/Store.js +32 -9
package/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { BaseElement } from './src/BaseElement';
|
|
2
|
-
import { StyledElement } from './src/StyledElement';
|
|
3
|
-
import { TemplateElement, html } from './src/TemplateElement';
|
|
4
|
-
import { toString } from './src/util/toString';
|
|
5
|
-
import { defineElement } from './src/util/defineElement';
|
|
6
|
-
import { i18n } from './src/util/i18n';
|
|
7
|
-
import { Store } from './src/util/Store';
|
|
1
|
+
import { BaseElement } from './src/BaseElement.js';
|
|
2
|
+
import { StyledElement } from './src/StyledElement.js';
|
|
3
|
+
import { TemplateElement, html } from './src/TemplateElement.js';
|
|
4
|
+
import { toString } from './src/util/toString.js';
|
|
5
|
+
import { defineElement } from './src/util/defineElement.js';
|
|
6
|
+
import { i18n } from './src/util/i18n.js';
|
|
7
|
+
import { Store } from './src/util/Store.js';
|
|
8
8
|
|
|
9
9
|
export { BaseElement, StyledElement, TemplateElement, Store, html, toString, defineElement, i18n };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webtides/element-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"module": "index.js",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@web/dev-server": "^0.1.30",
|
|
46
46
|
"@web/test-runner": "^0.13.26",
|
|
47
47
|
"babel-eslint": "^10.1.0",
|
|
48
|
-
"babel-plugin-module-resolver": "^
|
|
48
|
+
"babel-plugin-module-resolver": "^5.0.0",
|
|
49
49
|
"deepmerge": "^4.2.2",
|
|
50
50
|
"esbuild": "^0.8.57",
|
|
51
51
|
"eslint": "^7.0.0",
|
package/src/BaseElement.js
CHANGED
|
@@ -2,8 +2,8 @@ import { parseAttribute, isNaN, dashToCamel, camelToDash, isObjectLike } from '.
|
|
|
2
2
|
import { getClosestParentCustomElementNode, isOfSameNodeType } from './util/DOMHelper.js';
|
|
3
3
|
import { Store } from './util/Store.js';
|
|
4
4
|
|
|
5
|
-
export { defineElement } from './util/defineElement';
|
|
6
|
-
export { toString } from './util/toString';
|
|
5
|
+
export { defineElement } from './util/defineElement.js';
|
|
6
|
+
export { toString } from './util/toString.js';
|
|
7
7
|
|
|
8
8
|
class BaseElement extends HTMLElement {
|
|
9
9
|
constructor(options = {}) {
|
|
@@ -45,6 +45,9 @@ class BaseElement extends HTMLElement {
|
|
|
45
45
|
// define all computed properties to "this"
|
|
46
46
|
this.defineComputedProperties();
|
|
47
47
|
|
|
48
|
+
// define context
|
|
49
|
+
this.definePropertyInjection();
|
|
50
|
+
|
|
48
51
|
// define everything that should be observed
|
|
49
52
|
this.defineObserver();
|
|
50
53
|
|
|
@@ -96,6 +99,10 @@ class BaseElement extends HTMLElement {
|
|
|
96
99
|
// remove events for elements in the component
|
|
97
100
|
this.removeEvents();
|
|
98
101
|
|
|
102
|
+
if (Object.keys(this.provideProperties()).length > 0) {
|
|
103
|
+
this.removeEventListener('request-context', this.onRequestContext);
|
|
104
|
+
}
|
|
105
|
+
|
|
99
106
|
// remove observers
|
|
100
107
|
if (this._mutationObserver) this._mutationObserver.disconnect();
|
|
101
108
|
|
|
@@ -189,7 +196,7 @@ class BaseElement extends HTMLElement {
|
|
|
189
196
|
* Will trigger update() when a property was changed
|
|
190
197
|
*/
|
|
191
198
|
defineProperties() {
|
|
192
|
-
Object.keys(this.properties()).forEach((prop) => {
|
|
199
|
+
Object.keys({ ...this.injectProperties(), ...this.properties() }).forEach((prop) => {
|
|
193
200
|
// when mixing shadow dom elements with light dom elements and nesting custom elements
|
|
194
201
|
// it might occur that properties where set on an element before it was
|
|
195
202
|
// registered or connected. To avoid such timing issues we check
|
|
@@ -234,29 +241,17 @@ class BaseElement extends HTMLElement {
|
|
|
234
241
|
set: (newValue) => {
|
|
235
242
|
const oldValue = this._state[property];
|
|
236
243
|
const newValueString = JSON.stringify(newValue);
|
|
237
|
-
|
|
238
244
|
if (JSON.stringify(oldValue) !== newValueString) {
|
|
239
245
|
this._state[property] = newValue;
|
|
240
246
|
|
|
247
|
+
if (newValue instanceof Store) {
|
|
248
|
+
newValue.subscribe(this);
|
|
249
|
+
}
|
|
250
|
+
|
|
241
251
|
if (reflectAttribute || this._options.propertyOptions[property]?.reflect) {
|
|
242
252
|
this.reflectProperty({ property, newValue, newValueString });
|
|
243
253
|
}
|
|
244
254
|
|
|
245
|
-
const informWatchedPropertiesAndDispatchChangeEvent = () => {
|
|
246
|
-
// notify watched properties (after update())
|
|
247
|
-
if (property in this.watch()) {
|
|
248
|
-
this.watch()[property](newValue, oldValue);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// dispatch change event
|
|
252
|
-
if (
|
|
253
|
-
property in this._options['propertyOptions'] &&
|
|
254
|
-
this._options['propertyOptions'][property]['notify'] === true
|
|
255
|
-
) {
|
|
256
|
-
this.dispatch(`${camelToDash(property)}-changed`, newValue, true);
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
|
|
260
255
|
if (this._options.autoUpdate) {
|
|
261
256
|
this.requestUpdate({
|
|
262
257
|
notify: true,
|
|
@@ -265,18 +260,51 @@ class BaseElement extends HTMLElement {
|
|
|
265
260
|
newValueString: newValueString,
|
|
266
261
|
oldValue: oldValue,
|
|
267
262
|
}).finally(() => {
|
|
268
|
-
|
|
263
|
+
this.callPropertyWatcher(property, newValue, oldValue);
|
|
264
|
+
this.notifyPropertyChange(property, newValue);
|
|
269
265
|
});
|
|
270
266
|
} else {
|
|
271
|
-
|
|
267
|
+
this.callPropertyWatcher(property, newValue, oldValue);
|
|
268
|
+
this.notifyPropertyChange(property, newValue);
|
|
272
269
|
}
|
|
273
270
|
}
|
|
274
|
-
|
|
275
271
|
return this;
|
|
276
272
|
},
|
|
277
273
|
});
|
|
278
274
|
}
|
|
279
275
|
|
|
276
|
+
/**
|
|
277
|
+
* call the watch callbacks on property changes
|
|
278
|
+
*
|
|
279
|
+
* @param context is the elements instance
|
|
280
|
+
* @param property property that changes
|
|
281
|
+
* @param newValue
|
|
282
|
+
* @param oldValue
|
|
283
|
+
*/
|
|
284
|
+
callPropertyWatcher(property, newValue, oldValue) {
|
|
285
|
+
// notify watched properties (after update())
|
|
286
|
+
const watch = this.watch();
|
|
287
|
+
if (property in watch) {
|
|
288
|
+
watch[property](newValue, oldValue);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* notify property observer via change event
|
|
293
|
+
*
|
|
294
|
+
* @param context is the elements instance
|
|
295
|
+
* @param property property that changes
|
|
296
|
+
* @param newValue
|
|
297
|
+
*/
|
|
298
|
+
notifyPropertyChange(property, newValue) {
|
|
299
|
+
// dispatch change event
|
|
300
|
+
if (
|
|
301
|
+
property in this._options['propertyOptions'] &&
|
|
302
|
+
this._options['propertyOptions'][property]['notify'] === true
|
|
303
|
+
) {
|
|
304
|
+
this.dispatch(`${camelToDash(property)}-changed`, newValue, true);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
280
308
|
reflectProperty(options) {
|
|
281
309
|
const { property, newValue } = options;
|
|
282
310
|
const newValueString = options.newValueString || JSON.stringify(newValue);
|
|
@@ -294,6 +322,72 @@ class BaseElement extends HTMLElement {
|
|
|
294
322
|
}
|
|
295
323
|
}
|
|
296
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Context Properties to issue Context Requests and to pull contextual properties into the elements scope
|
|
327
|
+
* @return {{}}
|
|
328
|
+
*/
|
|
329
|
+
injectProperties() {
|
|
330
|
+
return {};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Define which properties that can be provided to children
|
|
335
|
+
* @return {{}}
|
|
336
|
+
*/
|
|
337
|
+
provideProperties() {
|
|
338
|
+
return {};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Defines context on the element based on keys from this.injectProperties()
|
|
343
|
+
*/
|
|
344
|
+
definePropertyInjection() {
|
|
345
|
+
Object.entries(this.injectProperties()).forEach(([key, value]) => {
|
|
346
|
+
this.requestContext(key, value);
|
|
347
|
+
});
|
|
348
|
+
// define context provider
|
|
349
|
+
if (Object.keys(this.provideProperties()).length > 0) {
|
|
350
|
+
this.addEventListener('request-context', this.onRequestContext);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Helper Function to initialize the context requests
|
|
356
|
+
*
|
|
357
|
+
* @param propertyName
|
|
358
|
+
* @param valueOrCallback
|
|
359
|
+
*/
|
|
360
|
+
requestContext(propertyName, valueOrCallback) {
|
|
361
|
+
this.dispatch('request-context', { [propertyName]: valueOrCallback }, true);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Internal Context Request Handler
|
|
366
|
+
* This will check if the current/receiving instance actually provides a value under the requested name.
|
|
367
|
+
* If there is something to provide it will either assign the value to the requesting element or call the callback.
|
|
368
|
+
*
|
|
369
|
+
* @param event
|
|
370
|
+
*/
|
|
371
|
+
onRequestContext(event) {
|
|
372
|
+
const properties = this.properties();
|
|
373
|
+
const provideProperties = this.provideProperties();
|
|
374
|
+
|
|
375
|
+
Object.entries(event.detail ?? {}).forEach(([key, valueOrCallback]) => {
|
|
376
|
+
if (key in provideProperties) {
|
|
377
|
+
// found it, provide it
|
|
378
|
+
const providedValue = provideProperties[key];
|
|
379
|
+
event.stopPropagation();
|
|
380
|
+
if (typeof valueOrCallback === 'function') {
|
|
381
|
+
// call function with context value
|
|
382
|
+
valueOrCallback(providedValue);
|
|
383
|
+
} else {
|
|
384
|
+
// assign to prop
|
|
385
|
+
event.target[key] = providedValue;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
297
391
|
// Deprecated
|
|
298
392
|
hooks() {
|
|
299
393
|
return {};
|
package/src/StyledElement.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { BaseElement } from './BaseElement';
|
|
2
|
-
import { supportsAdoptingStyleSheets, getShadowParentOrBody } from './util/DOMHelper';
|
|
3
|
-
export { i18n } from './util/i18n';
|
|
1
|
+
import { BaseElement } from './BaseElement.js';
|
|
2
|
+
import { supportsAdoptingStyleSheets, getShadowParentOrBody } from './util/DOMHelper.js';
|
|
3
|
+
export { i18n } from './util/i18n.js';
|
|
4
4
|
|
|
5
5
|
class StyledElement extends BaseElement {
|
|
6
6
|
static globalStyles = null;
|
package/src/TemplateElement.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { StyledElement } from './StyledElement';
|
|
1
|
+
import { StyledElement } from './StyledElement.js';
|
|
2
2
|
import { html } from 'lit-html';
|
|
3
3
|
import { render } from 'lit-html/lib/shady-render.js';
|
|
4
|
-
export { i18n } from './util/i18n';
|
|
4
|
+
export { i18n } from './util/i18n.js';
|
|
5
5
|
|
|
6
6
|
class TemplateElement extends StyledElement {
|
|
7
7
|
constructor(options) {
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { StyledElement } from '../../StyledElement';
|
|
2
|
-
import { html, spreadAttributes, unsafeHTML } from './util/html';
|
|
3
|
-
import { render } from './util/render';
|
|
4
|
-
export { i18n } from '../../util/i18n';
|
|
1
|
+
import { StyledElement } from '../../StyledElement.js';
|
|
2
|
+
import { html, spreadAttributes, unsafeHTML } from './util/html.js';
|
|
3
|
+
import { render } from './util/render.js';
|
|
4
|
+
export { i18n } from '../../util/i18n.js';
|
|
5
5
|
|
|
6
6
|
class TemplateElement extends StyledElement {
|
|
7
|
-
static _$templateElement$ = true;
|
|
8
|
-
|
|
9
7
|
constructor(options) {
|
|
10
8
|
super({
|
|
11
9
|
deferUpdate: false,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { BaseElement } from '../../BaseElement';
|
|
2
|
-
import { StyledElement } from '../../StyledElement';
|
|
3
|
-
import { TemplateElement, html, unsafeHTML, spreadAttributes } from './TemplateElement';
|
|
4
|
-
import { toString } from '../../util/toString';
|
|
5
|
-
import { defineElement } from '../../util/defineElement';
|
|
6
|
-
import { i18n } from '../../util/i18n';
|
|
1
|
+
import { BaseElement } from '../../BaseElement.js';
|
|
2
|
+
import { StyledElement } from '../../StyledElement.js';
|
|
3
|
+
import { TemplateElement, html, unsafeHTML, spreadAttributes } from './TemplateElement.js';
|
|
4
|
+
import { toString } from '../../util/toString.js';
|
|
5
|
+
import { defineElement } from '../../util/defineElement.js';
|
|
6
|
+
import { i18n } from '../../util/i18n.js';
|
|
7
7
|
|
|
8
8
|
export {
|
|
9
9
|
BaseElement,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { TemplateElement } from '../TemplateElement.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Parses the given template string and returns a real DOM element.
|
|
@@ -45,22 +45,10 @@ const diffAttributes = function (templateElement, domElement) {
|
|
|
45
45
|
*/
|
|
46
46
|
const isTemplateElement = (element) => {
|
|
47
47
|
const tagName = element.tagName?.toLowerCase() ?? false;
|
|
48
|
-
if (!tagName || !tagName.includes('-')) {
|
|
48
|
+
if (!tagName || !tagName.includes('-') || element.shadowRoot) {
|
|
49
49
|
return false;
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
if (_cachedTemplateElements[tagName]) {
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const elementClass = customElements.get(tagName);
|
|
57
|
-
const isTemplateElement = elementClass && elementClass._$templateElement$ === true;
|
|
58
|
-
|
|
59
|
-
if (isTemplateElement) {
|
|
60
|
-
_cachedTemplateElements[tagName] = elementClass;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return isTemplateElement;
|
|
51
|
+
return element instanceof TemplateElement;
|
|
64
52
|
};
|
|
65
53
|
|
|
66
54
|
/**
|
|
@@ -138,7 +126,10 @@ const diffNodes = function (templateNode, domNode) {
|
|
|
138
126
|
|
|
139
127
|
// If there are child nodes, diff them recursively
|
|
140
128
|
if (templateChildNode.hasChildNodes()) {
|
|
141
|
-
|
|
129
|
+
// https://stackoverflow.com/questions/46898739/why-domparser-doesnt-use-the-customelements-registered
|
|
130
|
+
// we have to use the domChildNode here because the templateChildNode is not upgraded because it was not
|
|
131
|
+
// imported into a document yet and therefore instanceOf will not work...
|
|
132
|
+
if (!isTemplateElement(domChildNode)) {
|
|
142
133
|
diffNodes(templateChildNode, domChildNode);
|
|
143
134
|
}
|
|
144
135
|
}
|
|
@@ -33,6 +33,18 @@ export function isNaN(value) {
|
|
|
33
33
|
return Number.isNaN(value);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* deep compare two values primitive or complex
|
|
38
|
+
*
|
|
39
|
+
* @param valueA
|
|
40
|
+
* @param valueB
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
export function deepEquals(valueA, valueB) {
|
|
45
|
+
return JSON.stringify(valueA) === JSON.stringify(valueB);
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
export function parseAttribute(value, options = {}) {
|
|
37
49
|
if (options.parse === false || !isString(value)) {
|
|
38
50
|
// no-op
|
package/src/util/Store.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { isObjectLike } from './AttributeParser';
|
|
1
|
+
import { isObjectLike, deepEquals } from './AttributeParser.js';
|
|
2
|
+
import { BaseElement } from '../BaseElement';
|
|
2
3
|
|
|
3
4
|
export class Store {
|
|
4
5
|
_observer = new Set();
|
|
@@ -17,13 +18,17 @@ export class Store {
|
|
|
17
18
|
return this._state[key];
|
|
18
19
|
},
|
|
19
20
|
set: (newValue) => {
|
|
21
|
+
const oldState = structuredClone(this._state);
|
|
20
22
|
const oldValue = this._state[key];
|
|
21
23
|
this._state[key] = newValue;
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
watch
|
|
24
|
+
|
|
25
|
+
if (!deepEquals(newValue, oldValue)) {
|
|
26
|
+
const watch = this.watch();
|
|
27
|
+
if (watch.hasOwnProperty(key) && typeof watch[key] === 'function') {
|
|
28
|
+
watch[key](newValue, oldValue);
|
|
29
|
+
}
|
|
30
|
+
this.requestUpdate(oldState);
|
|
25
31
|
}
|
|
26
|
-
this.requestUpdate();
|
|
27
32
|
},
|
|
28
33
|
});
|
|
29
34
|
});
|
|
@@ -64,9 +69,27 @@ export class Store {
|
|
|
64
69
|
this._observer.delete(observer);
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
requestUpdate() {
|
|
68
|
-
this._observer.forEach((observer) =>
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
requestUpdate(oldState) {
|
|
73
|
+
this._observer.forEach(async (observer) => {
|
|
74
|
+
if (observer instanceof BaseElement) {
|
|
75
|
+
await observer.requestUpdate();
|
|
76
|
+
// check if store is watched by an observer
|
|
77
|
+
if (Object.keys(observer.watch() ?? {}).length > 0) {
|
|
78
|
+
// observer actually has watched properties
|
|
79
|
+
const properties = observer.properties();
|
|
80
|
+
Object.keys(observer.watch() ?? {}).forEach((key) => {
|
|
81
|
+
if (properties[key] === this) {
|
|
82
|
+
// observer is actually watching store changes provide new and old values
|
|
83
|
+
observer.callPropertyWatcher(key, this._state, oldState);
|
|
84
|
+
observer.notifyPropertyChange(key, this._state);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
} else if (typeof observer === 'function') {
|
|
89
|
+
observer();
|
|
90
|
+
} else {
|
|
91
|
+
console.error('Store: observer type is not supported.', observer);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
71
94
|
}
|
|
72
95
|
}
|