@oscarpalmer/abydon 0.10.0 → 0.12.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/event.ts CHANGED
@@ -1,10 +1,12 @@
1
- import type {ProperElement} from '../models';
1
+ import type {HTMLOrSVGElement} from '../models';
2
2
 
3
- const controllers = new WeakMap<ProperElement, AbortController>();
3
+ const controllers = new WeakMap<HTMLOrSVGElement, AbortController>();
4
4
 
5
5
  const eventNamePattern = /^@([\w-]+)(?::([a-z:]+))?$/i;
6
6
 
7
- function getController(element: ProperElement): AbortController {
7
+ const reason = 'Event removed as element was removed from document by Abydon';
8
+
9
+ function getController(element: HTMLOrSVGElement): AbortController {
8
10
  let controller = controllers.get(element);
9
11
 
10
12
  if (controller == null) {
@@ -27,7 +29,7 @@ function getOptions(options: string): AddEventListenerOptions {
27
29
  }
28
30
 
29
31
  export function mapEvent(
30
- element: ProperElement,
32
+ element: HTMLOrSVGElement,
31
33
  name: string,
32
34
  value: unknown,
33
35
  ): void {
@@ -43,9 +45,7 @@ export function mapEvent(
43
45
  }
44
46
  }
45
47
 
46
- export function removeEvents(element: ProperElement): void {
47
- if (controllers.has(element)) {
48
- controllers.get(element)?.abort();
49
- controllers.delete(element);
50
- }
48
+ export function removeEvents(element: HTMLOrSVGElement): void {
49
+ controllers.get(element)?.abort(reason);
50
+ controllers.delete(element);
51
51
  }
package/src/node/index.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
2
  import {computed, isReactive} from '@oscarpalmer/sentinel';
3
- import {isFragment, isProperElement} from '../helpers';
3
+ import {isFragment, isHTMLOrSVGElement} from '../helpers';
4
4
  import {createNodes} from '../helpers/dom';
5
- import type {FragmentData, ProperNode} from '../models';
5
+ import type {FragmentData} from '../models';
6
6
  import {mapAttributes} from './attribute';
7
- import {setReactiveNode} from './value';
7
+ import {setReactiveValue} from './value';
8
8
 
9
9
  const commentExpression = /^abydon\.(\d+)$/;
10
10
 
@@ -17,7 +17,7 @@ function mapNode(data: FragmentData, comment: Comment): void {
17
17
  }
18
18
  }
19
19
 
20
- export function mapNodes(data: FragmentData, nodes: ProperNode[]): void {
20
+ export function mapNodes(data: FragmentData, nodes: ChildNode[]): void {
21
21
  const {length} = nodes;
22
22
 
23
23
  for (let index = 0; index < length; index += 1) {
@@ -29,12 +29,12 @@ export function mapNodes(data: FragmentData, nodes: ProperNode[]): void {
29
29
  continue;
30
30
  }
31
31
 
32
- if (isProperElement(node)) {
32
+ if (isHTMLOrSVGElement(node)) {
33
33
  mapAttributes(data, node);
34
34
  }
35
35
 
36
36
  if (node.hasChildNodes()) {
37
- mapNodes(data, [...node.childNodes] as ProperNode[]);
37
+ mapNodes(data, [...node.childNodes] as ChildNode[]);
38
38
  }
39
39
  }
40
40
  }
@@ -42,11 +42,11 @@ export function mapNodes(data: FragmentData, nodes: ProperNode[]): void {
42
42
  function mapValue(data: FragmentData, comment: Comment, value: unknown): void {
43
43
  switch (true) {
44
44
  case typeof value === 'function':
45
- setReactiveNode(data, comment, computed(value as GenericCallback));
45
+ setComputedValue(data, comment, value as GenericCallback);
46
46
  break;
47
47
 
48
48
  case isReactive(value):
49
- setReactiveNode(data, comment, value);
49
+ setReactiveValue(data, comment, value);
50
50
  break;
51
51
 
52
52
  default:
@@ -64,9 +64,21 @@ function replaceComment(
64
64
  const nodes = createNodes(value);
65
65
 
66
66
  if (item != null) {
67
- item.fragment = isFragment(value) ? value : undefined;
67
+ item.fragments = isFragment(value) ? [value] : undefined;
68
68
  item.nodes = nodes;
69
69
  }
70
70
 
71
71
  comment.replaceWith(...nodes);
72
72
  }
73
+
74
+ function setComputedValue(
75
+ data: FragmentData,
76
+ comment: Comment,
77
+ callback: GenericCallback,
78
+ ): void {
79
+ const value = computed(callback);
80
+
81
+ data.sentinel.values.add(value);
82
+
83
+ setReactiveValue(data, comment, value);
84
+ }
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, isChildNode, isFragment} from '../helpers';
6
7
  import {createNodes, replaceNodes} from '../helpers/dom';
7
- import type {FragmentData, ProperNode} from '../models';
8
+ import type {FragmentData, FragmentItem} from '../models';
8
9
 
9
- function setNodes(
10
- nodes: ProperNode[] | undefined,
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: ChildNode[] | undefined,
11
23
  comment: Comment,
12
24
  text: Text,
13
- value: Fragment | ProperNode,
14
- ): ProperNode[] {
15
- const next = createNodes(value);
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
+ }
16
118
 
119
+ return {
120
+ fragments: next,
121
+ nodes: next.flatMap(fragment => fragment.get()),
122
+ };
123
+ }
124
+
125
+ function setNodes(
126
+ fragments: Fragment[] | undefined,
127
+ nodes: ChildNode[] | undefined,
128
+ comment: Comment,
129
+ text: Text,
130
+ next: ChildNode[],
131
+ ): ChildNode[] {
17
132
  if (nodes == null) {
18
133
  if (comment.parentNode != null) {
19
134
  replaceNodes([comment], next);
@@ -24,10 +139,12 @@ function setNodes(
24
139
  replaceNodes(nodes, next);
25
140
  }
26
141
 
142
+ removeFragments(fragments);
143
+
27
144
  return next;
28
145
  }
29
146
 
30
- export function setReactiveNode(
147
+ export function setReactiveValue(
31
148
  data: FragmentData,
32
149
  comment: Comment,
33
150
  reactive: Reactive,
@@ -35,53 +152,70 @@ export function setReactiveNode(
35
152
  const item = data.items.find(item => item.nodes.includes(comment));
36
153
  const text = new Text();
37
154
 
38
- let nodes: ProperNode[] | undefined;
39
-
40
- effect(() => {
41
- const value = reactive.get();
42
-
43
- const valueIsFragment = isFragment(value);
44
-
45
- if (valueIsFragment || isProperNode(value)) {
46
- nodes = setNodes(nodes, comment, text, value);
47
- } else {
48
- nodes = setText(nodes, comment, text, value) ? [text] : undefined;
49
- }
50
-
51
- if (item != null) {
52
- item.fragment = valueIsFragment ? value : undefined;
53
- item.nodes = [...(nodes ?? [comment])];
54
- }
55
- });
155
+ let fragments: Fragment[] | undefined;
156
+ let nodes: ChildNode[] | undefined;
157
+
158
+ data.sentinel.effects.add(
159
+ effect(() => {
160
+ const value = reactive.get();
161
+
162
+ if (Array.isArray(value)) {
163
+ const result = setArray(fragments, nodes, comment, text, value);
164
+
165
+ fragments = typeof result === 'boolean' ? undefined : result?.fragments;
166
+
167
+ nodes =
168
+ typeof result === 'boolean'
169
+ ? result
170
+ ? [text]
171
+ : undefined
172
+ : result?.nodes;
173
+ } else {
174
+ const valueIsFragment = isFragment(value);
175
+
176
+ fragments = valueIsFragment ? [value] : undefined;
177
+
178
+ if (valueIsFragment || isChildNode(value)) {
179
+ nodes = setNodes(fragments, nodes, comment, text, createNodes(value));
180
+ } else {
181
+ nodes = setText(fragments, nodes, comment, text, value);
182
+ }
183
+ }
184
+
185
+ if (item != null) {
186
+ item.fragments = fragments;
187
+ item.nodes = [...(nodes ?? [comment])];
188
+ }
189
+ }),
190
+ );
56
191
  }
57
192
 
58
193
  function setText(
59
- nodes: ProperNode[] | undefined,
194
+ fragments: Fragment[] | undefined,
195
+ nodes: ChildNode[] | undefined,
60
196
  comment: Comment,
61
197
  text: Text,
62
198
  value: unknown,
63
- ): boolean {
199
+ ): ChildNode[] | undefined {
64
200
  const isNullable = isNullableOrWhitespace(value);
65
201
 
66
202
  text.textContent = isNullable ? '' : getString(value);
67
203
 
204
+ let result = false;
205
+
68
206
  if (nodes != null) {
69
207
  replaceNodes(nodes, [isNullable ? comment : text]);
70
208
 
71
- return !isNullable;
72
- }
73
-
74
- if (isNullable && comment.parentNode == null) {
209
+ result = !isNullable;
210
+ } else if (isNullable && comment.parentNode == null) {
75
211
  text.replaceWith(comment);
76
-
77
- return false;
78
- }
79
-
80
- if (!isNullable && text.parentNode == null) {
212
+ } else if (!isNullable && text.parentNode == null) {
81
213
  comment.replaceWith(text);
82
214
 
83
- return true;
215
+ result = true;
84
216
  }
85
217
 
86
- return false;
218
+ removeFragments(fragments);
219
+
220
+ return result ? [text] : undefined;
87
221
  }
@@ -1,7 +1,8 @@
1
- import type { ProperNode } from './models';
1
+ import type { Key } from '@oscarpalmer/atoms/models';
2
2
  export declare class Fragment {
3
3
  private readonly $fragment;
4
4
  private readonly data;
5
+ get identifier(): Key | undefined;
5
6
  constructor(strings: TemplateStringsArray, expressions: unknown[]);
6
7
  /**
7
8
  * Appends the fragment to the given element
@@ -10,7 +11,8 @@ export declare class Fragment {
10
11
  /**
11
12
  * Gets a list of the fragment's nodes
12
13
  */
13
- get(): ProperNode[];
14
+ get(): ChildNode[];
15
+ identify(identifier: Key): Fragment;
14
16
  /**
15
17
  * Removes the fragment from the DOM
16
18
  */
@@ -1,4 +1,3 @@
1
- import type { ProperNode } from '../models';
2
- export declare function createNodes(value: unknown): ProperNode[];
3
- export declare function removeNodes(nodes: ProperNode[]): void;
4
- export declare function replaceNodes(from: ProperNode[], to: ProperNode[]): void;
1
+ export declare function createNodes(value: unknown): ChildNode[];
2
+ export declare function removeNodes(nodes: ChildNode[]): void;
3
+ export declare function replaceNodes(from: ChildNode[], to: ChildNode[]): void;
@@ -1,5 +1,6 @@
1
1
  import type { Fragment } from '../fragment';
2
- import type { ProperElement, ProperNode } from '../models';
2
+ import type { HTMLOrSVGElement } from '../models';
3
+ export declare function compareArrays(first: unknown[], second: unknown[]): 'added' | 'dissimilar' | 'removed';
4
+ export declare function isChildNode(value: unknown): value is ChildNode;
3
5
  export declare function isFragment(value: unknown): value is Fragment;
4
- export declare function isProperElement(value: unknown): value is ProperElement;
5
- export declare function isProperNode(value: unknown): value is ProperNode;
6
+ export declare function isHTMLOrSVGElement(value: unknown): value is HTMLOrSVGElement;
package/types/index.d.cts CHANGED
@@ -1,9 +1,11 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
- export type ProperNode = CharacterData | Element;
3
+ import { Key } from '@oscarpalmer/atoms/models';
4
+
4
5
  export declare class Fragment {
5
6
  private readonly $fragment;
6
7
  private readonly data;
8
+ get identifier(): Key | undefined;
7
9
  constructor(strings: TemplateStringsArray, expressions: unknown[]);
8
10
  /**
9
11
  * Appends the fragment to the given element
@@ -12,7 +14,8 @@ export declare class Fragment {
12
14
  /**
13
15
  * Gets a list of the fragment's nodes
14
16
  */
15
- get(): ProperNode[];
17
+ get(): ChildNode[];
18
+ identify(identifier: Key): Fragment;
16
19
  /**
17
20
  * Removes the fragment from the DOM
18
21
  */
package/types/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ import '@oscarpalmer/sentinel';
1
2
  export type { Fragment } from './fragment';
2
3
  export { html } from './html';
package/types/models.d.ts CHANGED
@@ -1,13 +1,21 @@
1
+ import type { Key } from '@oscarpalmer/atoms/models';
2
+ import type { Effect, Reactive } from '@oscarpalmer/sentinel';
1
3
  import type { Fragment } from './fragment';
2
4
  export type FragmentData = {
3
5
  expressions: unknown[];
6
+ identifier?: Key;
4
7
  items: FragmentItem[];
8
+ sentinel: FragmentDataSentinel;
5
9
  strings: TemplateStringsArray;
6
10
  values: unknown[];
7
11
  };
12
+ type FragmentDataSentinel = {
13
+ effects: Set<Effect>;
14
+ values: Set<Reactive>;
15
+ };
8
16
  export type FragmentItem = {
9
- fragment?: Fragment;
10
- nodes: ProperNode[];
17
+ fragments?: Fragment[];
18
+ nodes: ChildNode[];
11
19
  };
12
- export type ProperElement = HTMLElement | SVGElement;
13
- export type ProperNode = CharacterData | Element;
20
+ export type HTMLOrSVGElement = HTMLElement | SVGElement;
21
+ export {};
@@ -1,2 +1,2 @@
1
- import type { FragmentData, ProperElement } from '../../models';
2
- export declare function mapAttributes(data: FragmentData, element: ProperElement): void;
1
+ import type { FragmentData, HTMLOrSVGElement } from '../../models';
2
+ export declare function mapAttributes(data: FragmentData, element: HTMLOrSVGElement): void;
@@ -1,2 +1,2 @@
1
- import type { ProperElement } from '../../models';
2
- export declare function setAttribute(element: ProperElement, name: string, value: unknown): void;
1
+ import type { FragmentData, HTMLOrSVGElement } from '../../models';
2
+ export declare function setAttribute(data: FragmentData, element: HTMLOrSVGElement, name: string, value: unknown): void;
@@ -1,3 +1,3 @@
1
- import type { ProperElement } from '../models';
2
- export declare function mapEvent(element: ProperElement, name: string, value: unknown): void;
3
- export declare function removeEvents(element: ProperElement): void;
1
+ import type { HTMLOrSVGElement } from '../models';
2
+ export declare function mapEvent(element: HTMLOrSVGElement, name: string, value: unknown): void;
3
+ export declare function removeEvents(element: HTMLOrSVGElement): void;
@@ -1,2 +1,2 @@
1
- import type { FragmentData, ProperNode } from '../models';
2
- export declare function mapNodes(data: FragmentData, nodes: ProperNode[]): void;
1
+ import type { FragmentData } from '../models';
2
+ export declare function mapNodes(data: FragmentData, nodes: ChildNode[]): void;
@@ -1,3 +1,3 @@
1
1
  import { type Reactive } from '@oscarpalmer/sentinel';
2
2
  import type { FragmentData } from '../models';
3
- export declare function setReactiveNode(data: FragmentData, comment: Comment, reactive: Reactive): void;
3
+ export declare function setReactiveValue(data: FragmentData, comment: Comment, reactive: Reactive): void;