Skip to content

View Utilities

View-layer utilities for DOM interaction, caret positioning, and platform-specific behavior. These are used internally by the editor and exported for advanced plugin development.

Functions for navigating the cursor using DOM-based caret positioning.

endOfTextblock(container, state, direction, caretRect?)

Section titled “endOfTextblock(container, state, direction, caretRect?)”

Returns true if the caret is at the edge of a text block in the given direction. Used to determine when to cross block boundaries.

import { endOfTextblock } from '@notectl/core';
const atEnd: boolean = endOfTextblock(container, state, 'right');

Parameters:

ParameterTypeDescription
containerHTMLElementThe editor’s content element
stateEditorStateCurrent editor state
directionCaretDirection'left', 'right', 'up', or 'down'
caretRectDOMRect | nullOptional cached caret rect for performance

Moves the cursor to the adjacent block when at a block boundary:

import { navigateAcrossBlocks } from '@notectl/core';
const tr = navigateAcrossBlocks(state, 'down');
Section titled “navigateVerticalWithGoalColumn(container, state, direction, goalColumn)”

Vertical navigation with goal column preservation (standard behavior for up/down arrow keys):

import { navigateVerticalWithGoalColumn } from '@notectl/core';
const tr = navigateVerticalWithGoalColumn(container, state, 'up', goalColumn);

Skips over an inline node (width-1 in offset space) when the cursor is adjacent to one:

import { skipInlineNode } from '@notectl/core';
const tr = skipInlineNode(state, 'right');
Section titled “navigateFromGapCursor(state, direction, container?)”

Navigates from a gap cursor position to the nearest editable block:

import { navigateFromGapCursor } from '@notectl/core';
const tr = navigateFromGapCursor(state, 'down', container);

Returns the bounding rect of the current DOM selection’s caret, or null if unavailable:

import { getCaretRectFromSelection } from '@notectl/core';
const rect: DOMRect | null = getCaretRectFromSelection(window.getSelection()!);
type CaretDirection = 'left' | 'right' | 'up' | 'down';

Custom rendering for block node types. When a NodeView is registered for a block type, the reconciler delegates rendering to it instead of using the default NodeSpec.toDOM().

interface NodeView {
readonly dom: HTMLElement;
readonly contentDOM: HTMLElement | null;
getContentDOM?(childId: string): HTMLElement | null;
update?(node: BlockNode): boolean;
destroy?(): void;
selectNode?(): void;
deselectNode?(): void;
}
PropertyTypeDescription
domHTMLElementThe root DOM element managed by this view
contentDOMHTMLElement | nullThe element where text content is rendered. null for void nodes
MethodDescription
getContentDOM(childId)Returns the content container for a nested child block
update(node)Called when the block’s data changes. Return true if handled, false to recreate
destroy()Clean up DOM event listeners and resources
selectNode()Called when the node receives a node selection
deselectNode()Called when the node loses its node selection
type NodeViewFactory = (
node: BlockNode,
getState: () => EditorState,
dispatch: (tr: Transaction) => void,
) => NodeView;

Register via PluginContext.registerNodeView():

context.registerNodeView('code_block', (node, getState, dispatch) => {
const dom = document.createElement('pre');
dom.dataset.blockId = node.id;
const contentDOM = document.createElement('code');
dom.appendChild(contentDOM);
return { dom, contentDOM };
});

Stores NodeViewFactory registrations.

import { NodeViewRegistry } from '@notectl/core';
const registry = new NodeViewRegistry();
MethodSignatureDescription
registerNodeView(type: string, factory: NodeViewFactory) => voidRegister a factory for a node type
getNodeViewFactory(type: string) => NodeViewFactory | undefinedLook up by type
removeNodeView(type: string) => voidRemove a registration
clear() => voidRemove all registrations

Manages cursor display during IME composition, ensuring the cursor remains visible and correctly positioned.

import { CursorWrapper } from '@notectl/core';
const wrapper = new CursorWrapper(container, schemaRegistry);
PropertyTypeDescription
isActivebooleanWhether the cursor wrapper is currently active
MethodSignatureDescription
onCompositionStart(state: EditorState) => voidActivate the wrapper when composition begins
cleanup() => voidDeactivate and remove the wrapper element

Utility functions for detecting the current platform and text direction.

import { isMac, isFirefox, isWebKit, getTextDirection, isRtlContext } from '@notectl/core';
FunctionReturn TypeDescription
isMac()booleanReturns true on macOS (used for Cmd vs Ctrl keybindings)
isFirefox()booleanReturns true in Firefox
isWebKit()booleanReturns true in WebKit/Safari
getTextDirection(element)'ltr' | 'rtl'Computes the text direction of an element
isRtlContext(element)booleanReturns true if the element is in a right-to-left context

Utilities for injecting CSS styles into the document or shadow DOM. Used by plugins that need to register custom stylesheets.

Injects CSS into the document via a <style> element:

import { injectContentStyles } from '@notectl/core';
const styleEl = injectContentStyles('.highlight { background: yellow }', {
id: 'my-plugin-styles',
nonce: 'abc123',
});
interface InjectStylesOptions {
readonly nonce?: string;
readonly document?: Document;
readonly container?: HTMLElement;
readonly id?: string;
}

Removes a previously injected <style> element by its id:

import { removeContentStyles } from '@notectl/core';
removeContentStyles('my-plugin-styles');

Creates and adopts a CSSStyleSheet (for shadow DOM):

import { adoptContentStyles } from '@notectl/core';
const sheet = adoptContentStyles('.highlight { background: yellow }', {
target: shadowRoot,
});
interface AdoptStylesOptions {
readonly target?: Document | ShadowRoot;
readonly replace?: boolean;
}

Removes a previously adopted stylesheet:

import { removeAdoptedStyles } from '@notectl/core';
removeAdoptedStyles(sheet, shadowRoot);

  • Commands — view movement commands that use caret navigation
  • Plugin InterfaceregisterNodeView(), registerStyleSheet()
  • SchemaNodeSpec.toDOM() which NodeView overrides