diff --git a/.circleci/config.yml b/.circleci/config.yml index 51cb3e1dd8f..ae559eb988a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -157,6 +157,14 @@ jobs: - run: yarn run build-style-spec - run: yarn run build-flow-types - run: yarn run test-build + - deploy: + name: Trigger memory metrics when merging to master + command: | + if [ -n "${WEB_METRICS_TOKEN}" ]; then + if [[ $CIRCLE_BRANCH == master ]]; then + curl -X POST https://circleci.com/api/v1.1/project/github/mapbox/web-metrics/build?circle-token=${WEB_METRICS_TOKEN} + fi + fi - store_artifacts: path: "dist" - store_artifacts: diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000000..e43b0f98895 --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index ce6ecc2d8e9..9b0be4e6cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,64 @@ +## 1.11.0 + +### ✨ Features and improvements +* Add an option to scale the default `Marker` icon.([#9414](https://github.com/mapbox/mapbox-gl-js/pull/9414)) (h/t [adrianababakanian](https://github.com/adrianababakanian)) +* Improving the shader compilation speed by manually getting the run-time attributes and uniforms.([#9497](https://github.com/mapbox/mapbox-gl-js/pull/9497)) +* Added `clusterMinPoints` option for clustered GeoJSON sources that defines the minimum number of points to form a cluster.([#9748](https://github.com/mapbox/mapbox-gl-js/pull/9748)) + +### 🐞 Bug fixes +* Fix a bug where map got stuck in a DragRotate interaction if it's mouseup occurred outside of the browser window or iframe.([#9512](https://github.com/mapbox/mapbox-gl-js/pull/9512)) +* Fix potential visual regression for `*-pattern` properties on AMD graphics card vendor.([#9681](https://github.com/mapbox/mapbox-gl-js/pull/9681)) +* Fix zooming with a double tap on iOS Safari 13.([#9757](https://github.com/mapbox/mapbox-gl-js/pull/9757)) +* Removed a misleading `geometry exceeds allowed extent` warning when using Mapbox Streets vector tiles.([#9753](https://github.com/mapbox/mapbox-gl-js/pull/9753)) +* Fix reference error when requiring the browser bundle in Node. ([#9749](https://github.com/mapbox/mapbox-gl-js/pull/9749)) + +## 1.10.1 + +### 🐞 Bug fixes +* Fix markers interrupting touch gestures ([#9675](https://github.com/mapbox/mapbox-gl-js/issues/9675), fixed by [#9683](https://github.com/mapbox/mapbox-gl-js/pull/9683)) +* Fix bug where `map.isMoving()` returned true while map was not moving ([#9647](https://github.com/mapbox/mapbox-gl-js/issues/9647), fixed by [#9679](https://github.com/mapbox/mapbox-gl-js/pull/9679)) +* Fix regression that prevented `touchmove` events from firing during gestures ([#9676](https://github.com/mapbox/mapbox-gl-js/issues/9676), fixed by [#9685](https://github.com/mapbox/mapbox-gl-js/pull/9685)) +* Fix `image` expression evaluation which was broken under certain conditions ([#9630](https://github.com/mapbox/mapbox-gl-js/issues/9630), fixed by [#9685](https://github.com/mapbox/mapbox-gl-js/pull/9668)) +* Fix nested `within` expressions in filters not evaluating correctly ([#9605](https://github.com/mapbox/mapbox-gl-js/issues/9605), fixed by [#9611](https://github.com/mapbox/mapbox-gl-js/pull/9611)) +* Fix potential `undefined` paint variable in `StyleLayer` ([#9688](https://github.com/mapbox/mapbox-gl-js/pull/9688)) (h/t [mannnick24](https://github.com/mannnick24)) + +## 1.10.0 + +### ✨ Features +* Add `mapboxgl.prewarm()` and `mapboxgl.clearPrewarmedResources()` methods to allow developers to optimize load times for their maps ([#9391](https://github.com/mapbox/mapbox-gl-js/pull/9391)) +* Add `index-of` and `slice` expressions to search arrays and strings for the first occurrence of a specified value and return a section of the original array or string ([#9450](https://github.com/mapbox/mapbox-gl-js/pull/9450)) (h/t [lbutler](https://github.com/lbutler)) +* Correctly set RTL text plugin status if the plugin URL could not be loaded. This allows developers to add retry logic on network errors when loading the plugin ([#9489](https://github.com/mapbox/mapbox-gl-js/pull/9489)) + +### 🍏 Gestures +This release significantly refactors and improves gesture handling on desktop and mobile. Three new touch gestures have been added: `two-finger swipe` to adjust pitch, `two-finger double tap` to zoom out, and `tap then drag` to adjust zoom with one finger ([#9365](https://github.com/mapbox/mapbox-gl-js/pull/9365)). In addition, this release brings the following changes and bug fixes: + +- It's now possible to interact with multiple maps on the same page at the same time ([#9365](https://github.com/mapbox/mapbox-gl-js/pull/9365)) +- Fix map jump when releasing one finger after pinch zoom ([#9136](https://github.com/mapbox/mapbox-gl-js/issues/9136)) +- Stop mousedown and touchstart from interrupting `easeTo` animations when interaction handlers are disabled ([#8725](https://github.com/mapbox/mapbox-gl-js/issues/8725)) +- Stop mouse wheel from interrupting animations when `map.scrollZoom` is disabled ([#9230](https://github.com/mapbox/mapbox-gl-js/issues/9230)) +- A camera change can no longer be prevented by disabling the interaction handler within the camera change event. Selectively prevent camera changes by listening to the `mousedown` or `touchstart` map event and calling [.preventDefault()](https://docs.mapbox.com/mapbox-gl-js/api/#mapmouseevent#preventdefault) ([#9365](https://github.com/mapbox/mapbox-gl-js/pull/9365)) +- Undocumented properties on the camera change events fired by the doubleClickZoom handler have been removed ([#9365](https://github.com/mapbox/mapbox-gl-js/pull/9365)) + +### 🐞 Improvements and bug fixes +* Line labels now have improved collision detection, with greater precision in placement, reduced memory footprint, better placement under pitched camera orientations ([#9219](https://github.com/mapbox/mapbox-gl-js/pull/9219)) +* Fix `GlyphManager` continually re-requesting missing glyph ranges ([#8027](https://github.com/mapbox/mapbox-gl-js/issues/8027), fixed by [#9375](https://github.com/mapbox/mapbox-gl-js/pull/9375)) (h/t [oterral](https://github.com/oterral)) +* Avoid throwing errors when calling certain popup methods before the popup element is created ([#9433](https://github.com/mapbox/mapbox-gl-js/pull/9433)) +* Fix a bug where fill-extrusion features with colinear points were not returned by `map.queryRenderedFeatures(...)` ([#9454](https://github.com/mapbox/mapbox-gl-js/pull/9454)) +* Fix a bug where using feature state on a large input could cause a stack overflow error ([#9463](https://github.com/mapbox/mapbox-gl-js/pull/9463)) +* Fix exception when using `background-pattern` with data driven expressions ([#9518](https://github.com/mapbox/mapbox-gl-js/issues/9518), fixed by [#9520](https://github.com/mapbox/mapbox-gl-js/pull/9520)) +* Fix a bug where UI popups were potentially leaking event listeners ([#9498](https://github.com/mapbox/mapbox-gl-js/pull/9498)) (h/t [mbell697](https://github.com/mbell697)) +* Fix a bug where the `within` expression would return inconsistent values for points on tile boundaries ([#9411](https://github.com/mapbox/mapbox-gl-js/issues/9411), [#9428](https://github.com/mapbox/mapbox-gl-js/pull/9428)) +* Fix a bug where the `within` expression would incorrectly evaluate geometries that cross the antimeridian ([#9440](https://github.com/mapbox/mapbox-gl-js/pull/9440)) +* Fix possible undefined exception on paint variable of style layer ([#9437](https://github.com/mapbox/mapbox-gl-js/pull/9437)) (h/t [mannnick24](https://github.com/mannnick24)) +* Upgrade minimist to ^1.2.5 to get fix for security issue [CVE-2020-7598](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-7598) upstream ([#9425](https://github.com/mapbox/mapbox-gl-js/issues/9431), fixed by [#9425](https://github.com/mapbox/mapbox-gl-js/pull/9425)) (h/t [watson](https://github.com/watson)) + +## 1.9.1 + +### 🐞 Bug fixes +* Fix a bug [#9477](https://github.com/mapbox/mapbox-gl-js/issues/9477) in `Map#fitBounds(..)` wherein the `padding` passed to options would get applied twice. +* Fix rendering bug [#9479](https://github.com/mapbox/mapbox-gl-js/issues/9479) caused when data-driven `*-pattern` properties reference images added with `Map#addImage(..)`. +* Fix a bug [#9468](https://github.com/mapbox/mapbox-gl-js/issues/9468) in which an exception would get thrown when updating symbol layer paint property using `setPaintProperty`. + ## 1.9.0 With this release, we're adding [a new changelog policy](https://github.com/mapbox/mapbox-gl-js/blob/master/CONTRIBUTING.md#changelog-conventions) to our contribution guidelines. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c1dfc3fdc1..d3cbb5d9e14 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -139,7 +139,7 @@ Here is a recommended way to get setup: 1. Fork this project 2. Clone your new fork, `git clone git@github.com:GithubUser/mapbox-gl-js.git` 3. `cd mapbox-gl-js` -4. Add the Mapbox repository as an upstream repository: `git add remote upstream git@github.com:mapbox/mapbox-gl-js.git` +4. Add the Mapbox repository as an upstream repository: `git remote add upstream git@github.com:mapbox/mapbox-gl-js.git` 5. Create a new branch `git checkout -b your-branch` for your contribution 6. Write code, open a PR from your branch when you're ready 7. If you need to rebase your fork's PR branch onto master to resolve conflicts: `git fetch upstream`, `git rebase upstream/master` and force push to Github `git push --force origin your-branch` diff --git a/build/diff-tarball.js b/build/diff-tarball.js new file mode 100644 index 00000000000..43970decde2 --- /dev/null +++ b/build/diff-tarball.js @@ -0,0 +1,18 @@ +const packlist = require('npm-packlist') +const npmContent = require('list-npm-contents'); + +npmContent('mapbox-gl').then(function(last_version_files) { + packlist({ path: '.' }).then(function(new_version_files) { + new_version_files = new_version_files.map(file => file.replace(/\/\/+/g, '/')); + let diff_new = new_version_files.filter(x => !last_version_files.includes(x)); + let diff_last = last_version_files.filter(x => !new_version_files.includes(x)); + console.log(`${diff_new.length} files are about to be added in the new tarball`) + diff_new.forEach(file => { + console.log('+', file); + }); + console.log(`${diff_last.length} files are about to be deleted in the new tarball`) + diff_last.forEach(file => { + console.log('-', file); + }); + }); +}); \ No newline at end of file diff --git a/build/generate-struct-arrays.js b/build/generate-struct-arrays.js index ccbc1ecd179..f2ce2887159 100644 --- a/build/generate-struct-arrays.js +++ b/build/generate-struct-arrays.js @@ -93,6 +93,8 @@ function createStructArrayLayoutType({members, size, alignment}) { const key = `${members.map(m => `${m.components}${typeAbbreviations[m.type]}`).join('')}${size}`; const className = `StructArrayLayout${key}`; + // Layout alignment to 4 bytes boundaries can be an issue on some set of graphics cards. Particularly AMD. + if (size % 4 !== 0) { console.warn(`Warning: The layout ${className} is not aligned to 4-bytes boundaries.`); } if (!layoutCache[key]) { layoutCache[key] = { className, diff --git a/debug/markers.html b/debug/markers.html index 3e04b46804f..3f125d141f9 100644 --- a/debug/markers.html +++ b/debug/markers.html @@ -45,8 +45,11 @@ var g = Math.round(Math.random() * 255); var b = Math.round(Math.random() * 255); + var sc = Math.random() * 2.5 + 0.5; + var marker = new mapboxgl.Marker({ color: `rgb(${r}, ${g}, ${b})`, + scale: sc, draggable: true, rotationAlignment, pitchAlignment diff --git a/package.json b/package.json index 6fb990e75d7..ef105294c2b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mapbox-gl", "description": "A WebGL interactive maps library", - "version": "1.10.0-dev", + "version": "1.11.0", "main": "dist/mapbox-gl.js", "style": "dist/mapbox-gl.css", "license": "SEE LICENSE IN LICENSE.txt", @@ -33,7 +33,7 @@ "potpack": "^1.0.1", "quickselect": "^2.0.0", "rw": "^1.3.3", - "supercluster": "^7.0.0", + "supercluster": "^7.1.0", "tinyqueue": "^2.0.3", "vt-pbf": "^3.1.1" }, @@ -73,11 +73,13 @@ "jsdom": "^13.0.0", "json-stringify-pretty-compact": "^2.0.0", "jsonwebtoken": "^8.3.0", + "list-npm-contents": "^1.0.2", "lodash.template": "^4.5.0", "mapbox-gl-styles": "^2.0.2", "mock-geolocation": "^1.0.11", "node-notifier": "^5.4.3", "npm-font-open-sans": "^1.1.0", + "npm-packlist": "^2.1.1", "npm-run-all": "^4.1.5", "nyc": "^13.3.0", "pirates": "^4.0.1", @@ -125,7 +127,7 @@ "build-prod-min": "rollup -c --environment BUILD:production,MINIFY:true", "build-csp": "rollup -c rollup.config.csp.js", "build-query-suite": "rollup -c test/integration/rollup.config.test.js", - "build-flow-types": "cp build/mapbox-gl.js.flow dist/mapbox-gl.js.flow && cp build/mapbox-gl.js.flow dist/mapbox-gl-dev.js.flow", + "build-flow-types": "mkdir -p dist && cp build/mapbox-gl.js.flow dist/mapbox-gl.js.flow && cp build/mapbox-gl.js.flow dist/mapbox-gl-dev.js.flow", "build-css": "postcss -o dist/mapbox-gl.css src/css/mapbox-gl.css", "build-style-spec": "cd src/style-spec && npm run build && cd ../.. && mkdir -p dist/style-spec && cp src/style-spec/dist/* dist/style-spec", "watch-css": "postcss --watch -o dist/mapbox-gl.css src/css/mapbox-gl.css", @@ -138,6 +140,8 @@ "start-tests": "run-p build-token watch-css watch-query start-server", "start-bench": "run-p build-token watch-benchmarks start-server", "start-release": "run-s build-token build-prod-min build-css print-release-url start-server", + "diff-tarball": "build/run-node build/diff-tarball && echo \"Please confirm the above is correct [y/n]? \"; read answer; if [ \"$answer\" = \"${answer#[Yy]}\" ]; then false; fi", + "prepare-publish": "git clean -fdx && yarn install", "lint": "eslint --cache --ignore-path .gitignore src test bench debug/*.html", "lint-docs": "documentation lint src/index.js", "lint-css": "stylelint 'src/css/mapbox-gl.css'", @@ -154,14 +158,15 @@ "test-expressions": "build/run-node test/expression.test.js", "test-flow": "build/run-node build/generate-flow-typed-style-spec && flow .", "test-cov": "nyc --require=@mapbox/flow-remove-types/register --reporter=text-summary --reporter=lcov --cache run-s test-unit test-expressions test-query test-render", - "prepublishOnly": "run-s build-flow-types build-dev build-prod-min build-prod build-csp build-css build-style-spec test-build", + "prepublishOnly": "run-s prepare-publish build-flow-types build-dev build-prod-min build-prod build-csp build-css build-style-spec test-build diff-tarball", "print-release-url": "node build/print-release-url.js", "codegen": "build/run-node build/generate-style-code.js && build/run-node build/generate-struct-arrays.js" }, "files": [ "build/", - "dist/", - "flow-typed/", + "dist/mapbox-gl*", + "dist/style-spec/", + "flow-typed/*.js", "src/", ".flowconfig" ] diff --git a/rollup/bundle_prelude.js b/rollup/bundle_prelude.js index 50fe63085e3..4787f64f4d0 100644 --- a/rollup/bundle_prelude.js +++ b/rollup/bundle_prelude.js @@ -14,6 +14,8 @@ if (!shared) { var sharedChunk = {}; shared(sharedChunk); mapboxgl = chunk(sharedChunk); - mapboxgl.workerUrl = window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' })); + if (typeof window !== 'undefined') { + mapboxgl.workerUrl = window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' })); + } } } diff --git a/src/data/array_types.js b/src/data/array_types.js index 30b2c8c5f2f..db28da52273 100644 --- a/src/data/array_types.js +++ b/src/data/array_types.js @@ -149,12 +149,11 @@ register('StructArrayLayout2i4ub8', StructArrayLayout2i4ub8); /** * Implementation of the StructArray layout: - * [0]: Uint16[8] - * [16]: Uint8[2] + * [0]: Uint16[10] * * @private */ -class StructArrayLayout8ui2ub18 extends StructArray { +class StructArrayLayout10ui20 extends StructArray { uint8: Uint8Array; uint16: Uint16Array; @@ -170,8 +169,7 @@ class StructArrayLayout8ui2ub18 extends StructArray { } emplace(i: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number) { - const o2 = i * 9; - const o1 = i * 18; + const o2 = i * 10; this.uint16[o2 + 0] = v0; this.uint16[o2 + 1] = v1; this.uint16[o2 + 2] = v2; @@ -180,14 +178,14 @@ class StructArrayLayout8ui2ub18 extends StructArray { this.uint16[o2 + 5] = v5; this.uint16[o2 + 6] = v6; this.uint16[o2 + 7] = v7; - this.uint8[o1 + 16] = v8; - this.uint8[o1 + 17] = v9; + this.uint16[o2 + 8] = v8; + this.uint16[o2 + 9] = v9; return i; } } -StructArrayLayout8ui2ub18.prototype.bytesPerElement = 18; -register('StructArrayLayout8ui2ub18', StructArrayLayout8ui2ub18); +StructArrayLayout10ui20.prototype.bytesPerElement = 20; +register('StructArrayLayout10ui20', StructArrayLayout10ui20); /** * Implementation of the StructArray layout: @@ -1097,7 +1095,7 @@ export { StructArrayLayout4i8, StructArrayLayout2i4i12, StructArrayLayout2i4ub8, - StructArrayLayout8ui2ub18, + StructArrayLayout10ui20, StructArrayLayout4i4ui4i24, StructArrayLayout3f12, StructArrayLayout1ul4, @@ -1122,7 +1120,7 @@ export { StructArrayLayout2i4i12 as FillExtrusionLayoutArray, StructArrayLayout2i4 as HeatmapLayoutArray, StructArrayLayout2i4ub8 as LineLayoutArray, - StructArrayLayout8ui2ub18 as PatternLayoutArray, + StructArrayLayout10ui20 as PatternLayoutArray, StructArrayLayout4i4ui4i24 as SymbolLayoutArray, StructArrayLayout3f12 as SymbolDynamicLayoutArray, StructArrayLayout1ul4 as SymbolOpacityArray, diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index e9f86df667c..f86e63f53de 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -72,7 +72,7 @@ class CircleBucket implements Bucke this.layoutVertexArray = new CircleLayoutArray(); this.indexArray = new TriangleIndexArray(); this.segments = new SegmentVector(); - this.programConfigurations = new ProgramConfigurationSet(layoutAttributes, options.layers, options.zoom); + this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); } diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index 297ee0a872c..d46c7e0af09 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -68,7 +68,7 @@ class FillBucket implements Bucket { this.layoutVertexArray = new FillLayoutArray(); this.indexArray = new TriangleIndexArray(); this.indexArray2 = new LineIndexArray(); - this.programConfigurations = new ProgramConfigurationSet(layoutAttributes, options.layers, options.zoom); + this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.segments = new SegmentVector(); this.segments2 = new SegmentVector(); this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index bf507349946..0378758ba62 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -82,7 +82,7 @@ class FillExtrusionBucket implements Bucket { this.layoutVertexArray = new FillExtrusionLayoutArray(); this.indexArray = new TriangleIndexArray(); - this.programConfigurations = new ProgramConfigurationSet(layoutAttributes, options.layers, options.zoom); + this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.segments = new SegmentVector(); this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index d8a99723174..5764ddd4a9e 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -111,7 +111,7 @@ class LineBucket implements Bucket { this.layoutVertexArray = new LineLayoutArray(); this.indexArray = new TriangleIndexArray(); - this.programConfigurations = new ProgramConfigurationSet(layoutAttributes, options.layers, options.zoom); + this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.segments = new SegmentVector(); this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); diff --git a/src/data/bucket/pattern_attributes.js b/src/data/bucket/pattern_attributes.js index 01b512b8fc9..c4da6049f44 100644 --- a/src/data/bucket/pattern_attributes.js +++ b/src/data/bucket/pattern_attributes.js @@ -5,8 +5,8 @@ const patternAttributes = createLayout([ // [tl.x, tl.y, br.x, br.y] {name: 'a_pattern_from', components: 4, type: 'Uint16'}, {name: 'a_pattern_to', components: 4, type: 'Uint16'}, - {name: 'a_pixel_ratio_from', components: 1, type: 'Uint8'}, - {name: 'a_pixel_ratio_to', components: 1, type: 'Uint8'}, + {name: 'a_pixel_ratio_from', components: 1, type: 'Uint16'}, + {name: 'a_pixel_ratio_to', components: 1, type: 'Uint16'}, ]); export default patternAttributes; diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index cd7aadfaf66..02d96009206 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -382,8 +382,8 @@ class SymbolBucket implements Bucket { } createArrays() { - this.text = new SymbolBuffers(new ProgramConfigurationSet(symbolLayoutAttributes.members, this.layers, this.zoom, property => /^text/.test(property))); - this.icon = new SymbolBuffers(new ProgramConfigurationSet(symbolLayoutAttributes.members, this.layers, this.zoom, property => /^icon/.test(property))); + this.text = new SymbolBuffers(new ProgramConfigurationSet(this.layers, this.zoom, property => /^text/.test(property))); + this.icon = new SymbolBuffers(new ProgramConfigurationSet(this.layers, this.zoom, property => /^icon/.test(property))); this.glyphOffsetArray = new GlyphOffsetArray(); this.lineVertexArray = new SymbolLineVertexArray(); diff --git a/src/data/load_geometry.js b/src/data/load_geometry.js index 5a094fa1f81..c8885c57bda 100644 --- a/src/data/load_geometry.js +++ b/src/data/load_geometry.js @@ -10,14 +10,9 @@ import type Point from '@mapbox/point-geometry'; // While visible coordinates are within [0, EXTENT], tiles may theoretically // contain cordinates within [-Infinity, Infinity]. Our range is limited by the // number of bits used to represent the coordinate. -function createBounds(bits) { - return { - min: -1 * Math.pow(2, bits - 1), - max: Math.pow(2, bits - 1) - 1 - }; -} - -const bounds = createBounds(15); +const BITS = 15; +const MAX = Math.pow(2, BITS - 1) - 1; +const MIN = -MAX - 1; /** * Loads a geometry from a VectorTileFeature and scales it to the common extent @@ -34,13 +29,16 @@ export default function loadGeometry(feature: VectorTileFeature): Array bounds.max || point.y < bounds.min || point.y > bounds.max) { + if (x < point.x || x > point.x + 1 || y < point.y || y > point.y + 1) { + // warn when exceeding allowed extent except for the 1-px-off case + // https://github.com/mapbox/mapbox-gl-js/issues/8992 warnOnce('Geometry exceeds allowed extent, reduce your vector tile buffer size'); - point.x = clamp(point.x, bounds.min, bounds.max); - point.y = clamp(point.y, bounds.min, bounds.max); } } } diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 2d8c1578041..0ea6ee93895 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -398,13 +398,11 @@ class CrossFadedCompositeBinder implements AttributeBinder { export default class ProgramConfiguration { binders: {[_: string]: (AttributeBinder | UniformBinder) }; cacheKey: string; - layoutAttributes: Array; _buffers: Array; - constructor(layer: TypedStyleLayer, zoom: number, filterProperties: (_: string) => boolean, layoutAttributes: Array) { + constructor(layer: TypedStyleLayer, zoom: number, filterProperties: (_: string) => boolean) { this.binders = {}; - this.layoutAttributes = layoutAttributes; this._buffers = []; const keys = []; @@ -500,6 +498,36 @@ export default class ProgramConfiguration { return result; } + getBinderAttributes(): Array { + const result = []; + for (const property in this.binders) { + const binder = this.binders[property]; + if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder) { + for (let i = 0; i < binder.paintVertexAttributes.length; i++) { + result.push(binder.paintVertexAttributes[i].name); + } + } else if (binder instanceof CrossFadedCompositeBinder) { + for (let i = 0; i < patternAttributes.members.length; i++) { + result.push(patternAttributes.members[i].name); + } + } + } + return result; + } + + getBinderUniforms(): Array { + const uniforms = []; + for (const property in this.binders) { + const binder = this.binders[property]; + if (binder instanceof ConstantBinder || binder instanceof CrossFadedConstantBinder || binder instanceof CompositeExpressionBinder) { + for (const uniformName of binder.uniformNames) { + uniforms.push(uniformName); + } + } + } + return uniforms; + } + getPaintVertexBuffers(): Array { return this._buffers; } @@ -567,10 +595,10 @@ export class ProgramConfigurationSet { _featureMap: FeaturePositionMap; _bufferOffset: number; - constructor(layoutAttributes: Array, layers: $ReadOnlyArray, zoom: number, filterProperties: (_: string) => boolean = () => true) { + constructor(layers: $ReadOnlyArray, zoom: number, filterProperties: (_: string) => boolean = () => true) { this.programConfigurations = {}; for (const layer of layers) { - this.programConfigurations[layer.id] = new ProgramConfiguration(layer, zoom, filterProperties, layoutAttributes); + this.programConfigurations[layer.id] = new ProgramConfiguration(layer, zoom, filterProperties); } this.needsUpload = false; this._featureMap = new FeaturePositionMap(); diff --git a/src/geo/lng_lat.js b/src/geo/lng_lat.js index 971214994a3..e978f348cee 100644 --- a/src/geo/lng_lat.js +++ b/src/geo/lng_lat.js @@ -12,8 +12,10 @@ export const earthRadius = 6371008.8; /** * A `LngLat` object represents a given longitude and latitude coordinate, measured in degrees. + * These coordinates are based on the [WGS84 (EPSG:4326) standard](https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84). * - * Mapbox GL uses longitude, latitude coordinate order (as opposed to latitude, longitude) to match GeoJSON. + * Mapbox GL uses longitude, latitude coordinate order (as opposed to latitude, longitude) to match the + * [GeoJSON specification](https://tools.ietf.org/html/rfc7946). * * Note that any Mapbox GL method that accepts a `LngLat` object as an argument or option * can also accept an `Array` of two numbers and will perform an implicit conversion. @@ -22,7 +24,8 @@ export const earthRadius = 6371008.8; * @param {number} lng Longitude, measured in degrees. * @param {number} lat Latitude, measured in degrees. * @example - * var ll = new mapboxgl.LngLat(-73.9749, 40.7736); + * var ll = new mapboxgl.LngLat(-123.9749, 40.7736); + * ll.lng; // = -123.9749 * @see [Get coordinates of the mouse pointer](https://www.mapbox.com/mapbox-gl-js/example/mouse-position/) * @see [Display a popup](https://www.mapbox.com/mapbox-gl-js/example/popup/) * @see [Highlight features within a bounding box](https://www.mapbox.com/mapbox-gl-js/example/using-box-queryrenderedfeatures/) diff --git a/src/geo/lng_lat_bounds.js b/src/geo/lng_lat_bounds.js index 7fbf4db9c4a..f2360e96db6 100644 --- a/src/geo/lng_lat_bounds.js +++ b/src/geo/lng_lat_bounds.js @@ -216,6 +216,15 @@ class LngLatBounds { * * @param {LngLatLike} lnglat geographic point to check against. * @returns {boolean} True if the point is within the bounding box. + * @example + * var llb = new mapboxgl.LngLatBounds( + * new mapboxgl.LngLat(-73.9876, 40.7661), + * new mapboxgl.LngLat(-73.9397, 40.8002) + * ); + * + * var ll = new mapboxgl.LngLat(-73.9567, 40.7789); + * + * console.log(llb.contains(ll)); // = true */ contains(lnglat: LngLatLike) { const {lng, lat} = LngLat.convert(lnglat); diff --git a/src/index.js b/src/index.js index c85f5def198..bdc8b97f348 100644 --- a/src/index.js +++ b/src/index.js @@ -163,6 +163,8 @@ const exported = { * * @function clearStorage * @param {Function} callback Called with an error argument if there is an error. + * @example + * mapboxgl.clearStorage(); */ clearStorage(callback?: (err: ?Error) => void) { clearTileCache(callback); diff --git a/src/render/painter.js b/src/render/painter.js index 32fbe08548b..56507434800 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -589,6 +589,7 @@ class Painter { */ isPatternMissing(image: ?CrossFaded): boolean { if (!image) return false; + if (!image.from || !image.to) return true; const imagePosA = this.imageManager.getPattern(image.from.toString()); const imagePosB = this.imageManager.getPattern(image.to.toString()); return !imagePosA || !imagePosB; @@ -598,7 +599,7 @@ class Painter { this.cache = this.cache || {}; const key = `${name}${programConfiguration ? programConfiguration.cacheKey : ''}${this._showOverdrawInspector ? '/overdraw' : ''}`; if (!this.cache[key]) { - this.cache[key] = new Program(this.context, shaders[name], programConfiguration, programUniforms[name], this._showOverdrawInspector); + this.cache[key] = new Program(this.context, name, shaders[name], programConfiguration, programUniforms[name], this._showOverdrawInspector); } return this.cache[key]; } diff --git a/src/render/program.js b/src/render/program.js index 962988a78c5..7e273c0a339 100644 --- a/src/render/program.js +++ b/src/render/program.js @@ -21,6 +21,16 @@ export type DrawMode = | $PropertyType | $PropertyType; +function getTokenizedAttributesAndUniforms (array: Array): Array { + const result = []; + + for (let i = 0; i < array.length; i++) { + if (array[i] === null) continue; + const token = array[i].split(' '); + result.push(token.pop()); + } + return result; +} class Program { program: WebGLProgram; attributes: {[_: string]: number}; @@ -30,13 +40,27 @@ class Program { failedToCreate: boolean; constructor(context: Context, - source: {fragmentSource: string, vertexSource: string}, - configuration: ?ProgramConfiguration, - fixedUniforms: (Context, UniformLocations) => Us, - showOverdrawInspector: boolean) { + name: string, + source: {fragmentSource: string, vertexSource: string, staticAttributes: Array, staticUniforms: Array}, + configuration: ?ProgramConfiguration, + fixedUniforms: (Context, UniformLocations) => Us, + showOverdrawInspector: boolean) { const gl = context.gl; this.program = gl.createProgram(); + const staticAttrInfo = getTokenizedAttributesAndUniforms(source.staticAttributes); + const dynamicAttrInfo = configuration ? configuration.getBinderAttributes() : []; + const allAttrInfo = staticAttrInfo.concat(dynamicAttrInfo); + + const staticUniformsInfo = source.staticUniforms ? getTokenizedAttributesAndUniforms(source.staticUniforms) : []; + const dynamicUniformsInfo = configuration ? configuration.getBinderUniforms() : []; + // remove duplicate uniforms + const uniformList = staticUniformsInfo.concat(dynamicUniformsInfo); + const allUniformsInfo = []; + for (const uniform of uniformList) { + if (allUniformsInfo.indexOf(uniform) < 0) allUniformsInfo.push(uniform); + } + const defines = configuration ? configuration.defines() : []; if (showOverdrawInspector) { defines.push('#define OVERDRAW_INSPECTOR;'); @@ -64,13 +88,16 @@ class Program { assert(gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS), (gl.getShaderInfoLog(vertexShader): any)); gl.attachShader(this.program, vertexShader); - // Manually bind layout attributes in the order defined by their - // ProgramInterface so that we don't dynamically link an unused - // attribute at position 0, which can cause rendering to fail for an - // entire layer (see #4607, #4728) - const layoutAttributes = configuration ? configuration.layoutAttributes : []; - for (let i = 0; i < layoutAttributes.length; i++) { - gl.bindAttribLocation(this.program, i, layoutAttributes[i].name); + this.attributes = {}; + const uniformLocations = {}; + + this.numAttributes = allAttrInfo.length; + + for (let i = 0; i < this.numAttributes; i++) { + if (allAttrInfo[i]) { + gl.bindAttribLocation(this.program, i, allAttrInfo[i]); + this.attributes[allAttrInfo[i]] = i; + } } gl.linkProgram(this.program); @@ -79,23 +106,13 @@ class Program { gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); - this.numAttributes = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES); - - this.attributes = {}; - const uniformLocations = {}; - - for (let i = 0; i < this.numAttributes; i++) { - const attribute = gl.getActiveAttrib(this.program, i); - if (attribute) { - this.attributes[attribute.name] = gl.getAttribLocation(this.program, attribute.name); - } - } - - const numUniforms = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS); - for (let i = 0; i < numUniforms; i++) { - const uniform = gl.getActiveUniform(this.program, i); - if (uniform) { - uniformLocations[uniform.name] = gl.getUniformLocation(this.program, uniform.name); + for (let it = 0; it < allUniformsInfo.length; it++) { + const uniform = allUniformsInfo[it]; + if (uniform && !uniformLocations[uniform]) { + const uniformLocation = gl.getUniformLocation(this.program, uniform); + if (uniformLocation) { + uniformLocations[uniform] = uniformLocation; + } } } diff --git a/src/shaders/shaders.js b/src/shaders/shaders.js index 221728dfddd..25f1fabdbcb 100644 --- a/src/shaders/shaders.js +++ b/src/shaders/shaders.js @@ -87,6 +87,11 @@ export const symbolTextAndIcon = compile(symbolTextAndIconFrag, symbolTextAndIco function compile(fragmentSource, vertexSource) { const re = /#pragma mapbox: ([\w]+) ([\w]+) ([\w]+) ([\w]+)/g; + const staticAttributes = vertexSource.match(/attribute ([\w]+) ([\w]+)/g); + const fragmentUniforms = fragmentSource.match(/uniform ([\w]+) ([\w]+)([\s]*)([\w]*)/g); + const vertexUniforms = vertexSource.match(/uniform ([\w]+) ([\w]+)([\s]*)([\w]*)/g); + const staticUniforms = vertexUniforms ? vertexUniforms.concat(fragmentUniforms) : fragmentUniforms; + const fragmentPragmas = {}; fragmentSource = fragmentSource.replace(re, (match, operation, precision, type, name) => { @@ -176,5 +181,5 @@ uniform ${precision} ${type} u_${name}; } }); - return {fragmentSource, vertexSource}; + return {fragmentSource, vertexSource, staticAttributes, staticUniforms}; } diff --git a/src/source/geojson_source.js b/src/source/geojson_source.js index 2b043f1ef91..6bfc40b0cef 100644 --- a/src/source/geojson_source.js +++ b/src/source/geojson_source.js @@ -138,6 +138,7 @@ class GeoJSONSource extends Evented implements Source { maxZoom: options.clusterMaxZoom !== undefined ? Math.min(options.clusterMaxZoom, this.maxzoom - 1) : (this.maxzoom - 1), + minPoints: Math.max(2, options.clusterMinPoints || 2), extent: EXTENT, radius: (options.clusterRadius || 50) * scale, log: false, @@ -230,6 +231,22 @@ class GeoJSONSource extends Evented implements Source { * @param offset The number of features to skip (e.g. for pagination). * @param callback A callback to be called when the features are retrieved (`(error, features) => { ... }`). * @returns {GeoJSONSource} this + * @example + * // Retrieve cluster leaves on click + * map.on('click', 'clusters', function(e) { + * var features = map.queryRenderedFeatures(e.point, { + * layers: ['clusters'] + * }); + * + * var clusterId = features[0].properties.cluster_id; + * var pointCount = features[0].properties.point_count; + * var clusterSource = map.getSource('clusters'); + * + * clusterSource.getClusterLeaves(clusterId, pointCount, 0, function(error, features) { + * // Print cluster leaves in the console + * console.log('Cluster leaves:', error, features); + * }) + * }); */ getClusterLeaves(clusterId: number, limit: number, offset: number, callback: Callback>) { this.actor.send('geojson.getClusterLeaves', { diff --git a/src/source/tile.js b/src/source/tile.js index dcb8120bf2b..25164718dee 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -405,7 +405,7 @@ class Tile { bucket.update(sourceLayerStates, sourceLayer, this.imageAtlas && this.imageAtlas.patternPositions || {}); const layer = painter && painter.style && painter.style.getLayer(id); - if (layer && layer.paint) { + if (layer) { this.queryPadding = Math.max(this.queryPadding, layer.queryRadius(bucket)); } } diff --git a/src/source/worker.js b/src/source/worker.js index ab6827e3afc..27a10fa3a54 100644 --- a/src/source/worker.js +++ b/src/source/worker.js @@ -77,6 +77,12 @@ export default class Worker { setImages(mapId: string, images: Array, callback: WorkerTileCallback) { this.availableImages[mapId] = images; + for (const workerSource in this.workerSources[mapId]) { + const ws = this.workerSources[mapId][workerSource]; + for (const source in ws) { + ws[source].availableImages = images; + } + } callback(); } diff --git a/src/source/worker_source.js b/src/source/worker_source.js index 7acc8140728..c800049e3b3 100644 --- a/src/source/worker_source.js +++ b/src/source/worker_source.js @@ -71,6 +71,7 @@ export type WorkerDEMTileCallback = (err: ?Error, result: ?DEMData) => void; * @param layerIndex */ export interface WorkerSource { + availableImages: Array, // Disabled due to https://github.com/facebook/flow/issues/5208 // constructor(actor: Actor, layerIndex: StyleLayerIndex): WorkerSource; diff --git a/src/style-spec/CHANGELOG.md b/src/style-spec/CHANGELOG.md index 6bce8a3ebc4..f18b6812011 100644 --- a/src/style-spec/CHANGELOG.md +++ b/src/style-spec/CHANGELOG.md @@ -1,3 +1,21 @@ +## 13.15.0 + +### ✨ Features and improvements +* Add `distance` expression to `style-spec`. This expression returns the shortest distance between a feature and an input geometry ([#9655](https://github.com/mapbox/mapbox-gl-js/pull/9655)) + +## 13.14.0 + +### ✨ Features and improvements +* Add `index-of` and `slice` expressions to search arrays and strings for the first occurrence of a specified value and return a section of the original array or string ([#9450](https://github.com/mapbox/mapbox-gl-js/pull/9450)) (h/t [lbutler](https://github.com/lbutler)) + +## 13.13.1 + +### ✨ Features and improvements +* Expose `expression.isExpressionFilter(..)` from the bundle. ([#9530](https://github.com/mapbox/mapbox-gl-js/pull/9530)) + +### 🐛 Bug fixes +* Fix a broken module import where the `style-spec` package was importing files from `mapbox-gl-js`, it's parent repo, causing downstream build systems to break. ([#9522](https://github.com/mapbox/mapbox-gl-js/pull/9522)) + ## 13.13.0 ### ✨ Features and improvements diff --git a/src/style-spec/expression/definitions/within.js b/src/style-spec/expression/definitions/within.js index 7c0b17037b1..67d60beec45 100644 --- a/src/style-spec/expression/definitions/within.js +++ b/src/style-spec/expression/definitions/within.js @@ -7,8 +7,6 @@ import type {Expression} from '../expression'; import type ParsingContext from '../parsing_context'; import type EvaluationContext from '../evaluation_context'; import type {GeoJSON, GeoJSONPolygon, GeoJSONMultiPolygon} from '@mapbox/geojson-types'; -import MercatorCoordinate from '../../../geo/mercator_coordinate'; -import EXTENT from '../../../data/extent'; import Point from '@mapbox/point-geometry'; import type {CanonicalTileID} from '../../../source/tile_id'; @@ -16,6 +14,8 @@ type GeoJSONPolygons =| GeoJSONPolygon | GeoJSONMultiPolygon; // minX, minY, maxX, maxY type BBox = [number, number, number, number]; +const EXTENT = 8192; + function updateBBox(bbox: BBox, coord: Point) { bbox[0] = Math.min(bbox[0], coord[0]); bbox[1] = Math.min(bbox[1], coord[1]); @@ -23,6 +23,14 @@ function updateBBox(bbox: BBox, coord: Point) { bbox[3] = Math.max(bbox[3], coord[1]); } +function mercatorXfromLng(lng: number) { + return (180 + lng) / 360; +} + +function mercatorYfromLat(lat: number) { + return (180 - (180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)))) / 360; +} + function boxWithinBox(bbox1: BBox, bbox2: BBox) { if (bbox1[0] <= bbox2[0]) return false; if (bbox1[2] >= bbox2[2]) return false; @@ -32,9 +40,10 @@ function boxWithinBox(bbox1: BBox, bbox2: BBox) { } function getTileCoordinates(p, canonical: CanonicalTileID) { - const coord = MercatorCoordinate.fromLngLat({lng: p[0], lat: p[1]}, 0); + const x = mercatorXfromLng(p[0]); + const y = mercatorYfromLat(p[1]); const tilesAtZoom = Math.pow(2, canonical.z); - return [Math.round(coord.x * tilesAtZoom * EXTENT), Math.round(coord.y * tilesAtZoom * EXTENT)]; + return [Math.round(x * tilesAtZoom * EXTENT), Math.round(y * tilesAtZoom * EXTENT)]; } function onBoundary(p, p1, p2) { diff --git a/src/style-spec/feature_filter/index.js b/src/style-spec/feature_filter/index.js index ffe1662d3b5..a096d14ed73 100644 --- a/src/style-spec/feature_filter/index.js +++ b/src/style-spec/feature_filter/index.js @@ -85,7 +85,7 @@ function createFilter(filter: any): FeatureFilter { if (compiled.result === 'error') { throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', ')); } else { - const needGeometry = Array.isArray(filter) && filter.length !== 0 && filter[0] === 'within'; + const needGeometry = geometryNeeded(filter); return {filter: (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => compiled.value.evaluate(globalProperties, feature, {}, canonical), needGeometry}; } @@ -96,6 +96,15 @@ function compare(a, b) { return a < b ? -1 : a > b ? 1 : 0; } +function geometryNeeded(filter) { + if (!Array.isArray(filter)) return false; + if (filter[0] === 'within') return true; + for (let index = 1; index < filter.length; index++) { + if (geometryNeeded(filter[index])) return true; + } + return false; +} + function convertFilter(filter: ?Array): mixed { if (!filter) return true; const op = filter[0]; @@ -114,6 +123,7 @@ function convertFilter(filter: ?Array): mixed { op === '!in' ? convertNegation(convertInOp(filter[1], filter.slice(2))) : op === 'has' ? convertHasOp(filter[1]) : op === '!has' ? convertNegation(convertHasOp(filter[1])) : + op === 'within' ? filter : true; return converted; } diff --git a/src/style-spec/package.json b/src/style-spec/package.json index 5d0fd6ad889..53958fff0c9 100644 --- a/src/style-spec/package.json +++ b/src/style-spec/package.json @@ -1,7 +1,7 @@ { "name": "@mapbox/mapbox-gl-style-spec", "description": "a specification for mapbox gl styles", - "version": "13.14.0-dev", + "version": "13.16.0", "author": "Mapbox", "keywords": [ "mapbox", @@ -14,7 +14,7 @@ "scripts": { "copy-flow-typed": "cp -R ../../flow-typed .", "build": "../../node_modules/.bin/rollup -c && ../../node_modules/.bin/rollup -c --environment esm", - "prepublish": "yarn copy-flow-typed && yarn build", + "prepublish": "git clean -fdx && yarn copy-flow-typed && yarn build", "postpublish": "rm -r flow-typed dist/index.js" }, "repository": { diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index 0ced8c311e0..e713ee924e5 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -377,6 +377,10 @@ "type": "number", "doc": "Max zoom on which to cluster points if clustering is enabled. Defaults to one zoom less than maxzoom (so that last zoom features are not clustered)." }, + "clusterMinPoints": { + "type": "number", + "doc": "Minimum number of points necessary to form a cluster if clustering is enabled. Defaults to `2`." + }, "clusterProperties": { "type": "*", "doc": "An object defining custom properties on the generated clusters if clustering is enabled, aggregating values from clustered points. Has the form `{\"property_name\": [operator, map_expression]}`. `operator` is any expression function that accepts at least 2 operands (e.g. `\"+\"` or `\"max\"`) — it accumulates the property value from clusters/points the cluster contains; `map_expression` produces the value of a single point.\n\nExample: `{\"sum\": [\"+\", [\"get\", \"scalerank\"]]}`.\n\nFor more advanced use cases, in place of `operator`, you can use a custom reduce expression that references a special `[\"accumulated\"]` value, e.g.:\n`{\"sum\": [[\"+\", [\"accumulated\"], [\"get\", \"sum\"]], [\"get\", \"scalerank\"]]}`" @@ -645,10 +649,16 @@ "doc": "Sorts features in ascending order based on this value. Features with a higher sort key will appear above features with a lower sort key.", "sdk-support": { "basic functionality": { - "js": "1.2.0" + "js": "1.2.0", + "android": "9.1.0", + "ios": "5.8.0", + "macos": "0.15.0" }, "data-driven styling": { - "js": "1.2.0" + "js": "1.2.0", + "android": "9.1.0", + "ios": "5.8.0", + "macos": "0.15.0" } }, "expression": { @@ -689,10 +699,16 @@ "doc": "Sorts features in ascending order based on this value. Features with a higher sort key will appear above features with a lower sort key.", "sdk-support": { "basic functionality": { - "js": "1.2.0" + "js": "1.2.0", + "android": "9.2.0", + "ios": "5.9.0", + "macos": "0.16.0" }, "data-driven styling": { - "js": "1.2.0" + "js": "1.2.0", + "android": "9.2.0", + "ios": "5.9.0", + "macos": "0.16.0" } }, "expression": { @@ -900,10 +916,16 @@ "doc": "Sorts features in ascending order based on this value. Features with a higher sort key will appear above features with a lower sort key.", "sdk-support": { "basic functionality": { - "js": "1.2.0" + "js": "1.2.0", + "android": "9.1.0", + "ios": "5.8.0", + "macos": "0.15.0" }, "data-driven styling": { - "js": "1.2.0" + "js": "1.2.0", + "android": "9.1.0", + "ios": "5.8.0", + "macos": "0.15.0" } }, "expression": { @@ -1053,17 +1075,17 @@ "type": "enum", "values": { "auto": { - "doc": "If `symbol-sort-key` is set, sort based on that. Otherwise sort symbols by their y-position relative to the viewport." + "doc": "Sorts symbols by `symbol-sort-key` if set. Otherwise, sorts symbols by their y-position relative to the viewport if `icon-allow-overlap` or `text-allow-overlap` is set to `true` or `icon-ignore-placement` or `text-ignore-placement` is `false`." }, "viewport-y": { - "doc": "Symbols will be sorted by their y-position relative to the viewport." + "doc": "Sorts symbols by their y-position relative to the viewport if `icon-allow-overlap` or `text-allow-overlap` is set to `true` or `icon-ignore-placement` or `text-ignore-placement` is `false`." }, "source": { - "doc": "Symbols will be rendered in the same order as the source data with no sorting applied." + "doc": "Sorts symbols by `symbol-sort-key` if set. Otherwise, no sorting is applied; symbols are rendered in the same order as the source data." } }, "default": "auto", - "doc": "Controls the order in which overlapping symbols in the same layer are rendered", + "doc": "Determines whether overlapping symbols in the same layer are rendered in the order that they appear in the data source or by their y-position relative to the viewport. To control the order and prioritization of symbols otherwise, use `symbol-sort-key`.", "sdk-support": { "basic functionality": { "js": "0.49.0", @@ -1250,6 +1272,12 @@ "android": "4.2.0", "ios": "3.4.0", "macos": "0.2.1" + }, + "stretchable icons": { + "js": "1.6.0", + "android": "9.2.0", + "ios": "5.8.0", + "macos": "0.15.0" } }, "expression": { @@ -2059,7 +2087,7 @@ "js": "1.3.0", "android": "8.3.0", "ios": "5.3.0", - "macos": "0.14.0" + "macos": "0.15.0" } }, "expression": { @@ -2426,6 +2454,9 @@ }, "!has": { "doc": "`[\"!has\", key]` `feature[key]` does not exist" + }, + "within": { + "doc": "`[\"within\", object]` feature geometry is within object geometry" } }, "doc": "The filter operator." @@ -2593,12 +2624,24 @@ "group": "Lookup", "sdk-support": { "basic functionality": { - "js": "1.6.0" + "js": "1.6.0", + "android": "9.1.0", + "ios": "5.8.0", + "macos": "0.15.0" + } + } + }, + "index-of": { + "doc": "Returns the first position at which an item can be found in an array or a substring can be found in a string, or `-1` if the input cannot be found. Accepts an optional index from where to begin the search.", + "group": "Lookup", + "sdk-support": { + "basic functionality": { + "js": "1.10.0" } } }, - "index-of": { - "doc": "Returns the first index at which a given element can be found in an array, or for a string, the first occurrence of the specified value. If a second argument is provided, then the search is started from that position. Returns -1 if the value is not found.", + "slice": { + "doc": "Returns an item from an array or a substring from a string from a specified start index, or between a start index and an end index if set. The return value is inclusive of the start index but not of the end index.", "group": "Lookup", "sdk-support": { "basic functionality": { @@ -2619,7 +2662,7 @@ } }, "match": { - "doc": "Selects the output whose label value matches the input value, or the fallback value if no match is found. The input can be any expression (e.g. `[\"get\", \"building_type\"]`). Each label must be either:\n * a single literal value; or\n * an array of literal values, whose values must be all strings or all numbers (e.g. `[100, 101]` or `[\"c\", \"b\"]`). The input matches if any of the values in the array matches, similar to the `\"in\"` operator.\n\nEach label must be unique. If the input type does not match the type of the labels, the result will be the fallback value.", + "doc": "Selects the output whose label value matches the input value, or the fallback value if no match is found. The input can be any expression (e.g. `[\"get\", \"building_type\"]`). Each label must be either:\n - a single literal value; or\n - an array of literal values, whose values must be all strings or all numbers (e.g. `[100, 101]` or `[\"c\", \"b\"]`). The input matches if any of the values in the array matches, similar to the `\"in\"` operator.\nEach label must be unique. If the input type does not match the type of the labels, the result will be the fallback value.", "group": "Decision", "sdk-support": { "basic functionality": { @@ -2655,7 +2698,7 @@ } }, "interpolate": { - "doc": "Produces continuous, smooth results by interpolating between pairs of input and output values (\"stops\"). The `input` may be any numeric expression (e.g., `[\"get\", \"population\"]`). Stop inputs must be numeric literals in strictly ascending order. The output type must be `number`, `array`, or `color`.\n\nInterpolation types:\n- `[\"linear\"]`: interpolates linearly between the pair of stops just less than and just greater than the input.\n- `[\"exponential\", base]`: interpolates exponentially between the stops just less than and just greater than the input. `base` controls the rate at which the output increases: higher values make the output increase more towards the high end of the range. With values close to 1 the output increases linearly.\n- `[\"cubic-bezier\", x1, y1, x2, y2]`: interpolates using the cubic bezier curve defined by the given control points.", + "doc": "Produces continuous, smooth results by interpolating between pairs of input and output values (\"stops\"). The `input` may be any numeric expression (e.g., `[\"get\", \"population\"]`). Stop inputs must be numeric literals in strictly ascending order. The output type must be `number`, `array`, or `color`.\n\nInterpolation types:\n- `[\"linear\"]`: Interpolates linearly between the pair of stops just less than and just greater than the input.\n- `[\"exponential\", base]`: Interpolates exponentially between the stops just less than and just greater than the input. `base` controls the rate at which the output increases: higher values make the output increase more towards the high end of the range. With values close to 1 the output increases linearly.\n- `[\"cubic-bezier\", x1, y1, x2, y2]`: Interpolates using the cubic bezier curve defined by the given control points.", "group": "Ramps, scales, curves", "sdk-support": { "basic functionality": { @@ -2793,7 +2836,7 @@ } }, "format": { - "doc": "Returns `formatted` text containing annotations for use in mixed-format `text-field` entries. For a `text-field` entries of a string type, following option object's properties are supported: If set, the `text-font` value overrides the font specified by the root layout properties. If set, the `font-scale` value specifies a scaling factor relative to the `text-size` specified in the root layout properties. If set, the `text-color` value overrides the color specified by the root paint properties for this layer.", + "doc": "Returns a `formatted` string for displaying mixed-format text in the `text-field` property. The input may contain a string literal or expression, including an [`'image'`](#types-image) expression. Strings may be followed by a style override object that supports the following properties:\n- `\"text-font\"`: Overrides the font stack specified by the root layout property.\n- `\"text-color\"`: Overrides the color specified by the root paint property.\n- `\"font-scale\"`: Applies a scaling factor on `text-size` as specified by the root layout property.", "group": "Types", "sdk-support": { "basic functionality": { @@ -2821,7 +2864,10 @@ "macos": "0.14.0" }, "image": { - "js": "1.6.0" + "js": "1.6.0", + "android": "8.6.0", + "ios": "5.7.0", + "macos": "0.15.0" } } }, @@ -2832,7 +2878,8 @@ "basic functionality": { "js": "1.4.0", "android": "8.6.0", - "ios": "5.6.0" + "ios": "5.7.0", + "macos": "0.15.0" } } }, @@ -2987,7 +3034,7 @@ } }, "geometry-type": { - "doc": "Gets the feature's geometry type: Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon.", + "doc": "Gets the feature's geometry type: `Point`, `MultiPoint`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`.", "group": "Feature data", "sdk-support": { "basic functionality": { @@ -3319,6 +3366,17 @@ } } }, + "distance": { + "doc": "Returns the shortest distance in meters between the evaluated feature and the input geometry. The input value can be a valid GeoJSON of type `Point`, `MultiPoint`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`, `Feature`, or `FeatureCollection`. Distance values returned may vary in precision due to loss in precision from encoding geometries, particularly below zoom level 13.", + "group": "Math", + "sdk-support": { + "basic functionality": { + "android": "9.2.0", + "ios": "5.9.0", + "macos": "0.16.0" + } + } + }, "==": { "doc": "Returns `true` if the input values are equal, `false` otherwise. The comparison is strictly typed: values of different runtime types are always considered unequal. Cases where the types are known to be different at parse time are considered invalid and will produce a parse error. Accepts an optional `collator` argument to control locale-dependent string comparisons.", "group": "Decision", @@ -3464,11 +3522,14 @@ } }, "within": { - "doc": "Returns `true` if the feature being evaluated is inside the pre-defined geometry boundary, `false` otherwise. The expression has one argument which must be a valid GeoJSON Polygon/Multi-Polygon object. The expression only evaluates on `Point` or `LineString` feature. For `Point` feature, The expression will return false if any point of the feature is on the boundary or outside the boundary. For `LineString` feature, the expression will return false if the line is fully outside the boundary, or the line is partially intersecting the boundary, which means either part of the line is outside of the boundary, or end point of the line lies on the boundary.", + "doc": "Returns `true` if the evaluated feature is fully contained inside a boundary of the input geometry, `false` otherwise. The input value can be a valid GeoJSON of type `Polygon`, `MultiPolygon`, `Feature`, or `FeatureCollection`. Supported features for evaluation:\n- `Point`: Returns `false` if a point is on the boundary or falls outside the boundary.\n- `LineString`: Returns `false` if any part of a line falls outside the boundary, the line intersects the boundary, or a line's endpoint is on the boundary.", "group": "Decision", "sdk-support": { "basic functionality": { - "js": "1.9.0" + "js": "1.9.0", + "android": "9.1.0", + "ios": "5.8.0", + "macos": "0.15.0" } } }, @@ -3529,15 +3590,6 @@ "macos": "0.9.0" } } - }, - "slice": { - "doc": "Returns a portion of a string or an array starting from the provided beginning index. If a second argument is provided, then the return portion will run to, but not include, the end index.", - "group": "String", - "sdk-support": { - "basic functionality": { - "js": "1.10.0" - } - } } } }, diff --git a/src/style-spec/rollup.config.js b/src/style-spec/rollup.config.js index 21105dec504..f0829ad896f 100644 --- a/src/style-spec/rollup.config.js +++ b/src/style-spec/rollup.config.js @@ -1,3 +1,4 @@ +import path from 'path'; import replace from 'rollup-plugin-replace'; import buble from 'rollup-plugin-buble'; import resolve from 'rollup-plugin-node-resolve'; @@ -14,6 +15,8 @@ const transforms = { modules: esm ? false : undefined }; +const ROOT_DIR = __dirname; + const config = [{ input: `${__dirname}/style-spec.js`, output: { @@ -23,6 +26,23 @@ const config = [{ sourcemap: true }, plugins: [ + { + name: 'dep-checker', + resolveId(source, importer) { + // Some users reference modules within style-spec package directly, instead of the bundle + // This means that files within the style-spec package should NOT import files from the parent mapbox-gl-js tree. + // This check will cause the build to fail on CI allowing these issues to be caught. + if (importer && !importer.includes('node_modules')) { + const resolvedPath = path.join(importer, source); + const fromRoot = path.relative(ROOT_DIR, resolvedPath); + if (fromRoot.length > 2 && fromRoot.slice(0, 2) === '..') { + throw new Error(`Module ${importer} imports ${source} from outside the style-spec package root directory.`); + } + } + + return null; + } + }, // https://github.com/zaach/jison/issues/351 replace({ include: /\/jsonlint-lines-primitives\/lib\/jsonlint.js/, diff --git a/src/style-spec/style-spec.js b/src/style-spec/style-spec.js index 66e039f9fc7..7bf17892353 100644 --- a/src/style-spec/style-spec.js +++ b/src/style-spec/style-spec.js @@ -69,7 +69,8 @@ import diff from './diff'; import ValidationError from './error/validation_error'; import ParsingError from './error/parsing_error'; import {StyleExpression, isExpression, createExpression, createPropertyExpression, normalizePropertyExpression, ZoomConstantExpression, ZoomDependentExpression, StylePropertyFunction} from './expression'; -import featureFilter from './feature_filter'; +import featureFilter, {isExpressionFilter} from './feature_filter'; + import convertFilter from './feature_filter/convert'; import Color from './util/color'; import {createFunction, isFunction} from './function'; @@ -82,6 +83,7 @@ import validateMapboxApiSupported from './validate_mapbox_api_supported'; const expression = { StyleExpression, isExpression, + isExpressionFilter, createExpression, createPropertyExpression, normalizePropertyExpression, diff --git a/src/style-spec/validate/validate_filter.js b/src/style-spec/validate/validate_filter.js index 470ce40731a..804b7baac38 100644 --- a/src/style-spec/validate/validate_filter.js +++ b/src/style-spec/validate/validate_filter.js @@ -104,8 +104,14 @@ function validateNonExpressionFilter(options) { errors.push(new ValidationError(`${key}[1]`, value[1], `string expected, ${type} found`)); } break; - + case 'within': + type = getType(value[1]); + if (value.length !== 2) { + errors.push(new ValidationError(key, value, `filter array for "${value[0]}" operator must have 2 elements`)); + } else if (type !== 'object') { + errors.push(new ValidationError(`${key}[1]`, value[1], `object expected, ${type} found`)); + } + break; } - return errors; } diff --git a/src/style-spec/expression/definitions/format_section_override.js b/src/style/format_section_override.js similarity index 71% rename from src/style-spec/expression/definitions/format_section_override.js rename to src/style/format_section_override.js index 86952258263..fd69be19ee1 100644 --- a/src/style-spec/expression/definitions/format_section_override.js +++ b/src/style/format_section_override.js @@ -1,14 +1,16 @@ // @flow import assert from 'assert'; -import type {Expression} from '../expression'; -import type EvaluationContext from '../evaluation_context'; -import type {Type} from '../types'; -import type {ZoomConstantExpression} from '../../expression'; -import {NullType} from '../types'; -import {PossiblyEvaluatedPropertyValue} from '../../../style/properties'; -import {register} from '../../../util/web_worker_transfer'; - +import type {Expression} from '../style-spec/expression/expression'; +import type EvaluationContext from '../style-spec/expression/evaluation_context'; +import type {Type} from '../style-spec/expression/types'; +import type {ZoomConstantExpression} from '../style-spec/expression'; +import {NullType} from '../style-spec/expression/types'; +import {PossiblyEvaluatedPropertyValue} from './properties'; +import {register} from '../util/web_worker_transfer'; + +// This is an internal expression class. It is only used in GL JS and +// has GL JS dependencies which can break the standalone style-spec module export default class FormatSectionOverride implements Expression { type: Type; defaultValue: PossiblyEvaluatedPropertyValue; diff --git a/src/style/properties.js b/src/style/properties.js index 1f6f971ec0f..57829f85bdf 100644 --- a/src/style/properties.js +++ b/src/style/properties.js @@ -317,10 +317,10 @@ export class Transitioning { this._values = (Object.create(properties.defaultTransitioningPropertyValues): any); } - possiblyEvaluate(parameters: EvaluationParameters, availableImages?: Array): PossiblyEvaluated { + possiblyEvaluate(parameters: EvaluationParameters, canonical?: CanonicalTileID, availableImages?: Array): PossiblyEvaluated { const result = new PossiblyEvaluated(this._properties); // eslint-disable-line no-use-before-define for (const property of Object.keys(this._values)) { - result._values[property] = this._values[property].possiblyEvaluate(parameters, availableImages); + result._values[property] = this._values[property].possiblyEvaluate(parameters, canonical, availableImages); } return result; } @@ -385,10 +385,10 @@ export class Layout { return result; } - possiblyEvaluate(parameters: EvaluationParameters, availableImages?: Array): PossiblyEvaluated { + possiblyEvaluate(parameters: EvaluationParameters, canonical?: CanonicalTileID, availableImages?: Array): PossiblyEvaluated { const result = new PossiblyEvaluated(this._properties); // eslint-disable-line no-use-before-define for (const property of Object.keys(this._values)) { - result._values[property] = this._values[property].possiblyEvaluate(parameters, availableImages); + result._values[property] = this._values[property].possiblyEvaluate(parameters, canonical, availableImages); } return result; } diff --git a/src/style/style_image.js b/src/style/style_image.js index 6905a196998..6c327fa6f42 100644 --- a/src/style/style_image.js +++ b/src/style/style_image.js @@ -25,7 +25,7 @@ export type StyleImageInterface = { width: number, height: number, data: Uint8Array | Uint8ClampedArray, - render?: () => void, + render?: () => boolean, onAdd?: (map: Map, id: string) => void, onRemove?: () => void }; diff --git a/src/style/style_layer.js b/src/style/style_layer.js index 82c3f68df4d..c2a7d6cb0cb 100644 --- a/src/style/style_layer.js +++ b/src/style/style_layer.js @@ -10,7 +10,7 @@ import { emitValidationErrors } from './validate_style'; import {Evented} from '../util/evented'; -import {Layout, Transitionable, Transitioning, Properties, PossiblyEvaluatedPropertyValue} from './properties'; +import {Layout, Transitionable, Transitioning, Properties, PossiblyEvaluated, PossiblyEvaluatedPropertyValue} from './properties'; import {supportsPropertyExpression} from '../style-spec/util/properties'; import type {FeatureState} from '../style-spec/expression'; @@ -100,6 +100,8 @@ class StyleLayer extends Evented { } this._transitioningPaint = this._transitionablePaint.untransitioned(); + //$FlowFixMe + this.paint = new PossiblyEvaluated(properties.paint); } } @@ -199,10 +201,10 @@ class StyleLayer extends Evented { } if (this._unevaluatedLayout) { - (this: any).layout = this._unevaluatedLayout.possiblyEvaluate(parameters, availableImages); + (this: any).layout = this._unevaluatedLayout.possiblyEvaluate(parameters, undefined, availableImages); } - (this: any).paint = this._transitioningPaint.possiblyEvaluate(parameters, availableImages); + (this: any).paint = this._transitioningPaint.possiblyEvaluate(parameters, undefined, availableImages); } serialize() { diff --git a/src/style/style_layer/symbol_style_layer.js b/src/style/style_layer/symbol_style_layer.js index 249f0f4b878..37254264509 100644 --- a/src/style/style_layer/symbol_style_layer.js +++ b/src/style/style_layer/symbol_style_layer.js @@ -33,7 +33,7 @@ import type {CanonicalTileID} from '../../source/tile_id'; import {FormattedType} from '../../style-spec/expression/types'; import {typeOf} from '../../style-spec/expression/values'; import Formatted from '../../style-spec/expression/types/formatted'; -import FormatSectionOverride from '../../style-spec/expression/definitions/format_section_override'; +import FormatSectionOverride from '../format_section_override'; import FormatExpression from '../../style-spec/expression/definitions/format'; import Literal from '../../style-spec/expression/definitions/literal'; diff --git a/src/ui/camera.js b/src/ui/camera.js index b5d09d89bc7..a0f89ef56bf 100644 --- a/src/ui/camera.js +++ b/src/ui/camera.js @@ -33,11 +33,28 @@ import type {PaddingOptions} from '../geo/edge_insets'; * @typedef {Object} CameraOptions * @property {LngLatLike} center The desired center. * @property {number} zoom The desired zoom level. - * @property {number} bearing The desired bearing, in degrees. The bearing is the compass direction that - * is "up"; for example, a bearing of 90° orients the map so that east is up. - * @property {number} pitch The desired pitch, in degrees. + * @property {number} bearing The desired bearing in degrees. The bearing is the compass direction that + * is "up". For example, `bearing: 90` orients the map so that east is up. + * @property {number} pitch The desired pitch in degrees. The pitch is the angle towards the horizon + * measured in degrees with a range between 0 and 60 degrees. For example, pitch: 0 provides the appearance + * of looking straight down at the map, while pitch: 60 tilts the user's perspective towards the horizon. + * Increasing the pitch value is often used to display 3D objects. * @property {LngLatLike} around If `zoom` is specified, `around` determines the point around which the zoom is centered. - * @property {PaddingOptions} padding Dimensions in pixels applied on eachs side of the viewport for shifting the vanishing point. + * @property {PaddingOptions} padding Dimensions in pixels applied on each side of the viewport for shifting the vanishing point. + * @example + * // set the map's initial perspective with CameraOptions + * var map = new mapboxgl.Map({ + * container: 'map', + * style: 'mapbox://styles/mapbox/streets-v11', + * center: [-73.5804, 45.53483], + * pitch: 60, + * bearing: -60, + * zoom: 10 + * }); + * @see [Set pitch and bearing](https://docs.mapbox.com/mapbox-gl-js/example/set-perspective/) + * @see [Jump to a series of locations](https://docs.mapbox.com/mapbox-gl-js/example/jump-to/) + * @see [Fly to a location](https://docs.mapbox.com/mapbox-gl-js/example/flyto/) + * @see [Display buildings in 3D](https://docs.mapbox.com/mapbox-gl-js/example/3d-buildings/) */ export type CameraOptions = { center?: LngLatLike, @@ -71,7 +88,7 @@ export type AnimationOptions = { }; /** - * Options for setting padding on a call to {@link Map#fitBounds}. All properties of this object must be + * Options for setting padding on calls to methods such as {@link Map#fitBounds}, {@link Map#fitScreenCoordinates}, and {@link Map#setPadding}. Adjust these options to set the amount of padding in pixels added to the edges of the canvas. Set a uniform padding on all edges or individual values for each edge. All properties of this object must be * non-negative integers. * * @typedef {Object} PaddingOptions @@ -79,6 +96,20 @@ export type AnimationOptions = { * @property {number} bottom Padding in pixels from the bottom of the map canvas. * @property {number} left Padding in pixels from the left of the map canvas. * @property {number} right Padding in pixels from the right of the map canvas. + * + * @example + * var bbox = [[-79, 43], [-73, 45]]; + * map.fitBounds(bbox, { + * padding: {top: 10, bottom:25, left: 15, right: 5} + * }); + * + * @example + * var bbox = [[-79, 43], [-73, 45]]; + * map.fitBounds(bbox, { + * padding: 20 + * }); + * @see [Fit to the bounds of a LineString](https://docs.mapbox.com/mapbox-gl-js/example/zoomto-linestring/) + * @see [Fit a map to a bounding box](https://docs.mapbox.com/mapbox-gl-js/example/fitbounds/) */ class Camera extends Evented { @@ -119,6 +150,12 @@ class Camera extends Evented { * * @memberof Map# * @returns The map's geographical centerpoint. + * @example + * // return a LngLat object such as {lng: 0, lat: 0} + * var center = map.getCenter(); + * // access longitude and latitude values directly + * var {longitude, latitude} = map.getCenter(); + * @see Tutorial: [Use Mapbox GL JS in a React app](https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-react/#store-the-new-coordinates) */ getCenter(): LngLat { return new LngLat(this.transform.center.lng, this.transform.center.lat); } @@ -156,15 +193,21 @@ class Camera extends Evented { } /** - * Pans the map to the specified location, with an animated transition. + * Pans the map to the specified location with an animated transition. * * @memberof Map# * @param lnglat The location to pan the map to. - * @param options Options object + * @param options Options describing the destination and animation of the transition. * @param eventData Additional properties to be added to event objects of events triggered by this method. * @fires movestart * @fires moveend * @returns {Map} `this` + * @example + * map.panTo([-74, 38]); + * @example + * // Specify that the panTo animation should last 5000 milliseconds. + * map.panTo([-74, 38], {duration: 5000}); + * @see [Update a feature in realtime](https://docs.mapbox.com/mapbox-gl-js/example/live-update-feature/) */ panTo(lnglat: LngLatLike, options?: AnimationOptions, eventData?: Object) { return this.easeTo(extend({ @@ -177,6 +220,8 @@ class Camera extends Evented { * * @memberof Map# * @returns The map's current zoom level. + * @example + * map.getZoom(); */ getZoom(): number { return this.transform.zoom; } @@ -194,7 +239,7 @@ class Camera extends Evented { * @fires zoomend * @returns {Map} `this` * @example - * // zoom the map to 5 + * // Zoom to the zoom level 5 without an animated transition * map.setZoom(5); */ setZoom(zoom: number, eventData?: Object) { @@ -216,6 +261,14 @@ class Camera extends Evented { * @fires moveend * @fires zoomend * @returns {Map} `this` + * @example + * // Zoom to the zoom level 5 without an animated transition + * map.zoomTo(5); + * // Zoom to the zoom level 8 with an animated transition + * map.zoomTo(8, { + * duration: 2000, + * offset: [100, 50] + * }); */ zoomTo(zoom: number, options: ? AnimationOptions, eventData?: Object) { return this.easeTo(extend({ @@ -236,6 +289,9 @@ class Camera extends Evented { * @fires moveend * @fires zoomend * @returns {Map} `this` + * @example + * // zoom the map in one level with a custom animation duration + * map.zoomIn({duration: 1000}); */ zoomIn(options?: AnimationOptions, eventData?: Object) { this.zoomTo(this.getZoom() + 1, options, eventData); @@ -255,6 +311,9 @@ class Camera extends Evented { * @fires moveend * @fires zoomend * @returns {Map} `this` + * @example + * // zoom the map out one level with a custom animation offset + * map.zoomOut({offset: [80, 60]}); */ zoomOut(options?: AnimationOptions, eventData?: Object) { this.zoomTo(this.getZoom() - 1, options, eventData); @@ -586,7 +645,7 @@ class Camera extends Evented { * map.fitScreenCoordinates(p0, p1, map.getBearing(), { * padding: {top: 10, bottom:25, left: 15, right: 5} * }); - * @see [Used by BoxZoomHandler](https://www.mapbox.com/mapbox-gl-js/api/#boxzoomhandler) + * @see Used by {@link BoxZoomHandler} */ fitScreenCoordinates(p0: PointLike, p1: PointLike, bearing: number, options?: AnimationOptions & CameraOptions, eventData?: Object) { return this._fitInternal( @@ -631,6 +690,18 @@ class Camera extends Evented { * @fires zoomend * @fires pitchend * @returns {Map} `this` + * @example + * // jump to coordinates at current zoom + * map.jumpTo({center: [0, 0]}); + * // jump with zoom, pitch, and bearing options + * map.jumpTo({ + * center: [0, 0], + * zoom: 8, + * pitch: 45, + * bearing: 90 + * }); + * @see [Jump to a series of locations](https://docs.mapbox.com/mapbox-gl-js/example/jump-to/) + * @see [Update a feature in realtime](https://docs.mapbox.com/mapbox-gl-js/example/live-update-feature/) */ jumpTo(options: CameraOptions, eventData?: Object) { this.stop(); diff --git a/src/ui/control/attribution_control.js b/src/ui/control/attribution_control.js index 628148677fe..a1664f7da2b 100644 --- a/src/ui/control/attribution_control.js +++ b/src/ui/control/attribution_control.js @@ -12,11 +12,11 @@ type Options = { }; /** - * An `AttributionControl` control presents the map's [attribution information](https://www.mapbox.com/help/attribution/). + * An `AttributionControl` control presents the map's [attribution information](https://docs.mapbox.com/help/how-mapbox-works/attribution/). * * @implements {IControl} * @param {Object} [options] - * @param {boolean} [options.compact] If `true` force a compact attribution that shows the full attribution on mouse hover, or if `false` force the full attribution control. The default is a responsive attribution that collapses when the map is less than 640 pixels wide. + * @param {boolean} [options.compact] If `true`, force a compact attribution that shows the full attribution on mouse hover. If `false`, force the full attribution control. The default is a responsive attribution that collapses when the map is less than 640 pixels wide. **Attribution should not be collapsed if it can comfortably fit on the map. `compact` should only be used to modify default attribution when map size makes it impossible to fit [default attribution](https://docs.mapbox.com/help/how-mapbox-works/attribution/) and when the automatic compact resizing for default settings are not sufficient.** * @param {string | Array} [options.customAttribution] String or strings to show in addition to any other attributions. * @example * var map = new mapboxgl.Map({attributionControl: false}) diff --git a/src/ui/control/geolocate_control.js b/src/ui/control/geolocate_control.js index 0e5f5e80736..2cd3866db03 100644 --- a/src/ui/control/geolocate_control.js +++ b/src/ui/control/geolocate_control.js @@ -80,7 +80,7 @@ let noTimeout = false; * @implements {IControl} * @param {Object} [options] * @param {Object} [options.positionOptions={enableHighAccuracy: false, timeout: 6000}] A Geolocation API [PositionOptions](https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions) object. - * @param {Object} [options.fitBoundsOptions={maxZoom: 15}] A [`fitBounds`](#map#fitbounds) options object to use when the map is panned and zoomed to the user's location. The default is to use a `maxZoom` of 15 to limit how far the map will zoom in for very accurate locations. + * @param {Object} [options.fitBoundsOptions={maxZoom: 15}] A {@link Map#fitBounds} options object to use when the map is panned and zoomed to the user's location. The default is to use a `maxZoom` of 15 to limit how far the map will zoom in for very accurate locations. * @param {Object} [options.trackUserLocation=false] If `true` the Geolocate Control becomes a toggle button and when active the map will receive updates to the user's location as it changes. * @param {Object} [options.showAccuracyCircle=true] By default, if showUserLocation is `true`, a transparent circle will be drawn around the user location indicating the accuracy (95% confidence level) of the user's location. Set to `false` to disable. Always disabled when showUserLocation is `false`. * @param {Object} [options.showUserLocation=true] By default a dot will be shown on the map at the user's location. Set to `false` to disable. @@ -406,10 +406,23 @@ class GeolocateControl extends Evented { } /** - * Trigger a geolocation - * - * @returns {boolean} Returns `false` if called before control was added to a map, otherwise returns `true`. - */ + * Programmatically request and move the map to the user's location. + * + * @returns {boolean} Returns `false` if called before control was added to a map, otherwise returns `true`. + * @example + * // Initialize the geolocate control. + * var geolocate = new mapboxgl.GeolocateControl({ + * positionOptions: { + * enableHighAccuracy: true + * }, + * trackUserLocation: true + * }); + * // Add the control to the map. + * map.addControl(geolocate); + * map.on('load', function() { + * geolocate.trigger(); + * }); + */ trigger() { if (!this._setup) { warnOnce('Geolocate control triggered before added to a map'); @@ -552,6 +565,21 @@ export default GeolocateControl; * @memberof GeolocateControl * @instance * @property {Position} data The returned [Position](https://developer.mozilla.org/en-US/docs/Web/API/Position) object from the callback in [Geolocation.getCurrentPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) or [Geolocation.watchPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition). + * @example + * // Initialize the geolocate control. + * var geolocate = new mapboxgl.GeolocateControl({ + * positionOptions: { + * enableHighAccuracy: true + * }, + * trackUserLocation: true + * }); + * // Add the control to the map. + * map.addControl(geolocate); + * // Set an event listener that fires + * // when a geolocate event occurs. + * geolocate.on('geolocate', function() { + * console.log('A geolocate event has occurred.') + * }); * */ @@ -562,6 +590,21 @@ export default GeolocateControl; * @memberof GeolocateControl * @instance * @property {PositionError} data The returned [PositionError](https://developer.mozilla.org/en-US/docs/Web/API/PositionError) object from the callback in [Geolocation.getCurrentPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) or [Geolocation.watchPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition). + * @example + * // Initialize the geolocate control. + * var geolocate = new mapboxgl.GeolocateControl({ + * positionOptions: { + * enableHighAccuracy: true + * }, + * trackUserLocation: true + * }); + * // Add the control to the map. + * map.addControl(geolocate); + * // Set an event listener that fires + * // when an error event occurs. + * geolocate.on('error', function() { + * console.log('An error event has occurred.') + * }); * */ @@ -572,6 +615,21 @@ export default GeolocateControl; * @memberof GeolocateControl * @instance * @property {Position} data The returned [Position](https://developer.mozilla.org/en-US/docs/Web/API/Position) object from the callback in [Geolocation.getCurrentPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) or [Geolocation.watchPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition). + * @example + * // Initialize the geolocate control. + * var geolocate = new mapboxgl.GeolocateControl({ + * positionOptions: { + * enableHighAccuracy: true + * }, + * trackUserLocation: true + * }); + * // Add the control to the map. + * map.addControl(geolocate); + * // Set an event listener that fires + * // when an outofmaxbounds event occurs. + * geolocate.on('outofmaxbounds', function() { + * console.log('An outofmaxbounds event has occurred.') + * }); * */ @@ -581,6 +639,21 @@ export default GeolocateControl; * @event trackuserlocationstart * @memberof GeolocateControl * @instance + * @example + * // Initialize the geolocate control. + * var geolocate = new mapboxgl.GeolocateControl({ + * positionOptions: { + * enableHighAccuracy: true + * }, + * trackUserLocation: true + * }); + * // Add the control to the map. + * map.addControl(geolocate); + * // Set an event listener that fires + * // when a trackuserlocationstart event occurs. + * geolocate.on('trackuserlocationstart', function() { + * console.log('A trackuserlocationstart event has occurred.') + * }); * */ @@ -590,5 +663,20 @@ export default GeolocateControl; * @event trackuserlocationend * @memberof GeolocateControl * @instance + * @example + * // Initialize the geolocate control. + * var geolocate = new mapboxgl.GeolocateControl({ + * positionOptions: { + * enableHighAccuracy: true + * }, + * trackUserLocation: true + * }); + * // Add the control to the map. + * map.addControl(geolocate); + * // Set an event listener that fires + * // when a trackuserlocationend event occurs. + * geolocate.on('trackuserlocationend', function() { + * console.log('A trackuserlocationend event has occurred.') + * }); * */ diff --git a/src/ui/control/navigation_control.js b/src/ui/control/navigation_control.js index 63aece262f7..743eb21f1b6 100644 --- a/src/ui/control/navigation_control.js +++ b/src/ui/control/navigation_control.js @@ -173,10 +173,10 @@ class MouseRotateWrapper { move(e: MouseEvent, point: Point) { const map = this.map; - const r = this.mouseRotate.windowMousemove(e, point); + const r = this.mouseRotate.mousemoveWindow(e, point); if (r && r.bearingDelta) map.setBearing(map.getBearing() + r.bearingDelta); if (this.mousePitch) { - const p = this.mousePitch.windowMousemove(e, point); + const p = this.mousePitch.mousemoveWindow(e, point); if (p && p.pitchDelta) map.setPitch(map.getPitch() + p.pitchDelta); } } @@ -208,8 +208,8 @@ class MouseRotateWrapper { } mouseup(e: MouseEvent) { - this.mouseRotate.windowMouseup(e); - if (this.mousePitch) this.mousePitch.windowMouseup(e); + this.mouseRotate.mouseupWindow(e); + if (this.mousePitch) this.mousePitch.mouseupWindow(e); this.offTemp(); } diff --git a/src/ui/events.js b/src/ui/events.js index 2cbe7bb047c..108ae88d553 100644 --- a/src/ui/events.js +++ b/src/ui/events.js @@ -12,10 +12,27 @@ import type LngLat from '../geo/lng_lat'; /** * `MapMouseEvent` is the event type for mouse-related map events. * @extends {Object} + * @example + * // The `click` event is an example of a `MapMouseEvent`. + * // Set up an event listener on the map. + * map.on('click', function(e) { + * // The event object (e) contains information like the + * // coordinates of the point on the map that was clicked. + * console.log('A click event has occurred at ' + e.lngLat); + * }); */ export class MapMouseEvent extends Event { /** - * The event type. + * The event type (one of {@link Map.event:mousedown}, + * {@link Map.event:mouseup}, + * {@link Map.event:click}, + * {@link Map.event:dblclick}, + * {@link Map.event:mousemove}, + * {@link Map.event:mouseover}, + * {@link Map.event:mouseenter}, + * {@link Map.event:mouseleave}, + * {@link Map.event:mouseout}, + * {@link Map.event:contextmenu}). */ type: 'mousedown' | 'mouseup' @@ -157,7 +174,8 @@ export class MapTouchEvent extends Event { * @private */ constructor(type: string, map: Map, originalEvent: TouchEvent) { - const points = DOM.touchPos(map.getCanvasContainer(), originalEvent.touches); + const touches = type === "touchend" ? originalEvent.changedTouches : originalEvent.touches; + const points = DOM.touchPos(map.getCanvasContainer(), touches); const lngLats = points.map((t) => map.unproject(t)); const point = points.reduce((prev, curr, i, arr) => { return prev.add(curr.div(arr.length)); @@ -217,17 +235,18 @@ export class MapWheelEvent extends Event { } /** - * A `MapBoxZoomEvent` is the event type for boxzoom-related map events. - * `originalEvent` can be a {@link Map.event:click} when the zoom is triggered by a UI event. + * A `MapBoxZoomEvent` is the event type for the boxzoom-related map events emitted by the {@link BoxZoomHandler}. * * @typedef {Object} MapBoxZoomEvent - * @property {MouseEvent} originalEvent + * @property {MouseEvent} originalEvent The DOM event that triggered the boxzoom event. Can be a `MouseEvent` or `KeyboardEvent` + * @property {string} type The type of boxzoom event. One of `boxzoomstart`, `boxzoomend` or `boxzoomcancel` + * @property {Map} target The `Map` instance that triggerred the event */ export type MapBoxZoomEvent = { type: 'boxzoomstart' | 'boxzoomend' | 'boxzoomcancel', - map: Map, + target: Map, originalEvent: MouseEvent }; @@ -250,6 +269,14 @@ export type MapBoxZoomEvent = { * the event is related to loading of a tile. * @property {Coordinate} [coord] The coordinate of the tile if the event has a `dataType` of `source` and * the event is related to loading of a tile. + * @example + * // The sourcedata event is an example of MapDataEvent. + * // Set up an event listener on the map. + * map.on('sourcedata', function(e) { + * if (e.isSourceLoaded) { + * // Do something when the source has finished loading + * } + * }); */ export type MapDataEvent = { type: string, @@ -265,34 +292,90 @@ export type MapEvent = /** * Fired when a pointing device (usually a mouse) is pressed within the map. * + * **Note:** This event is compatible with the optional `layerId` parameter. + * If `layerId` is included as the second argument in {@link Map#on}, the event listener will fire only when the + * the cursor is pressed while inside a visible portion of the specifed layer. + * * @event mousedown * @memberof Map * @instance * @property {MapMouseEvent} data - * @see [Highlight features within a bounding box](https://www.mapbox.com/mapbox-gl-js/example/using-box-queryrenderedfeatures/) - * @see [Create a draggable point](https://www.mapbox.com/mapbox-gl-js/example/drag-a-point/) + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener + * map.on('mousedown', function() { + * console.log('A mousedown event has occurred.'); + * }); + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener for a specific layer + * map.on('mousedown', 'poi-label', function() { + * console.log('A mousedown event has occurred on a visible portion of the poi-label layer.'); + * }); + * @see [Highlight features within a bounding box](https://docs.mapbox.com/mapbox-gl-js/example/using-box-queryrenderedfeatures/) + * @see [Create a draggable point](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-point/) */ | 'mousedown' /** * Fired when a pointing device (usually a mouse) is released within the map. * + * **Note:** This event is compatible with the optional `layerId` parameter. + * If `layerId` is included as the second argument in {@link Map#on}, the event listener will fire only when the + * the cursor is released while inside a visible portion of the specifed layer. + * * @event mouseup * @memberof Map * @instance * @property {MapMouseEvent} data - * @see [Highlight features within a bounding box](https://www.mapbox.com/mapbox-gl-js/example/using-box-queryrenderedfeatures/) - * @see [Create a draggable point](https://www.mapbox.com/mapbox-gl-js/example/drag-a-point/) + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener + * map.on('mouseup', function() { + * console.log('A mouseup event has occurred.'); + * }); + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener for a specific layer + * map.on('mouseup', 'poi-label', function() { + * console.log('A mouseup event has occurred on a visible portion of the poi-label layer.'); + * }); + * @see [Highlight features within a bounding box](https://docs.mapbox.com/mapbox-gl-js/example/using-box-queryrenderedfeatures/) + * @see [Create a draggable point](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-point/) */ | 'mouseup' /** * Fired when a pointing device (usually a mouse) is moved within the map. + * As you move the cursor across a web page containing a map, + * the event will fire each time it enters the map or any child elements. + * + * **Note:** This event is compatible with the optional `layerId` parameter. + * If `layerId` is included as the second argument in {@link Map#on}, the event listener will fire only when the + * the cursor is moved inside a visible portion of the specifed layer. * * @event mouseover * @memberof Map * @instance * @property {MapMouseEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener + * map.on('mouseover', function() { + * console.log('A mouseover event has occurred.'); + * }); + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener for a specific layer + * map.on('mouseover', 'poi-label', function() { + * console.log('A mouseover event has occurred on a visible portion of the poi-label layer.'); + * }); * @see [Get coordinates of the mouse pointer](https://www.mapbox.com/mapbox-gl-js/example/mouse-position/) * @see [Highlight features under the mouse pointer](https://www.mapbox.com/mapbox-gl-js/example/hover-styles/) * @see [Display a popup on hover](https://www.mapbox.com/mapbox-gl-js/example/popup-on-hover/) @@ -300,12 +383,31 @@ export type MapEvent = | 'mouseover' /** - * Fired when a pointing device (usually a mouse) is moved within the map. + * Fired when a pointing device (usually a mouse) is moved while the cursor is inside the map. + * As you move the cursor across the map, the event will fire every time the cursor changes position within the map. + * + * **Note:** This event is compatible with the optional `layerId` parameter. + * If `layerId` is included as the second argument in {@link Map#on}, the event listener will fire only when the + * the cursor is inside a visible portion of the specified layer. * * @event mousemove * @memberof Map * @instance * @property {MapMouseEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener + * map.on('mousemove', function() { + * console.log('A mousemove event has occurred.'); + * }); + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener for a specific layer + * map.on('mousemove', 'poi-label', function() { + * console.log('A mousemove event has occurred on a visible portion of the poi-label layer.'); + * }); * @see [Get coordinates of the mouse pointer](https://www.mapbox.com/mapbox-gl-js/example/mouse-position/) * @see [Highlight features under the mouse pointer](https://www.mapbox.com/mapbox-gl-js/example/hover-styles/) * @see [Display a popup on over](https://www.mapbox.com/mapbox-gl-js/example/popup-on-hover/) @@ -315,47 +417,107 @@ export type MapEvent = /** * Fired when a pointing device (usually a mouse) is pressed and released at the same point on the map. * + * **Note:** This event is compatible with the optional `layerId` parameter. + * If `layerId` is included as the second argument in {@link Map#on}, the event listener will fire only when the + * point that is pressed and released contains a visible portion of the specifed layer. + * * @event click * @memberof Map * @instance * @property {MapMouseEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener + * map.on('click', function(e) { + * console.log('A click event has occurred at ' + e.lngLat); + * }); + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener for a specific layer + * map.on('click', 'poi-label', function(e) { + * console.log('A click event has occurred on a visible portion of the poi-label layer at ' + e.lngLat); + * }); * @see [Measure distances](https://www.mapbox.com/mapbox-gl-js/example/measure/) * @see [Center the map on a clicked symbol](https://www.mapbox.com/mapbox-gl-js/example/center-on-symbol/) */ | 'click' /** - * Fired when a pointing device (usually a mouse) is clicked twice at the same point on the map. + * Fired when a pointing device (usually a mouse) is pressed and released twice at the same point on + * the map in rapid succession. + * + * **Note:** This event is compatible with the optional `layerId` parameter. + * If `layerId` is included as the second argument in {@link Map#on}, the event listener will fire only + * when the point that is clicked twice contains a visible portion of the specifed layer. * * @event dblclick * @memberof Map * @instance * @property {MapMouseEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener + * map.on('dblclick', function(e) { + * console.log('A dblclick event has occurred at ' + e.lngLat); + * }); + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener for a specific layer + * map.on('dblclick', 'poi-label', function(e) { + * console.log('A dblclick event has occurred on a visible portion of the poi-label layer at ' + e.lngLat); + * }); */ | 'dblclick' /** * Fired when a pointing device (usually a mouse) enters a visible portion of a specified layer from - * outside that layer or outside the map canvas. This event can only be listened for via the three-argument - * version of {@link Map#on}, where the second argument specifies the desired layer. + * outside that layer or outside the map canvas. + * + * **Important:** This event can only be listened for when {@link Map#on} includes three arguements, + * where the second argument specifies the desired layer. * * @event mouseenter * @memberof Map * @instance * @property {MapMouseEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener + * map.on('mouseenter', 'water', function() { + * console.log('A mouseenter event occurred on a visible portion of the water layer.'); + * }); + * @see [Center the map on a clicked symbol](https://docs.mapbox.com/mapbox-gl-js/example/center-on-symbol/) + * @see [Display a popup on click](https://docs.mapbox.com/mapbox-gl-js/example/popup-on-click/) */ | 'mouseenter' /** * Fired when a pointing device (usually a mouse) leaves a visible portion of a specified layer, or leaves - * the map canvas. This event can only be listened for via the three-argument version of {@link Map#on}, + * the map canvas. + * + * **Important:** This event can only be listened for when {@link Map#on} includes three arguements, * where the second argument specifies the desired layer. * * @event mouseleave * @memberof Map * @instance * @property {MapMouseEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when the pointing device leaves + * // a visible portion of the specified layer. + * map.on('mouseleave', 'water', function() { + * console.log('A mouseleave event occurred.'); + * }); * @see [Highlight features under the mouse pointer](https://www.mapbox.com/mapbox-gl-js/example/hover-styles/) + * @see [Display a popup on click](https://docs.mapbox.com/mapbox-gl-js/example/popup-on-click/) */ | 'mouseleave' @@ -366,6 +528,15 @@ export type MapEvent = * @memberof Map * @instance * @property {MapMouseEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when the pointing device leave's + * // the map's canvas. + * map.on('mouseout', function() { + * console.log('A mouseout event occurred.'); + * }); */ | 'mouseout' @@ -376,6 +547,15 @@ export type MapEvent = * @memberof Map * @instance * @property {MapMouseEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when the right mouse button is + * // pressed within the map. + * map.on('contextmenu', function() { + * console.log('A contextmenu event occurred.'); + * }); */ | 'contextmenu' @@ -386,6 +566,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapWheelEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when a wheel event occurs within the map. + * map.on('wheel', function() { + * console.log('A wheel event occurred.'); + * }); */ | 'wheel' @@ -396,6 +584,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapTouchEvent} data + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when a touchstart event occurs within the map. + * map.on('touchstart', function() { + * console.log('A touchstart event occurred.'); + * }); + * @see [Create a draggable point](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-point/) */ | 'touchstart' @@ -406,6 +602,15 @@ export type MapEvent = * @memberof Map * @instance * @property {MapTouchEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when a touchstart event occurs within the map. + * map.on('touchstart', function() { + * console.log('A touchstart event occurred.'); + * }); + * @see [Create a draggable point](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-point/) */ | 'touchend' @@ -416,6 +621,15 @@ export type MapEvent = * @memberof Map * @instance * @property {MapTouchEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when a touchmove event occurs within the map. + * map.on('touchmove', function() { + * console.log('A touchmove event occurred.'); + * }); + * @see [Create a draggable point](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-point/) */ | 'touchmove' @@ -426,6 +640,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapTouchEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when a touchcancel event occurs within the map. + * map.on('touchcancel', function() { + * console.log('A touchcancel event occurred.'); + * }); */ | 'touchcancel' @@ -437,6 +659,15 @@ export type MapEvent = * @memberof Map * @instance * @property {{originalEvent: DragEvent}} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // just before the map begins a transition + * // from one view to another. + * map.on('movestart', function() { + * console.log('A movestart` event occurred.'); + * }); */ | 'movestart' @@ -448,6 +679,16 @@ export type MapEvent = * @memberof Map * @instance * @property {MapMouseEvent | MapTouchEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // repeatedly during an animated transition. + * map.on('move', function() { + * console.log('A move event occurred.'); + * }); + * @see [Display HTML clusters with custom properties](https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/) + * @see [Filter features within map view](https://docs.mapbox.com/mapbox-gl-js/example/filter-features-within-map-view/) */ | 'move' @@ -459,8 +700,17 @@ export type MapEvent = * @memberof Map * @instance * @property {{originalEvent: DragEvent}} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // just after the map completes a transition. + * map.on('moveend', function() { + * console.log('A moveend event occurred.'); + * }); * @see [Play map locations as a slideshow](https://www.mapbox.com/mapbox-gl-js/example/playback-locations/) * @see [Filter features within map view](https://www.mapbox.com/mapbox-gl-js/example/filter-features-within-map-view/) + * @see [Display HTML clusters with custom properties](https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/) */ | 'moveend' @@ -471,6 +721,14 @@ export type MapEvent = * @memberof Map * @instance * @property {{originalEvent: DragEvent}} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when a "drag to pan" interaction starts. + * map.on('dragstart', function() { + * console.log('A dragstart event occurred.'); + * }); */ | 'dragstart' @@ -481,6 +739,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapMouseEvent | MapTouchEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // repeatedly during a "drag to pan" interaction. + * map.on('drag', function() { + * console.log('A drag event occurred.'); + * }); */ | 'drag' @@ -491,6 +757,15 @@ export type MapEvent = * @memberof Map * @instance * @property {{originalEvent: DragEvent}} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when a "drag to pan" interaction ends. + * map.on('dragend', function() { + * console.log('A dragend event occurred.'); + * }); + * @see [Create a draggable marker](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-marker/) */ | 'dragend' @@ -502,6 +777,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapMouseEvent | MapTouchEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // just before a zoom transition starts. + * map.on('zoomstart', function() { + * console.log('A zoomstart event occurred.'); + * }); */ | 'zoomstart' @@ -513,6 +796,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapMouseEvent | MapTouchEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // repeatedly during a zoom transition. + * map.on('zoom', function() { + * console.log('A zoom event occurred.'); + * }); * @see [Update a choropleth layer by zoom level](https://www.mapbox.com/mapbox-gl-js/example/updating-choropleth/) */ | 'zoom' @@ -525,6 +816,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapMouseEvent | MapTouchEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // just after a zoom transition finishes. + * map.on('zoomend', function() { + * console.log('A zoomend event occurred.'); + * }); */ | 'zoomend' @@ -535,6 +834,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapMouseEvent | MapTouchEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // just before a "drag to rotate" interaction starts. + * map.on('rotatestart', function() { + * console.log('A rotatestart event occurred.'); + * }); */ | 'rotatestart' @@ -545,6 +852,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapMouseEvent | MapTouchEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // repeatedly during "drag to rotate" interaction. + * map.on('rotate', function() { + * console.log('A rotate event occurred.'); + * }); */ | 'rotate' @@ -555,6 +870,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapMouseEvent | MapTouchEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // just after a "drag to rotate" interaction ends. + * map.on('rotateend', function() { + * console.log('A rotateend event occurred.'); + * }); */ | 'rotateend' @@ -566,17 +889,34 @@ export type MapEvent = * @memberof Map * @instance * @property {MapEventData} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // just before a pitch (tilt) transition starts. + * map.on('pitchstart', function() { + * console.log('A pitchstart event occurred.'); + * }); */ | 'pitchstart' /** - * Fired whenever the map's pitch (tilt) changes as - * the result of either user interaction or methods such as {@link Map#flyTo}. + * Fired repeatedly during the map's pitch (tilt) animation between + * one state and another as the result of either user interaction + * or methods such as {@link Map#flyTo}. * * @event pitch * @memberof Map * @instance * @property {MapEventData} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // repeatedly during a pitch (tilt) transition. + * map.on('pitch', function() { + * console.log('A pitch event occurred.'); + * }); */ | 'pitch' @@ -588,6 +928,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapEventData} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // just after a pitch (tilt) transition ends. + * map.on('pitchend', function() { + * console.log('A pitchend event occurred.'); + * }); */ | 'pitchend' @@ -598,6 +946,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapBoxZoomEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // just before a "box zoom" interaction starts. + * map.on('boxzoomstart', function() { + * console.log('A boxzoomstart event occurred.'); + * }); */ | 'boxzoomstart' @@ -609,6 +965,14 @@ export type MapEvent = * @instance * @type {Object} * @property {MapBoxZoomEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // just after a "box zoom" interaction ends. + * map.on('boxzoomend', function() { + * console.log('A boxzoomend event occurred.'); + * }); */ | 'boxzoomend' @@ -620,6 +984,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapBoxZoomEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // the user cancels a "box zoom" interaction. + * map.on('boxzoomcancel', function() { + * console.log('A boxzoomcancel event occurred.'); + * }); */ | 'boxzoomcancel' @@ -629,6 +1001,14 @@ export type MapEvent = * @event resize * @memberof Map * @instance + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // immediately after the map has been resized. + * map.on('resize', function() { + * console.log('A resize event occurred.'); + * }); */ | 'resize' @@ -638,6 +1018,14 @@ export type MapEvent = * @event webglcontextlost * @memberof Map * @instance + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when the WebGL context is lost. + * map.on('webglcontextlost', function() { + * console.log('A webglcontextlost event occurred.'); + * }); */ | 'webglcontextlost' @@ -647,6 +1035,14 @@ export type MapEvent = * @event webglcontextrestored * @memberof Map * @instance + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when the WebGL context is restored. + * map.on('webglcontextrestored', function() { + * console.log('A webglcontextrestored event occurred.'); + * }); */ | 'webglcontextrestored' @@ -658,6 +1054,14 @@ export type MapEvent = * @memberof Map * @instance * @type {Object} + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when the map has finished loading. + * map.on('load', function() { + * console.log('A load event occurred.'); + * }); * @see [Draw GeoJSON points](https://www.mapbox.com/mapbox-gl-js/example/geojson-markers/) * @see [Add live realtime data](https://www.mapbox.com/mapbox-gl-js/example/live-geojson/) * @see [Animate a point](https://www.mapbox.com/mapbox-gl-js/example/animate-point-along-line/) @@ -675,6 +1079,14 @@ export type MapEvent = * @event render * @memberof Map * @instance + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // whenever the map is drawn to the screen. + * map.on('render', function() { + * console.log('A render event occurred.'); + * }); */ | 'render' @@ -689,6 +1101,14 @@ export type MapEvent = * @event idle * @memberof Map * @instance + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // just before the map enters an "idle" state. + * map.on('idle', function() { + * console.log('A idle event occurred.'); + * }); */ | 'idle' @@ -698,6 +1118,14 @@ export type MapEvent = * @event remove * @memberof Map * @instance + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // just after the map is removed. + * map.on('remove', function() { + * console.log('A remove event occurred.'); + * }); */ | 'remove' @@ -711,6 +1139,14 @@ export type MapEvent = * @memberof Map * @instance * @property {{error: {message: string}}} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when an error occurs. + * map.on('error', function() { + * console.log('A error event occurred.'); + * }); */ | 'error' @@ -722,6 +1158,15 @@ export type MapEvent = * @memberof Map * @instance * @property {MapDataEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when map data loads or changes. + * map.on('data', function() { + * console.log('A data event occurred.'); + * }); + * @see [Display HTML clusters with custom properties](https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/) */ | 'data' @@ -733,6 +1178,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapDataEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when the map's style loads or changes. + * map.on('styledata', function() { + * console.log('A styledata event occurred.'); + * }); */ | 'styledata' @@ -744,6 +1197,14 @@ export type MapEvent = * @memberof Map * @instance * @property {MapDataEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when one of the map's sources loads or changes. + * map.on('sourcedata', function() { + * console.log('A sourcedata event occurred.'); + * }); */ | 'sourcedata' @@ -756,6 +1217,15 @@ export type MapEvent = * @memberof Map * @instance * @property {MapDataEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // when any map data begins loading + * // or changing asynchronously. + * map.on('dataloading', function() { + * console.log('A dataloading event occurred.'); + * }); */ | 'dataloading' @@ -768,6 +1238,15 @@ export type MapEvent = * @memberof Map * @instance * @property {MapDataEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // map's style begins loading or + * // changing asyncronously. + * map.on('styledataloading', function() { + * console.log('A styledataloading event occurred.'); + * }); */ | 'styledataloading' @@ -780,6 +1259,15 @@ export type MapEvent = * @memberof Map * @instance * @property {MapDataEvent} data + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // map's sources begin loading or + * // changing asyncronously. + * map.on('sourcedataloading', function() { + * console.log('A sourcedataloading event occurred.'); + * }); */ | 'sourcedataloading' @@ -792,7 +1280,14 @@ export type MapEvent = * @memberof Map * @instance * @property {string} id The id of the missing image. - * + * @example + * // Initialize the map + * var map = new mapboxgl.Map({ // map options }); + * // Set an event listener that fires + * // an icon or pattern is missing. + * map.on('styleimagemissing', function() { + * console.log('A styleimagemissing event occurred.'); + * }); * @see [Generate and add a missing icon to the map](https://mapbox.com/mapbox-gl-js/example/add-image-missing-generated/) */ | 'styleimagemissing' diff --git a/src/ui/handler/box_zoom.js b/src/ui/handler/box_zoom.js index 5665e9a1d6f..be8415ab7e3 100644 --- a/src/ui/handler/box_zoom.js +++ b/src/ui/handler/box_zoom.js @@ -82,7 +82,7 @@ class BoxZoomHandler { this._active = true; } - windowMousemove(e: MouseEvent, point: Point) { + mousemoveWindow(e: MouseEvent, point: Point) { if (!this._active) return; const pos = point; @@ -111,7 +111,7 @@ class BoxZoomHandler { this._box.style.height = `${maxY - minY}px`; } - windowMouseup(e: MouseEvent, point: Point) { + mouseupWindow(e: MouseEvent, point: Point) { if (!this._active) return; if (e.button !== 0) return; diff --git a/src/ui/handler/handler_util.js b/src/ui/handler/handler_util.js index e26f8a80d5b..493f93d6264 100644 --- a/src/ui/handler/handler_util.js +++ b/src/ui/handler/handler_util.js @@ -2,7 +2,7 @@ import assert from 'assert'; -export function indexTouches(touches: TouchList, points: Array) { +export function indexTouches(touches: Array, points: Array) { assert(touches.length === points.length); const obj = {}; for (let i = 0; i < touches.length; i++) { diff --git a/src/ui/handler/map_event.js b/src/ui/handler/map_event.js index 891fe414f2b..31b46e5ba23 100644 --- a/src/ui/handler/map_event.js +++ b/src/ui/handler/map_event.js @@ -68,6 +68,10 @@ export class MapEventHandler { return this._firePreventable(new MapTouchEvent(e.type, this._map, e)); } + touchmove(e: TouchEvent) { + this._map.fire(new MapTouchEvent(e.type, this._map, e)); + } + touchend(e: TouchEvent) { this._map.fire(new MapTouchEvent(e.type, this._map, e)); } @@ -114,11 +118,6 @@ export class BlockableMapEventHandler { this._map.fire(new MapMouseEvent(e.type, this._map, e)); } - touchmove(e: TouchEvent) { - // touchmove map events should not be fired when interaction handlers (pan, rotate, etc) are active - this._map.fire(new MapTouchEvent(e.type, this._map, e)); - } - mousedown() { this._delayContextMenu = true; } diff --git a/src/ui/handler/mouse.js b/src/ui/handler/mouse.js index 8c8d42d2ee1..3cf5c532816 100644 --- a/src/ui/handler/mouse.js +++ b/src/ui/handler/mouse.js @@ -6,6 +6,17 @@ import type Point from '@mapbox/point-geometry'; const LEFT_BUTTON = 0; const RIGHT_BUTTON = 2; +// the values for each button in MouseEvent.buttons +const BUTTONS_FLAGS = { + [LEFT_BUTTON]: 1, + [RIGHT_BUTTON]: 2 +}; + +function buttonStillPressed(e: MouseEvent, button: number) { + const flag = BUTTONS_FLAGS[button]; + return e.buttons === undefined || (e.buttons & flag) !== flag; +} + class MouseHandler { _enabled: boolean; @@ -45,11 +56,22 @@ class MouseHandler { this._eventButton = eventButton; } - windowMousemove(e: MouseEvent, point: Point) { + mousemoveWindow(e: MouseEvent, point: Point) { const lastPoint = this._lastPoint; if (!lastPoint) return; e.preventDefault(); + if (buttonStillPressed(e, this._eventButton)) { + // Some browsers don't fire a `mouseup` when the mouseup occurs outside + // the window or iframe: + // https://github.com/mapbox/mapbox-gl-js/issues/4622 + // + // If the button is no longer pressed during this `mousemove` it may have + // been released outside of the window or iframe. + this.reset(); + return; + } + if (!this._moved && point.dist(lastPoint) < this._clickTolerance) return; this._moved = true; this._lastPoint = point; @@ -58,7 +80,8 @@ class MouseHandler { return this._move(lastPoint, point); } - windowMouseup(e: MouseEvent) { + mouseupWindow(e: MouseEvent) { + if (!this._lastPoint) return; const eventButton = DOM.mouseButton(e); if (eventButton !== this._eventButton) return; if (this._moved) DOM.suppressClick(); diff --git a/src/ui/handler/scroll_zoom.js b/src/ui/handler/scroll_zoom.js index a123a1e711e..a8fc61d109f 100644 --- a/src/ui/handler/scroll_zoom.js +++ b/src/ui/handler/scroll_zoom.js @@ -81,15 +81,21 @@ class ScrollZoomHandler { /** * Set the zoom rate of a trackpad * @param {number} [zoomRate=1/100] The rate used to scale trackpad movement to a zoom value. + * @example + * // Speed up trackpad zoom + * map.scrollZoom.setZoomRate(1/25); */ setZoomRate(zoomRate: number) { this._defaultZoomRate = zoomRate; } /** - * Set the zoom rate of a mouse wheel - * @param {number} [wheelZoomRate=1/450] The rate used to scale mouse wheel movement to a zoom value. - */ + * Set the zoom rate of a mouse wheel + * @param {number} [wheelZoomRate=1/450] The rate used to scale mouse wheel movement to a zoom value. + * @example + * // Slow down zoom of mouse wheel + * map.scrollZoom.setWheelZoomRate(1/600); + */ setWheelZoomRate(wheelZoomRate: number) { this._wheelZoomRate = wheelZoomRate; } diff --git a/src/ui/handler/shim/drag_pan.js b/src/ui/handler/shim/drag_pan.js index 97e9281b773..2b55e42539f 100644 --- a/src/ui/handler/shim/drag_pan.js +++ b/src/ui/handler/shim/drag_pan.js @@ -42,7 +42,7 @@ export default class DragPanHandler { * @example * map.dragPan.enable(); * @example - * map.dragpan.enable({ + * map.dragPan.enable({ * linearity: 0.3, * easing: bezier(0, 0, 0.3, 1), * maxSpeed: 1400, diff --git a/src/ui/handler/shim/drag_rotate.js b/src/ui/handler/shim/drag_rotate.js index 5d3878b9f7d..22141fa5116 100644 --- a/src/ui/handler/shim/drag_rotate.js +++ b/src/ui/handler/shim/drag_rotate.js @@ -62,6 +62,6 @@ export default class DragRotateHandler { * @returns {boolean} `true` if the "drag to rotate" interaction is active. */ isActive() { - return this._mouseRotate.isEnabled() || this._mousePitch.isEnabled(); + return this._mouseRotate.isActive() || this._mousePitch.isActive(); } } diff --git a/src/ui/handler/tap_drag_zoom.js b/src/ui/handler/tap_drag_zoom.js index 7f651f6b99e..d8bf435a2c5 100644 --- a/src/ui/handler/tap_drag_zoom.js +++ b/src/ui/handler/tap_drag_zoom.js @@ -30,7 +30,7 @@ export default class TapDragZoomHandler { this._tap.reset(); } - touchstart(e: TouchEvent, points: Array) { + touchstart(e: TouchEvent, points: Array, mapTouches: Array) { if (this._swipePoint) return; if (this._tapTime && e.timeStamp - this._tapTime > MAX_TAP_INTERVAL) { @@ -38,19 +38,19 @@ export default class TapDragZoomHandler { } if (!this._tapTime) { - this._tap.touchstart(e, points); - } else if (e.targetTouches.length > 0) { + this._tap.touchstart(e, points, mapTouches); + } else if (mapTouches.length > 0) { this._swipePoint = points[0]; - this._swipeTouch = e.targetTouches[0].identifier; + this._swipeTouch = mapTouches[0].identifier; } } - touchmove(e: TouchEvent, points: Array) { + touchmove(e: TouchEvent, points: Array, mapTouches: Array) { if (!this._tapTime) { - this._tap.touchmove(e, points); + this._tap.touchmove(e, points, mapTouches); } else if (this._swipePoint) { - if (e.targetTouches[0].identifier !== this._swipeTouch) { + if (mapTouches[0].identifier !== this._swipeTouch) { return; } @@ -62,19 +62,19 @@ export default class TapDragZoomHandler { this._active = true; return { - zoomDelta: dist / -128 + zoomDelta: dist / 128 }; } } - touchend(e: TouchEvent) { + touchend(e: TouchEvent, points: Array, mapTouches: Array) { if (!this._tapTime) { - const point = this._tap.touchend(e); + const point = this._tap.touchend(e, points, mapTouches); if (point) { this._tapTime = e.timeStamp; } } else if (this._swipePoint) { - if (e.targetTouches.length === 0) { + if (mapTouches.length === 0) { this.reset(); } } diff --git a/src/ui/handler/tap_recognizer.js b/src/ui/handler/tap_recognizer.js index 1d9f11121a1..01e9dee629e 100644 --- a/src/ui/handler/tap_recognizer.js +++ b/src/ui/handler/tap_recognizer.js @@ -35,9 +35,9 @@ export class SingleTapRecognizer { this.aborted = false; } - touchstart(e: TouchEvent, points: Array) { + touchstart(e: TouchEvent, points: Array, mapTouches: Array) { - if (this.centroid || e.targetTouches.length > this.numTouches) { + if (this.centroid || mapTouches.length > this.numTouches) { this.aborted = true; } if (this.aborted) { @@ -48,16 +48,16 @@ export class SingleTapRecognizer { this.startTime = e.timeStamp; } - if (e.targetTouches.length === this.numTouches) { + if (mapTouches.length === this.numTouches) { this.centroid = getCentroid(points); - this.touches = indexTouches(e.targetTouches, points); + this.touches = indexTouches(mapTouches, points); } } - touchmove(e: TouchEvent, points: Array) { + touchmove(e: TouchEvent, points: Array, mapTouches: Array) { if (this.aborted || !this.centroid) return; - const newTouches = indexTouches(e.targetTouches, points); + const newTouches = indexTouches(mapTouches, points); for (const id in this.touches) { const prevPos = this.touches[id]; const pos = newTouches[id]; @@ -67,12 +67,12 @@ export class SingleTapRecognizer { } } - touchend(e: TouchEvent) { + touchend(e: TouchEvent, points: Array, mapTouches: Array) { if (!this.centroid || e.timeStamp - this.startTime > MAX_TOUCH_TIME) { this.aborted = true; } - if (e.targetTouches.length === 0) { + if (mapTouches.length === 0) { const centroid = !this.aborted && this.centroid; this.reset(); if (centroid) return centroid; @@ -102,16 +102,16 @@ export class TapRecognizer { this.singleTap.reset(); } - touchstart(e: TouchEvent, points: Array) { - this.singleTap.touchstart(e, points); + touchstart(e: TouchEvent, points: Array, mapTouches: Array) { + this.singleTap.touchstart(e, points, mapTouches); } - touchmove(e: TouchEvent, points: Array) { - this.singleTap.touchmove(e, points); + touchmove(e: TouchEvent, points: Array, mapTouches: Array) { + this.singleTap.touchmove(e, points, mapTouches); } - touchend(e: TouchEvent) { - const tap = this.singleTap.touchend(e); + touchend(e: TouchEvent, points: Array, mapTouches: Array) { + const tap = this.singleTap.touchend(e, points, mapTouches); if (tap) { const soonEnough = e.timeStamp - this.lastTime < MAX_TAP_INTERVAL; const closeEnough = !this.lastTap || this.lastTap.dist(tap) < MAX_DIST; @@ -125,7 +125,6 @@ export class TapRecognizer { this.lastTap = tap; if (this.count === this.numTaps) { - e.preventDefault(); this.reset(); return tap; } diff --git a/src/ui/handler/tap_zoom.js b/src/ui/handler/tap_zoom.js index 060a40af9d8..1aee59d1e88 100644 --- a/src/ui/handler/tap_zoom.js +++ b/src/ui/handler/tap_zoom.js @@ -31,22 +31,23 @@ export default class TapZoomHandler { this._zoomOut.reset(); } - touchstart(e: TouchEvent, points: Array) { - this._zoomIn.touchstart(e, points); - this._zoomOut.touchstart(e, points); + touchstart(e: TouchEvent, points: Array, mapTouches: Array) { + this._zoomIn.touchstart(e, points, mapTouches); + this._zoomOut.touchstart(e, points, mapTouches); } - touchmove(e: TouchEvent, points: Array) { - this._zoomIn.touchmove(e, points); - this._zoomOut.touchmove(e, points); + touchmove(e: TouchEvent, points: Array, mapTouches: Array) { + this._zoomIn.touchmove(e, points, mapTouches); + this._zoomOut.touchmove(e, points, mapTouches); } - touchend(e: TouchEvent) { - const zoomInPoint = this._zoomIn.touchend(e); - const zoomOutPoint = this._zoomOut.touchend(e); + touchend(e: TouchEvent, points: Array, mapTouches: Array) { + const zoomInPoint = this._zoomIn.touchend(e, points, mapTouches); + const zoomOutPoint = this._zoomOut.touchend(e, points, mapTouches); if (zoomInPoint) { this._active = true; + e.preventDefault(); setTimeout(() => this.reset(), 0); return { cameraAnimation: (map: Map) => map.easeTo({ @@ -57,6 +58,7 @@ export default class TapZoomHandler { }; } else if (zoomOutPoint) { this._active = true; + e.preventDefault(); setTimeout(() => this.reset(), 0); return { cameraAnimation: (map: Map) => map.easeTo({ diff --git a/src/ui/handler/touch_pan.js b/src/ui/handler/touch_pan.js index a04a7f7f1d6..be63664b6bd 100644 --- a/src/ui/handler/touch_pan.js +++ b/src/ui/handler/touch_pan.js @@ -24,20 +24,20 @@ export default class TouchPanHandler { this._sum = new Point(0, 0); } - touchstart(e: TouchEvent, points: Array) { - return this._calculateTransform(e, points); + touchstart(e: TouchEvent, points: Array, mapTouches: Array) { + return this._calculateTransform(e, points, mapTouches); } - touchmove(e: TouchEvent, points: Array) { + touchmove(e: TouchEvent, points: Array, mapTouches: Array) { if (!this._active) return; e.preventDefault(); - return this._calculateTransform(e, points); + return this._calculateTransform(e, points, mapTouches); } - touchend(e: TouchEvent, points: Array) { - this._calculateTransform(e, points); + touchend(e: TouchEvent, points: Array, mapTouches: Array) { + this._calculateTransform(e, points, mapTouches); - if (this._active && e.targetTouches.length < this._minTouches) { + if (this._active && mapTouches.length < this._minTouches) { this.reset(); } } @@ -46,10 +46,10 @@ export default class TouchPanHandler { this.reset(); } - _calculateTransform(e: TouchEvent, points: Array) { - if (e.targetTouches.length > 0) this._active = true; + _calculateTransform(e: TouchEvent, points: Array, mapTouches: Array) { + if (mapTouches.length > 0) this._active = true; - const touches = indexTouches(e.targetTouches, points); + const touches = indexTouches(mapTouches, points); const touchPointSum = new Point(0, 0); const touchDeltaSum = new Point(0, 0); diff --git a/src/ui/handler/touch_zoom_rotate.js b/src/ui/handler/touch_zoom_rotate.js index d7b7743b5b9..8e7b910c205 100644 --- a/src/ui/handler/touch_zoom_rotate.js +++ b/src/ui/handler/touch_zoom_rotate.js @@ -24,26 +24,28 @@ class TwoTouchHandler { _start(points: [Point, Point]) {} //eslint-disable-line _move(points: [Point, Point], pinchAround: Point, e: TouchEvent) { return {}; } //eslint-disable-line - touchstart(e: TouchEvent, points: Array) { - if (this._firstTwoTouches || e.targetTouches.length < 2) return; + touchstart(e: TouchEvent, points: Array, mapTouches: Array) { + //console.log(e.target, e.targetTouches.length ? e.targetTouches[0].target : null); + //log('touchstart', points, e.target.innerHTML, e.targetTouches.length ? e.targetTouches[0].target.innerHTML: undefined); + if (this._firstTwoTouches || mapTouches.length < 2) return; this._firstTwoTouches = [ - e.targetTouches[0].identifier, - e.targetTouches[1].identifier + mapTouches[0].identifier, + mapTouches[1].identifier ]; // implemented by child classes this._start([points[0], points[1]]); } - touchmove(e: TouchEvent, points: Array) { + touchmove(e: TouchEvent, points: Array, mapTouches: Array) { if (!this._firstTwoTouches) return; e.preventDefault(); const [idA, idB] = this._firstTwoTouches; - const a = getTouchById(e, points, idA); - const b = getTouchById(e, points, idB); + const a = getTouchById(mapTouches, points, idA); + const b = getTouchById(mapTouches, points, idB); if (!a || !b) return; const pinchAround = this._aroundCenter ? null : a.add(b).div(2); @@ -52,12 +54,12 @@ class TwoTouchHandler { } - touchend(e: TouchEvent, points: Array) { + touchend(e: TouchEvent, points: Array, mapTouches: Array) { if (!this._firstTwoTouches) return; const [idA, idB] = this._firstTwoTouches; - const a = getTouchById(e, points, idA); - const b = getTouchById(e, points, idB); + const a = getTouchById(mapTouches, points, idA); + const b = getTouchById(mapTouches, points, idB); if (a && b) return; if (this._active) DOM.suppressClick(); @@ -88,9 +90,9 @@ class TwoTouchHandler { } } -function getTouchById(e: TouchEvent, points: Array, identifier: number) { - for (let i = 0; i < e.targetTouches.length; i++) { - if (e.targetTouches[i].identifier === identifier) return points[i]; +function getTouchById(mapTouches: Array, points: Array, identifier: number) { + for (let i = 0; i < mapTouches.length; i++) { + if (mapTouches[i].identifier === identifier) return points[i]; } } diff --git a/src/ui/handler_manager.js b/src/ui/handler_manager.js index 0a617bd41aa..6ebba919b63 100644 --- a/src/ui/handler_manager.js +++ b/src/ui/handler_manager.js @@ -18,7 +18,7 @@ import TapDragZoomHandler from './handler/tap_drag_zoom'; import DragPanHandler from './handler/shim/drag_pan'; import DragRotateHandler from './handler/shim/drag_rotate'; import TouchZoomRotateHandler from './handler/shim/touch_zoom_rotate'; -import {extend} from '../util/util'; +import {bindAll, extend} from '../util/util'; import window from '../util/window'; import Point from '@mapbox/point-geometry'; import assert from 'assert'; @@ -49,10 +49,10 @@ export interface Handler { // Handlers can optionally implement these methods. // They are called with dom events whenever those dom evens are received. - +touchstart?: (e: TouchEvent, points: Array) => HandlerResult | void; - +touchmove?: (e: TouchEvent, points: Array) => HandlerResult | void; - +touchend?: (e: TouchEvent, points: Array) => HandlerResult | void; - +touchcancel?: (e: TouchEvent, points: Array) => HandlerResult | void; + +touchstart?: (e: TouchEvent, points: Array, mapTouches: Array) => HandlerResult | void; + +touchmove?: (e: TouchEvent, points: Array, mapTouches: Array) => HandlerResult | void; + +touchend?: (e: TouchEvent, points: Array, mapTouches: Array) => HandlerResult | void; + +touchcancel?: (e: TouchEvent, points: Array, mapTouches: Array) => HandlerResult | void; +mousedown?: (e: MouseEvent, point: Point) => HandlerResult | void; +mousemove?: (e: MouseEvent, point: Point) => HandlerResult | void; +mouseup?: (e: MouseEvent, point: Point) => HandlerResult | void; @@ -105,6 +105,7 @@ class HandlerManager { _changes: Array<[HandlerResult, Object, any]>; _previousActiveHandlers: { [string]: Handler }; _bearingChanged: boolean; + _listeners: Array<[HTMLElement, string, void | {passive?: boolean, capture?: boolean}]>; constructor(map: Map, options: { interactive: boolean, pitchWithRotate: boolean, clickTolerance: number, bearingSnap: number}) { this._map = map; @@ -122,45 +123,58 @@ class HandlerManager { this._addDefaultHandlers(options); - // Bind touchstart and touchmove with passive: false because, even though - // they only fire a map events and therefore could theoretically be - // passive, binding with passive: true causes iOS not to respect - // e.preventDefault() in _other_ handlers, even if they are non-passive - // (see https://bugs.webkit.org/show_bug.cgi?id=184251) - this._addListener(this._el, 'touchstart', {passive: false}); - this._addListener(this._el, 'touchmove', {passive: false}); - this._addListener(this._el, 'touchend'); - this._addListener(this._el, 'touchcancel'); - - this._addListener(this._el, 'mousedown'); - this._addListener(this._el, 'mousemove'); - this._addListener(this._el, 'mouseup'); - - // Bind window-level event listeners for move and up/end events. In the absence of - // the pointer capture API, which is not supported by all necessary platforms, - // window-level event listeners give us the best shot at capturing events that - // fall outside the map canvas element. Use `{capture: true}` for the move event - // to prevent map move events from being fired during a drag. - this._addListener(window.document, 'mousemove', {capture: true}, 'windowMousemove'); - this._addListener(window.document, 'mouseup', undefined, 'windowMouseup'); - - this._addListener(this._el, 'mouseover'); - this._addListener(this._el, 'mouseout'); - this._addListener(this._el, 'dblclick'); - this._addListener(this._el, 'click'); - - this._addListener(this._el, 'keydown', {capture: false}); - this._addListener(this._el, 'keyup'); - - this._addListener(this._el, 'wheel', {passive: false}); - this._addListener(this._el, 'contextmenu'); - - DOM.addEventListener(window, 'blur', () => this.stop()); + bindAll(['handleEvent', 'handleWindowEvent'], this); + + const el = this._el; + + this._listeners = [ + // This needs to be `passive: true` so that a double tap fires two + // pairs of touchstart/end events in iOS Safari 13. If this is set to + // `passive: false` then the second pair of events is only fired if + // preventDefault() is called on the first touchstart. Calling preventDefault() + // undesirably prevents click events. + [el, 'touchstart', {passive: true}], + // This needs to be `passive: false` so that scrolls and pinches can be + // prevented in browsers that don't support `touch-actions: none`, for example iOS Safari 12. + [el, 'touchmove', {passive: false}], + [el, 'touchend', undefined], + [el, 'touchcancel', undefined], + + [el, 'mousedown', undefined], + [el, 'mousemove', undefined], + [el, 'mouseup', undefined], + + // Bind window-level event listeners for move and up/end events. In the absence of + // the pointer capture API, which is not supported by all necessary platforms, + // window-level event listeners give us the best shot at capturing events that + // fall outside the map canvas element. Use `{capture: true}` for the move event + // to prevent map move events from being fired during a drag. + [window.document, 'mousemove', {capture: true}], + [window.document, 'mouseup', undefined], + + [el, 'mouseover', undefined], + [el, 'mouseout', undefined], + [el, 'dblclick', undefined], + [el, 'click', undefined], + + [el, 'keydown', {capture: false}], + [el, 'keyup', undefined], + + [el, 'wheel', {passive: false}], + [el, 'contextmenu', undefined], + + [window, 'blur', undefined] + ]; + + for (const [target, type, listenerOptions] of this._listeners) { + DOM.addEventListener(target, type, target === window.document ? this.handleWindowEvent : this.handleEvent, listenerOptions); + } } - _addListener(element: Element, eventType: string, options: Object, name_?: string) { - const name = name_ || eventType; - DOM.addEventListener(element, eventType, e => this._processInputEvent(e, name), options); + destroy() { + for (const [target, type, listenerOptions] of this._listeners) { + DOM.removeEventListener(target, type, target === window.document ? this.handleWindowEvent : this.handleEvent, listenerOptions); + } } _addDefaultHandlers(options: { interactive: boolean, pitchWithRotate: boolean, clickTolerance: number }) { @@ -247,6 +261,10 @@ class HandlerManager { return !!this._eventsInProgress.rotate; } + isMoving() { + return Boolean(isMoving(this._eventsInProgress)) || this.isZooming(); + } + _blockedByActive(activeHandlers: { [string]: Handler }, allowed: Array, myName: string) { for (const name in activeHandlers) { if (name === myName) continue; @@ -257,7 +275,27 @@ class HandlerManager { return false; } - _processInputEvent(e: InputEvent | RenderFrameEvent, eventName?: string) { + handleWindowEvent(e: InputEvent) { + this.handleEvent(e, `${e.type}Window`); + } + + _getMapTouches(touches: TouchList) { + const mapTouches = []; + for (const t of touches) { + const target = ((t.target: any): Node); + if (this._el.contains(target)) { + mapTouches.push(t); + } + } + return ((mapTouches: any): TouchList); + } + + handleEvent(e: InputEvent | RenderFrameEvent, eventName?: string) { + + if (e.type === 'blur') { + this.stop(); + return; + } this._updatingCamera = true; assert(e.timeStamp !== undefined); @@ -273,9 +311,8 @@ class HandlerManager { const eventsInProgress = {}; const activeHandlers = {}; - const points = e ? (e.targetTouches ? - DOM.touchPos(this._el, ((e: any): TouchEvent).targetTouches) : - DOM.mousePos(this._el, ((e: any): MouseEvent))) : null; + const mapTouches = e.touches ? this._getMapTouches(((e: any): TouchEvent).touches) : undefined; + const points = mapTouches ? DOM.touchPos(this._el, mapTouches) : DOM.mousePos(this._el, ((e: any): MouseEvent)); for (const {handlerName, handler, allowed} of this._handlers) { if (!handler.isEnabled()) continue; @@ -286,7 +323,7 @@ class HandlerManager { } else { if ((handler: any)[eventName || e.type]) { - data = (handler: any)[eventName || e.type](e, points); + data = (handler: any)[eventName || e.type](e, points, mapTouches); this.mergeHandlerResult(mergedHandlerResult, eventsInProgress, data, handlerName, inputEvent); if (data && data.needsRenderFrame) { this._triggerRenderFrame(); @@ -409,17 +446,23 @@ class HandlerManager { const wasMoving = isMoving(this._eventsInProgress); const nowMoving = isMoving(newEventsInProgress); - if (!wasMoving && nowMoving) { - this._fireEvent('movestart', nowMoving.originalEvent); - } + const startEvents = {}; for (const eventName in newEventsInProgress) { const {originalEvent} = newEventsInProgress[eventName]; - const isStart = !this._eventsInProgress[eventName]; - this._eventsInProgress[eventName] = newEventsInProgress[eventName]; - if (isStart) { - this._fireEvent(`${eventName}start`, originalEvent); + if (!this._eventsInProgress[eventName]) { + startEvents[`${eventName}start`] = originalEvent; } + this._eventsInProgress[eventName] = newEventsInProgress[eventName]; + } + + // fire start events only after this._eventsInProgress has been updated + if (!wasMoving && nowMoving) { + this._fireEvent('movestart', nowMoving.originalEvent); + } + + for (const name in startEvents) { + this._fireEvent(name, startEvents[name]); } if (newEventsInProgress.rotate) this._bearingChanged = true; @@ -433,16 +476,22 @@ class HandlerManager { this._fireEvent(eventName, originalEvent); } + const endEvents = {}; + let originalEndEvent; for (const eventName in this._eventsInProgress) { const {handlerName, originalEvent} = this._eventsInProgress[eventName]; if (!this._handlersById[handlerName].isActive()) { delete this._eventsInProgress[eventName]; originalEndEvent = deactivatedHandlers[handlerName] || originalEvent; - this._fireEvent(`${eventName}end`, originalEndEvent); + endEvents[`${eventName}end`] = originalEndEvent; } } + for (const name in endEvents) { + this._fireEvent(name, endEvents[name]); + } + const stillMoving = isMoving(this._eventsInProgress); if ((wasMoving || nowMoving) && !stillMoving) { this._updatingCamera = true; @@ -475,7 +524,7 @@ class HandlerManager { if (this._frameId === undefined) { this._frameId = this._map._requestRenderFrame(timeStamp => { delete this._frameId; - this._processInputEvent(new RenderFrameEvent('renderFrame', {timeStamp})); + this.handleEvent(new RenderFrameEvent('renderFrame', {timeStamp})); this._applyChanges(); }); } diff --git a/src/ui/map.js b/src/ui/map.js index cac2ff4b32b..e3192d562ec 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -226,7 +226,7 @@ const defaultOptions = { * @param {number} [options.bearing=0] The initial bearing (rotation) of the map, measured in degrees counter-clockwise from north. If `bearing` is not specified in the constructor options, Mapbox GL JS will look for it in the map's style object. If it is not specified in the style, either, it will default to `0`. * @param {number} [options.pitch=0] The initial pitch (tilt) of the map, measured in degrees away from the plane of the screen (0-60). If `pitch` is not specified in the constructor options, Mapbox GL JS will look for it in the map's style object. If it is not specified in the style, either, it will default to `0`. * @param {LngLatBoundsLike} [options.bounds] The initial bounds of the map. If `bounds` is specified, it overrides `center` and `zoom` constructor options. - * @param {Object} [options.fitBoundsOptions] A [`fitBounds`](#map#fitbounds) options object to use _only_ when fitting the initial `bounds` provided above. + * @param {Object} [options.fitBoundsOptions] A {@link Map#fitBounds} options object to use _only_ when fitting the initial `bounds` provided above. * @param {boolean} [options.renderWorldCopies=true] If `true`, multiple copies of the world will be rendered side by side beyond -180 and 180 degrees longitude. If set to `false`: * - When the map is zoomed out far enough that a single representation of the world does not fill the map's entire * container, there will be blank space beyond 180 and -180 degrees longitude. @@ -853,7 +853,7 @@ class Map extends Camera { * var isMoving = map.isMoving(); */ isMoving(): boolean { - return this._moving || this.handlers.isActive(); + return this._moving || this.handlers.isMoving(); } /** @@ -925,33 +925,103 @@ class Map extends Camera { } /** - * Adds a listener for events of a specified type. - * - * @method - * @name on - * @memberof Map - * @instance - * @param {string} type The event type to add a listen for. - * @param {Function} listener The function to be called when the event is fired. - * The listener function is called with the data object passed to `fire`, - * extended with `target` and `type` properties. - * @returns {Map} `this` - */ - - /** - * Adds a listener for events of a specified type occurring on features in a specified style layer. - * - * @param {string} type The event type to listen for; one of `'mousedown'`, `'mouseup'`, `'click'`, `'dblclick'`, - * `'mousemove'`, `'mouseenter'`, `'mouseleave'`, `'mouseover'`, `'mouseout'`, `'contextmenu'`, `'touchstart'`, - * `'touchend'`, or `'touchcancel'`. `mouseenter` and `mouseover` events are triggered when the cursor enters - * a visible portion of the specified layer from outside that layer or outside the map canvas. `mouseleave` - * and `mouseout` events are triggered when the cursor leaves a visible portion of the specified layer, or leaves - * the map canvas. - * @param {string} layerId The ID of a style layer. Only events whose location is within a visible - * feature in this layer will trigger the listener. The event will have a `features` property containing - * an array of the matching features. + * Adds a listener for events of a specified type, optionally limited to features in a specified style layer. + * + * @param {string} type The event type to listen for. Events compatible with the optional `layerId` parameter are triggered + * when the cursor enters a visible portion of the specified layer from outside that layer or outside the map canvas. + * + * | Event | Compatible with `layerId` | + * |-----------------------------------------------------------|---------------------------| + * | [`mousedown`](#map.event:mousedown) | yes | + * | [`mouseup`](#map.event:mouseup) | yes | + * | [`mouseover`](#map.event:mouseover) | yes | + * | [`mouseout`](#map.event:mouseout) | yes | + * | [`mousemove`](#map.event:mousemove) | yes | + * | [`mouseenter`](#map.event:mouseenter) | yes (required) | + * | [`mouseleave`](#map.event:mouseleave) | yes (required) | + * | [`click`](#map.event:click) | yes | + * | [`dblclick`](#map.event:dblclick) | yes | + * | [`contextmenu`](#map.event:contextmenu) | yes | + * | [`touchstart`](#map.event:touchstart) | yes | + * | [`touchend`](#map.event:touchend) | yes | + * | [`touchcancel`](#map.event:touchcancel) | yes | + * | [`wheel`](#map.event:wheel) | | + * | [`resize`](#map.event:resize) | | + * | [`remove`](#map.event:remove) | | + * | [`touchmove`](#map.event:touchmove) | | + * | [`movestart`](#map.event:movestart) | | + * | [`move`](#map.event:move) | | + * | [`moveend`](#map.event:moveend) | | + * | [`dragstart`](#map.event:dragstart) | | + * | [`drag`](#map.event:drag) | | + * | [`dragend`](#map.event:dragend) | | + * | [`zoomstart`](#map.event:zoomstart) | | + * | [`zoom`](#map.event:zoom) | | + * | [`zoomend`](#map.event:zoomend) | | + * | [`rotatestart`](#map.event:rotatestart) | | + * | [`rotate`](#map.event:rotate) | | + * | [`rotateend`](#map.event:rotateend) | | + * | [`pitchstart`](#map.event:pitchstart) | | + * | [`pitch`](#map.event:pitch) | | + * | [`pitchend`](#map.event:pitchend) | | + * | [`boxzoomstart`](#map.event:boxzoomstart) | | + * | [`boxzoomend`](#map.event:boxzoomend) | | + * | [`boxzoomcancel`](#map.event:boxzoomcancel) | | + * | [`webglcontextlost`](#map.event:webglcontextlost) | | + * | [`webglcontextrestored`](#map.event:webglcontextrestored) | | + * | [`load`](#map.event:load) | | + * | [`render`](#map.event:render) | | + * | [`idle`](#map.event:idle) | | + * | [`error`](#map.event:error) | | + * | [`data`](#map.event:data) | | + * | [`styledata`](#map.event:styledata) | | + * | [`sourcedata`](#map.event:sourcedata) | | + * | [`dataloading`](#map.event:dataloading) | | + * | [`styledataloading`](#map.event:styledataloading) | | + * | [`sourcedataloading`](#map.event:sourcedataloading) | | + * | [`styleimagemissing`](#map.event:styleimagemissing) | | + * + * @param {string} layerId (optional) The ID of a style layer. Event will only be triggered if its location + * is within a visible feature in this layer. The event will have a `features` property containing + * an array of the matching features. If `layerId` is not supplied, the event will not have a `features` property. + * Please note that many event types are not compatible with the optional `layerId` parameter. * @param {Function} listener The function to be called when the event is fired. * @returns {Map} `this` + * @example + * // Set an event listener that will fire + * // when the map has finished loading + * map.on('load', function() { + * // Once the map has finished loading, + * // add a new layer + * map.addLayer({ + * id: 'points-of-interest', + * source: { + * type: 'vector', + * url: 'mapbox://mapbox.mapbox-streets-v8' + * }, + * 'source-layer': 'poi_label', + * type: 'circle', + * paint: { + * // Mapbox Style Specification paint properties + * }, + * layout: { + * // Mapbox Style Specification layout properties + * } + * }); + * }); + * @example + * // Set an event listener that will fire + * // when a feature on the countries layer of the map is clicked + * map.on('click', 'countries', function(e) { + * new mapboxgl.Popup() + * .setLngLat(e.lngLat) + * .setHTML(`Country name: ${e.features[0].properties.name}`) + * .addTo(map); + * }); + * @see [Display popup on click](https://docs.mapbox.com/mapbox-gl-js/example/popup-on-click/) + * @see [Center the map on a clicked symbol](https://docs.mapbox.com/mapbox-gl-js/example/center-on-symbol/) + * @see [Create a hover effect](https://docs.mapbox.com/mapbox-gl-js/example/hover-styles/) + * @see [Create a draggable marker](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-point/) */ on(type: MapEvent, layerId: any, listener: any) { if (listener === undefined) { @@ -1326,9 +1396,9 @@ class Map extends Camera { } /** - * Returns the map's Mapbox style object, which can be used to recreate the map's style. + * Returns the map's Mapbox [style](https://docs.mapbox.com/help/glossary/style/) object, a JSON object which can be used to recreate the map's style. * - * @returns {Object} The map's style object. + * @returns {Object} The map's style JSON object. * * @example * var styleJson = map.getStyle(); @@ -1393,7 +1463,8 @@ class Map extends Camera { } /** - * Returns a Boolean indicating whether the source is loaded. + * Returns a Boolean indicating whether the source is loaded. Returns `true` if the source with + * the given ID in the map's style has no outstanding network requests, otherwise `false`. * * @param {string} id The ID of the source to be checked. * @returns {boolean} A Boolean indicating whether the source is loaded. @@ -1460,14 +1531,22 @@ class Map extends Camera { /** * Returns the source with the specified ID in the map's style. * + * This method is often used to update a source using the instance members for the relevant + * source type as defined in [Sources](#sources). + * For example, setting the `data` for a GeoJSON source or updating the `url` and `coordinates` + * of an image source. + * * @param {string} id The ID of the source to get. - * @returns {?Object} The style source with the specified ID, or `undefined` - * if the ID corresponds to no existing sources. + * @returns {?Object} The style source with the specified ID or `undefined` if the ID + * corresponds to no existing sources. + * The shape of the object varies by source type. + * A list of options for each source type is available on the Mapbox Style Specification's + * [Sources](https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/) page. * @example * var sourceObject = map.getSource('points'); - * @see [Create a draggable point](https://www.mapbox.com/mapbox-gl-js/example/drag-a-point/) - * @see [Animate a point](https://www.mapbox.com/mapbox-gl-js/example/animate-point-along-line/) - * @see [Add live realtime data](https://www.mapbox.com/mapbox-gl-js/example/live-geojson/) + * @see [Create a draggable point](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-point/) + * @see [Animate a point](https://docs.mapbox.com/mapbox-gl-js/example/animate-point-along-line/) + * @see [Add live realtime data](https://docs.mapbox.com/mapbox-gl-js/example/live-geojson/) */ getSource(id: string) { return this.style.getSource(id); @@ -1481,7 +1560,7 @@ class Map extends Camera { * [`background-pattern`](https://docs.mapbox.com/mapbox-gl-js/style-spec/#paint-background-background-pattern), * [`fill-pattern`](https://docs.mapbox.com/mapbox-gl-js/style-spec/#paint-fill-fill-pattern), * or [`line-pattern`](https://docs.mapbox.com/mapbox-gl-js/style-spec/#paint-line-line-pattern). - * A {@link Map#error} event will be fired if there is not enough space in the sprite to add this image. + * A {@link Map.event:error} event will be fired if there is not enough space in the sprite to add this image. * * @param id The ID of the image. * @param image The image as an `HTMLImageElement`, `ImageData`, `ImageBitmap` or object with `width`, `height`, and `data` @@ -1680,15 +1759,51 @@ class Map extends Camera { * A layer defines how data from a specified source will be styled. Read more about layer types * and available paint and layout properties in the [Mapbox Style Specification](https://docs.mapbox.com/mapbox-gl-js/style-spec/#layers). * - * @param {Object | CustomLayerInterface} layer The style layer to add, conforming to the Mapbox Style Specification's - * [layer definition](https://docs.mapbox.com/mapbox-gl-js/style-spec/#layers). + * @param {Object | CustomLayerInterface} layer The layer to add, conforming to either the Mapbox Style Specification's [layer definition](https://docs.mapbox.com/mapbox-gl-js/style-spec/#layers) or, less commonly, the {@link CustomLayerInterface} specification. + * The Mapbox Style Specification's layer definition is appropriate for most layers. + * + * @param {string} layer.id A unique idenfier that you define. + * @param {string} layer.type The type of layer (for example `fill` or `symbol`). + * A list of layer types is available in the [Mapbox Style Specification](https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#type). + * + * (This can also be `custom`. For more information, see {@link CustomLayerInterface}.) + * @param {string | Object} [layer.source] The data source for the layer. + * Reference a source that has _already been defined_ using the source's unique id. + * Reference a _new source_ using a source object (as defined in the [Mapbox Style Specification](https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/)) directly. + * This is **required** for all `layer.type` options _except_ for `custom`. + * @param {string} [layer.sourceLayer] (optional) The name of the [source layer](https://docs.mapbox.com/help/glossary/source-layer/) within the specified `layer.source` to use for this style layer. + * This is only applicable for vector tile sources and is **required** when `layer.source` is of the type `vector`. + * @param {array} [layer.filter] (optional) An expression specifying conditions on source features. + * Only features that match the filter are displayed. + * The Mapbox Style Specification includes more information on the limitations of the [`filter`](https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#filter) parameter + * and a complete list of available [expressions](https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions/). + * If no filter is provided, all features in the source (or source layer for vector tilesets) will be displayed. + * @param {Object} [layer.paint] (optional) Paint properties for the layer. + * Available paint properties vary by `layer.type`. + * A full list of paint properties for each layer type is available in the [Mapbox Style Specification](https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/). + * If no paint properties are specified, default values will be used. + * @param {Object} [layer.layout] (optional) Layout properties for the layer. + * Available layout properties vary by `layer.type`. + * A full list of layout properties for each layer type is available in the [Mapbox Style Specification](https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/). + * If no layout properties are specified, default values will be used. + * @param {number} [layer.maxzoom] (optional) The maximum zoom level for the layer. + * At zoom levels equal to or greater than the maxzoom, the layer will be hidden. + * The value can be any number between `0` and `24` (inclusive). + * If no maxzoom is provided, the layer will be visible at all zoom levels for which there are tiles available. + * @param {number} [layer.minzoom] (optional) The minimum zoom level for the layer. + * At zoom levels less than the minzoom, the layer will be hidden. + * The value can be any number between `0` and `24` (inclusive). + * If no minzoom is provided, the layer will be visible at all zoom levels for which there are tiles available. + * @param {Object} [layer.metadata] (optional) Arbitrary properties useful to track with the layer, but do not influence rendering. + * @param {string} [layer.renderingMode] This is only applicable for layers with the type `custom`. + * See {@link CustomLayerInterface} for more information. * @param {string} [beforeId] The ID of an existing layer to insert the new layer before. - * If this argument is omitted, the layer will be appended to the end of the layers array. + * If this argument is not specified, the layer will be appended to the end of the layers array. * * @returns {Map} `this` * * @example - * // Add a circle layer with a vector source. + * // Add a circle layer with a vector source * map.addLayer({ * id: 'points-of-interest', * source: { @@ -1705,9 +1820,44 @@ class Map extends Camera { * } * }); * - * @see [Create and style clusters](https://www.mapbox.com/mapbox-gl-js/example/cluster/) - * @see [Add a vector tile source](https://www.mapbox.com/mapbox-gl-js/example/vector-source/) - * @see [Add a WMS source](https://www.mapbox.com/mapbox-gl-js/example/wms/) + * @example + * // Define a source before using it to create a new layer + * map.addSource('state-data', { + * type: 'geojson', + * data: 'path/to/data.geojson' + * }); + * + * map.addLayer({ + * id: 'states', + * // References the GeoJSON source defined above + * // and does not require a `source-layer` + * source: 'state-data', + * type: 'symbol', + * layout: { + * // Set the label content to the + * // feature's `name` property + * text-field: ['get', 'name'] + * } + * }); + * + * @example + * // Add a new symbol layer before an existing layer + * map.addLayer({ + * id: 'states', + * // References a source that's already been defined + * source: 'state-data', + * type: 'symbol', + * layout: { + * // Set the label content to the + * // feature's `name` property + * text-field: ['get', 'name'] + * } + * // Add the layer before the existing `cities` layer + * }, 'cities'); + * + * @see [Create and style clusters](https://docs.mapbox.com/mapbox-gl-js/example/cluster/) + * @see [Add a vector tile source](https://docs.mapbox.com/mapbox-gl-js/example/vector-source/) + * @see [Add a WMS source](https://docs.mapbox.com/mapbox-gl-js/example/wms/) */ addLayer(layer: LayerSpecification | CustomLayerInterface, beforeId?: string) { this._lazyInitEmptyStyle(); @@ -1719,13 +1869,12 @@ class Map extends Camera { * Moves a layer to a different z-position. * * @param {string} id The ID of the layer to move. - * @param {string} [beforeId] The ID of an existing layer to insert the new layer before. - * If this argument is omitted, the layer will be appended to the end of the layers array. + * @param {string} [beforeId] The ID of an existing layer to insert the new layer before. When viewing the map, the `id` layer will appear beneath the `beforeId` layer. If `beforeId` is omitted, the layer will be appended to the end of the layers array and appear above all other layers on the map. * @returns {Map} `this` * * @example - * // Move a layer with ID 'label' before the layer with ID 'waterways'. - * map.moveLayer('label', 'waterways'); + * // Move a layer with ID 'polygon' before the layer with ID 'country-label'. The `polygon` layer will appear beneath the `country-label` layer on the map. + * map.moveLayer('polygon', 'country-label'); */ moveLayer(id: string, beforeId?: string) { this.style.moveLayer(id, beforeId); @@ -1795,19 +1944,35 @@ class Map extends Camera { /** * Sets the filter for the specified style layer. * + * Filters control which features a style layer renders from its source. + * Any feature for which the filter expression evaluates to `true` will be + * rendered on the map. Those that are false will be hidden. + * + * Use `setFilter` to show a subset of your source data. + * + * To clear the filter, pass `null` or `undefined` as the second parameter. + * * @param {string} layerId The ID of the layer to which the filter will be applied. * @param {Array | null | undefined} filter The filter, conforming to the Mapbox Style Specification's * [filter definition](https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#filter). If `null` or `undefined` is provided, the function removes any existing filter from the layer. * @param {Object} [options] Options object. * @param {boolean} [options.validate=true] Whether to check if the filter conforms to the Mapbox GL Style Specification. Disabling validation is a performance optimization that should only be used if you have previously validated the values you will be passing to this function. - * * @returns {Map} `this` + * + * @example + * // display only features with the 'name' property 'USA' + * map.setFilter('my-layer', ['==', ['get', 'name'], 'USA']); * @example - * map.setFilter('my-layer', ['==', 'name', 'USA']); + * // display only features with five or more 'available-spots' + * map.setFilter('bike-docks', ['>=', ['get', 'available-spots'], 5]); + * @example + * // remove the filter for the 'bike-docks' style layer + * map.setFilter('bike-docks', null); * * @see [Filter features within map view](https://www.mapbox.com/mapbox-gl-js/example/filter-features-within-map-view/) * @see [Highlight features containing similar data](https://www.mapbox.com/mapbox-gl-js/example/query-similar-features/) * @see [Create a timeline animation](https://www.mapbox.com/mapbox-gl-js/example/timeline-animation/) + * @see Tutorial: [Show changes over time](https://docs.mapbox.com/help/tutorials/show-changes-over-time/) */ setFilter(layerId: string, filter: ?FilterSpecification, options: StyleSetterOptions = {}) { this.style.setFilter(layerId, filter, options); @@ -1867,6 +2032,7 @@ class Map extends Camera { * @returns {Map} `this` * @example * map.setLayoutProperty('my-layer', 'visibility', 'none'); + * @see [Show and hide layers](https://docs.mapbox.com/mapbox-gl-js/example/toggle-layers/) */ setLayoutProperty(layerId: string, name: string, value: any, options: StyleSetterOptions = {}) { this.style.setLayoutProperty(layerId, name, value, options); @@ -1891,6 +2057,9 @@ class Map extends Camera { * @param {Object} [options] Options object. * @param {boolean} [options.validate=true] Whether to check if the filter conforms to the Mapbox GL Style Specification. Disabling validation is a performance optimization that should only be used if you have previously validated the values you will be passing to this function. * @returns {Map} `this` + * @example + * var layerVisibility = map.getLayoutProperty('my-layer', 'visibility'); + * @see [Show and hide layers](https://docs.mapbox.com/mapbox-gl-js/example/toggle-layers/) */ setLight(light: LightSpecification, options: StyleSetterOptions = {}) { this._lazyInitEmptyStyle(); @@ -1909,23 +2078,42 @@ class Map extends Camera { // eslint-disable-next-line jsdoc/require-returns /** - * Sets the state of a feature. The `state` object is merged in with the existing state of the feature. - * Features are identified by their `id` attribute, which must be an integer or a string that can be - * cast to an integer. + * Sets the `state` of a feature. + * A feature's `state` is a set of user-defined key-value pairs that are assigned to a feature at runtime. + * When using this method, the `state` object is merged with any existing key-value pairs in the feature's state. + * Features are identified by their `feature.id` attribute, which can be any number or string. + * + * This method can only be used with sources that have a `feature.id` attribute. The `feature.id` attribute can be defined in three ways: + * - For vector or GeoJSON sources, including an `id` attribute in the original data file. + * - For vector or GeoJSON sources, using the [`promoteId`](https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/#vector-promoteId) option at the time the source is defined. + * - For GeoJSON sources, using the [`generateId`](https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/#geojson-generateId) option to auto-assign an `id` based on the feature's index in the source data. If you change feature data using `map.getSource('some id').setData(..)`, you may need to re-apply state taking into account updated `id` values. + * + * _Note: You can use the [`feature-state` expression](https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions/#feature-state) to access the values in a feature's state object for the purposes of styling._ * * @param {Object} feature Feature identifier. Feature objects returned from * {@link Map#queryRenderedFeatures} or event handlers can be used as feature identifiers. * @param {string | number} feature.id Unique id of the feature. - * @param {string} feature.source The Id of the vector source or GeoJSON source for the feature. - * @param {string} [feature.sourceLayer] (optional) *For vector tile sources, the sourceLayer is - * required.* + * @param {string} feature.source The id of the vector or GeoJSON source for the feature. + * @param {string} [feature.sourceLayer] (optional) *For vector tile sources, `sourceLayer` is required.* * @param {Object} state A set of key-value pairs. The values should be valid JSON types. * - * This method requires the `feature.id` attribute on data sets. For GeoJSON sources without - * feature ids, set the `generateId` option in the `GeoJSONSourceSpecification` to auto-assign them. This - * option assigns ids based on a feature's index in the source data. If you change feature data using - * `map.getSource('some id').setData(..)`, you may need to re-apply state taking into account updated `id` values. + * @example + * // When the mouse moves over the `my-layer` layer, update + * // the feature state for the feature under the mouse + * map.on('mousemove', 'my-layer', function(e) { + * if (e.features.length > 0) { + * map.setFeatureState({ + * source: 'my-source', + * sourceLayer: 'my-source-layer', + * id: e.features[0].id, + * }, { + * hover: true + * }); + * } + * }); + * * @see [Create a hover effect](https://docs.mapbox.com/mapbox-gl-js/example/hover-styles/) + * @see Tutorial: [Create interactive hover effects with Mapbox GL JS](https://docs.mapbox.com/help/tutorials/create-interactive-hover-effects-with-mapbox-gl-js/) */ setFeatureState(feature: { source: string; sourceLayer?: string; id: string | number; }, state: Object) { this.style.setFeatureState(feature, state); @@ -1934,19 +2122,50 @@ class Map extends Camera { // eslint-disable-next-line jsdoc/require-returns /** - * Removes feature state, setting it back to the default behavior. If only - * source is specified, removes all states of that source. If - * target.id is also specified, removes all keys for that feature's state. - * If key is also specified, removes that key from that feature's state. - * Features are identified by their `id` attribute, which must be an integer or a string that can be - * cast to an integer. - * @param {Object} target Identifier of where to set state: can be a source, a feature, or a specific key of feature. + * Removes the `state` of a feature, setting it back to the default behavior. + * If only a `target.source` is specified, it will remove the state for all features from that source. + * If `target.id` is also specified, it will remove all keys for that feature's state. + * If `key` is also specified, it removes only that key from that feature's state. + * Features are identified by their `feature.id` attribute, which can be any number or string. + * + * @param {Object} target Identifier of where to remove state. It can be a source, a feature, or a specific key of feature. * Feature objects returned from {@link Map#queryRenderedFeatures} or event handlers can be used as feature identifiers. * @param {string | number} target.id (optional) Unique id of the feature. Optional if key is not specified. - * @param {string} target.source The Id of the vector source or GeoJSON source for the feature. - * @param {string} [target.sourceLayer] (optional) *For vector tile sources, the sourceLayer is - * required.* + * @param {string} target.source The id of the vector or GeoJSON source for the feature. + * @param {string} [target.sourceLayer] (optional) *For vector tile sources, `sourceLayer` is required.* * @param {string} key (optional) The key in the feature state to reset. + * + * @example + * // Reset the entire state object for all features + * // in the `my-source` source + * map.removeFeatureState({ + * source: 'my-source' + * }); + * + * @example + * // When the mouse leaves the `my-layer` layer, + * // reset the entire state object for the + * // feature under the mouse + * map.on('mouseleave', 'my-layer', function(e) { + * map.removeFeatureState({ + * source: 'my-source', + * sourceLayer: 'my-source-layer', + * id: e.features[0].id + * }); + * }); + * + * @example + * // When the mouse leaves the `my-layer` layer, + * // reset only the `hover` key-value pair in the + * // state for the feature under the mouse + * map.on('mouseleave', 'my-layer', function(e) { + * map.removeFeatureState({ + * source: 'my-source', + * sourceLayer: 'my-source-layer', + * id: e.features[0].id + * }, 'hover'); + * }); + * */ removeFeatureState(target: { source: string; sourceLayer?: string; id?: string | number; }, key?: string) { this.style.removeFeatureState(target, key); @@ -1954,18 +2173,33 @@ class Map extends Camera { } /** - * Gets the state of a feature. - * Features are identified by their `id` attribute, which must be an integer or a string that can be - * cast to an integer. + * Gets the `state` of a feature. + * A feature's `state` is a set of user-defined key-value pairs that are assigned to a feature at runtime. + * Features are identified by their `feature.id` attribute, which can be any number or string. + * + * _Note: To access the values in a feature's state object for the purposes of styling the feature, use the [`feature-state` expression](https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions/#feature-state)._ * * @param {Object} feature Feature identifier. Feature objects returned from * {@link Map#queryRenderedFeatures} or event handlers can be used as feature identifiers. * @param {string | number} feature.id Unique id of the feature. - * @param {string} feature.source The Id of the vector source or GeoJSON source for the feature. - * @param {string} [feature.sourceLayer] (optional) *For vector tile sources, the sourceLayer is - * required.* + * @param {string} feature.source The id of the vector or GeoJSON source for the feature. + * @param {string} [feature.sourceLayer] (optional) *For vector tile sources, `sourceLayer` is required.* + * + * @returns {Object} The state of the feature: a set of key-value pairs that was assigned to the feature at runtime. + * + * @example + * // When the mouse moves over the `my-layer` layer, + * // get the feature state for the feature under the mouse + * map.on('mousemove', 'my-layer', function(e) { + * if (e.features.length > 0) { + * map.getFeatureState({ + * source: 'my-source', + * sourceLayer: 'my-source-layer' + * id: e.features[0].id + * }); + * } + * }); * - * @returns {Object} The state of the feature. */ getFeatureState(feature: { source: string; sourceLayer?: string; id: string | number; }): any { return this.style.getFeatureState(feature); @@ -2288,13 +2522,14 @@ class Map extends Camera { if (somethingDirty || this._repaint) { this.triggerRepaint(); } else if (!this.isMoving() && this.loaded()) { - if (!this._fullyLoaded) { - this._fullyLoaded = true; - PerformanceUtils.mark(PerformanceMarkers.fullLoad); - } this.fire(new Event('idle')); } + if (this._loaded && !this._fullyLoaded && !somethingDirty) { + this._fullyLoaded = true; + PerformanceUtils.mark(PerformanceMarkers.fullLoad); + } + return this; } @@ -2319,6 +2554,8 @@ class Map extends Camera { } this._renderTaskQueue.clear(); this.painter.destroy(); + this.handlers.destroy(); + delete this.handlers; this.setStyle(null); if (typeof window !== 'undefined') { window.removeEventListener('resize', this._onWindowResize, false); @@ -2342,6 +2579,10 @@ class Map extends Camera { * Trigger the rendering of a single frame. Use this method with custom layers to * repaint the map when the layer changes. Calling this multiple times before the * next frame is rendered will still result in only a single frame being rendered. + * @example + * map.triggerRepaint(); + * @see [Add a 3D model](https://docs.mapbox.com/mapbox-gl-js/example/add-3d-model/) + * @see [Add an animated icon to the map](https://docs.mapbox.com/mapbox-gl-js/example/add-image-animated/) */ triggerRepaint() { if (this.style && !this._frame) { @@ -2375,6 +2616,8 @@ class Map extends Camera { * @type {boolean} * @instance * @memberof Map + * @example + * map.showTileBoundaries = true; */ get showTileBoundaries(): boolean { return !!this._showTileBoundaries; } set showTileBoundaries(value: boolean) { @@ -2580,10 +2823,15 @@ function removeNode(node) { * `x` and `y` properties representing screen coordinates in pixels. * * @typedef {Object} Point + * @example + * var point = new mapboxgl.Point(-77, 38); */ /** * A {@link Point} or an array of two numbers representing `x` and `y` screen coordinates in pixels. * * @typedef {(Point | Array)} PointLike + * @example + * var p1 = new mapboxgl.Point(-77, 38); // a PointLike which is a Point + * var p2 = [-77, 38]; // a PointLike which is an array of two numbers */ diff --git a/src/ui/marker.js b/src/ui/marker.js index 55484adb013..7dcdc22670c 100644 --- a/src/ui/marker.js +++ b/src/ui/marker.js @@ -19,6 +19,7 @@ type Options = { offset?: PointLike, anchor?: Anchor, color?: string, + scale?: number, draggable?: boolean, rotation?: number, rotationAlignment?: string, @@ -33,6 +34,7 @@ type Options = { * Options are `'center'`, `'top'`, `'bottom'`, `'left'`, `'right'`, `'top-left'`, `'top-right'`, `'bottom-left'`, and `'bottom-right'`. * @param {PointLike} [options.offset] The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up. * @param {string} [options.color='#3FB1CE'] The color to use for the default marker if options.element is not provided. The default is light blue. + * @param {number} [options.scale=1] The scale to use for the default marker if options.element is not provided. The default scale corresponds to a height of `41px` and a width of `27px`. * @param {boolean} [options.draggable=false] A boolean indicating whether or not a marker is able to be dragged to a new position on the map. * @param {number} [options.rotation=0] The rotation angle of the marker in degrees, relative to its respective `rotationAlignment` setting. A positive value will rotate the marker clockwise. * @param {string} [options.pitchAlignment='auto'] `map` aligns the `Marker` to the plane of the map. `viewport` aligns the `Marker` to the plane of the viewport. `auto` automatically matches the value of `rotationAlignment`. @@ -53,6 +55,7 @@ export default class Marker extends Evented { _lngLat: LngLat; _pos: ?Point; _color: ?string; + _scale: number; _defaultMarker: boolean; _draggable: boolean; _state: 'inactive' | 'pending' | 'active'; // used for handling drag events @@ -81,6 +84,7 @@ export default class Marker extends Evented { this._anchor = options && options.anchor || 'center'; this._color = options && options.color || '#3FB1CE'; + this._scale = options && options.scale || 1; this._draggable = options && options.draggable || false; this._state = 'inactive'; this._rotation = options && options.rotation || 0; @@ -94,10 +98,12 @@ export default class Marker extends Evented { // create default map marker SVG const svg = DOM.createNS('http://www.w3.org/2000/svg', 'svg'); + const defaultHeight = 41; + const defaultWidth = 27; svg.setAttributeNS(null, 'display', 'block'); - svg.setAttributeNS(null, 'height', '41px'); - svg.setAttributeNS(null, 'width', '27px'); - svg.setAttributeNS(null, 'viewBox', '0 0 27 41'); + svg.setAttributeNS(null, 'height', `${defaultHeight}px`); + svg.setAttributeNS(null, 'width', `${defaultWidth}px`); + svg.setAttributeNS(null, 'viewBox', `0 0 ${defaultWidth} ${defaultHeight}`); const markerLarge = DOM.createNS('http://www.w3.org/2000/svg', 'g'); markerLarge.setAttributeNS(null, 'stroke', 'none'); @@ -181,6 +187,9 @@ export default class Marker extends Evented { svg.appendChild(page1); + svg.setAttributeNS(null, 'height', `${defaultHeight * this._scale}px`); + svg.setAttributeNS(null, 'width', `${defaultWidth * this._scale}px`); + this._element.appendChild(svg); // if no element and no offset option given apply an offset for the default marker @@ -216,9 +225,13 @@ export default class Marker extends Evented { } /** - * Attaches the marker to a map + * Attaches the `Marker` to a `Map` object. * @param {Map} map The Mapbox GL JS map to add the marker to. * @returns {Marker} `this` + * @example + * var marker = new mapboxgl.Marker() + * .setLngLat([30.5, 50.5]) + * .addTo(map); // add the marker to the map */ addTo(map: Map) { this.remove(); @@ -270,16 +283,30 @@ export default class Marker extends Evented { * the marker on screen. * * @returns {LngLat} A {@link LngLat} describing the marker's location. - */ + * @example + * // Store the marker's longitude and latitude coordinates in a variable + * var lngLat = marker.getLngLat(); + * // Print the marker's longitude and latitude values in the console + * console.log('Longitude: ' + lngLat.lng + ', Latitude: ' + lngLat.lat ) + * @see [Create a draggable Marker](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-marker/) + */ getLngLat() { return this._lngLat; } /** - * Set the marker's geographical position and move it. - * @param {LngLat} lnglat A {@link LngLat} describing where the marker should be located. - * @returns {Marker} `this` - */ + * Set the marker's geographical position and move it. + * @param {LngLat} lnglat A {@link LngLat} describing where the marker should be located. + * @returns {Marker} `this` + * @example + * // Create a new marker, set the longitude and latitude, and add it to the map + * new mapboxgl.Marker() + * .setLngLat([-65.017, -16.457]) + * .addTo(map); + * @see [Add custom icons with Markers](https://docs.mapbox.com/mapbox-gl-js/example/custom-marker-icons/) + * @see [Create a draggable Marker](https://docs.mapbox.com/mapbox-gl-js/example/drag-a-marker/) + * @see [Add a marker using a place name](https://docs.mapbox.com/mapbox-gl-js/example/marker-from-geocode/) + */ setLngLat(lnglat: LngLatLike) { this._lngLat = LngLat.convert(lnglat); this._pos = null; @@ -297,10 +324,16 @@ export default class Marker extends Evented { } /** - * Binds a Popup to the Marker - * @param popup an instance of the `Popup` class. If undefined or null, any popup - * set on this `Marker` instance is unset + * Binds a {@link Popup} to the {@link Marker}. + * @param popup An instance of the {@link Popup} class. If undefined or null, any popup + * set on this {@link Marker} instance is unset. * @returns {Marker} `this` + * @example + * var marker = new mapboxgl.Marker() + * .setLngLat([0, 0]) + * .setPopup(new mapboxgl.Popup().setHTML("

