| /** |
| @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 = {}; |