infamous
Version:
A CSS3D/WebGL UI library.
234 lines (194 loc) • 8.96 kB
JavaScript
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;
;