@pfern/elements 0.1.2 → 0.1.3
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/elements.js +215 -62
- package/package.json +1 -1
package/elements.js
CHANGED
|
@@ -61,14 +61,14 @@ const diffTree = (a, b) => {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
65
64
|
/**
|
|
66
65
|
* Compares the children of two vnodes and returns patch list.
|
|
67
66
|
*
|
|
68
67
|
* @param {Array} aChildren - Previous vnode children
|
|
69
68
|
* @param {Array} bChildren - New vnode children
|
|
70
69
|
* @returns {Array} patches - One per child node
|
|
71
|
-
*/
|
|
70
|
+
*/
|
|
71
|
+
const diffChildren = (aChildren, bChildren) => {
|
|
72
72
|
const patches = []
|
|
73
73
|
const len = Math.max(aChildren.length, bChildren.length)
|
|
74
74
|
for (let i = 0; i < len; i++) {
|
|
@@ -85,6 +85,8 @@ const diffTree = (a, b) => {
|
|
|
85
85
|
* *if* the listener returns a vnode (to support declarative form updates).
|
|
86
86
|
* - Handlers for these event types receive `(elements, event)` as arguments,
|
|
87
87
|
* where `elements` is `event.target.elements` if available.
|
|
88
|
+
* - Async handlers are supported: if the listener returns a Promise,
|
|
89
|
+
* it will be awaited and the resulting vnode (if any) will be rendered.
|
|
88
90
|
*
|
|
89
91
|
* @param {HTMLElement} el - The DOM element to receive props
|
|
90
92
|
* @param {Object} props - Attributes and event listeners to assign
|
|
@@ -92,7 +94,7 @@ const diffTree = (a, b) => {
|
|
|
92
94
|
const assignProperties = (el, props) =>
|
|
93
95
|
Object.entries(props).forEach(([key, value]) => {
|
|
94
96
|
if (key.startsWith('on') && typeof value === 'function') {
|
|
95
|
-
el[key] = (...args) => {
|
|
97
|
+
el[key] = async (...args) => {
|
|
96
98
|
let target = el
|
|
97
99
|
while (target && !target.__root) target = target.parentNode
|
|
98
100
|
if (!target) return
|
|
@@ -102,9 +104,9 @@ const assignProperties = (el, props) =>
|
|
|
102
104
|
const isFormEvent = /^(oninput|onsubmit|onchange)$/.test(key)
|
|
103
105
|
const elements = isFormEvent && event?.target?.elements || null
|
|
104
106
|
|
|
105
|
-
const result = isFormEvent
|
|
107
|
+
const result = await (isFormEvent
|
|
106
108
|
? value.call(el, elements, event)
|
|
107
|
-
: value.call(el, event)
|
|
109
|
+
: value.call(el, event))
|
|
108
110
|
|
|
109
111
|
if (isFormEvent && result !== undefined) {
|
|
110
112
|
event.preventDefault()
|
|
@@ -119,11 +121,12 @@ const assignProperties = (el, props) =>
|
|
|
119
121
|
|
|
120
122
|
if (DEBUG && result !== undefined && !Array.isArray(result)) {
|
|
121
123
|
isFormEvent && event.preventDefault()
|
|
122
|
-
DEBUG
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
DEBUG
|
|
125
|
+
&& console.warn(
|
|
126
|
+
`Listener '${key}' on <${el.tagName.toLowerCase()}> returned "${result}".\n`
|
|
127
|
+
+ 'If you intended a UI update, return a vnode array like: div({}, ...).\n'
|
|
128
|
+
+ 'Otherwise, return undefined (or nothing) for native event listener behavior.'
|
|
129
|
+
)
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
if (Array.isArray(result)) {
|
|
@@ -153,8 +156,10 @@ const assignProperties = (el, props) =>
|
|
|
153
156
|
el.setAttribute(key, value)
|
|
154
157
|
}
|
|
155
158
|
} catch {
|
|
156
|
-
DEBUG
|
|
157
|
-
|
|
159
|
+
DEBUG
|
|
160
|
+
&& console.warn(
|
|
161
|
+
`Illegal DOM property assignment for ${el.tagName}: ${key}: ${value}`
|
|
162
|
+
)
|
|
158
163
|
}
|
|
159
164
|
}
|
|
160
165
|
})
|
|
@@ -194,9 +199,12 @@ const renderTree = (node, isRoot = true) => {
|
|
|
194
199
|
}
|
|
195
200
|
|
|
196
201
|
let el =
|
|
197
|
-
tag === 'html'
|
|
198
|
-
|
|
199
|
-
|
|
202
|
+
tag === 'html'
|
|
203
|
+
? document.documentElement
|
|
204
|
+
: tag === 'head'
|
|
205
|
+
? document.head
|
|
206
|
+
: tag === 'body'
|
|
207
|
+
? document.body
|
|
200
208
|
: svgTagNames.includes(tag)
|
|
201
209
|
? document.createElementNS(svgNS, tag)
|
|
202
210
|
: document.createElement(tag)
|
|
@@ -311,88 +319,232 @@ export const component = fn => {
|
|
|
311
319
|
|
|
312
320
|
const htmlTagNames = [
|
|
313
321
|
// Document metadata
|
|
314
|
-
'html',
|
|
322
|
+
'html',
|
|
323
|
+
'head',
|
|
324
|
+
'base',
|
|
325
|
+
'link',
|
|
326
|
+
'meta',
|
|
327
|
+
'title',
|
|
315
328
|
|
|
316
329
|
// Sections
|
|
317
|
-
'body',
|
|
318
|
-
'
|
|
330
|
+
'body',
|
|
331
|
+
'header',
|
|
332
|
+
'hgroup',
|
|
333
|
+
'nav',
|
|
334
|
+
'main',
|
|
335
|
+
'section',
|
|
336
|
+
'article',
|
|
337
|
+
'aside',
|
|
338
|
+
'footer',
|
|
339
|
+
'address',
|
|
319
340
|
|
|
320
341
|
// Text content
|
|
321
|
-
'h1',
|
|
322
|
-
'
|
|
342
|
+
'h1',
|
|
343
|
+
'h2',
|
|
344
|
+
'h3',
|
|
345
|
+
'h4',
|
|
346
|
+
'h5',
|
|
347
|
+
'h6',
|
|
348
|
+
'p',
|
|
349
|
+
'hr',
|
|
350
|
+
'menu',
|
|
351
|
+
'pre',
|
|
352
|
+
'blockquote',
|
|
353
|
+
'ol',
|
|
354
|
+
'ul',
|
|
355
|
+
'li',
|
|
356
|
+
'dl',
|
|
357
|
+
'dt',
|
|
358
|
+
'dd',
|
|
359
|
+
'figure',
|
|
360
|
+
'figcaption',
|
|
323
361
|
'div',
|
|
324
362
|
|
|
325
363
|
// Inline text semantics
|
|
326
|
-
'a',
|
|
327
|
-
'
|
|
328
|
-
'
|
|
329
|
-
'
|
|
364
|
+
'a',
|
|
365
|
+
'abbr',
|
|
366
|
+
'b',
|
|
367
|
+
'bdi',
|
|
368
|
+
'bdo',
|
|
369
|
+
'br',
|
|
370
|
+
'cite',
|
|
371
|
+
'code',
|
|
372
|
+
'data',
|
|
373
|
+
'dfn',
|
|
374
|
+
'em',
|
|
375
|
+
'i',
|
|
376
|
+
'kbd',
|
|
377
|
+
'mark',
|
|
378
|
+
'q',
|
|
379
|
+
'rb',
|
|
380
|
+
'rp',
|
|
381
|
+
'rt',
|
|
382
|
+
'rtc',
|
|
383
|
+
'ruby',
|
|
384
|
+
's',
|
|
385
|
+
'samp',
|
|
386
|
+
'small',
|
|
387
|
+
'span',
|
|
388
|
+
'strong',
|
|
389
|
+
'sub',
|
|
390
|
+
'sup',
|
|
391
|
+
'time',
|
|
392
|
+
'u',
|
|
393
|
+
'var',
|
|
394
|
+
'wbr',
|
|
330
395
|
|
|
331
396
|
// Edits
|
|
332
|
-
'ins',
|
|
397
|
+
'ins',
|
|
398
|
+
'del',
|
|
333
399
|
|
|
334
400
|
// Embedded content
|
|
335
|
-
'img',
|
|
336
|
-
'
|
|
401
|
+
'img',
|
|
402
|
+
'iframe',
|
|
403
|
+
'embed',
|
|
404
|
+
'object',
|
|
405
|
+
'param',
|
|
406
|
+
'video',
|
|
407
|
+
'audio',
|
|
408
|
+
'source',
|
|
409
|
+
'track',
|
|
410
|
+
'picture',
|
|
337
411
|
|
|
338
412
|
// Table content
|
|
339
|
-
'table',
|
|
340
|
-
'
|
|
413
|
+
'table',
|
|
414
|
+
'caption',
|
|
415
|
+
'thead',
|
|
416
|
+
'tbody',
|
|
417
|
+
'tfoot',
|
|
418
|
+
'tr',
|
|
419
|
+
'th',
|
|
420
|
+
'td',
|
|
421
|
+
'colgroup',
|
|
422
|
+
'col',
|
|
341
423
|
|
|
342
424
|
// Forms
|
|
343
|
-
'form',
|
|
344
|
-
'
|
|
345
|
-
'
|
|
425
|
+
'form',
|
|
426
|
+
'fieldset',
|
|
427
|
+
'legend',
|
|
428
|
+
'label',
|
|
429
|
+
'input',
|
|
430
|
+
'button',
|
|
431
|
+
'select',
|
|
432
|
+
'datalist',
|
|
433
|
+
'optgroup',
|
|
434
|
+
'option',
|
|
435
|
+
'textarea',
|
|
436
|
+
'output',
|
|
437
|
+
'progress',
|
|
438
|
+
'meter',
|
|
346
439
|
|
|
347
440
|
// Interactive elements
|
|
348
|
-
'details',
|
|
441
|
+
'details',
|
|
442
|
+
'search',
|
|
443
|
+
'summary',
|
|
444
|
+
'dialog',
|
|
445
|
+
'slot',
|
|
446
|
+
'template',
|
|
349
447
|
|
|
350
448
|
// Scripting and style
|
|
351
|
-
'script',
|
|
449
|
+
'script',
|
|
450
|
+
'noscript',
|
|
451
|
+
'style',
|
|
352
452
|
|
|
353
453
|
// Web components and others
|
|
354
|
-
'canvas',
|
|
454
|
+
'canvas',
|
|
455
|
+
'picture',
|
|
456
|
+
'map',
|
|
457
|
+
'area',
|
|
458
|
+
'slot'
|
|
355
459
|
]
|
|
356
460
|
|
|
357
461
|
const svgTagNames = [
|
|
358
462
|
// Animation elements
|
|
359
|
-
'a',
|
|
463
|
+
'a',
|
|
464
|
+
'animate',
|
|
465
|
+
'animateMotion',
|
|
466
|
+
'animateTransform',
|
|
467
|
+
'mpath',
|
|
468
|
+
'set',
|
|
360
469
|
|
|
361
470
|
// Basic shapes
|
|
362
|
-
'circle',
|
|
471
|
+
'circle',
|
|
472
|
+
'ellipse',
|
|
473
|
+
'line',
|
|
474
|
+
'path',
|
|
475
|
+
'polygon',
|
|
476
|
+
'polyline',
|
|
477
|
+
'rect',
|
|
363
478
|
|
|
364
479
|
// Container / structural
|
|
365
|
-
'defs',
|
|
480
|
+
'defs',
|
|
481
|
+
'g',
|
|
482
|
+
'marker',
|
|
483
|
+
'mask',
|
|
484
|
+
'pattern',
|
|
485
|
+
'svg',
|
|
486
|
+
'switch',
|
|
487
|
+
'symbol',
|
|
488
|
+
'use',
|
|
366
489
|
|
|
367
490
|
// Descriptive
|
|
368
|
-
'desc',
|
|
491
|
+
'desc',
|
|
492
|
+
'metadata',
|
|
493
|
+
'title',
|
|
369
494
|
|
|
370
495
|
// Filter primitives
|
|
371
|
-
'filter',
|
|
372
|
-
'
|
|
373
|
-
'
|
|
374
|
-
'
|
|
375
|
-
'
|
|
376
|
-
'
|
|
496
|
+
'filter',
|
|
497
|
+
'feBlend',
|
|
498
|
+
'feColorMatrix',
|
|
499
|
+
'feComponentTransfer',
|
|
500
|
+
'feComposite',
|
|
501
|
+
'feConvolveMatrix',
|
|
502
|
+
'feDiffuseLighting',
|
|
503
|
+
'feDisplacementMap',
|
|
504
|
+
'feDistantLight',
|
|
505
|
+
'feDropShadow',
|
|
506
|
+
'feFlood',
|
|
507
|
+
'feFuncA',
|
|
508
|
+
'feFuncB',
|
|
509
|
+
'feFuncG',
|
|
510
|
+
'feFuncR',
|
|
511
|
+
'feGaussianBlur',
|
|
512
|
+
'feImage',
|
|
513
|
+
'feMerge',
|
|
514
|
+
'feMergeNode',
|
|
515
|
+
'feMorphology',
|
|
516
|
+
'feOffset',
|
|
517
|
+
'fePointLight',
|
|
518
|
+
'feSpecularLighting',
|
|
519
|
+
'feSpotLight',
|
|
520
|
+
'feTile',
|
|
521
|
+
'feTurbulence',
|
|
377
522
|
|
|
378
523
|
// Gradient / paint servers
|
|
379
|
-
'linearGradient',
|
|
524
|
+
'linearGradient',
|
|
525
|
+
'radialGradient',
|
|
526
|
+
'stop',
|
|
380
527
|
|
|
381
528
|
// Graphics elements
|
|
382
|
-
'image',
|
|
529
|
+
'image',
|
|
530
|
+
'foreignObject', // included in graphics section as non‑standard children
|
|
383
531
|
|
|
384
532
|
// Text and text-path
|
|
385
|
-
'text',
|
|
533
|
+
'text',
|
|
534
|
+
'textPath',
|
|
535
|
+
'tspan',
|
|
386
536
|
|
|
387
537
|
// Scripting/style
|
|
388
|
-
'script',
|
|
538
|
+
'script',
|
|
539
|
+
'style',
|
|
389
540
|
|
|
390
541
|
// View
|
|
391
542
|
'view'
|
|
392
543
|
]
|
|
393
544
|
|
|
394
545
|
const tagNames = [...htmlTagNames, ...svgTagNames]
|
|
395
|
-
const isPropsObject = x =>
|
|
546
|
+
const isPropsObject = x =>
|
|
547
|
+
typeof x === 'object'
|
|
396
548
|
&& x !== null
|
|
397
549
|
&& !Array.isArray(x)
|
|
398
550
|
&& !(typeof Node !== 'undefined' && x instanceof Node)
|
|
@@ -422,18 +574,20 @@ const isPropsObject = x => typeof x === 'object'
|
|
|
422
574
|
*
|
|
423
575
|
* @type {Record<string, ElementHelper>}
|
|
424
576
|
*/
|
|
425
|
-
export const elements = tagNames.reduce(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
577
|
+
export const elements = tagNames.reduce(
|
|
578
|
+
(acc, tag) => ({
|
|
579
|
+
...acc,
|
|
580
|
+
[tag]: (propsOrChild, ...children) => {
|
|
581
|
+
const props = isPropsObject(propsOrChild) ? propsOrChild : {}
|
|
582
|
+
const actualChildren =
|
|
583
|
+
props === propsOrChild ? children : [propsOrChild, ...children]
|
|
584
|
+
return [tag, props, ...actualChildren]
|
|
585
|
+
}
|
|
586
|
+
}),
|
|
587
|
+
{
|
|
588
|
+
fragment: (...children) => ['fragment', {}, ...children]
|
|
433
589
|
}
|
|
434
|
-
|
|
435
|
-
fragment: (...children) => ['fragment', {}, ...children]
|
|
436
|
-
})
|
|
590
|
+
)
|
|
437
591
|
|
|
438
592
|
/**
|
|
439
593
|
* <html>
|
|
@@ -1928,4 +2082,3 @@ export const view = elements.view
|
|
|
1928
2082
|
|
|
1929
2083
|
// TODO: MathML
|
|
1930
2084
|
// https://developer.mozilla.org/en-US/docs/Web/MathML/Reference/Element
|
|
1931
|
-
|