Skip to content

Commit 639c8a1

Browse files
committed
bug #1828 [LiveComponent] Tokenize classes on all allowed whitespaces (aleho)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [LiveComponent] Tokenize classes on all allowed whitespaces | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Issues | Fix #1819 | License | MIT The class attribute can be tokenized by any white space as defined by the HTML spec. When a class attribute contains whitespaces other than a space change tracking fails and invalid classes are applied resulting in `Uncaught (in promise) DOMException: DOMTokenList.remove: The empty string is not a valid token.`. Commits ------- ca47377 [LiveComponent] Tokenize classes on all allowed whitespaces
2 parents 97724fc + ca47377 commit 639c8a1

File tree

3 files changed

+33
-22
lines changed

3 files changed

+33
-22
lines changed

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,17 +1822,8 @@ class ExternalMutationTracker {
18221822
}
18231823
handleClassAttributeMutation(mutation, elementChanges) {
18241824
const element = mutation.target;
1825-
const previousValue = mutation.oldValue;
1826-
const previousValues = previousValue ? previousValue.split(' ') : [];
1827-
previousValues.forEach((value, index) => {
1828-
const trimmedValue = value.trim();
1829-
if (trimmedValue !== '') {
1830-
previousValues[index] = trimmedValue;
1831-
}
1832-
else {
1833-
previousValues.splice(index, 1);
1834-
}
1835-
});
1825+
const previousValue = mutation.oldValue || '';
1826+
const previousValues = previousValue.match(/(\S+)/gu) || [];
18361827
const newValues = [].slice.call(element.classList);
18371828
const addedValues = newValues.filter((value) => !previousValues.includes(value));
18381829
const removedValues = previousValues.filter((value) => !newValues.includes(value));

src/LiveComponent/assets/src/Rendering/ExternalMutationTracker.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -189,16 +189,8 @@ export default class {
189189
private handleClassAttributeMutation(mutation: MutationRecord, elementChanges: ElementChanges) {
190190
const element = mutation.target as Element;
191191

192-
const previousValue = mutation.oldValue;
193-
const previousValues = previousValue ? previousValue.split(' ') : [];
194-
previousValues.forEach((value, index) => {
195-
const trimmedValue = value.trim();
196-
if (trimmedValue !== '') {
197-
previousValues[index] = trimmedValue;
198-
} else {
199-
previousValues.splice(index, 1);
200-
}
201-
});
192+
const previousValue = mutation.oldValue || '';
193+
const previousValues: string[] = previousValue.match(/(\S+)/gu) || [];
202194

203195
const newValues: string[] = [].slice.call(element.classList);
204196
const addedValues = newValues.filter((value) => !previousValues.includes(value));

src/LiveComponent/assets/test/Rendering/ExternalMutationTracker.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ describe('ExternalMutationTracker', () => {
114114
element.classList.remove('second-class');
115115
element.classList.add('second-class');
116116
// add new (with some whitespace to be sneaky)
117-
element.setAttribute('class', ` ${element.getAttribute('class')} new-class `)
117+
element.setAttribute('class', ` ${element.getAttribute('class')} \n new-class `)
118118
// remove
119119
element.classList.remove('first-class');
120120
// add then remove
@@ -135,6 +135,34 @@ describe('ExternalMutationTracker', () => {
135135
expect(changes.getRemovedClasses()).toEqual(['first-class']);
136136
});
137137

138+
it('can track class changes with whitespaces', async () => {
139+
const { element, tracker } = createTracker(`
140+
<div
141+
class="
142+
first-class
143+
second-class
144+
third-class
145+
"
146+
>Text inside!</div>
147+
`)
148+
149+
element.classList.remove('second-class');
150+
element.classList.add('new-class');
151+
await shortTimeout();
152+
153+
const changes = tracker.getChangedElement(element);
154+
if (!changes) {
155+
throw new Error('Expected changes to be present');
156+
}
157+
expect(changes.getChangedStyles()).toHaveLength(0);
158+
expect(changes.getRemovedStyles()).toHaveLength(0);
159+
expect(changes.getChangedAttributes()).toHaveLength(0);
160+
expect(changes.getRemovedAttributes()).toHaveLength(0);
161+
162+
expect(changes.getAddedClasses()).toEqual(['new-class']);
163+
expect(changes.getRemovedClasses()).toEqual(['second-class']);
164+
});
165+
138166
it('can track added element', async () => {
139167
const { element, tracker } = createTracker(`
140168
<div>

0 commit comments

Comments
 (0)