From 43d0a613761078de07f9f16110586f89b52c788f Mon Sep 17 00:00:00 2001 From: Sana Javed Date: Tue, 7 Mar 2023 12:15:41 +0100 Subject: [PATCH 1/7] Adding support for top-layer --- src/validate.ts | 36 +++- tests/e2e/validate.test.ts | 338 +++++++++++++++++++++++++++++++++++++ 2 files changed, 366 insertions(+), 8 deletions(-) diff --git a/src/validate.ts b/src/validate.ts index fcd960f2..9a2997df 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -42,21 +42,42 @@ function isAbsolutelyPositioned(el?: HTMLElement | null) { ); } +function isTopLayer(el?: HTMLElement | null) { + return Boolean(el && getComputedStyle(el, ':backdrop').position === 'fixed'); +} + +function isTargetPreceedingAnchor(target: HTMLElement, anchor: HTMLElement) { + // const topLayerSet = [] // TODO add/remove based on calls to `showPopover` or `showDialog` and `close()` + if (isFixedPositioned(target) && hasStyle(anchor, 'position', 'absolute')) { + return true; + } + return false; +} + // Validates that anchor element is a valid anchor for given target element export async function isValidAnchorElement( anchor: HTMLElement, target: HTMLElement, ) { + if (isTopLayer(anchor) && isTopLayer(target)) { + if (isTargetPreceedingAnchor(target, anchor)) { + return false; + } + return true; + } + const anchorContainingBlock = await platform.getOffsetParent?.(anchor); const targetContainingBlock = await platform.getOffsetParent?.(target); // If el has the same containing block as the querying element, // el must not be absolutely positioned. - if ( - isAbsolutelyPositioned(anchor) && - anchorContainingBlock === targetContainingBlock - ) { - return false; + if (isAbsolutelyPositioned(anchor)) { + if (isTopLayer(target)) { + return true; + } + if (anchorContainingBlock === targetContainingBlock) { + return false; + } } // If el has a different containing block from the querying element, @@ -109,9 +130,8 @@ export async function validatedForPositioning( ) { if ( !( - targetEl instanceof HTMLElement && - anchorSelectors.length && - isAbsolutelyPositioned(targetEl) + (targetEl instanceof HTMLElement && anchorSelectors.length) //&& + //isAbsolutelyPositioned(targetEl) // TODO ) ) { return null; diff --git a/tests/e2e/validate.test.ts b/tests/e2e/validate.test.ts index 7cc33b21..c343ada9 100644 --- a/tests/e2e/validate.test.ts +++ b/tests/e2e/validate.test.ts @@ -510,3 +510,341 @@ test('target anchor element is first element el in tree order.', async ({ expect(validationResults.results.anchor).toBeTruthy; expect(validationResults.anchorWidth).toBe('9px'); }); + +test('top layer - valid - absolutely positioned top-layer anchor with top-layer target - WPT anchor-position-top-layer-003', async ({ + browser, +}) => { + // HTML from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-position-top-layer-003.html + const page = await buildPage(browser); + await page.setContent( + ` + + + + + `, + { waitUntil: 'load' }, + ); + const valid = await callValidFunction(page); + + await page.close(); + expect(valid).toBe(true); +}); + +test('top layer - valid - fixed position top-layer anchor with top-layer target - WPT anchor-position-top-layer-004', async ({ + browser, +}) => { + // HTML from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-position-top-layer-004.html + const page = await buildPage(browser); + await page.setContent( + ` + + + + + `, + { waitUntil: 'load' }, + ); + const valid = await callValidFunction(page); + + await page.close(); + expect(valid).toBe(true); +}); + +test('top layer - valid - absolultely positioned non-top-layer anchor with top-layer target - WPT anchor-position-top-layer-001', async ({ + browser, +}) => { + // HTML from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-position-top-layer-001.html + const page = await buildPage(browser); + await page.setContent( + ` + + +
+ + `, + { waitUntil: 'load' }, + ); + const valid = await callValidFunction(page); + + await page.close(); + expect(valid).toBe(true); +}); + +test('top layer - valid - fixed positioned non-top-layer anchor with top-layer target - WPT anchor-position-top-layer-002', async ({ + browser, +}) => { + // HTML from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-position-top-layer-002.html + const page = await buildPage(browser); + await page.setContent( + ` + + +
+ + `, + { waitUntil: 'load' }, + ); + const valid = await callValidFunction(page); + + await page.close(); + expect(valid).toBe(true); +}); + +test('top layer - invalid - top-layer anchor with non-top-layer target - WPT anchor-position-top-layer-005', async ({ + browser, +}) => { + // HTML from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-position-top-layer-005.html + const page = await buildPage(browser); + await page.setContent( + ` + + + +
+ `, + { waitUntil: 'load' }, + ); + const valid = await callValidFunction(page); + + await page.close(); + expect(valid).toBe(false); +}); + +test('top layer - invalid - succeeding top-layer anchor with top-layer target - WPT anchor-position-top-layer-006', async ({ + browser, +}) => { + // HTML from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-position-top-layer-006.html + const page = await buildPage(browser); + await page.setContent( + ` + + + + + `, + { waitUntil: 'load' }, + ); + const valid = await callValidFunction(page); + + await page.close(); + expect(valid).toBe(false); +}); From 5915cfed80db4d57efe749259e1579caa97a0273 Mon Sep 17 00:00:00 2001 From: Sana Javed Date: Wed, 8 Mar 2023 11:47:52 +0100 Subject: [PATCH 2/7] Alternative approach to isTopLayer --- src/validate.ts | 35 +++++++----- tests/e2e/validate.test.ts | 112 ++++++++++++++++++------------------- 2 files changed, 77 insertions(+), 70 deletions(-) diff --git a/src/validate.ts b/src/validate.ts index 9a2997df..230560a9 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -42,27 +42,33 @@ function isAbsolutelyPositioned(el?: HTMLElement | null) { ); } -function isTopLayer(el?: HTMLElement | null) { - return Boolean(el && getComputedStyle(el, ':backdrop').position === 'fixed'); -} +function isTopLayer(el: HTMLElement) { + // below only works for chromium, FF / Webkit return 'fixed' for non-top layer elements as well + // return Boolean(getComputedStyle(el, '::backdrop').position === 'fixed'); -function isTargetPreceedingAnchor(target: HTMLElement, anchor: HTMLElement) { - // const topLayerSet = [] // TODO add/remove based on calls to `showPopover` or `showDialog` and `close()` - if (isFixedPositioned(target) && hasStyle(anchor, 'position', 'absolute')) { - return true; - } - return false; + // alternative approach - check for the specific top layer element types: "Currently, the top layer elements are: popovers, modal dialogs, and elements in a fullscreen mode." + return Boolean( + el.nodeName === 'DIALOG' || el.attributes.hasOwnProperty('popover'), + ); } +// function isTargetPreceedingAnchor(target: HTMLElement, anchor: HTMLElement) { +// // TODO keep track of top layer order +// if (isFixedPositioned(target) && hasStyle(anchor, 'position', 'absolute')) { +// return true; +// } +// return false; +// } + // Validates that anchor element is a valid anchor for given target element export async function isValidAnchorElement( anchor: HTMLElement, target: HTMLElement, ) { if (isTopLayer(anchor) && isTopLayer(target)) { - if (isTargetPreceedingAnchor(target, anchor)) { - return false; - } + // if (isTargetPreceedingAnchor(target, anchor)) { + // return false; + // } return true; } @@ -130,8 +136,9 @@ export async function validatedForPositioning( ) { if ( !( - (targetEl instanceof HTMLElement && anchorSelectors.length) //&& - //isAbsolutelyPositioned(targetEl) // TODO + targetEl instanceof HTMLElement && + anchorSelectors.length && + isAbsolutelyPositioned(targetEl) ) ) { return null; diff --git a/tests/e2e/validate.test.ts b/tests/e2e/validate.test.ts index c343ada9..aaa7d9b0 100644 --- a/tests/e2e/validate.test.ts +++ b/tests/e2e/validate.test.ts @@ -792,59 +792,59 @@ test('top layer - invalid - top-layer anchor with non-top-layer target - WPT anc expect(valid).toBe(false); }); -test('top layer - invalid - succeeding top-layer anchor with top-layer target - WPT anchor-position-top-layer-006', async ({ - browser, -}) => { - // HTML from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-position-top-layer-006.html - const page = await buildPage(browser); - await page.setContent( - ` - - - - - `, - { waitUntil: 'load' }, - ); - const valid = await callValidFunction(page); - - await page.close(); - expect(valid).toBe(false); -}); +// test('top layer - invalid - succeeding top-layer anchor with top-layer target - WPT anchor-position-top-layer-006', async ({ +// browser, +// }) => { +// // HTML from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-position-top-layer-006.html +// const page = await buildPage(browser); +// await page.setContent( +// ` +// + +// +// +// `, +// { waitUntil: 'load' }, +// ); +// const valid = await callValidFunction(page); + +// await page.close(); +// expect(valid).toBe(false); +// }); From 12d6c389ad4e1c81833ad4792ec761f710c36138 Mon Sep 17 00:00:00 2001 From: Sana Javed Date: Wed, 8 Mar 2023 11:53:17 +0100 Subject: [PATCH 3/7] Updated property check for popovers --- src/validate.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/validate.ts b/src/validate.ts index 230560a9..c133019c 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -48,7 +48,8 @@ function isTopLayer(el: HTMLElement) { // alternative approach - check for the specific top layer element types: "Currently, the top layer elements are: popovers, modal dialogs, and elements in a fullscreen mode." return Boolean( - el.nodeName === 'DIALOG' || el.attributes.hasOwnProperty('popover'), + el.nodeName === 'DIALOG' || + Object.prototype.hasOwnProperty.call(el.attributes, 'popover'), ); } From 9b8c544df501722e73ee59e79d1d9dde83631076 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Thu, 9 Mar 2023 12:10:15 -0500 Subject: [PATCH 4/7] Add demo for popover anchoring --- index.html | 45 +++++++++++++++++++++++++++++++++++++++ public/anchor-popover.css | 12 +++++++++++ 2 files changed, 57 insertions(+) create mode 100644 public/anchor-popover.css diff --git a/index.html b/index.html index 2cd9d3df..849a221e 100644 --- a/index.html +++ b/index.html @@ -10,9 +10,20 @@ rel="stylesheet" href="https://unpkg.com/prismjs@v1.x/themes/prism.css" /> + + + @@ -283,6 +294,40 @@

position: absolute; right: anchor(left); bottom: anchor(top); +} + +
+

