diff --git a/site/demo.html b/site/demo.html
index d15bffe..e7713b8 100644
--- a/site/demo.html
+++ b/site/demo.html
@@ -229,15 +229,8 @@ bar.addEventListener('click', function(e){
else if (cmd === 'outline') { toggleOutline(btn); }
else if (cmd === 'wrap') { editor.toggleWordWrap(); btn.classList.toggle('on', !editor.isWordWrap()); }
else if (cmd === 'float') {
- var hasFloat = editor._floatingToolbar && editor._floatingToolbar.parentNode;
- if (hasFloat) {
- if (editor._floatingToolbar.parentNode) editor._floatingToolbar.parentNode.removeChild(editor._floatingToolbar);
- editor._floatingToolbar = null;
- } else {
- editor.config.floatingToolbar = true;
- editor._initFloatingToolbar();
- }
- btn.classList.toggle('on', !!editor._floatingToolbar);
+ editor.toggleFloatingToolbar();
+ btn.classList.toggle('on', editor.isFloatingToolbar());
}
else if (cmd === 'toast') {
var types = ['success','error','warning','info'];
diff --git a/src/core.ts b/src/core.ts
index 90dea25..2fba20a 100644
--- a/src/core.ts
+++ b/src/core.ts
@@ -84,6 +84,7 @@ export class MarkdownEditor {
_i18nCtx!: InstanceI18n;
_floatingToolbar!: HTMLElement | null;
_selectionTimer!: ReturnType | null;
+ _floatingEnabled!: boolean;
constructor(container: string | HTMLElement, options: EditorOptions = {}) {
if (!isBrowser()) {
@@ -108,6 +109,7 @@ export class MarkdownEditor {
this._outlineTimer = null; this._zenMode = false; this._wordWrap = true; this._syncing = false;
this._zenMouseHandler = null;
this._floatingToolbar = null; this._selectionTimer = null;
+ this._floatingEnabled = this.config.floatingToolbar !== false;
this._renderFn = (typeof this.config.render === 'function') ? this.config.render : parseMarkdown;
this._highlightFn = (typeof this.config.highlight === 'function') ? this.config.highlight : null;
@@ -716,13 +718,15 @@ export class MarkdownEditor {
document.head.appendChild(style);
}
+ if (!this._floatingEnabled) return;
+
const ta = this.textarea;
const show = () => {
- if (this._destroyed || this.config.readOnly) return;
+ if (this._destroyed || this.config.readOnly || !this._floatingEnabled) return;
const start = ta.selectionStart; const end = ta.selectionEnd;
if (start === end) { this._hideFloatingToolbar(); return; }
- if (!this._floatingToolbar) this._buildFloatingToolbar();
+ if (!this._floatingToolbar || !this._floatingToolbar.parentNode) this._buildFloatingToolbar();
// Position the toolbar near the selection
const rect = ta.getBoundingClientRect();
@@ -731,10 +735,9 @@ export class MarkdownEditor {
const currentLine = lines.length - 1;
const lineHeight = parseInt(getComputedStyle(ta).lineHeight, 10) || 22;
- let top = rect.top + currentLine * lineHeight - 38; // above the selection
+ let top = rect.top + currentLine * lineHeight - 38;
let left = rect.left + (ta.selectionDirection !== 'backward' ? (ta.selectionEnd - ta.selectionStart) / 2 : 0) * 8 + 10;
- // Clamp to stay within the editor pane
if (top < rect.top) top = rect.top + currentLine * lineHeight + lineHeight + 4;
if (left < rect.left) left = rect.left + 4;
const maxLeft = rect.right - 200;
@@ -747,35 +750,55 @@ export class MarkdownEditor {
const hide = () => { this._hideFloatingToolbar(); };
- // Detect selection changes via events + throttled polling
let lastSelStart = ta.selectionStart;
let lastSelEnd = ta.selectionEnd;
- const check = () => {
- if (this._destroyed || !document.body.contains(ta)) return;
+ this._selectionTimer = setInterval(() => {
+ if (this._destroyed || !document.body.contains(ta) || !this._floatingEnabled) return;
const s = ta.selectionStart; const e = ta.selectionEnd;
if (s !== lastSelStart || e !== lastSelEnd) {
lastSelStart = s; lastSelEnd = e;
const text = this._value.slice(s, e);
this._emit('selectionChange', { start: s, end: e, text });
}
- };
- // Use longer interval for polling (won't block event loop as much)
- this._selectionTimer = setInterval(check, 500) as any;
+ }, 500) as any;
this._cleanups.push(() => { if (this._selectionTimer) { clearInterval(this._selectionTimer); this._selectionTimer = null; } });
- ta.addEventListener('mouseup', () => setTimeout(show, 0));
+ ta.addEventListener('mouseup', () => { if (this._floatingEnabled) setTimeout(show, 0); });
ta.addEventListener('keyup', () => {
- const s = ta.selectionStart; const e = ta.selectionEnd;
- if (s !== e) setTimeout(show, 0);
+ if (!this._floatingEnabled) return;
+ if (ta.selectionStart !== ta.selectionEnd) setTimeout(show, 0);
else setTimeout(hide, 0);
this._emit('cursorMove', this.getCursorPosition());
});
ta.addEventListener('blur', () => setTimeout(hide, 300));
- ta.addEventListener('click', () => setTimeout(() => {
- if (ta.selectionStart === ta.selectionEnd) hide();
- }, 0));
+ ta.addEventListener('click', () => { if (ta.selectionStart === ta.selectionEnd) setTimeout(hide, 0); });
}
+ toggleFloatingToolbar(): this {
+ this._floatingEnabled = !this._floatingEnabled;
+ if (this._floatingEnabled) {
+ // Re-enable: restart the interval
+ if (!this._selectionTimer) {
+ const ta = this.textarea;
+ this._selectionTimer = setInterval(() => {
+ if (this._destroyed || !this._floatingEnabled) return;
+ const s = ta.selectionStart; const e = ta.selectionEnd;
+ if (s !== e) { /* will show via keyup/mouseup */ }
+ }, 500) as any;
+ this._cleanups.push(() => { if (this._selectionTimer) { clearInterval(this._selectionTimer); this._selectionTimer = null; } });
+ }
+ } else {
+ // Disable: clear interval and hide toolbar
+ if (this._selectionTimer) { clearInterval(this._selectionTimer); this._selectionTimer = null; }
+ if (this._floatingToolbar) {
+ if (this._floatingToolbar.parentNode) this._floatingToolbar.parentNode.removeChild(this._floatingToolbar);
+ this._floatingToolbar = null;
+ }
+ }
+ return this;
+ }
+ isFloatingToolbar(): boolean { return this._floatingEnabled; }
+
_buildFloatingToolbar(): void {
const bar = document.createElement('div');
bar.className = 'me-float-toolbar';