diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..b87688f --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,81 @@ +module.exports = { + env: { + es6: true, + browser: true + }, + plugins: [ + 'jsdoc', + 'security', + 'security-node' + ], + extends: [ + 'eslint:recommended', + 'airbnb-base', + 'plugin:jsdoc/recommended', + 'plugin:security/recommended', + 'plugin:security-node/recommended' + ], + parserOptions: { + sourceType: 'module', + ecmaVersion: 9 + }, + rules: { + 'class-methods-use-this': 'off', + 'comma-dangle': 'off', + 'jsdoc/tag-lines': [ + 'warn', + 'never', + { + tags: + { + param: + { + lines: 'any' + } + } + } + ], + 'linebreak-style': [ + 'error', + process.platform === 'win32' ? 'windows' : 'unix' + ], + 'max-classes-per-file': 'off', + 'no-console': [ + 'error', + { + allow: [ + 'log', + 'warn', + 'error', + 'dir' + ] + } + ], + 'no-else-return': 'off', + 'no-multi-spaces': [ + 'error', + { + ignoreEOLComments: true + } + ], + 'no-underscore-dangle': [ + 'error', + { + allowAfterThis: true + } + ], + 'operator-linebreak': [ + 'error', + 'after' + ], + 'security/detect-object-injection': 'off', + 'space-before-function-paren': [ + 'error', + { + anonymous: 'never', + asyncArrow: 'always', + named: 'never' + } + ] + } +}; diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index f8cc48b..0000000 --- a/.jscsrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "preset": "airbnb", - "disallowMultipleSpaces": true -} \ No newline at end of file diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 9c96134..0000000 --- a/.jshintrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "browser": true, - "browserify": true, - "devel": true, - "predef": [ "Pangea" ] -} diff --git a/Gulpfile.js b/Gulpfile.js deleted file mode 100644 index 905822e..0000000 --- a/Gulpfile.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -var gulp = require('gulp'); -var jscs = require('gulp-jscs'); -var jshint = require('gulp-jshint'); -var plumber = require('gulp-plumber'); -var rename = require('gulp-rename'); -var uglify = require('gulp-uglify'); - -gulp.task('js:build', function (done) { - gulp.src(['./src/**/*.js', '!./src/**/*.min.js']) - .pipe(plumber()) - .pipe(uglify()) - .pipe(rename({ suffix: '.min' })) - .pipe(gulp.dest('./src')); - done(); -}); - -gulp.task('js:lint', function (done) { - gulp.src(['./src/**/*.js', '!./src/**/*.min.js', 'Gulpfile.js']) - .pipe(plumber()) - .pipe(jscs()) - .pipe(jshint()) - .pipe(jshint.reporter('default')); - done(); -}); - -gulp.task('build', gulp.series(['js:lint', 'js:build'])); - -gulp.task('watch', function () { - var watcher = gulp.watch(['./src/**/*.js', '!./src/**/*min*.js'], gulp.parallel('build')); - watcher.on('change', function (path, stats) { - console.log('File ' + path + ' was changed'); - }); -}); diff --git a/README.md b/README.md index 369c7b7..146ec94 100644 --- a/README.md +++ b/README.md @@ -101,17 +101,147 @@ var options = { var pig = new Pig(imageData, options).enable(); ``` +## Custom layout + +This section shows the basics of how to customize the \
tags that make up the PIG. The full code can be seen in the `/test` directory of this project,. + +Step 0, step 1 and step 2 of 'Getting Started' remain unchanged. + +#### Step 3: Create your markup and extend the pig classes + +Add values to the imageData element + +```javascript +var imageData = [ + {"filename":"2015-01-01.19.47.28.jpg","aspectRatio":1.7777, imageDate: '14.04.2019', description: 'salad'}, + {"filename":"2015-01-01.21.11.37.jpg","aspectRatio":1.7777, imageDate: '14.04.2019', description: 'cookies'}, + {"filename":"2015-01-02.20.03.26.jpg","aspectRatio":1.7777, imageDate: '14.04.2019', description: 'finished'}, + {"filename":"2015-01-02.23.19.25.jpg","aspectRatio":0.5625, imageDate: '15.04.2019', description: 'icecream for me'}, + {"filename":"2015-01-03.20.12.35.jpg","aspectRatio":0.5625, imageDate: '15.04.2019', description: 'current food 1'}, + {"filename":"2015-01-03.21.18.58.jpg","aspectRatio":0.5625, imageDate: '15.04.2019', description: 'current food 2'}, + {"filename":"2015-01-04.12.39.47.jpg","aspectRatio":1.7777, imageDate: '16.04.2019', description: 'current food 3'} +]; +``` + +Extend the `ProgressiveImage` class + +```javascript +class ProgressiveImageCustom extends ProgressiveImage { + constructor(singleImageData, index, pig) { + super(singleImageData, index, pig); + + // Additional data for ProgressiveImage instance + this.imageDate = singleImageData.imageDate; // Date taken + this.description = singleImageData.description; // What to see + + this.classNames.date = pig.settings.classPrefix + '-date'; + this.classNames.desc = pig.settings.classPrefix + '-desc'; + } + + /** + * Add a div tag as a subelement to the
tag. + * @param {string} subElementName - name of the subelement + * @param {string} content - static content of the div tag + * @param {string} classname - name of the class to be added to the new subelement (default value='' - i.e. no class added) + */ + addDivAsSubElement(subElementName, content, classname = '') { + let subElement = this[subElementName]; + if (!subElement) { + this[subElementName] = document.createElement('div'); + let subElement = this[subElementName]; + subElement.innerHTML = content; + if (classname.length > 0) { + subElement.className = classname; + } + this.getElement().appendChild(subElement); + } + } + + /** + * Modify layout of image by adding 2 div tags to the structure created + * by the base class. + */ + addAllSubElements() { + super.addAllSubElements(); + + // Add div for date + this.addDivAsSubElement('date', this.imageDate, `${this.classNames.date} pig-overlay`); + + // Add div for desc + this.addDivAsSubElement('desc', this.description, `${this.classNames.desc} pig-overlay`); + + // Modify default element generated by base class + if (this.fullImage) { + this.fullImage.title = this.description; + // Overwrite eventhandler as we must show custom fields too, when image is loaded + this.fullImage.onload = () => { + // We have to make sure fullImage still exists, we may have already been + // deallocated if the user scrolls too fast. + if (this.fullImage) { + this.fullImage.className += ' ' + this.classNames.loaded; + } + if (this.date) { + this.date.className += ' ' + this.classNames.loaded; + } + if (this.desc) { + this.desc.className += ' ' + this.classNames.loaded; + } + }; + } + } + + /** + * Remove all elements of image - required by pig system. + */ + removeAllSubElements() { + super.removeAllSubElements(); + this.removeSubElement('date'); + this.removeSubElement('desc'); + } +} +``` + +Add required css classes to the `Pig` class + +```javascript +class PigCustom extends Pig { + _injectStyle(containerId, classPrefix, transitionSpeed) { + super._injectStyle(containerId, classPrefix, transitionSpeed); + const customCss = `.pig-date { background-color: rgba(128, 128, 128, 0.35); color: white; font-size: 11px; opacity: 0; padding: 0 4px; position: absolute; top: 0; } + .pig-desc {background-color: rgba(128, 128, 128, 0.35); bottom: 0; color: white; font-size: 11px; overflow: hidden; padding: 0 4px; opacity: 0; position: absolute; white-space: nowrap; } + .pig-figure div.pig-loaded { opacity: 1; }`; + + const head = document.head || document.getElementsByTagName('head')[0]; + const style = document.createElement('style'); + style.appendChild(document.createTextNode(customCss)); + head.appendChild(style); + } +} +``` + +Set required pig options and create a new `Pig` instance, passing image data and options + +```javascript +const pigOptions = { + urlForSize: (filename, size) => `https://feeding.schlosser.io/img/food/${size}/${filename}`, + onClickHandler: (filename) => window.open(`https://feeding.schlosser.io/img/food/250/${filename}`, '_blank'), + createProgressiveImage: (singleImageData, index, pig) => new ProgressiveImageCustom(singleImageData, index, pig) +}; + +const pig = new PigCustom(imageData, pigOptions).enable(); +``` + ## API ### Pig(_imageData_[, _options_]) -The `Pig` constructor will setup a new progressive image grid instance. It returns a new instance of the `Pangea` class. +The `Pig` constructor will setup a new progressive image grid instance. It returns a new instance of the `Pig` class. #### `imageData` _(array)_ **Note:** This argument is required. -A list of objects, one per image in the grid. In each object, the `filename` key gives the name of the image file and the `aspectRatio` key gives the aspect ratio of the image. The below example shows how you would pass six images into the PIG: +A list of objects, one per image in the grid. In each object, the `filename` key gives the name of the image file and the `aspectRatio` key gives the aspect ratio of the image. More keys can be added for derived `Pig` classes. The below example shows how you would pass six images into the PIG: ```javascript var imageData = [ @@ -144,7 +274,7 @@ var options = { urlForSize: function(filename, size) { return '/img/' + size + '/' + filename; }, - onClickHandler: function(filename) { }, + onClickHandler: null, getMinAspectRatio: function(lastWindowWidth) { if (lastWindowWidth <= 640) // Phones return 2; @@ -278,13 +408,24 @@ Get the image size (height in pixels) to use for this window width. Responsive r #### `options.onClickHandler` _(function)_ -Add callback function which is called when a image is clicked with the image name. By default this is an empty function. +Add callback function which is called, when a image is clicked with the image name. By default this is null. > **Parameters** > - `filename` _(string)_ - The name of the clicked image > +> **Default**: null + +#### `options.createProgressiveImage` _(function)_ + +Factory function that creates a new instance of the ProgressiveImage class. + +> **Parameters** +> - `singleImageData` _(object)_ - Data of one image in data source +> - `index` _(number)_ - Index of the image in the data source +> - `pig` _(object)_ - Pig instance, that should contain this image +> > **Default** -> function(filename) {} +> function(singleImageData, index, pig) { return new ProgressiveImage(singleImageData, index, pig) } ### Pig.enable() @@ -297,3 +438,105 @@ Disable the Pig library by removing event listeners set in `Pig.enable()`. [download]: https://github.com/schlosser/pig.js/releases/download/v0.3/pig.min.js [feeding-dan]: https://feeding.schlosser.io/ [feeding-dan-gh]: https://github.com/schlosser/feeding-dan/ + +### ProgressiveImage(__singleImageData__, __index__, __pig__) + +The `ProgressiveImage` constructor will setup a new progressive image instance that represents 1 image in the grid. It returns a new instance of the `ProgressiveImage` class. + +#### `singleImageData` _(object)_ + +An object from the list of imageData, representing one image in the grid. + +> **Keys**: +> - `filename` _(string)_ - The name of the image file. **Required** +> - `aspectRatio` _(number)_ - The aspect ratio of the image. **Required** +> - More keys can be added for derived `ProgressiveImage` classes. + +#### `index` _(number)_ + +The index of this image in the list of images (zero based). + +#### `pig` _(object)_ + +The reference to the `Pig` instance, this `ProgressiveImage` image belongs to. + +### ProgressiveImage.addAllSubElements _(function) + +Add all subelements that make up an image in the PIG. +Uses `addImageAsSubElement` to insert subelements like images or div tags. + +> **Default**: +> ```javascript +> addAllSubElements() { +> // Add thumbnail +> this.addImageAsSubElement('thumbnail', +> this.filename, +> this.pig.settings.>thumbnailSize, +> this.classNames.thumbnail); +> +> // Add full image +> this.addImageAsSubElement('fullImage', +> this.filename, +> this.pig.settings.getImageSize( +> this.pig.lastWindowWidth)); +> } +> ``` + +### ProgressiveImage.addImageAsSubElement _(function) + +Add an imgage as a subelement to the \
tag representing this image. +This function is (only) called by `ProgressiveImage.addAllSubElements`. Therefore the signature can be anything needed. + +> **Default**: +> ```javascript +> addImageAsSubElement(subElementName, filename, size, classname = '') { +> let subElement = this[subElementName]; +> if (!subElement) { +> this[subElementName] = new Image(); +> subElement = this[subElementName]; +> subElement.src = this.pig.settings.urlForSize(filename, size); +> if (classname.length > 0) { +> subElement.className = classname; +> } +> subElement.onload = () => { +> // We have to make sure that the thumbnail still exists; we may +> // have been already deallocating it, if the user scrolls too fast. +> if (subElement) { +> subElement.className += ' ' + this.classNames.loaded; +> } +> }; +> +> this.getElement().appendChild(subElement); +> } +> } +> ``` + +### ProgressiveImage.removeAllSubElements _(function) + +Remove all subelements that make up an image in the PIG. +Uses `removeSubElement` to remove the subelements like images or div tags. + +> **Default**: +> ```javascript +> removeAllSubElements() { +> this.removeSubElement('thumbnail'); +> this.removeSubElement('fullImage'); +> } +> ``` + +### ProgressiveImage.removeSubElement _(function) + +Remove a subelement of the \
tag (e.g. an image element). +This function is (only) called by `ProgressiveImage.removeAllSubElements`. Therefore the signature can be anything needed. + +> **Default**: +> ```javascript +> removeSubElement(subElementName) { +> const subElement = this[subElementName]; +> if (subElement) { +> subElement.src = ''; +> this.getElement().removeChild(subElement); +> delete this[subElementName]; +> } +> } +> ``` diff --git a/dist/README.md b/dist/README.md new file mode 100644 index 0000000..18d1888 --- /dev/null +++ b/dist/README.md @@ -0,0 +1,542 @@ +Progressive Image Grid (`pig.js`) +================================= + +[![npm](https://img.shields.io/npm/v/pig.js.svg)](https://www.npmjs.com/package/pig.js) [![Bower](https://img.shields.io/bower/v/pig.js.svg)]() + +#### → → [Play with a demo][feeding-dan] + +Arrange images in a responsive, progressive-loading grid managed in JavaScript using CSS transforms, with one lightweight library. Here's what you get: + +1. **Performance**: The `pig.js` grid specializes in displaying a large number of photos. While a more traditional approaches would paginate images, or use AJAX to load and insert buckets of additional images onto the page, `pig.js` intelligently loads and unloads images as you scroll down the page, to ensure ensure speedy rendering of images, smooth scrolling, and minimal network activitiy. +2. **Responsiveness**: Images are loaded in JavaScript, at different resolutions depending on screen size. Also, because images are positioned using CSS transforms, images smartly re-tile when you shrink or expand the browser window. +3. **User Experience**: Images are previewed with a small placeholder image that is scaled and blured. When the image loads, it gives the effect that the image was brought into focus (it's super cool!). + +A lot of this is stolen straight from Google Photos (by way of observation and some creative use of the Chrome Inspector) and Medium (by way of some [dope blog posts](https://jmperezperez.com/medium-image-progressive-loading-placeholder/)). + +If you want to see `pig.js` in action, check out the site that motivated it in the first place: a catalog of everything I ate in 2015: [feeding.schlosser.io][feeding-dan]. That site is also [on GitHub][feeding-dan-gh]. + +## Getting Started + +#### Step 0: Install + +[Download the latest release][download]. + +#### Step 1: Create your markup + +```html + + + + +
+``` + +#### Step 2: Create a structure to serve your images + +Pig includes by default an easy method for handling responsive images at different screen sizes. By default, Pig will attempt to request images with size (height in pixels) 100, 250, and 500. It will also request a thumbnail 20px tall, which is used to create an effect of the blurred image coming into focus. + +###### Example: Serving Static Files + +Create a directory structure like: + +```bash +. +├── index.html +├── path +│   └── to +│   └── pig.min.js +├── img +│   ├── 20 +│   | ├── blue.jpg +│   | ├── red.jpg +│   | ... +│   | +│   ├── 100 +│   | ├── blue.jpg +│   | ├── red.jpg +│   | ... +│   | +│   ├── 250 +│   | ├── blue.jpg +│   | ├── red.jpg +│   | ... +│   | +│   └── 500 +│   ├── blue.jpg +│   ├── red.jpg +│   ... +... +``` + +And then set the `urlForSize` configuration option to account for this structure: + +```javascript +var options = { + urlForSize: function(filename, size) { + return '/img/' + size + '/' + filename; + }, + // ... +}; +``` + +#### Step 3: Create a new `Pig` instance, passing image data and options + +```javascript +var imageData = [ + {filename: 'blue.jpg', aspectRatio: 1.777}, + {filename: 'red.jpg', aspectRatio: 1.5}, + {filename: 'green.jpg', aspectRatio: 1.777}, + {filename: 'orange.jpg', aspectRatio: 1.777}, + {filename: 'yellow.jpg', aspectRatio: 1}, + {filename: 'purple.jpg', aspectRatio: 2.4}, +]; + +var options = { + urlForSize: function(filename, size) { + return '/img/' + size + '/' + filename; + }, + // ... +}; + +var pig = new Pig(imageData, options).enable(); +``` + +## Custom layout + +This section shows the basics of how to customize the \
tags that make up the PIG. The full code can be seen in the `/test` directory of this project,. + +Step 0, step 1 and step 2 of 'Getting Started' remain unchanged. + +#### Step 3: Create your markup and extend the pig classes + +Add values to the imageData element + +```javascript +var imageData = [ + {"filename":"2015-01-01.19.47.28.jpg","aspectRatio":1.7777, imageDate: '14.04.2019', description: 'salad'}, + {"filename":"2015-01-01.21.11.37.jpg","aspectRatio":1.7777, imageDate: '14.04.2019', description: 'cookies'}, + {"filename":"2015-01-02.20.03.26.jpg","aspectRatio":1.7777, imageDate: '14.04.2019', description: 'finished'}, + {"filename":"2015-01-02.23.19.25.jpg","aspectRatio":0.5625, imageDate: '15.04.2019', description: 'icecream for me'}, + {"filename":"2015-01-03.20.12.35.jpg","aspectRatio":0.5625, imageDate: '15.04.2019', description: 'current food 1'}, + {"filename":"2015-01-03.21.18.58.jpg","aspectRatio":0.5625, imageDate: '15.04.2019', description: 'current food 2'}, + {"filename":"2015-01-04.12.39.47.jpg","aspectRatio":1.7777, imageDate: '16.04.2019', description: 'current food 3'} +]; +``` + +Extend the `ProgressiveImage` class + +```javascript +class ProgressiveImageCustom extends ProgressiveImage { + constructor(singleImageData, index, pig) { + super(singleImageData, index, pig); + + // Additional data for ProgressiveImage instance + this.imageDate = singleImageData.imageDate; // Date taken + this.description = singleImageData.description; // What to see + + this.classNames.date = pig.settings.classPrefix + '-date'; + this.classNames.desc = pig.settings.classPrefix + '-desc'; + } + + /** + * Add a div tag as a subelement to the
tag. + * @param {string} subElementName - name of the subelement + * @param {string} content - static content of the div tag + * @param {string} classname - name of the class to be added to the new subelement (default value='' - i.e. no class added) + */ + addDivAsSubElement(subElementName, content, classname = '') { + let subElement = this[subElementName]; + if (!subElement) { + this[subElementName] = document.createElement('div'); + let subElement = this[subElementName]; + subElement.innerHTML = content; + if (classname.length > 0) { + subElement.className = classname; + } + this.getElement().appendChild(subElement); + } + } + + /** + * Modify layout of image by adding 2 div tags to the structure created + * by the base class. + */ + addAllSubElements() { + super.addAllSubElements(); + + // Add div for date + this.addDivAsSubElement('date', this.imageDate, `${this.classNames.date} pig-overlay`); + + // Add div for desc + this.addDivAsSubElement('desc', this.description, `${this.classNames.desc} pig-overlay`); + + // Modify default element generated by base class + if (this.fullImage) { + this.fullImage.title = this.description; + // Overwrite eventhandler as we must show custom fields too, when image is loaded + this.fullImage.onload = () => { + // We have to make sure fullImage still exists, we may have already been + // deallocated if the user scrolls too fast. + if (this.fullImage) { + this.fullImage.className += ' ' + this.classNames.loaded; + } + if (this.date) { + this.date.className += ' ' + this.classNames.loaded; + } + if (this.desc) { + this.desc.className += ' ' + this.classNames.loaded; + } + }; + } + } + + /** + * Remove all elements of image - required by pig system. + */ + removeAllSubElements() { + super.removeAllSubElements(); + this.removeSubElement('date'); + this.removeSubElement('desc'); + } +} +``` + +Add required css classes to the `Pig` class + +```javascript +class PigCustom extends Pig { + _injectStyle(containerId, classPrefix, transitionSpeed) { + super._injectStyle(containerId, classPrefix, transitionSpeed); + const customCss = `.pig-date { background-color: rgba(128, 128, 128, 0.35); color: white; font-size: 11px; opacity: 0; padding: 0 4px; position: absolute; top: 0; } + .pig-desc {background-color: rgba(128, 128, 128, 0.35); bottom: 0; color: white; font-size: 11px; overflow: hidden; padding: 0 4px; opacity: 0; position: absolute; white-space: nowrap; } + .pig-figure div.pig-loaded { opacity: 1; }`; + + const head = document.head || document.getElementsByTagName('head')[0]; + const style = document.createElement('style'); + style.appendChild(document.createTextNode(customCss)); + head.appendChild(style); + } +} +``` + +Set required pig options and create a new `Pig` instance, passing image data and options + +```javascript +const pigOptions = { + urlForSize: (filename, size) => `https://feeding.schlosser.io/img/food/${size}/${filename}`, + onClickHandler: (filename) => window.open(`https://feeding.schlosser.io/img/food/250/${filename}`, '_blank'), + createProgressiveImage: (singleImageData, index, pig) => new ProgressiveImageCustom(singleImageData, index, pig) +}; + +const pig = new PigCustom(imageData, pigOptions).enable(); +``` + +## API + +### Pig(_imageData_[, _options_]) + +The `Pig` constructor will setup a new progressive image grid instance. It returns a new instance of the `Pig` class. + +#### `imageData` _(array)_ + +**Note:** This argument is required. + +A list of objects, one per image in the grid. In each object, the `filename` key gives the name of the image file and the `aspectRatio` key gives the aspect ratio of the image. More keys can be added for derived `Pig` classes. The below example shows how you would pass six images into the PIG: + +```javascript +var imageData = [ + {filename: 'blue.jpg', aspectRatio: 1.777}, + {filename: 'red.jpg', aspectRatio: 1.5}, + {filename: 'green.jpg', aspectRatio: 1.777}, + {filename: 'orange.jpg', aspectRatio: 1.777}, + {filename: 'yellow.jpg', aspectRatio: 1}, + {filename: 'purple.jpg', aspectRatio: 2.4}, +]; +var options = { /* ... */ }; +var pig = new Pig(imageData, options); +``` + +#### `options` _(object)_ + +You can customize the instance by passing the `options` parameter. The example below uses all options and their defaults: + +```javascript +var imageData = [ /* ... */ ]; +var options = { + containerId: 'pig', + classPrefix: 'pig', + figureTagName: 'figure', + spaceBetweenImages: 8, + transitionSpeed: 500, + primaryImageBufferHeight: 1000, + secondaryImageBufferHeight: 300, + thumbnailSize: 20, + urlForSize: function(filename, size) { + return '/img/' + size + '/' + filename; + }, + onClickHandler: null, + getMinAspectRatio: function(lastWindowWidth) { + if (lastWindowWidth <= 640) // Phones + return 2; + else if (lastWindowWidth <= 1280) // Tablets + return 4; + else if (lastWindowWidth <= 1920) // Laptops + return 5; + return 6; // Large desktops + }, + getImageSize: function(lastWindowWidth) { + if (lastWindowWidth <= 640) // Phones + return 100; + else if (lastWindowWidth <= 1920) // Tablets and latops + return 250; + return 500; // Large desktops + } +}; +var pig = new Pig(imageData, options); +``` + +#### `options.containerId` _(string)_ + +The class name of the element inside of which images should be loaded. + +> **Default**: `'pig'` + +#### `options.classPrefix` _(string)_ + +The prefix associated with this library that should be prepended to class names within the grid. + +> **Default**: `'pig'` + +#### `options.figureTagName` _(string)_ + +The tag name to use for each figure. The default setting is to use a `
` tag. + +> **Default**: `'figure'` + +#### `options.spaceBetweenImages` _(number)_ + +Size in pixels of the gap between images in the grid. + +> **Default**: `8` + +#### `options.transitionSpeed` _(number)_ + +Transition speed in milliseconds. + +> **Default**: `500` + +#### `options.primaryImageBufferHeight` _(number)_ + +Height in pixels of images to preload in the direction that the user is scrolling. For example, in the default case, if the user is scrolling down, 1000px worth of images will be loaded below the viewport. + +> **Default**: `1000` + +#### `options.secondaryImageBufferHeight` _(number)_ + +Height in pixels of images to preload in the direction that the user is NOT scrolling. For example, in the default case, if the user is scrolling down, 300px worth of images will be loaded above the viewport. Images further up will be removed. + +> **Default**: `300` + +#### `options.thumbnailSize` _(number)_ + +The height in pixels of the thumbnail that should be loaded and blurred to give the effect that images are loading out of focus and then coming into focus. + +> **Default**: `20` + +#### `options.urlForSize` _(function)_ + +Get the URL for an image with the given filename & size. + +> **Parameters**: +> - `filename` _(string)_ - The filename of the image. +> - `size` _(number)_ - The size (height in pixels) of the image. +> +> **Returns**: +> - _(string)_ - The URL of the image at the given size. +> +> **Default**: +> ```javascript +> function(filename, size) { +> return '/img/' + size + '/' + filename; +> } +> ``` + +#### `options.getMinAspectRatio` _(function)_ + +Get the minimum required aspect ratio for a valid row of images. The perfect rows are maintained by building up a row of images by adding together their aspect ratios (the aspect ratio when they are placed next to each other) until that aspect ratio exceeds the value returned by this function. Responsive reordering is achieved through changes to what this function returns at different values of the passed parameter `lastWindowWidth`. + +> **Parameters**: +> - `lastWindowWidth` _(number)_ - The last computed width of the browser window. +> +> **Returns**: +> - _(number)_ - The minimum aspect ratio at this window width. +> +> **Default**: +> ```javascript +> function(lastWindowWidth) { +> if (lastWindowWidth <= 640) // Phones +> return 2; +> else if (lastWindowWidth <= 1280) // Tablets +> return 4; +> else if (lastWindowWidth <= 1920) // Laptops +> return 5; +> return 6; // Large desktops +> } +> ``` + +#### `options.getImageSize` _(function)_ + + +Get the image size (height in pixels) to use for this window width. Responsive resizing of images is achieved through changes to what this function returns at different values of the passed parameter `lastWindowWidth`. + +> **Parameters**: +> - `lastWindowWidth` _(number)_ - The last computed width of the browser window. +> +> **Returns**: +> - _(number)_ - The size (height in pixels) of the images to load. +> +> **Default**: +> ```javascript +> function(lastWindowWidth) { +> if (lastWindowWidth <= 640) // Phones +> return 100; +> else if (lastWindowWidth <= 1920) // Tablets and latops +> return 250; +> return 500; // Large desktops +> } +> ``` + +#### `options.onClickHandler` _(function)_ + +Add callback function which is called, when a image is clicked with the image name. By default this is null. + +> **Parameters** +> - `filename` _(string)_ - The name of the clicked image +> +> **Default**: null + +#### `options.createProgressiveImage` _(function)_ + +Factory function that creates a new instance of the ProgressiveImage class. + +> **Parameters** +> - `singleImageData` _(object)_ - Data of one image in data source +> - `index` _(number)_ - Index of the image in the data source +> - `pig` _(object)_ - Pig instance, that should contain this image +> +> **Default** +> function(singleImageData, index, pig) { return new ProgressiveImage(singleImageData, index, pig) } + +### Pig.enable() + +Enable the Pig library by beginning to listen to scroll and resize events, loading images and displaying them in the grid. + +### Pig.disable() + +Disable the Pig library by removing event listeners set in `Pig.enable()`. + +[download]: https://github.com/schlosser/pig.js/releases/download/v0.2.1/pig.min.js +[feeding-dan]: https://feeding.schlosser.io/ +[feeding-dan-gh]: https://github.com/schlosser/feeding-dan/ + +### ProgressiveImage(__singleImageData__, __index__, __pig__) + +The `ProgressiveImage` constructor will setup a new progressive image instance that represents 1 image in the grid. It returns a new instance of the `ProgressiveImage` class. + +#### `singleImageData` _(object)_ + +An object from the list of imageData, representing one image in the grid. + +> **Keys**: +> - `filename` _(string)_ - The name of the image file. **Required** +> - `aspectRatio` _(number)_ - The aspect ratio of the image. **Required** +> - More keys can be added for derived `ProgressiveImage` classes. + +#### `index` _(number)_ + +The index of this image in the list of images (zero based). + +#### `pig` _(object)_ + +The reference to the `Pig` instance, this `ProgressiveImage` image belongs to. + +### ProgressiveImage.addAllSubElements _(function) + +Add all subelements that make up an image in the PIG. +Uses `addImageAsSubElement` to insert subelements like images or div tags. + +> **Default**: +> ```javascript +> addAllSubElements() { +> // Add thumbnail +> this.addImageAsSubElement('thumbnail', +> this.filename, +> this.pig.settings.>thumbnailSize, +> this.classNames.thumbnail); +> +> // Add full image +> this.addImageAsSubElement('fullImage', +> this.filename, +> this.pig.settings.getImageSize( +> this.pig.lastWindowWidth)); +> } +> ``` + +### ProgressiveImage.addImageAsSubElement _(function) + +Add an imgage as a subelement to the \
tag representing this image. +This function is (only) called by `ProgressiveImage.addAllSubElements`. Therefore the signature can be anything needed. + +> **Default**: +> ```javascript +> addImageAsSubElement(subElementName, filename, size, classname = '') { +> let subElement = this[subElementName]; +> if (!subElement) { +> this[subElementName] = new Image(); +> subElement = this[subElementName]; +> subElement.src = this.pig.settings.urlForSize(filename, size); +> if (classname.length > 0) { +> subElement.className = classname; +> } +> subElement.onload = () => { +> // We have to make sure that the thumbnail still exists; we may +> // have been already deallocating it, if the user scrolls too fast. +> if (subElement) { +> subElement.className += ' ' + this.classNames.loaded; +> } +> }; +> +> this.getElement().appendChild(subElement); +> } +> } +> ``` + +### ProgressiveImage.removeAllSubElements _(function) + +Remove all subelements that make up an image in the PIG. +Uses `removeSubElement` to remove the subelements like images or div tags. + +> **Default**: +> ```javascript +> removeAllSubElements() { +> this.removeSubElement('thumbnail'); +> this.removeSubElement('fullImage'); +> } +> ``` + +### ProgressiveImage.removeSubElement _(function) + +Remove a subelement of the \
tag (e.g. an image element). +This function is (only) called by `ProgressiveImage.removeAllSubElements`. Therefore the signature can be anything needed. + +> **Default**: +> ```javascript +> removeSubElement(subElementName) { +> const subElement = this[subElementName]; +> if (subElement) { +> subElement.src = ''; +> this.getElement().removeChild(subElement); +> delete this[subElementName]; +> } +> } +> ``` diff --git a/dist/esm/package.json b/dist/esm/package.json new file mode 100644 index 0000000..17b955a --- /dev/null +++ b/dist/esm/package.json @@ -0,0 +1,19 @@ +{ + "name": "pig.js", + "version": "0.2.1", + "description": "Arrange images in a responsive, progressive-loading grid managed in JavaScript using CSS transforms.", + "repository": "https://github.com/schlosser/pig.js", + "devDependencies": { + "esbuild": "^0.13.5", + "eslint": "^7.32.0", + "eslint-config-airbnb-base": "^14.2.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-jsdoc": "^36.1.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-security": "^1.4.0", + "eslint-plugin-security-node": "^1.0.14" + }, + "type": "module", + "author": "Dan Schlosser", + "license": "MIT" +} diff --git a/dist/esm/pig.js b/dist/esm/pig.js new file mode 100644 index 0000000..08ed1da --- /dev/null +++ b/dist/esm/pig.js @@ -0,0 +1,321 @@ +// src/pig.js +var ProgressiveImage = class { + constructor(singleImageData, index, pig) { + this.existsOnPage = false; + this.aspectRatio = singleImageData.aspectRatio; + this.filename = singleImageData.filename; + this.index = index; + this.pig = pig; + this.classNames = { + figure: `${pig.settings.classPrefix}-figure`, + thumbnail: `${pig.settings.classPrefix}-thumbnail`, + loaded: `${pig.settings.classPrefix}-loaded` + }; + return this; + } + load() { + this.existsOnPage = true; + this._updateStyles(); + this.pig.container.appendChild(this.getElement()); + setTimeout(() => { + if (!this.existsOnPage) { + return; + } + this.addAllSubElements(); + }, 100); + } + hide() { + if (this.getElement()) { + this.removeAllSubElements(); + } + if (this.existsOnPage) { + this.pig.container.removeChild(this.getElement()); + } + this.existsOnPage = false; + } + getElement() { + if (!this.element) { + this.element = document.createElement(this.pig.settings.figureTagName); + this.element.className = this.classNames.figure; + if (this.pig.settings.onClickHandler !== null) { + this.element.addEventListener("click", () => { + this.pig.settings.onClickHandler(this.filename); + }); + } + this._updateStyles(); + } + return this.element; + } + addImageAsSubElement(subElementName, filename, size, classname = "") { + let subElement = this[subElementName]; + if (!subElement) { + this[subElementName] = new Image(); + subElement = this[subElementName]; + subElement.src = this.pig.settings.urlForSize(filename, size); + if (classname.length > 0) { + subElement.className = classname; + } + subElement.onload = () => { + if (subElement) { + subElement.className += ` ${this.classNames.loaded}`; + } + }; + this.getElement().appendChild(subElement); + } + } + addAllSubElements() { + this.addImageAsSubElement("thumbnail", this.filename, this.pig.settings.thumbnailSize, this.classNames.thumbnail); + this.addImageAsSubElement("fullImage", this.filename, this.pig.settings.getImageSize(this.pig.lastWindowWidth)); + } + removeSubElement(subElementName) { + const subElement = this[subElementName]; + if (subElement) { + subElement.src = ""; + this.getElement().removeChild(subElement); + delete this[subElementName]; + } + } + removeAllSubElements() { + this.removeSubElement("thumbnail"); + this.removeSubElement("fullImage"); + } + _updateStyles() { + this.getElement().style.transition = this.style.transition; + this.getElement().style.width = `${this.style.width}px`; + this.getElement().style.height = `${this.style.height}px`; + this.getElement().style.transform = `translate3d(${this.style.translateX}px, ${this.style.translateY}px, 0)`; + } +}; +var OptimizedResize = class { + constructor() { + this._callbacks = []; + this._running = false; + } + add(callback) { + if (!this._callbacks.length) { + window.addEventListener("resize", this._resize.bind(this)); + } + this._callbacks.push(callback); + } + disable() { + window.removeEventListener("resize", this._resize.bind(this)); + } + reEnable() { + window.addEventListener("resize", this._resize.bind(this)); + } + _resize() { + if (!this._running) { + this._running = true; + if (window.requestAnimationFrame) { + window.requestAnimationFrame(this._runCallbacks.bind(this)); + } else { + setTimeout(this._runCallbacks.bind(this), 66); + } + } + } + _runCallbacks() { + this._callbacks.forEach((callback) => { + callback(); + }); + this._running = false; + } +}; +var PigSettings = class { + constructor() { + this.containerId = "pig"; + this.scroller = window; + this.classPrefix = "pig"; + this.figureTagName = "figure"; + this.spaceBetweenImages = 8; + this.transitionSpeed = 500; + this.primaryImageBufferHeight = 1e3; + this.secondaryImageBufferHeight = 300; + this.thumbnailSize = 20; + this.onClickHandler = null; + } + urlForSize(filename, size) { + return `/img/${size.toString(10)}/${filename}`; + } + getMinAspectRatio(lastWindowWidth) { + if (lastWindowWidth <= 640) { + return 2; + } else if (lastWindowWidth <= 1280) { + return 4; + } else if (lastWindowWidth <= 1920) { + return 5; + } + return 6; + } + getImageSize(lastWindowWidth) { + if (lastWindowWidth <= 640) { + return 100; + } else if (lastWindowWidth <= 1920) { + return 250; + } + return 500; + } + createProgressiveImage(singleImageData, index, pig) { + return new ProgressiveImage(singleImageData, index, pig); + } +}; +var Pig = class { + constructor(imageData, options) { + this._optimizedResize = new OptimizedResize(); + this.inRAF = false; + this.isTransitioning = false; + this.minAspectRatioRequiresTransition = false; + this.minAspectRatio = null; + this.latestYOffset = 0; + this.lastWindowWidth = window.innerWidth; + this.scrollDirection = "down"; + this.visibleImages = []; + this.settings = Object.assign(new PigSettings(), options); + this.container = document.getElementById(this.settings.containerId); + if (!this.container) { + console.error(`Could not find element with ID ${this.settings.containerId}`); + } + this.scroller = this.settings.scroller; + this.images = this._parseImageData(imageData); + this._injectStyle(this.settings.containerId, this.settings.classPrefix, this.settings.transitionSpeed); + return this; + } + enable() { + this.onScroll = this._getOnScroll(); + this.scroller.addEventListener("scroll", this.onScroll); + this.onScroll(); + this._computeLayout(); + this._doLayout(); + this._optimizedResize.add(() => { + this.lastWindowWidth = this.scroller === window ? window.innerWidth : this.scroller.offsetWidth; + this._computeLayout(); + this._doLayout(); + }); + return this; + } + disable() { + this.scroller.removeEventListener("scroll", this.onScroll); + this._optimizedResize.disable(); + return this; + } + _parseImageData(imageData) { + const progressiveImages = []; + imageData.forEach((image, index) => { + const progressiveImage = this.settings.createProgressiveImage(image, index, this); + progressiveImages.push(progressiveImage); + }); + return progressiveImages; + } + _getOffsetTop(elem) { + let offsetTop = 0; + do { + if (!Number.isNaN(elem.offsetTop)) { + offsetTop += elem.offsetTop; + } + elem = elem.offsetParent; + } while (elem); + return offsetTop; + } + _injectStyle(containerId, classPrefix, transitionSpeed) { + const css = `#${containerId} { position: relative;}.${classPrefix}-figure { background-color: #D5D5D5; overflow: hidden; left: 0; position: absolute; top: 0; margin: 0;}.${classPrefix}-figure img { left: 0; position: absolute; top: 0; height: 100%; width: 100%; opacity: 0; transition: ${(transitionSpeed / 1e3).toString(10)}s ease opacity; -webkit-transition: ${(transitionSpeed / 1e3).toString(10)}s ease opacity;}.${classPrefix}-figure img.${classPrefix}-thumbnail { -webkit-filter: blur(30px); filter: blur(30px); left: auto; position: relative; width: auto;}.${classPrefix}-figure img.${classPrefix}-loaded { opacity: 1;}`; + const head = document.head || document.getElementsByTagName("head")[0]; + const style = document.createElement("style"); + style.appendChild(document.createTextNode(css)); + head.appendChild(style); + } + _getTransitionTimeout() { + const transitionTimeoutScaleFactor = 1.5; + return this.settings.transitionSpeed * transitionTimeoutScaleFactor; + } + _getTransitionString() { + if (this.isTransitioning) { + return `${(this.settings.transitionSpeed / 1e3).toString(10)}s transform ease`; + } + return "none"; + } + _recomputeMinAspectRatio() { + const oldMinAspectRatio = this.minAspectRatio; + this.minAspectRatio = this.settings.getMinAspectRatio(this.lastWindowWidth); + if (oldMinAspectRatio !== null && oldMinAspectRatio !== this.minAspectRatio) { + this.minAspectRatioRequiresTransition = true; + } else { + this.minAspectRatioRequiresTransition = false; + } + } + _computeLayout() { + const wrapperWidth = parseInt(this.container.clientWidth, 10); + let row = []; + let translateX = 0; + let translateY = 0; + let rowAspectRatio = 0; + this._recomputeMinAspectRatio(); + if (!this.isTransitioning && this.minAspectRatioRequiresTransition) { + this.isTransitioning = true; + setTimeout(() => { + this.isTransitioning = false; + }, this._getTransitionTimeout()); + } + const transition = this._getTransitionString(); + [].forEach.call(this.images, (image, index) => { + rowAspectRatio += parseFloat(image.aspectRatio); + row.push(image); + if (rowAspectRatio >= this.minAspectRatio || index + 1 === this.images.length) { + rowAspectRatio = Math.max(rowAspectRatio, this.minAspectRatio); + const totalDesiredWidthOfImages = wrapperWidth - this.settings.spaceBetweenImages * (row.length - 1); + const rowHeight = totalDesiredWidthOfImages / rowAspectRatio; + row.forEach((img) => { + const imageWidth = rowHeight * img.aspectRatio; + img.style = { + width: parseInt(imageWidth, 10), + height: parseInt(rowHeight, 10), + translateX, + translateY, + transition + }; + translateX += imageWidth + this.settings.spaceBetweenImages; + }); + row = []; + rowAspectRatio = 0; + translateY += parseInt(rowHeight, 10) + this.settings.spaceBetweenImages; + translateX = 0; + } + }); + this.totalHeight = translateY - this.settings.spaceBetweenImages; + } + _doLayout() { + this.container.style.height = `${this.totalHeight}px`; + const bufferTop = this.scrollDirection === "up" ? this.settings.primaryImageBufferHeight : this.settings.secondaryImageBufferHeight; + const bufferBottom = this.scrollDirection === "down" ? this.settings.secondaryImageBufferHeight : this.settings.primaryImageBufferHeight; + const containerOffset = this._getOffsetTop(this.container); + const scrollerHeight = this.scroller === window ? window.innerHeight : this.scroller.offsetHeight; + const minTranslateYPlusHeight = this.latestYOffset - containerOffset - bufferTop; + const maxTranslateY = this.latestYOffset - containerOffset + scrollerHeight + bufferBottom; + this.images.forEach((image) => { + if (image.style.translateY + image.style.height < minTranslateYPlusHeight || image.style.translateY > maxTranslateY) { + image.hide(); + } else { + image.load(); + } + }); + } + _getOnScroll() { + const that = this; + const onScroll = () => { + const newYOffset = that.scroller === window ? window.pageYOffset : that.scroller.scrollTop; + that.previousYOffset = that.latestYOffset || newYOffset; + that.latestYOffset = newYOffset; + that.scrollDirection = that.latestYOffset > that.previousYOffset ? "down" : "up"; + if (!that.inRAF) { + that.inRAF = true; + window.requestAnimationFrame(() => { + that._doLayout(); + that.inRAF = false; + }); + } + }; + return onScroll; + } +}; +export { + Pig, + ProgressiveImage +}; diff --git a/dist/esm/pig.min.js b/dist/esm/pig.min.js new file mode 100644 index 0000000..32222fa --- /dev/null +++ b/dist/esm/pig.min.js @@ -0,0 +1 @@ +var c=class{constructor(t,e,i){return this.existsOnPage=!1,this.aspectRatio=t.aspectRatio,this.filename=t.filename,this.index=e,this.pig=i,this.classNames={figure:`${i.settings.classPrefix}-figure`,thumbnail:`${i.settings.classPrefix}-thumbnail`,loaded:`${i.settings.classPrefix}-loaded`},this}load(){this.existsOnPage=!0,this._updateStyles(),this.pig.container.appendChild(this.getElement()),setTimeout(()=>{!this.existsOnPage||this.addAllSubElements()},100)}hide(){this.getElement()&&this.removeAllSubElements(),this.existsOnPage&&this.pig.container.removeChild(this.getElement()),this.existsOnPage=!1}getElement(){return this.element||(this.element=document.createElement(this.pig.settings.figureTagName),this.element.className=this.classNames.figure,this.pig.settings.onClickHandler!==null&&this.element.addEventListener("click",()=>{this.pig.settings.onClickHandler(this.filename)}),this._updateStyles()),this.element}addImageAsSubElement(t,e,i,n=""){let s=this[t];s||(this[t]=new Image,s=this[t],s.src=this.pig.settings.urlForSize(e,i),n.length>0&&(s.className=n),s.onload=()=>{s&&(s.className+=` ${this.classNames.loaded}`)},this.getElement().appendChild(s))}addAllSubElements(){this.addImageAsSubElement("thumbnail",this.filename,this.pig.settings.thumbnailSize,this.classNames.thumbnail),this.addImageAsSubElement("fullImage",this.filename,this.pig.settings.getImageSize(this.pig.lastWindowWidth))}removeSubElement(t){const e=this[t];e&&(e.src="",this.getElement().removeChild(e),delete this[t])}removeAllSubElements(){this.removeSubElement("thumbnail"),this.removeSubElement("fullImage")}_updateStyles(){this.getElement().style.transition=this.style.transition,this.getElement().style.width=`${this.style.width}px`,this.getElement().style.height=`${this.style.height}px`,this.getElement().style.transform=`translate3d(${this.style.translateX}px, ${this.style.translateY}px, 0)`}},d=class{constructor(){this._callbacks=[],this._running=!1}add(t){this._callbacks.length||window.addEventListener("resize",this._resize.bind(this)),this._callbacks.push(t)}disable(){window.removeEventListener("resize",this._resize.bind(this))}reEnable(){window.addEventListener("resize",this._resize.bind(this))}_resize(){this._running||(this._running=!0,window.requestAnimationFrame?window.requestAnimationFrame(this._runCallbacks.bind(this)):setTimeout(this._runCallbacks.bind(this),66))}_runCallbacks(){this._callbacks.forEach(t=>{t()}),this._running=!1}},m=class{constructor(){this.containerId="pig",this.scroller=window,this.classPrefix="pig",this.figureTagName="figure",this.spaceBetweenImages=8,this.transitionSpeed=500,this.primaryImageBufferHeight=1e3,this.secondaryImageBufferHeight=300,this.thumbnailSize=20,this.onClickHandler=null}urlForSize(t,e){return`/img/${e.toString(10)}/${t}`}getMinAspectRatio(t){return t<=640?2:t<=1280?4:t<=1920?5:6}getImageSize(t){return t<=640?100:t<=1920?250:500}createProgressiveImage(t,e,i){return new c(t,e,i)}},u=class{constructor(t,e){return this._optimizedResize=new d,this.inRAF=!1,this.isTransitioning=!1,this.minAspectRatioRequiresTransition=!1,this.minAspectRatio=null,this.latestYOffset=0,this.lastWindowWidth=window.innerWidth,this.scrollDirection="down",this.visibleImages=[],this.settings=Object.assign(new m,e),this.container=document.getElementById(this.settings.containerId),this.container||console.error(`Could not find element with ID ${this.settings.containerId}`),this.scroller=this.settings.scroller,this.images=this._parseImageData(t),this._injectStyle(this.settings.containerId,this.settings.classPrefix,this.settings.transitionSpeed),this}enable(){return this.onScroll=this._getOnScroll(),this.scroller.addEventListener("scroll",this.onScroll),this.onScroll(),this._computeLayout(),this._doLayout(),this._optimizedResize.add(()=>{this.lastWindowWidth=this.scroller===window?window.innerWidth:this.scroller.offsetWidth,this._computeLayout(),this._doLayout()}),this}disable(){return this.scroller.removeEventListener("scroll",this.onScroll),this._optimizedResize.disable(),this}_parseImageData(t){const e=[];return t.forEach((i,n)=>{const s=this.settings.createProgressiveImage(i,n,this);e.push(s)}),e}_getOffsetTop(t){let e=0;do Number.isNaN(t.offsetTop)||(e+=t.offsetTop),t=t.offsetParent;while(t);return e}_injectStyle(t,e,i){const n=`#${t} { position: relative;}.${e}-figure { background-color: #D5D5D5; overflow: hidden; left: 0; position: absolute; top: 0; margin: 0;}.${e}-figure img { left: 0; position: absolute; top: 0; height: 100%; width: 100%; opacity: 0; transition: ${(i/1e3).toString(10)}s ease opacity; -webkit-transition: ${(i/1e3).toString(10)}s ease opacity;}.${e}-figure img.${e}-thumbnail { -webkit-filter: blur(30px); filter: blur(30px); left: auto; position: relative; width: auto;}.${e}-figure img.${e}-loaded { opacity: 1;}`,s=document.head||document.getElementsByTagName("head")[0],r=document.createElement("style");r.appendChild(document.createTextNode(n)),s.appendChild(r)}_getTransitionTimeout(){const t=1.5;return this.settings.transitionSpeed*t}_getTransitionString(){return this.isTransitioning?`${(this.settings.transitionSpeed/1e3).toString(10)}s transform ease`:"none"}_recomputeMinAspectRatio(){const t=this.minAspectRatio;this.minAspectRatio=this.settings.getMinAspectRatio(this.lastWindowWidth),t!==null&&t!==this.minAspectRatio?this.minAspectRatioRequiresTransition=!0:this.minAspectRatioRequiresTransition=!1}_computeLayout(){const t=parseInt(this.container.clientWidth,10);let e=[],i=0,n=0,s=0;this._recomputeMinAspectRatio(),!this.isTransitioning&&this.minAspectRatioRequiresTransition&&(this.isTransitioning=!0,setTimeout(()=>{this.isTransitioning=!1},this._getTransitionTimeout()));const r=this._getTransitionString();[].forEach.call(this.images,(a,g)=>{if(s+=parseFloat(a.aspectRatio),e.push(a),s>=this.minAspectRatio||g+1===this.images.length){s=Math.max(s,this.minAspectRatio);const o=(t-this.settings.spaceBetweenImages*(e.length-1))/s;e.forEach(l=>{const h=o*l.aspectRatio;l.style={width:parseInt(h,10),height:parseInt(o,10),translateX:i,translateY:n,transition:r},i+=h+this.settings.spaceBetweenImages}),e=[],s=0,n+=parseInt(o,10)+this.settings.spaceBetweenImages,i=0}}),this.totalHeight=n-this.settings.spaceBetweenImages}_doLayout(){this.container.style.height=`${this.totalHeight}px`;const t=this.scrollDirection==="up"?this.settings.primaryImageBufferHeight:this.settings.secondaryImageBufferHeight,e=this.scrollDirection==="down"?this.settings.secondaryImageBufferHeight:this.settings.primaryImageBufferHeight,i=this._getOffsetTop(this.container),n=this.scroller===window?window.innerHeight:this.scroller.offsetHeight,s=this.latestYOffset-i-t,r=this.latestYOffset-i+n+e;this.images.forEach(a=>{a.style.translateY+a.style.heightr?a.hide():a.load()})}_getOnScroll(){const t=this;return()=>{const i=t.scroller===window?window.pageYOffset:t.scroller.scrollTop;t.previousYOffset=t.latestYOffset||i,t.latestYOffset=i,t.scrollDirection=t.latestYOffset>t.previousYOffset?"down":"up",t.inRAF||(t.inRAF=!0,window.requestAnimationFrame(()=>{t._doLayout(),t.inRAF=!1}))}}};export{u as Pig,c as ProgressiveImage}; diff --git a/dist/package.json b/dist/package.json new file mode 100644 index 0000000..4aceee3 --- /dev/null +++ b/dist/package.json @@ -0,0 +1,18 @@ +{ + "name": "pig.js", + "version": "0.2.1", + "description": "Arrange images in a responsive, progressive-loading grid managed in JavaScript using CSS transforms.", + "repository": "https://github.com/schlosser/pig.js", + "devDependencies": { + "esbuild": "^0.13.5", + "eslint": "^7.32.0", + "eslint-config-airbnb-base": "^14.2.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-jsdoc": "^36.1.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-security": "^1.4.0", + "eslint-plugin-security-node": "^1.0.14" + }, + "author": "Dan Schlosser", + "license": "MIT" +} diff --git a/dist/pig.js b/dist/pig.js new file mode 100644 index 0000000..44ad679 --- /dev/null +++ b/dist/pig.js @@ -0,0 +1,337 @@ +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory()); + } else if (typeof module === 'object' && module.exports) { + // CommonJS. + module.exports = factory(); + } else { + // Browser globals (root is window) + root.Pig = factory().Pig; + root.ProgressiveImage = factory().ProgressiveImage; +} +}(typeof self !== 'undefined' ? self : this, function() { +// src/pig.js +var ProgressiveImage = class { + constructor(singleImageData, index, pig) { + this.existsOnPage = false; + this.aspectRatio = singleImageData.aspectRatio; + this.filename = singleImageData.filename; + this.index = index; + this.pig = pig; + this.classNames = { + figure: `${pig.settings.classPrefix}-figure`, + thumbnail: `${pig.settings.classPrefix}-thumbnail`, + loaded: `${pig.settings.classPrefix}-loaded` + }; + return this; + } + load() { + this.existsOnPage = true; + this._updateStyles(); + this.pig.container.appendChild(this.getElement()); + setTimeout(() => { + if (!this.existsOnPage) { + return; + } + this.addAllSubElements(); + }, 100); + } + hide() { + if (this.getElement()) { + this.removeAllSubElements(); + } + if (this.existsOnPage) { + this.pig.container.removeChild(this.getElement()); + } + this.existsOnPage = false; + } + getElement() { + if (!this.element) { + this.element = document.createElement(this.pig.settings.figureTagName); + this.element.className = this.classNames.figure; + if (this.pig.settings.onClickHandler !== null) { + this.element.addEventListener("click", () => { + this.pig.settings.onClickHandler(this.filename); + }); + } + this._updateStyles(); + } + return this.element; + } + addImageAsSubElement(subElementName, filename, size, classname = "") { + let subElement = this[subElementName]; + if (!subElement) { + this[subElementName] = new Image(); + subElement = this[subElementName]; + subElement.src = this.pig.settings.urlForSize(filename, size); + if (classname.length > 0) { + subElement.className = classname; + } + subElement.onload = () => { + if (subElement) { + subElement.className += ` ${this.classNames.loaded}`; + } + }; + this.getElement().appendChild(subElement); + } + } + addAllSubElements() { + this.addImageAsSubElement("thumbnail", this.filename, this.pig.settings.thumbnailSize, this.classNames.thumbnail); + this.addImageAsSubElement("fullImage", this.filename, this.pig.settings.getImageSize(this.pig.lastWindowWidth)); + } + removeSubElement(subElementName) { + const subElement = this[subElementName]; + if (subElement) { + subElement.src = ""; + this.getElement().removeChild(subElement); + delete this[subElementName]; + } + } + removeAllSubElements() { + this.removeSubElement("thumbnail"); + this.removeSubElement("fullImage"); + } + _updateStyles() { + this.getElement().style.transition = this.style.transition; + this.getElement().style.width = `${this.style.width}px`; + this.getElement().style.height = `${this.style.height}px`; + this.getElement().style.transform = `translate3d(${this.style.translateX}px, ${this.style.translateY}px, 0)`; + } +}; +var OptimizedResize = class { + constructor() { + this._callbacks = []; + this._running = false; + } + add(callback) { + if (!this._callbacks.length) { + window.addEventListener("resize", this._resize.bind(this)); + } + this._callbacks.push(callback); + } + disable() { + window.removeEventListener("resize", this._resize.bind(this)); + } + reEnable() { + window.addEventListener("resize", this._resize.bind(this)); + } + _resize() { + if (!this._running) { + this._running = true; + if (window.requestAnimationFrame) { + window.requestAnimationFrame(this._runCallbacks.bind(this)); + } else { + setTimeout(this._runCallbacks.bind(this), 66); + } + } + } + _runCallbacks() { + this._callbacks.forEach((callback) => { + callback(); + }); + this._running = false; + } +}; +var PigSettings = class { + constructor() { + this.containerId = "pig"; + this.scroller = window; + this.classPrefix = "pig"; + this.figureTagName = "figure"; + this.spaceBetweenImages = 8; + this.transitionSpeed = 500; + this.primaryImageBufferHeight = 1e3; + this.secondaryImageBufferHeight = 300; + this.thumbnailSize = 20; + this.onClickHandler = null; + } + urlForSize(filename, size) { + return `/img/${size.toString(10)}/${filename}`; + } + getMinAspectRatio(lastWindowWidth) { + if (lastWindowWidth <= 640) { + return 2; + } else if (lastWindowWidth <= 1280) { + return 4; + } else if (lastWindowWidth <= 1920) { + return 5; + } + return 6; + } + getImageSize(lastWindowWidth) { + if (lastWindowWidth <= 640) { + return 100; + } else if (lastWindowWidth <= 1920) { + return 250; + } + return 500; + } + createProgressiveImage(singleImageData, index, pig) { + return new ProgressiveImage(singleImageData, index, pig); + } +}; +var Pig = class { + constructor(imageData, options) { + this._optimizedResize = new OptimizedResize(); + this.inRAF = false; + this.isTransitioning = false; + this.minAspectRatioRequiresTransition = false; + this.minAspectRatio = null; + this.latestYOffset = 0; + this.lastWindowWidth = window.innerWidth; + this.scrollDirection = "down"; + this.visibleImages = []; + this.settings = Object.assign(new PigSettings(), options); + this.container = document.getElementById(this.settings.containerId); + if (!this.container) { + console.error(`Could not find element with ID ${this.settings.containerId}`); + } + this.scroller = this.settings.scroller; + this.images = this._parseImageData(imageData); + this._injectStyle(this.settings.containerId, this.settings.classPrefix, this.settings.transitionSpeed); + return this; + } + enable() { + this.onScroll = this._getOnScroll(); + this.scroller.addEventListener("scroll", this.onScroll); + this.onScroll(); + this._computeLayout(); + this._doLayout(); + this._optimizedResize.add(() => { + this.lastWindowWidth = this.scroller === window ? window.innerWidth : this.scroller.offsetWidth; + this._computeLayout(); + this._doLayout(); + }); + return this; + } + disable() { + this.scroller.removeEventListener("scroll", this.onScroll); + this._optimizedResize.disable(); + return this; + } + _parseImageData(imageData) { + const progressiveImages = []; + imageData.forEach((image, index) => { + const progressiveImage = this.settings.createProgressiveImage(image, index, this); + progressiveImages.push(progressiveImage); + }); + return progressiveImages; + } + _getOffsetTop(elem) { + let offsetTop = 0; + do { + if (!Number.isNaN(elem.offsetTop)) { + offsetTop += elem.offsetTop; + } + elem = elem.offsetParent; + } while (elem); + return offsetTop; + } + _injectStyle(containerId, classPrefix, transitionSpeed) { + const css = `#${containerId} { position: relative;}.${classPrefix}-figure { background-color: #D5D5D5; overflow: hidden; left: 0; position: absolute; top: 0; margin: 0;}.${classPrefix}-figure img { left: 0; position: absolute; top: 0; height: 100%; width: 100%; opacity: 0; transition: ${(transitionSpeed / 1e3).toString(10)}s ease opacity; -webkit-transition: ${(transitionSpeed / 1e3).toString(10)}s ease opacity;}.${classPrefix}-figure img.${classPrefix}-thumbnail { -webkit-filter: blur(30px); filter: blur(30px); left: auto; position: relative; width: auto;}.${classPrefix}-figure img.${classPrefix}-loaded { opacity: 1;}`; + const head = document.head || document.getElementsByTagName("head")[0]; + const style = document.createElement("style"); + style.appendChild(document.createTextNode(css)); + head.appendChild(style); + } + _getTransitionTimeout() { + const transitionTimeoutScaleFactor = 1.5; + return this.settings.transitionSpeed * transitionTimeoutScaleFactor; + } + _getTransitionString() { + if (this.isTransitioning) { + return `${(this.settings.transitionSpeed / 1e3).toString(10)}s transform ease`; + } + return "none"; + } + _recomputeMinAspectRatio() { + const oldMinAspectRatio = this.minAspectRatio; + this.minAspectRatio = this.settings.getMinAspectRatio(this.lastWindowWidth); + if (oldMinAspectRatio !== null && oldMinAspectRatio !== this.minAspectRatio) { + this.minAspectRatioRequiresTransition = true; + } else { + this.minAspectRatioRequiresTransition = false; + } + } + _computeLayout() { + const wrapperWidth = parseInt(this.container.clientWidth, 10); + let row = []; + let translateX = 0; + let translateY = 0; + let rowAspectRatio = 0; + this._recomputeMinAspectRatio(); + if (!this.isTransitioning && this.minAspectRatioRequiresTransition) { + this.isTransitioning = true; + setTimeout(() => { + this.isTransitioning = false; + }, this._getTransitionTimeout()); + } + const transition = this._getTransitionString(); + [].forEach.call(this.images, (image, index) => { + rowAspectRatio += parseFloat(image.aspectRatio); + row.push(image); + if (rowAspectRatio >= this.minAspectRatio || index + 1 === this.images.length) { + rowAspectRatio = Math.max(rowAspectRatio, this.minAspectRatio); + const totalDesiredWidthOfImages = wrapperWidth - this.settings.spaceBetweenImages * (row.length - 1); + const rowHeight = totalDesiredWidthOfImages / rowAspectRatio; + row.forEach((img) => { + const imageWidth = rowHeight * img.aspectRatio; + img.style = { + width: parseInt(imageWidth, 10), + height: parseInt(rowHeight, 10), + translateX, + translateY, + transition + }; + translateX += imageWidth + this.settings.spaceBetweenImages; + }); + row = []; + rowAspectRatio = 0; + translateY += parseInt(rowHeight, 10) + this.settings.spaceBetweenImages; + translateX = 0; + } + }); + this.totalHeight = translateY - this.settings.spaceBetweenImages; + } + _doLayout() { + this.container.style.height = `${this.totalHeight}px`; + const bufferTop = this.scrollDirection === "up" ? this.settings.primaryImageBufferHeight : this.settings.secondaryImageBufferHeight; + const bufferBottom = this.scrollDirection === "down" ? this.settings.secondaryImageBufferHeight : this.settings.primaryImageBufferHeight; + const containerOffset = this._getOffsetTop(this.container); + const scrollerHeight = this.scroller === window ? window.innerHeight : this.scroller.offsetHeight; + const minTranslateYPlusHeight = this.latestYOffset - containerOffset - bufferTop; + const maxTranslateY = this.latestYOffset - containerOffset + scrollerHeight + bufferBottom; + this.images.forEach((image) => { + if (image.style.translateY + image.style.height < minTranslateYPlusHeight || image.style.translateY > maxTranslateY) { + image.hide(); + } else { + image.load(); + } + }); + } + _getOnScroll() { + const that = this; + const onScroll = () => { + const newYOffset = that.scroller === window ? window.pageYOffset : that.scroller.scrollTop; + that.previousYOffset = that.latestYOffset || newYOffset; + that.latestYOffset = newYOffset; + that.scrollDirection = that.latestYOffset > that.previousYOffset ? "down" : "up"; + if (!that.inRAF) { + that.inRAF = true; + window.requestAnimationFrame(() => { + that._doLayout(); + that.inRAF = false; + }); + } + }; + return onScroll; + } +}; + + // Just return an object to define the module export. + return { + Pig: Pig, + ProgressiveImage: ProgressiveImage + }; +})); diff --git a/dist/pig.min.js b/dist/pig.min.js new file mode 100644 index 0000000..f74c6b2 --- /dev/null +++ b/dist/pig.min.js @@ -0,0 +1 @@ +(function(l,a){typeof define=="function"&&define.amd?define([],a()):typeof module=="object"&&module.exports?module.exports=a():(l.Pig=a().Pig,l.ProgressiveImage=a().ProgressiveImage)})(typeof self!="undefined"?self:exports,function(){var l=class{constructor(t,e,i){return this.existsOnPage=!1,this.aspectRatio=t.aspectRatio,this.filename=t.filename,this.index=e,this.pig=i,this.classNames={figure:`${i.settings.classPrefix}-figure`,thumbnail:`${i.settings.classPrefix}-thumbnail`,loaded:`${i.settings.classPrefix}-loaded`},this}load(){this.existsOnPage=!0,this._updateStyles(),this.pig.container.appendChild(this.getElement()),setTimeout(()=>{!this.existsOnPage||this.addAllSubElements()},100)}hide(){this.getElement()&&this.removeAllSubElements(),this.existsOnPage&&this.pig.container.removeChild(this.getElement()),this.existsOnPage=!1}getElement(){return this.element||(this.element=document.createElement(this.pig.settings.figureTagName),this.element.className=this.classNames.figure,this.pig.settings.onClickHandler!==null&&this.element.addEventListener("click",()=>{this.pig.settings.onClickHandler(this.filename)}),this._updateStyles()),this.element}addImageAsSubElement(t,e,i,n=""){let s=this[t];s||(this[t]=new Image,s=this[t],s.src=this.pig.settings.urlForSize(e,i),n.length>0&&(s.className=n),s.onload=()=>{s&&(s.className+=` ${this.classNames.loaded}`)},this.getElement().appendChild(s))}addAllSubElements(){this.addImageAsSubElement("thumbnail",this.filename,this.pig.settings.thumbnailSize,this.classNames.thumbnail),this.addImageAsSubElement("fullImage",this.filename,this.pig.settings.getImageSize(this.pig.lastWindowWidth))}removeSubElement(t){const e=this[t];e&&(e.src="",this.getElement().removeChild(e),delete this[t])}removeAllSubElements(){this.removeSubElement("thumbnail"),this.removeSubElement("fullImage")}_updateStyles(){this.getElement().style.transition=this.style.transition,this.getElement().style.width=`${this.style.width}px`,this.getElement().style.height=`${this.style.height}px`,this.getElement().style.transform=`translate3d(${this.style.translateX}px, ${this.style.translateY}px, 0)`}},a=class{constructor(){this._callbacks=[],this._running=!1}add(t){this._callbacks.length||window.addEventListener("resize",this._resize.bind(this)),this._callbacks.push(t)}disable(){window.removeEventListener("resize",this._resize.bind(this))}reEnable(){window.addEventListener("resize",this._resize.bind(this))}_resize(){this._running||(this._running=!0,window.requestAnimationFrame?window.requestAnimationFrame(this._runCallbacks.bind(this)):setTimeout(this._runCallbacks.bind(this),66))}_runCallbacks(){this._callbacks.forEach(t=>{t()}),this._running=!1}},d=class{constructor(){this.containerId="pig",this.scroller=window,this.classPrefix="pig",this.figureTagName="figure",this.spaceBetweenImages=8,this.transitionSpeed=500,this.primaryImageBufferHeight=1e3,this.secondaryImageBufferHeight=300,this.thumbnailSize=20,this.onClickHandler=null}urlForSize(t,e){return`/img/${e.toString(10)}/${t}`}getMinAspectRatio(t){return t<=640?2:t<=1280?4:t<=1920?5:6}getImageSize(t){return t<=640?100:t<=1920?250:500}createProgressiveImage(t,e,i){return new l(t,e,i)}},m=class{constructor(t,e){return this._optimizedResize=new a,this.inRAF=!1,this.isTransitioning=!1,this.minAspectRatioRequiresTransition=!1,this.minAspectRatio=null,this.latestYOffset=0,this.lastWindowWidth=window.innerWidth,this.scrollDirection="down",this.visibleImages=[],this.settings=Object.assign(new d,e),this.container=document.getElementById(this.settings.containerId),this.container||console.error(`Could not find element with ID ${this.settings.containerId}`),this.scroller=this.settings.scroller,this.images=this._parseImageData(t),this._injectStyle(this.settings.containerId,this.settings.classPrefix,this.settings.transitionSpeed),this}enable(){return this.onScroll=this._getOnScroll(),this.scroller.addEventListener("scroll",this.onScroll),this.onScroll(),this._computeLayout(),this._doLayout(),this._optimizedResize.add(()=>{this.lastWindowWidth=this.scroller===window?window.innerWidth:this.scroller.offsetWidth,this._computeLayout(),this._doLayout()}),this}disable(){return this.scroller.removeEventListener("scroll",this.onScroll),this._optimizedResize.disable(),this}_parseImageData(t){const e=[];return t.forEach((i,n)=>{const s=this.settings.createProgressiveImage(i,n,this);e.push(s)}),e}_getOffsetTop(t){let e=0;do Number.isNaN(t.offsetTop)||(e+=t.offsetTop),t=t.offsetParent;while(t);return e}_injectStyle(t,e,i){const n=`#${t} { position: relative;}.${e}-figure { background-color: #D5D5D5; overflow: hidden; left: 0; position: absolute; top: 0; margin: 0;}.${e}-figure img { left: 0; position: absolute; top: 0; height: 100%; width: 100%; opacity: 0; transition: ${(i/1e3).toString(10)}s ease opacity; -webkit-transition: ${(i/1e3).toString(10)}s ease opacity;}.${e}-figure img.${e}-thumbnail { -webkit-filter: blur(30px); filter: blur(30px); left: auto; position: relative; width: auto;}.${e}-figure img.${e}-loaded { opacity: 1;}`,s=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.appendChild(document.createTextNode(n)),s.appendChild(o)}_getTransitionTimeout(){const t=1.5;return this.settings.transitionSpeed*t}_getTransitionString(){return this.isTransitioning?`${(this.settings.transitionSpeed/1e3).toString(10)}s transform ease`:"none"}_recomputeMinAspectRatio(){const t=this.minAspectRatio;this.minAspectRatio=this.settings.getMinAspectRatio(this.lastWindowWidth),t!==null&&t!==this.minAspectRatio?this.minAspectRatioRequiresTransition=!0:this.minAspectRatioRequiresTransition=!1}_computeLayout(){const t=parseInt(this.container.clientWidth,10);let e=[],i=0,n=0,s=0;this._recomputeMinAspectRatio(),!this.isTransitioning&&this.minAspectRatioRequiresTransition&&(this.isTransitioning=!0,setTimeout(()=>{this.isTransitioning=!1},this._getTransitionTimeout()));const o=this._getTransitionString();[].forEach.call(this.images,(r,u)=>{if(s+=parseFloat(r.aspectRatio),e.push(r),s>=this.minAspectRatio||u+1===this.images.length){s=Math.max(s,this.minAspectRatio);const h=(t-this.settings.spaceBetweenImages*(e.length-1))/s;e.forEach(g=>{const c=h*g.aspectRatio;g.style={width:parseInt(c,10),height:parseInt(h,10),translateX:i,translateY:n,transition:o},i+=c+this.settings.spaceBetweenImages}),e=[],s=0,n+=parseInt(h,10)+this.settings.spaceBetweenImages,i=0}}),this.totalHeight=n-this.settings.spaceBetweenImages}_doLayout(){this.container.style.height=`${this.totalHeight}px`;const t=this.scrollDirection==="up"?this.settings.primaryImageBufferHeight:this.settings.secondaryImageBufferHeight,e=this.scrollDirection==="down"?this.settings.secondaryImageBufferHeight:this.settings.primaryImageBufferHeight,i=this._getOffsetTop(this.container),n=this.scroller===window?window.innerHeight:this.scroller.offsetHeight,s=this.latestYOffset-i-t,o=this.latestYOffset-i+n+e;this.images.forEach(r=>{r.style.translateY+r.style.heighto?r.hide():r.load()})}_getOnScroll(){const t=this;return()=>{const i=t.scroller===window?window.pageYOffset:t.scroller.scrollTop;t.previousYOffset=t.latestYOffset||i,t.latestYOffset=i,t.scrollDirection=t.latestYOffset>t.previousYOffset?"down":"up",t.inRAF||(t.inRAF=!0,window.requestAnimationFrame(()=>{t._doLayout(),t.inRAF=!1}))}}};return{Pig:m,ProgressiveImage:l}}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2d529ad --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1840 @@ +{ + "name": "pig.js", + "version": "0.2.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + } + } + }, + "@es-joy/jsdoccomment": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.10.8.tgz", + "integrity": "sha512-3P1JiGL4xaR9PoTKUHa2N/LKwa2/eUdRqGwijMWWgBqbFEqJUVpmaOi2TcjcemrsRMgFLBzQCK4ToPhrSVDiFQ==", + "dev": true, + "requires": { + "comment-parser": "1.2.4", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "1.1.1" + }, + "dependencies": { + "jsdoc-type-pratt-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-1.1.1.tgz", + "integrity": "sha512-uelRmpghNwPBuZScwgBG/OzodaFk5RbO5xaivBdsAY70icWfShwZ7PCMO0x1zSkOa8T1FzHThmrdoyg/0AwV5g==", + "dev": true + } + } + }, + "@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-includes": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, + "array.prototype.flat": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "comment-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.2.4.tgz", + "integrity": "sha512-pm0b+qv+CkWNriSTMsfnjChF9kH0kxz55y44Wo5le9qLxMj5xDQAaEd9ZN1ovSuk9CsrncWaFwgpOMg7ClJwkw==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "confusing-browser-globals": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", + "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "es-abstract": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "esbuild": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.5.tgz", + "integrity": "sha512-Q9/f1njsZaO+Qqe3dqAdtu4zGHNZIbcEtdg44/NooyPhqCerns4FeC1UPYeB4pKD08iDuWcmyINFJTqpdN+pqg==", + "dev": true, + "requires": { + "esbuild-android-arm64": "0.13.5", + "esbuild-darwin-64": "0.13.5", + "esbuild-darwin-arm64": "0.13.5", + "esbuild-freebsd-64": "0.13.5", + "esbuild-freebsd-arm64": "0.13.5", + "esbuild-linux-32": "0.13.5", + "esbuild-linux-64": "0.13.5", + "esbuild-linux-arm": "0.13.5", + "esbuild-linux-arm64": "0.13.5", + "esbuild-linux-mips64le": "0.13.5", + "esbuild-linux-ppc64le": "0.13.5", + "esbuild-openbsd-64": "0.13.5", + "esbuild-sunos-64": "0.13.5", + "esbuild-windows-32": "0.13.5", + "esbuild-windows-64": "0.13.5", + "esbuild-windows-arm64": "0.13.5" + } + }, + "esbuild-android-arm64": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.5.tgz", + "integrity": "sha512-xaNH58b9XRAWT5q0rwA2GNTgJynb51JhdotlNKdLmSCyKXPVlF87yqNLNdmlX/zndzRDrZdtpCWSALdn/J63Ug==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.5.tgz", + "integrity": "sha512-ClGQeUObXIxEpZviGzjTinDikXy9XodojP9jLKwqLCBpZ9wdV3MW7JOmw60fgXgnbNRvkZCqM6uEi+ur8p80Ow==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.5.tgz", + "integrity": "sha512-qro6M/qzs1dBPh14Ca+5moIkLo2KE3ll3dOpiN7aAususkM1HmqQptCEchi0XwX+6nfqWI96YvVqPJ3DfUUK5A==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.5.tgz", + "integrity": "sha512-vklf7L7fghREEvS1sjAFcxcw/Qqt+Z+L0ySN+pEeb7rA8nPLfRBSFdXAru8UNuHsMWns6CrcZ5eDOKTerZZ5ng==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.5.tgz", + "integrity": "sha512-kJoouhbZt4QvjiPak7/Lz57Azok0CgFnNtixiOsqEQXTabIaKmMmnq4qgjD6EBFeU/hvSXDrPe6U8dWhBZOrWQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.5.tgz", + "integrity": "sha512-/QufG6tTGKAf42pIYkOVZzKBPxF01xH1kCPyOFJZukZBV/Tk3TeOZfhJIAf7pxl4jhfa+c4Jcdp7CvIAjXrmJg==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.5.tgz", + "integrity": "sha512-NmNFMXEthuFJTFaD4cLhAHCxg+y3uXzo7nqH/WNNSZ8PPY11jbeOvMbdArYlbo2Wy1N/mTHXMcK1synSJj+4Iw==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.5.tgz", + "integrity": "sha512-69nQmbKLBRaAxf88diyaOyarrI7yIdBkZ8bmVzQ7XVWneY+nYIcGtugTSOs5znNGfPqGOElAjh1lX+0sGYHNpA==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.5.tgz", + "integrity": "sha512-dOS5EZsZj8Lw0TgEj3zy1/slTBbfBw4v7uHEqZXP34dUaRq2oltNaUYIj735CtgB7I5/MXrXEUYkXLqcVfzJQQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.5.tgz", + "integrity": "sha512-dmKA8ZI/nHwpxIQW/L5crk7Ac4wJJ2Kquvdo1CdXPW1UljMyKUDuHc4K7D1Iws5igqJmNO6U5vdRUKrdnIov6Q==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.5.tgz", + "integrity": "sha512-HkVGKkPL3XOhJqNOJ752Q1li5zeidrJHv+XWX6qCnCipNsVuGqaAGfxeWbL/+A/giolMlP7wvAuiKgoe+a5UAw==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.5.tgz", + "integrity": "sha512-BuOZzmdsdreSs0qDgbuiEhSbUDDW2Wyp4VtpNGBmaLwPMHftdprOJXLkeFud3HlnRB2n9qdiTVKg1B8YqMogSw==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.5.tgz", + "integrity": "sha512-YJNB6Og1QYAPikvYDbqvk5xCqr6WL2i5cRWPGGgWOEItQPnq6gFsWogS3DiYM8TQKe50KRiD3Lwu7eNYsdPO4w==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.5.tgz", + "integrity": "sha512-CigOlBSKsZ61IS+FyhD3luqCpl7LN9ntDaBZXumls/0IZ/8BJ5txqw4a6pv4LtnfIgt0ixGHSH7kAUmApw/HAw==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.5.tgz", + "integrity": "sha512-pg2BZXLpcPcrIcmToGapLRExzj6sm0VmQlqlmnMOtIJh0YQV9c0CRbhfIT0gYvJqCz5JEGiRvYpArRlxWADN3Q==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.5.tgz", + "integrity": "sha512-KKRDmUOIE4oCvJp0I4p4QyazK2X79spF29vsZr2U8qHhmxbTLSQWvYmb2WlF5Clb1URRsX0L013rhwHx1SEu0w==", + "dev": true, + "optional": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-config-airbnb-base": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz", + "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.2" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.0.tgz", + "integrity": "sha512-hqSE88MmHl3ru9SYvDyGrlo0JwROlf9fiEMplEV7j/EAuq9iSlIlyCFbBT6pdULQBSnBYtYKiMLps+hKkyP7Gg==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "find-up": "^2.1.0", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + } + }, + "eslint-plugin-import": { + "version": "2.25.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.2.tgz", + "integrity": "sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.0", + "has": "^1.0.3", + "is-core-module": "^2.7.0", + "is-glob": "^4.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.5", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.11.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-jsdoc": { + "version": "36.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-36.1.1.tgz", + "integrity": "sha512-nuLDvH1EJaKx0PCa9oeQIxH6pACIhZd1gkalTUxZbaxxwokjs7TplqY0Q8Ew3CoZaf5aowm0g/Z3JGHCatt+gQ==", + "dev": true, + "requires": { + "@es-joy/jsdoccomment": "0.10.8", + "comment-parser": "1.2.4", + "debug": "^4.3.2", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "^1.1.1", + "lodash": "^4.17.21", + "regextras": "^0.8.0", + "semver": "^7.3.5", + "spdx-expression-parse": "^3.0.1" + } + }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-security": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.4.0.tgz", + "integrity": "sha512-xlS7P2PLMXeqfhyf3NpqbvbnW04kN8M9NtmhpR3XGyOvt/vNKS7XPXT5EDbwKW9vCjWH4PpfQvgD/+JgN0VJKA==", + "dev": true, + "requires": { + "safe-regex": "^1.1.0" + } + }, + "eslint-plugin-security-node": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/eslint-plugin-security-node/-/eslint-plugin-security-node-1.0.14.tgz", + "integrity": "sha512-PVN8vGDzXHiQJcGLB6Db/yqC2o43ZnTWgckjSiZKRu0jqpyFPCkT4tZPJ1FPqPDQsdgHHD//f18EOb7yw+N/Ig==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", + "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, + "is-core-module": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", + "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-weakref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", + "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsdoc-type-pratt-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-1.2.0.tgz", + "integrity": "sha512-4STjeF14jp4bqha44nKMY1OUI6d2/g6uclHWUCZ7B4DoLzaB5bmpTkQrpqU+vSVzMD0LsKAOskcnI3I3VfIpmg==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "regextras": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.8.0.tgz", + "integrity": "sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", + "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.2.tgz", + "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "tsconfig-paths": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", + "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 74eb6df..b501501 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,23 @@ "description": "Arrange images in a responsive, progressive-loading grid managed in JavaScript using CSS transforms.", "repository": "https://github.com/schlosser/pig.js", "devDependencies": { - "gulp": "^4.0.2", - "gulp-jscs": "^4.1.0", - "gulp-jshint": "^2.1.0", - "gulp-jsmin": "^0.1.5", - "gulp-minify": "3.1.0", - "gulp-plumber": "^1.2.1", - "gulp-rename": "^1.4.0", - "gulp-uglify": "^3.0.2", - "jshint": "^2.10.2" + "esbuild": "^0.13.5", + "eslint": "^7.32.0", + "eslint-config-airbnb-base": "^14.2.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-jsdoc": "^36.1.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-security": "^1.4.0", + "eslint-plugin-security-node": "^1.0.14" }, + "type": "module", "author": "Dan Schlosser", - "license": "MIT" + "license": "MIT", + "scripts": { + "lint": ".\\node_modules\\.bin\\eslint -c .eslintrc.cjs src/**/*.js", + "build": "npm run build:esm && npm run build:umd", + "build:esm": "node scripts/build/build.targets.cjs esm", + "build:umd": "node scripts/build/build.targets.cjs umd", + "version": "node ./scripts/copy-packagejson-to-dist.cjs" + } } diff --git a/scripts/build/build.targets.cjs b/scripts/build/build.targets.cjs new file mode 100644 index 0000000..ba5fd7c --- /dev/null +++ b/scripts/build/build.targets.cjs @@ -0,0 +1,172 @@ +const fs = require('fs'); +const path = require('path'); +const esbuild = require('esbuild'); + +const builds = { + 'esm-bundle': { + entryPoints: ['src/pig.js'], + bundle: true, + target: 'es2020', + platform: 'browser', + format: 'esm', + metafile: false, + write: false, + outfile: 'dist/esm/pig.js' + }, + 'minify-esm': { + entryPoints: ['dist/esm/pig.js'], + bundle: false, + target: 'es2020', + platform: 'browser', + format: 'esm', + metafile: false, + write: false, + minify: true, + outfile: 'dist/esm/pig.min.js' + }, + 'umd-bundle': { + entryPoints: ['scripts/build/pig.source.umd.js'], + bundle: true, + target: 'es2020', + platform: 'neutral', + format: 'cjs', + banner: { + js: `(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory()); + } else if (typeof module === 'object' && module.exports) { + // CommonJS. + module.exports = factory(); + } else { + // Browser globals (root is window) + root.Pig = factory().Pig; + root.ProgressiveImage = factory().ProgressiveImage; +} +}(typeof self !== 'undefined' ? self : this, function() {` + }, + footer: { + js: ` + // Just return an object to define the module export. + return { + Pig: Pig, + ProgressiveImage: ProgressiveImage + }; +}));` + }, + metafile: false, + write: false, + outfile: 'dist/pig.js' + }, + 'minify-umd': { + entryPoints: ['dist/pig.js'], + bundle: false, + target: 'es2020', + platform: 'neutral', + format: 'cjs', + metafile: false, + write: false, + minify: true, + outfile: 'dist/pig.min.js' + } +}; + +/** + * Build target files in /dist directory. + * Call on cli like this: + * 'node scripts/build/build.targets esm' + * Calling without option will throw. + * + * Cli param {string} Type of output to create ('umd' or 'esm'). + */ +(async () => { + const outputType = process.argv[2]; + switch (outputType) { + case 'umd': + await buildStep('umd-bundle'); + await buildStep('minify-umd'); + break; + case 'esm': + await buildStep('esm-bundle'); + await buildStep('minify-esm'); + break; + default: + console.error(`the configuration '${outputType}' does not exist`); + process.exit(1); + } +})(); + +async function buildStep(configName) { + const config = builds[configName]; + if (config === undefined) { + console.error(`the configuration '${configName}' does not exist`); + process.exit(1); + } + + const result = await esbuild.build(config) + .catch(() => process.exit(1)); + + if (config.metafile) { + fs.mkdirSync('artifacts', { recursive: true }); + fs.writeFileSync('artifacts/build-results.json', JSON.stringify(result.metafile)); + } + + if (config.write === false) { + if (result.outputFiles !== undefined) { + if (result.outputFiles.length === 1) { + const contentRows = result.outputFiles[0].text.split('\n'); + + // Remove unnecessary parts of generated code + let rowsToRemove = []; + if (configName === 'umd-bundle') { + rowsToRemove = umdRemoveUnnecessaryRows(contentRows); + } else if (configName === 'esm-bundle') { + rowsToRemove = esmRemoveUnnecessaryRows(contentRows); + } + + // Remove rows needed to trigger output generation + let filteredContent = result.outputFiles[0].text; + if (rowsToRemove.length > 0) { + for (let i = 0; i < rowsToRemove.length; i++) { + contentRows.splice(rowsToRemove[i].start, rowsToRemove[i].length); + } + } + + // write output to file + filteredContent = contentRows.join('\n'); + const outdir = path.dirname(config.outfile); + fs.mkdirSync(outdir, { recursive: true }); + fs.writeFileSync(config.outfile, filteredContent); + } else { + console.log('too many file generated for umd bundle'); + } + } + } +} + +function umdRemoveUnnecessaryRows(contentRows) { + const rowsToRemove = []; + for (let i = contentRows.length - 1; i >= 0; i--) { + if (contentRows[i] === '// scripts/build/pig.source.umd.js') { + // remove dummy rows from 'entrypoint', required by esbuild to generate a result + rowsToRemove.push({ start: i, length: 3 }); + } + } + return rowsToRemove; +} + +function esmRemoveUnnecessaryRows(contentRows) { + const searchVarForDefault = new RegExp('^var .+_default = .+;$'); + const rowsToRemove = []; + for (let i = contentRows.length - 1; i >= 0; i--) { + // remove dummy entry required by source to generate a result + if (searchVarForDefault.test(contentRows[i])) { + rowsToRemove.push({ start: i, length: 1 }); + } else { + contentRows[i] = contentRows[i].replace('progressive_image_default', 'ProgressiveImage'); + contentRows[i] = contentRows[i].replace('optimized_resize_default', 'OptimizedResize'); + contentRows[i] = contentRows[i].replace('pig_settings_default', 'PigSettings'); + } + } + return rowsToRemove; +} diff --git a/scripts/build/pig.source.esm.js b/scripts/build/pig.source.esm.js new file mode 100644 index 0000000..1c69949 --- /dev/null +++ b/scripts/build/pig.source.esm.js @@ -0,0 +1,3 @@ +import { Pig, ProgressiveImage } from '../../src/pig'; + +export { Pig, ProgressiveImage }; diff --git a/scripts/build/pig.source.umd.js b/scripts/build/pig.source.umd.js new file mode 100644 index 0000000..9eea9e1 --- /dev/null +++ b/scripts/build/pig.source.umd.js @@ -0,0 +1,3 @@ +import { Pig, ProgressiveImage } from '../../src/pig'; + +const pigEmptyExample = new Pig([], {}); diff --git a/scripts/copy-packagejson-to-dist.cjs b/scripts/copy-packagejson-to-dist.cjs new file mode 100644 index 0000000..8d3db1e --- /dev/null +++ b/scripts/copy-packagejson-to-dist.cjs @@ -0,0 +1,67 @@ +/** + * Copy package.json to dist folders. + * This way we can publish /dist to npm and use it as a + * package. + * Called in npm 'version' script, when version is changed + * e.g. by 'npm version patch' command. + */ + +const fs = require('fs'); +const exec = require('child_process').exec; + +let package = {}; + +// Get package.json and remove unnecessary elements +try { + let rawdata = fs.readFileSync('package.json', 'utf8'); + package = JSON.parse(rawdata); + delete package['scripts']; +} catch (error) { + console.log('Error reading "package.json"', error); + return; +} + +// Copy package.json for esm variant +try { + const packageForEsm = JSON.stringify(package, null, ' ').replace(/\n/g, '\r\n').concat('\r\n'); + fs.writeFileSync('./dist/esm/package.json', packageForEsm); + console.log('package.json copied to "/dist/esm/"'); +} catch (err) { + console.log('Error saving "package.json" to "/dist/esm/"', err); +} + +// Copy package.json for umd variant +try { + delete package['type']; + const packageForUmd = JSON.stringify(package, null, ' ').replace(/\n/g, '\r\n').concat('\r\n'); + fs.writeFileSync('./dist/package.json', packageForUmd); + console.log('package.json copied to "/dist/"'); +} catch (err) { + console.log('Error saving "package.json" to "/dist/"', err); +} + +// Copy README.md +try { + const readme = fs.readFileSync('README.md', 'utf8'); + fs.writeFileSync('./dist/README.md', readme); + console.log('README.md copied to "/dist/"'); +} catch (err) { + console.log('Error copying "README.md" to "/dist/"', err); +} + +// Commit modified files to git +try { + exec(`"${process.env['npm_config_git']}" add .`, function(err, stdout, stderr){ + if (err) { + console.log('Error executing "git add ."', err); + return; + } else if (typeof(stderr) != 'string') { + console.log(stderr); + } + console.log(stdout); + }); + + console.log('Committed changes to git repository'); +} catch (err) { + console.log('Error committing changed files to GIT', err); +} diff --git a/src/pig.js b/src/pig.js index 57a3113..ce1ad19 100644 --- a/src/pig.js +++ b/src/pig.js @@ -1,180 +1,459 @@ -(function(global) { - 'use strict'; +/** + * This class manages a single image. It keeps track of the image's height, + * width, and position in the grid. An instance of this class is associated + * with a single image figure, which looks like this: + * + *
+ * + * + *
+ * + * However, this element may or may not actually exist in the DOM. The actual + * DOM element may loaded and unloaded depending on where it is with respect + * to the viewport. This class is responsible for managing the DOM elements, + * but does not include logic to determine _when_ the DOM elements should + * be removed. + * + * This class also manages the blur-into-focus load effect. First, the + *
element is inserted into the page. Then, a very small thumbnail + * is loaded, stretched out to the full size of the image. This pixelated + * image is then blurred using CSS filter: blur(). Then, the full image is + * loaded, with opacity:0. Once it has loaded, it is given the `pig-loaded` + * class, and its opacity is set to 1. This creates an effect where there is + * first a blurred version of the image, and then it appears to come into + * focus. + */ +export class ProgressiveImage { + /** + * Creates an instance of ProgressiveImage. + * + * @param {object[]} singleImageData - An array of metadata about each image to + * include in the grid. + * @param {string} singleImageData[].filename - The filename of the image. + * @param {string} singleImageData[].aspectRatio - The aspect ratio of the + * image. + * @param {number} index - Index of image in data source + * @param {object} pig - The Pig instance + * + * @returns {object} The ProgressiveImage instance, for easy chaining with the constructor. + */ + constructor(singleImageData, index, pig) { + // Global State + this.existsOnPage = false; // True if the element exists on the page. + + // Instance information + this.aspectRatio = singleImageData.aspectRatio; // Aspect Ratio + this.filename = singleImageData.filename; // Filename + this.index = index; // The index in the list of images + + // The Pig instance + this.pig = pig; + + this.classNames = { + figure: `${pig.settings.classPrefix}-figure`, + thumbnail: `${pig.settings.classPrefix}-thumbnail`, + loaded: `${pig.settings.classPrefix}-loaded` + }; + + return this; + } /** - * This is a manager for our resize handlers. You can add a callback, disable - * all resize handlers, and re-enable handlers after they have been disabled. + * Load the image element associated with this ProgressiveImage into the DOM. * - * optimizedResize is adapted from Mozilla code: - * https://developer.mozilla.org/en-US/docs/Web/Events/resize + * This function will append the figure into the DOM, create and insert the + * thumbnail, and create and insert the full image. */ - const optimizedResize = (function() { - const callbacks = []; - let running = false; - - // fired on resize event - function resize() { - if (!running) { - running = true; - if (window.requestAnimationFrame) { - window.requestAnimationFrame(runCallbacks); - } else { - setTimeout(runCallbacks, 66); - } + load() { + // Create a new image element, and insert it into the DOM. It doesn't + // matter the order of the figure elements, because all positioning + // is done using transforms. + this.existsOnPage = true; + this._updateStyles(); + this.pig.container.appendChild(this.getElement()); + + // We run the rest of the function in a 100ms setTimeout so that if the + // user is scrolling down the page very fast and hide() is called within + // 100ms of load(), the hide() function will set this.existsOnPage to false + // and we can exit. + setTimeout(() => { + // The image was hidden very quickly after being loaded, so don't bother + // loading it at all. + if (!this.existsOnPage) { + return; } - } - // run the actual callbacks - function runCallbacks() { - callbacks.forEach(function(callback) { - callback(); - }); + this.addAllSubElements(); + }, 100); + } - running = false; + /** + * Removes the figure from the DOM, removes the thumbnail and full image, and + * deletes the this.thumbnail and this.fullImage properties off of the + * ProgressiveImage object. + */ + hide() { + // Remove the images from the element, so that if a user is scrolling super + // fast, we won't try to load every image we scroll past. + if (this.getElement()) { + this.removeAllSubElements(); } - return { - /** - * Add a callback to be run on resize. - * - * @param {function} callback - the callback to run on resize. - */ - add: function(callback) { - if (!callbacks.length) { - window.addEventListener('resize', resize); - } + // Remove the image from the DOM. + if (this.existsOnPage) { + this.pig.container.removeChild(this.getElement()); + } - callbacks.push(callback); - }, - - /** - * Disables all resize handlers. - */ - disable: function() { - window.removeEventListener('resize', resize); - }, - - /** - * Enables all resize handlers, if they were disabled. - */ - reEnable: function() { - window.addEventListener('resize', resize); + this.existsOnPage = false; + } + + /** + * Get the DOM element associated with this ProgressiveImage. We default to + * using this.element, and we create it, if it doesn't exist. + * + * @returns {HTMLElement} The DOM element associated with this instance. + */ + getElement() { + if (!this.element) { + this.element = document.createElement(this.pig.settings.figureTagName); + this.element.className = this.classNames.figure; + if (this.pig.settings.onClickHandler !== null) { + this.element.addEventListener('click', () => { + this.pig.settings.onClickHandler(this.filename); + }); } - }; - }()); + this._updateStyles(); + } + + return this.element; + } /** - * Inject CSS needed to make the grid work in the . + * Add an imgage as a subelement to the
tag. * - * @param {string} containerId - ID of the container for the images. - * @param {string} classPrefix - the prefix associated with this library that - * should be prepended to classnames. - * @param {number} transitionSpeed - animation duration in milliseconds + * @param {string} subElementName - Name of the subelement + * @param {string} filename - ID, used to access the image (e.g. the filename) + * @param {number} size - Size of the image the image (e.g. this.pig.settings.thumbnailSize) + * @param {string} classname - Name of the class to be added to the new subelement + * (default value='' - i.e. no class added) */ - function _injectStyle(containerId, classPrefix, transitionSpeed) { + addImageAsSubElement(subElementName, filename, size, classname = '') { + let subElement = this[subElementName]; + if (!subElement) { + this[subElementName] = new Image(); + subElement = this[subElementName]; + subElement.src = this.pig.settings.urlForSize(filename, size); + if (classname.length > 0) { + subElement.className = classname; + } + subElement.onload = () => { + // We have to make sure thumbnail still exists, we may have already been + // deallocated if the user scrolls too fast. + if (subElement) { + subElement.className += ` ${this.classNames.loaded}`; + } + }; - const css = ( - '#' + containerId + ' {' + - ' position: relative;' + - '}' + - '.' + classPrefix + '-figure {' + - ' background-color: #D5D5D5;' + - ' overflow: hidden;' + - ' left: 0;' + - ' position: absolute;' + - ' top: 0;' + - ' margin: 0;' + - '}' + - '.' + classPrefix + '-figure img {' + - ' left: 0;' + - ' position: absolute;' + - ' top: 0;' + - ' height: 100%;' + - ' width: 100%;' + - ' opacity: 0;' + - ' transition: ' + (transitionSpeed / 1000).toString(10) + 's ease opacity;' + - ' -webkit-transition: ' + (transitionSpeed / 1000).toString(10) + 's ease opacity;' + - '}' + - '.' + classPrefix + '-figure img.' + classPrefix + '-thumbnail {' + - ' -webkit-filter: blur(30px);' + - ' filter: blur(30px);' + - ' left: auto;' + - ' position: relative;' + - ' width: auto;' + - '}' + - '.' + classPrefix + '-figure img.' + classPrefix + '-loaded {' + - ' opacity: 1;' + - '}' - ); + this.getElement().appendChild(subElement); + } + } - const head = document.head || document.getElementsByTagName('head')[0]; - const style = document.createElement('style'); + /** + * Add all subelements of the
tag (default: 'thumbnail' and 'fullImage'). + */ + addAllSubElements() { + // Add thumbnail + this.addImageAsSubElement('thumbnail', this.filename, this.pig.settings.thumbnailSize, this.classNames.thumbnail); - style.type = 'text/css'; - if (style.styleSheet) { - // set style for IE8 and below - style.styleSheet.cssText = css; - } else { - style.appendChild(document.createTextNode(css)); + // Add full image + this.addImageAsSubElement('fullImage', this.filename, this.pig.settings.getImageSize(this.pig.lastWindowWidth)); + } + + /** + * Remove a subelement of the
tag (e.g. an image element). + * + * @param {string} subElementName - SubElement of the
tag - (e.g. 'this.fullImage') + */ + removeSubElement(subElementName) { + const subElement = this[subElementName]; + if (subElement) { + subElement.src = ''; + this.getElement().removeChild(subElement); + delete this[subElementName]; } + } - head.appendChild(style); + /** + * Remove all subelements of the
tag (default: 'thumbnail' and 'fullImage'). + */ + removeAllSubElements() { + this.removeSubElement('thumbnail'); + this.removeSubElement('fullImage'); + } + + /** + * Updates the style attribute to reflect this style property on this object. + */ + _updateStyles() { + this.getElement().style.transition = this.style.transition; + this.getElement().style.width = `${this.style.width}px`; + this.getElement().style.height = `${this.style.height}px`; + this.getElement().style.transform = ( + `translate3d(${this.style.translateX}px, ${this.style.translateY}px, 0)`); + } +} + +/** + * This is a manager for our resize handlers. You can add a callback, disable + * all resize handlers, and re-enable handlers after they have been disabled. + * + * optimizedResize is adapted from Mozilla code: + * https://developer.mozilla.org/en-US/docs/Web/Events/resize + */ +class OptimizedResize { + /** + * Creates an instance of OptimizedResize. + */ + constructor() { + this._callbacks = []; + this._running = false; } /** - * Extend obj1 with each key in obj2, overriding default values in obj1 with - * values in obj2 + * Add a callback to be run on resize. * - * @param {object} obj1 - The object to extend. - * @param {object} obj2 - The overrides to apply onto obj1. + * @param {Function} callback - The callback to run on resize. */ - function _extend(obj1, obj2) { - for (const i in obj2) { - if (obj2.hasOwnProperty(i)) { - obj1[i] = obj2[i]; + add(callback) { + if (!this._callbacks.length) { + window.addEventListener('resize', this._resize.bind(this)); + } + + this._callbacks.push(callback); + } + + /** + * Disables all resize handlers. + */ + disable() { + window.removeEventListener('resize', this._resize.bind(this)); + } + + /** + * Enables all resize handlers, if they were disabled. + */ + reEnable() { + window.addEventListener('resize', this._resize.bind(this)); + } + + // fired on resize event + _resize() { + if (!this._running) { + this._running = true; + if (window.requestAnimationFrame) { + window.requestAnimationFrame(this._runCallbacks.bind(this)); + } else { + setTimeout(this._runCallbacks.bind(this), 66); } } } + // run the actual callbacks + _runCallbacks() { + this._callbacks.forEach((callback) => { + callback(); + }); + + this._running = false; + } +} + +/** + * This is the class for defining all Pig options. + */ +class PigSettings { /** - * Returns the distance from `elem` to the top of the page. This is done by - * walking up the node tree, getting the offsetTop of each parent node, until - * the top of the page. + * Creates an instance of PigSettings. + */ + constructor() { + /** + * Type: string + * Default: 'pig' + * Description: The class name of the element inside of which images should be loaded. + */ + this.containerId = 'pig'; + + /** + * Type: window | HTMLElement + * Default: window + * Description: The window or HTML element that the grid scrolls in. + */ + this.scroller = window; + + /** + * Type: string + * Default: 'pig' + * Description: The prefix associated with this library that should be + * ! prepended to class names within the grid. + */ + this.classPrefix = 'pig'; + + /** + * Type: string + * Default: 'figure' + * Description: The tag name to use for each figure. The default setting is + * ! to use a
tag. + */ + this.figureTagName = 'figure'; + + /** + * Type: Number + * Default: 8 + * Description: Size in pixels of the gap between images in the grid. + */ + this.spaceBetweenImages = 8; + + /** + * Type: Number + * Default: 500 + * Description: Transition speed in milliseconds + */ + this.transitionSpeed = 500; + + /** + * Type: Number + * Default: 3000 + * Description: Height in pixels of images to preload in the direction + * that the user is scrolling. For example, in the default case, if the + * user is scrolling down, 1000px worth of images will be loaded below + * the viewport. + */ + this.primaryImageBufferHeight = 1000; + + /** + * Type: Number + * Default: 100 + * Description: Height in pixels of images to preload in the direction + * that the user is NOT scrolling. For example, in the default case, if + * the user is scrolling down, 300px worth of images will be loaded + * above the viewport. Images further up will be removed. + */ + this.secondaryImageBufferHeight = 300; + + /** + * Type: Number + * Default: 20 + * Description: The height in pixels of the thumbnail that should be + * loaded and blurred to give the effect that images are loading out of + * focus and then coming into focus. + */ + this.thumbnailSize = 20; + + /** + * Get a callback with the filename property of the image + * which was clicked. + * callback signature: function(filename) { ... } + * + * @param {string} filename - The filename property of the image. + */ + this.onClickHandler = null; + } + + /** + * Get the URL for an image with the given filename & size. * - * @param {object} elem - The element to compute the offset of. - * @returns {number} - distance of `elem` to the top of the page + * @param {string} filename - The filename of the image. + * @param {number} size - The size (height in pixels) of the image. + * + * @returns {string} The URL of the image at the given size. */ - function _getOffsetTop(elem){ - let offsetTop = 0; - do { - if (!isNaN(elem.offsetTop)){ - offsetTop += elem.offsetTop; - } - elem = elem.offsetParent; - } while(elem); - return offsetTop; + urlForSize(filename, size) { + return `/img/${size.toString(10)}/${filename}`; } /** - * Creates an instance of the progressive image grid, inserting boilerplate - * CSS and loading image data. Instantiating an instance of the Pig class - * does not cause any images to appear however. After instantiating, call the - * `enable()` function on the returned instance: + * Get the minimum required aspect ratio for a valid row of images. The + * perfect rows are maintained by building up a row of images by adding + * together their aspect ratios (the aspect ratio when they are placed + * next to each other) until that aspect ratio exceeds the value returned + * by this function. Responsive reordering is achieved through changes + * to what this function returns at different values of the passed + * parameter `lastWindowWidth`. + * + * @param {number} lastWindowWidth - The last computed width of the browser window. + * + * @returns {number} The minimum aspect ratio at this window width. + */ + getMinAspectRatio(lastWindowWidth) { + if (lastWindowWidth <= 640) { + return 2; + } else if (lastWindowWidth <= 1280) { + return 4; + } else if (lastWindowWidth <= 1920) { + return 5; + } + return 6; + } + + /** + * Get the image size (height in pixels) to use for this window width. + * Responsive resizing of images is achieved through changes to what this + * function returns at different values of the passed parameter + * `lastWindowWidth`. * - * var pig = new Pig(imageData, opts); - * pig.enable(); + * @param {number} lastWindowWidth - The last computed width of the browser window. * - * @param {array} imageData - An array of metadata about each image to - * include in the grid. - * @param {string} imageData[0].filename - The filename of the image. - * @param {string} imageData[0].aspectRatio - The aspect ratio of the image. + * @returns {number} The size (height in pixels) of the images to load. + */ + getImageSize(lastWindowWidth) { + if (lastWindowWidth <= 640) { + return 100; + } else if (lastWindowWidth <= 1920) { + return 250; + } + return 500; + } + + /** + * Factory function that creates a new instance of the ProgressiveImage class. + * + * @param {object} singleImageData - Data of the image in the data source + * @param {number} index - Index of the image in the data source + * @param {object} pig - Pig instance, that should contain the image + * + * @returns {object} The newly created instance of the ProgressiveImage class + */ + createProgressiveImage(singleImageData, index, pig) { + return new ProgressiveImage(singleImageData, index, pig); + } +} + +/** + * Class for the progressive image grid. Instantiating an instance of the Pig + * class does not cause any images to appear however. After instantiating, + * call the `enable()` function on the returned instance: + * + * var pig = new Pig(imageData, opts); + * pig.enable(); + */ +export class Pig { + /** + * Creates an instance of the progressive image grid, inserting boilerplate + * CSS and loading image data. + * + * @param {object[]} imageData - An array of metadata about each image to + * include in the grid. + * @param {string} imageData[].filename - The filename of the image. + * @param {string} imageData[].aspectRatio - The aspect ratio of the image. * @param {object} options - An object containing overrides for the default - * options. See below for the full list of options - * and defaults. + * options. See class PigSettings for the full list + * of options and defaults. * * @returns {object} The Pig instance. */ - function Pig(imageData, options) { + constructor(imageData, options) { + this._optimizedResize = new OptimizedResize(); + // Global State this.inRAF = false; this.isTransitioning = false; @@ -187,156 +466,13 @@ // List of images that are loading or completely loaded on screen. this.visibleImages = []; - // These are the default settings, which may be overridden. - this.settings = { - - /** - * Type: string - * Default: 'pig' - * Description: The class name of the element inside of which images should - * be loaded. - */ - containerId: 'pig', - - /** - * Type: window | HTMLElement - * Default: window - * Description: The window or HTML element that the grid scrolls in. - */ - scroller: window, - - /** - * Type: string - * Default: 'pig' - * Description: The prefix associated with this library that should be - * prepended to class names within the grid. - */ - classPrefix: 'pig', - - /** - * Type: string - * Default: 'figure' - * Description: The tag name to use for each figure. The default setting is - * to use a
tag. - */ - figureTagName: 'figure', - - /** - * Type: Number - * Default: 8 - * Description: Size in pixels of the gap between images in the grid. - */ - spaceBetweenImages: 8, - - /** - * Type: Number - * Default: 500 - * Description: Transition speed in milliseconds - */ - transitionSpeed: 500, - - /** - * Type: Number - * Default: 3000 - * Description: Height in pixels of images to preload in the direction - * that the user is scrolling. For example, in the default case, if the - * user is scrolling down, 1000px worth of images will be loaded below - * the viewport. - */ - primaryImageBufferHeight: 1000, - - /** - * Type: Number - * Default: 100 - * Description: Height in pixels of images to preload in the direction - * that the user is NOT scrolling. For example, in the default case, if - * the user is scrolling down, 300px worth of images will be loaded - * above the viewport. Images further up will be removed. - */ - secondaryImageBufferHeight: 300, - - /** - * Type: Number - * Default: 20 - * Description: The height in pixels of the thumbnail that should be - * loaded and blurred to give the effect that images are loading out of - * focus and then coming into focus. - */ - thumbnailSize: 20, - - /** - * Get the URL for an image with the given filename & size. - * - * @param {string} filename - The filename of the image. - * @param {Number} size - The size (height in pixels) of the image. - * - * @returns {string} The URL of the image at the given size. - */ - urlForSize: function(filename, size) { - return '/img/' + size.toString(10) + '/' + filename; - }, - - /** - * Get a callback with the filename of the image - * which was clicked. - * - * @param {string} filename - The filename property of the image. - */ - onClickHandler: null, - - /** - * Get the minimum required aspect ratio for a valid row of images. The - * perfect rows are maintained by building up a row of images by adding - * together their aspect ratios (the aspect ratio when they are placed - * next to each other) until that aspect ratio exceeds the value returned - * by this function. Responsive reordering is achieved through changes - * to what this function returns at different values of the passed - * parameter `lastWindowWidth`. - * - * @param {Number} lastWindowWidth - The last computed width of the - * browser window. - * - * @returns {Number} The minimum aspect ratio at this window width. - */ - getMinAspectRatio: function(lastWindowWidth) { - if (lastWindowWidth <= 640) { - return 2; - } else if (lastWindowWidth <= 1280) { - return 4; - } else if (lastWindowWidth <= 1920) { - return 5; - } - return 6; - }, - - /** - * Get the image size (height in pixels) to use for this window width. - * Responsive resizing of images is achieved through changes to what this - * function returns at different values of the passed parameter - * `lastWindowWidth`. - * - * @param {Number} lastWindowWidth - The last computed width of the - * browser window. - * - * @returns {Number} The size (height in pixels) of the images to load. - */ - getImageSize: function(lastWindowWidth) { - if (lastWindowWidth <= 640) { - return 100; - } else if (lastWindowWidth <= 1920) { - return 250; - } - return 500; - } - }; - // We extend the default settings with the provided overrides. - _extend(this.settings, options || {}); + this.settings = Object.assign(new PigSettings(), options); // Find the container to load images into, if it exists. this.container = document.getElementById(this.settings.containerId); if (!this.container) { - console.error('Could not find element with ID ' + this.settings.containerId); + console.error(`Could not find element with ID ${this.settings.containerId}`); } this.scroller = this.settings.scroller; @@ -346,12 +482,141 @@ this.images = this._parseImageData(imageData); // Inject our boilerplate CSS. - _injectStyle(this.settings.containerId, this.settings.classPrefix, this.settings.transitionSpeed); + // eslint-disable-next-line max-len + this._injectStyle(this.settings.containerId, this.settings.classPrefix, this.settings.transitionSpeed); // Allows for chaining with `enable()`. return this; } + /** + * Enable scroll and resize handlers, and run a complete layout computation / + * application. + * + * @returns {object} The Pig instance, for easy chaining with the constructor. + */ + enable() { + this.onScroll = this._getOnScroll(); + + this.scroller.addEventListener('scroll', this.onScroll); + + this.onScroll(); + this._computeLayout(); + this._doLayout(); + + this._optimizedResize.add(() => { + // eslint-disable-next-line max-len + this.lastWindowWidth = this.scroller === window ? window.innerWidth : this.scroller.offsetWidth; + this._computeLayout(); + this._doLayout(); + }); + + return this; + } + + /** + * Remove all scroll and resize listeners. + * + * @returns {object} The Pig instance. + */ + disable() { + this.scroller.removeEventListener('scroll', this.onScroll); + this._optimizedResize.disable(); + return this; + } + + /** + * Creates new instances of the ProgressiveImage class for each of the images + * defined in `imageData`. + * + * @param {object[]} imageData - An array of metadata about each image to + * include in the grid. + * @param {string} imageData[].filename - The filename of the image. + * @param {string} imageData[].aspectRatio - The aspect ratio of the image. + * + * @returns {ProgressiveImage[]} - An array of ProgressiveImage instances + * that we created. + */ + _parseImageData(imageData) { + const progressiveImages = []; + + imageData.forEach((image, index) => { + const progressiveImage = this.settings.createProgressiveImage(image, index, this); + progressiveImages.push(progressiveImage); + }); + + return progressiveImages; + } + + /** + * Returns the distance from `elem` to the top of the page. This is done by + * walking up the node tree, getting the offsetTop of each parent node, until + * the top of the page. + * + * @param {object} elem - The element to compute the offset of. + * + * @returns {number} - Distance of `elem` to the top of the page + */ + _getOffsetTop(elem) { + let offsetTop = 0; + do { + if (!Number.isNaN(elem.offsetTop)) { + offsetTop += elem.offsetTop; + } + elem = elem.offsetParent; // eslint-disable-line no-param-reassign + } while (elem); + return offsetTop; + } + + /** + * Inject CSS needed to make the grid work in the . + * + * @param {string} containerId - ID of the container for the images. + * @param {string} classPrefix - The prefix associated with this library that + * should be prepended to classnames. + * @param {number} transitionSpeed - Animation duration in milliseconds + */ + _injectStyle(containerId, classPrefix, transitionSpeed) { + const css = ( + `#${containerId} {` + + ' position: relative;' + + '}' + + `.${classPrefix}-figure {` + + ' background-color: #D5D5D5;' + + ' overflow: hidden;' + + ' left: 0;' + + ' position: absolute;' + + ' top: 0;' + + ' margin: 0;' + + '}' + + `.${classPrefix}-figure img {` + + ' left: 0;' + + ' position: absolute;' + + ' top: 0;' + + ' height: 100%;' + + ' width: 100%;' + + ' opacity: 0;' + + ` transition: ${(transitionSpeed / 1000).toString(10)}s ease opacity;` + + ` -webkit-transition: ${(transitionSpeed / 1000).toString(10)}s ease opacity;` + + '}' + + `.${classPrefix}-figure img.${classPrefix}-thumbnail {` + + ' -webkit-filter: blur(30px);' + + ' filter: blur(30px);' + + ' left: auto;' + + ' position: relative;' + + ' width: auto;' + + '}' + + `.${classPrefix}-figure img.${classPrefix}-loaded {` + + ' opacity: 1;' + + '}' + ); + + const head = document.head || document.getElementsByTagName('head')[0]; + const style = document.createElement('style'); + style.appendChild(document.createTextNode(css)); + head.appendChild(style); + } + /** * Because we may be transitioning a very large number of elements on a * resize, and because we cannot reliably determine when all elements are @@ -362,27 +627,27 @@ * given as 500ms, we will wait 750ms before assuming that we are actually * done resizing. * - * @returns {Number} Time in milliseconds before we can consider a resize to - * have been completed. + * @returns {number} Time in milliseconds before we can consider a resize to + * ! have been completed. */ - Pig.prototype._getTransitionTimeout = function() { + _getTransitionTimeout() { const transitionTimeoutScaleFactor = 1.5; return this.settings.transitionSpeed * transitionTimeoutScaleFactor; - }; + } /** * Gives the CSS property string to set for the transition value, depending * on whether or not we are transitioning. * - * @returns {string} a value for the `transition` CSS property. + * @returns {string} A value for the `transition` CSS property. */ - Pig.prototype._getTransitionString = function() { + _getTransitionString() { if (this.isTransitioning) { - return (this.settings.transitionSpeed / 1000).toString(10) + 's transform ease'; + return `${(this.settings.transitionSpeed / 1000).toString(10)}s transform ease`; } return 'none'; - }; + } /** * Computes the current value for `this.minAspectRatio`, using the @@ -390,7 +655,7 @@ * `this.minAspectRatioRequiresTransition` will be set, depending on whether * or not the value of this.minAspectRatio has changed. */ - Pig.prototype._recomputeMinAspectRatio = function() { + _recomputeMinAspectRatio() { const oldMinAspectRatio = this.minAspectRatio; this.minAspectRatio = this.settings.getMinAspectRatio(this.lastWindowWidth); @@ -399,30 +664,7 @@ } else { this.minAspectRatioRequiresTransition = false; } - }; - - /** - * Creates new instances of the ProgressiveImage class for each of the images - * defined in `imageData`. - * - * @param {array} imageData - An array of metadata about each image to - * include in the grid. - * @param {string} imageData[0].filename - The filename of the image. - * @param {string} imageData[0].aspectRatio - The aspect ratio of the image. - * - * @returns {Array[ProgressiveImage]} - An array of ProgressiveImage - * instances that we created. - */ - Pig.prototype._parseImageData = function(imageData) { - const progressiveImages = []; - - imageData.forEach(function(image, index) { - const progressiveImage = new ProgressiveImage(image, index, this); - progressiveImages.push(progressiveImage); - }.bind(this)); - - return progressiveImages; - }; + } /** * This computes the layout of the entire grid, setting the height, width, @@ -439,7 +681,7 @@ * * All DOM manipulation occurs in `_doLayout`. */ - Pig.prototype._computeLayout = function() { + _computeLayout() { // Constants const wrapperWidth = parseInt(this.container.clientWidth, 10); @@ -462,7 +704,7 @@ // future calls to `_computeLayout` will set "transition: none". if (!this.isTransitioning && this.minAspectRatioRequiresTransition) { this.isTransitioning = true; - setTimeout(function() { + setTimeout(() => { this.isTransitioning = false; }, this._getTransitionTimeout()); } @@ -472,7 +714,7 @@ // Loop through all our images, building them up into rows and computing // the working rowAspectRatio. - [].forEach.call(this.images, function(image, index) { + [].forEach.call(this.images, (image, index) => { rowAspectRatio += parseFloat(image.aspectRatio); row.push(image); @@ -481,11 +723,11 @@ // need for this row, and compute the style values for each of these // images. if (rowAspectRatio >= this.minAspectRatio || index + 1 === this.images.length) { - // Make sure that the last row also has a reasonable height rowAspectRatio = Math.max(rowAspectRatio, this.minAspectRatio); // Compute this row's height. + // eslint-disable-next-line max-len const totalDesiredWidthOfImages = wrapperWidth - this.settings.spaceBetweenImages * (row.length - 1); const rowHeight = totalDesiredWidthOfImages / rowAspectRatio; @@ -496,24 +738,22 @@ // NOTE: This does not manipulate the DOM, rather it just sets the // style values on the ProgressiveImage instance. The DOM nodes // will be updated in _doLayout. - row.forEach(function(img) { - + row.forEach((img) => { const imageWidth = rowHeight * img.aspectRatio; // This is NOT DOM manipulation. - img.style = { + img.style = { // eslint-disable-line no-param-reassign width: parseInt(imageWidth, 10), height: parseInt(rowHeight, 10), - translateX: translateX, - translateY: translateY, - transition: transition + translateX, + translateY, + transition }; // The next image is this.settings.spaceBetweenImages pixels to the // right of this image. translateX += imageWidth + this.settings.spaceBetweenImages; - - }.bind(this)); + }); // Reset our state variables for next row. row = []; @@ -521,11 +761,11 @@ translateY += parseInt(rowHeight, 10) + this.settings.spaceBetweenImages; translateX = 0; } - }.bind(this)); + }); // No space below the last image this.totalHeight = translateY - this.settings.spaceBetweenImages; - }; + } /** * Update the DOM to reflect the style values of each image in the PIG, @@ -550,7 +790,7 @@ * the primary buffer, and the secondary buffer will exist in the DOM. * * - * Illustration: the primary and secondary buffers + * ! Illustration: the primary and secondary buffers * * * +---------------------------+ @@ -589,10 +829,9 @@ * | | * */ - Pig.prototype._doLayout = function() { - + _doLayout() { // Set the container height - this.container.style.height = this.totalHeight + 'px'; + this.container.style.height = `${this.totalHeight}px`; // Get the top and bottom buffers heights. const bufferTop = @@ -605,7 +844,8 @@ this.settings.primaryImageBufferHeight; // Now we compute the location of the top and bottom buffers: - const containerOffset = _getOffsetTop(this.container); + const containerOffset = this._getOffsetTop(this.container); + // eslint-disable-next-line max-len const scrollerHeight = this.scroller === window ? window.innerHeight : this.scroller.offsetHeight; // This is the top of the top buffer. If the bottom of an image is above @@ -618,8 +858,7 @@ // Here, we loop over every image, determine if it is inside our buffers or // no, and either insert it or remove it appropriately. - this.images.forEach(function(image) { - + this.images.forEach((image) => { if (image.style.translateY + image.style.height < minTranslateYPlusHeight || image.style.translateY > maxTranslateY) { // Hide Image @@ -628,16 +867,16 @@ // Load Image image.load(); } - }.bind(this)); - }; + }); + } /** * Create our onScroll handler and return it. * - * @returns {function} Our optimized onScroll handler that we should attach to. + * @returns {Function} Our optimized onScroll handler that we should attach. */ - Pig.prototype._getOnScroll = function() { - const _this = this; + _getOnScroll() { + const that = this; /** * This function is called on scroll. It computes variables about the page @@ -648,252 +887,24 @@ * the number of layouts we perform by starting another layout while we are * in the middle of doing one. */ - const onScroll = function() { + const onScroll = () => { // Compute the scroll direction using the latestYOffset and the // previousYOffset - const newYOffset = _this.scroller === window ? window.pageYOffset : _this.scroller.scrollTop; - _this.previousYOffset = _this.latestYOffset || newYOffset; - _this.latestYOffset = newYOffset; - _this.scrollDirection = (_this.latestYOffset > _this.previousYOffset) ? 'down' : 'up'; + const newYOffset = that.scroller === window ? window.pageYOffset : that.scroller.scrollTop; + that.previousYOffset = that.latestYOffset || newYOffset; + that.latestYOffset = newYOffset; + that.scrollDirection = (that.latestYOffset > that.previousYOffset) ? 'down' : 'up'; // Call _this.doLayout, guarded by window.requestAnimationFrame - if (!_this.inRAF) { - _this.inRAF = true; - window.requestAnimationFrame(function() { - _this._doLayout(); - _this.inRAF = false; + if (!that.inRAF) { + that.inRAF = true; + window.requestAnimationFrame(() => { + that._doLayout(); // eslint-disable-line no-underscore-dangle + that.inRAF = false; }); } }; return onScroll; - }; - - /** - * Enable scroll and resize handlers, and run a complete layout computation / - * application. - * - * @returns {object} The Pig instance, for easy chaining with the constructor. - */ - Pig.prototype.enable = function() { - this.onScroll = this._getOnScroll(); - - this.scroller.addEventListener('scroll', this.onScroll); - - this.onScroll(); - this._computeLayout(); - this._doLayout(); - - optimizedResize.add(function() { - this.lastWindowWidth = this.scroller === window ? window.innerWidth : this.scroller.offsetWidth; - this._computeLayout(); - this._doLayout(); - }.bind(this)); - - return this; - }; - - /** - * Remove all scroll and resize listeners. - * - * @returns {object} The Pig instance. - */ - Pig.prototype.disable = function() { - this.scroller.removeEventListener('scroll', this.onScroll); - optimizedResize.disable(); - return this; - }; - - /** - * This class manages a single image. It keeps track of the image's height, - * width, and position in the grid. An instance of this class is associated - * with a single image figure, which looks like this: - * - *
- * - * - *
- * - * However, this element may or may not actually exist in the DOM. The actual - * DOM element may loaded and unloaded depending on where it is with respect - * to the viewport. This class is responsible for managing the DOM elements, - * but does not include logic to determine _when_ the DOM elements should - * be removed. - * - * This class also manages the blur-into-focus load effect. First, the - *
element is inserted into the page. Then, a very small thumbnail - * is loaded, stretched out to the full size of the image. This pixelated - * image is then blurred using CSS filter: blur(). Then, the full image is - * loaded, with opacity:0. Once it has loaded, it is given the `pig-loaded` - * class, and its opacity is set to 1. This creates an effect where there is - * first a blurred version of the image, and then it appears to come into - * focus. - * - * @param {array} singleImageData - An array of metadata about each image to - * include in the grid. - * @param {string} singleImageData[0].filename - The filename of the image. - * @param {string} singleImageData[0].aspectRatio - The aspect ratio of the - * image. - * @param {number} index - Index of the image in the list of images - * @param {object} pig - The Pig instance - * - * @returns {object} The Pig instance, for easy chaining with the constructor. - */ - function ProgressiveImage(singleImageData, index, pig) { - - // Global State - this.existsOnPage = false; // True if the element exists on the page. - - // Instance information - this.aspectRatio = singleImageData.aspectRatio; // Aspect Ratio - this.filename = singleImageData.filename; // Filename - this.index = index; // The index in the list of images - - // The Pig instance - this.pig = pig; - - this.classNames = { - figure: pig.settings.classPrefix + '-figure', - thumbnail: pig.settings.classPrefix + '-thumbnail', - loaded: pig.settings.classPrefix + '-loaded' - }; - - return this; } - - /** - * Load the image element associated with this ProgressiveImage into the DOM. - * - * This function will append the figure into the DOM, create and insert the - * thumbnail, and create and insert the full image. - */ - ProgressiveImage.prototype.load = function() { - // Create a new image element, and insert it into the DOM. It doesn't - // matter the order of the figure elements, because all positioning - // is done using transforms. - this.existsOnPage = true; - this._updateStyles(); - this.pig.container.appendChild(this.getElement()); - - // We run the rest of the function in a 100ms setTimeout so that if the - // user is scrolling down the page very fast and hide() is called within - // 100ms of load(), the hide() function will set this.existsOnPage to false - // and we can exit. - setTimeout(function() { - - // The image was hidden very quickly after being loaded, so don't bother - // loading it at all. - if (!this.existsOnPage) { - return; - } - - // Show thumbnail - if (!this.thumbnail) { - this.thumbnail = new Image(); - this.thumbnail.src = this.pig.settings.urlForSize(this.filename, this.pig.settings.thumbnailSize); - this.thumbnail.className = this.classNames.thumbnail; - this.thumbnail.onload = function() { - - // We have to make sure thumbnail still exists, we may have already been - // deallocated if the user scrolls too fast. - if (this.thumbnail) { - this.thumbnail.className += ' ' + this.classNames.loaded; - } - }.bind(this); - - this.getElement().appendChild(this.thumbnail); - } - - // Show full image - if (!this.fullImage) { - this.fullImage = new Image(); - this.fullImage.src = this.pig.settings.urlForSize(this.filename, this.pig.settings.getImageSize(this.pig.lastWindowWidth)); - this.fullImage.onload = function() { - - // We have to make sure fullImage still exists, we may have already been - // deallocated if the user scrolls too fast. - if (this.fullImage) { - this.fullImage.className += ' ' + this.classNames.loaded; - } - }.bind(this); - - this.getElement().appendChild(this.fullImage); - } - - }.bind(this), 100); - }; - - /** - * Removes the figure from the DOM, removes the thumbnail and full image, and - * deletes the this.thumbnail and this.fullImage properties off of the - * ProgressiveImage object. - */ - ProgressiveImage.prototype.hide = function() { - // Remove the images from the element, so that if a user is scrolling super - // fast, we won't try to load every image we scroll past. - if (this.getElement()) { - if (this.thumbnail) { - this.thumbnail.src = ''; - this.getElement().removeChild(this.thumbnail); - delete this.thumbnail; - } - - if (this.fullImage) { - this.fullImage.src = ''; - this.getElement().removeChild(this.fullImage); - delete this.fullImage; - } - } - - // Remove the image from the DOM. - if (this.existsOnPage) { - this.pig.container.removeChild(this.getElement()); - } - - this.existsOnPage = false; - }; - - /** - * Get the DOM element associated with this ProgressiveImage. We default to - * using this.element, and we create it if it doesn't exist. - * - * @returns {HTMLElement} The DOM element associated with this instance. - */ - ProgressiveImage.prototype.getElement = function() { - if (!this.element) { - this.element = document.createElement(this.pig.settings.figureTagName); - this.element.className = this.classNames.figure; - if (this.pig.settings.onClickHandler !== null) { - this.element.addEventListener('click', function() { - this.pig.settings.onClickHandler(this.filename); - }.bind(this) ); - } - this._updateStyles(); - } - - return this.element; - }; - - - /** - * Updates the style attribute to reflect this style property on this object. - */ - ProgressiveImage.prototype._updateStyles = function() { - this.getElement().style.transition = this.style.transition; - this.getElement().style.width = this.style.width + 'px'; - this.getElement().style.height = this.style.height + 'px'; - this.getElement().style.transform = ( - 'translate3d(' + this.style.translateX + 'px,' + - this.style.translateY + 'px, 0)'); - }; - - // Export Pig into the global scope. - if (typeof define === 'function' && define.amd) { - define([], function() { return { Pig: Pig }; }); - } else if (typeof module !== 'undefined' && module.exports) { - module.exports = Pig; - } else { - global.Pig = Pig; - } - -}(typeof window !== 'undefined' ? window : this)); +} diff --git a/src/pig.min.js b/src/pig.min.js deleted file mode 100644 index 4a4f30f..0000000 --- a/src/pig.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(t){"use strict";var i,e,s=(e=!(i=[]),{add:function(t){i.length||window.addEventListener("resize",n),i.push(t)},disable:function(){window.removeEventListener("resize",n)},reEnable:function(){window.addEventListener("resize",n)}});function n(){e||(e=!0,window.requestAnimationFrame?window.requestAnimationFrame(o):setTimeout(o,66))}function o(){i.forEach(function(t){t()}),e=!1}function a(t,i){var e;return this.inRAF=!1,this.isTransitioning=!1,this.minAspectRatioRequiresTransition=!1,this.minAspectRatio=null,this.latestYOffset=0,this.lastWindowWidth=window.innerWidth,this.scrollDirection="down",this.visibleImages=[],this.settings={containerId:"pig",scroller:window,classPrefix:"pig",figureTagName:"figure",spaceBetweenImages:8,transitionSpeed:500,primaryImageBufferHeight:1e3,secondaryImageBufferHeight:300,thumbnailSize:20,urlForSize:function(t,i){return"/img/"+i+"/"+t},onClickHandler:function(t){},getMinAspectRatio:function(t){return t<=640?2:t<=1280?4:t<=1920?5:6},getImageSize:function(t){return t<=640?100:t<=1920?250:500}},function(t,i){for(var e in i)i.hasOwnProperty(e)&&(t[e]=i[e])}(this.settings,i||{}),this.container=document.getElementById(this.settings.containerId),this.container||console.error("Could not find element with ID "+this.settings.containerId),this.scroller=this.settings.scroller,this.images=this._parseImageData(t),e=this.settings.containerId,i=this.settings.classPrefix,t=this.settings.transitionSpeed,e="#"+e+" { position: relative;}."+i+"-figure { background-color: #D5D5D5; overflow: hidden; left: 0; position: absolute; top: 0; margin: 0;}."+i+"-figure img { left: 0; position: absolute; top: 0; height: 100%; width: 100%; opacity: 0; transition: "+t/1e3+"s ease opacity; -webkit-transition: "+t/1e3+"s ease opacity;}."+i+"-figure img."+i+"-thumbnail { -webkit-filter: blur(30px); filter: blur(30px); left: auto; position: relative; width: auto;}."+i+"-figure img."+i+"-loaded { opacity: 1;}",t=document.head||document.getElementsByTagName("head")[0],(i=document.createElement("style")).type="text/css",i.styleSheet?i.styleSheet.cssText=e:i.appendChild(document.createTextNode(e)),t.appendChild(i),this}function r(t,i,e){return this.existsOnPage=!1,this.aspectRatio=t.aspectRatio,this.filename=t.filename,this.index=i,this.pig=e,this.classNames={figure:e.settings.classPrefix+"-figure",thumbnail:e.settings.classPrefix+"-thumbnail",loaded:e.settings.classPrefix+"-loaded"},this}a.prototype._getTransitionTimeout=function(){return 1.5*this.settings.transitionSpeed},a.prototype._getTransitionString=function(){return this.isTransitioning?this.settings.transitionSpeed/1e3+"s transform ease":"none"},a.prototype._recomputeMinAspectRatio=function(){var t=this.minAspectRatio;this.minAspectRatio=this.settings.getMinAspectRatio(this.lastWindowWidth),null!==t&&t!==this.minAspectRatio?this.minAspectRatioRequiresTransition=!0:this.minAspectRatioRequiresTransition=!1},a.prototype._parseImageData=function(t){var e=[];return t.forEach(function(t,i){i=new r(t,i,this);e.push(i)}.bind(this)),e},a.prototype._computeLayout=function(){var s=parseInt(this.container.clientWidth),n=[],o=0,a=0,r=0;this._recomputeMinAspectRatio(),!this.isTransitioning&&this.minAspectRatioRequiresTransition&&(this.isTransitioning=!0,setTimeout(function(){this.isTransitioning=!1},this._getTransitionTimeout()));var h=this._getTransitionString();[].forEach.call(this.images,function(t,i){var e;r+=parseFloat(t.aspectRatio),n.push(t),(r>=this.minAspectRatio||i+1===this.images.length)&&(r=Math.max(r,this.minAspectRatio),e=(s-this.settings.spaceBetweenImages*(n.length-1))/r,n.forEach(function(t){var i=e*t.aspectRatio;t.style={width:parseInt(i),height:parseInt(e),translateX:o,translateY:a,transition:h},o+=i+this.settings.spaceBetweenImages}.bind(this)),n=[],r=0,a+=parseInt(e)+this.settings.spaceBetweenImages,o=0)}.bind(this)),this.totalHeight=a-this.settings.spaceBetweenImages},a.prototype._doLayout=function(){this.container.style.height=this.totalHeight+"px";var t="up"===this.scrollDirection?this.settings.primaryImageBufferHeight:this.settings.secondaryImageBufferHeight,i="down"===this.scrollDirection?this.settings.secondaryImageBufferHeight:this.settings.primaryImageBufferHeight,e=function(t){for(var i=0;isNaN(t.offsetTop)||(i+=t.offsetTop),t=t.offsetParent;);return i}(this.container),s=this.scroller===window?window.innerHeight:this.scroller.offsetHeight,n=this.latestYOffset-e-t,o=this.latestYOffset-e+s+i;this.images.forEach(function(t){t.style.translateY+t.style.heighto?t.hide():t.load()}.bind(this))},a.prototype._getOnScroll=function(){var i=this;return function(){var t=i.scroller===window?window.pageYOffset:i.scroller.scrollTop;i.previousYOffset=i.latestYOffset||t,i.latestYOffset=t,i.scrollDirection=i.latestYOffset>i.previousYOffset?"down":"up",i.inRAF||(i.inRAF=!0,window.requestAnimationFrame(function(){i._doLayout(),i.inRAF=!1}))}},a.prototype.enable=function(){return this.onScroll=this._getOnScroll(),this.scroller.addEventListener("scroll",this.onScroll),this.onScroll(),this._computeLayout(),this._doLayout(),s.add(function(){this.lastWindowWidth=this.scroller===window?window.innerWidth:this.scroller.offsetWidth,this._computeLayout(),this._doLayout()}.bind(this)),this},a.prototype.disable=function(){return this.scroller.removeEventListener("scroll",this.onScroll),s.disable(),this},r.prototype.load=function(){this.existsOnPage=!0,this._updateStyles(),this.pig.container.appendChild(this.getElement()),setTimeout(function(){this.existsOnPage&&(this.thumbnail||(this.thumbnail=new Image,this.thumbnail.src=this.pig.settings.urlForSize(this.filename,this.pig.settings.thumbnailSize),this.thumbnail.className=this.classNames.thumbnail,this.thumbnail.onload=function(){this.thumbnail&&(this.thumbnail.className+=" "+this.classNames.loaded)}.bind(this),this.getElement().appendChild(this.thumbnail)),this.fullImage||(this.fullImage=new Image,this.fullImage.src=this.pig.settings.urlForSize(this.filename,this.pig.settings.getImageSize(this.pig.lastWindowWidth)),this.fullImage.onload=function(){this.fullImage&&(this.fullImage.className+=" "+this.classNames.loaded)}.bind(this),this.getElement().appendChild(this.fullImage)))}.bind(this),100)},r.prototype.hide=function(){this.getElement()&&(this.thumbnail&&(this.thumbnail.src="",this.getElement().removeChild(this.thumbnail),delete this.thumbnail),this.fullImage&&(this.fullImage.src="",this.getElement().removeChild(this.fullImage),delete this.fullImage)),this.existsOnPage&&this.pig.container.removeChild(this.getElement()),this.existsOnPage=!1},r.prototype.getElement=function(){return this.element||(this.element=document.createElement(this.pig.settings.figureTagName),this.element.className=this.classNames.figure,this.element.addEventListener("click",function(){this.pig.settings.onClickHandler(this.filename)}.bind(this)),this._updateStyles()),this.element},r.prototype._updateStyles=function(){this.getElement().style.transition=this.style.transition,this.getElement().style.width=this.style.width+"px",this.getElement().style.height=this.style.height+"px",this.getElement().style.transform="translate3d("+this.style.translateX+"px,"+this.style.translateY+"px, 0)"},"function"==typeof define&&define.amd?define([],function(){return{Pig:a}}):"undefined"!=typeof module&&module.exports?module.exports=a:t.Pig=a}("undefined"!=typeof window?window:this); \ No newline at end of file diff --git a/test/esm/index-customized-min.html b/test/esm/index-customized-min.html new file mode 100644 index 0000000..fceb63c --- /dev/null +++ b/test/esm/index-customized-min.html @@ -0,0 +1,210 @@ + + + + + + Progressive Image Grid: pig.js + + + +

Scroll to see some images...

+
+
+
+ + + diff --git a/test/esm/index-customized.html b/test/esm/index-customized.html new file mode 100644 index 0000000..b8d57f9 --- /dev/null +++ b/test/esm/index-customized.html @@ -0,0 +1,210 @@ + + + + + + Progressive Image Grid: pig.js + + + +

Scroll to see some images...

+
+
+
+ + + diff --git a/test/esm/index.html b/test/esm/index.html new file mode 100644 index 0000000..64f3eff --- /dev/null +++ b/test/esm/index.html @@ -0,0 +1,128 @@ + + + + + + Progressive Image Grid: pig.js + + + +

Scroll to see some images...

+
+
+
+ + + diff --git a/test/index-src-customized.html b/test/index-src-customized.html new file mode 100644 index 0000000..4f9f52d --- /dev/null +++ b/test/index-src-customized.html @@ -0,0 +1,210 @@ + + + + + + Progressive Image Grid: pig.js + + + +

Scroll to see some images...

+
+
+
+ + + diff --git a/test/index-src.html b/test/index-src.html new file mode 100644 index 0000000..df7631b --- /dev/null +++ b/test/index-src.html @@ -0,0 +1,127 @@ + + + + + + Progressive Image Grid: pig.js + + + +

Scroll to see some images...

+
+
+
+ + + diff --git a/test/index.html b/test/index.html deleted file mode 100644 index e515505..0000000 --- a/test/index.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - Progressive Image Grid: pig.js - - - -

Scroll to see some images...

-
-
-
- - - - diff --git a/test/umd/index-customized-min.html b/test/umd/index-customized-min.html new file mode 100644 index 0000000..5f8df08 --- /dev/null +++ b/test/umd/index-customized-min.html @@ -0,0 +1,209 @@ + + + + + + Progressive Image Grid: pig.js + + + +

Scroll to see some images...

+
+
+
+ + + + diff --git a/test/umd/index-customized.html b/test/umd/index-customized.html new file mode 100644 index 0000000..acd65ed --- /dev/null +++ b/test/umd/index-customized.html @@ -0,0 +1,209 @@ + + + + + + Progressive Image Grid: pig.js + + + +

Scroll to see some images...

+
+
+
+ + + + diff --git a/test/umd/index.html b/test/umd/index.html new file mode 100644 index 0000000..b5ba589 --- /dev/null +++ b/test/umd/index.html @@ -0,0 +1,127 @@ + + + + + + Progressive Image Grid: pig.js + + + +

Scroll to see some images...

+
+
+
+ + + +