Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@oddbird/css-anchor-positioning

Package Overview
Dependencies
Maintainers
3
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@oddbird/css-anchor-positioning - npm Package Compare versions

Comparing version 0.0.2 to 0.0.3

src/@types/global.d.ts

2

dist/polyfill.d.ts

@@ -16,2 +16,2 @@ import { type Rect } from '@floating-ui/dom';

export declare const getPixelValue: ({ targetEl, targetProperty, anchorRect, anchorSide, anchorSize, fallback, }: GetPixelValueOpts) => Promise<string>;
export declare function polyfill(): Promise<AnchorPositions>;
export declare function polyfill(animationFrame?: boolean): Promise<AnchorPositions>;
{
"name": "@oddbird/css-anchor-positioning",
"version": "0.0.2",
"version": "0.0.3",
"description": "Polyfill for the proposed CSS anchor positioning spec",

@@ -81,15 +81,15 @@ "license": "BSD-3-Clause",

"dependencies": {
"@floating-ui/dom": "^1.2.1",
"@floating-ui/dom": "^1.2.5",
"@types/css-tree": "^2.3.1",
"css-tree": "^2.3.1",
"nanoid": "^4.0.1"
"nanoid": "^4.0.2"
},
"devDependencies": {
"@playwright/test": "^1.31.1",
"@playwright/test": "^1.32.1",
"@types/async": "^3.2.18",
"@types/node": "*",
"@types/selenium-webdriver": "^4.1.12",
"@typescript-eslint/eslint-plugin": "^5.53.0",
"@typescript-eslint/parser": "^5.53.0",
"@vitest/coverage-istanbul": "^0.29.1",
"@types/selenium-webdriver": "^4.1.13",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
"@vitest/coverage-istanbul": "^0.29.7",
"async": "^3.2.4",

@@ -99,4 +99,4 @@ "browserslist": "^4.21.5",

"cross-env": "^7.0.3",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.8.0",
"eslint-import-resolver-typescript": "^3.5.3",

@@ -108,14 +108,14 @@ "eslint-plugin-import": "^2.27.5",

"fetch-mock": "^9.11.0",
"jsdom": "^21.1.0",
"liquidjs": "^10.6.0",
"jsdom": "^21.1.1",
"liquidjs": "^10.7.0",
"node-fetch": "^2.6.7",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.4",
"selenium-webdriver": "^4.8.1",
"stylelint": "^15.2.0",
"stylelint-config-standard": "^30.0.1",
"prettier": "^2.8.7",
"selenium-webdriver": "^4.8.2",
"stylelint": "^15.3.0",
"stylelint-config-standard": "^31.0.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.5",
"vite": "^4.1.4",
"vitest": "^0.29.1"
"typescript": "^5.0.2",
"vite": "^4.2.1",
"vitest": "^0.29.7"
},

@@ -122,0 +122,0 @@ "resolutions": {

@@ -30,2 +30,33 @@ # CSS Anchor Positioning Polyfill

## Configuration
The polyfill accepts one argument (type: `boolean`, default: `false`), which
determines whether anchor calculations should [update on every animation
frame](https://floating-ui.com/docs/autoUpdate#animationframe) (e.g. when the
anchor element moves), in addition to always updating on scroll/resize. While
this option is optimized for performance, it should be used sparingly.
```js
<script type="module">
if (!("anchorName" in document.documentElement.style)) {
const { default: polyfill } = await import("https://unpkg.com/@oddbird/css-anchor-positioning/dist/css-anchor-positioning-fn.js");
polyfill(true);
}
</script>
```
When using the default version of the polyfill that executes automatically, this
option can be set by setting the value of
`window.UPDATE_ANCHOR_ON_ANIMATION_FRAME`.
```js
<script type="module">
if (!("anchorName" in document.documentElement.style)) {
window.UPDATE_ANCHOR_ON_ANIMATION_FRAME = true;
import("https://unpkg.com/@oddbird/css-anchor-positioning");
}
</script>
```
## Limitations

@@ -35,3 +66,2 @@

- top layer anchor elements
- `anchor-default` property

@@ -42,6 +72,10 @@ - `anchor-scroll` property

anchor-side
- dynamic anchor movement other than container resize/scroll
([#73](https://github.com/oddbird/css-anchor-positioning/issues/73))
- 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

@@ -52,10 +86,10 @@ property

CSS Grid layout
- `@position-fallback` where targets overflow the grid area but do not overflow
the containing block
- `@position-fallback` where targets in a CSS Grid layout overflow the grid area
but do not overflow the containing block
- `@position-fallback` where targets overflow their inset-modified containing
block, overlapping the anchor element
- anchors in multi-column layouts
- anchor functions used as the fallback value for another anchor function
- anchor functions used as the fallback value in another anchor function
- anchor functions assigned to `bottom` or `right` properties on inline targets
whose offset-parent is inline with `clientHeight`/`clientWidth` of `0`
(partial support -- does not account for possible scrollbar width)
import { polyfill } from './polyfill.js';
// @ts-expect-error Used by the WPT test harness to delay test assertions
// Used by the WPT test harness to delay test assertions
// and give the polyfill time to apply changes

@@ -10,6 +10,6 @@ window.CHECK_LAYOUT_DELAY = true;

window.addEventListener('load', () => {
polyfill();
polyfill(true);
});
} else {
polyfill();
polyfill(true);
}

@@ -290,5 +290,9 @@ import * as csstree from 'css-tree';

if (name) {
if (isIdentifier(name) && name.name.startsWith('--')) {
// Store anchor name
anchorName = name.name;
if (isIdentifier(name)) {
if (name.name === 'implicit') {
name = undefined;
} else if (name.name.startsWith('--')) {
// Store anchor name
anchorName = name.name;
}
} else if (isVarFunction(name) && name.children.first) {

@@ -295,0 +299,0 @@ // Store CSS custom prop for anchor name

@@ -279,3 +279,6 @@ import {

async function applyAnchorPositions(declarations: AnchorFunctionDeclaration) {
async function applyAnchorPositions(
declarations: AnchorFunctionDeclaration,
useAnimationFrame = false,
) {
const root = document.documentElement;

@@ -291,18 +294,23 @@

if (anchor && target) {
autoUpdate(anchor, target, async () => {
const rects = await platform.getElementRects({
reference: anchor,
floating: target,
strategy: 'absolute',
});
const resolved = await getPixelValue({
targetEl: target,
targetProperty: property,
anchorRect: rects.reference,
anchorSide: anchorValue.anchorSide,
anchorSize: anchorValue.anchorSize,
fallback: anchorValue.fallbackValue,
});
root.style.setProperty(anchorValue.uuid, resolved);
});
autoUpdate(
anchor,
target,
async () => {
const rects = await platform.getElementRects({
reference: anchor,
floating: target,
strategy: 'absolute',
});
const resolved = await getPixelValue({
targetEl: target,
targetProperty: property,
anchorRect: rects.reference,
anchorSide: anchorValue.anchorSide,
anchorSize: anchorValue.anchorSize,
fallback: anchorValue.fallbackValue,
});
root.style.setProperty(anchorValue.uuid, resolved);
},
{ animationFrame: useAnimationFrame },
);
} else {

@@ -325,2 +333,3 @@ // Use fallback value

fallbacks: TryBlock[],
useAnimationFrame = false,
) {

@@ -336,52 +345,57 @@ if (!fallbacks.length) {

const offsetParent = await getOffsetParent(target);
autoUpdate(offsetParent, target, async () => {
// If this auto-update was triggered while the polyfill is already looping
// through the possible `@try` blocks, do not check again.
if (checking) {
return;
}
checking = true;
// Apply the styles from each `@try` block (in order), stopping when we
// reach one that does not cause the target's margin-box to overflow
// its offsetParent (containing block).
for (const [index, { uuid }] of fallbacks.entries()) {
target.setAttribute('data-anchor-polyfill', uuid);
if (index === fallbacks.length - 1) {
checking = false;
break;
autoUpdate(
target,
target,
async () => {
// If this auto-update was triggered while the polyfill is already looping
// through the possible `@try` blocks, do not check again.
if (checking) {
return;
}
const rects = await platform.getElementRects({
reference: offsetParent,
floating: target,
strategy: 'absolute',
});
const overflow = await detectOverflow(
{
x: target.offsetLeft,
y: target.offsetTop,
platform: platformWithCache,
rects,
elements: { floating: target },
checking = true;
// Apply the styles from each `@try` block (in order), stopping when we
// reach one that does not cause the target's margin-box to overflow
// its offsetParent (containing block).
for (const [index, { uuid }] of fallbacks.entries()) {
target.setAttribute('data-anchor-polyfill', uuid);
if (index === fallbacks.length - 1) {
checking = false;
break;
}
const rects = await platform.getElementRects({
reference: target,
floating: target,
strategy: 'absolute',
} as unknown as MiddlewareState,
{
boundary: offsetParent,
rootBoundary: 'document',
padding: getMargins(target),
},
);
// If none of the sides overflow, use this `@try` block and stop loop...
if (Object.values(overflow).every((side) => side <= 0)) {
checking = false;
break;
});
const overflow = await detectOverflow(
{
x: target.offsetLeft,
y: target.offsetTop,
platform: platformWithCache,
rects,
elements: { floating: target },
strategy: 'absolute',
} as unknown as MiddlewareState,
{
boundary: offsetParent,
rootBoundary: 'document',
padding: getMargins(target),
},
);
// If none of the sides overflow, use this `@try` block and stop loop...
if (Object.values(overflow).every((side) => side <= 0)) {
checking = false;
break;
}
}
}
});
},
{ animationFrame: useAnimationFrame },
);
}
}
async function position(rules: AnchorPositions) {
async function position(rules: AnchorPositions, useAnimationFrame = false) {
for (const pos of Object.values(rules)) {
// Handle `anchor()` and `anchor-size()` functions...
await applyAnchorPositions(pos.declarations ?? {});
await applyAnchorPositions(pos.declarations ?? {}, useAnimationFrame);
}

@@ -391,7 +405,15 @@

// Handle `@position-fallback` blocks...
await applyPositionFallbacks(targetSel, position.fallbacks ?? []);
await applyPositionFallbacks(
targetSel,
position.fallbacks ?? [],
useAnimationFrame,
);
}
}
export async function polyfill() {
export async function polyfill(animationFrame?: boolean) {
const useAnimationFrame =
animationFrame === undefined
? Boolean(window.UPDATE_ANCHOR_ON_ANIMATION_FRAME)
: animationFrame;
// fetch CSS from stylesheet and inline style

@@ -408,3 +430,3 @@ const styleData = await fetchCSS();

// calculate position values
await position(rules);
await position(rules, useAnimationFrame);
}

@@ -411,0 +433,0 @@

@@ -45,2 +45,14 @@ import { platform } from '@floating-ui/dom';

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.
// 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));
}
// Validates that anchor element is a valid anchor for given target element

@@ -51,2 +63,10 @@ export async function isValidAnchorElement(

) {
if (isTopLayer(anchor) && isTopLayer(target)) {
// TODO: keep track of top layer order
// if (isTargetPrecedingAnchor(target, anchor)) {
// return false;
// }
return true;
}
const anchorContainingBlock = await platform.getOffsetParent?.(anchor);

@@ -57,7 +77,9 @@ const targetContainingBlock = await platform.getOffsetParent?.(target);

// 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;
}
}

@@ -64,0 +86,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc