From 3d54ddd3936f20fe07fd317894c42763c8064e84 Mon Sep 17 00:00:00 2001 From: callumalpass Date: Tue, 30 Sep 2025 18:42:58 +1000 Subject: [PATCH] refactor: consolidate CodeMirror widget cursor styles into reusable utility Extracted duplicated cursor-hiding CSS from widget files into a single .cm-widget-cursor-fix utility class. Uses minimal approach that only hides widget buffer artifacts and prevents editing inside widgets, without hiding the editor cursor. Changes: - Created .cm-widget-cursor-fix utility class in utilities.css - Applied utility class to task-card-note-widget and project-note-subtasks - Removed 150+ lines of duplicated CSS across widget files - Simplified widget-specific CSS to only styling concerns Benefits: - DRY principle: Single source of truth for widget behavior - Maintainability: Update widget fixes in one place - Consistency: All widgets use same approach - Smaller bundle: Removed redundant CSS rules - Better UX: Preserves cursor visibility and normal navigation This approach only hides the widget buffer image artifact (the actual visual bug) and prevents text selection inside widgets, without interfering with normal editor cursor behavior or navigation. --- src/editor/ProjectNoteDecorations.ts | 4 +- src/editor/TaskCardNoteDecorations.ts | 4 +- styles/project-note-subtasks.css | 78 +------------------------- styles/task-card-note-widget.css | 81 +-------------------------- styles/utilities.css | 43 ++++++++++++++ 5 files changed, 51 insertions(+), 159 deletions(-) diff --git a/src/editor/ProjectNoteDecorations.ts b/src/editor/ProjectNoteDecorations.ts index 35a6b1f6..2aab2538 100644 --- a/src/editor/ProjectNoteDecorations.ts +++ b/src/editor/ProjectNoteDecorations.ts @@ -141,7 +141,7 @@ export class ProjectSubtasksWidget extends WidgetType { this.editorView = view; const container = document.createElement("div"); - container.className = "tasknotes-plugin project-note-subtasks project-subtasks-widget"; + container.className = "tasknotes-plugin project-note-subtasks project-subtasks-widget cm-widget-cursor-fix"; container.setAttribute("contenteditable", "false"); container.setAttribute("spellcheck", "false"); @@ -1008,7 +1008,7 @@ class ProjectNoteDecorationsPlugin implements PluginValue { notePath, this.version ), - side: 1, // Place widget after the position so cursor can't go past it + side: 1, }); builder.add(insertPos, insertPos, widget); diff --git a/src/editor/TaskCardNoteDecorations.ts b/src/editor/TaskCardNoteDecorations.ts index 3ab2c8ee..b1cae7a7 100644 --- a/src/editor/TaskCardNoteDecorations.ts +++ b/src/editor/TaskCardNoteDecorations.ts @@ -62,7 +62,7 @@ export class TaskCardWidget extends WidgetType { toDOM(view: EditorView): HTMLElement { const container = document.createElement("div"); - container.className = "tasknotes-plugin task-card-note-widget"; + container.className = "tasknotes-plugin task-card-note-widget cm-widget-cursor-fix"; container.setAttribute("contenteditable", "false"); container.setAttribute("spellcheck", "false"); @@ -312,7 +312,7 @@ class TaskCardNoteDecorationsPlugin implements PluginValue { const widget = Decoration.widget({ widget: new TaskCardWidget(this.plugin, this.cachedTask, this.version), - side: 1, // Place widget after the position so cursor can't go past it + side: 1, }); builder.add(insertPos, insertPos, widget); diff --git a/styles/project-note-subtasks.css b/styles/project-note-subtasks.css index ad9d5c60..2f6cffd9 100644 --- a/styles/project-note-subtasks.css +++ b/styles/project-note-subtasks.css @@ -13,86 +13,10 @@ border: 1px dashed var(--background-modifier-border-hover); border-radius: var(--radius-s); clear: both; - position: relative; z-index: 10; /* Higher z-index to allow dropdowns to appear above other content */ - + /* Allow filter dropdowns to extend beyond widget boundaries */ overflow: visible !important; - - /* Prevent cursor and editing issues */ - pointer-events: auto; - user-select: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - cursor: default; - - /* Prevent contenteditable behavior */ - -webkit-user-modify: read-only; - -moz-user-modify: read-only; - - /* Ensure no text cursor appears */ - caret-color: transparent; - outline: none; -} - -/* Hide CodeMirror widgetBuffer img when it's adjacent to our widget and our widget is on top of note (not last-child) */ -.cm-content > .cm-line:not(:last-child):has(.project-note-subtasks) img.cm-widgetBuffer { - display: none !important; -} - -/* Hide CodeMirror cursor when it's adjacent to our widget */ -.cm-line:has(.project-note-subtasks) .cm-cursor, -.cm-line:has(.project-note-subtasks) + .cm-line .cm-cursor { - display: none !important; -} - -/* Alternative approach - hide cursor specifically around widget */ -.project-note-subtasks + .cm-cursor, -.project-note-subtasks ~ .cm-cursor { - display: none !important; -} - -/* Hide cursor when widget is in focus context */ -.cm-focused .cm-line:has(.project-note-subtasks) .cm-cursor { - display: none !important; -} - -/* More specific cursor hiding using new class and data attribute */ -.cm-line:has(.project-subtasks-widget) .cm-cursor, -.cm-line:has([data-widget-type="project-subtasks"]) .cm-cursor { - display: none !important; -} - -/* Hide any cursor that appears after our widget */ -.project-subtasks-widget ~ .cm-cursor, -[data-widget-type="project-subtasks"] ~ .cm-cursor { - display: none !important; -} - -/* Ensure widget creates proper isolation from editor cursor */ -.project-subtasks-widget { - isolation: isolate; - z-index: 10; - position: relative; -} - -/* Hide cursor in the entire editor when widget is present and focused */ -.cm-editor:has(.project-subtasks-widget) .cm-cursor { - opacity: 0 !important; - visibility: hidden !important; -} - -/* Comprehensive cursor hiding - use as fallback */ -.cm-content:has(.project-subtasks-widget) .cm-cursor, -.cm-scroller:has(.project-subtasks-widget) .cm-cursor { - display: none !important; -} - -/* Target cursor by position relative to widget */ -.project-subtasks-widget + * .cm-cursor, -.project-subtasks-widget ~ * .cm-cursor { - display: none !important; } .tasknotes-plugin .project-note-subtasks__header { diff --git a/styles/task-card-note-widget.css b/styles/task-card-note-widget.css index 1368132c..13dc30fe 100644 --- a/styles/task-card-note-widget.css +++ b/styles/task-card-note-widget.css @@ -1,89 +1,14 @@ -/* Task Card Note Widget Styles */ +/* Task Card Note Widget - minimal widget for inline task cards in notes */ .task-card-note-widget { - /* Simple border container with a little padding - let task card handle its own styling */ + display: block; + border-radius: var(--radius-s); border: 1px solid var(--background-modifier-border); margin: var(--cs-spacing-md) 0; padding: var(--cs-spacing-sm); - - /* Prevent cursor and editing issues */ - pointer-events: auto; - user-select: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - cursor: default; - - /* Prevent contenteditable behavior */ - -webkit-user-modify: read-only; - -moz-user-modify: read-only; - - /* Ensure no text cursor appears */ - caret-color: transparent; - outline: none; - } .task-card-note-widget__card { /* Reset margins for the inner task card */ margin: 0 !important; -} - -/* Hide CodeMirror widgetBuffer img when it's adjacent to our widget */ -.cm-content > .cm-line:has(.task-card-note-widget) img.cm-widgetBuffer { - display: none !important; -} - -/* Hide CodeMirror cursor when it's adjacent to our widget */ -.cm-line:has(.task-card-note-widget) .cm-cursor, -.cm-line:has(.task-card-note-widget) + .cm-line .cm-cursor { - display: none !important; -} - -/* Alternative approach - hide cursor specifically around widget */ -.task-card-note-widget + .cm-cursor, -.task-card-note-widget ~ .cm-cursor { - display: none !important; -} - -/* Hide cursor when widget is in focus context */ -.cm-focused .cm-line:has(.task-card-note-widget) .cm-cursor { - display: none !important; -} - -/* More specific cursor hiding using new class and data attribute */ -.cm-line:has(.task-card-note-widget) .cm-cursor, -.cm-line:has([data-widget-type="task-card"]) .cm-cursor { - display: none !important; -} - -/* Hide any cursor that appears after our widget */ -.task-card-note-widget ~ .cm-cursor, -[data-widget-type="task-card"] ~ .cm-cursor { - display: none !important; -} - -/* Ensure widget creates proper isolation from editor cursor */ -.task-card-note-widget { - isolation: isolate; - z-index: 10; - position: relative; -} - -/* Hide cursor in the entire editor when widget is present and focused */ -.cm-editor:has(.task-card-note-widget) .cm-cursor { - opacity: 0 !important; - visibility: hidden !important; -} - -/* Comprehensive cursor hiding - use as fallback */ -.cm-content:has(.task-card-note-widget) .cm-cursor, -.cm-scroller:has(.task-card-note-widget) .cm-cursor { - display: none !important; -} - -/* Target cursor by position relative to widget */ -.task-card-note-widget + * .cm-cursor, -.task-card-note-widget ~ * .cm-cursor { - display: none !important; } \ No newline at end of file diff --git a/styles/utilities.css b/styles/utilities.css index b71fbd41..5f91c857 100644 --- a/styles/utilities.css +++ b/styles/utilities.css @@ -2299,3 +2299,46 @@ border-radius: 50%; animation: tn-spin 0.8s linear infinite; } + + +/* ================================================ + CODEMIRROR WIDGET UTILITIES + ================================================ */ + +/** + * Widget Cursor Fix + * + * Minimal fix for CodeMirror widget artifacts. + * Only prevents editing inside widgets and hides visual artifacts. + * Does NOT hide the editor cursor to preserve normal editing experience. + * + * Fixes: + * - Prevents text selection inside widgets + * - Prevents contenteditable behavior + * - Hides widget buffer image artifacts + * + * Trade-offs: + * - Cursor may appear next to widgets (acceptable for normal editing UX) + * - Widget buffer artifacts are hidden + * + * Usage: Add .cm-widget-cursor-fix to your widget container element + */ +.cm-widget-cursor-fix { + /* Prevent text selection and editing inside widget */ + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + + /* Prevent contenteditable behavior */ + -webkit-user-modify: read-only; + -moz-user-modify: read-only; + + /* Ensure no text cursor appears inside widget elements */ + caret-color: transparent; +} + +/* Hide CodeMirror widgetBuffer image artifacts only */ +.cm-content > .cm-line:has(.cm-widget-cursor-fix) img.cm-widgetBuffer { + display: none !important; +}