@wrnrlr/prelude 0.2.16 → 0.2.18

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.
@@ -0,0 +1,112 @@
1
+ <!DOCTYPE html>
2
+ <head>
3
+ <title>Widget</title>
4
+ <script type="importmap">{"imports": {"prelude": "../src/mod.ts"}}</script>
5
+ <link rel="stylesheet" href="./style.css"/>
6
+ </head>
7
+ <style>
8
+ script,script[type="module"] {
9
+ display: block;
10
+ }
11
+ </style>
12
+
13
+ <a href="/index.html">Home</a>
14
+
15
+ <h1>Widgets</h1>
16
+ <ul>
17
+ <li><a href="#dialog">Dialog</a></li>
18
+ <li><a href="#input">Input</a></li>
19
+ <li><a href="#checkbox">Checkbox</a></li>
20
+ <li><a href="#select">Select</a></li>
21
+ </ul>
22
+
23
+ <h2 id="dialog">Dialog</h2>
24
+ <div class="example" id="dialog_example"></div>
25
+ <details>
26
+ <summary>Code</summary>
27
+ <script type="module">
28
+ import { h, signal, render, Dialog, useDialog } from '../src/mod.ts'
29
+
30
+ function HelloDialog(props) {
31
+ let dialog
32
+ return h('', {ref(e){dialog=e}}, [
33
+ 'Hello',
34
+ h('button', {onClick:_=>dialog.close()}, 'Close')
35
+ ])
36
+ }
37
+
38
+ function MenuDialog(props) {
39
+ let dialog
40
+ return h('', {ref(e){dialog=e}}, [
41
+ 'Hello',
42
+ h('button', {onClick:_=>dialog.close()}, 'Close')
43
+ ])
44
+ }
45
+
46
+ function DialogExample() {
47
+ const showModal = signal(false)
48
+ const showDialog = signal(false)
49
+ return h('', {style:'display: flex; gap: 0.25rem'}, [
50
+ h('button', {onClick:e=>showModal(true)}, 'Modal'),
51
+ h(Dialog, {show:()=>showModal, modal: true}, h(HelloDialog)),
52
+ h('button', {onClick:e=>showDialog(true)}, 'Dialog'),
53
+ h(Dialog, {show:()=>showDialog}, h(MenuDialog)),
54
+ ])
55
+ }
56
+
57
+ render(DialogExample, document.getElementById('dialog_example'))
58
+ </script>
59
+ </details>
60
+
61
+ <h2 id="input">Input</h2>
62
+ <div class="example" id="input_example"></div>
63
+
64
+ <h2>Input autosize</h2>
65
+ <div class="example" id="input_autosize"></div>
66
+ <script type="module">
67
+ import { h, signal, render, Input } from '../src/mod.ts'
68
+
69
+ function InputExample() {
70
+ const name = signal('Bob')
71
+ return h(Input, {value:()=>name})
72
+ }
73
+
74
+ render(InputExample, document.getElementById('input_autosize'))
75
+ </script>
76
+
77
+ <h2 id="checkbox">Checkbox</h2>
78
+ <div class="example" id="checkbox_example"></div>
79
+ <script type="module">
80
+ import { h, signal, render, Checkbox } from "../src/mod.ts"
81
+
82
+ function CheckboxExample() {
83
+ const valid = signal(false)
84
+ return h(Checkbox, {value:()=>valid})
85
+ }
86
+
87
+ render(CheckboxExample, document.getElementById('checkbox_example'))
88
+ </script>
89
+
90
+ <h2 id="select">Select</h2>
91
+ <div class="example " id="select_car"></div>
92
+ <script type="module">
93
+ import { h, signal, memo, render, Select } from "../src/mod.ts"
94
+
95
+ function SelectCar() {
96
+ const cars = { 'Audi': ['A1','A6'], 'BMW': ['B5', 'BX'] }
97
+ const companies = Object.keys(cars)
98
+ const company = signal()
99
+ const model = signal('')
100
+ const models = memo(() => cars[company()] || null)
101
+ return [
102
+ '', h(Select, {options:companies, value:()=>company}),
103
+ '', h(Select, {options:models, value:()=>model})
104
+ ]
105
+ }
106
+
107
+ render(SelectCar, document.getElementById('select_car'))
108
+ </script>
109
+
110
+ <style>
111
+
112
+ </style>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wrnrlr/prelude",
3
3
  "type": "module",
