chrome-devtools-frontend
Version:
Chrome DevTools UI
394 lines (370 loc) • 12.9 kB
JavaScript
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
const writingModesAffectingFlexDirection = new Set([
'tb',
'tb-rl',
'vertical-lr',
'vertical-rl',
]);
/** @enum {string} */
export const PhysicalFlexDirection = {
LEFT_TO_RIGHT: 'left-to-right',
RIGHT_TO_LEFT: 'right-to-left',
BOTTOM_TO_TOP: 'bottom-to-top',
TOP_TO_BOTTOM: 'top-to-bottom',
};
/**
* @param {!PhysicalFlexDirection} direction
* @return {!PhysicalFlexDirection}
*/
export function reverseDirection(direction) {
if (direction === PhysicalFlexDirection.LEFT_TO_RIGHT) {
return PhysicalFlexDirection.RIGHT_TO_LEFT;
}
if (direction === PhysicalFlexDirection.RIGHT_TO_LEFT) {
return PhysicalFlexDirection.LEFT_TO_RIGHT;
}
if (direction === PhysicalFlexDirection.TOP_TO_BOTTOM) {
return PhysicalFlexDirection.BOTTOM_TO_TOP;
}
if (direction === PhysicalFlexDirection.BOTTOM_TO_TOP) {
return PhysicalFlexDirection.TOP_TO_BOTTOM;
}
throw new Error('Unknown PhysicalFlexDirection');
}
/**
* @param {!Object.<string, !PhysicalFlexDirection>} directions
* @return {!Object.<string, !PhysicalFlexDirection>}
*/
function extendWithReverseDirections(directions) {
return {
...directions,
'row-reverse': reverseDirection(directions.row),
'column-reverse': reverseDirection(directions.column),
};
}
/**
* Returns absolute directions for row and column values of flex-direction
* taking into account the direction and writing-mode attributes.
*
* @param {!Map<string, string>} computedStyles
* @return {!Object.<string, !PhysicalFlexDirection>}
*/
export function getPhysicalFlexDirections(computedStyles) {
const isRtl = computedStyles.get('direction') === 'rtl';
const writingMode = computedStyles.get('writing-mode');
const isVertical = writingMode && writingModesAffectingFlexDirection.has(writingMode);
if (isVertical) {
return extendWithReverseDirections({
row: isRtl ? PhysicalFlexDirection.BOTTOM_TO_TOP : PhysicalFlexDirection.TOP_TO_BOTTOM,
column: writingMode === 'vertical-lr' ? PhysicalFlexDirection.LEFT_TO_RIGHT : PhysicalFlexDirection.RIGHT_TO_LEFT
});
}
return extendWithReverseDirections({
row: isRtl ? PhysicalFlexDirection.RIGHT_TO_LEFT : PhysicalFlexDirection.LEFT_TO_RIGHT,
column: PhysicalFlexDirection.TOP_TO_BOTTOM,
});
}
/**
* Rotates the flex direction icon in such way that it indicates
* the desired `direction` and the arrow in the icon is always at the bottom
* or at the right.
*
* By default, the icon is pointing top-down with the arrow on the right-hand side.
*
* @param {!PhysicalFlexDirection} direction
* @return {!IconInfo}
*/
export function rotateFlexDirectionIcon(direction) {
// Default to LTR.
let flipX = true;
let flipY = false;
let rotate = -90;
if (direction === PhysicalFlexDirection.RIGHT_TO_LEFT) {
rotate = 90;
flipY = false;
flipX = false;
} else if (direction === PhysicalFlexDirection.TOP_TO_BOTTOM) {
rotate = 0;
flipX = false;
flipY = false;
} else if (direction === PhysicalFlexDirection.BOTTOM_TO_TOP) {
rotate = 0;
flipX = false;
flipY = true;
}
return {
iconName: 'flex-direction-icon',
rotate: rotate,
scaleX: flipX ? -1 : 1,
scaleY: flipY ? -1 : 1,
};
}
/**
* @param {string} iconName
* @param {!PhysicalFlexDirection} direction
* @return {!IconInfo}
*/
export function rotateAlignContentIcon(iconName, direction) {
return {
iconName,
rotate: direction === PhysicalFlexDirection.RIGHT_TO_LEFT ?
90 :
(direction === PhysicalFlexDirection.LEFT_TO_RIGHT ? -90 : 0),
scaleX: 1,
scaleY: 1,
};
}
/**
* @param {string} iconName
* @param {!PhysicalFlexDirection} direction
* @return {!IconInfo}
*/
export function rotateJustifyContentIcon(iconName, direction) {
return {
iconName,
rotate: direction === PhysicalFlexDirection.TOP_TO_BOTTOM ?
90 :
(direction === PhysicalFlexDirection.BOTTOM_TO_TOP ? -90 : 0),
scaleX: direction === PhysicalFlexDirection.RIGHT_TO_LEFT ? -1 : 1,
scaleY: 1,
};
}
/**
* @param {string} iconName
* @param {!PhysicalFlexDirection} direction
* @return {!IconInfo}
*/
export function rotateAlignItemsIcon(iconName, direction) {
return {
iconName,
rotate: direction === PhysicalFlexDirection.RIGHT_TO_LEFT ?
90 :
(direction === PhysicalFlexDirection.LEFT_TO_RIGHT ? -90 : 0),
scaleX: 1,
scaleY: 1,
};
}
/**
*
* @param {string} value
* @return {function(!Map<string, string>):!IconInfo}
*/
function flexDirectionIcon(value) {
/**
* @param {!Map<string, string>} computedStyles
* @return {!IconInfo}
*/
function getIcon(computedStyles) {
const directions = getPhysicalFlexDirections(computedStyles);
return rotateFlexDirectionIcon(directions[value]);
}
return getIcon;
}
/**
*
* @param {string} iconName
* @return {function(!Map<string, string>):!IconInfo}
*/
function flexAlignContentIcon(iconName) {
/**
* @param {!Map<string, string>} computedStyles
* @return {!IconInfo}
*/
function getIcon(computedStyles) {
const directions = getPhysicalFlexDirections(computedStyles);
/**
* @type {!Map<string, !PhysicalFlexDirection>}
*/
const flexDirectionToPhysicalDirection = new Map([
['column', directions.row],
['row', directions.column],
['column-reverse', directions.row],
['row-reverse', directions.column],
]);
const computedFlexDirection = computedStyles.get('flex-direction') || 'row';
const iconDirection = flexDirectionToPhysicalDirection.get(computedFlexDirection);
if (!iconDirection) {
throw new Error('Unknown direction for flex-align icon');
}
return rotateAlignContentIcon(iconName, iconDirection);
}
return getIcon;
}
/**
*
* @param {string} iconName
* @return {function(!Map<string, string>):!IconInfo}
*/
function flexJustifyContentIcon(iconName) {
/**
* @param {!Map<string, string>} computedStyles
* @return {!IconInfo}
*/
function getIcon(computedStyles) {
const directions = getPhysicalFlexDirections(computedStyles);
return rotateJustifyContentIcon(iconName, directions[computedStyles.get('flex-direction') || 'row']);
}
return getIcon;
}
/**
*
* @param {string} iconName
* @return {function(!Map<string, string>):!IconInfo}
*/
function flexAlignItemsIcon(iconName) {
/**
* @param {!Map<string, string>} computedStyles
* @return {!IconInfo}
*/
function getIcon(computedStyles) {
const directions = getPhysicalFlexDirections(computedStyles);
/**
* @type {!Map<string, !PhysicalFlexDirection>}
*/
const flexDirectionToPhysicalDirection = new Map([
['column', directions.row],
['row', directions.column],
['column-reverse', directions.row],
['row-reverse', directions.column],
]);
const computedFlexDirection = computedStyles.get('flex-direction') || 'row';
const iconDirection = flexDirectionToPhysicalDirection.get(computedFlexDirection);
if (!iconDirection) {
throw new Error('Unknown direction for flex-align icon');
}
return rotateAlignItemsIcon(iconName, iconDirection);
}
return getIcon;
}
/**
* The baseline icon contains the letter A to indicate that we're aligning based on where the text baseline is.
* Therefore we're not rotating this icon like the others, as this would become confusing. Plus baseline alignment
* is likely only really useful in horizontal flow cases.
*
* @return {!IconInfo}
*/
function baselineIcon() {
return {
iconName: 'baseline-icon',
rotate: 0,
scaleX: 1,
scaleY: 1,
};
}
/**
*
* @param {string} iconName
* @return {function(!Map<string, string>,!Map<string, string>)}):!IconInfo}
*/
function flexAlignSelfIcon(iconName) {
/**
* @param {!Map<string, string>} computedStyles
* @param {!Map<string, string>} parentComputedStyles
* @return {!IconInfo}
*/
function getIcon(computedStyles, parentComputedStyles) {
return flexAlignItemsIcon(iconName)(parentComputedStyles);
}
return getIcon;
}
/**
* @param {string} iconName
* @param {PhysicalFlexDirection} direction
* @returns
*/
export function roateFlexWrapIcon(iconName, direction) {
return {
iconName,
rotate:
direction === PhysicalFlexDirection.BOTTOM_TO_TOP || direction === PhysicalFlexDirection.TOP_TO_BOTTOM ? 90 : 0,
scaleX: 1,
scaleY: 1,
};
}
/**
*
* @param {string} iconName
* @return {function(!Map<string, string>):!IconInfo}
*/
function flexWrapIcon(iconName) {
/**
* @param {!Map<string, string>} computedStyles
* @return {!IconInfo}
*/
function getIcon(computedStyles) {
const directions = getPhysicalFlexDirections(computedStyles);
const computedFlexDirection = computedStyles.get('flex-direction') || 'row';
return roateFlexWrapIcon(iconName, directions[computedFlexDirection]);
}
return getIcon;
}
/**
* @type {!Map<string, function(!Map<string, string>, !Map<string, string>):!IconInfo>}
*/
const textToIconResolver = new Map([
['flex-direction: row', flexDirectionIcon('row')],
['flex-direction: column', flexDirectionIcon('column')],
['flex-direction: column-reverse', flexDirectionIcon('column-reverse')],
['flex-direction: row-reverse', flexDirectionIcon('row-reverse')],
['flex-direction: initial', flexDirectionIcon('row')],
['flex-direction: unset', flexDirectionIcon('row')],
['flex-direction: revert', flexDirectionIcon('row')],
['align-content: center', flexAlignContentIcon('flex-align-content-center-icon')],
['align-content: space-around', flexAlignContentIcon('flex-align-content-space-around-icon')],
['align-content: space-between', flexAlignContentIcon('flex-align-content-space-between-icon')],
['align-content: stretch', flexAlignContentIcon('flex-align-content-stretch-icon')],
['align-content: space-evenly', flexAlignContentIcon('flex-align-content-space-evenly-icon')],
['align-content: flex-end', flexAlignContentIcon('flex-align-content-end-icon')],
['align-content: flex-start', flexAlignContentIcon('flex-align-content-start-icon')],
// TODO(crbug.com/1139945): Start & end should be enabled once Chromium supports them for flexbox.
// ['align-content: start', flexAlignContentIcon('flex-align-content-start-icon')],
// ['align-content: end', flexAlignContentIcon('flex-align-content-end-icon')],
['align-content: normal', flexAlignContentIcon('flex-align-content-stretch-icon')],
['align-content: revert', flexAlignContentIcon('flex-align-content-stretch-icon')],
['align-content: unset', flexAlignContentIcon('flex-align-content-stretch-icon')],
['align-content: initial', flexAlignContentIcon('flex-align-content-stretch-icon')],
['justify-content: center', flexJustifyContentIcon('flex-justify-content-center-icon')],
['justify-content: space-around', flexJustifyContentIcon('flex-justify-content-space-around-icon')],
['justify-content: space-between', flexJustifyContentIcon('flex-justify-content-space-between-icon')],
['justify-content: space-evenly', flexJustifyContentIcon('flex-justify-content-space-evenly-icon')],
['justify-content: flex-end', flexJustifyContentIcon('flex-justify-content-flex-end-icon')],
['justify-content: flex-start', flexJustifyContentIcon('flex-justify-content-flex-start-icon')],
['align-items: stretch', flexAlignItemsIcon('flex-align-items-stretch-icon')],
['align-items: flex-end', flexAlignItemsIcon('flex-align-items-flex-end-icon')],
['align-items: flex-start', flexAlignItemsIcon('flex-align-items-flex-start-icon')],
['align-items: center', flexAlignItemsIcon('flex-align-items-center-icon')],
['align-items: baseline', baselineIcon],
['align-content: baseline', baselineIcon],
['align-self: baseline', baselineIcon],
['align-self: center', flexAlignSelfIcon('flex-align-self-center-icon')],
['align-self: flex-start', flexAlignSelfIcon('flex-align-self-flex-start-icon')],
['align-self: flex-end', flexAlignSelfIcon('flex-align-self-flex-end-icon')],
['align-self: stretch', flexAlignSelfIcon('flex-align-self-stretch-icon')],
['flex-wrap: wrap', flexWrapIcon('flex-wrap-icon')],
['flex-wrap: nowrap', flexWrapIcon('flex-nowrap-icon')],
]);
/**
* @param {string} text
* @param {?Map<string, string>} computedStyles
* @param {?Map<string, string>=} parentComputedStyles
* @return {?IconInfo}
*/
export function findIcon(text, computedStyles, parentComputedStyles) {
const resolver = textToIconResolver.get(text);
if (resolver) {
return resolver(computedStyles || new Map(), parentComputedStyles || new Map());
}
return null;
}
/**
* @typedef {{
* iconName: string,
* rotate: number,
* scaleX: number,
* scaleY: number,
* }}
*/
// @ts-ignore typedef
export let IconInfo;