diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..de9f334 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,33 @@ +import PCancelable from 'p-cancelable'; + +export interface Options { + /** + The element that's expected to contain a match. + + @default document + */ + readonly target?: Element | Document; +} + +/** +Detect when an element is ready in the DOM. + +@param selector - [CSS selector.](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) +@returns The matching element. +*/ +export default function elementReady< + ElementName extends keyof HTMLElementTagNameMap +>( + selector: ElementName, + options?: Options +): PCancelable; +export default function elementReady< + ElementName extends keyof SVGElementTagNameMap +>( + selector: ElementName, + options?: Options +): PCancelable; +export default function elementReady( + selector: string, + options?: Options +): PCancelable; diff --git a/index.js b/index.js index 5e930df..f01cdcc 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,7 @@ const cleanCache = (target, selector) => { } }; -module.exports = (selector, options) => { +const elementReady = (selector, options) => { options = Object.assign({ target: document }, options); @@ -24,9 +24,9 @@ module.exports = (selector, options) => { let alreadyFound = false; const promise = new PCancelable((resolve, reject, onCancel) => { - let raf; + let rafId; onCancel(() => { - cancelAnimationFrame(raf); + cancelAnimationFrame(rafId); cleanCache(options.target, selector); }); @@ -39,7 +39,7 @@ module.exports = (selector, options) => { alreadyFound = true; cleanCache(options.target, selector); } else { - raf = requestAnimationFrame(check); + rafId = requestAnimationFrame(check); } })(); }); @@ -55,3 +55,6 @@ module.exports = (selector, options) => { return promise; }; + +module.exports = elementReady; +module.exports.default = elementReady; diff --git a/index.test-d.ts b/index.test-d.ts new file mode 100644 index 0000000..e055d39 --- /dev/null +++ b/index.test-d.ts @@ -0,0 +1,13 @@ +import {expectType} from 'tsd-check'; +import PCancelable from 'p-cancelable'; +import elementReady from '.'; + +const promise = elementReady('#unicorn'); +elementReady('#unicorn', {target: document}); +elementReady('#unicorn', {target: document.documentElement}); + +expectType>(promise); +expectType>(elementReady('div')); +expectType>(elementReady('text')); + +promise.cancel(); diff --git a/package.json b/package.json index bca1ca0..9ee8ee0 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,11 @@ "node": ">=6" }, "scripts": { - "test": "xo && ava" + "test": "xo && ava && tsd-check" }, "files": [ - "index.js" + "index.js", + "index.d.ts" ], "keywords": [ "browser", @@ -31,13 +32,14 @@ "check" ], "dependencies": { - "p-cancelable": "^0.4.1" + "p-cancelable": "^1.1.0" }, "devDependencies": { - "ava": "*", - "delay": "^2.0.0", - "jsdom": "^11.10.0", - "xo": "*" + "ava": "^1.3.1", + "delay": "^4.1.0", + "jsdom": "^14.0.0", + "tsd-check": "^0.3.0", + "xo": "^0.24.0" }, "xo": { "envs": [ diff --git a/test.js b/test.js index 5374c19..5a05953 100644 --- a/test.js +++ b/test.js @@ -2,7 +2,7 @@ import test from 'ava'; import jsdom from 'jsdom'; import delay from 'delay'; import PCancelable from 'p-cancelable'; -import m from '.'; +import elementReady from '.'; const dom = new jsdom.JSDOM(); global.window = dom.window; @@ -11,68 +11,68 @@ global.requestAnimationFrame = fn => setTimeout(fn, 16); global.cancelAnimationFrame = id => clearTimeout(id); test('check if element ready', async t => { - const elCheck = m('#unicorn'); + const elementCheck = elementReady('#unicorn'); delay(500).then(() => { - const el = document.createElement('p'); - el.id = 'unicorn'; - document.body.appendChild(el); + const element = document.createElement('p'); + element.id = 'unicorn'; + document.body.append(element); }); - const el = await elCheck; - t.is(el.id, 'unicorn'); + const element = await elementCheck; + t.is(element.id, 'unicorn'); }); test('check if element ready inside target', async t => { const target = document.createElement('p'); - const elCheck = m('#unicorn', { + const elCheck = elementReady('#unicorn', { target }); delay(500).then(() => { - const el = document.createElement('p'); - el.id = 'unicorn'; - target.appendChild(el); + const element = document.createElement('p'); + element.id = 'unicorn'; + target.append(element); }); - const el = await elCheck; - t.is(el.id, 'unicorn'); + const element = await elCheck; + t.is(element.id, 'unicorn'); }); test('check if different elements ready inside different targets with same selector', async t => { const target1 = document.createElement('p'); - const elCheck1 = m('.unicorn', { + const elementCheck1 = elementReady('.unicorn', { target: target1 }); const target2 = document.createElement('span'); - const elCheck2 = m('.unicorn', { + const elementCheck2 = elementReady('.unicorn', { target: target2 }); delay(500).then(() => { - const el1 = document.createElement('p'); - el1.id = 'unicorn1'; - el1.className = 'unicorn'; - target1.appendChild(el1); - - const el2 = document.createElement('span'); - el2.id = 'unicorn2'; - el2.className = 'unicorn'; - target2.appendChild(el2); + const element1 = document.createElement('p'); + element1.id = 'unicorn1'; + element1.className = 'unicorn'; + target1.append(element1); + + const element2 = document.createElement('span'); + element2.id = 'unicorn2'; + element2.className = 'unicorn'; + target2.append(element2); }); - const el1 = await elCheck1; - t.is(el1.id, 'unicorn1'); + const element1 = await elementCheck1; + t.is(element1.id, 'unicorn1'); - const el2 = await elCheck2; - t.is(el2.id, 'unicorn2'); + const element2 = await elementCheck2; + t.is(element2.id, 'unicorn2'); }); test('ensure only one promise is returned on multiple calls passing the same selector', t => { - const elCheck = m('#unicorn'); + const elementCheck = elementReady('#unicorn'); for (let i = 0; i <= 10; i++) { - if (m('#unicorn') !== elCheck) { + if (elementReady('#unicorn') !== elementCheck) { t.fail(); } } @@ -81,43 +81,43 @@ test('ensure only one promise is returned on multiple calls passing the same sel }); test('check if wait can be canceled', async t => { - const elCheck = m('#dofle'); + const elementCheck = elementReady('#dofle'); await delay(200); - elCheck.cancel(); + elementCheck.cancel(); await delay(500); - const el = document.createElement('p'); - el.id = 'dofle'; - document.body.appendChild(el); + const element = document.createElement('p'); + element.id = 'dofle'; + document.body.append(element); - await t.throws(elCheck, PCancelable.CancelError); + await t.throwsAsync(elementCheck, PCancelable.CancelError); }); test('ensure different promises are returned on second call with the same selector when first was canceled', async t => { - const elCheck1 = m('.unicorn'); + const elementCheck1 = elementReady('.unicorn'); - elCheck1.cancel(); + elementCheck1.cancel(); - const elCheck2 = m('.unicorn'); + const elementCheck2 = elementReady('.unicorn'); - await t.throws(elCheck1, PCancelable.CancelError); - t.not(elCheck1, elCheck2); + await t.throwsAsync(elementCheck1, PCancelable.CancelError); + t.not(elementCheck1, elementCheck2); }); test('ensure different promises are returned on second call with the same selector when first was found', async t => { const prependElement = () => { - const el = document.createElement('p'); - el.className = 'unicorn'; - document.body.prepend(el); - return el; + const element = document.createElement('p'); + element.className = 'unicorn'; + document.body.prepend(element); + return element; }; - t.is(prependElement(), await m('.unicorn')); + t.is(prependElement(), await elementReady('.unicorn')); document.querySelector('.unicorn').remove(); - t.is(prependElement(), await m('.unicorn')); + t.is(prependElement(), await elementReady('.unicorn')); document.querySelector('.unicorn').remove(); - t.is(prependElement(), await m('.unicorn')); + t.is(prependElement(), await elementReady('.unicorn')); });