@oscarpalmer/abydon 0.15.0 → 0.17.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.
Files changed (63) hide show
  1. package/dist/abydon.full.mjs +1443 -0
  2. package/dist/constants.d.mts +20 -0
  3. package/dist/constants.mjs +27 -0
  4. package/dist/fragment-9qTBAvtR.d.mts +82 -0
  5. package/dist/fragment.d.mts +2 -0
  6. package/dist/{fragment.js → fragment.mjs} +41 -14
  7. package/dist/fragments.d.mts +16 -0
  8. package/dist/{fragments.js → fragments.mjs} +10 -4
  9. package/dist/helpers/dom.d.mts +6 -0
  10. package/dist/helpers/{dom.js → dom.mjs} +4 -12
  11. package/dist/helpers/index.d.mts +9 -0
  12. package/dist/helpers/index.mjs +23 -0
  13. package/dist/index.d.mts +21 -0
  14. package/dist/index.mjs +24 -0
  15. package/dist/models.d.mts +2 -0
  16. package/dist/models.mjs +1 -0
  17. package/dist/node/attribute/index.d.mts +6 -0
  18. package/dist/node/attribute/index.mjs +41 -0
  19. package/dist/node/attribute/value.d.mts +6 -0
  20. package/dist/node/attribute/value.mjs +65 -0
  21. package/dist/node/event.d.mts +4 -0
  22. package/dist/node/event.mjs +25 -0
  23. package/dist/node/index.d.mts +6 -0
  24. package/dist/node/{index.js → index.mjs} +10 -8
  25. package/dist/node/value.d.mts +7 -0
  26. package/dist/node/{value.js → value.mjs} +12 -10
  27. package/dist/parse.d.mts +6 -0
  28. package/dist/{parse.js → parse.mjs} +11 -1
  29. package/package.json +42 -42
  30. package/src/constants.ts +30 -10
  31. package/src/fragment.ts +12 -24
  32. package/src/fragments.ts +3 -5
  33. package/src/helpers/dom.ts +1 -20
  34. package/src/helpers/index.ts +18 -15
  35. package/src/index.ts +14 -10
  36. package/src/models.ts +10 -1
  37. package/src/node/attribute/index.ts +22 -21
  38. package/src/node/attribute/value.ts +47 -37
  39. package/src/node/event.ts +26 -33
  40. package/src/node/index.ts +6 -18
  41. package/src/node/value.ts +15 -46
  42. package/src/parse.ts +15 -17
  43. package/dist/abydon.full.js +0 -1274
  44. package/dist/constants.js +0 -10
  45. package/dist/helpers/index.js +0 -14
  46. package/dist/index.js +0 -11
  47. package/dist/models.js +0 -0
  48. package/dist/node/attribute/index.js +0 -39
  49. package/dist/node/attribute/value.js +0 -56
  50. package/dist/node/event.js +0 -30
  51. package/types/constants.d.ts +0 -9
  52. package/types/fragment.d.ts +0 -38
  53. package/types/fragments.d.ts +0 -13
  54. package/types/helpers/dom.d.ts +0 -3
  55. package/types/helpers/index.d.ts +0 -5
  56. package/types/index.d.ts +0 -9
  57. package/types/models.d.ts +0 -32
  58. package/types/node/attribute/index.d.ts +0 -3
  59. package/types/node/attribute/value.d.ts +0 -3
  60. package/types/node/event.d.ts +0 -3
  61. package/types/node/index.d.ts +0 -2
  62. package/types/node/value.d.ts +0 -3
  63. package/types/parse.d.ts +0 -2
@@ -1,11 +1,9 @@
1
1
  import type {PlainObject} from '@oscarpalmer/atoms/models';
2
- import {getString} from '@oscarpalmer/atoms/string';
3
2
  import {isReactive} from '@oscarpalmer/mora';
4
3
  import {
5
4
  isBooleanAttribute,
6
- setAttribute as setAttr,
5
+ setAttribute as setTorettoAttribute,
7
6
  } from '@oscarpalmer/toretto/attribute';
