Skip to content

Commit caa52c6

Browse files
committed
add checkVisibility
1 parent 7d751a8 commit caa52c6

File tree

4 files changed

+161
-0
lines changed

4 files changed

+161
-0
lines changed

docs/index.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,20 @@ <h1>GitHub Feature Support Table</h1>
554554
<td data-supported="true"><div>78+</div></td>
555555
<td data-supported="true"><div>16.0+</div></td>
556556
</tr>
557+
<tr>
558+
<th>
559+
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility">
560+
<code>Element.checkVisibility</code>
561+
</a>
562+
</th>
563+
<td data-polyfill="elementCheckVisibility"><div>*</div></td>
564+
<td data-supported="true"><div>105+</div></td>
565+
<td data-supported="true"><div>105+</div></td>
566+
<td data-supported="true"><div>106+</div></td>
567+
<td data-supported="false"><div>*</div></td>
568+
<td data-supported="true"><div>91+</div></td>
569+
<td data-supported="true"><div>20.0+</div></td>
570+
</tr>
557571
<tr>
558572
<th>
559573
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceChildren">

src/element-checkvisibility.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
declare global {
2+
interface Element {
3+
checkVisibility(options?: Partial<CheckVisibilityOptions>): boolean
4+
}
5+
}
6+
7+
interface CheckVisibilityOptions {
8+
checkOpacity: boolean
9+
checkVisibilityCSS: boolean
10+
}
11+
12+
export function checkVisibility(
13+
this: Element,
14+
{checkOpacity = false, checkVisibilityCSS = false}: Partial<CheckVisibilityOptions> = {}
15+
) {
16+
if (!this.isConnected) return false
17+
const styles = getComputedStyle(this)
18+
if (styles.getPropertyValue('display') === 'contents') return false
19+
if (checkVisibilityCSS && styles.getPropertyValue('visibility') !== 'visible') return false
20+
// eslint-disable-next-line @typescript-eslint/no-this-alias
21+
let node: Element | null = this
22+
while (node) {
23+
const nodeStyles = node === this ? styles : getComputedStyle(node)
24+
if (nodeStyles.getPropertyValue('display') === 'none') return false
25+
if (checkOpacity && nodeStyles.getPropertyValue('opacity') === '0') return false
26+
if (node !== this && nodeStyles.getPropertyValue('content-visibility') === 'hidden') {
27+
return false
28+
}
29+
if (!node.parentElement && node.getRootNode() instanceof ShadowRoot) {
30+
node = (node.getRootNode() as ShadowRoot).host
31+
} else {
32+
node = node.parentElement
33+
}
34+
}
35+
return true
36+
}
37+
38+
export function isSupported(): boolean {
39+
return 'checkVisibility' in Element.prototype && typeof Element.prototype.checkVisibility === 'function'
40+
}
41+
42+
export function isPolyfilled(): boolean {
43+
return Element.prototype.checkVisibility === checkVisibility
44+
}
45+
46+
export function apply(): void {
47+
if (!isSupported()) {
48+
Element.prototype.checkVisibility = checkVisibility
49+
}
50+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as arrayAt from './arraylike-at.js'
55
import * as clipboardItem from './clipboarditem.js'
66
import * as cryptoRandomUUID from './crypto-randomuuid.js'
77
import * as elementReplaceChildren from './element-replacechildren.js'
8+
import * as elementCheckVisibility from './element-checkvisibility.js'
89
import * as eventAbortSignal from './event-abortsignal.js'
910
import * as navigatorClipboard from './navigator-clipboard.js'
1011
import * as formRequestSubmit from './form-requestsubmit.js'
@@ -62,6 +63,7 @@ export const polyfills = {
6263
arrayAt,
6364
clipboardItem,
6465
cryptoRandomUUID,
66+
elementCheckVisibility,
6567
elementReplaceChildren,
6668
eventAbortSignal,
6769
navigatorClipboard,

test/element-checkvisibility.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import {apply, isPolyfilled, isSupported, checkVisibility} from '../lib/element-checkvisibility.js'
2+
3+
describe('checkVisibility', () => {
4+
it('has standard isSupported, isPolyfilled, apply API', () => {
5+
expect(isSupported).to.be.a('function')
6+
expect(isPolyfilled).to.be.a('function')
7+
expect(apply).to.be.a('function')
8+
expect(isSupported()).to.be.a('boolean')
9+
expect(isPolyfilled()).to.equal(false)
10+
})
11+
12+
it.only('checks visibility of elements', async () => {
13+
// These tests originate from
14+
// https://github.com/web-platform-tests/wpt/blob/master/css/cssom-view/checkVisibility.html
15+
const el = document.createElement('div')
16+
// eslint-disable-next-line github/no-inner-html
17+
el.innerHTML = `
18+
<div id=visibilityhidden style="visibility:hidden">hello</div>
19+
<div style="content-visibility:hidden">
20+
<div id=cvhidden>hello</div>
21+
</div>
22+
<div style="content-visibility:auto">
23+
<div id=cvauto>hello</div>
24+
</div>
25+
<div id=displaynone style="display:none">hello</div>
26+
<div style="display:none" class="shadow-host-with-slot">
27+
<div id="slottedindisplaynone" slot="slot">slotted</div>
28+
</div>
29+
<div id=displaycontents style="display:contents">
30+
<div id=displaycontentschild>hello</div>
31+
</div>
32+
<div id=opacityzero style="opacity:0">hello</div>
33+
<div style="opacity:0" class="shadow-host-with-slot">
34+
<div id="slottedinopacityzero" slot="slot">slotted</div>
35+
</div>
36+
<div style="content-visibility:hidden">
37+
<div id=cvhiddenchildwithupdate></div>
38+
</div>
39+
<div style="content-visibility:hidden" id=cvhiddenwithupdate></div>
40+
<div style="content-visibility:hidden" class="shadow-host-with-slot">
41+
<div id="slottedincvhidden" slot="slot">slotted</div>
42+
</div>
43+
<div style="height:10000px">spacer</div>
44+
<div style="content-visibility:auto">
45+
<div id=cvautooffscreen>hello</div>
46+
</div>
47+
<div id=cvautocontainer>
48+
<div id=cvautochild></div>
49+
</div>
50+
<div style="content-visibility:auto">
51+
<div style="content-visibility:auto">
52+
<div id=nestedcvautochild></div>
53+
</div>
54+
`
55+
document.body.append(el)
56+
for (const host of document.querySelectorAll('.shadow-host-with-slot')) {
57+
const shadowRoot = host.attachShadow({mode: 'open'})
58+
const slot = document.createElement('slot')
59+
slot.name = 'slot'
60+
shadowRoot.appendChild(slot)
61+
}
62+
expect(checkVisibility.call(document.getElementById('visibilityhidden'), {checkVisibilityCSS: true})).to.equal(
63+
false
64+
)
65+
expect(checkVisibility.call(document.getElementById('visibilityhidden'), {checkVisibilityCSS: false})).to.equal(
66+
true
67+
)
68+
expect(checkVisibility.call(document.getElementById('cvhidden'))).to.equal(false)
69+
expect(checkVisibility.call(document.getElementById('slottedincvhidden'))).to.equal(false)
70+
expect(checkVisibility.call(document.getElementById('cvauto'))).to.equal(true)
71+
expect(checkVisibility.call(document.getElementById('cvautooffscreen'))).to.equal(true)
72+
expect(checkVisibility.call(document.getElementById('displaynone'))).to.equal(false)
73+
expect(checkVisibility.call(document.getElementById('slottedindisplaynone'))).to.equal(false)
74+
expect(checkVisibility.call(document.getElementById('displaycontents'))).to.equal(false)
75+
expect(checkVisibility.call(document.getElementById('displaycontentschild'))).to.equal(true)
76+
expect(checkVisibility.call(document.getElementById('opacityzero'), {checkOpacity: true})).to.equal(false)
77+
expect(checkVisibility.call(document.getElementById('opacityzero'), {checkOpacity: false})).to.equal(true)
78+
expect(checkVisibility.call(document.getElementById('slottedinopacityzero'), {checkOpacity: true})).to.equal(false)
79+
expect(checkVisibility.call(document.getElementById('slottedinopacityzero'), {checkOpacity: false})).to.equal(true)
80+
const cvautocontainer = document.getElementById('cvautocontainer')
81+
const cvautochild = document.getElementById('cvautochild')
82+
cvautocontainer.style.contentVisibility = 'auto'
83+
cvautochild.style.visibility = 'hidden'
84+
expect(checkVisibility.call(cvautochild, {checkVisibilityCSS: true})).to.equal(false)
85+
cvautochild.style.visibility = 'visible'
86+
expect(checkVisibility.call(cvautochild, {checkVisibilityCSS: true})).to.equal(true)
87+
expect(checkVisibility.call(document.getElementById('nestedcvautochild'))).to.equal(true)
88+
const cvhiddenchildwithupdate = document.getElementById('cvhiddenchildwithupdate')
89+
cvhiddenchildwithupdate.getBoundingClientRect()
90+
expect(checkVisibility.call(cvhiddenchildwithupdate)).to.equal(false)
91+
const cvhiddenwithupdate = document.getElementById('cvhiddenwithupdate')
92+
cvhiddenwithupdate.getBoundingClientRect()
93+
expect(checkVisibility.call(cvhiddenwithupdate)).to.equal(true)
94+
})
95+
})

0 commit comments

Comments
 (0)