8000 More complete patch for the TextEditor to support being rendered in the Shadow DOM by maximlt · Pull Request #6222 · holoviz/panel · GitHub
[go: up one dir, main page]

Skip to content
Merged
Changes from 1 commit
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
Next Next commit
use another more complete Shadow DOM patch
  • Loading branch information
maximlt committed Jan 17, 2024
commit 22897583cae3b64754e02e95f5f7fab087d141f9
126 changes: 75 additions & 51 deletions panel/models/quill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,6 @@ import { div } from "@bokehjs/core/dom"

import {HTMLBox, HTMLBoxView} from "./layout"

const normalizeNative = (nativeRange: any) => {

// document.getSelection model has properties startContainer and endContainer
// shadow.getSelection model has baseNode and focusNode
// Unify formats to always look like document.getSelection

if (nativeRange) {

const range = nativeRange;

// // HACK: To allow pasting
if (range.baseNode?.classList?.value === 'ql-clipboard') {
return null
}

if (range.baseNode) {
range.startContainer = nativeRange.baseNode;
range.endContainer = nativeRange.focusNode;
range.startOffset = nativeRange.baseOffset;
range.endOffset = nativeRange.focusOffset;

if (range.endOffset < range.startOffset) {
range.startContainer = nativeRange.focusNode;
range.endContainer = nativeRange.baseNode;
range.startOffset = nativeRange.focusOffset;
range.endOffset = nativeRange.baseOffset;
}
}

if (range.startContainer) {

return {
start: { node: range.startContainer, offset: range.startOffset },
end: { node: range.endContainer, offset: range.endOffset },
native: range
};
}
}

return null
};

export class QuillInputView extends HTMLBoxView {
override model: QuillInput
protected container: HTMLDivElement
Expand Down Expand Up @@ -106,16 +64,82 @@ export class QuillInputView extends HTMLBoxView {
theme: theme
});

// Apply only with getSelection() is defined (e.g. undefined on Firefox)
if (typeof this.quill.root.getRootNode().getSelection !== 'undefined') {
// Hack Quill and replace document.getSelection with shadow.getSelection
// see https://stackoverflow.com/questions/67914657/quill-editor-inside-shadow-dom/67944380#67944380
this.quill.selection.getNativeRange = () => {
// Apply ShadowDOM patch found at:
// https://github.com/quilljs/quill/issues/2961#issuecomment-1775999845

const hasShadowRootSelection = !!((document.createElement('div').attachShadow({ mode: 'open' }) as any).getSelection);
// Each browser engine has a different implementation for retrieving the Range
const getNativeRange = (rootNode: any) => {
try {
if (hasShadowRootSelection) {
// In Chromium, the shadow root has a getSelection function which returns the range
return rootNode.getSelection().getRangeAt(0);
} else {
const selection = window.getSelection();
if ((selection as any).getComposedRanges) {
// Webkit range retrieval is done with getComposedRanges (see: https://bugs.webkit.org/show_bug.cgi?id=163921)
return (selection as any).getComposedRanges(rootNode)[0];
} else {
// Gecko implements the range API properly in Native Shadow: https://developer.mozilla.org/en-US/docs/Web/API/Selection/getRangeAt
return (selection as any).getRangeAt(0);
}
}
} catch {
return null;
}
}

/**
* Original implementation uses document.active element which does not work in Native Shadow.
* Replace document.activeElement with shadowRoot.activeElement
**/
this.quill.selection.hasFocus = () => {
const rootNode = (this.quill.root.getRootNode() as ShadowRoot);
return rootNode.activeElement === this.quill.root;
}

const selection = (this.shadow_el as any AF07 ).getSelection();
const range = normalizeNative(selection);
return range;
};
/**
* Original implementation uses document.getSelection which does not work in Native Shadow.
* Replace document.getSelection with shadow dom equivalent (different for each browser)
**/
this.quill.selection.getNativeRange = () => {
const rootNode = (this.quill.root.getRootNode() as ShadowRoot);
const nativeRange = getNativeRange(rootNode);
return !!nativeRange ? this.quill.selection.normalizeNative(nativeRange) : null;
};

/**
* Original implementation relies on Selection.addRange to programmatically set the range, which does not work
* in Webkit with Native Shadow. Selection.addRange works fine in Chromium and Gecko.
**/
this.quill.selection.setNativeRange = (startNode: any, startOffset: any) => {
var endNode = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : startNode;
var endOffset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : startOffset;
var force = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
if (startNode != null && (this.quill.selection.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {
return;
}
var selection = document.getSelection();
if (selection == null) return;
if (startNode != null) {
if (!this.quill.selection.hasFocus()) this.quill.selection.root.focus();
var native = (this.quill.selection.getNativeRange() || {}).native;
if (native == null || force || startNode !== native.startContainer || startOffset !== native.startOffset || endNode !== native.endContainer || endOffset !== native.endOffset) {
if (startNode.tagName == "BR") {
startOffset = [].indexOf.call(startNode.parentNode.childNodes, startNode);
startNode = startNode.parentNode;
}
if (endNode.tagName == "BR") {
endOffset = [].indexOf.call(endNode.parentNode.childNodes, endNode);
endNode = endNode.parentNode;
}
selection.setBaseAndExtent(startNode, startOffset, endNode, endOffset);
}
} else {
selection.removeAllRanges();
this.quill.selection.root.blur();
document.body.focus();
}
}

this._editor = (this.shadow_el.querySelector('.ql-editor') as HTMLDivElement)
Expand Down
0