@thi.ng/rdom 0.13.3 → 0.13.4

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/klist.js CHANGED
@@ -3,169 +3,113 @@ import { Component } from "./component.js";
3
3
  import { $moveTo } from "./dom.js";
4
4
  import { __nextID } from "./idgen.js";
5
5
  import { $subWithID } from "./sub.js";
6
- /**
7
- * Similar to {@link $list}, however additionally uses keying to establish list
8
- * item identities and uses these keys (and *only* these!) in a more complex
9
- * diffing algorithm to avoid re-initialization of list items if they've been
10
- * re-ordered or changed positions due to removal/insertion of other items in
11
- * the list.
12
- *
13
- * @remarks
14
- * The given `keyFn` is used to obtain a *unique* key value for each list item
15
- * obtained from the reactive arrays obtained from `src`. Like a hash, the key
16
- * value MUST represent an item's *current* value such that if the value
17
- * changes, so does the key.
18
- *
19
- * @example
20
- * ```ts
21
- * const items = reactive([{id: "a", val: 1}, {id: "b", val: 2}, {id: "c", val: 3}]);
22
- *
23
- * $klist(
24
- * // data source (any rstream subscribable)
25
- * items,
26
- * // outer list element & attribs
27
- * "ul",
28
- * { class: "list red" },
29
- * // list item component constructor
30
- * (x) => ["li", {}, x.id, ` (${x.val})`],
31
- * // key function
32
- * (x) => `${x.id}-${x.val}`
33
- * ).mount(document.body);
34
- *
35
- * // update list:
36
- * // - item a will be removed
37
- * // - item b is unchanged
38
- * // - item d will be newly inserted
39
- * // - item c will be updated (due to new value)
40
- * setTimeout(
41
- * () => {
42
- * items.next([
43
- * { id: "b", val: 2 },
44
- * { id: "d", val: 4 },
45
- * { id: "c", val: 30 },
46
- * ]);
47
- * },
48
- * 1000
49
- * );
50
- * ```
51
- *
52
- * @param src -
53
- * @param tag -
54
- * @param attribs -
55
- * @param childCtor -
56
- * @param keyFn -
57
- */
58
- export const $klist = (src, tag, attribs, childCtor, keyFn) => $subWithID(src, new KList(tag, attribs, childCtor, keyFn), __nextID("klist", src));
59
- export class KList extends Component {
60
- tag;
61
- attribs;
62
- ctor;
63
- keyFn;
64
- items = [];
65
- cache;
66
- constructor(tag, attribs, ctor, keyFn = (_, i) => i) {
67
- super();
68
- this.tag = tag;
69
- this.attribs = attribs;
70
- this.ctor = ctor;
71
- this.keyFn = keyFn;
6
+ const $klist = (src, tag, attribs, childCtor, keyFn) => $subWithID(
7
+ src,
8
+ new KList(tag, attribs, childCtor, keyFn),
9
+ __nextID("klist", src)
10
+ );
11
+ class KList extends Component {
12
+ constructor(tag, attribs, ctor, keyFn = (_, i) => i) {
13
+ super();
14
+ this.tag = tag;
15
+ this.attribs = attribs;
16
+ this.ctor = ctor;
17
+ this.keyFn = keyFn;
18
+ }
19
+ items = [];
20
+ cache;
21
+ async mount(parent, index, state) {
22
+ this.items = [];
23
+ this.cache = /* @__PURE__ */ new Map();
24
+ this.el = this.$el(this.tag, this.attribs, null, parent, index);
25
+ this.update(state);
26
+ return this.el;
27
+ }
28
+ async unmount() {
29
+ this.items.forEach((c) => c.v.unmount());
30
+ this.$remove();
31
+ this.el = void 0;
32
+ this.items = void 0;
33
+ this.cache = void 0;
34
+ }
35
+ async update(curr) {
36
+ if (!curr)
37
+ return;
38
+ const { keyFn, items, ctor, cache, el: parent } = this;
39
+ const currItems = [];
40
+ const currCache = /* @__PURE__ */ new Map();
41
+ const offsets = /* @__PURE__ */ new Map();
42
+ const deltas = /* @__PURE__ */ new Map();
43
+ let numPrev = items.length;
44
+ let numCurr = curr.length;
45
+ let i;
46
+ for (i = numPrev; i-- > 0; ) {
47
+ offsets.set(items[i].k, i);
72
48
  }
73
- async mount(parent, index, state) {
74
- this.items = [];
75
- this.cache = new Map();
76
- this.el = this.$el(this.tag, this.attribs, null, parent, index);
77
- this.update(state);
78
- return this.el;
49
+ for (i = numCurr; i-- > 0; ) {
50
+ const val = curr[i];
51
+ const key = keyFn(val, i);
52
+ let item = cache.get(key);
53
+ item ? item.v.update(val) : item = {
54
+ k: key,
55
+ v: $compile(ctor(val))
56
+ };
57
+ currCache.set(key, currItems[i] = item);
58
+ const off = offsets.get(key);
59
+ off != void 0 && deltas.set(key, Math.abs(i - off));
79
60
  }
80
- async unmount() {
81
- this.items.forEach((c) => c.v.unmount());
82
- this.$remove();
83
- this.el = undefined;
84
- this.items = undefined;
85
- this.cache = undefined;
61
+ const willMove = /* @__PURE__ */ new Set();
62
+ const didMove = /* @__PURE__ */ new Set();
63
+ let next;
64
+ const insert = async (item) => {
65
+ if (cache.has(item.k)) {
66
+ $moveTo(parent, item.v.el, next);
67
+ next = item.v.el;
68
+ } else {
69
+ cache.set(item.k, item);
70
+ next = await item.v.mount(parent, next);
71
+ }
72
+ numCurr--;
73
+ };
74
+ while (numPrev && numCurr) {
75
+ const prevItem = items[numPrev - 1];
76
+ const prevKey = prevItem.k;
77
+ const currItem = currItems[numCurr - 1];
78
+ const currKey = currItem.k;
79
+ if (currItem === prevItem) {
80
+ next = currItem.v.el;
81
+ numPrev--;
82
+ numCurr--;
83
+ } else if (!currCache.has(prevKey)) {
84
+ await prevItem.v.unmount();
85
+ cache.delete(prevKey);
86
+ numPrev--;
87
+ } else if (!cache.has(currKey) || willMove.has(currKey)) {
88
+ await insert(currItem);
89
+ } else if (didMove.has(prevKey)) {
90
+ numPrev--;
91
+ } else if (deltas.get(currKey) > deltas.get(prevKey)) {
92
+ await insert(currItem);
93
+ didMove.add(currKey);
94
+ } else {
95
+ willMove.add(prevKey);
96
+ numPrev--;
97
+ }
86
98
  }
87
- async update(curr) {
88
- if (!curr)
89
- return;
90
- const { keyFn, items, ctor, cache, el: parent } = this;
91
- const currItems = [];
92
- const currCache = new Map();
93
- const offsets = new Map();
94
- const deltas = new Map();
95
- let numPrev = items.length;
96
- let numCurr = curr.length;
97
- let i;
98
- for (i = numPrev; i-- > 0;) {
99
- offsets.set(items[i].k, i);
100
- }
101
- for (i = numCurr; i-- > 0;) {
102
- const val = curr[i];
103
- const key = keyFn(val, i);
104
- let item = cache.get(key);
105
- item
106
- ? item.v.update(val) // TODO obsolete?
107
- : (item = {
108
- k: key,
109
- v: $compile(ctor(val)),
110
- });
111
- currCache.set(key, (currItems[i] = item));
112
- const off = offsets.get(key);
113
- off != undefined && deltas.set(key, Math.abs(i - off));
114
- }
115
- const willMove = new Set();
116
- const didMove = new Set();
117
- let next;
118
- const insert = async (item) => {
119
- if (cache.has(item.k)) {
120
- $moveTo(parent, item.v.el, next);
121
- next = item.v.el;
122
- }
123
- else {
124
- cache.set(item.k, item);
125
- next = await item.v.mount(parent, next);
126
- }
127
- numCurr--;
128
- };
129
- while (numPrev && numCurr) {
130
- const prevItem = items[numPrev - 1];
131
- const prevKey = prevItem.k;
132
- const currItem = currItems[numCurr - 1];
133
- const currKey = currItem.k;
134
- if (currItem === prevItem) {
135
- next = currItem.v.el;
136
- numPrev--;
137
- numCurr--;
138
- }
139
- else if (!currCache.has(prevKey)) {
140
- await prevItem.v.unmount();
141
- cache.delete(prevKey);
142
- numPrev--;
143
- }
144
- else if (!cache.has(currKey) || willMove.has(currKey)) {
145
- await insert(currItem);
146
- }
147
- else if (didMove.has(prevKey)) {
148
- numPrev--;
149
- }
150
- else if (deltas.get(currKey) > deltas.get(prevKey)) {
151
- await insert(currItem);
152
- didMove.add(currKey);
153
- }
154
- else {
155
- willMove.add(prevKey);
156
- numPrev--;
157
- }
158
- }
159
- while (numPrev--) {
160
- const item = items[numPrev];
161
- if (!currCache.has(item.k)) {
162
- await item.v.unmount();
163
- cache.delete(item.k);
164
- }
165
- }
166
- while (numCurr) {
167
- await insert(currItems[numCurr - 1]);
168
- }
169
- this.items = currItems;
99
+ while (numPrev--) {
100
+ const item = items[numPrev];
101
+ if (!currCache.has(item.k)) {
102
+ await item.v.unmount();
103
+ cache.delete(item.k);
104
+ }
170
105
  }
106
+ while (numCurr) {
107
+ await insert(currItems[numCurr - 1]);
108
+ }
109
+ this.items = currItems;
110
+ }
171
111
  }
112
+ export {
113
+ $klist,
114
+ KList
115
+ };
package/list.js CHANGED
@@ -2,114 +2,70 @@ import { $compile } from "./compile.js";
2
2
  import { Component } from "./component.js";
3
3
  import { __nextID } from "./idgen.js";
4
4
  import { $subWithID } from "./sub.js";
5
- /**
6
- * Creates a generalized and dynamically updating list component from items of
7
- * the given `src` stream.
8
- *
9
- * @remarks
10
- * Only very basic key-less diffing of list items is performed (using the
11
- * `equiv` equality predicate arg, i.e. `equiv(prev[i], curr[i])`).
12
- *
13
- * Use this list component only for cases when the child item components do not
14
- * involve complex lifecycles (e.g. preloaders, intro animations etc.). Any list
15
- * items changing position will be first unmounted, then remounted. To avoid
16
- * this full lifecycle transition, consider using {@link $klist}, which employs
17
- * a more elaborate diffing mechanism and keying to uniquely identify list items
18
- * (regardless of their position in the array).
19
- *
20
- * @example
21
- * ```ts
22
- * const items = reactive([{id: "a"}, {id: "b"}, {id: "c"}]);
23
- *
24
- * $list(
25
- * // data source (rstream subsribable)
26
- * items,
27
- * // outer list element & attribs
28
- * "ul",
29
- * { class: "list red" },
30
- * // list item component constructor
31
- * (x) => ["li", {}, x.id],
32
- * // optional equality predicate (default this.ng/equiv)
33
- * (a, b) => a.id === b.id
34
- * ).mount(document.body);
35
- *
36
- * // update list
37
- * items.next([{id: "b"}, {id: "d"}, {id: "c"}]);
38
- *
39
- * // removes component for item A
40
- * // recreates B in new position
41
- * // creates D
42
- * // keeps C
43
- * ```
44
- *
45
- *
46
- * @param src -
47
- * @param tag -
48
- * @param attribs -
49
- * @param ctor -
50
- * @param equiv -
51
- */
52
- export const $list = (src, tag, attribs, ctor, equiv) => $subWithID(src, new List(tag, attribs, ctor, equiv), __nextID("list", src));
53
- export class List extends Component {
54
- tag;
55
- attribs;
56
- ctor;
57
- equiv;
58
- prev;
59
- items;
60
- constructor(tag, attribs, ctor, equiv = (a, b) => a === b) {
61
- super();
62
- this.tag = tag;
63
- this.attribs = attribs;
64
- this.ctor = ctor;
65
- this.equiv = equiv;
5
+ const $list = (src, tag, attribs, ctor, equiv) => $subWithID(
6
+ src,
7
+ new List(tag, attribs, ctor, equiv),
8
+ __nextID("list", src)
9
+ );
10
+ class List extends Component {
11
+ constructor(tag, attribs, ctor, equiv = (a, b) => a === b) {
12
+ super();
13
+ this.tag = tag;
14
+ this.attribs = attribs;
15
+ this.ctor = ctor;
16
+ this.equiv = equiv;
17
+ }
18
+ prev;
19
+ items;
20
+ async mount(parent, index, state) {
21
+ this.prev = [];
22
+ this.items = [];
23
+ this.el = this.$el(this.tag, this.attribs, null, parent, index);
24
+ this.update(state);
25
+ return this.el;
26
+ }
27
+ async unmount() {
28
+ this.items.forEach((c) => c.unmount());
29
+ this.$remove();
30
+ this.el = void 0;
31
+ this.items = void 0;
32
+ this.prev = void 0;
33
+ }
34
+ async update(curr) {
35
+ if (!curr)
36
+ return;
37
+ const { ctor, equiv, items, prev, el: parent } = this;
38
+ const nb = curr.length;
39
+ let na = prev.length;
40
+ let n = Math.min(na, nb);
41
+ for (let i = 0; i < n; i++) {
42
+ if (!equiv(prev[i], curr[i])) {
43
+ await items[i].unmount();
44
+ const val = curr[i];
45
+ const child = $compile(ctor(val));
46
+ await child.mount(parent, i);
47
+ items[i] = child;
48
+ prev[i] = val;
49
+ }
66
50
  }
67
- async mount(parent, index, state) {
68
- this.prev = [];
69
- this.items = [];
70
- this.el = this.$el(this.tag, this.attribs, null, parent, index);
71
- this.update(state);
72
- return this.el;
73
- }
74
- async unmount() {
75
- this.items.forEach((c) => c.unmount());
76
- this.$remove();
77
- this.el = undefined;
78
- this.items = undefined;
79
- this.prev = undefined;
80
- }
81
- async update(curr) {
82
- if (!curr)
83
- return;
84
- const { ctor, equiv, items, prev, el: parent } = this;
85
- const nb = curr.length;
86
- let na = prev.length;
87
- let n = Math.min(na, nb);
88
- for (let i = 0; i < n; i++) {
89
- if (!equiv(prev[i], curr[i])) {
90
- await items[i].unmount();
91
- const val = curr[i];
92
- const child = $compile(ctor(val));
93
- await child.mount(parent, i);
94
- items[i] = child;
95
- prev[i] = val;
96
- }
97
- }
98
- if (na < nb) {
99
- for (; n < nb; n++) {
100
- const val = curr[n];
101
- const child = $compile(ctor(val));
102
- await child.mount(parent, -1);
103
- items[n] = child;
104
- prev[n] = val;
105
- }
106
- }
107
- else {
108
- while (--na >= nb) {
109
- await items[na].unmount();
110
- items.pop();
111
- prev.pop();
112
- }
113
- }
51
+ if (na < nb) {
52
+ for (; n < nb; n++) {
53
+ const val = curr[n];
54
+ const child = $compile(ctor(val));
55
+ await child.mount(parent, -1);
56
+ items[n] = child;
57
+ prev[n] = val;
58
+ }
59
+ } else {
60
+ while (--na >= nb) {
61
+ await items[na].unmount();
62
+ items.pop();
63
+ prev.pop();
64
+ }
114
65
  }
66
+ }
115
67
  }
68
+ export {
69
+ $list,
70
+ List
71
+ };
package/object.js CHANGED
@@ -2,101 +2,38 @@ import { fromObject } from "@thi.ng/rstream/object";
2
2
  import { Component } from "./component.js";
3
3
  import { __nextID } from "./idgen.js";
4
4
  import { $subWithID } from "./sub.js";
5
- /**
6
- * Creates a control component wrapper with an internal stream setup for user
7
- * defined keys in the given object. When this component is mounted, it will
8
- * call `inner` with an object of key-value streams and then {@link $compile}s
9
- * that function's return value as component body.
10
- *
11
- * @remarks
12
- * Uses
13
- * [`fromObject()`](https://docs.thi.ng/umbrella/rstream/functions/fromObject.html)
14
- * for creating the internal key-value streams. These can then be used by
15
- * `inner` to produce reactive child elements. The given `src` object is only
16
- * used to seed those streams with initial values. The component wrapper can be
17
- * updated with new values, using the `.update()` life cycle method with a new
18
- * object.
19
- *
20
- * By default the value streams will only trigger updates if their values have
21
- * changed. See
22
- * [`StreamObjOpts`](https://docs.thi.ng/umbrella/rstream/interfaces/StreamObjOpts.html)
23
- * for more details and options.
24
- *
25
- * Also see {@link $subObject}.
26
- *
27
- * @example
28
- * ```ts
29
- * const obj = $object(
30
- * // source object (for seeding)
31
- * { id: "a", name: "foo", ignore: 23 },
32
- * // create subscriptions for given keys
33
- * { keys: ["id", "name"] }
34
- * // component factory
35
- * async (obj) => <ComponentLike>["div", {}, "id: ", obj.id, " name: ", obj.name]
36
- * );
37
- *
38
- * obj.mount(document.body);
39
- *
40
- * obj.update({ id: "b", name: "bar" });
41
- * ```
42
- *
43
- * @param src -
44
- * @param opts - options for `fromObject()` stream setup
45
- * @param inner -
46
- */
47
- export const $object = (src, opts, inner) => new $Object(src, opts, inner);
48
- /**
49
- * Syntax sugar for a combination of {@link $sub} and {@link $object} to allow
50
- * reactive updates of `$object()` components themselves.
51
- *
52
- * @example
53
- * ```ts
54
- * interface Foo {
55
- * id: string;
56
- * name: string;
57
- * }
58
- *
59
- * const state = reactive<Foo>({ id: "a", name: "foo" });
60
- *
61
- * $subObject<Foo, keyof Foo>(
62
- * state,
63
- * { keys: ["id", "name"] },
64
- * // component factory
65
- * // only executed once, but `obj.id` and `obj.name` are reactive values
66
- * async (obj) => <ComponentLike>["div", {}, "id: ", obj.id, " name: ", obj.name]
67
- * ).mount(document.body);
68
- *
69
- * // update
70
- * state.next({ id: "b", name: "bar" });
71
- * ```
72
- *
73
- * @param src -
74
- * @param opts -
75
- * @param inner -
76
- */
77
- export const $subObject = (src, opts, inner) => $subWithID(src, $object(src.deref() || {}, opts, inner), __nextID("obj", src));
78
- export class $Object extends Component {
79
- ctor;
80
- obj;
81
- inner;
82
- constructor(src, opts, ctor) {
83
- super();
84
- this.ctor = ctor;
85
- this.obj = fromObject(src, opts);
86
- }
87
- async mount(parent, index = -1, state) {
88
- state !== undefined && this.obj.next(state);
89
- this.inner = this.$compile(await this.ctor(this.obj.streams));
90
- this.el = await this.inner.mount(parent, index);
91
- return this.el;
92
- }
93
- async unmount() {
94
- this.obj.done();
95
- await this.inner.unmount();
96
- this.el = undefined;
97
- this.inner = undefined;
98
- }
99
- update(state) {
100
- this.obj.next(state);
101
- }
5
+ const $object = (src, opts, inner) => new $Object(src, opts, inner);
6
+ const $subObject = (src, opts, inner) => $subWithID(
7
+ src,
8
+ $object(src.deref() || {}, opts, inner),
9
+ __nextID("obj", src)
10
+ );
11
+ class $Object extends Component {
12
+ constructor(src, opts, ctor) {
13
+ super();
14
+ this.ctor = ctor;
15
+ this.obj = fromObject(src, opts);
16
+ }
17
+ obj;
18
+ inner;
19
+ async mount(parent, index = -1, state) {
20
+ state !== void 0 && this.obj.next(state);
21
+ this.inner = this.$compile(await this.ctor(this.obj.streams));
22
+ this.el = await this.inner.mount(parent, index);
23
+ return this.el;
24
+ }
25
+ async unmount() {
26
+ this.obj.done();
27
+ await this.inner.unmount();
28
+ this.el = void 0;
29
+ this.inner = void 0;
30
+ }
31
+ update(state) {
32
+ this.obj.next(state);
33
+ }
102
34
  }
35
+ export {
36
+ $Object,
37
+ $object,
38
+ $subObject
39
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/rdom",
3
- "version": "0.13.3",
3
+ "version": "0.13.4",
4
4
  "description": "Lightweight, reactive, VDOM-less UI/DOM components with async lifecycle and @thi.ng/hiccup compatible",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -24,7 +24,9 @@
24
24
  "author": "Karsten Schmidt (https://thi.ng)",
25
25
  "license": "Apache-2.0",
26
26
  "scripts": {
27
- "build": "yarn clean && tsc --declaration",
27
+ "build": "yarn build:esbuild && yarn build:decl",
28
+ "build:decl": "tsc --declaration --emitDeclarationOnly",
29
+ "build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
28
30
  "clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
29
31
  "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
30
32
  "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
@@ -34,17 +36,18 @@
34
36
  "test": "bun test"
35
37
  },
36
38
  "dependencies": {
37
- "@thi.ng/api": "^8.9.11",
38
- "@thi.ng/checks": "^3.4.11",
39
- "@thi.ng/errors": "^2.4.5",
40
- "@thi.ng/hiccup": "^5.1.0",
41
- "@thi.ng/paths": "^5.1.52",
42
- "@thi.ng/prefixes": "^2.2.7",
43
- "@thi.ng/rstream": "^8.2.13",
44
- "@thi.ng/strings": "^3.7.2"
39
+ "@thi.ng/api": "^8.9.12",
40
+ "@thi.ng/checks": "^3.4.12",
41
+ "@thi.ng/errors": "^2.4.6",
42
+ "@thi.ng/hiccup": "^5.1.1",
43
+ "@thi.ng/paths": "^5.1.53",
44
+ "@thi.ng/prefixes": "^2.2.8",
45
+ "@thi.ng/rstream": "^8.2.14",
46
+ "@thi.ng/strings": "^3.7.3"
45
47
  },
46
48
  "devDependencies": {
47
49
  "@microsoft/api-extractor": "^7.38.3",
50
+ "esbuild": "^0.19.8",
48
51
  "rimraf": "^5.0.5",
49
52
  "tools": "^0.0.1",
50
53
  "typedoc": "^0.25.4",
@@ -140,5 +143,5 @@
140
143
  ],
141
144
  "year": 2020
142
145
  },
143
- "gitHead": "25f2ac8ff795a432a930119661b364d4d93b59a0\n"
146
+ "gitHead": "5e7bafedfc3d53bc131469a28de31dd8e5b4a3ff\n"
144
147
  }