Skip to content

Commit

Permalink
WIP on try tactics"
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesnw committed Jun 26, 2024
1 parent 6e8c1b0 commit a1047e1
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 16 deletions.
84 changes: 83 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<link rel="stylesheet" href="/anchor-positioning.css" />
<link rel="stylesheet" href="/anchor-popover.css" />
<link rel="stylesheet" href="/position-try.css" />
<link rel="stylesheet" href="/position-try-tactics.css" />
<link rel="stylesheet" href="/position-anchor.css" />
<link rel="stylesheet" href="/anchor-scroll.css" />
<link rel="stylesheet" href="/anchor-size.css" />
Expand Down Expand Up @@ -420,7 +421,7 @@ <h2>
<section id="position-try" class="demo-item">
<h2>
<a href="#position-try" aria-hidden="true">🔗</a>
Positioning with <code>@position-try</code>
Fallbacks with <code>@position-try</code>
</h2>
<div style="position: relative" class="demo-elements">
<div class="scroll-container">
Expand Down Expand Up @@ -489,6 +490,87 @@ <h2>
right: anchor(--my-anchor-fallback right);
}

@position-try --bottom-right {
/* Finally, try to align the top, right edge of the target
with the bottom, right edge of the anchor. */
top: anchor(--my-anchor-fallback bottom);
right: anchor(--my-anchor-fallback right);
width: 100px;
height: 100px;
}</code></pre>
</section>
<section id="position-try-tactics" class="demo-item">
<h2>
<a href="#position-try-tactics" aria-hidden="true">🔗</a>
Positioning with try tactics
</h2>
<div style="position: relative" class="demo-elements">
<div class="scroll-container">
<div class="placefiller-above-anchor"></div>
<div class="placefiller-before-anchor"></div>
<span id="my-anchor-try-tactics" class="anchor"
>@position-try-tactics Anchor</span
>
<div id="my-target-try-tactics" class="target">Target</div>
<div class="placefiller-after-anchor"></div>
</div>
</div>
<div class="note">
<p>
With polyfill applied, the following positions are attempted in order:
</p>
<ol>
<li>
Align the bottom, left edge of the target with the top, left edge of
the anchor.
</li>
<li>
Align the top, left edge of the target with the bottom, left edge of
the anchor.
</li>
<li>
Align the bottom, right edge of the target with the top, right edge
of the anchor.
</li>
<li>
Align the top, right edge of the target with the bottom, right edge
of the anchor, and increase the height and width of the target.
</li>
<li>
When every position overflows, revert to the initial base styles in
#1, even though it overflows.
</li>
</ol>
</div>
<pre><code class="language-css"
>#my-anchor-fallback {
anchor-name: --my-anchor-fallback;
}

#my-target-fallback {
position: absolute;

/* First try to align the bottom, left edge of the target
with the top, left edge of the anchor. */
bottom: anchor(--my-anchor-fallback top);
left: anchor(--my-anchor-fallback left);
position-try-options: --bottom-left, --top-right, --bottom-right;
}

@position-try --bottom-left {
/* Next try to align the top, left edge of the target
with the bottom, left edge of the anchor. */
top: anchor(--my-anchor-fallback bottom);
left: anchor(--my-anchor-fallback left);
}

@position-try --top-right {
/* Next try to align the bottom, right edge of the target
with the top, right edge of the anchor. */
bottom: anchor(--my-anchor-fallback top);
right: anchor(--my-anchor-fallback right);
}

@position-try --bottom-right {
/* Finally, try to align the top, right edge of the target
with the bottom, right edge of the anchor. */
Expand Down
11 changes: 11 additions & 0 deletions public/position-try-tactics.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#my-anchor-try-tactics {
anchor-name: --my-anchor-try-tactics;
}

#my-target-try-tactics {
position: absolute;
position-anchor: --my-anchor-try-tactics;
bottom: anchor(top);
left: anchor(left);
position-try-options: flip-block, flip-inline, flip-start;
}
23 changes: 23 additions & 0 deletions src/cascade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
generateCSS,
getAST,
getDeclarationValue,
isAnchorFunction,
POSITION_ANCHOR_PROPERTY,
type StyleData,
} from './utils.js';
Expand All @@ -27,6 +28,24 @@ function shiftPositionAnchorData(node: csstree.CssNode, block?: csstree.Block) {
return {};
}

// Move inset declarations to cascadable properties
// property.
function shiftInsetData(node: csstree.CssNode, block?: csstree.Block) {
if (isAnchorFunction(node) && block) {
block.children.appendData({
type: 'Declaration',
important: false,
property: POSITION_ANCHOR_PROPERTY,
value: {
type: 'Raw',
value: node,
},
});
return { updated: true };
}
return {};
}

