UNPKG

@portabletext/editor

Version:

Portable Text Editor made in React

1,022 lines (1,021 loc) 38.4 kB
import { resolveContainerAt, isInline, isSpanNode, getEnclosingBlock, hasNode, getBlock, getNodes, parentPath, getPathSubSchema, isTextBlockNode, getSibling } from "./get-path-sub-schema.js"; import { isKeyedSegment, getNode, isObjectNode, getNodeChildren, getChildren } from "./get-ancestor.js"; import { isEqualPaths, getSelectionStartPoint as getSelectionStartPoint$1, getSelectionEndPoint as getSelectionEndPoint$1, sliceBlocks, getBlockStartPoint, getBlockEndPoint, isEqualSelectionPoints, isSelectionCollapsed as isSelectionCollapsed$1, blockOffsetToSpanSelectionPoint, getAncestorTextBlock, spanSelectionPointToBlockOffset, isListBlock } from "./util.slice-blocks.js"; import { isSpan, isTextBlock } from "@portabletext/schema"; function comparePaths(path, another, root) { const min = Math.min(path.length, another.length); let currentChildren = root?.children, currentNode; for (let i = 0; i < min; i++) { const segment = path[i], otherSegment = another[i]; if (isKeyedSegment(segment) && isKeyedSegment(otherSegment)) { if (segment._key === otherSegment._key) { currentChildren && (currentNode = currentChildren.find((c) => c._key === segment._key), currentChildren = void 0); continue; } if (currentChildren) { const segmentIndex = currentChildren.findIndex((c) => c._key === segment._key), otherSegmentIndex = currentChildren.findIndex((c) => c._key === otherSegment._key); if (segmentIndex !== -1 && otherSegmentIndex !== -1) return segmentIndex < otherSegmentIndex ? -1 : 1; } if (segment._key < otherSegment._key) return -1; if (segment._key > otherSegment._key) return 1; continue; } if (typeof segment == "string" && typeof otherSegment == "string") { if (segment === otherSegment) { if (currentNode) { const fieldValue = currentNode[segment]; currentChildren = Array.isArray(fieldValue) ? fieldValue : void 0, currentNode = void 0; } continue; } if (segment < otherSegment) return -1; if (segment > otherSegment) return 1; continue; } if (typeof segment == "number" && typeof otherSegment == "number") { if (segment < otherSegment) return -1; if (segment > otherSegment) return 1; continue; } break; } return 0; } function comparePoints(point, another, root) { const result = comparePaths(point.path, another.path, root); return result === 0 ? point.offset < another.offset ? -1 : point.offset > another.offset ? 1 : 0 : result; } function isAfterPoint(point, another, root) { return comparePoints(point, another, root) === 1; } function isBackwardRange(range, root) { const { anchor, focus } = range; return isAfterPoint(anchor, focus, root); } function rangeEdges(range, root) { const { anchor, focus } = range; return isBackwardRange(range, root) ? [focus, anchor] : [anchor, focus]; } function isEditableContainer(snapshot, _node, path) { if (snapshot.context.containers.size === 0) return !1; const resolved = resolveContainerAt(snapshot.context.containers, snapshot.context.value, path); return !!(resolved && "field" in resolved); } function rangesOverlap(rangeA, rangeB, root) { const [startA, endA] = rangeEdges(rangeA, root), [startB, endB] = rangeEdges(rangeB, root); return comparePoints(startA, endB, root) <= 0 && comparePoints(startB, endA, root) <= 0; } const isSelectionCollapsed = (snapshot) => snapshot.context.selection ? isEqualPaths(snapshot.context.selection.anchor.path, snapshot.context.selection.focus.path) && snapshot.context.selection.anchor.offset === snapshot.context.selection.focus.offset : !1; function getInline(snapshot, path) { const entry = getNode(snapshot, path); if (!(!entry || !isInline(snapshot, path)) && !(!isSpanNode({ schema: snapshot.context.schema }, entry.node) && !isObjectNode({ schema: snapshot.context.schema }, entry.node))) return { node: entry.node, path: entry.path }; } const getFocusChild = (snapshot) => { const selection = snapshot.context.selection; if (selection) return getInline(snapshot, selection.focus.path); }, getFocusInlineObject = (snapshot) => { const focusChild = getFocusChild(snapshot); return focusChild && !isSpanNode(snapshot.context, focusChild.node) ? { node: focusChild.node, path: focusChild.path } : void 0; }, getFocusSpan = (snapshot) => { const focusChild = getFocusChild(snapshot); return focusChild && isSpan(snapshot.context, focusChild.node) ? { node: focusChild.node, path: focusChild.path } : void 0; }, getFocusBlock = (snapshot) => { const selection = snapshot.context.selection; if (selection) return getEnclosingBlock(snapshot, selection.focus.path); }, getFocusTextBlock = (snapshot) => { const focusBlock = getFocusBlock(snapshot); return focusBlock && isTextBlock(snapshot.context, focusBlock.node) ? { node: focusBlock.node, path: focusBlock.path } : void 0; }; function getRootAcceptedTypes(schema) { return /* @__PURE__ */ new Set([schema.block.name, ...schema.blockObjects.map((blockObject) => blockObject.name)]); } const getSelectedValue = (snapshot) => { const selection = snapshot.context.selection; if (!selection) return []; const startPoint = getSelectionStartPoint$1(selection), endPoint = getSelectionEndPoint$1(selection); return !startPoint || !endPoint ? [] : sliceArray({ context: snapshot.context, blocks: snapshot.context.value, pathPrefix: [], fieldNameInPrefix: void 0, startEdge: startPoint, endEdge: endPoint }); }; function sliceArray({ context, blocks, pathPrefix, fieldNameInPrefix, startEdge, endEdge, parent }) { if (blocks.length === 0) return []; const startIdx = resolveIndex(blocks, pathPrefix, startEdge), endIdx = resolveIndex(blocks, pathPrefix, endEdge); if (startIdx === -1 || endIdx === -1) return []; const [lo, hi, loEdge, hiEdge] = startIdx <= endIdx ? [startIdx, endIdx, startEdge, endEdge] : [endIdx, startIdx, endEdge, startEdge], result = []; for (let i = lo; i <= hi; i++) { const block = blocks[i], blockPath = [...pathPrefix, ...fieldNameInPrefix ? [fieldNameInPrefix] : [], { _key: block._key }], startPointForBlock = i === lo ? loEdge : "array-start", endPointForBlock = i === hi ? hiEdge : "array-end", startInside = edgeIsInside(startPointForBlock, blockPath), endInside = edgeIsInside(endPointForBlock, blockPath); if (!startInside && !endInside) { result.push(block); continue; } if (isTextBlock({ schema: context.schema }, block)) { const sliced = sliceBoundaryTextBlock({ context, block, blockPath, startEdge: startPointForBlock, endEdge: endPointForBlock }); sliced && result.push(sliced); continue; } const childInfo = getNodeChildren(context, block, parent); if (!childInfo) { result.push(block); continue; } const innerSliced = sliceArray({ context, blocks: childInfo.children, pathPrefix: blockPath, fieldNameInPrefix: childInfo.fieldName, startEdge: startPointForBlock, endEdge: endPointForBlock, parent: childInfo.parent }), updatedBlock = { ...block }; updatedBlock[childInfo.fieldName] = innerSliced, result.push(updatedBlock); } return result; } function resolveIndex(blocks, pathPrefix, edge) { return edge === "array-start" ? 0 : edge === "array-end" ? blocks.length - 1 : findBlockIndexForPoint(blocks, pathPrefix, edge); } function findBlockIndexForPoint(blocks, pathPrefix, point) { let nodeSegmentIndex = pathPrefix.length; nodeSegmentIndex < point.path.length && typeof point.path[nodeSegmentIndex] == "string" && nodeSegmentIndex++; const segment = point.path[nodeSegmentIndex]; return segment === void 0 ? -1 : isKeyedSegment(segment) ? blocks.findIndex((block) => block._key === segment._key) : typeof segment == "number" && segment >= 0 && segment < blocks.length ? segment : -1; } function edgeIsInside(edge, blockPath) { return edge === "array-start" || edge === "array-end" ? !1 : edge.path.length > blockPath.length; } function sliceBoundaryTextBlock({ context, block, blockPath, startEdge, endEdge }) { if (!isTextBlock({ schema: context.schema }, block)) return block; const firstChild = block.children[0], lastChild = block.children[block.children.length - 1], blockRelativeStart = stripPrefix(startEdge, blockPath, block, { fallback: "block-start", firstChild }), blockRelativeEnd = stripPrefix(endEdge, blockPath, block, { fallback: "block-end", lastChild, context }); return sliceBlocks({ context: { ...context, selection: { anchor: blockRelativeStart, focus: blockRelativeEnd } }, blocks: [block] })[0]; } function stripPrefix(edge, blockPath, block, opts) { return edge !== "array-start" && edge !== "array-end" && edge.path.length > blockPath.length ? { path: edge.path.slice(blockPath.length - 1), offset: edge.offset } : opts.fallback === "block-start" ? { path: [{ _key: block._key }, "children", { _key: opts.firstChild?._key ?? "" }], offset: 0 } : { path: [{ _key: block._key }, "children", { _key: opts.lastChild?._key ?? "" }], offset: opts.lastChild && opts.context && isSpan({ schema: opts.context.schema }, opts.lastChild) ? opts.lastChild.text.length : 0 }; } const getFragment = (snapshot) => { const envelope = getSelectedValue(snapshot); if (envelope.length === 0) return []; const { schema, containers, value } = snapshot.context, rootAcceptedTypes = getRootAcceptedTypes(schema), textBlockName = schema.block.name; let lastRootValid = envelope, lastRootValidPrefix = [], current = envelope; const pathPrefix = []; for (; current.length === 1; ) { const single = current[0], singlePath = [...pathPrefix, { _key: single._key }], container = resolveContainerAt(containers, value, singlePath); if (!container || !("field" in container)) break; const children = single[container.field.name]; if (!Array.isArray(children) || children.length === 0) break; const childBlocks = children; if (pathPrefix.push({ _key: single._key }, container.field.name), current = childBlocks, childBlocks.every((block) => block._type === textBlockName || rootAcceptedTypes.has(block._type))) { lastRootValid = childBlocks, lastRootValidPrefix = [...pathPrefix]; continue; } if (childBlocks.length !== 1) break; } return lastRootValid.map((block) => ({ node: block, path: [...lastRootValidPrefix, { _key: block._key }] })); }, getSelectionEndBlock = (snapshot) => { const endPoint = getSelectionEndPoint$1(snapshot.context.selection); if (endPoint) return getFocusBlock({ ...snapshot, context: { ...snapshot.context, selection: { anchor: endPoint, focus: endPoint } } }); }, getSelectionStartBlock = (snapshot) => { const startPoint = getSelectionStartPoint$1(snapshot.context.selection); if (startPoint) return getFocusBlock({ ...snapshot, context: { ...snapshot.context, selection: { anchor: startPoint, focus: startPoint } } }); }; function isOverlappingSelection(selection) { return (snapshot) => { const editorSelection = snapshot.context.selection; return !selection || !editorSelection || !hasNode(snapshot, selection.anchor.path) || !hasNode(snapshot, selection.focus.path) || !hasNode(snapshot, editorSelection.anchor.path) || !hasNode(snapshot, editorSelection.focus.path) ? !1 : rangesOverlap(selection, editorSelection, { children: snapshot.context.value }); }; } const isSelectionExpanded$1 = (snapshot) => snapshot.context.selection !== null && !isSelectionCollapsed(snapshot), getSelectedBlocks = (snapshot) => { const selection = snapshot.context.selection; if (!selection) return []; const startPoint = getSelectionStartPoint$1(selection), endPoint = getSelectionEndPoint$1(selection); if (!startPoint || !endPoint) return []; const startRootKey = startPoint.path.find(isKeyedSegment)?._key, endRootKey = endPoint.path.find(isKeyedSegment)?._key; if (startRootKey && startRootKey === endRootKey) { const block = getBlock(snapshot, [{ _key: startRootKey }]); if (block) return [{ node: block.node, path: block.path }]; } const result = []; for (const entry of getNodes(snapshot, { from: startPoint.path, to: endPoint.path, match: (_, path) => path.length === 1 })) { const block = getBlock(snapshot, entry.path); block && result.push({ node: block.node, path: block.path }); } return result; }, isSelectingEntireBlocks = (snapshot) => { if (!snapshot.context.selection) return !1; const startPoint = snapshot.context.selection.backward ? snapshot.context.selection.focus : snapshot.context.selection.anchor, endPoint = snapshot.context.selection.backward ? snapshot.context.selection.anchor : snapshot.context.selection.focus, startBlock = getSelectionStartBlock(snapshot), endBlock = getSelectionEndBlock(snapshot); if (!startBlock || !endBlock) return !1; const startBlockStartPoint = getBlockStartPoint({ context: snapshot.context, block: startBlock }), endBlockEndPoint = getBlockEndPoint({ context: snapshot.context, block: endBlock }); return isEqualSelectionPoints(startBlockStartPoint, startPoint) && isEqualSelectionPoints(endBlockEndPoint, endPoint); }; function isBlockPath(path) { const firstSegment = path.at(0); return path.length === 1 && firstSegment !== void 0 && isRecord(firstSegment) && "_key" in firstSegment && typeof firstSegment._key == "string"; } function isRecord(value) { return !!value && (typeof value == "object" || typeof value == "function"); } function isSelectionExpanded(selection) { return selection ? !isSelectionCollapsed$1(selection) : !1; } function findSibling(snapshot, path, direction, match) { if (path.length === 0) return; const lastSegment = path.at(-1); if (!isKeyedSegment(lastSegment)) return; const parent = parentPath(path), children = getChildren(snapshot, parent), currentIndex = children.findIndex((child) => child.node._key === lastSegment._key); return currentIndex === -1 ? void 0 : (direction === "next" ? children.slice(currentIndex + 1) : children.slice(0, currentIndex).reverse()).find(match); } const getSelectionEndPoint = (snapshot) => { if (snapshot.context.selection) return snapshot.context.selection.backward ? snapshot.context.selection.anchor : snapshot.context.selection.focus; }, getNextSpan = (snapshot) => { const point = getSelectionEndPoint(snapshot); if (point) return findSibling(snapshot, point.path, "next", (entry) => isSpan(snapshot.context, entry.node)); }, getSelectionStartPoint = (snapshot) => { if (snapshot.context.selection) return snapshot.context.selection.backward ? snapshot.context.selection.focus : snapshot.context.selection.anchor; }, getPreviousSpan = (snapshot) => { const point = getSelectionStartPoint(snapshot); if (point) return findSibling(snapshot, point.path, "previous", (entry) => isSpan(snapshot.context, entry.node)); }; function getSelectedChildren(options) { const filter = options?.filter; return (snapshot) => { const startPoint = getSelectionStartPoint$1(snapshot.context.selection), endPoint = getSelectionEndPoint$1(snapshot.context.selection); if (!startPoint || !endPoint) return []; const startChildKey = startPoint.path.findLast(isKeyedSegment)?._key, endChildKey = endPoint.path.findLast(isKeyedSegment)?._key, result = []; if (startChildKey && startChildKey === endChildKey && isInline(snapshot, startPoint.path)) { const node = getNode(snapshot, startPoint.path); if (node) { const child = node.node; let skip = !1; child._key === startChildKey && isSpan(snapshot.context, child) && (startPoint.offset >= child.text.length && (skip = !0), endPoint.offset <= 0 && (skip = !0)), !skip && (!filter || filter(child)) && result.push({ node: child, path: node.path }); } return result; } for (const entry of getNodes(snapshot, { from: startPoint.path, to: endPoint.path, match: (_, path) => isInline(snapshot, path) })) { const child = entry.node; child._key === startChildKey && isSpan(snapshot.context, child) && startPoint.offset >= child.text.length || child._key === endChildKey && isSpan(snapshot.context, child) && endPoint.offset <= 0 || filter && !filter(child) || result.push({ node: child, path: entry.path }); } return result; }; } const getSelectedSpans = (snapshot) => snapshot.context.selection ? getSelectedChildren({ filter: (child) => isSpan(snapshot.context, child) })(snapshot) : [], getMarkState = (snapshot) => { if (!snapshot.context.selection) return; let selection = snapshot.context.selection; if (isBlockPath(selection.anchor.path)) { const spanSelectionPoint = blockOffsetToSpanSelectionPoint({ snapshot, blockOffset: { path: selection.anchor.path, offset: selection.anchor.offset }, direction: selection.backward ? "backward" : "forward" }); selection = spanSelectionPoint ? { ...selection, anchor: spanSelectionPoint } : selection; } if (isBlockPath(selection.focus.path)) { const spanSelectionPoint = blockOffsetToSpanSelectionPoint({ snapshot, blockOffset: { path: selection.focus.path, offset: selection.focus.offset }, direction: selection.backward ? "backward" : "forward" }); selection = spanSelectionPoint ? { ...selection, focus: spanSelectionPoint } : selection; } const focusSpan = getFocusSpan({ ...snapshot, context: { ...snapshot.context, selection } }); if (!focusSpan) return; if (isSelectionExpanded(selection)) { const spanInfo = getSelectedSpans({ ...snapshot, context: { ...snapshot.context, selection } }).map((span) => { const block = getAncestorTextBlock(snapshot, span.path); return { marks: span.node.marks ?? [], decoratorNames: getPathSubSchema(snapshot, span.path).decorators.map((decorator) => decorator.name), markDefKeys: (block?.node.markDefs ?? []).map((markDef) => markDef._key) }; }), candidateMarks = /* @__PURE__ */ new Set(); for (const { marks: spanMarks } of spanInfo) for (const mark of spanMarks) candidateMarks.add(mark); const marks2 = []; for (const candidate of candidateMarks) { const isDecoratorSomewhere = spanInfo.some(({ decoratorNames }) => decoratorNames.includes(candidate)); let active = !0; for (const { marks: spanMarks, decoratorNames, markDefKeys } of spanInfo) if ((isDecoratorSomewhere ? decoratorNames.includes(candidate) : markDefKeys.includes(candidate)) && !spanMarks.includes(candidate)) { active = !1; break; } active && marks2.push(candidate); } return { state: "unchanged", marks: marks2 }; } const decorators = getPathSubSchema(snapshot, focusSpan.path).decorators.map((decorator) => decorator.name), marks = focusSpan.node.marks ?? [], marksWithoutAnnotations = marks.filter((mark) => decorators.includes(mark)), spanHasAnnotations = marks.length > marksWithoutAnnotations.length, spanIsEmpty = focusSpan.node.text.length === 0, atTheBeginningOfSpan = snapshot.context.selection.anchor.offset === 0, atTheEndOfSpan = snapshot.context.selection.anchor.offset === focusSpan.node.text.length, previousSpan = getPreviousSpan({ context: { ...snapshot.context, selection } }), nextSpan = getNextSpan({ context: { ...snapshot.context, selection } }), nextSpanAnnotations = nextSpan?.node?.marks?.filter((mark) => !decorators.includes(mark)) ?? [], spanAnnotations = marks.filter((mark) => !decorators.includes(mark)), previousSpanHasAnnotations = previousSpan ? previousSpan.node.marks?.some((mark) => !decorators.includes(mark)) : !1, previousSpanHasSameAnnotations = previousSpan ? previousSpan.node.marks?.filter((mark) => !decorators.includes(mark)).every((mark) => marks.includes(mark)) : !1, previousSpanHasSameAnnotation = previousSpan ? previousSpan.node.marks?.some((mark) => !decorators.includes(mark) && marks.includes(mark)) : !1, previousSpanHasSameMarks = previousSpan ? previousSpan.node.marks?.every((mark) => marks.includes(mark)) : !1, nextSpanSharesSomeAnnotations = spanAnnotations.some((mark) => nextSpanAnnotations?.includes(mark)); if (spanHasAnnotations && !spanIsEmpty) { if (atTheBeginningOfSpan) { if (previousSpanHasSameMarks) return { state: "changed", previousMarks: marks, marks: previousSpan?.node.marks ?? [] }; if (previousSpanHasSameAnnotations) return { state: "changed", previousMarks: marks, marks: previousSpan?.node.marks ?? [] }; if (previousSpanHasSameAnnotation) return { state: "unchanged", marks: focusSpan.node.marks ?? [] }; if (!previousSpan) return { state: "changed", previousMarks: marks, marks: [] }; } if (atTheEndOfSpan) { if (!nextSpan) return { state: "changed", previousMarks: marks, marks: [] }; if (nextSpanAnnotations.length > 0 && !nextSpanSharesSomeAnnotations) return { state: "changed", previousMarks: marks, marks: [] }; if (nextSpanSharesSomeAnnotations && nextSpanAnnotations.length < spanAnnotations.length || !nextSpanSharesSomeAnnotations) return { state: "changed", previousMarks: marks, marks: nextSpan?.node.marks ?? [] }; } } return atTheBeginningOfSpan && !spanIsEmpty && previousSpan ? previousSpanHasAnnotations ? { state: "changed", marks, previousMarks: previousSpan?.node.marks ?? [] } : { state: "changed", previousMarks: marks, marks: (previousSpan?.node.marks ?? []).filter((mark) => decorators.includes(mark)) } : { state: "unchanged", marks }; }; function getActiveAnnotationsMarks(snapshot) { const schema = snapshot.context.schema; return (getMarkState(snapshot)?.marks ?? []).filter((mark) => !schema.decorators.map((decorator) => decorator.name).includes(mark)); } const getSelectedTextBlocks = (snapshot) => { const selection = snapshot.context.selection; if (!selection) return []; const startPoint = getSelectionStartPoint$1(selection), endPoint = getSelectionEndPoint$1(selection); if (!startPoint || !endPoint) return []; const startBlock = getEnclosingBlock(snapshot, startPoint.path), endBlock = getEnclosingBlock(snapshot, endPoint.path); if (startBlock && endBlock && startBlock.node._key === endBlock.node._key && isTextBlock(snapshot.context, startBlock.node)) return [{ node: startBlock.node, path: startBlock.path }]; const result = []; for (const entry of getNodes(snapshot, { from: startPoint.path, to: endPoint.path, match: (node) => isTextBlock(snapshot.context, node) })) isTextBlock(snapshot.context, entry.node) && result.push({ node: entry.node, path: entry.path }); return result; }; function getActiveDecorators(snapshot) { const decoratorState = snapshot.decoratorState, markState = getMarkState(snapshot), selectedBlocks = getSelectedTextBlocks(snapshot), decorators = /* @__PURE__ */ new Set(); for (const block of selectedBlocks) for (const decorator of getPathSubSchema(snapshot, block.path).decorators) decorators.add(decorator.name); if (decorators.size === 0) for (const decorator of snapshot.context.schema.decorators) decorators.add(decorator.name); let activeDecorators = (markState?.marks ?? []).filter((mark) => decorators.has(mark)); for (const decorator in decoratorState) decoratorState[decorator] === !1 ? activeDecorators = activeDecorators.filter((activeDecorator) => activeDecorator !== decorator) : decoratorState[decorator] === !0 && (activeDecorators.includes(decorator) || activeDecorators.push(decorator)); return activeDecorators; } function isActiveAnnotation(annotation, options) { return (snapshot) => { if ((options?.mode ?? "full") === "partial") return getSelectedValue(snapshot).flatMap((block) => isTextBlock(snapshot.context, block) ? block.markDefs ?? [] : []).some((markDef) => markDef._type === annotation); const selectionMarkDefs = getSelectedTextBlocks(snapshot).flatMap((block) => block.node.markDefs ?? []), activeAnnotations = getActiveAnnotationsMarks(snapshot); return selectionMarkDefs.filter((markDef) => markDef._type === annotation && activeAnnotations.includes(markDef._key)).length > 0; }; } const getApplicableSchema = (snapshot) => { if (!snapshot.context.selection) return EMPTY; const focusBlock = getFocusBlock(snapshot), focusSub = focusBlock ? getPathSubSchema(snapshot, focusBlock.path) : void 0, insertion = focusSub ? { blockObjects: namesOfArray(focusSub.blockObjects), inlineObjects: namesOfArray(focusSub.inlineObjects) } : { blockObjects: EMPTY_SET, inlineObjects: EMPTY_SET }, textBlocks = getSelectedTextBlocks(snapshot); if (textBlocks.length === 0) return { decorators: EMPTY_SET, annotations: EMPTY_SET, lists: EMPTY_SET, styles: EMPTY_SET, ...insertion }; let textApplicable = textCategoryNames(getPathSubSchema(snapshot, textBlocks[0].path)); for (let i = 1; i < textBlocks.length; i++) textApplicable = unionTextCategories(textApplicable, textCategoryNames(getPathSubSchema(snapshot, textBlocks[i].path))); return { ...textApplicable, ...insertion }; }; function compareApplicableSchema(a, b) { return a === b ? !0 : sameSet(a.decorators, b.decorators) && sameSet(a.annotations, b.annotations) && sameSet(a.lists, b.lists) && sameSet(a.styles, b.styles) && sameSet(a.blockObjects, b.blockObjects) && sameSet(a.inlineObjects, b.inlineObjects); } const EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set()), EMPTY = Object.freeze({ decorators: EMPTY_SET, annotations: EMPTY_SET, lists: EMPTY_SET, styles: EMPTY_SET, blockObjects: EMPTY_SET, inlineObjects: EMPTY_SET }); function textCategoryNames(schema) { return { decorators: namesOfArray(schema.decorators), annotations: namesOfArray(schema.annotations), lists: namesOfArray(schema.lists), styles: namesOfArray(schema.styles) }; } function unionTextCategories(a, b) { return { decorators: unionSet(a.decorators, b.decorators), annotations: unionSet(a.annotations, b.annotations), lists: unionSet(a.lists, b.lists), styles: unionSet(a.styles, b.styles) }; } function namesOfArray(entries) { return new Set(entries.map((entry) => entry.name)); } function unionSet(a, b) { const result = new Set(a); for (const item of b) result.add(item); return result; } function sameSet(a, b) { if (a === b) return !0; if (a.size !== b.size) return !1; for (const item of a) if (!b.has(item)) return !1; return !0; } const getActiveAnnotations = (snapshot) => { if (!snapshot.context.selection) return []; const selectedBlocks = getSelectedTextBlocks(snapshot), markState = getMarkState(snapshot), decoratorNames = /* @__PURE__ */ new Set(); for (const block of selectedBlocks) for (const decorator of getPathSubSchema(snapshot, block.path).decorators) decoratorNames.add(decorator.name); if (decoratorNames.size === 0) for (const decorator of snapshot.context.schema.decorators) decoratorNames.add(decorator.name); const activeAnnotations = (markState?.marks ?? []).filter((mark) => !decoratorNames.has(mark)); return selectedBlocks.flatMap((block) => block.node.markDefs ?? []).filter((markDef) => activeAnnotations.includes(markDef._key)); }, getActiveListItem = (snapshot) => { if (!snapshot.context.selection) return; const selectedTextBlocks = getSelectedTextBlocks(snapshot), firstTextBlock = selectedTextBlocks.at(0); if (!firstTextBlock) return; const firstListItem = firstTextBlock.node.listItem; if (firstListItem && selectedTextBlocks.filter((block) => getPathSubSchema(snapshot, block.path).lists.some((l) => l.name === firstListItem)).every((block) => block.node.listItem === firstListItem)) return firstListItem; }, getActiveStyle = (snapshot) => { if (!snapshot.context.selection) return; const selectedTextBlocks = getSelectedTextBlocks(snapshot), firstTextBlock = selectedTextBlocks.at(0); if (!firstTextBlock) return; const firstStyle = firstTextBlock.node.style; if (firstStyle && selectedTextBlocks.filter((block) => getPathSubSchema(snapshot, block.path).styles.some((s) => s.name === firstStyle)).every((block) => block.node.style === firstStyle)) return firstStyle; }, getNextInlineObject = (snapshot) => { const point = getSelectionEndPoint(snapshot); return point ? findSibling(snapshot, point.path, "next", (entry) => isObjectNode({ schema: snapshot.context.schema }, entry.node)) : void 0; }, getPreviousInlineObject = (snapshot) => { const point = getSelectionStartPoint(snapshot); return point ? findSibling(snapshot, point.path, "previous", (entry) => isObjectNode({ schema: snapshot.context.schema }, entry.node)) : void 0; }, getSelectionText = (snapshot) => { const selectedValue = getSelectedValue(snapshot); return collectText(snapshot.context, selectedValue); }; function collectText(context, blocks, parent) { let text = ""; for (const block of blocks) { if (isTextBlock(context, block)) { for (const child of block.children) isSpan(context, child) && (text += child.text); continue; } const childInfo = getNodeChildren(context, block, parent); childInfo && (text += collectText(context, childInfo.children, childInfo.parent)); } return text; } const getCaretWordSelection = (snapshot) => { if (!snapshot.context.selection || !isSelectionCollapsed(snapshot)) return null; const focusTextBlock = getFocusTextBlock(snapshot), selectionStartPoint = getSelectionStartPoint(snapshot), selectionStartOffset = selectionStartPoint ? spanSelectionPointToBlockOffset({ snapshot, selectionPoint: selectionStartPoint }) : void 0; if (!focusTextBlock || !selectionStartPoint || !selectionStartOffset) return null; const previousInlineObject = getPreviousInlineObject(snapshot), blockStartPoint = getBlockStartPoint({ context: snapshot.context, block: focusTextBlock }), textDirectlyBefore = getSelectionText({ context: { ...snapshot.context, selection: { anchor: previousInlineObject ? { path: previousInlineObject.path, offset: 0 } : blockStartPoint, focus: selectionStartPoint } } }).split(/\s+/).at(-1), nextInlineObject = getNextInlineObject(snapshot), blockEndPoint = getBlockEndPoint({ context: snapshot.context, block: focusTextBlock }), textDirectlyAfter = getSelectionText({ context: { ...snapshot.context, selection: { anchor: selectionStartPoint, focus: nextInlineObject ? { path: nextInlineObject.path, offset: 0 } : blockEndPoint } } }).split(/\s+/).at(0); if ((textDirectlyBefore === void 0 || textDirectlyBefore === "") && (textDirectlyAfter === void 0 || textDirectlyAfter === "")) return null; const caretWordStartOffset = textDirectlyBefore ? { ...selectionStartOffset, offset: selectionStartOffset.offset - textDirectlyBefore.length } : selectionStartOffset, caretWordEndOffset = textDirectlyAfter ? { ...selectionStartOffset, offset: selectionStartOffset.offset + textDirectlyAfter.length } : selectionStartOffset, caretWordStartSelectionPoint = blockOffsetToSpanSelectionPoint({ snapshot, blockOffset: caretWordStartOffset, direction: "backward" }), caretWordEndSelectionPoint = blockOffsetToSpanSelectionPoint({ snapshot, blockOffset: caretWordEndOffset, direction: "forward" }); if (!caretWordStartSelectionPoint || !caretWordEndSelectionPoint) return null; const caretWordSelection = { anchor: caretWordStartSelectionPoint, focus: caretWordEndSelectionPoint }; return isSelectionExpanded$1({ context: { ...snapshot.context, selection: caretWordSelection } }) ? caretWordSelection : null; }, getFirstBlock = (snapshot) => { const focusBlock = getFocusBlock(snapshot); if (focusBlock) { const first = getChildren(snapshot, parentPath(focusBlock.path)).at(0); if (first) return getBlock(snapshot, first.path); } const node = snapshot.context.value[0]; return node ? { node, path: [{ _key: node._key }] } : void 0; }, getFocusBlockObject = (snapshot) => { const focusBlock = getFocusBlock(snapshot); if (focusBlock && !isTextBlockNode(snapshot.context, focusBlock.node) && !isEditableContainer(snapshot, focusBlock.node, focusBlock.path)) return { node: focusBlock.node, path: focusBlock.path }; }, getFocusListBlock = (snapshot) => { const focusTextBlock = getFocusTextBlock(snapshot); return focusTextBlock && isListBlock(snapshot.context, focusTextBlock.node) ? { node: focusTextBlock.node, path: focusTextBlock.path } : void 0; }, getLastBlock = (snapshot) => { const focusBlock = getFocusBlock(snapshot); if (focusBlock) { const last = getChildren(snapshot, parentPath(focusBlock.path)).at(-1); if (last) return getBlock(snapshot, last.path); } const node = snapshot.context.value.at(-1); return node ? { node, path: [{ _key: node._key }] } : void 0; }, getNextBlock = (snapshot) => { const selectionEndBlock = getSelectionEndBlock(snapshot); if (!selectionEndBlock) return; const next = getSibling(snapshot, selectionEndBlock.path, "next"); if (next) return getBlock(snapshot, next.path); }, getPreviousBlock = (snapshot) => { const selectionStartBlock = getSelectionStartBlock(snapshot); if (!selectionStartBlock) return; const previous = getSibling(snapshot, selectionStartBlock.path, "previous"); if (previous) return getBlock(snapshot, previous.path); }, getSelectionEndChild = (snapshot) => { const endPoint = getSelectionEndPoint$1(snapshot.context.selection); if (endPoint) return getFocusChild({ ...snapshot, context: { ...snapshot.context, selection: { anchor: endPoint, focus: endPoint } } }); }, getSelectionStartChild = (snapshot) => { const startPoint = getSelectionStartPoint$1(snapshot.context.selection); if (startPoint) return getFocusChild({ ...snapshot, context: { ...snapshot.context, selection: { anchor: startPoint, focus: startPoint } } }); }; function isActiveDecorator(decorator) { return (snapshot) => { if (isSelectionExpanded$1(snapshot)) { const inScopeSpans = getSelectedSpans(snapshot).filter((span) => getPathSubSchema(snapshot, span.path).decorators.some((d) => d.name === decorator)); return inScopeSpans.length > 0 && inScopeSpans.every((span) => span.node.marks?.includes(decorator)); } return getActiveDecorators(snapshot).includes(decorator); }; } function isActiveListItem(listItem) { return (snapshot) => getActiveListItem(snapshot) === listItem; } function isActiveStyle(style) { return (snapshot) => getActiveStyle(snapshot) === style; } function isAtTheEndOfBlock(block) { return (snapshot) => { if (!snapshot.context.selection || !isSelectionCollapsed(snapshot)) return !1; const blockEndPoint = getBlockEndPoint({ context: snapshot.context, block }); return isEqualSelectionPoints(snapshot.context.selection.focus, blockEndPoint); }; } function isAtTheStartOfBlock(block) { return (snapshot) => { if (!snapshot.context.selection || !isSelectionCollapsed(snapshot)) return !1; const blockStartPoint = getBlockStartPoint({ context: snapshot.context, block }); return isEqualSelectionPoints(snapshot.context.selection.focus, blockStartPoint); }; } export { compareApplicableSchema, comparePaths, comparePoints, getActiveAnnotations, getActiveAnnotationsMarks, getActiveDecorators, getActiveListItem, getActiveStyle, getApplicableSchema, getCaretWordSelection, getFirstBlock, getFocusBlock, getFocusBlockObject, getFocusChild, getFocusInlineObject, getFocusListBlock, getFocusSpan, getFocusTextBlock, getFragment, getInline, getLastBlock, getMarkState, getNextBlock, getNextInlineObject, getNextSpan, getPreviousBlock, getPreviousInlineObject, getPreviousSpan, getRootAcceptedTypes, getSelectedBlocks, getSelectedSpans, getSelectedTextBlocks, getSelectedValue, getSelectionEndBlock, getSelectionEndChild, getSelectionEndPoint, getSelectionStartBlock, getSelectionStartChild, getSelectionStartPoint, getSelectionText, isActiveAnnotation, isActiveDecorator, isActiveListItem, isActiveStyle, isAfterPoint, isAtTheEndOfBlock, isAtTheStartOfBlock, isBackwardRange, isEditableContainer, isOverlappingSelection, isSelectingEntireBlocks, isSelectionCollapsed, isSelectionExpanded$1 as isSelectionExpanded, rangeEdges, rangesOverlap }; //# sourceMappingURL=selector.is-at-the-start-of-block.js.map