Skip to content

Commit 609f23b

Browse files
committed
bug #269 Allow SVG elements in LiveComponent (norkunas)
This PR was merged into the 2.x branch. Discussion ---------- Allow SVG elements in LiveComponent | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Tickets | N/A | License | MIT When the component contains svg elements then it throws error on rerender: ``` Uncaught (in promise) Error: Could not clone element at cloneHTMLElement (live_controller.js:977:15) at onBeforeElUpdated (live_controller.js:1331:46) at morphEl (live_controller.js:492:21) at morphChildren (live_controller.js:605:33) at morphEl (live_controller.js:507:15) at morphChildren (live_controller.js:605:33) at morphEl (live_controller.js:507:15) at morphChildren (live_controller.js:605:33) at morphEl (live_controller.js:507:15) at morphdom (live_controller.js:722:13) ``` Commits ------- c25f021 Allow SVG elements in LiveComponent
2 parents 82f18ba + c25f021 commit 609f23b

File tree

3 files changed

+47
-10
lines changed

3 files changed

+47
-10
lines changed

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,9 @@ class default_1 extends Controller {
10801080
return element.dataset.value || element.value;
10811081
}
10821082
_updateModelFromElement(element, value, shouldRender) {
1083+
if (!(element instanceof HTMLElement)) {
1084+
throw new Error('Could not update model for non HTMLElement');
1085+
}
10831086
const model = element.dataset.model || element.getAttribute('name');
10841087
if (!model) {
10851088
const clonedElement = cloneHTMLElement(element);
@@ -1273,7 +1276,7 @@ class default_1 extends Controller {
12731276
_getLoadingDirectives() {
12741277
const loadingDirectives = [];
12751278
this.element.querySelectorAll('[data-loading]').forEach((element => {
1276-
if (!(element instanceof HTMLElement)) {
1279+
if (!(element instanceof HTMLElement) && !(element instanceof SVGElement)) {
12771280
throw new Error('Invalid Element Type');
12781281
}
12791282
const directives = parseDirectives(element.dataset.loading || 'show');
@@ -1327,6 +1330,9 @@ class default_1 extends Controller {
13271330
const newElement = htmlToElement(newHtml);
13281331
morphdom(this.element, newElement, {
13291332
onBeforeElUpdated: (fromEl, toEl) => {
1333+
if (!(fromEl instanceof HTMLElement) || !(toEl instanceof HTMLElement)) {
1334+
return false;
1335+
}
13301336
if (fromEl.isEqualNode(toEl)) {
13311337
const normalizedFromEl = cloneHTMLElement(fromEl);
13321338
normalizeAttributesForComparison(normalizedFromEl);

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)