Hello World!

")) // add popup + * .addTo(map); + * @see [Attach a popup to a marker instance](https://docs.mapbox.com/mapbox-gl-js/example/set-popup/) */ setPopup(popup: ?Popup) { if (this._popup) { @@ -364,16 +397,30 @@ export default class Marker extends Evented { } /** - * Returns the Popup instance that is bound to the Marker + * Returns the {@link Popup} instance that is bound to the {@link Marker}. * @returns {Popup} popup + * @example + * var marker = new mapboxgl.Marker() + * .setLngLat([0, 0]) + * .setPopup(new mapboxgl.Popup().setHTML("

Hello World!

")) + * .addTo(map); + * + * console.log(marker.getPopup()); // return the popup instance */ getPopup() { return this._popup; } /** - * Opens or closes the bound popup, depending on the current state + * Opens or closes the {@link Popup} instance that is bound to the {@link Marker}, depending on the current state of the {@link Popup}. * @returns {Marker} `this` + * @example + * var marker = new mapboxgl.Marker() + * .setLngLat([0, 0]) + * .setPopup(new mapboxgl.Popup().setHTML("

Hello World!

")) + * .addTo(map); + * + * marker.togglePopup(); // toggle popup open or closed */ togglePopup() { const popup = this._popup; diff --git a/src/ui/popup.js b/src/ui/popup.js index 8acb87e2192..2732eb82857 100644 --- a/src/ui/popup.js +++ b/src/ui/popup.js @@ -104,6 +104,15 @@ export default class Popup extends Evented { * * @param {Map} map The Mapbox GL JS map to add the popup to. * @returns {Popup} `this` + * @example + * new mapboxgl.Popup() + * .setLngLat([0, 0]) + * .setHTML("

