Comparing version 0.1.33 to 0.1.34
@@ -5,5 +5,7 @@ 'use strict'; | ||
const {ATTRIBUTE_NODE} = require('./constants.js'); | ||
const {String, attributeChangedCallback} = require('./utils.js'); | ||
const {String} = require('./utils.js'); | ||
const {Node} = require('./node.js'); | ||
const {attributeChangedCallback} = require('./custom-element-registry.js'); | ||
/** | ||
@@ -31,5 +33,5 @@ * @implements globalThis.Attr | ||
this._changed = true; | ||
attributeChangedCallback( | ||
ownerElement, name, _value, this._value = String(value) | ||
); | ||
this._value = String(value); | ||
if (ownerElement) | ||
attributeChangedCallback(ownerElement, name, _value, this._value); | ||
} | ||
@@ -36,0 +38,0 @@ |
'use strict'; | ||
const getCE = element => element.getAttribute('is') || element.localName; | ||
const {ELEMENT_NODE} = require("../cjs/constants"); | ||
const {keys, setPrototypeOf} = Object; | ||
const classes = new WeakMap; | ||
exports.classes = classes; | ||
const {keys, setPrototypeOf} = Object; | ||
const shouldTrigger = element => { | ||
const {_active, _hold} = element.ownerDocument._customElements; | ||
return _active && !_hold; | ||
}; | ||
const attributeChangedCallback = (element, name, oldValue, newValue) => { | ||
if ( | ||
element._custom && | ||
element.attributeChangedCallback && | ||
element.constructor.observedAttributes.includes(name) | ||
) { | ||
element.attributeChangedCallback(name, oldValue, newValue); | ||
} | ||
}; | ||
exports.attributeChangedCallback = attributeChangedCallback; | ||
const triggerConnected = element => { | ||
if (element._custom && element.connectedCallback && element.isConnected) | ||
element.connectedCallback(); | ||
}; | ||
const connectedCallback = element => { | ||
if (shouldTrigger(element)) { | ||
triggerConnected(element); | ||
let {_next, _end} = element; | ||
while (_next !== _end) { | ||
if (_next.nodeType === ELEMENT_NODE) | ||
triggerConnected(_next); | ||
_next = _next._next; | ||
} | ||
} | ||
}; | ||
exports.connectedCallback = connectedCallback; | ||
const triggerDisconnected = element => { | ||
if (element._custom && element.disconnectedCallback && !element.isConnected) | ||
element.disconnectedCallback(); | ||
}; | ||
const disconnectedCallback = element => { | ||
if (shouldTrigger(element)) { | ||
triggerDisconnected(element); | ||
let {_next, _end} = element; | ||
while (_next !== _end) { | ||
if (_next.nodeType === ELEMENT_NODE) | ||
triggerDisconnected(_next); | ||
_next = _next._next; | ||
} | ||
} | ||
}; | ||
exports.disconnectedCallback = disconnectedCallback; | ||
/** | ||
@@ -22,2 +74,3 @@ * @implements globalThis.CustomElementRegistry | ||
this._active = false; | ||
this._hold = false; | ||
} | ||
@@ -74,3 +127,3 @@ | ||
const {_registry} = this; | ||
const ce = getCE(element); | ||
const ce = element.getAttribute('is') || element.localName; | ||
if (_registry.has(ce)) { | ||
@@ -90,4 +143,4 @@ const {Class, check} = _registry.get(ce); | ||
element._custom = true; | ||
if (element.isConnected && element.connectedCallback) | ||
element.connectedCallback(); | ||
if (element.isConnected) | ||
connectedCallback(element); | ||
} | ||
@@ -94,0 +147,0 @@ } |
'use strict'; | ||
const {DOCUMENT_NODE, DOM} = require('./constants.js'); | ||
const {DOCUMENT_NODE, TEXT_NODE, DOM} = require('./constants.js'); | ||
const {Mime} = require('./utils.js'); | ||
const {Mime, setBoundaries} = require('./utils.js'); | ||
@@ -96,3 +96,51 @@ // mixins & interfaces | ||
const {create, defineProperties} = Object; | ||
const {toString} = Element.prototype; | ||
const textOnlyDescriptors = { | ||
_updateTextContent: { | ||
value(content, createNode) { | ||
this.replaceChildren(); | ||
const {ownerDocument, _end} = this; | ||
const text = createNode ? ownerDocument.createTextNode(content) : content; | ||
text.parentNode = this; | ||
setBoundaries(this, text, this._end); | ||
} | ||
}, | ||
innerHTML : { | ||
get() { | ||
return this.textContent; | ||
}, | ||
set(html) { | ||
this.textContent = html; | ||
} | ||
}, | ||
textContent: { | ||
get() { | ||
const {firstChild} = this; | ||
return firstChild ? firstChild.textContent : ''; | ||
}, | ||
set(content) { | ||
this._updateTextContent(content, true); | ||
} | ||
}, | ||
// TODO: this is not perfect, although I don't think there are | ||
// real world use cases to make it perfect 🤷♂️ | ||
// if there are though, this node should accept nodes *but* | ||
// use these as `textContent` only | ||
insertBefore: { | ||
value(node) { | ||
node.remove(); | ||
this._updateTextContent(node, node.nodeType !== TEXT_NODE); | ||
node.parentNode = this; | ||
return node; | ||
} | ||
}, | ||
toString: { | ||
value() { | ||
const outerHTML = toString.call(this.cloneNode()); | ||
return outerHTML.replace(/></, `>${this.textContent}<`); | ||
} | ||
} | ||
}; | ||
const defaultViewExports = { | ||
@@ -181,3 +229,3 @@ Event, CustomEvent, | ||
this._mime = Mime[type]; | ||
this._customElements = {_active: false}; | ||
this._customElements = {_active: false, _hold: false}; | ||
@@ -328,2 +376,4 @@ /** | ||
element.setAttribute('is', options.is); | ||
if (this._mime.textOnly.test(localName)) | ||
defineProperties(element, textOnlyDescriptors); | ||
} | ||
@@ -330,0 +380,0 @@ else |
@@ -5,10 +5,13 @@ 'use strict'; | ||
String, | ||
attributeChangedCallback, | ||
getNext, | ||
getPrev, | ||
setAdjacent, | ||
ignoreCase, | ||
localCase, | ||
parseFromString | ||
parseFromString, | ||
setBoundaries | ||
} = require('./utils.js'); | ||
const {attributeChangedCallback} = require('./custom-element-registry.js'); | ||
const {NodeList} = require('./interfaces.js'); | ||
@@ -133,6 +136,14 @@ const {NonDocumentTypeChildNode, ParentNode} = require('./mixins.js'); | ||
// awkward ... but necessary to avoid triggering Custom Events | ||
// while created through the parseFromString procedure | ||
set innerHTML(html) { | ||
const {constructor} = this.ownerDocument; | ||
const document = parseFromString(new constructor, ignoreCase(this), html); | ||
this.replaceChildren(...document.documentElement.childNodes); | ||
const {constructor, _customElements} = this.ownerDocument; | ||
const document = new constructor; | ||
document._customElements = _customElements; | ||
_customElements._hold = true; | ||
const {childNodes} = parseFromString(document, ignoreCase(this), html).documentElement; | ||
const fragment = document.createDocumentFragment(); | ||
fragment.append(...childNodes); | ||
_customElements._hold = false; | ||
this.replaceChildren(fragment); | ||
} | ||
@@ -228,10 +239,7 @@ | ||
const {_prev, _next, name} = attribute; | ||
_prev._next = _next; | ||
_next._prev = _prev; | ||
setAdjacent(_prev, _next); | ||
attribute.ownerElement = attribute._prev = attribute._next = null; | ||
if (name === 'class') | ||
this._classList = null; | ||
attributeChangedCallback( | ||
this, name, attribute._value, null | ||
); | ||
attributeChangedCallback(this, name, attribute._value, null); | ||
return; | ||
@@ -258,10 +266,6 @@ } | ||
attribute.ownerElement = this; | ||
attribute._prev = this; | ||
attribute._next = _next; | ||
this._next = _next._prev = attribute; | ||
setBoundaries(this, attribute, _next); | ||
if (name === 'class') | ||
this.className = attribute._value; | ||
attributeChangedCallback( | ||
this, name, null, attribute._value | ||
); | ||
attributeChangedCallback(this, name, null, attribute._value); | ||
} | ||
@@ -268,0 +272,0 @@ return previously; |
@@ -7,2 +7,6 @@ 'use strict'; | ||
(m => { | ||
exports.Node = m.Node; | ||
})(require('./node.js')); | ||
(m => { | ||
exports.HTMLElement = m.HTMLElement; | ||
@@ -12,5 +16,5 @@ })(require('./html/html-element.js')); | ||
(m => { | ||
exports.EventTarget = m.EventTarget; | ||
exports.Event = m.Event; | ||
exports.CustomEvent = m.CustomEvent; | ||
exports.Event = m.Event; | ||
exports.EventTarget = m.EventTarget; | ||
})(require('./interfaces.js')); |
@@ -11,5 +11,9 @@ 'use strict'; | ||
const {disconnectedCallback} = require('./custom-element-registry.js'); | ||
const { | ||
findNext, | ||
getEnd | ||
getEnd, | ||
setAdjacent, | ||
setBoundaries | ||
} = require('./utils.js'); | ||
@@ -64,14 +68,12 @@ | ||
remove(node) { | ||
let {_prev, _next, nodeType} = node; | ||
let _end = node; | ||
if (nodeType === ELEMENT_NODE) { | ||
_end = node._end; | ||
_next = _end._next; | ||
const {_prev} = node; | ||
const {_next} = getEnd(node); | ||
if (_prev || _next || node.parentNode) { | ||
node.parentNode = null; | ||
setAdjacent(_prev, _next); | ||
setBoundaries(null, node, null); | ||
if (node.nodeType === ELEMENT_NODE) | ||
disconnectedCallback(node); | ||
// DO_NOT_REMOVE if (parentNode) invalidate(parentNode); | ||
} | ||
if (_prev) _prev._next = _next; | ||
if (_next) _next._prev = _prev; | ||
node.parentNode = node._prev = _end._next = null; | ||
if (node._custom && node.disconnectedCallback) | ||
node.disconnectedCallback(); | ||
// DO_NOT_REMOVE if (parentNode) invalidate(parentNode); | ||
} | ||
@@ -78,0 +80,0 @@ }; |
@@ -16,2 +16,4 @@ 'use strict'; | ||
const {connectedCallback} = require('./custom-element-registry.js'); | ||
const { | ||
@@ -21,2 +23,4 @@ String, | ||
getBoundaries, | ||
setAdjacent, | ||
setBoundaries, | ||
getEnd, | ||
@@ -72,2 +76,9 @@ getNext, | ||
get ELEMENT_NODE() { return this.constructor.ELEMENT_NODE; } | ||
get ATTRIBUTE_NODE() { return this.constructor.ATTRIBUTE_NODE; } | ||
get TEXT_NODE() { return this.constructor.TEXT_NODE; } | ||
get COMMENT_NODE() { return this.constructor.COMMENT_NODE; } | ||
get DOCUMENT_NODE() { return this.constructor.DOCUMENT_NODE; } | ||
get DOCUMENT_FRAGMENT_NODE() { return this.constructor.DOCUMENT_FRAGMENT_NODE; } | ||
// <ChildNode> | ||
@@ -147,4 +158,4 @@ before(...nodes) { | ||
_next.parentNode = parentNode; | ||
_next._prev = $next; | ||
$next = ($next._next = _next); | ||
setAdjacent($next, _next); | ||
$next = _next; | ||
}; | ||
@@ -157,4 +168,4 @@ const clone = create(OD, this, localName, SVGElement); | ||
case ELEMENT_NODE_END: | ||
parentNode._end._prev = $next; | ||
$next = ($next._next = parentNode._end); | ||
setAdjacent($next, parentNode._end); | ||
$next = parentNode._end; | ||
parentNode = parentNode.parentNode; | ||
@@ -181,4 +192,3 @@ break; | ||
} | ||
clone._end._prev = $next; | ||
$next._next = clone._end; | ||
setAdjacent($next, clone._end); | ||
return clone; | ||
@@ -379,9 +389,5 @@ case TEXT_NODE: | ||
node.remove(); | ||
_prev._next = node; | ||
_end._prev = node._end; | ||
node._prev = _prev; | ||
node._end._next = _end; | ||
node.parentNode = this; | ||
if (node._custom && node.connectedCallback) | ||
node.connectedCallback(); | ||
setBoundaries(_prev, node, _end); | ||
connectedCallback(node); | ||
break; | ||
@@ -393,18 +399,14 @@ } | ||
if (firstChild) { | ||
_prev._next = firstChild; | ||
firstChild._prev = _prev; | ||
const last = getEnd(lastChild); | ||
_end._prev = last; | ||
last._next = _end; | ||
setAdjacent(_prev, firstChild); | ||
setAdjacent(getEnd(lastChild), _end); | ||
// reset fragment | ||
node._next = node._end; | ||
node._end._prev = node; | ||
setAdjacent(node, node._end); | ||
// set parent node | ||
do { | ||
firstChild.parentNode = this; | ||
if (firstChild._custom && firstChild.connectedCallback) | ||
firstChild.connectedCallback(); | ||
if (firstChild.nodeType === ELEMENT_NODE) | ||
connectedCallback(firstChild); | ||
} while ( | ||
firstChild !== lastChild && | ||
(firstChild = firstChild._next) | ||
(firstChild = firstChild.nextSibling) | ||
); | ||
@@ -416,6 +418,4 @@ } | ||
node.remove(); | ||
_prev._next = _end._prev = node; | ||
node._prev = _prev; | ||
node._next = _end; | ||
node.parentNode = this; | ||
setBoundaries(_prev, node, _end); | ||
break; | ||
@@ -469,8 +469,4 @@ } | ||
node.remove(); | ||
_prev._next = node; | ||
node._prev = _prev; | ||
const _end = getEnd(node); | ||
_next._prev = _end; | ||
_end._next = _next; | ||
node.parentNode = this; | ||
setBoundaries(_prev, node, _next); | ||
return replaced; | ||
@@ -477,0 +473,0 @@ } |
'use strict'; | ||
const {getEnd} = require('./utils.js'); | ||
const {getEnd, setAdjacent} = require('./utils.js'); | ||
@@ -11,4 +11,3 @@ // https://dom.spec.whatwg.org/#concept-live-range | ||
const deleteContents = ({_start, _end}, fragment = null) => { | ||
_start._prev._next = _end._next; | ||
_end._next._prev = _start._prev; | ||
setAdjacent(_start._prev, _end._next); | ||
do { | ||
@@ -15,0 +14,0 @@ const end = getEnd(_start); |
@@ -16,2 +16,3 @@ 'use strict'; | ||
const textOnly = {test: () => false}; | ||
const voidElements = {test: () => true}; | ||
@@ -22,7 +23,15 @@ const Mime = { | ||
ignoreCase: true, | ||
textOnly: /^(?:plaintext|script|style|textarea|title|xmp)$/i, | ||
voidElements: /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i | ||
}, | ||
'image/svg+xml': { | ||
docType: '', | ||
ignoreCase: false, | ||
textOnly, | ||
voidElements | ||
}, | ||
'text/xml': { | ||
docType: '<?xml version="1.0" encoding="utf-8"?>', | ||
ignoreCase: false, | ||
textOnly, | ||
voidElements | ||
@@ -33,2 +42,3 @@ }, | ||
ignoreCase: false, | ||
textOnly, | ||
voidElements | ||
@@ -39,8 +49,4 @@ }, | ||
ignoreCase: false, | ||
textOnly, | ||
voidElements | ||
}, | ||
'image/svg+xml': { | ||
docType: '', | ||
ignoreCase: false, | ||
voidElements | ||
} | ||
@@ -50,14 +56,2 @@ }; | ||
const attributeChangedCallback = (element, name, oldValue, newValue) => { | ||
if ( | ||
element && | ||
element._custom && | ||
element.attributeChangedCallback && | ||
element.constructor.observedAttributes.includes(name) | ||
) { | ||
element.attributeChangedCallback(name, oldValue, newValue); | ||
} | ||
}; | ||
exports.attributeChangedCallback = attributeChangedCallback; | ||
const booleanAttribute = { | ||
@@ -124,2 +118,21 @@ get(element, name) { | ||
const setAdjacent = (before, after) => { | ||
if (before) | ||
before._next = after; | ||
if (after) | ||
after._prev = before; | ||
}; | ||
exports.setAdjacent = setAdjacent; | ||
const setBoundaries = (before, current, after) => { | ||
if (before) | ||
before._next = current; | ||
current._prev = before; | ||
current = getEnd(current); | ||
current._next = after; | ||
if (after) | ||
after._prev = current; | ||
}; | ||
exports.setBoundaries = setBoundaries; | ||
const HTML = 'text/html'; | ||
@@ -134,2 +147,4 @@ const VOID_SOURCE = Mime[HTML].voidElements.source.slice(4, -2); | ||
const {SVGElement} = document[DOM]; | ||
const {_customElements} = document; | ||
const {_active, _registry} = _customElements; | ||
let node = document.root || document.createElement('root'); | ||
@@ -149,2 +164,7 @@ let ownerSVGElement = null; | ||
} | ||
else if (_active && _registry.has(name)) { | ||
const {Class} = _registry.get(name); | ||
node = node.appendChild(new Class); | ||
return; | ||
} | ||
} | ||
@@ -154,3 +174,12 @@ node = node.appendChild(document.createElement(name)); | ||
onattribute(name, value) { | ||
node.setAttribute(name, value); | ||
if (isHTML && _active && name === 'is' && _registry.has(value)) { | ||
const {Class} = _registry.get(value); | ||
const custom = new Class; | ||
for (const attribute of node.attributes) | ||
custom.setAttributeNode(attribute); | ||
node.replaceWith(custom); | ||
node = custom; | ||
} | ||
else | ||
node.setAttribute(name, value); | ||
}, | ||
@@ -157,0 +186,0 @@ oncomment(data) { |
import {escape} from 'html-escaper'; | ||
import {ATTRIBUTE_NODE} from './constants.js'; | ||
import {String, attributeChangedCallback} from './utils.js'; | ||
import {String} from './utils.js'; | ||
import {Node} from './node.js'; | ||
import {attributeChangedCallback} from './custom-element-registry.js'; | ||
/** | ||
@@ -29,5 +31,5 @@ * @implements globalThis.Attr | ||
this._changed = true; | ||
attributeChangedCallback( | ||
ownerElement, name, _value, this._value = String(value) | ||
); | ||
this._value = String(value); | ||
if (ownerElement) | ||
attributeChangedCallback(ownerElement, name, _value, this._value); | ||
} | ||
@@ -34,0 +36,0 @@ |
@@ -1,7 +0,56 @@ | ||
const getCE = element => element.getAttribute('is') || element.localName; | ||
import {ELEMENT_NODE} from "../cjs/constants"; | ||
const {keys, setPrototypeOf} = Object; | ||
export const classes = new WeakMap; | ||
const {keys, setPrototypeOf} = Object; | ||
const shouldTrigger = element => { | ||
const {_active, _hold} = element.ownerDocument._customElements; | ||
return _active && !_hold; | ||
}; | ||
export const attributeChangedCallback = (element, name, oldValue, newValue) => { | ||
if ( | ||
element._custom && | ||
element.attributeChangedCallback && | ||
element.constructor.observedAttributes.includes(name) | ||
) { | ||
element.attributeChangedCallback(name, oldValue, newValue); | ||
} | ||
}; | ||
const triggerConnected = element => { | ||
if (element._custom && element.connectedCallback && element.isConnected) | ||
element.connectedCallback(); | ||
}; | ||
export const connectedCallback = element => { | ||
if (shouldTrigger(element)) { | ||
triggerConnected(element); | ||
let {_next, _end} = element; | ||
while (_next !== _end) { | ||
if (_next.nodeType === ELEMENT_NODE) | ||
triggerConnected(_next); | ||
_next = _next._next; | ||
} | ||
} | ||
}; | ||
const triggerDisconnected = element => { | ||
if (element._custom && element.disconnectedCallback && !element.isConnected) | ||
element.disconnectedCallback(); | ||
}; | ||
export const disconnectedCallback = element => { | ||
if (shouldTrigger(element)) { | ||
triggerDisconnected(element); | ||
let {_next, _end} = element; | ||
while (_next !== _end) { | ||
if (_next.nodeType === ELEMENT_NODE) | ||
triggerDisconnected(_next); | ||
_next = _next._next; | ||
} | ||
} | ||
}; | ||
/** | ||
@@ -20,2 +69,3 @@ * @implements globalThis.CustomElementRegistry | ||
this._active = false; | ||
this._hold = false; | ||
} | ||
@@ -72,3 +122,3 @@ | ||
const {_registry} = this; | ||
const ce = getCE(element); | ||
const ce = element.getAttribute('is') || element.localName; | ||
if (_registry.has(ce)) { | ||
@@ -88,4 +138,4 @@ const {Class, check} = _registry.get(ce); | ||
element._custom = true; | ||
if (element.isConnected && element.connectedCallback) | ||
element.connectedCallback(); | ||
if (element.isConnected) | ||
connectedCallback(element); | ||
} | ||
@@ -92,0 +142,0 @@ } |
@@ -1,4 +0,4 @@ | ||
import {DOCUMENT_NODE, DOM} from './constants.js'; | ||
import {DOCUMENT_NODE, TEXT_NODE, DOM} from './constants.js'; | ||
import {Mime} from './utils.js'; | ||
import {Mime, setBoundaries} from './utils.js'; | ||
@@ -95,3 +95,51 @@ // mixins & interfaces | ||
const {create, defineProperties} = Object; | ||
const {toString} = Element.prototype; | ||
const textOnlyDescriptors = { | ||
_updateTextContent: { | ||
value(content, createNode) { | ||
this.replaceChildren(); | ||
const {ownerDocument, _end} = this; | ||
const text = createNode ? ownerDocument.createTextNode(content) : content; | ||
text.parentNode = this; | ||
setBoundaries(this, text, this._end); | ||
} | ||
}, | ||
innerHTML : { | ||
get() { | ||
return this.textContent; | ||
}, | ||
set(html) { | ||
this.textContent = html; | ||
} | ||
}, | ||
textContent: { | ||
get() { | ||
const {firstChild} = this; | ||
return firstChild ? firstChild.textContent : ''; | ||
}, | ||
set(content) { | ||
this._updateTextContent(content, true); | ||
} | ||
}, | ||
// TODO: this is not perfect, although I don't think there are | ||
// real world use cases to make it perfect 🤷♂️ | ||
// if there are though, this node should accept nodes *but* | ||
// use these as `textContent` only | ||
insertBefore: { | ||
value(node) { | ||
node.remove(); | ||
this._updateTextContent(node, node.nodeType !== TEXT_NODE); | ||
node.parentNode = this; | ||
return node; | ||
} | ||
}, | ||
toString: { | ||
value() { | ||
const outerHTML = toString.call(this.cloneNode()); | ||
return outerHTML.replace(/></, `>${this.textContent}<`); | ||
} | ||
} | ||
}; | ||
const defaultViewExports = { | ||
@@ -180,3 +228,3 @@ Event, CustomEvent, | ||
this._mime = Mime[type]; | ||
this._customElements = {_active: false}; | ||
this._customElements = {_active: false, _hold: false}; | ||
@@ -327,2 +375,4 @@ /** | ||
element.setAttribute('is', options.is); | ||
if (this._mime.textOnly.test(localName)) | ||
defineProperties(element, textOnlyDescriptors); | ||
} | ||
@@ -329,0 +379,0 @@ else |
import {ELEMENT_NODE, ELEMENT_NODE_END, ATTRIBUTE_NODE, TEXT_NODE, COMMENT_NODE} from './constants.js'; | ||
import { | ||
String, | ||
attributeChangedCallback, | ||
getNext, getPrev, | ||
getNext, getPrev, setAdjacent, | ||
ignoreCase, localCase, | ||
parseFromString | ||
parseFromString, | ||
setBoundaries | ||
} from './utils.js'; | ||
import {attributeChangedCallback} from './custom-element-registry.js'; | ||
import {NodeList} from './interfaces.js'; | ||
@@ -129,6 +131,14 @@ import {NonDocumentTypeChildNode, ParentNode} from './mixins.js'; | ||
// awkward ... but necessary to avoid triggering Custom Events | ||
// while created through the parseFromString procedure | ||
set innerHTML(html) { | ||
const {constructor} = this.ownerDocument; | ||
const document = parseFromString(new constructor, ignoreCase(this), html); | ||
this.replaceChildren(...document.documentElement.childNodes); | ||
const {constructor, _customElements} = this.ownerDocument; | ||
const document = new constructor; | ||
document._customElements = _customElements; | ||
_customElements._hold = true; | ||
const {childNodes} = parseFromString(document, ignoreCase(this), html).documentElement; | ||
const fragment = document.createDocumentFragment(); | ||
fragment.append(...childNodes); | ||
_customElements._hold = false; | ||
this.replaceChildren(fragment); | ||
} | ||
@@ -224,10 +234,7 @@ | ||
const {_prev, _next, name} = attribute; | ||
_prev._next = _next; | ||
_next._prev = _prev; | ||
setAdjacent(_prev, _next); | ||
attribute.ownerElement = attribute._prev = attribute._next = null; | ||
if (name === 'class') | ||
this._classList = null; | ||
attributeChangedCallback( | ||
this, name, attribute._value, null | ||
); | ||
attributeChangedCallback(this, name, attribute._value, null); | ||
return; | ||
@@ -254,10 +261,6 @@ } | ||
attribute.ownerElement = this; | ||
attribute._prev = this; | ||
attribute._next = _next; | ||
this._next = _next._prev = attribute; | ||
setBoundaries(this, attribute, _next); | ||
if (name === 'class') | ||
this.className = attribute._value; | ||
attributeChangedCallback( | ||
this, name, null, attribute._value | ||
); | ||
attributeChangedCallback(this, name, null, attribute._value); | ||
} | ||
@@ -264,0 +267,0 @@ return previously; |
export {DOMParser} from './dom-parser.js'; | ||
export {Node} from './node.js'; | ||
export {HTMLElement} from './html/html-element.js'; | ||
export { | ||
CustomEvent, | ||
Event, | ||
EventTarget | ||
} from './interfaces.js'; | ||
export {EventTarget, Event, CustomEvent} from './interfaces.js'; |
@@ -10,5 +10,9 @@ import { | ||
import {disconnectedCallback} from './custom-element-registry.js'; | ||
import { | ||
findNext, | ||
getEnd, | ||
setAdjacent, | ||
setBoundaries | ||
// invalidate | ||
@@ -64,14 +68,12 @@ } from './utils.js'; | ||
remove(node) { | ||
let {_prev, _next, nodeType} = node; | ||
let _end = node; | ||
if (nodeType === ELEMENT_NODE) { | ||
_end = node._end; | ||
_next = _end._next; | ||
const {_prev} = node; | ||
const {_next} = getEnd(node); | ||
if (_prev || _next || node.parentNode) { | ||
node.parentNode = null; | ||
setAdjacent(_prev, _next); | ||
setBoundaries(null, node, null); | ||
if (node.nodeType === ELEMENT_NODE) | ||
disconnectedCallback(node); | ||
// DO_NOT_REMOVE if (parentNode) invalidate(parentNode); | ||
} | ||
if (_prev) _prev._next = _next; | ||
if (_next) _next._prev = _prev; | ||
node.parentNode = node._prev = _end._next = null; | ||
if (node._custom && node.disconnectedCallback) | ||
node.disconnectedCallback(); | ||
// DO_NOT_REMOVE if (parentNode) invalidate(parentNode); | ||
} | ||
@@ -78,0 +80,0 @@ }; |
@@ -15,2 +15,4 @@ import { | ||
import {connectedCallback} from './custom-element-registry.js'; | ||
import { | ||
@@ -20,2 +22,4 @@ String, | ||
getBoundaries, | ||
setAdjacent, | ||
setBoundaries, | ||
getEnd, | ||
@@ -72,2 +76,9 @@ getNext, | ||
get ELEMENT_NODE() { return this.constructor.ELEMENT_NODE; } | ||
get ATTRIBUTE_NODE() { return this.constructor.ATTRIBUTE_NODE; } | ||
get TEXT_NODE() { return this.constructor.TEXT_NODE; } | ||
get COMMENT_NODE() { return this.constructor.COMMENT_NODE; } | ||
get DOCUMENT_NODE() { return this.constructor.DOCUMENT_NODE; } | ||
get DOCUMENT_FRAGMENT_NODE() { return this.constructor.DOCUMENT_FRAGMENT_NODE; } | ||
// <ChildNode> | ||
@@ -147,4 +158,4 @@ before(...nodes) { | ||
_next.parentNode = parentNode; | ||
_next._prev = $next; | ||
$next = ($next._next = _next); | ||
setAdjacent($next, _next); | ||
$next = _next; | ||
}; | ||
@@ -157,4 +168,4 @@ const clone = create(OD, this, localName, SVGElement); | ||
case ELEMENT_NODE_END: | ||
parentNode._end._prev = $next; | ||
$next = ($next._next = parentNode._end); | ||
setAdjacent($next, parentNode._end); | ||
$next = parentNode._end; | ||
parentNode = parentNode.parentNode; | ||
@@ -181,4 +192,3 @@ break; | ||
} | ||
clone._end._prev = $next; | ||
$next._next = clone._end; | ||
setAdjacent($next, clone._end); | ||
return clone; | ||
@@ -378,9 +388,5 @@ case TEXT_NODE: | ||
node.remove(); | ||
_prev._next = node; | ||
_end._prev = node._end; | ||
node._prev = _prev; | ||
node._end._next = _end; | ||
node.parentNode = this; | ||
if (node._custom && node.connectedCallback) | ||
node.connectedCallback(); | ||
setBoundaries(_prev, node, _end); | ||
connectedCallback(node); | ||
break; | ||
@@ -392,18 +398,14 @@ } | ||
if (firstChild) { | ||
_prev._next = firstChild; | ||
firstChild._prev = _prev; | ||
const last = getEnd(lastChild); | ||
_end._prev = last; | ||
last._next = _end; | ||
setAdjacent(_prev, firstChild); | ||
setAdjacent(getEnd(lastChild), _end); | ||
// reset fragment | ||
node._next = node._end; | ||
node._end._prev = node; | ||
setAdjacent(node, node._end); | ||
// set parent node | ||
do { | ||
firstChild.parentNode = this; | ||
if (firstChild._custom && firstChild.connectedCallback) | ||
firstChild.connectedCallback(); | ||
if (firstChild.nodeType === ELEMENT_NODE) | ||
connectedCallback(firstChild); | ||
} while ( | ||
firstChild !== lastChild && | ||
(firstChild = firstChild._next) | ||
(firstChild = firstChild.nextSibling) | ||
); | ||
@@ -415,6 +417,4 @@ } | ||
node.remove(); | ||
_prev._next = _end._prev = node; | ||
node._prev = _prev; | ||
node._next = _end; | ||
node.parentNode = this; | ||
setBoundaries(_prev, node, _end); | ||
break; | ||
@@ -468,8 +468,4 @@ } | ||
node.remove(); | ||
_prev._next = node; | ||
node._prev = _prev; | ||
const _end = getEnd(node); | ||
_next._prev = _end; | ||
_end._next = _next; | ||
node.parentNode = this; | ||
setBoundaries(_prev, node, _next); | ||
return replaced; | ||
@@ -476,0 +472,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import {getEnd} from './utils.js'; | ||
import {getEnd, setAdjacent} from './utils.js'; | ||
@@ -10,4 +10,3 @@ // https://dom.spec.whatwg.org/#concept-live-range | ||
const deleteContents = ({_start, _end}, fragment = null) => { | ||
_start._prev._next = _end._next; | ||
_end._next._prev = _start._prev; | ||
setAdjacent(_start._prev, _end._next); | ||
do { | ||
@@ -14,0 +13,0 @@ const end = getEnd(_start); |
@@ -15,2 +15,3 @@ import {Parser} from 'htmlparser2'; | ||
const textOnly = {test: () => false}; | ||
const voidElements = {test: () => true}; | ||
@@ -21,7 +22,15 @@ export const Mime = { | ||
ignoreCase: true, | ||
textOnly: /^(?:plaintext|script|style|textarea|title|xmp)$/i, | ||
voidElements: /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i | ||
}, | ||
'image/svg+xml': { | ||
docType: '', | ||
ignoreCase: false, | ||
textOnly, | ||
voidElements | ||
}, | ||
'text/xml': { | ||
docType: '<?xml version="1.0" encoding="utf-8"?>', | ||
ignoreCase: false, | ||
textOnly, | ||
voidElements | ||
@@ -32,2 +41,3 @@ }, | ||
ignoreCase: false, | ||
textOnly, | ||
voidElements | ||
@@ -38,22 +48,7 @@ }, | ||
ignoreCase: false, | ||
textOnly, | ||
voidElements | ||
}, | ||
'image/svg+xml': { | ||
docType: '', | ||
ignoreCase: false, | ||
voidElements | ||
} | ||
}; | ||
export const attributeChangedCallback = (element, name, oldValue, newValue) => { | ||
if ( | ||
element && | ||
element._custom && | ||
element.attributeChangedCallback && | ||
element.constructor.observedAttributes.includes(name) | ||
) { | ||
element.attributeChangedCallback(name, oldValue, newValue); | ||
} | ||
}; | ||
export const booleanAttribute = { | ||
@@ -112,2 +107,19 @@ get(element, name) { | ||
export const setAdjacent = (before, after) => { | ||
if (before) | ||
before._next = after; | ||
if (after) | ||
after._prev = before; | ||
}; | ||
export const setBoundaries = (before, current, after) => { | ||
if (before) | ||
before._next = current; | ||
current._prev = before; | ||
current = getEnd(current); | ||
current._next = after; | ||
if (after) | ||
after._prev = current; | ||
}; | ||
const HTML = 'text/html'; | ||
@@ -122,2 +134,4 @@ const VOID_SOURCE = Mime[HTML].voidElements.source.slice(4, -2); | ||
const {SVGElement} = document[DOM]; | ||
const {_customElements} = document; | ||
const {_active, _registry} = _customElements; | ||
let node = document.root || document.createElement('root'); | ||
@@ -137,2 +151,7 @@ let ownerSVGElement = null; | ||
} | ||
else if (_active && _registry.has(name)) { | ||
const {Class} = _registry.get(name); | ||
node = node.appendChild(new Class); | ||
return; | ||
} | ||
} | ||
@@ -142,3 +161,12 @@ node = node.appendChild(document.createElement(name)); | ||
onattribute(name, value) { | ||
node.setAttribute(name, value); | ||
if (isHTML && _active && name === 'is' && _registry.has(value)) { | ||
const {Class} = _registry.get(value); | ||
const custom = new Class; | ||
for (const attribute of node.attributes) | ||
custom.setAttributeNode(attribute); | ||
node.replaceWith(custom); | ||
node = custom; | ||
} | ||
else | ||
node.setAttribute(name, value); | ||
}, | ||
@@ -145,0 +173,0 @@ oncomment(data) { |
{ | ||
"name": "linkedom", | ||
"version": "0.1.33", | ||
"version": "0.1.34", | ||
"description": "A triple-linked lists based DOM implementation", | ||
@@ -5,0 +5,0 @@ "main": "./cjs/index.js", |
@@ -120,7 +120,7 @@ # 🔗 linkedom | ||
All nodes are linked on both side and all elements consist of 2 nodes, also linked in between. | ||
All nodes are linked on both sides, and all elements consist of 2 nodes, also linked in between. | ||
Attributes are always at the beginning of an element, while zero or more can be found before the end node. | ||
Attributes are always at the beginning of an element, while zero or more extra nodes can be found before the end. | ||
A fragment is a special element without boundaries or parent node. | ||
A fragment is a special element without boundaries, or parent node. | ||
@@ -158,3 +158,3 @@ ``` | ||
Moving *N* nodes from a container, either *Element* or *Fragment*, requires these steps: | ||
Moving *N* nodes from a container, being it either an *Element* or a *Fragment*, requires the following steps: | ||
@@ -166,6 +166,8 @@ * update the first *left* link of the moved segment | ||
As result, no array operations, no memory operations, everything is kept in sync by updating a few properties, so that removing `3714` sparse `<div>` elements, in a *12M* document, takes as little as *3ms*, while appending a whole fragment takes close to *0ms*. | ||
As result, there are no array operations, and no memory operations, and everything is kept in sync by updating a few properties, so that removing `3714` sparse `<div>` elements in a *12M* document, as example, takes as little as *3ms*, while appending a whole fragment takes close to *0ms*. | ||
This structure also allows developers to avoid issues such as "*Maximum call stack size exceeded*" or "*JavaScript heap out of memory*" crashes, thanks to its reduced usage of memory, zero stacks involved, hence scaling better from small, to very big, documents. | ||
Try `npm run benchmark:html` to see it yourself. | ||
This structure also allows programs to avoid issues such as "*Maximum call stack size exceeded*" <sup><sub>(basicHTML)</sub></sup>, or "*JavaScript heap out of memory*" crashes <sup><sub>(JSDOM)</sub></sup>, thanks to its reduced usage of memory and zero stacks involved, hence scaling better from small to very big documents. | ||
### Are *childNodes* and *children* always computed? | ||
@@ -172,0 +174,0 @@ |
1584533
6921
174