@wrnrlr/prelude 0.1.9 → 0.2.1
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/.github/workflows/publish.yml +12 -11
- package/{deno.json → deno.jsonc} +10 -9
- package/example/index.html +43 -0
- package/package.json +6 -2
- package/src/constants.ts +110 -52
- package/src/controlflow.ts +140 -92
- package/src/hyperscript.ts +81 -106
- package/src/mod.ts +11 -19
- package/src/reactive.ts +29 -17
- package/src/runtime.ts +126 -66
- package/test/hyperscript.js +7 -6
- package/test/reactive.js +22 -2
- package/test/types.ts +44 -0
- package/src/components.js +0 -20
- package/src/ui/accordion.ts +0 -8
- package/src/ui/button.ts +0 -9
- package/src/ui/canvas.ts +0 -8
- package/src/ui/date.ts +0 -8
- package/src/ui/dialog.ts +0 -12
- package/src/ui/filter.ts +0 -8
- package/src/ui/form.ts +0 -7
- package/src/ui/h.ts +0 -48
- package/src/ui/image.ts +0 -5
- package/src/ui/input.ts +0 -49
- package/src/ui/mod.ts +0 -12
- package/src/ui/multiselect.ts +0 -42
- package/src/ui/option.ts +0 -1
- package/src/ui/select.ts +0 -28
- package/src/ui/tab.ts +0 -7
- package/src/ui/table.ts +0 -9
- package/src/ui/upload.ts +0 -7
- package/www/assets/css/presets.css +0 -504
- package/www/assets/css/style.css +0 -90
- package/www/demo.html +0 -28
- package/www/index.html +0 -211
- package/www/playground.html +0 -184
- package/www/public/banner.svg +0 -6
- package/www/public/example/admin.html +0 -88
- package/www/public/example/counter.html +0 -24
- package/www/public/example/greeting.html +0 -25
- package/www/public/example/select.html +0 -27
- package/www/public/example/show.html +0 -18
- package/www/public/example/todo.html +0 -70
- package/www/public/fonts/fab.ttf +0 -0
- package/www/public/fonts/fab.woff2 +0 -0
- package/www/public/fonts/far.ttf +0 -0
- package/www/public/fonts/far.woff2 +0 -0
- package/www/public/fonts/fas.ttf +0 -0
- package/www/public/fonts/fas.woff2 +0 -0
- package/www/public/logo.svg +0 -16
- package/www/typedoc.json +0 -13
- package/www/ui.html +0 -49
- package/www/vite.config.js +0 -106
package/src/reactive.ts
CHANGED
@@ -65,7 +65,9 @@ const SYMBOL_ERRORS = Symbol()
|
|
65
65
|
// // Value node
|
66
66
|
// class VNode {}
|
67
67
|
|
68
|
-
export
|
68
|
+
export type Signal = Getter<T> & Setter<T>
|
69
|
+
|
70
|
+
class Signal<T = unknown> {
|
69
71
|
public parent: Computation<T> | undefined
|
70
72
|
public value: T
|
71
73
|
private readonly equals: EqualsFn<T>
|
@@ -209,7 +211,7 @@ n(3)
|
|
209
211
|
*/
|
210
212
|
export function signal<T>(value:T, options?:Options<T>): Getter<T> & Setter<T> {
|
211
213
|
const s = new Signal<T>(value,options)
|
212
|
-
const f = Object.assign((...args:T[]) => args.length ? s.set(args[0]) as T : s.get() as T, s)
|
214
|
+
const f = Object.assign((...args:T[]) => args.length ? s.set(args[(0)]) as T : s.get() as T, s)
|
213
215
|
return f as unknown as Getter<T> & Setter<T>
|
214
216
|
}
|
215
217
|
|
@@ -294,27 +296,37 @@ export type S<T> = Getter<T> | Setter<T>
|
|
294
296
|
@param s Signal
|
295
297
|
@param k
|
296
298
|
*/
|
297
|
-
export function wrap<T>(s:S<Array<T>>, k:number
|
298
|
-
export function wrap<T>(s:S<
|
299
|
+
export function wrap<T>(s:S<Array<T>>, k:number): S<T>
|
300
|
+
export function wrap<T>(s:S<Array<T>>, k:()=>number): S<T>
|
301
|
+
export function wrap<T>(s:S<Record<string,T>>, k:string): S<T>
|
302
|
+
export function wrap<T>(s:S<Record<string,T>>, k:()=>string): S<T>
|
299
303
|
export function wrap<T>(s:S<Array<T>>|S<Record<string,T>>, k:number|string|(()=>number)|(()=>string)): S<T> {
|
300
304
|
const t = typeof k
|
301
305
|
if (t === 'number') {
|
302
|
-
return (
|
303
|
-
const b =
|
304
|
-
return
|
305
|
-
}
|
306
|
+
return (...a) => {
|
307
|
+
const b = s()
|
308
|
+
return a.length ? s(b.toSpliced(k, 1, a[0])).at(k) : b.at(k)
|
309
|
+
}
|
306
310
|
} else if (t === 'string') {
|
307
|
-
return (
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
+
return (...a) => {
|
312
|
+
if (a.length) {
|
313
|
+
const b = s()
|
314
|
+
return s({...b, [k]:a[0]})[k]
|
315
|
+
} else {
|
316
|
+
return s()[k]
|
317
|
+
}
|
318
|
+
}
|
311
319
|
} else if (t === 'function')
|
312
|
-
return (
|
313
|
-
const i =
|
314
|
-
if (c==='number') return a.length ?
|
315
|
-
|
320
|
+
return (...a) => {
|
321
|
+
const i = k(), c = typeof i
|
322
|
+
if (c==='number') return a.length ?
|
323
|
+
s(old => old.toSpliced(i, 1, a[0]))[i] :
|
324
|
+
s()[i]
|
325
|
+
else if (c === 'string') return a.length ?
|
326
|
+
s(b => ({...b, [i]:a[0]}))[i] :
|
327
|
+
s()[i]
|
316
328
|
throw new Error('Cannot wrap signal')
|
317
|
-
}
|
329
|
+
}
|
318
330
|
throw new Error('Cannot wrap signal')
|
319
331
|
}
|
320
332
|
|
package/src/runtime.ts
CHANGED
@@ -1,10 +1,24 @@
|
|
1
1
|
// @ts-nocheck:
|
2
2
|
import {effect,untrack,root} from './reactive.ts'
|
3
3
|
import {SVGNamespace,SVGElements,ChildProperties,getPropAlias,Properties,Aliases,DelegatedEvents} from './constants.ts'
|
4
|
-
import type {
|
4
|
+
// import type {Mountable} from './constants.ts'
|
5
5
|
|
6
6
|
const {isArray} = Array
|
7
|
-
|
7
|
+
|
8
|
+
export type Mountable = HTMLElement | Document | ShadowRoot | DocumentFragment | Node | string | number | bigint | symbol;
|
9
|
+
|
10
|
+
// declare global {
|
11
|
+
// interface Document {
|
12
|
+
// '_$DX_DELEGATE'?: Record<string, Set<unknown>>
|
13
|
+
// }
|
14
|
+
// // interface SVGElement {}
|
15
|
+
// }
|
16
|
+
|
17
|
+
// interface Element {
|
18
|
+
// style?: string
|
19
|
+
// }
|
20
|
+
|
21
|
+
// declare const globalThis: Document
|
8
22
|
|
9
23
|
/**
|
10
24
|
|
@@ -12,47 +26,61 @@ export const $RUNTIME = Symbol()
|
|
12
26
|
*/
|
13
27
|
export type Runtime = {
|
14
28
|
// window:Window
|
15
|
-
render(code:()=>void, element:
|
29
|
+
render(code:()=>void, element:Element, init:unknown): void;
|
16
30
|
insert(parent:Mountable, accessor:any, marker?:Node|null, init?:any): any;
|
17
|
-
spread(node:
|
18
|
-
assign(node:
|
31
|
+
spread(node:Element, accessor:any, skipChildren?: boolean): void;
|
32
|
+
assign(node:Element, props:any, skipChildren?:boolean): void;
|
19
33
|
element(name:string): any;
|
34
|
+
component(fn:()=>unknown): any
|
20
35
|
text(s:string): any;
|
21
36
|
isChild(a:any): boolean;
|
22
37
|
clearDelegatedEvents():void
|
23
38
|
}
|
24
39
|
|
40
|
+
type ClassListProps = Record<string, string|boolean>
|
41
|
+
type StyleProps = undefined | string | Record<string, string|undefined>
|
42
|
+
|
25
43
|
/**
|
26
44
|
Create `Runtime` for `window`
|
27
45
|
@param window
|
28
46
|
@group Internal
|
29
47
|
*/
|
30
|
-
export function runtime(
|
31
|
-
const document =
|
32
|
-
|
33
|
-
element = (name:string) => SVGElements.has(name) ?
|
48
|
+
export function runtime(w:Window):Runtime {
|
49
|
+
const document = w.document
|
50
|
+
const isSVG = (e: unknown) => e instanceof (w.SVGElement),
|
51
|
+
element = (name:string) => SVGElements.has(name) ?
|
52
|
+
document.createElementNS("http://www.w3.org/2000/svg",name) : document.createElement(name),
|
34
53
|
text = (s:string) => document.createTextNode(s)
|
35
54
|
|
36
|
-
function isChild(a:unknown):
|
37
|
-
return a instanceof
|
55
|
+
function isChild(a:unknown): a is Element {
|
56
|
+
return a instanceof Element
|
57
|
+
}
|
58
|
+
|
59
|
+
function component(fn:()=>unknown) {
|
60
|
+
return untrack(fn)
|
38
61
|
}
|
39
62
|
|
40
|
-
function render(code:()=>void, element:
|
63
|
+
function render(code: ()=>void, element:Element|Document, init?: unknown): void {
|
41
64
|
if (!element) throw new Error("The `element` passed to `render(..., element)` doesn't exist.");
|
42
65
|
root(() => {
|
43
|
-
if (element
|
44
|
-
else insert(element, code(), element.firstChild ? null : undefined, init)
|
66
|
+
if (element instanceof Document) code()
|
67
|
+
else insert(element as Element, code(), element.firstChild ? null : undefined, init)
|
45
68
|
})
|
46
69
|
}
|
47
70
|
|
48
|
-
|
71
|
+
type Value = string | number | Element | Text
|
72
|
+
type RxValue = (()=>Value) | Value
|
73
|
+
|
74
|
+
function insert(parent: Element, accessor: string, marker?: Node|null, initial?: any): void
|
75
|
+
function insert(parent: Element, accessor: ()=>string, marker?: Node|null, initial?: any): void
|
76
|
+
function insert(parent: Element, accessor: string|(()=>string), marker?: Node|null, initial?: any): void {
|
49
77
|
if (marker !== undefined && !initial) initial = []
|
50
|
-
if (
|
78
|
+
if (typeof accessor !== 'function') return insertExpression(parent, accessor, initial||[], marker)
|
51
79
|
let current = initial||[]
|
52
80
|
effect(() => {current = insertExpression(parent, accessor(), current, marker)})
|
53
81
|
}
|
54
82
|
|
55
|
-
function spread(node:
|
83
|
+
function spread(node:Element, props:any = {}, skipChildren:boolean) {
|
56
84
|
const prevProps:any = {}
|
57
85
|
if (!skipChildren) effect(() => (prevProps.children = insertExpression(node, props.children, prevProps.children)))
|
58
86
|
effect(() => (props.ref?.call ? untrack(() => props.ref(node)) : (props.ref = node)))
|
@@ -60,7 +88,7 @@ export function runtime(window:Window):Runtime {
|
|
60
88
|
return prevProps
|
61
89
|
}
|
62
90
|
|
63
|
-
function assign(node:
|
91
|
+
function assign(node:Element, props:any, skipChildren:boolean, prevProps:any = {}, skipRef:boolean = false) {
|
64
92
|
const svg = isSVG(node)
|
65
93
|
props || (props = {})
|
66
94
|
for (const prop in prevProps) {
|
@@ -79,22 +107,34 @@ export function runtime(window:Window):Runtime {
|
|
79
107
|
}
|
80
108
|
}
|
81
109
|
|
82
|
-
function assignProp(node:
|
110
|
+
function assignProp(node: Element, prop: 'style', value: StyleProps, prev: StyleProps, isSVG: boolean, skipRef: boolean): StyleProps
|
111
|
+
function assignProp(node: Element, prop: 'classList', value: ClassListProps, prev: ClassListProps, isSVG: boolean, skipRef: boolean): ClassListProps
|
112
|
+
function assignProp(node: Element, prop: 'ref', value: ()=>string, prev:string, isSVG: boolean, skipRef: false): string
|
113
|
+
function assignProp(node: Element, prop: string, value: string, prev: string|undefined, isSVG: boolean, skipRef: boolean): string
|
114
|
+
function assignProp(node: Element, prop: string, value: undefined, prev: string|undefined, isSVG: boolean, skipRef: boolean): undefined
|
115
|
+
function assignProp(
|
116
|
+
node: Element,
|
117
|
+
prop: 'style' | 'classList' | 'ref' | string,
|
118
|
+
value: StyleProps | ClassListProps | (()=>string) | string | undefined,
|
119
|
+
prev: StyleProps | ClassListProps | (()=>string) | string | undefined,
|
120
|
+
isSVG: false | boolean,
|
121
|
+
skipRef: boolean
|
122
|
+
) {
|
83
123
|
let isCE, isProp, isChildProp, propAlias, forceProp;
|
84
|
-
if (prop ===
|
85
|
-
if (prop ===
|
124
|
+
if (prop === 'style') return style(node, value, prev);
|
125
|
+
if (prop === 'classList') return classList(node, value, prev);
|
86
126
|
if (value === prev) return prev;
|
87
|
-
if (prop ===
|
127
|
+
if (prop === 'ref') {
|
88
128
|
if (!skipRef) value(node);
|
89
|
-
} else if (prop.slice(0, 3) ===
|
129
|
+
} else if (prop.slice(0, 3) === 'on:') {
|
90
130
|
const e = prop.slice(3);
|
91
131
|
prev && node.removeEventListener(e, prev);
|
92
132
|
value && node.addEventListener(e, value);
|
93
|
-
} else if (prop.slice(0, 10) ===
|
133
|
+
} else if (prop.slice(0, 10) === 'oncapture:') {
|
94
134
|
const e = prop.slice(10);
|
95
135
|
prev && node.removeEventListener(e, prev, true);
|
96
136
|
value && node.addEventListener(e, value, true);
|
97
|
-
} else if (prop.slice(0, 2) ===
|
137
|
+
} else if (prop.slice(0, 2) === 'on') {
|
98
138
|
const name = prop.slice(2).toLowerCase();
|
99
139
|
const delegate = DelegatedEvents.has(name);
|
100
140
|
if (!delegate && prev) {
|
@@ -105,13 +145,13 @@ export function runtime(window:Window):Runtime {
|
|
105
145
|
addEventListener(node, name, value, delegate);
|
106
146
|
delegate && delegateEvents([name],document);
|
107
147
|
}
|
108
|
-
} else if (prop.slice(0, 5) ===
|
148
|
+
} else if (prop.slice(0, 5) === 'attr:') {
|
109
149
|
setAttribute(node, prop.slice(5), value);
|
110
150
|
} else if (
|
111
|
-
(forceProp = prop.slice(0, 5) ===
|
151
|
+
(forceProp = prop.slice(0, 5) === 'prop:') ||
|
112
152
|
(isChildProp = ChildProperties.has(prop)) ||
|
113
153
|
(!isSVG && ((propAlias = getPropAlias(prop, node.tagName)) || (isProp = Properties.has(prop)))) ||
|
114
|
-
(isCE = node.nodeName.includes(
|
154
|
+
(isCE = node.nodeName.includes('-'))
|
115
155
|
) {
|
116
156
|
if (forceProp) {
|
117
157
|
prop = prop.slice(5);
|
@@ -121,15 +161,21 @@ export function runtime(window:Window):Runtime {
|
|
121
161
|
else if (isCE && !isProp && !isChildProp) node[toPropertyName(prop)] = value;
|
122
162
|
else node[propAlias || prop] = value;
|
123
163
|
} else {
|
124
|
-
const ns = isSVG && prop.indexOf(
|
125
|
-
if (ns) setAttributeNS(node, ns, prop, value)
|
126
|
-
else setAttribute(node, Aliases[prop] || prop, value)
|
164
|
+
const ns = isSVG && prop.indexOf(':') > -1 && SVGNamespace[prop.split(':')[0]]
|
165
|
+
if (ns) setAttributeNS(node, ns, prop, value)
|
166
|
+
else setAttribute(node, Aliases[prop] || prop, value)
|
127
167
|
}
|
128
168
|
return value;
|
129
169
|
}
|
130
170
|
|
131
|
-
function insertExpression(
|
132
|
-
|
171
|
+
function insertExpression(
|
172
|
+
parent: Element,
|
173
|
+
value: RxValue,
|
174
|
+
current?: RxValue|Value[],
|
175
|
+
marker?: Node,
|
176
|
+
unwrapArray?: boolean
|
177
|
+
): Value|{():Value} {
|
178
|
+
while (typeof current === 'function') current = current();
|
133
179
|
if (value === current) return current;
|
134
180
|
const t = typeof value,
|
135
181
|
multi = marker !== undefined;
|
@@ -147,21 +193,21 @@ export function runtime(window:Window):Runtime {
|
|
147
193
|
} else node = document.createTextNode(value);
|
148
194
|
current = cleanChildren(parent, current, marker, node);
|
149
195
|
} else {
|
150
|
-
if (current !== "" && typeof current ===
|
151
|
-
current = parent.firstChild.data = value;
|
196
|
+
if (current !== "" && typeof current === 'string') {
|
197
|
+
current = (parent.firstChild as Text).data = value;
|
152
198
|
} else current = parent.textContent = value;
|
153
199
|
}
|
154
|
-
} else if (value == null || t ===
|
200
|
+
} else if (value == null || t === 'boolean') {
|
155
201
|
current = cleanChildren(parent, current, marker);
|
156
|
-
} else if (t ===
|
202
|
+
} else if (t === 'function') {
|
157
203
|
effect(() => {
|
158
204
|
let v = value();
|
159
|
-
while (typeof v ===
|
205
|
+
while (typeof v === 'function') v = v();
|
160
206
|
current = insertExpression(parent, v, current, marker);
|
161
207
|
});
|
162
208
|
return () => current;
|
163
209
|
} else if (isArray(value)) {
|
164
|
-
const array:
|
210
|
+
const array:Node[] = [];
|
165
211
|
const currentArray = current && isArray(current);
|
166
212
|
if (normalizeIncomingArray(array, value, current, unwrapArray)) {
|
167
213
|
effect(() => (current = insertExpression(parent, array, current, marker, true)));
|
@@ -191,20 +237,21 @@ export function runtime(window:Window):Runtime {
|
|
191
237
|
return current;
|
192
238
|
}
|
193
239
|
|
194
|
-
function normalizeIncomingArray(normalized:
|
240
|
+
function normalizeIncomingArray(normalized:Node[], array:Node[], current:Node[], unwrap?:boolean): boolean {
|
195
241
|
let dynamic = false;
|
196
242
|
for (let i = 0, len = array.length; i < len; i++) {
|
197
243
|
let item = array[i]
|
198
244
|
const prev = current && current[normalized.length];
|
199
|
-
if (item == null || item === true || item === false) {
|
200
|
-
|
201
|
-
} else
|
245
|
+
// if (item == null || item === true || item === false) {
|
246
|
+
// // matches null, undefined, true or false skip
|
247
|
+
// } else
|
248
|
+
if (typeof item === 'object' && item.nodeType) {
|
202
249
|
normalized.push(item);
|
203
250
|
} else if (isArray(item)) {
|
204
251
|
dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic;
|
205
252
|
} else if (item.call) {
|
206
253
|
if (unwrap) {
|
207
|
-
while (typeof item ===
|
254
|
+
while (typeof item === 'function') item = item();
|
208
255
|
dynamic = normalizeIncomingArray(
|
209
256
|
normalized,
|
210
257
|
isArray(item) ? item : [item],
|
@@ -223,8 +270,13 @@ export function runtime(window:Window):Runtime {
|
|
223
270
|
return dynamic;
|
224
271
|
}
|
225
272
|
|
226
|
-
function cleanChildren(
|
227
|
-
|
273
|
+
function cleanChildren(
|
274
|
+
parent: Element,
|
275
|
+
current?: Node[],
|
276
|
+
marker?: Node|null,
|
277
|
+
replacement?: boolean
|
278
|
+
): string | Node[] {
|
279
|
+
if (marker === undefined) return (parent.textContent = '');
|
228
280
|
const node = replacement || document.createTextNode('');
|
229
281
|
if (current.length) {
|
230
282
|
let inserted = false;
|
@@ -242,18 +294,18 @@ export function runtime(window:Window):Runtime {
|
|
242
294
|
}
|
243
295
|
|
244
296
|
function clearDelegatedEvents() {
|
245
|
-
if (
|
246
|
-
for (const name of
|
247
|
-
delete
|
297
|
+
if (globalThis[$$EVENTS]) {
|
298
|
+
for (const name of globalThis[$$EVENTS].keys()) document.removeEventListener(name, eventHandler);
|
299
|
+
delete globalThis[$$EVENTS];
|
248
300
|
}
|
249
301
|
}
|
250
302
|
|
251
|
-
return {render,insert,spread,assign,element,text,isChild,clearDelegatedEvents}
|
303
|
+
return {render,component,insert,spread,assign,element,text,isChild,clearDelegatedEvents}
|
252
304
|
}
|
253
305
|
|
254
306
|
const $$EVENTS = "_$DX_DELEGATE"
|
255
307
|
|
256
|
-
function delegateEvents(eventNames:string[], document:
|
308
|
+
function delegateEvents(eventNames:string[], document:Document) {
|
257
309
|
const e = document[$$EVENTS] || (document[$$EVENTS] = new Set());
|
258
310
|
for (let i = 0, l = eventNames.length; i < l; i++) {
|
259
311
|
const name = eventNames[i];
|
@@ -264,13 +316,13 @@ function delegateEvents(eventNames:string[], document:any) {
|
|
264
316
|
}
|
265
317
|
}
|
266
318
|
|
267
|
-
function eventHandler(e:
|
319
|
+
function eventHandler(e: Event) {
|
268
320
|
const key = `$$${e.type}`
|
269
321
|
let node = (e.composedPath && e.composedPath()[0]) || e.target
|
270
322
|
// reverse Shadow DOM retargetting
|
271
|
-
if (e.target !== node) Object.defineProperty(e,
|
323
|
+
if (e.target !== node) Object.defineProperty(e, 'target', {configurable: true, value: node})
|
272
324
|
// simulate currentTarget
|
273
|
-
Object.defineProperty(e,
|
325
|
+
Object.defineProperty(e, 'currentTarget', {configurable: true, get() {return node || document}})
|
274
326
|
while (node) {
|
275
327
|
const handler = node[key];
|
276
328
|
if (handler && !node.disabled) {
|
@@ -282,7 +334,7 @@ function eventHandler(e:any) {
|
|
282
334
|
}
|
283
335
|
}
|
284
336
|
|
285
|
-
function setAttribute(node:
|
337
|
+
function setAttribute(node: Element, name: string, value?: string): undefined {
|
286
338
|
value===undefined || value===null ? node.removeAttribute(name) : node.setAttribute(name, value)
|
287
339
|
}
|
288
340
|
|
@@ -290,7 +342,7 @@ function setAttributeNS(node:Node, ns:string, name:string, value?:string):any {
|
|
290
342
|
value ? node.setAttributeNS(ns, name, value) : node.removeAttributeNS(ns, name)
|
291
343
|
}
|
292
344
|
|
293
|
-
function addEventListener(node:
|
345
|
+
function addEventListener(node: Element, name: string, handler:((e:Event)=>void), delegate:boolean): void {
|
294
346
|
if (delegate) {
|
295
347
|
if (isArray(handler)) {
|
296
348
|
node[`$$${name}`] = handler[0];
|
@@ -298,35 +350,43 @@ function addEventListener(node:Node, name:any, handler:any, delegate:any):any {
|
|
298
350
|
} else node[`$$${name}`] = handler;
|
299
351
|
} else if (isArray(handler)) {
|
300
352
|
const handlerFn = handler[0];
|
301
|
-
node.addEventListener(name, (handler[0] = (e:
|
353
|
+
node.addEventListener(name, (handler[0] = (e:Event) => handlerFn.call(node, handler[1], e)));
|
302
354
|
} else node.addEventListener(name, handler);
|
303
355
|
}
|
304
356
|
|
305
|
-
function classList(
|
357
|
+
function classList(
|
358
|
+
node: Element,
|
359
|
+
value: ClassListProps,
|
360
|
+
prev: ClassListProps = {}
|
361
|
+
): ClassListProps {
|
306
362
|
const classKeys = Object.keys(value || {}),
|
307
363
|
prevKeys = Object.keys(prev);
|
308
364
|
let i, len;
|
309
365
|
for (i = 0, len = prevKeys.length; i < len; i++) {
|
310
366
|
const key = prevKeys[i];
|
311
|
-
if (!key || key ===
|
367
|
+
if (!key || key === 'undefined' || value[key]) continue;
|
312
368
|
toggleClassKey(node, key, false);
|
313
369
|
delete prev[key];
|
314
370
|
}
|
315
371
|
for (i = 0, len = classKeys.length; i < len; i++) {
|
316
372
|
const key = classKeys[i],
|
317
373
|
classValue = !!value[key];
|
318
|
-
if (!key || key ===
|
374
|
+
if (!key || key === 'undefined' || prev[key] === classValue || !classValue) continue;
|
319
375
|
toggleClassKey(node, key, true);
|
320
376
|
prev[key] = classValue;
|
321
377
|
}
|
322
378
|
return prev;
|
323
379
|
}
|
324
380
|
|
325
|
-
function style(
|
326
|
-
|
381
|
+
function style(
|
382
|
+
node: Element,
|
383
|
+
value: StyleProps,
|
384
|
+
prev: StyleProps
|
385
|
+
): StyleProps {
|
386
|
+
if (!value) return prev ? setAttribute(node, 'style', undefined) : value;
|
327
387
|
const nodeStyle = node.style;
|
328
|
-
if (typeof value ===
|
329
|
-
if (typeof prev ===
|
388
|
+
if (typeof value === 'string') return (nodeStyle.cssText = value);
|
389
|
+
if (typeof prev === 'string') nodeStyle.cssText = prev = undefined
|
330
390
|
if (!prev) prev = {}
|
331
391
|
if (!value) value = {}
|
332
392
|
let v, s;
|
@@ -348,7 +408,7 @@ function toPropertyName(name:string):string {
|
|
348
408
|
return name.toLowerCase().replace(/-([a-z])/g, (_:unknown, w:string) => w.toUpperCase());
|
349
409
|
}
|
350
410
|
|
351
|
-
function toggleClassKey(node:
|
411
|
+
function toggleClassKey(node: Element, key:string, value:boolean) {
|
352
412
|
const classNames = key.trim().split(/\s+/)
|
353
413
|
for (let i = 0, nameLen = classNames.length; i < nameLen; i++)
|
354
414
|
node.classList.toggle(classNames[i], value)
|
@@ -360,7 +420,7 @@ function appendNodes(parent:Node, array:Node[], marker:null|Node = null) {
|
|
360
420
|
}
|
361
421
|
|
362
422
|
// Slightly modified version of: https://github.com/WebReflection/udomdiff/blob/master/index.js
|
363
|
-
function reconcileArrays(parentNode:Node, a:
|
423
|
+
function reconcileArrays(parentNode:Node, a:Element[], b:Element[]) {
|
364
424
|
const bLength = b.length
|
365
425
|
let aEnd = a.length,
|
366
426
|
bEnd = bLength,
|
package/test/hyperscript.js
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
import {runtime} from '../src/runtime.ts'
|
2
2
|
import {hyperscript} from '../src/hyperscript.ts'
|
3
3
|
import {signal,root} from '../src/reactive.ts'
|
4
|
-
import {
|
4
|
+
import { Window } from 'happy-dom'
|
5
5
|
import {assertEquals} from '@std/assert'
|
6
6
|
|
7
|
-
const
|
8
|
-
const
|
7
|
+
const window = new Window
|
8
|
+
const document = window.document
|
9
|
+
globalThis = window
|
9
10
|
const r = runtime(window), h = hyperscript(r)
|
10
11
|
|
11
12
|
function testing(name, props, f=props) {
|
@@ -13,7 +14,7 @@ function testing(name, props, f=props) {
|
|
13
14
|
let disposer
|
14
15
|
return root(dispose => {
|
15
16
|
disposer = () => {
|
16
|
-
document.body.textContent = ''
|
17
|
+
window.document.body.textContent = ''
|
17
18
|
r.clearDelegatedEvents()
|
18
19
|
dispose()
|
19
20
|
}
|
@@ -25,7 +26,7 @@ function testing(name, props, f=props) {
|
|
25
26
|
|
26
27
|
function assertHTML(t, e, msg) { assertEquals(t().outerHTML, e, msg) }
|
27
28
|
|
28
|
-
testing('h with basic element',
|
29
|
+
testing('h with basic element', async test => {
|
29
30
|
await test('empty tag', () => assertHTML(h(''), '<div></div>'))
|
30
31
|
await test('tag with id', () => assertHTML(h('#a'), '<div id="a"></div>'))
|
31
32
|
await test('tag with class', () => assertHTML(h('.a'), '<div class="a"></div>'))
|
@@ -63,7 +64,7 @@ function assertText(t, e, msg) { assertEquals(t(), e, msg) }
|
|
63
64
|
// await test('signal fragment', () => assertText(h([()=>1]), '1'))
|
64
65
|
// })
|
65
66
|
|
66
|
-
testing('h with reactive content',
|
67
|
+
testing('h with reactive content', async test => {
|
67
68
|
await test('higher-order component', () => {
|
68
69
|
const Hi = p => h('b',['Hi ',p.name]),
|
69
70
|
name = signal('An'),
|
package/test/reactive.js
CHANGED
@@ -82,7 +82,7 @@ describe('memo',{skip:true},() => {
|
|
82
82
|
describe('memo with initial value',() => {})
|
83
83
|
})
|
84
84
|
|
85
|
-
describe('wrap',()=>{
|
85
|
+
describe('wrap', ()=>{
|
86
86
|
describe('wrap singal of array', () => {
|
87
87
|
const all = signal(['a','b']), first = wrap(all,0)
|
88
88
|
assertEquals(first(),'a')
|
@@ -95,6 +95,7 @@ describe('wrap',()=>{
|
|
95
95
|
assertEquals(name(),'a')
|
96
96
|
assertEquals(name('A'),'A')
|
97
97
|
assertEquals(name(),'A')
|
98
|
+
assertEquals(all(),{name:'A'})
|
98
99
|
})
|
99
100
|
|
100
101
|
describe('wrap singal of array of objects', () => {
|
@@ -104,9 +105,10 @@ describe('wrap',()=>{
|
|
104
105
|
assertEquals(name(),'b')
|
105
106
|
assertEquals(name('A'),'A')
|
106
107
|
assertEquals(name(),'A')
|
108
|
+
assertEquals(all(),[{name:'A'}])
|
107
109
|
})
|
108
110
|
|
109
|
-
describe('wrap singal of object
|
111
|
+
describe('wrap singal of object with arrays', () => {
|
110
112
|
const all = signal({ids:[0,1,2]}), ids = wrap(all,'id'), last = wrap(ids,-1)
|
111
113
|
assertEquals(ids([1,2,3]),[1,2,3])
|
112
114
|
assertEquals(ids(),[1,2,3])
|
@@ -114,4 +116,22 @@ describe('wrap',()=>{
|
|
114
116
|
assertEquals(last(4),4)
|
115
117
|
assertEquals(last(),4)
|
116
118
|
})
|
119
|
+
|
120
|
+
describe('wrap singal of array of objects with array', () => {
|
121
|
+
const all = signal([{ids:[0,1,2]}]), first = wrap(all,0), ids = wrap(first,'ids'), last = wrap(ids,-1)
|
122
|
+
assertEquals(ids([1,2,3]),[1,2,3])
|
123
|
+
assertEquals(last(4),4)
|
124
|
+
assertEquals(all(),[{ids:[1,2,4]}])
|
125
|
+
})
|
126
|
+
|
127
|
+
describe('wrap singal of object with array of objects', () => {
|
128
|
+
const obj = signal({todos:[{done:false,name:'a'}, {done:false,name:'b'}]}), todos = wrap(obj, 'todos'), todo = wrap(todos, 0),
|
129
|
+
name = wrap(todo, 'name'), done = wrap(todo,'done')
|
130
|
+
assertEquals(done(true),true)
|
131
|
+
console.log('OBJ',obj())
|
132
|
+
effect(()=>{
|
133
|
+
name();done()
|
134
|
+
})
|
135
|
+
assertEquals(obj(),{todos:[{done:true,name:'a'},{done:false,name:'b'}]})
|
136
|
+
})
|
117
137
|
})
|
package/test/types.ts
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
import { runtime } from '../src/runtime.ts'
|
2
|
+
import { hyperscript, type Mountable } from '../src/hyperscript.ts'
|
3
|
+
import { signal, root } from '../src/reactive.ts'
|
4
|
+
import { List } from '../src/controlflow.ts'
|
5
|
+
import { Window } from 'happy-dom'
|
6
|
+
|
7
|
+
const window = new Window
|
8
|
+
const r = runtime(window as any), h = hyperscript(r)
|
9
|
+
|
10
|
+
h('hr')
|
11
|
+
h('div', ['hello'])
|
12
|
+
h('', {class:''})
|
13
|
+
|
14
|
+
function CompReturningString() { return 'hi' }
|
15
|
+
function CompReturningNumber() { return 0 }
|
16
|
+
function CompReturningEmptyFragment() { return [] }
|
17
|
+
function CompReturningFragment() { return [h(CompReturningString), h(CompReturningNumber), '', 1] }
|
18
|
+
|
19
|
+
h(CompReturningString)
|
20
|
+
h(CompReturningNumber)
|
21
|
+
h(CompReturningEmptyFragment)
|
22
|
+
h(CompReturningFragment)
|
23
|
+
|
24
|
+
function CompWithProps(props: {a: string}) { return 'hi' }
|
25
|
+
|
26
|
+
h(CompWithProps, {a:''})
|
27
|
+
|
28
|
+
function CompWithChildren(props: {children:Mountable}) { return 'hi' }
|
29
|
+
|
30
|
+
h(CompWithChildren, [])
|
31
|
+
h(CompWithChildren, {}, [])
|
32
|
+
h(CompWithChildren, {children:[]})
|
33
|
+
|
34
|
+
|
35
|
+
function CompWithOptionalChildren(props: {children?:string[]}) { return 'hi' }
|
36
|
+
|
37
|
+
h(CompWithOptionalChildren)
|
38
|
+
h(CompWithOptionalChildren, [])
|
39
|
+
h(CompWithOptionalChildren, {})
|
40
|
+
h(CompWithOptionalChildren, {children:[]})
|
41
|
+
|
42
|
+
const booleans = signal([true, true, false])
|
43
|
+
|
44
|
+
// h(List, {each:()=>booleans}, (b, _i) => b)
|
package/src/components.js
DELETED
package/src/ui/accordion.ts
DELETED
package/src/ui/button.ts
DELETED
package/src/ui/canvas.ts
DELETED