1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | "use strict";
|
8 |
|
9 |
|
10 | import PACKAGE_JSON from "../package.json";
|
11 |
|
12 |
|
13 | const PACKAGE_NAME = PACKAGE_JSON.name;
|
14 |
|
15 |
|
16 | const PACKAGE_VERSION = PACKAGE_JSON.version;
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | if(typeof IMPORT_POLYFILLS !== "undefined" && !!IMPORT_POLYFILLS) require("./polyfills");
|
22 |
|
23 |
|
24 | const DEFAULT_OPTIONS = {
|
25 | directive: {
|
26 | name: "v-svg-inline",
|
27 | spriteModifierName: "sprite"
|
28 | },
|
29 | attributes: {
|
30 | clone: [ "viewbox" ],
|
31 | merge: [ "class", "style" ],
|
32 | add: [ {
|
33 | name: "focusable",
|
34 | value: false
|
35 | }, {
|
36 | name: "role",
|
37 | value: "presentation"
|
38 | }, {
|
39 | name: "tabindex",
|
40 | value: -1
|
41 | } ],
|
42 | data: [],
|
43 | remove: [ "alt", "src", "data-src" ]
|
44 | },
|
45 | cache: {
|
46 | version: PACKAGE_VERSION,
|
47 | persistent: true,
|
48 | removeRevisions: true
|
49 | },
|
50 | intersectionObserverOptions: {},
|
51 | axios: null,
|
52 | xhtml: false
|
53 | };
|
54 |
|
55 |
|
56 | const OBSERVER_REF_ID = "observer";
|
57 |
|
58 |
|
59 | const CONTAINER_REF_ID = "container";
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | const FLAGS_ID = `${PACKAGE_NAME}-flags`;
|
67 |
|
68 |
|
69 | const SYMBOL_ID = `${PACKAGE_NAME}-sprite`;
|
70 |
|
71 |
|
72 | const CONTAINER_ID = `${SYMBOL_ID}-${CONTAINER_REF_ID}`;
|
73 |
|
74 |
|
75 | const REGEXP_SVG_FILENAME = /.+\.svg(?:[?#].*)?$/i;
|
76 | const REGEXP_SVG_CONTENT = /<svg(\s+[^>]+)?>([\s\S]+)<\/svg>/i;
|
77 | const REGEXP_ATTRIBUTES = /\s*([^\s=]+)[\s=]+(?:"([^"]*)"|'([^']*)')?\s*/g;
|
78 | const REGEXP_ATTRIBUTE_NAME = /^[a-z](?:[a-z0-9-:]*[a-z0-9])?$/i;
|
79 | const REGEXP_VUE_DIRECTIVE = /^v-/i;
|
80 | const REGEXP_WHITESPACE = /\s+/g;
|
81 | const REGEXP_TEMPLATE_LITERALS_WHITESPACE = /[\n\t]+/g;
|
82 |
|
83 |
|
84 | const CORRECT_RESPONSE_STATUSES = new Set([
|
85 | 200,
|
86 | 304
|
87 | ]);
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 | const install = (VueOrApp = null, options = {}) => {
|
96 |
|
97 |
|
98 | const _str = "string";
|
99 | const _fnc = "function";
|
100 | const _obj = "object";
|
101 |
|
102 |
|
103 | if(!VueOrApp) throw new Error(`[${PACKAGE_NAME}] Required argument is missing! [VueOrApp]`);
|
104 |
|
105 |
|
106 | if(![ _fnc, _obj ].includes(typeof VueOrApp)) throw new TypeError(`[${PACKAGE_NAME}] Required argument is not valid! [VueOrApp]`);
|
107 |
|
108 |
|
109 | if(!VueOrApp.directive) throw new Error(`[${PACKAGE_NAME}] Required method is missing! [VueOrApp.directive]`);
|
110 |
|
111 |
|
112 | if(typeof VueOrApp.directive !== _fnc) throw new TypeError(`[${PACKAGE_NAME}] Required method is not valid! [VueOrApp.directive]`);
|
113 |
|
114 |
|
115 | if(!VueOrApp.version) throw new Error(`[${PACKAGE_NAME}] Required property is missing! [VueOrApp.version]`);
|
116 |
|
117 |
|
118 | if(typeof VueOrApp.version !== _str) throw new TypeError(`[${PACKAGE_NAME}] Required property is not valid! [VueOrApp.version]`);
|
119 |
|
120 |
|
121 | if(VueOrApp.version.startsWith("1.")) throw new Error(`[${PACKAGE_NAME}] Vue@1 is not supported!`);
|
122 |
|
123 |
|
124 | ["directive", "attributes", "cache", "intersectionObserverOptions"].forEach(option => options[option] = Object.assign({}, DEFAULT_OPTIONS[option], options[option] || {}));
|
125 | options = Object.assign({}, DEFAULT_OPTIONS, options);
|
126 |
|
127 |
|
128 | for(const option in options.directive) {
|
129 |
|
130 |
|
131 | options.directive[option] = options.directive[option].toString().trim().toLowerCase();
|
132 |
|
133 |
|
134 | if(!options.directive[option] || option === "name" && !REGEXP_ATTRIBUTE_NAME.test(options.directive[option])) throw new TypeError(`[${PACKAGE_NAME}] Option is not valid! [options.directives.${option}="${options.directives[option]}"]`);
|
135 |
|
136 | }
|
137 |
|
138 |
|
139 | options.directive.name = options.directive.name.replace(REGEXP_VUE_DIRECTIVE, "");
|
140 |
|
141 |
|
142 | for(const option in options.attributes) {
|
143 |
|
144 |
|
145 | if(!Array.isArray(options.attributes[option])) throw new TypeError(`[${PACKAGE_NAME}] Option is not valid! [options.attributes.${option}=${JSON.stringify(options.attributes[option])}]`);
|
146 |
|
147 |
|
148 | options.attributes[option] = option === "add" ? options.attributes[option].map(attribute => ({
|
149 | name: attribute.name.toString().trim().toLowerCase(),
|
150 | value: attribute.value.toString().trim()
|
151 | })) : options.attributes[option].map(attribute => attribute.toString().trim().toLowerCase());
|
152 |
|
153 |
|
154 | options.attributes[option] = new Set(options.attributes[option]);
|
155 |
|
156 | }
|
157 |
|
158 |
|
159 | for(const option in options.cache) {
|
160 |
|
161 |
|
162 | options.cache[option] = option === "version" ? options.cache[option].toString().trim().toLowerCase() : !!options.cache[option];
|
163 |
|
164 | }
|
165 |
|
166 |
|
167 | options.xhtml = !!options.xhtml;
|
168 |
|
169 |
|
170 | const isVue3 = VueOrApp.version.startsWith("3.");
|
171 |
|
172 |
|
173 | options._fetch = "fetch" in window && typeof fetch === _fnc;
|
174 |
|
175 |
|
176 | options._axios = "axios" in window && typeof axios === _fnc;
|
177 |
|
178 | |
179 |
|
180 |
|
181 |
|
182 |
|
183 | const validateAxiosGetMethod = (axios = null) => !!axios && typeof axios === _fnc && "get" in axios && typeof axios.get === _fnc;
|
184 |
|
185 |
|
186 | let axiosIsValid = false;
|
187 |
|
188 |
|
189 | options.axios = ((axiosIsValid = validateAxiosGetMethod(options.axios)) ? options.axios : null) || (options._axios && "create" in axios && typeof axios.create === _fnc ? axios.create() : null);
|
190 |
|
191 |
|
192 | options._axios = axiosIsValid || validateAxiosGetMethod(options.axios);
|
193 |
|
194 |
|
195 | if(!options._fetch && !options._axios) throw new Error(`[${PACKAGE_NAME}] Feature is not supported by browser! [fetch || axios]`);
|
196 |
|
197 |
|
198 | options._observer = "IntersectionObserver" in window;
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | if(!options._observer) console.error(`[${PACKAGE_NAME}] Feature is not supported by browser! Disabling lazy processing of image nodes. [IntersectionObserver]`);
|
204 |
|
205 |
|
206 | options._storage = "localStorage" in window;
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | if(!options._storage && options.cache.persistent) console.error(`[${PACKAGE_NAME}] Feature is not supported by browser! Disabling persistent cache of SVG files. [localStorage]`);
|
212 |
|
213 |
|
214 | const CACHE_ID = `${PACKAGE_NAME}:${options.cache.version}`;
|
215 |
|
216 |
|
217 | if(options._storage && options.cache.removeRevisions) Object.entries(localStorage).map(item => item.shift()).filter(item => item.startsWith(`${PACKAGE_NAME}:`) && !item.endsWith(`:${options.cache.version}`)).forEach(item => localStorage.removeItem(item));
|
218 |
|
219 |
|
220 | const cache = options._storage && options.cache.persistent ? new Map(JSON.parse(localStorage.getItem(CACHE_ID) || "[]")) : new Map;
|
221 |
|
222 |
|
223 | const symbols = new Set;
|
224 |
|
225 |
|
226 | const refs = new Map;
|
227 |
|
228 | |
229 |
|
230 |
|
231 |
|
232 | const createImageNodeIntersectionObserver = () => {
|
233 |
|
234 |
|
235 | if(!options._observer) throw new Error(`[${PACKAGE_NAME}] Feature is not supported by browser! [IntersectionObserver]`);
|
236 |
|
237 |
|
238 | if(refs.has(OBSERVER_REF_ID)) throw new Error(`[${PACKAGE_NAME}] Can not create image node intersection observer, intersection observer already exists!`);
|
239 |
|
240 |
|
241 | const observer = new IntersectionObserver((entries, observer) => {
|
242 |
|
243 |
|
244 | for(const entry of entries) {
|
245 |
|
246 |
|
247 | if(!entry.isIntersecting) continue;
|
248 |
|
249 |
|
250 | const node = entry.target;
|
251 |
|
252 |
|
253 | processImageNode(node);
|
254 |
|
255 |
|
256 | observer.unobserve(node);
|
257 |
|
258 | }
|
259 |
|
260 | }, options.intersectionObserverOptions);
|
261 |
|
262 |
|
263 | refs.set(OBSERVER_REF_ID, observer);
|
264 |
|
265 |
|
266 | return observer;
|
267 |
|
268 | };
|
269 |
|
270 | |
271 |
|
272 |
|
273 |
|
274 | const getImageNodeIntersectionObserver = () => {
|
275 |
|
276 |
|
277 | return refs.has(OBSERVER_REF_ID) ? refs.get(OBSERVER_REF_ID) : createImageNodeIntersectionObserver();
|
278 |
|
279 | };
|
280 |
|
281 | |
282 |
|
283 |
|
284 |
|
285 | const createSvgSymbolContainer = () => {
|
286 |
|
287 |
|
288 | if(refs.has(CONTAINER_REF_ID)) throw new Error(`[${PACKAGE_NAME}] Can not create SVG symbol container node, container node already exists!`);
|
289 |
|
290 |
|
291 | let container = createNode(`<svg xmlns="http://www.w3.org/2000/svg" id="${CONTAINER_ID}" style="display: none !important;"></svg>`);
|
292 |
|
293 |
|
294 | document.body.appendChild(container);
|
295 |
|
296 |
|
297 | refs.set(CONTAINER_REF_ID, container = document.getElementById(CONTAINER_ID));
|
298 |
|
299 |
|
300 | return container;
|
301 |
|
302 | };
|
303 |
|
304 | |
305 |
|
306 |
|
307 |
|
308 | const getSvgSymbolContainer = () => {
|
309 |
|
310 |
|
311 | return refs.has(CONTAINER_REF_ID) ? refs.get(CONTAINER_REF_ID) : createSvgSymbolContainer();
|
312 |
|
313 | };
|
314 |
|
315 | |
316 |
|
317 |
|
318 |
|
319 |
|
320 | const createNode = (string = "") => {
|
321 |
|
322 |
|
323 | if(!string) throw new Error(`[${PACKAGE_NAME}] Required argument is missing! [string]`);
|
324 |
|
325 |
|
326 | string = string.toString().trim();
|
327 |
|
328 |
|
329 | if(!string.startsWith("<") || !string.endsWith(">")) throw new TypeError(`[${PACKAGE_NAME}] Argument is not valid! [string="${string}"]`);
|
330 |
|
331 |
|
332 | string = string.replace(REGEXP_TEMPLATE_LITERALS_WHITESPACE, "");
|
333 |
|
334 |
|
335 | return document.createRange().createContextualFragment(string);
|
336 |
|
337 | };
|
338 |
|
339 | |
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 | const replaceNode = (node = null, newNode = null) => {
|
346 |
|
347 |
|
348 | if(!node) throw new Error(`[${PACKAGE_NAME}] Required argument is missing! [node]`);
|
349 |
|
350 |
|
351 | if(!newNode) throw new Error(`[${PACKAGE_NAME}] Required argument is missing! [newNode]`);
|
352 |
|
353 |
|
354 | if(!node.parentNode) throw new Error(`[${PACKAGE_NAME}] Required property is missing! [node.parentNode]`);
|
355 |
|
356 |
|
357 | node.parentNode.replaceChild(newNode, node);
|
358 |
|
359 | };
|
360 |
|
361 | |
362 |
|
363 |
|
364 |
|
365 |
|
366 | const createAttributeMapFromString = (string = "") => {
|
367 |
|
368 |
|
369 | if(!string) throw new Error(`[${PACKAGE_NAME}] Required argument is missing! [string]`);
|
370 |
|
371 |
|
372 | string = string.toString().trim();
|
373 |
|
374 |
|
375 | const attributes = new Map;
|
376 |
|
377 |
|
378 | REGEXP_ATTRIBUTES.lastIndex = 0;
|
379 |
|
380 |
|
381 | let attribute;
|
382 | while(attribute = REGEXP_ATTRIBUTES.exec(string)) {
|
383 |
|
384 |
|
385 | if(attribute.index === REGEXP_ATTRIBUTES.lastIndex) REGEXP_ATTRIBUTES.lastIndex++;
|
386 |
|
387 |
|
388 | const name = (attribute[1] || "").trim().toLowerCase();
|
389 |
|
390 |
|
391 | if(!name || name.startsWith("<") || name.endsWith(">")) continue;
|
392 |
|
393 |
|
394 | if(!REGEXP_ATTRIBUTE_NAME.test(name)) throw new TypeError(`[${PACKAGE_NAME}] Attribute name is not valid! [attribute="${name}"]`);
|
395 |
|
396 |
|
397 | const value = (attribute[2] || attribute[3] || "").trim();
|
398 |
|
399 |
|
400 | attributes.set(name, value ? value : (options.xhtml ? name : ""));
|
401 |
|
402 | }
|
403 |
|
404 |
|
405 | return attributes;
|
406 |
|
407 | };
|
408 |
|
409 | |
410 |
|
411 |
|
412 |
|
413 |
|
414 | const createAttributeMapFromNamedNodeMap = (namedNodeAttributeMap = null) => {
|
415 |
|
416 |
|
417 | if(!namedNodeAttributeMap) throw new Error(`[${PACKAGE_NAME}] Required argument is missing! [namedNodeAttributeMap]`);
|
418 |
|
419 |
|
420 | if(!(namedNodeAttributeMap instanceof NamedNodeMap)) throw new TypeError(`[${PACKAGE_NAME}] Argument is not valid! [namedNodeAttributeMap]`);
|
421 |
|
422 |
|
423 | const attributes = new Map([ ...namedNodeAttributeMap ].map(({ name, value }) => {
|
424 |
|
425 |
|
426 | name = (name || "").trim().toLowerCase();
|
427 |
|
428 |
|
429 | if(!REGEXP_ATTRIBUTE_NAME.test(name)) throw new TypeError(`[${PACKAGE_NAME}] Attribute name is not valid! [attribute="${name}"]`);
|
430 |
|
431 |
|
432 | value = (value || "").trim();
|
433 |
|
434 |
|
435 | return [ name, value ? value : (options.xhtml ? name : "") ];
|
436 |
|
437 | }));
|
438 |
|
439 |
|
440 | return attributes;
|
441 |
|
442 | };
|
443 |
|
444 | |
445 |
|
446 |
|
447 |
|
448 |
|
449 | const fetchSvgFile = (path = "") => {
|
450 |
|
451 |
|
452 | if(!options._fetch && !options._axios) throw new Error(`[${PACKAGE_NAME}] Feature is not supported by browser! [fetch || axios]`);
|
453 |
|
454 |
|
455 | if(!path) throw new Error(`[${PACKAGE_NAME}] Required argument is missing! [path]`);
|
456 |
|
457 |
|
458 | path = path.toString().trim();
|
459 |
|
460 |
|
461 | if(!REGEXP_SVG_FILENAME.test(path)) throw new TypeError(`[${PACKAGE_NAME}] Argument is not valid! [path="${path}"]`);
|
462 |
|
463 |
|
464 | return new Promise((resolve, reject) => {
|
465 |
|
466 |
|
467 | const file = { path };
|
468 |
|
469 |
|
470 | if(cache.has(file.path)) {
|
471 | file.content = cache.get(file.path);
|
472 | return resolve(file);
|
473 | }
|
474 |
|
475 |
|
476 | (options._axios ? options.axios.get : fetch)(file.path)
|
477 |
|
478 |
|
479 | .then(response => {
|
480 |
|
481 |
|
482 | if(!CORRECT_RESPONSE_STATUSES.has(response.status | 0)) throw new Error(`Wrong response status! [response.status=${response.status}]`);
|
483 |
|
484 |
|
485 | return options._axios ? response.data.toString() : response.text();
|
486 |
|
487 | })
|
488 |
|
489 |
|
490 | .then(content => {
|
491 |
|
492 |
|
493 | file.content = content.trim();
|
494 |
|
495 |
|
496 | cache.set(file.path, file.content);
|
497 |
|
498 |
|
499 | if(options._storage && options.cache.persistent) localStorage.setItem(CACHE_ID, JSON.stringify([ ...cache ]));
|
500 |
|
501 |
|
502 | return resolve(file);
|
503 |
|
504 | })
|
505 |
|
506 |
|
507 | .catch(reject);
|
508 |
|
509 | });
|
510 |
|
511 | };
|
512 |
|
513 | |
514 |
|
515 |
|
516 |
|
517 |
|
518 |
|
519 | const parseSvgFile = (file = null, node = null) => {
|
520 |
|
521 |
|
522 | if(!file) throw new Error(`[${PACKAGE_NAME}] Required argument is missing! [file]`);
|
523 |
|
524 |
|
525 | if(!node) throw new Error(`[${PACKAGE_NAME}] Required argument is missing! [node]`);
|
526 |
|
527 |
|
528 | if(!file.path) throw new Error(`[${PACKAGE_NAME}] Required property is missing! [file.path]`);
|
529 |
|
530 |
|
531 | file.path = file.path.toString().trim();
|
532 |
|
533 |
|
534 | if(!REGEXP_SVG_FILENAME.test(file.path)) throw new TypeError(`[${PACKAGE_NAME}] Argument property is not valid! [file.path="${file.path}"]`);
|
535 |
|
536 |
|
537 | if(!file.content) throw new Error(`[${PACKAGE_NAME}] Required property is missing! [file.content]`);
|
538 |
|
539 |
|
540 | file.content = file.content.toString().trim();
|
541 |
|
542 |
|
543 | if(!REGEXP_SVG_CONTENT.test(file.content)) throw new TypeError(`[${PACKAGE_NAME}] Argument property is not valid! [file.content="${file.content}"]`);
|
544 |
|
545 |
|
546 | if(!node.outerHTML) throw new Error(`[${PACKAGE_NAME}] Required property is missing! [node.outerHTML]`);
|
547 |
|
548 |
|
549 | if(node[FLAGS_ID].has("sprite")) {
|
550 |
|
551 |
|
552 | file.content = file.content.replace(REGEXP_SVG_CONTENT, (svg, attributes, symbol) => {
|
553 |
|
554 |
|
555 | const symbolAlreadyDefined = symbols.has(file.path);
|
556 |
|
557 |
|
558 | const id = `${SYMBOL_ID}-${symbolAlreadyDefined ? [ ...symbols ].indexOf(file.path) : symbols.size}`;
|
559 |
|
560 |
|
561 | if(!symbolAlreadyDefined) {
|
562 |
|
563 |
|
564 | const symbolNode = createNode(`
|
565 | <svg xmlns="http://www.w3.org/2000/svg">
|
566 | <symbol id="${id}"${attributes}>
|
567 | ${symbol}
|
568 | </symbol>
|
569 | </svg>
|
570 | `);
|
571 |
|
572 |
|
573 | getSvgSymbolContainer().appendChild(symbolNode.firstChild.firstChild);
|
574 |
|
575 |
|
576 | symbols.add(file.path);
|
577 |
|
578 | }
|
579 |
|
580 |
|
581 | return `
|
582 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"${options.attributes.clone.size && (attributes = createAttributeMapFromString(attributes)) ? ` ${[ ...options.attributes.clone ].filter(attribute => !!attribute && attributes.has(attribute)).map(attribute => `${attribute}="${attributes.get(attribute)}"`).join(" ")}` : "" }>
|
583 | <use xlink:href="#${id}" href="#${id}"></use>
|
584 | </svg>
|
585 | `;
|
586 |
|
587 | });
|
588 |
|
589 | }
|
590 |
|
591 |
|
592 | return file.content.replace(REGEXP_SVG_CONTENT, (svg, attributes, symbol) => {
|
593 |
|
594 |
|
595 | const fileAttributes = createAttributeMapFromString(attributes);
|
596 | const nodeAttributes = createAttributeMapFromNamedNodeMap(node.attributes);
|
597 |
|
598 |
|
599 | attributes = new Map([ ...fileAttributes, ...nodeAttributes ]);
|
600 |
|
601 |
|
602 | const uniqueAttributeValues = new Set([ "class" ]);
|
603 |
|
604 |
|
605 | for(const attribute of options.attributes.merge) {
|
606 |
|
607 |
|
608 | const fileValues = fileAttributes.has(attribute) ? fileAttributes.get(attribute).split(REGEXP_WHITESPACE).filter(value => !!value) : [];
|
609 | const nodeValues = nodeAttributes.has(attribute) ? nodeAttributes.get(attribute).split(REGEXP_WHITESPACE).filter(value => !!value) : [];
|
610 |
|
611 |
|
612 | if(options.xhtml && !fileValues.length && !nodeValues.length) continue;
|
613 |
|
614 |
|
615 | const values = [ ...fileValues, ...nodeValues ];
|
616 |
|
617 |
|
618 | attributes.set(attribute, (uniqueAttributeValues.has(attribute) ? [ ...new Set(values) ] : values).join(" ").trim());
|
619 |
|
620 | }
|
621 |
|
622 |
|
623 | for(const attribute of options.attributes.add) {
|
624 |
|
625 |
|
626 | let values = attribute.value.split(REGEXP_WHITESPACE).filter(value => !!value);
|
627 |
|
628 |
|
629 | if(attributes.has(attribute.name)) {
|
630 |
|
631 |
|
632 | if(!options.attributes.merge.has(attribute.name)) throw new Error(`[${PACKAGE_NAME}] Can not add attribute, attribute already exists. [${attribute.name}]`);
|
633 |
|
634 |
|
635 | const oldValues = attributes.get(attribute.name).split(REGEXP_WHITESPACE).filter(value => !!value);
|
636 |
|
637 |
|
638 | if(options.xhtml && !values.length && !oldValues.length) continue;
|
639 |
|
640 |
|
641 | values = [ ...oldValues, ...values ];
|
642 |
|
643 | }
|
644 |
|
645 |
|
646 | attributes.set(attribute.name, (uniqueAttributeValues.has(attribute.name) ? [ ...new Set(values) ] : values).join(" ").trim());
|
647 |
|
648 | }
|
649 |
|
650 |
|
651 | for(const attribute of options.attributes.data) {
|
652 |
|
653 |
|
654 | if(!attributes.has(attribute)) continue;
|
655 |
|
656 |
|
657 | let values = attributes.get(attribute).split(REGEXP_WHITESPACE).filter(value => !!value);
|
658 |
|
659 |
|
660 | const dataAttribute = `data-${attribute}`;
|
661 |
|
662 |
|
663 | if(attributes.has(dataAttribute)) {
|
664 |
|
665 |
|
666 | if(!options.attributes.merge.has(dataAttribute)) throw new Error(`[${PACKAGE_NAME}] Can not transform attribute to data-attribute, data-attribute already exists. [${attribute}]`);
|
667 |
|
668 |
|
669 | const oldValues = attributes.get(dataAttribute).split(REGEXP_WHITESPACE).filter(value => !!value);
|
670 |
|
671 |
|
672 | if(options.xhtml && !values.length && !oldValues.length) continue;
|
673 |
|
674 |
|
675 | values = [ ...oldValues, ...values ];
|
676 |
|
677 | }
|
678 |
|
679 |
|
680 | attributes.set(dataAttribute, (uniqueAttributeValues.has(attribute) ? [ ...new Set(values) ] : values).join(" ").trim());
|
681 |
|
682 |
|
683 | if(!options.attributes.remove.has(attribute)) options.attributes.remove.add(attribute);
|
684 |
|
685 | }
|
686 |
|
687 |
|
688 | for(const attribute of options.attributes.remove) {
|
689 |
|
690 |
|
691 | if(!attributes.has(attribute)) continue;
|
692 |
|
693 |
|
694 | attributes.delete(attribute);
|
695 |
|
696 | }
|
697 |
|
698 |
|
699 | return `
|
700 | <svg${attributes.size ? ` ${[ ...attributes.keys() ].filter(attribute => !!attribute).map(attribute => `${attribute}="${attributes.get(attribute)}"`).join(" ")}` : ""}>
|
701 | ${symbol}
|
702 | </svg>
|
703 | `;
|
704 |
|
705 | });
|
706 |
|
707 | };
|
708 |
|
709 | |
710 |
|
711 |
|
712 |
|
713 |
|
714 | const processImageNode = (node = null) => {
|
715 |
|
716 |
|
717 | if(!node) throw new Error(`[${PACKAGE_NAME}] Required argument is missing! [node]`);
|
718 |
|
719 |
|
720 | if(!node.dataset.src && !node.src) throw new Error(`[${PACKAGE_NAME}] Required property is missing! [node.data-src || node.src]`);
|
721 |
|
722 |
|
723 | if(node.dataset.src) node.dataset.src = node.dataset.src.toString().trim();
|
724 | if(node.src) node.src = node.src.toString().trim();
|
725 |
|
726 |
|
727 | fetchSvgFile(node.dataset.src || node.src)
|
728 |
|
729 |
|
730 | .then(file => {
|
731 |
|
732 |
|
733 | const svgString = parseSvgFile(file, node);
|
734 |
|
735 |
|
736 | const svgNode = createNode(svgString);
|
737 |
|
738 |
|
739 | replaceNode(node, svgNode);
|
740 |
|
741 | })
|
742 |
|
743 |
|
744 | .catch(error => console.error(`[${PACKAGE_NAME}] ${error.toString()}`));
|
745 |
|
746 | };
|
747 |
|
748 | |
749 |
|
750 |
|
751 |
|
752 |
|
753 |
|
754 |
|
755 | const beforeMount = (node = null, binding = null, vnode = null) => {
|
756 |
|
757 |
|
758 | if(!node) throw new Error(`[${PACKAGE_NAME}] Required argument is missing! [node]`);
|
759 |
|
760 |
|
761 | if(node.tagName !== "IMG") throw new Error(`[${PACKAGE_NAME}] Required argument is not valid! [node]`);
|
762 |
|
763 |
|
764 | if(!vnode) throw new Error(`[${PACKAGE_NAME}] Required argument is missing! [vnode]`);
|
765 |
|
766 |
|
767 | if(!node[FLAGS_ID]) node[FLAGS_ID] = new Set;
|
768 |
|
769 |
|
770 | if(node[FLAGS_ID].has("processed")) return;
|
771 |
|
772 |
|
773 | node[FLAGS_ID].add("processed");
|
774 |
|
775 |
|
776 | const directives = isVue3 ? vnode.dirs : vnode.data.directives;
|
777 |
|
778 |
|
779 | if(directives.length > 1) throw new Error(`[${PACKAGE_NAME}] Node has more than 1 directive! [${isVue3 ? "vnode.dirs" : "vnode.data.directives"}]`);
|
780 |
|
781 |
|
782 | if(!!directives[0].modifiers[options.directive.spriteModifierName]) node[FLAGS_ID].add("sprite");
|
783 |
|
784 |
|
785 | if(!options._observer && node.dataset.src) {
|
786 |
|
787 |
|
788 | node.src = node.dataset.src;
|
789 | delete node.dataset.src;
|
790 |
|
791 | }
|
792 |
|
793 |
|
794 | if(node.dataset.src) getImageNodeIntersectionObserver().observe(node);
|
795 | else processImageNode(node);
|
796 |
|
797 | };
|
798 |
|
799 |
|
800 | VueOrApp.directive(options.directive.name, isVue3 ? { beforeMount } : { bind: beforeMount });
|
801 |
|
802 | };
|
803 |
|
804 |
|
805 | export default { install };
|