diff --git a/demo/sites/methods/touch.html b/demo/sites/methods/touch.html new file mode 100644 index 000000000..23cd68788 --- /dev/null +++ b/demo/sites/methods/touch.html @@ -0,0 +1,515 @@ + + + + + + + + Touch + + + + + + + + + + + + + + + + +
+
+
+
+

+ Press +

+ +
+ +
+
+

+ Hold the button to remove the mask from the image +

+
+
+
+ + + +
+
+
+ +

+ Press duration +

+ +
+ +
+
+

+ Hold the button to remove the mask from the image with 5 seconds +

+
+
+
+ + +
+
+
+ +

+ Tap +

+ +
+ +
+
+

Tap button to change a color

+
+
+
+ + +
+
+
+ +

+ Double Tap +

+ +
+ +
+
+

+ Change background color with 2 taps +

+
+
+
+ + +
+
+
+ +

+ Pan +

+ +
+ +
+
+
+
+ +

+ Pan Left +

+ +
+ +
+
+
+
+ +

+ Pan Right +

+ +
+ +
+
+
+
+ +

+ Pan Up/Down +

+ +
+ +
+
+
+
+ +

+ Pinch +

+ +
+ +
+
+
+
+ +

+ Swipe left/right +

+ +
+ +
+
+

+ Swipe Left-Right to change a color +

+
+
+
+
+
+
+ +

+ Swipe up/down +

+ +
+ +
+
+

+ Swipe Up-Down to change a color +

+
+
+
+
+
+
+ +

+ Rotate +

+ +
+ +
+
+
+
+
+ + + + + + + + + + + + diff --git a/index.html b/index.html index 955a62be5..55084375f 100644 --- a/index.html +++ b/index.html @@ -434,6 +434,11 @@

Your sites:

