selection-ranges
Advanced tools
Comparing version 2.0.1 to 2.1.0
@@ -9,2 +9,6 @@ # Changelog | ||
## 2.1.0 | ||
* `CHORE`: add `browser` field | ||
## 2.0.0 | ||
@@ -11,0 +15,0 @@ |
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
typeof define === 'function' && define.amd ? define(['exports'], factory) : | ||
(factory((global.SelectionRanges = {}))); | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
typeof define === 'function' && define.amd ? define(['exports'], factory) : | ||
(factory((global.SelectionRanges = {}))); | ||
}(this, (function (exports) { 'use strict'; | ||
/** | ||
* Expose `xor` | ||
*/ | ||
/** | ||
* Expose `xor` | ||
*/ | ||
var componentXor = xor; | ||
var componentXor = xor; | ||
/** | ||
* XOR utility | ||
* | ||
* T T F | ||
* T F T | ||
* F T T | ||
* F F F | ||
* | ||
* @param {Boolean} a | ||
* @param {Boolean} b | ||
* @return {Boolean} | ||
*/ | ||
/** | ||
* XOR utility | ||
* | ||
* T T F | ||
* T F T | ||
* F T T | ||
* F F F | ||
* | ||
* @param {Boolean} a | ||
* @param {Boolean} b | ||
* @return {Boolean} | ||
*/ | ||
function xor(a, b) { | ||
return a ^ b; | ||
} | ||
function xor(a, b) { | ||
return a ^ b; | ||
} | ||
/** | ||
* Global Names | ||
*/ | ||
/** | ||
* Global Names | ||
*/ | ||
var globals = /\b(Array|Date|Object|Math|JSON)\b/g; | ||
var globals = /\b(Array|Date|Object|Math|JSON)\b/g; | ||
/** | ||
* Return immediate identifiers parsed from `str`. | ||
* | ||
* @param {String} str | ||
* @param {String|Function} map function or prefix | ||
* @return {Array} | ||
* @api public | ||
*/ | ||
/** | ||
* Return immediate identifiers parsed from `str`. | ||
* | ||
* @param {String} str | ||
* @param {String|Function} map function or prefix | ||
* @return {Array} | ||
* @api public | ||
*/ | ||
var componentProps = function(str, fn){ | ||
var p = unique(props(str)); | ||
if (fn && 'string' == typeof fn) fn = prefixed(fn); | ||
if (fn) return map(str, p, fn); | ||
return p; | ||
}; | ||
var componentProps = function(str, fn){ | ||
var p = unique(props(str)); | ||
if (fn && 'string' == typeof fn) fn = prefixed(fn); | ||
if (fn) return map(str, p, fn); | ||
return p; | ||
}; | ||
/** | ||
* Return immediate identifiers in `str`. | ||
* | ||
* @param {String} str | ||
* @return {Array} | ||
* @api private | ||
*/ | ||
/** | ||
* Return immediate identifiers in `str`. | ||
* | ||
* @param {String} str | ||
* @return {Array} | ||
* @api private | ||
*/ | ||
function props(str) { | ||
return str | ||
.replace(/\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\//g, '') | ||
.replace(globals, '') | ||
.match(/[a-zA-Z_]\w*/g) | ||
|| []; | ||
} | ||
function props(str) { | ||
return str | ||
.replace(/\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\//g, '') | ||
.replace(globals, '') | ||
.match(/[a-zA-Z_]\w*/g) | ||
|| []; | ||
} | ||
/** | ||
* Return `str` with `props` mapped with `fn`. | ||
* | ||
* @param {String} str | ||
* @param {Array} props | ||
* @param {Function} fn | ||
* @return {String} | ||
* @api private | ||
*/ | ||
/** | ||
* Return `str` with `props` mapped with `fn`. | ||
* | ||
* @param {String} str | ||
* @param {Array} props | ||
* @param {Function} fn | ||
* @return {String} | ||
* @api private | ||
*/ | ||
function map(str, props, fn) { | ||
var re = /\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\/|[a-zA-Z_]\w*/g; | ||
return str.replace(re, function(_){ | ||
if ('(' == _[_.length - 1]) return fn(_); | ||
if (!~props.indexOf(_)) return _; | ||
return fn(_); | ||
}); | ||
} | ||
function map(str, props, fn) { | ||
var re = /\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\/|[a-zA-Z_]\w*/g; | ||
return str.replace(re, function(_){ | ||
if ('(' == _[_.length - 1]) return fn(_); | ||
if (!~props.indexOf(_)) return _; | ||
return fn(_); | ||
}); | ||
} | ||
/** | ||
* Return unique array. | ||
* | ||
* @param {Array} arr | ||
* @return {Array} | ||
* @api private | ||
*/ | ||
/** | ||
* Return unique array. | ||
* | ||
* @param {Array} arr | ||
* @return {Array} | ||
* @api private | ||
*/ | ||
function unique(arr) { | ||
var ret = []; | ||
function unique(arr) { | ||
var ret = []; | ||
for (var i = 0; i < arr.length; i++) { | ||
if (~ret.indexOf(arr[i])) continue; | ||
ret.push(arr[i]); | ||
for (var i = 0; i < arr.length; i++) { | ||
if (~ret.indexOf(arr[i])) continue; | ||
ret.push(arr[i]); | ||
} | ||
return ret; | ||
} | ||
return ret; | ||
} | ||
/** | ||
* Map with prefix `str`. | ||
*/ | ||
/** | ||
* Map with prefix `str`. | ||
*/ | ||
function prefixed(str) { | ||
return function(_){ | ||
return str + _; | ||
}; | ||
} | ||
function prefixed(str) { | ||
return function(_){ | ||
return str + _; | ||
}; | ||
} | ||
/** | ||
* Module Dependencies | ||
*/ | ||
/** | ||
* Module Dependencies | ||
*/ | ||
@@ -123,463 +124,421 @@ | ||
/** | ||
* Export `Iterator` | ||
*/ | ||
/** | ||
* Export `Iterator` | ||
*/ | ||
var domIterator = Iterator; | ||
var domIterator = Iterator; | ||
/** | ||
* Initialize `Iterator` | ||
* | ||
* @param {Node} node | ||
* @param {Node} root | ||
* @return {Iterator} self | ||
* @api public | ||
*/ | ||
/** | ||
* Initialize `Iterator` | ||
* | ||
* @param {Node} node | ||
* @param {Node} root | ||
* @return {Iterator} self | ||
* @api public | ||
*/ | ||
function Iterator(node, root) { | ||
if (!(this instanceof Iterator)) return new Iterator(node, root); | ||
this.node = this.start = this.peeked = node; | ||
this.root = root; | ||
this.closingTag = false; | ||
this._revisit = true; | ||
this._selects = []; | ||
this._rejects = []; | ||
function Iterator(node, root) { | ||
if (!(this instanceof Iterator)) return new Iterator(node, root); | ||
this.node = this.start = this.peeked = node; | ||
this.root = root; | ||
this.closingTag = false; | ||
this._revisit = true; | ||
this._selects = []; | ||
this._rejects = []; | ||
if (node && this.higher(node)) { | ||
throw new Error('root must be a parent or ancestor to node'); | ||
if (node && this.higher(node)) { | ||
throw new Error('root must be a parent or ancestor to node'); | ||
} | ||
} | ||
} | ||
/** | ||
* Reset the Iterator | ||
* | ||
* @param {Node} node (optional) | ||
* @return {Iterator} self | ||
* @api public | ||
*/ | ||
/** | ||
* Reset the Iterator | ||
* | ||
* @param {Node} node (optional) | ||
* @return {Iterator} self | ||
* @api public | ||
*/ | ||
Iterator.prototype.reset = function(node) { | ||
this.node = node || this.start; | ||
return this; | ||
}; | ||
Iterator.prototype.reset = function(node) { | ||
this.node = node || this.start; | ||
return this; | ||
}; | ||
/** | ||
* Revisit element nodes. Defaults to `true` | ||
*/ | ||
/** | ||
* Revisit element nodes. Defaults to `true` | ||
*/ | ||
Iterator.prototype.revisit = function(revisit) { | ||
this._revisit = undefined == revisit ? true : revisit; | ||
return this; | ||
}; | ||
Iterator.prototype.revisit = function(revisit) { | ||
this._revisit = undefined == revisit ? true : revisit; | ||
return this; | ||
}; | ||
/** | ||
* Jump to the opening tag | ||
*/ | ||
/** | ||
* Jump to the opening tag | ||
*/ | ||
Iterator.prototype.opening = function() { | ||
if (1 == this.node.nodeType) this.closingTag = false; | ||
return this; | ||
}; | ||
Iterator.prototype.opening = function() { | ||
if (1 == this.node.nodeType) this.closingTag = false; | ||
return this; | ||
}; | ||
/** | ||
* Jump to the closing tag | ||
*/ | ||
/** | ||
* Jump to the closing tag | ||
*/ | ||
Iterator.prototype.atOpening = function() { | ||
return !this.closingTag; | ||
}; | ||
Iterator.prototype.atOpening = function() { | ||
return !this.closingTag; | ||
}; | ||
/** | ||
* Jump to the closing tag | ||
*/ | ||
/** | ||
* Jump to the closing tag | ||
*/ | ||
Iterator.prototype.closing = function() { | ||
if (1 == this.node.nodeType) this.closingTag = true; | ||
return this; | ||
}; | ||
Iterator.prototype.closing = function() { | ||
if (1 == this.node.nodeType) this.closingTag = true; | ||
return this; | ||
}; | ||
/** | ||
* Jump to the closing tag | ||
*/ | ||
/** | ||
* Jump to the closing tag | ||
*/ | ||
Iterator.prototype.atClosing = function() { | ||
return this.closingTag; | ||
}; | ||
Iterator.prototype.atClosing = function() { | ||
return this.closingTag; | ||
}; | ||
/** | ||
* Next node | ||
* | ||
* @param {Number} type | ||
* @return {Node|null} | ||
* @api public | ||
*/ | ||
/** | ||
* Next node | ||
* | ||
* @param {Number} type | ||
* @return {Node|null} | ||
* @api public | ||
*/ | ||
Iterator.prototype.next = traverse('nextSibling', 'firstChild'); | ||
Iterator.prototype.next = traverse('nextSibling', 'firstChild'); | ||
/** | ||
* Previous node | ||
* | ||
* @param {Number} type | ||
* @return {Node|null} | ||
* @api public | ||
*/ | ||
/** | ||
* Previous node | ||
* | ||
* @param {Number} type | ||
* @return {Node|null} | ||
* @api public | ||
*/ | ||
Iterator.prototype.previous = | ||
Iterator.prototype.prev = traverse('previousSibling', 'lastChild'); | ||
Iterator.prototype.previous = | ||
Iterator.prototype.prev = traverse('previousSibling', 'lastChild'); | ||
/** | ||
* Make traverse function | ||
* | ||
* @param {String} dir | ||
* @param {String} child | ||
* @return {Function} | ||
* @api private | ||
*/ | ||
/** | ||
* Make traverse function | ||
* | ||
* @param {String} dir | ||
* @param {String} child | ||
* @return {Function} | ||
* @api private | ||
*/ | ||
function traverse(dir, child) { | ||
var next = dir == 'nextSibling'; | ||
return function walk(expr, n, peek) { | ||
expr = this.compile(expr); | ||
n = n && n > 0 ? n : 1; | ||
var node = this.node; | ||
var closing = this.closingTag; | ||
var revisit = this._revisit; | ||
function traverse(dir, child) { | ||
var next = dir == 'nextSibling'; | ||
return function walk(expr, n, peek) { | ||
expr = this.compile(expr); | ||
n = n && n > 0 ? n : 1; | ||
var node = this.node; | ||
var closing = this.closingTag; | ||
var revisit = this._revisit; | ||
while (node) { | ||
if (componentXor(next, closing) && node[child]) { | ||
// element with children: <em>...</em> | ||
node = node[child]; | ||
closing = !next; | ||
} else if (1 == node.nodeType && !node[child] && componentXor(next, closing)) { | ||
// empty element tag: <em></em> | ||
closing = next; | ||
if (!revisit) continue; | ||
} else if (node[dir]) { | ||
// element has a neighbor: ...<em></em>... | ||
node = node[dir]; | ||
closing = !next; | ||
} else { | ||
// done with current layer, move up. | ||
node = node.parentNode; | ||
closing = next; | ||
if (!revisit) continue; | ||
} | ||
while (node) { | ||
if (componentXor(next, closing) && node[child]) { | ||
// element with children: <em>...</em> | ||
node = node[child]; | ||
closing = !next; | ||
} else if (1 == node.nodeType && !node[child] && componentXor(next, closing)) { | ||
// empty element tag: <em></em> | ||
closing = next; | ||
if (!revisit) continue; | ||
} else if (node[dir]) { | ||
// element has a neighbor: ...<em></em>... | ||
node = node[dir]; | ||
closing = !next; | ||
} else { | ||
// done with current layer, move up. | ||
node = node.parentNode; | ||
closing = next; | ||
if (!revisit) continue; | ||
} | ||
if (!node || this.higher(node, this.root)) break; | ||
if (!node || this.higher(node, this.root)) break; | ||
if (expr(node) && this.selects(node, peek) && this.rejects(node, peek)) { | ||
if (--n) continue; | ||
if (!peek) this.node = node; | ||
this.closingTag = closing; | ||
return node; | ||
if (expr(node) && this.selects(node, peek) && this.rejects(node, peek)) { | ||
if (--n) continue; | ||
if (!peek) this.node = node; | ||
this.closingTag = closing; | ||
return node; | ||
} | ||
} | ||
} | ||
return null; | ||
}; | ||
} | ||
/** | ||
* Select nodes that cause `expr(node)` | ||
* to be truthy | ||
* | ||
* @param {Number|String|Function} expr | ||
* @return {Iterator} self | ||
* @api public | ||
*/ | ||
Iterator.prototype.select = function(expr) { | ||
expr = this.compile(expr); | ||
this._selects.push(expr); | ||
return this; | ||
}; | ||
/** | ||
* Run through the selects ORing each | ||
* | ||
* @param {Node} node | ||
* @param {Boolean} peek | ||
* @return {Boolean} | ||
* @api private | ||
*/ | ||
Iterator.prototype.selects = function(node, peek) { | ||
var exprs = this._selects; | ||
var len = exprs.length; | ||
if (!len) return true; | ||
for (var i = 0; i < len; i++) { | ||
if (exprs[i].call(this, node, peek)) return true; | ||
return null; | ||
}; | ||
} | ||
return false; | ||
}; | ||
/** | ||
* Select nodes that cause `expr(node)` | ||
* to be falsy | ||
* | ||
* @param {Number|String|Function} expr | ||
* @return {Iterator} self | ||
* @api public | ||
*/ | ||
/** | ||
* Select nodes that cause `expr(node)` | ||
* to be truthy | ||
* | ||
* @param {Number|String|Function} expr | ||
* @return {Iterator} self | ||
* @api public | ||
*/ | ||
Iterator.prototype.reject = function(expr) { | ||
expr = this.compile(expr); | ||
this._rejects.push(expr); | ||
return this; | ||
}; | ||
Iterator.prototype.select = function(expr) { | ||
expr = this.compile(expr); | ||
this._selects.push(expr); | ||
return this; | ||
}; | ||
/** | ||
* Run through the reject expressions ANDing each | ||
* | ||
* @param {Node} node | ||
* @param {Boolean} peek | ||
* @return {Boolean} | ||
* @api private | ||
*/ | ||
/** | ||
* Run through the selects ORing each | ||
* | ||
* @param {Node} node | ||
* @param {Boolean} peek | ||
* @return {Boolean} | ||
* @api private | ||
*/ | ||
Iterator.prototype.rejects = function(node, peek) { | ||
var exprs = this._rejects; | ||
var len = exprs.length; | ||
if (!len) return true; | ||
Iterator.prototype.selects = function(node, peek) { | ||
var exprs = this._selects; | ||
var len = exprs.length; | ||
if (!len) return true; | ||
for (var i = 0; i < len; i++) { | ||
if (exprs[i].call(this, node, peek)) return false; | ||
} | ||
return true; | ||
}; | ||
for (var i = 0; i < len; i++) { | ||
if (exprs[i].call(this, node, peek)) return true; | ||
} | ||
return false; | ||
}; | ||
/** | ||
* Check if node is higher | ||
* than root. | ||
* | ||
* @param {Node} node | ||
* @param {Node} root | ||
* @return {Boolean} | ||
* @api private | ||
*/ | ||
/** | ||
* Select nodes that cause `expr(node)` | ||
* to be falsy | ||
* | ||
* @param {Number|String|Function} expr | ||
* @return {Iterator} self | ||
* @api public | ||
*/ | ||
Iterator.prototype.higher = function(node) { | ||
var root = this.root; | ||
if (!root) return false; | ||
node = node.parentNode; | ||
while (node && node != root) node = node.parentNode; | ||
return node != root; | ||
}; | ||
Iterator.prototype.reject = function(expr) { | ||
expr = this.compile(expr); | ||
this._rejects.push(expr); | ||
return this; | ||
}; | ||
/** | ||
* Compile an expression | ||
* | ||
* @param {String|Function|Number} expr | ||
* @return {Function} | ||
*/ | ||
/** | ||
* Run through the reject expressions ANDing each | ||
* | ||
* @param {Node} node | ||
* @param {Boolean} peek | ||
* @return {Boolean} | ||
* @api private | ||
*/ | ||
Iterator.prototype.compile = function(expr) { | ||
switch (typeof expr) { | ||
case 'number': | ||
return function(node) { return expr == node.nodeType; }; | ||
case 'string': | ||
return new Function('node', 'return ' + componentProps(expr, 'node.')); | ||
case 'function': | ||
return expr; | ||
default: | ||
return function() { return true; }; | ||
} | ||
}; | ||
Iterator.prototype.rejects = function(node, peek) { | ||
var exprs = this._rejects; | ||
var len = exprs.length; | ||
if (!len) return true; | ||
/** | ||
* Peek in either direction | ||
* `n` nodes. Peek backwards | ||
* using negative numbers. | ||
* | ||
* @param {Number} n (optional) | ||
* @return {Node|null} | ||
* @api public | ||
*/ | ||
for (var i = 0; i < len; i++) { | ||
if (exprs[i].call(this, node, peek)) return false; | ||
} | ||
return true; | ||
}; | ||
Iterator.prototype.peak = | ||
Iterator.prototype.peek = function(expr, n) { | ||
if (arguments.length == 1) n = expr, expr = true; | ||
n = undefined == n ? 1 : n; | ||
if (!n) return this.node; | ||
else if (n > 0) return this.next(expr, n, true); | ||
else return this.prev(expr, Math.abs(n), true); | ||
}; | ||
/** | ||
* Check if node is higher | ||
* than root. | ||
* | ||
* @param {Node} node | ||
* @param {Node} root | ||
* @return {Boolean} | ||
* @api private | ||
*/ | ||
/** | ||
* Add a plugin | ||
* | ||
* @param {Function} fn | ||
* @return {Iterator} | ||
* @api public | ||
*/ | ||
Iterator.prototype.higher = function(node) { | ||
var root = this.root; | ||
if (!root) return false; | ||
node = node.parentNode; | ||
while (node && node != root) node = node.parentNode; | ||
return node != root; | ||
}; | ||
Iterator.prototype.use = function(fn) { | ||
fn(this); | ||
return this; | ||
}; | ||
/** | ||
* Compile an expression | ||
* | ||
* @param {String|Function|Number} expr | ||
* @return {Function} | ||
*/ | ||
var selection = window.getSelection(); | ||
Iterator.prototype.compile = function(expr) { | ||
switch (typeof expr) { | ||
case 'number': | ||
return function(node) { return expr == node.nodeType; }; | ||
case 'string': | ||
return new Function('node', 'return ' + componentProps(expr, 'node.')); | ||
case 'function': | ||
return expr; | ||
default: | ||
return function() { return true; }; | ||
} | ||
}; | ||
/** | ||
* Peek in either direction | ||
* `n` nodes. Peek backwards | ||
* using negative numbers. | ||
* | ||
* @param {Number} n (optional) | ||
* @return {Node|null} | ||
* @api public | ||
*/ | ||
/** | ||
* Add selection / insert cursor. | ||
* | ||
* @param {Range} range | ||
*/ | ||
function applyRange(range) { | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
Iterator.prototype.peak = | ||
Iterator.prototype.peek = function(expr, n) { | ||
if (arguments.length == 1) n = expr, expr = true; | ||
n = undefined == n ? 1 : n; | ||
if (!n) return this.node; | ||
else if (n > 0) return this.next(expr, n, true); | ||
else return this.prev(expr, Math.abs(n), true); | ||
}; | ||
/** | ||
* Add a plugin | ||
* | ||
* @param {Function} fn | ||
* @return {Iterator} | ||
* @api public | ||
*/ | ||
/** | ||
* Get current document selection. | ||
* | ||
* @return {Selection} | ||
*/ | ||
function getWindowSelection() { | ||
return selection; | ||
} | ||
Iterator.prototype.use = function(fn) { | ||
fn(this); | ||
return this; | ||
}; | ||
var selection = window.getSelection(); | ||
/** | ||
* Return true if element is part of window selection. | ||
* | ||
* @param {Element} el | ||
* @return {Boolean} | ||
*/ | ||
function isSelected(el) { | ||
if (!selection.rangeCount) { | ||
return null; | ||
/** | ||
* Add selection / insert cursor. | ||
* | ||
* @param {Range} range | ||
*/ | ||
function applyRange(range) { | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
var focusNode = selection.focusNode; | ||
// IE supports Node#contains for elements only | ||
// thus we ensure we check against an actual Element node | ||
if (isText(focusNode)) { | ||
focusNode = focusNode.parentNode; | ||
/** | ||
* Get current document selection. | ||
* | ||
* @return {Selection} | ||
*/ | ||
function getWindowSelection() { | ||
return selection; | ||
} | ||
return el == focusNode || el.contains(focusNode); | ||
} | ||
/** | ||
* Return true if element is part of window selection. | ||
* | ||
* @param {Element} el | ||
* @return {Boolean} | ||
*/ | ||
function isSelected(el) { | ||
/** | ||
* Set cursor or selection position. | ||
* | ||
* @param {Element} el | ||
* @param {SelectionRange} selection | ||
*/ | ||
function setRange(el, selection) { | ||
if (!selection.rangeCount) { | ||
return null; | ||
} | ||
var range = createRange(el, selection); | ||
var focusNode = selection.focusNode; | ||
applyRange(range); | ||
} | ||
// IE supports Node#contains for elements only | ||
// thus we ensure we check against an actual Element node | ||
if (isText(focusNode)) { | ||
focusNode = focusNode.parentNode; | ||
} | ||
/** | ||
* Get cursor or selection position. | ||
* | ||
* @param {Element} el | ||
*/ | ||
function getRange(el) { | ||
if (!isSelected(el)) { | ||
return null; | ||
return el == focusNode || el.contains(focusNode); | ||
} | ||
var range = selection.getRangeAt(0); | ||
var startContainer = range.startContainer; | ||
var endContainer = range.endContainer; | ||
var startOffset = range.startOffset; | ||
var endOffset = range.endOffset; | ||
/** | ||
* Set cursor or selection position. | ||
* | ||
* @param {Element} el | ||
* @param {SelectionRange} selection | ||
*/ | ||
function setRange(el, selection) { | ||
var i = domIterator(el.firstChild, el); | ||
var range = createRange(el, selection); | ||
var next = i.node; | ||
var last; | ||
applyRange(range); | ||
} | ||
var isClosing = false; | ||
var selectionStart; | ||
var count = 0; | ||
/** | ||
* Get cursor or selection position. | ||
* | ||
* @param {Element} el | ||
*/ | ||
function getRange(el) { | ||
function isBeforeEnd(node, referenceNode) { | ||
if (arguments.length === 1) { | ||
referenceNode = node; | ||
if (!isSelected(el)) { | ||
return null; | ||
} | ||
return ( | ||
node.parentNode === endContainer && | ||
referenceNode == endContainer.childNodes[endOffset] | ||
); | ||
var range = selection.getRangeAt(0); | ||
} | ||
var startContainer = range.startContainer; | ||
var endContainer = range.endContainer; | ||
var startOffset = range.startOffset; | ||
var endOffset = range.endOffset; | ||
function isBeforeStart(node, referenceNode) { | ||
var i = domIterator(el.firstChild, el); | ||
if (arguments.length === 1) { | ||
referenceNode = node; | ||
} | ||
var next = i.node; | ||
var last; | ||
return ( | ||
node.parentNode === startContainer && | ||
referenceNode == startContainer.childNodes[startOffset] | ||
); | ||
var isClosing = false; | ||
} | ||
var selectionStart; | ||
var count = 0; | ||
while (next) { | ||
function isBeforeEnd(node, referenceNode) { | ||
// start before node | ||
if (isBeforeStart(next)) { | ||
selectionStart = count; | ||
} | ||
if (arguments.length === 1) { | ||
referenceNode = node; | ||
} | ||
// end before node | ||
if (isBeforeEnd(next)) { | ||
break; | ||
} | ||
return ( | ||
node.parentNode === endContainer && | ||
referenceNode == endContainer.childNodes[endOffset] | ||
); | ||
if (!isClosing) { | ||
if ( | ||
isBr(next) || ( | ||
last && (last.nextSibling == next) && ( | ||
isDiv(next) || | ||
isParagraph(next) | ||
) | ||
) | ||
) { | ||
count++; | ||
} | ||
} | ||
function isBeforeStart(node, referenceNode) { | ||
if (isText(next)) { | ||
// #text node | ||
if (startContainer === next) { | ||
selectionStart = count + startOffset; | ||
if (arguments.length === 1) { | ||
referenceNode = node; | ||
} | ||
if (endContainer === next) { | ||
count += endOffset; | ||
break; | ||
} | ||
return ( | ||
node.parentNode === startContainer && | ||
referenceNode == startContainer.childNodes[startOffset] | ||
); | ||
count += next.textContent.length; | ||
} | ||
if (isText(next) || isClosing) { | ||
while (next) { | ||
// start before node | ||
if (isBeforeStart(next, next.nextSibling)) { | ||
if (isBeforeStart(next)) { | ||
selectionStart = count; | ||
@@ -589,176 +548,217 @@ } | ||
// end before node | ||
if (isBeforeEnd(next, next.nextSibling)) { | ||
if (isBeforeEnd(next)) { | ||
break; | ||
} | ||
if (!isClosing) { | ||
if ( | ||
isBr(next) || ( | ||
last && (last.nextSibling == next) && ( | ||
isDiv(next) || | ||
isParagraph(next) | ||
) | ||
) | ||
) { | ||
count++; | ||
} | ||
} | ||
if (isText(next)) { | ||
// #text node | ||
if (startContainer === next) { | ||
selectionStart = count + startOffset; | ||
} | ||
if (endContainer === next) { | ||
count += endOffset; | ||
break; | ||
} | ||
count += next.textContent.length; | ||
} | ||
if (isText(next) || isClosing) { | ||
// start before node | ||
if (isBeforeStart(next, next.nextSibling)) { | ||
selectionStart = count; | ||
} | ||
// end before node | ||
if (isBeforeEnd(next, next.nextSibling)) { | ||
break; | ||
} | ||
} | ||
last = next; | ||
next = i.next(); | ||
isClosing = i.closingTag; | ||
} | ||
last = next; | ||
next = i.next(); | ||
isClosing = i.closingTag; | ||
// selection until end of text | ||
return { | ||
start: typeof selectionStart === 'undefined' ? count : selectionStart, | ||
end: count | ||
}; | ||
} | ||
// selection until end of text | ||
return { | ||
start: typeof selectionStart === 'undefined' ? count : selectionStart, | ||
end: count | ||
}; | ||
} | ||
/** | ||
* Annotate the given text with markers based on the | ||
* given range. | ||
* | ||
* @param {String} text | ||
* @param {SelectionRange} range | ||
* | ||
* @return {String} annotated text | ||
*/ | ||
function annotateRange(text, range) { | ||
var str; | ||
/** | ||
* Annotate the given text with markers based on the | ||
* given range. | ||
* | ||
* @param {String} text | ||
* @param {SelectionRange} range | ||
* | ||
* @return {String} annotated text | ||
*/ | ||
function annotateRange(text, range) { | ||
var str; | ||
if (range.start === range.end) { | ||
str = ( | ||
text.substring(0, range.start) + | ||
'|' + | ||
text.substring(range.start) | ||
); | ||
} else { | ||
str = ( | ||
text.substring(0, range.start) + | ||
'<' + | ||
text.substring(range.start, range.end) + | ||
'>' + | ||
text.substring(range.end) | ||
); | ||
} | ||
if (range.start === range.end) { | ||
str = ( | ||
text.substring(0, range.start) + | ||
'|' + | ||
text.substring(range.start) | ||
); | ||
} else { | ||
str = ( | ||
text.substring(0, range.start) + | ||
'<' + | ||
text.substring(range.start, range.end) + | ||
'>' + | ||
text.substring(range.end) | ||
); | ||
return str; | ||
} | ||
return str; | ||
} | ||
//////// helpers /////////////////////////// | ||
//////// helpers /////////////////////////// | ||
function createRange(el, selection) { | ||
function createRange(el, selection) { | ||
var start = selection.start; | ||
var end = selection.end; | ||
var start = selection.start; | ||
var end = selection.end; | ||
var range = document.createRange(); | ||
var range = document.createRange(); | ||
var i = domIterator(el.firstChild, el); | ||
var i = domIterator(el.firstChild, el); | ||
var next = i.node; | ||
var isClosing = false; | ||
var next = i.node; | ||
var isClosing = false; | ||
var count = 0; | ||
var length; | ||
var count = 0; | ||
var length; | ||
while (next) { | ||
while (next) { | ||
if (count === start) { | ||
if (isClosing) { | ||
range.setStartAfter(next); | ||
} else { | ||
range.setStartBefore(next); | ||
if (count === start) { | ||
if (isClosing) { | ||
range.setStartAfter(next); | ||
} else { | ||
range.setStartBefore(next); | ||
} | ||
} | ||
} | ||
if (count === end) { | ||
if (isClosing) { | ||
range.setEndAfter(next); | ||
} else { | ||
range.setEndBefore(next); | ||
if (count === end) { | ||
if (isClosing) { | ||
range.setEndAfter(next); | ||
} else { | ||
range.setEndBefore(next); | ||
} | ||
return range; | ||
} | ||
return range; | ||
} | ||
if (!isClosing) { | ||
if ( | ||
isBr(next) || ( | ||
next.previousSibling && ( | ||
isDiv(next) || | ||
isParagraph(next) | ||
if (!isClosing) { | ||
if ( | ||
isBr(next) || ( | ||
next.previousSibling && ( | ||
isDiv(next) || | ||
isParagraph(next) | ||
) | ||
) | ||
) | ||
) { | ||
count++; | ||
) { | ||
count++; | ||
} | ||
} | ||
} | ||
if (isText(next)) { | ||
if (isText(next)) { | ||
length = next.textContent.length; | ||
length = next.textContent.length; | ||
if (count <= start && count + length > start) { | ||
range.setStart(next, start - count); | ||
} | ||
if (count <= start && count + length > start) { | ||
range.setStart(next, start - count); | ||
} | ||
if (count + length > end) { | ||
range.setEnd(next, end - count); | ||
if (count + length > end) { | ||
range.setEnd(next, end - count); | ||
return range; | ||
return range; | ||
} | ||
count += length; | ||
} | ||
count += length; | ||
next = i.next(); | ||
isClosing = i.closingTag; | ||
} | ||
next = i.next(); | ||
isClosing = i.closingTag; | ||
} | ||
// out of range | ||
if (count <= start) { | ||
if (el.lastChild) { | ||
range.setStartAfter(el.lastChild); | ||
} else { | ||
range.setStart(el, 0); | ||
} | ||
} | ||
// out of range | ||
if (count <= start) { | ||
if (el.lastChild) { | ||
range.setStartAfter(el.lastChild); | ||
range.setEndAfter(el.lastChild); | ||
} else { | ||
range.setStart(el, 0); | ||
range.setEnd(el, 0); | ||
} | ||
return range; | ||
} | ||
if (el.lastChild) { | ||
range.setEndAfter(el.lastChild); | ||
} else { | ||
range.setEnd(el, 0); | ||
function isText(node) { | ||
return node.nodeType === 3; | ||
} | ||
return range; | ||
} | ||
function isBr(node) { | ||
return ( | ||
node.nodeType === 1 && | ||
node.nodeName === 'BR' | ||
); | ||
} | ||
function isText(node) { | ||
return node.nodeType === 3; | ||
} | ||
function isDiv(node) { | ||
return ( | ||
node.nodeType === 1 && | ||
node.nodeName === 'DIV' | ||
); | ||
} | ||
function isBr(node) { | ||
return ( | ||
node.nodeType === 1 && | ||
node.nodeName === 'BR' | ||
); | ||
} | ||
function isParagraph(node) { | ||
return ( | ||
node.nodeType === 1 && | ||
node.nodeName === 'P' | ||
); | ||
} | ||
function isDiv(node) { | ||
return ( | ||
node.nodeType === 1 && | ||
node.nodeName === 'DIV' | ||
); | ||
} | ||
exports.applyRange = applyRange; | ||
exports.getWindowSelection = getWindowSelection; | ||
exports.isSelected = isSelected; | ||
exports.setRange = setRange; | ||
exports.getRange = getRange; | ||
exports.annotateRange = annotateRange; | ||
function isParagraph(node) { | ||
return ( | ||
node.nodeType === 1 && | ||
node.nodeName === 'P' | ||
); | ||
} | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
exports.applyRange = applyRange; | ||
exports.getWindowSelection = getWindowSelection; | ||
exports.isSelected = isSelected; | ||
exports.setRange = setRange; | ||
exports.getRange = getRange; | ||
exports.annotateRange = annotateRange; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
}))); |
{ | ||
"name": "selection-ranges", | ||
"version": "2.0.1", | ||
"version": "2.1.0", | ||
"description": "Selection range manipulation for contenteditable elements", | ||
"main": "dist/index.js", | ||
"module": "dist/index.mjs", | ||
"module": "dist/index.esm.js", | ||
"browser": "dist/selection-ranges.js", | ||
"scripts": { | ||
@@ -8,0 +9,0 @@ "all": "run-s lint test bundle test:integration", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
50475