| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| const ESCAPE_KEYCODE = 27; // KeyboardEvent.which value for Escape (Esc) key |
| |
| const Default = { |
| backdrop: true, |
| keyboard: true, |
| focus: true, |
| show: true |
| }; |
| |
| const DefaultType = { |
| backdrop: "(boolean|string)", |
| keyboard: "boolean", |
| focus: "boolean", |
| show: "boolean" |
| }; |
| |
| const Event = { |
| HIDE: "hide.bs.modal", |
| HIDE_PREVENTED: "hidePrevented.bs.modal", |
| HIDDEN: "hidden.bs.modal", |
| SHOW: "show.bs.modal", |
| SHOWN: "shown.bs.modal", |
| FOCUSIN: "focusin.bs.modal", |
| RESIZE: "resize.bs.modal", |
| CLICK_DISMISS: "click.dismiss.bs.modal", |
| KEYDOWN_DISMISS: "keydown.dismiss.bs.modal", |
| MOUSEUP_DISMISS: "mouseup.dismiss.bs.modal", |
| MOUSEDOWN_DISMISS: "mousedown.dismiss.bs.modal", |
| CLICK_DATA_API: "click.bs.modal.data-api" |
| }; |
| |
| const ClassName = { |
| SCROLLABLE: "modal-dialog-scrollable", |
| SCROLLBAR_MEASURER: "modal-scrollbar-measure", |
| BACKDROP: "modal-backdrop", |
| OPEN: "modal-open", |
| FADE: "fade", |
| SHOW: "show", |
| STATIC: "modal-static" |
| }; |
| |
| const Selector = { |
| DIALOG: ".modal-dialog", |
| MODAL_BODY: ".modal-body", |
| DATA_TOGGLE: "[data-toggle='modal']", |
| DATA_DISMISS: "[data-dismiss='modal']", |
| FIXED_CONTENT: ".fixed-top, .fixed-bottom, .is-fixed, .sticky-top", |
| STICKY_CONTENT: ".sticky-top" |
| }; |
| |
| export class Popup extends HTMLElement { |
| |
| TRANSITION_END: string = "bsTransitionEnd"; |
| |
| _dialog: HTMLElement; |
| _backdrop; |
| _isShown; |
| _isBodyOverflowing; |
| _ignoreBackdropClick; |
| // _isTransitioning; |
| _scrollbarWidth; |
| _clickDismiss: (event: Event) => void; |
| |
| constructor() { |
| super(); |
| this._dialog = this.querySelector(Selector.DIALOG); |
| this._backdrop = null; |
| this._isShown = false; |
| this._isBodyOverflowing = false; |
| this._ignoreBackdropClick = false; |
| // this._isTransitioning = false; |
| this._scrollbarWidth = 0; |
| } |
| |
| connectedCallback(): void { |
| const hidden = Collapse.findHidden(this); |
| if (hidden.value === "false") { |
| // XXX hack: this is needed for popups open by AJAX. |
| // XXX currently the DOM replacement done by Tobago doesn't remove the modal-backdrop |
| for (const backdrop of document.querySelectorAll(".modal-backdrop")) { |
| backdrop.parentNode.removeChild(backdrop); |
| } |
| this.show(); // inits and opens the popup |
| } else { |
| this.hide(); // inits and hides the popup |
| } |
| } |
| |
| // Public |
| |
| // toggle(relatedTarget) { |
| // return this._isShown ? this.hide() : this.show(relatedTarget) |
| // } |
| |
| show():void { |
| if (this._isShown /*|| this._isTransitioning*/) { |
| return; |
| } |
| |
| /* |
| if (this.classList.contains(ClassName.FADE)) { |
| this._isTransitioning = true; |
| } |
| */ |
| |
| const showEvent = new CustomEvent(Event.SHOW, /*{*/ |
| // detail: relatedTarget // TBD: detail or anything other |
| /*}*/); |
| |
| this.dispatchEvent(showEvent); |
| |
| if (this._isShown || showEvent.defaultPrevented) { |
| return; |
| } |
| |
| this._isShown = true; |
| |
| // this._checkScrollbar(); |
| // this._setScrollbar(); |
| // |
| // this._adjustDialog(); |
| // |
| // this._setEscapeEvent(); |
| // this._setResizeEvent(); |
| |
| this._clickDismiss = (event: Event) => {this.hide();}; |
| // this._clickDismiss = (event: Event) => {this.hide(event)}; |
| if (this.classList.contains(Selector.DATA_DISMISS)) { |
| this.addEventListener(Event.CLICK_DISMISS, this._clickDismiss); |
| } |
| |
| this._dialog.addEventListener(Event.MOUSEDOWN_DISMISS, () => { |
| // $(this._element).one(Event.MOUSEUP_DISMISS, (event) => { // XXX not implemented yet |
| // if ($(event.target).is(this._element)) { // XXX not implemented yet |
| this._ignoreBackdropClick = true; |
| // } |
| // }) |
| }); |
| |
| // this._showBackdrop(() => this._showElement(relatedTarget)) |
| this._showElement(); |
| } |
| |
| hide(/*event*/):void { |
| // if (event) { |
| // event.preventDefault() |
| // } |
| |
| if (!this._isShown/* || this._isTransitioning*/) { |
| return; |
| } |
| |
| const hideEvent = new CustomEvent(Event.HIDE); |
| |
| this.dispatchEvent(hideEvent); |
| |
| if (!this._isShown || hideEvent.defaultPrevented) { |
| return; |
| } |
| |
| this._isShown = false; |
| // const transition = this.classList.contains(ClassName.FADE); |
| |
| // if (transition) { |
| // this._isTransitioning = true |
| // } |
| |
| // this._setEscapeEvent(); |
| // this._setResizeEvent(); |
| |
| $(document).off(Event.FOCUSIN); |
| |
| this.classList.remove(ClassName.SHOW); |
| |
| this.removeEventListener(Event.CLICK_DISMISS, this._clickDismiss); |
| $(this._dialog).off(Event.MOUSEDOWN_DISMISS); |
| |
| // if (transition) { |
| // const transitionDuration = this.getTransitionDuration(); |
| // |
| // this.addEventListener(Popup.TRANSITION_END, (event:Event) => this._hideModal(event)); |
| // this.emulateTransitionEnd(transitionDuration) |
| // } else { |
| this._hideModal(); |
| // } |
| } |
| |
| // dispose() { |
| // [window, this._element, this._dialog] |
| // .forEach((htmlElement) => $(htmlElement).off(`.bs.modal`)); |
| |
| /** |
| * `document` has 2 events `Event.FOCUSIN` and `Event.CLICK_DATA_API` |
| * Do not move `document` in `htmlElements` array |
| * It will remove `Event.CLICK_DATA_API` event that should remain |
| */ |
| // $(document).off(Event.FOCUSIN); |
| // |
| // $.removeData(this._element, 'bs.modal'); |
| // |
| // this._dialog = null; |
| // this._backdrop = null; |
| // this._isShown = null; |
| // this._isBodyOverflowing = null; |
| // this._ignoreBackdropClick = null; |
| // this._isTransitioning = null; |
| // this._scrollbarWidth = null; |
| // } |
| |
| // handleUpdate() { |
| // this._adjustDialog(); |
| // } |
| |
| // Private |
| /* |
| _triggerBackdropTransition() { |
| if (this._config.backdrop === 'static') { |
| const hideEventPrevented = $.Event(Event.HIDE_PREVENTED); |
| |
| $(this._element).trigger(hideEventPrevented); |
| if (hideEventPrevented.defaultPrevented) { |
| return; |
| } |
| |
| this._element.classList.add(ClassName.STATIC); |
| |
| const modalTransitionDuration = Util.getTransitionDurationFromElement(this._element) |
| |
| $(this._element).one(Util.TRANSITION_END, () => { |
| this._element.classList.remove(ClassName.STATIC) |
| }) |
| .emulateTransitionEnd(modalTransitionDuration); |
| this._element.focus(); |
| } else { |
| this.hide(); |
| } |
| } |
| */ |
| _showElement(/*relatedTarget*/):void { |
| // const transition = $(this._element).hasClass(ClassName.FADE) |
| const modalBody = this._dialog ? this._dialog.querySelector(Selector.MODAL_BODY) : null; |
| |
| if (!this.parentNode || |
| this.parentNode.nodeType !== Node.ELEMENT_NODE) { |
| // Don't move modal's DOM position |
| document.body.appendChild(this); |
| } |
| |
| this.style.display = "block"; |
| this.removeAttribute("aria-hidden"); |
| this.setAttribute("aria-modal", "true"); |
| |
| if ($(this._dialog).hasClass(ClassName.SCROLLABLE) && modalBody) { |
| modalBody.scrollTop = 0; |
| } else { |
| this.scrollTop = 0; |
| } |
| |
| // if (transition) { |
| // Util.reflow(this._element) |
| // } |
| |
| this.classList.add(ClassName.SHOW); |
| |
| // if (this._config.focus) { |
| // this._enforceFocus() |
| // } |
| |
| // const shownEvent = $.Event(Event.SHOWN, { |
| // relatedTarget |
| // }); |
| |
| // const transitionComplete = () => { |
| // if (this._config.focus) { |
| // this._element.focus() |
| // } |
| // this._isTransitioning = false |
| // $(this._element).trigger(shownEvent) |
| // }; |
| |
| // if (transition) { |
| // const transitionDuration = Util.getTransitionDurationFromElement(this._dialog) |
| // |
| // $(this._dialog) |
| // .one(Util.TRANSITION_END, transitionComplete) |
| // .emulateTransitionEnd(transitionDuration) |
| // } else { |
| // transitionComplete() |
| // } |
| } |
| /* |
| _enforceFocus() { |
| $(document) |
| .off(Event.FOCUSIN) // Guard against infinite focus loop |
| .on(Event.FOCUSIN, (event) => { |
| if (document !== event.target && |
| this._element !== event.target && |
| $(this._element).has(event.target).length === 0) { |
| this._element.focus() |
| } |
| }) |
| } |
| |
| _setEscapeEvent() { |
| if (this._isShown && this._config.keyboard) { |
| $(this._element).on(Event.KEYDOWN_DISMISS, (event) => { |
| if (event.which === ESCAPE_KEYCODE) { |
| this._triggerBackdropTransition() |
| } |
| }) |
| } else if (!this._isShown) { |
| $(this._element).off(Event.KEYDOWN_DISMISS) |
| } |
| } |
| |
| _setResizeEvent() { |
| if (this._isShown) { |
| $(window).on(Event.RESIZE, (event) => this.handleUpdate(event)) |
| } else { |
| $(window).off(Event.RESIZE) |
| } |
| } |
| */ |
| _hideModal():void { |
| this.style.display = "none"; |
| this.setAttribute("aria-hidden", "true"); |
| this.removeAttribute("aria-modal"); |
| // this._isTransitioning = false; |
| /* |
| this._showBackdrop(() => { |
| $(document.body).removeClass(ClassName.OPEN); |
| this._resetAdjustments(); |
| this._resetScrollbar(); |
| $(this._element).trigger(Event.HIDDEN) |
| }) |
| */ |
| } |
| /* |
| _removeBackdrop() { |
| if (this._backdrop) { |
| $(this._backdrop).remove(); |
| this._backdrop = null; |
| } |
| } |
| |
| _showBackdrop(callback) { |
| const animate = $(this._element).hasClass(ClassName.FADE) |
| ? ClassName.FADE : '' |
| |
| if (this._isShown && this._config.backdrop) { |
| this._backdrop = document.createElement('div') |
| this._backdrop.className = ClassName.BACKDROP |
| |
| if (animate) { |
| this._backdrop.classList.add(animate) |
| } |
| |
| $(this._backdrop).appendTo(document.body) |
| |
| $(this._element).on(Event.CLICK_DISMISS, (event) => { |
| if (this._ignoreBackdropClick) { |
| this._ignoreBackdropClick = false |
| return |
| } |
| if (event.target !== event.currentTarget) { |
| return |
| } |
| |
| this._triggerBackdropTransition() |
| }) |
| |
| if (animate) { |
| Util.reflow(this._backdrop) |
| } |
| |
| $(this._backdrop).addClass(ClassName.SHOW) |
| |
| if (!callback) { |
| return |
| } |
| |
| if (!animate) { |
| callback() |
| return |
| } |
| |
| const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop) |
| |
| $(this._backdrop) |
| .one(Util.TRANSITION_END, callback) |
| .emulateTransitionEnd(backdropTransitionDuration) |
| } else if (!this._isShown && this._backdrop) { |
| $(this._backdrop).removeClass(ClassName.SHOW) |
| |
| const callbackRemove = () => { |
| this._removeBackdrop() |
| if (callback) { |
| callback() |
| } |
| } |
| |
| if ($(this._element).hasClass(ClassName.FADE)) { |
| const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop) |
| |
| $(this._backdrop) |
| .one(Util.TRANSITION_END, callbackRemove) |
| .emulateTransitionEnd(backdropTransitionDuration) |
| } else { |
| callbackRemove() |
| } |
| } else if (callback) { |
| callback() |
| } |
| } |
| |
| // ---------------------------------------------------------------------- |
| // the following methods are used to handle overflowing modals |
| // todo (fat): these should probably be refactored out of modal.js |
| // ---------------------------------------------------------------------- |
| |
| _adjustDialog() { |
| const isModalOverflowing = |
| this._element.scrollHeight > document.documentElement.clientHeight |
| |
| if (!this._isBodyOverflowing && isModalOverflowing) { |
| this._element.style.paddingLeft = `${this._scrollbarWidth}px` |
| } |
| |
| if (this._isBodyOverflowing && !isModalOverflowing) { |
| this._element.style.paddingRight = `${this._scrollbarWidth}px` |
| } |
| } |
| |
| _resetAdjustments() { |
| this._element.style.paddingLeft = '' |
| this._element.style.paddingRight = '' |
| } |
| |
| _checkScrollbar() { |
| const rect = document.body.getBoundingClientRect() |
| this._isBodyOverflowing = rect.left + rect.right < window.innerWidth |
| this._scrollbarWidth = this._getScrollbarWidth() |
| } |
| |
| _setScrollbar() { |
| if (this._isBodyOverflowing) { |
| // Note: DOMNode.style.paddingRight returns the actual value or '' if not set |
| // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set |
| const fixedContent = [].slice.call(document.querySelectorAll(Selector.FIXED_CONTENT)) |
| const stickyContent = [].slice.call(document.querySelectorAll(Selector.STICKY_CONTENT)) |
| |
| // Adjust fixed content padding |
| $(fixedContent).each((index, element) => { |
| const actualPadding = element.style.paddingRight |
| const calculatedPadding = $(element).css('padding-right') |
| $(element) |
| .data('padding-right', actualPadding) |
| .css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`) |
| }) |
| |
| // Adjust sticky content margin |
| $(stickyContent).each((index, element) => { |
| const actualMargin = element.style.marginRight |
| const calculatedMargin = $(element).css('margin-right') |
| $(element) |
| .data('margin-right', actualMargin) |
| .css('margin-right', `${parseFloat(calculatedMargin) - this._scrollbarWidth}px`) |
| }) |
| |
| // Adjust body padding |
| const actualPadding = document.body.style.paddingRight |
| const calculatedPadding = $(document.body).css('padding-right') |
| $(document.body) |
| .data('padding-right', actualPadding) |
| .css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`) |
| } |
| |
| $(document.body).addClass(ClassName.OPEN) |
| } |
| |
| _resetScrollbar() { |
| // Restore fixed content padding |
| const fixedContent = [].slice.call(document.querySelectorAll(Selector.FIXED_CONTENT)) |
| $(fixedContent).each((index, element) => { |
| const padding = $(element).data('padding-right') |
| $(element).removeData('padding-right') |
| element.style.paddingRight = padding ? padding : '' |
| }) |
| |
| // Restore sticky content |
| const elements = [].slice.call(document.querySelectorAll(`${Selector.STICKY_CONTENT}`)) |
| $(elements).each((index, element) => { |
| const margin = $(element).data('margin-right') |
| if (typeof margin !== 'undefined') { |
| $(element).css('margin-right', margin).removeData('margin-right') |
| } |
| }) |
| |
| // Restore body padding |
| const padding = $(document.body).data('padding-right') |
| $(document.body).removeData('padding-right') |
| document.body.style.paddingRight = padding ? padding : '' |
| } |
| |
| _getScrollbarWidth() { // thx d.walsh |
| const scrollDiv = document.createElement('div') |
| scrollDiv.className = ClassName.SCROLLBAR_MEASURER |
| document.body.appendChild(scrollDiv) |
| const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth |
| document.body.removeChild(scrollDiv) |
| return scrollbarWidth |
| } |
| |
| // Static |
| |
| static _jQueryInterface(config, relatedTarget) { |
| return this.each(function () { |
| let data = $(this).data('bs.modal') |
| const _config = { |
| ...Default, |
| ...$(this).data(), |
| ...typeof config === 'object' && config ? config : {} |
| } |
| |
| if (!data) { |
| data = new Modal(this, _config) |
| $(this).data('bs.modal', data) |
| } |
| |
| if (typeof config === 'string') { |
| if (typeof data[config] === 'undefined') { |
| throw new TypeError(`No method named "${config}"`) |
| } |
| data[config](relatedTarget) |
| } else if (_config.show) { |
| data.show(relatedTarget) |
| } |
| }) |
| } |
| |
| getTransitionDuration(): number { |
| // Get transition-duration of the element |
| let transitionDuration = this.style.transitionDuration; |
| let transitionDelay = this.style.transitionDelay; |
| |
| const floatTransitionDuration = parseFloat(transitionDuration); |
| const floatTransitionDelay = parseFloat(transitionDelay); |
| |
| // Return 0 if element or transition duration is not found |
| if (!floatTransitionDuration && !floatTransitionDelay) { |
| return 0; |
| } |
| |
| // If multiple durations are defined, take the first |
| transitionDuration = transitionDuration.split(',')[0]; |
| transitionDelay = transitionDelay.split(',')[0]; |
| |
| return (parseFloat(transitionDuration) + parseFloat(transitionDelay)) * 1000; |
| } |
| */ |
| |
| } |
| |
| document.addEventListener("DOMContentLoaded", function (event: Event): void { |
| window.customElements.define("tobago-popup", Popup); |
| }); |
| |
| export class Collapse { |
| |
| static findHidden(element: HTMLElement): HTMLInputElement { |
| return document.getElementById(element.id + "::collapse") as HTMLInputElement; |
| } |
| |
| static execute = function (action: string, target: HTMLElement): void { |
| const hidden = Collapse.findHidden(target); |
| let newCollapsed; |
| switch (action) { |
| case "hide": |
| newCollapsed = true; |
| break; |
| case "show": |
| newCollapsed = false; |
| break; |
| default: |
| console.error("unknown action: '" + action + "'"); |
| } |
| if (newCollapsed) { |
| if (target instanceof Popup) { |
| target.hide(); |
| } else { |
| target.classList.add("tobago-collapsed"); |
| } |
| } else { |
| if (target instanceof Popup) { |
| target.show(); |
| } else { |
| target.classList.remove("tobago-collapsed"); |
| } |
| } |
| hidden.value = newCollapsed; |
| }; |
| } |