class="mb-1 mr-1 rounded bg-primary px-6 pb-2 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-[0_4px_9px_-4px_#3b71ca] transition duration-150 ease-in-out hover:bg-primary-600 hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:bg-primary-600 focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:outline-none focus:ring-0 active:bg-primary-700 active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)]" >Lightbox + Touch + Import + +
  • + Usage +
  • +
  • + Options +
  • +
  • + Methods +
  • +
  • + Events +
  • diff --git a/site/content/docs/standard/methods/touch/a.html b/site/content/docs/standard/methods/touch/a.html new file mode 100644 index 000000000..110d21597 --- /dev/null +++ b/site/content/docs/standard/methods/touch/a.html @@ -0,0 +1,931 @@ +--- +--- + + +
    + +
    + +

    + Import +

    + +

    + Importing components depends on how your application works. If you intend + to use the Tailwind Elements ES format, you must first import + the component and then initialize it with the initTE method. + If you are going to use the UMD format, just import the + tw-elements package. +

    + + + {{< twsnippet/no-demo id="api-example1" >}} + + +
    + + +
    + + +
    + +

    + Usage +

    + +

    Via data attributes

    + + + {{< twsnippet/no-demo id="api-example2" >}} + + + +

    Via JavaScript

    + + + {{< twsnippet/no-demo id="api-example3" >}} + + + +
    + +
    + + +
    + +

    + Options +

    + +

    + Options can be passed via data attributes or JavaScript. For data + attributes, append the option name to data-te-, as in + data-te-event="press". +

    + +

    + Touch +

    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + +
    + Name + + Type + + Default + Description
    + event + + String + + 'swipe' + + Event type. Can be 'pan', 'pinch', + 'press', 'rotate', + 'swipe', 'tap'. +
    +
    +
    +
    +
    + +
    + +

    + Pan +

    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Name + + Type + + Default + Description
    + direction + + String + + 'all' + + Set direction to pan. Available options: all, + right, left, up, + down. +
    + pointers + + Number + + 1 + + Set default value of number for pointers. +
    + threshold + + Number + + 20 + + Set distance bettwen when event fires. +
    +
    +
    +
    +
    + +
    + +

    + Pinch +

    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + Name + + Type + + Default + Description
    + pointers + + Number + + 2 + + Option for tap event, set duration to how long + it takes to take a tap. +
    + threshold + + Number + + 10 + + Set distance bettwen when event fires. +
    +
    +
    +
    +
    + +
    + +

    + Press +

    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + Name + + Type + + Default + Description
    + pointers + + Number + + 1 + + Set default value of number for pointers. +
    + time + + Number + + 250 + + Set time delays to take tap. +
    +
    +
    +
    +
    + +
    + +

    + Rotate +

    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + Name + + Type + + Default + Description
    + angle + + Number + + 0 + + Set started angle to rotate. +
    + pointers + + Number + + 2 + + Set default value of number for pointers. +
    +
    +
    +
    +
    + +
    + +

    + Swipe +

    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + Name + + Type + + Default + Description
    + direction + + String + + 'all' + + Set direction to pan. Available options: all, + right, left, up, + down. +
    + treshold + + Number + + 10 + + Set distance bettwen when event fires. +
    +
    +
    +
    +
    + +
    + +

    + Tap +

    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Name + + Type + + Default + Description
    + interval + + Number + + 500 + + Set interval to tap. +
    + pointers + + Number + + 1 + + Set default value of number for pointers. +
    + taps + + Number + + 1 + + Set default value of number for taps. +
    + time + + Number + + 250 + + Set delays time to tap event. +
    +
    +
    +
    +
    +
    + + +
    + + +
    + +

    + Methods +

    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Method + + Description + Example
    + dispose + + Manually disposes an instance. + + touch.dispose(); +
    + getInstance + + Static method which allows you to get the touch instance + associated to a DOM element. + + Touch.getInstance(element) +
    + getOrCreateInstance + + Static method which returns the touch instance associated to + a DOM element or create a new one in case it wasn't + initialized. + + Touch.getOrCreateInstance(element) +
    +
    +
    +
    +
    + + + {{< twsnippet/no-demo id="api-example4" >}} + + +
    + + +
    + + +
    + +

    + Events +

    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Event type + Description
    + pan + + This event fires pan touch funcionality. +
    + pinch + + This event fires pinch touch funcionality. +
    + press + + This event fires press touch funcionality. +
    + rotate + + This event fires rotate touch funcionality. +
    + swipe + + This event fires swipe touch funcionality. +
    + tap + + This event fires tap touch funcionality. +
    +
    +
    +
    +
    + + + {{< twsnippet/no-demo id="api-example5" >}} + + +
    + +
    diff --git a/site/content/docs/standard/methods/touch/index-js.html b/site/content/docs/standard/methods/touch/index-js.html new file mode 100644 index 000000000..0b3e72ee1 --- /dev/null +++ b/site/content/docs/standard/methods/touch/index-js.html @@ -0,0 +1,169 @@ +--- +--- + + + diff --git a/site/content/docs/standard/methods/touch/index-ss.html b/site/content/docs/standard/methods/touch/index-ss.html new file mode 100644 index 000000000..3a1638773 --- /dev/null +++ b/site/content/docs/standard/methods/touch/index-ss.html @@ -0,0 +1,42 @@ +--- +--- + +
  • + Press +
  • +
  • + Duration +
  • +
  • + Tap +
  • +
  • + Double Tap +
  • +
  • + Pan +
  • +
  • + Pan Left +
  • +
  • + Pan Right +
  • +
  • + Pan Up/Down +
  • +
  • + Pinch +
  • +
  • + Swipe Left/Right +
  • +
  • + Swipe Up/Down +
  • +
  • + Rotate +
  • +
  • + Related resources +
  • diff --git a/site/content/docs/standard/methods/touch/index.html b/site/content/docs/standard/methods/touch/index.html new file mode 100644 index 000000000..f8434d31c --- /dev/null +++ b/site/content/docs/standard/methods/touch/index.html @@ -0,0 +1,1586 @@ +--- +title: "Touch" +date: 2023-03-07T16:00:58+02:00 +draft: false +main_title: "Tailwind Touch method" +subheading: "Tailwind CSS Touch" +seo_title: "Tailwind CSS Touch - Free Examples & Tutorial" +description: "This component allows you to improve the user experience on mobile touch screens by calling methods on the following custom events: pinch, swipe, tap, press, pan and rotate." +image: "" +video: "https://www.youtube.com/watch?v=-GmnyjgI4Jc&ab_channel=Keepcoding" +url: "/docs/standard/methods/touch/" +menu: + methods: + name: "Touch" +autoinits: "Touch" +--- + + +
    + +

    + Press +

    + +

    + Press calls the chosen method when the touch event on the element lasts + longer than 250 milliseconds. +

    + + + {{< twsnippet/demo id="example1">}} +
    +
    +
    + +
    +
    +

    + Hold the button to remove the mask from the image +

    +
    +
    +
    + + +
    +
    + {{< /twsnippet/demo >}} + + + +
    + + + +
    + +
    + + + +
    + +

    + Press duration +

    + +

    Touch event press with custom duration.

    + + + {{< twsnippet/demo id="example2">}} +
    +
    +
    + +
    +
    +

    + Hold the button to remove the mask from the image with 5 seconds +

    +
    +
    +
    + + +
    +
    + {{< /twsnippet/demo >}} + + + +
    + + + +
    + +

    + Tap +

    + +

    + The callback on tap event is called with an object containing origin field - + the x and y cooridinates of the user's touch. +

    + + + {{< twsnippet/demo id="example3">}} +
    +
    +
    + +
    +
    +

    Tap button to change a color

    +
    +
    +
    + + +
    +
    + {{< /twsnippet/demo >}} + + + +
    + + + +
    + +

    + Double Tap +

    + +

    Set default taps to touch event.

    + + + {{< twsnippet/demo id="example4">}} +
    +
    +
    + +
    +
    +

    + Change background color with 2 taps +

    +
    +
    +
    + + +
    +
    + {{< /twsnippet/demo >}} + + + +
    + + + +
    + +

    + Pan +

    + +

    + The pan event is useful for dragging elements - every time the user moves a + finger on the element to which the directive is attached to, the given + method is being called with an argument consisting of two keys: x and y (the + values corresponds to the horizontal and vertical translation). +

    + + + {{< twsnippet/demo id="example5">}} +
    +
    + +
    +
    + {{< /twsnippet/demo >}} + + + +
    + + + +
    + +

    + Pan Left +

    + +

    Pan with only left direction.

    + + + {{< twsnippet/demo id="example6">}} +
    +
    + +
    +
    + {{< /twsnippet/demo >}} + + + +
    + + + +
    + +

    + Pan Right +

    + +

    Pan with only right direction.

    + + + {{< twsnippet/demo id="example7">}} +
    +
    + +
    +
    + {{< /twsnippet/demo >}} + + + +
    + + + +
    + +

    + Pan Up/Down +

    + +

    Pan with only up/down direction.

    + + + {{< twsnippet/demo id="example8">}} +
    +
    + +
    +
    + {{< /twsnippet/demo >}} + + + +
    + + + +
    + +

    + Pinch +

    + +

    + The pinch event calls the given method with an object containing two keys - + ratio and origin. The first one it the ratio of the distance between user's + fingers on touchend to the same distance on touchstart - it's particularly + useful for scaling images. The second one, similarly as in doubleTap event, + is a pair of coordinates indicating the middle point between the user's + fingers. +

    + + + {{< twsnippet/demo id="example9">}} +
    +
    + +
    +
    + {{< /twsnippet/demo >}} + + + +
    + + + +
    + +

    + Swipe Left/Right +

    + +

    + The swipe event comes with several modifiers (left, right, up, down) - each + of them will ensure that event will fire only on swipe in that particular + direction. If the directive is used without any modifier, the callback + method will be called each time the swiping occurs, and the string + indicating the direction will be passed as an argument. +

    + +

    This example shows example with left and right:

    + + + {{< twsnippet/demo id="example10">}} +
    +
    + +
    +
    +

    + Swipe Left-Right to change a color +

    +
    +
    +
    +
    + {{< /twsnippet/demo >}} + + + +
    + + + +
    + +

    + Swipe Up/Down +

    + +

    This example shows example with up and down:

    + + + {{< twsnippet/demo id="example11">}} +
    +
    + +
    +
    +

    Swipe Up-Down to change a color

    +
    +
    +
    +
    + {{< /twsnippet/demo >}} + + + +
    + + + +
    + +

    + Rotate +

    + +

    This example shows example with rotate:

    + + + {{< twsnippet/demo id="example12">}} +
    +
    + +
    +
    + {{< /twsnippet/demo >}} + + + +
    + + +
    + + + + diff --git a/site/static/search.json b/site/static/search.json index 638a54cb5..ac29795bb 100644 --- a/site/static/search.json +++ b/site/static/search.json @@ -841,6 +841,15 @@ "scrollbar": 1 } }, + { + "href": "/docs/standard/methods/touch/", + "name": "Touch", + "keywords": ["touch"], + "category": "Methods", + "priority": { + "touch": 1 + } + }, { "href": "/docs/standard/methods/smooth-scroll/", "name": "Smooth scroll", diff --git a/src/js/autoinit/index.js b/src/js/autoinit/index.js index a0e5b2d5f..a3b734fc6 100644 --- a/src/js/autoinit/index.js +++ b/src/js/autoinit/index.js @@ -194,6 +194,10 @@ const defaultInitSelectors = { isToggler: true, callback: lightboxCallback, }, + touch: { + name: "Touch", + selector: "[data-te-touch-init]", + }, }; const getComponentData = (component) => { diff --git a/src/js/index.es.js b/src/js/index.es.js index 4e2f9b073..290a8647f 100644 --- a/src/js/index.es.js +++ b/src/js/index.es.js @@ -33,10 +33,11 @@ import ChipsInput from "./components/chips"; import Chip from "./components/chips/chip"; import Chart from "./data/chart/charts"; import PerfectScrollbar from "./methods/perfect-scrollbar"; -import Datatable from "./data/datatables/index"; +import Datatable from "./data/datatables"; import Rating from "./components/rating"; import Popconfirm from "./components/popconfirm"; import Lightbox from "./components/lightbox"; +import Touch from "./methods/touch"; import SmoothScroll from "./methods/smooth-scroll"; import LazyLoad from "./methods/lazy-load"; import Clipboard from "./methods/clipboard"; @@ -71,6 +72,7 @@ export { Rating, Popconfirm, Lightbox, + Touch, SmoothScroll, LazyLoad, Clipboard, diff --git a/src/js/index.umd.js b/src/js/index.umd.js index 24c466075..b02313f37 100644 --- a/src/js/index.umd.js +++ b/src/js/index.umd.js @@ -33,10 +33,11 @@ import ChipsInput from "./components/chips"; import Chip from "./components/chips/chip"; import Chart from "./data/chart/charts"; import PerfectScrollbar from "./methods/perfect-scrollbar"; -import Datatable from "./data/datatables/index"; +import Datatable from "./data/datatables"; import Rating from "./components/rating"; import Popconfirm from "./components/popconfirm"; import Lightbox from "./components/lightbox"; +import Touch from "./methods/touch"; import SmoothScroll from "./methods/smooth-scroll"; import LazyLoad from "./methods/lazy-load"; import Clipboard from "./methods/clipboard"; @@ -72,6 +73,7 @@ const te = { Popconfirm, SmoothScroll, Lightbox, + Touch, LazyLoad, Clipboard, }; @@ -107,6 +109,7 @@ export { Rating, Popconfirm, Lightbox, + Touch, SmoothScroll, LazyLoad, Clipboard, diff --git a/src/js/methods/touch/index.js b/src/js/methods/touch/index.js new file mode 100644 index 000000000..a86c02c54 --- /dev/null +++ b/src/js/methods/touch/index.js @@ -0,0 +1,149 @@ +/* +-------------------------------------------------------------------------- +Tailwind Elements is an open-source UI kit of advanced components for TailwindCSS. +Copyright © 2023 MDBootstrap.com + +Unless a custom, individually assigned license has been granted, this program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +In addition, a custom license may be available upon request, subject to the terms and conditions of that license. Please contact tailwind@mdbootstrap.com for more information on obtaining a custom license. +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. +-------------------------------------------------------------------------- +*/ + +import Data from "../../dom/data"; +import EventHandler from "../../dom/event-handler"; +import Press from "./press"; +import Swipe from "./swipe"; +import Pan from "./pan"; +import Pinch from "./pinch"; +import Tap from "./tap"; +import Rotate from "./rotate"; +import { typeCheckConfig } from "../../util"; +import Manipulator from "../../dom/manipulator"; + +const NAME = "touch"; +const DATA_KEY = `te.${NAME}`; + +const DefaultType = { + event: "string", +}; + +const Default = { + event: "swipe", +}; + +class Touch { + constructor(element, options = {}) { + this._element = element; + this._options = this._getConfig(options); + this._event = this._options.event; + // events + + this.swipe = this._event === "swipe" ? new Swipe(element, options) : null; + this.press = this._event === "press" ? new Press(element, options) : null; + this.pan = this._event === "pan" ? new Pan(element, options) : null; + this.pinch = this._event === "pinch" ? new Pinch(element, options) : null; + this.tap = this._event === "tap" ? new Tap(element, options) : null; + this.rotate = + this._event === "rotate" ? new Rotate(element, options) : null; + + // handlers + + this._touchStartHandler = (e) => this._handleTouchStart(e); + this._touchMoveHandler = (e) => this._handleTouchMove(e); + this._touchEndHandler = (e) => this._handleTouchEnd(e); + + // istanbul ignore next + EventHandler.on(this._element, "touchstart", this._touchStartHandler); + + // istanbul ignore next + EventHandler.on(this._element, "touchmove", this._touchMoveHandler); + + // istanbul ignore next + EventHandler.on(this._element, "touchend", this._touchEndHandler); + + if (this._element) { + Data.setData(element, DATA_KEY, this); + } + } + + // Getters + static get NAME() { + return NAME; + } + + dispose() { + EventHandler.off(this._element, "touchstart", this._touchStartHandler); + EventHandler.off(this._element, "touchmove", this._touchMoveHandler); + EventHandler.off(this._element, "touchend", this._touchEndHandler); + + this.swipe = null; + this.press = null; + this.pan = null; + this.pinch = null; + this.tap = null; + this.rotate = null; + } + + _getConfig(options) { + const config = { + ...Default, + ...Manipulator.getDataAttributes(this._element), + ...options, + }; + + typeCheckConfig(NAME, config, DefaultType); + + return config; + } + + _handleTouchStart(e) { + this[this._event].handleTouchStart(e); + } + + _handleTouchMove(e) { + if (this[this._event].handleTouchMove) { + this[this._event].handleTouchMove(e); + } + } + + _handleTouchEnd(e) { + this[this._event].handleTouchEnd(e); + } + + static jQueryInterface(config) { + return this.each(function () { + let data = Data.getData(this, DATA_KEY); + const _config = typeof config === "object" && config; + + if (!data && /dispose/.test(config)) { + return; + } + + if (!data) { + data = new Touch(this, _config); + } + + if (typeof config === "string") { + if (typeof data[config] === "undefined") { + throw new TypeError(`No method named "${config}"`); + } + + // eslint-disable-next-line consistent-return + return data[config]; + } + }); + } + + static getInstance(element) { + return Data.getData(element, DATA_KEY); + } + + static getOrCreateInstance(element, config = {}) { + return ( + this.getInstance(element) || + new this(element, typeof config === "object" ? config : null) + ); + } +} + +export default Touch; diff --git a/src/js/methods/touch/pan.js b/src/js/methods/touch/pan.js new file mode 100644 index 000000000..51ad3ebd8 --- /dev/null +++ b/src/js/methods/touch/pan.js @@ -0,0 +1,123 @@ +/* +-------------------------------------------------------------------------- +Tailwind Elements is an open-source UI kit of advanced components for TailwindCSS. +Copyright © 2023 MDBootstrap.com + +Unless a custom, individually assigned license has been granted, this program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +In addition, a custom license may be available upon request, subject to the terms and conditions of that license. Please contact tailwind@mdbootstrap.com for more information on obtaining a custom license. +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. +-------------------------------------------------------------------------- +*/ + +import TouchUtil from "./touchUtil"; +import EventHandler from "../../dom/event-handler"; +import { typeCheckConfig } from "../../util"; +import Manipulator from "../../dom/manipulator"; + +const NAME = "pan"; +const EVENT_START = `${NAME}start`; +const EVENT_END = `${NAME}end`; +const EVENT_MOVE = `${NAME}move`; +const LEFT = "left"; +const RIGHT = "right"; + +const DefaultType = { + threshold: "number", + direction: "string", + pointers: "number", +}; + +const Default = { + threshold: 20, + direction: "all", + pointers: 1, +}; + +class Pan extends TouchUtil { + constructor(element, options = {}) { + super(); + this._element = element; + this._options = this._getConfig(options); + this._startTouch = null; + } + + // Getters + + static get NAME() { + return NAME; + } + + handleTouchStart(e) { + this._startTouch = this._getCoordinates(e); + this._movedTouch = e; + + EventHandler.trigger(this._element, EVENT_START, { touch: e }); + } + + handleTouchMove(e) { + // eslint-disable-next-line no-unused-expressions + e.type === "touchmove" && e.preventDefault(); + + const { threshold, direction } = this._options; + const postion = this._getCoordinates(e); + const movedPosition = this._getCoordinates(this._movedTouch); + + const displacement = this._getOrigin(postion, this._startTouch); + const displacementMoved = this._getOrigin(postion, movedPosition); + + const pan = this._getDirection(displacement); + const movedDirection = this._getDirection(displacementMoved); + + const { x, y } = pan; + + if (direction === "all" && (y.value > threshold || x.value > threshold)) { + const direction = y.value > x.value ? y.direction : x.direction; + + EventHandler.trigger(this._element, `${NAME}${direction}`, { touch: e }); + EventHandler.trigger(this._element, NAME, { + ...displacementMoved, + touch: e, + }); + } + + const axis = direction === LEFT || direction === RIGHT ? "x" : "y"; + + if ( + movedDirection[axis].direction === direction && + pan[axis].value > threshold + ) { + EventHandler.trigger(this._element, `${NAME}${direction}`, { + touch: e, + [axis]: postion[axis] - movedPosition[axis], + }); + } + + this._movedTouch = e; + + EventHandler.trigger(this._element, EVENT_MOVE, { touch: e }); + } + + handleTouchEnd(e) { + // eslint-disable-next-line no-unused-expressions + e.type === "touchend" && e.preventDefault(); + + this._movedTouch = null; + this._startTouch = null; + + EventHandler.trigger(this._element, EVENT_END, { touch: e }); + } + + _getConfig(options) { + const config = { + ...Default, + ...Manipulator.getDataAttributes(this._element), + ...options, + }; + + typeCheckConfig(NAME, config, DefaultType); + + return config; + } +} + +export default Pan; diff --git a/src/js/methods/touch/pinch.js b/src/js/methods/touch/pinch.js new file mode 100644 index 000000000..4981b487d --- /dev/null +++ b/src/js/methods/touch/pinch.js @@ -0,0 +1,134 @@ +/* +-------------------------------------------------------------------------- +Tailwind Elements is an open-source UI kit of advanced components for TailwindCSS. +Copyright © 2023 MDBootstrap.com + +Unless a custom, individually assigned license has been granted, this program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +In addition, a custom license may be available upon request, subject to the terms and conditions of that license. Please contact tailwind@mdbootstrap.com for more information on obtaining a custom license. +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. +-------------------------------------------------------------------------- +*/ + +import TouchUtil from "./touchUtil"; +import EventHandler from "../../dom/event-handler"; +import Manipulator from "../../dom/manipulator"; +import { typeCheckConfig } from "../../util"; + +const NAME = "pinch"; +const EVENT_END = `${NAME}end`; +const EVENT_START = `${NAME}start`; +const EVENT_MOVE = `${NAME}move`; + +const DefaultType = { + threshold: "number", + pointers: "number", +}; + +const Default = { + threshold: 10, + pointers: 2, +}; + +class Pinch extends TouchUtil { + constructor(element, options = {}) { + super(); + this._element = element; + this._options = this._getConfig(options); + this._startTouch = null; + this._origin = null; + this._touch = null; + this._math = null; + this._ratio = null; + } + + // Getters + + static get NAME() { + return NAME; + } + + get isNumber() { + return ( + typeof this._startTouch === "number" && + typeof this._touch === "number" && + // eslint-disable-next-line no-restricted-globals + !isNaN(this._startTouch) && + // eslint-disable-next-line no-restricted-globals + !isNaN(this._touch) + ); + } + + handleTouchStart(e) { + if (e.touches.length !== this._options.pointers) return; + + // eslint-disable-next-line no-unused-expressions + e.type === "touchstart" && e.preventDefault(); + + const [touch, origin] = this._getPinchTouchOrigin(e.touches); + + this._touch = touch; + this._origin = origin; + this._startTouch = this._touch; + + EventHandler.trigger(this._element, EVENT_START, { + touch: e, + ratio: this._ratio, + origin: this._origin, + }); + } + + handleTouchMove(e) { + const { threshold, pointers } = this._options; + + if (e.touches.length !== pointers) return; + + // eslint-disable-next-line no-unused-expressions + e.type === "touchmove" && e.preventDefault(); + + this._touch = this._getPinchTouchOrigin(e.touches)[0]; + this._ratio = this._touch / this._startTouch; + + if (this.isNumber) { + if (this._origin.x > threshold || this._origin.y > threshold) { + this._startTouch = this._touch; + + EventHandler.trigger(this._element, NAME, { + touch: e, + ratio: this._ratio, + origin: this._origin, + }); + EventHandler.trigger(this._element, EVENT_MOVE, { + touch: e, + ratio: this._ratio, + origin: this._origin, + }); + } + } + } + + handleTouchEnd(e) { + if (this.isNumber) { + this._startTouch = null; + + EventHandler.trigger(this._element, EVENT_END, { + touch: e, + ratio: this._ratio, + origin: this._origin, + }); + } + } + + _getConfig(options) { + const config = { + ...Default, + ...Manipulator.getDataAttributes(this._element), + ...options, + }; + + typeCheckConfig(NAME, config, DefaultType); + + return config; + } +} + +export default Pinch; diff --git a/src/js/methods/touch/press.js b/src/js/methods/touch/press.js new file mode 100644 index 000000000..d2fc0376d --- /dev/null +++ b/src/js/methods/touch/press.js @@ -0,0 +1,72 @@ +/* +-------------------------------------------------------------------------- +Tailwind Elements is an open-source UI kit of advanced components for TailwindCSS. +Copyright © 2023 MDBootstrap.com + +Unless a custom, individually assigned license has been granted, this program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +In addition, a custom license may be available upon request, subject to the terms and conditions of that license. Please contact tailwind@mdbootstrap.com for more information on obtaining a custom license. +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. +-------------------------------------------------------------------------- +*/ + +import TouchUtil from "./touchUtil"; +import EventHandler from "../../dom/event-handler"; +import { typeCheckConfig } from "../../util"; +import Manipulator from "../../dom/manipulator"; + +const NAME = "press"; +const EVENT_UP = "pressup"; + +const DefaultType = { + time: "number", + pointers: "number", +}; + +const Default = { + time: 250, + pointers: 1, +}; + +class Press extends TouchUtil { + constructor(element, options = {}) { + super(); + this._element = element; + this._options = this._getConfig(options); + this._timer = null; + } + + // Getters + + static get NAME() { + return NAME; + } + + handleTouchStart(e) { + const { time, pointers } = this._options; + + if (e.touches.length === pointers) { + this._timer = setTimeout(() => { + EventHandler.trigger(this._element, NAME, { touch: e, time }); + EventHandler.trigger(this._element, EVENT_UP, { touch: e }); + }, time); + } + } + + handleTouchEnd() { + clearTimeout(this._timer); + } + + _getConfig(options) { + const config = { + ...Default, + ...Manipulator.getDataAttributes(this._element), + ...options, + }; + + typeCheckConfig(NAME, config, DefaultType); + + return config; + } +} + +export default Press; diff --git a/src/js/methods/touch/rotate.js b/src/js/methods/touch/rotate.js new file mode 100644 index 000000000..f8d79b469 --- /dev/null +++ b/src/js/methods/touch/rotate.js @@ -0,0 +1,141 @@ +/* +-------------------------------------------------------------------------- +Tailwind Elements is an open-source UI kit of advanced components for TailwindCSS. +Copyright © 2023 MDBootstrap.com + +Unless a custom, individually assigned license has been granted, this program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +In addition, a custom license may be available upon request, subject to the terms and conditions of that license. Please contact tailwind@mdbootstrap.com for more information on obtaining a custom license. +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. +-------------------------------------------------------------------------- +*/ + +/* eslint-disable no-multi-assign */ +import TouchUtil from "./touchUtil"; +import EventHandler from "../../dom/event-handler"; +import { typeCheckConfig } from "../../util"; +import Manipulator from "../../dom/manipulator"; + +const NAME = "rotate"; +const EVENT_END = `${NAME}end`; +const EVENT_START = `${NAME}start`; + +const DefaultType = { + angle: "number", + pointers: "number", +}; + +const Default = { + angle: 0, + pointers: 2, +}; + +class Rotate extends TouchUtil { + constructor(element, options) { + super(); + this._element = element; + this._options = this._getConfig(options); + this._origin = {}; + } + + // Getters + + static get NAME() { + return NAME; + } + + handleTouchStart(e) { + // eslint-disable-next-line no-unused-expressions + e.type === "touchstart" && e.preventDefault(); + + if (e.touches.length < 2) return; + this._startTouch = e; + this._origin = {}; + EventHandler.trigger(this._element, EVENT_START, { touch: e }); + return; + } + + handleTouchMove(e) { + // eslint-disable-next-line no-unused-expressions + e.type === "touchmove" && e.preventDefault(); + + let origin; + let input; + const touches = e.touches; + + if (touches.length === 1 && this._options.pointers === 1) { + const { left, top, width, height } = + this._element.getBoundingClientRect(); + origin = { + x: left + width / 2, + y: top + height / 2, + }; + + input = touches[0]; + } else if (e.touches.length === 2 && this._options.pointers === 2) { + const [t2, t1] = e.touches; + const _position = { + x1: t1.clientX, + x2: t2.clientX, + y1: t1.clientY, + y2: t2.clientY, + }; + + origin = this._getMidPoint(_position); + input = this._getRightMostTouch(e.touches); + } else { + return; + } + + this.currentAngle = this._getAngle( + origin.x, + origin.y, + input.clientX, + input.clientY + ); + + if (!this._origin.initialAngle) { + this._origin.initialAngle = this._origin.previousAngle = + this.currentAngle; + this._origin.distance = this._origin.change = 0; + } else { + this._origin.change = this._getAngularDistance( + this._origin.previousAngle, + this.currentAngle + ); + this._origin.distance += this._origin.change; + } + + this._origin.previousAngle = this.currentAngle; + + this.rotate = { + currentAngle: this.currentAngle, + distance: this._origin.distance, + change: this._origin.change, + }; + + EventHandler.trigger(this._element, NAME, { ...this.rotate, touch: e }); + } + + handleTouchEnd(e) { + // eslint-disable-next-line no-unused-expressions + e.type === "touchend" && e.preventDefault(); + + this._origin = {}; + + EventHandler.trigger(this._element, EVENT_END, { touch: e }); + } + + _getConfig(options) { + const config = { + ...Default, + ...Manipulator.getDataAttributes(this._element), + ...options, + }; + + typeCheckConfig(NAME, config, DefaultType); + + return config; + } +} + +export default Rotate; diff --git a/src/js/methods/touch/swipe.js b/src/js/methods/touch/swipe.js new file mode 100644 index 000000000..ec4c85354 --- /dev/null +++ b/src/js/methods/touch/swipe.js @@ -0,0 +1,119 @@ +/* +-------------------------------------------------------------------------- +Tailwind Elements is an open-source UI kit of advanced components for TailwindCSS. +Copyright © 2023 MDBootstrap.com + +Unless a custom, individually assigned license has been granted, this program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +In addition, a custom license may be available upon request, subject to the terms and conditions of that license. Please contact tailwind@mdbootstrap.com for more information on obtaining a custom license. +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. +-------------------------------------------------------------------------- +*/ + +import EventHandler from "../../dom/event-handler"; +import { typeCheckConfig } from "../../util"; +import Manipulator from "../../dom/manipulator"; + +const NAME = "swipe"; + +const DefaultType = { + threshold: "number", + direction: "string", +}; + +const Default = { + threshold: 10, + direction: "all", +}; + +class Swipe { + constructor(element, options) { + this._element = element; + this._startPosition = null; + this._options = this._getConfig(options); + } + + handleTouchStart(e) { + this._startPosition = this._getCoordinates(e); + } + + handleTouchMove(e) { + if (!this._startPosition) return; + + const position = this._getCoordinates(e); + const displacement = { + x: position.x - this._startPosition.x, + y: position.y - this._startPosition.y, + }; + + const swipe = this._getDirection(displacement); + + if (this._options.direction === "all") { + if ( + swipe.y.value < this._options.threshold && + swipe.x.value < this._options.threshold + ) { + return; + } + const direction = + swipe.y.value > swipe.x.value ? swipe.y.direction : swipe.x.direction; + EventHandler.trigger(this._element, `swipe${direction}`, { touch: e }); + EventHandler.trigger(this._element, "swipe", { touch: e, direction }); + this._startPosition = null; + return; + } + + const axis = + this._options.direction === "left" || this._options === "right" + ? "x" + : "y"; + + if ( + swipe[axis].direction === this._options.direction && + swipe[axis].value > this._options.threshold + ) { + EventHandler.trigger(this._element, `swipe${swipe[axis].direction}`, { + touch: e, + }); + this._startPosition = null; + } + } + + handleTouchEnd() { + this._startPosition = null; + } + + _getCoordinates(e) { + const [touch] = e.touches; + return { + x: touch.clientX, + y: touch.clientY, + }; + } + + _getDirection(displacement) { + return { + x: { + direction: displacement.x < 0 ? "left" : "right", + value: Math.abs(displacement.x), + }, + y: { + direction: displacement.y < 0 ? "up" : "down", + value: Math.abs(displacement.y), + }, + }; + } + + _getConfig(options) { + const config = { + ...Default, + ...Manipulator.getDataAttributes(this._element), + ...options, + }; + + typeCheckConfig(NAME, config, DefaultType); + + return config; + } +} + +export default Swipe; diff --git a/src/js/methods/touch/tap.js b/src/js/methods/touch/tap.js new file mode 100644 index 000000000..40b460441 --- /dev/null +++ b/src/js/methods/touch/tap.js @@ -0,0 +1,98 @@ +/* +-------------------------------------------------------------------------- +Tailwind Elements is an open-source UI kit of advanced components for TailwindCSS. +Copyright © 2023 MDBootstrap.com + +Unless a custom, individually assigned license has been granted, this program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +In addition, a custom license may be available upon request, subject to the terms and conditions of that license. Please contact tailwind@mdbootstrap.com for more information on obtaining a custom license. +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. +-------------------------------------------------------------------------- +*/ + +import TouchUtil from "./touchUtil"; +import EventHandler from "../../dom/event-handler"; +import { typeCheckConfig } from "../../util"; +import Manipulator from "../../dom/manipulator"; + +const NAME = "tap"; + +const DefaultType = { + interval: "number", + time: "number", + taps: "number", + pointers: "number", +}; + +const Default = { + interval: 500, + time: 250, + taps: 1, + pointers: 1, +}; + +class Tap extends TouchUtil { + constructor(element, options) { + super(); + this._element = element; + this._options = this._getConfig(options); + this._timer = null; + this._tapCount = 0; + } + + // Getters + + static get NAME() { + return NAME; + } + + handleTouchStart(e) { + const { x, y } = this._getCoordinates(e); + const { interval, taps, pointers } = this._options; + + if (e.touches.length === pointers) { + this._tapCount += 1; + + if (this._tapCount === 1) { + this._timer = setTimeout(() => { + this._tapCount = 0; + }, interval); + } + + if (this._tapCount === taps) { + clearTimeout(this._timer); + this._tapCount = 0; + EventHandler.trigger(this._element, NAME, { + touch: e, + origin: { + x, + y, + }, + }); + } + } + + return e; + } + + handleTouchEnd() { + return; + } + + handleTouchMove() { + return; + } + + _getConfig(options) { + const config = { + ...Default, + ...Manipulator.getDataAttributes(this._element), + ...options, + }; + + typeCheckConfig(NAME, config, DefaultType); + + return config; + } +} + +export default Tap; diff --git a/src/js/methods/touch/touchUtil.js b/src/js/methods/touch/touchUtil.js new file mode 100644 index 000000000..856e7aa74 --- /dev/null +++ b/src/js/methods/touch/touchUtil.js @@ -0,0 +1,101 @@ +/* +-------------------------------------------------------------------------- +Tailwind Elements is an open-source UI kit of advanced components for TailwindCSS. +Copyright © 2023 MDBootstrap.com + +Unless a custom, individually assigned license has been granted, this program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +In addition, a custom license may be available upon request, subject to the terms and conditions of that license. Please contact tailwind@mdbootstrap.com for more information on obtaining a custom license. +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. +-------------------------------------------------------------------------- +*/ + +class TouchUtil { + _getCoordinates(e) { + const [touch] = e.touches; + + return { + x: touch.clientX, + y: touch.clientY, + }; + } + + _getDirection({ x, y }) { + return { + x: { + direction: x < 0 ? "left" : "right", + value: Math.abs(x), + }, + y: { + direction: y < 0 ? "up" : "down", + value: Math.abs(y), + }, + }; + } + + _getOrigin({ x, y }, { x: x2, y: y2 }) { + return { + x: x - x2, + y: y - y2, + }; + } + + _getDistanceBetweenTwoPoints(x1, x2, y1, y2) { + return Math.hypot(x2 - x1, y2 - y1); + } + + _getMidPoint({ x1, x2, y1, y2 }) { + return { + x: (x1 + x2) / 2, + y: (y1 + y2) / 2, + }; + } + + _getVectorLength({ x1, x2, y1, y2 }) { + return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); + } + + _getRightMostTouch(touches) { + let rightMost = null; + const distance = Number.MIN_VALUE; + touches.forEach((touch) => { + if (touch.clientX > distance) { + rightMost = touch; + } + }); + return rightMost; + } + + _getAngle(x1, y1, x2, y2) { + return Math.atan2(y2 - y1, x2 - x1); + } + + _getAngularDistance(start, end) { + return end - start; + } + + _getCenterXY({ x1, x2, y1, y2 }) { + return { + x: x1 + (x2 - x1) / 2, + y: y1 + (y2 - y1) / 2, + }; + } + + _getPinchTouchOrigin(touches) { + const [t1, t2] = touches; + + const _position = { + x1: t1.clientX, + x2: t2.clientX, + y1: t1.clientY, + y2: t2.clientY, + }; + + return [this._getVectorLength(_position), this._getCenterXY(_position)]; + } + + _getPosition({ x1, x2, y1, y2 }) { + return { x1, x2, y1, y2 }; + } +} + +export default TouchUtil;