mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-04-21 22:02:05 +02:00
fix normalization of selection from node and offset, closes #440
This commit is contained in:
parent
e37d1b20f3
commit
2a355afa1e
81
src/utils/normalize-node-and-offset.js
Normal file
81
src/utils/normalize-node-and-offset.js
Normal file
@ -0,0 +1,81 @@
|
||||
|
||||
/**
|
||||
* From a DOM selection's `node` and `offset`, normalize so that it always
|
||||
* refers to a text node.
|
||||
*
|
||||
* @param {Element} node
|
||||
* @param {Number} offset
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function normalizeNodeAndOffset(node, offset) {
|
||||
// If it's an element node, its offset refers to the index of its children
|
||||
// including comment nodes, so convert it to a text equivalent.
|
||||
if (node.nodeType == 1) {
|
||||
const isLast = offset == node.childNodes.length
|
||||
const direction = isLast ? 'backward' : 'forward'
|
||||
const index = isLast ? offset - 1 : offset
|
||||
node = getNonComment(node, index, direction)
|
||||
|
||||
// If the node is not a text node, traverse until we have one.
|
||||
while (node.nodeType != 3) {
|
||||
const i = isLast ? node.childNodes.length - 1 : 0
|
||||
node = getNonComment(node, i, direction)
|
||||
}
|
||||
|
||||
// Determine the new offset inside the text node.
|
||||
offset = isLast ? node.textContent.length : 0
|
||||
}
|
||||
|
||||
// Return the node and offset.
|
||||
return { node, offset }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the nearest non-comment to `index` in a `parent`, preferring `direction`.
|
||||
*
|
||||
* @param {Element} parent
|
||||
* @param {Number} index
|
||||
* @param {String} direction ('forward' or 'backward')
|
||||
* @return {Element|Null}
|
||||
*/
|
||||
|
||||
function getNonComment(parent, index, direction) {
|
||||
const { childNodes } = parent
|
||||
let child = childNodes[index]
|
||||
let i = index
|
||||
let triedForward = false
|
||||
let triedBackward = false
|
||||
|
||||
while (child.nodeType == 8) {
|
||||
if (triedForward && triedBackward) break
|
||||
|
||||
if (i >= childNodes.length) {
|
||||
triedForward = true
|
||||
i = index - 1
|
||||
direction = 'backward'
|
||||
continue
|
||||
}
|
||||
|
||||
if (i < 0) {
|
||||
triedBackward = true
|
||||
i = index + 1
|
||||
direction = 'forward'
|
||||
continue
|
||||
}
|
||||
|
||||
child = childNodes[i]
|
||||
if (direction == 'forward') i++
|
||||
if (direction == 'backward') i--
|
||||
}
|
||||
|
||||
return child || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default normalizeNodeAndOffset
|
@ -1,4 +1,6 @@
|
||||
|
||||
import normalizeNodeAndOffset from './normalize-node-and-offset'
|
||||
|
||||
/**
|
||||
* Offset key parser regex.
|
||||
*
|
||||
@ -46,44 +48,30 @@ function findBounds(index, ranges) {
|
||||
}
|
||||
|
||||
/**
|
||||
* From a `element`, find the closest parent's offset key.
|
||||
* From a DOM node, find the closest parent's offset key.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {Number} offset
|
||||
* @param {Element} rawNode
|
||||
* @param {Number} rawOffset
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function findKey(element, offset) {
|
||||
if (element.nodeType == 3) element = element.parentNode
|
||||
function findKey(rawNode, rawOffset) {
|
||||
let { node, offset } = normalizeNodeAndOffset(rawNode, rawOffset)
|
||||
|
||||
const parent = element.closest(SELECTOR)
|
||||
const children = element.querySelectorAll(SELECTOR)
|
||||
// Find the closest parent with an offset key attribute.
|
||||
const closest = node.parentNode.closest(SELECTOR)
|
||||
let offsetKey
|
||||
|
||||
// Get the key from a parent if one exists.
|
||||
if (parent) {
|
||||
offsetKey = parent.getAttribute(ATTRIBUTE)
|
||||
// Get the key from the closest matching node if one exists.
|
||||
if (closest) {
|
||||
offsetKey = closest.getAttribute(ATTRIBUTE)
|
||||
}
|
||||
|
||||
// COMPAT: In Firefox, and potentially other browsers, when performing a
|
||||
// "select all" action, a parent element is selected instead of the text. In
|
||||
// this case, we need to select the proper inner text nodes. (2016/07/26)
|
||||
else if (children.length) {
|
||||
let child = children[0]
|
||||
|
||||
if (offset != 0) {
|
||||
child = children[children.length - 1]
|
||||
offset = child.textContent.length
|
||||
}
|
||||
|
||||
offsetKey = child.getAttribute(ATTRIBUTE)
|
||||
}
|
||||
|
||||
// Otherwise, for void node scenarios, a cousin element will be selected, and
|
||||
// Otherwise, for void node scenarios, a cousin node will be selected, and
|
||||
// we need to select the first text node cousin we can find.
|
||||
else {
|
||||
while (element = element.parentNode) {
|
||||
const cousin = element.querySelector(SELECTOR)
|
||||
while (node = node.parentNode) {
|
||||
const cousin = node.querySelector(SELECTOR)
|
||||
if (!cousin) continue
|
||||
offsetKey = cousin.getAttribute(ATTRIBUTE)
|
||||
offset = cousin.textContent.length
|
||||
@ -93,12 +81,11 @@ function findKey(element, offset) {
|
||||
|
||||
// If we still didn't find an offset key, error. This is a bug.
|
||||
if (!offsetKey) {
|
||||
throw new Error(`Unable to find offset key for ${element} with offset "${offset}".`)
|
||||
throw new Error(`Unable to find offset key for ${node} with offset "${offset}".`)
|
||||
}
|
||||
|
||||
// Parse the offset key.
|
||||
// Return the parsed the offset key.
|
||||
const parsed = parse(offsetKey)
|
||||
|
||||
return {
|
||||
key: parsed.key,
|
||||
index: parsed.index,
|
||||
|
Loading…
x
Reference in New Issue
Block a user