├── .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 |
--------------------------------------------------------------------------------