traversing.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. /**
  2. * Methods for traversing the DOM structure.
  3. *
  4. * @module cheerio/traversing
  5. */
  6. import { isTag, hasChildren, isDocument, } from 'domhandler';
  7. import * as select from 'cheerio-select';
  8. import { domEach, isCheerio } from '../utils.js';
  9. import { contains } from '../static.js';
  10. import { getChildren, getSiblings, nextElementSibling, prevElementSibling, uniqueSort, } from 'domutils';
  11. const reSiblingSelector = /^\s*[+~]/;
  12. /**
  13. * Get the descendants of each element in the current set of matched elements,
  14. * filtered by a selector, jQuery object, or element.
  15. *
  16. * @category Traversing
  17. * @example
  18. *
  19. * ```js
  20. * $('#fruits').find('li').length;
  21. * //=> 3
  22. * $('#fruits').find($('.apple')).length;
  23. * //=> 1
  24. * ```
  25. *
  26. * @param selectorOrHaystack - Element to look for.
  27. * @returns The found elements.
  28. * @see {@link https://api.jquery.com/find/}
  29. */
  30. export function find(selectorOrHaystack) {
  31. if (!selectorOrHaystack) {
  32. return this._make([]);
  33. }
  34. if (typeof selectorOrHaystack !== 'string') {
  35. const haystack = isCheerio(selectorOrHaystack)
  36. ? selectorOrHaystack.toArray()
  37. : [selectorOrHaystack];
  38. const context = this.toArray();
  39. return this._make(haystack.filter((elem) => context.some((node) => contains(node, elem))));
  40. }
  41. return this._findBySelector(selectorOrHaystack, Number.POSITIVE_INFINITY);
  42. }
  43. /**
  44. * Find elements by a specific selector.
  45. *
  46. * @private
  47. * @category Traversing
  48. * @param selector - Selector to filter by.
  49. * @param limit - Maximum number of elements to match.
  50. * @returns The found elements.
  51. */
  52. export function _findBySelector(selector, limit) {
  53. var _a;
  54. const context = this.toArray();
  55. const elems = reSiblingSelector.test(selector)
  56. ? context
  57. : this.children().toArray();
  58. const options = {
  59. context,
  60. root: (_a = this._root) === null || _a === void 0 ? void 0 : _a[0],
  61. // Pass options that are recognized by `cheerio-select`
  62. xmlMode: this.options.xmlMode,
  63. lowerCaseTags: this.options.lowerCaseTags,
  64. lowerCaseAttributeNames: this.options.lowerCaseAttributeNames,
  65. pseudos: this.options.pseudos,
  66. quirksMode: this.options.quirksMode,
  67. };
  68. return this._make(select.select(selector, elems, options, limit));
  69. }
  70. /**
  71. * Creates a matcher, using a particular mapping function. Matchers provide a
  72. * function that finds elements using a generating function, supporting
  73. * filtering.
  74. *
  75. * @private
  76. * @param matchMap - Mapping function.
  77. * @returns - Function for wrapping generating functions.
  78. */
  79. function _getMatcher(matchMap) {
  80. return function (fn, ...postFns) {
  81. return function (selector) {
  82. var _a;
  83. let matched = matchMap(fn, this);
  84. if (selector) {
  85. matched = filterArray(matched, selector, this.options.xmlMode, (_a = this._root) === null || _a === void 0 ? void 0 : _a[0]);
  86. }
  87. return this._make(
  88. // Post processing is only necessary if there is more than one element.
  89. this.length > 1 && matched.length > 1
  90. ? postFns.reduce((elems, fn) => fn(elems), matched)
  91. : matched);
  92. };
  93. };
  94. }
  95. /** Matcher that adds multiple elements for each entry in the input. */
  96. const _matcher = _getMatcher((fn, elems) => {
  97. let ret = [];
  98. for (let i = 0; i < elems.length; i++) {
  99. const value = fn(elems[i]);
  100. if (value.length > 0)
  101. ret = ret.concat(value);
  102. }
  103. return ret;
  104. });
  105. /** Matcher that adds at most one element for each entry in the input. */
  106. const _singleMatcher = _getMatcher((fn, elems) => {
  107. const ret = [];
  108. for (let i = 0; i < elems.length; i++) {
  109. const value = fn(elems[i]);
  110. if (value !== null) {
  111. ret.push(value);
  112. }
  113. }
  114. return ret;
  115. });
  116. /**
  117. * Matcher that supports traversing until a condition is met.
  118. *
  119. * @param nextElem - Function that returns the next element.
  120. * @param postFns - Post processing functions.
  121. * @returns A function usable for `*Until` methods.
  122. */
  123. function _matchUntil(nextElem, ...postFns) {
  124. // We use a variable here that is used from within the matcher.
  125. let matches = null;
  126. const innerMatcher = _getMatcher((nextElem, elems) => {
  127. const matched = [];
  128. domEach(elems, (elem) => {
  129. for (let next; (next = nextElem(elem)); elem = next) {
  130. // FIXME: `matched` might contain duplicates here and the index is too large.
  131. if (matches === null || matches === void 0 ? void 0 : matches(next, matched.length))
  132. break;
  133. matched.push(next);
  134. }
  135. });
  136. return matched;
  137. })(nextElem, ...postFns);
  138. return function (selector, filterSelector) {
  139. // Override `matches` variable with the new target.
  140. matches =
  141. typeof selector === 'string'
  142. ? (elem) => select.is(elem, selector, this.options)
  143. : selector
  144. ? getFilterFn(selector)
  145. : null;
  146. const ret = innerMatcher.call(this, filterSelector);
  147. // Set `matches` to `null`, so we don't waste memory.
  148. matches = null;
  149. return ret;
  150. };
  151. }
  152. function _removeDuplicates(elems) {
  153. return elems.length > 1 ? Array.from(new Set(elems)) : elems;
  154. }
  155. /**
  156. * Get the parent of each element in the current set of matched elements,
  157. * optionally filtered by a selector.
  158. *
  159. * @category Traversing
  160. * @example
  161. *
  162. * ```js
  163. * $('.pear').parent().attr('id');
  164. * //=> fruits
  165. * ```
  166. *
  167. * @param selector - If specified filter for parent.
  168. * @returns The parents.
  169. * @see {@link https://api.jquery.com/parent/}
  170. */
  171. export const parent = _singleMatcher(({ parent }) => (parent && !isDocument(parent) ? parent : null), _removeDuplicates);
  172. /**
  173. * Get a set of parents filtered by `selector` of each element in the current
  174. * set of match elements.
  175. *
  176. * @category Traversing
  177. * @example
  178. *
  179. * ```js
  180. * $('.orange').parents().length;
  181. * //=> 2
  182. * $('.orange').parents('#fruits').length;
  183. * //=> 1
  184. * ```
  185. *
  186. * @param selector - If specified filter for parents.
  187. * @returns The parents.
  188. * @see {@link https://api.jquery.com/parents/}
  189. */
  190. export const parents = _matcher((elem) => {
  191. const matched = [];
  192. while (elem.parent && !isDocument(elem.parent)) {
  193. matched.push(elem.parent);
  194. elem = elem.parent;
  195. }
  196. return matched;
  197. }, uniqueSort, (elems) => elems.reverse());
  198. /**
  199. * Get the ancestors of each element in the current set of matched elements, up
  200. * to but not including the element matched by the selector, DOM node, or
  201. * cheerio object.
  202. *
  203. * @category Traversing
  204. * @example
  205. *
  206. * ```js
  207. * $('.orange').parentsUntil('#food').length;
  208. * //=> 1
  209. * ```
  210. *
  211. * @param selector - Selector for element to stop at.
  212. * @param filterSelector - Optional filter for parents.
  213. * @returns The parents.
  214. * @see {@link https://api.jquery.com/parentsUntil/}
  215. */
  216. export const parentsUntil = _matchUntil(({ parent }) => (parent && !isDocument(parent) ? parent : null), uniqueSort, (elems) => elems.reverse());
  217. /**
  218. * For each element in the set, get the first element that matches the selector
  219. * by testing the element itself and traversing up through its ancestors in the
  220. * DOM tree.
  221. *
  222. * @category Traversing
  223. * @example
  224. *
  225. * ```js
  226. * $('.orange').closest();
  227. * //=> []
  228. *
  229. * $('.orange').closest('.apple');
  230. * // => []
  231. *
  232. * $('.orange').closest('li');
  233. * //=> [<li class="orange">Orange</li>]
  234. *
  235. * $('.orange').closest('#fruits');
  236. * //=> [<ul id="fruits"> ... </ul>]
  237. * ```
  238. *
  239. * @param selector - Selector for the element to find.
  240. * @returns The closest nodes.
  241. * @see {@link https://api.jquery.com/closest/}
  242. */
  243. export function closest(selector) {
  244. var _a;
  245. const set = [];
  246. if (!selector) {
  247. return this._make(set);
  248. }
  249. const selectOpts = {
  250. xmlMode: this.options.xmlMode,
  251. root: (_a = this._root) === null || _a === void 0 ? void 0 : _a[0],
  252. };
  253. const selectFn = typeof selector === 'string'
  254. ? (elem) => select.is(elem, selector, selectOpts)
  255. : getFilterFn(selector);
  256. domEach(this, (elem) => {
  257. if (elem && !isDocument(elem) && !isTag(elem)) {
  258. elem = elem.parent;
  259. }
  260. while (elem && isTag(elem)) {
  261. if (selectFn(elem, 0)) {
  262. // Do not add duplicate elements to the set
  263. if (!set.includes(elem)) {
  264. set.push(elem);
  265. }
  266. break;
  267. }
  268. elem = elem.parent;
  269. }
  270. });
  271. return this._make(set);
  272. }
  273. /**
  274. * Gets the next sibling of each selected element, optionally filtered by a
  275. * selector.
  276. *
  277. * @category Traversing
  278. * @example
  279. *
  280. * ```js
  281. * $('.apple').next().hasClass('orange');
  282. * //=> true
  283. * ```
  284. *
  285. * @param selector - If specified filter for sibling.
  286. * @returns The next nodes.
  287. * @see {@link https://api.jquery.com/next/}
  288. */
  289. export const next = _singleMatcher((elem) => nextElementSibling(elem));
  290. /**
  291. * Gets all the following siblings of the each selected element, optionally
  292. * filtered by a selector.
  293. *
  294. * @category Traversing
  295. * @example
  296. *
  297. * ```js
  298. * $('.apple').nextAll();
  299. * //=> [<li class="orange">Orange</li>, <li class="pear">Pear</li>]
  300. * $('.apple').nextAll('.orange');
  301. * //=> [<li class="orange">Orange</li>]
  302. * ```
  303. *
  304. * @param selector - If specified filter for siblings.
  305. * @returns The next nodes.
  306. * @see {@link https://api.jquery.com/nextAll/}
  307. */
  308. export const nextAll = _matcher((elem) => {
  309. const matched = [];
  310. while (elem.next) {
  311. elem = elem.next;
  312. if (isTag(elem))
  313. matched.push(elem);
  314. }
  315. return matched;
  316. }, _removeDuplicates);
  317. /**
  318. * Gets all the following siblings up to but not including the element matched
  319. * by the selector, optionally filtered by another selector.
  320. *
  321. * @category Traversing
  322. * @example
  323. *
  324. * ```js
  325. * $('.apple').nextUntil('.pear');
  326. * //=> [<li class="orange">Orange</li>]
  327. * ```
  328. *
  329. * @param selector - Selector for element to stop at.
  330. * @param filterSelector - If specified filter for siblings.
  331. * @returns The next nodes.
  332. * @see {@link https://api.jquery.com/nextUntil/}
  333. */
  334. export const nextUntil = _matchUntil((el) => nextElementSibling(el), _removeDuplicates);
  335. /**
  336. * Gets the previous sibling of each selected element optionally filtered by a
  337. * selector.
  338. *
  339. * @category Traversing
  340. * @example
  341. *
  342. * ```js
  343. * $('.orange').prev().hasClass('apple');
  344. * //=> true
  345. * ```
  346. *
  347. * @param selector - If specified filter for siblings.
  348. * @returns The previous nodes.
  349. * @see {@link https://api.jquery.com/prev/}
  350. */
  351. export const prev = _singleMatcher((elem) => prevElementSibling(elem));
  352. /**
  353. * Gets all the preceding siblings of each selected element, optionally filtered
  354. * by a selector.
  355. *
  356. * @category Traversing
  357. * @example
  358. *
  359. * ```js
  360. * $('.pear').prevAll();
  361. * //=> [<li class="orange">Orange</li>, <li class="apple">Apple</li>]
  362. *
  363. * $('.pear').prevAll('.orange');
  364. * //=> [<li class="orange">Orange</li>]
  365. * ```
  366. *
  367. * @param selector - If specified filter for siblings.
  368. * @returns The previous nodes.
  369. * @see {@link https://api.jquery.com/prevAll/}
  370. */
  371. export const prevAll = _matcher((elem) => {
  372. const matched = [];
  373. while (elem.prev) {
  374. elem = elem.prev;
  375. if (isTag(elem))
  376. matched.push(elem);
  377. }
  378. return matched;
  379. }, _removeDuplicates);
  380. /**
  381. * Gets all the preceding siblings up to but not including the element matched
  382. * by the selector, optionally filtered by another selector.
  383. *
  384. * @category Traversing
  385. * @example
  386. *
  387. * ```js
  388. * $('.pear').prevUntil('.apple');
  389. * //=> [<li class="orange">Orange</li>]
  390. * ```
  391. *
  392. * @param selector - Selector for element to stop at.
  393. * @param filterSelector - If specified filter for siblings.
  394. * @returns The previous nodes.
  395. * @see {@link https://api.jquery.com/prevUntil/}
  396. */
  397. export const prevUntil = _matchUntil((el) => prevElementSibling(el), _removeDuplicates);
  398. /**
  399. * Get the siblings of each element (excluding the element) in the set of
  400. * matched elements, optionally filtered by a selector.
  401. *
  402. * @category Traversing
  403. * @example
  404. *
  405. * ```js
  406. * $('.pear').siblings().length;
  407. * //=> 2
  408. *
  409. * $('.pear').siblings('.orange').length;
  410. * //=> 1
  411. * ```
  412. *
  413. * @param selector - If specified filter for siblings.
  414. * @returns The siblings.
  415. * @see {@link https://api.jquery.com/siblings/}
  416. */
  417. export const siblings = _matcher((elem) => getSiblings(elem).filter((el) => isTag(el) && el !== elem), uniqueSort);
  418. /**
  419. * Gets the element children of each element in the set of matched elements.
  420. *
  421. * @category Traversing
  422. * @example
  423. *
  424. * ```js
  425. * $('#fruits').children().length;
  426. * //=> 3
  427. *
  428. * $('#fruits').children('.pear').text();
  429. * //=> Pear
  430. * ```
  431. *
  432. * @param selector - If specified filter for children.
  433. * @returns The children.
  434. * @see {@link https://api.jquery.com/children/}
  435. */
  436. export const children = _matcher((elem) => getChildren(elem).filter(isTag), _removeDuplicates);
  437. /**
  438. * Gets the children of each element in the set of matched elements, including
  439. * text and comment nodes.
  440. *
  441. * @category Traversing
  442. * @example
  443. *
  444. * ```js
  445. * $('#fruits').contents().length;
  446. * //=> 3
  447. * ```
  448. *
  449. * @returns The children.
  450. * @see {@link https://api.jquery.com/contents/}
  451. */
  452. export function contents() {
  453. const elems = this.toArray().reduce((newElems, elem) => hasChildren(elem) ? newElems.concat(elem.children) : newElems, []);
  454. return this._make(elems);
  455. }
  456. /**
  457. * Iterates over a cheerio object, executing a function for each matched
  458. * element. When the callback is fired, the function is fired in the context of
  459. * the DOM element, so `this` refers to the current element, which is equivalent
  460. * to the function parameter `element`. To break out of the `each` loop early,
  461. * return with `false`.
  462. *
  463. * @category Traversing
  464. * @example
  465. *
  466. * ```js
  467. * const fruits = [];
  468. *
  469. * $('li').each(function (i, elem) {
  470. * fruits[i] = $(this).text();
  471. * });
  472. *
  473. * fruits.join(', ');
  474. * //=> Apple, Orange, Pear
  475. * ```
  476. *
  477. * @param fn - Function to execute.
  478. * @returns The instance itself, useful for chaining.
  479. * @see {@link https://api.jquery.com/each/}
  480. */
  481. export function each(fn) {
  482. let i = 0;
  483. const len = this.length;
  484. while (i < len && fn.call(this[i], i, this[i]) !== false)
  485. ++i;
  486. return this;
  487. }
  488. /**
  489. * Pass each element in the current matched set through a function, producing a
  490. * new Cheerio object containing the return values. The function can return an
  491. * individual data item or an array of data items to be inserted into the
  492. * resulting set. If an array is returned, the elements inside the array are
  493. * inserted into the set. If the function returns null or undefined, no element
  494. * will be inserted.
  495. *
  496. * @category Traversing
  497. * @example
  498. *
  499. * ```js
  500. * $('li')
  501. * .map(function (i, el) {
  502. * // this === el
  503. * return $(this).text();
  504. * })
  505. * .toArray()
  506. * .join(' ');
  507. * //=> "apple orange pear"
  508. * ```
  509. *
  510. * @param fn - Function to execute.
  511. * @returns The mapped elements, wrapped in a Cheerio collection.
  512. * @see {@link https://api.jquery.com/map/}
  513. */
  514. export function map(fn) {
  515. let elems = [];
  516. for (let i = 0; i < this.length; i++) {
  517. const el = this[i];
  518. const val = fn.call(el, i, el);
  519. if (val != null) {
  520. elems = elems.concat(val);
  521. }
  522. }
  523. return this._make(elems);
  524. }
  525. /**
  526. * Creates a function to test if a filter is matched.
  527. *
  528. * @param match - A filter.
  529. * @returns A function that determines if a filter has been matched.
  530. */
  531. function getFilterFn(match) {
  532. if (typeof match === 'function') {
  533. return (el, i) => match.call(el, i, el);
  534. }
  535. if (isCheerio(match)) {
  536. return (el) => Array.prototype.includes.call(match, el);
  537. }
  538. return function (el) {
  539. return match === el;
  540. };
  541. }
  542. export function filter(match) {
  543. var _a;
  544. return this._make(filterArray(this.toArray(), match, this.options.xmlMode, (_a = this._root) === null || _a === void 0 ? void 0 : _a[0]));
  545. }
  546. export function filterArray(nodes, match, xmlMode, root) {
  547. return typeof match === 'string'
  548. ? select.filter(match, nodes, { xmlMode, root })
  549. : nodes.filter(getFilterFn(match));
  550. }
  551. /**
  552. * Checks the current list of elements and returns `true` if _any_ of the
  553. * elements match the selector. If using an element or Cheerio selection,
  554. * returns `true` if _any_ of the elements match. If using a predicate function,
  555. * the function is executed in the context of the selected element, so `this`
  556. * refers to the current element.
  557. *
  558. * @category Traversing
  559. * @param selector - Selector for the selection.
  560. * @returns Whether or not the selector matches an element of the instance.
  561. * @see {@link https://api.jquery.com/is/}
  562. */
  563. export function is(selector) {
  564. const nodes = this.toArray();
  565. return typeof selector === 'string'
  566. ? select.some(nodes.filter(isTag), selector, this.options)
  567. : selector
  568. ? nodes.some(getFilterFn(selector))
  569. : false;
  570. }
  571. /**
  572. * Remove elements from the set of matched elements. Given a Cheerio object that
  573. * represents a set of DOM elements, the `.not()` method constructs a new
  574. * Cheerio object from a subset of the matching elements. The supplied selector
  575. * is tested against each element; the elements that don't match the selector
  576. * will be included in the result.
  577. *
  578. * The `.not()` method can take a function as its argument in the same way that
  579. * `.filter()` does. Elements for which the function returns `true` are excluded
  580. * from the filtered set; all other elements are included.
  581. *
  582. * @category Traversing
  583. * @example <caption>Selector</caption>
  584. *
  585. * ```js
  586. * $('li').not('.apple').length;
  587. * //=> 2
  588. * ```
  589. *
  590. * @example <caption>Function</caption>
  591. *
  592. * ```js
  593. * $('li').not(function (i, el) {
  594. * // this === el
  595. * return $(this).attr('class') === 'orange';
  596. * }).length; //=> 2
  597. * ```
  598. *
  599. * @param match - Value to look for, following the rules above.
  600. * @returns The filtered collection.
  601. * @see {@link https://api.jquery.com/not/}
  602. */
  603. export function not(match) {
  604. let nodes = this.toArray();
  605. if (typeof match === 'string') {
  606. const matches = new Set(select.filter(match, nodes, this.options));
  607. nodes = nodes.filter((el) => !matches.has(el));
  608. }
  609. else {
  610. const filterFn = getFilterFn(match);
  611. nodes = nodes.filter((el, i) => !filterFn(el, i));
  612. }
  613. return this._make(nodes);
  614. }
  615. /**
  616. * Filters the set of matched elements to only those which have the given DOM
  617. * element as a descendant or which have a descendant that matches the given
  618. * selector. Equivalent to `.filter(':has(selector)')`.
  619. *
  620. * @category Traversing
  621. * @example <caption>Selector</caption>
  622. *
  623. * ```js
  624. * $('ul').has('.pear').attr('id');
  625. * //=> fruits
  626. * ```
  627. *
  628. * @example <caption>Element</caption>
  629. *
  630. * ```js
  631. * $('ul').has($('.pear')[0]).attr('id');
  632. * //=> fruits
  633. * ```
  634. *
  635. * @param selectorOrHaystack - Element to look for.
  636. * @returns The filtered collection.
  637. * @see {@link https://api.jquery.com/has/}
  638. */
  639. export function has(selectorOrHaystack) {
  640. return this.filter(typeof selectorOrHaystack === 'string'
  641. ? // Using the `:has` selector here short-circuits searches.
  642. `:has(${selectorOrHaystack})`
  643. : (_, el) => this._make(el).find(selectorOrHaystack).length > 0);
  644. }
  645. /**
  646. * Will select the first element of a cheerio object.
  647. *
  648. * @category Traversing
  649. * @example
  650. *
  651. * ```js
  652. * $('#fruits').children().first().text();
  653. * //=> Apple
  654. * ```
  655. *
  656. * @returns The first element.
  657. * @see {@link https://api.jquery.com/first/}
  658. */
  659. export function first() {
  660. return this.length > 1 ? this._make(this[0]) : this;
  661. }
  662. /**
  663. * Will select the last element of a cheerio object.
  664. *
  665. * @category Traversing
  666. * @example
  667. *
  668. * ```js
  669. * $('#fruits').children().last().text();
  670. * //=> Pear
  671. * ```
  672. *
  673. * @returns The last element.
  674. * @see {@link https://api.jquery.com/last/}
  675. */
  676. export function last() {
  677. return this.length > 0 ? this._make(this[this.length - 1]) : this;
  678. }
  679. /**
  680. * Reduce the set of matched elements to the one at the specified index. Use
  681. * `.eq(-i)` to count backwards from the last selected element.
  682. *
  683. * @category Traversing
  684. * @example
  685. *
  686. * ```js
  687. * $('li').eq(0).text();
  688. * //=> Apple
  689. *
  690. * $('li').eq(-1).text();
  691. * //=> Pear
  692. * ```
  693. *
  694. * @param i - Index of the element to select.
  695. * @returns The element at the `i`th position.
  696. * @see {@link https://api.jquery.com/eq/}
  697. */
  698. export function eq(i) {
  699. var _a;
  700. i = +i;
  701. // Use the first identity optimization if possible
  702. if (i === 0 && this.length <= 1)
  703. return this;
  704. if (i < 0)
  705. i = this.length + i;
  706. return this._make((_a = this[i]) !== null && _a !== void 0 ? _a : []);
  707. }
  708. export function get(i) {
  709. if (i == null) {
  710. return this.toArray();
  711. }
  712. return this[i < 0 ? this.length + i : i];
  713. }
  714. /**
  715. * Retrieve all the DOM elements contained in the jQuery set as an array.
  716. *
  717. * @example
  718. *
  719. * ```js
  720. * $('li').toArray();
  721. * //=> [ {...}, {...}, {...} ]
  722. * ```
  723. *
  724. * @returns The contained items.
  725. */
  726. export function toArray() {
  727. return Array.prototype.slice.call(this);
  728. }
  729. /**
  730. * Search for a given element from among the matched elements.
  731. *
  732. * @category Traversing
  733. * @example
  734. *
  735. * ```js
  736. * $('.pear').index();
  737. * //=> 2 $('.orange').index('li');
  738. * //=> 1
  739. * $('.apple').index($('#fruit, li'));
  740. * //=> 1
  741. * ```
  742. *
  743. * @param selectorOrNeedle - Element to look for.
  744. * @returns The index of the element.
  745. * @see {@link https://api.jquery.com/index/}
  746. */
  747. export function index(selectorOrNeedle) {
  748. let $haystack;
  749. let needle;
  750. if (selectorOrNeedle == null) {
  751. $haystack = this.parent().children();
  752. needle = this[0];
  753. }
  754. else if (typeof selectorOrNeedle === 'string') {
  755. $haystack = this._make(selectorOrNeedle);
  756. needle = this[0];
  757. }
  758. else {
  759. // eslint-disable-next-line @typescript-eslint/no-this-alias, unicorn/no-this-assignment
  760. $haystack = this;
  761. needle = isCheerio(selectorOrNeedle)
  762. ? selectorOrNeedle[0]
  763. : selectorOrNeedle;
  764. }
  765. return Array.prototype.indexOf.call($haystack, needle);
  766. }
  767. /**
  768. * Gets the elements matching the specified range (0-based position).
  769. *
  770. * @category Traversing
  771. * @example
  772. *
  773. * ```js
  774. * $('li').slice(1).eq(0).text();
  775. * //=> 'Orange'
  776. *
  777. * $('li').slice(1, 2).length;
  778. * //=> 1
  779. * ```
  780. *
  781. * @param start - A position at which the elements begin to be selected. If
  782. * negative, it indicates an offset from the end of the set.
  783. * @param end - A position at which the elements stop being selected. If
  784. * negative, it indicates an offset from the end of the set. If omitted, the
  785. * range continues until the end of the set.
  786. * @returns The elements matching the specified range.
  787. * @see {@link https://api.jquery.com/slice/}
  788. */
  789. export function slice(start, end) {
  790. return this._make(Array.prototype.slice.call(this, start, end));
  791. }
  792. /**
  793. * End the most recent filtering operation in the current chain and return the
  794. * set of matched elements to its previous state.
  795. *
  796. * @category Traversing
  797. * @example
  798. *
  799. * ```js
  800. * $('li').eq(0).end().length;
  801. * //=> 3
  802. * ```
  803. *
  804. * @returns The previous state of the set of matched elements.
  805. * @see {@link https://api.jquery.com/end/}
  806. */
  807. export function end() {
  808. var _a;
  809. return (_a = this.prevObject) !== null && _a !== void 0 ? _a : this._make([]);
  810. }
  811. /**
  812. * Add elements to the set of matched elements.
  813. *
  814. * @category Traversing
  815. * @example
  816. *
  817. * ```js
  818. * $('.apple').add('.orange').length;
  819. * //=> 2
  820. * ```
  821. *
  822. * @param other - Elements to add.
  823. * @param context - Optionally the context of the new selection.
  824. * @returns The combined set.
  825. * @see {@link https://api.jquery.com/add/}
  826. */
  827. export function add(other, context) {
  828. const selection = this._make(other, context);
  829. const contents = uniqueSort([...this.get(), ...selection.get()]);
  830. return this._make(contents);
  831. }
  832. /**
  833. * Add the previous set of elements on the stack to the current set, optionally
  834. * filtered by a selector.
  835. *
  836. * @category Traversing
  837. * @example
  838. *
  839. * ```js
  840. * $('li').eq(0).addBack('.orange').length;
  841. * //=> 2
  842. * ```
  843. *
  844. * @param selector - Selector for the elements to add.
  845. * @returns The combined set.
  846. * @see {@link https://api.jquery.com/addBack/}
  847. */
  848. export function addBack(selector) {
  849. return this.prevObject
  850. ? this.add(selector ? this.prevObject.filter(selector) : this.prevObject)
  851. : this;
  852. }
  853. //# sourceMappingURL=traversing.js.map