/* globals define */ ;(function(define){'use strict';define(function(require,exports,module){ /** * Locals */ var textContent = Object.getOwnPropertyDescriptor(Node.prototype, 'textContent'); var innerHTML = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML'); var removeAttribute = Element.prototype.removeAttribute; var setAttribute = Element.prototype.setAttribute; var noop = function() {}; /** * Register a new component. * * @param {String} name * @param {Object} props * @return {constructor} * @public */ exports.register = function(name, props) { var baseProto = getBaseProto(props.extends); var template = props.template || baseProto.templateString; // Components are extensible by default but can be declared // as non extensible as an optimization to avoid // storing the template strings var extensible = props.extensible = props.hasOwnProperty('extensible')? props.extensible : true; // Clean up delete props.extends; // Pull out CSS that needs to be in the light-dom if (template) { // Stores the string to be reprocessed when // a new component extends this one if (extensible && props.template) { props.templateString = props.template; } var output = processCss(template, name); props.template = document.createElement('template'); props.template.innerHTML = output.template; props.lightCss = output.lightCss; props.globalCss = props.globalCss || ''; props.globalCss += output.globalCss; } // Inject global CSS into the document, // and delete as no longer needed injectGlobalCss(props.globalCss); delete props.globalCss; // Merge base getter/setter attributes with the user's, // then define the property descriptors on the prototype. var descriptors = mixin(props.attrs || {}, base.descriptors); // Store the orginal descriptors somewhere // a little more private and delete the original props._attrs = props.attrs; delete props.attrs; // Create the prototype, extended from base and // define the descriptors directly on the prototype var proto = createProto(baseProto, props); Object.defineProperties(proto, descriptors); // Register the custom-element and return the constructor try { return document.registerElement(name, { prototype: proto }); } catch (e) { if (e.name !== 'NotSupportedError') { throw e; } } }; var base = { properties: { GaiaComponent: true, attributeChanged: noop, attached: noop, detached: noop, created: noop, createdCallback: function() { if (this.rtl) { addDirObserver(); } injectLightCss(this); this.created(); }, /** * It is very common to want to keep object * properties in-sync with attributes, * for example: * * el.value = 'foo'; * el.setAttribute('value', 'foo'); * * So we support an object on the prototype * named 'attrs' to provide a consistent * way for component authors to define * these properties. When an attribute * changes we keep the attr[name] * up-to-date. * * @param {String} name * @param {String||null} from * @param {String||null} to */ attributeChangedCallback: function(name, from, to) { var prop = toCamelCase(name); if (this._attrs && this._attrs[prop]) { this[prop] = to; } this.attributeChanged(name, from, to); }, attachedCallback: function() { this.attached(); }, detachedCallback: function() { this.detached(); }, /** * A convenient method for setting up * a shadow-root using the defined template. * * @return {ShadowRoot} */ setupShadowRoot: function() { if (!this.template) { return; } var node = document.importNode(this.template.content, true); this.createShadowRoot().appendChild(node); return this.shadowRoot; }, /** * Sets an attribute internally * and externally. This is so that * we can style internal shadow-dom * content. * * @param {String} name * @param {String} value */ setAttr: function(name, value) { var internal = this.shadowRoot.firstElementChild; setAttribute.call(internal, name, value); setAttribute.call(this, name, value); }, /** * Removes an attribute internally * and externally. This is so that * we can style internal shadow-dom * content. * * @param {String} name * @param {String} value */ removeAttr: function(name) { var internal = this.shadowRoot.firstElementChild; removeAttribute.call(internal, name); removeAttribute.call(this, name); } }, descriptors: { textContent: { set: function(value) { textContent.set.call(this, value); if (this.lightStyle) { this.appendChild(this.lightStyle); } }, get: function() { return textContent.get(); } }, innerHTML: { set: function(value) { innerHTML.set.call(this, value); if (this.lightStyle) { this.appendChild(this.lightStyle); } }, get: innerHTML.get } } }; /** * The default base prototype to use * when `extends` is undefined. * * @type {Object} */ var defaultPrototype = createProto(HTMLElement.prototype, base.properties); /** * Returns a suitable prototype based * on the object passed. * * @private * @param {HTMLElementPrototype|undefined} proto * @return {HTMLElementPrototype} */ function getBaseProto(proto) { if (!proto) { return defaultPrototype; } proto = proto.prototype || proto; return !proto.GaiaComponent ? createProto(proto, base.properties) : proto; } /** * Extends the given proto and mixes * in the given properties. * * @private * @param {Object} proto * @param {Object} props * @return {Object} */ function createProto(proto, props) { return mixin(Object.create(proto), props); } /** * Detects presence of shadow-dom * CSS selectors. * * @private * @return {Boolean} */ var hasShadowCSS = (function() { var div = document.createElement('div'); try { div.querySelector(':host'); div.querySelector('::content'); return true; } catch (e) { return false; } })(); /** * Regexs used to extract shadow-css * * @type {Object} */ var regex = { shadowCss: /(?:\:host|\:\:content)[^{]*\{[^}]*\}/g, ':host': /(?:\:host)/g, ':host()': /\:host\((.+)\)(?: \:\:content)?/g, ':host-context': /\:host-context\((.+)\)([^{,]+)?/g, '::content': /(?:\:\:content)/g }; /** * Extracts the :host and ::content rules * from the shadow-dom CSS and rewrites * them to work from the