Null Island

") + * .addTo(map); + * @see [Display a popup](https://docs.mapbox.com/mapbox-gl-js/example/popup/) + * @see [Display a popup on hover](https://docs.mapbox.com/mapbox-gl-js/example/popup-on-hover/) + * @see [Display a popup on click](https://docs.mapbox.com/mapbox-gl-js/example/popup-on-click/) + * @see [Show polygon information on click](https://docs.mapbox.com/mapbox-gl-js/example/polygon-popup-on-click/) */ addTo(map: Map) { if (this._map) this.remove(); @@ -139,6 +148,16 @@ export default class Popup extends Evented { * @instance * @type {Object} * @property {Popup} popup object that was opened + * + * @example + * // Create a popup + * var popup = new mapboxgl.Popup(); + * // Set an event listener that will fire + * // any time the popup is opened + * popup.on('open', function(){ + * console.log('popup was opened'); + * }); + * */ this.fire(new Event('open')); @@ -189,6 +208,16 @@ export default class Popup extends Evented { * @instance * @type {Object} * @property {Popup} popup object that was closed + * + * @example + * // Create a popup + * var popup = new mapboxgl.Popup(); + * // Set an event listener that will fire + * // any time the popup is closed + * popup.on('close', function(){ + * console.log('popup was closed'); + * }); + * */ this.fire(new Event('close')); @@ -235,8 +264,13 @@ export default class Popup extends Evented { } /** - * Tracks the popup anchor to the cursor position, on screens with a pointer device (will be hidden on touchscreens). Replaces the setLngLat behavior. - * For most use cases, `closeOnClick` and `closeButton` should also be set to `false` here. + * Tracks the popup anchor to the cursor position on screens with a pointer device (it will be hidden on touchscreens). Replaces the `setLngLat` behavior. + * For most use cases, set `closeOnClick` and `closeButton` to `false`. + * @example + * var popup = new mapboxgl.Popup({ closeOnClick: false, closeButton: false }) + * .setHTML("

