Convert `Ui/Alignment` to TypeScript
authorAlexander Ebert <ebert@woltlab.com>
Wed, 21 Oct 2020 20:26:35 +0000 (22:26 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Wed, 28 Oct 2020 11:37:15 +0000 (12:37 +0100)
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Alignment.js
wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Alignment.js [deleted file]
wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Alignment.ts [new file with mode: 0644]

index f5f04e1d0091d7668d307a175842b3b1f29500ea..a6b6dbc8f79fd1d9e8218667835f479377678114 100644 (file)
 /**
  * Utility class to align elements relatively to another.
  *
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     Ui/Alignment (alias)
- * @module     WoltLabSuite/Core/Ui/Alignment
+ * @author  Alexander Ebert
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  Ui/Alignment (alias)
+ * @module  WoltLabSuite/Core/Ui/Alignment
  */
-define(['Core', 'Language', 'Dom/Traverse', 'Dom/Util'], function (Core, Language, DomTraverse, DomUtil) {
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+var __importDefault = (this && this.__importDefault) || function (mod) {
+    return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+define(["require", "exports", "../Core", "../Dom/Traverse", "../Dom/Util", "../Language"], function (require, exports, Core, DomTraverse, Util_1, Language) {
     "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.set = void 0;
+    Core = __importStar(Core);
+    DomTraverse = __importStar(DomTraverse);
+    Util_1 = __importDefault(Util_1);
+    Language = __importStar(Language);
     /**
-     * @exports        WoltLabSuite/Core/Ui/Alignment
+     * Calculates left/right position and verifies if the element would be still within the page's boundaries.
+     *
+     * @param  {string}    alignment    align to this side of the reference element
+     * @param  {Object<string, int>}  elDimensions  element dimensions
+     * @param  {Object<string, int>}  refDimensions  reference element dimensions
+     * @param  {Object<string, int>}  refOffsets  position of reference element relative to the document
+     * @param  {int}      windowWidth  window width
+     * @returns  {Object<string, *>}  calculation results
      */
-    return {
-        /**
-         * Sets the alignment for target element relatively to the reference element.
-         *
-         * @param      {Element}               el              target element
-         * @param      {Element}               ref             reference element
-         * @param      {Object<string, *>}     options         list of options to alter the behavior
-         */
-        set: function (el, ref, options) {
-            options = Core.extend({
-                // offset to reference element
-                verticalOffset: 0,
-                // align the pointer element, expects .elementPointer as a direct child of given element
-                pointer: false,
-                // use static pointer positions, expects two items: class to move it to the bottom and the second to move it to the right
-                pointerClassNames: [],
-                // alternate element used to calculate dimensions
-                refDimensionsElement: null,
-                // preferred alignment, possible values: left/right/center and top/bottom
-                horizontal: 'left',
-                vertical: 'bottom',
-                // allow flipping over axis, possible values: both, horizontal, vertical and none
-                allowFlip: 'both'
-            }, options);
-            if (!Array.isArray(options.pointerClassNames) || options.pointerClassNames.length !== (options.pointer ? 1 : 2))
-                options.pointerClassNames = [];
-            if (['left', 'right', 'center'].indexOf(options.horizontal) === -1)
-                options.horizontal = 'left';
-            if (options.vertical !== 'bottom')
-                options.vertical = 'top';
-            if (['both', 'horizontal', 'vertical', 'none'].indexOf(options.allowFlip) === -1)
-                options.allowFlip = 'both';
-            // place element in the upper left corner to prevent calculation issues due to possible scrollbars
-            DomUtil.setStyles(el, {
-                bottom: 'auto !important',
-                left: '0 !important',
-                right: 'auto !important',
-                top: '0 !important',
-                visibility: 'hidden !important'
-            });
-            var elDimensions = DomUtil.outerDimensions(el);
-            var refDimensions = DomUtil.outerDimensions((options.refDimensionsElement instanceof Element ? options.refDimensionsElement : ref));
-            var refOffsets = DomUtil.offset(ref);
-            var windowHeight = window.innerHeight;
-            var windowWidth = document.body.clientWidth;
-            var horizontal = { result: null };
-            var alignCenter = false;
-            if (options.horizontal === 'center') {
-                alignCenter = true;
-                horizontal = this._tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
-                if (!horizontal.result) {
-                    if (options.allowFlip === 'both' || options.allowFlip === 'horizontal') {
-                        options.horizontal = 'left';
-                    }
-                    else {
-                        horizontal.result = true;
-                    }
-                }
+    function tryAlignmentHorizontal(alignment, elDimensions, refDimensions, refOffsets, windowWidth) {
+        let left = 'auto';
+        let right = 'auto';
+        let result = true;
+        if (alignment === 'left') {
+            left = refOffsets.left;
+            if (left + elDimensions.width > windowWidth) {
+                result = false;
             }
-            // in rtl languages we simply swap the value for 'horizontal'
-            if (Language.get('wcf.global.pageDirection') === 'rtl') {
-                options.horizontal = (options.horizontal === 'left') ? 'right' : 'left';
+        }
+        else if (alignment === 'right') {
+            if (refOffsets.left + refDimensions.width < elDimensions.width) {
+                result = false;
             }
-            if (!horizontal.result) {
-                var horizontalCenter = horizontal;
-                horizontal = this._tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
-                if (!horizontal.result && (options.allowFlip === 'both' || options.allowFlip === 'horizontal')) {
-                    var horizontalFlipped = this._tryAlignmentHorizontal((options.horizontal === 'left' ? 'right' : 'left'), elDimensions, refDimensions, refOffsets, windowWidth);
-                    // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
-                    if (horizontalFlipped.result) {
-                        horizontal = horizontalFlipped;
-                    }
-                    else if (alignCenter) {
-                        horizontal = horizontalCenter;
-                    }
+            else {
+                right = windowWidth - (refOffsets.left + refDimensions.width);
+                if (right < 0) {
+                    result = false;
                 }
             }
-            var left = horizontal.left;
-            var right = horizontal.right;
-            var vertical = this._tryAlignmentVertical(options.vertical, elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
-            if (!vertical.result && (options.allowFlip === 'both' || options.allowFlip === 'vertical')) {
-                var verticalFlipped = this._tryAlignmentVertical((options.vertical === 'top' ? 'bottom' : 'top'), elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
-                // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
-                if (verticalFlipped.result) {
-                    vertical = verticalFlipped;
-                }
+        }
+        else {
+            left = refOffsets.left + (refDimensions.width / 2) - (elDimensions.width / 2);
+            left = ~~left;
+            if (left < 0 || left + elDimensions.width > windowWidth) {
+                result = false;
             }
-            var bottom = vertical.bottom;
-            var top = vertical.top;
-            // set pointer position
-            if (options.pointer) {
-                var pointer = DomTraverse.childrenByClass(el, 'elementPointer');
-                pointer = pointer[0] || null;
-                if (pointer === null) {
-                    throw new Error("Expected the .elementPointer element to be a direct children.");
-                }
-                if (horizontal.align === 'center') {
-                    pointer.classList.add('center');
-                    pointer.classList.remove('left');
-                    pointer.classList.remove('right');
+        }
+        return {
+            align: alignment,
+            left: left,
+            right: right,
+            result: result,
+        };
+    }
+    /**
+     * Calculates top/bottom position and verifies if the element would be still within the page's boundaries.
+     *
+     * @param  {string}    alignment    align to this side of the reference element
+     * @param  {Object<string, int>}  elDimensions  element dimensions
+     * @param  {Object<string, int>}  refDimensions  reference element dimensions
+     * @param  {Object<string, int>}  refOffsets  position of reference element relative to the document
+     * @param  {int}      windowHeight  window height
+     * @param  {int}      verticalOffset  desired gap between element and reference element
+     * @returns  {object<string, *>}  calculation results
+     */
+    function tryAlignmentVertical(alignment, elDimensions, refDimensions, refOffsets, windowHeight, verticalOffset) {
+        let bottom = 'auto';
+        let top = 'auto';
+        let result = true;
+        let pageHeaderOffset = 50;
+        const pageHeaderPanel = document.getElementById('pageHeaderPanel');
+        if (pageHeaderPanel !== null) {
+            const position = window.getComputedStyle(pageHeaderPanel).position;
+            if (position === 'fixed' || position === 'static') {
+                pageHeaderOffset = pageHeaderPanel.offsetHeight;
+            }
+            else {
+                pageHeaderOffset = 0;
+            }
+        }
+        if (alignment === 'top') {
+            const bodyHeight = document.body.clientHeight;
+            bottom = (bodyHeight - refOffsets.top) + verticalOffset;
+            if (bodyHeight - (bottom + elDimensions.height) < (window.scrollY || window.pageYOffset) + pageHeaderOffset) {
+                result = false;
+            }
+        }
+        else {
+            top = refOffsets.top + refDimensions.height + verticalOffset;
+            if (top + elDimensions.height - (window.scrollY || window.pageYOffset) > windowHeight) {
+                result = false;
+            }
+        }
+        return {
+            align: alignment,
+            bottom: bottom,
+            top: top,
+            result: result,
+        };
+    }
+    /**
+     * Sets the alignment for target element relatively to the reference element.
+     *
+     * @param  {Element}    element    target element
+     * @param  {Element}    referenceElement    reference element
+     * @param  {Object<string, *>}  options    list of options to alter the behavior
+     */
+    function set(element, referenceElement, options) {
+        options = Core.extend({
+            // offset to reference element
+            verticalOffset: 0,
+            // align the pointer element, expects .elementPointer as a direct child of given element
+            pointer: false,
+            // use static pointer positions, expects two items: class to move it to the bottom and the second to move it to the right
+            pointerClassNames: [],
+            // alternate element used to calculate dimensions
+            refDimensionsElement: null,
+            // preferred alignment, possible values: left/right/center and top/bottom
+            horizontal: 'left',
+            vertical: 'bottom',
+            // allow flipping over axis, possible values: both, horizontal, vertical and none
+            allowFlip: 'both',
+        }, options);
+        if (!Array.isArray(options.pointerClassNames) || options.pointerClassNames.length !== (options.pointer ? 1 : 2)) {
+            options.pointerClassNames = [];
+        }
+        if (['left', 'right', 'center'].indexOf(options.horizontal) === -1) {
+            options.horizontal = 'left';
+        }
+        if (options.vertical !== 'bottom') {
+            options.vertical = 'top';
+        }
+        if (['both', 'horizontal', 'vertical', 'none'].indexOf(options.allowFlip) === -1) {
+            options.allowFlip = 'both';
+        }
+        // Place the element in the upper left corner to prevent calculation issues due to possible scrollbars.
+        Util_1.default.setStyles(element, {
+            bottom: 'auto !important',
+            left: '0 !important',
+            right: 'auto !important',
+            top: '0 !important',
+            visibility: 'hidden !important',
+        });
+        const elDimensions = Util_1.default.outerDimensions(element);
+        const refDimensions = Util_1.default.outerDimensions(options.refDimensionsElement instanceof HTMLElement ? options.refDimensionsElement : referenceElement);
+        const refOffsets = Util_1.default.offset(referenceElement);
+        const windowHeight = window.innerHeight;
+        const windowWidth = document.body.clientWidth;
+        let horizontal = null;
+        let alignCenter = false;
+        if (options.horizontal === 'center') {
+            alignCenter = true;
+            horizontal = tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
+            if (!horizontal.result) {
+                if (options.allowFlip === 'both' || options.allowFlip === 'horizontal') {
+                    options.horizontal = 'left';
                 }
                 else {
-                    pointer.classList.add(horizontal.align);
-                    pointer.classList.remove('center');
-                    pointer.classList.remove(horizontal.align === 'left' ? 'right' : 'left');
+                    horizontal.result = true;
                 }
-                if (vertical.align === 'top') {
-                    pointer.classList.add('flipVertical');
+            }
+        }
+        // in rtl languages we simply swap the value for 'horizontal'
+        if (Language.get('wcf.global.pageDirection') === 'rtl') {
+            options.horizontal = (options.horizontal === 'left') ? 'right' : 'left';
+        }
+        if (horizontal === null || !horizontal.result) {
+            const horizontalCenter = horizontal;
+            horizontal = tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
+            if (!horizontal.result && (options.allowFlip === 'both' || options.allowFlip === 'horizontal')) {
+                const horizontalFlipped = tryAlignmentHorizontal((options.horizontal === 'left' ? 'right' : 'left'), elDimensions, refDimensions, refOffsets, windowWidth);
+                // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
+                if (horizontalFlipped.result) {
+                    horizontal = horizontalFlipped;
                 }
-                else {
-                    pointer.classList.remove('flipVertical');
+                else if (alignCenter) {
+                    horizontal = horizontalCenter;
                 }
             }
-            else if (options.pointerClassNames.length === 2) {
-                var pointerBottom = 0;
-                var pointerRight = 1;
-                el.classList[(top === 'auto' ? 'add' : 'remove')](options.pointerClassNames[pointerBottom]);
-                el.classList[(left === 'auto' ? 'add' : 'remove')](options.pointerClassNames[pointerRight]);
-            }
-            if (bottom !== 'auto')
-                bottom = Math.round(bottom) + 'px';
-            if (left !== 'auto')
-                left = Math.ceil(left) + 'px';
-            if (right !== 'auto')
-                right = Math.floor(right) + 'px';
-            if (top !== 'auto')
-                top = Math.round(top) + 'px';
-            DomUtil.setStyles(el, {
-                bottom: bottom,
-                left: left,
-                right: right,
-                top: top
-            });
-            elShow(el);
-            el.style.removeProperty('visibility');
-        },
-        /**
-         * Calculates left/right position and verifies if the element would be still within the page's boundaries.
-         *
-         * @param      {string}                align           align to this side of the reference element
-         * @param      {Object<string, int>}   elDimensions    element dimensions
-         * @param      {Object<string, int>}   refDimensions   reference element dimensions
-         * @param      {Object<string, int>}   refOffsets      position of reference element relative to the document
-         * @param      {int}                   windowWidth     window width
-         * @returns    {Object<string, *>}     calculation results
-         */
-        _tryAlignmentHorizontal: function (align, elDimensions, refDimensions, refOffsets, windowWidth) {
-            var left = 'auto';
-            var right = 'auto';
-            var result = true;
-            if (align === 'left') {
-                left = refOffsets.left;
-                if (left + elDimensions.width > windowWidth) {
-                    result = false;
-                }
+        }
+        const left = horizontal.left;
+        const right = horizontal.right;
+        let vertical = tryAlignmentVertical(options.vertical, elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
+        if (!vertical.result && (options.allowFlip === 'both' || options.allowFlip === 'vertical')) {
+            const verticalFlipped = tryAlignmentVertical((options.vertical === 'top' ? 'bottom' : 'top'), elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
+            // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
+            if (verticalFlipped.result) {
+                vertical = verticalFlipped;
             }
-            else if (align === 'right') {
-                if (refOffsets.left + refDimensions.width < elDimensions.width) {
-                    result = false;
-                }
-                else {
-                    right = windowWidth - (refOffsets.left + refDimensions.width);
-                    if (right < 0) {
-                        result = false;
-                    }
-                }
+        }
+        const bottom = vertical.bottom;
+        const top = vertical.top;
+        // set pointer position
+        if (options.pointer) {
+            const pointers = DomTraverse.childrenByClass(element, 'elementPointer');
+            const pointer = pointers[0] || null;
+            if (pointer === null) {
+                throw new Error("Expected the .elementPointer element to be a direct children.");
             }
-            else {
-                left = refOffsets.left + (refDimensions.width / 2) - (elDimensions.width / 2);
-                left = ~~left;
-                if (left < 0 || left + elDimensions.width > windowWidth) {
-                    result = false;
-                }
+            if (horizontal.align === 'center') {
+                pointer.classList.add('center');
+                pointer.classList.remove('left', 'right');
             }
-            return {
-                align: align,
-                left: left,
-                right: right,
-                result: result
-            };
-        },
-        /**
-         * Calculates top/bottom position and verifies if the element would be still within the page's boundaries.
-         *
-         * @param      {string}                align           align to this side of the reference element
-         * @param      {Object<string, int>}   elDimensions    element dimensions
-         * @param      {Object<string, int>}   refDimensions   reference element dimensions
-         * @param      {Object<string, int>}   refOffsets      position of reference element relative to the document
-         * @param      {int}                   windowHeight    window height
-         * @param      {int}                   verticalOffset  desired gap between element and reference element
-         * @returns    {object<string, *>}     calculation results
-         */
-        _tryAlignmentVertical: function (align, elDimensions, refDimensions, refOffsets, windowHeight, verticalOffset) {
-            var bottom = 'auto';
-            var top = 'auto';
-            var result = true;
-            var pageHeaderOffset = 50;
-            var pageHeaderPanel = elById('pageHeaderPanel');
-            if (pageHeaderPanel !== null) {
-                var position = window.getComputedStyle(pageHeaderPanel).position;
-                if (position === 'fixed' || position === 'static') {
-                    pageHeaderOffset = pageHeaderPanel.offsetHeight;
-                }
-                else {
-                    pageHeaderOffset = 0;
-                }
+            else {
+                pointer.classList.add(horizontal.align);
+                pointer.classList.remove('center');
+                pointer.classList.remove(horizontal.align === 'left' ? 'right' : 'left');
             }
-            if (align === 'top') {
-                var bodyHeight = document.body.clientHeight;
-                bottom = (bodyHeight - refOffsets.top) + verticalOffset;
-                if (bodyHeight - (bottom + elDimensions.height) < (window.scrollY || window.pageYOffset) + pageHeaderOffset) {
-                    result = false;
-                }
+            if (vertical.align === 'top') {
+                pointer.classList.add('flipVertical');
             }
             else {
-                top = refOffsets.top + refDimensions.height + verticalOffset;
-                if (top + elDimensions.height - (window.scrollY || window.pageYOffset) > windowHeight) {
-                    result = false;
-                }
+                pointer.classList.remove('flipVertical');
             }
-            return {
-                align: align,
-                bottom: bottom,
-                top: top,
-                result: result
-            };
         }
-    };
+        else if (options.pointerClassNames.length === 2) {
+            element.classList[(top === 'auto' ? 'add' : 'remove')](options.pointerClassNames[0 /* Bottom */]);
+            element.classList[(left === 'auto' ? 'add' : 'remove')](options.pointerClassNames[1 /* Right */]);
+        }
+        Util_1.default.setStyles(element, {
+            bottom: bottom === 'auto' ? bottom : Math.round(bottom) + 'px',
+            left: left === 'auto' ? left : Math.ceil(left) + 'px',
+            right: right === 'auto' ? right : Math.floor(right) + 'px',
+            top: top === 'auto' ? top : Math.round(top) + 'px',
+        });
+        Util_1.default.show(element);
+        element.style.removeProperty('visibility');
+    }
+    exports.set = set;
 });
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Alignment.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Alignment.js
deleted file mode 100644 (file)
index f5f04e1..0000000
+++ /dev/null
@@ -1,243 +0,0 @@
-/**
- * Utility class to align elements relatively to another.
- *
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     Ui/Alignment (alias)
- * @module     WoltLabSuite/Core/Ui/Alignment
- */
-define(['Core', 'Language', 'Dom/Traverse', 'Dom/Util'], function (Core, Language, DomTraverse, DomUtil) {
-    "use strict";
-    /**
-     * @exports        WoltLabSuite/Core/Ui/Alignment
-     */
-    return {
-        /**
-         * Sets the alignment for target element relatively to the reference element.
-         *
-         * @param      {Element}               el              target element
-         * @param      {Element}               ref             reference element
-         * @param      {Object<string, *>}     options         list of options to alter the behavior
-         */
-        set: function (el, ref, options) {
-            options = Core.extend({
-                // offset to reference element
-                verticalOffset: 0,
-                // align the pointer element, expects .elementPointer as a direct child of given element
-                pointer: false,
-                // use static pointer positions, expects two items: class to move it to the bottom and the second to move it to the right
-                pointerClassNames: [],
-                // alternate element used to calculate dimensions
-                refDimensionsElement: null,
-                // preferred alignment, possible values: left/right/center and top/bottom
-                horizontal: 'left',
-                vertical: 'bottom',
-                // allow flipping over axis, possible values: both, horizontal, vertical and none
-                allowFlip: 'both'
-            }, options);
-            if (!Array.isArray(options.pointerClassNames) || options.pointerClassNames.length !== (options.pointer ? 1 : 2))
-                options.pointerClassNames = [];
-            if (['left', 'right', 'center'].indexOf(options.horizontal) === -1)
-                options.horizontal = 'left';
-            if (options.vertical !== 'bottom')
-                options.vertical = 'top';
-            if (['both', 'horizontal', 'vertical', 'none'].indexOf(options.allowFlip) === -1)
-                options.allowFlip = 'both';
-            // place element in the upper left corner to prevent calculation issues due to possible scrollbars
-            DomUtil.setStyles(el, {
-                bottom: 'auto !important',
-                left: '0 !important',
-                right: 'auto !important',
-                top: '0 !important',
-                visibility: 'hidden !important'
-            });
-            var elDimensions = DomUtil.outerDimensions(el);
-            var refDimensions = DomUtil.outerDimensions((options.refDimensionsElement instanceof Element ? options.refDimensionsElement : ref));
-            var refOffsets = DomUtil.offset(ref);
-            var windowHeight = window.innerHeight;
-            var windowWidth = document.body.clientWidth;
-            var horizontal = { result: null };
-            var alignCenter = false;
-            if (options.horizontal === 'center') {
-                alignCenter = true;
-                horizontal = this._tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
-                if (!horizontal.result) {
-                    if (options.allowFlip === 'both' || options.allowFlip === 'horizontal') {
-                        options.horizontal = 'left';
-                    }
-                    else {
-                        horizontal.result = true;
-                    }
-                }
-            }
-            // in rtl languages we simply swap the value for 'horizontal'
-            if (Language.get('wcf.global.pageDirection') === 'rtl') {
-                options.horizontal = (options.horizontal === 'left') ? 'right' : 'left';
-            }
-            if (!horizontal.result) {
-                var horizontalCenter = horizontal;
-                horizontal = this._tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
-                if (!horizontal.result && (options.allowFlip === 'both' || options.allowFlip === 'horizontal')) {
-                    var horizontalFlipped = this._tryAlignmentHorizontal((options.horizontal === 'left' ? 'right' : 'left'), elDimensions, refDimensions, refOffsets, windowWidth);
-                    // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
-                    if (horizontalFlipped.result) {
-                        horizontal = horizontalFlipped;
-                    }
-                    else if (alignCenter) {
-                        horizontal = horizontalCenter;
-                    }
-                }
-            }
-            var left = horizontal.left;
-            var right = horizontal.right;
-            var vertical = this._tryAlignmentVertical(options.vertical, elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
-            if (!vertical.result && (options.allowFlip === 'both' || options.allowFlip === 'vertical')) {
-                var verticalFlipped = this._tryAlignmentVertical((options.vertical === 'top' ? 'bottom' : 'top'), elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
-                // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
-                if (verticalFlipped.result) {
-                    vertical = verticalFlipped;
-                }
-            }
-            var bottom = vertical.bottom;
-            var top = vertical.top;
-            // set pointer position
-            if (options.pointer) {
-                var pointer = DomTraverse.childrenByClass(el, 'elementPointer');
-                pointer = pointer[0] || null;
-                if (pointer === null) {
-                    throw new Error("Expected the .elementPointer element to be a direct children.");
-                }
-                if (horizontal.align === 'center') {
-                    pointer.classList.add('center');
-                    pointer.classList.remove('left');
-                    pointer.classList.remove('right');
-                }
-                else {
-                    pointer.classList.add(horizontal.align);
-                    pointer.classList.remove('center');
-                    pointer.classList.remove(horizontal.align === 'left' ? 'right' : 'left');
-                }
-                if (vertical.align === 'top') {
-                    pointer.classList.add('flipVertical');
-                }
-                else {
-                    pointer.classList.remove('flipVertical');
-                }
-            }
-            else if (options.pointerClassNames.length === 2) {
-                var pointerBottom = 0;
-                var pointerRight = 1;
-                el.classList[(top === 'auto' ? 'add' : 'remove')](options.pointerClassNames[pointerBottom]);
-                el.classList[(left === 'auto' ? 'add' : 'remove')](options.pointerClassNames[pointerRight]);
-            }
-            if (bottom !== 'auto')
-                bottom = Math.round(bottom) + 'px';
-            if (left !== 'auto')
-                left = Math.ceil(left) + 'px';
-            if (right !== 'auto')
-                right = Math.floor(right) + 'px';
-            if (top !== 'auto')
-                top = Math.round(top) + 'px';
-            DomUtil.setStyles(el, {
-                bottom: bottom,
-                left: left,
-                right: right,
-                top: top
-            });
-            elShow(el);
-            el.style.removeProperty('visibility');
-        },
-        /**
-         * Calculates left/right position and verifies if the element would be still within the page's boundaries.
-         *
-         * @param      {string}                align           align to this side of the reference element
-         * @param      {Object<string, int>}   elDimensions    element dimensions
-         * @param      {Object<string, int>}   refDimensions   reference element dimensions
-         * @param      {Object<string, int>}   refOffsets      position of reference element relative to the document
-         * @param      {int}                   windowWidth     window width
-         * @returns    {Object<string, *>}     calculation results
-         */
-        _tryAlignmentHorizontal: function (align, elDimensions, refDimensions, refOffsets, windowWidth) {
-            var left = 'auto';
-            var right = 'auto';
-            var result = true;
-            if (align === 'left') {
-                left = refOffsets.left;
-                if (left + elDimensions.width > windowWidth) {
-                    result = false;
-                }
-            }
-            else if (align === 'right') {
-                if (refOffsets.left + refDimensions.width < elDimensions.width) {
-                    result = false;
-                }
-                else {
-                    right = windowWidth - (refOffsets.left + refDimensions.width);
-                    if (right < 0) {
-                        result = false;
-                    }
-                }
-            }
-            else {
-                left = refOffsets.left + (refDimensions.width / 2) - (elDimensions.width / 2);
-                left = ~~left;
-                if (left < 0 || left + elDimensions.width > windowWidth) {
-                    result = false;
-                }
-            }
-            return {
-                align: align,
-                left: left,
-                right: right,
-                result: result
-            };
-        },
-        /**
-         * Calculates top/bottom position and verifies if the element would be still within the page's boundaries.
-         *
-         * @param      {string}                align           align to this side of the reference element
-         * @param      {Object<string, int>}   elDimensions    element dimensions
-         * @param      {Object<string, int>}   refDimensions   reference element dimensions
-         * @param      {Object<string, int>}   refOffsets      position of reference element relative to the document
-         * @param      {int}                   windowHeight    window height
-         * @param      {int}                   verticalOffset  desired gap between element and reference element
-         * @returns    {object<string, *>}     calculation results
-         */
-        _tryAlignmentVertical: function (align, elDimensions, refDimensions, refOffsets, windowHeight, verticalOffset) {
-            var bottom = 'auto';
-            var top = 'auto';
-            var result = true;
-            var pageHeaderOffset = 50;
-            var pageHeaderPanel = elById('pageHeaderPanel');
-            if (pageHeaderPanel !== null) {
-                var position = window.getComputedStyle(pageHeaderPanel).position;
-                if (position === 'fixed' || position === 'static') {
-                    pageHeaderOffset = pageHeaderPanel.offsetHeight;
-                }
-                else {
-                    pageHeaderOffset = 0;
-                }
-            }
-            if (align === 'top') {
-                var bodyHeight = document.body.clientHeight;
-                bottom = (bodyHeight - refOffsets.top) + verticalOffset;
-                if (bodyHeight - (bottom + elDimensions.height) < (window.scrollY || window.pageYOffset) + pageHeaderOffset) {
-                    result = false;
-                }
-            }
-            else {
-                top = refOffsets.top + refDimensions.height + verticalOffset;
-                if (top + elDimensions.height - (window.scrollY || window.pageYOffset) > windowHeight) {
-                    result = false;
-                }
-            }
-            return {
-                align: align,
-                bottom: bottom,
-                top: top,
-                result: result
-            };
-        }
-    };
-});
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Alignment.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Alignment.ts
new file mode 100644 (file)
index 0000000..29188ed
--- /dev/null
@@ -0,0 +1,287 @@
+/**
+ * Utility class to align elements relatively to another.
+ *
+ * @author  Alexander Ebert
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  Ui/Alignment (alias)
+ * @module  WoltLabSuite/Core/Ui/Alignment
+ */
+
+import * as Core from '../Core';
+import * as DomTraverse from '../Dom/Traverse';
+import DomUtil from '../Dom/Util';
+import * as Language from '../Language';
+
+type HorizontalAlignment = 'center' | 'left' | 'right';
+type VerticalAlignment = 'bottom' | 'top';
+type Offset = number | 'auto';
+
+interface HorizontalResult {
+  align: HorizontalAlignment;
+  left: Offset;
+  result: boolean;
+  right: Offset;
+}
+
+interface VerticalResult {
+  align: VerticalAlignment;
+  bottom: Offset;
+  result: boolean;
+  top: Offset;
+}
+
+const enum PointerClass {
+  Bottom = 0,
+  Right = 1,
+}
+
+/**
+ * Calculates left/right position and verifies if the element would be still within the page's boundaries.
+ *
+ * @param  {string}    alignment    align to this side of the reference element
+ * @param  {Object<string, int>}  elDimensions  element dimensions
+ * @param  {Object<string, int>}  refDimensions  reference element dimensions
+ * @param  {Object<string, int>}  refOffsets  position of reference element relative to the document
+ * @param  {int}      windowWidth  window width
+ * @returns  {Object<string, *>}  calculation results
+ */
+function tryAlignmentHorizontal(alignment: HorizontalAlignment, elDimensions, refDimensions, refOffsets, windowWidth): HorizontalResult {
+  let left: Offset = 'auto';
+  let right: Offset = 'auto';
+  let result = true;
+
+  if (alignment === 'left') {
+    left = refOffsets.left;
+
+    if (left + elDimensions.width > windowWidth) {
+      result = false;
+    }
+  } else if (alignment === 'right') {
+    if (refOffsets.left + refDimensions.width < elDimensions.width) {
+      result = false;
+    } else {
+      right = windowWidth - (refOffsets.left + refDimensions.width);
+
+      if (right < 0) {
+        result = false;
+      }
+    }
+  } else {
+    left = refOffsets.left + (refDimensions.width / 2) - (elDimensions.width / 2);
+    left = ~~left;
+
+    if (left < 0 || left + elDimensions.width > windowWidth) {
+      result = false;
+    }
+  }
+
+  return {
+    align: alignment,
+    left: left,
+    right: right,
+    result: result,
+  };
+}
+
+/**
+ * Calculates top/bottom position and verifies if the element would be still within the page's boundaries.
+ *
+ * @param  {string}    alignment    align to this side of the reference element
+ * @param  {Object<string, int>}  elDimensions  element dimensions
+ * @param  {Object<string, int>}  refDimensions  reference element dimensions
+ * @param  {Object<string, int>}  refOffsets  position of reference element relative to the document
+ * @param  {int}      windowHeight  window height
+ * @param  {int}      verticalOffset  desired gap between element and reference element
+ * @returns  {object<string, *>}  calculation results
+ */
+function tryAlignmentVertical(alignment: VerticalAlignment, elDimensions, refDimensions, refOffsets, windowHeight, verticalOffset): VerticalResult {
+  let bottom: Offset = 'auto';
+  let top: Offset = 'auto';
+  let result = true;
+  let pageHeaderOffset = 50;
+
+  const pageHeaderPanel = document.getElementById('pageHeaderPanel');
+  if (pageHeaderPanel !== null) {
+    const position = window.getComputedStyle(pageHeaderPanel).position;
+    if (position === 'fixed' || position === 'static') {
+      pageHeaderOffset = pageHeaderPanel.offsetHeight;
+    } else {
+      pageHeaderOffset = 0;
+    }
+  }
+
+  if (alignment === 'top') {
+    const bodyHeight = document.body.clientHeight;
+    bottom = (bodyHeight - refOffsets.top) + verticalOffset;
+    if (bodyHeight - (bottom + elDimensions.height) < (window.scrollY || window.pageYOffset) + pageHeaderOffset) {
+      result = false;
+    }
+  } else {
+    top = refOffsets.top + refDimensions.height + verticalOffset;
+    if (top + elDimensions.height - (window.scrollY || window.pageYOffset) > windowHeight) {
+      result = false;
+    }
+  }
+
+  return {
+    align: alignment,
+    bottom: bottom,
+    top: top,
+    result: result,
+  };
+}
+
+/**
+ * Sets the alignment for target element relatively to the reference element.
+ *
+ * @param  {Element}    element    target element
+ * @param  {Element}    referenceElement    reference element
+ * @param  {Object<string, *>}  options    list of options to alter the behavior
+ */
+export function set(element: HTMLElement, referenceElement: HTMLElement, options: AlignmentOptions): void {
+  options = Core.extend({
+    // offset to reference element
+    verticalOffset: 0,
+    // align the pointer element, expects .elementPointer as a direct child of given element
+    pointer: false,
+    // use static pointer positions, expects two items: class to move it to the bottom and the second to move it to the right
+    pointerClassNames: [],
+    // alternate element used to calculate dimensions
+    refDimensionsElement: null,
+    // preferred alignment, possible values: left/right/center and top/bottom
+    horizontal: 'left',
+    vertical: 'bottom',
+    // allow flipping over axis, possible values: both, horizontal, vertical and none
+    allowFlip: 'both',
+  }, options) as AlignmentOptions;
+
+  if (!Array.isArray(options.pointerClassNames) || options.pointerClassNames.length !== (options.pointer ? 1 : 2)) {
+    options.pointerClassNames = [];
+  }
+  if (['left', 'right', 'center'].indexOf(options.horizontal) === -1) {
+    options.horizontal = 'left';
+  }
+  if (options.vertical !== 'bottom') {
+    options.vertical = 'top';
+  }
+  if (['both', 'horizontal', 'vertical', 'none'].indexOf(options.allowFlip) === -1) {
+    options.allowFlip = 'both';
+  }
+
+  // Place the element in the upper left corner to prevent calculation issues due to possible scrollbars.
+  DomUtil.setStyles(element, {
+    bottom: 'auto !important',
+    left: '0 !important',
+    right: 'auto !important',
+    top: '0 !important',
+    visibility: 'hidden !important',
+  });
+
+  const elDimensions = DomUtil.outerDimensions(element);
+  const refDimensions = DomUtil.outerDimensions(options.refDimensionsElement instanceof HTMLElement ? options.refDimensionsElement : referenceElement);
+  const refOffsets = DomUtil.offset(referenceElement);
+  const windowHeight = window.innerHeight;
+  const windowWidth = document.body.clientWidth;
+
+  let horizontal: HorizontalResult | null = null;
+  let alignCenter = false;
+  if (options.horizontal === 'center') {
+    alignCenter = true;
+    horizontal = tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
+    if (!horizontal.result) {
+      if (options.allowFlip === 'both' || options.allowFlip === 'horizontal') {
+        options.horizontal = 'left';
+      } else {
+        horizontal.result = true;
+      }
+    }
+  }
+
+  // in rtl languages we simply swap the value for 'horizontal'
+  if (Language.get('wcf.global.pageDirection') === 'rtl') {
+    options.horizontal = (options.horizontal === 'left') ? 'right' : 'left';
+  }
+
+  if (horizontal === null || !horizontal.result) {
+    const horizontalCenter = horizontal;
+    horizontal = tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
+    if (!horizontal.result && (options.allowFlip === 'both' || options.allowFlip === 'horizontal')) {
+      const horizontalFlipped = tryAlignmentHorizontal((options.horizontal === 'left' ? 'right' : 'left'), elDimensions, refDimensions, refOffsets, windowWidth);
+      // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
+      if (horizontalFlipped.result) {
+        horizontal = horizontalFlipped;
+      } else if (alignCenter) {
+        horizontal = horizontalCenter;
+      }
+    }
+  }
+
+  const left = horizontal!.left;
+  const right = horizontal!.right;
+  let vertical = tryAlignmentVertical(options.vertical, elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
+  if (!vertical.result && (options.allowFlip === 'both' || options.allowFlip === 'vertical')) {
+    const verticalFlipped = tryAlignmentVertical((options.vertical === 'top' ? 'bottom' : 'top'), elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
+    // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
+    if (verticalFlipped.result) {
+      vertical = verticalFlipped;
+    }
+  }
+
+  const bottom = vertical.bottom;
+  const top = vertical.top;
+  // set pointer position
+  if (options.pointer) {
+    const pointers = DomTraverse.childrenByClass(element, 'elementPointer');
+    const pointer = pointers[0] || null;
+    if (pointer === null) {
+      throw new Error("Expected the .elementPointer element to be a direct children.");
+    }
+
+    if (horizontal!.align === 'center') {
+      pointer.classList.add('center');
+      pointer.classList.remove('left', 'right');
+    } else {
+      pointer.classList.add(horizontal!.align);
+      pointer.classList.remove('center');
+      pointer.classList.remove(horizontal!.align === 'left' ? 'right' : 'left');
+    }
+
+    if (vertical.align === 'top') {
+      pointer.classList.add('flipVertical');
+    } else {
+      pointer.classList.remove('flipVertical');
+    }
+  } else if (options.pointerClassNames.length === 2) {
+    element.classList[(top === 'auto' ? 'add' : 'remove')](options.pointerClassNames[PointerClass.Bottom]);
+    element.classList[(left === 'auto' ? 'add' : 'remove')](options.pointerClassNames[PointerClass.Right]);
+  }
+  
+  DomUtil.setStyles(element, {
+    bottom: bottom === 'auto' ? bottom : Math.round(bottom) + 'px',
+    left: left === 'auto' ? left : Math.ceil(left) + 'px',
+    right: right === 'auto' ? right : Math.floor(right) + 'px',
+    top: top === 'auto' ? top : Math.round(top) + 'px',
+  });
+  
+  DomUtil.show(element);
+  element.style.removeProperty('visibility');
+}
+
+type AllowFlip = 'both' | 'horizontal' | 'none' | 'vertical';
+
+export interface AlignmentOptions {
+  // offset to reference element
+  verticalOffset: number;
+  // align the pointer element, expects .elementPointer as a direct child of given element
+  pointer: boolean;
+  // use static pointer positions, expects two items: class to move it to the bottom and the second to move it to the right
+  pointerClassNames: string[];
+  // alternate element used to calculate dimensions
+  refDimensionsElement: HTMLElement | null;
+  // preferred alignment, possible values: left/right/center and top/bottom
+  horizontal: HorizontalAlignment;
+  vertical: VerticalAlignment;
+  // allow flipping over axis, possible values: both, horizontal, vertical and none
+  allowFlip: AllowFlip;
+}