Skip to content

Commit af1cb22

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

File tree

3 files changed

+42
-11
lines changed

3 files changed

+42
-11
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/live_controller.ts

Lines changed: 17 additions & 9 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

@@ -190,11 +190,15 @@ export default class extends Controller {
190190
this._makeRequest(null);
191191
}
192192

193-
_getValueFromElement(element: HTMLElement) {
193+
_getValueFromElement(element: HTMLElement|SVGElement) {
194194
return element.dataset.value || (element as any).value;
195195
}
196196

197-
_updateModelFromElement(element: HTMLElement, value: string, shouldRender: boolean) {
197+
_updateModelFromElement(element: Element, value: string, shouldRender: boolean) {
198+
if (!(element instanceof HTMLElement)) {
199+
throw new Error('Could not update model for non HTMLElement');
200+
}
201+
198202
const model = element.dataset.model || element.getAttribute('name');
199203

200204
if (!model) {
@@ -419,7 +423,7 @@ export default class extends Controller {
419423
/**
420424
* @private
421425
*/
422-
_handleLoadingDirective(element: HTMLElement, isLoading: boolean, directive: Directive) {
426+
_handleLoadingDirective(element: HTMLElement|SVGElement, isLoading: boolean, directive: Directive) {
423427
const finalAction = parseLoadingAction(directive.action, isLoading);
424428

425429
let loadingDirective: (() => void);
@@ -490,7 +494,7 @@ export default class extends Controller {
490494
const loadingDirectives: ElementLoadingDirectives[] = [];
491495

492496
this.element.querySelectorAll('[data-loading]').forEach((element => {
493-
if (!(element instanceof HTMLElement)) {
497+
if (!(element instanceof HTMLElement) && !(element instanceof SVGElement)) {
494498
throw new Error('Invalid Element Type');
495499
}
496500

@@ -506,19 +510,19 @@ export default class extends Controller {
506510
return loadingDirectives;
507511
}
508512

509-
_showElement(element: HTMLElement) {
513+
_showElement(element: HTMLElement|SVGElement) {
510514
element.style.display = 'inline-block';
511515
}
512516

513-
_hideElement(element: HTMLElement) {
517+
_hideElement(element: HTMLElement|SVGElement) {
514518
element.style.display = 'none';
515519
}
516520

517-
_addClass(element: HTMLElement, classes: string[]) {
521+
_addClass(element: HTMLElement|SVGElement, classes: string[]) {
518522
element.classList.add(...combineSpacedArray(classes));
519523
}
520524

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

524528
// remove empty class="" to avoid morphdom "diff" problem
@@ -564,6 +568,10 @@ export default class extends Controller {
564568
const newElement = htmlToElement(newHtml);
565569
morphdom(this.element, newElement, {
566570
onBeforeElUpdated: (fromEl, toEl) => {
571+
if (!(fromEl instanceof HTMLElement) || !(toEl instanceof HTMLElement)) {
572+
return false;
573+
}
574+
567575
// https://github.com/patrick-steele-idem/morphdom#can-i-make-morphdom-blaze-through-the-dom-tree-even-faster-yes
568576
if (fromEl.isEqualNode(toEl)) {
569577
// the nodes are equal, but the "value" on some might differ
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { cloneHTMLElement } from '../src/clone_html_element';
2+
3+
const createElement = function(html: string): HTMLElement {
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)) {
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+
});

0 commit comments

Comments
 (0)