@portabletext/editor
Version:
Portable Text Editor made in React
1,022 lines (1,021 loc) • 38.4 kB
JavaScript
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