@oscarpalmer/abydon 0.14.0 → 0.16.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/package.json CHANGED
@@ -4,23 +4,21 @@
4
4
  "url": "https://oscarpalmer.se"
5
5
  },
6
6
  "dependencies": {
7
- "@oscarpalmer/atoms": "^0.114",
8
- "@oscarpalmer/mora": "^0.23",
9
- "@oscarpalmer/toretto": "^0.24"
7
+ "@oscarpalmer/atoms": "^0.118",
8
+ "@oscarpalmer/mora": "^0.25",
9
+ "@oscarpalmer/toretto": "^0.32"
10
10
  },
11
11
  "devDependencies": {
12
- "@biomejs/biome": "^2.3",
13
12
  "@oscarpalmer/oui": "^0.18",
14
- "@rollup/plugin-node-resolve": "^16",
15
- "@rollup/plugin-typescript": "^12.3",
16
- "@types/node": "^24.9",
13
+ "@types/node": "^25",
17
14
  "@vitest/coverage-istanbul": "^4",
18
- "glob": "^11",
19
- "jsdom": "^27.1",
20
- "rollup": "^4.52",
15
+ "jsdom": "^27.3",
16
+ "oxfmt": "^0.19",
17
+ "oxlint": "^1.34",
18
+ "rolldown": "1.0.0-beta.56",
21
19
  "tslib": "^2.8",
22
20
  "typescript": "^5.9",
23
- "vite": "npm:rolldown-vite@latest",
21
+ "vite": "8.0.0-beta.2",
24
22
  "vitest": "^4"
25
23
  },
26
24
  "exports": {
@@ -49,14 +47,14 @@
49
47
  "url": "git+https://github.com/oscarpalmer/abydon.git"
50
48
  },
51
49
  "scripts": {
52
- "build": "npm run clean && npx vite build && npm run rollup:build && npx tsc",
50
+ "build": "npm run clean && npx vite build && npm run rolldown:build && npx tsc",
53
51
  "clean": "rm -rf ./dist && rm -rf ./types && rm -f ./tsconfig.tsbuildinfo",
54
- "rollup:build": "npx rollup -c",
55
- "rollup:watch": "npx rollup -c --watch",
52
+ "rolldown:build": "npx rolldown -c",
53
+ "rolldown:watch": "npx rolldown -c ./rolldown.config.js --watch",
56
54
  "test": "npx vitest --coverage",
57
55
  "watch": "npx vite build --watch"
58
56
  },
59
57
  "type": "module",
60
58
  "types": "types/index.d.ts",
61
- "version": "0.14.0"
59
+ "version": "0.16.0"
62
60
  }
package/src/constants.ts CHANGED
@@ -1,20 +1,40 @@
1
- export const ABORT_CONTROLLERS: WeakMap<HTMLOrSVGElement, AbortController> =
2
- new WeakMap();
3
-
4
1
  export const ATTRIBUTE_CLASS_PREFIX_LENGTH = 6;
5
2
 
3
+ export const EVENT_DEFAULTS: Record<string, string> = {
4
+ A: 'click',
5
+ BUTTON: 'click',
6
+ DETAILS: 'toggle',
7
+ FORM: 'submit',
8
+ SELECT: 'change',
9
+ TEXTAREA: 'input',
10
+ };
11
+
12
+ export const EXPRESSION_ABYDON_ATTRIBUTE_FULL = /(@?\w+)="<!--abydon.(\d+)-->"/g;
13
+
14
+ export const EXPRESSION_ABYDON_ATTRIBUTE_PREFIX = /^_/;
15
+
16
+ export const EXPRESSION_ABYDON_CONTENT = /^@?abydon\.(\d+)@?$/;
17
+
6
18
  export const EXPRESSION_ATTRIBUTE_CLASS = /^class\./;
7
19
 
8
- export const EXPRESSION_ATTRIBUTE_STYLE_FULL =
9
- /^style\.([\w-]+)(?:\.([\w-]+))?$/;
20
+ export const EXPRESSION_ATTRIBUTE_STYLE_FULL = /^style\.([\w-]+)(?:\.([\w-]+))?$/;
10
21
 
11
22
  export const EXPRESSION_ATTRIBUTE_STYLE_PREFIX = /^style\./;
12
23
 
