mi-element 0.6.7 → 0.8.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.
package/README.md CHANGED
@@ -24,7 +24,7 @@ setting objects or functions either through `el.setAttribute(name, value)` or
24
24
  properties `el[name] = value`.
25
25
 
26
26
  Furthermore all observed attributes have a reactive behavior through the use of
27
- signals and effects (loosely) following the
27
+ signals and effects (loosely) following the
28
28
  [TC39 JavaScript Signals standard proposal][].
29
29
 
30
30
  # Usage
@@ -38,7 +38,8 @@ npm i mi-element
38
38
  ```js
39
39
  /** @file ./mi-counter.js */
40
40
 
41
- import { MiElement, define, refsById, Signal } from 'mi-element'
41
+ import { MiElement, define, Signal } from 'mi-element'
42
+ const { effect, createSignal } = Signal
42
43
 
43
44
  // define your Component
44
45
  class MiCounter extends MiElement {
@@ -46,25 +47,27 @@ class MiCounter extends MiElement {
46
47
  <style>
47
48
  :host { font-size: 1.25rem; }
48
49
  </style>
49
- <div id aria-label="Counter value">0</div>
50
- <button id="increment" aria-label="Increment counter"> + </button>
50
+ <div aria-label="Counter value">0</div>
51
+ <button aria-label="Increment counter"> + </button>
51
52
  `
52
53
 
