UNPKG

infamous

Version:

A CSS3D/WebGL UI library.

234 lines (194 loc) 8.96 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _lowclass = _interopRequireDefault(require("lowclass")); var _Mixin = _interopRequireDefault(require("../core/Mixin")); var _native = require("lowclass/native"); var _Utility = require("../core/Utility"); var _jss = _interopRequireDefault(require("../lib/jss")); var _documentReady = _interopRequireDefault(require("@awaitbox/document-ready")); var _DefaultBehaviors = _interopRequireDefault(require("./behaviors/DefaultBehaviors")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* global customElements */ // Very very stupid hack needed for Safari in order for us to be able to extend // the HTMLElement class. See: // https://github.com/google/traceur-compiler/issues/1709 if (typeof window.HTMLElement != 'function') { const _HTMLElement = function HTMLElement() {}; _HTMLElement.prototype = window.HTMLElement.prototype; window.HTMLElement = _HTMLElement; } function classExtendsHTMLElement(constructor) { if (!constructor) return false; if (constructor === HTMLElement) return true;else return classExtendsHTMLElement(constructor.__proto__); } /** * Creates a WebComponent base class dynamically, depending on which * HTMLElement class you want it to extend from. Extend from WebComponent when * making a new Custom Element class. * * @example * const WebComponent = WebComponentMixin(HTMLButtonElement) * class AwesomeButton extends WebComponent { ... } * * @param {Function} Base The class that the generated WebComponent * base class will extend from. */ var _default = (0, _Mixin.default)(Base => { // the extra `class extends` is necessary here so that // babel-plugin-transform-builtin-classes can work properly. Base = Base || (0, _native.native)(HTMLElement); // XXX: In the future, possibly check for Element if other things besides // HTML are supported (f.e. SVGElements) if (!classExtendsHTMLElement(Base)) { throw new TypeError('The argument to WebComponent.mixin must be a constructor that extends from or is HTMLElement.'); } // otherwise, create it. const WebComponent = (0, _lowclass.default)('WebComponent').extends(_DefaultBehaviors.default.mixin(Base), ({ Super, Public, Private }) => ({ isConnected: false, constructor(...args) { // Throw an error if no Custom Elements v1 API exists. if (!('customElements' in window)) { // TODO: provide a link to the Docs. throw new Error(` Your browser does not support the Custom Elements API. You'll need to install a polyfill. See how at http://.... `); } const self = Super(this).constructor(...args); return self; }, // Subclasses can implement these. childConnectedCallback(child) {}, childDisconnectedCallback(child) {}, connectedCallback() { if (Super(this).connectedCallback) Super(this).connectedCallback(); this.isConnected = true; if (!Private(this).initialized) { this.init(); Private(this).initialized = true; } }, async disconnectedCallback() { if (Super(this).disconnectedCallback) Super(this).disconnectedCallback(); this.isConnected = false; // Deferr to the next tick before cleaning up in case the // element is actually being re-attached somewhere else within this // same tick (detaching and attaching is synchronous, so by // deferring to the next tick we'll be able to know if the element // was re-attached or not in order to clean up or not). Note that // appendChild can be used to move an element to another parent // element, in which case connectedCallback and disconnectedCallback // both get called, and in which case we don't necessarily want to // clean up. If the element gets re-attached before the next tick // (for example, gets moved), then we want to preserve the // stuff that would be cleaned up by an extending class' deinit // method by not running the following this.deinit() call. await Promise.resolve(); // deferr to the next tick. // As mentioned in the previous comment, if the element was not // re-attached in the last tick (for example, it was moved to // another element), then clean up. if (!this.isConnected && Private(this).initialized) { this.deinit(); Private(this).initialized = false; } }, /** * This method can be overridden by extending classes, it should return * JSS-compatible styling. See http://github.com/cssinjs/jss for * documentation. * @abstract */ getStyles() { return {}; }, /** * Init is called exactly once, the first time this element is * connected into the DOM. When an element is disconnected then * connected right away within the same synchronous tick, init() is not * fired again. However, if an element is disconnected and the current * tick completes before the element is connected again, then deinit() * will be called (i.e. the element was not simply moved to a new * location, it was actually removed), then the next time that the * element is connected back into DOM init() will be called again. * * This is in contrast to connectedCallback and disconnectedCallback: * connectedCallback is guaranteed to always fire even if the elemet * was previously disconnected in the same synchronous tick. * * For example, ... * * Subclasses should extend this to add such logic. */ init() { if (!Private(this).style) Private(this).style = Private(this).createStyles(); // Deferral needed in case the Custom Element classes are // registered after the elements are already defined in the // DOM (they're not yet upgraded). This means that children of this node // might be a `<motor-node>` but if they aren't upgraded yet then // their API won't be available to the logic inside the following // call to childConnectedCallback. The reason this happens is // because parents are upgraded first and their // connectedCallbacks fired before their children are // upgraded. // (0, _documentReady.default)().then(() => { // implies a Promise.resolve() behavior if the DOM is already ready // Handle any nodes that may have been connected before `this` node // was created (f.e. child nodes that were connected before the // custom elements were registered and which would therefore not be // detected by the following MutationObserver). if (!Private(this).childObserver) { const children = this.childNodes; for (let l = children.length, i = 0; i < l; i += 1) { this.childConnectedCallback(children[i]); } Private(this).childObserver = (0, _Utility.observeChildren)(this, this.childConnectedCallback, this.childDisconnectedCallback); } }); // fire this.attributeChangedCallback in case some attributes have // existed before the custom element was upgraded. if (!Private(this).initialAttributeChange && this.hasAttributes()) { const { attributes } = this; for (let l = attributes.length, i = 0; i < l; i += 1) this.attributeChangedCallback(attributes[i].name, null, attributes[i].value); } }, // TODO: when we make setAttribute accept non-strings, we need to move // logic from attributeChangedCallback attributeChangedCallback(...args) { if (Super(this).attributeChangedCallback) Super(this).attributeChangedCallback(...args); Private(this).initialAttributeChange = true; }, /** * This is the reciprocal of init(). It will be called when an element * has been disconnected but not re-connected within the same tick. * * The reason that init() and deinit() exist is so that if an element is * moved from one place to another within the same synchronous tick, * that deinit and init logic will not fire unnecessarily. If logic is * needed in that case, then connectedCallback and disconnectedCallback * can be used directly instead. */ deinit() { // Nothing much at the moment, but extending classes can extend // this to add deintialization logic. Private(this).childObserver.disconnect(); }, private: { style: null, initialized: false, initialAttributeChange: false, childObserver: null, createStyles() { const rule = _jss.default.createRule(Public(this).getStyles()); rule.applyTo(Public(this)); return rule; } } })); return WebComponent; }, (0, _native.native)(HTMLElement)); exports.default = _default;