UNPKG

hy-push-state

Version:

Turn static web sites into dynamic web apps

174 lines (139 loc) 9.35 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.eventListenersMixin = void 0; var _rxjs = require("hy-component/src/rxjs"); var _common = require("../common"); function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function () { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } var eventListenersMixin = function eventListenersMixin(C) { return /*#__PURE__*/function (_C) { _inherits(_class, _C); var _super = _createSuper(_class); function _class() { _classCallCheck(this, _class); return _super.apply(this, arguments); } _createClass(_class, [{ key: "setupEventListeners", value: function setupEventListeners() { var _this = this; // #### Keeping track of links // We use a `MutationObserver` to keep track of all the links inside the component, // and put events on the `pushSubject` and `hintSubject` observables, // but first we need to check if `MutationObserver` is available. if ("MutationObserver" in window && "WeakSet" in window) { // A `Set` of `Element`s. // We use this to keep track of which links already have their event listeners registered. var links = new WeakSet(); // Binding `next` functions to their `Subject`s, // so that we can pass them as callbacks directly. This is just for convenience. var pushNext = this.pushSubject.next.bind(this.pushSubject); var hintNext = this.hintSubject.next.bind(this.hintSubject); // We don't use `Observable.fromEvent` here to avoid creating too many observables. // Registering an unknown number of event listeners is somewhat debatable, // but we certainly don't want to make it wrose. // (The number could be brought down by using an `IntersectionObserver` in the future. // Also note that typically there will be an animation playing while this is happening, // so the effects are not easily noticed). // // In any case, `MutationObserver` and `Set` help us keep track of which links are children // of this component, so that we can reliably add and remove the event listeners. // The function to be called for every added node: var addLink = function addLink(link) { if (!links.has(link)) { links.add(link); link.addEventListener("click", pushNext); link.addEventListener("mouseenter", hintNext, { passive: true }); link.addEventListener("touchstart", hintNext, { passive: true }); link.addEventListener("focus", hintNext, { passive: true }); // When fetching resources from an external domain, rewrite the link's href, // so that shift-click, etc works as expected. // if (isExternal(this)) { // link.href = new URL(link.getAttribute("href"), this.href).href; // } } }; var addListeners = function addListeners(addedNode) { if (addedNode instanceof Element) { if (_common.matches.call(addedNode, _this.linkSelector)) { addLink(addedNode); } else { Array.from(addedNode.querySelectorAll(_this.linkSelector)).forEach(addLink); } } }; // Next, The function to be called for every removed node. // Usually the elments will be removed from the document altogher // when they are removed from this component, // but since we can't be sure, we remove the event listeners anyway. var removeLink = function removeLink(link) { links["delete"](link); link.removeEventListener("click", pushNext); link.removeEventListener("mouseenter", hintNext, { passive: true }); link.removeEventListener("touchstart", hintNext, { passive: true }); link.removeEventListener("focus", hintNext, { passive: true }); }; var removeListeners = function removeListeners(removedNode) { if (removedNode instanceof Element) { if (_common.matches.call(removedNode, _this.linkSelector)) { removeLink(removedNode); } else { Array.from(removedNode.querySelectorAll(_this.linkSelector)).forEach(removeLink); } } }; // An observable wrapper around the mutation observer. // We're only interested in nodes entering and leaving the entire subtree of this component, // but not attribute changes. var mutation$ = (0, _rxjs.createXObservable)(MutationObserver)(this.el, {}, { childList: true, subtree: true }); // For every mutation, we remove the event listeners of elements that go out of the component // (if any), and add event listeners to all elements that make it into the compnent (if any). mutation$.subscribe(function (_ref) { var addedNodes = _ref.addedNodes, removedNodes = _ref.removedNodes; Array.from(removedNodes).forEach(removeListeners.bind(_this)); Array.from(addedNodes).forEach(addListeners.bind(_this)); }); // TODO this.subjects.linkSelector.subscribe(function () { // TODO Array.from(links).forEach(removeLink); // The mutation observer does not pick up the links that are already on the page, // so we add them manually here, once. addListeners.call(_this, _this.el); }); // If we don't have `MutationObserver` and `Set`, we just register a `click` event listener // on the entire component, and check if a click occurred on one of our links. // Note that we can't reliably generate hints this way, so we don't. } else { this.el.addEventListener("click", function (event) { var anchor = _common.matchesAncestors.call(event.target, _this.linkSelector); if (anchor && anchor.href) { event.currentTarget = anchor; // eslint-disable-line no-param-reassign pushSubject.next(event); } }); } } }]); return _class; }(C); }; exports.eventListenersMixin = eventListenersMixin;