diff --git a/README.md b/README.md index 7cb7e3c..b41f7b4 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,9 @@ - [el-is](#el-is) - [Install](#install) + - [Requirements](#requirements) - [Usage](#usage) - [Usage Notes](#usage-notes) - - [API](#api) - - [elIs](#elis) - - [Type parameters](#type-parameters) - - [Parameters](#parameters) - - [Returns](#returns) ## Install @@ -16,29 +12,33 @@ npm install @michaelallenwarner/el-is ``` +## Requirements + +- TypeScript >= 5.0 (assuming you're using TypeScript) + ## Usage ```ts import { elIs } from '@michaelallenwarner/el-is'; const htmlElement = document.querySelector('section'); // `HTMLElement | null` -if (htmlElement && elIs(htmlElement, 'SECTION')) { +if (elIs(htmlElement, 'SECTION')) { // `htmlElement` now has type `HTMLElement & { tagName: 'SECTION' }` } const mathMlElement = document.querySelector('mrow'); // `MathMLElement | null` -if (mathMlElement && elIs(mathMlElement, 'mrow')) { +if (elIs(mathMlElement, 'mrow')) { // `mathMlElement` now has type `MathMLElement & { tagName: 'mrow' }` } const element = document.querySelector('.svg-path-element'); // `Element | null` -if (element && elIs(element, 'path')) { +if (elIs(element, 'path')) { // `element` now has type `Element & { tagName: 'path' }` /* But probably just do `if (element instanceof SVGPathElement)` instead! Also, although `Element` will work as a type for the first parameter, - it's better to narrow it first to `HTMLElement`, `MathMLElement`, or `SVGElement`. + it's advisable to narrow it first to `HTMLElement`, `MathMLElement`, or `SVGElement`. See Usage Notes below. */ @@ -47,43 +47,21 @@ if (element && elIs(element, 'path')) { ## Usage Notes -The `elIs()` function takes a DOM element (`el`) and a string-literal (`tagName`) and returns the `boolean` result of a simple `el.tagName === tagName` check, but with some TypeScript niceties described below. +The `elIs()` function takes two arguments—a maybe-`null` DOM element (`el`) and a string literal (`tagName`)—and returns the `boolean` result of a simple `el?.tagName === tagName` check, but with some TypeScript niceties described below. -But first, a quick warning: _**you probably should only use this function if the element-type you're checking against doesn't have its own dedicated interface!**_ For example, if you want to check whether `el` is a link, I can't think of any reason not to just do `if (el instanceof HTMLAnchorElement)`, so that the type-narrowing gives you all the properties and methods that come with the more specific interface (like `HTMLAnchorElement.href`). The `elIs()` function is really for checking against element-types that _don't_ have their own interfaces, like `
`. +But first, a quick warning: _**you probably should only use this function if the kind of element you're checking against doesn't have its own dedicated interface!**_ For example, if you want to check whether `el` is a link, I can't think of any reason not to just do `if (el instanceof HTMLAnchorElement)`, so that the type-narrowing gives you all the properties and methods that come with the more specific interface (like `HTMLAnchorElement.href`). The `elIs()` function is really for testing whether an element is of a kind that _doesn't_ have its own interface, like `
`. -With that out of the way, here are the TypeScript niceties you get with the `elIs()` function: +With that out of the way, here are the TypeScript niceties you get with `elIs()`: -- In the `true` case, the function's type-predicate narrows the type of `el` by intersecting its input-type with `{ tagName: T }`, where `T` is the string-literal type provided as the `tagName` argument. -- To prevent you from making typos (and to enable autocomplete), the `tagName` parameter is restricted to valid tag-name values for built-in elements of the `Element`-subtype that `el` is an instance of (if applicable). The supported `Element`-subtypes are `HTMLElement`, `SVGElement`, and `MathMLElement`. For example, if `el` is an instance of `HTMLElement`, then `tagName` must be a string literal that's the value of the `tagName` property of a built-in HTML element. If `el` is an `Element` but not an instance of one of those subtypes, then `tagName` is still restricted, but less so: it must be a string literal that's the value of the `tagName` property of a built-in HTML element, SVG element, or MathML element. (It's best if `el` is more specific than `Element`, though, so that you don't have to worry about confusing uppercase HTML tag-names like `'A'` with lowercase SVG or MathML tag-names like `'a'`.) +- When it returns `true`, the function narrows `el`'s type by ruling out `null` (if applicable) and intersecting with `{ tagName: T }`, where `T` is the string-literal type provided as the `tagName` argument. So if the `el` you supply has type `HTMLElement` (or `HTMLElement | null`), then if `elIs(el, 'DIV')` returns `true`, the type of `el` will be narrowed to `HTMLElement & { tagName: 'DIV' }`. +- You get autocomplete functionality and typo-prevention with the `tagName` parameter. + + Specifically, the `tagName` parameter must be the value of a standard DOM element's `tagName` property, and this restriction further depends on the type of the `el` argument you supply. For example, if `el` is an instance of `HTMLElement`, then the supplied `tagName` argument must be the value of a standard HTML element's `tagName` property, like `'DIV'`. Supported `el` types are `HTMLElement`, `SVGElement`, `MathMLElement`, the more general `Element`, and unions of any of these; the `tagName` argument will always be appropriately restricted. (It's best, though, if `el` is just `HTMLElement`, `SVGElement`, or `MathMLElement`, so that you don't have to worry about confusing uppercase HTML tag-names like `'A'` with lowercase SVG or MathML tag-names like `'a'`.) -If you don't use `elIs()`, you can just do something like `if (el.matches('section'))` or `if (el.tagName === 'section')` instead. But then you don't get the type-narrowing, the autocomplete, or the typo-prevention, and you'll probably make the mistake I just made in the previous sentence (should be `if (el.tagName === 'SECTION')`). +If you don't use `elIs()`, you can just do something like `if (el.matches('section'))` or `if (el.tagName === 'section')` instead. But then you don't get the type-narrowing, the autocomplete, or the typo-prevention, and you'll probably make the mistake I made in the previous sentence (should be `if (el.tagName === 'SECTION')`). A few more notes: -- Because of the aforementioned `tagName` restrictions, this function won't work with custom elements. -- Although `SVGElement` is a supported `Element`-subtype here, it's worth noting that, at the time of writing, every kind of SVG element has its own dedicated interface (like `SVGPathElement`), so you probably want to reach for `instanceof` when checking against it. -- Deprecated HTML elements are supported (e.g., if `el` is an `HTMLElement` or `Element`, then `tagName` can be `'MARQUEE'`). - -## API - -### elIs - -▸ **elIs**<`E`, `T`\>(`el`, `tagName`): `el` is `E` & { `tagName`: `T` } - -#### Type parameters - -| Name | Type | -| :------ | :------ | -| `E` | extends `HTMLElement` \| `Element` \| `SVGElement` \| `MathMLElement` | -| `T` | extends `Uppercase` \| keyof `SVGElementTagNameMap` \| keyof `MathMLElementTagNameMap` | - -#### Parameters - -| Name | Type | -| :------ | :------ | -| `el` | `E` | -| `tagName` | `T` | - -#### Returns - -`el` is `E` & { `tagName`: `T` } +- Because of the `tagName` restrictions, this function won't work with custom elements in TypeScript. +- Although `SVGElement` is a supported type for the `el` parameter, it's worth noting that, at the time of writing, every kind of SVG element has its own dedicated interface (like `SVGPathElement`), so you probably want to reach for `instanceof` with SVG tags. +- Deprecated HTML elements are supported (e.g., if `el` is an `HTMLElement`, then `tagName` can be `'MARQUEE'`). diff --git a/package-lock.json b/package-lock.json index 3707f5d..d245647 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@michaelallenwarner/el-is", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@michaelallenwarner/el-is", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "devDependencies": { "@types/jest": "^29.5.4", @@ -22,8 +22,6 @@ "prettier": "^3.0.3", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", - "typedoc": "^0.25.1", - "typedoc-plugin-markdown": "^3.16.0", "typescript": "^5.2.2" }, "engines": { @@ -1809,12 +1807,6 @@ "node": ">=8" } }, - "node_modules/ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", - "dev": true - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3323,27 +3315,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -4435,12 +4406,6 @@ "node": ">=6" } }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -4517,12 +4482,6 @@ "node": ">=10" } }, - "node_modules/lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -4553,18 +4512,6 @@ "tmpl": "1.0.5" } }, - "node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -4635,12 +4582,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4653,12 +4594,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5296,18 +5231,6 @@ "node": ">=8" } }, - "node_modules/shiki": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.4.tgz", - "integrity": "sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==", - "dev": true, - "dependencies": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -5710,63 +5633,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typedoc": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.1.tgz", - "integrity": "sha512-c2ye3YUtGIadxN2O6YwPEXgrZcvhlZ6HlhWZ8jQRNzwLPn2ylhdGqdR8HbyDRyALP8J6lmSANILCkkIdNPFxqA==", - "dev": true, - "dependencies": { - "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.3", - "shiki": "^0.14.1" - }, - "bin": { - "typedoc": "bin/typedoc" - }, - "engines": { - "node": ">= 16" - }, - "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x" - } - }, - "node_modules/typedoc-plugin-markdown": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.16.0.tgz", - "integrity": "sha512-eeiC78fDNGFwemPIHiwRC+mEC7W5jwt3fceUev2gJ2nFnXpVHo8eRrpC9BLWZDee6ehnz/sPmNjizbXwpfaTBw==", - "dev": true, - "dependencies": { - "handlebars": "^4.7.7" - }, - "peerDependencies": { - "typedoc": ">=0.24.0" - } - }, - "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -5780,19 +5646,6 @@ "node": ">=14.17" } }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -5896,18 +5749,6 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true - }, - "node_modules/vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -5984,12 +5825,6 @@ "node": ">=12" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -7503,12 +7338,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, - "ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -8582,19 +8411,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -9404,12 +9220,6 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, - "jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -9468,12 +9278,6 @@ "yallist": "^4.0.0" } }, - "lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, "make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -9498,12 +9302,6 @@ "tmpl": "1.0.5" } }, - "marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "dev": true - }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -9556,12 +9354,6 @@ "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -9574,12 +9366,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -10014,18 +9800,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "shiki": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.4.tgz", - "integrity": "sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==", - "dev": true, - "requires": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" - } - }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -10300,60 +10074,12 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, - "typedoc": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.1.tgz", - "integrity": "sha512-c2ye3YUtGIadxN2O6YwPEXgrZcvhlZ6HlhWZ8jQRNzwLPn2ylhdGqdR8HbyDRyALP8J6lmSANILCkkIdNPFxqA==", - "dev": true, - "requires": { - "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.3", - "shiki": "^0.14.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "typedoc-plugin-markdown": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.16.0.tgz", - "integrity": "sha512-eeiC78fDNGFwemPIHiwRC+mEC7W5jwt3fceUev2gJ2nFnXpVHo8eRrpC9BLWZDee6ehnz/sPmNjizbXwpfaTBw==", - "dev": true, - "requires": { - "handlebars": "^4.7.7" - } - }, "typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true }, - "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true - }, "universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -10430,18 +10156,6 @@ } } }, - "vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true - }, - "vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, "w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -10502,12 +10216,6 @@ "webidl-conversions": "^7.0.0" } }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index ff6c35d..fa49c15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@michaelallenwarner/el-is", - "version": "1.0.2", + "version": "1.1.0", "description": "A tiny TypeScript type guard (predicate-function) for narrowing a DOM element by tag-name.", "main": "./lib/index.js", "files": [ @@ -64,8 +64,6 @@ "prettier": "^3.0.3", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", - "typedoc": "^0.25.1", - "typedoc-plugin-markdown": "^3.16.0", "typescript": "^5.2.2" } } diff --git a/src/index.ts b/src/index.ts index c3d9bd5..1205842 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,20 +1,29 @@ +type HTMLTagNames = Uppercase< + keyof HTMLElementTagNameMap | keyof HTMLElementDeprecatedTagNameMap +>; + +type SVGTagNames = keyof SVGElementTagNameMap; + +type MathMLTagNames = keyof MathMLElementTagNameMap; + export const elIs = < - const E extends HTMLElement | SVGElement | MathMLElement | Element, - const T extends E extends HTMLElement - ? Uppercase< - keyof HTMLElementTagNameMap | keyof HTMLElementDeprecatedTagNameMap - > + const E extends HTMLElement | SVGElement | MathMLElement | Element | null, + const T extends E extends null + ? never + : E extends HTMLElement + ? HTMLTagNames : E extends SVGElement - ? keyof SVGElementTagNameMap + ? SVGTagNames : E extends MathMLElement - ? keyof MathMLElementTagNameMap - : - | Uppercase< - keyof HTMLElementTagNameMap | keyof HTMLElementDeprecatedTagNameMap - > - | keyof SVGElementTagNameMap - | keyof MathMLElementTagNameMap, + ? MathMLTagNames + : E extends HTMLElement | SVGElement + ? HTMLTagNames | SVGTagNames + : E extends HTMLElement | MathMLElement + ? HTMLTagNames | MathMLTagNames + : E extends SVGElement | MathMLElement + ? SVGTagNames | MathMLTagNames + : HTMLTagNames | SVGTagNames | MathMLTagNames, >( el: E, tagName: T -): el is E & { tagName: T } => el.tagName === tagName; +): el is E & { tagName: T } => el?.tagName === tagName; diff --git a/test/index.spec.ts b/test/index.spec.ts index 97fef1c..6e7f5c5 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -248,45 +248,52 @@ const math: { [K in keyof MathMLElementTagNameMap]: K } = { }; describe('index', () => { - describe('elIs', () => { - it('should return `true` for each type of `HTMLElement` paired with its correct `tagName` value', () => { - for (const key in html) { - const value = - html[ - key as - | keyof HTMLElementTagNameMap - | keyof HTMLElementDeprecatedTagNameMap - ]; - const el = document.createElement(key); - const result = elIs(el, value); + describe('elIs(el, tagName)', () => { + for (const key in html) { + const tagName = + html[ + key as + | keyof HTMLElementTagNameMap + | keyof HTMLElementDeprecatedTagNameMap + ]; + const el = document.createElement(key); + const result = elIs(el, tagName); + it(`should return \`true\` when \`el\` is \`<${key}>\` (an \`HTMLElement\`) and \`tagName\` is \`'${tagName}'\``, () => { expect(result).toBe(true); - } - }); + }); + } - it('should return `true` for each type of `SVGElement` paired with its correct `tagName` value', () => { - for (const key in svg) { - const value = svg[key as keyof SVGElementTagNameMap]; - const el = document.createElementNS('http://www.w3.org/2000/svg', key); - const result = elIs(el, value); + for (const key in svg) { + const tagName = svg[key as keyof SVGElementTagNameMap]; + const el = document.createElementNS('http://www.w3.org/2000/svg', key); + const result = elIs(el, tagName); + it(`should return \`true\` when \`el\` is \`<${key}>\` (an \`SVGElement\`) and \`tagName\` is \`'${tagName}'\``, () => { expect(result).toBe(true); - } - }); + }); + } - it('should return `true` for each type of `MathMLElement` paired with its correct `tagName` value', () => { - for (const key in math) { - const value = math[key as keyof MathMLElementTagNameMap]; - const el = document.createElementNS( - 'http://www.w3.org/1998/Math/MathML', - key - ); - const result = elIs(el, value); + for (const key in math) { + const tagName = math[key as keyof MathMLElementTagNameMap]; + const el = document.createElementNS( + 'http://www.w3.org/1998/Math/MathML', + key + ); + const result = elIs(el, tagName); + it(`should return \`true\` when \`el\` is \`<${key}>\` (a \`MathMLElement\`) and \`tagName\` is \`'${tagName}'\``, () => { expect(result).toBe(true); - } - }); + }); + } it('should return `false` when `el.tagName !== tagName`', () => { const result = elIs(document.createElement('div'), 'SPAN'); expect(result).toBe(false); }); + + it('should return `false` when `el === null`', () => { + const nullEl = document.querySelector('div'); + expect(nullEl).toBe(null); + const result = elIs(nullEl, 'DIV'); + expect(result).toBe(false); + }); }); });