2
0
mirror of https://github.com/iconify/iconify.git synced 2024-11-09 23:00:56 +00:00

Rebuild observer in SVG framework. Now it supports multiple root nodes

This commit is contained in:
Vjacheslav Trushkin 2020-08-04 18:30:24 +03:00
parent 0266760091
commit 7645047431
22 changed files with 635 additions and 309 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@iconify/iconify-browser-tests", "name": "@iconify/iconify-browser-tests",
"version": "2.0.0-beta.3", "version": "2.0.0-beta.4",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -11,9 +11,9 @@
"dev": true "dev": true
}, },
"@iconify/core": { "@iconify/core": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.3",
"resolved": "https://registry.npmjs.org/@iconify/core/-/core-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/@iconify/core/-/core-1.0.0-beta.3.tgz",
"integrity": "sha512-PIPWM7Z0R8CClnl9kx8c3G8qe4gF+WGvro6IOcYRLw5YsVt0JyagGh5MgTHH8yfhMWlpAgiiROm65tJpKHctQw==", "integrity": "sha512-VqdW1vu7uWBI4QhOijVbT4vxDUKLjGNOye7SbJxvB2XQpJIQe9ZKnNqIyX1fO1olSV+Zf4Y1CQhWjw/sOxW+sQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@cyberalien/redundancy": "^1.0.0", "@cyberalien/redundancy": "^1.0.0",
@ -22,9 +22,9 @@
} }
}, },
"@iconify/iconify": { "@iconify/iconify": {
"version": "2.0.0-beta.3", "version": "2.0.0-beta.4",
"resolved": "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-beta.3.tgz", "resolved": "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-beta.4.tgz",
"integrity": "sha512-s+R3tKl+5+21Foj/JtODPtoAZmxCBt+NLTS0oTwDaibl8VFkomoPDuMPdT+twSVD7iJG81+MqERr0zTk8HyFfg==", "integrity": "sha512-/uUDM3D2rYWa5JmfC9DIfoznGbodBwwsMcpMOyG5xx5vgMZzfJYrgBWA1kCT+ec5NJ3bcH2etZBzkLOVS3sJAA==",
"dev": true "dev": true
}, },
"@iconify/types": { "@iconify/types": {

View File

@ -3,7 +3,7 @@
"private": true, "private": true,
"description": "Browser tests for @iconify/iconify package", "description": "Browser tests for @iconify/iconify package",
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)", "author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
"version": "2.0.0-beta.3", "version": "2.0.0-beta.4",
"license": "(Apache-2.0 OR GPL-2.0)", "license": "(Apache-2.0 OR GPL-2.0)",
"bugs": "https://github.com/iconify/iconify/issues", "bugs": "https://github.com/iconify/iconify/issues",
"homepage": "https://iconify.design/", "homepage": "https://iconify.design/",
@ -19,8 +19,8 @@
}, },
"devDependencies": { "devDependencies": {
"@cyberalien/redundancy": "^1.0.0", "@cyberalien/redundancy": "^1.0.0",
"@iconify/core": "^1.0.0-beta.2", "@iconify/core": "^1.0.0-beta.3",
"@iconify/iconify": "^2.0.0-beta.3", "@iconify/iconify": "^2.0.0-beta.4",
"@iconify/types": "^1.0.3", "@iconify/types": "^1.0.3",
"@rollup/plugin-buble": "^0.21.1", "@rollup/plugin-buble": "^0.21.1",
"@rollup/plugin-commonjs": "^11.0.2", "@rollup/plugin-commonjs": "^11.0.2",

View File

@ -1,11 +1,10 @@
import mocha from 'mocha'; import mocha from 'mocha';
import chai from 'chai'; import chai from 'chai';
import { getNode } from './node'; import { getNode, setRoot } from './node';
import { setRoot } from '@iconify/iconify/lib/modules/root'; import { listRootNodes } from '@iconify/iconify/lib/modules/root';
import { import {
initObserver, initObserver,
isObserverPaused,
pauseObserver, pauseObserver,
} from '@iconify/iconify/lib/modules/observer'; } from '@iconify/iconify/lib/modules/observer';
@ -16,28 +15,40 @@ describe('Testing observer creation', () => {
const node = getNode('observer-creation'); const node = getNode('observer-creation');
setRoot(node); setRoot(node);
// Get node
const list = listRootNodes();
expect(list.length).to.be.equal(1);
const item = list[0];
expect(item.node).to.be.equal(node);
expect(item.observer).to.be.equal(void 0);
// Do test
let counter = 0; let counter = 0;
node.innerHTML = '<div></div><ul><li>test</li><li>test2</li></ul>'; node.innerHTML = '<div></div><ul><li>test</li><li>test2</li></ul>';
initObserver((root) => { initObserver((root) => {
expect(root).to.be.equal(node); expect(root.node).to.be.equal(node);
counter++; counter++;
// Should be called only once // Should be called only once
expect(counter).to.be.equal(1); expect(counter).to.be.equal(1);
expect(isObserverPaused()).to.be.equal(false); // Check if observer is paused
expect(item.observer).to.not.be.equal(void 0);
expect(item.observer.paused).to.be.equal(0);
// Pause observer // Pause observer
pauseObserver(); pauseObserver();
expect(isObserverPaused()).to.be.equal(true); expect(item.observer.paused).to.be.equal(1);
done(); done();
}); });
// Add few nodes to trigger observer // Add few nodes to trigger observer
expect(isObserverPaused()).to.be.equal(false); expect(item.observer).to.not.be.equal(void 0);
expect(item.observer.paused).to.be.equal(0);
node.querySelector('div').innerHTML = node.querySelector('div').innerHTML =
'<span class="test">Some text</span><i>!</i>'; '<span class="test">Some text</span><i>!</i>';
}); });

View File

