@webtides/element-js 1.0.0 → 1.1.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +147 -90
  2. package/README.md +3 -3
  3. package/docs/README.md +70 -89
  4. package/docs/concepts/classes.md +37 -66
  5. package/docs/concepts/hierarchy.md +29 -31
  6. package/docs/concepts/lifecycle.md +9 -23
  7. package/docs/concepts/reactivity.md +1 -3
  8. package/docs/features/computed-properties.md +13 -16
  9. package/docs/features/dependency-injection-context-protocol.md +75 -87
  10. package/docs/features/global-styles.md +1 -1
  11. package/docs/features/methods.md +14 -15
  12. package/docs/features/properties.md +21 -33
  13. package/docs/features/refs.md +23 -31
  14. package/docs/features/renderless.md +31 -37
  15. package/docs/features/store.md +63 -78
  16. package/docs/features/templates.md +4 -1
  17. package/docs/features/watch.md +28 -35
  18. package/docs/getting-started/upgrade-to-v1.md +16 -0
  19. package/docs/guide/guide.md +10 -22
  20. package/index.js +29 -2
  21. package/package.json +62 -62
  22. package/src/BaseElement.js +632 -631
  23. package/src/StyledElement.js +104 -104
  24. package/src/TemplateElement.js +53 -53
  25. package/src/dom-parts/AttributePart.js +116 -116
  26. package/src/dom-parts/ChildNodePart.js +288 -288
  27. package/src/dom-parts/NodePart.js +27 -27
  28. package/src/dom-parts/Part.js +6 -6
  29. package/src/dom-parts/TemplatePart.js +119 -119
  30. package/src/dom-parts/TemplateResult.js +270 -270
  31. package/src/dom-parts/directives.js +105 -48
  32. package/src/dom-parts/html.js +1 -1
  33. package/src/dom-parts/render.js +6 -6
  34. package/src/util/AttributeParser.js +47 -47
  35. package/src/util/DOMHelper.js +190 -190
  36. package/src/util/Directive.js +31 -31
  37. package/src/util/GlobalStylesStore.js +63 -53
  38. package/src/util/SerializeStateHelper.js +56 -56
  39. package/src/util/Store.js +175 -174
  40. package/src/util/crypto.js +13 -0
  41. package/src/util/defineElement.js +5 -5
  42. package/src/util/toString.js +5 -5
@@ -1,4 +1,4 @@
1
- import { camelToDash, decodeAttribute, encodeAttribute } from '../util/AttributeParser.js';
1
+ import { camelToDash, decodeAttribute, encodeAttribute, isNaN } from '../util/AttributeParser.js';
2
2
  import { TemplateResult } from './TemplateResult.js';
3
3
  import { convertStringToHTML, convertStringToTemplate } from '../util/DOMHelper.js';
4
4
  import { defineDirective, Directive } from '../util/Directive.js';
@@ -10,11 +10,11 @@ import { defineDirective, Directive } from '../util/Directive.js';
10
10
  * @returns {string} string of classes joined together
11
11
  */
12
12
  const classMap = (map) => {
13
- const classes = [];
14
- for (const [key, value] of Object.entries(map)) {
15
- if (value) classes.push(key);
16
- }
17
- return classes.join(' ');
13
+ const classes = [];
14
+ for (const [key, value] of Object.entries(map)) {
15
+ if (value) classes.push(key);
16
+ }
17
+ return classes.join(' ');
18
18
  };
19
19
 
20
20
  /**
@@ -24,11 +24,11 @@ const classMap = (map) => {
24
24
  * @returns {string} string of styles joined together
25
25
  */
26
26
  const styleMap = (map) => {
27
- const styles = [];
28
- for (const [key, value] of Object.entries(map)) {
29
- if (value) styles.push(`${key}:${value};`);
30
- }
31
- return styles.join(' ');
27
+ const styles = [];
28
+ for (const [key, value] of Object.entries(map)) {
29
+ if (value) styles.push(`${key}:${value};`);
30
+ }
31
+ return styles.join(' ');
32
32
  };
33
33
 
34
34
  /**
@@ -39,7 +39,7 @@ const styleMap = (map) => {
39
39
  * @returns {TemplateResult | string}
40
40
  */
41
41
  const when = (condition, trueCase, falseCase) => {
42
- return condition ? trueCase : falseCase;
42
+ return condition ? trueCase : falseCase;
43
43
  };
44
44
 
45
45
  /**
@@ -50,7 +50,7 @@ const when = (condition, trueCase, falseCase) => {
50
50
  * @returns {TemplateResult | string}
51
51
  */
