microtags 0.0.3 → 0.0.5
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/README.md +305 -49
- package/dist/meta.json +2 -2
- package/dist/render.cjs.map +2 -2
- package/dist/render.d.ts +4 -6
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js.map +2 -2
- package/dist/render.min.js +1 -1
- package/dist/render.min.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
[](https://bundlephobia.com/package/microtags)
|
|
10
10
|
[](LICENSE)
|
|
11
11
|
|
|
12
|
-
Web component helper with reactive props, automatic cleanup,
|
|
13
|
-
|
|
14
|
-
[alien-signals](https://github.com/stackblitz/alien-signals).
|
|
12
|
+
Web component helper with reactive props, automatic cleanup, and no
|
|
13
|
+
[Shadow DOM](https://gomakethings.com/the-shadow-dom-is-an-antipattern/).
|
|
14
|
+
Reactive via [alien-signals](https://github.com/stackblitz/alien-signals).
|
|
15
15
|
|
|
16
16
|
**Size**: 3.41 kB with all dependencies, minified and brotlied
|
|
17
17
|
|
|
@@ -36,13 +36,17 @@ Inspired by [nanotags](https://nanotags.psdcoder.dev/). Reactive props use
|
|
|
36
36
|
* [Refs](#refs)
|
|
37
37
|
+ [`withRefs`](#withrefs)
|
|
38
38
|
+ [Element types](#element-types)
|
|
39
|
-
* [define](#define)
|
|
40
|
-
* [withContexts](#withcontexts)
|
|
41
|
-
* [setup and ctx](#setup-and-ctx)
|
|
39
|
+
* [`define`](#define)
|
|
40
|
+
* [`withContexts`](#withcontexts)
|
|
41
|
+
* [`setup` and `ctx`](#setup-and-ctx)
|
|
42
42
|
+ [peek and untracked](#peek-and-untracked)
|
|
43
|
-
- [
|
|
44
|
-
|
|
45
|
-
* [microtags/
|
|
43
|
+
- [Templates](#templates)
|
|
44
|
+
- [Subpath Exports](#subpath-exports)
|
|
45
|
+
* [`microtags/context`](#microtagscontext)
|
|
46
|
+
* [`microtags/render`](#microtagsrender)
|
|
47
|
+
+ [`renderList`](#renderlist)
|
|
48
|
+
+ [`render`](#render)
|
|
49
|
+
+ [Templates](#templates-1)
|
|
46
50
|
- [Divergence from nanotags](#divergence-from-nanotags)
|
|
47
51
|
|
|
48
52
|
<!-- tocstop -->
|
|
@@ -356,8 +360,8 @@ it instead. Queries are resolved relative to the host element, during
|
|
|
356
360
|
|
|
357
361
|
```ts
|
|
358
362
|
.withRefs(r => ({
|
|
359
|
-
button: r.one(), // resolves [data-ref="button"]
|
|
360
|
-
items: r.all(), // resolves all [data-ref="items"]
|
|
363
|
+
button: r.one(), // resolves [data-ref="button"] (throws if missing)
|
|
364
|
+
items: r.all(), // resolves all [data-ref="items"] (empty array if none)
|
|
361
365
|
}))
|
|
362
366
|
```
|
|
363
367
|
|
|
@@ -407,46 +411,83 @@ constrained to `HTMLElement`, so cast at the use site if you need one
|
|
|
407
411
|
(e.g. `ctx.refs.icon as unknown as SVGSVGElement`).
|
|
408
412
|
|
|
409
413
|
|
|
410
|
-
### define
|
|
414
|
+
### `define`
|
|
411
415
|
|
|
412
416
|
```ts
|
|
413
417
|
import { define } from 'microtags'
|
|
414
418
|
|
|
415
|
-
define(tagName:
|
|
419
|
+
define(tagName:string):ComponentBuilder
|
|
416
420
|
```
|
|
417
421
|
|
|
418
422
|
Returns a fluent builder. Call `.setup()` to register the custom element.
|
|
419
423
|
All builder methods are chainable and fully type-inferred.
|
|
420
424
|
|
|
421
|
-
### withContexts
|
|
425
|
+
### `withContexts`
|
|
426
|
+
|
|
427
|
+
Context is for the case where a parent component defines some state, e.g. a
|
|
428
|
+
`theme`, and the top-level (application) knows nothing about the theme. But the
|
|
429
|
+
top level application does determine what children the parent renders
|
|
430
|
+
(it passes in children), and the children need to know the theme.
|
|
431
|
+
|
|
432
|
+
In that case, there is no way for the "parent" component to pass the theme to
|
|
433
|
+
the children, and the root level (application) doesn't know what the theme is.
|
|
434
|
+
|
|
435
|
+
Context gives us a "pull" based way to model this. The context-provider
|
|
436
|
+
publishes the value, and any descendants opt in (or pull values) with
|
|
437
|
+
`withContexts`, and the unknown-consumer and late-arrival problems are handled.
|
|
438
|
+
|
|
439
|
+
>
|
|
440
|
+
> [!IMPORTANT]
|
|
441
|
+
> Context is only relevant on the client-side.
|
|
442
|
+
> Context gives you nothing for first paint or the no-JS case.
|
|
443
|
+
>
|
|
444
|
+
|
|
422
445
|
|
|
423
446
|
```ts
|
|
424
|
-
import {
|
|
447
|
+
import { define } from 'microtags'
|
|
448
|
+
import { createContext, provide } from 'microtags/context'
|
|
425
449
|
|
|
426
450
|
const ThemeToken = createContext<string>()
|
|
427
451
|
|
|
428
|
-
|
|
452
|
+
// Parent: owns the theme and publishes it to its descendants. It does
|
|
453
|
+
// not reference the children below. The application composes those in.
|
|
454
|
+
define('theme-provider')
|
|
455
|
+
.setup(ctx => {
|
|
456
|
+
ctx.onCleanup(provide(ctx.host, ThemeToken, 'dark'))
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
// Child: opts in to the theme and reads whichever provider it is
|
|
460
|
+
// composed into. It never receives the value as a prop.
|
|
461
|
+
define('themed-card')
|
|
429
462
|
.withContexts(() => ({
|
|
430
463
|
theme: ThemeToken,
|
|
431
464
|
}))
|
|
432
465
|
.setup(ctx => {
|
|
433
466
|
ctx.effect(() => {
|
|
434
|
-
|
|
467
|
+
ctx.host.dataset.theme = ctx.contexts.theme
|
|
435
468
|
})
|
|
436
469
|
})
|
|
437
470
|
```
|
|
438
471
|
|
|
472
|
+
```html
|
|
473
|
+
<!-- The application writes this markup. It nests <themed-card> inside
|
|
474
|
+
<theme-provider> but never sets the theme itself. -->
|
|
475
|
+
<theme-provider>
|
|
476
|
+
<themed-card>Card content</themed-card>
|
|
477
|
+
</theme-provider>
|
|
478
|
+
```
|
|
479
|
+
|
|
439
480
|
`setup()` is deferred until all required contexts resolve. See
|
|
440
|
-
[microtags/context](#microtagscontext) for the provider API.
|
|
481
|
+
[`microtags/context`](#microtagscontext) for the provider API.
|
|
441
482
|
|
|
442
|
-
### setup and ctx
|
|
483
|
+
### `setup` and `ctx`
|
|
443
484
|
|
|
444
485
|
```ts
|
|
445
486
|
.setup(ctx => {
|
|
446
|
-
// ctx.host
|
|
447
|
-
// ctx.props
|
|
448
|
-
// ctx.refs
|
|
449
|
-
// ctx.contexts
|
|
487
|
+
// ctx.host the HTMLElement itself
|
|
488
|
+
// ctx.props callable signals for each declared prop
|
|
489
|
+
// ctx.refs resolved DOM refs
|
|
490
|
+
// ctx.contexts consumed context values (reactive)
|
|
450
491
|
|
|
451
492
|
// Run a tracked effect; auto-disposed on disconnect.
|
|
452
493
|
ctx.effect(() => {
|
|
@@ -486,46 +527,265 @@ ctx.effect(() => {
|
|
|
486
527
|
})
|
|
487
528
|
```
|
|
488
529
|
|
|
489
|
-
##
|
|
530
|
+
## Templates
|
|
531
|
+
|
|
532
|
+
[`render` and `renderList`](#microtagsrender) both take a `<template>`
|
|
533
|
+
element as their second argument. A
|
|
534
|
+
[`<template>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/template)
|
|
535
|
+
is a native HTML element that holds markup the browser parses but does not
|
|
536
|
+
render. Its contents are inert. Nothing inside paints, scripts do not run,
|
|
537
|
+
images and other resources do not load, and the nodes stay out of the
|
|
538
|
+
document until you clone them.
|
|
490
539
|
|
|
491
|
-
|
|
540
|
+
See [./example/ssr.ts, line 89](./example/ssr.ts#L89).
|
|
492
541
|
|
|
493
|
-
|
|
542
|
+
You write the markup for an item inside the template, and the
|
|
543
|
+
reconciler stamps out a copy per item by cloning `template.content`. The
|
|
544
|
+
template's first element child is the unit that gets cloned, so give each
|
|
545
|
+
template a single root element.
|
|
546
|
+
|
|
547
|
+
```html
|
|
548
|
+
<item-list>
|
|
549
|
+
<ul></ul>
|
|
550
|
+
<template>
|
|
551
|
+
<li>
|
|
552
|
+
<span class="name"></span>
|
|
553
|
+
<span class="count"></span>
|
|
554
|
+
</li>
|
|
555
|
+
</template>
|
|
556
|
+
</item-list>
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
The component resolves the list and the template as refs, then calls
|
|
560
|
+
`renderList` from inside an effect so the list re-renders whenever the
|
|
561
|
+
data signal changes.
|
|
494
562
|
|
|
495
563
|
```ts
|
|
496
|
-
import {
|
|
564
|
+
import { define } from 'microtags'
|
|
565
|
+
import { signal } from 'alien-signals'
|
|
566
|
+
import { renderList } from 'microtags/render'
|
|
567
|
+
|
|
568
|
+
type Item = { id:string; name:string; count:number }
|
|
569
|
+
|
|
570
|
+
define('item-list')
|
|
571
|
+
.withRefs(r => ({
|
|
572
|
+
list: r.one('ul'),
|
|
573
|
+
row: r.one('template'), // typed as HTMLTemplateElement
|
|
574
|
+
}))
|
|
575
|
+
.setup(ctx => {
|
|
576
|
+
const items = signal<Item[]>([
|
|
577
|
+
{ id: 'a', name: 'Alice', count: 1 },
|
|
578
|
+
{ id: 'b', name: 'Bob', count: 2 },
|
|
579
|
+
])
|
|
580
|
+
|
|
581
|
+
// Re-runs on every items() change; only changed rows are touched.
|
|
582
|
+
ctx.effect(() => {
|
|
583
|
+
renderList(ctx.refs.list, ctx.refs.row, {
|
|
584
|
+
data: items(),
|
|
585
|
+
key: item => item.id,
|
|
586
|
+
update: (el, item) => {
|
|
587
|
+
el.querySelector('.name')!.textContent = item.name
|
|
588
|
+
el.querySelector('.count')!.textContent =
|
|
589
|
+
String(item.count)
|
|
590
|
+
},
|
|
591
|
+
})
|
|
592
|
+
})
|
|
593
|
+
})
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
Because the template lives in your HTML instead of a JavaScript string, each
|
|
597
|
+
render clones real DOM nodes rather than re-parsing HTML, and the `update`
|
|
598
|
+
callback function mutates the DOM through properties like `textContent`.
|
|
599
|
+
Nothing concatenates strings into markup, so there is no string-injection
|
|
600
|
+
XSS vector.
|
|
601
|
+
|
|
602
|
+
## Subpath Exports
|
|
603
|
+
|
|
604
|
+
### `microtags/context`
|
|
605
|
+
|
|
606
|
+
Cross-component context backed by alien-signals. A provider shares a value
|
|
607
|
+
with everything nested inside it; a consumer reads the value from its nearest
|
|
608
|
+
provider ancestor and reacts when it changes.
|
|
609
|
+
|
|
610
|
+
`provide` seeds a signal the first time it runs and updates that same signal
|
|
611
|
+
on later calls, so re-providing inside an effect pushes changes to consumers.
|
|
612
|
+
|
|
613
|
+
```ts
|
|
614
|
+
import { define } from 'microtags'
|
|
615
|
+
import { signal } from 'alien-signals'
|
|
616
|
+
import { createContext, provide } from 'microtags/context'
|
|
497
617
|
|
|
498
618
|
const ThemeToken = createContext<string>()
|
|
499
619
|
|
|
500
|
-
//
|
|
501
|
-
|
|
502
|
-
|
|
620
|
+
// Provider: owns the theme and shares it with its descendants.
|
|
621
|
+
define('theme-provider')
|
|
622
|
+
.withRefs(r => ({
|
|
623
|
+
toggle: r.one('button'),
|
|
624
|
+
}))
|
|
625
|
+
.setup(ctx => {
|
|
626
|
+
const theme = signal('light')
|
|
627
|
+
|
|
628
|
+
// Share with descendants; remove the entry on disconnect.
|
|
629
|
+
ctx.onCleanup(provide(ctx.host, ThemeToken, theme()))
|
|
630
|
+
|
|
631
|
+
// Re-provide on change. provide() updates the existing signal,
|
|
632
|
+
// so consumers that read this value re-run.
|
|
633
|
+
ctx.effect(() => provide(ctx.host, ThemeToken, theme()))
|
|
634
|
+
|
|
635
|
+
ctx.on(ctx.refs.toggle, 'click', () => {
|
|
636
|
+
theme(theme() === 'light' ? 'dark' : 'light')
|
|
637
|
+
})
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
// Consumer: reads the nearest provider's theme, reactively.
|
|
641
|
+
define('themed-card')
|
|
642
|
+
.withContexts(() => ({
|
|
643
|
+
theme: ThemeToken,
|
|
644
|
+
}))
|
|
645
|
+
.setup(ctx => {
|
|
646
|
+
// Reading ctx.contexts.theme inside an effect subscribes to it,
|
|
647
|
+
// so this re-runs whenever the provider toggles the value.
|
|
648
|
+
ctx.effect(() => {
|
|
649
|
+
ctx.host.dataset.theme = ctx.contexts.theme
|
|
650
|
+
})
|
|
651
|
+
})
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
```html
|
|
655
|
+
<theme-provider>
|
|
656
|
+
<button>Toggle theme</button>
|
|
657
|
+
<themed-card>Card A</themed-card>
|
|
658
|
+
<themed-card>Card B</themed-card>
|
|
659
|
+
</theme-provider>
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
Both cards resolve to the same nearest `<theme-provider>`, so one click
|
|
663
|
+
updates both. `resolveContextSignal(element, token)` is the low-level lookup
|
|
664
|
+
those reads use under the hood; reach for it only when you need the provider
|
|
665
|
+
signal imperatively.
|
|
666
|
+
|
|
667
|
+
---
|
|
668
|
+
|
|
669
|
+
### `microtags/render`
|
|
670
|
+
|
|
671
|
+
A keyed reconciler for rendering lists and single items from a
|
|
672
|
+
`<template>`, with no virtual DOM and no diffing library.
|
|
673
|
+
|
|
674
|
+
Microtags' state is reactive. When a signal changes, you want the DOM to follow.
|
|
675
|
+
A way to do that for a list is to rebuild the markup on every change,
|
|
676
|
+
e.g. `container.innerHTML = items.map(toHTML).join('')`.
|
|
503
677
|
|
|
504
|
-
|
|
505
|
-
|
|
678
|
+
But rebuilding the markup on every change
|
|
679
|
+
**throws away and recreates every node on every update**, losing any DOM
|
|
680
|
+
state, like focus, text selection, scroll position, and the internal state of
|
|
681
|
+
elements like `<video>`, `<details>`, and `<iframe>`. It also re-parses HTML on
|
|
682
|
+
every pass, which creates a vector for string-concatenation XSS.
|
|
683
|
+
|
|
684
|
+
**This library exports a `renderList` and a `render` function**.
|
|
685
|
+
They diff against the live DOM. See
|
|
686
|
+
[example/render-demo.ts](./example/render-demo.ts) for a working example.
|
|
687
|
+
|
|
688
|
+
#### `renderList`
|
|
689
|
+
|
|
690
|
+
Each item is matched to an existing element by its `key` attribute, so nodes
|
|
691
|
+
are reused across renders rather than being recreated. Nodes whose keys
|
|
692
|
+
disappear from the data are removed, surviving nodes are moved into place
|
|
693
|
+
with the fewest DOM operations, and your `update` callback runs only for
|
|
694
|
+
items whose value actually changed (compared by identity). Focus state and other
|
|
695
|
+
DOM state survives a re-render, and changing one row touches only that row.
|
|
696
|
+
|
|
697
|
+
```ts
|
|
698
|
+
function renderList<T, E extends Element = Element> (
|
|
699
|
+
container:Element,
|
|
700
|
+
template:HTMLTemplateElement,
|
|
701
|
+
options:{
|
|
702
|
+
data:readonly T[];
|
|
703
|
+
key:(item:T, index:number) => string|number;
|
|
704
|
+
update:(el:E, item:T) => void;
|
|
705
|
+
}
|
|
706
|
+
):void
|
|
506
707
|
```
|
|
507
708
|
|
|
508
|
-
|
|
709
|
+
##### `renderList` Example
|
|
509
710
|
|
|
510
|
-
|
|
711
|
+
The markup is the list container plus a `<template>` for one row:
|
|
712
|
+
|
|
713
|
+
```html
|
|
714
|
+
<ul></ul>
|
|
715
|
+
<template>
|
|
716
|
+
<li>
|
|
717
|
+
<span class="name"></span>
|
|
718
|
+
</li>
|
|
719
|
+
</template>
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
Call `renderList` from inside an effect so it re-runs whenever the data
|
|
723
|
+
signal changes:
|
|
511
724
|
|
|
512
725
|
```ts
|
|
513
|
-
|
|
726
|
+
ctx.effect(() => {
|
|
727
|
+
renderList(ctx.refs.list, ctx.refs.row, {
|
|
728
|
+
data: items(),
|
|
729
|
+
key: item => item.id,
|
|
730
|
+
update: (el, item) => {
|
|
731
|
+
el.querySelector('.name')!.textContent = item.name
|
|
732
|
+
},
|
|
733
|
+
})
|
|
734
|
+
})
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
#### `render`
|
|
738
|
+
|
|
739
|
+
`render` is the same machinery for a single item. It keys on the template, so
|
|
740
|
+
repeated calls update the same element in place instead of replacing it.
|
|
741
|
+
|
|
742
|
+
```ts
|
|
743
|
+
function render<T, E extends Element = Element> (
|
|
744
|
+
container:Element,
|
|
745
|
+
template:HTMLTemplateElement,
|
|
746
|
+
options?:{
|
|
747
|
+
data?:T;
|
|
748
|
+
update?:(el:E, item:T) => void;
|
|
749
|
+
}
|
|
750
|
+
):void
|
|
751
|
+
```
|
|
514
752
|
|
|
515
|
-
|
|
753
|
+
##### `render` Example
|
|
754
|
+
|
|
755
|
+
The markup is a container plus a single-item `<template>`:
|
|
756
|
+
|
|
757
|
+
```html
|
|
758
|
+
<div class="item"></div>
|
|
759
|
+
<template>
|
|
760
|
+
<div>
|
|
761
|
+
<span></span>
|
|
762
|
+
</div>
|
|
763
|
+
</template>
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
```ts
|
|
767
|
+
import { render } from 'microtags/render'
|
|
768
|
+
|
|
769
|
+
// Render a single item into the container from the <template>.
|
|
516
770
|
render(container, template, {
|
|
517
771
|
data: item,
|
|
518
|
-
update: (el, item) => {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
// Render a keyed list with minimal DOM mutations.
|
|
522
|
-
renderList(container, template, {
|
|
523
|
-
data: items,
|
|
524
|
-
key: item => item.id,
|
|
525
|
-
update: (el, item) => { /* update el from item */ },
|
|
772
|
+
update: (el, item) => {
|
|
773
|
+
el.querySelector('span')!.textContent = item.name
|
|
774
|
+
},
|
|
526
775
|
})
|
|
527
776
|
```
|
|
528
777
|
|
|
778
|
+
For a runnable version, see `example/render-demo.ts`. It keeps an editable
|
|
779
|
+
`<input>` in every row and a single item panel rendered with `render`, then
|
|
780
|
+
re-renders on a timer and reorders on demand to show that focus, caret, and
|
|
781
|
+
typed text survive: keyed nodes are reused and at most moved, never rebuilt.
|
|
782
|
+
|
|
783
|
+
#### Templates
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
---
|
|
788
|
+
|
|
529
789
|
## Divergence from nanotags
|
|
530
790
|
|
|
531
791
|
| nanotags (nanostores) | microtags (alien-signals) |
|
|
@@ -536,8 +796,4 @@ renderList(container, template, {
|
|
|
536
796
|
| `ctx.props.$count` (store) | `ctx.props.count` (callable signal) |
|
|
537
797
|
|
|
538
798
|
Props declared with `withProps` are native alien-signals callable signals.
|
|
539
|
-
There is no `$` prefix convention
|
|
540
|
-
|
|
541
|
-
```
|
|
542
|
-
/ed3d-plan-and-execute:execute-implementation-plan /Users/nick/code/_microtags/docs/implementation-plans/2026-06-28-zod-validation-example/ /Users/nick/code/_microtags/
|
|
543
|
-
```
|
|
799
|
+
There is no `$` prefix convention; all prop names are plain identifiers.
|
package/dist/meta.json
CHANGED
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"format": "esm"
|
|
134
134
|
},
|
|
135
135
|
"src/render.ts": {
|
|
136
|
-
"bytes":
|
|
136
|
+
"bytes": 2457,
|
|
137
137
|
"imports": [
|
|
138
138
|
{
|
|
139
139
|
"path": "<runtime>",
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
"imports": [],
|
|
193
193
|
"exports": [],
|
|
194
194
|
"inputs": {},
|
|
195
|
-
"bytes":
|
|
195
|
+
"bytes": 4056
|
|
196
196
|
},
|
|
197
197
|
"dist/render.js": {
|
|
198
198
|
"imports": [
|
package/dist/render.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/render.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["const elementData = new WeakMap<Element, unknown>()\nconst elementKeys = new WeakMap<Element, string | number>()\n\nexport function renderList<T, E extends Element = Element> (\n container:Element,\n template:HTMLTemplateElement,\n options:{\n data:readonly T[];\n key:(item:T, index:number) => string|number;\n update:(el:E, item:T) => void;\n }\n):void {\n const { data, key: getKey, update } = options\n const existingByKey = new Map<string | number, E>()\n\n for (const child of Array.from(container.children)) {\n const key = elementKeys.get(child)\n if (key !== undefined) existingByKey.set(key, child as E)\n }\n\n const dataKeys = new Set(data.map(getKey))\n\n for (const child of Array.from(container.children)) {\n const key = elementKeys.get(child)\n if (key === undefined || !dataKeys.has(key)) {\n child.remove()\n elementKeys.delete(child)\n elementData.delete(child)\n }\n }\n\n let prev:E | null = null\n for (let i = 0; i < data.length; i++) {\n const item = data[i]\n const key = getKey(item, i)\n let el = existingByKey.get(key)\n\n if (!el) {\n el = (template.content.cloneNode(true) as Element)\n .firstElementChild as E\n elementKeys.set(el, key)\n }\n\n if (elementData.get(el) !== item) {\n update(el, item)\n elementData.set(el, item)\n }\n\n const expected = prev ?\n prev.nextElementSibling :\n container.firstElementChild\n\n if (el !== expected) {\n if (prev) prev.after(el)\n else container.prepend(el)\n }\n\n prev = el\n }\n}\n\nlet _tplCounter = 0\nconst tplIds = new WeakMap<HTMLTemplateElement, number>()\n\nexport function render<T, E extends Element = Element> (\n container:Element,\n template:HTMLTemplateElement,\n options?:{\n data?:T;\n update?:(el:E, item:T) => void;\n }\n):void {\n renderList<T, E>(container, template, {\n data: [\n options !== undefined && 'data' in options ?\n options.data as T :\n {} as T\n ],\n key: () => {\n let id = tplIds.get(template)\n if (id === undefined) {\n id = _tplCounter++\n tplIds.set(template, id)\n }\n return id\n },\n update: options?.update ?? (() => {}),\n })\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAM,cAAc,oBAAI,QAA0B;AAClD,IAAM,cAAc,oBAAI,QAAkC;AAEnD,SAAS,WACZ,WACA,UACA,SAKG;AACH,QAAM,EAAE,MAAM,KAAK,QAAQ,OAAO,IAAI;AACtC,QAAM,gBAAgB,oBAAI,IAAwB;AAElD,aAAW,SAAS,MAAM,KAAK,UAAU,QAAQ,GAAG;AAChD,UAAM,MAAM,YAAY,IAAI,KAAK;AACjC,QAAI,QAAQ,OAAW,eAAc,IAAI,KAAK,KAAU;AAAA,EAC5D;AAEA,QAAM,WAAW,IAAI,IAAI,KAAK,IAAI,MAAM,CAAC;AAEzC,aAAW,SAAS,MAAM,KAAK,UAAU,QAAQ,GAAG;AAChD,UAAM,MAAM,YAAY,IAAI,KAAK;AACjC,QAAI,QAAQ,UAAa,CAAC,SAAS,IAAI,GAAG,GAAG;AACzC,YAAM,OAAO;AACb,kBAAY,OAAO,KAAK;AACxB,kBAAY,OAAO,KAAK;AAAA,IAC5B;AAAA,EACJ;AAEA,MAAI,OAAgB;AACpB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AAClC,UAAM,OAAO,KAAK,CAAC;AACnB,UAAM,MAAM,OAAO,MAAM,CAAC;AAC1B,QAAI,KAAK,cAAc,IAAI,GAAG;AAE9B,QAAI,CAAC,IAAI;AACL,WAAM,SAAS,QAAQ,UAAU,IAAI,EAChC;AACL,kBAAY,IAAI,IAAI,GAAG;AAAA,IAC3B;AAEA,QAAI,YAAY,IAAI,EAAE,MAAM,MAAM;AAC9B,aAAO,IAAI,IAAI;AACf,kBAAY,IAAI,IAAI,IAAI;AAAA,IAC5B;AAEA,UAAM,WAAW,OACb,KAAK,qBACL,UAAU;AAEd,QAAI,OAAO,UAAU;AACjB,UAAI,KAAM,MAAK,MAAM,EAAE;AAAA,UAClB,WAAU,QAAQ,EAAE;AAAA,IAC7B;AAEA,WAAO;AAAA,EACX;AACJ;AAxDgB;AA0DhB,IAAI,cAAc;AAClB,IAAM,SAAS,oBAAI,QAAqC;AAEjD,SAAS,OACZ,WACA,UACA,SAIG;AACH,aAAiB,WAAW,UAAU;AAAA,IAClC,MAAM;AAAA,MACF,YAAY,UAAa,UAAU,UAC/B,QAAQ,OACR,CAAC;AAAA,IACT;AAAA,IACA,KAAK,6BAAM;AACP,UAAI,KAAK,OAAO,IAAI,QAAQ;AAC5B,UAAI,OAAO,QAAW;AAClB,aAAK;AACL,eAAO,IAAI,UAAU,EAAE;AAAA,MAC3B;AACA,aAAO;AAAA,IACX,GAPK;AAAA,IAQL,QAAQ,SAAS,WAAW,MAAM;AAAA,IAAC;AAAA,EACvC,CAAC;AACL;AAxBgB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/render.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare function renderList<T, E extends Element = Element>(container: Element, template: HTMLTemplateElement, options: {
|
|
2
2
|
data: readonly T[];
|
|
3
3
|
key: (item: T, index: number) => string | number;
|
|
4
4
|
update: (el: E, item: T) => void;
|
|
5
|
-
};
|
|
6
|
-
export
|
|
5
|
+
}): void;
|
|
6
|
+
export declare function render<T, E extends Element = Element>(container: Element, template: HTMLTemplateElement, options?: {
|
|
7
7
|
data?: T;
|
|
8
8
|
update?: (el: E, item: T) => void;
|
|
9
|
-
};
|
|
10
|
-
export declare function renderList<T, E extends Element = Element>(container: Element, template: HTMLTemplateElement, options: RenderListOptions<T, E>): void;
|
|
11
|
-
export declare function render<T, E extends Element = Element>(container: Element, template: HTMLTemplateElement, options?: RenderOptions<T, E>): void;
|
|
9
|
+
}): void;
|
|
12
10
|
//# sourceMappingURL=render.d.ts.map
|
package/dist/render.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,GAAG,OAAO,EACrD,SAAS,EAAC,OAAO,EACjB,QAAQ,EAAC,mBAAmB,EAC5B,OAAO,EAAC;IACJ,IAAI,EAAC,SAAS,CAAC,EAAE,CAAC;IAClB,GAAG,EAAC,CAAC,IAAI,EAAC,CAAC,EAAE,KAAK,EAAC,MAAM,KAAK,MAAM,GAAC,MAAM,CAAC;IAC5C,MAAM,EAAC,CAAC,EAAE,EAAC,CAAC,EAAE,IAAI,EAAC,CAAC,KAAK,IAAI,CAAC;CACjC,GACH,IAAI,CAgDL;AAKD,wBAAgB,MAAM,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,GAAG,OAAO,EACjD,SAAS,EAAC,OAAO,EACjB,QAAQ,EAAC,mBAAmB,EAC5B,OAAO,CAAC,EAAC;IACL,IAAI,CAAC,EAAC,CAAC,CAAC;IACR,MAAM,CAAC,EAAC,CAAC,EAAE,EAAC,CAAC,EAAE,IAAI,EAAC,CAAC,KAAK,IAAI,CAAC;CAClC,GACH,IAAI,CAiBL"}
|
package/dist/render.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/render.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": ";;;;;
|
|
4
|
+
"sourcesContent": ["const elementData = new WeakMap<Element, unknown>()\nconst elementKeys = new WeakMap<Element, string | number>()\n\nexport function renderList<T, E extends Element = Element> (\n container:Element,\n template:HTMLTemplateElement,\n options:{\n data:readonly T[];\n key:(item:T, index:number) => string|number;\n update:(el:E, item:T) => void;\n }\n):void {\n const { data, key: getKey, update } = options\n const existingByKey = new Map<string | number, E>()\n\n for (const child of Array.from(container.children)) {\n const key = elementKeys.get(child)\n if (key !== undefined) existingByKey.set(key, child as E)\n }\n\n const dataKeys = new Set(data.map(getKey))\n\n for (const child of Array.from(container.children)) {\n const key = elementKeys.get(child)\n if (key === undefined || !dataKeys.has(key)) {\n child.remove()\n elementKeys.delete(child)\n elementData.delete(child)\n }\n }\n\n let prev:E | null = null\n for (let i = 0; i < data.length; i++) {\n const item = data[i]\n const key = getKey(item, i)\n let el = existingByKey.get(key)\n\n if (!el) {\n el = (template.content.cloneNode(true) as Element)\n .firstElementChild as E\n elementKeys.set(el, key)\n }\n\n if (elementData.get(el) !== item) {\n update(el, item)\n elementData.set(el, item)\n }\n\n const expected = prev ?\n prev.nextElementSibling :\n container.firstElementChild\n\n if (el !== expected) {\n if (prev) prev.after(el)\n else container.prepend(el)\n }\n\n prev = el\n }\n}\n\nlet _tplCounter = 0\nconst tplIds = new WeakMap<HTMLTemplateElement, number>()\n\nexport function render<T, E extends Element = Element> (\n container:Element,\n template:HTMLTemplateElement,\n options?:{\n data?:T;\n update?:(el:E, item:T) => void;\n }\n):void {\n renderList<T, E>(container, template, {\n data: [\n options !== undefined && 'data' in options ?\n options.data as T :\n {} as T\n ],\n key: () => {\n let id = tplIds.get(template)\n if (id === undefined) {\n id = _tplCounter++\n tplIds.set(template, id)\n }\n return id\n },\n update: options?.update ?? (() => {}),\n })\n}\n"],
|
|
5
|
+
"mappings": ";;;;;AAAA,IAAM,cAAc,oBAAI,QAA0B;AAClD,IAAM,cAAc,oBAAI,QAAkC;AAEnD,SAAS,WACZ,WACA,UACA,SAKG;AACH,QAAM,EAAE,MAAM,KAAK,QAAQ,OAAO,IAAI;AACtC,QAAM,gBAAgB,oBAAI,IAAwB;AAElD,aAAW,SAAS,MAAM,KAAK,UAAU,QAAQ,GAAG;AAChD,UAAM,MAAM,YAAY,IAAI,KAAK;AACjC,QAAI,QAAQ,OAAW,eAAc,IAAI,KAAK,KAAU;AAAA,EAC5D;AAEA,QAAM,WAAW,IAAI,IAAI,KAAK,IAAI,MAAM,CAAC;AAEzC,aAAW,SAAS,MAAM,KAAK,UAAU,QAAQ,GAAG;AAChD,UAAM,MAAM,YAAY,IAAI,KAAK;AACjC,QAAI,QAAQ,UAAa,CAAC,SAAS,IAAI,GAAG,GAAG;AACzC,YAAM,OAAO;AACb,kBAAY,OAAO,KAAK;AACxB,kBAAY,OAAO,KAAK;AAAA,IAC5B;AAAA,EACJ;AAEA,MAAI,OAAgB;AACpB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AAClC,UAAM,OAAO,KAAK,CAAC;AACnB,UAAM,MAAM,OAAO,MAAM,CAAC;AAC1B,QAAI,KAAK,cAAc,IAAI,GAAG;AAE9B,QAAI,CAAC,IAAI;AACL,WAAM,SAAS,QAAQ,UAAU,IAAI,EAChC;AACL,kBAAY,IAAI,IAAI,GAAG;AAAA,IAC3B;AAEA,QAAI,YAAY,IAAI,EAAE,MAAM,MAAM;AAC9B,aAAO,IAAI,IAAI;AACf,kBAAY,IAAI,IAAI,IAAI;AAAA,IAC5B;AAEA,UAAM,WAAW,OACb,KAAK,qBACL,UAAU;AAEd,QAAI,OAAO,UAAU;AACjB,UAAI,KAAM,MAAK,MAAM,EAAE;AAAA,UAClB,WAAU,QAAQ,EAAE;AAAA,IAC7B;AAEA,WAAO;AAAA,EACX;AACJ;AAxDgB;AA0DhB,IAAI,cAAc;AAClB,IAAM,SAAS,oBAAI,QAAqC;AAEjD,SAAS,OACZ,WACA,UACA,SAIG;AACH,aAAiB,WAAW,UAAU;AAAA,IAClC,MAAM;AAAA,MACF,YAAY,UAAa,UAAU,UAC/B,QAAQ,OACR,CAAC;AAAA,IACT;AAAA,IACA,KAAK,6BAAM;AACP,UAAI,KAAK,OAAO,IAAI,QAAQ;AAC5B,UAAI,OAAO,QAAW;AAClB,aAAK;AACL,eAAO,IAAI,UAAU,EAAE;AAAA,MAC3B;AACA,aAAO;AAAA,IACX,GAPK;AAAA,IAQL,QAAQ,SAAS,WAAW,MAAM;AAAA,IAAC;AAAA,EACvC,CAAC;AACL;AAxBgB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/render.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var g=Object.defineProperty;var
|
|
1
|
+
var g=Object.defineProperty;var s=(l,a)=>g(l,"name",{value:a,configurable:!0});var o=new WeakMap,m=new WeakMap;function k(l,a,i){let{data:d,key:E,update:p}=i,f=new Map;for(let e of Array.from(l.children)){let t=m.get(e);t!==void 0&&f.set(t,e)}let T=new Set(d.map(E));for(let e of Array.from(l.children)){let t=m.get(e);(t===void 0||!T.has(t))&&(e.remove(),m.delete(e),o.delete(e))}let r=null;for(let e=0;e<d.length;e++){let t=d[e],u=E(t,e),n=f.get(u);n||(n=a.content.cloneNode(!0).firstElementChild,m.set(n,u)),o.get(n)!==t&&(p(n,t),o.set(n,t));let y=r?r.nextElementSibling:l.firstElementChild;n!==y&&(r?r.after(n):l.prepend(n)),r=n}}s(k,"renderList");var h=0,c=new WeakMap;function M(l,a,i){k(l,a,{data:[i!==void 0&&"data"in i?i.data:{}],key:s(()=>{let d=c.get(a);return d===void 0&&(d=h++,c.set(a,d)),d},"key"),update:i?.update??(()=>{})})}s(M,"render");export{M as render,k as renderList};
|
|
2
2
|
//# sourceMappingURL=render.min.js.map
|
package/dist/render.min.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/render.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": "+
|
|
4
|
+
"sourcesContent": ["const elementData = new WeakMap<Element, unknown>()\nconst elementKeys = new WeakMap<Element, string | number>()\n\nexport function renderList<T, E extends Element = Element> (\n container:Element,\n template:HTMLTemplateElement,\n options:{\n data:readonly T[];\n key:(item:T, index:number) => string|number;\n update:(el:E, item:T) => void;\n }\n):void {\n const { data, key: getKey, update } = options\n const existingByKey = new Map<string | number, E>()\n\n for (const child of Array.from(container.children)) {\n const key = elementKeys.get(child)\n if (key !== undefined) existingByKey.set(key, child as E)\n }\n\n const dataKeys = new Set(data.map(getKey))\n\n for (const child of Array.from(container.children)) {\n const key = elementKeys.get(child)\n if (key === undefined || !dataKeys.has(key)) {\n child.remove()\n elementKeys.delete(child)\n elementData.delete(child)\n }\n }\n\n let prev:E | null = null\n for (let i = 0; i < data.length; i++) {\n const item = data[i]\n const key = getKey(item, i)\n let el = existingByKey.get(key)\n\n if (!el) {\n el = (template.content.cloneNode(true) as Element)\n .firstElementChild as E\n elementKeys.set(el, key)\n }\n\n if (elementData.get(el) !== item) {\n update(el, item)\n elementData.set(el, item)\n }\n\n const expected = prev ?\n prev.nextElementSibling :\n container.firstElementChild\n\n if (el !== expected) {\n if (prev) prev.after(el)\n else container.prepend(el)\n }\n\n prev = el\n }\n}\n\nlet _tplCounter = 0\nconst tplIds = new WeakMap<HTMLTemplateElement, number>()\n\nexport function render<T, E extends Element = Element> (\n container:Element,\n template:HTMLTemplateElement,\n options?:{\n data?:T;\n update?:(el:E, item:T) => void;\n }\n):void {\n renderList<T, E>(container, template, {\n data: [\n options !== undefined && 'data' in options ?\n options.data as T :\n {} as T\n ],\n key: () => {\n let id = tplIds.get(template)\n if (id === undefined) {\n id = _tplCounter++\n tplIds.set(template, id)\n }\n return id\n },\n update: options?.update ?? (() => {}),\n })\n}\n"],
|
|
5
|
+
"mappings": "+EAAA,IAAMA,EAAc,IAAI,QAClBC,EAAc,IAAI,QAEjB,SAASC,EACZC,EACAC,EACAC,EAKG,CACH,GAAM,CAAE,KAAAC,EAAM,IAAKC,EAAQ,OAAAC,CAAO,EAAIH,EAChCI,EAAgB,IAAI,IAE1B,QAAWC,KAAS,MAAM,KAAKP,EAAU,QAAQ,EAAG,CAChD,IAAMQ,EAAMV,EAAY,IAAIS,CAAK,EAC7BC,IAAQ,QAAWF,EAAc,IAAIE,EAAKD,CAAU,CAC5D,CAEA,IAAME,EAAW,IAAI,IAAIN,EAAK,IAAIC,CAAM,CAAC,EAEzC,QAAWG,KAAS,MAAM,KAAKP,EAAU,QAAQ,EAAG,CAChD,IAAMQ,EAAMV,EAAY,IAAIS,CAAK,GAC7BC,IAAQ,QAAa,CAACC,EAAS,IAAID,CAAG,KACtCD,EAAM,OAAO,EACbT,EAAY,OAAOS,CAAK,EACxBV,EAAY,OAAOU,CAAK,EAEhC,CAEA,IAAIG,EAAgB,KACpB,QAASC,EAAI,EAAGA,EAAIR,EAAK,OAAQQ,IAAK,CAClC,IAAMC,EAAOT,EAAKQ,CAAC,EACbH,EAAMJ,EAAOQ,EAAMD,CAAC,EACtBE,EAAKP,EAAc,IAAIE,CAAG,EAEzBK,IACDA,EAAMZ,EAAS,QAAQ,UAAU,EAAI,EAChC,kBACLH,EAAY,IAAIe,EAAIL,CAAG,GAGvBX,EAAY,IAAIgB,CAAE,IAAMD,IACxBP,EAAOQ,EAAID,CAAI,EACff,EAAY,IAAIgB,EAAID,CAAI,GAG5B,IAAME,EAAWJ,EACbA,EAAK,mBACLV,EAAU,kBAEVa,IAAOC,IACHJ,EAAMA,EAAK,MAAMG,CAAE,EAClBb,EAAU,QAAQa,CAAE,GAG7BH,EAAOG,CACX,CACJ,CAxDgBE,EAAAhB,EAAA,cA0DhB,IAAIiB,EAAc,EACZC,EAAS,IAAI,QAEZ,SAASC,EACZlB,EACAC,EACAC,EAIG,CACHH,EAAiBC,EAAWC,EAAU,CAClC,KAAM,CACFC,IAAY,QAAa,SAAUA,EAC/BA,EAAQ,KACR,CAAC,CACT,EACA,IAAKa,EAAA,IAAM,CACP,IAAII,EAAKF,EAAO,IAAIhB,CAAQ,EAC5B,OAAIkB,IAAO,SACPA,EAAKH,IACLC,EAAO,IAAIhB,EAAUkB,CAAE,GAEpBA,CACX,EAPK,OAQL,OAAQjB,GAAS,SAAW,IAAM,CAAC,EACvC,CAAC,CACL,CAxBgBa,EAAAG,EAAA",
|
|
6
6
|
"names": ["elementData", "elementKeys", "renderList", "container", "template", "options", "data", "getKey", "update", "existingByKey", "child", "key", "dataKeys", "prev", "i", "item", "el", "expected", "__name", "_tplCounter", "tplIds", "render", "id"]
|
|
7
7
|
}
|