UNPKG

@zkochan/pnpm

Version:

A fast implementation of npm install

369 lines (310 loc) 9.19 kB
/* global define */ void (function (root, factory) { if (typeof define === 'function' && define.amd) define(factory) else if (typeof exports === 'object') module.exports = factory() else { if (window.jQuery) window.jQuery.onmount = factory() else root.onmount = factory() } }(this, function ($) { /* * Internal: Registry. */ var handlers, behaviors, selectors, log /* * Internal: IDs for auto-incrementing. */ var bid = 0 /* behavior ID */ var cid = 0 /* component ID */ /** * (Module) Adds a behavior, or triggers behaviors. * * When no parameters are passed, it triggers all behaviors. When one * parameter is passed, it triggers the given behavior. Otherwise, it adds a * behavior. * * // define a behavior * $.onmount('.select-box', function () { * $(this).on('...') * }) * * // define a behavior with exit * $.onmount('.select-box', function () { * $(document).on('...') * }, function () { * $(document).off('...') * }) * * // retrigger a onmount * $.onmount('.select-box') * * // retriggers all behaviors * $.onmount() */ function onmount (selector, init, exit) { if (arguments.length === 0 || isjQuery(selector) || isEvent(selector)) { // onmount() - trigger all behaviors. Also account for cases such as // $($.onmount), where it's triggered with a jQuery event object. onmount.poll() } else if (arguments.length === 1) { // onmount(selector) - trigger for a given selector. onmount.poll(selector) } else { // onmount(sel, fn, [fn]) - register a new behavior. var be = new Behavior(selector, init, exit) behaviors.push(be) be.register() } return this } /* * Use jQuery (or a jQuery-like) when available. This will allow * the use of jQuery selectors. */ onmount.$ = window.jQuery || window.Zepto || window.Ender /* * Detect MutationObserver support for `onmount.observe()`. * You may even add a polyfill here via * `onmount.MutationObserver = require('mutation-observer')`. */ onmount.MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver /** * Set this to true if you want to see debug messages. */ onmount.debug = false /** * Internal: triggers behaviors for a selector or for all. * * onmount.poll() * onmount.poll('.js-button') */ onmount.poll = function poll (selector) { if (selector) selector = onmount.selectify(selector) var list = (selector ? selectors[selector] : handlers) || [] each(list, function (item) { item() }) } /** * Observes automatically using MutationObserver events. * * onmount.observe() */ onmount.observe = function observe () { var MutationObserver = onmount.MutationObserver if (typeof MutationObserver === 'undefined') return var obs = new MutationObserver(function (mutations) { each(behaviors, function (be) { each(mutations, function (mutation) { each(mutation.addedNodes, function (el) { if (matches(el, be.selector)) be.visitEnter(el) }) each(mutation.removedNodes, function (el) { if (matches(el, be.selector)) be.visitExit(el) }) }) }) }) obs.observe(document, { subtree: true, childList: true }) onmount.observer = obs // trigger everything before going onmount() return true } /** * Turns off observation first issued by `onmount.observe()`. */ onmount.unobserve = function unobserve () { if (!this.observer) return this.observer.disconnect() delete this.observer } /** * Clears all behaviors. Useful for tests. * This will NOT call exit handlers. */ onmount.reset = function reset () { handlers = onmount.handlers = [] selectors = onmount.selectors = {} behaviors = onmount.behaviors = [] } /** * Internal: Converts `@role` to `[role~="role"]` if needed. You can override * this by reimplementing `onmount.selectify`. * * selectify('@hi') //=> '[role="hi"]' * selectify('.btn') //=> '.btn' */ onmount.selectify = function selectify (selector) { if (selector[0] === '@') { return '[role~="' + selector.substr(1).replace(/"/g, '\\"') + '"]' } return selector } /** * Internal: behavior class */ function Behavior (selector, init, exit) { this.id = 'b' + bid++ this.init = init this.exit = exit this.selector = onmount.selectify(selector) this.loaded = [] // keep track of dom elements loaded for this behavior this.key = '__onmount:' + bid // leave the state in el['__onmount:12'] } /** * Internal: Register this behavior */ Behavior.prototype.register = function () { var be = this var loaded = this.loaded var selector = this.selector register(selector, function () { // clean up old ones and initialize new ones each(loaded, function (element, i) { be.visitExit(element, i) }) query(selector, function () { be.visitEnter(this) }) }) } /** * Internal: visits the element `el` and turns it on if applicable */ Behavior.prototype.visitEnter = function (el) { if (el[this.key]) return var options = { id: 'c' + cid, selector: this.selector } if (this.init.call(el, options) !== false) { if (onmount.debug) log('enter', this.selector, el) el[this.key] = options this.loaded.push(el) cid++ } } /** * Internal: visits the element `el` and sees if it needs its exit handler * called */ Behavior.prototype.visitExit = function (el, i) { if (el && !isAttached(el)) { if (typeof i === 'undefined') i = this.loaded.indexOf(el) this.loaded[i] = undefined if (this.exit && this.exit.call(el, el[this.key]) !== false) { if (onmount.debug) log('exit', this.selector, el) delete el[this.key] } } } /** * Internal: check if an element is still attached to its document. */ function isAttached (el) { while (el) { if (el === document.documentElement) return true el = el.parentElement } } /** * Internal: reimplementation of `$('...').each()`. If jQuery is available, * use it (I guess to preserve IE compatibility and to enable special jQuery * attribute selectors). */ function query (selector, fn) { if (onmount.$) return onmount.$(selector).each(fn) var list = document.querySelectorAll(selector) for (var i = 0, len = list.length; i < len; i++) { fn.apply(list[i]) } } /** * Internal: registers a behavior handler for a selector. */ function register (selector, fn) { if (!selectors[selector]) selectors[selector] = [] selectors[selector].push(fn) handlers.push(fn) } /** * Checks if a given element `el` matches `selector`. * Compare with [$.fn.is](http://api.jquery.com/is/). * * var matches = require('dom101/matches'); * * matches(button, ':focus'); */ function matches (el, selector) { var _matches = el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector if (onmount.$) { return onmount.$(el).is(selector) } else if (_matches) { return _matches.call(el, selector) } else if (el.parentNode) { // IE8 and below var nodes = el.parentNode.querySelectorAll(selector) for (var i = nodes.length; i--; 0) { if (nodes[i] === el) return true } return false } } /** * Iterates through `list` (an array or an object). This is useful when dealing * with NodeLists like `document.querySelectorAll`. * * var each = require('dom101/each'); * var qa = require('dom101/query-selector-all'); * * each(qa('.button'), function (el) { * addClass('el', 'selected'); * }); */ function each (list, fn) { var i var len = list.length if (len === +len) { for (i = 0; i < len; i++) { fn(list[i], i) } } else { for (i in list) { if (list.hasOwnProperty(i)) fn(list[i], i) } } return list } /** * Internal: Check if a given object is jQuery */ function isjQuery ($) { return typeof $ === 'function' && $.fn && $.noConflict } function isEvent (e) { return typeof e === 'object' && e.target } /** * Internal: logging */ var styles = { enter: 'background-color:#dfd;font-weight:bold;color:#141', exit: 'background-color:#fdd;font-weight:bold;color:#411' } if (~navigator.userAgent.indexOf('Mozilla')) { log = function (type, selector, el) { console.log('%c %s ', styles[type], selector, el) } } else { log = function (type, selector, el) { console.log('(onmount)', type, selector) } } /* * Export */ onmount.reset() return onmount }))