@webtides/element-js 0.7.2 → 1.0.0-alpha.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/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@webtides/element-js",
3
- "version": "0.7.2",
3
+ "version": "1.0.0-alpha.1",
4
4
  "module": "index.js",
5
5
  "main": "dist/index.js",
6
+ "type": "module",
6
7
  "files": [
7
8
  "/dist",
8
9
  "/src",
@@ -16,12 +17,7 @@
16
17
  },
17
18
  "./src/*": "./src/*.js",
18
19
  "./src/util/*": "./src/util/*.js",
19
- "./src/renderer/vanilla": "./src/renderer/vanilla/index.js",
20
- "./src/renderer/vanilla/TemplateElement": "./src/renderer/vanilla/TemplateElement.js",
21
- "./src/renderer/vanilla/util/*": "./src/renderer/vanilla/util/*.js",
22
- "./src/renderer/vanilla/util/directives.js": "./src/renderer/vanilla/util/directives.js",
23
- "./src/renderer/vanilla/util/html.js": "./src/renderer/vanilla/util/html.js",
24
- "./src/renderer/vanilla/util/render.js": "./src/renderer/vanilla/util/render.js"
20
+ "./src/dom-parts/*": "./src/dom-parts/*.js"
25
21
  },
26
22
  "repository": "https://github.com/webtides/element-js.git",
27
23
  "author": "@webtides",
@@ -37,23 +33,23 @@
37
33
  "element-js",
38
34
  "shadow-dom"
39
35
  ],
40
- "dependencies": {
41
- "lit-html": "^1.2.1"
42
- },
36
+ "dependencies": {},
43
37
  "devDependencies": {
44
- "@open-wc/testing": "^3.0.3",
38
+ "@babel/eslint-parser": "^7.18.9",
39
+ "@open-wc/testing": "^3.1.6",
45
40
  "@web/dev-server": "^0.1.30",
46
- "@web/test-runner": "^0.13.26",
41
+ "@web/test-runner": "^0.14.0",
47
42
  "babel-eslint": "^10.1.0",
48
43
  "babel-plugin-module-resolver": "^5.0.0",
49
44
  "deepmerge": "^4.2.2",
50
- "esbuild": "^0.8.57",
51
- "eslint": "^7.0.0",
52
- "eslint-config-prettier": "^6.11.0",
53
- "eslint-plugin-prettier": "^3.1.3",
54
- "husky": "^7.0.4",
55
- "lint-staged": "^12.1.2",
56
- "prettier": "^2.5.1"
45
+ "esbuild": "^0.15.5",
46
+ "eslint": "8.22.0",
47
+ "eslint-config-prettier": "^7.2.0",
48
+ "eslint-plugin-import": "^2.26.0",
49
+ "eslint-plugin-prettier": "^4.2.1",
50
+ "husky": "^8.0.1",
51
+ "lint-staged": "^13.0.3",
52
+ "prettier": "^2.7.1"
57
53
  },
58
54
  "lint-staged": {
59
55
  "*.{js,json}": [
@@ -62,7 +58,7 @@
62
58
  ]
63
59
  },
64
60
  "scripts": {
65
- "lint": "eslint src/**/*.js",
61
+ "lint": "eslint index.js 'src/**/*.js'",
66
62
  "prepare": "esbuild index.js --outfile=dist/index.js --bundle --format=esm --sourcemap --target=chrome79,firefox68,safari12,edge79",
67
63
  "test": "web-test-runner test/**/*.test.{html,js} --node-resolve",
68
64
  "test:coverage": "web-test-runner test/**/*.test.{html,js} --node-resolve --coverage",
@@ -26,13 +26,6 @@ class BaseElement extends HTMLElement {
26
26
  propertyOptions: {},
27
27
  ...options,
28
28
  };
29
-
30
- if (options.childListUpdate !== undefined && options.childListUpdate !== null) {
31
- this._options.mutationObserverOptions.childList = options.childListUpdate;
32
- console.warn(
33
- `[${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`,
34
- );
35
- }
36
29
  }
37
30
 
38
31
  connectedCallback() {
@@ -42,9 +35,6 @@ class BaseElement extends HTMLElement {
42
35
  // define all properties to "this"
43
36
  this.defineProperties();
44
37
 
45
- // define all computed properties to "this"
46
- this.defineComputedProperties();
47
-
48
38
  // define context
49
39
  this.definePropertyInjection();
50
40
 
@@ -52,13 +42,14 @@ class BaseElement extends HTMLElement {
52
42
  this.defineObserver();
53
43
 
54
44
  if (this.hasAttribute('defer-update') || this._options.deferUpdate) {
55
- // don't updates/render, but register refs and events
45
+ // don't update/render, but register refs and events
56
46
  this.registerEventsAndRefs();
57
47
 
58
48
  this.triggerHook('connected');
59
49
  } else {
60
50
  this.requestUpdate({ notify: false }).then(() => {
61
51
  this.triggerHook('connected');
52
+ this.triggerHook('afterUpdate');
62
53
  });
63
54
  }
64
55
  }
@@ -388,11 +379,6 @@ class BaseElement extends HTMLElement {
388
379
  });
389
380
  }
