mi-element 0.1.0 → 0.2.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
@@ -2,6 +2,8 @@
2
2
 
3
3
  > a lightweight alternative to write web components
4
4
 
5
+ Only weights 2.3kB minified and gzipped.
6
+
5
7
  mi-element provides further features to build web applications through
6
8
  [Web Components][] like:
7
9
 
@@ -77,6 +79,7 @@ In `./example` you'll find a working sample of a Todo App. Check it out with
77
79
  - [controller][docs-controller] adding controllers to mi-element to hook into the lifecycle
78
80
  - [context][docs-context] Implementation of the [Context Protocol][].
79
81
  - [store][docs-store] Manage shared state in an application
82
+ - [styling][docs-styling] Styling directives for "class" and "style"
80
83
 
81
84
  # License
82
85
 
@@ -88,3 +91,4 @@ MIT licensed
88
91
  [docs-controller]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/controller.md
89
92
  [docs-context]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/context.md
90
93
  [docs-store]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/store.md
94
+ [docs-styling]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/styling.md
package/dist/case.js ADDED
@@ -0,0 +1,3 @@
1
+ const camelToKebabCase = (str = "") => str.replace(/([A-Z])/g, ((_, m) => `-${m.toLowerCase()}`)), kebabToCamelCase = (str = "") => str.toLowerCase().replace(/[-_]\w/g, (m => m[1].toUpperCase()));
2
+
3
+ export { camelToKebabCase, kebabToCamelCase };
package/dist/context.js CHANGED
@@ -1,8 +1,8 @@
1
- import { isSignalLike, createSignal } from './signal.js';
1
+ import { createSignal, effect } from './signal.js';
2
2
 
