From 32a4dafc0b0aa5261ee10a3d6d8bf518562c2cdc Mon Sep 17 00:00:00 2001 From: Philip Nuzhnyi Date: Thu, 14 Nov 2024 09:00:31 +0000 Subject: [PATCH] correctly handle attributes with dots in names --- src/nodes/html.ts | 32 ++++++++++++---------- test/tests/attributes-with-dots.js | 44 ++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 test/tests/attributes-with-dots.js diff --git a/src/nodes/html.ts b/src/nodes/html.ts index 7df09eb..eb930f5 100644 --- a/src/nodes/html.ts +++ b/src/nodes/html.ts @@ -159,7 +159,11 @@ export default class HTMLElement extends Node { return 'null'; } - return JSON.stringify(attr.replace(/"/g, '"')).replace(/\\t/g, '\t').replace(/\\n/g, '\n').replace(/\\r/g, '\r').replace(/\\/g, ''); + return JSON.stringify(attr.replace(/"/g, '"')) + .replace(/\\t/g, '\t') + .replace(/\\n/g, '\n') + .replace(/\\r/g, '\r') + .replace(/\\/g, ''); } /** @@ -373,11 +377,7 @@ export default class HTMLElement extends Node { return child === this; }); resetParent([this], null); - parent.childNodes = [ - ...parent.childNodes.slice(0, idx), - ...resetParent(content, parent), - ...parent.childNodes.slice(idx + 1), - ]; + parent.childNodes = [...parent.childNodes.slice(0, idx), ...resetParent(content, parent), ...parent.childNodes.slice(idx + 1)]; return this; } @@ -456,10 +456,12 @@ export default class HTMLElement extends Node { this.childNodes.length = o; // remove whitespace between attributes - const attrs = Object.keys( this.rawAttributes).map((key) => { - const val = this.rawAttributes[key]; - return `${key}=${ JSON.stringify(val)}`; - }).join(' '); + const attrs = Object.keys(this.rawAttributes) + .map((key) => { + const val = this.rawAttributes[key]; + return `${key}=${JSON.stringify(val)}`; + }) + .join(' '); this.rawAttrs = attrs; delete this._rawAttrs; return this; @@ -565,7 +567,7 @@ export default class HTMLElement extends Node { if (child.nodeType === NodeType.ELEMENT_NODE) { if (child.id === id) { return child; - }; + } // if children are existing push the current status to the stack and keep searching for elements in the level below if (child.childNodes.length > 0) { @@ -686,7 +688,7 @@ export default class HTMLElement extends Node { } const attrs = {} as RawAttributes; if (this.rawAttrs) { - const re = /([a-zA-Z()[\]#@$.?:][a-zA-Z0-9-_:()[\]#]*)(?:\s*=\s*((?:'[^']*')|(?:"[^"]*")|\S+))?/g; + const re = /([a-zA-Z()\[\]#@$.?:][a-zA-Z0-9-._:()[\]#]*)(?:\s*=\s*((?:'[^']*')|(?:"[^"]*")|\S+))?/g; let match: RegExpExecArray; while ((match = re.exec(this.rawAttrs))) { const key = match[1]; @@ -1023,7 +1025,7 @@ export interface Options { * void tag serialisation, add a final slash
*/ closingSlash?: boolean; - } + }; } const frameflag = 'documentfragmentcontainer'; @@ -1109,7 +1111,7 @@ export function base_parse(data: string, options = {} as Partial) { if (!leadingSlash) { /* Populate attributes */ const attrs = {} as Record; - for (let attMatch; (attMatch = kAttributePattern.exec(attributes));) { + for (let attMatch; (attMatch = kAttributePattern.exec(attributes)); ) { const { 1: key, 2: val } = attMatch; const isQuoted = val[0] === `'` || val[0] === `"`; attrs[key.toLowerCase()] = isQuoted ? val.slice(1, val.length - 1) : val; @@ -1247,7 +1249,7 @@ export function parse(data: string, options = {} as Partial) { * and removes nodes from any potential parent. */ function resolveInsertable(insertable: NodeInsertable[]): Node[] { - return insertable.map(val => { + return insertable.map((val) => { if (typeof val === 'string') { return new TextNode(val); } diff --git a/test/tests/attributes-with-dots.js b/test/tests/attributes-with-dots.js new file mode 100644 index 0000000..fe3de80 --- /dev/null +++ b/test/tests/attributes-with-dots.js @@ -0,0 +1,44 @@ +const { parse } = require('@test/test-target'); + +describe('funky attributes', function () { + it('x-transition.duration.500ms', function () { + const root = parse('
'); + const div = root.firstChild; + div.getAttribute('x-transition.duration.500ms').should.eql(''); + div.toString().should.eql('
'); + }); + + it('x-transition:enter.duration.500ms and x-transition:leave.duration.400ms', function () { + const root = parse('
'); + const div = root.firstChild; + div.getAttribute('x-transition:enter.duration.500ms').should.eql(''); + div.getAttribute('x-transition:leave.duration.400ms').should.eql(''); + div.toString().should.eql('
'); + }); + + it('@click="open = ! open"', function () { + const root = parse(''); + const div = root.firstChild; + div.getAttribute('@click').should.eql('open = ! open'); + div.toString().should.eql(''); + }); + + it('a bunch of stuff at the same time', function () { + const root = parse( + '
Hello 👋
' + ); + const div = root.firstChild; + + div.getAttribute('x-show').should.eql('open'); + div.getAttribute('x-transition:enter').should.eql('transition ease-out duration-300'); + div.getAttribute('x-transition:enter-start').should.eql('opacity-0 scale-90'); + div.getAttribute('x-transition:enter-end').should.eql('opacity-100 scale-100'); + div.getAttribute('x-transition:leave').should.eql('transition ease-in duration-300'); + div.getAttribute('x-transition:leave-start').should.eql('opacity-100 scale-100'); + div.getAttribute('x-transition:leave-end').should.eql('opacity-0 scale-90'); + + div.toString().should.eql( + '
Hello 👋
' + ); + }); +});