@@ -1512,10 +1512,11 @@ export class Query {
15121512 return height . length > 1 ? height : height [ 0 ] ;
15131513 }
15141514
1515- naturalDisplay ( ) {
1516- const displays = this . map ( ( el ) => {
1517- // Create cache key from current stylesheet state + inline styles
1518- const stylesheetData = Array . from ( document . styleSheets ) . map ( sheet => {
1515+ naturalDisplay ( { calculate = true } = { } ) {
1516+ // store style hash across map
1517+ let styleHash ;
1518+ if ( calculate ) {
1519+ styleHash = Array . from ( document . styleSheets ) . map ( sheet => {
15191520 try {
15201521 return { href : sheet . href , title : sheet . title , disabled : sheet . disabled } ;
15211522 }
@@ -1524,137 +1525,160 @@ export class Query {
15241525 return { crossOrigin : true , index : Array . from ( document . styleSheets ) . indexOf ( sheet ) } ;
15251526 }
15261527 } ) ;
1528+ }
15271529
1528- // Include inline display style in cache key since it overrides everything
1529- const inlineDisplay = el . style . display ;
1530- const cacheKey = { stylesheetData, inlineDisplay } ;
1531- const rulesHash = hashCode ( cacheKey ) ;
1532-
1533- // Check cache for this element
1534- const cached = Query . elementDisplayCache . get ( el ) ;
1535- if ( cached && cached . rulesHash === rulesHash ) {
1536- return cached . displayValue ;
1537- }
1538-
1539- // If already visible, return current display
1540- const current = getComputedStyle ( el ) . display ;
1541- if ( current !== 'none' ) {
1542- // Cache visible elements too
1543- Query . elementDisplayCache . set ( el , { rulesHash, displayValue : current } ) ;
1544- return current ;
1545- }
1546-
1547- const matchingRules = [ ] ;
1548-
1549- // Calculate CSS specificity (simplified)
1550- const calculateSpecificity = ( selector ) => {
1551- // Remove quoted strings to avoid counting selectors inside quotes
1552- const cleaned = selector . replace ( / " [ ^ " ] * " / g, '' ) . replace ( / ' [ ^ ' ] * ' / g, '' ) ;
1553- const ids = ( cleaned . match ( / # [ \w - ] + / g) || [ ] ) . length * 100 ;
1554- const classes = ( cleaned . match ( / \. [ \w - ] + / g) || [ ] ) . length * 10 ;
1555- const attrs = ( cleaned . match ( / \[ [ ^ \] ] + \] / g) || [ ] ) . length * 10 ;
1556- const pseudoClasses = ( cleaned . match ( / : [ \w - ] + / g) || [ ] ) . length * 10 ;
1557- const elements = ( cleaned . match ( / \b [ a - z ] [ \w - ] * / gi) || [ ] ) . length * 1 ;
1558- return ids + classes + attrs + pseudoClasses + elements ;
1559- } ;
1530+ const displays = this . map ( ( el , index ) => {
1531+ // FAST PATH: if an inline style is applied return this
1532+ let inlineDisplay = el . style . display ;
1533+ if ( inlineDisplay && inlineDisplay !== 'none' ) {
1534+ return inlineDisplay ;
1535+ }
15601536
1561- // Helper to recursively parse CSS rules (handles nesting)
1562- const parseRules = ( rules ) => {
1563- for ( const rule of rules ) {
1564- // Handle regular style rules
1565- if (
1566- rule . style ?. display
1567- && rule . style . display !== 'none' // IGNORE ALL none rules
1568- && rule . selectorText
1569- && el . matches ( rule . selectorText )
1570- ) {
1571- matchingRules . push ( {
1572- display : rule . style . display ,
1573- specificity : calculateSpecificity ( rule . selectorText ) ,
1574- sourceOrder : matchingRules . length ,
1575- } ) ;
1537+ let cacheKey ;
1538+ let rulesHash ;
1539+
1540+ // CALCULATED PATH: check stylesheets for highest specificity display state
1541+ if ( calculate ) {
1542+ // Include inline display style in cache key since it overrides everything
1543+ cacheKey = { styleHash, inlineDisplay } ;
1544+ rulesHash = hashCode ( cacheKey ) ;
1545+
1546+ // FAST PATH: Skip stylesheet check, if rules are same
1547+ const cached = Query . elementDisplayCache . get ( el ) ;
1548+ if ( cached && cached . rulesHash === rulesHash ) {
1549+ return cached . displayValue ;
1550+ }
1551+
1552+ // MEDIUM PATH: If already visible, return current display
1553+ const computedDisplay = getComputedStyle ( el ) . display ;
1554+ if ( computedDisplay !== 'none' ) {
1555+ Query . elementDisplayCache . set ( el , { rulesHash, displayValue : computedDisplay } ) ;
1556+ return computedDisplay ;
1557+ }
1558+
1559+ // SLOW PATH: Start parsing rules to determine display type
1560+ const matchingRules = [ ] ;
1561+
1562+ // Calculate CSS specificity (simplified)
1563+ const calculateSpecificity = ( selector ) => {
1564+ // Remove quoted strings to avoid counting selectors inside quotes
1565+ const cleaned = selector . replace ( / " [ ^ " ] * " / g, '' ) . replace ( / ' [ ^ ' ] * ' / g, '' ) ;
1566+ const ids = ( cleaned . match ( / # [ \w - ] + / g) || [ ] ) . length * 100 ;
1567+ const classes = ( cleaned . match ( / \. [ \w - ] + / g) || [ ] ) . length * 10 ;
1568+ const attrs = ( cleaned . match ( / \[ [ ^ \] ] + \] / g) || [ ] ) . length * 10 ;
1569+ const pseudoClasses = ( cleaned . match ( / : [ \w - ] + / g) || [ ] ) . length * 10 ;
1570+ const elements = ( cleaned . match ( / \b [ a - z ] [ \w - ] * / gi) || [ ] ) . length * 1 ;
1571+ return ids + classes + attrs + pseudoClasses + elements ;
1572+ } ;
1573+
1574+ // Helper to recursively parse CSS rules (handles nesting)
1575+ const parseRules = ( rules , parentSelector = '' ) => {
1576+ for ( const rule of rules ) {
1577+ // Handle regular style rules
1578+ if (
1579+ rule . style ?. display
1580+ && rule . style . display !== 'none' // IGNORE ALL none rules
1581+ && rule . selectorText
1582+ ) {
1583+ // Resolve nested selector by replacing & with parent
1584+ let resolvedSelector = rule . selectorText ;
1585+ if ( parentSelector && resolvedSelector . includes ( '&' ) ) {
1586+ resolvedSelector = resolvedSelector . replace ( / & / g, parentSelector ) ;
1587+ }
1588+
1589+ if ( el . matches ( resolvedSelector ) ) {
1590+ matchingRules . push ( {
1591+ display : rule . style . display ,
1592+ specificity : calculateSpecificity ( resolvedSelector ) ,
1593+ sourceOrder : matchingRules . length ,
1594+ } ) ;
1595+ }
1596+ }
1597+ // Recursively handle nested rules (CSS nesting)
1598+ if ( rule . cssRules && rule . cssRules . length > 0 ) {
1599+ const nestedParent = parentSelector || rule . selectorText ;
1600+ parseRules ( rule . cssRules , nestedParent ) ;
1601+ }
1602+ }
1603+ } ;
1604+
1605+ // Parse all stylesheets for matching rules
1606+ for ( const sheet of document . styleSheets ) {
1607+ try {
1608+ parseRules ( sheet . cssRules ) ;
15761609 }
1577- // Recursively handle nested rules (CSS nesting)
1578- if ( rule . cssRules && rule . cssRules . length > 0 ) {
1579- parseRules ( rule . cssRules ) ;
1610+ catch ( e ) {
1611+ // Cross-origin stylesheets - ignore
15801612 }
15811613 }
1582- } ;
15831614
1584- // Parse all stylesheets for matching rules
1585- for ( const sheet of document . styleSheets ) {
1586- try {
1587- parseRules ( sheet . cssRules ) ;
1588- }
1589- catch ( e ) {
1590- // Cross-origin stylesheets - ignore
1615+ // Sort by specificity, then source order
1616+ matchingRules . sort ( ( a , b ) => b . specificity - a . specificity || b . sourceOrder - a . sourceOrder ) ;
1617+
1618+ // If we have matching rules, return the highest precedence value
1619+ if ( matchingRules . length > 0 ) {
1620+ const displayValue = matchingRules [ 0 ] . display ;
1621+ Query . elementDisplayCache . set ( el , { rulesHash, displayValue } ) ;
1622+ return displayValue ;
15911623 }
15921624 }
15931625
1594- // Sort by specificity, then source order
1595- matchingRules . sort ( ( a , b ) => b . specificity - a . specificity || b . sourceOrder - a . sourceOrder ) ;
1626+ // BACKUP Path: Use natural display type for browsers based off a lookup table
1627+ const naturalDisplay = {
1628+ inline : [
1629+ 'a' ,
1630+ 'abbr' ,
1631+ 'b' ,
1632+ 'bdi' ,
1633+ 'bdo' ,
1634+ 'br' ,
1635+ 'cite' ,
1636+ 'code' ,
1637+ 'dfn' ,
1638+ 'em' ,
1639+ 'i' ,
1640+ 'kbd' ,
1641+ 'mark' ,
1642+ 'q' ,
1643+ 'ruby' ,
1644+ 'samp' ,
1645+ 'small' ,
1646+ 'span' ,
1647+ 'strong' ,
1648+ 'sub' ,
1649+ 'sup' ,
1650+ 'time' ,
1651+ 'u' ,
1652+ 'var' ,
1653+ 'wbr' ,
1654+ ] ,
1655+ 'inline-block' : [ 'button' , 'img' , 'input' , 'meter' , 'object' , 'progress' , 'select' , 'textarea' ] ,
1656+ 'table' : [ 'table' ] ,
1657+ 'table-row' : [ 'tr' ] ,
1658+ 'table-cell' : [ 'td' , 'th' ] ,
1659+ 'table-header-group' : [ 'thead' ] ,
1660+ 'table-row-group' : [ 'tbody' ] ,
1661+ 'table-footer-group' : [ 'tfoot' ] ,
1662+ 'table-caption' : [ 'caption' ] ,
1663+ 'table-column' : [ 'col' ] ,
1664+ 'table-column-group' : [ 'colgroup' ] ,
1665+ 'list-item' : [ 'li' ] ,
1666+ } ;
15961667
1668+ const tagName = el . tagName . toLowerCase ( ) ;
15971669 let displayValue ;
1598-
1599- // If we have matching rules, return the highest precedence value
1600- if ( matchingRules . length > 0 ) {
1601- displayValue = matchingRules [ 0 ] . display ;
1602- }
1603- else {
1604- // No CSS rules found - use element's natural display value
1605- const naturalDisplay = {
1606- inline : [
1607- 'a' ,
1608- 'abbr' ,
1609- 'b' ,
1610- 'bdi' ,
1611- 'bdo' ,
1612- 'br' ,
1613- 'cite' ,
1614- 'code' ,
1615- 'dfn' ,
1616- 'em' ,
1617- 'i' ,
1618- 'kbd' ,
1619- 'mark' ,
1620- 'q' ,
1621- 'ruby' ,
1622- 'samp' ,
1623- 'small' ,
1624- 'span' ,
1625- 'strong' ,
1626- 'sub' ,
1627- 'sup' ,
1628- 'time' ,
1629- 'u' ,
1630- 'var' ,
1631- 'wbr' ,
1632- ] ,
1633- 'inline-block' : [ 'button' , 'img' , 'input' , 'meter' , 'object' , 'progress' , 'select' , 'textarea' ] ,
1634- 'table' : [ 'table' ] ,
1635- 'table-row' : [ 'tr' ] ,
1636- 'table-cell' : [ 'td' , 'th' ] ,
1637- 'table-header-group' : [ 'thead' ] ,
1638- 'table-row-group' : [ 'tbody' ] ,
1639- 'table-footer-group' : [ 'tfoot' ] ,
1640- 'table-caption' : [ 'caption' ] ,
1641- 'table-column' : [ 'col' ] ,
1642- 'table-column-group' : [ 'colgroup' ] ,
1643- 'list-item' : [ 'li' ] ,
1644- } ;
1645-
1646- const tagName = el . tagName . toLowerCase ( ) ;
1647- for ( const [ display , tags ] of Object . entries ( naturalDisplay ) ) {
1648- if ( tags . includes ( tagName ) ) {
1649- displayValue = display ;
1650- break ;
1651- }
1670+ for ( const [ display , tags ] of Object . entries ( naturalDisplay ) ) {
1671+ if ( tags . includes ( tagName ) ) {
1672+ displayValue = display ;
1673+ break ;
16521674 }
1653- displayValue = displayValue || 'block' ; // Default for most elements
16541675 }
1676+ displayValue = displayValue || 'block' ; // Default for most elements
16551677
1656- // Cache the result
1657- Query . elementDisplayCache . set ( el , { rulesHash, displayValue } ) ;
1678+ // Cache the result if we are calculating
1679+ if ( calculate ) {
1680+ Query . elementDisplayCache . set ( el , { rulesHash, displayValue } ) ;
1681+ }
16581682
16591683 return displayValue ;
16601684 } ) ;
@@ -1755,22 +1779,27 @@ export class Query {
17551779 return this . length > 0 ;
17561780 }
17571781
1758- isVisible ( options = { } ) {
1782+ isVisible ( { includeOpacity = false , includeVisibility = true } = { } ) {
17591783 if ( this . length === 0 ) {
17601784 return undefined ;
17611785 }
1762-
1763- const { includeOpacity = false } = options ;
1764-
17651786 // Return true only if ALL elements are visible
17661787 return this . map ( el => {
17671788 const rect = el . getBoundingClientRect ( ) ;
17681789 const hasDimensions = rect . width > 0 && rect . height > 0 ;
17691790
17701791 if ( ! hasDimensions ) { return false ; }
17711792
1793+ const style = window . getComputedStyle ( el ) ;
1794+
1795+ // Check intentional hiding methods
1796+ if ( includeVisibility ) {
1797+ if ( style . visibility === 'hidden' ) { return false ; }
1798+ if ( style . contentVisibility === 'hidden' ) { return false ; }
1799+ }
1800+
1801+ // Check optional hiding mechanisms
17721802 if ( includeOpacity ) {
1773- const style = window . getComputedStyle ( el ) ;
17741803 return parseFloat ( style . opacity ) > 0 ;
17751804 }
17761805
@@ -1876,6 +1905,30 @@ export class Query {
18761905 return this . chain ( combinedElements ) ;
18771906 }
18781907
1908+ show ( { calculate = true } = { } ) {
1909+ return this . each ( function ( el ) {
1910+ const naturalDisplayValue = this . naturalDisplay ( { calculate } ) ;
1911+ el . style . display = naturalDisplayValue || '' ;
1912+ } ) ;
1913+ }
1914+
1915+ hide ( ) {
1916+ return this . css ( 'display' , 'none' ) ;
1917+ }
1918+
1919+ toggle ( { calculate = true } = { } ) {
1920+ return this . each ( function ( el ) {
1921+ const isHidden = getComputedStyle ( el ) . display === 'none' ;
1922+ if ( isHidden ) {
1923+ const naturalDisplayValue = this . naturalDisplay ( { calculate } ) ;
1924+ el . style . display = naturalDisplayValue || '' ;
1925+ }
1926+ else {
1927+ el . style . display = 'none' ;
1928+ }
1929+ } ) ;
1930+ }
1931+
18791932 // special helper for SUI components
18801933 component ( ) {
18811934 const components = this . map ( el => el . component ) . filter ( Boolean ) ;
0 commit comments