vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit b47cf97409acddffb71aa30f483b95fe9ba09ee4
parent 5e802f8aa3b269975cc286c9dd06435fcd599468
Author: BlackDex <black.dex@gmail.com>
Date:   Mon,  1 Jun 2020 18:58:38 +0200

Updated js/css libraries and fixed smallscreen err

- Updated bootstrap js and css to the latest version
- Fixed issue with small-screens where the menu overlaps the token input
  - The menu now collapses to a hamburger menu
  - Menu's only accessable when logedin are hidden when you are not
- Changed Users Overview to use a table to prevent small-screen issues.

Diffstat:
Msrc/static/scripts/bootstrap-native-v4.js | 3485++++++++++++++++++++++++++++++++++++-------------------------------------------
Msrc/static/scripts/bootstrap.css | 748++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/static/templates/admin/base.hbs | 98+++++++++++++++++++++++++++++++++----------------------------------------------
Msrc/static/templates/admin/users.hbs | 88+++++++++++++++++++++++++++++++++++++++++--------------------------------------
4 files changed, 2173 insertions(+), 2246 deletions(-)

diff --git a/src/static/scripts/bootstrap-native-v4.js b/src/static/scripts/bootstrap-native-v4.js @@ -1,1999 +1,1689 @@ -// Native Javascript for Bootstrap 4 v2.0.27 | © dnp_theme | MIT-License -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD support: - define([], factory); - } else if (typeof module === 'object' && module.exports) { - // CommonJS-like: - module.exports = factory(); - } else { - // Browser globals (root is window) - var bsn = factory(); - root.Alert = bsn.Alert; - root.Button = bsn.Button; - root.Carousel = bsn.Carousel; - root.Collapse = bsn.Collapse; - root.Dropdown = bsn.Dropdown; - root.Modal = bsn.Modal; - root.Popover = bsn.Popover; - root.ScrollSpy = bsn.ScrollSpy; - root.Tab = bsn.Tab; - root.Toast = bsn.Toast; - root.Tooltip = bsn.Tooltip; +/*! + * Native JavaScript for Bootstrap v3.0.1 (https://thednp.github.io/bootstrap.native/) + * Copyright 2015-2020 © dnp_theme + * Licensed under MIT (https://github.com/thednp/bootstrap.native/blob/master/LICENSE) + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.BSN = factory()); +}(this, (function () { 'use strict'; + + function hasClass(element,classNAME) { + return element.classList.contains(classNAME) } -}(this, function () { - - /* Native Javascript for Bootstrap 4 | Internal Utility Functions - ----------------------------------------------------------------*/ - "use strict"; - - // globals - var globalObject = typeof global !== 'undefined' ? global : this||window, - DOC = document, HTML = DOC.documentElement, body = 'body', // allow the library to be used in <head> - - // Native Javascript for Bootstrap Global Object - BSN = globalObject.BSN = {}, - supports = BSN.supports = [], - - // function toggle attributes - dataToggle = 'data-toggle', - dataDismiss = 'data-dismiss', - dataSpy = 'data-spy', - dataRide = 'data-ride', - - // components - stringAlert = 'Alert', - stringButton = 'Button', - stringCarousel = 'Carousel', - stringCollapse = 'Collapse', - stringDropdown = 'Dropdown', - stringModal = 'Modal', - stringPopover = 'Popover', - stringScrollSpy = 'ScrollSpy', - stringTab = 'Tab', - stringTooltip = 'Tooltip', - stringToast = 'Toast', - - // options DATA API - dataAutohide = 'data-autohide', - databackdrop = 'data-backdrop', - dataKeyboard = 'data-keyboard', - dataTarget = 'data-target', - dataInterval = 'data-interval', - dataHeight = 'data-height', - dataPause = 'data-pause', - dataTitle = 'data-title', - dataOriginalTitle = 'data-original-title', - dataDismissible = 'data-dismissible', - dataTrigger = 'data-trigger', - dataAnimation = 'data-animation', - dataContainer = 'data-container', - dataPlacement = 'data-placement', - dataDelay = 'data-delay', - - // option keys - backdrop = 'backdrop', keyboard = 'keyboard', delay = 'delay', - content = 'content', target = 'target', currentTarget = 'currentTarget', - interval = 'interval', pause = 'pause', animation = 'animation', - placement = 'placement', container = 'container', - - // box model - offsetTop = 'offsetTop', offsetBottom = 'offsetBottom', - offsetLeft = 'offsetLeft', - scrollTop = 'scrollTop', scrollLeft = 'scrollLeft', - clientWidth = 'clientWidth', clientHeight = 'clientHeight', - offsetWidth = 'offsetWidth', offsetHeight = 'offsetHeight', - innerWidth = 'innerWidth', innerHeight = 'innerHeight', - scrollHeight = 'scrollHeight', scrollWidth = 'scrollWidth', - height = 'height', - - // aria - ariaExpanded = 'aria-expanded', - ariaHidden = 'aria-hidden', - ariaSelected = 'aria-selected', - - // event names - clickEvent = 'click', - focusEvent = 'focus', - hoverEvent = 'hover', - keydownEvent = 'keydown', - keyupEvent = 'keyup', - resizeEvent = 'resize', // passive - scrollEvent = 'scroll', // passive - mouseHover = ('onmouseleave' in DOC) ? [ 'mouseenter', 'mouseleave'] : [ 'mouseover', 'mouseout' ], - // touch since 2.0.26 - touchEvents = { start: 'touchstart', end: 'touchend', move:'touchmove' }, // passive - // originalEvents - showEvent = 'show', - shownEvent = 'shown', - hideEvent = 'hide', - hiddenEvent = 'hidden', - closeEvent = 'close', - closedEvent = 'closed', - slidEvent = 'slid', - slideEvent = 'slide', - changeEvent = 'change', - - // other - getAttribute = 'getAttribute', - setAttribute = 'setAttribute', - hasAttribute = 'hasAttribute', - createElement = 'createElement', - appendChild = 'appendChild', - innerHTML = 'innerHTML', - getElementsByTagName = 'getElementsByTagName', - preventDefault = 'preventDefault', - getBoundingClientRect = 'getBoundingClientRect', - querySelectorAll = 'querySelectorAll', - getElementsByCLASSNAME = 'getElementsByClassName', - getComputedStyle = 'getComputedStyle', - - indexOf = 'indexOf', - parentNode = 'parentNode', - length = 'length', - toLowerCase = 'toLowerCase', - Transition = 'Transition', - Duration = 'Duration', - Webkit = 'Webkit', - style = 'style', - push = 'push', - tabindex = 'tabindex', - contains = 'contains', - - active = 'active', - showClass = 'show', - collapsing = 'collapsing', - disabled = 'disabled', - loading = 'loading', - left = 'left', - right = 'right', - top = 'top', - bottom = 'bottom', - - // tooltip / popover - tipPositions = /\b(top|bottom|left|right)+/, - - // modal - modalOverlay = 0, - fixedTop = 'fixed-top', - fixedBottom = 'fixed-bottom', - - // transitionEnd since 2.0.4 - supportTransitions = Webkit+Transition in HTML[style] || Transition[toLowerCase]() in HTML[style], - transitionEndEvent = Webkit+Transition in HTML[style] ? Webkit[toLowerCase]()+Transition+'End' : Transition[toLowerCase]()+'end', - transitionDuration = Webkit+Duration in HTML[style] ? Webkit[toLowerCase]()+Transition+Duration : Transition[toLowerCase]()+Duration, - - // set new focus element since 2.0.3 - setFocus = function(element){ - element.focus ? element.focus() : element.setActive(); - }, - - // class manipulation, since 2.0.0 requires polyfill.js - addClass = function(element,classNAME) { - element.classList.add(classNAME); - }, - removeClass = function(element,classNAME) { - element.classList.remove(classNAME); - }, - hasClass = function(element,classNAME){ // since 2.0.0 - return element.classList[contains](classNAME); - }, - - // selection methods - getElementsByClassName = function(element,classNAME) { // returns Array - return [].slice.call(element[getElementsByCLASSNAME]( classNAME )); - }, - queryElement = function (selector, parent) { - var lookUp = parent ? parent : DOC; - return typeof selector === 'object' ? selector : lookUp.querySelector(selector); - }, - getClosest = function (element, selector) { //element is the element and selector is for the closest parent element to find - // source http://gomakethings.com/climbing-up-and-down-the-dom-tree-with-vanilla-javascript/ - var firstChar = selector.charAt(0), selectorSubstring = selector.substr(1); - if ( firstChar === '.' ) {// If selector is a class - for ( ; element && element !== DOC; element = element[parentNode] ) { // Get closest match - if ( queryElement(selector,element[parentNode]) !== null && hasClass(element,selectorSubstring) ) { return element; } - } - } else if ( firstChar === '#' ) { // If selector is an ID - for ( ; element && element !== DOC; element = element[parentNode] ) { // Get closest match - if ( element.id === selectorSubstring ) { return element; } - } - } - return false; - }, - - // event attach jQuery style / trigger since 1.2.0 - on = function (element, event, handler, options) { - options = options || false; - element.addEventListener(event, handler, options); - }, - off = function(element, event, handler, options) { - options = options || false; - element.removeEventListener(event, handler, options); - }, - one = function (element, event, handler, options) { // one since 2.0.4 - on(element, event, function handlerWrapper(e){ + + function removeClass(element,classNAME) { + element.classList.remove(classNAME); + } + + function on (element, event, handler, options) { + options = options || false; + element.addEventListener(event, handler, options); + } + + function off (element, event, handler, options) { + options = options || false; + element.removeEventListener(event, handler, options); + } + + function one (element, event, handler, options) { + on(element, event, function handlerWrapper(e){ + if (e.target === element) { handler(e); off(element, event, handlerWrapper, options); - }, options); - }, - // determine support for passive events - supportPassive = (function(){ - // Test via a getter in the options object to see if the passive property is accessed - var result = false; - try { - var opts = Object.defineProperty({}, 'passive', { - get: function() { - result = true; - } - }); - one(globalObject, 'testPassive', null, opts); - } catch (e) {} - - return result; - }()), - // event options - // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection - passiveHandler = supportPassive ? { passive: true } : false, - // transitions - getTransitionDurationFromElement = function(element) { - var duration = supportTransitions ? globalObject[getComputedStyle](element)[transitionDuration] : 0; - duration = parseFloat(duration); - duration = typeof duration === 'number' && !isNaN(duration) ? duration * 1000 : 0; - return duration; // we take a short offset to make sure we fire on the next frame after animation - }, - emulateTransitionEnd = function(element,handler){ // emulateTransitionEnd since 2.0.4 - var called = 0, duration = getTransitionDurationFromElement(element); - duration ? one(element, transitionEndEvent, function(e){ !called && handler(e), called = 1; }) - : setTimeout(function() { !called && handler(), called = 1; }, 17); - }, - bootstrapCustomEvent = function (eventName, componentName, related) { - var OriginalCustomEvent = new CustomEvent( eventName + '.bs.' + componentName); - OriginalCustomEvent.relatedTarget = related; - this.dispatchEvent(OriginalCustomEvent); - }, - - // tooltip / popover stuff - getScroll = function() { // also Affix and ScrollSpy uses it - return { - y : globalObject.pageYOffset || HTML[scrollTop], - x : globalObject.pageXOffset || HTML[scrollLeft] - } - }, - styleTip = function(link,element,position,parent) { // both popovers and tooltips (target,tooltip,placement,elementToAppendTo) - var elementDimensions = { w : element[offsetWidth], h: element[offsetHeight] }, - windowWidth = (HTML[clientWidth] || DOC[body][clientWidth]), - windowHeight = (HTML[clientHeight] || DOC[body][clientHeight]), - rect = link[getBoundingClientRect](), - scroll = parent === DOC[body] ? getScroll() : { x: parent[offsetLeft] + parent[scrollLeft], y: parent[offsetTop] + parent[scrollTop] }, - linkDimensions = { w: rect[right] - rect[left], h: rect[bottom] - rect[top] }, - isPopover = hasClass(element,'popover'), - topPosition, leftPosition, - - arrow = queryElement('.arrow',element), - arrowTop, arrowLeft, arrowWidth, arrowHeight, - - halfTopExceed = rect[top] + linkDimensions.h/2 - elementDimensions.h/2 < 0, - halfLeftExceed = rect[left] + linkDimensions.w/2 - elementDimensions.w/2 < 0, - halfRightExceed = rect[left] + elementDimensions.w/2 + linkDimensions.w/2 >= windowWidth, - halfBottomExceed = rect[top] + elementDimensions.h/2 + linkDimensions.h/2 >= windowHeight, - topExceed = rect[top] - elementDimensions.h < 0, - leftExceed = rect[left] - elementDimensions.w < 0, - bottomExceed = rect[top] + elementDimensions.h + linkDimensions.h >= windowHeight, - rightExceed = rect[left] + elementDimensions.w + linkDimensions.w >= windowWidth; - - // recompute position - position = (position === left || position === right) && leftExceed && rightExceed ? top : position; // first, when both left and right limits are exceeded, we fall back to top|bottom - position = position === top && topExceed ? bottom : position; - position = position === bottom && bottomExceed ? top : position; - position = position === left && leftExceed ? right : position; - position = position === right && rightExceed ? left : position; - - // update tooltip/popover class - element.className[indexOf](position) === -1 && (element.className = element.className.replace(tipPositions,position)); - - // we check the computed width & height and update here - arrowWidth = arrow[offsetWidth]; arrowHeight = arrow[offsetHeight]; - - // apply styling to tooltip or popover - if ( position === left || position === right ) { // secondary|side positions - if ( position === left ) { // LEFT - leftPosition = rect[left] + scroll.x - elementDimensions.w - ( isPopover ? arrowWidth : 0 ); - } else { // RIGHT - leftPosition = rect[left] + scroll.x + linkDimensions.w; - } - - // adjust top and arrow - if (halfTopExceed) { - topPosition = rect[top] + scroll.y; - arrowTop = linkDimensions.h/2 - arrowWidth; - } else if (halfBottomExceed) { - topPosition = rect[top] + scroll.y - elementDimensions.h + linkDimensions.h; - arrowTop = elementDimensions.h - linkDimensions.h/2 - arrowWidth; + } + }, options); + } + + var transitionEndEvent = 'webkitTransition' in document.body.style ? 'webkitTransitionEnd' : 'transitionend'; + + var supportTransition = 'webkitTransition' in document.body.style || 'transition' in document.body.style; + + var transitionDuration = 'webkitTransition' in document.body.style ? 'webkitTransitionDuration' : 'transitionDuration'; + + function getElementTransitionDuration (element) { + var duration = supportTransition ? window.getComputedStyle(element)[transitionDuration] : 0; + duration = parseFloat(duration); + duration = typeof duration === 'number' && !isNaN(duration) ? duration * 1000 : 0; + return duration; + } + + function emulateTransitionEnd (element,handler){ + var called = 0, duration = getElementTransitionDuration(element); + duration ? one(element, transitionEndEvent, function(e){ !called && handler(e), called = 1; }) + : setTimeout(function() { !called && handler(), called = 1; }, 17); + } + + function queryElement (selector, parent) { + var lookUp = parent && parent instanceof Element ? parent : document; + return selector instanceof Element ? selector : lookUp.querySelector(selector); + } + + function tryWrapper (fn,origin){ + try{ fn(); } + catch(e){ + console.error((origin + ": " + e)); + } + } + + function bootstrapCustomEvent (eventName, componentName, related) { + var OriginalCustomEvent = new CustomEvent( eventName + '.bs.' + componentName, {cancelable: true}); + OriginalCustomEvent.relatedTarget = related; + return OriginalCustomEvent; + } + function dispatchCustomEvent (customEvent){ + this && this.dispatchEvent(customEvent); + } + + function Alert(element) { + var self = this, + alert, + closeCustomEvent = bootstrapCustomEvent('close','alert'), + closedCustomEvent = bootstrapCustomEvent('closed','alert'); + function triggerHandler() { + hasClass(alert,'fade') ? emulateTransitionEnd(alert,transitionEndHandler) : transitionEndHandler(); + } + function clickHandler(e) { + alert = e && e.target.closest(".alert"); + element = queryElement('[data-dismiss="alert"]',alert); + element && alert && (element === e.target || element.contains(e.target)) && self.close(); + } + function transitionEndHandler() { + off(element, 'click', clickHandler); + alert.parentNode.removeChild(alert); + dispatchCustomEvent.call(alert,closedCustomEvent); + } + self.close = function () { + if ( alert && element && hasClass(alert,'show') ) { + dispatchCustomEvent.call(alert,closeCustomEvent); + if ( closeCustomEvent.defaultPrevented ) { return; } + self.dispose(); + removeClass(alert,'show'); + triggerHandler(); + } + }; + self.dispose = function () { + off(element, 'click', clickHandler); + delete element.Alert; + }; + tryWrapper(function (){ + element = queryElement(element); + alert = element.closest('.alert'); + element.Alert && element.Alert.dispose(); + if ( !element.Alert ) { + on(element, 'click', clickHandler); + } + self.element = element; + element.Alert = self; + },"BSN.Alert"); + } + + function addClass(element,classNAME) { + element.classList.add(classNAME); + } + + function Button(element) { + var self = this, labels, + changeCustomEvent = bootstrapCustomEvent('change', 'button'); + function toggle(e) { + var input, + label = e.target.tagName === 'LABEL' ? e.target + : e.target.closest('LABEL') ? e.target.closest('LABEL') : null; + input = label && label.getElementsByTagName('INPUT')[0]; + if ( !input ) { return; } + dispatchCustomEvent.call(input, changeCustomEvent); + dispatchCustomEvent.call(element, changeCustomEvent); + if ( input.type === 'checkbox' ) { + if ( changeCustomEvent.defaultPrevented ) { return; } + if ( !input.checked ) { + addClass(label,'active'); + input.getAttribute('checked'); + input.setAttribute('checked','checked'); + input.checked = true; } else { - topPosition = rect[top] + scroll.y - elementDimensions.h/2 + linkDimensions.h/2; - arrowTop = elementDimensions.h/2 - (isPopover ? arrowHeight*0.9 : arrowHeight/2); + removeClass(label,'active'); + input.getAttribute('checked'); + input.removeAttribute('checked'); + input.checked = false; } - } else if ( position === top || position === bottom ) { // primary|vertical positions - if ( position === top) { // TOP - topPosition = rect[top] + scroll.y - elementDimensions.h - ( isPopover ? arrowHeight : 0 ); - } else { // BOTTOM - topPosition = rect[top] + scroll.y + linkDimensions.h; + if (!element.toggled) { + element.toggled = true; } - // adjust left | right and also the arrow - if (halfLeftExceed) { - leftPosition = 0; - arrowLeft = rect[left] + linkDimensions.w/2 - arrowWidth; - } else if (halfRightExceed) { - leftPosition = windowWidth - elementDimensions.w*1.01; - arrowLeft = elementDimensions.w - ( windowWidth - rect[left] ) + linkDimensions.w/2 - arrowWidth/2; - } else { - leftPosition = rect[left] + scroll.x - elementDimensions.w/2 + linkDimensions.w/2; - arrowLeft = elementDimensions.w/2 - ( isPopover ? arrowWidth : arrowWidth/2 ); + } + if ( input.type === 'radio' && !element.toggled ) { + if ( changeCustomEvent.defaultPrevented ) { return; } + if ( !input.checked || (e.screenX === 0 && e.screenY == 0) ) { + addClass(label,'active'); + addClass(label,'focus'); + input.setAttribute('checked','checked'); + input.checked = true; + element.toggled = true; + Array.from(labels).map(function (otherLabel){ + var otherInput = otherLabel.getElementsByTagName('INPUT')[0]; + if ( otherLabel !== label && hasClass(otherLabel,'active') ) { + dispatchCustomEvent.call(otherInput, changeCustomEvent); + removeClass(otherLabel,'active'); + otherInput.removeAttribute('checked'); + otherInput.checked = false; + } + }); } } - - // apply style to tooltip/popover and its arrow - element[style][top] = topPosition + 'px'; - element[style][left] = leftPosition + 'px'; - - arrowTop && (arrow[style][top] = arrowTop + 'px'); - arrowLeft && (arrow[style][left] = arrowLeft + 'px'); - }; - - BSN.version = '2.0.27'; - - /* Native Javascript for Bootstrap 4 | Alert - -------------------------------------------*/ - - // ALERT DEFINITION - // ================ - var Alert = function( element ) { - - // initialization element - element = queryElement(element); - - // bind, target alert, duration and stuff - var self = this, component = 'alert', - alert = getClosest(element,'.'+component), - triggerHandler = function(){ hasClass(alert,'fade') ? emulateTransitionEnd(alert,transitionEndHandler) : transitionEndHandler(); }, - // handlers - clickHandler = function(e){ - alert = getClosest(e[target],'.'+component); - element = queryElement('['+dataDismiss+'="'+component+'"]',alert); - element && alert && (element === e[target] || element[contains](e[target])) && self.close(); - }, - transitionEndHandler = function(){ - bootstrapCustomEvent.call(alert, closedEvent, component); - off(element, clickEvent, clickHandler); // detach it's listener - alert[parentNode].removeChild(alert); - }; - - // public method - this.close = function() { - if ( alert && element && hasClass(alert,showClass) ) { - bootstrapCustomEvent.call(alert, closeEvent, component); - removeClass(alert,showClass); - alert && triggerHandler(); + setTimeout( function () { element.toggled = false; }, 50 ); + } + function keyHandler(e) { + var key = e.which || e.keyCode; + key === 32 && e.target === document.activeElement && toggle(e); + } + function preventScroll(e) { + var key = e.which || e.keyCode; + key === 32 && e.preventDefault(); + } + function focusToggle(e) { + var action = e.type === 'focusin' ? addClass : removeClass; + if (e.target.tagName === 'INPUT' ) { + action(e.target.closest('.btn'),'focus'); } - }; - - // init - if ( !(stringAlert in element ) ) { // prevent adding event handlers twice - on(element, clickEvent, clickHandler); } - element[stringAlert] = self; - }; - - // ALERT DATA API - // ============== - supports[push]([stringAlert, Alert, '['+dataDismiss+'="alert"]']); - - - /* Native Javascript for Bootstrap 4 | Button - ---------------------------------------------*/ - - // BUTTON DEFINITION - // =================== - var Button = function( element ) { - - // initialization element - element = queryElement(element); - - // constant - var toggled = false, // toggled makes sure to prevent triggering twice the change.bs.button events - - // strings - component = 'button', - checked = 'checked', - LABEL = 'LABEL', - INPUT = 'INPUT', - - // private methods - keyHandler = function(e){ - var key = e.which || e.keyCode; - key === 32 && e[target] === DOC.activeElement && toggle(e); - }, - preventScroll = function(e){ - var key = e.which || e.keyCode; - key === 32 && e[preventDefault](); - }, - toggle = function(e) { - var label = e[target].tagName === LABEL ? e[target] : e[target][parentNode].tagName === LABEL ? e[target][parentNode] : null; // the .btn label - - if ( !label ) return; //react if a label or its immediate child is clicked - - var labels = getElementsByClassName(label[parentNode],'btn'), // all the button group buttons - input = label[getElementsByTagName](INPUT)[0]; - - if ( !input ) return; // return if no input found - - // manage the dom manipulation - if ( input.type === 'checkbox' ) { //checkboxes - if ( !input[checked] ) { - addClass(label,active); - input[getAttribute](checked); - input[setAttribute](checked,checked); - input[checked] = true; - } else { - removeClass(label,active); - input[getAttribute](checked); - input.removeAttribute(checked); - input[checked] = false; - } - - if (!toggled) { // prevent triggering the event twice - toggled = true; - bootstrapCustomEvent.call(input, changeEvent, component); //trigger the change for the input - bootstrapCustomEvent.call(element, changeEvent, component); //trigger the change for the btn-group - } - } - - if ( input.type === 'radio' && !toggled ) { // radio buttons - // don't trigger if already active (the OR condition is a hack to check if the buttons were selected with key press and NOT mouse click) - if ( !input[checked] || (e.screenX === 0 && e.screenY == 0) ) { - addClass(label,active); - addClass(label,focusEvent); - input[setAttribute](checked,checked); - input[checked] = true; - bootstrapCustomEvent.call(input, changeEvent, component); //trigger the change for the input - bootstrapCustomEvent.call(element, changeEvent, component); //trigger the change for the btn-group - - toggled = true; - for (var i = 0, ll = labels[length]; i<ll; i++) { - var otherLabel = labels[i], otherInput = otherLabel[getElementsByTagName](INPUT)[0]; - if ( otherLabel !== label && hasClass(otherLabel,active) ) { - removeClass(otherLabel,active); - otherInput.removeAttribute(checked); - otherInput[checked] = false; - bootstrapCustomEvent.call(otherInput, changeEvent, component); // trigger the change - } - } - } + function toggleEvents(action) { + action( element, 'click', toggle ); + action( element, 'keyup', keyHandler ), action( element, 'keydown', preventScroll ); + action( element, 'focusin', focusToggle), action( element, 'focusout', focusToggle); + } + self.dispose = function () { + toggleEvents(off); + delete element.Button; + }; + tryWrapper(function (){ + element = queryElement(element); + element.Button && element.Button.dispose(); + labels = element.getElementsByClassName('btn'); + if (!labels.length) { return; } + if ( !element.Button ) { + toggleEvents(on); + } + element.toggled = false; + element.Button = self; + Array.from(labels).map(function (btn){ + !hasClass(btn,'active') + && queryElement('input:checked',btn) + && addClass(btn,'active'); + hasClass(btn,'active') + && !queryElement('input:checked',btn) + && removeClass(btn,'active'); + }); + },"BSN.Button"); + } + + var touchEvents = { start: 'touchstart', end: 'touchend', move:'touchmove', cancel:'touchcancel' }; + + var mouseHoverEvents = ('onmouseleave' in document) ? [ 'mouseenter', 'mouseleave'] : [ 'mouseover', 'mouseout' ]; + + var supportPassive = (function () { + var result = false; + try { + var opts = Object.defineProperty({}, 'passive', { + get: function() { + result = true; } - setTimeout( function() { toggled = false; }, 50 ); - }, - focusHandler = function(e) { - addClass(e[target][parentNode],focusEvent); - }, - blurHandler = function(e) { - removeClass(e[target][parentNode],focusEvent); - }; - - // init - if ( !( stringButton in element ) ) { // prevent adding event handlers twice - on( element, clickEvent, toggle ); - on( element, keyupEvent, keyHandler ), on( element, keydownEvent, preventScroll ); - - var allBtns = getElementsByClassName(element, 'btn'); - for (var i=0; i<allBtns.length; i++) { - var input = allBtns[i][getElementsByTagName](INPUT)[0]; - on( input, focusEvent, focusHandler), on( input, 'blur', blurHandler); - } - } - - // activate items on load - var labelsToACtivate = getElementsByClassName(element, 'btn'), lbll = labelsToACtivate[length]; - for (var i=0; i<lbll; i++) { - !hasClass(labelsToACtivate[i],active) && queryElement('input:checked',labelsToACtivate[i]) - && addClass(labelsToACtivate[i],active); - } - element[stringButton] = this; - }; - - // BUTTON DATA API - // ================= - supports[push]( [ stringButton, Button, '['+dataToggle+'="buttons"]' ] ); - - - /* Native Javascript for Bootstrap 4 | Carousel - ----------------------------------------------*/ - - // CAROUSEL DEFINITION - // =================== - var Carousel = function( element, options ) { - - // initialization element - element = queryElement( element ); - - // set options + }); + one(document, 'DOMContentLoaded', function (){}, opts); + } catch (e) {} + return result; + })(); + + var passiveHandler = supportPassive ? { passive: true } : false; + + function isElementInScrollRange(element) { + var bcr = element.getBoundingClientRect(), + viewportHeight = window.innerHeight || document.documentElement.clientHeight; + return bcr.top <= viewportHeight && bcr.bottom >= 0; + } + + function Carousel (element,options) { options = options || {}; - - // DATA API - var intervalAttribute = element[getAttribute](dataInterval), - intervalOption = options[interval], - intervalData = intervalAttribute === 'false' ? 0 : parseInt(intervalAttribute), - pauseData = element[getAttribute](dataPause) === hoverEvent || false, - keyboardData = element[getAttribute](dataKeyboard) === 'true' || false, - - // strings - component = 'carousel', - paused = 'paused', - direction = 'direction', - carouselItem = 'carousel-item', - dataSlideTo = 'data-slide-to'; - - this[keyboard] = options[keyboard] === true || keyboardData; - this[pause] = (options[pause] === hoverEvent || pauseData) ? hoverEvent : false; // false / hover - - this[interval] = typeof intervalOption === 'number' ? intervalOption - : intervalOption === false || intervalData === 0 || intervalData === false ? 0 - : isNaN(intervalData) ? 5000 // bootstrap carousel default interval - : intervalData; - - // bind, event targets - var self = this, index = element.index = 0, timer = element.timer = 0, - isSliding = false, // isSliding prevents click event handlers when animation is running - isTouch = false, startXPosition = null, currentXPosition = null, endXPosition = null, // touch and event coordinates - slides = getElementsByClassName(element,carouselItem), total = slides[length], - slideDirection = this[direction] = left, - leftArrow = getElementsByClassName(element,component+'-control-prev')[0], - rightArrow = getElementsByClassName(element,component+'-control-next')[0], - indicator = queryElement( '.'+component+'-indicators', element ), - indicators = indicator && indicator[getElementsByTagName]( "LI" ) || []; - - // invalidate when not enough items - if (total < 2) { return; } - - // handlers - var pauseHandler = function () { - if ( self[interval] !==false && !hasClass(element,paused) ) { - addClass(element,paused); - !isSliding && ( clearInterval(timer), timer = null ); - } - }, - resumeHandler = function() { - if ( self[interval] !== false && hasClass(element,paused) ) { - removeClass(element,paused); - !isSliding && ( clearInterval(timer), timer = null ); - !isSliding && self.cycle(); - } - }, - indicatorHandler = function(e) { - e[preventDefault](); - if (isSliding) return; - - var eventTarget = e[target]; // event target | the current active item - - if ( eventTarget && !hasClass(eventTarget,active) && eventTarget[getAttribute](dataSlideTo) ) { - index = parseInt( eventTarget[getAttribute](dataSlideTo), 10 ); - } else { return false; } - - self.slideTo( index ); //Do the slide - }, - controlsHandler = function (e) { - e[preventDefault](); - if (isSliding) return; - - var eventTarget = e.currentTarget || e.srcElement; - - if ( eventTarget === rightArrow ) { - index++; - } else if ( eventTarget === leftArrow ) { - index--; - } - - self.slideTo( index ); //Do the slide - }, - keyHandler = function (e) { - if (isSliding) return; - switch (e.which) { - case 39: - index++; - break; - case 37: - index--; - break; - default: return; - } - self.slideTo( index ); //Do the slide - }, - // touch events - toggleTouchEvents = function(toggle){ - toggle( element, touchEvents.move, touchMoveHandler, passiveHandler ); - toggle( element, touchEvents.end, touchEndHandler, passiveHandler ); - }, - touchDownHandler = function(e) { - if ( isTouch ) { return; } - - startXPosition = parseInt(e.touches[0].pageX); - - if ( element.contains(e[target]) ) { - isTouch = true; - toggleTouchEvents(on); - } - }, - touchMoveHandler = function(e) { - if ( !isTouch ) { e.preventDefault(); return; } - - currentXPosition = parseInt(e.touches[0].pageX); - - //cancel touch if more than one touches detected - if ( e.type === 'touchmove' && e.touches[length] > 1 ) { - e.preventDefault(); + var self = this, + vars, ops = {}, + slideCustomEvent, slidCustomEvent, + slides, leftArrow, rightArrow, indicator, indicators; + function pauseHandler() { + if ( ops.interval !==false && !hasClass(element,'paused') ) { + addClass(element,'paused'); + !vars.isSliding && ( clearInterval(vars.timer), vars.timer = null ); + } + } + function resumeHandler() { + if ( ops.interval !== false && hasClass(element,'paused') ) { + removeClass(element,'paused'); + !vars.isSliding && ( clearInterval(vars.timer), vars.timer = null ); + !vars.isSliding && self.cycle(); + } + } + function indicatorHandler(e) { + e.preventDefault(); + if (vars.isSliding) { return; } + var eventTarget = e.target; + if ( eventTarget && !hasClass(eventTarget,'active') && eventTarget.getAttribute('data-slide-to') ) { + vars.index = parseInt( eventTarget.getAttribute('data-slide-to'), 10 ); + } else { return false; } + self.slideTo( vars.index ); + } + function controlsHandler(e) { + e.preventDefault(); + if (vars.isSliding) { return; } + var eventTarget = e.currentTarget || e.srcElement; + if ( eventTarget === rightArrow ) { + vars.index++; + } else if ( eventTarget === leftArrow ) { + vars.index--; + } + self.slideTo( vars.index ); + } + function keyHandler(ref) { + var which = ref.which; + if (vars.isSliding) { return; } + switch (which) { + case 39: + vars.index++; + break; + case 37: + vars.index--; + break; + default: return; + } + self.slideTo( vars.index ); + } + function toggleEvents(action) { + if ( ops.pause && ops.interval ) { + action( element, mouseHoverEvents[0], pauseHandler ); + action( element, mouseHoverEvents[1], resumeHandler ); + action( element, touchEvents.start, pauseHandler, passiveHandler ); + action( element, touchEvents.end, resumeHandler, passiveHandler ); + } + slides.length > 1 && action( element, touchEvents.start, touchDownHandler, passiveHandler ); + rightArrow && action( rightArrow, 'click', controlsHandler ); + leftArrow && action( leftArrow, 'click', controlsHandler ); + indicator && action( indicator, 'click', indicatorHandler ); + ops.keyboard && action( window, 'keydown', keyHandler ); + } + function toggleTouchEvents(action) { + action( element, touchEvents.move, touchMoveHandler, passiveHandler ); + action( element, touchEvents.end, touchEndHandler, passiveHandler ); + } + function touchDownHandler(e) { + if ( vars.isTouch ) { return; } + vars.touchPosition.startX = e.changedTouches[0].pageX; + if ( element.contains(e.target) ) { + vars.isTouch = true; + toggleTouchEvents(on); + } + } + function touchMoveHandler(e) { + if ( !vars.isTouch ) { e.preventDefault(); return; } + vars.touchPosition.currentX = e.changedTouches[0].pageX; + if ( e.type === 'touchmove' && e.changedTouches.length > 1 ) { + e.preventDefault(); + return false; + } + } + function touchEndHandler (e) { + if ( !vars.isTouch || vars.isSliding ) { return } + vars.touchPosition.endX = vars.touchPosition.currentX || e.changedTouches[0].pageX; + if ( vars.isTouch ) { + if ( (!element.contains(e.target) || !element.contains(e.relatedTarget) ) + && Math.abs(vars.touchPosition.startX - vars.touchPosition.endX) < 75 ) { return false; + } else { + if ( vars.touchPosition.currentX < vars.touchPosition.startX ) { + vars.index++; + } else if ( vars.touchPosition.currentX > vars.touchPosition.startX ) { + vars.index--; + } + vars.isTouch = false; + self.slideTo(vars.index); } - }, - touchEndHandler = function(e) { - if ( !isTouch || isSliding ) { return } - - endXPosition = currentXPosition || parseInt( e.touches[0].pageX ); - - if ( isTouch ) { - if ( (!element.contains(e[target]) || !element.contains(e.relatedTarget) ) && Math.abs(startXPosition - endXPosition) < 75 ) { - return false; - } else { - if ( currentXPosition < startXPosition ) { - index++; - } else if ( currentXPosition > startXPosition ) { - index--; + toggleTouchEvents(off); + } + } + function setActivePage(pageIndex) { + Array.from(indicators).map(function (x){removeClass(x,'active');}); + indicators[pageIndex] && addClass(indicators[pageIndex], 'active'); + } + function transitionEndHandler(e){ + if (vars.touchPosition){ + var next = vars.index, + timeout = e && e.target !== slides[next] ? e.elapsedTime*1000+100 : 20, + activeItem = self.getActiveIndex(), + orientation = vars.direction === 'left' ? 'next' : 'prev'; + vars.isSliding && setTimeout(function () { + if (vars.touchPosition){ + vars.isSliding = false; + addClass(slides[next],'active'); + removeClass(slides[activeItem],'active'); + removeClass(slides[next],("carousel-item-" + orientation)); + removeClass(slides[next],("carousel-item-" + (vars.direction))); + removeClass(slides[activeItem],("carousel-item-" + (vars.direction))); + dispatchCustomEvent.call(element, slidCustomEvent); + if ( !document.hidden && ops.interval && !hasClass(element,'paused') ) { + self.cycle(); } - isTouch = false; - self.slideTo(index); } - toggleTouchEvents(off); - } - }, - - // private methods - isElementInScrollRange = function () { - var rect = element[getBoundingClientRect](), - viewportHeight = globalObject[innerHeight] || HTML[clientHeight] - return rect[top] <= viewportHeight && rect[bottom] >= 0; // bottom && top - }, - setActivePage = function( pageIndex ) { //indicators - for ( var i = 0, icl = indicators[length]; i < icl; i++ ) { - removeClass(indicators[i],active); - } - if (indicators[pageIndex]) addClass(indicators[pageIndex], active); - }; - - - // public methods - this.cycle = function() { - if (timer) { - clearInterval(timer); - timer = null; - } - - timer = setInterval(function() { - isElementInScrollRange() && (index++, self.slideTo( index ) ); - }, this[interval]); + }, timeout); + } + } + self.cycle = function () { + if (vars.timer) { + clearInterval(vars.timer); + vars.timer = null; + } + vars.timer = setInterval(function () { + var idx = vars.index || self.getActiveIndex(); + isElementInScrollRange(element) && (idx++, self.slideTo( idx ) ); + }, ops.interval); }; - this.slideTo = function( next ) { - if (isSliding) return; // when controled via methods, make sure to check again - - var activeItem = this.getActiveIndex(), // the current active - orientation; - - // first return if we're on the same item #227 + self.slideTo = function (next) { + if (vars.isSliding) { return; } + var activeItem = self.getActiveIndex(), orientation; if ( activeItem === next ) { return; - // or determine slideDirection - } else if ( (activeItem < next ) || (activeItem === 0 && next === total -1 ) ) { - slideDirection = self[direction] = left; // next - } else if ( (activeItem > next) || (activeItem === total - 1 && next === 0 ) ) { - slideDirection = self[direction] = right; // prev - } - - // find the right next index - if ( next < 0 ) { next = total - 1; } - else if ( next >= total ){ next = 0; } - - // update index - index = next; - - orientation = slideDirection === left ? 'next' : 'prev'; //determine type - bootstrapCustomEvent.call(element, slideEvent, component, slides[next]); // here we go with the slide - - isSliding = true; - clearInterval(timer); - timer = null; + } else if ( (activeItem < next ) || (activeItem === 0 && next === slides.length -1 ) ) { + vars.direction = 'left'; + } else if ( (activeItem > next) || (activeItem === slides.length - 1 && next === 0 ) ) { + vars.direction = 'right'; + } + if ( next < 0 ) { next = slides.length - 1; } + else if ( next >= slides.length ){ next = 0; } + orientation = vars.direction === 'left' ? 'next' : 'prev'; + slideCustomEvent = bootstrapCustomEvent('slide', 'carousel', slides[next]); + slidCustomEvent = bootstrapCustomEvent('slid', 'carousel', slides[next]); + dispatchCustomEvent.call(element, slideCustomEvent); + if (slideCustomEvent.defaultPrevented) { return; } + vars.index = next; + vars.isSliding = true; + clearInterval(vars.timer); + vars.timer = null; setActivePage( next ); - - if ( supportTransitions && hasClass(element,'slide') ) { - - addClass(slides[next],carouselItem +'-'+ orientation); - slides[next][offsetWidth]; - addClass(slides[next],carouselItem +'-'+ slideDirection); - addClass(slides[activeItem],carouselItem +'-'+ slideDirection); - - emulateTransitionEnd(slides[next], function(e) { - var timeout = e && e[target] !== slides[next] ? e.elapsedTime*1000+100 : 20; - - isSliding && setTimeout(function(){ - isSliding = false; - - addClass(slides[next],active); - removeClass(slides[activeItem],active); - - removeClass(slides[next],carouselItem +'-'+ orientation); - removeClass(slides[next],carouselItem +'-'+ slideDirection); - removeClass(slides[activeItem],carouselItem +'-'+ slideDirection); - - bootstrapCustomEvent.call(element, slidEvent, component, slides[next]); - - if ( !DOC.hidden && self[interval] && !hasClass(element,paused) ) { - self.cycle(); - } - }, timeout); - }); - + if ( getElementTransitionDuration(slides[next]) && hasClass(element,'slide') ) { + addClass(slides[next],("carousel-item-" + orientation)); + slides[next].offsetWidth; + addClass(slides[next],("carousel-item-" + (vars.direction))); + addClass(slides[activeItem],("carousel-item-" + (vars.direction))); + emulateTransitionEnd(slides[next], transitionEndHandler); } else { - addClass(slides[next],active); - slides[next][offsetWidth]; - removeClass(slides[activeItem],active); - setTimeout(function() { - isSliding = false; - if ( self[interval] && !hasClass(element,paused) ) { + addClass(slides[next],'active'); + slides[next].offsetWidth; + removeClass(slides[activeItem],'active'); + setTimeout(function () { + vars.isSliding = false; + if ( ops.interval && element && !hasClass(element,'paused') ) { self.cycle(); } - bootstrapCustomEvent.call(element, slidEvent, component, slides[next]); + dispatchCustomEvent.call(element, slidCustomEvent); }, 100 ); } }; - this.getActiveIndex = function () { - return slides[indexOf](getElementsByClassName(element,carouselItem+' active')[0]) || 0; + self.getActiveIndex = function () { return Array.from(slides).indexOf(element.getElementsByClassName('carousel-item active')[0]) || 0; }; + self.dispose = function () { + var itemClasses = ['left','right','prev','next']; + Array.from(slides).map(function (slide,idx) { + if (hasClass(slide,'active')){ + setActivePage( idx ); + } + itemClasses.map(function (cls) { return removeClass(slide,("carousel-item-" + cls)); }); + }); + clearInterval(vars.timer); + toggleEvents(off); + vars = {}; + delete element.Carousel; }; - - // init - if ( !(stringCarousel in element ) ) { // prevent adding event handlers twice - - if ( self[pause] && self[interval] ) { - on( element, mouseHover[0], pauseHandler ); - on( element, mouseHover[1], resumeHandler ); - on( element, touchEvents.start, pauseHandler, passiveHandler ); - on( element, touchEvents.end, resumeHandler, passiveHandler ); - } - - slides[length] > 1 && on( element, touchEvents.start, touchDownHandler, passiveHandler ); - - rightArrow && on( rightArrow, clickEvent, controlsHandler ); - leftArrow && on( leftArrow, clickEvent, controlsHandler ); - - indicator && on( indicator, clickEvent, indicatorHandler ); - self[keyboard] && on( globalObject, keydownEvent, keyHandler ); - - } - if (self.getActiveIndex()<0) { - slides[length] && addClass(slides[0],active); - indicators[length] && setActivePage(0); - } - - if ( self[interval] ){ self.cycle(); } - element[stringCarousel] = self; - }; - - // CAROUSEL DATA API - // ================= - supports[push]( [ stringCarousel, Carousel, '['+dataRide+'="carousel"]' ] ); - - - /* Native Javascript for Bootstrap 4 | Collapse - -----------------------------------------------*/ - - // COLLAPSE DEFINITION - // =================== - var Collapse = function( element, options ) { - - // initialization element - element = queryElement(element); - - // set options + vars = {}; + vars.direction = 'left'; + vars.index = 0; + vars.timer = null; + vars.isSliding = false; + vars.isTouch = false; + vars.touchPosition = { + startX : 0, + currentX : 0, + endX : 0 + }; + tryWrapper(function (){ + element = queryElement( element ); + element.Carousel && element.Carousel.dispose(); + var + intervalAttribute = element.getAttribute('data-interval'), + intervalOption = options.interval, + intervalData = intervalAttribute === 'false' ? 0 : parseInt(intervalAttribute), + pauseData = element.getAttribute('data-pause') === 'hover' || false, + keyboardData = element.getAttribute('data-keyboard') === 'true' || false; + slides = element.getElementsByClassName('carousel-item'); + leftArrow = element.getElementsByClassName('carousel-control-prev')[0]; + rightArrow = element.getElementsByClassName('carousel-control-next')[0]; + indicator = element.getElementsByClassName('carousel-indicators')[0]; + indicators = indicator && indicator.getElementsByTagName( "LI" ) || []; + ops.keyboard = options.keyboard === true || keyboardData; + ops.pause = (options.pause === 'hover' || pauseData) ? 'hover' : false; + ops.interval = typeof intervalOption === 'number' ? intervalOption + : intervalOption === false || intervalData === 0 || intervalData === false ? 0 + : isNaN(intervalData) ? 5000 + : intervalData; + if (slides.length < 2) { return; } + if ( !element.Carousel ) { + toggleEvents(on); + } + if (self.getActiveIndex()<0) { + slides.length && addClass(slides[0],'active'); + indicators.length && setActivePage(0); + } + if ( ops.interval ){ self.cycle(); } + element.Carousel = self; + },"BSN.Carousel"); + } + + function Collapse(element,options) { options = options || {}; - - // event targets and constants - var accordion = null, collapse = null, self = this, - accordionData = element[getAttribute]('data-parent'), - activeCollapse, activeElement, - - // component strings - component = 'collapse', - collapsed = 'collapsed', - isAnimating = 'isAnimating', - - // private methods - openAction = function(collapseElement,toggle) { - bootstrapCustomEvent.call(collapseElement, showEvent, component); - collapseElement[isAnimating] = true; - addClass(collapseElement,collapsing); - removeClass(collapseElement,component); - collapseElement[style][height] = collapseElement[scrollHeight] + 'px'; - - emulateTransitionEnd(collapseElement, function() { - collapseElement[isAnimating] = false; - collapseElement[setAttribute](ariaExpanded,'true'); - toggle[setAttribute](ariaExpanded,'true'); - removeClass(collapseElement,collapsing); - addClass(collapseElement, component); - addClass(collapseElement,showClass); - collapseElement[style][height] = ''; - bootstrapCustomEvent.call(collapseElement, shownEvent, component); - }); - }, - closeAction = function(collapseElement,toggle) { - bootstrapCustomEvent.call(collapseElement, hideEvent, component); - collapseElement[isAnimating] = true; - collapseElement[style][height] = collapseElement[scrollHeight] + 'px'; // set height first - removeClass(collapseElement,component); - removeClass(collapseElement,showClass); - addClass(collapseElement,collapsing); - collapseElement[offsetWidth]; // force reflow to enable transition - collapseElement[style][height] = '0px'; - - emulateTransitionEnd(collapseElement, function() { - collapseElement[isAnimating] = false; - collapseElement[setAttribute](ariaExpanded,'false'); - toggle[setAttribute](ariaExpanded,'false'); - removeClass(collapseElement,collapsing); - addClass(collapseElement,component); - collapseElement[style][height] = ''; - bootstrapCustomEvent.call(collapseElement, hiddenEvent, component); - }); - }, - getTarget = function() { - var href = element.href && element[getAttribute]('href'), - parent = element[getAttribute](dataTarget), - id = href || ( parent && parent.charAt(0) === '#' ) && parent; - return id && queryElement(id); - }; - - // public methods - this.toggle = function(e) { - e[preventDefault](); - if (!hasClass(collapse,showClass)) { self.show(); } - else { self.hide(); } + var self = this; + var accordion = null, + collapse = null, + activeCollapse, + activeElement, + showCustomEvent, + shownCustomEvent, + hideCustomEvent, + hiddenCustomEvent; + function openAction(collapseElement, toggle) { + dispatchCustomEvent.call(collapseElement, showCustomEvent); + if ( showCustomEvent.defaultPrevented ) { return; } + collapseElement.isAnimating = true; + addClass(collapseElement,'collapsing'); + removeClass(collapseElement,'collapse'); + collapseElement.style.height = (collapseElement.scrollHeight) + "px"; + emulateTransitionEnd(collapseElement, function () { + collapseElement.isAnimating = false; + collapseElement.setAttribute('aria-expanded','true'); + toggle.setAttribute('aria-expanded','true'); + removeClass(collapseElement,'collapsing'); + addClass(collapseElement, 'collapse'); + addClass(collapseElement,'show'); + collapseElement.style.height = ''; + dispatchCustomEvent.call(collapseElement, shownCustomEvent); + }); + } + function closeAction(collapseElement, toggle) { + dispatchCustomEvent.call(collapseElement, hideCustomEvent); + if ( hideCustomEvent.defaultPrevented ) { return; } + collapseElement.isAnimating = true; + collapseElement.style.height = (collapseElement.scrollHeight) + "px"; + removeClass(collapseElement,'collapse'); + removeClass(collapseElement,'show'); + addClass(collapseElement,'collapsing'); + collapseElement.offsetWidth; + collapseElement.style.height = '0px'; + emulateTransitionEnd(collapseElement, function () { + collapseElement.isAnimating = false; + collapseElement.setAttribute('aria-expanded','false'); + toggle.setAttribute('aria-expanded','false'); + removeClass(collapseElement,'collapsing'); + addClass(collapseElement,'collapse'); + collapseElement.style.height = ''; + dispatchCustomEvent.call(collapseElement, hiddenCustomEvent); + }); + } + self.toggle = function (e) { + if (e && e.target.tagName === 'A' || element.tagName === 'A') {e.preventDefault();} + if (element.contains(e.target) || e.target === element) { + if (!hasClass(collapse,'show')) { self.show(); } + else { self.hide(); } + } }; - this.hide = function() { - if ( collapse[isAnimating] ) return; + self.hide = function () { + if ( collapse.isAnimating ) { return; } closeAction(collapse,element); - addClass(element,collapsed); + addClass(element,'collapsed'); }; - this.show = function() { + self.show = function () { if ( accordion ) { - activeCollapse = queryElement('.'+component+'.'+showClass,accordion); - activeElement = activeCollapse && (queryElement('['+dataTarget+'="#'+activeCollapse.id+'"]',accordion) - || queryElement('[href="#'+activeCollapse.id+'"]',accordion) ); + activeCollapse = accordion.getElementsByClassName("collapse show")[0]; + activeElement = activeCollapse && (queryElement(("[data-target=\"#" + (activeCollapse.id) + "\"]"),accordion) + || queryElement(("[href=\"#" + (activeCollapse.id) + "\"]"),accordion) ); } - - if ( !collapse[isAnimating] || activeCollapse && !activeCollapse[isAnimating] ) { + if ( !collapse.isAnimating ) { if ( activeElement && activeCollapse !== collapse ) { - closeAction(activeCollapse,activeElement); - addClass(activeElement,collapsed); + closeAction(activeCollapse,activeElement); + addClass(activeElement,'collapsed'); } openAction(collapse,element); - removeClass(element,collapsed); + removeClass(element,'collapsed'); } }; - - // init - if ( !(stringCollapse in element ) ) { // prevent adding event handlers twice - on(element, clickEvent, self.toggle); - } - collapse = getTarget(); - collapse[isAnimating] = false; // when true it will prevent click handlers - accordion = queryElement(options.parent) || accordionData && getClosest(element, accordionData); - element[stringCollapse] = self; - }; - - // COLLAPSE DATA API - // ================= - supports[push]( [ stringCollapse, Collapse, '['+dataToggle+'="collapse"]' ] ); - - - /* Native Javascript for Bootstrap 4 | Dropdown - ----------------------------------------------*/ - - // DROPDOWN DEFINITION - // =================== - var Dropdown = function( element, option ) { - - // initialization element - element = queryElement(element); - - // set option - this.persist = option === true || element[getAttribute]('data-persist') === 'true' || false; - - // constants, event targets, strings - var self = this, children = 'children', - parent = element[parentNode], - component = 'dropdown', open = 'open', - relatedTarget = null, - menu = queryElement('.dropdown-menu', parent), - menuItems = (function(){ - var set = menu[children], newSet = []; - for ( var i=0; i<set[length]; i++ ){ - set[i][children][length] && (set[i][children][0].tagName === 'A' && newSet[push](set[i][children][0])); - set[i].tagName === 'A' && newSet[push](set[i]); - } - return newSet; - })(), - - // preventDefault on empty anchor links - preventEmptyAnchor = function(anchor){ - (anchor.href && anchor.href.slice(-1) === '#' || anchor[parentNode] && anchor[parentNode].href - && anchor[parentNode].href.slice(-1) === '#') && this[preventDefault](); - }, - - // toggle dismissible events - toggleDismiss = function(){ - var type = element[open] ? on : off; - type(DOC, clickEvent, dismissHandler); - type(DOC, keydownEvent, preventScroll); - type(DOC, keyupEvent, keyHandler); - type(DOC, focusEvent, dismissHandler, true); - }, - - // handlers - dismissHandler = function(e) { - var eventTarget = e[target], hasData = eventTarget && (eventTarget[getAttribute](dataToggle) - || eventTarget[parentNode] && getAttribute in eventTarget[parentNode] - && eventTarget[parentNode][getAttribute](dataToggle)); - if ( e.type === focusEvent && (eventTarget === element || eventTarget === menu || menu[contains](eventTarget) ) ) { - return; - } - if ( (eventTarget === menu || menu[contains](eventTarget)) && (self.persist || hasData) ) { return; } - else { - relatedTarget = eventTarget === element || element[contains](eventTarget) ? element : null; - hide(); - } - preventEmptyAnchor.call(e,eventTarget); - }, - clickHandler = function(e) { - relatedTarget = element; - show(); - preventEmptyAnchor.call(e,e[target]); - }, - preventScroll = function(e){ - var key = e.which || e.keyCode; - if( key === 38 || key === 40 ) { e[preventDefault](); } - }, - keyHandler = function(e){ - var key = e.which || e.keyCode, - activeItem = DOC.activeElement, - idx = menuItems[indexOf](activeItem), + self.dispose = function () { + off(element, 'click', self.toggle); + delete element.Collapse; + }; + tryWrapper(function (){ + element = queryElement(element); + element.Collapse && element.Collapse.dispose(); + var accordionData = element.getAttribute('data-parent'); + showCustomEvent = bootstrapCustomEvent('show', 'collapse'); + shownCustomEvent = bootstrapCustomEvent('shown', 'collapse'); + hideCustomEvent = bootstrapCustomEvent('hide', 'collapse'); + hiddenCustomEvent = bootstrapCustomEvent('hidden', 'collapse'); + collapse = queryElement(options.target || element.getAttribute('data-target') || element.getAttribute('href')); + collapse.isAnimating = false; + accordion = element.closest(options.parent || accordionData); + if ( !element.Collapse ) { + on(element, 'click', self.toggle); + } + element.Collapse = self; + },"BSN.Collapse"); + } + + var mouseClickEvents = { down: 'mousedown', up: 'mouseup' }; + + var support3DTransform = 'webkitPerspective' in document.body.style || 'perspective' in document.body.style; + + var supportTransform = 'webkitTransform' in document.body.style || 'transform' in document.body.style; + + function setFocus (element){ + element.focus ? element.focus() : element.setActive(); + } + function getScroll () { + return { + y : window.pageYOffset || document.documentElement.scrollTop, + x : window.pageXOffset || document.documentElement.scrollLeft + } + } + function styleTip (link,element,position,parent) { + var tipPositions = /\b(top|bottom|left|right)+/, + elementDimensions = { w : element.offsetWidth, h: element.offsetHeight }, + windowWidth = (document.documentElement.clientWidth || document.body.clientWidth), + windowHeight = (document.documentElement.clientHeight || document.body.clientHeight), + rect = link.getBoundingClientRect(), + scroll = parent === document.body ? getScroll() : { x: parent.offsetLeft + parent.scrollLeft, y: parent.offsetTop + parent.scrollTop }, + linkDimensions = { w: rect.right - rect.left, h: rect.bottom - rect.top }, + isPopover = hasClass(element,'popover'), + arrow = queryElement('.arrow',element), + halfTopExceed = rect.top + linkDimensions.h/2 - elementDimensions.h/2 < 0, + halfLeftExceed = rect.left + linkDimensions.w/2 - elementDimensions.w/2 < 0, + halfRightExceed = rect.left + elementDimensions.w/2 + linkDimensions.w/2 >= windowWidth, + halfBottomExceed = rect.top + elementDimensions.h/2 + linkDimensions.h/2 >= windowHeight, + topExceed = rect.top - elementDimensions.h < 0, + leftExceed = rect.left - elementDimensions.w < 0, + bottomExceed = rect.top + elementDimensions.h + linkDimensions.h >= windowHeight, + rightExceed = rect.left + elementDimensions.w + linkDimensions.w >= windowWidth; + position = (position === 'left' || position === 'right') && leftExceed && rightExceed ? 'top' : position; + position = position === 'top' && topExceed ? 'bottom' : position; + position = position === 'bottom' && bottomExceed ? 'top' : position; + position = position === 'left' && leftExceed ? 'right' : position; + position = position === 'right' && rightExceed ? 'left' : position; + var topPosition, + leftPosition, + arrowTop, + arrowLeft, + arrowWidth, + arrowHeight; + element.className.indexOf(position) === -1 && (element.className = element.className.replace(tipPositions,position)); + arrowWidth = arrow.offsetWidth; arrowHeight = arrow.offsetHeight; + if ( position === 'left' || position === 'right' ) { + if ( position === 'left' ) { + leftPosition = rect.left + scroll.x - elementDimensions.w - ( isPopover ? arrowWidth : 0 ); + } else { + leftPosition = rect.left + scroll.x + linkDimensions.w; + } + if (halfTopExceed) { + topPosition = rect.top + scroll.y; + arrowTop = linkDimensions.h/2 - arrowWidth; + } else if (halfBottomExceed) { + topPosition = rect.top + scroll.y - elementDimensions.h + linkDimensions.h; + arrowTop = elementDimensions.h - linkDimensions.h/2 - arrowWidth; + } else { + topPosition = rect.top + scroll.y - elementDimensions.h/2 + linkDimensions.h/2; + arrowTop = elementDimensions.h/2 - (isPopover ? arrowHeight*0.9 : arrowHeight/2); + } + } else if ( position === 'top' || position === 'bottom' ) { + if ( position === 'top') { + topPosition = rect.top + scroll.y - elementDimensions.h - ( isPopover ? arrowHeight : 0 ); + } else { + topPosition = rect.top + scroll.y + linkDimensions.h; + } + if (halfLeftExceed) { + leftPosition = 0; + arrowLeft = rect.left + linkDimensions.w/2 - arrowWidth; + } else if (halfRightExceed) { + leftPosition = windowWidth - elementDimensions.w*1.01; + arrowLeft = elementDimensions.w - ( windowWidth - rect.left ) + linkDimensions.w/2 - arrowWidth/2; + } else { + leftPosition = rect.left + scroll.x - elementDimensions.w/2 + linkDimensions.w/2; + arrowLeft = elementDimensions.w/2 - ( isPopover ? arrowWidth : arrowWidth/2 ); + } + } + element.style.top = topPosition + 'px'; + element.style.left = leftPosition + 'px'; + arrowTop && (arrow.style.top = arrowTop + 'px'); + arrowLeft && (arrow.style.left = arrowLeft + 'px'); + } + + function Dropdown(element,option) { + var self = this, + showCustomEvent, + shownCustomEvent, + hideCustomEvent, + hiddenCustomEvent, + relatedTarget = null, + parent, menu, menuItems = [], + persist; + function preventEmptyAnchor(anchor) { + (anchor.href && anchor.href.slice(-1) === '#' || anchor.parentNode && anchor.parentNode.href + && anchor.parentNode.href.slice(-1) === '#') && this.preventDefault(); + } + function toggleDismiss() { + var action = element.open ? on : off; + action(document, 'click', dismissHandler); + action(document, 'keydown', preventScroll); + action(document, 'keyup', keyHandler); + action(document, 'focus', dismissHandler, true); + } + function dismissHandler(e) { + var eventTarget = e.target, + hasData = eventTarget && (eventTarget.getAttribute('data-toggle') + || eventTarget.parentNode && eventTarget.parentNode.getAttribute + && eventTarget.parentNode.getAttribute('data-toggle')); + if ( e.type === 'focus' && (eventTarget === element || eventTarget === menu || menu.contains(eventTarget) ) ) { + return; + } + if ( (eventTarget === menu || menu.contains(eventTarget)) && (persist || hasData) ) { return; } + else { + relatedTarget = eventTarget === element || element.contains(eventTarget) ? element : null; + self.hide(); + } + preventEmptyAnchor.call(e,eventTarget); + } + function clickHandler(e) { + relatedTarget = element; + self.show(); + preventEmptyAnchor.call(e,e.target); + } + function preventScroll(e) { + var key = e.which || e.keyCode; + if( key === 38 || key === 40 ) { e.preventDefault(); } + } + function keyHandler(ref) { + var which = ref.which; + var keyCode = ref.keyCode; + var key = which || keyCode, + activeItem = document.activeElement, isSameElement = activeItem === element, - isInsideMenu = menu[contains](activeItem), - isMenuItem = activeItem[parentNode] === menu || activeItem[parentNode][parentNode] === menu; - - if ( isMenuItem ) { // navigate up | down - idx = isSameElement ? 0 - : key === 38 ? (idx>1?idx-1:0) - : key === 40 ? (idx<menuItems[length]-1?idx+1:idx) : idx; - menuItems[idx] && setFocus(menuItems[idx]); - } - if ( (menuItems[length] && isMenuItem // menu has items - || !menuItems[length] && (isInsideMenu || isSameElement) // menu might be a form - || !isInsideMenu ) // or the focused element is not in the menu at all - && element[open] && key === 27 // menu must be open - ) { - self.toggle(); - relatedTarget = null; - } - }, - - // private methods - show = function() { - bootstrapCustomEvent.call(parent, showEvent, component, relatedTarget); - addClass(menu,showClass); - addClass(parent,showClass); - element[setAttribute](ariaExpanded,true); - bootstrapCustomEvent.call(parent, shownEvent, component, relatedTarget); - element[open] = true; - off(element, clickEvent, clickHandler); - setTimeout(function(){ - setFocus( menu[getElementsByTagName]('INPUT')[0] || element ); // focus the first input item | element - toggleDismiss(); - },1); - }, - hide = function() { - bootstrapCustomEvent.call(parent, hideEvent, component, relatedTarget); - removeClass(menu,showClass); - removeClass(parent,showClass); - element[setAttribute](ariaExpanded,false); - bootstrapCustomEvent.call(parent, hiddenEvent, component, relatedTarget); - element[open] = false; + isInsideMenu = menu.contains(activeItem), + isMenuItem = activeItem.parentNode === menu || activeItem.parentNode.parentNode === menu; + var idx = menuItems.indexOf(activeItem); + if ( isMenuItem ) { + idx = isSameElement ? 0 + : key === 38 ? (idx>1?idx-1:0) + : key === 40 ? (idx<menuItems.length-1?idx+1:idx) : idx; + menuItems[idx] && setFocus(menuItems[idx]); + } + if ( (menuItems.length && isMenuItem + || !menuItems.length && (isInsideMenu || isSameElement) + || !isInsideMenu ) + && element.open && key === 27 + ) { + self.toggle(); + relatedTarget = null; + } + } + self.show = function () { + showCustomEvent = bootstrapCustomEvent('show', 'dropdown', relatedTarget); + dispatchCustomEvent.call(parent, showCustomEvent); + if ( showCustomEvent.defaultPrevented ) { return; } + addClass(menu,'show'); + addClass(parent,'show'); + element.setAttribute('aria-expanded',true); + element.open = true; + off(element, 'click', clickHandler); + setTimeout(function () { + setFocus( menu.getElementsByTagName('INPUT')[0] || element ); toggleDismiss(); - setFocus(element); - setTimeout(function(){ on(element, clickEvent, clickHandler); },1); - }; - - // set initial state to closed - element[open] = false; - - // public methods - this.toggle = function() { - if (hasClass(parent,showClass) && element[open]) { hide(); } - else { show(); } + shownCustomEvent = bootstrapCustomEvent( 'shown', 'dropdown', relatedTarget); + dispatchCustomEvent.call(parent, shownCustomEvent); + },1); }; - - // init - if ( !(stringDropdown in element) ) { // prevent adding event handlers twice - !tabindex in menu && menu[setAttribute](tabindex, '0'); // Fix onblur on Chrome | Safari - on(element, clickEvent, clickHandler); - } - - element[stringDropdown] = self; - }; - - // DROPDOWN DATA API - // ================= - supports[push]( [stringDropdown, Dropdown, '['+dataToggle+'="dropdown"]'] ); - - - /* Native Javascript for Bootstrap 4 | Modal - -------------------------------------------*/ - - // MODAL DEFINITION - // =============== - var Modal = function(element, options) { // element can be the modal/triggering button - - // the modal (both JavaScript / DATA API init) / triggering button element (DATA API) - element = queryElement(element); - - // strings - var component = 'modal', - staticString = 'static', - modalTrigger = 'modalTrigger', - paddingRight = 'paddingRight', - modalBackdropString = 'modal-backdrop', - isAnimating = 'isAnimating', - // determine modal, triggering element - btnCheck = element[getAttribute](dataTarget)||element[getAttribute]('href'), - checkModal = queryElement( btnCheck ), - modal = hasClass(element,component) ? element : checkModal; - - if ( hasClass(element, component) ) { element = null; } // modal is now independent of it's triggering element - - if ( !modal ) { return; } // invalidate - - // set options + self.hide = function () { + hideCustomEvent = bootstrapCustomEvent('hide', 'dropdown', relatedTarget); + dispatchCustomEvent.call(parent, hideCustomEvent); + if ( hideCustomEvent.defaultPrevented ) { return; } + removeClass(menu,'show'); + removeClass(parent,'show'); + element.setAttribute('aria-expanded',false); + element.open = false; + toggleDismiss(); + setFocus(element); + setTimeout(function () { + element.Dropdown && on(element, 'click', clickHandler); + },1); + hiddenCustomEvent = bootstrapCustomEvent('hidden', 'dropdown', relatedTarget); + dispatchCustomEvent.call(parent, hiddenCustomEvent); + }; + self.toggle = function () { + if (hasClass(parent,'show') && element.open) { self.hide(); } + else { self.show(); } + }; + self.dispose = function () { + if (hasClass(parent,'show') && element.open) { self.hide(); } + off(element, 'click', clickHandler); + delete element.Dropdown; + }; + tryWrapper(function (){ + element = queryElement(element); + element.Dropdown && element.Dropdown.dispose(); + parent = element.parentNode; + menu = queryElement('.dropdown-menu', parent); + Array.from(menu.children).map(function (child){ + child.children.length && (child.children[0].tagName === 'A' && menuItems.push(child.children[0])); + child.tagName === 'A' && menuItems.push(child); + }); + if ( !element.Dropdown ) { + !('tabindex' in menu) && menu.setAttribute('tabindex', '0'); + on(element, 'click', clickHandler); + } + persist = option === true || element.getAttribute('data-persist') === 'true' || false; + element.open = false; + element.Dropdown = self; + },"BSN.Dropdown"); + } + + function Modal(element,options) { options = options || {}; - - this[keyboard] = options[keyboard] === false || modal[getAttribute](dataKeyboard) === 'false' ? false : true; - this[backdrop] = options[backdrop] === staticString || modal[getAttribute](databackdrop) === staticString ? staticString : true; - this[backdrop] = options[backdrop] === false || modal[getAttribute](databackdrop) === 'false' ? false : this[backdrop]; - this[animation] = hasClass(modal, 'fade') ? true : false; - this[content] = options[content]; // JavaScript only - - // set an initial state of the modal - modal[isAnimating] = false; - - // bind, constants, event targets and other vars - var self = this, relatedTarget = null, - bodyIsOverflowing, scrollBarWidth, overlay, overlayDelay, modalTimer, - - // also find fixed-top / fixed-bottom items - fixedItems = getElementsByClassName(HTML,fixedTop).concat(getElementsByClassName(HTML,fixedBottom)), - - // private methods - getWindowWidth = function() { - var htmlRect = HTML[getBoundingClientRect](); - return globalObject[innerWidth] || (htmlRect[right] - Math.abs(htmlRect[left])); - }, - setScrollbar = function () { - var bodyStyle = globalObject[getComputedStyle](DOC[body]), - bodyPad = parseInt((bodyStyle[paddingRight]), 10), itemPad; - if (bodyIsOverflowing) { - DOC[body][style][paddingRight] = (bodyPad + scrollBarWidth) + 'px'; - modal[style][paddingRight] = scrollBarWidth+'px'; - if (fixedItems[length]){ - for (var i = 0; i < fixedItems[length]; i++) { - itemPad = globalObject[getComputedStyle](fixedItems[i])[paddingRight]; - fixedItems[i][style][paddingRight] = ( parseInt(itemPad) + scrollBarWidth) + 'px'; - } - } - } - }, - resetScrollbar = function () { - DOC[body][style][paddingRight] = ''; - modal[style][paddingRight] = ''; - if (fixedItems[length]){ - for (var i = 0; i < fixedItems[length]; i++) { - fixedItems[i][style][paddingRight] = ''; - } - } - }, - measureScrollbar = function () { // thx walsh - var scrollDiv = DOC[createElement]('div'), widthValue; - scrollDiv.className = component+'-scrollbar-measure'; // this is here to stay - DOC[body][appendChild](scrollDiv); - widthValue = scrollDiv[offsetWidth] - scrollDiv[clientWidth]; - DOC[body].removeChild(scrollDiv); - return widthValue; - }, - checkScrollbar = function () { - bodyIsOverflowing = DOC[body][clientWidth] < getWindowWidth(); - scrollBarWidth = measureScrollbar(); - }, - createOverlay = function() { - var newOverlay = DOC[createElement]('div'); - overlay = queryElement('.'+modalBackdropString); - - if ( overlay === null ) { - newOverlay[setAttribute]('class', modalBackdropString + (self[animation] ? ' fade' : '')); - overlay = newOverlay; - DOC[body][appendChild](overlay); - } - modalOverlay = 1; - }, - removeOverlay = function() { - overlay = queryElement('.'+modalBackdropString); - if ( overlay && overlay !== null && typeof overlay === 'object' ) { - modalOverlay = 0; - DOC[body].removeChild(overlay); overlay = null; - } - }, - // triggers - triggerShow = function() { - setFocus(modal); - modal[isAnimating] = false; - bootstrapCustomEvent.call(modal, shownEvent, component, relatedTarget); - - on(globalObject, resizeEvent, self.update, passiveHandler); - on(modal, clickEvent, dismissHandler); - on(DOC, keydownEvent, keyHandler); - }, - triggerHide = function() { - modal[style].display = ''; - element && (setFocus(element)); - bootstrapCustomEvent.call(modal, hiddenEvent, component); - - (function(){ - if (!getElementsByClassName(DOC,component+' '+showClass)[0]) { - resetScrollbar(); - removeClass(DOC[body],component+'-open'); - overlay && hasClass(overlay,'fade') ? (removeClass(overlay,showClass), emulateTransitionEnd(overlay,removeOverlay)) - : removeOverlay(); - - off(globalObject, resizeEvent, self.update, passiveHandler); - off(modal, clickEvent, dismissHandler); - off(DOC, keydownEvent, keyHandler); - } - }()); - modal[isAnimating] = false; - }, - // handlers - clickHandler = function(e) { - if ( modal[isAnimating] ) return; - - var clickTarget = e[target]; - clickTarget = clickTarget[hasAttribute](dataTarget) || clickTarget[hasAttribute]('href') ? clickTarget : clickTarget[parentNode]; - if ( clickTarget === element && !hasClass(modal,showClass) ) { - modal[modalTrigger] = element; - relatedTarget = element; - self.show(); - e[preventDefault](); - } - }, - keyHandler = function(e) { - if ( modal[isAnimating] ) return; - - if (self[keyboard] && e.which == 27 && hasClass(modal,showClass) ) { - self.hide(); - } - }, - dismissHandler = function(e) { - if ( modal[isAnimating] ) return; - var clickTarget = e[target]; - - if ( hasClass(modal,showClass) && ( clickTarget[parentNode][getAttribute](dataDismiss) === component - || clickTarget[getAttribute](dataDismiss) === component - || clickTarget === modal && self[backdrop] !== staticString ) ) { - self.hide(); relatedTarget = null; - e[preventDefault](); - } - }; - - // public methods - this.toggle = function() { - if ( hasClass(modal,showClass) ) {this.hide();} else {this.show();} + var self = this, modal, + showCustomEvent, + shownCustomEvent, + hideCustomEvent, + hiddenCustomEvent, + relatedTarget = null, + scrollBarWidth, + overlay, + overlayDelay, + fixedItems, + ops = {}; + function setScrollbar() { + var openModal = hasClass(document.body,'modal-open'), + bodyPad = parseInt(getComputedStyle(document.body).paddingRight), + modalOverflow = modal.clientHeight !== modal.scrollHeight, + itemPad; + modal.style.paddingRight = (!modalOverflow && scrollBarWidth?(scrollBarWidth + "px"):''); + document.body.style.paddingRight = (bodyPad + (openModal ?0:scrollBarWidth)) + "px"; + fixedItems.length && fixedItems.map(function (fixed){ + itemPad = getComputedStyle(fixed).paddingRight; + fixed.style.paddingRight = (parseInt(itemPad) + (openModal?0:scrollBarWidth)) + "px"; + }); + } + function resetScrollbar() { + document.body.style.paddingRight = ''; + modal.style.paddingRight = ''; + fixedItems.length && fixedItems.map(function (fixed){ + fixed.style.paddingRight = ''; + }); + } + function measureScrollbar() { + var scrollDiv = document.createElement('div'), widthValue; + scrollDiv.className = 'modal-scrollbar-measure'; + document.body.appendChild(scrollDiv); + widthValue = scrollDiv.offsetWidth - scrollDiv.clientWidth; + document.body.removeChild(scrollDiv); + return widthValue; + } + function checkScrollbar() { + scrollBarWidth = measureScrollbar(); + } + function createOverlay() { + var newOverlay = document.createElement('div'); + overlay = queryElement('.modal-backdrop'); + if ( overlay === null ) { + newOverlay.setAttribute('class', 'modal-backdrop' + (ops.animation ? ' fade' : '')); + overlay = newOverlay; + document.body.appendChild(overlay); + } + return overlay; + } + function removeOverlay () { + overlay = queryElement('.modal-backdrop'); + if ( overlay && !document.getElementsByClassName('modal show')[0] ) { + document.body.removeChild(overlay); overlay = null; + } + overlay === null && (removeClass(document.body,'modal-open'), resetScrollbar()); + } + function toggleEvents(action) { + action(window, 'resize', self.update, passiveHandler); + action(modal, 'click', dismissHandler); + action(document, 'keydown', keyHandler); + } + function beforeShow() { + modal.style.display = 'block'; + checkScrollbar(); + setScrollbar(); + !document.getElementsByClassName('modal show')[0] && addClass(document.body,'modal-open'); + addClass(modal,'show'); + modal.setAttribute('aria-hidden', false); + hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerShow) : triggerShow(); + } + function triggerShow() { + setFocus(modal); + modal.isAnimating = false; + toggleEvents(on); + shownCustomEvent = bootstrapCustomEvent('shown', 'modal', relatedTarget); + dispatchCustomEvent.call(modal, shownCustomEvent); + } + function triggerHide(force) { + modal.style.display = ''; + element && (setFocus(element)); + overlay = queryElement('.modal-backdrop'); + if (force !== 1 && overlay && hasClass(overlay,'show') && !document.getElementsByClassName('modal show')[0]) { + removeClass(overlay,'show'); + emulateTransitionEnd(overlay,removeOverlay); + } else { + removeOverlay(); + } + toggleEvents(off); + modal.isAnimating = false; + hiddenCustomEvent = bootstrapCustomEvent('hidden', 'modal'); + dispatchCustomEvent.call(modal, hiddenCustomEvent); + } + function clickHandler(e) { + if ( modal.isAnimating ) { return; } + var clickTarget = e.target, + modalID = "#" + (modal.getAttribute('id')), + targetAttrValue = clickTarget.getAttribute('data-target') || clickTarget.getAttribute('href'), + elemAttrValue = element.getAttribute('data-target') || element.getAttribute('href'); + if ( !hasClass(modal,'show') + && (clickTarget === element && targetAttrValue === modalID + || element.contains(clickTarget) && elemAttrValue === modalID) ) { + modal.modalTrigger = element; + relatedTarget = element; + self.show(); + e.preventDefault(); + } + } + function keyHandler(ref) { + var which = ref.which; + if (!modal.isAnimating && ops.keyboard && which == 27 && hasClass(modal,'show') ) { + self.hide(); + } + } + function dismissHandler(e) { + if ( modal.isAnimating ) { return; } + var clickTarget = e.target, + hasData = clickTarget.getAttribute('data-dismiss') === 'modal', + parentWithData = clickTarget.closest('[data-dismiss="modal"]'); + if ( hasClass(modal,'show') && ( parentWithData || hasData + || clickTarget === modal && ops.backdrop !== 'static' ) ) { + self.hide(); relatedTarget = null; + e.preventDefault(); + } + } + self.toggle = function () { + if ( hasClass(modal,'show') ) {self.hide();} else {self.show();} }; - this.show = function() { - if ( hasClass(modal,showClass) || modal[isAnimating] ) {return} - - clearTimeout(modalTimer); - modalTimer = setTimeout(function(){ - modal[isAnimating] = true; - bootstrapCustomEvent.call(modal, showEvent, component, relatedTarget); - - // we elegantly hide any opened modal - var currentOpen = getElementsByClassName(DOC,component+' '+showClass)[0]; - if (currentOpen && currentOpen !== modal) { - modalTrigger in currentOpen && currentOpen[modalTrigger][stringModal].hide(); - stringModal in currentOpen && currentOpen[stringModal].hide(); - } - - if ( self[backdrop] ) { - !modalOverlay && !overlay && createOverlay(); - } - - if ( overlay && !hasClass(overlay,showClass) ) { - overlay[offsetWidth]; // force reflow to enable trasition - overlayDelay = getTransitionDurationFromElement(overlay); - addClass(overlay, showClass); - } - - setTimeout( function() { - modal[style].display = 'block'; - - checkScrollbar(); - setScrollbar(); - - addClass(DOC[body],component+'-open'); - addClass(modal,showClass); - modal[setAttribute](ariaHidden, false); - - hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerShow) : triggerShow(); - }, supportTransitions && overlay && overlayDelay ? overlayDelay : 1); - },1); + self.show = function () { + if (hasClass(modal, 'show') && !!modal.isAnimating ) {return} + showCustomEvent = bootstrapCustomEvent('show', 'modal', relatedTarget); + dispatchCustomEvent.call(modal, showCustomEvent); + if ( showCustomEvent.defaultPrevented ) { return; } + modal.isAnimating = true; + var currentOpen = document.getElementsByClassName('modal show')[0]; + if (currentOpen && currentOpen !== modal) { + currentOpen.modalTrigger && currentOpen.modalTrigger.Modal.hide(); + currentOpen.Modal && currentOpen.Modal.hide(); + } + if ( ops.backdrop ) { + overlay = createOverlay(); + } + if ( overlay && !currentOpen && !hasClass(overlay,'show') ) { + overlay.offsetWidth; + overlayDelay = getElementTransitionDuration(overlay); + addClass(overlay, 'show'); + } + !currentOpen ? setTimeout( beforeShow, overlay && overlayDelay ? overlayDelay:0 ) : beforeShow(); }; - this.hide = function() { - if ( modal[isAnimating] || !hasClass(modal,showClass) ) {return} - - clearTimeout(modalTimer); - modalTimer = setTimeout(function(){ - modal[isAnimating] = true; - bootstrapCustomEvent.call(modal, hideEvent, component); - overlay = queryElement('.'+modalBackdropString); - overlayDelay = overlay && getTransitionDurationFromElement(overlay); - - removeClass(modal,showClass); - modal[setAttribute](ariaHidden, true); - - setTimeout(function(){ - hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerHide) : triggerHide(); - }, supportTransitions && overlay && overlayDelay ? overlayDelay : 2); - },2) + self.hide = function (force) { + if ( !hasClass(modal,'show') ) {return} + hideCustomEvent = bootstrapCustomEvent( 'hide', 'modal'); + dispatchCustomEvent.call(modal, hideCustomEvent); + if ( hideCustomEvent.defaultPrevented ) { return; } + modal.isAnimating = true; + removeClass(modal,'show'); + modal.setAttribute('aria-hidden', true); + hasClass(modal,'fade') && force !== 1 ? emulateTransitionEnd(modal, triggerHide) : triggerHide(); }; - this.setContent = function( content ) { - queryElement('.'+component+'-content',modal)[innerHTML] = content; + self.setContent = function (content) { + queryElement('.modal-content',modal).innerHTML = content; }; - this.update = function() { - if (hasClass(modal,showClass)) { + self.update = function () { + if (hasClass(modal,'show')) { checkScrollbar(); setScrollbar(); } }; - - // init - // prevent adding event handlers over and over - // modal is independent of a triggering element - if ( !!element && !(stringModal in element) ) { - on(element, clickEvent, clickHandler); - } - if ( !!self[content] ) { self.setContent( self[content] ); } - if (element) { element[stringModal] = self; modal[modalTrigger] = element; } - else { modal[stringModal] = self; } - }; - - // DATA API - supports[push]( [ stringModal, Modal, '['+dataToggle+'="modal"]' ] ); - - /* Native Javascript for Bootstrap 4 | Popover - ----------------------------------------------*/ - - // POPOVER DEFINITION - // ================== - var Popover = function( element, options ) { - - // initialization element - element = queryElement(element); - - // set options + self.dispose = function () { + self.hide(1); + if (element) {off(element, 'click', clickHandler); delete element.Modal; } + else {delete modal.Modal;} + }; + tryWrapper(function (){ + element = queryElement(element); + var checkModal = queryElement( element.getAttribute('data-target') || element.getAttribute('href') ); + modal = hasClass(element,'modal') ? element : checkModal; + fixedItems = Array.from(document.getElementsByClassName('fixed-top')) + .concat(Array.from(document.getElementsByClassName('fixed-bottom'))); + if ( hasClass(element, 'modal') ) { element = null; } + element && element.Modal && element.Modal.dispose(); + modal && modal.Modal && modal.Modal.dispose(); + ops.keyboard = options.keyboard === false || modal.getAttribute('data-keyboard') === 'false' ? false : true; + ops.backdrop = options.backdrop === 'static' || modal.getAttribute('data-backdrop') === 'static' ? 'static' : true; + ops.backdrop = options.backdrop === false || modal.getAttribute('data-backdrop') === 'false' ? false : ops.backdrop; + ops.animation = hasClass(modal, 'fade') ? true : false; + ops.content = options.content; + modal.isAnimating = false; + if ( element && !element.Modal ) { + on(element, 'click', clickHandler); + } + if ( ops.content ) { + self.setContent( ops.content.trim() ); + } + if (element) { + modal.modalTrigger = element; + element.Modal = self; + } else { + modal.Modal = self; + } + },"BSN.Modal"); + } + + function Popover(element,options) { options = options || {}; - - // DATA API - var triggerData = element[getAttribute](dataTrigger), // click / hover / focus - animationData = element[getAttribute](dataAnimation), // true / false - placementData = element[getAttribute](dataPlacement), - dismissibleData = element[getAttribute](dataDismissible), - delayData = element[getAttribute](dataDelay), - containerData = element[getAttribute](dataContainer), - - // internal strings - component = 'popover', - template = 'template', - trigger = 'trigger', - classString = 'class', - div = 'div', - fade = 'fade', - dataContent = 'data-content', - dismissible = 'dismissible', - closeBtn = '<button type="button" class="close">×</button>', - - // check container - containerElement = queryElement(options[container]), - containerDataElement = queryElement(containerData), - - // maybe the element is inside a modal - modal = getClosest(element,'.modal'), - - // maybe the element is inside a fixed navbar - navbarFixedTop = getClosest(element,'.'+fixedTop), - navbarFixedBottom = getClosest(element,'.'+fixedBottom); - - // set instance options - this[template] = options[template] ? options[template] : null; // JavaScript only - this[trigger] = options[trigger] ? options[trigger] : triggerData || hoverEvent; - this[animation] = options[animation] && options[animation] !== fade ? options[animation] : animationData || fade; - this[placement] = options[placement] ? options[placement] : placementData || top; - this[delay] = parseInt(options[delay] || delayData) || 200; - this[dismissible] = options[dismissible] || dismissibleData === 'true' ? true : false; - this[container] = containerElement ? containerElement - : containerDataElement ? containerDataElement - : navbarFixedTop ? navbarFixedTop - : navbarFixedBottom ? navbarFixedBottom - : modal ? modal : DOC[body]; - - // bind, content - var self = this, - titleString = options.title || element[getAttribute](dataTitle) || null, - contentString = options.content || element[getAttribute](dataContent) || null; - - if ( !contentString && !this[template] ) return; // invalidate - - // constants, vars - var popover = null, timer = 0, placementSetting = this[placement], - - // handlers - dismissibleHandler = function(e) { - if (popover !== null && e[target] === queryElement('.close',popover)) { - self.hide(); - } - }, - - // private methods - removePopover = function() { - self[container].removeChild(popover); - timer = null; popover = null; - }, - createPopover = function() { - titleString = options.title || element[getAttribute](dataTitle); - contentString = options.content || element[getAttribute](dataContent); - // fixing https://github.com/thednp/bootstrap.native/issues/233 - contentString = !!contentString ? contentString.trim() : null; - - popover = DOC[createElement](div); - - // popover arrow - var popoverArrow = DOC[createElement](div); - popoverArrow[setAttribute](classString,'arrow'); - popover[appendChild](popoverArrow); - - if ( contentString !== null && self[template] === null ) { //create the popover from data attributes - - popover[setAttribute]('role','tooltip'); - - if (titleString !== null) { - var popoverTitle = DOC[createElement]('h3'); - popoverTitle[setAttribute](classString,component+'-header'); - - popoverTitle[innerHTML] = self[dismissible] ? titleString + closeBtn : titleString; - popover[appendChild](popoverTitle); - } - - //set popover content - var popoverContent = DOC[createElement](div); - popoverContent[setAttribute](classString,component+'-body'); - popoverContent[innerHTML] = self[dismissible] && titleString === null ? contentString + closeBtn : contentString; - popover[appendChild](popoverContent); - - } else { // or create the popover from template - var popoverTemplate = DOC[createElement](div); - self[template] = self[template].trim(); - popoverTemplate[innerHTML] = self[template]; - popover[innerHTML] = popoverTemplate.firstChild[innerHTML]; - } - - //append to the container - self[container][appendChild](popover); - popover[style].display = 'block'; - popover[setAttribute](classString, component+ ' bs-' + component+'-'+placementSetting + ' ' + self[animation]); - }, - showPopover = function () { - !hasClass(popover,showClass) && ( addClass(popover,showClass) ); - }, - updatePopover = function() { - styleTip(element, popover, placementSetting, self[container]); - }, - - // event toggle - dismissHandlerToggle = function(type){ - if (clickEvent == self[trigger] || 'focus' == self[trigger]) { - !self[dismissible] && type( element, 'blur', self.hide ); + var self = this; + var popover = null, + timer = 0, + isIphone = /(iPhone|iPod|iPad)/.test(navigator.userAgent), + titleString, + contentString, + ops = {}; + var triggerData, + animationData, + placementData, + dismissibleData, + delayData, + containerData, + closeBtn, + showCustomEvent, + shownCustomEvent, + hideCustomEvent, + hiddenCustomEvent, + containerElement, + containerDataElement, + modal, + navbarFixedTop, + navbarFixedBottom, + placementClass; + function dismissibleHandler(e) { + if (popover !== null && e.target === queryElement('.close',popover)) { + self.hide(); + } + } + function getContents() { + return { + 0 : options.title || element.getAttribute('data-title') || null, + 1 : options.content || element.getAttribute('data-content') || null + } + } + function removePopover() { + ops.container.removeChild(popover); + timer = null; popover = null; + } + function createPopover() { + titleString = getContents()[0] || null; + contentString = getContents()[1]; + contentString = !!contentString ? contentString.trim() : null; + popover = document.createElement('div'); + var popoverArrow = document.createElement('div'); + addClass(popoverArrow,'arrow'); + popover.appendChild(popoverArrow); + if ( contentString !== null && ops.template === null ) { + popover.setAttribute('role','tooltip'); + if (titleString !== null) { + var popoverTitle = document.createElement('h3'); + addClass(popoverTitle,'popover-header'); + popoverTitle.innerHTML = ops.dismissible ? titleString + closeBtn : titleString; + popover.appendChild(popoverTitle); } - self[dismissible] && type( DOC, clickEvent, dismissibleHandler ); - type( globalObject, resizeEvent, self.hide, passiveHandler ); - }, - - // triggers - showTrigger = function() { - dismissHandlerToggle(on); - bootstrapCustomEvent.call(element, shownEvent, component); - }, - hideTrigger = function() { - dismissHandlerToggle(off); - removePopover(); - bootstrapCustomEvent.call(element, hiddenEvent, component); - }; - - // public methods / handlers - this.toggle = function() { - if (popover === null) { self.show(); } + var popoverBodyMarkup = document.createElement('div'); + addClass(popoverBodyMarkup,'popover-body'); + popoverBodyMarkup.innerHTML = ops.dismissible && titleString === null ? contentString + closeBtn : contentString; + popover.appendChild(popoverBodyMarkup); + } else { + var popoverTemplate = document.createElement('div'); + popoverTemplate.innerHTML = ops.template.trim(); + popover.className = popoverTemplate.firstChild.className; + popover.innerHTML = popoverTemplate.firstChild.innerHTML; + var popoverHeader = queryElement('.popover-header',popover), + popoverBody = queryElement('.popover-body',popover); + titleString && popoverHeader && (popoverHeader.innerHTML = titleString.trim()); + contentString && popoverBody && (popoverBody.innerHTML = contentString.trim()); + } + ops.container.appendChild(popover); + popover.style.display = 'block'; + !hasClass(popover, 'popover') && addClass(popover, 'popover'); + !hasClass(popover, ops.animation) && addClass(popover, ops.animation); + !hasClass(popover, placementClass) && addClass(popover, placementClass); + } + function showPopover() { + !hasClass(popover,'show') && ( addClass(popover,'show') ); + } + function updatePopover() { + styleTip(element, popover, ops.placement, ops.container); + } + function provideFocus () { + if (popover === null) { element.focus(); } + } + function toggleEvents(action) { + if (ops.trigger === 'hover') { + action( element, mouseClickEvents.down, self.show ); + action( element, mouseHoverEvents[0], self.show ); + if (!ops.dismissible) { action( element, mouseHoverEvents[1], self.hide ); } + } else if ('click' == ops.trigger) { + action( element, ops.trigger, self.toggle ); + } else if ('focus' == ops.trigger) { + isIphone && action( element, 'click', provideFocus ); + action( element, ops.trigger, self.toggle ); + } + } + function touchHandler(e){ + if ( popover && popover.contains(e.target) || e.target === element || element.contains(e.target)) ; else { + self.hide(); + } + } + function dismissHandlerToggle(action) { + if (ops.dismissible) { + action( document, 'click', dismissibleHandler ); + } else { + 'focus' == ops.trigger && action( element, 'blur', self.hide ); + 'hover' == ops.trigger && action( document, touchEvents.start, touchHandler, passiveHandler ); + } + action( window, 'resize', self.hide, passiveHandler ); + } + function showTrigger() { + dismissHandlerToggle(on); + dispatchCustomEvent.call(element, shownCustomEvent); + } + function hideTrigger() { + dismissHandlerToggle(off); + removePopover(); + dispatchCustomEvent.call(element, hiddenCustomEvent); + } + self.toggle = function () { + if (popover === null) { self.show(); } else { self.hide(); } }; - this.show = function() { + self.show = function () { clearTimeout(timer); - timer = setTimeout( function() { + timer = setTimeout( function () { if (popover === null) { - placementSetting = self[placement]; // we reset placement in all cases + dispatchCustomEvent.call(element, showCustomEvent); + if ( showCustomEvent.defaultPrevented ) { return; } createPopover(); updatePopover(); showPopover(); - bootstrapCustomEvent.call(element, showEvent, component); - !!self[animation] ? emulateTransitionEnd(popover, showTrigger) : showTrigger(); + !!ops.animation ? emulateTransitionEnd(popover, showTrigger) : showTrigger(); } }, 20 ); }; - this.hide = function() { + self.hide = function () { clearTimeout(timer); - timer = setTimeout( function() { - if (popover && popover !== null && hasClass(popover,showClass)) { - bootstrapCustomEvent.call(element, hideEvent, component); - removeClass(popover,showClass); - !!self[animation] ? emulateTransitionEnd(popover, hideTrigger) : hideTrigger(); + timer = setTimeout( function () { + if (popover && popover !== null && hasClass(popover,'show')) { + dispatchCustomEvent.call(element, hideCustomEvent); + if ( hideCustomEvent.defaultPrevented ) { return; } + removeClass(popover,'show'); + !!ops.animation ? emulateTransitionEnd(popover, hideTrigger) : hideTrigger(); } - }, self[delay] ); + }, ops.delay ); }; - - // init - if ( !(stringPopover in element) ) { // prevent adding event handlers twice - if (self[trigger] === hoverEvent) { - on( element, mouseHover[0], self.show ); - if (!self[dismissible]) { on( element, mouseHover[1], self.hide ); } - } else if (clickEvent == self[trigger] || 'focus' == self[trigger]) { - on( element, self[trigger], self.toggle ); + self.dispose = function () { + self.hide(); + toggleEvents(off); + delete element.Popover; + }; + tryWrapper(function (){ + element = queryElement(element); + element.Popover && element.Popover.dispose(); + triggerData = element.getAttribute('data-trigger'); + animationData = element.getAttribute('data-animation'); + placementData = element.getAttribute('data-placement'); + dismissibleData = element.getAttribute('data-dismissible'); + delayData = element.getAttribute('data-delay'); + containerData = element.getAttribute('data-container'); + closeBtn = '<button type="button" class="close">×</button>'; + showCustomEvent = bootstrapCustomEvent('show', 'popover'); + shownCustomEvent = bootstrapCustomEvent('shown', 'popover'); + hideCustomEvent = bootstrapCustomEvent('hide', 'popover'); + hiddenCustomEvent = bootstrapCustomEvent('hidden', 'popover'); + containerElement = queryElement(options.container); + containerDataElement = queryElement(containerData); + modal = element.closest('.modal'); + navbarFixedTop = element.closest('.fixed-top'); + navbarFixedBottom = element.closest('.fixed-bottom'); + ops.template = options.template ? options.template : null; + ops.trigger = options.trigger ? options.trigger : triggerData || 'hover'; + ops.animation = options.animation && options.animation !== 'fade' ? options.animation : animationData || 'fade'; + ops.placement = options.placement ? options.placement : placementData || 'top'; + ops.delay = parseInt(options.delay || delayData) || 200; + ops.dismissible = options.dismissible || dismissibleData === 'true' ? true : false; + ops.container = containerElement ? containerElement + : containerDataElement ? containerDataElement + : navbarFixedTop ? navbarFixedTop + : navbarFixedBottom ? navbarFixedBottom + : modal ? modal : document.body; + placementClass = "bs-popover-" + (ops.placement); + var popoverContents = getContents(); + titleString = popoverContents[0]; + contentString = popoverContents[1]; + if ( !contentString && !ops.template ) { return; } + if ( !element.Popover ) { + toggleEvents(on); } - } - element[stringPopover] = self; - }; - - // POPOVER DATA API - // ================ - supports[push]( [ stringPopover, Popover, '['+dataToggle+'="popover"]' ] ); - - - /* Native Javascript for Bootstrap 4 | ScrollSpy - -----------------------------------------------*/ - - // SCROLLSPY DEFINITION - // ==================== - var ScrollSpy = function(element, options) { - - // initialization element, the element we spy on - element = queryElement(element); - - // DATA API - var targetData = queryElement(element[getAttribute](dataTarget)), - offsetData = element[getAttribute]('data-offset'); - - // set options + element.Popover = self; + },"BSN.Popover"); + } + + function ScrollSpy(element,options) { options = options || {}; - - // invalidate - if ( !options[target] && !targetData ) { return; } - - // event targets, constants - var self = this, spyTarget = options[target] && queryElement(options[target]) || targetData, - links = spyTarget && spyTarget[getElementsByTagName]('A'), - offset = parseInt(options['offset'] || offsetData) || 10, - items = [], targetItems = [], scrollOffset, - scrollTarget = element[offsetHeight] < element[scrollHeight] ? element : globalObject, // determine which is the real scrollTarget - isWindow = scrollTarget === globalObject; - - // populate items and targets - for (var i=0, il=links[length]; i<il; i++) { - var href = links[i][getAttribute]('href'), - targetItem = href && href.charAt(0) === '#' && href.slice(-1) !== '#' && queryElement(href); - if ( !!targetItem ) { - items[push](links[i]); - targetItems[push](targetItem); - } - } - - // private methods - var updateItem = function(index) { - var item = items[index], - targetItem = targetItems[index], // the menu item targets this element - dropdown = item[parentNode][parentNode], - dropdownLink = hasClass(dropdown,'dropdown') && dropdown[getElementsByTagName]('A')[0], - targetRect = isWindow && targetItem[getBoundingClientRect](), - - isActive = hasClass(item,active) || false, - - topEdge = (isWindow ? targetRect[top] + scrollOffset : targetItem[offsetTop]) - offset, - bottomEdge = isWindow ? targetRect[bottom] + scrollOffset - offset : targetItems[index+1] ? targetItems[index+1][offsetTop] - offset : element[scrollHeight], - - inside = scrollOffset >= topEdge && bottomEdge > scrollOffset; - - if ( !isActive && inside ) { - if ( !hasClass(item,active) ) { - addClass(item,active); - if (dropdownLink && !hasClass(dropdownLink,active) ) { - addClass(dropdownLink,active); - } - bootstrapCustomEvent.call(element, 'activate', 'scrollspy', items[index]); - } - } else if ( !inside ) { - if ( hasClass(item,active) ) { - removeClass(item,active); - if (dropdownLink && hasClass(dropdownLink,active) && !getElementsByClassName(item[parentNode],active).length ) { - removeClass(dropdownLink,active); - } + var self = this, + vars, + targetData, + offsetData, + spyTarget, + scrollTarget, + ops = {}; + function updateTargets(){ + var links = spyTarget.getElementsByTagName('A'); + if (vars.length !== links.length) { + vars.items = []; + vars.targets = []; + Array.from(links).map(function (link){ + var href = link.getAttribute('href'), + targetItem = href && href.charAt(0) === '#' && href.slice(-1) !== '#' && queryElement(href); + if ( targetItem ) { + vars.items.push(link); + vars.targets.push(targetItem); } - } else if ( !inside && !isActive || isActive && inside ) { - return; + }); + vars.length = links.length; + } + } + function updateItem(index) { + var item = vars.items[index], + targetItem = vars.targets[index], + dropmenu = hasClass(item,'dropdown-item') && item.closest('.dropdown-menu'), + dropLink = dropmenu && dropmenu.previousElementSibling, + nextSibling = item.nextElementSibling, + activeSibling = nextSibling && nextSibling.getElementsByClassName('active').length, + targetRect = vars.isWindow && targetItem.getBoundingClientRect(), + isActive = hasClass(item,'active') || false, + topEdge = (vars.isWindow ? targetRect.top + vars.scrollOffset : targetItem.offsetTop) - ops.offset, + bottomEdge = vars.isWindow ? targetRect.bottom + vars.scrollOffset - ops.offset + : vars.targets[index+1] ? vars.targets[index+1].offsetTop - ops.offset + : element.scrollHeight, + inside = activeSibling || vars.scrollOffset >= topEdge && bottomEdge > vars.scrollOffset; + if ( !isActive && inside ) { + addClass(item,'active'); + if (dropLink && !hasClass(dropLink,'active') ) { + addClass(dropLink,'active'); } - }, - updateItems = function(){ - scrollOffset = isWindow ? getScroll().y : element[scrollTop]; - for (var index=0, itl=items[length]; index<itl; index++) { - updateItem(index) + dispatchCustomEvent.call(element, bootstrapCustomEvent( 'activate', 'scrollspy', vars.items[index])); + } else if ( isActive && !inside ) { + removeClass(item,'active'); + if (dropLink && hasClass(dropLink,'active') && !item.parentNode.getElementsByClassName('active').length ) { + removeClass(dropLink,'active'); } - }; - - // public method - this.refresh = function () { - updateItems(); + } else if ( isActive && inside || !inside && !isActive ) { + return; + } } - - // init - if ( !(stringScrollSpy in element) ) { // prevent adding event handlers twice - on( scrollTarget, scrollEvent, self.refresh, passiveHandler ); - on( globalObject, resizeEvent, self.refresh, passiveHandler ); + function updateItems() { + updateTargets(); + vars.scrollOffset = vars.isWindow ? getScroll().y : element.scrollTop; + vars.items.map(function (l,idx){ return updateItem(idx); }); } - self.refresh(); - element[stringScrollSpy] = self; - }; - - // SCROLLSPY DATA API - // ================== - supports[push]( [ stringScrollSpy, ScrollSpy, '['+dataSpy+'="scroll"]' ] ); - - - /* Native Javascript for Bootstrap 4 | Tab - -----------------------------------------*/ - - // TAB DEFINITION - // ============== - var Tab = function( element, options ) { - - // initialization element - element = queryElement(element); - - // DATA API - var heightData = element[getAttribute](dataHeight), - - // strings - component = 'tab', height = 'height', float = 'float', isAnimating = 'isAnimating'; - - // set options + function toggleEvents(action) { + action( scrollTarget, 'scroll', self.refresh, passiveHandler ); + action( window, 'resize', self.refresh, passiveHandler ); + } + self.refresh = function () { + updateItems(); + }; + self.dispose = function () { + toggleEvents(off); + delete element.ScrollSpy; + }; + tryWrapper(function (){ + element = queryElement(element); + element.ScrollSpy && element.ScrollSpy.dispose(); + targetData = element.getAttribute('data-target'); + offsetData = element.getAttribute('data-offset'); + spyTarget = queryElement(options.target || targetData); + scrollTarget = element.offsetHeight < element.scrollHeight ? element : window; + if (!spyTarget) { return } + ops.target = spyTarget; + ops.offset = parseInt(options.offset || offsetData) || 10; + vars = {}; + vars.length = 0; + vars.items = []; + vars.targets = []; + vars.isWindow = scrollTarget === window; + if ( !element.ScrollSpy ) { + toggleEvents(on); + } + self.refresh(); + element.ScrollSpy = self; + },"BSN.ScrollSpy"); + } + + function Tab(element,options) { options = options || {}; - this[height] = supportTransitions ? (options[height] || heightData === 'true') : false; - - // bind, event targets - var self = this, next, - tabs = getClosest(element,'.nav'), + var self = this, + heightData, + tabs, dropdown, + showCustomEvent, + shownCustomEvent, + hideCustomEvent, + hiddenCustomEvent, + next, tabsContentContainer = false, - dropdown = tabs && queryElement('.dropdown-toggle',tabs), - activeTab, activeContent, nextContent, containerHeight, equalContents, nextHeight, - - // trigger - triggerEnd = function(){ - tabsContentContainer[style][height] = ''; - removeClass(tabsContentContainer,collapsing); - tabs[isAnimating] = false; - }, - triggerShow = function() { - if (tabsContentContainer) { // height animation - if ( equalContents ) { - triggerEnd(); - } else { - setTimeout(function(){ // enables height animation - tabsContentContainer[style][height] = nextHeight + 'px'; // height animation - tabsContentContainer[offsetWidth]; - emulateTransitionEnd(tabsContentContainer, triggerEnd); - },50); - } + activeTab, + activeContent, + nextContent, + containerHeight, + equalContents, + nextHeight, + animateHeight; + function triggerEnd() { + tabsContentContainer.style.height = ''; + removeClass(tabsContentContainer,'collapsing'); + tabs.isAnimating = false; + } + function triggerShow() { + if (tabsContentContainer) { + if ( equalContents ) { + triggerEnd(); } else { - tabs[isAnimating] = false; - } - bootstrapCustomEvent.call(next, shownEvent, component, activeTab); - }, - triggerHide = function() { - if (tabsContentContainer) { - activeContent[style][float] = left; - nextContent[style][float] = left; - containerHeight = activeContent[scrollHeight]; - } - - addClass(nextContent,active); - bootstrapCustomEvent.call(next, showEvent, component, activeTab); - - removeClass(activeContent,active); - bootstrapCustomEvent.call(activeTab, hiddenEvent, component, next); - - if (tabsContentContainer) { - nextHeight = nextContent[scrollHeight]; - equalContents = nextHeight === containerHeight; - addClass(tabsContentContainer,collapsing); - tabsContentContainer[style][height] = containerHeight + 'px'; // height animation - tabsContentContainer[offsetHeight]; - activeContent[style][float] = ''; - nextContent[style][float] = ''; + setTimeout(function () { + tabsContentContainer.style.height = nextHeight + "px"; + tabsContentContainer.offsetWidth; + emulateTransitionEnd(tabsContentContainer, triggerEnd); + },50); } - - if ( hasClass(nextContent, 'fade') ) { - setTimeout(function(){ - addClass(nextContent,showClass); - emulateTransitionEnd(nextContent,triggerShow); - },20); - } else { triggerShow(); } - }; - - if (!tabs) return; // invalidate - - // set default animation state - tabs[isAnimating] = false; - - // private methods - var getActiveTab = function() { - var activeTabs = getElementsByClassName(tabs,active), activeTab; - if ( activeTabs[length] === 1 && !hasClass(activeTabs[0][parentNode],'dropdown') ) { - activeTab = activeTabs[0]; - } else if ( activeTabs[length] > 1 ) { - activeTab = activeTabs[activeTabs[length]-1]; - } - return activeTab; - }, - getActiveContent = function() { - return queryElement(getActiveTab()[getAttribute]('href')); - }, - // handler - clickHandler = function(e) { - e[preventDefault](); - next = e[currentTarget]; - !tabs[isAnimating] && !hasClass(next,active) && self.show(); - }; - - // public method - this.show = function() { // the tab we clicked is now the next tab + } else { + tabs.isAnimating = false; + } + shownCustomEvent = bootstrapCustomEvent('shown', 'tab', activeTab); + dispatchCustomEvent.call(next, shownCustomEvent); + } + function triggerHide() { + if (tabsContentContainer) { + activeContent.style.float = 'left'; + nextContent.style.float = 'left'; + containerHeight = activeContent.scrollHeight; + } + showCustomEvent = bootstrapCustomEvent('show', 'tab', activeTab); + hiddenCustomEvent = bootstrapCustomEvent('hidden', 'tab', next); + dispatchCustomEvent.call(next, showCustomEvent); + if ( showCustomEvent.defaultPrevented ) { return; } + addClass(nextContent,'active'); + removeClass(activeContent,'active'); + if (tabsContentContainer) { + nextHeight = nextContent.scrollHeight; + equalContents = nextHeight === containerHeight; + addClass(tabsContentContainer,'collapsing'); + tabsContentContainer.style.height = containerHeight + "px"; + tabsContentContainer.offsetHeight; + activeContent.style.float = ''; + nextContent.style.float = ''; + } + if ( hasClass(nextContent, 'fade') ) { + setTimeout(function () { + addClass(nextContent,'show'); + emulateTransitionEnd(nextContent,triggerShow); + },20); + } else { triggerShow(); } + dispatchCustomEvent.call(activeTab, hiddenCustomEvent); + } + function getActiveTab() { + var activeTabs = tabs.getElementsByClassName('active'), activeTab; + if ( activeTabs.length === 1 && !hasClass(activeTabs[0].parentNode,'dropdown') ) { + activeTab = activeTabs[0]; + } else if ( activeTabs.length > 1 ) { + activeTab = activeTabs[activeTabs.length-1]; + } + return activeTab; + } + function getActiveContent() { return queryElement(getActiveTab().getAttribute('href')) } + function clickHandler(e) { + e.preventDefault(); + next = e.currentTarget; + !tabs.isAnimating && self.show(); + } + self.show = function () { next = next || element; - nextContent = queryElement(next[getAttribute]('href')); //this is the actual object, the next tab content to activate - activeTab = getActiveTab(); - activeContent = getActiveContent(); - - tabs[isAnimating] = true; - removeClass(activeTab,active); - activeTab[setAttribute](ariaSelected,'false'); - addClass(next,active); - next[setAttribute](ariaSelected,'true'); - - if ( dropdown ) { - if ( !hasClass(element[parentNode],'dropdown-menu') ) { - if (hasClass(dropdown,active)) removeClass(dropdown,active); - } else { - if (!hasClass(dropdown,active)) addClass(dropdown,active); + if (!hasClass(next,'active')) { + nextContent = queryElement(next.getAttribute('href')); + activeTab = getActiveTab(); + activeContent = getActiveContent(); + hideCustomEvent = bootstrapCustomEvent( 'hide', 'tab', next); + dispatchCustomEvent.call(activeTab, hideCustomEvent); + if (hideCustomEvent.defaultPrevented) { return; } + tabs.isAnimating = true; + removeClass(activeTab,'active'); + activeTab.setAttribute('aria-selected','false'); + addClass(next,'active'); + next.setAttribute('aria-selected','true'); + if ( dropdown ) { + if ( !hasClass(element.parentNode,'dropdown-menu') ) { + if (hasClass(dropdown,'active')) { removeClass(dropdown,'active'); } + } else { + if (!hasClass(dropdown,'active')) { addClass(dropdown,'active'); } + } } + if (hasClass(activeContent, 'fade')) { + removeClass(activeContent,'show'); + emulateTransitionEnd(activeContent, triggerHide); + } else { triggerHide(); } } - - bootstrapCustomEvent.call(activeTab, hideEvent, component, next); - - if (hasClass(activeContent, 'fade')) { - removeClass(activeContent,showClass); - emulateTransitionEnd(activeContent, triggerHide); - } else { triggerHide(); } }; - - // init - if ( !(stringTab in element) ) { // prevent adding event handlers twice - on(element, clickEvent, clickHandler); - } - if (self[height]) { tabsContentContainer = getActiveContent()[parentNode]; } - element[stringTab] = self; - }; - - // TAB DATA API - // ============ - supports[push]( [ stringTab, Tab, '['+dataToggle+'="tab"]' ] ); - - - /* Native Javascript for Bootstrap 4 | Toast - ---------------------------------------------*/ - - // TOAST DEFINITION - // ================== - var Toast = function( element,options ) { - - // initialization element - element = queryElement(element); - - // set options + self.dispose = function () { + off(element, 'click', clickHandler); + delete element.Tab; + }; + tryWrapper(function (){ + element = queryElement(element); + element.Tab && element.Tab.dispose(); + heightData = element.getAttribute('data-height'); + tabs = element.closest('.nav'); + dropdown = tabs && queryElement('.dropdown-toggle',tabs); + animateHeight = !supportTransition || (options.height === false || heightData === 'false') ? false : true; + tabs.isAnimating = false; + if ( !element.Tab ) { + on(element, 'click', clickHandler); + } + if (animateHeight) { tabsContentContainer = getActiveContent().parentNode; } + element.Tab = self; + },'BSN.Tab'); + } + + function Toast(element,options) { options = options || {}; - - // DATA API - var animationData = element[getAttribute](dataAnimation), - autohideData = element[getAttribute](dataAutohide), - delayData = element[getAttribute](dataDelay), - - // strings - component = 'toast', - autohide = 'autohide', - animation = 'animation', - showing = 'showing', - hide = 'hide', - fade = 'fade'; - - // set instance options - this[animation] = options[animation] === false || animationData === 'false' ? 0 : 1; // true by default - this[autohide] = options[autohide] === false || autohideData === 'false' ? 0 : 1; // true by default - this[delay] = parseInt(options[delay] || delayData) || 500; // 500ms default - - // bind,toast and timer - var self = this, timer = 0, - // get the toast element - toast = getClosest(element,'.toast'); - - // private methods - // animation complete - var showComplete = function() { - removeClass( toast, showing ); - addClass( toast, showClass ); - bootstrapCustomEvent.call(toast, shownEvent, component); - if (self[autohide]) { self.hide(); } - }, - hideComplete = function() { - addClass( toast, hide ); - bootstrapCustomEvent.call(toast, hiddenEvent, component); - }, - close = function() { - removeClass( toast,showClass ); - self[animation] ? emulateTransitionEnd(toast, hideComplete) : hideComplete(); - }, - disposeComplete = function(){ - clearTimeout(timer); timer = null; - addClass( toast, hide ); - off(element, clickEvent, self.hide); - element[stringToast] = null; - element = null; - toast = null; - }; - - // public methods - this.show = function() { - if (toast) { - bootstrapCustomEvent.call(toast, showEvent, component); - self[animation] && addClass( toast,fade ); - removeClass( toast,hide ); - addClass( toast,showing ); - - self[animation] ? emulateTransitionEnd(toast, showComplete) : showComplete(); + var self = this, + toast, timer = 0, + animationData, + autohideData, + delayData, + showCustomEvent, + hideCustomEvent, + shownCustomEvent, + hiddenCustomEvent, + ops = {}; + function showComplete() { + removeClass( toast, 'showing' ); + addClass( toast, 'show' ); + dispatchCustomEvent.call(toast,shownCustomEvent); + if (ops.autohide) { self.hide(); } + } + function hideComplete() { + addClass( toast, 'hide' ); + dispatchCustomEvent.call(toast,hiddenCustomEvent); + } + function close () { + removeClass( toast,'show' ); + ops.animation ? emulateTransitionEnd(toast, hideComplete) : hideComplete(); + } + function disposeComplete() { + clearTimeout(timer); + off(element, 'click', self.hide); + delete element.Toast; + } + self.show = function () { + if (toast && !hasClass(toast,'show')) { + dispatchCustomEvent.call(toast,showCustomEvent); + if (showCustomEvent.defaultPrevented) { return; } + ops.animation && addClass( toast,'fade' ); + removeClass( toast,'hide' ); + toast.offsetWidth; + addClass( toast,'showing' ); + ops.animation ? emulateTransitionEnd(toast, showComplete) : showComplete(); } }; - this.hide = function(noTimer) { - if (toast && hasClass(toast,showClass)) { - bootstrapCustomEvent.call(toast, hideEvent, component); - - if (noTimer) { - close(); - } else { - timer = setTimeout( close, self[delay]); - } + self.hide = function (noTimer) { + if (toast && hasClass(toast,'show')) { + dispatchCustomEvent.call(toast,hideCustomEvent); + if(hideCustomEvent.defaultPrevented) { return; } + noTimer ? close() : (timer = setTimeout( close, ops.delay)); } }; - this.dispose = function() { - if ( toast && hasClass(toast,showClass) ) { - removeClass( toast,showClass ); - self[animation] ? emulateTransitionEnd(toast, disposeComplete) : disposeComplete(); - } + self.dispose = function () { + ops.animation ? emulateTransitionEnd(toast, disposeComplete) : disposeComplete(); }; - - // init - if ( !(stringToast in element) ) { // prevent adding event handlers twice - on(element, clickEvent, self.hide); - } - element[stringToast] = self; - }; - - // TOAST DATA API - // ================= - supports[push]( [ stringToast, Toast, '['+dataDismiss+'="toast"]' ] ); - - - /* Native Javascript for Bootstrap 4 | Tooltip - ---------------------------------------------*/ - - // TOOLTIP DEFINITION - // ================== - var Tooltip = function( element,options ) { - - // initialization element - element = queryElement(element); - - // set options + tryWrapper(function (){ + element = queryElement(element); + element.Toast && element.Toast.dispose(); + toast = element.closest('.toast'); + animationData = element.getAttribute('data-animation'); + autohideData = element.getAttribute('data-autohide'); + delayData = element.getAttribute('data-delay'); + showCustomEvent = bootstrapCustomEvent('show', 'toast'); + hideCustomEvent = bootstrapCustomEvent('hide', 'toast'); + shownCustomEvent = bootstrapCustomEvent('shown', 'toast'); + hiddenCustomEvent = bootstrapCustomEvent('hidden', 'toast'); + ops.animation = options.animation === false || animationData === 'false' ? 0 : 1; + ops.autohide = options.autohide === false || autohideData === 'false' ? 0 : 1; + ops.delay = parseInt(options.delay || delayData) || 500; + if ( !element.Toast ) { + on(element, 'click', self.hide); + } + element.Toast = self; + },'BSN.Toast'); + } + + function Tooltip(element,options) { options = options || {}; - - // DATA API - var animationData = element[getAttribute](dataAnimation), - placementData = element[getAttribute](dataPlacement), - delayData = element[getAttribute](dataDelay), - containerData = element[getAttribute](dataContainer), - - // strings - component = 'tooltip', - classString = 'class', - title = 'title', - fade = 'fade', - div = 'div', - - // check container - containerElement = queryElement(options[container]), - containerDataElement = queryElement(containerData), - - // maybe the element is inside a modal - modal = getClosest(element,'.modal'), - - // maybe the element is inside a fixed navbar - navbarFixedTop = getClosest(element,'.'+fixedTop), - navbarFixedBottom = getClosest(element,'.'+fixedBottom); - - // set instance options - this[animation] = options[animation] && options[animation] !== fade ? options[animation] : animationData || fade; - this[placement] = options[placement] ? options[placement] : placementData || top; - this[delay] = parseInt(options[delay] || delayData) || 200; - this[container] = containerElement ? containerElement - : containerDataElement ? containerDataElement - : navbarFixedTop ? navbarFixedTop - : navbarFixedBottom ? navbarFixedBottom - : modal ? modal : DOC[body]; - - // bind, event targets, title and constants - var self = this, timer = 0, placementSetting = this[placement], tooltip = null, - titleString = element[getAttribute](title) || element[getAttribute](dataTitle) || element[getAttribute](dataOriginalTitle); - - if ( !titleString || titleString == "" ) return; // invalidate - - // private methods - var removeToolTip = function() { - self[container].removeChild(tooltip); - tooltip = null; timer = null; - }, - createToolTip = function() { - titleString = element[getAttribute](title) || element[getAttribute](dataTitle) || element[getAttribute](dataOriginalTitle); // read the title again - - if ( titleString && titleString !== "" ) { // invalidate, maybe markup changed - tooltip = DOC[createElement](div); - tooltip[setAttribute]('role',component); - tooltip[style][left] = '0'; - tooltip[style][top] = '0'; - - // tooltip arrow - var tooltipArrow = DOC[createElement](div); - tooltipArrow[setAttribute](classString,'arrow'); - tooltip[appendChild](tooltipArrow); - - var tooltipInner = DOC[createElement](div); - tooltipInner[setAttribute](classString,component+'-inner'); - tooltip[appendChild](tooltipInner); - tooltipInner[innerHTML] = titleString; - - self[container][appendChild](tooltip); - tooltip[setAttribute](classString, component + ' bs-' + component+'-'+placementSetting + ' ' + self[animation]); + var self = this, + tooltip = null, timer = 0, titleString, + animationData, + placementData, + delayData, + containerData, + showCustomEvent, + shownCustomEvent, + hideCustomEvent, + hiddenCustomEvent, + containerElement, + containerDataElement, + modal, + navbarFixedTop, + navbarFixedBottom, + placementClass, + ops = {}; + function getTitle() { + return element.getAttribute('title') + || element.getAttribute('data-title') + || element.getAttribute('data-original-title') + } + function removeToolTip() { + ops.container.removeChild(tooltip); + tooltip = null; timer = null; + } + function createToolTip() { + titleString = getTitle(); + if ( titleString ) { + tooltip = document.createElement('div'); + if (ops.template) { + var tooltipMarkup = document.createElement('div'); + tooltipMarkup.innerHTML = ops.template.trim(); + tooltip.className = tooltipMarkup.firstChild.className; + tooltip.innerHTML = tooltipMarkup.firstChild.innerHTML; + queryElement('.tooltip-inner',tooltip).innerHTML = titleString.trim(); + } else { + var tooltipArrow = document.createElement('div'); + addClass(tooltipArrow,'arrow'); + tooltip.appendChild(tooltipArrow); + var tooltipInner = document.createElement('div'); + addClass(tooltipInner,'tooltip-inner'); + tooltip.appendChild(tooltipInner); + tooltipInner.innerHTML = titleString; } - }, - updateTooltip = function () { - styleTip(element, tooltip, placementSetting, self[container]); - }, - showTooltip = function () { - !hasClass(tooltip,showClass) && ( addClass(tooltip,showClass) ); - }, - // triggers - showTrigger = function() { - on( globalObject, resizeEvent, self.hide, passiveHandler ); - bootstrapCustomEvent.call(element, shownEvent, component); - }, - hideTrigger = function() { - off( globalObject, resizeEvent, self.hide, passiveHandler ); - removeToolTip(); - bootstrapCustomEvent.call(element, hiddenEvent, component); - }; - - // public methods - this.show = function() { + tooltip.style.left = '0'; + tooltip.style.top = '0'; + tooltip.setAttribute('role','tooltip'); + !hasClass(tooltip, 'tooltip') && addClass(tooltip, 'tooltip'); + !hasClass(tooltip, ops.animation) && addClass(tooltip, ops.animation); + !hasClass(tooltip, placementClass) && addClass(tooltip, placementClass); + ops.container.appendChild(tooltip); + } + } + function updateTooltip() { + styleTip(element, tooltip, ops.placement, ops.container); + } + function showTooltip() { + !hasClass(tooltip,'show') && ( addClass(tooltip,'show') ); + } + function touchHandler(e){ + if ( tooltip && tooltip.contains(e.target) || e.target === element || element.contains(e.target)) ; else { + self.hide(); + } + } + function showAction() { + on( document, touchEvents.start, touchHandler, passiveHandler ); + on( window, 'resize', self.hide, passiveHandler ); + dispatchCustomEvent.call(element, shownCustomEvent); + } + function hideAction() { + off( document, touchEvents.start, touchHandler, passiveHandler ); + off( window, 'resize', self.hide, passiveHandler ); + removeToolTip(); + dispatchCustomEvent.call(element, hiddenCustomEvent); + } + function toggleEvents(action) { + action(element, mouseClickEvents.down, self.show); + action(element, mouseHoverEvents[0], self.show); + action(element, mouseHoverEvents[1], self.hide); + } + self.show = function () { clearTimeout(timer); - timer = setTimeout( function() { + timer = setTimeout( function () { if (tooltip === null) { - placementSetting = self[placement]; // we reset placement in all cases - // if(createToolTip() == false) return; + dispatchCustomEvent.call(element, showCustomEvent); + if (showCustomEvent.defaultPrevented) { return; } if(createToolTip() !== false) { updateTooltip(); showTooltip(); - bootstrapCustomEvent.call(element, showEvent, component); - !!self[animation] ? emulateTransitionEnd(tooltip, showTrigger) : showTrigger(); + !!ops.animation ? emulateTransitionEnd(tooltip, showAction) : showAction(); } } }, 20 ); }; - this.hide = function() { + self.hide = function () { clearTimeout(timer); - timer = setTimeout( function() { - if (tooltip && hasClass(tooltip,showClass)) { - bootstrapCustomEvent.call(element, hideEvent, component); - removeClass(tooltip,showClass); - !!self[animation] ? emulateTransitionEnd(tooltip, hideTrigger) : hideTrigger(); + timer = setTimeout( function () { + if (tooltip && hasClass(tooltip,'show')) { + dispatchCustomEvent.call(element, hideCustomEvent); + if (hideCustomEvent.defaultPrevented) { return; } + removeClass(tooltip,'show'); + !!ops.animation ? emulateTransitionEnd(tooltip, hideAction) : hideAction(); } - }, self[delay]); + }, ops.delay); }; - this.toggle = function() { - if (!tooltip) { self.show(); } + self.toggle = function () { + if (!tooltip) { self.show(); } else { self.hide(); } }; - - // init - if ( !(stringTooltip in element) ) { // prevent adding event handlers twice - element[setAttribute](dataOriginalTitle,titleString); - element.removeAttribute(title); - on(element, mouseHover[0], self.show); - on(element, mouseHover[1], self.hide); - } - element[stringTooltip] = self; - }; - - // TOOLTIP DATA API - // ================= - supports[push]( [ stringTooltip, Tooltip, '['+dataToggle+'="tooltip"]' ] ); - - - - /* Native Javascript for Bootstrap | Initialize Data API - --------------------------------------------------------*/ - var initializeDataAPI = function( constructor, collection ){ - for (var i=0, l=collection[length]; i<l; i++) { - new constructor(collection[i]); - } - }, - initCallback = BSN.initCallback = function(lookUp){ - lookUp = lookUp || DOC; - for (var i=0, l=supports[length]; i<l; i++) { - initializeDataAPI( supports[i][1], lookUp[querySelectorAll] (supports[i][2]) ); + self.dispose = function () { + toggleEvents(off); + self.hide(); + element.setAttribute('title', element.getAttribute('data-original-title')); + element.removeAttribute('data-original-title'); + delete element.Tooltip; + }; + tryWrapper(function (){ + element = queryElement(element); + element.Tooltip && element.Tooltip.dispose(); + animationData = element.getAttribute('data-animation'); + placementData = element.getAttribute('data-placement'); + delayData = element.getAttribute('data-delay'); + containerData = element.getAttribute('data-container'); + showCustomEvent = bootstrapCustomEvent('show', 'tooltip'); + shownCustomEvent = bootstrapCustomEvent('shown', 'tooltip'); + hideCustomEvent = bootstrapCustomEvent('hide', 'tooltip'); + hiddenCustomEvent = bootstrapCustomEvent('hidden', 'tooltip'); + containerElement = queryElement(options.container); + containerDataElement = queryElement(containerData); + modal = element.closest('.modal'); + navbarFixedTop = element.closest('.fixed-top'); + navbarFixedBottom = element.closest('.fixed-bottom'); + ops.animation = options.animation && options.animation !== 'fade' ? options.animation : animationData || 'fade'; + ops.placement = options.placement ? options.placement : placementData || 'top'; + ops.template = options.template ? options.template : null; + ops.delay = parseInt(options.delay || delayData) || 200; + ops.container = containerElement ? containerElement + : containerDataElement ? containerDataElement + : navbarFixedTop ? navbarFixedTop + : navbarFixedBottom ? navbarFixedBottom + : modal ? modal : document.body; + placementClass = "bs-tooltip-" + (ops.placement); + titleString = getTitle(); + if ( !titleString ) { return; } + if (!element.Tooltip) { + element.setAttribute('data-original-title',titleString); + element.removeAttribute('title'); + toggleEvents(on); } + element.Tooltip = self; + },'BSN.Tooltip'); + } + + var componentsInit = {}; + + var initCallback = function (lookUp){ + lookUp = lookUp || document; + var initializeDataAPI = function( Constructor, collection ){ + Array.from(collection).map(function (x){ return new Constructor(x); }); + }; + for (var component in componentsInit) { + initializeDataAPI( componentsInit[component][0], lookUp.querySelectorAll (componentsInit[component][1]) ); + } + }; + var removeDataAPI = function (lookUp) { + lookUp = lookUp || document; + var removeElementDataAPI = function( ConstructorName, collection ){ + Array.from(collection).map(function (x){ return x[ConstructorName].dispose(); }); }; - - // bulk initialize all components - DOC[body] ? initCallback() : on( DOC, 'DOMContentLoaded', function(){ initCallback(); } ); - - return { + for (var component in componentsInit) { + removeElementDataAPI( component, lookUp.querySelectorAll (componentsInit[component][1]) ); + } + }; + + componentsInit.Alert = [ Alert, '[data-dismiss="alert"]']; + componentsInit.Button = [ Button, '[data-toggle="buttons"]' ]; + componentsInit.Carousel = [ Carousel, '[data-ride="carousel"]' ]; + componentsInit.Collapse = [ Collapse, '[data-toggle="collapse"]' ]; + componentsInit.Dropdown = [ Dropdown, '[data-toggle="dropdown"]']; + componentsInit.Modal = [ Modal, '[data-toggle="modal"]' ]; + componentsInit.Popover = [ Popover, '[data-toggle="popover"],[data-tip="popover"]' ]; + componentsInit.ScrollSpy = [ ScrollSpy, '[data-spy="scroll"]' ]; + componentsInit.Tab = [ Tab, '[data-toggle="tab"]' ]; + componentsInit.Toast = [ Toast, '[data-dismiss="toast"]' ]; + componentsInit.Tooltip = [ Tooltip, '[data-toggle="tooltip"],[data-tip="tooltip"]' ]; + document.body ? initCallback() : one( document, 'DOMContentLoaded', initCallback ); + + var version = "3.0.1"; + + var index = { Alert: Alert, Button: Button, Carousel: Carousel, @@ -2004,6 +1694,13 @@ ScrollSpy: ScrollSpy, Tab: Tab, Toast: Toast, - Tooltip: Tooltip + Tooltip: Tooltip, + initCallback: initCallback, + removeDataAPI: removeDataAPI, + componentsInit: componentsInit, + Version: version }; -})); + + return index; + +}))); diff --git a/src/static/scripts/bootstrap.css b/src/static/scripts/bootstrap.css @@ -1,7 +1,7 @@ /*! - * Bootstrap v4.3.1 (https://getbootstrap.com/) - * Copyright 2011-2019 The Bootstrap Authors - * Copyright 2011-2019 Twitter, Inc. + * Bootstrap v4.5.0 (https://getbootstrap.com/) + * Copyright 2011-2020 The Bootstrap Authors + * Copyright 2011-2020 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ :root { @@ -63,7 +63,7 @@ body { background-color: #fff; } -[tabindex="-1"]:focus { +[tabindex="-1"]:focus:not(:focus-visible) { outline: 0 !important; } @@ -163,20 +163,16 @@ a:hover { text-decoration: underline; } -a:not([href]):not([tabindex]) { +a:not([href]) { color: inherit; text-decoration: none; } -a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { +a:not([href]):hover { color: inherit; text-decoration: none; } -a:not([href]):not([tabindex]):focus { - outline: 0; -} - pre, code, kbd, @@ -189,6 +185,7 @@ pre { margin-top: 0; margin-bottom: 1rem; overflow: auto; + -ms-overflow-style: scrollbar; } figure { @@ -256,6 +253,10 @@ select { text-transform: none; } +[role="button"] { + cursor: pointer; +} + select { word-wrap: normal; } @@ -288,13 +289,6 @@ input[type="checkbox"] { padding: 0; } -input[type="date"], -input[type="time"], -input[type="datetime-local"], -input[type="month"] { - -webkit-appearance: listbox; -} - textarea { overflow: auto; resize: vertical; @@ -507,7 +501,7 @@ mark, code { font-size: 87.5%; color: #e83e8c; - word-break: break-word; + word-wrap: break-word; } a > code { @@ -577,7 +571,7 @@ pre code { } } -.container-fluid { +.container-fluid, .container-sm, .container-md, .container-lg, .container-xl { width: 100%; padding-right: 15px; padding-left: 15px; @@ -585,6 +579,30 @@ pre code { margin-left: auto; } +@media (min-width: 576px) { + .container, .container-sm { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .container, .container-sm, .container-md { + max-width: 720px; + } +} + +@media (min-width: 992px) { + .container, .container-sm, .container-md, .container-lg { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container, .container-sm, .container-md, .container-lg, .container-xl { + max-width: 1140px; + } +} + .row { display: -ms-flexbox; display: flex; @@ -622,9 +640,46 @@ pre code { flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; + min-width: 0; max-width: 100%; } +.row-cols-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} + +.row-cols-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.row-cols-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} + +.row-cols-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} + +.row-cols-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; +} + +.row-cols-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; +} + .col-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; @@ -829,8 +884,39 @@ pre code { flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; + min-width: 0; + max-width: 100%; + } + .row-cols-sm-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; max-width: 100%; } + .row-cols-sm-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-sm-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-sm-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-sm-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-sm-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } .col-sm-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; @@ -1001,8 +1087,39 @@ pre code { flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; + min-width: 0; + max-width: 100%; + } + .row-cols-md-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; max-width: 100%; } + .row-cols-md-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-md-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-md-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-md-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-md-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } .col-md-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; @@ -1173,8 +1290,39 @@ pre code { flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; + min-width: 0; + max-width: 100%; + } + .row-cols-lg-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; max-width: 100%; } + .row-cols-lg-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-lg-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-lg-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-lg-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-lg-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } .col-lg-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; @@ -1345,8 +1493,39 @@ pre code { flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; + min-width: 0; + max-width: 100%; + } + .row-cols-xl-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; max-width: 100%; } + .row-cols-xl-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-xl-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-xl-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-xl-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-xl-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } .col-xl-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; @@ -1881,6 +2060,11 @@ pre code { border: 0; } +.form-control:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #495057; +} + .form-control:focus { color: #495057; background-color: #fff; @@ -1919,6 +2103,15 @@ pre code { opacity: 1; } +input[type="date"].form-control, +input[type="time"].form-control, +input[type="datetime-local"].form-control, +input[type="month"].form-control { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + select.form-control:focus::-ms-value { color: #495057; background-color: #fff; @@ -1955,9 +2148,9 @@ select.form-control:focus::-ms-value { .form-control-plaintext { display: block; width: 100%; - padding-top: 0.375rem; - padding-bottom: 0.375rem; + padding: 0.375rem 0; margin-bottom: 0; + font-size: 1rem; line-height: 1.5; color: #212529; background-color: transparent; @@ -2030,6 +2223,7 @@ textarea.form-control { margin-left: -1.25rem; } +.form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label { color: #6c757d; } @@ -2077,12 +2271,19 @@ textarea.form-control { border-radius: 0.25rem; } +.was-validated :valid ~ .valid-feedback, +.was-validated :valid ~ .valid-tooltip, +.is-valid ~ .valid-feedback, +.is-valid ~ .valid-tooltip { + display: block; +} + .was-validated .form-control:valid, .form-control.is-valid { border-color: #28a745; padding-right: calc(1.5em + 0.75rem); - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); background-repeat: no-repeat; - background-position: center right calc(0.375em + 0.1875rem); + background-position: right calc(0.375em + 0.1875rem) center; background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } @@ -2091,12 +2292,6 @@ textarea.form-control { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } -.was-validated .form-control:valid ~ .valid-feedback, -.was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback, -.form-control.is-valid ~ .valid-tooltip { - display: block; -} - .was-validated textarea.form-control:valid, textarea.form-control.is-valid { padding-right: calc(1.5em + 0.75rem); background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); @@ -2104,8 +2299,8 @@ textarea.form-control { .was-validated .custom-select:valid, .custom-select.is-valid { border-color: #28a745; - padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem); - background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); + padding-right: calc(0.75em + 2.3125rem); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } .was-validated .custom-select:valid:focus, .custom-select.is-valid:focus { @@ -2113,18 +2308,6 @@ textarea.form-control { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } -.was-validated .custom-select:valid ~ .valid-feedback, -.was-validated .custom-select:valid ~ .valid-tooltip, .custom-select.is-valid ~ .valid-feedback, -.custom-select.is-valid ~ .valid-tooltip { - display: block; -} - -.was-validated .form-control-file:valid ~ .valid-feedback, -.was-validated .form-control-file:valid ~ .valid-tooltip, .form-control-file.is-valid ~ .valid-feedback, -.form-control-file.is-valid ~ .valid-tooltip { - display: block; -} - .was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { color: #28a745; } @@ -2143,12 +2326,6 @@ textarea.form-control { border-color: #28a745; } -.was-validated .custom-control-input:valid ~ .valid-feedback, -.was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback, -.custom-control-input.is-valid ~ .valid-tooltip { - display: block; -} - .was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before { border-color: #34ce57; background-color: #34ce57; @@ -2166,12 +2343,6 @@ textarea.form-control { border-color: #28a745; } -.was-validated .custom-file-input:valid ~ .valid-feedback, -.was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback, -.custom-file-input.is-valid ~ .valid-tooltip { - display: block; -} - .was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label { border-color: #28a745; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); @@ -2200,12 +2371,19 @@ textarea.form-control { border-radius: 0.25rem; } +.was-validated :invalid ~ .invalid-feedback, +.was-validated :invalid ~ .invalid-tooltip, +.is-invalid ~ .invalid-feedback, +.is-invalid ~ .invalid-tooltip { + display: block; +} + .was-validated .form-control:invalid, .form-control.is-invalid { border-color: #dc3545; padding-right: calc(1.5em + 0.75rem); - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); background-repeat: no-repeat; - background-position: center right calc(0.375em + 0.1875rem); + background-position: right calc(0.375em + 0.1875rem) center; background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } @@ -2214,12 +2392,6 @@ textarea.form-control { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } -.was-validated .form-control:invalid ~ .invalid-feedback, -.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback, -.form-control.is-invalid ~ .invalid-tooltip { - display: block; -} - .was-validated textarea.form-control:invalid, textarea.form-control.is-invalid { padding-right: calc(1.5em + 0.75rem); background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); @@ -2227,8 +2399,8 @@ textarea.form-control { .was-validated .custom-select:invalid, .custom-select.is-invalid { border-color: #dc3545; - padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem); - background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); + padding-right: calc(0.75em + 2.3125rem); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } .was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus { @@ -2236,18 +2408,6 @@ textarea.form-control { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } -.was-validated .custom-select:invalid ~ .invalid-feedback, -.was-validated .custom-select:invalid ~ .invalid-tooltip, .custom-select.is-invalid ~ .invalid-feedback, -.custom-select.is-invalid ~ .invalid-tooltip { - display: block; -} - -.was-validated .form-control-file:invalid ~ .invalid-feedback, -.was-validated .form-control-file:invalid ~ .invalid-tooltip, .form-control-file.is-invalid ~ .invalid-feedback, -.form-control-file.is-invalid ~ .invalid-tooltip { - display: block; -} - .was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { color: #dc3545; } @@ -2266,12 +2426,6 @@ textarea.form-control { border-color: #dc3545; } -.was-validated .custom-control-input:invalid ~ .invalid-feedback, -.was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback, -.custom-control-input.is-invalid ~ .invalid-tooltip { - display: block; -} - .was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before { border-color: #e4606d; background-color: #e4606d; @@ -2289,12 +2443,6 @@ textarea.form-control { border-color: #dc3545; } -.was-validated .custom-file-input:invalid ~ .invalid-feedback, -.was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback, -.custom-file-input.is-invalid ~ .invalid-tooltip { - display: block; -} - .was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label { border-color: #dc3545; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); @@ -2414,6 +2562,10 @@ textarea.form-control { opacity: 0.65; } +.btn:not(:disabled):not(.disabled) { + cursor: pointer; +} + a.btn.disabled, fieldset:disabled a.btn { pointer-events: none; @@ -2432,6 +2584,9 @@ fieldset:disabled a.btn { } .btn-primary:focus, .btn-primary.focus { + color: #fff; + background-color: #0069d9; + border-color: #0062cc; box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5); } @@ -2466,6 +2621,9 @@ fieldset:disabled a.btn { } .btn-secondary:focus, .btn-secondary.focus { + color: #fff; + background-color: #5a6268; + border-color: #545b62; box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); } @@ -2500,6 +2658,9 @@ fieldset:disabled a.btn { } .btn-success:focus, .btn-success.focus { + color: #fff; + background-color: #218838; + border-color: #1e7e34; box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5); } @@ -2534,6 +2695,9 @@ fieldset:disabled a.btn { } .btn-info:focus, .btn-info.focus { + color: #fff; + background-color: #138496; + border-color: #117a8b; box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); } @@ -2568,6 +2732,9 @@ fieldset:disabled a.btn { } .btn-warning:focus, .btn-warning.focus { + color: #212529; + background-color: #e0a800; + border-color: #d39e00; box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5); } @@ -2602,6 +2769,9 @@ fieldset:disabled a.btn { } .btn-danger:focus, .btn-danger.focus { + color: #fff; + background-color: #c82333; + border-color: #bd2130; box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5); } @@ -2636,6 +2806,9 @@ fieldset:disabled a.btn { } .btn-light:focus, .btn-light.focus { + color: #212529; + background-color: #e2e6ea; + border-color: #dae0e5; box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); } @@ -2670,6 +2843,9 @@ fieldset:disabled a.btn { } .btn-dark:focus, .btn-dark.focus { + color: #fff; + background-color: #23272b; + border-color: #1d2124; box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); } @@ -2960,7 +3136,6 @@ fieldset:disabled a.btn { .btn-link:focus, .btn-link.focus { text-decoration: underline; - box-shadow: none; } .btn-link:disabled, .btn-link.disabled { @@ -3422,6 +3597,7 @@ input[type="button"].btn-block { -ms-flex: 1 1 auto; flex: 1 1 auto; width: 1%; + min-width: 0; margin-bottom: 0; } @@ -3613,7 +3789,10 @@ input[type="button"].btn-block { .custom-control-input { position: absolute; + left: 0; z-index: -1; + width: 1rem; + height: 1.25rem; opacity: 0; } @@ -3637,11 +3816,11 @@ input[type="button"].btn-block { border-color: #b3d7ff; } -.custom-control-input:disabled ~ .custom-control-label { +.custom-control-input[disabled] ~ .custom-control-label, .custom-control-input:disabled ~ .custom-control-label { color: #6c757d; } -.custom-control-input:disabled ~ .custom-control-label::before { +.custom-control-input[disabled] ~ .custom-control-label::before, .custom-control-input:disabled ~ .custom-control-label::before { background-color: #e9ecef; } @@ -3680,7 +3859,7 @@ input[type="button"].btn-block { } .custom-checkbox .custom-control-input:checked ~ .custom-control-label::after { - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e"); } .custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before { @@ -3689,7 +3868,7 @@ input[type="button"].btn-block { } .custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after { - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e"); } .custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before { @@ -3705,7 +3884,7 @@ input[type="button"].btn-block { } .custom-radio .custom-control-input:checked ~ .custom-control-label::after { - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); } .custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before { @@ -3761,8 +3940,7 @@ input[type="button"].btn-block { line-height: 1.5; color: #495057; vertical-align: middle; - background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px; - background-color: #fff; + background: #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px; border: 1px solid #ced4da; border-radius: 0.25rem; -webkit-appearance: none; @@ -3796,6 +3974,11 @@ input[type="button"].btn-block { display: none; } +.custom-select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #495057; +} + .custom-select-sm { height: calc(1.5em + 0.5rem + 2px); padding-top: 0.25rem; @@ -3834,6 +4017,7 @@ input[type="button"].btn-block { box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } +.custom-file-input[disabled] ~ .custom-file-label, .custom-file-input:disabled ~ .custom-file-label { background-color: #e9ecef; } @@ -3881,7 +4065,7 @@ input[type="button"].btn-block { .custom-range { width: 100%; - height: calc(1rem + 0.4rem); + height: 1.4rem; padding: 0; background-color: transparent; -webkit-appearance: none; @@ -3916,6 +4100,7 @@ input[type="button"].btn-block { background-color: #007bff; border: 0; border-radius: 1rem; + -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -webkit-appearance: none; appearance: none; @@ -3923,6 +4108,7 @@ input[type="button"].btn-block { @media (prefers-reduced-motion: reduce) { .custom-range::-webkit-slider-thumb { + -webkit-transition: none; transition: none; } } @@ -3947,6 +4133,7 @@ input[type="button"].btn-block { background-color: #007bff; border: 0; border-radius: 1rem; + -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -moz-appearance: none; appearance: none; @@ -3954,6 +4141,7 @@ input[type="button"].btn-block { @media (prefers-reduced-motion: reduce) { .custom-range::-moz-range-thumb { + -moz-transition: none; transition: none; } } @@ -3981,12 +4169,14 @@ input[type="button"].btn-block { background-color: #007bff; border: 0; border-radius: 1rem; + -ms-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; appearance: none; } @media (prefers-reduced-motion: reduce) { .custom-range::-ms-thumb { + -ms-transition: none; transition: none; } } @@ -4157,8 +4347,8 @@ input[type="button"].btn-block { padding: 0.5rem 1rem; } -.navbar > .container, -.navbar > .container-fluid { +.navbar .container, +.navbar .container-fluid, .navbar .container-sm, .navbar .container-md, .navbar .container-lg, .navbar .container-xl { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; @@ -4243,7 +4433,7 @@ input[type="button"].btn-block { @media (max-width: 575.98px) { .navbar-expand-sm > .container, - .navbar-expand-sm > .container-fluid { + .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl { padding-right: 0; padding-left: 0; } @@ -4268,7 +4458,7 @@ input[type="button"].btn-block { padding-left: 0.5rem; } .navbar-expand-sm > .container, - .navbar-expand-sm > .container-fluid { + .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } @@ -4285,7 +4475,7 @@ input[type="button"].btn-block { @media (max-width: 767.98px) { .navbar-expand-md > .container, - .navbar-expand-md > .container-fluid { + .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl { padding-right: 0; padding-left: 0; } @@ -4310,7 +4500,7 @@ input[type="button"].btn-block { padding-left: 0.5rem; } .navbar-expand-md > .container, - .navbar-expand-md > .container-fluid { + .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } @@ -4327,7 +4517,7 @@ input[type="button"].btn-block { @media (max-width: 991.98px) { .navbar-expand-lg > .container, - .navbar-expand-lg > .container-fluid { + .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl { padding-right: 0; padding-left: 0; } @@ -4352,7 +4542,7 @@ input[type="button"].btn-block { padding-left: 0.5rem; } .navbar-expand-lg > .container, - .navbar-expand-lg > .container-fluid { + .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } @@ -4369,7 +4559,7 @@ input[type="button"].btn-block { @media (max-width: 1199.98px) { .navbar-expand-xl > .container, - .navbar-expand-xl > .container-fluid { + .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl { padding-right: 0; padding-left: 0; } @@ -4394,7 +4584,7 @@ input[type="button"].btn-block { padding-left: 0.5rem; } .navbar-expand-xl > .container, - .navbar-expand-xl > .container-fluid { + .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } @@ -4417,7 +4607,7 @@ input[type="button"].btn-block { } .navbar-expand > .container, -.navbar-expand > .container-fluid { +.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl { padding-right: 0; padding-left: 0; } @@ -4437,7 +4627,7 @@ input[type="button"].btn-block { } .navbar-expand > .container, -.navbar-expand > .container-fluid { +.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } @@ -4486,7 +4676,7 @@ input[type="button"].btn-block { } .navbar-light .navbar-toggler-icon { - background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } .navbar-light .navbar-text { @@ -4534,7 +4724,7 @@ input[type="button"].btn-block { } .navbar-dark .navbar-toggler-icon { - background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } .navbar-dark .navbar-text { @@ -4568,19 +4758,27 @@ input[type="button"].btn-block { margin-left: 0; } -.card > .list-group:first-child .list-group-item:first-child { - border-top-left-radius: 0.25rem; - border-top-right-radius: 0.25rem; +.card > .list-group { + border-top: inherit; + border-bottom: inherit; } -.card > .list-group:last-child .list-group-item:last-child { - border-bottom-right-radius: 0.25rem; - border-bottom-left-radius: 0.25rem; +.card > .list-group:first-child { + border-top-width: 0; + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); +} + +.card > .list-group:last-child { + border-bottom-width: 0; + border-bottom-right-radius: calc(0.25rem - 1px); + border-bottom-left-radius: calc(0.25rem - 1px); } .card-body { -ms-flex: 1 1 auto; flex: 1 1 auto; + min-height: 1px; padding: 1.25rem; } @@ -4651,67 +4849,56 @@ input[type="button"].btn-block { padding: 1.25rem; } -.card-img { +.card-img, +.card-img-top, +.card-img-bottom { + -ms-flex-negative: 0; + flex-shrink: 0; width: 100%; - border-radius: calc(0.25rem - 1px); } +.card-img, .card-img-top { - width: 100%; border-top-left-radius: calc(0.25rem - 1px); border-top-right-radius: calc(0.25rem - 1px); } +.card-img, .card-img-bottom { - width: 100%; border-bottom-right-radius: calc(0.25rem - 1px); border-bottom-left-radius: calc(0.25rem - 1px); } -.card-deck { - display: -ms-flexbox; - display: flex; - -ms-flex-direction: column; - flex-direction: column; -} - .card-deck .card { margin-bottom: 15px; } @media (min-width: 576px) { .card-deck { + display: -ms-flexbox; + display: flex; -ms-flex-flow: row wrap; flex-flow: row wrap; margin-right: -15px; margin-left: -15px; } .card-deck .card { - display: -ms-flexbox; - display: flex; -ms-flex: 1 0 0%; flex: 1 0 0%; - -ms-flex-direction: column; - flex-direction: column; margin-right: 15px; margin-bottom: 0; margin-left: 15px; } } -.card-group { - display: -ms-flexbox; - display: flex; - -ms-flex-direction: column; - flex-direction: column; -} - .card-group > .card { margin-bottom: 15px; } @media (min-width: 576px) { .card-group { + display: -ms-flexbox; + display: flex; -ms-flex-flow: row wrap; flex-flow: row wrap; } @@ -4775,27 +4962,19 @@ input[type="button"].btn-block { overflow: hidden; } -.accordion > .card:not(:first-of-type) .card-header:first-child { - border-radius: 0; -} - -.accordion > .card:not(:first-of-type):not(:last-of-type) { - border-bottom: 0; - border-radius: 0; -} - -.accordion > .card:first-of-type { +.accordion > .card:not(:last-of-type) { border-bottom: 0; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } -.accordion > .card:last-of-type { +.accordion > .card:not(:first-of-type) { border-top-left-radius: 0; border-top-right-radius: 0; } -.accordion > .card .card-header { +.accordion > .card > .card-header { + border-radius: 0; margin-bottom: -1px; } @@ -4811,6 +4990,11 @@ input[type="button"].btn-block { border-radius: 0.25rem; } +.breadcrumb-item { + display: -ms-flexbox; + display: flex; +} + .breadcrumb-item + .breadcrumb-item { padding-left: 0.5rem; } @@ -4862,7 +5046,7 @@ input[type="button"].btn-block { } .page-link:focus { - z-index: 2; + z-index: 3; outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } @@ -4879,7 +5063,7 @@ input[type="button"].btn-block { } .page-item.active .page-link { - z-index: 1; + z-index: 3; color: #fff; background-color: #007bff; border-color: #007bff; @@ -5265,6 +5449,7 @@ a.badge-dark:focus, a.badge-dark.focus { display: flex; height: 1rem; overflow: hidden; + line-height: 0; font-size: 0.75rem; background-color: #e9ecef; border-radius: 0.25rem; @@ -5277,6 +5462,7 @@ a.badge-dark:focus, a.badge-dark.focus { flex-direction: column; -ms-flex-pack: center; justify-content: center; + overflow: hidden; color: #fff; text-align: center; white-space: nowrap; @@ -5326,6 +5512,7 @@ a.badge-dark:focus, a.badge-dark.focus { flex-direction: column; padding-left: 0; margin-bottom: 0; + border-radius: 0.25rem; } .list-group-item-action { @@ -5350,20 +5537,18 @@ a.badge-dark:focus, a.badge-dark.focus { position: relative; display: block; padding: 0.75rem 1.25rem; - margin-bottom: -1px; background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.125); } .list-group-item:first-child { - border-top-left-radius: 0.25rem; - border-top-right-radius: 0.25rem; + border-top-left-radius: inherit; + border-top-right-radius: inherit; } .list-group-item:last-child { - margin-bottom: 0; - border-bottom-right-radius: 0.25rem; - border-bottom-left-radius: 0.25rem; + border-bottom-right-radius: inherit; + border-bottom-left-radius: inherit; } .list-group-item.disabled, .list-group-item:disabled { @@ -5379,49 +5564,68 @@ a.badge-dark:focus, a.badge-dark.focus { border-color: #007bff; } +.list-group-item + .list-group-item { + border-top-width: 0; +} + +.list-group-item + .list-group-item.active { + margin-top: -1px; + border-top-width: 1px; +} + .list-group-horizontal { -ms-flex-direction: row; flex-direction: row; } -.list-group-horizontal .list-group-item { - margin-right: -1px; - margin-bottom: 0; -} - -.list-group-horizontal .list-group-item:first-child { - border-top-left-radius: 0.25rem; +.list-group-horizontal > .list-group-item:first-child { border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } -.list-group-horizontal .list-group-item:last-child { - margin-right: 0; +.list-group-horizontal > .list-group-item:last-child { border-top-right-radius: 0.25rem; - border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0; } +.list-group-horizontal > .list-group-item.active { + margin-top: 0; +} + +.list-group-horizontal > .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0; +} + +.list-group-horizontal > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; +} + @media (min-width: 576px) { .list-group-horizontal-sm { -ms-flex-direction: row; flex-direction: row; } - .list-group-horizontal-sm .list-group-item { - margin-right: -1px; - margin-bottom: 0; - } - .list-group-horizontal-sm .list-group-item:first-child { - border-top-left-radius: 0.25rem; + .list-group-horizontal-sm > .list-group-item:first-child { border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } - .list-group-horizontal-sm .list-group-item:last-child { - margin-right: 0; + .list-group-horizontal-sm > .list-group-item:last-child { border-top-right-radius: 0.25rem; - border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0; } + .list-group-horizontal-sm > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-sm > .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0; + } + .list-group-horizontal-sm > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; + } } @media (min-width: 768px) { @@ -5429,21 +5633,25 @@ a.badge-dark:focus, a.badge-dark.focus { -ms-flex-direction: row; flex-direction: row; } - .list-group-horizontal-md .list-group-item { - margin-right: -1px; - margin-bottom: 0; - } - .list-group-horizontal-md .list-group-item:first-child { - border-top-left-radius: 0.25rem; + .list-group-horizontal-md > .list-group-item:first-child { border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } - .list-group-horizontal-md .list-group-item:last-child { - margin-right: 0; + .list-group-horizontal-md > .list-group-item:last-child { border-top-right-radius: 0.25rem; - border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0; } + .list-group-horizontal-md > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-md > .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0; + } + .list-group-horizontal-md > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; + } } @media (min-width: 992px) { @@ -5451,21 +5659,25 @@ a.badge-dark:focus, a.badge-dark.focus { -ms-flex-direction: row; flex-direction: row; } - .list-group-horizontal-lg .list-group-item { - margin-right: -1px; - margin-bottom: 0; - } - .list-group-horizontal-lg .list-group-item:first-child { - border-top-left-radius: 0.25rem; + .list-group-horizontal-lg > .list-group-item:first-child { border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } - .list-group-horizontal-lg .list-group-item:last-child { - margin-right: 0; + .list-group-horizontal-lg > .list-group-item:last-child { border-top-right-radius: 0.25rem; - border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0; } + .list-group-horizontal-lg > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-lg > .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0; + } + .list-group-horizontal-lg > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; + } } @media (min-width: 1200px) { @@ -5473,40 +5685,37 @@ a.badge-dark:focus, a.badge-dark.focus { -ms-flex-direction: row; flex-direction: row; } - .list-group-horizontal-xl .list-group-item { - margin-right: -1px; - margin-bottom: 0; - } - .list-group-horizontal-xl .list-group-item:first-child { - border-top-left-radius: 0.25rem; + .list-group-horizontal-xl > .list-group-item:first-child { border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } - .list-group-horizontal-xl .list-group-item:last-child { - margin-right: 0; + .list-group-horizontal-xl > .list-group-item:last-child { border-top-right-radius: 0.25rem; - border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0; } + .list-group-horizontal-xl > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-xl > .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0; + } + .list-group-horizontal-xl > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; + } } -.list-group-flush .list-group-item { - border-right: 0; - border-left: 0; +.list-group-flush { border-radius: 0; } -.list-group-flush .list-group-item:last-child { - margin-bottom: -1px; +.list-group-flush > .list-group-item { + border-width: 0 0 1px; } -.list-group-flush:first-child .list-group-item:first-child { - border-top: 0; -} - -.list-group-flush:last-child .list-group-item:last-child { - margin-bottom: 0; - border-bottom: 0; +.list-group-flush > .list-group-item:last-child { + border-bottom-width: 0; } .list-group-item-primary { @@ -5660,9 +5869,6 @@ button.close { padding: 0; background-color: transparent; border: 0; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; } a.close.disabled { @@ -5763,6 +5969,11 @@ a.close.disabled { transform: none; } +.modal.modal-static .modal-dialog { + -webkit-transform: scale(1.02); + transform: scale(1.02); +} + .modal-dialog-scrollable { display: -ms-flexbox; display: flex; @@ -5795,6 +6006,9 @@ a.close.disabled { .modal-dialog-centered::before { display: block; height: calc(100vh - 1rem); + height: -webkit-min-content; + height: -moz-min-content; + height: min-content; content: ""; } @@ -5856,8 +6070,8 @@ a.close.disabled { justify-content: space-between; padding: 1rem 1rem; border-bottom: 1px solid #dee2e6; - border-top-left-radius: 0.3rem; - border-top-right-radius: 0.3rem; + border-top-left-radius: calc(0.3rem - 1px); + border-top-right-radius: calc(0.3rem - 1px); } .modal-header .close { @@ -5880,22 +6094,20 @@ a.close.disabled { .modal-footer { display: -ms-flexbox; display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; -ms-flex-align: center; align-items: center; -ms-flex-pack: end; justify-content: flex-end; - padding: 1rem; + padding: 0.75rem; border-top: 1px solid #dee2e6; - border-bottom-right-radius: 0.3rem; - border-bottom-left-radius: 0.3rem; + border-bottom-right-radius: calc(0.3rem - 1px); + border-bottom-left-radius: calc(0.3rem - 1px); } -.modal-footer > :not(:first-child) { - margin-left: .25rem; -} - -.modal-footer > :not(:last-child) { - margin-right: .25rem; +.modal-footer > * { + margin: 0.25rem; } .modal-scrollbar-measure { @@ -5922,6 +6134,9 @@ a.close.disabled { } .modal-dialog-centered::before { height: calc(100vh - 3.5rem); + height: -webkit-min-content; + height: -moz-min-content; + height: min-content; } .modal-sm { max-width: 300px; @@ -6102,7 +6317,7 @@ a.close.disabled { } .bs-popover-top > .arrow, .bs-popover-auto[x-placement^="top"] > .arrow { - bottom: calc((0.5rem + 1px) * -1); + bottom: calc(-0.5rem - 1px); } .bs-popover-top > .arrow::before, .bs-popover-auto[x-placement^="top"] > .arrow::before { @@ -6122,7 +6337,7 @@ a.close.disabled { } .bs-popover-right > .arrow, .bs-popover-auto[x-placement^="right"] > .arrow { - left: calc((0.5rem + 1px) * -1); + left: calc(-0.5rem - 1px); width: 0.5rem; height: 1rem; margin: 0.3rem 0; @@ -6145,7 +6360,7 @@ a.close.disabled { } .bs-popover-bottom > .arrow, .bs-popover-auto[x-placement^="bottom"] > .arrow { - top: calc((0.5rem + 1px) * -1); + top: calc(-0.5rem - 1px); } .bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^="bottom"] > .arrow::before { @@ -6176,7 +6391,7 @@ a.close.disabled { } .bs-popover-left > .arrow, .bs-popover-auto[x-placement^="left"] > .arrow { - right: calc((0.5rem + 1px) * -1); + right: calc(-0.5rem - 1px); width: 0.5rem; height: 1rem; margin: 0.3rem 0; @@ -6289,7 +6504,7 @@ a.close.disabled { .carousel-fade .active.carousel-item-right { z-index: 0; opacity: 0; - transition: 0s 0.6s opacity; + transition: opacity 0s 0.6s; } @media (prefers-reduced-motion: reduce) { @@ -6351,11 +6566,11 @@ a.close.disabled { } .carousel-control-prev-icon { - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e"); } .carousel-control-next-icon { - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e"); } .carousel-indicators { @@ -6453,6 +6668,8 @@ a.close.disabled { } 50% { opacity: 1; + -webkit-transform: none; + transform: none; } } @@ -6463,6 +6680,8 @@ a.close.disabled { } 50% { opacity: 1; + -webkit-transform: none; + transform: none; } } @@ -7748,6 +7967,27 @@ button.bg-dark:focus { } } +.user-select-all { + -webkit-user-select: all !important; + -moz-user-select: all !important; + -ms-user-select: all !important; + user-select: all !important; +} + +.user-select-auto { + -webkit-user-select: auto !important; + -moz-user-select: auto !important; + -ms-user-select: auto !important; + user-select: auto !important; +} + +.user-select-none { + -webkit-user-select: none !important; + -moz-user-select: none !important; + -ms-user-select: none !important; + user-select: none !important; +} + .overflow-auto { overflow: auto !important; } @@ -7807,6 +8047,7 @@ button.bg-dark:focus { width: 1px; height: 1px; padding: 0; + margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; @@ -7902,18 +8143,6 @@ button.bg-dark:focus { height: 100vh !important; } -.stretched-link::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - pointer-events: auto; - content: ""; - background-color: rgba(0, 0, 0, 0); -} - .m-0 { margin: 0 !important; } @@ -9726,6 +9955,18 @@ button.bg-dark:focus { } } +.stretched-link::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + pointer-events: auto; + content: ""; + background-color: rgba(0, 0, 0, 0); +} + .text-monospace { font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important; } @@ -9941,8 +10182,7 @@ a.text-dark:hover, a.text-dark:focus { } .text-break { - word-break: break-word !important; - overflow-wrap: break-word !important; + word-wrap: break-word !important; } .text-reset { @@ -10035,3 +10275,4 @@ a.text-dark:hover, a.text-dark:focus { border-color: #dee2e6; } } +/*# sourceMappingURL=bootstrap.css.map */ +\ No newline at end of file diff --git a/src/static/templates/admin/base.hbs b/src/static/templates/admin/base.hbs @@ -1,40 +1,26 @@ <!DOCTYPE html> <html lang="en"> - <head> - <meta http-equiv="content-type" content="text/html; charset=UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> + <meta name="robots" content="noindex,nofollow" /> <title>Bitwarden_rs Admin Panel</title> - <link rel="stylesheet" href="{{urlpath}}/bwrs_static/bootstrap.css" /> - <script src="{{urlpath}}/bwrs_static/bootstrap-native-v4.js"></script> - <script src="{{urlpath}}/bwrs_static/md5.js"></script> - <script src="{{urlpath}}/bwrs_static/identicon.js"></script> <style> body { - padding-top: 70px; - } - - @media (max-width:768px) { - body { - padding-top: 190px; - } - - .container { - max-width: 100%; - } + padding-top: 75px; } - img { width: 48px; height: 48px; } - .navbar img { height: 24px; width: auto; } </style> + <script src="{{urlpath}}/bwrs_static/md5.js"></script> + <script src="{{urlpath}}/bwrs_static/identicon.js"></script> <script> function reload() { window.location.reload(); } function msg(text, reload_page = true) { @@ -86,47 +72,44 @@ </head> <body class="bg-light"> - <nav class="navbar navbar-expand-sm navbar-dark bg-dark fixed-top shadow mb-4"> - <div class="container"> - <a class="navbar-brand" href="{{urlpath}}/admin"><img class="pr-1" src="{{urlpath}}/bwrs_static/shield-white.png">Bitwarden_rs Admin</a> - <div class="navbar-collapse"> - <ul class="navbar-nav"> - <li class="nav-item"> - <a class="nav-link" href="{{urlpath}}/admin">Settings</a> - </li> - <li class="nav-item"> - <a class="nav-link" href="{{urlpath}}/admin/users/overview">Users</a> - </li> - <li class="nav-item"> - <a class="nav-link" href="{{urlpath}}/admin/organizations/overview">Organizations</a> - </li> - <li class="nav-item"> - <a class="nav-link" href="{{urlpath}}/admin/diagnostics">Diagnostics</a> - </li> - <li class="nav-item"> - <a class="nav-link" href="{{urlpath}}/">Vault</a> - </li> - </ul> - </div> + <nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4 shadow fixed-top"> + <div class="container"> + <a class="navbar-brand" href="{{urlpath}}/admin"><img class="pr-1" src="{{urlpath}}/bwrs_static/shield-white.png">Bitwarden_rs Admin</a> + <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" + aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> + <span class="navbar-toggler-icon"></span> + </button> + <div class="collapse navbar-collapse" id="navbarCollapse"> + <ul class="navbar-nav mr-auto"> + {{#if logged_in}} + <li class="nav-item"> + <a class="nav-link" href="{{urlpath}}/admin">Settings</a> + </li> + <li class="nav-item"> + <a class="nav-link" href="{{urlpath}}/admin/users/overview">Users</a> + </li> + <li class="nav-item"> + <a class="nav-link" href="{{urlpath}}/admin/organizations/overview">Organizations</a> + </li> + <li class="nav-item"> + <a class="nav-link" href="{{urlpath}}/admin/diagnostics">Diagnostics</a> + </li> + {{/if}} + <li class="nav-item"> + <a class="nav-link" href="{{urlpath}}/">Vault</a> + </li> + </ul> - <ul class="navbar-nav"> - {{#if version}} - <li class="nav-item"> - <span class="navbar-text mr-2">Version: {{version}}</span> - </li> - {{/if}} - - {{#if logged_in}} - <li class="nav-item rounded btn-secondary"> - <a class="nav-link" href="{{urlpath}}/admin/logout">Log Out</a> - </li> - {{/if}} - </ul> - </div> + {{#if logged_in}} + <a class="btn btn-sm btn-secondary" href="{{urlpath}}/admin/logout">Log Out</a> + {{/if}} + </div> + </div> </nav> {{> (page_content) }} + <!-- This script needs to be at the bottom, else it will fail! --> <script> // get current URL path and assign 'active' class to the correct nav-item (function () { @@ -138,6 +121,7 @@ } })(); </script> + <!-- This script needs to be at the bottom, else it will fail! --> + <script src="{{urlpath}}/bwrs_static/bootstrap-native-v4.js"></script> </body> - </html> \ No newline at end of file diff --git a/src/static/templates/admin/users.hbs b/src/static/templates/admin/users.hbs @@ -2,57 +2,61 @@ <div id="users-block" class="my-3 p-3 bg-white rounded shadow"> <h6 class="border-bottom pb-2 mb-0">Registered Users</h6> - <div id="users-list"> - {{#each users}} - <div class="media pt-3"> - <img class="mr-2 rounded identicon" data-src="{{Email}}"> - <div class="media-body pb-3 mb-0 small border-bottom"> - <div class="row justify-content-between"> - <div class="col"> + + + <div class="table-responsive-xl small"> + <table class="table table-sm table-striped table-hover"> + <thead> + <tr> + <th style="width: 24px;">User</th> + <th></th> + <th style="width:90px; min-width: 90px;">Items</th> + <th style="min-width: 140px;">Organizations</th> + <th style="width: 140px; min-width: 140px;">Actions</th> + </tr> + </thead> + <tbody> + {{#each users}} + <tr> + <td><img class="mr-2 rounded identicon" data-src="{{Email}}"></td> + <td> <strong>{{Name}}</strong> - {{#if TwoFactorEnabled}} - <span class="badge badge-success ml-2">2FA</span> - {{/if}} - {{#case _Status 1}} - <span class="badge badge-warning ml-2">Invited</span> - {{/case}} - <span class="d-block">{{Email}} + <span class="d-block">{{Email}}</span> + <span class="d-block"> + {{#if TwoFactorEnabled}} + <span class="badge badge-success mr-2" title="2FA is enabled">2FA</span> + {{/if}} + {{#case _Status 1}} + <span class="badge badge-warning mr-2" title="User is invited">Invited</span> + {{/case}} {{#if EmailVerified}} - <span class="badge badge-success ml-2">Verified</span> + <span class="badge badge-success mr-2" title="Email has been verified">Verified</span> {{/if}} </span> - </div> - <div class="col"> - <strong> Personal Items: </strong> - <span class="d-block"> - {{cipher_count}} - </span> - </div> - <div class="col-4"> - <strong> Organizations: </strong> - <span class="d-block"> - {{#each Organizations}} - <span class="badge badge-primary" data-orgtype="{{Type}}">{{Name}}</span> - {{/each}} - </span> - </div> - <div class="col" style="font-size: 90%; text-align: right; padding-right: 15px"> + </td> + <td> + <span class="d-block">{{cipher_count}}</span> + </td> + <td> + {{#each Organizations}} + <span class="badge badge-primary" data-orgtype="{{Type}}">{{Name}}</span> + {{/each}} + </td> + <td style="font-size: 90%; text-align: right; padding-right: 15px"> {{#if TwoFactorEnabled}} - <a class="mr-2" href="#" onclick='remove2fa({{jsesc Id}})'>Remove all 2FA</a> + <a class="d-block" href="#" onclick='remove2fa({{jsesc Id}})'>Remove all 2FA</a> {{/if}} - - <a class="mr-2" href="#" onclick='deauthUser({{jsesc Id}})'>Deauthorize sessions</a> - <a class="mr-2" href="#" onclick='deleteUser({{jsesc Id}}, {{jsesc Email}})'>Delete User</a> - </div> - </div> - </div> - </div> - {{/each}} - + <a class="d-block" href="#" onclick='deauthUser({{jsesc Id}})'>Deauthorize sessions</a> + <a class="d-block" href="#" onclick='deleteUser({{jsesc Id}}, {{jsesc Email}})'>Delete User</a> + </td> + </tr> + {{/each}} + </tbody> + </table> </div> <div class="mt-3"> - <button type="button" class="btn btn-sm btn-link" onclick="updateRevisions();" + <button type="button" class="btn btn-sm btn-danger" onclick="updateRevisions();" title="Force all clients to fetch new data next time they connect. Useful after restoring a backup to remove any stale data."> Force clients to resync </button>