refactor: Still trying to reorganize edit mode to be more robust
Some checks failed
Test Suite / code-quality (push) Has been cancelled
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
Some checks failed
Test Suite / code-quality (push) Has been cancelled
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
This commit is contained in:
1
node_modules/.bin/tldts
generated
vendored
Symbolic link
1
node_modules/.bin/tldts
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../tldts/bin/cli.js
|
||||
924
node_modules/.package-lock.json
generated
vendored
924
node_modules/.package-lock.json
generated
vendored
File diff suppressed because it is too large
Load Diff
20
node_modules/@acemir/cssom/LICENSE.txt
generated
vendored
Normal file
20
node_modules/@acemir/cssom/LICENSE.txt
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright (c) Nikita Vasilyev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
64
node_modules/@acemir/cssom/README.mdown
generated
vendored
Normal file
64
node_modules/@acemir/cssom/README.mdown
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
# CSSOM
|
||||
|
||||
CSSOM.js is a CSS parser written in pure JavaScript. It is also a partial implementation of [CSS Object Model](http://dev.w3.org/csswg/cssom/).
|
||||
|
||||
CSSOM.parse("body {color: black}")
|
||||
-> {
|
||||
cssRules: [
|
||||
{
|
||||
selectorText: "body",
|
||||
style: {
|
||||
0: "color",
|
||||
color: "black",
|
||||
length: 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## [Parser demo](https://acemir.github.io/CSSOM/docs/parse.html)
|
||||
|
||||
Works well in Google Chrome 6+, Safari 5+, Firefox 3.6+, Opera 10.63+.
|
||||
Doesn't work in IE < 9 because of unsupported getters/setters.
|
||||
|
||||
To use CSSOM.js in the browser you might want to build a one-file version that exposes a single `CSSOM` global variable:
|
||||
|
||||
➤ git clone https://github.com/acemir/CSSOM.git
|
||||
➤ cd CSSOM
|
||||
➤ node build.js
|
||||
build/CSSOM.js is done
|
||||
|
||||
To use it with Node.js or any other CommonJS loader:
|
||||
|
||||
➤ npm install @acemir/cssom
|
||||
|
||||
## Don’t use it if...
|
||||
|
||||
You parse CSS to mungle, minify or reformat code like this:
|
||||
|
||||
```css
|
||||
div {
|
||||
background: gray;
|
||||
background: linear-gradient(to bottom, white 0%, black 100%);
|
||||
}
|
||||
```
|
||||
|
||||
This pattern is often used to give browsers that don’t understand linear gradients a fallback solution (e.g. gray color in the example).
|
||||
In CSSOM, `background: gray` [gets overwritten](http://nv.github.io/CSSOM/docs/parse.html#css=div%20%7B%0A%20%20%20%20%20%20background%3A%20gray%3B%0A%20%20%20%20background%3A%20linear-gradient(to%20bottom%2C%20white%200%25%2C%20black%20100%25)%3B%0A%7D).
|
||||
It does **NOT** get preserved.
|
||||
|
||||
If you do CSS mungling, minification, or image inlining, considere using one of the following:
|
||||
|
||||
* [postcss](https://github.com/postcss/postcss)
|
||||
* [reworkcss/css](https://github.com/reworkcss/css)
|
||||
* [csso](https://github.com/css/csso)
|
||||
* [mensch](https://github.com/brettstimmerman/mensch)
|
||||
|
||||
|
||||
## [Tests](https://acemir.github.io/CSSOM/spec/)
|
||||
|
||||
To run tests locally:
|
||||
|
||||
➤ git submodule init
|
||||
➤ git submodule update
|
||||
30
node_modules/@acemir/cssom/package.json
generated
vendored
Normal file
30
node_modules/@acemir/cssom/package.json
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@acemir/cssom",
|
||||
"description": "CSS Object Model implementation and CSS parser",
|
||||
"keywords": [
|
||||
"CSS",
|
||||
"CSSOM",
|
||||
"parser",
|
||||
"styleSheet"
|
||||
],
|
||||
"version": "0.9.19",
|
||||
"author": "Nikita Vasilyev <me@elv1s.ru>",
|
||||
"contributors": [
|
||||
"Acemir Sousa Mendes <acemirsm@gmail.com>"
|
||||
],
|
||||
"repository": "acemir/CSSOM",
|
||||
"files": [
|
||||
"lib/",
|
||||
"build/"
|
||||
],
|
||||
"main": "./lib/index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "node build.js",
|
||||
"release": "npm run build && changeset publish"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/changelog-github": "^0.5.1",
|
||||
"@changesets/cli": "^2.27.1"
|
||||
}
|
||||
}
|
||||
21
node_modules/@asamuzakjp/css-color/LICENSE
generated
vendored
Normal file
21
node_modules/@asamuzakjp/css-color/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 asamuzaK (Kazz)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
316
node_modules/@asamuzakjp/css-color/README.md
generated
vendored
Normal file
316
node_modules/@asamuzakjp/css-color/README.md
generated
vendored
Normal file
@@ -0,0 +1,316 @@
|
||||
# CSS color
|
||||
|
||||
[](https://github.com/asamuzaK/cssColor/actions/workflows/node.js.yml)
|
||||
[](https://github.com/asamuzaK/cssColor/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://www.npmjs.com/package/@asamuzakjp/css-color)
|
||||
|
||||
Resolve and convert CSS colors.
|
||||
|
||||
## Install
|
||||
|
||||
```console
|
||||
npm i @asamuzakjp/css-color
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
import { convert, resolve, utils } from '@asamuzakjp/css-color';
|
||||
|
||||
const resolvedValue = resolve(
|
||||
'color-mix(in oklab, lch(67.5345 42.5 258.2), color(srgb 0 0.5 0))'
|
||||
);
|
||||
// 'oklab(0.620754 -0.0931934 -0.00374881)'
|
||||
|
||||
const convertedValue = convert.colorToHex('lab(46.2775% -47.5621 48.5837)');
|
||||
// '#008000'
|
||||
|
||||
const result = utils.isColor('green');
|
||||
// true
|
||||
```
|
||||
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
### resolve(color, opt)
|
||||
|
||||
resolves CSS color
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `color` **[string][133]** color value
|
||||
- system colors are not supported
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.currentColor` **[string][133]?**
|
||||
- color to use for `currentcolor` keyword
|
||||
- if omitted, it will be treated as a missing color,
|
||||
i.e. `rgb(none none none / none)`
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties
|
||||
- pair of `--` prefixed property name as a key and it's value,
|
||||
e.g.
|
||||
```javascript
|
||||
const opt = {
|
||||
customProperty: {
|
||||
'--some-color': '#008000',
|
||||
'--some-length': '16px'
|
||||
}
|
||||
};
|
||||
```
|
||||
- and/or `callback` function to get the value of the custom property,
|
||||
e.g.
|
||||
```javascript
|
||||
const node = document.getElementById('foo');
|
||||
const opt = {
|
||||
customProperty: {
|
||||
callback: node.style.getPropertyValue
|
||||
}
|
||||
};
|
||||
```
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, e.g. for converting relative length to pixels
|
||||
- pair of unit as a key and number in pixels as it's value,
|
||||
e.g. suppose `1em === 12px`, `1rem === 16px` and `100vw === 1024px`, then
|
||||
```javascript
|
||||
const opt = {
|
||||
dimension: {
|
||||
em: 12,
|
||||
rem: 16,
|
||||
vw: 10.24
|
||||
}
|
||||
};
|
||||
```
|
||||
- and/or `callback` function to get the value as a number in pixels,
|
||||
e.g.
|
||||
```javascript
|
||||
const opt = {
|
||||
dimension: {
|
||||
callback: unit => {
|
||||
switch (unit) {
|
||||
case 'em':
|
||||
return 12;
|
||||
case 'rem':
|
||||
return 16;
|
||||
case 'vw':
|
||||
return 10.24;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
- `opt.format` **[string][133]?**
|
||||
- output format, one of below
|
||||
- `computedValue` (default), [computed value][139] of the color
|
||||
- `specifiedValue`, [specified value][140] of the color
|
||||
- `hex`, hex color notation, i.e. `#rrggbb`
|
||||
- `hexAlpha`, hex color notation with alpha channel, i.e. `#rrggbbaa`
|
||||
|
||||
Returns **[string][133]?** one of `rgba?()`, `#rrggbb(aa)?`, `color-name`, `color(color-space r g b / alpha)`, `color(color-space x y z / alpha)`, `(ok)?lab(l a b / alpha)`, `(ok)?lch(l c h / alpha)`, `'(empty-string)'`, `null`
|
||||
|
||||
- in `computedValue`, values are numbers, however `rgb()` values are integers
|
||||
- in `specifiedValue`, returns `empty string` for unknown and/or invalid color
|
||||
- in `hex`, returns `null` for `transparent`, and also returns `null` if any of `r`, `g`, `b`, `alpha` is not a number
|
||||
- in `hexAlpha`, returns `#00000000` for `transparent`, however returns `null` if any of `r`, `g`, `b`, `alpha` is not a number
|
||||
|
||||
### convert
|
||||
|
||||
Contains various color conversion functions.
|
||||
|
||||
### convert.numberToHex(value)
|
||||
|
||||
convert number to hex string
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[number][134]** color value
|
||||
|
||||
Returns **[string][133]** hex string: 00..ff
|
||||
|
||||
### convert.colorToHex(value, opt)
|
||||
|
||||
convert color to hex
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.alpha` **[boolean][136]?** return in #rrggbbaa notation
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[string][133]** #rrggbb(aa)?
|
||||
|
||||
### convert.colorToHsl(value, opt)
|
||||
|
||||
convert color to hsl
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[h, s, l, alpha]
|
||||
|
||||
### convert.colorToHwb(value, opt)
|
||||
|
||||
convert color to hwb
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[h, w, b, alpha]
|
||||
|
||||
### convert.colorToLab(value, opt)
|
||||
|
||||
convert color to lab
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[l, a, b, alpha]
|
||||
|
||||
### convert.colorToLch(value, opt)
|
||||
|
||||
convert color to lch
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[l, c, h, alpha]
|
||||
|
||||
### convert.colorToOklab(value, opt)
|
||||
|
||||
convert color to oklab
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[l, a, b, alpha]
|
||||
|
||||
### convert.colorToOklch(value, opt)
|
||||
|
||||
convert color to oklch
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[l, c, h, alpha]
|
||||
|
||||
### convert.colorToRgb(value, opt)
|
||||
|
||||
convert color to rgb
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[r, g, b, alpha]
|
||||
|
||||
### convert.colorToXyz(value, opt)
|
||||
|
||||
convert color to xyz
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
- `opt.d50` **[boolean][136]?** xyz in d50 white point
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[x, y, z, alpha]
|
||||
|
||||
### convert.colorToXyzD50(value, opt)
|
||||
|
||||
convert color to xyz-d50
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[x, y, z, alpha]
|
||||
|
||||
### utils
|
||||
|
||||
Contains utility functions.
|
||||
|
||||
### utils.isColor(color)
|
||||
|
||||
is valid color type
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `color` **[string][133]** color value
|
||||
- system colors are not supported
|
||||
|
||||
Returns **[boolean][136]**
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
The following resources have been of great help in the development of the CSS color.
|
||||
|
||||
- [csstools/postcss-plugins](https://github.com/csstools/postcss-plugins)
|
||||
- [lru-cache](https://github.com/isaacs/node-lru-cache)
|
||||
|
||||
---
|
||||
|
||||
Copyright (c) 2024 [asamuzaK (Kazz)](https://github.com/asamuzaK/)
|
||||
|
||||
[133]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
[134]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
[135]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
[136]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
[137]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||
[138]: https://w3c.github.io/csswg-drafts/css-color-4/#color-conversion-code
|
||||
[139]: https://developer.mozilla.org/en-US/docs/Web/CSS/computed_value
|
||||
[140]: https://developer.mozilla.org/en-US/docs/Web/CSS/specified_value
|
||||
[141]: https://www.npmjs.com/package/@csstools/css-calc
|
||||
15
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/LICENSE
generated
vendored
Normal file
15
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
The ISC License
|
||||
|
||||
Copyright (c) 2010-2023 Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
338
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/README.md
generated
vendored
Normal file
338
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/README.md
generated
vendored
Normal file
@@ -0,0 +1,338 @@
|
||||
# lru-cache
|
||||
|
||||
A cache object that deletes the least-recently-used items.
|
||||
|
||||
Specify a max number of the most recently used items that you
|
||||
want to keep, and this cache will keep that many of the most
|
||||
recently accessed items.
|
||||
|
||||
This is not primarily a TTL cache, and does not make strong TTL
|
||||
guarantees. There is no preemptive pruning of expired items by
|
||||
default, but you _may_ set a TTL on the cache or on a single
|
||||
`set`. If you do so, it will treat expired items as missing, and
|
||||
delete them when fetched. If you are more interested in TTL
|
||||
caching than LRU caching, check out
|
||||
[@isaacs/ttlcache](http://npm.im/@isaacs/ttlcache).
|
||||
|
||||
As of version 7, this is one of the most performant LRU
|
||||
implementations available in JavaScript, and supports a wide
|
||||
diversity of use cases. However, note that using some of the
|
||||
features will necessarily impact performance, by causing the
|
||||
cache to have to do more work. See the "Performance" section
|
||||
below.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install lru-cache --save
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
// hybrid module, either works
|
||||
import { LRUCache } from 'lru-cache'
|
||||
// or:
|
||||
const { LRUCache } = require('lru-cache')
|
||||
// or in minified form for web browsers:
|
||||
import { LRUCache } from 'http://unpkg.com/lru-cache@9/dist/mjs/index.min.mjs'
|
||||
|
||||
// At least one of 'max', 'ttl', or 'maxSize' is required, to prevent
|
||||
// unsafe unbounded storage.
|
||||
//
|
||||
// In most cases, it's best to specify a max for performance, so all
|
||||
// the required memory allocation is done up-front.
|
||||
//
|
||||
// All the other options are optional, see the sections below for
|
||||
// documentation on what each one does. Most of them can be
|
||||
// overridden for specific items in get()/set()
|
||||
const options = {
|
||||
max: 500,
|
||||
|
||||
// for use with tracking overall storage size
|
||||
maxSize: 5000,
|
||||
sizeCalculation: (value, key) => {
|
||||
return 1
|
||||
},
|
||||
|
||||
// for use when you need to clean up something when objects
|
||||
// are evicted from the cache
|
||||
dispose: (value, key, reason) => {
|
||||
freeFromMemoryOrWhatever(value)
|
||||
},
|
||||
|
||||
// for use when you need to know that an item is being inserted
|
||||
// note that this does NOT allow you to prevent the insertion,
|
||||
// it just allows you to know about it.
|
||||
onInsert: (value, key, reason) => {
|
||||
logInsertionOrWhatever(key, value)
|
||||
},
|
||||
|
||||
// how long to live in ms
|
||||
ttl: 1000 * 60 * 5,
|
||||
|
||||
// return stale items before removing from cache?
|
||||
allowStale: false,
|
||||
|
||||
updateAgeOnGet: false,
|
||||
updateAgeOnHas: false,
|
||||
|
||||
// async method to use for cache.fetch(), for
|
||||
// stale-while-revalidate type of behavior
|
||||
fetchMethod: async (
|
||||
key,
|
||||
staleValue,
|
||||
{ options, signal, context },
|
||||
) => {},
|
||||
}
|
||||
|
||||
const cache = new LRUCache(options)
|
||||
|
||||
cache.set('key', 'value')
|
||||
cache.get('key') // "value"
|
||||
|
||||
// non-string keys ARE fully supported
|
||||
// but note that it must be THE SAME object, not
|
||||
// just a JSON-equivalent object.
|
||||
var someObject = { a: 1 }
|
||||
cache.set(someObject, 'a value')
|
||||
// Object keys are not toString()-ed
|
||||
cache.set('[object Object]', 'a different value')
|
||||
assert.equal(cache.get(someObject), 'a value')
|
||||
// A similar object with same keys/values won't work,
|
||||
// because it's a different object identity
|
||||
assert.equal(cache.get({ a: 1 }), undefined)
|
||||
|
||||
cache.clear() // empty the cache
|
||||
```
|
||||
|
||||
If you put more stuff in the cache, then less recently used items
|
||||
will fall out. That's what an LRU cache is.
|
||||
|
||||
For full description of the API and all options, please see [the
|
||||
LRUCache typedocs](https://isaacs.github.io/node-lru-cache/)
|
||||
|
||||
## Storage Bounds Safety
|
||||
|
||||
This implementation aims to be as flexible as possible, within
|
||||
the limits of safe memory consumption and optimal performance.
|
||||
|
||||
At initial object creation, storage is allocated for `max` items.
|
||||
If `max` is set to zero, then some performance is lost, and item
|
||||
count is unbounded. Either `maxSize` or `ttl` _must_ be set if
|
||||
`max` is not specified.
|
||||
|
||||
If `maxSize` is set, then this creates a safe limit on the
|
||||
maximum storage consumed, but without the performance benefits of
|
||||
pre-allocation. When `maxSize` is set, every item _must_ provide
|
||||
a size, either via the `sizeCalculation` method provided to the
|
||||
constructor, or via a `size` or `sizeCalculation` option provided
|
||||
to `cache.set()`. The size of every item _must_ be a positive
|
||||
integer.
|
||||
|
||||
If neither `max` nor `maxSize` are set, then `ttl` tracking must
|
||||
be enabled. Note that, even when tracking item `ttl`, items are
|
||||
_not_ preemptively deleted when they become stale, unless
|
||||
`ttlAutopurge` is enabled. Instead, they are only purged the
|
||||
next time the key is requested. Thus, if `ttlAutopurge`, `max`,
|
||||
and `maxSize` are all not set, then the cache will potentially
|
||||
grow unbounded.
|
||||
|
||||
In this case, a warning is printed to standard error. Future
|
||||
versions may require the use of `ttlAutopurge` if `max` and
|
||||
`maxSize` are not specified.
|
||||
|
||||
If you truly wish to use a cache that is bound _only_ by TTL
|
||||
expiration, consider using a `Map` object, and calling
|
||||
`setTimeout` to delete entries when they expire. It will perform
|
||||
much better than an LRU cache.
|
||||
|
||||
Here is an implementation you may use, under the same
|
||||
[license](./LICENSE) as this package:
|
||||
|
||||
```js
|
||||
// a storage-unbounded ttl cache that is not an lru-cache
|
||||
const cache = {
|
||||
data: new Map(),
|
||||
timers: new Map(),
|
||||
set: (k, v, ttl) => {
|
||||
if (cache.timers.has(k)) {
|
||||
clearTimeout(cache.timers.get(k))
|
||||
}
|
||||
cache.timers.set(
|
||||
k,
|
||||
setTimeout(() => cache.delete(k), ttl),
|
||||
)
|
||||
cache.data.set(k, v)
|
||||
},
|
||||
get: k => cache.data.get(k),
|
||||
has: k => cache.data.has(k),
|
||||
delete: k => {
|
||||
if (cache.timers.has(k)) {
|
||||
clearTimeout(cache.timers.get(k))
|
||||
}
|
||||
cache.timers.delete(k)
|
||||
return cache.data.delete(k)
|
||||
},
|
||||
clear: () => {
|
||||
cache.data.clear()
|
||||
for (const v of cache.timers.values()) {
|
||||
clearTimeout(v)
|
||||
}
|
||||
cache.timers.clear()
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If that isn't to your liking, check out
|
||||
[@isaacs/ttlcache](http://npm.im/@isaacs/ttlcache).
|
||||
|
||||
## Storing Undefined Values
|
||||
|
||||
This cache never stores undefined values, as `undefined` is used
|
||||
internally in a few places to indicate that a key is not in the
|
||||
cache.
|
||||
|
||||
You may call `cache.set(key, undefined)`, but this is just
|
||||
an alias for `cache.delete(key)`. Note that this has the effect
|
||||
that `cache.has(key)` will return _false_ after setting it to
|
||||
undefined.
|
||||
|
||||
```js
|
||||
cache.set(myKey, undefined)
|
||||
cache.has(myKey) // false!
|
||||
```
|
||||
|
||||
If you need to track `undefined` values, and still note that the
|
||||
key is in the cache, an easy workaround is to use a sigil object
|
||||
of your own.
|
||||
|
||||
```js
|
||||
import { LRUCache } from 'lru-cache'
|
||||
const undefinedValue = Symbol('undefined')
|
||||
const cache = new LRUCache(...)
|
||||
const mySet = (key, value) =>
|
||||
cache.set(key, value === undefined ? undefinedValue : value)
|
||||
const myGet = (key, value) => {
|
||||
const v = cache.get(key)
|
||||
return v === undefinedValue ? undefined : v
|
||||
}
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
As of January 2022, version 7 of this library is one of the most
|
||||
performant LRU cache implementations in JavaScript.
|
||||
|
||||
Benchmarks can be extremely difficult to get right. In
|
||||
particular, the performance of set/get/delete operations on
|
||||
objects will vary _wildly_ depending on the type of key used. V8
|
||||
is highly optimized for objects with keys that are short strings,
|
||||
especially integer numeric strings. Thus any benchmark which
|
||||
tests _solely_ using numbers as keys will tend to find that an
|
||||
object-based approach performs the best.
|
||||
|
||||
Note that coercing _anything_ to strings to use as object keys is
|
||||
unsafe, unless you can be 100% certain that no other type of
|
||||
value will be used. For example:
|
||||
|
||||
```js
|
||||
const myCache = {}
|
||||
const set = (k, v) => (myCache[k] = v)
|
||||
const get = k => myCache[k]
|
||||
|
||||
set({}, 'please hang onto this for me')
|
||||
set('[object Object]', 'oopsie')
|
||||
```
|
||||
|
||||
Also beware of "Just So" stories regarding performance. Garbage
|
||||
collection of large (especially: deep) object graphs can be
|
||||
incredibly costly, with several "tipping points" where it
|
||||
increases exponentially. As a result, putting that off until
|
||||
later can make it much worse, and less predictable. If a library
|
||||
performs well, but only in a scenario where the object graph is
|
||||
kept shallow, then that won't help you if you are using large
|
||||
objects as keys.
|
||||
|
||||
In general, when attempting to use a library to improve
|
||||
performance (such as a cache like this one), it's best to choose
|
||||
an option that will perform well in the sorts of scenarios where
|
||||
you'll actually use it.
|
||||
|
||||
This library is optimized for repeated gets and minimizing
|
||||
eviction time, since that is the expected need of a LRU. Set
|
||||
operations are somewhat slower on average than a few other
|
||||
options, in part because of that optimization. It is assumed
|
||||
that you'll be caching some costly operation, ideally as rarely
|
||||
as possible, so optimizing set over get would be unwise.
|
||||
|
||||
If performance matters to you:
|
||||
|
||||
1. If it's at all possible to use small integer values as keys,
|
||||
and you can guarantee that no other types of values will be
|
||||
used as keys, then do that, and use a cache such as
|
||||
[lru-fast](https://npmjs.com/package/lru-fast), or
|
||||
[mnemonist's
|
||||
LRUCache](https://yomguithereal.github.io/mnemonist/lru-cache)
|
||||
which uses an Object as its data store.
|
||||
|
||||
2. Failing that, if at all possible, use short non-numeric
|
||||
strings (ie, less than 256 characters) as your keys, and use
|
||||
[mnemonist's
|
||||
LRUCache](https://yomguithereal.github.io/mnemonist/lru-cache).
|
||||
|
||||
3. If the types of your keys will be anything else, especially
|
||||
long strings, strings that look like floats, objects, or some
|
||||
mix of types, or if you aren't sure, then this library will
|
||||
work well for you.
|
||||
|
||||
If you do not need the features that this library provides
|
||||
(like asynchronous fetching, a variety of TTL staleness
|
||||
options, and so on), then [mnemonist's
|
||||
LRUMap](https://yomguithereal.github.io/mnemonist/lru-map) is
|
||||
a very good option, and just slightly faster than this module
|
||||
(since it does considerably less).
|
||||
|
||||
4. Do not use a `dispose` function, size tracking, or especially
|
||||
ttl behavior, unless absolutely needed. These features are
|
||||
convenient, and necessary in some use cases, and every attempt
|
||||
has been made to make the performance impact minimal, but it
|
||||
isn't nothing.
|
||||
|
||||
## Breaking Changes in Version 7
|
||||
|
||||
This library changed to a different algorithm and internal data
|
||||
structure in version 7, yielding significantly better
|
||||
performance, albeit with some subtle changes as a result.
|
||||
|
||||
If you were relying on the internals of LRUCache in version 6 or
|
||||
before, it probably will not work in version 7 and above.
|
||||
|
||||
## Breaking Changes in Version 8
|
||||
|
||||
- The `fetchContext` option was renamed to `context`, and may no
|
||||
longer be set on the cache instance itself.
|
||||
- Rewritten in TypeScript, so pretty much all the types moved
|
||||
around a lot.
|
||||
- The AbortController/AbortSignal polyfill was removed. For this
|
||||
reason, **Node version 16.14.0 or higher is now required**.
|
||||
- Internal properties were moved to actual private class
|
||||
properties.
|
||||
- Keys and values must not be `null` or `undefined`.
|
||||
- Minified export available at `'lru-cache/min'`, for both CJS
|
||||
and MJS builds.
|
||||
|
||||
## Breaking Changes in Version 9
|
||||
|
||||
- Named export only, no default export.
|
||||
- AbortController polyfill returned, albeit with a warning when
|
||||
used.
|
||||
|
||||
## Breaking Changes in Version 10
|
||||
|
||||
- `cache.fetch()` return type is now `Promise<V | undefined>`
|
||||
instead of `Promise<V | void>`. This is an irrelevant change
|
||||
practically speaking, but can require changes for TypeScript
|
||||
users.
|
||||
|
||||
For more info, see the [change log](CHANGELOG.md).
|
||||
113
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/package.json
generated
vendored
Normal file
113
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/package.json
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"name": "lru-cache",
|
||||
"description": "A cache object that deletes the least-recently-used items.",
|
||||
"version": "11.2.2",
|
||||
"author": "Isaac Z. Schlueter <i@izs.me>",
|
||||
"keywords": [
|
||||
"mru",
|
||||
"lru",
|
||||
"cache"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "npm run prepare",
|
||||
"prepare": "tshy && bash fixup.sh",
|
||||
"pretest": "npm run prepare",
|
||||
"presnap": "npm run prepare",
|
||||
"test": "tap",
|
||||
"snap": "tap",
|
||||
"preversion": "npm test",
|
||||
"postversion": "npm publish",
|
||||
"prepublishOnly": "git push origin --follow-tags",
|
||||
"format": "prettier --write .",
|
||||
"typedoc": "typedoc --tsconfig ./.tshy/esm.json ./src/*.ts",
|
||||
"benchmark-results-typedoc": "bash scripts/benchmark-results-typedoc.sh",
|
||||
"prebenchmark": "npm run prepare",
|
||||
"benchmark": "make -C benchmark",
|
||||
"preprofile": "npm run prepare",
|
||||
"profile": "make -C benchmark profile"
|
||||
},
|
||||
"main": "./dist/commonjs/index.js",
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"tshy": {
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./min": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.min.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"default": "./dist/commonjs/index.min.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/isaacs/node-lru-cache.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.3.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"esbuild": "^0.25.9",
|
||||
"marked": "^4.2.12",
|
||||
"mkdirp": "^3.0.1",
|
||||
"prettier": "^3.6.2",
|
||||
"tap": "^21.1.0",
|
||||
"tshy": "^3.0.2",
|
||||
"typedoc": "^0.28.12"
|
||||
},
|
||||
"license": "ISC",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"prettier": {
|
||||
"experimentalTernaries": true,
|
||||
"semi": false,
|
||||
"printWidth": 70,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": false,
|
||||
"bracketSameLine": true,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "lf"
|
||||
},
|
||||
"tap": {
|
||||
"node-arg": [
|
||||
"--expose-gc"
|
||||
],
|
||||
"plugin": [
|
||||
"@tapjs/clock"
|
||||
]
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"default": "./dist/commonjs/index.js"
|
||||
}
|
||||
},
|
||||
"./min": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.min.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"default": "./dist/commonjs/index.min.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "module",
|
||||
"module": "./dist/esm/index.js"
|
||||
}
|
||||
82
node_modules/@asamuzakjp/css-color/package.json
generated
vendored
Normal file
82
node_modules/@asamuzakjp/css-color/package.json
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"name": "@asamuzakjp/css-color",
|
||||
"description": "CSS color - Resolve and convert CSS colors.",
|
||||
"author": "asamuzaK",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/asamuzaK/cssColor.git"
|
||||
},
|
||||
"homepage": "https://github.com/asamuzaK/cssColor#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/asamuzaK/cssColor/issues"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "dist/esm/index.d.ts",
|
||||
"module": "dist/esm/index.js",
|
||||
"main": "dist/cjs/index.cjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/cjs/index.d.cts",
|
||||
"default": "./dist/cjs/index.cjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@csstools/css-calc": "^2.1.4",
|
||||
"@csstools/css-color-parser": "^3.1.0",
|
||||
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||
"@csstools/css-tokenizer": "^3.0.4",
|
||||
"lru-cache": "^11.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/vite-config": "^0.2.1",
|
||||
"@vitest/coverage-istanbul": "^3.2.4",
|
||||
"esbuild": "^0.25.10",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-regexp": "^2.10.0",
|
||||
"globals": "^16.4.0",
|
||||
"knip": "^5.64.0",
|
||||
"neostandard": "^0.12.2",
|
||||
"prettier": "^3.6.2",
|
||||
"publint": "^0.3.13",
|
||||
"rimraf": "^6.0.1",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^6.3.6",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"packageManager": "pnpm@10.14.0",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild",
|
||||
"oxc-resolver",
|
||||
"unrs-resolver"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm run clean && pnpm run test && pnpm run knip && pnpm run build:prod && pnpm run build:cjs && pnpm run build:browser && pnpm run publint",
|
||||
"build:browser": "vite build -c ./vite.browser.config.ts",
|
||||
"build:prod": "vite build",
|
||||
"build:cjs": "tsup ./src/index.ts --format=cjs --platform=node --outDir=./dist/cjs/ --sourcemap --dts",
|
||||
"clean": "rimraf ./coverage ./dist",
|
||||
"knip": "knip",
|
||||
"prettier": "prettier . --ignore-unknown --write",
|
||||
"publint": "publint --strict",
|
||||
"test": "pnpm run prettier && pnpm run --stream \"/^test:.*/\"",
|
||||
"test:eslint": "eslint ./src ./test --fix",
|
||||
"test:types": "tsc",
|
||||
"test:unit": "vitest"
|
||||
},
|
||||
"version": "4.0.5"
|
||||
}
|
||||
24
node_modules/@asamuzakjp/css-color/src/index.ts
generated
vendored
Normal file
24
node_modules/@asamuzakjp/css-color/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* CSS color - Resolve, parse, convert CSS color.
|
||||
* @license MIT
|
||||
* @copyright asamuzaK (Kazz)
|
||||
* @see {@link https://github.com/asamuzaK/cssColor/blob/main/LICENSE}
|
||||
*/
|
||||
|
||||
import { cssCalc as csscalc } from './js/css-calc';
|
||||
import { isGradient, resolveGradient } from './js/css-gradient';
|
||||
import { cssVar } from './js/css-var';
|
||||
import { extractDashedIdent, isColor as iscolor, splitValue } from './js/util';
|
||||
|
||||
export { convert } from './js/convert';
|
||||
export { resolve } from './js/resolve';
|
||||
/* utils */
|
||||
export const utils = {
|
||||
cssCalc: csscalc,
|
||||
cssVar,
|
||||
extractDashedIdent,
|
||||
isColor: iscolor,
|
||||
isGradient,
|
||||
resolveGradient,
|
||||
splitValue
|
||||
};
|
||||
114
node_modules/@asamuzakjp/css-color/src/js/cache.ts
generated
vendored
Normal file
114
node_modules/@asamuzakjp/css-color/src/js/cache.ts
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* cache
|
||||
*/
|
||||
|
||||
import { LRUCache } from 'lru-cache';
|
||||
import { Options } from './typedef';
|
||||
import { valueToJsonString } from './util';
|
||||
|
||||
/* numeric constants */
|
||||
const MAX_CACHE = 4096;
|
||||
|
||||
/**
|
||||
* CacheItem
|
||||
*/
|
||||
export class CacheItem {
|
||||
/* private */
|
||||
#isNull: boolean;
|
||||
#item: unknown;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
constructor(item: unknown, isNull: boolean = false) {
|
||||
this.#item = item;
|
||||
this.#isNull = !!isNull;
|
||||
}
|
||||
|
||||
get item() {
|
||||
return this.#item;
|
||||
}
|
||||
|
||||
get isNull() {
|
||||
return this.#isNull;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NullObject
|
||||
*/
|
||||
export class NullObject extends CacheItem {
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
constructor() {
|
||||
super(Symbol('null'), true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* lru cache
|
||||
*/
|
||||
export const lruCache = new LRUCache({
|
||||
max: MAX_CACHE
|
||||
});
|
||||
|
||||
/**
|
||||
* set cache
|
||||
* @param key - cache key
|
||||
* @param value - value to cache
|
||||
* @returns void
|
||||
*/
|
||||
export const setCache = (key: string, value: unknown): void => {
|
||||
if (key) {
|
||||
if (value === null) {
|
||||
lruCache.set(key, new NullObject());
|
||||
} else if (value instanceof CacheItem) {
|
||||
lruCache.set(key, value);
|
||||
} else {
|
||||
lruCache.set(key, new CacheItem(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* get cache
|
||||
* @param key - cache key
|
||||
* @returns cached item or false otherwise
|
||||
*/
|
||||
export const getCache = (key: string): CacheItem | boolean => {
|
||||
if (key && lruCache.has(key)) {
|
||||
const item = lruCache.get(key);
|
||||
if (item instanceof CacheItem) {
|
||||
return item;
|
||||
}
|
||||
// delete unexpected cached item
|
||||
lruCache.delete(key);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* create cache key
|
||||
* @param keyData - key data
|
||||
* @param [opt] - options
|
||||
* @returns cache key
|
||||
*/
|
||||
export const createCacheKey = (
|
||||
keyData: Record<string, string>,
|
||||
opt: Options = {}
|
||||
): string => {
|
||||
const { customProperty = {}, dimension = {} } = opt;
|
||||
let cacheKey = '';
|
||||
if (
|
||||
keyData &&
|
||||
Object.keys(keyData).length &&
|
||||
typeof customProperty.callback !== 'function' &&
|
||||
typeof dimension.callback !== 'function'
|
||||
) {
|
||||
keyData.opt = valueToJsonString(opt);
|
||||
cacheKey = valueToJsonString(keyData);
|
||||
}
|
||||
return cacheKey;
|
||||
};
|
||||
3511
node_modules/@asamuzakjp/css-color/src/js/color.ts
generated
vendored
Normal file
3511
node_modules/@asamuzakjp/css-color/src/js/color.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
31
node_modules/@asamuzakjp/css-color/src/js/common.ts
generated
vendored
Normal file
31
node_modules/@asamuzakjp/css-color/src/js/common.ts
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* common
|
||||
*/
|
||||
|
||||
/* numeric constants */
|
||||
const TYPE_FROM = 8;
|
||||
const TYPE_TO = -1;
|
||||
|
||||
/**
|
||||
* get type
|
||||
* @param o - object to check
|
||||
* @returns type of object
|
||||
*/
|
||||
export const getType = (o: unknown): string =>
|
||||
Object.prototype.toString.call(o).slice(TYPE_FROM, TYPE_TO);
|
||||
|
||||
/**
|
||||
* is string
|
||||
* @param o - object to check
|
||||
* @returns result
|
||||
*/
|
||||
export const isString = (o: unknown): o is string =>
|
||||
typeof o === 'string' || o instanceof String;
|
||||
|
||||
/**
|
||||
* is string or number
|
||||
* @param o - object to check
|
||||
* @returns result
|
||||
*/
|
||||
export const isStringOrNumber = (o: unknown): boolean =>
|
||||
isString(o) || typeof o === 'number';
|
||||
68
node_modules/@asamuzakjp/css-color/src/js/constant.ts
generated
vendored
Normal file
68
node_modules/@asamuzakjp/css-color/src/js/constant.ts
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* constant
|
||||
*/
|
||||
|
||||
/* values and units */
|
||||
const _DIGIT = '(?:0|[1-9]\\d*)';
|
||||
const _COMPARE = 'clamp|max|min';
|
||||
const _EXPO = 'exp|hypot|log|pow|sqrt';
|
||||
const _SIGN = 'abs|sign';
|
||||
const _STEP = 'mod|rem|round';
|
||||
const _TRIG = 'a?(?:cos|sin|tan)|atan2';
|
||||
const _MATH = `${_COMPARE}|${_EXPO}|${_SIGN}|${_STEP}|${_TRIG}`;
|
||||
const _CALC = `calc|${_MATH}`;
|
||||
const _VAR = `var|${_CALC}`;
|
||||
export const ANGLE = 'deg|g?rad|turn';
|
||||
export const LENGTH =
|
||||
'[cm]m|[dls]?v(?:[bhiw]|max|min)|in|p[ctx]|q|r?(?:[cl]h|cap|e[mx]|ic)';
|
||||
export const NUM = `[+-]?(?:${_DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${_DIGIT})?`;
|
||||
export const NUM_POSITIVE = `\\+?(?:${_DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${_DIGIT})?`;
|
||||
export const NONE = 'none';
|
||||
export const PCT = `${NUM}%`;
|
||||
export const SYN_FN_CALC = `^(?:${_CALC})\\(|(?<=[*\\/\\s\\(])(?:${_CALC})\\(`;
|
||||
export const SYN_FN_MATH_START = `^(?:${_MATH})\\($`;
|
||||
export const SYN_FN_VAR = '^var\\(|(?<=[*\\/\\s\\(])var\\(';
|
||||
export const SYN_FN_VAR_START = `^(?:${_VAR})\\(`;
|
||||
|
||||
/* colors */
|
||||
const _ALPHA = `(?:\\s*\\/\\s*(?:${NUM}|${PCT}|${NONE}))?`;
|
||||
const _ALPHA_LV3 = `(?:\\s*,\\s*(?:${NUM}|${PCT}))?`;
|
||||
const _COLOR_FUNC = '(?:ok)?l(?:ab|ch)|color|hsla?|hwb|rgba?';
|
||||
const _COLOR_KEY = '[a-z]+|#[\\da-f]{3}|#[\\da-f]{4}|#[\\da-f]{6}|#[\\da-f]{8}';
|
||||
const _CS_HUE = '(?:ok)?lch|hsl|hwb';
|
||||
const _CS_HUE_ARC = '(?:de|in)creasing|longer|shorter';
|
||||
const _NUM_ANGLE = `${NUM}(?:${ANGLE})?`;
|
||||
const _NUM_ANGLE_NONE = `(?:${NUM}(?:${ANGLE})?|${NONE})`;
|
||||
const _NUM_PCT_NONE = `(?:${NUM}|${PCT}|${NONE})`;
|
||||
export const CS_HUE = `(?:${_CS_HUE})(?:\\s(?:${_CS_HUE_ARC})\\shue)?`;
|
||||
export const CS_HUE_CAPT = `(${_CS_HUE})(?:\\s(${_CS_HUE_ARC})\\shue)?`;
|
||||
export const CS_LAB = '(?:ok)?lab';
|
||||
export const CS_LCH = '(?:ok)?lch';
|
||||
export const CS_SRGB = 'srgb(?:-linear)?';
|
||||
export const CS_RGB = `(?:a98|prophoto)-rgb|display-p3|rec2020|${CS_SRGB}`;
|
||||
export const CS_XYZ = 'xyz(?:-d(?:50|65))?';
|
||||
export const CS_RECT = `${CS_LAB}|${CS_RGB}|${CS_XYZ}`;
|
||||
export const CS_MIX = `${CS_HUE}|${CS_RECT}`;
|
||||
export const FN_COLOR = 'color(';
|
||||
export const FN_LIGHT_DARK = 'light-dark(';
|
||||
export const FN_MIX = 'color-mix(';
|
||||
export const FN_REL = `(?:${_COLOR_FUNC})\\(\\s*from\\s+`;
|
||||
export const FN_REL_CAPT = `(${_COLOR_FUNC})\\(\\s*from\\s+`;
|
||||
export const FN_VAR = 'var(';
|
||||
export const SYN_FN_COLOR = `(?:${CS_RGB}|${CS_XYZ})(?:\\s+${_NUM_PCT_NONE}){3}${_ALPHA}`;
|
||||
export const SYN_FN_LIGHT_DARK = '^light-dark\\(';
|
||||
export const SYN_FN_REL = `^${FN_REL}|(?<=[\\s])${FN_REL}`;
|
||||
export const SYN_HSL = `${_NUM_ANGLE_NONE}(?:\\s+${_NUM_PCT_NONE}){2}${_ALPHA}`;
|
||||
export const SYN_HSL_LV3 = `${_NUM_ANGLE}(?:\\s*,\\s*${PCT}){2}${_ALPHA_LV3}`;
|
||||
export const SYN_LCH = `(?:${_NUM_PCT_NONE}\\s+){2}${_NUM_ANGLE_NONE}${_ALPHA}`;
|
||||
export const SYN_MOD = `${_NUM_PCT_NONE}(?:\\s+${_NUM_PCT_NONE}){2}${_ALPHA}`;
|
||||
export const SYN_RGB_LV3 = `(?:${NUM}(?:\\s*,\\s*${NUM}){2}|${PCT}(?:\\s*,\\s*${PCT}){2})${_ALPHA_LV3}`;
|
||||
export const SYN_COLOR_TYPE = `${_COLOR_KEY}|hsla?\\(\\s*${SYN_HSL_LV3}\\s*\\)|rgba?\\(\\s*${SYN_RGB_LV3}\\s*\\)|(?:hsla?|hwb)\\(\\s*${SYN_HSL}\\s*\\)|(?:(?:ok)?lab|rgba?)\\(\\s*${SYN_MOD}\\s*\\)|(?:ok)?lch\\(\\s*${SYN_LCH}\\s*\\)|color\\(\\s*${SYN_FN_COLOR}\\s*\\)`;
|
||||
export const SYN_MIX_PART = `(?:${SYN_COLOR_TYPE})(?:\\s+${PCT})?`;
|
||||
export const SYN_MIX = `color-mix\\(\\s*in\\s+(?:${CS_MIX})\\s*,\\s*${SYN_MIX_PART}\\s*,\\s*${SYN_MIX_PART}\\s*\\)`;
|
||||
export const SYN_MIX_CAPT = `color-mix\\(\\s*in\\s+(${CS_MIX})\\s*,\\s*(${SYN_MIX_PART})\\s*,\\s*(${SYN_MIX_PART})\\s*\\)`;
|
||||
|
||||
/* formats */
|
||||
export const VAL_COMP = 'computedValue';
|
||||
export const VAL_MIX = 'mixValue';
|
||||
export const VAL_SPEC = 'specifiedValue';
|
||||
469
node_modules/@asamuzakjp/css-color/src/js/convert.ts
generated
vendored
Normal file
469
node_modules/@asamuzakjp/css-color/src/js/convert.ts
generated
vendored
Normal file
@@ -0,0 +1,469 @@
|
||||
/**
|
||||
* convert
|
||||
*/
|
||||
|
||||
import {
|
||||
CacheItem,
|
||||
NullObject,
|
||||
createCacheKey,
|
||||
getCache,
|
||||
setCache
|
||||
} from './cache';
|
||||
import {
|
||||
convertColorToHsl,
|
||||
convertColorToHwb,
|
||||
convertColorToLab,
|
||||
convertColorToLch,
|
||||
convertColorToOklab,
|
||||
convertColorToOklch,
|
||||
convertColorToRgb,
|
||||
numberToHexString,
|
||||
parseColorFunc,
|
||||
parseColorValue
|
||||
} from './color';
|
||||
import { isString } from './common';
|
||||
import { cssCalc } from './css-calc';
|
||||
import { resolveVar } from './css-var';
|
||||
import { resolveRelativeColor } from './relative-color';
|
||||
import { resolveColor } from './resolve';
|
||||
import { ColorChannels, ComputedColorChannels, Options } from './typedef';
|
||||
|
||||
/* constants */
|
||||
import { SYN_FN_CALC, SYN_FN_REL, SYN_FN_VAR, VAL_COMP } from './constant';
|
||||
const NAMESPACE = 'convert';
|
||||
|
||||
/* regexp */
|
||||
const REG_FN_CALC = new RegExp(SYN_FN_CALC);
|
||||
const REG_FN_REL = new RegExp(SYN_FN_REL);
|
||||
const REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
|
||||
/**
|
||||
* pre process
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns value
|
||||
*/
|
||||
export const preProcess = (
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): string | NullObject => {
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
if (!value) {
|
||||
return new NullObject();
|
||||
}
|
||||
} else {
|
||||
return new NullObject();
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'preProcess',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return cachedResult as NullObject;
|
||||
}
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
if (REG_FN_VAR.test(value)) {
|
||||
const resolvedValue = resolveVar(value, opt);
|
||||
if (isString(resolvedValue)) {
|
||||
value = resolvedValue;
|
||||
} else {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
}
|
||||
if (REG_FN_REL.test(value)) {
|
||||
const resolvedValue = resolveRelativeColor(value, opt);
|
||||
if (isString(resolvedValue)) {
|
||||
value = resolvedValue;
|
||||
} else {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
} else if (REG_FN_CALC.test(value)) {
|
||||
value = cssCalc(value, opt);
|
||||
}
|
||||
if (value.startsWith('color-mix')) {
|
||||
const clonedOpt = structuredClone(opt);
|
||||
clonedOpt.format = VAL_COMP;
|
||||
clonedOpt.nullable = true;
|
||||
const resolvedValue = resolveColor(value, clonedOpt);
|
||||
setCache(cacheKey, resolvedValue);
|
||||
return resolvedValue;
|
||||
}
|
||||
setCache(cacheKey, value);
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert number to hex string
|
||||
* @param value - numeric value
|
||||
* @returns hex string: 00..ff
|
||||
*/
|
||||
export const numberToHex = (value: number): string => {
|
||||
const hex = numberToHexString(value);
|
||||
return hex;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert color to hex
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @param [opt.alpha] - enable alpha channel
|
||||
* @returns #rrggbb | #rrggbbaa | null
|
||||
*/
|
||||
export const colorToHex = (value: string, opt: Options = {}): string | null => {
|
||||
if (isString(value)) {
|
||||
const resolvedValue = preProcess(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
return null;
|
||||
}
|
||||
value = resolvedValue.toLowerCase();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const { alpha = false } = opt;
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'colorToHex',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return null;
|
||||
}
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
let hex;
|
||||
opt.nullable = true;
|
||||
if (alpha) {
|
||||
opt.format = 'hexAlpha';
|
||||
hex = resolveColor(value, opt);
|
||||
} else {
|
||||
opt.format = 'hex';
|
||||
hex = resolveColor(value, opt);
|
||||
}
|
||||
if (isString(hex)) {
|
||||
setCache(cacheKey, hex);
|
||||
return hex;
|
||||
}
|
||||
setCache(cacheKey, null);
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert color to hsl
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [h, s, l, alpha]
|
||||
*/
|
||||
export const colorToHsl = (value: string, opt: Options = {}): ColorChannels => {
|
||||
if (isString(value)) {
|
||||
const resolvedValue = preProcess(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
value = resolvedValue.toLowerCase();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'colorToHsl',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as ColorChannels;
|
||||
}
|
||||
opt.format = 'hsl';
|
||||
const hsl = convertColorToHsl(value, opt) as ColorChannels;
|
||||
setCache(cacheKey, hsl);
|
||||
return hsl;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert color to hwb
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [h, w, b, alpha]
|
||||
*/
|
||||
export const colorToHwb = (value: string, opt: Options = {}): ColorChannels => {
|
||||
if (isString(value)) {
|
||||
const resolvedValue = preProcess(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
value = resolvedValue.toLowerCase();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'colorToHwb',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as ColorChannels;
|
||||
}
|
||||
opt.format = 'hwb';
|
||||
const hwb = convertColorToHwb(value, opt) as ColorChannels;
|
||||
setCache(cacheKey, hwb);
|
||||
return hwb;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert color to lab
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, a, b, alpha]
|
||||
*/
|
||||
export const colorToLab = (value: string, opt: Options = {}): ColorChannels => {
|
||||
if (isString(value)) {
|
||||
const resolvedValue = preProcess(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
value = resolvedValue.toLowerCase();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'colorToLab',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as ColorChannels;
|
||||
}
|
||||
const lab = convertColorToLab(value, opt) as ColorChannels;
|
||||
setCache(cacheKey, lab);
|
||||
return lab;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert color to lch
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, c, h, alpha]
|
||||
*/
|
||||
export const colorToLch = (value: string, opt: Options = {}): ColorChannels => {
|
||||
if (isString(value)) {
|
||||
const resolvedValue = preProcess(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
value = resolvedValue.toLowerCase();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'colorToLch',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as ColorChannels;
|
||||
}
|
||||
const lch = convertColorToLch(value, opt) as ColorChannels;
|
||||
setCache(cacheKey, lch);
|
||||
return lch;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert color to oklab
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, a, b, alpha]
|
||||
*/
|
||||
export const colorToOklab = (
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): ColorChannels => {
|
||||
if (isString(value)) {
|
||||
const resolvedValue = preProcess(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
value = resolvedValue.toLowerCase();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'colorToOklab',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as ColorChannels;
|
||||
}
|
||||
const lab = convertColorToOklab(value, opt) as ColorChannels;
|
||||
setCache(cacheKey, lab);
|
||||
return lab;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert color to oklch
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, c, h, alpha]
|
||||
*/
|
||||
export const colorToOklch = (
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): ColorChannels => {
|
||||
if (isString(value)) {
|
||||
const resolvedValue = preProcess(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
value = resolvedValue.toLowerCase();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'colorToOklch',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as ColorChannels;
|
||||
}
|
||||
const lch = convertColorToOklch(value, opt) as ColorChannels;
|
||||
setCache(cacheKey, lch);
|
||||
return lch;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert color to rgb
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [r, g, b, alpha]
|
||||
*/
|
||||
export const colorToRgb = (value: string, opt: Options = {}): ColorChannels => {
|
||||
if (isString(value)) {
|
||||
const resolvedValue = preProcess(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
value = resolvedValue.toLowerCase();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'colorToRgb',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as ColorChannels;
|
||||
}
|
||||
const rgb = convertColorToRgb(value, opt) as ColorChannels;
|
||||
setCache(cacheKey, rgb);
|
||||
return rgb;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert color to xyz
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [x, y, z, alpha]
|
||||
*/
|
||||
export const colorToXyz = (value: string, opt: Options = {}): ColorChannels => {
|
||||
if (isString(value)) {
|
||||
const resolvedValue = preProcess(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
value = resolvedValue.toLowerCase();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'colorToXyz',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as ColorChannels;
|
||||
}
|
||||
let xyz;
|
||||
if (value.startsWith('color(')) {
|
||||
[, ...xyz] = parseColorFunc(value, opt) as ComputedColorChannels;
|
||||
} else {
|
||||
[, ...xyz] = parseColorValue(value, opt) as ComputedColorChannels;
|
||||
}
|
||||
setCache(cacheKey, xyz);
|
||||
return xyz as ColorChannels;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert color to xyz-d50
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [x, y, z, alpha]
|
||||
*/
|
||||
export const colorToXyzD50 = (
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): ColorChannels => {
|
||||
opt.d50 = true;
|
||||
return colorToXyz(value, opt);
|
||||
};
|
||||
|
||||
/* convert */
|
||||
export const convert = {
|
||||
colorToHex,
|
||||
colorToHsl,
|
||||
colorToHwb,
|
||||
colorToLab,
|
||||
colorToLch,
|
||||
colorToOklab,
|
||||
colorToOklch,
|
||||
colorToRgb,
|
||||
colorToXyz,
|
||||
colorToXyzD50,
|
||||
numberToHex
|
||||
};
|
||||
965
node_modules/@asamuzakjp/css-color/src/js/css-calc.ts
generated
vendored
Normal file
965
node_modules/@asamuzakjp/css-color/src/js/css-calc.ts
generated
vendored
Normal file
@@ -0,0 +1,965 @@
|
||||
/**
|
||||
* css-calc
|
||||
*/
|
||||
|
||||
import { calc } from '@csstools/css-calc';
|
||||
import { CSSToken, TokenType, tokenize } from '@csstools/css-tokenizer';
|
||||
import {
|
||||
CacheItem,
|
||||
NullObject,
|
||||
createCacheKey,
|
||||
getCache,
|
||||
setCache
|
||||
} from './cache';
|
||||
import { isString, isStringOrNumber } from './common';
|
||||
import { resolveVar } from './css-var';
|
||||
import { roundToPrecision } from './util';
|
||||
import { MatchedRegExp, Options } from './typedef';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
ANGLE,
|
||||
LENGTH,
|
||||
NUM,
|
||||
SYN_FN_CALC,
|
||||
SYN_FN_MATH_START,
|
||||
SYN_FN_VAR,
|
||||
SYN_FN_VAR_START,
|
||||
VAL_SPEC
|
||||
} from './constant';
|
||||
const {
|
||||
CloseParen: PAREN_CLOSE,
|
||||
Comment: COMMENT,
|
||||
Dimension: DIM,
|
||||
EOF,
|
||||
Function: FUNC,
|
||||
OpenParen: PAREN_OPEN,
|
||||
Whitespace: W_SPACE
|
||||
} = TokenType;
|
||||
const NAMESPACE = 'css-calc';
|
||||
|
||||
/* numeric constants */
|
||||
const TRIA = 3;
|
||||
const HEX = 16;
|
||||
const MAX_PCT = 100;
|
||||
|
||||
/* regexp */
|
||||
const REG_FN_CALC = new RegExp(SYN_FN_CALC);
|
||||
const REG_FN_CALC_NUM = new RegExp(`^calc\\((${NUM})\\)$`);
|
||||
const REG_FN_MATH_START = new RegExp(SYN_FN_MATH_START);
|
||||
const REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
const REG_FN_VAR_START = new RegExp(SYN_FN_VAR_START);
|
||||
const REG_OPERATOR = /\s[*+/-]\s/;
|
||||
const REG_TYPE_DIM = new RegExp(`^(${NUM})(${ANGLE}|${LENGTH})$`);
|
||||
const REG_TYPE_DIM_PCT = new RegExp(`^(${NUM})(${ANGLE}|${LENGTH}|%)$`);
|
||||
const REG_TYPE_PCT = new RegExp(`^(${NUM})%$`);
|
||||
|
||||
/**
|
||||
* Calclator
|
||||
*/
|
||||
export class Calculator {
|
||||
/* private */
|
||||
// number
|
||||
#hasNum: boolean;
|
||||
#numSum: number[];
|
||||
#numMul: number[];
|
||||
// percentage
|
||||
#hasPct: boolean;
|
||||
#pctSum: number[];
|
||||
#pctMul: number[];
|
||||
// dimension
|
||||
#hasDim: boolean;
|
||||
#dimSum: string[];
|
||||
#dimSub: string[];
|
||||
#dimMul: string[];
|
||||
#dimDiv: string[];
|
||||
// et cetra
|
||||
#hasEtc: boolean;
|
||||
#etcSum: string[];
|
||||
#etcSub: string[];
|
||||
#etcMul: string[];
|
||||
#etcDiv: string[];
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
constructor() {
|
||||
// number
|
||||
this.#hasNum = false;
|
||||
this.#numSum = [];
|
||||
this.#numMul = [];
|
||||
// percentage
|
||||
this.#hasPct = false;
|
||||
this.#pctSum = [];
|
||||
this.#pctMul = [];
|
||||
// dimension
|
||||
this.#hasDim = false;
|
||||
this.#dimSum = [];
|
||||
this.#dimSub = [];
|
||||
this.#dimMul = [];
|
||||
this.#dimDiv = [];
|
||||
// et cetra
|
||||
this.#hasEtc = false;
|
||||
this.#etcSum = [];
|
||||
this.#etcSub = [];
|
||||
this.#etcMul = [];
|
||||
this.#etcDiv = [];
|
||||
}
|
||||
|
||||
get hasNum() {
|
||||
return this.#hasNum;
|
||||
}
|
||||
|
||||
set hasNum(value: boolean) {
|
||||
this.#hasNum = !!value;
|
||||
}
|
||||
|
||||
get numSum() {
|
||||
return this.#numSum;
|
||||
}
|
||||
|
||||
get numMul() {
|
||||
return this.#numMul;
|
||||
}
|
||||
|
||||
get hasPct() {
|
||||
return this.#hasPct;
|
||||
}
|
||||
|
||||
set hasPct(value: boolean) {
|
||||
this.#hasPct = !!value;
|
||||
}
|
||||
|
||||
get pctSum() {
|
||||
return this.#pctSum;
|
||||
}
|
||||
|
||||
get pctMul() {
|
||||
return this.#pctMul;
|
||||
}
|
||||
|
||||
get hasDim() {
|
||||
return this.#hasDim;
|
||||
}
|
||||
|
||||
set hasDim(value: boolean) {
|
||||
this.#hasDim = !!value;
|
||||
}
|
||||
|
||||
get dimSum() {
|
||||
return this.#dimSum;
|
||||
}
|
||||
|
||||
get dimSub() {
|
||||
return this.#dimSub;
|
||||
}
|
||||
|
||||
get dimMul() {
|
||||
return this.#dimMul;
|
||||
}
|
||||
|
||||
get dimDiv() {
|
||||
return this.#dimDiv;
|
||||
}
|
||||
|
||||
get hasEtc() {
|
||||
return this.#hasEtc;
|
||||
}
|
||||
|
||||
set hasEtc(value: boolean) {
|
||||
this.#hasEtc = !!value;
|
||||
}
|
||||
|
||||
get etcSum() {
|
||||
return this.#etcSum;
|
||||
}
|
||||
|
||||
get etcSub() {
|
||||
return this.#etcSub;
|
||||
}
|
||||
|
||||
get etcMul() {
|
||||
return this.#etcMul;
|
||||
}
|
||||
|
||||
get etcDiv() {
|
||||
return this.#etcDiv;
|
||||
}
|
||||
|
||||
/**
|
||||
* clear values
|
||||
* @returns void
|
||||
*/
|
||||
clear() {
|
||||
// number
|
||||
this.#hasNum = false;
|
||||
this.#numSum = [];
|
||||
this.#numMul = [];
|
||||
// percentage
|
||||
this.#hasPct = false;
|
||||
this.#pctSum = [];
|
||||
this.#pctMul = [];
|
||||
// dimension
|
||||
this.#hasDim = false;
|
||||
this.#dimSum = [];
|
||||
this.#dimSub = [];
|
||||
this.#dimMul = [];
|
||||
this.#dimDiv = [];
|
||||
// et cetra
|
||||
this.#hasEtc = false;
|
||||
this.#etcSum = [];
|
||||
this.#etcSub = [];
|
||||
this.#etcMul = [];
|
||||
this.#etcDiv = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* sort values
|
||||
* @param values - values
|
||||
* @returns sorted values
|
||||
*/
|
||||
sort(values: string[] = []): string[] {
|
||||
const arr = [...values];
|
||||
if (arr.length > 1) {
|
||||
arr.sort((a, b) => {
|
||||
let res;
|
||||
if (REG_TYPE_DIM_PCT.test(a) && REG_TYPE_DIM_PCT.test(b)) {
|
||||
const [, valA, unitA] = a.match(REG_TYPE_DIM_PCT) as MatchedRegExp;
|
||||
const [, valB, unitB] = b.match(REG_TYPE_DIM_PCT) as MatchedRegExp;
|
||||
if (unitA === unitB) {
|
||||
if (Number(valA) === Number(valB)) {
|
||||
res = 0;
|
||||
} else if (Number(valA) > Number(valB)) {
|
||||
res = 1;
|
||||
} else {
|
||||
res = -1;
|
||||
}
|
||||
} else if (unitA > unitB) {
|
||||
res = 1;
|
||||
} else {
|
||||
res = -1;
|
||||
}
|
||||
} else {
|
||||
if (a === b) {
|
||||
res = 0;
|
||||
} else if (a > b) {
|
||||
res = 1;
|
||||
} else {
|
||||
res = -1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* multiply values
|
||||
* @returns resolved value
|
||||
*/
|
||||
multiply(): string {
|
||||
const value = [];
|
||||
let num;
|
||||
if (this.#hasNum) {
|
||||
num = 1;
|
||||
for (const i of this.#numMul) {
|
||||
num *= i;
|
||||
if (num === 0 || !Number.isFinite(num) || Number.isNaN(num)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!this.#hasPct && !this.#hasDim && !this.hasEtc) {
|
||||
if (Number.isFinite(num)) {
|
||||
num = roundToPrecision(num, HEX);
|
||||
}
|
||||
value.push(num);
|
||||
}
|
||||
}
|
||||
if (this.#hasPct) {
|
||||
if (typeof num !== 'number') {
|
||||
num = 1;
|
||||
}
|
||||
for (const i of this.#pctMul) {
|
||||
num *= i;
|
||||
if (num === 0 || !Number.isFinite(num) || Number.isNaN(num)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Number.isFinite(num)) {
|
||||
num = `${roundToPrecision(num, HEX)}%`;
|
||||
}
|
||||
if (!this.#hasDim && !this.hasEtc) {
|
||||
value.push(num);
|
||||
}
|
||||
}
|
||||
if (this.#hasDim) {
|
||||
let dim = '';
|
||||
let mul = '';
|
||||
let div = '';
|
||||
if (this.#dimMul.length) {
|
||||
if (this.#dimMul.length === 1) {
|
||||
[mul] = this.#dimMul as [string];
|
||||
} else {
|
||||
mul = `${this.sort(this.#dimMul).join(' * ')}`;
|
||||
}
|
||||
}
|
||||
if (this.#dimDiv.length) {
|
||||
if (this.#dimDiv.length === 1) {
|
||||
[div] = this.#dimDiv as [string];
|
||||
} else {
|
||||
div = `${this.sort(this.#dimDiv).join(' * ')}`;
|
||||
}
|
||||
}
|
||||
if (Number.isFinite(num)) {
|
||||
if (mul) {
|
||||
if (div) {
|
||||
if (div.includes('*')) {
|
||||
dim = calc(`calc(${num} * ${mul} / (${div}))`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
} else {
|
||||
dim = calc(`calc(${num} * ${mul} / ${div})`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
dim = calc(`calc(${num} * ${mul})`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
}
|
||||
} else if (div.includes('*')) {
|
||||
dim = calc(`calc(${num} / (${div}))`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
} else {
|
||||
dim = calc(`calc(${num} / ${div})`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
}
|
||||
value.push(dim.replace(/^calc/, ''));
|
||||
} else {
|
||||
if (!value.length && num !== undefined) {
|
||||
value.push(num);
|
||||
}
|
||||
if (mul) {
|
||||
if (div) {
|
||||
if (div.includes('*')) {
|
||||
dim = calc(`calc(${mul} / (${div}))`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
} else {
|
||||
dim = calc(`calc(${mul} / ${div})`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
dim = calc(`calc(${mul})`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
}
|
||||
if (value.length) {
|
||||
value.push('*', dim.replace(/^calc/, ''));
|
||||
} else {
|
||||
value.push(dim.replace(/^calc/, ''));
|
||||
}
|
||||
} else {
|
||||
dim = calc(`calc(${div})`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
if (value.length) {
|
||||
value.push('/', dim.replace(/^calc/, ''));
|
||||
} else {
|
||||
value.push('1', '/', dim.replace(/^calc/, ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.#hasEtc) {
|
||||
if (this.#etcMul.length) {
|
||||
if (!value.length && num !== undefined) {
|
||||
value.push(num);
|
||||
}
|
||||
const mul = this.sort(this.#etcMul).join(' * ');
|
||||
if (value.length) {
|
||||
value.push(`* ${mul}`);
|
||||
} else {
|
||||
value.push(`${mul}`);
|
||||
}
|
||||
}
|
||||
if (this.#etcDiv.length) {
|
||||
const div = this.sort(this.#etcDiv).join(' * ');
|
||||
if (div.includes('*')) {
|
||||
if (value.length) {
|
||||
value.push(`/ (${div})`);
|
||||
} else {
|
||||
value.push(`1 / (${div})`);
|
||||
}
|
||||
} else if (value.length) {
|
||||
value.push(`/ ${div}`);
|
||||
} else {
|
||||
value.push(`1 / ${div}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value.length) {
|
||||
return value.join(' ');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* sum values
|
||||
* @returns resolved value
|
||||
*/
|
||||
sum(): string {
|
||||
const value = [];
|
||||
if (this.#hasNum) {
|
||||
let num = 0;
|
||||
for (const i of this.#numSum) {
|
||||
num += i;
|
||||
if (!Number.isFinite(num) || Number.isNaN(num)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
value.push(num);
|
||||
}
|
||||
if (this.#hasPct) {
|
||||
let num: number | string = 0;
|
||||
for (const i of this.#pctSum) {
|
||||
num += i;
|
||||
if (!Number.isFinite(num)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Number.isFinite(num)) {
|
||||
num = `${num}%`;
|
||||
}
|
||||
if (value.length) {
|
||||
value.push(`+ ${num}`);
|
||||
} else {
|
||||
value.push(num);
|
||||
}
|
||||
}
|
||||
if (this.#hasDim) {
|
||||
let dim, sum, sub;
|
||||
if (this.#dimSum.length) {
|
||||
sum = this.sort(this.#dimSum).join(' + ');
|
||||
}
|
||||
if (this.#dimSub.length) {
|
||||
sub = this.sort(this.#dimSub).join(' + ');
|
||||
}
|
||||
if (sum) {
|
||||
if (sub) {
|
||||
if (sub.includes('-')) {
|
||||
dim = calc(`calc(${sum} - (${sub}))`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
} else {
|
||||
dim = calc(`calc(${sum} - ${sub})`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
dim = calc(`calc(${sum})`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
dim = calc(`calc(-1 * (${sub}))`, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
}
|
||||
if (value.length) {
|
||||
value.push('+', dim.replace(/^calc/, ''));
|
||||
} else {
|
||||
value.push(dim.replace(/^calc/, ''));
|
||||
}
|
||||
}
|
||||
if (this.#hasEtc) {
|
||||
if (this.#etcSum.length) {
|
||||
const sum = this.sort(this.#etcSum)
|
||||
.map(item => {
|
||||
let res;
|
||||
if (
|
||||
REG_OPERATOR.test(item) &&
|
||||
!item.startsWith('(') &&
|
||||
!item.endsWith(')')
|
||||
) {
|
||||
res = `(${item})`;
|
||||
} else {
|
||||
res = item;
|
||||
}
|
||||
return res;
|
||||
})
|
||||
.join(' + ');
|
||||
if (value.length) {
|
||||
if (this.#etcSum.length > 1) {
|
||||
value.push(`+ (${sum})`);
|
||||
} else {
|
||||
value.push(`+ ${sum}`);
|
||||
}
|
||||
} else {
|
||||
value.push(`${sum}`);
|
||||
}
|
||||
}
|
||||
if (this.#etcSub.length) {
|
||||
const sub = this.sort(this.#etcSub)
|
||||
.map(item => {
|
||||
let res;
|
||||
if (
|
||||
REG_OPERATOR.test(item) &&
|
||||
!item.startsWith('(') &&
|
||||
!item.endsWith(')')
|
||||
) {
|
||||
res = `(${item})`;
|
||||
} else {
|
||||
res = item;
|
||||
}
|
||||
return res;
|
||||
})
|
||||
.join(' + ');
|
||||
if (value.length) {
|
||||
if (this.#etcSub.length > 1) {
|
||||
value.push(`- (${sub})`);
|
||||
} else {
|
||||
value.push(`- ${sub}`);
|
||||
}
|
||||
} else if (this.#etcSub.length > 1) {
|
||||
value.push(`-1 * (${sub})`);
|
||||
} else {
|
||||
value.push(`-1 * ${sub}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value.length) {
|
||||
return value.join(' ');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sort calc values
|
||||
* @param values - values to sort
|
||||
* @param [finalize] - finalize values
|
||||
* @returns sorted values
|
||||
*/
|
||||
export const sortCalcValues = (
|
||||
values: (number | string)[] = [],
|
||||
finalize: boolean = false
|
||||
): string => {
|
||||
if (values.length < TRIA) {
|
||||
throw new Error(`Unexpected array length ${values.length}.`);
|
||||
}
|
||||
const start = values.shift();
|
||||
if (!isString(start) || !start.endsWith('(')) {
|
||||
throw new Error(`Unexpected token ${start}.`);
|
||||
}
|
||||
const end = values.pop();
|
||||
if (end !== ')') {
|
||||
throw new Error(`Unexpected token ${end}.`);
|
||||
}
|
||||
if (values.length === 1) {
|
||||
const [value] = values;
|
||||
if (!isStringOrNumber(value)) {
|
||||
throw new Error(`Unexpected token ${value}.`);
|
||||
}
|
||||
return `${start}${value}${end}`;
|
||||
}
|
||||
const sortedValues = [];
|
||||
const cal = new Calculator();
|
||||
let operator: string = '';
|
||||
const l = values.length;
|
||||
for (let i = 0; i < l; i++) {
|
||||
const value = values[i];
|
||||
if (!isStringOrNumber(value)) {
|
||||
throw new Error(`Unexpected token ${value}.`);
|
||||
}
|
||||
if (value === '*' || value === '/') {
|
||||
operator = value;
|
||||
} else if (value === '+' || value === '-') {
|
||||
const sortedValue = cal.multiply();
|
||||
if (sortedValue) {
|
||||
sortedValues.push(sortedValue, value);
|
||||
}
|
||||
cal.clear();
|
||||
operator = '';
|
||||
} else {
|
||||
const numValue = Number(value);
|
||||
const strValue = `${value}`;
|
||||
switch (operator) {
|
||||
case '/': {
|
||||
if (Number.isFinite(numValue)) {
|
||||
cal.hasNum = true;
|
||||
cal.numMul.push(1 / numValue);
|
||||
} else if (REG_TYPE_PCT.test(strValue)) {
|
||||
const [, val] = strValue.match(REG_TYPE_PCT) as MatchedRegExp;
|
||||
cal.hasPct = true;
|
||||
cal.pctMul.push((MAX_PCT * MAX_PCT) / Number(val));
|
||||
} else if (REG_TYPE_DIM.test(strValue)) {
|
||||
cal.hasDim = true;
|
||||
cal.dimDiv.push(strValue);
|
||||
} else {
|
||||
cal.hasEtc = true;
|
||||
cal.etcDiv.push(strValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '*':
|
||||
default: {
|
||||
if (Number.isFinite(numValue)) {
|
||||
cal.hasNum = true;
|
||||
cal.numMul.push(numValue);
|
||||
} else if (REG_TYPE_PCT.test(strValue)) {
|
||||
const [, val] = strValue.match(REG_TYPE_PCT) as MatchedRegExp;
|
||||
cal.hasPct = true;
|
||||
cal.pctMul.push(Number(val));
|
||||
} else if (REG_TYPE_DIM.test(strValue)) {
|
||||
cal.hasDim = true;
|
||||
cal.dimMul.push(strValue);
|
||||
} else {
|
||||
cal.hasEtc = true;
|
||||
cal.etcMul.push(strValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i === l - 1) {
|
||||
const sortedValue = cal.multiply();
|
||||
if (sortedValue) {
|
||||
sortedValues.push(sortedValue);
|
||||
}
|
||||
cal.clear();
|
||||
operator = '';
|
||||
}
|
||||
}
|
||||
let resolvedValue = '';
|
||||
if (finalize && (sortedValues.includes('+') || sortedValues.includes('-'))) {
|
||||
const finalizedValues = [];
|
||||
cal.clear();
|
||||
operator = '';
|
||||
const l = sortedValues.length;
|
||||
for (let i = 0; i < l; i++) {
|
||||
const value = sortedValues[i];
|
||||
if (isStringOrNumber(value)) {
|
||||
if (value === '+' || value === '-') {
|
||||
operator = value;
|
||||
} else {
|
||||
const numValue = Number(value);
|
||||
const strValue = `${value}`;
|
||||
switch (operator) {
|
||||
case '-': {
|
||||
if (Number.isFinite(numValue)) {
|
||||
cal.hasNum = true;
|
||||
cal.numSum.push(-1 * numValue);
|
||||
} else if (REG_TYPE_PCT.test(strValue)) {
|
||||
const [, val] = strValue.match(REG_TYPE_PCT) as MatchedRegExp;
|
||||
cal.hasPct = true;
|
||||
cal.pctSum.push(-1 * Number(val));
|
||||
} else if (REG_TYPE_DIM.test(strValue)) {
|
||||
cal.hasDim = true;
|
||||
cal.dimSub.push(strValue);
|
||||
} else {
|
||||
cal.hasEtc = true;
|
||||
cal.etcSub.push(strValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '+':
|
||||
default: {
|
||||
if (Number.isFinite(numValue)) {
|
||||
cal.hasNum = true;
|
||||
cal.numSum.push(numValue);
|
||||
} else if (REG_TYPE_PCT.test(strValue)) {
|
||||
const [, val] = strValue.match(REG_TYPE_PCT) as MatchedRegExp;
|
||||
cal.hasPct = true;
|
||||
cal.pctSum.push(Number(val));
|
||||
} else if (REG_TYPE_DIM.test(strValue)) {
|
||||
cal.hasDim = true;
|
||||
cal.dimSum.push(strValue);
|
||||
} else {
|
||||
cal.hasEtc = true;
|
||||
cal.etcSum.push(strValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i === l - 1) {
|
||||
const sortedValue = cal.sum();
|
||||
if (sortedValue) {
|
||||
finalizedValues.push(sortedValue);
|
||||
}
|
||||
cal.clear();
|
||||
operator = '';
|
||||
}
|
||||
}
|
||||
resolvedValue = finalizedValues.join(' ').replace(/\+\s-/g, '- ');
|
||||
} else {
|
||||
resolvedValue = sortedValues.join(' ').replace(/\+\s-/g, '- ');
|
||||
}
|
||||
if (
|
||||
resolvedValue.startsWith('(') &&
|
||||
resolvedValue.endsWith(')') &&
|
||||
resolvedValue.lastIndexOf('(') === 0 &&
|
||||
resolvedValue.indexOf(')') === resolvedValue.length - 1
|
||||
) {
|
||||
resolvedValue = resolvedValue.replace(/^\(/, '').replace(/\)$/, '');
|
||||
}
|
||||
return `${start}${resolvedValue}${end}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* serialize calc
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns serialized value
|
||||
*/
|
||||
export const serializeCalc = (value: string, opt: Options = {}): string => {
|
||||
const { format = '' } = opt;
|
||||
if (isString(value)) {
|
||||
if (!REG_FN_VAR_START.test(value) || format !== VAL_SPEC) {
|
||||
return value;
|
||||
}
|
||||
value = value.toLowerCase().trim();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'serializeCalc',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
const items: string[] = tokenize({ css: value })
|
||||
.map((token: CSSToken): string => {
|
||||
const [type, value] = token as [TokenType, string];
|
||||
let res = '';
|
||||
if (type !== W_SPACE && type !== COMMENT) {
|
||||
res = value;
|
||||
}
|
||||
return res;
|
||||
})
|
||||
.filter(v => v);
|
||||
let startIndex = items.findLastIndex((item: string) => /\($/.test(item));
|
||||
while (startIndex) {
|
||||
const endIndex = items.findIndex((item: unknown, index: number) => {
|
||||
return item === ')' && index > startIndex;
|
||||
});
|
||||
const slicedValues: string[] = items.slice(startIndex, endIndex + 1);
|
||||
let serializedValue: string = sortCalcValues(slicedValues);
|
||||
if (REG_FN_VAR_START.test(serializedValue)) {
|
||||
serializedValue = calc(serializedValue, {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
}
|
||||
items.splice(startIndex, endIndex - startIndex + 1, serializedValue);
|
||||
startIndex = items.findLastIndex((item: string) => /\($/.test(item));
|
||||
}
|
||||
const serializedCalc = sortCalcValues(items, true);
|
||||
setCache(cacheKey, serializedCalc);
|
||||
return serializedCalc;
|
||||
};
|
||||
|
||||
/**
|
||||
* resolve dimension
|
||||
* @param token - CSS token
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export const resolveDimension = (
|
||||
token: CSSToken,
|
||||
opt: Options = {}
|
||||
): string | NullObject => {
|
||||
if (!Array.isArray(token)) {
|
||||
throw new TypeError(`${token} is not an array.`);
|
||||
}
|
||||
const [, , , , detail = {}] = token;
|
||||
const { unit, value } = detail as {
|
||||
unit: string;
|
||||
value: number;
|
||||
};
|
||||
const { dimension = {} } = opt;
|
||||
if (unit === 'px') {
|
||||
return `${value}${unit}`;
|
||||
}
|
||||
const relativeValue = Number(value);
|
||||
if (unit && Number.isFinite(relativeValue)) {
|
||||
let pixelValue;
|
||||
if (Object.hasOwn(dimension, unit)) {
|
||||
pixelValue = dimension[unit];
|
||||
} else if (typeof dimension.callback === 'function') {
|
||||
pixelValue = dimension.callback(unit);
|
||||
}
|
||||
pixelValue = Number(pixelValue);
|
||||
if (Number.isFinite(pixelValue)) {
|
||||
return `${relativeValue * pixelValue}px`;
|
||||
}
|
||||
}
|
||||
return new NullObject();
|
||||
};
|
||||
|
||||
/**
|
||||
* parse tokens
|
||||
* @param tokens - CSS tokens
|
||||
* @param [opt] - options
|
||||
* @returns parsed tokens
|
||||
*/
|
||||
export const parseTokens = (
|
||||
tokens: CSSToken[],
|
||||
opt: Options = {}
|
||||
): string[] => {
|
||||
if (!Array.isArray(tokens)) {
|
||||
throw new TypeError(`${tokens} is not an array.`);
|
||||
}
|
||||
const { format = '' } = opt;
|
||||
const mathFunc = new Set();
|
||||
let nest = 0;
|
||||
const res: string[] = [];
|
||||
while (tokens.length) {
|
||||
const token = tokens.shift();
|
||||
if (!Array.isArray(token)) {
|
||||
throw new TypeError(`${token} is not an array.`);
|
||||
}
|
||||
const [type = '', value = ''] = token as [TokenType, string];
|
||||
switch (type) {
|
||||
case DIM: {
|
||||
if (format === VAL_SPEC && !mathFunc.has(nest)) {
|
||||
res.push(value);
|
||||
} else {
|
||||
const resolvedValue = resolveDimension(token, opt);
|
||||
if (isString(resolvedValue)) {
|
||||
res.push(resolvedValue);
|
||||
} else {
|
||||
res.push(value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FUNC:
|
||||
case PAREN_OPEN: {
|
||||
res.push(value);
|
||||
nest++;
|
||||
if (REG_FN_MATH_START.test(value)) {
|
||||
mathFunc.add(nest);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PAREN_CLOSE: {
|
||||
if (res.length) {
|
||||
const lastValue = res[res.length - 1];
|
||||
if (lastValue === ' ') {
|
||||
res.splice(-1, 1, value);
|
||||
} else {
|
||||
res.push(value);
|
||||
}
|
||||
} else {
|
||||
res.push(value);
|
||||
}
|
||||
if (mathFunc.has(nest)) {
|
||||
mathFunc.delete(nest);
|
||||
}
|
||||
nest--;
|
||||
break;
|
||||
}
|
||||
case W_SPACE: {
|
||||
if (res.length) {
|
||||
const lastValue = res[res.length - 1];
|
||||
if (
|
||||
isString(lastValue) &&
|
||||
!lastValue.endsWith('(') &&
|
||||
lastValue !== ' '
|
||||
) {
|
||||
res.push(value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (type !== COMMENT && type !== EOF) {
|
||||
res.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* CSS calc()
|
||||
* @param value - CSS value including calc()
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export const cssCalc = (value: string, opt: Options = {}): string => {
|
||||
const { format = '' } = opt;
|
||||
if (isString(value)) {
|
||||
if (REG_FN_VAR.test(value)) {
|
||||
if (format === VAL_SPEC) {
|
||||
return value;
|
||||
} else {
|
||||
const resolvedValue = resolveVar(value, opt);
|
||||
if (isString(resolvedValue)) {
|
||||
return resolvedValue;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
} else if (!REG_FN_CALC.test(value)) {
|
||||
return value;
|
||||
}
|
||||
value = value.toLowerCase().trim();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'cssCalc',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
const tokens = tokenize({ css: value });
|
||||
const values = parseTokens(tokens, opt);
|
||||
let resolvedValue: string = calc(values.join(''), {
|
||||
toCanonicalUnits: true
|
||||
});
|
||||
if (REG_FN_VAR_START.test(value)) {
|
||||
if (REG_TYPE_DIM_PCT.test(resolvedValue)) {
|
||||
const [, val, unit] = resolvedValue.match(
|
||||
REG_TYPE_DIM_PCT
|
||||
) as MatchedRegExp;
|
||||
resolvedValue = `${roundToPrecision(Number(val), HEX)}${unit}`;
|
||||
}
|
||||
// wrap with `calc()`
|
||||
if (
|
||||
resolvedValue &&
|
||||
!REG_FN_VAR_START.test(resolvedValue) &&
|
||||
format === VAL_SPEC
|
||||
) {
|
||||
resolvedValue = `calc(${resolvedValue})`;
|
||||
}
|
||||
}
|
||||
if (format === VAL_SPEC) {
|
||||
if (/\s[-+*/]\s/.test(resolvedValue) && !resolvedValue.includes('NaN')) {
|
||||
resolvedValue = serializeCalc(resolvedValue, opt);
|
||||
} else if (REG_FN_CALC_NUM.test(resolvedValue)) {
|
||||
const [, val] = resolvedValue.match(REG_FN_CALC_NUM) as MatchedRegExp;
|
||||
resolvedValue = `calc(${roundToPrecision(Number(val), HEX)})`;
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, resolvedValue);
|
||||
return resolvedValue;
|
||||
};
|
||||
384
node_modules/@asamuzakjp/css-color/src/js/css-gradient.ts
generated
vendored
Normal file
384
node_modules/@asamuzakjp/css-color/src/js/css-gradient.ts
generated
vendored
Normal file
@@ -0,0 +1,384 @@
|
||||
/**
|
||||
* css-gradient
|
||||
*/
|
||||
|
||||
import { CacheItem, createCacheKey, getCache, setCache } from './cache';
|
||||
import { resolveColor } from './resolve';
|
||||
import { isString } from './common';
|
||||
import { MatchedRegExp, Options } from './typedef';
|
||||
import { isColor, splitValue } from './util';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
ANGLE,
|
||||
CS_HUE,
|
||||
CS_RECT,
|
||||
LENGTH,
|
||||
NUM,
|
||||
NUM_POSITIVE,
|
||||
PCT,
|
||||
VAL_COMP,
|
||||
VAL_SPEC
|
||||
} from './constant';
|
||||
const NAMESPACE = 'css-gradient';
|
||||
const DIM_ANGLE = `${NUM}(?:${ANGLE})`;
|
||||
const DIM_ANGLE_PCT = `${DIM_ANGLE}|${PCT}`;
|
||||
const DIM_LEN = `${NUM}(?:${LENGTH})|0`;
|
||||
const DIM_LEN_PCT = `${DIM_LEN}|${PCT}`;
|
||||
const DIM_LEN_PCT_POSI = `${NUM_POSITIVE}(?:${LENGTH}|%)|0`;
|
||||
const DIM_LEN_POSI = `${NUM_POSITIVE}(?:${LENGTH})|0`;
|
||||
const CTR = 'center';
|
||||
const L_R = 'left|right';
|
||||
const T_B = 'top|bottom';
|
||||
const S_E = 'start|end';
|
||||
const AXIS_X = `${L_R}|x-(?:${S_E})`;
|
||||
const AXIS_Y = `${T_B}|y-(?:${S_E})`;
|
||||
const BLOCK = `block-(?:${S_E})`;
|
||||
const INLINE = `inline-(?:${S_E})`;
|
||||
const POS_1 = `${CTR}|${AXIS_X}|${AXIS_Y}|${BLOCK}|${INLINE}|${DIM_LEN_PCT}`;
|
||||
const POS_2 = [
|
||||
`(?:${CTR}|${AXIS_X})\\s+(?:${CTR}|${AXIS_Y})`,
|
||||
`(?:${CTR}|${AXIS_Y})\\s+(?:${CTR}|${AXIS_X})`,
|
||||
`(?:${CTR}|${AXIS_X}|${DIM_LEN_PCT})\\s+(?:${CTR}|${AXIS_Y}|${DIM_LEN_PCT})`,
|
||||
`(?:${CTR}|${BLOCK})\\s+(?:${CTR}|${INLINE})`,
|
||||
`(?:${CTR}|${INLINE})\\s+(?:${CTR}|${BLOCK})`,
|
||||
`(?:${CTR}|${S_E})\\s+(?:${CTR}|${S_E})`
|
||||
].join('|');
|
||||
const POS_4 = [
|
||||
`(?:${AXIS_X})\\s+(?:${DIM_LEN_PCT})\\s+(?:${AXIS_Y})\\s+(?:${DIM_LEN_PCT})`,
|
||||
`(?:${AXIS_Y})\\s+(?:${DIM_LEN_PCT})\\s+(?:${AXIS_X})\\s+(?:${DIM_LEN_PCT})`,
|
||||
`(?:${BLOCK})\\s+(?:${DIM_LEN_PCT})\\s+(?:${INLINE})\\s+(?:${DIM_LEN_PCT})`,
|
||||
`(?:${INLINE})\\s+(?:${DIM_LEN_PCT})\\s+(?:${BLOCK})\\s+(?:${DIM_LEN_PCT})`,
|
||||
`(?:${S_E})\\s+(?:${DIM_LEN_PCT})\\s+(?:${S_E})\\s+(?:${DIM_LEN_PCT})`
|
||||
].join('|');
|
||||
const RAD_EXTENT = '(?:clos|farth)est-(?:corner|side)';
|
||||
const RAD_SIZE = [
|
||||
`${RAD_EXTENT}(?:\\s+${RAD_EXTENT})?`,
|
||||
`${DIM_LEN_POSI}`,
|
||||
`(?:${DIM_LEN_PCT_POSI})\\s+(?:${DIM_LEN_PCT_POSI})`
|
||||
].join('|');
|
||||
const RAD_SHAPE = 'circle|ellipse';
|
||||
const FROM_ANGLE = `from\\s+${DIM_ANGLE}`;
|
||||
const AT_POSITION = `at\\s+(?:${POS_1}|${POS_2}|${POS_4})`;
|
||||
const TO_SIDE_CORNER = `to\\s+(?:(?:${L_R})(?:\\s(?:${T_B}))?|(?:${T_B})(?:\\s(?:${L_R}))?)`;
|
||||
const IN_COLOR_SPACE = `in\\s+(?:${CS_RECT}|${CS_HUE})`;
|
||||
|
||||
/* type definitions */
|
||||
/**
|
||||
* @type ColorStopList - list of color stops
|
||||
*/
|
||||
type ColorStopList = [string, string, ...string[]];
|
||||
|
||||
/**
|
||||
* @typedef ValidateGradientLine - validate gradient line
|
||||
* @property line - gradient line
|
||||
* @property valid - result
|
||||
*/
|
||||
interface ValidateGradientLine {
|
||||
line: string;
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef ValidateColorStops - validate color stops
|
||||
* @property colorStops - list of color stops
|
||||
* @property valid - result
|
||||
*/
|
||||
interface ValidateColorStops {
|
||||
colorStops: string[];
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef Gradient - parsed CSS gradient
|
||||
* @property value - input value
|
||||
* @property type - gradient type
|
||||
* @property [gradientLine] - gradient line
|
||||
* @property colorStopList - list of color stops
|
||||
*/
|
||||
interface Gradient {
|
||||
value: string;
|
||||
type: string;
|
||||
gradientLine?: string;
|
||||
colorStopList: ColorStopList;
|
||||
}
|
||||
|
||||
/* regexp */
|
||||
const REG_GRAD = /^(?:repeating-)?(?:conic|linear|radial)-gradient\(/;
|
||||
const REG_GRAD_CAPT = /^((?:repeating-)?(?:conic|linear|radial)-gradient)\(/;
|
||||
|
||||
/**
|
||||
* get gradient type
|
||||
* @param value - gradient value
|
||||
* @returns gradient type
|
||||
*/
|
||||
export const getGradientType = (value: string): string => {
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
if (REG_GRAD.test(value)) {
|
||||
const [, type] = value.match(REG_GRAD_CAPT) as MatchedRegExp;
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* validate gradient line
|
||||
* @param value - gradient line value
|
||||
* @param type - gradient type
|
||||
* @returns result
|
||||
*/
|
||||
export const validateGradientLine = (
|
||||
value: string,
|
||||
type: string
|
||||
): ValidateGradientLine => {
|
||||
if (isString(value) && isString(type)) {
|
||||
value = value.trim();
|
||||
type = type.trim();
|
||||
let lineSyntax = '';
|
||||
const defaultValues = [];
|
||||
if (/^(?:repeating-)?linear-gradient$/.test(type)) {
|
||||
/*
|
||||
* <linear-gradient-line> = [
|
||||
* [ <angle> | to <side-or-corner> ] ||
|
||||
* <color-interpolation-method>
|
||||
* ]
|
||||
*/
|
||||
lineSyntax = [
|
||||
`(?:${DIM_ANGLE}|${TO_SIDE_CORNER})(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+(?:${DIM_ANGLE}|${TO_SIDE_CORNER}))?`
|
||||
].join('|');
|
||||
defaultValues.push(/to\s+bottom/);
|
||||
} else if (/^(?:repeating-)?radial-gradient$/.test(type)) {
|
||||
/*
|
||||
* <radial-gradient-line> = [
|
||||
* [ [ <radial-shape> || <radial-size> ]? [ at <position> ]? ] ||
|
||||
* <color-interpolation-method>]?
|
||||
*/
|
||||
lineSyntax = [
|
||||
`(?:${RAD_SHAPE})(?:\\s+(?:${RAD_SIZE}))?(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`(?:${RAD_SIZE})(?:\\s+(?:${RAD_SHAPE}))?(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${AT_POSITION}(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+${RAD_SHAPE})(?:\\s+(?:${RAD_SIZE}))?(?:\\s+${AT_POSITION})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+${RAD_SIZE})(?:\\s+(?:${RAD_SHAPE}))?(?:\\s+${AT_POSITION})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+${AT_POSITION})?`
|
||||
].join('|');
|
||||
defaultValues.push(/ellipse/, /farthest-corner/, /at\s+center/);
|
||||
} else if (/^(?:repeating-)?conic-gradient$/.test(type)) {
|
||||
/*
|
||||
* <conic-gradient-line> = [
|
||||
* [ [ from <angle> ]? [ at <position> ]? ] ||
|
||||
* <color-interpolation-method>
|
||||
* ]
|
||||
*/
|
||||
lineSyntax = [
|
||||
`${FROM_ANGLE}(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${AT_POSITION}(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+${FROM_ANGLE})?(?:\\s+${AT_POSITION})?`
|
||||
].join('|');
|
||||
defaultValues.push(/at\s+center/);
|
||||
}
|
||||
if (lineSyntax) {
|
||||
const reg = new RegExp(`^(?:${lineSyntax})$`);
|
||||
const valid = reg.test(value);
|
||||
if (valid) {
|
||||
let line = value;
|
||||
for (const defaultValue of defaultValues) {
|
||||
line = line.replace(defaultValue, '');
|
||||
}
|
||||
line = line.replace(/\s{2,}/g, ' ').trim();
|
||||
return {
|
||||
line,
|
||||
valid
|
||||
};
|
||||
}
|
||||
return {
|
||||
valid,
|
||||
line: value
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
line: value,
|
||||
valid: false
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* validate color stop list
|
||||
* @param list
|
||||
* @param type
|
||||
* @param [opt]
|
||||
* @returns result
|
||||
*/
|
||||
export const validateColorStopList = (
|
||||
list: string[],
|
||||
type: string,
|
||||
opt: Options = {}
|
||||
): ValidateColorStops => {
|
||||
if (Array.isArray(list) && list.length > 1) {
|
||||
const dimension = /^(?:repeating-)?conic-gradient$/.test(type)
|
||||
? DIM_ANGLE_PCT
|
||||
: DIM_LEN_PCT;
|
||||
const regColorHint = new RegExp(`^(?:${dimension})$`);
|
||||
const regDimension = new RegExp(`(?:\\s+(?:${dimension})){1,2}$`);
|
||||
const valueTypes = [];
|
||||
const valueList = [];
|
||||
for (const item of list) {
|
||||
if (isString(item)) {
|
||||
if (regColorHint.test(item)) {
|
||||
valueTypes.push('hint');
|
||||
valueList.push(item);
|
||||
} else {
|
||||
const itemColor = item.replace(regDimension, '');
|
||||
if (isColor(itemColor, { format: VAL_SPEC })) {
|
||||
const resolvedColor = resolveColor(itemColor, opt) as string;
|
||||
valueTypes.push('color');
|
||||
valueList.push(item.replace(itemColor, resolvedColor));
|
||||
} else {
|
||||
return {
|
||||
colorStops: list,
|
||||
valid: false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const valid = /^color(?:,(?:hint,)?color)+$/.test(valueTypes.join(','));
|
||||
return {
|
||||
valid,
|
||||
colorStops: valueList
|
||||
};
|
||||
}
|
||||
return {
|
||||
colorStops: list,
|
||||
valid: false
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* parse CSS gradient
|
||||
* @param value - gradient value
|
||||
* @param [opt] - options
|
||||
* @returns parsed result
|
||||
*/
|
||||
export const parseGradient = (
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): Gradient | null => {
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'parseGradient',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return null;
|
||||
}
|
||||
return cachedResult.item as Gradient;
|
||||
}
|
||||
const type = getGradientType(value);
|
||||
const gradValue = value.replace(REG_GRAD, '').replace(/\)$/, '');
|
||||
if (type && gradValue) {
|
||||
const [lineOrColorStop = '', ...itemList] = splitValue(gradValue, {
|
||||
delimiter: ','
|
||||
});
|
||||
const dimension = /^(?:repeating-)?conic-gradient$/.test(type)
|
||||
? DIM_ANGLE_PCT
|
||||
: DIM_LEN_PCT;
|
||||
const regDimension = new RegExp(`(?:\\s+(?:${dimension})){1,2}$`);
|
||||
let colorStop = '';
|
||||
if (regDimension.test(lineOrColorStop)) {
|
||||
const itemColor = lineOrColorStop.replace(regDimension, '');
|
||||
if (isColor(itemColor, { format: VAL_SPEC })) {
|
||||
const resolvedColor = resolveColor(itemColor, opt) as string;
|
||||
colorStop = lineOrColorStop.replace(itemColor, resolvedColor);
|
||||
}
|
||||
} else if (isColor(lineOrColorStop, { format: VAL_SPEC })) {
|
||||
colorStop = resolveColor(lineOrColorStop, opt) as string;
|
||||
}
|
||||
if (colorStop) {
|
||||
itemList.unshift(colorStop);
|
||||
const { colorStops, valid } = validateColorStopList(
|
||||
itemList,
|
||||
type,
|
||||
opt
|
||||
);
|
||||
if (valid) {
|
||||
const res: Gradient = {
|
||||
value,
|
||||
type,
|
||||
colorStopList: colorStops as ColorStopList
|
||||
};
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
} else if (itemList.length > 1) {
|
||||
const { line: gradientLine, valid: validLine } = validateGradientLine(
|
||||
lineOrColorStop,
|
||||
type
|
||||
);
|
||||
const { colorStops, valid: validColorStops } = validateColorStopList(
|
||||
itemList,
|
||||
type,
|
||||
opt
|
||||
);
|
||||
if (validLine && validColorStops) {
|
||||
const res: Gradient = {
|
||||
value,
|
||||
type,
|
||||
gradientLine,
|
||||
colorStopList: colorStops as ColorStopList
|
||||
};
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, null);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* resolve CSS gradient
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns result
|
||||
*/
|
||||
export const resolveGradient = (value: string, opt: Options = {}): string => {
|
||||
const { format = VAL_COMP } = opt;
|
||||
const gradient = parseGradient(value, opt);
|
||||
if (gradient) {
|
||||
const { type = '', gradientLine = '', colorStopList = [] } = gradient;
|
||||
if (type && Array.isArray(colorStopList) && colorStopList.length > 1) {
|
||||
if (gradientLine) {
|
||||
return `${type}(${gradientLine}, ${colorStopList.join(', ')})`;
|
||||
}
|
||||
return `${type}(${colorStopList.join(', ')})`;
|
||||
}
|
||||
}
|
||||
if (format === VAL_SPEC) {
|
||||
return '';
|
||||
}
|
||||
return 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* is CSS gradient
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns result
|
||||
*/
|
||||
export const isGradient = (value: string, opt: Options = {}): boolean => {
|
||||
const gradient = parseGradient(value, opt);
|
||||
return gradient !== null;
|
||||
};
|
||||
250
node_modules/@asamuzakjp/css-color/src/js/css-var.ts
generated
vendored
Normal file
250
node_modules/@asamuzakjp/css-color/src/js/css-var.ts
generated
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* css-var
|
||||
*/
|
||||
|
||||
import { CSSToken, TokenType, tokenize } from '@csstools/css-tokenizer';
|
||||
import {
|
||||
CacheItem,
|
||||
NullObject,
|
||||
createCacheKey,
|
||||
getCache,
|
||||
setCache
|
||||
} from './cache';
|
||||
import { isString } from './common';
|
||||
import { cssCalc } from './css-calc';
|
||||
import { isColor } from './util';
|
||||
import { Options } from './typedef';
|
||||
|
||||
/* constants */
|
||||
import { FN_VAR, SYN_FN_CALC, SYN_FN_VAR, VAL_SPEC } from './constant';
|
||||
const {
|
||||
CloseParen: PAREN_CLOSE,
|
||||
Comment: COMMENT,
|
||||
EOF,
|
||||
Ident: IDENT,
|
||||
Whitespace: W_SPACE
|
||||
} = TokenType;
|
||||
const NAMESPACE = 'css-var';
|
||||
|
||||
/* regexp */
|
||||
const REG_FN_CALC = new RegExp(SYN_FN_CALC);
|
||||
const REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
|
||||
/**
|
||||
* resolve custom property
|
||||
* @param tokens - CSS tokens
|
||||
* @param [opt] - options
|
||||
* @returns result - [tokens, resolvedValue]
|
||||
*/
|
||||
export function resolveCustomProperty(
|
||||
tokens: CSSToken[],
|
||||
opt: Options = {}
|
||||
): [CSSToken[], string] {
|
||||
if (!Array.isArray(tokens)) {
|
||||
throw new TypeError(`${tokens} is not an array.`);
|
||||
}
|
||||
const { customProperty = {} } = opt;
|
||||
const items: string[] = [];
|
||||
while (tokens.length) {
|
||||
const token = tokens.shift();
|
||||
if (!Array.isArray(token)) {
|
||||
throw new TypeError(`${token} is not an array.`);
|
||||
}
|
||||
const [type, value] = token as [TokenType, string];
|
||||
// end of var()
|
||||
if (type === PAREN_CLOSE) {
|
||||
break;
|
||||
}
|
||||
// nested var()
|
||||
if (value === FN_VAR) {
|
||||
const [restTokens, item] = resolveCustomProperty(tokens, opt);
|
||||
tokens = restTokens;
|
||||
if (item) {
|
||||
items.push(item);
|
||||
}
|
||||
} else if (type === IDENT) {
|
||||
if (value.startsWith('--')) {
|
||||
let item;
|
||||
if (Object.hasOwn(customProperty, value)) {
|
||||
item = customProperty[value] as string;
|
||||
} else if (typeof customProperty.callback === 'function') {
|
||||
item = customProperty.callback(value);
|
||||
}
|
||||
if (item) {
|
||||
items.push(item);
|
||||
}
|
||||
} else if (value) {
|
||||
items.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
let resolveAsColor = false;
|
||||
if (items.length > 1) {
|
||||
const lastValue = items[items.length - 1];
|
||||
resolveAsColor = isColor(lastValue);
|
||||
}
|
||||
let resolvedValue = '';
|
||||
for (let item of items) {
|
||||
item = item.trim();
|
||||
if (REG_FN_VAR.test(item)) {
|
||||
// recurse resolveVar()
|
||||
const resolvedItem = resolveVar(item, opt);
|
||||
if (isString(resolvedItem)) {
|
||||
if (resolveAsColor) {
|
||||
if (isColor(resolvedItem)) {
|
||||
resolvedValue = resolvedItem;
|
||||
}
|
||||
} else {
|
||||
resolvedValue = resolvedItem;
|
||||
}
|
||||
}
|
||||
} else if (REG_FN_CALC.test(item)) {
|
||||
item = cssCalc(item, opt);
|
||||
if (resolveAsColor) {
|
||||
if (isColor(item)) {
|
||||
resolvedValue = item;
|
||||
}
|
||||
} else {
|
||||
resolvedValue = item;
|
||||
}
|
||||
} else if (
|
||||
item &&
|
||||
!/^(?:inherit|initial|revert(?:-layer)?|unset)$/.test(item)
|
||||
) {
|
||||
if (resolveAsColor) {
|
||||
if (isColor(item)) {
|
||||
resolvedValue = item;
|
||||
}
|
||||
} else {
|
||||
resolvedValue = item;
|
||||
}
|
||||
}
|
||||
if (resolvedValue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [tokens, resolvedValue];
|
||||
}
|
||||
|
||||
/**
|
||||
* parse tokens
|
||||
* @param tokens - CSS tokens
|
||||
* @param [opt] - options
|
||||
* @returns parsed tokens
|
||||
*/
|
||||
export function parseTokens(
|
||||
tokens: CSSToken[],
|
||||
opt: Options = {}
|
||||
): string[] | NullObject {
|
||||
const res: string[] = [];
|
||||
while (tokens.length) {
|
||||
const token = tokens.shift();
|
||||
const [type = '', value = ''] = token as [TokenType, string];
|
||||
if (value === FN_VAR) {
|
||||
const [restTokens, resolvedValue] = resolveCustomProperty(tokens, opt);
|
||||
if (!resolvedValue) {
|
||||
return new NullObject();
|
||||
}
|
||||
tokens = restTokens;
|
||||
res.push(resolvedValue);
|
||||
} else {
|
||||
switch (type) {
|
||||
case PAREN_CLOSE: {
|
||||
if (res.length) {
|
||||
const lastValue = res[res.length - 1];
|
||||
if (lastValue === ' ') {
|
||||
res.splice(-1, 1, value);
|
||||
} else {
|
||||
res.push(value);
|
||||
}
|
||||
} else {
|
||||
res.push(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case W_SPACE: {
|
||||
if (res.length) {
|
||||
const lastValue = res[res.length - 1];
|
||||
if (
|
||||
isString(lastValue) &&
|
||||
!lastValue.endsWith('(') &&
|
||||
lastValue !== ' '
|
||||
) {
|
||||
res.push(value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (type !== COMMENT && type !== EOF) {
|
||||
res.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* resolve CSS var()
|
||||
* @param value - CSS value including var()
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export function resolveVar(
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): string | NullObject {
|
||||
const { format = '' } = opt;
|
||||
if (isString(value)) {
|
||||
if (!REG_FN_VAR.test(value) || format === VAL_SPEC) {
|
||||
return value;
|
||||
}
|
||||
value = value.trim();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'resolveVar',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return cachedResult as NullObject;
|
||||
}
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
const tokens = tokenize({ css: value });
|
||||
const values = parseTokens(tokens, opt);
|
||||
if (Array.isArray(values)) {
|
||||
let color = values.join('');
|
||||
if (REG_FN_CALC.test(color)) {
|
||||
color = cssCalc(color, opt);
|
||||
}
|
||||
setCache(cacheKey, color);
|
||||
return color;
|
||||
} else {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS var()
|
||||
* @param value - CSS value including var()
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export const cssVar = (value: string, opt: Options = {}): string => {
|
||||
const resolvedValue = resolveVar(value, opt);
|
||||
if (isString(resolvedValue)) {
|
||||
return resolvedValue;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
603
node_modules/@asamuzakjp/css-color/src/js/relative-color.ts
generated
vendored
Normal file
603
node_modules/@asamuzakjp/css-color/src/js/relative-color.ts
generated
vendored
Normal file
@@ -0,0 +1,603 @@
|
||||
/**
|
||||
* relative-color
|
||||
*/
|
||||
|
||||
import { SyntaxFlag, color as colorParser } from '@csstools/css-color-parser';
|
||||
import {
|
||||
ComponentValue,
|
||||
parseComponentValue
|
||||
} from '@csstools/css-parser-algorithms';
|
||||
import { CSSToken, TokenType, tokenize } from '@csstools/css-tokenizer';
|
||||
import {
|
||||
CacheItem,
|
||||
NullObject,
|
||||
createCacheKey,
|
||||
getCache,
|
||||
setCache
|
||||
} from './cache';
|
||||
import { NAMED_COLORS, convertColorToRgb } from './color';
|
||||
import { isString, isStringOrNumber } from './common';
|
||||
import { resolveDimension, serializeCalc } from './css-calc';
|
||||
import { resolveColor } from './resolve';
|
||||
import { roundToPrecision, splitValue } from './util';
|
||||
import {
|
||||
ColorChannels,
|
||||
MatchedRegExp,
|
||||
Options,
|
||||
StringColorChannels
|
||||
} from './typedef';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
CS_LAB,
|
||||
CS_LCH,
|
||||
FN_LIGHT_DARK,
|
||||
FN_REL,
|
||||
FN_REL_CAPT,
|
||||
FN_VAR,
|
||||
NONE,
|
||||
SYN_COLOR_TYPE,
|
||||
SYN_FN_MATH_START,
|
||||
SYN_FN_VAR,
|
||||
SYN_MIX,
|
||||
VAL_SPEC
|
||||
} from './constant';
|
||||
const {
|
||||
CloseParen: PAREN_CLOSE,
|
||||
Comment: COMMENT,
|
||||
Dimension: DIM,
|
||||
EOF,
|
||||
Function: FUNC,
|
||||
Ident: IDENT,
|
||||
Number: NUM,
|
||||
OpenParen: PAREN_OPEN,
|
||||
Percentage: PCT,
|
||||
Whitespace: W_SPACE
|
||||
} = TokenType;
|
||||
const { HasNoneKeywords: KEY_NONE } = SyntaxFlag;
|
||||
const NAMESPACE = 'relative-color';
|
||||
|
||||
/* numeric constants */
|
||||
const OCT = 8;
|
||||
const DEC = 10;
|
||||
const HEX = 16;
|
||||
const MAX_PCT = 100;
|
||||
const MAX_RGB = 255;
|
||||
|
||||
/* type definitions */
|
||||
/**
|
||||
* @type NumberOrStringColorChannels - color channel
|
||||
*/
|
||||
type NumberOrStringColorChannels = ColorChannels & StringColorChannels;
|
||||
|
||||
/* regexp */
|
||||
const REG_COLOR_CAPT = new RegExp(
|
||||
`^${FN_REL}(${SYN_COLOR_TYPE}|${SYN_MIX})\\s+`
|
||||
);
|
||||
const REG_CS_HSL = /(?:hsla?|hwb)$/;
|
||||
const REG_CS_CIE = new RegExp(`^(?:${CS_LAB}|${CS_LCH})$`);
|
||||
const REG_FN_MATH_START = new RegExp(SYN_FN_MATH_START);
|
||||
const REG_FN_REL = new RegExp(FN_REL);
|
||||
const REG_FN_REL_CAPT = new RegExp(`^${FN_REL_CAPT}`);
|
||||
const REG_FN_REL_START = new RegExp(`^${FN_REL}`);
|
||||
const REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
|
||||
/**
|
||||
* resolve relative color channels
|
||||
* @param tokens - CSS tokens
|
||||
* @param [opt] - options
|
||||
* @returns resolved color channels
|
||||
*/
|
||||
export function resolveColorChannels(
|
||||
tokens: CSSToken[],
|
||||
opt: Options = {}
|
||||
): NumberOrStringColorChannels | NullObject {
|
||||
if (!Array.isArray(tokens)) {
|
||||
throw new TypeError(`${tokens} is not an array.`);
|
||||
}
|
||||
const { colorSpace = '', format = '' } = opt;
|
||||
const colorChannels = new Map([
|
||||
['color', ['r', 'g', 'b', 'alpha']],
|
||||
['hsl', ['h', 's', 'l', 'alpha']],
|
||||
['hsla', ['h', 's', 'l', 'alpha']],
|
||||
['hwb', ['h', 'w', 'b', 'alpha']],
|
||||
['lab', ['l', 'a', 'b', 'alpha']],
|
||||
['lch', ['l', 'c', 'h', 'alpha']],
|
||||
['oklab', ['l', 'a', 'b', 'alpha']],
|
||||
['oklch', ['l', 'c', 'h', 'alpha']],
|
||||
['rgb', ['r', 'g', 'b', 'alpha']],
|
||||
['rgba', ['r', 'g', 'b', 'alpha']]
|
||||
]);
|
||||
const colorChannel = colorChannels.get(colorSpace);
|
||||
// invalid color channel
|
||||
if (!colorChannel) {
|
||||
return new NullObject();
|
||||
}
|
||||
const mathFunc = new Set();
|
||||
const channels: [
|
||||
(number | string)[],
|
||||
(number | string)[],
|
||||
(number | string)[],
|
||||
(number | string)[]
|
||||
] = [[], [], [], []];
|
||||
let i = 0;
|
||||
let nest = 0;
|
||||
let func = false;
|
||||
while (tokens.length) {
|
||||
const token = tokens.shift();
|
||||
if (!Array.isArray(token)) {
|
||||
throw new TypeError(`${token} is not an array.`);
|
||||
}
|
||||
const [type, value, , , detail] = token as [
|
||||
TokenType,
|
||||
string,
|
||||
number,
|
||||
number,
|
||||
{ value: string | number } | undefined
|
||||
];
|
||||
const channel = channels[i];
|
||||
if (Array.isArray(channel)) {
|
||||
switch (type) {
|
||||
case DIM: {
|
||||
const resolvedValue = resolveDimension(token, opt);
|
||||
if (isString(resolvedValue)) {
|
||||
channel.push(resolvedValue);
|
||||
} else {
|
||||
channel.push(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FUNC: {
|
||||
channel.push(value);
|
||||
func = true;
|
||||
nest++;
|
||||
if (REG_FN_MATH_START.test(value)) {
|
||||
mathFunc.add(nest);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IDENT: {
|
||||
// invalid channel key
|
||||
if (!colorChannel.includes(value)) {
|
||||
return new NullObject();
|
||||
}
|
||||
channel.push(value);
|
||||
if (!func) {
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NUM: {
|
||||
channel.push(Number(detail?.value));
|
||||
if (!func) {
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PAREN_OPEN: {
|
||||
channel.push(value);
|
||||
nest++;
|
||||
break;
|
||||
}
|
||||
case PAREN_CLOSE: {
|
||||
if (func) {
|
||||
const lastValue = channel[channel.length - 1];
|
||||
if (lastValue === ' ') {
|
||||
channel.splice(-1, 1, value);
|
||||
} else {
|
||||
channel.push(value);
|
||||
}
|
||||
if (mathFunc.has(nest)) {
|
||||
mathFunc.delete(nest);
|
||||
}
|
||||
nest--;
|
||||
if (nest === 0) {
|
||||
func = false;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PCT: {
|
||||
channel.push(Number(detail?.value) / MAX_PCT);
|
||||
if (!func) {
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case W_SPACE: {
|
||||
if (channel.length && func) {
|
||||
const lastValue = channel[channel.length - 1];
|
||||
if (typeof lastValue === 'number') {
|
||||
channel.push(value);
|
||||
} else if (
|
||||
isString(lastValue) &&
|
||||
!lastValue.endsWith('(') &&
|
||||
lastValue !== ' '
|
||||
) {
|
||||
channel.push(value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (type !== COMMENT && type !== EOF && func) {
|
||||
channel.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const channelValues = [];
|
||||
for (const channel of channels) {
|
||||
if (channel.length === 1) {
|
||||
const [resolvedValue] = channel;
|
||||
if (isStringOrNumber(resolvedValue)) {
|
||||
channelValues.push(resolvedValue);
|
||||
}
|
||||
} else if (channel.length) {
|
||||
const resolvedValue = serializeCalc(channel.join(''), {
|
||||
format
|
||||
});
|
||||
channelValues.push(resolvedValue);
|
||||
}
|
||||
}
|
||||
return channelValues as NumberOrStringColorChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* extract origin color
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns origin color value
|
||||
*/
|
||||
export function extractOriginColor(
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): string | NullObject {
|
||||
const { colorScheme = 'normal', currentColor = '', format = '' } = opt;
|
||||
if (isString(value)) {
|
||||
value = value.toLowerCase().trim();
|
||||
if (!value) {
|
||||
return new NullObject();
|
||||
}
|
||||
if (!REG_FN_REL_START.test(value)) {
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
return new NullObject();
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'extractOriginColor',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return cachedResult as NullObject;
|
||||
}
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
if (/currentcolor/.test(value)) {
|
||||
if (currentColor) {
|
||||
value = value.replace(/currentcolor/g, currentColor);
|
||||
} else {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
}
|
||||
let colorSpace = '';
|
||||
if (REG_FN_REL_CAPT.test(value)) {
|
||||
[, colorSpace] = value.match(REG_FN_REL_CAPT) as MatchedRegExp;
|
||||
}
|
||||
opt.colorSpace = colorSpace;
|
||||
if (value.includes(FN_LIGHT_DARK)) {
|
||||
const colorParts = value
|
||||
.replace(new RegExp(`^${colorSpace}\\(`), '')
|
||||
.replace(/\)$/, '');
|
||||
const [, originColor = ''] = splitValue(colorParts);
|
||||
const specifiedOriginColor = resolveColor(originColor, {
|
||||
colorScheme,
|
||||
format: VAL_SPEC
|
||||
}) as string;
|
||||
if (specifiedOriginColor === '') {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
if (format === VAL_SPEC) {
|
||||
value = value.replace(originColor, specifiedOriginColor);
|
||||
} else {
|
||||
const resolvedOriginColor = resolveColor(specifiedOriginColor, opt);
|
||||
if (isString(resolvedOriginColor)) {
|
||||
value = value.replace(originColor, resolvedOriginColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (REG_COLOR_CAPT.test(value)) {
|
||||
const [, originColor] = value.match(REG_COLOR_CAPT) as MatchedRegExp;
|
||||
const [, restValue] = value.split(originColor) as MatchedRegExp;
|
||||
if (/^[a-z]+$/.test(originColor)) {
|
||||
if (
|
||||
!/^transparent$/.test(originColor) &&
|
||||
!Object.hasOwn(NAMED_COLORS, originColor)
|
||||
) {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
} else if (format === VAL_SPEC) {
|
||||
const resolvedOriginColor = resolveColor(originColor, opt);
|
||||
if (isString(resolvedOriginColor)) {
|
||||
value = value.replace(originColor, resolvedOriginColor);
|
||||
}
|
||||
}
|
||||
if (format === VAL_SPEC) {
|
||||
const tokens = tokenize({ css: restValue });
|
||||
const channelValues = resolveColorChannels(tokens, opt);
|
||||
if (channelValues instanceof NullObject) {
|
||||
setCache(cacheKey, null);
|
||||
return channelValues;
|
||||
}
|
||||
const [v1, v2, v3, v4] = channelValues;
|
||||
let channelValue = '';
|
||||
if (isStringOrNumber(v4)) {
|
||||
channelValue = ` ${v1} ${v2} ${v3} / ${v4})`;
|
||||
} else {
|
||||
channelValue = ` ${channelValues.join(' ')})`;
|
||||
}
|
||||
if (restValue !== channelValue) {
|
||||
value = value.replace(restValue, channelValue);
|
||||
}
|
||||
}
|
||||
// nested relative color
|
||||
} else {
|
||||
const [, restValue] = value.split(REG_FN_REL_START) as MatchedRegExp;
|
||||
const tokens = tokenize({ css: restValue });
|
||||
const originColor: string[] = [];
|
||||
let nest = 0;
|
||||
while (tokens.length) {
|
||||
const [type, tokenValue] = tokens.shift() as [TokenType, string];
|
||||
switch (type) {
|
||||
case FUNC:
|
||||
case PAREN_OPEN: {
|
||||
originColor.push(tokenValue);
|
||||
nest++;
|
||||
break;
|
||||
}
|
||||
case PAREN_CLOSE: {
|
||||
const lastValue = originColor[originColor.length - 1];
|
||||
if (lastValue === ' ') {
|
||||
originColor.splice(-1, 1, tokenValue);
|
||||
} else if (isString(lastValue)) {
|
||||
originColor.push(tokenValue);
|
||||
}
|
||||
nest--;
|
||||
break;
|
||||
}
|
||||
case W_SPACE: {
|
||||
const lastValue = originColor[originColor.length - 1];
|
||||
if (
|
||||
isString(lastValue) &&
|
||||
!lastValue.endsWith('(') &&
|
||||
lastValue !== ' '
|
||||
) {
|
||||
originColor.push(tokenValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (type !== COMMENT && type !== EOF) {
|
||||
originColor.push(tokenValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nest === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const resolvedOriginColor = resolveRelativeColor(
|
||||
originColor.join('').trim(),
|
||||
opt
|
||||
);
|
||||
if (resolvedOriginColor instanceof NullObject) {
|
||||
setCache(cacheKey, null);
|
||||
return resolvedOriginColor;
|
||||
}
|
||||
const channelValues = resolveColorChannels(tokens, opt);
|
||||
if (channelValues instanceof NullObject) {
|
||||
setCache(cacheKey, null);
|
||||
return channelValues;
|
||||
}
|
||||
const [v1, v2, v3, v4] = channelValues;
|
||||
let channelValue = '';
|
||||
if (isStringOrNumber(v4)) {
|
||||
channelValue = ` ${v1} ${v2} ${v3} / ${v4})`;
|
||||
} else {
|
||||
channelValue = ` ${channelValues.join(' ')})`;
|
||||
}
|
||||
value = value.replace(restValue, `${resolvedOriginColor}${channelValue}`);
|
||||
}
|
||||
setCache(cacheKey, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* resolve relative color
|
||||
* @param value - CSS relative color value
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export function resolveRelativeColor(
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): string | NullObject {
|
||||
const { format = '' } = opt;
|
||||
if (isString(value)) {
|
||||
if (REG_FN_VAR.test(value)) {
|
||||
if (format === VAL_SPEC) {
|
||||
return value;
|
||||
// var() must be resolved before resolveRelativeColor()
|
||||
} else {
|
||||
throw new SyntaxError(`Unexpected token ${FN_VAR} found.`);
|
||||
}
|
||||
} else if (!REG_FN_REL.test(value)) {
|
||||
return value;
|
||||
}
|
||||
value = value.toLowerCase().trim();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'resolveRelativeColor',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return cachedResult as NullObject;
|
||||
}
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
const originColor = extractOriginColor(value, opt);
|
||||
if (originColor instanceof NullObject) {
|
||||
setCache(cacheKey, null);
|
||||
return originColor;
|
||||
}
|
||||
value = originColor;
|
||||
if (format === VAL_SPEC) {
|
||||
if (value.startsWith('rgba(')) {
|
||||
value = value.replace(/^rgba\(/, 'rgb(');
|
||||
} else if (value.startsWith('hsla(')) {
|
||||
value = value.replace(/^hsla\(/, 'hsl(');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
const tokens = tokenize({ css: value });
|
||||
const components = parseComponentValue(tokens) as ComponentValue;
|
||||
const parsedComponents = colorParser(components);
|
||||
if (!parsedComponents) {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
const {
|
||||
alpha: alphaComponent,
|
||||
channels: channelsComponent,
|
||||
colorNotation,
|
||||
syntaxFlags
|
||||
} = parsedComponents;
|
||||
let alpha: number | string;
|
||||
if (Number.isNaN(Number(alphaComponent))) {
|
||||
if (syntaxFlags instanceof Set && syntaxFlags.has(KEY_NONE)) {
|
||||
alpha = NONE;
|
||||
} else {
|
||||
alpha = 0;
|
||||
}
|
||||
} else {
|
||||
alpha = roundToPrecision(Number(alphaComponent), OCT);
|
||||
}
|
||||
let v1: number | string;
|
||||
let v2: number | string;
|
||||
let v3: number | string;
|
||||
[v1, v2, v3] = channelsComponent;
|
||||
let resolvedValue;
|
||||
if (REG_CS_CIE.test(colorNotation)) {
|
||||
const hasNone = syntaxFlags instanceof Set && syntaxFlags.has(KEY_NONE);
|
||||
if (Number.isNaN(v1)) {
|
||||
if (hasNone) {
|
||||
v1 = NONE;
|
||||
} else {
|
||||
v1 = 0;
|
||||
}
|
||||
} else {
|
||||
v1 = roundToPrecision(v1, HEX);
|
||||
}
|
||||
if (Number.isNaN(v2)) {
|
||||
if (hasNone) {
|
||||
v2 = NONE;
|
||||
} else {
|
||||
v2 = 0;
|
||||
}
|
||||
} else {
|
||||
v2 = roundToPrecision(v2, HEX);
|
||||
}
|
||||
if (Number.isNaN(v3)) {
|
||||
if (hasNone) {
|
||||
v3 = NONE;
|
||||
} else {
|
||||
v3 = 0;
|
||||
}
|
||||
} else {
|
||||
v3 = roundToPrecision(v3, HEX);
|
||||
}
|
||||
if (alpha === 1) {
|
||||
resolvedValue = `${colorNotation}(${v1} ${v2} ${v3})`;
|
||||
} else {
|
||||
resolvedValue = `${colorNotation}(${v1} ${v2} ${v3} / ${alpha})`;
|
||||
}
|
||||
} else if (REG_CS_HSL.test(colorNotation)) {
|
||||
if (Number.isNaN(v1)) {
|
||||
v1 = 0;
|
||||
}
|
||||
if (Number.isNaN(v2)) {
|
||||
v2 = 0;
|
||||
}
|
||||
if (Number.isNaN(v3)) {
|
||||
v3 = 0;
|
||||
}
|
||||
let [r, g, b] = convertColorToRgb(
|
||||
`${colorNotation}(${v1} ${v2} ${v3} / ${alpha})`
|
||||
) as ColorChannels;
|
||||
r = roundToPrecision(r / MAX_RGB, DEC);
|
||||
g = roundToPrecision(g / MAX_RGB, DEC);
|
||||
b = roundToPrecision(b / MAX_RGB, DEC);
|
||||
if (alpha === 1) {
|
||||
resolvedValue = `color(srgb ${r} ${g} ${b})`;
|
||||
} else {
|
||||
resolvedValue = `color(srgb ${r} ${g} ${b} / ${alpha})`;
|
||||
}
|
||||
} else {
|
||||
const cs = colorNotation === 'rgb' ? 'srgb' : colorNotation;
|
||||
const hasNone = syntaxFlags instanceof Set && syntaxFlags.has(KEY_NONE);
|
||||
if (Number.isNaN(v1)) {
|
||||
if (hasNone) {
|
||||
v1 = NONE;
|
||||
} else {
|
||||
v1 = 0;
|
||||
}
|
||||
} else {
|
||||
v1 = roundToPrecision(v1, DEC);
|
||||
}
|
||||
if (Number.isNaN(v2)) {
|
||||
if (hasNone) {
|
||||
v2 = NONE;
|
||||
} else {
|
||||
v2 = 0;
|
||||
}
|
||||
} else {
|
||||
v2 = roundToPrecision(v2, DEC);
|
||||
}
|
||||
if (Number.isNaN(v3)) {
|
||||
if (hasNone) {
|
||||
v3 = NONE;
|
||||
} else {
|
||||
v3 = 0;
|
||||
}
|
||||
} else {
|
||||
v3 = roundToPrecision(v3, DEC);
|
||||
}
|
||||
if (alpha === 1) {
|
||||
resolvedValue = `color(${cs} ${v1} ${v2} ${v3})`;
|
||||
} else {
|
||||
resolvedValue = `color(${cs} ${v1} ${v2} ${v3} / ${alpha})`;
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, resolvedValue);
|
||||
return resolvedValue;
|
||||
}
|
||||
443
node_modules/@asamuzakjp/css-color/src/js/resolve.ts
generated
vendored
Normal file
443
node_modules/@asamuzakjp/css-color/src/js/resolve.ts
generated
vendored
Normal file
@@ -0,0 +1,443 @@
|
||||
/**
|
||||
* resolve
|
||||
*/
|
||||
|
||||
import {
|
||||
CacheItem,
|
||||
NullObject,
|
||||
createCacheKey,
|
||||
getCache,
|
||||
setCache
|
||||
} from './cache';
|
||||
import {
|
||||
convertRgbToHex,
|
||||
resolveColorFunc,
|
||||
resolveColorMix,
|
||||
resolveColorValue
|
||||
} from './color';
|
||||
import { isString } from './common';
|
||||
import { cssCalc } from './css-calc';
|
||||
import { resolveVar } from './css-var';
|
||||
import { resolveRelativeColor } from './relative-color';
|
||||
import { splitValue } from './util';
|
||||
import {
|
||||
ComputedColorChannels,
|
||||
Options,
|
||||
SpecifiedColorChannels
|
||||
} from './typedef';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
FN_COLOR,
|
||||
FN_MIX,
|
||||
SYN_FN_CALC,
|
||||
SYN_FN_LIGHT_DARK,
|
||||
SYN_FN_REL,
|
||||
SYN_FN_VAR,
|
||||
VAL_COMP,
|
||||
VAL_SPEC
|
||||
} from './constant';
|
||||
const NAMESPACE = 'resolve';
|
||||
const RGB_TRANSPARENT = 'rgba(0, 0, 0, 0)';
|
||||
|
||||
/* regexp */
|
||||
const REG_FN_CALC = new RegExp(SYN_FN_CALC);
|
||||
const REG_FN_LIGHT_DARK = new RegExp(SYN_FN_LIGHT_DARK);
|
||||
const REG_FN_REL = new RegExp(SYN_FN_REL);
|
||||
const REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
|
||||
/**
|
||||
* resolve color
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns resolved color
|
||||
*/
|
||||
export const resolveColor = (
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): string | NullObject => {
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const {
|
||||
colorScheme = 'normal',
|
||||
currentColor = '',
|
||||
format = VAL_COMP,
|
||||
nullable = false
|
||||
} = opt;
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'resolve',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return cachedResult as NullObject;
|
||||
}
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
if (REG_FN_VAR.test(value)) {
|
||||
if (format === VAL_SPEC) {
|
||||
setCache(cacheKey, value);
|
||||
return value;
|
||||
}
|
||||
const resolvedValue = resolveVar(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
switch (format) {
|
||||
case 'hex':
|
||||
case 'hexAlpha': {
|
||||
setCache(cacheKey, resolvedValue);
|
||||
return resolvedValue;
|
||||
}
|
||||
default: {
|
||||
if (nullable) {
|
||||
setCache(cacheKey, resolvedValue);
|
||||
return resolvedValue;
|
||||
}
|
||||
const res = RGB_TRANSPARENT;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value = resolvedValue;
|
||||
}
|
||||
}
|
||||
if (opt.format !== format) {
|
||||
opt.format = format;
|
||||
}
|
||||
value = value.toLowerCase();
|
||||
if (REG_FN_LIGHT_DARK.test(value) && value.endsWith(')')) {
|
||||
const colorParts = value.replace(REG_FN_LIGHT_DARK, '').replace(/\)$/, '');
|
||||
const [light = '', dark = ''] = splitValue(colorParts, {
|
||||
delimiter: ','
|
||||
});
|
||||
if (light && dark) {
|
||||
if (format === VAL_SPEC) {
|
||||
const lightColor = resolveColor(light, opt);
|
||||
const darkColor = resolveColor(dark, opt);
|
||||
let res;
|
||||
if (lightColor && darkColor) {
|
||||
res = `light-dark(${lightColor}, ${darkColor})`;
|
||||
} else {
|
||||
res = '';
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
let resolvedValue;
|
||||
if (colorScheme === 'dark') {
|
||||
resolvedValue = resolveColor(dark, opt);
|
||||
} else {
|
||||
resolvedValue = resolveColor(light, opt);
|
||||
}
|
||||
let res;
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
if (nullable) {
|
||||
res = resolvedValue;
|
||||
} else {
|
||||
res = RGB_TRANSPARENT;
|
||||
}
|
||||
} else {
|
||||
res = resolvedValue;
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
// invalid value
|
||||
switch (format) {
|
||||
case VAL_SPEC: {
|
||||
setCache(cacheKey, '');
|
||||
return '';
|
||||
}
|
||||
case 'hex':
|
||||
case 'hexAlpha': {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
case VAL_COMP:
|
||||
default: {
|
||||
const res = RGB_TRANSPARENT;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (REG_FN_REL.test(value)) {
|
||||
const resolvedValue = resolveRelativeColor(value, opt);
|
||||
if (format === VAL_COMP) {
|
||||
let res;
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
if (nullable) {
|
||||
res = resolvedValue;
|
||||
} else {
|
||||
res = RGB_TRANSPARENT;
|
||||
}
|
||||
} else {
|
||||
res = resolvedValue;
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
if (format === VAL_SPEC) {
|
||||
let res = '';
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
res = '';
|
||||
} else {
|
||||
res = resolvedValue;
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
value = '';
|
||||
} else {
|
||||
value = resolvedValue;
|
||||
}
|
||||
}
|
||||
if (REG_FN_CALC.test(value)) {
|
||||
value = cssCalc(value, opt);
|
||||
}
|
||||
let cs = '';
|
||||
let r = NaN;
|
||||
let g = NaN;
|
||||
let b = NaN;
|
||||
let alpha = NaN;
|
||||
if (value === 'transparent') {
|
||||
switch (format) {
|
||||
case VAL_SPEC: {
|
||||
setCache(cacheKey, value);
|
||||
return value;
|
||||
}
|
||||
case 'hex': {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
case 'hexAlpha': {
|
||||
const res = '#00000000';
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
case VAL_COMP:
|
||||
default: {
|
||||
const res = RGB_TRANSPARENT;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
} else if (value === 'currentcolor') {
|
||||
if (format === VAL_SPEC) {
|
||||
setCache(cacheKey, value);
|
||||
return value;
|
||||
}
|
||||
if (currentColor) {
|
||||
let resolvedValue;
|
||||
if (currentColor.startsWith(FN_MIX)) {
|
||||
resolvedValue = resolveColorMix(currentColor, opt);
|
||||
} else if (currentColor.startsWith(FN_COLOR)) {
|
||||
resolvedValue = resolveColorFunc(currentColor, opt);
|
||||
} else {
|
||||
resolvedValue = resolveColorValue(currentColor, opt);
|
||||
}
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
setCache(cacheKey, resolvedValue);
|
||||
return resolvedValue;
|
||||
}
|
||||
[cs, r, g, b, alpha] = resolvedValue as ComputedColorChannels;
|
||||
} else if (format === VAL_COMP) {
|
||||
const res = RGB_TRANSPARENT;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
} else if (format === VAL_SPEC) {
|
||||
if (value.startsWith(FN_MIX)) {
|
||||
const res = resolveColorMix(value, opt) as string;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
} else if (value.startsWith(FN_COLOR)) {
|
||||
const [scs, rr, gg, bb, aa] = resolveColorFunc(
|
||||
value,
|
||||
opt
|
||||
) as SpecifiedColorChannels;
|
||||
let res = '';
|
||||
if (aa === 1) {
|
||||
res = `color(${scs} ${rr} ${gg} ${bb})`;
|
||||
} else {
|
||||
res = `color(${scs} ${rr} ${gg} ${bb} / ${aa})`;
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
} else {
|
||||
const rgb = resolveColorValue(value, opt);
|
||||
if (isString(rgb)) {
|
||||
setCache(cacheKey, rgb);
|
||||
return rgb;
|
||||
}
|
||||
const [scs, rr, gg, bb, aa] = rgb as SpecifiedColorChannels;
|
||||
let res = '';
|
||||
if (scs === 'rgb') {
|
||||
if (aa === 1) {
|
||||
res = `${scs}(${rr}, ${gg}, ${bb})`;
|
||||
} else {
|
||||
res = `${scs}a(${rr}, ${gg}, ${bb}, ${aa})`;
|
||||
}
|
||||
} else if (aa === 1) {
|
||||
res = `${scs}(${rr} ${gg} ${bb})`;
|
||||
} else {
|
||||
res = `${scs}(${rr} ${gg} ${bb} / ${aa})`;
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
} else if (value.startsWith(FN_MIX)) {
|
||||
if (/currentcolor/.test(value)) {
|
||||
if (currentColor) {
|
||||
value = value.replace(/currentcolor/g, currentColor);
|
||||
}
|
||||
}
|
||||
if (/transparent/.test(value)) {
|
||||
value = value.replace(/transparent/g, RGB_TRANSPARENT);
|
||||
}
|
||||
const resolvedValue = resolveColorMix(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
setCache(cacheKey, resolvedValue);
|
||||
return resolvedValue;
|
||||
}
|
||||
[cs, r, g, b, alpha] = resolvedValue as ComputedColorChannels;
|
||||
} else if (value.startsWith(FN_COLOR)) {
|
||||
const resolvedValue = resolveColorFunc(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
setCache(cacheKey, resolvedValue);
|
||||
return resolvedValue;
|
||||
}
|
||||
[cs, r, g, b, alpha] = resolvedValue as ComputedColorChannels;
|
||||
} else if (value) {
|
||||
const resolvedValue = resolveColorValue(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
setCache(cacheKey, resolvedValue);
|
||||
return resolvedValue;
|
||||
}
|
||||
[cs, r, g, b, alpha] = resolvedValue as ComputedColorChannels;
|
||||
}
|
||||
let res = '';
|
||||
switch (format) {
|
||||
case 'hex': {
|
||||
if (
|
||||
Number.isNaN(r) ||
|
||||
Number.isNaN(g) ||
|
||||
Number.isNaN(b) ||
|
||||
Number.isNaN(alpha) ||
|
||||
alpha === 0
|
||||
) {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
res = convertRgbToHex([r, g, b, 1]);
|
||||
break;
|
||||
}
|
||||
case 'hexAlpha': {
|
||||
if (
|
||||
Number.isNaN(r) ||
|
||||
Number.isNaN(g) ||
|
||||
Number.isNaN(b) ||
|
||||
Number.isNaN(alpha)
|
||||
) {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
res = convertRgbToHex([r, g, b, alpha]);
|
||||
break;
|
||||
}
|
||||
case VAL_COMP:
|
||||
default: {
|
||||
switch (cs) {
|
||||
case 'rgb': {
|
||||
if (alpha === 1) {
|
||||
res = `${cs}(${r}, ${g}, ${b})`;
|
||||
} else {
|
||||
res = `${cs}a(${r}, ${g}, ${b}, ${alpha})`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'lab':
|
||||
case 'lch':
|
||||
case 'oklab':
|
||||
case 'oklch': {
|
||||
if (alpha === 1) {
|
||||
res = `${cs}(${r} ${g} ${b})`;
|
||||
} else {
|
||||
res = `${cs}(${r} ${g} ${b} / ${alpha})`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// color()
|
||||
default: {
|
||||
if (alpha === 1) {
|
||||
res = `color(${cs} ${r} ${g} ${b})`;
|
||||
} else {
|
||||
res = `color(${cs} ${r} ${g} ${b} / ${alpha})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* resolve CSS color
|
||||
* @param value
|
||||
* - CSS color value
|
||||
* - system colors are not supported
|
||||
* @param [opt] - options
|
||||
* @param [opt.currentColor]
|
||||
* - color to use for `currentcolor` keyword
|
||||
* - if omitted, it will be treated as a missing color
|
||||
* i.e. `rgb(none none none / none)`
|
||||
* @param [opt.customProperty]
|
||||
* - custom properties
|
||||
* - pair of `--` prefixed property name and value,
|
||||
* e.g. `customProperty: { '--some-color': '#0000ff' }`
|
||||
* - and/or `callback` function to get the value of the custom property,
|
||||
* e.g. `customProperty: { callback: someDeclaration.getPropertyValue }`
|
||||
* @param [opt.dimension]
|
||||
* - dimension, convert relative length to pixels
|
||||
* - pair of unit and it's value as a number in pixels,
|
||||
* e.g. `dimension: { em: 12, rem: 16, vw: 10.26 }`
|
||||
* - and/or `callback` function to get the value as a number in pixels,
|
||||
* e.g. `dimension: { callback: convertUnitToPixel }`
|
||||
* @param [opt.format]
|
||||
* - output format, one of below
|
||||
* - `computedValue` (default), [computed value][139] of the color
|
||||
* - `specifiedValue`, [specified value][140] of the color
|
||||
* - `hex`, hex color notation, i.e. `rrggbb`
|
||||
* - `hexAlpha`, hex color notation with alpha channel, i.e. `#rrggbbaa`
|
||||
* @returns
|
||||
* - one of rgba?(), #rrggbb(aa)?, color-name, '(empty-string)',
|
||||
* color(color-space r g b / alpha), color(color-space x y z / alpha),
|
||||
* lab(l a b / alpha), lch(l c h / alpha), oklab(l a b / alpha),
|
||||
* oklch(l c h / alpha), null
|
||||
* - in `computedValue`, values are numbers, however `rgb()` values are
|
||||
* integers
|
||||
* - in `specifiedValue`, returns `empty string` for unknown and/or invalid
|
||||
* color
|
||||
* - in `hex`, returns `null` for `transparent`, and also returns `null` if
|
||||
* any of `r`, `g`, `b`, `alpha` is not a number
|
||||
* - in `hexAlpha`, returns `#00000000` for `transparent`,
|
||||
* however returns `null` if any of `r`, `g`, `b`, `alpha` is not a number
|
||||
*/
|
||||
export const resolve = (value: string, opt: Options = {}): string | null => {
|
||||
opt.nullable = false;
|
||||
const resolvedValue = resolveColor(value, opt);
|
||||
if (resolvedValue instanceof NullObject) {
|
||||
return null;
|
||||
}
|
||||
return resolvedValue as string;
|
||||
};
|
||||
88
node_modules/@asamuzakjp/css-color/src/js/typedef.ts
generated
vendored
Normal file
88
node_modules/@asamuzakjp/css-color/src/js/typedef.ts
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* typedef
|
||||
*/
|
||||
|
||||
/* type definitions */
|
||||
/**
|
||||
* @typedef Options - options
|
||||
* @property [alpha] - enable alpha
|
||||
* @property [colorSpace] - color space
|
||||
* @property [currentColor] - color for currentcolor
|
||||
* @property [customProperty] - custom properties
|
||||
* @property [d50] - white point in d50
|
||||
* @property [dimension] - dimension
|
||||
* @property [format] - output format
|
||||
* @property [key] - key
|
||||
*/
|
||||
export interface Options {
|
||||
alpha?: boolean;
|
||||
colorScheme?: string;
|
||||
colorSpace?: string;
|
||||
currentColor?: string;
|
||||
customProperty?: Record<string, string | ((K: string) => string)>;
|
||||
d50?: boolean;
|
||||
delimiter?: string | string[];
|
||||
dimension?: Record<string, number | ((K: string) => number)>;
|
||||
format?: string;
|
||||
nullable?: boolean;
|
||||
preserveComment?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type ColorChannels - color channels
|
||||
*/
|
||||
export type ColorChannels = [x: number, y: number, z: number, alpha: number];
|
||||
|
||||
/**
|
||||
* @type StringColorChannels - color channels
|
||||
*/
|
||||
export type StringColorChannels = [
|
||||
x: string,
|
||||
y: string,
|
||||
z: string,
|
||||
alpha: string | undefined
|
||||
];
|
||||
|
||||
/**
|
||||
* @type StringColorSpacedChannels - specified value
|
||||
*/
|
||||
export type StringColorSpacedChannels = [
|
||||
cs: string,
|
||||
x: string,
|
||||
y: string,
|
||||
z: string,
|
||||
alpha: string | undefined
|
||||
];
|
||||
|
||||
/**
|
||||
* @type ComputedColorChannels - computed value
|
||||
*/
|
||||
export type ComputedColorChannels = [
|
||||
cs: string,
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
alpha: number
|
||||
];
|
||||
|
||||
/**
|
||||
* @type SpecifiedColorChannels - specified value
|
||||
*/
|
||||
export type SpecifiedColorChannels = [
|
||||
cs: string,
|
||||
x: number | string,
|
||||
y: number | string,
|
||||
z: number | string,
|
||||
alpha: number | string
|
||||
];
|
||||
|
||||
/**
|
||||
* @type MatchedRegExp - matched regexp array
|
||||
*/
|
||||
export type MatchedRegExp = [
|
||||
match: string,
|
||||
gr1: string,
|
||||
gr2: string,
|
||||
gr3: string,
|
||||
gr4: string
|
||||
];
|
||||
336
node_modules/@asamuzakjp/css-color/src/js/util.ts
generated
vendored
Normal file
336
node_modules/@asamuzakjp/css-color/src/js/util.ts
generated
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* util
|
||||
*/
|
||||
|
||||
import { TokenType, tokenize } from '@csstools/css-tokenizer';
|
||||
import { CacheItem, createCacheKey, getCache, setCache } from './cache';
|
||||
import { isString } from './common';
|
||||
import { resolveColor } from './resolve';
|
||||
import { Options } from './typedef';
|
||||
|
||||
/* constants */
|
||||
import { NAMED_COLORS } from './color';
|
||||
import { SYN_COLOR_TYPE, SYN_MIX, VAL_SPEC } from './constant';
|
||||
const {
|
||||
CloseParen: PAREN_CLOSE,
|
||||
Comma: COMMA,
|
||||
Comment: COMMENT,
|
||||
Delim: DELIM,
|
||||
EOF,
|
||||
Function: FUNC,
|
||||
Ident: IDENT,
|
||||
OpenParen: PAREN_OPEN,
|
||||
Whitespace: W_SPACE
|
||||
} = TokenType;
|
||||
const NAMESPACE = 'util';
|
||||
|
||||
/* numeric constants */
|
||||
const DEC = 10;
|
||||
const HEX = 16;
|
||||
const DEG = 360;
|
||||
const DEG_HALF = 180;
|
||||
|
||||
/* regexp */
|
||||
const REG_COLOR = new RegExp(`^(?:${SYN_COLOR_TYPE})$`);
|
||||
const REG_FN_COLOR =
|
||||
/^(?:(?:ok)?l(?:ab|ch)|color(?:-mix)?|hsla?|hwb|rgba?|var)\(/;
|
||||
const REG_MIX = new RegExp(SYN_MIX);
|
||||
|
||||
/**
|
||||
* split value
|
||||
* NOTE: comments are stripped, it can be preserved if, in the options param,
|
||||
* `delimiter` is either ',' or '/' and with `preserveComment` set to `true`
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns array of values
|
||||
*/
|
||||
export const splitValue = (value: string, opt: Options = {}): string[] => {
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const { delimiter = ' ', preserveComment = false } = opt;
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'splitValue',
|
||||
value
|
||||
},
|
||||
{
|
||||
delimiter,
|
||||
preserveComment
|
||||
}
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as string[];
|
||||
}
|
||||
let regDelimiter;
|
||||
if (delimiter === ',') {
|
||||
regDelimiter = /^,$/;
|
||||
} else if (delimiter === '/') {
|
||||
regDelimiter = /^\/$/;
|
||||
} else {
|
||||
regDelimiter = /^\s+$/;
|
||||
}
|
||||
const tokens = tokenize({ css: value });
|
||||
let nest = 0;
|
||||
let str = '';
|
||||
const res: string[] = [];
|
||||
while (tokens.length) {
|
||||
const [type, value] = tokens.shift() as [TokenType, string];
|
||||
switch (type) {
|
||||
case COMMA: {
|
||||
if (regDelimiter.test(value)) {
|
||||
if (nest === 0) {
|
||||
res.push(str.trim());
|
||||
str = '';
|
||||
} else {
|
||||
str += value;
|
||||
}
|
||||
} else {
|
||||
str += value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DELIM: {
|
||||
if (regDelimiter.test(value)) {
|
||||
if (nest === 0) {
|
||||
res.push(str.trim());
|
||||
str = '';
|
||||
} else {
|
||||
str += value;
|
||||
}
|
||||
} else {
|
||||
str += value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case COMMENT: {
|
||||
if (preserveComment && (delimiter === ',' || delimiter === '/')) {
|
||||
str += value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FUNC:
|
||||
case PAREN_OPEN: {
|
||||
str += value;
|
||||
nest++;
|
||||
break;
|
||||
}
|
||||
case PAREN_CLOSE: {
|
||||
str += value;
|
||||
nest--;
|
||||
break;
|
||||
}
|
||||
case W_SPACE: {
|
||||
if (regDelimiter.test(value)) {
|
||||
if (nest === 0) {
|
||||
if (str) {
|
||||
res.push(str.trim());
|
||||
str = '';
|
||||
}
|
||||
} else {
|
||||
str += ' ';
|
||||
}
|
||||
} else if (!str.endsWith(' ')) {
|
||||
str += ' ';
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (type === EOF) {
|
||||
res.push(str.trim());
|
||||
str = '';
|
||||
} else {
|
||||
str += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* extract dashed-ident tokens
|
||||
* @param value - CSS value
|
||||
* @returns array of dashed-ident tokens
|
||||
*/
|
||||
export const extractDashedIdent = (value: string): string[] => {
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: 'extractDashedIdent',
|
||||
value
|
||||
});
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as string[];
|
||||
}
|
||||
const tokens = tokenize({ css: value });
|
||||
const items = new Set();
|
||||
while (tokens.length) {
|
||||
const [type, value] = tokens.shift() as [TokenType, string];
|
||||
if (type === IDENT && value.startsWith('--')) {
|
||||
items.add(value);
|
||||
}
|
||||
}
|
||||
const res = [...items] as string[];
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* is color
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns result
|
||||
*/
|
||||
export const isColor = (value: unknown, opt: Options = {}): boolean => {
|
||||
if (isString(value)) {
|
||||
value = value.toLowerCase().trim();
|
||||
if (value && isString(value)) {
|
||||
if (/^[a-z]+$/.test(value)) {
|
||||
if (
|
||||
/^(?:currentcolor|transparent)$/.test(value) ||
|
||||
Object.hasOwn(NAMED_COLORS, value)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} else if (REG_COLOR.test(value) || REG_MIX.test(value)) {
|
||||
return true;
|
||||
} else if (REG_FN_COLOR.test(value)) {
|
||||
opt.nullable = true;
|
||||
if (!opt.format) {
|
||||
opt.format = VAL_SPEC;
|
||||
}
|
||||
const resolvedValue = resolveColor(value, opt);
|
||||
if (resolvedValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* value to JSON string
|
||||
* @param value - CSS value
|
||||
* @param [func] - stringify function
|
||||
* @returns stringified value in JSON notation
|
||||
*/
|
||||
export const valueToJsonString = (
|
||||
value: unknown,
|
||||
func: boolean = false
|
||||
): string => {
|
||||
if (typeof value === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
const res = JSON.stringify(value, (_key, val) => {
|
||||
let replacedValue;
|
||||
if (typeof val === 'undefined') {
|
||||
replacedValue = null;
|
||||
} else if (typeof val === 'function') {
|
||||
if (func) {
|
||||
replacedValue = val.toString().replace(/\s/g, '').substring(0, HEX);
|
||||
} else {
|
||||
replacedValue = val.name;
|
||||
}
|
||||
} else if (val instanceof Map || val instanceof Set) {
|
||||
replacedValue = [...val];
|
||||
} else if (typeof val === 'bigint') {
|
||||
replacedValue = val.toString();
|
||||
} else {
|
||||
replacedValue = val;
|
||||
}
|
||||
return replacedValue;
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* round to specified precision
|
||||
* @param value - numeric value
|
||||
* @param bit - minimum bits
|
||||
* @returns rounded value
|
||||
*/
|
||||
export const roundToPrecision = (value: number, bit: number = 0): number => {
|
||||
if (!Number.isFinite(value)) {
|
||||
throw new TypeError(`${value} is not a finite number.`);
|
||||
}
|
||||
if (!Number.isFinite(bit)) {
|
||||
throw new TypeError(`${bit} is not a finite number.`);
|
||||
} else if (bit < 0 || bit > HEX) {
|
||||
throw new RangeError(`${bit} is not between 0 and ${HEX}.`);
|
||||
}
|
||||
if (bit === 0) {
|
||||
return Math.round(value);
|
||||
}
|
||||
let val;
|
||||
if (bit === HEX) {
|
||||
val = value.toPrecision(6);
|
||||
} else if (bit < DEC) {
|
||||
val = value.toPrecision(4);
|
||||
} else {
|
||||
val = value.toPrecision(5);
|
||||
}
|
||||
return parseFloat(val);
|
||||
};
|
||||
|
||||
/**
|
||||
* interpolate hue
|
||||
* @param hueA - hue value
|
||||
* @param hueB - hue value
|
||||
* @param arc - shorter | longer | increasing | decreasing
|
||||
* @returns result - [hueA, hueB]
|
||||
*/
|
||||
export const interpolateHue = (
|
||||
hueA: number,
|
||||
hueB: number,
|
||||
arc: string = 'shorter'
|
||||
): [number, number] => {
|
||||
if (!Number.isFinite(hueA)) {
|
||||
throw new TypeError(`${hueA} is not a finite number.`);
|
||||
}
|
||||
if (!Number.isFinite(hueB)) {
|
||||
throw new TypeError(`${hueB} is not a finite number.`);
|
||||
}
|
||||
switch (arc) {
|
||||
case 'decreasing': {
|
||||
if (hueB > hueA) {
|
||||
hueA += DEG;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'increasing': {
|
||||
if (hueB < hueA) {
|
||||
hueB += DEG;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'longer': {
|
||||
if (hueB > hueA && hueB < hueA + DEG_HALF) {
|
||||
hueA += DEG;
|
||||
} else if (hueB > hueA + DEG_HALF * -1 && hueB <= hueA) {
|
||||
hueB += DEG;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'shorter':
|
||||
default: {
|
||||
if (hueB > hueA + DEG_HALF) {
|
||||
hueA += DEG;
|
||||
} else if (hueB < hueA + DEG_HALF * -1) {
|
||||
hueB += DEG;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [hueA, hueB];
|
||||
};
|
||||
21
node_modules/@asamuzakjp/dom-selector/LICENSE
generated
vendored
Normal file
21
node_modules/@asamuzakjp/dom-selector/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 asamuzaK (Kazz)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
324
node_modules/@asamuzakjp/dom-selector/README.md
generated
vendored
Normal file
324
node_modules/@asamuzakjp/dom-selector/README.md
generated
vendored
Normal file
@@ -0,0 +1,324 @@
|
||||
# DOM Selector
|
||||
|
||||
[](https://github.com/asamuzaK/domSelector/actions/workflows/node.js.yml)
|
||||
[](https://github.com/asamuzaK/domSelector/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://www.npmjs.com/package/@asamuzakjp/dom-selector)
|
||||
|
||||
A CSS selector engine.
|
||||
|
||||
## Install
|
||||
|
||||
```console
|
||||
npm i @asamuzakjp/dom-selector
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
import { DOMSelector } from '@asamuzakjp/dom-selector';
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
const { window } = new JSDOM();
|
||||
const {
|
||||
closest, matches, querySelector, querySelectorAll
|
||||
} = new DOMSelector(window);
|
||||
```
|
||||
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
### matches(selector, node, opt)
|
||||
|
||||
matches - equivalent to [Element.matches()][64]
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `selector` **[string][59]** CSS selector
|
||||
- `node` **[object][60]** Element node
|
||||
- `opt` **[object][60]?** options
|
||||
- `opt.noexcept` **[boolean][61]?** no exception
|
||||
- `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
|
||||
|
||||
Returns **[boolean][61]** `true` if matched, `false` otherwise
|
||||
|
||||
|
||||
### closest(selector, node, opt)
|
||||
|
||||
closest - equivalent to [Element.closest()][65]
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `selector` **[string][59]** CSS selector
|
||||
- `node` **[object][60]** Element node
|
||||
- `opt` **[object][60]?** options
|
||||
- `opt.noexcept` **[boolean][61]?** no exception
|
||||
- `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
|
||||
|
||||
Returns **[object][60]?** matched node
|
||||
|
||||
|
||||
### querySelector(selector, node, opt)
|
||||
|
||||
querySelector - equivalent to [Document.querySelector()][66], [DocumentFragment.querySelector()][67] and [Element.querySelector()][68]
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `selector` **[string][59]** CSS selector
|
||||
- `node` **[object][60]** Document, DocumentFragment or Element node
|
||||
- `opt` **[object][60]?** options
|
||||
- `opt.noexcept` **[boolean][61]?** no exception
|
||||
- `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
|
||||
|
||||
Returns **[object][60]?** matched node
|
||||
|
||||
|
||||
### querySelectorAll(selector, node, opt)
|
||||
|
||||
querySelectorAll - equivalent to [Document.querySelectorAll()][69], [DocumentFragment.querySelectorAll()][70] and [Element.querySelectorAll()][71]
|
||||
**NOTE**: returns Array, not NodeList
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `selector` **[string][59]** CSS selector
|
||||
- `node` **[object][60]** Document, DocumentFragment or Element node
|
||||
- `opt` **[object][60]?** options
|
||||
- `opt.noexcept` **[boolean][61]?** no exception
|
||||
- `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
|
||||
|
||||
Returns **[Array][62]<([object][60] \| [undefined][63])>** array of matched nodes
|
||||
|
||||
|
||||
## Monkey patch jsdom
|
||||
|
||||
``` javascript
|
||||
import { DOMSelector } from '@asamuzakjp/dom-selector';
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
const dom = new JSDOM('', {
|
||||
runScripts: 'dangerously',
|
||||
url: 'http://localhost/',
|
||||
beforeParse: window => {
|
||||
const domSelector = new DOMSelector(window);
|
||||
|
||||
const matches = domSelector.matches.bind(domSelector);
|
||||
window.Element.prototype.matches = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return matches(selector, this);
|
||||
};
|
||||
|
||||
const closest = domSelector.closest.bind(domSelector);
|
||||
window.Element.prototype.closest = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return closest(selector, this);
|
||||
};
|
||||
|
||||
const querySelector = domSelector.querySelector.bind(domSelector);
|
||||
window.Document.prototype.querySelector = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return querySelector(selector, this);
|
||||
};
|
||||
window.DocumentFragment.prototype.querySelector = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return querySelector(selector, this);
|
||||
};
|
||||
window.Element.prototype.querySelector = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return querySelector(selector, this);
|
||||
};
|
||||
|
||||
const querySelectorAll = domSelector.querySelectorAll.bind(domSelector);
|
||||
window.Document.prototype.querySelectorAll = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return querySelectorAll(selector, this);
|
||||
};
|
||||
window.DocumentFragment.prototype.querySelectorAll = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return querySelectorAll(selector, this);
|
||||
};
|
||||
window.Element.prototype.querySelectorAll = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return querySelectorAll(selector, this);
|
||||
};
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Supported CSS selectors
|
||||
|
||||
|Pattern|Supported|Note|
|
||||
|:--------|:-------:|:--------|
|
||||
|\*|✓| |
|
||||
|E|✓| |
|
||||
|ns\|E|✓| |
|
||||
|\*\|E|✓| |
|
||||
|\|E|✓| |
|
||||
|E F|✓| |
|
||||
|E > F|✓| |
|
||||
|E + F|✓| |
|
||||
|E ~ F|✓| |
|
||||
|F \|\| E|Unsupported| |
|
||||
|E.warning|✓| |
|
||||
|E#myid|✓| |
|
||||
|E\[foo\]|✓| |
|
||||
|E\[foo="bar"\]|✓| |
|
||||
|E\[foo="bar" i\]|✓| |
|
||||
|E\[foo="bar" s\]|✓| |
|
||||
|E\[foo~="bar"\]|✓| |
|
||||
|E\[foo^="bar"\]|✓| |
|
||||
|E\[foo$="bar"\]|✓| |
|
||||
|E\[foo*="bar"\]|✓| |
|
||||
|E\[foo\|="en"\]|✓| |
|
||||
|E:is(s1, s2, …)|✓| |
|
||||
|E:not(s1, s2, …)|✓| |
|
||||
|E:where(s1, s2, …)|✓| |
|
||||
|E:has(rs1, rs2, …)|✓| |
|
||||
|E:defined|Partially supported|Matching with MathML is not yet supported.|
|
||||
|E:dir(ltr)|✓| |
|
||||
|E:lang(en)|✓| |
|
||||
|E:any‑link|✓| |
|
||||
|E:link|✓| |
|
||||
|E:visited|✓|Returns `false` or `null` to prevent fingerprinting.|
|
||||
|E:local‑link|✓| |
|
||||
|E:target|✓| |
|
||||
|E:target‑within|✓| |
|
||||
|E:scope|✓| |
|
||||
|E:hover|✓| |
|
||||
|E:active|✓| |
|
||||
|E:focus|✓| |
|
||||
|E:focus‑visible|✓| |
|
||||
|E:focus‑within|✓| |
|
||||
|E:current|Unsupported| |
|
||||
|E:current(s)|Unsupported| |
|
||||
|E:past|Unsupported| |
|
||||
|E:future|Unsupported| |
|
||||
|E:open<br>E:closed|Partially supported|Matching with <select>, e.g. `select:open`, is not supported.|
|
||||
|E:popover-open|✓| |
|
||||
|E:enabled<br>E:disabled|✓| |
|
||||
|E:read‑write<br>E:read‑only|✓| |
|
||||
|E:placeholder‑shown|✓| |
|
||||
|E:default|✓| |
|
||||
|E:checked|✓| |
|
||||
|E:indeterminate|✓| |
|
||||
|E:blank|Unsupported| |
|
||||
|E:valid<br>E:invalid|✓| |
|
||||
|E:in-range<br>E:out-of-range|✓| |
|
||||
|E:required<br>E:optional|✓| |
|
||||
|E:user‑valid<br>E:user‑invalid|Unsupported| |
|
||||
|E:root|✓| |
|
||||
|E:empty|✓| |
|
||||
|E:nth‑child(n [of S]?)|✓| |
|
||||
|E:nth‑last‑child(n [of S]?)|✓| |
|
||||
|E:first‑child|✓| |
|
||||
|E:last‑child|✓| |
|
||||
|E:only‑child|✓| |
|
||||
|E:nth‑of‑type(n)|✓| |
|
||||
|E:nth‑last‑of‑type(n)|✓| |
|
||||
|E:first‑of‑type|✓| |
|
||||
|E:last‑of‑type|✓| |
|
||||
|E:only‑of‑type|✓| |
|
||||
|E:nth‑col(n)|Unsupported| |
|
||||
|E:nth‑last‑col(n)|Unsupported| |
|
||||
|CE:state(v)|✓|*1|
|
||||
|:host|✓| |
|
||||
|:host(s)|✓| |
|
||||
|:host(:state(v))|✓|*1|
|
||||
|:host:has(rs1, rs2, ...)|✓| |
|
||||
|:host(s):has(rs1, rs2, ...)|✓| |
|
||||
|:host‑context(s)|✓| |
|
||||
|:host‑context(s):has(rs1, rs2, ...)|✓| |
|
||||
|&|✓|Only supports outermost `&`, i.e. equivalent to `:scope`|
|
||||
|
||||
*1: `ElementInternals.states`, i.e. `CustomStateSet`, is not implemented in jsdom, so you need to apply a patch in the custom element constructor.
|
||||
|
||||
``` javascript
|
||||
class LabeledCheckbox extends window.HTMLElement {
|
||||
#internals;
|
||||
constructor() {
|
||||
super();
|
||||
this.#internals = this.attachInternals();
|
||||
// patch CustomStateSet
|
||||
if (!this.#internals.states) {
|
||||
this.#internals.states = new Set();
|
||||
}
|
||||
this.addEventListener('click', this._onClick.bind(this));
|
||||
}
|
||||
get checked() {
|
||||
return this.#internals.states.has('checked');
|
||||
}
|
||||
set checked(flag) {
|
||||
if (flag) {
|
||||
this.#internals.states.add('checked');
|
||||
} else {
|
||||
this.#internals.states.delete('checked');
|
||||
}
|
||||
}
|
||||
_onClick(event) {
|
||||
this.checked = !this.checked;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Performance
|
||||
|
||||
See [benchmark](https://github.com/asamuzaK/domSelector/actions/workflows/benchmark.yml) for the latest results.
|
||||
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
The following resources have been of great help in the development of the DOM Selector.
|
||||
|
||||
- [CSSTree](https://github.com/csstree/csstree)
|
||||
- [selery](https://github.com/danburzo/selery)
|
||||
- [jsdom](https://github.com/jsdom/jsdom)
|
||||
- [nwsapi](https://github.com/dperini/nwsapi)
|
||||
|
||||
---
|
||||
Copyright (c) 2023 [asamuzaK (Kazz)](https://github.com/asamuzaK/)
|
||||
|
||||
|
||||
[1]: #matches
|
||||
[2]: #parameters
|
||||
[3]: #closest
|
||||
[4]: #parameters-1
|
||||
[5]: #queryselector
|
||||
[6]: #parameters-2
|
||||
[7]: #queryselectorall
|
||||
[8]: #parameters-3
|
||||
[59]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
[60]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
[61]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
[62]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||
[63]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined
|
||||
[64]: https://developer.mozilla.org/docs/Web/API/Element/matches
|
||||
[65]: https://developer.mozilla.org/docs/Web/API/Element/closest
|
||||
[66]: https://developer.mozilla.org/docs/Web/API/Document/querySelector
|
||||
[67]: https://developer.mozilla.org/docs/Web/API/DocumentFragment/querySelector
|
||||
[68]: https://developer.mozilla.org/docs/Web/API/Element/querySelector
|
||||
[69]: https://developer.mozilla.org/docs/Web/API/Document/querySelectorAll
|
||||
[70]: https://developer.mozilla.org/docs/Web/API/DocumentFragment/querySelectorAll
|
||||
[71]: https://developer.mozilla.org/docs/Web/API/Element/querySelectorAll
|
||||
15
node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache/LICENSE
generated
vendored
Normal file
15
node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
The ISC License
|
||||
|
||||
Copyright (c) 2010-2023 Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
338
node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache/README.md
generated
vendored
Normal file
338
node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache/README.md
generated
vendored
Normal file
@@ -0,0 +1,338 @@
|
||||
# lru-cache
|
||||
|
||||
A cache object that deletes the least-recently-used items.
|
||||
|
||||
Specify a max number of the most recently used items that you
|
||||
want to keep, and this cache will keep that many of the most
|
||||
recently accessed items.
|
||||
|
||||
This is not primarily a TTL cache, and does not make strong TTL
|
||||
guarantees. There is no preemptive pruning of expired items by
|
||||
default, but you _may_ set a TTL on the cache or on a single
|
||||
`set`. If you do so, it will treat expired items as missing, and
|
||||
delete them when fetched. If you are more interested in TTL
|
||||
caching than LRU caching, check out
|
||||
[@isaacs/ttlcache](http://npm.im/@isaacs/ttlcache).
|
||||
|
||||
As of version 7, this is one of the most performant LRU
|
||||
implementations available in JavaScript, and supports a wide
|
||||
diversity of use cases. However, note that using some of the
|
||||
features will necessarily impact performance, by causing the
|
||||
cache to have to do more work. See the "Performance" section
|
||||
below.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install lru-cache --save
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
// hybrid module, either works
|
||||
import { LRUCache } from 'lru-cache'
|
||||
// or:
|
||||
const { LRUCache } = require('lru-cache')
|
||||
// or in minified form for web browsers:
|
||||
import { LRUCache } from 'http://unpkg.com/lru-cache@9/dist/mjs/index.min.mjs'
|
||||
|
||||
// At least one of 'max', 'ttl', or 'maxSize' is required, to prevent
|
||||
// unsafe unbounded storage.
|
||||
//
|
||||
// In most cases, it's best to specify a max for performance, so all
|
||||
// the required memory allocation is done up-front.
|
||||
//
|
||||
// All the other options are optional, see the sections below for
|
||||
// documentation on what each one does. Most of them can be
|
||||
// overridden for specific items in get()/set()
|
||||
const options = {
|
||||
max: 500,
|
||||
|
||||
// for use with tracking overall storage size
|
||||
maxSize: 5000,
|
||||
sizeCalculation: (value, key) => {
|
||||
return 1
|
||||
},
|
||||
|
||||
// for use when you need to clean up something when objects
|
||||
// are evicted from the cache
|
||||
dispose: (value, key, reason) => {
|
||||
freeFromMemoryOrWhatever(value)
|
||||
},
|
||||
|
||||
// for use when you need to know that an item is being inserted
|
||||
// note that this does NOT allow you to prevent the insertion,
|
||||
// it just allows you to know about it.
|
||||
onInsert: (value, key, reason) => {
|
||||
logInsertionOrWhatever(key, value)
|
||||
},
|
||||
|
||||
// how long to live in ms
|
||||
ttl: 1000 * 60 * 5,
|
||||
|
||||
// return stale items before removing from cache?
|
||||
allowStale: false,
|
||||
|
||||
updateAgeOnGet: false,
|
||||
updateAgeOnHas: false,
|
||||
|
||||
// async method to use for cache.fetch(), for
|
||||
// stale-while-revalidate type of behavior
|
||||
fetchMethod: async (
|
||||
key,
|
||||
staleValue,
|
||||
{ options, signal, context },
|
||||
) => {},
|
||||
}
|
||||
|
||||
const cache = new LRUCache(options)
|
||||
|
||||
cache.set('key', 'value')
|
||||
cache.get('key') // "value"
|
||||
|
||||
// non-string keys ARE fully supported
|
||||
// but note that it must be THE SAME object, not
|
||||
// just a JSON-equivalent object.
|
||||
var someObject = { a: 1 }
|
||||
cache.set(someObject, 'a value')
|
||||
// Object keys are not toString()-ed
|
||||
cache.set('[object Object]', 'a different value')
|
||||
assert.equal(cache.get(someObject), 'a value')
|
||||
// A similar object with same keys/values won't work,
|
||||
// because it's a different object identity
|
||||
assert.equal(cache.get({ a: 1 }), undefined)
|
||||
|
||||
cache.clear() // empty the cache
|
||||
```
|
||||
|
||||
If you put more stuff in the cache, then less recently used items
|
||||
will fall out. That's what an LRU cache is.
|
||||
|
||||
For full description of the API and all options, please see [the
|
||||
LRUCache typedocs](https://isaacs.github.io/node-lru-cache/)
|
||||
|
||||
## Storage Bounds Safety
|
||||
|
||||
This implementation aims to be as flexible as possible, within
|
||||
the limits of safe memory consumption and optimal performance.
|
||||
|
||||
At initial object creation, storage is allocated for `max` items.
|
||||
If `max` is set to zero, then some performance is lost, and item
|
||||
count is unbounded. Either `maxSize` or `ttl` _must_ be set if
|
||||
`max` is not specified.
|
||||
|
||||
If `maxSize` is set, then this creates a safe limit on the
|
||||
maximum storage consumed, but without the performance benefits of
|
||||
pre-allocation. When `maxSize` is set, every item _must_ provide
|
||||
a size, either via the `sizeCalculation` method provided to the
|
||||
constructor, or via a `size` or `sizeCalculation` option provided
|
||||
to `cache.set()`. The size of every item _must_ be a positive
|
||||
integer.
|
||||
|
||||
If neither `max` nor `maxSize` are set, then `ttl` tracking must
|
||||
be enabled. Note that, even when tracking item `ttl`, items are
|
||||
_not_ preemptively deleted when they become stale, unless
|
||||
`ttlAutopurge` is enabled. Instead, they are only purged the
|
||||
next time the key is requested. Thus, if `ttlAutopurge`, `max`,
|
||||
and `maxSize` are all not set, then the cache will potentially
|
||||
grow unbounded.
|
||||
|
||||
In this case, a warning is printed to standard error. Future
|
||||
versions may require the use of `ttlAutopurge` if `max` and
|
||||
`maxSize` are not specified.
|
||||
|
||||
If you truly wish to use a cache that is bound _only_ by TTL
|
||||
expiration, consider using a `Map` object, and calling
|
||||
`setTimeout` to delete entries when they expire. It will perform
|
||||
much better than an LRU cache.
|
||||
|
||||
Here is an implementation you may use, under the same
|
||||
[license](./LICENSE) as this package:
|
||||
|
||||
```js
|
||||
// a storage-unbounded ttl cache that is not an lru-cache
|
||||
const cache = {
|
||||
data: new Map(),
|
||||
timers: new Map(),
|
||||
set: (k, v, ttl) => {
|
||||
if (cache.timers.has(k)) {
|
||||
clearTimeout(cache.timers.get(k))
|
||||
}
|
||||
cache.timers.set(
|
||||
k,
|
||||
setTimeout(() => cache.delete(k), ttl),
|
||||
)
|
||||
cache.data.set(k, v)
|
||||
},
|
||||
get: k => cache.data.get(k),
|
||||
has: k => cache.data.has(k),
|
||||
delete: k => {
|
||||
if (cache.timers.has(k)) {
|
||||
clearTimeout(cache.timers.get(k))
|
||||
}
|
||||
cache.timers.delete(k)
|
||||
return cache.data.delete(k)
|
||||
},
|
||||
clear: () => {
|
||||
cache.data.clear()
|
||||
for (const v of cache.timers.values()) {
|
||||
clearTimeout(v)
|
||||
}
|
||||
cache.timers.clear()
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If that isn't to your liking, check out
|
||||
[@isaacs/ttlcache](http://npm.im/@isaacs/ttlcache).
|
||||
|
||||
## Storing Undefined Values
|
||||
|
||||
This cache never stores undefined values, as `undefined` is used
|
||||
internally in a few places to indicate that a key is not in the
|
||||
cache.
|
||||
|
||||
You may call `cache.set(key, undefined)`, but this is just
|
||||
an alias for `cache.delete(key)`. Note that this has the effect
|
||||
that `cache.has(key)` will return _false_ after setting it to
|
||||
undefined.
|
||||
|
||||
```js
|
||||
cache.set(myKey, undefined)
|
||||
cache.has(myKey) // false!
|
||||
```
|
||||
|
||||
If you need to track `undefined` values, and still note that the
|
||||
key is in the cache, an easy workaround is to use a sigil object
|
||||
of your own.
|
||||
|
||||
```js
|
||||
import { LRUCache } from 'lru-cache'
|
||||
const undefinedValue = Symbol('undefined')
|
||||
const cache = new LRUCache(...)
|
||||
const mySet = (key, value) =>
|
||||
cache.set(key, value === undefined ? undefinedValue : value)
|
||||
const myGet = (key, value) => {
|
||||
const v = cache.get(key)
|
||||
return v === undefinedValue ? undefined : v
|
||||
}
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
As of January 2022, version 7 of this library is one of the most
|
||||
performant LRU cache implementations in JavaScript.
|
||||
|
||||
Benchmarks can be extremely difficult to get right. In
|
||||
particular, the performance of set/get/delete operations on
|
||||
objects will vary _wildly_ depending on the type of key used. V8
|
||||
is highly optimized for objects with keys that are short strings,
|
||||
especially integer numeric strings. Thus any benchmark which
|
||||
tests _solely_ using numbers as keys will tend to find that an
|
||||
object-based approach performs the best.
|
||||
|
||||
Note that coercing _anything_ to strings to use as object keys is
|
||||
unsafe, unless you can be 100% certain that no other type of
|
||||
value will be used. For example:
|
||||
|
||||
```js
|
||||
const myCache = {}
|
||||
const set = (k, v) => (myCache[k] = v)
|
||||
const get = k => myCache[k]
|
||||
|
||||
set({}, 'please hang onto this for me')
|
||||
set('[object Object]', 'oopsie')
|
||||
```
|
||||
|
||||
Also beware of "Just So" stories regarding performance. Garbage
|
||||
collection of large (especially: deep) object graphs can be
|
||||
incredibly costly, with several "tipping points" where it
|
||||
increases exponentially. As a result, putting that off until
|
||||
later can make it much worse, and less predictable. If a library
|
||||
performs well, but only in a scenario where the object graph is
|
||||
kept shallow, then that won't help you if you are using large
|
||||
objects as keys.
|
||||
|
||||
In general, when attempting to use a library to improve
|
||||
performance (such as a cache like this one), it's best to choose
|
||||
an option that will perform well in the sorts of scenarios where
|
||||
you'll actually use it.
|
||||
|
||||
This library is optimized for repeated gets and minimizing
|
||||
eviction time, since that is the expected need of a LRU. Set
|
||||
operations are somewhat slower on average than a few other
|
||||
options, in part because of that optimization. It is assumed
|
||||
that you'll be caching some costly operation, ideally as rarely
|
||||
as possible, so optimizing set over get would be unwise.
|
||||
|
||||
If performance matters to you:
|
||||
|
||||
1. If it's at all possible to use small integer values as keys,
|
||||
and you can guarantee that no other types of values will be
|
||||
used as keys, then do that, and use a cache such as
|
||||
[lru-fast](https://npmjs.com/package/lru-fast), or
|
||||
[mnemonist's
|
||||
LRUCache](https://yomguithereal.github.io/mnemonist/lru-cache)
|
||||
which uses an Object as its data store.
|
||||
|
||||
2. Failing that, if at all possible, use short non-numeric
|
||||
strings (ie, less than 256 characters) as your keys, and use
|
||||
[mnemonist's
|
||||
LRUCache](https://yomguithereal.github.io/mnemonist/lru-cache).
|
||||
|
||||
3. If the types of your keys will be anything else, especially
|
||||
long strings, strings that look like floats, objects, or some
|
||||
mix of types, or if you aren't sure, then this library will
|
||||
work well for you.
|
||||
|
||||
If you do not need the features that this library provides
|
||||
(like asynchronous fetching, a variety of TTL staleness
|
||||
options, and so on), then [mnemonist's
|
||||
LRUMap](https://yomguithereal.github.io/mnemonist/lru-map) is
|
||||
a very good option, and just slightly faster than this module
|
||||
(since it does considerably less).
|
||||
|
||||
4. Do not use a `dispose` function, size tracking, or especially
|
||||
ttl behavior, unless absolutely needed. These features are
|
||||
convenient, and necessary in some use cases, and every attempt
|
||||
has been made to make the performance impact minimal, but it
|
||||
isn't nothing.
|
||||
|
||||
## Breaking Changes in Version 7
|
||||
|
||||
This library changed to a different algorithm and internal data
|
||||
structure in version 7, yielding significantly better
|
||||
performance, albeit with some subtle changes as a result.
|
||||
|
||||
If you were relying on the internals of LRUCache in version 6 or
|
||||
before, it probably will not work in version 7 and above.
|
||||
|
||||
## Breaking Changes in Version 8
|
||||
|
||||
- The `fetchContext` option was renamed to `context`, and may no
|
||||
longer be set on the cache instance itself.
|
||||
- Rewritten in TypeScript, so pretty much all the types moved
|
||||
around a lot.
|
||||
- The AbortController/AbortSignal polyfill was removed. For this
|
||||
reason, **Node version 16.14.0 or higher is now required**.
|
||||
- Internal properties were moved to actual private class
|
||||
properties.
|
||||
- Keys and values must not be `null` or `undefined`.
|
||||
- Minified export available at `'lru-cache/min'`, for both CJS
|
||||
and MJS builds.
|
||||
|
||||
## Breaking Changes in Version 9
|
||||
|
||||
- Named export only, no default export.
|
||||
- AbortController polyfill returned, albeit with a warning when
|
||||
used.
|
||||
|
||||
## Breaking Changes in Version 10
|
||||
|
||||
- `cache.fetch()` return type is now `Promise<V | undefined>`
|
||||
instead of `Promise<V | void>`. This is an irrelevant change
|
||||
practically speaking, but can require changes for TypeScript
|
||||
users.
|
||||
|
||||
For more info, see the [change log](CHANGELOG.md).
|
||||
113
node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache/package.json
generated
vendored
Normal file
113
node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache/package.json
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"name": "lru-cache",
|
||||
"description": "A cache object that deletes the least-recently-used items.",
|
||||
"version": "11.2.2",
|
||||
"author": "Isaac Z. Schlueter <i@izs.me>",
|
||||
"keywords": [
|
||||
"mru",
|
||||
"lru",
|
||||
"cache"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "npm run prepare",
|
||||
"prepare": "tshy && bash fixup.sh",
|
||||
"pretest": "npm run prepare",
|
||||
"presnap": "npm run prepare",
|
||||
"test": "tap",
|
||||
"snap": "tap",
|
||||
"preversion": "npm test",
|
||||
"postversion": "npm publish",
|
||||
"prepublishOnly": "git push origin --follow-tags",
|
||||
"format": "prettier --write .",
|
||||
"typedoc": "typedoc --tsconfig ./.tshy/esm.json ./src/*.ts",
|
||||
"benchmark-results-typedoc": "bash scripts/benchmark-results-typedoc.sh",
|
||||
"prebenchmark": "npm run prepare",
|
||||
"benchmark": "make -C benchmark",
|
||||
"preprofile": "npm run prepare",
|
||||
"profile": "make -C benchmark profile"
|
||||
},
|
||||
"main": "./dist/commonjs/index.js",
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"tshy": {
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./min": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.min.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"default": "./dist/commonjs/index.min.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/isaacs/node-lru-cache.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.3.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"esbuild": "^0.25.9",
|
||||
"marked": "^4.2.12",
|
||||
"mkdirp": "^3.0.1",
|
||||
"prettier": "^3.6.2",
|
||||
"tap": "^21.1.0",
|
||||
"tshy": "^3.0.2",
|
||||
"typedoc": "^0.28.12"
|
||||
},
|
||||
"license": "ISC",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"prettier": {
|
||||
"experimentalTernaries": true,
|
||||
"semi": false,
|
||||
"printWidth": 70,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": false,
|
||||
"bracketSameLine": true,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "lf"
|
||||
},
|
||||
"tap": {
|
||||
"node-arg": [
|
||||
"--expose-gc"
|
||||
],
|
||||
"plugin": [
|
||||
"@tapjs/clock"
|
||||
]
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"default": "./dist/commonjs/index.js"
|
||||
}
|
||||
},
|
||||
"./min": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.min.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"default": "./dist/commonjs/index.min.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "module",
|
||||
"module": "./dist/esm/index.js"
|
||||
}
|
||||
83
node_modules/@asamuzakjp/dom-selector/package.json
generated
vendored
Normal file
83
node_modules/@asamuzakjp/dom-selector/package.json
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"name": "@asamuzakjp/dom-selector",
|
||||
"description": "A CSS selector engine.",
|
||||
"author": "asamuzaK",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/asamuzaK/domSelector#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/asamuzaK/domSelector/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/asamuzaK/domSelector.git"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"types"
|
||||
],
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"import": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./src/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/cjs/index.d.cts",
|
||||
"default": "./dist/cjs/index.cjs"
|
||||
},
|
||||
"default": {
|
||||
"types": "./dist/cjs/types/index.d.cts",
|
||||
"default": "./dist/cjs/index.cjs"
|
||||
}
|
||||
},
|
||||
"types": "types/index.d.ts",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/nwsapi": "^2.3.9",
|
||||
"bidi-js": "^1.0.3",
|
||||
"css-tree": "^3.1.0",
|
||||
"is-potential-custom-element-name": "^1.0.1",
|
||||
"lru-cache": "^11.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/css-tree": "^2.3.11",
|
||||
"benchmark": "^2.1.4",
|
||||
"c8": "^10.1.3",
|
||||
"chai": "^6.2.0",
|
||||
"commander": "^14.0.2",
|
||||
"esbuild": "^0.25.11",
|
||||
"eslint": "^9.38.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-jsdoc": "^61.1.11",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-regexp": "^2.10.0",
|
||||
"eslint-plugin-unicorn": "^62.0.0",
|
||||
"globals": "^16.4.0",
|
||||
"happy-dom": "^20.0.10",
|
||||
"jsdom": "^27.1.0",
|
||||
"linkedom": "^0.18.12",
|
||||
"mocha": "^11.7.4",
|
||||
"neostandard": "^0.12.2",
|
||||
"prettier": "^3.6.2",
|
||||
"sinon": "^21.0.0",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.9.3",
|
||||
"wpt-runner": "^6.1.0"
|
||||
},
|
||||
"overrides": {
|
||||
"jsdom": "$jsdom"
|
||||
},
|
||||
"scripts": {
|
||||
"bench": "node benchmark/bench.js",
|
||||
"bench:sizzle": "node benchmark/bench-sizzle.js",
|
||||
"build": "npm run tsc && npm run lint && npm test && npm run bundle && npm run test:cjs",
|
||||
"bundle": "tsup src/index.js --format=cjs --platform=node --outDir=dist/cjs/ --sourcemap --dts",
|
||||
"lint": "eslint --fix .",
|
||||
"test": "c8 --reporter=text mocha --parallel --exit test/**/*.test.js",
|
||||
"test:cjs": "mocha --exit test/index.test.cjs",
|
||||
"test:wpt": "node test/wpt/wpt-runner.js",
|
||||
"tsc": "node scripts/index clean --dir=types -i && npx tsc",
|
||||
"update:wpt": "git submodule update --init --recursive --remote"
|
||||
},
|
||||
"version": "6.7.4"
|
||||
}
|
||||
353
node_modules/@asamuzakjp/dom-selector/src/index.js
generated
vendored
Normal file
353
node_modules/@asamuzakjp/dom-selector/src/index.js
generated
vendored
Normal file
@@ -0,0 +1,353 @@
|
||||
/*!
|
||||
* DOM Selector - A CSS selector engine.
|
||||
* @license MIT
|
||||
* @copyright asamuzaK (Kazz)
|
||||
* @see {@link https://github.com/asamuzaK/domSelector/blob/main/LICENSE}
|
||||
*/
|
||||
|
||||
/* import */
|
||||
import { LRUCache } from 'lru-cache';
|
||||
import { Finder } from './js/finder.js';
|
||||
import { filterSelector, getType, initNwsapi } from './js/utility.js';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
DOCUMENT_NODE,
|
||||
DOCUMENT_FRAGMENT_NODE,
|
||||
ELEMENT_NODE,
|
||||
TARGET_ALL,
|
||||
TARGET_FIRST,
|
||||
TARGET_LINEAL,
|
||||
TARGET_SELF
|
||||
} from './js/constant.js';
|
||||
const MAX_CACHE = 1024;
|
||||
|
||||
/**
|
||||
* @typedef {object} CheckResult
|
||||
* @property {boolean} match - The match result.
|
||||
* @property {string?} pseudoElement - The pseudo-element, if any.
|
||||
*/
|
||||
|
||||
/* DOMSelector */
|
||||
export class DOMSelector {
|
||||
/* private fields */
|
||||
#window;
|
||||
#document;
|
||||
#finder;
|
||||
#idlUtils;
|
||||
#nwsapi;
|
||||
#cache;
|
||||
|
||||
/**
|
||||
* Creates an instance of DOMSelector.
|
||||
* @param {Window} window - The window object.
|
||||
* @param {Document} document - The document object.
|
||||
* @param {object} [opt] - Options.
|
||||
*/
|
||||
constructor(window, document, opt = {}) {
|
||||
const { idlUtils } = opt;
|
||||
this.#window = window;
|
||||
this.#document = document ?? window.document;
|
||||
this.#finder = new Finder(window);
|
||||
this.#idlUtils = idlUtils;
|
||||
this.#nwsapi = initNwsapi(window, document);
|
||||
this.#cache = new LRUCache({
|
||||
max: MAX_CACHE
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the internal cache of finder results.
|
||||
* @returns {void}
|
||||
*/
|
||||
clear = () => {
|
||||
this.#finder.clearResults(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if an element matches a CSS selector.
|
||||
* @param {string} selector - The CSS selector to check against.
|
||||
* @param {Element} node - The element node to check.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @returns {CheckResult} An object containing the check result.
|
||||
*/
|
||||
check = (selector, node, opt = {}) => {
|
||||
if (!node?.nodeType) {
|
||||
const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
} else if (node.nodeType !== ELEMENT_NODE) {
|
||||
const e = new this.#window.TypeError(`Unexpected node ${node.nodeName}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
}
|
||||
const document = node.ownerDocument;
|
||||
if (
|
||||
document === this.#document &&
|
||||
document.contentType === 'text/html' &&
|
||||
document.documentElement &&
|
||||
node.parentNode
|
||||
) {
|
||||
const cacheKey = `check_${selector}`;
|
||||
let filterMatches = false;
|
||||
if (this.#cache.has(cacheKey)) {
|
||||
filterMatches = this.#cache.get(cacheKey);
|
||||
} else {
|
||||
filterMatches = filterSelector(selector, TARGET_SELF);
|
||||
this.#cache.set(cacheKey, filterMatches);
|
||||
}
|
||||
if (filterMatches) {
|
||||
try {
|
||||
const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
|
||||
const match = this.#nwsapi.match(selector, n);
|
||||
return {
|
||||
match,
|
||||
pseudoElement: null
|
||||
};
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
if (this.#idlUtils) {
|
||||
node = this.#idlUtils.wrapperForImpl(node);
|
||||
}
|
||||
opt.check = true;
|
||||
opt.noexept = true;
|
||||
opt.warn = false;
|
||||
this.#finder.setup(selector, node, opt);
|
||||
res = this.#finder.find(TARGET_SELF);
|
||||
} catch (e) {
|
||||
this.#finder.onError(e, opt);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the element matches the selector.
|
||||
* @param {string} selector - The CSS selector to match against.
|
||||
* @param {Element} node - The element node to test.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @returns {boolean} `true` if the element matches, or `false` otherwise.
|
||||
*/
|
||||
matches = (selector, node, opt = {}) => {
|
||||
if (!node?.nodeType) {
|
||||
const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
} else if (node.nodeType !== ELEMENT_NODE) {
|
||||
const e = new this.#window.TypeError(`Unexpected node ${node.nodeName}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
}
|
||||
const document = node.ownerDocument;
|
||||
if (
|
||||
document === this.#document &&
|
||||
document.contentType === 'text/html' &&
|
||||
document.documentElement &&
|
||||
node.parentNode
|
||||
) {
|
||||
const cacheKey = `matches_${selector}`;
|
||||
let filterMatches = false;
|
||||
if (this.#cache.has(cacheKey)) {
|
||||
filterMatches = this.#cache.get(cacheKey);
|
||||
} else {
|
||||
filterMatches = filterSelector(selector, TARGET_SELF);
|
||||
this.#cache.set(cacheKey, filterMatches);
|
||||
}
|
||||
if (filterMatches) {
|
||||
try {
|
||||
const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
|
||||
const res = this.#nwsapi.match(selector, n);
|
||||
return res;
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
if (this.#idlUtils) {
|
||||
node = this.#idlUtils.wrapperForImpl(node);
|
||||
}
|
||||
this.#finder.setup(selector, node, opt);
|
||||
const nodes = this.#finder.find(TARGET_SELF);
|
||||
res = nodes.size;
|
||||
} catch (e) {
|
||||
this.#finder.onError(e, opt);
|
||||
}
|
||||
return !!res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Traverses up the DOM tree to find the first node that matches the selector.
|
||||
* @param {string} selector - The CSS selector to match against.
|
||||
* @param {Element} node - The element from which to start traversing.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @returns {?Element} The first matching ancestor element, or `null`.
|
||||
*/
|
||||
closest = (selector, node, opt = {}) => {
|
||||
if (!node?.nodeType) {
|
||||
const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
} else if (node.nodeType !== ELEMENT_NODE) {
|
||||
const e = new this.#window.TypeError(`Unexpected node ${node.nodeName}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
}
|
||||
const document = node.ownerDocument;
|
||||
if (
|
||||
document === this.#document &&
|
||||
document.contentType === 'text/html' &&
|
||||
document.documentElement &&
|
||||
node.parentNode
|
||||
) {
|
||||
const cacheKey = `closest_${selector}`;
|
||||
let filterMatches = false;
|
||||
if (this.#cache.has(cacheKey)) {
|
||||
filterMatches = this.#cache.get(cacheKey);
|
||||
} else {
|
||||
filterMatches = filterSelector(selector, TARGET_LINEAL);
|
||||
this.#cache.set(cacheKey, filterMatches);
|
||||
}
|
||||
if (filterMatches) {
|
||||
try {
|
||||
const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
|
||||
const res = this.#nwsapi.closest(selector, n);
|
||||
return res;
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
if (this.#idlUtils) {
|
||||
node = this.#idlUtils.wrapperForImpl(node);
|
||||
}
|
||||
this.#finder.setup(selector, node, opt);
|
||||
const nodes = this.#finder.find(TARGET_LINEAL);
|
||||
if (nodes.size) {
|
||||
let refNode = node;
|
||||
while (refNode) {
|
||||
if (nodes.has(refNode)) {
|
||||
res = refNode;
|
||||
break;
|
||||
}
|
||||
refNode = refNode.parentNode;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.#finder.onError(e, opt);
|
||||
}
|
||||
return res ?? null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the first element within the subtree that matches the selector.
|
||||
* @param {string} selector - The CSS selector to match.
|
||||
* @param {Document|DocumentFragment|Element} node - The node to find within.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @returns {?Element} The first matching element, or `null`.
|
||||
*/
|
||||
querySelector = (selector, node, opt = {}) => {
|
||||
if (!node?.nodeType) {
|
||||
const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
}
|
||||
/*
|
||||
const document =
|
||||
node.nodeType === DOCUMENT_NODE ? node : node.ownerDocument;
|
||||
if (
|
||||
document === this.#document &&
|
||||
document.contentType === 'text/html' &&
|
||||
document.documentElement &&
|
||||
(node.nodeType !== DOCUMENT_FRAGMENT_NODE || !node.host)
|
||||
) {
|
||||
const cacheKey = `querySelector_${selector}`;
|
||||
let filterMatches = false;
|
||||
if (this.#cache.has(cacheKey)) {
|
||||
filterMatches = this.#cache.get(cacheKey);
|
||||
} else {
|
||||
filterMatches = filterSelector(selector, TARGET_FIRST);
|
||||
this.#cache.set(cacheKey, filterMatches);
|
||||
}
|
||||
if (filterMatches) {
|
||||
try {
|
||||
const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
|
||||
const res = this.#nwsapi.first(selector, n);
|
||||
return res;
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
let res;
|
||||
try {
|
||||
if (this.#idlUtils) {
|
||||
node = this.#idlUtils.wrapperForImpl(node);
|
||||
}
|
||||
this.#finder.setup(selector, node, opt);
|
||||
const nodes = this.#finder.find(TARGET_FIRST);
|
||||
if (nodes.size) {
|
||||
[res] = [...nodes];
|
||||
}
|
||||
} catch (e) {
|
||||
this.#finder.onError(e, opt);
|
||||
}
|
||||
return res ?? null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of elements within the subtree that match the selector.
|
||||
* Note: This method returns an Array, not a NodeList.
|
||||
* @param {string} selector - The CSS selector to match.
|
||||
* @param {Document|DocumentFragment|Element} node - The node to find within.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @returns {Array<Element>} An array of elements, or an empty array.
|
||||
*/
|
||||
querySelectorAll = (selector, node, opt = {}) => {
|
||||
if (!node?.nodeType) {
|
||||
const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
}
|
||||
const document =
|
||||
node.nodeType === DOCUMENT_NODE ? node : node.ownerDocument;
|
||||
if (
|
||||
document === this.#document &&
|
||||
document.contentType === 'text/html' &&
|
||||
document.documentElement &&
|
||||
(node.nodeType !== DOCUMENT_FRAGMENT_NODE || !node.host)
|
||||
) {
|
||||
const cacheKey = `querySelectorAll_${selector}`;
|
||||
let filterMatches = false;
|
||||
if (this.#cache.has(cacheKey)) {
|
||||
filterMatches = this.#cache.get(cacheKey);
|
||||
} else {
|
||||
filterMatches = filterSelector(selector, TARGET_ALL);
|
||||
this.#cache.set(cacheKey, filterMatches);
|
||||
}
|
||||
if (filterMatches) {
|
||||
try {
|
||||
const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
|
||||
const res = this.#nwsapi.select(selector, n);
|
||||
return res;
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
if (this.#idlUtils) {
|
||||
node = this.#idlUtils.wrapperForImpl(node);
|
||||
}
|
||||
this.#finder.setup(selector, node, opt);
|
||||
const nodes = this.#finder.find(TARGET_ALL);
|
||||
if (nodes.size) {
|
||||
res = [...nodes];
|
||||
}
|
||||
} catch (e) {
|
||||
this.#finder.onError(e, opt);
|
||||
}
|
||||
return res ?? [];
|
||||
};
|
||||
}
|
||||
129
node_modules/@asamuzakjp/dom-selector/src/js/constant.js
generated
vendored
Normal file
129
node_modules/@asamuzakjp/dom-selector/src/js/constant.js
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* constant.js
|
||||
*/
|
||||
|
||||
/* string */
|
||||
export const ATRULE = 'Atrule';
|
||||
export const ATTR_SELECTOR = 'AttributeSelector';
|
||||
export const CLASS_SELECTOR = 'ClassSelector';
|
||||
export const COMBINATOR = 'Combinator';
|
||||
export const IDENT = 'Identifier';
|
||||
export const ID_SELECTOR = 'IdSelector';
|
||||
export const NOT_SUPPORTED_ERR = 'NotSupportedError';
|
||||
export const NTH = 'Nth';
|
||||
export const OPERATOR = 'Operator';
|
||||
export const PS_CLASS_SELECTOR = 'PseudoClassSelector';
|
||||
export const PS_ELEMENT_SELECTOR = 'PseudoElementSelector';
|
||||
export const RULE = 'Rule';
|
||||
export const SCOPE = 'Scope';
|
||||
export const SELECTOR = 'Selector';
|
||||
export const SELECTOR_LIST = 'SelectorList';
|
||||
export const STRING = 'String';
|
||||
export const SYNTAX_ERR = 'SyntaxError';
|
||||
export const TARGET_ALL = 'all';
|
||||
export const TARGET_FIRST = 'first';
|
||||
export const TARGET_LINEAL = 'lineal';
|
||||
export const TARGET_SELF = 'self';
|
||||
export const TYPE_SELECTOR = 'TypeSelector';
|
||||
|
||||
/* numeric */
|
||||
export const BIT_01 = 1;
|
||||
export const BIT_02 = 2;
|
||||
export const BIT_04 = 4;
|
||||
export const BIT_08 = 8;
|
||||
export const BIT_16 = 0x10;
|
||||
export const BIT_32 = 0x20;
|
||||
export const BIT_FFFF = 0xffff;
|
||||
export const DUO = 2;
|
||||
export const HEX = 16;
|
||||
export const TYPE_FROM = 8;
|
||||
export const TYPE_TO = -1;
|
||||
|
||||
/* Node */
|
||||
export const ELEMENT_NODE = 1;
|
||||
export const TEXT_NODE = 3;
|
||||
export const DOCUMENT_NODE = 9;
|
||||
export const DOCUMENT_FRAGMENT_NODE = 11;
|
||||
export const DOCUMENT_POSITION_PRECEDING = 2;
|
||||
export const DOCUMENT_POSITION_CONTAINS = 8;
|
||||
export const DOCUMENT_POSITION_CONTAINED_BY = 0x10;
|
||||
|
||||
/* NodeFilter */
|
||||
export const SHOW_ALL = 0xffffffff;
|
||||
export const SHOW_CONTAINER = 0x501;
|
||||
export const SHOW_DOCUMENT = 0x100;
|
||||
export const SHOW_DOCUMENT_FRAGMENT = 0x400;
|
||||
export const SHOW_ELEMENT = 1;
|
||||
|
||||
/* selectors */
|
||||
export const ALPHA_NUM = '[A-Z\\d]+';
|
||||
export const CHILD_IDX = '(?:first|last|only)-(?:child|of-type)';
|
||||
export const DIGIT = '(?:0|[1-9]\\d*)';
|
||||
export const LANG_PART = `(?:-${ALPHA_NUM})*`;
|
||||
export const PSEUDO_CLASS = `(?:any-)?link|${CHILD_IDX}|checked|empty|indeterminate|read-(?:only|write)|target`;
|
||||
export const ANB = `[+-]?(?:${DIGIT}n?|n)|(?:[+-]?${DIGIT})?n\\s*[+-]\\s*${DIGIT}`;
|
||||
// N_TH: excludes An+B with selector list, e.g. :nth-child(2n+1 of .foo)
|
||||
export const N_TH = `nth-(?:last-)?(?:child|of-type)\\(\\s*(?:even|odd|${ANB})\\s*\\)`;
|
||||
// SUB_TYPE: attr, id, class, pseudo-class, note that [foo|=bar] is excluded
|
||||
export const SUB_TYPE = '\\[[^|\\]]+\\]|[#.:][\\w-]+';
|
||||
export const SUB_TYPE_WO_PSEUDO = '\\[[^|\\]]+\\]|[#.][\\w-]+';
|
||||
// TAG_TYPE: *, tag
|
||||
export const TAG_TYPE = '\\*|[A-Za-z][\\w-]*';
|
||||
export const TAG_TYPE_I = '\\*|[A-Z][\\w-]*';
|
||||
export const COMPOUND = `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE})+)`;
|
||||
export const COMPOUND_WO_PSEUDO = `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE_WO_PSEUDO})+)`;
|
||||
export const COMBO = '\\s?[\\s>~+]\\s?';
|
||||
export const COMPLEX = `${COMPOUND}(?:${COMBO}${COMPOUND})*`;
|
||||
export const DESCEND = '\\s?[\\s>]\\s?';
|
||||
export const SIBLING = '\\s?[+~]\\s?';
|
||||
export const NESTED_LOGIC_A = `:is\\(\\s*${COMPOUND}(?:\\s*,\\s*${COMPOUND})*\\s*\\)`;
|
||||
export const NESTED_LOGIC_B = `:is\\(\\s*${COMPLEX}(?:\\s*,\\s*${COMPLEX})*\\s*\\)`;
|
||||
export const COMPOUND_A = `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE}|${NESTED_LOGIC_A})+)`;
|
||||
export const COMPOUND_B = `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE}|${NESTED_LOGIC_B})+)`;
|
||||
export const COMPOUND_I = `(?:${TAG_TYPE_I}|(?:${TAG_TYPE_I})?(?:${SUB_TYPE})+)`;
|
||||
export const COMPLEX_L = `${COMPOUND_B}(?:${COMBO}${COMPOUND_B})*`;
|
||||
export const LOGIC_COMPLEX = `(?:is|not)\\(\\s*${COMPLEX_L}(?:\\s*,\\s*${COMPLEX_L})*\\s*\\)`;
|
||||
export const LOGIC_COMPOUND = `(?:is|not)\\(\\s*${COMPOUND_A}(?:\\s*,\\s*${COMPOUND_A})*\\s*\\)`;
|
||||
export const HAS_COMPOUND = `has\\([\\s>]?\\s*${COMPOUND_WO_PSEUDO}\\s*\\)`;
|
||||
|
||||
/* forms and input types */
|
||||
export const FORM_PARTS = Object.freeze([
|
||||
'button',
|
||||
'input',
|
||||
'select',
|
||||
'textarea'
|
||||
]);
|
||||
export const INPUT_BUTTON = Object.freeze(['button', 'reset', 'submit']);
|
||||
export const INPUT_CHECK = Object.freeze(['checkbox', 'radio']);
|
||||
export const INPUT_DATE = Object.freeze([
|
||||
'date',
|
||||
'datetime-local',
|
||||
'month',
|
||||
'time',
|
||||
'week'
|
||||
]);
|
||||
export const INPUT_TEXT = Object.freeze([
|
||||
'email',
|
||||
'password',
|
||||
'search',
|
||||
'tel',
|
||||
'text',
|
||||
'url'
|
||||
]);
|
||||
export const INPUT_EDIT = Object.freeze([
|
||||
...INPUT_DATE,
|
||||
...INPUT_TEXT,
|
||||
'number'
|
||||
]);
|
||||
export const INPUT_LTR = Object.freeze([
|
||||
...INPUT_CHECK,
|
||||
'color',
|
||||
'date',
|
||||
'image',
|
||||
'number',
|
||||
'range',
|
||||
'time'
|
||||
]);
|
||||
|
||||
/* logical combination pseudo-classes */
|
||||
export const KEYS_LOGICAL = new Set(['has', 'is', 'not', 'where']);
|
||||
3115
node_modules/@asamuzakjp/dom-selector/src/js/finder.js
generated
vendored
Normal file
3115
node_modules/@asamuzakjp/dom-selector/src/js/finder.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
587
node_modules/@asamuzakjp/dom-selector/src/js/matcher.js
generated
vendored
Normal file
587
node_modules/@asamuzakjp/dom-selector/src/js/matcher.js
generated
vendored
Normal file
@@ -0,0 +1,587 @@
|
||||
/**
|
||||
* matcher.js
|
||||
*/
|
||||
|
||||
/* import */
|
||||
import { generateCSS, parseAstName, unescapeSelector } from './parser.js';
|
||||
import {
|
||||
generateException,
|
||||
getDirectionality,
|
||||
getLanguageAttribute,
|
||||
getType,
|
||||
isContentEditable,
|
||||
isCustomElement,
|
||||
isNamespaceDeclared
|
||||
} from './utility.js';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
ALPHA_NUM,
|
||||
FORM_PARTS,
|
||||
IDENT,
|
||||
INPUT_EDIT,
|
||||
LANG_PART,
|
||||
NOT_SUPPORTED_ERR,
|
||||
PS_ELEMENT_SELECTOR,
|
||||
STRING,
|
||||
SYNTAX_ERR
|
||||
} from './constant.js';
|
||||
const KEYS_FORM_PS_DISABLED = new Set([
|
||||
...FORM_PARTS,
|
||||
'fieldset',
|
||||
'optgroup',
|
||||
'option'
|
||||
]);
|
||||
const KEYS_INPUT_EDIT = new Set(INPUT_EDIT);
|
||||
const REG_LANG_VALID = new RegExp(`^(?:\\*-)?${ALPHA_NUM}${LANG_PART}$`, 'i');
|
||||
const REG_TAG_NAME = /[A-Z][\\w-]*/i;
|
||||
|
||||
/**
|
||||
* Validates a pseudo-element selector.
|
||||
* @param {string} astName - The name of the pseudo-element from the AST.
|
||||
* @param {string} astType - The type of the selector from the AST.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @param {boolean} [opt.forgive] - If true, ignores unknown pseudo-elements.
|
||||
* @param {boolean} [opt.warn] - If true, throws an error for unsupported ones.
|
||||
* @throws {DOMException} If the selector is invalid or unsupported.
|
||||
* @returns {void}
|
||||
*/
|
||||
export const matchPseudoElementSelector = (astName, astType, opt = {}) => {
|
||||
const { forgive, globalObject, warn } = opt;
|
||||
if (astType !== PS_ELEMENT_SELECTOR) {
|
||||
// Ensure the AST node is a pseudo-element selector.
|
||||
throw new TypeError(`Unexpected ast type ${getType(astType)}`);
|
||||
}
|
||||
switch (astName) {
|
||||
case 'after':
|
||||
case 'backdrop':
|
||||
case 'before':
|
||||
case 'cue':
|
||||
case 'cue-region':
|
||||
case 'first-letter':
|
||||
case 'first-line':
|
||||
case 'file-selector-button':
|
||||
case 'marker':
|
||||
case 'placeholder':
|
||||
case 'selection':
|
||||
case 'target-text': {
|
||||
// Warn if the pseudo-element is known but unsupported.
|
||||
if (warn) {
|
||||
throw generateException(
|
||||
`Unsupported pseudo-element ::${astName}`,
|
||||
NOT_SUPPORTED_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'part':
|
||||
case 'slotted': {
|
||||
// Warn if the functional pseudo-element is known but unsupported.
|
||||
if (warn) {
|
||||
throw generateException(
|
||||
`Unsupported pseudo-element ::${astName}()`,
|
||||
NOT_SUPPORTED_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Handle vendor-prefixed or unknown pseudo-elements.
|
||||
if (astName.startsWith('-webkit-')) {
|
||||
if (warn) {
|
||||
throw generateException(
|
||||
`Unsupported pseudo-element ::${astName}`,
|
||||
NOT_SUPPORTED_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
// Throw an error for unknown pseudo-elements if not forgiven.
|
||||
} else if (!forgive) {
|
||||
throw generateException(
|
||||
`Unknown pseudo-element ::${astName}`,
|
||||
SYNTAX_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches the :dir() pseudo-class against an element's directionality.
|
||||
* @param {object} ast - The AST object for the pseudo-class.
|
||||
* @param {object} node - The element node to match against.
|
||||
* @throws {TypeError} If the AST does not contain a valid direction value.
|
||||
* @returns {boolean} - True if the directionality matches, otherwise false.
|
||||
*/
|
||||
export const matchDirectionPseudoClass = (ast, node) => {
|
||||
const { name } = ast;
|
||||
// The :dir() pseudo-class requires a direction argument (e.g., "ltr").
|
||||
if (!name) {
|
||||
const type = name === '' ? '(empty String)' : getType(name);
|
||||
throw new TypeError(`Unexpected ast type ${type}`);
|
||||
}
|
||||
// Get the computed directionality of the element.
|
||||
const dir = getDirectionality(node);
|
||||
// Compare the expected direction with the element's actual direction.
|
||||
return name === dir;
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches the :lang() pseudo-class against an element's language.
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc4647#section-3.3.1
|
||||
* @param {object} ast - The AST object for the pseudo-class.
|
||||
* @param {object} node - The element node to match against.
|
||||
* @returns {boolean} - True if the language matches, otherwise false.
|
||||
*/
|
||||
export const matchLanguagePseudoClass = (ast, node) => {
|
||||
const { name, type, value } = ast;
|
||||
let langPattern;
|
||||
// Determine the language pattern from the AST.
|
||||
if (type === STRING && value) {
|
||||
langPattern = value;
|
||||
} else if (type === IDENT && name) {
|
||||
langPattern = unescapeSelector(name);
|
||||
}
|
||||
// If no valid language pattern is provided, it cannot match.
|
||||
if (typeof langPattern !== 'string') {
|
||||
return false;
|
||||
}
|
||||
// Get the effective language attribute for the current node.
|
||||
const elementLang = getLanguageAttribute(node);
|
||||
// If the element has no language, it cannot match a specific pattern.
|
||||
if (elementLang === null) {
|
||||
return false;
|
||||
}
|
||||
// Handle the universal selector '*' for :lang.
|
||||
if (langPattern === '*') {
|
||||
// It matches any language unless attribute is not empty.
|
||||
return elementLang !== '';
|
||||
}
|
||||
// Validate the provided language pattern structure.
|
||||
if (!REG_LANG_VALID.test(langPattern)) {
|
||||
return false;
|
||||
}
|
||||
// Build a regex for extended language range matching.
|
||||
let matcherRegex;
|
||||
if (langPattern.indexOf('-') > -1) {
|
||||
// Handle complex patterns with wildcards and sub-tags (e.g., '*-US').
|
||||
const [langMain, langSub, ...langRest] = langPattern.split('-');
|
||||
const extendedMain =
|
||||
langMain === '*' ? `${ALPHA_NUM}${LANG_PART}` : `${langMain}${LANG_PART}`;
|
||||
const extendedSub = `-${langSub}${LANG_PART}`;
|
||||
let extendedRest = '';
|
||||
// Use a standard for loop for performance as per the rules.
|
||||
for (let i = 0; i < langRest.length; i++) {
|
||||
extendedRest += `-${langRest[i]}${LANG_PART}`;
|
||||
}
|
||||
matcherRegex = new RegExp(
|
||||
`^${extendedMain}${extendedSub}${extendedRest}$`,
|
||||
'i'
|
||||
);
|
||||
} else {
|
||||
// Handle simple language patterns (e.g., 'en').
|
||||
matcherRegex = new RegExp(`^${langPattern}${LANG_PART}$`, 'i');
|
||||
}
|
||||
// Test the element's language against the constructed regex.
|
||||
return matcherRegex.test(elementLang);
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches the :disabled and :enabled pseudo-classes.
|
||||
* @param {string} astName - pseudo-class name
|
||||
* @param {object} node - Element node
|
||||
* @returns {boolean} - True if matched
|
||||
*/
|
||||
export const matchDisabledPseudoClass = (astName, node) => {
|
||||
const { localName, parentNode } = node;
|
||||
if (
|
||||
!KEYS_FORM_PS_DISABLED.has(localName) &&
|
||||
!isCustomElement(node, { formAssociated: true })
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
let isDisabled = false;
|
||||
if (node.disabled || node.hasAttribute('disabled')) {
|
||||
isDisabled = true;
|
||||
} else if (localName === 'option') {
|
||||
if (
|
||||
parentNode &&
|
||||
parentNode.localName === 'optgroup' &&
|
||||
(parentNode.disabled || parentNode.hasAttribute('disabled'))
|
||||
) {
|
||||
isDisabled = true;
|
||||
}
|
||||
} else if (localName !== 'optgroup') {
|
||||
let current = parentNode;
|
||||
while (current) {
|
||||
if (
|
||||
current.localName === 'fieldset' &&
|
||||
(current.disabled || current.hasAttribute('disabled'))
|
||||
) {
|
||||
// The first <legend> in a disabled <fieldset> is not disabled.
|
||||
let legend;
|
||||
let element = current.firstElementChild;
|
||||
while (element) {
|
||||
if (element.localName === 'legend') {
|
||||
legend = element;
|
||||
break;
|
||||
}
|
||||
element = element.nextElementSibling;
|
||||
}
|
||||
if (!legend || !legend.contains(node)) {
|
||||
isDisabled = true;
|
||||
}
|
||||
// Found the containing fieldset, stop searching up.
|
||||
break;
|
||||
}
|
||||
current = current.parentNode;
|
||||
}
|
||||
}
|
||||
if (astName === 'disabled') {
|
||||
return isDisabled;
|
||||
}
|
||||
return !isDisabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* Match the :read-only and :read-write pseudo-classes
|
||||
* @param {string} astName - pseudo-class name
|
||||
* @param {object} node - Element node
|
||||
* @returns {boolean} - True if matched
|
||||
*/
|
||||
export const matchReadOnlyPseudoClass = (astName, node) => {
|
||||
const { localName } = node;
|
||||
let isReadOnly = false;
|
||||
switch (localName) {
|
||||
case 'textarea':
|
||||
case 'input': {
|
||||
const isEditableInput = !node.type || KEYS_INPUT_EDIT.has(node.type);
|
||||
if (localName === 'textarea' || isEditableInput) {
|
||||
isReadOnly =
|
||||
node.readOnly ||
|
||||
node.hasAttribute('readonly') ||
|
||||
node.disabled ||
|
||||
node.hasAttribute('disabled');
|
||||
} else {
|
||||
// Non-editable input types are always read-only
|
||||
isReadOnly = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
isReadOnly = !isContentEditable(node);
|
||||
}
|
||||
}
|
||||
if (astName === 'read-only') {
|
||||
return isReadOnly;
|
||||
}
|
||||
return !isReadOnly;
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches an attribute selector against an element.
|
||||
* This function handles various attribute matchers like '=', '~=', '^=', etc.,
|
||||
* and considers namespaces and case sensitivity based on document type.
|
||||
* @param {object} ast - The AST for the attribute selector.
|
||||
* @param {object} node - The element node to match against.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @param {boolean} [opt.check] - True if running in an internal check.
|
||||
* @param {boolean} [opt.forgive] - True to forgive certain syntax errors.
|
||||
* @returns {boolean} - True if the attribute selector matches, otherwise false.
|
||||
*/
|
||||
export const matchAttributeSelector = (ast, node, opt = {}) => {
|
||||
const {
|
||||
flags: astFlags,
|
||||
matcher: astMatcher,
|
||||
name: astName,
|
||||
value: astValue
|
||||
} = ast;
|
||||
const { check, forgive, globalObject } = opt;
|
||||
// Validate selector flags ('i' or 's').
|
||||
if (typeof astFlags === 'string' && !/^[is]$/i.test(astFlags) && !forgive) {
|
||||
const css = generateCSS(ast);
|
||||
throw generateException(
|
||||
`Invalid selector ${css}`,
|
||||
SYNTAX_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
const { attributes } = node;
|
||||
// An element with no attributes cannot match.
|
||||
if (!attributes || !attributes.length) {
|
||||
return false;
|
||||
}
|
||||
// Determine case sensitivity based on document type and flags.
|
||||
const contentType = node.ownerDocument.contentType;
|
||||
let caseInsensitive;
|
||||
if (contentType === 'text/html') {
|
||||
if (typeof astFlags === 'string' && /^s$/i.test(astFlags)) {
|
||||
caseInsensitive = false;
|
||||
} else {
|
||||
caseInsensitive = true;
|
||||
}
|
||||
} else if (typeof astFlags === 'string' && /^i$/i.test(astFlags)) {
|
||||
caseInsensitive = true;
|
||||
} else {
|
||||
caseInsensitive = false;
|
||||
}
|
||||
// Prepare the attribute name from the selector for matching.
|
||||
let astAttrName = unescapeSelector(astName.name);
|
||||
if (caseInsensitive) {
|
||||
astAttrName = astAttrName.toLowerCase();
|
||||
}
|
||||
// A set to store the values of attributes whose names match.
|
||||
const attrValues = new Set();
|
||||
// Handle namespaced attribute names (e.g., [*|attr], [ns|attr]).
|
||||
if (astAttrName.indexOf('|') > -1) {
|
||||
const { prefix: astPrefix, localName: astLocalName } =
|
||||
parseAstName(astAttrName);
|
||||
for (const item of attributes) {
|
||||
let { name: itemName, value: itemValue } = item;
|
||||
if (caseInsensitive) {
|
||||
itemName = itemName.toLowerCase();
|
||||
itemValue = itemValue.toLowerCase();
|
||||
}
|
||||
switch (astPrefix) {
|
||||
case '': {
|
||||
if (astLocalName === itemName) {
|
||||
attrValues.add(itemValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '*': {
|
||||
if (itemName.indexOf(':') > -1) {
|
||||
const [, ...restItemName] = itemName.split(':');
|
||||
const itemLocalName = restItemName.join(':').replace(/^:/, '');
|
||||
if (itemLocalName === astLocalName) {
|
||||
attrValues.add(itemValue);
|
||||
}
|
||||
} else if (astLocalName === itemName) {
|
||||
attrValues.add(itemValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (!check) {
|
||||
if (forgive) {
|
||||
return false;
|
||||
}
|
||||
const css = generateCSS(ast);
|
||||
throw generateException(
|
||||
`Invalid selector ${css}`,
|
||||
SYNTAX_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
if (itemName.indexOf(':') > -1) {
|
||||
const [itemPrefix, ...restItemName] = itemName.split(':');
|
||||
const itemLocalName = restItemName.join(':').replace(/^:/, '');
|
||||
// Ignore the 'xml:lang' attribute.
|
||||
if (itemPrefix === 'xml' && itemLocalName === 'lang') {
|
||||
continue;
|
||||
} else if (
|
||||
astPrefix === itemPrefix &&
|
||||
astLocalName === itemLocalName
|
||||
) {
|
||||
const namespaceDeclared = isNamespaceDeclared(astPrefix, node);
|
||||
if (namespaceDeclared) {
|
||||
attrValues.add(itemValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle non-namespaced attribute names.
|
||||
} else {
|
||||
for (let { name: itemName, value: itemValue } of attributes) {
|
||||
if (caseInsensitive) {
|
||||
itemName = itemName.toLowerCase();
|
||||
itemValue = itemValue.toLowerCase();
|
||||
}
|
||||
if (itemName.indexOf(':') > -1) {
|
||||
const [itemPrefix, ...restItemName] = itemName.split(':');
|
||||
const itemLocalName = restItemName.join(':').replace(/^:/, '');
|
||||
// The attribute is starting with ':'.
|
||||
if (!itemPrefix && astAttrName === `:${itemLocalName}`) {
|
||||
attrValues.add(itemValue);
|
||||
// Ignore the 'xml:lang' attribute.
|
||||
} else if (itemPrefix === 'xml' && itemLocalName === 'lang') {
|
||||
continue;
|
||||
} else if (astAttrName === itemLocalName) {
|
||||
attrValues.add(itemValue);
|
||||
}
|
||||
} else if (astAttrName === itemName) {
|
||||
attrValues.add(itemValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!attrValues.size) {
|
||||
return false;
|
||||
}
|
||||
// Prepare the value from the selector's RHS for comparison.
|
||||
const { name: astIdentValue, value: astStringValue } = astValue ?? {};
|
||||
let attrValue;
|
||||
if (astIdentValue) {
|
||||
if (caseInsensitive) {
|
||||
attrValue = astIdentValue.toLowerCase();
|
||||
} else {
|
||||
attrValue = astIdentValue;
|
||||
}
|
||||
} else if (astStringValue) {
|
||||
if (caseInsensitive) {
|
||||
attrValue = astStringValue.toLowerCase();
|
||||
} else {
|
||||
attrValue = astStringValue;
|
||||
}
|
||||
} else if (astStringValue === '') {
|
||||
attrValue = astStringValue;
|
||||
}
|
||||
// Perform the final match based on the specified matcher.
|
||||
switch (astMatcher) {
|
||||
case '=': {
|
||||
return typeof attrValue === 'string' && attrValues.has(attrValue);
|
||||
}
|
||||
case '~=': {
|
||||
if (attrValue && typeof attrValue === 'string') {
|
||||
for (const value of attrValues) {
|
||||
const item = new Set(value.split(/\s+/));
|
||||
if (item.has(attrValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case '|=': {
|
||||
if (attrValue && typeof attrValue === 'string') {
|
||||
for (const value of attrValues) {
|
||||
if (value === attrValue || value.startsWith(`${attrValue}-`)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case '^=': {
|
||||
if (attrValue && typeof attrValue === 'string') {
|
||||
for (const value of attrValues) {
|
||||
if (value.startsWith(`${attrValue}`)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case '$=': {
|
||||
if (attrValue && typeof attrValue === 'string') {
|
||||
for (const value of attrValues) {
|
||||
if (value.endsWith(`${attrValue}`)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case '*=': {
|
||||
if (attrValue && typeof attrValue === 'string') {
|
||||
for (const value of attrValues) {
|
||||
if (value.includes(`${attrValue}`)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case null:
|
||||
default: {
|
||||
// This case handles attribute existence checks (e.g., '[disabled]').
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* match type selector
|
||||
* @param {object} ast - AST
|
||||
* @param {object} node - Element node
|
||||
* @param {object} [opt] - options
|
||||
* @param {boolean} [opt.check] - running in internal check()
|
||||
* @param {boolean} [opt.forgive] - forgive undeclared namespace
|
||||
* @returns {boolean} - result
|
||||
*/
|
||||
export const matchTypeSelector = (ast, node, opt = {}) => {
|
||||
const astName = unescapeSelector(ast.name);
|
||||
const { localName, namespaceURI, prefix } = node;
|
||||
const { check, forgive, globalObject } = opt;
|
||||
let { prefix: astPrefix, localName: astLocalName } = parseAstName(
|
||||
astName,
|
||||
node
|
||||
);
|
||||
if (
|
||||
node.ownerDocument.contentType === 'text/html' &&
|
||||
(!namespaceURI || namespaceURI === 'http://www.w3.org/1999/xhtml') &&
|
||||
REG_TAG_NAME.test(localName)
|
||||
) {
|
||||
astPrefix = astPrefix.toLowerCase();
|
||||
astLocalName = astLocalName.toLowerCase();
|
||||
}
|
||||
let nodePrefix;
|
||||
let nodeLocalName;
|
||||
// just in case that the namespaced content is parsed as text/html
|
||||
if (localName.indexOf(':') > -1) {
|
||||
[nodePrefix, nodeLocalName] = localName.split(':');
|
||||
} else {
|
||||
nodePrefix = prefix || '';
|
||||
nodeLocalName = localName;
|
||||
}
|
||||
switch (astPrefix) {
|
||||
case '': {
|
||||
if (
|
||||
!nodePrefix &&
|
||||
!namespaceURI &&
|
||||
(astLocalName === '*' || astLocalName === nodeLocalName)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case '*': {
|
||||
if (astLocalName === '*' || astLocalName === nodeLocalName) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
if (!check) {
|
||||
if (forgive) {
|
||||
return false;
|
||||
}
|
||||
const css = generateCSS(ast);
|
||||
throw generateException(
|
||||
`Invalid selector ${css}`,
|
||||
SYNTAX_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
const astNS = node.lookupNamespaceURI(astPrefix);
|
||||
const nodeNS = node.lookupNamespaceURI(nodePrefix);
|
||||
if (astNS === nodeNS && astPrefix === nodePrefix) {
|
||||
if (astLocalName === '*' || astLocalName === nodeLocalName) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if (!forgive && !astNS) {
|
||||
throw generateException(
|
||||
`Undeclared namespace ${astPrefix}`,
|
||||
SYNTAX_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
431
node_modules/@asamuzakjp/dom-selector/src/js/parser.js
generated
vendored
Normal file
431
node_modules/@asamuzakjp/dom-selector/src/js/parser.js
generated
vendored
Normal file
@@ -0,0 +1,431 @@
|
||||
/**
|
||||
* parser.js
|
||||
*/
|
||||
|
||||
/* import */
|
||||
import * as cssTree from 'css-tree';
|
||||
import { getType } from './utility.js';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
ATTR_SELECTOR,
|
||||
BIT_01,
|
||||
BIT_02,
|
||||
BIT_04,
|
||||
BIT_08,
|
||||
BIT_16,
|
||||
BIT_32,
|
||||
BIT_FFFF,
|
||||
CLASS_SELECTOR,
|
||||
DUO,
|
||||
HEX,
|
||||
ID_SELECTOR,
|
||||
KEYS_LOGICAL,
|
||||
NTH,
|
||||
PS_CLASS_SELECTOR,
|
||||
PS_ELEMENT_SELECTOR,
|
||||
SELECTOR,
|
||||
SYNTAX_ERR,
|
||||
TYPE_SELECTOR
|
||||
} from './constant.js';
|
||||
const AST_SORT_ORDER = new Map([
|
||||
[PS_ELEMENT_SELECTOR, BIT_01],
|
||||
[ID_SELECTOR, BIT_02],
|
||||
[CLASS_SELECTOR, BIT_04],
|
||||
[TYPE_SELECTOR, BIT_08],
|
||||
[ATTR_SELECTOR, BIT_16],
|
||||
[PS_CLASS_SELECTOR, BIT_32]
|
||||
]);
|
||||
const KEYS_PS_CLASS_STATE = new Set([
|
||||
'checked',
|
||||
'closed',
|
||||
'disabled',
|
||||
'empty',
|
||||
'enabled',
|
||||
'in-range',
|
||||
'indeterminate',
|
||||
'invalid',
|
||||
'open',
|
||||
'out-of-range',
|
||||
'placeholder-shown',
|
||||
'read-only',
|
||||
'read-write',
|
||||
'valid'
|
||||
]);
|
||||
const KEYS_SHADOW_HOST = new Set(['host', 'host-context']);
|
||||
const REG_EMPTY_PS_FUNC =
|
||||
/(?<=:(?:dir|has|host(?:-context)?|is|lang|not|nth-(?:last-)?(?:child|of-type)|where))\(\s+\)/g;
|
||||
const REG_SHADOW_PS_ELEMENT = /^part|slotted$/;
|
||||
const U_FFFD = '\uFFFD';
|
||||
|
||||
/**
|
||||
* Unescapes a CSS selector string.
|
||||
* @param {string} selector - The CSS selector to unescape.
|
||||
* @returns {string} The unescaped selector string.
|
||||
*/
|
||||
export const unescapeSelector = (selector = '') => {
|
||||
if (typeof selector === 'string' && selector.indexOf('\\', 0) >= 0) {
|
||||
const arr = selector.split('\\');
|
||||
const selectorItems = [arr[0]];
|
||||
const l = arr.length;
|
||||
for (let i = 1; i < l; i++) {
|
||||
const item = arr[i];
|
||||
if (item === '' && i === l - 1) {
|
||||
selectorItems.push(U_FFFD);
|
||||
} else {
|
||||
const hexExists = /^([\da-f]{1,6}\s?)/i.exec(item);
|
||||
if (hexExists) {
|
||||
const [, hex] = hexExists;
|
||||
let str;
|
||||
try {
|
||||
const low = parseInt('D800', HEX);
|
||||
const high = parseInt('DFFF', HEX);
|
||||
const deci = parseInt(hex, HEX);
|
||||
if (deci === 0 || (deci >= low && deci <= high)) {
|
||||
str = U_FFFD;
|
||||
} else {
|
||||
str = String.fromCodePoint(deci);
|
||||
}
|
||||
} catch (e) {
|
||||
str = U_FFFD;
|
||||
}
|
||||
let postStr = '';
|
||||
if (item.length > hex.length) {
|
||||
postStr = item.substring(hex.length);
|
||||
}
|
||||
selectorItems.push(`${str}${postStr}`);
|
||||
// whitespace
|
||||
} else if (/^[\n\r\f]/.test(item)) {
|
||||
selectorItems.push(`\\${item}`);
|
||||
} else {
|
||||
selectorItems.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return selectorItems.join('');
|
||||
}
|
||||
return selector;
|
||||
};
|
||||
|
||||
/**
|
||||
* Preprocesses a selector string according to the specification.
|
||||
* @see https://drafts.csswg.org/css-syntax-3/#input-preprocessing
|
||||
* @param {string} value - The value to preprocess.
|
||||
* @returns {string} The preprocessed selector string.
|
||||
*/
|
||||
export const preprocess = value => {
|
||||
// Non-string values will be converted to string.
|
||||
if (typeof value !== 'string') {
|
||||
if (value === undefined || value === null) {
|
||||
return getType(value).toLowerCase();
|
||||
} else if (Array.isArray(value)) {
|
||||
return value.join(',');
|
||||
} else if (Object.hasOwn(value, 'toString')) {
|
||||
return value.toString();
|
||||
} else {
|
||||
throw new DOMException(`Invalid selector ${value}`, SYNTAX_ERR);
|
||||
}
|
||||
}
|
||||
let selector = value;
|
||||
let index = 0;
|
||||
while (index >= 0) {
|
||||
// @see https://drafts.csswg.org/selectors/#id-selectors
|
||||
index = selector.indexOf('#', index);
|
||||
if (index < 0) {
|
||||
break;
|
||||
}
|
||||
const preHash = selector.substring(0, index + 1);
|
||||
let postHash = selector.substring(index + 1);
|
||||
const codePoint = postHash.codePointAt(0);
|
||||
if (codePoint > BIT_FFFF) {
|
||||
const str = `\\${codePoint.toString(HEX)} `;
|
||||
if (postHash.length === DUO) {
|
||||
postHash = str;
|
||||
} else {
|
||||
postHash = `${str}${postHash.substring(DUO)}`;
|
||||
}
|
||||
}
|
||||
selector = `${preHash}${postHash}`;
|
||||
index++;
|
||||
}
|
||||
return selector
|
||||
.replace(/\f|\r\n?/g, '\n')
|
||||
.replace(/[\0\uD800-\uDFFF]|\\$/g, U_FFFD)
|
||||
.replace(/\x26/g, ':scope');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an Abstract Syntax Tree (AST) from a CSS selector string.
|
||||
* @param {string} sel - The CSS selector string.
|
||||
* @returns {object} The parsed AST object.
|
||||
*/
|
||||
export const parseSelector = sel => {
|
||||
const selector = preprocess(sel);
|
||||
// invalid selectors
|
||||
if (/^$|^\s*>|,\s*$/.test(selector)) {
|
||||
throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR);
|
||||
}
|
||||
try {
|
||||
const ast = cssTree.parse(selector, {
|
||||
context: 'selectorList',
|
||||
parseCustomProperty: true
|
||||
});
|
||||
return cssTree.toPlainObject(ast);
|
||||
} catch (e) {
|
||||
const { message } = e;
|
||||
if (
|
||||
/^(?:"\]"|Attribute selector [()\s,=~^$*|]+) is expected$/.test(
|
||||
message
|
||||
) &&
|
||||
!selector.endsWith(']')
|
||||
) {
|
||||
const index = selector.lastIndexOf('[');
|
||||
const selPart = selector.substring(index);
|
||||
if (selPart.includes('"')) {
|
||||
const quotes = selPart.match(/"/g).length;
|
||||
if (quotes % 2) {
|
||||
return parseSelector(`${selector}"]`);
|
||||
}
|
||||
return parseSelector(`${selector}]`);
|
||||
}
|
||||
return parseSelector(`${selector}]`);
|
||||
} else if (message === '")" is expected') {
|
||||
// workaround for https://github.com/csstree/csstree/issues/283
|
||||
if (REG_EMPTY_PS_FUNC.test(selector)) {
|
||||
return parseSelector(`${selector.replaceAll(REG_EMPTY_PS_FUNC, '()')}`);
|
||||
} else if (!selector.endsWith(')')) {
|
||||
return parseSelector(`${selector})`);
|
||||
} else {
|
||||
throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR);
|
||||
}
|
||||
} else {
|
||||
throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Walks the provided AST to collect selector branches and gather information
|
||||
* about its contents.
|
||||
* @param {object} ast - The AST to traverse.
|
||||
* @returns {{branches: Array<object>, info: object}} An object containing the selector branches and info.
|
||||
*/
|
||||
export const walkAST = (ast = {}) => {
|
||||
const branches = new Set();
|
||||
const info = {
|
||||
hasForgivenPseudoFunc: false,
|
||||
hasHasPseudoFunc: false,
|
||||
hasLogicalPseudoFunc: false,
|
||||
hasNotPseudoFunc: false,
|
||||
hasNthChildOfSelector: false,
|
||||
hasNestedSelector: false,
|
||||
hasStatePseudoClass: false
|
||||
};
|
||||
const opt = {
|
||||
enter(node) {
|
||||
switch (node.type) {
|
||||
case CLASS_SELECTOR: {
|
||||
if (/^-?\d/.test(node.name)) {
|
||||
throw new DOMException(
|
||||
`Invalid selector .${node.name}`,
|
||||
SYNTAX_ERR
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ID_SELECTOR: {
|
||||
if (/^-?\d/.test(node.name)) {
|
||||
throw new DOMException(
|
||||
`Invalid selector #${node.name}`,
|
||||
SYNTAX_ERR
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PS_CLASS_SELECTOR: {
|
||||
if (KEYS_LOGICAL.has(node.name)) {
|
||||
info.hasNestedSelector = true;
|
||||
info.hasLogicalPseudoFunc = true;
|
||||
if (node.name === 'has') {
|
||||
info.hasHasPseudoFunc = true;
|
||||
} else if (node.name === 'not') {
|
||||
info.hasNotPseudoFunc = true;
|
||||
} else {
|
||||
info.hasForgivenPseudoFunc = true;
|
||||
}
|
||||
} else if (KEYS_PS_CLASS_STATE.has(node.name)) {
|
||||
info.hasStatePseudoClass = true;
|
||||
} else if (
|
||||
KEYS_SHADOW_HOST.has(node.name) &&
|
||||
Array.isArray(node.children) &&
|
||||
node.children.length
|
||||
) {
|
||||
info.hasNestedSelector = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PS_ELEMENT_SELECTOR: {
|
||||
if (REG_SHADOW_PS_ELEMENT.test(node.name)) {
|
||||
info.hasNestedSelector = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NTH: {
|
||||
if (node.selector) {
|
||||
info.hasNestedSelector = true;
|
||||
info.hasNthChildOfSelector = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SELECTOR: {
|
||||
branches.add(node.children);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
};
|
||||
cssTree.walk(ast, opt);
|
||||
if (info.hasNestedSelector === true) {
|
||||
cssTree.findAll(ast, (node, item, list) => {
|
||||
if (list) {
|
||||
if (node.type === PS_CLASS_SELECTOR && KEYS_LOGICAL.has(node.name)) {
|
||||
const itemList = list.filter(i => {
|
||||
const { name, type } = i;
|
||||
return type === PS_CLASS_SELECTOR && KEYS_LOGICAL.has(name);
|
||||
});
|
||||
for (const { children } of itemList) {
|
||||
// SelectorList
|
||||
for (const { children: grandChildren } of children) {
|
||||
// Selector
|
||||
for (const { children: greatGrandChildren } of grandChildren) {
|
||||
if (branches.has(greatGrandChildren)) {
|
||||
branches.delete(greatGrandChildren);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
node.type === PS_CLASS_SELECTOR &&
|
||||
KEYS_SHADOW_HOST.has(node.name) &&
|
||||
Array.isArray(node.children) &&
|
||||
node.children.length
|
||||
) {
|
||||
const itemList = list.filter(i => {
|
||||
const { children, name, type } = i;
|
||||
const res =
|
||||
type === PS_CLASS_SELECTOR &&
|
||||
KEYS_SHADOW_HOST.has(name) &&
|
||||
Array.isArray(children) &&
|
||||
children.length;
|
||||
return res;
|
||||
});
|
||||
for (const { children } of itemList) {
|
||||
// Selector
|
||||
for (const { children: grandChildren } of children) {
|
||||
if (branches.has(grandChildren)) {
|
||||
branches.delete(grandChildren);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
node.type === PS_ELEMENT_SELECTOR &&
|
||||
REG_SHADOW_PS_ELEMENT.test(node.name)
|
||||
) {
|
||||
const itemList = list.filter(i => {
|
||||
const { name, type } = i;
|
||||
const res =
|
||||
type === PS_ELEMENT_SELECTOR && REG_SHADOW_PS_ELEMENT.test(name);
|
||||
return res;
|
||||
});
|
||||
for (const { children } of itemList) {
|
||||
// Selector
|
||||
for (const { children: grandChildren } of children) {
|
||||
if (branches.has(grandChildren)) {
|
||||
branches.delete(grandChildren);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (node.type === NTH && node.selector) {
|
||||
const itemList = list.filter(i => {
|
||||
const { selector, type } = i;
|
||||
const res = type === NTH && selector;
|
||||
return res;
|
||||
});
|
||||
for (const { selector } of itemList) {
|
||||
const { children } = selector;
|
||||
// Selector
|
||||
for (const { children: grandChildren } of children) {
|
||||
if (branches.has(grandChildren)) {
|
||||
branches.delete(grandChildren);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return {
|
||||
info,
|
||||
branches: [...branches]
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Comparison function for sorting AST nodes based on specificity.
|
||||
* @param {object} a - The first AST node.
|
||||
* @param {object} b - The second AST node.
|
||||
* @returns {number} -1, 0 or 1, depending on the sort order.
|
||||
*/
|
||||
export const compareASTNodes = (a, b) => {
|
||||
const bitA = AST_SORT_ORDER.get(a.type);
|
||||
const bitB = AST_SORT_ORDER.get(b.type);
|
||||
if (bitA === bitB) {
|
||||
return 0;
|
||||
} else if (bitA > bitB) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sorts a collection of AST nodes based on CSS specificity rules.
|
||||
* @param {Array<object>} asts - A collection of AST nodes to sort.
|
||||
* @returns {Array<object>} A new array containing the sorted AST nodes.
|
||||
*/
|
||||
export const sortAST = asts => {
|
||||
const arr = [...asts];
|
||||
if (arr.length > 1) {
|
||||
arr.sort(compareASTNodes);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses a type selector's name, which may include a namespace prefix.
|
||||
* @param {string} selector - The type selector name (e.g., 'ns|E' or 'E').
|
||||
* @returns {{prefix: string, localName: string}} An object with `prefix` and
|
||||
* `localName` properties.
|
||||
*/
|
||||
export const parseAstName = selector => {
|
||||
let prefix;
|
||||
let localName;
|
||||
if (selector && typeof selector === 'string') {
|
||||
if (selector.indexOf('|') > -1) {
|
||||
[prefix, localName] = selector.split('|');
|
||||
} else {
|
||||
prefix = '*';
|
||||
localName = selector;
|
||||
}
|
||||
} else {
|
||||
throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR);
|
||||
}
|
||||
return {
|
||||
prefix,
|
||||
localName
|
||||
};
|
||||
};
|
||||
|
||||
/* Re-exported from css-tree. */
|
||||
export { find as findAST, generate as generateCSS } from 'css-tree';
|
||||
1107
node_modules/@asamuzakjp/dom-selector/src/js/utility.js
generated
vendored
Normal file
1107
node_modules/@asamuzakjp/dom-selector/src/js/utility.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
14
node_modules/@asamuzakjp/dom-selector/types/index.d.ts
generated
vendored
Normal file
14
node_modules/@asamuzakjp/dom-selector/types/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export class DOMSelector {
|
||||
constructor(window: Window, document: Document, opt?: object);
|
||||
clear: () => void;
|
||||
check: (selector: string, node: Element, opt?: object) => CheckResult;
|
||||
matches: (selector: string, node: Element, opt?: object) => boolean;
|
||||
closest: (selector: string, node: Element, opt?: object) => Element | null;
|
||||
querySelector: (selector: string, node: Document | DocumentFragment | Element, opt?: object) => Element | null;
|
||||
querySelectorAll: (selector: string, node: Document | DocumentFragment | Element, opt?: object) => Array<Element>;
|
||||
#private;
|
||||
}
|
||||
export type CheckResult = {
|
||||
match: boolean;
|
||||
pseudoElement: string | null;
|
||||
};
|
||||
79
node_modules/@asamuzakjp/dom-selector/types/js/constant.d.ts
generated
vendored
Normal file
79
node_modules/@asamuzakjp/dom-selector/types/js/constant.d.ts
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
export const ATRULE: "Atrule";
|
||||
export const ATTR_SELECTOR: "AttributeSelector";
|
||||
export const CLASS_SELECTOR: "ClassSelector";
|
||||
export const COMBINATOR: "Combinator";
|
||||
export const IDENT: "Identifier";
|
||||
export const ID_SELECTOR: "IdSelector";
|
||||
export const NOT_SUPPORTED_ERR: "NotSupportedError";
|
||||
export const NTH: "Nth";
|
||||
export const OPERATOR: "Operator";
|
||||
export const PS_CLASS_SELECTOR: "PseudoClassSelector";
|
||||
export const PS_ELEMENT_SELECTOR: "PseudoElementSelector";
|
||||
export const RULE: "Rule";
|
||||
export const SCOPE: "Scope";
|
||||
export const SELECTOR: "Selector";
|
||||
export const SELECTOR_LIST: "SelectorList";
|
||||
export const STRING: "String";
|
||||
export const SYNTAX_ERR: "SyntaxError";
|
||||
export const TARGET_ALL: "all";
|
||||
export const TARGET_FIRST: "first";
|
||||
export const TARGET_LINEAL: "lineal";
|
||||
export const TARGET_SELF: "self";
|
||||
export const TYPE_SELECTOR: "TypeSelector";
|
||||
export const BIT_01: 1;
|
||||
export const BIT_02: 2;
|
||||
export const BIT_04: 4;
|
||||
export const BIT_08: 8;
|
||||
export const BIT_16: 16;
|
||||
export const BIT_32: 32;
|
||||
export const BIT_FFFF: 65535;
|
||||
export const DUO: 2;
|
||||
export const HEX: 16;
|
||||
export const TYPE_FROM: 8;
|
||||
export const TYPE_TO: -1;
|
||||
export const ELEMENT_NODE: 1;
|
||||
export const TEXT_NODE: 3;
|
||||
export const DOCUMENT_NODE: 9;
|
||||
export const DOCUMENT_FRAGMENT_NODE: 11;
|
||||
export const DOCUMENT_POSITION_PRECEDING: 2;
|
||||
export const DOCUMENT_POSITION_CONTAINS: 8;
|
||||
export const DOCUMENT_POSITION_CONTAINED_BY: 16;
|
||||
export const SHOW_ALL: 4294967295;
|
||||
export const SHOW_CONTAINER: 1281;
|
||||
export const SHOW_DOCUMENT: 256;
|
||||
export const SHOW_DOCUMENT_FRAGMENT: 1024;
|
||||
export const SHOW_ELEMENT: 1;
|
||||
export const ALPHA_NUM: "[A-Z\\d]+";
|
||||
export const CHILD_IDX: "(?:first|last|only)-(?:child|of-type)";
|
||||
export const DIGIT: "(?:0|[1-9]\\d*)";
|
||||
export const LANG_PART: "(?:-[A-Z\\d]+)*";
|
||||
export const PSEUDO_CLASS: "(?:any-)?link|(?:first|last|only)-(?:child|of-type)|checked|empty|indeterminate|read-(?:only|write)|target";
|
||||
export const ANB: "[+-]?(?:(?:0|[1-9]\\d*)n?|n)|(?:[+-]?(?:0|[1-9]\\d*))?n\\s*[+-]\\s*(?:0|[1-9]\\d*)";
|
||||
export const N_TH: "nth-(?:last-)?(?:child|of-type)\\(\\s*(?:even|odd|[+-]?(?:(?:0|[1-9]\\d*)n?|n)|(?:[+-]?(?:0|[1-9]\\d*))?n\\s*[+-]\\s*(?:0|[1-9]\\d*))\\s*\\)";
|
||||
export const SUB_TYPE: "\\[[^|\\]]+\\]|[#.:][\\w-]+";
|
||||
export const SUB_TYPE_WO_PSEUDO: "\\[[^|\\]]+\\]|[#.][\\w-]+";
|
||||
export const TAG_TYPE: "\\*|[A-Za-z][\\w-]*";
|
||||
export const TAG_TYPE_I: "\\*|[A-Z][\\w-]*";
|
||||
export const COMPOUND: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)";
|
||||
export const COMPOUND_WO_PSEUDO: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.][\\w-]+)+)";
|
||||
export const COMBO: "\\s?[\\s>~+]\\s?";
|
||||
export const COMPLEX: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*";
|
||||
export const DESCEND: "\\s?[\\s>]\\s?";
|
||||
export const SIBLING: "\\s?[+~]\\s?";
|
||||
export const NESTED_LOGIC_A: ":is\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*\\s*\\)";
|
||||
export const NESTED_LOGIC_B: ":is\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*)*\\s*\\)";
|
||||
export const COMPOUND_A: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*\\s*\\))+)";
|
||||
export const COMPOUND_B: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*)*\\s*\\))+)";
|
||||
export const COMPOUND_I: "(?:\\*|[A-Z][\\w-]*|(?:\\*|[A-Z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)";
|
||||
export const COMPLEX_L: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*)*\\s*\\))+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*)*\\s*\\))+))*";
|
||||
export const LOGIC_COMPLEX: "(?:is|not)\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*)*\\s*\\))+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*)*\\s*\\))+))*(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*)*\\s*\\))+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*)*\\s*\\))+))*)*\\s*\\)";
|
||||
export const LOGIC_COMPOUND: "(?:is|not)\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*\\s*\\))+)(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*\\s*\\))+))*\\s*\\)";
|
||||
export const HAS_COMPOUND: "has\\([\\s>]?\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.][\\w-]+)+)\\s*\\)";
|
||||
export const FORM_PARTS: readonly string[];
|
||||
export const INPUT_BUTTON: readonly string[];
|
||||
export const INPUT_CHECK: readonly string[];
|
||||
export const INPUT_DATE: readonly string[];
|
||||
export const INPUT_TEXT: readonly string[];
|
||||
export const INPUT_EDIT: readonly string[];
|
||||
export const INPUT_LTR: readonly string[];
|
||||
export const KEYS_LOGICAL: Set<string>;
|
||||
64
node_modules/@asamuzakjp/dom-selector/types/js/finder.d.ts
generated
vendored
Normal file
64
node_modules/@asamuzakjp/dom-selector/types/js/finder.d.ts
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
export class Finder {
|
||||
constructor(window: object);
|
||||
onError: (e: Error, opt?: {
|
||||
noexcept?: boolean;
|
||||
}) => void;
|
||||
setup: (selector: string, node: object, opt?: {
|
||||
check?: boolean;
|
||||
noexcept?: boolean;
|
||||
warn?: boolean;
|
||||
}) => object;
|
||||
clearResults: (all?: boolean) => void;
|
||||
private _handleFocusEvent;
|
||||
private _handleKeyboardEvent;
|
||||
private _handleMouseEvent;
|
||||
private _registerEventListeners;
|
||||
private _processSelectorBranches;
|
||||
private _correspond;
|
||||
private _createTreeWalker;
|
||||
private _getSelectorBranches;
|
||||
private _getFilteredChildren;
|
||||
private _collectNthChild;
|
||||
private _collectNthOfType;
|
||||
private _matchAnPlusB;
|
||||
private _matchHasPseudoFunc;
|
||||
private _evaluateHasPseudo;
|
||||
private _matchLogicalPseudoFunc;
|
||||
private _matchPseudoClassSelector;
|
||||
private _evaluateHostPseudo;
|
||||
private _evaluateHostContextPseudo;
|
||||
private _matchShadowHostPseudoClass;
|
||||
private _matchSelectorForElement;
|
||||
private _matchSelectorForShadowRoot;
|
||||
private _matchSelector;
|
||||
private _matchLeaves;
|
||||
private _traverseAllDescendants;
|
||||
private _findDescendantNodes;
|
||||
private _matchDescendantCombinator;
|
||||
private _matchChildCombinator;
|
||||
private _matchAdjacentSiblingCombinator;
|
||||
private _matchGeneralSiblingCombinator;
|
||||
private _matchCombinator;
|
||||
private _traverseAndCollectNodes;
|
||||
private _findPrecede;
|
||||
private _findNodeWalker;
|
||||
private _matchSelf;
|
||||
private _findLineal;
|
||||
private _findEntryNodesForPseudoElement;
|
||||
private _findEntryNodesForId;
|
||||
private _findEntryNodesForClass;
|
||||
private _findEntryNodesForType;
|
||||
private _findEntryNodesForOther;
|
||||
private _findEntryNodes;
|
||||
private _determineTraversalStrategy;
|
||||
private _processPendingItems;
|
||||
private _collectNodes;
|
||||
private _getCombinedNodes;
|
||||
private _matchNodeNext;
|
||||
private _matchNodePrev;
|
||||
private _processComplexBranchAll;
|
||||
private _findChildNodeContainedByNode;
|
||||
private _processComplexBranchFirst;
|
||||
find: (targetType: string) => Set<object>;
|
||||
#private;
|
||||
}
|
||||
16
node_modules/@asamuzakjp/dom-selector/types/js/matcher.d.ts
generated
vendored
Normal file
16
node_modules/@asamuzakjp/dom-selector/types/js/matcher.d.ts
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
export function matchPseudoElementSelector(astName: string, astType: string, opt?: {
|
||||
forgive?: boolean;
|
||||
warn?: boolean;
|
||||
}): void;
|
||||
export function matchDirectionPseudoClass(ast: object, node: object): boolean;
|
||||
export function matchLanguagePseudoClass(ast: object, node: object): boolean;
|
||||
export function matchDisabledPseudoClass(astName: string, node: object): boolean;
|
||||
export function matchReadOnlyPseudoClass(astName: string, node: object): boolean;
|
||||
export function matchAttributeSelector(ast: object, node: object, opt?: {
|
||||
check?: boolean;
|
||||
forgive?: boolean;
|
||||
}): boolean;
|
||||
export function matchTypeSelector(ast: object, node: object, opt?: {
|
||||
check?: boolean;
|
||||
forgive?: boolean;
|
||||
}): boolean;
|
||||
14
node_modules/@asamuzakjp/dom-selector/types/js/parser.d.ts
generated
vendored
Normal file
14
node_modules/@asamuzakjp/dom-selector/types/js/parser.d.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export function unescapeSelector(selector?: string): string;
|
||||
export function preprocess(value: string): string;
|
||||
export function parseSelector(sel: string): object;
|
||||
export function walkAST(ast?: object): {
|
||||
branches: Array<object>;
|
||||
info: object;
|
||||
};
|
||||
export function compareASTNodes(a: object, b: object): number;
|
||||
export function sortAST(asts: Array<object>): Array<object>;
|
||||
export function parseAstName(selector: string): {
|
||||
prefix: string;
|
||||
localName: string;
|
||||
};
|
||||
export { find as findAST, generate as generateCSS } from "css-tree";
|
||||
30
node_modules/@asamuzakjp/dom-selector/types/js/utility.d.ts
generated
vendored
Normal file
30
node_modules/@asamuzakjp/dom-selector/types/js/utility.d.ts
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
export function getType(o: object): string;
|
||||
export function verifyArray(arr: any[], type: string): any[];
|
||||
export function generateException(msg: string, name: string, globalObject?: object): DOMException;
|
||||
export function findNestedHas(leaf: object): object | null;
|
||||
export function findLogicalWithNestedHas(leaf: object): object | null;
|
||||
export function filterNodesByAnB(nodes: Array<object>, anb: {
|
||||
a: number;
|
||||
b: number;
|
||||
reverse?: boolean;
|
||||
}): Array<object>;
|
||||
export function resolveContent(node: object): Array<object | boolean>;
|
||||
export function traverseNode(node: object, walker: object, force?: boolean): object | null;
|
||||
export function isCustomElement(node: object, opt?: object): boolean;
|
||||
export function getSlottedTextContent(node: object): string | null;
|
||||
export function getDirectionality(node: object): string | null;
|
||||
export function getLanguageAttribute(node: object): string | null;
|
||||
export function isContentEditable(node: object): boolean;
|
||||
export function isVisible(node: object): boolean;
|
||||
export function isFocusVisible(node: object): boolean;
|
||||
export function isFocusableArea(node: object): boolean;
|
||||
export function isFocusable(node: object): boolean;
|
||||
export function getNamespaceURI(ns: string, node: object): string | null;
|
||||
export function isNamespaceDeclared(ns?: string, node?: object): boolean;
|
||||
export function isPreceding(nodeA: object, nodeB: object): boolean;
|
||||
export function compareNodes(a: object, b: object): number;
|
||||
export function sortNodes(nodes?: Array<object> | Set<object>): Array<object>;
|
||||
export function concatNestedSelectors(selectors: Array<Array<string>>): string;
|
||||
export function extractNestedSelectors(css: string): Array<Array<string>>;
|
||||
export function initNwsapi(window: object, document: object): object;
|
||||
export function filterSelector(selector: string, target: string): boolean;
|
||||
22
node_modules/@asamuzakjp/nwsapi/LICENSE
generated
vendored
Normal file
22
node_modules/@asamuzakjp/nwsapi/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2007-2019 Diego Perini (http://www.iport.it/)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
132
node_modules/@asamuzakjp/nwsapi/README.md
generated
vendored
Normal file
132
node_modules/@asamuzakjp/nwsapi/README.md
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
# [NWSAPI](http://dperini.github.io/nwsapi/)
|
||||
|
||||
Fast CSS Selectors API Engine
|
||||
|
||||
   
|
||||
|
||||
NWSAPI is the development progress of [NWMATCHER](https://github.com/dperini/nwmatcher) aiming at [Selectors Level 4](https://www.w3.org/TR/selectors-4/) conformance. It has been completely reworked to be easily extended and maintained. It is a right-to-left selector parser and compiler written in pure Javascript with no external dependencies. It was initially thought as a cross browser library to improve event delegation and web page scraping in various frameworks but it has become a popular replacement of the native CSS selection and matching functionality in newer browsers and headless environments.
|
||||
|
||||
It uses [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) to parse CSS selector strings and [metaprogramming](https://en.wikipedia.org/wiki/Metaprogramming) to transforms these selector strings into Javascript function resolvers. This process is executed only once for each selector string allowing memoization of the function resolvers and achieving unmatched performances.
|
||||
|
||||
## Installation
|
||||
|
||||
To include NWSAPI in a standard web page:
|
||||
|
||||
```html
|
||||
<script type="text/javascript" src="nwsapi.js"></script>
|
||||
```
|
||||
|
||||
To include NWSAPI in a standard web page and automatically replace the native QSA:
|
||||
|
||||
```html
|
||||
<script type="text/javascript" src="nwsapi.js" onload="NW.Dom.install()"></script>
|
||||
```
|
||||
|
||||
To use NWSAPI with Node.js:
|
||||
|
||||
```
|
||||
$ npm install nwsapi
|
||||
```
|
||||
|
||||
NWSAPI currently supports browsers (as a global, `NW.Dom`) and headless environments (as a CommonJS module).
|
||||
|
||||
|
||||
## Supported Selectors
|
||||
|
||||
Here is a list of all the CSS2/CSS3/CSS4 [Supported selectors](https://github.com/dperini/nwsapi/wiki/CSS-supported-selectors).
|
||||
|
||||
|
||||
## Features and Compliance
|
||||
|
||||
You can read more about NWSAPI [features and compliance](https://github.com/dperini/nwsapi/wiki/Features-and-compliance) on the wiki.
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### DOM Selection
|
||||
|
||||
#### `ancestor( selector, context, callback )`
|
||||
|
||||
Returns a reference to the nearest ancestor element matching `selector`, starting at `context`. Returns `null` if no element is found. If `callback` is provided, it is invoked for the matched element.
|
||||
|
||||
#### `first( selector, context, callback )`
|
||||
|
||||
Returns a reference to the first element matching `selector`, starting at `context`. Returns `null` if no element matches. If `callback` is provided, it is invoked for the matched element.
|
||||
|
||||
#### `match( selector, element, callback )`
|
||||
|
||||
Returns `true` if `element` matches `selector`, starting at `context`; returns `false` otherwise. If `callback` is provided, it is invoked for the matched element.
|
||||
|
||||
#### `select( selector, context, callback )`
|
||||
|
||||
Returns an array of all the elements matching `selector`, starting at `context`; returns empty `Array` otherwise. If `callback` is provided, it is invoked for each matching element.
|
||||
|
||||
|
||||
### DOM Helpers
|
||||
|
||||
#### `byId( id, from )`
|
||||
|
||||
Returns a reference to the first element with ID `id`, optionally filtered to descendants of the element `from`.
|
||||
|
||||
#### `byTag( tag, from )`
|
||||
|
||||
Returns an array of elements having the specified tag name `tag`, optionally filtered to descendants of the element `from`.
|
||||
|
||||
#### `byClass( class, from )`
|
||||
|
||||
Returns an array of elements having the specified class name `class`, optionally filtered to descendants of the element `from`.
|
||||
|
||||
|
||||
### Engine Configuration
|
||||
|
||||
#### `configure( options )`
|
||||
|
||||
The following is the list of currently available configuration options, their default values and descriptions, they are boolean flags that can be set to `true` or `false`:
|
||||
|
||||
* `IDS_DUPES`: true - true to allow using multiple elements having the same id, false to disallow
|
||||
* `LIVECACHE`: true - true for caching both results and resolvers, false for caching only resolvers
|
||||
* `MIXEDCASE`: true - true to match tag names case insensitive, false to match using case sensitive
|
||||
* `LOGERRORS`: true - true to print errors and warnings to the console, false to mute both of them
|
||||
|
||||
|
||||
### Examples on extending the basic functionalities
|
||||
|
||||
#### `configure( { <configuration-flag>: [ true | false ] } )`
|
||||
|
||||
Disable logging errors/warnings to console, disallow duplicate ids. Example:
|
||||
|
||||
```js
|
||||
NW.Dom.configure( { LOGERRORS: false, IDS_DUPES: false } );
|
||||
```
|
||||
NOTE: NW.Dom.configure() without parameters return the current configuration.
|
||||
|
||||
#### `registerCombinator( symbol, resolver )`
|
||||
|
||||
Registers a new symbol and its matching resolver in the combinators table. Example:
|
||||
|
||||
```js
|
||||
NW.Dom.registerCombinator( '^', 'e.parentElement' );
|
||||
```
|
||||
|
||||
#### `registerOperator( symbol, resolver )`
|
||||
|
||||
Registers a new symbol and its matching resolver in the attribute operators table. Example:
|
||||
|
||||
```js
|
||||
NW.Dom.registerOperator( '!=', { p1: '^', p2: '$', p3: 'false' } );
|
||||
```
|
||||
|
||||
#### `registerSelector( name, rexp, func )`
|
||||
|
||||
Registers a new selector, the matching RE and the resolver function, in the selectors table. Example:
|
||||
|
||||
```js
|
||||
NW.Dom.registerSelector('Controls', /^\:(control)(.*)/i,
|
||||
(function(global) {
|
||||
return function(match, source, mode, callback) {
|
||||
var status = true;
|
||||
source = 'if(/^(button|input|select|textarea)/i.test(e.nodeName)){' + source + '}';
|
||||
return { 'source': source, 'status': status };
|
||||
};
|
||||
})(this));
|
||||
```
|
||||
43
node_modules/@asamuzakjp/nwsapi/package.json
generated
vendored
Normal file
43
node_modules/@asamuzakjp/nwsapi/package.json
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@asamuzakjp/nwsapi",
|
||||
"version": "2.3.9",
|
||||
"description": "Fast CSS Selectors API Engine",
|
||||
"homepage": "http://javascript.nwbox.com/nwsapi/",
|
||||
"main": "./src/nwsapi",
|
||||
"keywords": [
|
||||
"css",
|
||||
"css3",
|
||||
"css4",
|
||||
"matcher",
|
||||
"selector"
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "http://javascript.nwbox.com/nwsapi/MIT-LICENSE"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Diego Perini",
|
||||
"email": "diego.perini@gmail.com",
|
||||
"web": "http://www.iport.it/"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Diego Perini",
|
||||
"email": "diego.perini@gmail.com",
|
||||
"web": "http://www.iport.it/"
|
||||
}
|
||||
],
|
||||
"bugs": {
|
||||
"url": "http://github.com/dperini/nwsapi/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/dperini/nwsapi.git"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint ./src/nwsapi.js"
|
||||
}
|
||||
}
|
||||
1855
node_modules/@asamuzakjp/nwsapi/src/nwsapi.js
generated
vendored
Normal file
1855
node_modules/@asamuzakjp/nwsapi/src/nwsapi.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
10
node_modules/@csstools/color-helpers/CHANGELOG.md
generated
vendored
Normal file
10
node_modules/@csstools/color-helpers/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Changes to Color Helpers
|
||||
|
||||
### 5.1.0
|
||||
|
||||
_August 22, 2025_
|
||||
|
||||
- Add `lin_P3_to_XYZ_D50`
|
||||
- Add `XYZ_D50_to_lin_P3`
|
||||
|
||||
[Full CHANGELOG](https://github.com/csstools/postcss-plugins/tree/main/packages/color-helpers/CHANGELOG.md)
|
||||
18
node_modules/@csstools/color-helpers/LICENSE.md
generated
vendored
Normal file
18
node_modules/@csstools/color-helpers/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
MIT No Attribution (MIT-0)
|
||||
|
||||
Copyright © CSSTools Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the “Software”), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
32
node_modules/@csstools/color-helpers/README.md
generated
vendored
Normal file
32
node_modules/@csstools/color-helpers/README.md
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# Color Helpers <img src="https://cssdb.org/images/css.svg" alt="for CSS" width="90" height="90" align="right">
|
||||
|
||||
[<img alt="npm version" src="https://img.shields.io/npm/v/@csstools/color-helpers.svg" height="20">][npm-url]
|
||||
[<img alt="Build Status" src="https://github.com/csstools/postcss-plugins/actions/workflows/test.yml/badge.svg?branch=main" height="20">][cli-url]
|
||||
[<img alt="Discord" src="https://shields.io/badge/Discord-5865F2?logo=discord&logoColor=white">][discord]
|
||||
|
||||
## Usage
|
||||
|
||||
Add [Color Helpers] to your project:
|
||||
|
||||
```bash
|
||||
npm install @csstools/color-helpers --save-dev
|
||||
```
|
||||
|
||||
This package exists to join all the different color functions scattered among the Colors 4 and Colors 5 plugins we maintain such as:
|
||||
|
||||
* [PostCSS Color Function]
|
||||
* [PostCSS Lab Function]
|
||||
* [PostCSS OKLab Function]
|
||||
|
||||
## Copyright
|
||||
|
||||
This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/tree/main/css-color-4. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang).
|
||||
|
||||
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
|
||||
[discord]: https://discord.gg/bUadyRwkJS
|
||||
[npm-url]: https://www.npmjs.com/package/@csstools/color-helpers
|
||||
|
||||
[Color Helpers]: https://github.com/csstools/postcss-plugins/tree/main/packages/color-helpers
|
||||
[PostCSS Color Function]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-color-function
|
||||
[PostCSS Lab Function]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-lab-functionw
|
||||
[PostCSS OKLab Function]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-oklab-function
|
||||
62
node_modules/@csstools/color-helpers/package.json
generated
vendored
Normal file
62
node_modules/@csstools/color-helpers/package.json
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@csstools/color-helpers",
|
||||
"description": "Color helpers to ease transformation between formats, gamut, etc",
|
||||
"version": "5.1.0",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Antonio Laguna",
|
||||
"email": "antonio@laguna.es",
|
||||
"url": "https://antonio.laguna.es"
|
||||
},
|
||||
{
|
||||
"name": "Romain Menke",
|
||||
"email": "romainmenke@gmail.com"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"require": {
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"CHANGELOG.md",
|
||||
"LICENSE.md",
|
||||
"README.md",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {},
|
||||
"homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/color-helpers#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/csstools/postcss-plugins.git",
|
||||
"directory": "packages/color-helpers"
|
||||
},
|
||||
"bugs": "https://github.com/csstools/postcss-plugins/issues",
|
||||
"keywords": [
|
||||
"colors",
|
||||
"css"
|
||||
]
|
||||
}
|
||||
10
node_modules/@csstools/css-calc/CHANGELOG.md
generated
vendored
Normal file
10
node_modules/@csstools/css-calc/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Changes to CSS Calc
|
||||
|
||||
### 2.1.4
|
||||
|
||||
_May 27, 2025_
|
||||
|
||||
- Updated [`@csstools/css-tokenizer`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer) to [`3.0.4`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer/CHANGELOG.md#304) (patch)
|
||||
- Updated [`@csstools/css-parser-algorithms`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms) to [`3.0.5`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms/CHANGELOG.md#305) (patch)
|
||||
|
||||
[Full CHANGELOG](https://github.com/csstools/postcss-plugins/tree/main/packages/css-calc/CHANGELOG.md)
|
||||
20
node_modules/@csstools/css-calc/LICENSE.md
generated
vendored
Normal file
20
node_modules/@csstools/css-calc/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2022 Romain Menke, Antonio Laguna <antonio@laguna.es>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
132
node_modules/@csstools/css-calc/README.md
generated
vendored
Normal file
132
node_modules/@csstools/css-calc/README.md
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
# CSS Calc <img src="https://cssdb.org/images/css.svg" alt="for CSS" width="90" height="90" align="right">
|
||||
|
||||
[<img alt="npm version" src="https://img.shields.io/npm/v/@csstools/css-calc.svg" height="20">][npm-url]
|
||||
[<img alt="Build Status" src="https://github.com/csstools/postcss-plugins/actions/workflows/test.yml/badge.svg?branch=main" height="20">][cli-url]
|
||||
[<img alt="Discord" src="https://shields.io/badge/Discord-5865F2?logo=discord&logoColor=white">][discord]
|
||||
|
||||
Implemented from : https://drafts.csswg.org/css-values-4/ on 2023-02-17
|
||||
|
||||
## Usage
|
||||
|
||||
Add [CSS calc] to your project:
|
||||
|
||||
```bash
|
||||
npm install @csstools/css-calc @csstools/css-parser-algorithms @csstools/css-tokenizer --save-dev
|
||||
```
|
||||
|
||||
### With string values :
|
||||
|
||||
```mjs
|
||||
import { calc } from '@csstools/css-calc';
|
||||
|
||||
// '20'
|
||||
console.log(calc('calc(10 * 2)'));
|
||||
```
|
||||
|
||||
### With component values :
|
||||
|
||||
```mjs
|
||||
import { stringify, tokenizer } from '@csstools/css-tokenizer';
|
||||
import { parseCommaSeparatedListOfComponentValues } from '@csstools/css-parser-algorithms';
|
||||
import { calcFromComponentValues } from '@csstools/css-calc';
|
||||
|
||||
const t = tokenizer({
|
||||
css: 'calc(10 * 2)',
|
||||
});
|
||||
|
||||
const tokens = [];
|
||||
|
||||
{
|
||||
while (!t.endOfFile()) {
|
||||
tokens.push(t.nextToken());
|
||||
}
|
||||
|
||||
tokens.push(t.nextToken()); // EOF-token
|
||||
}
|
||||
|
||||
const result = parseCommaSeparatedListOfComponentValues(tokens, {});
|
||||
|
||||
// filter or mutate the component values
|
||||
|
||||
const calcResult = calcFromComponentValues(result, { precision: 5, toCanonicalUnits: true });
|
||||
|
||||
// filter or mutate the component values even further
|
||||
|
||||
const calcResultStr = calcResult.map((componentValues) => {
|
||||
return componentValues.map((x) => stringify(...x.tokens())).join('');
|
||||
}).join(',');
|
||||
|
||||
// '20'
|
||||
console.log(calcResultStr);
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
#### `precision` :
|
||||
|
||||
The default precision is fairly high.
|
||||
It aims to be high enough to make rounding unnoticeable in the browser.
|
||||
|
||||
You can set it to a lower number to suit your needs.
|
||||
|
||||
```mjs
|
||||
import { calc } from '@csstools/css-calc';
|
||||
|
||||
// '0.3'
|
||||
console.log(calc('calc(1 / 3)', { precision: 1 }));
|
||||
// '0.33'
|
||||
console.log(calc('calc(1 / 3)', { precision: 2 }));
|
||||
```
|
||||
|
||||
#### `globals` :
|
||||
|
||||
Pass global values as a map of key value pairs.
|
||||
|
||||
> Example : Relative color syntax (`lch(from pink calc(l / 2) c h)`) exposes color channel information as ident tokens.
|
||||
> By passing globals for `l`, `c` and `h` it is possible to solve nested `calc()`'s.
|
||||
|
||||
```mjs
|
||||
import { calc } from '@csstools/css-calc';
|
||||
|
||||
const globals = new Map([
|
||||
['a', '10px'],
|
||||
['b', '2rem'],
|
||||
]);
|
||||
|
||||
// '20px'
|
||||
console.log(calc('calc(a * 2)', { globals: globals }));
|
||||
// '6rem'
|
||||
console.log(calc('calc(b * 3)', { globals: globals }));
|
||||
```
|
||||
|
||||
#### `toCanonicalUnits` :
|
||||
|
||||
By default this package will try to preserve units.
|
||||
The heuristic to do this is very simplistic.
|
||||
We take the first unit we encounter and try to convert other dimensions to that unit.
|
||||
|
||||
This better matches what users expect from a CSS dev tool.
|
||||
|
||||
If you want to have outputs that are closes to CSS serialized values you can pass `toCanonicalUnits: true`.
|
||||
|
||||
```mjs
|
||||
import { calc } from '@csstools/css-calc';
|
||||
|
||||
// '20hz'
|
||||
console.log(calc('calc(0.01khz + 10hz)', { toCanonicalUnits: true }));
|
||||
|
||||
// '20hz'
|
||||
console.log(calc('calc(10hz + 0.01khz)', { toCanonicalUnits: true }));
|
||||
|
||||
// '0.02khz' !!!
|
||||
console.log(calc('calc(0.01khz + 10hz)', { toCanonicalUnits: false }));
|
||||
|
||||
// '20hz'
|
||||
console.log(calc('calc(10hz + 0.01khz)', { toCanonicalUnits: false }));
|
||||
```
|
||||
|
||||
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
|
||||
[discord]: https://discord.gg/bUadyRwkJS
|
||||
[npm-url]: https://www.npmjs.com/package/@csstools/css-calc
|
||||
|
||||
[CSS calc]: https://github.com/csstools/postcss-plugins/tree/main/packages/css-calc
|
||||
66
node_modules/@csstools/css-calc/package.json
generated
vendored
Normal file
66
node_modules/@csstools/css-calc/package.json
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "@csstools/css-calc",
|
||||
"description": "Solve CSS math expressions",
|
||||
"version": "2.1.4",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Antonio Laguna",
|
||||
"email": "antonio@laguna.es",
|
||||
"url": "https://antonio.laguna.es"
|
||||
},
|
||||
{
|
||||
"name": "Romain Menke",
|
||||
"email": "romainmenke@gmail.com"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"require": {
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"CHANGELOG.md",
|
||||
"LICENSE.md",
|
||||
"README.md",
|
||||
"dist"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||
"@csstools/css-tokenizer": "^3.0.4"
|
||||
},
|
||||
"scripts": {},
|
||||
"homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/css-calc#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/csstools/postcss-plugins.git",
|
||||
"directory": "packages/css-calc"
|
||||
},
|
||||
"bugs": "https://github.com/csstools/postcss-plugins/issues",
|
||||
"keywords": [
|
||||
"calc",
|
||||
"css"
|
||||
]
|
||||
}
|
||||
13
node_modules/@csstools/css-color-parser/CHANGELOG.md
generated
vendored
Normal file
13
node_modules/@csstools/css-color-parser/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Changes to CSS Color Parser
|
||||
|
||||
### 3.1.0
|
||||
|
||||
_August 22, 2025_
|
||||
|
||||
- Add support for `display-p3-linear` in `color(display-p3-linear 0.3081 0.014 0.0567)`
|
||||
- Add support for `display-p3-linear` in `color-mix(in display-p3-linear, red, blue)`
|
||||
- Add support for omitting the color space in `color-mix(red, blue)`
|
||||
- Add support for `alpha(from red / 0.5)`
|
||||
- Updated [`@csstools/color-helpers`](https://github.com/csstools/postcss-plugins/tree/main/packages/color-helpers) to [`5.1.0`](https://github.com/csstools/postcss-plugins/tree/main/packages/color-helpers/CHANGELOG.md#510) (minor)
|
||||
|
||||
[Full CHANGELOG](https://github.com/csstools/postcss-plugins/tree/main/packages/css-color-parser/CHANGELOG.md)
|
||||
20
node_modules/@csstools/css-color-parser/LICENSE.md
generated
vendored
Normal file
20
node_modules/@csstools/css-color-parser/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2022 Romain Menke, Antonio Laguna <antonio@laguna.es>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
37
node_modules/@csstools/css-color-parser/README.md
generated
vendored
Normal file
37
node_modules/@csstools/css-color-parser/README.md
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# CSS Color Parser <img src="https://cssdb.org/images/css.svg" alt="for CSS" width="90" height="90" align="right">
|
||||
|
||||
[<img alt="npm version" src="https://img.shields.io/npm/v/@csstools/css-color-parser.svg" height="20">][npm-url]
|
||||
[<img alt="Build Status" src="https://github.com/csstools/postcss-plugins/actions/workflows/test.yml/badge.svg?branch=main" height="20">][cli-url]
|
||||
[<img alt="Discord" src="https://shields.io/badge/Discord-5865F2?logo=discord&logoColor=white">][discord]
|
||||
|
||||
## Usage
|
||||
|
||||
Add [CSS Color Parser] to your project:
|
||||
|
||||
```bash
|
||||
npm install @csstools/css-color-parser @csstools/css-parser-algorithms @csstools/css-tokenizer --save-dev
|
||||
```
|
||||
|
||||
```ts
|
||||
import { color } from '@csstools/css-color-parser';
|
||||
import { isFunctionNode, parseComponentValue } from '@csstools/css-parser-algorithms';
|
||||
import { serializeRGB } from '@csstools/css-color-parser';
|
||||
import { tokenize } from '@csstools/css-tokenizer';
|
||||
|
||||
// color() expects a parsed component value.
|
||||
const hwbComponentValue = parseComponentValue(tokenize({ css: 'hwb(10deg 10% 20%)' }));
|
||||
const colorData = color(hwbComponentValue);
|
||||
if (colorData) {
|
||||
console.log(colorData);
|
||||
|
||||
// serializeRGB() returns a component value.
|
||||
const rgbComponentValue = serializeRGB(colorData);
|
||||
console.log(rgbComponentValue.toString());
|
||||
}
|
||||
```
|
||||
|
||||
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
|
||||
[discord]: https://discord.gg/bUadyRwkJS
|
||||
[npm-url]: https://www.npmjs.com/package/@csstools/css-color-parser
|
||||
|
||||
[CSS Color Parser]: https://github.com/csstools/postcss-plugins/tree/main/packages/css-color-parser
|
||||
71
node_modules/@csstools/css-color-parser/package.json
generated
vendored
Normal file
71
node_modules/@csstools/css-color-parser/package.json
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "@csstools/css-color-parser",
|
||||
"description": "Parse CSS color values",
|
||||
"version": "3.1.0",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Antonio Laguna",
|
||||
"email": "antonio@laguna.es",
|
||||
"url": "https://antonio.laguna.es"
|
||||
},
|
||||
{
|
||||
"name": "Romain Menke",
|
||||
"email": "romainmenke@gmail.com"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"require": {
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"CHANGELOG.md",
|
||||
"LICENSE.md",
|
||||
"README.md",
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@csstools/color-helpers": "^5.1.0",
|
||||
"@csstools/css-calc": "^2.1.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||
"@csstools/css-tokenizer": "^3.0.4"
|
||||
},
|
||||
"scripts": {},
|
||||
"homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/css-color-parser#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/csstools/postcss-plugins.git",
|
||||
"directory": "packages/css-color-parser"
|
||||
},
|
||||
"bugs": "https://github.com/csstools/postcss-plugins/issues",
|
||||
"keywords": [
|
||||
"color",
|
||||
"css",
|
||||
"parser"
|
||||
]
|
||||
}
|
||||
9
node_modules/@csstools/css-parser-algorithms/CHANGELOG.md
generated
vendored
Normal file
9
node_modules/@csstools/css-parser-algorithms/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Changes to CSS Parser Algorithms
|
||||
|
||||
### 3.0.5
|
||||
|
||||
_May 27, 2025_
|
||||
|
||||
- Updated [`@csstools/css-tokenizer`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer) to [`3.0.4`](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer/CHANGELOG.md#304) (patch)
|
||||
|
||||
[Full CHANGELOG](https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms/CHANGELOG.md)
|
||||
20
node_modules/@csstools/css-parser-algorithms/LICENSE.md
generated
vendored
Normal file
20
node_modules/@csstools/css-parser-algorithms/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2022 Romain Menke, Antonio Laguna <antonio@laguna.es>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
119
node_modules/@csstools/css-parser-algorithms/README.md
generated
vendored
Normal file
119
node_modules/@csstools/css-parser-algorithms/README.md
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
# CSS Parser Algorithms <img src="https://cssdb.org/images/css.svg" alt="for CSS" width="90" height="90" align="right">
|
||||
|
||||
[<img alt="npm version" src="https://img.shields.io/npm/v/@csstools/css-parser-algorithms.svg" height="20">][npm-url]
|
||||
[<img alt="Build Status" src="https://github.com/csstools/postcss-plugins/actions/workflows/test.yml/badge.svg?branch=main" height="20">][cli-url]
|
||||
[<img alt="Discord" src="https://shields.io/badge/Discord-5865F2?logo=discord&logoColor=white">][discord]
|
||||
|
||||
Implemented from : https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/
|
||||
|
||||
## API
|
||||
|
||||
[Read the API docs](./docs/css-parser-algorithms.md)
|
||||
|
||||
## Usage
|
||||
|
||||
Add [CSS Parser Algorithms] to your project:
|
||||
|
||||
```bash
|
||||
npm install @csstools/css-parser-algorithms @csstools/css-tokenizer --save-dev
|
||||
```
|
||||
|
||||
[CSS Parser Algorithms] only accepts tokenized CSS.
|
||||
It must be used together with `@csstools/css-tokenizer`.
|
||||
|
||||
|
||||
```js
|
||||
import { tokenizer, TokenType } from '@csstools/css-tokenizer';
|
||||
import { parseComponentValue } from '@csstools/css-parser-algorithms';
|
||||
|
||||
const myCSS = `@media only screen and (min-width: 768rem) {
|
||||
.foo {
|
||||
content: 'Some content!' !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const t = tokenizer({
|
||||
css: myCSS,
|
||||
});
|
||||
|
||||
const tokens = [];
|
||||
|
||||
{
|
||||
while (!t.endOfFile()) {
|
||||
tokens.push(t.nextToken());
|
||||
}
|
||||
|
||||
tokens.push(t.nextToken()); // EOF-token
|
||||
}
|
||||
|
||||
const options = {
|
||||
onParseError: ((err) => {
|
||||
throw err;
|
||||
}),
|
||||
};
|
||||
|
||||
const result = parseComponentValue(tokens, options);
|
||||
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
### Available functions
|
||||
|
||||
- [`parseComponentValue`](https://www.w3.org/TR/css-syntax-3/#parse-component-value)
|
||||
- [`parseListOfComponentValues`](https://www.w3.org/TR/css-syntax-3/#parse-list-of-component-values)
|
||||
- [`parseCommaSeparatedListOfComponentValues`](https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values)
|
||||
|
||||
### Utilities
|
||||
|
||||
#### `gatherNodeAncestry`
|
||||
|
||||
The AST does not expose the entire ancestry of each node.
|
||||
The walker methods do provide access to the current parent, but also not the entire ancestry.
|
||||
|
||||
To gather the entire ancestry for a a given sub tree of the AST you can use `gatherNodeAncestry`.
|
||||
The result is a `Map` with the child nodes as keys and the parents as values.
|
||||
This allows you to lookup any ancestor of any node.
|
||||
|
||||
```js
|
||||
import { parseComponentValue } from '@csstools/css-parser-algorithms';
|
||||
|
||||
const result = parseComponentValue(tokens, options);
|
||||
const ancestry = gatherNodeAncestry(result);
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```ts
|
||||
{
|
||||
onParseError?: (error: ParseError) => void
|
||||
}
|
||||
```
|
||||
|
||||
#### `onParseError`
|
||||
|
||||
The parser algorithms are forgiving and won't stop when a parse error is encountered.
|
||||
Parse errors also aren't tokens.
|
||||
|
||||
To receive parsing error information you can set a callback.
|
||||
|
||||
Parser errors will try to inform you about the point in the parsing logic the error happened.
|
||||
This tells you the kind of error.
|
||||
|
||||
## Goals and non-goals
|
||||
|
||||
Things this package aims to be:
|
||||
- specification compliant CSS parser
|
||||
- a reliable low level package to be used in CSS sub-grammars
|
||||
|
||||
What it is not:
|
||||
- opinionated
|
||||
- fast
|
||||
- small
|
||||
- a replacement for PostCSS (PostCSS is fast and also an ecosystem)
|
||||
|
||||
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
|
||||
[discord]: https://discord.gg/bUadyRwkJS
|
||||
[npm-url]: https://www.npmjs.com/package/@csstools/css-parser-algorithms
|
||||
|
||||
[CSS Parser Algorithms]: https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms
|
||||
65
node_modules/@csstools/css-parser-algorithms/package.json
generated
vendored
Normal file
65
node_modules/@csstools/css-parser-algorithms/package.json
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "@csstools/css-parser-algorithms",
|
||||
"description": "Algorithms to help you parse CSS from an array of tokens.",
|
||||
"version": "3.0.5",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Antonio Laguna",
|
||||
"email": "antonio@laguna.es",
|
||||
"url": "https://antonio.laguna.es"
|
||||
},
|
||||
{
|
||||
"name": "Romain Menke",
|
||||
"email": "romainmenke@gmail.com"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"require": {
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"CHANGELOG.md",
|
||||
"LICENSE.md",
|
||||
"README.md",
|
||||
"dist"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@csstools/css-tokenizer": "^3.0.4"
|
||||
},
|
||||
"scripts": {},
|
||||
"homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/css-parser-algorithms#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/csstools/postcss-plugins.git",
|
||||
"directory": "packages/css-parser-algorithms"
|
||||
},
|
||||
"bugs": "https://github.com/csstools/postcss-plugins/issues",
|
||||
"keywords": [
|
||||
"css",
|
||||
"parser"
|
||||
]
|
||||
}
|
||||
9
node_modules/@csstools/css-syntax-patches-for-csstree/CHANGELOG.md
generated
vendored
Normal file
9
node_modules/@csstools/css-syntax-patches-for-csstree/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Changes to CSS Syntax Patches For CSSTree
|
||||
|
||||
### 1.0.15
|
||||
|
||||
_October 30, 2025_
|
||||
|
||||
- [Remove unused peer dependency on `postcss`](https://github.com/csstools/postcss-plugins/pull/1708) by @jnoordsij
|
||||
|
||||
[Full CHANGELOG](https://github.com/csstools/postcss-plugins/tree/main/packages/css-syntax-patches-for-csstree/CHANGELOG.md)
|
||||
18
node_modules/@csstools/css-syntax-patches-for-csstree/LICENSE.md
generated
vendored
Normal file
18
node_modules/@csstools/css-syntax-patches-for-csstree/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
MIT No Attribution (MIT-0)
|
||||
|
||||
Copyright © CSSTools Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the “Software”), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
43
node_modules/@csstools/css-syntax-patches-for-csstree/README.md
generated
vendored
Normal file
43
node_modules/@csstools/css-syntax-patches-for-csstree/README.md
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# CSS Syntax Patches For CSSTree <img src="https://cssdb.org/images/css.svg" alt="for CSS" width="90" height="90" align="right">
|
||||
|
||||
[<img alt="npm version" src="https://img.shields.io/npm/v/@csstools/css-syntax-patches-for-csstree.svg" height="20">][npm-url]
|
||||
[<img alt="Build Status" src="https://github.com/csstools/postcss-plugins/actions/workflows/test.yml/badge.svg?branch=main" height="20">][cli-url]
|
||||
|
||||
Patch [csstree](https://github.com/csstree/csstree) syntax definitions with the latest data from CSS specifications.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
npm install @csstools/css-syntax-patches-for-csstree
|
||||
```
|
||||
|
||||
```js
|
||||
import { fork } from 'css-tree';
|
||||
import syntax_patches from '@csstools/css-syntax-patches-for-csstree' with { type: 'json' };
|
||||
|
||||
const forkedLexer = fork({
|
||||
atrules: syntax_patches.next.atrules,
|
||||
properties: syntax_patches.next.properties,
|
||||
types: syntax_patches.next.types,
|
||||
}).lexer;
|
||||
```
|
||||
|
||||
## `next`
|
||||
|
||||
```js
|
||||
import syntax_patches from '@csstools/css-syntax-patches-for-csstree' with { type: 'json' };
|
||||
|
||||
console.log(syntax_patches.next);
|
||||
// ^^^^
|
||||
```
|
||||
|
||||
CSS specifications are often still in flux and various parts might change or disappear altogether.
|
||||
Specifications also contains parts that haven't been implemented yet in a browser.
|
||||
Only CSS that is widely adopted can be expected to be stable.
|
||||
|
||||
The `next` grouping contains a combination of what is currently valid in browsers and the progress in various specifications.
|
||||
|
||||
_In the future more groupings might be added._
|
||||
|
||||
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
|
||||
[npm-url]: https://www.npmjs.com/package/@csstools/css-syntax-patches-for-csstree
|
||||
51
node_modules/@csstools/css-syntax-patches-for-csstree/package.json
generated
vendored
Normal file
51
node_modules/@csstools/css-syntax-patches-for-csstree/package.json
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@csstools/css-syntax-patches-for-csstree",
|
||||
"description": "CSS syntax patches for CSS tree",
|
||||
"version": "1.0.15",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Antonio Laguna",
|
||||
"email": "antonio@laguna.es",
|
||||
"url": "https://antonio.laguna.es"
|
||||
},
|
||||
{
|
||||
"name": "Romain Menke",
|
||||
"email": "romainmenke@gmail.com"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"main": "dist/index.json",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"CHANGELOG.md",
|
||||
"LICENSE.md",
|
||||
"README.md",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {},
|
||||
"homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/css-syntax-patches-for-csstree#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/csstools/postcss-plugins.git",
|
||||
"directory": "packages/css-syntax-patches-for-csstree"
|
||||
},
|
||||
"bugs": "https://github.com/csstools/postcss-plugins/issues",
|
||||
"keywords": [
|
||||
"css",
|
||||
"csstree",
|
||||
"syntax"
|
||||
]
|
||||
}
|
||||
9
node_modules/@csstools/css-tokenizer/CHANGELOG.md
generated
vendored
Normal file
9
node_modules/@csstools/css-tokenizer/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Changes to CSS Tokenizer
|
||||
|
||||
### 3.0.4
|
||||
|
||||
_May 27, 2025_
|
||||
|
||||
- align serializers with CSSOM
|
||||
|
||||
[Full CHANGELOG](https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer/CHANGELOG.md)
|
||||
20
node_modules/@csstools/css-tokenizer/LICENSE.md
generated
vendored
Normal file
20
node_modules/@csstools/css-tokenizer/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2022 Romain Menke, Antonio Laguna <antonio@laguna.es>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
111
node_modules/@csstools/css-tokenizer/README.md
generated
vendored
Normal file
111
node_modules/@csstools/css-tokenizer/README.md
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
# CSS Tokenizer <img src="https://cssdb.org/images/css.svg" alt="for CSS" width="90" height="90" align="right">
|
||||
|
||||
[<img alt="npm version" src="https://img.shields.io/npm/v/@csstools/css-tokenizer.svg" height="20">][npm-url]
|
||||
[<img alt="Build Status" src="https://github.com/csstools/postcss-plugins/actions/workflows/test.yml/badge.svg?branch=main" height="20">][cli-url]
|
||||
[<img alt="Discord" src="https://shields.io/badge/Discord-5865F2?logo=discord&logoColor=white">][discord]
|
||||
|
||||
Implemented from : https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/
|
||||
|
||||
## API
|
||||
|
||||
[Read the API docs](./docs/css-tokenizer.md)
|
||||
|
||||
## Usage
|
||||
|
||||
Add [CSS Tokenizer] to your project:
|
||||
|
||||
```bash
|
||||
npm install @csstools/css-tokenizer --save-dev
|
||||
```
|
||||
|
||||
```js
|
||||
import { tokenizer, TokenType } from '@csstools/css-tokenizer';
|
||||
|
||||
const myCSS = `@media only screen and (min-width: 768rem) {
|
||||
.foo {
|
||||
content: 'Some content!' !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const t = tokenizer({
|
||||
css: myCSS,
|
||||
});
|
||||
|
||||
while (true) {
|
||||
const token = t.nextToken();
|
||||
if (token[0] === TokenType.EOF) {
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(token);
|
||||
}
|
||||
```
|
||||
|
||||
Or use the `tokenize` helper function:
|
||||
|
||||
```js
|
||||
import { tokenize } from '@csstools/css-tokenizer';
|
||||
|
||||
const myCSS = `@media only screen and (min-width: 768rem) {
|
||||
.foo {
|
||||
content: 'Some content!' !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const tokens = tokenize({
|
||||
css: myCSS,
|
||||
});
|
||||
|
||||
console.log(tokens);
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```ts
|
||||
{
|
||||
onParseError?: (error: ParseError) => void
|
||||
}
|
||||
```
|
||||
|
||||
#### `onParseError`
|
||||
|
||||
The tokenizer is forgiving and won't stop when a parse error is encountered.
|
||||
|
||||
To receive parsing error information you can set a callback.
|
||||
|
||||
```js
|
||||
import { tokenizer, TokenType } from '@csstools/css-tokenizer';
|
||||
|
||||
const t = tokenizer({
|
||||
css: '\\',
|
||||
}, { onParseError: (err) => console.warn(err) });
|
||||
|
||||
while (true) {
|
||||
const token = t.nextToken();
|
||||
if (token[0] === TokenType.EOF) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Parser errors will try to inform you where in the tokenizer logic the error happened.
|
||||
This tells you what kind of error occurred.
|
||||
|
||||
## Goals and non-goals
|
||||
|
||||
Things this package aims to be:
|
||||
- specification compliant CSS tokenizer
|
||||
- a reliable low level package to be used in CSS parsers
|
||||
|
||||
What it is not:
|
||||
- opinionated
|
||||
- fast
|
||||
- small
|
||||
|
||||
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
|
||||
[discord]: https://discord.gg/bUadyRwkJS
|
||||
[npm-url]: https://www.npmjs.com/package/@csstools/css-tokenizer
|
||||
|
||||
[CSS Tokenizer]: https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer
|
||||
62
node_modules/@csstools/css-tokenizer/package.json
generated
vendored
Normal file
62
node_modules/@csstools/css-tokenizer/package.json
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@csstools/css-tokenizer",
|
||||
"description": "Tokenize CSS",
|
||||
"version": "3.0.4",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Antonio Laguna",
|
||||
"email": "antonio@laguna.es",
|
||||
"url": "https://antonio.laguna.es"
|
||||
},
|
||||
{
|
||||
"name": "Romain Menke",
|
||||
"email": "romainmenke@gmail.com"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"require": {
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"CHANGELOG.md",
|
||||
"LICENSE.md",
|
||||
"README.md",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {},
|
||||
"homepage": "https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/csstools/postcss-plugins.git",
|
||||
"directory": "packages/css-tokenizer"
|
||||
},
|
||||
"bugs": "https://github.com/csstools/postcss-plugins/issues",
|
||||
"keywords": [
|
||||
"css",
|
||||
"tokenizer"
|
||||
]
|
||||
}
|
||||
3
node_modules/@unrs/resolver-binding-linux-x64-musl/README.md
generated
vendored
Normal file
3
node_modules/@unrs/resolver-binding-linux-x64-musl/README.md
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# `@unrs/resolver-binding-linux-x64-musl`
|
||||
|
||||
This is the **x86_64-unknown-linux-musl** binary for `@unrs/resolver-binding`
|
||||
26
node_modules/@unrs/resolver-binding-linux-x64-musl/package.json
generated
vendored
Normal file
26
node_modules/@unrs/resolver-binding-linux-x64-musl/package.json
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@unrs/resolver-binding-linux-x64-musl",
|
||||
"version": "1.11.1",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"main": "resolver.linux-x64-musl.node",
|
||||
"files": [
|
||||
"resolver.linux-x64-musl.node"
|
||||
],
|
||||
"description": "UnRS Resolver Node API with PNP support",
|
||||
"author": "JounQin <admin@1stg.me> (https://www.1stG.me)",
|
||||
"homepage": "https://github.com/unrs/unrs-resolver#readme",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org",
|
||||
"access": "public"
|
||||
},
|
||||
"repository": "git+https://github.com/unrs/unrs-resolver.git",
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
]
|
||||
}
|
||||
BIN
node_modules/@unrs/resolver-binding-linux-x64-musl/resolver.linux-x64-musl.node
generated
vendored
Normal file
BIN
node_modules/@unrs/resolver-binding-linux-x64-musl/resolver.linux-x64-musl.node
generated
vendored
Normal file
Binary file not shown.
22
node_modules/agent-base/LICENSE
generated
vendored
Normal file
22
node_modules/agent-base/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
69
node_modules/agent-base/README.md
generated
vendored
Normal file
69
node_modules/agent-base/README.md
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
agent-base
|
||||
==========
|
||||
### Turn a function into an [`http.Agent`][http.Agent] instance
|
||||
|
||||
This module is a thin wrapper around the base `http.Agent` class.
|
||||
|
||||
It provides an abstract class that must define a `connect()` function,
|
||||
which is responsible for creating the underlying socket that the HTTP
|
||||
client requests will use.
|
||||
|
||||
The `connect()` function may return an arbitrary `Duplex` stream, or
|
||||
another `http.Agent` instance to delegate the request to, and may be
|
||||
asynchronous (by defining an `async` function).
|
||||
|
||||
Instances of this agent can be used with the `http` and `https`
|
||||
modules. To differentiate, the options parameter in the `connect()`
|
||||
function includes a `secureEndpoint` property, which can be checked
|
||||
to determine what type of socket should be returned.
|
||||
|
||||
#### Some subclasses:
|
||||
|
||||
Here are some more interesting uses of `agent-base`.
|
||||
Send a pull request to list yours!
|
||||
|
||||
* [`http-proxy-agent`][http-proxy-agent]: An HTTP(s) proxy `http.Agent` implementation for HTTP endpoints
|
||||
* [`https-proxy-agent`][https-proxy-agent]: An HTTP(s) proxy `http.Agent` implementation for HTTPS endpoints
|
||||
* [`pac-proxy-agent`][pac-proxy-agent]: A PAC file proxy `http.Agent` implementation for HTTP and HTTPS
|
||||
* [`socks-proxy-agent`][socks-proxy-agent]: A SOCKS proxy `http.Agent` implementation for HTTP and HTTPS
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Here's a minimal example that creates a new `net.Socket` or `tls.Socket`
|
||||
based on the `secureEndpoint` property. This agent can be used with both
|
||||
the `http` and `https` modules.
|
||||
|
||||
```ts
|
||||
import * as net from 'net';
|
||||
import * as tls from 'tls';
|
||||
import * as http from 'http';
|
||||
import { Agent } from 'agent-base';
|
||||
|
||||
class MyAgent extends Agent {
|
||||
connect(req, opts) {
|
||||
// `secureEndpoint` is true when using the "https" module
|
||||
if (opts.secureEndpoint) {
|
||||
return tls.connect(opts);
|
||||
} else {
|
||||
return net.connect(opts);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Keep alive enabled means that `connect()` will only be
|
||||
// invoked when a new connection needs to be created
|
||||
const agent = new MyAgent({ keepAlive: true });
|
||||
|
||||
// Pass the `agent` option when creating the HTTP request
|
||||
http.get('http://nodejs.org/api/', { agent }, (res) => {
|
||||
console.log('"response" event!', res.headers);
|
||||
res.pipe(process.stdout);
|
||||
});
|
||||
```
|
||||
|
||||
[http-proxy-agent]: ../http-proxy-agent
|
||||
[https-proxy-agent]: ../https-proxy-agent
|
||||
[pac-proxy-agent]: ../pac-proxy-agent
|
||||
[socks-proxy-agent]: ../socks-proxy-agent
|
||||
[http.Agent]: https://nodejs.org/api/http.html#http_class_http_agent
|
||||
46
node_modules/agent-base/package.json
generated
vendored
Normal file
46
node_modules/agent-base/package.json
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "agent-base",
|
||||
"version": "7.1.4",
|
||||
"description": "Turn a function into an `http.Agent` instance",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/TooTallNate/proxy-agents.git",
|
||||
"directory": "packages/agent-base"
|
||||
},
|
||||
"keywords": [
|
||||
"http",
|
||||
"agent",
|
||||
"base",
|
||||
"barebones",
|
||||
"https"
|
||||
],
|
||||
"author": "Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/)",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/jest": "^29.5.1",
|
||||
"@types/node": "^14.18.45",
|
||||
"@types/semver": "^7.3.13",
|
||||
"@types/ws": "^6.0.4",
|
||||
"async-listen": "^3.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.0.4",
|
||||
"ws": "^5.2.4",
|
||||
"tsconfig": "0.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest --env node --verbose --bail",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"pack": "node ../../scripts/pack.mjs"
|
||||
}
|
||||
}
|
||||
22
node_modules/bidi-js/LICENSE.txt
generated
vendored
Normal file
22
node_modules/bidi-js/LICENSE.txt
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2021 Jason Johnston
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
133
node_modules/bidi-js/README.md
generated
vendored
Normal file
133
node_modules/bidi-js/README.md
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
# bidi-js
|
||||
|
||||
This is a pure JavaScript implementation of the [Unicode Bidirectional Algorithm](https://www.unicode.org/reports/tr9/) version 13.0.0. Its goals, in no particular order, are to be:
|
||||
|
||||
* Correct
|
||||
* Small
|
||||
* Fast
|
||||
|
||||
|
||||
## Conformance
|
||||
|
||||
This implementation currently conforms to section [UAX-C1](https://unicode.org/reports/tr9/#C1) of the bidi spec, as verified by running all the provided [conformance tests](https://unicode.org/reports/tr9/#Bidi_Conformance_Testing).
|
||||
|
||||
## Compatibility
|
||||
|
||||
It has no external dependencies and therefore should run just fine in any relatively capable web browser, Node.js, etc. The provided distribution `.js` files are valid ES5.
|
||||
|
||||
## Usage
|
||||
|
||||
Install it from npm:
|
||||
|
||||
```shell
|
||||
npm install bidi-js
|
||||
```
|
||||
|
||||
[](https://npmjs.org/package/bidi-js)
|
||||
|
||||
Import and initialize:
|
||||
|
||||
```js
|
||||
import bidiFactory from 'bidi-js'
|
||||
// or: const bidiFactory = require('bidi-js')
|
||||
|
||||
const bidi = bidiFactory()
|
||||
```
|
||||
|
||||
The `bidi-js` package's only export is a factory function which you _must invoke_ to return a `bidi` object; that object exposes the methods for bidi processing.
|
||||
|
||||
(_Why a factory function?_ The main reason is to ensure the entire module's code is wrapped within a single self-contained function with no closure dependencies. This enables that function to be stringified and passed into a web worker, for example.)
|
||||
|
||||
Now that you have the `bidi` object, you can:
|
||||
|
||||
### Calculate bidi embedding levels
|
||||
|
||||
```js
|
||||
const embeddingLevels = bidi.getEmbeddingLevels(
|
||||
text, //the input string containing mixed-direction text
|
||||
explicitDirection //"ltr" or "rtl" if you don't want to auto-detect it
|
||||
)
|
||||
|
||||
const { levels, paragraphs } = embeddingLevels
|
||||
```
|
||||
|
||||
The result object `embeddingLevels` will usually be passed to other functions described below. Its contents, should you need to inspect them individually, are:
|
||||
|
||||
* `levels` is a `Uint8Array` holding the calculated [bidi embedding levels](https://unicode.org/reports/tr9/#BD2) for each character in the string. The most important thing to know about these levels is that any given character is in a right-to-left scope if its embedding level is an odd number, and left-to-right if it's an even number.
|
||||
|
||||
* `paragraphs` is an array of `{start, end, level}` objects, one for each paragraph in the text (paragraphs are separated by explicit breaking characters, not soft line wrapping). The `start` and `end` indices are inclusive, and `level` is the resolved base embedding level of that paragraph.
|
||||
|
||||
### Calculate character reorderings
|
||||
|
||||
```js
|
||||
const flips = bidi.getReorderSegments(
|
||||
text, //the full input string
|
||||
embeddingLevels //the full result object from getEmbeddingLevels
|
||||
)
|
||||
|
||||
// Process all reversal sequences, in order:
|
||||
flips.forEach(range => {
|
||||
const [start, end] = range
|
||||
// Reverse this sequence of characters from start to end, inclusive
|
||||
for (let i = start; i <= end; i++) {
|
||||
//...
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Each "flip" is a range that should be reversed in place; they must all be applied in order.
|
||||
|
||||
Sometimes you don't want to process the whole string at once, but just a particular substring. A common example would be if you've applied line wrapping, in which case you need to process each line individually (in particular this does some special handling for trailing whitespace for each line). For this you can pass the extra `start` and `end` parameters:
|
||||
|
||||
```js
|
||||
yourWrappedLines.forEach(([lineStart, lineEnd]) => {
|
||||
const flips = bidi.getReorderSegments(
|
||||
text,
|
||||
embeddingLevels,
|
||||
lineStart,
|
||||
lineEnd //inclusive
|
||||
)
|
||||
// ...process flips for this line
|
||||
})
|
||||
```
|
||||
|
||||
### Handle right-to-left mirrored characters
|
||||
|
||||
Some characters that resolve to right-to-left need to be swapped with their "mirrored" characters. Examples of this are opening/closing parentheses. You can determine all the characters that need to be mirrored like so:
|
||||
|
||||
```js
|
||||
const mirrored = bidi.getMirroredCharactersMap(
|
||||
text,
|
||||
embeddingLevels
|
||||
)
|
||||
```
|
||||
|
||||
This returns a `Map` of numeric character indices to replacement characters.
|
||||
|
||||
You can also process just a substring with extra `start` and `end` parameters:
|
||||
|
||||
```js
|
||||
const mirrored = bidi.getMirroredCharactersMap(
|
||||
text,
|
||||
embeddingLevels,
|
||||
start,
|
||||
end //inclusive
|
||||
)
|
||||
```
|
||||
|
||||
If you'd rather process mirrored characters individually, you can use the single `getMirroredCharacter` function, just make sure you only do it for right-to-left characters (those whose embedding level is an odd number.) It will return `null` if the character doesn't support mirroring.
|
||||
|
||||
```js
|
||||
const mirroredChar = (embeddingLevels.levels[charIndex] & 1) //odd number means RTL
|
||||
? bidi.getMirroredCharacter(text[charIndex])
|
||||
: null
|
||||
```
|
||||
|
||||
### Get a character's bidi type
|
||||
|
||||
This is used internally, but you can also ask for the ["bidi character type"](https://unicode.org/reports/tr9/#BD1) of any character, should you need it:
|
||||
|
||||
```js
|
||||
const bidiType = bidi.getBidiCharTypeName(string[charIndex])
|
||||
// e.g. "L", "R", "AL", "NSM", ...
|
||||
```
|
||||
39
node_modules/bidi-js/package.json
generated
vendored
Normal file
39
node_modules/bidi-js/package.json
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "bidi-js",
|
||||
"version": "1.0.3",
|
||||
"description": "A JavaScript implementation of the Unicode Bidirectional Algorithm",
|
||||
"main": "dist/bidi.js",
|
||||
"module": "dist/bidi.mjs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/lojjic/bidi-js.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rollup -c rollup.config.js",
|
||||
"test": "npx babel-node --plugins @babel/plugin-transform-modules-commonjs test/runTestsOnSrc.js",
|
||||
"test-build": "node test/runTestsOnBuild.js"
|
||||
},
|
||||
"author": "Jason Johnston",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.13.16",
|
||||
"@babel/core": "^7.14.0",
|
||||
"@babel/node": "^7.13.13",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.13.8",
|
||||
"@babel/preset-env": "^7.14.0",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@rollup/plugin-buble": "^0.21.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
"rollup": "^2.45.1",
|
||||
"rollup-plugin-terser": "^7.0.2"
|
||||
},
|
||||
"files": [
|
||||
"/dist",
|
||||
"/src",
|
||||
"/LICENSE.txt",
|
||||
"/README.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
}
|
||||
30
node_modules/bidi-js/src/brackets.js
generated
vendored
Normal file
30
node_modules/bidi-js/src/brackets.js
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import data from './data/bidiBrackets.data.js'
|
||||
import { parseCharacterMap } from './util/parseCharacterMap.js'
|
||||
|
||||
let openToClose, closeToOpen, canonical
|
||||
|
||||
function parse () {
|
||||
if (!openToClose) {
|
||||
//const start = performance.now()
|
||||
let { map, reverseMap } = parseCharacterMap(data.pairs, true)
|
||||
openToClose = map
|
||||
closeToOpen = reverseMap
|
||||
canonical = parseCharacterMap(data.canonical, false).map
|
||||
//console.log(`brackets parsed in ${performance.now() - start}ms`)
|
||||
}
|
||||
}
|
||||
|
||||
export function openingToClosingBracket (char) {
|
||||
parse()
|
||||
return openToClose.get(char) || null
|
||||
}
|
||||
|
||||
export function closingToOpeningBracket (char) {
|
||||
parse()
|
||||
return closeToOpen.get(char) || null
|
||||
}
|
||||
|
||||
export function getCanonicalBracket (char) {
|
||||
parse()
|
||||
return canonical.get(char) || null
|
||||
}
|
||||
66
node_modules/bidi-js/src/charTypes.js
generated
vendored
Normal file
66
node_modules/bidi-js/src/charTypes.js
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
import DATA from './data/bidiCharTypes.data.js'
|
||||
|
||||
const TYPES = {}
|
||||
const TYPES_TO_NAMES = {}
|
||||
TYPES.L = 1 //L is the default
|
||||
TYPES_TO_NAMES[1] = 'L'
|
||||
Object.keys(DATA).forEach((type, i) => {
|
||||
TYPES[type] = 1 << (i + 1)
|
||||
TYPES_TO_NAMES[TYPES[type]] = type
|
||||
})
|
||||
Object.freeze(TYPES)
|
||||
|
||||
const ISOLATE_INIT_TYPES = TYPES.LRI | TYPES.RLI | TYPES.FSI
|
||||
const STRONG_TYPES = TYPES.L | TYPES.R | TYPES.AL
|
||||
const NEUTRAL_ISOLATE_TYPES = TYPES.B | TYPES.S | TYPES.WS | TYPES.ON | TYPES.FSI | TYPES.LRI | TYPES.RLI | TYPES.PDI
|
||||
const BN_LIKE_TYPES = TYPES.BN | TYPES.RLE | TYPES.LRE | TYPES.RLO | TYPES.LRO | TYPES.PDF
|
||||
const TRAILING_TYPES = TYPES.S | TYPES.WS | TYPES.B | ISOLATE_INIT_TYPES | TYPES.PDI | BN_LIKE_TYPES
|
||||
|
||||
let map = null
|
||||
|
||||
function parseData () {
|
||||
if (!map) {
|
||||
//const start = performance.now()
|
||||
map = new Map()
|
||||
for (let type in DATA) {
|
||||
if (DATA.hasOwnProperty(type)) {
|
||||
let lastCode = 0
|
||||
DATA[type].split(',').forEach(range => {
|
||||
let [skip, step] = range.split('+')
|
||||
skip = parseInt(skip, 36)
|
||||
step = step ? parseInt(step, 36) : 0
|
||||
map.set(lastCode += skip, TYPES[type])
|
||||
for (let i = 0; i < step; i++) {
|
||||
map.set(++lastCode, TYPES[type])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
//console.log(`char types parsed in ${performance.now() - start}ms`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} char
|
||||
* @return {number}
|
||||
*/
|
||||
function getBidiCharType (char) {
|
||||
parseData()
|
||||
return map.get(char.codePointAt(0)) || TYPES.L
|
||||
}
|
||||
|
||||
function getBidiCharTypeName(char) {
|
||||
return TYPES_TO_NAMES[getBidiCharType(char)]
|
||||
}
|
||||
|
||||
export {
|
||||
getBidiCharType,
|
||||
getBidiCharTypeName,
|
||||
TYPES,
|
||||
TYPES_TO_NAMES,
|
||||
ISOLATE_INIT_TYPES,
|
||||
STRONG_TYPES,
|
||||
NEUTRAL_ISOLATE_TYPES,
|
||||
BN_LIKE_TYPES,
|
||||
TRAILING_TYPES
|
||||
}
|
||||
5
node_modules/bidi-js/src/data/bidiBrackets.data.js
generated
vendored
Normal file
5
node_modules/bidi-js/src/data/bidiBrackets.data.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// Bidi bracket pairs data, auto generated
|
||||
export default {
|
||||
"pairs": "14>1,1e>2,u>2,2wt>1,1>1,1ge>1,1wp>1,1j>1,f>1,hm>1,1>1,u>1,u6>1,1>1,+5,28>1,w>1,1>1,+3,b8>1,1>1,+3,1>3,-1>-1,3>1,1>1,+2,1s>1,1>1,x>1,th>1,1>1,+2,db>1,1>1,+3,3>1,1>1,+2,14qm>1,1>1,+1,4q>1,1e>2,u>2,2>1,+1",
|
||||
"canonical": "6f1>-6dx,6dy>-6dx,6ec>-6ed,6ee>-6ed,6ww>2jj,-2ji>2jj,14r4>-1e7l,1e7m>-1e7l,1e7m>-1e5c,1e5d>-1e5b,1e5c>-14qx,14qy>-14qx,14vn>-1ecg,1ech>-1ecg,1edu>-1ecg,1eci>-1ecg,1eda>-1ecg,1eci>-1ecg,1eci>-168q,168r>-168q,168s>-14ye,14yf>-14ye"
|
||||
}
|
||||
25
node_modules/bidi-js/src/data/bidiCharTypes.data.js
generated
vendored
Normal file
25
node_modules/bidi-js/src/data/bidiCharTypes.data.js
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// Bidi character types data, auto generated
|
||||
export default {
|
||||
"R": "13k,1a,2,3,3,2+1j,ch+16,a+1,5+2,2+n,5,a,4,6+16,4+3,h+1b,4mo,179q,2+9,2+11,2i9+7y,2+68,4,3+4,5+13,4+3,2+4k,3+29,8+cf,1t+7z,w+17,3+3m,1t+3z,16o1+5r,8+30,8+mc,29+1r,29+4v,75+73",
|
||||
"EN": "1c+9,3d+1,6,187+9,513,4+5,7+9,sf+j,175h+9,qw+q,161f+1d,4xt+a,25i+9",
|
||||
"ES": "17,2,6dp+1,f+1,av,16vr,mx+1,4o,2",
|
||||
"ET": "z+2,3h+3,b+1,ym,3e+1,2o,p4+1,8,6u,7c,g6,1wc,1n9+4,30+1b,2n,6d,qhx+1,h0m,a+1,49+2,63+1,4+1,6bb+3,12jj",
|
||||
"AN": "16o+5,2j+9,2+1,35,ed,1ff2+9,87+u",
|
||||
"CS": "18,2+1,b,2u,12k,55v,l,17v0,2,3,53,2+1,b",
|
||||
"B": "a,3,f+2,2v,690",
|
||||
"S": "9,2,k",
|
||||
"WS": "c,k,4f4,1vk+a,u,1j,335",
|
||||
"ON": "x+1,4+4,h+5,r+5,r+3,z,5+3,2+1,2+1,5,2+2,3+4,o,w,ci+1,8+d,3+d,6+8,2+g,39+1,9,6+1,2,33,b8,3+1,3c+1,7+1,5r,b,7h+3,sa+5,2,3i+6,jg+3,ur+9,2v,ij+1,9g+9,7+a,8m,4+1,49+x,14u,2+2,c+2,e+2,e+2,e+1,i+n,e+e,2+p,u+2,e+2,36+1,2+3,2+1,b,2+2,6+5,2,2,2,h+1,5+4,6+3,3+f,16+2,5+3l,3+81,1y+p,2+40,q+a,m+13,2r+ch,2+9e,75+hf,3+v,2+2w,6e+5,f+6,75+2a,1a+p,2+2g,d+5x,r+b,6+3,4+o,g,6+1,6+2,2k+1,4,2j,5h+z,1m+1,1e+f,t+2,1f+e,d+3,4o+3,2s+1,w,535+1r,h3l+1i,93+2,2s,b+1,3l+x,2v,4g+3,21+3,kz+1,g5v+1,5a,j+9,n+v,2,3,2+8,2+1,3+2,2,3,46+1,4+4,h+5,r+5,r+a,3h+2,4+6,b+4,78,1r+24,4+c,4,1hb,ey+6,103+j,16j+c,1ux+7,5+g,fsh,jdq+1t,4,57+2e,p1,1m,1m,1m,1m,4kt+1,7j+17,5+2r,d+e,3+e,2+e,2+10,m+4,w,1n+5,1q,4z+5,4b+rb,9+c,4+c,4+37,d+2g,8+b,l+b,5+1j,9+9,7+13,9+t,3+1,27+3c,2+29,2+3q,d+d,3+4,4+2,6+6,a+o,8+6,a+2,e+6,16+42,2+1i",
|
||||
"BN": "0+8,6+d,2s+5,2+p,e,4m9,1kt+2,2b+5,5+5,17q9+v,7k,6p+8,6+1,119d+3,440+7,96s+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+75,6p+2rz,1ben+1,1ekf+1,1ekf+1",
|
||||
"NSM": "lc+33,7o+6,7c+18,2,2+1,2+1,2,21+a,1d+k,h,2u+6,3+5,3+1,2+3,10,v+q,2k+a,1n+8,a,p+3,2+8,2+2,2+4,18+2,3c+e,2+v,1k,2,5+7,5,4+6,b+1,u,1n,5+3,9,l+1,r,3+1,1m,5+1,5+1,3+2,4,v+1,4,c+1,1m,5+4,2+1,5,l+1,n+5,2,1n,3,2+3,9,8+1,c+1,v,1q,d,1f,4,1m+2,6+2,2+3,8+1,c+1,u,1n,g+1,l+1,t+1,1m+1,5+3,9,l+1,u,21,8+2,2,2j,3+6,d+7,2r,3+8,c+5,23+1,s,2,2,1k+d,2+4,2+1,6+a,2+z,a,2v+3,2+5,2+1,3+1,q+1,5+2,h+3,e,3+1,7,g,jk+2,qb+2,u+2,u+1,v+1,1t+1,2+6,9,3+a,a,1a+2,3c+1,z,3b+2,5+1,a,7+2,64+1,3,1n,2+6,2,2,3+7,7+9,3,1d+g,1s+3,1d,2+4,2,6,15+8,d+1,x+3,3+1,2+2,1l,2+1,4,2+2,1n+7,3+1,49+2,2+c,2+6,5,7,4+1,5j+1l,2+4,k1+w,2db+2,3y,2p+v,ff+3,30+1,n9x+3,2+9,x+1,29+1,7l,4,5,q+1,6,48+1,r+h,e,13+7,q+a,1b+2,1d,3+3,3+1,14,1w+5,3+1,3+1,d,9,1c,1g,2+2,3+1,6+1,2,17+1,9,6n,3,5,fn5,ki+f,h+f,r2,6b,46+4,1af+2,2+1,6+3,15+2,5,4m+1,fy+3,as+1,4a+a,4x,1j+e,1l+2,1e+3,3+1,1y+2,11+4,2+7,1r,d+1,1h+8,b+3,3,2o+2,3,2+1,7,4h,4+7,m+1,1m+1,4,12+6,4+4,5g+7,3+2,2,o,2d+5,2,5+1,2+1,6n+3,7+1,2+1,s+1,2e+7,3,2+1,2z,2,3+5,2,2u+2,3+3,2+4,78+8,2+1,75+1,2,5,41+3,3+1,5,x+5,3+1,15+5,3+3,9,a+5,3+2,1b+c,2+1,bb+6,2+5,2d+l,3+6,2+1,2+1,3f+5,4,2+1,2+6,2,21+1,4,2,9o+1,f0c+4,1o+6,t5,1s+3,2a,f5l+1,43t+2,i+7,3+6,v+3,45+2,1j0+1i,5+1d,9,f,n+4,2+e,11t+6,2+g,3+6,2+1,2+4,7a+6,c6+3,15t+6,32+6,gzhy+6n",
|
||||
"AL": "16w,3,2,e+1b,z+2,2+2s,g+1,8+1,b+m,2+t,s+2i,c+e,4h+f,1d+1e,1bwe+dp,3+3z,x+c,2+1,35+3y,2rm+z,5+7,b+5,dt+l,c+u,17nl+27,1t+27,4x+6n,3+d",
|
||||
"LRO": "6ct",
|
||||
"RLO": "6cu",
|
||||
"LRE": "6cq",
|
||||
"RLE": "6cr",
|
||||
"PDF": "6cs",
|
||||
"LRI": "6ee",
|
||||
"RLI": "6ef",
|
||||
"FSI": "6eg",
|
||||
"PDI": "6eh"
|
||||
}
|
||||
2
node_modules/bidi-js/src/data/bidiMirroring.data.js
generated
vendored
Normal file
2
node_modules/bidi-js/src/data/bidiMirroring.data.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// Bidi mirrored chars data, auto generated
|
||||
export default "14>1,j>2,t>2,u>2,1a>g,2v3>1,1>1,1ge>1,1wd>1,b>1,1j>1,f>1,ai>3,-2>3,+1,8>1k0,-1jq>1y7,-1y6>1hf,-1he>1h6,-1h5>1ha,-1h8>1qi,-1pu>1,6>3u,-3s>7,6>1,1>1,f>1,1>1,+2,3>1,1>1,+13,4>1,1>1,6>1eo,-1ee>1,3>1mg,-1me>1mk,-1mj>1mi,-1mg>1mi,-1md>1,1>1,+2,1>10k,-103>1,1>1,4>1,5>1,1>1,+10,3>1,1>8,-7>8,+1,-6>7,+1,a>1,1>1,u>1,u6>1,1>1,+5,26>1,1>1,2>1,2>2,8>1,7>1,4>1,1>1,+5,b8>1,1>1,+3,1>3,-2>1,2>1,1>1,+2,c>1,3>1,1>1,+2,h>1,3>1,a>1,1>1,2>1,3>1,1>1,d>1,f>1,3>1,1a>1,1>1,6>1,7>1,13>1,k>1,1>1,+19,4>1,1>1,+2,2>1,1>1,+18,m>1,a>1,1>1,lk>1,1>1,4>1,2>1,f>1,3>1,1>1,+3,db>1,1>1,+3,3>1,1>1,+2,14qm>1,1>1,+1,6>1,4j>1,j>2,t>2,u>2,2>1,+1"
|
||||
690
node_modules/bidi-js/src/embeddingLevels.js
generated
vendored
Normal file
690
node_modules/bidi-js/src/embeddingLevels.js
generated
vendored
Normal file
@@ -0,0 +1,690 @@
|
||||
import {
|
||||
BN_LIKE_TYPES,
|
||||
getBidiCharType,
|
||||
ISOLATE_INIT_TYPES,
|
||||
NEUTRAL_ISOLATE_TYPES,
|
||||
STRONG_TYPES,
|
||||
TRAILING_TYPES,
|
||||
TYPES
|
||||
} from './charTypes.js'
|
||||
import { closingToOpeningBracket, getCanonicalBracket, openingToClosingBracket } from './brackets.js'
|
||||
|
||||
// Local type aliases
|
||||
const {
|
||||
L: TYPE_L,
|
||||
R: TYPE_R,
|
||||
EN: TYPE_EN,
|
||||
ES: TYPE_ES,
|
||||
ET: TYPE_ET,
|
||||
AN: TYPE_AN,
|
||||
CS: TYPE_CS,
|
||||
B: TYPE_B,
|
||||
S: TYPE_S,
|
||||
ON: TYPE_ON,
|
||||
BN: TYPE_BN,
|
||||
NSM: TYPE_NSM,
|
||||
AL: TYPE_AL,
|
||||
LRO: TYPE_LRO,
|
||||
RLO: TYPE_RLO,
|
||||
LRE: TYPE_LRE,
|
||||
RLE: TYPE_RLE,
|
||||
PDF: TYPE_PDF,
|
||||
LRI: TYPE_LRI,
|
||||
RLI: TYPE_RLI,
|
||||
FSI: TYPE_FSI,
|
||||
PDI: TYPE_PDI
|
||||
} = TYPES
|
||||
|
||||
/**
|
||||
* @typedef {object} GetEmbeddingLevelsResult
|
||||
* @property {{start, end, level}[]} paragraphs
|
||||
* @property {Uint8Array} levels
|
||||
*/
|
||||
|
||||
/**
|
||||
* This function applies the Bidirectional Algorithm to a string, returning the resolved embedding levels
|
||||
* in a single Uint8Array plus a list of objects holding each paragraph's start and end indices and resolved
|
||||
* base embedding level.
|
||||
*
|
||||
* @param {string} string - The input string
|
||||
* @param {"ltr"|"rtl"|"auto"} [baseDirection] - Use "ltr" or "rtl" to force a base paragraph direction,
|
||||
* otherwise a direction will be chosen automatically from each paragraph's contents.
|
||||
* @return {GetEmbeddingLevelsResult}
|
||||
*/
|
||||
export function getEmbeddingLevels (string, baseDirection) {
|
||||
const MAX_DEPTH = 125
|
||||
|
||||
// Start by mapping all characters to their unicode type, as a bitmask integer
|
||||
const charTypes = new Uint32Array(string.length)
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
charTypes[i] = getBidiCharType(string[i])
|
||||
}
|
||||
|
||||
const charTypeCounts = new Map() //will be cleared at start of each paragraph
|
||||
function changeCharType(i, type) {
|
||||
const oldType = charTypes[i]
|
||||
charTypes[i] = type
|
||||
charTypeCounts.set(oldType, charTypeCounts.get(oldType) - 1)
|
||||
if (oldType & NEUTRAL_ISOLATE_TYPES) {
|
||||
charTypeCounts.set(NEUTRAL_ISOLATE_TYPES, charTypeCounts.get(NEUTRAL_ISOLATE_TYPES) - 1)
|
||||
}
|
||||
charTypeCounts.set(type, (charTypeCounts.get(type) || 0) + 1)
|
||||
if (type & NEUTRAL_ISOLATE_TYPES) {
|
||||
charTypeCounts.set(NEUTRAL_ISOLATE_TYPES, (charTypeCounts.get(NEUTRAL_ISOLATE_TYPES) || 0) + 1)
|
||||
}
|
||||
}
|
||||
|
||||
const embedLevels = new Uint8Array(string.length)
|
||||
const isolationPairs = new Map() //init->pdi and pdi->init
|
||||
|
||||
// === 3.3.1 The Paragraph Level ===
|
||||
// 3.3.1 P1: Split the text into paragraphs
|
||||
const paragraphs = [] // [{start, end, level}, ...]
|
||||
let paragraph = null
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
if (!paragraph) {
|
||||
paragraphs.push(paragraph = {
|
||||
start: i,
|
||||
end: string.length - 1,
|
||||
// 3.3.1 P2-P3: Determine the paragraph level
|
||||
level: baseDirection === 'rtl' ? 1 : baseDirection === 'ltr' ? 0 : determineAutoEmbedLevel(i, false)
|
||||
})
|
||||
}
|
||||
if (charTypes[i] & TYPE_B) {
|
||||
paragraph.end = i
|
||||
paragraph = null
|
||||
}
|
||||
}
|
||||
|
||||
const FORMATTING_TYPES = TYPE_RLE | TYPE_LRE | TYPE_RLO | TYPE_LRO | ISOLATE_INIT_TYPES | TYPE_PDI | TYPE_PDF | TYPE_B
|
||||
const nextEven = n => n + ((n & 1) ? 1 : 2)
|
||||
const nextOdd = n => n + ((n & 1) ? 2 : 1)
|
||||
|
||||
// Everything from here on will operate per paragraph.
|
||||
for (let paraIdx = 0; paraIdx < paragraphs.length; paraIdx++) {
|
||||
paragraph = paragraphs[paraIdx]
|
||||
const statusStack = [{
|
||||
_level: paragraph.level,
|
||||
_override: 0, //0=neutral, 1=L, 2=R
|
||||
_isolate: 0 //bool
|
||||
}]
|
||||
let stackTop
|
||||
let overflowIsolateCount = 0
|
||||
let overflowEmbeddingCount = 0
|
||||
let validIsolateCount = 0
|
||||
charTypeCounts.clear()
|
||||
|
||||
// === 3.3.2 Explicit Levels and Directions ===
|
||||
for (let i = paragraph.start; i <= paragraph.end; i++) {
|
||||
let charType = charTypes[i]
|
||||
stackTop = statusStack[statusStack.length - 1]
|
||||
|
||||
// Set initial counts
|
||||
charTypeCounts.set(charType, (charTypeCounts.get(charType) || 0) + 1)
|
||||
if (charType & NEUTRAL_ISOLATE_TYPES) {
|
||||
charTypeCounts.set(NEUTRAL_ISOLATE_TYPES, (charTypeCounts.get(NEUTRAL_ISOLATE_TYPES) || 0) + 1)
|
||||
}
|
||||
|
||||
// Explicit Embeddings: 3.3.2 X2 - X3
|
||||
if (charType & FORMATTING_TYPES) { //prefilter all formatters
|
||||
if (charType & (TYPE_RLE | TYPE_LRE)) {
|
||||
embedLevels[i] = stackTop._level // 5.2
|
||||
const level = (charType === TYPE_RLE ? nextOdd : nextEven)(stackTop._level)
|
||||
if (level <= MAX_DEPTH && !overflowIsolateCount && !overflowEmbeddingCount) {
|
||||
statusStack.push({
|
||||
_level: level,
|
||||
_override: 0,
|
||||
_isolate: 0
|
||||
})
|
||||
} else if (!overflowIsolateCount) {
|
||||
overflowEmbeddingCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit Overrides: 3.3.2 X4 - X5
|
||||
else if (charType & (TYPE_RLO | TYPE_LRO)) {
|
||||
embedLevels[i] = stackTop._level // 5.2
|
||||
const level = (charType === TYPE_RLO ? nextOdd : nextEven)(stackTop._level)
|
||||
if (level <= MAX_DEPTH && !overflowIsolateCount && !overflowEmbeddingCount) {
|
||||
statusStack.push({
|
||||
_level: level,
|
||||
_override: (charType & TYPE_RLO) ? TYPE_R : TYPE_L,
|
||||
_isolate: 0
|
||||
})
|
||||
} else if (!overflowIsolateCount) {
|
||||
overflowEmbeddingCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Isolates: 3.3.2 X5a - X5c
|
||||
else if (charType & ISOLATE_INIT_TYPES) {
|
||||
// X5c - FSI becomes either RLI or LRI
|
||||
if (charType & TYPE_FSI) {
|
||||
charType = determineAutoEmbedLevel(i + 1, true) === 1 ? TYPE_RLI : TYPE_LRI
|
||||
}
|
||||
|
||||
embedLevels[i] = stackTop._level
|
||||
if (stackTop._override) {
|
||||
changeCharType(i, stackTop._override)
|
||||
}
|
||||
const level = (charType === TYPE_RLI ? nextOdd : nextEven)(stackTop._level)
|
||||
if (level <= MAX_DEPTH && overflowIsolateCount === 0 && overflowEmbeddingCount === 0) {
|
||||
validIsolateCount++
|
||||
statusStack.push({
|
||||
_level: level,
|
||||
_override: 0,
|
||||
_isolate: 1,
|
||||
_isolInitIndex: i
|
||||
})
|
||||
} else {
|
||||
overflowIsolateCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Terminating Isolates: 3.3.2 X6a
|
||||
else if (charType & TYPE_PDI) {
|
||||
if (overflowIsolateCount > 0) {
|
||||
overflowIsolateCount--
|
||||
} else if (validIsolateCount > 0) {
|
||||
overflowEmbeddingCount = 0
|
||||
while (!statusStack[statusStack.length - 1]._isolate) {
|
||||
statusStack.pop()
|
||||
}
|
||||
// Add to isolation pairs bidirectional mapping:
|
||||
const isolInitIndex = statusStack[statusStack.length - 1]._isolInitIndex
|
||||
if (isolInitIndex != null) {
|
||||
isolationPairs.set(isolInitIndex, i)
|
||||
isolationPairs.set(i, isolInitIndex)
|
||||
}
|
||||
statusStack.pop()
|
||||
validIsolateCount--
|
||||
}
|
||||
stackTop = statusStack[statusStack.length - 1]
|
||||
embedLevels[i] = stackTop._level
|
||||
if (stackTop._override) {
|
||||
changeCharType(i, stackTop._override)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Terminating Embeddings and Overrides: 3.3.2 X7
|
||||
else if (charType & TYPE_PDF) {
|
||||
if (overflowIsolateCount === 0) {
|
||||
if (overflowEmbeddingCount > 0) {
|
||||
overflowEmbeddingCount--
|
||||
} else if (!stackTop._isolate && statusStack.length > 1) {
|
||||
statusStack.pop()
|
||||
stackTop = statusStack[statusStack.length - 1]
|
||||
}
|
||||
}
|
||||
embedLevels[i] = stackTop._level // 5.2
|
||||
}
|
||||
|
||||
// End of Paragraph: 3.3.2 X8
|
||||
else if (charType & TYPE_B) {
|
||||
embedLevels[i] = paragraph.level
|
||||
}
|
||||
}
|
||||
|
||||
// Non-formatting characters: 3.3.2 X6
|
||||
else {
|
||||
embedLevels[i] = stackTop._level
|
||||
// NOTE: This exclusion of BN seems to go against what section 5.2 says, but is required for test passage
|
||||
if (stackTop._override && charType !== TYPE_BN) {
|
||||
changeCharType(i, stackTop._override)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === 3.3.3 Preparations for Implicit Processing ===
|
||||
|
||||
// Remove all RLE, LRE, RLO, LRO, PDF, and BN characters: 3.3.3 X9
|
||||
// Note: Due to section 5.2, we won't remove them, but we'll use the BN_LIKE_TYPES bitset to
|
||||
// easily ignore them all from here on out.
|
||||
|
||||
// 3.3.3 X10
|
||||
// Compute the set of isolating run sequences as specified by BD13
|
||||
const levelRuns = []
|
||||
let currentRun = null
|
||||
let isolationLevel = 0
|
||||
for (let i = paragraph.start; i <= paragraph.end; i++) {
|
||||
const charType = charTypes[i]
|
||||
if (!(charType & BN_LIKE_TYPES)) {
|
||||
const lvl = embedLevels[i]
|
||||
const isIsolInit = charType & ISOLATE_INIT_TYPES
|
||||
const isPDI = charType === TYPE_PDI
|
||||
if (isIsolInit) {
|
||||
isolationLevel++
|
||||
}
|
||||
if (currentRun && lvl === currentRun._level) {
|
||||
currentRun._end = i
|
||||
currentRun._endsWithIsolInit = isIsolInit
|
||||
} else {
|
||||
levelRuns.push(currentRun = {
|
||||
_start: i,
|
||||
_end: i,
|
||||
_level: lvl,
|
||||
_startsWithPDI: isPDI,
|
||||
_endsWithIsolInit: isIsolInit
|
||||
})
|
||||
}
|
||||
if (isPDI) {
|
||||
isolationLevel--
|
||||
}
|
||||
}
|
||||
}
|
||||
const isolatingRunSeqs = [] // [{seqIndices: [], sosType: L|R, eosType: L|R}]
|
||||
for (let runIdx = 0; runIdx < levelRuns.length; runIdx++) {
|
||||
const run = levelRuns[runIdx]
|
||||
if (!run._startsWithPDI || (run._startsWithPDI && !isolationPairs.has(run._start))) {
|
||||
const seqRuns = [currentRun = run]
|
||||
for (let pdiIndex; currentRun && currentRun._endsWithIsolInit && (pdiIndex = isolationPairs.get(currentRun._end)) != null;) {
|
||||
for (let i = runIdx + 1; i < levelRuns.length; i++) {
|
||||
if (levelRuns[i]._start === pdiIndex) {
|
||||
seqRuns.push(currentRun = levelRuns[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// build flat list of indices across all runs:
|
||||
const seqIndices = []
|
||||
for (let i = 0; i < seqRuns.length; i++) {
|
||||
const run = seqRuns[i]
|
||||
for (let j = run._start; j <= run._end; j++) {
|
||||
seqIndices.push(j)
|
||||
}
|
||||
}
|
||||
// determine the sos/eos types:
|
||||
let firstLevel = embedLevels[seqIndices[0]]
|
||||
let prevLevel = paragraph.level
|
||||
for (let i = seqIndices[0] - 1; i >= 0; i--) {
|
||||
if (!(charTypes[i] & BN_LIKE_TYPES)) { //5.2
|
||||
prevLevel = embedLevels[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
const lastIndex = seqIndices[seqIndices.length - 1]
|
||||
let lastLevel = embedLevels[lastIndex]
|
||||
let nextLevel = paragraph.level
|
||||
if (!(charTypes[lastIndex] & ISOLATE_INIT_TYPES)) {
|
||||
for (let i = lastIndex + 1; i <= paragraph.end; i++) {
|
||||
if (!(charTypes[i] & BN_LIKE_TYPES)) { //5.2
|
||||
nextLevel = embedLevels[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
isolatingRunSeqs.push({
|
||||
_seqIndices: seqIndices,
|
||||
_sosType: Math.max(prevLevel, firstLevel) % 2 ? TYPE_R : TYPE_L,
|
||||
_eosType: Math.max(nextLevel, lastLevel) % 2 ? TYPE_R : TYPE_L
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The next steps are done per isolating run sequence
|
||||
for (let seqIdx = 0; seqIdx < isolatingRunSeqs.length; seqIdx++) {
|
||||
const { _seqIndices: seqIndices, _sosType: sosType, _eosType: eosType } = isolatingRunSeqs[seqIdx]
|
||||
/**
|
||||
* All the level runs in an isolating run sequence have the same embedding level.
|
||||
*
|
||||
* DO NOT change any `embedLevels[i]` within the current scope.
|
||||
*/
|
||||
const embedDirection = ((embedLevels[seqIndices[0]]) & 1) ? TYPE_R : TYPE_L;
|
||||
|
||||
// === 3.3.4 Resolving Weak Types ===
|
||||
|
||||
// W1 + 5.2. Search backward from each NSM to the first character in the isolating run sequence whose
|
||||
// bidirectional type is not BN, and set the NSM to ON if it is an isolate initiator or PDI, and to its
|
||||
// type otherwise. If the NSM is the first non-BN character, change the NSM to the type of sos.
|
||||
if (charTypeCounts.get(TYPE_NSM)) {
|
||||
for (let si = 0; si < seqIndices.length; si++) {
|
||||
const i = seqIndices[si]
|
||||
if (charTypes[i] & TYPE_NSM) {
|
||||
let prevType = sosType
|
||||
for (let sj = si - 1; sj >= 0; sj--) {
|
||||
if (!(charTypes[seqIndices[sj]] & BN_LIKE_TYPES)) { //5.2 scan back to first non-BN
|
||||
prevType = charTypes[seqIndices[sj]]
|
||||
break
|
||||
}
|
||||
}
|
||||
changeCharType(i, (prevType & (ISOLATE_INIT_TYPES | TYPE_PDI)) ? TYPE_ON : prevType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// W2. Search backward from each instance of a European number until the first strong type (R, L, AL, or sos)
|
||||
// is found. If an AL is found, change the type of the European number to Arabic number.
|
||||
if (charTypeCounts.get(TYPE_EN)) {
|
||||
for (let si = 0; si < seqIndices.length; si++) {
|
||||
const i = seqIndices[si]
|
||||
if (charTypes[i] & TYPE_EN) {
|
||||
for (let sj = si - 1; sj >= -1; sj--) {
|
||||
const prevCharType = sj === -1 ? sosType : charTypes[seqIndices[sj]]
|
||||
if (prevCharType & STRONG_TYPES) {
|
||||
if (prevCharType === TYPE_AL) {
|
||||
changeCharType(i, TYPE_AN)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// W3. Change all ALs to R
|
||||
if (charTypeCounts.get(TYPE_AL)) {
|
||||
for (let si = 0; si < seqIndices.length; si++) {
|
||||
const i = seqIndices[si]
|
||||
if (charTypes[i] & TYPE_AL) {
|
||||
changeCharType(i, TYPE_R)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// W4. A single European separator between two European numbers changes to a European number. A single common
|
||||
// separator between two numbers of the same type changes to that type.
|
||||
if (charTypeCounts.get(TYPE_ES) || charTypeCounts.get(TYPE_CS)) {
|
||||
for (let si = 1; si < seqIndices.length - 1; si++) {
|
||||
const i = seqIndices[si]
|
||||
if (charTypes[i] & (TYPE_ES | TYPE_CS)) {
|
||||
let prevType = 0, nextType = 0
|
||||
for (let sj = si - 1; sj >= 0; sj--) {
|
||||
prevType = charTypes[seqIndices[sj]]
|
||||
if (!(prevType & BN_LIKE_TYPES)) { //5.2
|
||||
break
|
||||
}
|
||||
}
|
||||
for (let sj = si + 1; sj < seqIndices.length; sj++) {
|
||||
nextType = charTypes[seqIndices[sj]]
|
||||
if (!(nextType & BN_LIKE_TYPES)) { //5.2
|
||||
break
|
||||
}
|
||||
}
|
||||
if (prevType === nextType && (charTypes[i] === TYPE_ES ? prevType === TYPE_EN : (prevType & (TYPE_EN | TYPE_AN)))) {
|
||||
changeCharType(i, prevType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
|
||||
if (charTypeCounts.get(TYPE_EN)) {
|
||||
for (let si = 0; si < seqIndices.length; si++) {
|
||||
const i = seqIndices[si]
|
||||
if (charTypes[i] & TYPE_EN) {
|
||||
for (let sj = si - 1; sj >= 0 && (charTypes[seqIndices[sj]] & (TYPE_ET | BN_LIKE_TYPES)); sj--) {
|
||||
changeCharType(seqIndices[sj], TYPE_EN)
|
||||
}
|
||||
for (si++; si < seqIndices.length && (charTypes[seqIndices[si]] & (TYPE_ET | BN_LIKE_TYPES | TYPE_EN)); si++) {
|
||||
if (charTypes[seqIndices[si]] !== TYPE_EN) {
|
||||
changeCharType(seqIndices[si], TYPE_EN)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// W6. Otherwise, separators and terminators change to Other Neutral.
|
||||
if (charTypeCounts.get(TYPE_ET) || charTypeCounts.get(TYPE_ES) || charTypeCounts.get(TYPE_CS)) {
|
||||
for (let si = 0; si < seqIndices.length; si++) {
|
||||
const i = seqIndices[si]
|
||||
if (charTypes[i] & (TYPE_ET | TYPE_ES | TYPE_CS)) {
|
||||
changeCharType(i, TYPE_ON)
|
||||
// 5.2 transform adjacent BNs too:
|
||||
for (let sj = si - 1; sj >= 0 && (charTypes[seqIndices[sj]] & BN_LIKE_TYPES); sj--) {
|
||||
changeCharType(seqIndices[sj], TYPE_ON)
|
||||
}
|
||||
for (let sj = si + 1; sj < seqIndices.length && (charTypes[seqIndices[sj]] & BN_LIKE_TYPES); sj++) {
|
||||
changeCharType(seqIndices[sj], TYPE_ON)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// W7. Search backward from each instance of a European number until the first strong type (R, L, or sos)
|
||||
// is found. If an L is found, then change the type of the European number to L.
|
||||
// NOTE: implemented in single forward pass for efficiency
|
||||
if (charTypeCounts.get(TYPE_EN)) {
|
||||
for (let si = 0, prevStrongType = sosType; si < seqIndices.length; si++) {
|
||||
const i = seqIndices[si]
|
||||
const type = charTypes[i]
|
||||
if (type & TYPE_EN) {
|
||||
if (prevStrongType === TYPE_L) {
|
||||
changeCharType(i, TYPE_L)
|
||||
}
|
||||
} else if (type & STRONG_TYPES) {
|
||||
prevStrongType = type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === 3.3.5 Resolving Neutral and Isolate Formatting Types ===
|
||||
|
||||
if (charTypeCounts.get(NEUTRAL_ISOLATE_TYPES)) {
|
||||
// N0. Process bracket pairs in an isolating run sequence sequentially in the logical order of the text
|
||||
// positions of the opening paired brackets using the logic given below. Within this scope, bidirectional
|
||||
// types EN and AN are treated as R.
|
||||
const R_TYPES_FOR_N_STEPS = (TYPE_R | TYPE_EN | TYPE_AN)
|
||||
const STRONG_TYPES_FOR_N_STEPS = R_TYPES_FOR_N_STEPS | TYPE_L
|
||||
|
||||
// * Identify the bracket pairs in the current isolating run sequence according to BD16.
|
||||
const bracketPairs = []
|
||||
{
|
||||
const openerStack = []
|
||||
for (let si = 0; si < seqIndices.length; si++) {
|
||||
// NOTE: for any potential bracket character we also test that it still carries a NI
|
||||
// type, as that may have been changed earlier. This doesn't seem to be explicitly
|
||||
// called out in the spec, but is required for passage of certain tests.
|
||||
if (charTypes[seqIndices[si]] & NEUTRAL_ISOLATE_TYPES) {
|
||||
const char = string[seqIndices[si]]
|
||||
let oppositeBracket
|
||||
// Opening bracket
|
||||
if (openingToClosingBracket(char) !== null) {
|
||||
if (openerStack.length < 63) {
|
||||
openerStack.push({ char, seqIndex: si })
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Closing bracket
|
||||
else if ((oppositeBracket = closingToOpeningBracket(char)) !== null) {
|
||||
for (let stackIdx = openerStack.length - 1; stackIdx >= 0; stackIdx--) {
|
||||
const stackChar = openerStack[stackIdx].char
|
||||
if (stackChar === oppositeBracket ||
|
||||
stackChar === closingToOpeningBracket(getCanonicalBracket(char)) ||
|
||||
openingToClosingBracket(getCanonicalBracket(stackChar)) === char
|
||||
) {
|
||||
bracketPairs.push([openerStack[stackIdx].seqIndex, si])
|
||||
openerStack.length = stackIdx //pop the matching bracket and all following
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bracketPairs.sort((a, b) => a[0] - b[0])
|
||||
}
|
||||
// * For each bracket-pair element in the list of pairs of text positions
|
||||
for (let pairIdx = 0; pairIdx < bracketPairs.length; pairIdx++) {
|
||||
const [openSeqIdx, closeSeqIdx] = bracketPairs[pairIdx]
|
||||
// a. Inspect the bidirectional types of the characters enclosed within the bracket pair.
|
||||
// b. If any strong type (either L or R) matching the embedding direction is found, set the type for both
|
||||
// brackets in the pair to match the embedding direction.
|
||||
let foundStrongType = false
|
||||
let useStrongType = 0
|
||||
for (let si = openSeqIdx + 1; si < closeSeqIdx; si++) {
|
||||
const i = seqIndices[si]
|
||||
if (charTypes[i] & STRONG_TYPES_FOR_N_STEPS) {
|
||||
foundStrongType = true
|
||||
const lr = (charTypes[i] & R_TYPES_FOR_N_STEPS) ? TYPE_R : TYPE_L
|
||||
if (lr === embedDirection) {
|
||||
useStrongType = lr
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// c. Otherwise, if there is a strong type it must be opposite the embedding direction. Therefore, test
|
||||
// for an established context with a preceding strong type by checking backwards before the opening paired
|
||||
// bracket until the first strong type (L, R, or sos) is found.
|
||||
// 1. If the preceding strong type is also opposite the embedding direction, context is established, so
|
||||
// set the type for both brackets in the pair to that direction.
|
||||
// 2. Otherwise set the type for both brackets in the pair to the embedding direction.
|
||||
if (foundStrongType && !useStrongType) {
|
||||
useStrongType = sosType
|
||||
for (let si = openSeqIdx - 1; si >= 0; si--) {
|
||||
const i = seqIndices[si]
|
||||
if (charTypes[i] & STRONG_TYPES_FOR_N_STEPS) {
|
||||
const lr = (charTypes[i] & R_TYPES_FOR_N_STEPS) ? TYPE_R : TYPE_L
|
||||
if (lr !== embedDirection) {
|
||||
useStrongType = lr
|
||||
} else {
|
||||
useStrongType = embedDirection
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (useStrongType) {
|
||||
charTypes[seqIndices[openSeqIdx]] = charTypes[seqIndices[closeSeqIdx]] = useStrongType
|
||||
// * Any number of characters that had original bidirectional character type NSM prior to the application
|
||||
// of W1 that immediately follow a paired bracket which changed to L or R under N0 should change to match
|
||||
// the type of their preceding bracket.
|
||||
if (useStrongType !== embedDirection) {
|
||||
for (let si = openSeqIdx + 1; si < seqIndices.length; si++) {
|
||||
if (!(charTypes[seqIndices[si]] & BN_LIKE_TYPES)) {
|
||||
if (getBidiCharType(string[seqIndices[si]]) & TYPE_NSM) {
|
||||
charTypes[seqIndices[si]] = useStrongType
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (useStrongType !== embedDirection) {
|
||||
for (let si = closeSeqIdx + 1; si < seqIndices.length; si++) {
|
||||
if (!(charTypes[seqIndices[si]] & BN_LIKE_TYPES)) {
|
||||
if (getBidiCharType(string[seqIndices[si]]) & TYPE_NSM) {
|
||||
charTypes[seqIndices[si]] = useStrongType
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// N1. A sequence of NIs takes the direction of the surrounding strong text if the text on both sides has the
|
||||
// same direction.
|
||||
// N2. Any remaining NIs take the embedding direction.
|
||||
for (let si = 0; si < seqIndices.length; si++) {
|
||||
if (charTypes[seqIndices[si]] & NEUTRAL_ISOLATE_TYPES) {
|
||||
let niRunStart = si, niRunEnd = si
|
||||
let prevType = sosType //si === 0 ? sosType : (charTypes[seqIndices[si - 1]] & R_TYPES_FOR_N_STEPS) ? TYPE_R : TYPE_L
|
||||
for (let si2 = si - 1; si2 >= 0; si2--) {
|
||||
if (charTypes[seqIndices[si2]] & BN_LIKE_TYPES) {
|
||||
niRunStart = si2 //5.2 treat BNs adjacent to NIs as NIs
|
||||
} else {
|
||||
prevType = (charTypes[seqIndices[si2]] & R_TYPES_FOR_N_STEPS) ? TYPE_R : TYPE_L
|
||||
break
|
||||
}
|
||||
}
|
||||
let nextType = eosType
|
||||
for (let si2 = si + 1; si2 < seqIndices.length; si2++) {
|
||||
if (charTypes[seqIndices[si2]] & (NEUTRAL_ISOLATE_TYPES | BN_LIKE_TYPES)) {
|
||||
niRunEnd = si2
|
||||
} else {
|
||||
nextType = (charTypes[seqIndices[si2]] & R_TYPES_FOR_N_STEPS) ? TYPE_R : TYPE_L
|
||||
break
|
||||
}
|
||||
}
|
||||
for (let sj = niRunStart; sj <= niRunEnd; sj++) {
|
||||
charTypes[seqIndices[sj]] = prevType === nextType ? prevType : embedDirection
|
||||
}
|
||||
si = niRunEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === 3.3.6 Resolving Implicit Levels ===
|
||||
|
||||
for (let i = paragraph.start; i <= paragraph.end; i++) {
|
||||
const level = embedLevels[i]
|
||||
const type = charTypes[i]
|
||||
// I2. For all characters with an odd (right-to-left) embedding level, those of type L, EN or AN go up one level.
|
||||
if (level & 1) {
|
||||
if (type & (TYPE_L | TYPE_EN | TYPE_AN)) {
|
||||
embedLevels[i]++
|
||||
}
|
||||
}
|
||||
// I1. For all characters with an even (left-to-right) embedding level, those of type R go up one level
|
||||
// and those of type AN or EN go up two levels.
|
||||
else {
|
||||
if (type & TYPE_R) {
|
||||
embedLevels[i]++
|
||||
} else if (type & (TYPE_AN | TYPE_EN)) {
|
||||
embedLevels[i] += 2
|
||||
}
|
||||
}
|
||||
|
||||
// 5.2: Resolve any LRE, RLE, LRO, RLO, PDF, or BN to the level of the preceding character if there is one,
|
||||
// and otherwise to the base level.
|
||||
if (type & BN_LIKE_TYPES) {
|
||||
embedLevels[i] = i === 0 ? paragraph.level : embedLevels[i - 1]
|
||||
}
|
||||
|
||||
// 3.4 L1.1-4: Reset the embedding level of segment/paragraph separators, and any sequence of whitespace or
|
||||
// isolate formatting characters preceding them or the end of the paragraph, to the paragraph level.
|
||||
// NOTE: this will also need to be applied to each individual line ending after line wrapping occurs.
|
||||
if (i === paragraph.end || getBidiCharType(string[i]) & (TYPE_S | TYPE_B)) {
|
||||
for (let j = i; j >= 0 && (getBidiCharType(string[j]) & TRAILING_TYPES); j--) {
|
||||
embedLevels[j] = paragraph.level
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DONE! The resolved levels can then be used, after line wrapping, to flip runs of characters
|
||||
// according to section 3.4 Reordering Resolved Levels
|
||||
return {
|
||||
levels: embedLevels,
|
||||
paragraphs
|
||||
}
|
||||
|
||||
function determineAutoEmbedLevel (start, isFSI) {
|
||||
// 3.3.1 P2 - P3
|
||||
for (let i = start; i < string.length; i++) {
|
||||
const charType = charTypes[i]
|
||||
if (charType & (TYPE_R | TYPE_AL)) {
|
||||
return 1
|
||||
}
|
||||
if ((charType & (TYPE_B | TYPE_L)) || (isFSI && charType === TYPE_PDI)) {
|
||||
return 0
|
||||
}
|
||||
if (charType & ISOLATE_INIT_TYPES) {
|
||||
const pdi = indexOfMatchingPDI(i)
|
||||
i = pdi === -1 ? string.length : pdi
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function indexOfMatchingPDI (isolateStart) {
|
||||
// 3.1.2 BD9
|
||||
let isolationLevel = 1
|
||||
for (let i = isolateStart + 1; i < string.length; i++) {
|
||||
const charType = charTypes[i]
|
||||
if (charType & TYPE_B) {
|
||||
break
|
||||
}
|
||||
if (charType & TYPE_PDI) {
|
||||
if (--isolationLevel === 0) {
|
||||
return i
|
||||
}
|
||||
} else if (charType & ISOLATE_INIT_TYPES) {
|
||||
isolationLevel++
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
5
node_modules/bidi-js/src/index.js
generated
vendored
Normal file
5
node_modules/bidi-js/src/index.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export { getEmbeddingLevels } from './embeddingLevels.js'
|
||||
export { getReorderSegments, getReorderedIndices, getReorderedString } from './reordering.js'
|
||||
export { getBidiCharType, getBidiCharTypeName } from './charTypes.js'
|
||||
export { getMirroredCharacter, getMirroredCharactersMap } from './mirroring.js'
|
||||
export { closingToOpeningBracket, openingToClosingBracket, getCanonicalBracket } from './brackets.js'
|
||||
48
node_modules/bidi-js/src/mirroring.js
generated
vendored
Normal file
48
node_modules/bidi-js/src/mirroring.js
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import data from './data/bidiMirroring.data.js'
|
||||
import { parseCharacterMap } from './util/parseCharacterMap.js'
|
||||
|
||||
let mirrorMap
|
||||
|
||||
function parse () {
|
||||
if (!mirrorMap) {
|
||||
//const start = performance.now()
|
||||
const { map, reverseMap } = parseCharacterMap(data, true)
|
||||
// Combine both maps into one
|
||||
reverseMap.forEach((value, key) => {
|
||||
map.set(key, value)
|
||||
})
|
||||
mirrorMap = map
|
||||
//console.log(`mirrored chars parsed in ${performance.now() - start}ms`)
|
||||
}
|
||||
}
|
||||
|
||||
export function getMirroredCharacter (char) {
|
||||
parse()
|
||||
return mirrorMap.get(char) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string and its resolved embedding levels, build a map of indices to replacement chars
|
||||
* for any characters in right-to-left segments that have defined mirrored characters.
|
||||
* @param string
|
||||
* @param embeddingLevels
|
||||
* @param [start]
|
||||
* @param [end]
|
||||
* @return {Map<number, string>}
|
||||
*/
|
||||
export function getMirroredCharactersMap(string, embeddingLevels, start, end) {
|
||||
let strLen = string.length
|
||||
start = Math.max(0, start == null ? 0 : +start)
|
||||
end = Math.min(strLen - 1, end == null ? strLen - 1 : +end)
|
||||
|
||||
const map = new Map()
|
||||
for (let i = start; i <= end; i++) {
|
||||
if (embeddingLevels[i] & 1) { //only odd (rtl) levels
|
||||
const mirror = getMirroredCharacter(string[i])
|
||||
if (mirror !== null) {
|
||||
map.set(i, mirror)
|
||||
}
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
99
node_modules/bidi-js/src/reordering.js
generated
vendored
Normal file
99
node_modules/bidi-js/src/reordering.js
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
import { getBidiCharType, TRAILING_TYPES } from './charTypes.js'
|
||||
import { getMirroredCharacter } from './mirroring.js'
|
||||
|
||||
/**
|
||||
* Given a start and end denoting a single line within a string, and a set of precalculated
|
||||
* bidi embedding levels, produce a list of segments whose ordering should be flipped, in sequence.
|
||||
* @param {string} string - the full input string
|
||||
* @param {GetEmbeddingLevelsResult} embeddingLevelsResult - the result object from getEmbeddingLevels
|
||||
* @param {number} [start] - first character in a subset of the full string
|
||||
* @param {number} [end] - last character in a subset of the full string
|
||||
* @return {number[][]} - the list of start/end segments that should be flipped, in order.
|
||||
*/
|
||||
export function getReorderSegments(string, embeddingLevelsResult, start, end) {
|
||||
let strLen = string.length
|
||||
start = Math.max(0, start == null ? 0 : +start)
|
||||
end = Math.min(strLen - 1, end == null ? strLen - 1 : +end)
|
||||
|
||||
const segments = []
|
||||
embeddingLevelsResult.paragraphs.forEach(paragraph => {
|
||||
const lineStart = Math.max(start, paragraph.start)
|
||||
const lineEnd = Math.min(end, paragraph.end)
|
||||
if (lineStart < lineEnd) {
|
||||
// Local slice for mutation
|
||||
const lineLevels = embeddingLevelsResult.levels.slice(lineStart, lineEnd + 1)
|
||||
|
||||
// 3.4 L1.4: Reset any sequence of whitespace characters and/or isolate formatting characters at the
|
||||
// end of the line to the paragraph level.
|
||||
for (let i = lineEnd; i >= lineStart && (getBidiCharType(string[i]) & TRAILING_TYPES); i--) {
|
||||
lineLevels[i] = paragraph.level
|
||||
}
|
||||
|
||||
// L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels
|
||||
// not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher.
|
||||
let maxLevel = paragraph.level
|
||||
let minOddLevel = Infinity
|
||||
for (let i = 0; i < lineLevels.length; i++) {
|
||||
const level = lineLevels[i]
|
||||
if (level > maxLevel) maxLevel = level
|
||||
if (level < minOddLevel) minOddLevel = level | 1
|
||||
}
|
||||
for (let lvl = maxLevel; lvl >= minOddLevel; lvl--) {
|
||||
for (let i = 0; i < lineLevels.length; i++) {
|
||||
if (lineLevels[i] >= lvl) {
|
||||
const segStart = i
|
||||
while (i + 1 < lineLevels.length && lineLevels[i + 1] >= lvl) {
|
||||
i++
|
||||
}
|
||||
if (i > segStart) {
|
||||
segments.push([segStart + lineStart, i + lineStart])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return segments
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} string
|
||||
* @param {GetEmbeddingLevelsResult} embedLevelsResult
|
||||
* @param {number} [start]
|
||||
* @param {number} [end]
|
||||
* @return {string} the new string with bidi segments reordered
|
||||
*/
|
||||
export function getReorderedString(string, embedLevelsResult, start, end) {
|
||||
const indices = getReorderedIndices(string, embedLevelsResult, start, end)
|
||||
const chars = [...string]
|
||||
indices.forEach((charIndex, i) => {
|
||||
chars[i] = (
|
||||
(embedLevelsResult.levels[charIndex] & 1) ? getMirroredCharacter(string[charIndex]) : null
|
||||
) || string[charIndex]
|
||||
})
|
||||
return chars.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} string
|
||||
* @param {GetEmbeddingLevelsResult} embedLevelsResult
|
||||
* @param {number} [start]
|
||||
* @param {number} [end]
|
||||
* @return {number[]} an array with character indices in their new bidi order
|
||||
*/
|
||||
export function getReorderedIndices(string, embedLevelsResult, start, end) {
|
||||
const segments = getReorderSegments(string, embedLevelsResult, start, end)
|
||||
// Fill an array with indices
|
||||
const indices = []
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
indices[i] = i
|
||||
}
|
||||
// Reverse each segment in order
|
||||
segments.forEach(([start, end]) => {
|
||||
const slice = indices.slice(start, end + 1)
|
||||
for (let i = slice.length; i--;) {
|
||||
indices[end - i] = slice[i]
|
||||
}
|
||||
})
|
||||
return indices
|
||||
}
|
||||
30
node_modules/bidi-js/src/util/parseCharacterMap.js
generated
vendored
Normal file
30
node_modules/bidi-js/src/util/parseCharacterMap.js
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Parses an string that holds encoded codepoint mappings, e.g. for bracket pairs or
|
||||
* mirroring characters, as encoded by scripts/generateBidiData.js. Returns an object
|
||||
* holding the `map`, and optionally a `reverseMap` if `includeReverse:true`.
|
||||
* @param {string} encodedString
|
||||
* @param {boolean} includeReverse - true if you want reverseMap in the output
|
||||
* @return {{map: Map<number, number>, reverseMap?: Map<number, number>}}
|
||||
*/
|
||||
export function parseCharacterMap (encodedString, includeReverse) {
|
||||
const radix = 36
|
||||
let lastCode = 0
|
||||
const map = new Map()
|
||||
const reverseMap = includeReverse && new Map()
|
||||
let prevPair
|
||||
encodedString.split(',').forEach(function visit(entry) {
|
||||
if (entry.indexOf('+') !== -1) {
|
||||
for (let i = +entry; i--;) {
|
||||
visit(prevPair)
|
||||
}
|
||||
} else {
|
||||
prevPair = entry
|
||||
let [a, b] = entry.split('>')
|
||||
a = String.fromCodePoint(lastCode += parseInt(a, radix))
|
||||
b = String.fromCodePoint(lastCode += parseInt(b, radix))
|
||||
map.set(a, b)
|
||||
includeReverse && reverseMap.set(b, a)
|
||||
}
|
||||
})
|
||||
return { map, reverseMap }
|
||||
}
|
||||
19
node_modules/css-tree/LICENSE
generated
vendored
Normal file
19
node_modules/css-tree/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2016-2024 by Roman Dvornov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
192
node_modules/css-tree/README.md
generated
vendored
Normal file
192
node_modules/css-tree/README.md
generated
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
<img align="right" width="111" height="111" alt="CSSTree logo" src="assets/csstree-logo-rounded.svg" />
|
||||
|
||||
# CSSTree
|
||||
|
||||
[](https://www.npmjs.com/package/css-tree)
|
||||
[](https://github.com/csstree/csstree/actions/workflows/build.yml)
|
||||
[](https://coveralls.io/github/csstree/csstree?branch=master)
|
||||
[](https://www.npmjs.com/package/css-tree)
|
||||
[](https://twitter.com/csstree)
|
||||
|
||||
CSSTree is a tool set for CSS: [fast](https://github.com/postcss/benchmark) detailed parser (CSS → AST), walker (AST traversal), generator (AST → CSS) and lexer (validation and matching) based on specs and browser implementations. The main goal is to be efficient and W3C spec compliant, with focus on CSS analyzing and source-to-source transforming tasks.
|
||||
|
||||
## Features
|
||||
|
||||
- **Detailed parsing with an adjustable level of detail**
|
||||
|
||||
By default CSSTree parses CSS as detailed as possible, i.e. each single logical part is representing with its own AST node (see [AST format](docs/ast.md) for all possible node types). The parsing detail level can be changed through [parser options](docs/parsing.md#parsesource-options), for example, you can disable parsing of selectors or declaration values for component parts.
|
||||
|
||||
- **Tolerant to errors by design**
|
||||
|
||||
Parser behaves as [spec says](https://www.w3.org/TR/css-syntax-3/#error-handling): "When errors occur in CSS, the parser attempts to recover gracefully, throwing away only the minimum amount of content before returning to parsing as normal". The only thing the parser departs from the specification is that it doesn't throw away bad content, but wraps it in a special node type (`Raw`) that allows processing it later.
|
||||
|
||||
- **Fast and efficient**
|
||||
|
||||
CSSTree is created with focus on performance and effective memory consumption. Therefore it's [one of the fastest CSS parsers](https://github.com/postcss/benchmark) at the moment.
|
||||
|
||||
- **Syntax validation**
|
||||
|
||||
The built-in lexer can test CSS against syntaxes defined by W3C. CSSTree uses [mdn/data](https://github.com/mdn/data/) as a basis for lexer's dictionaries and extends it with vendor specific and legacy syntaxes. Lexer can only check the declaration values and at-rules currently, but this feature will be extended to other parts of the CSS in the future.
|
||||
|
||||
## Projects using CSSTree
|
||||
|
||||
- [Svelte](https://github.com/sveltejs/svelte) – Cybernetically enhanced web apps
|
||||
- [SVGO](https://github.com/svg/svgo) – Node.js tool for optimizing SVG files
|
||||
- [CSSO](https://github.com/css/csso) – CSS minifier with structural optimizations
|
||||
- [NativeScript](https://github.com/NativeScript/NativeScript) – NativeScript empowers you to access native APIs from JavaScript directly
|
||||
- [react-native-svg](https://github.com/react-native-svg/react-native-svg) – SVG library for React Native, React Native Web, and plain React web projects
|
||||
- [penthouse](https://github.com/pocketjoso/penthouse) – Critical Path CSS Generator
|
||||
- [Bit](https://github.com/teambit/bit) – Bit is the platform for collaborating on components
|
||||
- and more...
|
||||
|
||||
## Documentation
|
||||
|
||||
- [AST format](docs/ast.md)
|
||||
- [Parsing CSS → AST](docs/parsing.md)
|
||||
- [parse(source[, options])](docs/parsing.md#parsesource-options)
|
||||
- [Serialization AST → CSS](docs/generate.md)
|
||||
- [generate(ast[, options])](docs/generate.md#generateast-options)
|
||||
- [AST traversal](docs/traversal.md)
|
||||
- [walk(ast, options)](docs/traversal.md#walkast-options)
|
||||
- [find(ast, fn)](docs/traversal.md#findast-fn)
|
||||
- [findLast(ast, fn)](docs/traversal.md#findlastast-fn)
|
||||
- [findAll(ast, fn)](docs/traversal.md#findallast-fn)
|
||||
- [Util functions](docs/utils.md)
|
||||
- Value encoding & decoding
|
||||
- [property(name)](docs/utils.md#propertyname)
|
||||
- [keyword(name)](docs/utils.md#keywordname)
|
||||
- [ident](docs/utils.md#ident)
|
||||
- [string](docs/utils.md#string)
|
||||
- [url](docs/utils.md#url)
|
||||
- [List class](docs/list.md)
|
||||
- AST transforming
|
||||
- [clone(ast)](docs/utils.md#cloneast)
|
||||
- [fromPlainObject(object)](docs/utils.md#fromplainobjectobject)
|
||||
- [toPlainObject(ast)](docs/utils.md#toplainobjectast)
|
||||
- [Value Definition Syntax](docs/definition-syntax.md)
|
||||
- [parse(source)](docs/definition-syntax.md#parsesource)
|
||||
- [walk(node, options, context)](docs/definition-syntax.md#walknode-options-context)
|
||||
- [generate(node, options)](docs/definition-syntax.md#generatenode-options)
|
||||
- [AST format](docs/definition-syntax.md#ast-format)
|
||||
|
||||
## Tools
|
||||
|
||||
* [AST Explorer](https://astexplorer.net/#/gist/244e2fb4da940df52bf0f4b94277db44/e79aff44611020b22cfd9708f3a99ce09b7d67a8) – explore CSSTree AST format with zero setup
|
||||
* [CSS syntax reference](https://csstree.github.io/docs/syntax.html)
|
||||
* [CSS syntax validator](https://csstree.github.io/docs/validator.html)
|
||||
|
||||
## Related projects
|
||||
|
||||
* [csstree-validator](https://github.com/csstree/validator) – NPM package to validate CSS
|
||||
* [stylelint-csstree-validator](https://github.com/csstree/stylelint-validator) – plugin for stylelint to validate CSS
|
||||
* [Grunt plugin](https://github.com/sergejmueller/grunt-csstree-validator)
|
||||
* [Gulp plugin](https://github.com/csstree/gulp-csstree)
|
||||
* [Sublime plugin](https://github.com/csstree/SublimeLinter-contrib-csstree)
|
||||
* [VS Code plugin](https://github.com/csstree/vscode-plugin)
|
||||
* [Atom plugin](https://github.com/csstree/atom-plugin)
|
||||
|
||||
## Usage
|
||||
|
||||
Install with npm:
|
||||
|
||||
```
|
||||
npm install css-tree
|
||||
```
|
||||
|
||||
Basic usage:
|
||||
|
||||
```js
|
||||
import * as csstree from 'css-tree';
|
||||
|
||||
// parse CSS to AST
|
||||
const ast = csstree.parse('.example { world: "!" }');
|
||||
|
||||
// traverse AST and modify it
|
||||
csstree.walk(ast, (node) => {
|
||||
if (node.type === 'ClassSelector' && node.name === 'example') {
|
||||
node.name = 'hello';
|
||||
}
|
||||
});
|
||||
|
||||
// generate CSS from AST
|
||||
console.log(csstree.generate(ast));
|
||||
// .hello{world:"!"}
|
||||
```
|
||||
|
||||
Syntax matching:
|
||||
|
||||
```js
|
||||
// parse CSS to AST as a declaration value
|
||||
const ast = csstree.parse('red 1px solid', { context: 'value' });
|
||||
|
||||
// match to syntax of `border` property
|
||||
const matchResult = csstree.lexer.matchProperty('border', ast);
|
||||
|
||||
// check first value node is a <color>
|
||||
console.log(matchResult.isType(ast.children.first, 'color'));
|
||||
// true
|
||||
|
||||
// get a type list matched to a node
|
||||
console.log(matchResult.getTrace(ast.children.first));
|
||||
// [ { type: 'Property', name: 'border' },
|
||||
// { type: 'Type', name: 'color' },
|
||||
// { type: 'Type', name: 'named-color' },
|
||||
// { type: 'Keyword', name: 'red' } ]
|
||||
```
|
||||
|
||||
### Exports
|
||||
|
||||
Is it possible to import just a needed part of library like a parser or a walker. That's might useful for loading time or bundle size optimisations.
|
||||
|
||||
```js
|
||||
import * as tokenizer from 'css-tree/tokenizer';
|
||||
import * as parser from 'css-tree/parser';
|
||||
import * as walker from 'css-tree/walker';
|
||||
import * as lexer from 'css-tree/lexer';
|
||||
import * as definitionSyntax from 'css-tree/definition-syntax';
|
||||
import * as data from 'css-tree/definition-syntax-data';
|
||||
import * as dataPatch from 'css-tree/definition-syntax-data-patch';
|
||||
import * as utils from 'css-tree/utils';
|
||||
```
|
||||
|
||||
### Using in a browser
|
||||
|
||||
Bundles are available for use in a browser:
|
||||
|
||||
- `dist/csstree.js` – minified IIFE with `csstree` as global
|
||||
```html
|
||||
<script src="node_modules/css-tree/dist/csstree.js"></script>
|
||||
<script>
|
||||
csstree.parse('.example { color: green }');
|
||||
</script>
|
||||
```
|
||||
|
||||
- `dist/csstree.esm.js` – minified ES module
|
||||
```html
|
||||
<script type="module">
|
||||
import { parse } from 'node_modules/css-tree/dist/csstree.esm.js'
|
||||
parse('.example { color: green }');
|
||||
</script>
|
||||
```
|
||||
|
||||
One of CDN services like `unpkg` or `jsDelivr` can be used. By default (for short path) a ESM version is exposing. For IIFE version a full path to a bundle should be specified:
|
||||
|
||||
```html
|
||||
<!-- ESM -->
|
||||
<script type="module">
|
||||
import * as csstree from 'https://cdn.jsdelivr.net/npm/css-tree';
|
||||
import * as csstree from 'https://unpkg.com/css-tree';
|
||||
</script>
|
||||
|
||||
<!-- IIFE with an export to global -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/css-tree/dist/csstree.js"></script>
|
||||
<script src="https://unpkg.com/css-tree/dist/csstree.js"></script>
|
||||
```
|
||||
|
||||
## Top level API
|
||||
|
||||

|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
32
node_modules/css-tree/cjs/convertor/create.cjs
generated
vendored
Normal file
32
node_modules/css-tree/cjs/convertor/create.cjs
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
const List = require('../utils/List.cjs');
|
||||
|
||||
function createConvertor(walk) {
|
||||
return {
|
||||
fromPlainObject(ast) {
|
||||
walk(ast, {
|
||||
enter(node) {
|
||||
if (node.children && node.children instanceof List.List === false) {
|
||||
node.children = new List.List().fromArray(node.children);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ast;
|
||||
},
|
||||
toPlainObject(ast) {
|
||||
walk(ast, {
|
||||
leave(node) {
|
||||
if (node.children && node.children instanceof List.List) {
|
||||
node.children = node.children.toArray();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ast;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
exports.createConvertor = createConvertor;
|
||||
8
node_modules/css-tree/cjs/convertor/index.cjs
generated
vendored
Normal file
8
node_modules/css-tree/cjs/convertor/index.cjs
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
const create = require('./create.cjs');
|
||||
const index$1 = require('../walker/index.cjs');
|
||||
|
||||
const index = create.createConvertor(index$1);
|
||||
|
||||
module.exports = index;
|
||||
7
node_modules/css-tree/cjs/data-patch.cjs
generated
vendored
Normal file
7
node_modules/css-tree/cjs/data-patch.cjs
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const patch = require('../data/patch.json');
|
||||
|
||||
const patch$1 = patch;
|
||||
|
||||
module.exports = patch$1;
|
||||
120
node_modules/css-tree/cjs/data.cjs
generated
vendored
Normal file
120
node_modules/css-tree/cjs/data.cjs
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
'use strict';
|
||||
|
||||
const dataPatch = require('./data-patch.cjs');
|
||||
|
||||
const mdnAtrules = require('mdn-data/css/at-rules.json');
|
||||
const mdnProperties = require('mdn-data/css/properties.json');
|
||||
const mdnSyntaxes = require('mdn-data/css/syntaxes.json');
|
||||
|
||||
const hasOwn = Object.hasOwn || ((object, property) => Object.prototype.hasOwnProperty.call(object, property));
|
||||
const extendSyntax = /^\s*\|\s*/;
|
||||
|
||||
function preprocessAtrules(dict) {
|
||||
const result = Object.create(null);
|
||||
|
||||
for (const [atruleName, atrule] of Object.entries(dict)) {
|
||||
let descriptors = null;
|
||||
|
||||
if (atrule.descriptors) {
|
||||
descriptors = Object.create(null);
|
||||
|
||||
for (const [name, descriptor] of Object.entries(atrule.descriptors)) {
|
||||
descriptors[name] = descriptor.syntax;
|
||||
}
|
||||
}
|
||||
|
||||
result[atruleName.substr(1)] = {
|
||||
prelude: atrule.syntax.trim().replace(/\{(.|\s)+\}/, '').match(/^@\S+\s+([^;\{]*)/)[1].trim() || null,
|
||||
descriptors
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function patchDictionary(dict, patchDict) {
|
||||
const result = Object.create(null);
|
||||
|
||||
// copy all syntaxes for an original dict
|
||||
for (const [key, value] of Object.entries(dict)) {
|
||||
if (value) {
|
||||
result[key] = value.syntax || value;
|
||||
}
|
||||
}
|
||||
|
||||
// apply a patch
|
||||
for (const key of Object.keys(patchDict)) {
|
||||
if (hasOwn(dict, key)) {
|
||||
if (patchDict[key].syntax) {
|
||||
result[key] = extendSyntax.test(patchDict[key].syntax)
|
||||
? result[key] + ' ' + patchDict[key].syntax.trim()
|
||||
: patchDict[key].syntax;
|
||||
} else {
|
||||
delete result[key];
|
||||
}
|
||||
} else {
|
||||
if (patchDict[key].syntax) {
|
||||
result[key] = patchDict[key].syntax.replace(extendSyntax, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function preprocessPatchAtrulesDescritors(declarations) {
|
||||
const result = {};
|
||||
|
||||
for (const [key, value] of Object.entries(declarations || {})) {
|
||||
result[key] = typeof value === 'string'
|
||||
? { syntax: value }
|
||||
: value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function patchAtrules(dict, patchDict) {
|
||||
const result = {};
|
||||
|
||||
// copy all syntaxes for an original dict
|
||||
for (const key in dict) {
|
||||
if (patchDict[key] === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const atrulePatch = patchDict[key] || {};
|
||||
|
||||
result[key] = {
|
||||
prelude: key in patchDict && 'prelude' in atrulePatch
|
||||
? atrulePatch.prelude
|
||||
: dict[key].prelude || null,
|
||||
descriptors: patchDictionary(
|
||||
dict[key].descriptors || {},
|
||||
preprocessPatchAtrulesDescritors(atrulePatch.descriptors)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
// apply a patch
|
||||
for (const [key, atrulePatch] of Object.entries(patchDict)) {
|
||||
if (atrulePatch && !hasOwn(dict, key)) {
|
||||
result[key] = {
|
||||
prelude: atrulePatch.prelude || null,
|
||||
descriptors: atrulePatch.descriptors
|
||||
? patchDictionary({}, preprocessPatchAtrulesDescritors(atrulePatch.descriptors))
|
||||
: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const definitions = {
|
||||
types: patchDictionary(mdnSyntaxes, dataPatch.types),
|
||||
atrules: patchAtrules(preprocessAtrules(mdnAtrules), dataPatch.atrules),
|
||||
properties: patchDictionary(mdnProperties, dataPatch.properties)
|
||||
};
|
||||
|
||||
module.exports = definitions;
|
||||
16
node_modules/css-tree/cjs/definition-syntax/SyntaxError.cjs
generated
vendored
Normal file
16
node_modules/css-tree/cjs/definition-syntax/SyntaxError.cjs
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
const createCustomError = require('../utils/create-custom-error.cjs');
|
||||
|
||||
function SyntaxError(message, input, offset) {
|
||||
return Object.assign(createCustomError.createCustomError('SyntaxError', message), {
|
||||
input,
|
||||
offset,
|
||||
rawMessage: message,
|
||||
message: message + '\n' +
|
||||
' ' + input + '\n' +
|
||||
'--' + new Array((offset || input.length) + 1).join('-') + '^'
|
||||
});
|
||||
}
|
||||
|
||||
exports.SyntaxError = SyntaxError;
|
||||
139
node_modules/css-tree/cjs/definition-syntax/generate.cjs
generated
vendored
Normal file
139
node_modules/css-tree/cjs/definition-syntax/generate.cjs
generated
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
'use strict';
|
||||
|
||||
function noop(value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function generateMultiplier(multiplier) {
|
||||
const { min, max, comma } = multiplier;
|
||||
|
||||
if (min === 0 && max === 0) {
|
||||
return comma ? '#?' : '*';
|
||||
}
|
||||
|
||||
if (min === 0 && max === 1) {
|
||||
return '?';
|
||||
}
|
||||
|
||||
if (min === 1 && max === 0) {
|
||||
return comma ? '#' : '+';
|
||||
}
|
||||
|
||||
if (min === 1 && max === 1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
(comma ? '#' : '') +
|
||||
(min === max
|
||||
? '{' + min + '}'
|
||||
: '{' + min + ',' + (max !== 0 ? max : '') + '}'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function generateTypeOpts(node) {
|
||||
switch (node.type) {
|
||||
case 'Range':
|
||||
return (
|
||||
' [' +
|
||||
(node.min === null ? '-∞' : node.min) +
|
||||
',' +
|
||||
(node.max === null ? '∞' : node.max) +
|
||||
']'
|
||||
);
|
||||
|
||||
default:
|
||||
throw new Error('Unknown node type `' + node.type + '`');
|
||||
}
|
||||
}
|
||||
|
||||
function generateSequence(node, decorate, forceBraces, compact) {
|
||||
const combinator = node.combinator === ' ' || compact ? node.combinator : ' ' + node.combinator + ' ';
|
||||
const result = node.terms
|
||||
.map(term => internalGenerate(term, decorate, forceBraces, compact))
|
||||
.join(combinator);
|
||||
|
||||
if (node.explicit || forceBraces) {
|
||||
return (compact || result[0] === ',' ? '[' : '[ ') + result + (compact ? ']' : ' ]');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function internalGenerate(node, decorate, forceBraces, compact) {
|
||||
let result;
|
||||
|
||||
switch (node.type) {
|
||||
case 'Group':
|
||||
result =
|
||||
generateSequence(node, decorate, forceBraces, compact) +
|
||||
(node.disallowEmpty ? '!' : '');
|
||||
break;
|
||||
|
||||
case 'Multiplier':
|
||||
// return since node is a composition
|
||||
return (
|
||||
internalGenerate(node.term, decorate, forceBraces, compact) +
|
||||
decorate(generateMultiplier(node), node)
|
||||
);
|
||||
|
||||
case 'Boolean':
|
||||
result = '<boolean-expr[' + internalGenerate(node.term, decorate, forceBraces, compact) + ']>';
|
||||
break;
|
||||
|
||||
case 'Type':
|
||||
result = '<' + node.name + (node.opts ? decorate(generateTypeOpts(node.opts), node.opts) : '') + '>';
|
||||
break;
|
||||
|
||||
case 'Property':
|
||||
result = '<\'' + node.name + '\'>';
|
||||
break;
|
||||
|
||||
case 'Keyword':
|
||||
result = node.name;
|
||||
break;
|
||||
|
||||
case 'AtKeyword':
|
||||
result = '@' + node.name;
|
||||
break;
|
||||
|
||||
case 'Function':
|
||||
result = node.name + '(';
|
||||
break;
|
||||
|
||||
case 'String':
|
||||
case 'Token':
|
||||
result = node.value;
|
||||
break;
|
||||
|
||||
case 'Comma':
|
||||
result = ',';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error('Unknown node type `' + node.type + '`');
|
||||
}
|
||||
|
||||
return decorate(result, node);
|
||||
}
|
||||
|
||||
function generate(node, options) {
|
||||
let decorate = noop;
|
||||
let forceBraces = false;
|
||||
let compact = false;
|
||||
|
||||
if (typeof options === 'function') {
|
||||
decorate = options;
|
||||
} else if (options) {
|
||||
forceBraces = Boolean(options.forceBraces);
|
||||
compact = Boolean(options.compact);
|
||||
if (typeof options.decorate === 'function') {
|
||||
decorate = options.decorate;
|
||||
}
|
||||
}
|
||||
|
||||
return internalGenerate(node, decorate, forceBraces, compact);
|
||||
}
|
||||
|
||||
exports.generate = generate;
|
||||
13
node_modules/css-tree/cjs/definition-syntax/index.cjs
generated
vendored
Normal file
13
node_modules/css-tree/cjs/definition-syntax/index.cjs
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const SyntaxError = require('./SyntaxError.cjs');
|
||||
const generate = require('./generate.cjs');
|
||||
const parse = require('./parse.cjs');
|
||||
const walk = require('./walk.cjs');
|
||||
|
||||
|
||||
|
||||
exports.SyntaxError = SyntaxError.SyntaxError;
|
||||
exports.generate = generate.generate;
|
||||
exports.parse = parse.parse;
|
||||
exports.walk = walk.walk;
|
||||
556
node_modules/css-tree/cjs/definition-syntax/parse.cjs
generated
vendored
Normal file
556
node_modules/css-tree/cjs/definition-syntax/parse.cjs
generated
vendored
Normal file
@@ -0,0 +1,556 @@
|
||||
'use strict';
|
||||
|
||||
const scanner = require('./scanner.cjs');
|
||||
|
||||
const TAB = 9;
|
||||
const N = 10;
|
||||
const F = 12;
|
||||
const R = 13;
|
||||
const SPACE = 32;
|
||||
const EXCLAMATIONMARK = 33; // !
|
||||
const NUMBERSIGN = 35; // #
|
||||
const AMPERSAND = 38; // &
|
||||
const APOSTROPHE = 39; // '
|
||||
const LEFTPARENTHESIS = 40; // (
|
||||
const RIGHTPARENTHESIS = 41; // )
|
||||
const ASTERISK = 42; // *
|
||||
const PLUSSIGN = 43; // +
|
||||
const COMMA = 44; // ,
|
||||
const HYPERMINUS = 45; // -
|
||||
const LESSTHANSIGN = 60; // <
|
||||
const GREATERTHANSIGN = 62; // >
|
||||
const QUESTIONMARK = 63; // ?
|
||||
const COMMERCIALAT = 64; // @
|
||||
const LEFTSQUAREBRACKET = 91; // [
|
||||
const RIGHTSQUAREBRACKET = 93; // ]
|
||||
const LEFTCURLYBRACKET = 123; // {
|
||||
const VERTICALLINE = 124; // |
|
||||
const RIGHTCURLYBRACKET = 125; // }
|
||||
const INFINITY = 8734; // ∞
|
||||
const COMBINATOR_PRECEDENCE = {
|
||||
' ': 1,
|
||||
'&&': 2,
|
||||
'||': 3,
|
||||
'|': 4
|
||||
};
|
||||
|
||||
function readMultiplierRange(scanner) {
|
||||
let min = null;
|
||||
let max = null;
|
||||
|
||||
scanner.eat(LEFTCURLYBRACKET);
|
||||
scanner.skipWs();
|
||||
|
||||
min = scanner.scanNumber(scanner);
|
||||
scanner.skipWs();
|
||||
|
||||
if (scanner.charCode() === COMMA) {
|
||||
scanner.pos++;
|
||||
scanner.skipWs();
|
||||
|
||||
if (scanner.charCode() !== RIGHTCURLYBRACKET) {
|
||||
max = scanner.scanNumber(scanner);
|
||||
scanner.skipWs();
|
||||
}
|
||||
} else {
|
||||
max = min;
|
||||
}
|
||||
|
||||
scanner.eat(RIGHTCURLYBRACKET);
|
||||
|
||||
return {
|
||||
min: Number(min),
|
||||
max: max ? Number(max) : 0
|
||||
};
|
||||
}
|
||||
|
||||
function readMultiplier(scanner) {
|
||||
let range = null;
|
||||
let comma = false;
|
||||
|
||||
switch (scanner.charCode()) {
|
||||
case ASTERISK:
|
||||
scanner.pos++;
|
||||
|
||||
range = {
|
||||
min: 0,
|
||||
max: 0
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case PLUSSIGN:
|
||||
scanner.pos++;
|
||||
|
||||
range = {
|
||||
min: 1,
|
||||
max: 0
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case QUESTIONMARK:
|
||||
scanner.pos++;
|
||||
|
||||
range = {
|
||||
min: 0,
|
||||
max: 1
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case NUMBERSIGN:
|
||||
scanner.pos++;
|
||||
|
||||
comma = true;
|
||||
|
||||
if (scanner.charCode() === LEFTCURLYBRACKET) {
|
||||
range = readMultiplierRange(scanner);
|
||||
} else if (scanner.charCode() === QUESTIONMARK) {
|
||||
// https://www.w3.org/TR/css-values-4/#component-multipliers
|
||||
// > the # and ? multipliers may be stacked as #?
|
||||
// In this case just treat "#?" as a single multiplier
|
||||
// { min: 0, max: 0, comma: true }
|
||||
scanner.pos++;
|
||||
range = {
|
||||
min: 0,
|
||||
max: 0
|
||||
};
|
||||
} else {
|
||||
range = {
|
||||
min: 1,
|
||||
max: 0
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case LEFTCURLYBRACKET:
|
||||
range = readMultiplierRange(scanner);
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Multiplier',
|
||||
comma,
|
||||
min: range.min,
|
||||
max: range.max,
|
||||
term: null
|
||||
};
|
||||
}
|
||||
|
||||
function maybeMultiplied(scanner, node) {
|
||||
const multiplier = readMultiplier(scanner);
|
||||
|
||||
if (multiplier !== null) {
|
||||
multiplier.term = node;
|
||||
|
||||
// https://www.w3.org/TR/css-values-4/#component-multipliers
|
||||
// > The + and # multipliers may be stacked as +#;
|
||||
// Represent "+#" as nested multipliers:
|
||||
// { ...<multiplier #>,
|
||||
// term: {
|
||||
// ...<multipler +>,
|
||||
// term: node
|
||||
// }
|
||||
// }
|
||||
if (scanner.charCode() === NUMBERSIGN &&
|
||||
scanner.charCodeAt(scanner.pos - 1) === PLUSSIGN) {
|
||||
return maybeMultiplied(scanner, multiplier);
|
||||
}
|
||||
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function maybeToken(scanner) {
|
||||
const ch = scanner.peek();
|
||||
|
||||
if (ch === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return maybeMultiplied(scanner, {
|
||||
type: 'Token',
|
||||
value: ch
|
||||
});
|
||||
}
|
||||
|
||||
function readProperty(scanner) {
|
||||
let name;
|
||||
|
||||
scanner.eat(LESSTHANSIGN);
|
||||
scanner.eat(APOSTROPHE);
|
||||
|
||||
name = scanner.scanWord();
|
||||
|
||||
scanner.eat(APOSTROPHE);
|
||||
scanner.eat(GREATERTHANSIGN);
|
||||
|
||||
return maybeMultiplied(scanner, {
|
||||
type: 'Property',
|
||||
name
|
||||
});
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-values-3/#numeric-ranges
|
||||
// 4.1. Range Restrictions and Range Definition Notation
|
||||
//
|
||||
// Range restrictions can be annotated in the numeric type notation using CSS bracketed
|
||||
// range notation—[min,max]—within the angle brackets, after the identifying keyword,
|
||||
// indicating a closed range between (and including) min and max.
|
||||
// For example, <integer [0, 10]> indicates an integer between 0 and 10, inclusive.
|
||||
function readTypeRange(scanner) {
|
||||
// use null for Infinity to make AST format JSON serializable/deserializable
|
||||
let min = null; // -Infinity
|
||||
let max = null; // Infinity
|
||||
let sign = 1;
|
||||
|
||||
scanner.eat(LEFTSQUAREBRACKET);
|
||||
|
||||
if (scanner.charCode() === HYPERMINUS) {
|
||||
scanner.peek();
|
||||
sign = -1;
|
||||
}
|
||||
|
||||
if (sign == -1 && scanner.charCode() === INFINITY) {
|
||||
scanner.peek();
|
||||
} else {
|
||||
min = sign * Number(scanner.scanNumber(scanner));
|
||||
|
||||
if (scanner.isNameCharCode()) {
|
||||
min += scanner.scanWord();
|
||||
}
|
||||
}
|
||||
|
||||
scanner.skipWs();
|
||||
scanner.eat(COMMA);
|
||||
scanner.skipWs();
|
||||
|
||||
if (scanner.charCode() === INFINITY) {
|
||||
scanner.peek();
|
||||
} else {
|
||||
sign = 1;
|
||||
|
||||
if (scanner.charCode() === HYPERMINUS) {
|
||||
scanner.peek();
|
||||
sign = -1;
|
||||
}
|
||||
|
||||
max = sign * Number(scanner.scanNumber(scanner));
|
||||
|
||||
if (scanner.isNameCharCode()) {
|
||||
max += scanner.scanWord();
|
||||
}
|
||||
}
|
||||
|
||||
scanner.eat(RIGHTSQUAREBRACKET);
|
||||
|
||||
return {
|
||||
type: 'Range',
|
||||
min,
|
||||
max
|
||||
};
|
||||
}
|
||||
|
||||
function readType(scanner) {
|
||||
let name;
|
||||
let opts = null;
|
||||
|
||||
scanner.eat(LESSTHANSIGN);
|
||||
name = scanner.scanWord();
|
||||
|
||||
// https://drafts.csswg.org/css-values-5/#boolean
|
||||
if (name === 'boolean-expr') {
|
||||
scanner.eat(LEFTSQUAREBRACKET);
|
||||
|
||||
const implicitGroup = readImplicitGroup(scanner, RIGHTSQUAREBRACKET);
|
||||
|
||||
scanner.eat(RIGHTSQUAREBRACKET);
|
||||
scanner.eat(GREATERTHANSIGN);
|
||||
|
||||
return maybeMultiplied(scanner, {
|
||||
type: 'Boolean',
|
||||
term: implicitGroup.terms.length === 1
|
||||
? implicitGroup.terms[0]
|
||||
: implicitGroup
|
||||
});
|
||||
}
|
||||
|
||||
if (scanner.charCode() === LEFTPARENTHESIS &&
|
||||
scanner.nextCharCode() === RIGHTPARENTHESIS) {
|
||||
scanner.pos += 2;
|
||||
name += '()';
|
||||
}
|
||||
|
||||
if (scanner.charCodeAt(scanner.findWsEnd(scanner.pos)) === LEFTSQUAREBRACKET) {
|
||||
scanner.skipWs();
|
||||
opts = readTypeRange(scanner);
|
||||
}
|
||||
|
||||
scanner.eat(GREATERTHANSIGN);
|
||||
|
||||
return maybeMultiplied(scanner, {
|
||||
type: 'Type',
|
||||
name,
|
||||
opts
|
||||
});
|
||||
}
|
||||
|
||||
function readKeywordOrFunction(scanner) {
|
||||
const name = scanner.scanWord();
|
||||
|
||||
if (scanner.charCode() === LEFTPARENTHESIS) {
|
||||
scanner.pos++;
|
||||
|
||||
return {
|
||||
type: 'Function',
|
||||
name
|
||||
};
|
||||
}
|
||||
|
||||
return maybeMultiplied(scanner, {
|
||||
type: 'Keyword',
|
||||
name
|
||||
});
|
||||
}
|
||||
|
||||
function regroupTerms(terms, combinators) {
|
||||
function createGroup(terms, combinator) {
|
||||
return {
|
||||
type: 'Group',
|
||||
terms,
|
||||
combinator,
|
||||
disallowEmpty: false,
|
||||
explicit: false
|
||||
};
|
||||
}
|
||||
|
||||
let combinator;
|
||||
|
||||
combinators = Object.keys(combinators)
|
||||
.sort((a, b) => COMBINATOR_PRECEDENCE[a] - COMBINATOR_PRECEDENCE[b]);
|
||||
|
||||
while (combinators.length > 0) {
|
||||
combinator = combinators.shift();
|
||||
|
||||
let i = 0;
|
||||
let subgroupStart = 0;
|
||||
|
||||
for (; i < terms.length; i++) {
|
||||
const term = terms[i];
|
||||
|
||||
if (term.type === 'Combinator') {
|
||||
if (term.value === combinator) {
|
||||
if (subgroupStart === -1) {
|
||||
subgroupStart = i - 1;
|
||||
}
|
||||
terms.splice(i, 1);
|
||||
i--;
|
||||
} else {
|
||||
if (subgroupStart !== -1 && i - subgroupStart > 1) {
|
||||
terms.splice(
|
||||
subgroupStart,
|
||||
i - subgroupStart,
|
||||
createGroup(terms.slice(subgroupStart, i), combinator)
|
||||
);
|
||||
i = subgroupStart + 1;
|
||||
}
|
||||
subgroupStart = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subgroupStart !== -1 && combinators.length) {
|
||||
terms.splice(
|
||||
subgroupStart,
|
||||
i - subgroupStart,
|
||||
createGroup(terms.slice(subgroupStart, i), combinator)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return combinator;
|
||||
}
|
||||
|
||||
function readImplicitGroup(scanner, stopCharCode) {
|
||||
const combinators = Object.create(null);
|
||||
const terms = [];
|
||||
let token;
|
||||
let prevToken = null;
|
||||
let prevTokenPos = scanner.pos;
|
||||
|
||||
while (scanner.charCode() !== stopCharCode && (token = peek(scanner, stopCharCode))) {
|
||||
if (token.type !== 'Spaces') {
|
||||
if (token.type === 'Combinator') {
|
||||
// check for combinator in group beginning and double combinator sequence
|
||||
if (prevToken === null || prevToken.type === 'Combinator') {
|
||||
scanner.pos = prevTokenPos;
|
||||
scanner.error('Unexpected combinator');
|
||||
}
|
||||
|
||||
combinators[token.value] = true;
|
||||
} else if (prevToken !== null && prevToken.type !== 'Combinator') {
|
||||
combinators[' '] = true; // a b
|
||||
terms.push({
|
||||
type: 'Combinator',
|
||||
value: ' '
|
||||
});
|
||||
}
|
||||
|
||||
terms.push(token);
|
||||
prevToken = token;
|
||||
prevTokenPos = scanner.pos;
|
||||
}
|
||||
}
|
||||
|
||||
// check for combinator in group ending
|
||||
if (prevToken !== null && prevToken.type === 'Combinator') {
|
||||
scanner.pos -= prevTokenPos;
|
||||
scanner.error('Unexpected combinator');
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Group',
|
||||
terms,
|
||||
combinator: regroupTerms(terms, combinators) || ' ',
|
||||
disallowEmpty: false,
|
||||
explicit: false
|
||||
};
|
||||
}
|
||||
|
||||
function readGroup(scanner, stopCharCode) {
|
||||
let result;
|
||||
|
||||
scanner.eat(LEFTSQUAREBRACKET);
|
||||
result = readImplicitGroup(scanner, stopCharCode);
|
||||
scanner.eat(RIGHTSQUAREBRACKET);
|
||||
|
||||
result.explicit = true;
|
||||
|
||||
if (scanner.charCode() === EXCLAMATIONMARK) {
|
||||
scanner.pos++;
|
||||
result.disallowEmpty = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function peek(scanner, stopCharCode) {
|
||||
let code = scanner.charCode();
|
||||
|
||||
switch (code) {
|
||||
case RIGHTSQUAREBRACKET:
|
||||
// don't eat, stop scan a group
|
||||
break;
|
||||
|
||||
case LEFTSQUAREBRACKET:
|
||||
return maybeMultiplied(scanner, readGroup(scanner, stopCharCode));
|
||||
|
||||
case LESSTHANSIGN:
|
||||
return scanner.nextCharCode() === APOSTROPHE
|
||||
? readProperty(scanner)
|
||||
: readType(scanner);
|
||||
|
||||
case VERTICALLINE:
|
||||
return {
|
||||
type: 'Combinator',
|
||||
value: scanner.substringToPos(
|
||||
scanner.pos + (scanner.nextCharCode() === VERTICALLINE ? 2 : 1)
|
||||
)
|
||||
};
|
||||
|
||||
case AMPERSAND:
|
||||
scanner.pos++;
|
||||
scanner.eat(AMPERSAND);
|
||||
|
||||
return {
|
||||
type: 'Combinator',
|
||||
value: '&&'
|
||||
};
|
||||
|
||||
case COMMA:
|
||||
scanner.pos++;
|
||||
return {
|
||||
type: 'Comma'
|
||||
};
|
||||
|
||||
case APOSTROPHE:
|
||||
return maybeMultiplied(scanner, {
|
||||
type: 'String',
|
||||
value: scanner.scanString()
|
||||
});
|
||||
|
||||
case SPACE:
|
||||
case TAB:
|
||||
case N:
|
||||
case R:
|
||||
case F:
|
||||
return {
|
||||
type: 'Spaces',
|
||||
value: scanner.scanSpaces()
|
||||
};
|
||||
|
||||
case COMMERCIALAT:
|
||||
code = scanner.nextCharCode();
|
||||
|
||||
if (scanner.isNameCharCode(code)) {
|
||||
scanner.pos++;
|
||||
return {
|
||||
type: 'AtKeyword',
|
||||
name: scanner.scanWord()
|
||||
};
|
||||
}
|
||||
|
||||
return maybeToken(scanner);
|
||||
|
||||
case ASTERISK:
|
||||
case PLUSSIGN:
|
||||
case QUESTIONMARK:
|
||||
case NUMBERSIGN:
|
||||
case EXCLAMATIONMARK:
|
||||
// prohibited tokens (used as a multiplier start)
|
||||
break;
|
||||
|
||||
case LEFTCURLYBRACKET:
|
||||
// LEFTCURLYBRACKET is allowed since mdn/data uses it w/o quoting
|
||||
// check next char isn't a number, because it's likely a disjoined multiplier
|
||||
code = scanner.nextCharCode();
|
||||
|
||||
if (code < 48 || code > 57) {
|
||||
return maybeToken(scanner);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
if (scanner.isNameCharCode(code)) {
|
||||
return readKeywordOrFunction(scanner);
|
||||
}
|
||||
|
||||
return maybeToken(scanner);
|
||||
}
|
||||
}
|
||||
|
||||
function parse(source) {
|
||||
const scanner$1 = new scanner.Scanner(source);
|
||||
const result = readImplicitGroup(scanner$1);
|
||||
|
||||
if (scanner$1.pos !== source.length) {
|
||||
scanner$1.error('Unexpected input');
|
||||
}
|
||||
|
||||
// reduce redundant groups with single group term
|
||||
if (result.terms.length === 1 && result.terms[0].type === 'Group') {
|
||||
return result.terms[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
exports.parse = parse;
|
||||
113
node_modules/css-tree/cjs/definition-syntax/scanner.cjs
generated
vendored
Normal file
113
node_modules/css-tree/cjs/definition-syntax/scanner.cjs
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
'use strict';
|
||||
|
||||
const SyntaxError = require('./SyntaxError.cjs');
|
||||
|
||||
const TAB = 9;
|
||||
const N = 10;
|
||||
const F = 12;
|
||||
const R = 13;
|
||||
const SPACE = 32;
|
||||
const NAME_CHAR = new Uint8Array(128).map((_, idx) =>
|
||||
/[a-zA-Z0-9\-]/.test(String.fromCharCode(idx)) ? 1 : 0
|
||||
);
|
||||
|
||||
class Scanner {
|
||||
constructor(str) {
|
||||
this.str = str;
|
||||
this.pos = 0;
|
||||
}
|
||||
|
||||
charCodeAt(pos) {
|
||||
return pos < this.str.length ? this.str.charCodeAt(pos) : 0;
|
||||
}
|
||||
charCode() {
|
||||
return this.charCodeAt(this.pos);
|
||||
}
|
||||
isNameCharCode(code = this.charCode()) {
|
||||
return code < 128 && NAME_CHAR[code] === 1;
|
||||
}
|
||||
nextCharCode() {
|
||||
return this.charCodeAt(this.pos + 1);
|
||||
}
|
||||
nextNonWsCode(pos) {
|
||||
return this.charCodeAt(this.findWsEnd(pos));
|
||||
}
|
||||
skipWs() {
|
||||
this.pos = this.findWsEnd(this.pos);
|
||||
}
|
||||
findWsEnd(pos) {
|
||||
for (; pos < this.str.length; pos++) {
|
||||
const code = this.str.charCodeAt(pos);
|
||||
if (code !== R && code !== N && code !== F && code !== SPACE && code !== TAB) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
substringToPos(end) {
|
||||
return this.str.substring(this.pos, this.pos = end);
|
||||
}
|
||||
eat(code) {
|
||||
if (this.charCode() !== code) {
|
||||
this.error('Expect `' + String.fromCharCode(code) + '`');
|
||||
}
|
||||
|
||||
this.pos++;
|
||||
}
|
||||
peek() {
|
||||
return this.pos < this.str.length ? this.str.charAt(this.pos++) : '';
|
||||
}
|
||||
error(message) {
|
||||
throw new SyntaxError.SyntaxError(message, this.str, this.pos);
|
||||
}
|
||||
|
||||
scanSpaces() {
|
||||
return this.substringToPos(this.findWsEnd(this.pos));
|
||||
}
|
||||
scanWord() {
|
||||
let end = this.pos;
|
||||
|
||||
for (; end < this.str.length; end++) {
|
||||
const code = this.str.charCodeAt(end);
|
||||
if (code >= 128 || NAME_CHAR[code] === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.pos === end) {
|
||||
this.error('Expect a keyword');
|
||||
}
|
||||
|
||||
return this.substringToPos(end);
|
||||
}
|
||||
scanNumber() {
|
||||
let end = this.pos;
|
||||
|
||||
for (; end < this.str.length; end++) {
|
||||
const code = this.str.charCodeAt(end);
|
||||
|
||||
if (code < 48 || code > 57) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.pos === end) {
|
||||
this.error('Expect a number');
|
||||
}
|
||||
|
||||
return this.substringToPos(end);
|
||||
}
|
||||
scanString() {
|
||||
const end = this.str.indexOf('\'', this.pos + 1);
|
||||
|
||||
if (end === -1) {
|
||||
this.pos = this.str.length;
|
||||
this.error('Expect an apostrophe');
|
||||
}
|
||||
|
||||
return this.substringToPos(end + 1);
|
||||
}
|
||||
}
|
||||
|
||||
exports.Scanner = Scanner;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user