@thi.ng/rdom 1.7.88 → 2.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.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![Mastodon Follow](https://img.shields.io/mastodon/follow/109331703950160316?domain=https%3A%2F%2Fmastodon.thi.ng&style=social)](https://mastodon.thi.ng/@toxi)
8
8
 
9
9
  > [!NOTE]
10
- > This is one of 214 standalone projects, maintained as part
10
+ > This is one of 215 standalone projects, maintained as part
11
11
  > of the [@thi.ng/umbrella](https://codeberg.org/thi.ng/umbrella/) ecosystem
12
12
  > and anti-framework.
13
13
  >
@@ -294,6 +294,7 @@ $compile(
294
294
  - [@thi.ng/hiccup](https://codeberg.org/thi.ng/umbrella/src/branch/develop/packages/hiccup) - HTML/SVG/XML serialization of nested data structures, iterables & closures
295
295
  - [@thi.ng/hiccup-html](https://codeberg.org/thi.ng/umbrella/src/branch/develop/packages/hiccup-html) - 100+ type-checked HTML5 element functions for [@thi.ng/hiccup](https://codeberg.org/thi.ng/umbrella/src/branch/develop/packages/hiccup) related infrastructure
296
296
  - [@thi.ng/hiccup-svg](https://codeberg.org/thi.ng/umbrella/src/branch/develop/packages/hiccup-svg) - SVG element functions for [@thi.ng/hiccup](https://codeberg.org/thi.ng/umbrella/src/branch/develop/packages/hiccup) & related tooling
297
+ - [@thi.ng/rstream](https://codeberg.org/thi.ng/umbrella/src/branch/develop/packages/rstream) - Reactive streams & subscription primitives for constructing dataflow graphs / pipelines
297
298
  - [@thi.ng/transducers](https://codeberg.org/thi.ng/umbrella/src/branch/develop/packages/transducers) - Collection of ~170 lightweight, composable transducers, reducers, generators, iterators for functional data transformations
298
299
 
299
300
  ## Installation
@@ -316,7 +317,7 @@ Browser ESM import:
316
317
 
317
318
  [JSDelivr documentation](https://www.jsdelivr.com/)
318
319
 
319
- Package sizes (brotli'd, pre-treeshake): ESM: 4.37 KB
320
+ Package sizes (brotli'd, pre-treeshake): ESM: 4.53 KB
320
321
 
321
322
  ## Dependencies
322
323
 
@@ -333,7 +334,7 @@ Note: @thi.ng/api is in _most_ cases a type-only import (not used at runtime)
333
334
 
334
335
  ## Usage examples
335
336
 
336
- 52 projects in this repo's
337
+ 53 projects in this repo's
337
338
  [/examples](https://codeberg.org/thi.ng/umbrella/src/branch/develop/examples)
338
339
  directory are using this package:
339
340
 
@@ -365,6 +366,7 @@ directory are using this package:
365
366
  | <img src="https://codeberg.org/thi.ng/umbrella/media/branch/develop/assets/examples/procedural-text.jpg" width="240"/> | Procedural stochastic text generation via custom DSL, parse grammar & AST transformation | [Demo](https://demo.thi.ng/umbrella/procedural-text/) | [Source](https://codeberg.org/thi.ng/umbrella/src/branch/develop/examples/procedural-text) |
366
367
  | <img src="https://codeberg.org/thi.ng/umbrella/media/branch/develop/assets/examples/ramp-scroll-anim.png" width="240"/> | Scroll-based, reactive, multi-param CSS animation basics | [Demo](https://demo.thi.ng/umbrella/ramp-scroll-anim/) | [Source](https://codeberg.org/thi.ng/umbrella/src/branch/develop/examples/ramp-scroll-anim) |
367
368
  | | Basic & barebones usage of async iterables in thi.ng/rdom | [Demo](https://demo.thi.ng/umbrella/rdom-async/) | [Source](https://codeberg.org/thi.ng/umbrella/src/branch/develop/examples/rdom-async) |
369
+ | <img src="https://codeberg.org/thi.ng/umbrella/media/branch/develop/assets/examples/rdom-bare-lists.avif" width="240"/> | Basic usage of rdom bare list components in multiple scenarios | [Demo](https://demo.thi.ng/umbrella/rdom-bare-lists/) | [Source](https://codeberg.org/thi.ng/umbrella/src/branch/develop/examples/rdom-bare-lists) |
368
370
  | | Demonstates various rdom usage patterns | [Demo](https://demo.thi.ng/umbrella/rdom-basics/) | [Source](https://codeberg.org/thi.ng/umbrella/src/branch/develop/examples/rdom-basics) |
369
371
  | <img src="https://codeberg.org/thi.ng/umbrella/media/branch/develop/assets/examples/rdom-delayed-update.jpg" width="240"/> | Dynamically loaded images w/ preloader state | [Demo](https://demo.thi.ng/umbrella/rdom-delayed-update/) | [Source](https://codeberg.org/thi.ng/umbrella/src/branch/develop/examples/rdom-delayed-update) |
370
372
  | <img src="https://codeberg.org/thi.ng/umbrella/media/branch/develop/assets/examples/rdom-dnd.png" width="240"/> | rdom drag & drop example | [Demo](https://demo.thi.ng/umbrella/rdom-dnd/) | [Source](https://codeberg.org/thi.ng/umbrella/src/branch/develop/examples/rdom-dnd) |
@@ -396,11 +398,8 @@ directory are using this package:
396
398
 
397
399
  [Generated API docs](https://docs.thi.ng/umbrella/rdom/)
398
400
 
399
- TODO
400
-
401
- Currently, documentation only exists in the form of small examples and various
402
- doc strings (incomplete). I'm working to alleviate this situation ASAP... In
403
- that respect, PRs are welcome as well!
401
+ Currently, documentation only exists in the form of API docs and 50+ small
402
+ example projects (see above, all commented).
404
403
 
405
404
  ### Basic usage
406
405
 
@@ -462,13 +461,15 @@ const items = reactive([
462
461
  $klist(
463
462
  // reactive data source (any rstream subscribable)
464
463
  items,
465
- // outer list element & attribs
466
- "ul",
467
- { class: "list red" },
468
- // list item component constructor
469
- (x) => ["li", {}, x.id, ` (${x.val})`],
470
- // key function (includes)
471
- (x) => `${x.id}-${x.val}`
464
+ {
465
+ // outer list element & attribs
466
+ el: "ul",
467
+ attribs: { class: "list red" },
468
+ // list item component constructor
469
+ item: (x) => ["li", {}, x.id, ` (${x.val})`],
470
+ // key function (includes)
471
+ key: (x) => `${x.id}-${x.val}`
472
+ }
472
473
  ).mount(document.body);
473
474
 
474
475
  // update list:
package/api.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Fn0 } from "@thi.ng/api";
1
+ import type { Fn, Fn0 } from "@thi.ng/api";
2
2
  import type { ISubscribable } from "@thi.ng/rstream";
3
3
  /**
4
4
  * Main rdom component interface/contract. Also see
@@ -34,7 +34,7 @@ export interface IComponent<T = any> {
34
34
  * @param idx -
35
35
  * @param args -
36
36
  */
37
- mount(parent: ParentNode, idx?: NumOrElement, ...args: any[]): Promise<Element>;
37
+ mount(parent: ParentNode, idx?: NumOrNode, ...args: any[]): Promise<Element>;
38
38
  /**
39
39
  * Async component lifecycle method to remove the component from the
40
40
  * target DOM and release any other internal resources (e.g.
@@ -65,7 +65,7 @@ export interface IMountWith<T, M> extends IComponent<T> {
65
65
  * @param index -
66
66
  * @param state -
67
67
  */
68
- mount(parent: ParentNode, index: NumOrElement, state: M): Promise<Element>;
68
+ mount(parent: ParentNode, index: NumOrNode, state: M): Promise<Element>;
69
69
  /**
70
70
  * Same like {@link IComponent.update}, but new `state` value arg is
71
71
  * mandatory.
@@ -91,4 +91,30 @@ export interface CompiledComponent extends IComponent {
91
91
  export type ComponentLike = IComponent | [string, ...(any | null)[]];
92
92
  export type Callback = Fn0<void>;
93
93
  export type NumOrElement = number | Element;
94
+ export type NumOrNode = number | Node;
95
+ /**
96
+ * Common options for {@link $list} and {@link $klist} components.
97
+ */
98
+ export interface ListBaseOpts<T> {
99
+ /**
100
+ * Tag name or existing DOM element to use as list container.
101
+ *
102
+ * If given, the list items will be attached this this element. If given as
103
+ * string, an element of that type will be created first. In either case,
104
+ * the element can be further customized via {@link ListBaseOpts.attribs}.
105
+ *
106
+ * If NOT given, the list items will be directly attached to the list
107
+ * component's parent element. We call this a "bare" list.
108
+ */
109
+ el?: Element | string;
110
+ /**
111
+ * Element attributes for list container element (only used if
112
+ * {@link ListBaseOpts.el} is given).
113
+ */
114
+ attribs?: Record<string, any>;
115
+ /**
116
+ * List item component factory function.
117
+ */
118
+ item: Fn<T, any>;
119
+ }
94
120
  //# sourceMappingURL=api.d.ts.map
package/async.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Maybe, Path } from "@thi.ng/api";
2
- import type { IComponent, IMountWithState, NumOrElement } from "./api.js";
2
+ import type { IComponent, IMountWithState, NumOrNode } from "./api.js";
3
3
  /**
4
4
  * Takes an ES `AsyncIterable` and creates a simple component wrapper for its
5
5
  * asynchronously produced values.
@@ -33,7 +33,7 @@ export declare class $Async<T = any> {
33
33
  protected inner: IMountWithState<Maybe<T>>;
34
34
  el?: Element;
35
35
  constructor(src: AsyncIterable<T>, inner: IMountWithState<Maybe<T>>);
36
- mount(parent: ParentNode, index?: NumOrElement): Promise<Element>;
36
+ mount(parent: ParentNode, index?: NumOrNode): Promise<Element>;
37
37
  unmount(): Promise<void>;
38
38
  update(x: T): void;
39
39
  }
package/component.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Maybe, MaybeDeref } from "@thi.ng/api";
2
- import type { IComponent, NumOrElement } from "./api.js";
2
+ import type { IComponent, NumOrNode } from "./api.js";
3
3
  /**
4
4
  * Abstract base class / {@link IComponent} implementation. Provides
5
5
  * additional convenience methods for DOM element creation &
@@ -7,7 +7,7 @@ import type { IComponent, NumOrElement } from "./api.js";
7
7
  */
8
8
  export declare abstract class Component<T = any> implements IComponent<T> {
9
9
  el?: Element;
10
- abstract mount(parent: ParentNode, index?: NumOrElement, ...args: any[]): Promise<Element>;
10
+ abstract mount(parent: ParentNode, index?: NumOrNode, ...args: any[]): Promise<Element>;
11
11
  unmount(): Promise<void>;
12
12
  update(state?: T): void;
13
13
  /**
@@ -20,7 +20,7 @@ export declare abstract class Component<T = any> implements IComponent<T> {
20
20
  * @param parent
21
21
  * @param idx
22
22
  */
23
- $el(tag: string, attribs?: any, body?: any, parent?: Maybe<ParentNode>, idx?: NumOrElement): Element;
23
+ $el(tag: string, attribs?: any, body?: any, parent?: Maybe<ParentNode>, idx?: NumOrNode): Element;
24
24
  /**
25
25
  * Syntax sugar for {@link $comment}, creates a new comment DOM node using
26
26
  * this component's {@link IComponent.el} as default `parent`.
@@ -29,7 +29,7 @@ export declare abstract class Component<T = any> implements IComponent<T> {
29
29
  * @param parent
30
30
  * @param idx
31
31
  */
32
- $comment(body: string | string[], parent?: Maybe<ParentNode>, idx?: NumOrElement): Comment;
32
+ $comment(body: string | string[], parent?: Maybe<ParentNode>, idx?: NumOrNode): Comment;
33
33
  /**
34
34
  * Syntax sugar for {@link $clear}, using this component's
35
35
  * {@link IComponent.el} as default element to clear.
@@ -50,7 +50,7 @@ export declare abstract class Component<T = any> implements IComponent<T> {
50
50
  * @param root
51
51
  * @param index
52
52
  */
53
- $tree(tree: any, root?: ParentNode, index?: NumOrElement): Promise<any>;
53
+ $tree(tree: any, root?: ParentNode, index?: NumOrNode): Promise<any>;
54
54
  /**
55
55
  * Syntax sugar for {@link $text}, using this component's
56
56
  * {@link IComponent.el} as default element to edit.
@@ -106,6 +106,6 @@ export declare abstract class Component<T = any> implements IComponent<T> {
106
106
  * @param el
107
107
  * @param idx
108
108
  */
109
- $moveTo(newParent: ParentNode, el?: Element, idx?: NumOrElement): void;
109
+ $moveTo(newParent: ParentNode, el?: Element, idx?: NumOrNode): void;
110
110
  }
111
111
  //# sourceMappingURL=component.d.ts.map
package/dom.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type MaybeDeref } from "@thi.ng/api/deref";
2
- import type { NumOrElement } from "./api.js";
2
+ import type { NumOrNode } from "./api.js";
3
3
  /**
4
4
  * hdom-style DOM tree creation from hiccup format. Returns DOM element of
5
5
  * `tree` root. See {@link $el} for further details.
@@ -27,7 +27,7 @@ import type { NumOrElement } from "./api.js";
27
27
  * @param parent -
28
28
  * @param idx -
29
29
  */
30
- export declare const $tree: (tree: any, parent: ParentNode, idx?: NumOrElement) => Promise<any>;
30
+ export declare const $tree: (tree: any, parent: ParentNode, idx?: NumOrNode) => Promise<any>;
31
31
  /**
32
32
  * Create a single DOM element and optionally attaches it to `parent`.
33
33
  *
@@ -50,7 +50,7 @@ export declare const $tree: (tree: any, parent: ParentNode, idx?: NumOrElement)
50
50
  * @param parent -
51
51
  * @param idx -
52
52
  */
53
- export declare const $el: (tag: string, attribs: any, body?: any, parent?: ParentNode, idx?: NumOrElement) => Element;
53
+ export declare const $el: (tag: string, attribs: any, body?: any, parent?: ParentNode, idx?: NumOrNode) => Element;
54
54
  /**
55
55
  * Similar to {@link $el}, but creates a new comment DOM node using provided
56
56
  * body. If `parent` is given, the comment will be attached or inserted as child
@@ -64,7 +64,7 @@ export declare const $el: (tag: string, attribs: any, body?: any, parent?: Paren
64
64
  * @param parent
65
65
  * @param idx
66
66
  */
67
- export declare const $comment: (body: string | string[], parent?: ParentNode, idx?: NumOrElement) => Comment;
67
+ export declare const $comment: (body: string | string[], parent?: ParentNode, idx?: NumOrNode) => Comment;
68
68
  /**
69
69
  * Appends or inserts `child` as child element of `parent`. The default `idx` of
70
70
  * -1 means the child will be appended, else uses `parent.insertBefore()` to
@@ -74,7 +74,7 @@ export declare const $comment: (body: string | string[], parent?: ParentNode, id
74
74
  * @param child
75
75
  * @param idx
76
76
  */
77
- export declare const $addChild: (parent: ParentNode, child: Node | Comment, idx?: NumOrElement) => void;
77
+ export declare const $addChild: (parent: ParentNode, child: Node | Comment, idx?: NumOrNode) => void;
78
78
  /**
79
79
  * Removes given element or comment from the DOM.
80
80
  *
@@ -89,7 +89,7 @@ export declare const $remove: (el: Element | Comment) => void;
89
89
  * @param el
90
90
  * @param idx
91
91
  */
92
- export declare const $moveTo: (newParent: ParentNode, el: Element | Comment, idx?: NumOrElement) => void;
92
+ export declare const $moveTo: (newParent: ParentNode, el: Element | Comment, idx?: NumOrNode) => void;
93
93
  /**
94
94
  * Removes all content from given element.
95
95
  *
@@ -0,0 +1,10 @@
1
+ import type { ListBaseOpts, NumOrNode } from "../api.js";
2
+ /** @internal */
3
+ export declare const __initList: (parent: ParentNode, index: NumOrNode, { el, attribs }: ListBaseOpts<any>) => {
4
+ el: Element;
5
+ anchor?: undefined;
6
+ } | {
7
+ el: Element;
8
+ anchor: Comment;
9
+ };
10
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { isString } from "@thi.ng/checks/is-string";
2
+ import { $addChild, $attribs, $comment, $el } from "../dom.js";
3
+ const __initList = (parent, index, { el, attribs }) => {
4
+ if (isString(el)) {
5
+ return { el: $el(el, attribs, null, parent, index) };
6
+ } else if (el) {
7
+ if (attribs) $attribs(el, attribs);
8
+ $addChild(parent, el, index);
9
+ return { el };
10
+ }
11
+ return { el: parent, anchor: $comment("", parent, index) };
12
+ };
13
+ export {
14
+ __initList
15
+ };
package/klist.d.ts CHANGED
@@ -1,7 +1,10 @@
1
- import type { Fn, Fn2, NumOrString } from "@thi.ng/api";
1
+ import type { Fn2, NumOrString } from "@thi.ng/api";
2
2
  import type { ISubscribable } from "@thi.ng/rstream";
3
- import type { IComponent, IMountWithState, NumOrElement } from "./api.js";
3
+ import type { IComponent, IMountWithState, ListBaseOpts, NumOrNode } from "./api.js";
4
4
  import { Component } from "./component.js";
5
+ export interface KListOpts<T> extends ListBaseOpts<T> {
6
+ key?: Fn2<T, number, NumOrString>;
7
+ }
5
8
  export interface KListItem {
6
9
  k: NumOrString;
7
10
  v: IComponent;
@@ -14,11 +17,18 @@ export interface KListItem {
14
17
  * the list.
15
18
  *
16
19
  * @remarks
17
- * The given `keyFn` is used to obtain a *unique* key value for each list item
18
- * obtained from the reactive arrays obtained from `src`. Like a hash, the key
19
- * value MUST represent an item's *current* value such that if the value
20
+ * The given `key` function is used to obtain a *unique* key value for each list
21
+ * item obtained from the reactive arrays obtained from `src`. Like a hash, the
22
+ * key value MUST represent an item's *current* value such that if the value
20
23
  * changes, so does the key.
21
24
  *
25
+ * See {@link ListBaseOpts} for more details about wrapped vs. bare lists, i.e.
26
+ * using a list wrapper element for items or attaching list items directly to
27
+ * this component's parent. Also see this example project to illustrate the
28
+ * structure/possibilities of bare lists:
29
+ *
30
+ * - https://demo.thi.ng/umbrella/rdom-bare-lists/
31
+ *
22
32
  * @example
23
33
  * ```ts
24
34
  * import { $klist } from "@thi.ng/rdom";
@@ -29,13 +39,15 @@ export interface KListItem {
29
39
  * $klist(
30
40
  * // data source (any rstream subscribable)
31
41
  * items,
32
- * // outer list element & attribs
33
- * "ul",
34
- * { class: "list red" },
35
- * // list item component constructor
36
- * (x) => ["li", {}, x.id, ` (${x.val})`],
37
- * // key function
38
- * (x) => `${x.id}-${x.val}`
42
+ * {
43
+ * // outer list element & attribs
44
+ * el: "ul",
45
+ * attribs: { class: "list red" },
46
+ * // list item component constructor
47
+ * item: (x) => ["li", {}, x.id, ` (${x.val})`],
48
+ * // key function
49
+ * key: (x) => `${x.id}-${x.val}`
50
+ * }
39
51
  * ).mount(document.body);
40
52
  *
41
53
  * // update list:
@@ -56,21 +68,16 @@ export interface KListItem {
56
68
  * ```
57
69
  *
58
70
  * @param src -
59
- * @param tag -
60
- * @param attribs -
61
- * @param childCtor -
62
- * @param keyFn -
71
+ * @param opts -
63
72
  */
64
- export declare const $klist: <T>(src: ISubscribable<T[]>, tag: string, attribs: any, childCtor: Fn<T, any>, keyFn?: Fn2<T, number, NumOrString>) => IComponent<T[]>;
73
+ export declare const $klist: <T>(src: ISubscribable<T[]>, opts: KListOpts<T>) => IComponent<T[]>;
65
74
  export declare class KList<T> extends Component<T[]> implements IMountWithState<T[]> {
66
- protected tag: string;
67
- protected attribs: any;
68
- protected ctor: Fn<T, any>;
69
- protected keyFn: Fn2<T, number, NumOrString>;
70
- items?: KListItem[];
71
- cache?: Map<NumOrString, KListItem>;
72
- constructor(tag: string, attribs: any, ctor: Fn<T, any>, keyFn?: Fn2<T, number, NumOrString>);
73
- mount(parent: ParentNode, index: NumOrElement, state: T[]): Promise<Element>;
75
+ protected opts: KListOpts<T>;
76
+ protected items?: KListItem[];
77
+ protected cache?: Map<NumOrString, KListItem>;
78
+ protected anchor?: Comment;
79
+ constructor(opts: KListOpts<T>);
80
+ mount(parent: ParentNode, index: NumOrNode, state: T[]): Promise<Element>;
74
81
  unmount(): Promise<void>;
75
82
  update(curr: T[]): Promise<void>;
76
83
  }
package/klist.js CHANGED
@@ -1,43 +1,44 @@
1
+ import { isString } from "@thi.ng/checks/is-string";
1
2
  import { $compile } from "./compile.js";
2
3
  import { Component } from "./component.js";
3
4
  import { __nextID } from "./idgen.js";
5
+ import { __initList } from "./internal/list.js";
4
6
  import { $subWithID } from "./sub.js";
5
- const $klist = (src, tag, attribs, childCtor, keyFn) => $subWithID(
6
- src,
7
- new KList(tag, attribs, childCtor, keyFn),
8
- __nextID("klist", src)
9
- );
7
+ const $klist = (src, opts) => $subWithID(src, new KList(opts), __nextID("klist", src));
10
8
  class KList extends Component {
11
- constructor(tag, attribs, ctor, keyFn = (_, i) => i) {
9
+ constructor(opts) {
12
10
  super();
13
- this.tag = tag;
14
- this.attribs = attribs;
15
- this.ctor = ctor;
16
- this.keyFn = keyFn;
11
+ this.opts = opts;
17
12
  }
18
- tag;
19
- attribs;
20
- ctor;
21
- keyFn;
13
+ opts;
22
14
  items = [];
23
15
  cache;
16
+ anchor;
24
17
  async mount(parent, index, state) {
25
18
  this.items = [];
26
19
  this.cache = /* @__PURE__ */ new Map();
27
- this.el = this.$el(this.tag, this.attribs, null, parent, index);
20
+ const { el, anchor } = __initList(parent, index, this.opts);
21
+ this.el = el;
22
+ this.anchor = anchor;
28
23
  this.update(state);
29
24
  return this.el;
30
25
  }
31
26
  async unmount() {
32
27
  this.items.forEach((c) => c.v.unmount());
33
- this.$remove();
28
+ if (isString(this.opts.el)) this.$remove();
29
+ this.anchor?.remove();
34
30
  this.el = void 0;
35
31
  this.items = void 0;
36
32
  this.cache = void 0;
37
33
  }
38
34
  async update(curr) {
39
35
  if (!curr) return;
40
- const { keyFn, items, ctor, cache, el: parent } = this;
36
+ const {
37
+ opts: { item: ctor, key: keyFn = (_, i2) => i2 },
38
+ items,
39
+ cache,
40
+ el: parent
41
+ } = this;
41
42
  const currItems = [];
42
43
  const currCache = /* @__PURE__ */ new Map();
43
44
  const offsets = /* @__PURE__ */ new Map();
@@ -62,7 +63,7 @@ class KList extends Component {
62
63
  }
63
64
  const willMove = /* @__PURE__ */ new Set();
64
65
  const didMove = /* @__PURE__ */ new Set();
65
- let next;
66
+ let next = this.anchor;
66
67
  const insert = async (item) => {
67
68
  if (cache.has(item.k)) {
68
69
  this.$moveTo(parent, item.v.el, next);
package/lazy.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Fn0, Maybe } from "@thi.ng/api";
2
- import type { ComponentLike, IComponent, NumOrElement } from "./api.js";
2
+ import type { ComponentLike, IComponent, NumOrNode } from "./api.js";
3
3
  import { Component } from "./component.js";
4
4
  /**
5
5
  * `IntersectionObserver`-based helper component for lazy
@@ -25,7 +25,7 @@ export declare class $Lazy extends Component {
25
25
  protected observer: Maybe<IntersectionObserver>;
26
26
  protected inner: Maybe<IComponent>;
27
27
  constructor(tag: string, attribs: any, ctor: Fn0<Promise<ComponentLike>>, opts?: IntersectionObserverInit | undefined);
28
- mount(parent: ParentNode, index?: NumOrElement): Promise<Element>;
28
+ mount(parent: ParentNode, index?: NumOrNode): Promise<Element>;
29
29
  unmount(): Promise<void>;
30
30
  }
31
31
  //# sourceMappingURL=lazy.d.ts.map
package/list.d.ts CHANGED
@@ -1,7 +1,14 @@
1
- import type { Fn, Predicate2 } from "@thi.ng/api";
1
+ import type { Predicate2 } from "@thi.ng/api";
2
2
  import type { ISubscribable } from "@thi.ng/rstream";
3
- import type { IComponent, IMountWithState, NumOrElement } from "./api.js";
3
+ import type { IComponent, IMountWithState, ListBaseOpts, NumOrNode } from "./api.js";
4
4
  import { Component } from "./component.js";
5
+ export interface ListOpts<T> extends ListBaseOpts<T> {
6
+ /**
7
+ * Equality predicate function to determine if a list item has changed. Uses
8
+ * `===` by default.
9
+ */
10
+ equiv?: Predicate2<T>;
11
+ }
5
12
  /**
6
13
  * Creates a generalized and dynamically updating list component from items of
7
14
  * the given `src` stream.
@@ -17,6 +24,13 @@ import { Component } from "./component.js";
17
24
  * a more elaborate diffing mechanism and keying to uniquely identify list items
18
25
  * (regardless of their position in the array).
19
26
  *
27
+ * See {@link ListBaseOpts} for more details about wrapped vs. bare lists, i.e.
28
+ * using a list wrapper element for items or attaching list items directly to
29
+ * this component's parent. Also see this example project to illustrate the
30
+ * structure/possibilities of bare lists:
31
+ *
32
+ * - https://demo.thi.ng/umbrella/rdom-bare-lists/
33
+ *
20
34
  * @example
21
35
  * ```ts
22
36
  * import { $list } from "@thi.ng/rdom";
@@ -27,13 +41,15 @@ import { Component } from "./component.js";
27
41
  * $list(
28
42
  * // data source (rstream subsribable)
29
43
  * items,
30
- * // outer list element & attribs
31
- * "ul",
32
- * { class: "list red" },
33
- * // list item component constructor
34
- * (x) => ["li", {}, x.id],
35
- * // optional equality predicate (default this.ng/equiv)
36
- * (a, b) => a.id === b.id
44
+ * {
45
+ * // outer list element & attribs
46
+ * el: "ul",
47
+ * attribs: { class: "list red" },
48
+ * // list item component constructor
49
+ * item: (x) => ["li", {}, x.id],
50
+ * // optional equality predicate (default this.ng/equiv)
51
+ * equiv: (a, b) => a.id === b.id
52
+ * }
37
53
  * ).mount(document.body);
38
54
  *
39
55
  * // update list
@@ -46,21 +62,18 @@ import { Component } from "./component.js";
46
62
  * ```
47
63
  *
48
64
  * @param src -
49
- * @param tag -
50
- * @param attribs -
51
- * @param ctor -
52
- * @param equiv -
65
+ * @param opts -
53
66
  */
54
- export declare const $list: <T>(src: ISubscribable<T[]>, tag: string, attribs: any, ctor: Fn<T, any>, equiv?: Predicate2<T>) => IComponent<T[]>;
67
+ export declare const $list: <T>(src: ISubscribable<T[]>, opts: ListOpts<T>) => IComponent<T[]>;
55
68
  export declare class List<T> extends Component implements IMountWithState<T[]> {
56
- protected tag: string;
57
- protected attribs: any;
58
- protected ctor: Fn<T, IComponent>;
59
- protected equiv: Predicate2<T>;
60
- prev?: T[];
61
- items?: IComponent[];
62
- constructor(tag: string, attribs: any, ctor: Fn<T, IComponent>, equiv?: Predicate2<T>);
63
- mount(parent: ParentNode, index: NumOrElement, state: T[]): Promise<Element>;
69
+ protected opts: ListOpts<T>;
70
+ protected prev?: T[];
71
+ protected items?: IComponent[];
72
+ protected anchor?: Comment;
73
+ protected offset: number;
74
+ protected numChildren: number;
75
+ constructor(opts: ListOpts<T>);
76
+ mount(parent: ParentNode, index: NumOrNode, state: T[]): Promise<Element>;
64
77
  unmount(): Promise<void>;
65
78
  update(curr: T[]): Promise<void>;
66
79
  }
package/list.js CHANGED
@@ -1,52 +1,62 @@
1
+ import { isString } from "@thi.ng/checks/is-string";
1
2
  import { $compile } from "./compile.js";
2
3
  import { Component } from "./component.js";
3
4
  import { __nextID } from "./idgen.js";
5
+ import { __initList } from "./internal/list.js";
4
6
  import { $subWithID } from "./sub.js";
5
- const $list = (src, tag, attribs, ctor, equiv) => $subWithID(
6
- src,
7
- new List(tag, attribs, ctor, equiv),
8
- __nextID("list", src)
9
- );
7
+ const $list = (src, opts) => $subWithID(src, new List(opts), __nextID("list", src));
10
8
  class List extends Component {
11
- constructor(tag, attribs, ctor, equiv = (a, b) => a === b) {
9
+ constructor(opts) {
12
10
  super();
13
- this.tag = tag;
14
- this.attribs = attribs;
15
- this.ctor = ctor;
16
- this.equiv = equiv;
11
+ this.opts = opts;
17
12
  }
18
- tag;
19
- attribs;
20
- ctor;
21
- equiv;
13
+ opts;
22
14
  prev;
23
15
  items;
16
+ anchor;
17
+ offset = 0;
18
+ numChildren = -1;
24
19
  async mount(parent, index, state) {
25
20
  this.prev = [];
26
21
  this.items = [];
27
- this.el = this.$el(this.tag, this.attribs, null, parent, index);
22
+ const { el, anchor } = __initList(parent, index, this.opts);
23
+ this.el = el;
24
+ this.anchor = anchor;
28
25
  this.update(state);
29
26
  return this.el;
30
27
  }
31
28
  async unmount() {
32
29
  this.items.forEach((c) => c.unmount());
33
- this.$remove();
30
+ if (isString(this.opts.el)) this.$remove();
31
+ this.anchor?.remove();
34
32
  this.el = void 0;
35
33
  this.items = void 0;
36
34
  this.prev = void 0;
37
35
  }
38
36
  async update(curr) {
39
37
  if (!curr) return;
40
- const { ctor, equiv, items, prev, el: parent } = this;
38
+ const {
39
+ opts: { item: ctor, equiv = (a, b) => a === b },
40
+ items,
41
+ prev,
42
+ el: parent,
43
+ anchor
44
+ } = this;
41
45
  const nb = curr.length;
42
46
  let na = prev.length;
43
47
  let n = Math.min(na, nb);
48
+ let offset = this.offset;
49
+ const children = parent.childNodes;
50
+ if (anchor && children.length !== this.numChildren) {
51
+ this.numChildren = children.length;
52
+ this.offset = offset = [...children].indexOf(anchor);
53
+ }
44
54
  for (let i = 0; i < n; i++) {
45
55
  if (!equiv(prev[i], curr[i])) {
46
56
  await items[i].unmount();
47
57
  const val = curr[i];
48
58
  const child = $compile(ctor(val));
49
- await child.mount(parent, i);
59
+ await child.mount(parent, i + offset);
50
60
  items[i] = child;
51
61
  prev[i] = val;
52
62
  }
@@ -55,7 +65,7 @@ class List extends Component {
55
65
  for (; n < nb; n++) {
56
66
  const val = curr[n];
57
67
  const child = $compile(ctor(val));
58
- await child.mount(parent, -1);
68
+ await child.mount(parent, n + offset);
59
69
  items[n] = child;
60
70
  prev[n] = val;
61
71
  }
package/object.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Fn, Keys } from "@thi.ng/api";
2
2
  import type { ISubscribable, StreamObj, StreamObjOpts } from "@thi.ng/rstream";
3
- import type { ComponentLike, IComponent, IMountWithState, NumOrElement } from "./api.js";
3
+ import type { ComponentLike, IComponent, IMountWithState, NumOrNode } from "./api.js";
4
4
  import { Component } from "./component.js";
5
5
  /**
6
6
  * Creates a control component wrapper with an internal stream setup for user
@@ -85,7 +85,7 @@ export declare class $Object<T extends object, K extends Keys<T>> extends Compon
85
85
  protected obj: StreamObj<T, K>;
86
86
  protected inner?: IComponent;
87
87
  constructor(src: T, opts: Partial<StreamObjOpts<T, K>>, ctor: Fn<StreamObj<T, K>["streams"], Promise<ComponentLike>>);
88
- mount(parent: ParentNode, index?: NumOrElement, state?: T): Promise<Element>;
88
+ mount(parent: ParentNode, index?: NumOrNode, state?: T): Promise<Element>;
89
89
  unmount(): Promise<void>;
90
90
  update(state: T): void;
91
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/rdom",
3
- "version": "1.7.88",
3
+ "version": "2.0.1",
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",
@@ -41,14 +41,14 @@
41
41
  "tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@thi.ng/api": "^8.12.19",
45
- "@thi.ng/checks": "^3.8.9",
46
- "@thi.ng/errors": "^2.6.8",
47
- "@thi.ng/hiccup": "^5.4.4",
48
- "@thi.ng/paths": "^5.2.38",
49
- "@thi.ng/prefixes": "^2.3.69",
50
- "@thi.ng/rstream": "^9.5.2",
51
- "@thi.ng/strings": "^3.12.1"
44
+ "@thi.ng/api": "^8.12.21",
45
+ "@thi.ng/checks": "^3.9.1",
46
+ "@thi.ng/errors": "^2.6.10",
47
+ "@thi.ng/hiccup": "^5.4.6",
48
+ "@thi.ng/paths": "^5.2.40",
49
+ "@thi.ng/prefixes": "^2.3.71",
50
+ "@thi.ng/rstream": "^9.5.5",
51
+ "@thi.ng/strings": "^3.12.3"
52
52
  },
53
53
  "devDependencies": {
54
54
  "esbuild": "^0.28.0",
@@ -62,16 +62,19 @@
62
62
  "component",
63
63
  "css",
64
64
  "datastructure",
65
+ "diff",
65
66
  "dom",
66
67
  "hiccup",
67
68
  "html",
68
69
  "lifecycle",
70
+ "list",
69
71
  "reactive",
70
72
  "rstream",
71
73
  "svg",
72
74
  "tree",
73
75
  "typescript",
74
- "ui"
76
+ "ui",
77
+ "wrapper"
75
78
  ],
76
79
  "publishConfig": {
77
80
  "access": "public"
@@ -81,7 +84,8 @@
81
84
  },
82
85
  "files": [
83
86
  "./*.js",
84
- "./*.d.ts"
87
+ "./*.d.ts",
88
+ "internal"
85
89
  ],
86
90
  "exports": {
87
91
  ".": {
@@ -145,9 +149,10 @@
145
149
  "hiccup",
146
150
  "hiccup-html",
147
151
  "hiccup-svg",
152
+ "rstream",
148
153
  "transducers"
149
154
  ],
150
155
  "year": 2020
151
156
  },
152
- "gitHead": "2085e9600e121134429c85f6d0ead46e3ce920ca"
157
+ "gitHead": "dcaa4e2beb427e434a68cba1259a9bf9983a569a"
153
158
  }
package/promise.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Fn } from "@thi.ng/api";
2
- import type { ComponentLike, IComponent, NumOrElement } from "./api.js";
2
+ import type { ComponentLike, IComponent, NumOrNode } from "./api.js";
3
3
  import { Component } from "./component.js";
4
4
  /**
5
5
  * Simple component wrapper for {@link ComponentLike} promises. When this
@@ -26,7 +26,7 @@ export declare class $Promise extends Component {
26
26
  protected error: Fn<Error, any>;
27
27
  inner?: IComponent;
28
28
  constructor(promise: Promise<ComponentLike>, error?: Fn<Error, any>);
29
- mount(parent: ParentNode, index: NumOrElement): Promise<Element>;
29
+ mount(parent: ParentNode, index: NumOrNode): Promise<Element>;
30
30
  unmount(): Promise<void>;
31
31
  }
32
32
  //# sourceMappingURL=promise.d.ts.map
package/replace.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ISubscribable } from "@thi.ng/rstream";
2
- import type { IComponent, IMountWithState, NumOrElement } from "./api.js";
2
+ import type { IComponent, IMountWithState, NumOrNode } from "./api.js";
3
3
  import { Component } from "./component.js";
4
4
  /**
5
5
  * Similar to {@link $refresh}, but more basic/simple. Takes a reactive value
@@ -37,8 +37,8 @@ export declare const $replace: <T>(src: ISubscribable<T>) => IComponent<T>;
37
37
  export declare class Replace<T> extends Component implements IMountWithState<T> {
38
38
  protected parent?: ParentNode;
39
39
  protected inner?: IComponent<T>;
40
- protected index?: NumOrElement;
41
- mount(parent: ParentNode, index: NumOrElement, val: T): Promise<Element>;
40
+ protected index?: NumOrNode;
41
+ mount(parent: ParentNode, index: NumOrNode, val: T): Promise<Element>;
42
42
  unmount(): Promise<void>;
43
43
  update(val: T): Promise<void>;
44
44
  }
package/sub.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Fn2, Maybe, Path } from "@thi.ng/api";
2
2
  import type { ISubscribable } from "@thi.ng/rstream";
3
3
  import { Subscription } from "@thi.ng/rstream/subscription";
4
- import type { IComponent, IMountWithState, NumOrElement } from "./api.js";
4
+ import type { IComponent, IMountWithState, NumOrNode } from "./api.js";
5
5
  /**
6
6
  * Takes an
7
7
  * [`ISubscribable`](https://docs.thi.ng/umbrella/rstream/interfaces/ISubscribable.html)
@@ -45,7 +45,7 @@ export declare class $Sub<T = any> extends Subscription<T, T> {
45
45
  protected inner: IMountWithState<Maybe<T>>;
46
46
  el?: Element;
47
47
  constructor(inner: IMountWithState<Maybe<T>>, id?: string);
48
- mount(parent: ParentNode, index?: NumOrElement): Promise<Element>;
48
+ mount(parent: ParentNode, index?: NumOrNode): Promise<Element>;
49
49
  unmount(): Promise<void>;
50
50
  update(x: T): void;
51
51
  next(x: T): void;
package/switch.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Fn, NumOrString } from "@thi.ng/api";
2
2
  import type { ISubscribable } from "@thi.ng/rstream";
3
- import type { IComponent, IMountWithState, NumOrElement } from "./api.js";
3
+ import type { IComponent, IMountWithState, NumOrNode } from "./api.js";
4
4
  import { Component } from "./component.js";
5
5
  /**
6
6
  * Reactive component wrapper to dynamically switch/replace itself with one of
@@ -94,9 +94,9 @@ export declare class Switch<T> extends Component implements IMountWithState<T> {
94
94
  protected val?: T;
95
95
  protected parent?: ParentNode;
96
96
  protected inner?: IComponent<T>;
97
- protected index?: NumOrElement;
97
+ protected index?: NumOrNode;
98
98
  constructor(keyFn: Fn<T, NumOrString>, ctors: Record<NumOrString, Fn<T, Promise<any>>>, error?: Fn<Error, Promise<any>>, loader?: Fn<T, Promise<any>>);
99
- mount(parent: ParentNode, index: NumOrElement, val: T): Promise<Element>;
99
+ mount(parent: ParentNode, index: NumOrNode, val: T): Promise<Element>;
100
100
  unmount(): Promise<void>;
101
101
  update(val: T): Promise<void>;
102
102
  }