@oscarpalmer/abydon 0.9.0 → 0.11.0

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/src/node/value.ts CHANGED
@@ -1,19 +1,134 @@
1
1
  import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
2
+ import type {Key} from '@oscarpalmer/atoms/models';
2
3
  import {getString} from '@oscarpalmer/atoms/string';
3
4
  import {type Reactive, effect} from '@oscarpalmer/sentinel';
4
5
  import type {Fragment} from '../fragment';
5
- import {isFragment, isProperNode} from '../helpers';
6
+ import {compareArrays, isFragment, isProperNode} from '../helpers';
6
7
  import {createNodes, replaceNodes} from '../helpers/dom';
7
- import type {FragmentData, ProperNode} from '../models';
8
+ import type {FragmentData, FragmentItem, ProperNode} from '../models';
9
+
10
+ function removeFragments(fragments: Fragment[] | undefined): void {
11
+ if (fragments != null) {
12
+ const {length} = fragments;
13
+
14
+ for (let index = 0; index < length; index += 1) {
15
+ fragments[index].remove();
16
+ }
17
+ }
18
+ }
19
+
20
+ function setArray(
21
+ fragments: Fragment[] | undefined,
22
+ nodes: ProperNode[] | undefined,
23
+ comment: Comment,
24
+ text: Text,
25
+ value: unknown[],
26
+ ): Partial<FragmentItem> | boolean {
27
+ if (value.length === 0) {
28
+ return {
29
+ nodes: setText(fragments, nodes, comment, text, value),
30
+ };
31
+ }
32
+
33
+ let templates = value.filter(
34
+ item => isFragment(item) && item.identifier != null,
35
+ ) as Fragment[];
36
+
37
+ const identifiers = templates.map(fragment => fragment.identifier) as Key[];
38
+ const oldIdentifiers = fragments?.map(fragment => fragment.identifier) ?? [];
39
+
40
+ if (new Set(identifiers).size !== templates.length) {
41
+ templates = [];
42
+ }
43
+
44
+ const noTemplates = templates.length === 0;
45
+
46
+ if (
47
+ noTemplates ||
48
+ nodes == null ||
49
+ oldIdentifiers.some(identifier => identifier == null)
50
+ ) {
51
+ return {
52
+ fragments: noTemplates ? undefined : templates,
53
+ nodes: setNodes(
54
+ fragments,
55
+ nodes,
56
+ comment,
57
+ text,
58
+ noTemplates
59
+ ? value.flatMap(item => createNodes(item))
60
+ : templates.flatMap(template => template.get()),
61
+ ),
62
+ };
63
+ }
64
+
65
+ const next = templates.map(
66
+ template =>
67
+ fragments?.find(
68
+ fragment => fragment.identifier === template.identifier,
69
+ ) ?? template,
70
+ );
71
+
72
+ const comparison = compareArrays(fragments ?? [], templates);
73
+
74
+ if (comparison !== 'removed') {
75
+ let position = nodes[0];
76
+
77
+ const before =
78
+ comparison === 'added' &&
79
+ !oldIdentifiers.includes(templates[0].identifier);
80
+
81
+ const items = next.flatMap(fragment =>
82
+ fragment.get().flatMap(node => ({
83
+ identifier: fragment.identifier,
84
+ value: node,
85
+ })),
86
+ );
87
+
88
+ const {length} = items;
89
+
90
+ for (let index = 0; index < length; index += 1) {
91
+ const item = items[index];
92
+
93
+ if (
94
+ comparison === 'dissimilar' ||
95
+ !oldIdentifiers.includes(item.identifier)
96
+ ) {
97
+ if (index === 0 && before) {
98
+ position.before(item.value);
99
+ } else {
100
+ position.after(item.value);
101
+ }
102
+ }
103
+
104
+ position = item.value;
105
+ }
106
+ }
107
+
108
+ const toRemove =
109
+ fragments?.filter(
110
+ fragment => !identifiers.includes(fragment.identifier as Key),
111
+ ) ?? [];
112
+
113
+ const {length} = toRemove;
114
+
115
+ for (let index = 0; index < length; index += 1) {
116
+ toRemove[index].remove();
117
+ }
118
+
119
+ return {
120
+ fragments: next,
121
+ nodes: next.flatMap(fragment => fragment.get()),
122
+ };
123
+ }
8
124
 
