@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.
Files changed (53) hide show
  1. package/.github/workflows/publish.yml +12 -11
  2. package/{deno.json → deno.jsonc} +10 -9
  3. package/example/index.html +43 -0
  4. package/package.json +6 -2
  5. package/src/constants.ts +110 -52
  6. package/src/controlflow.ts +140 -92
  7. package/src/hyperscript.ts +81 -106
  8. package/src/mod.ts +11 -19
  9. package/src/reactive.ts +29 -17
  10. package/src/runtime.ts +126 -66
  11. package/test/hyperscript.js +7 -6
  12. package/test/reactive.js +22 -2
  13. package/test/types.ts +44 -0
  14. package/src/components.js +0 -20
  15. package/src/ui/accordion.ts +0 -8
  16. package/src/ui/button.ts +0 -9
  17. package/src/ui/canvas.ts +0 -8
  18. package/src/ui/date.ts +0 -8
  19. package/src/ui/dialog.ts +0 -12
  20. package/src/ui/filter.ts +0 -8
  21. package/src/ui/form.ts +0 -7
  22. package/src/ui/h.ts +0 -48
  23. package/src/ui/image.ts +0 -5
  24. package/src/ui/input.ts +0 -49
  25. package/src/ui/mod.ts +0 -12
  26. package/src/ui/multiselect.ts +0 -42
  27. package/src/ui/option.ts +0 -1
  28. package/src/ui/select.ts +0 -28
  29. package/src/ui/tab.ts +0 -7
  30. package/src/ui/table.ts +0 -9
  31. package/src/ui/upload.ts +0 -7
  32. package/www/assets/css/presets.css +0 -504
  33. package/www/assets/css/style.css +0 -90
  34. package/www/demo.html +0 -28
  35. package/www/index.html +0 -211
  36. package/www/playground.html +0 -184
  37. package/www/public/banner.svg +0 -6
  38. package/www/public/example/admin.html +0 -88
  39. package/www/public/example/counter.html +0 -24
  40. package/www/public/example/greeting.html +0 -25
  41. package/www/public/example/select.html +0 -27
  42. package/www/public/example/show.html +0 -18
  43. package/www/public/example/todo.html +0 -70
  44. package/www/public/fonts/fab.ttf +0 -0
  45. package/www/public/fonts/fab.woff2 +0 -0
  46. package/www/public/fonts/far.ttf +0 -0
  47. package/www/public/fonts/far.woff2 +0 -0
  48. package/www/public/fonts/fas.ttf +0 -0
  49. package/www/public/fonts/fas.woff2 +0 -0
  50. package/www/public/logo.svg +0 -16
  51. package/www/typedoc.json +0 -13
  52. package/www/ui.html +0 -49
  53. 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 class Signal<T = unknown> {
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|(()=>number)): S<T>
298
- export function wrap<T>(s:S<Record<string,T>>, k:string|(()=>string)): S<T>
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 ((...a:T[]) => {
303
- const b = (s as Getter<Array<T>>)()
304
- return (a.length) ? (s as Setter<Array<T>>)((b as any).toSpliced(k as number, 1, a[0])).at(k as number) : b.at(k as number)
305
- }) as S<T>
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 ((...a:T[]) => {
308
- const b = (s as Getter<Record<string,T>>)()
309
- return (a.length) ? (s as Setter<Record<string,T>>)({...b, [k as string]:a[0]})[k as string] : b[k as string]
310
- }) as S<T>
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 ((...a:T[]) => {
313
- const i = (k as ()=>string|number)(), c = typeof i
314
- if (c==='number') return a.length ? (s as Setter<Array<T>>)((old:any) => old.toSpliced(i, 1, a[0]))[i as number] : (s as Getter<Array<T>>)()[i as number]
315
- else if (c === 'string') return a.length ? (s as Setter<Record<string,T>>)((b) => ({...b, [i]:a[0]}))[i as string] : (s as Getter<Record<string,T>>)()[i]
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
- }) as S<T>
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 {Window,Mountable,Elem,Node} from './constants.ts'
4
+ // import type {Mountable} from './constants.ts'
5
5
 
6
6
  const {isArray} = Array