8
- import type {HTMLOrSVGElement} from '@oscarpalmer/toretto/models';
9
7
  import {
10
8
  ATTRIBUTE_CLASS_PREFIX_LENGTH,
11
9
  EXPRESSION_ATTRIBUTE_CLASS,
@@ -14,14 +12,20 @@ import {
14
12
  } from '../../constants';
15
13
  import type {FragmentData} from '../../models';
16
14
 
15
+ function getCallback(element: HTMLElement | SVGElement, name: string) {
16
+ if (isBooleanAttribute(name) && name in element) {
17
+ return name === 'checked' ? updateChecked : updateProperty;
18
+ }
19
+
20
+ return name === 'value' ? updateValue : setTorettoAttribute;
21
+ }
22
+
17
23
  export function setAttribute(
18
24
  data: FragmentData,
19
- element: HTMLOrSVGElement,
25
+ element: HTMLElement | SVGElement,
20
26
  name: string,
21
27
  value: unknown,
22
28
  ): void {
23
- element.removeAttribute(name);
24
-
25
29
  switch (true) {
26
30
  case EXPRESSION_ATTRIBUTE_CLASS.test(name):
27
31
  setClasses(data, element, name, value);
@@ -39,7 +43,7 @@ export function setAttribute(
39
43
 
40
44
  function setClasses(
41
45
  data: FragmentData,
42
- element: HTMLOrSVGElement,
46
+ element: HTMLElement | SVGElement,
43
47
  name: string,
44
48
  value: unknown,
45
49
  ): void {
@@ -62,7 +66,7 @@ function setClasses(
62
66
 
63
67
  function setStyle(
64
68
  data: FragmentData,
65
- element: HTMLOrSVGElement,
69
+ element: HTMLElement | SVGElement,
66
70
  name: string,
67
71
  value: unknown,
68
72
  ): void {
@@ -76,10 +80,7 @@ function setStyle(
76
80
  if (value == null || value === false || (value === true && unit == null)) {
77
81
  element.style.removeProperty(property);
78
82
  } else {
79
- element.style.setProperty(
80
- property,
81
- value === true ? unit : getString(value),
82
- );
83
+ element.style.setProperty(property, value === true ? unit : String(value));
83
84
  }
84
85
  }
85
86
 
@@ -92,21 +93,11 @@ function setStyle(
92
93
 
93
94
  function setValue(
94
95
  data: FragmentData,
95
- element: HTMLOrSVGElement,
96
+ element: HTMLElement | SVGElement,
96
97
  name: string,
97
98
  value: unknown,
98
99
  ): void {
99
- let callback: (
100
- element: HTMLOrSVGElement,
101
- name: string,
102
- value: unknown,
103
- ) => void;
104
-
105
- if (isBooleanAttribute(name) && name in element) {
106
- callback = name === 'selected' ? updateSelected : updateProperty;
107
- } else {
108
- callback = setAttr;
109
- }
100
+ const callback = getCallback(element, name);
110
101
 
111
102
  if (isReactive(value)) {
112
103
  data.mora.subscribers.add(
@@ -119,25 +110,44 @@ function setValue(
119
110
  }
120
111
  }
121
112
 
122
- function updateProperty(
123
- element: HTMLOrSVGElement,
124
- name: string,
125
- value: unknown,
126
- ): void {
127
- (element as unknown as PlainObject)[name] = value === true;
113
+ function updateChecked(element: HTMLElement | SVGElement, name: string, value: unknown): void {
114
+ updateElement('change', 'checked', element, name, value, value === true);
128
115
  }
129
116
 
130
- function updateSelected(
131
- element: HTMLOrSVGElement,
117
+ function updateElement(
118
+ event: string,
119
+ property: string,
120
+ element: HTMLElement | SVGElement,
132
121
  name: string,
133
122
  value: unknown,
123
+ next: unknown,
134
124
  ): void {
135
- const select = element.closest('select') as HTMLSelectElement;
136
- const options = [...(select?.options ?? [])];
125
+ if (!(property in element) || (element as unknown as PlainObject)[property] === next) {
126
+ return;
127
+ }
128
+
129
+ setTorettoAttribute(element, name, value);
137
130
 
138
- if (select != null && options.includes(element as HTMLOptionElement)) {
139
- select.dispatchEvent(new Event('change', {bubbles: true}));
131
+ if ((element as unknown as PlainObject)[property] === next) {
132
+ return;
140
133
  }
141
134
 
142
- updateProperty(element, name, value);
135
+ (element as unknown as PlainObject)[property] = next;
136
+
137
+ element.dispatchEvent(new Event(event, {bubbles: true}));
138
+ }
139
+
140
+ function updateProperty(element: HTMLElement | SVGElement, name: string, value: unknown): void {
141
+ setTorettoAttribute(element, name, value === true);
142
+ }
143
+
144
+ function updateValue(element: HTMLElement | SVGElement, name: string, value: unknown): void {
145
+ updateElement(
146
+ element instanceof HTMLSelectElement ? 'change' : 'input',
147
+ 'value',
148
+ element,
149
+ name,
150
+ value,
151
+ String(value),
152
+ );
143
153
  }
package/src/node/event.ts CHANGED
@@ -1,50 +1,43 @@
1
- import type {HTMLOrSVGElement} from '@oscarpalmer/toretto/models';
1
+ import {on} from '@oscarpalmer/toretto/event';
2
2
  import {
3
- ABORT_CONTROLLERS,
3
+ EVENT_DEFAULTS,
4
+ EXPRESSION_EVENT_CHANGE_TYPES,
4
5
  EXPRESSION_EVENT_NAME,
5
- REASON_EVENT_REMOVED,
6
+ EXPRESSION_EVENT_OPTIONS_ACTIVE,
7
+ EXPRESSION_EVENT_OPTIONS_CAPTURE,
8
+ EXPRESSION_EVENT_OPTIONS_ONCE,
6
9
  } from '../constants';
7
10
 
8
- function getController(element: HTMLOrSVGElement): AbortController {
9
- let controller = ABORT_CONTROLLERS.get(element);
10
-
11
- if (controller == null) {
12
- controller = new AbortController();
13
-
14
- ABORT_CONTROLLERS.set(element, controller);
15
- }
16
-
17
- return controller;
18
- }
19
-
20
11
  function getOptions(options: string): AddEventListenerOptions {
21
12
  const parts = options.split(':');
22
13
 
23
14
  return {
24
- capture: parts.includes('c') || parts.includes('capture'),
25
- once: parts.includes('o') || parts.includes('once'),
26
- passive: !(parts.includes('a') || parts.includes('active')),
15
+ capture: parts.some(part => EXPRESSION_EVENT_OPTIONS_CAPTURE.test(part)),
16
+ once: parts.some(part => EXPRESSION_EVENT_OPTIONS_ONCE.test(part)),
17
+ passive: !parts.some(part => EXPRESSION_EVENT_OPTIONS_ACTIVE.test(part)),
27
18
  };
28
19
  }
29
20
 
30
- export function mapEvent(
31
- element: HTMLOrSVGElement,
32
- name: string,
33
- value: unknown,
34
- ): void {
35
- element.removeAttribute(name);
21
+ function getType(element: HTMLElement | SVGElement, type: string): string {
22
+ if (type !== 'on') {
23
+ return type;
24
+ }
36
25
 
37
- const [, type, options] = EXPRESSION_EVENT_NAME.exec(name) ?? [];
26
+ if (element instanceof HTMLInputElement) {
27
+ if (EXPRESSION_EVENT_CHANGE_TYPES.test(element.type)) {
28
+ return 'change';
29
+ }
38
30
 
39
- if (typeof value === 'function' && type != null) {
40
- element.addEventListener(type, value as EventListener, {
41
- ...getOptions(options ?? ''),
42
- signal: getController(element).signal,
43
- });
31
+ return element.type === 'submit' ? 'submit' : 'input';
44
32
  }
33
+
34
+ return EVENT_DEFAULTS[element.tagName] ?? type;
45
35
  }
46
36
 
47
- export function removeEvents(element: HTMLOrSVGElement): void {
48
- ABORT_CONTROLLERS.get(element)?.abort(REASON_EVENT_REMOVED);
49
- ABORT_CONTROLLERS.delete(element);
37
+ export function mapEvent(element: HTMLElement | SVGElement, name: string, value: unknown): void {
38
+ const [, type, options] = EXPRESSION_EVENT_NAME.exec(name) ?? [];
39
+
40
+ if (type != null && typeof value === 'function') {
41
+ on(element, getType(element, type), value as EventListener, getOptions(options ?? ''));
42
+ }
50
43
  }
package/src/node/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
- import {computed, isReactive, Reactive} from '@oscarpalmer/mora';
2
+ import {computed, isReactive, type Reactive} from '@oscarpalmer/mora';
3
3
  import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
4
- import {EXPRESSION_COMMENT_CONTENT} from '../constants';
4
+ import {EXPRESSION_ABYDON_CONTENT} from '../constants';
5
5
  import {handleFragments} from '../fragments';
6
6
  import {isFragment, isFragments} from '../helpers';
7
7
  import {createNodes} from '../helpers/dom';
@@ -10,7 +10,7 @@ import {mapAttributes} from './attribute';
10
10
  import {setReactiveValue} from './value';
11
11
 
12
12
  function mapNode(data: FragmentData, comment: Comment): void {
13
- const matches = EXPRESSION_COMMENT_CONTENT.exec(comment.textContent ?? '');
13
+ const matches = EXPRESSION_ABYDON_CONTENT.exec(comment.textContent ?? '');
14
14
  const value = matches == null ? null : data.values[+matches[1]];
15
15
 
16
16
  if (value != null) {
@@ -48,11 +48,7 @@ function mapValue(data: FragmentData, comment: Comment, value: unknown): void {
48
48
 
49
49
  case isFragments(value):
50
50
  handleFragments(value, false);
51
- setReactiveValue(
52
- data,
53
- comment,
54
- value.items as Reactive<unknown[], unknown>,
55
- );
51
+ setReactiveValue(data, comment, value.items as Reactive<unknown[], unknown>);
56
52
  break;
57
53
 
58
54
  case isReactive(value):
@@ -65,11 +61,7 @@ function mapValue(data: FragmentData, comment: Comment, value: unknown): void {
65
61
  }
66
62
  }
67
63
 
68
- function replaceComment(
69
- data: FragmentData,
70
- comment: Comment,
71
- value: unknown,
72
- ): void {
64
+ function replaceComment(data: FragmentData, comment: Comment, value: unknown): void {
73
65
  const item = data.items.find(item => item.nodes?.includes(comment));
74
66
  const nodes = createNodes(value);
75
67
 
@@ -81,11 +73,7 @@ function replaceComment(
81
73
  comment.replaceWith(...nodes);
82
74
  }
83
75
 
84
- function setComputedValue(
85
- data: FragmentData,
86
- comment: Comment,
87
- callback: GenericCallback,
88
- ): void {
76
+ function setComputedValue(data: FragmentData, comment: Comment, callback: GenericCallback): void {
89
77
  const value = computed(callback);
90
78
 
91
79
  data.mora.values.add(value);
package/src/node/value.ts CHANGED
@@ -10,8 +10,8 @@ import type {FragmentData, FragmentItem} from '../models';
10
10
  //
11
11
 
12
12
  type Identifiers = {
13
- next: unknown[];
14
- previous: (unknown | undefined)[];
13
+ next: Set<unknown>;
14
+ previous: Set<unknown>;
15
15
  };
16
16
 
17
17
  type BaseItems = {
@@ -33,8 +33,7 @@ function addToArray(
33
33
  ): void {
34
34
  let position = nodes[0];
35
35
 
36
- const before =
37
- added && !identifiers.previous.includes(items.templates[0].identifier);
36
+ const before = added && !identifiers.previous.has(items.templates[0].identifier);
38
37
 
39
38
  const next = items.next.flatMap(fragment =>
40
39
  fragment.get().flatMap(node => ({
@@ -48,7 +47,7 @@ function addToArray(
48
47
  for (let index = 0; index < length; index += 1) {
49
48
  const node = next[index];
50
49
 
51
- if (!(added && identifiers.previous.includes(node.identifier))) {
50
+ if (!(added && identifiers.previous.has(node.identifier))) {
52
51
  if (index === 0 && before) {
53
52
  position.before(node.value);
54
53
  } else {
@@ -67,9 +66,7 @@ function handleArray(
67
66
  ): Partial<FragmentItem> {
68
67
  const next = items.templates.map(
69
68
  template =>
70
- items.fragments?.find(
71
- fragment => fragment.identifier === template.identifier,
72
- ) ?? template,
69
+ items.fragments?.find(fragment => fragment.identifier === template.identifier) ?? template,
73
70
  );
74
71
 
75
72
  const comparison = compareArrays(items.fragments ?? [], items.templates);
@@ -79,9 +76,7 @@ function handleArray(
79
76
  }
80
77
 
81
78
  const toRemove =
82
- items.fragments?.filter(
83
- fragment => !identifiers.next.includes(fragment.identifier),
84
- ) ?? [];
79
+ items.fragments?.filter(fragment => !identifiers.next.has(fragment.identifier)) ?? [];
85
80
 
86
81
  const {length} = toRemove;
87
82
 
@@ -105,11 +100,7 @@ function removeFragments(fragments: Fragment[] | undefined): void {
105
100
  }
106
101
  }
107
102
 
108
- function replaceText(
109
- item: FragmentItem,
110
- comment: Comment,
111
- isNullable: boolean,
112
- ): void {
103
+ function replaceText(item: FragmentItem, comment: Comment, isNullable: boolean): void {
113
104
  let to: ChildNode[];
114
105
 
115
106
  if (isNullable) {
@@ -132,9 +123,7 @@ function setArray(
132
123
  };
133
124
  }
134
125
 
135
- let templates = value.filter(
136
- item => isFragment(item) && item.identifier != null,
137
- ) as Fragment[];
126
+ let templates = value.filter(item => isFragment(item) && item.identifier != null) as Fragment[];
138
127
 
139
128
  const next = templates.map(fragment => fragment.identifier) as unknown[];
140
129
  const previous = item.fragments?.map(fragment => fragment.identifier) ?? [];
@@ -145,11 +134,7 @@ function setArray(
145
134
 
146
135
  const noTemplates = templates.length === 0;
147
136
 
148
- if (
149
- noTemplates ||
150
- item.nodes == null ||
151
- previous.some(identifier => identifier == null)
152
- ) {
137
+ if (noTemplates || item.nodes == null || previous.some(identifier => identifier == null)) {
153
138
  return {
154
139
  fragments: noTemplates ? undefined : templates,
155
140
  nodes: setNodes(
@@ -164,8 +149,8 @@ function setArray(
164
149
 
165
150
  return handleArray(
166
151
  {
167
- next,
168
- previous,
152
+ next: new Set(next),
153
+ previous: new Set(previous),
169
154
  },
170
155
  {
171
156
  templates,
@@ -175,11 +160,7 @@ function setArray(
175
160
  );
176
161
  }
177
162
 
178
- function setNodes(
179
- item: FragmentItem,
180
- comment: Comment,
181
- next: ChildNode[],
182
- ): ChildNode[] {
163
+ function setNodes(item: FragmentItem, comment: Comment, next: ChildNode[]): ChildNode[] {
183
164
  if (item.nodes == null) {
184
165
  if (comment.parentNode != null) {
185
166
  replaceNodes([comment], next);
@@ -219,11 +200,7 @@ export function setReactiveValue(
219
200
  );
220
201
  }
221
202
 
222
- function setReactiveValueForArray(
223
- item: FragmentItem,
224
- comment: Comment,
225
- value: unknown[],
226
- ): void {
203
+ function setReactiveValueForArray(item: FragmentItem, comment: Comment, value: unknown[]): void {
227
204
  const result = setArray(item, comment, value);
228
205
 
229
206
  item.fragments = typeof result === 'boolean' ? undefined : result?.fragments;
@@ -239,11 +216,7 @@ function setReactiveValueForArray(
239
216
  }
240
217
  }
241
218
 
242
- function setReactiveValueForSingle(
243
- item: FragmentItem,
244
- comment: Comment,
245
- value: unknown,
246
- ): void {
219
+ function setReactiveValueForSingle(item: FragmentItem, comment: Comment, value: unknown): void {
247
220
  const valueIsFragment = isFragment(value);
248
221
 
249
222
  item.fragments = valueIsFragment ? [value] : undefined;
@@ -255,11 +228,7 @@ function setReactiveValueForSingle(
255
228
  }
256
229
  }
257
230
 
258
- function setText(
259
- item: FragmentItem,
260
- comment: Comment,
261
- value: unknown,
262
- ): ChildNode[] | undefined {
231
+ function setText(item: FragmentItem, comment: Comment, value: unknown): ChildNode[] | undefined {
263
232
  const isNullable = isNullableOrWhitespace(value);
264
233
 
265
234
  if (item.text != null) {
package/src/parse.ts CHANGED
@@ -1,11 +1,8 @@
1
1
  import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
2
+ import {EXPRESSION_ABYDON_ATTRIBUTE_FULL} from './constants';
2
3
  import type {FragmentData} from './models';
3
4
 
4
- function handleExpression(
5
- data: FragmentData,
6
- prefix: string,
7
- expression: unknown,
8
- ): string {
5
+ function handleExpression(data: FragmentData, prefix: string, expression: unknown): string {
9
6
  if (Array.isArray(expression)) {
10
7
  const {length} = expression;
11
8
 
@@ -18,13 +15,8 @@ function handleExpression(
18
15
  return `${prefix}${expressions}`;
19
16
  }
20
17
 
21
- if (
22
- typeof expression === 'function' ||
23
- (typeof expression === 'object' && expression != null)
24
- ) {
25
- const index = data.values.push(expression) - 1;
26
-
27
- return `${prefix}<!--abydon.${index}-->`;
18
+ if (typeof expression === 'function' || (typeof expression === 'object' && expression != null)) {
19
+ return transformExpression(prefix, data.values.push(expression) - 1);
28
20
  }
29
21
 
30
22
  return isNullableOrWhitespace(expression) ? prefix : `${prefix}${expression}`;
@@ -40,15 +32,21 @@ export function parse(data: FragmentData): string {
40
32
  data.template = '';
41
33
 
42
34
  for (let index = 0; index < length; index += 1) {
43
- data.template += handleExpression(
44
- data,
45
- data.strings[index],
46
- data.expressions[index],
47
- );
35
+ data.template += handleExpression(data, data.strings[index], data.expressions[index]);
48
36
  }
49
37
 
38
+ data.template = data.template.replaceAll(EXPRESSION_ABYDON_ATTRIBUTE_FULL, transformAttribute);
39
+
50
40
  data.expressions = [];
51
41
  data.strings = [] as never;
52
42
 
53
43
  return data.template;
54
44
  }
45
+
46
+ function transformAttribute(_: string, name: string, index: string): string {
47
+ return `_${name}="@abydon.${index}@"`;
48
+ }
49
+
50
+ function transformExpression(prefix: string, index: number): string {
51
+ return `${prefix}<!--abydon.${index}-->`;
52
+ }