feat(ivy): properly apply style="", [style], [style.foo] and [attr.style] bindings (#24602)
PR Close #24602
This commit is contained in:
committed by
Miško Hevery
parent
52d43a99ef
commit
3980640d53
@@ -56,8 +56,11 @@ export {
|
||||
elementEnd as e,
|
||||
elementProperty as p,
|
||||
elementStart as E,
|
||||
elementStyle as s,
|
||||
elementStyleNamed as sn,
|
||||
|
||||
elementStyling as s,
|
||||
elementStyle as sm,
|
||||
elementStyleProp as sp,
|
||||
elementStylingApply as sa,
|
||||
|
||||
listener as L,
|
||||
store as st,
|
||||
|
||||
@@ -27,6 +27,7 @@ import {ComponentDefInternal, ComponentTemplate, ComponentQuery, DirectiveDefInt
|
||||
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {isDifferent, stringify} from './util';
|
||||
import {ViewRef} from './view_ref';
|
||||
import {StylingContext, allocStylingContext, createStylingContextTemplate, updateStyleMap as updateElementStyleMap, updateStyleProp as updateElementStyleProp, renderStyles as renderElementStyles} from './styling';
|
||||
|
||||
/**
|
||||
* Directive (D) sets a property on all component instances using this constant as a key and the
|
||||
@@ -652,7 +653,9 @@ export function elementStart(
|
||||
const node: LElementNode =
|
||||
createLNode(index, TNodeType.Element, native !, name, attrs || null, null);
|
||||
|
||||
if (attrs) setUpAttributes(native, attrs);
|
||||
if (attrs) {
|
||||
setUpAttributes(native, attrs);
|
||||
}
|
||||
appendChild(getParentLNode(node), native, viewData);
|
||||
createDirectivesAndLocals(localRefs);
|
||||
return native;
|
||||
@@ -1185,7 +1188,8 @@ export function createTNode(
|
||||
child: null,
|
||||
parent: parent,
|
||||
dynamicContainerNode: null,
|
||||
detached: null
|
||||
detached: null,
|
||||
stylingTemplate: null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1287,9 +1291,94 @@ export function elementClass<T>(index: number, value: T | NO_CHANGE): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a given style on an Element.
|
||||
* Assign any inline style values to the element during creation mode.
|
||||
*
|
||||
* @param index Index of the element to change in the data array
|
||||
* This instruction is meant to be called during creation mode to apply all styling
|
||||
* (e.g. `style="..."`) values to the element. This is also where the provided index
|
||||
* value is allocated for the styling details for its corresponding element (the element
|
||||
* index is the previous index value from this one).
|
||||
*
|
||||
* (Note this function calls `elementStylingApply` immediately when called.)
|
||||
*
|
||||
*
|
||||
* @param index Index value which will be allocated to store styling data for the element.
|
||||
* (Note that this is not the element index, but rather an index value allocated
|
||||
* specifically for element styling--the index must be the next index after the element
|
||||
* index.)
|
||||
* @param styles A key/value map of CSS styles that will be registered on the element.
|
||||
* Each individual style will be used on the element as long as it is not overridden
|
||||
* by any styles placed on the element by multiple (`[style]`) or singular (`[style.prop]`)
|
||||
* bindings. If a style binding changes its value to null then the initial styling
|
||||
* values that are passed in here will be applied to the element (if matched).
|
||||
*/
|
||||
export function elementStyling<T>(index: number, styles?: (string | number)[] | null): void {
|
||||
const tNode = load<LElementNode>(index - 1).tNode;
|
||||
if (!tNode.stylingTemplate) {
|
||||
// initialize the styling template.
|
||||
tNode.stylingTemplate = createStylingContextTemplate(styles);
|
||||
}
|
||||
// Allocate space but leave null for lazy creation.
|
||||
viewData[index + HEADER_OFFSET] = null;
|
||||
if (styles && styles.length) {
|
||||
elementStylingApply(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the `StylingContext` at a given index.
|
||||
*
|
||||
* This method lazily creates the `StylingContext`. This is because in most cases
|
||||
* we have styling without any bindings. Creating `StylingContext` eagerly would mean that
|
||||
* every style declaration such as `<div style="color: 'red' ">` would result `StyleContext`
|
||||
* which would create unnecessary memory pressure.
|
||||
*
|
||||
* @param index Index of the style allocation. See: `elementStyling`.
|
||||
*/
|
||||
function getStylingContext(index: number): StylingContext {
|
||||
let stylingContext = load<StylingContext>(index);
|
||||
if (!stylingContext) {
|
||||
const lElement: LElementNode = load(index - 1);
|
||||
const tNode = lElement.tNode;
|
||||
ngDevMode &&
|
||||
assertDefined(tNode.stylingTemplate, 'getStylingContext() called before elementStyling()');
|
||||
stylingContext = viewData[index + HEADER_OFFSET] = allocStylingContext(tNode.stylingTemplate !);
|
||||
}
|
||||
return stylingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply all styling values to the element which have been queued by any styling instructions.
|
||||
*
|
||||
* This instruction is meant to be run once one or more `elementStyle` and/or `elementStyleProp`
|
||||
* have been issued against the element. This function will also determine if any styles have
|
||||
* changed and will then skip the operation if there is nothing new to render.
|
||||
*
|
||||
* Once called then all queued styles will be flushed.
|
||||
*
|
||||
* @param index Index of the element's styling storage that will be rendered.
|
||||
* (Note that this is not the element index, but rather an index value allocated
|
||||
* specifically for element styling--the index must be the next index after the element
|
||||
* index.)
|
||||
*/
|
||||
export function elementStylingApply<T>(index: number): void {
|
||||
renderElementStyles(load<LElementNode>(index - 1), getStylingContext(index), renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a given style to be rendered on an Element.
|
||||
*
|
||||
* If the style value is `null` then it will be removed from the element
|
||||
* (or assigned a different value depending if there are any styles placed
|
||||
* on the element with `elementStyle` or any styles that are present
|
||||
* from when the element was created (with `elementStyling`).
|
||||
*
|
||||
* (Note that the styling instruction will not be applied until `elementStylingApply` is called.)
|
||||
*
|
||||
* @param index Index of the element's styling storage to change in the data array.
|
||||
* (Note that this is not the element index, but rather an index value allocated
|
||||
* specifically for element styling--the index must be the next index after the element
|
||||
* index.)
|
||||
* @param styleIndex Index of the style property on this element. (Monotonically increasing.)
|
||||
* @param styleName Name of property. Because it is going to DOM this is not subject to
|
||||
* renaming as part of minification.
|
||||
* @param value New value to write (null to remove).
|
||||
@@ -1297,69 +1386,44 @@ export function elementClass<T>(index: number, value: T | NO_CHANGE): void {
|
||||
* @param sanitizer An optional function used to transform the value typically used for
|
||||
* sanitization.
|
||||
*/
|
||||
export function elementStyleNamed<T>(
|
||||
index: number, styleName: string, value: T | NO_CHANGE, suffix?: string): void;
|
||||
export function elementStyleNamed<T>(
|
||||
index: number, styleName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void;
|
||||
export function elementStyleNamed<T>(
|
||||
index: number, styleName: string, value: T | NO_CHANGE,
|
||||
export function elementStyleProp<T>(
|
||||
index: number, styleIndex: number, value: T | null, suffix?: string): void;
|
||||
export function elementStyleProp<T>(
|
||||
index: number, styleIndex: number, value: T | null, sanitizer?: SanitizerFn): void;
|
||||
export function elementStyleProp<T>(
|
||||
index: number, styleIndex: number, value: T | null,
|
||||
suffixOrSanitizer?: string | SanitizerFn): void {
|
||||
if (value !== NO_CHANGE) {
|
||||
const lElement: LElementNode = load(index);
|
||||
if (value == null) {
|
||||
ngDevMode && ngDevMode.rendererRemoveStyle++;
|
||||
isProceduralRenderer(renderer) ?
|
||||
renderer.removeStyle(lElement.native, styleName, RendererStyleFlags3.DashCase) :
|
||||
lElement.native['style'].removeProperty(styleName);
|
||||
} else {
|
||||
let strValue =
|
||||
typeof suffixOrSanitizer == 'function' ? suffixOrSanitizer(value) : stringify(value);
|
||||
if (typeof suffixOrSanitizer == 'string') strValue = strValue + suffixOrSanitizer;
|
||||
ngDevMode && ngDevMode.rendererSetStyle++;
|
||||
isProceduralRenderer(renderer) ?
|
||||
renderer.setStyle(lElement.native, styleName, strValue, RendererStyleFlags3.DashCase) :
|
||||
lElement.native['style'].setProperty(styleName, strValue);
|
||||
let valueToAdd: string|null = null;
|
||||
if (value) {
|
||||
valueToAdd =
|
||||
typeof suffixOrSanitizer == 'function' ? suffixOrSanitizer(value) : stringify(value);
|
||||
if (typeof suffixOrSanitizer == 'string') {
|
||||
valueToAdd = valueToAdd + suffixOrSanitizer;
|
||||
}
|
||||
}
|
||||
updateElementStyleProp(getStylingContext(index), styleIndex, valueToAdd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the `style` property on a DOM element.
|
||||
* Queue a key/value map of styles to be rendered on an Element.
|
||||
*
|
||||
* This instruction is meant to handle the `[style]="exp"` usage.
|
||||
* This instruction is meant to handle the `[style]="exp"` usage. When styles are applied to
|
||||
* the Element they will then be placed with respect to any styles set with `elementStyleProp`.
|
||||
* If any styles are set to `null` then they will be removed from the element (unless the same
|
||||
* style properties have been assigned to the element during creation using `elementStyling`).
|
||||
*
|
||||
* (Note that the styling instruction will not be applied until `elementStylingApply` is called.)
|
||||
*
|
||||
* @param index The index of the element to update in the LViewData array
|
||||
* @param index Index of the element's styling storage to change in the data array.
|
||||
* (Note that this is not the element index, but rather an index value allocated
|
||||
* specifically for element styling--the index must be the next index after the element
|
||||
* index.)
|
||||
* @param value A value indicating if a given style should be added or removed.
|
||||
* The expected shape of `value` is an object where keys are style names and the values
|
||||
* are their corresponding values to set. If value is falsy, then the style is removed. An absence
|
||||
* of style does not cause that style to be removed. `NO_CHANGE` implies that no update should be
|
||||
* performed.
|
||||
* are their corresponding values to set. If value is null, then the style is removed.
|
||||
*/
|
||||
export function elementStyle<T>(
|
||||
index: number, value: {[styleName: string]: any} | NO_CHANGE): void {
|
||||
if (value !== NO_CHANGE) {
|
||||
// TODO: This is a naive implementation which simply writes value to the `style`. In the future
|
||||
// we will add logic here which would work with the animation code.
|
||||
const lElement = load(index) as LElementNode;
|
||||
if (isProceduralRenderer(renderer)) {
|
||||
ngDevMode && ngDevMode.rendererSetStyle++;
|
||||
renderer.setProperty(lElement.native, 'style', value);
|
||||
} else {
|
||||
const style = lElement.native['style'];
|
||||
for (let i = 0, keys = Object.keys(value); i < keys.length; i++) {
|
||||
const styleName: string = keys[i];
|
||||
const styleValue: any = (value as any)[styleName];
|
||||
if (styleValue == null) {
|
||||
ngDevMode && ngDevMode.rendererRemoveStyle++;
|
||||
style.removeProperty(styleName);
|
||||
} else {
|
||||
ngDevMode && ngDevMode.rendererSetStyle++;
|
||||
style.setProperty(styleName, styleValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export function elementStyle<T>(index: number, value: {[styleName: string]: any} | null): void {
|
||||
updateElementStyleMap(getStylingContext(index), value);
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
|
||||
@@ -290,3 +290,9 @@ export type PipeTypeList =
|
||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||
// failure based on types.
|
||||
export const unusedValueExportToPlacateAjd = 1;
|
||||
|
||||
// Note this will expand once `class` is introduced to styling
|
||||
export const enum InitialStylingFlags {
|
||||
/** Mode for matching initial style values */
|
||||
INITIAL_STYLES = 0b00,
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StylingContext} from '../styling';
|
||||
|
||||
import {LContainer} from './container';
|
||||
import {LInjector} from './injector';
|
||||
import {LProjection} from './projection';
|
||||
@@ -341,6 +343,8 @@ export interface TNode {
|
||||
* If this node is not part of an i18n block, this field is null.
|
||||
*/
|
||||
detached: boolean|null;
|
||||
|
||||
stylingTemplate: StylingContext|null;
|
||||
}
|
||||
|
||||
/** Static data for an LElementNode */
|
||||
|
||||
@@ -80,8 +80,9 @@ export const angularCoreEnv: {[name: string]: Function} = {
|
||||
'ɵqR': r3.qR,
|
||||
'ɵrS': r3.rS,
|
||||
'ɵs': r3.s,
|
||||
'ɵsn': r3.sn,
|
||||
'ɵst': r3.st,
|
||||
'ɵsm': r3.sm,
|
||||
'ɵsp': r3.sp,
|
||||
'ɵsa': r3.sa,
|
||||
'ɵT': r3.T,
|
||||
'ɵt': r3.t,
|
||||
'ɵV': r3.V,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user