diff --git a/README.md b/README.md
index 4641742..9cde8e3 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,8 @@
Bringing the [regularElements](https://github.com/WebReflection/regular-elements) goodness to a component based world.
- * no polyfills needed for IE11+, it [optionally](https://github.com/WebReflection/regular-elements/#compatibility) works [even in IE9](https://webreflection.github.io/wicked-elements/test/)
- * lightweight as in [~2K lightweight](https://unpkg.com/wicked-elements)
+ * no fancy polyfills needed for IE11+, it [optionally](https://github.com/WebReflection/regular-elements/#compatibility) works [even in IE9](https://webreflection.github.io/wicked-elements/test/)
+ * lightweight as in [~2K lightweight](https://unpkg.com/wicked-elements), with also a [1.7K brotli version](https://unpkg.com/wicked-elements/new.js), for modern browsers only, that drops all unnecessary polyfills for `WeakSet`, `CustomEvent`, `element.matches(...)` or `Object.assign` 🎉
* CPU & RAM friendly (100% based on [handleEvent](https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38) through prototypal inheritance)
* components can exist at any time (past, present, future)
* no issues with classes, it works well with composed behaviors
@@ -131,4 +131,4 @@ wickedElements.define('...', class {
```
-Bear in mind, if the array is empty all attributes changes will be nitified.
+Bear in mind, if the array is empty all attributes changes will be notified.
diff --git a/es.config.js b/es.config.js
new file mode 100644
index 0000000..14c8f10
--- /dev/null
+++ b/es.config.js
@@ -0,0 +1,27 @@
+import resolve from 'rollup-plugin-node-resolve';
+import includePaths from 'rollup-plugin-includepaths';
+
+export default {
+ input: 'esm/index.js',
+ plugins: [
+ includePaths({
+ include: {
+ '@ungap/assign': 'modern/assign.js',
+ '@ungap/element-matches': 'modern/element-matches.js',
+ '@ungap/weakset': 'modern/weakset.js',
+ '@ungap/custom-event': 'modern/custom-event.js',
+ },
+ }),
+ resolve({
+ module: true
+ })
+ ],
+ context: 'null',
+ moduleContext: 'null',
+ output: {
+ exports: 'named',
+ file: 'modern.js',
+ format: 'iife',
+ name: 'wickedElements'
+ }
+};
diff --git a/modern.js b/modern.js
new file mode 100644
index 0000000..2a2972a
--- /dev/null
+++ b/modern.js
@@ -0,0 +1,437 @@
+var wickedElements = wickedElements || (function (Object) {
+ 'use strict';
+
+ var assign = Object.assign;
+
+ function matches (selector) {
+ return this.matches(selector);
+ }
+
+ /*! (c) Andrea Giammarchi */
+ function attributechanged(poly) { var Event = poly.Event;
+ return function observe(node, attributeFilter) {
+ var options = {attributes: true, attributeOldValue: true};
+ var filtered = attributeFilter instanceof Array && attributeFilter.length;
+ if (filtered)
+ options.attributeFilter = attributeFilter.slice(0);
+ try {
+ (new MutationObserver(changes)).observe(node, options);
+ } catch(o_O) {
+ options.handleEvent = filtered ? handleEvent : attrModified;
+ node.addEventListener('DOMAttrModified', options, true);
+ }
+ return node;
+ };
+ function attrModified(event) {
+ dispatchEvent(event.target, event.attrName, event.prevValue);
+ }
+ function dispatchEvent(node, attributeName, oldValue) {
+ var event = new Event('attributechanged');
+ event.attributeName = attributeName;
+ event.oldValue = oldValue;
+ event.newValue = node.getAttribute(attributeName);
+ node.dispatchEvent(event);
+ }
+ function changes(records) {
+ for (var record, i = 0, length = records.length; i < length; i++) {
+ record = records[i];
+ dispatchEvent(record.target, record.attributeName, record.oldValue);
+ }
+ }
+ function handleEvent(event) {
+ if (-1 < this.attributeFilter.indexOf(event.attrName))
+ attrModified(event);
+ }
+ }
+
+ /*! (c) Andrea Giammarchi */
+ function disconnected(poly) { var CONNECTED = 'connected';
+ var DISCONNECTED = 'dis' + CONNECTED;
+ var Event = poly.Event;
+ var WeakSet = poly.WeakSet;
+ var notObserving = true;
+ var observer = new WeakSet;
+ return function observe(node) {
+ if (notObserving) {
+ notObserving = !notObserving;
+ startObserving(node.ownerDocument);
+ }
+ observer.add(node);
+ return node;
+ };
+ function startObserving(document) {
+ var dispatched = null;
+ try {
+ (new MutationObserver(changes)).observe(
+ document,
+ {subtree: true, childList: true}
+ );
+ }
+ catch(o_O) {
+ var timer = 0;
+ var records = [];
+ var reschedule = function (record) {
+ records.push(record);
+ clearTimeout(timer);
+ timer = setTimeout(
+ function () {
+ changes(records.splice(timer = 0, records.length));
+ },
+ 0
+ );
+ };
+ document.addEventListener(
+ 'DOMNodeRemoved',
+ function (event) {
+ reschedule({addedNodes: [], removedNodes: [event.target]});
+ },
+ true
+ );
+ document.addEventListener(
+ 'DOMNodeInserted',
+ function (event) {
+ reschedule({addedNodes: [event.target], removedNodes: []});
+ },
+ true
+ );
+ }
+ function changes(records) {
+ dispatched = new Tracker;
+ for (var
+ record,
+ length = records.length,
+ i = 0; i < length; i++
+ ) {
+ record = records[i];
+ dispatchAll(record.removedNodes, DISCONNECTED, CONNECTED);
+ dispatchAll(record.addedNodes, CONNECTED, DISCONNECTED);
+ }
+ dispatched = null;
+ }
+ function dispatchAll(nodes, type, counter) {
+ for (var
+ node,
+ event = new Event(type),
+ length = nodes.length,
+ i = 0; i < length;
+ (node = nodes[i++]).nodeType === 1 &&
+ dispatchTarget(node, event, type, counter)
+ );
+ }
+ function dispatchTarget(node, event, type, counter) {
+ if (observer.has(node) && !dispatched[type].has(node)) {
+ dispatched[counter].delete(node);
+ dispatched[type].add(node);
+ node.dispatchEvent(event);
+ /*
+ // The event is not bubbling (perf reason: should it?),
+ // hence there's no way to know if
+ // stop/Immediate/Propagation() was called.
+ // Should DOM Level 0 work at all?
+ // I say it's a YAGNI case for the time being,
+ // and easy to implement in user-land.
+ if (!event.cancelBubble) {
+ var fn = node['on' + type];
+ if (fn)
+ fn.call(node, event);
+ }
+ */
+ }
+ for (var
+ // apparently is node.children || IE11 ... ^_^;;
+ // https://github.com/WebReflection/disconnected/issues/1
+ children = node.children || [],
+ length = children.length,
+ i = 0; i < length;
+ dispatchTarget(children[i++], event, type, counter)
+ );
+ }
+ function Tracker() {
+ this[CONNECTED] = new WeakSet;
+ this[DISCONNECTED] = new WeakSet;
+ }
+ }
+ }
+
+ /**
+ * ISC License
+ *
+ * Copyright (c) 2018, Andrea Giammarchi, @WebReflection
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+ var poly = {Event: CustomEvent, WeakSet: WeakSet};
+ var contains = document.contains || function (el) {
+ while (el && el !== this) el = el.parentNode;
+ return this === el;
+ };
+
+ var bootstrap = true;
+
+ var query = [];
+ var config = [];
+ var waiting = {};
+ var known = {};
+
+ var regularElements = {
+ define: function (selector, options) {
+ if (bootstrap) {
+ bootstrap = false;
+ init(document);
+ }
+ var type = typeof selector;
+ if (type === 'string') {
+ if (get(selector))
+ throw new Error('duplicated: ' + selector);
+ query.push(selector);
+ config.push(options || {});
+ ready();
+ if (selector in waiting) {
+ var cfg = get(selector);
+ if (cfg) {
+ waiting[selector](cfg);
+ delete waiting[selector];
+ }
+ }
+ } else {
+ if (type !== "object" || selector.nodeType !== 1)
+ throw new Error('undefinable: ' + selector);
+ setupListeners(selector, options || {});
+ }
+ },
+ get: get,
+ whenDefined: function (selector) {
+ return Promise.resolve(
+ get(selector) ||
+ new Promise(function ($) {
+ waiting[selector] = $;
+ })
+ );
+ }
+ };
+
+ // passing along regularElements as poly for Event and WeakSet
+ var lifecycle = disconnected(poly);
+ var observe = {
+ attributechanged: attributechanged(poly),
+ connected: lifecycle,
+ disconnected: lifecycle
+ };
+
+ function changes(records) {
+ for (var i = 0, length = records.length; i < length; i++)
+ setupList(records[i].addedNodes, false);
+ }
+
+ function get(selector) {
+ var i = query.indexOf(selector);
+ return i < 0 ? null : assign({}, config[i]);
+ }
+
+ function init(doc) {
+ try {
+ (new MutationObserver(changes))
+ .observe(doc, {subtree: true, childList: true});
+ }
+ catch(o_O) {
+ doc.addEventListener(
+ 'DOMNodeInserted',
+ function (e) {
+ changes([{addedNodes: [e.target]}]);
+ },
+ false
+ );
+ }
+ if (doc.readyState !== 'complete')
+ doc.addEventListener('DOMContentLoaded', ready, {once: true});
+ }
+
+ function ready() {
+ if (query.length)
+ setupList(document.querySelectorAll(query), true);
+ }
+
+ function setup(node) {
+ setupList(node.querySelectorAll(query), true);
+ for (var ws, css, i = 0, length = query.length; i < length; i++) {
+ css = query[i];
+ ws = known[css] || (known[css] = new WeakSet);
+ if (!ws.has(node) && matches.call(node, query[i])) {
+ ws.add(node);
+ setupListeners(node, config[i]);
+ }
+ }
+ }
+
+ function setupList(nodes, isElement) {
+ for (var node, i = 0, length = nodes.length; i < length; i++) {
+ node = nodes[i];
+ if (isElement || node.nodeType === 1)
+ setup(node);
+ }
+ }
+
+ function setupListener(node, options, type, dispatch) {
+ var method = options['on' + type];
+ if (method) {
+ observe[type](node, options.attributeFilter)
+ .addEventListener(type, method, false);
+ if (dispatch && contains.call(document, node))
+ node.dispatchEvent(new CustomEvent(type));
+ }
+ }
+
+ function setupListeners(node, options) {
+ setupListener(node, options, 'attributechanged', false);
+ setupListener(node, options, 'disconnected', false);
+ setupListener(node, options, 'connected', true);
+ }
+
+ /**
+ * ISC License
+ *
+ * Copyright (c) 2018, Andrea Giammarchi, @WebReflection
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+ // minifier friendly constants
+ var ONDISCONNECTED = 'ondisconnected';
+ var ONATTRIBUTECHANGED = 'onattributechanged';
+
+ // one off scoped shortcut
+ var create = Object.create;
+ var defineProperty = Object.defineProperty;
+ var getOwnPropertyNames = Object.getOwnPropertyNames;
+ var getPrototypeOf = Object.getPrototypeOf;
+ var hasOwnProperty = Object.hasOwnProperty;
+ var root = Object.prototype;
+
+ // NOTE: the component is not returned,
+ // only its initial definition.
+ // This works well in terms of security
+ // so that a component prototype won't be
+ // exposed directly through the API.
+ var wickedElements = wickedElements || create(regularElements, {
+ define: {
+ value: function (selector, component) {
+ var ws = new WeakSet;
+ var definition = {onconnected: setup};
+ var isClass = typeof component === 'function';
+ var proto = isClass ? component.prototype : component;
+ var events = getEvents(proto);
+ if (ONDISCONNECTED in proto)
+ definition[ONDISCONNECTED] = setup;
+ if (ONATTRIBUTECHANGED in proto) {
+ definition[ONATTRIBUTECHANGED] = setup;
+ definition.attributeFilter =
+ (isClass ?
+ component.observedAttributes :
+ proto.observedAttributes) ||
+ proto.attributeFilter || [];
+ }
+ addIfNeeded(proto, 'init', init$1);
+ addIfNeeded(proto, 'handleEvent', handleEvent);
+ regularElements.define(selector, definition);
+ if (hasOwnProperty.call(component, 'style'))
+ injectStyle(component.style);
+ function setup(event) {
+ var el = event.currentTarget;
+ var type = event.type;
+ el.removeEventListener(type, setup, false);
+ if (!ws.has(el)) {
+ ws.add(el);
+ bootstrap$1(
+ isClass ? new component : create(proto),
+ events, event, el, type
+ );
+ }
+ }
+ }
+ }
+ });
+
+ function addIfNeeded(component, key, value) {
+ if (!(key in component))
+ defineProperty(component, key, {value: value});
+ }
+
+ function bootstrap$1(handler, events, event, el, method) {
+ var init = false;
+ var i = 0;
+ var length = events.length;
+ while (i < length) {
+ var evt = events[i++];
+ el.addEventListener(evt.type, handler, evt.options);
+ init = (init || evt.type === method);
+ }
+ handler.init(event);
+ if (init)
+ handler.handleEvent(event);
+ }
+
+ function getEvents(proto) {
+ var events = [];
+ while (proto && proto !== root) {
+ var keys = getOwnPropertyNames(proto);
+ var i = 0;
+ var length = keys.length;
+ while (i < length) {
+ var key = keys[i++];
+ if (key.slice(0, 2) === 'on' && key.slice(-7) !== 'Options')
+ events.push({
+ type: key.slice(2),
+ options: proto[key + 'Options'] || false
+ });
+ }
+ proto = getPrototypeOf(proto);
+ }
+ return events;
+ }
+
+ function handleEvent(event) {
+ var type = 'on' + event.type;
+ if (type in this)
+ this[type](event);
+ }
+
+ function init$1(event) {
+ this.el = event.currentTarget;
+ }
+
+ function injectStyle(cssText) {
+ var style = document.createElement('style');
+ style.type = 'text/css';
+ if (style.styleSheet)
+ style.styleSheet.cssText = cssText;
+ else
+ style.textContent = cssText;
+ (document.head || document.querySelector('head')).appendChild(style);
+ }
+
+
+
+ return wickedElements;
+
+}(Object));
diff --git a/modern/assign.js b/modern/assign.js
new file mode 100644
index 0000000..9379ae6
--- /dev/null
+++ b/modern/assign.js
@@ -0,0 +1 @@
+export default Object.assign;
diff --git a/modern/custom-event.js b/modern/custom-event.js
new file mode 100644
index 0000000..f0f9758
--- /dev/null
+++ b/modern/custom-event.js
@@ -0,0 +1 @@
+export default CustomEvent;
diff --git a/modern/element-matches.js b/modern/element-matches.js
new file mode 100644
index 0000000..b67172d
--- /dev/null
+++ b/modern/element-matches.js
@@ -0,0 +1,3 @@
+export default function (selector) {
+ return this.matches(selector);
+};
diff --git a/modern/weakset.js b/modern/weakset.js
new file mode 100644
index 0000000..bd45ce9
--- /dev/null
+++ b/modern/weakset.js
@@ -0,0 +1 @@
+export default WeakSet;
diff --git a/new.js b/new.js
new file mode 100644
index 0000000..2928ebe
--- /dev/null
+++ b/new.js
@@ -0,0 +1 @@
+/*! (c) Andrea Giammarchi - ISC */var wickedElements=wickedElements||function(e){"use strict";var n=e.assign;function i(e){return this.matches(e)}var t,s,l,f,v,r,h,o,a,d={Event:CustomEvent,WeakSet:WeakSet},c=document.contains||function(e){for(;e&&e!==this;)e=e.parentNode;return this===e},u=!0,p=[],y=[],b={},g={},m={define:function(e,t){u&&(u=!1,function(t){try{new MutationObserver(M).observe(t,{subtree:!0,childList:!0})}catch(e){t.addEventListener("DOMNodeInserted",function(e){M([{addedNodes:[e.target]}])},!1)}"complete"!==t.readyState&&t.addEventListener("DOMContentLoaded",k,{once:!0})}(document));var n=typeof e;if("string"==n){if(T(e))throw new Error("duplicated: "+e);if(p.push(e),y.push(t||{}),k(),e in b){var r=T(e);r&&(b[e](r),delete b[e])}}else{if("object"!=n||1!==e.nodeType)throw new Error("undefinable: "+e);x(e,t||{})}},get:T,whenDefined:function(t){return Promise.resolve(T(t)||new Promise(function(e){b[t]=e}))}},w=(l="dis"+(s="connected"),f=(t=d).Event,v=t.WeakSet,r=!0,h=new v,function(e){return r&&(r=!r,function(t){var d=null;try{new MutationObserver(i).observe(t,{subtree:!0,childList:!0})}catch(e){var n=0,r=[],o=function(e){r.push(e),clearTimeout(n),n=setTimeout(function(){i(r.splice(n=0,r.length))},0)};t.addEventListener("DOMNodeRemoved",function(e){o({addedNodes:[],removedNodes:[e.target]})},!0),t.addEventListener("DOMNodeInserted",function(e){o({addedNodes:[e.target],removedNodes:[]})},!0)}function i(e){d=new u;for(var t,n=e.length,r=0;r min.js",
- "size": "cat min.js | gzip -9 | wc -c && cat min.js | brotli | wc -c",
- "cleanup": "cat index.js|sed 's/(exports)/(Object)/'|sed 's/return exports;/return wickedElements;/'|sed -e 's/exports.*;//g'|sed 's/({})/(Object)/'>index.clean&&mv index.clean index.js",
+ "build": "ascjs esm cjs && npm run rollup && npm run new && npm run cleanup:min && npm run cleanup:new && npm run min && npm run size",
+ "new": "rollup --config es.config.js",
+ "min": "echo \"/*! (c) Andrea Giammarchi - ISC */$(uglifyjs index.js -c -m)\" > min.js && echo \"/*! (c) Andrea Giammarchi - ISC */$(uglifyjs modern.js -c -m)\" > new.js",
+ "rollup": "rollup --config rollup.config.js",
+ "size": "cat min.js | gzip -9 | wc -c && cat min.js | brotli | wc -c && cat new.js | gzip -9 | wc -c && cat new.js | brotli | wc -c",
+ "cleanup:min": "cat modern.js|sed 's/(exports)/(Object)/'|sed 's/return exports;/return wickedElements;/'|sed -e 's/exports.*;//g'|sed 's/({})/(Object)/'|sed 's/var wickedElements =/var wickedElements = wickedElements ||/'>modern.clean&&mv modern.clean modern.js",
+ "cleanup:new": "cat index.js|sed 's/(exports)/(Object)/'|sed 's/return exports;/return wickedElements;/'|sed -e 's/exports.*;//g'|sed 's/({})/(Object)/'>index.clean&&mv index.clean index.js",
"test": "echo 'use http-server and browse the test folder'"
},
"author": "Andrea Giammarchi",
@@ -17,6 +20,7 @@
"devDependencies": {
"ascjs": "^3.0.1",
"rollup": "^1.10.1",
+ "rollup-plugin-includepaths": "^0.2.3",
"rollup-plugin-node-resolve": "^5.0.0",
"uglify-js": "^3.5.9"
},
diff --git a/test/index.html b/test/index.html
index 55d2971..9aab894 100644
--- a/test/index.html
+++ b/test/index.html
@@ -5,8 +5,12 @@
wickedElements
-
-
+
+
+
+
+
+