390
381
 
391
- // Deprecated
392
- hooks() {
393
- return {};
394
- }
395
-
396
382
  // Connected lifecycle hook
397
383
  connected() {}
398
384
 
@@ -407,14 +393,6 @@ class BaseElement extends HTMLElement {
407
393
 
408
394
  // Triggers a lifecycle hook based on the name
409
395
  triggerHook(name) {
410
- if (this.hooks && name in this.hooks()) {
411
- console.warn(
412
- `[${this.localName}] Using the hooks() map for lifecycle hooks is deprecated! Please overwrite the existing lifecycle hook functions. See the docs for more info`,
413
- );
414
- this.hooks()[name]();
415
- return;
416
- }
417
-
418
396
  if (name in this) {
419
397
  this[name]();
420
398
  }
@@ -431,25 +409,6 @@ class BaseElement extends HTMLElement {
431
409
  return {};
432
410
  }
433
411
 
434
- // Deprecated
435
- computed() {
436
- return {};
437
- }
438
-
439
- // Deprecated
440
- defineComputedProperties() {
441
- Object.keys(this.computed()).forEach((prop) => {
442
- if (!this.hasOwnProperty(prop)) {
443
- console.warn(
444
- `[${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`,
445
- );
446
- Object.defineProperty(this, prop, {
447
- get: () => this.computed()[prop](),
448
- });
449
- }
450
- });
451
- }
452
-
453
412
  /**
454
413
  * Return a map of events that should be registered on the element.
455
414
  * eg. { '.link': {
@@ -33,14 +33,6 @@ class StyledElement extends BaseElement {
33
33
  if (supportsAdoptingStyleSheets() && this._options.shadowRender) {
34
34
  // adopting does only make sense in shadow dom. Fall back to append for light elements
35
35
  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
-
41
- // if shadowRoot is polyfilled - scope element template
42
- if (window.ShadyCSS !== undefined) {
43
- window.ShadyCSS.styleElement(this);
44
36
  }
45
37
  }
46
38
 
@@ -52,7 +44,7 @@ class StyledElement extends BaseElement {
52
44
  if (!supportsAdoptingStyleSheets() || this._options.shadowRender === false) {
53
45
  // append stylesheets to template if not already adopted
54
46
  const appendableStyles = [...this._styles];
55
- if (this._options.shadowRender && this._options.adoptGlobalStyles && !window.ShadyCSS) {
47
+ if (this._options.shadowRender && this._options.adoptGlobalStyles) {
56
48
  appendableStyles.unshift(StyledElement.globalStyles?.textContent ?? '');
57
49
  }
58
50
  this.appendStyleSheets(appendableStyles);
@@ -1,6 +1,6 @@
1
1
  import { StyledElement } from './StyledElement.js';
2
- import { html } from 'lit-html';
3
- import { render } from 'lit-html/lib/shady-render.js';
2
+ import { html } from './dom-parts/html.js';
3
+ import { render } from './dom-parts/render.js';
4
4
  export { i18n } from './util/i18n.js';
5
5
 
6
6
  class TemplateElement extends StyledElement {
@@ -35,11 +35,7 @@ class TemplateElement extends StyledElement {
35
35
  // just a plain string literal. no lit-html required
36
36
  this.getRoot().innerHTML = `${template}`;
37
37
  } else {
38
- // render via lit-html
39
- render(html` ${template} `, this.getRoot(), {
40
- scopeName: this.localName,
41
- eventContext: this,
42
- });
38
+ render(template, this.getRoot());
43
39
  }
44
40
  }
45
41
 
@@ -0,0 +1,160 @@
1
+ import { Part } from './Part.js';
2
+
3
+ /**
4
+ * @param {Element} node
5
+ * @param {String} name
6
+ * @param {Boolean} oldValue
7
+ * @return {(function(*): void)|*}
8
+ */
9
+ const processBooleanAttribute = (node, name, oldValue) => {
10
+ // TODO: It would be better if the ?boolean= attribute would not be there in the first place...
11
+ node.removeAttribute(`?${name}`);
12
+ return (newValue) => {
13
+ const value = !!newValue?.valueOf() && newValue !== 'false';
14
+ if (oldValue !== value) {
15
+ node.toggleAttribute(name, (oldValue = !!value));
16
+ }
17
+ };
18
+ };
19
+
20
+ /**
21
+ * @param {Element} node
22
+ * @param {String} name
23
+ * @return {(function(*): void)|*}
24
+ */
25
+ const processPropertyAttribute = (node, name) => {
26
+ // TODO: It would be better if the .property= attribute would not be there in the first place...
27
+ node.removeAttribute(`.${name}`);
28
+ return (value) => {
29
+ node[name] = value;
30
+ };
31
+ };
32
+
33
+ /**
34
+ * @param {Element} node
35
+ * @param {String} name
36
+ * @return {(function(*): void)|*}
37
+ */
38
+ const processEventAttribute = (node, name) => {
39
+ // TODO: It would be better if the event attribute would not be there in the first place...
40
+ node.removeAttribute(name);
41
+
42
+ let oldValue = undefined;
43
+ let type = name.startsWith('@') ? name.slice(1) : name.toLowerCase().slice(2);
44
+
45
+ return (newValue) => {
46
+ if (oldValue !== newValue) {
47
+ if (oldValue) node.removeEventListener(type, oldValue);
48
+ if ((oldValue = newValue)) node.addEventListener(type, oldValue);
49
+ }
50
+ };
51
+ };
52
+
53
+ /**
54
+ * @param {Element} node
55
+ * @param {String} name
56
+ * @return {(function(*): void)|*}
57
+ */
58
+ const processAttribute = (node, name) => {
59
+ let oldValue,
60
+ orphan = true;
61
+ const attributeNode = globalThis.document?.createAttributeNS(null, name);
62
+ return (newValue) => {
63
+ const value = newValue?.valueOf();
64
+ if (oldValue !== value) {
65
+ if ((oldValue = value) == null) {
66
+ if (!orphan) {
67
+ node.removeAttributeNode(attributeNode);
68
+ orphan = true;
69
+ }
70
+ } else {
71
+ attributeNode.value = value;
72
+ if (orphan) {
73
+ node.setAttributeNodeNS(attributeNode);
74
+ orphan = false;
75
+ }
76
+ }
77
+ }
78
+ };
79
+ };
80
+
81
+ /**
82
+ * @param {Element} node
83
+ * @param {String} name
84
+ * @return {(function(*): void)|*}
85
+ */
86
+ export const processAttributePart = (node, name) => {
87
+ // boolean attribute: ?boolean=${...}
88
+ if (name.startsWith('?')) {
89
+ return processBooleanAttribute(node, name.slice(1), false);
90
+ }
91
+
92
+ // property attribute: .property=${...}
93
+ if (name.startsWith('.')) {
94
+ return processPropertyAttribute(node, name.slice(1));
95
+ }
96
+
97
+ // event attribute: @event=${...} || "old school" event attribute: onevent=${...}
98
+ if (name.startsWith('@') || name.startsWith('on')) {
99
+ return processEventAttribute(node, name);
100
+ }
101
+
102
+ // normal "string" attribute: attribute=${...}
103
+ return processAttribute(node, name);
104
+ };
105
+
106
+ /**
107
+ * For updating a single attribute
108
+ */
109
+ export class AttributePart extends Part {
110
+ name = undefined;
111
+ interpolations = 1;
112
+ values = [];
113
+ currentValueIndex = 0;
114
+ initialValue = undefined;
115
+
116
+ /**
117
+ * @param {Node} node
118
+ * @param {String} name
119
+ * @param {String} initialValue
120
+ */
121
+ constructor(node, name, initialValue) {
122
+ super();
123
+ this.name = name;
124
+ this.initialValue = initialValue;
125
+ this.processor = processAttributePart(node.nextElementSibling, this.name);
126
+ }
127
+
128
+ /**
129
+ * @param {Node} node
130
+ */
131
+ addNode(node) {
132
+ this.interpolations++;
133
+ // TODO: what about the comment node? It will always be the first for now...
134
+ }
135
+
136
+ /**
137
+ * @param {string | number | bigint | boolean | undefined | symbol | null} value
138
+ */
139
+ update(value) {
140
+ // If we only have one sole interpolation, we can just apply the update
141
+ if (this.initialValue === '\x03') {
142
+ this.processor(value);
143
+ return;
144
+ }
145
+
146
+ // Instead of applying the update immediately, we check if the part has multiple interpolations
147
+ // and store the values for each interpolation in a list
148
+ this.values[this.currentValueIndex++] = value;
149
+
150
+ // Only the last call to update (for the last interpolation) will actually trigger the update
151
+ // on the DOM processor. Here we can reset everything before the next round of updates
152
+ if (this.interpolations === this.currentValueIndex) {
153
+ this.currentValueIndex = 0;
154
+ let replaceIndex = 0;
155
+ // Note: this will coarse the values into strings, but it's probably ok since there can only be multiple values in string attributes?!
156
+ const parsedValue = this.initialValue.replace(/\x03/g, () => this.values[replaceIndex++]);
157
+ this.processor(parsedValue);
158
+ }
159
+ }
160
+ }