Просмотр исходного кода

[cm6] Show "move" cursor when the selection is ready to be dragged (#13532)

GitOrigin-RevId: 1a89abceeadb6e1c53b4bc779df19b6723eab738
Alf Eaton 3 лет назад
Родитель
Сommit
1cfc1eb269

+ 64 - 0
services/web/frontend/js/features/source-editor/extensions/draggable-cursor.ts

@@ -0,0 +1,64 @@
+import { Extension } from '@codemirror/state'
+import { EditorView, ViewPlugin } from '@codemirror/view'
+import { isInPrimarySelection } from './visual/utils/selection'
+
+const showDraggable = { style: 'cursor: move' }
+
+/**
+ * An extension that changes the cursor style to "move" when the main mouse button
+ * is held down on the primary selection for a short amount of time.
+ */
+export const draggableCursor = (): Extension => {
+  let timer: number | undefined
+
+  const plugin = ViewPlugin.define(
+    view => {
+      return {
+        isActive: false,
+        set(isActive: boolean) {
+          if (this.isActive !== isActive) {
+            this.isActive = isActive
+            view.update([])
+          }
+        },
+      }
+    },
+    {
+      eventHandlers: {
+        mousedown(event, view) {
+          if (timer) {
+            window.clearTimeout(timer)
+          }
+          // single click with the main mouse button
+          if (event.detail === 1 && event.button === 0) {
+            timer = window.setTimeout(() => {
+              timer = undefined
+              if (isInPrimarySelection(event, view)) {
+                this.set(true)
+              }
+            }, 50)
+          }
+        },
+        mouseup() {
+          if (timer) {
+            window.clearTimeout(timer)
+          }
+          this.set(false)
+        },
+        dragstart() {
+          if (timer) {
+            window.clearTimeout(timer)
+          }
+          this.set(false)
+        },
+      },
+    }
+  )
+
+  return [
+    plugin,
+    EditorView.contentAttributes.of(view =>
+      view.plugin(plugin)?.isActive ? showDraggable : null
+    ),
+  ]
+}

+ 3 - 0
services/web/frontend/js/features/source-editor/extensions/index.ts

@@ -46,6 +46,7 @@ import { shortcuts } from './shortcuts'
 import { effectListeners } from './effect-listeners'
 import { highlightSpecialChars } from './highlight-special-chars'
 import { toolbarPanel } from './toolbar/toolbar-panel'
+import { draggableCursor } from './draggable-cursor'
 import { geometryChangeEvent } from './geometry-change-event'
 import { isSplitTestEnabled } from '../../../utils/splitTestUtils'
 
@@ -78,6 +79,8 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
   indentationMarkers(options.visual.visual),
   bracketMatching(),
   bracketSelection(),
+  // NOTE: `draggableCursor` needs to be before `crosshairCursor`, so it takes precedence when Alt is held down.
+  draggableCursor(),
   // A built-in extension that enables rectangular selections, created by dragging a new selection while holding down Alt.
   rectangularSelection(),
   // A built-in extension that turns the pointer into a crosshair while Alt is pressed.

+ 29 - 0
services/web/frontend/js/features/source-editor/extensions/visual/utils/selection.ts

@@ -0,0 +1,29 @@
+/**
+ * Adapted from the "isInPrimarySelection" function in CodeMirror 6, licensed under the MIT license:
+ * https://github.com/codemirror/view/blob/main/src/input.ts
+ */
+
+import { EditorView } from '@codemirror/view'
+
+export function isInPrimarySelection(
+  event: MouseEvent | undefined,
+  view?: EditorView
+) {
+  if (!event) return false
+  if (view?.state.selection.main.empty) return false
+
+  const selection = document.getSelection()
+  if (!selection || selection.rangeCount === 0) return true
+
+  const rects = selection.getRangeAt(0).getClientRects()
+  for (const rect of rects) {
+    if (
+      rect.left <= event.clientX &&
+      rect.right >= event.clientX &&
+      rect.top <= event.clientY &&
+      rect.bottom >= event.clientY
+    )
+      return true
+  }
+  return false
+}