compare-images-slider 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Nikola Stamatović
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.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # ↔️ Compare Images Slider [![npm version](https://img.shields.io/npm/v/compare-images-slider)](https://www.npmjs.com/package/compare-images-slider) [![license mit](https://img.shields.io/badge/license-MIT-green)](https://github.com/stamat/compare-images-slider/blob/main/LICENSE)
2
+
3
+ A simple slider for comparing two images visually.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install compare-images-slider
9
+ ```
10
+
11
+ or use the CDN:
12
+
13
+ ```html
14
+ <script src="https://unpkg.com/compare-images-slider/dist/compare-images-slider.min.js"></script>
15
+ <link rel="stylesheet" href="https://unpkg.com/compare-images-slider/dist/compare-images-slider.min.css">
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```html
21
+ <div class="js-compare-images-slider compare-images-slider">
22
+ <img src="img.jpg" alt="">
23
+ <div class="frame">
24
+ <img src="img-alt.jpg" alt="">
25
+ </div>
26
+ <span class="handle"></span>
27
+ </div>
28
+ ```
29
+
30
+ **⚠️ Note:** Don't be lazy and please set the intrinsic dimensions of the images. This eliminates layout shifts and will ensure the slider works as expected.
31
+
32
+ ```javascript
33
+ import CompareImagesSlider from 'compare-images-slider';
34
+
35
+ const slider = document.querySelector('.js-compare-images-slider');
36
+ const compareImagesSlider = new CompareImagesSlider(slider);
37
+ ```
38
+
39
+ If you are loading the script asynchronously, you can listen for the `CompareImagesSliderLoaded` event to initialize the slider:
40
+
41
+ ```javascript
42
+ document.addEventListener('CompareImagesSliderLoaded', function() {
43
+ const slider = document.querySelector('.js-compare-images-slider');
44
+ const compareImagesSlider = new CompareImagesSlider(slider);
45
+ });
46
+ ```
47
+
48
+ ```scss
49
+ @import 'node_modules/compare-images-slider/src/styles/index.scss';
50
+ ```
51
+
52
+ ## Options
53
+
54
+ Coming soon...
55
+
56
+ ## TODO:
57
+
58
+ - [ ] Add options
59
+ - [ ] Scroll block on drag
60
+ - [ ] Vertical option
61
+ - [ ] Add factory class, migrate the general factory class to the book of spells prior to that
62
+ - [ ] Refactor onDrag and move it to the book of spells
63
+ - [ ] Add initialized state, don't initialize twice
64
+
65
+ ---
66
+
67
+ Made with ❤️ by @stamat.
Binary file
Binary file
@@ -0,0 +1,57 @@
1
+ /* compare-images-slider v1.0.0 | https://stamat.github.io/compare-images-slider/ | MIT License */
2
+ @charset "UTF-8";
3
+ :root {
4
+ --compare-images-slider-initial-position: 50%;
5
+ }
6
+
7
+ .compare-images-slider {
8
+ display: block;
9
+ position: relative;
10
+ user-select: none;
11
+ -webkit-user-select: none;
12
+ -moz-user-select: none;
13
+ }
14
+ .compare-images-slider img {
15
+ pointer-events: none;
16
+ }
17
+ .compare-images-slider > img {
18
+ max-width: 100%;
19
+ height: auto;
20
+ }
21
+ .compare-images-slider .frame {
22
+ position: absolute;
23
+ top: 0;
24
+ left: 0;
25
+ height: 100%;
26
+ width: var(--compare-images-slider-initial-position);
27
+ overflow: hidden;
28
+ z-index: 2;
29
+ }
30
+ .compare-images-slider .frame > img {
31
+ height: auto;
32
+ }
33
+ .compare-images-slider .handle {
34
+ position: absolute;
35
+ left: var(--compare-images-slider-initial-position);
36
+ top: 0;
37
+ bottom: 0;
38
+ width: 2px;
39
+ margin-left: -1px;
40
+ height: 100%;
41
+ cursor: ew-resize;
42
+ z-index: 3;
43
+ }
44
+ .compare-images-slider .handle:after {
45
+ position: absolute;
46
+ top: 50%;
47
+ width: 42px;
48
+ height: 42px;
49
+ transform: translate3d(-50%, -50%, 0);
50
+ content: "↔";
51
+ line-height: 38px;
52
+ font-size: 28px;
53
+ background: #fff;
54
+ text-align: center;
55
+ border-radius: 21px;
56
+ }
57
+ /*# sourceMappingURL=compare-images-slider.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sourceRoot":"","sources":["file:///Users/stamat/Sites/localhost/compare-images-slider/src/styles/index.scss"],"names":[],"mappings":";;AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA","sourcesContent":[":root {\n --compare-images-slider-initial-position: 50%;\n}\n\n.compare-images-slider {\n display: block;\n position: relative;\n user-select: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n\n img {\n pointer-events: none;\n }\n\n & > img {\n max-width: 100%;\n height: auto;\n }\n\n .frame {\n position: absolute;\n top:0;\n left: 0;\n height: 100%;\n width: var(--compare-images-slider-initial-position);\n overflow: hidden;\n z-index: 2;\n\n & > img {\n height: auto;\n }\n }\n\n .handle {\n position:absolute;\n left: var(--compare-images-slider-initial-position);\n top:0;\n bottom:0;\n width: 2px;\n margin-left: -1px;\n height: 100%;\n cursor: ew-resize;\n z-index: 3;\n }\n\n .handle:after {\n position: absolute;\n top: 50%;\n width: 42px;\n height: 42px;\n transform: translate3d(-50%, -50%, 0);\n\n content:'↔';\n line-height: 38px;\n font-size: 28px;\n background: #fff;\n text-align: center;\n border-radius: 21px;\n }\n}\n"]}
@@ -0,0 +1,132 @@
1
+ /* compare-images-slider v1.0.0 | https://stamat.github.io/compare-images-slider/ | MIT License */
2
+ (() => {
3
+ // node_modules/book-of-spells/src/helpers.mjs
4
+ function percentage(num, total) {
5
+ if (!num || !total || Number.isNaN(num) || Number.isNaN(total))
6
+ return 0;
7
+ return num / total * 100;
8
+ }
9
+
10
+ // src/scripts/compare-images-slider.js
11
+ function onDrag(element, callback) {
12
+ let startX = 0;
13
+ let startY = 0;
14
+ let endX = 0;
15
+ let endY = 0;
16
+ let dragging = false;
17
+ let rect = element.getBoundingClientRect();
18
+ const handleStart = function(e) {
19
+ const carrier = e.type === "touchstart" ? e.touches[0] : e;
20
+ startX = carrier.clientX;
21
+ startY = carrier.clientY;
22
+ dragging = true;
23
+ rect = element.getBoundingClientRect();
24
+ const xPercentage = percentage(startX - rect.left, rect.width);
25
+ const yPercentage = percentage(startY - rect.top, rect.height);
26
+ const event = new CustomEvent("dragstart", { detail: { target: element, startX, startY, rect, xPercentage, yPercentage } });
27
+ element.dispatchEvent(event);
28
+ };
29
+ const handleMove = function(e) {
30
+ if (!dragging)
31
+ return;
32
+ const carrier = e.type === "touchmove" ? e.touches[0] : e;
33
+ endX = carrier.clientX;
34
+ endY = carrier.clientY;
35
+ handleDragGesture();
36
+ };
37
+ const handleEnd = function() {
38
+ dragging = false;
39
+ const event = new CustomEvent("dragend", { detail: { target: element, startX, startY, rect, endX, endY } });
40
+ element.dispatchEvent(event);
41
+ };
42
+ const handleDragGesture = function() {
43
+ const deltaX = endX - startX;
44
+ const deltaY = endY - startY;
45
+ const left = deltaX < 0;
46
+ const up = deltaY < 0;
47
+ const xPercentage = percentage(endX - rect.left, rect.width);
48
+ const yPercentage = percentage(endY - rect.top, rect.height);
49
+ const detail = {
50
+ target: element,
51
+ deltaX,
52
+ deltaY,
53
+ startX,
54
+ startY,
55
+ endX,
56
+ endY,
57
+ horizontalDirection: left ? "left" : "right",
58
+ verticalDirection: up ? "up" : "down",
59
+ xPercentage,
60
+ yPercentage
61
+ };
62
+ if (xPercentage < 0) {
63
+ detail.xPercentage = 0;
64
+ }
65
+ if (xPercentage > 100) {
66
+ detail.xPercentage = 100;
67
+ }
68
+ if (yPercentage < 0) {
69
+ detail.yPercentage = 0;
70
+ }
71
+ if (yPercentage > 100) {
72
+ detail.yPercentage = 100;
73
+ }
74
+ if (callback) {
75
+ callback(detail);
76
+ }
77
+ const event = new CustomEvent("drag", { detail });
78
+ element.dispatchEvent(event);
79
+ };
80
+ element.addEventListener("mousedown", handleStart);
81
+ element.addEventListener("mousemove", handleMove);
82
+ element.addEventListener("mouseup", handleEnd);
83
+ element.addEventListener("touchstart", handleStart);
84
+ element.addEventListener("touchmove", handleMove);
85
+ element.addEventListener("touchend", handleEnd);
86
+ return {
87
+ destroy: function() {
88
+ element.removeEventListener("mousedown", handleStart);
89
+ element.removeEventListener("mousemove", handleMove);
90
+ element.removeEventListener("mouseup", handleEnd);
91
+ element.removeEventListener("touchstart", handleStart);
92
+ element.removeEventListener("touchmove", handleMove);
93
+ element.removeEventListener("touchend", handleEnd);
94
+ }
95
+ };
96
+ }
97
+ var CompareImagesSlider = class {
98
+ constructor(element, options) {
99
+ this.element = element;
100
+ this.frame = this.element.querySelector(".frame");
101
+ this.second = this.frame.querySelector(":scope > img");
102
+ this.handle = this.element.querySelector(".handle");
103
+ window.addEventListener("resize", () => {
104
+ requestAnimationFrame(this.setupSecondImage.bind(this));
105
+ });
106
+ this.setupSecondImage();
107
+ this.drag = onDrag(this.element);
108
+ this.element.addEventListener("dragstart", this.updateVisibleHandler.bind(this));
109
+ this.element.addEventListener("drag", this.updateVisibleHandler.bind(this));
110
+ }
111
+ setupSecondImage() {
112
+ const width = this.element.offsetWidth + "px";
113
+ this.second.style.width = width;
114
+ }
115
+ updateVisibleHandler(e) {
116
+ this.frame.style.width = e.detail.xPercentage + "%";
117
+ this.handle.style.left = e.detail.xPercentage + "%";
118
+ }
119
+ destroy() {
120
+ this.drag.destroy();
121
+ this.element.removeEventListener("dragstart", this.updateVisibleHandler.bind(this));
122
+ this.element.removeEventListener("drag", this.updateVisibleHandler.bind(this));
123
+ }
124
+ };
125
+
126
+ // src/scripts/iife.js
127
+ if (!window.CompareImagesSlider) {
128
+ window.CompareImagesSlider = CompareImagesSlider;
129
+ document.dispatchEvent(new CustomEvent("CompareImagesSliderLoaded"));
130
+ }
131
+ })();
132
+ //# sourceMappingURL=compare-images-slider.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../node_modules/book-of-spells/src/helpers.mjs", "../src/scripts/compare-images-slider.js", "../src/scripts/iife.js"],
4
+ "sourcesContent": ["/**\n * Shallow merges two objects together. Used to pass simple options to functions.\n * \n * @param {object} target The target object to merge into\n * @param {object} source The source object to merge from\n * @returns object The merged object, in this case the target object with the source object's properties merged into it\n * @example\n * const target = { foo: 'bar' }\n * const source = { bar: 'baz' }\n * shallowMerge(target, source) // { foo: 'bar', bar: 'baz' }\n */\nexport function shallowMerge(target, source) {\n for (const key in source) {\n target[key] = source[key]\n }\n\n return target\n}\n\n/**\n * Deep merge function that's mindful of arrays and objects\n * \n * @param {object} target The target object to merge into\n * @param {object} source The source object to merge from\n * @returns object The merged object, in this case the target object with the source object's properties merged into it\n * @example\n * const target = { foo: 'bar' }\n * const source = { bar: 'baz' }\n * deepMerge(target, source) // { foo: 'bar', bar: 'baz' }\n */\nexport function deepMerge(target, source) {\n if (isObject(source) && isObject(target)) {\n for (const key in source) {\n target[key] = deepMerge(target[key], source[key])\n }\n } else if (isArray(source) && isArray(target)) {\n for (let i = 0; i < source.length; i++) {\n target[i] = deepMerge(target[i], source[i])\n }\n } else {\n target = source\n }\n return target\n}\n\n/**\n * Deep clone function that's mindful of nested arrays and objects\n * \n * @param {object} o The object to clone\n * @returns object The cloned object\n * @example\n * const obj = { foo: 'bar' }\n * const clone = clone(obj)\n * clone.foo = 'baz'\n * console.log(obj.foo) // 'bar'\n * console.log(clone.foo) // 'baz'\n * console.log(obj === clone) // false\n * console.log(JSON.stringify(obj) === JSON.stringify(clone)) // true\n * @todo Check if faster than assign. This function is pretty old...\n */ \nexport function clone(o) {\n let res = null\n if (isArray(o)) {\n res = []\n for (const i in o) {\n res[i] = clone(o[i])\n }\n } else if (isObject(o)) {\n res = {}\n for (const i in o) {\n res[i] = clone(o[i])\n }\n } else {\n res = o\n }\n return res\n}\n\n/**\n * Check if an object is empty\n * \n * @param {object} o The object to check\n * @returns boolean True if the object is empty, false otherwise\n * @example\n * isEmptyObject({}) // => true\n * isEmptyObject({ foo: 'bar' }) // => false\n */\nexport function isEmptyObject(o) {\n for (const i in o) {\n return false\n }\n return true\n}\n\n/**\n * Check if an array is empty, substitute for Array.length === 0\n * \n * @param {array} o The array to check\n * @returns boolean True if the array is empty, false otherwise\n * @example\n * isEmptyArray([]) // => true\n * isEmptyArray([1, 2, 3]) // => false\n */\nexport function isEmptyArray(o) {\n return o.length === 0\n}\n\n/**\n * Check if a variable is empty\n * \n * @param {any} o The variable to check\n * @returns boolean True if the variable is empty, false otherwise\n * @example\n * isEmpty({}) // => true\n * isEmpty([]) // => true\n * isEmpty('') // => true\n * isEmpty(null) // => false\n * isEmpty(undefined) // => false\n * isEmpty(0) // => false\n */\nexport function isEmpty(o) {\n if (isObject(o)) {\n return isEmptyObject(o)\n } else if (isArray(o)) {\n return isEmptyArray(o)\n } else if (isString(o)) {\n return o === ''\n }\n return false\n}\n\n/**\n * Try to convert a string to a boolean\n * \n * @param {string} str The string to convert\n * @returns boolean The converted boolean or undefined if conversion failed\n * @example\n * stringToBoolean('true') // => true\n * stringToBoolean('false') // => false\n * stringToBoolean('foo') // => null\n */\nexport function stringToBoolean(str) {\n if (/^\\s*(true|false)\\s*$/i.test(str)) return str === 'true'\n}\n\n/**\n * Try to convert a string to a number\n * \n * @param {string} str The string to convert\n * @returns number The converted number or undefined if conversion failed\n * @example\n * stringToNumber('1') // => 1\n * stringToNumber('1.5') // => 1.5\n * stringToNumber('foo') // => null\n * stringToNumber('1foo') // => null\n */\nexport function stringToNumber(str) {\n if (/^\\s*\\d+\\s*$/.test(str)) return parseInt(str)\n if (/^\\s*[\\d.]+\\s*$/.test(str)) return parseFloat(str)\n}\n\n/**\n * Try to convert a string to an array\n * \n * @param {string} str The string to convert\n * @returns array The converted array or undefined if conversion failed\n * @example\n * stringToArray('[1, 2, 3]') // => [1, 2, 3]\n * stringToArray('foo') // => null\n * stringToArray('1') // => null\n * stringToArray('{\"foo\": \"bar\"}') // => null\n */\nexport function stringToArray(str) {\n if (!/^\\s*\\[.*\\]\\s*$/.test(str)) return\n try {\n return JSON.parse(str)\n } catch (e) {}\n}\n\n/**\n * Try to convert a string to an object\n * \n * @param {string} str The string to convert\n * @returns object The converted object or undefined if conversion failed\n * @example\n * stringToObject('{ \"foo\": \"bar\" }') // => { foo: 'bar' }\n * stringToObject('foo') // => null\n * stringToObject('1') // => null\n * stringToObject('[1, 2, 3]') // => null\n */\nexport function stringToObject(str) {\n if (!/^\\s*\\{.*\\}\\s*$/.test(str)) return\n try {\n return JSON.parse(str)\n } catch (e) {}\n}\n\n/**\n * Try to convert a string to a regex\n * \n * @param {string} str The string to convert\n * @returns regex The converted regex or undefined if conversion failed\n * @example\n * stringToRegex('/foo/i') // => /foo/i\n * stringToRegex('foo') // => null\n * stringToRegex('1') // => null\n */\nexport function stringToRegex(str) {\n if (!/^\\s*\\/.*\\/g?i?\\s*$/.test(str)) return\n try {\n return new RegExp(str)\n } catch (e) {}\n}\n\n/**\n * Try to convert a string to a primitive\n * \n * @param {string} str The string to convert\n * @returns {null|boolean|int|float|string} The converted primitive or input string if conversion failed\n * @example\n * stringToPrimitive('null') // => null\n * stringToPrimitive('true') // => true\n * stringToPrimitive('false') // => false\n * stringToPrimitive('1') // => 1\n * stringToPrimitive('1.5') // => 1.5\n * stringToPrimitive('foo') // => 'foo'\n * stringToPrimitive('1foo') // => '1foo'\n */\nexport function stringToPrimitive(str) {\n if (/^\\s*null\\s*$/.test(str)) return null\n const bool = stringToBoolean(str)\n if (bool !== undefined) return bool\n return stringToNumber(str) || str\n}\n\n/**\n * Try to convert a string to a data type\n * \n * @param {string} str The string to convert\n * @returns any The converted data type or input string if conversion failed\n * @example\n * stringToData('null') // => null\n * stringToData('true') // => true\n * stringToData('false') // => false\n * stringToData('1') // => 1\n * stringToData('1.5') // => 1.5\n * stringToData('foo') // => 'foo'\n * stringToData('1foo') // => '1foo'\n * stringToData('[1, 2, 3]') // => [1, 2, 3]\n * stringToData('{ \"foo\": \"bar\" }') // => { foo: 'bar' }\n * stringToData('/foo/i') // => /foo/i\n */\nexport function stringToType(str) {\n if (/^\\s*null\\s*$/.test(str)) return null\n const bool = stringToBoolean(str)\n if (bool !== undefined) return bool\n return stringToNumber(str) || stringToArray(str) || stringToObject(str) || stringToRegex(str) || str\n}\n\n/**\n * If provided variable is an object\n * \n * @param {any} o \n * @returns boolean\n * @example\n * isObject({}) // => true\n * isObject([]) // => false\n * isObject(null) // => false\n */\nexport function isObject(o) {\n return typeof o === 'object' && !Array.isArray(o) && o !== null\n}\n\n/**\n * If provided variable is an array. Just a wrapper for Array.isArray\n * \n * @param {any} o\n * @returns boolean\n * @example\n * isArray([]) // => true\n * isArray({}) // => false\n */\nexport function isArray(o) {\n return Array.isArray(o)\n}\n\n/**\n * If provided variable is a string. Just a wrapper for typeof === 'string'\n * \n * @param {any} o\n * @returns boolean\n * @example\n * isString('foo') // => true\n * isString({}) // => false\n */\nexport function isString(o) {\n return typeof o === 'string'\n}\n\n/**\n * If provided variable is a function, substitute for typeof === 'function'\n * \n * @param {any} o\n * @returns boolean\n * @example\n * isFunction(function() {}) // => true\n * isFunction({}) // => false\n */\nexport function isFunction(o) {\n return typeof o === 'function'\n}\n\n/**\n * If object property is a function\n * \n * @param {object} obj\n * @param {string} propertyName\n * @returns boolean\n * @example\n * const obj = { foo: 'bar', baz: function() {} }\n * propertyIsFunction(obj, 'foo') // => false\n * propertyIsFunction(obj, 'baz') // => true\n */\nexport function propertyIsFunction(obj, propertyName) {\n return obj.hasOwnProperty(propertyName) && isFunction(obj[propertyName])\n}\n\n/**\n * If object property is a string\n * \n * @param {object} obj\n * @param {string} propertyName\n * @returns boolean\n * @example\n * const obj = { foo: 'bar', baz: function() {} }\n * propertyIsString(obj, 'foo') // => true\n * propertyIsString(obj, 'baz') // => false\n */\nexport function propertyIsString(obj, propertyName) {\n return obj.hasOwnProperty(propertyName) && isString(obj[propertyName])\n}\n\n/**\n * Transforms a dash separated string to camelCase\n *\n * @param {string} str\n * @returns boolean\n * @example\n * transformDashToCamelCase('foo-bar') // => 'fooBar'\n * transformDashToCamelCase('foo-bar-baz') // => 'fooBarBaz'\n * transformDashToCamelCase('foo') // => 'foo'\n * transformDashToCamelCase('fooBarBaz-qux') // => 'fooBarBazQux'\n */\nexport function transformDashToCamelCase(str) {\n return str.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase() });\n}\n\n/**\n * Transforms a camelCase string to dash separated string\n * \n * @param {string} str\n * @returns boolean\n * @example\n * transformCamelCaseToDash('fooBar') // => 'foo-bar'\n * transformCamelCaseToDash('fooBarBaz') // => 'foo-bar-baz'\n * transformCamelCaseToDash('foo') // => 'foo'\n * transformDashToCamelCase('fooBarBaz-qux') // => 'foo-bar-baz-qux'\n */\nexport function transformCamelCaseToDash(str) {\n return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()\n}\n\n/**\n * Maps an array of objects by a property name\n * \n * @param {array} arr\n * @param {string} propertyName\n * @returns object\n * @example\n * const arr = [{ foo: 'bar' }, { foo: 'baz' }]\n * mapByProperty(arr, 'foo') // => { bar: { foo: 'bar' }, baz: { foo: 'baz' } }\n */\nexport function mapByProperty(arr, propertyName) {\n const res = {}\n for (let i = 0; i < arr.length; i++) {\n res[arr[i][propertyName]] = arr[i]\n }\n return res\n}\n\n/**\n * Maps an array of objects by a property name to another property name\n * \n * @param {array} arr\n * @param {string} keyPropertyName\n * @param {string} valuePropertyName\n * @returns object\n * @example\n * const arr = [{ foo: 'bar', baz: 'qux' }, { foo: 'quux', baz: 'corge' }]\n * mapPropertyToProperty(arr, 'foo', 'baz') // => { bar: 'qux', quux: 'corge' }\n */\nexport function mapPropertyToProperty(arr, keyPropertyName, valuePropertyName) {\n const res = {}\n for (let i = 0; i < arr.length; i++) {\n res[arr[i][keyPropertyName]] = arr[i][valuePropertyName]\n }\n return res\n}\n\n/**\n * Remove accents from a string\n * \n * @param {string} inputString\n * @returns string\n * @example\n * removeAccents('\u00E1\u00E9\u00ED\u00F3\u00FA') // => 'aeiou'\n * removeAccents('\u00C1\u00C9\u00CD\u00D3\u00DA') // => 'AEIOU'\n * removeAccents('se\u00F1or') // => 'senor'\n * removeAccents('\u0152') // => 'OE'\n */\nexport function removeAccents(inputString) {\n return inputString.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').replace(/\\\u0153/g, \"oe\").replace(/\\\u00E6/g, \"ae\").normalize('NFC')\n}\n\n/**\n * Strip HTML tags from a string\n * \n * @param {string} inputString\n * @returns string\n * @example\n * stripHTMLTags('<span>foo</span>') // => 'foo'\n * stripHTMLTags('<span>foo</span> <span>bar</span>') // => 'foo bar'\n */\nexport function stripHTMLTags(inputString) {\n return inputString.replace(/<[^>]*>/g, '')\n}\n\n/**\n * Slugify a string, e.g. 'Foo Bar' => 'foo-bar'. Similar to WordPress' sanitize_title(). Will remove accents and HTML tags.\n * \n * @param {string} str \n * @returns string\n * @example\n * slugify('Foo Bar') // => 'foo-bar'\n * slugify('Foo Bar <span>baz</span>') // => 'foo-bar-baz'\n */\nexport function slugify(str) {\n str = str.trim().toLowerCase()\n str = removeAccents(str)\n str = stripHTMLTags(str)\n return str.replace(/\\s+|\\.+|\\/+|\\\\+|\u2014+|\u2013+/g, '-').replace(/[^\\w0-9\\-]+/g, '').replace(/-{2,}/g, '-').replace(/^-|-$/g, '')\n}\n\n/**\n * Check if object has multiple properties\n * \n * @param {object} obj\n * @param {string|array} properties\n * @returns boolean\n * @example\n * const obj = { foo: 'bar', baz: 'qux' }\n * hasOwnProperties(obj, ['foo', 'baz']) // => true\n * hasOwnProperties(obj, ['foo', 'baz', 'qux']) // => false\n */\nexport function hasOwnProperties(obj, properties) {\n if(!isArray(properties)) properties = [properties]\n for (let i = 0; i < properties.length; i++) {\n if (!obj.hasOwnProperty(properties[i])) return false\n }\n return true\n}\n\n/**\n * Finds the closest number to the set goal in an array to a given number\n * \n * @param {number} goal Number to search for\n * @param {array} arr Array of numbers to search in\n * @returns number\n * @example\n * closestNumber(10, [1, 2, 3, 4, 5, 6, 7, 8, 9]) // => 9\n * closestNumber(10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]) // => 9\n * closestNumber(10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 9.5]) // => 9.5\n * closestNumber(10, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) // => 10\n */\nexport function closestNumber(goal, arr) {\n return arr.reduce(function(prev, curr) {\n return Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev\n })\n}\n\n/**\n * Truncate a string to a given number of words\n * \n * @param {string} str String to truncate\n * @param {number} numWords Number of words to truncate to\n * @param {string} ellipsis Ellipsis to append to the end of the string\n * @returns string\n * @example\n * truncateString('foo bar baz', 2) // => 'foo bar\u2026'\n * truncateString('foo bar baz', 2, '...') // => 'foo bar...'\n * truncateString('foo bar. baz', 2, '...') // => 'foo bar. ...'\n */\nexport function truncateString(str, numWords, ellipsis = '\u2026') {\n const words = str.trim().split(' ')\n if (words.length <= numWords) return str\n if (numWords <= 0) return ''\n if (/[.?!]$/.test(words[numWords - 1]) && ellipsis.trim() !== '') ellipsis = ` ${ellipsis}`\n return words.slice(0, numWords).join(' ') + ellipsis\n}\n\n/**\n * Generates a random integer between two values, inclusive of both\n * \n * @param {number} min Minimum value\n * @param {number} max Maximum value\n * @param {boolean} safe Defaults to false, if true will use a cryptographically secure random number generator\n * @returns number\n * @example\n * randomIntInclusive(1, 10) // => 1\n * randomIntInclusive(1, 10) // => 10\n * randomIntInclusive(1, 10) // => 5\n */\nexport function randomIntInclusive(min, max, safe = false) {\n min = Number(min)\n max = Number(max)\n if (isNaN(min) || isNaN(max)) throw new TypeError('Both min and max must be numbers')\n if (min > max) [min, max] = [max, min]\n if (min === max) return min\n min = Math.round(min)\n max = Math.round(max)\n const rand = safe ? random() : Math.random()\n return Math.floor(rand * (max - min + 1)) + min\n}\n\n/**\n * Gets fixed number of digits after the decimal point\n * \n * @param {number} number Number to fix\n * @param {number} digits Number of digits to fix to\n * @returns number\n * @example\n * fixed(1.234, 2) // => 1.23\n * fixed(1.235, 2) // => 1.24\n * fixed(1.234) // => 1\n * fixed(1.234, 0) // => 1\n * fixed(1.234, 5) // => 1.234\n * @note Gotta ask myself why I wrote this function in the first place... \uD83E\uDD14 It's just not useful in a lot of cases lol...\n */\nexport function fixed(number, digits) {\n if (!digits) return parseInt(number)\n return parseFloat(number.toFixed(digits))\n}\n\n/**\n * Calculates the percentage of a number in relation to another number\n * \n * @param {number} num Number to calculate percentage of\n * @param {number} total Total number\n * @returns number\n * @example\n * percentage(1, 10) // => 10\n * percentage(5, 10) // => 50\n * percentage(10, 10) // => 100\n * percentage(0, 10) // => 0\n * percentage(10, 2) // => 500\n */\nexport function percentage(num, total) {\n if (!num || !total || Number.isNaN(num) || Number.isNaN(total)) return 0\n return num / total * 100\n}\n\nexport function pickProperties(obj, props) {\n const res = {}\n if (!props) return res\n if (!isArray(props)) props = [props]\n for (let i = 0; i < props.length; i++) {\n if (obj.hasOwnProperty(props[i])) res[props[i]] = obj[props[i]]\n }\n return res\n}\n\nexport function rejectProperties(obj, props, clone = true) {\n if (clone) obj = { ...obj }\n if (!props) return obj\n if (!isArray(props)) props = [props]\n for (let i = 0; i < props.length; i++) {\n if (obj.hasOwnProperty(props[i])) delete obj[props[i]]\n }\n return obj\n}\n\nexport function pickArrayElements(arr, indexes) {\n if (!isArray(arr)) return\n if (!isArray(indexes)) indexes = [indexes]\n const res = []\n for (let i = 0; i < indexes.length; i++) {\n if (arr.hasOwnProperty(indexes[i])) res.push(arr[indexes[i]])\n }\n return res\n}\n\nexport function rejectArrayElements(arr, indexes, clone = true) {\n if (clone) arr = [...arr]\n if (!isArray(arr)) return\n if (!isArray(indexes)) indexes = [indexes]\n for (let i = indexes.length - 1; i >= 0; i--) {\n if (arr.hasOwnProperty(indexes[i])) arr.splice(indexes[i], 1)\n }\n return arr\n}\n\n/**\n * Pick properties from an object or elements from an array\n * \n * @param {array} obj Object or array to pick properties or elements from\n * @param {array | string | number} props Properties to remove, can be an array of strings or a single string or number\n * @returns object | array | undefined\n * @example\n * \n * pick({ foo: 'bar', bar: 'baz', baz: 'qux' }) // => {}\n * pick({}, []) // => {}\n * pick(null, 'foo') // => undefined\n * pick({ foo: 'bar', bar: 'baz', baz: 'qux' }, undefined) // => {}\n * pick({ foo: 'bar', bar: 'baz', baz: 'qux' }, 'foo') // => { foo: 'bar'}\n * pick({ foo: 'bar', bar: 'baz', baz: 'qux' }, ['foo', 'baz']) // => { foo: 'bar', baz: 'qux' }\n * \n * pick(['foo', 'bar', 'baz'], []) // => []\n * pick([], []) // => []\n * pick(null, 0) // => undefined\n * pick(['foo', 'bar', 'baz'], undefined) // => []\n * pick(['foo', 'bar', 'baz'], 0) // => ['foo']\n * pick(['foo', 'bar', 'baz'], [0, 2]) // => ['foo', 'baz']\n * pick(['foo', 'bar', 'baz'], [0, 2, 3]) // => ['foo', 'baz']\n */\nexport function pick(obj, props) {\n return isObject(obj) ? pickProperties(obj, props) : pickArrayElements(obj, props)\n}\n\n/**\n * Remove properties from an object or elements from an array\n * \n * @param {array} obj Object or array to remove properties or elements from\n * @param {array | string | number} props Properties to remove, can be an array of strings or a single string or number\n * @param {boolean} clone Defaults to true, will clone the object or array before removing properties or elements.\n * @returns object | array | undefined\n * @example\n * \n * reject({ foo: 'bar', bar: 'baz', baz: 'qux' }) // => {}\n * reject({}, []) // => {}\n * reject(null, 'foo') // => undefined\n * reject({ foo: 'bar', bar: 'baz', baz: 'qux' }, undefined) // => {}\n * reject({ foo: 'bar', bar: 'baz', baz: 'qux' }, 'foo') // => { bar: 'baz', baz: 'qux' }\n * reject({ foo: 'bar', bar: 'baz', baz: 'qux' }, ['foo', 'baz']) // => { bar: 'baz' }\n * \n * reject(['foo', 'bar', 'baz'], []) // => []\n * reject([], []) // => []\n * reject(null, 0) // => undefined\n * reject(['foo', 'bar', 'baz'], undefined) // => []\n * reject(['foo', 'bar', 'baz'], 0) // => ['bar', 'baz']\n * reject(['foo', 'bar', 'baz'], [0, 2]) // => ['bar']\n * reject(['foo', 'bar', 'baz'], [0, 2, 3]) // => ['bar']\n */\nexport function reject(obj, props, clone = true) {\n return isObject(obj) ? rejectProperties(obj, props, clone) : rejectArrayElements(obj, props, clone)\n}\n\n/**\n * Basic timestamp first UID generator that's good enough for most use cases but not for security purposes.\n * There's an extremely small chance of collision, so create a map object to check for collisions if you're worried about that.\n * \n * - `Date.now().toString(16)` is used for the timestamp, which is a base16 representation of the current timestamp in milliseconds.\n * - `random().toString(16).substring(2)` is used for the random number, which is a base16 representation of a random number between 0 and 1, with the first two characters removed.\n * \n * @param {boolean} safe Defaults to false, if true will use a cryptographically secure random number generator for the random number improving security but reducing performance. If crypto is not available, will use Math.random() instead.\n * @returns string\n * @example\n * basicUID() // => '18d4613e4d2-750bf066ac6158'\n */\nexport function basicUID(safe = false) {\n const rand = safe ? random() : Math.random()\n return Date.now().toString(16)+'-'+rand.toString(16).substring(2)\n}\n\nfunction cryptoUUIDFallback() {\n return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>\n (c ^ Math.random() * 16 >> c / 4).toString(16)\n )\n}\n\n// Taken from https://stackoverflow.com/a/2117523/5437943\nfunction cryptoRandomUUIDFallback() {\n return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>\n (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)\n )\n}\n\n/**\n * Generates a UUID v4\n * - Uses crypto.randomUUID if available\n * - Uses crypto.getRandomValues if available\n * - Uses a fallback if neither is available, which is not safe because it uses Math.random() instead of a cryptographically secure random number generator\n * \n * I'm bad at crypto and bitwise operations, not my cup of tea, so I had to rely on StackOverflow for the fallback: https://stackoverflow.com/a/2117523/5437943\n * \n * @param {boolean} safe Defaults to true, if false will use a fallback that's not cryptographically secure but significantly faster\n * @returns string\n * @example\n * generateUUID() // UUID v4, example 09ed0fe4-8eb6-4c2a-a8d3-a862b7513294\n */\nexport function generateUUID(safe = true) {\n if (!crypto || !safe) return cryptoUUIDFallback()\n if (crypto.randomUUID) return crypto.randomUUID()\n if (crypto.getRandomValues) return cryptoRandomUUIDFallback();\n}\n\n/**\n * Generates a random number between 0 and 1, inclusive of 0 but not inclusive of 1.\n * \n * - Uses crypto.getRandomValues if available\n * - Uses Math.random() if crypto.getRandomValues is not available\n * \n * @returns number\n * @example\n * random() // => 0.123456789\n */\nexport function random() {\n if (!crypto) return Math.random()\n if (crypto.getRandomValues) return crypto.getRandomValues(new Uint32Array(1))[0] / 4294967295 // 2^32 - 1 = 4294967295\n}\n", "import { percentage } from 'book-of-spells'\n\nfunction onDrag(element, callback) {\n let startX = 0;\n let startY = 0;\n let endX = 0;\n let endY = 0;\n let dragging = false;\n let rect = element.getBoundingClientRect();\n\n const handleStart = function(e) {\n const carrier = e.type === 'touchstart' ? e.touches[0] : e;\n startX = carrier.clientX;\n startY = carrier.clientY;\n dragging = true;\n rect = element.getBoundingClientRect();\n const xPercentage = percentage(startX - rect.left, rect.width);\n const yPercentage = percentage(startY - rect.top, rect.height);\n const event = new CustomEvent('dragstart', { detail: { target: element, startX, startY, rect, xPercentage, yPercentage } });\n element.dispatchEvent(event);\n };\n\n const handleMove = function(e) {\n if (!dragging) return;\n const carrier = e.type === 'touchmove' ? e.touches[0] : e;\n endX = carrier.clientX;\n endY = carrier.clientY;\n handleDragGesture();\n };\n\n const handleEnd = function() {\n dragging = false;\n const event = new CustomEvent('dragend', { detail: { target: element, startX, startY, rect, endX, endY } });\n element.dispatchEvent(event);\n };\n\n const handleDragGesture = function() {\n const deltaX = endX - startX;\n const deltaY = endY - startY;\n const left = deltaX < 0;\n const up = deltaY < 0;\n const xPercentage = percentage(endX - rect.left, rect.width);\n const yPercentage = percentage(endY - rect.top, rect.height);\n\n const detail = {\n target: element,\n deltaX: deltaX,\n deltaY: deltaY,\n startX: startX,\n startY: startY,\n endX: endX,\n endY: endY,\n horizontalDirection: left ? 'left' : 'right',\n verticalDirection: up ? 'up' : 'down',\n xPercentage: xPercentage,\n yPercentage: yPercentage\n };\n\n if (xPercentage < 0) {\n detail.xPercentage = 0;\n }\n\n if (xPercentage > 100) {\n detail.xPercentage = 100;\n }\n\n if (yPercentage < 0) {\n detail.yPercentage = 0;\n }\n\n if (yPercentage > 100) {\n detail.yPercentage = 100;\n }\n\n if (callback) {\n callback(detail);\n }\n\n const event = new CustomEvent('drag', { detail: detail });\n element.dispatchEvent(event);\n };\n\n element.addEventListener('mousedown', handleStart);\n element.addEventListener('mousemove', handleMove);\n element.addEventListener('mouseup', handleEnd);\n element.addEventListener('touchstart', handleStart);\n element.addEventListener('touchmove', handleMove);\n element.addEventListener('touchend', handleEnd);\n\n return {\n destroy: function() {\n element.removeEventListener('mousedown', handleStart);\n element.removeEventListener('mousemove', handleMove);\n element.removeEventListener('mouseup', handleEnd);\n element.removeEventListener('touchstart', handleStart);\n element.removeEventListener('touchmove', handleMove);\n element.removeEventListener('touchend', handleEnd);\n }\n };\n}\n\nexport default class CompareImagesSlider {\n constructor(element, options) {\n this.element = element;\n this.frame = this.element.querySelector('.frame');\n this.second = this.frame.querySelector(':scope > img');\n this.handle = this.element.querySelector('.handle');\n\n window.addEventListener('resize', () => {\n requestAnimationFrame(this.setupSecondImage.bind(this));\n });\n this.setupSecondImage();\n\n this.drag = onDrag(this.element);\n this.element.addEventListener('dragstart', this.updateVisibleHandler.bind(this));\n this.element.addEventListener('drag', this.updateVisibleHandler.bind(this));\n }\n\n setupSecondImage() {\n const width = this.element.offsetWidth + 'px';\n this.second.style.width = width;\n }\n\n updateVisibleHandler(e) {\n this.frame.style.width = e.detail.xPercentage + '%';\n this.handle.style.left = e.detail.xPercentage + '%';\n }\n\n destroy() {\n this.drag.destroy();\n this.element.removeEventListener('dragstart', this.updateVisibleHandler.bind(this));\n this.element.removeEventListener('drag', this.updateVisibleHandler.bind(this));\n }\n}\n", "import CompareImagesSlider from './compare-images-slider';\n\nif (!window.CompareImagesSlider) {\n window.CompareImagesSlider = CompareImagesSlider;\n document.dispatchEvent(new CustomEvent('CompareImagesSliderLoaded'));\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ /* compare-images-slider v1.0.0 | https://stamat.github.io/compare-images-slider/ | MIT License */
2
+ @charset "UTF-8";:root{--compare-images-slider-initial-position: 50%}.compare-images-slider{display:block;position:relative;user-select:none;-webkit-user-select:none;-moz-user-select:none}.compare-images-slider img{pointer-events:none}.compare-images-slider>img{max-width:100%;height:auto}.compare-images-slider .frame{position:absolute;top:0;left:0;height:100%;width:var(--compare-images-slider-initial-position);overflow:hidden;z-index:2}.compare-images-slider .frame>img{height:auto}.compare-images-slider .handle{position:absolute;left:var(--compare-images-slider-initial-position);top:0;bottom:0;width:2px;margin-left:-1px;height:100%;cursor:ew-resize;z-index:3}.compare-images-slider .handle:after{position:absolute;top:50%;width:42px;height:42px;transform:translate3d(-50%,-50%,0);content:"\2194";line-height:38px;font-size:28px;background:#fff;text-align:center;border-radius:21px}
@@ -0,0 +1,2 @@
1
+ /* compare-images-slider v1.0.0 | https://stamat.github.io/compare-images-slider/ | MIT License */
2
+ (()=>{function u(e,s){return!e||!s||Number.isNaN(e)||Number.isNaN(s)?0:e/s*100}function w(e,s){let r=0,o=0,d=0,c=0,m=!1,n=e.getBoundingClientRect();const v=function(t){const i=t.type==="touchstart"?t.touches[0]:t;r=i.clientX,o=i.clientY,m=!0,n=e.getBoundingClientRect();const f=u(r-n.left,n.width),p=u(o-n.top,n.height),h=new CustomEvent("dragstart",{detail:{target:e,startX:r,startY:o,rect:n,xPercentage:f,yPercentage:p}});e.dispatchEvent(h)},g=function(t){if(!m)return;const i=t.type==="touchmove"?t.touches[0]:t;d=i.clientX,c=i.clientY,y()},l=function(){m=!1;const t=new CustomEvent("dragend",{detail:{target:e,startX:r,startY:o,rect:n,endX:d,endY:c}});e.dispatchEvent(t)},y=function(){const t=d-r,i=c-o,f=t<0,p=i<0,h=u(d-n.left,n.width),E=u(c-n.top,n.height),a={target:e,deltaX:t,deltaY:i,startX:r,startY:o,endX:d,endY:c,horizontalDirection:f?"left":"right",verticalDirection:p?"up":"down",xPercentage:h,yPercentage:E};h<0&&(a.xPercentage=0),h>100&&(a.xPercentage=100),E<0&&(a.yPercentage=0),E>100&&(a.yPercentage=100),s&&s(a);const b=new CustomEvent("drag",{detail:a});e.dispatchEvent(b)};return e.addEventListener("mousedown",v),e.addEventListener("mousemove",g),e.addEventListener("mouseup",l),e.addEventListener("touchstart",v),e.addEventListener("touchmove",g),e.addEventListener("touchend",l),{destroy:function(){e.removeEventListener("mousedown",v),e.removeEventListener("mousemove",g),e.removeEventListener("mouseup",l),e.removeEventListener("touchstart",v),e.removeEventListener("touchmove",g),e.removeEventListener("touchend",l)}}}var L=class{constructor(e,s){this.element=e,this.frame=this.element.querySelector(".frame"),this.second=this.frame.querySelector(":scope > img"),this.handle=this.element.querySelector(".handle"),window.addEventListener("resize",()=>{requestAnimationFrame(this.setupSecondImage.bind(this))}),this.setupSecondImage(),this.drag=w(this.element),this.element.addEventListener("dragstart",this.updateVisibleHandler.bind(this)),this.element.addEventListener("drag",this.updateVisibleHandler.bind(this))}setupSecondImage(){const e=this.element.offsetWidth+"px";this.second.style.width=e}updateVisibleHandler(e){this.frame.style.width=e.detail.xPercentage+"%",this.handle.style.left=e.detail.xPercentage+"%"}destroy(){this.drag.destroy(),this.element.removeEventListener("dragstart",this.updateVisibleHandler.bind(this)),this.element.removeEventListener("drag",this.updateVisibleHandler.bind(this))}};window.CompareImagesSlider||(window.CompareImagesSlider=L,document.dispatchEvent(new CustomEvent("CompareImagesSliderLoaded")))})();