9
125
  function setNodes(
126
+ fragments: Fragment[] | undefined,
10
127
  nodes: ProperNode[] | undefined,
11
128
  comment: Comment,
12
129
  text: Text,
13
- value: Fragment | ProperNode,
130
+ next: ProperNode[],
14
131
  ): ProperNode[] {
15
- const next = createNodes(value);
16
-
17
132
  if (nodes == null) {
18
133
  if (comment.parentNode != null) {
19
134
  replaceNodes([comment], next);
@@ -24,6 +139,8 @@ function setNodes(
24
139
  replaceNodes(nodes, next);
25
140
  }
26
141
 
142
+ removeFragments(fragments);
143
+
27
144
  return next;
28
145
  }
29
146
 
@@ -35,53 +152,68 @@ export function setReactiveNode(
35
152
  const item = data.items.find(item => item.nodes.includes(comment));
36
153
  const text = new Text();
37
154
 
155
+ let fragments: Fragment[] | undefined;
38
156
  let nodes: ProperNode[] | undefined;
39
157
 
40
158
  effect(() => {
41
159
  const value = reactive.get();
42
160
 
43
- const valueIsFragment = isFragment(value);
161
+ if (Array.isArray(value)) {
162
+ const result = setArray(fragments, nodes, comment, text, value);
163
+
164
+ fragments = typeof result === 'boolean' ? undefined : result?.fragments;
44
165
 
45
- if (valueIsFragment || isProperNode(value)) {
46
- nodes = setNodes(nodes, comment, text, value);
166
+ nodes =
167
+ typeof result === 'boolean'
168
+ ? result
169
+ ? [text]
170
+ : undefined
171
+ : result?.nodes;
47
172
  } else {
48
- nodes = setText(nodes, comment, text, value) ? [text] : undefined;
173
+ const valueIsFragment = isFragment(value);
174
+
175
+ fragments = valueIsFragment ? [value] : undefined;
176
+
177
+ if (valueIsFragment || isProperNode(value)) {
178
+ nodes = setNodes(fragments, nodes, comment, text, createNodes(value));
179
+ } else {
180
+ nodes = setText(fragments, nodes, comment, text, value);
181
+ }
49
182
  }
50
183
 
51
184
  if (item != null) {
52
- item.fragment = valueIsFragment ? value : undefined;
185
+ item.fragments = fragments;
53
186
  item.nodes = [...(nodes ?? [comment])];
54
187
  }
55
188
  });
56
189
  }
57
190
 
58
191
  function setText(
192
+ fragments: Fragment[] | undefined,
59
193
  nodes: ProperNode[] | undefined,
60
194
  comment: Comment,
61
195
  text: Text,
62
196
  value: unknown,
63
- ): boolean {
197
+ ): ProperNode[] | undefined {
64
198
  const isNullable = isNullableOrWhitespace(value);
65
199
 
66
200
  text.textContent = isNullable ? '' : getString(value);
67
201
 
202
+ let result = false;
203
+
68
204
  if (nodes != null) {
69
205
  replaceNodes(nodes, [isNullable ? comment : text]);
70
206
 
71
- return !isNullable;
72
- }
73
-
74
- if (isNullable && comment.parentNode == null) {
207
+ result = !isNullable;
208
+ } else if (isNullable && comment.parentNode == null) {
75
209
  text.replaceWith(comment);
76
-
77
- return false;
78
- }
79
-
80
- if (!isNullable && text.parentNode == null) {
210
+ } else if (!isNullable && text.parentNode == null) {
81
211
  comment.replaceWith(text);
82
212
 
83
- return true;
213
+ result = true;
84
214
  }
85
215
 
86
- return false;
216
+ removeFragments(fragments);
217
+
218
+ return result ? [text] : undefined;
87
219
  }
@@ -1,6 +1,9 @@
1
+ import type { Key } from '@oscarpalmer/atoms/models';
2
+ import type { ProperNode } from './models';
1
3
  export declare class Fragment {
2
4
  private readonly $fragment;
3
5
  private readonly data;
6
+ get identifier(): Key | undefined;
4
7
  constructor(strings: TemplateStringsArray, expressions: unknown[]);
5
8
  /**
6
9
  * Appends the fragment to the given element
@@ -9,7 +12,8 @@ export declare class Fragment {
9
12
  /**
10
13
  * Gets a list of the fragment's nodes
11
14
  */
12
- get(): Node[];
15
+ get(): ProperNode[];
16
+ identify(identifier: Key): Fragment;
13
17
  /**
14
18
  * Removes the fragment from the DOM
15
19
  */
@@ -1,3 +1,4 @@
1
1
  import type { ProperNode } from '../models';
2
2
  export declare function createNodes(value: unknown): ProperNode[];
3
+ export declare function removeNodes(nodes: ProperNode[]): void;
3
4
  export declare function replaceNodes(from: ProperNode[], to: ProperNode[]): void;
@@ -1,5 +1,6 @@
1
1
  import type { Fragment } from '../fragment';
2
2
  import type { ProperElement, ProperNode } from '../models';
3
+ export declare function compareArrays(first: unknown[], second: unknown[]): 'added' | 'dissimilar' | 'removed';
3
4
  export declare function isFragment(value: unknown): value is Fragment;
4
5
  export declare function isProperElement(value: unknown): value is ProperElement;
5
6
  export declare function isProperNode(value: unknown): value is ProperNode;
package/types/index.d.cts CHANGED
@@ -1,8 +1,12 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
+ import { Key } from '@oscarpalmer/atoms/models';
4
+
5
+ export type ProperNode = CharacterData | Element;
3
6
  export declare class Fragment {
4
7
  private readonly $fragment;
5
8
  private readonly data;
9
+ get identifier(): Key | undefined;
6
10
  constructor(strings: TemplateStringsArray, expressions: unknown[]);
7
11
  /**
8
12
  * Appends the fragment to the given element
@@ -11,7 +15,8 @@ export declare class Fragment {
11
15
  /**
12
16
  * Gets a list of the fragment's nodes
13
17
  */
14
- get(): Node[];
18
+ get(): ProperNode[];
19
+ identify(identifier: Key): Fragment;
15
20
  /**
16
21
  * Removes the fragment from the DOM
17
22
  */
package/types/models.d.ts CHANGED
@@ -1,12 +1,14 @@
1
+ import type { Key } from '@oscarpalmer/atoms/models';
1
2
  import type { Fragment } from './fragment';
2
3
  export type FragmentData = {
3
4
  expressions: unknown[];
5
+ identifier?: Key;
4
6
  items: FragmentItem[];
5
7
  strings: TemplateStringsArray;
6
8
  values: unknown[];
7
9
  };
8
10
  export type FragmentItem = {
9
- fragment?: Fragment;
11
+ fragments?: Fragment[];
10
12
  nodes: ProperNode[];
11
13
  };
12
14
  export type ProperElement = HTMLElement | SVGElement;
@@ -1,2 +1,3 @@
1
1
  import type { ProperElement } from '../models';
2
2
  export declare function mapEvent(element: ProperElement, name: string, value: unknown): void;
3
+ export declare function removeEvents(element: ProperElement): void;