@prose-reader/enhancer-gestures 1.303.0 → 1.304.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,543 +1,396 @@
1
- import { isHtmlElement, SettingsManager, HookManager } from "@prose-reader/core";
2
- import { PinchRecognizer, PanRecognizer, TapRecognizer, SwipeRecognizer, Recognizable } from "gesturx";
3
- import { switchMap, filter, merge, tap, of, map, withLatestFrom, EMPTY, throttleTime, animationFrameScheduler, takeUntil, combineLatest, first, share } from "rxjs";
4
- const name = "@prose-reader/enhancer-gestures";
5
- const registerPan = ({
6
- reader,
7
- recognizer,
8
- settingsManager
9
- }) => {
10
- const gestures$ = settingsManager.values$.pipe(
11
- switchMap(({ panNavigation }) => {
12
- const panStart$ = recognizer.events$.pipe(
13
- filter((event) => event.type === `panStart`)
14
- );
15
- const panMove$ = recognizer.events$.pipe(
16
- filter((event) => event.type === `panMove`)
17
- );
18
- const panEnd$ = recognizer.events$.pipe(
19
- filter((event) => event.type === `panEnd`)
20
- );
21
- const pan$ = panStart$.pipe(
22
- switchMap((panStartEvent) => {
23
- let lastDelta = { x: 0, y: 0 };
24
- const moveAndEnd$ = merge(panMove$, panEnd$).pipe(
25
- tap((event) => {
26
- const isZooming = reader.zoom.state.isZooming;
27
- const isZoomingIn = reader.zoom.state.currentScale > 1;
28
- if (isZooming && isZoomingIn) {
29
- const deltaX = event.deltaX - lastDelta.x;
30
- const deltaY = event.deltaY - lastDelta.y;
31
- lastDelta = {
32
- x: event.deltaX,
33
- y: event.deltaY
34
- };
35
- reader.zoom.move(
36
- {
37
- x: reader.zoom.state.currentPosition.x + deltaX,
38
- y: reader.zoom.state.currentPosition.y + deltaY
39
- },
40
- {
41
- constrain: "within-viewport"
42
- }
43
- );
44
- return;
45
- }
46
- if (panNavigation !== "pan") return;
47
- if (event.type === `panMove`) {
48
- if (!reader.navigation.panNavigator.value.isStarted) {
49
- reader.navigation.panNavigator.start({
50
- x: event.deltaX,
51
- y: event.deltaY
52
- });
53
- return;
54
- }
55
- reader.navigation.panNavigator.panMoveTo({
56
- x: event.deltaX,
57
- y: event.deltaY
58
- });
59
- return;
60
- }
61
- if (event.type === `panEnd` && reader.navigation.panNavigator.value.isStarted) {
62
- reader.navigation.panNavigator.stop({
63
- x: event.deltaX,
64
- y: event.deltaY
65
- });
66
- }
67
- })
68
- );
69
- return merge(of(panStartEvent), moveAndEnd$).pipe(
70
- map((event) => ({
71
- type: "pan",
72
- gestureEvent: event
73
- }))
74
- );
75
- })
76
- );
77
- return pan$;
78
- })
79
- );
80
- return gestures$;
1
+ import { HookManager, SettingsManager, isHtmlElement } from "@prose-reader/core";
2
+ import { PanRecognizer, PinchRecognizer, Recognizable, SwipeRecognizer, TapRecognizer } from "gesturx";
3
+ import { EMPTY, animationFrameScheduler, combineLatest, filter, first, map, merge, of, share, switchMap, takeUntil, tap, throttleTime, withLatestFrom } from "rxjs";
4
+ //#region package.json
5
+ var name = "@prose-reader/enhancer-gestures";
6
+ //#endregion
7
+ //#region src/gestures/pan.ts
8
+ var registerPan = ({ reader, recognizer, settingsManager }) => {
9
+ return settingsManager.values$.pipe(switchMap(({ panNavigation }) => {
10
+ const panStart$ = recognizer.events$.pipe(filter((event) => event.type === `panStart`));
11
+ const panMove$ = recognizer.events$.pipe(filter((event) => event.type === `panMove`));
12
+ const panEnd$ = recognizer.events$.pipe(filter((event) => event.type === `panEnd`));
13
+ return panStart$.pipe(switchMap((panStartEvent) => {
14
+ /**
15
+ * We use the last cumulative delta to derive the new event atomic delta.
16
+ * This is because panning the zoom does not necessarily means the zoom position
17
+ * will always changes. If the user keep dragging while the zoom is blocked, we want
18
+ * it to move the other direction when he start dragging the other way.
19
+ * We cannot use the `reader.zoom.state.currentPosition` as previous position
20
+ * and the event.deltaX to compute the new zoom position.
21
+ */
22
+ let lastDelta = {
23
+ x: 0,
24
+ y: 0
25
+ };
26
+ const moveAndEnd$ = merge(panMove$, panEnd$).pipe(tap((event) => {
27
+ const isZooming = reader.zoom.state.isZooming;
28
+ const isZoomingIn = reader.zoom.state.currentScale > 1;
29
+ /**
30
+ * When user is zooming in, we don't navigate anymore.
31
+ * We still allow the pan gesture to move the zoomed controlled
32
+ * viewport even when pan navigation itself is disabled.
33
+ */
34
+ if (isZooming && isZoomingIn) {
35
+ const deltaX = event.deltaX - lastDelta.x;
36
+ const deltaY = event.deltaY - lastDelta.y;
37
+ lastDelta = {
38
+ x: event.deltaX,
39
+ y: event.deltaY
40
+ };
41
+ reader.zoom.move({
42
+ x: reader.zoom.state.currentPosition.x + deltaX,
43
+ y: reader.zoom.state.currentPosition.y + deltaY
44
+ }, { constrain: "within-viewport" });
45
+ return;
46
+ }
47
+ if (panNavigation !== "pan") return;
48
+ if (event.type === `panMove`) {
49
+ if (!reader.navigation.panNavigator.value.isStarted) {
50
+ reader.navigation.panNavigator.start({
51
+ x: event.deltaX,
52
+ y: event.deltaY
53
+ });
54
+ return;
55
+ }
56
+ reader.navigation.panNavigator.panMoveTo({
57
+ x: event.deltaX,
58
+ y: event.deltaY
59
+ });
60
+ return;
61
+ }
62
+ if (event.type === `panEnd` && reader.navigation.panNavigator.value.isStarted) reader.navigation.panNavigator.stop({
63
+ x: event.deltaX,
64
+ y: event.deltaY
65
+ });
66
+ }));
67
+ return merge(of(panStartEvent), moveAndEnd$).pipe(map((event) => ({
68
+ type: "pan",
69
+ gestureEvent: event
70
+ })));
71
+ }));
72
+ }));
81
73
  };
82
- const isHtmlImageElement = (target) => isHtmlElement(target) && !!target.ownerDocument.defaultView && target instanceof target.ownerDocument.defaultView.HTMLImageElement;
83
- const registerPinch = ({
84
- reader,
85
- recognizable,
86
- settingsManager
87
- }) => {
88
- const pinchStart$ = recognizable.events$.pipe(
89
- map(({ event }) => event),
90
- filter((event) => event.type === "pinchStart")
91
- );
92
- const pinchMove$ = recognizable.events$.pipe(
93
- map(({ event }) => event),
94
- filter((event) => event.type === "pinchMove")
95
- );
96
- const pinchEnd$ = recognizable.events$.pipe(
97
- map(({ event }) => event),
98
- filter((event) => event.type === "pinchEnd")
99
- );
100
- const shouldStartZoom = (target) => isHtmlImageElement(target) && !reader.zoom.state.isZooming;
101
- return settingsManager.values$.pipe(
102
- switchMap(({ fontScalePinchEnabled, fontScalePinchThrottleTime }) => {
103
- const zoomGestures$ = pinchStart$.pipe(
104
- switchMap(() => {
105
- const startScale = reader.zoom.state.currentScale;
106
- return pinchMove$.pipe(
107
- withLatestFrom(reader.viewportState$),
108
- map(([event, viewportState]) => {
109
- const newScale = startScale * event.scale;
110
- if (viewportState === "busy") {
111
- return event;
112
- }
113
- if (!reader.zoom.state.isZooming && event.scale > 1) {
114
- reader.zoom.enter({ animate: false, scale: newScale });
115
- return event;
116
- }
117
- if (reader.zoom.state.isZooming) {
118
- if (newScale < 1) {
119
- reader.zoom.exit();
120
- } else {
121
- reader.zoom.scaleAt(
122
- Math.min(newScale, settingsManager.values.zoomMaxScale),
123
- {
124
- constrain: "within-viewport"
125
- }
126
- );
127
- }
128
- return event;
129
- }
130
- return event;
131
- })
132
- );
133
- })
134
- );
135
- const watchForFontScaleChange$ = !fontScalePinchEnabled ? EMPTY : pinchStart$.pipe(
136
- withLatestFrom(reader.viewportState$),
137
- switchMap(([pinchStartEvent, viewportState]) => {
138
- if (viewportState === "busy" || shouldStartZoom(pinchStartEvent.event.target) || reader.zoom.state.isZooming)
139
- return EMPTY;
140
- const lastFontScaleOnPinchStart = reader.settings.values.fontScale;
141
- return pinchMove$.pipe(
142
- throttleTime(
143
- fontScalePinchThrottleTime,
144
- animationFrameScheduler,
145
- {
146
- trailing: true
147
- }
148
- ),
149
- tap((event) => {
150
- const newScale = Number.parseFloat(
151
- (lastFontScaleOnPinchStart + (event.scale - 1)).toFixed(2)
152
- );
153
- const newMinMaxedFontScale = Math.max(
154
- Math.min(
155
- newScale,
156
- settingsManager.values.fontScaleMaxScale
157
- ),
158
- settingsManager.values.fontScaleMinScale
159
- );
160
- reader.settings.update({
161
- fontScale: newMinMaxedFontScale
162
- });
163
- }),
164
- takeUntil(pinchEnd$)
165
- );
166
- })
167
- );
168
- return merge(zoomGestures$, watchForFontScaleChange$).pipe(
169
- map((event) => ({
170
- type: "pinch",
171
- gestureEvent: event
172
- }))
173
- );
174
- })
175
- );
74
+ //#endregion
75
+ //#region src/gestures/pinch.ts
76
+ var isHtmlImageElement = (target) => isHtmlElement(target) && !!target.ownerDocument.defaultView && target instanceof target.ownerDocument.defaultView.HTMLImageElement;
77
+ var registerPinch = ({ reader, recognizable, settingsManager }) => {
78
+ const pinchStart$ = recognizable.events$.pipe(map(({ event }) => event), filter((event) => event.type === "pinchStart"));
79
+ const pinchMove$ = recognizable.events$.pipe(map(({ event }) => event), filter((event) => event.type === "pinchMove"));
80
+ const pinchEnd$ = recognizable.events$.pipe(map(({ event }) => event), filter((event) => event.type === "pinchEnd"));
81
+ const shouldStartZoom = (target) => isHtmlImageElement(target) && !reader.zoom.state.isZooming;
82
+ return settingsManager.values$.pipe(switchMap(({ fontScalePinchEnabled, fontScalePinchThrottleTime }) => {
83
+ return merge(pinchStart$.pipe(switchMap(() => {
84
+ const startScale = reader.zoom.state.currentScale;
85
+ return pinchMove$.pipe(withLatestFrom(reader.viewportState$), map(([event, viewportState]) => {
86
+ const newScale = startScale * event.scale;
87
+ /**
88
+ * @important
89
+ * We don't want to trigger zoom gestures if there is a pan navigation
90
+ * in progress. This can happens if the user start panning and then adds
91
+ * another finger triggering a pinch.
92
+ */
93
+ if (viewportState === "busy") return event;
94
+ if (!reader.zoom.state.isZooming && event.scale > 1) {
95
+ reader.zoom.enter({
96
+ animate: false,
97
+ scale: newScale
98
+ });
99
+ return event;
100
+ }
101
+ if (reader.zoom.state.isZooming) {
102
+ if (newScale < 1) reader.zoom.exit();
103
+ else reader.zoom.scaleAt(Math.min(newScale, settingsManager.values.zoomMaxScale), { constrain: "within-viewport" });
104
+ return event;
105
+ }
106
+ return event;
107
+ }));
108
+ })), !fontScalePinchEnabled ? EMPTY : pinchStart$.pipe(withLatestFrom(reader.viewportState$), switchMap(([pinchStartEvent, viewportState]) => {
109
+ if (viewportState === "busy" || shouldStartZoom(pinchStartEvent.event.target) || reader.zoom.state.isZooming) return EMPTY;
110
+ const lastFontScaleOnPinchStart = reader.settings.values.fontScale;
111
+ return pinchMove$.pipe(throttleTime(fontScalePinchThrottleTime, animationFrameScheduler, { trailing: true }), tap((event) => {
112
+ const newScale = Number.parseFloat((lastFontScaleOnPinchStart + (event.scale - 1)).toFixed(2));
113
+ const newMinMaxedFontScale = Math.max(Math.min(newScale, settingsManager.values.fontScaleMaxScale), settingsManager.values.fontScaleMinScale);
114
+ reader.settings.update({ fontScale: newMinMaxedFontScale });
115
+ }), takeUntil(pinchEnd$));
116
+ }))).pipe(map((event) => ({
117
+ type: "pinch",
118
+ gestureEvent: event
119
+ })));
120
+ }));
176
121
  };
177
- const isSwipeEvent = (event) => event.type === "swipe";
178
- const registerSwipe = ({
179
- reader,
180
- recognizable,
181
- settingsManager
182
- }) => {
183
- const gestures$ = settingsManager.values$.pipe(
184
- switchMap(
185
- ({ panNavigation }) => panNavigation !== "swipe" ? EMPTY : recognizable.events$.pipe(
186
- map(({ event }) => event),
187
- filter(isSwipeEvent),
188
- tap((event) => {
189
- const { computedPageTurnDirection } = reader.settings.values;
190
- if (computedPageTurnDirection === "vertical") {
191
- if (event.velocityY < -0.5) {
192
- reader?.navigation.turnRight();
193
- }
194
- if (event.velocityY > 0.5) {
195
- reader?.navigation.turnLeft();
196
- }
197
- } else {
198
- if (event.velocityX < -0.5) {
199
- reader?.navigation.turnRight();
200
- }
201
- if (event.velocityX > 0.5) {
202
- reader?.navigation.turnLeft();
203
- }
204
- }
205
- }),
206
- map((event) => ({ type: "swipe", gestureEvent: event }))
207
- )
208
- )
209
- );
210
- return gestures$;
122
+ //#endregion
123
+ //#region src/gestures/swipe.ts
124
+ var isSwipeEvent = (event) => event.type === "swipe";
125
+ var registerSwipe = ({ reader, recognizable, settingsManager }) => {
126
+ return settingsManager.values$.pipe(switchMap(({ panNavigation }) => panNavigation !== "swipe" ? EMPTY : recognizable.events$.pipe(map(({ event }) => event), filter(isSwipeEvent), tap((event) => {
127
+ const { computedPageTurnDirection } = reader.settings.values;
128
+ if (computedPageTurnDirection === "vertical") {
129
+ if (event.velocityY < -.5) reader?.navigation.turnRight();
130
+ if (event.velocityY > .5) reader?.navigation.turnLeft();
131
+ } else {
132
+ if (event.velocityX < -.5) reader?.navigation.turnRight();
133
+ if (event.velocityX > .5) reader?.navigation.turnLeft();
134
+ }
135
+ }), map((event) => ({
136
+ type: "swipe",
137
+ gestureEvent: event
138
+ })))));
211
139
  };
212
- const isNotLink = (event) => {
213
- const target = event.event.target;
214
- if (isHtmlElement(target) && target.tagName === "a") return false;
215
- return true;
140
+ //#endregion
141
+ //#region src/utils.ts
142
+ var isNotLink = (event) => {
143
+ const target = event.event.target;
144
+ if (isHtmlElement(target) && target.tagName === "a") return false;
145
+ return true;
216
146
  };
217
- const getPositionRelativeToContainer = (event, containerElementRect) => {
218
- const { x, y } = event;
219
- const { left, top } = containerElementRect;
220
- return {
221
- x: x - left,
222
- y: y - top
223
- };
147
+ var getPositionRelativeToContainer = (event, containerElementRect) => {
148
+ const { x, y } = event;
149
+ const { left, top } = containerElementRect;
150
+ return {
151
+ x: x - left,
152
+ y: y - top
153
+ };
224
154
  };
225
- const istMatchingSelectors = (selectors, event) => {
226
- const target = event.event.target;
227
- if (!isHtmlElement(target)) return false;
228
- const match = selectors.find((selector) => {
229
- if (target.matches(selector)) return true;
230
- if (target.closest(selector)) return true;
231
- return false;
232
- });
233
- return !!match;
155
+ var istMatchingSelectors = (selectors, event) => {
156
+ const target = event.event.target;
157
+ if (!isHtmlElement(target)) return false;
158
+ return !!selectors.find((selector) => {
159
+ if (target.matches(selector)) return true;
160
+ if (target.closest(selector)) return true;
161
+ return false;
162
+ });
234
163
  };
235
- const isPositionInArea = (position, area, containerSize) => {
236
- const { x, y } = position;
237
- const { width, height } = containerSize;
238
- switch (area.type) {
239
- case "margins": {
240
- const { top, bottom, left, right } = area;
241
- const inTop = top !== void 0 ? y < height * top : true;
242
- const inBottom = bottom !== void 0 ? y > height * (1 - bottom) : true;
243
- const inLeft = left !== void 0 ? x < width * left : true;
244
- const inRight = right !== void 0 ? x > width * (1 - right) : true;
245
- return top !== void 0 && inTop || bottom !== void 0 && inBottom || left !== void 0 && inLeft || right !== void 0 && inRight;
246
- }
247
- case "rectangle": {
248
- const {
249
- x: rectX,
250
- y: rectY,
251
- width: rectWidth,
252
- height: rectHeight,
253
- unit = "%"
254
- } = area;
255
- const actualX = unit === "%" ? width * (rectX / 100) : rectX;
256
- const actualY = unit === "%" ? height * (rectY / 100) : rectY;
257
- const actualWidth = unit === "%" ? width * (rectWidth / 100) : rectWidth;
258
- const actualHeight = unit === "%" ? height * (rectHeight / 100) : rectHeight;
259
- return x >= actualX && x <= actualX + actualWidth && y >= actualY && y <= actualY + actualHeight;
260
- }
261
- case "corner": {
262
- const { corner, size, unit = "%" } = area;
263
- const actualSize = unit === "%" ? Math.min(width, height) * (size / 100) : size;
264
- switch (corner) {
265
- case "top-left":
266
- return x < actualSize && y < actualSize;
267
- case "top-right":
268
- return x > width - actualSize && y < actualSize;
269
- case "bottom-left":
270
- return x < actualSize && y > height - actualSize;
271
- case "bottom-right":
272
- return x > width - actualSize && y > height - actualSize;
273
- default:
274
- return false;
275
- }
276
- }
277
- case "center": {
278
- const { width: centerWidth, height: centerHeight, unit = "%" } = area;
279
- const actualWidth = unit === "%" ? width * (centerWidth / 100) : centerWidth;
280
- const actualHeight = unit === "%" ? height * (centerHeight / 100) : centerHeight;
281
- const centerX = width / 2;
282
- const centerY = height / 2;
283
- return x >= centerX - actualWidth / 2 && x <= centerX + actualWidth / 2 && y >= centerY - actualHeight / 2 && y <= centerY + actualHeight / 2;
284
- }
285
- default:
286
- return false;
287
- }
164
+ //#endregion
165
+ //#region src/gestures/taps/utils.ts
166
+ var isPositionInArea = (position, area, containerSize) => {
167
+ const { x, y } = position;
168
+ const { width, height } = containerSize;
169
+ switch (area.type) {
170
+ case "margins": {
171
+ const { top, bottom, left, right } = area;
172
+ const inTop = top !== void 0 ? y < height * top : true;
173
+ const inBottom = bottom !== void 0 ? y > height * (1 - bottom) : true;
174
+ const inLeft = left !== void 0 ? x < width * left : true;
175
+ const inRight = right !== void 0 ? x > width * (1 - right) : true;
176
+ return top !== void 0 && inTop || bottom !== void 0 && inBottom || left !== void 0 && inLeft || right !== void 0 && inRight;
177
+ }
178
+ case "rectangle": {
179
+ const { x: rectX, y: rectY, width: rectWidth, height: rectHeight, unit = "%" } = area;
180
+ const actualX = unit === "%" ? width * (rectX / 100) : rectX;
181
+ const actualY = unit === "%" ? height * (rectY / 100) : rectY;
182
+ const actualWidth = unit === "%" ? width * (rectWidth / 100) : rectWidth;
183
+ const actualHeight = unit === "%" ? height * (rectHeight / 100) : rectHeight;
184
+ return x >= actualX && x <= actualX + actualWidth && y >= actualY && y <= actualY + actualHeight;
185
+ }
186
+ case "corner": {
187
+ const { corner, size, unit = "%" } = area;
188
+ const actualSize = unit === "%" ? Math.min(width, height) * (size / 100) : size;
189
+ switch (corner) {
190
+ case "top-left": return x < actualSize && y < actualSize;
191
+ case "top-right": return x > width - actualSize && y < actualSize;
192
+ case "bottom-left": return x < actualSize && y > height - actualSize;
193
+ case "bottom-right": return x > width - actualSize && y > height - actualSize;
194
+ default: return false;
195
+ }
196
+ }
197
+ case "center": {
198
+ const { width: centerWidth, height: centerHeight, unit = "%" } = area;
199
+ const actualWidth = unit === "%" ? width * (centerWidth / 100) : centerWidth;
200
+ const actualHeight = unit === "%" ? height * (centerHeight / 100) : centerHeight;
201
+ const centerX = width / 2;
202
+ const centerY = height / 2;
203
+ return x >= centerX - actualWidth / 2 && x <= centerX + actualWidth / 2 && y >= centerY - actualHeight / 2 && y <= centerY + actualHeight / 2;
204
+ }
205
+ default: return false;
206
+ }
288
207
  };
289
- const calculatePageTurnLinearMargin = (screenWidth) => {
290
- const minMargin = 0.15;
291
- const maxMargin = 0.2;
292
- const minWidth = 320;
293
- const maxWidth = 1200;
294
- if (screenWidth <= minWidth) return maxMargin;
295
- if (screenWidth >= maxWidth) return minMargin;
296
- const ratio = (screenWidth - minWidth) / (maxWidth - minWidth);
297
- return maxMargin - ratio * (maxMargin - minMargin);
208
+ var calculatePageTurnLinearMargin = (screenWidth) => {
209
+ const minMargin = .15;
210
+ const maxMargin = .2;
211
+ const minWidth = 320;
212
+ const maxWidth = 1200;
213
+ if (screenWidth <= minWidth) return maxMargin;
214
+ if (screenWidth >= maxWidth) return minMargin;
215
+ return maxMargin - (screenWidth - minWidth) / (maxWidth - minWidth) * (maxMargin - minMargin);
298
216
  };
299
- const registerTaps = ({
300
- reader,
301
- recognizable,
302
- hookManager,
303
- settingsManager,
304
- recognizer
305
- }) => {
306
- const gestures$ = recognizable.events$.pipe(
307
- filter((event) => event.recognizer === recognizer),
308
- withLatestFrom(reader.context.watch(`rootElement`), reader.spine.element$),
309
- switchMap(([{ event }, containerElement, spineElement]) => {
310
- if (!containerElement || !spineElement) return EMPTY;
311
- const normalizedEvent = event.event;
312
- const { computedPageTurnDirection, computedPageTurnMode } = reader.settings.values;
313
- if (event.type === "tap" && isNotLink(event) && !istMatchingSelectors(settingsManager.values.ignore, event)) {
314
- if (`x` in normalizedEvent) {
315
- const containerElementRect = containerElement.getBoundingClientRect();
316
- const width = containerElementRect.width;
317
- const pageTurnMargin = calculatePageTurnLinearMargin(width);
318
- const positionInContainer = getPositionRelativeToContainer(
319
- normalizedEvent,
320
- containerElementRect
321
- );
322
- const positionInSpineNonTransformed = reader.coordinates.getSpinePositionFromClientPosition(
323
- normalizedEvent
324
- );
325
- const spineItemPageInfo = positionInSpineNonTransformed ? reader.spine.locator.getSpineItemPagePositionFromSpinePosition(
326
- positionInSpineNonTransformed
327
- ) : void 0;
328
- const beforeTapResults$ = hookManager.execute("beforeTapGesture", {
329
- event$: of({ event, page: spineItemPageInfo })
330
- });
331
- return combineLatest([...beforeTapResults$, of(true)]).pipe(
332
- first(),
333
- filter((results) => !results.some((result) => result === false)),
334
- map(() => {
335
- const isZoomedIn = reader.zoom.state.isZooming && reader.zoom.state.currentScale > 1;
336
- if (computedPageTurnMode === "scrollable" || isZoomedIn) {
337
- return {
338
- type: "tap",
339
- gestureEvent: event,
340
- handled: false
341
- };
342
- }
343
- if (computedPageTurnDirection === "horizontal" && isPositionInArea(
344
- positionInContainer,
345
- { type: "margins", left: pageTurnMargin },
346
- containerElementRect
347
- )) {
348
- reader.navigation.turnLeftOrTop();
349
- } else if (computedPageTurnDirection === "vertical" && isPositionInArea(
350
- positionInContainer,
351
- { type: "margins", top: pageTurnMargin },
352
- containerElementRect
353
- )) {
354
- reader.navigation.turnLeftOrTop();
355
- } else if (computedPageTurnDirection === "vertical" && isPositionInArea(
356
- positionInContainer,
357
- { type: "margins", bottom: pageTurnMargin },
358
- containerElementRect
359
- )) {
360
- reader.navigation.turnRightOrBottom();
361
- } else if (computedPageTurnDirection === "horizontal" && isPositionInArea(
362
- positionInContainer,
363
- { type: "margins", right: pageTurnMargin },
364
- containerElementRect
365
- )) {
366
- reader.navigation.turnRightOrBottom();
367
- } else {
368
- return {
369
- type: "tap",
370
- gestureEvent: event,
371
- handled: false
372
- };
373
- }
374
- return {
375
- type: "tap",
376
- gestureEvent: event,
377
- handled: true
378
- };
379
- })
380
- );
381
- }
382
- }
383
- return EMPTY;
384
- })
385
- );
386
- return gestures$;
217
+ //#endregion
218
+ //#region src/gestures/taps/registerTaps.ts
219
+ var registerTaps = ({ reader, recognizable, hookManager, settingsManager, recognizer }) => {
220
+ return recognizable.events$.pipe(filter((event) => event.recognizer === recognizer), withLatestFrom(reader.context.watch(`rootElement`), reader.spine.element$), switchMap(([{ event }, containerElement, spineElement]) => {
221
+ if (!containerElement || !spineElement) return EMPTY;
222
+ const normalizedEvent = event.event;
223
+ const { computedPageTurnDirection, computedPageTurnMode } = reader.settings.values;
224
+ if (event.type === "tap" && isNotLink(event) && !istMatchingSelectors(settingsManager.values.ignore, event)) {
225
+ if (`x` in normalizedEvent) {
226
+ const containerElementRect = containerElement.getBoundingClientRect();
227
+ const width = containerElementRect.width;
228
+ const pageTurnMargin = calculatePageTurnLinearMargin(width);
229
+ const positionInContainer = getPositionRelativeToContainer(normalizedEvent, containerElementRect);
230
+ const positionInSpineNonTransformed = reader.coordinates.getSpinePositionFromClientPosition(normalizedEvent);
231
+ const spineItemPageInfo = positionInSpineNonTransformed ? reader.spine.locator.getSpineItemPagePositionFromSpinePosition(positionInSpineNonTransformed) : void 0;
232
+ return combineLatest([...hookManager.execute("beforeTapGesture", { event$: of({
233
+ event,
234
+ page: spineItemPageInfo
235
+ }) }), of(true)]).pipe(first(), filter((results) => !results.some((result) => result === false)), map(() => {
236
+ const isZoomedIn = reader.zoom.state.isZooming && reader.zoom.state.currentScale > 1;
237
+ if (computedPageTurnMode === "scrollable" || isZoomedIn) return {
238
+ type: "tap",
239
+ gestureEvent: event,
240
+ handled: false
241
+ };
242
+ if (computedPageTurnDirection === "horizontal" && isPositionInArea(positionInContainer, {
243
+ type: "margins",
244
+ left: pageTurnMargin
245
+ }, containerElementRect)) reader.navigation.turnLeftOrTop();
246
+ else if (computedPageTurnDirection === "vertical" && isPositionInArea(positionInContainer, {
247
+ type: "margins",
248
+ top: pageTurnMargin
249
+ }, containerElementRect)) reader.navigation.turnLeftOrTop();
250
+ else if (computedPageTurnDirection === "vertical" && isPositionInArea(positionInContainer, {
251
+ type: "margins",
252
+ bottom: pageTurnMargin
253
+ }, containerElementRect)) reader.navigation.turnRightOrBottom();
254
+ else if (computedPageTurnDirection === "horizontal" && isPositionInArea(positionInContainer, {
255
+ type: "margins",
256
+ right: pageTurnMargin
257
+ }, containerElementRect)) reader.navigation.turnRightOrBottom();
258
+ else return {
259
+ type: "tap",
260
+ gestureEvent: event,
261
+ handled: false
262
+ };
263
+ return {
264
+ type: "tap",
265
+ gestureEvent: event,
266
+ handled: true
267
+ };
268
+ }));
269
+ }
270
+ }
271
+ return EMPTY;
272
+ }));
387
273
  };
388
- class GesturesSettingsManager extends SettingsManager {
389
- constructor(initialSettings, reader) {
390
- super(initialSettings);
391
- this.reader = reader;
392
- reader.settings.values$.pipe(
393
- tap(() => {
394
- this.update({});
395
- }),
396
- takeUntil(this.destroy$)
397
- ).subscribe();
398
- }
399
- reader;
400
- getOutputSettings(inputSettings) {
401
- return {
402
- ...inputSettings,
403
- panNavigation: this.reader.settings.values.computedPageTurnMode === `scrollable` ? false : inputSettings.panNavigation
404
- };
405
- }
406
- getDefaultSettings() {
407
- return {
408
- panNavigation: "pan",
409
- pinchCancelPan: true,
410
- fontScalePinchEnabled: true,
411
- fontScalePinchThrottleTime: 500,
412
- fontScaleMaxScale: 5,
413
- fontScaleMinScale: 0.2,
414
- zoomMaxScale: Infinity,
415
- ignore: []
416
- };
417
- }
418
- }
419
- const styles = '[data-prose-reader-container="${id}"] * {\n /* Make sure that touche actions are correctly dispatched no matter where the user interact */\n touch-action: inherit;\n}\n';
420
- const gesturesEnhancer = (next) => (options) => {
421
- const { gestures = {}, ...rest } = options;
422
- const reader = next(rest);
423
- const removeStylesheet = reader.utils.injectScopedCSS(
424
- document,
425
- name,
426
- styles
427
- );
428
- const settingsManager = new GesturesSettingsManager(gestures, reader);
429
- const hookManager = new HookManager();
430
- const pinchRecognizer = new PinchRecognizer({
431
- options: {
432
- /**
433
- * @important
434
- * Ideally we want pinch to triggers before pan so we can
435
- * capture zoom before starting panning.
436
- */
437
- posThreshold: 10
438
- }
439
- });
440
- const failWithSelection = {
441
- start$: reader.selection.selectionStart$,
442
- end$: reader.selection.selectionEnd$
443
- };
444
- const panRecognizer = new PanRecognizer({
445
- failWith: [pinchRecognizer, failWithSelection],
446
- options: {
447
- // we want to have some margin to trigger zoom
448
- posThreshold: 20
449
- }
450
- });
451
- const tapRecognizer = new TapRecognizer({
452
- failWith: [panRecognizer]
453
- });
454
- const swipeRecognizer = new SwipeRecognizer({
455
- failWith: [failWithSelection]
456
- });
457
- const recognizable = new Recognizable({
458
- recognizers: [
459
- tapRecognizer,
460
- panRecognizer,
461
- swipeRecognizer,
462
- pinchRecognizer
463
- ],
464
- disableTextSelection: false
465
- });
466
- const tapGestures$ = registerTaps({
467
- hookManager,
468
- reader,
469
- recognizable,
470
- settingsManager,
471
- recognizer: tapRecognizer
472
- });
473
- const panGestures$ = registerPan({
474
- reader,
475
- recognizer: panRecognizer,
476
- settingsManager
477
- });
478
- const swipeGestures$ = registerSwipe({
479
- reader,
480
- recognizable,
481
- settingsManager
482
- });
483
- const pinchGestures$ = registerPinch({
484
- reader,
485
- recognizable,
486
- settingsManager
487
- });
488
- const containerUpdate$ = reader.context.watch(`rootElement`).pipe(
489
- tap((container) => {
490
- recognizable.update({
491
- container
492
- });
493
- })
494
- );
495
- const watchSettings$ = combineLatest([
496
- settingsManager.values$,
497
- panRecognizer.config$
498
- ]).pipe(
499
- tap(([{ pinchCancelPan }, panRecognizerConfig]) => {
500
- const pinchAlreadyInFailWith = panRecognizerConfig.failWith?.includes(pinchRecognizer);
501
- if (pinchCancelPan && !pinchAlreadyInFailWith) {
502
- panRecognizer.update({
503
- failWith: [
504
- ...panRecognizerConfig.failWith ?? [],
505
- pinchRecognizer
506
- ]
507
- });
508
- }
509
- if (!pinchCancelPan && pinchAlreadyInFailWith) {
510
- panRecognizer.update({
511
- failWith: panRecognizerConfig.failWith?.filter(
512
- (recognizer) => recognizer !== pinchRecognizer
513
- )
514
- });
515
- }
516
- })
517
- );
518
- const gestures$ = merge(
519
- pinchGestures$,
520
- tapGestures$,
521
- swipeGestures$,
522
- panGestures$
523
- ).pipe(share());
524
- merge(containerUpdate$, watchSettings$, gestures$).pipe(takeUntil(reader.$.destroy$)).subscribe();
525
- return {
526
- ...reader,
527
- destroy: () => {
528
- removeStylesheet();
529
- reader.destroy();
530
- settingsManager.destroy();
531
- },
532
- gestures: {
533
- settings: settingsManager,
534
- gestures$,
535
- hooks: hookManager
536
- }
537
- };
274
+ //#endregion
275
+ //#region src/SettingsManager.ts
276
+ var GesturesSettingsManager = class extends SettingsManager {
277
+ reader;
278
+ constructor(initialSettings, reader) {
279
+ super(initialSettings);
280
+ this.reader = reader;
281
+ /**
282
+ * Since we have settings that may be locked due to some reader settings
283
+ * we need to update as soon as they update as well.
284
+ */
285
+ reader.settings.values$.pipe(tap(() => {
286
+ this.update({});
287
+ }), takeUntil(this.destroy$)).subscribe();
288
+ }
289
+ getOutputSettings(inputSettings) {
290
+ return {
291
+ ...inputSettings,
292
+ panNavigation: this.reader.settings.values.computedPageTurnMode === `scrollable` ? false : inputSettings.panNavigation
293
+ };
294
+ }
295
+ getDefaultSettings() {
296
+ return {
297
+ panNavigation: "pan",
298
+ pinchCancelPan: true,
299
+ fontScalePinchEnabled: true,
300
+ fontScalePinchThrottleTime: 500,
301
+ fontScaleMaxScale: 5,
302
+ fontScaleMinScale: .2,
303
+ zoomMaxScale: Infinity,
304
+ ignore: []
305
+ };
306
+ }
538
307
  };
539
- export {
540
- gesturesEnhancer,
541
- isPositionInArea
308
+ //#endregion
309
+ //#region src/style.css?inline
310
+ var style_default = "[data-prose-reader-container=\"${id}\"] * {\n /* Make sure that touche actions are correctly dispatched no matter where the user interact */\n touch-action: inherit;\n}\n";
311
+ //#endregion
312
+ //#region src/index.ts
313
+ var gesturesEnhancer = (next) => (options) => {
314
+ const { gestures = {}, ...rest } = options;
315
+ const reader = next(rest);
316
+ const removeStylesheet = reader.utils.injectScopedCSS(document, name, style_default);
317
+ const settingsManager = new GesturesSettingsManager(gestures, reader);
318
+ const hookManager = new HookManager();
319
+ const pinchRecognizer = new PinchRecognizer({ options: {
320
+ /**
321
+ * @important
322
+ * Ideally we want pinch to triggers before pan so we can
323
+ * capture zoom before starting panning.
324
+ */
325
+ posThreshold: 10 } });
326
+ const failWithSelection = {
327
+ start$: reader.selection.selectionStart$,
328
+ end$: reader.selection.selectionEnd$
329
+ };
330
+ const panRecognizer = new PanRecognizer({
331
+ failWith: [pinchRecognizer, failWithSelection],
332
+ options: { posThreshold: 20 }
333
+ });
334
+ const tapRecognizer = new TapRecognizer({ failWith: [panRecognizer] });
335
+ const recognizable = new Recognizable({
336
+ recognizers: [
337
+ tapRecognizer,
338
+ panRecognizer,
339
+ new SwipeRecognizer({ failWith: [failWithSelection] }),
340
+ pinchRecognizer
341
+ ],
342
+ disableTextSelection: false
343
+ });
344
+ const tapGestures$ = registerTaps({
345
+ hookManager,
346
+ reader,
347
+ recognizable,
348
+ settingsManager,
349
+ recognizer: tapRecognizer
350
+ });
351
+ const panGestures$ = registerPan({
352
+ hookManager,
353
+ reader,
354
+ recognizer: panRecognizer,
355
+ settingsManager
356
+ });
357
+ const swipeGestures$ = registerSwipe({
358
+ hookManager,
359
+ reader,
360
+ recognizable,
361
+ settingsManager
362
+ });
363
+ const pinchGestures$ = registerPinch({
364
+ hookManager,
365
+ reader,
366
+ recognizable,
367
+ settingsManager
368
+ });
369
+ const containerUpdate$ = reader.context.watch(`rootElement`).pipe(tap((container) => {
370
+ recognizable.update({ container });
371
+ }));
372
+ const watchSettings$ = combineLatest([settingsManager.values$, panRecognizer.config$]).pipe(tap(([{ pinchCancelPan }, panRecognizerConfig]) => {
373
+ const pinchAlreadyInFailWith = panRecognizerConfig.failWith?.includes(pinchRecognizer);
374
+ if (pinchCancelPan && !pinchAlreadyInFailWith) panRecognizer.update({ failWith: [...panRecognizerConfig.failWith ?? [], pinchRecognizer] });
375
+ if (!pinchCancelPan && pinchAlreadyInFailWith) panRecognizer.update({ failWith: panRecognizerConfig.failWith?.filter((recognizer) => recognizer !== pinchRecognizer) });
376
+ }));
377
+ const gestures$ = merge(pinchGestures$, tapGestures$, swipeGestures$, panGestures$).pipe(share());
378
+ merge(containerUpdate$, watchSettings$, gestures$).pipe(takeUntil(reader.$.destroy$)).subscribe();
379
+ return {
380
+ ...reader,
381
+ destroy: () => {
382
+ removeStylesheet();
383
+ reader.destroy();
384
+ settingsManager.destroy();
385
+ },
386
+ gestures: {
387
+ settings: settingsManager,
388
+ gestures$,
389
+ hooks: hookManager
390
+ }
391
+ };
542
392
  };
543
- //# sourceMappingURL=index.js.map
393
+ //#endregion
394
+ export { gesturesEnhancer, isPositionInArea };
395
+
396
+ //# sourceMappingURL=index.js.map