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:

Parameter Type Description
container HTMLElement The editor’s content element
state EditorState Current editor state
direction CaretDirection 'left', 'right', 'up', or 'down'
caretRect DOMRect | null Optional 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);

getCaretRectFromSelection(domSel, container?)

Section titled “getCaretRectFromSelection(domSel, 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()!);
// With optional container for scoped lookups:
const rect2: DOMRect | null = getCaretRectFromSelection(window.getSelection()!, container);
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;
}
Property Type Description
dom HTMLElement The root DOM element managed by this view
contentDOM HTMLElement | null The element where text content is rendered. null for void nodes
Method Description
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();
Method Signature Description
registerNodeView (type: string, factory: NodeViewFactory) => void Register a factory for a node type
getNodeViewFactory (type: string) => NodeViewFactory | undefined Look up by type
removeNodeView (type: string) => void Remove a registration
clear () => void Remove 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);
Property Type Description
isActive boolean Whether the cursor wrapper is currently active
Method Signature Description
onCompositionStart (state: EditorState) => void Activate the wrapper when composition begins
cleanup () => void Deactivate and remove the wrapper element

Utility functions for detecting the current platform and text direction.

import { isMac, isFirefox, isWebKit, getTextDirection, isRtlContext } from '@notectl/core';
Function Return Type Description
isMac() boolean Returns true on macOS (used for Cmd vs Ctrl keybindings)
isFirefox() boolean Returns true in Firefox
isWebKit() boolean Returns true in WebKit/Safari
getTextDirection(element) 'ltr' | 'rtl' Computes the text direction of an element
isRtlContext(element) boolean Returns 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