/**
* Provides page actions such as "jump to top" and clipboard actions.
*
- * @author Alexander Ebert
- * @copyright 2001-2020 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Ui/Page/Action
+ * @author Alexander Ebert
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/Page/Action
*/
-define(['Dictionary', 'Language'], function (Dictionary, Language) {
- 'use strict';
-
- var _buttons = new Dictionary();
-
- /** @var {Element} */
- var _container;
-
- var _didInit = false;
-
- var _lastPosition = -1;
-
- /** @var {Element} */
- var _toTopButton;
-
- /** @var {Element} */
- var _wrapper;
-
- var _resetLastPosition = window.debounce(function () {
- _lastPosition = -1;
- }, 50, false);
-
- /**
- * @exports WoltLabSuite/Core/Ui/Page/Action
- */
- return {
- /**
- * Initializes the page action container.
- */
- setup: function () {
- if (_didInit) {
- return;
- }
-
- _didInit = true;
-
- _wrapper = elCreate('div');
- _wrapper.className = 'pageAction';
-
- _container = elCreate('div');
- _container.className = 'pageActionButtons';
- _wrapper.appendChild(_container);
-
- _toTopButton = this._buildToTopButton();
- _wrapper.appendChild(_toTopButton);
-
- document.body.appendChild(_wrapper);
-
- var debounce = window.debounce(this._onScroll.bind(this), 100, false);
- window.addEventListener(
- "scroll",
- function () {
- if (_lastPosition === -1) {
- _lastPosition = window.pageYOffset;
-
- // Invoke the scroll handler once to immediately respond to
- // the user action before debouncing all further calls.
- window.setTimeout(function () {
- this._onScroll();
-
- _lastPosition = window.pageYOffset;
- }.bind(this), 60);
- }
-
- debounce();
- }.bind(this),
- {passive: true}
- );
-
- window.addEventListener("touchstart", function () {
- // Force a reset of the scroll position to trigger an immediate reaction
- // when the user touches the display again.
- if (_lastPosition !== -1) {
- _lastPosition = -1;
- }
- }, {passive: true});
-
- this._onScroll();
- },
-
- _buildToTopButton: function () {
- var button = elCreate('a');
- button.className = 'button buttonPrimary pageActionButtonToTop initiallyHidden jsTooltip';
- button.href = '';
- elAttr(button, 'title', Language.get('wcf.global.scrollUp'));
- elAttr(button, 'aria-hidden', 'true');
- button.innerHTML = '<span class="icon icon32 fa-angle-up"></span>';
-
- button.addEventListener(WCF_CLICK_EVENT, this._scrollTopTop.bind(this));
-
- return button;
- },
-
- _onScroll: function () {
- if (document.documentElement.classList.contains('disableScrolling')) {
- // Ignore any scroll events that take place while body scrolling is disabled,
- // because it messes up the scroll offsets.
- return;
- }
-
- var offset = window.pageYOffset;
- if (offset === _lastPosition) {
- // Ignore any scroll event that is fired but without a position change. This can
- // happen after closing a dialog that prevented the body from being scrolled.
- _resetLastPosition();
- return;
- }
-
- if (offset >= 300) {
- if (_toTopButton.classList.contains('initiallyHidden')) {
- _toTopButton.classList.remove('initiallyHidden');
- }
-
- elAttr(_toTopButton, 'aria-hidden', 'false');
- }
- else {
- elAttr(_toTopButton, 'aria-hidden', 'true');
- }
-
- this._renderContainer();
-
- if (_lastPosition !== -1) {
- _wrapper.classList[offset < _lastPosition ? 'remove' : 'add']('scrolledDown');
- }
-
- _lastPosition = -1;
- },
-
- /**
- * @param {Event} event
- */
- _scrollTopTop: function (event) {
- event.preventDefault();
-
- elById('top').scrollIntoView({behavior: 'smooth'});
- },
-
- /**
- * Adds a button to the page action list. You can optionally provide a button name to
- * insert the button right before it. Unmatched button names or empty value will cause
- * the button to be prepended to the list.
- *
- * @param {string} buttonName unique identifier
- * @param {Element} button button element, must not be wrapped in a <li>
- * @param {string=} insertBeforeButton insert button before element identified by provided button name
- */
- add: function (buttonName, button, insertBeforeButton) {
- this.setup();
-
- // The wrapper is required for backwards compatibility, because some implementations rely on a
- // dedicated parent element to insert elements, for example, for drop-down menus.
- var wrapper = elCreate('div');
- wrapper.className = 'pageActionButton';
- wrapper.name = buttonName;
- elAttr(wrapper, 'aria-hidden', 'true');
-
- button.classList.add('button');
- button.classList.add('buttonPrimary');
- wrapper.appendChild(button);
-
- var insertBefore = null;
- if (insertBeforeButton) {
- insertBefore = _buttons.get(insertBeforeButton);
- if (insertBefore !== undefined) {
- insertBefore = insertBefore.parentNode;
- }
- }
-
- if (insertBefore === null && _container.childElementCount) {
- insertBefore = _container.children[0];
- }
- if (insertBefore === null) {
- insertBefore = _container.firstChild;
- }
-
- _container.insertBefore(wrapper, insertBefore);
- _wrapper.classList.remove('scrolledDown');
-
- _buttons.set(buttonName, button);
-
- // Query a layout related property to force a reflow, otherwise the transition is optimized away.
- // noinspection BadExpressionStatementJS
- wrapper.offsetParent;
-
- // Toggle the visibility to force the transition to be applied.
- elAttr(wrapper, 'aria-hidden', 'false');
-
- this._renderContainer();
- },
-
- /**
- * Returns true if there is a registered button with the provided name.
- *
- * @param {string} buttonName unique identifier
- * @return {boolean} true if there is a registered button with this name
- */
- has: function (buttonName) {
- return _buttons.has(buttonName);
- },
-
- /**
- * Returns the stored button by name or undefined.
- *
- * @param {string} buttonName unique identifier
- * @return {Element} button element or undefined
- */
- get: function (buttonName) {
- return _buttons.get(buttonName);
- },
-
- /**
- * Removes a button by its button name.
- *
- * @param {string} buttonName unique identifier
- */
- remove: function (buttonName) {
- var button = _buttons.get(buttonName);
- if (button !== undefined) {
- var listItem = button.parentNode;
- var callback = function () {
- try {
- if (elAttrBool(listItem, 'aria-hidden')) {
- _container.removeChild(listItem);
- _buttons.delete(buttonName);
- }
-
- listItem.removeEventListener('transitionend', callback);
- }
- catch (e) {
- // ignore errors if the element has already been removed
- }
- };
-
- listItem.addEventListener('transitionend', callback);
-
- this.hide(buttonName);
- }
- },
-
- /**
- * Hides a button by its button name.
- *
- * @param {string} buttonName unique identifier
- */
- hide: function (buttonName) {
- var button = _buttons.get(buttonName);
- if (button) {
- elAttr(button.parentNode, 'aria-hidden', 'true');
- this._renderContainer();
- }
- },
-
- /**
- * Shows a button by its button name.
- *
- * @param {string} buttonName unique identifier
- */
- show: function (buttonName) {
- var button = _buttons.get(buttonName);
- if (button) {
- if (button.parentNode.classList.contains('initiallyHidden')) {
- button.parentNode.classList.remove('initiallyHidden');
- }
-
- elAttr(button.parentNode, 'aria-hidden', 'false');
- _wrapper.classList.remove('scrolledDown');
- this._renderContainer();
- }
- },
-
- /**
- * Toggles the container's visibility.
- *
- * @protected
- */
- _renderContainer: function () {
- var hasVisibleItems = false;
- if (_container.childElementCount) {
- for (var i = 0, length = _container.childElementCount; i < length; i++) {
- if (elAttr(_container.children[i], 'aria-hidden') === 'false') {
- hasVisibleItems = true;
- break;
- }
- }
- }
-
- _container.classList[(hasVisibleItems ? 'add' : 'remove')]('active');
- }
- };
+define(["require", "exports", "tslib", "../../Core", "../../Language"], function (require, exports, tslib_1, Core, Language) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.show = exports.hide = exports.remove = exports.get = exports.has = exports.add = exports.setup = void 0;
+ Core = tslib_1.__importStar(Core);
+ Language = tslib_1.__importStar(Language);
+ const _buttons = new Map();
+ let _container;
+ let _didInit = false;
+ let _lastPosition = -1;
+ let _toTopButton;
+ let _wrapper;
++ const _resetLastPosition = Core.debounce(() => {
++ _lastPosition = -1;
++ }, 50);
+ function buildToTopButton() {
+ const button = document.createElement("a");
+ button.className = "button buttonPrimary pageActionButtonToTop initiallyHidden jsTooltip";
+ button.href = "";
+ button.title = Language.get("wcf.global.scrollUp");
+ button.setAttribute("aria-hidden", "true");
+ button.innerHTML = '<span class="icon icon32 fa-angle-up"></span>';
+ button.addEventListener("click", scrollToTop);
+ return button;
+ }
+ function onScroll() {
+ if (document.documentElement.classList.contains("disableScrolling")) {
+ // Ignore any scroll events that take place while body scrolling is disabled,
+ // because it messes up the scroll offsets.
+ return;
+ }
+ const offset = window.pageYOffset;
+ if (offset === _lastPosition) {
+ // Ignore any scroll event that is fired but without a position change. This can
+ // happen after closing a dialog that prevented the body from being scrolled.
++ _resetLastPosition();
+ return;
+ }
+ if (offset >= 300) {
+ if (_toTopButton.classList.contains("initiallyHidden")) {
+ _toTopButton.classList.remove("initiallyHidden");
+ }
+ _toTopButton.setAttribute("aria-hidden", "false");
+ }
+ else {
+ _toTopButton.setAttribute("aria-hidden", "true");
+ }
+ renderContainer();
+ if (_lastPosition !== -1) {
+ _wrapper.classList[offset < _lastPosition ? "remove" : "add"]("scrolledDown");
+ }
- _lastPosition = offset;
++ _lastPosition = -1;
+ }
+ function scrollToTop(event) {
+ event.preventDefault();
+ const topAnchor = document.getElementById("top");
+ topAnchor.scrollIntoView({ behavior: "smooth" });
+ }
+ /**
+ * Toggles the container's visibility.
+ */
+ function renderContainer() {
+ const visibleChild = Array.from(_container.children).find((element) => {
+ return element.getAttribute("aria-hidden") === "false";
+ });
+ _container.classList[visibleChild ? "add" : "remove"]("active");
+ }
+ /**
+ * Initializes the page action container.
+ */
+ function setup() {
+ if (_didInit) {
+ return;
+ }
+ _didInit = true;
+ _wrapper = document.createElement("div");
+ _wrapper.className = "pageAction";
+ _container = document.createElement("div");
+ _container.className = "pageActionButtons";
+ _wrapper.appendChild(_container);
+ _toTopButton = buildToTopButton();
+ _wrapper.appendChild(_toTopButton);
+ document.body.appendChild(_wrapper);
- window.addEventListener("scroll", Core.debounce(onScroll, 100), { passive: true });
++ const debounce = Core.debounce(onScroll, 100);
++ window.addEventListener("scroll", () => {
++ if (_lastPosition === -1) {
++ _lastPosition = window.pageYOffset;
++ // Invoke the scroll handler once to immediately respond to
++ // the user action before debouncing all further calls.
++ window.setTimeout(() => {
++ onScroll();
++ _lastPosition = window.pageYOffset;
++ }, 60);
++ }
++ debounce();
++ }, { passive: true });
++ window.addEventListener("touchstart", () => {
++ // Force a reset of the scroll position to trigger an immediate reaction
++ // when the user touches the display again.
++ if (_lastPosition !== -1) {
++ _lastPosition = -1;
++ }
++ }, { passive: true });
+ onScroll();
+ }
+ exports.setup = setup;
+ /**
+ * Adds a button to the page action list. You can optionally provide a button name to
+ * insert the button right before it. Unmatched button names or empty value will cause
+ * the button to be prepended to the list.
+ */
+ function add(buttonName, button, insertBeforeButton) {
+ setup();
+ // The wrapper is required for backwards compatibility, because some implementations rely on a
+ // dedicated parent element to insert elements, for example, for drop-down menus.
+ const wrapper = document.createElement("div");
+ wrapper.className = "pageActionButton";
+ wrapper.dataset.name = buttonName;
+ wrapper.setAttribute("aria-hidden", "true");
+ button.classList.add("button");
+ button.classList.add("buttonPrimary");
+ wrapper.appendChild(button);
+ let insertBefore = null;
+ if (insertBeforeButton) {
+ insertBefore = _buttons.get(insertBeforeButton) || null;
+ if (insertBefore) {
+ insertBefore = insertBefore.parentElement;
+ }
+ }
+ if (!insertBefore && _container.childElementCount) {
+ insertBefore = _container.children[0];
+ }
+ if (!insertBefore) {
+ insertBefore = _container.firstChild;
+ }
+ _container.insertBefore(wrapper, insertBefore);
+ _wrapper.classList.remove("scrolledDown");
+ _buttons.set(buttonName, button);
+ // Query a layout related property to force a reflow, otherwise the transition is optimized away.
+ // noinspection BadExpressionStatementJS
+ wrapper.offsetParent;
+ // Toggle the visibility to force the transition to be applied.
+ wrapper.setAttribute("aria-hidden", "false");
+ renderContainer();
+ }
+ exports.add = add;
+ /**
+ * Returns true if there is a registered button with the provided name.
+ */
+ function has(buttonName) {
+ return _buttons.has(buttonName);
+ }
+ exports.has = has;
+ /**
+ * Returns the stored button by name or undefined.
+ */
+ function get(buttonName) {
+ return _buttons.get(buttonName);
+ }
+ exports.get = get;
+ /**
+ * Removes a button by its button name.
+ */
+ function remove(buttonName) {
+ const button = _buttons.get(buttonName);
+ if (button !== undefined) {
+ const listItem = button.parentElement;
+ const callback = () => {
+ try {
+ if (Core.stringToBool(listItem.getAttribute("aria-hidden"))) {
+ _container.removeChild(listItem);
+ _buttons.delete(buttonName);
+ }
+ listItem.removeEventListener("transitionend", callback);
+ }
+ catch (e) {
+ // ignore errors if the element has already been removed
+ }
+ };
+ listItem.addEventListener("transitionend", callback);
+ hide(buttonName);
+ }
+ }
+ exports.remove = remove;
+ /**
+ * Hides a button by its button name.
+ */
+ function hide(buttonName) {
+ const button = _buttons.get(buttonName);
+ if (button) {
+ const parent = button.parentElement;
+ parent.setAttribute("aria-hidden", "true");
+ renderContainer();
+ }
+ }
+ exports.hide = hide;
+ /**
+ * Shows a button by its button name.
+ */
+ function show(buttonName) {
+ const button = _buttons.get(buttonName);
+ if (button) {
+ const parent = button.parentElement;
+ if (parent.classList.contains("initiallyHidden")) {
+ parent.classList.remove("initiallyHidden");
+ }
+ parent.setAttribute("aria-hidden", "false");
+ _wrapper.classList.remove("scrolledDown");
+ renderContainer();
+ }
+ }
+ exports.show = show;
});