mirror of
https://github.com/iconify/iconify.git
synced 2025-01-06 07:20:40 +00:00
Web component: first commit
This commit is contained in:
parent
2c2a9647fa
commit
b183dade9d
@ -6,6 +6,7 @@
|
||||
"packages/api-redundancy",
|
||||
"packages/utils",
|
||||
"packages/core",
|
||||
"packages/icon",
|
||||
"packages/*",
|
||||
"demo/*",
|
||||
"debug_packages/*"
|
||||
|
3
packages/icon/.eslintignore
Normal file
3
packages/icon/.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
lib
|
||||
dist
|
||||
tests-compiled
|
24
packages/icon/.eslintrc.js
Normal file
24
packages/icon/.eslintrc.js
Normal file
@ -0,0 +1,24 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
|
||||
globals: {
|
||||
Atomics: 'readonly',
|
||||
SharedArrayBuffer: 'readonly',
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
rules: {
|
||||
'no-mixed-spaces-and-tabs': ['off'],
|
||||
'no-unused-vars': ['off'],
|
||||
// '@typescript-eslint/no-unused-vars-experimental': ['error'],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['src/**/*.ts', 'tests/*.ts'],
|
||||
},
|
||||
],
|
||||
};
|
8
packages/icon/.gitignore
vendored
Normal file
8
packages/icon/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
yarn.lock
|
||||
tsconfig.tsbuildinfo
|
||||
node_modules
|
||||
dist
|
||||
lib
|
||||
tests-compiled
|
17
packages/icon/.npmignore
Normal file
17
packages/icon/.npmignore
Normal file
@ -0,0 +1,17 @@
|
||||
.DS_Store
|
||||
.eslintignore
|
||||
.eslintrc.js
|
||||
api-extractor*.json
|
||||
tsconfig*.json
|
||||
rollup.config.js
|
||||
build.js
|
||||
npm-debug.log
|
||||
yarn.lock
|
||||
tsconfig.tsbuildinfo
|
||||
jest.config.js
|
||||
node_modules
|
||||
src
|
||||
lib
|
||||
tests
|
||||
tests-compiled
|
||||
demo
|
341
packages/icon/README.md
Normal file
341
packages/icon/README.md
Normal file
@ -0,0 +1,341 @@
|
||||
# What is Iconify?
|
||||
|
||||
Iconify is the most versatile icon framework.
|
||||
|
||||
- Unified icon framework that can be used with any icon library.
|
||||
- Out of the box includes 100+ icon sets with more than 100,000 icons.
|
||||
- Embed icons in HTML with SVG framework or components for front-end frameworks.
|
||||
- Embed icons in designs with plug-ins for Figma, Sketch and Adobe XD.
|
||||
- Add icon search to your applications with Iconify Icon Finder.
|
||||
|
||||
For more information visit [https://iconify.design/](https://iconify.design/).
|
||||
|
||||
# Iconify SVG framework
|
||||
|
||||
There are many fonts and SVG sets available, but they all have one thing in common: using any font or SVG set limits you to icons that are included in that set and forces browsers to load entire font or icons set. That limits developers to one or two fonts or icon sets.
|
||||
|
||||
Iconify uses a new innovative approach to loading icons. Unlike fonts and SVG frameworks, Iconify only loads icons that are used on the page instead of loading entire fonts. How is it done? By serving icons dynamically from publicly available JSON API (you can make a copy of script and API if you prefer to keep everything on your servers).
|
||||
|
||||
Iconify SVG framework is designed to be as easy to use as possible.
|
||||
|
||||
Add this line to your page to load Iconify SVG framework (you can add it to `<head>` section of the page or before `</body>`):
|
||||
|
||||
```html
|
||||
<script src="https://code.iconify.design/2/0.0.1-dev/iconify.min.js"></script>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/@iconify/iconify@0.0.1-dev/dist/iconify.min.js"></script>
|
||||
```
|
||||
|
||||
or, if you are building a project with something like WebPack or Rollup, you can include the script by installing `@iconify/iconify` as a dependency and importing it in your project:
|
||||
|
||||
```js
|
||||
import Iconify from '@iconify/iconify';
|
||||
```
|
||||
|
||||
To add any icon, write something like this:
|
||||
|
||||
```html
|
||||
<span class="iconify" data-icon="eva:people-outline"></span>
|
||||
```
|
||||
|
||||
![Sample](https://iconify.design/assets/images/eva-people-outline.svg)
|
||||
|
||||
or this:
|
||||
|
||||
```html
|
||||
<span class="iconify-inline" data-icon="fa-solid:home"></span>
|
||||
<a href="#">Return home!</a>
|
||||
```
|
||||
|
||||
![Screenshot](https://iconify.design/assets/images/inline-sample.png)
|
||||
|
||||
That is it. Change `data-icon` value to the name of the icon you want to use. There are over 100,000 premade icons to choose from, including FontAwesome, Material Design Icons, Tabler Icons, Box Icons, Unicons, Bootstrap Icons and even several emoji sets.
|
||||
|
||||
Do you want to make your own icon sets? Everything you need is [available on GitHub](https://github.com/iconify): tools for creating custom icon sets, Iconify API application and documentation to help you.
|
||||
|
||||
## Full documentation
|
||||
|
||||
Below is a shortened version of documentation.
|
||||
|
||||
Full documentation is available on Iconify website:
|
||||
|
||||
- [SVG framework documentation](https://docs.iconify.design/implementations/svg-framework/).
|
||||
- [Iconify API documentation](https://docs.iconify.design/sources/api/).
|
||||
- [Creating icon bundles](https://docs.iconify.design/sources/bundles/).
|
||||
- [Iconify Tools documentation](https://docs.iconify.design/tools/node/).
|
||||
|
||||
## How does it work?
|
||||
|
||||
The syntax is similar to icon fonts. Instead of inserting `SVG` in the document, you write a placeholder element, such `SPAN` or `I`.
|
||||
|
||||
Iconify SVG framework finds those placeholders and uses the following logic to parse them:
|
||||
|
||||
1. Retrieves icon name from `data-icon` attribute.
|
||||
2. Checks if icon exists. If not, it sends a request to Iconify API to retrieve icon data.
|
||||
3. Replaces placeholder element with `SVG`.
|
||||
|
||||
This is done in a fraction of a second. Iconify SVG framework watches DOM for changes, so whenever you add new placeholders, it immediately replaces them with `SVG`, making it easy to use with dynamic content, such as AJAX forms.
|
||||
|
||||
### Inline mode
|
||||
|
||||
Code examples above use different class names: the first example uses "iconify", the second example uses "iconify-inline".
|
||||
|
||||
What is the difference?
|
||||
|
||||
- "iconify" renders icon as is, so it behaves like an image.
|
||||
- "iconify-inline" renders adds vertical alignment to the icon, making it behave like text (inline mode).
|
||||
|
||||
Usually, icon fonts do not render like normal images, they render like text. Text is aligned slightly below the baseline.
|
||||
|
||||
Visual example to show the difference between inline and block modes:
|
||||
|
||||
![Inline icon](https://iconify.design/assets/images/inline.png)
|
||||
|
||||
Why is the inline mode needed?
|
||||
|
||||
- To easily align icons within the text, such as emojis.
|
||||
- To make the transition from outdated icon fonts to SVG easier.
|
||||
|
||||
Use "iconify" for decorations, use "iconify-inline" if you want the icon to behave like an icon font.
|
||||
|
||||
#### data-inline attribute
|
||||
|
||||
In addition to using "iconify-inline" class, you can toggle inline mode with the `data-inline` attribute.
|
||||
|
||||
Set value to "true" to force inline mode, set value to "false" to use block mode.
|
||||
|
||||
Different ways to use block mode:
|
||||
|
||||
```html
|
||||
<span class="iconify" data-icon="eva:people-outline"></span>
|
||||
<span class="iconify" data-icon="eva:people-outline" data-inline="false"></span>
|
||||
```
|
||||
|
||||
Different ways to use inline mode:
|
||||
|
||||
```html
|
||||
<span class="iconify-inline" data-icon="eva:people-outline"></span>
|
||||
<span class="iconify" data-icon="eva:people-outline" data-inline="true"></span>
|
||||
<span
|
||||
class="iconify"
|
||||
data-icon="eva:people-outline"
|
||||
style="vertical-align: -0.125em"
|
||||
></span>
|
||||
```
|
||||
|
||||
## Iconify API
|
||||
|
||||
When you use an icon font, each visitor loads an entire font, even if your page only uses a few icons. This is a major downside of using icon fonts. That limits developers to one or two fonts or icon sets.
|
||||
|
||||
Unlike icon fonts, Iconify SVG framework does not load the entire icon set. Unlike fonts and SVG frameworks, Iconify only loads icons that are used on the current page instead of loading entire icon sets. How is it done? By serving icons dynamically from publicly available JSON API.
|
||||
|
||||
### Custom API
|
||||
|
||||
Relying on a third party service is often not an option. Many companies and developers prefer to keep everything on their own servers to have full control.
|
||||
|
||||
Iconify API and icon sets are all [available on GitHub](https://github.com/iconify), making it easy to host API on your own server.
|
||||
|
||||
For more details see [Iconify API documentation](https://docs.iconify.design/sources/api/).
|
||||
|
||||
You can also create custom Iconify API to serve your own icons. For more details see [hosting custom icons in Iconify documentation](https://iconify.design/docs/api-custom-hosting/).
|
||||
|
||||
### Using Iconify offline
|
||||
|
||||
While the default method of retrieving icons is to retrieve them from API, there are other options. Iconify SVG framework is designed to be as flexible as possible.
|
||||
|
||||
Easiest option to serve icons without API is by creating icon bundles.
|
||||
|
||||
Icon bundles are small scripts that you can load after Iconify SVG framework or bundle it together in one file.
|
||||
|
||||
For more details see [icon bundles in Iconify documentation](https://iconify.design/docs/icon-bundles/).
|
||||
|
||||
Another option is to import icons and bundle them with Iconify, similar to React and Vue components. Example:
|
||||
|
||||
```js
|
||||
// Installation: npm install --save-dev @iconify/iconify
|
||||
import Iconify from '@iconify/iconify/offline';
|
||||
// Installation: npm install --save-dev @iconify/icons-dashicons
|
||||
import adminUsers from '@iconify/icons-dashicons/admin-users';
|
||||
|
||||
// Unlike React and Vue components, in SVG framework each icon added with addIcon() name must have a
|
||||
// prefix and a name. In this example prefix is "dashicons" and name is "admin-users".
|
||||
Iconify.addIcon('dashicons:admin-users', adminUsers);
|
||||
```
|
||||
|
||||
```html
|
||||
<span class="iconify" data-icon="dashicons:admin-users"></span>
|
||||
```
|
||||
|
||||
See [Iconify for React](http://github.com/iconify/iconify/packages/react) documentation for more details.
|
||||
|
||||
## Color
|
||||
|
||||
There are 2 types of icons: monotone and coloured.
|
||||
|
||||
- Monotone icons are icons that use only 1 colour and you can change that colour. Most icon sets fall into this category: FontAwesome, Unicons, Material Design Icons, etc.
|
||||
- Coloured icons are icons that use the preset palette. Most emoji icons fall into this category: Noto Emoji, Emoji One, etc. You cannot change the palette for those icons.
|
||||
|
||||
Monotone icons use font colour, just like glyph fonts. To change colour, you can do this:
|
||||
|
||||
```html
|
||||
<span class="iconify icon-bell" data-icon="vaadin-bell"></span>
|
||||
```
|
||||
|
||||
and add this to CSS:
|
||||
|
||||
```css
|
||||
.icon-bell {
|
||||
color: #f80;
|
||||
}
|
||||
.icon-bell:hover {
|
||||
color: #f00;
|
||||
}
|
||||
```
|
||||
|
||||
Sample:
|
||||
|
||||
![Sample](https://iconify.design/samples/icon-color.png)
|
||||
|
||||
## Dimensions
|
||||
|
||||
By default all icons are scaled to 1em height. To control icon height use font-size:
|
||||
|
||||
```html
|
||||
<span class="iconify icon-clipboard" data-icon="emojione-clipboard"></span>
|
||||
```
|
||||
|
||||
and add this to css:
|
||||
|
||||
```css
|
||||
.icon-clipboard {
|
||||
font-size: 32px;
|
||||
}
|
||||
```
|
||||
|
||||
Sample:
|
||||
|
||||
![Sample](https://iconify.design/samples/icon-size.png)
|
||||
|
||||
you might also need to set line-height:
|
||||
|
||||
```css
|
||||
.icon-clipboard {
|
||||
font-size: 32px;
|
||||
line-height: 1em;
|
||||
}
|
||||
```
|
||||
|
||||
You can also set custom dimensions using `data-width` and `data-height` attributes:
|
||||
|
||||
```html
|
||||
<span
|
||||
data-icon="twemoji-ice-cream"
|
||||
data-width="32"
|
||||
data-height="32"
|
||||
class="iconify"
|
||||
></span>
|
||||
```
|
||||
|
||||
Sample:
|
||||
|
||||
![Sample](https://iconify.design/samples/icon-size2.png)
|
||||
|
||||
## Transformations
|
||||
|
||||
You can rotate and flip icon by adding `data-flip` and `data-rotate` attributes:
|
||||
|
||||
```html
|
||||
<span
|
||||
data-icon="twemoji-helicopter"
|
||||
class="iconify"
|
||||
data-flip="horizontal"
|
||||
></span>
|
||||
<span data-icon="twemoji-helicopter" class="iconify" data-rotate="90deg"></span>
|
||||
```
|
||||
|
||||
Possible values for `data-flip`: horizontal, vertical.
|
||||
Possible values for `data-rotate`: 90deg, 180deg, 270deg.
|
||||
|
||||
If you use both flip and rotation, the icon is flipped first, then rotated.
|
||||
|
||||
To use custom transformations use CSS transform rule. Add `!important` after rule to override the SVG inline style (inline style exists to fix an SVG rendering bug in Firefox browser).
|
||||
|
||||
```html
|
||||
<span data-icon="twemoji-helicopter" class="iconify icon-helicopter"></span>
|
||||
```
|
||||
|
||||
```css
|
||||
.icon-helicopter {
|
||||
transform: 45deg !important;
|
||||
}
|
||||
```
|
||||
|
||||
Samples:
|
||||
|
||||
![Sample](https://iconify.design/samples/icon-transform.png)
|
||||
|
||||
## Available icons
|
||||
|
||||
There are over 100,000 icons to choose from.
|
||||
|
||||
General collections (monotone icons):
|
||||
|
||||
- [Material Design Icons](https://icon-sets.iconify.design/mdi/) (5000+ icons)
|
||||
- [Unicons](https://icon-sets.iconify.design/uil/) (1000+ icons)
|
||||
- [Jam Icons](https://icon-sets.iconify.design/jam/) (900 icons)
|
||||
- [IonIcons](https://icon-sets.iconify.design/ion/) (1200+ icons)
|
||||
- [FontAwesome 4](https://icon-sets.iconify.design/fa/) and [FontAwesome 5](https://icon-sets.iconify.design/fa-solid/) (2000+ icons)
|
||||
- [Vaadin Icons](https://icon-sets.iconify.design/vaadin/) (600+ icons)
|
||||
- [Bootstrap Icons](https://icon-sets.iconify.design/bi/) (500+ icons)
|
||||
- [Feather Icons](https://icon-sets.iconify.design/feather/) and [Feather Icon](https://icon-sets.iconify.design/fe/) (500+ icons)
|
||||
- [IcoMoon Free](https://icon-sets.iconify.design/icomoon-free/) (400+ icons)
|
||||
- [Dashicons](https://icon-sets.iconify.design/dashicons/) (300 icons)
|
||||
|
||||
and many others.
|
||||
|
||||
Emoji collections (mostly colored icons):
|
||||
|
||||
- [Emoji One](https://icon-sets.iconify.design/emojione/) (1800+ colored version 2 icons, 1400+ monotone version 2 icons, 1200+ version 1 icons)
|
||||
- [OpenMoji](https://icon-sets.iconify.design/openmoji/) (3500+ icons)
|
||||
- [Noto Emoji](https://icon-sets.iconify.design/noto/) (2000+ icons for version 2, 2000+ icons for version 1)
|
||||
- [Twitter Emoji](https://icon-sets.iconify.design/twemoji/) (2000+ icons)
|
||||
- [Firefox OS Emoji](https://icon-sets.iconify.design/fxemoji/) (1000+ icons)
|
||||
|
||||
Also, there are several thematic collections, such as weather icons, map icons, etc.
|
||||
|
||||
You can use browse or search available icons on the Iconify website: https://icon-sets.iconify.design/
|
||||
|
||||
Click an icon to get HTML code.
|
||||
|
||||
## Iconify vs SVG vs glyph fonts
|
||||
|
||||
Why use Iconify instead of fonts or other frameworks?
|
||||
|
||||
There is a tutorial that explains all differences. See http://iconify.design/docs/iconify-svg-fonts/
|
||||
|
||||
## Browser support
|
||||
|
||||
Iconify supports all modern browsers.
|
||||
|
||||
Old browsers that are supported:
|
||||
|
||||
- IE 9+
|
||||
- iOS Safari for iOS 8+
|
||||
|
||||
IE 9, 10 and iOS 8 Safari do not support some modern functions that Iconify relies on. Iconify will automatically
|
||||
load polyfills for those browsers. All newer browsers do not require those polyfills.
|
||||
|
||||
## License
|
||||
|
||||
This package is licensed under MIT license.
|
||||
|
||||
`SPDX-License-Identifier: MIT`
|
||||
|
||||
This license does not apply to icons. Icons are released under different licenses, see each icon set for details.
|
||||
Icons available by default are all licensed under some kind of open-source or free license.
|
||||
|
||||
© 2022 Vjacheslav Trushkin / Iconify OÜ
|
45
packages/icon/api-extractor.json
Normal file
45
packages/icon/api-extractor.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
"mainEntryPointFilePath": "lib/iconify.d.ts",
|
||||
"bundledPackages": [
|
||||
"@iconify/types",
|
||||
"@iconify/core",
|
||||
"@iconify/utils",
|
||||
"@cyberalien/redundancy",
|
||||
"@iconify/api-redundancy"
|
||||
],
|
||||
"compiler": {},
|
||||
"apiReport": {
|
||||
"enabled": false
|
||||
},
|
||||
"docModel": {
|
||||
"enabled": false
|
||||
},
|
||||
"dtsRollup": {
|
||||
"enabled": true,
|
||||
"untrimmedFilePath": "<projectFolder>/dist/iconify.d.ts"
|
||||
},
|
||||
"tsdocMetadata": {
|
||||
"enabled": false
|
||||
},
|
||||
"messages": {
|
||||
"compilerMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
}
|
||||
},
|
||||
"extractorMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
},
|
||||
"ae-missing-release-tag": {
|
||||
"logLevel": "none"
|
||||
}
|
||||
},
|
||||
"tsdocMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
120
packages/icon/build.js
Normal file
120
packages/icon/build.js
Normal file
@ -0,0 +1,120 @@
|
||||
/* eslint-disable */
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const child_process = require('child_process');
|
||||
|
||||
const packagesDir = path.dirname(__dirname);
|
||||
|
||||
// List of commands to run
|
||||
const commands = [];
|
||||
|
||||
// Parse command line
|
||||
const compile = {
|
||||
core: false,
|
||||
lib: true,
|
||||
dist: true,
|
||||
api: true,
|
||||
};
|
||||
process.argv.slice(2).forEach((cmd) => {
|
||||
if (cmd.slice(0, 2) !== '--') {
|
||||
return;
|
||||
}
|
||||
const parts = cmd.slice(2).split('-');
|
||||
if (parts.length === 2) {
|
||||
// Parse 2 part commands like --with-lib
|
||||
const key = parts.pop();
|
||||
if (compile[key] === void 0) {
|
||||
return;
|
||||
}
|
||||
switch (parts.shift()) {
|
||||
case 'with':
|
||||
// enable module
|
||||
compile[key] = true;
|
||||
break;
|
||||
|
||||
case 'without':
|
||||
// disable module
|
||||
compile[key] = false;
|
||||
break;
|
||||
|
||||
case 'only':
|
||||
// disable other modules
|
||||
Object.keys(compile).forEach((key2) => {
|
||||
compile[key2] = key2 === key;
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Check if required modules in same monorepo are available
|
||||
const fileExists = (file) => {
|
||||
try {
|
||||
fs.statSync(file);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (compile.dist && !fileExists(packagesDir + '/iconify/lib/iconify.js')) {
|
||||
compile.lib = true;
|
||||
}
|
||||
|
||||
if (compile.api && !fileExists(packagesDir + '/iconify/lib/iconify.d.ts')) {
|
||||
compile.lib = true;
|
||||
}
|
||||
|
||||
if (compile.lib && !fileExists(packagesDir + '/core/lib/cache.mjs')) {
|
||||
compile.core = true;
|
||||
}
|
||||
|
||||
// Compile core before compiling this package
|
||||
if (compile.core) {
|
||||
commands.push({
|
||||
cmd: 'npm',
|
||||
args: ['run', 'build'],
|
||||
cwd: packagesDir + '/core',
|
||||
});
|
||||
}
|
||||
|
||||
// Add api2
|
||||
if (compile.api) {
|
||||
compile.api2 = true;
|
||||
}
|
||||
|
||||
// Compile other packages
|
||||
Object.keys(compile).forEach((key) => {
|
||||
if (key !== 'core' && compile[key]) {
|
||||
commands.push({
|
||||
cmd: 'npm',
|
||||
args: ['run', 'build:' + key],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Run next command
|
||||
*/
|
||||
const next = () => {
|
||||
const item = commands.shift();
|
||||
if (item === void 0) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (item.cwd === void 0) {
|
||||
item.cwd = __dirname;
|
||||
}
|
||||
|
||||
const result = child_process.spawnSync(item.cmd, item.args, {
|
||||
cwd: item.cwd,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
if (result.status === 0) {
|
||||
process.nextTick(next);
|
||||
} else {
|
||||
process.exit(result.status);
|
||||
}
|
||||
};
|
||||
next();
|
7
packages/icon/jest.config.js
Normal file
7
packages/icon/jest.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/tests/*-test.ts'],
|
||||
};
|
21
packages/icon/license.txt
Normal file
21
packages/icon/license.txt
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Vjacheslav Trushkin / Iconify OÜ
|
||||
|
||||
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.
|
14505
packages/icon/package-lock.json
generated
Normal file
14505
packages/icon/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
46
packages/icon/package.json
Normal file
46
packages/icon/package.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "iconify-icon",
|
||||
"description": "Icon web component that loads icon data on demand. Over 100,000 icons to choose from",
|
||||
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
|
||||
"version": "0.0.1-dev",
|
||||
"license": "MIT",
|
||||
"bugs": "https://github.com/iconify/iconify/issues",
|
||||
"homepage": "https://iconify.design/",
|
||||
"funding": "http://github.com/sponsors/cyberalien",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iconify/iconify.git",
|
||||
"directory": "packages/icon"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf lib dist tsconfig.tsbuildinfo",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"prebuild": "npm run lint && npm run clean",
|
||||
"build": "node build",
|
||||
"build:lib": "tsc -b",
|
||||
"build:dist": "rollup -c rollup.config.js",
|
||||
"test:jest": "jest --runInBand",
|
||||
"test:mjs": "echo \"TODO...\"",
|
||||
"test": "npm run test:jest && npm run test:mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/core": "^1.3.2",
|
||||
"@microsoft/api-extractor": "^7.19.5",
|
||||
"@rollup/plugin-buble": "^0.21.3",
|
||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/jsdom": "^16.2.14",
|
||||
"@types/node": "^17.0.22",
|
||||
"@typescript-eslint/eslint-plugin": "^5.16.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.11.0",
|
||||
"jest": "^28.0.0-alpha.7",
|
||||
"jsdom": "^19.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.70.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"ts-jest": "^27.1.4",
|
||||
"typescript": "^4.6.2"
|
||||
}
|
||||
}
|
146
packages/icon/rollup.config.js
Normal file
146
packages/icon/rollup.config.js
Normal file
@ -0,0 +1,146 @@
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import buble from '@rollup/plugin-buble';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
|
||||
const names = ['iconify', 'iconify.without-api'];
|
||||
const global = 'Iconify';
|
||||
|
||||
// Wrapper to export module as global and as ES module
|
||||
const header = `/**
|
||||
* (c) Iconify
|
||||
*
|
||||
* For the full copyright and license information, please view the license.txt or license.gpl.txt
|
||||
* files at https://github.com/iconify/iconify
|
||||
*
|
||||
* Licensed under Apache 2.0 or GPL 2.0 at your option.
|
||||
* If derivative product is not compatible with one of licenses, you can pick one of licenses.
|
||||
*
|
||||
* @license Apache 2.0
|
||||
* @license GPL 2.0
|
||||
* @version __iconify_version__
|
||||
*/`;
|
||||
|
||||
const defaultFooter = `
|
||||
// Export to window or web worker
|
||||
try {
|
||||
if (self.Iconify === void 0) {
|
||||
self.Iconify = Iconify;
|
||||
}
|
||||
} catch (err) {
|
||||
}`;
|
||||
|
||||
const iifeFooter = `
|
||||
// Export as ES module
|
||||
if (typeof exports === 'object') {
|
||||
try {
|
||||
exports.__esModule = true;
|
||||
exports.default = Iconify;
|
||||
for (var key in Iconify) {
|
||||
exports[key] = Iconify[key];
|
||||
}
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
|
||||
${defaultFooter}`;
|
||||
|
||||
// Get replacements
|
||||
const replacements = {
|
||||
preventAssignment: true,
|
||||
};
|
||||
const packageJSON = JSON.parse(readFileSync('package.json', 'utf8'));
|
||||
replacements['__iconify_version__'] = packageJSON.version;
|
||||
|
||||
// Update README.md
|
||||
let readme = readFileSync('README.md', 'utf8');
|
||||
const oldReadme = readme;
|
||||
const replaceCodeLink = (search) => {
|
||||
let start = 0;
|
||||
let pos;
|
||||
while ((pos = readme.indexOf(search, start)) !== -1) {
|
||||
start = pos + search.length;
|
||||
let pos2 = readme.indexOf('/', start);
|
||||
if (pos2 === -1) {
|
||||
return;
|
||||
}
|
||||
readme =
|
||||
readme.slice(0, start) + packageJSON.version + readme.slice(pos2);
|
||||
}
|
||||
};
|
||||
replaceCodeLink('/code.iconify.design/2/');
|
||||
replaceCodeLink('/@iconify/iconify@');
|
||||
|
||||
if (readme !== oldReadme) {
|
||||
console.log('Updatead README');
|
||||
writeFileSync('README.md', readme, 'utf8');
|
||||
}
|
||||
|
||||
// Export configuration
|
||||
const config = [];
|
||||
names.forEach((name) => {
|
||||
// Full and minified
|
||||
[false, true].forEach((minify) => {
|
||||
// Parse all formats
|
||||
['js', 'cjs', 'mjs'].forEach((ext) => {
|
||||
if (minify && ext !== 'js') {
|
||||
// Minify only .js files
|
||||
return;
|
||||
}
|
||||
|
||||
// Get export format and footer
|
||||
let format = ext;
|
||||
let footer = defaultFooter;
|
||||
switch (ext) {
|
||||
case 'js':
|
||||
format = 'iife';
|
||||
footer = iifeFooter;
|
||||
break;
|
||||
|
||||
case 'mjs':
|
||||
format = 'es';
|
||||
break;
|
||||
}
|
||||
|
||||
const item = {
|
||||
input: `lib/${name}.js`,
|
||||
output: [
|
||||
{
|
||||
file: `dist/${name}${minify ? '.min' : ''}.${ext}`,
|
||||
format,
|
||||
exports: 'named',
|
||||
name: global,
|
||||
banner: header,
|
||||
footer,
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
resolve({
|
||||
browser: true,
|
||||
}),
|
||||
replace(replacements),
|
||||
],
|
||||
};
|
||||
|
||||
if (ext === 'js') {
|
||||
// Support old browsers only in .js files.
|
||||
// Other files are for modern browsers that don't need it or
|
||||
// for bundlers that should handle old browser support themselves.
|
||||
item.plugins.push(
|
||||
buble({
|
||||
objectAssign: 'Object.assign',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (minify) {
|
||||
item.plugins.push(terser());
|
||||
}
|
||||
|
||||
config.push(item);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export default config;
|
60
packages/icon/src/attributes/customisations.ts
Normal file
60
packages/icon/src/attributes/customisations.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import type { FullIconCustomisations } from '@iconify/utils/lib/customisations';
|
||||
import { defaults } from '@iconify/utils/lib/customisations';
|
||||
import { rotateFromString } from '@iconify/utils/lib/customisations/rotate';
|
||||
import {
|
||||
flipFromString,
|
||||
alignmentFromString,
|
||||
} from '@iconify/utils/lib/customisations/shorthand';
|
||||
|
||||
// Remove 'inline' from defaults
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { inline, ...defaultCustomisations } = defaults;
|
||||
export { defaultCustomisations };
|
||||
|
||||
/**
|
||||
* Customisations that affect rendering
|
||||
*/
|
||||
export type RenderedIconCustomisations = Omit<FullIconCustomisations, 'inline'>;
|
||||
|
||||
/**
|
||||
* Get customisations
|
||||
*/
|
||||
export function getCustomisations(node: Element): RenderedIconCustomisations {
|
||||
const customisations = {
|
||||
...defaultCustomisations,
|
||||
};
|
||||
|
||||
const attr = (key: string, def: string | null) =>
|
||||
node.getAttribute(key) || def;
|
||||
|
||||
// Dimensions
|
||||
customisations.width = attr('width', null);
|
||||
customisations.height = attr('height', null);
|
||||
|
||||
// Rotation
|
||||
customisations.rotate = rotateFromString(attr('rotate', ''));
|
||||
|
||||
// Flip
|
||||
flipFromString(customisations, attr('flip', ''));
|
||||
|
||||
// Alignment
|
||||
alignmentFromString(customisations, attr('align', ''));
|
||||
|
||||
return customisations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if customisations have been updated
|
||||
*/
|
||||
export function haveCustomisationsChanged(
|
||||
value1: RenderedIconCustomisations,
|
||||
value2: RenderedIconCustomisations
|
||||
): boolean {
|
||||
for (const key in defaultCustomisations) {
|
||||
if (value1[key] !== value2[key]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
56
packages/icon/src/attributes/icon/index.ts
Normal file
56
packages/icon/src/attributes/icon/index.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import type { IconifyIcon } from '@iconify/types';
|
||||
import type { IconifyIconName } from '@iconify/utils/lib/icon/name';
|
||||
import { stringToIcon } from '@iconify/utils/lib/icon/name';
|
||||
import { getIconData } from '@iconify/core/lib/storage/functions';
|
||||
import { loadIcons } from '@iconify/core/lib/api/icons';
|
||||
import { testIconObject } from './object';
|
||||
import type { CurrentIconData } from './state';
|
||||
|
||||
/**
|
||||
* Callback
|
||||
*/
|
||||
export type IconOnLoadCallback = (
|
||||
value: unknown,
|
||||
name: IconifyIconName,
|
||||
data?: Required<IconifyIcon> | null
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Parse icon value, load if needed
|
||||
*/
|
||||
export function parseIconValue(
|
||||
value: unknown,
|
||||
onload: IconOnLoadCallback
|
||||
): CurrentIconData {
|
||||
// Check if icon name is valid
|
||||
const name = typeof value === 'string' ? stringToIcon(value, true) : null;
|
||||
if (!name) {
|
||||
// Test for serialised object
|
||||
const data = testIconObject(value);
|
||||
return {
|
||||
value,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
// Valid icon name: check if data is available
|
||||
const data = getIconData(name);
|
||||
if (data !== void 0) {
|
||||
return {
|
||||
value,
|
||||
name,
|
||||
data, // could be 'null' -> icon is missing
|
||||
};
|
||||
}
|
||||
|
||||
// Load icon
|
||||
const loading = loadIcons([name], () =>
|
||||
onload(value, name, getIconData(name))
|
||||
);
|
||||
|
||||
return {
|
||||
value,
|
||||
name,
|
||||
loading,
|
||||
};
|
||||
}
|
21
packages/icon/src/attributes/icon/object.ts
Normal file
21
packages/icon/src/attributes/icon/object.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import type { IconifyIcon } from '@iconify/types';
|
||||
import { iconDefaults } from '@iconify/utils/lib/icon';
|
||||
|
||||
/**
|
||||
* Test icon string
|
||||
*/
|
||||
export function testIconObject(
|
||||
value: unknown
|
||||
): Required<IconifyIcon> | undefined {
|
||||
try {
|
||||
const obj = typeof value === 'string' ? JSON.parse(value) : value;
|
||||
if (typeof obj.body === 'string') {
|
||||
return {
|
||||
...iconDefaults,
|
||||
...obj,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
28
packages/icon/src/attributes/icon/state.ts
Normal file
28
packages/icon/src/attributes/icon/state.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import type { IconifyIcon } from '@iconify/types';
|
||||
import type { IconifyIconName } from '@iconify/utils/lib/icon/name';
|
||||
import type { IconifyIconLoaderAbort } from '@iconify/core/lib/api/icons';
|
||||
|
||||
/**
|
||||
* Value for currently selected icon
|
||||
*/
|
||||
export interface CurrentIconData {
|
||||
// Value passed as parameter
|
||||
value: unknown;
|
||||
|
||||
// Data, if available. Can be null if icon is missing in API
|
||||
data?: Required<IconifyIcon> | null;
|
||||
|
||||
// Icon name as object, if `value` is a valid icon name
|
||||
name?: IconifyIconName | null;
|
||||
|
||||
// Loader abort function, set if icon is being loaded. Used only when `name` is valid
|
||||
loading?: IconifyIconLoaderAbort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as above, if
|
||||
*/
|
||||
export interface RenderedCurrentIconData extends CurrentIconData {
|
||||
// Full icon data
|
||||
data: Required<IconifyIcon>;
|
||||
}
|
6
packages/icon/src/attributes/inline.ts
Normal file
6
packages/icon/src/attributes/inline.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Check for inline
|
||||
*/
|
||||
export function getInline(node: Element): boolean {
|
||||
return node.hasAttribute('inline');
|
||||
}
|
24
packages/icon/src/attributes/mode.ts
Normal file
24
packages/icon/src/attributes/mode.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { ActualRenderMode, IconifyRenderMode } from './types';
|
||||
|
||||
/**
|
||||
* Get render mode
|
||||
*/
|
||||
export function getRenderMode(body: string, mode?: string): ActualRenderMode {
|
||||
switch (mode as ActualRenderMode | '') {
|
||||
// Force mode
|
||||
case 'svg':
|
||||
case 'bg':
|
||||
case 'mask':
|
||||
return mode as ActualRenderMode;
|
||||
}
|
||||
|
||||
// Check for animation, use 'style' for animated icons
|
||||
// (only <a>, which should be ignored or animations start with '<a')
|
||||
if ((mode as IconifyRenderMode) !== 'style' && body.indexOf('<a') === -1) {
|
||||
// Render <svg>
|
||||
return 'svg';
|
||||
}
|
||||
|
||||
// Use background or mask
|
||||
return body.indexOf('currentColor') === -1 ? 'bg' : 'mask';
|
||||
}
|
48
packages/icon/src/attributes/types.ts
Normal file
48
packages/icon/src/attributes/types.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import type { IconifyIcon } from '@iconify/types';
|
||||
|
||||
/**
|
||||
* Icon render modes
|
||||
*
|
||||
* 'bg' = SPAN with style using `background`
|
||||
* 'mask' = SPAN with style using `mask`
|
||||
* 'svg' = SVG
|
||||
*/
|
||||
export type ActualRenderMode = 'bg' | 'mask' | 'svg';
|
||||
|
||||
/**
|
||||
* Extra render modes
|
||||
*
|
||||
* 'style' = 'bg' or 'mask', depending on icon content
|
||||
*/
|
||||
export type IconifyRenderMode = 'style' | ActualRenderMode;
|
||||
|
||||
/**
|
||||
* Icon customisations
|
||||
*/
|
||||
export type IconifyIconCustomisationAttributes = {
|
||||
// Dimensions
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
|
||||
// Transformations
|
||||
rotate?: string | number;
|
||||
flip?: string;
|
||||
|
||||
// Alignment
|
||||
align?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* All attributes
|
||||
*/
|
||||
export interface IconifyIconAttributes
|
||||
extends IconifyIconCustomisationAttributes {
|
||||
// Icon to render: name, object or serialised object
|
||||
icon: string | IconifyIcon;
|
||||
|
||||
// Render mode
|
||||
mode?: IconifyRenderMode;
|
||||
|
||||
// Inline mode
|
||||
inline?: boolean | string;
|
||||
}
|
323
packages/icon/src/component.ts
Normal file
323
packages/icon/src/component.ts
Normal file
@ -0,0 +1,323 @@
|
||||
import {
|
||||
getCustomisations,
|
||||
haveCustomisationsChanged,
|
||||
RenderedIconCustomisations,
|
||||
} from './attributes/customisations';
|
||||
import { parseIconValue } from './attributes/icon';
|
||||
import type {
|
||||
CurrentIconData,
|
||||
RenderedCurrentIconData,
|
||||
} from './attributes/icon/state';
|
||||
import { getInline } from './attributes/inline';
|
||||
import { getRenderMode } from './attributes/mode';
|
||||
import type { IconifyIconAttributes } from './attributes/types';
|
||||
import { renderIcon } from './render/icon';
|
||||
import { updateStyle } from './render/style';
|
||||
import { IconState, setPendingState } from './state';
|
||||
|
||||
/**
|
||||
* Interface
|
||||
*/
|
||||
declare interface PartialIconifyIconHTMLElement extends HTMLElement {
|
||||
// Restart animation for animated icons
|
||||
restartAnimation: () => void;
|
||||
}
|
||||
|
||||
// Add dynamically generated getters and setters
|
||||
export declare interface IconifyIconHTMLElement
|
||||
extends PartialIconifyIconHTMLElement,
|
||||
Required<IconifyIconAttributes> {}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
interface PartialIconifyIconHTMLElementClass {
|
||||
new (): PartialIconifyIconHTMLElement;
|
||||
prototype: PartialIconifyIconHTMLElement;
|
||||
}
|
||||
|
||||
export interface IconifyIconHTMLElementClass {
|
||||
new (): IconifyIconHTMLElement;
|
||||
prototype: IconifyIconHTMLElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register 'iconify-icon' component, if it does not exist
|
||||
*/
|
||||
export function defineIconifyIcon(
|
||||
name = 'iconify-icon'
|
||||
): PartialIconifyIconHTMLElementClass | undefined {
|
||||
// Check for custom elements registry and HTMLElement
|
||||
let customElements: CustomElementRegistry;
|
||||
let ParentClass: typeof HTMLElement;
|
||||
try {
|
||||
customElements = window.customElements;
|
||||
ParentClass = window.HTMLElement;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure registry and HTMLElement exist
|
||||
if (!customElements || !ParentClass) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
const ConflictingClass = customElements.get(name);
|
||||
if (ConflictingClass) {
|
||||
return ConflictingClass as IconifyIconHTMLElementClass;
|
||||
}
|
||||
|
||||
// All attributes
|
||||
const attributes: (keyof IconifyIconAttributes)[] = [
|
||||
// Icon
|
||||
'icon',
|
||||
// Mode
|
||||
'mode',
|
||||
'inline',
|
||||
// Customisations
|
||||
'width',
|
||||
'height',
|
||||
'rotate',
|
||||
'flip',
|
||||
'align',
|
||||
];
|
||||
|
||||
/**
|
||||
* Component class
|
||||
*/
|
||||
class IconifyIcon extends ParentClass {
|
||||
// Root
|
||||
_shadowRoot: ShadowRoot;
|
||||
|
||||
// State
|
||||
_state: IconState;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Attach shadow DOM
|
||||
const root = (this._shadowRoot = this.attachShadow({
|
||||
mode: 'closed',
|
||||
}));
|
||||
|
||||
// Add style
|
||||
const inline = getInline(this);
|
||||
updateStyle(root, inline);
|
||||
|
||||
// Create empty state
|
||||
const value = this.getAttribute('icon');
|
||||
this._state = setPendingState(
|
||||
{
|
||||
value,
|
||||
},
|
||||
inline
|
||||
);
|
||||
|
||||
// Update icon
|
||||
this._iconChanged(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Observed attributes
|
||||
*/
|
||||
static get observedAttributes() {
|
||||
return attributes.slice(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute has changed
|
||||
*/
|
||||
attributeChangedCallback(
|
||||
name: string,
|
||||
oldValue: unknown,
|
||||
newValue: unknown
|
||||
) {
|
||||
const state = this._state;
|
||||
switch (name as keyof IconifyIconAttributes) {
|
||||
case 'icon': {
|
||||
this._iconChanged(newValue);
|
||||
return;
|
||||
}
|
||||
|
||||
case 'inline': {
|
||||
const newInline = !!newValue;
|
||||
if (newInline !== state.inline) {
|
||||
// Update style if inline mode changed
|
||||
state.inline = newInline;
|
||||
updateStyle(this._shadowRoot, newInline);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case 'mode': {
|
||||
if (state.rendered) {
|
||||
// Re-render if icon is currently being renrered
|
||||
this._renderIcon(state.icon);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
if (state.rendered) {
|
||||
// Check customisations only if icon has been rendered
|
||||
const newCustomisations = getCustomisations(this);
|
||||
if (
|
||||
haveCustomisationsChanged(
|
||||
newCustomisations,
|
||||
state.customisations
|
||||
)
|
||||
) {
|
||||
// Re-render
|
||||
this._renderIcon(state.icon, newCustomisations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set icon
|
||||
*/
|
||||
get icon() {
|
||||
const value = this.getAttribute('icon');
|
||||
if (value && value.slice(0, 1) === '{') {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
set icon(value) {
|
||||
if (typeof value === 'object') {
|
||||
value = JSON.stringify(value);
|
||||
}
|
||||
this.setAttribute('icon', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set inline
|
||||
*/
|
||||
get inline() {
|
||||
return getInline(this);
|
||||
}
|
||||
|
||||
set inline(value) {
|
||||
this.setAttribute('inline', value ? 'true' : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart animation
|
||||
*/
|
||||
restartAnimation() {
|
||||
const state = this._state;
|
||||
if (state.rendered) {
|
||||
const root = this._shadowRoot;
|
||||
if (state.renderedMode === 'svg') {
|
||||
// Update root node
|
||||
try {
|
||||
(root.lastChild as SVGSVGElement).setCurrentTime(0);
|
||||
return;
|
||||
} catch {
|
||||
// Failed: setCurrentTime() is not supported
|
||||
}
|
||||
}
|
||||
renderIcon(root, state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon value has changed
|
||||
*/
|
||||
_iconChanged(newValue: unknown) {
|
||||
const icon = parseIconValue(newValue, (value, name, data) => {
|
||||
// Asynchronous callback: re-check values to make sure stuff wasn't changed
|
||||
const state = this._state;
|
||||
if (state.rendered || state.icon.value !== value) {
|
||||
// Icon data is already available or icon attribute was changed
|
||||
return;
|
||||
}
|
||||
|
||||
// Change icon
|
||||
const icon: CurrentIconData = {
|
||||
value,
|
||||
name,
|
||||
data,
|
||||
};
|
||||
|
||||
if (icon.data) {
|
||||
// Render icon
|
||||
this._renderIcon(icon as RenderedCurrentIconData);
|
||||
} else {
|
||||
// Nothing to render: update icon in state
|
||||
state.icon = icon;
|
||||
}
|
||||
});
|
||||
|
||||
if (icon.data) {
|
||||
// Icon is ready to render
|
||||
this._renderIcon(icon as RenderedCurrentIconData);
|
||||
} else {
|
||||
// Pending icon
|
||||
this._state = setPendingState(
|
||||
icon,
|
||||
this._state.inline,
|
||||
this._state
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-render based on icon data
|
||||
*/
|
||||
_renderIcon(
|
||||
icon: RenderedCurrentIconData,
|
||||
customisations?: RenderedIconCustomisations
|
||||
) {
|
||||
// Get mode
|
||||
const attrMode = this.getAttribute('mode');
|
||||
const renderedMode = getRenderMode(icon.data.body, attrMode);
|
||||
|
||||
// Inline was not changed
|
||||
const inline = this._state.inline;
|
||||
|
||||
// Set state and render
|
||||
renderIcon(
|
||||
this._shadowRoot,
|
||||
(this._state = {
|
||||
rendered: true,
|
||||
icon,
|
||||
inline,
|
||||
customisations: customisations || getCustomisations(this),
|
||||
attrMode,
|
||||
renderedMode,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add getters and setters
|
||||
attributes.forEach((attr) => {
|
||||
if (!Object.hasOwn(IconifyIcon.prototype, attr)) {
|
||||
Object.defineProperty(IconifyIcon.prototype, attr, {
|
||||
get: function () {
|
||||
return this.getAttribute(attr);
|
||||
},
|
||||
set: function (value) {
|
||||
this.setAttribute(attr, value);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Define new component
|
||||
customElements.define(name, IconifyIcon);
|
||||
|
||||
return IconifyIcon;
|
||||
}
|
34
packages/icon/src/render/icon.ts
Normal file
34
packages/icon/src/render/icon.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { iconToSVG } from '@iconify/utils/lib/svg/build';
|
||||
import type { RenderedState } from '../state';
|
||||
import { renderSPAN } from './span';
|
||||
import { renderSVG } from './svg';
|
||||
|
||||
/**
|
||||
* Render icon
|
||||
*/
|
||||
export function renderIcon(parent: Element | ShadowRoot, state: RenderedState) {
|
||||
// Render icon
|
||||
const iconData = state.icon.data;
|
||||
const renderData = iconToSVG(iconData, {
|
||||
...state.customisations,
|
||||
inline: state.inline,
|
||||
});
|
||||
|
||||
const mode = state.renderedMode;
|
||||
let node: Element;
|
||||
switch (mode) {
|
||||
case 'svg':
|
||||
node = renderSVG(renderData);
|
||||
break;
|
||||
|
||||
default:
|
||||
node = renderSPAN(renderData, iconData, mode === 'mask');
|
||||
}
|
||||
|
||||
// Set element
|
||||
// Assumes first node is a style node created with updateStyle()
|
||||
if (parent.childNodes.length > 1) {
|
||||
parent.removeChild(parent.lastChild);
|
||||
}
|
||||
parent.appendChild(node);
|
||||
}
|
74
packages/icon/src/render/span.ts
Normal file
74
packages/icon/src/render/span.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import type { FullIconifyIcon } from '@iconify/utils/lib/icon';
|
||||
import type { IconifyIconBuildResult } from '@iconify/utils/lib/svg/build';
|
||||
import { iconToHTML } from '@iconify/utils/lib/svg/html';
|
||||
import { svgToURL } from '@iconify/utils/lib/svg/url';
|
||||
|
||||
// List of properties to apply
|
||||
const monotoneProps: Record<string, string> = {
|
||||
'background-color': 'currentColor',
|
||||
};
|
||||
|
||||
const coloredProps: Record<string, string> = {
|
||||
'background-color': 'transparent',
|
||||
};
|
||||
|
||||
// Dynamically add common props to variables above
|
||||
const propsToAdd: Record<string, string> = {
|
||||
image: 'var(--svg)',
|
||||
repeat: 'no-repeat',
|
||||
size: '100% 100%',
|
||||
};
|
||||
const propsToAddTo: Record<string, Record<string, string>> = {
|
||||
'-webkit-mask': monotoneProps,
|
||||
'mask': monotoneProps,
|
||||
'background': coloredProps,
|
||||
};
|
||||
for (const prefix in propsToAddTo) {
|
||||
const list = propsToAddTo[prefix];
|
||||
for (const prop in propsToAdd) {
|
||||
list[prefix + '-' + prop] = propsToAdd[prop];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render node as <span>
|
||||
*/
|
||||
export function renderSPAN(
|
||||
data: IconifyIconBuildResult,
|
||||
icon: FullIconifyIcon,
|
||||
useMask: boolean
|
||||
): Element {
|
||||
const node = document.createElement('span');
|
||||
|
||||
// Body
|
||||
let body = data.body;
|
||||
if (body.indexOf('<a') !== -1) {
|
||||
// Animated SVG: add something to fix timing bug
|
||||
body += '<!-- ' + Date.now() + ' -->';
|
||||
}
|
||||
|
||||
// Generate SVG as URL
|
||||
const renderAttribs = data.attributes;
|
||||
const html = iconToHTML(body, {
|
||||
...renderAttribs,
|
||||
width: icon.width + '',
|
||||
height: icon.height + '',
|
||||
});
|
||||
const url = svgToURL(html);
|
||||
|
||||
// Generate style
|
||||
const svgStyle = node.style;
|
||||
const styles: Record<string, string> = {
|
||||
'--svg': url,
|
||||
'width': renderAttribs.width,
|
||||
'height': renderAttribs.height,
|
||||
...(useMask ? monotoneProps : coloredProps),
|
||||
};
|
||||
|
||||
// Apply style
|
||||
for (const prop in styles) {
|
||||
svgStyle.setProperty(prop, styles[prop]);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
17
packages/icon/src/render/style.ts
Normal file
17
packages/icon/src/render/style.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Add/update style node
|
||||
*/
|
||||
export function updateStyle(parent: Element | ShadowRoot, inline: boolean) {
|
||||
// Get node, create if needed
|
||||
let style = parent.firstChild;
|
||||
if (!style) {
|
||||
style = document.createElement('style');
|
||||
parent.appendChild(style);
|
||||
}
|
||||
|
||||
// Update content
|
||||
style.textContent =
|
||||
':host{display:inline-block;vertical-align:' +
|
||||
(inline ? '-0.125em' : '0') +
|
||||
'}span,svg{display:block}';
|
||||
}
|
13
packages/icon/src/render/svg.ts
Normal file
13
packages/icon/src/render/svg.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import type { IconifyIconBuildResult } from '@iconify/utils/lib/svg/build';
|
||||
import { iconToHTML } from '@iconify/utils/lib/svg/html';
|
||||
|
||||
/**
|
||||
* Render node as <svg>
|
||||
*/
|
||||
export function renderSVG(data: IconifyIconBuildResult): Element {
|
||||
const node = document.createElement('span');
|
||||
|
||||
// Generate SVG
|
||||
node.innerHTML = iconToHTML(data.body, data.attributes);
|
||||
return node.firstChild as HTMLElement;
|
||||
}
|
72
packages/icon/src/state/index.ts
Normal file
72
packages/icon/src/state/index.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import type { ActualRenderMode } from '../attributes/types';
|
||||
import type {
|
||||
CurrentIconData,
|
||||
RenderedCurrentIconData,
|
||||
} from '../attributes/icon/state';
|
||||
import type { RenderedIconCustomisations } from '../attributes/customisations';
|
||||
|
||||
/**
|
||||
* Common attributes, rendered regardless of loading state
|
||||
*/
|
||||
interface BaseState {
|
||||
inline: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Successful render
|
||||
*/
|
||||
export interface RenderedState extends BaseState {
|
||||
rendered: true;
|
||||
|
||||
// Icon, including data
|
||||
icon: RenderedCurrentIconData;
|
||||
|
||||
// Mode passed as attribute
|
||||
attrMode?: string;
|
||||
|
||||
// Actual mode used to render icon
|
||||
renderedMode: ActualRenderMode;
|
||||
|
||||
// Customisations
|
||||
customisations: RenderedIconCustomisations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pending render
|
||||
*/
|
||||
export interface PendingState extends BaseState {
|
||||
rendered: false;
|
||||
|
||||
// Icon, without data
|
||||
icon: CurrentIconData;
|
||||
|
||||
// Last rendered state. Set if icon was not re-renderd, but new state is pending, such as new icon is being loaded.
|
||||
lastRender?: RenderedState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon
|
||||
*/
|
||||
export type IconState = RenderedState | PendingState;
|
||||
|
||||
/**
|
||||
* Set state to PendingState
|
||||
*/
|
||||
export function setPendingState(
|
||||
icon: CurrentIconData,
|
||||
inline: boolean,
|
||||
lastState?: IconState
|
||||
): PendingState {
|
||||
const lastRender: RenderedState | undefined =
|
||||
lastState &&
|
||||
(lastState.rendered
|
||||
? lastState
|
||||
: (lastState as PendingState).lastRender);
|
||||
|
||||
return {
|
||||
rendered: false,
|
||||
inline,
|
||||
icon,
|
||||
lastRender,
|
||||
};
|
||||
}
|
198
packages/icon/tests/component-test.ts
Normal file
198
packages/icon/tests/component-test.ts
Normal file
@ -0,0 +1,198 @@
|
||||
import {
|
||||
cleanupGlobals,
|
||||
expectedBlock,
|
||||
expectedInline,
|
||||
setupDOM,
|
||||
} from './helpers';
|
||||
import { defineIconifyIcon, IconifyIconHTMLElement } from '../src/component';
|
||||
import type { IconState } from '../src/state';
|
||||
|
||||
export declare interface DebugIconifyIconHTMLElement
|
||||
extends IconifyIconHTMLElement {
|
||||
// Internal stuff, used for debugging
|
||||
_shadowRoot: ShadowRoot;
|
||||
_state: IconState;
|
||||
}
|
||||
|
||||
describe('Testing icon component', () => {
|
||||
afterEach(cleanupGlobals);
|
||||
|
||||
it('Creating component instance, changing properties', () => {
|
||||
// Setup DOM
|
||||
const doc = setupDOM('').window.document;
|
||||
|
||||
// Make sure component does not exist and registry is available
|
||||
expect(window.customElements).toBeDefined();
|
||||
expect(window.customElements.get('iconify-icon')).toBeUndefined();
|
||||
|
||||
// Define component
|
||||
expect(defineIconifyIcon()).toBeDefined();
|
||||
expect(window.customElements.get('iconify-icon')).toBeDefined();
|
||||
|
||||
// Create element
|
||||
const node = document.createElement(
|
||||
'iconify-icon'
|
||||
) as DebugIconifyIconHTMLElement;
|
||||
|
||||
// Should be empty
|
||||
expect(node._shadowRoot.innerHTML).toBe(
|
||||
`<style>${expectedBlock}</style>`
|
||||
);
|
||||
|
||||
// Set icon
|
||||
node.icon = {
|
||||
body: '<g />',
|
||||
};
|
||||
expect(node.icon).toEqual({
|
||||
body: '<g />',
|
||||
});
|
||||
expect(node.getAttribute('icon')).toBe(
|
||||
JSON.stringify({
|
||||
body: '<g />',
|
||||
})
|
||||
);
|
||||
|
||||
// Should render SVG
|
||||
const blankSVG =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"><g></g></svg>';
|
||||
expect(node._shadowRoot.innerHTML).toBe(
|
||||
`<style>${expectedBlock}</style>${blankSVG}`
|
||||
);
|
||||
|
||||
// Check inline attribute
|
||||
expect(node.inline).toBe(false);
|
||||
expect(node.getAttribute('inline')).toBe(null);
|
||||
|
||||
// Change inline
|
||||
node.inline = true;
|
||||
expect(node.inline).toBe(true);
|
||||
expect(node.getAttribute('inline')).toBe('true');
|
||||
|
||||
expect(node._shadowRoot.innerHTML).toBe(
|
||||
`<style>${expectedInline}</style>${blankSVG}`
|
||||
);
|
||||
});
|
||||
|
||||
it('Restarting animation', async () => {
|
||||
// Setup DOM
|
||||
const doc = setupDOM('').window.document;
|
||||
|
||||
// Make sure component does not exist and registry is available
|
||||
expect(window.customElements).toBeDefined();
|
||||
expect(window.customElements.get('iconify-icon')).toBeUndefined();
|
||||
|
||||
// Define component
|
||||
expect(defineIconifyIcon()).toBeDefined();
|
||||
expect(window.customElements.get('iconify-icon')).toBeDefined();
|
||||
|
||||
// Create element
|
||||
const node = document.createElement(
|
||||
'iconify-icon'
|
||||
) as DebugIconifyIconHTMLElement;
|
||||
|
||||
// Set icon
|
||||
const body =
|
||||
'<rect width="10" height="10"><animate attributeName="width" values="10;5;10" dur="10s" repeatCount="indefinite" /></rect>';
|
||||
node.icon = {
|
||||
body,
|
||||
};
|
||||
expect(node.icon).toEqual({
|
||||
body,
|
||||
});
|
||||
expect(node.getAttribute('icon')).toBe(
|
||||
JSON.stringify({
|
||||
body,
|
||||
})
|
||||
);
|
||||
|
||||
// Should render SPAN, with comment
|
||||
const renderedIconWithComment =
|
||||
"<span style=\"--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16'%3E%3Crect width='10' height='10'%3E%3Canimate attributeName='width' values='10;5;10' dur='10s' repeatCount='indefinite' /%3E%3C/rect%3E%3C!-- --%3E%3C/svg%3E"); width: 1em; height: 1em; background-color: transparent; background-repeat: no-repeat; background-size: 100% 100%;\"></span>";
|
||||
const html1 = node._shadowRoot.innerHTML;
|
||||
expect(html1.replace(/-- [0-9]+ --/, '-- --')).toBe(
|
||||
`<style>${expectedBlock}</style>${renderedIconWithComment}`
|
||||
);
|
||||
|
||||
// Restart animation, test icon again
|
||||
node.restartAnimation();
|
||||
|
||||
const html2 = node._shadowRoot.innerHTML;
|
||||
expect(html2).not.toBe(html1);
|
||||
expect(html2.replace(/-- [0-9]+ --/, '-- --')).toBe(
|
||||
`<style>${expectedBlock}</style>${renderedIconWithComment}`
|
||||
);
|
||||
|
||||
// Small delay to make sure timer is increased to get new number
|
||||
await new Promise((fulfill) => {
|
||||
setTimeout(fulfill, 10);
|
||||
});
|
||||
|
||||
// Restart animation again, test icon again
|
||||
node.restartAnimation();
|
||||
|
||||
const html3 = node._shadowRoot.innerHTML;
|
||||
expect(html3.replace(/-- [0-9]+ --/, '-- --')).toBe(
|
||||
`<style>${expectedBlock}</style>${renderedIconWithComment}`
|
||||
);
|
||||
expect(html3).not.toBe(html1);
|
||||
expect(html3).not.toBe(html2);
|
||||
});
|
||||
|
||||
it('Restarting animation for SVG', () => {
|
||||
// Setup DOM
|
||||
const doc = setupDOM('').window.document;
|
||||
|
||||
// Make sure component does not exist and registry is available
|
||||
expect(window.customElements).toBeDefined();
|
||||
expect(window.customElements.get('iconify-icon')).toBeUndefined();
|
||||
|
||||
// Define component
|
||||
expect(defineIconifyIcon()).toBeDefined();
|
||||
expect(window.customElements.get('iconify-icon')).toBeDefined();
|
||||
|
||||
// Create element
|
||||
const node = document.createElement(
|
||||
'iconify-icon'
|
||||
) as DebugIconifyIconHTMLElement;
|
||||
|
||||
// Set icon
|
||||
const body =
|
||||
'<rect width="10" height="10"><animate attributeName="width" values="10;5;10" dur="10s" repeatCount="indefinite" /></rect>';
|
||||
node.mode = 'svg';
|
||||
node.icon = {
|
||||
body,
|
||||
};
|
||||
expect(node.icon).toEqual({
|
||||
body,
|
||||
});
|
||||
expect(node.getAttribute('icon')).toBe(
|
||||
JSON.stringify({
|
||||
body,
|
||||
})
|
||||
);
|
||||
|
||||
// Should render SVG
|
||||
const renderedIcon =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"><rect width="10" height="10"><animate attributeName="width" values="10;5;10" dur="10s" repeatCount="indefinite"></animate></rect></svg>';
|
||||
const html1 = node._shadowRoot.innerHTML;
|
||||
const svg1 = node._shadowRoot.lastChild as SVGSVGElement;
|
||||
const setCurrentTimeSupported = !!svg1.setCurrentTime;
|
||||
expect(html1).toBe(`<style>${expectedBlock}</style>${renderedIcon}`);
|
||||
expect(svg1.outerHTML).toBe(renderedIcon);
|
||||
|
||||
// Restart animation, test icon again
|
||||
node.restartAnimation();
|
||||
|
||||
const html2 = node._shadowRoot.innerHTML;
|
||||
const svg2 = node._shadowRoot.lastChild as SVGSVGElement;
|
||||
expect(html2).toBe(html1);
|
||||
expect(svg2.outerHTML).toBe(renderedIcon);
|
||||
|
||||
// Node should be different because JSDOM does not support setCurrentTime(), but that might change in future
|
||||
if (setCurrentTimeSupported) {
|
||||
expect(svg2).toBe(svg1);
|
||||
} else {
|
||||
expect(svg2).not.toBe(svg1);
|
||||
}
|
||||
});
|
||||
});
|
74
packages/icon/tests/customisations-test.ts
Normal file
74
packages/icon/tests/customisations-test.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import {
|
||||
getCustomisations,
|
||||
haveCustomisationsChanged,
|
||||
defaultCustomisations,
|
||||
} from '../src/attributes/customisations';
|
||||
import { getInline } from '../src/attributes/inline';
|
||||
import { cleanupGlobals, setupDOM } from './helpers';
|
||||
|
||||
describe('Testing customisations', () => {
|
||||
afterEach(cleanupGlobals);
|
||||
|
||||
it('Get and compare customisations', () => {
|
||||
// Setup DOM
|
||||
const doc = setupDOM('').window.document;
|
||||
|
||||
// Create node, test default values
|
||||
const node = doc.createElement('div');
|
||||
const emptyCustomisations = getCustomisations(node);
|
||||
expect(emptyCustomisations).toEqual(defaultCustomisations);
|
||||
expect(getInline(node)).toBe(false);
|
||||
expect(
|
||||
haveCustomisationsChanged(
|
||||
emptyCustomisations,
|
||||
defaultCustomisations
|
||||
)
|
||||
).toBe(false);
|
||||
|
||||
// Test inline and height
|
||||
node.innerHTML = '<span inline="true" height="1em"></span>';
|
||||
let testNode = node.lastChild as HTMLSpanElement;
|
||||
|
||||
const test1 = getCustomisations(testNode);
|
||||
expect(test1).toEqual({
|
||||
...defaultCustomisations,
|
||||
height: '1em',
|
||||
});
|
||||
expect(haveCustomisationsChanged(emptyCustomisations, test1)).toBe(
|
||||
true
|
||||
);
|
||||
expect(getInline(testNode)).toBe(true);
|
||||
|
||||
// Test transformations
|
||||
node.innerHTML = '<span flip="horizontal" rotate="2"></span>';
|
||||
testNode = node.lastChild as HTMLSpanElement;
|
||||
|
||||
const test2 = getCustomisations(testNode);
|
||||
expect(test2).toEqual({
|
||||
...defaultCustomisations,
|
||||
hFlip: true,
|
||||
rotate: 2,
|
||||
});
|
||||
expect(haveCustomisationsChanged(emptyCustomisations, test2)).toBe(
|
||||
true
|
||||
);
|
||||
expect(haveCustomisationsChanged(test1, test2)).toBe(true);
|
||||
expect(getInline(testNode)).toBe(false);
|
||||
|
||||
// Dimensions and alignment. Empty value
|
||||
node.innerHTML =
|
||||
'<span align="left top" width="auto" height=""></span>';
|
||||
testNode = node.lastChild as HTMLSpanElement;
|
||||
|
||||
const test3 = getCustomisations(testNode);
|
||||
expect(test3).toEqual({
|
||||
...defaultCustomisations,
|
||||
hAlign: 'left',
|
||||
vAlign: 'top',
|
||||
width: 'auto',
|
||||
});
|
||||
expect(haveCustomisationsChanged(test3, test2)).toBe(true);
|
||||
expect(haveCustomisationsChanged(test1, test3)).toBe(true);
|
||||
expect(getInline(testNode)).toBe(false);
|
||||
});
|
||||
});
|
60
packages/icon/tests/get-render-mode-test.ts
Normal file
60
packages/icon/tests/get-render-mode-test.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { getRenderMode } from '../src/attributes/mode';
|
||||
|
||||
describe('Testing getRenderMode', () => {
|
||||
// Default mode is 'svg'? Change this value if code in state/mode.ts changes
|
||||
const defautToSVG = true;
|
||||
|
||||
it('Force mode', () => {
|
||||
expect(getRenderMode('<g />', 'svg')).toBe('svg');
|
||||
expect(getRenderMode('<g />', 'bg')).toBe('bg');
|
||||
expect(getRenderMode('<g />', 'mask')).toBe('mask');
|
||||
});
|
||||
|
||||
it('Style', () => {
|
||||
expect(getRenderMode('<g />', 'style')).toBe('bg');
|
||||
expect(
|
||||
getRenderMode('<g><path d="" fill="currentColor" /></g>', 'style')
|
||||
).toBe('mask');
|
||||
});
|
||||
|
||||
it('Detect mode', () => {
|
||||
// Icon without 'currentColor'
|
||||
expect(getRenderMode('<g />', null)).toBe(defautToSVG ? 'svg' : 'bg');
|
||||
expect(getRenderMode('<g />', '')).toBe(defautToSVG ? 'svg' : 'bg');
|
||||
|
||||
// Icon with 'currentColor'
|
||||
expect(
|
||||
getRenderMode('<g><path d="" fill="currentColor" /></g>', '')
|
||||
).toBe(defautToSVG ? 'svg' : 'mask');
|
||||
expect(
|
||||
getRenderMode(
|
||||
'<g><path d="" fill="currentColor" /></g>',
|
||||
'whatever'
|
||||
)
|
||||
).toBe(defautToSVG ? 'svg' : 'mask');
|
||||
});
|
||||
|
||||
it('Animated icons', () => {
|
||||
// Animated icon without 'currentColor'
|
||||
const animatedDefaultFill =
|
||||
'<g><rect width="20"><animate attributeName="height" values="0;10" dur="1s" fill="freeze" /></rect></g>';
|
||||
|
||||
expect(getRenderMode(animatedDefaultFill, 'svg')).toBe('svg');
|
||||
expect(getRenderMode(animatedDefaultFill, 'bg')).toBe('bg');
|
||||
expect(getRenderMode(animatedDefaultFill, 'mask')).toBe('mask');
|
||||
|
||||
expect(getRenderMode(animatedDefaultFill, '')).toBe('bg');
|
||||
expect(getRenderMode(animatedDefaultFill, 'style')).toBe('bg');
|
||||
|
||||
// Animated icon with 'currentColor'
|
||||
const animatedCurrentColor =
|
||||
'<g><rect width="20" fill="currentColor"><animate attributeName="height" values="0;10" dur="1s" fill="freeze" /></rect></g>';
|
||||
|
||||
expect(getRenderMode(animatedCurrentColor, 'svg')).toBe('svg');
|
||||
expect(getRenderMode(animatedCurrentColor, 'bg')).toBe('bg');
|
||||
expect(getRenderMode(animatedCurrentColor, 'mask')).toBe('mask');
|
||||
|
||||
expect(getRenderMode(animatedCurrentColor, '')).toBe('mask');
|
||||
expect(getRenderMode(animatedCurrentColor, 'style')).toBe('mask');
|
||||
});
|
||||
});
|
115
packages/icon/tests/helpers.ts
Normal file
115
packages/icon/tests/helpers.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { mockAPIModule, mockAPIData } from '@iconify/core/lib/api/modules/mock';
|
||||
import { addAPIProvider } from '@iconify/core/lib/api/config';
|
||||
import { setAPIModule } from '@iconify/core/lib/api/modules';
|
||||
|
||||
/**
|
||||
* Generate next prefix
|
||||
*/
|
||||
let counter = 0;
|
||||
export const nextPrefix = () => 'mock-' + counter++;
|
||||
|
||||
/**
|
||||
* Set mock API module for provider
|
||||
*/
|
||||
export function fakeAPI(provider: string) {
|
||||
// Set API module for provider
|
||||
addAPIProvider(provider, {
|
||||
resources: ['http://localhost'],
|
||||
});
|
||||
setAPIModule(provider, mockAPIModule);
|
||||
}
|
||||
|
||||
export { mockAPIData };
|
||||
|
||||
/**
|
||||
* Timeout
|
||||
*
|
||||
* Can chain multiple setTimeout by adding multiple 0 delays
|
||||
*/
|
||||
export function nextTick(delays: number[] = [0]) {
|
||||
return new Promise((fulfill) => {
|
||||
function next() {
|
||||
if (!delays.length) {
|
||||
fulfill(undefined);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
next();
|
||||
}, delays.shift());
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeout, until condition is met
|
||||
*/
|
||||
type WaitUntilCheck = () => boolean;
|
||||
export function awaitUntil(callback: WaitUntilCheck, maxDelay = 1000) {
|
||||
return new Promise((fulfill, reject) => {
|
||||
const start = Date.now();
|
||||
|
||||
function next() {
|
||||
setTimeout(() => {
|
||||
if (callback()) {
|
||||
fulfill(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Date.now() - start > maxDelay) {
|
||||
reject('Timed out');
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create JSDOM instance, overwrite globals
|
||||
*/
|
||||
export function setupDOM(html: string): JSDOM {
|
||||
const dom = new JSDOM(html);
|
||||
(global as unknown as Record<string, unknown>).window = dom.window;
|
||||
(global as unknown as Record<string, unknown>).document =
|
||||
global.window.document;
|
||||
return dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete temporary globals
|
||||
*/
|
||||
export function cleanupGlobals() {
|
||||
delete global.window;
|
||||
delete global.document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap HTML
|
||||
*/
|
||||
export function wrapHTML(content: string): string {
|
||||
return `<!doctype html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
${content}
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Style tests
|
||||
*/
|
||||
function getStyleValue(inline: boolean): string {
|
||||
return (
|
||||
':host{display:inline-block;vertical-align:' +
|
||||
(inline ? '-0.125em' : '0') +
|
||||
'}span,svg{display:block}'
|
||||
);
|
||||
}
|
||||
export const expectedInline = getStyleValue(true);
|
||||
export const expectedBlock = getStyleValue(false);
|
222
packages/icon/tests/icon-load-api-test.ts
Normal file
222
packages/icon/tests/icon-load-api-test.ts
Normal file
@ -0,0 +1,222 @@
|
||||
import { fakeAPI, nextPrefix, mockAPIData } from './helpers';
|
||||
import { iconDefaults } from '@iconify/utils/lib/icon';
|
||||
import { addCollection } from '@iconify/core/lib/storage/functions';
|
||||
import { parseIconValue } from '../src/attributes/icon/index';
|
||||
|
||||
describe('Testing parseIconValue with API', () => {
|
||||
it('Loading icon from API', (done) => {
|
||||
// Set config
|
||||
const provider = nextPrefix();
|
||||
const prefix = nextPrefix();
|
||||
fakeAPI(provider);
|
||||
|
||||
// Mock data
|
||||
const name = 'mock-test';
|
||||
const iconName = `@${provider}:${prefix}:${name}`;
|
||||
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix,
|
||||
response: {
|
||||
prefix,
|
||||
icons: {
|
||||
[name]: {
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Test
|
||||
let callbackCalled = false;
|
||||
const result = parseIconValue(iconName, (value, icon, data) => {
|
||||
expect(callbackCalled).toBe(false);
|
||||
callbackCalled = true;
|
||||
|
||||
expect(value).toBe(iconName);
|
||||
expect(icon).toEqual({
|
||||
provider,
|
||||
prefix,
|
||||
name,
|
||||
});
|
||||
expect(data).toEqual({
|
||||
...iconDefaults,
|
||||
body: '<g />',
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
expect(result.loading).toBeDefined();
|
||||
expect(result).toEqual({
|
||||
value: iconName,
|
||||
name: {
|
||||
provider,
|
||||
prefix,
|
||||
name,
|
||||
},
|
||||
loading: result.loading,
|
||||
});
|
||||
expect(callbackCalled).toBe(false);
|
||||
});
|
||||
|
||||
it('Already exists', (done) => {
|
||||
// Set config
|
||||
const provider = nextPrefix();
|
||||
const prefix = nextPrefix();
|
||||
fakeAPI(provider);
|
||||
|
||||
const name = 'mock-test';
|
||||
const iconName = `@${provider}:${prefix}:${name}`;
|
||||
|
||||
addCollection(
|
||||
{
|
||||
prefix,
|
||||
icons: {
|
||||
[name]: {
|
||||
body: '<g id="test" />',
|
||||
},
|
||||
},
|
||||
},
|
||||
provider
|
||||
);
|
||||
|
||||
// Mock data
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix,
|
||||
response: {
|
||||
prefix,
|
||||
icons: {
|
||||
[name]: {
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
},
|
||||
delay: () => {
|
||||
done('This function should not have been called');
|
||||
},
|
||||
});
|
||||
|
||||
// Test
|
||||
const result = parseIconValue(iconName, () => {
|
||||
done('Callback should not have been called');
|
||||
});
|
||||
expect(result).toEqual({
|
||||
value: iconName,
|
||||
name: {
|
||||
provider,
|
||||
prefix,
|
||||
name,
|
||||
},
|
||||
data: {
|
||||
...iconDefaults,
|
||||
body: '<g id="test" />',
|
||||
},
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('Failing to load', (done) => {
|
||||
// Set config
|
||||
const provider = nextPrefix();
|
||||
const prefix = nextPrefix();
|
||||
fakeAPI(provider);
|
||||
|
||||
// Mock data
|
||||
const name = 'mock-test';
|
||||
const iconName = `@${provider}:${prefix}:${name}`;
|
||||
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix,
|
||||
response: {
|
||||
prefix,
|
||||
icons: {},
|
||||
not_found: [name],
|
||||
},
|
||||
});
|
||||
|
||||
// Test
|
||||
let callbackCalled = false;
|
||||
const result = parseIconValue(iconName, (value, icon, data) => {
|
||||
expect(callbackCalled).toBe(false);
|
||||
callbackCalled = true;
|
||||
|
||||
expect(value).toBe(iconName);
|
||||
expect(icon).toEqual({
|
||||
provider,
|
||||
prefix,
|
||||
name,
|
||||
});
|
||||
expect(data).toBeFalsy();
|
||||
|
||||
done();
|
||||
});
|
||||
expect(result.loading).toBeDefined();
|
||||
expect(result).toEqual({
|
||||
value: iconName,
|
||||
name: {
|
||||
provider,
|
||||
prefix,
|
||||
name,
|
||||
},
|
||||
loading: result.loading,
|
||||
});
|
||||
expect(callbackCalled).toBe(false);
|
||||
});
|
||||
|
||||
it('Already marked as missing', (done) => {
|
||||
// Set config
|
||||
const provider = nextPrefix();
|
||||
const prefix = nextPrefix();
|
||||
fakeAPI(provider);
|
||||
|
||||
const name = 'mock-test';
|
||||
const iconName = `@${provider}:${prefix}:${name}`;
|
||||
|
||||
addCollection(
|
||||
{
|
||||
prefix,
|
||||
icons: {},
|
||||
not_found: [name],
|
||||
},
|
||||
provider
|
||||
);
|
||||
|
||||
// Mock data
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix,
|
||||
response: {
|
||||
prefix,
|
||||
icons: {
|
||||
[name]: {
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
},
|
||||
delay: () => {
|
||||
done('This function should not have been called');
|
||||
},
|
||||
});
|
||||
|
||||
// Test
|
||||
const result = parseIconValue(iconName, () => {
|
||||
done('Callback should not have been called');
|
||||
});
|
||||
expect(result).toEqual({
|
||||
value: iconName,
|
||||
name: {
|
||||
provider,
|
||||
prefix,
|
||||
name,
|
||||
},
|
||||
data: null,
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
57
packages/icon/tests/icon-load-test.ts
Normal file
57
packages/icon/tests/icon-load-test.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { parseIconValue } from '../src/attributes/icon/index';
|
||||
import { iconDefaults } from '@iconify/utils/lib/icon';
|
||||
|
||||
describe('Testing parseIconValue without API', () => {
|
||||
it('Instantly loading object', () => {
|
||||
const value = {
|
||||
body: '<g />',
|
||||
};
|
||||
const result = parseIconValue(value, () => {
|
||||
throw new Error('callback should not have been called');
|
||||
});
|
||||
expect(result).toEqual({
|
||||
value,
|
||||
data: {
|
||||
...iconDefaults,
|
||||
...value,
|
||||
},
|
||||
});
|
||||
expect(result.value).toBe(value);
|
||||
});
|
||||
|
||||
it('Instantly loading serialised object', () => {
|
||||
const value = JSON.stringify({
|
||||
body: '<g />',
|
||||
});
|
||||
const result = parseIconValue(value, () => {
|
||||
throw new Error('callback should not have been called');
|
||||
});
|
||||
expect(result).toEqual({
|
||||
value,
|
||||
data: {
|
||||
...iconDefaults,
|
||||
body: '<g />',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Bad data', () => {
|
||||
const value = '<svg />';
|
||||
const result = parseIconValue(value, () => {
|
||||
throw new Error('callback should not have been called');
|
||||
});
|
||||
expect(result).toEqual({
|
||||
value,
|
||||
});
|
||||
});
|
||||
|
||||
it('Icon without prefix', () => {
|
||||
const value = 'test';
|
||||
const result = parseIconValue(value, () => {
|
||||
throw new Error('callback should not have been called');
|
||||
});
|
||||
expect(result).toEqual({
|
||||
value,
|
||||
});
|
||||
});
|
||||
});
|
61
packages/icon/tests/icon-object-test.ts
Normal file
61
packages/icon/tests/icon-object-test.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { testIconObject } from '../src/attributes/icon/object';
|
||||
import { iconDefaults } from '@iconify/utils/lib/icon';
|
||||
|
||||
describe('Testing testIconObject', () => {
|
||||
it('Objects', () => {
|
||||
expect(
|
||||
testIconObject({
|
||||
body: '<g />',
|
||||
})
|
||||
).toEqual({
|
||||
...iconDefaults,
|
||||
body: '<g />',
|
||||
});
|
||||
|
||||
expect(
|
||||
testIconObject({
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: '32',
|
||||
})
|
||||
).toEqual({
|
||||
...iconDefaults,
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
// Validation is simple, this will fail during render
|
||||
height: '32',
|
||||
});
|
||||
|
||||
// Invalid objects
|
||||
expect(testIconObject({})).toBeUndefined();
|
||||
expect(
|
||||
testIconObject([
|
||||
{
|
||||
body: '<g />',
|
||||
},
|
||||
])
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
testIconObject({
|
||||
body: true,
|
||||
})
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('String', () => {
|
||||
expect(
|
||||
testIconObject(
|
||||
JSON.stringify({
|
||||
body: '<g />',
|
||||
})
|
||||
)
|
||||
).toEqual({
|
||||
...iconDefaults,
|
||||
body: '<g />',
|
||||
});
|
||||
|
||||
// Strings that are not objects
|
||||
expect(testIconObject('foo')).toBeUndefined();
|
||||
expect(testIconObject('{"body": "<g />"')).toBeUndefined();
|
||||
});
|
||||
});
|
41
packages/icon/tests/mock-api-test.ts
Normal file
41
packages/icon/tests/mock-api-test.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { iconExists } from '@iconify/core/lib/storage/functions';
|
||||
import { loadIcon } from '@iconify/core/lib/api/icons';
|
||||
import { iconDefaults } from '@iconify/utils/lib/icon';
|
||||
import { fakeAPI, nextPrefix, mockAPIData } from './helpers';
|
||||
|
||||
describe('Testing mock API', () => {
|
||||
it('Setting up API', async () => {
|
||||
// Set config
|
||||
const provider = nextPrefix();
|
||||
const prefix = nextPrefix();
|
||||
fakeAPI(provider);
|
||||
|
||||
// Mock data
|
||||
const name = 'mock-test';
|
||||
const iconName = `@${provider}:${prefix}:${name}`;
|
||||
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix,
|
||||
response: {
|
||||
prefix,
|
||||
icons: {
|
||||
[name]: {
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Check if icon has been loaded
|
||||
expect(iconExists(iconName)).toBe(false);
|
||||
|
||||
// Load icon
|
||||
const data = await loadIcon(iconName);
|
||||
expect(data).toEqual({
|
||||
...iconDefaults,
|
||||
body: '<g />',
|
||||
});
|
||||
});
|
||||
});
|
125
packages/icon/tests/render-icon-test.ts
Normal file
125
packages/icon/tests/render-icon-test.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { iconDefaults } from '@iconify/utils/lib/icon';
|
||||
import {
|
||||
cleanupGlobals,
|
||||
expectedBlock,
|
||||
expectedInline,
|
||||
setupDOM,
|
||||
} from './helpers';
|
||||
import { updateStyle } from '../src/render/style';
|
||||
import { renderIcon } from '../src/render/icon';
|
||||
import { defaultCustomisations } from '../src/attributes/customisations';
|
||||
|
||||
describe('Testing rendering loaded icon', () => {
|
||||
afterEach(cleanupGlobals);
|
||||
|
||||
it('Render as SVG', () => {
|
||||
// Setup DOM
|
||||
const doc = setupDOM('').window.document;
|
||||
|
||||
// Create container node and add style
|
||||
const node = doc.createElement('div');
|
||||
updateStyle(node, false);
|
||||
|
||||
// Render SVG
|
||||
renderIcon(node, {
|
||||
rendered: true,
|
||||
icon: {
|
||||
value: 'whatever',
|
||||
data: {
|
||||
...iconDefaults,
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
renderedMode: 'svg',
|
||||
inline: false,
|
||||
customisations: {
|
||||
...defaultCustomisations,
|
||||
},
|
||||
});
|
||||
|
||||
// Test HTML
|
||||
expect(node.innerHTML).toBe(
|
||||
`<style>${expectedBlock}</style><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"><g></g></svg>`
|
||||
);
|
||||
|
||||
// Replace icon content
|
||||
renderIcon(node, {
|
||||
rendered: true,
|
||||
icon: {
|
||||
value: 'whatever',
|
||||
data: {
|
||||
...iconDefaults,
|
||||
width: 24,
|
||||
height: 24,
|
||||
body: '<g><path d="" /></g>',
|
||||
},
|
||||
},
|
||||
renderedMode: 'svg',
|
||||
inline: false,
|
||||
customisations: {
|
||||
...defaultCustomisations,
|
||||
rotate: 1,
|
||||
height: 'auto',
|
||||
},
|
||||
});
|
||||
|
||||
// Test HTML
|
||||
expect(node.innerHTML).toBe(
|
||||
`<style>${expectedBlock}</style><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g transform="rotate(90 12 12)"><g><path d=""></path></g></g></svg>`
|
||||
);
|
||||
});
|
||||
|
||||
it('Render as SPAN', () => {
|
||||
// Setup DOM
|
||||
const doc = setupDOM('').window.document;
|
||||
|
||||
// Create container node and add style
|
||||
const node = doc.createElement('div');
|
||||
updateStyle(node, true);
|
||||
|
||||
// Render SVG
|
||||
renderIcon(node, {
|
||||
rendered: true,
|
||||
icon: {
|
||||
value: 'whatever',
|
||||
data: {
|
||||
...iconDefaults,
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
renderedMode: 'mask',
|
||||
inline: true,
|
||||
customisations: {
|
||||
...defaultCustomisations,
|
||||
},
|
||||
});
|
||||
|
||||
// Test HTML
|
||||
expect(node.innerHTML).toBe(
|
||||
`<style>${expectedInline}</style><span style="--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16'%3E%3Cg /%3E%3C/svg%3E"); width: 1em; height: 1em; background-color: currentColor; mask-image: var(--svg); mask-repeat: no-repeat; mask-size: 100% 100%;"></span>`
|
||||
);
|
||||
|
||||
// Change mode to background, add some customisations
|
||||
renderIcon(node, {
|
||||
rendered: true,
|
||||
icon: {
|
||||
value: 'whatever',
|
||||
data: {
|
||||
...iconDefaults,
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
renderedMode: 'bg',
|
||||
inline: true,
|
||||
customisations: {
|
||||
...defaultCustomisations,
|
||||
width: 24,
|
||||
},
|
||||
});
|
||||
|
||||
// Test HTML
|
||||
expect(node.innerHTML).toBe(
|
||||
`<style>${expectedInline}</style><span style="--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' preserveAspectRatio='xMidYMid meet' viewBox='0 0 16 16'%3E%3Cg /%3E%3C/svg%3E"); background-color: transparent; background-repeat: no-repeat; background-size: 100% 100%;"></span>`
|
||||
);
|
||||
});
|
||||
});
|
35
packages/icon/tests/render-style-test.ts
Normal file
35
packages/icon/tests/render-style-test.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { updateStyle } from '../src/render/style';
|
||||
import {
|
||||
cleanupGlobals,
|
||||
expectedBlock,
|
||||
expectedInline,
|
||||
setupDOM,
|
||||
} from './helpers';
|
||||
|
||||
describe('Testing rendering style', () => {
|
||||
afterEach(cleanupGlobals);
|
||||
|
||||
it('updateStyle', () => {
|
||||
// Setup DOM
|
||||
const doc = setupDOM('').window.document;
|
||||
|
||||
// Create container node
|
||||
const node = doc.createElement('div');
|
||||
|
||||
// Add style to empty parent
|
||||
updateStyle(node, false);
|
||||
expect(node.innerHTML).toBe('<style>' + expectedBlock + '</style>');
|
||||
|
||||
// Change inline mode
|
||||
updateStyle(node, true);
|
||||
expect(node.innerHTML).toBe('<style>' + expectedInline + '</style>');
|
||||
|
||||
// Do not change anything
|
||||
updateStyle(node, true);
|
||||
expect(node.innerHTML).toBe('<style>' + expectedInline + '</style>');
|
||||
|
||||
// Change to block
|
||||
updateStyle(node, false);
|
||||
expect(node.innerHTML).toBe('<style>' + expectedBlock + '</style>');
|
||||
});
|
||||
});
|
8
packages/icon/tests/tsconfig.json
Normal file
8
packages/icon/tests/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node", "jest"],
|
||||
"rootDir": ".",
|
||||
"outDir": "../tests-compiled"
|
||||
}
|
||||
}
|
15
packages/icon/tsconfig-base.json
Normal file
15
packages/icon/tsconfig-base.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"declaration": true,
|
||||
"declarationMap": false,
|
||||
"sourceMap": false,
|
||||
"composite": true,
|
||||
"strict": false,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"importsNotUsedAsValues": "error"
|
||||
}
|
||||
}
|
8
packages/icon/tsconfig.json
Normal file
8
packages/icon/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig-base.json",
|
||||
"include": ["src/**/*.ts", ".eslintrc.js"],
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user