1- import {
2- transformToViewState ,
3- applyViewStateToTransform ,
4- cloneTransform ,
5- syncProjection
6- } from '../utils/transform' ;
1+ import { transformToViewState , compareViewStateWithTransform } from '../utils/transform' ;
2+ import { ProxyTransform , createProxyTransform } from './proxy-transform' ;
73import { normalizeStyle } from '../utils/style-utils' ;
84import { deepEqual } from '../utils/deep-equal' ;
95
@@ -162,23 +158,27 @@ const handlerNames = [
162158 */
163159export default class Mapbox {
164160 private _MapClass : { new ( options : any ) : MapInstance } ;
165- // mapboxgl.Map instance
161+ /** mapboxgl.Map instance */
166162 private _map : MapInstance = null ;
167- // User-supplied props
163+ /** User-supplied props */
168164 props : MapboxProps ;
169165
170- // Mapbox map is stateful.
171- // During method calls/user interactions, map.transform is mutated and
172- // deviate from user-supplied props.
173- // In order to control the map reactively, we shadow the transform
174- // with the one below, which reflects the view state resolved from
175- // both user-supplied props and the underlying state
176- private _renderTransform : Transform ;
166+ /** The transform that replaces native map.transform to resolve changes vs. React props
167+ * See proxy-transform.ts
168+ */
169+ private _proxyTransform : ProxyTransform ;
177170
178171 // Internal states
172+ /** Making updates driven by React props. Do not trigger React callbacks to avoid infinite loop */
179173 private _internalUpdate : boolean = false ;
174+ /** Map is currently rendering */
180175 private _inRender : boolean = false ;
176+ /** Map features under the pointer */
181177 private _hoveredFeatures : MapGeoJSONFeature [ ] = null ;
178+ /** View state changes driven by React props
179+ * They still need to fire move/etc. events because controls such as marker/popup
180+ * subscribe to the move event internally to update their position
181+ * React callbacks like onMove are not called for these */
182182 private _deferredEvents : {
183183 move : boolean ;
184184 zoom : boolean ;
@@ -208,7 +208,7 @@ export default class Mapbox {
208208 }
209209
210210 get transform ( ) : Transform {
211- return this . _renderTransform ;
211+ return this . _map . transform ;
212212 }
213213
214214 setProps ( props : MapboxProps ) {
@@ -217,7 +217,7 @@ export default class Mapbox {
217217
218218 const settingsChanged = this . _updateSettings ( props , oldProps ) ;
219219 if ( settingsChanged ) {
220- this . _createShadowTransform ( this . _map ) ;
220+ this . _createProxyTransform ( this . _map ) ;
221221 }
222222 const sizeChanged = this . _updateSize ( props ) ;
223223 const viewStateChanged = this . _updateViewState ( props , true ) ;
@@ -318,39 +318,40 @@ export default class Mapbox {
318318 if ( props . cursor ) {
319319 map . getCanvas ( ) . style . cursor = props . cursor ;
320320 }
321- this . _createShadowTransform ( map ) ;
321+ this . _createProxyTransform ( map ) ;
322322
323323 // Hack
324324 // Insert code into map's render cycle
325325 // eslint-disable-next-line @typescript-eslint/unbound-method
326326 const renderMap = map . _render ;
327327 map . _render = ( arg : number ) => {
328+ // Hijacked to set this state flag
328329 this . _inRender = true ;
329330 renderMap . call ( map , arg ) ;
330331 this . _inRender = false ;
331332 } ;
332333 // eslint-disable-next-line @typescript-eslint/unbound-method
333334 const runRenderTaskQueue = map . _renderTaskQueue . run ;
334335 map . _renderTaskQueue . run = ( arg : number ) => {
336+ // This is where camera updates from input handler/animation happens
337+ // And where all view state change events are fired
338+ this . _proxyTransform . $internalUpdate = true ;
335339 runRenderTaskQueue . call ( map . _renderTaskQueue , arg ) ;
336- this . _onBeforeRepaint ( ) ;
340+ this . _proxyTransform . $internalUpdate = false ;
341+ this . _fireDefferedEvents ( ) ;
337342 } ;
338- map . on ( 'render' , ( ) => this . _onAfterRepaint ( ) ) ;
339343 // Insert code into map's event pipeline
340344 // eslint-disable-next-line @typescript-eslint/unbound-method
341345 const fireEvent = map . fire ;
342346 map . fire = this . _fireEvent . bind ( this , fireEvent ) ;
343347
344348 // add listeners
345- map . on ( 'resize' , ( ) => {
346- this . _renderTransform . resize ( map . transform . width , map . transform . height ) ;
347- } ) ;
348349 map . on ( 'styledata' , ( ) => {
349350 this . _updateStyleComponents ( this . props , { } ) ;
350- // Projection can be set in stylesheet
351- syncProjection ( map . transform , this . _renderTransform ) ;
352351 } ) ;
353- map . on ( 'sourcedata' , ( ) => this . _updateStyleComponents ( this . props , { } ) ) ;
352+ map . on ( 'sourcedata' , ( ) => {
353+ this . _updateStyleComponents ( this . props , { } ) ;
354+ } ) ;
354355 for ( const eventName in pointerEvents ) {
355356 map . on ( eventName , this . _onPointerEvent ) ;
356357 }
@@ -396,11 +397,11 @@ export default class Mapbox {
396397 }
397398 }
398399
399- _createShadowTransform ( map : any ) {
400- const renderTransform = cloneTransform ( map . transform ) ;
401- map . painter . transform = renderTransform ;
402-
403- this . _renderTransform = renderTransform ;
400+ _createProxyTransform ( map : any ) {
401+ const proxyTransform = createProxyTransform ( map . transform ) ;
402+ map . transform = proxyTransform ;
403+ map . painter . transform = proxyTransform ;
404+ this . _proxyTransform = proxyTransform ;
404405 }
405406
406407 /* Trigger map resize if size is controlled
@@ -427,28 +428,11 @@ export default class Mapbox {
427428 @returns {bool } true if anything is changed
428429 */
429430 _updateViewState ( nextProps : MapboxProps , triggerEvents : boolean ) : boolean {
430- if ( this . _internalUpdate ) {
431- return false ;
432- }
433- const map = this . _map ;
434-
435- const tr = this . _renderTransform ;
436- // Take a snapshot of the transform before mutation
431+ const viewState : Partial < ViewState > = nextProps . viewState || nextProps ;
432+ const tr = this . _proxyTransform ;
437433 const { zoom, pitch, bearing} = tr ;
438- const isMoving = map . isMoving ( ) ;
439-
440- if ( isMoving ) {
441- // All movement of the camera is done relative to the sea level
442- tr . cameraElevationReference = 'sea' ;
443- }
444- const changed = applyViewStateToTransform ( tr , {
445- ...transformToViewState ( map . transform ) ,
446- ...nextProps
447- } ) ;
448- if ( isMoving ) {
449- // Reset camera reference
450- tr . cameraElevationReference = 'ground' ;
451- }
434+ const changed = compareViewStateWithTransform ( this . _proxyTransform , viewState ) ;
435+ tr . $reactViewState = viewState ;
452436
453437 if ( changed && triggerEvents ) {
454438 const deferredEvents = this . _deferredEvents ;
@@ -459,12 +443,6 @@ export default class Mapbox {
459443 deferredEvents . pitch ||= pitch !== tr . pitch ;
460444 }
461445
462- // Avoid manipulating the real transform when interaction/animation is ongoing
463- // as it would interfere with Mapbox's handlers
464- if ( ! isMoving ) {
465- applyViewStateToTransform ( map . transform , nextProps ) ;
466- }
467-
468446 return changed ;
469447 }
470448
@@ -576,18 +554,14 @@ export default class Mapbox {
576554
577555 private _queryRenderedFeatures ( point : Point ) {
578556 const map = this . _map ;
579- const tr = map . transform ;
580557 const { interactiveLayerIds = [ ] } = this . props ;
581558 try {
582- map . transform = this . _renderTransform ;
583559 return map . queryRenderedFeatures ( point , {
584560 layers : interactiveLayerIds . filter ( map . getLayer . bind ( map ) )
585561 } ) ;
586562 } catch {
587563 // May fail if style is not loaded
588564 return [ ] ;
589- } finally {
590- map . transform = tr ;
591565 }
592566 }
593567
@@ -637,9 +611,14 @@ export default class Mapbox {
637611 if ( ! this . _internalUpdate ) {
638612 // @ts -ignore
639613 const cb = this . props [ cameraEvents [ e . type ] ] ;
614+ const tr = this . _proxyTransform ;
640615 if ( cb ) {
616+ e . viewState = transformToViewState ( tr . $proposedTransform ?? tr ) ;
641617 cb ( e ) ;
642618 }
619+ if ( e . type === 'moveend' ) {
620+ tr . $proposedTransform = null ;
621+ }
643622 }
644623 if ( e . type in this . _deferredEvents ) {
645624 this . _deferredEvents [ e . type ] = false ;
@@ -648,57 +627,31 @@ export default class Mapbox {
648627
649628 _fireEvent ( baseFire : Function , event : string | MapEvent , properties ?: object ) {
650629 const map = this . _map ;
651- const tr = map . transform ;
630+ const tr = this . _proxyTransform ;
652631
653- const eventType = typeof event === 'string' ? event : event . type ;
654- if ( eventType === 'move' ) {
655- this . _updateViewState ( this . props , false ) ;
656- }
657- if ( eventType in cameraEvents ) {
658- if ( typeof event === 'object' ) {
659- ( event as unknown as ViewStateChangeEvent ) . viewState = transformToViewState ( tr ) ;
660- }
661- if ( this . _map . isMoving ( ) ) {
662- // Replace map.transform with ours during the callbacks
663- map . transform = this . _renderTransform ;
664- baseFire . call ( map , event , properties ) ;
665- map . transform = tr ;
666-
667- return map ;
668- }
632+ // Always expose the controlled transform to controls/end user
633+ const internal = tr . $internalUpdate ;
634+ try {
635+ tr . $internalUpdate = false ;
636+ baseFire . call ( map , event , properties ) ;
637+ } finally {
638+ tr . $internalUpdate = internal ;
669639 }
670- baseFire . call ( map , event , properties ) ;
671640
672641 return map ;
673642 }
674643
675- // All camera manipulations are complete, ready to repaint
676- _onBeforeRepaint ( ) {
644+ // If there are camera changes driven by props, invoke camera events so that DOM controls are synced
645+ _fireDefferedEvents ( ) {
677646 const map = this . _map ;
678-
679- // If there are camera changes driven by props, invoke camera events so that DOM controls are synced
680647 this . _internalUpdate = true ;
681648 for ( const eventType in this . _deferredEvents ) {
682649 if ( this . _deferredEvents [ eventType ] ) {
683650 map . fire ( eventType ) ;
684651 }
685652 }
686653 this . _internalUpdate = false ;
687-
688- const tr = this . _map . transform ;
689- // Make sure camera matches the current props
690- map . transform = this . _renderTransform ;
691-
692- this . _onAfterRepaint = ( ) => {
693- // Mapbox transitions between non-mercator projection and mercator during render time
694- // Copy it back to the other
695- syncProjection ( this . _renderTransform , tr ) ;
696- // Restores camera state before render/load events are fired
697- map . transform = tr ;
698- } ;
699654 }
700-
701- _onAfterRepaint : ( ) => void ;
702655}
703656
704657/**
0 commit comments