export async function cascadeCSS(styleData: StyleData[]) {
for (const styleObj of styleData) {
let changed = false;
Expand All @@ -39,6 +58,10 @@ export async function cascadeCSS(styleData: StyleData[]) {
if (updated) {
changed = true;
}
const { updated: insetUpdated} = shiftInsetData(node, block);
if (insetUpdated) {
changed = true;
}
},
});

Expand Down
18 changes: 4 additions & 14 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
type DeclarationWithValue,
generateCSS,
getAST,
isAnchorFunction,
POSITION_ANCHOR_PROPERTY,
splitCommaList,
type StyleData,
} from './utils.js';
import { validatedForPositioning } from './validate.js';
Expand Down Expand Up @@ -141,8 +143,7 @@ type PositionTryOptionsTryTactics = 'flip-block' | 'flip-inline' | 'flip-start';
type PositionTryOption =
| 'none'
| PositionTryOptionsTryTactics
// | csstree.Identifier
// todo: what's the dashed ident type
| csstree.Identifier
| InsetProperty;

export interface AnchorFunction {
Expand Down Expand Up @@ -210,12 +211,6 @@ function isAnchorNameDeclaration(
return node.type === 'Declaration' && node.property === 'anchor-name';
}

function isAnchorFunction(
node: csstree.CssNode | null,
): node is csstree.FunctionNode {
return Boolean(node && node.type === 'Function' && node.name === 'anchor');
}

function isAnchorSizeFunction(
node: csstree.CssNode | null,
): node is csstree.FunctionNode {
Expand Down Expand Up @@ -456,12 +451,7 @@ function getPositionTryOptionsDeclaration(
node.value.children.first &&
rule?.value
) {
return node.value.children
.map((item) => {
const { name } = item as csstree.Identifier;
return name as PositionTryOption;
})
.toArray();
return splitCommaList(node.value.children);
}
return [];
}
Expand Down
23 changes: 23 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ export interface DeclarationWithValue extends csstree.Declaration {
value: csstree.Value;
}

export function isAnchorFunction(
node: csstree.CssNode | null,
): node is csstree.FunctionNode {
return Boolean(node && node.type === 'Function' && node.name === 'anchor');
}

export function getAST(cssText: string) {
return csstree.parse(cssText, {
parseAtrulePrelude: false,
Expand Down Expand Up @@ -33,3 +39,20 @@ export interface StyleData {
}

export const POSITION_ANCHOR_PROPERTY = `--position-anchor-${nanoid(12)}`;

export function splitCommaList(list: csstree.List<csstree.CssNode>) {
return list.toArray().reduce(
(acc: csstree.Identifier[][], child) => {
if (child.type === 'Operator' && child.value === ',') {
acc.push([]);
return acc;
}
if (child.type === 'Identifier') {
acc[acc.length - 1].push(child);
}

return acc;
},
[[]],
);
}
11 changes: 11 additions & 0 deletions tests/unit/cascade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,15 @@ describe('cascadeCSS', () => {
expect(css).toContain(`${POSITION_ANCHOR_PROPERTY}:--my-position-anchor-b`);
expect(css).toContain(`${POSITION_ANCHOR_PROPERTY}:--my-position-anchor-a`);
});
it('adds insets with anchors as custom properties', async () => {
const srcCSS = getSampleCSS('position-try-tactics');
const styleData: StyleData[] = [
{ css: srcCSS, el: document.createElement('div') },
];
const cascadeCausedChanges = await cascadeCSS(styleData);
expect(cascadeCausedChanges).toBe(true);
const { css } = styleData[0];
expect(css).toContain('--bottom: anchor(top);');
expect(css).toContain('--left: anchor(top);');
});
});
2 changes: 1 addition & 1 deletion tests/unit/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ describe('parseCSS', () => {
targetEl,
},
],
width: expect.any(Array)
width: expect.any(Array),
},
fallbacks: [
{
Expand Down
22 changes: 22 additions & 0 deletions tests/unit/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type * as csstree from 'css-tree';

import { getAST, splitCommaList } from '../../src/utils.js';

describe('splitCommaList', () => {
it('works', () => {
const { children } = getAST('a{b: c d, e, f;}') as csstree.StyleSheet;
const value = (
(children.first as csstree.Rule).block.children
.first as csstree.Declaration
).value as csstree.Value;
const res = splitCommaList(value.children);
expect(res).toEqual([
[
{ name: 'c', type: 'Identifier', loc: null },
{ name: 'd', type: 'Identifier', loc: null },
],
[{ name: 'e', type: 'Identifier', loc: null }],
[{ name: 'f', type: 'Identifier', loc: null }],
]);
});
});

0 comments on commit a1047e1

Please sign in to comment.