Hello World!

") + * .trackPointer() + * .addTo(map); * @returns {Popup} `this` */ trackPointer() { @@ -259,6 +293,14 @@ export default class Popup extends Evented { /** * Returns the `Popup`'s HTML element. + * @example + * // Change the `Popup` element's font size + * var popup = new mapboxgl.Popup() + * .setLngLat([-96, 37.8]) + * .setHTML("

Hello World!

") + * .addTo(map); + * var popupElem = popup.getElement(); + * popupElem.style.fontSize = "25px"; * @returns {HTMLElement} element */ getElement() { @@ -293,6 +335,15 @@ export default class Popup extends Evented { * * @param html A string representing HTML content for the popup. * @returns {Popup} `this` + * @example + * var popup = new mapboxgl.Popup() + * .setLngLat(e.lngLat) + * .setHTML("

Hello World!

") + * .addTo(map); + * @see [Display a popup](https://docs.mapbox.com/mapbox-gl-js/example/popup/) + * @see [Display a popup on hover](https://docs.mapbox.com/mapbox-gl-js/example/popup-on-hover/) + * @see [Display a popup on click](https://docs.mapbox.com/mapbox-gl-js/example/popup-on-click/) + * @see [Attach a popup to a marker instance](https://docs.mapbox.com/mapbox-gl-js/example/set-popup/) */ setHTML(html: string) { const frag = window.document.createDocumentFragment(); diff --git a/src/util/ajax.js b/src/util/ajax.js index be730d0ed93..7c2d4445251 100644 --- a/src/util/ajax.js +++ b/src/util/ajax.js @@ -39,7 +39,23 @@ if (typeof Object.freeze == 'function') { * @typedef {Object} RequestParameters * @property {string} url The URL to be requested. * @property {Object} headers The headers to be sent with the request. + * @property {string} method Request method `'GET' | 'POST' | 'PUT'`. + * @property {string} body Request body. + * @property {string} type Response body type to be returned `'string' | 'json' | 'arrayBuffer'`. * @property {string} credentials `'same-origin'|'include'` Use 'include' to send cookies with cross-origin requests. + * @property {boolean} collectResourceTiming If true, Resource Timing API information will be collected for these transformed requests and returned in a resourceTiming property of relevant data events. + * @example + * // use transformRequest to modify requests that begin with `http://myHost` + * transformRequest: function(url, resourceType) { + * if (resourceType === 'Source' && url.indexOf('http://myHost') > -1) { + * return { + * url: url.replace('http', 'https'), + * headers: { 'my-custom-header': true }, + * credentials: 'include' // Include cookies for cross-origin requests + * } + * } + * } + * */ export type RequestParameters = { url: string, diff --git a/src/util/browser.js b/src/util/browser.js index 710841b0d13..808a6fcc5c4 100755 --- a/src/util/browser.js +++ b/src/util/browser.js @@ -54,7 +54,7 @@ const exported = { return linkEl.href; }, - hardwareConcurrency: window.navigator.hardwareConcurrency || 4, + hardwareConcurrency: window.navigator && window.navigator.hardwareConcurrency || 4, get devicePixelRatio() { return window.devicePixelRatio; }, get prefersReducedMotion(): boolean { diff --git a/src/util/browser/window.js b/src/util/browser/window.js index 7a525659dbb..d2ceea5613a 100644 --- a/src/util/browser/window.js +++ b/src/util/browser/window.js @@ -2,4 +2,5 @@ /* eslint-env browser */ import type {Window} from '../../types/window'; -export default (self: Window); +// shim window for the case of requiring the browser bundle in Node +export default typeof self !== 'undefined' ? (self: Window) : (({}: any): Window); diff --git a/src/util/debug.js b/src/util/debug.js index 0255172726f..e26672e7472 100644 --- a/src/util/debug.js +++ b/src/util/debug.js @@ -5,6 +5,8 @@ import window from './window'; /** * This is a private namespace for utility functions that will get automatically stripped * out in production builds. + * + * @private */ export const Debug = { extend(dest: Object, ...sources: Array): Object { diff --git a/src/util/dom.js b/src/util/dom.js index fd5d3e91498..34e2af64f8b 100644 --- a/src/util/dom.js +++ b/src/util/dom.js @@ -20,7 +20,7 @@ DOM.createNS = function (namespaceURI: string, tagName: string) { return el; }; -const docStyle = window.document.documentElement.style; +const docStyle = window.document && window.document.documentElement.style; function testProp(props) { if (!docStyle) return props[0]; diff --git a/test/build/min.test.js b/test/build/min.test.js index 5cdcda50406..649a893274c 100644 --- a/test/build/min.test.js +++ b/test/build/min.test.js @@ -36,6 +36,11 @@ test('can be browserified', (t) => { }); }); +test('evaluates without errors', (t) => { + t.doesNotThrow(() => require(path.join(__dirname, '../../dist/mapbox-gl.js'))); + t.end(); +}); + test('distributed in plain ES5 code', (t) => { const linter = new Linter(); const messages = linter.verify(minBundle, { diff --git a/test/ignores.json b/test/ignores.json index 025342d3e2f..a395277c56b 100644 --- a/test/ignores.json +++ b/test/ignores.json @@ -1,6 +1,7 @@ { "query-tests/regressions/mapbox-gl-js#4494": "https://github.com/mapbox/mapbox-gl-js/issues/2716", "render-tests/geojson/inline-linestring-fill": "current behavior is arbitrary", + "render-tests/line-dasharray/case/square": "https://github.com/mapbox/mapbox-gl-js/issues/9531", "render-tests/map-mode/static": "https://github.com/mapbox/mapbox-gl-js/issues/5649", "render-tests/map-mode/tile": "skip - mapbox-gl-js does not support tile-mode", "render-tests/map-mode/tile-avoid-edges": "skip - mapbox-gl-js does not support tile-mode", @@ -22,5 +23,8 @@ "render-tests/text-size/zero": "https://github.com/mapbox/mapbox-gl-js/issues/9161", "render-tests/text-variable-anchor/left-top-right-bottom-offset-tile-map-mode": "skip - mapbox-gl-js does not support tile-mode", "render-tests/tile-mode/streets-v11": "skip - mapbox-gl-js does not support tile-mode", - "render-tests/within/paint-line": "https://github.com/mapbox/mapbox-gl-js/issues/7023" + "render-tests/within/paint-line": "https://github.com/mapbox/mapbox-gl-js/issues/7023", + "render-tests/icon-text-fit/text-variable-anchor-tile-map-mode": "skip - mapbox-gl-js does not support tile-mode", + "render-tests/text-variable-anchor/all-anchors-labels-priority-tile-map-mode": "skip - mapbox-gl-js does not support tile-mode", + "render-tests/fill-extrusion-pattern/1.5x-on-1x-add-image": "skip - non-deterministic on AMD graphics cards" } diff --git a/test/integration/README.md b/test/integration/README.md index 77c43e037bf..79e570ef274 100644 --- a/test/integration/README.md +++ b/test/integration/README.md @@ -32,6 +32,12 @@ or yarn run test-query-node ``` +To run only the expression tests: + +``` +yarn run test-expressions +``` + ### Running specific tests To run a subset of tests or an individual test, you can pass a specific subdirectory to the `test-render` script. For example, to run all the tests for a given property, e.g. `circle-radius`: diff --git a/test/integration/expression-tests/literal/literal-false/test.json b/test/integration/expression-tests/literal/literal-false/test.json new file mode 100644 index 00000000000..9a6850c7744 --- /dev/null +++ b/test/integration/expression-tests/literal/literal-false/test.json @@ -0,0 +1,14 @@ +{ + "expression": ["literal", false], + "inputs": [[{}, {}]], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": true, + "isZoomConstant": true, + "type": "boolean" + }, + "outputs": [false], + "serialized": false + } +} diff --git a/test/integration/expression-tests/literal/literal-null/test.json b/test/integration/expression-tests/literal/literal-null/test.json new file mode 100644 index 00000000000..2d4ebfa598d --- /dev/null +++ b/test/integration/expression-tests/literal/literal-null/test.json @@ -0,0 +1,14 @@ +{ + "expression": ["literal", null], + "inputs": [[{}, {}]], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": true, + "isZoomConstant": true, + "type": "null" + }, + "outputs": [null], + "serialized": null + } +} diff --git a/test/integration/expression-tests/literal/literal-number/test.json b/test/integration/expression-tests/literal/literal-number/test.json new file mode 100644 index 00000000000..01c129ec58a --- /dev/null +++ b/test/integration/expression-tests/literal/literal-number/test.json @@ -0,0 +1,14 @@ +{ + "expression": ["literal", 7.5], + "inputs": [[{}, {}]], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": true, + "isZoomConstant": true, + "type": "number" + }, + "outputs": [7.5], + "serialized": 7.5 + } +} diff --git a/test/integration/expression-tests/literal/literal-string/test.json b/test/integration/expression-tests/literal/literal-string/test.json new file mode 100644 index 00000000000..ac6914326cb --- /dev/null +++ b/test/integration/expression-tests/literal/literal-string/test.json @@ -0,0 +1,14 @@ +{ + "expression": ["literal", "hello"], + "inputs": [[{}, {}]], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": true, + "isZoomConstant": true, + "type": "string" + }, + "outputs": ["hello"], + "serialized": "hello" + } +} diff --git a/test/integration/expression-tests/literal/literal-true/test.json b/test/integration/expression-tests/literal/literal-true/test.json new file mode 100644 index 00000000000..0e6d30e35ff --- /dev/null +++ b/test/integration/expression-tests/literal/literal-true/test.json @@ -0,0 +1,14 @@ +{ + "expression": ["literal", true], + "inputs": [[{}, {}]], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": true, + "isZoomConstant": true, + "type": "boolean" + }, + "outputs": [true], + "serialized": true + } +} diff --git a/test/integration/render-tests/icon-text-fit/text-variable-anchor-tile-map-mode/expected.png b/test/integration/render-tests/icon-text-fit/text-variable-anchor-tile-map-mode/expected.png new file mode 100644 index 00000000000..1017418a59b Binary files /dev/null and b/test/integration/render-tests/icon-text-fit/text-variable-anchor-tile-map-mode/expected.png differ diff --git a/test/integration/render-tests/icon-text-fit/text-variable-anchor-tile-map-mode/style.json b/test/integration/render-tests/icon-text-fit/text-variable-anchor-tile-map-mode/style.json new file mode 100644 index 00000000000..5cb9c085643 --- /dev/null +++ b/test/integration/render-tests/icon-text-fit/text-variable-anchor-tile-map-mode/style.json @@ -0,0 +1,60 @@ +{ + "version": 8, + "metadata": { + "test": { + "allowed": 0.0005, + "mapMode": "tile", + "debug": true + } + }, + "center": [ + 13.417, + 52.502 + ], + "zoom": 14, + "sources": { + "mapbox": { + "type": "vector", + "maxzoom": 14, + "tiles": [ + "local://tiles/{z}-{x}-{y}.mvt" + ] + } + }, + "sprite": "local://sprites/icon-text-fit", + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "road", + "type": "symbol", + "source": "mapbox", + "source-layer": "road_label", + "layout": { + "text-variable-anchor": ["left", "right", "bottom", "top"], + "text-field": "{name}", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "icon-image": "label", + "icon-text-fit": "both", + "icon-text-fit-padding": [ + 5, + 10, + 5, + 10 + ] + }, + "paint": { + "icon-opacity": 1 + } + } + ] +} diff --git a/test/integration/render-tests/line-dasharray/case/butt/expected.png b/test/integration/render-tests/line-dasharray/case/butt/expected.png new file mode 100644 index 00000000000..1b532d4b7be Binary files /dev/null and b/test/integration/render-tests/line-dasharray/case/butt/expected.png differ diff --git a/test/integration/render-tests/line-dasharray/case/butt/style.json b/test/integration/render-tests/line-dasharray/case/butt/style.json new file mode 100644 index 00000000000..add14b42983 --- /dev/null +++ b/test/integration/render-tests/line-dasharray/case/butt/style.json @@ -0,0 +1,70 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 200, + "width": 200 + } + }, + "center": [ + 0.0, + 0.0 + ], + "zoom": 15, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -0.001, + -0.001 + ], + [ + 0.001, + 0.001 + ] + ] + } + } + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "case", + "type": "line", + "source": "geojson", + "paint": { + "line-color": "blue", + "line-dasharray": [ + 1.666666, + 1.666666 + ], + "line-width": 12 + } + }, + { + "id": "dash", + "type": "line", + "source": "geojson", + "paint": { + "line-color": "red", + "line-dasharray": [ + 2.5, + 2.5 + ], + "line-width": 8 + } + } + ] +} diff --git a/test/integration/render-tests/line-dasharray/case/round/expected.png b/test/integration/render-tests/line-dasharray/case/round/expected.png new file mode 100644 index 00000000000..701bdce7e06 Binary files /dev/null and b/test/integration/render-tests/line-dasharray/case/round/expected.png differ diff --git a/test/integration/render-tests/line-dasharray/case/round/style.json b/test/integration/render-tests/line-dasharray/case/round/style.json new file mode 100644 index 00000000000..448eda173ad --- /dev/null +++ b/test/integration/render-tests/line-dasharray/case/round/style.json @@ -0,0 +1,76 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 200, + "width": 200 + } + }, + "center": [ + 0.0, + 0.0 + ], + "zoom": 15, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -0.001, + -0.001 + ], + [ + 0.001, + 0.001 + ] + ] + } + } + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "case", + "type": "line", + "source": "geojson", + "layout": { + "line-cap": "round" + }, + "paint": { + "line-color": "blue", + "line-dasharray": [ + 1.666666, + 1.666666 + ], + "line-width": 12 + } + }, + { + "id": "dash", + "type": "line", + "source": "geojson", + "layout": { + "line-cap": "round" + }, + "paint": { + "line-color": "red", + "line-dasharray": [ + 2.5, + 2.5 + ], + "line-width": 8 + } + } + ] +} diff --git a/test/integration/render-tests/line-dasharray/case/square/expected.png b/test/integration/render-tests/line-dasharray/case/square/expected.png new file mode 100644 index 00000000000..950a308bc81 Binary files /dev/null and b/test/integration/render-tests/line-dasharray/case/square/expected.png differ diff --git a/test/integration/render-tests/line-dasharray/case/square/style.json b/test/integration/render-tests/line-dasharray/case/square/style.json new file mode 100644 index 00000000000..65024b51f33 --- /dev/null +++ b/test/integration/render-tests/line-dasharray/case/square/style.json @@ -0,0 +1,76 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 200, + "width": 200 + } + }, + "center": [ + 0.0, + 0.0 + ], + "zoom": 15, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -0.001, + -0.001 + ], + [ + 0.001, + 0.001 + ] + ] + } + } + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "case", + "type": "line", + "source": "geojson", + "layout": { + "line-cap": "square" + }, + "paint": { + "line-color": "blue", + "line-dasharray": [ + 1.666666, + 1.666666 + ], + "line-width": 12 + } + }, + { + "id": "dash", + "type": "line", + "source": "geojson", + "layout": { + "line-cap": "square" + }, + "paint": { + "line-color": "red", + "line-dasharray": [ + 2.5, + 2.5 + ], + "line-width": 8 + } + } + ] +} diff --git a/test/integration/render-tests/regressions/mapbox-gl-js#9518/expected.png b/test/integration/render-tests/regressions/mapbox-gl-js#9518/expected.png new file mode 100644 index 00000000000..1ed60094c40 Binary files /dev/null and b/test/integration/render-tests/regressions/mapbox-gl-js#9518/expected.png differ diff --git a/test/integration/render-tests/regressions/mapbox-gl-js#9518/style.json b/test/integration/render-tests/regressions/mapbox-gl-js#9518/style.json new file mode 100644 index 00000000000..a7d66de5770 --- /dev/null +++ b/test/integration/render-tests/regressions/mapbox-gl-js#9518/style.json @@ -0,0 +1,27 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 8, + "height": 8 + } + }, + "zoom": 3, + "sources": {}, + "sprite": "local://sprites/emerald", + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-pattern": [ + "step", + ["zoom"], + "", + 5, + "cemetery_icon" + ] + } + } + ] +} diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-labels-priority-tile-map-mode/expected.png b/test/integration/render-tests/text-variable-anchor/all-anchors-labels-priority-tile-map-mode/expected.png new file mode 100644 index 00000000000..22bb9c87953 Binary files /dev/null and b/test/integration/render-tests/text-variable-anchor/all-anchors-labels-priority-tile-map-mode/expected.png differ diff --git a/test/integration/render-tests/text-variable-anchor/all-anchors-labels-priority-tile-map-mode/style.json b/test/integration/render-tests/text-variable-anchor/all-anchors-labels-priority-tile-map-mode/style.json new file mode 100644 index 00000000000..7fb8b4faf3b --- /dev/null +++ b/test/integration/render-tests/text-variable-anchor/all-anchors-labels-priority-tile-map-mode/style.json @@ -0,0 +1,99 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "collisionDebug": true, + "mapMode": "tile" + } + }, + "center": [ + 13.418056, + 52.499167 + ], + "zoom": 14, + "sources": { + "mapbox": { + "type": "vector", + "maxzoom": 14, + "tiles": [ + "local://tiles/{z}-{x}-{y}.mvt" + ] + } + }, + "sprite": "local://sprites/sprite", + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "low-priority", + "type": "symbol", + "source": "mapbox", + "source-layer": "poi_label", + "filter": [ + "==", + "maki", + "restaurant" + ], + "layout": { + "text-field": "Low", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-max-width": 5, + "text-justify": "auto", + "text-variable-anchor": [ + "center", + "top", + "bottom", + "left", + "right", + "top-left", + "top-right", + "bottom-left", + "bottom-right" + ] + }, + "paint": { + "text-color": "red" + } + }, + { + "id": "high-priority", + "type": "symbol", + "source": "mapbox", + "source-layer": "poi_label", + "filter": [ + "==", + "maki", + "restaurant" + ], + "layout": { + "text-field": "High", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-max-width": 5, + "text-justify": "auto", + "text-variable-anchor": [ + "center", + "top", + "bottom", + "left", + "right", + "top-left", + "top-right", + "bottom-left", + "bottom-right" + ] + } + }] +} diff --git a/test/integration/render-tests/within/within-in-complex-filter/expected.png b/test/integration/render-tests/within/within-in-complex-filter/expected.png new file mode 100644 index 00000000000..7f3f172cee2 Binary files /dev/null and b/test/integration/render-tests/within/within-in-complex-filter/expected.png differ diff --git a/test/integration/render-tests/within/within-in-complex-filter/style.json b/test/integration/render-tests/within/within-in-complex-filter/style.json new file mode 100644 index 00000000000..f6c69364438 --- /dev/null +++ b/test/integration/render-tests/within/within-in-complex-filter/style.json @@ -0,0 +1,102 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 64, + "height": 64 + } + }, + "zoom": 3, + "center": [2.5, 2.5], + "sources": { + "points": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "number": [5] + }, + "geometry": { + "type": "Point", + "coordinates": [ + 1.9775390625, + 2.3284603685731593 + ] + } + }, + { + "type": "Feature", + "properties": { + "number": [5] + }, + "geometry": { + "type": "Point", + "coordinates": [ + 1.7138671875, + -1.7136116598836224 + ] + } + } + ] + } + }, + "polygon": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [[0, 0], + [0, 5], + [5, 5], + [5, 0], + [0, 0]] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "border", + "type": "fill", + "source": "polygon", + "paint": { + "fill-color": "black", + "fill-opacity": 0.5 + } + }, + { + "id": "circle", + "type": "circle", + "source": "points", + "filter": ["all", ["in", 5, ["get", "number"]], ["==", ["within", { + "type": "Polygon", + "coordinates": [ + [ + [0, 0], + [0, 5], + [5, 5], + [5, 0], + [0, 0] + ] + ] + }], true] + ], + "paint": { + "circle-radius": 5, + "circle-color": "red" + } + } + ] +} diff --git a/test/unit/source/geojson_source.test.js b/test/unit/source/geojson_source.test.js index ee672172ae5..ba8328460a3 100644 --- a/test/unit/source/geojson_source.test.js +++ b/test/unit/source/geojson_source.test.js @@ -176,6 +176,7 @@ test('GeoJSONSource#update', (t) => { t.equal(message, 'geojson.loadData'); t.deepEqual(params.superclusterOptions, { maxZoom: 12, + minPoints: 3, extent: 8192, radius: 1600, log: false, @@ -190,6 +191,7 @@ test('GeoJSONSource#update', (t) => { cluster: true, clusterMaxZoom: 12, clusterRadius: 100, + clusterMinPoints: 3, generateId: true }, mockDispatcher).load(); }); diff --git a/test/unit/style-spec/expression.test.js b/test/unit/style-spec/expression.test.js index 7579190a657..0936eef3cae 100644 --- a/test/unit/style-spec/expression.test.js +++ b/test/unit/style-spec/expression.test.js @@ -11,7 +11,11 @@ const definitionList = Object.keys(definitions).filter((expression) => { test('v8.json includes all definitions from style-spec', (t) => { const v8List = Object.keys(v8.expression_name.values); - t.deepEquals(definitionList, v8List.sort()); + const v8SupportedList = v8List.filter((expression) => { + //filter out expressions that are not supported in GL-JS + return !!v8.expression_name.values[expression]["sdk-support"]["basic functionality"]["js"]; + }); + t.deepEquals(definitionList, v8SupportedList.sort()); t.end(); }); diff --git a/test/unit/symbol/format_section_override.test.js b/test/unit/style/format_section_override.test.js similarity index 95% rename from test/unit/symbol/format_section_override.test.js rename to test/unit/style/format_section_override.test.js index 3364ba2fb06..292f9374893 100644 --- a/test/unit/symbol/format_section_override.test.js +++ b/test/unit/style/format_section_override.test.js @@ -3,7 +3,7 @@ import {createExpression, ZoomConstantExpression} from '../../../src/style-spec/ import EvaluationContext from '../../../src/style-spec/expression/evaluation_context'; import properties from '../../../src/style/style_layer/symbol_style_layer_properties'; import {PossiblyEvaluatedPropertyValue} from '../../../src/style/properties'; -import FormatSectionOverride from '../../../src/style-spec/expression/definitions/format_section_override'; +import FormatSectionOverride from '../../../src/style/format_section_override'; test('evaluate', (t) => { diff --git a/test/unit/style/style_layer.test.js b/test/unit/style/style_layer.test.js index 9152dc82f34..8d682f164ad 100644 --- a/test/unit/style/style_layer.test.js +++ b/test/unit/style/style_layer.test.js @@ -403,5 +403,12 @@ test('StyleLayer#serialize', (t) => { t.end(); }); + t.test('layer.paint is never undefined', (t) => { + const layer = createStyleLayer({type: 'fill'}); + // paint is never undefined + t.ok(layer.paint); + t.end(); + }); + t.end(); }); diff --git a/test/unit/symbol/symbol_style_layer.test.js b/test/unit/symbol/symbol_style_layer.test.js index 34ca2755960..982d912c996 100644 --- a/test/unit/symbol/symbol_style_layer.test.js +++ b/test/unit/symbol/symbol_style_layer.test.js @@ -1,6 +1,6 @@ import {test} from '../../util/test'; import SymbolStyleLayer from '../../../src/style/style_layer/symbol_style_layer'; -import FormatSectionOverride from '../../../src/style-spec/expression/definitions/format_section_override'; +import FormatSectionOverride from '../../../src/style/format_section_override'; import properties from '../../../src/style/style_layer/symbol_style_layer_properties'; function createSymbolLayer(layerProperties) { diff --git a/test/unit/ui/handler/dblclick_zoom.test.js b/test/unit/ui/handler/dblclick_zoom.test.js index a3adb4a63f4..737c7b86cf6 100644 --- a/test/unit/ui/handler/dblclick_zoom.test.js +++ b/test/unit/ui/handler/dblclick_zoom.test.js @@ -12,10 +12,10 @@ function createMap(t) { function simulateDoubleTap(map, delay = 100) { const canvas = map.getCanvas(); return new Promise(resolve => { - simulate.touchstart(canvas, {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(canvas, {touches: [{target: canvas, clientX: 0, clientY: 0}]}); simulate.touchend(canvas); setTimeout(() => { - simulate.touchstart(canvas, {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(canvas, {touches: [{target: canvas, clientX: 0, clientY: 0}]}); simulate.touchend(canvas); map._renderTaskQueue.run(); resolve(); @@ -122,7 +122,7 @@ test('DoubleClickZoomHandler zooms on the second touchend event of a double tap' map.on('zoomstart', zoom); const canvas = map.getCanvas(); - const touchOptions = {targetTouches: [{clientX: 0.5, clientY: 0.5}]}; + const touchOptions = {touches: [{target: canvas, clientX: 0.5, clientY: 0.5}]}; simulate.touchstart(canvas, touchOptions); simulate.touchend(canvas); diff --git a/test/unit/ui/handler/drag_pan.test.js b/test/unit/ui/handler/drag_pan.test.js index 971c981186c..785efaf488e 100644 --- a/test/unit/ui/handler/drag_pan.test.js +++ b/test/unit/ui/handler/drag_pan.test.js @@ -13,6 +13,9 @@ function createMap(t, clickTolerance, dragPan) { }); } +// MouseEvent.buttons = 1 // left button +const buttons = 1; + test('DragPanHandler fires dragstart, drag, and dragend events at appropriate times in response to a mouse-triggered drag', (t) => { const map = createMap(t); @@ -30,7 +33,7 @@ test('DragPanHandler fires dragstart, drag, and dragend events at appropriate ti t.equal(drag.callCount, 0); t.equal(dragend.callCount, 0); - simulate.mousemove(map.getCanvas(), {clientX: 10, clientY: 10}); + simulate.mousemove(map.getCanvas(), {buttons, clientX: 10, clientY: 10}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 1); t.equal(drag.callCount, 1); @@ -63,7 +66,7 @@ test('DragPanHandler captures mousemove events during a mouse-triggered drag (re t.equal(drag.callCount, 0); t.equal(dragend.callCount, 0); - simulate.mousemove(window.document.body, {clientX: 10, clientY: 10}); + simulate.mousemove(window.document.body, {buttons, clientX: 10, clientY: 10}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 1); t.equal(drag.callCount, 1); @@ -81,6 +84,7 @@ test('DragPanHandler captures mousemove events during a mouse-triggered drag (re test('DragPanHandler fires dragstart, drag, and dragend events at appropriate times in response to a touch-triggered drag', (t) => { const map = createMap(t); + const target = map.getCanvas(); const dragstart = t.spy(); const drag = t.spy(); @@ -90,13 +94,13 @@ test('DragPanHandler fires dragstart, drag, and dragend events at appropriate ti map.on('drag', drag); map.on('dragend', dragend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}]}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 0); t.equal(drag.callCount, 0); t.equal(dragend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{clientX: 10, clientY: 10}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, clientX: 10, clientY: 10}]}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 1); t.equal(drag.callCount, 1); @@ -121,7 +125,7 @@ test('DragPanHandler prevents mousemove events from firing during a drag (#1555) simulate.mousedown(map.getCanvasContainer()); map._renderTaskQueue.run(); - simulate.mousemove(map.getCanvasContainer(), {clientX: 10, clientY: 10}); + simulate.mousemove(map.getCanvasContainer(), {buttons, clientX: 10, clientY: 10}); map._renderTaskQueue.run(); simulate.mouseup(map.getCanvasContainer()); @@ -142,7 +146,7 @@ test('DragPanHandler ends a mouse-triggered drag if the window blurs', (t) => { simulate.mousedown(map.getCanvas()); map._renderTaskQueue.run(); - simulate.mousemove(map.getCanvas(), {clientX: 10, clientY: 10}); + simulate.mousemove(map.getCanvas(), {buttons, clientX: 10, clientY: 10}); map._renderTaskQueue.run(); simulate.blur(window); @@ -154,14 +158,15 @@ test('DragPanHandler ends a mouse-triggered drag if the window blurs', (t) => { test('DragPanHandler ends a touch-triggered drag if the window blurs', (t) => { const map = createMap(t); + const target = map.getCanvas(); const dragend = t.spy(); map.on('dragend', dragend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}]}); map._renderTaskQueue.run(); - simulate.touchmove(map.getCanvas(), {targetTouches: [{clientX: 10, clientY: 10}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, clientX: 10, clientY: 10}]}); map._renderTaskQueue.run(); simulate.blur(window); @@ -176,14 +181,14 @@ test('DragPanHandler requests a new render frame after each mousemove event', (t const requestFrame = t.spy(map, '_requestRenderFrame'); simulate.mousedown(map.getCanvas()); - simulate.mousemove(map.getCanvas(), {clientX: 10, clientY: 10}); + simulate.mousemove(map.getCanvas(), {buttons, clientX: 10, clientY: 10}); t.ok(requestFrame.callCount > 0); map._renderTaskQueue.run(); // https://github.com/mapbox/mapbox-gl-js/issues/6063 requestFrame.resetHistory(); - simulate.mousemove(map.getCanvas(), {clientX: 20, clientY: 20}); + simulate.mousemove(map.getCanvas(), {buttons, clientX: 20, clientY: 20}); t.equal(requestFrame.callCount, 1); map.remove(); @@ -208,7 +213,7 @@ test('DragPanHandler can interleave with another handler', (t) => { t.equal(drag.callCount, 0); t.equal(dragend.callCount, 0); - simulate.mousemove(map.getCanvas(), {clientX: 10, clientY: 10}); + simulate.mousemove(map.getCanvas(), {buttons, clientX: 10, clientY: 10}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 1); t.equal(drag.callCount, 1); @@ -221,7 +226,7 @@ test('DragPanHandler can interleave with another handler', (t) => { t.equal(drag.callCount, 1); t.equal(dragend.callCount, 0); - simulate.mousemove(map.getCanvas(), {clientX: 20, clientY: 20}); + simulate.mousemove(map.getCanvas(), {buttons, clientX: 20, clientY: 20}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 1); t.equal(drag.callCount, 2); @@ -250,13 +255,13 @@ test('DragPanHandler can interleave with another handler', (t) => { map.on('drag', drag); map.on('dragend', dragend); - simulate.mousedown(map.getCanvas(), {[`${modifier}Key`]: true}); + simulate.mousedown(map.getCanvas(), {buttons, [`${modifier}Key`]: true}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 0); t.equal(drag.callCount, 0); t.equal(dragend.callCount, 0); - simulate.mousemove(map.getCanvas(), {[`${modifier}Key`]: true, clientX: 10, clientY: 10}); + simulate.mousemove(map.getCanvas(), {buttons, [`${modifier}Key`]: true, clientX: 10, clientY: 10}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 0); t.equal(drag.callCount, 0); @@ -296,7 +301,7 @@ test('DragPanHandler can interleave with another handler', (t) => { t.equal(drag.callCount, 0); t.equal(dragend.callCount, 0); - simulate.mousemove(map.getCanvas(), {clientX: 10, clientY: 10}); + simulate.mousemove(map.getCanvas(), {buttons, clientX: 10, clientY: 10}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 0); t.equal(drag.callCount, 0); @@ -359,25 +364,25 @@ test('DragPanHandler does not end a drag on right button mouseup', (t) => { t.equal(drag.callCount, 0); t.equal(dragend.callCount, 0); - simulate.mousemove(map.getCanvas(), {clientX: 10, clientY: 10}); + simulate.mousemove(map.getCanvas(), {buttons, clientX: 10, clientY: 10}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 1); t.equal(drag.callCount, 1); t.equal(dragend.callCount, 0); - simulate.mousedown(map.getCanvas(), {buttons: 2, button: 2}); + simulate.mousedown(map.getCanvas(), {buttons: buttons + 2, button: 2}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 1); t.equal(drag.callCount, 1); t.equal(dragend.callCount, 0); - simulate.mouseup(map.getCanvas(), {buttons: 0, button: 2}); + simulate.mouseup(map.getCanvas(), {buttons, button: 2}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 1); t.equal(drag.callCount, 1); t.equal(dragend.callCount, 0); - simulate.mousemove(map.getCanvas(), {clientX: 20, clientY: 20}); + simulate.mousemove(map.getCanvas(), {buttons, clientX: 20, clientY: 20}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 1); t.equal(drag.callCount, 2); @@ -409,7 +414,7 @@ test('DragPanHandler does not begin a drag if preventDefault is called on the mo simulate.mousedown(map.getCanvas()); map._renderTaskQueue.run(); - simulate.mousemove(map.getCanvas(), {clientX: 10, clientY: 10}); + simulate.mousemove(map.getCanvas(), {buttons, clientX: 10, clientY: 10}); map._renderTaskQueue.run(); simulate.mouseup(map.getCanvas()); @@ -425,6 +430,7 @@ test('DragPanHandler does not begin a drag if preventDefault is called on the mo test('DragPanHandler does not begin a drag if preventDefault is called on the touchstart event', (t) => { const map = createMap(t); + const target = map.getCanvas(); map.on('touchstart', e => e.preventDefault()); @@ -436,10 +442,10 @@ test('DragPanHandler does not begin a drag if preventDefault is called on the to map.on('drag', drag); map.on('dragend', dragend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}]}); map._renderTaskQueue.run(); - simulate.touchmove(map.getCanvas(), {targetTouches: [{clientX: 10, clientY: 10}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, clientX: 10, clientY: 10}]}); map._renderTaskQueue.run(); simulate.touchend(map.getCanvas()); @@ -455,6 +461,7 @@ test('DragPanHandler does not begin a drag if preventDefault is called on the to test('DragPanHandler does not begin a drag if preventDefault is called on the touchstart event (delegated)', (t) => { const map = createMap(t); + const target = map.getCanvas(); t.stub(map, 'getLayer') .callsFake(() => true); @@ -473,10 +480,10 @@ test('DragPanHandler does not begin a drag if preventDefault is called on the to map.on('drag', drag); map.on('dragend', dragend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}]}); map._renderTaskQueue.run(); - simulate.touchmove(map.getCanvas(), {targetTouches: [{clientX: 10, clientY: 10}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, clientX: 10, clientY: 10}]}); map._renderTaskQueue.run(); simulate.touchend(map.getCanvas()); diff --git a/test/unit/ui/handler/drag_rotate.test.js b/test/unit/ui/handler/drag_rotate.test.js index 84235f1d20c..25da019b54b 100644 --- a/test/unit/ui/handler/drag_rotate.test.js +++ b/test/unit/ui/handler/drag_rotate.test.js @@ -11,6 +11,30 @@ function createMap(t, options) { return new Map(extend({container: DOM.create('div', '', window.document.body)}, options)); } +test('DragRotateHandler#isActive', (t) => { + const map = createMap(t); + + // Prevent inertial rotation. + t.stub(browser, 'now').returns(0); + + t.equal(map.dragRotate.isActive(), false); + + simulate.mousedown(map.getCanvas(), {buttons: 2, button: 2}); + map._renderTaskQueue.run(); + t.equal(map.dragRotate.isActive(), false); + + simulate.mousemove(map.getCanvas(), {buttons: 2, clientX: 10, clientY: 10}); + map._renderTaskQueue.run(); + t.equal(map.dragRotate.isActive(), true); + + simulate.mouseup(map.getCanvas(), {buttons: 0, button: 2}); + map._renderTaskQueue.run(); + t.equal(map.dragRotate.isActive(), false); + + map.remove(); + t.end(); +}); + test('DragRotateHandler fires rotatestart, rotate, and rotateend events at appropriate times in response to a right-click drag', (t) => { const map = createMap(t); @@ -249,9 +273,11 @@ test('DragRotateHandler ensures that map.isMoving() returns true during drag', ( simulate.mousedown(map.getCanvas(), {buttons: 2, button: 2}); simulate.mousemove(map.getCanvas(), {buttons: 2, clientX: 10, clientY: 10}); + map._renderTaskQueue.run(); t.ok(map.isMoving()); simulate.mouseup(map.getCanvas(), {buttons: 0, button: 2}); + map._renderTaskQueue.run(); t.ok(!map.isMoving()); map.remove(); diff --git a/test/unit/ui/handler/map_event.test.js b/test/unit/ui/handler/map_event.test.js new file mode 100644 index 00000000000..d99a496358b --- /dev/null +++ b/test/unit/ui/handler/map_event.test.js @@ -0,0 +1,92 @@ +import {test} from '../../../util/test'; +import window from '../../../../src/util/window'; +import Map from '../../../../src/ui/map'; +import DOM from '../../../../src/util/dom'; +import simulate from '../../../util/simulate_interaction'; + +function createMap(t) { + t.stub(Map.prototype, '_detectMissingCSS'); + return new Map({interactive: false, container: DOM.create('div', '', window.document.body)}); +} + +test('MapEvent handler fires touch events with correct values', (t) => { + const map = createMap(t); + const target = map.getCanvas(); + + const touchstart = t.spy(); + const touchmove = t.spy(); + const touchend = t.spy(); + + map.on('touchstart', touchstart); + map.on('touchmove', touchmove); + map.on('touchend', touchend); + + const touchesStart = [{target, identifier: 1, clientX: 0, clientY: 50}]; + const touchesMove = [{target, identifier: 1, clientX: 0, clientY: 60}]; + const touchesEnd = [{target, identifier: 1, clientX: 0, clientY: 60}]; + + simulate.touchstart(map.getCanvas(), {touches: touchesStart, targetTouches: touchesStart}); + t.equal(touchstart.callCount, 1); + t.deepEqual(touchstart.getCall(0).args[0].point, {x: 0, y: 50}); + t.equal(touchmove.callCount, 0); + t.equal(touchend.callCount, 0); + + simulate.touchmove(map.getCanvas(), {touches: touchesMove, targetTouches: touchesMove}); + t.equal(touchstart.callCount, 1); + t.equal(touchmove.callCount, 1); + t.deepEqual(touchmove.getCall(0).args[0].point, {x: 0, y: 60}); + t.equal(touchend.callCount, 0); + + simulate.touchend(map.getCanvas(), {touches: [], targetTouches: [], changedTouches: touchesEnd}); + t.equal(touchstart.callCount, 1); + t.equal(touchmove.callCount, 1); + t.equal(touchend.callCount, 1); + t.deepEqual(touchend.getCall(0).args[0].point, {x: 0, y: 60}); + + map.remove(); + t.end(); +}); + +test('MapEvent handler fires touchmove even while drag handler is active', (t) => { + const map = createMap(t); + const target = map.getCanvas(); + map.dragPan.enable(); + + const touchstart = t.spy(); + const touchmove = t.spy(); + const touchend = t.spy(); + const drag = t.spy(); + + map.on('touchstart', touchstart); + map.on('touchmove', touchmove); + map.on('touchend', touchend); + map.on('drag', drag); + + const touchesStart = [{target, identifier: 1, clientX: 0, clientY: 50}]; + const touchesMove = [{target, identifier: 1, clientX: 0, clientY: 60}]; + const touchesEnd = [{target, identifier: 1, clientX: 0, clientY: 60}]; + + simulate.touchstart(map.getCanvas(), {touches: touchesStart, targetTouches: touchesStart}); + t.equal(touchstart.callCount, 1); + t.deepEqual(touchstart.getCall(0).args[0].point, {x: 0, y: 50}); + t.equal(touchmove.callCount, 0); + t.equal(touchend.callCount, 0); + + simulate.touchmove(map.getCanvas(), {touches: touchesMove, targetTouches: touchesMove}); + t.equal(touchstart.callCount, 1); + t.equal(touchmove.callCount, 1); + t.deepEqual(touchmove.getCall(0).args[0].point, {x: 0, y: 60}); + t.equal(touchend.callCount, 0); + + simulate.touchend(map.getCanvas(), {touches: [], targetTouches: [], changedTouches: touchesEnd}); + t.equal(touchstart.callCount, 1); + t.equal(touchmove.callCount, 1); + t.equal(touchend.callCount, 1); + t.deepEqual(touchend.getCall(0).args[0].point, {x: 0, y: 60}); + + map._renderTaskQueue.run(); + t.equal(drag.callCount, 1); + + map.remove(); + t.end(); +}); diff --git a/test/unit/ui/handler/mouse_rotate.test.js b/test/unit/ui/handler/mouse_rotate.test.js new file mode 100644 index 00000000000..0a587b7a0dd --- /dev/null +++ b/test/unit/ui/handler/mouse_rotate.test.js @@ -0,0 +1,60 @@ +import {test} from '../../../util/test'; +import {extend} from '../../../../src/util/util'; +import window from '../../../../src/util/window'; +import Map from '../../../../src/ui/map'; +import DOM from '../../../../src/util/dom'; +import simulate from '../../../util/simulate_interaction'; +import browser from '../../../../src/util/browser'; + +function createMap(t, options) { + t.stub(Map.prototype, '_detectMissingCSS'); + return new Map(extend({container: DOM.create('div', '', window.document.body)}, options)); +} + +test('MouseRotateHandler#isActive', (t) => { + const map = createMap(t); + const mouseRotate = map.handlers._handlersById.mouseRotate; + + // Prevent inertial rotation. + t.stub(browser, 'now').returns(0); + t.equal(mouseRotate.isActive(), false); + + simulate.mousedown(map.getCanvas(), {buttons: 2, button: 2}); + map._renderTaskQueue.run(); + t.equal(mouseRotate.isActive(), false); + + simulate.mousemove(map.getCanvas(), {buttons: 2, clientX: 10, clientY: 10}); + map._renderTaskQueue.run(); + t.equal(mouseRotate.isActive(), true); + + simulate.mouseup(map.getCanvas(), {buttons: 0, button: 2}); + map._renderTaskQueue.run(); + t.equal(mouseRotate.isActive(), false); + + map.remove(); + t.end(); +}); + +test('MouseRotateHandler#isActive #4622 regression test', (t) => { + const map = createMap(t); + const mouseRotate = map.handlers._handlersById.mouseRotate; + + // Prevent inertial rotation. + simulate.mousedown(map.getCanvas(), {buttons: 2, button: 2}); + map._renderTaskQueue.run(); + t.equal(mouseRotate.isActive(), false); + + simulate.mousemove(map.getCanvas(), {buttons: 2, clientX: 10, clientY: 10}); + map._renderTaskQueue.run(); + t.equal(mouseRotate.isActive(), true); + + // Some browsers don't fire mouseup when it happens outside the window. + // Make the handler in active when it encounters a mousemove without the button pressed. + + simulate.mousemove(map.getCanvas(), {buttons: 0, clientX: 10, clientY: 10}); + map._renderTaskQueue.run(); + t.equal(mouseRotate.isActive(), false); + + map.remove(); + t.end(); +}); diff --git a/test/unit/ui/handler/touch_zoom_rotate.test.js b/test/unit/ui/handler/touch_zoom_rotate.test.js index a70ce438c4f..5028aed6c0c 100644 --- a/test/unit/ui/handler/touch_zoom_rotate.test.js +++ b/test/unit/ui/handler/touch_zoom_rotate.test.js @@ -1,6 +1,7 @@ import {test} from '../../../util/test'; import window from '../../../../src/util/window'; import Map from '../../../../src/ui/map'; +import Marker from '../../../../src/ui/marker'; import DOM from '../../../../src/util/dom'; import simulate from '../../../util/simulate_interaction'; @@ -11,6 +12,7 @@ function createMap(t) { test('TouchZoomRotateHandler fires zoomstart, zoom, and zoomend events at appropriate times in response to a pinch-zoom gesture', (t) => { const map = createMap(t); + const target = map.getCanvas(); const zoomstart = t.spy(); const zoom = t.spy(); @@ -22,25 +24,25 @@ test('TouchZoomRotateHandler fires zoomstart, zoom, and zoomend events at approp map.on('zoom', zoom); map.on('zoomend', zoomend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{identifier: 1, clientX: 0, clientY: -50}, {identifier: 2, clientX: 0, clientY: 50}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, identifier: 1, clientX: 0, clientY: -50}, {target, identifier: 2, clientX: 0, clientY: 50}]}); map._renderTaskQueue.run(); t.equal(zoomstart.callCount, 0); t.equal(zoom.callCount, 0); t.equal(zoomend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{identifier: 1, clientX: 0, clientY: -100}, {identifier: 2, clientX: 0, clientY: 100}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 1, clientX: 0, clientY: -100}, {target, identifier: 2, clientX: 0, clientY: 100}]}); map._renderTaskQueue.run(); t.equal(zoomstart.callCount, 1); t.equal(zoom.callCount, 1); t.equal(zoomend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{identifier: 1, clientX: 0, clientY: -60}, {identifier: 2, clientX: 0, clientY: 60}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 1, clientX: 0, clientY: -60}, {target, identifier: 2, clientX: 0, clientY: 60}]}); map._renderTaskQueue.run(); t.equal(zoomstart.callCount, 1); t.equal(zoom.callCount, 2); t.equal(zoomend.callCount, 0); - simulate.touchend(map.getCanvas(), {targetTouches: []}); + simulate.touchend(map.getCanvas(), {touches: []}); map._renderTaskQueue.run(); // incremented because inertia starts a second zoom @@ -55,6 +57,7 @@ test('TouchZoomRotateHandler fires zoomstart, zoom, and zoomend events at approp test('TouchZoomRotateHandler fires rotatestart, rotate, and rotateend events at appropriate times in response to a pinch-rotate gesture', (t) => { const map = createMap(t); + const target = map.getCanvas(); const rotatestart = t.spy(); const rotate = t.spy(); @@ -64,25 +67,25 @@ test('TouchZoomRotateHandler fires rotatestart, rotate, and rotateend events at map.on('rotate', rotate); map.on('rotateend', rotateend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{identifier: 0, clientX: 0, clientY: -50}, {identifier: 1, clientX: 0, clientY: 50}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, identifier: 0, clientX: 0, clientY: -50}, {target, identifier: 1, clientX: 0, clientY: 50}]}); map._renderTaskQueue.run(); t.equal(rotatestart.callCount, 0); t.equal(rotate.callCount, 0); t.equal(rotateend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{identifier: 0, clientX: -50, clientY: 0}, {identifier: 1, clientX: 50, clientY: 0}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 0, clientX: -50, clientY: 0}, {target, identifier: 1, clientX: 50, clientY: 0}]}); map._renderTaskQueue.run(); t.equal(rotatestart.callCount, 1); t.equal(rotate.callCount, 1); t.equal(rotateend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{identifier: 0, clientX: 0, clientY: -50}, {identifier: 1, clientX: 0, clientY: 50}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 0, clientX: 0, clientY: -50}, {target, identifier: 1, clientX: 0, clientY: 50}]}); map._renderTaskQueue.run(); t.equal(rotatestart.callCount, 1); t.equal(rotate.callCount, 2); t.equal(rotateend.callCount, 0); - simulate.touchend(map.getCanvas(), {targetTouches: []}); + simulate.touchend(map.getCanvas(), {touches: []}); map._renderTaskQueue.run(); t.equal(rotatestart.callCount, 1); t.equal(rotate.callCount, 2); @@ -94,19 +97,20 @@ test('TouchZoomRotateHandler fires rotatestart, rotate, and rotateend events at test('TouchZoomRotateHandler does not begin a gesture if preventDefault is called on the touchstart event', (t) => { const map = createMap(t); + const target = map.getCanvas(); map.on('touchstart', e => e.preventDefault()); const move = t.spy(); map.on('move', move); - simulate.touchstart(map.getCanvas(), {targetTouches: [{clientX: 0, clientY: 0}, {clientX: 5, clientY: 0}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}, {target, clientX: 5, clientY: 0}]}); map._renderTaskQueue.run(); - simulate.touchmove(map.getCanvas(), {targetTouches: [{clientX: 0, clientY: 0}, {clientX: 0, clientY: 5}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}, {target, clientX: 0, clientY: 5}]}); map._renderTaskQueue.run(); - simulate.touchend(map.getCanvas(), {targetTouches: []}); + simulate.touchend(map.getCanvas(), {touches: []}); map._renderTaskQueue.run(); t.equal(move.callCount, 0); @@ -117,6 +121,7 @@ test('TouchZoomRotateHandler does not begin a gesture if preventDefault is calle test('TouchZoomRotateHandler starts zoom immediately when rotation disabled', (t) => { const map = createMap(t); + const target = map.getCanvas(); map.touchZoomRotate.disableRotation(); map.handlers._handlersById.tapZoom.disable(); @@ -128,25 +133,25 @@ test('TouchZoomRotateHandler starts zoom immediately when rotation disabled', (t map.on('zoom', zoom); map.on('zoomend', zoomend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{identifier: 0, clientX: 0, clientY: -5}, {identifier: 2, clientX: 0, clientY: 5}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, identifier: 0, clientX: 0, clientY: -5}, {target, identifier: 2, clientX: 0, clientY: 5}]}); map._renderTaskQueue.run(); t.equal(zoomstart.callCount, 0); t.equal(zoom.callCount, 0); t.equal(zoomend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{identifier: 0, clientX: 0, clientY: -5}, {identifier: 2, clientX: 0, clientY: 6}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 0, clientX: 0, clientY: -5}, {target, identifier: 2, clientX: 0, clientY: 6}]}); map._renderTaskQueue.run(); t.equal(zoomstart.callCount, 1); t.equal(zoom.callCount, 1); t.equal(zoomend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{identifier: 0, clientX: 0, clientY: -5}, {identifier: 2, clientX: 0, clientY: 4}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 0, clientX: 0, clientY: -5}, {target, identifier: 2, clientX: 0, clientY: 4}]}); map._renderTaskQueue.run(); t.equal(zoomstart.callCount, 1); t.equal(zoom.callCount, 2); t.equal(zoomend.callCount, 0); - simulate.touchend(map.getCanvas(), {targetTouches: []}); + simulate.touchend(map.getCanvas(), {touches: []}); map._renderTaskQueue.run(); // incremented because inertia starts a second zoom t.equal(zoomstart.callCount, 2); @@ -169,3 +174,113 @@ test('TouchZoomRotateHandler adds css class used for disabling default touch beh t.ok(map.getCanvasContainer().classList.contains(className)); t.end(); }); + +test('TouchZoomRotateHandler zooms when touching two markers on the same map', (t) => { + const map = createMap(t); + + const marker1 = new Marker() + .setLngLat([0, 0]) + .addTo(map); + const marker2 = new Marker() + .setLngLat([0, 0]) + .addTo(map); + const target1 = marker1.getElement(); + const target2 = marker2.getElement(); + + const zoomstart = t.spy(); + const zoom = t.spy(); + const zoomend = t.spy(); + + map.handlers._handlersById.tapZoom.disable(); + map.touchPitch.disable(); + map.on('zoomstart', zoomstart); + map.on('zoom', zoom); + map.on('zoomend', zoomend); + + simulate.touchstart(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -50}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -50}, {target: target2, identifier: 2, clientX: 0, clientY: 50}]}); + map._renderTaskQueue.run(); + t.equal(zoomstart.callCount, 0); + t.equal(zoom.callCount, 0); + t.equal(zoomend.callCount, 0); + + simulate.touchmove(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -100}, {target: target2, identifier: 2, clientX: 0, clientY: 100}]}); + map._renderTaskQueue.run(); + t.equal(zoomstart.callCount, 1); + t.equal(zoom.callCount, 1); + t.equal(zoomend.callCount, 0); + + simulate.touchmove(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -60}, {target: target2, identifier: 2, clientX: 0, clientY: 60}]}); + map._renderTaskQueue.run(); + t.equal(zoomstart.callCount, 1); + t.equal(zoom.callCount, 2); + t.equal(zoomend.callCount, 0); + + simulate.touchend(map.getCanvas(), {touches: []}); + map._renderTaskQueue.run(); + + // incremented because inertia starts a second zoom + t.equal(zoomstart.callCount, 2); + map._renderTaskQueue.run(); + t.equal(zoom.callCount, 3); + t.equal(zoomend.callCount, 1); + + map.remove(); + t.end(); +}); + +test('TouchZoomRotateHandler does not zoom when touching an element not on the map', (t) => { + const map = createMap(t); + + const marker1 = new Marker() + .setLngLat([0, 0]) + .addTo(map); + const marker2 = new Marker() + .setLngLat([0, 0]); + + const target1 = marker1.getElement(); // on map + const target2 = marker2.getElement(); // not on map + + const zoomstart = t.spy(); + const zoom = t.spy(); + const zoomend = t.spy(); + + map.handlers._handlersById.tapZoom.disable(); + map.touchPitch.disable(); + map.dragPan.disable(); + map.on('zoomstart', zoomstart); + map.on('zoom', zoom); + map.on('zoomend', zoomend); + + simulate.touchstart(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -50}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -50}, {target: target2, identifier: 2, clientX: 0, clientY: 50}]}); + map._renderTaskQueue.run(); + t.equal(zoomstart.callCount, 0); + t.equal(zoom.callCount, 0); + t.equal(zoomend.callCount, 0); + + simulate.touchmove(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -100}, {target: target2, identifier: 2, clientX: 0, clientY: 100}]}); + map._renderTaskQueue.run(); + t.equal(zoomstart.callCount, 0); + t.equal(zoom.callCount, 0); + t.equal(zoomend.callCount, 0); + + simulate.touchmove(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -60}, {target: target2, identifier: 2, clientX: 0, clientY: 60}]}); + map._renderTaskQueue.run(); + t.equal(zoomstart.callCount, 0); + t.equal(zoom.callCount, 0); + t.equal(zoomend.callCount, 0); + + simulate.touchend(map.getCanvas(), {touches: []}); + map._renderTaskQueue.run(); + + // incremented because inertia starts a second zoom + t.equal(zoomstart.callCount, 0); + map._renderTaskQueue.run(); + t.equal(zoom.callCount, 0); + t.equal(zoomend.callCount, 0); + + map.remove(); + t.end(); +}); + diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js index 6e312951bd7..dedddf15f67 100755 --- a/test/unit/ui/map.test.js +++ b/test/unit/ui/map.test.js @@ -1999,7 +1999,7 @@ test('Map', (t) => { const map = createMap(t, {interactive: true}); map.flyTo({center: [200, 0], duration: 100}); - simulate.touchstart(map.getCanvasContainer(), {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(map.getCanvasContainer(), {touches: [{target: map.getCanvas(), clientX: 0, clientY: 0}]}); t.equal(map.isEasing(), false); map.remove(); diff --git a/test/unit/ui/map/isMoving.test.js b/test/unit/ui/map/isMoving.test.js index ffc7d1ad324..f215bb2ddc3 100644 --- a/test/unit/ui/map/isMoving.test.js +++ b/test/unit/ui/map/isMoving.test.js @@ -10,6 +10,9 @@ function createMap(t) { return new Map({container: DOM.create('div', '', window.document.body)}); } +// MouseEvent.buttons +const buttons = 1; + test('Map#isMoving returns false by default', (t) => { const map = createMap(t); t.equal(map.isMoving(), false); @@ -36,12 +39,18 @@ test('Map#isMoving returns true during a camera zoom animation', (t) => { test('Map#isMoving returns true when drag panning', (t) => { const map = createMap(t); + map.on('movestart', () => { + t.equal(map.isMoving(), true); + }); map.on('dragstart', () => { t.equal(map.isMoving(), true); }); map.on('dragend', () => { t.equal(map.isMoving(), false); + }); + map.on('moveend', () => { + t.equal(map.isMoving(), false); map.remove(); t.end(); }); @@ -49,7 +58,7 @@ test('Map#isMoving returns true when drag panning', (t) => { simulate.mousedown(map.getCanvas()); map._renderTaskQueue.run(); - simulate.mousemove(map.getCanvas(), {clientX: 10, clientY: 10}); + simulate.mousemove(map.getCanvas(), {buttons, clientX: 10, clientY: 10}); map._renderTaskQueue.run(); simulate.mouseup(map.getCanvas()); @@ -62,12 +71,18 @@ test('Map#isMoving returns true when drag rotating', (t) => { // Prevent inertial rotation. t.stub(browser, 'now').returns(0); + map.on('movestart', () => { + t.equal(map.isMoving(), true); + }); map.on('rotatestart', () => { t.equal(map.isMoving(), true); }); map.on('rotateend', () => { t.equal(map.isMoving(), false); + }); + map.on('moveend', () => { + t.equal(map.isMoving(), false); map.remove(); t.end(); }); @@ -139,7 +154,7 @@ test('Map#isMoving returns true when drag panning and scroll zooming interleave' simulate.mousedown(map.getCanvas()); map._renderTaskQueue.run(); - simulate.mousemove(map.getCanvas(), {clientX: 10, clientY: 10}); + simulate.mousemove(map.getCanvas(), {buttons, clientX: 10, clientY: 10}); map._renderTaskQueue.run(); const browserNow = t.stub(browser, 'now'); diff --git a/test/unit/ui/map_events.test.js b/test/unit/ui/map_events.test.js index 21e8041fe88..a0cd8d99d78 100644 --- a/test/unit/ui/map_events.test.js +++ b/test/unit/ui/map_events.test.js @@ -601,3 +601,32 @@ test(`Map#on click fires subsequent click event if there is no corresponding mou map.remove(); t.end(); }); + +test("Map#isMoving() returns false in mousedown/mouseup/click with no movement", (t) => { + const map = createMap(t, {interactive: true, clickTolerance: 4}); + let mousedown, mouseup, click; + map.on('mousedown', () => { mousedown = map.isMoving(); }); + map.on('mouseup', () => { mouseup = map.isMoving(); }); + map.on('click', () => { click = map.isMoving(); }); + + const canvas = map.getCanvas(); + const MouseEvent = window(canvas).MouseEvent; + + canvas.dispatchEvent(new MouseEvent('mousedown', {bubbles: true, clientX: 100, clientY: 100})); + t.equal(mousedown, false); + map._renderTaskQueue.run(); + t.equal(mousedown, false); + + canvas.dispatchEvent(new MouseEvent('mouseup', {bubbles: true, clientX: 100, clientY: 100})); + t.equal(mouseup, false); + map._renderTaskQueue.run(); + t.equal(mouseup, false); + + canvas.dispatchEvent(new MouseEvent('click', {bubbles: true, clientX: 100, clientY: 100})); + t.equal(click, false); + map._renderTaskQueue.run(); + t.equal(click, false); + + map.remove(); + t.end(); +}); diff --git a/test/unit/ui/marker.test.js b/test/unit/ui/marker.test.js index 3fed1cf3bfd..20376e22dd4 100644 --- a/test/unit/ui/marker.test.js +++ b/test/unit/ui/marker.test.js @@ -27,6 +27,35 @@ test('Marker uses a default marker element with custom color', (t) => { t.end(); }); +test('Marker uses a default marker element with custom scale', (t) => { + const map = createMap(t); + const defaultMarker = new Marker() + .setLngLat([0, 0]) + .addTo(map); + // scale smaller than default + const smallerMarker = new Marker({scale: 0.8}) + .setLngLat([0, 0]) + .addTo(map); + // scale larger than default + const largerMarker = new Marker({scale: 2}) + .setLngLat([0, 0]) + .addTo(map); + + // initial dimensions of svg element + t.ok(defaultMarker.getElement().firstChild.getAttribute('height').includes('41')); + t.ok(defaultMarker.getElement().firstChild.getAttribute('width').includes('27')); + + // (41 * 0.8) = 32.8, (27 * 0.8) = 21.6 + t.ok(smallerMarker.getElement().firstChild.getAttribute('height').includes(`32.8`)); + t.ok(smallerMarker.getElement().firstChild.getAttribute('width').includes(`21.6`)); + + // (41 * 2) = 82, (27 * 2) = 54 + t.ok(largerMarker.getElement().firstChild.getAttribute('height').includes('82')); + t.ok(largerMarker.getElement().firstChild.getAttribute('width').includes('54')); + + t.end(); +}); + test('Marker uses a default marker with custom offset', (t) => { const marker = new Marker({offset: [1, 2]}); t.ok(marker.getElement()); diff --git a/yarn.lock b/yarn.lock index 60a091a99e7..e15ae38e4c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1914,7 +1914,14 @@ blob@0.0.5: resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== -bluebird@^3.1.1, bluebird@^3.4.6: +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= + dependencies: + inherits "~2.0.0" + +bluebird@^3.1.1, bluebird@^3.4.6, bluebird@^3.5.0: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -2293,6 +2300,14 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + camelcase-keys@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77" @@ -2302,6 +2317,11 @@ camelcase-keys@^4.0.0: map-obj "^2.0.0" quick-lru "^1.0.0" +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= + camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -2596,6 +2616,11 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" +colors@~0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc" + integrity sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w= + combine-source-map@^0.8.0, combine-source-map@~0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.8.0.tgz#a58d0df042c186fcf822a8e8015f5450d2d79a8b" @@ -2830,6 +2855,13 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" +create-error-class@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" + integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= + dependencies: + capture-stack-trace "^1.0.0" + create-hash@^1.1.0, create-hash@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" @@ -3458,7 +3490,7 @@ decamelize-keys@^1.0.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0, decamelize@^1.2.0: +decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -3828,6 +3860,11 @@ duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: dependencies: readable-stream "^2.0.2" +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" @@ -4531,6 +4568,14 @@ find-cache-dir@^2.0.0: make-dir "^2.0.0" pkg-dir "^3.0.0" +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" @@ -4693,6 +4738,16 @@ fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.1.1, function-bind@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -4742,16 +4797,33 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== +get-npm-tarball-url@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/get-npm-tarball-url/-/get-npm-tarball-url-2.0.1.tgz#43c15223c35096e3e4068d8a6c6747bbdfc23462" + integrity sha512-POrVRGyS9X5w+855/H46JGVYBGuVgJXyIkbsTCzW+sv5x2qH+rfQjc7652DzkgOskF+cqLevA2En7V0hu0gZCg== + dependencies: + normalize-registry-url "^1.0.0" + get-port@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119" integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw== +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= + get-stdin@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= + get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -4869,7 +4941,7 @@ glob@^5.0.14: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.3, glob@^7.0.4, glob@^7.0.5, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.6: +glob@^7.0.3, glob@^7.0.4, glob@^7.0.5, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -4940,6 +5012,23 @@ gonzales-pe@^4.2.3: dependencies: minimist "1.1.x" +got@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" + integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= + dependencies: + create-error-class "^3.0.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + is-redirect "^1.0.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + lowercase-keys "^1.0.0" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + unzip-response "^2.0.1" + url-parse-lax "^1.0.0" + graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.2.3" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" @@ -5257,7 +5346,7 @@ ieee754@^1.1.12, ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== -ignore-walk@^3.0.1: +ignore-walk@^3.0.1, ignore-walk@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== @@ -5319,6 +5408,13 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= + dependencies: + repeating "^2.0.0" + indent-string@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" @@ -5342,7 +5438,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -5602,6 +5698,11 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= +is-finite@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" + integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== + is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -5689,6 +5790,11 @@ is-promise@^2.1.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= +is-redirect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" + integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= + is-reference@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.4.tgz#3f95849886ddb70256a3e6d062b1a68c13c51427" @@ -5720,6 +5826,11 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== +is-retry-allowed@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" + integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== + is-ssh@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.1.tgz#f349a8cadd24e65298037a522cf7520f2e81a0f3" @@ -5727,7 +5838,7 @@ is-ssh@^1.3.0: dependencies: protocols "^1.1.0" -is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -5775,7 +5886,12 @@ is-unc-path@^1.0.0: dependencies: unc-path-regex "^0.1.2" -is-utf8@^0.2.1: +is-url@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + +is-utf8@^0.2.0, is-utf8@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= @@ -6207,11 +6323,36 @@ lie@~3.3.0: dependencies: immediate "~3.0.5" +list-npm-contents@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/list-npm-contents/-/list-npm-contents-1.0.2.tgz#4fadf7a0e2c894fad92fcfc0092b0cec69444683" + integrity sha512-cpAkA9+ioEqHnxTuh3UDRewX+obC3mTr9dlYRVnTt0riggK+0IdKIed7BPn1BgkBQP+TVHiso4Rj0ZxGaXzh1Q== + dependencies: + bluebird "^3.5.0" + get-npm-tarball-url "^2.0.0" + got "^6.7.1" + is-url "^1.2.2" + ls-archive "^1.2.3" + meow "^3.3.0" + registry-url "^4.0.0" + tmp "0.0.31" + livereload-js@^2.3.0: version "2.4.0" resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c" integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw== +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + load-json-file@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" @@ -6455,6 +6596,11 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" +lowercase-keys@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + lru-cache@^4.0.0, lru-cache@^4.0.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -6463,6 +6609,18 @@ lru-cache@^4.0.0, lru-cache@^4.0.1: pseudomap "^1.0.2" yallist "^2.1.2" +ls-archive@^1.2.3: + version "1.3.4" + resolved "https://registry.yarnpkg.com/ls-archive/-/ls-archive-1.3.4.tgz#52150919dab1acb094cdcef9dde9c66934a4650f" + integrity sha512-7GmjZOckV+gzm4PM1/LcWIsZIRsSkAVmIchoEf5xjquNKU0Ti5KUvGQ3dl/7VsbZIduMOPwRDXrvpo3LVJ0Pmg== + dependencies: + async "~0.2.9" + colors "~0.6.2" + optimist "~0.5.2" + rimraf "~2.2.6" + tar "^2.2.1" + yauzl "^2.9.1" + macos-release@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" @@ -6507,7 +6665,7 @@ map-cache@^0.2.0, map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= -map-obj@^1.0.0: +map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= @@ -6642,6 +6800,22 @@ memorystream@^0.3.1: resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= +meow@^3.3.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + meow@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4" @@ -6788,7 +6962,7 @@ minimist@1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.0: +minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.0: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -6828,6 +7002,13 @@ mkdirp-classic@^0.5.2: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b" integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g== +"mkdirp@>=0.5 0": + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@~0.5.1: version "0.5.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" @@ -7025,22 +7206,6 @@ node-notifier@^5.0.1, node-notifier@^5.4.3: shellwords "^0.1.1" which "^1.3.0" -node-pre-gyp@*: - version "0.14.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - node-pre-gyp@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" @@ -7119,6 +7284,11 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= +normalize-registry-url@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/normalize-registry-url/-/normalize-registry-url-1.0.0.tgz#f75d2c48373da780c76f1f0eeb6382c06e784d13" + integrity sha512-0v6T4851b72ykk5zEtFoN4QX/Fqyk7pouIj9xZyAvAe9jlDhAwT4z6FlwsoQCHjeuK2EGUoAwy/F4y4B1uZq9A== + normalize-selector@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/normalize-selector/-/normalize-selector-0.2.0.tgz#d0b145eb691189c63a78d201dc4fdb1293ef0c03" @@ -7136,7 +7306,7 @@ now-and-later@^2.0.0: dependencies: once "^1.3.2" -npm-bundled@^1.0.1: +npm-bundled@^1.0.1, npm-bundled@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== @@ -7162,6 +7332,16 @@ npm-packlist@^1.1.6: npm-bundled "^1.0.1" npm-normalize-package-bin "^1.0.1" +npm-packlist@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-2.1.1.tgz#08806a1df79acdc43d02d20c83a3d5472d96c90c" + integrity sha512-95TSDvGwujIhqfSpIiRRLodEF+y6mJMopuZdahoGzqtRDFZXGav46S0p6ngeWaiAkb5R72w6eVARhzej0HvZeQ== + dependencies: + glob "^7.1.6" + ignore-walk "^3.0.3" + npm-bundled "^1.1.1" + npm-normalize-package-bin "^1.0.1" + npm-run-all@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" @@ -7251,7 +7431,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -7388,6 +7568,13 @@ opener@^1.5.1: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA== +optimist@~0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.5.2.tgz#85c8c1454b3315e4a78947e857b1df033450bfbc" + integrity sha1-hcjBRUszFeSniUfoV7HfAzRQv7w= + dependencies: + wordwrap "~0.0.2" + optionator@^0.8.1, optionator@^0.8.2: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -7651,6 +7838,13 @@ path-dirname@^1.0.0: resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= + dependencies: + pinkie-promise "^2.0.0" + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -7705,6 +7899,15 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" @@ -7773,6 +7976,18 @@ pify@^4.0.0, pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + pirates@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/pirates/-/pirates-3.0.2.tgz#7e6f85413fd9161ab4e12b539b06010d85954bb9" @@ -8272,6 +8487,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +prepend-http@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= + pretty-bytes@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" @@ -8558,6 +8778,14 @@ read-only-stream@^2.0.0: dependencies: readable-stream "^2.0.2" +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + read-pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" @@ -8582,6 +8810,15 @@ read-pkg-up@^4.0.0: find-up "^3.0.0" read-pkg "^3.0.0" +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + read-pkg@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" @@ -8673,6 +8910,14 @@ readdirp@~3.3.0: dependencies: picomatch "^2.0.7" +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + redent@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" @@ -8744,6 +8989,13 @@ regextras@^0.6.1: resolved "https://registry.yarnpkg.com/regextras/-/regextras-0.6.1.tgz#9689641bbb338e0ff7001a5c507c6a2008df7b36" integrity sha512-EzIHww9xV2Kpqx+corS/I7OBmf2rZ0pKKJPsw5Dc+l6Zq1TslDmtRIP9maVn3UH+72MIXmn8zzDgP07ihQogUA== +registry-url@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-4.0.0.tgz#7dc344ef0f1496fc95a6ad04ccb9a491df11c025" + integrity sha512-WAfGLywivb8s2+Cfblq1UV+kOyzURHzWSJmciDvrmstr4bv/0lnVSB9jfoOfkxx5xNJ1OGlSFmZh9WYBLFJOPg== + dependencies: + rc "^1.2.7" + regjsgen@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" @@ -8929,6 +9181,13 @@ repeat-string@^1.5.0, repeat-string@^1.5.4, repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= + dependencies: + is-finite "^1.0.0" + replace-ext@1.0.0, replace-ext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" @@ -9081,6 +9340,11 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" +rimraf@~2.2.6: + version "2.2.8" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" + integrity sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI= + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -9903,6 +10167,13 @@ strip-ansi@~0.1.0: resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991" integrity sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE= +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= + dependencies: + is-utf8 "^0.2.0" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -9913,6 +10184,13 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= + dependencies: + get-stdin "^4.0.1" + strip-indent@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" @@ -10021,10 +10299,10 @@ sugarss@^2.0.0: dependencies: postcss "^7.0.2" -supercluster@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.0.0.tgz#75d474fafb0a055db552ed7bd7bbda583f6ab321" - integrity sha512-8VuHI8ynylYQj7Qf6PBMWy1PdgsnBiIxujOgc9Z83QvJ8ualIYWNx2iMKyKeC4DZI5ntD9tz/CIwwZvIelixsA== +supercluster@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.0.tgz#f0a457426ec0ab95d69c5f03b51e049774b94479" + integrity sha512-LDasImUAFMhTqhK+cUXfy9C2KTUqJ3gucLjmNLNFmKWOnDUBxLFLH9oKuXOTCLveecmxh8fbk8kgh6Q0gsfe2w== dependencies: kdbush "^3.0.0" @@ -10234,7 +10512,16 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4, tar@^4.4.2, tar@^4.4.8: +tar@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" + integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== + dependencies: + block-stream "*" + fstream "^1.0.12" + inherits "2" + +tar@^4, tar@^4.4.8: version "4.4.13" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== @@ -10335,6 +10622,11 @@ through2@^2.0.0, through2@^2.0.3, through2@~2.0.0: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +timed-out@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= + timers-browserify@^1.0.1: version "1.4.2" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" @@ -10376,6 +10668,13 @@ tmp@0.0.30: dependencies: os-tmpdir "~1.0.1" +tmp@0.0.31: + version "0.0.31" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" + integrity sha1-jzirlDjhcxXl29izZX6L+yd65Kc= + dependencies: + os-tmpdir "~1.0.1" + tmp@0.0.33, tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -10465,6 +10764,11 @@ trim-lines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-1.1.3.tgz#839514be82428fd9e7ec89e35081afe8f6f93115" integrity sha512-E0ZosSWYK2mkSu+KEtQ9/KqarVjA9HztOSX+9FDdNacRAq29RRV6ZQNgob3iuW8Htar9vAfEa6yyt5qBAHZDBA== +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= + trim-newlines@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" @@ -10799,6 +11103,11 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +unzip-response@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" + integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= + upath@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" @@ -10816,6 +11125,13 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= + dependencies: + prepend-http "^1.0.1" + url@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -11152,6 +11468,11 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -11307,7 +11628,7 @@ yargs@^12.0.1, yargs@^12.0.2, yargs@^12.0.5: y18n "^3.2.1 || ^4.0.0" yargs-parser "^11.1.1" -yauzl@^2.10.0: +yauzl@^2.10.0, yauzl@^2.9.1: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=