@hot-page/fun 0.0.1 → 0.0.3
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 +322 -277
- package/package.json +1 -1
- package/src/index.ts +111 -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,10 @@ 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
|
-
#
|
|
159
|
-
#
|
|
164
|
+
#template: Signal.Computed<ReturnType<typeof html>> | undefined
|
|
165
|
+
#watcher: any
|
|
166
|
+
#renderTemplate: (() => void) | undefined
|
|
167
|
+
#effects: EffectEntry[] = []
|
|
160
168
|
#attributeSignals: Map<string, Signal.State<string | null>> = new Map()
|
|
161
169
|
|
|
162
170
|
static get observedAttributes() {
|
|
@@ -180,11 +188,9 @@ export function define<Attrs extends string = never>(options: DefineOptions<Attr
|
|
|
180
188
|
|
|
181
189
|
const context = {
|
|
182
190
|
internals: this.attachInternals(),
|
|
183
|
-
effect: (fn: EffectFunction) => {
|
|
184
|
-
this.#effects.push(fn)
|
|
185
|
-
},
|
|
191
|
+
effect: (fn: EffectFunction) => this.#effects.push({ fn }),
|
|
186
192
|
styleProps: (props: Record<string, string | number | null>) => {
|
|
187
|
-
|
|
193
|
+
Object.keys(props).forEach(key => {
|
|
188
194
|
const value = props[key]
|
|
189
195
|
const name = toCustomProperty(key)
|
|
190
196
|
if (value === null) {
|
|
@@ -192,7 +198,7 @@ export function define<Attrs extends string = never>(options: DefineOptions<Attr
|
|
|
192
198
|
} else {
|
|
193
199
|
this.style.setProperty(name, String(value))
|
|
194
200
|
}
|
|
195
|
-
}
|
|
201
|
+
})
|
|
196
202
|
}
|
|
197
203
|
} as ComponentContext<Attrs>
|
|
198
204
|
|
|
@@ -234,42 +240,110 @@ export function define<Attrs extends string = never>(options: DefineOptions<Attr
|
|
|
234
240
|
watcher.watch(signal)
|
|
235
241
|
})
|
|
236
242
|
|
|
237
|
-
const
|
|
243
|
+
const setupResult = setup.call(this, context)
|
|
238
244
|
|
|
239
|
-
|
|
245
|
+
const target = useShadow ? this.shadowRoot! : this
|
|
240
246
|
|
|
241
|
-
|
|
242
|
-
|
|
247
|
+
if (typeof setupResult === 'function') {
|
|
248
|
+
this.#template = new Signal.Computed(() => setupResult())
|
|
243
249
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
})
|
|
253
|
-
})
|
|
250
|
+
this.#renderTemplate = () => {
|
|
251
|
+
const result = this.#template!.get()
|
|
252
|
+
if (typeof result === 'string') {
|
|
253
|
+
target.innerHTML = result
|
|
254
|
+
} else {
|
|
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
|
+
this.#renderTemplate!()
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error(
|
|
269
|
+
`Error in render function for <${elementName}> fun element: `,
|
|
270
|
+
error,
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
this.#watcher.watch()
|
|
274
|
+
})
|
|
275
|
+
})
|
|
256
276
|
|
|
257
|
-
|
|
277
|
+
} else if (typeof setupResult === 'string') {
|
|
278
|
+
target.innerHTML = setupResult
|
|
279
|
+
} else if (typeof setupResult === 'object' && '_$litType$' in (setupResult as object)) {
|
|
280
|
+
render(setupResult, target)
|
|
281
|
+
} else if (setupResult === undefined) {
|
|
282
|
+
return
|
|
283
|
+
} else {
|
|
284
|
+
console.error(
|
|
285
|
+
`Setup function for <${elementName}> returned an unexpected value. ` +
|
|
286
|
+
`Expected a render function, a template (html\`...\`), a string, or nothing. ` +
|
|
287
|
+
`Got: ${typeof setupResult}`
|
|
288
|
+
)
|
|
289
|
+
}
|
|
258
290
|
}
|
|
259
291
|
|
|
260
292
|
connectedCallback() {
|
|
261
|
-
this.#watcher
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
293
|
+
if (this.#watcher && this.#template) {
|
|
294
|
+
this.#watcher.watch(this.#template)
|
|
295
|
+
this.#renderTemplate?.()
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this.#effects.forEach(entry => {
|
|
299
|
+
try {
|
|
300
|
+
entry.computed = new Signal.Computed(() => entry.fn())
|
|
301
|
+
|
|
302
|
+
entry.watcher = new Signal.subtle.Watcher(() => {
|
|
303
|
+
queueMicrotask(() => {
|
|
304
|
+
if (typeof entry.cleanup === 'function') {
|
|
305
|
+
entry.cleanup()
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
entry.computed = new Signal.Computed(() => entry.fn())
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
entry.cleanup = entry.computed.get()
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error(
|
|
314
|
+
`Error in effect for <${elementName}> fun element: `,
|
|
315
|
+
error
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
entry.watcher.watch(entry.computed)
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
entry.cleanup = entry.computed.get()
|
|
324
|
+
entry.watcher.watch(entry.computed)
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.error(
|
|
327
|
+
`Error in effect for <${elementName}> fun element: `,
|
|
328
|
+
error
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
})
|
|
267
332
|
}
|
|
268
333
|
|
|
269
334
|
disconnectedCallback() {
|
|
270
|
-
this.#watcher
|
|
271
|
-
|
|
272
|
-
|
|
335
|
+
if (this.#watcher && this.#template) {
|
|
336
|
+
this.#watcher.unwatch(this.#template)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Clean up all effects and stop watching
|
|
340
|
+
this.#effects.forEach(effectEntry => {
|
|
341
|
+
effectEntry.watcher.unwatch(effectEntry.computed)
|
|
342
|
+
if (typeof effectEntry.cleanup === 'function') {
|
|
343
|
+
effectEntry.cleanup()
|
|
344
|
+
}
|
|
345
|
+
effectEntry.cleanup = undefined
|
|
346
|
+
})
|
|
273
347
|
}
|
|
274
348
|
|
|
275
349
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
|