@wrnrlr/prelude 0.0.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.
@@ -0,0 +1,515 @@
1
+ /**
2
+ * Non-breakable space in Unicode
3
+ * @group Utils
4
+ */
5
+ export const nbsp:string = '\u00A0'
6
+
7
+ export declare type Window = { document: Document; SVGElement: typeof SVGElements}
8
+ export declare type Elem = any
9
+ declare type SVGElement = any
10
+ declare type Document = any
11
+ declare type ShadowRoot = any
12
+ declare type DocumentFragment = any
13
+ export declare type Node = any
14
+
15
+ export type Mountable = Elem | Document | ShadowRoot | DocumentFragment | Node;
16
+ type ExpandableNode = Node & { [key: string]: any };
17
+
18
+ // type Expect<T extends true> = T;
19
+ // type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
20
+ // const test = {children:[] as any[]}
21
+ // type test1 = Expect<Equal<typeof test, ElementProps>>
22
+
23
+ const booleans:string[] = [
24
+ 'allowfullscreen',
25
+ 'async',
26
+ 'autofocus',
27
+ 'autoplay',
28
+ 'checked',
29
+ 'controls',
30
+ 'default',
31
+ 'disabled',
32
+ 'formnovalidate',
33
+ 'hidden',
34
+ 'indeterminate',
35
+ 'inert',
36
+ 'ismap',
37
+ 'loop',
38
+ 'multiple',
39
+ 'muted',
40
+ 'nomodule',
41
+ 'novalidate',
42
+ 'open',
43
+ 'playsinline',
44
+ 'readonly',
45
+ 'required',
46
+ 'reversed',
47
+ 'seamless',
48
+ 'selected'
49
+ ];
50
+
51
+ const BooleanAttributes:Set<string> = /*#__PURE__*/ new Set(booleans);
52
+
53
+ const Properties:Set<string> = /*#__PURE__*/ new Set([
54
+ 'className',
55
+ 'value',
56
+ 'readOnly',
57
+ 'formNoValidate',
58
+ 'isMap',
59
+ 'noModule',
60
+ 'playsInline',
61
+ ...booleans
62
+ ]);
63
+
64
+ const ChildProperties:Set<string> = /*#__PURE__*/ new Set([
65
+ 'innerHTML',
66
+ 'textContent',
67
+ 'innerText',
68
+ 'children'
69
+ ]);
70
+
71
+ // React Compat
72
+ const Aliases:Record<string,string> = /*#__PURE__*/ Object.assign(Object.create(null), {
73
+ className: 'class',
74
+ htmlFor: 'for'
75
+ });
76
+
77
+ const PropAliases = /*#__PURE__*/ Object.assign(Object.create(null), {
78
+ class: 'className',
79
+ formnovalidate: {
80
+ $: 'formNoValidate',
81
+ BUTTON: 1,
82
+ INPUT: 1
83
+ },
84
+ ismap: {
85
+ $: 'isMap',
86
+ IMG: 1
87
+ },
88
+ nomodule: {
89
+ $: 'noModule',
90
+ SCRIPT: 1
91
+ },
92
+ playsinline: {
93
+ $: 'playsInline',
94
+ VIDEO: 1
95
+ },
96
+ readonly: {
97
+ $: 'readOnly',
98
+ INPUT: 1,
99
+ TEXTAREA: 1
100
+ }
101
+ });
102
+
103
+ function getPropAlias(prop:string,tagName:string):string | undefined {
104
+ const a = PropAliases[prop];
105
+ return typeof a === 'object' ? (a[tagName] ? a['$'] : undefined) : a;
106
+ }
107
+
108
+ // list of Element events that will be delegated
109
+ const DelegatedEvents:Set<string> = /*#__PURE__*/ new Set([
110
+ 'beforeinput',
111
+ 'click',
112
+ 'dblclick',
113
+ 'contextmenu',
114
+ 'focusin',
115
+ 'focusout',
116
+ 'input',
117
+ 'keydown',
118
+ 'keyup',
119
+ 'mousedown',
120
+ 'mousemove',
121
+ 'mouseout',
122
+ 'mouseover',
123
+ 'mouseup',
124
+ 'pointerdown',
125
+ 'pointermove',
126
+ 'pointerout',
127
+ 'pointerover',
128
+ 'pointerup',
129
+ 'touchend',
130
+ 'touchmove',
131
+ 'touchstart'
132
+ ]);
133
+
134
+ const SVGElements:Set<string> = /*#__PURE__*/ new Set([
135
+ // 'a',
136
+ 'altGlyph',
137
+ 'altGlyphDef',
138
+ 'altGlyphItem',
139
+ 'animate',
140
+ 'animateColor',
141
+ 'animateMotion',
142
+ 'animateTransform',
143
+ 'circle',
144
+ 'clipPath',
145
+ 'color-profile',
146
+ 'cursor',
147
+ 'defs',
148
+ 'desc',
149
+ 'ellipse',
150
+ 'feBlend',
151
+ 'feColorMatrix',
152
+ 'feComponentTransfer',
153
+ 'feComposite',
154
+ 'feConvolveMatrix',
155
+ 'feDiffuseLighting',
156
+ 'feDisplacementMap',
157
+ 'feDistantLight',
158
+ 'feDropShadow',
159
+ 'feFlood',
160
+ 'feFuncA',
161
+ 'feFuncB',
162
+ 'feFuncG',
163
+ 'feFuncR',
164
+ 'feGaussianBlur',
165
+ 'feImage',
166
+ 'feMerge',
167
+ 'feMergeNode',
168
+ 'feMorphology',
169
+ 'feOffset',
170
+ 'fePointLight',
171
+ 'feSpecularLighting',
172
+ 'feSpotLight',
173
+ 'feTile',
174
+ 'feTurbulence',
175
+ 'filter',
176
+ 'font',
177
+ 'font-face',
178
+ 'font-face-format',
179
+ 'font-face-name',
180
+ 'font-face-src',
181
+ 'font-face-uri',
182
+ 'foreignObject',
183
+ 'g',
184
+ 'glyph',
185
+ 'glyphRef',
186
+ 'hkern',
187
+ 'image',
188
+ 'line',
189
+ 'linearGradient',
190
+ 'marker',
191
+ 'mask',
192
+ 'metadata',
193
+ 'missing-glyph',
194
+ 'mpath',
195
+ 'path',
196
+ 'pattern',
197
+ 'polygon',
198
+ 'polyline',
199
+ 'radialGradient',
200
+ 'rect',
201
+ // 'script',
202
+ 'set',
203
+ 'stop',
204
+ // 'style',
205
+ 'svg',
206
+ 'switch',
207
+ 'symbol',
208
+ 'text',
209
+ 'textPath',
210
+ // 'title',
211
+ 'tref',
212
+ 'tspan',
213
+ 'use',
214
+ 'view',
215
+ 'vkern'
216
+ ]);
217
+
218
+ const SVGNamespace:Record<string,string> = {
219
+ xlink: 'http://www.w3.org/1999/xlink',
220
+ xml: 'http://www.w3.org/XML/1998/namespace'
221
+ };
222
+
223
+ const DOMElements:Set<string> = /*#__PURE__*/ new Set([
224
+ 'html',
225
+ 'base',
226
+ 'head',
227
+ 'link',
228
+ 'meta',
229
+ 'style',
230
+ 'title',
231
+ 'body',
232
+ 'address',
233
+ 'article',
234
+ 'aside',
235
+ 'footer',
236
+ 'header',
237
+ 'main',
238
+ 'nav',
239
+ 'section',
240
+ 'body',
241
+ 'blockquote',
242
+ 'dd',
243
+ 'div',
244
+ 'dl',
245
+ 'dt',
246
+ 'figcaption',
247
+ 'figure',
248
+ 'hr',
249
+ 'li',
250
+ 'ol',
251
+ 'p',
252
+ 'pre',
253
+ 'ul',
254
+ 'a',
255
+ 'abbr',
256
+ 'b',
257
+ 'bdi',
258
+ 'bdo',
259
+ 'br',
260
+ 'cite',
261
+ 'code',
262
+ 'data',
263
+ 'dfn',
264
+ 'em',
265
+ 'i',
266
+ 'kbd',
267
+ 'mark',
268
+ 'q',
269
+ 'rp',
270
+ 'rt',
271
+ 'ruby',
272
+ 's',
273
+ 'samp',
274
+ 'small',
275
+ 'span',
276
+ 'strong',
277
+ 'sub',
278
+ 'sup',
279
+ 'time',
280
+ 'u',
281
+ 'var',
282
+ 'wbr',
283
+ 'area',
284
+ 'audio',
285
+ 'img',
286
+ 'map',
287
+ 'track',
288
+ 'video',
289
+ 'embed',
290
+ 'iframe',
291
+ 'object',
292
+ 'param',
293
+ 'picture',
294
+ 'portal',
295
+ 'source',
296
+ 'svg',
297
+ 'math',
298
+ 'canvas',
299
+ 'noscript',
300
+ 'script',
301
+ 'del',
302
+ 'ins',
303
+ 'caption',
304
+ 'col',
305
+ 'colgroup',
306
+ 'table',
307
+ 'tbody',
308
+ 'td',
309
+ 'tfoot',
310
+ 'th',
311
+ 'thead',
312
+ 'tr',
313
+ 'button',
314
+ 'datalist',
315
+ 'fieldset',
316
+ 'form',
317
+ 'input',
318
+ 'label',
319
+ 'legend',
320
+ 'meter',
321
+ 'optgroup',
322
+ 'option',
323
+ 'output',
324
+ 'progress',
325
+ 'select',
326
+ 'textarea',
327
+ 'details',
328
+ 'dialog',
329
+ 'menu',
330
+ 'summary',
331
+ 'details',
332
+ 'slot',
333
+ 'template',
334
+ // 'acronym',
335
+ // 'applet',
336
+ // 'basefont',
337
+ // 'bgsound',
338
+ // 'big',
339
+ // 'blink',
340
+ // 'center',
341
+ 'content',
342
+ // 'dir',
343
+ // 'font',
344
+ 'frame',
345
+ // 'frameset',
346
+ 'hgroup',
347
+ 'image',
348
+ // 'keygen',
349
+ // 'marquee',
350
+ 'menuitem',
351
+ // 'nobr',
352
+ // 'noembed',
353
+ // 'noframes',
354
+ 'plaintext',
355
+ 'rb',
356
+ 'rtc',
357
+ 'shadow',
358
+ 'spacer',
359
+ 'strike',
360
+ 'tt',
361
+ 'xmp',
362
+ 'a',
363
+ 'abbr',
364
+ 'acronym',
365
+ 'address',
366
+ // 'applet',
367
+ 'area',
368
+ 'article',
369
+ 'aside',
370
+ 'audio',
371
+ 'b',
372
+ 'base',
373
+ // 'basefont',
374
+ 'bdi',
375
+ 'bdo',
376
+ // 'bgsound',
377
+ // 'big',
378
+ // 'blink',
379
+ 'blockquote',
380
+ 'body',
381
+ 'br',
382
+ 'button',
383
+ 'canvas',
384
+ 'caption',
385
+ // 'center',
386
+ 'cite',
387
+ 'code',
388
+ 'col',
389
+ 'colgroup',
390
+ 'content',
391
+ 'data',
392
+ 'datalist',
393
+ 'dd',
394
+ 'del',
395
+ 'details',
396
+ 'dfn',
397
+ 'dialog',
398
+ // 'dir',
399
+ 'div',
400
+ 'dl',
401
+ 'dt',
402
+ 'em',
403
+ // 'embed',
404
+ 'fieldset',
405
+ 'figcaption',
406
+ 'figure',
407
+ // 'font',
408
+ 'footer',
409
+ 'form',
410
+ // 'frame',
411
+ // 'frameset',
412
+ 'head',
413
+ 'header',
414
+ 'hgroup',
415
+ 'hr',
416
+ 'html',
417
+ 'i',
418
+ 'iframe',
419
+ 'image',
420
+ 'img',
421
+ 'input',
422
+ 'ins',
423
+ 'kbd',
424
+ // 'keygen',
425
+ 'label',
426
+ 'legend',
427
+ 'li',
428
+ 'link',
429
+ 'main',
430
+ 'map',
431
+ 'mark',
432
+ // 'marquee',
433
+ // 'menu',
434
+ // 'menuitem',
435
+ 'meta',
436
+ 'meter',
437
+ 'nav',
438
+ // 'nobr',
439
+ // 'noembed',
440
+ // 'noframes',
441
+ 'noscript',
442
+ 'object',
443
+ 'ol',
444
+ 'optgroup',
445
+ 'option',
446
+ 'output',
447
+ 'p',
448
+ // 'param',
449
+ 'picture',
450
+ // 'plaintext',
451
+ 'portal',
452
+ 'pre',
453
+ 'progress',
454
+ 'q',
455
+ // 'rb',
456
+ 'rp',
457
+ 'rt',
458
+ // 'rtc',
459
+ 'ruby',
460
+ // 's',
461
+ 'samp',
462
+ 'script',
463
+ 'section',
464
+ 'select',
465
+ 'shadow',
466
+ 'slot',
467
+ 'small',
468
+ 'source',
469
+ // 'spacer',
470
+ 'span',
471
+ // 'strike',
472
+ 'strong',
473
+ 'style',
474
+ 'sub',
475
+ 'summary',
476
+ 'sup',
477
+ 'table',
478
+ 'tbody',
479
+ 'td',
480
+ 'template',
481
+ 'textarea',
482
+ 'tfoot',
483
+ 'th',
484
+ 'thead',
485
+ 'time',
486
+ 'title',
487
+ 'tr',
488
+ 'track',
489
+ // 'tt',
490
+ 'u',
491
+ 'ul',
492
+ // 'var',
493
+ 'video',
494
+ 'wbr',
495
+ // 'xmp',
496
+ 'input',
497
+ 'h1',
498
+ 'h2',
499
+ 'h3',
500
+ 'h4',
501
+ 'h5',
502
+ 'h6'
503
+ ]);
504
+
505
+ export {
506
+ BooleanAttributes,
507
+ Properties,
508
+ ChildProperties,
509
+ getPropAlias,
510
+ Aliases,
511
+ DelegatedEvents,
512
+ SVGElements,
513
+ SVGNamespace,
514
+ DOMElements
515
+ };
@@ -0,0 +1,163 @@
1
+ import {signal,sample,batch,memo,root,Signal} from './reactive.ts'
2
+
3
+ /**
4
+ Show children if `when` prop is true, otherwise show `fallback`.
5
+ @group Components
6
+ */
7
+ export function Show(props) {
8
+ const condition = memo(()=>props.when)
9
+ return memo(()=>{
10
+ const c = condition()
11
+ if (c) {
12
+ const child = props.children
13
+ const fn = typeof child === "function" && child.length > 0
14
+ return fn ? sample(() => child(() => props.when)) : child
15
+ } else return props.fallback
16
+ })
17
+ }
18
+
19
+ export function wrap(s,k) {
20
+ const t = typeof k
21
+ if (t === 'number') return (...a) => {
22
+ const b = s()
23
+ return (a.length) ? s(b.toSpliced(k, 1, a[0])).at(k) : b.at(k)
24
+ }; else if (t === 'string') return (...a) => {
25
+ const b = s()
26
+ return a.length ? s(({ ...b, [k]: a[0] }))[k] : b[k]
27
+ }; else if (t === 'function') return (...a) => {
28
+ const i = k(), c = typeof i
29
+ if (c==='number') return a.length ? s(old => old.toSpliced(i, 1, a[0]))[i] : s()[i]
30
+ else if (c === 'string') return a => a.length ? s(b => ({...b, [i]:a[0]}))[i] : s()[i]
31
+ throw new Error('Cannot wrap signal')
32
+ }
33
+ throw new Error('Cannot wrap signal')
34
+ }
35
+
36
+ /**
37
+ List
38
+ @group Components
39
+ */
40
+ export function List(props) {
41
+ const fallback = "fallback" in props && { fallback: () => props.fallback }
42
+ const list = props.each
43
+ const cb = props.children.call ? props.children : (v)=>v
44
+ let items = [], item, unusedItems, i, j, newValue, mapped, oldIndex, oldValue,
45
+ indexes = cb.length > 1 ? [] : null;
46
+ function newValueGetter(_) { return newValue }
47
+ function changeBoth() {
48
+ item.index = i
49
+ item.indexSetter?.(i)
50
+ item.value = newValue
51
+ item.valueSetter?.(newValueGetter)
52
+ }
53
+ function mapperWithIndexes(disposer) {
54
+ const V = newValue, I = i, Is = signal(I), Vs = signal(V)
55
+ items.push({value: newValue, index: I, disposer, indexSetter: Is, valueSetter: Vs})
56
+ return cb(
57
+ (...a) => a.length ?
58
+ sample(()=>list(list=>list.toSpliced(I,1,a[0])))
59
+ : Vs(),
60
+ ()=>Is())
61
+ }
62
+ function mapperWithoutIndexes(disposer) {
63
+ const V = newValue, I = i, Vs = signal(V)
64
+ items.push({value: V, index: i, disposer, valueSetter: Vs})
65
+ return cb((...a) => a.length ?
66
+ sample(()=>list(list=>list.toSpliced(I,1,a[0])))
67
+ : Vs())
68
+ }
69
+ const mapper = indexes ? mapperWithIndexes : mapperWithoutIndexes
70
+ return memo(() => {
71
+ const newItems = list() || []
72
+ // (newItems)[$TRACK]; // top level tracking
73
+ return sample(() => {
74
+ const temp = new Array(newItems.length) // new mapped array
75
+ unusedItems = items.length
76
+
77
+ // 1) no change when values & indexes match
78
+ for (j = unusedItems - 1; j >= 0; --j) {
79
+ item = items[j]
80
+ oldIndex = item.index
81
+ if (oldIndex < newItems.length && newItems[oldIndex] === item.value) {
82
+ temp[oldIndex] = mapped[oldIndex]
83
+ if (--unusedItems !== j) {
84
+ items[j] = items[unusedItems]
85
+ items[unusedItems] = item
86
+ }
87
+ }
88
+ }
89
+
90
+ // #2 prepare values matcher
91
+ const matcher = new Map()
92
+ const matchedItems = new Uint8Array(unusedItems)
93
+ for (j = unusedItems - 1; j >= 0; --j) {
94
+ oldValue = items[j].value
95
+ matcher.get(oldValue)?.push(j) ?? matcher.set(oldValue, [j])
96
+ }
97
+
98
+ // 2) change indexes when values match
99
+ for (i = 0; i < newItems.length; ++i) {
100
+ if (i in temp) continue
101
+ newValue = newItems[i]
102
+ j = matcher.get(newValue)?.pop() ?? -1
103
+ if (j >= 0) {
104
+ item = items[j]
105
+ oldIndex = item.index
106
+ temp[i] = mapped[oldIndex]
107
+ item.index = i
108
+ item.indexSetter?.(i)
109
+ matchedItems[j] = 1
110
+ }
111
+ }
112
+
113
+ // 3) reduce unusedItems for matched items
114
+ for (j = matchedItems.length - 1; j >= 0; --j) {
115
+ if (matchedItems[j] && --unusedItems !== j) {
116
+ item = items[j]
117
+ items[j] = items[unusedItems]
118
+ items[unusedItems] = item
119
+ }
120
+ }
121
+
122
+ // 4) change values when indexes match
123
+ for (j = unusedItems - 1; j >= 0; --j) {
124
+ item = items[j];
125
+ oldIndex = item.index;
126
+ if (!(oldIndex in temp) && oldIndex < newItems.length) {
127
+ temp[oldIndex] = mapped[oldIndex]
128
+ newValue = newItems[oldIndex]
129
+ item.value = newValue
130
+ item.valueSetter?.(item.valueSetter)
131
+ if (--unusedItems !== j) {
132
+ items[j] = items[unusedItems]
133
+ items[unusedItems] = item
134
+ }
135
+ }
136
+ }
137
+
138
+ // 5) change value & index when none matched and create new if no unused items left
139
+ for (i = 0; i < newItems.length; ++i) {
140
+ if (i in temp) continue
141
+ newValue = newItems[i]
142
+ if (unusedItems > 0) {
143
+ item = items[--unusedItems]
144
+ temp[i] = mapped[item.index]
145
+ batch(changeBoth);
146
+ } else {
147
+ temp[i] = root(mapper)
148
+ }
149
+ }
150
+
151
+ // 6) delete any old unused items left
152
+ disposeList(items.splice(0, unusedItems))
153
+
154
+ return (mapped = temp);
155
+ })
156
+ })
157
+ }
158
+
159
+ function disposeList(list) {
160
+ for (let i = 0; i < list.length; i++) {
161
+ list[i]?.disposer()
162
+ }
163
+ }