Precise DOM morphing
morphing typescript dom
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Support inner morph (#28)

authored by

Joel Drapper and committed by
GitHub
bf208fb2 5d8e9762

+98 -54
+4 -1
dist/morphlex.d.ts
··· 12 12 beforePropertyUpdated?: (node: Node, propertyName: PropertyKey, newValue: unknown) => boolean; 13 13 afterPropertyUpdated?: (node: Node, propertyName: PropertyKey, previousValue: unknown) => void; 14 14 } 15 - export declare function morph(node: ChildNode, reference: ChildNode | string, options?: Options): void; 15 + export declare function morph(node: ChildNode, reference: ChildNode, options?: Options): void; 16 + export declare function morphInner(element: Element, reference: Element, options?: Options): void; 17 + export declare function morphFromString(node: ChildNode, reference: string, options?: Options): void; 18 + export declare function morphInnerFromString(element: Element, reference: string, options?: Options): void; 16 19 export {};
+43 -26
dist/morphlex.js
··· 1 1 export function morph(node, reference, options = {}) { 2 - if (typeof reference === "string") { 3 - const template = document.createElement("template"); 4 - template.innerHTML = reference.trim(); 5 - reference = template.content.firstChild; 6 - if (!reference) { 7 - throw new Error("[Morphlex] The string did not contain any nodes."); 8 - } 9 - } 10 - if (isElement(node)) { 11 - const originalAriaBusy = node.ariaBusy; 12 - node.ariaBusy = "true"; 13 - new Morph(options).morph([node, reference]); 14 - node.ariaBusy = originalAriaBusy; 15 - } else { 16 - new Morph(options).morph([node, reference]); 17 - } 2 + new Morph(options).morph([node, reference]); 3 + } 4 + export function morphInner(element, reference, options = {}) { 5 + new Morph(options).morphInner([element, reference]); 6 + } 7 + export function morphFromString(node, reference, options = {}) { 8 + morph(node, parseChildNodeFromString(reference), options); 9 + } 10 + export function morphInnerFromString(element, reference, options = {}) { 11 + morphInner(element, parseElementFromString(reference), options); 12 + } 13 + function parseElementFromString(string) { 14 + const node = parseChildNodeFromString(string); 15 + if (isElement(node)) return node; 16 + else throw new Error("[Morphlex] The string was not a valid HTML element."); 17 + } 18 + function parseChildNodeFromString(string) { 19 + const parser = new DOMParser(); 20 + const doc = parser.parseFromString(string, "text/html"); 21 + const firstChild = doc.body.firstChild; 22 + if (doc.childNodes.length === 1) return firstChild; 23 + else throw new Error("[Morphlex] The string was not a valid HTML node."); 18 24 } 19 25 class Morph { 20 26 #idMap; ··· 49 55 Object.freeze(this); 50 56 } 51 57 morph(pair) { 52 - if (isParentNodePair(pair)) this.#buildMaps(pair); 53 - this.#morphNode(pair); 58 + this.#withAriaBusy(pair[0], () => { 59 + if (isParentNodePair(pair)) this.#buildMaps(pair); 60 + this.#morphNode(pair); 61 + }); 54 62 } 55 63 morphInner(pair) { 56 - if (isMatchingElementPair(pair)) { 57 - this.#buildMaps(pair); 58 - this.#morphMatchingElementContent(pair); 59 - } else { 60 - throw new Error("[Morphlex] You can only do an inner morph with matching elements."); 61 - } 64 + this.#withAriaBusy(pair[0], () => { 65 + if (isMatchingElementPair(pair)) { 66 + this.#buildMaps(pair); 67 + this.#morphMatchingElementContent(pair); 68 + } else { 69 + throw new Error("[Morphlex] You can only do an inner morph with matching elements."); 70 + } 71 + }); 72 + } 73 + #withAriaBusy(node, block) { 74 + if (isElement(node)) { 75 + const originalAriaBusy = node.ariaBusy; 76 + node.ariaBusy = "true"; 77 + block(); 78 + node.ariaBusy = originalAriaBusy; 79 + } else block(); 62 80 } 63 81 #buildMaps([node, reference]) { 64 82 this.#mapIdSets(node); ··· 326 344 return isElement(a) && isElement(b) && a.localName === b.localName; 327 345 } 328 346 function isParentNodePair(pair) { 329 - const [a, b] = pair; 330 - return isParentNode(a) && isParentNode(b); 347 + return isParentNode(pair[0]) && isParentNode(pair[1]); 331 348 } 332 349 function isElement(node) { 333 350 return node.nodeType === 1;
+51 -27
src/morphlex.ts
··· 49 49 afterPropertyUpdated?: (node: Node, propertyName: PropertyKey, previousValue: unknown) => void; 50 50 } 51 51 52 - export function morph(node: ChildNode, reference: ChildNode | string, options: Options = {}): void { 53 - if (typeof reference === "string") { 54 - const template = document.createElement("template"); 55 - template.innerHTML = reference.trim(); 56 - reference = template.content.firstChild as ChildNode; 57 - if (!reference) { 58 - throw new Error("[Morphlex] The string did not contain any nodes."); 59 - } 60 - } 52 + export function morph(node: ChildNode, reference: ChildNode, options: Options = {}): void { 53 + new Morph(options).morph([node, reference]); 54 + } 55 + 56 + export function morphInner(element: Element, reference: Element, options: Options = {}): void { 57 + new Morph(options).morphInner([element, reference]); 58 + } 61 59 62 - if (isElement(node)) { 63 - const originalAriaBusy = node.ariaBusy; 64 - node.ariaBusy = "true"; 65 - new Morph(options).morph([node, reference]); 66 - node.ariaBusy = originalAriaBusy; 67 - } else { 68 - new Morph(options).morph([node, reference]); 69 - } 60 + export function morphFromString(node: ChildNode, reference: string, options: Options = {}): void { 61 + morph(node, parseChildNodeFromString(reference), options); 62 + } 63 + 64 + export function morphInnerFromString(element: Element, reference: string, options: Options = {}): void { 65 + morphInner(element, parseElementFromString(reference), options); 66 + } 67 + 68 + function parseElementFromString(string: string): Element { 69 + const node = parseChildNodeFromString(string); 70 + 71 + if (isElement(node)) return node; 72 + else throw new Error("[Morphlex] The string was not a valid HTML element."); 73 + } 74 + 75 + function parseChildNodeFromString(string: string): ChildNode { 76 + const parser = new DOMParser(); 77 + const doc = parser.parseFromString(string, "text/html"); 78 + const firstChild = doc.body.firstChild; 79 + 80 + if (doc.childNodes.length === 1) return firstChild as ChildNode; 81 + else throw new Error("[Morphlex] The string was not a valid HTML node."); 70 82 } 71 83 72 84 class Morph { ··· 107 119 } 108 120 109 121 morph(pair: NodeReferencePair<ChildNode>): void { 110 - if (isParentNodePair(pair)) this.#buildMaps(pair); 111 - this.#morphNode(pair); 122 + this.#withAriaBusy(pair[0], () => { 123 + if (isParentNodePair(pair)) this.#buildMaps(pair); 124 + this.#morphNode(pair); 125 + }); 112 126 } 113 127 114 128 morphInner(pair: NodeReferencePair<Element>): void { 115 - if (isMatchingElementPair(pair)) { 116 - this.#buildMaps(pair); 117 - this.#morphMatchingElementContent(pair); 118 - } else { 119 - throw new Error("[Morphlex] You can only do an inner morph with matching elements."); 120 - } 129 + this.#withAriaBusy(pair[0], () => { 130 + if (isMatchingElementPair(pair)) { 131 + this.#buildMaps(pair); 132 + this.#morphMatchingElementContent(pair); 133 + } else { 134 + throw new Error("[Morphlex] You can only do an inner morph with matching elements."); 135 + } 136 + }); 137 + } 138 + 139 + #withAriaBusy(node: Node, block: () => void): void { 140 + if (isElement(node)) { 141 + const originalAriaBusy = node.ariaBusy; 142 + node.ariaBusy = "true"; 143 + block(); 144 + node.ariaBusy = originalAriaBusy; 145 + } else block(); 121 146 } 122 147 123 148 #buildMaps([node, reference]: NodeReferencePair<ParentNode>): void { ··· 451 476 } 452 477 453 478 function isParentNodePair(pair: NodeReferencePair<Node>): pair is NodeReferencePair<ParentNode> { 454 - const [a, b] = pair; 455 - return isParentNode(a) && isParentNode(b); 479 + return isParentNode(pair[0]) && isParentNode(pair[1]); 456 480 } 457 481 458 482 function isElement(node: Node): node is Element;