diff --git a/Lexical/Core/Nodes/ElementNode.swift b/Lexical/Core/Nodes/ElementNode.swift index a3faf43c..bdae28ed 100644 --- a/Lexical/Core/Nodes/ElementNode.swift +++ b/Lexical/Core/Nodes/ElementNode.swift @@ -227,6 +227,7 @@ open class ElementNode: Node { return false } + @discardableResult open func collapseAtStart(selection: RangeSelection) throws -> Bool { return false } @@ -462,4 +463,9 @@ open class ElementNode: Node { return writableSelf } + + // Shadow root functionality not yet implemented in Lexical iOS. + public func isShadowRoot() -> Bool { + return false + } } diff --git a/Lexical/Core/Nodes/TextNode.swift b/Lexical/Core/Nodes/TextNode.swift index 3d262728..a2079be1 100644 --- a/Lexical/Core/Nodes/TextNode.swift +++ b/Lexical/Core/Nodes/TextNode.swift @@ -412,6 +412,11 @@ open class TextNode: Node { return node.style } + public func setStyle(_ style: String) throws { + let writable = try getWritable() + writable.style = style + } + public func splitText(splitOffsets: [Int]) throws -> [TextNode] { try errorOnReadOnly() let textContent = getTextPart() as NSString diff --git a/Lexical/Core/Reconciler.swift b/Lexical/Core/Reconciler.swift index a85ad2dd..36a549fc 100644 --- a/Lexical/Core/Reconciler.swift +++ b/Lexical/Core/Reconciler.swift @@ -92,6 +92,17 @@ internal enum Reconciler { fatalError("Cannot run reconciler on an editor with no text storage") } + if editor.dirtyNodes.isEmpty, + editor.dirtyType == .noDirtyNodes, + let currentSelection = currentEditorState.selection, + let pendingSelection = pendingEditorState.selection, + currentSelection.isSelection(pendingSelection), + pendingSelection.dirty == false, + markedTextOperation == nil { + // should be nothing to reconcile + return + } + if let markedTextOperation, markedTextOperation.createMarkedText { guard shouldReconcileSelection == false else { editor.log(.reconciler, .warning, "should not reconcile selection whilst starting marked text!") diff --git a/Lexical/Core/Selection/RangeSelection.swift b/Lexical/Core/Selection/RangeSelection.swift index 68ce7d2f..5f31e6be 100644 --- a/Lexical/Core/Selection/RangeSelection.swift +++ b/Lexical/Core/Selection/RangeSelection.swift @@ -14,6 +14,7 @@ public class RangeSelection: BaseSelection { public var focus: Point public var dirty: Bool public var format: TextFormat + public var style: String // TODO: add style support to iOS // MARK: - Init @@ -22,6 +23,7 @@ public class RangeSelection: BaseSelection { self.focus = focus self.dirty = false self.format = format + self.style = "" anchor.selection = self focus.selection = self @@ -242,12 +244,15 @@ public class RangeSelection: BaseSelection { public func insertText(_ text: String) throws { let anchor = anchor let focus = focus - let isBefore = try isCollapsed() || anchor.isBefore(point: focus) + let anchorIsBefore = try anchor.isBefore(point: focus) + let isBefore = isCollapsed() || anchorIsBefore + let format = format + let style = style if isBefore && anchor.type == .element { - try transferStartingElementPointToTextPoint(start: anchor, end: focus, format: format) + try transferStartingElementPointToTextPoint(start: anchor, end: focus, format: format, style: style) } else if !isBefore && focus.type == .element { - try transferStartingElementPointToTextPoint(start: focus, end: anchor, format: format) + try transferStartingElementPointToTextPoint(start: focus, end: anchor, format: format, style: style) } let selectedNodes = try getNodes() @@ -256,89 +261,123 @@ public class RangeSelection: BaseSelection { let endPoint = isBefore ? focus : anchor let startOffset = firstPoint.offset let endOffset = endPoint.offset - - guard var firstNode = selectedNodes[0] as? TextNode else { + guard var firstNode = selectedNodes.first as? TextNode else { throw LexicalError.invariantViolation("insertText: first node is not a text node") } let firstNodeText = firstNode.getTextPart() let firstNodeTextLength = firstNodeText.lengthAsNSString() let firstNodeParent = try firstNode.getParentOrThrow() - - if isCollapsed() && startOffset == firstNodeTextLength && - (firstNode.isSegmented() || firstNode.isToken() || !firstNode.canInsertTextAfter() || !firstNodeParent.canInsertTextAfter()) { + var lastNode = selectedNodes.last + + if isCollapsed() && + startOffset == firstNodeTextLength && + (firstNode.isSegmented() || + firstNode.isToken() || + !firstNode.canInsertTextAfter() || + (!firstNodeParent.canInsertTextAfter() && firstNode.getNextSibling() == nil)) { var nextSibling = firstNode.getNextSibling() as? TextNode - if !isTextNode(nextSibling) || isTokenOrInertOrSegmented(nextSibling) { - nextSibling = createTextNode(text: "") - guard let nextSibling else { - return - } - if !firstNodeParent.canInsertTextAfter() { - _ = try firstNodeParent.insertAfter(nodeToInsert: nextSibling) - } else { - _ = try firstNode.insertAfter(nodeToInsert: nextSibling) + if nextSibling == nil || + !(nextSibling?.canInsertTextBefore() ?? true) || + isTokenOrSegmented(nextSibling) { + nextSibling = TextNode() + if let nextSibling { + try nextSibling.setFormat(format: format) + if !firstNodeParent.canInsertTextAfter() { + try firstNodeParent.insertAfter(nodeToInsert: nextSibling) + } else { + try firstNode.insertAfter(nodeToInsert: nextSibling) + } } } - guard let nextSibling else { - return + if let nextSibling { + try nextSibling.select(anchorOffset: 0, focusOffset: 0) + firstNode = nextSibling } - _ = try nextSibling.select(anchorOffset: 0, focusOffset: 0) - firstNode = nextSibling - if text != "" { + if text.lengthAsNSString() > 0 { try insertText(text) return } - } else if isCollapsed() && startOffset == 0 && - (firstNode.isSegmented() || firstNode.isToken() || !firstNode.canInsertTextBefore() || !firstNodeParent.canInsertTextBefore()) { + } else if isCollapsed() && + startOffset == 0 && + (firstNode.isSegmented() || + firstNode.isToken() || + !firstNode.canInsertTextBefore() || + (!firstNodeParent.canInsertTextBefore() && firstNode.getPreviousSibling() == nil)) { var prevSibling = firstNode.getPreviousSibling() as? TextNode - if !isTextNode(prevSibling ) || isTokenOrInertOrSegmented(prevSibling) { - prevSibling = createTextNode(text: "") - guard let prevSibling else { - return - } - if !firstNodeParent.canInsertTextBefore() { - _ = try firstNodeParent.insertBefore(nodeToInsert: prevSibling) - } else { - _ = try firstNode.insertBefore(nodeToInsert: prevSibling) + if prevSibling == nil || isTokenOrSegmented(prevSibling) { + prevSibling = TextNode() + if let prevSibling { + try prevSibling.setFormat(format: format) + if !firstNodeParent.canInsertTextBefore() { + try firstNodeParent.insertBefore(nodeToInsert: prevSibling) + } else { + try firstNode.insertBefore(nodeToInsert: prevSibling) + } } } - guard let prevSibling else { - return + if let prevSibling { + try prevSibling.select(anchorOffset: nil, focusOffset: nil) + firstNode = prevSibling } - _ = try prevSibling.select(anchorOffset: nil, focusOffset: nil) - firstNode = prevSibling - if text != "" { + if text.lengthAsNSString() > 0 { try insertText(text) return } } else if firstNode.isSegmented() && startOffset != firstNodeTextLength { - let textNode = createTextNode(text: firstNode.getTextContent(includeInert: false, includeDirectionless: true)) - _ = try firstNode.replace(replaceWith: textNode) + let textNode = TextNode(text: firstNode.getTextPart()) + try textNode.setFormat(format: format) + try firstNode.replace(replaceWith: textNode) firstNode = textNode + } else if !isCollapsed() && text.lengthAsNSString() > 0 { + // When the firstNode or lastNode parents are elements that + // do not allow text to be inserted before or after, we first + // clear the content. Then we normalize selection, then insert + // the new content. + let lastNodeParent = lastNode?.getParent() + + if !firstNodeParent.canInsertTextBefore() || + !firstNodeParent.canInsertTextAfter() || + (lastNodeParent != nil && + (!(lastNodeParent?.canInsertTextBefore() ?? true) || + !(lastNodeParent?.canInsertTextAfter() ?? true))) { + try insertText("") + try normalizeSelectionPointsForBoundaries(anchor: self.anchor, focus: self.focus, lastSelection: nil) + try insertText(text) + return + } } if selectedNodesLength == 1 { - if isTokenOrInert(firstNode) { - try firstNode.remove() + if firstNode.isToken() { + let textNode = TextNode(text: text) + try textNode.select(anchorOffset: nil, focusOffset: nil) + try firstNode.replace(replaceWith: textNode) return } let firstNodeFormat = firstNode.getFormat() + let firstNodeStyle = firstNode.getStyle() - if startOffset == endOffset && firstNodeFormat != format { - if firstNode.getTextPart().isEmpty { - firstNode = try firstNode.setFormat(format: format) + if startOffset == endOffset && (firstNodeFormat != format || firstNodeStyle != style) { + if firstNode.getTextPart().lengthAsNSString() == 0 { + try firstNode.setFormat(format: format) + try firstNode.setStyle(style) } else { - var textNode = createTextNode(text: text) - textNode = try textNode.setFormat(format: format) - _ = try textNode.select(anchorOffset: nil, focusOffset: nil) + let textNode = TextNode(text: text) + try textNode.setFormat(format: format) + try textNode.setStyle(style) + try textNode.select(anchorOffset: nil, focusOffset: nil) if startOffset == 0 { - _ = try firstNode.insertBefore(nodeToInsert: textNode) + try firstNode.insertBefore(nodeToInsert: textNode) } else { - let targetNodeArray = try firstNode.splitText(splitOffsets: [startOffset]) - guard let targetNode = targetNodeArray.first else { - throw LexicalError.invariantViolation("insertText: splitText returned no node") + if let targetNode = try firstNode.splitText(splitOffsets: [startOffset]).first { + try targetNode.insertAfter(nodeToInsert: textNode) } - try targetNode.insertAfter(nodeToInsert: textNode) + } + // When composing, we need to adjust the anchor offset so that + // we correctly replace that right range. + if textNode.isComposing() && self.anchor.type == .text { + self.anchor.offset -= text.lengthAsNSString() } return } @@ -346,132 +385,144 @@ public class RangeSelection: BaseSelection { let delCount = endOffset - startOffset firstNode = try firstNode.spliceText(offset: startOffset, delCount: delCount, newText: text, moveSelection: true) - - if firstNode.getTextPart().isEmpty { + if firstNode.getTextPart().lengthAsNSString() == 0 { try firstNode.remove() - } else if firstNode.isComposing() && anchor.type == .text { - anchor.offset -= text.lengthAsNSString() + } else if self.anchor.type == .text { + if firstNode.isComposing() { + // When composing, we need to adjust the anchor offset so that + // we correctly replace that right range. + self.anchor.offset -= text.lengthAsNSString() + } else { + self.format = firstNodeFormat + self.style = firstNodeStyle + } } } else { - let lastIndex = selectedNodesLength - 1 - var lastNode = selectedNodes[lastIndex] - var markedNodeKeysForKeep: Set = Set(firstNode.getParentKeys()) - lastNode.getParentKeys().forEach({ markedNodeKeysForKeep.insert($0) }) - - // First node is a TextNode, so we're getting a new "firstNode" from the start of selectedNodes - let firstElement: ElementNode - if let elementNode = selectedNodes[0] as? ElementNode { - firstElement = elementNode - } else { - firstElement = try firstNode.getParentOrThrow() + var markedNodeKeysForKeep = Set(firstNode.getParentKeys()).union(lastNode?.getParentKeys() ?? []) + + // We have to get the parent elements before the next section, + // as in that section we might mutate the lastNode. + let firstElement = try firstNode.getParentOrThrow() + var lastElement: ElementNode? = lastNode is ElementNode ? lastNode as? ElementNode : try lastNode?.getParentOrThrow() + var lastElementChild = lastNode + + // If the last element is inline, we should instead look at getting + // the nodes of its parent, rather than itself. This behavior will + // then better match how text node insertions work. We will need to + // also update the last element's child accordingly as we do this. + if firstElement != lastElement && (lastElement?.isInline() ?? false) { + // Keep traversing till we have a non-inline element parent. + repeat { + lastElementChild = lastElement + lastElement = try lastElement?.getParentOrThrow() + } while lastElement?.isInline() ?? false } - let lastElement: ElementNode = try (lastNode as? ElementNode ?? lastNode.getParentOrThrow()) - // Handle mutations to the last node. - if let lastIndex = lastNode.getIndexWithinParent() { - if endPoint.type == .text && (endOffset != 0 || lastNode.getTextPart().isEmpty) || - (endPoint.type == .element && lastIndex < endOffset) { - if let lastTextNode = lastNode as? TextNode, - !isTokenOrInert(lastTextNode) && endOffset != lastTextNode.getTextContentSize() { - if lastTextNode.isSegmented() { - let textNode = createTextNode(text: lastNode.getTextContent()) - _ = try lastNode.replace(replaceWith: textNode) - lastNode = textNode - } - lastNode = try lastTextNode.spliceText(offset: 0, delCount: endOffset, newText: "") - markedNodeKeysForKeep.insert(lastNode.getKey()) - } else { - if format != firstNode.format, let lastTextNode = lastNode as? TextNode { - lastNode = try lastTextNode.spliceText(offset: 0, delCount: endOffset, newText: text, moveSelection: true) - markedNodeKeysForKeep.insert(lastNode.getKey()) - } else { - try lastNode.remove() + if (endPoint.type == .text && (endOffset != 0 || (lastNode?.getTextContent().lengthAsNSString() == 0))) || + (endPoint.type == .element && lastNode?.getIndexWithinParent() ?? 0 < endOffset) { + if let lastNodeAsTextNode = lastNode as? TextNode, + !lastNodeAsTextNode.isToken(), + endOffset != lastNodeAsTextNode.getTextContentSize() { + if lastNodeAsTextNode.isSegmented() { + let textNode = TextNode(text: lastNodeAsTextNode.getTextPart()) + try lastNodeAsTextNode.replace(replaceWith: textNode) + lastNode = textNode + } + if let lastNodeAsTextNode = lastNode as? TextNode { + lastNode = try lastNodeAsTextNode.spliceText(offset: 0, delCount: endOffset, newText: "") + if let lastNode { + markedNodeKeysForKeep.insert(lastNode.key) } } } else { - markedNodeKeysForKeep.insert(lastNode.getKey()) + let lastNodeParent = try lastNode?.getParentOrThrow() + if let lastNodeParent, + !lastNodeParent.canBeEmpty(), + lastNodeParent.getChildrenSize() == 1 { + try lastNodeParent.remove() + } else { + try lastNode?.remove() + } + } + } else { + if let lastNode { + markedNodeKeysForKeep.insert(lastNode.key) } } // Either move the remaining nodes of the last parent to after // the first child, or remove them entirely. If the last parent // is the same as the first parent, this logic also works. - let lastNodeChildren = lastElement.getChildren().reversed() + let lastNodeChildren = lastElement?.getChildren() ?? [] let selectedNodesSet = Set(selectedNodes) - let firstAndLastElementsAreEqual = firstElement === lastElement - - // If the last element is an "inline" element, don't move it's text nodes to the first node. - // Instead, preserve the "inline" element's children and append to the first element. - if !lastElement.canBeEmpty() { - try firstElement.append([lastElement]) - } else { - for node in lastNodeChildren { - if node === firstNode { - break - } + let firstAndLastElementsAreEqual = firstElement == lastElement + + // We choose a target to insert all nodes after. In the case of having + // and inline starting parent element with a starting node that has no + // siblings, we should insert after the starting parent element, otherwise + // we will incorrectly merge into the starting parent element. + // TODO: should we keep on traversing parents if we're inside another + // nested inline element? + let insertionTarget = firstElement.isInline() && firstNode.getNextSibling() == nil ? firstElement : firstNode + + for (_, lastNodeChild) in lastNodeChildren.enumerated().reversed() { + if lastNodeChild.isSameNode(firstNode) || ((lastNodeChild as? ElementNode)?.isParentOf(firstNode) ?? false) { + break + } - if node.isAttached() { - if !selectedNodesSet.contains(node) || node === lastNode { - if !firstAndLastElementsAreEqual { - _ = try firstNode.insertAfter(nodeToInsert: node) - } - } else { - try node.remove() + if lastNodeChild.isAttached() { + if !selectedNodesSet.contains(lastNodeChild) || lastNodeChild == lastElementChild { + if !firstAndLastElementsAreEqual { + try insertionTarget.insertAfter(nodeToInsert: lastNodeChild) } + } else { + try lastNodeChild.remove() } } + } - if !firstAndLastElementsAreEqual { - // Check if we have already moved out all the nodes of the - // last parent, and if so, traverse the parent tree and mark - // them all as being able to deleted too. - var parent: Node? = lastElement - var lastRemovedParent: Node? - - while let unwrappedParent = parent as? ElementNode { - let children = unwrappedParent.getChildren() - let childrenLength = children.count - - if childrenLength == 0 || children[childrenLength - 1].isSameKey(lastRemovedParent) { - markedNodeKeysForKeep.remove(unwrappedParent.getKey()) - lastRemovedParent = unwrappedParent - } - - parent = unwrappedParent.getParent() + if !firstAndLastElementsAreEqual { + // Check if we have already moved out all the nodes of the + // last parent, and if so, traverse the parent tree and mark + // them all as being able to deleted too. + var parent: ElementNode? = lastElement + var lastRemovedParent: ElementNode? = nil + + while let thisParent = parent { + let children = thisParent.getChildren() + let childrenLength = children.count + if childrenLength == 0 || children.last == lastRemovedParent { + markedNodeKeysForKeep.remove(thisParent.key) + lastRemovedParent = thisParent } + parent = thisParent.getParent() } } - if firstNode.format == format { - // Ensure we do splicing after moving of nodes, as splicing - // can have side-effects (in the case of hashtags). - if !isTokenOrInert(firstNode) { - firstNode = try firstNode.spliceText( - offset: startOffset, - delCount: firstNodeTextLength - startOffset, - newText: text, - moveSelection: true - ) - if firstNode.getTextPart().isEmpty { - try firstNode.remove() - } else if firstNode.isComposing() && anchor.type == .text { - anchor.offset -= text.lengthAsNSString() - } - } else if startOffset == firstNodeTextLength { - _ = try firstNode.select(anchorOffset: nil, focusOffset: nil) - } else { + // Ensure we do splicing after moving of nodes, as splicing + // can have side-effects (in the case of hashtags). + if !firstNode.isToken() { + firstNode = try firstNode.spliceText(offset: startOffset, delCount: firstNodeTextLength - startOffset, newText: text, moveSelection: true) + if firstNode.getTextContent().lengthAsNSString() == 0 { try firstNode.remove() + } else if firstNode.isComposing() && self.anchor.type == .text { + // When composing, we need to adjust the anchor offset so that + // we correctly replace that right range. + self.anchor.offset -= text.lengthAsNSString() } + } else if startOffset == firstNodeTextLength { + try firstNode.select(anchorOffset: nil, focusOffset: nil) + } else { + let textNode = TextNode(text: text) + try textNode.select(anchorOffset: nil, focusOffset: nil) + try firstNode.replace(replaceWith: textNode) } - // Remove all selected nodes that haven't already been removed. - for node in selectedNodes[1.. Bool { return !node.isInline() && node.canBeEmpty() != false && isLeafElement } + +private func resolveSelectionPointOnBoundary( + point: Point, + isBackward: Bool, + isCollapsed: Bool +) throws { + let offset = point.offset + let node = try point.getNode() + + if offset == 0 { + let prevSibling = node.getPreviousSibling() + let parent = node.getParent() + + if (!isBackward) { + if + let prevSibling = prevSibling as? ElementNode, + !isCollapsed, + prevSibling.isInline() + { + point.key = prevSibling.key + point.offset = prevSibling.getChildrenSize() + point.type = .element + } else if let prevSibling = prevSibling as? TextNode { + point.key = prevSibling.key + point.offset = prevSibling.getTextContent().lengthAsNSString() + } + } else if + (isCollapsed || !isBackward), + prevSibling == nil, + let parent, + parent.isInline() { + let parentSibling = parent.getPreviousSibling() + if let parentSibling = parentSibling as? TextNode { + point.key = parentSibling.key + point.offset = parentSibling.getTextContent().lengthAsNSString() + } + } + } else if offset == node.getTextContent().lengthAsNSString() { + let nextSibling = node.getNextSibling() + let parent = node.getParent() + + if isBackward, let nextSibling = nextSibling as? ElementNode, nextSibling.isInline() { + point.key = nextSibling.key + point.offset = 0 + point.type = .element + } else if + (isCollapsed || isBackward), + nextSibling == nil, + let parent, + parent.isInline(), + !parent.canInsertTextAfter() + { + let parentSibling = parent.getNextSibling(); + if let parentSibling = parentSibling as? TextNode { + point.key = parentSibling.key + point.offset = 0 + } + } + } +} + + +internal func normalizeSelectionPointsForBoundaries( + anchor: Point, + focus: Point, + lastSelection: BaseSelection? +) throws { + if anchor.type == .text && focus.type == .text { + let isBackward = try anchor.isBefore(point: focus) + let isCollapsed = anchor == focus + + // Attempt to normalize the offset to the previous sibling if we're at the + // start of a text node and the sibling is a text node or inline element. + try resolveSelectionPointOnBoundary(point: anchor, isBackward: isBackward, isCollapsed: isCollapsed) + try resolveSelectionPointOnBoundary(point: focus, isBackward: !isBackward, isCollapsed: isCollapsed) + + if isCollapsed { + focus.key = anchor.key + focus.offset = anchor.offset + focus.type = anchor.type + } + guard let editor = getActiveEditor() else { + throw LexicalError.invariantViolation("no editor") + } + + if + editor.isComposing(), + editor.compositionKey != anchor.key, + let lastSelection = lastSelection as? RangeSelection + { + let lastAnchor = lastSelection.anchor + let lastFocus = lastSelection.focus + anchor.key = lastAnchor.key + anchor.type = lastAnchor.type + anchor.offset = lastAnchor.offset + focus.key = lastFocus.key + focus.type = lastFocus.type + focus.offset = lastFocus.offset + } + } +} diff --git a/Lexical/Core/Utils.swift b/Lexical/Core/Utils.swift index 60888d04..d5803731 100644 --- a/Lexical/Core/Utils.swift +++ b/Lexical/Core/Utils.swift @@ -223,6 +223,11 @@ public func isTokenOrInertOrSegmented(_ node: TextNode?) -> Bool { return isTokenOrInert(node) || node.isSegmented() } +public func isTokenOrSegmented(_ node: TextNode?) -> Bool { + guard let node else { return false } + return node.isToken() || node.isSegmented() +} + public func getRoot() -> RootNode? { guard let editorState = getActiveEditorState(), let rootNode = editorState.nodeMap[kRootNodeKey] as? RootNode @@ -536,3 +541,63 @@ public func removeFromParent(node: Node) throws { internallyMarkNodeAsDirty(node: writableParent) } + +private func resolveElement( + element: ElementNode, + isBackward: Bool, + focusOffset: Int +) -> Node? { + let parent = element.getParent() + var offset = focusOffset + var block = element + if let parent { + if isBackward, focusOffset == 0, let indexWithinParent = block.getIndexWithinParent() { + offset = indexWithinParent + block = parent + } else if !isBackward, focusOffset == block.getChildrenSize(), let indexWithinParent = block.getIndexWithinParent() { + offset = indexWithinParent + 1 + block = parent + } + } + return block.getChildAtIndex(index: isBackward ? offset - 1 : offset) +} + + +public func getAdjacentNode( + focus: Point, + isBackward: Bool +) throws -> Node? { + let focusOffset = focus.offset + if focus.type == .element, let focusElement = try focus.getNode() as? ElementNode { + return resolveElement(element: focusElement, isBackward: isBackward, focusOffset: focusOffset); + } else { + let focusNode = try focus.getNode() + if + (isBackward && focusOffset == 0) || + (!isBackward && focusOffset == focusNode.getTextContentSize()) + { + let possibleNode = isBackward + ? focusNode.getPreviousSibling() + : focusNode.getNextSibling() + + guard let possibleNode else { + return resolveElement( + element: try focusNode.getParentOrThrow(), + isBackward: isBackward, + focusOffset: (focusNode.getIndexWithinParent() ?? 0) + (isBackward ? 0 : 1) + ) + } + return possibleNode + } + } + return nil +} + +public func setSelection(_ selection: BaseSelection?) throws { + try errorOnReadOnly() + let editorState = getActiveEditorState() + if let selection { + selection.dirty = true + } + editorState?.selection = selection +} diff --git a/LexicalTests/Tests/SelectionUtilsTests.swift b/LexicalTests/Tests/SelectionUtilsTests.swift index 88d6047e..a4166003 100644 --- a/LexicalTests/Tests/SelectionUtilsTests.swift +++ b/LexicalTests/Tests/SelectionUtilsTests.swift @@ -561,7 +561,8 @@ class SelectionUtilsTests: XCTestCase { try transferStartingElementPointToTextPoint( start: startPoint, end: endPoint, - format: TextFormat() + format: TextFormat(), + style: "" ) ) }