├── .editorconfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── build.js ├── funding.yml ├── index.js ├── license ├── package.json ├── readme.md ├── test.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | on: 3 | - pull_request 4 | - push 5 | jobs: 6 | main: 7 | name: ${{matrix.node}} 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: ${{matrix.node}} 14 | - run: npm install 15 | - run: npm test 16 | - uses: codecov/codecov-action@v3 17 | strategy: 18 | matrix: 19 | node: 20 | - lts/hydrogen 21 | - node 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ 3 | *.d.ts 4 | *.log 5 | .DS_Store 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | *.md 3 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises' 2 | import {fetch} from 'undici' 3 | import {fromHtml} from 'hast-util-from-html' 4 | import {selectAll} from 'hast-util-select' 5 | import {toString} from 'hast-util-to-string' 6 | import {htmlElementAttributes} from './index.js' 7 | 8 | const own = {}.hasOwnProperty 9 | 10 | if (!('*' in htmlElementAttributes)) { 11 | htmlElementAttributes['*'] = [] 12 | } 13 | 14 | // Global attributes. 15 | const globals = htmlElementAttributes['*'] 16 | 17 | // Crawl WHATWG HTML. 18 | const response = await fetch( 19 | 'https://html.spec.whatwg.org/multipage/indices.html' 20 | ) 21 | const text = await response.text() 22 | 23 | const nodes = selectAll('#attributes-1 tbody tr', fromHtml(text)) 24 | 25 | // Throw if we didn’t match, e.g., when the spec updates. 26 | if (nodes.length === 0) { 27 | throw new Error('Missing results in html') 28 | } 29 | 30 | /** @type {Record>} */ 31 | const result = {} 32 | let index = -1 33 | 34 | while (++index < nodes.length) { 35 | const name = toString(nodes[index].children[0]).trim() 36 | const value = toString(nodes[index].children[1]).trim() 37 | 38 | if (/custom elements/i.test(value)) { 39 | continue 40 | } 41 | 42 | const elements = /HTML elements/.test(value) 43 | ? ['*'] 44 | : value.split(/;/g).map((d) => d.replace(/\([^)]+\)/g, '').trim()) 45 | let offset = -1 46 | 47 | while (++offset < elements.length) { 48 | const tagName = elements[offset].toLowerCase().trim() 49 | 50 | if (!own.call(htmlElementAttributes, tagName)) { 51 | htmlElementAttributes[tagName] = [] 52 | } 53 | 54 | /** @type {string[]} */ 55 | const attributes = htmlElementAttributes[tagName] 56 | 57 | if (!attributes.includes(name)) { 58 | attributes.push(name) 59 | } 60 | } 61 | } 62 | 63 | const keys = Object.keys(htmlElementAttributes).sort() 64 | index = -1 65 | 66 | while (++index < keys.length) { 67 | const key = keys[index] 68 | 69 | htmlElementAttributes[key].sort() 70 | 71 | if (key !== '*') { 72 | htmlElementAttributes[key] = htmlElementAttributes[key].filter( 73 | (/** @type {string} */ d) => !globals.includes(d) 74 | ) 75 | } 76 | 77 | if (htmlElementAttributes[key].length > 0) { 78 | result[key] = htmlElementAttributes[key] 79 | } 80 | } 81 | 82 | await fs.writeFile( 83 | 'index.js', 84 | [ 85 | '/**', 86 | ' * Map of HTML elements to allowed attributes.', 87 | ' *', 88 | ' * @type {Record>}', 89 | ' */', 90 | 'export const htmlElementAttributes = ' + JSON.stringify(result, null, 2), 91 | '' 92 | ].join('\n') 93 | ) 94 | -------------------------------------------------------------------------------- /funding.yml: -------------------------------------------------------------------------------- 1 | github: wooorm 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Map of HTML elements to allowed attributes. 3 | * 4 | * @type {Record>} 5 | */ 6 | export const htmlElementAttributes = { 7 | '*': [ 8 | 'accesskey', 9 | 'autocapitalize', 10 | 'autofocus', 11 | 'class', 12 | 'contenteditable', 13 | 'dir', 14 | 'draggable', 15 | 'enterkeyhint', 16 | 'hidden', 17 | 'id', 18 | 'inert', 19 | 'inputmode', 20 | 'is', 21 | 'itemid', 22 | 'itemprop', 23 | 'itemref', 24 | 'itemscope', 25 | 'itemtype', 26 | 'lang', 27 | 'nonce', 28 | 'popover', 29 | 'slot', 30 | 'spellcheck', 31 | 'style', 32 | 'tabindex', 33 | 'title', 34 | 'translate', 35 | 'writingsuggestions' 36 | ], 37 | a: [ 38 | 'charset', 39 | 'coords', 40 | 'download', 41 | 'href', 42 | 'hreflang', 43 | 'name', 44 | 'ping', 45 | 'referrerpolicy', 46 | 'rel', 47 | 'rev', 48 | 'shape', 49 | 'target', 50 | 'type' 51 | ], 52 | applet: [ 53 | 'align', 54 | 'alt', 55 | 'archive', 56 | 'code', 57 | 'codebase', 58 | 'height', 59 | 'hspace', 60 | 'name', 61 | 'object', 62 | 'vspace', 63 | 'width' 64 | ], 65 | area: [ 66 | 'alt', 67 | 'coords', 68 | 'download', 69 | 'href', 70 | 'hreflang', 71 | 'nohref', 72 | 'ping', 73 | 'referrerpolicy', 74 | 'rel', 75 | 'shape', 76 | 'target', 77 | 'type' 78 | ], 79 | audio: [ 80 | 'autoplay', 81 | 'controls', 82 | 'crossorigin', 83 | 'loop', 84 | 'muted', 85 | 'preload', 86 | 'src' 87 | ], 88 | base: ['href', 'target'], 89 | basefont: ['color', 'face', 'size'], 90 | blockquote: ['cite'], 91 | body: ['alink', 'background', 'bgcolor', 'link', 'text', 'vlink'], 92 | br: ['clear'], 93 | button: [ 94 | 'disabled', 95 | 'form', 96 | 'formaction', 97 | 'formenctype', 98 | 'formmethod', 99 | 'formnovalidate', 100 | 'formtarget', 101 | 'name', 102 | 'popovertarget', 103 | 'popovertargetaction', 104 | 'type', 105 | 'value' 106 | ], 107 | canvas: ['height', 'width'], 108 | caption: ['align'], 109 | col: ['align', 'char', 'charoff', 'span', 'valign', 'width'], 110 | colgroup: ['align', 'char', 'charoff', 'span', 'valign', 'width'], 111 | data: ['value'], 112 | del: ['cite', 'datetime'], 113 | details: ['name', 'open'], 114 | dialog: ['open'], 115 | dir: ['compact'], 116 | div: ['align'], 117 | dl: ['compact'], 118 | embed: ['height', 'src', 'type', 'width'], 119 | fieldset: ['disabled', 'form', 'name'], 120 | font: ['color', 'face', 'size'], 121 | form: [ 122 | 'accept', 123 | 'accept-charset', 124 | 'action', 125 | 'autocomplete', 126 | 'enctype', 127 | 'method', 128 | 'name', 129 | 'novalidate', 130 | 'target' 131 | ], 132 | frame: [ 133 | 'frameborder', 134 | 'longdesc', 135 | 'marginheight', 136 | 'marginwidth', 137 | 'name', 138 | 'noresize', 139 | 'scrolling', 140 | 'src' 141 | ], 142 | frameset: ['cols', 'rows'], 143 | h1: ['align'], 144 | h2: ['align'], 145 | h3: ['align'], 146 | h4: ['align'], 147 | h5: ['align'], 148 | h6: ['align'], 149 | head: ['profile'], 150 | hr: ['align', 'noshade', 'size', 'width'], 151 | html: ['manifest', 'version'], 152 | iframe: [ 153 | 'align', 154 | 'allow', 155 | 'allowfullscreen', 156 | 'allowpaymentrequest', 157 | 'allowusermedia', 158 | 'frameborder', 159 | 'height', 160 | 'loading', 161 | 'longdesc', 162 | 'marginheight', 163 | 'marginwidth', 164 | 'name', 165 | 'referrerpolicy', 166 | 'sandbox', 167 | 'scrolling', 168 | 'src', 169 | 'srcdoc', 170 | 'width' 171 | ], 172 | img: [ 173 | 'align', 174 | 'alt', 175 | 'border', 176 | 'crossorigin', 177 | 'decoding', 178 | 'fetchpriority', 179 | 'height', 180 | 'hspace', 181 | 'ismap', 182 | 'loading', 183 | 'longdesc', 184 | 'name', 185 | 'referrerpolicy', 186 | 'sizes', 187 | 'src', 188 | 'srcset', 189 | 'usemap', 190 | 'vspace', 191 | 'width' 192 | ], 193 | input: [ 194 | 'accept', 195 | 'align', 196 | 'alt', 197 | 'autocomplete', 198 | 'checked', 199 | 'dirname', 200 | 'disabled', 201 | 'form', 202 | 'formaction', 203 | 'formenctype', 204 | 'formmethod', 205 | 'formnovalidate', 206 | 'formtarget', 207 | 'height', 208 | 'ismap', 209 | 'list', 210 | 'max', 211 | 'maxlength', 212 | 'min', 213 | 'minlength', 214 | 'multiple', 215 | 'name', 216 | 'pattern', 217 | 'placeholder', 218 | 'popovertarget', 219 | 'popovertargetaction', 220 | 'readonly', 221 | 'required', 222 | 'size', 223 | 'src', 224 | 'step', 225 | 'type', 226 | 'usemap', 227 | 'value', 228 | 'width' 229 | ], 230 | ins: ['cite', 'datetime'], 231 | isindex: ['prompt'], 232 | label: ['for', 'form'], 233 | legend: ['align'], 234 | li: ['type', 'value'], 235 | link: [ 236 | 'as', 237 | 'blocking', 238 | 'charset', 239 | 'color', 240 | 'crossorigin', 241 | 'disabled', 242 | 'fetchpriority', 243 | 'href', 244 | 'hreflang', 245 | 'imagesizes', 246 | 'imagesrcset', 247 | 'integrity', 248 | 'media', 249 | 'referrerpolicy', 250 | 'rel', 251 | 'rev', 252 | 'sizes', 253 | 'target', 254 | 'type' 255 | ], 256 | map: ['name'], 257 | menu: ['compact'], 258 | meta: ['charset', 'content', 'http-equiv', 'media', 'name', 'scheme'], 259 | meter: ['high', 'low', 'max', 'min', 'optimum', 'value'], 260 | object: [ 261 | 'align', 262 | 'archive', 263 | 'border', 264 | 'classid', 265 | 'codebase', 266 | 'codetype', 267 | 'data', 268 | 'declare', 269 | 'form', 270 | 'height', 271 | 'hspace', 272 | 'name', 273 | 'standby', 274 | 'type', 275 | 'typemustmatch', 276 | 'usemap', 277 | 'vspace', 278 | 'width' 279 | ], 280 | ol: ['compact', 'reversed', 'start', 'type'], 281 | optgroup: ['disabled', 'label'], 282 | option: ['disabled', 'label', 'selected', 'value'], 283 | output: ['for', 'form', 'name'], 284 | p: ['align'], 285 | param: ['name', 'type', 'value', 'valuetype'], 286 | pre: ['width'], 287 | progress: ['max', 'value'], 288 | q: ['cite'], 289 | script: [ 290 | 'async', 291 | 'blocking', 292 | 'charset', 293 | 'crossorigin', 294 | 'defer', 295 | 'fetchpriority', 296 | 'integrity', 297 | 'language', 298 | 'nomodule', 299 | 'referrerpolicy', 300 | 'src', 301 | 'type' 302 | ], 303 | select: [ 304 | 'autocomplete', 305 | 'disabled', 306 | 'form', 307 | 'multiple', 308 | 'name', 309 | 'required', 310 | 'size' 311 | ], 312 | slot: ['name'], 313 | source: ['height', 'media', 'sizes', 'src', 'srcset', 'type', 'width'], 314 | style: ['blocking', 'media', 'type'], 315 | table: [ 316 | 'align', 317 | 'bgcolor', 318 | 'border', 319 | 'cellpadding', 320 | 'cellspacing', 321 | 'frame', 322 | 'rules', 323 | 'summary', 324 | 'width' 325 | ], 326 | tbody: ['align', 'char', 'charoff', 'valign'], 327 | td: [ 328 | 'abbr', 329 | 'align', 330 | 'axis', 331 | 'bgcolor', 332 | 'char', 333 | 'charoff', 334 | 'colspan', 335 | 'headers', 336 | 'height', 337 | 'nowrap', 338 | 'rowspan', 339 | 'scope', 340 | 'valign', 341 | 'width' 342 | ], 343 | template: [ 344 | 'shadowrootclonable', 345 | 'shadowrootdelegatesfocus', 346 | 'shadowrootmode' 347 | ], 348 | textarea: [ 349 | 'autocomplete', 350 | 'cols', 351 | 'dirname', 352 | 'disabled', 353 | 'form', 354 | 'maxlength', 355 | 'minlength', 356 | 'name', 357 | 'placeholder', 358 | 'readonly', 359 | 'required', 360 | 'rows', 361 | 'wrap' 362 | ], 363 | tfoot: ['align', 'char', 'charoff', 'valign'], 364 | th: [ 365 | 'abbr', 366 | 'align', 367 | 'axis', 368 | 'bgcolor', 369 | 'char', 370 | 'charoff', 371 | 'colspan', 372 | 'headers', 373 | 'height', 374 | 'nowrap', 375 | 'rowspan', 376 | 'scope', 377 | 'valign', 378 | 'width' 379 | ], 380 | thead: ['align', 'char', 'charoff', 'valign'], 381 | time: ['datetime'], 382 | tr: ['align', 'bgcolor', 'char', 'charoff', 'valign'], 383 | track: ['default', 'kind', 'label', 'src', 'srclang'], 384 | ul: ['compact', 'type'], 385 | video: [ 386 | 'autoplay', 387 | 'controls', 388 | 'crossorigin', 389 | 'height', 390 | 'loop', 391 | 'muted', 392 | 'playsinline', 393 | 'poster', 394 | 'preload', 395 | 'src', 396 | 'width' 397 | ] 398 | } 399 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2016 Titus Wormer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-element-attributes", 3 | "version": "3.4.0", 4 | "description": "Map of HTML elements to allowed attributes", 5 | "license": "MIT", 6 | "keywords": [ 7 | "html", 8 | "element", 9 | "tag", 10 | "name", 11 | "attribute", 12 | "property", 13 | "w3c", 14 | "whatwg" 15 | ], 16 | "repository": "wooorm/html-element-attributes", 17 | "bugs": "https://github.com/wooorm/html-element-attributes/issues", 18 | "funding": { 19 | "type": "github", 20 | "url": "https://github.com/sponsors/wooorm" 21 | }, 22 | "author": "Titus Wormer (https://wooorm.com)", 23 | "contributors": [ 24 | "Titus Wormer (https://wooorm.com)" 25 | ], 26 | "sideEffects": false, 27 | "type": "module", 28 | "main": "index.js", 29 | "types": "index.d.ts", 30 | "files": [ 31 | "index.d.ts", 32 | "index.js" 33 | ], 34 | "devDependencies": { 35 | "@types/node": "^20.0.0", 36 | "c8": "^8.0.0", 37 | "hast-util-from-html": "^2.0.0", 38 | "hast-util-select": "^6.0.0", 39 | "hast-util-to-string": "^3.0.0", 40 | "prettier": "^3.0.0", 41 | "remark-cli": "^11.0.0", 42 | "remark-preset-wooorm": "^9.0.0", 43 | "type-coverage": "^2.0.0", 44 | "typescript": "^5.0.0", 45 | "undici": "^6.0.0", 46 | "xo": "^0.58.0" 47 | }, 48 | "scripts": { 49 | "prepack": "npm run build && npm run format", 50 | "generate": "node --conditions development build.js", 51 | "build": "tsc --build --clean && tsc --build && type-coverage", 52 | "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix", 53 | "test-api": "node --conditions development test.js", 54 | "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api", 55 | "test": "npm run generate && npm run build && npm run format && npm run test-coverage" 56 | }, 57 | "prettier": { 58 | "tabWidth": 2, 59 | "useTabs": false, 60 | "singleQuote": true, 61 | "bracketSpacing": false, 62 | "semi": false, 63 | "trailingComma": "none" 64 | }, 65 | "xo": { 66 | "prettier": true, 67 | "rules": { 68 | "unicorn/prefer-string-replace-all": "off" 69 | } 70 | }, 71 | "remarkConfig": { 72 | "plugins": [ 73 | "preset-wooorm" 74 | ] 75 | }, 76 | "typeCoverage": { 77 | "atLeast": 100, 78 | "detail": true, 79 | "strict": true, 80 | "ignoreCatch": true 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # html-element-attributes 2 | 3 | [![Build][build-badge]][build] 4 | [![Coverage][coverage-badge]][coverage] 5 | [![Downloads][downloads-badge]][downloads] 6 | [![Size][size-badge]][size] 7 | 8 | Map of HTML elements to allowed attributes. 9 | 10 | ## Contents 11 | 12 | * [What is this?](#what-is-this) 13 | * [When should I use this?](#when-should-i-use-this) 14 | * [Install](#install) 15 | * [Use](#use) 16 | * [API](#api) 17 | * [`htmlElementAttributes`](#htmlelementattributes) 18 | * [Types](#types) 19 | * [Compatibility](#compatibility) 20 | * [Security](#security) 21 | * [Related](#related) 22 | * [Contribute](#contribute) 23 | * [License](#license) 24 | 25 | ## What is this? 26 | 27 | This is a map of tag names to lists of allowed attributes. 28 | Global attributes are stored at the special tag name `*`. 29 | All attributes from HTML 4 and the current living HTML spec are included. 30 | 31 | > 👉 **Note**: Includes deprecated attributes. 32 | 33 | > 👉 **Note**: Attributes which were not global in HTML 4 but are in HTML, are 34 | > only included in the list of global attributes. 35 | 36 | ## When should I use this? 37 | 38 | You can use this to figure out if certain attributes are allowed on certain 39 | HTML elements. 40 | 41 | ## Install 42 | 43 | This package is [ESM only][esm]. 44 | In Node.js (version 14.14+, 16.0+), install with [npm][]: 45 | 46 | ```sh 47 | npm install html-element-attributes 48 | ``` 49 | 50 | In Deno with [`esm.sh`][esmsh]: 51 | 52 | ```js 53 | import {htmlElementAttributes} from 'https://esm.sh/html-element-attributes@3' 54 | ``` 55 | 56 | In browsers with [`esm.sh`][esmsh]: 57 | 58 | ```html 59 | 62 | ``` 63 | 64 | ## Use 65 | 66 | ```js 67 | import {htmlElementAttributes} from 'html-element-attributes' 68 | 69 | console.log(htmlElementAttributes['*']) 70 | console.log(htmlElementAttributes.ol) 71 | ``` 72 | 73 | Yields: 74 | 75 | ```js 76 | [ 77 | 'accesskey', 78 | 'autocapitalize', 79 | 'autofocus', 80 | 'class', 81 | // … 82 | 'style', 83 | 'tabindex', 84 | 'title', 85 | 'translate' 86 | ] 87 | ['compact', 'reversed', 'start', 'type'] 88 | ``` 89 | 90 | ## API 91 | 92 | This package exports the identifier `htmlElementAttributes`. 93 | There is no default export. 94 | 95 | ### `htmlElementAttributes` 96 | 97 | Map of lowercase HTML elements to allowed attributes 98 | (`Record>`). 99 | 100 | ## Types 101 | 102 | This package is fully typed with [TypeScript][]. 103 | It exports no additional types. 104 | 105 | ## Compatibility 106 | 107 | This package is at least compatible with all maintained versions of Node.js. 108 | As of now, that is Node.js 14.14+ and 16.0+. 109 | It also works in Deno and modern browsers. 110 | 111 | ## Security 112 | 113 | This package is safe. 114 | 115 | ## Related 116 | 117 | * [`wooorm/web-namespaces`](https://github.com/wooorm/web-namespaces) 118 | — list of web namespaces 119 | * [`wooorm/html-tag-names`](https://github.com/wooorm/html-tag-names) 120 | — list of HTML tag names 121 | * [`wooorm/mathml-tag-names`](https://github.com/wooorm/mathml-tag-names) 122 | — list of MathML tag names 123 | * [`wooorm/svg-tag-names`](https://github.com/wooorm/svg-tag-names) 124 | — list of SVG tag names 125 | * [`wooorm/html-void-elements`](https://github.com/wooorm/html-void-elements) 126 | — list of void HTML tag names 127 | * [`wooorm/svg-element-attributes`](https://github.com/wooorm/svg-element-attributes) 128 | — map of SVG elements to attributes 129 | * [`wooorm/aria-attributes`](https://github.com/wooorm/aria-attributes) 130 | — list of ARIA attributes 131 | 132 | ## Contribute 133 | 134 | Yes please! 135 | See [How to Contribute to Open Source][contribute]. 136 | 137 | ## License 138 | 139 | [MIT][license] © [Titus Wormer][author] 140 | 141 | 142 | 143 | [build-badge]: https://github.com/wooorm/html-element-attributes/workflows/main/badge.svg 144 | 145 | [build]: https://github.com/wooorm/html-element-attributes/actions 146 | 147 | [coverage-badge]: https://img.shields.io/codecov/c/github/wooorm/html-element-attributes.svg 148 | 149 | [coverage]: https://codecov.io/github/wooorm/html-element-attributes 150 | 151 | [downloads-badge]: https://img.shields.io/npm/dm/html-element-attributes.svg 152 | 153 | [downloads]: https://www.npmjs.com/package/html-element-attributes 154 | 155 | [size-badge]: https://img.shields.io/bundlephobia/minzip/html-element-attributes.svg 156 | 157 | [size]: https://bundlephobia.com/result?p=html-element-attributes 158 | 159 | [npm]: https://docs.npmjs.com/cli/install 160 | 161 | [esmsh]: https://esm.sh 162 | 163 | [license]: license 164 | 165 | [author]: https://wooorm.com 166 | 167 | [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c 168 | 169 | [typescript]: https://www.typescriptlang.org 170 | 171 | [contribute]: https://opensource.guide/how-to-contribute/ 172 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | import test from 'node:test' 3 | import {htmlElementAttributes} from './index.js' 4 | 5 | const own = {}.hasOwnProperty 6 | 7 | test('htmlElementAttributes', function () { 8 | assert.equal(typeof htmlElementAttributes, 'object', 'should be an `object`') 9 | 10 | /** @type {string} */ 11 | let key 12 | 13 | for (key in htmlElementAttributes) { 14 | if (own.call(htmlElementAttributes, key)) { 15 | assert.ok(Array.isArray(htmlElementAttributes[key]), key) 16 | } 17 | } 18 | 19 | for (key in htmlElementAttributes) { 20 | if (own.call(htmlElementAttributes, key)) { 21 | const properties = htmlElementAttributes[key] 22 | let index = -1 23 | 24 | while (++index < properties.length) { 25 | const property = properties[index] 26 | const label = property + ' in ' + key 27 | 28 | assert.strictEqual( 29 | typeof property, 30 | 'string', 31 | label + ' should be string' 32 | ) 33 | assert.strictEqual( 34 | property, 35 | property.toLowerCase(), 36 | label + ' should be lower-case' 37 | ) 38 | assert.strictEqual( 39 | property, 40 | property.trim(), 41 | label + ' should be trimmed' 42 | ) 43 | assert.ok(/^[a-z-]+$/.test(property), label + ' should be `a-z-`') 44 | } 45 | } 46 | } 47 | }) 48 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["**/*.js"], 3 | "exclude": ["coverage/", "node_modules/"], 4 | "compilerOptions": { 5 | "customConditions": ["development"], 6 | "checkJs": true, 7 | "declaration": true, 8 | "emitDeclarationOnly": true, 9 | "exactOptionalPropertyTypes": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "lib": ["es2020"], 12 | "module": "node16", 13 | "newLine": "lf", 14 | "strict": true, 15 | "target": "es2020" 16 | } 17 | } 18 | --------------------------------------------------------------------------------