UNPKG

@atlaskit/editor-plugin-placeholder

Version:

Placeholder plugin for @atlaskit/editor-core.

102 lines (98 loc) 5.26 kB
import { getBrowserInfo } from '@atlaskit/editor-common/browser'; import { processRawValue } from '@atlaskit/editor-common/process-raw-value'; import { ZERO_WIDTH_SPACE } from '@atlaskit/editor-common/utils'; import { DOMSerializer } from '@atlaskit/editor-prosemirror/model'; import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { cycleThroughPlaceholderPrompts } from './animation'; import { placeholderTestId } from './constants'; export function createPlaceholderDecoration(editorState, placeholderText, placeholderPrompts, activeTypewriterTimeouts, pos = 1, initialDelayWhenUserTypedAndDeleted = 0, placeholderADF, showOnEmptyParagraph) { const browser = getBrowserInfo(); const placeholderDecoration = document.createElement('span'); let placeholderNodeWithText = placeholderDecoration; placeholderDecoration.setAttribute('data-testid', placeholderTestId); const shouldFadeIn = showOnEmptyParagraph; placeholderDecoration.className = shouldFadeIn ? 'placeholder-decoration placeholder-decoration-fade-in' : 'placeholder-decoration'; placeholderDecoration.setAttribute('aria-hidden', 'true'); // PM sets contenteditable to false on Decorations so Firefox doesn't display the flashing cursor // So adding an extra span which will contain the placeholder text if (browser.gecko) { const placeholderNode = document.createElement('span'); placeholderNode.setAttribute('contenteditable', 'true'); // explicitly overriding the default Decoration behaviour placeholderDecoration.appendChild(placeholderNode); placeholderNodeWithText = placeholderNode; } if (placeholderText) { placeholderNodeWithText.textContent = placeholderText || ' '; } else if (placeholderADF) { const serializer = DOMSerializer.fromSchema(editorState.schema); // Get a PMNode from docnode const docNode = processRawValue(editorState.schema, placeholderADF); if (docNode) { // Extract only the inline content from paragraphs, avoiding block-level elements // that can interfere with cursor rendering docNode.children.forEach(node => { // For paragraph nodes, serialize their content (inline elements) directly // without the wrapping <p> tag if (node.type.name === 'paragraph') { node.content.forEach(inlineNode => { const inlineDOM = serializer.serializeNode(inlineNode); placeholderNodeWithText.append(inlineDOM); }); } else { // For non-paragraph nodes, serialize normally const nodeDOM = serializer.serializeNode(node); placeholderNodeWithText.append(nodeDOM); } }); const markElements = placeholderNodeWithText.querySelectorAll('[data-prosemirror-content-type="mark"]'); markElements.forEach(markEl => { if (markEl instanceof HTMLElement) { markEl.style.setProperty('color', "var(--ds-text-subtlest, #6B6E76)"); } }); // Ensure all child elements don't block pointer events or cursor const allElements = placeholderNodeWithText.querySelectorAll('*'); allElements.forEach(el => { if (el instanceof HTMLElement) { el.style.pointerEvents = 'none'; el.style.userSelect = 'none'; } }); } } else if (placeholderPrompts) { cycleThroughPlaceholderPrompts(placeholderPrompts, activeTypewriterTimeouts, placeholderNodeWithText, initialDelayWhenUserTypedAndDeleted); } // ME-2289 Tapping on backspace in empty editor hides and displays the keyboard // Add a editable buff node as the cursor moving forward is inevitable // when backspace in GBoard composition if (browser.android && browser.chrome) { const buffNode = document.createElement('span'); buffNode.setAttribute('class', 'placeholder-android'); buffNode.setAttribute('contenteditable', 'true'); buffNode.textContent = ' '; placeholderDecoration.appendChild(buffNode); } const isTargetNested = editorState.doc.resolve(pos).depth > 1; // only truncate text for nested nodes, otherwise applying 'overflow: hidden;' to top level nodes // creates issues with quick insert button if (isTargetNested && editorExperiment('platform_editor_controls', 'variant1')) { placeholderDecoration.classList.add('placeholder-decoration-hide-overflow'); } if (placeholderADF && browser.chrome) { const fragment = document.createDocumentFragment(); // An issue occurs with the caret where it gets bigger when it's next to a non-editable element like a decoration. // See: https://discuss.prosemirror.net/t/chrome-caret-cursor-larger-than-the-text-with-inlined-items/5946/2 // Adding a zero-width space seems to fix this issue. fragment.appendChild(document.createTextNode(ZERO_WIDTH_SPACE)); fragment.appendChild(placeholderDecoration); return DecorationSet.create(editorState.doc, [Decoration.widget(pos, fragment, { side: 0, key: `placeholder ${placeholderText}` })]); } return DecorationSet.create(editorState.doc, [Decoration.widget(pos, placeholderDecoration, { side: 0, key: `placeholder ${placeholderText}` })]); }