Skip to content

Commit

Permalink
fix(color-contrast): correctly handle flex and position (#4086)
Browse files Browse the repository at this point in the history
* fix(color-contrast): correctly handle flex and position

* typos

* indentation

* tests
  • Loading branch information
straker authored Jul 13, 2023
1 parent 6ca38f6 commit 9d5f496
Show file tree
Hide file tree
Showing 5 changed files with 299 additions and 102 deletions.
124 changes: 74 additions & 50 deletions lib/commons/dom/create-grid.js
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));
} 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
// 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
*/
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

0 comments on commit 9d5f496

Please sign in to comment.