4
- "version": "0.2.16",
4
+ "version": "0.2.18",
5
5
  "author": "Werner Laurensse",
6
6
  "description": "A signal based frontend library with fine-grained reactivity",
7
7
  "main": "./src/mod.ts",
package/readme.md CHANGED
@@ -157,7 +157,7 @@ const useCounter = () => useContext(CounterCtx)
157
157
  function CounterProvider(props) {
158
158
  const count = signal(0)
159
159
  const increment = () => count(i=>i+1)
160
- return h(CounterCtx.Provider, {value:[count,increment]}, props.children)
160
+ return h(CounterCtx, {value:[count,increment]}, props.children)
161
161
  }
162
162
 
163
163
  function Counter() {
@@ -227,3 +227,7 @@ deno task test
227
227
  * [Homepage](https://wrnrlr.github.io/prelude)
228
228
  * [NPM](https://www.npmjs.com/package/@wrnrlr/prelude)
229
229
  * [JSR](https://jsr.io/@wrnrlr/prelude)
230
+
231
+ ## More Links
232
+
233
+ * [Fine-grained reactivity - A deep dive into SolidJS](https://github.com/everweij/solidjs-fine-grained-reactivity)
package/src/canvas.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- function Canvas(props) {
2
+ export function Canvas(props) {
3
3
 
4
4
  }
5
5
 
package/src/form.js CHANGED
@@ -1,20 +1,93 @@
1
- import {signal, context, useContext} from './reactive.ts'
1
+ import { signal, memo, effect, context, useContext, renderEffect } from './reactive.ts'
2
+ import { Show } from './show.ts'
3
+ import { List } from './list.ts'
4
+ import { h } from './hyperscript.ts'
5
+ import { nbsp } from './constants.ts'
2
6
 
3
7
  const Ctx = context()
4
8
  const useForm = () => useContext(Ctx)
5
9
 
6
- function Form(props) {
10
+ export function Form(props) {
7
11
  return h('form', h(Ctx.Provider, {value:[props.value]}, props.children))
8
12
  }
9
13
 
10
- function Input(props) {
11
- if (props.name) {
12
- const [form] = useForm()
13
- props.value = wrap(form, props.name)
14
- }
14
+ export function Input(props) {
15
+ props.onInput = ((e) => props.value((e?.target)?.value))
16
+ props.autocomplete = props.autocomplete || 'off'
17
+ // const onInput = ((e) => props.value((e?.target)?.value))
18
+ if (props.autosize) props.ref = (r) => props.autosize(r, props)
15
19
  return h('input', props)
16
20
  }
17
21
 
22
+ export function autosize(r, props) {
23
+ const style = globalThis.getComputedStyle(r)
24
+ renderEffect(() => {
25
+ const font = style.getPropertyValue('font')
26
+ const metrics = fontMetrics(font, props.value().toString())
27
+ r.style.width = metrics.width + 'px'
28
+ })
29
+ }
30
+
31
+ let _fontMetricsCtx
32
+
33
+ function fontMetrics(font, text) {
34
+ if (!_fontMetricsCtx) _fontMetricsCtx = document.createElement('canvas').getContext('2d')
35
+ _fontMetricsCtx.font = font;
36
+ return _fontMetricsCtx.measureText(text)
37
+ }
38
+
39
+ export function Checkbox(props) {
40
+ return h('input',{
41
+ ...props,
42
+ type:'checkbox',
43
+ checked:props.value,
44
+ onInput:e => props.value(e.target.checked)
45
+ })
46
+ }
47
+
48
+ export function Select(props) {
49
+ // const description = wrap(props.value,'description')
50
+ const show = signal(false)
51
+ const selected = memo(()=>{
52
+ const options = typeof props.options === 'function' ? props.options() : props.options || []
53
+ return {options}
54
+ })
55
+ const fallback = props.placeholder ? h('span', props.placeholder) : 'nbsp'
56
+ return h('.select', [
57
+ h('button', {onClick:e=>show(s=>!s)}, h(Show, {when:()=>props.value, fallback}, ()=>props.value() || nbsp)),
58
+ h(Show, {when: show}, h('.options', {style:'position:absolute'}, h(List, {each:()=>selected().options},
59
+ (option) => h('.option', {
60
+ onClick: (_) => { props.value(option()); show(false) },
61
+ style: 'cursor: pointer'
62
+ }, option)
63
+ )))
64
+ ])
65
+ }
66
+
67
+ const DialogCtx = context(null)
68
+ export const useDialog = () => useContext(DialogCtx)
69
+
70
+ export function Dialog(props) {
71
+ let dialog
72
+ const close = () => dialog.close()
73
+ const show = props.modal ? () => dialog.showModal() : () => dialog.show()
74
+ return h('dialog', {
75
+ ref(dia) {
76
+ dialog = dia
77
+ const closeHandler = (_) => props.show(false)
78
+ effect(() => props.show() && show())
79
+ dialog.addEventListener('close', closeHandler)
80
+ if (props.ref) props.ref(dialog)
81
+ return () => dialog.removeEventListener('close', closeHandler)
82
+ }
83
+ }, h(DialogCtx, {value:()=>[{close:close}]}, props.children))
84
+ }
85
+
18
86
  function CurrencyInput(props) {
19
87
  return h(Input, props)
20
88
  }
89
+
90
+ export function Button(props) {
91
+ let checked
92
+ return h('button', {}, props.children)
93
+ }
@@ -1,167 +1,211 @@
1
1
  import type {DOMElements} from './constants.ts'
2
- import type {Runtime,Mountable} from './runtime.ts'
2
+ import type {Mountable} from './runtime.ts'
3
3
  import {r} from './runtime.ts'
4
4
 
5
- const ELEMENT: unique symbol = Symbol(), {isArray} = Array
6
-
7
- // export type Mountable = View | HTMLElement | string | number | bigint | symbol | boolean | Date | Mountable[];
8
- export type Component<T> = ((props:T) => Mountable) | ((props:T) => Mountable[])
9
- export type Tag = typeof DOMElements extends Set<infer K> ? K : never;
10
- export type Child = { ():Child } | Element | Child[] | string | number | symbol | bigint | boolean | Date | Record<string,unknown> | {():Child, [ELEMENT]:boolean}
11
- export type View = {():void, [ELEMENT]?:boolean}
5
+ // const document = globalThis.document
12
6
 
13
7
  export const h = hyperscript(r)
14
8
 
15
- // export type PropsKeys = typeof Properties extends Set<infer K> ? K : never;
16
- // export type BooleanProps = typeof BooleanAttributes ext ends Set<infer K> ? K : never;
17
- // export type HandlerProps = typeof DelegatedEvents extends Set<infer K> ? K : never;
18
- // export type ChildProps = {children?:any[]}
19
- // export type ElementProps = BooleanProps & HandlerProps & ChildProps & {class: string}
20
- // export type ComponentProps = { children?:Child }
21
- // export type Props = Record<string, any>
22
- // type AllProps = ElementProps | ComponentProps
23
- // type EmptyProps = Record<string,never>
24
- // const Fragment:Component<Props> = <T extends Props>(props:T):Mountable => (props as any).children
9
+ type MountableElement = Element | Document | ShadowRoot | DocumentFragment | Node
10
+
11
+ interface Runtime {
12
+ insert(parent: MountableElement, accessor: any, marker?: Node | null, init?: any): any;
13
+ spread(node: Element, accessor: any, isSVG?: Boolean, skipChildren?: Boolean): void;
14
+ assign(node: Element, props: any, isSVG?: Boolean, skipChildren?: Boolean): void;
15
+ component(Comp: (props: any) => any, props: any): any;
16
+ SVGElements: Set<string>;
17
+ }
18
+
19
+ const $ELEMENT = Symbol("hyper-element");
25
20
 
26
- export type TagParser = <T extends string>(s:T) => {name:string,id?:string,classes:string[],namespace?:string}
21
+ type ExpandableNode = Node & { [key: string]: any };
22
+ type Props = { [key: string]: any };
27
23
 
28
24
  export type HyperScript = {
29
- (first: Tag): View
30
- <P>(first: Tag, second: P): View
31
- <C extends Child>(first: Tag, second: C): View
32
- <C extends Child, P>(first: Tag, second: P, third: C): View
33
-
34
- (first: Component<Record<string,never>>): View
35
- <P extends Record<string, unknown>>(first: Component<P>, second: P): View
36
- <C>(first: Component<{children:C}>, second: C): View
37
- <P extends Record<string, unknown>, C>(first: Component<P & {children:C}>, second:P, third:C): View
38
- }
25
+ (...args: any[]): () => ExpandableNode | ExpandableNode[];
26
+ Fragment: (props: {
27
+ children: (() => ExpandableNode) | (() => ExpandableNode)[];
28
+ }) => ExpandableNode[];
29
+ };
39
30
 
40
- /**
41
- @param r
42
- @param patch
43
- @group Hyperscript
44
- */
45
- export function hyperscript(r: Runtime, parseTag: TagParser = parseHtmlTag): HyperScript {
46
-
47
- function item(e: Element, c: Child, m?: null): void {
48
- if (c===null) return
49
- const t = typeof c
50
- if (isArray(c))
51
- for (const child of c)
52
- item(e, child, m)
53
- else if (t === 'object' && r.isChild(c))
54
- r.insert(e, c, m)
55
- else if (t === 'string')
56
- e.appendChild(r.text((c as string)))
57
- else if (t === 'function') {
58
- // while (c[ELEMENT]?.call) c = (c as any)()
59
- r.insert(e, c, m)
60
- } else e.appendChild(r.text(c.toString()))
61
- }
31
+ // Inspired by https://github.com/hyperhype/hyperscript
32
+ export function hyperscript(r: Runtime): HyperScript {
33
+ function h() {
34
+ let args: any = [].slice.call(arguments),
35
+ e: ExpandableNode | undefined,
36
+ classes:string[] = [],
37
+ multiExpression = false;
62
38
 
63
- return function h<P extends Record<string,unknown>, C = never>(
64
- first: Tag | Component<P>,
65
- second?: P | C,
66
- third?: C
67
- ): View {
68
- let props: P
69
- let children: Child
70
-
71
- if (typeof second === 'object' && !isArray(second)) {
72
- children = (third as Child) || [];
73
- props = ((second ?? {}) as P & {children:C})
74
- } else {
75
- children = (second as Child) || []
76
- props = {} as P & {children:C}
39
+ while (Array.isArray(args[0])) args = args[0];
40
+ if (args[0][$ELEMENT]) args.unshift(h.Fragment);
41
+ typeof args[0] === "string" && detectMultiExpression(args);
42
+ const ret: (() => ExpandableNode) & { [$ELEMENT]?: boolean } = () => {
43
+ while (args.length) item(args.shift());
44
+ if (e instanceof globalThis.Element && classes.length) e.classList.add(...classes)
45
+ return e as ExpandableNode;
46
+ };
47
+ ret[$ELEMENT] = true;
48
+ return ret;
49
+
50
+ function item(l: any) {
51
+ const type = typeof l;
52
+ if (l == null) void 0;
53
+ else if ("string" === type) {
54
+ if (!e) parseHtmlTag(l);
55
+ else e.appendChild(globalThis.document.createTextNode(l));
56
+ } else if (
57
+ "number" === type ||
58
+ "boolean" === type ||
59
+ "bigint" === type ||
60
+ "symbol" === type ||
61
+ l instanceof Date ||
62
+ l instanceof RegExp
63
+ ) {
64
+ (e as Node).appendChild(globalThis.document.createTextNode(l.toString()));
65
+ } else if (Array.isArray(l)) {
66
+ for (let i = 0; i < l.length; i++) item(l[i]);
67
+ } else if (l instanceof globalThis.Element) {
68
+ r.insert(e as globalThis.Element, l, multiExpression ? null : undefined);
69
+ } else if ("object" === type) {
70
+ let dynamic = false;
71
+ const d = Object.getOwnPropertyDescriptors(l);
72
+ for (const k in d) {
73
+ if (k === "class" && classes.length !== 0) {
74
+ console.log('classes',classes)
75
+ const fixedClasses = classes.join(" "),
76
+ value = typeof d["class"].value === "function" ?
77
+ ()=>[...classes,...(d["class"].value()??'').split(' ')].filter(c=>c).join(' ') :
78
+ [...classes,...(d["class"].value??'').split(' ')].filter(c=>c).join(' ')
79
+ Object.defineProperty(l,"class",{...d[k],value})
80
+ // classes = []
81
+ }
82
+ if (k !== "ref" && k.slice(0, 2) !== "on" && typeof d[k].value === "function") {
83
+ dynamicProperty(l, k);
84
+ dynamic = true;
85
+ } else if (d[k].get) dynamic = true;
86
+ }
87
+ dynamic
88
+ ? r.spread(e as globalThis.Element, l, !!args.length)
89
+ : r.assign(e as globalThis.Element, l, !!args.length);
90
+ } else if ("function" === type) {
91
+ if (!e) {
92
+ let props: Props | undefined,
93
+ next = args[0];
94
+ if (
95
+ next == null ||
96
+ (typeof next === "object" && !Array.isArray(next) && !(next instanceof globalThis.Element))
97
+ )
98
+ props = args.shift();
99
+ props || (props = {});
100
+ if (args.length) {
101
+ props.children = args.length > 1 ? args : args[0];
102
+ }
103
+ const d = Object.getOwnPropertyDescriptors(props);
104
+ for (const k in d) {
105
+ if (Array.isArray(d[k].value)) {
106
+ const list = d[k].value;
107
+ props[k] = () => {
108
+ for (let i = 0; i < list.length; i++) {
109
+ while (list[i][$ELEMENT]) list[i] = list[i]();
110
+ }
111
+ return list;
112
+ };
113
+ dynamicProperty(props, k);
114
+ } else if (typeof d[k].value === "function" && !d[k].value.length)
115
+ dynamicProperty(props, k);
116
+ }
117
+ e = r.component(l, props);
118
+ args = [];
119
+ } else {
120
+ while ((l as any)[$ELEMENT]) l = ((l as unknown) as () => ExpandableNode)();
121
+ r.insert(e as globalThis.Element, l, multiExpression ? null : undefined);
122
+ }
123
+ }
77
124
  }
125
+ function parseHtmlTag(s: string) {
126
+ let i:number
127
+
128
+ i = s.indexOf('#')
129
+ if (i===-1) i = s.indexOf('.')
130
+ if (i===-1) i = s.length
131
+ const name = s.slice(0, i) || 'div'
132
+ e = r.SVGElements.has(name)
133
+ ? globalThis.document.createElementNS("http://www.w3.org/2000/svg", name)
134
+ : globalThis.document.createElement(name);
135
+ s = s.slice(i)
136
+
137
+ if (s[0]==='#') {
138
+ i = s.indexOf('.')
139
+ if (i===-1) i = s.length
140
+ e!.setAttribute("id", s.slice(1, i))
141
+ s = s.slice(i)
142
+ }
78
143
 
79
- let ret:View
80
-
81
- if (typeof first === 'string') {
82
- const tag = parseTag(first)
83
- const multiExpression = detectMultiExpression(children) ? null : undefined
84
- const e = r.element(tag.name)
85
- const props2 = props as P & {class?: string|(()=>string)}
86
- if (tag.id) e.setAttribute('id',tag.id)
87
- if (tag.classes?.length) {
88
- const cd = Object.getOwnPropertyDescriptor(props2,'class') ?? ({value:'',writable:true,enumerable:true});
89
- props2.class = (cd.value?.call) ?
90
- () => [...tag.classes,...(cd.value()??'').split(' ')].filter(c=>c).join(' ') :
91
- [...tag.classes,...(cd.value??'').split(' ')].filter(c=>c).join(' ')
144
+ while(s[0]==='.') {
145
+ i = s.indexOf('.',1)
146
+ if (i===-1) i = s.length
147
+ classes.push(s.slice(1, i))
148
+ s = s.slice(i)
92
149
  }
93
- // if (patch) patch(props2)
94
- let dynamic = false
95
- const d = Object.getOwnPropertyDescriptors(props2)
96
- for (const k in d) {
97
- if (k !== 'ref' && !k.startsWith('on') && typeof d[k].value === 'function') {
98
- dynamicProperty(props2, k)
99
- dynamic = true
100
- } else if (d[k].get) dynamic = true
150
+ }
151
+ function parseClass(string: string) {
152
+ const m = string.split(/([\.#]?[^\s#.]+)/);
153
+ if (/^\.|#/.test(m[1])) e = globalThis.document.createElement("div");
154
+ for (let i = 0; i < m.length; i++) {
155
+ let v = m[i]
156
+ const s = v.substring(1, v.length);
157
+ if (!v) v = 'div';
158
+ if (!e)
159
+ e = r.SVGElements.has(v)
160
+ ? globalThis.document.createElementNS("http://www.w3.org/2000/svg", v)
161
+ : globalThis.document.createElement(v);
162
+ if (v[0] === ".") classes.push(s);
163
+ else if (v[0] === "#") e.setAttribute("id", s);
101
164
  }
102
- (dynamic ? r.spread : r.assign) (e, props2, !!(children as {length?: number})?.length)
103
- item(e, children, multiExpression)
104
- ret = () => e
105
- } else {
106
- const d = Object.getOwnPropertyDescriptors(props)
107
- if (children) (props as unknown as {children:unknown}).children = children
108
- for (const k in d) {
109
- if (isArray(d[k].value)) {
110
- const list = d[k].value;
111
- (props as Record<string, ()=>unknown>)[k] = () => {
112
- for (let i = 0; i < list.length; i++)
113
- while (list[i][ELEMENT]) list[i] = list[i]()
114
- return list
115
- }
116
- dynamicProperty(props, k)
117
- } else if (typeof d[k].value==='function' && !d[k].value.length) { // A function with zero arguments
118
- dynamicProperty(props, k)
165
+ }
166
+ function detectMultiExpression(list: any[]) {
167
+ for (let i = 1; i < list.length; i++) {
168
+ if (typeof list[i] === "function") {
169
+ multiExpression = true;
170
+ return;
171
+ } else if (Array.isArray(list[i])) {
172
+ detectMultiExpression(list[i]);
119
173
  }
120
174
  }
121
- const e = r.component(() => (first as Component<P>)(props))
122
- ret = () => e
123
175
  }
124
- ret[ELEMENT] = true
125
- return ret
126
176
  }
127
- }
128
177
 
129
- function detectMultiExpression(children: Child): boolean {
130
- if (typeof children === 'function') return true
131
- else if (!isArray(children)) return false
132
- for (const i of children) {
133
- if (typeof i === 'function') return true
134
- else if (isArray(i)) return detectMultiExpression(i)
135
- }
136
- return false
178
+ h.Fragment = (props: any) => props.children;
179
+ return h;
137
180
  }
138
181
 
182
+
139
183
  // ^([a-zA-Z]\w*)?(#[a-zA-Z][-\w]*)?(.[a-zA-Z][-\w]*)*
140
- export function parseHtmlTag(s:Tag) {
141
- const classes:string[] = [];
142
- let id:string|undefined = undefined, i:number
143
-
144
- i = s.indexOf('#')
145
- if (i===-1) i = s.indexOf('.')
146
- if (i===-1) i = s.length
147
- const name = s.slice(0, i) || 'div'
148
- s = s.slice(i)
149
-
150
- if (s[0]==='#') {
151
- i = s.indexOf('.')
152
- if (i===-1) i = s.length
153
- id = s.slice(1, i)
154
- s = s.slice(i)
155
- }
184
+ // export function parseHtmlTag(s:Tag) {
185
+ // const classes:string[] = [];
186
+ // let id:string|undefined = undefined, i:number
156
187
 
157
- while(s[0]==='.') {
158
- i = s.indexOf('.',1)
159
- if (i===-1) i = s.length
160
- classes.push(s.slice(1, i))
161
- s = s.slice(i)
162
- }
163
- return {name:name as string,classes,id:id}
164
- }
188
+ // i = s.indexOf('#')
189
+ // if (i===-1) i = s.indexOf('.')
190
+ // if (i===-1) i = s.length
191
+ // const name = s.slice(0, i) || 'div'
192
+ // s = s.slice(i)
193
+
194
+ // if (s[0]==='#') {
195
+ // i = s.indexOf('.')
196
+ // if (i===-1) i = s.length
197
+ // id = s.slice(1, i)
198
+ // s = s.slice(i)
199
+ // }
200
+
201
+ // while(s[0]==='.') {
202
+ // i = s.indexOf('.',1)
203
+ // if (i===-1) i = s.length
204
+ // classes.push(s.slice(1, i))
205
+ // s = s.slice(i)
206
+ // }
207
+ // return {name:name as string,classes,id:id}
208
+ // }
165
209
 
166
210
  function dynamicProperty<T>(props: Record<string, unknown>, key: string) {
167
211
  const src = props[key] as ()=>unknown
package/src/mod.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export type {Getter,Setter,Signal} from './reactive.ts'
2
- export {signal,effect,untrack,batch,memo,root,context,useContext,wrap,fuse,onMount,onCleanup} from './reactive.ts'
2
+ export {signal,effect,untrack,batch,memo,root,context,useContext,wrap,fuse,onMount,onCleanup,renderEffect} from './reactive.ts'
3
3
  export {nbsp} from './constants.ts'
4
4
  export {List} from './list.ts'
5
5
  export {Show} from './show.ts'
@@ -10,6 +10,7 @@ export {HashRouter} from './router.js'
10
10
  export {resource,makeAbortable,abortable} from './resource.js'
11
11
  import {r} from './runtime.ts'
12
12
  export const render = r.render
13
+ export {Dialog,useDialog,Input,Checkbox,Select} from './form.js'
13
14
 
14
15
  // const r:Runtime = /*#__PURE__*/ (typeof window === 'object') ? runtime(window) : undefined as unknown as Runtime
15
16
 
package/src/reactive.ts CHANGED
@@ -59,13 +59,6 @@ export type Setter<T> = {
59
59
  (value: T): T
60
60
  }
61
61
 
62
- export type Context<T> = {
63
- id: symbol,
64
- defaultValue: T,
65
- get(): T,
66
- set(value: T): void
67
- }
68
-
69
62
  export type Options<T> = {
70
63
  equals?: false | EqualsFn<T>
71
64
  }
@@ -297,22 +290,30 @@ export function root<T>(fn: RootFn<T>): T {
297
290
  return new Root().wrap(fn)
298
291
  }
299
292
 
300
- export function context<T>(): Context<T | undefined>;
301
- export function context<T>(defaultValue: T): Context<T>;
293
+ export type Context<T> = {
294
+ id: symbol,
295
+ defaultValue: T,
296
+ get(): T,
297
+ set(value: T): void
298
+ }
299
+
300
+ export function context<T>(): Context<T | undefined>
301
+ export function context<T>(defaultValue: T): Context<T>
302
302
  export function context<T>(defaultValue?: T) {
303
303
  const id = Symbol()
304
- const get = (): T | undefined => OBSERVER?.get ( id ) ?? defaultValue
305
- const set = ( value: T ): void => OBSERVER?.set ( id, value )
306
- const s = {id, defaultValue, get, set}
307
- const f = Object.assign((props:any) => {
308
- set(props.value)
304
+ const s = {id, defaultValue}
305
+ return Object.assign((props:any) => {
306
+ OBSERVER?.set(id, props.value)
309
307
  return () => props.children.call ? props.children() : props.children
310
- }, s)
311
- return f as unknown as Context<T>
308
+ }, s) as unknown as Context<T>
309
+ }
310
+
311
+ export function useContext<T>(ctx: Context<T>): T {
312
+ return OBSERVER?.get(ctx.id) ?? ctx.defaultValue
312
313
  }
313
314
 
314
- export function useContext<T>(context: Context<T>): T {
315
- return context.get()
315
+ export function renderEffect(fn:()=>void) {
316
+ return globalThis.requestAnimationFrame(()=>effect(fn))
316
317
  }
317
318
 
318
319
  export type S<T> = Getter<T> | Setter<T>