Fixes
This commit is contained in:
@@ -29,23 +29,42 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.custom-floating-menu {
|
||||
.selection-menu {
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.custom-floating-menu[hidden] {
|
||||
:host([menu-position="static"]) .selection-menu {
|
||||
position: static;
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
border-radius: 0;
|
||||
background-color: #f8f9fa;
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.selection-menu[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host([menu-position="static"]) .selection-menu[hidden] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.selection-menu[data-disabled="true"] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.suggestion-menu {
|
||||
display: block;
|
||||
padding: 0;
|
||||
@@ -53,7 +72,7 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 101;
|
||||
@@ -72,8 +91,12 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<!-- Selection-based floating menu with all controls -->
|
||||
<div class="custom-floating-menu" ?hidden=${this.menuMode !== 'selection'}>
|
||||
<!-- Selection-based menu with all controls -->
|
||||
<div
|
||||
class="selection-menu"
|
||||
?hidden=${this.menuMode !== 'selection' && this.menuPosition !== 'static'}
|
||||
?data-disabled=${this.menuPosition === 'static' && this.menuMode !== 'selection'}
|
||||
>
|
||||
<slot @slotchange=${this._handleSlotChange}></slot>
|
||||
</div>
|
||||
|
||||
@@ -92,7 +115,7 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
extensions: { type: Array },
|
||||
menuMode: { type: String, reflect: true },
|
||||
activeSuggestionType: { type: String },
|
||||
asHtml: { type: Boolean }
|
||||
menuPosition: { type: String, reflect: true }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -101,7 +124,7 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
this.extensions = [];
|
||||
this.menuMode = 'hidden'; // 'hidden', 'selection', 'suggestion'
|
||||
this.activeSuggestionType = null; // 'emoji', 'user', null
|
||||
this.asHtml = false;
|
||||
this.menuPosition = 'floating'; // 'floating', 'static'
|
||||
this._slotObserver = new MutationObserver(this._processSlotChanges.bind(this));
|
||||
}
|
||||
|
||||
@@ -127,7 +150,7 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
this.editor = new Editor({
|
||||
element: this.querySelector('[slot="content"]'),
|
||||
extensions: this.extensions,
|
||||
content: '<p>Hello World!</p>',
|
||||
content: '',
|
||||
});
|
||||
|
||||
// Notify child extensions that the editor is ready
|
||||
@@ -151,9 +174,22 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
}
|
||||
|
||||
const { empty, from, to } = editor.state.selection;
|
||||
const hasSelection = !empty && from !== to;
|
||||
|
||||
// Hide menu if selection is empty or a caret
|
||||
if (empty || from === to) {
|
||||
// In static mode, always keep menu visible but update its state
|
||||
if (this.menuPosition === 'static') {
|
||||
if (hasSelection) {
|
||||
this.menuMode = 'selection';
|
||||
} else {
|
||||
this.menuMode = 'hidden'; // This will disable the menu but keep it visible
|
||||
}
|
||||
// Trigger a re-render to update the data-disabled attribute
|
||||
this.requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
// In floating mode, hide menu if selection is empty or a caret
|
||||
if (!hasSelection) {
|
||||
this.hideMenu();
|
||||
console.log('Selection is empty or a caret. Hiding menu.');
|
||||
return;
|
||||
@@ -165,16 +201,20 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the selection-based floating menu
|
||||
* Show the selection-based menu
|
||||
*/
|
||||
showSelectionMenu() {
|
||||
this.menuMode = 'selection';
|
||||
requestAnimationFrame(() => {
|
||||
const menu = this.shadowRoot.querySelector('.custom-floating-menu');
|
||||
if (menu) {
|
||||
MenuPositioner.positionMenu(menu, this.editor);
|
||||
}
|
||||
});
|
||||
|
||||
// Only position the menu if it's floating
|
||||
if (this.menuPosition === 'floating') {
|
||||
requestAnimationFrame(() => {
|
||||
const menu = this.shadowRoot.querySelector('.selection-menu');
|
||||
if (menu) {
|
||||
MenuPositioner.positionMenu(menu, this.editor);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,6 +239,13 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
* Hide all menus
|
||||
*/
|
||||
hideMenu() {
|
||||
// In static mode, don't change the menu mode (it stays visible but disabled)
|
||||
if (this.menuPosition === 'static') {
|
||||
this.activeSuggestionType = null;
|
||||
this._updateSuggestionComponentVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
this.menuMode = 'hidden';
|
||||
this.activeSuggestionType = null;
|
||||
// Reset all suggestion component visibility
|
||||
@@ -217,6 +264,7 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
_updateSuggestionComponentVisibility() {
|
||||
const emojiComponent = this.querySelector('tp-rtb-emoji-suggestion[slot="suggestion-content"]');
|
||||
const userComponent = this.querySelector('tp-rtb-user-mention[slot="suggestion-content"]');
|
||||
const commandComponent = this.querySelector('dp-rtb-command[slot="suggestion-content"]');
|
||||
|
||||
if (emojiComponent) {
|
||||
emojiComponent.style.display = this.activeSuggestionType === 'emoji' ? 'block' : 'none';
|
||||
@@ -224,6 +272,9 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
if (userComponent) {
|
||||
userComponent.style.display = this.activeSuggestionType === 'user' ? 'block' : 'none';
|
||||
}
|
||||
if (commandComponent) {
|
||||
commandComponent.style.display = this.activeSuggestionType === 'command' ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
_processSlotChanges(mutations) {
|
||||
@@ -273,29 +324,24 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
// If we have suggestions, add a single Mention extension with all suggestions
|
||||
if (suggestions.length > 0) {
|
||||
this.extensions.push(
|
||||
Mention.extend({
|
||||
renderText({ options, node }) {
|
||||
// Return just the label without the trigger character
|
||||
return node.attrs.label || node.attrs.id;
|
||||
},
|
||||
renderHTML({ options, node }) {
|
||||
Mention.configure({
|
||||
suggestions: suggestions,
|
||||
renderHTML({ options, node, suggestion }) {
|
||||
if (suggestion && suggestion.renderHTML) {
|
||||
return suggestion.renderHTML({ options, node, suggestion });
|
||||
}
|
||||
|
||||
console.warn('No renderHTML defined for mention suggestion. Using default rendering.');
|
||||
|
||||
return [
|
||||
'span',
|
||||
{
|
||||
class: 'mention',
|
||||
'data-type': 'mention',
|
||||
'data-id': node.attrs.id,
|
||||
'data-label': node.attrs.label,
|
||||
contenteditable: 'false'
|
||||
},
|
||||
node.attrs.label || node.attrs.id
|
||||
];
|
||||
},
|
||||
}).configure({
|
||||
HTMLAttributes: {
|
||||
class: 'mention',
|
||||
},
|
||||
suggestions: suggestions
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -330,10 +376,10 @@ class TpRichTextBox extends FormElement(LitElement) {
|
||||
|
||||
get value() {
|
||||
if (this.editor) {
|
||||
return this.asHtml ? this.editor.getHTML() : this.editor.getJSON();
|
||||
return { html: this.editor.getHTML(), json: this.editor.getJSON() };
|
||||
}
|
||||
|
||||
return this.asHtml ? '' : {};
|
||||
|
||||
return { html: '', json: {} };
|
||||
}
|
||||
|
||||
set value(content) {
|
||||
|
||||
Reference in New Issue
Block a user