You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
831 lines
23 KiB
831 lines
23 KiB
2 years ago
|
/**
|
||
|
* Methods for traversing the DOM structure.
|
||
|
*
|
||
|
* @module cheerio/traversing
|
||
|
*/
|
||
|
import { hasChildren, isDocument, } from 'domhandler';
|
||
|
import * as select from 'cheerio-select';
|
||
|
import { domEach, isTag, isCheerio } from '../utils.js';
|
||
|
import { contains } from '../static.js';
|
||
|
import { getChildren, getSiblings, nextElementSibling, prevElementSibling, uniqueSort, } from 'domutils';
|
||
|
const reSiblingSelector = /^\s*[~+]/;
|
||
|
/**
|
||
|
* Get the descendants of each element in the current set of matched elements,
|
||
|
* filtered by a selector, jQuery object, or element.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('#fruits').find('li').length;
|
||
|
* //=> 3
|
||
|
* $('#fruits').find($('.apple')).length;
|
||
|
* //=> 1
|
||
|
* ```
|
||
|
*
|
||
|
* @param selectorOrHaystack - Element to look for.
|
||
|
* @returns The found elements.
|
||
|
* @see {@link https://api.jquery.com/find/}
|
||
|
*/
|
||
|
export function find(selectorOrHaystack) {
|
||
|
var _a;
|
||
|
if (!selectorOrHaystack) {
|
||
|
return this._make([]);
|
||
|
}
|
||
|
const context = this.toArray();
|
||
|
if (typeof selectorOrHaystack !== 'string') {
|
||
|
const haystack = isCheerio(selectorOrHaystack)
|
||
|
? selectorOrHaystack.toArray()
|
||
|
: [selectorOrHaystack];
|
||
|
return this._make(haystack.filter((elem) => context.some((node) => contains(node, elem))));
|
||
|
}
|
||
|
const elems = reSiblingSelector.test(selectorOrHaystack)
|
||
|
? context
|
||
|
: this.children().toArray();
|
||
|
const options = {
|
||
|
context,
|
||
|
root: (_a = this._root) === null || _a === void 0 ? void 0 : _a[0],
|
||
|
// Pass options that are recognized by `cheerio-select`
|
||
|
xmlMode: this.options.xmlMode,
|
||
|
lowerCaseTags: this.options.lowerCaseTags,
|
||
|
lowerCaseAttributeNames: this.options.lowerCaseAttributeNames,
|
||
|
pseudos: this.options.pseudos,
|
||
|
quirksMode: this.options.quirksMode,
|
||
|
};
|
||
|
return this._make(select.select(selectorOrHaystack, elems, options));
|
||
|
}
|
||
|
/**
|
||
|
* Creates a matcher, using a particular mapping function. Matchers provide a
|
||
|
* function that finds elements using a generating function, supporting filtering.
|
||
|
*
|
||
|
* @private
|
||
|
* @param matchMap - Mapping function.
|
||
|
* @returns - Function for wrapping generating functions.
|
||
|
*/
|
||
|
function _getMatcher(matchMap) {
|
||
|
return function (fn, ...postFns) {
|
||
|
return function (selector) {
|
||
|
var _a;
|
||
|
let matched = matchMap(fn, this);
|
||
|
if (selector) {
|
||
|
matched = filterArray(matched, selector, this.options.xmlMode, (_a = this._root) === null || _a === void 0 ? void 0 : _a[0]);
|
||
|
}
|
||
|
return this._make(
|
||
|
// Post processing is only necessary if there is more than one element.
|
||
|
this.length > 1 && matched.length > 1
|
||
|
? postFns.reduce((elems, fn) => fn(elems), matched)
|
||
|
: matched);
|
||
|
};
|
||
|
};
|
||
|
}
|
||
|
/** Matcher that adds multiple elements for each entry in the input. */
|
||
|
const _matcher = _getMatcher((fn, elems) => {
|
||
|
const ret = [];
|
||
|
for (let i = 0; i < elems.length; i++) {
|
||
|
const value = fn(elems[i]);
|
||
|
ret.push(value);
|
||
|
}
|
||
|
return new Array().concat(...ret);
|
||
|
});
|
||
|
/** Matcher that adds at most one element for each entry in the input. */
|
||
|
const _singleMatcher = _getMatcher((fn, elems) => {
|
||
|
const ret = [];
|
||
|
for (let i = 0; i < elems.length; i++) {
|
||
|
const value = fn(elems[i]);
|
||
|
if (value !== null) {
|
||
|
ret.push(value);
|
||
|
}
|
||
|
}
|
||
|
return ret;
|
||
|
});
|
||
|
/**
|
||
|
* Matcher that supports traversing until a condition is met.
|
||
|
*
|
||
|
* @returns A function usable for `*Until` methods.
|
||
|
*/
|
||
|
function _matchUntil(nextElem, ...postFns) {
|
||
|
// We use a variable here that is used from within the matcher.
|
||
|
let matches = null;
|
||
|
const innerMatcher = _getMatcher((nextElem, elems) => {
|
||
|
const matched = [];
|
||
|
domEach(elems, (elem) => {
|
||
|
for (let next; (next = nextElem(elem)); elem = next) {
|
||
|
// FIXME: `matched` might contain duplicates here and the index is too large.
|
||
|
if (matches === null || matches === void 0 ? void 0 : matches(next, matched.length))
|
||
|
break;
|
||
|
matched.push(next);
|
||
|
}
|
||
|
});
|
||
|
return matched;
|
||
|
})(nextElem, ...postFns);
|
||
|
return function (selector, filterSelector) {
|
||
|
// Override `matches` variable with the new target.
|
||
|
matches =
|
||
|
typeof selector === 'string'
|
||
|
? (elem) => select.is(elem, selector, this.options)
|
||
|
: selector
|
||
|
? getFilterFn(selector)
|
||
|
: null;
|
||
|
const ret = innerMatcher.call(this, filterSelector);
|
||
|
// Set `matches` to `null`, so we don't waste memory.
|
||
|
matches = null;
|
||
|
return ret;
|
||
|
};
|
||
|
}
|
||
|
function _removeDuplicates(elems) {
|
||
|
return Array.from(new Set(elems));
|
||
|
}
|
||
|
/**
|
||
|
* Get the parent of each element in the current set of matched elements,
|
||
|
* optionally filtered by a selector.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.pear').parent().attr('id');
|
||
|
* //=> fruits
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - If specified filter for parent.
|
||
|
* @returns The parents.
|
||
|
* @see {@link https://api.jquery.com/parent/}
|
||
|
*/
|
||
|
export const parent = _singleMatcher(({ parent }) => (parent && !isDocument(parent) ? parent : null), _removeDuplicates);
|
||
|
/**
|
||
|
* Get a set of parents filtered by `selector` of each element in the current
|
||
|
* set of match elements.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.orange').parents().length;
|
||
|
* //=> 2
|
||
|
* $('.orange').parents('#fruits').length;
|
||
|
* //=> 1
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - If specified filter for parents.
|
||
|
* @returns The parents.
|
||
|
* @see {@link https://api.jquery.com/parents/}
|
||
|
*/
|
||
|
export const parents = _matcher((elem) => {
|
||
|
const matched = [];
|
||
|
while (elem.parent && !isDocument(elem.parent)) {
|
||
|
matched.push(elem.parent);
|
||
|
elem = elem.parent;
|
||
|
}
|
||
|
return matched;
|
||
|
}, uniqueSort, (elems) => elems.reverse());
|
||
|
/**
|
||
|
* Get the ancestors of each element in the current set of matched elements, up
|
||
|
* to but not including the element matched by the selector, DOM node, or cheerio object.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.orange').parentsUntil('#food').length;
|
||
|
* //=> 1
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - Selector for element to stop at.
|
||
|
* @param filterSelector - Optional filter for parents.
|
||
|
* @returns The parents.
|
||
|
* @see {@link https://api.jquery.com/parentsUntil/}
|
||
|
*/
|
||
|
export const parentsUntil = _matchUntil(({ parent }) => (parent && !isDocument(parent) ? parent : null), uniqueSort, (elems) => elems.reverse());
|
||
|
/**
|
||
|
* For each element in the set, get the first element that matches the selector
|
||
|
* by testing the element itself and traversing up through its ancestors in the DOM tree.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.orange').closest();
|
||
|
* //=> []
|
||
|
*
|
||
|
* $('.orange').closest('.apple');
|
||
|
* // => []
|
||
|
*
|
||
|
* $('.orange').closest('li');
|
||
|
* //=> [<li class="orange">Orange</li>]
|
||
|
*
|
||
|
* $('.orange').closest('#fruits');
|
||
|
* //=> [<ul id="fruits"> ... </ul>]
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - Selector for the element to find.
|
||
|
* @returns The closest nodes.
|
||
|
* @see {@link https://api.jquery.com/closest/}
|
||
|
*/
|
||
|
export function closest(selector) {
|
||
|
var _a;
|
||
|
const set = [];
|
||
|
if (!selector) {
|
||
|
return this._make(set);
|
||
|
}
|
||
|
const selectOpts = {
|
||
|
xmlMode: this.options.xmlMode,
|
||
|
root: (_a = this._root) === null || _a === void 0 ? void 0 : _a[0],
|
||
|
};
|
||
|
const selectFn = typeof selector === 'string'
|
||
|
? (elem) => select.is(elem, selector, selectOpts)
|
||
|
: getFilterFn(selector);
|
||
|
domEach(this, (elem) => {
|
||
|
while (elem && isTag(elem)) {
|
||
|
if (selectFn(elem, 0)) {
|
||
|
// Do not add duplicate elements to the set
|
||
|
if (!set.includes(elem)) {
|
||
|
set.push(elem);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
elem = elem.parent;
|
||
|
}
|
||
|
});
|
||
|
return this._make(set);
|
||
|
}
|
||
|
/**
|
||
|
* Gets the next sibling of the first selected element, optionally filtered by a selector.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.apple').next().hasClass('orange');
|
||
|
* //=> true
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - If specified filter for sibling.
|
||
|
* @returns The next nodes.
|
||
|
* @see {@link https://api.jquery.com/next/}
|
||
|
*/
|
||
|
export const next = _singleMatcher((elem) => nextElementSibling(elem));
|
||
|
/**
|
||
|
* Gets all the following siblings of the first selected element, optionally
|
||
|
* filtered by a selector.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.apple').nextAll();
|
||
|
* //=> [<li class="orange">Orange</li>, <li class="pear">Pear</li>]
|
||
|
* $('.apple').nextAll('.orange');
|
||
|
* //=> [<li class="orange">Orange</li>]
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - If specified filter for siblings.
|
||
|
* @returns The next nodes.
|
||
|
* @see {@link https://api.jquery.com/nextAll/}
|
||
|
*/
|
||
|
export const nextAll = _matcher((elem) => {
|
||
|
const matched = [];
|
||
|
while (elem.next) {
|
||
|
elem = elem.next;
|
||
|
if (isTag(elem))
|
||
|
matched.push(elem);
|
||
|
}
|
||
|
return matched;
|
||
|
}, _removeDuplicates);
|
||
|
/**
|
||
|
* Gets all the following siblings up to but not including the element matched
|
||
|
* by the selector, optionally filtered by another selector.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.apple').nextUntil('.pear');
|
||
|
* //=> [<li class="orange">Orange</li>]
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - Selector for element to stop at.
|
||
|
* @param filterSelector - If specified filter for siblings.
|
||
|
* @returns The next nodes.
|
||
|
* @see {@link https://api.jquery.com/nextUntil/}
|
||
|
*/
|
||
|
export const nextUntil = _matchUntil((el) => nextElementSibling(el), _removeDuplicates);
|
||
|
/**
|
||
|
* Gets the previous sibling of the first selected element optionally filtered
|
||
|
* by a selector.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.orange').prev().hasClass('apple');
|
||
|
* //=> true
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - If specified filter for siblings.
|
||
|
* @returns The previous nodes.
|
||
|
* @see {@link https://api.jquery.com/prev/}
|
||
|
*/
|
||
|
export const prev = _singleMatcher((elem) => prevElementSibling(elem));
|
||
|
/**
|
||
|
* Gets all the preceding siblings of the first selected element, optionally
|
||
|
* filtered by a selector.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.pear').prevAll();
|
||
|
* //=> [<li class="orange">Orange</li>, <li class="apple">Apple</li>]
|
||
|
*
|
||
|
* $('.pear').prevAll('.orange');
|
||
|
* //=> [<li class="orange">Orange</li>]
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - If specified filter for siblings.
|
||
|
* @returns The previous nodes.
|
||
|
* @see {@link https://api.jquery.com/prevAll/}
|
||
|
*/
|
||
|
export const prevAll = _matcher((elem) => {
|
||
|
const matched = [];
|
||
|
while (elem.prev) {
|
||
|
elem = elem.prev;
|
||
|
if (isTag(elem))
|
||
|
matched.push(elem);
|
||
|
}
|
||
|
return matched;
|
||
|
}, _removeDuplicates);
|
||
|
/**
|
||
|
* Gets all the preceding siblings up to but not including the element matched
|
||
|
* by the selector, optionally filtered by another selector.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.pear').prevUntil('.apple');
|
||
|
* //=> [<li class="orange">Orange</li>]
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - Selector for element to stop at.
|
||
|
* @param filterSelector - If specified filter for siblings.
|
||
|
* @returns The previous nodes.
|
||
|
* @see {@link https://api.jquery.com/prevUntil/}
|
||
|
*/
|
||
|
export const prevUntil = _matchUntil((el) => prevElementSibling(el), _removeDuplicates);
|
||
|
/**
|
||
|
* Get the siblings of each element (excluding the element) in the set of
|
||
|
* matched elements, optionally filtered by a selector.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.pear').siblings().length;
|
||
|
* //=> 2
|
||
|
*
|
||
|
* $('.pear').siblings('.orange').length;
|
||
|
* //=> 1
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - If specified filter for siblings.
|
||
|
* @returns The siblings.
|
||
|
* @see {@link https://api.jquery.com/siblings/}
|
||
|
*/
|
||
|
export const siblings = _matcher((elem) => getSiblings(elem).filter((el) => isTag(el) && el !== elem), uniqueSort);
|
||
|
/**
|
||
|
* Gets the element children of each element in the set of matched elements.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('#fruits').children().length;
|
||
|
* //=> 3
|
||
|
*
|
||
|
* $('#fruits').children('.pear').text();
|
||
|
* //=> Pear
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - If specified filter for children.
|
||
|
* @returns The children.
|
||
|
* @see {@link https://api.jquery.com/children/}
|
||
|
*/
|
||
|
export const children = _matcher((elem) => getChildren(elem).filter(isTag), _removeDuplicates);
|
||
|
/**
|
||
|
* Gets the children of each element in the set of matched elements, including
|
||
|
* text and comment nodes.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('#fruits').contents().length;
|
||
|
* //=> 3
|
||
|
* ```
|
||
|
*
|
||
|
* @returns The children.
|
||
|
* @see {@link https://api.jquery.com/contents/}
|
||
|
*/
|
||
|
export function contents() {
|
||
|
const elems = this.toArray().reduce((newElems, elem) => hasChildren(elem) ? newElems.concat(elem.children) : newElems, []);
|
||
|
return this._make(elems);
|
||
|
}
|
||
|
/**
|
||
|
* Iterates over a cheerio object, executing a function for each matched
|
||
|
* element. When the callback is fired, the function is fired in the context of
|
||
|
* the DOM element, so `this` refers to the current element, which is equivalent
|
||
|
* to the function parameter `element`. To break out of the `each` loop early,
|
||
|
* return with `false`.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* const fruits = [];
|
||
|
*
|
||
|
* $('li').each(function (i, elem) {
|
||
|
* fruits[i] = $(this).text();
|
||
|
* });
|
||
|
*
|
||
|
* fruits.join(', ');
|
||
|
* //=> Apple, Orange, Pear
|
||
|
* ```
|
||
|
*
|
||
|
* @param fn - Function to execute.
|
||
|
* @returns The instance itself, useful for chaining.
|
||
|
* @see {@link https://api.jquery.com/each/}
|
||
|
*/
|
||
|
export function each(fn) {
|
||
|
let i = 0;
|
||
|
const len = this.length;
|
||
|
while (i < len && fn.call(this[i], i, this[i]) !== false)
|
||
|
++i;
|
||
|
return this;
|
||
|
}
|
||
|
/**
|
||
|
* Pass each element in the current matched set through a function, producing a
|
||
|
* new Cheerio object containing the return values. The function can return an
|
||
|
* individual data item or an array of data items to be inserted into the
|
||
|
* resulting set. If an array is returned, the elements inside the array are
|
||
|
* inserted into the set. If the function returns null or undefined, no element
|
||
|
* will be inserted.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('li')
|
||
|
* .map(function (i, el) {
|
||
|
* // this === el
|
||
|
* return $(this).text();
|
||
|
* })
|
||
|
* .toArray()
|
||
|
* .join(' ');
|
||
|
* //=> "apple orange pear"
|
||
|
* ```
|
||
|
*
|
||
|
* @param fn - Function to execute.
|
||
|
* @returns The mapped elements, wrapped in a Cheerio collection.
|
||
|
* @see {@link https://api.jquery.com/map/}
|
||
|
*/
|
||
|
export function map(fn) {
|
||
|
let elems = [];
|
||
|
for (let i = 0; i < this.length; i++) {
|
||
|
const el = this[i];
|
||
|
const val = fn.call(el, i, el);
|
||
|
if (val != null) {
|
||
|
elems = elems.concat(val);
|
||
|
}
|
||
|
}
|
||
|
return this._make(elems);
|
||
|
}
|
||
|
/**
|
||
|
* Creates a function to test if a filter is matched.
|
||
|
*
|
||
|
* @param match - A filter.
|
||
|
* @returns A function that determines if a filter has been matched.
|
||
|
*/
|
||
|
function getFilterFn(match) {
|
||
|
if (typeof match === 'function') {
|
||
|
return (el, i) => match.call(el, i, el);
|
||
|
}
|
||
|
if (isCheerio(match)) {
|
||
|
return (el) => Array.prototype.includes.call(match, el);
|
||
|
}
|
||
|
return function (el) {
|
||
|
return match === el;
|
||
|
};
|
||
|
}
|
||
|
export function filter(match) {
|
||
|
var _a;
|
||
|
return this._make(filterArray(this.toArray(), match, this.options.xmlMode, (_a = this._root) === null || _a === void 0 ? void 0 : _a[0]));
|
||
|
}
|
||
|
export function filterArray(nodes, match, xmlMode, root) {
|
||
|
return typeof match === 'string'
|
||
|
? select.filter(match, nodes, { xmlMode, root })
|
||
|
: nodes.filter(getFilterFn(match));
|
||
|
}
|
||
|
/**
|
||
|
* Checks the current list of elements and returns `true` if _any_ of the
|
||
|
* elements match the selector. If using an element or Cheerio selection,
|
||
|
* returns `true` if _any_ of the elements match. If using a predicate function,
|
||
|
* the function is executed in the context of the selected element, so `this`
|
||
|
* refers to the current element.
|
||
|
*
|
||
|
* @category Attributes
|
||
|
* @param selector - Selector for the selection.
|
||
|
* @returns Whether or not the selector matches an element of the instance.
|
||
|
* @see {@link https://api.jquery.com/is/}
|
||
|
*/
|
||
|
export function is(selector) {
|
||
|
const nodes = this.toArray();
|
||
|
return typeof selector === 'string'
|
||
|
? select.some(nodes.filter(isTag), selector, this.options)
|
||
|
: selector
|
||
|
? nodes.some(getFilterFn(selector))
|
||
|
: false;
|
||
|
}
|
||
|
/**
|
||
|
* Remove elements from the set of matched elements. Given a Cheerio object that
|
||
|
* represents a set of DOM elements, the `.not()` method constructs a new
|
||
|
* Cheerio object from a subset of the matching elements. The supplied selector
|
||
|
* is tested against each element; the elements that don't match the selector
|
||
|
* will be included in the result.
|
||
|
*
|
||
|
* The `.not()` method can take a function as its argument in the same way that
|
||
|
* `.filter()` does. Elements for which the function returns `true` are excluded
|
||
|
* from the filtered set; all other elements are included.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example <caption>Selector</caption>
|
||
|
*
|
||
|
* ```js
|
||
|
* $('li').not('.apple').length;
|
||
|
* //=> 2
|
||
|
* ```
|
||
|
*
|
||
|
* @example <caption>Function</caption>
|
||
|
*
|
||
|
* ```js
|
||
|
* $('li').not(function (i, el) {
|
||
|
* // this === el
|
||
|
* return $(this).attr('class') === 'orange';
|
||
|
* }).length; //=> 2
|
||
|
* ```
|
||
|
*
|
||
|
* @param match - Value to look for, following the rules above.
|
||
|
* @param container - Optional node to filter instead.
|
||
|
* @returns The filtered collection.
|
||
|
* @see {@link https://api.jquery.com/not/}
|
||
|
*/
|
||
|
export function not(match) {
|
||
|
let nodes = this.toArray();
|
||
|
if (typeof match === 'string') {
|
||
|
const matches = new Set(select.filter(match, nodes, this.options));
|
||
|
nodes = nodes.filter((el) => !matches.has(el));
|
||
|
}
|
||
|
else {
|
||
|
const filterFn = getFilterFn(match);
|
||
|
nodes = nodes.filter((el, i) => !filterFn(el, i));
|
||
|
}
|
||
|
return this._make(nodes);
|
||
|
}
|
||
|
/**
|
||
|
* Filters the set of matched elements to only those which have the given DOM
|
||
|
* element as a descendant or which have a descendant that matches the given
|
||
|
* selector. Equivalent to `.filter(':has(selector)')`.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example <caption>Selector</caption>
|
||
|
*
|
||
|
* ```js
|
||
|
* $('ul').has('.pear').attr('id');
|
||
|
* //=> fruits
|
||
|
* ```
|
||
|
*
|
||
|
* @example <caption>Element</caption>
|
||
|
*
|
||
|
* ```js
|
||
|
* $('ul').has($('.pear')[0]).attr('id');
|
||
|
* //=> fruits
|
||
|
* ```
|
||
|
*
|
||
|
* @param selectorOrHaystack - Element to look for.
|
||
|
* @returns The filtered collection.
|
||
|
* @see {@link https://api.jquery.com/has/}
|
||
|
*/
|
||
|
export function has(selectorOrHaystack) {
|
||
|
return this.filter(typeof selectorOrHaystack === 'string'
|
||
|
? // Using the `:has` selector here short-circuits searches.
|
||
|
`:has(${selectorOrHaystack})`
|
||
|
: (_, el) => this._make(el).find(selectorOrHaystack).length > 0);
|
||
|
}
|
||
|
/**
|
||
|
* Will select the first element of a cheerio object.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('#fruits').children().first().text();
|
||
|
* //=> Apple
|
||
|
* ```
|
||
|
*
|
||
|
* @returns The first element.
|
||
|
* @see {@link https://api.jquery.com/first/}
|
||
|
*/
|
||
|
export function first() {
|
||
|
return this.length > 1 ? this._make(this[0]) : this;
|
||
|
}
|
||
|
/**
|
||
|
* Will select the last element of a cheerio object.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('#fruits').children().last().text();
|
||
|
* //=> Pear
|
||
|
* ```
|
||
|
*
|
||
|
* @returns The last element.
|
||
|
* @see {@link https://api.jquery.com/last/}
|
||
|
*/
|
||
|
export function last() {
|
||
|
return this.length > 0 ? this._make(this[this.length - 1]) : this;
|
||
|
}
|
||
|
/**
|
||
|
* Reduce the set of matched elements to the one at the specified index. Use
|
||
|
* `.eq(-i)` to count backwards from the last selected element.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('li').eq(0).text();
|
||
|
* //=> Apple
|
||
|
*
|
||
|
* $('li').eq(-1).text();
|
||
|
* //=> Pear
|
||
|
* ```
|
||
|
*
|
||
|
* @param i - Index of the element to select.
|
||
|
* @returns The element at the `i`th position.
|
||
|
* @see {@link https://api.jquery.com/eq/}
|
||
|
*/
|
||
|
export function eq(i) {
|
||
|
var _a;
|
||
|
i = +i;
|
||
|
// Use the first identity optimization if possible
|
||
|
if (i === 0 && this.length <= 1)
|
||
|
return this;
|
||
|
if (i < 0)
|
||
|
i = this.length + i;
|
||
|
return this._make((_a = this[i]) !== null && _a !== void 0 ? _a : []);
|
||
|
}
|
||
|
export function get(i) {
|
||
|
if (i == null) {
|
||
|
return this.toArray();
|
||
|
}
|
||
|
return this[i < 0 ? this.length + i : i];
|
||
|
}
|
||
|
/**
|
||
|
* Retrieve all the DOM elements contained in the jQuery set as an array.
|
||
|
*
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('li').toArray();
|
||
|
* //=> [ {...}, {...}, {...} ]
|
||
|
* ```
|
||
|
*
|
||
|
* @returns The contained items.
|
||
|
*/
|
||
|
export function toArray() {
|
||
|
return Array.prototype.slice.call(this);
|
||
|
}
|
||
|
/**
|
||
|
* Search for a given element from among the matched elements.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.pear').index();
|
||
|
* //=> 2 $('.orange').index('li');
|
||
|
* //=> 1
|
||
|
* $('.apple').index($('#fruit, li'));
|
||
|
* //=> 1
|
||
|
* ```
|
||
|
*
|
||
|
* @param selectorOrNeedle - Element to look for.
|
||
|
* @returns The index of the element.
|
||
|
* @see {@link https://api.jquery.com/index/}
|
||
|
*/
|
||
|
export function index(selectorOrNeedle) {
|
||
|
let $haystack;
|
||
|
let needle;
|
||
|
if (selectorOrNeedle == null) {
|
||
|
$haystack = this.parent().children();
|
||
|
needle = this[0];
|
||
|
}
|
||
|
else if (typeof selectorOrNeedle === 'string') {
|
||
|
$haystack = this._make(selectorOrNeedle);
|
||
|
needle = this[0];
|
||
|
}
|
||
|
else {
|
||
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||
|
$haystack = this;
|
||
|
needle = isCheerio(selectorOrNeedle)
|
||
|
? selectorOrNeedle[0]
|
||
|
: selectorOrNeedle;
|
||
|
}
|
||
|
return Array.prototype.indexOf.call($haystack, needle);
|
||
|
}
|
||
|
/**
|
||
|
* Gets the elements matching the specified range (0-based position).
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('li').slice(1).eq(0).text();
|
||
|
* //=> 'Orange'
|
||
|
*
|
||
|
* $('li').slice(1, 2).length;
|
||
|
* //=> 1
|
||
|
* ```
|
||
|
*
|
||
|
* @param start - A position at which the elements begin to be selected. If
|
||
|
* negative, it indicates an offset from the end of the set.
|
||
|
* @param end - A position at which the elements stop being selected. If
|
||
|
* negative, it indicates an offset from the end of the set. If omitted, the
|
||
|
* range continues until the end of the set.
|
||
|
* @returns The elements matching the specified range.
|
||
|
* @see {@link https://api.jquery.com/slice/}
|
||
|
*/
|
||
|
export function slice(start, end) {
|
||
|
return this._make(Array.prototype.slice.call(this, start, end));
|
||
|
}
|
||
|
/**
|
||
|
* End the most recent filtering operation in the current chain and return the
|
||
|
* set of matched elements to its previous state.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('li').eq(0).end().length;
|
||
|
* //=> 3
|
||
|
* ```
|
||
|
*
|
||
|
* @returns The previous state of the set of matched elements.
|
||
|
* @see {@link https://api.jquery.com/end/}
|
||
|
*/
|
||
|
export function end() {
|
||
|
var _a;
|
||
|
return (_a = this.prevObject) !== null && _a !== void 0 ? _a : this._make([]);
|
||
|
}
|
||
|
/**
|
||
|
* Add elements to the set of matched elements.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('.apple').add('.orange').length;
|
||
|
* //=> 2
|
||
|
* ```
|
||
|
*
|
||
|
* @param other - Elements to add.
|
||
|
* @param context - Optionally the context of the new selection.
|
||
|
* @returns The combined set.
|
||
|
* @see {@link https://api.jquery.com/add/}
|
||
|
*/
|
||
|
export function add(other, context) {
|
||
|
const selection = this._make(other, context);
|
||
|
const contents = uniqueSort([...this.get(), ...selection.get()]);
|
||
|
return this._make(contents);
|
||
|
}
|
||
|
/**
|
||
|
* Add the previous set of elements on the stack to the current set, optionally
|
||
|
* filtered by a selector.
|
||
|
*
|
||
|
* @category Traversing
|
||
|
* @example
|
||
|
*
|
||
|
* ```js
|
||
|
* $('li').eq(0).addBack('.orange').length;
|
||
|
* //=> 2
|
||
|
* ```
|
||
|
*
|
||
|
* @param selector - Selector for the elements to add.
|
||
|
* @returns The combined set.
|
||
|
* @see {@link https://api.jquery.com/addBack/}
|
||
|
*/
|
||
|
export function addBack(selector) {
|
||
|
return this.prevObject
|
||
|
? this.add(selector ? this.prevObject.filter(selector) : this.prevObject)
|
||
|
: this;
|
||
|
}
|
||
|
//# sourceMappingURL=traversing.js.map
|