+ + Anchoring a popover +

+
+
+ Popover (Target) +
+ +
+

+ With polyfill applied: Target is positioned at the bottom right corner + of the Anchor. +

+
#my-anchor-popover {
+  position: absolute;
+  left: 200px;
+  anchor-name: --my-anchor-popover;
+}
+
+#my-target-popover {
+  position: absolute;
+  left: anchor(--my-anchor-popover right);
+  top: anchor(--my-anchor-popover bottom);
 }
diff --git a/public/anchor-popover.css b/public/anchor-popover.css new file mode 100644 index 00000000..fc493828 --- /dev/null +++ b/public/anchor-popover.css @@ -0,0 +1,12 @@ +#my-anchor-popover { + position: absolute; + left: 200px; + anchor-name: --my-anchor-popover; +} + +#my-target-popover { + position: absolute; + margin: 0; + left: anchor(--my-anchor-popover right); + top: anchor(--my-anchor-popover bottom); +} From 8b33f3b5acbc9ac8edc5e851a30a894949438e2a Mon Sep 17 00:00:00 2001 From: Sana Javed Date: Mon, 27 Mar 2023 14:11:13 +0200 Subject: [PATCH 5/7] Updating query for top layer elements --- src/validate.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/validate.ts b/src/validate.ts index c133019c..4017a72f 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -43,14 +43,9 @@ function isAbsolutelyPositioned(el?: HTMLElement | null) { } function isTopLayer(el: HTMLElement) { - // below only works for chromium, FF / Webkit return 'fixed' for non-top layer elements as well - // return Boolean(getComputedStyle(el, '::backdrop').position === 'fixed'); - - // alternative approach - check for the specific top layer element types: "Currently, the top layer elements are: popovers, modal dialogs, and elements in a fullscreen mode." - return Boolean( - el.nodeName === 'DIALOG' || - Object.prototype.hasOwnProperty.call(el.attributes, 'popover'), - ); + // check for the specific top layer element types: "Currently, the top layer elements are: popovers, modal dialogs, and elements in a fullscreen mode." + const topLayerElements = document.querySelectorAll('dialog, [popover]'); + return Boolean(Array.from(topLayerElements).includes(el)); } // function isTargetPreceedingAnchor(target: HTMLElement, anchor: HTMLElement) { From 97047a2e13178ff1b832fb30e6c80c41790bfb79 Mon Sep 17 00:00:00 2001 From: Sana Javed Date: Mon, 27 Mar 2023 14:30:36 +0200 Subject: [PATCH 6/7] Updating README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0eeeacd8..807e6797 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ option can be set by setting the value of This polyfill doesn't (yet) support the following: -- top layer anchor elements +- Tracking the order of elements in the [top-layer](https://fullscreen.spec.whatwg.org/#new-stacking-layer) to invalidate top-layer target elements from anchoring to succeding top-layer anchors. See [this WPT](https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-position-top-layer-006.html) for an example. - `anchor-default` property - `anchor-scroll` property - anchor functions with `implicit` anchor-element From 46b3b0ab0ec994f47b2bc27cf7bdaa78de3c423b Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Mon, 27 Mar 2023 11:13:08 -0400 Subject: [PATCH 7/7] Add comments and fix popover demo --- README.md | 7 ++++++- index.html | 6 +++--- src/validate.ts | 19 +++++++++---------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 807e6797..16b5b2e8 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,6 @@ option can be set by setting the value of This polyfill doesn't (yet) support the following: -- Tracking the order of elements in the [top-layer](https://fullscreen.spec.whatwg.org/#new-stacking-layer) to invalidate top-layer target elements from anchoring to succeding top-layer anchors. See [this WPT](https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-position-top-layer-006.html) for an example. - `anchor-default` property - `anchor-scroll` property - anchor functions with `implicit` anchor-element @@ -70,6 +69,12 @@ This polyfill doesn't (yet) support the following: anchor-side - dynamically added/removed anchors or targets - anchors or targets in the shadow-dom +- tracking the order of elements in the + [top-layer](https://fullscreen.spec.whatwg.org/#new-stacking-layer) to + invalidate top-layer target elements from anchoring to succeeding top-layer + anchors. See [this + WPT](https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-position-top-layer-006.html) + for an example. - anchor functions assigned to `inset-*` properties or `inset` shorthand property - vertical/rtl writing-modes (partial support) diff --git a/index.html b/index.html index 849a221e..0759f3d7 100644 --- a/index.html +++ b/index.html @@ -12,11 +12,11 @@ /> @@ -308,7 +308,7 @@

