Skip to content
Closed
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 60 additions & 3 deletions packages/ckeditor5-link/src/linkui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
type ViewAttributeElement,
type ViewDocumentClickEvent,
type ViewElement,
type ViewPosition
type ViewPosition,
type ViewDocumentTouchStartEvent
} from 'ckeditor5/src/engine.js';
import {
ButtonView,
Expand All @@ -28,7 +29,7 @@ import {
type ButtonExecuteEvent
} from 'ckeditor5/src/ui.js';

import { Collection, type ObservableChangeEvent, type PositionOptions } from 'ckeditor5/src/utils.js';
import { Collection, env, type ObservableChangeEvent, type PositionOptions } from 'ckeditor5/src/utils.js';
import { isWidget } from 'ckeditor5/src/widget.js';

import LinkEditing from './linkediting.js';
Expand Down Expand Up @@ -708,6 +709,62 @@ export default class LinkUI extends Plugin {
const editor = this.editor;
const viewDocument = editor.editing.view.document;

if ( env.isiOS ) {
this.listenTo<ViewDocumentTouchStartEvent>( viewDocument, 'touchstart', ( evt, data ) => {
// Get the view element directly from the event data.
const targetViewElement = data.target;

// If target is not an element, exit early.
if ( !targetViewElement.is( 'element' ) ) {
return;
}

// Find the closest link element (could be the target itself or one of its ancestors).
let linkElement: ViewElement | null = null;

if ( isLinkElement( targetViewElement ) ) {
linkElement = targetViewElement;
} else {
linkElement = targetViewElement.getAncestors().find( ancestor => isLinkElement( ancestor ) ) as ViewElement | null;
}

// If no link element found, exit early.
if ( !linkElement ) {
return;
}

// Check if touch happened in the middle of the link.
const domElement = editor.editing.view.domConverter.mapViewToDom( linkElement );
if ( !domElement || !data.domEvent ) {
return;
}

const rect = domElement.getBoundingClientRect();
const touchX = data.domEvent.touches[ 0 ].clientX;

// Define edge threshold in pixels.
const edgeThresholdPx = 10;

// Consider it a middle click if not on left or right edge.
const isNotLeftEdge = touchX > ( rect.left + edgeThresholdPx );
const isNotRightEdge = touchX < ( rect.right - edgeThresholdPx );
const isMiddleLinkClick = isNotLeftEdge && isNotRightEdge;

// If not a middle click, exit early.
if ( !isMiddleLinkClick ) {
return;
}

// Set the selection to the end of the link.
editor.model.change( writer => {
const viewRange = editor.editing.view.createPositionAt( linkElement!, 'end' );
const modelPosition = editor.editing.mapper.toModelPosition( viewRange );

writer.setSelection( modelPosition );
} );
} );
}

// Handle click on view document and show panel when selection is placed inside the link element.
// Keep panel open until selection will be inside the same link element.
this.listenTo<ViewDocumentClickEvent>( viewDocument, 'click', () => {
Expand Down Expand Up @@ -1204,7 +1261,7 @@ export default class LinkUI extends Plugin {
* selected and the **only** element within the selection boundaries, or when
* a linked widget is selected.
*/
private _getSelectedLinkElement(): ViewAttributeElement | null {
private _getSelectedLinkElement(): ViewElement | null {
const view = this.editor.editing.view;
const selection = view.document.selection;
const selectedElement = selection.getSelectedElement();
Expand Down