Skip to content

Commit

Permalink
fix(toolkit/observable): position document root inside CSS layer for …
Browse files Browse the repository at this point in the history
…lower priority

CSS layers have lower priority than normal CSS declarations, and the layer name indicates the styling originates from `@scion/toolkit`.
  • Loading branch information
danielwiehl committed Oct 9, 2024
1 parent c1484af commit 31c5dd5
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import {fromBoundingClientRect$} from './bounding-client-rect.observable';
import {ObserveCaptor} from '@scion/toolkit/testing';
import {Arrays} from '@scion/toolkit/util';

const destroyAfterEach = true;
const disposables = new Array<() => void>();
Expand Down Expand Up @@ -452,6 +453,52 @@ describe('fromBoundingClientRect$', () => {
expect(emitCaptor.getLastValue()).toEqual(jasmine.objectContaining({x: x2, y: y2 - 20, width: 100, height: 100}));
});

it('should position document root element (html)', async () => {
// Precondition: Ensure document root not to be positioned so its position will be changed to 'relative'.
document.documentElement.style.setProperty('position', 'static');

const subscription = fromBoundingClientRect$(document.body).subscribe();
onDestroy(() => subscription.unsubscribe());

// Remove style from precondition.
document.documentElement.style.removeProperty('position');

// Expect document root to be positioned.
expect(getComputedStyle(document.documentElement)).toEqual(jasmine.objectContaining({
position: 'relative',
}));
});

it('should allow overriding positioning of document root element (html)', async () => {
// Precondition: Ensure document root not to be positioned so its position will be changed to 'relative'.
document.documentElement.style.setProperty('position', 'static');

const subscription = fromBoundingClientRect$(document.body).subscribe();
onDestroy(() => subscription.unsubscribe());

// Remove style from precondition.
document.documentElement.style.removeProperty('position');

// Expect document root to be positioned.
expect(getComputedStyle(document.documentElement)).toEqual(jasmine.objectContaining({
position: 'relative',
}));

// Override positioning of root element.
const styleSheet = new CSSStyleSheet();
styleSheet.insertRule(`
html {
position: absolute;
}`);
document.adoptedStyleSheets.push(styleSheet);
onDestroy(() => Arrays.remove(document.adoptedStyleSheets, styleSheet));

// Expect overrides to be applied.
expect(getComputedStyle(document.documentElement)).toEqual(jasmine.objectContaining({
position: 'absolute',
}));
});

describe('Moving element out of the viewport', async () => {

it('should emit until moved the element out of the viewport (moving element to the right)', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import {fromResize$} from './resize.observable';
*
* Upon subscription, emits the current bounding box, and then continuously when the bounding box changes. The Observable never completes.
*
* The target element and the document root (`<html>`) must be positioned (`relative`, `absolute`, or `fixed`). If not, positioning is changed to `relative`.
*
* The target element and the document root (`<html>`) must be positioned `relative` or `absolute`.
* If not, a warning is logged and positioning is changed to `relative`.
* Note:
* As of 2024, there is no native browser API to observe the position of an element. This implementation uses {@link IntersectionObserver} and
* {@link ResizeObserver} to detect position changes. For tracking only size changes, use {@link fromResize$} instead.
Expand Down Expand Up @@ -212,10 +213,16 @@ function ensureElementPositioned(element: HTMLElement): void {
return;
}

// Position the HTML root using a constructable stylesheet to not clutter its element styles.
// Position the document root element in a CSS layer. CSS layers have lower priority than normal CSS declarations,
// and the layer name indicates the styling originates from @scion/toolkit.
if (element === document.documentElement) {
const styleSheet = new CSSStyleSheet({});
styleSheet.insertRule(`html { position: relative; }`);
styleSheet.insertRule(`
@layer sci-toolkit {
:root {
position: relative;
}
}`);
document.adoptedStyleSheets.push(styleSheet);
console?.warn?.('[@scion/toolkit] fromBoundingClientRect$ requires the document root element (<html>) to be positioned relative or absolute. Changing positioning to relative.');
}
Expand Down

0 comments on commit 31c5dd5

Please sign in to comment.