@webtides/element-js 1.0.0 → 1.1.0

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 (41) hide show
  1. package/CHANGELOG.md +140 -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 +631 -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 +56 -53
  38. package/src/util/SerializeStateHelper.js +56 -56
  39. package/src/util/Store.js +174 -174
  40. package/src/util/defineElement.js +5 -5
  41. package/src/util/toString.js +5 -5
@@ -1,14 +1,8 @@
1
1
  #### Shared State / Reactive State / Store
2
2
 
3
- When a certain property in the properties map is an instance of the provided class `Store` it is treated as an
4
- external `Store`.
5
- A `Store` can be created outside a components´ scope or in a dedicated Module to keep track of shared Application
6
- State.
7
- When a components´ property is an instance of a `Store` the instance will be added as an observer and
8
- automatically updated when the store ist changed.
3
+ When a certain property in the properties map is an instance of the provided class `Store` it is treated as an external `Store`. A `Store` can be created outside a components´ scope or in a dedicated Module to keep track of shared Application State. When a components´ property is an instance of a `Store` the instance will be added as an observer and automatically updated when the store ist changed.
9
4
 
10
- Instances of Store provide a way to share global state between as many components in an application as you like.
11
- Shared State can be something very simple as (updated) Viewport Dimensions, Media Changes or complex fetched data from a REST Endpoint.
5
+ Instances of Store provide a way to share global state between as many components in an application as you like. Shared State can be something very simple as (updated) Viewport Dimensions, Media Changes or complex fetched data from a REST Endpoint.
12
6
 
13
7
  Stores can also be initialized with a primitive value (Numbers, Booleans). Such stores will switch to a single Property Mode and provide direct access tio the property value via the stores valueOf / toString Function for direct access.
14
8
 
@@ -16,53 +10,49 @@ Stores can also be initialized with a primitive value (Numbers, Booleans). Such
16
10
 
17
11
  ```js
18
12
  export const simpleStore = new Store({
19
- value: 'simple'
20
- })
13
+ value: 'simple'
14
+ });
21
15
 
22
16
  class MoreComplexStore extends Store {
23
- properties() {
24
- return {
25
- storeCount: 1
26
- };
27
- }
28
- get sum() {
29
- return this.storeCount + this.argumentCount;
30
- }
17
+ properties() {
18
+ return {
19
+ storeCount: 1
20
+ };
21
+ }
22
+ get sum() {
23
+ return this.storeCount + this.argumentCount;
24
+ }
31
25
  }
32
26
  export const exampleStore = new MoreComplexStore({
33
- argumentCount: 1
34
- })
35
-
27
+ argumentCount: 1
28
+ });
36
29
 
37
30
  class MediaStore extends Store {
38
- constructor() {
39
- super();
40
- const mql = window.matchMedia('(max-width: 600px)');
41
- mql.onchange = (e) => {
42
- this.isMobile = e.matches;
43
- }
44
- }
45
- properties() {
46
- return {
47
- isMobile: true
48
- };
49
- }
31
+ constructor() {
32
+ super();
33
+ const mql = window.matchMedia('(max-width: 600px)');
34
+ mql.onchange = (e) => {
35
+ this.isMobile = e.matches;
36
+ };
37
+ }
38
+ properties() {
39
+ return {
40
+ isMobile: true
41
+ };
42
+ }
50
43
  }
51
- export const mediaStore = new MediaStore()
44
+ export const mediaStore = new MediaStore();
52
45
 
53
- export const scrollYStore = new Store(0)
46
+ export const scrollYStore = new Store(0);
54
47
  window.addEventListener('scroll', () => {
55
- scrollYStore.value = window.scrollY;
48
+ scrollYStore.value = window.scrollY;
56
49
  });
57
-
58
-
59
50
  ```
60
51
 
61
52
  ##### MyElement.js
62
53
 
63
- ```js
64
-
65
- import { exampleStore, simpleStore, mediaStore } from './store.js';
54
+ ```js
55
+ import { exampleStore, simpleStore, mediaStore } from './store.js';
66
56
 
