a post-component library for building user-interfaces on the web.

add signals example (#180)

authored by tombl.dev and committed by

GitHub 63f051de 390d814e

+175 -2
+28
examples/signals/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>dhtml example with signals</title> 7 + <script type="importmap"> 8 + { 9 + "imports": { 10 + "dhtml": "../../dist/index.js", 11 + "dhtml/client": "../../dist/client.js", 12 + "alien-signals": "../../node_modules/alien-signals/esm/index.mjs" 13 + } 14 + } 15 + </script> 16 + <script type="module" src="main.js"></script> 17 + <style> 18 + @import url(../../reset.css); 19 + [data-theme='light'] { 20 + color-scheme: light; 21 + } 22 + [data-theme='dark'] { 23 + color-scheme: dark; 24 + } 25 + </style> 26 + </head> 27 + <body></body> 28 + </html>
+63
examples/signals/main.js
··· 1 + import { html } from 'dhtml' 2 + import { createRoot, invalidate } from 'dhtml/client' 3 + import { computed, effect, signal } from './signals.js' 4 + 5 + function getSystemTheme() { 6 + const media = window.matchMedia('(prefers-color-scheme: dark)') 7 + const matches = signal(media.matches) 8 + media.addEventListener('change', e => { 9 + matches(e.matches) 10 + }) 11 + return computed(() => (matches() ? 'dark' : 'light')) 12 + } 13 + 14 + function createThemeToggle(preference, systemTheme) { 15 + return computed( 16 + () => html` 17 + <select 18 + value=${preference()} 19 + onchange=${e => { 20 + preference(e.target.value) 21 + }} 22 + > 23 + ${['System', 'Light', 'Dark'].map( 24 + t => html` <option value=${t.toLowerCase()}>${t === 'System' ? `System (${systemTheme()})` : t}</option> `, 25 + )} 26 + </select> 27 + `, 28 + ) 29 + } 30 + 31 + class App { 32 + preference = signal('system') 33 + systemTheme = getSystemTheme() 34 + theme = computed(() => (this.preference() === 'system' ? this.systemTheme() : this.preference)) 35 + 36 + #themeToggle1 = createThemeToggle(this.preference, this.systemTheme) 37 + #themeToggle2 = createThemeToggle(this.preference, this.systemTheme) 38 + 39 + render() { 40 + return html` 41 + <main> 42 + <h1>Hello World</h1> 43 + ${this.#themeToggle1} ${this.#themeToggle2} 44 + </main> 45 + ` 46 + } 47 + } 48 + 49 + const app = new App() 50 + globalThis.app = app 51 + document.body.addEventListener('keypress', e => { 52 + if (e.ctrlKey && e.key === 'i') invalidate(app) 53 + }) 54 + 55 + effect(() => { 56 + if (app.preference() === 'system') { 57 + delete document.documentElement.dataset.theme 58 + } else { 59 + document.documentElement.dataset.theme = app.preference() 60 + } 61 + }) 62 + 63 + createRoot(document.body).render(app)
+15
examples/signals/package.json
··· 1 + { 2 + "name": "@dhtml-examples/signals", 3 + "private": true, 4 + "type": "module", 5 + "scripts": { 6 + "check": "tsc" 7 + }, 8 + "devDependencies": { 9 + "typescript": "~5.8.3" 10 + }, 11 + "dependencies": { 12 + "alien-signals": "^2.0.7", 13 + "dhtml": "file:../../dist" 14 + } 15 + }
+18
examples/signals/signals.js
··· 1 + import * as core from 'alien-signals' 2 + import { invalidate, onMount } from 'dhtml/client' 3 + 4 + function upgrade(rx) { 5 + rx.render = () => rx() 6 + onMount(rx, () => 7 + core.effect(() => { 8 + rx() 9 + invalidate(rx) 10 + }), 11 + ) 12 + return rx 13 + } 14 + 15 + export const signal = init => upgrade(core.signal(init)) 16 + export const computed = fn => upgrade(core.computed(fn)) 17 + 18 + export * from 'alien-signals'
+11
examples/signals/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "checkJs": true, 4 + "noEmit": true, 5 + "allowImportingTsExtensions": true, 6 + "verbatimModuleSyntax": true, 7 + "moduleResolution": "bundler", 8 + "module": "preserve", 9 + "target": "es2020" 10 + } 11 + }
+38
package-lock.json
··· 48 48 "node": ">=14.17" 49 49 } 50 50 }, 51 + "examples/signals": { 52 + "name": "@dhtml-examples/signals", 53 + "dependencies": { 54 + "alien-signals": "^2.0.7", 55 + "dhtml": "file:../../dist" 56 + }, 57 + "devDependencies": { 58 + "typescript": "~5.8.3" 59 + } 60 + }, 61 + "examples/signals/node_modules/dhtml": { 62 + "resolved": "dist", 63 + "link": true 64 + }, 65 + "examples/signals/node_modules/typescript": { 66 + "version": "5.8.3", 67 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", 68 + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", 69 + "dev": true, 70 + "license": "Apache-2.0", 71 + "bin": { 72 + "tsc": "bin/tsc", 73 + "tsserver": "bin/tsserver" 74 + }, 75 + "engines": { 76 + "node": ">=14.17" 77 + } 78 + }, 51 79 "examples/ssr": { 52 80 "name": "@dhtml-examples/ssr", 53 81 "dependencies": { ··· 192 220 }, 193 221 "node_modules/@dhtml-examples/kanban": { 194 222 "resolved": "examples/kanban", 223 + "link": true 224 + }, 225 + "node_modules/@dhtml-examples/signals": { 226 + "resolved": "examples/signals", 195 227 "link": true 196 228 }, 197 229 "node_modules/@dhtml-examples/ssr": { ··· 729 761 "engines": { 730 762 "node": ">= 14" 731 763 } 764 + }, 765 + "node_modules/alien-signals": { 766 + "version": "2.0.7", 767 + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-2.0.7.tgz", 768 + "integrity": "sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==", 769 + "license": "MIT" 732 770 }, 733 771 "node_modules/amaro": { 734 772 "version": "1.1.1",
+2 -2
src/shared.ts
··· 15 15 } 16 16 17 17 export function is_renderable(value: unknown): value is Renderable { 18 - return typeof value === 'object' && value !== null && 'render' in value 18 + return (typeof value === 'object' || typeof value === 'function') && value !== null && 'render' in value 19 19 } 20 20 21 21 export function is_iterable(value: unknown): value is Iterable<unknown> { 22 - return typeof value === 'object' && value !== null && Symbol.iterator in value 22 + return (typeof value === 'object' || typeof value === 'function') && value !== null && Symbol.iterator in value 23 23 } 24 24 25 25 export function assert(value: unknown, message?: string): asserts value {