UNPKG

chrome-devtools-frontend

Version:
134 lines (122 loc) 4.48 kB
/** * @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};