jquery/src/selector-native.js
Michał Gołębiowski-Owczarek 2e644e8450
Selector: Backport jQuery selection context logic to selector-native
This makes:
```js
$div.find("div > *")
```
no longer matching children of `$div`.

Also, leading combinators now work, e.g.:
```js
$div.find( "> *" );
```
returns children of `$div`.

As a result of that, a number of tests are no longer skipped in the
`selector-native` mode.

Also, rename `rcombinators` to `rleadingCombinator`.

Fixes gh-5185
Closes gh-5186
Ref gh-5085
2023-02-13 18:34:41 +01:00

147 lines
4.5 KiB
JavaScript

/*
* Optional limited selector module for custom builds.
*
* Note that this DOES NOT SUPPORT many documented jQuery
* features in exchange for its smaller size:
*
* * Attribute not equal selector (!=)
* * Positional selectors (:first; :eq(n); :odd; etc.)
* * Type selectors (:input; :checkbox; :button; etc.)
* * State-based selectors (:animated; :visible; :hidden; etc.)
* * :has(selector) in browsers without native support
* * :not(complex selector) in IE
* * custom selectors via jQuery extensions
* * Reliable functionality on XML fragments
* * Matching against non-elements
* * Reliable sorting of disconnected nodes
* * querySelectorAll bug fixes (e.g., unreliable :focus on WebKit)
*
* If any of these are unacceptable tradeoffs, either use the full
* selector engine or customize this stub for the project's specific
* needs.
*/
import jQuery from "./core.js";
import document from "./var/document.js";
import whitespace from "./var/whitespace.js";
// The following utils are attached directly to the jQuery object.
import "./selector/escapeSelector.js";
import "./selector/uniqueSort.js";
import isIE from "./var/isIE.js";
import booleans from "./selector/var/booleans.js";
import rleadingCombinator from "./selector/var/rleadingCombinator.js";
import rdescend from "./selector/var/rdescend.js";
import rsibling from "./selector/var/rsibling.js";
import matches from "./selector/var/matches.js";
import testContext from "./selector/testContext.js";
import filterMatchExpr from "./selector/filterMatchExpr.js";
import preFilter from "./selector/preFilter.js";
import tokenize from "./selector/tokenize.js";
import toSelector from "./selector/toSelector.js";
var matchExpr = jQuery.extend( {
bool: new RegExp( "^(?:" + booleans + ")$", "i" ),
needsContext: new RegExp( "^" + whitespace + "*[>+~]" )
}, filterMatchExpr );
jQuery.extend( {
find: function( selector, context, results, seed ) {
var elem, nid, groups, newSelector,
newContext = context && context.ownerDocument,
// nodeType defaults to 9, since context defaults to document
nodeType = context ? context.nodeType : 9,
i = 0;
results = results || [];
context = context || document;
// Same basic safeguard as in the full selector module
if ( !selector || typeof selector !== "string" ) {
return results;
}
// Early return if context is not an element, document or document fragment
if ( nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
return [];
}
if ( seed ) {
while ( ( elem = seed[ i++ ] ) ) {
if ( jQuery.find.matchesSelector( elem, selector ) ) {
results.push( elem );
}
}
} else {
newSelector = selector;
newContext = context;
// qSA considers elements outside a scoping root when evaluating child or
// descendant combinators, which is not what we want.
// In such cases, we work around the behavior by prefixing every selector in the
// list with an ID selector referencing the scope context.
// The technique has to be used as well when a leading combinator is used
// as such selectors are not recognized by querySelectorAll.
// Thanks to Andrew Dupont for this technique.
if ( nodeType === 1 &&
( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) {
// Expand context for sibling selectors
newContext = rsibling.test( selector ) &&
testContext( context.parentNode ) ||
context;
// Outside of IE, if we're not changing the context we can
// use :scope instead of an ID.
if ( newContext !== context || isIE ) {
// Capture the context ID, setting it first if necessary
if ( ( nid = context.getAttribute( "id" ) ) ) {
nid = jQuery.escapeSelector( nid );
} else {
context.setAttribute( "id", ( nid = jQuery.expando ) );
}
}
// Prefix every selector in the list
groups = tokenize( selector );
i = groups.length;
while ( i-- ) {
groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
toSelector( groups[ i ] );
}
newSelector = groups.join( "," );
}
try {
jQuery.merge( results, newContext.querySelectorAll( newSelector ) );
} finally {
if ( nid === jQuery.expando ) {
context.removeAttribute( "id" );
}
}
}
return results;
},
expr: {
// Can be adjusted by the user
cacheLength: 50,
match: matchExpr,
preFilter: preFilter
}
} );
jQuery.extend( jQuery.find, {
matches: function( expr, elements ) {
return jQuery.find( expr, null, null, elements );
},
matchesSelector: function( elem, expr ) {
return matches.call( elem, expr );
}
} );