Skip to content

Commit

Permalink
Hook up prosemirror-view's InputState
Browse files Browse the repository at this point in the history
  • Loading branch information
smoores-dev committed Aug 4, 2023
1 parent 5e1fc4f commit f9b75b2
Show file tree
Hide file tree
Showing 8 changed files with 1,654 additions and 50 deletions.
32 changes: 27 additions & 5 deletions src/components/EditorView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, {
DetailedHTMLProps,
ForwardRefExoticComponent,
HTMLAttributes,
MutableRefObject,
RefAttributes,
useMemo,
useRef,
Expand All @@ -18,12 +19,19 @@ import { useContentEditable } from "../hooks/useContentEditable.js";
import { useSyncSelection } from "../hooks/useSyncSelection.js";
import { DecorationSourceInternal } from "../prosemirror-internal/DecorationInternal.js";
import { EditorViewInternal } from "../prosemirror-internal/EditorViewInternal.js";
import { DOMNode, DOMSelection } from "../prosemirror-internal/dom.js";
import * as browser from "../prosemirror-internal/browser.js";
import {
DOMNode,
DOMSelection,

Check failure on line 25 in src/components/EditorView.tsx

View workflow job for this annotation

GitHub Actions / check

'DOMSelection' is declared but its value is never read.
deepActiveElement,
safariShadowSelectionRange,
} from "../prosemirror-internal/dom.js";
import {
coordsAtPos,
endOfTextblock,
posAtCoords,
} from "../prosemirror-internal/domcoords.js";
import { InputState } from "../prosemirror-internal/input.js";

import { DocNodeView } from "./DocNodeView.js";
import { NodeViewComponentProps } from "./NodeViewComponentProps.js";
Expand Down Expand Up @@ -110,9 +118,11 @@ export function EditorView(props: Props) {

const editable = editableProp ? editableProp(state) : true;

const editorViewRefInternal = useRef<EditorViewInternal | null>(null);

// This is only safe to use in effects/layout effects or
// event handlers!
const editorViewAPI = useMemo<EditorViewInternal>(
const editorViewAPI: EditorViewInternal = useMemo<EditorViewInternal>(
// @ts-expect-error - EditorView API not fully implemented yet
() => ({
/* Start TODO */
Expand All @@ -137,6 +147,9 @@ export function EditorView(props: Props) {
handleTripleClick,
handleTripleClickOn,
},
focused: editorViewRefInternal.current?.focused ?? false,
markCursor: editorViewRefInternal.current?.markCursor ?? null,
input: editorViewRefInternal.current?.input ?? new InputState(),
get dom() {
if (!mountRef.current) {
throw new Error(
Expand Down Expand Up @@ -207,10 +220,17 @@ export function EditorView(props: Props) {
}
return cached || document;
},
domSelection(): DOMSelection {
domSelection() {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return (this.root as Document).getSelection()!;
},
domSelectionRange() {
return browser.safari &&
this.root.nodeType === 11 &&
deepActiveElement(this.dom.ownerDocument) == this.dom
? safariShadowSelectionRange(this)
: this.domSelection();
},
props: {
editable: editableProp,
state: stateProp ?? defaultState,
Expand Down Expand Up @@ -266,8 +286,10 @@ export function EditorView(props: Props) {
]
);

const editorViewRef = useRef(editorViewAPI);
editorViewRef.current = editorViewAPI;
editorViewRefInternal.current = editorViewAPI;

const editorViewRef =
editorViewRefInternal as MutableRefObject<EditorViewInternal>;

useContentEditable(editorViewRef);

Expand Down
46 changes: 2 additions & 44 deletions src/hooks/useContentEditable.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,13 @@
import { MutableRefObject, useEffect } from "react";

import { EditorViewInternal } from "../prosemirror-internal/EditorViewInternal.js";
import { initInput } from "../prosemirror-internal/input.js";

export function useContentEditable(
viewRef: MutableRefObject<EditorViewInternal>
) {
useEffect(() => {
const attachedHandlers: Record<string, (e: Event) => void> = {};
for (const [type, handler] of Object.entries(
viewRef.current.someProp("handleDOMEvents") ?? {}
)) {
if (!handler) continue;
const eventHandler = (event: Event) => {
if (event.defaultPrevented) return;
handler(viewRef.current, event);
};

attachedHandlers[type] = eventHandler;

viewRef.current.dom.addEventListener(
type as keyof HTMLElementEventMap,
eventHandler
);
}

function onKeyDown(event: KeyboardEvent) {
if (viewRef.current.someProp("handleKeyDown")?.(viewRef.current, event)) {
event.preventDefault();
}
}

viewRef.current.dom.addEventListener("keydown", onKeyDown);

function onKeyPress(event: KeyboardEvent) {
if (
viewRef.current.someProp("handleKeyPress")?.(viewRef.current, event)
) {
event.preventDefault();
}
}

viewRef.current.dom.addEventListener("keypress", onKeyPress);
initInput(viewRef);

function onBeforeInput(event: InputEvent) {
switch (event.inputType) {
Expand Down Expand Up @@ -81,14 +48,5 @@ export function useContentEditable(
}

viewRef.current.dom.addEventListener("beforeinput", onBeforeInput);

return () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
viewRef.current.dom.removeEventListener("beforeinput", onBeforeInput);
for (const [type, handler] of Object.entries(attachedHandlers)) {
// eslint-disable-next-line react-hooks/exhaustive-deps
viewRef.current.dom.removeEventListener(type, handler);
}
};
}, [viewRef]);
}
8 changes: 7 additions & 1 deletion src/prosemirror-internal/EditorViewInternal.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Mark } from "prosemirror-model";
import { EditorView as EditorViewT } from "prosemirror-view";

import { NodeViewDesc } from "../descriptors/ViewDesc.js";
import { DOMSelection } from "./dom.js";
import { DOMSelection, DOMSelectionRange } from "./dom.js";
import { InputState } from "./input.js";

export interface EditorViewInternal extends EditorViewT {
docView: NodeViewDesc;
domSelection: () => DOMSelection;
focused: boolean;
input: InputState;
markCursor: readonly Mark[] | null;
domSelectionRange: () => DOMSelectionRange
};
Loading

0 comments on commit f9b75b2

Please sign in to comment.