@ -1,14 +1,12 @@
import mocha from 'mocha'; import mocha from 'mocha';
import chai from 'chai'; import chai from 'chai';
import { getNode } from './node'; import { getNode, setRoot } from './node';
import { elementFinderProperty } from '@iconify/iconify/lib/modules/element'; import { elementFinderProperty } from '@iconify/iconify/lib/modules/element';
import { setRoot } from '@iconify/iconify/lib/modules/root';
import { import {
initObserver, initObserver,
pauseObserver, pauseObserver,
resumeObserver, resumeObserver,
isObserverPaused,
} from '@iconify/iconify/lib/modules/observer'; } from '@iconify/iconify/lib/modules/observer';
const expect = chai.expect; const expect = chai.expect;
@ -24,7 +22,7 @@ describe('Testing observer with DOM manipulation', () => {
node.innerHTML = node.innerHTML =
'<div></div><ul><li>test</li><li>test2</li></ul><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" role="img" width="1em" height="1em" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg); vertical-align: -0.125em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi-home" data-inline="false" class="iconify"><path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"></path></svg>'; '<div></div><ul><li>test</li><li>test2</li></ul><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" role="img" width="1em" height="1em" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg); vertical-align: -0.125em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi-home" data-inline="false" class="iconify"><path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"></path></svg>';
initObserver((root) => { initObserver((root) => {
expect(root).to.be.equal(node); expect(root.node).to.be.equal(node);
expect(waitingCallback).to.be.equal(true); expect(waitingCallback).to.be.equal(true);
counter++; counter++;
@ -108,7 +106,6 @@ describe('Testing observer with DOM manipulation', () => {
}); });
// Add few nodes to trigger observer // Add few nodes to trigger observer
expect(isObserverPaused()).to.be.equal(false);
node.querySelector('div').innerHTML = node.querySelector('div').innerHTML =
'<span class="test">Some text</span><i>!</i>'; '<span class="test">Some text</span><i>!</i>';
}); });

View File

@ -1,13 +1,14 @@
import mocha from 'mocha'; import mocha from 'mocha';
import chai from 'chai'; import chai from 'chai';
import { getNode } from './node'; import { getNode, setRoot } from './node';
import { addFinder } from '@iconify/iconify/lib/modules/finder'; import { addFinder } from '@iconify/iconify/lib/modules/finder';
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify'; import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify';
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon'; import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon';
import { getStorage, addIconSet } from '@iconify/core/lib/storage'; import { getStorage, addIconSet } from '@iconify/core/lib/storage';
import { setRoot, getRootNodes } from '@iconify/iconify/lib/modules/root'; import { listRootNodes } from '@iconify/iconify/lib/modules/root';
import { scanDOM } from '@iconify/iconify/lib/modules/scanner'; import { scanDOM, scanElement } from '@iconify/iconify/lib/modules/scanner';
import { removeObservedNode } from '@iconify/iconify/lib/modules/observer';
const expect = chai.expect; const expect = chai.expect;
@ -43,10 +44,12 @@ describe('Scanning DOM', () => {
}); });
// Sanity check before running tests // Sanity check before running tests
expect(getRootNodes()).to.be.eql([]); expect(listRootNodes()).to.be.eql([]);
it('Scan DOM with preloaded icons', () => { it('Scan DOM with preloaded icons', () => {
const node = getNode('scan-dom'); const node = getNode('scan-dom');
setRoot(node);
node.innerHTML = node.innerHTML =
'<div><p>Testing scanning DOM</p><ul>' + '<div><p>Testing scanning DOM</p><ul>' +
'<li>Valid icons:' + '<li>Valid icons:' +
@ -60,49 +63,66 @@ describe('Scanning DOM', () => {
'</ul></div>'; '</ul></div>';
// Scan node // Scan node
setRoot(node);
scanDOM(); scanDOM();
// Find elements // Find elements
const elements = node.querySelectorAll('svg.iconify'); const elements = node.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(4); expect(elements.length).to.be.equal(4);
// Make sure tempoary node was not added as root, but new root node was // Check root nodes list
expect(getRootNodes()).to.be.eql([ const nodes = listRootNodes();
{ expect(nodes.length).to.be.equal(1);
node: node, expect(nodes[0].node).to.be.equal(node);
temporary: false,
},
]);
}); });
it('Scan DOM with unattached root', () => { it('Scan DOM with unattached root', () => {
const fakeNode = getNode('scan-dom');
setRoot(fakeNode);
const node = document.createElement('div'); const node = document.createElement('div');
node.innerHTML = '<span class="iconify" data-icon="mdi:home"></span>'; node.innerHTML = '<span class="iconify" data-icon="mdi:home"></span>';
// Get old root nodes. It should not be empty because of previous test(s) // Check root nodes list
const oldRoot = getRootNodes(); let nodes = listRootNodes();
expect(nodes.length).to.be.equal(1);
expect(nodes[0].node).to.be.equal(fakeNode);
// Scan node // Scan node
scanDOM(node); scanElement(node);
// Find elements // Find elements
const elements = node.querySelectorAll('svg.iconify'); const elements = node.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(1); expect(elements.length).to.be.equal(1);
// Make sure tempoary node was not added as root // Make sure tempoary node was not added as root
expect(getRootNodes()).to.be.eql(oldRoot); nodes = listRootNodes();
expect(nodes.length).to.be.equal(1);
expect(nodes[0].node).to.be.equal(fakeNode);
}); });
it('Scan DOM with icon as root', () => { it('Scan DOM with icon as root', () => {
const fakeNode = getNode('scan-dom');
setRoot(fakeNode);
const node = document.createElement('span'); const node = document.createElement('span');
node.setAttribute('data-icon', 'mdi:home'); node.setAttribute('data-icon', 'mdi:home');
// Check root nodes list
let nodes = listRootNodes();
expect(nodes.length).to.be.equal(1);
expect(nodes[0].node).to.be.equal(fakeNode);
// Scan node // Scan node
scanDOM(node); scanElement(node);
// Check node // Check node
expect(node.tagName).to.be.equal('SPAN'); expect(node.tagName).to.be.equal('SPAN');
expect(node.innerHTML).to.be.equal(''); expect(node.innerHTML).to.be.equal('');
// Make sure tempoary node was not added as root
nodes = listRootNodes();
expect(nodes.length).to.be.equal(1);
expect(nodes[0].node).to.be.equal(fakeNode);
}); });
}); });

