@hot-page/fun 0.0.1 → 0.0.2
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 +375 -9
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +307 -270
- package/package.json +1 -1
- package/src/index.ts +112 -37
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -10,6 +10,13 @@ export type StylePropsFunction = (
|
|
|
10
10
|
props: Record<string, string | number | null>
|
|
11
11
|
) => void
|
|
12
12
|
|
|
13
|
+
interface EffectEntry {
|
|
14
|
+
fn: EffectFunction
|
|
15
|
+
computed?: Signal.Computed<CleanupFunction | void>
|
|
16
|
+
watcher?: any
|
|
17
|
+
cleanup?: CleanupFunction | void
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
type AttributeSignals<Attrs extends string> = {
|
|
14
21
|
[K in Attrs]: Signal.State<string | null>
|
|
15
22
|
}
|
|
@@ -103,8 +110,9 @@ function resolveOverload<Attrs extends string>(
|
|
|
103
110
|
}
|
|
104
111
|
|
|
105
112
|
const state = <T>(value: T) => new Signal.State(value)
|
|
113
|
+
const computed = <T>(fn: () => T) => new Signal.Computed(fn)
|
|
106
114
|
|
|
107
|
-
export { state, lightElement, shadowElement }
|
|
115
|
+
export { state, computed, lightElement, shadowElement }
|
|
108
116
|
|
|
109
117
|
export function define<Attrs extends string = never>(options: DefineOptions<Attrs>) {
|
|
110
118
|
const {
|
|
@@ -129,11 +137,11 @@ export function define<Attrs extends string = never>(options: DefineOptions<Attr
|
|
|
129
137
|
throw new Error(`Custom element with name ${elementName} already defined`)
|
|
130
138
|
}
|
|
131
139
|
|
|
132
|
-
|
|
140
|
+
attributes.forEach(attr => {
|
|
133
141
|
if (RESERVED_KEYS.has(attr)) {
|
|
134
142
|
throw new Error(`Attribute name "${attr}" conflicts with a reserved context property.`)
|
|
135
143
|
}
|
|
136
|
-
}
|
|
144
|
+
})
|
|
137
145
|
|
|
138
146
|
// One constructed stylesheet per element type, shared across all instances.
|
|
139
147
|
// For shadow DOM, it's adopted into each shadowRoot.
|
|
@@ -153,10 +161,9 @@ export function define<Attrs extends string = never>(options: DefineOptions<Attr
|
|
|
153
161
|
}
|
|
154
162
|
|
|
155
163
|
customElements.define(elementName, class extends HTMLElement {
|
|
156
|
-
#template
|
|
157
|
-
#watcher
|
|
158
|
-
#effects:
|
|
159
|
-
#cleanups: CleanupFunction[] = []
|
|
164
|
+
#template: Signal.Computed<ReturnType<typeof html>> | undefined
|
|
165
|
+
#watcher: any
|
|
166
|
+
#effects: EffectEntry[] = []
|
|
160
167
|
#attributeSignals: Map<string, Signal.State<string | null>> = new Map()
|
|
161
168
|
|
|
162
169
|
static get observedAttributes() {
|
|
@@ -180,11 +187,9 @@ export function define<Attrs extends string = never>(options: DefineOptions<Attr
|
|
|
180
187
|
|
|
181
188
|
const context = {
|
|
182
189
|
internals: this.attachInternals(),
|
|
183
|
-
effect: (fn: EffectFunction) => {
|
|
184
|
-
this.#effects.push(fn)
|
|
185
|
-
},
|
|
190
|
+
effect: (fn: EffectFunction) => this.#effects.push({ fn }),
|
|
186
191
|
styleProps: (props: Record<string, string | number | null>) => {
|
|
187
|
-
|
|
192
|
+
Object.keys(props).forEach(key => {
|
|
188
193
|
const value = props[key]
|
|
189
194
|
const name = toCustomProperty(key)
|
|
190
195
|
if (value === null) {
|
|
@@ -192,7 +197,7 @@ export function define<Attrs extends string = never>(options: DefineOptions<Attr
|
|
|
192
197
|
} else {
|
|
193
198
|
this.style.setProperty(name, String(value))
|
|
194
199
|
}
|
|
195
|
-
}
|
|
200
|
+
})
|
|
196
201
|
}
|
|
197
202
|
} as ComponentContext<Attrs>
|
|
198
203
|
|
|
@@ -234,42 +239,112 @@ export function define<Attrs extends string = never>(options: DefineOptions<Attr
|
|
|
234
239
|
watcher.watch(signal)
|
|
235
240
|
})
|
|
236
241
|
|
|
237
|
-
const
|
|
242
|
+
const setupResult = setup.call(this, context)
|
|
238
243
|
|
|
239
|
-
|
|
244
|
+
const target = useShadow ? this.shadowRoot! : this
|
|
240
245
|
|
|
241
|
-
|
|
242
|
-
|
|
246
|
+
if (typeof setupResult === 'function') {
|
|
247
|
+
this.#template = new Signal.Computed(() => setupResult())
|
|
243
248
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
})
|
|
249
|
+
const template = this.#template
|
|
250
|
+
const renderTemplate = () => {
|
|
251
|
+
const result = template.get()
|
|
252
|
+
if (typeof result === 'string') {
|
|
253
|
+
target.innerHTML = result
|
|
254
|
+
} else if (result !== undefined && result !== null) {
|
|
255
|
+
render(result, target)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
254
258
|
|
|
255
|
-
|
|
259
|
+
let renderPending = false
|
|
260
|
+
this.#watcher = new Signal.subtle.Watcher(() => {
|
|
261
|
+
if (renderPending) return
|
|
262
|
+
renderPending = true
|
|
263
|
+
queueMicrotask(() => {
|
|
264
|
+
renderPending = false
|
|
265
|
+
try {
|
|
266
|
+
renderTemplate()
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error(
|
|
269
|
+
`Error in render function for <${tagName}> fun element: `,
|
|
270
|
+
error,
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
this.#watcher.watch()
|
|
274
|
+
})
|
|
275
|
+
})
|
|
256
276
|
|
|
257
|
-
|
|
277
|
+
this.#watcher.watch(this.#template)
|
|
278
|
+
|
|
279
|
+
renderTemplate()
|
|
280
|
+
} else if (setupResult === undefined) {
|
|
281
|
+
} else if (typeof setupResult === 'string') {
|
|
282
|
+
target.innerHTML = setupResult
|
|
283
|
+
} else if (typeof setupResult === 'object' && '_$litType$' in (setupResult as object)) {
|
|
284
|
+
render(setupResult, target)
|
|
285
|
+
} else {
|
|
286
|
+
console.error(
|
|
287
|
+
`Setup function for <${tagName}> returned an unexpected value. ` +
|
|
288
|
+
`Expected a render function, a template (html\`...\`), a string, or nothing. ` +
|
|
289
|
+
`Got: ${typeof setupResult}`
|
|
290
|
+
)
|
|
291
|
+
}
|
|
258
292
|
}
|
|
259
293
|
|
|
260
294
|
connectedCallback() {
|
|
261
|
-
this.#watcher
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
295
|
+
if (this.#watcher && this.#template) {
|
|
296
|
+
this.#watcher.watch(this.#template)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
this.#effects.forEach(entry => {
|
|
300
|
+
try {
|
|
301
|
+
entry.computed = new Signal.Computed(() => entry.fn())
|
|
302
|
+
|
|
303
|
+
entry.watcher = new Signal.subtle.Watcher(() => {
|
|
304
|
+
queueMicrotask(() => {
|
|
305
|
+
if (typeof entry.cleanup === 'function') {
|
|
306
|
+
entry.cleanup()
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
entry.computed = new Signal.Computed(() => entry.fn())
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
entry.cleanup = entry.computed.get()
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error(
|
|
315
|
+
`Error in effect for <${elementName}> fun element: `,
|
|
316
|
+
error
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
entry.watcher.watch(entry.computed)
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
entry.cleanup = entry.computed.get()
|
|
325
|
+
entry.watcher.watch(entry.computed)
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.error(
|
|
328
|
+
`Error in effect for <${elementName}> fun element: `,
|
|
329
|
+
error
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
})
|
|
267
333
|
}
|
|
268
334
|
|
|
269
335
|
disconnectedCallback() {
|
|
270
|
-
this.#watcher
|
|
271
|
-
|
|
272
|
-
|
|
336
|
+
if (this.#watcher && this.#template) {
|
|
337
|
+
this.#watcher.unwatch(this.#template)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Clean up all effects and stop watching
|
|
341
|
+
this.#effects.forEach(effectEntry => {
|
|
342
|
+
effectEntry.watcher.unwatch(effectEntry.computed)
|
|
343
|
+
if (typeof effectEntry.cleanup === 'function') {
|
|
344
|
+
effectEntry.cleanup()
|
|
345
|
+
}
|
|
346
|
+
effectEntry.cleanup = undefined
|
|
347
|
+
})
|
|
273
348
|
}
|
|
274
349
|
|
|
275
350
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
|