7
- export const $RUNTIME = Symbol()
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:Elem, init:any): any;
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:Elem, accessor:any, skipChildren?: boolean): void;
18
- assign(node:Elem, props:any, skipChildren?:boolean): void;
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(window:Window):Runtime {
31
- const document = window.document,
32
- isSVG = (e:any) => e instanceof (window.SVGElement as any),
33
- element = (name:string) => SVGElements.has(name) ? document.createElementNS("http://www.w3.org/2000/svg",name) : document.createElement(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):boolean {
37
- return a instanceof document.Element
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:Elem, init?:any) {
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 === document) code()
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
- function insert(parent:Mountable, accessor:any, marker?:Node|null, initial?:any) {
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 (!accessor.call) return insertExpression(parent, accessor, initial||[], marker)
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:Elem, props:any = {}, skipChildren:boolean) {
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:Elem, props:any, skipChildren:boolean, prevProps:any = {}, skipRef:boolean = false) {
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:Node, prop:any, value:any, prev:any, isSVG:any, skipRef:any) {
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 === "style") return style(node, value, prev);
85
- if (prop === "classList") return classList(node, value, prev);
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 === "ref") {
127
+ if (prop === 'ref') {
88
128
  if (!skipRef) value(node);
89
- } else if (prop.slice(0, 3) === "on:") {
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) === "oncapture:") {
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) === "on") {
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) === "attr:") {
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) === "prop:") ||
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(":") > -1 && SVGNamespace[prop.split(":")[0]];
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(parent:Node, value:any, current?:any, marker?:Node, unwrapArray?:any) {
132
- while (current?.call) current = current();
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 === "string") {
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 === "boolean") {
200
+ } else if (value == null || t === 'boolean') {
155
201
  current = cleanChildren(parent, current, marker);
156
- } else if (t === "function") {
202
+ } else if (t === 'function') {
157
203
  effect(() => {
158
204
  let v = value();
159
- while (typeof v === "function") v = 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:any[] = [];
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:any, array:any, current:any, unwrap?:any):any {
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
- // matches null, undefined, true or false skip
201
- } else if (typeof item === "object" && item.nodeType) {
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 === "function") item = 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(parent:any, current?:any, marker?:Node, replacement?:any):any {
227
- if (marker === undefined) return (parent.textContent = "");
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 (document[$$EVENTS]) {
246
- for (const name of document[$$EVENTS].keys()) document.removeEventListener(name, eventHandler);
247
- delete document[$$EVENTS];
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:any) {
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:any) {
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, "target", {configurable: true, value: node})
323
+ if (e.target !== node) Object.defineProperty(e, 'target', {configurable: true, value: node})
272
324
  // simulate currentTarget
273
- Object.defineProperty(e, "currentTarget", {configurable: true, get() {return node || document}})
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:Node, name:string, value?:string):any {
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:Node, name:any, handler:any, delegate:any):any {
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:any) => handlerFn.call(node, handler[1], 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(node:Node, value:any, prev:any = {}):any {
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 === "undefined" || value[key]) continue;
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 === "undefined" || prev[key] === classValue || !classValue) continue;
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(node:Node, value:any, prev:any) {
326
- if (!value) return prev ? setAttribute(node, "style") : value;
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 === "string") return (nodeStyle.cssText = value);
329
- if (typeof prev === "string") nodeStyle.cssText = prev = undefined
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:Node, key:string, value:boolean) {
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:Node[], b:Node[]) {
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,
@@ -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 {JSDOM} from 'jsdom'
4
+ import { Window } from 'happy-dom'
5
5
  import {assertEquals} from '@std/assert'
6
6
 
7
- const {window} = new JSDOM('<!DOCTYPE html>', {runScripts:'dangerously'})
8
- const {document,MouseEvent} = window
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', {skip:true}, async test => {
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', {skip:true}, async test => {
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 of arrays', () => {
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
@@ -1,20 +0,0 @@
1
- /**
2
- TODO
3
- @group Components
4
- */
5
- export function Input(props) {
6
- }
7
-
8
- /**
9
- TODO
10
- @group Components
11
- */
12
- export function Table(props) {
13
- }
14
-
15
- /**
16
- TODO
17
- @group Components
18
- */
19
- export function Canvas(props) {
20
- }
@@ -1,8 +0,0 @@
1
- import {h} from './h.ts'
2
-
3
- export type AccordionProps = {
4
- }
5
-
6
- export function Accordion(props:AccordionProps):undefined|any {
7
- return h('.accordion', {},'')
8
- }
package/src/ui/button.ts DELETED
@@ -1,9 +0,0 @@
1
- import {h} from './h.ts'
2
-
3
- export type ButtonProps = {
4
- children?: any[]
5
- }
6
-
7
- export function Button(props:AccordionProps) {
8
- return h('button', {type: 'button'}, props.children)
9
- }
package/src/ui/canvas.ts DELETED
@@ -1,8 +0,0 @@
1
- import {h} from './h.ts'
2
-
3
- export type CanvasProps = {
4
- }
5
-
6
- export function Canvas(props:CanvasProps) {
7
- return h('canvas')
8
- }
package/src/ui/date.ts DELETED
@@ -1,8 +0,0 @@
1
- import {h} from './h.ts'
2
-
3
- export type DateInputProps = {
4
- }
5
-
6
- export function DateInput(props:DateInputProps) {
7
- return h('input', {type: 'date'})
8
- }