diff --git a/src/validate.ts b/src/validate.ts index 4017a72f..0a639919 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -43,26 +43,25 @@ function isAbsolutelyPositioned(el?: HTMLElement | null) { } function isTopLayer(el: HTMLElement) { - // check for the specific top layer element types: "Currently, the top layer elements are: popovers, modal dialogs, and elements in a fullscreen mode." + // check for the specific top layer element types... + // Currently, the top layer elements are: + // popovers, modal dialogs, and elements in a fullscreen mode. + // See https://developer.chrome.com/blog/top-layer-devtools/#what-are-the-top-layer-and-top-layer-elements + // TODO: + // - only check for "open" popovers + // - add support for fullscreen elements const topLayerElements = document.querySelectorAll('dialog, [popover]'); return Boolean(Array.from(topLayerElements).includes(el)); } -// function isTargetPreceedingAnchor(target: HTMLElement, anchor: HTMLElement) { -// // TODO keep track of top layer order -// if (isFixedPositioned(target) && hasStyle(anchor, 'position', 'absolute')) { -// return true; -// } -// return false; -// } - // Validates that anchor element is a valid anchor for given target element export async function isValidAnchorElement( anchor: HTMLElement, target: HTMLElement, ) { if (isTopLayer(anchor) && isTopLayer(target)) { - // if (isTargetPreceedingAnchor(target, anchor)) { + // TODO: keep track of top layer order + // if (isTargetPrecedingAnchor(target, anchor)) { // return false; // } return true;