53
- static get attributes() {
54
+ static get properties() {
54
55
  // declare reactive attribute(s)
55
- return { count: 0 }
56
+ return { count: { type: Number, initial: 0 } }
56
57
  }
57
58
 
59
+ static createSignal = createSignal
60
+
58
61
  // called by connectedCallback()
59
62
  render() {
60
63
  // gather refs from template (here by id)
61
- this.refs = refsById(this.renderRoot)
64
+ this.refs = this.refsBySelector({ increment: 'button', div: 'div' })
62
65
  // apply event listeners
63
66
  this.refs.increment.addEventListener('click', () => {
64
67
  // change observed and reactive attribute...
65
68
  this.count++
66
69
  })
67
- Signal.effect(() => {
70
+ effect(() => {
68
71
  // ...triggers update on every change of `this.count`
69
72
  this.refs.div.textContent = this.count
70
73
  })
@@ -91,6 +94,7 @@ In `./example` you'll find a working sample of a Todo App. Check it out with
91
94
  # Documentation
92
95
 
93
96
  - [element][docs-element] mi-element's lifecycle
97
+ - [render][docs-render] mi-elements html template literal and render function
94
98
  - [controller][docs-controller] adding controllers to mi-element to hook into the lifecycle
95
99
  - [signal][docs-signal] Signals and effect for reactive behavior
96
100
  - [store][docs-store] Manage shared state in an application
@@ -103,6 +107,7 @@ In `./example` you'll find a working sample of a Todo App. Check it out with
103
107
  MIT licensed
104
108
 
105
109
  [docs-element]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/element.md
110
+ [docs-render]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/render.md
106
111
  [docs-controller]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/controller.md
107
112
  [docs-context]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/context.md
108
113
  [docs-signal]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/signal.md
@@ -114,7 +119,6 @@ MIT licensed
114
119
  [Web Components]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks
115
120
  [TC39 JavaScript Signals standard proposal]: https://github.com/tc39/proposal-signals
116
121
  [krausest/js-framework-benchmark]: https://github.com/krausest/js-framework-benchmark
117
-
118
122
  [npm-badge]: https://badgen.net/npm/v/mi-element
119
123
  [npm]: https://www.npmjs.com/package/mi-element
120
124
  [types-badge]: https://badgen.net/npm/types/mi-element
package/dist/context.js CHANGED
@@ -17,6 +17,12 @@ class ContextProvider {
17
17
  get() {
18
18
  return this.state.get();
19
19
  }
20
+ set value(newValue) {
21
+ this.set(newValue);
22
+ }
23
+ get value() {
24
+ return this.get();
25
+ }
20
26
  onContextRequest=ev => {
21
27
  if (ev.context !== this.context) return;
22
28
  let unsubscribe;
package/dist/element.js CHANGED
@@ -1,86 +1,82 @@
1
- import { camelToKebabCase } from './case.js';
2
-
3
1
  import { createSignal } from 'mi-signal';
4
2
 
3
+ import { kebabToCamelCase, camelToKebabCase } from './case.js';
4
+
5
+ import { addGlobalStyles } from './styling.js';
6
+
7
+ import { refsBySelector } from './refs.js';
8
+
9
+ import { toNumber, toJson } from './utils.js';
10
+
11
+ const nameMap = {
12
+ class: 'className',
13
+ for: 'htmlFor'
14
+ };
15
+
5
16
  class MiElement extends HTMLElement {
6
- #attr={};
7
- #attrLc=new Map;
8
- #types=new Map;
17
+ _props={};
18
+ #changedProps={};
9
19
  #disposers=new Set;
10
20
  #controllers=new Set;
11
- #changedAttr={};
12
- #dedupe=!1;
13
- static shadowRootOptions={
14
- mode: 'open'
15
- };
16
- static template;
17
- static get attributes() {
18
- return {};
21
+ #updateRequested=!1;
22
+ static get shadowRootInit() {
23
+ return {
24
+ mode: 'open'
25
+ };
19
26
  }
20
- static get properties() {
21
- return {};
27
+ static template;
28
+ static get properties() {}
29
+ static observedAttributes=[];
30
+ static styles='';
31
+ static get useGlobalStyles() {
32
+ return !1;
22
33
  }
34
+ static createSignal=createSignal;
23
35
  constructor() {
24
- super(), this.#observedAttributes(this.constructor.attributes), this.#observedProperties(this.constructor.properties);
25
- }
26
- #observe(name, initialValue) {
27
- this.#attr[name] = createSignal(initialValue), Object.defineProperty(this, name, {
28
- enumerable: !0,
29
- get() {
30
- return this.#attr[name].get();
31
- },
32
- set(newValue) {
33
- const oldValue = this.#attr[name].get();
34
- oldValue !== newValue && (this.#attr[name].set(newValue), this.#changedAttr[name] = oldValue,
35
- this.requestUpdate());
36
- }
37
- });
38
- }
39
- #observedAttributes(attributes = {}) {
40
- for (const [name, value] of Object.entries(attributes)) {
41
- const initial = initialValueType(value);
42
- this.#types.set(name, initial.type), this.#attrLc.set(name.toLowerCase(), name),
43
- this.#attrLc.set(camelToKebabCase(name), name), this.#observe(name, initial.value);
36
+ super();
37
+ const {createSignal: createSignal, properties: properties} = this.constructor;
38
+ for (const [name, {initial: initial}] of Object.entries(properties)) {
39
+ const descriptor = Object.getOwnPropertyDescriptor(this.constructor.prototype, name);
40
+ createSignal && (this._props[name] = createSignal()), Object.defineProperty(this, name, {
41
+ get() {
42
+ return descriptor?.get ? descriptor.get.call(this) : createSignal ? this._props[name].value : this._props[name];
43
+ },
44
+ set(value) {
45
+ const oldValue = this[name];
46
+ descriptor?.set ? descriptor.set.call(this, value) : createSignal ? this._props[name].value = value : this._props[name] = value,
47
+ oldValue !== this[name] && this.requestUpdate({
48
+ [name]: value
49
+ });
50
+ }
51
+ }), this[name] = initial;
44
52
  }
45
53
  }
46
- #observedProperties(properties = {}) {
47
- for (const [name, value] of Object.entries(properties)) this.#attrLc.has(name) || name in this.#attr || this.#observe(name, value);
48
- }
49
- #getName(name) {
50
- return this.#attrLc.get(name) || name;
51
- }
52
- #getType(name) {
53
- return this.#types.get(name);
54
- }
55
54
  connectedCallback() {
56
55
  this.#controllers.forEach(controller => controller.hostConnected?.());
57
- const {shadowRootOptions: shadowRootOptions, template: template} = this.constructor;
58
- this.renderRoot = shadowRootOptions ? this.shadowRoot ?? this.attachShadow(shadowRootOptions) : this,
59
- this.addTemplate(template), this.render(), this.requestUpdate();
56
+ const {shadowRootInit: shadowRootInit, useGlobalStyles: useGlobalStyles, template: template} = this.constructor;
57
+ this.renderRoot = shadowRootInit ? this.shadowRoot ?? this.attachShadow(shadowRootInit) : this,
58
+ this.addTemplate(template), useGlobalStyles && addGlobalStyles(this.renderRoot),
59
+ this.render(), this.requestUpdate();
60
60
  }
61
61
  disconnectedCallback() {
62
62
  this.#disposers.forEach(remover => remover()), this.#controllers.forEach(controller => controller.hostDisconnected?.());
63
63
  }
64
- attributeChangedCallback(name, oldValue, newValue) {
65
- const attr = this.#getName(name), type = this.#getType(attr);
66
- this.#changedAttr[attr] = this[attr], this[attr] = convertType(newValue, type),
67
- 'Boolean' === type && 'false' === newValue && this.removeAttribute(name), this.requestUpdate();
68
- }
69
- setAttribute(name, newValue) {
70
- const attr = this.#getName(name);
71
- if (!(attr in this.#attr)) return;
72
- const type = this.#getType(attr);
73
- 'Boolean' === type ? !0 === newValue || '' === newValue ? super.setAttribute(name, '') : super.removeAttribute(name) : [ 'String', 'Number' ].includes(type ?? '') || !0 === newValue ? super.setAttribute(name, newValue) : (this.#changedAttr[attr] = this[attr],
74
- this[attr] = newValue, this.requestUpdate());
75
- }
76
- shouldUpdate(_changedAttributes) {
77
- return !0;
78
- }
79
- requestUpdate() {
80
- !this.#dedupe && this.isConnected && (this.#dedupe = !0, requestAnimationFrame(() => {
81
- this.#dedupe = !1;
82
- const _changedAttributes = this.#changedAttr;
83
- this.#changedAttr = {}, this.shouldUpdate(_changedAttributes) && this.update(_changedAttributes);
64
+ attributeChangedCallback(name, _oldValue, newValue) {
65
+ const camelName = nameMap[name] ?? kebabToCamelCase(name), properties = this.constructor?.properties, {type: type} = properties?.[camelName] ?? {}, coercedValue = convertType(newValue, type);
66
+ if (name.startsWith('data-')) {
67
+ const datasetName = kebabToCamelCase(name.substring(5));
68
+ datasetName && (this.dataset[datasetName] = coercedValue);
69
+ }
70
+ this[camelName] = coercedValue;
71
+ }
72
+ requestUpdate(changedProps) {
73
+ this.#changedProps = {
74
+ ...this.#changedProps,
75
+ ...changedProps
76
+ }, !this.#updateRequested && this.renderRoot && (this.#updateRequested = !0, window.requestAnimationFrame(() => {
77
+ this.#updateRequested = !1;
78
+ const changedProps = this.#changedProps;
79
+ this.#changedProps = {}, this.update(changedProps);
84
80
  }));
85
81
  }
86
82
  addTemplate(template) {
@@ -90,7 +86,7 @@ class MiElement extends HTMLElement {
90
86
  }
91
87
  }
92
88
  render() {}
93
- update(_changedAttributes) {}
89
+ update(_changedProps) {}
94
90
  on(eventName, listener, node = this) {
95
91
  node.addEventListener(eventName, listener), this.#disposers.add(() => node.removeEventListener(eventName, listener));
96
92
  }
@@ -111,53 +107,33 @@ class MiElement extends HTMLElement {
111
107
  removeController(controller) {
112
108
  this.#controllers.delete(controller);
113
109
  }
110
+ refsBySelector(selectors) {
111
+ return refsBySelector(this.renderRoot, selectors);
112
+ }
114
113
  }
115
114
 
116
- const define = (name, element, options) => {
117
- element.observedAttributes = (element.observedAttributes || Object.keys(element.attributes || [])).map(attr => attr.toLowerCase()),
118
- renderTemplate(element), window.customElements.define(name, element, options);
115
+ const define = (tagName, elementClass, options) => {
116
+ if (customElements.get(tagName)) return;
117
+ const {usedCssPrefix: usedCssPrefix = "", cssPrefix: cssPrefix = "", styles: styles} = options || {};
118
+ if (elementClass.properties) {
119
+ const observedAttrs = [];
120
+ for (const [name, {attribute: attribute = !0}] of Object.entries(elementClass.properties)) attribute && observedAttrs.push(camelToKebabCase(name));
121
+ Object.defineProperty(elementClass, 'observedAttributes', {
122
+ get: () => observedAttrs
123
+ });
124
+ } else if (elementClass.observedAttributes) {
125
+ const properties = elementClass.observedAttributes.reduce((acc, attr) => (acc[kebabToCamelCase(attr)] = {},
126
+ acc), {});
127
+ Object.defineProperty(elementClass, 'properties', {
128
+ get: () => properties
129
+ });
130
+ }
131
+ elementClass.styles && (elementClass.styles = styles || (usedCssPrefix === cssPrefix ? elementClass.styles : elementClass.styles.replaceAll(`--${usedCssPrefix}-`, cssPrefix))),
132
+ renderTemplate(elementClass), window.customElements.define(tagName, elementClass);
119
133
  }, renderTemplate = element => {
120
- if (element.template instanceof HTMLTemplateElement) return;
134
+ if (!element.template || element.template instanceof HTMLTemplateElement) return;
121
135
  const el = document.createElement('template');
122
- el.innerHTML = element.template, element.template = el;
123
- }, initialValueType = value => {
124
- switch (value) {
125
- case Boolean:
126
- return {
127
- value: void 0,
128
- type: 'Boolean'
129
- };
130
-
131
- case Number:
132
- return {
133
- value: void 0,
134
- type: 'Number'
135
- };
136
-
137
- case String:
138
- return {
139
- value: void 0,
140
- type: 'String'
141
- };
142
-
143
- default:
144
- return {
145
- value: value,
146
- type: toString.call(value).slice(8, -1)
147
- };
148
- }
149
- }, convertType = (any, type) => {
150
- switch (type) {
151
- case 'Number':
152
- return (any => {
153
- const n = Number(any);
154
- return isNaN(n) ? any : n;
155
- })(any);
156
-
157
- case 'Boolean':
158
- return 'false' !== any && ('' === any || !!any);
159
- }
160
- return any;
161
- };
136
+ el.innerHTML = element.template || '', element.template = el;
137
+ }, convertType = (value, type) => type === Boolean ? null !== value : type === Number ? toNumber(value) : type === Array ? toJson(value) ?? value.split(',').map(v => v.trim()) : type === Object ? toJson(value) : value;
162
138
 
163
139
  export { MiElement, convertType, define };
package/dist/html.js ADDED
@@ -0,0 +1,74 @@
1
+ import { toJson } from './utils.js';
2
+
3
+ const globalRenderCache = new class {
4
+ cnt=0;
5
+ map=new Map;
6
+ cache=new WeakMap;
7
+ _inc() {
8
+ return this.cnt = 268435455 & ++this.cnt, this.cnt;
9
+ }
10
+ clear() {
11
+ this.cnt = 0, this.map.clear();
12
+ }
13
+ set(value) {
14
+ const key = '__rc:' + this._inc().toString(36), ref = {};
15
+ return this.map.set(key, ref), this.cache.set(ref, value), key;
16
+ }
17
+ get(key) {
18
+ const ref = this.map.get(key);
19
+ return this.map.delete(key), this.cache.get(ref);
20
+ }
21
+ };
22
+
23
+ class UnsafeHtml extends String {}
24
+
25
+ const unsafeHtml = str => new UnsafeHtml(str), escMap = {
26
+ '&': '&amp;',
27
+ '<': '&lt;',
28
+ '>': '&gt;',
29
+ '"': '&quot;',
30
+ "'": '&#39;'
31
+ }, esc = string => string.replace(/[&<>"']/g, tag => escMap[tag]), escHtml = string => string instanceof UnsafeHtml ? string : unsafeHtml(esc('' + string)), escValue = any => {
32
+ if (any instanceof UnsafeHtml) return any;
33
+ if ([ 'object', 'function' ].includes(typeof any)) {
34
+ const key = globalRenderCache.set(any);
35
+ return unsafeHtml(key);
36
+ }
37
+ return unsafeHtml(esc('' + any));
38
+ }, html = (strings, ...values) => unsafeHtml(String.raw({
39
+ raw: strings
40
+ }, ...values.map(val => Array.isArray(val) ? val.map(escValue).join('') : escValue(val))));
41
+
42
+ function render(node, template, handlers = {}) {
43
+ const refs = {}, div = document.createElement('div');
44
+ div.innerHTML = template.toString();
45
+ for (let child of Array.from(div.children)) renderAttrs(child, handlers, refs),
46
+ node.appendChild(child);
47
+ return refs;
48
+ }
49
+
50
+ function renderAttrs(node, handlers = {}, refs = {}) {
51
+ if (node.nodeType === Node.ELEMENT_NODE) for (let attr of node.attributes) {
52
+ const startsWith = attr.name[0], name = attr.name.slice(1);
53
+ let rm = 0;
54
+ if ('?' === startsWith) toJson(attr.value) ? node.setAttribute(name, '') : node.removeAttribute(name),
55
+ rm = 1; else if ('...' === attr.name) {
56
+ const obj = globalRenderCache.get(attr.value);
57
+ if (obj && 'object' == typeof obj) for (const [k, v] of Object.entries(obj)) node[k] = v;
58
+ rm = 1;
59
+ } else if ('.' === startsWith) node[name] = globalRenderCache.get(attr.value) ?? attr.value,
60
+ rm = 1; else if ('@' === startsWith) {
61
+ const handlerName = attr.value, fn = globalRenderCache.get(handlerName);
62
+ fn ? node.addEventListener(name, e => fn(e)) : 'function' == typeof handlers[handlerName] && node.addEventListener(name, e => handlers[handlerName](e)),
63
+ rm = 1;
64
+ } else 'ref' === attr.name && (refs[attr.value] = node, rm = 1);
65
+ rm && requestAnimationFrame(() => {
66
+ node.removeAttribute(attr.name);
67
+ });
68
+ }
69
+ if (!node.children?.length || customElements.get(node.localName)) return refs;
70
+ for (let child of Array.from(node.children)) renderAttrs(child, handlers, refs);
71
+ return refs;
72
+ }
73
+
74
+ export { escHtml, globalRenderCache, html, render, renderAttrs, unsafeHtml };
package/dist/index.js CHANGED
@@ -2,12 +2,12 @@ export { ContextConsumer, ContextProvider, ContextRequestEvent } from './context
2
2
 
3
3
  export { MiElement, convertType, define } from './element.js';
4
4
 
5
- export { esc, escHtml, unsafeHtml } from './escape.js';
5
+ export { escHtml, html, render, renderAttrs, unsafeHtml } from './html.js';
6
6
 
7
- export { refsById, refsBySelector } from './refs.js';
8
-
9
- export { Computed, default as Signal, State, createSignal, effect } from 'mi-signal';
7
+ export { refsBySelector } from './refs.js';
10
8
 
11
9
  export { Store } from './store.js';
12
10
 
13
- export { addGlobalStyles, classMap, styleMap } from './styling.js';
11
+ export { addGlobalStyles, classNames, css, styleMap } from './styling.js';
12
+
13
+ export { default as Signal } from 'mi-signal';
package/dist/refs.js CHANGED
@@ -1,15 +1,7 @@
1
- import { kebabToCamelCase } from './case.js';
2
-
3
- function refsById(container) {
4
- const nodes = container.querySelectorAll?.('[id]') || [], found = {};
5
- for (const node of nodes) found[kebabToCamelCase(node.getAttribute('id') || node.nodeName.toLowerCase())] = node;
6
- return found;
7
- }
8
-
9
1
  function refsBySelector(container, selectors) {
10
2
  const found = {};
11
3
  for (const [name, selector] of Object.entries(selectors)) found[name] = container.querySelector?.(selector);
12
4
  return found;
13
5
  }
14
6
 
15
- export { refsById, refsBySelector };
7
+ export { refsBySelector };
package/dist/styling.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import { camelToKebabCase } from './case.js';
2
2
 
3
- const classMap = map => {
4
- const acc = [];
5
- for (const [name, value] of Object.entries(map ?? {})) value && acc.push(name);
6
- return acc.join(' ');
3
+ const classNames = (...args) => {
4
+ const classList = [];
5
+ return args.forEach(arg => {
6
+ arg && ('string' == typeof arg ? classList.push(arg) : Array.isArray(arg) ? classList.push(classNames(...arg)) : 'object' == typeof arg && Object.entries(arg).forEach(([key, value]) => {
7
+ value && classList.push(key);
8
+ }));
9
+ }), classList.join(' ');
7
10
  }, styleMap = (map, options) => {
8
11
  const {unit: unit = "px"} = options || {}, acc = [];
9
12
  for (const [name, value] of Object.entries(map ?? {})) {
@@ -23,4 +26,8 @@ function addGlobalStyles(renderRoot) {
23
26
  })), globalSheets));
24
27
  }
25
28
 
26
- export { addGlobalStyles, classMap, styleMap };
29
+ const css = (strings, ...values) => String.raw({
30
+ raw: strings
31
+ }, ...values);
32
+
33
+ export { addGlobalStyles, classNames, css, styleMap };
package/dist/utils.js ADDED
@@ -0,0 +1,12 @@
1
+ const toNumber = any => {
2
+ const n = Number(any);
3
+ return isNaN(n) ? 0 : n;
4
+ }, toJson = any => {
5
+ try {
6
+ return JSON.parse(any);
7
+ } catch {
8
+ return;
9
+ }
10
+ };
11
+
12
+ export { toJson, toNumber };
package/docs/context.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  <!-- !toc (minlevel=2) -->
4
4
 
5
- * [ContextProvider](#contextprovider)
6
- * [ContextConsumer](#contextconsumer)
7
- * [Connecting consumers to providers](#connecting-consumers-to-providers)
5
+ - [ContextProvider](#contextprovider)
6
+ - [ContextConsumer](#contextconsumer)
7
+ - [Connecting consumers to providers](#connecting-consumers-to-providers)
8
8
 
9
9
  <!-- toc! -->
10
10
 
@@ -17,29 +17,34 @@ Implements the [Context Protocol][].
17
17
  ## ContextProvider
18
18
 
19
19
  ```js
20
- import {
21
- define,
22
- MiElement,
23
- ContextProvider,
24
- ContextConsumer,
25
- } from 'mi-element'
20
+ import { define, MiElement, ContextProvider, ContextConsumer } from 'mi-element'
26
21
 
27
22
  define(
28
23
  'mi-context-provider',
29
24
  class extends MiElement {
30
- static get attributes() {
25
+ static get properties () {
31
26
  return {
32
- // define the context
33
- context: 'counter',
34
- value: 0
27
+ context: {},
28
+ value: { type: Number }
35
29
  }
36
30
  }
37
31
 
32
+ constructor() {
33
+ super()
34
+ // define the context and set initial value
35
+ // default context shall be unique amongst other context providers
36
+ this.context = 'my-context-provider'
37
+ this.value = 0
38
+ }
39
+
38
40
  render() {
39
- this.value = this.initialValue
40
41
  this.renderRoot.innerHTML = '<slot></slot>'
41
42
  this.provider = new ContextProvider(
42
- this, this.context, this._providerValue())
43
+ this,
44
+ this.context,
45
+ this._providerValue()
46
+ )
47
+ this.update()
43
48
  }
44
49
 
45
50
  update() {
@@ -48,14 +53,16 @@ define(
48
53
  }
49
54
 
50
55
  increment() {
51
- // value is observed value, requestUpdate() is called on any change
56
+ // value is observed value, requestUpdate() is called on every change
52
57
  this.value++
53
58
  }
54
59
 
55
60
  _providerValue() {
56
61
  // create a new object on every change and add all shared values and methods
57
62
  return {
58
- value: this.value, increment: this.increment }
63
+ value: this.value,
64
+ increment: () => this.increment()
65
+ }
59
66
  }
60
67
  }
61
68
  )
@@ -64,7 +71,7 @@ define(
64
71
  Works also with HTMLElement. In this case you must provide the necessary wiring.
65
72
 
66
73
  ```js
67
- customeElement.define('html-context-provider', extends class HTMLElement {
74
+ customElement.define('html-context-provider', extends class HTMLElement {
68
75
  connectedCallback() {
69
76
  this.provider = new ContextProvider(this, this.context, this)
70
77
  this.provider.hostConnected()
@@ -85,11 +92,10 @@ customeElement.define('html-context-provider', extends class HTMLElement {
85
92
  define(
86
93
  'mi-context-consumer',
87
94
  class extends MiElement {
88
- static get attributes() {
95
+ static get properties() {
89
96
  return {
90
- // define the context
91
- context: 'counter',
92
- subscribe: true
97
+ context: {},
98
+ subscribe: { type: Boolean }
93
99
  }
94
100
  }
95
101
 
@@ -97,14 +103,24 @@ define(
97
103
  <button id>Increment</button>
98
104
  <span id>0</span>`
99
105
 
106
+ constructor() {
107
+ super()
108
+ // define the context and set initial value
109
+ // default context shall be unique amongst other context providers
110
+ // must match the context-providers context!
111
+ this.context = 'my-context-provider'
112
+ this.subscribe = false
113
+ }
114
+
100
115
  render() {
101
116
  this.consumer = new ContextConsumer(this, this.context, {
102
- subscribe: !!this.subscribe
117
+ subscribe: this.subscribe
103
118
  })
104
119
  this.refs = refsById(this.renderRoot)
105
120
  this.refs.button.addEventListener('click', () => {
106
121
  this.consumer.value.increment()
107
122
  })
123
+ this.update()
108
124
  }
109
125
 
110
126
  update() {
@@ -120,14 +136,14 @@ define(
120
136
  <mi-context-provider context="outer">
121
137
  <mi-context-provider value="3">
122
138
  <div>
123
- <!-- does not subscribe to any changes (works only with MiElement)-->
124
- <mi-context-consumer subscribe="false">
139
+ <!-- does not subscribe to any changes -->
140
+ <mi-context-consumer>
125
141
  <!-- 3 -->
126
142
  </mi-context-consumer>
127
143
  </div>
128
144
  <div>
129
- <!-- connects to outer context provider -->
130
- <mi-context-consumer context="outer">
145
+ <!-- connects to outer context provider and subscribes to changes -->
146
+ <mi-context-consumer context="outer" subscribe>
131
147
  <!-- 0 -->
132
148
  </mi-context-consumer>
133
149
  </div>
@@ -55,6 +55,10 @@ define(
55
55
  this.controller = new ClockController(this)
56
56
  }
57
57
 
58
+ render() {
59
+ this.update()
60
+ }
61
+
58
62
  update() {
59
63
  // get value from controller
60
64
  const { value } = this.controller