diff --git a/.babelrc.json b/.babelrc.json new file mode 100644 index 0000000..7d25832 --- /dev/null +++ b/.babelrc.json @@ -0,0 +1,8 @@ +{ + "presets": [ + "@babel/preset-env" + ], + "plugins": [ + "@babel/plugin-proposal-class-properties" + ] +} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..1881c55 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,48 @@ +{ + "rules": { + "prefer-template": "off", + "no-var": 1, + "no-unused-vars": 1, + "camelcase": 1, + "no-nested-ternary": 1, + "no-console": 1, + "no-template-curly-in-string": 1, + "no-self-compare": 1, + "import/prefer-default-export": 0, + "arrow-body-style": 1, + "import/no-extraneous-dependencies": [ + "off", + { + "devDependencies": false + } + ] + }, + "ignorePatterns": [ + "dist", + "node_modules", + "webpack.*", + "config/paths.js" + ], + "env": { + "browser": true, + "es6": true + }, + "extends": [ + "eslint:recommended", + "prettier" + ], + "parserOptions": { + "ecmaVersion": 2021, + "sourceType": "module" + }, + "plugins": [ + "prettier" + ], + "settings": { + "import/resolver": { + "webpack": { + "config": "config/webpack.common.js" + } + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e6fdbc --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/package-lock.json +/node_modules/ +/.idea/ +/package-lock.json \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..56a830c --- /dev/null +++ b/.npmignore @@ -0,0 +1,11 @@ +.idea +.gitignore +.babelrc.json +.eslintrc.json +.prettierrc.json +jsconfig.json +/config +/public +/src +/web +/dev \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..edc0835 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "trailingComma": "es5", + "singleQuote": true, + "tabWidth": 2, + "printWidth": 100, + "semi": false +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..562b904 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +### v2.0.0 - 2022-11-11 + +- Refactor dev environment with webpack +- Support jQuery and vanilla JS +- Release NPM package + +### v1.1.1 - 2021-09-27 + +- Fix bug when `responsive` property is not defined. + +### v1.1.0 - 2021-09-24 + +- The core responsive handle has been split into `class ResponsiveObject()` so we can use this feature for other + libraries as well. + +> See [ResponsiveObject()](https://github.com/phucbm/js-gist/blob/main/responsive-object.js) + +### v1.0.0 - 2021-08-14 + +- jQuery plugin for Flickity responsive \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7db8016 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Phuc Bui + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 4bbae24..333df05 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,95 @@ -# Flickity responsive v1.1.1 [![](https://data.jsdelivr.com/v1/package/gh/phucbm/flickity-responsive/badge)](https://www.jsdelivr.com/package/gh/phucbm/flickity-responsive) +# Flickity Responsive -A jQuery plugin that adds `responsive` option for Flickity. +[![release](https://badgen.net/github/release/phucbm/flickity-responsive/)](https://github.com/phucbm/flickity-responsive/releases/latest) +[![minified](https://badgen.net/badge/minified/4KB/cyan)](https://www.jsdelivr.com/package/gh/phucbm/flickity-responsive) +[![jsdelivr](https://data.jsdelivr.com/v1/package/gh/phucbm/flickity-responsive/badge?style=rounded)](https://www.jsdelivr.com/package/gh/phucbm/flickity-responsive) +[![license](https://badgen.net/github/license/phucbm/flickity-responsive/)](https://github.com/phucbm/flickity-responsive/blob/main/LICENSE) -## Introduce +> A vanilla JS plugin that adds `responsive` option for Flickity. -> At the time of this plugin was made, Flickity does not officially offer any way to update the options on various screensizes. +## Introduction + +> At the time of this plugin was made, Flickity does not officially offer any way to update the options on various +> screensizes. Read more about the issue here 👉 https://github.com/metafizzy/flickity/issues/233 -So, I create an jQuery plugin that brings `responsive` to Flickity, just like the +So, I create a plugin that brings `responsive` to Flickity, just like the way [Slick](https://kenwheeler.github.io/slick/) works. -## Demo - -Check the latest demo on CodePen 👉 https://codepen.io/phucbui/pen/ExmJVZa - ## Getting started -Add `flickity-responsive.js` to your scripts, in this order 👇 +Add the script to your project in this order 👇 -- jQuery +- jQuery (optional) - Flickity -- `flickity-responsive.js` +- `flickity-responsive` -### Download +### NPM Package + +Install NPM package + +```shell +npm i flickity-responsive +``` + +Import + +```js +import {FlickityResponsive} from "flickity-responsive"; +``` -Directly from Github +### Download -[⬇️ flickity-responsive.js](https://raw.githubusercontent.com/phucbm/flickity-responsive/master/flickity-responsive.js) +👉 Self hosted - [Download the latest release](https://github.com/phucbm/flickity-responsive/releases/latest) -or +```html -### CDN + +``` -Get the latest minify version thanks for jsDelivr +👉 CDN Hosted - [jsDelivr](https://www.jsdelivr.com/package/gh/phucbm/flickity-responsive) ```html - + ``` ## Usage -Use `$('.carousel').flickityResponsive()` to initialize your carousel. +Just change the name, all other options stay the same. + +- `new Flickity()` => `new FlickityResponsive()`. +- `$('.carousel').flickity()` => `$('.carousel').flickityResponsive()`. ```js -// init flickity responsive +// init with vanilla JS +new FlickityResponsive('.carousel', { + cellAlign: "left", + contain: true, + freeScroll: true, + responsive: [ + { + breakpoint: 748, + settings: { + wrapAround: true, + cellAlign: "center", + freeScroll: false, + prevNextButtons: false, + pageDots: false + } + } + ] +}); + +// init with jQuery $('.carousel').flickityResponsive({ cellAlign: "left", contain: true, freeScroll: true, responsive: [ { - breakpoint: 1024, + breakpoint: 748, settings: { wrapAround: true, cellAlign: "center", @@ -67,24 +104,35 @@ $('.carousel').flickityResponsive({ ## FYI -This plugin uses Flickity's API and `matchMedia()` with some logics to decide when to destroy and re-initialize the +This plugin respects Flickity's API and use `matchMedia()` to know when to destroy and re-initialize the carousel. -> **⚠️ Important note**: the `breakpoint` property is using CSS `max-width` logic. For instance, when you set `breakpoint:480`, that means responsive settings will be applied when the viewport is `<=480px` (while Slick is `<480px`). Let's be cleared 💎 +> **⚠️ Important note**: the `breakpoint` property is using CSS `max-width` logic. For instance, when you +> set `breakpoint:480`, that means responsive settings will be applied when the viewport is `<=480px` (while Slick +> is `<480px`). Let's be cleared 💎 -## Changelog +## Deployment -### v1.1.1 - 2021-09-27 +### Dev server -- Fix bug when `responsive` property is not defined. +Run dev server -### v1.1.0 - 2021-09-24 +```shell +npm run dev +``` + +### Generate production files -- The core responsive handle has been split into `class ResponsiveObject()` so we can use this feature for other - libraries as well. +Generate UMD and module version + +```shell +npm run prod +``` -> See [ResponsiveObject()](https://github.com/phucbm/js-gist/blob/main/responsive-object.js) +### Build sites -### v1.0.0 - 2021-08-14 +Build production site -- jQuery plugin for Flickity responsive \ No newline at end of file +```shell +npm run build +``` \ No newline at end of file diff --git a/config/config.js b/config/config.js new file mode 100644 index 0000000..25282f3 --- /dev/null +++ b/config/config.js @@ -0,0 +1,104 @@ +const packageInfo = require('../package.json'); + +/** + * Environment variables + * scripts: cross-env NODE_ENV=development + */ +const env = process.env; + + +/** + * Banner + */ +const bannerConfig = { + banner: ` +/**! + * ${packageInfo.prettyName} v${packageInfo.version} + * @author ${packageInfo.author.name} + * @homepage ${packageInfo.homepage} + * @license ${packageInfo.license} ${new Date().getFullYear()} + */`, + raw: true +}; + + +/** + * Paths + */ +const path = require('path'); +const paths = { + // Source files + root: path.resolve(__dirname, '../'), + src: path.resolve(__dirname, '../src'), + entry: path.resolve(__dirname, '../src/_index.js'), + + // Production build files + dist: path.resolve(__dirname, '../dist'), + + // Build web + build: path.resolve(__dirname, '../build'), + + // Static files that get copied to build folder + public: path.resolve(__dirname, '../public'), +}; + + +/** + * Server + */ +const server = { + // Determine how modules within the project are treated + module: { + rules: [ + // JavaScript: Use Babel to transpile JavaScript files + {test: /\.js$/, use: ['babel-loader']}, + + // Images: Copy image files to build folder + {test: /\.(?:ico|gif|png|jpg|jpeg)$/i, type: 'asset/resource'}, + + // Fonts and SVGs: Inline files + {test: /\.(woff(2)?|eot|ttf|otf|svg|)$/, type: 'asset/inline'}, + + // HTML + {test: /\.html$/i, loader: "html-loader",}, + + // Styles: Inject CSS into the head with source maps + { + test: /\.(sass|scss|css)$/, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: {sourceMap: true, importLoaders: 1, modules: false}, + }, + {loader: 'postcss-loader', options: {sourceMap: true}}, + {loader: 'sass-loader', options: {sourceMap: true}}, + ], + }, + ], + }, + + resolve: { + modules: [paths.src, 'node_modules'], + extensions: ['.js', '.jsx', '.json'], + alias: { + '@': paths.src, + assets: paths.public, + }, + }, + + // Control how source maps are generated + devtool: 'inline-source-map', +}; + + +/** + * Export + */ +module.exports = { + paths, + packageInfo, + bannerConfig, + server, + env +}; \ No newline at end of file diff --git a/config/webpack.build.js b/config/webpack.build.js new file mode 100644 index 0000000..cd4d94d --- /dev/null +++ b/config/webpack.build.js @@ -0,0 +1,95 @@ +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); +const {merge} = require('webpack-merge'); +const {CleanWebpackPlugin} = require("clean-webpack-plugin"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const {paths, server, packageInfo, env} = require('./config'); +const path = require("path"); + +/** + * Sample variables: "cross-env ENTRY=web" + * ENTRY: folder to start building the bundle + */ +const entryFolder = env.ENTRY || 'web'; +const entryPath = path.resolve(__dirname, `../${entryFolder}`); + +module.exports = merge(server, { + mode: 'production', + devtool: false, + + // Where webpack looks to start building the bundle + entry: [entryPath + '/script.js'], + + output: { + path: paths.build, + publicPath: '/', + filename: 'js/[name].[contenthash].bundle.js', + }, + module: { + rules: [ + { + test: /\.(sass|scss|css)$/, + use: [ + MiniCssExtractPlugin.loader, + { + loader: 'css-loader', + options: { + importLoaders: 2, + sourceMap: false, + modules: false, + }, + }, + 'postcss-loader', + 'sass-loader', + ], + }, + ], + }, + plugins: [ + // Extracts CSS into separate files + new MiniCssExtractPlugin({ + filename: 'styles/[name].[contenthash].css', + chunkFilename: '[id].css', + }), + // Removes/cleans build folders and unused assets when rebuilding + new CleanWebpackPlugin(), + + // Copies files from target to destination folder + new CopyWebpackPlugin({ + patterns: [ + { + from: paths.public, + to: 'assets', + globOptions: { + ignore: ['*.DS_Store'], + }, + noErrorOnMissing: true, + }, + ], + }), + + // Generates an HTML file from a template + // Generates deprecation warning: https://github.com/jantimon/html-webpack-plugin/issues/1501 + new HtmlWebpackPlugin({ + inject: true, + hash: true, + title: packageInfo.prettyName, + favicon: paths.public + '/images/favicon.png', + template: entryPath + '/index.html', // template file + filename: 'index.html', // output file + }), + ], + optimization: { + minimize: true, + minimizer: [new CssMinimizerPlugin(), '...'], + runtimeChunk: { + name: 'runtime', + }, + }, + performance: { + hints: false, + maxEntrypointSize: 512000, + maxAssetSize: 512000, + }, +}) diff --git a/config/webpack.dev.js b/config/webpack.dev.js new file mode 100644 index 0000000..eb9e813 --- /dev/null +++ b/config/webpack.dev.js @@ -0,0 +1,70 @@ +const {merge} = require('webpack-merge'); +const {CleanWebpackPlugin} = require("clean-webpack-plugin"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const path = require("path"); +const {paths, packageInfo, server, env} = require("./config"); + +/** + * Sample variables: "cross-env ENTRY=dev PORT=8080" + * PORT: open a server in this port + * ENTRY: folder to start building the bundle + */ +const port = env.PORT || '8080'; +const entryFolder = env.ENTRY || 'dev'; +const entryPath = path.resolve(__dirname, `../${entryFolder}`); + +module.exports = merge(server, { + // Set the mode to development or production + mode: 'development', + + // Where webpack looks to start building the bundle + entry: [entryPath + '/script.js'], + + // Where webpack outputs the assets and bundles + output: { + path: paths.dist, + filename: '[name].bundle.js', + publicPath: '/', + }, + + // Customize the webpack build process + plugins: [ + // Removes/cleans build folders and unused assets when rebuilding + new CleanWebpackPlugin(), + + // Copies files from target to destination folder + new CopyWebpackPlugin({ + patterns: [ + { + from: paths.public, + to: 'assets', + globOptions: { + ignore: ['*.DS_Store'], + }, + noErrorOnMissing: true, + }, + ], + }), + + // Generates an HTML file from a template + // Generates deprecation warning: https://github.com/jantimon/html-webpack-plugin/issues/1501 + new HtmlWebpackPlugin({ + inject: true, + hash: true, + title: packageInfo.prettyName, + favicon: paths.public + '/images/favicon.png', + template: entryPath + '/index.html', // template file + filename: 'index.html', // output file + }), + ], + + // Spin up a server for quick development + devServer: { + historyApiFallback: true, + open: true, + compress: true, + hot: true, + port: port, + }, +}); \ No newline at end of file diff --git a/config/webpack.prod.js b/config/webpack.prod.js new file mode 100644 index 0000000..7296f3c --- /dev/null +++ b/config/webpack.prod.js @@ -0,0 +1,43 @@ +const TerserPlugin = require('terser-webpack-plugin'); +const webpack = require('webpack'); +const {paths, packageInfo, bannerConfig, env} = require('./config'); + +/** + * Sample variables: "cross-env TARGET=umd" + * TARGET: libraryTarget + */ +const libraryTarget = env.TARGET || 'umd'; +let filename, experiments = {}, library = undefined, path = undefined; +switch(libraryTarget){ + case "module": + filename = `${packageInfo.outputFilename}.module.js`; + experiments = { + outputModule: true, + }; + break; + default: + filename = `${packageInfo.outputFilename}.min.js`; + path = paths.root; +} + +module.exports = { + mode: 'production', + devtool: false, + entry: paths.entry, + experiments, + output: { + path, + filename, + library, + libraryTarget, + umdNamedDefine: true, + // prevent error: `Uncaught ReferenceError: self is not define` + globalObject: 'this', + }, + plugins: [ + new webpack.BannerPlugin(bannerConfig) + ], + optimization: { + minimizer: [new TerserPlugin({extractComments: false})], + }, +}; \ No newline at end of file diff --git a/dev/index.html b/dev/index.html new file mode 100644 index 0000000..c360dd0 --- /dev/null +++ b/dev/index.html @@ -0,0 +1,63 @@ + + + + + + + + Flickity Responsive + + + + + + + + + + + +
+ +

Flickity Responsive

+

+ +
+

Init with vanilla JS

+ + +
+ +
+

Init with jQuery

+ + +
+ +
+ + + diff --git a/dev/script.js b/dev/script.js new file mode 100644 index 0000000..26b33f2 --- /dev/null +++ b/dev/script.js @@ -0,0 +1,63 @@ +// public styles +import '../public/style/fonts.css'; + +// private style +import './style.scss'; + +// source script +import {FlickityResponsive} from "@/_index"; + +// import package info +const packageInfo = require('../package.json'); + +/** + * Update HTML + */ +// update title +const title = `${packageInfo.prettyName} v${packageInfo.version}`; +document.title = `[DEV] ${title} - ${packageInfo.description}`; +document.querySelector('[data-title]').innerHTML = title; +document.querySelector('[data-description]').innerHTML = packageInfo.description; + +/** + * Lib usage + */ +// init with vanilla JS +new FlickityResponsive('.carousel', { + cellAlign: "left", + contain: true, + freeScroll: true, + responsive: [ + { + breakpoint: 748, + settings: { + wrapAround: true, + cellAlign: "center", + freeScroll: false, + prevNextButtons: false, + pageDots: false + } + } + ] +}); + +// init with jQuery +if(typeof jQuery !== 'undefined'){ + jQuery('.carousel-2').flickityResponsive({ + cellAlign: "left", + contain: true, + freeScroll: true, + responsive: [ + { + breakpoint: 748, + settings: { + wrapAround: true, + cellAlign: "center", + freeScroll: false, + prevNextButtons: false, + pageDots: false + } + } + ] + }); +} \ No newline at end of file diff --git a/dev/style.scss b/dev/style.scss new file mode 100644 index 0000000..fe7a141 --- /dev/null +++ b/dev/style.scss @@ -0,0 +1,51 @@ +/** + * Variables + */ +$font-size: 1rem; +$font-family: 'Finlandica', sans-serif; +$page-width: 768px; +$color-primary: #f1c400; +$color-on-primary: #141414; + +/* external css: flickity.css */ + +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; +} + +.container { + max-width: $page-width; + margin-left: auto; + margin-right: auto; +} + +.carousel { + background: #FAFAFA; +} + +.carousel-cell { + width: 33%; + height: 200px; + margin-right: 10px; + background: #8C8; + border-radius: 5px; + counter-increment: carousel-cell; +} + +/* cell number */ +.carousel-cell:before { + display: block; + text-align: center; + content: counter(carousel-cell); + line-height: 200px; + font-size: 80px; + color: white; +} + +section { + margin-bottom: 100px; +} \ No newline at end of file diff --git a/dist/flickity-responsive.module.js b/dist/flickity-responsive.module.js new file mode 100644 index 0000000..9130f67 --- /dev/null +++ b/dist/flickity-responsive.module.js @@ -0,0 +1,13 @@ +/**! + * Flickity Responsive v2.0.0 + * @author phucbm + * @homepage https://github.com/phucbm/flickity-responsive + * @license MIT 2022 + */var t={d:(e,i)=>{for(var o in i)t.o(i,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:i[o]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{D:()=>a}); +/**! + * Match Media Screen v0.0.1 + * @author phucbm + * @homepage https://github.com/phucbm/match-media-screen + * @license MIT 2022 + */ +var i={d:(t,e)=>{for(var o in e)i.o(e,o)&&!i.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},o={};function n(t,e=!0){const i=[...t];return e?i.sort(((t,e)=>t.breakpointt.breakpoint>e.breakpoint?1:-1)),i}i.d(o,{o:()=>r});class r{constructor(t){if(this.object=t.object||void 0,this.object){if(this.onMatched=t.onMatched,this.onUpdate=t.onUpdate,!this.object.responsive)return this.currentObject={type:"no-responsive",lastBreakpoint:void 0,breakpoint:-1,object:this.mergeObject(-1,this.object)},"function"==typeof this.onMatched&&this.onMatched(this.currentObject),console.warn("Property object must have responsive array."),!1;this.isInherit=void 0===t.isInherit||t.isInherit,this.debounce=t.debounce||100,this.currentObject={breakpoint:void 0,object:{}},this.object.responsive=n(this.object.responsive),this.match(),window.addEventListener("resize",function(t,e=150){let i;return(...o)=>{clearTimeout(i),i=setTimeout((()=>{t.apply(this,o)}),e)}}((()=>this.match()),this.debounce))}else console.warn("Property object:{} must be provided.")}match(){let t=!1;for(let e=0;et&&(i={...e[o].settings,...i})}return i={...this.object,...i},delete i.responsive,i}}var s=o.o;const c=(t,e)=>{new s({object:e,onMatched:e=>{if("undefined"==typeof Flickity)return void console.warn("Flickity is undefined!");let i=Flickity.data(t);void 0!==i&&i.destroy(),i=new Flickity(t,e.object),i.resize()}})};class a{constructor(t,e){c(t,e)}}var h;"undefined"!=typeof jQuery&&((h=jQuery).fn.flickityResponsive=function(t){h(this).get().forEach((e=>c(e,t)))});var b=e.D;export{b as FlickityResponsive}; \ No newline at end of file diff --git a/flickity-responsive.js b/flickity-responsive.js deleted file mode 100644 index 105f802..0000000 --- a/flickity-responsive.js +++ /dev/null @@ -1,187 +0,0 @@ -/*! - * Flickity responsive v1.1.1 - * https://github.com/phucbm/flickity-responsive - */ - -(function($){ - $.fn.flickityResponsive = function(options){ - const $carousel = $(this); - new ResponsiveObject({ - object: options, - onMatched: (data) => { - if(typeof $carousel.flickity !== undefined){ - // if already have flickity init - if($carousel.data('flickity') !== undefined){ - // destroy - $carousel.flickity('destroy'); - } - - // then init flickity - $carousel.flickity(data.object); - - // and resize - $carousel.flickity('resize'); - }else{ - console.warn('Flickity is undefined!'); - } - } - }) - } -})(jQuery); - - -/*! - * Responsive Object v1.0.1 - * https://github.com/phucbm/js-gist/blob/main/responsive-object.js - */ -class ResponsiveObject{ - constructor(config){ - this.object = config.object || undefined; - if(!this.object) return; - - // callbacks - this.onMatched = config.onMatched || function(){ - }; - this.onUpdate = config.onUpdate || function(){ - }; - - // exit if there is no responsive object - if(!this.object.responsive){ - // update - this.currentObject = { - type: 'no-responsive', - lastBreakpoint: undefined, - breakpoint: -1, - object: this.mergeObject(-1, this.object) - }; - - // callback onMatched - if(typeof this.onMatched === 'function'){ - this.onMatched(this.currentObject); - } - return false; - } - - // if the current object don't have this key, search from the closest breakpoint above - this.isInherit = typeof config.isInherit === 'undefined' ? true : config.isInherit; - - /** Current object bases on responsive data **/ - this.currentObject = {breakpoint: undefined, object: {}}; - - /** Sort responsive breakpoints from big to small **/ - this.object.responsive = this.getSortedArray(this.object.responsive); - - /** Matching **/ - this.match(); - window.addEventListener('resize', () => { - this.match(); - }); - } - - match(){ - let isMatched = false; - - // loop through all breakpoints - for(let i = 0; i < this.object.responsive.length; i++){ - const breakpointData = this.object.responsive[i]; - - // match query - isMatched = matchMedia(this.getQuery(i)).matches; - - // if matched - if(isMatched){ - // and is a new breakpoint - if(this.currentObject.breakpoint !== breakpointData.breakpoint){ - // update - this.currentObject = { - type: 'responsive', - lastBreakpoint: this.currentObject.breakpoint, - breakpoint: breakpointData.breakpoint, - object: this.mergeObject(breakpointData.breakpoint, breakpointData.settings) - }; - - // callback onMatched - if(typeof this.onMatched === 'function'){ - this.onMatched(this.currentObject); - } - } - - // stop looping once matched - break; - } - } - - // if no matching - if(!isMatched && this.currentObject.breakpoint !== -1){ - // update - this.currentObject = { - type: 'default', - lastBreakpoint: this.currentObject.breakpoint, - breakpoint: -1, - object: this.mergeObject(-1, this.object) - }; - - // callback onMatched - if(typeof this.onMatched === 'function'){ - this.onMatched(this.currentObject); - } - } - - // callback onUpdate - if(typeof this.onUpdate === 'function'){ - this.onUpdate(this.currentObject); - } - } - - // get query string from breakpoint - getQuery(breakpointIndex){ - const breakpoint = this.object.responsive[breakpointIndex].breakpoint; - - let query = `screen and (max-width:${breakpoint}px)`; - - // set the min breakpoint if any - const nextBreakpoint = this.object.responsive[breakpointIndex + 1]; - if(nextBreakpoint){ - query += ` and (min-width:${nextBreakpoint.breakpoint + 1}px)` - } - - return query; - } - - getSortedArray(array, isASC = true){ - const newArray = [...array]; - - if(isASC){ - newArray.sort((a, b) => a.breakpoint < b.breakpoint && 1 || -1); - }else{ - newArray.sort((a, b) => a.breakpoint > b.breakpoint && 1 || -1); - } - - return newArray; - } - - mergeObject(breakpoint, newObject){ - // clone new object - let object = {...newObject}; - - // if is inherit, check for previous breakpoint - if(this.isInherit && breakpoint !== -1){ - const reversedBreakpoints = this.getSortedArray(this.object.responsive, false); - - for(let i = 0; i < reversedBreakpoints.length; i++){ - // only check for bigger breakpoint - if(reversedBreakpoints[i].breakpoint > breakpoint){ - object = {...reversedBreakpoints[i].settings, ...object}; - } - } - } - - // merge with default object - object = {...this.object, ...object}; - - // remove responsive property - delete object.responsive; - - return object; - } -} \ No newline at end of file diff --git a/flickity-responsive.min.js b/flickity-responsive.min.js new file mode 100644 index 0000000..1cd4649 --- /dev/null +++ b/flickity-responsive.min.js @@ -0,0 +1,14 @@ +/**! + * Flickity Responsive v2.0.0 + * @author phucbm + * @homepage https://github.com/phucbm/flickity-responsive + * @license MIT 2022 + */ +!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var o=t();for(var i in o)("object"==typeof exports?exports:e)[i]=o[i]}}(this,(()=>(()=>{"use strict";var e={d:(t,o)=>{for(var i in o)e.o(o,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:o[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{FlickityResponsive:()=>a}); +/**! + * Match Media Screen v0.0.1 + * @author phucbm + * @homepage https://github.com/phucbm/match-media-screen + * @license MIT 2022 + */ +var o={d:(e,t)=>{for(var i in t)o.o(t,i)&&!o.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},i={};function r(e,t=!0){const o=[...e];return t?o.sort(((e,t)=>e.breakpointe.breakpoint>t.breakpoint?1:-1)),o}o.d(i,{o:()=>n});class n{constructor(e){if(this.object=e.object||void 0,this.object){if(this.onMatched=e.onMatched,this.onUpdate=e.onUpdate,!this.object.responsive)return this.currentObject={type:"no-responsive",lastBreakpoint:void 0,breakpoint:-1,object:this.mergeObject(-1,this.object)},"function"==typeof this.onMatched&&this.onMatched(this.currentObject),console.warn("Property object must have responsive array."),!1;this.isInherit=void 0===e.isInherit||e.isInherit,this.debounce=e.debounce||100,this.currentObject={breakpoint:void 0,object:{}},this.object.responsive=r(this.object.responsive),this.match(),window.addEventListener("resize",function(e,t=150){let o;return(...i)=>{clearTimeout(o),o=setTimeout((()=>{e.apply(this,i)}),t)}}((()=>this.match()),this.debounce))}else console.warn("Property object:{} must be provided.")}match(){let e=!1;for(let t=0;te&&(o={...t[i].settings,...o})}return o={...this.object,...o},delete o.responsive,o}}var s=i.o;const c=(e,t)=>{new s({object:t,onMatched:t=>{if("undefined"==typeof Flickity)return void console.warn("Flickity is undefined!");let o=Flickity.data(e);void 0!==o&&o.destroy(),o=new Flickity(e,t.object),o.resize()}})};class a{constructor(e,t){c(e,t)}}var b;return"undefined"!=typeof jQuery&&((b=jQuery).fn.flickityResponsive=function(e){b(this).get().forEach((t=>c(t,e)))}),t})())); \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..5f5e5a8 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "paths": { + "@/*": [ + "./src/*" + ] + }, + "allowJs": true + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..648be2e --- /dev/null +++ b/package.json @@ -0,0 +1,61 @@ +{ + "name": "flickity-responsive", + "outputFilename": "flickity-responsive", + "prettyName": "Flickity Responsive", + "codeName": "FlickityResponsive", + "version": "2.0.0", + "description": "Responsive option for Flickity.", + "homepage": "https://github.com/phucbm/flickity-responsive", + "repository": { + "type": "git", + "url": "git@github.com:phucbm/flickity-responsive" + }, + "author": { + "name": "phucbm", + "url": "https://github.com/phucbm" + }, + "keywords": [ + "phucbm", + "javascript", + "flickity", + "responsive", + "es6" + ], + "main": "./dist/flickity-responsive.module.js", + "license": "MIT", + "scripts": { + "dev": "cross-env PORT=8081 webpack serve --config config/webpack.dev.js", + "build": "cross-env ENTRY=dev webpack --config config/webpack.build.js", + "prod": "webpack --config config/webpack.prod.js && cross-env TARGET=module webpack --config config/webpack.prod.js", + "publish": "npm run prod & npm publish" + }, + "devDependencies": { + "@babel/core": "^7.15.8", + "@babel/plugin-proposal-class-properties": "^7.14.5", + "@babel/preset-env": "^7.15.8", + "@viivue/atomic-css": "^1.1.4", + "babel-loader": "^8.2.2", + "babel-preset-es2015": "^6.24.1", + "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^9.1.0", + "cross-env": "^7.0.3", + "css-loader": "^6.4.0", + "css-minimizer-webpack-plugin": "^3.1.1", + "html-loader": "^3.1.0", + "html-webpack-plugin": "^5.3.2", + "mini-css-extract-plugin": "^2.4.2", + "postcss-loader": "^6.2.0", + "postcss-preset-env": "^6.7.0", + "sass": "^1.43.5", + "sass-loader": "^12.2.0", + "style-loader": "^3.3.0", + "terser-webpack-plugin": "^5.3.1", + "webpack": "^5.58.2", + "webpack-cli": "^4.9.0", + "webpack-dev-server": "^4.3.1", + "webpack-merge": "^5.8.0" + }, + "dependencies": { + "match-media-screen": "^0.0.1" + } +} diff --git a/public/fonts/Finlandica-Bold.woff b/public/fonts/Finlandica-Bold.woff new file mode 100644 index 0000000..37c2c3d Binary files /dev/null and b/public/fonts/Finlandica-Bold.woff differ diff --git a/public/fonts/Finlandica-Bold.woff2 b/public/fonts/Finlandica-Bold.woff2 new file mode 100644 index 0000000..e5b2949 Binary files /dev/null and b/public/fonts/Finlandica-Bold.woff2 differ diff --git a/public/fonts/Finlandica-BoldItalic.woff b/public/fonts/Finlandica-BoldItalic.woff new file mode 100644 index 0000000..f8f29a6 Binary files /dev/null and b/public/fonts/Finlandica-BoldItalic.woff differ diff --git a/public/fonts/Finlandica-BoldItalic.woff2 b/public/fonts/Finlandica-BoldItalic.woff2 new file mode 100644 index 0000000..b5b87f5 Binary files /dev/null and b/public/fonts/Finlandica-BoldItalic.woff2 differ diff --git a/public/fonts/Finlandica-Italic.woff b/public/fonts/Finlandica-Italic.woff new file mode 100644 index 0000000..6d4bb23 Binary files /dev/null and b/public/fonts/Finlandica-Italic.woff differ diff --git a/public/fonts/Finlandica-Italic.woff2 b/public/fonts/Finlandica-Italic.woff2 new file mode 100644 index 0000000..391fb64 Binary files /dev/null and b/public/fonts/Finlandica-Italic.woff2 differ diff --git a/public/fonts/Finlandica-Regular.woff b/public/fonts/Finlandica-Regular.woff new file mode 100644 index 0000000..3fcc1e0 Binary files /dev/null and b/public/fonts/Finlandica-Regular.woff differ diff --git a/public/fonts/Finlandica-Regular.woff2 b/public/fonts/Finlandica-Regular.woff2 new file mode 100644 index 0000000..5479a7b Binary files /dev/null and b/public/fonts/Finlandica-Regular.woff2 differ diff --git a/public/images/favicon.png b/public/images/favicon.png new file mode 100644 index 0000000..06ec22a Binary files /dev/null and b/public/images/favicon.png differ diff --git a/public/style/fonts.css b/public/style/fonts.css new file mode 100644 index 0000000..cc4de6f --- /dev/null +++ b/public/style/fonts.css @@ -0,0 +1,38 @@ +/** + * Fonts + */ +@font-face { + font-family:"Finlandica"; + src:url("../fonts/Finlandica-Bold.woff2") format("woff2"), + url("../fonts/Finlandica-Bold.woff") format("woff"); + font-weight:bold; + font-style:normal; + font-display:swap; +} + +@font-face { + font-family:"Finlandica"; + src:url("../fonts/Finlandica-BoldItalic.woff2") format("woff2"), + url("../fonts/Finlandica-BoldItalic.woff") format("woff"); + font-weight:bold; + font-style:italic; + font-display:swap; +} + +@font-face { + font-family:"Finlandica"; + src:url("../fonts/Finlandica-Italic.woff2") format("woff2"), + url("../fonts/Finlandica-Italic.woff") format("woff"); + font-weight:normal; + font-style:italic; + font-display:swap; +} + +@font-face { + font-family:"Finlandica"; + src:url("../fonts/Finlandica-Regular.woff2") format("woff2"), + url("../fonts/Finlandica-Regular.woff") format("woff"); + font-weight:normal; + font-style:normal; + font-display:swap; +} \ No newline at end of file diff --git a/src/_index.js b/src/_index.js new file mode 100644 index 0000000..110aecc --- /dev/null +++ b/src/_index.js @@ -0,0 +1,54 @@ +import {MatchMediaScreen} from "match-media-screen"; + +/** + * Init Flickity Responsive + * @param el + * @param options + */ +const init = (el, options) => { + new MatchMediaScreen({ + object: options, + onMatched: (data) => { + // skip if Flickity is undefined + if(typeof Flickity === 'undefined'){ + console.warn('Flickity is undefined!'); + return; + } + + // get instance + let customFlickity = Flickity.data(el); + + // destroy if instance is found + if(typeof customFlickity !== 'undefined') customFlickity.destroy(); + + // init new instance + customFlickity = new Flickity(el, data.object); + + // resize + customFlickity.resize(); + } + }); +} + + +/** + * Public class + * keep this class public to support legacy versions + */ +export class FlickityResponsive{ + constructor(el, options){ + init(el, options); + } +} + + +/** + * jQuery plugin + */ +if(typeof jQuery !== 'undefined'){ + (function($){ + $.fn.flickityResponsive = function(options){ + $(this).get().forEach(el => init(el, options)); + } + })(jQuery); +} \ No newline at end of file diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..d1c9f99 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,97 @@ +/** + * Debounce (ignore all, run the last) + * https://www.freecodecamp.org/news/javascript-debounce-example/ + * @param func + * @param timeout + * @returns {(function(...[*]): void)|*} + */ +export function debounce(func, timeout = 150){ + let timer; + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => { + func.apply(this, args); + }, timeout); + }; +} + + +/** + * Debounce leading (run the first, ignore the rest) + * https://www.freecodecamp.org/news/javascript-debounce-example/ + * @param func + * @param timeout + * @returns {(function(...[*]): void)|*} + */ +export function debounceLeading(func, timeout = 150){ + let timer; + return (...args) => { + if(!timer){ + func.apply(this, args); + } + clearTimeout(timer); + timer = setTimeout(() => { + timer = undefined; + }, timeout); + }; +} + + +/** + * Get array with unique values + * https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates + * @param array + * @returns {*} + */ +export function arrayUnique(array){ + function onlyUnique(value, index, self){ + return self.indexOf(value) === index; + } + + return array.filter(onlyUnique); +} + + +/** + * Sort array of integers + * @param array + * @param asc + * @returns {*} + */ +export function arraySortInteger(array, asc = true){ + return array.sort(function(a, b){ + return asc ? a - b : b - a; + }); +} + + +/** + * Set CSS + * @param target + * @param props + */ +export function setCSS(target, props){ + Object.assign(target.style, props); +} + + +/** + * Console log + * @param context + * @param status + * @param message + */ +export function log(context, status, ...message){ + if(context.options.dev){ + console?.[status](...message); + } +} + + +/** + * Generate unique ID + */ +export function uniqueId(prefix = ''){ + return prefix + (+new Date()).toString(16) + + (Math.random() * 100000000 | 0).toString(16); +} \ No newline at end of file diff --git a/test/flickity.min.css b/test/flickity.min.css deleted file mode 100644 index 18829b0..0000000 --- a/test/flickity.min.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! Flickity v2.2.2 -https://flickity.metafizzy.co ----------------------------------------------- */ -.flickity-enabled{position:relative}.flickity-enabled:focus{outline:0}.flickity-viewport{overflow:hidden;position:relative;height:100%}.flickity-slider{position:absolute;width:100%;height:100%}.flickity-enabled.is-draggable{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.flickity-enabled.is-draggable .flickity-viewport{cursor:move;cursor:-webkit-grab;cursor:grab}.flickity-enabled.is-draggable .flickity-viewport.is-pointer-down{cursor:-webkit-grabbing;cursor:grabbing}.flickity-button{position:absolute;background:hsla(0,0%,100%,.75);border:none;color:#333}.flickity-button:hover{background:#fff;cursor:pointer}.flickity-button:focus{outline:0;box-shadow:0 0 0 5px #19f}.flickity-button:active{opacity:.6}.flickity-button:disabled{opacity:.3;cursor:auto;pointer-events:none}.flickity-button-icon{fill:currentColor}.flickity-prev-next-button{top:50%;width:44px;height:44px;border-radius:50%;transform:translateY(-50%)}.flickity-prev-next-button.previous{left:10px}.flickity-prev-next-button.next{right:10px}.flickity-rtl .flickity-prev-next-button.previous{left:auto;right:10px}.flickity-rtl .flickity-prev-next-button.next{right:auto;left:10px}.flickity-prev-next-button .flickity-button-icon{position:absolute;left:20%;top:20%;width:60%;height:60%}.flickity-page-dots{position:absolute;width:100%;bottom:-25px;padding:0;margin:0;list-style:none;text-align:center;line-height:1}.flickity-rtl .flickity-page-dots{direction:rtl}.flickity-page-dots .dot{display:inline-block;width:10px;height:10px;margin:0 8px;background:#333;border-radius:50%;opacity:.25;cursor:pointer}.flickity-page-dots .dot.is-selected{opacity:1} \ No newline at end of file diff --git a/test/flickity.pkgd.min.js b/test/flickity.pkgd.min.js deleted file mode 100644 index db7e391..0000000 --- a/test/flickity.pkgd.min.js +++ /dev/null @@ -1,56 +0,0 @@ -/*! - * Flickity PACKAGED v2.2.2 - * Touch, responsive, flickable carousels - * - * Licensed GPLv3 for open source use - * or Flickity Commercial License for commercial use - * - * https://flickity.metafizzy.co - * Copyright 2015-2021 Metafizzy - */ -(function(e,i){if(typeof define=="function"&&define.amd){define("jquery-bridget/jquery-bridget",["jquery"],function(t){return i(e,t)})}else if(typeof module=="object"&&module.exports){module.exports=i(e,require("jquery"))}else{e.jQueryBridget=i(e,e.jQuery)}})(window,function t(e,r){"use strict";var o=Array.prototype.slice;var i=e.console;var u=typeof i=="undefined"?function(){}:function(t){i.error(t)};function n(h,s,c){c=c||r||e.jQuery;if(!c){return}if(!s.prototype.option){s.prototype.option=function(t){if(!c.isPlainObject(t)){return}this.options=c.extend(true,this.options,t)}}c.fn[h]=function(t){if(typeof t=="string"){var e=o.call(arguments,1);return i(this,t,e)}n(this,t);return this};function i(t,r,o){var a;var l="$()."+h+'("'+r+'")';t.each(function(t,e){var i=c.data(e,h);if(!i){u(h+" not initialized. Cannot call methods, i.e. "+l);return}var n=i[r];if(!n||r.charAt(0)=="_"){u(l+" is not a valid method");return}var s=n.apply(i,o);a=a===undefined?s:a});return a!==undefined?a:t}function n(t,n){t.each(function(t,e){var i=c.data(e,h);if(i){i.option(n);i._init()}else{i=new s(e,n);c.data(e,h,i)}})}a(c)}function a(t){if(!t||t&&t.bridget){return}t.bridget=n}a(r||e.jQuery);return n});(function(t,e){if(typeof define=="function"&&define.amd){define("ev-emitter/ev-emitter",e)}else if(typeof module=="object"&&module.exports){module.exports=e()}else{t.EvEmitter=e()}})(typeof window!="undefined"?window:this,function(){function t(){}var e=t.prototype;e.on=function(t,e){if(!t||!e){return}var i=this._events=this._events||{};var n=i[t]=i[t]||[];if(n.indexOf(e)==-1){n.push(e)}return this};e.once=function(t,e){if(!t||!e){return}this.on(t,e);var i=this._onceEvents=this._onceEvents||{};var n=i[t]=i[t]||{};n[e]=true;return this};e.off=function(t,e){var i=this._events&&this._events[t];if(!i||!i.length){return}var n=i.indexOf(e);if(n!=-1){i.splice(n,1)}return this};e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(!i||!i.length){return}i=i.slice(0);e=e||[];var n=this._onceEvents&&this._onceEvents[t];for(var s=0;s