3
3
  class ContextProvider {
4
4
  constructor(host, context, initialValue) {
5
- this.host = host, this.context = context, this.state = isSignalLike(initialValue) ? initialValue : createSignal(initialValue),
5
+ this.host = host, this.context = context, this.state = createSignal(initialValue),
6
6
  this.host.addController?.(this);
7
7
  }
8
8
  hostConnected() {
@@ -11,20 +11,19 @@ class ContextProvider {
11
11
  hostDisconnected() {
12
12
  this.host.removeEventListener("context-request", this.onContextRequest);
13
13
  }
14
- set value(newValue) {
15
- this.state.value = newValue;
14
+ set(newValue) {
15
+ this.state.set(newValue);
16
16
  }
17
- get value() {
18
- return this.state.value;
19
- }
20
- notify() {
21
- this.state.notify();
17
+ get() {
18
+ return this.state.get();
22
19
  }
23
20
  onContextRequest=ev => {
24
21
  if (ev.context !== this.context) return;
25
- ev.stopPropagation();
26
- const unsubscribe = ev.subscribe ? this.state.subscribe(ev.callback) : void 0;
27
- ev.callback(this.value, unsubscribe);
22
+ let unsubscribe;
23
+ ev.stopPropagation(), ev.subscribe && (unsubscribe = effect((() => {
24
+ const value = this.get();
25
+ unsubscribe && ev.callback(value, unsubscribe);
26
+ }))), ev.callback(this.get(), unsubscribe);
28
27
  };
29
28
  }
30
29
 
package/dist/element.js CHANGED
@@ -1,3 +1,7 @@
1
+ import { camelToKebabCase } from './case.js';
2
+
3
+ import { createSignal } from './signal.js';
4
+
1
5
  class MiElement extends HTMLElement {
2
6
  #attr={};
3
7
  #attrLc=new Map;
@@ -9,19 +13,19 @@ class MiElement extends HTMLElement {
9
13
  mode: 'open'
10
14
  };
11
15
  constructor() {
12
- super(), this.#attr = {
13
- ...this.constructor.attributes
14
- }, this.#observedAttributes();
16
+ super(), this.#observedAttributes(this.constructor.attributes);
15
17
  }
16
- #observedAttributes() {
17
- for (const [name, val] of Object.entries(this.#attr)) this.#types.set(name, initialType(val)),
18
- this.#attrLc.set(name.toLowerCase(), name), Object.defineProperty(this, name, {
18
+ #observedAttributes(attributes = {}) {
19
+ for (const [name, value] of Object.entries(attributes)) this.#types.set(name, initialType(value)),
20
+ this.#attrLc.set(name.toLowerCase(), name), this.#attrLc.set(camelToKebabCase(name), name),
21
+ this.#attr[name] = createSignal(value), Object.defineProperty(this, name, {
19
22
  enumerable: !0,
20
23
  get() {
21
- return this.#attr[name];
24
+ return this.#attr[name].get();
22
25
  },
23
26
  set(newValue) {
24
- this.#attr[name] !== newValue && (this.#attr[name] = newValue, this.#changedAttr[name] = newValue,
27
+ const oldValue = this.#attr[name].get();
28
+ oldValue !== newValue && (this.#attr[name].set(newValue), this.#changedAttr[name] = oldValue,
25
29
  this.requestUpdate());
26
30
  }
27
31
  });
@@ -41,16 +45,20 @@ class MiElement extends HTMLElement {
41
45
  disconnectedCallback() {
42
46
  this.#disposers.forEach((remover => remover())), this.#controllers.forEach((controller => controller.hostDisconnected?.()));
43
47
  }
44
- attributeChangedCallback(name, _oldValue, newValue) {
45
- const attr = this.#getName(name), type = this.#getType(attr), _newValue = convertType(newValue, type);
46
- this.#attr[attr] = this.#changedAttr[attr] = _newValue, 'Boolean' === type && 'false' === newValue && this.removeAttribute(name),
47
- this.requestUpdate();
48
+ attributeChangedCallback(name, oldValue, newValue) {
49
+ const attr = this.#getName(name), type = this.#getType(attr);
50
+ this.#changedAttr[attr] = this[attr], this[attr] = convertType(newValue, type),
51
+ 'Boolean' === type && 'false' === newValue && this.removeAttribute(name), this.requestUpdate();
48
52
  }
49
53
  setAttribute(name, newValue) {
50
54
  const attr = this.#getName(name);
51
55
  if (!(attr in this.#attr)) return;
52
56
  const type = this.#getType(attr);
53
- this.#attr[attr] = this.#changedAttr[attr] = newValue, 'Boolean' === type ? !0 === newValue || '' === newValue ? super.setAttribute(name, '') : super.removeAttribute(name) : [ 'String', 'Number' ].includes(type) || !0 === newValue ? super.setAttribute(name, newValue) : this.requestUpdate();
57
+ '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],
58
+ this[attr] = newValue, this.requestUpdate());
59
+ }
60
+ shouldUpdate(_changedAttributes) {
61
+ return !0;
54
62
  }
55
63
  requestUpdate() {
56
64
  this.isConnected && requestAnimationFrame((() => {
@@ -61,9 +69,6 @@ class MiElement extends HTMLElement {
61
69
  template instanceof HTMLTemplateElement && this.renderRoot.appendChild(template.content.cloneNode(!0));
62
70
  }
63
71
  render() {}
64
- shouldUpdate(_changedAttributes) {
65
- return !0;
66
- }
67
72
  update(_changedAttributes) {}
68
73
  on(eventName, listener, node = this) {
69
74
  node.addEventListener(eventName, listener), this.#disposers.add((() => node.removeEventListener(eventName, listener)));
@@ -73,9 +78,11 @@ class MiElement extends HTMLElement {
73
78
  once: !0
74
79
  });
75
80
  }
76
- dispose(listener) {
77
- if ('function' != typeof listener) throw new TypeError('listener must be a function');
78
- this.#disposers.add(listener);
81
+ dispose(...listeners) {
82
+ for (const listener of listeners) {
83
+ if ('function' != typeof listener) throw new TypeError('listener must be a function');
84
+ this.#disposers.add(listener);
85
+ }
79
86
  }
80
87
  addController(controller) {
81
88
  this.#controllers.add(controller), this.isConnected && controller.hostConnected?.();
package/dist/index.js CHANGED
@@ -6,6 +6,8 @@ export { esc, escAttr, escHtml } from './escape.js';
6
6
 
7
7
  export { refsById, refsBySelector } from './refs.js';
8
8
 
9
- export { Signal, createSignal, isSignalLike } from './signal.js';
9
+ export { default as Signal } from './signal.js';
10
10
 
11
- export { Store, subscribeToStore } from './store.js';
11
+ export { Store } from './store.js';
12
+
13
+ export { classMap, styleMap } from './styling.js';
package/dist/refs.js CHANGED
@@ -1,15 +1,15 @@
1
+ import { kebabToCamelCase } from './case.js';
2
+
1
3
  function refsById(container) {
2
4
  const nodes = container.querySelectorAll?.('[id]') || [], found = {};
3
5
  for (const node of nodes) found[kebabToCamelCase(node.getAttribute('id') || node.nodeName.toLowerCase())] = node;
4
6
  return found;
5
7
  }
6
8
 
7
- const kebabToCamelCase = (str = "") => str.toLowerCase().replace(/[-_]\w/g, (m => m[1].toUpperCase()));
8
-
9
9
  function refsBySelector(container, selectors) {
10
10
  const found = {};
11
11
  for (const [name, selector] of Object.entries(selectors)) found[name] = container.querySelector?.(selector);
12
12
  return found;
13
13
  }
14
14
 
15
- export { kebabToCamelCase, refsById, refsBySelector };
15
+ export { refsById, refsBySelector };
package/dist/signal.js CHANGED
@@ -1,24 +1,62 @@
1
- class Signal {
2
- _subscribers=new Set;
3
- constructor(initialValue) {
4
- this._value = initialValue;
1
+ const context = [];
2
+
3
+ class State {
4
+ subscribers=new Set;
5
+ constructor(value, options) {
6
+ const {equals: equals} = options || {};
7
+ this.value = value, this.equals = equals ?? ((value, nextValue) => value === nextValue);
5
8
  }
6
- get value() {
7
- return this._value;
9
+ get() {
10
+ const running = context[context.length - 1];
11
+ return running && (this.subscribers.add(running), running.dependencies.add(this.subscribers)),
12
+ this.value;
8
13
  }
9
- set value(newValue) {
10
- this._value !== newValue && (this._value = newValue, this.notify());
14
+ set(nextValue) {
15
+ if (!this.equals(this.value, nextValue)) {
16
+ this.value = nextValue;
17
+ for (const running of [ ...this.subscribers ]) running.execute();
18
+ }
11
19
  }
12
- notify() {
13
- for (const callback of this._subscribers) callback(this._value);
20
+ }
21
+
22
+ const createSignal = value => value instanceof State ? value : new State(value);
23
+
24
+ function cleanup(running) {
25
+ for (const dep of running.dependencies) dep.delete(running);
26
+ running.dependencies.clear();
27
+ }
28
+
29
+ function effect(cb) {
30
+ const execute = () => {
31
+ cleanup(running), context.push(running);
32
+ try {
33
+ cb();
34
+ } finally {
35
+ context.pop();
36
+ }
37
+ }, running = {
38
+ execute: execute,
39
+ dependencies: new Set
40
+ };
41
+ return execute(), () => {
42
+ cleanup(running);
43
+ };
44
+ }
45
+
46
+ class Computed {
47
+ constructor(cb) {
48
+ this.state = new State, effect((() => this.state.set(cb)));
14
49
  }
15
- subscribe(callback) {
16
- return this._subscribers.add(callback), () => {
17
- this._subscribers.delete(callback);
18
- };
50
+ get() {
51
+ return this.state.get();
19
52
  }
20
53
  }
21
54
 
22
- const createSignal = initialValue => new Signal(initialValue), isSignalLike = possibleSignal => 'function' == typeof possibleSignal?.subscribe && 'function' == typeof possibleSignal?.notify && 'value' in possibleSignal;
55
+ var signal = {
56
+ State: State,
57
+ createSignal: createSignal,
58
+ effect: effect,
59
+ Computed: Computed
60
+ };
23
61
 
24
- export { Signal, createSignal, isSignalLike };
62
+ export { Computed, State, createSignal, signal as default, effect };
package/dist/store.js CHANGED
@@ -1,28 +1,10 @@
1
- import { Signal, isSignalLike } from './signal.js';
1
+ import { State } from './signal.js';
2
2
 
3
- class Store extends Signal {
4
- constructor(actions, initialValue) {
5
- super(initialValue);
6
- for (const [action, dispatcher] of Object.entries(actions)) this[action] = data => {
7
- this.value = dispatcher(data)(this.value);
8
- };
3
+ class Store extends State {
4
+ constructor(actions, initialValue, options) {
5
+ super(initialValue, options);
6
+ for (const [action, dispatcher] of Object.entries(actions)) this[action] = data => this.set(dispatcher(data)(this.get()));
9
7
  }
10
8
  }
11
9
 
12
- const subscribeToStore = (element, store, propOrSignal) => {
13
- if (propOrSignal instanceof Signal || isSignalLike(propOrSignal)) return void element.dispose(store.subscribe((value => {
14
- propOrSignal.value = value;
15
- })));
16
- const keys = Array.isArray(propOrSignal) ? propOrSignal : propOrSignal.split('.').filter(Boolean), last = keys.pop();
17
- if (!last) throw TypeError('need prop');
18
- let tmp = element;
19
- for (const key of keys) {
20
- if ('object' != typeof tmp[key]) throw new TypeError(`object expected for property "${key}"`);
21
- tmp = tmp[key];
22
- }
23
- element.dispose(store.subscribe((value => {
24
- tmp[last] = value, element.requestUpdate();
25
- })));
26
- };
27
-
28
- export { Store, subscribeToStore };
10
+ export { Store };
@@ -0,0 +1,17 @@
1
+ import { camelToKebabCase } from './case.js';
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(' ');
7
+ }, styleMap = (map, options) => {
8
+ const {unit: unit = "px"} = options || {}, acc = [];
9
+ for (const [name, value] of Object.entries(map ?? {})) {
10
+ if (null == value) continue;
11
+ const _unit = Number.isFinite(value) ? unit : '';
12
+ acc.push(`${camelToKebabCase(name)}:${value}${_unit}`);
13
+ }
14
+ return acc.join(';');
15
+ };
16
+
17
+ export { classMap, styleMap };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mi-element",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Build lightweight reactive micro web-components",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/commenthol/mi-element/tree/main/packages/mi-element#readme",
@@ -29,6 +29,11 @@
29
29
  "development": "./src/element.js",
30
30
  "types": "./types/element.d.ts"
31
31
  },
32
+ "./case": {
33
+ "default": "./dist/case.js",
34
+ "development": "./src/case.js",
35
+ "types": "./types/case.d.ts"
36
+ },
32
37
  "./context": {
33
38
  "default": "./dist/context.js",
34
39
  "development": "./src/context.js",
@@ -54,6 +59,11 @@
54
59
  "development": "./src/store.js",
55
60
  "types": "./types/store.d.ts"
56
61
  },
62
+ "./styling": {
63
+ "default": "./dist/styling.js",
64
+ "development": "./src/styling.js",
65
+ "types": "./types/styling.d.ts"
66
+ },
57
67
  "./package.json": {
58
68
  "default": "./package.json"
59
69
  }
@@ -83,8 +93,8 @@
83
93
  "vitest": "^2.0.5"
84
94
  },
85
95
  "scripts": {
86
- "all": "npm-run-all lint test build",
87
- "build": "rimraf dist && rollup -c && gzip dist/index.min.js && ls -al dist && rimraf dist/index.min.*",
96
+ "all": "npm-run-all pretty lint test build types",
97
+ "build": "rimraf dist && rollup -c && gzip -k dist/index.min.js && ls -al dist && rimraf dist/index.min.*",
88
98
  "example": "vite --open /example/",
89
99
  "lint": "eslint",
90
100
  "pretty": "prettier -w **/*.js",
package/src/case.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * convert lowerCamelCase to kebab-case
3
+ * @param {string} str
4
+ * @returns {string}
5
+ */
6
+ export const camelToKebabCase = (str = '') =>
7
+ str.replace(/([A-Z])/g, (_, m) => `-${m.toLowerCase()}`)
8
+
9
+ /**
10
+ * convert kebab-case to lowerCamelCase
11
+ * @param {string} str
12
+ * @returns {string}
13
+ */
14
+ export const kebabToCamelCase = (str = '') =>
15
+ str.toLowerCase().replace(/[-_]\w/g, (m) => m[1].toUpperCase())
package/src/context.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * @see https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md
3
3
  */
4
4
 
5
- import { createSignal, isSignalLike } from './signal.js'
5
+ import { createSignal, effect } from './signal.js'
6
6
 
7
7
  /**
8
8
  * @typedef {import('./element.js').HostController} HostController
@@ -25,9 +25,7 @@ export class ContextProvider {
25
25
  constructor(host, context, initialValue) {
26
26
  this.host = host
27
27
  this.context = context
28
- this.state = isSignalLike(initialValue)
29
- ? initialValue
30
- : createSignal(initialValue)
28
+ this.state = createSignal(initialValue)
31
29
  // @ts-expect-error
32
30
  this.host.addController?.(this)
33
31
  }
@@ -45,24 +43,20 @@ export class ContextProvider {
45
43
  /**
46
44
  * @param {any} newValue
47
45
  */
48
- set value(newValue) {
49
- this.state.value = newValue
46
+ set(newValue) {
47
+ this.state.set(newValue)
50
48
  }
51
49
 
52
50
  /**
53
51
  * @returns {any}
54
52
  */
55
- get value() {
56
- return this.state.value
57
- }
58
-
59
- notify() {
60
- this.state.notify()
53
+ get() {
54
+ return this.state.get()
61
55
  }
62
56
 
63
57
  /**
64
58
  * @private
65
- * @param {ContextRequestEvent} ev
59
+ * @param {ContextRequestEvent} ev
66
60
  */
67
61
  onContextRequest = (ev) => {
68
62
  if (ev.context !== this.context) {
@@ -70,10 +64,17 @@ export class ContextProvider {
70
64
  return
71
65
  }
72
66
  ev.stopPropagation()
73
- const unsubscribe = ev.subscribe
74
- ? this.state.subscribe(ev.callback)
75
- : undefined
76
- ev.callback(this.value, unsubscribe)
67
+ console.debug('provider.onContextRequest', this.state)
68
+ let unsubscribe
69
+ if (ev.subscribe) {
70
+ unsubscribe = effect(() => {
71
+ // needed to subscribe to signal
72
+ const value = this.get()
73
+ // don't call callback the first time where unsubscribe is not defined
74
+ if (unsubscribe) ev.callback(value, unsubscribe)
75
+ })
76
+ }
77
+ ev.callback(this.get(), unsubscribe)
77
78
  }
78
79
  }
79
80
 
@@ -140,6 +141,7 @@ export class ContextConsumer {
140
141
  }
141
142
 
142
143
  _callback(value, unsubscribe) {
144
+ console.debug('consumer.callback', { value, unsubscribe })
143
145
  if (unsubscribe) {
144
146
  if (!this.subscribe) {
145
147
  // unsubscribe as we didn't ask for subscription
package/src/element.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { camelToKebabCase } from './case.js'
2
+ import { createSignal } from './signal.js'
3
+
1
4
  /**
2
5
  * @typedef {object} HostController controller
3
6
  * @property {() => void} hostConnected is called when host element is added to
@@ -57,28 +60,29 @@ export class MiElement extends HTMLElement {
57
60
  constructor() {
58
61
  super()
59
62
  // @ts-expect-error
60
- this.#attr = { ...this.constructor.attributes }
61
- this.#observedAttributes()
63
+ this.#observedAttributes(this.constructor.attributes)
62
64
  }
63
65
 
64
66
  /**
65
67
  * requests update on component when property changes
66
68
  */
67
- #observedAttributes() {
68
- for (const [name, val] of Object.entries(this.#attr)) {
69
- this.#types.set(name, initialType(val))
69
+ #observedAttributes(attributes = {}) {
70
+ for (const [name, value] of Object.entries(attributes)) {
71
+ this.#types.set(name, initialType(value))
70
72
  this.#attrLc.set(name.toLowerCase(), name)
73
+ this.#attrLc.set(camelToKebabCase(name), name)
74
+ this.#attr[name] = createSignal(value)
71
75
  Object.defineProperty(this, name, {
72
76
  enumerable: true,
73
77
  get() {
74
- return this.#attr[name]
78
+ return this.#attr[name].get()
75
79
  },
76
80
  set(newValue) {
77
81
  console.debug('%s.%s =', this.nodeName, name, newValue)
78
- const oldValue = this.#attr[name]
82
+ const oldValue = this.#attr[name].get()
79
83
  if (oldValue === newValue) return
80
- this.#attr[name] = newValue
81
- this.#changedAttr[name] = newValue
84
+ this.#attr[name].set(newValue)
85
+ this.#changedAttr[name] = oldValue
82
86
  this.requestUpdate()
83
87
  }
84
88
  })
@@ -129,14 +133,14 @@ export class MiElement extends HTMLElement {
129
133
 
130
134
  /**
131
135
  * @param {string} name change attribute
132
- * @param {any} _oldValue
136
+ * @param {any} oldValue
133
137
  * @param {any} newValue new value
134
138
  */
135
- attributeChangedCallback(name, _oldValue, newValue) {
139
+ attributeChangedCallback(name, oldValue, newValue) {
136
140
  const attr = this.#getName(name)
137
141
  const type = this.#getType(attr)
138
- const _newValue = convertType(newValue, type)
139
- this.#attr[attr] = this.#changedAttr[attr] = _newValue
142
+ this.#changedAttr[attr] = this[attr]
143
+ this[attr] = convertType(newValue, type)
140
144
  // correct initial setting of `trueish="false"` otherwise there's no chance
141
145
  // to overwrite a trueish value. The case `falsish="true"` is covered.
142
146
  if (type === 'Boolean' && newValue === 'false') {
@@ -146,7 +150,7 @@ export class MiElement extends HTMLElement {
146
150
  '%s.attributeChangedCallback("%s",',
147
151
  this.nodeName,
148
152
  name,
149
- _oldValue,
153
+ oldValue,
150
154
  newValue
151
155
  )
152
156
  this.requestUpdate()
@@ -165,7 +169,6 @@ export class MiElement extends HTMLElement {
165
169
  return
166
170
  }
167
171
  const type = this.#getType(attr)
168
- this.#attr[attr] = this.#changedAttr[attr] = newValue
169
172
  console.debug('%s.setAttribute("%s",', this.nodeName, name, newValue)
170
173
 
171
174
  // only set string values in these cases
@@ -178,10 +181,21 @@ export class MiElement extends HTMLElement {
178
181
  } else if (['String', 'Number'].includes(type) || newValue === true) {
179
182
  super.setAttribute(name, newValue)
180
183
  } else {
184
+ this.#changedAttr[attr] = this[attr]
185
+ this[attr] = newValue
181
186
  this.requestUpdate()
182
187
  }
183
188
  }
184
189
 
190
+ /**
191
+ * controls if component shall be updated
192
+ * @param {Record<string,any>} [_changedAttributes] previous values of changed attributes
193
+ * @returns {boolean}
194
+ */
195
+ shouldUpdate(_changedAttributes) {
196
+ return true
197
+ }
198
+
185
199
  /**
186
200
  * request rendering
187
201
  */
@@ -198,7 +212,7 @@ export class MiElement extends HTMLElement {
198
212
 
199
213
  /**
200
214
  * adds a template to renderRoot
201
- * @param {HTMLTemplateElement}
215
+ * @param {HTMLTemplateElement} template
202
216
  */
203
217
  addTemplate(template) {
204
218
  if (!(template instanceof HTMLTemplateElement)) {
@@ -213,18 +227,10 @@ export class MiElement extends HTMLElement {
213
227
  */
214
228
  render() {}
215
229
 
216
- /**
217
- * controls if component shall be updated
218
- * @param {Record<string,any>} _changedAttributes changed attributes
219
- * @returns {boolean}
220
- */
221
- shouldUpdate(_changedAttributes) {
222
- return true
223
- }
224
-
225
230
  /**
226
231
  * called every time the components needs a render update
227
- * @param {Record<string,any>} _changedAttributes changed attributes
232
+ * @param {Record<string,any>} [_changedAttributes] previous values of changed
233
+ * attributes
228
234
  */
229
235
  update(_changedAttributes) {}
230
236
 
@@ -253,13 +259,15 @@ export class MiElement extends HTMLElement {
253
259
 
254
260
  /**
255
261
  * Unsubscribe a listener function for disposal on disconnectedCallback()
256
- * @param {function} listener
262
+ * @param {...function} listeners
257
263
  */
258
- dispose(listener) {
259
- if (typeof listener !== 'function') {
260
- throw new TypeError('listener must be a function')
264
+ dispose(...listeners) {
265
+ for (const listener of listeners) {
266
+ if (typeof listener !== 'function') {
267
+ throw new TypeError('listener must be a function')
268
+ }
269
+ this.#disposers.add(listener)
261
270
  }
262
- this.#disposers.add(listener)
263
271
  }
264
272
 
265
273
  /**
package/src/index.js CHANGED
@@ -13,10 +13,13 @@ export { MiElement, convertType, define } from './element.js'
13
13
  export { esc, escAttr, escHtml } from './escape.js'
14
14
  export { refsById, refsBySelector } from './refs.js'
15
15
  /**
16
- * @typedef {import('./signal.js').Callback} Callback
16
+ * @template T
17
+ * @typedef {import('./signal.js').SignalOptions<T>} SignalOptions<T>
17
18
  */
18
- export { Signal, createSignal, isSignalLike } from './signal.js'
19
+ import Signal from './signal.js'
20
+ export { Signal }
19
21
  /**
20
22
  * @typedef {import('./store.js').Action} Action
21
23
  */
22
- export { Store, subscribeToStore } from './store.js'
24
+ export { Store } from './store.js'
25
+ export { classMap, styleMap } from './styling.js'
package/src/refs.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { kebabToCamelCase } from './case.js'
2
+
1
3
  /**
2
4
  * Helper function to find `id` attributes in `container`s node tree.
3
5
  * id names are camelCased, e.g. 'list-container' becomes 'listContainer'
@@ -20,14 +22,6 @@ export function refsById(container) {
20
22
  return found
21
23
  }
22
24
 
23
- /**
24
- * convert kebab-case to lowerCamelCase
25
- * @param {string} str
26
- * @returns {string}
27
- */
28
- export const kebabToCamelCase = (str = '') =>
29
- str.toLowerCase().replace(/[-_]\w/g, (m) => m[1].toUpperCase())
30
-
31
25
  /**
32
26
  * Helper function to gather references by a map of selectors
33
27
  * @param {Element} container root element