@codemirror-toolkit/react
TypeScript icon, indicating that this package has built-in type declarations

0.6.0 • Public • Published

@codemirror-toolkit/react

npm (scoped) npm bundle size (scoped) GitHub Workflow Status (with branch) Codecov branch

A small and flexible solution for binding CodeMirror 6 to React.

Instead of providing component with a big amount of props and un-tree-shakable dependencies (like @uiw/react-codemirror), it offers a set of hooks and an optional context provider that can be used to better integrate with your components.

Examples

See codemirror-toolkit#examples.

Installation

# npm
npm install @codemirror-toolkit/react

# Yarn
yarn add @codemirror-toolkit/react

Note that, you also need to install the peer dependencies @codemirror/state and @codemirror/view if you don't use the official all-in-one package codemirror or your package manager doesn't do it automatically.

Usage

First create an instance with configuration as an object or a factory function:

import { createCodeMirror } from '@codemirror-toolkit/react'

const codeMirror = createCodeMirror<HTMLDivElement>((prevState) => ({
  doc: prevState?.doc ?? 'Hello World!',
  // ...otherConfig,
}))

// if you want to use them in other files
export const { useViewEffect, useContainerRef /* ... */ } = codeMirror

Then bind your components with the hooks:

function Editor() {
  const containerRef = useContainerRef()
  return <div ref={containerRef} />
}

function App() {
  const [showEditor, setShowEditor] = useState(true)
  const [lastInput, setLastInput] = useState('')
  useViewEffect((view) => {
    console.log('EditorView is created')
    return () => {
      console.log('EditorView is destroyed')
      // expect(view.dom.parentElement).toBeNull()
      setLastInput(view.state.doc.toString())
    }
  })
  return (
    <>
      <button onClick={() => setShowEditor(!showEditor)}>
        {showEditor ? 'Destroy' : 'Create'} Editor
      </button>
      {showEditor ? (
        <Editor />
      ) : (
        <div>
          <p>Editor destroyed</p>
          <p>Last input: {lastInput}</p>
        </div>
      )}
    </>
  )
}

⚠️ An instance of EditorView will be created only when a DOM node is assigned to containerRef.current, and will be destroyed only when containerRef.current is set back to null.

With Context Provider

All the functions and hooks created with createCodeMirror don't require a context provider to use in different components, but in some cases you may want to instantiate EditorView with props from a component. In this case, you can use createCodeMirrorWithContext to create an instance within a context:

import { createCodeMirrorWithContext } from '@codemirror-toolkit/react'

const {
  Provider: CodeMirrorProvider,
  useView,
  useContainerRef,
  // ...
} = createCodeMirrorWithContext<HTMLDivElement>('CodeMirrorContext')

function MenuBar() {
  const view = useView()
  // ...
}

function Editor() {
  const containerRef = useContainerRef()
  return <div ref={containerRef} />
}

function App({ initialInput }: { initialInput: string }) {
  return (
    <CodeMirrorProvider
      config={{
        doc: initialInput,
        // ...otherConfig,
      }}>
      <MenuBar />
      <Editor />
    </CodeMirrorProvider>
  )
}

API

🚧 Documentation is WIP.

There are only two functions exported: createCodeMirror and createCodeMirrorWithContext.

Common Types

import type { EditorState } from '@codemirror/state'
import type { EditorView, EditorViewConfig } from '@codemirror/view'
import type { EffectCallback, MutableRefObject } from 'react'

type EditorViewConfigWithoutParentElement = Omit<EditorViewConfig, 'parent'>
interface CodeMirrorConfig extends EditorViewConfigWithoutParentElement {}

type CodeMirrorConfigCreator = (prevState: EditorState | undefined) => CodeMirrorConfig
type ProvidedCodeMirrorConfig = CodeMirrorConfig | CodeMirrorConfigCreator

type GetView = () => EditorView | null
type UseViewHook = () => EditorView | null

type ViewEffectCleanup = ReturnType<EffectCallback>
type ViewEffectSetup = (view: EditorView) => ViewEffectCleanup
type UseViewEffectHook = (setup: ViewEffectSetup) => void

type ViewDispath = typeof EditorView.prototype.dispatch
type UseViewDispatchHook = () => ViewDispath

type ContainerRef<ContainerElement extends Element = Element> =
  MutableRefObject<ContainerElement | null>
type UseContainerRefHook<ContainerElement extends Element = Element> =
  () => ContainerRef<ContainerElement>

interface CodeMirror<ContainerElement extends Element = Element> {
  getView: GetView
  useView: UseViewHook
  useViewEffect: UseViewEffectHook
  useViewDispatch: UseViewDispatchHook
  useContainerRef: UseContainerRefHook<ContainerElement>
}

createCodeMirror

function createCodeMirror<ContainerElement extends Element>(
  config?: ProvidedCodeMirrorConfig,
): CodeMirror<ContainerElement>

createCodeMirrorWithContext

import type { FunctionComponent, PropsWithChildren } from 'react'

interface CodeMirrorProps {
  config?: ProvidedCodeMirrorConfig
}
interface CodeMirrorProviderProps extends PropsWithChildren<CodeMirrorProps> {}
interface CodeMirrorProvider extends FunctionComponent<CodeMirrorProviderProps> {}

type UseCodeMirrorContextHook<ContainerElement extends Element = Element> =
  () => CodeMirror<ContainerElement>

type UseGetViewHook = () => GetView

interface CodeMirrorWithContext<ContainerElement extends Element = Element> {
  Provider: CodeMirrorProvider
  useContext: UseCodeMirrorContextHook<ContainerElement>
  useGetView: UseGetViewHook
  useView: UseViewHook
  useViewEffect: UseViewEffectHook
  useViewDispatch: UseViewDispatchHook
  useContainerRef: UseContainerRefHook<ContainerElement>
}

function createCodeMirrorWithContext<ContainerElement extends Element>(
  displayName?: string | false,
): CodeMirrorWithContext<ContainerElement>

License

MIT License @ 2022-Present Xuanbo Cheng

Package Sidebar

Install

npm i @codemirror-toolkit/react

Weekly Downloads

137

Version

0.6.0

License

MIT

Unpacked Size

79.3 kB

Total Files

38

Last publish

Collaborators

  • exuanbo