├── .eslintrc.json
├── .github
└── FUNDING.yml
├── .npmrc
├── LICENSE
├── _animations.scss
├── _icon-park.scss
├── _material-icons.scss
├── _material-symbols.scss
├── _transformations.scss
├── bower.json
├── demo.html
├── last-icon.css
├── last-icon.css.map
├── last-icon.js
├── last-icon.min.css
├── last-icon.min.css.map
├── last-icon.min.js
├── last-icon.min.js.map
├── last-icon.scss
├── package.json
└── readme.md
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true
5 | },
6 | "extends": "eslint:recommended",
7 | "parserOptions": {
8 | "ecmaVersion": 12,
9 | "sourceType": "module"
10 | },
11 | "rules": {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: lekoala
4 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | tag-version-prefix=""
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Thomas Portelange
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/_animations.scss:
--------------------------------------------------------------------------------
1 | $default-duration: 1.5s !default;
2 |
3 | @-webkit-keyframes spin {
4 | 0% {
5 | transform: rotate(0);
6 | }
7 | 100% {
8 | transform: rotate(359deg);
9 | }
10 | }
11 | @keyframes spin {
12 | 0% {
13 | transform: rotate(0);
14 | }
15 | 100% {
16 | transform: rotate(359deg);
17 | }
18 | }
19 | @-webkit-keyframes burst {
20 | 0% {
21 | transform: scale(1);
22 | opacity: 1;
23 | }
24 | 90% {
25 | transform: scale(1.5);
26 | opacity: 0;
27 | }
28 | }
29 | @keyframes burst {
30 | 0% {
31 | transform: scale(1);
32 | opacity: 1;
33 | }
34 | 90% {
35 | transform: scale(1.5);
36 | opacity: 0;
37 | }
38 | }
39 | @-webkit-keyframes flashing {
40 | 0% {
41 | opacity: 1;
42 | }
43 | 45% {
44 | opacity: 0;
45 | }
46 | 90% {
47 | opacity: 1;
48 | }
49 | }
50 | @keyframes flashing {
51 | 0% {
52 | opacity: 1;
53 | }
54 | 45% {
55 | opacity: 0;
56 | }
57 | 90% {
58 | opacity: 1;
59 | }
60 | }
61 | @-webkit-keyframes fade-left {
62 | 0% {
63 | transform: translateX(0);
64 | opacity: 1;
65 | }
66 | 75% {
67 | transform: translateX(-20px);
68 | opacity: 0;
69 | }
70 | }
71 | @keyframes fade-left {
72 | 0% {
73 | transform: translateX(0);
74 | opacity: 1;
75 | }
76 | 75% {
77 | transform: translateX(-20px);
78 | opacity: 0;
79 | }
80 | }
81 | @-webkit-keyframes fade-right {
82 | 0% {
83 | transform: translateX(0);
84 | opacity: 1;
85 | }
86 | 75% {
87 | transform: translateX(20px);
88 | opacity: 0;
89 | }
90 | }
91 | @keyframes fade-right {
92 | 0% {
93 | transform: translateX(0);
94 | opacity: 1;
95 | }
96 | 75% {
97 | transform: translateX(20px);
98 | opacity: 0;
99 | }
100 | }
101 | @-webkit-keyframes fade-up {
102 | 0% {
103 | transform: translateY(0);
104 | opacity: 1;
105 | }
106 | 75% {
107 | transform: translateY(-20px);
108 | opacity: 0;
109 | }
110 | }
111 | @keyframes fade-up {
112 | 0% {
113 | transform: translateY(0);
114 | opacity: 1;
115 | }
116 | 75% {
117 | transform: translateY(-20px);
118 | opacity: 0;
119 | }
120 | }
121 | @-webkit-keyframes fade-down {
122 | 0% {
123 | transform: translateY(0);
124 | opacity: 1;
125 | }
126 | 75% {
127 | transform: translateY(20px);
128 | opacity: 0;
129 | }
130 | }
131 | @keyframes fade-down {
132 | 0% {
133 | transform: translateY(0);
134 | opacity: 1;
135 | }
136 | 75% {
137 | transform: translateY(20px);
138 | opacity: 0;
139 | }
140 | }
141 | @-webkit-keyframes tada {
142 | from {
143 | transform: scale3d(1, 1, 1);
144 | }
145 |
146 | 10%,
147 | 20% {
148 | transform: scale3d(0.95, 0.95, 0.95) rotate3d(0, 0, 1, -10deg);
149 | }
150 |
151 | 30%,
152 | 50%,
153 | 70%,
154 | 90% {
155 | transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
156 | }
157 |
158 | 40%,
159 | 60%,
160 | 80% {
161 | transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
162 | }
163 |
164 | to {
165 | transform: scale3d(1, 1, 1);
166 | }
167 | }
168 | @keyframes tada {
169 | from {
170 | transform: scale3d(1, 1, 1);
171 | }
172 |
173 | 10%,
174 | 20% {
175 | transform: scale3d(0.95, 0.95, 0.95) rotate3d(0, 0, 1, -10deg);
176 | }
177 |
178 | 30%,
179 | 50%,
180 | 70%,
181 | 90% {
182 | transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
183 | }
184 |
185 | 40%,
186 | 60%,
187 | 80% {
188 | transform: rotate3d(0, 0, 1, -10deg);
189 | }
190 |
191 | to {
192 | transform: scale3d(1, 1, 1);
193 | }
194 | }
195 | .spin {
196 | animation: spin $default-duration linear infinite;
197 | }
198 | .spin-hover:hover {
199 | animation: spin $default-duration linear infinite;
200 | }
201 | .tada {
202 | animation: tada $default-duration ease infinite;
203 | }
204 | .tada-hover:hover {
205 | animation: tada $default-duration ease infinite;
206 | }
207 | .flashing {
208 | animation: flashing $default-duration infinite linear;
209 | }
210 | .flashing-hover:hover {
211 | animation: flashing $default-duration infinite linear;
212 | }
213 | .burst {
214 | animation: burst $default-duration infinite linear;
215 | }
216 | .burst-hover:hover {
217 | animation: burst $default-duration infinite linear;
218 | }
219 | .fade-up {
220 | animation: fade-up $default-duration infinite linear;
221 | }
222 | .fade-up-hover:hover {
223 | animation: fade-up $default-duration infinite linear;
224 | }
225 | .fade-down {
226 | animation: fade-down $default-duration infinite linear;
227 | }
228 | .fade-down-hover:hover {
229 | animation: fade-down $default-duration infinite linear;
230 | }
231 | .fade-left {
232 | animation: fade-left $default-duration infinite linear;
233 | }
234 | .fade-left-hover:hover {
235 | animation: fade-left $default-duration infinite linear;
236 | }
237 | .fade-right {
238 | animation: fade-right $default-duration infinite linear;
239 | }
240 | .fade-right-hover:hover {
241 | animation: fade-right $default-duration infinite linear;
242 | }
243 |
--------------------------------------------------------------------------------
/_icon-park.scss:
--------------------------------------------------------------------------------
1 | l-i[theme="multi-color"] {
2 | // outer fill color
3 | path[fill="#2F88FF"] {
4 | fill: var(--color);
5 | }
6 | // inner fill color
7 | path[fill="#43CCF8"] {
8 | fill: var(--color-secondary);
9 | stroke: var(--color-bg);
10 | }
11 | path[stroke="white"] {
12 | stroke: white;
13 | }
14 | path[stroke="black"] {
15 | stroke: #333;
16 | }
17 | }
18 | l-i[theme="two-tone"] {
19 | path[fill="#2F88FF"] {
20 | fill: var(--color);
21 | }
22 | path[fill="#43CCF8"] {
23 | fill: var(--color);
24 | }
25 | path[stroke="white"],
26 | path[stroke="black"] {
27 | stroke: #333;
28 | }
29 | }
30 | l-i[theme="filled"] {
31 | path[fill] {
32 | fill: currentColor;
33 | }
34 | path[stroke="black"] {
35 | stroke: white;
36 | }
37 | }
38 | l-i[theme="outline"] {
39 | path[fill] {
40 | fill: none;
41 | }
42 | path[stroke="black"] {
43 | stroke: currentColor;
44 | }
45 | path[fill="#43CCF8"] {
46 | fill: currentColor;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/_material-icons.scss:
--------------------------------------------------------------------------------
1 | .material-icons-two-tone {
2 | background-color: currentColor;
3 | -webkit-background-clip: text;
4 | background-clip: text;
5 | -webkit-text-fill-color: transparent;
6 | }
7 |
--------------------------------------------------------------------------------
/_material-symbols.scss:
--------------------------------------------------------------------------------
1 | $default-symbols-duration: 0.5s !default;
2 |
3 | @keyframes symbols-pulse {
4 | 0% {
5 | font-variation-settings: "wght" 100;
6 | }
7 |
8 | 50% {
9 | font-variation-settings: "wght" 700;
10 | }
11 |
12 | 100% {
13 | font-variation-settings: "wght" 100;
14 | }
15 | }
16 |
17 | @keyframes symbols-fill {
18 | 0% {
19 | font-variation-settings: "FILL" 0;
20 | }
21 |
22 | 100% {
23 | font-variation-settings: "FILL" 1;
24 | }
25 | }
26 |
27 | @keyframes symbols-empty {
28 | 0% {
29 | font-variation-settings: "FILL" 1;
30 | }
31 |
32 | 100% {
33 | font-variation-settings: "FILL" 0;
34 | }
35 | }
36 |
37 | .symbols-pulse i,
38 | .symbols-pulse-hover:hover i,
39 | .symbols-fill i,
40 | .symbols-fill-hover:hover i,
41 | .symbols-empty i,
42 | .symbols-empty-hover:hover i {
43 | animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
44 | animation-duration: var(--duration, $default-symbols-duration);
45 | }
46 |
47 | .symbols-pulse i,
48 | .symbols-pulse-hover:hover i {
49 | --duration: #{$default-symbols-duration * 3};
50 | animation-name: symbols-pulse;
51 | animation-iteration-count: infinite;
52 | }
53 |
54 | .symbols-fill i,
55 | .symbols-fill-hover:hover i {
56 | animation-name: symbols-fill;
57 | animation-fill-mode: forwards;
58 | }
59 |
60 | .symbols-empty i,
61 | .symbols-empty-hover:hover i {
62 | animation-name: symbols-empty;
63 | animation-fill-mode: forwards;
64 | }
65 |
66 | .material-symbols-rounded,
67 | .material-symbols-outlined,
68 | .material-symbols-sharp {
69 | font-variation-settings: "FILL" var(--fill, 0), "wght" var(--weight, 400), "GRAD" var(--grad, 0), "OPSZ" var(--opsz, 24);
70 | }
71 |
72 | .dark {
73 | --grad: -25;
74 | background: black;
75 | color: rgba(255, 255, 255, 1);
76 |
77 | &[disabled],
78 | &.disabled {
79 | color: rgba(255, 255, 255, 0.3);
80 | }
81 | }
--------------------------------------------------------------------------------
/_transformations.scss:
--------------------------------------------------------------------------------
1 | .rotate-90 {
2 | transform: rotate(90deg);
3 | }
4 | .rotate-180 {
5 | transform: rotate(180deg);
6 | }
7 | .rotate-270 {
8 | transform: rotate(270deg);
9 | }
10 | .flip-horizontal {
11 | transform: scaleX(-1);
12 | }
13 | .flip-vertical {
14 | transform: scaleY(-1);
15 | }
16 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "last-icon",
3 | "version": "2.0.0",
4 | "description": "One custom icon element to rule them all",
5 | "authors": ["LeKoala"],
6 | "keywords": ["DOM", "icons", "icon", "custom", "element", "component", "es6"],
7 | "main": "last-icon.js"
8 | }
9 |
--------------------------------------------------------------------------------
/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Last Icon Demo
7 |
9 |
10 |
11 |
43 |
44 |
45 |
46 |
65 |
66 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
107 |
109 |
111 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
133 |
134 |
135 |
136 |
148 |
149 | Basic usage
150 | Simply use <l-i></l-i>
151 | Default icons
152 |
153 | Set the name with name="your-icon"
and, optionnaly, set a custom size with size="xx"
(which is just a
154 | shortcut for setting style="--size:{xx}px")
155 |
156 |
157 |
158 | My button
159 |
160 | Icons are using vertical-align:middle
by default and a vertical-align:0.125em
offset when contained in a
161 | p
, button
, a
or span
162 |
163 |
164 |
165 | Bootstrap Icons
166 |
167 |
168 |
169 |
170 | Use set="bootstrap"
or set="bs"
171 |
172 |
173 |
174 | Github
175 |
176 |
177 | Tabler Icons
178 |
179 |
180 |
181 |
182 | This is the default set
183 | Use set="tabler"
or set="tb"
184 |
185 |
186 |
187 |
188 | Github
189 |
190 | You can also set various widths using stroke
attribute (it will NOT use the font) ! It can also be set globally with
191 | defaultStroke
.
192 |
193 |
194 |
195 |
196 |
197 |
198 | Material Icons
199 |
200 |
201 |
202 |
203 | Use set="material"
or set="mi"
204 |
205 |
206 | Github
207 | Github
208 |
209 | You can specify the following types: filled
, outlined
, round
, sharp
,
210 | two-tone
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 | You can also use the font as an icon if needed!
219 |
220 |
221 | Material Symbols
222 |
223 |
224 |
225 |
226 | Use set="symbols"
or set="ms"
. Works best with font icons if you intend to use options.
227 |
230 |
231 | Weight (note how search and star behave differently, the star becomes much smaller with lower weights)
232 |
233 |
234 |
235 |
236 |
237 | Github
238 | You can specify the following types: outlined
, rounded
, sharp
239 |
240 |
241 |
242 | Click to toggle fill. Note that we don't see much difference between outlined and sharp ;-)
243 |
244 |
245 | Animate them! (only works with font icons)
246 |
247 |
248 | Animate on hover (only works with font icons)
249 |
250 |
251 |
252 | Make sure size is perfect
253 |
254 |
255 |
256 |
257 |
258 |
259 | Dark background
260 |
261 |
262 |
263 |
264 |
265 | Super Tiny Icons
266 |
267 |
268 |
269 |
270 | Use set="supertiny"
or set="st"
271 | Super useful if you need the social icons with proper colors 👌
272 |
273 |
274 |
275 |
276 | Flags Icons
277 |
278 |
279 |
280 |
281 | Use set="flags"
or set="fl"
282 |
283 |
284 |
285 |
286 | Emoji Icons
287 |
288 |
289 |
290 |
291 | Use set="emojicc"
or set="em"
292 |
293 |
294 |
295 | Iconoir
296 |
297 |
298 |
299 |
300 | Use set="iconoir"
or set="in"
301 | Stroke only works with svg
302 |
303 |
304 |
305 |
306 |
307 |
308 | Star this document
309 |
310 |
311 | Feather
312 |
313 |
314 |
315 |
316 | Use set="feather"
or set="ft"
317 | You can also set various widths using stroke
attribute !
318 |
319 |
320 |
321 | Add activity
322 |
323 |
324 | Lucide
325 |
326 |
327 |
328 |
329 | Use set="lucide"
or set="lu"
330 | You can also set various widths using stroke
attribute !
331 |
332 |
333 |
334 | Add activity
335 |
336 |
337 | IconPark
338 |
339 |
340 |
341 |
342 | Use set="iconpark"
or set="ip"
343 | You can also set various widths using stroke
attribute !
344 | You can set 4 themes (outline, filled, two-tone, multi-color): these need to be set in CSS.
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 | Add bookmark
355 |
356 | Add bookmark
357 |
358 |
359 | Phosphor
360 |
361 |
362 |
363 |
364 | Use set="phosphor"
or set="ph"
365 | You can set 6 types (bold, duotone, fill, light, regular, thin)
366 |
367 |
368 |
369 |
370 |
371 |
372 | Add bookmark
373 |
374 | Add bookmark
375 |
376 | Invalid icons
377 | Invalid icons are logged to the console and display a warning emoji
378 |
379 |
380 | Animations and transformations
381 |
382 | Use the same icon and rotate
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 | Use the animation classes or they -hover variant
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 | Or use svg animation
411 |
412 |
413 |
414 |
415 |
416 |
417 |
419 |
420 |
421 |
422 |
423 |
424 |
425 | Advanced usage
426 | Host your own icons
427 | You can customize or add your own iconSets.
428 |
429 | customElements.whenDefined("l-i").then(() => {
430 | // Access through registry
431 | customElements.get("l-i").configure({
432 | debug: true,
433 | // Specify our own loading path
434 | sets: {
435 | bootstrap: {
436 | svgPath: () => "./vendor/bootstrap",
437 | }
438 | },
439 | });
440 | });
441 |
442 |
443 |
444 |
445 | Made with ♥ in by LeKoala
446 |
447 |
448 |
449 |
--------------------------------------------------------------------------------
/last-icon.css:
--------------------------------------------------------------------------------
1 | l-i {
2 | --size: 1em;
3 | display: inline-flex;
4 | width: var(--size);
5 | height: var(--size);
6 | vertical-align: middle;
7 | contain: strict;
8 | justify-content: center;
9 | align-items: center;
10 | }
11 | l-i span,
12 | l-i svg,
13 | l-i i {
14 | pointer-events: none;
15 | }
16 | l-i span {
17 | font-size: 0.8em;
18 | }
19 | l-i svg {
20 | display: block;
21 | width: 100%;
22 | height: 100%;
23 | }
24 | l-i i {
25 | font-size: var(--size) !important;
26 | color: currentColor;
27 | }
28 |
29 | p l-i:not([size]),
30 | button l-i:not([size]),
31 | a l-i:not([size]),
32 | span l-i:not([size]) {
33 | vertical-align: -0.125em;
34 | }
35 |
36 | .material-icons-two-tone {
37 | background-color: currentColor;
38 | -webkit-background-clip: text;
39 | background-clip: text;
40 | -webkit-text-fill-color: transparent;
41 | }
42 |
43 | @-webkit-keyframes symbols-pulse {
44 | 0% {
45 | font-variation-settings: "wght" 100;
46 | }
47 | 50% {
48 | font-variation-settings: "wght" 700;
49 | }
50 | 100% {
51 | font-variation-settings: "wght" 100;
52 | }
53 | }
54 |
55 | @keyframes symbols-pulse {
56 | 0% {
57 | font-variation-settings: "wght" 100;
58 | }
59 | 50% {
60 | font-variation-settings: "wght" 700;
61 | }
62 | 100% {
63 | font-variation-settings: "wght" 100;
64 | }
65 | }
66 | @-webkit-keyframes symbols-fill {
67 | 0% {
68 | font-variation-settings: "FILL" 0;
69 | }
70 | 100% {
71 | font-variation-settings: "FILL" 1;
72 | }
73 | }
74 | @keyframes symbols-fill {
75 | 0% {
76 | font-variation-settings: "FILL" 0;
77 | }
78 | 100% {
79 | font-variation-settings: "FILL" 1;
80 | }
81 | }
82 | @-webkit-keyframes symbols-empty {
83 | 0% {
84 | font-variation-settings: "FILL" 1;
85 | }
86 | 100% {
87 | font-variation-settings: "FILL" 0;
88 | }
89 | }
90 | @keyframes symbols-empty {
91 | 0% {
92 | font-variation-settings: "FILL" 1;
93 | }
94 | 100% {
95 | font-variation-settings: "FILL" 0;
96 | }
97 | }
98 | .symbols-pulse i,
99 | .symbols-pulse-hover:hover i,
100 | .symbols-fill i,
101 | .symbols-fill-hover:hover i,
102 | .symbols-empty i,
103 | .symbols-empty-hover:hover i {
104 | -webkit-animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
105 | animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
106 | -webkit-animation-duration: var(--duration, 0.5s);
107 | animation-duration: var(--duration, 0.5s);
108 | }
109 |
110 | .symbols-pulse i,
111 | .symbols-pulse-hover:hover i {
112 | --duration: 1.5s;
113 | -webkit-animation-name: symbols-pulse;
114 | animation-name: symbols-pulse;
115 | -webkit-animation-iteration-count: infinite;
116 | animation-iteration-count: infinite;
117 | }
118 |
119 | .symbols-fill i,
120 | .symbols-fill-hover:hover i {
121 | -webkit-animation-name: symbols-fill;
122 | animation-name: symbols-fill;
123 | -webkit-animation-fill-mode: forwards;
124 | animation-fill-mode: forwards;
125 | }
126 |
127 | .symbols-empty i,
128 | .symbols-empty-hover:hover i {
129 | -webkit-animation-name: symbols-empty;
130 | animation-name: symbols-empty;
131 | -webkit-animation-fill-mode: forwards;
132 | animation-fill-mode: forwards;
133 | }
134 |
135 | .material-symbols-rounded,
136 | .material-symbols-outlined,
137 | .material-symbols-sharp {
138 | font-variation-settings: "FILL" var(--fill, 0), "wght" var(--weight, 400), "GRAD" var(--grad, 0), "OPSZ" var(--opsz, 24);
139 | }
140 |
141 | .dark {
142 | --grad: -25;
143 | background: black;
144 | color: rgb(255, 255, 255);
145 | }
146 | .dark[disabled], .dark.disabled {
147 | color: rgba(255, 255, 255, 0.3);
148 | }
149 |
150 | l-i[theme=multi-color] path[fill="#2F88FF"] {
151 | fill: var(--color);
152 | }
153 | l-i[theme=multi-color] path[fill="#43CCF8"] {
154 | fill: var(--color-secondary);
155 | stroke: var(--color-bg);
156 | }
157 | l-i[theme=multi-color] path[stroke=white] {
158 | stroke: white;
159 | }
160 | l-i[theme=multi-color] path[stroke=black] {
161 | stroke: #333;
162 | }
163 |
164 | l-i[theme=two-tone] path[fill="#2F88FF"] {
165 | fill: var(--color);
166 | }
167 | l-i[theme=two-tone] path[fill="#43CCF8"] {
168 | fill: var(--color);
169 | }
170 | l-i[theme=two-tone] path[stroke=white],
171 | l-i[theme=two-tone] path[stroke=black] {
172 | stroke: #333;
173 | }
174 |
175 | l-i[theme=filled] path[fill] {
176 | fill: currentColor;
177 | }
178 | l-i[theme=filled] path[stroke=black] {
179 | stroke: white;
180 | }
181 |
182 | l-i[theme=outline] path[fill] {
183 | fill: none;
184 | }
185 | l-i[theme=outline] path[stroke=black] {
186 | stroke: currentColor;
187 | }
188 | l-i[theme=outline] path[fill="#43CCF8"] {
189 | fill: currentColor;
190 | }
191 |
192 | @-webkit-keyframes spin {
193 | 0% {
194 | transform: rotate(0);
195 | }
196 | 100% {
197 | transform: rotate(359deg);
198 | }
199 | }
200 | @keyframes spin {
201 | 0% {
202 | transform: rotate(0);
203 | }
204 | 100% {
205 | transform: rotate(359deg);
206 | }
207 | }
208 | @-webkit-keyframes burst {
209 | 0% {
210 | transform: scale(1);
211 | opacity: 1;
212 | }
213 | 90% {
214 | transform: scale(1.5);
215 | opacity: 0;
216 | }
217 | }
218 | @keyframes burst {
219 | 0% {
220 | transform: scale(1);
221 | opacity: 1;
222 | }
223 | 90% {
224 | transform: scale(1.5);
225 | opacity: 0;
226 | }
227 | }
228 | @-webkit-keyframes flashing {
229 | 0% {
230 | opacity: 1;
231 | }
232 | 45% {
233 | opacity: 0;
234 | }
235 | 90% {
236 | opacity: 1;
237 | }
238 | }
239 | @keyframes flashing {
240 | 0% {
241 | opacity: 1;
242 | }
243 | 45% {
244 | opacity: 0;
245 | }
246 | 90% {
247 | opacity: 1;
248 | }
249 | }
250 | @-webkit-keyframes fade-left {
251 | 0% {
252 | transform: translateX(0);
253 | opacity: 1;
254 | }
255 | 75% {
256 | transform: translateX(-20px);
257 | opacity: 0;
258 | }
259 | }
260 | @keyframes fade-left {
261 | 0% {
262 | transform: translateX(0);
263 | opacity: 1;
264 | }
265 | 75% {
266 | transform: translateX(-20px);
267 | opacity: 0;
268 | }
269 | }
270 | @-webkit-keyframes fade-right {
271 | 0% {
272 | transform: translateX(0);
273 | opacity: 1;
274 | }
275 | 75% {
276 | transform: translateX(20px);
277 | opacity: 0;
278 | }
279 | }
280 | @keyframes fade-right {
281 | 0% {
282 | transform: translateX(0);
283 | opacity: 1;
284 | }
285 | 75% {
286 | transform: translateX(20px);
287 | opacity: 0;
288 | }
289 | }
290 | @-webkit-keyframes fade-up {
291 | 0% {
292 | transform: translateY(0);
293 | opacity: 1;
294 | }
295 | 75% {
296 | transform: translateY(-20px);
297 | opacity: 0;
298 | }
299 | }
300 | @keyframes fade-up {
301 | 0% {
302 | transform: translateY(0);
303 | opacity: 1;
304 | }
305 | 75% {
306 | transform: translateY(-20px);
307 | opacity: 0;
308 | }
309 | }
310 | @-webkit-keyframes fade-down {
311 | 0% {
312 | transform: translateY(0);
313 | opacity: 1;
314 | }
315 | 75% {
316 | transform: translateY(20px);
317 | opacity: 0;
318 | }
319 | }
320 | @keyframes fade-down {
321 | 0% {
322 | transform: translateY(0);
323 | opacity: 1;
324 | }
325 | 75% {
326 | transform: translateY(20px);
327 | opacity: 0;
328 | }
329 | }
330 | @-webkit-keyframes tada {
331 | from {
332 | transform: scale3d(1, 1, 1);
333 | }
334 | 10%, 20% {
335 | transform: scale3d(0.95, 0.95, 0.95) rotate3d(0, 0, 1, -10deg);
336 | }
337 | 30%, 50%, 70%, 90% {
338 | transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
339 | }
340 | 40%, 60%, 80% {
341 | transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
342 | }
343 | to {
344 | transform: scale3d(1, 1, 1);
345 | }
346 | }
347 | @keyframes tada {
348 | from {
349 | transform: scale3d(1, 1, 1);
350 | }
351 | 10%, 20% {
352 | transform: scale3d(0.95, 0.95, 0.95) rotate3d(0, 0, 1, -10deg);
353 | }
354 | 30%, 50%, 70%, 90% {
355 | transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
356 | }
357 | 40%, 60%, 80% {
358 | transform: rotate3d(0, 0, 1, -10deg);
359 | }
360 | to {
361 | transform: scale3d(1, 1, 1);
362 | }
363 | }
364 | .spin {
365 | -webkit-animation: spin 1.5s linear infinite;
366 | animation: spin 1.5s linear infinite;
367 | }
368 |
369 | .spin-hover:hover {
370 | -webkit-animation: spin 1.5s linear infinite;
371 | animation: spin 1.5s linear infinite;
372 | }
373 |
374 | .tada {
375 | -webkit-animation: tada 1.5s ease infinite;
376 | animation: tada 1.5s ease infinite;
377 | }
378 |
379 | .tada-hover:hover {
380 | -webkit-animation: tada 1.5s ease infinite;
381 | animation: tada 1.5s ease infinite;
382 | }
383 |
384 | .flashing {
385 | -webkit-animation: flashing 1.5s infinite linear;
386 | animation: flashing 1.5s infinite linear;
387 | }
388 |
389 | .flashing-hover:hover {
390 | -webkit-animation: flashing 1.5s infinite linear;
391 | animation: flashing 1.5s infinite linear;
392 | }
393 |
394 | .burst {
395 | -webkit-animation: burst 1.5s infinite linear;
396 | animation: burst 1.5s infinite linear;
397 | }
398 |
399 | .burst-hover:hover {
400 | -webkit-animation: burst 1.5s infinite linear;
401 | animation: burst 1.5s infinite linear;
402 | }
403 |
404 | .fade-up {
405 | -webkit-animation: fade-up 1.5s infinite linear;
406 | animation: fade-up 1.5s infinite linear;
407 | }
408 |
409 | .fade-up-hover:hover {
410 | -webkit-animation: fade-up 1.5s infinite linear;
411 | animation: fade-up 1.5s infinite linear;
412 | }
413 |
414 | .fade-down {
415 | -webkit-animation: fade-down 1.5s infinite linear;
416 | animation: fade-down 1.5s infinite linear;
417 | }
418 |
419 | .fade-down-hover:hover {
420 | -webkit-animation: fade-down 1.5s infinite linear;
421 | animation: fade-down 1.5s infinite linear;
422 | }
423 |
424 | .fade-left {
425 | -webkit-animation: fade-left 1.5s infinite linear;
426 | animation: fade-left 1.5s infinite linear;
427 | }
428 |
429 | .fade-left-hover:hover {
430 | -webkit-animation: fade-left 1.5s infinite linear;
431 | animation: fade-left 1.5s infinite linear;
432 | }
433 |
434 | .fade-right {
435 | -webkit-animation: fade-right 1.5s infinite linear;
436 | animation: fade-right 1.5s infinite linear;
437 | }
438 |
439 | .fade-right-hover:hover {
440 | -webkit-animation: fade-right 1.5s infinite linear;
441 | animation: fade-right 1.5s infinite linear;
442 | }
443 |
444 | .rotate-90 {
445 | transform: rotate(90deg);
446 | }
447 |
448 | .rotate-180 {
449 | transform: rotate(180deg);
450 | }
451 |
452 | .rotate-270 {
453 | transform: rotate(270deg);
454 | }
455 |
456 | .flip-horizontal {
457 | transform: scaleX(-1);
458 | }
459 |
460 | .flip-vertical {
461 | transform: scaleY(-1);
462 | }
463 | /*# sourceMappingURL=last-icon.css.map */
--------------------------------------------------------------------------------
/last-icon.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["last-icon.scss","last-icon.css","_material-icons.scss","_material-symbols.scss","_icon-park.scss","_animations.scss","_transformations.scss"],"names":[],"mappings":"AAAA;EACE,WAAA;EACA,oBAAA;EACA,kBAAA;EACA,mBAAA;EACA,sBAAA;EACA,eAAA;EACA,uBAAA;EACA,mBAAA;ACCF;ADCE;;;EAGE,oBAAA;ACCJ;ADIE;EACE,gBAAA;ACFJ;ADKE;EACE,cAAA;EACA,WAAA;EACA,YAAA;ACHJ;ADME;EACE,iCAAA;EACA,mBAAA;ACJJ;;ADWE;;;;EACE,wBAAA;ACLJ;;ACjCA;EACE,8BAAA;EACA,6BAAA;EACA,qBAAA;EACA,oCAAA;ADoCF;;AEtCA;EACE;IACE,mCAAA;EFyCF;EEtCA;IACE,mCAAA;EFwCF;EErCA;IACE,mCAAA;EFuCF;AACF;;AElDA;EACE;IACE,mCAAA;EFyCF;EEtCA;IACE,mCAAA;EFwCF;EErCA;IACE,mCAAA;EFuCF;AACF;AEpCA;EACE;IACE,iCAAA;EFsCF;EEnCA;IACE,iCAAA;EFqCF;AACF;AE5CA;EACE;IACE,iCAAA;EFsCF;EEnCA;IACE,iCAAA;EFqCF;AACF;AElCA;EACE;IACE,iCAAA;EFoCF;EEjCA;IACE,iCAAA;EFmCF;AACF;AE1CA;EACE;IACE,iCAAA;EFoCF;EEjCA;IACE,iCAAA;EFmCF;AACF;AEhCA;;;;;;EAME,iEAAA;UAAA,yDAAA;EACA,iDAAA;UAAA,yCAAA;AFkCF;;AE/BA;;EAEE,gBAAA;EACA,qCAAA;UAAA,6BAAA;EACA,2CAAA;UAAA,mCAAA;AFkCF;;AE/BA;;EAEE,oCAAA;UAAA,4BAAA;EACA,qCAAA;UAAA,6BAAA;AFkCF;;AE/BA;;EAEE,qCAAA;UAAA,6BAAA;EACA,qCAAA;UAAA,6BAAA;AFkCF;;AE/BA;;;EAGE,wHAAA;AFkCF;;AE/BA;EACE,WAAA;EACA,iBAAA;EACA,yBAAA;AFkCF;AEhCE;EAEE,+BAAA;AFiCJ;;AG7GE;EACE,kBAAA;AHgHJ;AG7GE;EACE,4BAAA;EACA,uBAAA;AH+GJ;AG7GE;EACE,aAAA;AH+GJ;AG7GE;EACE,YAAA;AH+GJ;;AG3GE;EACE,kBAAA;AH8GJ;AG5GE;EACE,kBAAA;AH8GJ;AG5GE;;EAEE,YAAA;AH8GJ;;AG1GE;EACE,kBAAA;AH6GJ;AG3GE;EACE,aAAA;AH6GJ;;AGzGE;EACE,UAAA;AH4GJ;AG1GE;EACE,oBAAA;AH4GJ;AG1GE;EACE,kBAAA;AH4GJ;;AIvJA;EACE;IACE,oBAAA;EJ0JF;EIxJA;IACE,yBAAA;EJ0JF;AACF;AIxJA;EACE;IACE,oBAAA;EJ0JF;EIxJA;IACE,yBAAA;EJ0JF;AACF;AIxJA;EACE;IACE,mBAAA;IACA,UAAA;EJ0JF;EIxJA;IACE,qBAAA;IACA,UAAA;EJ0JF;AACF;AIxJA;EACE;IACE,mBAAA;IACA,UAAA;EJ0JF;EIxJA;IACE,qBAAA;IACA,UAAA;EJ0JF;AACF;AIxJA;EACE;IACE,UAAA;EJ0JF;EIxJA;IACE,UAAA;EJ0JF;EIxJA;IACE,UAAA;EJ0JF;AACF;AIxJA;EACE;IACE,UAAA;EJ0JF;EIxJA;IACE,UAAA;EJ0JF;EIxJA;IACE,UAAA;EJ0JF;AACF;AIxJA;EACE;IACE,wBAAA;IACA,UAAA;EJ0JF;EIxJA;IACE,4BAAA;IACA,UAAA;EJ0JF;AACF;AIxJA;EACE;IACE,wBAAA;IACA,UAAA;EJ0JF;EIxJA;IACE,4BAAA;IACA,UAAA;EJ0JF;AACF;AIxJA;EACE;IACE,wBAAA;IACA,UAAA;EJ0JF;EIxJA;IACE,2BAAA;IACA,UAAA;EJ0JF;AACF;AIxJA;EACE;IACE,wBAAA;IACA,UAAA;EJ0JF;EIxJA;IACE,2BAAA;IACA,UAAA;EJ0JF;AACF;AIxJA;EACE;IACE,wBAAA;IACA,UAAA;EJ0JF;EIxJA;IACE,4BAAA;IACA,UAAA;EJ0JF;AACF;AIxJA;EACE;IACE,wBAAA;IACA,UAAA;EJ0JF;EIxJA;IACE,4BAAA;IACA,UAAA;EJ0JF;AACF;AIxJA;EACE;IACE,wBAAA;IACA,UAAA;EJ0JF;EIxJA;IACE,2BAAA;IACA,UAAA;EJ0JF;AACF;AIxJA;EACE;IACE,wBAAA;IACA,UAAA;EJ0JF;EIxJA;IACE,2BAAA;IACA,UAAA;EJ0JF;AACF;AIxJA;EACE;IACE,2BAAA;EJ0JF;EIvJA;IAEE,8DAAA;EJwJF;EIrJA;IAIE,oDAAA;EJoJF;EIjJA;IAGE,qDAAA;EJiJF;EI9IA;IACE,2BAAA;EJgJF;AACF;AI9IA;EACE;IACE,2BAAA;EJgJF;EI7IA;IAEE,8DAAA;EJ8IF;EI3IA;IAIE,oDAAA;EJ0IF;EIvIA;IAGE,oCAAA;EJuIF;EIpIA;IACE,2BAAA;EJsIF;AACF;AIpIA;EACE,4CAAA;UAAA,oCAAA;AJsIF;;AIpIA;EACE,4CAAA;UAAA,oCAAA;AJuIF;;AIrIA;EACE,0CAAA;UAAA,kCAAA;AJwIF;;AItIA;EACE,0CAAA;UAAA,kCAAA;AJyIF;;AIvIA;EACE,gDAAA;UAAA,wCAAA;AJ0IF;;AIxIA;EACE,gDAAA;UAAA,wCAAA;AJ2IF;;AIzIA;EACE,6CAAA;UAAA,qCAAA;AJ4IF;;AI1IA;EACE,6CAAA;UAAA,qCAAA;AJ6IF;;AI3IA;EACE,+CAAA;UAAA,uCAAA;AJ8IF;;AI5IA;EACE,+CAAA;UAAA,uCAAA;AJ+IF;;AI7IA;EACE,iDAAA;UAAA,yCAAA;AJgJF;;AI9IA;EACE,iDAAA;UAAA,yCAAA;AJiJF;;AI/IA;EACE,iDAAA;UAAA,yCAAA;AJkJF;;AIhJA;EACE,iDAAA;UAAA,yCAAA;AJmJF;;AIjJA;EACE,kDAAA;UAAA,0CAAA;AJoJF;;AIlJA;EACE,kDAAA;UAAA,0CAAA;AJqJF;;AKrYA;EACE,wBAAA;ALwYF;;AKtYA;EACE,yBAAA;ALyYF;;AKvYA;EACE,yBAAA;AL0YF;;AKxYA;EACE,qBAAA;AL2YF;;AKzYA;EACE,qBAAA;AL4YF","file":"last-icon.css"}
--------------------------------------------------------------------------------
/last-icon.js:
--------------------------------------------------------------------------------
1 | const JSDELIVR = "https://cdn.jsdelivr.net/";
2 | const CACHE = {};
3 |
4 | /**
5 | * @typedef IconSet
6 | * @property {String} alias Short two letters alias
7 | * @property {Function} svgPath The svg path
8 | * @property {Boolean} [fixFill] Does this set needs fixing fill:currentColor ?
9 | * @property {String} [useStroke] Add stroke to svg
10 | * @property {String} [defaultStroke] Default stroke to use (if supports stroke)
11 | * @property {String} [defaultType] Default type to use (when there are multiple types)
12 | * @property {Function} [fontClass] Font class
13 | * @property {Boolean} [opticalFont] Is an optical font?
14 | * @property {String} [name] Full name (injected automatically)
15 | */
16 |
17 | /**
18 | * @typedef Options
19 | * @property {Boolean} debug Should we output messages to console
20 | * @property {Boolean} lazy Load icons lazily
21 | * @property {Object} replaceName Transparently replace icons with other values
22 | * @property {Array} fonts Icon sets using font icons rather than svg
23 | * @property {String} defaultSet Default icon set
24 | * @property {Object.} sets Available iconsets
25 | */
26 | const options = {
27 | debug: false,
28 | lazy: true,
29 | replaceName: {},
30 | fonts: [],
31 | defaultSet: "tabler",
32 | defaultStroke: 2,
33 | sets: {
34 | bootstrap: {
35 | alias: "bs",
36 | svgPath: () => `${JSDELIVR}npm/bootstrap-icons@1/icons/{icon}.svg`,
37 | },
38 | flags: {
39 | alias: "fl",
40 | // types: ["4x3", "1x1"],
41 | defaultType: "4x3",
42 | svgPath: () =>
43 | `${JSDELIVR}npm/flag-svg-collection@1/flags/{type}/{icon}.svg`,
44 | },
45 | iconoir: {
46 | alias: "in",
47 | svgPath: () => `${JSDELIVR}gh/lucaburgio/iconoir/icons/{icon}.svg`,
48 | fontClass: () => "iconoir-{icon}",
49 | useStroke: true,
50 | },
51 | iconpark: {
52 | alias: "ip",
53 | types: [], // see full list here https://github.com/bytedance/IconPark/tree/master/source
54 | svgPath: () =>
55 | `${JSDELIVR}gh/bytedance/IconPark/source/{type}/{icon}.svg`,
56 | useStroke: true,
57 | },
58 | lucide: {
59 | alias: "lu",
60 | svgPath: () => `${JSDELIVR}npm/lucide-static/icons/{icon}.svg`,
61 | },
62 | material: {
63 | alias: "mi",
64 | // types: ["filled", "outlined", "round", "sharp", "two-tone"],
65 | defaultType: "filled",
66 | svgPath: () =>
67 | `${JSDELIVR}npm/@material-design-icons/svg/{type}/{icon}.svg`,
68 | fontClass: (type) => {
69 | if (type === "filled") {
70 | return "material-icons";
71 | }
72 | return "material-icons-{type}";
73 | },
74 | },
75 | phosphor: {
76 | alias: "ph",
77 | // types: ["regular", "bold", "duotone", "fill", "light", "thin"],
78 | defaultType: "regular",
79 | svgPath: (type) => {
80 | if (type === "regular") {
81 | return `${JSDELIVR}npm/@phosphor-icons/core@2/assets/{type}/{icon}.svg`;
82 | }
83 | return `${JSDELIVR}npm/@phosphor-icons/core@2/assets/{type}/{icon}-{type}.svg`;
84 | },
85 | fontClass: (type) => {
86 | if (type === "regular") {
87 | return "ph ph-{icon}";
88 | }
89 | return "ph-{type} ph-{icon}";
90 | },
91 | },
92 | supertiny: {
93 | alias: "st",
94 | svgPath: () => `${JSDELIVR}npm/super-tiny-icons/images/svg/{icon}.svg`,
95 | },
96 | symbols: {
97 | alias: "ms",
98 | // types: ["outlined", "rounded", "sharp"],
99 | defaultType: "outlined",
100 | svgPath: () =>
101 | `${JSDELIVR}npm/@material-symbols/svg-400@0.5/{type}/{icon}.svg`,
102 | fixFill: true,
103 | fontClass: () => "material-symbols-{type}",
104 | opticalFont: true,
105 | },
106 | tabler: {
107 | alias: "ti",
108 | svgPath: () => `${JSDELIVR}npm/@tabler/icons@2/icons/{icon}.svg`,
109 | useStroke: true,
110 | fontClass: () => "ti ti-{icon}",
111 | },
112 | },
113 | };
114 |
115 | /**
116 | * @var {IntersectionObserver}
117 | */
118 | const observer = new window.IntersectionObserver((entries, observerRef) => {
119 | for (const entry of entries) {
120 | if (entry.isIntersecting) {
121 | observerRef.unobserve(entry.target);
122 | entry.target.init();
123 | }
124 | }
125 | });
126 |
127 | /**
128 | * @param {string} value
129 | * @param {string} iconName
130 | * @param {string} iconType
131 | * @return {string}
132 | */
133 | function replacePlaceholders(value, iconName, iconType) {
134 | let v = value;
135 | v = v.replace("{icon}", iconName);
136 | if (iconType) {
137 | v = v.replaceAll("{type}", iconType);
138 | } else {
139 | // Maybe we want to remove the type like in material icons
140 | v = v.replace("-{type}", "");
141 | }
142 | return v;
143 | }
144 |
145 | function log(message) {
146 | if (options.debug) {
147 | console.log(`[l-i] ${message}`);
148 | }
149 | }
150 |
151 | /**
152 | * @param {string} iconName
153 | * @param {IconSet} iconSet
154 | * @param {string} iconType
155 | * @return {Promise}
156 | */
157 | function getIconSvg(iconName, iconSet, iconType) {
158 | let iconUrl = iconSet.svgPath(iconType);
159 | if (!iconUrl) {
160 | throw Error(`Icon set ${iconSet} does not exists`);
161 | }
162 | const cacheKey = `${iconSet.name}-${iconName}-${iconType || "base"}`;
163 | iconUrl = replacePlaceholders(iconUrl, iconName, iconType);
164 |
165 | // If we have it in cache
166 | if (iconUrl && CACHE[cacheKey]) {
167 | log(`Fetching ${cacheKey} from cache`);
168 | return CACHE[cacheKey];
169 | }
170 |
171 | // Or resolve
172 | log(`Fetching ${cacheKey} from url ${iconUrl}`);
173 | CACHE[cacheKey] = fetch(iconUrl).then((response) => {
174 | if (response.ok) {
175 | return response.text();
176 | }
177 | throw Error(response.status);
178 | });
179 | return CACHE[cacheKey];
180 | }
181 |
182 | /**
183 | * @param {LastIcon} inst
184 | * @param {string} name
185 | * @param {IconSet} iconSet
186 | * @param {string} type
187 | */
188 | function refreshIcon(inst, name, iconSet, type) {
189 | let iconName = name;
190 | let iconType = type;
191 | // Replace name
192 | if (options.replaceName[iconName]) {
193 | iconName = options.replaceName[iconName];
194 | }
195 | // Set default type if any
196 | if (!iconType && iconSet.defaultType) {
197 | iconType = iconSet.defaultType;
198 | }
199 |
200 | // Use font (if not using a specific stroke)
201 | if (options.fonts.includes(iconSet.name) && !inst.hasAttribute("stroke")) {
202 | log(`Using font for ${iconName}`);
203 | let iconClass = iconSet.fontClass(iconType);
204 | const nameAsClass = iconClass.includes("{icon}");
205 | iconClass = replacePlaceholders(iconClass, iconName, iconType);
206 | if (nameAsClass) {
207 | inst.innerHTML = ` `;
208 | } else {
209 | inst.innerHTML = `${iconName} `;
210 | }
211 | if (inst.stroke && iconSet.opticalFont) {
212 | inst.style.setProperty("--weight", inst.stroke * 100);
213 | }
214 | return; // Return early
215 | }
216 |
217 | getIconSvg(iconName, iconSet, iconType)
218 | .then((data) => {
219 | let iconData = data;
220 | // Strip class attribute as it may be affected by css
221 | if (iconData.includes("class=")) {
222 | iconData = iconData.replace(/ class="([a-z- ]*)"/g, "");
223 | }
224 | // Add and/or fix stroke
225 | if (inst.stroke || iconSet.useStroke) {
226 | iconData = iconData.replace(
227 | /stroke-width="([0-9\.]*)"/g,
228 | `stroke-width="${inst.stroke}"`,
229 | );
230 | }
231 | // Fix fill to currentColor
232 | if (iconSet.fixFill) {
233 | iconData = iconData.replace(/(/, '$1 fill="currentColor">');
234 | }
235 | // If we have some html, pass it along (useful for svg anim)
236 | if (inst.defaultHTML) {
237 | iconData = iconData.replace("", `${inst.defaultHTML}`);
238 | }
239 | inst.innerHTML = iconData;
240 | })
241 | .catch((error) => {
242 | inst.innerHTML = "⚠️ ";
243 | console.error(`Failed to load icon ${iconName} (error ${error})`);
244 | });
245 | }
246 |
247 | /**
248 | * @param {HTMLElement} element
249 | * @returns {Boolean}
250 | */
251 | function isInViewport(element) {
252 | const rect = element.getBoundingClientRect();
253 | return (
254 | rect.top >= 0 &&
255 | rect.left >= 0 &&
256 | rect.bottom <=
257 | (window.innerHeight || document.documentElement.clientHeight) &&
258 | rect.right <= (window.innerWidth || document.documentElement.clientWidth)
259 | );
260 | }
261 |
262 | /**
263 | * Performs a deep merge of objects and returns new object. Does not modify
264 | * objects (immutable) and merges arrays via concatenation.
265 | *
266 | * @param {...object} objects - Objects to merge
267 | * @returns {object} New object with merged key/values
268 | */
269 | function mergeDeep(...objects) {
270 | const isObject = (obj) => obj && typeof obj === "object";
271 | const isArray = Array.isArray;
272 |
273 | return objects.reduce((prev, obj) => {
274 | for (const key of Object.keys(obj)) {
275 | const pVal = prev[key];
276 | const oVal = obj[key];
277 |
278 | if (isArray(pVal) && isArray(oVal)) {
279 | prev[key] = pVal.concat(...oVal);
280 | } else if (isObject(pVal) && isObject(oVal)) {
281 | prev[key] = mergeDeep(pVal, oVal);
282 | } else {
283 | prev[key] = oVal;
284 | }
285 | }
286 | return prev;
287 | }, {});
288 | }
289 |
290 | const aliases = {};
291 | function processIconSets() {
292 | for (const [key, set] of Object.entries(options.sets)) {
293 | // List aliases for easy retrieval
294 | aliases[set.alias] = key;
295 | // Include full name in iconset definition
296 | set.name = key;
297 | }
298 | }
299 | processIconSets();
300 |
301 | class LastIcon extends HTMLElement {
302 | /**
303 | * @param {object} opts
304 | * @returns {Options} The updated option object
305 | */
306 | static configure(opts = {}) {
307 | for (const k in opts) {
308 | if (typeof options[k] === "undefined") {
309 | console.error(`Invalid option key ${k}`);
310 | return;
311 | }
312 | if (Array.isArray(opts[k])) {
313 | options[k] = options[k].concat(opts[k]);
314 | } else if (typeof opts[k] === "object") {
315 | options[k] = mergeDeep(options[k], opts[k]);
316 | } else {
317 | options[k] = opts[k];
318 | }
319 | }
320 | processIconSets();
321 | // Log after we had the opportunity to change debug flag
322 | log("configuring options");
323 | return options;
324 | }
325 |
326 | /**
327 | * @return {String|null}
328 | */
329 | get type() {
330 | return this.getAttribute("type") || null;
331 | }
332 |
333 | /**
334 | * @return {String}
335 | */
336 | get set() {
337 | return aliases[this.getAttribute("set")] || options.defaultSet;
338 | }
339 |
340 | /**
341 | * @return {IconSet}
342 | */
343 | get iconSet() {
344 | return options.sets[this.set];
345 | }
346 |
347 | /**
348 | * @return {Number}
349 | */
350 | get stroke() {
351 | return this.getAttribute("stroke") || options.defaultStroke;
352 | }
353 |
354 | static get observedAttributes() {
355 | return ["name", "stroke", "size", "set", "type"];
356 | }
357 |
358 | connectedCallback() {
359 | // innerHTML is not available because not parsed yet
360 | // setTimeout also allows whenDefined to kick in before init
361 | setTimeout(() => {
362 | if (options.lazy && !isInViewport(this)) {
363 | // observer will call init when element is visible
364 | observer.observe(this);
365 | } else {
366 | // init directly
367 | this.init();
368 | }
369 | });
370 | }
371 |
372 | init() {
373 | // Store default content as we will inject it back later
374 | this.defaultHTML = this.innerHTML;
375 | this.loadIcon();
376 | }
377 |
378 | loadIcon() {
379 | const name = this.getAttribute("name");
380 | if (!name) {
381 | return;
382 | }
383 | const iconSet = this.iconSet;
384 | // Clear icon
385 | this.innerHTML = "";
386 | // Useful for customizing size in css
387 | const size = this.getAttribute("size");
388 | if (size) {
389 | this.setSize(size);
390 | }
391 | refreshIcon(this, name, iconSet, this.type);
392 | }
393 |
394 | setSize(size) {
395 | this.style.setProperty("--size", `${size}px`);
396 | if (this.iconSet.opticalFont) {
397 | this.style.setProperty("--opsz", size);
398 | }
399 | }
400 |
401 | attributeChangedCallback(attr, oldVal, newVal) {
402 | // Wait until properly loaded for the first time
403 | if (typeof this.defaultHTML !== "string") {
404 | return;
405 | }
406 | log(`Attr ${attr} changed from ${oldVal} to ${newVal}`);
407 | if (attr === "size") {
408 | this.setSize(newVal);
409 | } else if (newVal) {
410 | log("attribute changed");
411 | this.loadIcon();
412 | }
413 | }
414 | }
415 |
416 | customElements.define("l-i", LastIcon);
417 |
--------------------------------------------------------------------------------
/last-icon.min.css:
--------------------------------------------------------------------------------
1 | l-i{--size: 1em;display:inline-flex;width:var(--size);height:var(--size);vertical-align:middle;contain:strict;justify-content:center;align-items:center}l-i span,l-i svg,l-i i{pointer-events:none}l-i span{font-size:.8em}l-i svg{display:block;width:100%;height:100%}l-i i{font-size:var(--size) !important;color:currentColor}p l-i:not([size]),button l-i:not([size]),a l-i:not([size]),span l-i:not([size]){vertical-align:-0.125em}.material-icons-two-tone{background-color:currentColor;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0)}@-webkit-keyframes symbols-pulse{0%{font-variation-settings:"wght" 100}50%{font-variation-settings:"wght" 700}100%{font-variation-settings:"wght" 100}}@keyframes symbols-pulse{0%{font-variation-settings:"wght" 100}50%{font-variation-settings:"wght" 700}100%{font-variation-settings:"wght" 100}}@-webkit-keyframes symbols-fill{0%{font-variation-settings:"FILL" 0}100%{font-variation-settings:"FILL" 1}}@keyframes symbols-fill{0%{font-variation-settings:"FILL" 0}100%{font-variation-settings:"FILL" 1}}@-webkit-keyframes symbols-empty{0%{font-variation-settings:"FILL" 1}100%{font-variation-settings:"FILL" 0}}@keyframes symbols-empty{0%{font-variation-settings:"FILL" 1}100%{font-variation-settings:"FILL" 0}}.symbols-pulse i,.symbols-pulse-hover:hover i,.symbols-fill i,.symbols-fill-hover:hover i,.symbols-empty i,.symbols-empty-hover:hover i{-webkit-animation-timing-function:cubic-bezier(0.45, 0, 0.55, 1);animation-timing-function:cubic-bezier(0.45, 0, 0.55, 1);-webkit-animation-duration:var(--duration, 0.5s);animation-duration:var(--duration, 0.5s)}.symbols-pulse i,.symbols-pulse-hover:hover i{--duration: 1.5s;-webkit-animation-name:symbols-pulse;animation-name:symbols-pulse;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.symbols-fill i,.symbols-fill-hover:hover i{-webkit-animation-name:symbols-fill;animation-name:symbols-fill;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.symbols-empty i,.symbols-empty-hover:hover i{-webkit-animation-name:symbols-empty;animation-name:symbols-empty;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.material-symbols-rounded,.material-symbols-outlined,.material-symbols-sharp{font-variation-settings:"FILL" var(--fill, 0),"wght" var(--weight, 400),"GRAD" var(--grad, 0),"OPSZ" var(--opsz, 24)}.dark{--grad: -25;background:#000;color:#fff}.dark[disabled],.dark.disabled{color:rgba(255,255,255,.3)}l-i[theme=multi-color] path[fill="#2F88FF"]{fill:var(--color)}l-i[theme=multi-color] path[fill="#43CCF8"]{fill:var(--color-secondary);stroke:var(--color-bg)}l-i[theme=multi-color] path[stroke=white]{stroke:#fff}l-i[theme=multi-color] path[stroke=black]{stroke:#333}l-i[theme=two-tone] path[fill="#2F88FF"]{fill:var(--color)}l-i[theme=two-tone] path[fill="#43CCF8"]{fill:var(--color)}l-i[theme=two-tone] path[stroke=white],l-i[theme=two-tone] path[stroke=black]{stroke:#333}l-i[theme=filled] path[fill]{fill:currentColor}l-i[theme=filled] path[stroke=black]{stroke:#fff}l-i[theme=outline] path[fill]{fill:none}l-i[theme=outline] path[stroke=black]{stroke:currentColor}l-i[theme=outline] path[fill="#43CCF8"]{fill:currentColor}@-webkit-keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(359deg)}}@-webkit-keyframes burst{0%{transform:scale(1);opacity:1}90%{transform:scale(1.5);opacity:0}}@keyframes burst{0%{transform:scale(1);opacity:1}90%{transform:scale(1.5);opacity:0}}@-webkit-keyframes flashing{0%{opacity:1}45%{opacity:0}90%{opacity:1}}@keyframes flashing{0%{opacity:1}45%{opacity:0}90%{opacity:1}}@-webkit-keyframes fade-left{0%{transform:translateX(0);opacity:1}75%{transform:translateX(-20px);opacity:0}}@keyframes fade-left{0%{transform:translateX(0);opacity:1}75%{transform:translateX(-20px);opacity:0}}@-webkit-keyframes fade-right{0%{transform:translateX(0);opacity:1}75%{transform:translateX(20px);opacity:0}}@keyframes fade-right{0%{transform:translateX(0);opacity:1}75%{transform:translateX(20px);opacity:0}}@-webkit-keyframes fade-up{0%{transform:translateY(0);opacity:1}75%{transform:translateY(-20px);opacity:0}}@keyframes fade-up{0%{transform:translateY(0);opacity:1}75%{transform:translateY(-20px);opacity:0}}@-webkit-keyframes fade-down{0%{transform:translateY(0);opacity:1}75%{transform:translateY(20px);opacity:0}}@keyframes fade-down{0%{transform:translateY(0);opacity:1}75%{transform:translateY(20px);opacity:0}}@-webkit-keyframes tada{from{transform:scale3d(1, 1, 1)}10%,20%{transform:scale3d(0.95, 0.95, 0.95) rotate3d(0, 0, 1, -10deg)}30%,50%,70%,90%{transform:scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg)}40%,60%,80%{transform:scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg)}to{transform:scale3d(1, 1, 1)}}@keyframes tada{from{transform:scale3d(1, 1, 1)}10%,20%{transform:scale3d(0.95, 0.95, 0.95) rotate3d(0, 0, 1, -10deg)}30%,50%,70%,90%{transform:scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg)}40%,60%,80%{transform:rotate3d(0, 0, 1, -10deg)}to{transform:scale3d(1, 1, 1)}}.spin{-webkit-animation:spin 1.5s linear infinite;animation:spin 1.5s linear infinite}.spin-hover:hover{-webkit-animation:spin 1.5s linear infinite;animation:spin 1.5s linear infinite}.tada{-webkit-animation:tada 1.5s ease infinite;animation:tada 1.5s ease infinite}.tada-hover:hover{-webkit-animation:tada 1.5s ease infinite;animation:tada 1.5s ease infinite}.flashing{-webkit-animation:flashing 1.5s infinite linear;animation:flashing 1.5s infinite linear}.flashing-hover:hover{-webkit-animation:flashing 1.5s infinite linear;animation:flashing 1.5s infinite linear}.burst{-webkit-animation:burst 1.5s infinite linear;animation:burst 1.5s infinite linear}.burst-hover:hover{-webkit-animation:burst 1.5s infinite linear;animation:burst 1.5s infinite linear}.fade-up{-webkit-animation:fade-up 1.5s infinite linear;animation:fade-up 1.5s infinite linear}.fade-up-hover:hover{-webkit-animation:fade-up 1.5s infinite linear;animation:fade-up 1.5s infinite linear}.fade-down{-webkit-animation:fade-down 1.5s infinite linear;animation:fade-down 1.5s infinite linear}.fade-down-hover:hover{-webkit-animation:fade-down 1.5s infinite linear;animation:fade-down 1.5s infinite linear}.fade-left{-webkit-animation:fade-left 1.5s infinite linear;animation:fade-left 1.5s infinite linear}.fade-left-hover:hover{-webkit-animation:fade-left 1.5s infinite linear;animation:fade-left 1.5s infinite linear}.fade-right{-webkit-animation:fade-right 1.5s infinite linear;animation:fade-right 1.5s infinite linear}.fade-right-hover:hover{-webkit-animation:fade-right 1.5s infinite linear;animation:fade-right 1.5s infinite linear}.rotate-90{transform:rotate(90deg)}.rotate-180{transform:rotate(180deg)}.rotate-270{transform:rotate(270deg)}.flip-horizontal{transform:scaleX(-1)}.flip-vertical{transform:scaleY(-1)}
2 | /*# sourceMappingURL=last-icon.min.css.map */
--------------------------------------------------------------------------------
/last-icon.min.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["last-icon.scss","_material-icons.scss","_material-symbols.scss","_icon-park.scss","_animations.scss","_transformations.scss"],"names":[],"mappings":"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","file":"last-icon.min.css"}
--------------------------------------------------------------------------------
/last-icon.min.js:
--------------------------------------------------------------------------------
1 | (()=>{var o="https://cdn.jsdelivr.net/",g={},n={debug:!1,lazy:!0,replaceName:{},fonts:[],defaultSet:"tabler",defaultStroke:2,sets:{bootstrap:{alias:"bs",svgPath:()=>o+"npm/bootstrap-icons@1/icons/{icon}.svg"},boxicons:{alias:"bx",defaultType:"solid",svgPath:()=>o+"npm/boxicons@2/svg/{type}/{prefix}-{icon}.svg",fixFill:!0,fontClass:()=>"bx {prefix}-{icon}",prefixes:{solid:"bxs",regular:"bx",logos:"bxl"}},bytesize:{alias:"by",svgPath:()=>o+"npm/bytesize-icons@1/dist/icons/{icon}.svg",useStroke:!0},cssgg:{alias:"gg",svgPath:()=>o+"npm/css.gg@2/icons/svg/{icon}.svg"},emojicc:{alias:"em",svgPath:()=>o+"npm/emoji-cc@1/svg/{icon}.svg"},eos:{alias:"eo",defaultType:"solid",svgPath:()=>o+"gh/lekoala/eos-icons-mirror/{type}/{icon}.svg",fixFill:!0},feather:{alias:"ft",svgPath:()=>o+"npm/feather-icons@4/dist/icons/{icon}.svg"},flags:{alias:"fl",defaultType:"4x3",svgPath:()=>o+"npm/flag-svg-collection@1/flags/{type}/{icon}.svg"},fontawesome:{alias:"fa",defaultType:"solid",svgPath:()=>o+"npm/@fortawesome/fontawesome-free@5/svgs/{type}/{icon}.svg",fixFill:!0,fontClass:()=>"{prefix} fa-{icon}",prefixes:{solid:"fas",regular:"far",light:"fal",duotone:"fad",brands:"fab"}},iconoir:{alias:"in",svgPath:()=>o+"gh/lucaburgio/iconoir/icons/{icon}.svg",fontClass:()=>"iconoir-{icon}",useStroke:!0},iconpark:{alias:"ip",types:[],svgPath:()=>o+"gh/bytedance/IconPark/source/{type}/{icon}.svg",useStroke:!0},lucide:{alias:"lu",svgPath:()=>o+"npm/lucide-static/icons/{icon}.svg"},material:{alias:"mi",defaultType:"filled",svgPath:()=>o+"npm/@material-design-icons/svg/{type}/{icon}.svg",fontClass:t=>t==="filled"?"material-icons":"material-icons-{type}"},phosphor:{alias:"ph",defaultType:"regular",svgPath:t=>t==="regular"?o+"npm/@phosphor-icons/core@2/assets/{type}/{icon}.svg":o+"npm/@phosphor-icons/core@2/assets/{type}/{icon}-{type}.svg",fontClass:t=>t==="regular"?"ph ph-{icon}":"ph-{type} ph-{icon}"},supertiny:{alias:"st",svgPath:()=>o+"npm/super-tiny-icons/images/svg/{icon}.svg"},symbols:{alias:"ms",defaultType:"outlined",svgPath:()=>o+"npm/@material-symbols/svg-400@0.5/{type}/{icon}.svg",fixFill:!0,fontClass:()=>"material-symbols-{type}",opticalFont:!0},tabler:{alias:"ti",svgPath:()=>o+"npm/@tabler/icons@2/icons/{icon}.svg",useStroke:!0,fontClass:()=>"ti ti-{icon}"}}},b=new window.IntersectionObserver((t,e)=>{t.forEach(async s=>{s.isIntersecting&&(e.unobserve(s.target),s.target.init())})});function u(t,e,s,i){return t=t.replace("{icon}",e),i?t=t.replaceAll("{type}",i):t=t.replace("-{type}",""),s.prefixes&&s.prefixes[i]&&(t=t.replace("{prefix}",s.prefixes[i])),t}function a(t){n.debug&&console.log(`[l-i] ${t}`)}function y(t,e,s){let i=e.svgPath(s);if(!i)throw Error(`Icon set ${e} does not exists`);let r=`${e.name}-${t}-${s||"base"}`;return i=u(i,t,e,s),i&&g[r]?(a(`Fetching ${r} from cache`),g[r]):(a(`Fetching ${r} from url ${i}`),g[r]=fetch(i).then(function(l){if(l.ok)return l.text();throw Error(l.status)}),g[r])}function v(t,e,s,i){if(n.replaceName[e]&&(e=n.replaceName[e]),!i&&s.defaultType&&(i=s.defaultType),n.fonts.includes(s.name)&&!t.hasAttribute("stroke")){a(`Using font for ${e}`);let r=s.fontClass(i),l=r.includes("{icon}");r=u(r,e,s,i),l?t.innerHTML=` `:t.innerHTML=`${e} `,t.stroke&&s.opticalFont&&t.style.setProperty("--weight",t.stroke*100);return}y(e,s,i).then(r=>{r.includes("class=")&&(r=r.replace(/ class="([a-z- ]*)"/g,"")),(t.stroke||s.useStroke)&&(r=r.replace(/stroke-width="([0-9\.]*)"/g,`stroke-width="${t.stroke}"`)),s.fixFill&&(r=r.replace(/(/,'$1 fill="currentColor">')),t.defaultHTML&&(r=r.replace("",`${t.defaultHTML}`)),t.innerHTML=r}).catch(r=>{t.innerHTML="\u26A0\uFE0F ",console.error(`Failed to load icon ${e} (error ${r})`)})}function x(t){let e=t.getBoundingClientRect();return e.top>=0&&e.left>=0&&e.bottom<=(window.innerHeight||document.documentElement.clientHeight)&&e.right<=(window.innerWidth||document.documentElement.clientWidth)}function h(...t){let e=i=>i&&typeof i=="object",s=Array.isArray;return t.reduce((i,r)=>(Object.keys(r).forEach(l=>{let f=i[l],c=r[l];s(f)&&s(c)?i[l]=f.concat(...c):e(f)&&e(c)?i[l]=h(f,c):i[l]=c}),i),{})}var p={};function d(){for(let[t,e]of Object.entries(n.sets))p[e.alias]=t,e.name=t}d();var m=class extends HTMLElement{static configure(e={}){for(let s in e){if(typeof n[s]>"u"){console.error(`Invalid option key ${s}`);return}Array.isArray(e[s])?n[s]=n[s].concat(e[s]):typeof e[s]=="object"?n[s]=h(n[s],e[s]):n[s]=e[s]}return d(),a("configuring options"),n}get type(){return this.getAttribute("type")||null}get set(){let e=this.getAttribute("set");return p[e]||n.defaultSet}get iconSet(){return n.sets[this.set]}get stroke(){return this.getAttribute("stroke")||n.defaultStroke}static get observedAttributes(){return["name","stroke","size","set","type"]}connectedCallback(){setTimeout(()=>{n.lazy&&!x(this)?b.observe(this):this.init()})}init(){this.defaultHTML=this.innerHTML,this.loadIcon()}loadIcon(){let e=this.getAttribute("name");if(!e)return;let s=this.iconSet;this.innerHTML="";let i=this.getAttribute("size");i&&this.setSize(i),v(this,e,s,this.type)}setSize(e){this.style.setProperty("--size",`${e}px`),this.iconSet.opticalFont&&this.style.setProperty("--opsz",e)}attributeChangedCallback(e,s,i){typeof this.defaultHTML=="string"&&(a(`Attr ${e} changed from ${s} to ${i}`),e==="size"?this.setSize(i):i&&(a("attribute changed"),this.loadIcon()))}};customElements.define("l-i",m);})();
2 | //# sourceMappingURL=last-icon.min.js.map
3 |
--------------------------------------------------------------------------------
/last-icon.min.js.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "sources": ["last-icon.js"],
4 | "sourcesContent": ["const JSDELIVR = \"https://cdn.jsdelivr.net/\";\r\nconst CACHE = {};\r\n\r\n/**\r\n * @typedef IconSet\r\n * @property {String} alias Short two letters alias\r\n * @property {Function} svgPath The svg path\r\n * @property {Boolean} [fixFill] Does this set needs fixing fill:currentColor ?\r\n * @property {String} [useStroke] Add stroke to svg\r\n * @property {String} [defaultStroke] Default stroke to use (if supports stroke)\r\n * @property {String} [defaultType] Default type to use (when there are multiple types)\r\n * @property {Object.} [prefixes] Types to prefixes\r\n * @property {Function} [fontClass] Font class\r\n * @property {Boolean} [opticalFont] Is an optical font?\r\n * @property {String} [name] Full name (injected automatically)\r\n */\r\n\r\n/**\r\n * @typedef Options\r\n * @property {Boolean} debug Should we output messages to console\r\n * @property {Boolean} lazy Load icons lazily\r\n * @property {Object} replaceName Transparently replace icons with other values\r\n * @property {Array} fonts Icon sets using font icons rather than svg\r\n * @property {String} defaultSet Default icon set\r\n * @property {Object.} sets Available iconsets\r\n */\r\nconst options = {\r\n debug: false,\r\n lazy: true,\r\n replaceName: {},\r\n fonts: [],\r\n defaultSet: \"tabler\",\r\n defaultStroke: 2,\r\n sets: {\r\n bootstrap: {\r\n alias: \"bs\",\r\n svgPath: () => JSDELIVR + \"npm/bootstrap-icons@1/icons/{icon}.svg\",\r\n },\r\n boxicons: {\r\n alias: \"bx\",\r\n // types: [\"solid\", \"regular\", \"logos\"],\r\n defaultType: \"solid\",\r\n svgPath: () => JSDELIVR + \"npm/boxicons@2/svg/{type}/{prefix}-{icon}.svg\",\r\n fixFill: true,\r\n fontClass: () => \"bx {prefix}-{icon}\",\r\n prefixes: {\r\n solid: \"bxs\",\r\n regular: \"bx\",\r\n logos: \"bxl\",\r\n },\r\n },\r\n bytesize: {\r\n alias: \"by\",\r\n svgPath: () => JSDELIVR + \"npm/bytesize-icons@1/dist/icons/{icon}.svg\",\r\n useStroke: true,\r\n },\r\n cssgg: {\r\n alias: \"gg\",\r\n svgPath: () => JSDELIVR + \"npm/css.gg@2/icons/svg/{icon}.svg\",\r\n },\r\n emojicc: {\r\n alias: \"em\",\r\n svgPath: () => JSDELIVR + \"npm/emoji-cc@1/svg/{icon}.svg\",\r\n },\r\n eos: {\r\n alias: \"eo\",\r\n // types: [\"solid\", \"outlined\", \"animated\"],\r\n defaultType: \"solid\",\r\n svgPath: () => JSDELIVR + \"gh/lekoala/eos-icons-mirror/{type}/{icon}.svg\",\r\n fixFill: true,\r\n },\r\n feather: {\r\n alias: \"ft\",\r\n svgPath: () => JSDELIVR + \"npm/feather-icons@4/dist/icons/{icon}.svg\",\r\n },\r\n flags: {\r\n alias: \"fl\",\r\n // types: [\"4x3\", \"1x1\"],\r\n defaultType: \"4x3\",\r\n svgPath: () => JSDELIVR + \"npm/flag-svg-collection@1/flags/{type}/{icon}.svg\",\r\n },\r\n fontawesome: {\r\n alias: \"fa\",\r\n // types: [\"solid\", \"regular\", \"brands\", \"light\", \"duotone\"],\r\n defaultType: \"solid\",\r\n svgPath: () => JSDELIVR + \"npm/@fortawesome/fontawesome-free@5/svgs/{type}/{icon}.svg\",\r\n fixFill: true,\r\n fontClass: () => \"{prefix} fa-{icon}\",\r\n prefixes: {\r\n solid: \"fas\",\r\n regular: \"far\",\r\n light: \"fal\",\r\n duotone: \"fad\",\r\n brands: \"fab\",\r\n },\r\n },\r\n iconoir: {\r\n alias: \"in\",\r\n svgPath: () => JSDELIVR + \"gh/lucaburgio/iconoir/icons/{icon}.svg\",\r\n fontClass: () => \"iconoir-{icon}\",\r\n useStroke: true,\r\n },\r\n iconpark: {\r\n alias: \"ip\",\r\n types: [], // see full list here https://github.com/bytedance/IconPark/tree/master/source\r\n svgPath: () => JSDELIVR + \"gh/bytedance/IconPark/source/{type}/{icon}.svg\",\r\n useStroke: true,\r\n },\r\n lucide: {\r\n alias: \"lu\",\r\n svgPath: () => JSDELIVR + \"npm/lucide-static/icons/{icon}.svg\",\r\n },\r\n material: {\r\n alias: \"mi\",\r\n // types: [\"filled\", \"outlined\", \"round\", \"sharp\", \"two-tone\"],\r\n defaultType: \"filled\",\r\n svgPath: () => JSDELIVR + \"npm/@material-design-icons/svg/{type}/{icon}.svg\",\r\n fontClass: (type) => {\r\n if (type === \"filled\") {\r\n return \"material-icons\";\r\n }\r\n return \"material-icons-{type}\";\r\n },\r\n },\r\n phosphor: {\r\n alias: \"ph\",\r\n // types: [\"regular\", \"bold\", \"duotone\", \"fill\", \"light\", \"thin\"],\r\n defaultType: \"regular\",\r\n svgPath: (type) => {\r\n if (type === \"regular\") {\r\n return JSDELIVR + \"npm/@phosphor-icons/core@2/assets/{type}/{icon}.svg\";\r\n }\r\n return JSDELIVR + \"npm/@phosphor-icons/core@2/assets/{type}/{icon}-{type}.svg\";\r\n },\r\n fontClass: (type) => {\r\n if (type === \"regular\") {\r\n return \"ph ph-{icon}\";\r\n }\r\n return \"ph-{type} ph-{icon}\";\r\n },\r\n },\r\n supertiny: {\r\n alias: \"st\",\r\n svgPath: () => JSDELIVR + \"npm/super-tiny-icons/images/svg/{icon}.svg\",\r\n },\r\n symbols: {\r\n alias: \"ms\",\r\n // types: [\"outlined\", \"rounded\", \"sharp\"],\r\n defaultType: \"outlined\",\r\n svgPath: () => JSDELIVR + \"npm/@material-symbols/svg-400@0.5/{type}/{icon}.svg\",\r\n fixFill: true,\r\n fontClass: () => \"material-symbols-{type}\",\r\n opticalFont: true,\r\n },\r\n tabler: {\r\n alias: \"ti\",\r\n svgPath: () => JSDELIVR + \"npm/@tabler/icons@2/icons/{icon}.svg\",\r\n useStroke: true,\r\n fontClass: () => \"ti ti-{icon}\",\r\n },\r\n },\r\n};\r\n\r\n/**\r\n * @var {IntersectionObserver}\r\n */\r\nconst observer = new window.IntersectionObserver((entries, observerRef) => {\r\n entries.forEach(async (entry) => {\r\n if (entry.isIntersecting) {\r\n observerRef.unobserve(entry.target);\r\n entry.target.init();\r\n }\r\n });\r\n});\r\n\r\n/**\r\n * @param {string} value\r\n * @param {string} iconName\r\n * @param {IconSet} iconSet\r\n * @param {string} iconType\r\n * @return {string}\r\n */\r\nfunction replacePlaceholders(value, iconName, iconSet, iconType) {\r\n value = value.replace(\"{icon}\", iconName);\r\n if (iconType) {\r\n value = value.replaceAll(\"{type}\", iconType);\r\n } else {\r\n // Maybe we want to remove the type like in material icons\r\n value = value.replace(\"-{type}\", \"\");\r\n }\r\n if (iconSet.prefixes && iconSet.prefixes[iconType]) {\r\n value = value.replace(\"{prefix}\", iconSet.prefixes[iconType]);\r\n }\r\n return value;\r\n}\r\n\r\nfunction log(message) {\r\n if (options.debug) {\r\n console.log(`[l-i] ${message}`);\r\n }\r\n}\r\n\r\n/**\r\n * @param {string} iconName\r\n * @param {IconSet} iconSet\r\n * @param {string} iconType\r\n * @return {Promise}\r\n */\r\nfunction getIconSvg(iconName, iconSet, iconType) {\r\n let iconUrl = iconSet.svgPath(iconType);\r\n if (!iconUrl) {\r\n throw Error(`Icon set ${iconSet} does not exists`);\r\n }\r\n const cacheKey = `${iconSet.name}-${iconName}-${iconType || \"base\"}`;\r\n iconUrl = replacePlaceholders(iconUrl, iconName, iconSet, iconType);\r\n\r\n // If we have it in cache\r\n if (iconUrl && CACHE[cacheKey]) {\r\n log(`Fetching ${cacheKey} from cache`);\r\n return CACHE[cacheKey];\r\n }\r\n\r\n // Or resolve\r\n log(`Fetching ${cacheKey} from url ${iconUrl}`);\r\n CACHE[cacheKey] = fetch(iconUrl).then(function (response) {\r\n if (response.ok) {\r\n return response.text();\r\n }\r\n throw Error(response.status);\r\n });\r\n return CACHE[cacheKey];\r\n}\r\n\r\n/**\r\n * @param {LastIcon} inst\r\n * @param {string} iconName\r\n * @param {IconSet} iconSet\r\n * @param {string} iconType\r\n */\r\nfunction refreshIcon(inst, iconName, iconSet, iconType) {\r\n // Replace name\r\n if (options.replaceName[iconName]) {\r\n iconName = options.replaceName[iconName];\r\n }\r\n // Set default type if any\r\n if (!iconType && iconSet.defaultType) {\r\n iconType = iconSet.defaultType;\r\n }\r\n\r\n // Use font (if not using a specific stroke)\r\n if (options.fonts.includes(iconSet.name) && !inst.hasAttribute(\"stroke\")) {\r\n log(`Using font for ${iconName}`);\r\n let iconClass = iconSet.fontClass(iconType);\r\n let nameAsClass = iconClass.includes(\"{icon}\");\r\n iconClass = replacePlaceholders(iconClass, iconName, iconSet, iconType);\r\n if (nameAsClass) {\r\n inst.innerHTML = ` `;\r\n } else {\r\n inst.innerHTML = `${iconName} `;\r\n }\r\n if (inst.stroke && iconSet.opticalFont) {\r\n inst.style.setProperty(\"--weight\", inst.stroke * 100);\r\n }\r\n return; // Return early\r\n }\r\n\r\n getIconSvg(iconName, iconSet, iconType)\r\n .then((iconData) => {\r\n // Strip class attribute as it may be affected by css\r\n if (iconData.includes(\"class=\")) {\r\n iconData = iconData.replace(/ class=\"([a-z- ]*)\"/g, \"\");\r\n }\r\n // Add and/or fix stroke\r\n if (inst.stroke || iconSet.useStroke) {\r\n iconData = iconData.replace(/stroke-width=\"([0-9\\.]*)\"/g, `stroke-width=\"${inst.stroke}\"`);\r\n }\r\n // Fix fill to currentColor\r\n if (iconSet.fixFill) {\r\n iconData = iconData.replace(/(/, '$1 fill=\"currentColor\">');\r\n }\r\n // If we have some html, pass it along (useful for svg anim)\r\n if (inst.defaultHTML) {\r\n iconData = iconData.replace(\"\", `${inst.defaultHTML}`);\r\n }\r\n inst.innerHTML = iconData;\r\n })\r\n .catch((error) => {\r\n inst.innerHTML = \"\u26A0\uFE0F \";\r\n console.error(`Failed to load icon ${iconName} (error ${error})`);\r\n });\r\n}\r\n\r\n/**\r\n * @param {HTMLElement} element\r\n * @returns {Boolean}\r\n */\r\nfunction isInViewport(element) {\r\n const rect = element.getBoundingClientRect();\r\n return (\r\n rect.top >= 0 &&\r\n rect.left >= 0 &&\r\n rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\r\n rect.right <= (window.innerWidth || document.documentElement.clientWidth)\r\n );\r\n}\r\n\r\n/**\r\n * Performs a deep merge of objects and returns new object. Does not modify\r\n * objects (immutable) and merges arrays via concatenation.\r\n *\r\n * @param {...object} objects - Objects to merge\r\n * @returns {object} New object with merged key/values\r\n */\r\nfunction mergeDeep(...objects) {\r\n const isObject = (obj) => obj && typeof obj === \"object\";\r\n const isArray = Array.isArray;\r\n\r\n return objects.reduce((prev, obj) => {\r\n Object.keys(obj).forEach((key) => {\r\n const pVal = prev[key];\r\n const oVal = obj[key];\r\n\r\n if (isArray(pVal) && isArray(oVal)) {\r\n prev[key] = pVal.concat(...oVal);\r\n } else if (isObject(pVal) && isObject(oVal)) {\r\n prev[key] = mergeDeep(pVal, oVal);\r\n } else {\r\n prev[key] = oVal;\r\n }\r\n });\r\n\r\n return prev;\r\n }, {});\r\n}\r\n\r\nlet aliases = {};\r\nfunction processIconSets() {\r\n for (const [key, set] of Object.entries(options.sets)) {\r\n // List aliases for easy retrieval\r\n aliases[set.alias] = key;\r\n // Include full name in iconset definition\r\n set.name = key;\r\n }\r\n}\r\nprocessIconSets();\r\n\r\nclass LastIcon extends HTMLElement {\r\n /**\r\n * @param {object} opts\r\n * @returns {Options} The updated option object\r\n */\r\n static configure(opts = {}) {\r\n for (const k in opts) {\r\n if (typeof options[k] === \"undefined\") {\r\n console.error(`Invalid option key ${k}`);\r\n return;\r\n }\r\n if (Array.isArray(opts[k])) {\r\n options[k] = options[k].concat(opts[k]);\r\n } else if (typeof opts[k] === \"object\") {\r\n options[k] = mergeDeep(options[k], opts[k]);\r\n } else {\r\n options[k] = opts[k];\r\n }\r\n }\r\n processIconSets();\r\n // Log after we had the opportunity to change debug flag\r\n log(\"configuring options\");\r\n return options;\r\n }\r\n\r\n /**\r\n * @return {String|null}\r\n */\r\n get type() {\r\n return this.getAttribute(\"type\") || null;\r\n }\r\n\r\n /**\r\n * @return {String}\r\n */\r\n get set() {\r\n let v = this.getAttribute(\"set\");\r\n return aliases[v] || options.defaultSet;\r\n }\r\n\r\n /**\r\n * @return {IconSet}\r\n */\r\n get iconSet() {\r\n return options.sets[this.set];\r\n }\r\n\r\n /**\r\n * @return {Number}\r\n */\r\n get stroke() {\r\n return this.getAttribute(\"stroke\") || options.defaultStroke;\r\n }\r\n\r\n static get observedAttributes() {\r\n return [\"name\", \"stroke\", \"size\", \"set\", \"type\"];\r\n }\r\n\r\n connectedCallback() {\r\n // innerHTML is not available because not parsed yet\r\n // setTimeout also allows whenDefined to kick in before init\r\n setTimeout(() => {\r\n if (options.lazy && !isInViewport(this)) {\r\n // observer will call init when element is visible\r\n observer.observe(this);\r\n } else {\r\n // init directly\r\n this.init();\r\n }\r\n });\r\n }\r\n\r\n init() {\r\n // Store default content as we will inject it back later\r\n this.defaultHTML = this.innerHTML;\r\n this.loadIcon();\r\n }\r\n\r\n loadIcon() {\r\n const name = this.getAttribute(\"name\");\r\n if (!name) {\r\n return;\r\n }\r\n const iconSet = this.iconSet;\r\n // Clear icon\r\n this.innerHTML = \"\";\r\n // Useful for customizing size in css\r\n const size = this.getAttribute(\"size\");\r\n if (size) {\r\n this.setSize(size);\r\n }\r\n refreshIcon(this, name, iconSet, this.type);\r\n }\r\n\r\n setSize(size) {\r\n this.style.setProperty(\"--size\", `${size}px`);\r\n if (this.iconSet.opticalFont) {\r\n this.style.setProperty(\"--opsz\", size);\r\n }\r\n }\r\n\r\n attributeChangedCallback(attr, oldVal, newVal) {\r\n // Wait until properly loaded for the first time\r\n if (typeof this.defaultHTML !== \"string\") {\r\n return;\r\n }\r\n log(`Attr ${attr} changed from ${oldVal} to ${newVal}`);\r\n if (attr === \"size\") {\r\n this.setSize(newVal);\r\n } else if (newVal) {\r\n log(\"attribute changed\");\r\n this.loadIcon();\r\n }\r\n }\r\n}\r\n\r\ncustomElements.define(\"l-i\", LastIcon);\r\n"],
5 | "mappings": "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",
6 | "names": []
7 | }
8 |
--------------------------------------------------------------------------------
/last-icon.scss:
--------------------------------------------------------------------------------
1 | l-i {
2 | --size: 1em;
3 | display: inline-flex;
4 | width: var(--size);
5 | height: var(--size);
6 | vertical-align: middle;
7 | contain: strict;
8 | justify-content: center;
9 | align-items: center;
10 |
11 | span,
12 | svg,
13 | i {
14 | pointer-events: none;
15 | }
16 |
17 | // This is needed for ⚠️ icon as it goes outside of the limits of the div
18 | // And it would be cut due to contain: strict usage
19 | span {
20 | font-size: 0.8em;
21 | }
22 |
23 | svg {
24 | display: block;
25 | width: 100%;
26 | height: 100%;
27 | }
28 | // When rendering font icons (eg: material, symbols...)
29 | i {
30 | font-size: var(--size) !important;
31 | color: currentColor;
32 | }
33 | }
34 | p,
35 | button,
36 | a,
37 | span {
38 | l-i:not([size]) {
39 | vertical-align: -0.125em;
40 | }
41 | }
42 |
43 | // Optional features
44 | @import "material-icons";
45 | @import "material-symbols";
46 | @import "icon-park";
47 | @import "animations";
48 | @import "transformations";
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "last-icon",
3 | "version": "2.2.0",
4 | "description": "One custom icon element to rule them all",
5 | "main": "last-icon.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "esbuild --bundle --minify --format=iife last-icon.js --outfile=last-icon.min.js",
9 | "build-min": "npm run build && git add -A && git commit -m \"build files\"",
10 | "start": "npm run build -- --servedir=.",
11 | "watch": "npm run build -- --watch",
12 | "bump": "npm run build-min && npm version patch",
13 | "jsdoc": "npx jsdoc-to-markdown last-icon.js"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/lekoala/last-icon"
18 | },
19 | "keywords": [
20 | "DOM",
21 | "icons",
22 | "icon",
23 | "custom",
24 | "element",
25 | "component",
26 | "es6"
27 | ],
28 | "author": "LeKoala",
29 | "license": "MIT"
30 | }
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # last-icon
2 |
3 | [](https://nodei.co/npm/last-icon/)
4 | [](https://www.npmjs.com/package/last-icon)
5 |
6 | The last icon library you will ever need.
7 |
8 | Key features:
9 |
10 | - Load as svg or font icons
11 | - Mix & match icon sets if needed
12 | - Bring your own icons
13 | - Fix iconsistencies
14 | - Lazy load your icons
15 |
16 | ## How to use
17 |
18 | Simply include the library
19 |
20 | ```html
21 |
22 | ```
23 |
24 | **NOTE:** It is recommended to define this as early as possible so that all icons are resolved as soon
25 | as possible. Otherwise, you might see a delay before your icons are being displayed.
26 | Even when doing this, you might still see a very small delay as opposed as a font icon or an embedded SVG.
27 |
28 | If you are fine with a little more delay, you can use this instead (which will be ignored on browsers
29 | that don’t support modules):
30 |
31 | ```html
32 |
33 |
34 |
35 | ```
36 |
37 | And call your icons!
38 |
39 | ```html
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ```
48 |
49 | The following CSS is recommended:
50 |
51 | ```css
52 | l-i {
53 | --size: 1em;
54 | display: inline-flex;
55 | width: var(--size);
56 | height: var(--size);
57 | vertical-align: middle;
58 | }
59 | l-i svg {
60 | display: block;
61 | width: 100%;
62 | height: 100%;
63 | }
64 | p l-i,
65 | button l-i,
66 | a l-i,
67 | span l-i {
68 | vertical-align: -0.125em;
69 | }
70 | ```
71 |
72 | ## Configuring
73 |
74 | You can set any options using `LastIcon.configure`. The recommended way to call it is this way:
75 |
76 | ```js
77 | customElements.whenDefined("l-i").then(() => {
78 | // Access through registry
79 | customElements.get("l-i").configure({
80 | debug: true,
81 | // Transform stars to trash
82 | // replaceName: {
83 | // star: "trash"
84 | // },
85 | // Use font icon
86 | // fonts: [
87 | // "material",
88 | // "phosphor",
89 | // ],
90 | // Change default set
91 | defaultSet: "tabler",
92 | // Change default stroke
93 | // defaultStroke: 1,
94 | });
95 | });
96 | ```
97 |
98 | All available options:
99 |
100 | | Name | Type | Description |
101 | |-------------|---------------------------------------------|-----------------------------------------------|
102 | | debug | Boolean
| Should we output messages to console |
103 | | lazy | Boolean
| Load icons lazily |
104 | | replaceName | Object
| Transparently replace icons with other values |
105 | | fonts | Array
| Icon sets using font icons rather than SVG |
106 | | defaultSet | String
| Default icon set |
107 | | sets | Object.<string, IconSet>
| Available iconsets |
108 |
109 | ## Supported icon sets
110 |
111 | | Icon Set | Name | Alias | Types | Stroke | Count | Website |
112 | |-------------------|-------------|-------|:-----:|:------:|:------|--------------------------------------------------------------------------|
113 | | Bootstrap Icons | bootstrap | bs | 1 | x | 1800+ | [bootstrap](https://icons.getbootstrap.com/) |
114 | | Flags | flags | fl | 1 | x | ? | [flags](https://github.com/lipis/flag-icons/) |
115 | | Fontawesome | fontawesome | fa | 5 | x | 1600+ | [fontawesome](https://fontawesome.com/cheatsheet) |
116 | | Iconoir | iconoir | in | 1 | x | 1600+ | [iconoir](https://iconoir.com/) |
117 | | IconPark | iconpark | ip | 4 | v | 2400+ | [iconpark](https://iconpark.oceanengine.com/official) |
118 | | Lucide | lucide | lu | 1 | v | 1800+ | [lucide](https://lucide.dev/) |
119 | | Materials Icons | material | mi | 5 | x | 1100+ | [material icons](https://fonts.google.com/icons?selected=Material+Icons) |
120 | | Phosphor | phosphor | ph | 6 | x | 9000+ | [phosphor](https://phosphoricons.com/) |
121 | | Super Tiny Icons | supertiny | st | 1 | x | 386 | [supertiny](https://github.com/edent/SuperTinyIcons) |
122 | | Materials Symbols | symbols | ms | 3 | v | 2500+ | [material symbols](https://fonts.google.com/icons) |
123 | | Tabler Icons | tabler | ti | 1 | v | 5700+ | [tabler](https://tabler-icons.io/) |
124 |
125 | ---
126 |
127 | ## Adding or updating an icon set
128 |
129 | You can update any option for an icon set
130 |
131 | | Name | Type | Description |
132 | |-----------------|--------------------------------------------|-----------------------------------------------------|
133 | | alias | String
| Short two letters alias |
134 | | svgPath | function
| The SVG path |
135 | | [fixFill] | Boolean
| Does this set needs fixing `fill:currentColor`? |
136 | | [useStroke] | String
| Add stroke to svg |
137 | | [defaultStroke] | String
| Default stroke to use (if supports stroke) |
138 | | [defaultType] | String
| Default type to use (when there are multiple types) |
139 | | [prefixes] | Object.<string, string>
| Types to prefixes |
140 | | [fontClass] | function
| Font class |
141 | | [opticalFont] | Boolean
| Is an optical font? |
142 | | [name] | String
| Full name (injected automatically) |
143 |
144 | ## Fill
145 |
146 | Some icon sets include a default `fill="currentColor"` and some don't. In order
147 | to have all icon sets behave consistently, we apply a `fill="currentColor"` to all
148 | icon sets. This fix apply to: 'material', 'fontawesome'.
149 |
150 | ## Why a custom element
151 |
152 | - External sprite or font is loading all the icons which lead to extra load time
153 | - Including SVG is leading to really super long HTML and not very developer friendly
154 | - No need for custom JS inliner, it feels cleaner overall
155 |
156 | ## Why external CSS
157 |
158 | A custom element CSS is not loaded until the component itself is loaded, which
159 | can lead to FOUC and things moving around as the icon appear.
160 | The solution I've found so far is to apply some global CSS rules than are known
161 | before the component is loaded.
162 |
163 | You can check any extra SCSS that might be useful for you as well.
164 |
165 | ## Using fonts
166 |
167 | Sometimes it is easier to use an icon font. Indeed, it is fully cached by the browser and will not have any display glitch.
168 | Obviously, the downside is that you have to load the whole font, but it's cached after the first load.
169 | The advantage of using LastIcon over regular icons is that is allows you to switch easily between one way or the other.
170 |
171 | First of all, load any relevant fonts style
172 |
173 | ```html
174 |
175 |
176 |
177 |
178 |
179 | ```
180 |
181 | And after that, use the font config to tell Last Icon to use the font over the SVG icons
182 |
183 | ```js
184 | customElements.whenDefined("l-i").then(() => {
185 | // Access through registry
186 | customElements.get("l-i").configure({
187 | debug: true,
188 | fonts: ["material"],
189 | material: {
190 | defaultType: "two-tone",
191 | },
192 | });
193 | });
194 | ```
195 |
196 | And then, update your styles:
197 |
198 | ```css
199 | l-i {
200 | --size: 1em;
201 | display: inline-flex;
202 | width: var(--size);
203 | height: var(--size);
204 | vertical-align: middle;
205 |
206 | svg {
207 | display: block;
208 | width: 100%;
209 | height: 100%;
210 | }
211 | i {
212 | font-size: var(--size) !important;
213 | color: currentColor;
214 | }
215 | }
216 | p,
217 | button,
218 | a,
219 | span {
220 | l-i {
221 | vertical-align: -0.125em;
222 | }
223 | }
224 |
225 | .material-icons-two-tone {
226 | background-color: currentColor;
227 | -webkit-background-clip: text;
228 | -webkit-text-fill-color: transparent;
229 | }
230 | ```
231 |
232 | ## Demo
233 |
234 | See `demo.html` or the following pen https://codepen.io/lekoalabe/pen/eYvdjqY
235 |
236 | ## Worth looking at
237 |
238 | You might also be interested in https://icon-sets.iconify.design/
239 |
--------------------------------------------------------------------------------