chrome-devtools-frontend
Version:
Chrome DevTools UI
134 lines (122 loc) • 4.48 kB
text/typescript
/**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
import {
directive,
Directive,
DirectiveParameters,
PartInfo,
PartType,
} from '../directive.js';
import {AttributePart, noChange} from '../lit-html.js';
/**
* A key-value set of CSS properties and values.
*
* The key should be either a valid CSS property name string, like
* `'background-color'`, or a valid JavaScript camel case property name
* for CSSStyleDeclaration like `backgroundColor`.
*/
export interface StyleInfo {
readonly [name: string]: string | undefined | null;
}
class StyleMapDirective extends Directive {
_previousStyleProperties?: Set<string>;
constructor(partInfo: PartInfo) {
super(partInfo);
if (
partInfo.type !== PartType.ATTRIBUTE ||
partInfo.name !== 'style' ||
(partInfo.strings?.length as number) > 2
) {
throw new Error(
'The `styleMap` directive must be used in the `style` attribute ' +
'and must be the only part in the attribute.'
);
}
}
render(styleInfo: StyleInfo) {
return Object.keys(styleInfo).reduce((style, prop) => {
const value = styleInfo[prop];
if (value == null) {
return style;
}
// Convert property names from camel-case to dash-case, i.e.:
// `backgroundColor` -> `background-color`
// Vendor-prefixed names need an extra `-` appended to front:
// `webkitAppearance` -> `-webkit-appearance`
// Exception is any property name containing a dash, including
// custom properties; we assume these are already dash-cased i.e.:
// `--my-button-color` --> `--my-button-color`
prop = prop
.replace(/(?:^(webkit|moz|ms|o)|)(?=[A-Z])/g, '-$&')
.toLowerCase();
return style + `${prop}:${value};`;
}, '');
}
update(part: AttributePart, [styleInfo]: DirectiveParameters<this>) {
const {style} = part.element as HTMLElement;
if (this._previousStyleProperties === undefined) {
this._previousStyleProperties = new Set();
for (const name in styleInfo) {
this._previousStyleProperties.add(name);
}
return this.render(styleInfo);
}
// Remove old properties that no longer exist in styleInfo
// We use forEach() instead of for-of so that re don't require down-level
// iteration.
this._previousStyleProperties!.forEach((name) => {
// If the name isn't in styleInfo or it's null/undefined
if (styleInfo[name] == null) {
this._previousStyleProperties!.delete(name);
if (name.includes('-')) {
style.removeProperty(name);
} else {
// Note reset using empty string (vs null) as IE11 does not always
// reset via null (https://developer.mozilla.org/en-US/docs/Web/API/ElementCSSInlineStyle/style#setting_styles)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(style as any)[name] = '';
}
}
});
// Add or update properties
for (const name in styleInfo) {
const value = styleInfo[name];
if (value != null) {
this._previousStyleProperties.add(name);
if (name.includes('-')) {
style.setProperty(name, value);
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(style as any)[name] = value;
}
}
}
return noChange;
}
}
/**
* A directive that applies CSS properties to an element.
*
* `styleMap` can only be used in the `style` attribute and must be the only
* expression in the attribute. It takes the property names in the `styleInfo`
* object and adds the property values as CSS properties. Property names with
* dashes (`-`) are assumed to be valid CSS property names and set on the
* element's style object using `setProperty()`. Names without dashes are
* assumed to be camelCased JavaScript property names and set on the element's
* style object using property assignment, allowing the style object to
* translate JavaScript-style names to CSS property names.
*
* For example `styleMap({backgroundColor: 'red', 'border-top': '5px', '--size':
* '0'})` sets the `background-color`, `border-top` and `--size` properties.
*
* @param styleInfo
*/
export const styleMap = directive(StyleMapDirective);
/**
* The type of the class that powers this directive. Necessary for naming the
* directive's return type.
*/
export type {StyleMapDirective};