Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(color-contrast): correctly handle flex and position #4086

Merged
merged 4 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 74 additions & 50 deletions lib/commons/dom/create-grid.js
WilcoFiers marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import constants from '../../core/constants';
import cache from '../../core/base/cache';
import assert from '../../core/utils/assert';

const ROOT_ORDER = 0;
const DEFAULT_ORDER = 0.1;
const FLOAT_ORDER = 0.2;
const POSITION_STATIC_ORDER = 0.3;
const ROOT_LEVEL = 0;
const DEFAULT_LEVEL = 0.1;
const FLOAT_LEVEL = 0.2;
const POSITION_LEVEL = 0.3;
let nodeIndex = 0;

/**
Expand Down Expand Up @@ -39,7 +39,9 @@ export default function createGrid(
}

nodeIndex = 0;
vNode._stackingOrder = [createContext(ROOT_ORDER, null)];
vNode._stackingOrder = [
createStackingContext(ROOT_LEVEL, nodeIndex++, null)
];
rootGrid ??= new Grid();
addNodeToGrid(rootGrid, vNode);

Expand Down Expand Up @@ -251,72 +253,94 @@ function isFlexOrGridContainer(vNode) {

/**
* Determine the stacking order of an element. The stacking order is an array of
* zIndex values for each stacking context parent.
* stacking contexts in ancestor order.
* @param {VirtualNode} vNode
* @param {VirtualNode} parentVNode
* @param {Number} nodeIndex
* @param {Number} treeOrder
* @return {Number[]}
*/
function createStackingOrder(vNode, parentVNode, nodeIndex) {
function createStackingOrder(vNode, parentVNode, treeOrder) {
const stackingOrder = parentVNode._stackingOrder.slice();

// if a positioned element has z-index: auto or 0 (step #8), or if
// a non-positioned floating element (step #5), treat it as its
// own stacking context
// @see https://www.w3.org/Style/css2-updates/css2/zindex.html
if (!isStackingContext(vNode, parentVNode)) {
if (vNode.getComputedStylePropertyValue('position') !== 'static') {
// Put positioned elements above floated elements
stackingOrder.push(createContext(POSITION_STATIC_ORDER, vNode));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, did we call the position for when something's NOT static the POSITION_STATIC_ORDER. Whoops! 🙄 That was my idea too wasn't it?

} else if (vNode.getComputedStylePropertyValue('float') !== 'none') {
// Put floated elements above z-index: 0
// (step #5 floating get sorted below step #8 positioned)
stackingOrder.push(createContext(FLOAT_ORDER, vNode));
}
return stackingOrder;
}

// if an element creates a stacking context, find the first
// true stack (not a "fake" stack created from positioned or
// floated elements without a z-index) and create a new stack at
// that point (step #5 and step #8)
// @see https://www.w3.org/Style/css2-updates/css2/zindex.html
const index = stackingOrder.findIndex(({ value }) =>
[ROOT_ORDER, FLOAT_ORDER, POSITION_STATIC_ORDER].includes(value)
);
if (index !== -1) {
stackingOrder.splice(index, stackingOrder.length - index);
if (isStackingContext(vNode, parentVNode)) {
const index = stackingOrder.findIndex(({ stackLevel }) =>
[ROOT_LEVEL, FLOAT_LEVEL, POSITION_LEVEL].includes(stackLevel)
);
if (index !== -1) {
stackingOrder.splice(index, stackingOrder.length - index);
}
}

const zIndex = getRealZIndex(vNode, parentVNode);
if (!['auto', '0'].includes(zIndex)) {
stackingOrder.push(createContext(parseInt(zIndex), vNode));
return stackingOrder;
}
// since many things can create a new stacking context without position or
Copy link
Contributor

@dbjorge dbjorge Jul 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice change, I think your update including the nodeIndex in the comparator is much cleaner than the old way.

// z-index, we need to know the order in the dom to sort them by. Use the
// nodeIndex property to create a number less than the "fake" stacks from
// positioned or floated elements but still larger than 0
// 10 pad gives us the ability to sort up to 1B nodes (padStart does not
// exist in ie11)
let float = nodeIndex.toString();
while (float.length < 10) {
float = '0' + float;
}
stackingOrder.push(
createContext(parseFloat(`${DEFAULT_ORDER}${float}`), vNode)
);

const stackLevel = getStackLevel(vNode, parentVNode);
if (stackLevel !== null) {
stackingOrder.push(createStackingContext(stackLevel, treeOrder, vNode));
}
return stackingOrder;
}

function createContext(value, vNode) {
/**
* Create a stacking context, keeping track of the stack level, tree order, and virtual
* node container.
* @see https://www.w3.org/Style/css2-updates/css2/zindex.html
* @see https://www.w3.org/Style/css2-updates/css2/visuren.html#layers
* @param {Number} stackLevel - The stack level of the stacking context
* @param {Number} treeOrder - The elements depth-first traversal order
* @param {VirtualNode} vNode - The virtual node that is the container for the stacking context
Comment on lines +291 to +293
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

*/
function createStackingContext(stackLevel, treeOrder, vNode) {
return {
value,
stackLevel,
treeOrder,
vNode
};
}

/**
* Calculate the level of the stacking context.
* @param {VirtualNode} vNode - The virtual node container of the stacking context
* @param {VirtualNode} parentVNode - The parent virtual node of the vNode
* @return {Number|null}
*/
function getStackLevel(vNode, parentVNode) {
const zIndex = getRealZIndex(vNode, parentVNode);
if (!['auto', '0'].includes(zIndex)) {
return parseInt(zIndex);
}

// if a positioned element has z-index: auto or 0 (step #8), or if
// a non-positioned floating element (step #5), treat it as its
// own stacking context
// @see https://www.w3.org/Style/css2-updates/css2/zindex.html

// Put positioned elements above floated elements
if (vNode.getComputedStylePropertyValue('position') !== 'static') {
return POSITION_LEVEL;
}

// Put floated elements above z-index: 0
// (step #5 floating get sorted below step #8 positioned)
if (vNode.getComputedStylePropertyValue('float') !== 'none') {
return FLOAT_LEVEL;
}

if (isStackingContext(vNode, parentVNode)) {
return DEFAULT_LEVEL;
}

return null;
}

/**
* Calculate the z-index value of a node taking into account when doesn't apply.
* @param {VirtualNode} vNode - The virtual node to get z-index of
* @param {VirtualNode} parentVNode - The parent virtual node of the vNode
* @return {Number|'auto'}
*/
function getRealZIndex(vNode, parentVNode) {
const position = vNode.getComputedStylePropertyValue('position');
if (position === 'static' && !isFlexOrGridContainer(parentVNode)) {
Expand Down
11 changes: 9 additions & 2 deletions lib/commons/dom/visually-sort.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import createGrid from './create-grid';

/**
* Visually sort nodes based on their stack order
* References:
* https://www.w3.org/Style/css2-updates/css2/zindex.html
* https://www.w3.org/Style/css2-updates/css2/visuren.html#layers
* @param {VirtualNode}
* @param {VirtualNode}
*/
Expand All @@ -19,14 +21,19 @@ export default function visuallySort(a, b) {
}

// 7. the child stacking contexts with positive stack levels (least positive first).
if (b._stackingOrder[i].value > a._stackingOrder[i].value) {
if (b._stackingOrder[i].stackLevel > a._stackingOrder[i].stackLevel) {
return 1;
}

// 2. the child stacking contexts with negative stack levels (most negative first).
if (b._stackingOrder[i].value < a._stackingOrder[i].value) {
if (b._stackingOrder[i].stackLevel < a._stackingOrder[i].stackLevel) {
return -1;
}

// stacks share the same stack level so compare document order
if (b._stackingOrder[i].treeOrder !== a._stackingOrder[i].treeOrder) {
return b._stackingOrder[i].treeOrder - a._stackingOrder[i].treeOrder;
}
}

// nodes are the same stacking order
Expand Down
Loading