mi-element 0.1.0 → 0.3.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 +35 -16
- package/dist/case.js +3 -0
- package/dist/context.js +11 -12
- package/dist/element.js +26 -19
- package/dist/index.js +4 -2
- package/dist/refs.js +3 -3
- package/dist/signal.js +45 -16
- package/dist/store.js +6 -24
- package/dist/styling.js +17 -0
- package/package.json +14 -3
- package/src/case.js +15 -0
- package/src/context.js +19 -17
- package/src/element.js +39 -31
- package/src/index.js +6 -3
- package/src/refs.js +2 -8
- package/src/signal.js +98 -49
- package/src/store.js +27 -48
- package/src/styling.js +33 -0
- package/types/case.d.ts +2 -0
- package/types/context.d.ts +56 -58
- package/types/element.d.ts +101 -123
- package/types/escape.d.ts +3 -3
- package/types/index.d.ts +15 -14
- package/types/refs.d.ts +4 -6
- package/types/signal.d.ts +64 -33
- package/types/store.d.ts +53 -35
- package/types/styling.d.ts +8 -0
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.#
|
|
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,
|
|
69
|
-
this.#types.set(name, initialType(
|
|
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]
|
|
81
|
-
this.#changedAttr[name] =
|
|
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}
|
|
136
|
+
* @param {any} oldValue
|
|
133
137
|
* @param {any} newValue new value
|
|
134
138
|
*/
|
|
135
|
-
attributeChangedCallback(name,
|
|
139
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
136
140
|
const attr = this.#getName(name)
|
|
137
141
|
const type = this.#getType(attr)
|
|
138
|
-
|
|
139
|
-
this
|
|
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
|
-
|
|
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
|
|
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}
|
|
262
|
+
* @param {...function} listeners
|
|
257
263
|
*/
|
|
258
|
-
dispose(
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
* @
|
|
16
|
+
* @template T
|
|
17
|
+
* @typedef {import('./signal.js').SignalOptions<T>} SignalOptions<T>
|
|
17
18
|
*/
|
|
18
|
-
|
|
19
|
+
import Signal from './signal.js'
|
|
20
|
+
export { Signal }
|
|
19
21
|
/**
|
|
20
22
|
* @typedef {import('./store.js').Action} Action
|
|
21
23
|
*/
|
|
22
|
-
export { Store
|
|
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
|
package/src/signal.js
CHANGED
|
@@ -1,76 +1,125 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* tries to follow proposal JavaScript Signals standard proposal which is in
|
|
3
|
+
* stage 1
|
|
4
|
+
* @see https://github.com/tc39/proposal-signals
|
|
5
|
+
* @credits https://github.com/jsebrech/tiny-signals
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
// global context for nested reactivity
|
|
9
|
+
const context = []
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @template T
|
|
13
|
+
* @typedef {(value?: T|null, nextValue?: T|null) => boolean} EqualsFn
|
|
14
|
+
* Custom comparison function between old and new value
|
|
15
|
+
* default `(value, nextValue) => value === nextValue`
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @template T
|
|
20
|
+
* @typedef {{equals: EqualsFn<T>}} SignalOptions
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* read- write signal
|
|
25
|
+
* @template T
|
|
26
|
+
*/
|
|
27
|
+
export class State extends EventTarget {
|
|
28
|
+
#value
|
|
29
|
+
#equals
|
|
7
30
|
|
|
8
31
|
/**
|
|
9
|
-
*
|
|
10
|
-
* @param {
|
|
32
|
+
* @param {T|null} [value]
|
|
33
|
+
* @param {SignalOptions<T>} [options]
|
|
11
34
|
*/
|
|
12
|
-
constructor(
|
|
13
|
-
|
|
35
|
+
constructor(value, options) {
|
|
36
|
+
super()
|
|
37
|
+
const { equals } = options || {}
|
|
38
|
+
this.#value = value
|
|
39
|
+
this.#equals = equals ?? ((value, nextValue) => value === nextValue)
|
|
14
40
|
}
|
|
15
41
|
|
|
16
42
|
/**
|
|
17
|
-
*
|
|
18
|
-
* @returns {any}
|
|
43
|
+
* @returns {T|null|undefined}
|
|
19
44
|
*/
|
|
20
|
-
get
|
|
21
|
-
|
|
45
|
+
get() {
|
|
46
|
+
const running = context[context.length - 1]
|
|
47
|
+
if (running) {
|
|
48
|
+
running.add(this)
|
|
49
|
+
}
|
|
50
|
+
return this.#value
|
|
22
51
|
}
|
|
23
52
|
|
|
24
53
|
/**
|
|
25
|
-
*
|
|
26
|
-
* if value has changed all subscribers are called
|
|
27
|
-
* @param {any} newValue
|
|
54
|
+
* @param {T|null|undefined} nextValue
|
|
28
55
|
*/
|
|
29
|
-
set
|
|
30
|
-
|
|
31
|
-
if (this._value === newValue) {
|
|
56
|
+
set(nextValue) {
|
|
57
|
+
if (this.#equals(this.#value, nextValue)) {
|
|
32
58
|
return
|
|
33
59
|
}
|
|
34
|
-
this
|
|
35
|
-
this.
|
|
60
|
+
this.#value = nextValue
|
|
61
|
+
this.dispatchEvent(new CustomEvent('signal'))
|
|
36
62
|
}
|
|
63
|
+
}
|
|
37
64
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
/**
|
|
66
|
+
* @template T
|
|
67
|
+
* @param {T} initialValue
|
|
68
|
+
* @param {SignalOptions<T>} [options]
|
|
69
|
+
* @returns {State<T>}
|
|
70
|
+
*/
|
|
71
|
+
export const createSignal = (initialValue, options) =>
|
|
72
|
+
initialValue instanceof State
|
|
73
|
+
? initialValue
|
|
74
|
+
: new State(initialValue, options)
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* effect subscribes to state at first run only. Do not hide a signal.get()
|
|
78
|
+
* inside conditionals!
|
|
79
|
+
* @param {() => void|Promise<void>} cb
|
|
80
|
+
*/
|
|
81
|
+
export function effect(cb) {
|
|
82
|
+
const running = new Set()
|
|
83
|
+
|
|
84
|
+
context.push(running)
|
|
85
|
+
try {
|
|
86
|
+
cb()
|
|
87
|
+
} finally {
|
|
88
|
+
context.pop()
|
|
89
|
+
}
|
|
90
|
+
for (const dep of running) {
|
|
91
|
+
dep.addEventListener('signal', cb)
|
|
45
92
|
}
|
|
46
93
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*/
|
|
52
|
-
subscribe(callback) {
|
|
53
|
-
this._subscribers.add(callback)
|
|
54
|
-
const unsubscribe = () => {
|
|
55
|
-
this._subscribers.delete(callback)
|
|
94
|
+
return () => {
|
|
95
|
+
// unsubscribe from all dependencies
|
|
96
|
+
for (const dep of running) {
|
|
97
|
+
dep.removeEventListener('signal', cb)
|
|
56
98
|
}
|
|
57
|
-
return unsubscribe
|
|
58
99
|
}
|
|
59
100
|
}
|
|
60
101
|
|
|
61
102
|
/**
|
|
62
|
-
*
|
|
63
|
-
* @param {any} [initialValue]
|
|
64
|
-
* @returns {Signal}
|
|
103
|
+
* @template T
|
|
65
104
|
*/
|
|
66
|
-
export
|
|
105
|
+
export class Computed {
|
|
106
|
+
#state
|
|
67
107
|
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
108
|
+
/**
|
|
109
|
+
* @param {() => T} cb
|
|
110
|
+
*/
|
|
111
|
+
constructor(cb) {
|
|
112
|
+
this.#state = new State()
|
|
113
|
+
effect(() => this.#state.set(cb()))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @template T
|
|
118
|
+
* @returns {T}
|
|
119
|
+
*/
|
|
120
|
+
get() {
|
|
121
|
+
return this.#state.get()
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default { State, createSignal, effect, Computed }
|
package/src/store.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { State } from './signal.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {import('./element.js').MiElement} MiElement
|
|
@@ -6,22 +6,29 @@ import { Signal, isSignalLike } from './signal.js'
|
|
|
6
6
|
/**
|
|
7
7
|
* @typedef {(state: any, data?: any) => any} Action
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* @template T
|
|
11
|
+
* @typedef {import('./signal.js').SignalOptions<T>} SignalOptions<T>
|
|
12
|
+
*/
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* Store implementing [Flux](https://www.npmjs.com/package/flux) pattern based
|
|
12
16
|
* on Signals
|
|
17
|
+
* @template T
|
|
13
18
|
*/
|
|
14
|
-
export class Store extends
|
|
19
|
+
export class Store extends State {
|
|
15
20
|
/**
|
|
16
21
|
* @param {Record<string, Action>} actions
|
|
17
|
-
* @param {
|
|
22
|
+
* @param {T|null} [initialValue]
|
|
23
|
+
* @param {SignalOptions<T>} [options]
|
|
18
24
|
* @example
|
|
19
25
|
* ```js
|
|
26
|
+
* import { Signal, Store } from 'mi-element'
|
|
20
27
|
* const actions = { increment: (by = 1) => (current) => current + by }
|
|
21
28
|
* const initialValue = 1
|
|
22
29
|
* const store = new Store(actions, initialValue)
|
|
23
30
|
* // subscribe with a callback function
|
|
24
|
-
* const unsubscribe =
|
|
31
|
+
* const unsubscribe = Signal.effect(() => console.log(`count is ${store.get()}`))
|
|
25
32
|
* // change the store
|
|
26
33
|
* store.increment(2) // increment by 2
|
|
27
34
|
* //> count is 3
|
|
@@ -33,12 +40,24 @@ export class Store extends Signal {
|
|
|
33
40
|
* ```js
|
|
34
41
|
* const initialValue = { count: 0, other: 'foo' }
|
|
35
42
|
* const actions = {
|
|
36
|
-
*
|
|
43
|
+
* increment: (by = 1) => (state) => ({...state, count: state.count + by})
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
* or you change the signals options equality function
|
|
47
|
+
* ```js
|
|
48
|
+
* const actions = {
|
|
49
|
+
* increment: (by = 1) => (state) => {
|
|
50
|
+
* state.count += by
|
|
51
|
+
* return state
|
|
52
|
+
* }
|
|
37
53
|
* }
|
|
54
|
+
* const initialValue = { count: 0, other: 'foo' }
|
|
55
|
+
* const options = { equals: (value, nextValue) => true }
|
|
56
|
+
* const store = new Store(actions, initialValue, options)
|
|
38
57
|
* ```
|
|
39
58
|
*/
|
|
40
|
-
constructor(actions, initialValue) {
|
|
41
|
-
super(initialValue)
|
|
59
|
+
constructor(actions, initialValue, options) {
|
|
60
|
+
super(initialValue, options)
|
|
42
61
|
for (const [action, dispatcher] of Object.entries(actions)) {
|
|
43
62
|
if (process.env.NODE_ENV !== 'production') {
|
|
44
63
|
if (this[action]) {
|
|
@@ -53,47 +72,7 @@ export class Store extends Signal {
|
|
|
53
72
|
)
|
|
54
73
|
}
|
|
55
74
|
}
|
|
56
|
-
this[action] = (data) =>
|
|
57
|
-
this.value = dispatcher(data)(this.value)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* subscribe to a store from a MiElement with unsubscription from store on
|
|
65
|
-
* disconnectedCallback()
|
|
66
|
-
* @param {MiElement} element
|
|
67
|
-
* @param {Store} store
|
|
68
|
-
* @param {string|string[]|Signal} propOrSignal element property to apply store
|
|
69
|
-
* value updates
|
|
70
|
-
*/
|
|
71
|
-
export const subscribeToStore = (element, store, propOrSignal) => {
|
|
72
|
-
if (propOrSignal instanceof Signal || isSignalLike(propOrSignal)) {
|
|
73
|
-
element.dispose(
|
|
74
|
-
store.subscribe((value) => {
|
|
75
|
-
// @ts-expect-error
|
|
76
|
-
propOrSignal.value = value
|
|
77
|
-
})
|
|
78
|
-
)
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
const keys = Array.isArray(propOrSignal)
|
|
82
|
-
? propOrSignal
|
|
83
|
-
: propOrSignal.split('.').filter(Boolean)
|
|
84
|
-
const last = keys.pop()
|
|
85
|
-
if (!last) throw TypeError('need prop')
|
|
86
|
-
let tmp = element
|
|
87
|
-
for (const key of keys) {
|
|
88
|
-
if (typeof tmp[key] !== 'object') {
|
|
89
|
-
throw new TypeError(`object expected for property "${key}"`)
|
|
75
|
+
this[action] = (data) => this.set(dispatcher(data)(this.get()))
|
|
90
76
|
}
|
|
91
|
-
tmp = tmp[key]
|
|
92
77
|
}
|
|
93
|
-
element.dispose(
|
|
94
|
-
store.subscribe((value) => {
|
|
95
|
-
tmp[last] = value
|
|
96
|
-
element.requestUpdate()
|
|
97
|
-
})
|
|
98
|
-
)
|
|
99
78
|
}
|
package/src/styling.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { camelToKebabCase } from './case.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Construct className based on trueish values of map
|
|
5
|
+
* @param {{[name: string]: string | boolean | number}} map
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
export const classMap = (map) => {
|
|
9
|
+
/** @type {string[]} */
|
|
10
|
+
const acc = []
|
|
11
|
+
for (const [name, value] of Object.entries(map ?? {})) {
|
|
12
|
+
if (value) acc.push(name)
|
|
13
|
+
}
|
|
14
|
+
return acc.join(' ')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Construct style from camelCased map.
|
|
19
|
+
* @param {{[name: string]: string | number | undefined | null}} map
|
|
20
|
+
* @param {object} [options]
|
|
21
|
+
* @param {string} [options.unit] cssUnit for number values; default='px'
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
export const styleMap = (map, options) => {
|
|
25
|
+
const { unit = 'px' } = options || {}
|
|
26
|
+
const acc = []
|
|
27
|
+
for (const [name, value] of Object.entries(map ?? {})) {
|
|
28
|
+
if (value === null || value === undefined) continue
|
|
29
|
+
const _unit = Number.isFinite(value) ? unit : ''
|
|
30
|
+
acc.push(`${camelToKebabCase(name)}:${value}${_unit}`)
|
|
31
|
+
}
|
|
32
|
+
return acc.join(';')
|
|
33
|
+
}
|
package/types/case.d.ts
ADDED
package/types/context.d.ts
CHANGED
|
@@ -2,69 +2,67 @@
|
|
|
2
2
|
* @implements {HostController}
|
|
3
3
|
*/
|
|
4
4
|
export class ContextProvider implements HostController {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
5
|
+
/**
|
|
6
|
+
* @param {HTMLElement} host
|
|
7
|
+
* @param {Context} context
|
|
8
|
+
* @param {any} initialValue
|
|
9
|
+
*/
|
|
10
|
+
constructor(host: HTMLElement, context: Context, initialValue: any);
|
|
11
|
+
host: HTMLElement;
|
|
12
|
+
context: Context;
|
|
13
|
+
state: import("./signal.js").State<any>;
|
|
14
|
+
hostConnected(): void;
|
|
15
|
+
hostDisconnected(): void;
|
|
16
|
+
/**
|
|
17
|
+
* @param {any} newValue
|
|
18
|
+
*/
|
|
19
|
+
set(newValue: any): void;
|
|
20
|
+
/**
|
|
21
|
+
* @returns {any}
|
|
22
|
+
*/
|
|
23
|
+
get(): any;
|
|
24
|
+
/**
|
|
25
|
+
* @private
|
|
26
|
+
* @param {ContextRequestEvent} ev
|
|
27
|
+
*/
|
|
28
|
+
private onContextRequest;
|
|
20
29
|
}
|
|
21
30
|
export class ContextRequestEvent extends Event {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
context: Context
|
|
29
|
-
callback: (value: any, unsubscribe?: () => void) => void
|
|
30
|
-
subscribe
|
|
31
|
-
)
|
|
32
|
-
context: Context
|
|
33
|
-
callback: (value: any, unsubscribe?: () => void) => void
|
|
34
|
-
subscribe: boolean | undefined
|
|
31
|
+
/**
|
|
32
|
+
* @param {Context} context
|
|
33
|
+
* @param {(value: any, unsubscribe?: () => void) => void} callback
|
|
34
|
+
* @param {boolean} [subscribe=false] subscribe to value changes
|
|
35
|
+
*/
|
|
36
|
+
constructor(context: Context, callback: (value: any, unsubscribe?: () => void) => void, subscribe?: boolean | undefined);
|
|
37
|
+
context: Context;
|
|
38
|
+
callback: (value: any, unsubscribe?: () => void) => void;
|
|
39
|
+
subscribe: boolean | undefined;
|
|
35
40
|
}
|
|
36
41
|
/**
|
|
37
42
|
* @implements {HostController}
|
|
38
43
|
*/
|
|
39
44
|
export class ContextConsumer implements HostController {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
value: any
|
|
62
|
-
unsubscribe: any
|
|
63
|
-
hostConnected(): void
|
|
64
|
-
hostDisconnected(): void
|
|
65
|
-
dispatchRequest(): void
|
|
66
|
-
_callback(value: any, unsubscribe: any): void
|
|
45
|
+
/**
|
|
46
|
+
* @param {HTMLElement} host
|
|
47
|
+
* @param {Context} context
|
|
48
|
+
* @param {object} [options]
|
|
49
|
+
* @param {boolean} [options.subscribe=false] subscribe to value changes
|
|
50
|
+
* @param {(any) => boolean} [options.validate] validation function
|
|
51
|
+
*/
|
|
52
|
+
constructor(host: HTMLElement, context: Context, options?: {
|
|
53
|
+
subscribe?: boolean | undefined;
|
|
54
|
+
validate?: ((any: any) => boolean) | undefined;
|
|
55
|
+
} | undefined);
|
|
56
|
+
host: HTMLElement;
|
|
57
|
+
context: Context;
|
|
58
|
+
subscribe: boolean;
|
|
59
|
+
validate: (any: any) => boolean;
|
|
60
|
+
value: any;
|
|
61
|
+
unsubscribe: any;
|
|
62
|
+
hostConnected(): void;
|
|
63
|
+
hostDisconnected(): void;
|
|
64
|
+
dispatchRequest(): void;
|
|
65
|
+
_callback(value: any, unsubscribe: any): void;
|
|
67
66
|
}
|
|
68
|
-
export type HostController = import(
|
|
69
|
-
export type
|
|
70
|
-
export type Context = string | Symbol
|
|
67
|
+
export type HostController = import("./element.js").HostController;
|
|
68
|
+
export type Context = string | Symbol;
|