View File

@ -1,14 +1,18 @@
import mocha from 'mocha'; import mocha from 'mocha';
import chai from 'chai'; import chai from 'chai';
import { getNode } from './node'; import { getNode, setRoot } from './node';
import { addFinder } from '@iconify/iconify/lib/modules/finder'; import { addFinder } from '@iconify/iconify/lib/modules/finder';
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify'; import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify';
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon'; import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon';
import { getStorage, addIconSet } from '@iconify/core/lib/storage'; import { getStorage, addIconSet } from '@iconify/core/lib/storage';
import { setRoot, getRootNodes } from '@iconify/iconify/lib/modules/root'; import { listRootNodes } from '@iconify/iconify/lib/modules/root';
import { scanDOM } from '@iconify/iconify/lib/modules/scanner'; import { scanDOM } from '@iconify/iconify/lib/modules/scanner';
import { initObserver } from '@iconify/iconify/lib/modules/observer'; import {
initObserver,
observeNode,
removeObservedNode,
} from '@iconify/iconify/lib/modules/observer';
const expect = chai.expect; const expect = chai.expect;
@ -45,30 +49,38 @@ describe('Observe DOM', () => {
it('Basic test', (done) => { it('Basic test', (done) => {
const node = getNode('observe-dom'); const node = getNode('observe-dom');
const ignoredNode = getNode('observe-dom');
// Set root and init observer // Set root and init observer
setRoot(node); setRoot(node);
initObserver(scanDOM); initObserver(scanDOM);
// Test getRootNodes // Test listRootNodes
expect(getRootNodes()).to.be.eql([ const nodes = listRootNodes();
{ expect(nodes.length).to.be.equal(1);
node: node, expect(nodes[0].node).to.be.equal(node);
temporary: false, expect(nodes[0].temporary).to.be.equal(false);
},
]);
// Set HTML // Set HTML
node.innerHTML = node.innerHTML =
'<p>Testing observing DOM</p>' + '<p>Testing observing DOM</p>' +
'<span class="iconify" data-icon="mdi:home"></span>'; '<span class="iconify" data-icon="mdi:home"></span>';
ignoredNode.innerHTML =
'<p>This node should be ignored</p>' +
'<span class="iconify" data-icon="mdi:home"></span>';
// Test nodes // Test nodes
setTimeout(() => { setTimeout(() => {
// Find elements // Find elements
const elements = node.querySelectorAll('svg.iconify'); let elements = node.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(1); expect(elements.length).to.be.equal(1);
elements = ignoredNode.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(
0,
'Looks like document.body is observed!'
);
// Test for "home" icon contents // Test for "home" icon contents
expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(-1); expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(-1);
@ -114,4 +126,112 @@ describe('Observe DOM', () => {
}, 100); }, 100);
}, 100); }, 100);
}); });
it('Adding node to observe', (done) => {
const baseNode = getNode('observe-dom');
const node = getNode('observe-dom');
// Set root and init observer
setRoot(baseNode);
initObserver(scanDOM);
// Test listRootNodes
let nodes = listRootNodes();
expect(nodes.length).to.be.equal(1);
expect(nodes[0].node).to.be.equal(baseNode);
expect(nodes[0].temporary).to.be.equal(false);
// Observe another node
observeNode(node);
nodes = listRootNodes();
expect(nodes.length).to.be.equal(2);
expect(nodes[0].node).to.be.equal(baseNode);
expect(nodes[0].temporary).to.be.equal(false);
expect(nodes[1].node).to.be.equal(node);
expect(nodes[1].temporary).to.be.equal(false);
// Set HTML
baseNode.innerHTML =
'<p>Testing observing 2 nodes (1)</p>' +
'<span class="iconify" data-icon="mdi:home"></span>';
node.innerHTML =
'<p>Testing observing 2 nodes (2)</p>' +
'<span class="iconify" data-icon="mdi:home"></span>';
// Test nodes
setTimeout(() => {
// Find elements
let elements = node.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(1);
elements = baseNode.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(1);
// Test for "home" icon contents
expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(-1);
expect(baseNode.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(
-1
);
done();
}, 100);
});
it('Stop observing node', (done) => {
const baseNode = getNode('observe-dom');
const node = getNode('observe-dom');
// Set root and init observer
setRoot(baseNode);
initObserver(scanDOM);
// Test listRootNodes
let nodes = listRootNodes();
expect(nodes.length).to.be.equal(1);
expect(nodes[0].node).to.be.equal(baseNode);
expect(nodes[0].temporary).to.be.equal(false);
// Observe another node
observeNode(node);
nodes = listRootNodes();
expect(nodes.length).to.be.equal(2);
expect(nodes[0].node).to.be.equal(baseNode);
expect(nodes[0].temporary).to.be.equal(false);
expect(nodes[1].node).to.be.equal(node);
expect(nodes[1].temporary).to.be.equal(false);
// Stop observing baseNode
removeObservedNode(baseNode);
nodes = listRootNodes();
expect(nodes.length).to.be.equal(1);
expect(nodes[0].node).to.be.equal(node);
expect(nodes[0].temporary).to.be.equal(false);
// Set HTML
baseNode.innerHTML =
'<p>Testing observing 2 nodes (1)</p>' +
'<span class="iconify" data-icon="mdi:home"></span>';
node.innerHTML =
'<p>Testing observing 2 nodes (2)</p>' +
'<span class="iconify" data-icon="mdi:home"></span>';
// Test nodes
setTimeout(() => {
// Find elements
let elements = node.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(1);
elements = baseNode.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(0);
// Test for "home" icon contents
expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(-1);
expect(baseNode.innerHTML.indexOf('20v-6h4v6h5v')).to.be.equal(-1);
done();
}, 100);
});
}); });

View File

