{"id":11593,"library":"prosemirror-resizable-view","title":"ProseMirror Resizable View","description":"The `prosemirror-resizable-view` package provides a specialized `NodeView` implementation for ProseMirror that enables users to resize custom nodes directly within the editor interface. It is an integral part of the broader Remirror ecosystem and currently aligns with Remirror's stable version 3.0.0. This package itself is at version 3.0.0, benefiting from the active development cadence of the Remirror project, which includes regular patch releases for bug fixes and dependency updates. Its primary differentiator lies in abstracting the complexities of implementing resizable DOM elements within a ProseMirror `NodeView`, offering a convenient base class that handles drag events and dimension management. This significantly simplifies the development process for integrating dynamic resize functionality for various content types like images, video embeds, or custom block components. The package comes with comprehensive TypeScript type definitions, enhancing developer experience and code safety.","status":"active","version":"3.0.0","language":"javascript","source_language":"en","source_url":"https://github.com/remirror/remirror","tags":["javascript","prosemirror","resizable","typescript"],"install":[{"cmd":"npm install prosemirror-resizable-view","lang":"bash","label":"npm"},{"cmd":"yarn add prosemirror-resizable-view","lang":"bash","label":"yarn"},{"cmd":"pnpm add prosemirror-resizable-view","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Required for `ProsemirrorNode` which represents the editor's content nodes and is passed to the NodeView constructor.","package":"prosemirror-model","optional":false},{"reason":"Required for `NodeView` and `EditorView` classes, fundamental components for rendering and interacting with ProseMirror documents.","package":"prosemirror-view","optional":false}],"imports":[{"note":"Primarily designed for ESM environments. While CommonJS `require` might work with transpilation, direct ESM import is recommended.","wrong":"const ResizableNodeView = require('prosemirror-resizable-view');","symbol":"ResizableNodeView","correct":"import { ResizableNodeView } from 'prosemirror-resizable-view';"},{"note":"`Node` is a common global type in TypeScript/JavaScript; aliasing it as `ProsemirrorNode` avoids naming conflicts.","wrong":"import { Node } from 'prosemirror-model';","symbol":"Node","correct":"import { Node as ProsemirrorNode } from 'prosemirror-model';"},{"note":"The `NodeView` interface originates from `prosemirror-view`, not `prosemirror-model`.","wrong":"import { NodeView } from 'prosemirror-model';","symbol":"NodeView","correct":"import { EditorView, NodeView } from 'prosemirror-view';"},{"note":"While `@remirror/pm` re-exports ProseMirror modules, directly importing `EditorView` from `prosemirror-view` is standard practice.","wrong":"import { EditorView } from '@remirror/pm';","symbol":"EditorView","correct":"import { EditorView } from 'prosemirror-view';"}],"quickstart":{"code":"import { ResizableNodeView } from 'prosemirror-resizable-view';\nimport { Node as ProsemirrorNode, Schema } from 'prosemirror-model';\nimport { EditorView, NodeView } from 'prosemirror-view';\nimport { EditorState } from 'prosemirror-state';\nimport { baseKeymap } from 'prosemirror-commands';\nimport { keymap } from 'prosemirror-keymap';\n\n// 1. Define a basic schema with an 'image' node that can have width/height attributes\nconst mySchema = new Schema({\n  nodes: {\n    doc: { content: 'block+' },\n    paragraph: { content: 'inline*', group: 'block' },\n    image: {\n      inline: false,\n      attrs: {\n        src: { default: '' },\n        alt: { default: null },\n        title: { default: null },\n        width: { default: null },\n        height: { default: null },\n      },\n      group: 'block',\n      parseDOM: [{\n        tag: 'img[src]',\n        getAttrs(dom) {\n          if (typeof dom === 'string') return {};\n          return {\n            src: dom.getAttribute('src'),\n            alt: dom.getAttribute('alt'),\n            title: dom.getAttribute('title'),\n            width: dom.getAttribute('width'),\n            height: dom.getAttribute('height'),\n          };\n        },\n      }],\n      toDOM(node) {\n        return [\n          'img',\n          {\n            src: node.attrs.src,\n            alt: node.attrs.alt,\n            title: node.attrs.title,\n            width: node.attrs.width, // Render initial width\n            height: node.attrs.height, // Render initial height\n          },\n        ];\n      },\n    },\n    text: { inline: true, group: 'inline' },\n  },\n  marks: {},\n});\n\n// 2. Helper function to create the actual DOM element for the image\nconst createInnerImage = ({ node }: { node: ProsemirrorNode }) => {\n  const inner = document.createElement('img');\n  inner.setAttribute('src', node.attrs.src);\n  if (node.attrs.alt) inner.setAttribute('alt', node.attrs.alt);\n  if (node.attrs.title) inner.setAttribute('title', node.attrs.title);\n  inner.style.width = node.attrs.width ? `${node.attrs.width}px` : '100%';\n  inner.style.height = node.attrs.height ? `${node.attrs.height}px` : 'auto';\n  inner.style.minWidth = '50px';\n  inner.style.objectFit = 'contain';\n  return inner;\n};\n\n// 3. Extend ResizableNodeView to create a custom resizable image view\nexport class ResizableImageView extends ResizableNodeView implements NodeView {\n  constructor(node: ProsemirrorNode, view: EditorView, getPos: () => number) {\n    super({\n      node,\n      view,\n      getPos,\n      createElement: createInnerImage,\n      // Optional: Set aspectRatio to 'lock' to maintain aspect ratio, or 'free'.\n      // aspectRatio: 'lock',\n      updateSize: (width, height) => {\n        // This callback is crucial: dispatch a transaction to update the node's attributes\n        const tr = view.state.tr.setNodeMarkup(getPos(), undefined, {\n          ...node.attrs,\n          width,\n          height,\n        });\n        view.dispatch(tr);\n      },\n    });\n  }\n}\n\n// 4. Set up the ProseMirror Editor\nconst editorDiv = document.createElement('div');\neditorDiv.id = 'editor';\ndocument.body.appendChild(editorDiv);\n\nconst state = EditorState.create({\n  schema: mySchema,\n  doc: mySchema.nodeFromJSON({\n    type: 'doc',\n    content: [\n      {\n        type: 'paragraph',\n        content: [{ type: 'text', text: 'Drag the handles to resize the image:' }],\n      },\n      {\n        type: 'image',\n        attrs: { src: 'https://via.placeholder.com/250x180', width: 250, height: 180 },\n      },\n      {\n        type: 'paragraph',\n        content: [{ type: 'text', text: 'This is some text below the resizable image.' }],\n      },\n    ],\n  }),\n  plugins: [keymap(baseKeymap)],\n});\n\nconst view = new EditorView(editorDiv, {\n  state,\n  nodeViews: {\n    image(node, view, getPos) {\n      return new ResizableImageView(node, view, getPos);\n    },\n  },\n});\n\n// Expose view for debugging in browser console\n(window as any).view = view;\n","lang":"typescript","description":"This quickstart demonstrates how to create a basic ProseMirror editor with a custom `image` node, then renders this node using `ResizableImageView` to allow users to resize images within the editor. It includes schema definition, `ResizableNodeView` extension, and editor setup."},"warnings":[{"fix":"Ensure your TypeScript configuration (`tsconfig.json`) is set to support Stage 3 decorators (e.g., `\"experimentalDecorators\": true` and `\"emitDecoratorMetadata\": true` if using decorators with reflection, and potentially `\"target\": \"es2020\"` or higher).","message":"As `prosemirror-resizable-view` aligns with the Remirror v3 ecosystem, projects upgrading to Remirror v3 will need to contend with its shift to Stage 3 decorators from TypeScript's experimental decorators. While this package itself may not directly expose decorators, other Remirror extensions commonly used alongside it require this update, potentially affecting TypeScript configurations.","severity":"breaking","affected_versions":">=3.0.0"},{"fix":"Check the `peerDependencies` of `prosemirror-resizable-view` (or the wider Remirror ecosystem) and ensure your direct `prosemirror-*` dependencies match the specified ranges to avoid runtime errors or unexpected behavior due to API mismatches.","message":"Remirror v3, which this package is part of, has updated its underlying ProseMirror dependencies. This may necessitate users to verify that their core `prosemirror-*` package versions are compatible with the versions expected by the `prosemirror-resizable-view` package and its Remirror counterparts.","severity":"breaking","affected_versions":">=3.0.0"},{"fix":"Always reflect size changes in the ProseMirror node's attributes and dispatch a transaction using `view.dispatch(view.state.tr.setNodeMarkup(...))` within the `updateSize` callback of `ResizableNodeView` or similar methods.","message":"Directly manipulating the DOM elements of a `NodeView` (e.g., changing width/height styles) without dispatching a ProseMirror transaction will often result in the changes being overwritten by ProseMirror's rendering cycle, leading to non-persistent visual updates.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Create a subclass that extends `ResizableNodeView` and explicitly implements the `createElement(props: { node: ProsemirrorNode; view: EditorView; })` method to return an `HTMLElement` for your node view.","message":"`ResizableNodeView` is an abstract class and cannot be instantiated directly. It requires extension by a custom class that implements the abstract `createElement` method to provide the actual DOM structure for the node.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Ensure that the CSS for your resizable node view and its handles is correctly applied to make the resize controls interactive. Refer to the package's documentation or examples for recommended styling approaches, or use development tools to inspect event listeners.","message":"Implementing functional resize handles requires careful styling (CSS) for the overlay and interaction areas. Without correct `position`, `z-index`, and `pointer-events` properties, the handles may not be visible or respond to user drag events.","severity":"gotcha","affected_versions":">=1.0.0"}],"env_vars":null,"last_verified":"2026-04-19T00:00:00.000Z","next_check":"2026-07-18T00:00:00.000Z","problems":[{"fix":"Ensure your `ResizableNodeView` subclass implements an `update(node: ProsemirrorNode)` method that appropriately handles attribute changes and returns `true` if it can update the DOM in place, or `false` if ProseMirror should redraw the node view entirely.","cause":"This error occurs when a custom `NodeView` (including one extending `ResizableNodeView`) does not correctly implement or return a value from its `update` method when the underlying ProseMirror node attributes change.","error":"TypeError: node.update is not a function"},{"fix":"Verify that your `ResizableNodeView` subclass's `createElement` method always returns a valid `HTMLElement`, and that the `dom` property of the `NodeView` instance is correctly assigned to this element.","cause":"This typically indicates that the `dom` or `contentDOM` property of the `NodeView` (or the element returned by `createElement`) was not properly initialized or returned a `null`/`undefined` value, preventing ProseMirror from attaching children.","error":"Cannot read properties of undefined (reading 'appendChild')"},{"fix":"Inspect the CSS of the resize handles to ensure `pointer-events` are not set to `none` (unless intended) and that they are positioned above other elements. Double-check that event listeners for resizing are correctly bound to the resize handle elements.","cause":"This is often a CSS-related issue, where the resize handles' `pointer-events` property or their z-index prevents them from receiving mouse events, or event listeners are incorrectly attached.","error":"Resize handles are visible but do not respond to drag events."}],"ecosystem":"npm"}