blokhaus

Video Nodes

VideoNode and LoadingVideoNode for video content.

Blokhaus provides two nodes for video handling: VideoNode for permanent video embeds and uploaded files, and LoadingVideoNode for optimistic previews during file upload. Together they implement the video upload and embed flow described in the Video Embeds guide.


VideoNode

A DecoratorNode that renders either an <iframe> (for embedded videos from YouTube, Vimeo, Loom, etc.) or a <video> element (for uploaded video files). Supports click-to-select with a selection ring, and full JSON serialization.

Import

import { VideoNode, $createVideoNode, $isVideoNode } from "@blokhaus/core";
import type { VideoPayload } from "@blokhaus/core";

VideoPayload

interface VideoPayload {
  src: string;
  videoType: "embed" | "file";
  provider: string;
  title?: string;
  key?: NodeKey;
}
FieldTypeRequiredDescription
srcstringYesThe video URL. For embeds, this is the embed URL (e.g., https://www.youtube.com/embed/dQw4w9WgXcQ). For files, this is the remote file URL.
videoType'embed' | 'file'YesWhether the video is an iframe embed or a native <video> file.
providerstringYesThe video provider identifier: "youtube", "vimeo", "loom", or "generic".
titlestringNoTitle/label for the video. Used as the <iframe> title attribute.
keyNodeKeyNoExplicit Lexical node key.

Functions

$createVideoNode(payload: VideoPayload): VideoNode

Creates a new VideoNode. Must be called inside editor.update().

import { parseVideoEmbed } from "@blokhaus/core";

editor.update(() => {
  const embedInfo = parseVideoEmbed(
    "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  );
  if (embedInfo) {
    const video = $createVideoNode({
      src: embedInfo.embedUrl,
      videoType: "embed",
      provider: embedInfo.provider,
      title: "My Video",
    });
    $getRoot().append(video);
  }
});

$isVideoNode(node: LexicalNode | null | undefined): node is VideoNode

Type guard that returns true if the given node is a VideoNode.

Instance methods

MethodReturnsDescription
getSrc()stringReturns the video URL.
getVideoType()'embed' | 'file'Returns whether this is an embed or a file.
getProvider()stringReturns the provider identifier.
getTitle()stringReturns the video title.

Serialized format

type SerializedVideoNode = {
  type: "video";
  version: 1;
  src: string;
  videoType: "embed" | "file";
  provider: string;
  title: string;
};

DOM behavior

  • exportDOM: Produces a <div> with data-video-src, data-video-type, data-video-provider, and optional data-video-title attributes.
  • importDOM: Converts both <div> elements with data-video-src attributes and <iframe> elements from YouTube, Vimeo, or Loom back into VideoNode instances.

Rendering

The component renders differently based on videoType:

  • 'embed': Renders an <iframe> with allow="autoplay; encrypted-media; picture-in-picture" and allowFullScreen. The iframe has a 16:9 aspect ratio.
  • 'file': Renders a native <video> element with controls.

A transparent click overlay covers the video when it is not selected. This overlay captures clicks that iframes and video players would otherwise swallow, allowing the node to be selected. When the node is selected, the overlay is removed so the user can interact with the video player controls.

Selection behavior

Clicking on the video selects the node and renders a selection ring. When selected, pressing Backspace or Delete removes the video from the AST.


LoadingVideoNode

A transient DecoratorNode used during video file upload. It renders a placeholder with the file name and a spinner overlay. This node should never be persisted to a database.

Import

import {
  LoadingVideoNode,
  $createLoadingVideoNode,
  $isLoadingVideoNode,
} from "@blokhaus/core";

Functions

$createLoadingVideoNode(objectURL: string, fileName: string): LoadingVideoNode

Creates a new LoadingVideoNode. Must be called inside editor.update().

editor.update(() => {
  const objectURL = URL.createObjectURL(videoFile);
  const loadingNode = $createLoadingVideoNode(objectURL, videoFile.name);
  $getRoot().append(loadingNode);
});

$isLoadingVideoNode(node: LexicalNode | null | undefined): node is LoadingVideoNode

Type guard that returns true if the given node is a LoadingVideoNode.

Instance methods

MethodReturnsDescription
getObjectURL()stringReturns the local blob URL.
getFileName()stringReturns the original file name.

Serialized format

type SerializedLoadingVideoNode = {
  type: "loading-video";
  version: 1;
  objectURL: string;
  fileName: string;
};

LoadingVideoNode is transient. It exists only while the upload is in progress. After the upload resolves or rejects, the VideoPlugin replaces or removes it. Always call URL.revokeObjectURL() after the upload completes to prevent memory leaks.

Visual rendering

The loading state renders as a placeholder with a 16:9 aspect ratio, a muted background color, the file name at the bottom, and a centered spinner animation. The placeholder is rendered at 60% opacity to visually distinguish it from permanent content.