Add placeholder and hide floating menu if no actions are exposed.
This commit is contained in:
+30
-6
@@ -11,7 +11,7 @@ import Document from '@tiptap/extension-document';
|
|||||||
import Paragraph from '@tiptap/extension-paragraph';
|
import Paragraph from '@tiptap/extension-paragraph';
|
||||||
import Text from '@tiptap/extension-text';
|
import Text from '@tiptap/extension-text';
|
||||||
import HardBreak from '@tiptap/extension-hard-break';
|
import HardBreak from '@tiptap/extension-hard-break';
|
||||||
import { UndoRedo } from '@tiptap/extensions';
|
import { UndoRedo, Placeholder } from '@tiptap/extensions';
|
||||||
import Mention from '@tiptap/extension-mention';
|
import Mention from '@tiptap/extension-mention';
|
||||||
import { MenuPositioner } from './menu-positioner.js';
|
import { MenuPositioner } from './menu-positioner.js';
|
||||||
import { FormElement } from '@tp/helpers/form-element.js';
|
import { FormElement } from '@tp/helpers/form-element.js';
|
||||||
@@ -23,7 +23,6 @@ class TpRichTextBox extends FormElement(LitElement) {
|
|||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border: 1px solid #ccc;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +91,7 @@ class TpRichTextBox extends FormElement(LitElement) {
|
|||||||
<!-- Selection-based menu with all controls -->
|
<!-- Selection-based menu with all controls -->
|
||||||
<div
|
<div
|
||||||
class="selection-menu"
|
class="selection-menu"
|
||||||
?hidden=${this.menuMode !== 'selection' && this.menuPosition !== 'static'}
|
?hidden=${!this._menuControlCount || (this.menuMode !== 'selection' && this.menuPosition !== 'static')}
|
||||||
?data-disabled=${this.menuPosition === 'static' && this.menuMode !== 'selection'}
|
?data-disabled=${this.menuPosition === 'static' && this.menuMode !== 'selection'}
|
||||||
>
|
>
|
||||||
<slot @slotchange=${this._handleSlotChange}></slot>
|
<slot @slotchange=${this._handleSlotChange}></slot>
|
||||||
@@ -113,7 +112,9 @@ class TpRichTextBox extends FormElement(LitElement) {
|
|||||||
extensions: { type: Array },
|
extensions: { type: Array },
|
||||||
menuMode: { type: String, reflect: true },
|
menuMode: { type: String, reflect: true },
|
||||||
activeSuggestionType: { type: String },
|
activeSuggestionType: { type: String },
|
||||||
menuPosition: { type: String, reflect: true }
|
menuPosition: { type: String, reflect: true },
|
||||||
|
placeholder: { type: String },
|
||||||
|
_menuControlCount: { state: true }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +123,8 @@ class TpRichTextBox extends FormElement(LitElement) {
|
|||||||
this.extensions = [];
|
this.extensions = [];
|
||||||
this.menuMode = 'hidden'; // 'hidden', 'selection', 'suggestion'
|
this.menuMode = 'hidden'; // 'hidden', 'selection', 'suggestion'
|
||||||
this.activeSuggestionType = null; // 'emoji', 'user', null
|
this.activeSuggestionType = null; // 'emoji', 'user', null
|
||||||
|
this.placeholder = '';
|
||||||
|
this._menuControlCount = 0;
|
||||||
this.menuPosition = 'floating'; // 'floating', 'static'
|
this.menuPosition = 'floating'; // 'floating', 'static'
|
||||||
this._slotObserver = new MutationObserver(this._processSlotChanges.bind(this));
|
this._slotObserver = new MutationObserver(this._processSlotChanges.bind(this));
|
||||||
}
|
}
|
||||||
@@ -188,16 +191,19 @@ class TpRichTextBox extends FormElement(LitElement) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No formatting controls slotted — no menu, ever. Consumers without
|
||||||
|
// tp-rtb-* buttons (chat composer, agents composer) otherwise got an
|
||||||
|
// EMPTY floating box popping up on every text selection.
|
||||||
|
if (!this._menuControlCount) return;
|
||||||
|
|
||||||
// In floating mode, hide menu if selection is empty or a caret
|
// In floating mode, hide menu if selection is empty or a caret
|
||||||
if (!hasSelection) {
|
if (!hasSelection) {
|
||||||
this.hideMenu();
|
this.hideMenu();
|
||||||
console.log('Selection is empty or a caret. Hiding menu.');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show selection menu for text selection
|
// Show selection menu for text selection
|
||||||
this.showSelectionMenu();
|
this.showSelectionMenu();
|
||||||
console.log('Text selected. Showing selection menu.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -253,6 +259,7 @@ class TpRichTextBox extends FormElement(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_handleSlotChange(e) {
|
_handleSlotChange(e) {
|
||||||
|
this._menuControlCount = e.target.assignedElements().length;
|
||||||
this._processChildExtensions();
|
this._processChildExtensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +305,10 @@ class TpRichTextBox extends FormElement(LitElement) {
|
|||||||
Text,
|
Text,
|
||||||
HardBreak,
|
HardBreak,
|
||||||
UndoRedo,
|
UndoRedo,
|
||||||
|
// Empty-state placeholder text, rendered by the consumer's CSS
|
||||||
|
// (.tiptap p.is-editor-empty:first-child::before) from the
|
||||||
|
// data-placeholder attribute the extension decorates onto the node.
|
||||||
|
Placeholder.configure({ placeholder: this.placeholder }),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Collect suggestion configurations from child components
|
// Collect suggestion configurations from child components
|
||||||
@@ -417,6 +428,19 @@ class TpRichTextBox extends FormElement(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updated(changes) {
|
||||||
|
super.updated(changes);
|
||||||
|
|
||||||
|
// The placeholder is baked into the editor's extension config —
|
||||||
|
// re-create the editor (content preserved) when it changes. Consumers
|
||||||
|
// typically set it once as a static attribute.
|
||||||
|
if (changes.has('placeholder') && this.editor) {
|
||||||
|
const html = this.editor.getHTML();
|
||||||
|
this._initEditor();
|
||||||
|
if (html) this.editor.commands.setContent(html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
if (this.editor) {
|
if (this.editor) {
|
||||||
return { html: this.editor.getHTML(), json: this.editor.getJSON() };
|
return { html: this.editor.getHTML(), json: this.editor.getJSON() };
|
||||||
|
|||||||
Reference in New Issue
Block a user