13
- export const EXPRESSION_COMMENT_FULL = /^<!--abydon\.(\d+)-->$/;
14
-
15
- export const EXPRESSION_COMMENT_CONTENT = /^abydon\.(\d+)$/;
24
+ export const EXPRESSION_EVENT_CHANGE_TYPES = /^(checkbox|radio)$/;
16
25
 
17
26
  export const EXPRESSION_EVENT_NAME = /^@([\w-]+)(?::([a-z:]+))?$/i;
18
27
 
19
- export const REASON_EVENT_REMOVED =
20
- 'Event removed as element was removed from document by Abydon';
28
+ export const EXPRESSION_EVENT_OPTIONS_ACTIVE = /^a(ctive)$/i;
29
+
30
+ export const EXPRESSION_EVENT_OPTIONS_CAPTURE = /^c(apture)$/i;
31
+
32
+ export const EXPRESSION_EVENT_OPTIONS_ONCE = /^o(nce)$/i;
33
+
34
+ export const EXPRESSION_EVENT_PREFIX = /^@/;
35
+
36
+ export const EXPRESSION_PERIOD = /\./;
37
+
38
+ export const NAME_FRAGMENT = '$fragment';
39
+
40
+ export const NAME_FRAGMENTS = '$fragments';
package/src/fragment.ts CHANGED
@@ -1,23 +1,33 @@
1
+ import {isPlainObject} from '@oscarpalmer/atoms/is';
1
2
  import {html} from '@oscarpalmer/toretto/html';
3
+ import {NAME_FRAGMENT} from './constants';
4
+ import {handleFragments} from './fragments';
5
+ import {isFragments} from './helpers';
2
6
  import {removeNodes} from './helpers/dom';
3
7
  import type {FragmentConfiguration, FragmentData} from './models';
4
8
  import {mapNodes} from './node';
5
9
  import {parse} from './parse';
6
- import { isPlainObject } from '@oscarpalmer/atoms';
7
10
 
8
11
  export class Fragment {
9
12
  readonly #data: FragmentData;
10
13
 
11
14
  readonly #configuration: Required<FragmentConfiguration> = {
12
15
  identifier: undefined,
13
- ignoreCache: false,
14
- }
16
+ cache: true,
17
+ };
15
18
 
19
+ /**
20
+ * Fragment identifier
21
+ */
16
22
  get identifier(): unknown {
17
23
  return this.#configuration.identifier;
18
24
  }
19
25
 
20
26
  constructor(strings: TemplateStringsArray, expressions: unknown[]) {
27
+ Object.defineProperty(this, NAME_FRAGMENT, {
28
+ value: true,
29
+ });
30
+
21
31
  this.#data = {
22
32
  expressions,
23
33
  strings,
@@ -28,35 +38,38 @@ export class Fragment {
28
38
  },
29
39
  values: [],
30
40
  };
31
-
32
- Object.defineProperty(this, '$fragment', {
33
- value: true,
34
- });
35
41
  }
36
42
 
37
43
  /**
38
- * Appends the fragment to the given element
44
+ * Append the fragment to the given element
45
+ * @param element Element to append to
39
46
  */
40
47
  appendTo(element: Element): void {
41
48
  element.append(...this.get());
42
49
  }
43
50
 
