Skip to content

Commit ce82db4

Browse files
committed
Allow SVG elements in LiveComponent
1 parent 9f50cbb commit ce82db4

File tree

5 files changed

+43
-13
lines changed

5 files changed

+43
-13
lines changed

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,7 @@ function normalizeAttributesForComparison(element) {
973973

974974
function cloneHTMLElement(element) {
975975
const newElement = element.cloneNode(true);
976-
if (!(newElement instanceof HTMLElement)) {
976+
if (!(newElement instanceof HTMLElement) && !(newElement instanceof SVGElement)) {
977977
throw new Error('Could not clone element');
978978
}
979979
return newElement;
@@ -1273,7 +1273,7 @@ class default_1 extends Controller {
12731273
_getLoadingDirectives() {
12741274
const loadingDirectives = [];
12751275
this.element.querySelectorAll('[data-loading]').forEach((element => {
1276-
if (!(element instanceof HTMLElement)) {
1276+
if (!(element instanceof HTMLElement) && !(element instanceof SVGElement)) {
12771277
throw new Error('Invalid Element Type');
12781278
}
12791279
const directives = parseDirectives(element.dataset.loading || 'show');

src/LiveComponent/assets/src/clone_html_element.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
export function cloneHTMLElement(element: HTMLElement): HTMLElement {
1+
export function cloneHTMLElement(element: HTMLElement|SVGElement): HTMLElement|SVGElement {
22
const newElement = element.cloneNode(true);
3-
if (!(newElement instanceof HTMLElement)) {
3+
if (!(newElement instanceof HTMLElement) && !(newElement instanceof SVGElement)) {
44
throw new Error('Could not clone element');
55
}
66

src/LiveComponent/assets/src/live_controller.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { normalizeAttributesForComparison } from './normalize_attributes_for_com
88
import { cloneHTMLElement } from './clone_html_element';
99

1010
interface ElementLoadingDirectives {
11-
element: HTMLElement,
11+
element: HTMLElement|SVGElement,
1212
directives: Directive[]
1313
}
1414

@@ -419,7 +419,7 @@ export default class extends Controller {
419419
/**
420420
* @private
421421
*/
422-
_handleLoadingDirective(element: HTMLElement, isLoading: boolean, directive: Directive) {
422+
_handleLoadingDirective(element: HTMLElement|SVGElement, isLoading: boolean, directive: Directive) {
423423
const finalAction = parseLoadingAction(directive.action, isLoading);
424424

425425
let loadingDirective: (() => void);
@@ -490,7 +490,7 @@ export default class extends Controller {
490490
const loadingDirectives: ElementLoadingDirectives[] = [];
491491

492492
this.element.querySelectorAll('[data-loading]').forEach((element => {
493-
if (!(element instanceof HTMLElement)) {
493+
if (!(element instanceof HTMLElement) && !(element instanceof SVGElement)) {
494494
throw new Error('Invalid Element Type');
495495
}
496496

@@ -506,19 +506,19 @@ export default class extends Controller {
506506
return loadingDirectives;
507507
}
508508

509-
_showElement(element: HTMLElement) {
509+
_showElement(element: HTMLElement|SVGElement) {
510510
element.style.display = 'inline-block';
511511
}
512512

513-
_hideElement(element: HTMLElement) {
513+
_hideElement(element: HTMLElement|SVGElement) {
514514
element.style.display = 'none';
515515
}
516516

517-
_addClass(element: HTMLElement, classes: string[]) {
517+
_addClass(element: HTMLElement|SVGElement, classes: string[]) {
518518
element.classList.add(...combineSpacedArray(classes));
519519
}
520520

521-
_removeClass(element: HTMLElement, classes: string[]) {
521+
_removeClass(element: HTMLElement|SVGElement, classes: string[]) {
522522
element.classList.remove(...combineSpacedArray(classes));
523523

524524
// remove empty class="" to avoid morphdom "diff" problem

src/LiveComponent/assets/src/normalize_attributes_for_comparison.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
* and sets that onto the value attribute. This is useful to compare
66
* if two nodes are identical.
77
*/
8-
export function normalizeAttributesForComparison(element: HTMLElement): void {
8+
export function normalizeAttributesForComparison(element: HTMLElement|SVGElement): void {
99
if (element.value) {
1010
element.setAttribute('value', element.value);
1111
} else if (element.hasAttribute('value')) {
1212
element.setAttribute('value', '');
1313
}
1414

15-
Array.from(element.children).forEach((child: HTMLElement) => {
15+
Array.from(element.children).forEach((child: HTMLElement|SVGElement) => {
1616
normalizeAttributesForComparison(child);
1717
});
1818
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { cloneHTMLElement } from '../src/clone_html_element';
2+
3+
const createElement = function(html: string): HTMLElement|SVGElement {
4+
const template = document.createElement('template');
5+
html = html.trim();
6+
template.innerHTML = html;
7+
8+
const child = template.content.firstChild;
9+
if (!child || (!(child instanceof HTMLElement) && !(child instanceof SVGElement))) {
10+
throw new Error('Child not found');
11+
}
12+
13+
return child;
14+
}
15+
16+
describe('cloneHTMLElement', () => {
17+
it('allows to clone HTMLElement', () => {
18+
const element = createElement('<div class="foo"></div>');
19+
const clone = cloneHTMLElement(element);
20+
21+
expect(clone.outerHTML).toEqual('<div class="foo"></div>');
22+
});
23+
24+
it('allows to clone SVGElement', () => {
25+
const element = createElement('<svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"></svg>');
26+
const clone = cloneHTMLElement(element);
27+
28+
expect(clone.outerHTML).toEqual('<svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"></svg>');
29+
});
30+
});

0 commit comments

Comments
 (0)