├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── elm.json ├── examples ├── .gitignore ├── DragAndDrop │ ├── DragPorts.js │ ├── Main.elm │ ├── elm.json │ └── index.html ├── FileDrop │ ├── Main.elm │ ├── SSCCE.elm │ ├── elm.json │ └── index.html ├── Mouse │ ├── Main.elm │ ├── elm.json │ └── index.html ├── Pointer │ ├── Main.elm │ ├── elm-pep.js │ ├── elm.json │ └── index.html ├── Touch │ ├── Main.elm │ ├── elm.json │ └── index.html └── Wheel │ ├── Main.elm │ ├── elm.json │ └── index.html ├── src ├── DragPorts.elm ├── DragPorts.js ├── Html │ └── Events │ │ └── Extra │ │ ├── Drag.elm │ │ ├── Mouse.elm │ │ ├── Pointer.elm │ │ ├── Touch.elm │ │ └── Wheel.elm └── Internal │ └── Decode.elm └── upgrade.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | 4 | # Created by https://www.gitignore.io/api/vim,osx,elm,linux,windows 5 | 6 | ### Elm ### 7 | # elm-package generated files 8 | elm-stuff 9 | # elm-repl generated files 10 | repl-temp-* 11 | 12 | ### Linux ### 13 | *~ 14 | 15 | # temporary files which can be created if a process still has a handle open of a deleted file 16 | .fuse_hidden* 17 | 18 | # KDE directory preferences 19 | .directory 20 | 21 | # Linux trash folder which might appear on any partition or disk 22 | .Trash-* 23 | 24 | # .nfs files are created when an open file is removed but is still being accessed 25 | .nfs* 26 | 27 | ### OSX ### 28 | *.DS_Store 29 | .AppleDouble 30 | .LSOverride 31 | 32 | # Icon must end with two \r 33 | Icon 34 | 35 | # Thumbnails 36 | ._* 37 | 38 | # Files that might appear in the root of a volume 39 | .DocumentRevisions-V100 40 | .fseventsd 41 | .Spotlight-V100 42 | .TemporaryItems 43 | .Trashes 44 | .VolumeIcon.icns 45 | .com.apple.timemachine.donotpresent 46 | 47 | # Directories potentially created on remote AFP share 48 | .AppleDB 49 | .AppleDesktop 50 | Network Trash Folder 51 | Temporary Items 52 | .apdisk 53 | 54 | ### Vim ### 55 | # swap 56 | [._]*.s[a-v][a-z] 57 | [._]*.sw[a-p] 58 | [._]s[a-v][a-z] 59 | [._]sw[a-p] 60 | # session 61 | Session.vim 62 | # temporary 63 | .netrwhist 64 | # auto-generated tag files 65 | tags 66 | 67 | ### Windows ### 68 | # Windows thumbnail cache files 69 | Thumbs.db 70 | ehthumbs.db 71 | ehthumbs_vista.db 72 | 73 | # Folder config file 74 | Desktop.ini 75 | 76 | # Recycle Bin used on file shares 77 | $RECYCLE.BIN/ 78 | 79 | # Windows Installer files 80 | *.cab 81 | *.msi 82 | *.msm 83 | *.msp 84 | 85 | # Windows shortcuts 86 | *.lnk 87 | 88 | # End of https://www.gitignore.io/api/vim,osx,elm,linux,windows 89 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## Unreleased - [(diff with 5.0.0)][diff-unreleased] 6 | 7 | ## [5.0.0] - 2023-04-29 - [(diff with 4.0.0)][diff-5.0.0] 8 | 9 | ### Added 10 | 11 | - The `Mouse` and `Touch` events add support for the `meta` key. 12 | This is a breaking change as the `Keys` type alias has a new `meta` key. 13 | 14 | ## [4.0.0] - 2019-01-04 - [(diff with 3.1.0)][diff-4.0.0] 15 | 16 | ### Added 17 | 18 | - Dependency to `File` type in elm/file. 19 | 20 | ### Changed 21 | 22 | - Keep the examples in the tagged commit for people coming from 23 | the package website. 24 | 25 | ### Removed 26 | 27 | - Previous `File` type alias. 28 | - File decoder (just use the one from elm/file now). 29 | 30 | ## [3.1.0] - 2018-09-27 - [(diff with 3.0.0)][diff-3.1.0] 31 | 32 | ### Added 33 | 34 | - Exposed the forgotten type `EventOptions`. 35 | 36 | ### Changed 37 | 38 | - Do not stop propagation anymore by default 39 | (Except for drag events). 40 | 41 | ## [3.0.0] - 2018-08-21 - [(diff with 2.0.0)][diff-3.0.0] 42 | 43 | ### Added 44 | 45 | - This `CHANGELOG` to record important changes. 46 | - Drag events are now all supported. 47 | - File drop example. 48 | - Drag and drop example. 49 | - Port files to setup drag ports `src/DragPorts.js` and `src/DragPorts.elm`. 50 | 51 | ### Changed 52 | 53 | - This is now an elm 0.19 package so there are upgrade changes. 54 | - All modules are now under the namespace `Html.Events.Extra`. 55 | For example `Html.Events.Extra.Mouse`. 56 | - Tagged version commit is orphanned and stripped down 57 | to only keep the necessary for elm packaging. 58 | 59 | ### Removed 60 | 61 | - `elm-pep/` is not anymore a git sub-module. 62 | - Previous drag example. 63 | 64 | ## [2.0.0] - 2018-02-18 - [(diff with 1.0.0)][diff-2.0.0] 65 | 66 | ### Added 67 | 68 | - `src/Mouse.elm` module to handle mouse events. 69 | - `src/Touch.elm` module to handle touch events. 70 | - `src/Wheel.elm` module to handle wheel events. 71 | - `src/Drag.elm` module to handle drag events. 72 | - Examples for the Mouse, Touch, Wheel and Drag modules. 73 | 74 | ### Changed 75 | 76 | - Update `README` to reflect addition of mouse, touch, wheel and drag events. 77 | - Improve `elm-pep/` polyfill to better handle Apple devices. 78 | 79 | ## [1.0.0] - 2017-10-17 80 | 81 | ### Added 82 | 83 | - `src/Pointer.elm` module providing pointer events to elm. 84 | - `examples/` containing one fully functional pointer events example. 85 | - `elm-pep/` as a submodule to a pointer event polyfill. 86 | - `README` describing this package. 87 | - `LICENSE` under MPL-2.0. 88 | 89 | [5.0.0]: https://github.com/mpizenberg/elm-pointer-events/releases/tag/5.0.0 90 | [4.0.0]: https://github.com/mpizenberg/elm-pointer-events/releases/tag/4.0.0 91 | [3.1.0]: https://github.com/mpizenberg/elm-pointer-events/releases/tag/3.1.0 92 | [3.0.0]: https://github.com/mpizenberg/elm-pointer-events/releases/tag/3.0.0 93 | [2.0.0]: https://github.com/mpizenberg/elm-pointer-events/releases/tag/2.0.0 94 | [1.0.0]: https://github.com/mpizenberg/elm-pointer-events/releases/tag/1.0.0 95 | [diff-unreleased]: https://github.com/mpizenberg/elm-pointer-events/compare/5.0.0...HEAD 96 | [diff-5.0.0]: https://github.com/mpizenberg/elm-pointer-events/compare/4.0.0...5.0.0 97 | [diff-4.0.0]: https://github.com/mpizenberg/elm-pointer-events/compare/3.1.0...4.0.0 98 | [diff-3.1.0]: https://github.com/mpizenberg/elm-pointer-events/compare/3.0.0...3.1.0 99 | [diff-3.0.0]: https://github.com/mpizenberg/elm-pointer-events/compare/2.0.0...3.0.0 100 | [diff-2.0.0]: https://github.com/mpizenberg/elm-pointer-events/compare/1.0.0...2.0.0 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elm-pointer-events 2 | 3 | [![][badge-license]][license] 4 | [![][badge-doc]][doc] 5 | 6 | [badge-doc]: https://img.shields.io/badge/documentation-latest-yellow.svg?style=flat-square 7 | [doc]: http://package.elm-lang.org/packages/mpizenberg/elm-pointer-events/latest 8 | [badge-license]: https://img.shields.io/badge/license-MPL--2.0-blue.svg?style=flat-square 9 | [license]: https://www.mozilla.org/en-US/MPL/2.0/ 10 | 11 | > If upgrading from [elm-mouse-events] or [elm-touch-events], 12 | > please read the [upgrade notes][upgrade]. 13 | > Otherwise, if upgrading from elm-pointer-events 2.0.0, 14 | > reading the [CHANGELOG][changelog] should be enough. 15 | 16 | [elm-mouse-events]: https://github.com/mpizenberg/elm-mouse-events 17 | [elm-touch-events]: https://github.com/mpizenberg/elm-touch-events 18 | [upgrade]: https://github.com/mpizenberg/elm-pointer-events/blob/master/upgrade.md 19 | [changelog]: https://github.com/mpizenberg/elm-pointer-events/blob/master/CHANGELOG.md 20 | 21 | ```elm 22 | import Html.Events.Extra.Pointer as Pointer 23 | -- ... example usage 24 | div [ Pointer.onDown (\event -> PointerDownMsg event.pointer.offsetPos) ] [ text "click here" ] 25 | ``` 26 | 27 | This package aims at handling all kinds of pointer events in elm. 28 | To be more specific, this means: 29 | 30 | - [`MouseEvent`][mouse-events]: standard mouse events 31 | - [`WheelEvent`][wheel-events]: standard wheel events 32 | - [`DragEvent`][drag-events]: HTML5 drag events 33 | - [`TouchEvent`][touch-events]: standard touch events 34 | - [`PointerEvent`][pointer-events]: new pointer events 35 | 36 | If you are looking for only one standard kind of interaction (mouse or touch), 37 | I recommend that you use the `Mouse` or `Touch` modules. 38 | If however, you are designing a multi-device (desktop/tablet/mobile/...) experience, 39 | I recommend that you use the `Pointer` module. 40 | 41 | Pointer events are a unified interface for similar input devices 42 | (mouse, pen, touch, ...). 43 | Since maintaining both mouse and touch events for compatibility 44 | is really cumbersome, using a unified pointer events interface 45 | is a relief. 46 | 47 | Beware though, that the pointer API is not well supported by all browsers. 48 | Firefox < 59 and Safari do not natively support pointer events. 49 | So I strongly recommend to use this package in pair with the [elm-pep polyfill][elm-pep] 50 | for compatibility with major browsers. 51 | 52 | [mouse-events]: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent 53 | [wheel-events]: https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent 54 | [drag-events]: https://developer.mozilla.org/en-US/docs/Web/API/DragEvent 55 | [touch-events]: https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent 56 | [pointer-events]: https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent 57 | [elm-pep]: https://github.com/mpizenberg/elm-pep 58 | 59 | ## Usage 60 | 61 | ### Mouse and Pointer 62 | 63 | The `Mouse` and `Pointer` modules have very similar API 64 | so I will use `Mouse` as an example. 65 | Let's say you want the coordinates of a mouse down event relative to the DOM 66 | element that triggered it. 67 | In JavaScript, these are provided by the [`offsetX` and `offsetY` properties][offsetx] 68 | of the mouse event. 69 | Using this module, these are similarly provided by the `offsetPos` attribute 70 | of a mouse `Event`: 71 | 72 | ```elm 73 | import Html.Events.Extra.Mouse as Mouse 74 | 75 | -- ... 76 | 77 | type Msg 78 | = MouseDownAt ( Float, Float ) 79 | 80 | view = 81 | div 82 | [Mouse.onDown (\event -> MouseDownAt event.offsetPos)] 83 | [text "click here"] 84 | ``` 85 | 86 | If you are using the `Pointer` module, 87 | it is recommended that you deactivate `touch-action` 88 | to disable browsers scroll/pinch/... touch behaviors. 89 | 90 | Also, if you are designing some kind of drawing application, 91 | you want to be able to keep track of a pointer that leave the 92 | drawing area to know if the pointer went up. 93 | This is possible using what is called [pointer capture][pointer-capture]. 94 | But requires the use of ports. Look at `examples/Pointer/` 95 | if you are interested in how to do this. 96 | 97 | ```elm 98 | div 99 | [ Pointer.onDown ... 100 | , Pointer.onMove ... 101 | , Pointer.onUp ... 102 | 103 | -- no touch-action 104 | , Html.Attributes.style "touch-action" "none" 105 | ] 106 | [ -- the drawing area 107 | ] 108 | ``` 109 | 110 | [offsetx]: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/offsetX 111 | [pointer-capture]: https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture 112 | 113 | ### Touch 114 | 115 | Multi-touch interactions can be managed using the `Touch` module. 116 | In case you only want to handle single touch interactions, 117 | you could do something like below: 118 | 119 | ```elm 120 | import Html.Events.Extra.Touch as Touch 121 | 122 | -- ... 123 | 124 | type Msg 125 | = StartAt ( Float, Float ) 126 | | MoveAt ( Float, Float ) 127 | | EndAt ( Float, Float ) 128 | 129 | view = 130 | div 131 | [ Touch.onStart (StartAt << touchCoordinates) 132 | , Touch.onMove (MoveAt << touchCoordinates) 133 | , Touch.onEnd (EndAt << touchCoordinates) 134 | ] 135 | [text "touch here"] 136 | 137 | touchCoordinates : Touch.Event -> ( Float, Float ) 138 | touchCoordinates touchEvent = 139 | List.head touchEvent.changedTouches 140 | |> Maybe.map .clientPos 141 | |> Maybe.withDefault ( 0, 0 ) 142 | ``` 143 | 144 | ### Wheel 145 | 146 | You can manage `Wheel` events with the corresponding module. 147 | Since it is an extension to the `Mouse` module all mouse inherited properties 148 | are also available in the attribute `mouseEvent`. 149 | 150 | To simply check for wheel rolling up or down you could do something like below: 151 | 152 | ```elm 153 | import Html.Events.Extra.Wheel as Wheel 154 | 155 | -- ... 156 | 157 | type Msg 158 | = Scrolling Float 159 | 160 | view = 161 | div 162 | [Wheel.onWheel (\event -> Scrolling event.deltaY)] 163 | [text "scroll here"] 164 | ``` 165 | 166 | ### Drag 167 | 168 | The API presented by this package is slightly opinionated, 169 | to mitigate most errors induced by the complicated HTML5 drag events. 170 | This API is organized around two use cases: 171 | 172 | 1. Dropping files from OS 173 | 2. Drag and drop of DOM elements 174 | 175 | For dropping files, everything can be done purely in elm so the API reflects that. 176 | For drag and drop however some events require JavaScript function calls. 177 | Consequently it requires the use of ports. 178 | Two files, `DragPorts.js` and `Ports.elm` are provided in the source code 179 | of this repo to help setup things. 180 | 181 | More info is available in the module documentation. 182 | One example for each use case is present in the `examples/` directory. 183 | 184 | ## Examples 185 | 186 | Minimalist working examples are available for each module in the `examples/` directory. 187 | To test one example, `cd` into one of them and compile the elm file with the command: 188 | 189 | ```shell 190 | elm make Main.elm --output Main.js 191 | ``` 192 | 193 | Then use any static http server like: 194 | 195 | ```shell 196 | $ python3 -m http.server 8888 197 | ``` 198 | 199 | And open your browser at localhost:8888 200 | to load the `index.html` page. 201 | 202 | ## Want to contribute? 203 | 204 | If you are interested in contributing in any way 205 | (feedback, bug report, implementation of new functionality, ...) 206 | don't hesitate to reach out on slack (user @mattpiz) 207 | and/or open an issue. 208 | Discussion is the best way to start any contribution. 209 | 210 | ## Documentation 211 | 212 | [![][badge-doc]][doc] 213 | 214 | The package documentation is available on the [elm package website][doc]. 215 | 216 | ## License 217 | 218 | [![][badge-license]][license] 219 | 220 | This Source Code Form is subject to the terms of the Mozilla Public License,v. 2.0. 221 | If a copy of the MPL was not distributed with this file, 222 | You can obtain one at https://mozilla.org/MPL/2.0/. 223 | 224 | ## Contributors 225 | 226 | - Matthieu Pizenberg - @mpizenberg 227 | - Thomas Forgione - @tforgione ([elm-pep] polyfill) 228 | - Robert Vollmert - @robx ([elm-pep] polyfill) 229 | - Matthew Dupree - @4onen (upgrade File type to official one in elm/file) 230 | - Brendan Weibrecht - @ZimbiX (documentation) 231 | - Simon Lydell - @lydell (add support for metaKey) 232 | -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "mpizenberg/elm-pointer-events", 4 | "summary": "Mouse, Touch, Pointer, Wheel and Drag events", 5 | "license": "MPL-2.0", 6 | "version": "5.0.0", 7 | "exposed-modules": [ 8 | "Html.Events.Extra.Pointer", 9 | "Html.Events.Extra.Mouse", 10 | "Html.Events.Extra.Touch", 11 | "Html.Events.Extra.Wheel", 12 | "Html.Events.Extra.Drag" 13 | ], 14 | "elm-version": "0.19.0 <= v < 0.20.0", 15 | "dependencies": { 16 | "elm/core": "1.0.0 <= v < 2.0.0", 17 | "elm/file": "1.0.1 <= v < 2.0.0", 18 | "elm/html": "1.0.0 <= v < 2.0.0", 19 | "elm/json": "1.0.0 <= v < 2.0.0" 20 | }, 21 | "test-dependencies": {} 22 | } 23 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled examples 2 | Main.js 3 | -------------------------------------------------------------------------------- /examples/DragAndDrop/DragPorts.js: -------------------------------------------------------------------------------- 1 | ../../src/DragPorts.js -------------------------------------------------------------------------------- /examples/DragAndDrop/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Browser 4 | import Dict exposing (Dict) 5 | import DragPorts 6 | import Html exposing (Html, div, h1, p, text) 7 | import Html.Attributes exposing (class, id) 8 | import Html.Events.Extra.Drag as Drag 9 | import Json.Decode as Decode exposing (Value) 10 | import List 11 | 12 | 13 | main : Program () Model Msg 14 | main = 15 | Browser.element 16 | { init = init 17 | , view = view 18 | , update = update 19 | , subscriptions = always Sub.none 20 | } 21 | 22 | 23 | type alias Model = 24 | { dragAndDropStatus : DragAndDropStatus 25 | , tasks : Dict Int Task 26 | } 27 | 28 | 29 | type DragAndDropStatus 30 | = NoDnD 31 | | Dragging Int 32 | 33 | 34 | type Task 35 | = Task ProgressStatus String 36 | 37 | 38 | type ProgressStatus 39 | = ToDo 40 | | Doing 41 | | Done 42 | 43 | 44 | init : () -> ( Model, Cmd Msg ) 45 | init () = 46 | ( initialModel, Cmd.none ) 47 | 48 | 49 | initialModel : Model 50 | initialModel = 51 | { dragAndDropStatus = NoDnD 52 | , tasks = 53 | Dict.fromList 54 | [ ( 1, Task ToDo "Bake a cake" ) 55 | , ( 2, Task ToDo "Go for a run" ) 56 | , ( 3, Task ToDo "Pet the cat" ) 57 | , ( 4, Task ToDo "Watch that episode before I get spoiled!" ) 58 | , ( 5, Task ToDo "Sleep, yes really!" ) 59 | ] 60 | } 61 | 62 | 63 | 64 | -- Update 65 | 66 | 67 | type Msg 68 | = DragStart Int Drag.EffectAllowed Value 69 | | DragEnd 70 | | DragOver Drag.DropEffect Value 71 | | Drop ProgressStatus 72 | 73 | 74 | update : Msg -> Model -> ( Model, Cmd Msg ) 75 | update msg model = 76 | case ( msg, model.dragAndDropStatus ) of 77 | ( DragStart id effectAllowed value, _ ) -> 78 | ( { model | dragAndDropStatus = Dragging id } 79 | , DragPorts.dragstart (Drag.startPortData effectAllowed value) 80 | ) 81 | 82 | ( DragEnd, _ ) -> 83 | ( { model | dragAndDropStatus = NoDnD }, Cmd.none ) 84 | 85 | ( DragOver dropEffect value, _ ) -> 86 | ( model, DragPorts.dragover (Drag.overPortData dropEffect value) ) 87 | 88 | ( Drop status, Dragging id ) -> 89 | let 90 | newTasks = 91 | Dict.update id (maybeSetStatus status) model.tasks 92 | in 93 | ( { model | tasks = newTasks }, Cmd.none ) 94 | 95 | _ -> 96 | ( model, Cmd.none ) 97 | 98 | 99 | maybeSetStatus : ProgressStatus -> Maybe Task -> Maybe Task 100 | maybeSetStatus status maybeTask = 101 | case maybeTask of 102 | Just (Task _ instruction) -> 103 | Just (Task status instruction) 104 | 105 | _ -> 106 | maybeTask 107 | 108 | 109 | 110 | -- View 111 | 112 | 113 | view : Model -> Html Msg 114 | view model = 115 | div [ id "kanban" ] 116 | [ div 117 | (class "kanban-area" :: id "todo" :: Drag.onDropTarget (dropTargetConfig ToDo)) 118 | (h1 [] [ text "To Do" ] :: viewTasks ToDo model.tasks) 119 | , div 120 | (class "kanban-area" :: id "doing" :: Drag.onDropTarget (dropTargetConfig Doing)) 121 | (h1 [] [ text "Doing" ] :: viewTasks Doing model.tasks) 122 | , div 123 | (class "kanban-area" :: id "done" :: Drag.onDropTarget (dropTargetConfig Done)) 124 | (h1 [] [ text "Done" ] :: viewTasks Done model.tasks) 125 | ] 126 | 127 | 128 | dropTargetConfig : ProgressStatus -> Drag.DropTargetConfig Msg 129 | dropTargetConfig status = 130 | { dropEffect = Drag.MoveOnDrop 131 | , onOver = DragOver 132 | , onDrop = always (Drop status) 133 | , onEnter = Nothing 134 | , onLeave = Nothing 135 | } 136 | 137 | 138 | viewTasks : ProgressStatus -> Dict Int Task -> List (Html Msg) 139 | viewTasks status tasks = 140 | tasks 141 | |> Dict.filter (\_ task -> progressStatus task == status) 142 | |> Dict.toList 143 | |> List.map viewOneTask 144 | 145 | 146 | progressStatus : Task -> ProgressStatus 147 | progressStatus (Task status _) = 148 | status 149 | 150 | 151 | viewOneTask : ( Int, Task ) -> Html Msg 152 | viewOneTask ( id, Task status instruction ) = 153 | p (class "task" :: Drag.onSourceDrag (draggedSourceConfig id)) [ text instruction ] 154 | 155 | 156 | draggedSourceConfig : Int -> Drag.DraggedSourceConfig Msg 157 | draggedSourceConfig id = 158 | { effectAllowed = { move = True, copy = False, link = False } 159 | , onStart = DragStart id 160 | , onEnd = always DragEnd 161 | , onDrag = Nothing 162 | } 163 | -------------------------------------------------------------------------------- /examples/DragAndDrop/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | ".", 5 | "../../src" 6 | ], 7 | "elm-version": "0.19.0", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.1", 11 | "elm/core": "1.0.2", 12 | "elm/file": "1.0.1", 13 | "elm/html": "1.0.0", 14 | "elm/json": "1.1.2" 15 | }, 16 | "indirect": { 17 | "elm/bytes": "1.0.7", 18 | "elm/time": "1.0.0", 19 | "elm/url": "1.0.0", 20 | "elm/virtual-dom": "1.0.2" 21 | } 22 | }, 23 | "test-dependencies": { 24 | "direct": {}, 25 | "indirect": {} 26 | } 27 | } -------------------------------------------------------------------------------- /examples/DragAndDrop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example: Drag and Drop 5 | 6 | 15 | 16 | 17 | 18 | 19 |
20 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/FileDrop/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (DragEvent(..), MetaData, WithoutRawData, extractMetadata, fileDropConfig, main, view, withoutRawData) 2 | 3 | import Browser 4 | import File exposing (File) 5 | import Html exposing (Html, div, p, text) 6 | import Html.Events.Extra.Drag as Drag 7 | import Html.Events.Extra.Mouse as Mouse 8 | 9 | 10 | main : Program () DragEvent DragEvent 11 | main = 12 | Browser.sandbox 13 | { init = None 14 | , view = view 15 | , update = \event _ -> event 16 | } 17 | 18 | 19 | type DragEvent 20 | = None 21 | | Over WithoutRawData 22 | | Drop WithoutRawData 23 | | Enter WithoutRawData 24 | | Leave WithoutRawData 25 | 26 | 27 | type alias WithoutRawData = 28 | { mouseEvent : Mouse.Event 29 | , metadata : List MetaData 30 | } 31 | 32 | 33 | type alias MetaData = 34 | { name : String 35 | , mimeType : String 36 | , size : Int 37 | } 38 | 39 | 40 | 41 | -- View 42 | 43 | 44 | view : DragEvent -> Html DragEvent 45 | view model = 46 | div [] 47 | -- Dropable area (grayed in css) 48 | [ p (Drag.onFileFromOS fileDropConfig) [ text <| Debug.toString model ] ] 49 | 50 | 51 | fileDropConfig : Drag.FileDropConfig DragEvent 52 | fileDropConfig = 53 | { onOver = Over << withoutRawData 54 | , onDrop = Drop << withoutRawData 55 | , onEnter = Just (Enter << withoutRawData) 56 | , onLeave = Just (Leave << withoutRawData) 57 | } 58 | 59 | 60 | withoutRawData : Drag.Event -> WithoutRawData 61 | withoutRawData event = 62 | { mouseEvent = event.mouseEvent 63 | , metadata = List.map extractMetadata event.dataTransfer.files 64 | } 65 | 66 | 67 | extractMetadata : File -> MetaData 68 | extractMetadata file = 69 | { name = File.name file 70 | , mimeType = File.mime file 71 | , size = File.size file 72 | } 73 | -------------------------------------------------------------------------------- /examples/FileDrop/SSCCE.elm: -------------------------------------------------------------------------------- 1 | port module Main exposing (..) 2 | 3 | import Browser 4 | import Html exposing (Attribute, Html, div, p, text) 5 | import Html.Events 6 | import Json.Decode as Decode exposing (Value) 7 | 8 | 9 | main : Program () () Msg 10 | main = 11 | Browser.element 12 | { init = always ( (), Cmd.none ) 13 | , view = view 14 | , update = update 15 | , subscriptions = always Sub.none 16 | } 17 | 18 | 19 | 20 | -- Update 21 | 22 | 23 | type Msg 24 | = NoOp 25 | | PortMsg Value 26 | 27 | 28 | update : Msg -> () -> ( (), Cmd Msg ) 29 | update msg () = 30 | case msg of 31 | PortMsg value -> 32 | ( (), valuePort value ) 33 | 34 | _ -> 35 | ( (), Cmd.none ) 36 | 37 | 38 | port valuePort : Value -> Cmd msg 39 | 40 | 41 | 42 | -- View 43 | 44 | 45 | view : () -> Html Msg 46 | view () = 47 | div [ valueOn "dragover" PortMsg ] 48 | [ p [ discard "dragleave" ] [] ] 49 | 50 | 51 | valueOn : String -> (Value -> Msg) -> Attribute Msg 52 | valueOn event tag = 53 | Html.Events.preventDefaultOn event <| 54 | Decode.map (\v -> ( tag v, True )) Decode.value 55 | 56 | 57 | discard : String -> Attribute Msg 58 | discard event = 59 | Html.Events.custom event <| 60 | Decode.succeed { message = NoOp, stopPropagation = True, preventDefault = True } 61 | -------------------------------------------------------------------------------- /examples/FileDrop/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | ".", 5 | "../../src" 6 | ], 7 | "elm-version": "0.19.0", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.1", 11 | "elm/core": "1.0.2", 12 | "elm/file": "1.0.1", 13 | "elm/html": "1.0.0", 14 | "elm/json": "1.1.2" 15 | }, 16 | "indirect": { 17 | "elm/bytes": "1.0.7", 18 | "elm/time": "1.0.0", 19 | "elm/url": "1.0.0", 20 | "elm/virtual-dom": "1.0.2" 21 | } 22 | }, 23 | "test-dependencies": { 24 | "direct": {}, 25 | "indirect": {} 26 | } 27 | } -------------------------------------------------------------------------------- /examples/FileDrop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example: File Drop 5 | 6 | 12 | 13 | 14 | 15 |
16 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/Mouse/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Browser 4 | import Html exposing (..) 5 | import Html.Events.Extra.Mouse as Mouse 6 | 7 | 8 | main : Program () MouseEvent MouseEvent 9 | main = 10 | Browser.sandbox 11 | { init = None 12 | , view = view 13 | , update = always 14 | } 15 | 16 | 17 | type MouseEvent 18 | = None 19 | | Down Mouse.Event 20 | | Move Mouse.Event 21 | | Up Mouse.Event 22 | | Click Mouse.Event 23 | | DoubleClick Mouse.Event 24 | | Over Mouse.Event 25 | | Out Mouse.Event 26 | | ContextMenu Mouse.Event 27 | 28 | 29 | view : MouseEvent -> Html MouseEvent 30 | view event = 31 | div [] 32 | [ p 33 | [ Mouse.onDown Down 34 | , Mouse.onMove Move 35 | , Mouse.onUp Up 36 | , Mouse.onClick Click 37 | , Mouse.onDoubleClick DoubleClick 38 | , Mouse.onOver Over 39 | , Mouse.onOut Out 40 | , Mouse.onContextMenu ContextMenu 41 | ] 42 | [ text <| Debug.toString event ] 43 | ] 44 | -------------------------------------------------------------------------------- /examples/Mouse/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | ".", 5 | "../../src" 6 | ], 7 | "elm-version": "0.19.0", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.1", 11 | "elm/core": "1.0.2", 12 | "elm/file": "1.0.1", 13 | "elm/html": "1.0.0", 14 | "elm/json": "1.1.2" 15 | }, 16 | "indirect": { 17 | "elm/bytes": "1.0.7", 18 | "elm/time": "1.0.0", 19 | "elm/url": "1.0.0", 20 | "elm/virtual-dom": "1.0.2" 21 | } 22 | }, 23 | "test-dependencies": { 24 | "direct": {}, 25 | "indirect": {} 26 | } 27 | } -------------------------------------------------------------------------------- /examples/Mouse/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example: Mouse Events 5 | 6 | 12 | 13 | 14 | 15 |
16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/Pointer/Main.elm: -------------------------------------------------------------------------------- 1 | port module Main exposing (main) 2 | 3 | import Browser 4 | import Html exposing (..) 5 | import Html.Events 6 | import Html.Events.Extra.Pointer as Pointer 7 | import Json.Decode as Decode exposing (Decoder, Value) 8 | import Json.Encode as Encode 9 | 10 | 11 | main : Program () Model Msg 12 | main = 13 | Browser.element 14 | { init = always ( Nothing, Cmd.none ) 15 | , view = view 16 | , update = update 17 | , subscriptions = always Sub.none 18 | } 19 | 20 | 21 | type alias Model = 22 | Maybe Event 23 | 24 | 25 | type Event 26 | = Down Pointer.Event 27 | | Move Pointer.Event 28 | | Up Pointer.Event 29 | | Cancel Pointer.Event 30 | 31 | 32 | type Msg 33 | = EventMsg Event 34 | | RawDownMsg Value 35 | 36 | 37 | update : Msg -> Model -> ( Model, Cmd Msg ) 38 | update msg _ = 39 | case msg of 40 | EventMsg event -> 41 | ( Just event, Cmd.none ) 42 | 43 | RawDownMsg value -> 44 | ( Decode.decodeValue Pointer.eventDecoder value 45 | |> Result.toMaybe 46 | |> Maybe.map Down 47 | -- use a port to "capture" pointer event 48 | -- since it requires JS function calls 49 | , capture value 50 | ) 51 | 52 | 53 | port capture : Value -> Cmd msg 54 | 55 | 56 | view : Model -> Html Msg 57 | view model = 58 | div [] 59 | [ p 60 | [ Pointer.onUp (EventMsg << Up) 61 | , Pointer.onMove (EventMsg << Move) 62 | , Pointer.onCancel (EventMsg << Cancel) 63 | , msgOn "pointerdown" (Decode.map RawDownMsg Decode.value) 64 | ] 65 | [ text <| Debug.toString model ] 66 | ] 67 | 68 | 69 | msgOn : String -> Decoder msg -> Attribute msg 70 | msgOn event = 71 | Decode.map (\msg -> { message = msg, stopPropagation = True, preventDefault = True }) 72 | >> Html.Events.custom event 73 | -------------------------------------------------------------------------------- /examples/Pointer/elm-pep.js: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/ 4 | 5 | // Variable to hold current primary touch event identifier. 6 | // iOS needs this since it does not attribute 7 | // identifier 0 to primary touch event. 8 | let primaryTouchId = null; 9 | 10 | // Variable to hold mouse pointer captures. 11 | let mouseCaptureTarget = null; 12 | 13 | if (!("PointerEvent" in window)) { 14 | // Define {set,release}PointerCapture 15 | definePointerCapture(); 16 | 17 | // Create Pointer polyfill from mouse events only on non-touch device 18 | if (!("TouchEvent" in window)) { 19 | addMouseToPointerListener(document, "mousedown", "pointerdown"); 20 | addMouseToPointerListener(document, "mousemove", "pointermove"); 21 | addMouseToPointerListener(document, "mouseup", "pointerup"); 22 | } 23 | 24 | // Define Pointer polyfill from touch events 25 | addTouchToPointerListener(document, "touchstart", "pointerdown"); 26 | addTouchToPointerListener(document, "touchmove", "pointermove"); 27 | addTouchToPointerListener(document, "touchend", "pointerup"); 28 | } 29 | 30 | // Function defining {set,release}PointerCapture from {set,releas}Capture 31 | function definePointerCapture() { 32 | Element.prototype.setPointerCapture = Element.prototype.setCapture; 33 | Element.prototype.releasePointerCapture = Element.prototype.releaseCapture; 34 | } 35 | 36 | // Function converting a Mouse event to a Pointer event. 37 | function addMouseToPointerListener(target, mouseType, pointerType) { 38 | target.addEventListener(mouseType, mouseEvent => { 39 | let pointerEvent = new MouseEvent(pointerType, mouseEvent); 40 | pointerEvent.pointerId = 1; 41 | pointerEvent.isPrimary = true; 42 | pointerEvent.pointerType = "mouse"; 43 | pointerEvent.width = 1; 44 | pointerEvent.height = 1; 45 | pointerEvent.tiltX = 0; 46 | pointerEvent.tiltY = 0; 47 | 48 | // pressure is 0.5 if a button is holded 49 | "buttons" in mouseEvent && mouseEvent.buttons !== 0 50 | ? (pointerEvent.pressure = 0.5) 51 | : (pointerEvent.pressure = 0); 52 | 53 | // if already capturing mouse event, transfer target 54 | // and don't forget implicit release on mouseup. 55 | let target = mouseEvent.target; 56 | if (mouseCaptureTarget !== null) { 57 | target = mouseCaptureTarget; 58 | if (mouseType === "mouseup") { 59 | mouseCaptureTarget = null; 60 | } 61 | } 62 | 63 | target.dispatchEvent(pointerEvent); 64 | if (pointerEvent.defaultPrevented) { 65 | mouseEvent.preventDefault(); 66 | } 67 | }); 68 | } 69 | 70 | // Function converting a Touch event to a Pointer event. 71 | function addTouchToPointerListener(target, touchType, pointerType) { 72 | target.addEventListener(touchType, touchEvent => { 73 | const changedTouches = touchEvent.changedTouches; 74 | const nbTouches = changedTouches.length; 75 | for (let t = 0; t < nbTouches; t++) { 76 | let pointerEvent = new CustomEvent(pointerType, { 77 | bubbles: true, 78 | cancelable: true 79 | }); 80 | pointerEvent.ctrlKey = touchEvent.ctrlKey; 81 | pointerEvent.shiftKey = touchEvent.shiftKey; 82 | pointerEvent.altKey = touchEvent.altKey; 83 | pointerEvent.metaKey = touchEvent.metaKey; 84 | 85 | const touch = changedTouches.item(t); 86 | pointerEvent.clientX = touch.clientX; 87 | pointerEvent.clientY = touch.clientY; 88 | pointerEvent.screenX = touch.screenX; 89 | pointerEvent.screenY = touch.screenY; 90 | pointerEvent.pageX = touch.pageX; 91 | pointerEvent.pageY = touch.pageY; 92 | const rect = touch.target.getBoundingClientRect(); 93 | pointerEvent.offsetX = touch.clientX - rect.left; 94 | pointerEvent.offsetY = touch.clientY - rect.top; 95 | pointerEvent.pointerId = 1 + touch.identifier; 96 | 97 | // Default values for standard MouseEvent fields. 98 | pointerEvent.button = 0; 99 | pointerEvent.buttons = 1; 100 | pointerEvent.movementX = 0; 101 | pointerEvent.movementY = 0; 102 | pointerEvent.region = null; 103 | pointerEvent.relatedTarget = null; 104 | pointerEvent.x = pointerEvent.clientX; 105 | pointerEvent.y = pointerEvent.clientY; 106 | 107 | // Pointer event details 108 | pointerEvent.pointerType = "touch"; 109 | pointerEvent.width = 1; 110 | pointerEvent.height = 1; 111 | pointerEvent.tiltX = 0; 112 | pointerEvent.tiltY = 0; 113 | pointerEvent.pressure = 1; 114 | 115 | // First touch is the primary pointer event. 116 | if (touchType === "touchstart" && primaryTouchId === null) { 117 | primaryTouchId = touch.identifier; 118 | } 119 | pointerEvent.isPrimary = touch.identifier === primaryTouchId; 120 | 121 | // If first touch ends, reset primary touch id. 122 | if (touchType === "touchend" && pointerEvent.isPrimary) { 123 | primaryTouchId = null; 124 | } 125 | 126 | touchEvent.target.dispatchEvent(pointerEvent); 127 | if (pointerEvent.defaultPrevented) { 128 | touchEvent.preventDefault(); 129 | } 130 | } 131 | }); 132 | } 133 | -------------------------------------------------------------------------------- /examples/Pointer/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | ".", 5 | "../../src" 6 | ], 7 | "elm-version": "0.19.0", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.1", 11 | "elm/core": "1.0.2", 12 | "elm/file": "1.0.1", 13 | "elm/html": "1.0.0", 14 | "elm/json": "1.1.2" 15 | }, 16 | "indirect": { 17 | "elm/bytes": "1.0.7", 18 | "elm/time": "1.0.0", 19 | "elm/url": "1.0.0", 20 | "elm/virtual-dom": "1.0.2" 21 | } 22 | }, 23 | "test-dependencies": { 24 | "direct": {}, 25 | "indirect": {} 26 | } 27 | } -------------------------------------------------------------------------------- /examples/Pointer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example: Pointer Events 5 | 6 | 13 | 14 | 15 | 16 | 17 |
18 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/Touch/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Browser 4 | import Html exposing (Html, div, p, text) 5 | import Html.Attributes exposing (style) 6 | import Html.Events.Extra.Touch as Touch 7 | 8 | 9 | main : Program () TouchEvent TouchEvent 10 | main = 11 | Browser.sandbox 12 | { init = None 13 | , view = view 14 | , update = always 15 | } 16 | 17 | 18 | type TouchEvent 19 | = None 20 | | Start Touch.Event 21 | | Move Touch.Event 22 | | End Touch.Event 23 | | Cancel Touch.Event 24 | 25 | 26 | view : TouchEvent -> Html TouchEvent 27 | view event = 28 | div [] 29 | [ p 30 | [ Touch.onStart Start 31 | , Touch.onMove Move 32 | , Touch.onEnd End 33 | , Touch.onCancel Cancel 34 | 35 | -- no touch-action 36 | , style "touch-action" "none" 37 | ] 38 | [ text <| Debug.toString event ] 39 | ] 40 | -------------------------------------------------------------------------------- /examples/Touch/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | ".", 5 | "../../src" 6 | ], 7 | "elm-version": "0.19.0", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.0", 11 | "elm/core": "1.0.0", 12 | "elm/html": "1.0.0", 13 | "elm/json": "1.0.0" 14 | }, 15 | "indirect": { 16 | "elm/time": "1.0.0", 17 | "elm/url": "1.0.0", 18 | "elm/virtual-dom": "1.0.0" 19 | } 20 | }, 21 | "test-dependencies": { 22 | "direct": {}, 23 | "indirect": {} 24 | } 25 | } -------------------------------------------------------------------------------- /examples/Touch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example: Touch Events 5 | 6 | 12 | 13 | 14 | 15 |
16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/Wheel/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Browser 4 | import Html exposing (Html, div, p, text) 5 | import Html.Events.Extra.Wheel as Wheel 6 | 7 | 8 | main : Program () WheelEvent WheelEvent 9 | main = 10 | Browser.sandbox 11 | { init = None 12 | , view = view 13 | , update = always 14 | } 15 | 16 | 17 | type WheelEvent 18 | = None 19 | | Wheel Wheel.Event 20 | 21 | 22 | view : WheelEvent -> Html WheelEvent 23 | view event = 24 | div [] 25 | [ p 26 | [ Wheel.onWheel Wheel ] 27 | [ text <| Debug.toString event ] 28 | ] 29 | -------------------------------------------------------------------------------- /examples/Wheel/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | ".", 5 | "../../src" 6 | ], 7 | "elm-version": "0.19.0", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.0", 11 | "elm/core": "1.0.0", 12 | "elm/html": "1.0.0", 13 | "elm/json": "1.0.0" 14 | }, 15 | "indirect": { 16 | "elm/time": "1.0.0", 17 | "elm/url": "1.0.0", 18 | "elm/virtual-dom": "1.0.0" 19 | } 20 | }, 21 | "test-dependencies": { 22 | "direct": {}, 23 | "indirect": {} 24 | } 25 | } -------------------------------------------------------------------------------- /examples/Wheel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example: Wheel Event 5 | 6 | 12 | 13 | 14 | 15 |
16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/DragPorts.elm: -------------------------------------------------------------------------------- 1 | port module DragPorts exposing (dragover, dragstart) 2 | 3 | import Json.Decode exposing (Value) 4 | 5 | 6 | port dragstart : { effectAllowed : String, event : Value } -> Cmd msg 7 | 8 | 9 | port dragover : { dropEffect : String, event : Value } -> Cmd msg 10 | -------------------------------------------------------------------------------- /src/DragPorts.js: -------------------------------------------------------------------------------- 1 | var DragPorts = (function() { 2 | // data must be of the format: 3 | // { effectAllowed: string, event: DragEvent } 4 | function processDragStart(data) { 5 | data.event.dataTransfer.setData("text/plain", null); // needed 6 | data.event.dataTransfer.effectAllowed = data.effectAllowed; 7 | } 8 | 9 | // data must be of the format: 10 | // { dropEffect: string, event: DragEvent } 11 | function processDragOver(data) { 12 | data.event.dataTransfer.dropEffect = data.dropEffect; 13 | } 14 | 15 | // Automatic setup of standard drag ports subscriptions. 16 | function setup(elmApp) { 17 | elmApp.ports.dragstart.subscribe(processDragStart); 18 | elmApp.ports.dragover.subscribe(processDragOver); 19 | } 20 | 21 | return { 22 | processDragStart: processDragStart, 23 | processDragOver: processDragOver, 24 | setup: setup 25 | }; 26 | })(); 27 | -------------------------------------------------------------------------------- /src/Html/Events/Extra/Drag.elm: -------------------------------------------------------------------------------- 1 | -- This Source Code Form is subject to the terms of the Mozilla Public 2 | -- License, v. 2.0. If a copy of the MPL was not distributed with this 3 | -- file, You can obtain one at http://mozilla.org/MPL/2.0/ 4 | 5 | 6 | module Html.Events.Extra.Drag exposing 7 | ( Event, DataTransfer 8 | , onFileFromOS, FileDropConfig 9 | , onSourceDrag, DraggedSourceConfig, EffectAllowed, startPortData, effectAllowedToString 10 | , onDropTarget, DropTargetConfig, DropEffect(..), overPortData, dropEffectToString 11 | , eventDecoder, dataTransferDecoder, fileListDecoder 12 | ) 13 | 14 | {-| [HTML5 drag events][dragevent] is a quite complicated specification. 15 | Mostly because it is very stateful, and many properties and functions 16 | only make sense in one situation and not the rest. 17 | For example, the `effectAllowed` property can only be set successfully 18 | in a `dragstart` event, and setting it in another will be ignored. 19 | Another example, the `dragend` should be attached to the object dragged, 20 | while the `dragleave` should be attached to potential drop target. 21 | One more, the `dragover` event listener is required on a drop target. 22 | Otherwise the drop event will be cancelled. 23 | 24 | Consequently, I've chosen to present a slightly opinionated API for drag events, 25 | mitigating most of potential errors. 26 | In case it prevents you from using it, please report your use case in 27 | [an issue][issues]. I hope by also providing the decoders, 28 | the library can still help you setup your own event listeners. 29 | 30 | There seems to be two main use cases for drag events: 31 | 32 | 1. Dropping files from the OS as resources to load. 33 | 2. Drag and dropping DOM elements in page. 34 | 35 | The rest of the documentation presents the API with those use cases in mind. 36 | 37 | [dragevent]: https://developer.mozilla.org/en-US/docs/Web/API/DragEvent 38 | [issues]: https://github.com/mpizenberg/elm-pointer-events/issues 39 | 40 | 41 | # The Event Type 42 | 43 | @docs Event, DataTransfer 44 | 45 | 46 | # File Dropping 47 | 48 | @docs onFileFromOS, FileDropConfig 49 | 50 | 51 | # Drag and Drop 52 | 53 | I encourage you to read [this blog post][disaster] before you take the decision 54 | to use HTML5 drag and drop API instead of your own custom solution. 55 | 56 | [disaster]: https://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html 57 | 58 | 59 | ## Managing the Dragged Item 60 | 61 | @docs onSourceDrag, DraggedSourceConfig, EffectAllowed, startPortData, effectAllowedToString 62 | 63 | 64 | ## Managing a Drop Target 65 | 66 | @docs onDropTarget, DropTargetConfig, DropEffect, overPortData, dropEffectToString 67 | 68 | 69 | # Decoders for Advanced Usage 70 | 71 | @docs eventDecoder, dataTransferDecoder, fileListDecoder 72 | 73 | -} 74 | 75 | import File exposing (File) 76 | import Html 77 | import Html.Attributes 78 | import Html.Events 79 | import Html.Events.Extra.Mouse as Mouse 80 | import Internal.Decode 81 | import Json.Decode as Decode exposing (Decoder, Value) 82 | 83 | 84 | {-| Type that get returned by a browser drag event. 85 | It corresponds to a JavaScript [DragEvent]. 86 | 87 | Since a `DragEvent` inherits from `MouseEvent`, 88 | all mouse event related properties are provided in the 89 | `mouseEvent` attribute of type `Mouse.Event`. 90 | Please refer to the `Mouse` module for more information on this value. 91 | 92 | [DragEvent]: https://developer.mozilla.org/en-US/docs/Web/API/DragEvent 93 | 94 | -} 95 | type alias Event = 96 | { dataTransfer : DataTransfer 97 | , mouseEvent : Mouse.Event 98 | } 99 | 100 | 101 | {-| Hold the data being dragged during a drag and drop operation. 102 | This corresponds to JavaScript [DataTransfer]. 103 | 104 | The `files` attribute holds the list of files being dragged. 105 | Each file is of the type `File` provided by [elm/file]. 106 | 107 | The `types` attribute contains a list of strings providing 108 | the different formats of objects being dragged. 109 | 110 | The `dropEffect` attribute provides feedback on the selected effect 111 | for the current drag and drop operation. It can be one of: 112 | 113 | - `"none"`: the item may not be dropped 114 | - `"copy"`: a copy of the source item is made at the new location 115 | - `"move"`: the item is moved to a new location 116 | - `"link"`: a link to the source is somehow established at the new location 117 | 118 | Beware that contrary to JavaScript, you have no way of modifying 119 | `dropEffect` in elm. This is provided purely for information as read only, 120 | like any other elm value. 121 | 122 | The `effectAllowed` property is not provided since it has 123 | no use in the context of elm. 124 | 125 | The `items` property is not provided by lack of compatibility. 126 | 127 | [DataTransfer]: https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer 128 | [elm/file]: https://package.elm-lang.org/packages/elm/file/latest/File 129 | 130 | -} 131 | type alias DataTransfer = 132 | { files : List File 133 | , types : List String 134 | , dropEffect : String 135 | } 136 | 137 | 138 | 139 | -- FILE DROPPING ##################################################### 140 | 141 | 142 | {-| Events listeners for a file drop target element. 143 | 144 | PS: incompatible with `onDropTarget` since both functions 145 | use the same events listeners. 146 | If you need to have a drop target working for both files and DOM elements, 147 | you can directly use `onDropTarget`. 148 | 149 | -} 150 | onFileFromOS : FileDropConfig msg -> List (Html.Attribute msg) 151 | onFileFromOS config = 152 | List.filterMap identity <| 153 | [ Just (on "dragover" config.onOver) 154 | , Just (on "drop" config.onDrop) 155 | , Maybe.map (on "dragenter") config.onEnter 156 | , Maybe.map (on "dragleave") config.onLeave 157 | ] 158 | 159 | 160 | {-| Configuration of a file drop target. 161 | 162 | PS: `dragenter` and `dragleave` are kind of inconsistent since they 163 | bubble up from children items (not consistently depending on borders in addition). 164 | You should prefer to let them be `Nothing`, or to add the CSS property 165 | `pointer-events: none` to all children. 166 | 167 | -} 168 | type alias FileDropConfig msg = 169 | { onOver : Event -> msg 170 | , onDrop : Event -> msg 171 | , onEnter : Maybe (Event -> msg) 172 | , onLeave : Maybe (Event -> msg) 173 | } 174 | 175 | 176 | 177 | -- DRAG AND DROP ##################################################### 178 | 179 | 180 | {-| Drag events listeners for the source dragged element. 181 | -} 182 | onSourceDrag : DraggedSourceConfig msg -> List (Html.Attribute msg) 183 | onSourceDrag config = 184 | List.filterMap identity <| 185 | [ Just (Html.Attributes.draggable "true") 186 | , Just (valueOn "dragstart" (config.onStart config.effectAllowed)) 187 | , Just (on "dragend" config.onEnd) 188 | , Maybe.map (on "drag") config.onDrag 189 | ] 190 | 191 | 192 | {-| Configuration of a draggable element. 193 | You should provide message taggers for `dragstart` and `dragend` events. 194 | You can (but it is more compute-intensive) provide a message tagger for `drag` events. 195 | -} 196 | type alias DraggedSourceConfig msg = 197 | { effectAllowed : EffectAllowed 198 | , onStart : EffectAllowed -> Value -> msg 199 | , onEnd : Event -> msg 200 | , onDrag : Maybe (Event -> msg) 201 | } 202 | 203 | 204 | {-| Drop effects allowed for this draggable element. 205 | Set to `True` all effects allowed. 206 | This is used in the port of the `dragstart` event. 207 | -} 208 | type alias EffectAllowed = 209 | { move : Bool 210 | , copy : Bool 211 | , link : Bool 212 | } 213 | 214 | 215 | {-| Put the effect allowed and the dragstart event 216 | in a data format that can be sent through port. 217 | -} 218 | startPortData : EffectAllowed -> Value -> { effectAllowed : String, event : Value } 219 | startPortData effectAllowed value = 220 | { effectAllowed = effectAllowedToString effectAllowed, event = value } 221 | 222 | 223 | {-| Convert `EffectAllowed` into its String equivalent. 224 | -} 225 | effectAllowedToString : EffectAllowed -> String 226 | effectAllowedToString eff = 227 | case ( eff.move, eff.copy, eff.link ) of 228 | ( False, False, False ) -> 229 | "none" 230 | 231 | ( True, False, False ) -> 232 | "move" 233 | 234 | ( False, True, False ) -> 235 | "copy" 236 | 237 | ( False, False, True ) -> 238 | "link" 239 | 240 | ( True, True, False ) -> 241 | "copyMove" 242 | 243 | ( True, False, True ) -> 244 | "linkMove" 245 | 246 | ( False, True, True ) -> 247 | "copyLink" 248 | 249 | ( True, True, True ) -> 250 | "all" 251 | 252 | 253 | {-| Drag events listeners for the drop target element. 254 | 255 | PS: `dragenter` and `dragleave` are kind of inconsistent since they 256 | bubble up from children items (not consistently depending on borders in addition). 257 | You should prefer to let them be `Nothing`, or to add the CSS property 258 | `pointer-events: none` to all children. 259 | 260 | -} 261 | onDropTarget : DropTargetConfig msg -> List (Html.Attribute msg) 262 | onDropTarget config = 263 | List.filterMap identity <| 264 | [ Just (valuePreventedOn "dragover" (config.onOver config.dropEffect)) 265 | , Just (on "drop" config.onDrop) 266 | , Maybe.map (on "dragenter") config.onEnter 267 | , Maybe.map (on "dragleave") config.onLeave 268 | ] 269 | 270 | 271 | {-| Configuration of a drop target. 272 | You should provide message taggers for `dragover` and `drop` events. 273 | You can also provide message taggers for `dragenter` and `dragleave` events. 274 | -} 275 | type alias DropTargetConfig msg = 276 | { dropEffect : DropEffect 277 | , onOver : DropEffect -> Value -> msg 278 | , onDrop : Event -> msg 279 | , onEnter : Maybe (Event -> msg) 280 | , onLeave : Maybe (Event -> msg) 281 | } 282 | 283 | 284 | {-| Drop effect as configured by the drop target. 285 | This will change the visual aspect of the mouse icon. 286 | 287 | If the drop target sets (via port on `dragover`) a drop effect 288 | incompatible with the effects allowed for the dragged item, 289 | the drop will not happen. 290 | 291 | -} 292 | type DropEffect 293 | = NoDropEffect 294 | | MoveOnDrop 295 | | CopyOnDrop 296 | | LinkOnDrop 297 | 298 | 299 | {-| Put the drop effect and the dragover event 300 | in a data format that can be sent through port. 301 | -} 302 | overPortData : DropEffect -> Value -> { dropEffect : String, event : Value } 303 | overPortData dropEffect value = 304 | { dropEffect = dropEffectToString dropEffect, event = value } 305 | 306 | 307 | {-| Convert a `DropEffect` into its string equivalent. 308 | -} 309 | dropEffectToString : DropEffect -> String 310 | dropEffectToString dropEffect = 311 | case dropEffect of 312 | NoDropEffect -> 313 | "none" 314 | 315 | MoveOnDrop -> 316 | "move" 317 | 318 | CopyOnDrop -> 319 | "copy" 320 | 321 | LinkOnDrop -> 322 | "link" 323 | 324 | 325 | 326 | -- EVENTS LISTENERS ################################################## 327 | 328 | 329 | valueOn : String -> (Value -> msg) -> Html.Attribute msg 330 | valueOn event tag = 331 | Decode.value 332 | |> Decode.map (\value -> { message = tag value, stopPropagation = True, preventDefault = False }) 333 | |> Html.Events.custom event 334 | 335 | 336 | valuePreventedOn : String -> (Value -> msg) -> Html.Attribute msg 337 | valuePreventedOn event tag = 338 | Decode.value 339 | |> Decode.map (\value -> { message = tag value, stopPropagation = True, preventDefault = True }) 340 | |> Html.Events.custom event 341 | 342 | 343 | on : String -> (Event -> msg) -> Html.Attribute msg 344 | on event tag = 345 | eventDecoder 346 | |> Decode.map (\ev -> { message = tag ev, stopPropagation = True, preventDefault = True }) 347 | |> Html.Events.custom event 348 | 349 | 350 | 351 | -- DECODERS ########################################################## 352 | 353 | 354 | {-| `Drag.Event` default decoder. 355 | It is provided in case you would like to reuse/extend it. 356 | -} 357 | eventDecoder : Decoder Event 358 | eventDecoder = 359 | Decode.map2 Event 360 | (Decode.field "dataTransfer" dataTransferDecoder) 361 | Mouse.eventDecoder 362 | 363 | 364 | {-| `DataTransfer` decoder. 365 | It is provided in case you would like to reuse/extend it. 366 | -} 367 | dataTransferDecoder : Decoder DataTransfer 368 | dataTransferDecoder = 369 | Decode.map3 DataTransfer 370 | (Decode.field "files" <| fileListDecoder File.decoder) 371 | (Decode.field "types" <| Decode.list Decode.string) 372 | (Decode.field "dropEffect" Decode.string) 373 | 374 | 375 | {-| Transform a personalized `File` decoder into a `List File` decoder 376 | since `Json.Decode.list` does not work for the list of files. 377 | -} 378 | fileListDecoder : Decoder a -> Decoder (List a) 379 | fileListDecoder = 380 | Internal.Decode.dynamicListOf 381 | -------------------------------------------------------------------------------- /src/Html/Events/Extra/Mouse.elm: -------------------------------------------------------------------------------- 1 | -- This Source Code Form is subject to the terms of the Mozilla Public 2 | -- License, v. 2.0. If a copy of the MPL was not distributed with this 3 | -- file, You can obtain one at http://mozilla.org/MPL/2.0/ 4 | 5 | 6 | module Html.Events.Extra.Mouse exposing 7 | ( Event, Keys, Button(..) 8 | , onDown, onMove, onUp 9 | , onClick, onDoubleClick 10 | , onEnter, onOver, onLeave, onOut 11 | , onContextMenu 12 | , EventOptions, onWithOptions 13 | , eventDecoder 14 | ) 15 | 16 | {-| Handling detailed mouse events. 17 | 18 | @docs Event, Keys, Button 19 | 20 | 21 | # Basic Usage 22 | 23 | The three default mouse events are `mousedown`, `mousemove` and `mouseup`. 24 | 25 | @docs onDown, onMove, onUp 26 | 27 | 28 | # Other Supported Events 29 | 30 | The other supported events by this library are 31 | `click`, `dblclick`, `mouseenter`, `mouseover`, `mouseleave` and `mouseout`. 32 | You can use them exactly like the previous examples. 33 | 34 | @docs onClick, onDoubleClick 35 | 36 | @docs onEnter, onOver, onLeave, onOut 37 | 38 | @docs onContextMenu 39 | 40 | 41 | # Advanced Usage 42 | 43 | @docs EventOptions, onWithOptions 44 | 45 | @docs eventDecoder 46 | 47 | -} 48 | 49 | import Html exposing (Attribute) 50 | import Html.Events as Events 51 | import Internal.Decode 52 | import Json.Decode as Decode exposing (Decoder) 53 | 54 | 55 | 56 | -- MOUSE EVENT ####################################################### 57 | 58 | 59 | {-| Type that get returned by a browser mouse event. 60 | Its purpose is to provide all useful properties of 61 | JavaScript [MouseEvent][js-mouse-event] in the context of 62 | the elm programming language. 63 | 64 | Coordinates of a specific kind (`clientX/Y`, `pageX/Y`, ...) 65 | are available under the attribute of the same name grouped by pairs. 66 | For example, if `mouseEvent` is of type `Mouse.Event` then 67 | `mouseEvent.clientPos` holds the `( clientX, clientY )` 68 | properties of the event. 69 | 70 | For some applications like drawing in a canvas, relative coordinates are needed. 71 | Beware that those coordinates are called `offsetX/Y` in a mouse event. 72 | Therefore they are available here with attribute `offsetPos`. 73 | 74 | relativePos : Mouse.Event -> ( Float, Float ) 75 | relativePos mouseEvent = 76 | mouseEvent.offsetPos 77 | 78 | The `movementX/Y` properties not being compatible with Safari / iOS, 79 | they are not provided by this package. 80 | The `x` and `y` properties being equivalent to `clientX/Y`, 81 | are not provided either. 82 | The `screenPos` attribute provides `screenX/Y` properties in case needed, 83 | but you shall use instead the `clientPos` attribute when in doubt. 84 | `screenX/Y` values are not given in CSS pixel sizes and thus not very useful. 85 | More info is available in the excellent 86 | article [A tale of two viewports][tale-viewports]. 87 | 88 | [js-mouse-event]: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent 89 | [tale-viewports]: https://www.quirksmode.org/mobile/viewports.html 90 | 91 | -} 92 | type alias Event = 93 | { keys : Keys 94 | , button : Button 95 | , clientPos : ( Float, Float ) 96 | , offsetPos : ( Float, Float ) 97 | , pagePos : ( Float, Float ) 98 | , screenPos : ( Float, Float ) 99 | } 100 | 101 | 102 | {-| The keys that might have been pressed during mouse event. 103 | Key modifiers of mouse events are available in the key attribute. 104 | Checking if the ctrl key was hold when the event triggered is as easy as: 105 | 106 | isCtrlKeyPressed : Mouse.Event -> Bool 107 | isCtrlKeyPressed mouseEvent = 108 | mouseEvent.keys.ctrl 109 | 110 | -} 111 | type alias Keys = 112 | { alt : Bool, ctrl : Bool, meta : Bool, shift : Bool } 113 | 114 | 115 | {-| The button pressed for the event. 116 | The button that was used to trigger the mouse event is available 117 | in the `button` attribute. However, beware that its value is not reliable 118 | for events such as `mouseenter`, `mouseleave`, `mouseover`, `mouseout` or `mousemove`. 119 | 120 | The `buttons` (with an "s") property of a mouse event is not provided here since 121 | it is not compatible with mac / safari. 122 | 123 | -} 124 | type Button 125 | = ErrorButton 126 | | MainButton 127 | | MiddleButton 128 | | SecondButton 129 | | BackButton 130 | | ForwardButton 131 | 132 | 133 | 134 | -- SIMPLE USAGE ###################################################### 135 | 136 | 137 | {-| Listen to `mousedown` events. 138 | Let's say that we have a message type like this: 139 | 140 | type Msg 141 | = DownMsg ( Float, Float ) 142 | | MoveMsg ( Float, Float ) 143 | | UpMsg ( Float, Float ) 144 | 145 | Then we could listen to `mousedown` events like below: 146 | 147 | div 148 | [ Mouse.onDown (\event -> DownMsg event.clientPos) ] 149 | [ text "click here" ] 150 | 151 | In a curried style, this can also be written: 152 | 153 | div 154 | [ Mouse.onDown (.clientPos >> DownMsg) ] 155 | [ text "click here" ] 156 | 157 | -} 158 | onDown : (Event -> msg) -> Attribute msg 159 | onDown = 160 | onWithOptions "mousedown" defaultOptions 161 | 162 | 163 | {-| Listen to `mousemove` events. 164 | Similarly than with `onDown`, we can write something like: 165 | 166 | div 167 | [ Mouse.onMove (.clientPos >> MoveMsg) ] 168 | [ text "move here" ] 169 | 170 | -} 171 | onMove : (Event -> msg) -> Attribute msg 172 | onMove = 173 | onWithOptions "mousemove" defaultOptions 174 | 175 | 176 | {-| Listen to `mouseup` events. 177 | Similarly than with `onDown`, we can write something like: 178 | 179 | div 180 | [ Mouse.onUp (.clientPos >> UpMsg) ] 181 | [ text "click here" ] 182 | 183 | -} 184 | onUp : (Event -> msg) -> Attribute msg 185 | onUp = 186 | onWithOptions "mouseup" defaultOptions 187 | 188 | 189 | 190 | -- EVENTS ############################################################ 191 | 192 | 193 | {-| Listen to `click` events. 194 | -} 195 | onClick : (Event -> msg) -> Attribute msg 196 | onClick = 197 | onWithOptions "click" defaultOptions 198 | 199 | 200 | {-| Listen to `dblclick` events. 201 | -} 202 | onDoubleClick : (Event -> msg) -> Attribute msg 203 | onDoubleClick = 204 | onWithOptions "dblclick" defaultOptions 205 | 206 | 207 | {-| Listen to `mouseenter` events. 208 | This event is fired when a mouse is moved over the element 209 | that has the listener attached. 210 | It is similar to `mouseover` but doesn't bubble. 211 | More details available on the [MDN documentation][mdn-mouseenter]. 212 | 213 | [mdn-mouseenter]: https://developer.mozilla.org/en-US/docs/Web/Events/mouseenter 214 | 215 | -} 216 | onEnter : (Event -> msg) -> Attribute msg 217 | onEnter = 218 | onWithOptions "mouseenter" defaultOptions 219 | 220 | 221 | {-| Listen to `mouseover` events. 222 | -} 223 | onOver : (Event -> msg) -> Attribute msg 224 | onOver = 225 | onWithOptions "mouseover" defaultOptions 226 | 227 | 228 | {-| Listen to `mouseleave` events. 229 | This event is fired when a mouse is moved out of the element 230 | that has the listener attached. 231 | It is similar to `mouseout` but doesn't bubble. 232 | More details available on the [MDN documentation][mdn-mouseleave]. 233 | 234 | [mdn-mouseleave]: https://developer.mozilla.org/en-US/docs/Web/Events/mouseleave 235 | 236 | -} 237 | onLeave : (Event -> msg) -> Attribute msg 238 | onLeave = 239 | onWithOptions "mouseleave" defaultOptions 240 | 241 | 242 | {-| Listen to `mouseout` events. 243 | -} 244 | onOut : (Event -> msg) -> Attribute msg 245 | onOut = 246 | onWithOptions "mouseout" defaultOptions 247 | 248 | 249 | {-| Listen to `contextmenu` events. 250 | Fired on right mousedown, before the context menu is displayed. 251 | -} 252 | onContextMenu : (Event -> msg) -> Attribute msg 253 | onContextMenu = 254 | onWithOptions "contextmenu" defaultOptions 255 | 256 | 257 | {-| Choose the mouse event to listen to, and specify the event options. 258 | If for some reason the default behavior of this package 259 | (prevent default) does not fit your needs, 260 | you can change it with for example: 261 | 262 | onDown : (Mouse.Event -> msg) -> Html.Attribute msg 263 | onDown = 264 | { stopPropagation = False, preventDefault = True } 265 | |> Mouse.onWithOptions "mousedown" 266 | 267 | -} 268 | onWithOptions : String -> EventOptions -> (Event -> msg) -> Attribute msg 269 | onWithOptions event options tag = 270 | eventDecoder 271 | |> Decode.map (\ev -> { message = tag ev, stopPropagation = options.stopPropagation, preventDefault = options.preventDefault }) 272 | |> Events.custom event 273 | 274 | 275 | defaultOptions : EventOptions 276 | defaultOptions = 277 | { stopPropagation = False 278 | , preventDefault = True 279 | } 280 | 281 | 282 | {-| Options for the event. 283 | -} 284 | type alias EventOptions = 285 | { stopPropagation : Bool 286 | , preventDefault : Bool 287 | } 288 | 289 | 290 | 291 | -- DECODERS ########################################################## 292 | 293 | 294 | {-| An `Event` decoder for mouse events. 295 | The decoder is provided so that you can extend `Mouse.Event` with 296 | specific properties you need. If for example you need the `movementX/Y` 297 | properties and you can guaranty that users will not use safari, 298 | you could do: 299 | 300 | type alias EventWithMovement = 301 | { mouseEvent : Mouse.Event 302 | , movement : ( Float, Float ) 303 | } 304 | 305 | decodeWithMovement : Decoder EventWithMovement 306 | decodeWithMovement = 307 | Decode.map2 EventWithMovement 308 | Mouse.eventDecoder 309 | movementDecoder 310 | 311 | movementDecoder : Decoder ( Float, Float ) 312 | movementDecoder = 313 | Decode.map2 (\a b -> ( a, b )) 314 | (Decode.field "movementX" Decode.float) 315 | (Decode.field "movementY" Decode.float) 316 | 317 | And use it like follows: 318 | 319 | type Msg 320 | = Movement ( Float, Float ) 321 | 322 | div 323 | [ onMove (.movement >> Movement) ] 324 | [ text "move here" ] 325 | 326 | 327 | onMove : (EventWithMovement -> msg) -> Html.Attribute msg 328 | onMove tag = 329 | let 330 | decoder = 331 | decodeWithMovement 332 | |> Decode.map tag 333 | |> Decode.map options 334 | 335 | options message = 336 | { message = message 337 | , stopPropagation = False 338 | , preventDefault = True 339 | } 340 | in 341 | Html.Events.custom "mousemove" decoder 342 | 343 | -} 344 | eventDecoder : Decoder Event 345 | eventDecoder = 346 | Decode.map6 Event 347 | Internal.Decode.keys 348 | buttonDecoder 349 | Internal.Decode.clientPos 350 | Internal.Decode.offsetPos 351 | Internal.Decode.pagePos 352 | Internal.Decode.screenPos 353 | 354 | 355 | buttonDecoder : Decoder Button 356 | buttonDecoder = 357 | Decode.map buttonFromId 358 | (Decode.field "button" Decode.int) 359 | 360 | 361 | buttonFromId : Int -> Button 362 | buttonFromId id = 363 | case id of 364 | 0 -> 365 | MainButton 366 | 367 | 1 -> 368 | MiddleButton 369 | 370 | 2 -> 371 | SecondButton 372 | 373 | 3 -> 374 | BackButton 375 | 376 | 4 -> 377 | ForwardButton 378 | 379 | _ -> 380 | ErrorButton 381 | -------------------------------------------------------------------------------- /src/Html/Events/Extra/Pointer.elm: -------------------------------------------------------------------------------- 1 | -- This Source Code Form is subject to the terms of the Mozilla Public 2 | -- License, v. 2.0. If a copy of the MPL was not distributed with this 3 | -- file, You can obtain one at http://mozilla.org/MPL/2.0/ 4 | 5 | 6 | module Html.Events.Extra.Pointer exposing 7 | ( Event, DeviceType(..), ContactDetails 8 | , onDown, onMove, onUp, onCancel, onOver, onEnter, onOut, onLeave 9 | , EventOptions, onWithOptions, eventDecoder 10 | ) 11 | 12 | {-| Handling pointer events. 13 | 14 | @docs Event, DeviceType, ContactDetails 15 | 16 | 17 | # Basic Usage 18 | 19 | @docs onDown, onMove, onUp, onCancel, onOver, onEnter, onOut, onLeave 20 | 21 | 22 | # Advanced Usage 23 | 24 | @docs EventOptions, onWithOptions, eventDecoder 25 | 26 | -} 27 | 28 | import Html 29 | import Html.Events 30 | import Html.Events.Extra.Mouse as Mouse 31 | import Json.Decode as Decode exposing (Decoder) 32 | 33 | 34 | 35 | -- MODEL ############################################################# 36 | 37 | 38 | {-| Type that get returned by a pointer event. 39 | 40 | Since the JS class [`PointerEvent`][PointerEvent] inherits from [`MouseEvent`][MouseEvent], 41 | the `pointer` attribute here is of type [`Mouse.Event`][Mouse-Event]. 42 | 43 | So to get the relative (offset) position of a pointer event for example: 44 | 45 | relativePos : Pointer.Event -> ( Float, Float ) 46 | relativePos event = 47 | event.pointer.offsetPos 48 | 49 | And to know if the shift key was pressed: 50 | 51 | isShiftKeyPressed : Pointer.Event -> Bool 52 | isShiftKeyPressed event = 53 | event.pointer.key.shift 54 | 55 | [PointerEvent]: https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent 56 | [MouseEvent]: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent 57 | [Mouse-Event]: Html-Events-Extra-Mouse#Event 58 | 59 | -} 60 | type alias Event = 61 | { pointerType : DeviceType 62 | , pointer : Mouse.Event 63 | , pointerId : Int 64 | , isPrimary : Bool 65 | , contactDetails : ContactDetails 66 | } 67 | 68 | 69 | {-| The type of device that generated the pointer event 70 | -} 71 | type DeviceType 72 | = MouseType 73 | | TouchType 74 | | PenType 75 | 76 | 77 | {-| Details of the point of contact, for advanced use cases. 78 | -} 79 | type alias ContactDetails = 80 | { width : Float 81 | , height : Float 82 | , pressure : Float 83 | , tiltX : Float 84 | , tiltY : Float 85 | } 86 | 87 | 88 | stringToPointerType : String -> DeviceType 89 | stringToPointerType str = 90 | case str of 91 | "pen" -> 92 | PenType 93 | 94 | "touch" -> 95 | TouchType 96 | 97 | _ -> 98 | MouseType 99 | 100 | 101 | 102 | -- EVENTS ############################################################ 103 | 104 | 105 | {-| Listen to `pointerdown` events. 106 | 107 | Let's say that we have a message type like this: 108 | 109 | type Msg 110 | = DownMsg ( Float, Float ) 111 | | MoveMsg ( Float, Float ) 112 | | UpMsg ( Float, Float ) 113 | 114 | And we already have defined the `relativePos : Pointer.Event -> ( Float, Float )` 115 | function (see [`Pointer.Event`](#Event) doc). Then we could listen to `pointerdown` 116 | events with something like: 117 | 118 | div [ Pointer.onDown (relativePos >> DownMsg) ] [ text "click here" ] 119 | 120 | However, since the [Pointer API][pointer-events] 121 | is not well [supported by all browsers][caniuse-pointer], 122 | I strongly recommend to use it in pair with the [elm-pep polyfill][elm-pep] 123 | for compatibility with Safari and Firefox < 59. 124 | It is also recommended that you deactivate `touch-action` 125 | to disable browsers scroll behaviors. 126 | 127 | div 128 | [ Pointer.onDown ... 129 | , Pointer.onMove ... 130 | , Pointer.onUp ... 131 | 132 | -- no touch-action (prevent scroll etc.) 133 | , Html.Attributes.style [ ( "touch-action", "none" ) ] 134 | ] 135 | [] 136 | 137 | [pointer-events]: https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent 138 | [caniuse-pointer]: https://caniuse.com/#feat=pointer 139 | [elm-pep]: https://github.com/mpizenberg/elm-pep 140 | 141 | -} 142 | onDown : (Event -> msg) -> Html.Attribute msg 143 | onDown = 144 | onWithOptions "pointerdown" defaultOptions 145 | 146 | 147 | {-| Listen to `pointermove` events. 148 | 149 | Similarly than with [`onDown`](#onDown), we can write something like: 150 | 151 | div [ Pointer.onMove (relativePos >> MoveMsg) ] [ text "move here" ] 152 | 153 | -} 154 | onMove : (Event -> msg) -> Html.Attribute msg 155 | onMove = 156 | onWithOptions "pointermove" defaultOptions 157 | 158 | 159 | {-| Listen to `pointerup` events. 160 | 161 | Similarly than with [`onDown`](#onDown), we can write something like: 162 | 163 | div [ Pointer.onUp (relativePos >> UpMsg) ] [ text "click here" ] 164 | 165 | -} 166 | onUp : (Event -> msg) -> Html.Attribute msg 167 | onUp = 168 | onWithOptions "pointerup" defaultOptions 169 | 170 | 171 | {-| Listen to `pointercancel` events. 172 | 173 | Similarly than with [`onDown`](#onDown), we can write something like: 174 | 175 | div [ Pointer.onCancel (relativePos >> UpMsg) ] [ text "move here" ] 176 | 177 | -} 178 | onCancel : (Event -> msg) -> Html.Attribute msg 179 | onCancel = 180 | onWithOptions "pointercancel" defaultOptions 181 | 182 | 183 | {-| Listen to `pointerover` events. 184 | 185 | Similarly than with [`onDown`](#onDown), we can write something like: 186 | 187 | div [ Pointer.onOver (relativePos >> UpMsg) ] [ text "move in here" ] 188 | 189 | -} 190 | onOver : (Event -> msg) -> Html.Attribute msg 191 | onOver = 192 | onWithOptions "pointerover" defaultOptions 193 | 194 | 195 | {-| Listen to `pointerenter` events. 196 | 197 | Similarly than with [`onDown`](#onDown), we can write something like: 198 | 199 | div [ Pointer.onEnter (relativePos >> UpMsg) ] [ text "move in here" ] 200 | 201 | -} 202 | onEnter : (Event -> msg) -> Html.Attribute msg 203 | onEnter = 204 | onWithOptions "pointerenter" defaultOptions 205 | 206 | 207 | {-| Listen to `pointerout` events. 208 | 209 | Similarly than with [`onDown`](#onDown), we can write something like: 210 | 211 | div [ Pointer.onOut (relativePos >> UpMsg) ] [ text "move out of here" ] 212 | 213 | -} 214 | onOut : (Event -> msg) -> Html.Attribute msg 215 | onOut = 216 | onWithOptions "pointerout" defaultOptions 217 | 218 | 219 | {-| Listen to `pointerleave` events. 220 | 221 | Similarly than with [`onDown`](#onDown), we can write something like: 222 | 223 | div [ Pointer.onLeave (relativePos >> UpMsg) ] [ text "move out of here" ] 224 | 225 | -} 226 | onLeave : (Event -> msg) -> Html.Attribute msg 227 | onLeave = 228 | onWithOptions "pointerleave" defaultOptions 229 | 230 | 231 | {-| Choose the pointer event to listen to, and specify the event options. 232 | 233 | If for some reason the default behavior of this lib 234 | (prevent default) does not fit your needs, 235 | you can change it with for example: 236 | 237 | myOnDown : (Pointer.Event -> msg) -> Html.Attribute msg 238 | myOnDown = 239 | { stopPropagation = False, preventDefault = True } 240 | |> Pointer.onWithOptions "pointerdown" 241 | 242 | -} 243 | onWithOptions : String -> EventOptions -> (Event -> msg) -> Html.Attribute msg 244 | onWithOptions event options tag = 245 | eventDecoder 246 | |> Decode.map (\ev -> { message = tag ev, stopPropagation = options.stopPropagation, preventDefault = options.preventDefault }) 247 | |> Html.Events.custom event 248 | 249 | 250 | defaultOptions : EventOptions 251 | defaultOptions = 252 | { stopPropagation = False 253 | , preventDefault = True 254 | } 255 | 256 | 257 | {-| Options for the event. 258 | -} 259 | type alias EventOptions = 260 | { stopPropagation : Bool 261 | , preventDefault : Bool 262 | } 263 | 264 | 265 | 266 | -- DECODERS ########################################################## 267 | 268 | 269 | {-| An Event decoder for pointer events. 270 | 271 | Similarly than with the `Mouse` module, the decoder is provided so that 272 | you can extend `Pointer.Event` with specific properties you need. 273 | If you need for example the [`tangentialPressure`][tangentialPressure] 274 | attribute of the pointer event, you could extend the present decoder like: 275 | 276 | type alias MyPointerEvent = 277 | { pointerEvent : Pointer.Event 278 | , tangentialPressure : Float 279 | } 280 | 281 | myEventDecoder : Decoder MyPointerEvent 282 | myEventDecoder = 283 | Decode.map2 MyPointerEvent 284 | Pointer.eventDecoder 285 | (Decode.field "tangentialPressure" Decode.float) 286 | 287 | And use it like as follows: 288 | 289 | type Msg 290 | = TangentialPressureMsg Float 291 | 292 | div 293 | [ myOnDown (.tangentialPressure >> TangentialPressureMsg) ] 294 | [ text "Use pen here to measure tangentialPressure" ] 295 | 296 | myOnDown : (MyPointerEvent -> msg) -> Html.Attribute msg 297 | myOnDown tag = 298 | let 299 | decoder = 300 | myEventDecoder 301 | |> Decode.map tag 302 | |> Decode.map options 303 | 304 | options message = 305 | { message = message 306 | , stopPropagation = False 307 | , preventDefault = True 308 | } 309 | in 310 | Html.Events.custom "pointerdown" decoder 311 | 312 | BEWARE that the minimalist [elm-pep] polyfill may not support 313 | all properties. So if you rely on it for compatibility with browsers 314 | not supporting pointer events, a decoder with an unsupported attribute 315 | will silently fail. 316 | If such a need arises, please open an issue in [elm-pep]. 317 | 318 | [tangentialPressure]: https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tangentialPressure 319 | [elm-pep]: https://github.com/mpizenberg/elm-pep 320 | 321 | -} 322 | eventDecoder : Decoder Event 323 | eventDecoder = 324 | Decode.map5 Event 325 | (Decode.field "pointerType" pointerTypeDecoder) 326 | Mouse.eventDecoder 327 | (Decode.field "pointerId" Decode.int) 328 | (Decode.field "isPrimary" Decode.bool) 329 | contactDetailsDecoder 330 | 331 | 332 | pointerTypeDecoder : Decoder DeviceType 333 | pointerTypeDecoder = 334 | Decode.map stringToPointerType Decode.string 335 | 336 | 337 | contactDetailsDecoder : Decoder ContactDetails 338 | contactDetailsDecoder = 339 | Decode.map5 ContactDetails 340 | (Decode.field "width" Decode.float) 341 | (Decode.field "height" Decode.float) 342 | (Decode.field "pressure" Decode.float) 343 | (Decode.field "tiltX" Decode.float) 344 | (Decode.field "tiltY" Decode.float) 345 | -------------------------------------------------------------------------------- /src/Html/Events/Extra/Touch.elm: -------------------------------------------------------------------------------- 1 | -- This Source Code Form is subject to the terms of the Mozilla Public 2 | -- License, v. 2.0. If a copy of the MPL was not distributed with this 3 | -- file, You can obtain one at http://mozilla.org/MPL/2.0/ 4 | 5 | 6 | module Html.Events.Extra.Touch exposing 7 | ( Event, Keys, Touch 8 | , onStart, onMove, onEnd, onCancel 9 | , EventOptions, onWithOptions, eventDecoder, touchDecoder, touchListDecoder 10 | ) 11 | 12 | {-| Handling touch events. 13 | 14 | @docs Event, Keys, Touch 15 | 16 | 17 | # Touch Events 18 | 19 | @docs onStart, onMove, onEnd, onCancel 20 | 21 | 22 | # Advanced Usage 23 | 24 | @docs EventOptions, onWithOptions, eventDecoder, touchDecoder, touchListDecoder 25 | 26 | -} 27 | 28 | import Html 29 | import Html.Events 30 | import Internal.Decode 31 | import Json.Decode as Decode exposing (Decoder) 32 | 33 | 34 | {-| Type that get returned by a browser touch event. 35 | Its purpose is to provide all useful properties of JavaScript [TouchEvent] 36 | in the context of the elm programming language. 37 | 38 | This event contains key modifiers that may have been pressed during touch event, 39 | and lists of Touch objects corresponding to JavaScript [Touch] objects. 40 | 41 | - `changedTouches`: the Touch objects representing individual points 42 | of contact whose states changed between the previous event and this one. 43 | - `targetTouches`: the Touch objects that are both currently in contact with 44 | the touch surface and were also started on the same element that is the event target. 45 | - `touches`: the Touch objects representing all current points of contact 46 | with the surface, regardless of target or changed status. 47 | 48 | [TouchEvent]: https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent 49 | [Touch]: https://developer.mozilla.org/en-US/docs/Web/API/Touch 50 | 51 | -} 52 | type alias Event = 53 | { keys : Keys 54 | , changedTouches : List Touch 55 | , targetTouches : List Touch 56 | , touches : List Touch 57 | } 58 | 59 | 60 | {-| Keys modifiers pressed during the event. 61 | Checking if the ctrl key was pressed when the event triggered is as easy as: 62 | 63 | isCtrlKeyPressed : Touch.Event -> Bool 64 | isCtrlKeyPressed touchEvent = 65 | touchEvent.keys.ctrl 66 | 67 | Beware that it may not be working on some platforms, returning always false. 68 | 69 | -} 70 | type alias Keys = 71 | { alt : Bool 72 | , ctrl : Bool 73 | , meta : Bool 74 | , shift : Bool 75 | } 76 | 77 | 78 | {-| A Touch object. 79 | It has a unique identifier, kept from start to end of a touch interaction. 80 | Client, page, and screen positions are provided for API completeness, 81 | however, you shall only need to use the `clientPos` property. 82 | For example, to get the coordinates of a touch event: 83 | 84 | touchCoordinates : Touch.Event -> ( Float, Float ) 85 | touchCoordinates touchEvent = 86 | List.head touchEvent.changedTouches 87 | |> Maybe.map .clientPos 88 | |> Maybe.withDefault ( 0, 0 ) 89 | 90 | -} 91 | type alias Touch = 92 | { identifier : Int 93 | , clientPos : ( Float, Float ) 94 | , pagePos : ( Float, Float ) 95 | , screenPos : ( Float, Float ) 96 | } 97 | 98 | 99 | 100 | -- EVENTS ############################################################ 101 | 102 | 103 | {-| Triggered on a "touchstart" event. 104 | Let's say that we have a message type like this: 105 | 106 | type Msg 107 | = StartMsg ( Float, Float ) 108 | | MoveMsg ( Float, Float ) 109 | | EndMsg ( Float, Float ) 110 | | CancelMsg ( Float, Float ) 111 | 112 | We can listen to `touchstart` events like follows: 113 | 114 | div 115 | [ Touch.onStart (\event -> StartMsg (touchCoordinates event)) ] 116 | [ text "touch here" ] 117 | 118 | In a curried style, this can also be written: 119 | 120 | div 121 | [ Touch.onStart (StartMsg << touchCoordinates) ] 122 | [ text "touch here" ] 123 | 124 | -} 125 | onStart : (Event -> msg) -> Html.Attribute msg 126 | onStart = 127 | onWithOptions "touchstart" defaultOptions 128 | 129 | 130 | {-| Triggered on a "touchmove" event. 131 | Similarly than with `onStart`, we can write: 132 | 133 | div 134 | [ Touch.onMove (MoveMsg << touchCoordinates) ] 135 | [ text "touch here" ] 136 | 137 | -} 138 | onMove : (Event -> msg) -> Html.Attribute msg 139 | onMove = 140 | onWithOptions "touchmove" defaultOptions 141 | 142 | 143 | {-| Triggered on a "touchend" event. 144 | Similarly than with `onStart`, we can write: 145 | 146 | div 147 | [ Touch.onEnd (EndMsg << touchCoordinates) ] 148 | [ text "touch here" ] 149 | 150 | -} 151 | onEnd : (Event -> msg) -> Html.Attribute msg 152 | onEnd = 153 | onWithOptions "touchend" defaultOptions 154 | 155 | 156 | {-| Triggered on a "touchcancel" event. 157 | Similarly than with `onStart`, we can write: 158 | 159 | div 160 | [ Touch.onCancel (CancelMsg << touchCoordinates) ] 161 | [ text "touch here" ] 162 | 163 | -} 164 | onCancel : (Event -> msg) -> Html.Attribute msg 165 | onCancel = 166 | onWithOptions "touchcancel" defaultOptions 167 | 168 | 169 | {-| Personalize the html event options. 170 | If for some reason the default behavior of this package (prevent default) 171 | does not fit your needs, you can change it like follows: 172 | 173 | onStart : (Touch.Event -> msg) -> Html.Attribute msg 174 | onStart = 175 | { stopPropagation = False, preventDefault = True } 176 | |> Touch.onWithOptions "touchstart" 177 | 178 | -} 179 | onWithOptions : String -> EventOptions -> (Event -> msg) -> Html.Attribute msg 180 | onWithOptions event options tag = 181 | eventDecoder 182 | |> Decode.map (\ev -> { message = tag ev, stopPropagation = options.stopPropagation, preventDefault = options.preventDefault }) 183 | |> Html.Events.custom event 184 | 185 | 186 | defaultOptions : EventOptions 187 | defaultOptions = 188 | { stopPropagation = False 189 | , preventDefault = True 190 | } 191 | 192 | 193 | {-| Options for the event. 194 | -} 195 | type alias EventOptions = 196 | { stopPropagation : Bool 197 | , preventDefault : Bool 198 | } 199 | 200 | 201 | 202 | -- DECODERS ########################################################## 203 | 204 | 205 | {-| Touch event decoder. 206 | The decoder is provided so that you can extend touch events if something you need is not provided. 207 | -} 208 | eventDecoder : Decoder Event 209 | eventDecoder = 210 | Decode.map4 Event 211 | Internal.Decode.keys 212 | (Decode.field "changedTouches" <| touchListDecoder touchDecoder) 213 | (Decode.field "targetTouches" <| touchListDecoder touchDecoder) 214 | (Decode.field "touches" <| touchListDecoder touchDecoder) 215 | 216 | 217 | {-| Touch object decoder. 218 | The decoder is provided so that you can extend touch events if something you need is not provided. 219 | -} 220 | touchDecoder : Decoder Touch 221 | touchDecoder = 222 | Decode.map4 Touch 223 | (Decode.field "identifier" Decode.int) 224 | Internal.Decode.clientPos 225 | Internal.Decode.pagePos 226 | Internal.Decode.screenPos 227 | 228 | 229 | {-| Personalized TouchList decoder. 230 | This decoder is provided so that you can extend touch events if something you need is not provided. 231 | If for example, you need the [`Touch.force`][touch-force] property, 232 | (which is not implemented by this package since not compatible with most browsers) 233 | and can guaranty in your use case that it will be used with a compatible browser, 234 | you could define: 235 | 236 | type alias MyTouchEvent = 237 | { changedTouches : TouchWithForce 238 | } 239 | 240 | type alias TouchWithForce = 241 | { touch : Touch 242 | , force : Float 243 | } 244 | 245 | decodeMyTouchEvent : Decoder MyTouchEvent 246 | decodeMyTouchEvent = 247 | Decode.map MyTouchEvent 248 | (Decode.field "changedTouches" (touchListDecoder decodeWithForce)) 249 | 250 | decodeWithForce : Decoder TouchWithForce 251 | decodeWithForce = 252 | Decode.map2 TouchWithForce 253 | Touch.touchDecoder 254 | (Decode.field "force" Decode.float) 255 | 256 | [touch-force]: https://developer.mozilla.org/en-US/docs/Web/API/Touch/force 257 | 258 | -} 259 | touchListDecoder : Decoder a -> Decoder (List a) 260 | touchListDecoder = 261 | Internal.Decode.dynamicListOf 262 | -------------------------------------------------------------------------------- /src/Html/Events/Extra/Wheel.elm: -------------------------------------------------------------------------------- 1 | -- This Source Code Form is subject to the terms of the Mozilla Public 2 | -- License, v. 2.0. If a copy of the MPL was not distributed with this 3 | -- file, You can obtain one at http://mozilla.org/MPL/2.0/ 4 | 5 | 6 | module Html.Events.Extra.Wheel exposing 7 | ( Event, DeltaMode(..) 8 | , onWheel 9 | , EventOptions, onWithOptions, eventDecoder 10 | ) 11 | 12 | {-| Handling wheel events. 13 | 14 | @docs Event, DeltaMode 15 | 16 | 17 | # Basic Usage 18 | 19 | @docs onWheel 20 | 21 | 22 | # Advanced Usage 23 | 24 | @docs EventOptions, onWithOptions, eventDecoder 25 | 26 | -} 27 | 28 | import Html 29 | import Html.Events 30 | import Html.Events.Extra.Mouse as Mouse 31 | import Json.Decode as Decode exposing (Decoder) 32 | 33 | 34 | {-| Type that get returned by a browser wheel event. 35 | Its purpose is to provide all useful properties of JavaScript 36 | [WheelEvent] in the context of the elm programming language. 37 | 38 | `deltaX` and `deltaZ` properties are not provided by default 39 | since not compatible with Safari. 40 | If you really need them, you can very easily build your own wheel event 41 | decoder by extending this one, 42 | or looking at the source code of this module and recoding one. 43 | 44 | [WheelEvent]: https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent 45 | 46 | -} 47 | type alias Event = 48 | { mouseEvent : Mouse.Event 49 | , deltaY : Float 50 | , deltaMode : DeltaMode 51 | } 52 | 53 | 54 | {-| The deltaMode property of a Wheel event. 55 | -} 56 | type DeltaMode 57 | = DeltaPixel 58 | | DeltaLine 59 | | DeltaPage 60 | 61 | 62 | {-| Listen to `wheel` events. 63 | Let's say that we have a message type like this: 64 | 65 | type Msg 66 | = ZoomIn 67 | | ZoomOut 68 | 69 | And we want to zoom in or out on an element depending on a wheel event: 70 | 71 | chooseZoom : Wheel.Event -> Msg 72 | chooseZoom wheelEvent = 73 | if wheelEvent.deltaY > 0 then 74 | ZoomOut 75 | else 76 | ZoomIn 77 | 78 | div 79 | [ Wheel.onWheel chooseZoom ] 80 | [ text "some zoomable area like an image" ] 81 | 82 | -} 83 | onWheel : (Event -> msg) -> Html.Attribute msg 84 | onWheel = 85 | onWithOptions defaultOptions 86 | 87 | 88 | {-| Enable personalization of html events options (prevent default) 89 | in case the default options do not fit your needs. 90 | You can change options like follows: 91 | 92 | onWheel : (Wheel.Event -> msg) -> Html.Attribute msg 93 | onWheel = 94 | { stopPropagation = False, preventDefault = True } 95 | |> Wheel.onWithOptions 96 | 97 | -} 98 | onWithOptions : EventOptions -> (Event -> msg) -> Html.Attribute msg 99 | onWithOptions options tag = 100 | eventDecoder 101 | |> Decode.map (\ev -> { message = tag ev, stopPropagation = options.stopPropagation, preventDefault = options.preventDefault }) 102 | |> Html.Events.custom "wheel" 103 | 104 | 105 | defaultOptions : EventOptions 106 | defaultOptions = 107 | { stopPropagation = False 108 | , preventDefault = True 109 | } 110 | 111 | 112 | {-| Options for the event. 113 | -} 114 | type alias EventOptions = 115 | { stopPropagation : Bool 116 | , preventDefault : Bool 117 | } 118 | 119 | 120 | {-| Wheel event decoder. 121 | It is provided in case you want to extend this `Wheel.Event` type with 122 | non provided properties (like `deltaX`, `deltaZ`). 123 | -} 124 | eventDecoder : Decoder Event 125 | eventDecoder = 126 | Decode.map3 Event 127 | Mouse.eventDecoder 128 | (Decode.field "deltaY" Decode.float) 129 | (Decode.field "deltaMode" deltaModeDecoder) 130 | 131 | 132 | deltaModeDecoder : Decoder DeltaMode 133 | deltaModeDecoder = 134 | let 135 | intToMode int = 136 | case int of 137 | 1 -> 138 | DeltaLine 139 | 140 | 2 -> 141 | DeltaPage 142 | 143 | _ -> 144 | DeltaPixel 145 | in 146 | Decode.map intToMode Decode.int 147 | -------------------------------------------------------------------------------- /src/Internal/Decode.elm: -------------------------------------------------------------------------------- 1 | -- This Source Code Form is subject to the terms of the Mozilla Public 2 | -- License, v. 2.0. If a copy of the MPL was not distributed with this 3 | -- file, You can obtain one at http://mozilla.org/MPL/2.0/ 4 | 5 | 6 | module Internal.Decode exposing 7 | ( Keys 8 | , clientPos 9 | , dynamicListOf 10 | , keys 11 | , offsetPos 12 | , pagePos 13 | , screenPos 14 | ) 15 | 16 | import Json.Decode as Decode exposing (Decoder) 17 | 18 | 19 | type alias Keys = 20 | { alt : Bool 21 | , ctrl : Bool 22 | , meta : Bool 23 | , shift : Bool 24 | } 25 | 26 | 27 | dynamicListOf : Decoder a -> Decoder (List a) 28 | dynamicListOf itemDecoder = 29 | let 30 | decodeN n = 31 | List.range 0 (n - 1) 32 | |> List.map decodeOne 33 | |> all 34 | 35 | decodeOne n = 36 | Decode.field (String.fromInt n) itemDecoder 37 | in 38 | Decode.field "length" Decode.int 39 | |> Decode.andThen decodeN 40 | 41 | 42 | all : List (Decoder a) -> Decoder (List a) 43 | all = 44 | List.foldr (Decode.map2 (::)) (Decode.succeed []) 45 | 46 | 47 | keys : Decoder Keys 48 | keys = 49 | Decode.map4 Keys 50 | (Decode.field "altKey" Decode.bool) 51 | (Decode.field "ctrlKey" Decode.bool) 52 | (Decode.field "metaKey" Decode.bool) 53 | (Decode.field "shiftKey" Decode.bool) 54 | 55 | 56 | clientPos : Decoder ( Float, Float ) 57 | clientPos = 58 | Decode.map2 (\a b -> ( a, b )) 59 | (Decode.field "clientX" Decode.float) 60 | (Decode.field "clientY" Decode.float) 61 | 62 | 63 | offsetPos : Decoder ( Float, Float ) 64 | offsetPos = 65 | Decode.map2 (\a b -> ( a, b )) 66 | (Decode.field "offsetX" Decode.float) 67 | (Decode.field "offsetY" Decode.float) 68 | 69 | 70 | pagePos : Decoder ( Float, Float ) 71 | pagePos = 72 | Decode.map2 (\a b -> ( a, b )) 73 | (Decode.field "pageX" Decode.float) 74 | (Decode.field "pageY" Decode.float) 75 | 76 | 77 | screenPos : Decoder ( Float, Float ) 78 | screenPos = 79 | Decode.map2 (\a b -> ( a, b )) 80 | (Decode.field "screenX" Decode.float) 81 | (Decode.field "screenY" Decode.float) 82 | -------------------------------------------------------------------------------- /upgrade.md: -------------------------------------------------------------------------------- 1 | # Upgrade Notice 2 | 3 | ## From elm 0.18 to 0.19 4 | 5 | ### Coming from elm-mouse-events 6 | 7 | Not many changes, you should get through without any major issue. 8 | Most importantly: 9 | 10 | * Mouse module is now under `Html.Events.Extra.Mouse` so changing your imports 11 | from `import Mouse` to `import Html.Events.Extra.Mouse as Mouse` 12 | should be sufficient most of the time. 13 | * The `Mouse.Event` type now also has `button`, `pagePos` and `screenPos` attributes. 14 | Depending on your usage, it might imply no or minor changes. 15 | 16 | ### Coming from elm-touch-events 17 | 18 | Many changes. Most importantly: 19 | 20 | * The `Touch` module is now under `Html.Events.Extra.Touch`. 21 | You can change imports like so: `import Html.Events.Extra.Touch as Touch`. 22 | * The modules `Touch`, `SingleTouch` and `MultiTouch` have been merged in one 23 | unique `Touch` module. 24 | It only features multitouch since getting a single touch event from 25 | the multitouch event is trivial with a function like `touchCoordinates` 26 | (see below). 27 | * The type `Touch.Event` is not opaque anymore. 28 | * Touches (changed, target, touches) are simple list instead of dicts now. 29 | Previous dict id are now in the `identifier : Int` field of a `Touch`. 30 | * A `Touch` also provides page and screen positions in addition to client. 31 | * Decoders are provided for advanced usage in case needed. 32 | 33 | ```elm 34 | touchCoordinates : Touch.Event -> ( Float, Float ) 35 | touchCoordinates touchEvent = 36 | List.head touchEvent.changedTouches 37 | |> Maybe.map .clientPos 38 | |> Maybe.withDefault ( 0, 0 ) 39 | ``` 40 | --------------------------------------------------------------------------------