Skip to content

Commit

Permalink
Active element handling updated to work in shadow DOM.
Browse files Browse the repository at this point in the history
  • Loading branch information
niegowski committed Sep 6, 2024
1 parent 3c1d883 commit fedf8be
Show file tree
Hide file tree
Showing 15 changed files with 41 additions and 17 deletions.
2 changes: 2 additions & 0 deletions packages/ckeditor5-ckbox/src/ckboxcommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ export default class CKBoxCommand extends Command {
}

// TODO ShadowRoot
// - can we append it to the body collection?
// - does CKBox support Shadow DOM?
this._wrapper = createElement( document, 'div', { class: 'ck ckbox-wrapper' } );
document.body.appendChild( this._wrapper );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export default class CKBoxImageEditCommand extends Command {
}

// TODO ShadowRoot
// - can we append it to the body collection?
// - does CKBox support Shadow DOM?
const wrapper = createElement( document, 'div', { class: 'ck ckbox-wrapper' } );

this._wrapper = wrapper;
Expand Down
2 changes: 2 additions & 0 deletions packages/ckeditor5-clipboard/src/dragdrop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,8 @@ export default class DragDrop extends Plugin {
} );

// TODO ShadowRoot
// - can we append it to the body collection?
// - is the preview generated correctly in the Shadow DOM
global.document.body.appendChild( this._previewContainer );
} else if ( this._previewContainer.firstElementChild ) {
this._previewContainer.removeChild( this._previewContainer.firstElementChild );
Expand Down
7 changes: 6 additions & 1 deletion packages/ckeditor5-clipboard/src/dragdropblocktoolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ export default class DragDropBlockToolbar extends Plugin {
const element = blockToolbar.buttonView.element!;

this._domEmitter.listenTo( element, 'dragstart', ( evt, data ) => this._handleBlockDragStart( data ) );

// TODO ShadowRoot
// - those events will propagate across the shadow DOM boundary (bubbles and composed flags set)
this._domEmitter.listenTo( global.document, 'dragover', ( evt, data ) => this._handleBlockDragging( data ) );
this._domEmitter.listenTo( global.document, 'drop', ( evt, data ) => this._handleBlockDragging( data ) );
this._domEmitter.listenTo( global.document, 'dragend', () => this._handleBlockDragEnd(), { useCapture: true } );
Expand Down Expand Up @@ -133,7 +135,10 @@ export default class DragDropBlockToolbar extends Plugin {

let target = document.elementFromPoint( clientX, clientY );

// TODO ShadowRoot - this is a workaround, works this way only in open shadow root
// TODO ShadowRoot
// - this is a workaround, works this way only in open shadow root
// - we should use map of known shadow roots and not depend on the shadowRoot property (it's there only for open mode)
// - the ShadowRoot#elementFromPoint() is non-standard but available in all browsers.
if ( target && target.shadowRoot && target.shadowRoot.elementFromPoint ) {
target = target.shadowRoot.elementFromPoint( clientX, clientY );
}
Expand Down
2 changes: 2 additions & 0 deletions packages/ckeditor5-clipboard/src/dragdroptarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,8 @@ function findScrollableElement( domNode: HTMLElement ): HTMLElement {
let domElement: HTMLElement = domNode;

do {
// TODO ShadowRoot
// - use helper for easier parent element access
domElement = domElement.parentNode instanceof ShadowRoot ?
domElement.parentNode.host as HTMLElement :
domElement.parentElement!;
Expand Down
5 changes: 3 additions & 2 deletions packages/ckeditor5-engine/src/view/domconverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,8 @@ export default class DomConverter {
public focus( viewEditable: EditableElement ): void {
const domEditable = this.mapViewToDom( viewEditable );

if ( domEditable && domEditable.ownerDocument.activeElement !== domEditable ) {
// TODO ShadowRoot
if ( domEditable && domEditable.getRootNode().activeElement !== domEditable ) {
// Save the scrollX and scrollY positions before the focus.
const { scrollX, scrollY } = global.window;
const scrollPositions: Array<[ number, number ]> = [];
Expand Down Expand Up @@ -1850,7 +1851,7 @@ function forEachDomElementAncestor( element: DomElement, callback: ( node: DomEl

while ( node ) {
callback( node );
node = node.parentElement;
node = node.parentElement; // TODO ShadowRoot
}
}

Expand Down
19 changes: 11 additions & 8 deletions packages/ckeditor5-engine/src/view/observer/selectionobserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,6 @@ export default class SelectionObserver extends Observer {
* @inheritDoc
*/
public override observe( domElement: HTMLElement ): void {
const domDocument = domElement.ownerDocument;

const startDocumentIsSelecting = () => {
this.document.isSelecting = true;

Expand All @@ -131,7 +129,7 @@ export default class SelectionObserver extends Observer {

// Make sure that model selection is up-to-date at the end of selecting process.
// Sometimes `selectionchange` events could arrive after the `mouseup` event and that selection could be already outdated.
this._handleSelectionChange( domDocument );
this._handleSelectionChange( domElement );

this.document.isSelecting = false;

Expand All @@ -147,16 +145,19 @@ export default class SelectionObserver extends Observer {
this.listenTo( domElement, 'keydown', endDocumentIsSelecting, { priority: 'highest', useCapture: true } );
this.listenTo( domElement, 'keyup', endDocumentIsSelecting, { priority: 'highest', useCapture: true } );

const domDocument = domElement.ownerDocument;

// Add document-wide listeners only once. This method could be called for multiple editing roots.
// TODO ShadowRoot
if ( this._documents.has( domDocument ) ) {
return;
}

// This listener is using capture mode to make sure that selection is upcasted before any other
// handler would like to check it and update (for example table multi cell selection).
// TODO ShadowRoot - this event will propagate across the shadow DOM boundary (bubbles and composed flags set)
this.listenTo( domDocument, 'mouseup', endDocumentIsSelecting, { priority: 'highest', useCapture: true } );

// TODO ShadowRoot - this event is always fired from the document, even inside a Shadow DOM.
this.listenTo( domDocument, 'selectionchange', ( evt, domEvent ) => {
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
// @if CK_DEBUG_TYPING // _debouncedLine();
Expand All @@ -182,6 +183,7 @@ export default class SelectionObserver extends Observer {
return;
}

// TODO ShadowRoot - this will not work if separate roots are in separate shadow DOMs
this._handleSelectionChange( domElement );

// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
Expand All @@ -207,7 +209,8 @@ export default class SelectionObserver extends Observer {
// @if CK_DEBUG_TYPING // );
// @if CK_DEBUG_TYPING // }

this._handleSelectionChange( domDocument );
// TODO ShadowRoot - this will not work if separate roots are in separate shadow DOMs
this._handleSelectionChange( domElement );

// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
// @if CK_DEBUG_TYPING // console.groupEnd();
Expand Down Expand Up @@ -248,14 +251,14 @@ export default class SelectionObserver extends Observer {
* a selection changes and fires {@link module:engine/view/document~Document#event:selectionChange} event on every change
* and {@link module:engine/view/document~Document#event:selectionChangeDone} when a selection stop changing.
*
* @param domDocument DOM document.
* @param domElement DOM element.
*/
private _handleSelectionChange( domDocument: Document ) {
private _handleSelectionChange( domElement: HTMLElement ) {
if ( !this.isEnabled ) {
return;
}

const domSelection = getSelection( domDocument )!;
const domSelection = getSelection( domElement )!;

if ( this.checkShouldIgnoreEventFromTarget( domSelection.anchorNode! ) ) {
return;
Expand Down
1 change: 1 addition & 0 deletions packages/ckeditor5-engine/src/view/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,7 @@ export default class Renderer extends /* #__PURE__ */ ObservableMixin() {
const domSelection = doc.getSelection()!;

if ( domSelection.rangeCount ) {
// TODO ShadowRoot - the activeElement of the closest ShadowRoot?
const activeDomElement = doc.activeElement!;
const viewElement = this.domConverter.mapDomToView( activeDomElement as DomElement );

Expand Down
5 changes: 3 additions & 2 deletions packages/ckeditor5-ui/src/bindings/clickoutsidehandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ export default function clickOutsideHandler(

// Check if `composedPath` is `undefined` in case the browser does not support native shadow DOM.
// Can be removed when all supported browsers support native shadow DOM.
// TODO ShadowRoot This won't work for closed shadow root.
// We probably should listen to all shadow roots we know of and have access to.
// TODO ShadowRoot
// - This won't work for closed shadow root.
// - We probably should listen to all shadow roots we know of and have access to.
const path = typeof domEvt.composedPath == 'function' ? domEvt.composedPath() : [];

const contextElementsList = typeof contextElements == 'function' ? contextElements() : contextElements;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,6 @@ export default class DropdownMenuNestedMenuView extends View implements Focusabl
keystrokes.listenTo( panelView.element! );
panelView.pin( {
positions: this._panelPositions,
// TODO ShadowRoot
limiter: global.document.body,
element: panelView.element!,
target: buttonView.element!,
Expand Down
4 changes: 2 additions & 2 deletions packages/ckeditor5-ui/src/dropdown/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,8 +606,8 @@ function focusDropdownButtonOnClose( dropdownView: DropdownView ) {
// If the dropdown was closed, move the focus back to the button (#12125).
// Don't touch the focus, if it moved somewhere else (e.g. moved to the editing root on #execute) (#12178).
// Note: Don't use the state of the DropdownView#focusTracker here. It fires #blur with the timeout.
// TODO ShadowRoot
if ( elements.some( element => element.contains( global.document.activeElement ) ) ) {
// TODO ShadowRoot - the activeElement is valid for the closest ShadowRoot
if ( elements.some( element => element.getRootNode().activeElement && element.contains( element.getRootNode().activeElement ) ) ) {
dropdownView.buttonView.focus();
}
} );
Expand Down
4 changes: 3 additions & 1 deletion packages/ckeditor5-ui/src/panel/balloon/balloonpanelview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,9 +399,11 @@ export default class BalloonPanelView extends View {
}

let targetElement = getDomElement( options.target );
// TODO ShadowRoot
const limiterElement = options.limiter ? getDomElement( options.limiter ) : global.document.body;

// TODO ShadowRoot
// - we need to listen to the scroll event on every ShadowRoot
// (it is not composed and does not propagate to parent DOM)
// Then we need to listen on scroll event of eny element in the document.
this.listenTo( global.document, 'scroll', ( evt, domEvt ) => {
const scrollTarget = domEvt.target as Element;
Expand Down
1 change: 1 addition & 0 deletions packages/ckeditor5-ui/src/tooltipmanager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ export default class TooltipManager extends /* #__PURE__ */ DomEmitterMixin() {
this._pinTooltipDebounced = debounce( this._pinTooltip, 600 );
this._unpinTooltipDebounced = debounce( this._unpinTooltip, 400 );

// TODO ShadowRoot - make sure those events propagate to parent shadow DOM
this.listenTo( global.document, 'keydown', this._onKeyDown.bind( this ), { useCapture: true } );

this.listenTo( global.document, 'focus', this._onEnterOrFocus.bind( this ), { useCapture: true } );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import global from './global.js';
*/
export default function findClosestScrollableAncestor( domElement: HTMLElement ): HTMLElement | null {
let element = domElement.parentElement;

if ( !element ) {
return null;
}

// TODO: ShadowRoot
while ( element.tagName != 'BODY' ) {
const overflow = element.style.overflowY || global.window.getComputedStyle( element ).overflowY;

Expand Down
1 change: 1 addition & 0 deletions packages/ckeditor5-widget/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ export function calculateResizeHostAncestorWidth( domResizeHost: HTMLElement ):
let checkedElement = domResizeHostParent!;

while ( isNaN( parentWidth ) ) {
// TODO ShadowRoot
checkedElement = checkedElement.parentElement!;

if ( ++currentLevel > ancestorLevelLimit ) {
Expand Down

0 comments on commit fedf8be

Please sign in to comment.