mirror of
https://github.com/NoRedInk/noredink-ui.git
synced 2024-11-24 00:42:29 +03:00
Direct class compatibility (#146)
* drop support for attributes and move class compatibility into factory * fix docs * bring back attributes for now * forgot attribute change in class, don't load shim * delete native-shim file * make it more minification-friendly * fix docs * no need for method names
This commit is contained in:
parent
4309dd85d1
commit
aac0c1febc
@ -3,27 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
// Return a function for making an event based on what the browser supports.
|
/**
|
||||||
// IE11 doesn't support Event constructor, and uses the old Java-style
|
|
||||||
// methods instead
|
|
||||||
function makeMakeEvent() {
|
|
||||||
try {
|
|
||||||
// if calling Event with new works, do it that way
|
|
||||||
var testEvent = new CustomEvent('myEvent', { detail: 1 })
|
|
||||||
return function makeEventNewStyle(type, detail) {
|
|
||||||
return new CustomEvent(type, { detail: detail })
|
|
||||||
}
|
|
||||||
} catch (_error) {
|
|
||||||
// if calling CustomEvent with new throws an error, do it the old way
|
|
||||||
return function makeEventOldStyle(type, detail) {
|
|
||||||
var event = document.createEvent('CustomEvent')
|
|
||||||
event.initCustomEvent(type, false, false, detail)
|
|
||||||
return event
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a DOM Event without worrying about browser compatibility
|
* Create a DOM Event without worrying about browser compatibility
|
||||||
*
|
*
|
||||||
* @param {string} eventName The name of the event to create
|
* @param {string} eventName The name of the event to create
|
||||||
@ -35,6 +15,8 @@ function makeMakeEvent() {
|
|||||||
*/
|
*/
|
||||||
exports.makeEvent = makeMakeEvent()
|
exports.makeEvent = makeMakeEvent()
|
||||||
|
|
||||||
|
var makeClass = makeMakeClass()
|
||||||
|
|
||||||
function noOp() {}
|
function noOp() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,6 +45,21 @@ function noOp() {}
|
|||||||
* this._button = document.createElement('button')
|
* this._button = document.createElement('button')
|
||||||
* },
|
* },
|
||||||
*
|
*
|
||||||
|
* // Let the custom element runtime know that you want to be notified of
|
||||||
|
* // changes to the `hello` attribute
|
||||||
|
* observedAttributes: ['hello'],
|
||||||
|
*
|
||||||
|
* // Do any updating when an attribute changes on the element. Note the
|
||||||
|
* // difference between attributes and properties of an element (see:
|
||||||
|
* // https://javascript.info/dom-attributes-and-properties). This is a
|
||||||
|
* // proxy for `attributeChangedCallback` (see:
|
||||||
|
* // https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#Using_the_lifecycle_callbacks).
|
||||||
|
* // Takes the name of the attribute that changed, the previous string value,
|
||||||
|
* // and the new string value.
|
||||||
|
* onAttributeChange: function(name, previous, next) {
|
||||||
|
* if (name === 'hello') this._hello = next
|
||||||
|
* },
|
||||||
|
*
|
||||||
* // Do any setup work after the element has been inserted into the DOM.
|
* // Do any setup work after the element has been inserted into the DOM.
|
||||||
* // Takes no arguments. This is a proxy for `connectedCallback` (see:
|
* // Takes no arguments. This is a proxy for `connectedCallback` (see:
|
||||||
* // https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#Using_the_lifecycle_callbacks)
|
* // https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#Using_the_lifecycle_callbacks)
|
||||||
@ -79,21 +76,6 @@ function noOp() {}
|
|||||||
* document.removeEventListener('click', this._onDocClick)
|
* document.removeEventListener('click', this._onDocClick)
|
||||||
* },
|
* },
|
||||||
*
|
*
|
||||||
* // Let the custom element runtime know that you want to be notified of
|
|
||||||
* // changes to the `hello` attribute
|
|
||||||
* observedAttributes: ['hello'],
|
|
||||||
*
|
|
||||||
* // Do any updating when an attribute changes on the element. Note the
|
|
||||||
* // difference between attributes and properties of an element (see:
|
|
||||||
* // https://javascript.info/dom-attributes-and-properties). This is a
|
|
||||||
* // proxy for `attributeChangedCallback` (see:
|
|
||||||
* // https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#Using_the_lifecycle_callbacks).
|
|
||||||
* // Takes the name of the attribute that changed, the previous string value,
|
|
||||||
* // and the new string value.
|
|
||||||
* onAttributeChange: function(name, previous, next) {
|
|
||||||
* if (name === 'hello') this._hello = next
|
|
||||||
* },
|
|
||||||
*
|
|
||||||
* // Set up properties. These allow you to expose data to Elm's virtual DOM.
|
* // Set up properties. These allow you to expose data to Elm's virtual DOM.
|
||||||
* // You can use any value that can be encoded as a `Json.Encode.Value`.
|
* // You can use any value that can be encoded as a `Json.Encode.Value`.
|
||||||
* // You'll often want to implement updates to some visual detail of your element
|
* // You'll often want to implement updates to some visual detail of your element
|
||||||
@ -120,7 +102,7 @@ function noOp() {}
|
|||||||
* alert('document clicked')
|
* alert('document clicked')
|
||||||
* },
|
* },
|
||||||
* _onButtonClick: function() {
|
* _onButtonClick: function() {
|
||||||
* alert("clicked on #{@_hello} button")
|
* alert('clicked on ' + this._hello + ' button')
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* })
|
* })
|
||||||
@ -130,44 +112,95 @@ exports.create = function create(config) {
|
|||||||
throw Error('Custom element with tag name ' + config.tagName + ' already exists.')
|
throw Error('Custom element with tag name ' + config.tagName + ' already exists.')
|
||||||
}
|
}
|
||||||
|
|
||||||
config.methods = config.methods || {}
|
var observedAttributes = config.observedAttributes || []
|
||||||
config.properties = config.properties || {}
|
var methods = config.methods || {}
|
||||||
|
var properties = config.properties || {}
|
||||||
|
var initialize = config.initialize || noOp
|
||||||
|
var onConnect = config.onConnect || noOp
|
||||||
|
var onDisconnect = config.onDisconnect || noOp
|
||||||
|
var onAttributeChange = config.onAttributeChange || noOp
|
||||||
|
|
||||||
function CustomElementConstructor() {
|
var Class = makeClass()
|
||||||
// This is the best we can do to trick modern browsers into thinking this
|
|
||||||
// is a real, legitimate class constructor and not a plane old JS function.
|
for (var key in methods) {
|
||||||
var _this = HTMLElement.call(this) || this
|
if (!methods.hasOwnProperty(key)) continue
|
||||||
|
Class.prototype[key] = methods[key]
|
||||||
if (typeof config.initialize === 'function') {
|
|
||||||
config.initialize.call(_this)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var key in config.methods) {
|
|
||||||
if (!config.methods.hasOwnProperty(key)) continue
|
|
||||||
var method = config.methods[key]
|
|
||||||
if (typeof method !== 'function') continue
|
|
||||||
_this[key] = method.bind(_this)
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperties(_this, config.properties)
|
|
||||||
return _this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some browsers respect this in various debugging tools.
|
Object.defineProperties(Class.prototype, properties)
|
||||||
CustomElementConstructor.displayName = '<' + config.tagName + '> custom element'
|
|
||||||
|
|
||||||
CustomElementConstructor.prototype = Object.create(HTMLElement.prototype)
|
Class.prototype.connectedCallback = onConnect
|
||||||
CustomElementConstructor.prototype.constructor = CustomElementConstructor
|
Class.prototype.disconnectedCallback = onDisconnect
|
||||||
CustomElementConstructor.prototype.connectedCallback = config.onConnect
|
Class.prototype.attributeChangedCallback = onAttributeChange
|
||||||
CustomElementConstructor.prototype.disconnectedCallback = config.onDisconnect
|
if (Array.isArray(observedAttributes)) {
|
||||||
CustomElementConstructor.prototype.attributeChangedCallback = config.onAttributeChange
|
Object.defineProperty(Class, 'observedAttributes', {
|
||||||
|
get: function () { return observedAttributes }
|
||||||
if (config.observedAttributes) {
|
|
||||||
var observedAttributes = config.observedAttributes
|
|
||||||
Object.defineProperty(CustomElementConstructor, 'observedAttributes', {
|
|
||||||
get: function() { return observedAttributes }
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define(config.tagName, CustomElementConstructor)
|
Class.displayName = '<' + config.tagName + '> custom element'
|
||||||
|
customElements.define(config.tagName, Class)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to make an ES6 class using the Function constructor rather than
|
||||||
|
* ordinary class syntax. The string we pass to the Function constructor is
|
||||||
|
* static so there is no script injection risk. It allows us to catch
|
||||||
|
* syntax errors at runtime for older browsers that don't support class and
|
||||||
|
* fall back to an ES5 constructor function.
|
||||||
|
*/
|
||||||
|
function makeMakeClass() {
|
||||||
|
try {
|
||||||
|
return new Function([
|
||||||
|
"return class extends HTMLElement {",
|
||||||
|
" constructor() {",
|
||||||
|
" super()",
|
||||||
|
" for (var key in this) {",
|
||||||
|
" var value = this[key]",
|
||||||
|
" if (typeof value !== 'function') continue",
|
||||||
|
" this[key] = value.bind(this)",
|
||||||
|
" }",
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
].join("\n"))
|
||||||
|
} catch (e) {
|
||||||
|
return function () {
|
||||||
|
function Class() {
|
||||||
|
// This is the best we can do to trick modern browsers into thinking this
|
||||||
|
// is a real, legitimate class constructor and not a plane old JS function.
|
||||||
|
var _this = HTMLElement.call(this) || this
|
||||||
|
for (var key in _this) {
|
||||||
|
var value = _this[key]
|
||||||
|
if (typeof value !== 'function') continue
|
||||||
|
_this[key] = value.bind(_this)
|
||||||
|
}
|
||||||
|
return _this
|
||||||
|
}
|
||||||
|
Class.prototype = Object.create(HTMLElement.prototype)
|
||||||
|
Class.prototype.constructor = Class
|
||||||
|
return Class
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a function for making an event based on what the browser supports.
|
||||||
|
* IE11 doesn't support Event constructor, and uses the old Java-style
|
||||||
|
* methods instead
|
||||||
|
*/
|
||||||
|
function makeMakeEvent() {
|
||||||
|
try {
|
||||||
|
// if calling Event with new works, do it that way
|
||||||
|
var testEvent = new CustomEvent('myEvent', { detail: 1 })
|
||||||
|
return function makeEventNewStyle(type, detail) {
|
||||||
|
return new CustomEvent(type, { detail: detail })
|
||||||
|
}
|
||||||
|
} catch (_error) {
|
||||||
|
// if calling CustomEvent with new throws an error, do it the old way
|
||||||
|
return function makeEventOldStyle(type, detail) {
|
||||||
|
var event = document.createEvent('CustomEvent')
|
||||||
|
event.initCustomEvent(type, false, false, detail)
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,164 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2016 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
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This shim allows elements written in, or compiled to, ES5 to work on native
|
|
||||||
* implementations of Custom Elements.
|
|
||||||
*
|
|
||||||
* ES5-style classes don't work with native Custom Elements because the
|
|
||||||
* HTMLElement constructor uses the value of `new.target` to look up the custom
|
|
||||||
* element definition for the currently called constructor. `new.target` is only
|
|
||||||
* set when `new` is called and is only propagated via super() calls. super()
|
|
||||||
* is not emulatable in ES5. The pattern of `SuperClass.call(this)`` only works
|
|
||||||
* when extending other ES5-style classes, and does not propagate `new.target`.
|
|
||||||
*
|
|
||||||
* This shim allows the native HTMLElement constructor to work by generating and
|
|
||||||
* registering a stand-in class instead of the users custom element class. This
|
|
||||||
* stand-in class's constructor has an actual call to super().
|
|
||||||
* `customElements.define()` and `customElements.get()` are both overridden to
|
|
||||||
* hide this stand-in class from users.
|
|
||||||
*
|
|
||||||
* In order to create instance of the user-defined class, rather than the stand
|
|
||||||
* in, the stand-in's constructor swizzles its instances prototype and invokes
|
|
||||||
* the user-defined constructor. When the user-defined constructor is called
|
|
||||||
* directly it creates an instance of the stand-in class to get a real extension
|
|
||||||
* of HTMLElement and returns that.
|
|
||||||
*
|
|
||||||
* There are two important constructors: A patched HTMLElement constructor, and
|
|
||||||
* the StandInElement constructor. They both will be called to create an element
|
|
||||||
* but which is called first depends on whether the browser creates the element
|
|
||||||
* or the user-defined constructor is called directly. The variables
|
|
||||||
* `browserConstruction` and `userConstruction` control the flow between the
|
|
||||||
* two constructors.
|
|
||||||
*
|
|
||||||
* This shim should be better than forcing the polyfill because:
|
|
||||||
* 1. It's smaller
|
|
||||||
* 2. All reaction timings are the same as native (mostly synchronous)
|
|
||||||
* 3. All reaction triggering DOM operations are automatically supported
|
|
||||||
*
|
|
||||||
* There are some restrictions and requirements on ES5 constructors:
|
|
||||||
* 1. All constructors in a inheritance hierarchy must be ES5-style, so that
|
|
||||||
* they can be called with Function.call(). This effectively means that the
|
|
||||||
* whole application must be compiled to ES5.
|
|
||||||
* 2. Constructors must return the value of the emulated super() call. Like
|
|
||||||
* `return SuperClass.call(this)`
|
|
||||||
* 3. The `this` reference should not be used before the emulated super() call
|
|
||||||
* just like `this` is illegal to use before super() in ES6.
|
|
||||||
* 4. Constructors should not create other custom elements before the emulated
|
|
||||||
* super() call. This is the same restriction as with native custom
|
|
||||||
* elements.
|
|
||||||
*
|
|
||||||
* Compiling valid class-based custom elements to ES5 will satisfy these
|
|
||||||
* requirements with the latest version of popular transpilers.
|
|
||||||
*/
|
|
||||||
(() => {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Do nothing if `customElements` does not exist.
|
|
||||||
if (!window.customElements) return;
|
|
||||||
|
|
||||||
const NativeHTMLElement = window.HTMLElement;
|
|
||||||
const nativeDefine = window.customElements.define;
|
|
||||||
const nativeGet = window.customElements.get;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map of user-provided constructors to tag names.
|
|
||||||
*
|
|
||||||
* @type {Map<Function, string>}
|
|
||||||
*/
|
|
||||||
const tagnameByConstructor = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map of tag names to user-provided constructors.
|
|
||||||
*
|
|
||||||
* @type {Map<string, Function>}
|
|
||||||
*/
|
|
||||||
const constructorByTagname = new Map();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the constructors are being called by a browser process, ie parsing
|
|
||||||
* or createElement.
|
|
||||||
*/
|
|
||||||
let browserConstruction = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the constructors are being called by a user-space process, ie
|
|
||||||
* calling an element constructor.
|
|
||||||
*/
|
|
||||||
let userConstruction = false;
|
|
||||||
|
|
||||||
window.HTMLElement = function() {
|
|
||||||
if (!browserConstruction) {
|
|
||||||
const tagname = tagnameByConstructor.get(this.constructor);
|
|
||||||
const fakeClass = nativeGet.call(window.customElements, tagname);
|
|
||||||
|
|
||||||
// Make sure that the fake constructor doesn't call back to this constructor
|
|
||||||
userConstruction = true;
|
|
||||||
const instance = new (fakeClass)();
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
// Else do nothing. This will be reached by ES5-style classes doing
|
|
||||||
// HTMLElement.call() during initialization
|
|
||||||
browserConstruction = false;
|
|
||||||
};
|
|
||||||
// By setting the patched HTMLElement's prototype property to the native
|
|
||||||
// HTMLElement's prototype we make sure that:
|
|
||||||
// document.createElement('a') instanceof HTMLElement
|
|
||||||
// works because instanceof uses HTMLElement.prototype, which is on the
|
|
||||||
// ptototype chain of built-in elements.
|
|
||||||
window.HTMLElement.prototype = NativeHTMLElement.prototype;
|
|
||||||
|
|
||||||
const define = (tagname, elementClass) => {
|
|
||||||
const elementProto = elementClass.prototype;
|
|
||||||
const StandInElement = class extends NativeHTMLElement {
|
|
||||||
constructor() {
|
|
||||||
// Call the native HTMLElement constructor, this gives us the
|
|
||||||
// under-construction instance as `this`:
|
|
||||||
super();
|
|
||||||
|
|
||||||
// The prototype will be wrong up because the browser used our fake
|
|
||||||
// class, so fix it:
|
|
||||||
Object.setPrototypeOf(this, elementProto);
|
|
||||||
|
|
||||||
if (!userConstruction) {
|
|
||||||
// Make sure that user-defined constructor bottom's out to a do-nothing
|
|
||||||
// HTMLElement() call
|
|
||||||
browserConstruction = true;
|
|
||||||
// Call the user-defined constructor on our instance:
|
|
||||||
elementClass.call(this);
|
|
||||||
}
|
|
||||||
userConstruction = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const standInProto = StandInElement.prototype;
|
|
||||||
StandInElement.observedAttributes = elementClass.observedAttributes;
|
|
||||||
standInProto.connectedCallback = elementProto.connectedCallback;
|
|
||||||
standInProto.disconnectedCallback = elementProto.disconnectedCallback;
|
|
||||||
standInProto.attributeChangedCallback = elementProto.attributeChangedCallback;
|
|
||||||
standInProto.adoptedCallback = elementProto.adoptedCallback;
|
|
||||||
|
|
||||||
tagnameByConstructor.set(elementClass, tagname);
|
|
||||||
constructorByTagname.set(tagname, elementClass);
|
|
||||||
nativeDefine.call(window.customElements, tagname, StandInElement);
|
|
||||||
};
|
|
||||||
|
|
||||||
const get = (tagname) => constructorByTagname.get(tagname);
|
|
||||||
|
|
||||||
// Workaround for Safari bug where patching customElements can be lost, likely
|
|
||||||
// due to native wrapper garbage collection issue
|
|
||||||
Object.defineProperty(window, 'customElements',
|
|
||||||
{value: window.customElements, configurable: true, writable: true});
|
|
||||||
Object.defineProperty(window.customElements, 'define',
|
|
||||||
{value: define, configurable: true, writable: true});
|
|
||||||
Object.defineProperty(window.customElements, 'get',
|
|
||||||
{value: get, configurable: true, writable: true});
|
|
||||||
|
|
||||||
})();
|
|
@ -6,7 +6,6 @@
|
|||||||
<link href="assets/reset.css" rel="stylesheet">
|
<link href="assets/reset.css" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Muli:400,400i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Muli:400,400i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet">
|
||||||
<script src="assets/custom-elements/custom-elements.min.js"></script>
|
<script src="assets/custom-elements/custom-elements.min.js"></script>
|
||||||
<script src="assets/custom-elements/native-shim.js"></script>
|
|
||||||
<script src="assets/generated_svgs.js"></script>
|
<script src="assets/generated_svgs.js"></script>
|
||||||
<script src="javascript.js"></script>
|
<script src="javascript.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
Loading…
Reference in New Issue
Block a user