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' ;
7
3
import { normalizeStyle } from '../utils/style-utils' ;
8
4
import { deepEqual } from '../utils/deep-equal' ;
9
5
@@ -162,23 +158,27 @@ const handlerNames = [
162
158
*/
163
159
export default class Mapbox {
164
160
private _MapClass : { new ( options : any ) : MapInstance } ;
165
- // mapboxgl.Map instance
161
+ /** mapboxgl.Map instance */
166
162
private _map : MapInstance = null ;
167
- // User-supplied props
163
+ /** User-supplied props */
168
164
props : MapboxProps ;
169
165
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 ;
177
170
178
171
// Internal states
172
+ /** Making updates driven by React props. Do not trigger React callbacks to avoid infinite loop */
179
173
private _internalUpdate : boolean = false ;
174
+ /** Map is currently rendering */
180
175
private _inRender : boolean = false ;
176
+ /** Map features under the pointer */
181
177
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 */
182
182
private _deferredEvents : {
183
183
move : boolean ;
184
184
zoom : boolean ;
@@ -208,7 +208,7 @@ export default class Mapbox {
208
208
}
209
209
210
210
get transform ( ) : Transform {
211
- return this . _renderTransform ;
211
+ return this . _map . transform ;
212
212
}
213
213
214
214
setProps ( props : MapboxProps ) {
@@ -217,7 +217,7 @@ export default class Mapbox {
217
217
218
218
const settingsChanged = this . _updateSettings ( props , oldProps ) ;
219
219
if ( settingsChanged ) {
220
- this . _createShadowTransform ( this . _map ) ;
220
+ this . _createProxyTransform ( this . _map ) ;
221
221
}
222
222
const sizeChanged = this . _updateSize ( props ) ;
223
223
const viewStateChanged = this . _updateViewState ( props , true ) ;
@@ -318,39 +318,40 @@ export default class Mapbox {
318
318
if ( props . cursor ) {
319
319
map . getCanvas ( ) . style . cursor = props . cursor ;
320
320
}
321
- this . _createShadowTransform ( map ) ;
321
+ this . _createProxyTransform ( map ) ;
322
322
323
323
// Hack
324
324
// Insert code into map's render cycle
325
325
// eslint-disable-next-line @typescript-eslint/unbound-method
326
326
const renderMap = map . _render ;
327
327
map . _render = ( arg : number ) => {
328
+ // Hijacked to set this state flag
328
329
this . _inRender = true ;
329
330
renderMap . call ( map , arg ) ;
330
331
this . _inRender = false ;
331
332
} ;
332
333
// eslint-disable-next-line @typescript-eslint/unbound-method
333
334
const runRenderTaskQueue = map . _renderTaskQueue . run ;
334
335
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 ;
335
339
runRenderTaskQueue . call ( map . _renderTaskQueue , arg ) ;
336
- this . _onBeforeRepaint ( ) ;
340
+ this . _proxyTransform . $internalUpdate = false ;
341
+ this . _fireDefferedEvents ( ) ;
337
342
} ;
338
- map . on ( 'render' , ( ) => this . _onAfterRepaint ( ) ) ;
339
343
// Insert code into map's event pipeline
340
344
// eslint-disable-next-line @typescript-eslint/unbound-method
341
345
const fireEvent = map . fire ;
342
346
map . fire = this . _fireEvent . bind ( this , fireEvent ) ;
343
347
344
348
// add listeners
345
- map . on ( 'resize' , ( ) => {
346
- this . _renderTransform . resize ( map . transform . width , map . transform . height ) ;
347
- } ) ;
348
349
map . on ( 'styledata' , ( ) => {
349
350
this . _updateStyleComponents ( this . props , { } ) ;
350
- // Projection can be set in stylesheet
351
- syncProjection ( map . transform , this . _renderTransform ) ;
352
351
} ) ;
353
- map . on ( 'sourcedata' , ( ) => this . _updateStyleComponents ( this . props , { } ) ) ;
352
+ map . on ( 'sourcedata' , ( ) => {
353
+ this . _updateStyleComponents ( this . props , { } ) ;
354
+ } ) ;
354
355
for ( const eventName in pointerEvents ) {
355
356
map . on ( eventName , this . _onPointerEvent ) ;
356
357
}
@@ -396,11 +397,11 @@ export default class Mapbox {
396
397
}
397
398
}
398
399
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 ;
404
405
}
405
406
406
407
/* Trigger map resize if size is controlled
@@ -427,28 +428,11 @@ export default class Mapbox {
427
428
@returns {bool } true if anything is changed
428
429
*/
429
430
_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 ;
437
433
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 ;
452
436
453
437
if ( changed && triggerEvents ) {
454
438
const deferredEvents = this . _deferredEvents ;
@@ -459,12 +443,6 @@ export default class Mapbox {
459
443
deferredEvents . pitch ||= pitch !== tr . pitch ;
460
444
}
461
445
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
-
468
446
return changed ;
469
447
}
470
448
@@ -576,18 +554,14 @@ export default class Mapbox {
576
554
577
555
private _queryRenderedFeatures ( point : Point ) {
578
556
const map = this . _map ;
579
- const tr = map . transform ;
580
557
const { interactiveLayerIds = [ ] } = this . props ;
581
558
try {
582
- map . transform = this . _renderTransform ;
583
559
return map . queryRenderedFeatures ( point , {
584
560
layers : interactiveLayerIds . filter ( map . getLayer . bind ( map ) )
585
561
} ) ;
586
562
} catch {
587
563
// May fail if style is not loaded
588
564
return [ ] ;
589
- } finally {
590
- map . transform = tr ;
591
565
}
592
566
}
593
567
@@ -637,9 +611,14 @@ export default class Mapbox {
637
611
if ( ! this . _internalUpdate ) {
638
612
// @ts -ignore
639
613
const cb = this . props [ cameraEvents [ e . type ] ] ;
614
+ const tr = this . _proxyTransform ;
640
615
if ( cb ) {
616
+ e . viewState = transformToViewState ( tr . $proposedTransform ?? tr ) ;
641
617
cb ( e ) ;
642
618
}
619
+ if ( e . type === 'moveend' ) {
620
+ tr . $proposedTransform = null ;
621
+ }
643
622
}
644
623
if ( e . type in this . _deferredEvents ) {
645
624
this . _deferredEvents [ e . type ] = false ;
@@ -648,57 +627,31 @@ export default class Mapbox {
648
627
649
628
_fireEvent ( baseFire : Function , event : string | MapEvent , properties ?: object ) {
650
629
const map = this . _map ;
651
- const tr = map . transform ;
630
+ const tr = this . _proxyTransform ;
652
631
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 ;
669
639
}
670
- baseFire . call ( map , event , properties ) ;
671
640
672
641
return map ;
673
642
}
674
643
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 ( ) {
677
646
const map = this . _map ;
678
-
679
- // If there are camera changes driven by props, invoke camera events so that DOM controls are synced
680
647
this . _internalUpdate = true ;
681
648
for ( const eventType in this . _deferredEvents ) {
682
649
if ( this . _deferredEvents [ eventType ] ) {
683
650
map . fire ( eventType ) ;
684
651
}
685
652
}
686
653
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
- } ;
699
654
}
700
-
701
- _onAfterRepaint : ( ) => void ;
702
655
}
703
656
704
657
/**
0 commit comments