File: ve.init.mw.ViewportZoomHandler.js

package info (click to toggle)
mediawiki 1%3A1.43.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 417,464 kB
  • sloc: php: 1,062,949; javascript: 664,290; sql: 9,714; python: 5,458; xml: 3,489; sh: 1,131; makefile: 64
file content (146 lines) | stat: -rw-r--r-- 4,456 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*!
 * VisualEditor MediaWiki Initialization ViewportZoomHandler class.
 *
 * @copyright See AUTHORS.txt
 * @license The MIT License (MIT); see LICENSE.txt
 */

/**
 * Prevent iOS browsers from wrongly zooming in the page when the surface is focussed. (T216446)
 *
 * When the user places a cursor for text input anywhere on the page, iOS browsers zoom in the page
 * to ensure the text size is legible and the cursor can be comfortably placed in the right place
 * with a finger.
 *
 * There's a browser bug that, on some devices (e.g. iPhone XS, but not iPhone SE), causes this
 * zoom to occur even though our text is already using the required minimum font size (16px).
 * Additionally, the zoom occurs when placing the cursor in image captions, which intentionally
 * use a smaller font size.
 *
 * In both cases the zoom is more problematic than helpful, because it causes parts of the toolbar
 * to disappear outside the viewport.
 *
 * To prevent it, temporarily add a tag like `<meta name="viewport" content="maximum-scale=1.0">`
 * to the page when the user is about to focus the editing surface. However, on iOS Chrome, this
 * also prevents intentional pinch-zoom. To avoid this, immediately remove the tag again after
 * focussing, or if it looks like the user is trying to zoom (used multi-touch or caused a scroll).
 *
 * @class
 * @constructor
 */
ve.init.mw.ViewportZoomHandler = function VeInitMwViewportZoomHandler() {
	// eslint-disable-next-line no-jquery/no-global-selector
	this.$viewportMeta = $( 'meta[name="viewport"]' );
	if ( !this.$viewportMeta.length ) {
		this.$viewportMeta = $( '<meta>' ).attr( 'name', 'viewport' ).appendTo( document.head );
	}
	this.origViewportMetaContent = this.$viewportMeta.attr( 'content' );

	this.onTouchStartHandler = this.onTouchStart.bind( this );
	this.onTouchMoveHandler = this.onTouchMove.bind( this );
	this.onTouchEndHandler = this.onTouchEnd.bind( this );
};

/* Methods */

/**
 * Change the `<meta name="viewport">` tag to prevent automatic zooming.
 */
ve.init.mw.ViewportZoomHandler.prototype.preventZoom = function () {
	this.$viewportMeta.attr( 'content', ( i, val ) => {
		// Remove existing maximum-scale, if any, and add 'maximum-scale=1.0'. Don't change other values.
		if ( val ) {
			val = val.replace( /maximum-scale=[\d.]+(,\s*|$)/, '' );
			val += ', ';
		} else {
			val = '';
		}
		return val + 'maximum-scale=1.0';
	} );
};

/**
 * Change the `<meta name="viewport">` tag to allow automatic zooming once again.
 */
ve.init.mw.ViewportZoomHandler.prototype.allowZoom = function () {
	this.$viewportMeta.attr( 'content', this.origViewportMetaContent );
};

/**
 * Start listening to events and preventing zooming.
 *
 * @param {ve.ui.Surface} surface
 */
ve.init.mw.ViewportZoomHandler.prototype.attach = function ( surface ) {
	this.surface = surface;

	this.surface.getView().$element.on( {
		touchstart: this.onTouchStartHandler,
		touchmove: this.onTouchMoveHandler,
		touchend: this.onTouchEndHandler
	} );
	this.surface.getModel().connect( this, {
		focus: 'onFocus'
	} );
};

/**
 * Stop listening to events.
 */
ve.init.mw.ViewportZoomHandler.prototype.detach = function () {
	this.surface.getView().$element.off( {
		touchstart: this.onTouchStartHandler,
		touchmove: this.onTouchMoveHandler,
		touchend: this.onTouchEndHandler
	} );
	this.surface.getModel().disconnect( this, {
		focus: 'onFocus'
	} );

	this.surface = null;
};

/**
 * Handle touch start events.
 *
 * @param {jQuery.Event} e Touch start event
 */
ve.init.mw.ViewportZoomHandler.prototype.onTouchStart = function ( e ) {
	if ( e.touches.length === 1 ) {
		this.wasMoved = false;
	}

	this.allowZoom();
};

/**
 * Handle touch move events.
 *
 * @param {jQuery.Event} e Touch move event
 */
ve.init.mw.ViewportZoomHandler.prototype.onTouchMove = function () {
	this.wasMoved = true;
};

/**
 * Handle touch end events.
 *
 * @param {jQuery.Event} e Touch end event
 */
ve.init.mw.ViewportZoomHandler.prototype.onTouchEnd = function ( e ) {
	if ( e.touches.length === 0 && !this.wasMoved ) {
		// There was a single touch point, that hasn't moved, and now it's gone.
		// Looks like we're going to focus the surface, so prevent automatic zoom.
		this.preventZoom();
	} else {
		// Otherwise, allow zoom, so that the user can pinch-zoom
		this.allowZoom();
	}
};

/**
 * Handle surface model focus events.
 */
ve.init.mw.ViewportZoomHandler.prototype.onFocus = function () {
	this.allowZoom();
};