67
57
  export class MyElement extends TemplateElement {
68
58
  properties() {
@@ -78,66 +68,61 @@ export class MyElement extends TemplateElement {
78
68
  return `
79
69
  <div>Simple Store: ${this.store.value} == "simple" </div>
80
70
  <div>Complex Store: ${this.exampleStore.sum} == 2</div>
81
- <div>Media: ${this.mediaStore.isMobile ? "MOBILE" : "DESKTOP"}</div>
71
+ <div>Media: ${this.mediaStore.isMobile ? 'MOBILE' : 'DESKTOP'}</div>
82
72
  <div>Scoll Position: ${this.scrollYStore}</div>
83
- `
73
+ `;
84
74
  }
85
75
  }
86
76
 
87
77
  export class AnotherElement extends TemplateElement {
88
78
  properties() {
89
79
  return {
90
- store: simpleStore,
80
+ store: simpleStore
91
81
  };
92
82
  }
93
83
 
94
84
  template() {
95
85
  return `
96
86
  <div>Simple Store: ${this.store.value} == the same as in MyElement's template.</div>
97
- `
87
+ `;
98
88
  }
99
89
  }
100
-
101
- ```
102
-
90
+ ```
103
91
 
104
92
  ##### storeception
105
93
 
106
94
  Changes too nested stores will trigger updates and watcher in ParentStore and ultimately transfer to all elements referencing the parent
107
95
 
108
- ```js
109
-
96
+ ```js
110
97
  class NestedStore extends Store {
111
- properties() {
112
- return {
113
- nestedCount: 0,
114
- };
115
- }
98
+ properties() {
99
+ return {
100
+ nestedCount: 0
101
+ };
102
+ }
116
103
  }
117
104
 
118
105
  class ParentStore extends Store {
119
- properties() {
120
- return {
121
- count: 0,
122
- nestedStore: new NestedStore(),
123
- };
124
- }
125
-
126
- requestUpdate() {
127
- // will be triggered on count OR nestedStore.nestedCount changes
128
- }
129
-
130
- watch() {
131
- return {
132
- count: (newCount, oldCount) => {
133
- // will be triggered on count changes
134
- },
135
- nestedStore: () => {
136
- // will be triggered on nestedStore changes
137
- },
138
- };
139
- }
140
- }
106
+ properties() {
107
+ return {
108
+ count: 0,
109
+ nestedStore: new NestedStore()
110
+ };
111
+ }
141
112
 
142
- ```
113
+ requestUpdate() {
114
+ // will be triggered on count OR nestedStore.nestedCount changes
115
+ }
143
116
 
117
+ watch() {
118
+ return {
119
+ count: (newCount, oldCount) => {
120
+ // will be triggered on count changes
121
+ },
122
+ nestedStore: () => {
123
+ // will be triggered on nestedStore changes
124
+ }
125
+ };
126
+ }
127
+ }
128
+ ```
@@ -135,7 +135,9 @@ Returns:
135
135
 
136
136
  **spreadAttributes(attributes)**
137
137
 
138
- Renders multiple attributes as key="value" pairs from a map of attributes.
138
+ Renders multiple attributes as key="value" pairs from a map of attributes.
139
+ Be aware that `spreadAttributes` only works with the given values and does not remember prior iterations.
140
+ Therefore, it will only add / remove values that are present in the given map.
139
141
 
140
142
  Parameters:
141
143
 
@@ -152,6 +154,7 @@ const attrs = { id: 'myElement', ariaLabel: 'Description' };
152
154
  const attributeString = spreadAttributes(attrs);
153
155
  // When invoked, returns: "id='myElement' aria-label='Description'"
154
156
  ```
157
+
155
158
 
156
159
  ##### Writing custom directives
157
160
 
@@ -1,54 +1,47 @@
1
-
2
1
  ### Watch Changes
3
2
 
4
- Alongside defining reactive properties in the `properties()` map, you can define a watcher for every property by
5
- implementing a `watch()` map in the same manner.
3
+ Alongside defining reactive properties in the `properties()` map, you can define a watcher for every property by implementing a `watch()` map in the same manner.
6
4
 
7
5
  ```javascript
8
6
  export class MyElement extends BaseElement {
9
- properties() {
10
- return {
11
- property: 'I am public & reactive'
12
- };
13
- }
14
-
15
- watch() {
16
- return {
17
- property: (newValue, oldValue) => {
18
- console.log('property changed', {newValue, oldValue});
19
- }
20
- };
21
- }
7
+ properties() {
8
+ return {
9
+ property: 'I am public & reactive'
10
+ };
11
+ }
12
+
13
+ watch() {
14
+ return {
15
+ property: (newValue, oldValue) => {
16
+ console.log('property changed', { newValue, oldValue });
17
+ }
18
+ };
19
+ }
22
20
  }
23
21
  ```
24
22
 
25
- Whenever a property is changed (either from within the element or from outside) it will trigger the callback defined in
26
- the `watch()` map if present. The callback will be invoked with the old value and the new value as parameters.
23
+ Whenever a property is changed (either from within the element or from outside) it will trigger the callback defined in the `watch()` map if present. The callback will be invoked with the old value and the new value as parameters.
27
24
 
28
- _element-js_ compares values by stringifying them rather than by reference and will only trigger update/render cycles if
29
- the value actually changed.
25
+ _element-js_ compares values by stringifying them rather than by reference and will only trigger update/render cycles if the value actually changed.
30
26
 
31
- > `array` or `object` data will not trigger changes if nested elements/keys are changed. Also methods like `push()`
32
- > and `shift()` won't work.
27
+ > `array` or `object` data will not trigger changes if nested elements/keys are changed. Also methods like `push()` and `shift()` won't work.
33
28
 
34
- Non-mutable operations like `map()` and `shift()` and the spread operator will however change the value and trigger an
35
- update cycle.
29
+ Non-mutable operations like `map()` and `shift()` and the spread operator will however change the value and trigger an update cycle.
36
30
 
37
31
  ```javascript
38
32
  export class MyElement extends BaseElement {
39
- properties() {
40
- return {
41
- items: ['one', 'two']
42
- };
43
- }
44
-
45
- count() {
46
- this.items = [...this.items, 'three'];
47
- }
33
+ properties() {
34
+ return {
35
+ items: ['one', 'two']
36
+ };
37
+ }
38
+
39
+ count() {
40
+ this.items = [...this.items, 'three'];
41
+ }
48
42
  }
49
43
  ```
50
44
 
51
45
  The spread operator works for objects and arrays.
52
46
 
53
- If you want to make sure that an update will be triggered you can always request it manually by calling
54
- the `requestUpdate()` method.
47
+ If you want to make sure that an update will be triggered you can always request it manually by calling the `requestUpdate()` method.
@@ -75,6 +75,22 @@ In both cases you must now change the imports to our new directives:
75
75
  import { unsafeHTML } from '@webtides/element-js/src/dom-parts/directives';
76
76
  ```
77
77
 
78
+ ### element-js@1.1+
79
+
80
+ since 1.1 its also possible to import directives directly from the modules index
81
+
82
+ ```javascript
83
+ import {
84
+ classMap,
85
+ styleMap,
86
+ when,
87
+ choose,
88
+ unsafeHTML,
89
+ spreadAttributes,
90
+ optionalAttribute
91
+ } from '@webtides/element-js';
92
+ ```
93
+
78
94
  ## Removed childListUpdate option from constructor options
79
95
 
80
96
  We removed the deprecated `BaseElement` constructor option `childListUpdate`. Please use the "mutationObserverOptions" dictionary instead.
@@ -1,4 +1,3 @@
1
-
2
1
  ## Guides/Tooling
3
2
 
4
3
  ### Frontend Stack
@@ -13,10 +12,9 @@ This is an element style guide created and enforced for the purpose of standardi
13
12
 
14
13
  #### File structure
15
14
 
16
- - One element per file.
17
- - One element per directory. Though it may make sense to group similar elements into the same directory, we've found it'
18
- s easier to consume and document elements when each one has its own directory.
19
- - Implementation (.js) and styles (.css) of an element should live in the same directory.
15
+ - One element per file.
16
+ - One element per directory. Though it may make sense to group similar elements into the same directory, we've found it' s easier to consume and document elements when each one has its own directory.
17
+ - Implementation (.js) and styles (.css) of an element should live in the same directory.
20
18
 
21
19
  Example:
22
20
 
@@ -39,16 +37,12 @@ Example:
39
37
 
40
38
  ##### Name
41
39
 
42
- Elements are not actions, they are conceptually "things". It is better to use nouns, instead of verbs, such us: "
43
- animation" instead of "animating". "input", "tab", "nav", "menu" are some examples.
40
+ Elements are not actions, they are conceptually "things". It is better to use nouns, instead of verbs, such us: " animation" instead of "animating". "input", "tab", "nav", "menu" are some examples.
44
41
 
45
42
  ##### -element postfix
46
43
 
47
- The naming has a major role when you are creating a collection of elements intended to be used across different
48
- projects. Web Components are not scoped because they are globally declared within the page, which means a "unique" name
49
- is needed to prevent collisions.
50
- Additionally, web components are required to contain a "-" dash within the tag name. When using the first section to
51
- namespace your components - everything will look the same, and it will be hard to distinguish elements.
44
+ The naming has a major role when you are creating a collection of elements intended to be used across different projects. Web Components are not scoped because they are globally declared within the page, which means a "unique" name is needed to prevent collisions.
45
+ Additionally, web components are required to contain a "-" dash within the tag name. When using the first section to namespace your components - everything will look the same, and it will be hard to distinguish elements.
52
46
 
53
47
  DO NOT do this:
54
48
 
@@ -72,8 +66,7 @@ card-element
72
66
 
73
67
  ##### Modifiers
74
68
 
75
- When several elements are related and/or coupled, it is a good idea to share the name, and then add different modifiers,
76
- for example:
69
+ When several elements are related and/or coupled, it is a good idea to share the name, and then add different modifiers, for example:
77
70
 
78
71
  ```
79
72
  menu-element
@@ -91,13 +84,11 @@ card-content
91
84
  The name of the ES6 class of the element should reflect the file name, and the html tag name.
92
85
 
93
86
  ```js
94
- export class ButtonElement extends BaseElement {
95
- }
87
+ export class ButtonElement extends BaseElement {}
96
88
 
97
89
  customElements.define('button-element', ButtonElement);
98
90
 
99
- export class MenuElement extends BaseElement {
100
- }
91
+ export class MenuElement extends BaseElement {}
101
92
 
102
93
  customElements.define('menu-element', MenuElement);
103
94
  ```
@@ -106,7 +97,4 @@ customElements.define('menu-element', MenuElement);
106
97
 
107
98
  ##### Newspaper Metaphor from The Robert C. Martin's _Clean Code_
108
99
 
109
- > The source file should be organized like a newspaper article, with the highest level summary at the top, and more and
110
- > more details further down. Functions called from the top function come directly below it, and so on down to the lowest
111
- > level, and most detailed functions at the bottom. This is a good way to organize the source code, even though IDE:s make
112
- > the location of functions less important, since it is so easy to navigate in and out of them.
100
+ > The source file should be organized like a newspaper article, with the highest level summary at the top, and more and more details further down. Functions called from the top function come directly below it, and so on down to the lowest level, and most detailed functions at the bottom. This is a good way to organize the source code, even though IDE:s make the location of functions less important, since it is so easy to navigate in and out of them.
package/index.js CHANGED
@@ -4,7 +4,17 @@ import { TemplateElement, html } from './src/TemplateElement.js';
4
4
  import { toString } from './src/util/toString.js';
5
5
  import { defineElement } from './src/util/defineElement.js';
6
6
  import { Store } from './src/util/Store.js';
7
- import { Directive } from './src/util/Directive.js';
7
+ import {
8
+ defineDirective,
9
+ Directive,
10
+ classMap,
11
+ styleMap,
12
+ when,
13
+ choose,
14
+ unsafeHTML,
15
+ spreadAttributes,
16
+ optionalAttribute,
17
+ } from './src/dom-parts/directives.js';
8
18
 
9
19
  /**
10
20
  * Options object for element-js
@@ -13,4 +23,21 @@ import { Directive } from './src/util/Directive.js';
13
23
  * @property {boolean} [observeGlobalStyles] - By default element-js will look for all style elements in the global document and apply them before any custom/element styles inside the shadow DOM. When set to true element-js will also observe the document for any changes regarding styles. This is needed if styles will get added async or late.
14
24
  */
15
25
 
16
- export { BaseElement, StyledElement, TemplateElement, Store, Directive, html, toString, defineElement };
26
+ export {
27
+ BaseElement,
28
+ StyledElement,
29
+ TemplateElement,
30
+ Store,
31
+ html,
32
+ toString,
33
+ defineElement,
34
+ defineDirective,
35
+ Directive,
36
+ classMap,
37
+ styleMap,
38
+ when,
39
+ choose,
40
+ unsafeHTML,
41
+ spreadAttributes,
42
+ optionalAttribute,
43
+ };
package/package.json CHANGED
@@ -1,64 +1,64 @@
1
1
  {
2
- "name": "@webtides/element-js",
3
- "version": "1.0.0",
4
- "module": "index.js",
5
- "main": "index.js",
6
- "type": "module",
7
- "files": [
8
- "/docs",
9
- "/src",
10
- "index.js"
11
- ],
12
- "exports": {
13
- ".": {
14
- "browser": "./index.js",
15
- "import": "./index.js",
16
- "default": "./index.js"
17
- },
18
- "./src/*": "./src/*.js",
19
- "./src/util/*": "./src/util/*.js",
20
- "./src/dom-parts/*": "./src/dom-parts/*.js"
21
- },
22
- "repository": "https://github.com/webtides/element-js.git",
23
- "author": "@webtides",
24
- "license": "MIT",
25
- "keywords": [
26
- "web components",
27
- "web component",
28
- "components",
29
- "component",
30
- "custom elements",
31
- "elements",
32
- "element",
33
- "element-js",
34
- "shadow-dom"
35
- ],
36
- "dependencies": {},
37
- "devDependencies": {
38
- "@babel/eslint-parser": "^7.23.3",
39
- "@open-wc/testing": "^4.0.0",
40
- "@web/dev-server": "^0.4.1",
41
- "@web/test-runner": "^0.14.1",
42
- "babel-eslint": "^10.1.0",
43
- "eslint": "8.55.0",
44
- "eslint-config-prettier": "^9.1.0",
45
- "eslint-plugin-import": "^2.29.0",
46
- "eslint-plugin-prettier": "^5.0.1",
47
- "husky": "^8.0.3",
48
- "lint-staged": "^15.1.0",
49
- "prettier": "^3.1.0"
50
- },
51
- "lint-staged": {
52
- "*.{js,json}": [
53
- "prettier --write",
54
- "git add"
55
- ]
56
- },
57
- "scripts": {
58
- "lint": "eslint index.js 'src/**/*.js'",
59
- "test": "web-test-runner test/**/*.test.{html,js} --node-resolve",
60
- "test:coverage": "web-test-runner test/**/*.test.{html,js} --node-resolve --coverage",
61
- "test:watch": "web-test-runner test/**/*.test.{html,js} --node-resolve --watch",
62
- "start": "web-dev-server --config web-dev-server.config.mjs"
63
- }
2
+ "name": "@webtides/element-js",
3
+ "version": "1.1.0",
4
+ "module": "index.js",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "files": [
8
+ "/docs",
9
+ "/src",
10
+ "index.js"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "browser": "./index.js",
15
+ "import": "./index.js",
16
+ "default": "./index.js"
17
+ },
18
+ "./src/*": "./src/*.js",
19
+ "./src/util/*": "./src/util/*.js",
20
+ "./src/dom-parts/*": "./src/dom-parts/*.js"
21
+ },
22
+ "repository": "https://github.com/webtides/element-js.git",
23
+ "author": "@webtides",
24
+ "license": "MIT",
25
+ "keywords": [
26
+ "web components",
27
+ "web component",
28
+ "components",
29
+ "component",
30
+ "custom elements",
31
+ "elements",
32
+ "element",
33
+ "element-js",
34
+ "shadow-dom"
35
+ ],
36
+ "dependencies": {},
37
+ "devDependencies": {
38
+ "@babel/eslint-parser": "^7.23.3",
39
+ "@open-wc/testing": "^4.0.0",
40
+ "@web/dev-server": "^0.4.1",
41
+ "@web/test-runner": "^0.14.1",
42
+ "babel-eslint": "^10.1.0",
43
+ "eslint": "8.55.0",
44
+ "eslint-config-prettier": "^9.1.0",
45
+ "eslint-plugin-import": "^2.29.0",
46
+ "eslint-plugin-prettier": "^5.0.1",
47
+ "husky": "^8.0.3",
48
+ "lint-staged": "^15.1.0",
49
+ "prettier": "^3.1.0"
50
+ },
51
+ "lint-staged": {
52
+ "*.{js,json}": [
53
+ "prettier --write",
54
+ "git add"
55
+ ]
56
+ },
57
+ "scripts": {
58
+ "lint": "eslint index.js 'src/**/*.js'",
59
+ "test": "web-test-runner test/**/*.test.{html,js} --node-resolve",
60
+ "test:coverage": "web-test-runner test/**/*.test.{html,js} --node-resolve --coverage",
61
+ "test:watch": "web-test-runner test/**/*.test.{html,js} --node-resolve --watch",
62
+ "start": "web-dev-server --config web-dev-server.config.mjs"
63
+ }
64
64
  }