UNPKG

prosemirror-flat-list

Version:
123 lines (112 loc) 3.48 kB
import type { DOMOutputSpec, Node as ProsemirrorNode } from 'prosemirror-model' import type { ListAttributes } from '../types' /** * @public @group Schema */ export interface ListToDOMOptions { /** * The list node to be rendered. */ node: ProsemirrorNode /** * If `true`, the list will be rendered as a native `<ul>` or `<ol>` element. * You might want to use {@link joinListElements} to join the list elements * afterward. * * @defaultValue false */ nativeList?: boolean /** * An optional function to get elements inside `<div class="list-marker">`. * Return `null` to hide the marker. */ getMarkers?: (node: ProsemirrorNode) => DOMOutputSpec[] | null /** * An optional function to get the attributes added to HTML element. */ getAttributes?: (node: ProsemirrorNode) => Record<string, string | undefined> } /** * Renders a list node to DOM output spec. * * @public @group Schema */ export function listToDOM(options: ListToDOMOptions): DOMOutputSpec { const { node, nativeList = false, getMarkers = defaultMarkerGetter, getAttributes = defaultAttributesGetter, } = options const attrs = node.attrs as ListAttributes const markerHidden = node.firstChild?.type === node.type const markers = markerHidden ? null : getMarkers(node) const domAttrs = getAttributes(node) const contentContainer: DOMOutputSpec = ['div', { class: 'list-content' }, 0] const markerContainer: DOMOutputSpec | null = markers && [ 'div', { class: 'list-marker list-marker-click-target', // Set `contenteditable` to `false` so that the cursor won't be // moved into the mark container when clicking on it. contenteditable: 'false', }, ...markers, ] if (nativeList) { const listTag = attrs.kind === 'ordered' ? 'ol' : 'ul' if (markerContainer) { return [listTag, ['li', domAttrs, markerContainer, contentContainer]] } else { return [listTag, ['li', domAttrs, 0]] } } else { if (markerContainer) { return ['div', domAttrs, markerContainer, contentContainer] } else { return ['div', domAttrs, contentContainer] } } } /** @internal */ export function defaultMarkerGetter( node: ProsemirrorNode, ): DOMOutputSpec[] | null { const attrs = node.attrs as ListAttributes switch (attrs.kind) { case 'task': // Use a `label` element here so that the area around the checkbox is also checkable. return [ [ 'label', [ 'input', { type: 'checkbox', checked: attrs.checked ? '' : undefined }, ], ], ] case 'toggle': return [] default: return null } } /** @internal */ export function defaultAttributesGetter(node: ProsemirrorNode) { const attrs = node.attrs as ListAttributes const markerHidden = node.firstChild?.type === node.type const markerType = markerHidden ? undefined : attrs.kind || 'bullet' const domAttrs = { class: 'prosemirror-flat-list', 'data-list-kind': markerType, 'data-list-order': attrs.order != null ? String(attrs.order) : undefined, 'data-list-checked': attrs.checked ? '' : undefined, 'data-list-collapsed': attrs.collapsed ? '' : undefined, 'data-list-collapsable': node.childCount >= 2 ? '' : undefined, style: attrs.order != null ? `--prosemirror-flat-list-order: ${attrs.order};` : undefined, } return domAttrs }