52
52
  const choose = (value, cases, defaultCase) => {
53
- return cases[value] || defaultCase;
53
+ return cases[value] || defaultCase;
54
54
  };
55
55
 
56
56
  /**
@@ -59,47 +59,88 @@ const choose = (value, cases, defaultCase) => {
59
59
  * @returns {(function(): Node) | { __unsafeHTML: true, string: string }}
60
60
  */
61
61
  const unsafeHTML = (string) => {
62
- if (!globalThis.DOMParser) {
63
- return { __unsafeHTML: true, string };
64
- }
65
- const node = convertStringToHTML(string).firstChild;
66
- const importedNode = globalThis.document?.importNode(node, true);
67
- return () => importedNode;
62
+ if (!globalThis.DOMParser) {
63
+ return { __unsafeHTML: true, string };
64
+ }
65
+ const node = convertStringToHTML(string).firstChild;
66
+ const importedNode = globalThis.document?.importNode(node, true);
67
+ return () => importedNode;
68
68
  };
69
69
 
70
70
  /**
71
71
  * Renders multiple attributes as key="value" pairs from a map of attributes
72
72
  */
73
73
  export class SpreadAttributesDirective extends Directive {
74
- /**
75
- * @param {Object.<string, any>} attributes
76
- */
77
- update(attributes) {
78
- // TODO: what if the attribute was already there..?!
79
- // TODO: what if the attribute was there from a previous render/update, but not anymore?!
80
- for (const name of Object.keys(attributes)) {
81
- const value = attributes[name];
82
- this.node.setAttribute(
83
- camelToDash(name),
84
- encodeAttribute(typeof value !== 'string' ? JSON.stringify(value) : value),
85
- );
86
- }
87
- }
74
+ /**
75
+ * @param {Object.<string, any>} attributes
76
+ */
77
+ update(attributes) {
78
+ for (const name of Object.keys(attributes)) {
79
+ const value = attributes[name];
80
+ if (value === undefined || value === null || isNaN(value)) {
81
+ this.node.removeAttribute(camelToDash(name));
82
+ } else {
83
+ this.node.setAttribute(
84
+ camelToDash(name),
85
+ encodeAttribute(typeof value !== 'string' ? JSON.stringify(value) : value),
86
+ );
87
+ }
88
+ }
89
+ }
88
90
 
89
- /**
90
- * @param {Object.<string, any>} attributes
91
- * @returns {string}
92
- */
93
- stringify(attributes) {
94
- return Object.keys(attributes)
95
- .map((name) => {
96
- let value = attributes[name];
97
- return `${camelToDash(name)}='${encodeAttribute(
98
- typeof value !== 'string' ? JSON.stringify(value) : value,
99
- )}'`;
100
- })
101
- .join(' ');
102
- }
91
+ /**
92
+ * @param {Object.<string, any>} attributes
93
+ * @returns {string}
94
+ */
95
+ stringify(attributes) {
96
+ return Object.keys(attributes)
97
+ .map((name) => {
98
+ let value = attributes[name];
99
+ return `${camelToDash(name)}='${encodeAttribute(
100
+ typeof value !== 'string' ? JSON.stringify(value) : value,
101
+ )}'`;
102
+ })
103
+ .join(' ');
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Renders an attribute as key="value" if a condition is truthy
109
+ */
110
+ export class OptionalAttributeDirective extends Directive {
111
+ /**
112
+ * @param {boolean} condition
113
+ * @param {string} attributeName
114
+ * @param {string|any} attributeValue
115
+ */
116
+ update(condition, attributeName, attributeValue = '') {
117
+ if (condition) {
118
+ this.node.setAttribute(
119
+ camelToDash(attributeName),
120
+ encodeAttribute(typeof attributeValue !== 'string' ? JSON.stringify(attributeValue) : attributeValue),
121
+ );
122
+ } else {
123
+ this.node.removeAttribute(camelToDash(attributeName));
124
+ }
125
+ }
126
+ /**
127
+ * @param {boolean} condition
128
+ * @param {string} attributeName
129
+ * @param {string|any} attributeValue
130
+ */
131
+ stringify(condition, attributeName, attributeValue = '') {
132
+ if (condition) {
133
+ if (attributeValue === '') {
134
+ // empty string toggles boolean attributes
135
+ return `${camelToDash(attributeName)}`;
136
+ }
137
+ return `${camelToDash(attributeName)}='${encodeAttribute(
138
+ typeof attributeValue !== 'string' ? JSON.stringify(attributeValue) : attributeValue,
139
+ )}'`;
140
+ } else {
141
+ return '';
142
+ }
143
+ }
103
144
  }
104
145
 
105
146
  /**
@@ -108,4 +149,20 @@ export class SpreadAttributesDirective extends Directive {
108
149
  */
109
150
  const spreadAttributes = defineDirective(SpreadAttributesDirective);
110
151
 
111
- export { classMap, styleMap, when, choose, unsafeHTML, spreadAttributes };
152
+ /**
153
+ * Renders an optional attribute based on a condition
154
+ * @param {Object.<string, any>} attributes
155
+ */
156
+ const optionalAttribute = defineDirective(OptionalAttributeDirective);
157
+
158
+ export {
159
+ defineDirective,
160
+ Directive,
161
+ classMap,
162
+ styleMap,
163
+ when,
164
+ choose,
165
+ unsafeHTML,
166
+ spreadAttributes,
167
+ optionalAttribute,
168
+ };
@@ -6,7 +6,7 @@ import { TemplateResult } from './TemplateResult.js';
6
6
  * @return {TemplateResult}
7
7
  */
8
8
  const html = function (strings, ...values) {
9
- return new TemplateResult(strings, ...values);
9
+ return new TemplateResult(strings, ...values);
10
10
  };
11
11
 
12
12
  export { html };
@@ -4,12 +4,12 @@
4
4
  * @param {Element} domNode
5
5
  */
6
6
  const render = (template, domNode) => {
7
- if (typeof template === 'string') {
8
- // just a plain string (or literal)
9
- domNode.innerHTML = template;
10
- } else {
11
- template.renderInto(domNode);
12
- }
7
+ if (typeof template === 'string') {
8
+ // just a plain string (or literal)
9
+ domNode.innerHTML = template;
10
+ } else {
11
+ template.renderInto(domNode);
12
+ }
13
13
  };
14
14
 
15
15
  export { render };
@@ -4,7 +4,7 @@
4
4
  * @returns {boolean}
5
5
  */
6
6
  export function isObjectLike(value) {
7
- return typeof value == 'object' && value !== null;
7
+ return typeof value == 'object' && value !== null;
8
8
  }
9
9
 
10
10
  /**
@@ -13,11 +13,11 @@ export function isObjectLike(value) {
13
13
  * @returns {boolean}
14
14
  */
15
15
  export function isJSON(value) {
16
- try {
17
- return JSON.parse(value) && !!value;
18
- } catch (e) {
19
- return false;
20
- }
16
+ try {
17
+ return JSON.parse(value) && !!value;
18
+ } catch (e) {
19
+ return false;
20
+ }
21
21
  }
22
22
 
23
23
  /**
@@ -26,7 +26,7 @@ export function isJSON(value) {
26
26
  * @returns {boolean}
27
27
  */
28
28
  export function isBoolean(value) {
29
- return value === 'true' || value === 'false';
29
+ return value === 'true' || value === 'false';
30
30
  }
31
31
 
32
32
  /**
@@ -35,7 +35,7 @@ export function isBoolean(value) {
35
35
  * @returns {boolean}
36
36
  */
37
37
  export function parseBoolean(value) {
38
- return value === 'true';
38
+ return value === 'true';
39
39
  }
40
40
 
41
41
  /**
@@ -44,10 +44,10 @@ export function parseBoolean(value) {
44
44
  * @returns {boolean}
45
45
  */
46
46
  export function isString(value) {
47
- return (
48
- typeof value === 'string' ||
49
- (!!value && typeof value === 'object' && Object.prototype.toString.call(value) === '[object String]')
50
- );
47
+ return (
48
+ typeof value === 'string' ||
49
+ (!!value && typeof value === 'object' && Object.prototype.toString.call(value) === '[object String]')
50
+ );
51
51
  }
52
52
 
53
53
  /**
@@ -56,7 +56,7 @@ export function isString(value) {
56
56
  * @returns {boolean}
57
57
  */
58
58
  export function isNumber(value) {
59
- return new RegExp('^-?(0|0\\.\\d+|[1-9]\\d*(\\.\\d+)?)$').test(value);
59
+ return new RegExp('^-?(0|0\\.\\d+|[1-9]\\d*(\\.\\d+)?)$').test(value);
60
60
  }
61
61
 
62
62
  /**
@@ -65,7 +65,7 @@ export function isNumber(value) {
65
65
  * @returns {boolean}
66
66
  */
67
67
  export function isNaN(value) {
68
- return Number.isNaN(value);
68
+ return Number.isNaN(value);
69
69
  }
70
70
 
71
71
  /**
@@ -75,7 +75,7 @@ export function isNaN(value) {
75
75
  * @returns {boolean}
76
76
  */
77
77
  export function deepEquals(valueA, valueB) {
78
- return JSON.stringify(valueA) === JSON.stringify(valueB);
78
+ return JSON.stringify(valueA) === JSON.stringify(valueB);
79
79
  }
80
80
 
81
81
  /**
@@ -85,23 +85,23 @@ export function deepEquals(valueA, valueB) {
85
85
  * @returns {string | number | boolean | object | array}
86
86
  */
87
87
  export function parseAttribute(value, options = {}) {
88
- if (options.parse === false || !isString(value)) {
89
- // no-op
90
- return value;
91
- }
92
- if (typeof options.parse === 'function') {
93
- // custom parse fn
94
- return options.parse(value);
95
- }
96
-
97
- // default parse
98
- let parsedValue = value;
99
-
100
- if (isJSON(value)) parsedValue = JSON.parse(value);
101
- else if (isBoolean(value)) parsedValue = parseBoolean(value);
102
- else if (isNumber(value)) parsedValue = parseFloat(value);
103
-
104
- return parsedValue;
88
+ if (options.parse === false || !isString(value)) {
89
+ // no-op
90
+ return value;
91
+ }
92
+ if (typeof options.parse === 'function') {
93
+ // custom parse fn
94
+ return options.parse(value);
95
+ }
96
+
97
+ // default parse
98
+ let parsedValue = value;
99
+
100
+ if (isJSON(value)) parsedValue = JSON.parse(value);
101
+ else if (isBoolean(value)) parsedValue = parseBoolean(value);
102
+ else if (isNumber(value)) parsedValue = parseFloat(value);
103
+
104
+ return parsedValue;
105
105
  }
106
106
 
107
107
  /**
@@ -110,9 +110,9 @@ export function parseAttribute(value, options = {}) {
110
110
  * @returns {string}
111
111
  */
112
112
  export function dashToCamel(string) {
113
- if (string.indexOf('-') === -1) return string;
113
+ if (string.indexOf('-') === -1) return string;
114
114
 
115
- return string.replace(/-[a-z]/g, (matches) => matches[1].toUpperCase());
115
+ return string.replace(/-[a-z]/g, (matches) => matches[1].toUpperCase());
116
116
  }
117
117
 
118
118
  /**
@@ -121,7 +121,7 @@ export function dashToCamel(string) {
121
121
  * @returns {string}
122
122
  */
123
123
  export function camelToDash(string) {
124
- return string.replace(/([A-Z])/g, '-$1').toLowerCase();
124
+ return string.replace(/([A-Z])/g, '-$1').toLowerCase();
125
125
  }
126
126
 
127
127
  /**
@@ -130,11 +130,11 @@ export function camelToDash(string) {
130
130
  * @returns {string}
131
131
  */
132
132
  export function decodeAttribute(attribute) {
133
- return `${attribute}`
134
- .replace(/&lt;/g, '<')
135
- .replace(/&gt;/g, '>')
136
- .replace(/&quot;/g, '"')
137
- .replace(/&apos;/g, "'");
133
+ return `${attribute}`
134
+ .replace(/&lt;/g, '<')
135
+ .replace(/&gt;/g, '>')
136
+ .replace(/&quot;/g, '"')
137
+ .replace(/&apos;/g, "'");
138
138
  }
139
139
 
140
140
  /**
@@ -143,11 +143,11 @@ export function decodeAttribute(attribute) {
143
143
  * @returns {string}
144
144
  */
145
145
  export function encodeAttribute(attribute) {
146
- return `${attribute}`
147
- .replace(/'/g, '&apos;')
148
- .replace(/"/g, '&quot;')
149
- .replace(/</g, '&lt;')
150
- .replace(/>/g, '&gt;')
151
- .replace(/\r\n/g, '\n')
152
- .replace(/[\r\n]/g, '\n');
146
+ return `${attribute}`
147
+ .replace(/'/g, '&apos;')
148
+ .replace(/"/g, '&quot;')
149
+ .replace(/</g, '&lt;')
150
+ .replace(/>/g, '&gt;')
151
+ .replace(/\r\n/g, '\n')
152
+ .replace(/[\r\n]/g, '\n');
153
153
  }