blob: 257433bfbbcd5e0ddff32d348e4c301146e03ddc [file] [log] [blame]
/**
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
'use strict';
import documentWait from './document-wait.js';
/**
* @typedef {HTMLStyleElement | {getStyle: function():HTMLStyleElement}}
*/
export let CustomStyleProvider;
const SEEN_MARKER = '__seenByShadyCSS';
const CACHED_STYLE = '__shadyCSSCachedStyle';
/** @type {?function(!HTMLStyleElement)} */
let transformFn = null;
/** @type {?function()} */
let validateFn = null;
/**
This interface is provided to add document-level <style> elements to ShadyCSS for processing.
These styles must be processed by ShadyCSS to simulate ShadowRoot upper-bound encapsulation from outside styles
In addition, these styles may also need to be processed for @apply rules and CSS Custom Properties
To add document-level styles to ShadyCSS, one can call `ShadyCSS.addDocumentStyle(styleElement)` or `ShadyCSS.addDocumentStyle({getStyle: () => styleElement})`
In addition, if the process used to discover document-level styles can be synchronously flushed, one should set `ShadyCSS.documentStyleFlush`.
This function will be called when calculating styles.
An example usage of the document-level styling api can be found in `examples/document-style-lib.js`
@unrestricted
*/
export default class CustomStyleInterface {
constructor() {
/** @type {!Array<!CustomStyleProvider>} */
this['customStyles'] = [];
this['enqueued'] = false;
// NOTE(dfreedm): use quotes here to prevent closure inlining to `function(){}`;
documentWait(() => {
if (window['ShadyCSS']['flushCustomStyles']) {
window['ShadyCSS']['flushCustomStyles']();
}
})
}
/**
* Queue a validation for new custom styles to batch style recalculations
*/
enqueueDocumentValidation() {
if (this['enqueued'] || !validateFn) {
return;
}
this['enqueued'] = true;
documentWait(validateFn);
}
/**
* @param {!HTMLStyleElement} style
*/
addCustomStyle(style) {
if (!style[SEEN_MARKER]) {
style[SEEN_MARKER] = true;
this['customStyles'].push(style);
this.enqueueDocumentValidation();
}
}
/**
* @param {!CustomStyleProvider} customStyle
* @return {HTMLStyleElement}
*/
getStyleForCustomStyle(customStyle) {
if (customStyle[CACHED_STYLE]) {
return customStyle[CACHED_STYLE];
}
let style;
if (customStyle['getStyle']) {
style = customStyle['getStyle']();
} else {
style = customStyle;
}
return style;
}
/**
* @return {!Array<!CustomStyleProvider>}
*/
processStyles() {
const cs = this['customStyles'];
for (let i = 0; i < cs.length; i++) {
const customStyle = cs[i];
if (customStyle[CACHED_STYLE]) {
continue;
}
const style = this.getStyleForCustomStyle(customStyle);
if (style) {
// HTMLImports polyfill may have cloned the style into the main document,
// which is referenced with __appliedElement.
const styleToTransform = /** @type {!HTMLStyleElement} */(style['__appliedElement'] || style);
if (transformFn) {
transformFn(styleToTransform);
}
customStyle[CACHED_STYLE] = styleToTransform;
}
}
return cs;
}
}
/* eslint-disable no-self-assign */
CustomStyleInterface.prototype['addCustomStyle'] = CustomStyleInterface.prototype.addCustomStyle;
CustomStyleInterface.prototype['getStyleForCustomStyle'] = CustomStyleInterface.prototype.getStyleForCustomStyle;
CustomStyleInterface.prototype['processStyles'] = CustomStyleInterface.prototype.processStyles;
/* eslint-enable no-self-assign */
Object.defineProperties(CustomStyleInterface.prototype, {
'transformCallback': {
/** @return {?function(!HTMLStyleElement)} */
get() {
return transformFn;
},
/** @param {?function(!HTMLStyleElement)} fn */
set(fn) {
transformFn = fn;
}
},
'validateCallback': {
/** @return {?function()} */
get() {
return validateFn;
},
/**
* @param {?function()} fn
* @this {CustomStyleInterface}
*/
set(fn) {
let needsEnqueue = false;
if (!validateFn) {
needsEnqueue = true;
}
validateFn = fn;
if (needsEnqueue) {
this.enqueueDocumentValidation();
}
},
}
})
/** @typedef {{
* customStyles: !Array<!CustomStyleProvider>,
* addCustomStyle: function(!CustomStyleProvider),
* getStyleForCustomStyle: function(!CustomStyleProvider): HTMLStyleElement,
* findStyles: function(),
* transformCallback: ?function(!HTMLStyleElement),
* validateCallback: ?function()
* }}
*/
export const CustomStyleInterfaceInterface = {};