51
+ /**
52
+ * Configure the fragment
53
+ * @param configuration Configuration options
54
+ * @returns Fragment
55
+ */
44
56
  configure(configuration: FragmentConfiguration): Fragment {
45
57
  const actual = isPlainObject(configuration) ? configuration : {};
46
58
 
47
- if (actual.identifier !== undefined) {
59
+ if ('identifier' in actual) {
48
60
  this.#configuration.identifier = actual.identifier;
49
61
  }
50
62
 
51
- if (typeof actual.ignoreCache === 'boolean') {
52
- this.#configuration.ignoreCache = actual.ignoreCache;
63
+ if (typeof actual.cache === 'boolean') {
64
+ this.#configuration.cache = actual.cache;
53
65
  }
54
66
 
55
67
  return this;
56
68
  }
57
69
 
58
70
  /**
59
- * Gets a list of the fragment's nodes
71
+ * Get a list of the fragment's nodes
72
+ * @returns List of nodes
60
73
  */
61
74
  get(): ChildNode[] {
62
75
  const data = this.#data;
@@ -65,13 +78,9 @@ export class Fragment {
65
78
  const parsed = parse(data);
66
79
 
67
80
  const templated = html(parsed, {
68
- sanitizeBooleanAttributes: false,
81
+ cache: this.#configuration.cache,
69
82
  });
70
83
 
71
- if (this.#configuration.ignoreCache) {
72
- html.remove(parsed);
73
- }
74
-
75
84
  data.items.splice(
76
85
  0,
77
86
  data.items.length,
@@ -83,20 +92,24 @@ export class Fragment {
83
92
  mapNodes(
84
93
  data,
85
94
  data.items.flatMap(
86
- item =>
87
- item.fragments?.flatMap(fragment => fragment.get()) ?? item.nodes ?? [],
95
+ item => item.fragments?.flatMap(fragment => fragment.get()) ?? item.nodes ?? [],
88
96
  ),
89
97
  );
90
98
  }
91
99
 
92
- return [
93
- ...data.items.flatMap(
94
- item =>
95
- item.fragments?.flatMap(fragment => fragment.get()) ?? item.nodes ?? [],
96
- ),
97
- ];
100
+ return data.items.flatMap(
101
+ item => item.fragments?.flatMap(fragment => fragment.get()) ?? item.nodes ?? [],
102
+ );
98
103
  }
99
104
 
105
+ /**
106
+ * Set an identifier for the fragment
107
+ *
108
+ * _An identifier can be used to uniquely identify a fragment,
109
+ * which helps prevent re-rendering in certain scenarios._
110
+ * @param identifier Identifier
111
+ * @returns Fragment
112
+ */
100
113
  identify(identifier: unknown): Fragment {
101
114
  this.#configuration.identifier = identifier;
102
115
 
@@ -104,7 +117,7 @@ export class Fragment {
104
117
  }
105
118
 
106
119
  /**
107
- * Removes the fragment from the DOM
120
+ * Remove the fragment from the DOM
108
121
  */
109
122
  remove(): void {
110
123
  removeFragment(this.#data);
@@ -114,20 +127,30 @@ export class Fragment {
114
127
  function removeFragment(data: FragmentData): void {
115
128
  removeMora(data);
116
129
 
117
- const {length} = data.items;
130
+ let {length} = data.items;
118
131
 
119
132
  for (let index = 0; index < length; index += 1) {
120
133
  const {fragments, nodes} = data.items[index];
121
- const length = fragments?.length ?? 0;
134
+ const fragmentsLength = fragments?.length ?? 0;
122
135
 
123
- for (let index = 0; index < length; index += 1) {
124
- fragments?.[index]?.remove();
136
+ for (let fragmentIndex = 0; fragmentIndex < fragmentsLength; fragmentIndex += 1) {
137
+ fragments?.[fragmentIndex]?.remove();
125
138
  }
126
139
 
127
140
  removeNodes(nodes ?? []);
128
141
  }
129
142
 
130
143
  data.items.length = 0;
144
+
145
+ length = data.values.length;
146
+
147
+ for (let index = 0; index < length; index += 1) {
148
+ const value = data.values[index];
149
+
150
+ if (isFragments(value)) {
151
+ handleFragments(value, true);
152
+ }
153
+ }
131
154
  }
132
155
 
133
156
  function removeMora(data: FragmentData): void {
@@ -0,0 +1,132 @@
1
+ import {getString} from '@oscarpalmer/atoms/string';
2
+ import {array, type ReactiveArray} from '@oscarpalmer/mora';
3
+ import {NAME_FRAGMENTS} from './constants';
4
+ import type {Fragment} from './fragment';
5
+ import {isFragment, isFragments} from './helpers';
6
+ import type {FragmentsState} from './models';
7
+
8
+ export class Fragments {
9
+ readonly #state: FragmentsState;
10
+
11
+ /**
12
+ * Fragment items
13
+ */
14
+ get items(): ReactiveArray<Fragment> {
15
+ return this.#state.mapped;
16
+ }
17
+
18
+ constructor(
19
+ items: ReactiveArray<unknown>,
20
+ identify: (item: unknown) => unknown,
21
+ fragment: (item: unknown) => Fragment,
22
+ ) {
23
+ Object.defineProperty(this, NAME_FRAGMENTS, {
24
+ value: true,
25
+ });
26
+
27
+ this.#state = {
28
+ fragment,
29
+ identify,
30
+ array: items,
31
+ instances: {},
32
+ mapped: array<Fragment>([]),
33
+ subscriber: undefined,
34
+ };
35
+
36
+ states.set(this, this.#state);
37
+
38
+ initializeFragments(this.#state);
39
+ }
40
+ }
41
+
42
+ export function handleFragments(item: Fragments | FragmentsState, remove: boolean): void {
43
+ const state = isFragments(item) ? states.get(item) : item;
44
+
45
+ if (state != null) {
46
+ (remove ? removeFragments : initializeFragments)(state);
47
+ }
48
+ }
49
+
50
+ function handleItems(state: FragmentsState, items: unknown[]): void {
51
+ const keys = new Set<string>();
52
+ const mapped: Fragment[] = [];
53
+
54
+ const {length} = items;
55
+
56
+ for (let index = 0; index < length; index += 1) {
57
+ const item = items[index];
58
+ const identifier = state.identify(item);
59
+
60
+ if (identifier == null) {
61
+ throw new Error('Identifier cannot be null or undefined');
62
+ }
63
+
64
+ const key = getString(identifier);
65
+
66
+ if (keys.has(key)) {
67
+ throw new Error(`Duplicate identifier found: "${key}"`);
68
+ }
69
+
70
+ let instance = state.instances[key];
71
+
72
+ if (instance == null) {
73
+ instance = state.fragment(item);
74
+
75
+ if (!isFragment(instance)) {
76
+ throw new Error('Fragment function must return a Fragment instance');
77
+ }
78
+ }
79
+
80
+ instance.identify(key);
81
+
82
+ state.instances[key] = instance;
83
+
84
+ keys.add(key);
85
+
86
+ mapped.push(instance);
87
+ }
88
+
89
+ state.mapped.set(mapped);
90
+
91
+ updateFragments(state, keys);
92
+ }
93
+
94
+ export function initializeFragments(state: FragmentsState): void {
95
+ state.subscriber ??= state.array.subscribe(items => {
96
+ handleItems(state, items);
97
+ });
98
+ }
99
+
100
+ function removeFragments(state: FragmentsState): void {
101
+ state.subscriber?.();
102
+
103
+ updateFragments(state);
104
+
105
+ state.subscriber = undefined;
106
+ }
107
+
108
+ function updateFragments(state: FragmentsState, active?: Set<string>): void {
109
+ const next: Record<string, Fragment> = {};
110
+
111
+ const previous = {...state.instances};
112
+ const keys = Object.keys(previous);
113
+ const {length} = keys;
114
+
115
+ for (let index = 0; index < length; index += 1) {
116
+ const key = keys[index];
117
+
118
+ if (active?.has(key)) {
119
+ next[key] = previous[key];
120
+ } else {
121
+ previous[key].remove();
122
+ }
123
+ }
124
+
125
+ state.instances = next;
126
+
127
+ active?.clear();
128
+ }
129
+
130
+ //
131
+
132
+ const states: WeakMap<Fragments, FragmentsState> = new WeakMap();
@@ -1,6 +1,5 @@
1
1
  import {getString} from '@oscarpalmer/atoms/string';
2
- import {isChildNode, isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
3
- import {removeEvents} from '../node/event';
2
+ import {isChildNode} from '@oscarpalmer/toretto/is';
4
3
  import {isFragment} from './index';
5
4
 
6
5
  export function createNodes(value: unknown): ChildNode[] {
@@ -16,8 +15,6 @@ export function createNodes(value: unknown): ChildNode[] {
16
15
  }
17
16
 
18
17
  export function removeNodes(nodes: ChildNode[]): void {
19
- sanitizeNodes(nodes);
20
-
21
18
  const {length} = nodes;
22
19
 
23
20
  for (let index = 0; index < length; index += 1) {
@@ -34,19 +31,3 @@ export function replaceNodes(from: ChildNode[], to: ChildNode[]): void {
34
31
  from[index].remove();
35
32
  }
36
33
  }
37
-
38
- function sanitizeNodes(nodes: ChildNode[]): void {
39
- const {length} = nodes;
40
-
41
- for (let index = 0; index < length; index += 1) {
42
- const node = nodes[index];
43
-
44
- if (isHTMLOrSVGElement(node)) {
45
- removeEvents(node);
46
- }
47
-
48
- if (node.hasChildNodes()) {
49
- sanitizeNodes([...node.childNodes] as ChildNode[]);
50
- }
51
- }
52
- }
@@ -1,4 +1,7 @@
1
+ import type {PlainObject} from '@oscarpalmer/atoms/models';
2
+ import {NAME_FRAGMENT, NAME_FRAGMENTS} from '../constants';
1
3
  import type {Fragment} from '../fragment';
4
+ import type {Fragments} from '../fragments';
2
5
 
3
6
  export function compareArrays(
4
7
  first: unknown[],
@@ -8,22 +11,32 @@ export function compareArrays(
8
11
  const from = firstIsLarger ? first : second;
9
12
  const to = firstIsLarger ? second : first;
10
13
 
11
- if (
12
- !from
13
- .filter(key => to.includes(key))
14
- .every((key, index) => to[index] === key)
15
- ) {
16
- return 'dissimilar';
14
+ if (!from.filter(key => to.includes(key)).every((key, index) => to[index] === key)) {
15
+ return COMPARISON_DISSIMILAR;
17
16
  }
18
17
 
19
- return firstIsLarger ? 'removed' : 'added';
18
+ return firstIsLarger ? COMPARISON_REMOVED : COMPARISON_ADDED;
20
19
  }
21
20
 
22
21
  export function isFragment(value: unknown): value is Fragment {
22
+ return isNamed(value, NAME_FRAGMENT);
23
+ }
24
+
25
+ export function isFragments(value: unknown): value is Fragments {
26
+ return isNamed(value, NAME_FRAGMENTS);
27
+ }
28
+
29
+ function isNamed(value: unknown, name: string): boolean {
23
30
  return (
24
31
  typeof value === 'object' &&
25
32
  value != null &&
26
- '$fragment' in value &&
27
- value.$fragment === true
33
+ name in value &&
34
+ (value as PlainObject)[name] === true
28
35
  );
29
36
  }
37
+
38
+ const COMPARISON_ADDED = 'added';
39
+
40
+ const COMPARISON_DISSIMILAR = 'dissimilar';
41
+
42
+ const COMPARISON_REMOVED = 'removed';
package/src/index.ts CHANGED
@@ -1,13 +1,31 @@
1
1
  import '@oscarpalmer/mora';
2
- import { Fragment } from './fragment';
2
+ import type {ReactiveArray} from '@oscarpalmer/mora';
3
+ import {Fragment} from './fragment';
4
+ import {Fragments} from './fragments';
3
5
 
4
- export function html(
5
- strings: TemplateStringsArray,
6
- ...values: unknown[]
7
- ): Fragment {
8
- return new Fragment(strings, values);
6
+ /**
7
+ * Create Fragments from a reactive array
8
+ * @param array Reactive array
9
+ * @param identify Function to identify item
10
+ * @param fragment Function to create fragment from item
11
+ * @returns Fragments
12
+ */
13
+ export function fragments<Item>(
14
+ array: ReactiveArray<Item>,
15
+ identify: (item: Item) => unknown,
16
+ fragment: (item: Item) => Fragment,
17
+ ): Fragments {
18
+ return new Fragments(array as ReactiveArray<unknown>, identify as never, fragment as never);
9
19
  }
10
20
 
11
- export * from '@oscarpalmer/mora';
12
- export type { Fragment } from './fragment';
21
+ /**
22
+ * Create Fragment from a template
23
+ * @returns Fragment
24
+ */
25
+ export function html(template: TemplateStringsArray, ...values: unknown[]): Fragment {
26
+ return new Fragment(template, values);
27
+ }
13
28
 
29
+ export * from '@oscarpalmer/mora';
30
+ export type {Fragment} from './fragment';
31
+ export type {Fragments} from './fragments';
package/src/models.ts CHANGED
@@ -1,9 +1,18 @@
1
- import type {Reactive} from '@oscarpalmer/mora';
1
+ import type {Reactive, ReactiveArray, Unsubscribe} from '@oscarpalmer/mora';
2
2
  import type {Fragment} from './fragment';
3
3
 
4
4
  export type FragmentConfiguration = {
5
+ /**
6
+ * Should the template be cached? _(defaults to `true`)_
7
+ */
8
+ cache?: boolean;
9
+ /**
10
+ * Identifier for the fragment
11
+ *
12
+ * _(An identifier can be used to uniquely identify a fragment,
13
+ * which helps prevent re-rendering in certain scenarios)_
14
+ */
5
15
  identifier?: unknown;
6
- ignoreCache?: boolean;
7
16
  };
8
17
 
9
18
  export type FragmentData = {
@@ -21,6 +30,15 @@ export type FragmentItem = {
21
30
  text?: Text;
22
31
  };
23
32
 
33
+ export type FragmentsState = {
34
+ array: ReactiveArray<unknown>;
35
+ fragment: (item: unknown) => Fragment;
36
+ identify: (item: unknown) => unknown;
37
+ instances: Record<string, Fragment>;
38
+ mapped: ReactiveArray<Fragment>;
39
+ subscriber: Unsubscribe | undefined;
40
+ };
41
+
24
42
  type MoraData = {
25
43
  subscribers: Set<() => void>;
26
44
  values: Set<Reactive<unknown>>;
@@ -1,43 +1,44 @@
1
1
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
2
  import {computed, isReactive} from '@oscarpalmer/mora';
3
- import {isBooleanAttribute, setProperty} from '@oscarpalmer/toretto/attribute';
4
- import type {HTMLOrSVGElement} from '@oscarpalmer/toretto/models';
5
- import {EXPRESSION_COMMENT_FULL} from '../../constants';
3
+ import {
4
+ EXPRESSION_ABYDON_ATTRIBUTE_PREFIX,
5
+ EXPRESSION_ABYDON_CONTENT,
6
+ EXPRESSION_EVENT_PREFIX,
7
+ EXPRESSION_PERIOD,
8
+ } from '../../constants';
6
9
  import type {FragmentData} from '../../models';
7
10
  import {mapEvent} from '../event';
8
11
  import {setAttribute} from './value';
9
12
 
10
13
  function getValue(data: FragmentData, original: string): unknown {
11
- const matches = EXPRESSION_COMMENT_FULL.exec(original ?? '');
14
+ const matches = EXPRESSION_ABYDON_CONTENT.exec(original ?? '');
12
15
 
13
16
  return matches == null ? original : data.values[+matches[1]];
14
17
  }
15
18
 
16
- export function mapAttributes(
17
- data: FragmentData,
18
- element: HTMLOrSVGElement,
19
- ): void {
19
+ export function mapAttributes(data: FragmentData, element: HTMLElement | SVGElement): void {
20
20
  const attributes = [...element.attributes];
21
21
  const {length} = attributes;
22
22
 
23
23
  for (let index = 0; index < length; index += 1) {
24
24
  const {name, value} = attributes[index];
25
25
 
26
- const actual = getValue(data, value);
26
+ const actualName = name.replace(EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, '');
27
+ const actualValue = getValue(data, value) as never;
27
28
 
28
- switch (true) {
29
- case name.startsWith('@'):
30
- mapEvent(element, name, actual);
31
- break;
29
+ if (actualName !== name) {
30
+ element.removeAttribute(name);
31
+ }
32
32
 
33
- case name.includes('.') ||
34
- typeof actual === 'function' ||
35
- isReactive(actual):
36
- mapValue(data, element, name, actual);
33
+ switch (true) {
34
+ case EXPRESSION_EVENT_PREFIX.test(actualName):
35
+ mapEvent(element, actualName, actualValue);
37
36
  break;
38
37
 
39
- case isBooleanAttribute(name):
40
- setProperty(element, name, value);
38
+ case EXPRESSION_PERIOD.test(actualName):
39
+ case typeof actualValue === 'function':
40
+ case isReactive(actualValue):
41
+ mapValue(data, element, actualName, actualValue);
41
42
  break;
42
43
 
43
44
  default:
@@ -48,7 +49,7 @@ export function mapAttributes(
48
49
 
49
50
  function mapValue(
50
51
  data: FragmentData,
51
- element: HTMLOrSVGElement,
52
+ element: HTMLElement | SVGElement,
52
53
  name: string,
53
54
  value: unknown,
54
55
  ): void {
@@ -61,7 +62,7 @@ function mapValue(
61
62
 
62
63
  function setComputedAttribute(
63
64
  data: FragmentData,
64
- element: HTMLOrSVGElement,
65
+ element: HTMLElement | SVGElement,
65
66
  name: string,
66
67
  callback: GenericCallback,
67
68
  ): void {