@ -1,7 +1,7 @@
import mocha from 'mocha'; import mocha from 'mocha';
import chai from 'chai'; import chai from 'chai';
import { getNode } from './node'; import { getNode, setRoot } from './node';
import { addFinder } from '@iconify/iconify/lib/modules/finder'; import { addFinder } from '@iconify/iconify/lib/modules/finder';
import { FakeData, setFakeData, prepareQuery, sendQuery } from './fake-api'; import { FakeData, setFakeData, prepareQuery, sendQuery } from './fake-api';
import { API } from '@iconify/core/lib/api/'; import { API } from '@iconify/core/lib/api/';
@ -10,8 +10,8 @@ import { setAPIConfig } from '@iconify/core/lib/api/config';
import { coreModules } from '@iconify/core/lib/modules'; import { coreModules } from '@iconify/core/lib/modules';
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify'; import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify';
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon'; import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon';
import { setRoot, getRootNodes } from '@iconify/iconify/lib/modules/root'; import { listRootNodes } from '@iconify/iconify/lib/modules/root';
import { scanDOM } from '@iconify/iconify/lib/modules/scanner'; import { scanDOM, scanElement } from '@iconify/iconify/lib/modules/scanner';
const expect = chai.expect; const expect = chai.expect;
@ -86,6 +86,7 @@ describe('Scanning DOM with API', () => {
setFakeData(provider, prefix2, data2); setFakeData(provider, prefix2, data2);
const node = getNode('scan-dom'); const node = getNode('scan-dom');
node.innerHTML = node.innerHTML =
'<div><p>Testing scanning DOM with API</p><ul>' + '<div><p>Testing scanning DOM with API</p><ul>' +
'<li>Inline icons:' + '<li>Inline icons:' +
@ -118,13 +119,11 @@ describe('Scanning DOM with API', () => {
setRoot(node); setRoot(node);
scanDOM(); scanDOM();
// Test getRootNodes // Test listRootNodes
expect(getRootNodes()).to.be.eql([ const nodes = listRootNodes();
{ expect(nodes.length).to.be.equal(1);
node: node, expect(nodes[0].node).to.be.equal(node);
temporary: false, expect(nodes[0].temporary).to.be.equal(false);
},
]);
// First API response should have loaded // First API response should have loaded
setTimeout(() => { setTimeout(() => {
@ -253,13 +252,11 @@ describe('Scanning DOM with API', () => {
setRoot(node); setRoot(node);
scanDOM(); scanDOM();
// Test getRootNodes // Test listRootNodes
expect(getRootNodes()).to.be.eql([ const nodes = listRootNodes();
{ expect(nodes.length).to.be.equal(1);
node: node, expect(nodes[0].node).to.be.equal(node);
temporary: false, expect(nodes[0].temporary).to.be.equal(false);
},
]);
// Make sure no icons were rendered yet // Make sure no icons were rendered yet
const elements = node.querySelectorAll('svg.iconify'); const elements = node.querySelectorAll('svg.iconify');
@ -447,27 +444,22 @@ describe('Scanning DOM with API', () => {
// Set root node, test nodes list // Set root node, test nodes list
setRoot(fakeRoot); setRoot(fakeRoot);
expect(getRootNodes()).to.be.eql([
{ // Test listRootNodes
node: fakeRoot, let nodes = listRootNodes();
temporary: false, expect(nodes.length).to.be.equal(1);
}, expect(nodes[0].node).to.be.equal(fakeRoot);
]); expect(nodes[0].temporary).to.be.equal(false);
// Scan different node // Scan different node
scanDOM(node); scanElement(node);
// Test nodes list // Test listRootNodes
expect(getRootNodes()).to.be.eql([ nodes = listRootNodes();
{ expect(nodes.length).to.be.equal(2);
node: fakeRoot, expect(nodes[0].node).to.be.equal(fakeRoot);
temporary: false, expect(nodes[1].node).to.be.equal(node);
}, expect(nodes[1].temporary).to.be.equal(true);
{
node: node,
temporary: true,
},
]);
// API response should have loaded // API response should have loaded
setTimeout(() => { setTimeout(() => {
@ -478,12 +470,9 @@ describe('Scanning DOM with API', () => {
); );
// Test nodes list: temporary node should have been removed // Test nodes list: temporary node should have been removed
expect(getRootNodes()).to.be.eql([ nodes = listRootNodes();
{ expect(nodes.length).to.be.equal(1);
node: fakeRoot, expect(nodes[0].node).to.be.equal(fakeRoot);
temporary: false,
},
]);
// Done // Done
done(); done();

View File

@ -9,10 +9,16 @@ const expect = chai.expect;
const selector = const selector =
'span.iconify, i.iconify, span.iconify-inline, i.iconify-inline'; 'span.iconify, i.iconify, span.iconify-inline, i.iconify-inline';
const node = getNode('iconify-api'); // Do not observe document.body!
Iconify.setRoot(node); Iconify.stopObserving(document.body);
node.innerHTML = // Create node to observe
const observedNode = getNode('iconify-api');
const ignoredNode = getNode('iconify-api');
Iconify.observe(observedNode);
observedNode.innerHTML =
'<div><p>Testing Iconify with API</p><ul>' + '<div><p>Testing Iconify with API</p><ul>' +
'<li>Inline icons:' + '<li>Inline icons:' +
' <span class="iconify-inline" data-icon="mdi:home" style="color: red; box-shadow: 0 0 2px black;"></span>' + ' <span class="iconify-inline" data-icon="mdi:home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
@ -24,13 +30,23 @@ node.innerHTML =
'</li>' + '</li>' +
'</ul></div>'; '</ul></div>';
describe('Testing Iconify object', () => { ignoredNode.innerHTML =
'<div>This node should not have icons! <span class="iconify-inline" data-icon="mdi:home" style="color: red; box-shadow: 0 0 2px black;"></span>';
describe('Testing Iconify object with API', () => {
it('Rendering icons with API', () => { it('Rendering icons with API', () => {
// Icons should have been replaced by now // Icons should have been replaced by now
let list = node.querySelectorAll(selector); let list = observedNode.querySelectorAll(selector);
expect(list.length).to.be.equal(0); expect(list.length).to.be.equal(0);
list = node.querySelectorAll('svg.iconify'); list = observedNode.querySelectorAll('svg.iconify');
expect(list.length).to.be.equal(4); expect(list.length).to.be.equal(4);
// Icons in ignored node should not have been replaced
list = ignoredNode.querySelectorAll(selector);
expect(list.length).to.be.equal(1);
list = ignoredNode.querySelectorAll('svg.iconify');
expect(list.length).to.be.equal(0);
}); });
}); });

View File

@ -12,8 +12,11 @@ const selector =
const node1 = getNode('iconify-basic'); const node1 = getNode('iconify-basic');
const node2 = getNode('iconify-basic'); const node2 = getNode('iconify-basic');
// Do not observe document.body!
Iconify.stopObserving(document.body);
// Set root node // Set root node
Iconify.setRoot(node1); Iconify.observe(node1);
describe('Testing Iconify object', () => { describe('Testing Iconify object', () => {
const prefix = 'invalid-' + Date.now(); const prefix = 'invalid-' + Date.now();

View File

@ -12,8 +12,11 @@ const selector =
const node1 = getNode('iconify-basic'); const node1 = getNode('iconify-basic');
const node2 = getNode('iconify-basic'); const node2 = getNode('iconify-basic');
// Do not observe document.body!
Iconify.stopObserving(document.body);
// Set root node // Set root node
Iconify.setRoot(node1); Iconify.observe(node1);
describe('Testing Iconify object (without API)', () => { describe('Testing Iconify object (without API)', () => {
const prefix = 'invalid-' + Date.now(); const prefix = 'invalid-' + Date.now();

View File

@ -1,3 +1,7 @@
import { addRootNode, listRootNodes } from '@iconify/iconify/lib/modules/root';
import { removeObservedNode } from '@iconify/iconify/lib/modules/observer';
import { ObservedNode } from '@iconify/iconify/lib/modules/observed-node';
let counter = 0; let counter = 0;
/** /**
@ -12,3 +16,15 @@ export function getNode(prefix = 'test') {
document.getElementById('debug').appendChild(node); document.getElementById('debug').appendChild(node);
return node; return node;
} }
/**
* Set root node, remove old nodes
*/
export function setRoot(node: HTMLElement): ObservedNode {
listRootNodes().forEach((node) => {
if (typeof node.node !== 'function') {
removeObservedNode(node.node);
}
});
return addRootNode(node);
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@iconify/core", "name": "@iconify/core",
"version": "1.0.0-beta.2", "version": "1.0.0-beta.3",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -2,7 +2,7 @@
"name": "@iconify/core", "name": "@iconify/core",
"description": "Reusable files used by multiple Iconify packages", "description": "Reusable files used by multiple Iconify packages",
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)", "author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
"version": "1.0.0-beta.2", "version": "1.0.0-beta.3",
"license": "(Apache-2.0 OR GPL-2.0)", "license": "(Apache-2.0 OR GPL-2.0)",
"bugs": "https://github.com/iconify/iconify/issues", "bugs": "https://github.com/iconify/iconify/issues",
"homepage": "https://iconify.design/", "homepage": "https://iconify.design/",

View File

@ -21,13 +21,13 @@ Iconify SVG framework is designed to be as easy to use as possible.
Add this line to your page to load Iconify SVG framework (you can add it to `<head>` section of the page or before `</body>`): Add this line to your page to load Iconify SVG framework (you can add it to `<head>` section of the page or before `</body>`):
```html ```html
<script src="https://code.iconify.design/2/2.0.0-beta.3/iconify.min.js"></script> <script src="https://code.iconify.design/2/2.0.0-beta.4/iconify.min.js"></script>
``` ```
or or
```html ```html
<script src="https://cdn.jsdelivr.net/npm/@iconify/iconify@2.0.0-beta.3/dist/iconify.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@iconify/iconify@2.0.0-beta.4/dist/iconify.min.js"></script>
``` ```
or, if you are building a project with something like WebPack or Rollup, you can include the script by installing `@iconify/iconify` as a dependency and importing it in your project: or, if you are building a project with something like WebPack or Rollup, you can include the script by installing `@iconify/iconify` as a dependency and importing it in your project:

View File

@ -1,6 +1,6 @@
{ {
"name": "@iconify/iconify", "name": "@iconify/iconify",
"version": "2.0.0-beta.3", "version": "2.0.0-beta.4",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -31,9 +31,9 @@
"dev": true "dev": true
}, },
"@iconify/core": { "@iconify/core": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.3",
"resolved": "https://registry.npmjs.org/@iconify/core/-/core-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/@iconify/core/-/core-1.0.0-beta.3.tgz",
"integrity": "sha512-PIPWM7Z0R8CClnl9kx8c3G8qe4gF+WGvro6IOcYRLw5YsVt0JyagGh5MgTHH8yfhMWlpAgiiROm65tJpKHctQw==", "integrity": "sha512-VqdW1vu7uWBI4QhOijVbT4vxDUKLjGNOye7SbJxvB2XQpJIQe9ZKnNqIyX1fO1olSV+Zf4Y1CQhWjw/sOxW+sQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@cyberalien/redundancy": "^1.0.0", "@cyberalien/redundancy": "^1.0.0",

View File

@ -2,7 +2,7 @@
"name": "@iconify/iconify", "name": "@iconify/iconify",
"description": "Unified SVG framework with over 50,000 icons to choose from", "description": "Unified SVG framework with over 50,000 icons to choose from",
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)", "author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
"version": "2.0.0-beta.3", "version": "2.0.0-beta.4",
"license": "(Apache-2.0 OR GPL-2.0)", "license": "(Apache-2.0 OR GPL-2.0)",
"main": "./dist/iconify.min.js", "main": "./dist/iconify.min.js",
"types": "./dist/iconify.d.ts", "types": "./dist/iconify.d.ts",
@ -24,7 +24,7 @@
}, },
"devDependencies": { "devDependencies": {
"@cyberalien/redundancy": "^1.0.0", "@cyberalien/redundancy": "^1.0.0",
"@iconify/core": "^1.0.0-beta.2", "@iconify/core": "^1.0.0-beta.3",
"@iconify/types": "^1.0.3", "@iconify/types": "^1.0.3",
"@microsoft/api-extractor": "^7.9.2", "@microsoft/api-extractor": "^7.9.2",
"@rollup/plugin-buble": "^0.21.3", "@rollup/plugin-buble": "^0.21.3",

View File

@ -25,13 +25,16 @@ import {
initObserver, initObserver,
pauseObserver, pauseObserver,
resumeObserver, resumeObserver,
observeNode,
removeObservedNode,
} from './modules/observer'; } from './modules/observer';
import { scanDOM } from './modules/scanner'; import { scanDOM, scanElement } from './modules/scanner';
// Finders // Finders
import { addFinder } from './modules/finder'; import { addFinder } from './modules/finder';
import { finder as iconifyFinder } from './finders/iconify'; import { finder as iconifyFinder } from './finders/iconify';
import { setRoot } from './modules/root'; import { findRootNode, addRootNode, addBodyNode } from './modules/root';
import { onReady } from './modules/ready';
// import { finder as iconifyIconFinder } from './finders/iconify-icon'; // import { finder as iconifyIconFinder } from './finders/iconify-icon';
/** /**
@ -194,23 +197,28 @@ export interface IconifyGlobal {
/** /**
* Scan DOM * Scan DOM
*/ */
scanDOM: typeof scanDOM; scan: (root?: HTMLElement) => void;
/** /**
* Set root node * Add root node
*/ */
setRoot: (root: HTMLElement) => void; observe: (root: HTMLElement) => void;
/**
* Remove root node
*/
stopObserving: (root: HTMLElement) => void;
/* Observer */ /* Observer */
/** /**
* Pause observer * Pause observer
*/ */
pauseObserver: typeof pauseObserver; pauseObserver: (root?: HTMLElement) => void;
/** /**
* Resume observer * Resume observer
*/ */
resumeObserver: typeof resumeObserver; resumeObserver: (root?: HTMLElement) => void;
} }
/** /**
@ -296,30 +304,56 @@ export const IconifyCommon: IconifyGlobal = {
replaceIDs, replaceIDs,
// Scan DOM // Scan DOM
scanDOM, scan: (root?: HTMLElement) => {
if (root) {
scanElement(root);
} else {
scanDOM();
}
},
// Set root node // Add root node
setRoot: (root: HTMLElement) => { observe: (root: HTMLElement) => {
setRoot(root); observeNode(root);
},
// Restart observer // Remove root node
initObserver(scanDOM); stopObserving: (root: HTMLElement) => {
removeObservedNode(root);
// Scan DOM on next tick
setTimeout(scanDOM);
}, },
// Pause observer // Pause observer
pauseObserver, pauseObserver: (root?: HTMLElement) => {
if (root) {
const node = findRootNode(root);
if (node) {
pauseObserver(node);
}
} else {
pauseObserver();
}
},
// Resume observer // Resume observer
resumeObserver, resumeObserver: (root?: HTMLElement) => {
if (root) {
const node = findRootNode(root);
if (node) {
resumeObserver(node);
}
} else {
resumeObserver();
}
},
}; };
/** /**
* Initialise stuff * Initialise stuff
*/ */
if (typeof document !== 'undefined' && typeof window !== 'undefined') { if (typeof document !== 'undefined' && typeof window !== 'undefined') {
// Add document.body node
addBodyNode();
// Add finder modules // Add finder modules
// addFinder(iconifyIconFinder); // addFinder(iconifyIconFinder);
addFinder(iconifyFinder); addFinder(iconifyFinder);
@ -362,7 +396,7 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
// Load observer and scan DOM on next tick // Load observer and scan DOM on next tick
setTimeout(() => { setTimeout(() => {
scanDOM();
initObserver(scanDOM); initObserver(scanDOM);
scanDOM();
}); });
} }

View File

@ -0,0 +1,27 @@
/**
* Callback
*/
export type GetHTMLElement = () => HTMLElement | null;
/**
* Observed node type
*/
export interface ObservedNode {
// Node
node: HTMLElement | GetHTMLElement;
// True if node should be removed when all placeholders have been replaced
temporary?: boolean;
// Observer data
observer?: {
// MutationObserver instance
instance?: MutationObserver;
// Number instead of boolean to allow multiple pause/resume calls. Observer is resumed only when pause reaches 0
paused: number;
// Timer for pending scan
pendingScan?: number;
};
}

View File

@ -1,15 +1,17 @@
import { elementFinderProperty, IconifyElement } from './element'; import { elementFinderProperty, IconifyElement } from './element';
import { getRoot } from './root'; import { ObservedNode } from './observed-node';
import {
/** listRootNodes,
* MutationObserver instance, null until DOM is ready addRootNode,
*/ findRootNode,
let instance: MutationObserver | null = null; removeRootNode,
} from './root';
import { onReady } from './ready';
/** /**
* Observer callback function * Observer callback function
*/ */
export type ObserverCallback = (root: HTMLElement) => void; export type ObserverCallback = (item: ObservedNode) => void;
/** /**
* Callback * Callback
@ -25,32 +27,20 @@ const observerParams: MutationObserverInit = {
attributes: true, attributes: true,
}; };
/**
* Pause. Number instead of boolean to allow multiple pause/resume calls. Observer is resumed only when pause reaches 0
*/
let paused = 0;
/**
* Scan is pending when observer is resumed
*/
let scanPending = false;
/**
* Scan is already queued
*/
let scanQueued = false;
/** /**
* Queue DOM scan * Queue DOM scan
*/ */
function queueScan(): void { function queueScan(node: ObservedNode): void {
if (!scanQueued) { if (!node.observer) {
scanQueued = true; return;
setTimeout(() => { }
scanQueued = false;
scanPending = false; const observer = node.observer;
if (!observer.pendingScan) {
observer.pendingScan = setTimeout(() => {
delete observer.pendingScan;
if (callback) { if (callback) {
callback(getRoot()); callback(node);
} }
}); });
} }
@ -59,8 +49,13 @@ function queueScan(): void {
/** /**
* Check mutations for added nodes * Check mutations for added nodes
*/ */
function checkMutations(mutations: MutationRecord[]): void { function checkMutations(node: ObservedNode, mutations: MutationRecord[]): void {
if (!scanPending) { if (!node.observer) {
return;
}
const observer = node.observer;
if (!observer.pendingScan) {
for (let i = 0; i < mutations.length; i++) { for (let i = 0; i < mutations.length; i++) {
const item = mutations[i]; const item = mutations[i];
if ( if (
@ -71,9 +66,8 @@ function checkMutations(mutations: MutationRecord[]): void {
(item.target as IconifyElement)[elementFinderProperty] !== (item.target as IconifyElement)[elementFinderProperty] !==
void 0) void 0)
) { ) {
scanPending = true; if (!observer.paused) {
if (!paused) { queueScan(node);
queueScan();
} }
return; return;
} }
@ -84,108 +78,173 @@ function checkMutations(mutations: MutationRecord[]): void {
/** /**
* Start/resume observer * Start/resume observer
*/ */
function observe(): void { function observe(node: ObservedNode, root: HTMLElement): void {
if (instance) { node.observer.instance.observe(root, observerParams);
instance.observe(getRoot(), observerParams);
}
} }
/** /**
* Start mutation observer * Start mutation observer
*/ */
function startObserver(): void { function startObserver(node: ObservedNode): void {
if (instance !== null) { let observer = node.observer;
if (observer && observer.instance) {
// Already started
return; return;
} }
scanPending = true; const root = typeof node.node === 'function' ? node.node() : node.node;
instance = new MutationObserver(checkMutations); if (!root) {
observe(); // document.body is not available yet
if (!paused) { return;
queueScan(); }
if (!observer) {
observer = {
paused: 0,
};
node.observer = observer;
}
// Create new instance, observe
observer.instance = new MutationObserver(checkMutations.bind(null, node));
observe(node, root);
// Scan immediately
if (!observer.paused) {
queueScan(node);
} }
} }
// Fake interface to test old IE properties /**
interface OldIEElement extends HTMLElement { * Start all observers
doScroll?: boolean; */
function startObservers(): void {
listRootNodes().forEach(startObserver);
} }
/** /**
* Export module * Stop observer
*/ */
function stopObserver(node: ObservedNode): void {
if (!node.observer) {
return;
}
const observer = node.observer;
// Stop scan
if (observer.pendingScan) {
clearTimeout(observer.pendingScan);
delete observer.pendingScan;
}
// Disconnect observer
if (observer.instance) {
observer.instance.disconnect();
delete observer.instance;
}
}
/** /**
* Start observer when DOM is ready * Start observer when DOM is ready
*/ */
export function initObserver(cb: ObserverCallback): void { export function initObserver(cb: ObserverCallback): void {
callback = cb; let isRestart = callback !== void 0;
if (instance && !paused) { if (callback !== cb) {
// Restart observer // Change callback and stop all pending observers
instance.disconnect(); callback = cb;
observe(); if (isRestart) {
listRootNodes().forEach(stopObserver);
}
}
if (isRestart) {
// Restart instances
startObservers();
return; return;
} }
setTimeout(() => { // Start observers when document is ready
const doc = document; onReady(startObservers);
if (
doc.readyState === 'complete' ||
(doc.readyState !== 'loading' &&
!(doc.documentElement as OldIEElement).doScroll)
) {
startObserver();
} else {
doc.addEventListener('DOMContentLoaded', startObserver);
window.addEventListener('load', startObserver);
}
});
} }
/** /**
* Pause observer * Pause observer
*/ */
export function pauseObserver(root?: HTMLElement): void { export function pauseObserver(node?: ObservedNode): void {
if (root && getRoot() !== root) { (node ? [node] : listRootNodes()).forEach((node) => {
// Invalid root node if (!node.observer) {
return; node.observer = {
} paused: 1,
};
return;
}
paused++; const observer = node.observer;
if (paused > 1 || instance === null) { observer.paused++;
return; if (observer.paused > 1 || !observer.instance) {
} return;
}
// Check pending records, stop observer // Disconnect observer
checkMutations(instance.takeRecords()); const instance = observer.instance;
instance.disconnect(); // checkMutations(node, instance.takeRecords());
instance.disconnect();
});
} }
/** /**
* Resume observer * Resume observer
*/ */
export function resumeObserver(root?: HTMLElement): void { export function resumeObserver(observer?: ObservedNode): void {
if (root && getRoot() !== root) { (observer ? [observer] : listRootNodes()).forEach((node) => {
// Invalid root node if (!node.observer) {
return; // Start observer
} startObserver(node);
return;
if (!paused) {
return;
}
paused--;
if (!paused && instance) {
observe();
if (scanPending) {
queueScan();
} }
}
const observer = node.observer;
if (observer.paused) {
observer.paused--;
if (!observer.paused) {
// Start / resume
const root =
typeof node.node === 'function' ? node.node() : node.node;
if (!root) {
return;
} else if (observer.instance) {
observe(node, root);
} else {
startObserver(node);
}
}
}
});
} }
/** /**
* Check if observer is paused * Observe node
*/ */
export function isObserverPaused(): boolean { export function observeNode(
return paused > 0; root: HTMLElement,
autoRemove = false
): ObservedNode {
const node = addRootNode(root, autoRemove);
startObserver(node);
return node;
}
/**
* Remove observed node
*/
export function removeObservedNode(root: HTMLElement): void {
const node = findRootNode(root);
if (node) {
stopObserver(node);
removeRootNode(root);
}
} }

View File

@ -0,0 +1,21 @@
// Fake interface to test old IE properties
interface OldIEElement extends HTMLElement {
doScroll?: boolean;
}
/**
* Execute function when DOM is ready
*/
export function onReady(callback): void {
const doc = document;
if (
doc.readyState === 'complete' ||
(doc.readyState !== 'loading' &&
!(doc.documentElement as OldIEElement).doScroll)
) {
callback();
} else {
doc.addEventListener('DOMContentLoaded', callback);
window.addEventListener('load', callback);
}
}

View File

@ -1,80 +1,77 @@
// Default root element import { ObservedNode } from './observed-node';
let root: HTMLElement;
// Interface for extra root nodes
export interface ExtraRootNode {
node: HTMLElement;
temporary: boolean; // True if node should be removed when all placeholders have been replaced
}
// Additional root elements
let customRoot: ExtraRootNode[] = [];
/** /**
* Get root element * List of root nodes
*/ */
export function getRoot(): HTMLElement { let nodes: ObservedNode[] = [];
return root ? root : (document.querySelector('body') as HTMLElement);
}
/** /**
* Set root element * Find node
*/ */
export function setRoot(node: HTMLElement): void { export function findRootNode(node: HTMLElement): ObservedNode | undefined {
root = node; for (let i = 0; i < nodes.length; i++) {
const item = nodes[i];
let root = typeof item.node === 'function' ? item.node() : item.node;
if (root === node) {
return item;
}
}
} }
/** /**
* Add extra root node * Add extra root node
*/ */
export function addRoot(node: HTMLElement, autoRemove = false): ExtraRootNode { export function addRootNode(
if (root === node) { root: HTMLElement,
return { autoRemove = false
node: root, ): ObservedNode {
temporary: false, let node = findRootNode(root);
}; if (node) {
} // Node already exist: switch type if needed
if (node.temporary) {
for (let i = 0; i < customRoot.length; i++) { node.temporary = autoRemove;
const item = customRoot[i];
if (item.node === node) {
// Found matching node
if (!autoRemove && item.temporary) {
// Change to permanent
item.temporary = false;
}
return item;
} }
return node;
} }
// Add item // Create item, add it to list, start observer
const item = { node = {
node, node: root,
temporary: autoRemove, temporary: autoRemove,
}; };
customRoot.push(item); nodes.push(node);
return item; return node;
}
/**
* Add document.body node
*/
export function addBodyNode(): ObservedNode {
if (document.body) {
return addRootNode(document.body);
}
nodes.push({
node: () => {
return document.body;
},
});
} }
/** /**
* Remove root node * Remove root node
*/ */
export function removeRoot(node: HTMLElement): void { export function removeRootNode(root: HTMLElement): void {
customRoot = customRoot.filter((item) => item.node !== node); nodes = nodes.filter((node) => {
const element =
typeof node.node === 'function' ? node.node() : node.node;
return root !== element;
});
} }
/** /**
* Get all root nodes * Get list of root nodes
*/ */
export function getRootNodes(): ExtraRootNode[] { export function listRootNodes(): ObservedNode[] {
return (root return nodes;
? [
{
node: root,
temporary: false,
},
]
: []
).concat(customRoot);
} }

View File

@ -5,14 +5,14 @@ import { FullIconifyIcon } from '@iconify/core/lib/icon';
import { findPlaceholders } from './finder'; import { findPlaceholders } from './finder';
import { IconifyElementData, elementDataProperty } from './element'; import { IconifyElementData, elementDataProperty } from './element';
import { renderIcon } from './render'; import { renderIcon } from './render';
import { pauseObserver, resumeObserver } from './observer'; import { ObservedNode } from './observed-node';
import { import {
getRoot, pauseObserver,
getRootNodes, resumeObserver,
addRoot, removeObservedNode,
removeRoot, observeNode,
ExtraRootNode, } from './observer';
} from './root'; import { findRootNode, addRootNode, listRootNodes } from './root';
/** /**
* Flag to avoid scanning DOM too often * Flag to avoid scanning DOM too often
@ -51,10 +51,29 @@ const compareIcons = (
); );
}; };
/**
* Scan node for placeholders
*/
export function scanElement(root: HTMLElement): void {
// Add temporary node
let node = findRootNode(root);
if (!node) {
scanDOM(
{
node: root,
temporary: true,
},
true
);
} else {
scanDOM(node);
}
}
/** /**
* Scan DOM for placeholders * Scan DOM for placeholders
*/ */
export function scanDOM(customRoot?: HTMLElement): void { export function scanDOM(node?: ObservedNode, addTempNode = false): void {
scanQueued = false; scanQueued = false;
// List of icons to load: [provider][prefix][name] = boolean // List of icons to load: [provider][prefix][name] = boolean
@ -63,18 +82,9 @@ export function scanDOM(customRoot?: HTMLElement): void {
Record<string, Record<string, boolean>> Record<string, Record<string, boolean>>
> = Object.create(null); > = Object.create(null);
// Add temporary root node // Get placeholders
let customRootItem: ExtraRootNode; (node ? [node] : listRootNodes()).forEach((node) => {
if (customRoot) { const root = typeof node.node === 'function' ? node.node() : node.node;
customRootItem = addRoot(customRoot, true);
}
// Get root node and placeholders
const rootNodes: ExtraRootNode[] = customRoot
? [customRootItem]
: getRootNodes();
rootNodes.forEach((rootItem) => {
const root = rootItem.node;
if (!root || !root.querySelectorAll) { if (!root || !root.querySelectorAll) {
return; return;
@ -121,9 +131,9 @@ export function scanDOM(customRoot?: HTMLElement): void {
// Check icon // Check icon
const storage = getStorage(provider, prefix); const storage = getStorage(provider, prefix);
if (storage.icons[name] !== void 0) { if (storage.icons[name] !== void 0) {
// Icon exists - replace placeholder // Icon exists - pause observer before replacing placeholder
if (!paused && !rootItem.temporary) { if (!paused && node.observer) {
pauseObserver(root); pauseObserver(node);
paused = true; paused = true;
} }
@ -178,13 +188,16 @@ export function scanDOM(customRoot?: HTMLElement): void {
hasPlaceholders = true; hasPlaceholders = true;
}); });
// Remove temporay node // Node stuff
if (rootItem.temporary && !hasPlaceholders) { if (node.temporary && !hasPlaceholders) {
removeRoot(root); // Remove temporary node
} removeObservedNode(root);
} else if (addTempNode && hasPlaceholders) {
if (paused && !rootItem.temporary) { // Add new temporary node
resumeObserver(root); observeNode(root, true);
} else if (paused && node.observer) {
// Resume observer
resumeObserver(node);
} }
}); });