diff --git a/docs/release-notes-player.txt b/docs/release-notes-player.txt index 28f2381bec79b1f46f032334b2255bb8d316b401..7df24784e9004cebbf3e80eb9798142a787719d6 100644 --- a/docs/release-notes-player.txt +++ b/docs/release-notes-player.txt @@ -3,6 +3,7 @@ Player 1.20.0 - Fix dimensions of images with dynamicPositioning and fixedSize - Fix display of fixed size dynamic elements on iPad +- Fix selecting and marking of text on iPad 1.19.0 - Remove border of slider rectangle in number line mode at position 0 diff --git a/projects/common/components/marking-bar/marking-button.component.ts b/projects/common/components/marking-bar/marking-button.component.ts index d09610b30d009fd31e9d7c87893d4e22c6e29652..208978aaa00d1a492ea56c0d91cc2a88026d7154 100644 --- a/projects/common/components/marking-bar/marking-button.component.ts +++ b/projects/common/components/marking-bar/marking-button.component.ts @@ -10,8 +10,8 @@ import { [style.border-color]="selected ? 'black' : color" mat-mini-fab [style.background-color]="color" - (mousedown)="$event.stopPropagation();" - (click)="selected = !selected; selectedChanged.emit({ selected, mode, color })"> + (mousedown)="emitSelectedChanged($event)" + (touchstart)="emitSelectedChanged($event)"> <mat-icon *ngIf="mode === 'mark'" class="marking-icon">border_color </mat-icon> @@ -35,4 +35,13 @@ export class MarkingButtonComponent { mode: 'mark' | 'delete', color: string, }>(); + + emitSelectedChanged(event: TouchEvent | MouseEvent): void { + if (event instanceof TouchEvent && event.cancelable) { + event.preventDefault(); + } + event.stopPropagation(); + this.selected = !this.selected; + this.selectedChanged.emit({ selected: this.selected, mode: this.mode, color: this.color }); + } } diff --git a/projects/common/ui-elements/text/text.component.ts b/projects/common/ui-elements/text/text.component.ts index 7b0c52612531f4111def440d3046d7327a329d7b..8af9f767e99cff6ca1ffe73c2888ba8a5db07203 100644 --- a/projects/common/ui-elements/text/text.component.ts +++ b/projects/common/ui-elements/text/text.component.ts @@ -14,11 +14,11 @@ import { ValueChangeElement } from '../../models/uI-element'; [style.width]="elementModel.positionProps.fixedSize ? elementModel.width + 'px' : '100%'" [style.height]="elementModel.positionProps.fixedSize ? elementModel.height + 'px' : 'auto'"> <aspect-marking-bar - *ngIf="elementModel.highlightableYellow || + *ngIf="elementModel.highlightableYellow || elementModel.highlightableTurquoise || elementModel.highlightableOrange" - [elementModel]="elementModel" - (selectionChanged)="onSelectionChanged($event)"> + [elementModel]="elementModel" + (selectionChanged)="onSelectionChanged($event)"> </aspect-marking-bar> <div #textContainerRef class="text-container" [class.orange-selection]="selectedColor === 'orange'" @@ -34,9 +34,8 @@ import { ValueChangeElement } from '../../models/uI-element'; [style.font-style]="elementModel.fontProps.italic ? 'italic' : ''" [style.text-decoration]="elementModel.fontProps.underline ? 'underline' : ''" [innerHTML]="elementModel.text | safeResourceHTML" - (mousedown)="elementModel.highlightableYellow || - elementModel.highlightableTurquoise || - elementModel.highlightableOrange ? startSelection.emit($event) : null"> + (touchstart)="emitStartSelection($event)" + (mousedown)="emitStartSelection($event)"> </div> </div> </div> @@ -59,7 +58,7 @@ import { ValueChangeElement } from '../../models/uI-element'; export class TextComponent extends ElementComponent { @Input() elementModel!: TextElement; @Output() elementValueChanged = new EventEmitter<ValueChangeElement>(); - @Output() startSelection = new EventEmitter<MouseEvent>(); + @Output() startSelection = new EventEmitter<MouseEvent | TouchEvent>(); @Output() applySelection = new EventEmitter<{ active: boolean, mode: 'mark' | 'delete', @@ -71,6 +70,14 @@ export class TextComponent extends ElementComponent { @ViewChild('textContainerRef') textContainerRef!: ElementRef; + emitStartSelection(event: TouchEvent | MouseEvent): void { + if (this.elementModel.highlightableYellow || + this.elementModel.highlightableTurquoise || + this.elementModel.highlightableOrange) { + this.startSelection.emit(event); + } + } + onSelectionChanged(selection: { active: boolean, mode: 'mark' | 'delete', diff --git a/projects/player/src/app/components/element-container/element-container.component.ts b/projects/player/src/app/components/element-container/element-container.component.ts index 995ecb212946b949d36bc2efa1c4d154f4613718..3977a571ed489be0bec67697f21f1b77eefc5f52 100644 --- a/projects/player/src/app/components/element-container/element-container.component.ts +++ b/projects/player/src/app/components/element-container/element-container.component.ts @@ -194,35 +194,58 @@ export class ElementContainerComponent implements OnInit { if (elementComponent.startSelection) { elementComponent.startSelection .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe((mouseDown: MouseEvent) => { + .subscribe((pointerDown: MouseEvent | TouchEvent) => { this.isMarkingBarOpen = false; - this.nativeEventService.mouseUp + + this.nativeEventService.pointerUp .pipe(takeUntil(this.ngUnsubscribe), first()) - .subscribe((mouseUp: MouseEvent) => this.stopSelection(mouseUp, mouseDown, elementComponent)); + .subscribe((pointerUp: TouchEvent | MouseEvent) => { + if (pointerUp.cancelable) { + pointerUp.preventDefault(); + } + this.stopSelection( + this.getClientPointFromEvent(pointerUp), + pointerUp.ctrlKey, + this.getClientPointFromEvent(pointerDown), + elementComponent + ); + }); }); } } - private stopSelection(mouseUp: MouseEvent, mouseDown: MouseEvent, elementComponent: TextComponent) { + private stopSelection( + mouseUp: { clientX: number, clientY: number }, + ctrlKey: boolean, + downPosition: { clientX: number, clientY: number }, + elementComponent: TextComponent + ) { const selection = window.getSelection(); if (selection && TextMarker.isSelectionValid(selection) && selection.rangeCount > 0) { - if (!TextMarker.isRangeInside(selection.getRangeAt(0), - elementComponent.textContainerRef.nativeElement) || - (mouseUp.ctrlKey)) { + if (!TextMarker.isRangeInside(selection.getRangeAt(0), elementComponent.textContainerRef.nativeElement) || + (ctrlKey)) { selection.removeAllRanges(); } else if (this.selectedMode && this.selectedColor) { this.applySelectionToText(this.selectedMode, this.selectedColor); } else if (!this.isMarkingBarOpen) { - this.openMarkingBar(mouseUp, mouseDown); + this.openMarkingBar(mouseUp, downPosition); } } } - private openMarkingBar(mouseUp: MouseEvent, mouseDown: MouseEvent) { - this.markingBarPosition.left = mouseDown.clientY > mouseUp.clientY ? mouseDown.clientX : mouseUp.clientX; - this.markingBarPosition.top = mouseDown.clientY > mouseUp.clientY ? mouseDown.clientY : mouseUp.clientY; + private getClientPointFromEvent = (event: MouseEvent | TouchEvent): { clientX: number, clientY: number } => ({ + clientX: (event instanceof MouseEvent) ? event.clientX : event.changedTouches[0].clientX, + clientY: (event instanceof MouseEvent) ? event.clientY : event.changedTouches[0].clientY + }); + + private openMarkingBar( + mouseUp: { clientX: number, clientY: number }, + downPosition: { clientX: number, clientY: number } + ) { + this.markingBarPosition.left = downPosition.clientY > mouseUp.clientY ? downPosition.clientX : mouseUp.clientX; + this.markingBarPosition.top = downPosition.clientY > mouseUp.clientY ? downPosition.clientY : mouseUp.clientY; this.isMarkingBarOpen = true; - this.nativeEventService.mouseDown + this.nativeEventService.pointerDown .pipe(takeUntil(this.ngUnsubscribe), first()) .subscribe(() => this.closeMarkingBar()); } @@ -235,12 +258,13 @@ export class ElementContainerComponent implements OnInit { if (elementComponent.applySelection) { elementComponent.applySelection .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe((selection: - { - active: boolean, - mode: 'mark' | 'delete', - color: string; - }) => { + .subscribe(( + selection: { + active: boolean, + mode: 'mark' | 'delete', + color: string; + } + ) => { if (selection.active) { this.selectedColor = selection.color; this.selectedMode = selection.mode; diff --git a/projects/player/src/app/services/native-event.service.ts b/projects/player/src/app/services/native-event.service.ts index a7b019f8e87a5dde7f55c0257b7745764042bb7a..07bd9cb8a605fe0f4aa888182c24354ed6cd6dac 100644 --- a/projects/player/src/app/services/native-event.service.ts +++ b/projects/player/src/app/services/native-event.service.ts @@ -10,8 +10,8 @@ import { mergeMap } from 'rxjs/operators'; }) export class NativeEventService { private _focus = new Subject<boolean>(); - private _mouseUp = new Subject<MouseEvent>(); - private _mouseDown = new Subject<MouseEvent>(); + private _pointerUp = new Subject<MouseEvent | TouchEvent>(); + private _pointerDown = new Subject<MouseEvent | TouchEvent>(); private _resize = new Subject<number>(); constructor(@Inject(DOCUMENT) private document: Document) { @@ -23,15 +23,33 @@ export class NativeEventService { () => this._focus.next(document.hasFocus())// Do something with the event here ); - fromEvent(window, 'mouseup') - .subscribe((mouseEvent: Event) => { - this._mouseUp.next(mouseEvent as MouseEvent); - }); + from(['mouseup', 'touchend']) + .pipe( + mergeMap(event => fromEvent(window, event)) + ) + .subscribe( + (event: Event) => { + if (event instanceof TouchEvent) { + this._pointerUp.next(event as TouchEvent); + } else { + this._pointerUp.next(event as MouseEvent); + } + } + ); - fromEvent(window, 'mousedown') - .subscribe((mouseEvent: Event) => { - this._mouseDown.next(mouseEvent as MouseEvent); - }); + from(['mousedown', 'touchstart']) + .pipe( + mergeMap(event => fromEvent(window, event)) + ) + .subscribe( + (event: Event) => { + if (event instanceof TouchEvent) { + this._pointerDown.next(event as TouchEvent); + } else { + this._pointerDown.next(event as MouseEvent); + } + } + ); fromEvent(window, 'resize') .subscribe(() => this._resize.next(window.innerWidth)); @@ -41,12 +59,12 @@ export class NativeEventService { return this._focus.asObservable(); } - get mouseUp(): Observable<MouseEvent> { - return this._mouseUp.asObservable(); + get pointerUp(): Observable<MouseEvent | TouchEvent> { + return this._pointerUp.asObservable(); } - get mouseDown(): Observable<MouseEvent> { - return this._mouseDown.asObservable(); + get pointerDown(): Observable<MouseEvent | TouchEvent> { + return this._pointerDown.asObservable(); } get resize(): Observable<number> {