{"id":17332993,"url":"https://github.com/mvanderkamp/westures","last_synced_at":"2025-10-12T10:31:24.466Z","repository":{"id":33127520,"uuid":"152194021","full_name":"mvanderkamp/westures","owner":"mvanderkamp","description":"Delightfully robust multitouch gestures for JavaScript","archived":false,"fork":false,"pushed_at":"2024-07-26T21:54:55.000Z","size":4956,"stargazers_count":43,"open_issues_count":1,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-10-05T08:56:58.277Z","etag":null,"topics":["api","custom-gestures","gesture","javascript","mouse","multitouch","multitouch-api","multitouch-libraries","pan","pinch","pull","rotate","swivel","tap","touch"],"latest_commit_sha":null,"homepage":"https://mvanderkamp.github.io/westures/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mvanderkamp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-10-09T05:43:32.000Z","updated_at":"2024-07-26T21:54:59.000Z","dependencies_parsed_at":"2024-07-26T23:14:17.987Z","dependency_job_id":null,"html_url":"https://github.com/mvanderkamp/westures","commit_stats":{"total_commits":719,"total_committers":11,"mean_commits":65.36363636363636,"dds":"0.16133518776077882","last_synced_commit":"9e9a6bd3f258b09c87b816bac464b5f60bd94854"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/mvanderkamp/westures","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvanderkamp%2Fwestures","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvanderkamp%2Fwestures/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvanderkamp%2Fwestures/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvanderkamp%2Fwestures/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mvanderkamp","download_url":"https://codeload.github.com/mvanderkamp/westures/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvanderkamp%2Fwestures/sbom","scorecard":{"id":669112,"data":{"date":"2025-08-11","repo":{"name":"github.com/mvanderkamp/westures","commit":"a72af8cf5f89269da2bca96e52c3113732d70dea"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 1/19 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/node.js.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/node.js.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/mvanderkamp/westures/node.js.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/node.js.yml:31: update your workflow using https://app.stepsecurity.io/secureworkflow/mvanderkamp/westures/node.js.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 14 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":4,"reason":"6 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-xq7p-g2vc-g82p","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T19:19:45.178Z","repository_id":33127520,"created_at":"2025-08-21T19:19:45.178Z","updated_at":"2025-08-21T19:19:45.178Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279011063,"owners_count":26084865,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-12T02:00:06.719Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","custom-gestures","gesture","javascript","mouse","multitouch","multitouch-api","multitouch-libraries","pan","pinch","pull","rotate","swivel","tap","touch"],"created_at":"2024-10-15T14:59:26.920Z","updated_at":"2025-10-12T10:31:24.069Z","avatar_url":"https://github.com/mvanderkamp.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# westures\n\n[![Node.js CI](https://github.com/mvanderkamp/westures/actions/workflows/node.js.yml/badge.svg)](https://github.com/mvanderkamp/westures/actions/workflows/node.js.yml)\n[![Coverage Status](https://coveralls.io/repos/github/mvanderkamp/westures/badge.svg?branch=master)](https://coveralls.io/github/mvanderkamp/westures?branch=master)\n[![Maintainability](https://api.codeclimate.com/v1/badges/fc7d7ace5a3018dc4071/maintainability)](https://codeclimate.com/github/mvanderkamp/westures/maintainability)\n\nWestures is a robust multitouch gesture engine for JavaScript. Each gesture is\ncapable of working seamlessly as touch points are added and removed, with no\nlimit on the number of touch points, and with each touch point contributing to\nthe gesture.\n\nVisit this page for an example of the system in action: [Westures Example](\nhttps://mvanderkamp.github.io/westures-example/).\n\nThe library achieves its goals without using any dependencies except for its\nown core, yet maintains usability across the main modern browsers.\nTranspilation may be necessary for this last point to be achieved, as the\nlibrary is written using many of the newer features of the JavaScript language.\nA transpiled bundle is provided, but the browser target list is arbitrary and\nlikely includes some bloat. In most cases you will be better off performing\nbundling, transpilation, and minification yourself.\n\nThis module includes\n[westures-core](https://mvanderkamp.github.io/westures-core/)\nas well as a base set of gestures.\n\nWestures is a fork of [ZingTouch](https://github.com/zingchart/zingtouch).\n\n## Quick Example\n\n```javascript\n// Import the module.\nconst wes = require('westures');\n\n// Declare a region. The default is the window object, but other elements like\n// the document body work too.\nconst region = new wes.Region();\n\n// Combine an element and a handler into a Gesture.\nconst pan = new wes.Pan(document.querySelector('#pannable'), (data) =\u003e {\n  console.log(data.translation.x, data.translation.y);\n})\n\n// And add the gesture to the region.\nregion.addGesture(pan)\n```\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Basic Usage](#basic-usage)\n- [Implementing Custom Gestures](#implementing-custom-gestures)\n- [What's Changed](#changes)\n- [Nomenclature and Origins](#nomenclature-and-origins)\n- [Issues](#Issues)\n- [Links](#links)\n\n## Overview\n\nThere are nine gestures defined in this module:\n\nName   | # of Inputs | Emit Phase | Recognized Input Behaviour\n------ | ----------- | ---------- | -----------------\nPan    | 1+          | Move       | Sliding around the screen\nPinch  | 2+          | Move       | Moving together or apart\nPress  | 1+          | Move       | Held down without moving\nPull   | 1+          | Move       | Moving away from or toward a fixed point\nRotate | 2+          | Move       | Rotating around each other\nSwipe  | 1+          | End        | Moving quickly then released\nSwivel | 1+          | Move       | Rotating around a fixed pivot point\nTap    | 1+          | End        | Quickly pressing and releasing\nTrack  | 1+          | All        | Track locations of all active pointers\n\nSee the [documentation](https://mvanderkamp.github.io/westures/) for more\ninformation about each gesture.\n\nNote that all x,y positions are obtained from the corresponding `clientX` and\n`clientY` properties of the input event.\n\n## Basic Usage\n\n- [Declaring a Region](#declaring-a-region)\n- [Instantiating a Gesture](#instantiating-a-gesture)\n- [Adding a Gesture to a Region](#adding-a-gesture-to-a-region)\n\n### Importing the module\n\n```javascript\nconst wes = require('westures');\n```\n\n### Declaring a Region\n\nFirst, decide what region should listen for events. This could be the\ninteractable element itself, or a larger region (possibly containing many\ninteractable elements). Behaviour may differ slightly based on the approach you\ntake, as a `Region` will perform locking operations on its interactable\nelements and their bound gestures so as to limit interference between elements\nduring gestures, and no such locking occurs between Regions.\n\nIf you have lots of interactable elements on your page, you may find it\nconvenient to use smaller elements as regions. Test it out in case, and see what\nworks better for you.\n\nBy default, the window object is used.\n\n```javascript\nconst region = new wes.Region();\n```\n\n### Instantiating a Gesture\n\nWhen you instantiate a gesture, you need to provide a handler as well as an\nElement. The gesture will only be recognized when the first pointer to interact\nwith the region was inside the given Element. Therefore unless you want to try\nsomething fancy the gesture element should probably be contained inside the\nregion element. It could even be the region element.\n\nNow for an example. Suppose you have a div (id 'pannable', although this is\nirrelevant from Westures' perspective) within which you want to detect a Pan\ngesture. First we need to find the element.\n\n```javascript\nconst pannable = document.querySelector('#pannable');\n```\n\nAnd we also need a handler. This function will be called whenever a gesture\nhook returns non-null data. For `Pan`, this is just the move phase, but the\nhandler doesn't need to know that. The data returned by the hook will be\navailable inside the handler.\n\n```javascript\nfunction panLogger(data) {\n  console.log(data.translation.x, data.translation.y);\n}\n```\n\nNow we're ready to combine the element and its handler into a gesture.\n\n```javascript\npan = new wes.Pan(pannable, panLogger);\n```\n\nWe're not quite done though, as none of this will actually work until you add\nthe gesture to the region.\n\n### Adding a Gesture to a Region\n\nSimple:\n\n```javascript\nregion.addGesture(pan);\n```\n\nNow the `panLogger` function will be called whenever a `pan` gesture is\ndetected on the `#pannable` element inside the region.\n\n## Implementing Custom Gestures\n\nThe technique used by Westures (originally conceived for ZingTouch) is to\nfilter all user inputs through four key lifecycle phases: `start`, `move`,\n`end`, and `cancel`. Gestures are defined by how they respond to these phases.\nTo respond to the phases, a gesture extends the `Gesture` class provided by\nthis module and overrides the method (a.k.a. \"hook\") corresponding to the name\nof the phase.\n\nThe hook, when called, will receive the current `State` object of the region.\nTo maintain responsiveness, the functionality within a hook should be short and\nas efficient as possible.\n\nFor example, a simple way to implement a `Tap` gesture would be as follows:\n\n```javascript\nconst { Gesture } = require('westures');\n\nconst TIMEOUT = 100;\n\nclass Tap extends Gesture {\n  constructor() {\n    super('tap');\n    this.startTime = null;\n  }\n\n  start(state) {\n    this.startTime = Date.now();\n  }\n\n  end(state) {\n    if (Date.now() - this.startTime \u003c= TIMEOUT) {\n        return state.getInputsInPhase('end')[0].current.point;\n    }\n    return null;\n  }\n}\n```\n\nThere are problems with this example, and it should probably not be used as an\nactual Tap gesture, it is merely to illustrate the basic idea.\n\nThe default hooks for all Gestures simply return null. Data will only be\nforwarded to bound handlers when a non-null value is returned by a hook.\nReturned values should be packed inside an object. For example, instead of just\n`return 42;`, a custom hook should do `return { value: 42 };`\n\nIf your Gesture subclass needs to track any kind of complex state, remember that\nit may be necessary to reset the state in the `cancel` phase.\n\nFor information about what data is accessible via the State object, see the full\ndocumentation [here](https://mvanderkamp.github.io/westures-core/State.html).\nNote that his documentation was generated with `jsdoc`.\n\n### Data Passed to Handlers\n\nAs you can see from above, it is the gesture which decides when data gets passed\nto handlers, and for the most part what that data will be. Note though that a\nfew properties will get added to the outgoing data object before the handler is\ncalled. Those properties are:\n\nName     | Type     | Value\n-------- | -------- | -----\ncentroid | Point2D  | The centroid of the input points.\nevent    | Event    | The input event which caused the gesture to be recognized\nphase    | String   | `'start'`, `'move'`, `'end'`, or `'cancel'`\ntype     | String   | The name of the gesture as specified by its designer.\ntarget   | Element  | The Element that is associated with the recognized gesture.\n\nIf data properties returned by a hook have a name collision with one of these\nproperties, the value from the hook gets precedent and the default is\noverwritten.\n\n## Changes\n\nSee the [changelog](\nhttps://github.com/mvanderkamp/westures/blob/master/CHANGELOG.md) for the most\nrecent updates.\n\n## Nomenclature and Origins\n\nIn my last year of univerisity, I was working on an API for building\nmulti-device interfaces called \"WAMS\" (Workspaces Across Multiple Surfaces),\nwhich included the goal of supporting multi-device gestures.\n\nAfter an extensive search I found that none of the available multitouch\nlibraries for JavaScript provided the fidelity I needed, and concluded that I\nwould need to write my own, or at least fork an existing one. ZingTouch proved\nto the be the most approachable, so I decided it would make a good starting\npoint.\n\nThe name \"westures\" is a mash-up of \"WAMS\" and \"gestures\".\n\n## Issues\n\nIf you find any issues, please let me know!\n\n## Links\n\n### westures\n\n- [npm](https://www.npmjs.com/package/westures)\n- [github](https://github.com/mvanderkamp/westures)\n- [documentation](https://mvanderkamp.github.io/westures/)\n\n### westures-core\n\n- [npm](https://www.npmjs.com/package/westures-core)\n- [github](https://github.com/mvanderkamp/westures-core)\n- [documentation](https://mvanderkamp.github.io/westures-core/)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmvanderkamp%2Fwestures","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmvanderkamp%2Fwestures","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmvanderkamp%2Fwestures/lists"}