From 0225bdb772d1334cc1aa7ab0fc3678df0864df6b Mon Sep 17 00:00:00 2001 From: AlisaLinUwU Date: Sun, 26 Jan 2025 10:42:28 +0500 Subject: Initialize --- .../overlayScrollbars/css/OverlayScrollbars.css | 635 ++ .../css/OverlayScrollbars.min.css | 13 + .../overlayScrollbars/js/OverlayScrollbars.js | 6661 ++++++++++++++++++++ .../overlayScrollbars/js/OverlayScrollbars.min.js | 13 + .../js/jquery.overlayScrollbars.js | 5578 ++++++++++++++++ .../js/jquery.overlayScrollbars.min.js | 13 + 6 files changed, 12913 insertions(+) create mode 100644 src/main/resources/static/plugins/overlayScrollbars/css/OverlayScrollbars.css create mode 100644 src/main/resources/static/plugins/overlayScrollbars/css/OverlayScrollbars.min.css create mode 100644 src/main/resources/static/plugins/overlayScrollbars/js/OverlayScrollbars.js create mode 100644 src/main/resources/static/plugins/overlayScrollbars/js/OverlayScrollbars.min.js create mode 100644 src/main/resources/static/plugins/overlayScrollbars/js/jquery.overlayScrollbars.js create mode 100644 src/main/resources/static/plugins/overlayScrollbars/js/jquery.overlayScrollbars.min.js (limited to 'src/main/resources/static/plugins/overlayScrollbars') diff --git a/src/main/resources/static/plugins/overlayScrollbars/css/OverlayScrollbars.css b/src/main/resources/static/plugins/overlayScrollbars/css/OverlayScrollbars.css new file mode 100644 index 0000000..dc9f812 --- /dev/null +++ b/src/main/resources/static/plugins/overlayScrollbars/css/OverlayScrollbars.css @@ -0,0 +1,635 @@ +/*! + * OverlayScrollbars + * https://github.com/KingSora/OverlayScrollbars + * + * Version: 1.13.0 + * + * Copyright KingSora | Rene Haas. + * https://github.com/KingSora + * + * Released under the MIT license. + * Date: 02.08.2020 + */ + +/* +OVERLAY SCROLLBARS CORE: +*/ + +html.os-html, +html.os-html > .os-host { + display: block; + overflow: hidden; + box-sizing: border-box; + height: 100% !important; + width: 100% !important; + min-width: 100% !important; + min-height: 100% !important; + margin: 0 !important; + position: absolute !important; /* could be position: fixed; but it causes issues on iOS (-webkit-overflow-scrolling: touch) */ +} +html.os-html > .os-host > .os-padding { + position: absolute; /* could be position: fixed; but it causes issues on iOS (-webkit-overflow-scrolling: touch) */ +} +body.os-dragging, +body.os-dragging * { + cursor: default; +} +.os-host, +.os-host-textarea { + position: relative; + overflow: visible !important; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + -ms-flex-line-pack: start; + align-content: flex-start; + -webkit-box-align: start; + -ms-flex-align: start; + -ms-grid-row-align: flex-start; + align-items: flex-start; +} +.os-host-flexbox { + overflow: hidden !important; + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} +.os-host-flexbox > .os-size-auto-observer { + height: inherit !important; +} +.os-host-flexbox > .os-content-glue { + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -ms-flex-negative: 0; + flex-shrink: 0; +} +.os-host-flexbox > .os-size-auto-observer, +.os-host-flexbox > .os-content-glue { + min-height: 0; + min-width: 0; + -webkit-box-flex: 0; + -ms-flex-positive: 0; + flex-grow: 0; + -ms-flex-negative: 1; + flex-shrink: 1; + -ms-flex-preferred-size: auto; + flex-basis: auto; +} +#os-dummy-scrollbar-size { + position: fixed; + opacity: 0; + -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=0)'; + visibility: hidden; + overflow: scroll; + height: 500px; + width: 500px; +} +#os-dummy-scrollbar-size > div { + width: 200%; + height: 200%; + margin: 10px 0; +} +/* fix restricted measuring */ +#os-dummy-scrollbar-size:before, +#os-dummy-scrollbar-size:after, +.os-content:before, +.os-content:after { + content: ''; + display: table; + width: 0.01px; + height: 0.01px; + line-height: 0; + font-size: 0; + flex-grow: 0; + flex-shrink: 0; + visibility: hidden; +} +#os-dummy-scrollbar-size, +.os-viewport { + -ms-overflow-style: scrollbar !important; +} +.os-viewport-native-scrollbars-invisible#os-dummy-scrollbar-size, +.os-viewport-native-scrollbars-invisible.os-viewport { + scrollbar-width: none !important; +} +.os-viewport-native-scrollbars-invisible#os-dummy-scrollbar-size::-webkit-scrollbar, +.os-viewport-native-scrollbars-invisible.os-viewport::-webkit-scrollbar, +.os-viewport-native-scrollbars-invisible#os-dummy-scrollbar-size::-webkit-scrollbar-corner, +.os-viewport-native-scrollbars-invisible.os-viewport::-webkit-scrollbar-corner { + display: none !important; + width: 0px !important; + height: 0px !important; + visibility: hidden !important; + background: transparent !important; +} +.os-content-glue { + box-sizing: inherit; + max-height: 100%; + max-width: 100%; + width: 100%; + pointer-events: none; +} +.os-padding { + box-sizing: inherit; + direction: inherit; + position: absolute; + overflow: visible; + padding: 0; + margin: 0; + left: 0; + top: 0; + bottom: 0; + right: 0; + width: auto !important; + height: auto !important; + z-index: 0; +} +.os-host-overflow > .os-padding { + overflow: hidden; +} +.os-viewport { + direction: inherit !important; + box-sizing: inherit !important; + resize: none !important; + outline: none !important; + position: absolute; + overflow: hidden; + top: 0; + left: 0; + bottom: 0; + right: 0; + padding: 0; + margin: 0; + -webkit-overflow-scrolling: touch; +} +.os-content-arrange { + position: absolute; + z-index: -1; + min-height: 1px; + min-width: 1px; + pointer-events: none; +} +.os-content { + direction: inherit; + box-sizing: border-box !important; + position: relative; + display: block; + height: 100%; + width: 100%; + height: 100%; + width: 100%; + visibility: visible; +} +.os-content > .os-textarea { + box-sizing: border-box !important; + direction: inherit !important; + background: transparent !important; + outline: 0px none transparent !important; + overflow: hidden !important; + position: absolute !important; + display: block !important; + top: 0 !important; + left: 0 !important; + margin: 0 !important; + border-radius: 0px !important; + float: none !important; + -webkit-filter: none !important; + filter: none !important; + border: none !important; + resize: none !important; + -webkit-transform: none !important; + transform: none !important; + max-width: none !important; + max-height: none !important; + box-shadow: none !important; + -webkit-perspective: none !important; + perspective: none !important; + opacity: 1 !important; + z-index: 1 !important; + clip: auto !important; + vertical-align: baseline !important; + padding: 0px; +} +.os-host-rtl > .os-padding > .os-viewport > .os-content > .os-textarea { + right: 0 !important; +} +.os-content > .os-textarea-cover { + z-index: -1; + pointer-events: none; +} +.os-content > .os-textarea[wrap='off'] { + white-space: pre !important; + margin: 0px !important; +} +.os-text-inherit { + font-family: inherit; + font-size: inherit; + font-weight: inherit; + font-style: inherit; + font-variant: inherit; + text-transform: inherit; + text-decoration: inherit; + text-indent: inherit; + text-align: inherit; + text-shadow: inherit; + text-overflow: inherit; + letter-spacing: inherit; + word-spacing: inherit; + line-height: inherit; + unicode-bidi: inherit; + direction: inherit; + color: inherit; + cursor: text; +} +.os-resize-observer, +.os-resize-observer-host { + box-sizing: inherit; + display: block; + visibility: hidden; + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + overflow: hidden; + pointer-events: none; + z-index: -1; +} +.os-resize-observer-host { + padding: inherit; + border: inherit; + border-color: transparent; + border-style: solid; + box-sizing: border-box; +} +.os-resize-observer-host.observed { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; +} +.os-resize-observer-host > .os-resize-observer, +.os-resize-observer-host.observed > .os-resize-observer { + height: 200%; + width: 200%; + padding: inherit; + border: inherit; + margin: 0; + display: block; + box-sizing: content-box; +} +.os-resize-observer-host.observed > .os-resize-observer, +.os-resize-observer-host.observed > .os-resize-observer:before { + display: flex; + position: relative; + flex-grow: 1; + flex-shrink: 0; + flex-basis: auto; + box-sizing: border-box; +} +.os-resize-observer-host.observed > .os-resize-observer:before { + content: ''; + box-sizing: content-box; + padding: inherit; + border: inherit; + margin: 0; +} +.os-size-auto-observer { + box-sizing: inherit !important; + height: 100%; + width: inherit; + max-width: 1px; + position: relative; + float: left; + max-height: 1px; + overflow: hidden; + z-index: -1; + padding: 0; + margin: 0; + pointer-events: none; + -webkit-box-flex: inherit; + -ms-flex-positive: inherit; + flex-grow: inherit; + -ms-flex-negative: 0; + flex-shrink: 0; + -ms-flex-preferred-size: 0; + flex-basis: 0; +} +.os-size-auto-observer > .os-resize-observer { + width: 1000%; + height: 1000%; + min-height: 1px; + min-width: 1px; +} +.os-resize-observer-item { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + overflow: hidden; + z-index: -1; + opacity: 0; + direction: ltr !important; + -webkit-box-flex: 0 !important; + -ms-flex: none !important; + flex: none !important; +} +.os-resize-observer-item-final { + position: absolute; + left: 0; + top: 0; + -webkit-transition: none !important; + transition: none !important; + -webkit-box-flex: 0 !important; + -ms-flex: none !important; + flex: none !important; +} +.os-resize-observer { + -webkit-animation-duration: 0.001s; + animation-duration: 0.001s; + -webkit-animation-name: os-resize-observer-dummy-animation; + animation-name: os-resize-observer-dummy-animation; +} +object.os-resize-observer { + box-sizing: border-box !important; +} +@-webkit-keyframes os-resize-observer-dummy-animation { + from { + z-index: 0; + } + to { + z-index: -1; + } +} +@keyframes os-resize-observer-dummy-animation { + from { + z-index: 0; + } + to { + z-index: -1; + } +} + +/* +CUSTOM SCROLLBARS AND CORNER CORE: +*/ + +.os-host-transition > .os-scrollbar, +.os-host-transition > .os-scrollbar-corner { + -webkit-transition: opacity 0.3s, visibility 0.3s, top 0.3s, right 0.3s, bottom 0.3s, left 0.3s; + transition: opacity 0.3s, visibility 0.3s, top 0.3s, right 0.3s, bottom 0.3s, left 0.3s; +} +html.os-html > .os-host > .os-scrollbar { + position: absolute; /* could be position: fixed; but it causes issues on iOS (-webkit-overflow-scrolling: touch) */ + z-index: 999999; /* highest z-index of the page */ +} +.os-scrollbar, +.os-scrollbar-corner { + position: absolute; + opacity: 1; + -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)'; + z-index: 1; +} +.os-scrollbar-corner { + bottom: 0; + right: 0; +} +.os-scrollbar { + pointer-events: none; +} +.os-scrollbar-track { + pointer-events: auto; + position: relative; + height: 100%; + width: 100%; + padding: 0 !important; + border: none !important; +} +.os-scrollbar-handle { + pointer-events: auto; + position: absolute; + width: 100%; + height: 100%; +} +.os-scrollbar-handle-off, +.os-scrollbar-track-off { + pointer-events: none; +} +.os-scrollbar.os-scrollbar-unusable, +.os-scrollbar.os-scrollbar-unusable * { + pointer-events: none !important; +} +.os-scrollbar.os-scrollbar-unusable .os-scrollbar-handle { + opacity: 0 !important; +} +.os-scrollbar-horizontal { + bottom: 0; + left: 0; +} +.os-scrollbar-vertical { + top: 0; + right: 0; +} +.os-host-rtl > .os-scrollbar-horizontal { + right: 0; +} +.os-host-rtl > .os-scrollbar-vertical { + right: auto; + left: 0; +} +.os-host-rtl > .os-scrollbar-corner { + right: auto; + left: 0; +} +.os-scrollbar-auto-hidden, +.os-padding + .os-scrollbar-corner, +.os-host-resize-disabled.os-host-scrollbar-horizontal-hidden > .os-scrollbar-corner, +.os-host-scrollbar-horizontal-hidden > .os-scrollbar-horizontal, +.os-host-resize-disabled.os-host-scrollbar-vertical-hidden > .os-scrollbar-corner, +.os-host-scrollbar-vertical-hidden > .os-scrollbar-vertical, +.os-scrollbar-horizontal.os-scrollbar-auto-hidden + .os-scrollbar-vertical + .os-scrollbar-corner, +.os-scrollbar-horizontal + .os-scrollbar-vertical.os-scrollbar-auto-hidden + .os-scrollbar-corner, +.os-scrollbar-horizontal.os-scrollbar-auto-hidden + .os-scrollbar-vertical.os-scrollbar-auto-hidden + .os-scrollbar-corner { + opacity: 0; + visibility: hidden; + pointer-events: none; +} +.os-scrollbar-corner-resize-both { + cursor: nwse-resize; +} +.os-host-rtl > .os-scrollbar-corner-resize-both { + cursor: nesw-resize; +} +.os-scrollbar-corner-resize-horizontal { + cursor: ew-resize; +} +.os-scrollbar-corner-resize-vertical { + cursor: ns-resize; +} +.os-dragging .os-scrollbar-corner.os-scrollbar-corner-resize { + cursor: default; +} +.os-host-resize-disabled.os-host-scrollbar-horizontal-hidden > .os-scrollbar-vertical { + top: 0; + bottom: 0; +} +.os-host-resize-disabled.os-host-scrollbar-vertical-hidden > .os-scrollbar-horizontal, +.os-host-rtl.os-host-resize-disabled.os-host-scrollbar-vertical-hidden > .os-scrollbar-horizontal { + right: 0; + left: 0; +} +.os-scrollbar:hover, +.os-scrollbar-corner.os-scrollbar-corner-resize { + opacity: 1 !important; + visibility: visible !important; +} +.os-scrollbar-corner.os-scrollbar-corner-resize { + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB3aWR0aD0iMTAiICAgaGVpZ2h0PSIxMCIgICB2ZXJzaW9uPSIxLjEiPiAgPGcgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsLTEwNDIuMzYyMikiICAgICBzdHlsZT0iZGlzcGxheTppbmxpbmUiPiAgICA8cGF0aCAgICAgICBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjQ5NDExNzY1O2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTpub25lIiAgICAgICBkPSJtIDcuNDI0MjE4NywxMDQyLjM2MjIgYyAtMC43MjM1NzkyLDAgLTEuMzEwMTU2MiwwLjU4NjYgLTEuMzEwMTU2MiwxLjMxMDIgMCwwLjI5OSAwLjEwNDM0MTksMC41NzEgMC4yNzI5NDkyLDAuNzkxNSAwLjIwOTEwMjQsMC4xNDEzIDAuNDY1NjIwNiwwLjIxODQgMC43MzY5NjI5LDAuMjE4NCAwLjcyMzU3OTMsMCAxLjMxMDE1NjMsLTAuNTg2NiAxLjMxMDE1NjMsLTEuMzEwMiAwLC0wLjI3MTMgLTAuMDc3MDkzLC0wLjUyNzggLTAuMjE4MzU5NCwtMC43MzcgLTAuMjIwNDk0MSwtMC4xNjg2IC0wLjQ5MjU0NDMsLTAuMjcyOSAtMC43OTE1NTI4LC0wLjI3MjkgeiBtIDAsMy4wODQzIGMgLTAuNzIzNTc5MiwwIC0xLjMxMDE1NjIsMC41ODY2IC0xLjMxMDE1NjIsMS4zMTAyIDAsMC4yOTkgMC4xMDQzNDE5LDAuNTcxIDAuMjcyOTQ5MiwwLjc5MTUgMC4yMDkxMDI0LDAuMTQxMyAwLjQ2NTYyMDYsMC4yMTg0IDAuNzM2OTYyOSwwLjIxODQgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjYgMS4zMTAxNTYzLC0xLjMxMDIgMCwtMC4yNzEzIC0wLjA3NzA5MywtMC41Mjc4IC0wLjIxODM1OTQsLTAuNzM2OSAtMC4yMjA0OTQxLC0wLjE2ODYgLTAuNDkyNTQ0MywtMC4yNzMgLTAuNzkxNTUyOCwtMC4yNzMgeiBtIC0zLjA4NDMyNjEsMCBjIC0wLjcyMzU3OTMsMCAtMS4zMTAxNTYzLDAuNTg2NiAtMS4zMTAxNTYzLDEuMzEwMiAwLDAuMjk5IDAuMTA0MzQxOSwwLjU3MSAwLjI3Mjk0OTIsMC43OTE1IDAuMjA5MTAyNCwwLjE0MTMgMC40NjU2MjA3LDAuMjE4NCAwLjczNjk2MjksMC4yMTg0IDAuNzIzNTc5MywwIDEuMzEwMTU2MywtMC41ODY2IDEuMzEwMTU2MywtMS4zMTAyIDAsLTAuMjcxMyAtMC4wNzcwOTMsLTAuNTI3OCAtMC4yMTgzNTk0LC0wLjczNjkgLTAuMjIwNDk0LC0wLjE2ODYgLTAuNDkyNTQ0MiwtMC4yNzMgLTAuNzkxNTUyNywtMC4yNzMgeiBtIC0zLjAyOTczNjQsMy4wMjk4IEMgMC41ODY1NzY5MywxMDQ4LjQ3NjMgMCwxMDQ5LjA2MjggMCwxMDQ5Ljc4NjQgYyAwLDAuMjk5IDAuMTA0MzQxOSwwLjU3MTEgMC4yNzI5NDkyMiwwLjc5MTYgMC4yMDkxMDIyOSwwLjE0MTIgMC40NjU2MjA2NSwwLjIxODMgMC43MzY5NjI4OCwwLjIxODMgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjUgMS4zMTAxNTYzLC0xLjMxMDEgMCwtMC4yNzE0IC0wLjA3NzA5MywtMC41Mjc5IC0wLjIxODM1OTQsLTAuNzM3IC0wLjIyMDQ5NDEsLTAuMTY4NiAtMC40OTI1NDQzLC0wLjI3MjkgLTAuNzkxNTUyOCwtMC4yNzI5IHogbSAzLjAyOTczNjQsMCBjIC0wLjcyMzU3OTMsMCAtMS4zMTAxNTYzLDAuNTg2NSAtMS4zMTAxNTYzLDEuMzEwMSAwLDAuMjk5IDAuMTA0MzQxOSwwLjU3MTEgMC4yNzI5NDkyLDAuNzkxNiAwLjIwOTEwMjQsMC4xNDEyIDAuNDY1NjIwNywwLjIxODMgMC43MzY5NjI5LDAuMjE4MyAwLjcyMzU3OTMsMCAxLjMxMDE1NjMsLTAuNTg2NSAxLjMxMDE1NjMsLTEuMzEwMSAwLC0wLjI3MTQgLTAuMDc3MDkzLC0wLjUyNzkgLTAuMjE4MzU5NCwtMC43MzcgLTAuMjIwNDk0LC0wLjE2ODYgLTAuNDkyNTQ0MiwtMC4yNzI5IC0wLjc5MTU1MjcsLTAuMjcyOSB6IG0gMy4wODQzMjYxLDAgYyAtMC43MjM1NzkyLDAgLTEuMzEwMTU2MiwwLjU4NjUgLTEuMzEwMTU2MiwxLjMxMDEgMCwwLjI5OSAwLjEwNDM0MTksMC41NzExIDAuMjcyOTQ5MiwwLjc5MTYgMC4yMDkxMDI0LDAuMTQxMiAwLjQ2NTYyMDYsMC4yMTgzIDAuNzM2OTYyOSwwLjIxODMgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjUgMS4zMTAxNTYzLC0xLjMxMDEgMCwtMC4yNzE0IC0wLjA3NzA5MywtMC41Mjc5IC0wLjIxODM1OTQsLTAuNzM3IC0wLjIyMDQ5NDEsLTAuMTY4NiAtMC40OTI1NDQzLC0wLjI3MjkgLTAuNzkxNTUyOCwtMC4yNzI5IHoiLz4gIDwvZz4gIDxnICAgICBzdHlsZT0iZGlzcGxheTppbmxpbmUiPiAgICA8cGF0aCAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTpub25lIiAgICAgICBkPSJtIDguMjE1NzcxNSwwLjI3Mjk0OTIyIGMgMC4xNDEyNjY3LDAuMjA5MTAyMjkgMC4yMTgzNTk0LDAuNDY1NjIwNjUgMC4yMTgzNTk0LDAuNzM2OTYyODggMCwwLjcyMzU3OTMgLTAuNTg2NTc3LDEuMzEwMTU2MyAtMS4zMTAxNTYzLDEuMzEwMTU2MyAtMC4yNzEzNDIzLDAgLTAuNTI3ODYwNSwtMC4wNzcwOTMgLTAuNzM2OTYyOSwtMC4yMTgzNTk0IDAuMjM5NDEwNCwwLjMxMzA4NTkgMC42MTI2MzYyLDAuNTE4NjAzNSAxLjAzNzIwNywwLjUxODYwMzUgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjU3NyAxLjMxMDE1NjMsLTEuMzEwMTU2MyAwLC0wLjQyNDU3MDc2IC0wLjIwNTUxNzYsLTAuNzk3Nzk2NTkgLTAuNTE4NjAzNSwtMS4wMzcyMDY5OCB6IG0gMCwzLjA4NDMyNjE4IGMgMC4xNDEyNjY3LDAuMjA5MTAyMyAwLjIxODM1OTQsMC40NjU2MjA2IDAuMjE4MzU5NCwwLjczNjk2MjkgMCwwLjcyMzU3OTMgLTAuNTg2NTc3LDEuMzEwMTU2MiAtMS4zMTAxNTYzLDEuMzEwMTU2MiAtMC4yNzEzNDIzLDAgLTAuNTI3ODYwNSwtMC4wNzcwOTMgLTAuNzM2OTYyOSwtMC4yMTgzNTkzIDAuMjM5NDEwNCwwLjMxMzA4NTkgMC42MTI2MzYyLDAuNTE4NjAzNSAxLjAzNzIwNywwLjUxODYwMzUgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjU3NyAxLjMxMDE1NjMsLTEuMzEwMTU2MyAwLC0wLjQyNDU3MDggLTAuMjA1NTE3NiwtMC43OTc3OTY3IC0wLjUxODYwMzUsLTEuMDM3MjA3IHogbSAtMy4wODQzMjYyLDAgYyAwLjE0MTI2NjcsMC4yMDkxMDIzIDAuMjE4MzU5NCwwLjQ2NTYyMDYgMC4yMTgzNTk0LDAuNzM2OTYyOSAwLDAuNzIzNTc5MyAtMC41ODY1NzcsMS4zMTAxNTYyIC0xLjMxMDE1NjMsMS4zMTAxNTYyIC0wLjI3MTM0MjIsMCAtMC41Mjc4NjA1LC0wLjA3NzA5MyAtMC43MzY5NjI5LC0wLjIxODM1OTMgMC4yMzk0MTA0LDAuMzEzMDg1OSAwLjYxMjYzNjMsMC41MTg2MDM1IDEuMDM3MjA3MSwwLjUxODYwMzUgMC43MjM1NzkzLDAgMS4zMTAxNTYyLC0wLjU4NjU3NyAxLjMxMDE1NjIsLTEuMzEwMTU2MyAwLC0wLjQyNDU3MDggLTAuMjA1NTE3NSwtMC43OTc3OTY3IC0wLjUxODYwMzUsLTEuMDM3MjA3IHogTSAyLjEwMTcwOSw2LjM4NzAxMTcgYyAwLjE0MTI2NjcsMC4yMDkxMDI0IDAuMjE4MzU5NCwwLjQ2NTYyMDYgMC4yMTgzNTk0LDAuNzM2OTYyOSAwLDAuNzIzNTc5MyAtMC41ODY1NzcsMS4zMTAxNTYzIC0xLjMxMDE1NjMsMS4zMTAxNTYzIC0wLjI3MTM0MjIzLDAgLTAuNTI3ODYwNTksLTAuMDc3MDkzIC0wLjczNjk2Mjg4LC0wLjIxODM1OTQgMC4yMzk0MTAzOSwwLjMxMzA4NTkgMC42MTI2MzYyMiwwLjUxODYwMzUgMS4wMzcyMDY5OCwwLjUxODYwMzUgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjU3NyAxLjMxMDE1NjMsLTEuMzEwMTU2MyAwLC0wLjQyNDU3MDggLTAuMjA1NTE3NiwtMC43OTc3OTY2IC0wLjUxODYwMzUsLTEuMDM3MjA3IHogbSAzLjAyOTczNjMsMCBjIDAuMTQxMjY2NywwLjIwOTEwMjQgMC4yMTgzNTk0LDAuNDY1NjIwNiAwLjIxODM1OTQsMC43MzY5NjI5IDAsMC43MjM1NzkzIC0wLjU4NjU3NywxLjMxMDE1NjMgLTEuMzEwMTU2MywxLjMxMDE1NjMgLTAuMjcxMzQyMiwwIC0wLjUyNzg2MDUsLTAuMDc3MDkzIC0wLjczNjk2MjksLTAuMjE4MzU5NCAwLjIzOTQxMDQsMC4zMTMwODU5IDAuNjEyNjM2MywwLjUxODYwMzUgMS4wMzcyMDcxLDAuNTE4NjAzNSAwLjcyMzU3OTMsMCAxLjMxMDE1NjIsLTAuNTg2NTc3IDEuMzEwMTU2MiwtMS4zMTAxNTYzIDAsLTAuNDI0NTcwOCAtMC4yMDU1MTc1LC0wLjc5Nzc5NjYgLTAuNTE4NjAzNSwtMS4wMzcyMDcgeiBtIDMuMDg0MzI2MiwwIGMgMC4xNDEyNjY3LDAuMjA5MTAyNCAwLjIxODM1OTQsMC40NjU2MjA2IDAuMjE4MzU5NCwwLjczNjk2MjkgMCwwLjcyMzU3OTMgLTAuNTg2NTc3LDEuMzEwMTU2MyAtMS4zMTAxNTYzLDEuMzEwMTU2MyAtMC4yNzEzNDIzLDAgLTAuNTI3ODYwNSwtMC4wNzcwOTMgLTAuNzM2OTYyOSwtMC4yMTgzNTk0IDAuMjM5NDEwNCwwLjMxMzA4NTkgMC42MTI2MzYyLDAuNTE4NjAzNSAxLjAzNzIwNywwLjUxODYwMzUgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjU3NyAxLjMxMDE1NjMsLTEuMzEwMTU2MyAwLC0wLjQyNDU3MDggLTAuMjA1NTE3NiwtMC43OTc3OTY2IC0wLjUxODYwMzUsLTEuMDM3MjA3IHoiIC8+ICA8L2c+PC9zdmc+); + background-repeat: no-repeat; + background-position: 100% 100%; + pointer-events: auto !important; +} +.os-host-rtl > .os-scrollbar-corner.os-scrollbar-corner-resize { + -webkit-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.os-host-overflow { + overflow: hidden !important; +} +.os-host-overflow-x { +} +.os-host-overflow-y { +} + +/* +THEMES: +*/ + +/* NONE THEME: */ +.os-theme-none > .os-scrollbar-horizontal, +.os-theme-none > .os-scrollbar-vertical, +.os-theme-none > .os-scrollbar-corner { + display: none !important; +} +.os-theme-none > .os-scrollbar-corner-resize { + display: block !important; + min-width: 10px; + min-height: 10px; +} +/* DARK & LIGHT THEME: */ +.os-theme-dark > .os-scrollbar-horizontal, +.os-theme-light > .os-scrollbar-horizontal { + right: 10px; + height: 10px; +} +.os-theme-dark > .os-scrollbar-vertical, +.os-theme-light > .os-scrollbar-vertical { + bottom: 10px; + width: 10px; +} +.os-theme-dark.os-host-rtl > .os-scrollbar-horizontal, +.os-theme-light.os-host-rtl > .os-scrollbar-horizontal { + left: 10px; + right: 0; +} +.os-theme-dark > .os-scrollbar-corner, +.os-theme-light > .os-scrollbar-corner { + height: 10px; + width: 10px; +} +.os-theme-dark > .os-scrollbar-corner, +.os-theme-light > .os-scrollbar-corner { + background-color: transparent; +} +.os-theme-dark > .os-scrollbar, +.os-theme-light > .os-scrollbar { + padding: 2px; + box-sizing: border-box; + background: transparent; +} +.os-theme-dark > .os-scrollbar.os-scrollbar-unusable, +.os-theme-light > .os-scrollbar.os-scrollbar-unusable { + background: transparent; +} +.os-theme-dark > .os-scrollbar > .os-scrollbar-track, +.os-theme-light > .os-scrollbar > .os-scrollbar-track { + background: transparent; +} +.os-theme-dark > .os-scrollbar-horizontal > .os-scrollbar-track > .os-scrollbar-handle, +.os-theme-light > .os-scrollbar-horizontal > .os-scrollbar-track > .os-scrollbar-handle { + min-width: 30px; +} +.os-theme-dark > .os-scrollbar-vertical > .os-scrollbar-track > .os-scrollbar-handle, +.os-theme-light > .os-scrollbar-vertical > .os-scrollbar-track > .os-scrollbar-handle { + min-height: 30px; +} +.os-theme-dark.os-host-transition > .os-scrollbar > .os-scrollbar-track > .os-scrollbar-handle, +.os-theme-light.os-host-transition > .os-scrollbar > .os-scrollbar-track > .os-scrollbar-handle { + -webkit-transition: background-color 0.3s; + transition: background-color 0.3s; +} +.os-theme-dark > .os-scrollbar > .os-scrollbar-track > .os-scrollbar-handle, +.os-theme-light > .os-scrollbar > .os-scrollbar-track > .os-scrollbar-handle, +.os-theme-dark > .os-scrollbar > .os-scrollbar-track, +.os-theme-light > .os-scrollbar > .os-scrollbar-track { + border-radius: 10px; +} +.os-theme-dark > .os-scrollbar > .os-scrollbar-track > .os-scrollbar-handle { + background: rgba(0, 0, 0, 0.4); +} +.os-theme-light > .os-scrollbar > .os-scrollbar-track > .os-scrollbar-handle { + background: rgba(255, 255, 255, 0.4); +} +.os-theme-dark > .os-scrollbar:hover > .os-scrollbar-track > .os-scrollbar-handle { + background: rgba(0, 0, 0, .55); +} +.os-theme-light > .os-scrollbar:hover > .os-scrollbar-track > .os-scrollbar-handle { + background: rgba(255, 255, 255, .55); +} +.os-theme-dark > .os-scrollbar > .os-scrollbar-track > .os-scrollbar-handle.active { + background: rgba(0, 0, 0, .7); +} +.os-theme-light > .os-scrollbar > .os-scrollbar-track > .os-scrollbar-handle.active { + background: rgba(255, 255, 255, .7); +} +.os-theme-dark > .os-scrollbar-horizontal .os-scrollbar-handle:before, +.os-theme-dark > .os-scrollbar-vertical .os-scrollbar-handle:before, +.os-theme-light > .os-scrollbar-horizontal .os-scrollbar-handle:before, +.os-theme-light > .os-scrollbar-vertical .os-scrollbar-handle:before { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + display: block; +} +.os-theme-dark.os-host-scrollbar-horizontal-hidden > .os-scrollbar-horizontal .os-scrollbar-handle:before, +.os-theme-dark.os-host-scrollbar-vertical-hidden > .os-scrollbar-vertical .os-scrollbar-handle:before, +.os-theme-light.os-host-scrollbar-horizontal-hidden > .os-scrollbar-horizontal .os-scrollbar-handle:before, +.os-theme-light.os-host-scrollbar-vertical-hidden > .os-scrollbar-vertical .os-scrollbar-handle:before { + display: none; +} +.os-theme-dark > .os-scrollbar-horizontal .os-scrollbar-handle:before, +.os-theme-light > .os-scrollbar-horizontal .os-scrollbar-handle:before { + top: -6px; + bottom: -2px; +} +.os-theme-dark > .os-scrollbar-vertical .os-scrollbar-handle:before, +.os-theme-light > .os-scrollbar-vertical .os-scrollbar-handle:before { + left: -6px; + right: -2px; +} +.os-host-rtl.os-theme-dark > .os-scrollbar-vertical .os-scrollbar-handle:before, +.os-host-rtl.os-theme-light > .os-scrollbar-vertical .os-scrollbar-handle:before { + right: -6px; + left: -2px; +} diff --git a/src/main/resources/static/plugins/overlayScrollbars/css/OverlayScrollbars.min.css b/src/main/resources/static/plugins/overlayScrollbars/css/OverlayScrollbars.min.css new file mode 100644 index 0000000..d577690 --- /dev/null +++ b/src/main/resources/static/plugins/overlayScrollbars/css/OverlayScrollbars.min.css @@ -0,0 +1,13 @@ +/*! + * OverlayScrollbars + * https://github.com/KingSora/OverlayScrollbars + * + * Version: 1.13.0 + * + * Copyright KingSora | Rene Haas. + * https://github.com/KingSora + * + * Released under the MIT license. + * Date: 02.08.2020 + */ +html.os-html,html.os-html>.os-host{display:block;overflow:hidden;box-sizing:border-box;height:100%!important;width:100%!important;min-width:100%!important;min-height:100%!important;margin:0!important;position:absolute!important}html.os-html>.os-host>.os-padding{position:absolute}body.os-dragging,body.os-dragging *{cursor:default}.os-host,.os-host-textarea{position:relative;overflow:visible!important;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-box-align:start;-ms-flex-align:start;-ms-grid-row-align:flex-start;align-items:flex-start}.os-host-flexbox{overflow:hidden!important;display:-webkit-box;display:-ms-flexbox;display:flex}.os-host-flexbox>.os-size-auto-observer{height:inherit!important}.os-host-flexbox>.os-content-glue{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-negative:0;flex-shrink:0}.os-host-flexbox>.os-size-auto-observer,.os-host-flexbox>.os-content-glue{min-height:0;min-width:0;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:1;flex-shrink:1;-ms-flex-preferred-size:auto;flex-basis:auto}#os-dummy-scrollbar-size{position:fixed;opacity:0;-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=0)';visibility:hidden;overflow:scroll;height:500px;width:500px}#os-dummy-scrollbar-size>div{width:200%;height:200%;margin:10px 0}#os-dummy-scrollbar-size:before,#os-dummy-scrollbar-size:after,.os-content:before,.os-content:after{content:'';display:table;width:.01px;height:.01px;line-height:0;font-size:0;flex-grow:0;flex-shrink:0;visibility:hidden}#os-dummy-scrollbar-size,.os-viewport{-ms-overflow-style:scrollbar!important}.os-viewport-native-scrollbars-invisible#os-dummy-scrollbar-size,.os-viewport-native-scrollbars-invisible.os-viewport{scrollbar-width:none!important}.os-viewport-native-scrollbars-invisible#os-dummy-scrollbar-size::-webkit-scrollbar,.os-viewport-native-scrollbars-invisible.os-viewport::-webkit-scrollbar,.os-viewport-native-scrollbars-invisible#os-dummy-scrollbar-size::-webkit-scrollbar-corner,.os-viewport-native-scrollbars-invisible.os-viewport::-webkit-scrollbar-corner{display:none!important;width:0!important;height:0!important;visibility:hidden!important;background:0 0!important}.os-content-glue{box-sizing:inherit;max-height:100%;max-width:100%;width:100%;pointer-events:none}.os-padding{box-sizing:inherit;direction:inherit;position:absolute;overflow:visible;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto!important;height:auto!important;z-index:0}.os-host-overflow>.os-padding{overflow:hidden}.os-viewport{direction:inherit!important;box-sizing:inherit!important;resize:none!important;outline:0!important;position:absolute;overflow:hidden;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch}.os-content-arrange{position:absolute;z-index:-1;min-height:1px;min-width:1px;pointer-events:none}.os-content{direction:inherit;box-sizing:border-box!important;position:relative;display:block;height:100%;width:100%;height:100%;width:100%;visibility:visible}.os-content>.os-textarea{box-sizing:border-box!important;direction:inherit!important;background:0 0!important;outline:0 transparent!important;overflow:hidden!important;position:absolute!important;display:block!important;top:0!important;left:0!important;margin:0!important;border-radius:0!important;float:none!important;-webkit-filter:none!important;filter:none!important;border:0!important;resize:none!important;-webkit-transform:none!important;transform:none!important;max-width:none!important;max-height:none!important;box-shadow:none!important;-webkit-perspective:none!important;perspective:none!important;opacity:1!important;z-index:1!important;clip:auto!important;vertical-align:baseline!important;padding:0}.os-host-rtl>.os-padding>.os-viewport>.os-content>.os-textarea{right:0!important}.os-content>.os-textarea-cover{z-index:-1;pointer-events:none}.os-content>.os-textarea[wrap=off]{white-space:pre!important;margin:0!important}.os-text-inherit{font-family:inherit;font-size:inherit;font-weight:inherit;font-style:inherit;font-variant:inherit;text-transform:inherit;text-decoration:inherit;text-indent:inherit;text-align:inherit;text-shadow:inherit;text-overflow:inherit;letter-spacing:inherit;word-spacing:inherit;line-height:inherit;unicode-bidi:inherit;direction:inherit;color:inherit;cursor:text}.os-resize-observer,.os-resize-observer-host{box-sizing:inherit;display:block;visibility:hidden;position:absolute;top:0;left:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1}.os-resize-observer-host{padding:inherit;border:inherit;border-color:transparent;border-style:solid;box-sizing:border-box}.os-resize-observer-host.observed{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start}.os-resize-observer-host>.os-resize-observer,.os-resize-observer-host.observed>.os-resize-observer{height:200%;width:200%;padding:inherit;border:inherit;margin:0;display:block;box-sizing:content-box}.os-resize-observer-host.observed>.os-resize-observer,.os-resize-observer-host.observed>.os-resize-observer:before{display:flex;position:relative;flex-grow:1;flex-shrink:0;flex-basis:auto;box-sizing:border-box}.os-resize-observer-host.observed>.os-resize-observer:before{content:'';box-sizing:content-box;padding:inherit;border:inherit;margin:0}.os-size-auto-observer{box-sizing:inherit!important;height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-ms-flex-negative:0;flex-shrink:0;-ms-flex-preferred-size:0;flex-basis:0}.os-size-auto-observer>.os-resize-observer{width:1000%;height:1000%;min-height:1px;min-width:1px}.os-resize-observer-item{position:absolute;top:0;right:0;bottom:0;left:0;overflow:hidden;z-index:-1;opacity:0;direction:ltr!important;-webkit-box-flex:0!important;-ms-flex:none!important;flex:none!important}.os-resize-observer-item-final{position:absolute;left:0;top:0;-webkit-transition:none!important;transition:none!important;-webkit-box-flex:0!important;-ms-flex:none!important;flex:none!important}.os-resize-observer{-webkit-animation-duration:.001s;animation-duration:.001s;-webkit-animation-name:os-resize-observer-dummy-animation;animation-name:os-resize-observer-dummy-animation}object.os-resize-observer{box-sizing:border-box!important}@-webkit-keyframes os-resize-observer-dummy-animation{0%{z-index:0}to{z-index:-1}}@keyframes os-resize-observer-dummy-animation{0%{z-index:0}to{z-index:-1}}.os-host-transition>.os-scrollbar,.os-host-transition>.os-scrollbar-corner{-webkit-transition:opacity .3s,visibility .3s,top .3s,right .3s,bottom .3s,left .3s;transition:opacity .3s,visibility .3s,top .3s,right .3s,bottom .3s,left .3s}html.os-html>.os-host>.os-scrollbar{position:absolute;z-index:999999}.os-scrollbar,.os-scrollbar-corner{position:absolute;opacity:1;-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';z-index:1}.os-scrollbar-corner{bottom:0;right:0}.os-scrollbar{pointer-events:none}.os-scrollbar-track{pointer-events:auto;position:relative;height:100%;width:100%;padding:0!important;border:0!important}.os-scrollbar-handle{pointer-events:auto;position:absolute;width:100%;height:100%}.os-scrollbar-handle-off,.os-scrollbar-track-off{pointer-events:none}.os-scrollbar.os-scrollbar-unusable,.os-scrollbar.os-scrollbar-unusable *{pointer-events:none!important}.os-scrollbar.os-scrollbar-unusable .os-scrollbar-handle{opacity:0!important}.os-scrollbar-horizontal{bottom:0;left:0}.os-scrollbar-vertical{top:0;right:0}.os-host-rtl>.os-scrollbar-horizontal{right:0}.os-host-rtl>.os-scrollbar-vertical{right:auto;left:0}.os-host-rtl>.os-scrollbar-corner{right:auto;left:0}.os-scrollbar-auto-hidden,.os-padding+.os-scrollbar-corner,.os-host-resize-disabled.os-host-scrollbar-horizontal-hidden>.os-scrollbar-corner,.os-host-scrollbar-horizontal-hidden>.os-scrollbar-horizontal,.os-host-resize-disabled.os-host-scrollbar-vertical-hidden>.os-scrollbar-corner,.os-host-scrollbar-vertical-hidden>.os-scrollbar-vertical,.os-scrollbar-horizontal.os-scrollbar-auto-hidden+.os-scrollbar-vertical+.os-scrollbar-corner,.os-scrollbar-horizontal+.os-scrollbar-vertical.os-scrollbar-auto-hidden+.os-scrollbar-corner,.os-scrollbar-horizontal.os-scrollbar-auto-hidden+.os-scrollbar-vertical.os-scrollbar-auto-hidden+.os-scrollbar-corner{opacity:0;visibility:hidden;pointer-events:none}.os-scrollbar-corner-resize-both{cursor:nwse-resize}.os-host-rtl>.os-scrollbar-corner-resize-both{cursor:nesw-resize}.os-scrollbar-corner-resize-horizontal{cursor:ew-resize}.os-scrollbar-corner-resize-vertical{cursor:ns-resize}.os-dragging .os-scrollbar-corner.os-scrollbar-corner-resize{cursor:default}.os-host-resize-disabled.os-host-scrollbar-horizontal-hidden>.os-scrollbar-vertical{top:0;bottom:0}.os-host-resize-disabled.os-host-scrollbar-vertical-hidden>.os-scrollbar-horizontal,.os-host-rtl.os-host-resize-disabled.os-host-scrollbar-vertical-hidden>.os-scrollbar-horizontal{right:0;left:0}.os-scrollbar:hover,.os-scrollbar-corner.os-scrollbar-corner-resize{opacity:1!important;visibility:visible!important}.os-scrollbar-corner.os-scrollbar-corner-resize{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB3aWR0aD0iMTAiICAgaGVpZ2h0PSIxMCIgICB2ZXJzaW9uPSIxLjEiPiAgPGcgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsLTEwNDIuMzYyMikiICAgICBzdHlsZT0iZGlzcGxheTppbmxpbmUiPiAgICA8cGF0aCAgICAgICBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjQ5NDExNzY1O2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTpub25lIiAgICAgICBkPSJtIDcuNDI0MjE4NywxMDQyLjM2MjIgYyAtMC43MjM1NzkyLDAgLTEuMzEwMTU2MiwwLjU4NjYgLTEuMzEwMTU2MiwxLjMxMDIgMCwwLjI5OSAwLjEwNDM0MTksMC41NzEgMC4yNzI5NDkyLDAuNzkxNSAwLjIwOTEwMjQsMC4xNDEzIDAuNDY1NjIwNiwwLjIxODQgMC43MzY5NjI5LDAuMjE4NCAwLjcyMzU3OTMsMCAxLjMxMDE1NjMsLTAuNTg2NiAxLjMxMDE1NjMsLTEuMzEwMiAwLC0wLjI3MTMgLTAuMDc3MDkzLC0wLjUyNzggLTAuMjE4MzU5NCwtMC43MzcgLTAuMjIwNDk0MSwtMC4xNjg2IC0wLjQ5MjU0NDMsLTAuMjcyOSAtMC43OTE1NTI4LC0wLjI3MjkgeiBtIDAsMy4wODQzIGMgLTAuNzIzNTc5MiwwIC0xLjMxMDE1NjIsMC41ODY2IC0xLjMxMDE1NjIsMS4zMTAyIDAsMC4yOTkgMC4xMDQzNDE5LDAuNTcxIDAuMjcyOTQ5MiwwLjc5MTUgMC4yMDkxMDI0LDAuMTQxMyAwLjQ2NTYyMDYsMC4yMTg0IDAuNzM2OTYyOSwwLjIxODQgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjYgMS4zMTAxNTYzLC0xLjMxMDIgMCwtMC4yNzEzIC0wLjA3NzA5MywtMC41Mjc4IC0wLjIxODM1OTQsLTAuNzM2OSAtMC4yMjA0OTQxLC0wLjE2ODYgLTAuNDkyNTQ0MywtMC4yNzMgLTAuNzkxNTUyOCwtMC4yNzMgeiBtIC0zLjA4NDMyNjEsMCBjIC0wLjcyMzU3OTMsMCAtMS4zMTAxNTYzLDAuNTg2NiAtMS4zMTAxNTYzLDEuMzEwMiAwLDAuMjk5IDAuMTA0MzQxOSwwLjU3MSAwLjI3Mjk0OTIsMC43OTE1IDAuMjA5MTAyNCwwLjE0MTMgMC40NjU2MjA3LDAuMjE4NCAwLjczNjk2MjksMC4yMTg0IDAuNzIzNTc5MywwIDEuMzEwMTU2MywtMC41ODY2IDEuMzEwMTU2MywtMS4zMTAyIDAsLTAuMjcxMyAtMC4wNzcwOTMsLTAuNTI3OCAtMC4yMTgzNTk0LC0wLjczNjkgLTAuMjIwNDk0LC0wLjE2ODYgLTAuNDkyNTQ0MiwtMC4yNzMgLTAuNzkxNTUyNywtMC4yNzMgeiBtIC0zLjAyOTczNjQsMy4wMjk4IEMgMC41ODY1NzY5MywxMDQ4LjQ3NjMgMCwxMDQ5LjA2MjggMCwxMDQ5Ljc4NjQgYyAwLDAuMjk5IDAuMTA0MzQxOSwwLjU3MTEgMC4yNzI5NDkyMiwwLjc5MTYgMC4yMDkxMDIyOSwwLjE0MTIgMC40NjU2MjA2NSwwLjIxODMgMC43MzY5NjI4OCwwLjIxODMgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjUgMS4zMTAxNTYzLC0xLjMxMDEgMCwtMC4yNzE0IC0wLjA3NzA5MywtMC41Mjc5IC0wLjIxODM1OTQsLTAuNzM3IC0wLjIyMDQ5NDEsLTAuMTY4NiAtMC40OTI1NDQzLC0wLjI3MjkgLTAuNzkxNTUyOCwtMC4yNzI5IHogbSAzLjAyOTczNjQsMCBjIC0wLjcyMzU3OTMsMCAtMS4zMTAxNTYzLDAuNTg2NSAtMS4zMTAxNTYzLDEuMzEwMSAwLDAuMjk5IDAuMTA0MzQxOSwwLjU3MTEgMC4yNzI5NDkyLDAuNzkxNiAwLjIwOTEwMjQsMC4xNDEyIDAuNDY1NjIwNywwLjIxODMgMC43MzY5NjI5LDAuMjE4MyAwLjcyMzU3OTMsMCAxLjMxMDE1NjMsLTAuNTg2NSAxLjMxMDE1NjMsLTEuMzEwMSAwLC0wLjI3MTQgLTAuMDc3MDkzLC0wLjUyNzkgLTAuMjE4MzU5NCwtMC43MzcgLTAuMjIwNDk0LC0wLjE2ODYgLTAuNDkyNTQ0MiwtMC4yNzI5IC0wLjc5MTU1MjcsLTAuMjcyOSB6IG0gMy4wODQzMjYxLDAgYyAtMC43MjM1NzkyLDAgLTEuMzEwMTU2MiwwLjU4NjUgLTEuMzEwMTU2MiwxLjMxMDEgMCwwLjI5OSAwLjEwNDM0MTksMC41NzExIDAuMjcyOTQ5MiwwLjc5MTYgMC4yMDkxMDI0LDAuMTQxMiAwLjQ2NTYyMDYsMC4yMTgzIDAuNzM2OTYyOSwwLjIxODMgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjUgMS4zMTAxNTYzLC0xLjMxMDEgMCwtMC4yNzE0IC0wLjA3NzA5MywtMC41Mjc5IC0wLjIxODM1OTQsLTAuNzM3IC0wLjIyMDQ5NDEsLTAuMTY4NiAtMC40OTI1NDQzLC0wLjI3MjkgLTAuNzkxNTUyOCwtMC4yNzI5IHoiLz4gIDwvZz4gIDxnICAgICBzdHlsZT0iZGlzcGxheTppbmxpbmUiPiAgICA8cGF0aCAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTpub25lIiAgICAgICBkPSJtIDguMjE1NzcxNSwwLjI3Mjk0OTIyIGMgMC4xNDEyNjY3LDAuMjA5MTAyMjkgMC4yMTgzNTk0LDAuNDY1NjIwNjUgMC4yMTgzNTk0LDAuNzM2OTYyODggMCwwLjcyMzU3OTMgLTAuNTg2NTc3LDEuMzEwMTU2MyAtMS4zMTAxNTYzLDEuMzEwMTU2MyAtMC4yNzEzNDIzLDAgLTAuNTI3ODYwNSwtMC4wNzcwOTMgLTAuNzM2OTYyOSwtMC4yMTgzNTk0IDAuMjM5NDEwNCwwLjMxMzA4NTkgMC42MTI2MzYyLDAuNTE4NjAzNSAxLjAzNzIwNywwLjUxODYwMzUgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjU3NyAxLjMxMDE1NjMsLTEuMzEwMTU2MyAwLC0wLjQyNDU3MDc2IC0wLjIwNTUxNzYsLTAuNzk3Nzk2NTkgLTAuNTE4NjAzNSwtMS4wMzcyMDY5OCB6IG0gMCwzLjA4NDMyNjE4IGMgMC4xNDEyNjY3LDAuMjA5MTAyMyAwLjIxODM1OTQsMC40NjU2MjA2IDAuMjE4MzU5NCwwLjczNjk2MjkgMCwwLjcyMzU3OTMgLTAuNTg2NTc3LDEuMzEwMTU2MiAtMS4zMTAxNTYzLDEuMzEwMTU2MiAtMC4yNzEzNDIzLDAgLTAuNTI3ODYwNSwtMC4wNzcwOTMgLTAuNzM2OTYyOSwtMC4yMTgzNTkzIDAuMjM5NDEwNCwwLjMxMzA4NTkgMC42MTI2MzYyLDAuNTE4NjAzNSAxLjAzNzIwNywwLjUxODYwMzUgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjU3NyAxLjMxMDE1NjMsLTEuMzEwMTU2MyAwLC0wLjQyNDU3MDggLTAuMjA1NTE3NiwtMC43OTc3OTY3IC0wLjUxODYwMzUsLTEuMDM3MjA3IHogbSAtMy4wODQzMjYyLDAgYyAwLjE0MTI2NjcsMC4yMDkxMDIzIDAuMjE4MzU5NCwwLjQ2NTYyMDYgMC4yMTgzNTk0LDAuNzM2OTYyOSAwLDAuNzIzNTc5MyAtMC41ODY1NzcsMS4zMTAxNTYyIC0xLjMxMDE1NjMsMS4zMTAxNTYyIC0wLjI3MTM0MjIsMCAtMC41Mjc4NjA1LC0wLjA3NzA5MyAtMC43MzY5NjI5LC0wLjIxODM1OTMgMC4yMzk0MTA0LDAuMzEzMDg1OSAwLjYxMjYzNjMsMC41MTg2MDM1IDEuMDM3MjA3MSwwLjUxODYwMzUgMC43MjM1NzkzLDAgMS4zMTAxNTYyLC0wLjU4NjU3NyAxLjMxMDE1NjIsLTEuMzEwMTU2MyAwLC0wLjQyNDU3MDggLTAuMjA1NTE3NSwtMC43OTc3OTY3IC0wLjUxODYwMzUsLTEuMDM3MjA3IHogTSAyLjEwMTcwOSw2LjM4NzAxMTcgYyAwLjE0MTI2NjcsMC4yMDkxMDI0IDAuMjE4MzU5NCwwLjQ2NTYyMDYgMC4yMTgzNTk0LDAuNzM2OTYyOSAwLDAuNzIzNTc5MyAtMC41ODY1NzcsMS4zMTAxNTYzIC0xLjMxMDE1NjMsMS4zMTAxNTYzIC0wLjI3MTM0MjIzLDAgLTAuNTI3ODYwNTksLTAuMDc3MDkzIC0wLjczNjk2Mjg4LC0wLjIxODM1OTQgMC4yMzk0MTAzOSwwLjMxMzA4NTkgMC42MTI2MzYyMiwwLjUxODYwMzUgMS4wMzcyMDY5OCwwLjUxODYwMzUgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjU3NyAxLjMxMDE1NjMsLTEuMzEwMTU2MyAwLC0wLjQyNDU3MDggLTAuMjA1NTE3NiwtMC43OTc3OTY2IC0wLjUxODYwMzUsLTEuMDM3MjA3IHogbSAzLjAyOTczNjMsMCBjIDAuMTQxMjY2NywwLjIwOTEwMjQgMC4yMTgzNTk0LDAuNDY1NjIwNiAwLjIxODM1OTQsMC43MzY5NjI5IDAsMC43MjM1NzkzIC0wLjU4NjU3NywxLjMxMDE1NjMgLTEuMzEwMTU2MywxLjMxMDE1NjMgLTAuMjcxMzQyMiwwIC0wLjUyNzg2MDUsLTAuMDc3MDkzIC0wLjczNjk2MjksLTAuMjE4MzU5NCAwLjIzOTQxMDQsMC4zMTMwODU5IDAuNjEyNjM2MywwLjUxODYwMzUgMS4wMzcyMDcxLDAuNTE4NjAzNSAwLjcyMzU3OTMsMCAxLjMxMDE1NjIsLTAuNTg2NTc3IDEuMzEwMTU2MiwtMS4zMTAxNTYzIDAsLTAuNDI0NTcwOCAtMC4yMDU1MTc1LC0wLjc5Nzc5NjYgLTAuNTE4NjAzNSwtMS4wMzcyMDcgeiBtIDMuMDg0MzI2MiwwIGMgMC4xNDEyNjY3LDAuMjA5MTAyNCAwLjIxODM1OTQsMC40NjU2MjA2IDAuMjE4MzU5NCwwLjczNjk2MjkgMCwwLjcyMzU3OTMgLTAuNTg2NTc3LDEuMzEwMTU2MyAtMS4zMTAxNTYzLDEuMzEwMTU2MyAtMC4yNzEzNDIzLDAgLTAuNTI3ODYwNSwtMC4wNzcwOTMgLTAuNzM2OTYyOSwtMC4yMTgzNTk0IDAuMjM5NDEwNCwwLjMxMzA4NTkgMC42MTI2MzYyLDAuNTE4NjAzNSAxLjAzNzIwNywwLjUxODYwMzUgMC43MjM1NzkzLDAgMS4zMTAxNTYzLC0wLjU4NjU3NyAxLjMxMDE1NjMsLTEuMzEwMTU2MyAwLC0wLjQyNDU3MDggLTAuMjA1NTE3NiwtMC43OTc3OTY2IC0wLjUxODYwMzUsLTEuMDM3MjA3IHoiIC8+ICA8L2c+PC9zdmc+);background-repeat:no-repeat;background-position:100% 100%;pointer-events:auto!important}.os-host-rtl>.os-scrollbar-corner.os-scrollbar-corner-resize{-webkit-transform:scale(-1,1);transform:scale(-1,1)}.os-host-overflow{overflow:hidden!important}.os-theme-none>.os-scrollbar-horizontal,.os-theme-none>.os-scrollbar-vertical,.os-theme-none>.os-scrollbar-corner{display:none!important}.os-theme-none>.os-scrollbar-corner-resize{display:block!important;min-width:10px;min-height:10px}.os-theme-dark>.os-scrollbar-horizontal,.os-theme-light>.os-scrollbar-horizontal{right:10px;height:10px}.os-theme-dark>.os-scrollbar-vertical,.os-theme-light>.os-scrollbar-vertical{bottom:10px;width:10px}.os-theme-dark.os-host-rtl>.os-scrollbar-horizontal,.os-theme-light.os-host-rtl>.os-scrollbar-horizontal{left:10px;right:0}.os-theme-dark>.os-scrollbar-corner,.os-theme-light>.os-scrollbar-corner{height:10px;width:10px}.os-theme-dark>.os-scrollbar-corner,.os-theme-light>.os-scrollbar-corner{background-color:transparent}.os-theme-dark>.os-scrollbar,.os-theme-light>.os-scrollbar{padding:2px;box-sizing:border-box;background:0 0}.os-theme-dark>.os-scrollbar.os-scrollbar-unusable,.os-theme-light>.os-scrollbar.os-scrollbar-unusable{background:0 0}.os-theme-dark>.os-scrollbar>.os-scrollbar-track,.os-theme-light>.os-scrollbar>.os-scrollbar-track{background:0 0}.os-theme-dark>.os-scrollbar-horizontal>.os-scrollbar-track>.os-scrollbar-handle,.os-theme-light>.os-scrollbar-horizontal>.os-scrollbar-track>.os-scrollbar-handle{min-width:30px}.os-theme-dark>.os-scrollbar-vertical>.os-scrollbar-track>.os-scrollbar-handle,.os-theme-light>.os-scrollbar-vertical>.os-scrollbar-track>.os-scrollbar-handle{min-height:30px}.os-theme-dark.os-host-transition>.os-scrollbar>.os-scrollbar-track>.os-scrollbar-handle,.os-theme-light.os-host-transition>.os-scrollbar>.os-scrollbar-track>.os-scrollbar-handle{-webkit-transition:background-color .3s;transition:background-color .3s}.os-theme-dark>.os-scrollbar>.os-scrollbar-track>.os-scrollbar-handle,.os-theme-light>.os-scrollbar>.os-scrollbar-track>.os-scrollbar-handle,.os-theme-dark>.os-scrollbar>.os-scrollbar-track,.os-theme-light>.os-scrollbar>.os-scrollbar-track{border-radius:10px}.os-theme-dark>.os-scrollbar>.os-scrollbar-track>.os-scrollbar-handle{background:rgba(0,0,0,.4)}.os-theme-light>.os-scrollbar>.os-scrollbar-track>.os-scrollbar-handle{background:rgba(255,255,255,.4)}.os-theme-dark>.os-scrollbar:hover>.os-scrollbar-track>.os-scrollbar-handle{background:rgba(0,0,0,.55)}.os-theme-light>.os-scrollbar:hover>.os-scrollbar-track>.os-scrollbar-handle{background:rgba(255,255,255,.55)}.os-theme-dark>.os-scrollbar>.os-scrollbar-track>.os-scrollbar-handle.active{background:rgba(0,0,0,.7)}.os-theme-light>.os-scrollbar>.os-scrollbar-track>.os-scrollbar-handle.active{background:rgba(255,255,255,.7)}.os-theme-dark>.os-scrollbar-horizontal .os-scrollbar-handle:before,.os-theme-dark>.os-scrollbar-vertical .os-scrollbar-handle:before,.os-theme-light>.os-scrollbar-horizontal .os-scrollbar-handle:before,.os-theme-light>.os-scrollbar-vertical .os-scrollbar-handle:before{content:'';position:absolute;left:0;right:0;top:0;bottom:0;display:block}.os-theme-dark.os-host-scrollbar-horizontal-hidden>.os-scrollbar-horizontal .os-scrollbar-handle:before,.os-theme-dark.os-host-scrollbar-vertical-hidden>.os-scrollbar-vertical .os-scrollbar-handle:before,.os-theme-light.os-host-scrollbar-horizontal-hidden>.os-scrollbar-horizontal .os-scrollbar-handle:before,.os-theme-light.os-host-scrollbar-vertical-hidden>.os-scrollbar-vertical .os-scrollbar-handle:before{display:none}.os-theme-dark>.os-scrollbar-horizontal .os-scrollbar-handle:before,.os-theme-light>.os-scrollbar-horizontal .os-scrollbar-handle:before{top:-6px;bottom:-2px}.os-theme-dark>.os-scrollbar-vertical .os-scrollbar-handle:before,.os-theme-light>.os-scrollbar-vertical .os-scrollbar-handle:before{left:-6px;right:-2px}.os-host-rtl.os-theme-dark>.os-scrollbar-vertical .os-scrollbar-handle:before,.os-host-rtl.os-theme-light>.os-scrollbar-vertical .os-scrollbar-handle:before{right:-6px;left:-2px} \ No newline at end of file diff --git a/src/main/resources/static/plugins/overlayScrollbars/js/OverlayScrollbars.js b/src/main/resources/static/plugins/overlayScrollbars/js/OverlayScrollbars.js new file mode 100644 index 0000000..1de8174 --- /dev/null +++ b/src/main/resources/static/plugins/overlayScrollbars/js/OverlayScrollbars.js @@ -0,0 +1,6661 @@ +/*! + * OverlayScrollbars + * https://github.com/KingSora/OverlayScrollbars + * + * Version: 1.13.0 + * + * Copyright KingSora | Rene Haas. + * https://github.com/KingSora + * + * Released under the MIT license. + * Date: 02.08.2020 + */ + +(function (global, factory) { + if (typeof define === 'function' && define.amd) + define(function () { return factory(global, global.document, undefined); }); + else if (typeof module === 'object' && typeof module.exports === 'object') + module.exports = factory(global, global.document, undefined); + else + factory(global, global.document, undefined); +}(typeof window !== 'undefined' ? window : this, + function (window, document, undefined) { + 'use strict'; + var PLUGINNAME = 'OverlayScrollbars'; + var TYPES = { + o: 'object', + f: 'function', + a: 'array', + s: 'string', + b: 'boolean', + n: 'number', + u: 'undefined', + z: 'null' + //d : 'date', + //e : 'error', + //r : 'regexp', + //y : 'symbol' + }; + var LEXICON = { + c: 'class', + s: 'style', + i: 'id', + l: 'length', + p: 'prototype', + ti: 'tabindex', + oH: 'offsetHeight', + cH: 'clientHeight', + sH: 'scrollHeight', + oW: 'offsetWidth', + cW: 'clientWidth', + sW: 'scrollWidth', + hOP: 'hasOwnProperty', + bCR: 'getBoundingClientRect' + }; + var VENDORS = (function () { + //https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix + var jsCache = {}; + var cssCache = {}; + var cssPrefixes = ['-webkit-', '-moz-', '-o-', '-ms-']; + var jsPrefixes = ['WebKit', 'Moz', 'O', 'MS']; + function firstLetterToUpper(str) { + return str.charAt(0).toUpperCase() + str.slice(1); + } + + return { + _cssPrefixes: cssPrefixes, + _jsPrefixes: jsPrefixes, + _cssProperty: function (name) { + var result = cssCache[name]; + + if (cssCache[LEXICON.hOP](name)) + return result; + + var uppercasedName = firstLetterToUpper(name); + var elmStyle = document.createElement('div')[LEXICON.s]; + var resultPossibilities; + var i = 0; + var v; + var currVendorWithoutDashes; + + for (; i < cssPrefixes.length; i++) { + currVendorWithoutDashes = cssPrefixes[i].replace(/-/g, ''); + resultPossibilities = [ + name, //transition + cssPrefixes[i] + name, //-webkit-transition + currVendorWithoutDashes + uppercasedName, //webkitTransition + firstLetterToUpper(currVendorWithoutDashes) + uppercasedName //WebkitTransition + ]; + for (v = 0; v < resultPossibilities[LEXICON.l]; v++) { + if (elmStyle[resultPossibilities[v]] !== undefined) { + result = resultPossibilities[v]; + break; + } + } + } + + cssCache[name] = result; + return result; + }, + _cssPropertyValue: function (property, values, suffix) { + var name = property + ' ' + values; + var result = cssCache[name]; + + if (cssCache[LEXICON.hOP](name)) + return result; + + var dummyStyle = document.createElement('div')[LEXICON.s]; + var possbleValues = values.split(' '); + var preparedSuffix = suffix || ''; + var i = 0; + var v = -1; + var prop; + + for (; i < possbleValues[LEXICON.l]; i++) { + for (; v < VENDORS._cssPrefixes[LEXICON.l]; v++) { + prop = v < 0 ? possbleValues[i] : VENDORS._cssPrefixes[v] + possbleValues[i]; + dummyStyle.cssText = property + ':' + prop + preparedSuffix; + if (dummyStyle[LEXICON.l]) { + result = prop; + break; + } + } + } + + cssCache[name] = result; + return result; + }, + _jsAPI: function (name, isInterface, fallback) { + var i = 0; + var result = jsCache[name]; + + if (!jsCache[LEXICON.hOP](name)) { + result = window[name]; + for (; i < jsPrefixes[LEXICON.l]; i++) + result = result || window[(isInterface ? jsPrefixes[i] : jsPrefixes[i].toLowerCase()) + firstLetterToUpper(name)]; + jsCache[name] = result; + } + return result || fallback; + } + } + })(); + var COMPATIBILITY = (function () { + function windowSize(x) { + return x ? window.innerWidth || document.documentElement[LEXICON.cW] || document.body[LEXICON.cW] : window.innerHeight || document.documentElement[LEXICON.cH] || document.body[LEXICON.cH]; + } + function bind(func, thisObj) { + if (typeof func != TYPES.f) { + throw "Can't bind function!"; + // closest thing possible to the ECMAScript 5 + // internal IsCallable function + //throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); + } + var proto = LEXICON.p; + var aArgs = Array[proto].slice.call(arguments, 2); + var fNOP = function () { }; + var fBound = function () { return func.apply(this instanceof fNOP ? this : thisObj, aArgs.concat(Array[proto].slice.call(arguments))); }; + + if (func[proto]) + fNOP[proto] = func[proto]; // Function.prototype doesn't have a prototype property + fBound[proto] = new fNOP(); + + return fBound; + } + + return { + /** + * Gets the current window width. + * @returns {Number|number} The current window width in pixel. + */ + wW: bind(windowSize, 0, true), + + /** + * Gets the current window height. + * @returns {Number|number} The current window height in pixel. + */ + wH: bind(windowSize, 0), + + /** + * Gets the MutationObserver Object or undefined if not supported. + * @returns {MutationObserver|*|undefined} The MutationsObserver Object or undefined. + */ + mO: bind(VENDORS._jsAPI, 0, 'MutationObserver', true), + + /** + * Gets the ResizeObserver Object or undefined if not supported. + * @returns {MutationObserver|*|undefined} The ResizeObserver Object or undefined. + */ + rO: bind(VENDORS._jsAPI, 0, 'ResizeObserver', true), + + /** + * Gets the RequestAnimationFrame method or it's corresponding polyfill. + * @returns {*|Function} The RequestAnimationFrame method or it's corresponding polyfill. + */ + rAF: bind(VENDORS._jsAPI, 0, 'requestAnimationFrame', false, function (func) { return window.setTimeout(func, 1000 / 60); }), + + /** + * Gets the CancelAnimationFrame method or it's corresponding polyfill. + * @returns {*|Function} The CancelAnimationFrame method or it's corresponding polyfill. + */ + cAF: bind(VENDORS._jsAPI, 0, 'cancelAnimationFrame', false, function (id) { return window.clearTimeout(id); }), + + /** + * Gets the current time. + * @returns {number} The current time. + */ + now: function () { + return Date.now && Date.now() || new Date().getTime(); + }, + + /** + * Stops the propagation of the given event. + * @param event The event of which the propagation shall be stoped. + */ + stpP: function (event) { + if (event.stopPropagation) + event.stopPropagation(); + else + event.cancelBubble = true; + }, + + /** + * Prevents the default action of the given event. + * @param event The event of which the default action shall be prevented. + */ + prvD: function (event) { + if (event.preventDefault && event.cancelable) + event.preventDefault(); + else + event.returnValue = false; + }, + + /** + * Gets the pageX and pageY values of the given mouse event. + * @param event The mouse event of which the pageX and pageX shall be got. + * @returns {{x: number, y: number}} x = pageX value, y = pageY value. + */ + page: function (event) { + event = event.originalEvent || event; + + var strPage = 'page'; + var strClient = 'client'; + var strX = 'X'; + var strY = 'Y'; + var target = event.target || event.srcElement || document; + var eventDoc = target.ownerDocument || document; + var doc = eventDoc.documentElement; + var body = eventDoc.body; + + //if touch event return return pageX/Y of it + if (event.touches !== undefined) { + var touch = event.touches[0]; + return { + x: touch[strPage + strX], + y: touch[strPage + strY] + } + } + + // Calculate pageX/Y if not native supported + if (!event[strPage + strX] && event[strClient + strX] && event[strClient + strX] != null) { + + return { + x: event[strClient + strX] + + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - + (doc && doc.clientLeft || body && body.clientLeft || 0), + y: event[strClient + strY] + + (doc && doc.scrollTop || body && body.scrollTop || 0) - + (doc && doc.clientTop || body && body.clientTop || 0) + } + } + return { + x: event[strPage + strX], + y: event[strPage + strY] + }; + }, + + /** + * Gets the clicked mouse button of the given mouse event. + * @param event The mouse event of which the clicked button shal be got. + * @returns {number} The number of the clicked mouse button. (0 : none | 1 : leftButton | 2 : middleButton | 3 : rightButton) + */ + mBtn: function (event) { + var button = event.button; + if (!event.which && button !== undefined) + return (button & 1 ? 1 : (button & 2 ? 3 : (button & 4 ? 2 : 0))); + else + return event.which; + }, + + /** + * Checks whether a item is in the given array and returns its index. + * @param item The item of which the position in the array shall be determined. + * @param arr The array. + * @returns {number} The zero based index of the item or -1 if the item isn't in the array. + */ + inA: function (item, arr) { + for (var i = 0; i < arr[LEXICON.l]; i++) + //Sometiems in IE a "SCRIPT70" Permission denied error occurs if HTML elements in a iFrame are compared + try { + if (arr[i] === item) + return i; + } + catch (e) { } + return -1; + }, + + /** + * Returns true if the given value is a array. + * @param arr The potential array. + * @returns {boolean} True if the given value is a array, false otherwise. + */ + isA: function (arr) { + var def = Array.isArray; + return def ? def(arr) : this.type(arr) == TYPES.a; + }, + + /** + * Determine the internal JavaScript [[Class]] of the given object. + * @param obj The object of which the type shall be determined. + * @returns {string} The type of the given object. + */ + type: function (obj) { + if (obj === undefined) + return obj + ''; + if (obj === null) + return obj + ''; + return Object[LEXICON.p].toString.call(obj).replace(/^\[object (.+)\]$/, '$1').toLowerCase(); + }, + + + bind: bind + + /** + * Gets the vendor-prefixed CSS property by the given name. + * For example the given name is "transform" and you're using a old Firefox browser then the returned value would be "-moz-transform". + * If the browser doesn't need a vendor-prefix, then the returned string is the given name. + * If the browser doesn't support the given property name at all (not even with a vendor-prefix) the returned value is null. + * @param propName The unprefixed CSS property name. + * @returns {string|null} The vendor-prefixed CSS property or null if the browser doesn't support the given CSS property. + + cssProp: function(propName) { + return VENDORS._cssProperty(propName); + } + */ + } + })(); + + + var MATH = Math; + var JQUERY = window.jQuery; + var EASING = (function () { + var _easingsMath = { + p: MATH.PI, + c: MATH.cos, + s: MATH.sin, + w: MATH.pow, + t: MATH.sqrt, + n: MATH.asin, + a: MATH.abs, + o: 1.70158 + }; + + /* + x : current percent (0 - 1), + t : current time (duration * percent), + b : start value (from), + c : end value (to), + d : duration + + easingName : function(x, t, b, c, d) { return easedValue; } + */ + + return { + swing: function (x, t, b, c, d) { + return 0.5 - _easingsMath.c(x * _easingsMath.p) / 2; + }, + linear: function (x, t, b, c, d) { + return x; + }, + easeInQuad: function (x, t, b, c, d) { + return c * (t /= d) * t + b; + }, + easeOutQuad: function (x, t, b, c, d) { + return -c * (t /= d) * (t - 2) + b; + }, + easeInOutQuad: function (x, t, b, c, d) { + return ((t /= d / 2) < 1) ? c / 2 * t * t + b : -c / 2 * ((--t) * (t - 2) - 1) + b; + }, + easeInCubic: function (x, t, b, c, d) { + return c * (t /= d) * t * t + b; + }, + easeOutCubic: function (x, t, b, c, d) { + return c * ((t = t / d - 1) * t * t + 1) + b; + }, + easeInOutCubic: function (x, t, b, c, d) { + return ((t /= d / 2) < 1) ? c / 2 * t * t * t + b : c / 2 * ((t -= 2) * t * t + 2) + b; + }, + easeInQuart: function (x, t, b, c, d) { + return c * (t /= d) * t * t * t + b; + }, + easeOutQuart: function (x, t, b, c, d) { + return -c * ((t = t / d - 1) * t * t * t - 1) + b; + }, + easeInOutQuart: function (x, t, b, c, d) { + return ((t /= d / 2) < 1) ? c / 2 * t * t * t * t + b : -c / 2 * ((t -= 2) * t * t * t - 2) + b; + }, + easeInQuint: function (x, t, b, c, d) { + return c * (t /= d) * t * t * t * t + b; + }, + easeOutQuint: function (x, t, b, c, d) { + return c * ((t = t / d - 1) * t * t * t * t + 1) + b; + }, + easeInOutQuint: function (x, t, b, c, d) { + return ((t /= d / 2) < 1) ? c / 2 * t * t * t * t * t + b : c / 2 * ((t -= 2) * t * t * t * t + 2) + b; + }, + easeInSine: function (x, t, b, c, d) { + return -c * _easingsMath.c(t / d * (_easingsMath.p / 2)) + c + b; + }, + easeOutSine: function (x, t, b, c, d) { + return c * _easingsMath.s(t / d * (_easingsMath.p / 2)) + b; + }, + easeInOutSine: function (x, t, b, c, d) { + return -c / 2 * (_easingsMath.c(_easingsMath.p * t / d) - 1) + b; + }, + easeInExpo: function (x, t, b, c, d) { + return (t == 0) ? b : c * _easingsMath.w(2, 10 * (t / d - 1)) + b; + }, + easeOutExpo: function (x, t, b, c, d) { + return (t == d) ? b + c : c * (-_easingsMath.w(2, -10 * t / d) + 1) + b; + }, + easeInOutExpo: function (x, t, b, c, d) { + if (t == 0) return b; + if (t == d) return b + c; + if ((t /= d / 2) < 1) return c / 2 * _easingsMath.w(2, 10 * (t - 1)) + b; + return c / 2 * (-_easingsMath.w(2, -10 * --t) + 2) + b; + }, + easeInCirc: function (x, t, b, c, d) { + return -c * (_easingsMath.t(1 - (t /= d) * t) - 1) + b; + }, + easeOutCirc: function (x, t, b, c, d) { + return c * _easingsMath.t(1 - (t = t / d - 1) * t) + b; + }, + easeInOutCirc: function (x, t, b, c, d) { + return ((t /= d / 2) < 1) ? -c / 2 * (_easingsMath.t(1 - t * t) - 1) + b : c / 2 * (_easingsMath.t(1 - (t -= 2) * t) + 1) + b; + }, + easeInElastic: function (x, t, b, c, d) { + var s = _easingsMath.o; var p = 0; var a = c; + if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * .3; + if (a < _easingsMath.a(c)) { a = c; s = p / 4; } + else s = p / (2 * _easingsMath.p) * _easingsMath.n(c / a); + return -(a * _easingsMath.w(2, 10 * (t -= 1)) * _easingsMath.s((t * d - s) * (2 * _easingsMath.p) / p)) + b; + }, + easeOutElastic: function (x, t, b, c, d) { + var s = _easingsMath.o; var p = 0; var a = c; + if (t == 0) return b; + if ((t /= d) == 1) return b + c; + if (!p) p = d * .3; + if (a < _easingsMath.a(c)) { a = c; s = p / 4; } + else s = p / (2 * _easingsMath.p) * _easingsMath.n(c / a); + return a * _easingsMath.w(2, -10 * t) * _easingsMath.s((t * d - s) * (2 * _easingsMath.p) / p) + c + b; + }, + easeInOutElastic: function (x, t, b, c, d) { + var s = _easingsMath.o; var p = 0; var a = c; + if (t == 0) return b; + if ((t /= d / 2) == 2) return b + c; + if (!p) p = d * (.3 * 1.5); + if (a < _easingsMath.a(c)) { a = c; s = p / 4; } + else s = p / (2 * _easingsMath.p) * _easingsMath.n(c / a); + if (t < 1) return -.5 * (a * _easingsMath.w(2, 10 * (t -= 1)) * _easingsMath.s((t * d - s) * (2 * _easingsMath.p) / p)) + b; + return a * _easingsMath.w(2, -10 * (t -= 1)) * _easingsMath.s((t * d - s) * (2 * _easingsMath.p) / p) * .5 + c + b; + }, + easeInBack: function (x, t, b, c, d, s) { + s = s || _easingsMath.o; + return c * (t /= d) * t * ((s + 1) * t - s) + b; + }, + easeOutBack: function (x, t, b, c, d, s) { + s = s || _easingsMath.o; + return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; + }, + easeInOutBack: function (x, t, b, c, d, s) { + s = s || _easingsMath.o; + return ((t /= d / 2) < 1) ? c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b : c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; + }, + easeInBounce: function (x, t, b, c, d) { + return c - this.easeOutBounce(x, d - t, 0, c, d) + b; + }, + easeOutBounce: function (x, t, b, c, d) { + var o = 7.5625; + if ((t /= d) < (1 / 2.75)) { + return c * (o * t * t) + b; + } else if (t < (2 / 2.75)) { + return c * (o * (t -= (1.5 / 2.75)) * t + .75) + b; + } else if (t < (2.5 / 2.75)) { + return c * (o * (t -= (2.25 / 2.75)) * t + .9375) + b; + } else { + return c * (o * (t -= (2.625 / 2.75)) * t + .984375) + b; + } + }, + easeInOutBounce: function (x, t, b, c, d) { + return (t < d / 2) ? this.easeInBounce(x, t * 2, 0, c, d) * .5 + b : this.easeOutBounce(x, t * 2 - d, 0, c, d) * .5 + c * .5 + b; + } + }; + /* + * + * TERMS OF USE - EASING EQUATIONS + * + * Open source under the BSD License. + * + * Copyright © 2001 Robert Penner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the author nor the names of contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + })(); + var FRAMEWORK = (function () { + var _rnothtmlwhite = (/[^\x20\t\r\n\f]+/g); + var _strSpace = ' '; + var _strEmpty = ''; + var _strScrollLeft = 'scrollLeft'; + var _strScrollTop = 'scrollTop'; + var _animations = []; + var _type = COMPATIBILITY.type; + var _cssNumber = { + animationIterationCount: true, + columnCount: true, + fillOpacity: true, + flexGrow: true, + flexShrink: true, + fontWeight: true, + lineHeight: true, + opacity: true, + order: true, + orphans: true, + widows: true, + zIndex: true, + zoom: true + }; + + function extend() { + var src, copyIsArray, copy, name, options, clone, target = arguments[0] || {}, + i = 1, + length = arguments[LEXICON.l], + deep = false; + + // Handle a deep copy situation + if (_type(target) == TYPES.b) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if (_type(target) != TYPES.o && !_type(target) == TYPES.f) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if (length === i) { + target = FakejQuery; + --i; + } + + for (; i < length; i++) { + // Only deal with non-null/undefined values + if ((options = arguments[i]) != null) { + // Extend the base object + for (name in options) { + src = target[name]; + copy = options[name]; + + // Prevent never-ending loop + if (target === copy) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if (deep && copy && (isPlainObject(copy) || (copyIsArray = COMPATIBILITY.isA(copy)))) { + if (copyIsArray) { + copyIsArray = false; + clone = src && COMPATIBILITY.isA(src) ? src : []; + + } else { + clone = src && isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[name] = extend(deep, clone, copy); + + // Don't bring in undefined values + } else if (copy !== undefined) { + target[name] = copy; + } + } + } + } + + // Return the modified object + return target; + }; + + function inArray(item, arr, fromIndex) { + for (var i = fromIndex || 0; i < arr[LEXICON.l]; i++) + if (arr[i] === item) + return i; + return -1; + } + + function isFunction(obj) { + return _type(obj) == TYPES.f; + }; + + function isEmptyObject(obj) { + for (var name in obj) + return false; + return true; + }; + + function isPlainObject(obj) { + if (!obj || _type(obj) != TYPES.o) + return false; + + var key; + var proto = LEXICON.p; + var hasOwnProperty = Object[proto].hasOwnProperty; + var hasOwnConstructor = hasOwnProperty.call(obj, 'constructor'); + var hasIsPrototypeOf = obj.constructor && obj.constructor[proto] && hasOwnProperty.call(obj.constructor[proto], 'isPrototypeOf'); + + if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) { + return false; + } + + + for (key in obj) { /**/ } + + return _type(key) == TYPES.u || hasOwnProperty.call(obj, key); + }; + + function each(obj, callback) { + var i = 0; + + if (isArrayLike(obj)) { + for (; i < obj[LEXICON.l]; i++) { + if (callback.call(obj[i], i, obj[i]) === false) + break; + } + } + else { + for (i in obj) { + if (callback.call(obj[i], i, obj[i]) === false) + break; + } + } + + return obj; + }; + + function isArrayLike(obj) { + var length = !!obj && [LEXICON.l] in obj && obj[LEXICON.l]; + var t = _type(obj); + return isFunction(t) ? false : (t == TYPES.a || length === 0 || _type(length) == TYPES.n && length > 0 && (length - 1) in obj); + } + + function stripAndCollapse(value) { + var tokens = value.match(_rnothtmlwhite) || []; + return tokens.join(_strSpace); + } + + function matches(elem, selector) { + var nodeList = (elem.parentNode || document).querySelectorAll(selector) || []; + var i = nodeList[LEXICON.l]; + + while (i--) + if (nodeList[i] == elem) + return true; + + return false; + } + + function insertAdjacentElement(el, strategy, child) { + if (COMPATIBILITY.isA(child)) { + for (var i = 0; i < child[LEXICON.l]; i++) + insertAdjacentElement(el, strategy, child[i]); + } + else if (_type(child) == TYPES.s) + el.insertAdjacentHTML(strategy, child); + else + el.insertAdjacentElement(strategy, child.nodeType ? child : child[0]); + } + + function setCSSVal(el, prop, val) { + try { + if (el[LEXICON.s][prop] !== undefined) + el[LEXICON.s][prop] = parseCSSVal(prop, val); + } catch (e) { } + } + + function parseCSSVal(prop, val) { + if (!_cssNumber[prop.toLowerCase()] && _type(val) == TYPES.n) + val += 'px'; + return val; + } + + function startNextAnimationInQ(animObj, removeFromQ) { + var index; + var nextAnim; + if (removeFromQ !== false) + animObj.q.splice(0, 1); + if (animObj.q[LEXICON.l] > 0) { + nextAnim = animObj.q[0]; + animate(animObj.el, nextAnim.props, nextAnim.duration, nextAnim.easing, nextAnim.complete, true); + } + else { + index = inArray(animObj, _animations); + if (index > -1) + _animations.splice(index, 1); + } + } + + function setAnimationValue(el, prop, value) { + if (prop === _strScrollLeft || prop === _strScrollTop) + el[prop] = value; + else + setCSSVal(el, prop, value); + } + + function animate(el, props, options, easing, complete, guaranteedNext) { + var hasOptions = isPlainObject(options); + var from = {}; + var to = {}; + var i = 0; + var key; + var animObj; + var start; + var progress; + var step; + var specialEasing; + var duration; + if (hasOptions) { + easing = options.easing; + start = options.start; + progress = options.progress; + step = options.step; + specialEasing = options.specialEasing; + complete = options.complete; + duration = options.duration; + } + else + duration = options; + specialEasing = specialEasing || {}; + duration = duration || 400; + easing = easing || 'swing'; + guaranteedNext = guaranteedNext || false; + + for (; i < _animations[LEXICON.l]; i++) { + if (_animations[i].el === el) { + animObj = _animations[i]; + break; + } + } + + if (!animObj) { + animObj = { + el: el, + q: [] + }; + _animations.push(animObj); + } + + for (key in props) { + if (key === _strScrollLeft || key === _strScrollTop) + from[key] = el[key]; + else + from[key] = FakejQuery(el).css(key); + } + + for (key in from) { + if (from[key] !== props[key] && props[key] !== undefined) + to[key] = props[key]; + } + + if (!isEmptyObject(to)) { + var timeNow; + var end; + var percent; + var fromVal; + var toVal; + var easedVal; + var timeStart; + var frame; + var elapsed; + var qPos = guaranteedNext ? 0 : inArray(qObj, animObj.q); + var qObj = { + props: to, + duration: hasOptions ? options : duration, + easing: easing, + complete: complete + }; + if (qPos === -1) { + qPos = animObj.q[LEXICON.l]; + animObj.q.push(qObj); + } + + if (qPos === 0) { + if (duration > 0) { + timeStart = COMPATIBILITY.now(); + frame = function () { + timeNow = COMPATIBILITY.now(); + elapsed = (timeNow - timeStart); + end = qObj.stop || elapsed >= duration; + percent = 1 - ((MATH.max(0, timeStart + duration - timeNow) / duration) || 0); + + for (key in to) { + fromVal = parseFloat(from[key]); + toVal = parseFloat(to[key]); + easedVal = (toVal - fromVal) * EASING[specialEasing[key] || easing](percent, percent * duration, 0, 1, duration) + fromVal; + setAnimationValue(el, key, easedVal); + if (isFunction(step)) { + step(easedVal, { + elem: el, + prop: key, + start: fromVal, + now: easedVal, + end: toVal, + pos: percent, + options: { + easing: easing, + speacialEasing: specialEasing, + duration: duration, + complete: complete, + step: step + }, + startTime: timeStart + }); + } + } + + if (isFunction(progress)) + progress({}, percent, MATH.max(0, duration - elapsed)); + + if (end) { + startNextAnimationInQ(animObj); + if (isFunction(complete)) + complete(); + } + else + qObj.frame = COMPATIBILITY.rAF()(frame); + }; + qObj.frame = COMPATIBILITY.rAF()(frame); + } + else { + for (key in to) + setAnimationValue(el, key, to[key]); + startNextAnimationInQ(animObj); + } + } + } + else if (guaranteedNext) + startNextAnimationInQ(animObj); + } + + function stop(el, clearQ, jumpToEnd) { + var animObj; + var qObj; + var key; + var i = 0; + for (; i < _animations[LEXICON.l]; i++) { + animObj = _animations[i]; + if (animObj.el === el) { + if (animObj.q[LEXICON.l] > 0) { + qObj = animObj.q[0]; + qObj.stop = true; + COMPATIBILITY.cAF()(qObj.frame); + animObj.q.splice(0, 1); + + if (jumpToEnd) + for (key in qObj.props) + setAnimationValue(el, key, qObj.props[key]); + + if (clearQ) + animObj.q = []; + else + startNextAnimationInQ(animObj, false); + } + break; + } + } + } + + function elementIsVisible(el) { + return !!(el[LEXICON.oW] || el[LEXICON.oH] || el.getClientRects()[LEXICON.l]); + } + + function FakejQuery(selector) { + if (arguments[LEXICON.l] === 0) + return this; + + var base = new FakejQuery(); + var elements = selector; + var i = 0; + var elms; + var el; + + if (_type(selector) == TYPES.s) { + elements = []; + if (selector.charAt(0) === '<') { + el = document.createElement('div'); + el.innerHTML = selector; + elms = el.children; + } + else { + elms = document.querySelectorAll(selector); + } + + for (; i < elms[LEXICON.l]; i++) + elements.push(elms[i]); + } + + if (elements) { + if (_type(elements) != TYPES.s && (!isArrayLike(elements) || elements === window || elements === elements.self)) + elements = [elements]; + + for (i = 0; i < elements[LEXICON.l]; i++) + base[i] = elements[i]; + + base[LEXICON.l] = elements[LEXICON.l]; + } + + return base; + }; + + FakejQuery[LEXICON.p] = { + + //EVENTS: + + on: function (eventName, handler) { + eventName = (eventName || _strEmpty).match(_rnothtmlwhite) || [_strEmpty]; + + var eventNameLength = eventName[LEXICON.l]; + var i = 0; + var el; + return this.each(function () { + el = this; + try { + if (el.addEventListener) { + for (; i < eventNameLength; i++) + el.addEventListener(eventName[i], handler); + } + else if (el.detachEvent) { + for (; i < eventNameLength; i++) + el.attachEvent('on' + eventName[i], handler); + } + } catch (e) { } + }); + }, + + off: function (eventName, handler) { + eventName = (eventName || _strEmpty).match(_rnothtmlwhite) || [_strEmpty]; + + var eventNameLength = eventName[LEXICON.l]; + var i = 0; + var el; + return this.each(function () { + el = this; + try { + if (el.removeEventListener) { + for (; i < eventNameLength; i++) + el.removeEventListener(eventName[i], handler); + } + else if (el.detachEvent) { + for (; i < eventNameLength; i++) + el.detachEvent('on' + eventName[i], handler); + } + } catch (e) { } + }); + }, + + one: function (eventName, handler) { + eventName = (eventName || _strEmpty).match(_rnothtmlwhite) || [_strEmpty]; + return this.each(function () { + var el = FakejQuery(this); + FakejQuery.each(eventName, function (i, oneEventName) { + var oneHandler = function (e) { + handler.call(this, e); + el.off(oneEventName, oneHandler); + }; + el.on(oneEventName, oneHandler); + }); + }); + }, + + trigger: function (eventName) { + var el; + var event; + return this.each(function () { + el = this; + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent(eventName, true, false); + el.dispatchEvent(event); + } + else { + el.fireEvent('on' + eventName); + } + }); + }, + + //DOM NODE INSERTING / REMOVING: + + append: function (child) { + return this.each(function () { insertAdjacentElement(this, 'beforeend', child); }); + }, + + prepend: function (child) { + return this.each(function () { insertAdjacentElement(this, 'afterbegin', child); }); + }, + + before: function (child) { + return this.each(function () { insertAdjacentElement(this, 'beforebegin', child); }); + }, + + after: function (child) { + return this.each(function () { insertAdjacentElement(this, 'afterend', child); }); + }, + + remove: function () { + return this.each(function () { + var el = this; + var parentNode = el.parentNode; + if (parentNode != null) + parentNode.removeChild(el); + }); + }, + + unwrap: function () { + var parents = []; + var i; + var el; + var parent; + + this.each(function () { + parent = this.parentNode; + if (inArray(parent, parents) === - 1) + parents.push(parent); + }); + + for (i = 0; i < parents[LEXICON.l]; i++) { + el = parents[i]; + parent = el.parentNode; + while (el.firstChild) + parent.insertBefore(el.firstChild, el); + parent.removeChild(el); + } + + return this; + }, + + wrapAll: function (wrapperHTML) { + var i; + var nodes = this; + var wrapper = FakejQuery(wrapperHTML)[0]; + var deepest = wrapper; + var parent = nodes[0].parentNode; + var previousSibling = nodes[0].previousSibling; + while (deepest.childNodes[LEXICON.l] > 0) + deepest = deepest.childNodes[0]; + + for (i = 0; nodes[LEXICON.l] - i; deepest.firstChild === nodes[0] && i++) + deepest.appendChild(nodes[i]); + + var nextSibling = previousSibling ? previousSibling.nextSibling : parent.firstChild; + parent.insertBefore(wrapper, nextSibling); + + return this; + }, + + wrapInner: function (wrapperHTML) { + return this.each(function () { + var el = FakejQuery(this); + var contents = el.contents(); + + if (contents[LEXICON.l]) + contents.wrapAll(wrapperHTML); + else + el.append(wrapperHTML); + }); + }, + + wrap: function (wrapperHTML) { + return this.each(function () { FakejQuery(this).wrapAll(wrapperHTML); }); + }, + + + //DOM NODE MANIPULATION / INFORMATION: + + css: function (styles, val) { + var el; + var key; + var cptStyle; + var getCptStyle = window.getComputedStyle; + if (_type(styles) == TYPES.s) { + if (val === undefined) { + el = this[0]; + cptStyle = getCptStyle ? getCptStyle(el, null) : el.currentStyle[styles]; + + //https://bugzilla.mozilla.org/show_bug.cgi?id=548397 can be null sometimes if iframe with display: none (firefox only!) + return getCptStyle ? cptStyle != null ? cptStyle.getPropertyValue(styles) : el[LEXICON.s][styles] : cptStyle; + } + else { + return this.each(function () { + setCSSVal(this, styles, val); + }); + } + } + else { + return this.each(function () { + for (key in styles) + setCSSVal(this, key, styles[key]); + }); + } + }, + + hasClass: function (className) { + var elem, i = 0; + var classNamePrepared = _strSpace + className + _strSpace; + var classList; + + while ((elem = this[i++])) { + classList = elem.classList; + if (classList && classList.contains(className)) + return true; + else if (elem.nodeType === 1 && (_strSpace + stripAndCollapse(elem.className + _strEmpty) + _strSpace).indexOf(classNamePrepared) > -1) + return true; + } + + return false; + }, + + addClass: function (className) { + var classes; + var elem; + var cur; + var curValue; + var clazz; + var finalValue; + var supportClassList; + var elmClassList; + var i = 0; + var v = 0; + + if (className) { + classes = className.match(_rnothtmlwhite) || []; + + while ((elem = this[i++])) { + elmClassList = elem.classList; + if (supportClassList === undefined) + supportClassList = elmClassList !== undefined; + + if (supportClassList) { + while ((clazz = classes[v++])) + elmClassList.add(clazz); + } + else { + curValue = elem.className + _strEmpty; + cur = elem.nodeType === 1 && (_strSpace + stripAndCollapse(curValue) + _strSpace); + + if (cur) { + while ((clazz = classes[v++])) + if (cur.indexOf(_strSpace + clazz + _strSpace) < 0) + cur += clazz + _strSpace; + + finalValue = stripAndCollapse(cur); + if (curValue !== finalValue) + elem.className = finalValue; + } + } + } + } + + return this; + }, + + removeClass: function (className) { + var classes; + var elem; + var cur; + var curValue; + var clazz; + var finalValue; + var supportClassList; + var elmClassList; + var i = 0; + var v = 0; + + if (className) { + classes = className.match(_rnothtmlwhite) || []; + + while ((elem = this[i++])) { + elmClassList = elem.classList; + if (supportClassList === undefined) + supportClassList = elmClassList !== undefined; + + if (supportClassList) { + while ((clazz = classes[v++])) + elmClassList.remove(clazz); + } + else { + curValue = elem.className + _strEmpty; + cur = elem.nodeType === 1 && (_strSpace + stripAndCollapse(curValue) + _strSpace); + + if (cur) { + while ((clazz = classes[v++])) + while (cur.indexOf(_strSpace + clazz + _strSpace) > -1) + cur = cur.replace(_strSpace + clazz + _strSpace, _strSpace); + + finalValue = stripAndCollapse(cur); + if (curValue !== finalValue) + elem.className = finalValue; + } + } + } + } + + return this; + }, + + hide: function () { + return this.each(function () { this[LEXICON.s].display = 'none'; }); + }, + + show: function () { + return this.each(function () { this[LEXICON.s].display = 'block'; }); + }, + + attr: function (attrName, value) { + var i = 0; + var el; + while (el = this[i++]) { + if (value === undefined) + return el.getAttribute(attrName); + el.setAttribute(attrName, value); + } + return this; + }, + + removeAttr: function (attrName) { + return this.each(function () { this.removeAttribute(attrName); }); + }, + + offset: function () { + var el = this[0]; + var rect = el[LEXICON.bCR](); + var scrollLeft = window.pageXOffset || document.documentElement[_strScrollLeft]; + var scrollTop = window.pageYOffset || document.documentElement[_strScrollTop]; + return { + top: rect.top + scrollTop, + left: rect.left + scrollLeft + }; + }, + + position: function () { + var el = this[0]; + return { + top: el.offsetTop, + left: el.offsetLeft + }; + }, + + scrollLeft: function (value) { + var i = 0; + var el; + while (el = this[i++]) { + if (value === undefined) + return el[_strScrollLeft]; + el[_strScrollLeft] = value; + } + return this; + }, + + scrollTop: function (value) { + var i = 0; + var el; + while (el = this[i++]) { + if (value === undefined) + return el[_strScrollTop]; + el[_strScrollTop] = value; + } + return this; + }, + + val: function (value) { + var el = this[0]; + if (!value) + return el.value; + el.value = value; + return this; + }, + + + //DOM TRAVERSAL / FILTERING: + + first: function () { + return this.eq(0); + }, + + last: function () { + return this.eq(-1); + }, + + eq: function (index) { + return FakejQuery(this[index >= 0 ? index : this[LEXICON.l] + index]); + }, + + find: function (selector) { + var children = []; + var i; + this.each(function () { + var el = this; + var ch = el.querySelectorAll(selector); + for (i = 0; i < ch[LEXICON.l]; i++) + children.push(ch[i]); + }); + return FakejQuery(children); + }, + + children: function (selector) { + var children = []; + var el; + var ch; + var i; + + this.each(function () { + ch = this.children; + for (i = 0; i < ch[LEXICON.l]; i++) { + el = ch[i]; + if (selector) { + if ((el.matches && el.matches(selector)) || matches(el, selector)) + children.push(el); + } + else + children.push(el); + } + }); + return FakejQuery(children); + }, + + parent: function (selector) { + var parents = []; + var parent; + this.each(function () { + parent = this.parentNode; + if (selector ? FakejQuery(parent).is(selector) : true) + parents.push(parent); + }); + return FakejQuery(parents); + }, + + is: function (selector) { + + var el; + var i; + for (i = 0; i < this[LEXICON.l]; i++) { + el = this[i]; + if (selector === ':visible') + return elementIsVisible(el); + if (selector === ':hidden') + return !elementIsVisible(el); + if ((el.matches && el.matches(selector)) || matches(el, selector)) + return true; + } + return false; + }, + + contents: function () { + var contents = []; + var childs; + var i; + + this.each(function () { + childs = this.childNodes; + for (i = 0; i < childs[LEXICON.l]; i++) + contents.push(childs[i]); + }); + + return FakejQuery(contents); + }, + + each: function (callback) { + return each(this, callback); + }, + + + //ANIMATION: + + animate: function (props, duration, easing, complete) { + return this.each(function () { animate(this, props, duration, easing, complete); }); + }, + + stop: function (clearQ, jump) { + return this.each(function () { stop(this, clearQ, jump); }); + } + }; + + extend(FakejQuery, { + extend: extend, + inArray: inArray, + isEmptyObject: isEmptyObject, + isPlainObject: isPlainObject, + each: each + }); + + return FakejQuery; + })(); + var INSTANCES = (function () { + var _targets = []; + var _instancePropertyString = '__overlayScrollbars__'; + + /** + * Register, unregister or get a certain (or all) instances. + * Register: Pass the target and the instance. + * Unregister: Pass the target and null. + * Get Instance: Pass the target from which the instance shall be got. + * Get Targets: Pass no arguments. + * @param target The target to which the instance shall be registered / from which the instance shall be unregistered / the instance shall be got + * @param instance The instance. + * @returns {*|void} Returns the instance from the given target. + */ + return function (target, instance) { + var argLen = arguments[LEXICON.l]; + if (argLen < 1) { + //return all targets + return _targets; + } + else { + if (instance) { + //register instance + target[_instancePropertyString] = instance; + _targets.push(target); + } + else { + var index = COMPATIBILITY.inA(target, _targets); + if (index > -1) { + if (argLen > 1) { + //unregister instance + delete target[_instancePropertyString]; + _targets.splice(index, 1); + } + else { + //get instance from target + return _targets[index][_instancePropertyString]; + } + } + } + } + } + })(); + var PLUGIN = (function () { + var _plugin; + var _pluginsGlobals; + var _pluginsAutoUpdateLoop; + var _pluginsExtensions = []; + var _pluginsOptions = (function () { + var type = COMPATIBILITY.type; + var possibleTemplateTypes = [ + TYPES.b, //boolean + TYPES.n, //number + TYPES.s, //string + TYPES.a, //array + TYPES.o, //object + TYPES.f, //function + TYPES.z //null + ]; + var restrictedStringsSplit = ' '; + var restrictedStringsPossibilitiesSplit = ':'; + var classNameAllowedValues = [TYPES.z, TYPES.s]; + var numberAllowedValues = TYPES.n; + var booleanNullAllowedValues = [TYPES.z, TYPES.b]; + var booleanTrueTemplate = [true, TYPES.b]; + var booleanFalseTemplate = [false, TYPES.b]; + var callbackTemplate = [null, [TYPES.z, TYPES.f]]; + var updateOnLoadTemplate = [['img'], [TYPES.s, TYPES.a, TYPES.z]]; + var inheritedAttrsTemplate = [['style', 'class'], [TYPES.s, TYPES.a, TYPES.z]]; + var resizeAllowedValues = 'n:none b:both h:horizontal v:vertical'; + var overflowBehaviorAllowedValues = 'v-h:visible-hidden v-s:visible-scroll s:scroll h:hidden'; + var scrollbarsVisibilityAllowedValues = 'v:visible h:hidden a:auto'; + var scrollbarsAutoHideAllowedValues = 'n:never s:scroll l:leave m:move'; + var optionsDefaultsAndTemplate = { + className: ['os-theme-dark', classNameAllowedValues], //null || string + resize: ['none', resizeAllowedValues], //none || both || horizontal || vertical || n || b || h || v + sizeAutoCapable: booleanTrueTemplate, //true || false + clipAlways: booleanTrueTemplate, //true || false + normalizeRTL: booleanTrueTemplate, //true || false + paddingAbsolute: booleanFalseTemplate, //true || false + autoUpdate: [null, booleanNullAllowedValues], //true || false || null + autoUpdateInterval: [33, numberAllowedValues], //number + updateOnLoad: updateOnLoadTemplate, //string || array || null + nativeScrollbarsOverlaid: { + showNativeScrollbars: booleanFalseTemplate, //true || false + initialize: booleanTrueTemplate //true || false + }, + overflowBehavior: { + x: ['scroll', overflowBehaviorAllowedValues], //visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s + y: ['scroll', overflowBehaviorAllowedValues] //visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s + }, + scrollbars: { + visibility: ['auto', scrollbarsVisibilityAllowedValues], //visible || hidden || auto || v || h || a + autoHide: ['never', scrollbarsAutoHideAllowedValues], //never || scroll || leave || move || n || s || l || m + autoHideDelay: [800, numberAllowedValues], //number + dragScrolling: booleanTrueTemplate, //true || false + clickScrolling: booleanFalseTemplate, //true || false + touchSupport: booleanTrueTemplate, //true || false + snapHandle: booleanFalseTemplate //true || false + }, + textarea: { + dynWidth: booleanFalseTemplate, //true || false + dynHeight: booleanFalseTemplate, //true || false + inheritedAttrs: inheritedAttrsTemplate //string || array || null + }, + callbacks: { + onInitialized: callbackTemplate, //null || function + onInitializationWithdrawn: callbackTemplate, //null || function + onDestroyed: callbackTemplate, //null || function + onScrollStart: callbackTemplate, //null || function + onScroll: callbackTemplate, //null || function + onScrollStop: callbackTemplate, //null || function + onOverflowChanged: callbackTemplate, //null || function + onOverflowAmountChanged: callbackTemplate, //null || function + onDirectionChanged: callbackTemplate, //null || function + onContentSizeChanged: callbackTemplate, //null || function + onHostSizeChanged: callbackTemplate, //null || function + onUpdated: callbackTemplate //null || function + } + }; + var convert = function (template) { + var recursive = function (obj) { + var key; + var val; + var valType; + for (key in obj) { + if (!obj[LEXICON.hOP](key)) + continue; + val = obj[key]; + valType = type(val); + if (valType == TYPES.a) + obj[key] = val[template ? 1 : 0]; + else if (valType == TYPES.o) + obj[key] = recursive(val); + } + return obj; + }; + return recursive(FRAMEWORK.extend(true, {}, optionsDefaultsAndTemplate)); + }; + + return { + _defaults: convert(), + + _template: convert(true), + + /** + * Validates the passed object by the passed template. + * @param obj The object which shall be validated. + * @param template The template which defines the allowed values and types. + * @param writeErrors True if errors shall be logged to the console. + * @param diffObj If a object is passed then only valid differences to this object will be returned. + * @returns {{}} A object which contains two objects called "default" and "prepared" which contains only the valid properties of the passed original object and discards not different values compared to the passed diffObj. + */ + _validate: function (obj, template, writeErrors, diffObj) { + var validatedOptions = {}; + var validatedOptionsPrepared = {}; + var objectCopy = FRAMEWORK.extend(true, {}, obj); + var inArray = FRAMEWORK.inArray; + var isEmptyObj = FRAMEWORK.isEmptyObject; + var checkObjectProps = function (data, template, diffData, validatedOptions, validatedOptionsPrepared, prevPropName) { + for (var prop in template) { + if (template[LEXICON.hOP](prop) && data[LEXICON.hOP](prop)) { + var isValid = false; + var isDiff = false; + var templateValue = template[prop]; + var templateValueType = type(templateValue); + var templateIsComplex = templateValueType == TYPES.o; + var templateTypes = !COMPATIBILITY.isA(templateValue) ? [templateValue] : templateValue; + var dataDiffValue = diffData[prop]; + var dataValue = data[prop]; + var dataValueType = type(dataValue); + var propPrefix = prevPropName ? prevPropName + '.' : ''; + var error = "The option \"" + propPrefix + prop + "\" wasn't set, because"; + var errorPossibleTypes = []; + var errorRestrictedStrings = []; + var restrictedStringValuesSplit; + var restrictedStringValuesPossibilitiesSplit; + var isRestrictedValue; + var mainPossibility; + var currType; + var i; + var v; + var j; + + dataDiffValue = dataDiffValue === undefined ? {} : dataDiffValue; + + //if the template has a object as value, it means that the options are complex (verschachtelt) + if (templateIsComplex && dataValueType == TYPES.o) { + validatedOptions[prop] = {}; + validatedOptionsPrepared[prop] = {}; + checkObjectProps(dataValue, templateValue, dataDiffValue, validatedOptions[prop], validatedOptionsPrepared[prop], propPrefix + prop); + FRAMEWORK.each([data, validatedOptions, validatedOptionsPrepared], function (index, value) { + if (isEmptyObj(value[prop])) { + delete value[prop]; + } + }); + } + else if (!templateIsComplex) { + for (i = 0; i < templateTypes[LEXICON.l]; i++) { + currType = templateTypes[i]; + templateValueType = type(currType); + //if currtype is string and starts with restrictedStringPrefix and end with restrictedStringSuffix + isRestrictedValue = templateValueType == TYPES.s && inArray(currType, possibleTemplateTypes) === -1; + if (isRestrictedValue) { + errorPossibleTypes.push(TYPES.s); + + //split it into a array which contains all possible values for example: ["y:yes", "n:no", "m:maybe"] + restrictedStringValuesSplit = currType.split(restrictedStringsSplit); + errorRestrictedStrings = errorRestrictedStrings.concat(restrictedStringValuesSplit); + for (v = 0; v < restrictedStringValuesSplit[LEXICON.l]; v++) { + //split the possible values into their possibiliteis for example: ["y", "yes"] -> the first is always the mainPossibility + restrictedStringValuesPossibilitiesSplit = restrictedStringValuesSplit[v].split(restrictedStringsPossibilitiesSplit); + mainPossibility = restrictedStringValuesPossibilitiesSplit[0]; + for (j = 0; j < restrictedStringValuesPossibilitiesSplit[LEXICON.l]; j++) { + //if any possibility matches with the dataValue, its valid + if (dataValue === restrictedStringValuesPossibilitiesSplit[j]) { + isValid = true; + break; + } + } + if (isValid) + break; + } + } + else { + errorPossibleTypes.push(currType); + + if (dataValueType === currType) { + isValid = true; + break; + } + } + } + + if (isValid) { + isDiff = dataValue !== dataDiffValue; + + if (isDiff) + validatedOptions[prop] = dataValue; + + if (isRestrictedValue ? inArray(dataDiffValue, restrictedStringValuesPossibilitiesSplit) < 0 : isDiff) + validatedOptionsPrepared[prop] = isRestrictedValue ? mainPossibility : dataValue; + } + else if (writeErrors) { + console.warn(error + " it doesn't accept the type [ " + dataValueType.toUpperCase() + " ] with the value of \"" + dataValue + "\".\r\n" + + "Accepted types are: [ " + errorPossibleTypes.join(', ').toUpperCase() + " ]." + + (errorRestrictedStrings[length] > 0 ? "\r\nValid strings are: [ " + errorRestrictedStrings.join(', ').split(restrictedStringsPossibilitiesSplit).join(', ') + " ]." : '')); + } + delete data[prop]; + } + } + } + }; + checkObjectProps(objectCopy, template, diffObj || {}, validatedOptions, validatedOptionsPrepared); + + //add values which aren't specified in the template to the finished validated object to prevent them from being discarded + /* + if(keepForeignProps) { + FRAMEWORK.extend(true, validatedOptions, objectCopy); + FRAMEWORK.extend(true, validatedOptionsPrepared, objectCopy); + } + */ + + if (!isEmptyObj(objectCopy) && writeErrors) + console.warn('The following options are discarded due to invalidity:\r\n' + window.JSON.stringify(objectCopy, null, 2)); + + return { + _default: validatedOptions, + _prepared: validatedOptionsPrepared + }; + } + } + }()); + + /** + * Initializes the object which contains global information about the plugin and each instance of it. + */ + function initOverlayScrollbarsStatics() { + if (!_pluginsGlobals) + _pluginsGlobals = new OverlayScrollbarsGlobals(_pluginsOptions._defaults); + if (!_pluginsAutoUpdateLoop) + _pluginsAutoUpdateLoop = new OverlayScrollbarsAutoUpdateLoop(_pluginsGlobals); + } + + /** + * The global object for the OverlayScrollbars objects. It contains resources which every OverlayScrollbars object needs. This object is initialized only once: if the first OverlayScrollbars object gets initialized. + * @param defaultOptions + * @constructor + */ + function OverlayScrollbarsGlobals(defaultOptions) { + var _base = this; + var strOverflow = 'overflow'; + var strHidden = 'hidden'; + var strScroll = 'scroll'; + var bodyElement = FRAMEWORK('body'); + var scrollbarDummyElement = FRAMEWORK('
'); + var scrollbarDummyElement0 = scrollbarDummyElement[0]; + var dummyContainerChild = FRAMEWORK(scrollbarDummyElement.children('div').eq(0)); + + bodyElement.append(scrollbarDummyElement); + scrollbarDummyElement.hide().show(); //fix IE8 bug (incorrect measuring) + + var nativeScrollbarSize = calcNativeScrollbarSize(scrollbarDummyElement0); + var nativeScrollbarIsOverlaid = { + x: nativeScrollbarSize.x === 0, + y: nativeScrollbarSize.y === 0 + }; + var msie = (function () { + var ua = window.navigator.userAgent; + var strIndexOf = 'indexOf'; + var strSubString = 'substring'; + var msie = ua[strIndexOf]('MSIE '); + var trident = ua[strIndexOf]('Trident/'); + var edge = ua[strIndexOf]('Edge/'); + var rv = ua[strIndexOf]('rv:'); + var result; + var parseIntFunc = parseInt; + + // IE 10 or older => return version number + if (msie > 0) + result = parseIntFunc(ua[strSubString](msie + 5, ua[strIndexOf]('.', msie)), 10); + + // IE 11 => return version number + else if (trident > 0) + result = parseIntFunc(ua[strSubString](rv + 3, ua[strIndexOf]('.', rv)), 10); + + // Edge (IE 12+) => return version number + else if (edge > 0) + result = parseIntFunc(ua[strSubString](edge + 5, ua[strIndexOf]('.', edge)), 10); + + // other browser + return result; + })(); + + FRAMEWORK.extend(_base, { + defaultOptions: defaultOptions, + msie: msie, + autoUpdateLoop: false, + autoUpdateRecommended: !COMPATIBILITY.mO(), + nativeScrollbarSize: nativeScrollbarSize, + nativeScrollbarIsOverlaid: nativeScrollbarIsOverlaid, + nativeScrollbarStyling: (function () { + var result = false; + scrollbarDummyElement.addClass('os-viewport-native-scrollbars-invisible'); + try { + result = (scrollbarDummyElement.css('scrollbar-width') === 'none' && (msie > 9 || !msie)) || window.getComputedStyle(scrollbarDummyElement0, '::-webkit-scrollbar').getPropertyValue('display') === 'none'; + } catch (ex) { } + + //fix opera bug: scrollbar styles will only appear if overflow value is scroll or auto during the activation of the style. + //and set overflow to scroll + //scrollbarDummyElement.css(strOverflow, strHidden).hide().css(strOverflow, strScroll).show(); + //return (scrollbarDummyElement0[LEXICON.oH] - scrollbarDummyElement0[LEXICON.cH]) === 0 && (scrollbarDummyElement0[LEXICON.oW] - scrollbarDummyElement0[LEXICON.cW]) === 0; + + return result; + })(), + overlayScrollbarDummySize: { x: 30, y: 30 }, + cssCalc: VENDORS._cssPropertyValue('width', 'calc', '(1px)') || null, + restrictedMeasuring: (function () { + //https://bugzilla.mozilla.org/show_bug.cgi?id=1439305 + //since 1.11.0 always false -> fixed via CSS (hopefully) + scrollbarDummyElement.css(strOverflow, strHidden); + var scrollSize = { + w: scrollbarDummyElement0[LEXICON.sW], + h: scrollbarDummyElement0[LEXICON.sH] + }; + scrollbarDummyElement.css(strOverflow, 'visible'); + var scrollSize2 = { + w: scrollbarDummyElement0[LEXICON.sW], + h: scrollbarDummyElement0[LEXICON.sH] + }; + return (scrollSize.w - scrollSize2.w) !== 0 || (scrollSize.h - scrollSize2.h) !== 0; + })(), + rtlScrollBehavior: (function () { + scrollbarDummyElement.css({ 'overflow-y': strHidden, 'overflow-x': strScroll, 'direction': 'rtl' }).scrollLeft(0); + var dummyContainerOffset = scrollbarDummyElement.offset(); + var dummyContainerChildOffset = dummyContainerChild.offset(); + //https://github.com/KingSora/OverlayScrollbars/issues/187 + scrollbarDummyElement.scrollLeft(-999); + var dummyContainerChildOffsetAfterScroll = dummyContainerChild.offset(); + return { + //origin direction = determines if the zero scroll position is on the left or right side + //'i' means 'invert' (i === true means that the axis must be inverted to be correct) + //true = on the left side + //false = on the right side + i: dummyContainerOffset.left === dummyContainerChildOffset.left, + //negative = determines if the maximum scroll is positive or negative + //'n' means 'negate' (n === true means that the axis must be negated to be correct) + //true = negative + //false = positive + n: dummyContainerChildOffset.left !== dummyContainerChildOffsetAfterScroll.left + }; + })(), + supportTransform: !!VENDORS._cssProperty('transform'), + supportTransition: !!VENDORS._cssProperty('transition'), + supportPassiveEvents: (function () { + var supportsPassive = false; + try { + window.addEventListener('test', null, Object.defineProperty({}, 'passive', { + get: function () { + supportsPassive = true; + } + })); + } catch (e) { } + return supportsPassive; + })(), + supportResizeObserver: !!COMPATIBILITY.rO(), + supportMutationObserver: !!COMPATIBILITY.mO() + }); + + scrollbarDummyElement.removeAttr(LEXICON.s).remove(); + + //Catch zoom event: + (function () { + if (nativeScrollbarIsOverlaid.x && nativeScrollbarIsOverlaid.y) + return; + + var abs = MATH.abs; + var windowWidth = COMPATIBILITY.wW(); + var windowHeight = COMPATIBILITY.wH(); + var windowDpr = getWindowDPR(); + var onResize = function () { + if (INSTANCES().length > 0) { + var newW = COMPATIBILITY.wW(); + var newH = COMPATIBILITY.wH(); + var deltaW = newW - windowWidth; + var deltaH = newH - windowHeight; + + if (deltaW === 0 && deltaH === 0) + return; + + var deltaWRatio = MATH.round(newW / (windowWidth / 100.0)); + var deltaHRatio = MATH.round(newH / (windowHeight / 100.0)); + var absDeltaW = abs(deltaW); + var absDeltaH = abs(deltaH); + var absDeltaWRatio = abs(deltaWRatio); + var absDeltaHRatio = abs(deltaHRatio); + var newDPR = getWindowDPR(); + + var deltaIsBigger = absDeltaW > 2 && absDeltaH > 2; + var difference = !differenceIsBiggerThanOne(absDeltaWRatio, absDeltaHRatio); + var dprChanged = newDPR !== windowDpr && windowDpr > 0; + var isZoom = deltaIsBigger && difference && dprChanged; + var oldScrollbarSize = _base.nativeScrollbarSize; + var newScrollbarSize; + + if (isZoom) { + bodyElement.append(scrollbarDummyElement); + newScrollbarSize = _base.nativeScrollbarSize = calcNativeScrollbarSize(scrollbarDummyElement[0]); + scrollbarDummyElement.remove(); + if (oldScrollbarSize.x !== newScrollbarSize.x || oldScrollbarSize.y !== newScrollbarSize.y) { + FRAMEWORK.each(INSTANCES(), function () { + if (INSTANCES(this)) + INSTANCES(this).update('zoom'); + }); + } + } + + windowWidth = newW; + windowHeight = newH; + windowDpr = newDPR; + } + }; + + function differenceIsBiggerThanOne(valOne, valTwo) { + var absValOne = abs(valOne); + var absValTwo = abs(valTwo); + return !(absValOne === absValTwo || absValOne + 1 === absValTwo || absValOne - 1 === absValTwo); + } + + function getWindowDPR() { + var dDPI = window.screen.deviceXDPI || 0; + var sDPI = window.screen.logicalXDPI || 1; + return window.devicePixelRatio || (dDPI / sDPI); + } + + FRAMEWORK(window).on('resize', onResize); + })(); + + function calcNativeScrollbarSize(measureElement) { + return { + x: measureElement[LEXICON.oH] - measureElement[LEXICON.cH], + y: measureElement[LEXICON.oW] - measureElement[LEXICON.cW] + }; + } + } + + /** + * The object which manages the auto update loop for all OverlayScrollbars objects. This object is initialized only once: if the first OverlayScrollbars object gets initialized. + * @constructor + */ + function OverlayScrollbarsAutoUpdateLoop(globals) { + var _base = this; + var _inArray = FRAMEWORK.inArray; + var _getNow = COMPATIBILITY.now; + var _strAutoUpdate = 'autoUpdate'; + var _strAutoUpdateInterval = _strAutoUpdate + 'Interval'; + var _strLength = LEXICON.l; + var _loopingInstances = []; + var _loopingInstancesIntervalCache = []; + var _loopIsActive = false; + var _loopIntervalDefault = 33; + var _loopInterval = _loopIntervalDefault; + var _loopTimeOld = _getNow(); + var _loopID; + + + /** + * The auto update loop which will run every 50 milliseconds or less if the update interval of a instance is lower than 50 milliseconds. + */ + var loop = function () { + if (_loopingInstances[_strLength] > 0 && _loopIsActive) { + _loopID = COMPATIBILITY.rAF()(function () { + loop(); + }); + var timeNew = _getNow(); + var timeDelta = timeNew - _loopTimeOld; + var lowestInterval; + var instance; + var instanceOptions; + var instanceAutoUpdateAllowed; + var instanceAutoUpdateInterval; + var now; + + if (timeDelta > _loopInterval) { + _loopTimeOld = timeNew - (timeDelta % _loopInterval); + lowestInterval = _loopIntervalDefault; + for (var i = 0; i < _loopingInstances[_strLength]; i++) { + instance = _loopingInstances[i]; + if (instance !== undefined) { + instanceOptions = instance.options(); + instanceAutoUpdateAllowed = instanceOptions[_strAutoUpdate]; + instanceAutoUpdateInterval = MATH.max(1, instanceOptions[_strAutoUpdateInterval]); + now = _getNow(); + + if ((instanceAutoUpdateAllowed === true || instanceAutoUpdateAllowed === null) && (now - _loopingInstancesIntervalCache[i]) > instanceAutoUpdateInterval) { + instance.update('auto'); + _loopingInstancesIntervalCache[i] = new Date(now += instanceAutoUpdateInterval); + } + + lowestInterval = MATH.max(1, MATH.min(lowestInterval, instanceAutoUpdateInterval)); + } + } + _loopInterval = lowestInterval; + } + } else { + _loopInterval = _loopIntervalDefault; + } + }; + + /** + * Add OverlayScrollbars instance to the auto update loop. Only successful if the instance isn't already added. + * @param instance The instance which shall be updated in a loop automatically. + */ + _base.add = function (instance) { + if (_inArray(instance, _loopingInstances) === -1) { + _loopingInstances.push(instance); + _loopingInstancesIntervalCache.push(_getNow()); + if (_loopingInstances[_strLength] > 0 && !_loopIsActive) { + _loopIsActive = true; + globals.autoUpdateLoop = _loopIsActive; + loop(); + } + } + }; + + /** + * Remove OverlayScrollbars instance from the auto update loop. Only successful if the instance was added before. + * @param instance The instance which shall be updated in a loop automatically. + */ + _base.remove = function (instance) { + var index = _inArray(instance, _loopingInstances); + if (index > -1) { + //remove from loopingInstances list + _loopingInstancesIntervalCache.splice(index, 1); + _loopingInstances.splice(index, 1); + + //correct update loop behavior + if (_loopingInstances[_strLength] === 0 && _loopIsActive) { + _loopIsActive = false; + globals.autoUpdateLoop = _loopIsActive; + if (_loopID !== undefined) { + COMPATIBILITY.cAF()(_loopID); + _loopID = -1; + } + } + } + }; + } + + /** + * A object which manages the scrollbars visibility of the target element. + * @param pluginTargetElement The element from which the scrollbars shall be hidden. + * @param options The custom options. + * @param extensions The custom extensions. + * @param globals + * @param autoUpdateLoop + * @returns {*} + * @constructor + */ + function OverlayScrollbarsInstance(pluginTargetElement, options, extensions, globals, autoUpdateLoop) { + //shortcuts + var type = COMPATIBILITY.type; + var inArray = FRAMEWORK.inArray; + var each = FRAMEWORK.each; + + //make correct instanceof + var _base = new _plugin(); + var _frameworkProto = FRAMEWORK[LEXICON.p]; + + //if passed element is no HTML element: skip and return + if (!isHTMLElement(pluginTargetElement)) + return; + + //if passed element is already initialized: set passed options if there are any and return its instance + if (INSTANCES(pluginTargetElement)) { + var inst = INSTANCES(pluginTargetElement); + inst.options(options); + return inst; + } + + //globals: + var _nativeScrollbarIsOverlaid; + var _overlayScrollbarDummySize; + var _rtlScrollBehavior; + var _autoUpdateRecommended; + var _msieVersion; + var _nativeScrollbarStyling; + var _cssCalc; + var _nativeScrollbarSize; + var _supportTransition; + var _supportTransform; + var _supportPassiveEvents; + var _supportResizeObserver; + var _supportMutationObserver; + var _restrictedMeasuring; + + //general readonly: + var _initialized; + var _destroyed; + var _isTextarea; + var _isBody; + var _documentMixed; + var _domExists; + + //general: + var _isBorderBox; + var _sizeAutoObserverAdded; + var _paddingX; + var _paddingY; + var _borderX; + var _borderY; + var _marginX; + var _marginY; + var _isRTL; + var _sleeping; + var _contentBorderSize = {}; + var _scrollHorizontalInfo = {}; + var _scrollVerticalInfo = {}; + var _viewportSize = {}; + var _nativeScrollbarMinSize = {}; + + //naming: + var _strMinusHidden = '-hidden'; + var _strMarginMinus = 'margin-'; + var _strPaddingMinus = 'padding-'; + var _strBorderMinus = 'border-'; + var _strTop = 'top'; + var _strRight = 'right'; + var _strBottom = 'bottom'; + var _strLeft = 'left'; + var _strMinMinus = 'min-'; + var _strMaxMinus = 'max-'; + var _strWidth = 'width'; + var _strHeight = 'height'; + var _strFloat = 'float'; + var _strEmpty = ''; + var _strAuto = 'auto'; + var _strSync = 'sync'; + var _strScroll = 'scroll'; + var _strHundredPercent = '100%'; + var _strX = 'x'; + var _strY = 'y'; + var _strDot = '.'; + var _strSpace = ' '; + var _strScrollbar = 'scrollbar'; + var _strMinusHorizontal = '-horizontal'; + var _strMinusVertical = '-vertical'; + var _strScrollLeft = _strScroll + 'Left'; + var _strScrollTop = _strScroll + 'Top'; + var _strMouseTouchDownEvent = 'mousedown touchstart'; + var _strMouseTouchUpEvent = 'mouseup touchend touchcancel'; + var _strMouseTouchMoveEvent = 'mousemove touchmove'; + var _strMouseEnter = 'mouseenter'; + var _strMouseLeave = 'mouseleave'; + var _strKeyDownEvent = 'keydown'; + var _strKeyUpEvent = 'keyup'; + var _strSelectStartEvent = 'selectstart'; + var _strTransitionEndEvent = 'transitionend webkitTransitionEnd oTransitionEnd'; + var _strResizeObserverProperty = '__overlayScrollbarsRO__'; + + //class names: + var _cassNamesPrefix = 'os-'; + var _classNameHTMLElement = _cassNamesPrefix + 'html'; + var _classNameHostElement = _cassNamesPrefix + 'host'; + var _classNameHostElementForeign = _classNameHostElement + '-foreign'; + var _classNameHostTextareaElement = _classNameHostElement + '-textarea'; + var _classNameHostScrollbarHorizontalHidden = _classNameHostElement + '-' + _strScrollbar + _strMinusHorizontal + _strMinusHidden; + var _classNameHostScrollbarVerticalHidden = _classNameHostElement + '-' + _strScrollbar + _strMinusVertical + _strMinusHidden; + var _classNameHostTransition = _classNameHostElement + '-transition'; + var _classNameHostRTL = _classNameHostElement + '-rtl'; + var _classNameHostResizeDisabled = _classNameHostElement + '-resize-disabled'; + var _classNameHostScrolling = _classNameHostElement + '-scrolling'; + var _classNameHostOverflow = _classNameHostElement + '-overflow'; + var _classNameHostOverflow = _classNameHostElement + '-overflow'; + var _classNameHostOverflowX = _classNameHostOverflow + '-x'; + var _classNameHostOverflowY = _classNameHostOverflow + '-y'; + var _classNameTextareaElement = _cassNamesPrefix + 'textarea'; + var _classNameTextareaCoverElement = _classNameTextareaElement + '-cover'; + var _classNamePaddingElement = _cassNamesPrefix + 'padding'; + var _classNameViewportElement = _cassNamesPrefix + 'viewport'; + var _classNameViewportNativeScrollbarsInvisible = _classNameViewportElement + '-native-scrollbars-invisible'; + var _classNameViewportNativeScrollbarsOverlaid = _classNameViewportElement + '-native-scrollbars-overlaid'; + var _classNameContentElement = _cassNamesPrefix + 'content'; + var _classNameContentArrangeElement = _cassNamesPrefix + 'content-arrange'; + var _classNameContentGlueElement = _cassNamesPrefix + 'content-glue'; + var _classNameSizeAutoObserverElement = _cassNamesPrefix + 'size-auto-observer'; + var _classNameResizeObserverElement = _cassNamesPrefix + 'resize-observer'; + var _classNameResizeObserverItemElement = _cassNamesPrefix + 'resize-observer-item'; + var _classNameResizeObserverItemFinalElement = _classNameResizeObserverItemElement + '-final'; + var _classNameTextInherit = _cassNamesPrefix + 'text-inherit'; + var _classNameScrollbar = _cassNamesPrefix + _strScrollbar; + var _classNameScrollbarTrack = _classNameScrollbar + '-track'; + var _classNameScrollbarTrackOff = _classNameScrollbarTrack + '-off'; + var _classNameScrollbarHandle = _classNameScrollbar + '-handle'; + var _classNameScrollbarHandleOff = _classNameScrollbarHandle + '-off'; + var _classNameScrollbarUnusable = _classNameScrollbar + '-unusable'; + var _classNameScrollbarAutoHidden = _classNameScrollbar + '-' + _strAuto + _strMinusHidden; + var _classNameScrollbarCorner = _classNameScrollbar + '-corner'; + var _classNameScrollbarCornerResize = _classNameScrollbarCorner + '-resize'; + var _classNameScrollbarCornerResizeB = _classNameScrollbarCornerResize + '-both'; + var _classNameScrollbarCornerResizeH = _classNameScrollbarCornerResize + _strMinusHorizontal; + var _classNameScrollbarCornerResizeV = _classNameScrollbarCornerResize + _strMinusVertical; + var _classNameScrollbarHorizontal = _classNameScrollbar + _strMinusHorizontal; + var _classNameScrollbarVertical = _classNameScrollbar + _strMinusVertical; + var _classNameDragging = _cassNamesPrefix + 'dragging'; + var _classNameThemeNone = _cassNamesPrefix + 'theme-none'; + var _classNamesDynamicDestroy = [ + _classNameViewportNativeScrollbarsInvisible, + _classNameViewportNativeScrollbarsOverlaid, + _classNameScrollbarTrackOff, + _classNameScrollbarHandleOff, + _classNameScrollbarUnusable, + _classNameScrollbarAutoHidden, + _classNameScrollbarCornerResize, + _classNameScrollbarCornerResizeB, + _classNameScrollbarCornerResizeH, + _classNameScrollbarCornerResizeV, + _classNameDragging].join(_strSpace); + + //callbacks: + var _callbacksInitQeueue = []; + + //attrs viewport shall inherit from target + var _viewportAttrsFromTarget = [LEXICON.ti]; + + //options: + var _defaultOptions; + var _currentOptions; + var _currentPreparedOptions; + + //extensions: + var _extensions = {}; + var _extensionsPrivateMethods = 'added removed on contract'; + + //update + var _lastUpdateTime; + var _swallowedUpdateHints = {}; + var _swallowedUpdateTimeout; + var _swallowUpdateLag = 42; + var _updateOnLoadEventName = 'load'; + var _updateOnLoadElms = []; + + //DOM elements: + var _windowElement; + var _documentElement; + var _htmlElement; + var _bodyElement; + var _targetElement; //the target element of this OverlayScrollbars object + var _hostElement; //the host element of this OverlayScrollbars object -> may be the same as targetElement + var _sizeAutoObserverElement; //observes size auto changes + var _sizeObserverElement; //observes size and padding changes + var _paddingElement; //manages the padding + var _viewportElement; //is the viewport of our scrollbar model + var _contentElement; //the element which holds the content + var _contentArrangeElement; //is needed for correct sizing of the content element (only if native scrollbars are overlays) + var _contentGlueElement; //has always the size of the content element + var _textareaCoverElement; //only applied if target is a textarea element. Used for correct size calculation and for prevention of uncontrolled scrolling + var _scrollbarCornerElement; + var _scrollbarHorizontalElement; + var _scrollbarHorizontalTrackElement; + var _scrollbarHorizontalHandleElement; + var _scrollbarVerticalElement; + var _scrollbarVerticalTrackElement; + var _scrollbarVerticalHandleElement; + var _windowElementNative; + var _documentElementNative; + var _targetElementNative; + var _hostElementNative; + var _sizeAutoObserverElementNative; + var _sizeObserverElementNative; + var _paddingElementNative; + var _viewportElementNative; + var _contentElementNative; + + //Cache: + var _hostSizeCache; + var _contentScrollSizeCache; + var _arrangeContentSizeCache; + var _hasOverflowCache; + var _hideOverflowCache; + var _widthAutoCache; + var _heightAutoCache; + var _cssBoxSizingCache; + var _cssPaddingCache; + var _cssBorderCache; + var _cssMarginCache; + var _cssDirectionCache; + var _cssDirectionDetectedCache; + var _paddingAbsoluteCache; + var _clipAlwaysCache; + var _contentGlueSizeCache; + var _overflowBehaviorCache; + var _overflowAmountCache; + var _ignoreOverlayScrollbarHidingCache; + var _autoUpdateCache; + var _sizeAutoCapableCache; + var _contentElementScrollSizeChangeDetectedCache; + var _hostElementSizeChangeDetectedCache; + var _scrollbarsVisibilityCache; + var _scrollbarsAutoHideCache; + var _scrollbarsClickScrollingCache; + var _scrollbarsDragScrollingCache; + var _resizeCache; + var _normalizeRTLCache; + var _classNameCache; + var _oldClassName; + var _textareaAutoWrappingCache; + var _textareaInfoCache; + var _textareaSizeCache; + var _textareaDynHeightCache; + var _textareaDynWidthCache; + var _bodyMinSizeCache; + var _updateAutoCache = {}; + + //MutationObserver: + var _mutationObserverHost; + var _mutationObserverContent; + var _mutationObserverHostCallback; + var _mutationObserverContentCallback; + var _mutationObserversConnected; + var _mutationObserverAttrsTextarea = ['wrap', 'cols', 'rows']; + var _mutationObserverAttrsHost = [LEXICON.i, LEXICON.c, LEXICON.s, 'open'].concat(_viewportAttrsFromTarget); + + //events: + var _destroyEvents = []; + + //textarea: + var _textareaHasFocus; + + //scrollbars: + var _scrollbarsAutoHideTimeoutId; + var _scrollbarsAutoHideMoveTimeoutId; + var _scrollbarsAutoHideDelay; + var _scrollbarsAutoHideNever; + var _scrollbarsAutoHideScroll; + var _scrollbarsAutoHideMove; + var _scrollbarsAutoHideLeave; + var _scrollbarsHandleHovered; + var _scrollbarsHandlesDefineScrollPos; + + //resize + var _resizeNone; + var _resizeBoth; + var _resizeHorizontal; + var _resizeVertical; + + + //==== Event Listener ====// + + /** + * Adds or removes a event listener from the given element. + * @param element The element to which the event listener shall be applied or removed. + * @param eventNames The name(s) of the events. + * @param listener The method which shall be called. + * @param remove True if the handler shall be removed, false or undefined if the handler shall be added. + * @param passiveOrOptions The options for the event. + */ + function setupResponsiveEventListener(element, eventNames, listener, remove, passiveOrOptions) { + var collected = COMPATIBILITY.isA(eventNames) && COMPATIBILITY.isA(listener); + var method = remove ? 'removeEventListener' : 'addEventListener'; + var onOff = remove ? 'off' : 'on'; + var events = collected ? false : eventNames.split(_strSpace) + var i = 0; + + var passiveOrOptionsIsObj = FRAMEWORK.isPlainObject(passiveOrOptions); + var passive = (_supportPassiveEvents && (passiveOrOptionsIsObj ? (passiveOrOptions._passive) : passiveOrOptions)) || false; + var capture = passiveOrOptionsIsObj && (passiveOrOptions._capture || false); + var nativeParam = _supportPassiveEvents ? { + passive: passive, + capture: capture, + } : capture; + + if (collected) { + for (; i < eventNames[LEXICON.l]; i++) + setupResponsiveEventListener(element, eventNames[i], listener[i], remove, passiveOrOptions); + } + else { + for (; i < events[LEXICON.l]; i++) { + if(_supportPassiveEvents) { + element[0][method](events[i], listener, nativeParam); + } + else { + element[onOff](events[i], listener); + } + } + } + } + + + function addDestroyEventListener(element, eventNames, listener, passive) { + setupResponsiveEventListener(element, eventNames, listener, false, passive); + _destroyEvents.push(COMPATIBILITY.bind(setupResponsiveEventListener, 0, element, eventNames, listener, true, passive)); + } + + //==== Resize Observer ====// + + /** + * Adds or removes a resize observer from the given element. + * @param targetElement The element to which the resize observer shall be added or removed. + * @param onElementResizedCallback The callback which is fired every time the resize observer registers a size change or false / undefined if the resizeObserver shall be removed. + */ + function setupResizeObserver(targetElement, onElementResizedCallback) { + if (targetElement) { + var resizeObserver = COMPATIBILITY.rO(); + var strAnimationStartEvent = 'animationstart mozAnimationStart webkitAnimationStart MSAnimationStart'; + var strChildNodes = 'childNodes'; + var constScroll = 3333333; + var callback = function () { + targetElement[_strScrollTop](constScroll)[_strScrollLeft](_isRTL ? _rtlScrollBehavior.n ? -constScroll : _rtlScrollBehavior.i ? 0 : constScroll : constScroll); + onElementResizedCallback(); + }; + //add resize observer: + if (onElementResizedCallback) { + if (_supportResizeObserver) { + var element = targetElement.addClass('observed').append(generateDiv(_classNameResizeObserverElement)).contents()[0]; + var observer = element[_strResizeObserverProperty] = new resizeObserver(callback); + observer.observe(element); + } + else { + if (_msieVersion > 9 || !_autoUpdateRecommended) { + targetElement.prepend( + generateDiv(_classNameResizeObserverElement, + generateDiv({ c: _classNameResizeObserverItemElement, dir: 'ltr' }, + generateDiv(_classNameResizeObserverItemElement, + generateDiv(_classNameResizeObserverItemFinalElement) + ) + + generateDiv(_classNameResizeObserverItemElement, + generateDiv({ c: _classNameResizeObserverItemFinalElement, style: 'width: 200%; height: 200%' }) + ) + ) + ) + ); + + var observerElement = targetElement[0][strChildNodes][0][strChildNodes][0]; + var shrinkElement = FRAMEWORK(observerElement[strChildNodes][1]); + var expandElement = FRAMEWORK(observerElement[strChildNodes][0]); + var expandElementChild = FRAMEWORK(expandElement[0][strChildNodes][0]); + var widthCache = observerElement[LEXICON.oW]; + var heightCache = observerElement[LEXICON.oH]; + var isDirty; + var rAFId; + var currWidth; + var currHeight; + var factor = 2; + var nativeScrollbarSize = globals.nativeScrollbarSize; //care don't make changes to this object!!! + var reset = function () { + /* + var sizeResetWidth = observerElement[LEXICON.oW] + nativeScrollbarSize.x * factor + nativeScrollbarSize.y * factor + _overlayScrollbarDummySize.x + _overlayScrollbarDummySize.y; + var sizeResetHeight = observerElement[LEXICON.oH] + nativeScrollbarSize.x * factor + nativeScrollbarSize.y * factor + _overlayScrollbarDummySize.x + _overlayScrollbarDummySize.y; + var expandChildCSS = {}; + expandChildCSS[_strWidth] = sizeResetWidth; + expandChildCSS[_strHeight] = sizeResetHeight; + expandElementChild.css(expandChildCSS); + + + expandElement[_strScrollLeft](sizeResetWidth)[_strScrollTop](sizeResetHeight); + shrinkElement[_strScrollLeft](sizeResetWidth)[_strScrollTop](sizeResetHeight); + */ + expandElement[_strScrollLeft](constScroll)[_strScrollTop](constScroll); + shrinkElement[_strScrollLeft](constScroll)[_strScrollTop](constScroll); + }; + var onResized = function () { + rAFId = 0; + if (!isDirty) + return; + + widthCache = currWidth; + heightCache = currHeight; + callback(); + }; + var onScroll = function (event) { + currWidth = observerElement[LEXICON.oW]; + currHeight = observerElement[LEXICON.oH]; + isDirty = currWidth != widthCache || currHeight != heightCache; + + if (event && isDirty && !rAFId) { + COMPATIBILITY.cAF()(rAFId); + rAFId = COMPATIBILITY.rAF()(onResized); + } + else if (!event) + onResized(); + + reset(); + if (event) { + COMPATIBILITY.prvD(event); + COMPATIBILITY.stpP(event); + } + return false; + }; + var expandChildCSS = {}; + var observerElementCSS = {}; + + setTopRightBottomLeft(observerElementCSS, _strEmpty, [ + -((nativeScrollbarSize.y + 1) * factor), + nativeScrollbarSize.x * -factor, + nativeScrollbarSize.y * -factor, + -((nativeScrollbarSize.x + 1) * factor) + ]); + + FRAMEWORK(observerElement).css(observerElementCSS); + expandElement.on(_strScroll, onScroll); + shrinkElement.on(_strScroll, onScroll); + targetElement.on(strAnimationStartEvent, function () { + onScroll(false); + }); + //lets assume that the divs will never be that large and a constant value is enough + expandChildCSS[_strWidth] = constScroll; + expandChildCSS[_strHeight] = constScroll; + expandElementChild.css(expandChildCSS); + + reset(); + } + else { + var attachEvent = _documentElementNative.attachEvent; + var isIE = _msieVersion !== undefined; + if (attachEvent) { + targetElement.prepend(generateDiv(_classNameResizeObserverElement)); + findFirst(targetElement, _strDot + _classNameResizeObserverElement)[0].attachEvent('onresize', callback); + } + else { + var obj = _documentElementNative.createElement(TYPES.o); + obj.setAttribute(LEXICON.ti, '-1'); + obj.setAttribute(LEXICON.c, _classNameResizeObserverElement); + obj.onload = function () { + var wnd = this.contentDocument.defaultView; + wnd.addEventListener('resize', callback); + wnd.document.documentElement.style.display = 'none'; + }; + obj.type = 'text/html'; + if (isIE) + targetElement.prepend(obj); + obj.data = 'about:blank'; + if (!isIE) + targetElement.prepend(obj); + targetElement.on(strAnimationStartEvent, callback); + } + } + } + + if (targetElement[0] === _sizeObserverElementNative) { + var directionChanged = function () { + var dir = _hostElement.css('direction'); + var css = {}; + var scrollLeftValue = 0; + var result = false; + if (dir !== _cssDirectionDetectedCache) { + if (dir === 'ltr') { + css[_strLeft] = 0; + css[_strRight] = _strAuto; + scrollLeftValue = constScroll; + } + else { + css[_strLeft] = _strAuto; + css[_strRight] = 0; + scrollLeftValue = _rtlScrollBehavior.n ? -constScroll : _rtlScrollBehavior.i ? 0 : constScroll; + } + //execution order is important for IE!!! + _sizeObserverElement.children().eq(0).css(css); + _sizeObserverElement[_strScrollLeft](scrollLeftValue)[_strScrollTop](constScroll); + _cssDirectionDetectedCache = dir; + result = true; + } + return result; + }; + directionChanged(); + addDestroyEventListener(targetElement, _strScroll, function (event) { + if (directionChanged()) + update(); + COMPATIBILITY.prvD(event); + COMPATIBILITY.stpP(event); + return false; + }); + } + } + //remove resize observer: + else { + if (_supportResizeObserver) { + var element = targetElement.contents()[0]; + var resizeObserverObj = element[_strResizeObserverProperty]; + if (resizeObserverObj) { + resizeObserverObj.disconnect(); + delete element[_strResizeObserverProperty]; + } + } + else { + remove(targetElement.children(_strDot + _classNameResizeObserverElement).eq(0)); + } + } + } + } + + /** + * Freezes or unfreezes the given resize observer. + * @param targetElement The element to which the target resize observer is applied. + * @param freeze True if the resize observer shall be frozen, false otherwise. + + function freezeResizeObserver(targetElement, freeze) { + if (targetElement !== undefined) { + if(freeze) { + if (_supportResizeObserver) { + var element = targetElement.contents()[0]; + element[_strResizeObserverProperty].unobserve(element); + } + else { + targetElement = targetElement.children(_strDot + _classNameResizeObserverElement).eq(0); + var w = targetElement.css(_strWidth); + var h = targetElement.css(_strHeight); + var css = {}; + css[_strWidth] = w; + css[_strHeight] = h; + targetElement.css(css); + } + } + else { + if (_supportResizeObserver) { + var element = targetElement.contents()[0]; + element[_strResizeObserverProperty].observe(element); + } + else { + var css = { }; + css[_strHeight] = _strEmpty; + css[_strWidth] = _strEmpty; + targetElement.children(_strDot + _classNameResizeObserverElement).eq(0).css(css); + } + } + } + } + */ + + + //==== Mutation Observers ====// + + /** + * Creates MutationObservers for the host and content Element if they are supported. + */ + function createMutationObservers() { + if (_supportMutationObserver) { + var mutationObserverContentLag = 11; + var mutationObserver = COMPATIBILITY.mO(); + var contentLastUpdate = COMPATIBILITY.now(); + var mutationTarget; + var mutationAttrName; + var mutationIsClass; + var oldMutationVal; + var newClassVal; + var hostClassNameRegex; + var contentTimeout; + var now; + var sizeAuto; + var action; + + _mutationObserverHostCallback = function (mutations) { + + var doUpdate = false; + var doUpdateForce = false; + var mutation; + var mutatedAttrs = []; + + if (_initialized && !_sleeping) { + each(mutations, function () { + mutation = this; + mutationTarget = mutation.target; + mutationAttrName = mutation.attributeName; + mutationIsClass = mutationAttrName === LEXICON.c; + oldMutationVal = mutation.oldValue; + newClassVal = mutationTarget.className; + + if (_domExists && mutationIsClass && !doUpdateForce) { + // if old class value contains _classNameHostElementForeign and new class value doesn't + if (oldMutationVal.indexOf(_classNameHostElementForeign) > -1 && newClassVal.indexOf(_classNameHostElementForeign) < 0) { + hostClassNameRegex = createHostClassNameRegExp(true); + _hostElementNative.className = newClassVal.split(_strSpace).concat(oldMutationVal.split(_strSpace).filter(function (name) { + return name.match(hostClassNameRegex); + })).join(_strSpace); + doUpdate = doUpdateForce = true; + } + } + + if (!doUpdate) { + doUpdate = mutationIsClass + ? hostClassNamesChanged(oldMutationVal, newClassVal) + : mutationAttrName === LEXICON.s + ? oldMutationVal !== mutationTarget[LEXICON.s].cssText + : true; + } + + mutatedAttrs.push(mutationAttrName); + }); + + updateViewportAttrsFromTarget(mutatedAttrs); + + if (doUpdate) + _base.update(doUpdateForce || _strAuto); + } + return doUpdate; + }; + _mutationObserverContentCallback = function (mutations) { + var doUpdate = false; + var mutation; + + if (_initialized && !_sleeping) { + each(mutations, function () { + mutation = this; + doUpdate = isUnknownMutation(mutation); + return !doUpdate; + }); + + if (doUpdate) { + now = COMPATIBILITY.now(); + sizeAuto = (_heightAutoCache || _widthAutoCache); + action = function () { + if (!_destroyed) { + contentLastUpdate = now; + + //if cols, rows or wrap attr was changed + if (_isTextarea) + textareaUpdate(); + + if (sizeAuto) + update(); + else + _base.update(_strAuto); + } + }; + clearTimeout(contentTimeout); + if (mutationObserverContentLag <= 0 || now - contentLastUpdate > mutationObserverContentLag || !sizeAuto) + action(); + else + contentTimeout = setTimeout(action, mutationObserverContentLag); + } + } + return doUpdate; + } + + _mutationObserverHost = new mutationObserver(_mutationObserverHostCallback); + _mutationObserverContent = new mutationObserver(_mutationObserverContentCallback); + } + } + + /** + * Connects the MutationObservers if they are supported. + */ + function connectMutationObservers() { + if (_supportMutationObserver && !_mutationObserversConnected) { + _mutationObserverHost.observe(_hostElementNative, { + attributes: true, + attributeOldValue: true, + attributeFilter: _mutationObserverAttrsHost + }); + + _mutationObserverContent.observe(_isTextarea ? _targetElementNative : _contentElementNative, { + attributes: true, + attributeOldValue: true, + subtree: !_isTextarea, + childList: !_isTextarea, + characterData: !_isTextarea, + attributeFilter: _isTextarea ? _mutationObserverAttrsTextarea : _mutationObserverAttrsHost + }); + + _mutationObserversConnected = true; + } + } + + /** + * Disconnects the MutationObservers if they are supported. + */ + function disconnectMutationObservers() { + if (_supportMutationObserver && _mutationObserversConnected) { + _mutationObserverHost.disconnect(); + _mutationObserverContent.disconnect(); + + _mutationObserversConnected = false; + } + } + + + //==== Events of elements ====// + + /** + * This method gets called every time the host element gets resized. IMPORTANT: Padding changes are detected too!! + * It refreshes the hostResizedEventArgs and the hostSizeResizeCache. + * If there are any size changes, the update method gets called. + */ + function hostOnResized() { + if (!_sleeping) { + var changed; + var hostSize = { + w: _sizeObserverElementNative[LEXICON.sW], + h: _sizeObserverElementNative[LEXICON.sH] + }; + + changed = checkCache(hostSize, _hostElementSizeChangeDetectedCache); + _hostElementSizeChangeDetectedCache = hostSize; + if (changed) + update({ _hostSizeChanged: true }); + } + } + + /** + * The mouse enter event of the host element. This event is only needed for the autoHide feature. + */ + function hostOnMouseEnter() { + if (_scrollbarsAutoHideLeave) + refreshScrollbarsAutoHide(true); + } + + /** + * The mouse leave event of the host element. This event is only needed for the autoHide feature. + */ + function hostOnMouseLeave() { + if (_scrollbarsAutoHideLeave && !_bodyElement.hasClass(_classNameDragging)) + refreshScrollbarsAutoHide(false); + } + + /** + * The mouse move event of the host element. This event is only needed for the autoHide "move" feature. + */ + function hostOnMouseMove() { + if (_scrollbarsAutoHideMove) { + refreshScrollbarsAutoHide(true); + clearTimeout(_scrollbarsAutoHideMoveTimeoutId); + _scrollbarsAutoHideMoveTimeoutId = setTimeout(function () { + if (_scrollbarsAutoHideMove && !_destroyed) + refreshScrollbarsAutoHide(false); + }, 100); + } + } + + /** + * Prevents text from deselection if attached to the document element on the mousedown event of a DOM element. + * @param event The select start event. + */ + function documentOnSelectStart(event) { + COMPATIBILITY.prvD(event); + return false; + } + + /** + * A callback which will be called after a element has loaded. + */ + function updateOnLoadCallback(event) { + var elm = FRAMEWORK(event.target); + + eachUpdateOnLoad(function (i, updateOnLoadSelector) { + if (elm.is(updateOnLoadSelector)) { + update({ _contentSizeChanged: true }); + } + }); + } + + /** + * Adds or removes mouse & touch events of the host element. (for handling auto-hiding of the scrollbars) + * @param destroy Indicates whether the events shall be added or removed. + */ + function setupHostMouseTouchEvents(destroy) { + if (!destroy) + setupHostMouseTouchEvents(true); + + setupResponsiveEventListener(_hostElement, + _strMouseTouchMoveEvent.split(_strSpace)[0], + hostOnMouseMove, + (!_scrollbarsAutoHideMove || destroy), true); + setupResponsiveEventListener(_hostElement, + [_strMouseEnter, _strMouseLeave], + [hostOnMouseEnter, hostOnMouseLeave], + (!_scrollbarsAutoHideLeave || destroy), true); + + //if the plugin is initialized and the mouse is over the host element, make the scrollbars visible + if (!_initialized && !destroy) + _hostElement.one('mouseover', hostOnMouseEnter); + } + + + //==== Update Detection ====// + + /** + * Measures the min width and min height of the body element and refreshes the related cache. + * @returns {boolean} True if the min width or min height has changed, false otherwise. + */ + function bodyMinSizeChanged() { + var bodyMinSize = {}; + if (_isBody && _contentArrangeElement) { + bodyMinSize.w = parseToZeroOrNumber(_contentArrangeElement.css(_strMinMinus + _strWidth)); + bodyMinSize.h = parseToZeroOrNumber(_contentArrangeElement.css(_strMinMinus + _strHeight)); + bodyMinSize.c = checkCache(bodyMinSize, _bodyMinSizeCache); + bodyMinSize.f = true; //flag for "measured at least once" + } + _bodyMinSizeCache = bodyMinSize; + return !!bodyMinSize.c; + } + + /** + * Returns true if the class names really changed (new class without plugin host prefix) + * @param oldClassNames The old ClassName string or array. + * @param newClassNames The new ClassName string or array. + * @returns {boolean} True if the class names has really changed, false otherwise. + */ + function hostClassNamesChanged(oldClassNames, newClassNames) { + var currClasses = typeof newClassNames == TYPES.s ? newClassNames.split(_strSpace) : []; + var oldClasses = typeof oldClassNames == TYPES.s ? oldClassNames.split(_strSpace) : []; + var diff = getArrayDifferences(oldClasses, currClasses); + + // remove none theme from diff list to prevent update + var idx = inArray(_classNameThemeNone, diff); + var i; + var regex; + + if (idx > -1) + diff.splice(idx, 1); + + if (diff[LEXICON.l] > 0) { + regex = createHostClassNameRegExp(true, true); + for (i = 0; i < diff.length; i++) { + if (!diff[i].match(regex)) { + return true; + } + } + } + return false; + } + + /** + * Returns true if the given mutation is not from a from the plugin generated element. If the target element is a textarea the mutation is always unknown. + * @param mutation The mutation which shall be checked. + * @returns {boolean} True if the mutation is from a unknown element, false otherwise. + */ + function isUnknownMutation(mutation) { + var attributeName = mutation.attributeName; + var mutationTarget = mutation.target; + var mutationType = mutation.type; + var strClosest = 'closest'; + + if (mutationTarget === _contentElementNative) + return attributeName === null; + if (mutationType === 'attributes' && (attributeName === LEXICON.c || attributeName === LEXICON.s) && !_isTextarea) { + //ignore className changes by the plugin + if (attributeName === LEXICON.c && FRAMEWORK(mutationTarget).hasClass(_classNameHostElement)) + return hostClassNamesChanged(mutation.oldValue, mutationTarget.className); + + //only do it of browser support it natively + if (typeof mutationTarget[strClosest] != TYPES.f) + return true; + if (mutationTarget[strClosest](_strDot + _classNameResizeObserverElement) !== null || + mutationTarget[strClosest](_strDot + _classNameScrollbar) !== null || + mutationTarget[strClosest](_strDot + _classNameScrollbarCorner) !== null) + return false; + } + return true; + } + + /** + * Returns true if the content size was changed since the last time this method was called. + * @returns {boolean} True if the content size was changed, false otherwise. + */ + function updateAutoContentSizeChanged() { + if (_sleeping) + return false; + + var contentMeasureElement = getContentMeasureElement(); + var textareaValueLength = _isTextarea && _widthAutoCache && !_textareaAutoWrappingCache ? _targetElement.val().length : 0; + var setCSS = !_mutationObserversConnected && _widthAutoCache && !_isTextarea; + var css = {}; + var float; + var bodyMinSizeC; + var changed; + var contentElementScrollSize; + + if (setCSS) { + float = _contentElement.css(_strFloat); + css[_strFloat] = _isRTL ? _strRight : _strLeft; + css[_strWidth] = _strAuto; + _contentElement.css(css); + } + contentElementScrollSize = { + w: contentMeasureElement[LEXICON.sW] + textareaValueLength, + h: contentMeasureElement[LEXICON.sH] + textareaValueLength + }; + if (setCSS) { + css[_strFloat] = float; + css[_strWidth] = _strHundredPercent; + _contentElement.css(css); + } + + bodyMinSizeC = bodyMinSizeChanged(); + changed = checkCache(contentElementScrollSize, _contentElementScrollSizeChangeDetectedCache); + + _contentElementScrollSizeChangeDetectedCache = contentElementScrollSize; + + return changed || bodyMinSizeC; + } + + /** + * Returns true when a attribute which the MutationObserver would observe has changed. + * @returns {boolean} True if one of the attributes which a MutationObserver would observe has changed, false or undefined otherwise. + */ + function meaningfulAttrsChanged() { + if (_sleeping || _mutationObserversConnected) + return; + + var elem; + var curr; + var cache; + var changedAttrs = []; + var checks = [ + { + _elem: _hostElement, + _attrs: _mutationObserverAttrsHost.concat(':visible') + }, + { + _elem: _isTextarea ? _targetElement : undefined, + _attrs: _mutationObserverAttrsTextarea + } + ]; + + each(checks, function (index, check) { + elem = check._elem; + if (elem) { + each(check._attrs, function (index, attr) { + curr = attr.charAt(0) === ':' ? elem.is(attr) : elem.attr(attr); + cache = _updateAutoCache[attr]; + + if (checkCache(curr, cache)) { + changedAttrs.push(attr); + } + + _updateAutoCache[attr] = curr; + }); + } + }); + + updateViewportAttrsFromTarget(changedAttrs); + + return changedAttrs[LEXICON.l] > 0; + } + + /** + * Checks is a CSS Property of a child element is affecting the scroll size of the content. + * @param propertyName The CSS property name. + * @returns {boolean} True if the property is affecting the content scroll size, false otherwise. + */ + function isSizeAffectingCSSProperty(propertyName) { + if (!_initialized) + return true; + var flexGrow = 'flex-grow'; + var flexShrink = 'flex-shrink'; + var flexBasis = 'flex-basis'; + var affectingPropsX = [ + _strWidth, + _strMinMinus + _strWidth, + _strMaxMinus + _strWidth, + _strMarginMinus + _strLeft, + _strMarginMinus + _strRight, + _strLeft, + _strRight, + 'font-weight', + 'word-spacing', + flexGrow, + flexShrink, + flexBasis + ]; + var affectingPropsXContentBox = [ + _strPaddingMinus + _strLeft, + _strPaddingMinus + _strRight, + _strBorderMinus + _strLeft + _strWidth, + _strBorderMinus + _strRight + _strWidth + ]; + var affectingPropsY = [ + _strHeight, + _strMinMinus + _strHeight, + _strMaxMinus + _strHeight, + _strMarginMinus + _strTop, + _strMarginMinus + _strBottom, + _strTop, + _strBottom, + 'line-height', + flexGrow, + flexShrink, + flexBasis + ]; + var affectingPropsYContentBox = [ + _strPaddingMinus + _strTop, + _strPaddingMinus + _strBottom, + _strBorderMinus + _strTop + _strWidth, + _strBorderMinus + _strBottom + _strWidth + ]; + var _strS = 's'; + var _strVS = 'v-s'; + var checkX = _overflowBehaviorCache.x === _strS || _overflowBehaviorCache.x === _strVS; + var checkY = _overflowBehaviorCache.y === _strS || _overflowBehaviorCache.y === _strVS; + var sizeIsAffected = false; + var checkPropertyName = function (arr, name) { + for (var i = 0; i < arr[LEXICON.l]; i++) { + if (arr[i] === name) + return true; + } + return false; + }; + + if (checkY) { + sizeIsAffected = checkPropertyName(affectingPropsY, propertyName); + if (!sizeIsAffected && !_isBorderBox) + sizeIsAffected = checkPropertyName(affectingPropsYContentBox, propertyName); + } + if (checkX && !sizeIsAffected) { + sizeIsAffected = checkPropertyName(affectingPropsX, propertyName); + if (!sizeIsAffected && !_isBorderBox) + sizeIsAffected = checkPropertyName(affectingPropsXContentBox, propertyName); + } + return sizeIsAffected; + } + + + //==== Update ====// + + /** + * Sets the attribute values of the viewport element to the values from the target element. + * The value of a attribute is only set if the attribute is whitelisted. + * @attrs attrs The array of attributes which shall be set or undefined if all whitelisted shall be set. + */ + function updateViewportAttrsFromTarget(attrs) { + attrs = attrs || _viewportAttrsFromTarget; + each(attrs, function (index, attr) { + if (COMPATIBILITY.inA(attr, _viewportAttrsFromTarget) > -1) { + var targetAttr = _targetElement.attr(attr); + if (type(targetAttr) == TYPES.s) { + _viewportElement.attr(attr, targetAttr); + } + else { + _viewportElement.removeAttr(attr); + } + } + }); + } + + /** + * Updates the variables and size of the textarea element, and manages the scroll on new line or new character. + */ + function textareaUpdate() { + if (!_sleeping) { + var wrapAttrOff = !_textareaAutoWrappingCache; + var minWidth = _viewportSize.w; + var minHeight = _viewportSize.h; + var css = {}; + var doMeasure = _widthAutoCache || wrapAttrOff; + var origWidth; + var width; + var origHeight; + var height; + + //reset min size + css[_strMinMinus + _strWidth] = _strEmpty; + css[_strMinMinus + _strHeight] = _strEmpty; + + //set width auto + css[_strWidth] = _strAuto; + _targetElement.css(css); + + //measure width + origWidth = _targetElementNative[LEXICON.oW]; + width = doMeasure ? MATH.max(origWidth, _targetElementNative[LEXICON.sW] - 1) : 1; + /*width += (_widthAutoCache ? _marginX + (!_isBorderBox ? wrapAttrOff ? 0 : _paddingX + _borderX : 0) : 0);*/ + + //set measured width + css[_strWidth] = _widthAutoCache ? _strAuto /*width*/ : _strHundredPercent; + css[_strMinMinus + _strWidth] = _strHundredPercent; + + //set height auto + css[_strHeight] = _strAuto; + _targetElement.css(css); + + //measure height + origHeight = _targetElementNative[LEXICON.oH]; + height = MATH.max(origHeight, _targetElementNative[LEXICON.sH] - 1); + + //append correct size values + css[_strWidth] = width; + css[_strHeight] = height; + _textareaCoverElement.css(css); + + //apply min width / min height to prevent textarea collapsing + css[_strMinMinus + _strWidth] = minWidth /*+ (!_isBorderBox && _widthAutoCache ? _paddingX + _borderX : 0)*/; + css[_strMinMinus + _strHeight] = minHeight /*+ (!_isBorderBox && _heightAutoCache ? _paddingY + _borderY : 0)*/; + _targetElement.css(css); + + return { + _originalWidth: origWidth, + _originalHeight: origHeight, + _dynamicWidth: width, + _dynamicHeight: height + }; + } + } + + /** + * Updates the plugin and DOM to the current options. + * This method should only be called if a update is 100% required. + * @param updateHints A objects which contains hints for this update: + * { + * _hostSizeChanged : boolean, + * _contentSizeChanged : boolean, + * _force : boolean, == preventSwallowing + * _changedOptions : { }, == preventSwallowing && preventSleep + * } + */ + function update(updateHints) { + clearTimeout(_swallowedUpdateTimeout); + updateHints = updateHints || {}; + _swallowedUpdateHints._hostSizeChanged |= updateHints._hostSizeChanged; + _swallowedUpdateHints._contentSizeChanged |= updateHints._contentSizeChanged; + _swallowedUpdateHints._force |= updateHints._force; + + var now = COMPATIBILITY.now(); + var hostSizeChanged = !!_swallowedUpdateHints._hostSizeChanged; + var contentSizeChanged = !!_swallowedUpdateHints._contentSizeChanged; + var force = !!_swallowedUpdateHints._force; + var changedOptions = updateHints._changedOptions; + var swallow = _swallowUpdateLag > 0 && _initialized && !_destroyed && !force && !changedOptions && (now - _lastUpdateTime) < _swallowUpdateLag && (!_heightAutoCache && !_widthAutoCache); + var displayIsHidden; + + if (swallow) + _swallowedUpdateTimeout = setTimeout(update, _swallowUpdateLag); + + //abort update due to: + //destroyed + //swallowing + //sleeping + //host is hidden or has false display + if (_destroyed || swallow || (_sleeping && !changedOptions) || (_initialized && !force && (displayIsHidden = _hostElement.is(':hidden'))) || _hostElement.css('display') === 'inline') + return; + + _lastUpdateTime = now; + _swallowedUpdateHints = {}; + + //if scrollbar styling is possible and native scrollbars aren't overlaid the scrollbar styling will be applied which hides the native scrollbars completely. + if (_nativeScrollbarStyling && !(_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y)) { + //native scrollbars are hidden, so change the values to zero + _nativeScrollbarSize.x = 0; + _nativeScrollbarSize.y = 0; + } + else { + //refresh native scrollbar size (in case of zoom) + _nativeScrollbarSize = extendDeep({}, globals.nativeScrollbarSize); + } + + // Scrollbar padding is needed for firefox, because firefox hides scrollbar automatically if the size of the div is too small. + // The calculation: [scrollbar size +3 *3] + // (+3 because of possible decoration e.g. borders, margins etc., but only if native scrollbar is NOT a overlaid scrollbar) + // (*3 because (1)increase / (2)decrease -button and (3)resize handle) + _nativeScrollbarMinSize = { + x: (_nativeScrollbarSize.x + (_nativeScrollbarIsOverlaid.x ? 0 : 3)) * 3, + y: (_nativeScrollbarSize.y + (_nativeScrollbarIsOverlaid.y ? 0 : 3)) * 3 + }; + + changedOptions = changedOptions || {}; + //freezeResizeObserver(_sizeObserverElement, true); + //freezeResizeObserver(_sizeAutoObserverElement, true); + + var checkCacheAutoForce = function () { + return checkCache.apply(this, [].slice.call(arguments).concat([force])); + }; + + //save current scroll offset + var currScroll = { + x: _viewportElement[_strScrollLeft](), + y: _viewportElement[_strScrollTop]() + }; + + var currentPreparedOptionsScrollbars = _currentPreparedOptions.scrollbars; + var currentPreparedOptionsTextarea = _currentPreparedOptions.textarea; + + //scrollbars visibility: + var scrollbarsVisibility = currentPreparedOptionsScrollbars.visibility; + var scrollbarsVisibilityChanged = checkCacheAutoForce(scrollbarsVisibility, _scrollbarsVisibilityCache); + + //scrollbars autoHide: + var scrollbarsAutoHide = currentPreparedOptionsScrollbars.autoHide; + var scrollbarsAutoHideChanged = checkCacheAutoForce(scrollbarsAutoHide, _scrollbarsAutoHideCache); + + //scrollbars click scrolling + var scrollbarsClickScrolling = currentPreparedOptionsScrollbars.clickScrolling; + var scrollbarsClickScrollingChanged = checkCacheAutoForce(scrollbarsClickScrolling, _scrollbarsClickScrollingCache); + + //scrollbars drag scrolling + var scrollbarsDragScrolling = currentPreparedOptionsScrollbars.dragScrolling; + var scrollbarsDragScrollingChanged = checkCacheAutoForce(scrollbarsDragScrolling, _scrollbarsDragScrollingCache); + + //className + var className = _currentPreparedOptions.className; + var classNameChanged = checkCacheAutoForce(className, _classNameCache); + + //resize + var resize = _currentPreparedOptions.resize; + var resizeChanged = checkCacheAutoForce(resize, _resizeCache) && !_isBody; //body can't be resized since the window itself acts as resize possibility. + + //paddingAbsolute + var paddingAbsolute = _currentPreparedOptions.paddingAbsolute; + var paddingAbsoluteChanged = checkCacheAutoForce(paddingAbsolute, _paddingAbsoluteCache); + + //clipAlways + var clipAlways = _currentPreparedOptions.clipAlways; + var clipAlwaysChanged = checkCacheAutoForce(clipAlways, _clipAlwaysCache); + + //sizeAutoCapable + var sizeAutoCapable = _currentPreparedOptions.sizeAutoCapable && !_isBody; //body can never be size auto, because it shall be always as big as the viewport. + var sizeAutoCapableChanged = checkCacheAutoForce(sizeAutoCapable, _sizeAutoCapableCache); + + //showNativeScrollbars + var ignoreOverlayScrollbarHiding = _currentPreparedOptions.nativeScrollbarsOverlaid.showNativeScrollbars; + var ignoreOverlayScrollbarHidingChanged = checkCacheAutoForce(ignoreOverlayScrollbarHiding, _ignoreOverlayScrollbarHidingCache); + + //autoUpdate + var autoUpdate = _currentPreparedOptions.autoUpdate; + var autoUpdateChanged = checkCacheAutoForce(autoUpdate, _autoUpdateCache); + + //overflowBehavior + var overflowBehavior = _currentPreparedOptions.overflowBehavior; + var overflowBehaviorChanged = checkCacheAutoForce(overflowBehavior, _overflowBehaviorCache, force); + + //dynWidth: + var textareaDynWidth = currentPreparedOptionsTextarea.dynWidth; + var textareaDynWidthChanged = checkCacheAutoForce(_textareaDynWidthCache, textareaDynWidth); + + //dynHeight: + var textareaDynHeight = currentPreparedOptionsTextarea.dynHeight; + var textareaDynHeightChanged = checkCacheAutoForce(_textareaDynHeightCache, textareaDynHeight); + + //scrollbars visibility + _scrollbarsAutoHideNever = scrollbarsAutoHide === 'n'; + _scrollbarsAutoHideScroll = scrollbarsAutoHide === 's'; + _scrollbarsAutoHideMove = scrollbarsAutoHide === 'm'; + _scrollbarsAutoHideLeave = scrollbarsAutoHide === 'l'; + + //scrollbars autoHideDelay + _scrollbarsAutoHideDelay = currentPreparedOptionsScrollbars.autoHideDelay; + + //old className + _oldClassName = _classNameCache; + + //resize + _resizeNone = resize === 'n'; + _resizeBoth = resize === 'b'; + _resizeHorizontal = resize === 'h'; + _resizeVertical = resize === 'v'; + + //normalizeRTL + _normalizeRTLCache = _currentPreparedOptions.normalizeRTL; + + //ignore overlay scrollbar hiding + ignoreOverlayScrollbarHiding = ignoreOverlayScrollbarHiding && (_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y); + + //refresh options cache + _scrollbarsVisibilityCache = scrollbarsVisibility; + _scrollbarsAutoHideCache = scrollbarsAutoHide; + _scrollbarsClickScrollingCache = scrollbarsClickScrolling; + _scrollbarsDragScrollingCache = scrollbarsDragScrolling; + _classNameCache = className; + _resizeCache = resize; + _paddingAbsoluteCache = paddingAbsolute; + _clipAlwaysCache = clipAlways; + _sizeAutoCapableCache = sizeAutoCapable; + _ignoreOverlayScrollbarHidingCache = ignoreOverlayScrollbarHiding; + _autoUpdateCache = autoUpdate; + _overflowBehaviorCache = extendDeep({}, overflowBehavior); + _textareaDynWidthCache = textareaDynWidth; + _textareaDynHeightCache = textareaDynHeight; + _hasOverflowCache = _hasOverflowCache || { x: false, y: false }; + + //set correct class name to the host element + if (classNameChanged) { + removeClass(_hostElement, _oldClassName + _strSpace + _classNameThemeNone); + addClass(_hostElement, className !== undefined && className !== null && className.length > 0 ? className : _classNameThemeNone); + } + + //set correct auto Update + if (autoUpdateChanged) { + if (autoUpdate === true || (autoUpdate === null && _autoUpdateRecommended)) { + disconnectMutationObservers(); + autoUpdateLoop.add(_base); + } + else { + autoUpdateLoop.remove(_base); + connectMutationObservers(); + } + } + + //activate or deactivate size auto capability + if (sizeAutoCapableChanged) { + if (sizeAutoCapable) { + if (_contentGlueElement) { + _contentGlueElement.show(); + } + else { + _contentGlueElement = FRAMEWORK(generateDiv(_classNameContentGlueElement)); + _paddingElement.before(_contentGlueElement); + } + if (_sizeAutoObserverAdded) { + _sizeAutoObserverElement.show(); + } + else { + _sizeAutoObserverElement = FRAMEWORK(generateDiv(_classNameSizeAutoObserverElement)); + _sizeAutoObserverElementNative = _sizeAutoObserverElement[0]; + + _contentGlueElement.before(_sizeAutoObserverElement); + var oldSize = { w: -1, h: -1 }; + setupResizeObserver(_sizeAutoObserverElement, function () { + var newSize = { + w: _sizeAutoObserverElementNative[LEXICON.oW], + h: _sizeAutoObserverElementNative[LEXICON.oH] + }; + if (checkCache(newSize, oldSize)) { + if (_initialized && (_heightAutoCache && newSize.h > 0) || (_widthAutoCache && newSize.w > 0)) { + update(); + } + else if (_initialized && (!_heightAutoCache && newSize.h === 0) || (!_widthAutoCache && newSize.w === 0)) { + update(); + } + } + oldSize = newSize; + }); + _sizeAutoObserverAdded = true; + //fix heightAuto detector bug if height is fixed but contentHeight is 0. + //the probability this bug will ever happen is very very low, thats why its ok if we use calc which isn't supported in IE8. + if (_cssCalc !== null) + _sizeAutoObserverElement.css(_strHeight, _cssCalc + '(100% + 1px)'); + } + } + else { + if (_sizeAutoObserverAdded) + _sizeAutoObserverElement.hide(); + if (_contentGlueElement) + _contentGlueElement.hide(); + } + } + + //if force, update all resizeObservers too + if (force) { + _sizeObserverElement.find('*').trigger(_strScroll); + if (_sizeAutoObserverAdded) + _sizeAutoObserverElement.find('*').trigger(_strScroll); + } + + //display hidden: + displayIsHidden = displayIsHidden === undefined ? _hostElement.is(':hidden') : displayIsHidden; + + //textarea AutoWrapping: + var textareaAutoWrapping = _isTextarea ? _targetElement.attr('wrap') !== 'off' : false; + var textareaAutoWrappingChanged = checkCacheAutoForce(textareaAutoWrapping, _textareaAutoWrappingCache); + + //detect direction: + var cssDirection = _hostElement.css('direction'); + var cssDirectionChanged = checkCacheAutoForce(cssDirection, _cssDirectionCache); + + //detect box-sizing: + var boxSizing = _hostElement.css('box-sizing'); + var boxSizingChanged = checkCacheAutoForce(boxSizing, _cssBoxSizingCache); + + //detect padding: + var padding = getTopRightBottomLeftHost(_strPaddingMinus); + + //width + height auto detecting var: + var sizeAutoObserverElementBCRect; + //exception occurs in IE8 sometimes (unknown exception) + try { + sizeAutoObserverElementBCRect = _sizeAutoObserverAdded ? _sizeAutoObserverElementNative[LEXICON.bCR]() : null; + } catch (ex) { + return; + } + + _isRTL = cssDirection === 'rtl'; + _isBorderBox = (boxSizing === 'border-box'); + var isRTLLeft = _isRTL ? _strLeft : _strRight; + var isRTLRight = _isRTL ? _strRight : _strLeft; + + //detect width auto: + var widthAutoResizeDetection = false; + var widthAutoObserverDetection = (_sizeAutoObserverAdded && (_hostElement.css(_strFloat) !== 'none' /*|| _isTextarea */)) ? (MATH.round(sizeAutoObserverElementBCRect.right - sizeAutoObserverElementBCRect.left) === 0) && (!paddingAbsolute ? (_hostElementNative[LEXICON.cW] - _paddingX) > 0 : true) : false; + if (sizeAutoCapable && !widthAutoObserverDetection) { + var tmpCurrHostWidth = _hostElementNative[LEXICON.oW]; + var tmpCurrContentGlueWidth = _contentGlueElement.css(_strWidth); + _contentGlueElement.css(_strWidth, _strAuto); + + var tmpNewHostWidth = _hostElementNative[LEXICON.oW]; + _contentGlueElement.css(_strWidth, tmpCurrContentGlueWidth); + widthAutoResizeDetection = tmpCurrHostWidth !== tmpNewHostWidth; + if (!widthAutoResizeDetection) { + _contentGlueElement.css(_strWidth, tmpCurrHostWidth + 1); + tmpNewHostWidth = _hostElementNative[LEXICON.oW]; + _contentGlueElement.css(_strWidth, tmpCurrContentGlueWidth); + widthAutoResizeDetection = tmpCurrHostWidth !== tmpNewHostWidth; + } + } + var widthAuto = (widthAutoObserverDetection || widthAutoResizeDetection) && sizeAutoCapable && !displayIsHidden; + var widthAutoChanged = checkCacheAutoForce(widthAuto, _widthAutoCache); + var wasWidthAuto = !widthAuto && _widthAutoCache; + + //detect height auto: + var heightAuto = _sizeAutoObserverAdded && sizeAutoCapable && !displayIsHidden ? (MATH.round(sizeAutoObserverElementBCRect.bottom - sizeAutoObserverElementBCRect.top) === 0) /* && (!paddingAbsolute && (_msieVersion > 9 || !_msieVersion) ? true : true) */ : false; + var heightAutoChanged = checkCacheAutoForce(heightAuto, _heightAutoCache); + var wasHeightAuto = !heightAuto && _heightAutoCache; + + //detect border: + //we need the border only if border box and auto size + var updateBorderX = (widthAuto && _isBorderBox) || !_isBorderBox; + var updateBorderY = (heightAuto && _isBorderBox) || !_isBorderBox; + var border = getTopRightBottomLeftHost(_strBorderMinus, '-' + _strWidth, !updateBorderX, !updateBorderY) + + //detect margin: + var margin = getTopRightBottomLeftHost(_strMarginMinus); + + //vars to apply correct css + var contentElementCSS = {}; + var contentGlueElementCSS = {}; + + //funcs + var getHostSize = function () { + //has to be clientSize because offsetSize respect borders + return { + w: _hostElementNative[LEXICON.cW], + h: _hostElementNative[LEXICON.cH] + }; + }; + var getViewportSize = function () { + //viewport size is padding container because it never has padding, margin and a border + //determine zoom rounding error -> sometimes scrollWidth/Height is smaller than clientWidth/Height + //if this happens add the difference to the viewportSize to compensate the rounding error + return { + w: _paddingElementNative[LEXICON.oW] + MATH.max(0, _contentElementNative[LEXICON.cW] - _contentElementNative[LEXICON.sW]), + h: _paddingElementNative[LEXICON.oH] + MATH.max(0, _contentElementNative[LEXICON.cH] - _contentElementNative[LEXICON.sH]) + }; + }; + + //set info for padding + var paddingAbsoluteX = _paddingX = padding.l + padding.r; + var paddingAbsoluteY = _paddingY = padding.t + padding.b; + paddingAbsoluteX *= paddingAbsolute ? 1 : 0; + paddingAbsoluteY *= paddingAbsolute ? 1 : 0; + padding.c = checkCacheAutoForce(padding, _cssPaddingCache); + + //set info for border + _borderX = border.l + border.r; + _borderY = border.t + border.b; + border.c = checkCacheAutoForce(border, _cssBorderCache); + + //set info for margin + _marginX = margin.l + margin.r; + _marginY = margin.t + margin.b; + margin.c = checkCacheAutoForce(margin, _cssMarginCache); + + //refresh cache + _textareaAutoWrappingCache = textareaAutoWrapping; + _cssDirectionCache = cssDirection; + _cssBoxSizingCache = boxSizing; + _widthAutoCache = widthAuto; + _heightAutoCache = heightAuto; + _cssPaddingCache = padding; + _cssBorderCache = border; + _cssMarginCache = margin; + + //IEFix direction changed + if (cssDirectionChanged && _sizeAutoObserverAdded) + _sizeAutoObserverElement.css(_strFloat, isRTLRight); + + //apply padding: + if (padding.c || cssDirectionChanged || paddingAbsoluteChanged || widthAutoChanged || heightAutoChanged || boxSizingChanged || sizeAutoCapableChanged) { + var paddingElementCSS = {}; + var textareaCSS = {}; + var paddingValues = [padding.t, padding.r, padding.b, padding.l]; + + setTopRightBottomLeft(contentGlueElementCSS, _strMarginMinus, [-padding.t, -padding.r, -padding.b, -padding.l]); + if (paddingAbsolute) { + setTopRightBottomLeft(paddingElementCSS, _strEmpty, paddingValues); + setTopRightBottomLeft(_isTextarea ? textareaCSS : contentElementCSS, _strPaddingMinus); + } + else { + setTopRightBottomLeft(paddingElementCSS, _strEmpty); + setTopRightBottomLeft(_isTextarea ? textareaCSS : contentElementCSS, _strPaddingMinus, paddingValues); + } + + _paddingElement.css(paddingElementCSS); + _targetElement.css(textareaCSS); + } + + //viewport size is padding container because it never has padding, margin and a border. + _viewportSize = getViewportSize(); + + //update Textarea + var textareaSize = _isTextarea ? textareaUpdate() : false; + var textareaSizeChanged = _isTextarea && checkCacheAutoForce(textareaSize, _textareaSizeCache); + var textareaDynOrigSize = _isTextarea && textareaSize ? { + w: textareaDynWidth ? textareaSize._dynamicWidth : textareaSize._originalWidth, + h: textareaDynHeight ? textareaSize._dynamicHeight : textareaSize._originalHeight + } : {}; + _textareaSizeCache = textareaSize; + + //fix height auto / width auto in cooperation with current padding & boxSizing behavior: + if (heightAuto && (heightAutoChanged || paddingAbsoluteChanged || boxSizingChanged || padding.c || border.c)) { + contentElementCSS[_strHeight] = _strAuto; + } + else if (heightAutoChanged || paddingAbsoluteChanged) { + contentElementCSS[_strHeight] = _strHundredPercent; + } + if (widthAuto && (widthAutoChanged || paddingAbsoluteChanged || boxSizingChanged || padding.c || border.c || cssDirectionChanged)) { + contentElementCSS[_strWidth] = _strAuto; + contentGlueElementCSS[_strMaxMinus + _strWidth] = _strHundredPercent; //IE Fix + } + else if (widthAutoChanged || paddingAbsoluteChanged) { + contentElementCSS[_strWidth] = _strHundredPercent; + contentElementCSS[_strFloat] = _strEmpty; + contentGlueElementCSS[_strMaxMinus + _strWidth] = _strEmpty; //IE Fix + } + if (widthAuto) { + //textareaDynOrigSize.w || _strAuto :: doesnt works because applied margin will shift width + contentGlueElementCSS[_strWidth] = _strAuto; + + contentElementCSS[_strWidth] = VENDORS._cssPropertyValue(_strWidth, 'max-content intrinsic') || _strAuto; + contentElementCSS[_strFloat] = isRTLRight; + } + else { + contentGlueElementCSS[_strWidth] = _strEmpty; + } + if (heightAuto) { + //textareaDynOrigSize.h || _contentElementNative[LEXICON.cH] :: use for anti scroll jumping + contentGlueElementCSS[_strHeight] = textareaDynOrigSize.h || _contentElementNative[LEXICON.cH]; + } + else { + contentGlueElementCSS[_strHeight] = _strEmpty; + } + if (sizeAutoCapable) + _contentGlueElement.css(contentGlueElementCSS); + _contentElement.css(contentElementCSS); + + //CHECKPOINT HERE ~ + contentElementCSS = {}; + contentGlueElementCSS = {}; + + //if [content(host) client / scroll size, or target element direction, or content(host) max-sizes] changed, or force is true + if (hostSizeChanged || contentSizeChanged || textareaSizeChanged || cssDirectionChanged || boxSizingChanged || paddingAbsoluteChanged || widthAutoChanged || widthAuto || heightAutoChanged || heightAuto || ignoreOverlayScrollbarHidingChanged || overflowBehaviorChanged || clipAlwaysChanged || resizeChanged || scrollbarsVisibilityChanged || scrollbarsAutoHideChanged || scrollbarsDragScrollingChanged || scrollbarsClickScrollingChanged || textareaDynWidthChanged || textareaDynHeightChanged || textareaAutoWrappingChanged) { + var strOverflow = 'overflow'; + var strOverflowX = strOverflow + '-x'; + var strOverflowY = strOverflow + '-y'; + var strHidden = 'hidden'; + var strVisible = 'visible'; + + //Reset the viewport (very important for natively overlaid scrollbars and zoom change + //don't change the overflow prop as it is very expensive and affects performance !A LOT! + if (!_nativeScrollbarStyling) { + var viewportElementResetCSS = {}; + var resetXTmp = _hasOverflowCache.y && _hideOverflowCache.ys && !ignoreOverlayScrollbarHiding ? (_nativeScrollbarIsOverlaid.y ? _viewportElement.css(isRTLLeft) : -_nativeScrollbarSize.y) : 0; + var resetBottomTmp = _hasOverflowCache.x && _hideOverflowCache.xs && !ignoreOverlayScrollbarHiding ? (_nativeScrollbarIsOverlaid.x ? _viewportElement.css(_strBottom) : -_nativeScrollbarSize.x) : 0; + setTopRightBottomLeft(viewportElementResetCSS, _strEmpty); + _viewportElement.css(viewportElementResetCSS); + } + + //measure several sizes: + var contentMeasureElement = getContentMeasureElement(); + //in Firefox content element has to have overflow hidden, else element margins aren't calculated properly, this element prevents this bug, but only if scrollbars aren't overlaid + var contentSize = { + //use clientSize because natively overlaidScrollbars add borders + w: textareaDynOrigSize.w || contentMeasureElement[LEXICON.cW], + h: textareaDynOrigSize.h || contentMeasureElement[LEXICON.cH] + }; + var scrollSize = { + w: contentMeasureElement[LEXICON.sW], + h: contentMeasureElement[LEXICON.sH] + }; + + //apply the correct viewport style and measure viewport size + if (!_nativeScrollbarStyling) { + viewportElementResetCSS[_strBottom] = wasHeightAuto ? _strEmpty : resetBottomTmp; + viewportElementResetCSS[isRTLLeft] = wasWidthAuto ? _strEmpty : resetXTmp; + _viewportElement.css(viewportElementResetCSS); + } + _viewportSize = getViewportSize(); + + //measure and correct several sizes + var hostSize = getHostSize(); + var hostAbsoluteRectSize = { + w: hostSize.w - _marginX - _borderX - (_isBorderBox ? 0 : _paddingX), + h: hostSize.h - _marginY - _borderY - (_isBorderBox ? 0 : _paddingY) + }; + var contentGlueSize = { + //client/scrollSize + AbsolutePadding -> because padding is only applied to the paddingElement if its absolute, so you have to add it manually + //hostSize is clientSize -> so padding should be added manually, right? FALSE! Because content glue is inside hostElement, so we don't have to worry about padding + w: MATH.max((widthAuto ? contentSize.w : scrollSize.w) + paddingAbsoluteX, hostAbsoluteRectSize.w), + h: MATH.max((heightAuto ? contentSize.h : scrollSize.h) + paddingAbsoluteY, hostAbsoluteRectSize.h) + }; + contentGlueSize.c = checkCacheAutoForce(contentGlueSize, _contentGlueSizeCache); + _contentGlueSizeCache = contentGlueSize; + + //apply correct contentGlue size + if (sizeAutoCapable) { + //size contentGlue correctly to make sure the element has correct size if the sizing switches to auto + if (contentGlueSize.c || (heightAuto || widthAuto)) { + contentGlueElementCSS[_strWidth] = contentGlueSize.w; + contentGlueElementCSS[_strHeight] = contentGlueSize.h; + + //textarea-sizes are already calculated correctly at this point + if (!_isTextarea) { + contentSize = { + //use clientSize because natively overlaidScrollbars add borders + w: contentMeasureElement[LEXICON.cW], + h: contentMeasureElement[LEXICON.cH] + }; + } + } + var textareaCoverCSS = {}; + var setContentGlueElementCSSfunction = function (horizontal) { + var scrollbarVars = getScrollbarVars(horizontal); + var wh = scrollbarVars._w_h; + var strWH = scrollbarVars._width_height; + var autoSize = horizontal ? widthAuto : heightAuto; + var borderSize = horizontal ? _borderX : _borderY; + var paddingSize = horizontal ? _paddingX : _paddingY; + var marginSize = horizontal ? _marginX : _marginY; + var viewportSize = _viewportSize[wh] - borderSize - marginSize - (_isBorderBox ? 0 : paddingSize); + + //make contentGlue size -1 if element is not auto sized, to make sure that a resize event happens when the element shrinks + if (!autoSize || (!autoSize && border.c)) + contentGlueElementCSS[strWH] = hostAbsoluteRectSize[wh] - 1; + + //if size is auto and host is smaller than size as min size, make content glue size -1 to make sure size changes will be detected (this is only needed if padding is 0) + if (autoSize && (contentSize[wh] < viewportSize) && (horizontal && _isTextarea ? !textareaAutoWrapping : true)) { + if (_isTextarea) + textareaCoverCSS[strWH] = parseToZeroOrNumber(_textareaCoverElement.css(strWH)) - 1; + contentGlueElementCSS[strWH] -= 1; + } + + //make sure content glue size is at least 1 + if (contentSize[wh] > 0) + contentGlueElementCSS[strWH] = MATH.max(1, contentGlueElementCSS[strWH]); + }; + setContentGlueElementCSSfunction(true); + setContentGlueElementCSSfunction(false); + + if (_isTextarea) + _textareaCoverElement.css(textareaCoverCSS); + _contentGlueElement.css(contentGlueElementCSS); + } + if (widthAuto) + contentElementCSS[_strWidth] = _strHundredPercent; + if (widthAuto && !_isBorderBox && !_mutationObserversConnected) + contentElementCSS[_strFloat] = 'none'; + + //apply and reset content style + _contentElement.css(contentElementCSS); + contentElementCSS = {}; + + //measure again, but this time all correct sizes: + var contentScrollSize = { + w: contentMeasureElement[LEXICON.sW], + h: contentMeasureElement[LEXICON.sH], + }; + contentScrollSize.c = contentSizeChanged = checkCacheAutoForce(contentScrollSize, _contentScrollSizeCache); + _contentScrollSizeCache = contentScrollSize; + + //refresh viewport size after correct measuring + _viewportSize = getViewportSize(); + + hostSize = getHostSize(); + hostSizeChanged = checkCacheAutoForce(hostSize, _hostSizeCache); + _hostSizeCache = hostSize; + + var hideOverflowForceTextarea = _isTextarea && (_viewportSize.w === 0 || _viewportSize.h === 0); + var previousOverflowAmount = _overflowAmountCache; + var overflowBehaviorIsVS = {}; + var overflowBehaviorIsVH = {}; + var overflowBehaviorIsS = {}; + var overflowAmount = {}; + var hasOverflow = {}; + var hideOverflow = {}; + var canScroll = {}; + var viewportRect = _paddingElementNative[LEXICON.bCR](); + var setOverflowVariables = function (horizontal) { + var scrollbarVars = getScrollbarVars(horizontal); + var scrollbarVarsInverted = getScrollbarVars(!horizontal); + var xyI = scrollbarVarsInverted._x_y; + var xy = scrollbarVars._x_y; + var wh = scrollbarVars._w_h; + var widthHeight = scrollbarVars._width_height; + var scrollMax = _strScroll + scrollbarVars._Left_Top + 'Max'; + var fractionalOverflowAmount = viewportRect[widthHeight] ? MATH.abs(viewportRect[widthHeight] - _viewportSize[wh]) : 0; + var checkFractionalOverflowAmount = previousOverflowAmount && previousOverflowAmount[xy] > 0 && _viewportElementNative[scrollMax] === 0; + overflowBehaviorIsVS[xy] = overflowBehavior[xy] === 'v-s'; + overflowBehaviorIsVH[xy] = overflowBehavior[xy] === 'v-h'; + overflowBehaviorIsS[xy] = overflowBehavior[xy] === 's'; + overflowAmount[xy] = MATH.max(0, MATH.round((contentScrollSize[wh] - _viewportSize[wh]) * 100) / 100); + overflowAmount[xy] *= (hideOverflowForceTextarea || (checkFractionalOverflowAmount && fractionalOverflowAmount > 0 && fractionalOverflowAmount < 1)) ? 0 : 1; + hasOverflow[xy] = overflowAmount[xy] > 0; + + //hideOverflow: + //x || y : true === overflow is hidden by "overflow: scroll" OR "overflow: hidden" + //xs || ys : true === overflow is hidden by "overflow: scroll" + hideOverflow[xy] = overflowBehaviorIsVS[xy] || overflowBehaviorIsVH[xy] ? (hasOverflow[xyI] && !overflowBehaviorIsVS[xyI] && !overflowBehaviorIsVH[xyI]) : hasOverflow[xy]; + hideOverflow[xy + 's'] = hideOverflow[xy] ? (overflowBehaviorIsS[xy] || overflowBehaviorIsVS[xy]) : false; + + canScroll[xy] = hasOverflow[xy] && hideOverflow[xy + 's']; + }; + setOverflowVariables(true); + setOverflowVariables(false); + + overflowAmount.c = checkCacheAutoForce(overflowAmount, _overflowAmountCache); + _overflowAmountCache = overflowAmount; + hasOverflow.c = checkCacheAutoForce(hasOverflow, _hasOverflowCache); + _hasOverflowCache = hasOverflow; + hideOverflow.c = checkCacheAutoForce(hideOverflow, _hideOverflowCache); + _hideOverflowCache = hideOverflow; + + //if native scrollbar is overlay at x OR y axis, prepare DOM + if (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y) { + var borderDesign = 'px solid transparent'; + var contentArrangeElementCSS = {}; + var arrangeContent = {}; + var arrangeChanged = force; + var setContentElementCSS; + + if (hasOverflow.x || hasOverflow.y) { + arrangeContent.w = _nativeScrollbarIsOverlaid.y && hasOverflow.y ? contentScrollSize.w + _overlayScrollbarDummySize.y : _strEmpty; + arrangeContent.h = _nativeScrollbarIsOverlaid.x && hasOverflow.x ? contentScrollSize.h + _overlayScrollbarDummySize.x : _strEmpty; + arrangeChanged = checkCacheAutoForce(arrangeContent, _arrangeContentSizeCache); + _arrangeContentSizeCache = arrangeContent; + } + + if (hasOverflow.c || hideOverflow.c || contentScrollSize.c || cssDirectionChanged || widthAutoChanged || heightAutoChanged || widthAuto || heightAuto || ignoreOverlayScrollbarHidingChanged) { + contentElementCSS[_strMarginMinus + isRTLRight] = contentElementCSS[_strBorderMinus + isRTLRight] = _strEmpty; + setContentElementCSS = function (horizontal) { + var scrollbarVars = getScrollbarVars(horizontal); + var scrollbarVarsInverted = getScrollbarVars(!horizontal); + var xy = scrollbarVars._x_y; + var strDirection = horizontal ? _strBottom : isRTLLeft; + var invertedAutoSize = horizontal ? heightAuto : widthAuto; + + if (_nativeScrollbarIsOverlaid[xy] && hasOverflow[xy] && hideOverflow[xy + 's']) { + contentElementCSS[_strMarginMinus + strDirection] = invertedAutoSize ? (ignoreOverlayScrollbarHiding ? _strEmpty : _overlayScrollbarDummySize[xy]) : _strEmpty; + contentElementCSS[_strBorderMinus + strDirection] = ((horizontal ? !invertedAutoSize : true) && !ignoreOverlayScrollbarHiding) ? (_overlayScrollbarDummySize[xy] + borderDesign) : _strEmpty; + } + else { + arrangeContent[scrollbarVarsInverted._w_h] = + contentElementCSS[_strMarginMinus + strDirection] = + contentElementCSS[_strBorderMinus + strDirection] = _strEmpty; + arrangeChanged = true; + } + }; + + if (_nativeScrollbarStyling) { + addRemoveClass(_viewportElement, _classNameViewportNativeScrollbarsInvisible, !ignoreOverlayScrollbarHiding) + } + else { + setContentElementCSS(true); + setContentElementCSS(false); + } + } + if (ignoreOverlayScrollbarHiding) { + arrangeContent.w = arrangeContent.h = _strEmpty; + arrangeChanged = true; + } + if (arrangeChanged && !_nativeScrollbarStyling) { + contentArrangeElementCSS[_strWidth] = hideOverflow.y ? arrangeContent.w : _strEmpty; + contentArrangeElementCSS[_strHeight] = hideOverflow.x ? arrangeContent.h : _strEmpty; + + if (!_contentArrangeElement) { + _contentArrangeElement = FRAMEWORK(generateDiv(_classNameContentArrangeElement)); + _viewportElement.prepend(_contentArrangeElement); + } + _contentArrangeElement.css(contentArrangeElementCSS); + } + _contentElement.css(contentElementCSS); + } + + var viewportElementCSS = {}; + var paddingElementCSS = {}; + var setViewportCSS; + if (hostSizeChanged || hasOverflow.c || hideOverflow.c || contentScrollSize.c || overflowBehaviorChanged || boxSizingChanged || ignoreOverlayScrollbarHidingChanged || cssDirectionChanged || clipAlwaysChanged || heightAutoChanged) { + viewportElementCSS[isRTLRight] = _strEmpty; + setViewportCSS = function (horizontal) { + var scrollbarVars = getScrollbarVars(horizontal); + var scrollbarVarsInverted = getScrollbarVars(!horizontal); + var xy = scrollbarVars._x_y; + var XY = scrollbarVars._X_Y; + var strDirection = horizontal ? _strBottom : isRTLLeft; + + var reset = function () { + viewportElementCSS[strDirection] = _strEmpty; + _contentBorderSize[scrollbarVarsInverted._w_h] = 0; + }; + if (hasOverflow[xy] && hideOverflow[xy + 's']) { + viewportElementCSS[strOverflow + XY] = _strScroll; + if (ignoreOverlayScrollbarHiding || _nativeScrollbarStyling) { + reset(); + } + else { + viewportElementCSS[strDirection] = -(_nativeScrollbarIsOverlaid[xy] ? _overlayScrollbarDummySize[xy] : _nativeScrollbarSize[xy]); + _contentBorderSize[scrollbarVarsInverted._w_h] = _nativeScrollbarIsOverlaid[xy] ? _overlayScrollbarDummySize[scrollbarVarsInverted._x_y] : 0; + } + } else { + viewportElementCSS[strOverflow + XY] = _strEmpty; + reset(); + } + }; + setViewportCSS(true); + setViewportCSS(false); + + // if the scroll container is too small and if there is any overflow with no overlay scrollbar (and scrollbar styling isn't possible), + // make viewport element greater in size (Firefox hide Scrollbars fix) + // because firefox starts hiding scrollbars on too small elements + // with this behavior the overflow calculation may be incorrect or the scrollbars would appear suddenly + // https://bugzilla.mozilla.org/show_bug.cgi?id=292284 + if (!_nativeScrollbarStyling + && (_viewportSize.h < _nativeScrollbarMinSize.x || _viewportSize.w < _nativeScrollbarMinSize.y) + && ((hasOverflow.x && hideOverflow.x && !_nativeScrollbarIsOverlaid.x) || (hasOverflow.y && hideOverflow.y && !_nativeScrollbarIsOverlaid.y))) { + viewportElementCSS[_strPaddingMinus + _strTop] = _nativeScrollbarMinSize.x; + viewportElementCSS[_strMarginMinus + _strTop] = -_nativeScrollbarMinSize.x; + + viewportElementCSS[_strPaddingMinus + isRTLRight] = _nativeScrollbarMinSize.y; + viewportElementCSS[_strMarginMinus + isRTLRight] = -_nativeScrollbarMinSize.y; + } + else { + viewportElementCSS[_strPaddingMinus + _strTop] = + viewportElementCSS[_strMarginMinus + _strTop] = + viewportElementCSS[_strPaddingMinus + isRTLRight] = + viewportElementCSS[_strMarginMinus + isRTLRight] = _strEmpty; + } + viewportElementCSS[_strPaddingMinus + isRTLLeft] = + viewportElementCSS[_strMarginMinus + isRTLLeft] = _strEmpty; + + //if there is any overflow (x OR y axis) and this overflow shall be hidden, make overflow hidden, else overflow visible + if ((hasOverflow.x && hideOverflow.x) || (hasOverflow.y && hideOverflow.y) || hideOverflowForceTextarea) { + //only hide if is Textarea + if (_isTextarea && hideOverflowForceTextarea) { + paddingElementCSS[strOverflowX] = + paddingElementCSS[strOverflowY] = strHidden; + } + } + else { + if (!clipAlways || (overflowBehaviorIsVH.x || overflowBehaviorIsVS.x || overflowBehaviorIsVH.y || overflowBehaviorIsVS.y)) { + //only un-hide if Textarea + if (_isTextarea) { + paddingElementCSS[strOverflowX] = + paddingElementCSS[strOverflowY] = _strEmpty; + } + viewportElementCSS[strOverflowX] = + viewportElementCSS[strOverflowY] = strVisible; + } + } + + _paddingElement.css(paddingElementCSS); + _viewportElement.css(viewportElementCSS); + viewportElementCSS = {}; + + //force soft redraw in webkit because without the scrollbars will may appear because DOM wont be redrawn under special conditions + if ((hasOverflow.c || boxSizingChanged || widthAutoChanged || heightAutoChanged) && !(_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y)) { + var elementStyle = _contentElementNative[LEXICON.s]; + var dump; + elementStyle.webkitTransform = 'scale(1)'; + elementStyle.display = 'run-in'; + dump = _contentElementNative[LEXICON.oH]; + elementStyle.display = _strEmpty; //|| dump; //use dump to prevent it from deletion if minify + elementStyle.webkitTransform = _strEmpty; + } + /* + //force hard redraw in webkit if native overlaid scrollbars shall appear + if (ignoreOverlayScrollbarHidingChanged && ignoreOverlayScrollbarHiding) { + _hostElement.hide(); + var dump = _hostElementNative[LEXICON.oH]; + _hostElement.show(); + } + */ + } + + //change to direction RTL and width auto Bugfix in Webkit + //without this fix, the DOM still thinks the scrollbar is LTR and thus the content is shifted to the left + contentElementCSS = {}; + if (cssDirectionChanged || widthAutoChanged || heightAutoChanged) { + if (_isRTL && widthAuto) { + var floatTmp = _contentElement.css(_strFloat); + var posLeftWithoutFloat = MATH.round(_contentElement.css(_strFloat, _strEmpty).css(_strLeft, _strEmpty).position().left); + _contentElement.css(_strFloat, floatTmp); + var posLeftWithFloat = MATH.round(_contentElement.position().left); + + if (posLeftWithoutFloat !== posLeftWithFloat) + contentElementCSS[_strLeft] = posLeftWithoutFloat; + } + else { + contentElementCSS[_strLeft] = _strEmpty; + } + } + _contentElement.css(contentElementCSS); + + //handle scroll position + if (_isTextarea && contentSizeChanged) { + var textareaInfo = getTextareaInfo(); + if (textareaInfo) { + var textareaRowsChanged = _textareaInfoCache === undefined ? true : textareaInfo._rows !== _textareaInfoCache._rows; + var cursorRow = textareaInfo._cursorRow; + var cursorCol = textareaInfo._cursorColumn; + var widestRow = textareaInfo._widestRow; + var lastRow = textareaInfo._rows; + var lastCol = textareaInfo._columns; + var cursorPos = textareaInfo._cursorPosition; + var cursorMax = textareaInfo._cursorMax; + var cursorIsLastPosition = (cursorPos >= cursorMax && _textareaHasFocus); + var textareaScrollAmount = { + x: (!textareaAutoWrapping && (cursorCol === lastCol && cursorRow === widestRow)) ? _overflowAmountCache.x : -1, + y: (textareaAutoWrapping ? cursorIsLastPosition || textareaRowsChanged && (previousOverflowAmount ? (currScroll.y === previousOverflowAmount.y) : false) : (cursorIsLastPosition || textareaRowsChanged) && cursorRow === lastRow) ? _overflowAmountCache.y : -1 + }; + currScroll.x = textareaScrollAmount.x > -1 ? (_isRTL && _normalizeRTLCache && _rtlScrollBehavior.i ? 0 : textareaScrollAmount.x) : currScroll.x; //if inverted, scroll to 0 -> normalized this means to max scroll offset. + currScroll.y = textareaScrollAmount.y > -1 ? textareaScrollAmount.y : currScroll.y; + } + _textareaInfoCache = textareaInfo; + } + if (_isRTL && _rtlScrollBehavior.i && _nativeScrollbarIsOverlaid.y && hasOverflow.x && _normalizeRTLCache) + currScroll.x += _contentBorderSize.w || 0; + if (widthAuto) + _hostElement[_strScrollLeft](0); + if (heightAuto) + _hostElement[_strScrollTop](0); + _viewportElement[_strScrollLeft](currScroll.x)[_strScrollTop](currScroll.y); + + //scrollbars management: + var scrollbarsVisibilityVisible = scrollbarsVisibility === 'v'; + var scrollbarsVisibilityHidden = scrollbarsVisibility === 'h'; + var scrollbarsVisibilityAuto = scrollbarsVisibility === 'a'; + var refreshScrollbarsVisibility = function (showX, showY) { + showY = showY === undefined ? showX : showY; + refreshScrollbarAppearance(true, showX, canScroll.x) + refreshScrollbarAppearance(false, showY, canScroll.y) + }; + + //manage class name which indicates scrollable overflow + addRemoveClass(_hostElement, _classNameHostOverflow, hideOverflow.x || hideOverflow.y); + addRemoveClass(_hostElement, _classNameHostOverflowX, hideOverflow.x); + addRemoveClass(_hostElement, _classNameHostOverflowY, hideOverflow.y); + + //add or remove rtl class name for styling purposes except when its body, then the scrollbar stays + if (cssDirectionChanged && !_isBody) { + addRemoveClass(_hostElement, _classNameHostRTL, _isRTL); + } + + //manage the resize feature (CSS3 resize "polyfill" for this plugin) + if (_isBody) + addClass(_hostElement, _classNameHostResizeDisabled); + if (resizeChanged) { + addRemoveClass(_hostElement, _classNameHostResizeDisabled, _resizeNone); + addRemoveClass(_scrollbarCornerElement, _classNameScrollbarCornerResize, !_resizeNone); + addRemoveClass(_scrollbarCornerElement, _classNameScrollbarCornerResizeB, _resizeBoth); + addRemoveClass(_scrollbarCornerElement, _classNameScrollbarCornerResizeH, _resizeHorizontal); + addRemoveClass(_scrollbarCornerElement, _classNameScrollbarCornerResizeV, _resizeVertical); + } + + //manage the scrollbars general visibility + the scrollbar interactivity (unusable class name) + if (scrollbarsVisibilityChanged || overflowBehaviorChanged || hideOverflow.c || hasOverflow.c || ignoreOverlayScrollbarHidingChanged) { + if (ignoreOverlayScrollbarHiding) { + if (ignoreOverlayScrollbarHidingChanged) { + removeClass(_hostElement, _classNameHostScrolling); + if (ignoreOverlayScrollbarHiding) { + refreshScrollbarsVisibility(false); + } + } + } + else if (scrollbarsVisibilityAuto) { + refreshScrollbarsVisibility(canScroll.x, canScroll.y); + } + else if (scrollbarsVisibilityVisible) { + refreshScrollbarsVisibility(true); + } + else if (scrollbarsVisibilityHidden) { + refreshScrollbarsVisibility(false); + } + } + + //manage the scrollbars auto hide feature (auto hide them after specific actions) + if (scrollbarsAutoHideChanged || ignoreOverlayScrollbarHidingChanged) { + setupHostMouseTouchEvents(!_scrollbarsAutoHideLeave && !_scrollbarsAutoHideMove); + refreshScrollbarsAutoHide(_scrollbarsAutoHideNever, !_scrollbarsAutoHideNever); + } + + //manage scrollbars handle length & offset - don't remove! + if (hostSizeChanged || overflowAmount.c || heightAutoChanged || widthAutoChanged || resizeChanged || boxSizingChanged || paddingAbsoluteChanged || ignoreOverlayScrollbarHidingChanged || cssDirectionChanged) { + refreshScrollbarHandleLength(true); + refreshScrollbarHandleOffset(true); + refreshScrollbarHandleLength(false); + refreshScrollbarHandleOffset(false); + } + + //manage interactivity + if (scrollbarsClickScrollingChanged) + refreshScrollbarsInteractive(true, scrollbarsClickScrolling); + if (scrollbarsDragScrollingChanged) + refreshScrollbarsInteractive(false, scrollbarsDragScrolling); + + //callbacks: + dispatchCallback('onDirectionChanged', { + isRTL: _isRTL, + dir: cssDirection + }, cssDirectionChanged); + dispatchCallback('onHostSizeChanged', { + width: _hostSizeCache.w, + height: _hostSizeCache.h + }, hostSizeChanged); + dispatchCallback('onContentSizeChanged', { + width: _contentScrollSizeCache.w, + height: _contentScrollSizeCache.h + }, contentSizeChanged); + dispatchCallback('onOverflowChanged', { + x: hasOverflow.x, + y: hasOverflow.y, + xScrollable: hideOverflow.xs, + yScrollable: hideOverflow.ys, + clipped: hideOverflow.x || hideOverflow.y + }, hasOverflow.c || hideOverflow.c); + dispatchCallback('onOverflowAmountChanged', { + x: overflowAmount.x, + y: overflowAmount.y + }, overflowAmount.c); + } + + //fix body min size + if (_isBody && _bodyMinSizeCache && (_hasOverflowCache.c || _bodyMinSizeCache.c)) { + //its possible that no min size was measured until now, because the content arrange element was just added now, in this case, measure now the min size. + if (!_bodyMinSizeCache.f) + bodyMinSizeChanged(); + if (_nativeScrollbarIsOverlaid.y && _hasOverflowCache.x) + _contentElement.css(_strMinMinus + _strWidth, _bodyMinSizeCache.w + _overlayScrollbarDummySize.y); + if (_nativeScrollbarIsOverlaid.x && _hasOverflowCache.y) + _contentElement.css(_strMinMinus + _strHeight, _bodyMinSizeCache.h + _overlayScrollbarDummySize.x); + _bodyMinSizeCache.c = false; + } + + if (_initialized && changedOptions.updateOnLoad) { + updateElementsOnLoad(); + } + + //freezeResizeObserver(_sizeObserverElement, false); + //freezeResizeObserver(_sizeAutoObserverElement, false); + + dispatchCallback('onUpdated', { forced: force }); + } + + /** + * Updates the found elements of which the load event shall be handled. + */ + function updateElementsOnLoad() { + if (!_isTextarea) { + eachUpdateOnLoad(function (i, updateOnLoadSelector) { + _contentElement.find(updateOnLoadSelector).each(function (i, el) { + // if element doesn't have a updateOnLoadCallback applied + if (COMPATIBILITY.inA(el, _updateOnLoadElms) < 0) { + _updateOnLoadElms.push(el); + FRAMEWORK(el) + .off(_updateOnLoadEventName, updateOnLoadCallback) + .on(_updateOnLoadEventName, updateOnLoadCallback); + } + }); + }); + } + } + + //==== Options ====// + + /** + * Sets new options but doesn't call the update method. + * @param newOptions The object which contains the new options. + * @returns {*} A object which contains the changed options. + */ + function setOptions(newOptions) { + var validatedOpts = _pluginsOptions._validate(newOptions, _pluginsOptions._template, true, _currentOptions) + + _currentOptions = extendDeep({}, _currentOptions, validatedOpts._default); + _currentPreparedOptions = extendDeep({}, _currentPreparedOptions, validatedOpts._prepared); + + return validatedOpts._prepared; + } + + + //==== Structure ====// + + /** + * Builds or destroys the wrapper and helper DOM elements. + * @param destroy Indicates whether the DOM shall be build or destroyed. + */ + /** + * Builds or destroys the wrapper and helper DOM elements. + * @param destroy Indicates whether the DOM shall be build or destroyed. + */ + function setupStructureDOM(destroy) { + var strParent = 'parent'; + var classNameResizeObserverHost = 'os-resize-observer-host'; + var classNameTextareaElementFull = _classNameTextareaElement + _strSpace + _classNameTextInherit; + var textareaClass = _isTextarea ? _strSpace + _classNameTextInherit : _strEmpty; + var adoptAttrs = _currentPreparedOptions.textarea.inheritedAttrs; + var adoptAttrsMap = {}; + var applyAdoptedAttrs = function () { + var applyAdoptedAttrsElm = destroy ? _targetElement : _hostElement; + each(adoptAttrsMap, function (key, value) { + if (type(value) == TYPES.s) { + if (key == LEXICON.c) + applyAdoptedAttrsElm.addClass(value); + else + applyAdoptedAttrsElm.attr(key, value); + } + }); + }; + var hostElementClassNames = [ + _classNameHostElement, + _classNameHostElementForeign, + _classNameHostTextareaElement, + _classNameHostResizeDisabled, + _classNameHostRTL, + _classNameHostScrollbarHorizontalHidden, + _classNameHostScrollbarVerticalHidden, + _classNameHostTransition, + _classNameHostScrolling, + _classNameHostOverflow, + _classNameHostOverflowX, + _classNameHostOverflowY, + _classNameThemeNone, + _classNameTextareaElement, + _classNameTextInherit, + _classNameCache].join(_strSpace); + var hostElementCSS = {}; + + //get host element as first element, because that's the most upper element and required for the other elements + _hostElement = _hostElement || (_isTextarea ? (_domExists ? _targetElement[strParent]()[strParent]()[strParent]()[strParent]() : FRAMEWORK(generateDiv(_classNameHostTextareaElement))) : _targetElement); + _contentElement = _contentElement || selectOrGenerateDivByClass(_classNameContentElement + textareaClass); + _viewportElement = _viewportElement || selectOrGenerateDivByClass(_classNameViewportElement + textareaClass); + _paddingElement = _paddingElement || selectOrGenerateDivByClass(_classNamePaddingElement + textareaClass); + _sizeObserverElement = _sizeObserverElement || selectOrGenerateDivByClass(classNameResizeObserverHost); + _textareaCoverElement = _textareaCoverElement || (_isTextarea ? selectOrGenerateDivByClass(_classNameTextareaCoverElement) : undefined); + + //add this class to workaround class changing issues with UI frameworks especially Vue + if (_domExists) + addClass(_hostElement, _classNameHostElementForeign); + + //on destroy, remove all generated class names from the host element before collecting the adopted attributes + //to prevent adopting generated class names + if (destroy) + removeClass(_hostElement, hostElementClassNames); + + //collect all adopted attributes + adoptAttrs = type(adoptAttrs) == TYPES.s ? adoptAttrs.split(_strSpace) : adoptAttrs; + if (COMPATIBILITY.isA(adoptAttrs) && _isTextarea) { + each(adoptAttrs, function (i, v) { + if (type(v) == TYPES.s) { + adoptAttrsMap[v] = destroy ? _hostElement.attr(v) : _targetElement.attr(v); + } + }); + } + + if (!destroy) { + if (_isTextarea) { + if (!_currentPreparedOptions.sizeAutoCapable) { + hostElementCSS[_strWidth] = _targetElement.css(_strWidth); + hostElementCSS[_strHeight] = _targetElement.css(_strHeight); + } + + if (!_domExists) + _targetElement.addClass(_classNameTextInherit).wrap(_hostElement); + + //jQuery clones elements in wrap functions, so we have to select them again + _hostElement = _targetElement[strParent]().css(hostElementCSS); + } + + if (!_domExists) { + //add the correct class to the target element + addClass(_targetElement, _isTextarea ? classNameTextareaElementFull : _classNameHostElement); + + //wrap the content into the generated elements to create the required DOM + _hostElement.wrapInner(_contentElement) + .wrapInner(_viewportElement) + .wrapInner(_paddingElement) + .prepend(_sizeObserverElement); + + //jQuery clones elements in wrap functions, so we have to select them again + _contentElement = findFirst(_hostElement, _strDot + _classNameContentElement); + _viewportElement = findFirst(_hostElement, _strDot + _classNameViewportElement); + _paddingElement = findFirst(_hostElement, _strDot + _classNamePaddingElement); + + if (_isTextarea) { + _contentElement.prepend(_textareaCoverElement); + applyAdoptedAttrs(); + } + } + + if (_nativeScrollbarStyling) + addClass(_viewportElement, _classNameViewportNativeScrollbarsInvisible); + if (_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y) + addClass(_viewportElement, _classNameViewportNativeScrollbarsOverlaid); + if (_isBody) + addClass(_htmlElement, _classNameHTMLElement); + + _sizeObserverElementNative = _sizeObserverElement[0]; + _hostElementNative = _hostElement[0]; + _paddingElementNative = _paddingElement[0]; + _viewportElementNative = _viewportElement[0]; + _contentElementNative = _contentElement[0]; + + updateViewportAttrsFromTarget(); + } + else { + if (_domExists && _initialized) { + //clear size observer + _sizeObserverElement.children().remove(); + + //remove the style property and classes from already generated elements + each([_paddingElement, _viewportElement, _contentElement, _textareaCoverElement], function (i, elm) { + if (elm) { + removeClass(elm.removeAttr(LEXICON.s), _classNamesDynamicDestroy); + } + }); + + //add classes to the host element which was removed previously to match the expected DOM + addClass(_hostElement, _isTextarea ? _classNameHostTextareaElement : _classNameHostElement); + } + else { + //remove size observer + remove(_sizeObserverElement); + + //unwrap the content to restore DOM + _contentElement.contents() + .unwrap() + .unwrap() + .unwrap(); + + if (_isTextarea) { + _targetElement.unwrap(); + remove(_hostElement); + remove(_textareaCoverElement); + applyAdoptedAttrs(); + } + } + + if (_isTextarea) + _targetElement.removeAttr(LEXICON.s); + + if (_isBody) + removeClass(_htmlElement, _classNameHTMLElement); + } + } + + /** + * Adds or removes all wrapper elements interactivity events. + * @param destroy Indicates whether the Events shall be added or removed. + */ + function setupStructureEvents() { + var textareaKeyDownRestrictedKeyCodes = [ + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 123, //F1 to F12 + 33, 34, //page up, page down + 37, 38, 39, 40, //left, up, right, down arrows + 16, 17, 18, 19, 20, 144 //Shift, Ctrl, Alt, Pause, CapsLock, NumLock + ]; + var textareaKeyDownKeyCodesList = []; + var textareaUpdateIntervalID; + var scrollStopTimeoutId; + var scrollStopDelay = 175; + var strFocus = 'focus'; + + function updateTextarea(doClearInterval) { + textareaUpdate(); + _base.update(_strAuto); + if (doClearInterval && _autoUpdateRecommended) + clearInterval(textareaUpdateIntervalID); + } + function textareaOnScroll(event) { + _targetElement[_strScrollLeft](_rtlScrollBehavior.i && _normalizeRTLCache ? 9999999 : 0); + _targetElement[_strScrollTop](0); + COMPATIBILITY.prvD(event); + COMPATIBILITY.stpP(event); + return false; + } + function textareaOnDrop(event) { + setTimeout(function () { + if (!_destroyed) + updateTextarea(); + }, 50); + } + function textareaOnFocus() { + _textareaHasFocus = true; + addClass(_hostElement, strFocus); + } + function textareaOnFocusout() { + _textareaHasFocus = false; + textareaKeyDownKeyCodesList = []; + removeClass(_hostElement, strFocus); + updateTextarea(true); + } + function textareaOnKeyDown(event) { + var keyCode = event.keyCode; + + if (inArray(keyCode, textareaKeyDownRestrictedKeyCodes) < 0) { + if (!textareaKeyDownKeyCodesList[LEXICON.l]) { + updateTextarea(); + textareaUpdateIntervalID = setInterval(updateTextarea, 1000 / 60); + } + if (inArray(keyCode, textareaKeyDownKeyCodesList) < 0) + textareaKeyDownKeyCodesList.push(keyCode); + } + } + function textareaOnKeyUp(event) { + var keyCode = event.keyCode; + var index = inArray(keyCode, textareaKeyDownKeyCodesList); + + if (inArray(keyCode, textareaKeyDownRestrictedKeyCodes) < 0) { + if (index > -1) + textareaKeyDownKeyCodesList.splice(index, 1); + if (!textareaKeyDownKeyCodesList[LEXICON.l]) + updateTextarea(true); + } + } + function contentOnTransitionEnd(event) { + if (_autoUpdateCache === true) + return; + event = event.originalEvent || event; + if (isSizeAffectingCSSProperty(event.propertyName)) + _base.update(_strAuto); + } + function viewportOnScroll(event) { + if (!_sleeping) { + if (scrollStopTimeoutId !== undefined) + clearTimeout(scrollStopTimeoutId); + else { + if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) + refreshScrollbarsAutoHide(true); + + if (!nativeOverlayScrollbarsAreActive()) + addClass(_hostElement, _classNameHostScrolling); + + dispatchCallback('onScrollStart', event); + } + + //if a scrollbars handle gets dragged, the mousemove event is responsible for refreshing the handle offset + //because if CSS scroll-snap is used, the handle offset gets only refreshed on every snap point + //this looks laggy & clunky, it looks much better if the offset refreshes with the mousemove + if (!_scrollbarsHandlesDefineScrollPos) { + refreshScrollbarHandleOffset(true); + refreshScrollbarHandleOffset(false); + } + dispatchCallback('onScroll', event); + + scrollStopTimeoutId = setTimeout(function () { + if (!_destroyed) { + //OnScrollStop: + clearTimeout(scrollStopTimeoutId); + scrollStopTimeoutId = undefined; + + if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) + refreshScrollbarsAutoHide(false); + + if (!nativeOverlayScrollbarsAreActive()) + removeClass(_hostElement, _classNameHostScrolling); + + dispatchCallback('onScrollStop', event); + } + }, scrollStopDelay); + } + } + + + if (_isTextarea) { + if (_msieVersion > 9 || !_autoUpdateRecommended) { + addDestroyEventListener(_targetElement, 'input', updateTextarea); + } + else { + addDestroyEventListener(_targetElement, + [_strKeyDownEvent, _strKeyUpEvent], + [textareaOnKeyDown, textareaOnKeyUp]); + } + + addDestroyEventListener(_targetElement, + [_strScroll, 'drop', strFocus, strFocus + 'out'], + [textareaOnScroll, textareaOnDrop, textareaOnFocus, textareaOnFocusout]); + } + else { + addDestroyEventListener(_contentElement, _strTransitionEndEvent, contentOnTransitionEnd); + } + addDestroyEventListener(_viewportElement, _strScroll, viewportOnScroll, true); + } + + + //==== Scrollbars ====// + + /** + * Builds or destroys all scrollbar DOM elements (scrollbar, track, handle) + * @param destroy Indicates whether the DOM shall be build or destroyed. + */ + function setupScrollbarsDOM(destroy) { + var selectOrGenerateScrollbarDOM = function (isHorizontal) { + var scrollbarClassName = isHorizontal ? _classNameScrollbarHorizontal : _classNameScrollbarVertical; + var scrollbar = selectOrGenerateDivByClass(_classNameScrollbar + _strSpace + scrollbarClassName, true); + var track = selectOrGenerateDivByClass(_classNameScrollbarTrack, scrollbar); + var handle = selectOrGenerateDivByClass(_classNameScrollbarHandle, scrollbar); + + if (!_domExists && !destroy) { + scrollbar.append(track); + track.append(handle); + } + + return { + _scrollbar: scrollbar, + _track: track, + _handle: handle + }; + }; + function resetScrollbarDOM(isHorizontal) { + var scrollbarVars = getScrollbarVars(isHorizontal); + var scrollbar = scrollbarVars._scrollbar; + var track = scrollbarVars._track; + var handle = scrollbarVars._handle; + + if (_domExists && _initialized) { + each([scrollbar, track, handle], function (i, elm) { + removeClass(elm.removeAttr(LEXICON.s), _classNamesDynamicDestroy); + }); + } + else { + remove(scrollbar || selectOrGenerateScrollbarDOM(isHorizontal)._scrollbar); + } + } + var horizontalElements; + var verticalElements; + + if (!destroy) { + horizontalElements = selectOrGenerateScrollbarDOM(true); + verticalElements = selectOrGenerateScrollbarDOM(); + + _scrollbarHorizontalElement = horizontalElements._scrollbar; + _scrollbarHorizontalTrackElement = horizontalElements._track; + _scrollbarHorizontalHandleElement = horizontalElements._handle; + _scrollbarVerticalElement = verticalElements._scrollbar; + _scrollbarVerticalTrackElement = verticalElements._track; + _scrollbarVerticalHandleElement = verticalElements._handle; + + if (!_domExists) { + _paddingElement.after(_scrollbarVerticalElement); + _paddingElement.after(_scrollbarHorizontalElement); + } + } + else { + resetScrollbarDOM(true); + resetScrollbarDOM(); + } + } + + /** + * Initializes all scrollbar interactivity events. (track and handle dragging, clicking, scrolling) + * @param isHorizontal True if the target scrollbar is the horizontal scrollbar, false if the target scrollbar is the vertical scrollbar. + */ + function setupScrollbarEvents(isHorizontal) { + var scrollbarVars = getScrollbarVars(isHorizontal); + var scrollbarVarsInfo = scrollbarVars._info; + var insideIFrame = _windowElementNative.top !== _windowElementNative; + var xy = scrollbarVars._x_y; + var XY = scrollbarVars._X_Y; + var scroll = _strScroll + scrollbarVars._Left_Top; + var strActive = 'active'; + var strSnapHandle = 'snapHandle'; + var strClickEvent = 'click'; + var scrollDurationFactor = 1; + var increaseDecreaseScrollAmountKeyCodes = [16, 17]; //shift, ctrl + var trackTimeout; + var mouseDownScroll; + var mouseDownOffset; + var mouseDownInvertedScale; + + function getPointerPosition(event) { + return _msieVersion && insideIFrame ? event['screen' + XY] : COMPATIBILITY.page(event)[xy]; //use screen coordinates in EDGE & IE because the page values are incorrect in frames. + } + function getPreparedScrollbarsOption(name) { + return _currentPreparedOptions.scrollbars[name]; + } + function increaseTrackScrollAmount() { + scrollDurationFactor = 0.5; + } + function decreaseTrackScrollAmount() { + scrollDurationFactor = 1; + } + function stopClickEventPropagation(event) { + COMPATIBILITY.stpP(event); + } + function documentKeyDown(event) { + if (inArray(event.keyCode, increaseDecreaseScrollAmountKeyCodes) > -1) + increaseTrackScrollAmount(); + } + function documentKeyUp(event) { + if (inArray(event.keyCode, increaseDecreaseScrollAmountKeyCodes) > -1) + decreaseTrackScrollAmount(); + } + function onMouseTouchDownContinue(event) { + var originalEvent = event.originalEvent || event; + var isTouchEvent = originalEvent.touches !== undefined; + return _sleeping || _destroyed || nativeOverlayScrollbarsAreActive() || !_scrollbarsDragScrollingCache || (isTouchEvent && !getPreparedScrollbarsOption('touchSupport')) ? false : COMPATIBILITY.mBtn(event) === 1 || isTouchEvent; + } + function documentDragMove(event) { + if (onMouseTouchDownContinue(event)) { + var trackLength = scrollbarVarsInfo._trackLength; + var handleLength = scrollbarVarsInfo._handleLength; + var scrollRange = scrollbarVarsInfo._maxScroll; + var scrollRaw = (getPointerPosition(event) - mouseDownOffset) * mouseDownInvertedScale; + var scrollDeltaPercent = scrollRaw / (trackLength - handleLength); + var scrollDelta = (scrollRange * scrollDeltaPercent); + scrollDelta = isFinite(scrollDelta) ? scrollDelta : 0; + if (_isRTL && isHorizontal && !_rtlScrollBehavior.i) + scrollDelta *= -1; + + _viewportElement[scroll](MATH.round(mouseDownScroll + scrollDelta)); + + if (_scrollbarsHandlesDefineScrollPos) + refreshScrollbarHandleOffset(isHorizontal, mouseDownScroll + scrollDelta); + + if (!_supportPassiveEvents) + COMPATIBILITY.prvD(event); + } + else + documentMouseTouchUp(event); + } + function documentMouseTouchUp(event) { + event = event || event.originalEvent; + + setupResponsiveEventListener(_documentElement, + [_strMouseTouchMoveEvent, _strMouseTouchUpEvent, _strKeyDownEvent, _strKeyUpEvent, _strSelectStartEvent], + [documentDragMove, documentMouseTouchUp, documentKeyDown, documentKeyUp, documentOnSelectStart], + true); + COMPATIBILITY.rAF()(function() { + setupResponsiveEventListener(_documentElement, strClickEvent, stopClickEventPropagation, true, { _capture: true }); + }); + + + if (_scrollbarsHandlesDefineScrollPos) + refreshScrollbarHandleOffset(isHorizontal, true); + + _scrollbarsHandlesDefineScrollPos = false; + removeClass(_bodyElement, _classNameDragging); + removeClass(scrollbarVars._handle, strActive); + removeClass(scrollbarVars._track, strActive); + removeClass(scrollbarVars._scrollbar, strActive); + + mouseDownScroll = undefined; + mouseDownOffset = undefined; + mouseDownInvertedScale = 1; + + decreaseTrackScrollAmount(); + + if (trackTimeout !== undefined) { + _base.scrollStop(); + clearTimeout(trackTimeout); + trackTimeout = undefined; + } + + if (event) { + var rect = _hostElementNative[LEXICON.bCR](); + var mouseInsideHost = event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom; + + //if mouse is outside host element + if (!mouseInsideHost) + hostOnMouseLeave(); + + if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) + refreshScrollbarsAutoHide(false); + } + } + function onHandleMouseTouchDown(event) { + if (onMouseTouchDownContinue(event)) + onHandleMouseTouchDownAction(event); + } + function onHandleMouseTouchDownAction(event) { + mouseDownScroll = _viewportElement[scroll](); + mouseDownScroll = isNaN(mouseDownScroll) ? 0 : mouseDownScroll; + if (_isRTL && isHorizontal && !_rtlScrollBehavior.n || !_isRTL) + mouseDownScroll = mouseDownScroll < 0 ? 0 : mouseDownScroll; + + mouseDownInvertedScale = getHostElementInvertedScale()[xy]; + mouseDownOffset = getPointerPosition(event); + + _scrollbarsHandlesDefineScrollPos = !getPreparedScrollbarsOption(strSnapHandle); + addClass(_bodyElement, _classNameDragging); + addClass(scrollbarVars._handle, strActive); + addClass(scrollbarVars._scrollbar, strActive); + + setupResponsiveEventListener(_documentElement, + [_strMouseTouchMoveEvent, _strMouseTouchUpEvent, _strSelectStartEvent], + [documentDragMove, documentMouseTouchUp, documentOnSelectStart]); + COMPATIBILITY.rAF()(function() { + setupResponsiveEventListener(_documentElement, strClickEvent, stopClickEventPropagation, false, { _capture: true }); + }); + + + if (_msieVersion || !_documentMixed) + COMPATIBILITY.prvD(event); + COMPATIBILITY.stpP(event); + } + function onTrackMouseTouchDown(event) { + if (onMouseTouchDownContinue(event)) { + var handleToViewportRatio = scrollbarVars._info._handleLength / Math.round(MATH.min(1, _viewportSize[scrollbarVars._w_h] / _contentScrollSizeCache[scrollbarVars._w_h]) * scrollbarVars._info._trackLength); + var scrollDistance = MATH.round(_viewportSize[scrollbarVars._w_h] * handleToViewportRatio); + var scrollBaseDuration = 270 * handleToViewportRatio; + var scrollFirstIterationDelay = 400 * handleToViewportRatio; + var trackOffset = scrollbarVars._track.offset()[scrollbarVars._left_top]; + var ctrlKey = event.ctrlKey; + var instantScroll = event.shiftKey; + var instantScrollTransition = instantScroll && ctrlKey; + var isFirstIteration = true; + var easing = 'linear'; + var decreaseScroll; + var finishedCondition; + var scrollActionFinsished = function (transition) { + if (_scrollbarsHandlesDefineScrollPos) + refreshScrollbarHandleOffset(isHorizontal, transition); + }; + var scrollActionInstantFinished = function () { + scrollActionFinsished(); + onHandleMouseTouchDownAction(event); + }; + var scrollAction = function () { + if (!_destroyed) { + var mouseOffset = (mouseDownOffset - trackOffset) * mouseDownInvertedScale; + var handleOffset = scrollbarVarsInfo._handleOffset; + var trackLength = scrollbarVarsInfo._trackLength; + var handleLength = scrollbarVarsInfo._handleLength; + var scrollRange = scrollbarVarsInfo._maxScroll; + var currScroll = scrollbarVarsInfo._currentScroll; + var scrollDuration = scrollBaseDuration * scrollDurationFactor; + var timeoutDelay = isFirstIteration ? MATH.max(scrollFirstIterationDelay, scrollDuration) : scrollDuration; + var instantScrollPosition = scrollRange * ((mouseOffset - (handleLength / 2)) / (trackLength - handleLength)); // 100% * positionPercent + var rtlIsNormal = _isRTL && isHorizontal && ((!_rtlScrollBehavior.i && !_rtlScrollBehavior.n) || _normalizeRTLCache); + var decreaseScrollCondition = rtlIsNormal ? handleOffset < mouseOffset : handleOffset > mouseOffset; + var scrollObj = {}; + var animationObj = { + easing: easing, + step: function (now) { + if (_scrollbarsHandlesDefineScrollPos) { + _viewportElement[scroll](now); //https://github.com/jquery/jquery/issues/4340 + refreshScrollbarHandleOffset(isHorizontal, now); + } + } + }; + instantScrollPosition = isFinite(instantScrollPosition) ? instantScrollPosition : 0; + instantScrollPosition = _isRTL && isHorizontal && !_rtlScrollBehavior.i ? (scrollRange - instantScrollPosition) : instantScrollPosition; + + //_base.scrollStop(); + + if (instantScroll) { + _viewportElement[scroll](instantScrollPosition); //scroll instantly to new position + if (instantScrollTransition) { + //get the scroll position after instant scroll (in case CSS Snap Points are used) to get the correct snapped scroll position + //and the animation stops at the correct point + instantScrollPosition = _viewportElement[scroll](); + //scroll back to the position before instant scrolling so animation can be performed + _viewportElement[scroll](currScroll); + + instantScrollPosition = rtlIsNormal && _rtlScrollBehavior.i ? (scrollRange - instantScrollPosition) : instantScrollPosition; + instantScrollPosition = rtlIsNormal && _rtlScrollBehavior.n ? -instantScrollPosition : instantScrollPosition; + + scrollObj[xy] = instantScrollPosition; + _base.scroll(scrollObj, extendDeep(animationObj, { + duration: 130, + complete: scrollActionInstantFinished + })); + } + else + scrollActionInstantFinished(); + } + else { + decreaseScroll = isFirstIteration ? decreaseScrollCondition : decreaseScroll; + finishedCondition = rtlIsNormal + ? (decreaseScroll ? handleOffset + handleLength >= mouseOffset : handleOffset <= mouseOffset) + : (decreaseScroll ? handleOffset <= mouseOffset : handleOffset + handleLength >= mouseOffset); + + if (finishedCondition) { + clearTimeout(trackTimeout); + _base.scrollStop(); + trackTimeout = undefined; + scrollActionFinsished(true); + } + else { + trackTimeout = setTimeout(scrollAction, timeoutDelay); + + scrollObj[xy] = (decreaseScroll ? '-=' : '+=') + scrollDistance; + _base.scroll(scrollObj, extendDeep(animationObj, { + duration: scrollDuration + })); + } + isFirstIteration = false; + } + } + }; + if (ctrlKey) + increaseTrackScrollAmount(); + + mouseDownInvertedScale = getHostElementInvertedScale()[xy]; + mouseDownOffset = COMPATIBILITY.page(event)[xy]; + + _scrollbarsHandlesDefineScrollPos = !getPreparedScrollbarsOption(strSnapHandle); + addClass(_bodyElement, _classNameDragging); + addClass(scrollbarVars._track, strActive); + addClass(scrollbarVars._scrollbar, strActive); + + setupResponsiveEventListener(_documentElement, + [_strMouseTouchUpEvent, _strKeyDownEvent, _strKeyUpEvent, _strSelectStartEvent], + [documentMouseTouchUp, documentKeyDown, documentKeyUp, documentOnSelectStart]); + + scrollAction(); + COMPATIBILITY.prvD(event); + COMPATIBILITY.stpP(event); + } + } + function onTrackMouseTouchEnter(event) { + //make sure both scrollbars will stay visible if one scrollbar is hovered if autoHide is "scroll" or "move". + _scrollbarsHandleHovered = true; + if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) + refreshScrollbarsAutoHide(true); + } + function onTrackMouseTouchLeave(event) { + _scrollbarsHandleHovered = false; + if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) + refreshScrollbarsAutoHide(false); + } + function onScrollbarMouseTouchDown(event) { + COMPATIBILITY.stpP(event); + } + + addDestroyEventListener(scrollbarVars._handle, + _strMouseTouchDownEvent, + onHandleMouseTouchDown); + addDestroyEventListener(scrollbarVars._track, + [_strMouseTouchDownEvent, _strMouseEnter, _strMouseLeave], + [onTrackMouseTouchDown, onTrackMouseTouchEnter, onTrackMouseTouchLeave]); + addDestroyEventListener(scrollbarVars._scrollbar, + _strMouseTouchDownEvent, + onScrollbarMouseTouchDown); + + if (_supportTransition) { + addDestroyEventListener(scrollbarVars._scrollbar, _strTransitionEndEvent, function (event) { + if (event.target !== scrollbarVars._scrollbar[0]) + return; + refreshScrollbarHandleLength(isHorizontal); + refreshScrollbarHandleOffset(isHorizontal); + }); + } + } + + /** + * Shows or hides the given scrollbar and applied a class name which indicates if the scrollbar is scrollable or not. + * @param isHorizontal True if the horizontal scrollbar is the target, false if the vertical scrollbar is the target. + * @param shallBeVisible True if the scrollbar shall be shown, false if hidden. + * @param canScroll True if the scrollbar is scrollable, false otherwise. + */ + function refreshScrollbarAppearance(isHorizontal, shallBeVisible, canScroll) { + var scrollbarHiddenClassName = isHorizontal ? _classNameHostScrollbarHorizontalHidden : _classNameHostScrollbarVerticalHidden; + var scrollbarElement = isHorizontal ? _scrollbarHorizontalElement : _scrollbarVerticalElement; + + addRemoveClass(_hostElement, scrollbarHiddenClassName, !shallBeVisible); + addRemoveClass(scrollbarElement, _classNameScrollbarUnusable, !canScroll); + } + + /** + * Autoshows / autohides both scrollbars with. + * @param shallBeVisible True if the scrollbars shall be autoshown (only the case if they are hidden by a autohide), false if the shall be auto hidden. + * @param delayfree True if the scrollbars shall be hidden without a delay, false or undefined otherwise. + */ + function refreshScrollbarsAutoHide(shallBeVisible, delayfree) { + clearTimeout(_scrollbarsAutoHideTimeoutId); + if (shallBeVisible) { + //if(_hasOverflowCache.x && _hideOverflowCache.xs) + removeClass(_scrollbarHorizontalElement, _classNameScrollbarAutoHidden); + //if(_hasOverflowCache.y && _hideOverflowCache.ys) + removeClass(_scrollbarVerticalElement, _classNameScrollbarAutoHidden); + } + else { + var anyActive; + var strActive = 'active'; + var hide = function () { + if (!_scrollbarsHandleHovered && !_destroyed) { + anyActive = _scrollbarHorizontalHandleElement.hasClass(strActive) || _scrollbarVerticalHandleElement.hasClass(strActive); + if (!anyActive && (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove || _scrollbarsAutoHideLeave)) + addClass(_scrollbarHorizontalElement, _classNameScrollbarAutoHidden); + if (!anyActive && (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove || _scrollbarsAutoHideLeave)) + addClass(_scrollbarVerticalElement, _classNameScrollbarAutoHidden); + } + }; + if (_scrollbarsAutoHideDelay > 0 && delayfree !== true) + _scrollbarsAutoHideTimeoutId = setTimeout(hide, _scrollbarsAutoHideDelay); + else + hide(); + } + } + + /** + * Refreshes the handle length of the given scrollbar. + * @param isHorizontal True if the horizontal scrollbar handle shall be refreshed, false if the vertical one shall be refreshed. + */ + function refreshScrollbarHandleLength(isHorizontal) { + var handleCSS = {}; + var scrollbarVars = getScrollbarVars(isHorizontal); + var scrollbarVarsInfo = scrollbarVars._info; + var digit = 1000000; + //get and apply intended handle length + var handleRatio = MATH.min(1, _viewportSize[scrollbarVars._w_h] / _contentScrollSizeCache[scrollbarVars._w_h]); + handleCSS[scrollbarVars._width_height] = (MATH.floor(handleRatio * 100 * digit) / digit) + '%'; //the last * digit / digit is for flooring to the 4th digit + + if (!nativeOverlayScrollbarsAreActive()) + scrollbarVars._handle.css(handleCSS); + + //measure the handle length to respect min & max length + scrollbarVarsInfo._handleLength = scrollbarVars._handle[0]['offset' + scrollbarVars._Width_Height]; + scrollbarVarsInfo._handleLengthRatio = handleRatio; + } + + /** + * Refreshes the handle offset of the given scrollbar. + * @param isHorizontal True if the horizontal scrollbar handle shall be refreshed, false if the vertical one shall be refreshed. + * @param scrollOrTransition The scroll position of the given scrollbar axis to which the handle shall be moved or a boolean which indicates whether a transition shall be applied. If undefined or boolean if the current scroll-offset is taken. (if isHorizontal ? scrollLeft : scrollTop) + */ + function refreshScrollbarHandleOffset(isHorizontal, scrollOrTransition) { + var transition = type(scrollOrTransition) == TYPES.b; + var transitionDuration = 250; + var isRTLisHorizontal = _isRTL && isHorizontal; + var scrollbarVars = getScrollbarVars(isHorizontal); + var scrollbarVarsInfo = scrollbarVars._info; + var strTranslateBrace = 'translate('; + var strTransform = VENDORS._cssProperty('transform'); + var strTransition = VENDORS._cssProperty('transition'); + var nativeScroll = isHorizontal ? _viewportElement[_strScrollLeft]() : _viewportElement[_strScrollTop](); + var currentScroll = scrollOrTransition === undefined || transition ? nativeScroll : scrollOrTransition; + + //measure the handle length to respect min & max length + var handleLength = scrollbarVarsInfo._handleLength; + var trackLength = scrollbarVars._track[0]['offset' + scrollbarVars._Width_Height]; + var handleTrackDiff = trackLength - handleLength; + var handleCSS = {}; + var transformOffset; + var translateValue; + + //DONT use the variable '_contentScrollSizeCache[scrollbarVars._w_h]' instead of '_viewportElement[0]['scroll' + scrollbarVars._Width_Height]' + // because its a bit behind during the small delay when content size updates + //(delay = mutationObserverContentLag, if its 0 then this var could be used) + var maxScroll = (_viewportElementNative[_strScroll + scrollbarVars._Width_Height] - _viewportElementNative['client' + scrollbarVars._Width_Height]) * (_rtlScrollBehavior.n && isRTLisHorizontal ? -1 : 1); //* -1 if rtl scroll max is negative + var getScrollRatio = function (base) { + return isNaN(base / maxScroll) ? 0 : MATH.max(0, MATH.min(1, base / maxScroll)); + }; + var getHandleOffset = function (scrollRatio) { + var offset = handleTrackDiff * scrollRatio; + offset = isNaN(offset) ? 0 : offset; + offset = (isRTLisHorizontal && !_rtlScrollBehavior.i) ? (trackLength - handleLength - offset) : offset; + offset = MATH.max(0, offset); + return offset; + }; + var scrollRatio = getScrollRatio(nativeScroll); + var unsnappedScrollRatio = getScrollRatio(currentScroll); + var handleOffset = getHandleOffset(unsnappedScrollRatio); + var snappedHandleOffset = getHandleOffset(scrollRatio); + + scrollbarVarsInfo._maxScroll = maxScroll; + scrollbarVarsInfo._currentScroll = nativeScroll; + scrollbarVarsInfo._currentScrollRatio = scrollRatio; + + if (_supportTransform) { + transformOffset = isRTLisHorizontal ? -(trackLength - handleLength - handleOffset) : handleOffset; //in px + //transformOffset = (transformOffset / trackLength * 100) * (trackLength / handleLength); //in % + translateValue = isHorizontal ? strTranslateBrace + transformOffset + 'px, 0)' : strTranslateBrace + '0, ' + transformOffset + 'px)'; + + handleCSS[strTransform] = translateValue; + + //apply or clear up transition + if (_supportTransition) + handleCSS[strTransition] = transition && MATH.abs(handleOffset - scrollbarVarsInfo._handleOffset) > 1 ? getCSSTransitionString(scrollbarVars._handle) + ', ' + (strTransform + _strSpace + transitionDuration + 'ms') : _strEmpty; + } + else + handleCSS[scrollbarVars._left_top] = handleOffset; + + + //only apply css if offset has changed and overflow exists. + if (!nativeOverlayScrollbarsAreActive()) { + scrollbarVars._handle.css(handleCSS); + + //clear up transition + if (_supportTransform && _supportTransition && transition) { + scrollbarVars._handle.one(_strTransitionEndEvent, function () { + if (!_destroyed) + scrollbarVars._handle.css(strTransition, _strEmpty); + }); + } + } + + scrollbarVarsInfo._handleOffset = handleOffset; + scrollbarVarsInfo._snappedHandleOffset = snappedHandleOffset; + scrollbarVarsInfo._trackLength = trackLength; + } + + /** + * Refreshes the interactivity of the given scrollbar element. + * @param isTrack True if the track element is the target, false if the handle element is the target. + * @param value True for interactivity false for no interactivity. + */ + function refreshScrollbarsInteractive(isTrack, value) { + var action = value ? 'removeClass' : 'addClass'; + var element1 = isTrack ? _scrollbarHorizontalTrackElement : _scrollbarHorizontalHandleElement; + var element2 = isTrack ? _scrollbarVerticalTrackElement : _scrollbarVerticalHandleElement; + var className = isTrack ? _classNameScrollbarTrackOff : _classNameScrollbarHandleOff; + + element1[action](className); + element2[action](className); + } + + /** + * Returns a object which is used for fast access for specific variables. + * @param isHorizontal True if the horizontal scrollbar vars shall be accessed, false if the vertical scrollbar vars shall be accessed. + * @returns {{wh: string, WH: string, lt: string, _wh: string, _lt: string, t: *, h: *, c: {}, s: *}} + */ + function getScrollbarVars(isHorizontal) { + return { + _width_height: isHorizontal ? _strWidth : _strHeight, + _Width_Height: isHorizontal ? 'Width' : 'Height', + _left_top: isHorizontal ? _strLeft : _strTop, + _Left_Top: isHorizontal ? 'Left' : 'Top', + _x_y: isHorizontal ? _strX : _strY, + _X_Y: isHorizontal ? 'X' : 'Y', + _w_h: isHorizontal ? 'w' : 'h', + _l_t: isHorizontal ? 'l' : 't', + _track: isHorizontal ? _scrollbarHorizontalTrackElement : _scrollbarVerticalTrackElement, + _handle: isHorizontal ? _scrollbarHorizontalHandleElement : _scrollbarVerticalHandleElement, + _scrollbar: isHorizontal ? _scrollbarHorizontalElement : _scrollbarVerticalElement, + _info: isHorizontal ? _scrollHorizontalInfo : _scrollVerticalInfo + }; + } + + + //==== Scrollbar Corner ====// + + /** + * Builds or destroys the scrollbar corner DOM element. + * @param destroy Indicates whether the DOM shall be build or destroyed. + */ + function setupScrollbarCornerDOM(destroy) { + _scrollbarCornerElement = _scrollbarCornerElement || selectOrGenerateDivByClass(_classNameScrollbarCorner, true); + + if (!destroy) { + if (!_domExists) { + _hostElement.append(_scrollbarCornerElement); + } + } + else { + if (_domExists && _initialized) { + removeClass(_scrollbarCornerElement.removeAttr(LEXICON.s), _classNamesDynamicDestroy); + } + else { + remove(_scrollbarCornerElement); + } + } + } + + /** + * Initializes all scrollbar corner interactivity events. + */ + function setupScrollbarCornerEvents() { + var insideIFrame = _windowElementNative.top !== _windowElementNative; + var mouseDownPosition = {}; + var mouseDownSize = {}; + var mouseDownInvertedScale = {}; + var reconnectMutationObserver; + + function documentDragMove(event) { + if (onMouseTouchDownContinue(event)) { + var pageOffset = getCoordinates(event); + var hostElementCSS = {}; + if (_resizeHorizontal || _resizeBoth) + hostElementCSS[_strWidth] = (mouseDownSize.w + (pageOffset.x - mouseDownPosition.x) * mouseDownInvertedScale.x); + if (_resizeVertical || _resizeBoth) + hostElementCSS[_strHeight] = (mouseDownSize.h + (pageOffset.y - mouseDownPosition.y) * mouseDownInvertedScale.y); + _hostElement.css(hostElementCSS); + COMPATIBILITY.stpP(event); + } + else { + documentMouseTouchUp(event); + } + } + function documentMouseTouchUp(event) { + var eventIsTrusted = event !== undefined; + + setupResponsiveEventListener(_documentElement, + [_strSelectStartEvent, _strMouseTouchMoveEvent, _strMouseTouchUpEvent], + [documentOnSelectStart, documentDragMove, documentMouseTouchUp], + true); + + removeClass(_bodyElement, _classNameDragging); + if (_scrollbarCornerElement.releaseCapture) + _scrollbarCornerElement.releaseCapture(); + + if (eventIsTrusted) { + if (reconnectMutationObserver) + connectMutationObservers(); + _base.update(_strAuto); + } + reconnectMutationObserver = false; + } + function onMouseTouchDownContinue(event) { + var originalEvent = event.originalEvent || event; + var isTouchEvent = originalEvent.touches !== undefined; + return _sleeping || _destroyed ? false : COMPATIBILITY.mBtn(event) === 1 || isTouchEvent; + } + function getCoordinates(event) { + return _msieVersion && insideIFrame ? { x: event.screenX, y: event.screenY } : COMPATIBILITY.page(event); + } + + addDestroyEventListener(_scrollbarCornerElement, _strMouseTouchDownEvent, function (event) { + if (onMouseTouchDownContinue(event) && !_resizeNone) { + if (_mutationObserversConnected) { + reconnectMutationObserver = true; + disconnectMutationObservers(); + } + + mouseDownPosition = getCoordinates(event); + + mouseDownSize.w = _hostElementNative[LEXICON.oW] - (!_isBorderBox ? _paddingX : 0); + mouseDownSize.h = _hostElementNative[LEXICON.oH] - (!_isBorderBox ? _paddingY : 0); + mouseDownInvertedScale = getHostElementInvertedScale(); + + setupResponsiveEventListener(_documentElement, + [_strSelectStartEvent, _strMouseTouchMoveEvent, _strMouseTouchUpEvent], + [documentOnSelectStart, documentDragMove, documentMouseTouchUp]); + + addClass(_bodyElement, _classNameDragging); + if (_scrollbarCornerElement.setCapture) + _scrollbarCornerElement.setCapture(); + + COMPATIBILITY.prvD(event); + COMPATIBILITY.stpP(event); + } + }); + } + + + //==== Utils ====// + + /** + * Calls the callback with the given name. The Context of this callback is always _base (this). + * @param name The name of the target which shall be called. + * @param args The args with which the callback shall be called. + * @param dependent Boolean which decides whether the callback shall be fired, undefined is like a "true" value. + */ + function dispatchCallback(name, args, dependent) { + if (dependent === false) + return; + if (_initialized) { + var callback = _currentPreparedOptions.callbacks[name]; + var extensionOnName = name; + var ext; + + if (extensionOnName.substr(0, 2) === 'on') + extensionOnName = extensionOnName.substr(2, 1).toLowerCase() + extensionOnName.substr(3); + + if (type(callback) == TYPES.f) + callback.call(_base, args); + + each(_extensions, function () { + ext = this; + if (type(ext.on) == TYPES.f) + ext.on(extensionOnName, args); + }); + } + else if (!_destroyed) + _callbacksInitQeueue.push({ n: name, a: args }); + } + + /** + * Sets the "top, right, bottom, left" properties, with a given prefix, of the given css object. + * @param targetCSSObject The css object to which the values shall be applied. + * @param prefix The prefix of the "top, right, bottom, left" css properties. (example: 'padding-' is a valid prefix) + * @param values A array of values which shall be applied to the "top, right, bottom, left" -properties. The array order is [top, right, bottom, left]. + * If this argument is undefined the value '' (empty string) will be applied to all properties. + */ + function setTopRightBottomLeft(targetCSSObject, prefix, values) { + prefix = prefix || _strEmpty; + values = values || [_strEmpty, _strEmpty, _strEmpty, _strEmpty]; + + targetCSSObject[prefix + _strTop] = values[0]; + targetCSSObject[prefix + _strRight] = values[1]; + targetCSSObject[prefix + _strBottom] = values[2]; + targetCSSObject[prefix + _strLeft] = values[3]; + } + + /** + * Gets the "top, right, bottom, left" CSS properties of the CSS property with the given prefix from the host element. + * @param prefix The prefix of the "top, right, bottom, left" css properties. (example: 'padding-' is a valid prefix) + * @param suffix The suffix of the "top, right, bottom, left" css properties. (example: 'border-' is a valid prefix with '-width' is a valid suffix) + * @param zeroX True if the x axis shall be 0. + * @param zeroY True if the y axis shall be 0. + * @returns {{}} The object which contains the numbers of the read CSS properties. + */ + function getTopRightBottomLeftHost(prefix, suffix, zeroX, zeroY) { + suffix = suffix || _strEmpty; + prefix = prefix || _strEmpty; + return { + t: zeroY ? 0 : parseToZeroOrNumber(_hostElement.css(prefix + _strTop + suffix)), + r: zeroX ? 0 : parseToZeroOrNumber(_hostElement.css(prefix + _strRight + suffix)), + b: zeroY ? 0 : parseToZeroOrNumber(_hostElement.css(prefix + _strBottom + suffix)), + l: zeroX ? 0 : parseToZeroOrNumber(_hostElement.css(prefix + _strLeft + suffix)) + }; + } + + /** + * Returns the computed CSS transition string from the given element. + * @param element The element from which the transition string shall be returned. + * @returns {string} The CSS transition string from the given element. + */ + function getCSSTransitionString(element) { + var transitionStr = VENDORS._cssProperty('transition'); + var assembledValue = element.css(transitionStr); + if (assembledValue) + return assembledValue; + var regExpString = '\\s*(' + '([^,(]+(\\(.+?\\))?)+' + ')[\\s,]*'; + var regExpMain = new RegExp(regExpString); + var regExpValidate = new RegExp('^(' + regExpString + ')+$'); + var properties = 'property duration timing-function delay'.split(' '); + var result = []; + var strResult; + var valueArray; + var i = 0; + var j; + var splitCssStyleByComma = function (str) { + strResult = []; + if (!str.match(regExpValidate)) + return str; + while (str.match(regExpMain)) { + strResult.push(RegExp.$1); + str = str.replace(regExpMain, _strEmpty); + } + + return strResult; + }; + for (; i < properties[LEXICON.l]; i++) { + valueArray = splitCssStyleByComma(element.css(transitionStr + '-' + properties[i])); + for (j = 0; j < valueArray[LEXICON.l]; j++) + result[j] = (result[j] ? result[j] + _strSpace : _strEmpty) + valueArray[j]; + } + return result.join(', '); + } + + /** + * Generates a Regular Expression which matches with a string which starts with 'os-host'. + * @param {boolean} withCurrClassNameOption The Regular Expression also matches if the string is the current ClassName option (multiple values splitted by space possible). + * @param {boolean} withOldClassNameOption The Regular Expression also matches if the string is the old ClassName option (multiple values splitted by space possible). + */ + function createHostClassNameRegExp(withCurrClassNameOption, withOldClassNameOption) { + var i; + var split; + var appendix; + var appendClasses = function (classes, condition) { + appendix = ''; + if (condition && typeof classes == TYPES.s) { + split = classes.split(_strSpace); + for (i = 0; i < split[LEXICON.l]; i++) + appendix += '|' + split[i] + '$'; + // split[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&') for escaping regex characters + } + return appendix; + }; + + return new RegExp( + '(^' + _classNameHostElement + '([-_].+|)$)' + + appendClasses(_classNameCache, withCurrClassNameOption) + + appendClasses(_oldClassName, withOldClassNameOption), 'g'); + } + + /** + * Calculates the host-elements inverted scale. (invertedScale = 1 / scale) + * @returns {{x: number, y: number}} The scale of the host-element. + */ + function getHostElementInvertedScale() { + var rect = _paddingElementNative[LEXICON.bCR](); + return { + x: _supportTransform ? 1 / (MATH.round(rect.width) / _paddingElementNative[LEXICON.oW]) || 1 : 1, + y: _supportTransform ? 1 / (MATH.round(rect.height) / _paddingElementNative[LEXICON.oH]) || 1 : 1 + }; + } + + /** + * Checks whether the given object is a HTMLElement. + * @param o The object which shall be checked. + * @returns {boolean} True the given object is a HTMLElement, false otherwise. + */ + function isHTMLElement(o) { + var strOwnerDocument = 'ownerDocument'; + var strHTMLElement = 'HTMLElement'; + var wnd = o && o[strOwnerDocument] ? (o[strOwnerDocument].parentWindow || window) : window; + return ( + typeof wnd[strHTMLElement] == TYPES.o ? o instanceof wnd[strHTMLElement] : //DOM2 + o && typeof o == TYPES.o && o !== null && o.nodeType === 1 && typeof o.nodeName == TYPES.s + ); + } + + /** + * Compares 2 arrays and returns the differences between them as a array. + * @param a1 The first array which shall be compared. + * @param a2 The second array which shall be compared. + * @returns {Array} The differences between the two arrays. + */ + function getArrayDifferences(a1, a2) { + var a = []; + var diff = []; + var i; + var k; + for (i = 0; i < a1.length; i++) + a[a1[i]] = true; + for (i = 0; i < a2.length; i++) { + if (a[a2[i]]) + delete a[a2[i]]; + else + a[a2[i]] = true; + } + for (k in a) + diff.push(k); + return diff; + } + + /** + * Returns Zero or the number to which the value can be parsed. + * @param value The value which shall be parsed. + * @param toFloat Indicates whether the number shall be parsed to a float. + */ + function parseToZeroOrNumber(value, toFloat) { + var num = toFloat ? parseFloat(value) : parseInt(value, 10); + return isNaN(num) ? 0 : num; + } + + /** + * Gets several information of the textarea and returns them as a object or undefined if the browser doesn't support it. + * @returns {{cursorRow: Number, cursorCol, rows: Number, cols: number, wRow: number, pos: number, max : number}} or undefined if not supported. + */ + function getTextareaInfo() { + //read needed values + var textareaCursorPosition = _targetElementNative.selectionStart; + if (textareaCursorPosition === undefined) + return; + + var textareaValue = _targetElement.val(); + var textareaLength = textareaValue[LEXICON.l]; + var textareaRowSplit = textareaValue.split('\n'); + var textareaLastRow = textareaRowSplit[LEXICON.l]; + var textareaCurrentCursorRowSplit = textareaValue.substr(0, textareaCursorPosition).split('\n'); + var widestRow = 0; + var textareaLastCol = 0; + var cursorRow = textareaCurrentCursorRowSplit[LEXICON.l]; + var cursorCol = textareaCurrentCursorRowSplit[textareaCurrentCursorRowSplit[LEXICON.l] - 1][LEXICON.l]; + var rowCols; + var i; + + //get widest Row and the last column of the textarea + for (i = 0; i < textareaRowSplit[LEXICON.l]; i++) { + rowCols = textareaRowSplit[i][LEXICON.l]; + if (rowCols > textareaLastCol) { + widestRow = i + 1; + textareaLastCol = rowCols; + } + } + + return { + _cursorRow: cursorRow, //cursorRow + _cursorColumn: cursorCol, //cursorCol + _rows: textareaLastRow, //rows + _columns: textareaLastCol, //cols + _widestRow: widestRow, //wRow + _cursorPosition: textareaCursorPosition, //pos + _cursorMax: textareaLength //max + }; + } + + /** + * Determines whether native overlay scrollbars are active. + * @returns {boolean} True if native overlay scrollbars are active, false otherwise. + */ + function nativeOverlayScrollbarsAreActive() { + return (_ignoreOverlayScrollbarHidingCache && (_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y)); + } + + /** + * Gets the element which is used to measure the content size. + * @returns {*} TextareaCover if target element is textarea else the ContentElement. + */ + function getContentMeasureElement() { + return _isTextarea ? _textareaCoverElement[0] : _contentElementNative; + } + + /** + * Generates a string which represents a HTML div with the given classes or attributes. + * @param classesOrAttrs The class of the div as string or a object which represents the attributes of the div. (The class attribute can also be written as "className".) + * @param content The content of the div as string. + * @returns {string} The concated string which represents a HTML div and its content. + */ + function generateDiv(classesOrAttrs, content) { + return '
' + + (content || _strEmpty) + + '
'; + } + + /** + * Selects or generates a div with the given class attribute. + * @param className The class names (divided by spaces) of the div which shall be selected or generated. + * @param selectParentOrOnlyChildren The parent element from which of the element shall be selected. (if undefined or boolean its hostElement) + * If its a boolean it decides whether only the children of the host element shall be selected. + * @returns {*} The generated or selected element. + */ + function selectOrGenerateDivByClass(className, selectParentOrOnlyChildren) { + var onlyChildren = type(selectParentOrOnlyChildren) == TYPES.b; + var selectParent = onlyChildren ? _hostElement : (selectParentOrOnlyChildren || _hostElement); + + return (_domExists && !selectParent[LEXICON.l]) + ? null + : _domExists + ? selectParent[onlyChildren ? 'children' : 'find'](_strDot + className.replace(/\s/g, _strDot)).eq(0) + : FRAMEWORK(generateDiv(className)) + } + + /** + * Gets the value of the given property from the given object. + * @param obj The object from which the property value shall be got. + * @param path The property of which the value shall be got. + * @returns {*} Returns the value of the searched property or undefined of the property wasn't found. + */ + function getObjectPropVal(obj, path) { + var splits = path.split(_strDot); + var i = 0; + var val; + for (; i < splits.length; i++) { + if (!obj[LEXICON.hOP](splits[i])) + return; + val = obj[splits[i]]; + if (i < splits.length && type(val) == TYPES.o) + obj = val; + } + return val; + } + + /** + * Sets the value of the given property from the given object. + * @param obj The object from which the property value shall be set. + * @param path The property of which the value shall be set. + * @param val The value of the property which shall be set. + */ + function setObjectPropVal(obj, path, val) { + var splits = path.split(_strDot); + var splitsLength = splits.length; + var i = 0; + var extendObj = {}; + var extendObjRoot = extendObj; + for (; i < splitsLength; i++) + extendObj = extendObj[splits[i]] = i + 1 < splitsLength ? {} : val; + FRAMEWORK.extend(obj, extendObjRoot, true); + } + + /** + * Runs a action for each selector inside the updateOnLoad option. + * @param {Function} action The action for each updateOnLoad selector, the arguments the function takes is the index and the value (the selector). + */ + function eachUpdateOnLoad(action) { + var updateOnLoad = _currentPreparedOptions.updateOnLoad; + updateOnLoad = type(updateOnLoad) == TYPES.s ? updateOnLoad.split(_strSpace) : updateOnLoad; + + if (COMPATIBILITY.isA(updateOnLoad) && !_destroyed) { + each(updateOnLoad, action); + } + } + + + //==== Utils Cache ====// + + /** + * Compares two values or objects and returns true if they aren't equal. + * @param current The first value or object which shall be compared. + * @param cache The second value or object which shall be compared. + * @param force If true the returned value is always true. + * @returns {boolean} True if both values or objects aren't equal or force is true, false otherwise. + */ + function checkCache(current, cache, force) { + if (force) + return force; + if (type(current) == TYPES.o && type(cache) == TYPES.o) { + for (var prop in current) { + if (prop !== 'c') { + if (current[LEXICON.hOP](prop) && cache[LEXICON.hOP](prop)) { + if (checkCache(current[prop], cache[prop])) + return true; + } + else { + return true; + } + } + } + } + else { + return current !== cache; + } + return false; + } + + + //==== Shortcuts ====// + + /** + * jQuery extend method shortcut with a appended "true" as first argument. + */ + function extendDeep() { + return FRAMEWORK.extend.apply(this, [true].concat([].slice.call(arguments))); + } + + /** + * jQuery addClass method shortcut. + */ + function addClass(el, classes) { + return _frameworkProto.addClass.call(el, classes); + } + + /** + * jQuery removeClass method shortcut. + */ + function removeClass(el, classes) { + return _frameworkProto.removeClass.call(el, classes); + } + + /** + * Adds or removes the given classes dependent on the boolean value. True for add, false for remove. + */ + function addRemoveClass(el, classes, doAdd) { + return doAdd ? addClass(el, classes) : removeClass(el, classes); + } + + /** + * jQuery remove method shortcut. + */ + function remove(el) { + return _frameworkProto.remove.call(el); + } + + /** + * Finds the first child element with the given selector of the given element. + * @param el The root element from which the selector shall be valid. + * @param selector The selector of the searched element. + * @returns {*} The first element which is a child of the given element and matches the givens selector. + */ + function findFirst(el, selector) { + return _frameworkProto.find.call(el, selector).eq(0); + } + + + //==== API ====// + + /** + * Puts the instance to sleep. It wont respond to any changes in the DOM and won't update. Scrollbar Interactivity is also disabled as well as the resize handle. + * This behavior can be reset by calling the update method. + */ + _base.sleep = function () { + _sleeping = true; + }; + + /** + * Updates the plugin and DOM to the current options. + * This method should only be called if a update is 100% required. + * @param force True if every property shall be updated and the cache shall be ignored. + * !INTERNAL USAGE! : force can be a string "auto", "sync" or "zoom" too + * if "auto" then before a real update the content size and host element attributes gets checked, and if they changed only then the update method will be called. + * if "sync" then the async update process (MutationObserver or UpdateLoop) gets synchronized and a corresponding update takes place if one was needed due to pending changes. + * if "zoom" then a update takes place where it's assumed that content and host size changed + * @returns {boolean|undefined} + * If force is "sync" then a boolean is returned which indicates whether a update was needed due to pending changes. + * If force is "auto" then a boolean is returned whether a update was needed due to attribute or size changes. + * undefined otherwise. + */ + _base.update = function (force) { + if (_destroyed) + return; + + var attrsChanged; + var contentSizeC; + var isString = type(force) == TYPES.s; + var doUpdateAuto; + var mutHost; + var mutContent; + + if (isString) { + if (force === _strAuto) { + attrsChanged = meaningfulAttrsChanged(); + contentSizeC = updateAutoContentSizeChanged(); + doUpdateAuto = attrsChanged || contentSizeC; + if (doUpdateAuto) { + update({ + _contentSizeChanged: contentSizeC, + _changedOptions: _initialized ? undefined : _currentPreparedOptions + }); + } + } + else if (force === _strSync) { + if (_mutationObserversConnected) { + mutHost = _mutationObserverHostCallback(_mutationObserverHost.takeRecords()); + mutContent = _mutationObserverContentCallback(_mutationObserverContent.takeRecords()); + } + else { + mutHost = _base.update(_strAuto); + } + } + else if (force === 'zoom') { + update({ + _hostSizeChanged: true, + _contentSizeChanged: true + }); + } + } + else { + force = _sleeping || force; + _sleeping = false; + if (!_base.update(_strSync) || force) + update({ _force: force }); + } + + updateElementsOnLoad(); + + return doUpdateAuto || mutHost || mutContent; + }; + + /** + Gets or sets the current options. The update method will be called automatically if new options were set. + * @param newOptions If new options are given, then the new options will be set, if new options aren't given (undefined or a not a plain object) then the current options will be returned. + * @param value If new options is a property path string, then this value will be used to set the option to which the property path string leads. + * @returns {*} + */ + _base.options = function (newOptions, value) { + var option = {}; + var changedOps; + + //return current options if newOptions are undefined or empty + if (FRAMEWORK.isEmptyObject(newOptions) || !FRAMEWORK.isPlainObject(newOptions)) { + if (type(newOptions) == TYPES.s) { + if (arguments.length > 1) { + setObjectPropVal(option, newOptions, value); + changedOps = setOptions(option); + } + else + return getObjectPropVal(_currentOptions, newOptions); + } + else + return _currentOptions; + } + else { + changedOps = setOptions(newOptions); + } + + if (!FRAMEWORK.isEmptyObject(changedOps)) { + update({ _changedOptions: changedOps }); + } + }; + + /** + * Restore the DOM, disconnects all observers, remove all resize observers and put the instance to sleep. + */ + _base.destroy = function () { + if (_destroyed) + return; + + //remove this instance from auto update loop + autoUpdateLoop.remove(_base); + + //disconnect all mutation observers + disconnectMutationObservers(); + + //remove all resize observers + setupResizeObserver(_sizeObserverElement); + setupResizeObserver(_sizeAutoObserverElement); + + //remove all extensions + for (var extName in _extensions) + _base.removeExt(extName); + + //remove all 'destroy' events + while (_destroyEvents[LEXICON.l] > 0) + _destroyEvents.pop()(); + + //remove all events from host element + setupHostMouseTouchEvents(true); + + //remove all helper / detection elements + if (_contentGlueElement) + remove(_contentGlueElement); + if (_contentArrangeElement) + remove(_contentArrangeElement); + if (_sizeAutoObserverAdded) + remove(_sizeAutoObserverElement); + + //remove all generated DOM + setupScrollbarsDOM(true); + setupScrollbarCornerDOM(true); + setupStructureDOM(true); + + //remove all generated image load events + for (var i = 0; i < _updateOnLoadElms[LEXICON.l]; i++) + FRAMEWORK(_updateOnLoadElms[i]).off(_updateOnLoadEventName, updateOnLoadCallback); + _updateOnLoadElms = undefined; + + _destroyed = true; + _sleeping = true; + + //remove this instance from the instances list + INSTANCES(pluginTargetElement, 0); + dispatchCallback('onDestroyed'); + + //remove all properties and methods + //for (var property in _base) + // delete _base[property]; + //_base = undefined; + }; + + /** + * Scrolls to a given position or element. + * @param coordinates + * 1. Can be "coordinates" which looks like: + * { x : ?, y : ? } OR Object with x and y properties + * { left : ?, top : ? } OR Object with left and top properties + * { l : ?, t : ? } OR Object with l and t properties + * [ ?, ? ] OR Array where the first two element are the coordinates (first is x, second is y) + * ? A single value which stays for both axis + * A value can be a number, a string or a calculation. + * + * Operators: + * [NONE] The current scroll will be overwritten by the value. + * '+=' The value will be added to the current scroll offset + * '-=' The value will be subtracted from the current scroll offset + * '*=' The current scroll wil be multiplicated by the value. + * '/=' The current scroll wil be divided by the value. + * + * Units: + * [NONE] The value is the final scroll amount. final = (value * 1) + * 'px' Same as none + * '%' The value is dependent on the current scroll value. final = ((currentScrollValue / 100) * value) + * 'vw' The value is multiplicated by the viewport width. final = (value * viewportWidth) + * 'vh' The value is multiplicated by the viewport height. final = (value * viewportHeight) + * + * example final values: + * 200, '200px', '50%', '1vw', '1vh', '+=200', '/=1vw', '*=2px', '-=5vh', '+=33%', '+= 50% - 2px', '-= 1vw - 50%' + * + * 2. Can be a HTML or jQuery element: + * The final scroll offset is the offset (without margin) of the given HTML / jQuery element. + * + * 3. Can be a object with a HTML or jQuery element with additional settings: + * { + * el : [HTMLElement, jQuery element], MUST be specified, else this object isn't valid. + * scroll : [string, array, object], Default value is 'always'. + * block : [string, array, object], Default value is 'begin'. + * margin : [number, boolean, array, object] Default value is false. + * } + * + * Possible scroll settings are: + * 'always' Scrolls always. + * 'ifneeded' Scrolls only if the element isnt fully in view. + * 'never' Scrolls never. + * + * Possible block settings are: + * 'begin' Both axis shall be docked to the "begin" edge. - The element will be docked to the top and left edge of the viewport. + * 'end' Both axis shall be docked to the "end" edge. - The element will be docked to the bottom and right edge of the viewport. (If direction is RTL to the bottom and left edge.) + * 'center' Both axis shall be docked to "center". - The element will be centered in the viewport. + * 'nearest' The element will be docked to the nearest edge(s). + * + * Possible margin settings are: -- The actual margin of the element wont be affect, this option affects only the final scroll offset. + * [BOOLEAN] If true the css margin of the element will be used, if false no margin will be used. + * [NUMBER] The margin will be used for all edges. + * + * @param duration The duration of the scroll animation, OR a jQuery animation configuration object. + * @param easing The animation easing. + * @param complete The animation complete callback. + * @returns {{ + * position: {x: number, y: number}, + * ratio: {x: number, y: number}, + * max: {x: number, y: number}, + * handleOffset: {x: number, y: number}, + * handleLength: {x: number, y: number}, + * handleLengthRatio: {x: number, y: number}, t + * rackLength: {x: number, y: number}, + * isRTL: boolean, + * isRTLNormalized: boolean + * }} + */ + _base.scroll = function (coordinates, duration, easing, complete) { + if (arguments.length === 0 || coordinates === undefined) { + var infoX = _scrollHorizontalInfo; + var infoY = _scrollVerticalInfo; + var normalizeInvert = _normalizeRTLCache && _isRTL && _rtlScrollBehavior.i; + var normalizeNegate = _normalizeRTLCache && _isRTL && _rtlScrollBehavior.n; + var scrollX = infoX._currentScroll; + var scrollXRatio = infoX._currentScrollRatio; + var maxScrollX = infoX._maxScroll; + scrollXRatio = normalizeInvert ? 1 - scrollXRatio : scrollXRatio; + scrollX = normalizeInvert ? maxScrollX - scrollX : scrollX; + scrollX *= normalizeNegate ? -1 : 1; + maxScrollX *= normalizeNegate ? -1 : 1; + + return { + position: { + x: scrollX, + y: infoY._currentScroll + }, + ratio: { + x: scrollXRatio, + y: infoY._currentScrollRatio + }, + max: { + x: maxScrollX, + y: infoY._maxScroll + }, + handleOffset: { + x: infoX._handleOffset, + y: infoY._handleOffset + }, + handleLength: { + x: infoX._handleLength, + y: infoY._handleLength + }, + handleLengthRatio: { + x: infoX._handleLengthRatio, + y: infoY._handleLengthRatio + }, + trackLength: { + x: infoX._trackLength, + y: infoY._trackLength + }, + snappedHandleOffset: { + x: infoX._snappedHandleOffset, + y: infoY._snappedHandleOffset + }, + isRTL: _isRTL, + isRTLNormalized: _normalizeRTLCache + }; + } + + _base.update(_strSync); + + var normalizeRTL = _normalizeRTLCache; + var coordinatesXAxisProps = [_strX, _strLeft, 'l']; + var coordinatesYAxisProps = [_strY, _strTop, 't']; + var coordinatesOperators = ['+=', '-=', '*=', '/=']; + var durationIsObject = type(duration) == TYPES.o; + var completeCallback = durationIsObject ? duration.complete : complete; + var i; + var finalScroll = {}; + var specialEasing = {}; + var doScrollLeft; + var doScrollTop; + var animationOptions; + var strEnd = 'end'; + var strBegin = 'begin'; + var strCenter = 'center'; + var strNearest = 'nearest'; + var strAlways = 'always'; + var strNever = 'never'; + var strIfNeeded = 'ifneeded'; + var strLength = LEXICON.l; + var settingsAxis; + var settingsScroll; + var settingsBlock; + var settingsMargin; + var finalElement; + var elementObjSettingsAxisValues = [_strX, _strY, 'xy', 'yx']; + var elementObjSettingsBlockValues = [strBegin, strEnd, strCenter, strNearest]; + var elementObjSettingsScrollValues = [strAlways, strNever, strIfNeeded]; + var coordinatesIsElementObj = coordinates[LEXICON.hOP]('el'); + var possibleElement = coordinatesIsElementObj ? coordinates.el : coordinates; + var possibleElementIsJQuery = possibleElement instanceof FRAMEWORK || JQUERY ? possibleElement instanceof JQUERY : false; + var possibleElementIsHTMLElement = possibleElementIsJQuery ? false : isHTMLElement(possibleElement); + var updateScrollbarInfos = function () { + if (doScrollLeft) + refreshScrollbarHandleOffset(true); + if (doScrollTop) + refreshScrollbarHandleOffset(false); + }; + var proxyCompleteCallback = type(completeCallback) != TYPES.f ? undefined : function () { + updateScrollbarInfos(); + completeCallback(); + }; + function checkSettingsStringValue(currValue, allowedValues) { + for (i = 0; i < allowedValues[strLength]; i++) { + if (currValue === allowedValues[i]) + return true; + } + return false; + } + function getRawScroll(isX, coordinates) { + var coordinateProps = isX ? coordinatesXAxisProps : coordinatesYAxisProps; + coordinates = type(coordinates) == TYPES.s || type(coordinates) == TYPES.n ? [coordinates, coordinates] : coordinates; + + if (COMPATIBILITY.isA(coordinates)) + return isX ? coordinates[0] : coordinates[1]; + else if (type(coordinates) == TYPES.o) { + //decides RTL normalization "hack" with .n + //normalizeRTL = type(coordinates.n) == TYPES.b ? coordinates.n : normalizeRTL; + for (i = 0; i < coordinateProps[strLength]; i++) + if (coordinateProps[i] in coordinates) + return coordinates[coordinateProps[i]]; + } + } + function getFinalScroll(isX, rawScroll) { + var isString = type(rawScroll) == TYPES.s; + var operator; + var amount; + var scrollInfo = isX ? _scrollHorizontalInfo : _scrollVerticalInfo; + var currScroll = scrollInfo._currentScroll; + var maxScroll = scrollInfo._maxScroll; + var mult = ' * '; + var finalValue; + var isRTLisX = _isRTL && isX; + var normalizeShortcuts = isRTLisX && _rtlScrollBehavior.n && !normalizeRTL; + var strReplace = 'replace'; + var evalFunc = eval; + var possibleOperator; + if (isString) { + //check operator + if (rawScroll[strLength] > 2) { + possibleOperator = rawScroll.substr(0, 2); + if (inArray(possibleOperator, coordinatesOperators) > -1) + operator = possibleOperator; + } + + //calculate units and shortcuts + rawScroll = operator ? rawScroll.substr(2) : rawScroll; + rawScroll = rawScroll + [strReplace](/min/g, 0) //'min' = 0% + [strReplace](//g, (normalizeShortcuts ? '-' : _strEmpty) + _strHundredPercent) //'>' = 100% + [strReplace](/px/g, _strEmpty) + [strReplace](/%/g, mult + (maxScroll * (isRTLisX && _rtlScrollBehavior.n ? -1 : 1) / 100.0)) + [strReplace](/vw/g, mult + _viewportSize.w) + [strReplace](/vh/g, mult + _viewportSize.h); + amount = parseToZeroOrNumber(isNaN(rawScroll) ? parseToZeroOrNumber(evalFunc(rawScroll), true).toFixed() : rawScroll); + } + else { + amount = rawScroll; + } + + if (amount !== undefined && !isNaN(amount) && type(amount) == TYPES.n) { + var normalizeIsRTLisX = normalizeRTL && isRTLisX; + var operatorCurrScroll = currScroll * (normalizeIsRTLisX && _rtlScrollBehavior.n ? -1 : 1); + var invert = normalizeIsRTLisX && _rtlScrollBehavior.i; + var negate = normalizeIsRTLisX && _rtlScrollBehavior.n; + operatorCurrScroll = invert ? (maxScroll - operatorCurrScroll) : operatorCurrScroll; + switch (operator) { + case '+=': + finalValue = operatorCurrScroll + amount; + break; + case '-=': + finalValue = operatorCurrScroll - amount; + break; + case '*=': + finalValue = operatorCurrScroll * amount; + break; + case '/=': + finalValue = operatorCurrScroll / amount; + break; + default: + finalValue = amount; + break; + } + finalValue = invert ? maxScroll - finalValue : finalValue; + finalValue *= negate ? -1 : 1; + finalValue = isRTLisX && _rtlScrollBehavior.n ? MATH.min(0, MATH.max(maxScroll, finalValue)) : MATH.max(0, MATH.min(maxScroll, finalValue)); + } + return finalValue === currScroll ? undefined : finalValue; + } + function getPerAxisValue(value, valueInternalType, defaultValue, allowedValues) { + var resultDefault = [defaultValue, defaultValue]; + var valueType = type(value); + var valueArrLength; + var valueArrItem; + + //value can be [ string, or array of two strings ] + if (valueType == valueInternalType) { + value = [value, value]; + } + else if (valueType == TYPES.a) { + valueArrLength = value[strLength]; + if (valueArrLength > 2 || valueArrLength < 1) + value = resultDefault; + else { + if (valueArrLength === 1) + value[1] = defaultValue; + for (i = 0; i < valueArrLength; i++) { + valueArrItem = value[i]; + if (type(valueArrItem) != valueInternalType || !checkSettingsStringValue(valueArrItem, allowedValues)) { + value = resultDefault; + break; + } + } + } + } + else if (valueType == TYPES.o) + value = [value[_strX] || defaultValue, value[_strY] || defaultValue]; + else + value = resultDefault; + return { x: value[0], y: value[1] }; + } + function generateMargin(marginTopRightBottomLeftArray) { + var result = []; + var currValue; + var currValueType; + var valueDirections = [_strTop, _strRight, _strBottom, _strLeft]; + for (i = 0; i < marginTopRightBottomLeftArray[strLength]; i++) { + if (i === valueDirections[strLength]) + break; + currValue = marginTopRightBottomLeftArray[i]; + currValueType = type(currValue); + if (currValueType == TYPES.b) + result.push(currValue ? parseToZeroOrNumber(finalElement.css(_strMarginMinus + valueDirections[i])) : 0); + else + result.push(currValueType == TYPES.n ? currValue : 0); + } + return result; + } + + if (possibleElementIsJQuery || possibleElementIsHTMLElement) { + //get settings + var margin = coordinatesIsElementObj ? coordinates.margin : 0; + var axis = coordinatesIsElementObj ? coordinates.axis : 0; + var scroll = coordinatesIsElementObj ? coordinates.scroll : 0; + var block = coordinatesIsElementObj ? coordinates.block : 0; + var marginDefault = [0, 0, 0, 0]; + var marginType = type(margin); + var marginLength; + finalElement = possibleElementIsJQuery ? possibleElement : FRAMEWORK(possibleElement); + + if (finalElement[strLength] > 0) { + //margin can be [ boolean, number, array of 2, array of 4, object ] + if (marginType == TYPES.n || marginType == TYPES.b) + margin = generateMargin([margin, margin, margin, margin]); + else if (marginType == TYPES.a) { + marginLength = margin[strLength]; + if (marginLength === 2) + margin = generateMargin([margin[0], margin[1], margin[0], margin[1]]); + else if (marginLength >= 4) + margin = generateMargin(margin); + else + margin = marginDefault; + } + else if (marginType == TYPES.o) + margin = generateMargin([margin[_strTop], margin[_strRight], margin[_strBottom], margin[_strLeft]]); + else + margin = marginDefault; + + //block = type(block) === TYPES.b ? block ? [ strNearest, strBegin ] : [ strNearest, strEnd ] : block; + settingsAxis = checkSettingsStringValue(axis, elementObjSettingsAxisValues) ? axis : 'xy'; + settingsScroll = getPerAxisValue(scroll, TYPES.s, strAlways, elementObjSettingsScrollValues); + settingsBlock = getPerAxisValue(block, TYPES.s, strBegin, elementObjSettingsBlockValues); + settingsMargin = margin; + + var viewportScroll = { + l: _scrollHorizontalInfo._currentScroll, + t: _scrollVerticalInfo._currentScroll + }; + // use padding element instead of viewport element because padding element has never padding, margin or position applied. + var viewportOffset = _paddingElement.offset(); + + //get coordinates + var elementOffset = finalElement.offset(); + var doNotScroll = { + x: settingsScroll.x == strNever || settingsAxis == _strY, + y: settingsScroll.y == strNever || settingsAxis == _strX + }; + elementOffset[_strTop] -= settingsMargin[0]; + elementOffset[_strLeft] -= settingsMargin[3]; + var elementScrollCoordinates = { + x: MATH.round(elementOffset[_strLeft] - viewportOffset[_strLeft] + viewportScroll.l), + y: MATH.round(elementOffset[_strTop] - viewportOffset[_strTop] + viewportScroll.t) + }; + if (_isRTL) { + if (!_rtlScrollBehavior.n && !_rtlScrollBehavior.i) + elementScrollCoordinates.x = MATH.round(viewportOffset[_strLeft] - elementOffset[_strLeft] + viewportScroll.l); + if (_rtlScrollBehavior.n && normalizeRTL) + elementScrollCoordinates.x *= -1; + if (_rtlScrollBehavior.i && normalizeRTL) + elementScrollCoordinates.x = MATH.round(viewportOffset[_strLeft] - elementOffset[_strLeft] + (_scrollHorizontalInfo._maxScroll - viewportScroll.l)); + } + + //measuring is required + if (settingsBlock.x != strBegin || settingsBlock.y != strBegin || settingsScroll.x == strIfNeeded || settingsScroll.y == strIfNeeded || _isRTL) { + var measuringElm = finalElement[0]; + var rawElementSize = _supportTransform ? measuringElm[LEXICON.bCR]() : { + width: measuringElm[LEXICON.oW], + height: measuringElm[LEXICON.oH] + }; + var elementSize = { + w: rawElementSize[_strWidth] + settingsMargin[3] + settingsMargin[1], + h: rawElementSize[_strHeight] + settingsMargin[0] + settingsMargin[2] + }; + var finalizeBlock = function (isX) { + var vars = getScrollbarVars(isX); + var wh = vars._w_h; + var lt = vars._left_top; + var xy = vars._x_y; + var blockIsEnd = settingsBlock[xy] == (isX ? _isRTL ? strBegin : strEnd : strEnd); + var blockIsCenter = settingsBlock[xy] == strCenter; + var blockIsNearest = settingsBlock[xy] == strNearest; + var scrollNever = settingsScroll[xy] == strNever; + var scrollIfNeeded = settingsScroll[xy] == strIfNeeded; + var vpSize = _viewportSize[wh]; + var vpOffset = viewportOffset[lt]; + var elSize = elementSize[wh]; + var elOffset = elementOffset[lt]; + var divide = blockIsCenter ? 2 : 1; + var elementCenterOffset = elOffset + (elSize / 2); + var viewportCenterOffset = vpOffset + (vpSize / 2); + var isInView = + elSize <= vpSize + && elOffset >= vpOffset + && elOffset + elSize <= vpOffset + vpSize; + + if (scrollNever) + doNotScroll[xy] = true; + else if (!doNotScroll[xy]) { + if (blockIsNearest || scrollIfNeeded) { + doNotScroll[xy] = scrollIfNeeded ? isInView : false; + blockIsEnd = elSize < vpSize ? elementCenterOffset > viewportCenterOffset : elementCenterOffset < viewportCenterOffset; + } + elementScrollCoordinates[xy] -= blockIsEnd || blockIsCenter ? ((vpSize / divide) - (elSize / divide)) * (isX && _isRTL && normalizeRTL ? -1 : 1) : 0; + } + }; + finalizeBlock(true); + finalizeBlock(false); + } + + if (doNotScroll.y) + delete elementScrollCoordinates.y; + if (doNotScroll.x) + delete elementScrollCoordinates.x; + + coordinates = elementScrollCoordinates; + } + } + + finalScroll[_strScrollLeft] = getFinalScroll(true, getRawScroll(true, coordinates)); + finalScroll[_strScrollTop] = getFinalScroll(false, getRawScroll(false, coordinates)); + doScrollLeft = finalScroll[_strScrollLeft] !== undefined; + doScrollTop = finalScroll[_strScrollTop] !== undefined; + + if ((doScrollLeft || doScrollTop) && (duration > 0 || durationIsObject)) { + if (durationIsObject) { + duration.complete = proxyCompleteCallback; + _viewportElement.animate(finalScroll, duration); + } + else { + animationOptions = { + duration: duration, + complete: proxyCompleteCallback + }; + if (COMPATIBILITY.isA(easing) || FRAMEWORK.isPlainObject(easing)) { + specialEasing[_strScrollLeft] = easing[0] || easing.x; + specialEasing[_strScrollTop] = easing[1] || easing.y; + animationOptions.specialEasing = specialEasing; + } + else { + animationOptions.easing = easing; + } + _viewportElement.animate(finalScroll, animationOptions); + } + } + else { + if (doScrollLeft) + _viewportElement[_strScrollLeft](finalScroll[_strScrollLeft]); + if (doScrollTop) + _viewportElement[_strScrollTop](finalScroll[_strScrollTop]); + updateScrollbarInfos(); + } + }; + + /** + * Stops all scroll animations. + * @returns {*} The current OverlayScrollbars instance (for chaining). + */ + _base.scrollStop = function (param1, param2, param3) { + _viewportElement.stop(param1, param2, param3); + return _base; + }; + + /** + * Returns all relevant elements. + * @param elementName The name of the element which shall be returned. + * @returns {{target: *, host: *, padding: *, viewport: *, content: *, scrollbarHorizontal: {scrollbar: *, track: *, handle: *}, scrollbarVertical: {scrollbar: *, track: *, handle: *}, scrollbarCorner: *} | *} + */ + _base.getElements = function (elementName) { + var obj = { + target: _targetElementNative, + host: _hostElementNative, + padding: _paddingElementNative, + viewport: _viewportElementNative, + content: _contentElementNative, + scrollbarHorizontal: { + scrollbar: _scrollbarHorizontalElement[0], + track: _scrollbarHorizontalTrackElement[0], + handle: _scrollbarHorizontalHandleElement[0] + }, + scrollbarVertical: { + scrollbar: _scrollbarVerticalElement[0], + track: _scrollbarVerticalTrackElement[0], + handle: _scrollbarVerticalHandleElement[0] + }, + scrollbarCorner: _scrollbarCornerElement[0] + }; + return type(elementName) == TYPES.s ? getObjectPropVal(obj, elementName) : obj; + }; + + /** + * Returns a object which describes the current state of this instance. + * @param stateProperty A specific property from the state object which shall be returned. + * @returns {{widthAuto, heightAuto, overflowAmount, hideOverflow, hasOverflow, contentScrollSize, viewportSize, hostSize, autoUpdate} | *} + */ + _base.getState = function (stateProperty) { + function prepare(obj) { + if (!FRAMEWORK.isPlainObject(obj)) + return obj; + var extended = extendDeep({}, obj); + var changePropertyName = function (from, to) { + if (extended[LEXICON.hOP](from)) { + extended[to] = extended[from]; + delete extended[from]; + } + }; + changePropertyName('w', _strWidth); //change w to width + changePropertyName('h', _strHeight); //change h to height + delete extended.c; //delete c (the 'changed' prop) + return extended; + }; + var obj = { + destroyed: !!prepare(_destroyed), + sleeping: !!prepare(_sleeping), + autoUpdate: prepare(!_mutationObserversConnected), + widthAuto: prepare(_widthAutoCache), + heightAuto: prepare(_heightAutoCache), + padding: prepare(_cssPaddingCache), + overflowAmount: prepare(_overflowAmountCache), + hideOverflow: prepare(_hideOverflowCache), + hasOverflow: prepare(_hasOverflowCache), + contentScrollSize: prepare(_contentScrollSizeCache), + viewportSize: prepare(_viewportSize), + hostSize: prepare(_hostSizeCache), + documentMixed: prepare(_documentMixed) + }; + return type(stateProperty) == TYPES.s ? getObjectPropVal(obj, stateProperty) : obj; + }; + + /** + * Gets all or specific extension instance. + * @param extName The name of the extension from which the instance shall be got. + * @returns {{}} The instance of the extension with the given name or undefined if the instance couldn't be found. + */ + _base.ext = function (extName) { + var result; + var privateMethods = _extensionsPrivateMethods.split(' '); + var i = 0; + if (type(extName) == TYPES.s) { + if (_extensions[LEXICON.hOP](extName)) { + result = extendDeep({}, _extensions[extName]); + for (; i < privateMethods.length; i++) + delete result[privateMethods[i]]; + } + } + else { + result = {}; + for (i in _extensions) + result[i] = extendDeep({}, _base.ext(i)); + } + return result; + }; + + /** + * Adds a extension to this instance. + * @param extName The name of the extension which shall be added. + * @param extensionOptions The extension options which shall be used. + * @returns {{}} The instance of the added extension or undefined if the extension couldn't be added properly. + */ + _base.addExt = function (extName, extensionOptions) { + var registeredExtensionObj = _plugin.extension(extName); + var instance; + var instanceAdded; + var instanceContract; + var contractResult; + var contractFulfilled = true; + if (registeredExtensionObj) { + if (!_extensions[LEXICON.hOP](extName)) { + instance = registeredExtensionObj.extensionFactory.call(_base, + extendDeep({}, registeredExtensionObj.defaultOptions), + FRAMEWORK, + COMPATIBILITY); + + if (instance) { + instanceContract = instance.contract; + if (type(instanceContract) == TYPES.f) { + contractResult = instanceContract(window); + contractFulfilled = type(contractResult) == TYPES.b ? contractResult : contractFulfilled; + } + if (contractFulfilled) { + _extensions[extName] = instance; + instanceAdded = instance.added; + if (type(instanceAdded) == TYPES.f) + instanceAdded(extensionOptions); + + return _base.ext(extName); + } + } + } + else + return _base.ext(extName); + } + else + console.warn("A extension with the name \"" + extName + "\" isn't registered."); + }; + + /** + * Removes a extension from this instance. + * @param extName The name of the extension which shall be removed. + * @returns {boolean} True if the extension was removed, false otherwise e.g. if the extension wasn't added before. + */ + _base.removeExt = function (extName) { + var instance = _extensions[extName]; + var instanceRemoved; + if (instance) { + delete _extensions[extName]; + + instanceRemoved = instance.removed; + if (type(instanceRemoved) == TYPES.f) + instanceRemoved(); + + return true; + } + return false; + }; + + /** + * Constructs the plugin. + * @param targetElement The element to which the plugin shall be applied. + * @param options The initial options of the plugin. + * @param extensions The extension(s) which shall be added right after the initialization. + * @returns {boolean} True if the plugin was successfully initialized, false otherwise. + */ + function construct(targetElement, options, extensions) { + _defaultOptions = globals.defaultOptions; + _nativeScrollbarStyling = globals.nativeScrollbarStyling; + _nativeScrollbarSize = extendDeep({}, globals.nativeScrollbarSize); + _nativeScrollbarIsOverlaid = extendDeep({}, globals.nativeScrollbarIsOverlaid); + _overlayScrollbarDummySize = extendDeep({}, globals.overlayScrollbarDummySize); + _rtlScrollBehavior = extendDeep({}, globals.rtlScrollBehavior); + + //parse & set options but don't update + setOptions(extendDeep({}, _defaultOptions, options)); + + _cssCalc = globals.cssCalc; + _msieVersion = globals.msie; + _autoUpdateRecommended = globals.autoUpdateRecommended; + _supportTransition = globals.supportTransition; + _supportTransform = globals.supportTransform; + _supportPassiveEvents = globals.supportPassiveEvents; + _supportResizeObserver = globals.supportResizeObserver; + _supportMutationObserver = globals.supportMutationObserver; + _restrictedMeasuring = globals.restrictedMeasuring; + _documentElement = FRAMEWORK(targetElement.ownerDocument); + _documentElementNative = _documentElement[0]; + _windowElement = FRAMEWORK(_documentElementNative.defaultView || _documentElementNative.parentWindow); + _windowElementNative = _windowElement[0]; + _htmlElement = findFirst(_documentElement, 'html'); + _bodyElement = findFirst(_htmlElement, 'body'); + _targetElement = FRAMEWORK(targetElement); + _targetElementNative = _targetElement[0]; + _isTextarea = _targetElement.is('textarea'); + _isBody = _targetElement.is('body'); + _documentMixed = _documentElementNative !== document; + + /* On a div Element The if checks only whether: + * - the targetElement has the class "os-host" + * - the targetElement has a a child with the class "os-padding" + * + * If that's the case, its assumed the DOM has already the following structure: + * (The ".os-host" element is the targetElement) + * + *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * + * ===================================================================================== + * + * On a Textarea Element The if checks only whether: + * - the targetElement has the class "os-textarea" + * - the targetElement is inside a element with the class "os-content" + * + * If that's the case, its assumed the DOM has already the following structure: + * (The ".os-textarea" (textarea) element is the targetElement) + * + *
+ *
+ *
+ *
+ *
+ *
+ * + *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */ + _domExists = _isTextarea + ? _targetElement.hasClass(_classNameTextareaElement) && _targetElement.parent().hasClass(_classNameContentElement) + : _targetElement.hasClass(_classNameHostElement) && _targetElement.children(_strDot + _classNamePaddingElement)[LEXICON.l]; + + var initBodyScroll; + var bodyMouseTouchDownListener; + + //check if the plugin hasn't to be initialized + if (_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y && !_currentPreparedOptions.nativeScrollbarsOverlaid.initialize) { + dispatchCallback('onInitializationWithdrawn'); + if (_domExists) { + setupStructureDOM(true); + setupScrollbarsDOM(true); + setupScrollbarCornerDOM(true); + } + + _destroyed = true; + _sleeping = true; + + return _base; + } + + if (_isBody) { + initBodyScroll = {}; + initBodyScroll.l = MATH.max(_targetElement[_strScrollLeft](), _htmlElement[_strScrollLeft](), _windowElement[_strScrollLeft]()); + initBodyScroll.t = MATH.max(_targetElement[_strScrollTop](), _htmlElement[_strScrollTop](), _windowElement[_strScrollTop]()); + + bodyMouseTouchDownListener = function () { + _viewportElement.removeAttr(LEXICON.ti); + setupResponsiveEventListener(_viewportElement, _strMouseTouchDownEvent, bodyMouseTouchDownListener, true, true); + } + } + + //build OverlayScrollbars DOM + setupStructureDOM(); + setupScrollbarsDOM(); + setupScrollbarCornerDOM(); + + //create OverlayScrollbars events + setupStructureEvents(); + setupScrollbarEvents(true); + setupScrollbarEvents(false); + setupScrollbarCornerEvents(); + + //create mutation observers + createMutationObservers(); + + //build resize observer for the host element + setupResizeObserver(_sizeObserverElement, hostOnResized); + + if (_isBody) { + //apply the body scroll to handle it right in the update method + _viewportElement[_strScrollLeft](initBodyScroll.l)[_strScrollTop](initBodyScroll.t); + + //set the focus on the viewport element so you dont have to click on the page to use keyboard keys (up / down / space) for scrolling + if (document.activeElement == targetElement && _viewportElementNative.focus) { + //set a tabindex to make the viewportElement focusable + _viewportElement.attr(LEXICON.ti, '-1'); + _viewportElementNative.focus(); + + /* the tabindex has to be removed due to; + * If you set the tabindex attribute on an
, then its child content cannot be scrolled with the arrow keys unless you set tabindex on the content, too + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex + */ + setupResponsiveEventListener(_viewportElement, _strMouseTouchDownEvent, bodyMouseTouchDownListener, false, true); + } + } + + //update for the first time & initialize cache + _base.update(_strAuto); + + //the plugin is initialized now! + _initialized = true; + dispatchCallback('onInitialized'); + + //call all callbacks which would fire before the initialized was complete + each(_callbacksInitQeueue, function (index, value) { dispatchCallback(value.n, value.a); }); + _callbacksInitQeueue = []; + + //add extensions + if (type(extensions) == TYPES.s) + extensions = [extensions]; + if (COMPATIBILITY.isA(extensions)) + each(extensions, function (index, value) { _base.addExt(value); }); + else if (FRAMEWORK.isPlainObject(extensions)) + each(extensions, function (key, value) { _base.addExt(key, value); }); + + //add the transition class for transitions AFTER the first update & AFTER the applied extensions (for preventing unwanted transitions) + setTimeout(function () { + if (_supportTransition && !_destroyed) + addClass(_hostElement, _classNameHostTransition); + }, 333); + + return _base; + } + + if (_plugin.valid(construct(pluginTargetElement, options, extensions))) { + INSTANCES(pluginTargetElement, _base); + } + + return _base; + } + + /** + * Initializes a new OverlayScrollbarsInstance object or changes options if already initialized or returns the current instance. + * @param pluginTargetElements The elements to which the Plugin shall be initialized. + * @param options The custom options with which the plugin shall be initialized. + * @param extensions The extension(s) which shall be added right after initialization. + * @returns {*} + */ + _plugin = window[PLUGINNAME] = function (pluginTargetElements, options, extensions) { + if (arguments[LEXICON.l] === 0) + return this; + + var arr = []; + var optsIsPlainObj = FRAMEWORK.isPlainObject(options); + var inst; + var result; + + //pluginTargetElements is null or undefined + if (!pluginTargetElements) + return optsIsPlainObj || !options ? result : arr; + + /* + pluginTargetElements will be converted to: + 1. A jQueryElement Array + 2. A HTMLElement Array + 3. A Array with a single HTML Element + so pluginTargetElements is always a array. + */ + pluginTargetElements = pluginTargetElements[LEXICON.l] != undefined ? pluginTargetElements : [pluginTargetElements[0] || pluginTargetElements]; + initOverlayScrollbarsStatics(); + + if (pluginTargetElements[LEXICON.l] > 0) { + if (optsIsPlainObj) { + FRAMEWORK.each(pluginTargetElements, function (i, v) { + inst = v; + if (inst !== undefined) + arr.push(OverlayScrollbarsInstance(inst, options, extensions, _pluginsGlobals, _pluginsAutoUpdateLoop)); + }); + } + else { + FRAMEWORK.each(pluginTargetElements, function (i, v) { + inst = INSTANCES(v); + if ((options === '!' && _plugin.valid(inst)) || (COMPATIBILITY.type(options) == TYPES.f && options(v, inst))) + arr.push(inst); + else if (options === undefined) + arr.push(inst); + }); + } + result = arr[LEXICON.l] === 1 ? arr[0] : arr; + } + return result; + }; + + /** + * Returns a object which contains global information about the plugin and each instance of it. + * The returned object is just a copy, that means that changes to the returned object won't have any effect to the original object. + */ + _plugin.globals = function () { + initOverlayScrollbarsStatics(); + var globals = FRAMEWORK.extend(true, {}, _pluginsGlobals); + delete globals['msie']; + return globals; + }; + + /** + * Gets or Sets the default options for each new plugin initialization. + * @param newDefaultOptions The object with which the default options shall be extended. + */ + _plugin.defaultOptions = function (newDefaultOptions) { + initOverlayScrollbarsStatics(); + var currDefaultOptions = _pluginsGlobals.defaultOptions; + if (newDefaultOptions === undefined) + return FRAMEWORK.extend(true, {}, currDefaultOptions); + + //set the new default options + _pluginsGlobals.defaultOptions = FRAMEWORK.extend(true, {}, currDefaultOptions, _pluginsOptions._validate(newDefaultOptions, _pluginsOptions._template, true, currDefaultOptions)._default); + }; + + /** + * Checks whether the passed instance is a non-destroyed OverlayScrollbars instance. + * @param osInstance The potential OverlayScrollbars instance which shall be checked. + * @returns {boolean} True if the passed value is a non-destroyed OverlayScrollbars instance, false otherwise. + */ + _plugin.valid = function (osInstance) { + return osInstance instanceof _plugin && !osInstance.getState().destroyed; + }; + + /** + * Registers, Unregisters or returns a extension. + * Register: Pass the name and the extension. (defaultOptions is optional) + * Unregister: Pass the name and anything except a function as extension parameter. + * Get extension: Pass the name of the extension which shall be got. + * Get all extensions: Pass no arguments. + * @param extensionName The name of the extension which shall be registered, unregistered or returned. + * @param extension A function which generates the instance of the extension or anything other to remove a already registered extension. + * @param defaultOptions The default options which shall be used for the registered extension. + */ + _plugin.extension = function (extensionName, extension, defaultOptions) { + var extNameTypeString = COMPATIBILITY.type(extensionName) == TYPES.s; + var argLen = arguments[LEXICON.l]; + var i = 0; + if (argLen < 1 || !extNameTypeString) { + //return a copy of all extension objects + return FRAMEWORK.extend(true, { length: _pluginsExtensions[LEXICON.l] }, _pluginsExtensions); + } + else if (extNameTypeString) { + if (COMPATIBILITY.type(extension) == TYPES.f) { + //register extension + _pluginsExtensions.push({ + name: extensionName, + extensionFactory: extension, + defaultOptions: defaultOptions + }); + } + else { + for (; i < _pluginsExtensions[LEXICON.l]; i++) { + if (_pluginsExtensions[i].name === extensionName) { + if (argLen > 1) + _pluginsExtensions.splice(i, 1); //remove extension + else + return FRAMEWORK.extend(true, {}, _pluginsExtensions[i]); //return extension with the given name + } + } + } + } + }; + + return _plugin; + })(); + + if (JQUERY && JQUERY.fn) { + /** + * The jQuery initialization interface. + * @param options The initial options for the construction of the plugin. To initialize the plugin, this option has to be a object! If it isn't a object, the instance(s) are returned and the plugin wont be initialized. + * @param extensions The extension(s) which shall be added right after initialization. + * @returns {*} After initialization it returns the jQuery element array, else it returns the instance(s) of the elements which are selected. + */ + JQUERY.fn.overlayScrollbars = function (options, extensions) { + var _elements = this; + if (JQUERY.isPlainObject(options)) { + JQUERY.each(_elements, function () { PLUGIN(this, options, extensions); }); + return _elements; + } + else + return PLUGIN(_elements, options); + }; + } + return PLUGIN; + } +)); \ No newline at end of file diff --git a/src/main/resources/static/plugins/overlayScrollbars/js/OverlayScrollbars.min.js b/src/main/resources/static/plugins/overlayScrollbars/js/OverlayScrollbars.min.js new file mode 100644 index 0000000..a2e1a12 --- /dev/null +++ b/src/main/resources/static/plugins/overlayScrollbars/js/OverlayScrollbars.min.js @@ -0,0 +1,13 @@ +/*! + * OverlayScrollbars + * https://github.com/KingSora/OverlayScrollbars + * + * Version: 1.13.0 + * + * Copyright KingSora | Rene Haas. + * https://github.com/KingSora + * + * Released under the MIT license. + * Date: 02.08.2020 + */ +!function(n,t){"function"==typeof define&&define.amd?define(function(){return t(n,n.document,undefined)}):"object"==typeof module&&"object"==typeof module.exports?module.exports=t(n,n.document,undefined):t(n,n.document,undefined)}("undefined"!=typeof window?window:this,function(vi,hi,di){"use strict";var o,l,a,u,pi="object",bi="function",mi="array",gi="string",wi="boolean",yi="number",f="undefined",n="null",xi={c:"class",s:"style",i:"id",l:"length",p:"prototype",ti:"tabindex",oH:"offsetHeight",cH:"clientHeight",sH:"scrollHeight",oW:"offsetWidth",cW:"clientWidth",sW:"scrollWidth",hOP:"hasOwnProperty",bCR:"getBoundingClientRect"},_i=(o={},l={},{e:a=["-webkit-","-moz-","-o-","-ms-"],u:u=["WebKit","Moz","O","MS"],v:function(n){var t=l[n];if(l[xi.hOP](n))return t;for(var r,e,i,o=c(n),u=hi.createElement("div")[xi.s],f=0;f
'),o=S[0],e=Ci(S.children("div").eq(0));O.append(S),S.hide().show();var t,r,u,f,a,c,s,l,v,h=z(o),d={x:0===h.x,y:0===h.y},p=(r=vi.navigator.userAgent,f="substring",a=r[u="indexOf"]("MSIE "),c=r[u]("Trident/"),s=r[u]("Edge/"),l=r[u]("rv:"),v=parseInt,0i&&(t.update("auto"),d[a]=new Date(o+=i)),n=Si.max(1,Si.min(n,i)));b=n}}else b=33};this.add=function(n){-1===e(n,h)&&(h.push(n),d.push(s()),0/g,(l?"-":me)+ye)[v](/px/g,me)[v](/%/g," * "+c*(s&&Ct.n?-1:1)/100)[v](/vw/g," * "+ee.w)[v](/vh/g," * "+ee.h),ii(isNaN(t)?ii(h(t),!0).toFixed():t)):t)!==di&&!isNaN(e)&&cn(e)==yi){var d=y&&s,p=a*(d&&Ct.n?-1:1),b=d&&Ct.i,m=d&&Ct.n;switch(p=b?c-p:p,r){case"+=":i=p+e;break;case"-=":i=p-e;break;case"*=":i=p*e;break;case"/=":i=p/e;break;default:i=e}i=b?c-i:i,i*=m?-1:1,i=s&&Ct.n?Si.min(0,Si.max(c,i)):Si.max(0,Si.min(c,i))}return i===a?di:i}function Q(n,t,r,e){var i,o,u=[r,r],f=cn(n);if(f==t)n=[n,n];else if(f==mi){if(2<(i=n[A])||i<1)n=u;else for(1===i&&(n[1]=r),c=0;c=t.left&&n.clientX<=t.right&&n.clientY>=t.top&&n.clientY<=t.bottom||Zn(),(Yr||Gr)&&Ge(!1)}}function W(n){i=Zt[A](),i=isNaN(i)?0:i,(Qt&&S&&!Ct.n||!Qt)&&(i=i<0?0:i),k=vt()[T],C=c(n),Q=!s(u),ci(j,Mn),ci(e.ln,o),ci(e.cn,o),Xn(P,[$,V,K],[d,N,tt]),Oi.rAF()(function(){Xn(P,f,h,!1,{V:!0})}),!D&&y||Oi.prvD(n),Oi.stpP(n)}Yn(e.ln,U,function p(n){R(n)&&W(n)}),Yn(e.sn,[U,q,X],[function M(n){if(R(n)){var h,t=e.vn.M/Math.round(Si.min(1,ee[e.j]/vr[e.j])*e.vn.F),d=Si.round(ee[e.j]*t),p=270*t,b=400*t,m=e.sn.offset()[e.B],r=n.ctrlKey,g=n.shiftKey,w=g&&r,y=!0,x=function(n){Q&&Je(S,n)},_=function(){x(),W(n)},O=function(){if(!Et){var n=(C-m)*k,t=I.W,r=I.F,e=I.M,i=I.N,o=I.L,u=p*H,f=y?Si.max(b,u):u,a=i*((n-e/2)/(r-e)),c=Qt&&S&&(!Ct.i&&!Ct.n||Wr),s=c?t"+(n||me)+""}function pt(n,t){var r=cn(t)==wi,e=!r&&t||Yt;return p&&!e[xi.l]?null:p?e[r?"children":"find"](R+n.replace(/\s/g,R)).eq(0):Ci(ui(n))}function bt(n,t){for(var r,e=t.split(R),i=0;i -1) { + if (argLen > 1) { + //unregister instance + delete target[_instancePropertyString]; + _targets.splice(index, 1); + } + else { + //get instance from target + return _targets[index][_instancePropertyString]; + } + } + } + } + } + })(); + var PLUGIN = (function () { + var _plugin; + var _pluginsGlobals; + var _pluginsAutoUpdateLoop; + var _pluginsExtensions = []; + var _pluginsOptions = (function () { + var type = COMPATIBILITY.type; + var possibleTemplateTypes = [ + TYPES.b, //boolean + TYPES.n, //number + TYPES.s, //string + TYPES.a, //array + TYPES.o, //object + TYPES.f, //function + TYPES.z //null + ]; + var restrictedStringsSplit = ' '; + var restrictedStringsPossibilitiesSplit = ':'; + var classNameAllowedValues = [TYPES.z, TYPES.s]; + var numberAllowedValues = TYPES.n; + var booleanNullAllowedValues = [TYPES.z, TYPES.b]; + var booleanTrueTemplate = [true, TYPES.b]; + var booleanFalseTemplate = [false, TYPES.b]; + var callbackTemplate = [null, [TYPES.z, TYPES.f]]; + var updateOnLoadTemplate = [['img'], [TYPES.s, TYPES.a, TYPES.z]]; + var inheritedAttrsTemplate = [['style', 'class'], [TYPES.s, TYPES.a, TYPES.z]]; + var resizeAllowedValues = 'n:none b:both h:horizontal v:vertical'; + var overflowBehaviorAllowedValues = 'v-h:visible-hidden v-s:visible-scroll s:scroll h:hidden'; + var scrollbarsVisibilityAllowedValues = 'v:visible h:hidden a:auto'; + var scrollbarsAutoHideAllowedValues = 'n:never s:scroll l:leave m:move'; + var optionsDefaultsAndTemplate = { + className: ['os-theme-dark', classNameAllowedValues], //null || string + resize: ['none', resizeAllowedValues], //none || both || horizontal || vertical || n || b || h || v + sizeAutoCapable: booleanTrueTemplate, //true || false + clipAlways: booleanTrueTemplate, //true || false + normalizeRTL: booleanTrueTemplate, //true || false + paddingAbsolute: booleanFalseTemplate, //true || false + autoUpdate: [null, booleanNullAllowedValues], //true || false || null + autoUpdateInterval: [33, numberAllowedValues], //number + updateOnLoad: updateOnLoadTemplate, //string || array || null + nativeScrollbarsOverlaid: { + showNativeScrollbars: booleanFalseTemplate, //true || false + initialize: booleanTrueTemplate //true || false + }, + overflowBehavior: { + x: ['scroll', overflowBehaviorAllowedValues], //visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s + y: ['scroll', overflowBehaviorAllowedValues] //visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s + }, + scrollbars: { + visibility: ['auto', scrollbarsVisibilityAllowedValues], //visible || hidden || auto || v || h || a + autoHide: ['never', scrollbarsAutoHideAllowedValues], //never || scroll || leave || move || n || s || l || m + autoHideDelay: [800, numberAllowedValues], //number + dragScrolling: booleanTrueTemplate, //true || false + clickScrolling: booleanFalseTemplate, //true || false + touchSupport: booleanTrueTemplate, //true || false + snapHandle: booleanFalseTemplate //true || false + }, + textarea: { + dynWidth: booleanFalseTemplate, //true || false + dynHeight: booleanFalseTemplate, //true || false + inheritedAttrs: inheritedAttrsTemplate //string || array || null + }, + callbacks: { + onInitialized: callbackTemplate, //null || function + onInitializationWithdrawn: callbackTemplate, //null || function + onDestroyed: callbackTemplate, //null || function + onScrollStart: callbackTemplate, //null || function + onScroll: callbackTemplate, //null || function + onScrollStop: callbackTemplate, //null || function + onOverflowChanged: callbackTemplate, //null || function + onOverflowAmountChanged: callbackTemplate, //null || function + onDirectionChanged: callbackTemplate, //null || function + onContentSizeChanged: callbackTemplate, //null || function + onHostSizeChanged: callbackTemplate, //null || function + onUpdated: callbackTemplate //null || function + } + }; + var convert = function (template) { + var recursive = function (obj) { + var key; + var val; + var valType; + for (key in obj) { + if (!obj[LEXICON.hOP](key)) + continue; + val = obj[key]; + valType = type(val); + if (valType == TYPES.a) + obj[key] = val[template ? 1 : 0]; + else if (valType == TYPES.o) + obj[key] = recursive(val); + } + return obj; + }; + return recursive(FRAMEWORK.extend(true, {}, optionsDefaultsAndTemplate)); + }; + + return { + _defaults: convert(), + + _template: convert(true), + + /** + * Validates the passed object by the passed template. + * @param obj The object which shall be validated. + * @param template The template which defines the allowed values and types. + * @param writeErrors True if errors shall be logged to the console. + * @param diffObj If a object is passed then only valid differences to this object will be returned. + * @returns {{}} A object which contains two objects called "default" and "prepared" which contains only the valid properties of the passed original object and discards not different values compared to the passed diffObj. + */ + _validate: function (obj, template, writeErrors, diffObj) { + var validatedOptions = {}; + var validatedOptionsPrepared = {}; + var objectCopy = FRAMEWORK.extend(true, {}, obj); + var inArray = FRAMEWORK.inArray; + var isEmptyObj = FRAMEWORK.isEmptyObject; + var checkObjectProps = function (data, template, diffData, validatedOptions, validatedOptionsPrepared, prevPropName) { + for (var prop in template) { + if (template[LEXICON.hOP](prop) && data[LEXICON.hOP](prop)) { + var isValid = false; + var isDiff = false; + var templateValue = template[prop]; + var templateValueType = type(templateValue); + var templateIsComplex = templateValueType == TYPES.o; + var templateTypes = !COMPATIBILITY.isA(templateValue) ? [templateValue] : templateValue; + var dataDiffValue = diffData[prop]; + var dataValue = data[prop]; + var dataValueType = type(dataValue); + var propPrefix = prevPropName ? prevPropName + '.' : ''; + var error = "The option \"" + propPrefix + prop + "\" wasn't set, because"; + var errorPossibleTypes = []; + var errorRestrictedStrings = []; + var restrictedStringValuesSplit; + var restrictedStringValuesPossibilitiesSplit; + var isRestrictedValue; + var mainPossibility; + var currType; + var i; + var v; + var j; + + dataDiffValue = dataDiffValue === undefined ? {} : dataDiffValue; + + //if the template has a object as value, it means that the options are complex (verschachtelt) + if (templateIsComplex && dataValueType == TYPES.o) { + validatedOptions[prop] = {}; + validatedOptionsPrepared[prop] = {}; + checkObjectProps(dataValue, templateValue, dataDiffValue, validatedOptions[prop], validatedOptionsPrepared[prop], propPrefix + prop); + FRAMEWORK.each([data, validatedOptions, validatedOptionsPrepared], function (index, value) { + if (isEmptyObj(value[prop])) { + delete value[prop]; + } + }); + } + else if (!templateIsComplex) { + for (i = 0; i < templateTypes[LEXICON.l]; i++) { + currType = templateTypes[i]; + templateValueType = type(currType); + //if currtype is string and starts with restrictedStringPrefix and end with restrictedStringSuffix + isRestrictedValue = templateValueType == TYPES.s && inArray(currType, possibleTemplateTypes) === -1; + if (isRestrictedValue) { + errorPossibleTypes.push(TYPES.s); + + //split it into a array which contains all possible values for example: ["y:yes", "n:no", "m:maybe"] + restrictedStringValuesSplit = currType.split(restrictedStringsSplit); + errorRestrictedStrings = errorRestrictedStrings.concat(restrictedStringValuesSplit); + for (v = 0; v < restrictedStringValuesSplit[LEXICON.l]; v++) { + //split the possible values into their possibiliteis for example: ["y", "yes"] -> the first is always the mainPossibility + restrictedStringValuesPossibilitiesSplit = restrictedStringValuesSplit[v].split(restrictedStringsPossibilitiesSplit); + mainPossibility = restrictedStringValuesPossibilitiesSplit[0]; + for (j = 0; j < restrictedStringValuesPossibilitiesSplit[LEXICON.l]; j++) { + //if any possibility matches with the dataValue, its valid + if (dataValue === restrictedStringValuesPossibilitiesSplit[j]) { + isValid = true; + break; + } + } + if (isValid) + break; + } + } + else { + errorPossibleTypes.push(currType); + + if (dataValueType === currType) { + isValid = true; + break; + } + } + } + + if (isValid) { + isDiff = dataValue !== dataDiffValue; + + if (isDiff) + validatedOptions[prop] = dataValue; + + if (isRestrictedValue ? inArray(dataDiffValue, restrictedStringValuesPossibilitiesSplit) < 0 : isDiff) + validatedOptionsPrepared[prop] = isRestrictedValue ? mainPossibility : dataValue; + } + else if (writeErrors) { + console.warn(error + " it doesn't accept the type [ " + dataValueType.toUpperCase() + " ] with the value of \"" + dataValue + "\".\r\n" + + "Accepted types are: [ " + errorPossibleTypes.join(', ').toUpperCase() + " ]." + + (errorRestrictedStrings[length] > 0 ? "\r\nValid strings are: [ " + errorRestrictedStrings.join(', ').split(restrictedStringsPossibilitiesSplit).join(', ') + " ]." : '')); + } + delete data[prop]; + } + } + } + }; + checkObjectProps(objectCopy, template, diffObj || {}, validatedOptions, validatedOptionsPrepared); + + //add values which aren't specified in the template to the finished validated object to prevent them from being discarded + /* + if(keepForeignProps) { + FRAMEWORK.extend(true, validatedOptions, objectCopy); + FRAMEWORK.extend(true, validatedOptionsPrepared, objectCopy); + } + */ + + if (!isEmptyObj(objectCopy) && writeErrors) + console.warn('The following options are discarded due to invalidity:\r\n' + window.JSON.stringify(objectCopy, null, 2)); + + return { + _default: validatedOptions, + _prepared: validatedOptionsPrepared + }; + } + } + }()); + + /** + * Initializes the object which contains global information about the plugin and each instance of it. + */ + function initOverlayScrollbarsStatics() { + if (!_pluginsGlobals) + _pluginsGlobals = new OverlayScrollbarsGlobals(_pluginsOptions._defaults); + if (!_pluginsAutoUpdateLoop) + _pluginsAutoUpdateLoop = new OverlayScrollbarsAutoUpdateLoop(_pluginsGlobals); + } + + /** + * The global object for the OverlayScrollbars objects. It contains resources which every OverlayScrollbars object needs. This object is initialized only once: if the first OverlayScrollbars object gets initialized. + * @param defaultOptions + * @constructor + */ + function OverlayScrollbarsGlobals(defaultOptions) { + var _base = this; + var strOverflow = 'overflow'; + var strHidden = 'hidden'; + var strScroll = 'scroll'; + var bodyElement = FRAMEWORK('body'); + var scrollbarDummyElement = FRAMEWORK('
'); + var scrollbarDummyElement0 = scrollbarDummyElement[0]; + var dummyContainerChild = FRAMEWORK(scrollbarDummyElement.children('div').eq(0)); + + bodyElement.append(scrollbarDummyElement); + scrollbarDummyElement.hide().show(); //fix IE8 bug (incorrect measuring) + + var nativeScrollbarSize = calcNativeScrollbarSize(scrollbarDummyElement0); + var nativeScrollbarIsOverlaid = { + x: nativeScrollbarSize.x === 0, + y: nativeScrollbarSize.y === 0 + }; + var msie = (function () { + var ua = window.navigator.userAgent; + var strIndexOf = 'indexOf'; + var strSubString = 'substring'; + var msie = ua[strIndexOf]('MSIE '); + var trident = ua[strIndexOf]('Trident/'); + var edge = ua[strIndexOf]('Edge/'); + var rv = ua[strIndexOf]('rv:'); + var result; + var parseIntFunc = parseInt; + + // IE 10 or older => return version number + if (msie > 0) + result = parseIntFunc(ua[strSubString](msie + 5, ua[strIndexOf]('.', msie)), 10); + + // IE 11 => return version number + else if (trident > 0) + result = parseIntFunc(ua[strSubString](rv + 3, ua[strIndexOf]('.', rv)), 10); + + // Edge (IE 12+) => return version number + else if (edge > 0) + result = parseIntFunc(ua[strSubString](edge + 5, ua[strIndexOf]('.', edge)), 10); + + // other browser + return result; + })(); + + FRAMEWORK.extend(_base, { + defaultOptions: defaultOptions, + msie: msie, + autoUpdateLoop: false, + autoUpdateRecommended: !COMPATIBILITY.mO(), + nativeScrollbarSize: nativeScrollbarSize, + nativeScrollbarIsOverlaid: nativeScrollbarIsOverlaid, + nativeScrollbarStyling: (function () { + var result = false; + scrollbarDummyElement.addClass('os-viewport-native-scrollbars-invisible'); + try { + result = (scrollbarDummyElement.css('scrollbar-width') === 'none' && (msie > 9 || !msie)) || window.getComputedStyle(scrollbarDummyElement0, '::-webkit-scrollbar').getPropertyValue('display') === 'none'; + } catch (ex) { } + + //fix opera bug: scrollbar styles will only appear if overflow value is scroll or auto during the activation of the style. + //and set overflow to scroll + //scrollbarDummyElement.css(strOverflow, strHidden).hide().css(strOverflow, strScroll).show(); + //return (scrollbarDummyElement0[LEXICON.oH] - scrollbarDummyElement0[LEXICON.cH]) === 0 && (scrollbarDummyElement0[LEXICON.oW] - scrollbarDummyElement0[LEXICON.cW]) === 0; + + return result; + })(), + overlayScrollbarDummySize: { x: 30, y: 30 }, + cssCalc: VENDORS._cssPropertyValue('width', 'calc', '(1px)') || null, + restrictedMeasuring: (function () { + //https://bugzilla.mozilla.org/show_bug.cgi?id=1439305 + //since 1.11.0 always false -> fixed via CSS (hopefully) + scrollbarDummyElement.css(strOverflow, strHidden); + var scrollSize = { + w: scrollbarDummyElement0[LEXICON.sW], + h: scrollbarDummyElement0[LEXICON.sH] + }; + scrollbarDummyElement.css(strOverflow, 'visible'); + var scrollSize2 = { + w: scrollbarDummyElement0[LEXICON.sW], + h: scrollbarDummyElement0[LEXICON.sH] + }; + return (scrollSize.w - scrollSize2.w) !== 0 || (scrollSize.h - scrollSize2.h) !== 0; + })(), + rtlScrollBehavior: (function () { + scrollbarDummyElement.css({ 'overflow-y': strHidden, 'overflow-x': strScroll, 'direction': 'rtl' }).scrollLeft(0); + var dummyContainerOffset = scrollbarDummyElement.offset(); + var dummyContainerChildOffset = dummyContainerChild.offset(); + //https://github.com/KingSora/OverlayScrollbars/issues/187 + scrollbarDummyElement.scrollLeft(-999); + var dummyContainerChildOffsetAfterScroll = dummyContainerChild.offset(); + return { + //origin direction = determines if the zero scroll position is on the left or right side + //'i' means 'invert' (i === true means that the axis must be inverted to be correct) + //true = on the left side + //false = on the right side + i: dummyContainerOffset.left === dummyContainerChildOffset.left, + //negative = determines if the maximum scroll is positive or negative + //'n' means 'negate' (n === true means that the axis must be negated to be correct) + //true = negative + //false = positive + n: dummyContainerChildOffset.left !== dummyContainerChildOffsetAfterScroll.left + }; + })(), + supportTransform: !!VENDORS._cssProperty('transform'), + supportTransition: !!VENDORS._cssProperty('transition'), + supportPassiveEvents: (function () { + var supportsPassive = false; + try { + window.addEventListener('test', null, Object.defineProperty({}, 'passive', { + get: function () { + supportsPassive = true; + } + })); + } catch (e) { } + return supportsPassive; + })(), + supportResizeObserver: !!COMPATIBILITY.rO(), + supportMutationObserver: !!COMPATIBILITY.mO() + }); + + scrollbarDummyElement.removeAttr(LEXICON.s).remove(); + + //Catch zoom event: + (function () { + if (nativeScrollbarIsOverlaid.x && nativeScrollbarIsOverlaid.y) + return; + + var abs = MATH.abs; + var windowWidth = COMPATIBILITY.wW(); + var windowHeight = COMPATIBILITY.wH(); + var windowDpr = getWindowDPR(); + var onResize = function () { + if (INSTANCES().length > 0) { + var newW = COMPATIBILITY.wW(); + var newH = COMPATIBILITY.wH(); + var deltaW = newW - windowWidth; + var deltaH = newH - windowHeight; + + if (deltaW === 0 && deltaH === 0) + return; + + var deltaWRatio = MATH.round(newW / (windowWidth / 100.0)); + var deltaHRatio = MATH.round(newH / (windowHeight / 100.0)); + var absDeltaW = abs(deltaW); + var absDeltaH = abs(deltaH); + var absDeltaWRatio = abs(deltaWRatio); + var absDeltaHRatio = abs(deltaHRatio); + var newDPR = getWindowDPR(); + + var deltaIsBigger = absDeltaW > 2 && absDeltaH > 2; + var difference = !differenceIsBiggerThanOne(absDeltaWRatio, absDeltaHRatio); + var dprChanged = newDPR !== windowDpr && windowDpr > 0; + var isZoom = deltaIsBigger && difference && dprChanged; + var oldScrollbarSize = _base.nativeScrollbarSize; + var newScrollbarSize; + + if (isZoom) { + bodyElement.append(scrollbarDummyElement); + newScrollbarSize = _base.nativeScrollbarSize = calcNativeScrollbarSize(scrollbarDummyElement[0]); + scrollbarDummyElement.remove(); + if (oldScrollbarSize.x !== newScrollbarSize.x || oldScrollbarSize.y !== newScrollbarSize.y) { + FRAMEWORK.each(INSTANCES(), function () { + if (INSTANCES(this)) + INSTANCES(this).update('zoom'); + }); + } + } + + windowWidth = newW; + windowHeight = newH; + windowDpr = newDPR; + } + }; + + function differenceIsBiggerThanOne(valOne, valTwo) { + var absValOne = abs(valOne); + var absValTwo = abs(valTwo); + return !(absValOne === absValTwo || absValOne + 1 === absValTwo || absValOne - 1 === absValTwo); + } + + function getWindowDPR() { + var dDPI = window.screen.deviceXDPI || 0; + var sDPI = window.screen.logicalXDPI || 1; + return window.devicePixelRatio || (dDPI / sDPI); + } + + FRAMEWORK(window).on('resize', onResize); + })(); + + function calcNativeScrollbarSize(measureElement) { + return { + x: measureElement[LEXICON.oH] - measureElement[LEXICON.cH], + y: measureElement[LEXICON.oW] - measureElement[LEXICON.cW] + }; + } + } + + /** + * The object which manages the auto update loop for all OverlayScrollbars objects. This object is initialized only once: if the first OverlayScrollbars object gets initialized. + * @constructor + */ + function OverlayScrollbarsAutoUpdateLoop(globals) { + var _base = this; + var _inArray = FRAMEWORK.inArray; + var _getNow = COMPATIBILITY.now; + var _strAutoUpdate = 'autoUpdate'; + var _strAutoUpdateInterval = _strAutoUpdate + 'Interval'; + var _strLength = LEXICON.l; + var _loopingInstances = []; + var _loopingInstancesIntervalCache = []; + var _loopIsActive = false; + var _loopIntervalDefault = 33; + var _loopInterval = _loopIntervalDefault; + var _loopTimeOld = _getNow(); + var _loopID; + + + /** + * The auto update loop which will run every 50 milliseconds or less if the update interval of a instance is lower than 50 milliseconds. + */ + var loop = function () { + if (_loopingInstances[_strLength] > 0 && _loopIsActive) { + _loopID = COMPATIBILITY.rAF()(function () { + loop(); + }); + var timeNew = _getNow(); + var timeDelta = timeNew - _loopTimeOld; + var lowestInterval; + var instance; + var instanceOptions; + var instanceAutoUpdateAllowed; + var instanceAutoUpdateInterval; + var now; + + if (timeDelta > _loopInterval) { + _loopTimeOld = timeNew - (timeDelta % _loopInterval); + lowestInterval = _loopIntervalDefault; + for (var i = 0; i < _loopingInstances[_strLength]; i++) { + instance = _loopingInstances[i]; + if (instance !== undefined) { + instanceOptions = instance.options(); + instanceAutoUpdateAllowed = instanceOptions[_strAutoUpdate]; + instanceAutoUpdateInterval = MATH.max(1, instanceOptions[_strAutoUpdateInterval]); + now = _getNow(); + + if ((instanceAutoUpdateAllowed === true || instanceAutoUpdateAllowed === null) && (now - _loopingInstancesIntervalCache[i]) > instanceAutoUpdateInterval) { + instance.update('auto'); + _loopingInstancesIntervalCache[i] = new Date(now += instanceAutoUpdateInterval); + } + + lowestInterval = MATH.max(1, MATH.min(lowestInterval, instanceAutoUpdateInterval)); + } + } + _loopInterval = lowestInterval; + } + } else { + _loopInterval = _loopIntervalDefault; + } + }; + + /** + * Add OverlayScrollbars instance to the auto update loop. Only successful if the instance isn't already added. + * @param instance The instance which shall be updated in a loop automatically. + */ + _base.add = function (instance) { + if (_inArray(instance, _loopingInstances) === -1) { + _loopingInstances.push(instance); + _loopingInstancesIntervalCache.push(_getNow()); + if (_loopingInstances[_strLength] > 0 && !_loopIsActive) { + _loopIsActive = true; + globals.autoUpdateLoop = _loopIsActive; + loop(); + } + } + }; + + /** + * Remove OverlayScrollbars instance from the auto update loop. Only successful if the instance was added before. + * @param instance The instance which shall be updated in a loop automatically. + */ + _base.remove = function (instance) { + var index = _inArray(instance, _loopingInstances); + if (index > -1) { + //remove from loopingInstances list + _loopingInstancesIntervalCache.splice(index, 1); + _loopingInstances.splice(index, 1); + + //correct update loop behavior + if (_loopingInstances[_strLength] === 0 && _loopIsActive) { + _loopIsActive = false; + globals.autoUpdateLoop = _loopIsActive; + if (_loopID !== undefined) { + COMPATIBILITY.cAF()(_loopID); + _loopID = -1; + } + } + } + }; + } + + /** + * A object which manages the scrollbars visibility of the target element. + * @param pluginTargetElement The element from which the scrollbars shall be hidden. + * @param options The custom options. + * @param extensions The custom extensions. + * @param globals + * @param autoUpdateLoop + * @returns {*} + * @constructor + */ + function OverlayScrollbarsInstance(pluginTargetElement, options, extensions, globals, autoUpdateLoop) { + //shortcuts + var type = COMPATIBILITY.type; + var inArray = FRAMEWORK.inArray; + var each = FRAMEWORK.each; + + //make correct instanceof + var _base = new _plugin(); + var _frameworkProto = FRAMEWORK[LEXICON.p]; + + //if passed element is no HTML element: skip and return + if (!isHTMLElement(pluginTargetElement)) + return; + + //if passed element is already initialized: set passed options if there are any and return its instance + if (INSTANCES(pluginTargetElement)) { + var inst = INSTANCES(pluginTargetElement); + inst.options(options); + return inst; + } + + //globals: + var _nativeScrollbarIsOverlaid; + var _overlayScrollbarDummySize; + var _rtlScrollBehavior; + var _autoUpdateRecommended; + var _msieVersion; + var _nativeScrollbarStyling; + var _cssCalc; + var _nativeScrollbarSize; + var _supportTransition; + var _supportTransform; + var _supportPassiveEvents; + var _supportResizeObserver; + var _supportMutationObserver; + var _restrictedMeasuring; + + //general readonly: + var _initialized; + var _destroyed; + var _isTextarea; + var _isBody; + var _documentMixed; + var _domExists; + + //general: + var _isBorderBox; + var _sizeAutoObserverAdded; + var _paddingX; + var _paddingY; + var _borderX; + var _borderY; + var _marginX; + var _marginY; + var _isRTL; + var _sleeping; + var _contentBorderSize = {}; + var _scrollHorizontalInfo = {}; + var _scrollVerticalInfo = {}; + var _viewportSize = {}; + var _nativeScrollbarMinSize = {}; + + //naming: + var _strMinusHidden = '-hidden'; + var _strMarginMinus = 'margin-'; + var _strPaddingMinus = 'padding-'; + var _strBorderMinus = 'border-'; + var _strTop = 'top'; + var _strRight = 'right'; + var _strBottom = 'bottom'; + var _strLeft = 'left'; + var _strMinMinus = 'min-'; + var _strMaxMinus = 'max-'; + var _strWidth = 'width'; + var _strHeight = 'height'; + var _strFloat = 'float'; + var _strEmpty = ''; + var _strAuto = 'auto'; + var _strSync = 'sync'; + var _strScroll = 'scroll'; + var _strHundredPercent = '100%'; + var _strX = 'x'; + var _strY = 'y'; + var _strDot = '.'; + var _strSpace = ' '; + var _strScrollbar = 'scrollbar'; + var _strMinusHorizontal = '-horizontal'; + var _strMinusVertical = '-vertical'; + var _strScrollLeft = _strScroll + 'Left'; + var _strScrollTop = _strScroll + 'Top'; + var _strMouseTouchDownEvent = 'mousedown touchstart'; + var _strMouseTouchUpEvent = 'mouseup touchend touchcancel'; + var _strMouseTouchMoveEvent = 'mousemove touchmove'; + var _strMouseEnter = 'mouseenter'; + var _strMouseLeave = 'mouseleave'; + var _strKeyDownEvent = 'keydown'; + var _strKeyUpEvent = 'keyup'; + var _strSelectStartEvent = 'selectstart'; + var _strTransitionEndEvent = 'transitionend webkitTransitionEnd oTransitionEnd'; + var _strResizeObserverProperty = '__overlayScrollbarsRO__'; + + //class names: + var _cassNamesPrefix = 'os-'; + var _classNameHTMLElement = _cassNamesPrefix + 'html'; + var _classNameHostElement = _cassNamesPrefix + 'host'; + var _classNameHostElementForeign = _classNameHostElement + '-foreign'; + var _classNameHostTextareaElement = _classNameHostElement + '-textarea'; + var _classNameHostScrollbarHorizontalHidden = _classNameHostElement + '-' + _strScrollbar + _strMinusHorizontal + _strMinusHidden; + var _classNameHostScrollbarVerticalHidden = _classNameHostElement + '-' + _strScrollbar + _strMinusVertical + _strMinusHidden; + var _classNameHostTransition = _classNameHostElement + '-transition'; + var _classNameHostRTL = _classNameHostElement + '-rtl'; + var _classNameHostResizeDisabled = _classNameHostElement + '-resize-disabled'; + var _classNameHostScrolling = _classNameHostElement + '-scrolling'; + var _classNameHostOverflow = _classNameHostElement + '-overflow'; + var _classNameHostOverflow = _classNameHostElement + '-overflow'; + var _classNameHostOverflowX = _classNameHostOverflow + '-x'; + var _classNameHostOverflowY = _classNameHostOverflow + '-y'; + var _classNameTextareaElement = _cassNamesPrefix + 'textarea'; + var _classNameTextareaCoverElement = _classNameTextareaElement + '-cover'; + var _classNamePaddingElement = _cassNamesPrefix + 'padding'; + var _classNameViewportElement = _cassNamesPrefix + 'viewport'; + var _classNameViewportNativeScrollbarsInvisible = _classNameViewportElement + '-native-scrollbars-invisible'; + var _classNameViewportNativeScrollbarsOverlaid = _classNameViewportElement + '-native-scrollbars-overlaid'; + var _classNameContentElement = _cassNamesPrefix + 'content'; + var _classNameContentArrangeElement = _cassNamesPrefix + 'content-arrange'; + var _classNameContentGlueElement = _cassNamesPrefix + 'content-glue'; + var _classNameSizeAutoObserverElement = _cassNamesPrefix + 'size-auto-observer'; + var _classNameResizeObserverElement = _cassNamesPrefix + 'resize-observer'; + var _classNameResizeObserverItemElement = _cassNamesPrefix + 'resize-observer-item'; + var _classNameResizeObserverItemFinalElement = _classNameResizeObserverItemElement + '-final'; + var _classNameTextInherit = _cassNamesPrefix + 'text-inherit'; + var _classNameScrollbar = _cassNamesPrefix + _strScrollbar; + var _classNameScrollbarTrack = _classNameScrollbar + '-track'; + var _classNameScrollbarTrackOff = _classNameScrollbarTrack + '-off'; + var _classNameScrollbarHandle = _classNameScrollbar + '-handle'; + var _classNameScrollbarHandleOff = _classNameScrollbarHandle + '-off'; + var _classNameScrollbarUnusable = _classNameScrollbar + '-unusable'; + var _classNameScrollbarAutoHidden = _classNameScrollbar + '-' + _strAuto + _strMinusHidden; + var _classNameScrollbarCorner = _classNameScrollbar + '-corner'; + var _classNameScrollbarCornerResize = _classNameScrollbarCorner + '-resize'; + var _classNameScrollbarCornerResizeB = _classNameScrollbarCornerResize + '-both'; + var _classNameScrollbarCornerResizeH = _classNameScrollbarCornerResize + _strMinusHorizontal; + var _classNameScrollbarCornerResizeV = _classNameScrollbarCornerResize + _strMinusVertical; + var _classNameScrollbarHorizontal = _classNameScrollbar + _strMinusHorizontal; + var _classNameScrollbarVertical = _classNameScrollbar + _strMinusVertical; + var _classNameDragging = _cassNamesPrefix + 'dragging'; + var _classNameThemeNone = _cassNamesPrefix + 'theme-none'; + var _classNamesDynamicDestroy = [ + _classNameViewportNativeScrollbarsInvisible, + _classNameViewportNativeScrollbarsOverlaid, + _classNameScrollbarTrackOff, + _classNameScrollbarHandleOff, + _classNameScrollbarUnusable, + _classNameScrollbarAutoHidden, + _classNameScrollbarCornerResize, + _classNameScrollbarCornerResizeB, + _classNameScrollbarCornerResizeH, + _classNameScrollbarCornerResizeV, + _classNameDragging].join(_strSpace); + + //callbacks: + var _callbacksInitQeueue = []; + + //attrs viewport shall inherit from target + var _viewportAttrsFromTarget = [LEXICON.ti]; + + //options: + var _defaultOptions; + var _currentOptions; + var _currentPreparedOptions; + + //extensions: + var _extensions = {}; + var _extensionsPrivateMethods = 'added removed on contract'; + + //update + var _lastUpdateTime; + var _swallowedUpdateHints = {}; + var _swallowedUpdateTimeout; + var _swallowUpdateLag = 42; + var _updateOnLoadEventName = 'load'; + var _updateOnLoadElms = []; + + //DOM elements: + var _windowElement; + var _documentElement; + var _htmlElement; + var _bodyElement; + var _targetElement; //the target element of this OverlayScrollbars object + var _hostElement; //the host element of this OverlayScrollbars object -> may be the same as targetElement + var _sizeAutoObserverElement; //observes size auto changes + var _sizeObserverElement; //observes size and padding changes + var _paddingElement; //manages the padding + var _viewportElement; //is the viewport of our scrollbar model + var _contentElement; //the element which holds the content + var _contentArrangeElement; //is needed for correct sizing of the content element (only if native scrollbars are overlays) + var _contentGlueElement; //has always the size of the content element + var _textareaCoverElement; //only applied if target is a textarea element. Used for correct size calculation and for prevention of uncontrolled scrolling + var _scrollbarCornerElement; + var _scrollbarHorizontalElement; + var _scrollbarHorizontalTrackElement; + var _scrollbarHorizontalHandleElement; + var _scrollbarVerticalElement; + var _scrollbarVerticalTrackElement; + var _scrollbarVerticalHandleElement; + var _windowElementNative; + var _documentElementNative; + var _targetElementNative; + var _hostElementNative; + var _sizeAutoObserverElementNative; + var _sizeObserverElementNative; + var _paddingElementNative; + var _viewportElementNative; + var _contentElementNative; + + //Cache: + var _hostSizeCache; + var _contentScrollSizeCache; + var _arrangeContentSizeCache; + var _hasOverflowCache; + var _hideOverflowCache; + var _widthAutoCache; + var _heightAutoCache; + var _cssBoxSizingCache; + var _cssPaddingCache; + var _cssBorderCache; + var _cssMarginCache; + var _cssDirectionCache; + var _cssDirectionDetectedCache; + var _paddingAbsoluteCache; + var _clipAlwaysCache; + var _contentGlueSizeCache; + var _overflowBehaviorCache; + var _overflowAmountCache; + var _ignoreOverlayScrollbarHidingCache; + var _autoUpdateCache; + var _sizeAutoCapableCache; + var _contentElementScrollSizeChangeDetectedCache; + var _hostElementSizeChangeDetectedCache; + var _scrollbarsVisibilityCache; + var _scrollbarsAutoHideCache; + var _scrollbarsClickScrollingCache; + var _scrollbarsDragScrollingCache; + var _resizeCache; + var _normalizeRTLCache; + var _classNameCache; + var _oldClassName; + var _textareaAutoWrappingCache; + var _textareaInfoCache; + var _textareaSizeCache; + var _textareaDynHeightCache; + var _textareaDynWidthCache; + var _bodyMinSizeCache; + var _updateAutoCache = {}; + + //MutationObserver: + var _mutationObserverHost; + var _mutationObserverContent; + var _mutationObserverHostCallback; + var _mutationObserverContentCallback; + var _mutationObserversConnected; + var _mutationObserverAttrsTextarea = ['wrap', 'cols', 'rows']; + var _mutationObserverAttrsHost = [LEXICON.i, LEXICON.c, LEXICON.s, 'open'].concat(_viewportAttrsFromTarget); + + //events: + var _destroyEvents = []; + + //textarea: + var _textareaHasFocus; + + //scrollbars: + var _scrollbarsAutoHideTimeoutId; + var _scrollbarsAutoHideMoveTimeoutId; + var _scrollbarsAutoHideDelay; + var _scrollbarsAutoHideNever; + var _scrollbarsAutoHideScroll; + var _scrollbarsAutoHideMove; + var _scrollbarsAutoHideLeave; + var _scrollbarsHandleHovered; + var _scrollbarsHandlesDefineScrollPos; + + //resize + var _resizeNone; + var _resizeBoth; + var _resizeHorizontal; + var _resizeVertical; + + + //==== Event Listener ====// + + /** + * Adds or removes a event listener from the given element. + * @param element The element to which the event listener shall be applied or removed. + * @param eventNames The name(s) of the events. + * @param listener The method which shall be called. + * @param remove True if the handler shall be removed, false or undefined if the handler shall be added. + * @param passiveOrOptions The options for the event. + */ + function setupResponsiveEventListener(element, eventNames, listener, remove, passiveOrOptions) { + var collected = COMPATIBILITY.isA(eventNames) && COMPATIBILITY.isA(listener); + var method = remove ? 'removeEventListener' : 'addEventListener'; + var onOff = remove ? 'off' : 'on'; + var events = collected ? false : eventNames.split(_strSpace) + var i = 0; + + var passiveOrOptionsIsObj = FRAMEWORK.isPlainObject(passiveOrOptions); + var passive = (_supportPassiveEvents && (passiveOrOptionsIsObj ? (passiveOrOptions._passive) : passiveOrOptions)) || false; + var capture = passiveOrOptionsIsObj && (passiveOrOptions._capture || false); + var nativeParam = _supportPassiveEvents ? { + passive: passive, + capture: capture, + } : capture; + + if (collected) { + for (; i < eventNames[LEXICON.l]; i++) + setupResponsiveEventListener(element, eventNames[i], listener[i], remove, passiveOrOptions); + } + else { + for (; i < events[LEXICON.l]; i++) { + if(_supportPassiveEvents) { + element[0][method](events[i], listener, nativeParam); + } + else { + element[onOff](events[i], listener); + } + } + } + } + + + function addDestroyEventListener(element, eventNames, listener, passive) { + setupResponsiveEventListener(element, eventNames, listener, false, passive); + _destroyEvents.push(COMPATIBILITY.bind(setupResponsiveEventListener, 0, element, eventNames, listener, true, passive)); + } + + //==== Resize Observer ====// + + /** + * Adds or removes a resize observer from the given element. + * @param targetElement The element to which the resize observer shall be added or removed. + * @param onElementResizedCallback The callback which is fired every time the resize observer registers a size change or false / undefined if the resizeObserver shall be removed. + */ + function setupResizeObserver(targetElement, onElementResizedCallback) { + if (targetElement) { + var resizeObserver = COMPATIBILITY.rO(); + var strAnimationStartEvent = 'animationstart mozAnimationStart webkitAnimationStart MSAnimationStart'; + var strChildNodes = 'childNodes'; + var constScroll = 3333333; + var callback = function () { + targetElement[_strScrollTop](constScroll)[_strScrollLeft](_isRTL ? _rtlScrollBehavior.n ? -constScroll : _rtlScrollBehavior.i ? 0 : constScroll : constScroll); + onElementResizedCallback(); + }; + //add resize observer: + if (onElementResizedCallback) { + if (_supportResizeObserver) { + var element = targetElement.addClass('observed').append(generateDiv(_classNameResizeObserverElement)).contents()[0]; + var observer = element[_strResizeObserverProperty] = new resizeObserver(callback); + observer.observe(element); + } + else { + if (_msieVersion > 9 || !_autoUpdateRecommended) { + targetElement.prepend( + generateDiv(_classNameResizeObserverElement, + generateDiv({ c: _classNameResizeObserverItemElement, dir: 'ltr' }, + generateDiv(_classNameResizeObserverItemElement, + generateDiv(_classNameResizeObserverItemFinalElement) + ) + + generateDiv(_classNameResizeObserverItemElement, + generateDiv({ c: _classNameResizeObserverItemFinalElement, style: 'width: 200%; height: 200%' }) + ) + ) + ) + ); + + var observerElement = targetElement[0][strChildNodes][0][strChildNodes][0]; + var shrinkElement = FRAMEWORK(observerElement[strChildNodes][1]); + var expandElement = FRAMEWORK(observerElement[strChildNodes][0]); + var expandElementChild = FRAMEWORK(expandElement[0][strChildNodes][0]); + var widthCache = observerElement[LEXICON.oW]; + var heightCache = observerElement[LEXICON.oH]; + var isDirty; + var rAFId; + var currWidth; + var currHeight; + var factor = 2; + var nativeScrollbarSize = globals.nativeScrollbarSize; //care don't make changes to this object!!! + var reset = function () { + /* + var sizeResetWidth = observerElement[LEXICON.oW] + nativeScrollbarSize.x * factor + nativeScrollbarSize.y * factor + _overlayScrollbarDummySize.x + _overlayScrollbarDummySize.y; + var sizeResetHeight = observerElement[LEXICON.oH] + nativeScrollbarSize.x * factor + nativeScrollbarSize.y * factor + _overlayScrollbarDummySize.x + _overlayScrollbarDummySize.y; + var expandChildCSS = {}; + expandChildCSS[_strWidth] = sizeResetWidth; + expandChildCSS[_strHeight] = sizeResetHeight; + expandElementChild.css(expandChildCSS); + + + expandElement[_strScrollLeft](sizeResetWidth)[_strScrollTop](sizeResetHeight); + shrinkElement[_strScrollLeft](sizeResetWidth)[_strScrollTop](sizeResetHeight); + */ + expandElement[_strScrollLeft](constScroll)[_strScrollTop](constScroll); + shrinkElement[_strScrollLeft](constScroll)[_strScrollTop](constScroll); + }; + var onResized = function () { + rAFId = 0; + if (!isDirty) + return; + + widthCache = currWidth; + heightCache = currHeight; + callback(); + }; + var onScroll = function (event) { + currWidth = observerElement[LEXICON.oW]; + currHeight = observerElement[LEXICON.oH]; + isDirty = currWidth != widthCache || currHeight != heightCache; + + if (event && isDirty && !rAFId) { + COMPATIBILITY.cAF()(rAFId); + rAFId = COMPATIBILITY.rAF()(onResized); + } + else if (!event) + onResized(); + + reset(); + if (event) { + COMPATIBILITY.prvD(event); + COMPATIBILITY.stpP(event); + } + return false; + }; + var expandChildCSS = {}; + var observerElementCSS = {}; + + setTopRightBottomLeft(observerElementCSS, _strEmpty, [ + -((nativeScrollbarSize.y + 1) * factor), + nativeScrollbarSize.x * -factor, + nativeScrollbarSize.y * -factor, + -((nativeScrollbarSize.x + 1) * factor) + ]); + + FRAMEWORK(observerElement).css(observerElementCSS); + expandElement.on(_strScroll, onScroll); + shrinkElement.on(_strScroll, onScroll); + targetElement.on(strAnimationStartEvent, function () { + onScroll(false); + }); + //lets assume that the divs will never be that large and a constant value is enough + expandChildCSS[_strWidth] = constScroll; + expandChildCSS[_strHeight] = constScroll; + expandElementChild.css(expandChildCSS); + + reset(); + } + else { + var attachEvent = _documentElementNative.attachEvent; + var isIE = _msieVersion !== undefined; + if (attachEvent) { + targetElement.prepend(generateDiv(_classNameResizeObserverElement)); + findFirst(targetElement, _strDot + _classNameResizeObserverElement)[0].attachEvent('onresize', callback); + } + else { + var obj = _documentElementNative.createElement(TYPES.o); + obj.setAttribute(LEXICON.ti, '-1'); + obj.setAttribute(LEXICON.c, _classNameResizeObserverElement); + obj.onload = function () { + var wnd = this.contentDocument.defaultView; + wnd.addEventListener('resize', callback); + wnd.document.documentElement.style.display = 'none'; + }; + obj.type = 'text/html'; + if (isIE) + targetElement.prepend(obj); + obj.data = 'about:blank'; + if (!isIE) + targetElement.prepend(obj); + targetElement.on(strAnimationStartEvent, callback); + } + } + } + + if (targetElement[0] === _sizeObserverElementNative) { + var directionChanged = function () { + var dir = _hostElement.css('direction'); + var css = {}; + var scrollLeftValue = 0; + var result = false; + if (dir !== _cssDirectionDetectedCache) { + if (dir === 'ltr') { + css[_strLeft] = 0; + css[_strRight] = _strAuto; + scrollLeftValue = constScroll; + } + else { + css[_strLeft] = _strAuto; + css[_strRight] = 0; + scrollLeftValue = _rtlScrollBehavior.n ? -constScroll : _rtlScrollBehavior.i ? 0 : constScroll; + } + //execution order is important for IE!!! + _sizeObserverElement.children().eq(0).css(css); + _sizeObserverElement[_strScrollLeft](scrollLeftValue)[_strScrollTop](constScroll); + _cssDirectionDetectedCache = dir; + result = true; + } + return result; + }; + directionChanged(); + addDestroyEventListener(targetElement, _strScroll, function (event) { + if (directionChanged()) + update(); + COMPATIBILITY.prvD(event); + COMPATIBILITY.stpP(event); + return false; + }); + } + } + //remove resize observer: + else { + if (_supportResizeObserver) { + var element = targetElement.contents()[0]; + var resizeObserverObj = element[_strResizeObserverProperty]; + if (resizeObserverObj) { + resizeObserverObj.disconnect(); + delete element[_strResizeObserverProperty]; + } + } + else { + remove(targetElement.children(_strDot + _classNameResizeObserverElement).eq(0)); + } + } + } + } + + /** + * Freezes or unfreezes the given resize observer. + * @param targetElement The element to which the target resize observer is applied. + * @param freeze True if the resize observer shall be frozen, false otherwise. + + function freezeResizeObserver(targetElement, freeze) { + if (targetElement !== undefined) { + if(freeze) { + if (_supportResizeObserver) { + var element = targetElement.contents()[0]; + element[_strResizeObserverProperty].unobserve(element); + } + else { + targetElement = targetElement.children(_strDot + _classNameResizeObserverElement).eq(0); + var w = targetElement.css(_strWidth); + var h = targetElement.css(_strHeight); + var css = {}; + css[_strWidth] = w; + css[_strHeight] = h; + targetElement.css(css); + } + } + else { + if (_supportResizeObserver) { + var element = targetElement.contents()[0]; + element[_strResizeObserverProperty].observe(element); + } + else { + var css = { }; + css[_strHeight] = _strEmpty; + css[_strWidth] = _strEmpty; + targetElement.children(_strDot + _classNameResizeObserverElement).eq(0).css(css); + } + } + } + } + */ + + + //==== Mutation Observers ====// + + /** + * Creates MutationObservers for the host and content Element if they are supported. + */ + function createMutationObservers() { + if (_supportMutationObserver) { + var mutationObserverContentLag = 11; + var mutationObserver = COMPATIBILITY.mO(); + var contentLastUpdate = COMPATIBILITY.now(); + var mutationTarget; + var mutationAttrName; + var mutationIsClass; + var oldMutationVal; + var newClassVal; + var hostClassNameRegex; + var contentTimeout; + var now; + var sizeAuto; + var action; + + _mutationObserverHostCallback = function (mutations) { + + var doUpdate = false; + var doUpdateForce = false; + var mutation; + var mutatedAttrs = []; + + if (_initialized && !_sleeping) { + each(mutations, function () { + mutation = this; + mutationTarget = mutation.target; + mutationAttrName = mutation.attributeName; + mutationIsClass = mutationAttrName === LEXICON.c; + oldMutationVal = mutation.oldValue; + newClassVal = mutationTarget.className; + + if (_domExists && mutationIsClass && !doUpdateForce) { + // if old class value contains _classNameHostElementForeign and new class value doesn't + if (oldMutationVal.indexOf(_classNameHostElementForeign) > -1 && newClassVal.indexOf(_classNameHostElementForeign) < 0) { + hostClassNameRegex = createHostClassNameRegExp(true); + _hostElementNative.className = newClassVal.split(_strSpace).concat(oldMutationVal.split(_strSpace).filter(function (name) { + return name.match(hostClassNameRegex); + })).join(_strSpace); + doUpdate = doUpdateForce = true; + } + } + + if (!doUpdate) { + doUpdate = mutationIsClass + ? hostClassNamesChanged(oldMutationVal, newClassVal) + : mutationAttrName === LEXICON.s + ? oldMutationVal !== mutationTarget[LEXICON.s].cssText + : true; + } + + mutatedAttrs.push(mutationAttrName); + }); + + updateViewportAttrsFromTarget(mutatedAttrs); + + if (doUpdate) + _base.update(doUpdateForce || _strAuto); + } + return doUpdate; + }; + _mutationObserverContentCallback = function (mutations) { + var doUpdate = false; + var mutation; + + if (_initialized && !_sleeping) { + each(mutations, function () { + mutation = this; + doUpdate = isUnknownMutation(mutation); + return !doUpdate; + }); + + if (doUpdate) { + now = COMPATIBILITY.now(); + sizeAuto = (_heightAutoCache || _widthAutoCache); + action = function () { + if (!_destroyed) { + contentLastUpdate = now; + + //if cols, rows or wrap attr was changed + if (_isTextarea) + textareaUpdate(); + + if (sizeAuto) + update(); + else + _base.update(_strAuto); + } + }; + clearTimeout(contentTimeout); + if (mutationObserverContentLag <= 0 || now - contentLastUpdate > mutationObserverContentLag || !sizeAuto) + action(); + else + contentTimeout = setTimeout(action, mutationObserverContentLag); + } + } + return doUpdate; + } + + _mutationObserverHost = new mutationObserver(_mutationObserverHostCallback); + _mutationObserverContent = new mutationObserver(_mutationObserverContentCallback); + } + } + + /** + * Connects the MutationObservers if they are supported. + */ + function connectMutationObservers() { + if (_supportMutationObserver && !_mutationObserversConnected) { + _mutationObserverHost.observe(_hostElementNative, { + attributes: true, + attributeOldValue: true, + attributeFilter: _mutationObserverAttrsHost + }); + + _mutationObserverContent.observe(_isTextarea ? _targetElementNative : _contentElementNative, { + attributes: true, + attributeOldValue: true, + subtree: !_isTextarea, + childList: !_isTextarea, + characterData: !_isTextarea, + attributeFilter: _isTextarea ? _mutationObserverAttrsTextarea : _mutationObserverAttrsHost + }); + + _mutationObserversConnected = true; + } + } + + /** + * Disconnects the MutationObservers if they are supported. + */ + function disconnectMutationObservers() { + if (_supportMutationObserver && _mutationObserversConnected) { + _mutationObserverHost.disconnect(); + _mutationObserverContent.disconnect(); + + _mutationObserversConnected = false; + } + } + + + //==== Events of elements ====// + + /** + * This method gets called every time the host element gets resized. IMPORTANT: Padding changes are detected too!! + * It refreshes the hostResizedEventArgs and the hostSizeResizeCache. + * If there are any size changes, the update method gets called. + */ + function hostOnResized() { + if (!_sleeping) { + var changed; + var hostSize = { + w: _sizeObserverElementNative[LEXICON.sW], + h: _sizeObserverElementNative[LEXICON.sH] + }; + + changed = checkCache(hostSize, _hostElementSizeChangeDetectedCache); + _hostElementSizeChangeDetectedCache = hostSize; + if (changed) + update({ _hostSizeChanged: true }); + } + } + + /** + * The mouse enter event of the host element. This event is only needed for the autoHide feature. + */ + function hostOnMouseEnter() { + if (_scrollbarsAutoHideLeave) + refreshScrollbarsAutoHide(true); + } + + /** + * The mouse leave event of the host element. This event is only needed for the autoHide feature. + */ + function hostOnMouseLeave() { + if (_scrollbarsAutoHideLeave && !_bodyElement.hasClass(_classNameDragging)) + refreshScrollbarsAutoHide(false); + } + + /** + * The mouse move event of the host element. This event is only needed for the autoHide "move" feature. + */ + function hostOnMouseMove() { + if (_scrollbarsAutoHideMove) { + refreshScrollbarsAutoHide(true); + clearTimeout(_scrollbarsAutoHideMoveTimeoutId); + _scrollbarsAutoHideMoveTimeoutId = setTimeout(function () { + if (_scrollbarsAutoHideMove && !_destroyed) + refreshScrollbarsAutoHide(false); + }, 100); + } + } + + /** + * Prevents text from deselection if attached to the document element on the mousedown event of a DOM element. + * @param event The select start event. + */ + function documentOnSelectStart(event) { + COMPATIBILITY.prvD(event); + return false; + } + + /** + * A callback which will be called after a element has loaded. + */ + function updateOnLoadCallback(event) { + var elm = FRAMEWORK(event.target); + + eachUpdateOnLoad(function (i, updateOnLoadSelector) { + if (elm.is(updateOnLoadSelector)) { + update({ _contentSizeChanged: true }); + } + }); + } + + /** + * Adds or removes mouse & touch events of the host element. (for handling auto-hiding of the scrollbars) + * @param destroy Indicates whether the events shall be added or removed. + */ + function setupHostMouseTouchEvents(destroy) { + if (!destroy) + setupHostMouseTouchEvents(true); + + setupResponsiveEventListener(_hostElement, + _strMouseTouchMoveEvent.split(_strSpace)[0], + hostOnMouseMove, + (!_scrollbarsAutoHideMove || destroy), true); + setupResponsiveEventListener(_hostElement, + [_strMouseEnter, _strMouseLeave], + [hostOnMouseEnter, hostOnMouseLeave], + (!_scrollbarsAutoHideLeave || destroy), true); + + //if the plugin is initialized and the mouse is over the host element, make the scrollbars visible + if (!_initialized && !destroy) + _hostElement.one('mouseover', hostOnMouseEnter); + } + + + //==== Update Detection ====// + + /** + * Measures the min width and min height of the body element and refreshes the related cache. + * @returns {boolean} True if the min width or min height has changed, false otherwise. + */ + function bodyMinSizeChanged() { + var bodyMinSize = {}; + if (_isBody && _contentArrangeElement) { + bodyMinSize.w = parseToZeroOrNumber(_contentArrangeElement.css(_strMinMinus + _strWidth)); + bodyMinSize.h = parseToZeroOrNumber(_contentArrangeElement.css(_strMinMinus + _strHeight)); + bodyMinSize.c = checkCache(bodyMinSize, _bodyMinSizeCache); + bodyMinSize.f = true; //flag for "measured at least once" + } + _bodyMinSizeCache = bodyMinSize; + return !!bodyMinSize.c; + } + + /** + * Returns true if the class names really changed (new class without plugin host prefix) + * @param oldClassNames The old ClassName string or array. + * @param newClassNames The new ClassName string or array. + * @returns {boolean} True if the class names has really changed, false otherwise. + */ + function hostClassNamesChanged(oldClassNames, newClassNames) { + var currClasses = typeof newClassNames == TYPES.s ? newClassNames.split(_strSpace) : []; + var oldClasses = typeof oldClassNames == TYPES.s ? oldClassNames.split(_strSpace) : []; + var diff = getArrayDifferences(oldClasses, currClasses); + + // remove none theme from diff list to prevent update + var idx = inArray(_classNameThemeNone, diff); + var i; + var regex; + + if (idx > -1) + diff.splice(idx, 1); + + if (diff[LEXICON.l] > 0) { + regex = createHostClassNameRegExp(true, true); + for (i = 0; i < diff.length; i++) { + if (!diff[i].match(regex)) { + return true; + } + } + } + return false; + } + + /** + * Returns true if the given mutation is not from a from the plugin generated element. If the target element is a textarea the mutation is always unknown. + * @param mutation The mutation which shall be checked. + * @returns {boolean} True if the mutation is from a unknown element, false otherwise. + */ + function isUnknownMutation(mutation) { + var attributeName = mutation.attributeName; + var mutationTarget = mutation.target; + var mutationType = mutation.type; + var strClosest = 'closest'; + + if (mutationTarget === _contentElementNative) + return attributeName === null; + if (mutationType === 'attributes' && (attributeName === LEXICON.c || attributeName === LEXICON.s) && !_isTextarea) { + //ignore className changes by the plugin + if (attributeName === LEXICON.c && FRAMEWORK(mutationTarget).hasClass(_classNameHostElement)) + return hostClassNamesChanged(mutation.oldValue, mutationTarget.className); + + //only do it of browser support it natively + if (typeof mutationTarget[strClosest] != TYPES.f) + return true; + if (mutationTarget[strClosest](_strDot + _classNameResizeObserverElement) !== null || + mutationTarget[strClosest](_strDot + _classNameScrollbar) !== null || + mutationTarget[strClosest](_strDot + _classNameScrollbarCorner) !== null) + return false; + } + return true; + } + + /** + * Returns true if the content size was changed since the last time this method was called. + * @returns {boolean} True if the content size was changed, false otherwise. + */ + function updateAutoContentSizeChanged() { + if (_sleeping) + return false; + + var contentMeasureElement = getContentMeasureElement(); + var textareaValueLength = _isTextarea && _widthAutoCache && !_textareaAutoWrappingCache ? _targetElement.val().length : 0; + var setCSS = !_mutationObserversConnected && _widthAutoCache && !_isTextarea; + var css = {}; + var float; + var bodyMinSizeC; + var changed; + var contentElementScrollSize; + + if (setCSS) { + float = _contentElement.css(_strFloat); + css[_strFloat] = _isRTL ? _strRight : _strLeft; + css[_strWidth] = _strAuto; + _contentElement.css(css); + } + contentElementScrollSize = { + w: contentMeasureElement[LEXICON.sW] + textareaValueLength, + h: contentMeasureElement[LEXICON.sH] + textareaValueLength + }; + if (setCSS) { + css[_strFloat] = float; + css[_strWidth] = _strHundredPercent; + _contentElement.css(css); + } + + bodyMinSizeC = bodyMinSizeChanged(); + changed = checkCache(contentElementScrollSize, _contentElementScrollSizeChangeDetectedCache); + + _contentElementScrollSizeChangeDetectedCache = contentElementScrollSize; + + return changed || bodyMinSizeC; + } + + /** + * Returns true when a attribute which the MutationObserver would observe has changed. + * @returns {boolean} True if one of the attributes which a MutationObserver would observe has changed, false or undefined otherwise. + */ + function meaningfulAttrsChanged() { + if (_sleeping || _mutationObserversConnected) + return; + + var elem; + var curr; + var cache; + var changedAttrs = []; + var checks = [ + { + _elem: _hostElement, + _attrs: _mutationObserverAttrsHost.concat(':visible') + }, + { + _elem: _isTextarea ? _targetElement : undefined, + _attrs: _mutationObserverAttrsTextarea + } + ]; + + each(checks, function (index, check) { + elem = check._elem; + if (elem) { + each(check._attrs, function (index, attr) { + curr = attr.charAt(0) === ':' ? elem.is(attr) : elem.attr(attr); + cache = _updateAutoCache[attr]; + + if (checkCache(curr, cache)) { + changedAttrs.push(attr); + } + + _updateAutoCache[attr] = curr; + }); + } + }); + + updateViewportAttrsFromTarget(changedAttrs); + + return changedAttrs[LEXICON.l] > 0; + } + + /** + * Checks is a CSS Property of a child element is affecting the scroll size of the content. + * @param propertyName The CSS property name. + * @returns {boolean} True if the property is affecting the content scroll size, false otherwise. + */ + function isSizeAffectingCSSProperty(propertyName) { + if (!_initialized) + return true; + var flexGrow = 'flex-grow'; + var flexShrink = 'flex-shrink'; + var flexBasis = 'flex-basis'; + var affectingPropsX = [ + _strWidth, + _strMinMinus + _strWidth, + _strMaxMinus + _strWidth, + _strMarginMinus + _strLeft, + _strMarginMinus + _strRight, + _strLeft, + _strRight, + 'font-weight', + 'word-spacing', + flexGrow, + flexShrink, + flexBasis + ]; + var affectingPropsXContentBox = [ + _strPaddingMinus + _strLeft, + _strPaddingMinus + _strRight, + _strBorderMinus + _strLeft + _strWidth, + _strBorderMinus + _strRight + _strWidth + ]; + var affectingPropsY = [ + _strHeight, + _strMinMinus + _strHeight, + _strMaxMinus + _strHeight, + _strMarginMinus + _strTop, + _strMarginMinus + _strBottom, + _strTop, + _strBottom, + 'line-height', + flexGrow, + flexShrink, + flexBasis + ]; + var affectingPropsYContentBox = [ + _strPaddingMinus + _strTop, + _strPaddingMinus + _strBottom, + _strBorderMinus + _strTop + _strWidth, + _strBorderMinus + _strBottom + _strWidth + ]; + var _strS = 's'; + var _strVS = 'v-s'; + var checkX = _overflowBehaviorCache.x === _strS || _overflowBehaviorCache.x === _strVS; + var checkY = _overflowBehaviorCache.y === _strS || _overflowBehaviorCache.y === _strVS; + var sizeIsAffected = false; + var checkPropertyName = function (arr, name) { + for (var i = 0; i < arr[LEXICON.l]; i++) { + if (arr[i] === name) + return true; + } + return false; + }; + + if (checkY) { + sizeIsAffected = checkPropertyName(affectingPropsY, propertyName); + if (!sizeIsAffected && !_isBorderBox) + sizeIsAffected = checkPropertyName(affectingPropsYContentBox, propertyName); + } + if (checkX && !sizeIsAffected) { + sizeIsAffected = checkPropertyName(affectingPropsX, propertyName); + if (!sizeIsAffected && !_isBorderBox) + sizeIsAffected = checkPropertyName(affectingPropsXContentBox, propertyName); + } + return sizeIsAffected; + } + + + //==== Update ====// + + /** + * Sets the attribute values of the viewport element to the values from the target element. + * The value of a attribute is only set if the attribute is whitelisted. + * @attrs attrs The array of attributes which shall be set or undefined if all whitelisted shall be set. + */ + function updateViewportAttrsFromTarget(attrs) { + attrs = attrs || _viewportAttrsFromTarget; + each(attrs, function (index, attr) { + if (COMPATIBILITY.inA(attr, _viewportAttrsFromTarget) > -1) { + var targetAttr = _targetElement.attr(attr); + if (type(targetAttr) == TYPES.s) { + _viewportElement.attr(attr, targetAttr); + } + else { + _viewportElement.removeAttr(attr); + } + } + }); + } + + /** + * Updates the variables and size of the textarea element, and manages the scroll on new line or new character. + */ + function textareaUpdate() { + if (!_sleeping) { + var wrapAttrOff = !_textareaAutoWrappingCache; + var minWidth = _viewportSize.w; + var minHeight = _viewportSize.h; + var css = {}; + var doMeasure = _widthAutoCache || wrapAttrOff; + var origWidth; + var width; + var origHeight; + var height; + + //reset min size + css[_strMinMinus + _strWidth] = _strEmpty; + css[_strMinMinus + _strHeight] = _strEmpty; + + //set width auto + css[_strWidth] = _strAuto; + _targetElement.css(css); + + //measure width + origWidth = _targetElementNative[LEXICON.oW]; + width = doMeasure ? MATH.max(origWidth, _targetElementNative[LEXICON.sW] - 1) : 1; + /*width += (_widthAutoCache ? _marginX + (!_isBorderBox ? wrapAttrOff ? 0 : _paddingX + _borderX : 0) : 0);*/ + + //set measured width + css[_strWidth] = _widthAutoCache ? _strAuto /*width*/ : _strHundredPercent; + css[_strMinMinus + _strWidth] = _strHundredPercent; + + //set height auto + css[_strHeight] = _strAuto; + _targetElement.css(css); + + //measure height + origHeight = _targetElementNative[LEXICON.oH]; + height = MATH.max(origHeight, _targetElementNative[LEXICON.sH] - 1); + + //append correct size values + css[_strWidth] = width; + css[_strHeight] = height; + _textareaCoverElement.css(css); + + //apply min width / min height to prevent textarea collapsing + css[_strMinMinus + _strWidth] = minWidth /*+ (!_isBorderBox && _widthAutoCache ? _paddingX + _borderX : 0)*/; + css[_strMinMinus + _strHeight] = minHeight /*+ (!_isBorderBox && _heightAutoCache ? _paddingY + _borderY : 0)*/; + _targetElement.css(css); + + return { + _originalWidth: origWidth, + _originalHeight: origHeight, + _dynamicWidth: width, + _dynamicHeight: height + }; + } + } + + /** + * Updates the plugin and DOM to the current options. + * This method should only be called if a update is 100% required. + * @param updateHints A objects which contains hints for this update: + * { + * _hostSizeChanged : boolean, + * _contentSizeChanged : boolean, + * _force : boolean, == preventSwallowing + * _changedOptions : { }, == preventSwallowing && preventSleep + * } + */ + function update(updateHints) { + clearTimeout(_swallowedUpdateTimeout); + updateHints = updateHints || {}; + _swallowedUpdateHints._hostSizeChanged |= updateHints._hostSizeChanged; + _swallowedUpdateHints._contentSizeChanged |= updateHints._contentSizeChanged; + _swallowedUpdateHints._force |= updateHints._force; + + var now = COMPATIBILITY.now(); + var hostSizeChanged = !!_swallowedUpdateHints._hostSizeChanged; + var contentSizeChanged = !!_swallowedUpdateHints._contentSizeChanged; + var force = !!_swallowedUpdateHints._force; + var changedOptions = updateHints._changedOptions; + var swallow = _swallowUpdateLag > 0 && _initialized && !_destroyed && !force && !changedOptions && (now - _lastUpdateTime) < _swallowUpdateLag && (!_heightAutoCache && !_widthAutoCache); + var displayIsHidden; + + if (swallow) + _swallowedUpdateTimeout = setTimeout(update, _swallowUpdateLag); + + //abort update due to: + //destroyed + //swallowing + //sleeping + //host is hidden or has false display + if (_destroyed || swallow || (_sleeping && !changedOptions) || (_initialized && !force && (displayIsHidden = _hostElement.is(':hidden'))) || _hostElement.css('display') === 'inline') + return; + + _lastUpdateTime = now; + _swallowedUpdateHints = {}; + + //if scrollbar styling is possible and native scrollbars aren't overlaid the scrollbar styling will be applied which hides the native scrollbars completely. + if (_nativeScrollbarStyling && !(_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y)) { + //native scrollbars are hidden, so change the values to zero + _nativeScrollbarSize.x = 0; + _nativeScrollbarSize.y = 0; + } + else { + //refresh native scrollbar size (in case of zoom) + _nativeScrollbarSize = extendDeep({}, globals.nativeScrollbarSize); + } + + // Scrollbar padding is needed for firefox, because firefox hides scrollbar automatically if the size of the div is too small. + // The calculation: [scrollbar size +3 *3] + // (+3 because of possible decoration e.g. borders, margins etc., but only if native scrollbar is NOT a overlaid scrollbar) + // (*3 because (1)increase / (2)decrease -button and (3)resize handle) + _nativeScrollbarMinSize = { + x: (_nativeScrollbarSize.x + (_nativeScrollbarIsOverlaid.x ? 0 : 3)) * 3, + y: (_nativeScrollbarSize.y + (_nativeScrollbarIsOverlaid.y ? 0 : 3)) * 3 + }; + + changedOptions = changedOptions || {}; + //freezeResizeObserver(_sizeObserverElement, true); + //freezeResizeObserver(_sizeAutoObserverElement, true); + + var checkCacheAutoForce = function () { + return checkCache.apply(this, [].slice.call(arguments).concat([force])); + }; + + //save current scroll offset + var currScroll = { + x: _viewportElement[_strScrollLeft](), + y: _viewportElement[_strScrollTop]() + }; + + var currentPreparedOptionsScrollbars = _currentPreparedOptions.scrollbars; + var currentPreparedOptionsTextarea = _currentPreparedOptions.textarea; + + //scrollbars visibility: + var scrollbarsVisibility = currentPreparedOptionsScrollbars.visibility; + var scrollbarsVisibilityChanged = checkCacheAutoForce(scrollbarsVisibility, _scrollbarsVisibilityCache); + + //scrollbars autoHide: + var scrollbarsAutoHide = currentPreparedOptionsScrollbars.autoHide; + var scrollbarsAutoHideChanged = checkCacheAutoForce(scrollbarsAutoHide, _scrollbarsAutoHideCache); + + //scrollbars click scrolling + var scrollbarsClickScrolling = currentPreparedOptionsScrollbars.clickScrolling; + var scrollbarsClickScrollingChanged = checkCacheAutoForce(scrollbarsClickScrolling, _scrollbarsClickScrollingCache); + + //scrollbars drag scrolling + var scrollbarsDragScrolling = currentPreparedOptionsScrollbars.dragScrolling; + var scrollbarsDragScrollingChanged = checkCacheAutoForce(scrollbarsDragScrolling, _scrollbarsDragScrollingCache); + + //className + var className = _currentPreparedOptions.className; + var classNameChanged = checkCacheAutoForce(className, _classNameCache); + + //resize + var resize = _currentPreparedOptions.resize; + var resizeChanged = checkCacheAutoForce(resize, _resizeCache) && !_isBody; //body can't be resized since the window itself acts as resize possibility. + + //paddingAbsolute + var paddingAbsolute = _currentPreparedOptions.paddingAbsolute; + var paddingAbsoluteChanged = checkCacheAutoForce(paddingAbsolute, _paddingAbsoluteCache); + + //clipAlways + var clipAlways = _currentPreparedOptions.clipAlways; + var clipAlwaysChanged = checkCacheAutoForce(clipAlways, _clipAlwaysCache); + + //sizeAutoCapable + var sizeAutoCapable = _currentPreparedOptions.sizeAutoCapable && !_isBody; //body can never be size auto, because it shall be always as big as the viewport. + var sizeAutoCapableChanged = checkCacheAutoForce(sizeAutoCapable, _sizeAutoCapableCache); + + //showNativeScrollbars + var ignoreOverlayScrollbarHiding = _currentPreparedOptions.nativeScrollbarsOverlaid.showNativeScrollbars; + var ignoreOverlayScrollbarHidingChanged = checkCacheAutoForce(ignoreOverlayScrollbarHiding, _ignoreOverlayScrollbarHidingCache); + + //autoUpdate + var autoUpdate = _currentPreparedOptions.autoUpdate; + var autoUpdateChanged = checkCacheAutoForce(autoUpdate, _autoUpdateCache); + + //overflowBehavior + var overflowBehavior = _currentPreparedOptions.overflowBehavior; + var overflowBehaviorChanged = checkCacheAutoForce(overflowBehavior, _overflowBehaviorCache, force); + + //dynWidth: + var textareaDynWidth = currentPreparedOptionsTextarea.dynWidth; + var textareaDynWidthChanged = checkCacheAutoForce(_textareaDynWidthCache, textareaDynWidth); + + //dynHeight: + var textareaDynHeight = currentPreparedOptionsTextarea.dynHeight; + var textareaDynHeightChanged = checkCacheAutoForce(_textareaDynHeightCache, textareaDynHeight); + + //scrollbars visibility + _scrollbarsAutoHideNever = scrollbarsAutoHide === 'n'; + _scrollbarsAutoHideScroll = scrollbarsAutoHide === 's'; + _scrollbarsAutoHideMove = scrollbarsAutoHide === 'm'; + _scrollbarsAutoHideLeave = scrollbarsAutoHide === 'l'; + + //scrollbars autoHideDelay + _scrollbarsAutoHideDelay = currentPreparedOptionsScrollbars.autoHideDelay; + + //old className + _oldClassName = _classNameCache; + + //resize + _resizeNone = resize === 'n'; + _resizeBoth = resize === 'b'; + _resizeHorizontal = resize === 'h'; + _resizeVertical = resize === 'v'; + + //normalizeRTL + _normalizeRTLCache = _currentPreparedOptions.normalizeRTL; + + //ignore overlay scrollbar hiding + ignoreOverlayScrollbarHiding = ignoreOverlayScrollbarHiding && (_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y); + + //refresh options cache + _scrollbarsVisibilityCache = scrollbarsVisibility; + _scrollbarsAutoHideCache = scrollbarsAutoHide; + _scrollbarsClickScrollingCache = scrollbarsClickScrolling; + _scrollbarsDragScrollingCache = scrollbarsDragScrolling; + _classNameCache = className; + _resizeCache = resize; + _paddingAbsoluteCache = paddingAbsolute; + _clipAlwaysCache = clipAlways; + _sizeAutoCapableCache = sizeAutoCapable; + _ignoreOverlayScrollbarHidingCache = ignoreOverlayScrollbarHiding; + _autoUpdateCache = autoUpdate; + _overflowBehaviorCache = extendDeep({}, overflowBehavior); + _textareaDynWidthCache = textareaDynWidth; + _textareaDynHeightCache = textareaDynHeight; + _hasOverflowCache = _hasOverflowCache || { x: false, y: false }; + + //set correct class name to the host element + if (classNameChanged) { + removeClass(_hostElement, _oldClassName + _strSpace + _classNameThemeNone); + addClass(_hostElement, className !== undefined && className !== null && className.length > 0 ? className : _classNameThemeNone); + } + + //set correct auto Update + if (autoUpdateChanged) { + if (autoUpdate === true || (autoUpdate === null && _autoUpdateRecommended)) { + disconnectMutationObservers(); + autoUpdateLoop.add(_base); + } + else { + autoUpdateLoop.remove(_base); + connectMutationObservers(); + } + } + + //activate or deactivate size auto capability + if (sizeAutoCapableChanged) { + if (sizeAutoCapable) { + if (_contentGlueElement) { + _contentGlueElement.show(); + } + else { + _contentGlueElement = FRAMEWORK(generateDiv(_classNameContentGlueElement)); + _paddingElement.before(_contentGlueElement); + } + if (_sizeAutoObserverAdded) { + _sizeAutoObserverElement.show(); + } + else { + _sizeAutoObserverElement = FRAMEWORK(generateDiv(_classNameSizeAutoObserverElement)); + _sizeAutoObserverElementNative = _sizeAutoObserverElement[0]; + + _contentGlueElement.before(_sizeAutoObserverElement); + var oldSize = { w: -1, h: -1 }; + setupResizeObserver(_sizeAutoObserverElement, function () { + var newSize = { + w: _sizeAutoObserverElementNative[LEXICON.oW], + h: _sizeAutoObserverElementNative[LEXICON.oH] + }; + if (checkCache(newSize, oldSize)) { + if (_initialized && (_heightAutoCache && newSize.h > 0) || (_widthAutoCache && newSize.w > 0)) { + update(); + } + else if (_initialized && (!_heightAutoCache && newSize.h === 0) || (!_widthAutoCache && newSize.w === 0)) { + update(); + } + } + oldSize = newSize; + }); + _sizeAutoObserverAdded = true; + //fix heightAuto detector bug if height is fixed but contentHeight is 0. + //the probability this bug will ever happen is very very low, thats why its ok if we use calc which isn't supported in IE8. + if (_cssCalc !== null) + _sizeAutoObserverElement.css(_strHeight, _cssCalc + '(100% + 1px)'); + } + } + else { + if (_sizeAutoObserverAdded) + _sizeAutoObserverElement.hide(); + if (_contentGlueElement) + _contentGlueElement.hide(); + } + } + + //if force, update all resizeObservers too + if (force) { + _sizeObserverElement.find('*').trigger(_strScroll); + if (_sizeAutoObserverAdded) + _sizeAutoObserverElement.find('*').trigger(_strScroll); + } + + //display hidden: + displayIsHidden = displayIsHidden === undefined ? _hostElement.is(':hidden') : displayIsHidden; + + //textarea AutoWrapping: + var textareaAutoWrapping = _isTextarea ? _targetElement.attr('wrap') !== 'off' : false; + var textareaAutoWrappingChanged = checkCacheAutoForce(textareaAutoWrapping, _textareaAutoWrappingCache); + + //detect direction: + var cssDirection = _hostElement.css('direction'); + var cssDirectionChanged = checkCacheAutoForce(cssDirection, _cssDirectionCache); + + //detect box-sizing: + var boxSizing = _hostElement.css('box-sizing'); + var boxSizingChanged = checkCacheAutoForce(boxSizing, _cssBoxSizingCache); + + //detect padding: + var padding = getTopRightBottomLeftHost(_strPaddingMinus); + + //width + height auto detecting var: + var sizeAutoObserverElementBCRect; + //exception occurs in IE8 sometimes (unknown exception) + try { + sizeAutoObserverElementBCRect = _sizeAutoObserverAdded ? _sizeAutoObserverElementNative[LEXICON.bCR]() : null; + } catch (ex) { + return; + } + + _isRTL = cssDirection === 'rtl'; + _isBorderBox = (boxSizing === 'border-box'); + var isRTLLeft = _isRTL ? _strLeft : _strRight; + var isRTLRight = _isRTL ? _strRight : _strLeft; + + //detect width auto: + var widthAutoResizeDetection = false; + var widthAutoObserverDetection = (_sizeAutoObserverAdded && (_hostElement.css(_strFloat) !== 'none' /*|| _isTextarea */)) ? (MATH.round(sizeAutoObserverElementBCRect.right - sizeAutoObserverElementBCRect.left) === 0) && (!paddingAbsolute ? (_hostElementNative[LEXICON.cW] - _paddingX) > 0 : true) : false; + if (sizeAutoCapable && !widthAutoObserverDetection) { + var tmpCurrHostWidth = _hostElementNative[LEXICON.oW]; + var tmpCurrContentGlueWidth = _contentGlueElement.css(_strWidth); + _contentGlueElement.css(_strWidth, _strAuto); + + var tmpNewHostWidth = _hostElementNative[LEXICON.oW]; + _contentGlueElement.css(_strWidth, tmpCurrContentGlueWidth); + widthAutoResizeDetection = tmpCurrHostWidth !== tmpNewHostWidth; + if (!widthAutoResizeDetection) { + _contentGlueElement.css(_strWidth, tmpCurrHostWidth + 1); + tmpNewHostWidth = _hostElementNative[LEXICON.oW]; + _contentGlueElement.css(_strWidth, tmpCurrContentGlueWidth); + widthAutoResizeDetection = tmpCurrHostWidth !== tmpNewHostWidth; + } + } + var widthAuto = (widthAutoObserverDetection || widthAutoResizeDetection) && sizeAutoCapable && !displayIsHidden; + var widthAutoChanged = checkCacheAutoForce(widthAuto, _widthAutoCache); + var wasWidthAuto = !widthAuto && _widthAutoCache; + + //detect height auto: + var heightAuto = _sizeAutoObserverAdded && sizeAutoCapable && !displayIsHidden ? (MATH.round(sizeAutoObserverElementBCRect.bottom - sizeAutoObserverElementBCRect.top) === 0) /* && (!paddingAbsolute && (_msieVersion > 9 || !_msieVersion) ? true : true) */ : false; + var heightAutoChanged = checkCacheAutoForce(heightAuto, _heightAutoCache); + var wasHeightAuto = !heightAuto && _heightAutoCache; + + //detect border: + //we need the border only if border box and auto size + var updateBorderX = (widthAuto && _isBorderBox) || !_isBorderBox; + var updateBorderY = (heightAuto && _isBorderBox) || !_isBorderBox; + var border = getTopRightBottomLeftHost(_strBorderMinus, '-' + _strWidth, !updateBorderX, !updateBorderY) + + //detect margin: + var margin = getTopRightBottomLeftHost(_strMarginMinus); + + //vars to apply correct css + var contentElementCSS = {}; + var contentGlueElementCSS = {}; + + //funcs + var getHostSize = function () { + //has to be clientSize because offsetSize respect borders + return { + w: _hostElementNative[LEXICON.cW], + h: _hostElementNative[LEXICON.cH] + }; + }; + var getViewportSize = function () { + //viewport size is padding container because it never has padding, margin and a border + //determine zoom rounding error -> sometimes scrollWidth/Height is smaller than clientWidth/Height + //if this happens add the difference to the viewportSize to compensate the rounding error + return { + w: _paddingElementNative[LEXICON.oW] + MATH.max(0, _contentElementNative[LEXICON.cW] - _contentElementNative[LEXICON.sW]), + h: _paddingElementNative[LEXICON.oH] + MATH.max(0, _contentElementNative[LEXICON.cH] - _contentElementNative[LEXICON.sH]) + }; + }; + + //set info for padding + var paddingAbsoluteX = _paddingX = padding.l + padding.r; + var paddingAbsoluteY = _paddingY = padding.t + padding.b; + paddingAbsoluteX *= paddingAbsolute ? 1 : 0; + paddingAbsoluteY *= paddingAbsolute ? 1 : 0; + padding.c = checkCacheAutoForce(padding, _cssPaddingCache); + + //set info for border + _borderX = border.l + border.r; + _borderY = border.t + border.b; + border.c = checkCacheAutoForce(border, _cssBorderCache); + + //set info for margin + _marginX = margin.l + margin.r; + _marginY = margin.t + margin.b; + margin.c = checkCacheAutoForce(margin, _cssMarginCache); + + //refresh cache + _textareaAutoWrappingCache = textareaAutoWrapping; + _cssDirectionCache = cssDirection; + _cssBoxSizingCache = boxSizing; + _widthAutoCache = widthAuto; + _heightAutoCache = heightAuto; + _cssPaddingCache = padding; + _cssBorderCache = border; + _cssMarginCache = margin; + + //IEFix direction changed + if (cssDirectionChanged && _sizeAutoObserverAdded) + _sizeAutoObserverElement.css(_strFloat, isRTLRight); + + //apply padding: + if (padding.c || cssDirectionChanged || paddingAbsoluteChanged || widthAutoChanged || heightAutoChanged || boxSizingChanged || sizeAutoCapableChanged) { + var paddingElementCSS = {}; + var textareaCSS = {}; + var paddingValues = [padding.t, padding.r, padding.b, padding.l]; + + setTopRightBottomLeft(contentGlueElementCSS, _strMarginMinus, [-padding.t, -padding.r, -padding.b, -padding.l]); + if (paddingAbsolute) { + setTopRightBottomLeft(paddingElementCSS, _strEmpty, paddingValues); + setTopRightBottomLeft(_isTextarea ? textareaCSS : contentElementCSS, _strPaddingMinus); + } + else { + setTopRightBottomLeft(paddingElementCSS, _strEmpty); + setTopRightBottomLeft(_isTextarea ? textareaCSS : contentElementCSS, _strPaddingMinus, paddingValues); + } + + _paddingElement.css(paddingElementCSS); + _targetElement.css(textareaCSS); + } + + //viewport size is padding container because it never has padding, margin and a border. + _viewportSize = getViewportSize(); + + //update Textarea + var textareaSize = _isTextarea ? textareaUpdate() : false; + var textareaSizeChanged = _isTextarea && checkCacheAutoForce(textareaSize, _textareaSizeCache); + var textareaDynOrigSize = _isTextarea && textareaSize ? { + w: textareaDynWidth ? textareaSize._dynamicWidth : textareaSize._originalWidth, + h: textareaDynHeight ? textareaSize._dynamicHeight : textareaSize._originalHeight + } : {}; + _textareaSizeCache = textareaSize; + + //fix height auto / width auto in cooperation with current padding & boxSizing behavior: + if (heightAuto && (heightAutoChanged || paddingAbsoluteChanged || boxSizingChanged || padding.c || border.c)) { + contentElementCSS[_strHeight] = _strAuto; + } + else if (heightAutoChanged || paddingAbsoluteChanged) { + contentElementCSS[_strHeight] = _strHundredPercent; + } + if (widthAuto && (widthAutoChanged || paddingAbsoluteChanged || boxSizingChanged || padding.c || border.c || cssDirectionChanged)) { + contentElementCSS[_strWidth] = _strAuto; + contentGlueElementCSS[_strMaxMinus + _strWidth] = _strHundredPercent; //IE Fix + } + else if (widthAutoChanged || paddingAbsoluteChanged) { + contentElementCSS[_strWidth] = _strHundredPercent; + contentElementCSS[_strFloat] = _strEmpty; + contentGlueElementCSS[_strMaxMinus + _strWidth] = _strEmpty; //IE Fix + } + if (widthAuto) { + //textareaDynOrigSize.w || _strAuto :: doesnt works because applied margin will shift width + contentGlueElementCSS[_strWidth] = _strAuto; + + contentElementCSS[_strWidth] = VENDORS._cssPropertyValue(_strWidth, 'max-content intrinsic') || _strAuto; + contentElementCSS[_strFloat] = isRTLRight; + } + else { + contentGlueElementCSS[_strWidth] = _strEmpty; + } + if (heightAuto) { + //textareaDynOrigSize.h || _contentElementNative[LEXICON.cH] :: use for anti scroll jumping + contentGlueElementCSS[_strHeight] = textareaDynOrigSize.h || _contentElementNative[LEXICON.cH]; + } + else { + contentGlueElementCSS[_strHeight] = _strEmpty; + } + if (sizeAutoCapable) + _contentGlueElement.css(contentGlueElementCSS); + _contentElement.css(contentElementCSS); + + //CHECKPOINT HERE ~ + contentElementCSS = {}; + contentGlueElementCSS = {}; + + //if [content(host) client / scroll size, or target element direction, or content(host) max-sizes] changed, or force is true + if (hostSizeChanged || contentSizeChanged || textareaSizeChanged || cssDirectionChanged || boxSizingChanged || paddingAbsoluteChanged || widthAutoChanged || widthAuto || heightAutoChanged || heightAuto || ignoreOverlayScrollbarHidingChanged || overflowBehaviorChanged || clipAlwaysChanged || resizeChanged || scrollbarsVisibilityChanged || scrollbarsAutoHideChanged || scrollbarsDragScrollingChanged || scrollbarsClickScrollingChanged || textareaDynWidthChanged || textareaDynHeightChanged || textareaAutoWrappingChanged) { + var strOverflow = 'overflow'; + var strOverflowX = strOverflow + '-x'; + var strOverflowY = strOverflow + '-y'; + var strHidden = 'hidden'; + var strVisible = 'visible'; + + //Reset the viewport (very important for natively overlaid scrollbars and zoom change + //don't change the overflow prop as it is very expensive and affects performance !A LOT! + if (!_nativeScrollbarStyling) { + var viewportElementResetCSS = {}; + var resetXTmp = _hasOverflowCache.y && _hideOverflowCache.ys && !ignoreOverlayScrollbarHiding ? (_nativeScrollbarIsOverlaid.y ? _viewportElement.css(isRTLLeft) : -_nativeScrollbarSize.y) : 0; + var resetBottomTmp = _hasOverflowCache.x && _hideOverflowCache.xs && !ignoreOverlayScrollbarHiding ? (_nativeScrollbarIsOverlaid.x ? _viewportElement.css(_strBottom) : -_nativeScrollbarSize.x) : 0; + setTopRightBottomLeft(viewportElementResetCSS, _strEmpty); + _viewportElement.css(viewportElementResetCSS); + } + + //measure several sizes: + var contentMeasureElement = getContentMeasureElement(); + //in Firefox content element has to have overflow hidden, else element margins aren't calculated properly, this element prevents this bug, but only if scrollbars aren't overlaid + var contentSize = { + //use clientSize because natively overlaidScrollbars add borders + w: textareaDynOrigSize.w || contentMeasureElement[LEXICON.cW], + h: textareaDynOrigSize.h || contentMeasureElement[LEXICON.cH] + }; + var scrollSize = { + w: contentMeasureElement[LEXICON.sW], + h: contentMeasureElement[LEXICON.sH] + }; + + //apply the correct viewport style and measure viewport size + if (!_nativeScrollbarStyling) { + viewportElementResetCSS[_strBottom] = wasHeightAuto ? _strEmpty : resetBottomTmp; + viewportElementResetCSS[isRTLLeft] = wasWidthAuto ? _strEmpty : resetXTmp; + _viewportElement.css(viewportElementResetCSS); + } + _viewportSize = getViewportSize(); + + //measure and correct several sizes + var hostSize = getHostSize(); + var hostAbsoluteRectSize = { + w: hostSize.w - _marginX - _borderX - (_isBorderBox ? 0 : _paddingX), + h: hostSize.h - _marginY - _borderY - (_isBorderBox ? 0 : _paddingY) + }; + var contentGlueSize = { + //client/scrollSize + AbsolutePadding -> because padding is only applied to the paddingElement if its absolute, so you have to add it manually + //hostSize is clientSize -> so padding should be added manually, right? FALSE! Because content glue is inside hostElement, so we don't have to worry about padding + w: MATH.max((widthAuto ? contentSize.w : scrollSize.w) + paddingAbsoluteX, hostAbsoluteRectSize.w), + h: MATH.max((heightAuto ? contentSize.h : scrollSize.h) + paddingAbsoluteY, hostAbsoluteRectSize.h) + }; + contentGlueSize.c = checkCacheAutoForce(contentGlueSize, _contentGlueSizeCache); + _contentGlueSizeCache = contentGlueSize; + + //apply correct contentGlue size + if (sizeAutoCapable) { + //size contentGlue correctly to make sure the element has correct size if the sizing switches to auto + if (contentGlueSize.c || (heightAuto || widthAuto)) { + contentGlueElementCSS[_strWidth] = contentGlueSize.w; + contentGlueElementCSS[_strHeight] = contentGlueSize.h; + + //textarea-sizes are already calculated correctly at this point + if (!_isTextarea) { + contentSize = { + //use clientSize because natively overlaidScrollbars add borders + w: contentMeasureElement[LEXICON.cW], + h: contentMeasureElement[LEXICON.cH] + }; + } + } + var textareaCoverCSS = {}; + var setContentGlueElementCSSfunction = function (horizontal) { + var scrollbarVars = getScrollbarVars(horizontal); + var wh = scrollbarVars._w_h; + var strWH = scrollbarVars._width_height; + var autoSize = horizontal ? widthAuto : heightAuto; + var borderSize = horizontal ? _borderX : _borderY; + var paddingSize = horizontal ? _paddingX : _paddingY; + var marginSize = horizontal ? _marginX : _marginY; + var viewportSize = _viewportSize[wh] - borderSize - marginSize - (_isBorderBox ? 0 : paddingSize); + + //make contentGlue size -1 if element is not auto sized, to make sure that a resize event happens when the element shrinks + if (!autoSize || (!autoSize && border.c)) + contentGlueElementCSS[strWH] = hostAbsoluteRectSize[wh] - 1; + + //if size is auto and host is smaller than size as min size, make content glue size -1 to make sure size changes will be detected (this is only needed if padding is 0) + if (autoSize && (contentSize[wh] < viewportSize) && (horizontal && _isTextarea ? !textareaAutoWrapping : true)) { + if (_isTextarea) + textareaCoverCSS[strWH] = parseToZeroOrNumber(_textareaCoverElement.css(strWH)) - 1; + contentGlueElementCSS[strWH] -= 1; + } + + //make sure content glue size is at least 1 + if (contentSize[wh] > 0) + contentGlueElementCSS[strWH] = MATH.max(1, contentGlueElementCSS[strWH]); + }; + setContentGlueElementCSSfunction(true); + setContentGlueElementCSSfunction(false); + + if (_isTextarea) + _textareaCoverElement.css(textareaCoverCSS); + _contentGlueElement.css(contentGlueElementCSS); + } + if (widthAuto) + contentElementCSS[_strWidth] = _strHundredPercent; + if (widthAuto && !_isBorderBox && !_mutationObserversConnected) + contentElementCSS[_strFloat] = 'none'; + + //apply and reset content style + _contentElement.css(contentElementCSS); + contentElementCSS = {}; + + //measure again, but this time all correct sizes: + var contentScrollSize = { + w: contentMeasureElement[LEXICON.sW], + h: contentMeasureElement[LEXICON.sH], + }; + contentScrollSize.c = contentSizeChanged = checkCacheAutoForce(contentScrollSize, _contentScrollSizeCache); + _contentScrollSizeCache = contentScrollSize; + + //refresh viewport size after correct measuring + _viewportSize = getViewportSize(); + + hostSize = getHostSize(); + hostSizeChanged = checkCacheAutoForce(hostSize, _hostSizeCache); + _hostSizeCache = hostSize; + + var hideOverflowForceTextarea = _isTextarea && (_viewportSize.w === 0 || _viewportSize.h === 0); + var previousOverflowAmount = _overflowAmountCache; + var overflowBehaviorIsVS = {}; + var overflowBehaviorIsVH = {}; + var overflowBehaviorIsS = {}; + var overflowAmount = {}; + var hasOverflow = {}; + var hideOverflow = {}; + var canScroll = {}; + var viewportRect = _paddingElementNative[LEXICON.bCR](); + var setOverflowVariables = function (horizontal) { + var scrollbarVars = getScrollbarVars(horizontal); + var scrollbarVarsInverted = getScrollbarVars(!horizontal); + var xyI = scrollbarVarsInverted._x_y; + var xy = scrollbarVars._x_y; + var wh = scrollbarVars._w_h; + var widthHeight = scrollbarVars._width_height; + var scrollMax = _strScroll + scrollbarVars._Left_Top + 'Max'; + var fractionalOverflowAmount = viewportRect[widthHeight] ? MATH.abs(viewportRect[widthHeight] - _viewportSize[wh]) : 0; + var checkFractionalOverflowAmount = previousOverflowAmount && previousOverflowAmount[xy] > 0 && _viewportElementNative[scrollMax] === 0; + overflowBehaviorIsVS[xy] = overflowBehavior[xy] === 'v-s'; + overflowBehaviorIsVH[xy] = overflowBehavior[xy] === 'v-h'; + overflowBehaviorIsS[xy] = overflowBehavior[xy] === 's'; + overflowAmount[xy] = MATH.max(0, MATH.round((contentScrollSize[wh] - _viewportSize[wh]) * 100) / 100); + overflowAmount[xy] *= (hideOverflowForceTextarea || (checkFractionalOverflowAmount && fractionalOverflowAmount > 0 && fractionalOverflowAmount < 1)) ? 0 : 1; + hasOverflow[xy] = overflowAmount[xy] > 0; + + //hideOverflow: + //x || y : true === overflow is hidden by "overflow: scroll" OR "overflow: hidden" + //xs || ys : true === overflow is hidden by "overflow: scroll" + hideOverflow[xy] = overflowBehaviorIsVS[xy] || overflowBehaviorIsVH[xy] ? (hasOverflow[xyI] && !overflowBehaviorIsVS[xyI] && !overflowBehaviorIsVH[xyI]) : hasOverflow[xy]; + hideOverflow[xy + 's'] = hideOverflow[xy] ? (overflowBehaviorIsS[xy] || overflowBehaviorIsVS[xy]) : false; + + canScroll[xy] = hasOverflow[xy] && hideOverflow[xy + 's']; + }; + setOverflowVariables(true); + setOverflowVariables(false); + + overflowAmount.c = checkCacheAutoForce(overflowAmount, _overflowAmountCache); + _overflowAmountCache = overflowAmount; + hasOverflow.c = checkCacheAutoForce(hasOverflow, _hasOverflowCache); + _hasOverflowCache = hasOverflow; + hideOverflow.c = checkCacheAutoForce(hideOverflow, _hideOverflowCache); + _hideOverflowCache = hideOverflow; + + //if native scrollbar is overlay at x OR y axis, prepare DOM + if (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y) { + var borderDesign = 'px solid transparent'; + var contentArrangeElementCSS = {}; + var arrangeContent = {}; + var arrangeChanged = force; + var setContentElementCSS; + + if (hasOverflow.x || hasOverflow.y) { + arrangeContent.w = _nativeScrollbarIsOverlaid.y && hasOverflow.y ? contentScrollSize.w + _overlayScrollbarDummySize.y : _strEmpty; + arrangeContent.h = _nativeScrollbarIsOverlaid.x && hasOverflow.x ? contentScrollSize.h + _overlayScrollbarDummySize.x : _strEmpty; + arrangeChanged = checkCacheAutoForce(arrangeContent, _arrangeContentSizeCache); + _arrangeContentSizeCache = arrangeContent; + } + + if (hasOverflow.c || hideOverflow.c || contentScrollSize.c || cssDirectionChanged || widthAutoChanged || heightAutoChanged || widthAuto || heightAuto || ignoreOverlayScrollbarHidingChanged) { + contentElementCSS[_strMarginMinus + isRTLRight] = contentElementCSS[_strBorderMinus + isRTLRight] = _strEmpty; + setContentElementCSS = function (horizontal) { + var scrollbarVars = getScrollbarVars(horizontal); + var scrollbarVarsInverted = getScrollbarVars(!horizontal); + var xy = scrollbarVars._x_y; + var strDirection = horizontal ? _strBottom : isRTLLeft; + var invertedAutoSize = horizontal ? heightAuto : widthAuto; + + if (_nativeScrollbarIsOverlaid[xy] && hasOverflow[xy] && hideOverflow[xy + 's']) { + contentElementCSS[_strMarginMinus + strDirection] = invertedAutoSize ? (ignoreOverlayScrollbarHiding ? _strEmpty : _overlayScrollbarDummySize[xy]) : _strEmpty; + contentElementCSS[_strBorderMinus + strDirection] = ((horizontal ? !invertedAutoSize : true) && !ignoreOverlayScrollbarHiding) ? (_overlayScrollbarDummySize[xy] + borderDesign) : _strEmpty; + } + else { + arrangeContent[scrollbarVarsInverted._w_h] = + contentElementCSS[_strMarginMinus + strDirection] = + contentElementCSS[_strBorderMinus + strDirection] = _strEmpty; + arrangeChanged = true; + } + }; + + if (_nativeScrollbarStyling) { + addRemoveClass(_viewportElement, _classNameViewportNativeScrollbarsInvisible, !ignoreOverlayScrollbarHiding) + } + else { + setContentElementCSS(true); + setContentElementCSS(false); + } + } + if (ignoreOverlayScrollbarHiding) { + arrangeContent.w = arrangeContent.h = _strEmpty; + arrangeChanged = true; + } + if (arrangeChanged && !_nativeScrollbarStyling) { + contentArrangeElementCSS[_strWidth] = hideOverflow.y ? arrangeContent.w : _strEmpty; + contentArrangeElementCSS[_strHeight] = hideOverflow.x ? arrangeContent.h : _strEmpty; + + if (!_contentArrangeElement) { + _contentArrangeElement = FRAMEWORK(generateDiv(_classNameContentArrangeElement)); + _viewportElement.prepend(_contentArrangeElement); + } + _contentArrangeElement.css(contentArrangeElementCSS); + } + _contentElement.css(contentElementCSS); + } + + var viewportElementCSS = {}; + var paddingElementCSS = {}; + var setViewportCSS; + if (hostSizeChanged || hasOverflow.c || hideOverflow.c || contentScrollSize.c || overflowBehaviorChanged || boxSizingChanged || ignoreOverlayScrollbarHidingChanged || cssDirectionChanged || clipAlwaysChanged || heightAutoChanged) { + viewportElementCSS[isRTLRight] = _strEmpty; + setViewportCSS = function (horizontal) { + var scrollbarVars = getScrollbarVars(horizontal); + var scrollbarVarsInverted = getScrollbarVars(!horizontal); + var xy = scrollbarVars._x_y; + var XY = scrollbarVars._X_Y; + var strDirection = horizontal ? _strBottom : isRTLLeft; + + var reset = function () { + viewportElementCSS[strDirection] = _strEmpty; + _contentBorderSize[scrollbarVarsInverted._w_h] = 0; + }; + if (hasOverflow[xy] && hideOverflow[xy + 's']) { + viewportElementCSS[strOverflow + XY] = _strScroll; + if (ignoreOverlayScrollbarHiding || _nativeScrollbarStyling) { + reset(); + } + else { + viewportElementCSS[strDirection] = -(_nativeScrollbarIsOverlaid[xy] ? _overlayScrollbarDummySize[xy] : _nativeScrollbarSize[xy]); + _contentBorderSize[scrollbarVarsInverted._w_h] = _nativeScrollbarIsOverlaid[xy] ? _overlayScrollbarDummySize[scrollbarVarsInverted._x_y] : 0; + } + } else { + viewportElementCSS[strOverflow + XY] = _strEmpty; + reset(); + } + }; + setViewportCSS(true); + setViewportCSS(false); + + // if the scroll container is too small and if there is any overflow with no overlay scrollbar (and scrollbar styling isn't possible), + // make viewport element greater in size (Firefox hide Scrollbars fix) + // because firefox starts hiding scrollbars on too small elements + // with this behavior the overflow calculation may be incorrect or the scrollbars would appear suddenly + // https://bugzilla.mozilla.org/show_bug.cgi?id=292284 + if (!_nativeScrollbarStyling + && (_viewportSize.h < _nativeScrollbarMinSize.x || _viewportSize.w < _nativeScrollbarMinSize.y) + && ((hasOverflow.x && hideOverflow.x && !_nativeScrollbarIsOverlaid.x) || (hasOverflow.y && hideOverflow.y && !_nativeScrollbarIsOverlaid.y))) { + viewportElementCSS[_strPaddingMinus + _strTop] = _nativeScrollbarMinSize.x; + viewportElementCSS[_strMarginMinus + _strTop] = -_nativeScrollbarMinSize.x; + + viewportElementCSS[_strPaddingMinus + isRTLRight] = _nativeScrollbarMinSize.y; + viewportElementCSS[_strMarginMinus + isRTLRight] = -_nativeScrollbarMinSize.y; + } + else { + viewportElementCSS[_strPaddingMinus + _strTop] = + viewportElementCSS[_strMarginMinus + _strTop] = + viewportElementCSS[_strPaddingMinus + isRTLRight] = + viewportElementCSS[_strMarginMinus + isRTLRight] = _strEmpty; + } + viewportElementCSS[_strPaddingMinus + isRTLLeft] = + viewportElementCSS[_strMarginMinus + isRTLLeft] = _strEmpty; + + //if there is any overflow (x OR y axis) and this overflow shall be hidden, make overflow hidden, else overflow visible + if ((hasOverflow.x && hideOverflow.x) || (hasOverflow.y && hideOverflow.y) || hideOverflowForceTextarea) { + //only hide if is Textarea + if (_isTextarea && hideOverflowForceTextarea) { + paddingElementCSS[strOverflowX] = + paddingElementCSS[strOverflowY] = strHidden; + } + } + else { + if (!clipAlways || (overflowBehaviorIsVH.x || overflowBehaviorIsVS.x || overflowBehaviorIsVH.y || overflowBehaviorIsVS.y)) { + //only un-hide if Textarea + if (_isTextarea) { + paddingElementCSS[strOverflowX] = + paddingElementCSS[strOverflowY] = _strEmpty; + } + viewportElementCSS[strOverflowX] = + viewportElementCSS[strOverflowY] = strVisible; + } + } + + _paddingElement.css(paddingElementCSS); + _viewportElement.css(viewportElementCSS); + viewportElementCSS = {}; + + //force soft redraw in webkit because without the scrollbars will may appear because DOM wont be redrawn under special conditions + if ((hasOverflow.c || boxSizingChanged || widthAutoChanged || heightAutoChanged) && !(_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y)) { + var elementStyle = _contentElementNative[LEXICON.s]; + var dump; + elementStyle.webkitTransform = 'scale(1)'; + elementStyle.display = 'run-in'; + dump = _contentElementNative[LEXICON.oH]; + elementStyle.display = _strEmpty; //|| dump; //use dump to prevent it from deletion if minify + elementStyle.webkitTransform = _strEmpty; + } + /* + //force hard redraw in webkit if native overlaid scrollbars shall appear + if (ignoreOverlayScrollbarHidingChanged && ignoreOverlayScrollbarHiding) { + _hostElement.hide(); + var dump = _hostElementNative[LEXICON.oH]; + _hostElement.show(); + } + */ + } + + //change to direction RTL and width auto Bugfix in Webkit + //without this fix, the DOM still thinks the scrollbar is LTR and thus the content is shifted to the left + contentElementCSS = {}; + if (cssDirectionChanged || widthAutoChanged || heightAutoChanged) { + if (_isRTL && widthAuto) { + var floatTmp = _contentElement.css(_strFloat); + var posLeftWithoutFloat = MATH.round(_contentElement.css(_strFloat, _strEmpty).css(_strLeft, _strEmpty).position().left); + _contentElement.css(_strFloat, floatTmp); + var posLeftWithFloat = MATH.round(_contentElement.position().left); + + if (posLeftWithoutFloat !== posLeftWithFloat) + contentElementCSS[_strLeft] = posLeftWithoutFloat; + } + else { + contentElementCSS[_strLeft] = _strEmpty; + } + } + _contentElement.css(contentElementCSS); + + //handle scroll position + if (_isTextarea && contentSizeChanged) { + var textareaInfo = getTextareaInfo(); + if (textareaInfo) { + var textareaRowsChanged = _textareaInfoCache === undefined ? true : textareaInfo._rows !== _textareaInfoCache._rows; + var cursorRow = textareaInfo._cursorRow; + var cursorCol = textareaInfo._cursorColumn; + var widestRow = textareaInfo._widestRow; + var lastRow = textareaInfo._rows; + var lastCol = textareaInfo._columns; + var cursorPos = textareaInfo._cursorPosition; + var cursorMax = textareaInfo._cursorMax; + var cursorIsLastPosition = (cursorPos >= cursorMax && _textareaHasFocus); + var textareaScrollAmount = { + x: (!textareaAutoWrapping && (cursorCol === lastCol && cursorRow === widestRow)) ? _overflowAmountCache.x : -1, + y: (textareaAutoWrapping ? cursorIsLastPosition || textareaRowsChanged && (previousOverflowAmount ? (currScroll.y === previousOverflowAmount.y) : false) : (cursorIsLastPosition || textareaRowsChanged) && cursorRow === lastRow) ? _overflowAmountCache.y : -1 + }; + currScroll.x = textareaScrollAmount.x > -1 ? (_isRTL && _normalizeRTLCache && _rtlScrollBehavior.i ? 0 : textareaScrollAmount.x) : currScroll.x; //if inverted, scroll to 0 -> normalized this means to max scroll offset. + currScroll.y = textareaScrollAmount.y > -1 ? textareaScrollAmount.y : currScroll.y; + } + _textareaInfoCache = textareaInfo; + } + if (_isRTL && _rtlScrollBehavior.i && _nativeScrollbarIsOverlaid.y && hasOverflow.x && _normalizeRTLCache) + currScroll.x += _contentBorderSize.w || 0; + if (widthAuto) + _hostElement[_strScrollLeft](0); + if (heightAuto) + _hostElement[_strScrollTop](0); + _viewportElement[_strScrollLeft](currScroll.x)[_strScrollTop](currScroll.y); + + //scrollbars management: + var scrollbarsVisibilityVisible = scrollbarsVisibility === 'v'; + var scrollbarsVisibilityHidden = scrollbarsVisibility === 'h'; + var scrollbarsVisibilityAuto = scrollbarsVisibility === 'a'; + var refreshScrollbarsVisibility = function (showX, showY) { + showY = showY === undefined ? showX : showY; + refreshScrollbarAppearance(true, showX, canScroll.x) + refreshScrollbarAppearance(false, showY, canScroll.y) + }; + + //manage class name which indicates scrollable overflow + addRemoveClass(_hostElement, _classNameHostOverflow, hideOverflow.x || hideOverflow.y); + addRemoveClass(_hostElement, _classNameHostOverflowX, hideOverflow.x); + addRemoveClass(_hostElement, _classNameHostOverflowY, hideOverflow.y); + + //add or remove rtl class name for styling purposes except when its body, then the scrollbar stays + if (cssDirectionChanged && !_isBody) { + addRemoveClass(_hostElement, _classNameHostRTL, _isRTL); + } + + //manage the resize feature (CSS3 resize "polyfill" for this plugin) + if (_isBody) + addClass(_hostElement, _classNameHostResizeDisabled); + if (resizeChanged) { + addRemoveClass(_hostElement, _classNameHostResizeDisabled, _resizeNone); + addRemoveClass(_scrollbarCornerElement, _classNameScrollbarCornerResize, !_resizeNone); + addRemoveClass(_scrollbarCornerElement, _classNameScrollbarCornerResizeB, _resizeBoth); + addRemoveClass(_scrollbarCornerElement, _classNameScrollbarCornerResizeH, _resizeHorizontal); + addRemoveClass(_scrollbarCornerElement, _classNameScrollbarCornerResizeV, _resizeVertical); + } + + //manage the scrollbars general visibility + the scrollbar interactivity (unusable class name) + if (scrollbarsVisibilityChanged || overflowBehaviorChanged || hideOverflow.c || hasOverflow.c || ignoreOverlayScrollbarHidingChanged) { + if (ignoreOverlayScrollbarHiding) { + if (ignoreOverlayScrollbarHidingChanged) { + removeClass(_hostElement, _classNameHostScrolling); + if (ignoreOverlayScrollbarHiding) { + refreshScrollbarsVisibility(false); + } + } + } + else if (scrollbarsVisibilityAuto) { + refreshScrollbarsVisibility(canScroll.x, canScroll.y); + } + else if (scrollbarsVisibilityVisible) { + refreshScrollbarsVisibility(true); + } + else if (scrollbarsVisibilityHidden) { + refreshScrollbarsVisibility(false); + } + } + + //manage the scrollbars auto hide feature (auto hide them after specific actions) + if (scrollbarsAutoHideChanged || ignoreOverlayScrollbarHidingChanged) { + setupHostMouseTouchEvents(!_scrollbarsAutoHideLeave && !_scrollbarsAutoHideMove); + refreshScrollbarsAutoHide(_scrollbarsAutoHideNever, !_scrollbarsAutoHideNever); + } + + //manage scrollbars handle length & offset - don't remove! + if (hostSizeChanged || overflowAmount.c || heightAutoChanged || widthAutoChanged || resizeChanged || boxSizingChanged || paddingAbsoluteChanged || ignoreOverlayScrollbarHidingChanged || cssDirectionChanged) { + refreshScrollbarHandleLength(true); + refreshScrollbarHandleOffset(true); + refreshScrollbarHandleLength(false); + refreshScrollbarHandleOffset(false); + } + + //manage interactivity + if (scrollbarsClickScrollingChanged) + refreshScrollbarsInteractive(true, scrollbarsClickScrolling); + if (scrollbarsDragScrollingChanged) + refreshScrollbarsInteractive(false, scrollbarsDragScrolling); + + //callbacks: + dispatchCallback('onDirectionChanged', { + isRTL: _isRTL, + dir: cssDirection + }, cssDirectionChanged); + dispatchCallback('onHostSizeChanged', { + width: _hostSizeCache.w, + height: _hostSizeCache.h + }, hostSizeChanged); + dispatchCallback('onContentSizeChanged', { + width: _contentScrollSizeCache.w, + height: _contentScrollSizeCache.h + }, contentSizeChanged); + dispatchCallback('onOverflowChanged', { + x: hasOverflow.x, + y: hasOverflow.y, + xScrollable: hideOverflow.xs, + yScrollable: hideOverflow.ys, + clipped: hideOverflow.x || hideOverflow.y + }, hasOverflow.c || hideOverflow.c); + dispatchCallback('onOverflowAmountChanged', { + x: overflowAmount.x, + y: overflowAmount.y + }, overflowAmount.c); + } + + //fix body min size + if (_isBody && _bodyMinSizeCache && (_hasOverflowCache.c || _bodyMinSizeCache.c)) { + //its possible that no min size was measured until now, because the content arrange element was just added now, in this case, measure now the min size. + if (!_bodyMinSizeCache.f) + bodyMinSizeChanged(); + if (_nativeScrollbarIsOverlaid.y && _hasOverflowCache.x) + _contentElement.css(_strMinMinus + _strWidth, _bodyMinSizeCache.w + _overlayScrollbarDummySize.y); + if (_nativeScrollbarIsOverlaid.x && _hasOverflowCache.y) + _contentElement.css(_strMinMinus + _strHeight, _bodyMinSizeCache.h + _overlayScrollbarDummySize.x); + _bodyMinSizeCache.c = false; + } + + if (_initialized && changedOptions.updateOnLoad) { + updateElementsOnLoad(); + } + + //freezeResizeObserver(_sizeObserverElement, false); + //freezeResizeObserver(_sizeAutoObserverElement, false); + + dispatchCallback('onUpdated', { forced: force }); + } + + /** + * Updates the found elements of which the load event shall be handled. + */ + function updateElementsOnLoad() { + if (!_isTextarea) { + eachUpdateOnLoad(function (i, updateOnLoadSelector) { + _contentElement.find(updateOnLoadSelector).each(function (i, el) { + // if element doesn't have a updateOnLoadCallback applied + if (COMPATIBILITY.inA(el, _updateOnLoadElms) < 0) { + _updateOnLoadElms.push(el); + FRAMEWORK(el) + .off(_updateOnLoadEventName, updateOnLoadCallback) + .on(_updateOnLoadEventName, updateOnLoadCallback); + } + }); + }); + } + } + + //==== Options ====// + + /** + * Sets new options but doesn't call the update method. + * @param newOptions The object which contains the new options. + * @returns {*} A object which contains the changed options. + */ + function setOptions(newOptions) { + var validatedOpts = _pluginsOptions._validate(newOptions, _pluginsOptions._template, true, _currentOptions) + + _currentOptions = extendDeep({}, _currentOptions, validatedOpts._default); + _currentPreparedOptions = extendDeep({}, _currentPreparedOptions, validatedOpts._prepared); + + return validatedOpts._prepared; + } + + + //==== Structure ====// + + /** + * Builds or destroys the wrapper and helper DOM elements. + * @param destroy Indicates whether the DOM shall be build or destroyed. + */ + /** + * Builds or destroys the wrapper and helper DOM elements. + * @param destroy Indicates whether the DOM shall be build or destroyed. + */ + function setupStructureDOM(destroy) { + var strParent = 'parent'; + var classNameResizeObserverHost = 'os-resize-observer-host'; + var classNameTextareaElementFull = _classNameTextareaElement + _strSpace + _classNameTextInherit; + var textareaClass = _isTextarea ? _strSpace + _classNameTextInherit : _strEmpty; + var adoptAttrs = _currentPreparedOptions.textarea.inheritedAttrs; + var adoptAttrsMap = {}; + var applyAdoptedAttrs = function () { + var applyAdoptedAttrsElm = destroy ? _targetElement : _hostElement; + each(adoptAttrsMap, function (key, value) { + if (type(value) == TYPES.s) { + if (key == LEXICON.c) + applyAdoptedAttrsElm.addClass(value); + else + applyAdoptedAttrsElm.attr(key, value); + } + }); + }; + var hostElementClassNames = [ + _classNameHostElement, + _classNameHostElementForeign, + _classNameHostTextareaElement, + _classNameHostResizeDisabled, + _classNameHostRTL, + _classNameHostScrollbarHorizontalHidden, + _classNameHostScrollbarVerticalHidden, + _classNameHostTransition, + _classNameHostScrolling, + _classNameHostOverflow, + _classNameHostOverflowX, + _classNameHostOverflowY, + _classNameThemeNone, + _classNameTextareaElement, + _classNameTextInherit, + _classNameCache].join(_strSpace); + var hostElementCSS = {}; + + //get host element as first element, because that's the most upper element and required for the other elements + _hostElement = _hostElement || (_isTextarea ? (_domExists ? _targetElement[strParent]()[strParent]()[strParent]()[strParent]() : FRAMEWORK(generateDiv(_classNameHostTextareaElement))) : _targetElement); + _contentElement = _contentElement || selectOrGenerateDivByClass(_classNameContentElement + textareaClass); + _viewportElement = _viewportElement || selectOrGenerateDivByClass(_classNameViewportElement + textareaClass); + _paddingElement = _paddingElement || selectOrGenerateDivByClass(_classNamePaddingElement + textareaClass); + _sizeObserverElement = _sizeObserverElement || selectOrGenerateDivByClass(classNameResizeObserverHost); + _textareaCoverElement = _textareaCoverElement || (_isTextarea ? selectOrGenerateDivByClass(_classNameTextareaCoverElement) : undefined); + + //add this class to workaround class changing issues with UI frameworks especially Vue + if (_domExists) + addClass(_hostElement, _classNameHostElementForeign); + + //on destroy, remove all generated class names from the host element before collecting the adopted attributes + //to prevent adopting generated class names + if (destroy) + removeClass(_hostElement, hostElementClassNames); + + //collect all adopted attributes + adoptAttrs = type(adoptAttrs) == TYPES.s ? adoptAttrs.split(_strSpace) : adoptAttrs; + if (COMPATIBILITY.isA(adoptAttrs) && _isTextarea) { + each(adoptAttrs, function (i, v) { + if (type(v) == TYPES.s) { + adoptAttrsMap[v] = destroy ? _hostElement.attr(v) : _targetElement.attr(v); + } + }); + } + + if (!destroy) { + if (_isTextarea) { + if (!_currentPreparedOptions.sizeAutoCapable) { + hostElementCSS[_strWidth] = _targetElement.css(_strWidth); + hostElementCSS[_strHeight] = _targetElement.css(_strHeight); + } + + if (!_domExists) + _targetElement.addClass(_classNameTextInherit).wrap(_hostElement); + + //jQuery clones elements in wrap functions, so we have to select them again + _hostElement = _targetElement[strParent]().css(hostElementCSS); + } + + if (!_domExists) { + //add the correct class to the target element + addClass(_targetElement, _isTextarea ? classNameTextareaElementFull : _classNameHostElement); + + //wrap the content into the generated elements to create the required DOM + _hostElement.wrapInner(_contentElement) + .wrapInner(_viewportElement) + .wrapInner(_paddingElement) + .prepend(_sizeObserverElement); + + //jQuery clones elements in wrap functions, so we have to select them again + _contentElement = findFirst(_hostElement, _strDot + _classNameContentElement); + _viewportElement = findFirst(_hostElement, _strDot + _classNameViewportElement); + _paddingElement = findFirst(_hostElement, _strDot + _classNamePaddingElement); + + if (_isTextarea) { + _contentElement.prepend(_textareaCoverElement); + applyAdoptedAttrs(); + } + } + + if (_nativeScrollbarStyling) + addClass(_viewportElement, _classNameViewportNativeScrollbarsInvisible); + if (_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y) + addClass(_viewportElement, _classNameViewportNativeScrollbarsOverlaid); + if (_isBody) + addClass(_htmlElement, _classNameHTMLElement); + + _sizeObserverElementNative = _sizeObserverElement[0]; + _hostElementNative = _hostElement[0]; + _paddingElementNative = _paddingElement[0]; + _viewportElementNative = _viewportElement[0]; + _contentElementNative = _contentElement[0]; + + updateViewportAttrsFromTarget(); + } + else { + if (_domExists && _initialized) { + //clear size observer + _sizeObserverElement.children().remove(); + + //remove the style property and classes from already generated elements + each([_paddingElement, _viewportElement, _contentElement, _textareaCoverElement], function (i, elm) { + if (elm) { + removeClass(elm.removeAttr(LEXICON.s), _classNamesDynamicDestroy); + } + }); + + //add classes to the host element which was removed previously to match the expected DOM + addClass(_hostElement, _isTextarea ? _classNameHostTextareaElement : _classNameHostElement); + } + else { + //remove size observer + remove(_sizeObserverElement); + + //unwrap the content to restore DOM + _contentElement.contents() + .unwrap() + .unwrap() + .unwrap(); + + if (_isTextarea) { + _targetElement.unwrap(); + remove(_hostElement); + remove(_textareaCoverElement); + applyAdoptedAttrs(); + } + } + + if (_isTextarea) + _targetElement.removeAttr(LEXICON.s); + + if (_isBody) + removeClass(_htmlElement, _classNameHTMLElement); + } + } + + /** + * Adds or removes all wrapper elements interactivity events. + * @param destroy Indicates whether the Events shall be added or removed. + */ + function setupStructureEvents() { + var textareaKeyDownRestrictedKeyCodes = [ + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 123, //F1 to F12 + 33, 34, //page up, page down + 37, 38, 39, 40, //left, up, right, down arrows + 16, 17, 18, 19, 20, 144 //Shift, Ctrl, Alt, Pause, CapsLock, NumLock + ]; + var textareaKeyDownKeyCodesList = []; + var textareaUpdateIntervalID; + var scrollStopTimeoutId; + var scrollStopDelay = 175; + var strFocus = 'focus'; + + function updateTextarea(doClearInterval) { + textareaUpdate(); + _base.update(_strAuto); + if (doClearInterval && _autoUpdateRecommended) + clearInterval(textareaUpdateIntervalID); + } + function textareaOnScroll(event) { + _targetElement[_strScrollLeft](_rtlScrollBehavior.i && _normalizeRTLCache ? 9999999 : 0); + _targetElement[_strScrollTop](0); + COMPATIBILITY.prvD(event); + COMPATIBILITY.stpP(event); + return false; + } + function textareaOnDrop(event) { + setTimeout(function () { + if (!_destroyed) + updateTextarea(); + }, 50); + } + function textareaOnFocus() { + _textareaHasFocus = true; + addClass(_hostElement, strFocus); + } + function textareaOnFocusout() { + _textareaHasFocus = false; + textareaKeyDownKeyCodesList = []; + removeClass(_hostElement, strFocus); + updateTextarea(true); + } + function textareaOnKeyDown(event) { + var keyCode = event.keyCode; + + if (inArray(keyCode, textareaKeyDownRestrictedKeyCodes) < 0) { + if (!textareaKeyDownKeyCodesList[LEXICON.l]) { + updateTextarea(); + textareaUpdateIntervalID = setInterval(updateTextarea, 1000 / 60); + } + if (inArray(keyCode, textareaKeyDownKeyCodesList) < 0) + textareaKeyDownKeyCodesList.push(keyCode); + } + } + function textareaOnKeyUp(event) { + var keyCode = event.keyCode; + var index = inArray(keyCode, textareaKeyDownKeyCodesList); + + if (inArray(keyCode, textareaKeyDownRestrictedKeyCodes) < 0) { + if (index > -1) + textareaKeyDownKeyCodesList.splice(index, 1); + if (!textareaKeyDownKeyCodesList[LEXICON.l]) + updateTextarea(true); + } + } + function contentOnTransitionEnd(event) { + if (_autoUpdateCache === true) + return; + event = event.originalEvent || event; + if (isSizeAffectingCSSProperty(event.propertyName)) + _base.update(_strAuto); + } + function viewportOnScroll(event) { + if (!_sleeping) { + if (scrollStopTimeoutId !== undefined) + clearTimeout(scrollStopTimeoutId); + else { + if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) + refreshScrollbarsAutoHide(true); + + if (!nativeOverlayScrollbarsAreActive()) + addClass(_hostElement, _classNameHostScrolling); + + dispatchCallback('onScrollStart', event); + } + + //if a scrollbars handle gets dragged, the mousemove event is responsible for refreshing the handle offset + //because if CSS scroll-snap is used, the handle offset gets only refreshed on every snap point + //this looks laggy & clunky, it looks much better if the offset refreshes with the mousemove + if (!_scrollbarsHandlesDefineScrollPos) { + refreshScrollbarHandleOffset(true); + refreshScrollbarHandleOffset(false); + } + dispatchCallback('onScroll', event); + + scrollStopTimeoutId = setTimeout(function () { + if (!_destroyed) { + //OnScrollStop: + clearTimeout(scrollStopTimeoutId); + scrollStopTimeoutId = undefined; + + if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) + refreshScrollbarsAutoHide(false); + + if (!nativeOverlayScrollbarsAreActive()) + removeClass(_hostElement, _classNameHostScrolling); + + dispatchCallback('onScrollStop', event); + } + }, scrollStopDelay); + } + } + + + if (_isTextarea) { + if (_msieVersion > 9 || !_autoUpdateRecommended) { + addDestroyEventListener(_targetElement, 'input', updateTextarea); + } + else { + addDestroyEventListener(_targetElement, + [_strKeyDownEvent, _strKeyUpEvent], + [textareaOnKeyDown, textareaOnKeyUp]); + } + + addDestroyEventListener(_targetElement, + [_strScroll, 'drop', strFocus, strFocus + 'out'], + [textareaOnScroll, textareaOnDrop, textareaOnFocus, textareaOnFocusout]); + } + else { + addDestroyEventListener(_contentElement, _strTransitionEndEvent, contentOnTransitionEnd); + } + addDestroyEventListener(_viewportElement, _strScroll, viewportOnScroll, true); + } + + + //==== Scrollbars ====// + + /** + * Builds or destroys all scrollbar DOM elements (scrollbar, track, handle) + * @param destroy Indicates whether the DOM shall be build or destroyed. + */ + function setupScrollbarsDOM(destroy) { + var selectOrGenerateScrollbarDOM = function (isHorizontal) { + var scrollbarClassName = isHorizontal ? _classNameScrollbarHorizontal : _classNameScrollbarVertical; + var scrollbar = selectOrGenerateDivByClass(_classNameScrollbar + _strSpace + scrollbarClassName, true); + var track = selectOrGenerateDivByClass(_classNameScrollbarTrack, scrollbar); + var handle = selectOrGenerateDivByClass(_classNameScrollbarHandle, scrollbar); + + if (!_domExists && !destroy) { + scrollbar.append(track); + track.append(handle); + } + + return { + _scrollbar: scrollbar, + _track: track, + _handle: handle + }; + }; + function resetScrollbarDOM(isHorizontal) { + var scrollbarVars = getScrollbarVars(isHorizontal); + var scrollbar = scrollbarVars._scrollbar; + var track = scrollbarVars._track; + var handle = scrollbarVars._handle; + + if (_domExists && _initialized) { + each([scrollbar, track, handle], function (i, elm) { + removeClass(elm.removeAttr(LEXICON.s), _classNamesDynamicDestroy); + }); + } + else { + remove(scrollbar || selectOrGenerateScrollbarDOM(isHorizontal)._scrollbar); + } + } + var horizontalElements; + var verticalElements; + + if (!destroy) { + horizontalElements = selectOrGenerateScrollbarDOM(true); + verticalElements = selectOrGenerateScrollbarDOM(); + + _scrollbarHorizontalElement = horizontalElements._scrollbar; + _scrollbarHorizontalTrackElement = horizontalElements._track; + _scrollbarHorizontalHandleElement = horizontalElements._handle; + _scrollbarVerticalElement = verticalElements._scrollbar; + _scrollbarVerticalTrackElement = verticalElements._track; + _scrollbarVerticalHandleElement = verticalElements._handle; + + if (!_domExists) { + _paddingElement.after(_scrollbarVerticalElement); + _paddingElement.after(_scrollbarHorizontalElement); + } + } + else { + resetScrollbarDOM(true); + resetScrollbarDOM(); + } + } + + /** + * Initializes all scrollbar interactivity events. (track and handle dragging, clicking, scrolling) + * @param isHorizontal True if the target scrollbar is the horizontal scrollbar, false if the target scrollbar is the vertical scrollbar. + */ + function setupScrollbarEvents(isHorizontal) { + var scrollbarVars = getScrollbarVars(isHorizontal); + var scrollbarVarsInfo = scrollbarVars._info; + var insideIFrame = _windowElementNative.top !== _windowElementNative; + var xy = scrollbarVars._x_y; + var XY = scrollbarVars._X_Y; + var scroll = _strScroll + scrollbarVars._Left_Top; + var strActive = 'active'; + var strSnapHandle = 'snapHandle'; + var strClickEvent = 'click'; + var scrollDurationFactor = 1; + var increaseDecreaseScrollAmountKeyCodes = [16, 17]; //shift, ctrl + var trackTimeout; + var mouseDownScroll; + var mouseDownOffset; + var mouseDownInvertedScale; + + function getPointerPosition(event) { + return _msieVersion && insideIFrame ? event['screen' + XY] : COMPATIBILITY.page(event)[xy]; //use screen coordinates in EDGE & IE because the page values are incorrect in frames. + } + function getPreparedScrollbarsOption(name) { + return _currentPreparedOptions.scrollbars[name]; + } + function increaseTrackScrollAmount() { + scrollDurationFactor = 0.5; + } + function decreaseTrackScrollAmount() { + scrollDurationFactor = 1; + } + function stopClickEventPropagation(event) { + COMPATIBILITY.stpP(event); + } + function documentKeyDown(event) { + if (inArray(event.keyCode, increaseDecreaseScrollAmountKeyCodes) > -1) + increaseTrackScrollAmount(); + } + function documentKeyUp(event) { + if (inArray(event.keyCode, increaseDecreaseScrollAmountKeyCodes) > -1) + decreaseTrackScrollAmount(); + } + function onMouseTouchDownContinue(event) { + var originalEvent = event.originalEvent || event; + var isTouchEvent = originalEvent.touches !== undefined; + return _sleeping || _destroyed || nativeOverlayScrollbarsAreActive() || !_scrollbarsDragScrollingCache || (isTouchEvent && !getPreparedScrollbarsOption('touchSupport')) ? false : COMPATIBILITY.mBtn(event) === 1 || isTouchEvent; + } + function documentDragMove(event) { + if (onMouseTouchDownContinue(event)) { + var trackLength = scrollbarVarsInfo._trackLength; + var handleLength = scrollbarVarsInfo._handleLength; + var scrollRange = scrollbarVarsInfo._maxScroll; + var scrollRaw = (getPointerPosition(event) - mouseDownOffset) * mouseDownInvertedScale; + var scrollDeltaPercent = scrollRaw / (trackLength - handleLength); + var scrollDelta = (scrollRange * scrollDeltaPercent); + scrollDelta = isFinite(scrollDelta) ? scrollDelta : 0; + if (_isRTL && isHorizontal && !_rtlScrollBehavior.i) + scrollDelta *= -1; + + _viewportElement[scroll](MATH.round(mouseDownScroll + scrollDelta)); + + if (_scrollbarsHandlesDefineScrollPos) + refreshScrollbarHandleOffset(isHorizontal, mouseDownScroll + scrollDelta); + + if (!_supportPassiveEvents) + COMPATIBILITY.prvD(event); + } + else + documentMouseTouchUp(event); + } + function documentMouseTouchUp(event) { + event = event || event.originalEvent; + + setupResponsiveEventListener(_documentElement, + [_strMouseTouchMoveEvent, _strMouseTouchUpEvent, _strKeyDownEvent, _strKeyUpEvent, _strSelectStartEvent], + [documentDragMove, documentMouseTouchUp, documentKeyDown, documentKeyUp, documentOnSelectStart], + true); + COMPATIBILITY.rAF()(function() { + setupResponsiveEventListener(_documentElement, strClickEvent, stopClickEventPropagation, true, { _capture: true }); + }); + + + if (_scrollbarsHandlesDefineScrollPos) + refreshScrollbarHandleOffset(isHorizontal, true); + + _scrollbarsHandlesDefineScrollPos = false; + removeClass(_bodyElement, _classNameDragging); + removeClass(scrollbarVars._handle, strActive); + removeClass(scrollbarVars._track, strActive); + removeClass(scrollbarVars._scrollbar, strActive); + + mouseDownScroll = undefined; + mouseDownOffset = undefined; + mouseDownInvertedScale = 1; + + decreaseTrackScrollAmount(); + + if (trackTimeout !== undefined) { + _base.scrollStop(); + clearTimeout(trackTimeout); + trackTimeout = undefined; + } + + if (event) { + var rect = _hostElementNative[LEXICON.bCR](); + var mouseInsideHost = event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom; + + //if mouse is outside host element + if (!mouseInsideHost) + hostOnMouseLeave(); + + if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) + refreshScrollbarsAutoHide(false); + } + } + function onHandleMouseTouchDown(event) { + if (onMouseTouchDownContinue(event)) + onHandleMouseTouchDownAction(event); + } + function onHandleMouseTouchDownAction(event) { + mouseDownScroll = _viewportElement[scroll](); + mouseDownScroll = isNaN(mouseDownScroll) ? 0 : mouseDownScroll; + if (_isRTL && isHorizontal && !_rtlScrollBehavior.n || !_isRTL) + mouseDownScroll = mouseDownScroll < 0 ? 0 : mouseDownScroll; + + mouseDownInvertedScale = getHostElementInvertedScale()[xy]; + mouseDownOffset = getPointerPosition(event); + + _scrollbarsHandlesDefineScrollPos = !getPreparedScrollbarsOption(strSnapHandle); + addClass(_bodyElement, _classNameDragging); + addClass(scrollbarVars._handle, strActive); + addClass(scrollbarVars._scrollbar, strActive); + + setupResponsiveEventListener(_documentElement, + [_strMouseTouchMoveEvent, _strMouseTouchUpEvent, _strSelectStartEvent], + [documentDragMove, documentMouseTouchUp, documentOnSelectStart]); + COMPATIBILITY.rAF()(function() { + setupResponsiveEventListener(_documentElement, strClickEvent, stopClickEventPropagation, false, { _capture: true }); + }); + + + if (_msieVersion || !_documentMixed) + COMPATIBILITY.prvD(event); + COMPATIBILITY.stpP(event); + } + function onTrackMouseTouchDown(event) { + if (onMouseTouchDownContinue(event)) { + var handleToViewportRatio = scrollbarVars._info._handleLength / Math.round(MATH.min(1, _viewportSize[scrollbarVars._w_h] / _contentScrollSizeCache[scrollbarVars._w_h]) * scrollbarVars._info._trackLength); + var scrollDistance = MATH.round(_viewportSize[scrollbarVars._w_h] * handleToViewportRatio); + var scrollBaseDuration = 270 * handleToViewportRatio; + var scrollFirstIterationDelay = 400 * handleToViewportRatio; + var trackOffset = scrollbarVars._track.offset()[scrollbarVars._left_top]; + var ctrlKey = event.ctrlKey; + var instantScroll = event.shiftKey; + var instantScrollTransition = instantScroll && ctrlKey; + var isFirstIteration = true; + var easing = 'linear'; + var decreaseScroll; + var finishedCondition; + var scrollActionFinsished = function (transition) { + if (_scrollbarsHandlesDefineScrollPos) + refreshScrollbarHandleOffset(isHorizontal, transition); + }; + var scrollActionInstantFinished = function () { + scrollActionFinsished(); + onHandleMouseTouchDownAction(event); + }; + var scrollAction = function () { + if (!_destroyed) { + var mouseOffset = (mouseDownOffset - trackOffset) * mouseDownInvertedScale; + var handleOffset = scrollbarVarsInfo._handleOffset; + var trackLength = scrollbarVarsInfo._trackLength; + var handleLength = scrollbarVarsInfo._handleLength; + var scrollRange = scrollbarVarsInfo._maxScroll; + var currScroll = scrollbarVarsInfo._currentScroll; + var scrollDuration = scrollBaseDuration * scrollDurationFactor; + var timeoutDelay = isFirstIteration ? MATH.max(scrollFirstIterationDelay, scrollDuration) : scrollDuration; + var instantScrollPosition = scrollRange * ((mouseOffset - (handleLength / 2)) / (trackLength - handleLength)); // 100% * positionPercent + var rtlIsNormal = _isRTL && isHorizontal && ((!_rtlScrollBehavior.i && !_rtlScrollBehavior.n) || _normalizeRTLCache); + var decreaseScrollCondition = rtlIsNormal ? handleOffset < mouseOffset : handleOffset > mouseOffset; + var scrollObj = {}; + var animationObj = { + easing: easing, + step: function (now) { + if (_scrollbarsHandlesDefineScrollPos) { + _viewportElement[scroll](now); //https://github.com/jquery/jquery/issues/4340 + refreshScrollbarHandleOffset(isHorizontal, now); + } + } + }; + instantScrollPosition = isFinite(instantScrollPosition) ? instantScrollPosition : 0; + instantScrollPosition = _isRTL && isHorizontal && !_rtlScrollBehavior.i ? (scrollRange - instantScrollPosition) : instantScrollPosition; + + //_base.scrollStop(); + + if (instantScroll) { + _viewportElement[scroll](instantScrollPosition); //scroll instantly to new position + if (instantScrollTransition) { + //get the scroll position after instant scroll (in case CSS Snap Points are used) to get the correct snapped scroll position + //and the animation stops at the correct point + instantScrollPosition = _viewportElement[scroll](); + //scroll back to the position before instant scrolling so animation can be performed + _viewportElement[scroll](currScroll); + + instantScrollPosition = rtlIsNormal && _rtlScrollBehavior.i ? (scrollRange - instantScrollPosition) : instantScrollPosition; + instantScrollPosition = rtlIsNormal && _rtlScrollBehavior.n ? -instantScrollPosition : instantScrollPosition; + + scrollObj[xy] = instantScrollPosition; + _base.scroll(scrollObj, extendDeep(animationObj, { + duration: 130, + complete: scrollActionInstantFinished + })); + } + else + scrollActionInstantFinished(); + } + else { + decreaseScroll = isFirstIteration ? decreaseScrollCondition : decreaseScroll; + finishedCondition = rtlIsNormal + ? (decreaseScroll ? handleOffset + handleLength >= mouseOffset : handleOffset <= mouseOffset) + : (decreaseScroll ? handleOffset <= mouseOffset : handleOffset + handleLength >= mouseOffset); + + if (finishedCondition) { + clearTimeout(trackTimeout); + _base.scrollStop(); + trackTimeout = undefined; + scrollActionFinsished(true); + } + else { + trackTimeout = setTimeout(scrollAction, timeoutDelay); + + scrollObj[xy] = (decreaseScroll ? '-=' : '+=') + scrollDistance; + _base.scroll(scrollObj, extendDeep(animationObj, { + duration: scrollDuration + })); + } + isFirstIteration = false; + } + } + }; + if (ctrlKey) + increaseTrackScrollAmount(); + + mouseDownInvertedScale = getHostElementInvertedScale()[xy]; + mouseDownOffset = COMPATIBILITY.page(event)[xy]; + + _scrollbarsHandlesDefineScrollPos = !getPreparedScrollbarsOption(strSnapHandle); + addClass(_bodyElement, _classNameDragging); + addClass(scrollbarVars._track, strActive); + addClass(scrollbarVars._scrollbar, strActive); + + setupResponsiveEventListener(_documentElement, + [_strMouseTouchUpEvent, _strKeyDownEvent, _strKeyUpEvent, _strSelectStartEvent], + [documentMouseTouchUp, documentKeyDown, documentKeyUp, documentOnSelectStart]); + + scrollAction(); + COMPATIBILITY.prvD(event); + COMPATIBILITY.stpP(event); + } + } + function onTrackMouseTouchEnter(event) { + //make sure both scrollbars will stay visible if one scrollbar is hovered if autoHide is "scroll" or "move". + _scrollbarsHandleHovered = true; + if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) + refreshScrollbarsAutoHide(true); + } + function onTrackMouseTouchLeave(event) { + _scrollbarsHandleHovered = false; + if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) + refreshScrollbarsAutoHide(false); + } + function onScrollbarMouseTouchDown(event) { + COMPATIBILITY.stpP(event); + } + + addDestroyEventListener(scrollbarVars._handle, + _strMouseTouchDownEvent, + onHandleMouseTouchDown); + addDestroyEventListener(scrollbarVars._track, + [_strMouseTouchDownEvent, _strMouseEnter, _strMouseLeave], + [onTrackMouseTouchDown, onTrackMouseTouchEnter, onTrackMouseTouchLeave]); + addDestroyEventListener(scrollbarVars._scrollbar, + _strMouseTouchDownEvent, + onScrollbarMouseTouchDown); + + if (_supportTransition) { + addDestroyEventListener(scrollbarVars._scrollbar, _strTransitionEndEvent, function (event) { + if (event.target !== scrollbarVars._scrollbar[0]) + return; + refreshScrollbarHandleLength(isHorizontal); + refreshScrollbarHandleOffset(isHorizontal); + }); + } + } + + /** + * Shows or hides the given scrollbar and applied a class name which indicates if the scrollbar is scrollable or not. + * @param isHorizontal True if the horizontal scrollbar is the target, false if the vertical scrollbar is the target. + * @param shallBeVisible True if the scrollbar shall be shown, false if hidden. + * @param canScroll True if the scrollbar is scrollable, false otherwise. + */ + function refreshScrollbarAppearance(isHorizontal, shallBeVisible, canScroll) { + var scrollbarHiddenClassName = isHorizontal ? _classNameHostScrollbarHorizontalHidden : _classNameHostScrollbarVerticalHidden; + var scrollbarElement = isHorizontal ? _scrollbarHorizontalElement : _scrollbarVerticalElement; + + addRemoveClass(_hostElement, scrollbarHiddenClassName, !shallBeVisible); + addRemoveClass(scrollbarElement, _classNameScrollbarUnusable, !canScroll); + } + + /** + * Autoshows / autohides both scrollbars with. + * @param shallBeVisible True if the scrollbars shall be autoshown (only the case if they are hidden by a autohide), false if the shall be auto hidden. + * @param delayfree True if the scrollbars shall be hidden without a delay, false or undefined otherwise. + */ + function refreshScrollbarsAutoHide(shallBeVisible, delayfree) { + clearTimeout(_scrollbarsAutoHideTimeoutId); + if (shallBeVisible) { + //if(_hasOverflowCache.x && _hideOverflowCache.xs) + removeClass(_scrollbarHorizontalElement, _classNameScrollbarAutoHidden); + //if(_hasOverflowCache.y && _hideOverflowCache.ys) + removeClass(_scrollbarVerticalElement, _classNameScrollbarAutoHidden); + } + else { + var anyActive; + var strActive = 'active'; + var hide = function () { + if (!_scrollbarsHandleHovered && !_destroyed) { + anyActive = _scrollbarHorizontalHandleElement.hasClass(strActive) || _scrollbarVerticalHandleElement.hasClass(strActive); + if (!anyActive && (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove || _scrollbarsAutoHideLeave)) + addClass(_scrollbarHorizontalElement, _classNameScrollbarAutoHidden); + if (!anyActive && (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove || _scrollbarsAutoHideLeave)) + addClass(_scrollbarVerticalElement, _classNameScrollbarAutoHidden); + } + }; + if (_scrollbarsAutoHideDelay > 0 && delayfree !== true) + _scrollbarsAutoHideTimeoutId = setTimeout(hide, _scrollbarsAutoHideDelay); + else + hide(); + } + } + + /** + * Refreshes the handle length of the given scrollbar. + * @param isHorizontal True if the horizontal scrollbar handle shall be refreshed, false if the vertical one shall be refreshed. + */ + function refreshScrollbarHandleLength(isHorizontal) { + var handleCSS = {}; + var scrollbarVars = getScrollbarVars(isHorizontal); + var scrollbarVarsInfo = scrollbarVars._info; + var digit = 1000000; + //get and apply intended handle length + var handleRatio = MATH.min(1, _viewportSize[scrollbarVars._w_h] / _contentScrollSizeCache[scrollbarVars._w_h]); + handleCSS[scrollbarVars._width_height] = (MATH.floor(handleRatio * 100 * digit) / digit) + '%'; //the last * digit / digit is for flooring to the 4th digit + + if (!nativeOverlayScrollbarsAreActive()) + scrollbarVars._handle.css(handleCSS); + + //measure the handle length to respect min & max length + scrollbarVarsInfo._handleLength = scrollbarVars._handle[0]['offset' + scrollbarVars._Width_Height]; + scrollbarVarsInfo._handleLengthRatio = handleRatio; + } + + /** + * Refreshes the handle offset of the given scrollbar. + * @param isHorizontal True if the horizontal scrollbar handle shall be refreshed, false if the vertical one shall be refreshed. + * @param scrollOrTransition The scroll position of the given scrollbar axis to which the handle shall be moved or a boolean which indicates whether a transition shall be applied. If undefined or boolean if the current scroll-offset is taken. (if isHorizontal ? scrollLeft : scrollTop) + */ + function refreshScrollbarHandleOffset(isHorizontal, scrollOrTransition) { + var transition = type(scrollOrTransition) == TYPES.b; + var transitionDuration = 250; + var isRTLisHorizontal = _isRTL && isHorizontal; + var scrollbarVars = getScrollbarVars(isHorizontal); + var scrollbarVarsInfo = scrollbarVars._info; + var strTranslateBrace = 'translate('; + var strTransform = VENDORS._cssProperty('transform'); + var strTransition = VENDORS._cssProperty('transition'); + var nativeScroll = isHorizontal ? _viewportElement[_strScrollLeft]() : _viewportElement[_strScrollTop](); + var currentScroll = scrollOrTransition === undefined || transition ? nativeScroll : scrollOrTransition; + + //measure the handle length to respect min & max length + var handleLength = scrollbarVarsInfo._handleLength; + var trackLength = scrollbarVars._track[0]['offset' + scrollbarVars._Width_Height]; + var handleTrackDiff = trackLength - handleLength; + var handleCSS = {}; + var transformOffset; + var translateValue; + + //DONT use the variable '_contentScrollSizeCache[scrollbarVars._w_h]' instead of '_viewportElement[0]['scroll' + scrollbarVars._Width_Height]' + // because its a bit behind during the small delay when content size updates + //(delay = mutationObserverContentLag, if its 0 then this var could be used) + var maxScroll = (_viewportElementNative[_strScroll + scrollbarVars._Width_Height] - _viewportElementNative['client' + scrollbarVars._Width_Height]) * (_rtlScrollBehavior.n && isRTLisHorizontal ? -1 : 1); //* -1 if rtl scroll max is negative + var getScrollRatio = function (base) { + return isNaN(base / maxScroll) ? 0 : MATH.max(0, MATH.min(1, base / maxScroll)); + }; + var getHandleOffset = function (scrollRatio) { + var offset = handleTrackDiff * scrollRatio; + offset = isNaN(offset) ? 0 : offset; + offset = (isRTLisHorizontal && !_rtlScrollBehavior.i) ? (trackLength - handleLength - offset) : offset; + offset = MATH.max(0, offset); + return offset; + }; + var scrollRatio = getScrollRatio(nativeScroll); + var unsnappedScrollRatio = getScrollRatio(currentScroll); + var handleOffset = getHandleOffset(unsnappedScrollRatio); + var snappedHandleOffset = getHandleOffset(scrollRatio); + + scrollbarVarsInfo._maxScroll = maxScroll; + scrollbarVarsInfo._currentScroll = nativeScroll; + scrollbarVarsInfo._currentScrollRatio = scrollRatio; + + if (_supportTransform) { + transformOffset = isRTLisHorizontal ? -(trackLength - handleLength - handleOffset) : handleOffset; //in px + //transformOffset = (transformOffset / trackLength * 100) * (trackLength / handleLength); //in % + translateValue = isHorizontal ? strTranslateBrace + transformOffset + 'px, 0)' : strTranslateBrace + '0, ' + transformOffset + 'px)'; + + handleCSS[strTransform] = translateValue; + + //apply or clear up transition + if (_supportTransition) + handleCSS[strTransition] = transition && MATH.abs(handleOffset - scrollbarVarsInfo._handleOffset) > 1 ? getCSSTransitionString(scrollbarVars._handle) + ', ' + (strTransform + _strSpace + transitionDuration + 'ms') : _strEmpty; + } + else + handleCSS[scrollbarVars._left_top] = handleOffset; + + + //only apply css if offset has changed and overflow exists. + if (!nativeOverlayScrollbarsAreActive()) { + scrollbarVars._handle.css(handleCSS); + + //clear up transition + if (_supportTransform && _supportTransition && transition) { + scrollbarVars._handle.one(_strTransitionEndEvent, function () { + if (!_destroyed) + scrollbarVars._handle.css(strTransition, _strEmpty); + }); + } + } + + scrollbarVarsInfo._handleOffset = handleOffset; + scrollbarVarsInfo._snappedHandleOffset = snappedHandleOffset; + scrollbarVarsInfo._trackLength = trackLength; + } + + /** + * Refreshes the interactivity of the given scrollbar element. + * @param isTrack True if the track element is the target, false if the handle element is the target. + * @param value True for interactivity false for no interactivity. + */ + function refreshScrollbarsInteractive(isTrack, value) { + var action = value ? 'removeClass' : 'addClass'; + var element1 = isTrack ? _scrollbarHorizontalTrackElement : _scrollbarHorizontalHandleElement; + var element2 = isTrack ? _scrollbarVerticalTrackElement : _scrollbarVerticalHandleElement; + var className = isTrack ? _classNameScrollbarTrackOff : _classNameScrollbarHandleOff; + + element1[action](className); + element2[action](className); + } + + /** + * Returns a object which is used for fast access for specific variables. + * @param isHorizontal True if the horizontal scrollbar vars shall be accessed, false if the vertical scrollbar vars shall be accessed. + * @returns {{wh: string, WH: string, lt: string, _wh: string, _lt: string, t: *, h: *, c: {}, s: *}} + */ + function getScrollbarVars(isHorizontal) { + return { + _width_height: isHorizontal ? _strWidth : _strHeight, + _Width_Height: isHorizontal ? 'Width' : 'Height', + _left_top: isHorizontal ? _strLeft : _strTop, + _Left_Top: isHorizontal ? 'Left' : 'Top', + _x_y: isHorizontal ? _strX : _strY, + _X_Y: isHorizontal ? 'X' : 'Y', + _w_h: isHorizontal ? 'w' : 'h', + _l_t: isHorizontal ? 'l' : 't', + _track: isHorizontal ? _scrollbarHorizontalTrackElement : _scrollbarVerticalTrackElement, + _handle: isHorizontal ? _scrollbarHorizontalHandleElement : _scrollbarVerticalHandleElement, + _scrollbar: isHorizontal ? _scrollbarHorizontalElement : _scrollbarVerticalElement, + _info: isHorizontal ? _scrollHorizontalInfo : _scrollVerticalInfo + }; + } + + + //==== Scrollbar Corner ====// + + /** + * Builds or destroys the scrollbar corner DOM element. + * @param destroy Indicates whether the DOM shall be build or destroyed. + */ + function setupScrollbarCornerDOM(destroy) { + _scrollbarCornerElement = _scrollbarCornerElement || selectOrGenerateDivByClass(_classNameScrollbarCorner, true); + + if (!destroy) { + if (!_domExists) { + _hostElement.append(_scrollbarCornerElement); + } + } + else { + if (_domExists && _initialized) { + removeClass(_scrollbarCornerElement.removeAttr(LEXICON.s), _classNamesDynamicDestroy); + } + else { + remove(_scrollbarCornerElement); + } + } + } + + /** + * Initializes all scrollbar corner interactivity events. + */ + function setupScrollbarCornerEvents() { + var insideIFrame = _windowElementNative.top !== _windowElementNative; + var mouseDownPosition = {}; + var mouseDownSize = {}; + var mouseDownInvertedScale = {}; + var reconnectMutationObserver; + + function documentDragMove(event) { + if (onMouseTouchDownContinue(event)) { + var pageOffset = getCoordinates(event); + var hostElementCSS = {}; + if (_resizeHorizontal || _resizeBoth) + hostElementCSS[_strWidth] = (mouseDownSize.w + (pageOffset.x - mouseDownPosition.x) * mouseDownInvertedScale.x); + if (_resizeVertical || _resizeBoth) + hostElementCSS[_strHeight] = (mouseDownSize.h + (pageOffset.y - mouseDownPosition.y) * mouseDownInvertedScale.y); + _hostElement.css(hostElementCSS); + COMPATIBILITY.stpP(event); + } + else { + documentMouseTouchUp(event); + } + } + function documentMouseTouchUp(event) { + var eventIsTrusted = event !== undefined; + + setupResponsiveEventListener(_documentElement, + [_strSelectStartEvent, _strMouseTouchMoveEvent, _strMouseTouchUpEvent], + [documentOnSelectStart, documentDragMove, documentMouseTouchUp], + true); + + removeClass(_bodyElement, _classNameDragging); + if (_scrollbarCornerElement.releaseCapture) + _scrollbarCornerElement.releaseCapture(); + + if (eventIsTrusted) { + if (reconnectMutationObserver) + connectMutationObservers(); + _base.update(_strAuto); + } + reconnectMutationObserver = false; + } + function onMouseTouchDownContinue(event) { + var originalEvent = event.originalEvent || event; + var isTouchEvent = originalEvent.touches !== undefined; + return _sleeping || _destroyed ? false : COMPATIBILITY.mBtn(event) === 1 || isTouchEvent; + } + function getCoordinates(event) { + return _msieVersion && insideIFrame ? { x: event.screenX, y: event.screenY } : COMPATIBILITY.page(event); + } + + addDestroyEventListener(_scrollbarCornerElement, _strMouseTouchDownEvent, function (event) { + if (onMouseTouchDownContinue(event) && !_resizeNone) { + if (_mutationObserversConnected) { + reconnectMutationObserver = true; + disconnectMutationObservers(); + } + + mouseDownPosition = getCoordinates(event); + + mouseDownSize.w = _hostElementNative[LEXICON.oW] - (!_isBorderBox ? _paddingX : 0); + mouseDownSize.h = _hostElementNative[LEXICON.oH] - (!_isBorderBox ? _paddingY : 0); + mouseDownInvertedScale = getHostElementInvertedScale(); + + setupResponsiveEventListener(_documentElement, + [_strSelectStartEvent, _strMouseTouchMoveEvent, _strMouseTouchUpEvent], + [documentOnSelectStart, documentDragMove, documentMouseTouchUp]); + + addClass(_bodyElement, _classNameDragging); + if (_scrollbarCornerElement.setCapture) + _scrollbarCornerElement.setCapture(); + + COMPATIBILITY.prvD(event); + COMPATIBILITY.stpP(event); + } + }); + } + + + //==== Utils ====// + + /** + * Calls the callback with the given name. The Context of this callback is always _base (this). + * @param name The name of the target which shall be called. + * @param args The args with which the callback shall be called. + * @param dependent Boolean which decides whether the callback shall be fired, undefined is like a "true" value. + */ + function dispatchCallback(name, args, dependent) { + if (dependent === false) + return; + if (_initialized) { + var callback = _currentPreparedOptions.callbacks[name]; + var extensionOnName = name; + var ext; + + if (extensionOnName.substr(0, 2) === 'on') + extensionOnName = extensionOnName.substr(2, 1).toLowerCase() + extensionOnName.substr(3); + + if (type(callback) == TYPES.f) + callback.call(_base, args); + + each(_extensions, function () { + ext = this; + if (type(ext.on) == TYPES.f) + ext.on(extensionOnName, args); + }); + } + else if (!_destroyed) + _callbacksInitQeueue.push({ n: name, a: args }); + } + + /** + * Sets the "top, right, bottom, left" properties, with a given prefix, of the given css object. + * @param targetCSSObject The css object to which the values shall be applied. + * @param prefix The prefix of the "top, right, bottom, left" css properties. (example: 'padding-' is a valid prefix) + * @param values A array of values which shall be applied to the "top, right, bottom, left" -properties. The array order is [top, right, bottom, left]. + * If this argument is undefined the value '' (empty string) will be applied to all properties. + */ + function setTopRightBottomLeft(targetCSSObject, prefix, values) { + prefix = prefix || _strEmpty; + values = values || [_strEmpty, _strEmpty, _strEmpty, _strEmpty]; + + targetCSSObject[prefix + _strTop] = values[0]; + targetCSSObject[prefix + _strRight] = values[1]; + targetCSSObject[prefix + _strBottom] = values[2]; + targetCSSObject[prefix + _strLeft] = values[3]; + } + + /** + * Gets the "top, right, bottom, left" CSS properties of the CSS property with the given prefix from the host element. + * @param prefix The prefix of the "top, right, bottom, left" css properties. (example: 'padding-' is a valid prefix) + * @param suffix The suffix of the "top, right, bottom, left" css properties. (example: 'border-' is a valid prefix with '-width' is a valid suffix) + * @param zeroX True if the x axis shall be 0. + * @param zeroY True if the y axis shall be 0. + * @returns {{}} The object which contains the numbers of the read CSS properties. + */ + function getTopRightBottomLeftHost(prefix, suffix, zeroX, zeroY) { + suffix = suffix || _strEmpty; + prefix = prefix || _strEmpty; + return { + t: zeroY ? 0 : parseToZeroOrNumber(_hostElement.css(prefix + _strTop + suffix)), + r: zeroX ? 0 : parseToZeroOrNumber(_hostElement.css(prefix + _strRight + suffix)), + b: zeroY ? 0 : parseToZeroOrNumber(_hostElement.css(prefix + _strBottom + suffix)), + l: zeroX ? 0 : parseToZeroOrNumber(_hostElement.css(prefix + _strLeft + suffix)) + }; + } + + /** + * Returns the computed CSS transition string from the given element. + * @param element The element from which the transition string shall be returned. + * @returns {string} The CSS transition string from the given element. + */ + function getCSSTransitionString(element) { + var transitionStr = VENDORS._cssProperty('transition'); + var assembledValue = element.css(transitionStr); + if (assembledValue) + return assembledValue; + var regExpString = '\\s*(' + '([^,(]+(\\(.+?\\))?)+' + ')[\\s,]*'; + var regExpMain = new RegExp(regExpString); + var regExpValidate = new RegExp('^(' + regExpString + ')+$'); + var properties = 'property duration timing-function delay'.split(' '); + var result = []; + var strResult; + var valueArray; + var i = 0; + var j; + var splitCssStyleByComma = function (str) { + strResult = []; + if (!str.match(regExpValidate)) + return str; + while (str.match(regExpMain)) { + strResult.push(RegExp.$1); + str = str.replace(regExpMain, _strEmpty); + } + + return strResult; + }; + for (; i < properties[LEXICON.l]; i++) { + valueArray = splitCssStyleByComma(element.css(transitionStr + '-' + properties[i])); + for (j = 0; j < valueArray[LEXICON.l]; j++) + result[j] = (result[j] ? result[j] + _strSpace : _strEmpty) + valueArray[j]; + } + return result.join(', '); + } + + /** + * Generates a Regular Expression which matches with a string which starts with 'os-host'. + * @param {boolean} withCurrClassNameOption The Regular Expression also matches if the string is the current ClassName option (multiple values splitted by space possible). + * @param {boolean} withOldClassNameOption The Regular Expression also matches if the string is the old ClassName option (multiple values splitted by space possible). + */ + function createHostClassNameRegExp(withCurrClassNameOption, withOldClassNameOption) { + var i; + var split; + var appendix; + var appendClasses = function (classes, condition) { + appendix = ''; + if (condition && typeof classes == TYPES.s) { + split = classes.split(_strSpace); + for (i = 0; i < split[LEXICON.l]; i++) + appendix += '|' + split[i] + '$'; + // split[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&') for escaping regex characters + } + return appendix; + }; + + return new RegExp( + '(^' + _classNameHostElement + '([-_].+|)$)' + + appendClasses(_classNameCache, withCurrClassNameOption) + + appendClasses(_oldClassName, withOldClassNameOption), 'g'); + } + + /** + * Calculates the host-elements inverted scale. (invertedScale = 1 / scale) + * @returns {{x: number, y: number}} The scale of the host-element. + */ + function getHostElementInvertedScale() { + var rect = _paddingElementNative[LEXICON.bCR](); + return { + x: _supportTransform ? 1 / (MATH.round(rect.width) / _paddingElementNative[LEXICON.oW]) || 1 : 1, + y: _supportTransform ? 1 / (MATH.round(rect.height) / _paddingElementNative[LEXICON.oH]) || 1 : 1 + }; + } + + /** + * Checks whether the given object is a HTMLElement. + * @param o The object which shall be checked. + * @returns {boolean} True the given object is a HTMLElement, false otherwise. + */ + function isHTMLElement(o) { + var strOwnerDocument = 'ownerDocument'; + var strHTMLElement = 'HTMLElement'; + var wnd = o && o[strOwnerDocument] ? (o[strOwnerDocument].parentWindow || window) : window; + return ( + typeof wnd[strHTMLElement] == TYPES.o ? o instanceof wnd[strHTMLElement] : //DOM2 + o && typeof o == TYPES.o && o !== null && o.nodeType === 1 && typeof o.nodeName == TYPES.s + ); + } + + /** + * Compares 2 arrays and returns the differences between them as a array. + * @param a1 The first array which shall be compared. + * @param a2 The second array which shall be compared. + * @returns {Array} The differences between the two arrays. + */ + function getArrayDifferences(a1, a2) { + var a = []; + var diff = []; + var i; + var k; + for (i = 0; i < a1.length; i++) + a[a1[i]] = true; + for (i = 0; i < a2.length; i++) { + if (a[a2[i]]) + delete a[a2[i]]; + else + a[a2[i]] = true; + } + for (k in a) + diff.push(k); + return diff; + } + + /** + * Returns Zero or the number to which the value can be parsed. + * @param value The value which shall be parsed. + * @param toFloat Indicates whether the number shall be parsed to a float. + */ + function parseToZeroOrNumber(value, toFloat) { + var num = toFloat ? parseFloat(value) : parseInt(value, 10); + return isNaN(num) ? 0 : num; + } + + /** + * Gets several information of the textarea and returns them as a object or undefined if the browser doesn't support it. + * @returns {{cursorRow: Number, cursorCol, rows: Number, cols: number, wRow: number, pos: number, max : number}} or undefined if not supported. + */ + function getTextareaInfo() { + //read needed values + var textareaCursorPosition = _targetElementNative.selectionStart; + if (textareaCursorPosition === undefined) + return; + + var textareaValue = _targetElement.val(); + var textareaLength = textareaValue[LEXICON.l]; + var textareaRowSplit = textareaValue.split('\n'); + var textareaLastRow = textareaRowSplit[LEXICON.l]; + var textareaCurrentCursorRowSplit = textareaValue.substr(0, textareaCursorPosition).split('\n'); + var widestRow = 0; + var textareaLastCol = 0; + var cursorRow = textareaCurrentCursorRowSplit[LEXICON.l]; + var cursorCol = textareaCurrentCursorRowSplit[textareaCurrentCursorRowSplit[LEXICON.l] - 1][LEXICON.l]; + var rowCols; + var i; + + //get widest Row and the last column of the textarea + for (i = 0; i < textareaRowSplit[LEXICON.l]; i++) { + rowCols = textareaRowSplit[i][LEXICON.l]; + if (rowCols > textareaLastCol) { + widestRow = i + 1; + textareaLastCol = rowCols; + } + } + + return { + _cursorRow: cursorRow, //cursorRow + _cursorColumn: cursorCol, //cursorCol + _rows: textareaLastRow, //rows + _columns: textareaLastCol, //cols + _widestRow: widestRow, //wRow + _cursorPosition: textareaCursorPosition, //pos + _cursorMax: textareaLength //max + }; + } + + /** + * Determines whether native overlay scrollbars are active. + * @returns {boolean} True if native overlay scrollbars are active, false otherwise. + */ + function nativeOverlayScrollbarsAreActive() { + return (_ignoreOverlayScrollbarHidingCache && (_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y)); + } + + /** + * Gets the element which is used to measure the content size. + * @returns {*} TextareaCover if target element is textarea else the ContentElement. + */ + function getContentMeasureElement() { + return _isTextarea ? _textareaCoverElement[0] : _contentElementNative; + } + + /** + * Generates a string which represents a HTML div with the given classes or attributes. + * @param classesOrAttrs The class of the div as string or a object which represents the attributes of the div. (The class attribute can also be written as "className".) + * @param content The content of the div as string. + * @returns {string} The concated string which represents a HTML div and its content. + */ + function generateDiv(classesOrAttrs, content) { + return '
' + + (content || _strEmpty) + + '
'; + } + + /** + * Selects or generates a div with the given class attribute. + * @param className The class names (divided by spaces) of the div which shall be selected or generated. + * @param selectParentOrOnlyChildren The parent element from which of the element shall be selected. (if undefined or boolean its hostElement) + * If its a boolean it decides whether only the children of the host element shall be selected. + * @returns {*} The generated or selected element. + */ + function selectOrGenerateDivByClass(className, selectParentOrOnlyChildren) { + var onlyChildren = type(selectParentOrOnlyChildren) == TYPES.b; + var selectParent = onlyChildren ? _hostElement : (selectParentOrOnlyChildren || _hostElement); + + return (_domExists && !selectParent[LEXICON.l]) + ? null + : _domExists + ? selectParent[onlyChildren ? 'children' : 'find'](_strDot + className.replace(/\s/g, _strDot)).eq(0) + : FRAMEWORK(generateDiv(className)) + } + + /** + * Gets the value of the given property from the given object. + * @param obj The object from which the property value shall be got. + * @param path The property of which the value shall be got. + * @returns {*} Returns the value of the searched property or undefined of the property wasn't found. + */ + function getObjectPropVal(obj, path) { + var splits = path.split(_strDot); + var i = 0; + var val; + for (; i < splits.length; i++) { + if (!obj[LEXICON.hOP](splits[i])) + return; + val = obj[splits[i]]; + if (i < splits.length && type(val) == TYPES.o) + obj = val; + } + return val; + } + + /** + * Sets the value of the given property from the given object. + * @param obj The object from which the property value shall be set. + * @param path The property of which the value shall be set. + * @param val The value of the property which shall be set. + */ + function setObjectPropVal(obj, path, val) { + var splits = path.split(_strDot); + var splitsLength = splits.length; + var i = 0; + var extendObj = {}; + var extendObjRoot = extendObj; + for (; i < splitsLength; i++) + extendObj = extendObj[splits[i]] = i + 1 < splitsLength ? {} : val; + FRAMEWORK.extend(obj, extendObjRoot, true); + } + + /** + * Runs a action for each selector inside the updateOnLoad option. + * @param {Function} action The action for each updateOnLoad selector, the arguments the function takes is the index and the value (the selector). + */ + function eachUpdateOnLoad(action) { + var updateOnLoad = _currentPreparedOptions.updateOnLoad; + updateOnLoad = type(updateOnLoad) == TYPES.s ? updateOnLoad.split(_strSpace) : updateOnLoad; + + if (COMPATIBILITY.isA(updateOnLoad) && !_destroyed) { + each(updateOnLoad, action); + } + } + + + //==== Utils Cache ====// + + /** + * Compares two values or objects and returns true if they aren't equal. + * @param current The first value or object which shall be compared. + * @param cache The second value or object which shall be compared. + * @param force If true the returned value is always true. + * @returns {boolean} True if both values or objects aren't equal or force is true, false otherwise. + */ + function checkCache(current, cache, force) { + if (force) + return force; + if (type(current) == TYPES.o && type(cache) == TYPES.o) { + for (var prop in current) { + if (prop !== 'c') { + if (current[LEXICON.hOP](prop) && cache[LEXICON.hOP](prop)) { + if (checkCache(current[prop], cache[prop])) + return true; + } + else { + return true; + } + } + } + } + else { + return current !== cache; + } + return false; + } + + + //==== Shortcuts ====// + + /** + * jQuery extend method shortcut with a appended "true" as first argument. + */ + function extendDeep() { + return FRAMEWORK.extend.apply(this, [true].concat([].slice.call(arguments))); + } + + /** + * jQuery addClass method shortcut. + */ + function addClass(el, classes) { + return _frameworkProto.addClass.call(el, classes); + } + + /** + * jQuery removeClass method shortcut. + */ + function removeClass(el, classes) { + return _frameworkProto.removeClass.call(el, classes); + } + + /** + * Adds or removes the given classes dependent on the boolean value. True for add, false for remove. + */ + function addRemoveClass(el, classes, doAdd) { + return doAdd ? addClass(el, classes) : removeClass(el, classes); + } + + /** + * jQuery remove method shortcut. + */ + function remove(el) { + return _frameworkProto.remove.call(el); + } + + /** + * Finds the first child element with the given selector of the given element. + * @param el The root element from which the selector shall be valid. + * @param selector The selector of the searched element. + * @returns {*} The first element which is a child of the given element and matches the givens selector. + */ + function findFirst(el, selector) { + return _frameworkProto.find.call(el, selector).eq(0); + } + + + //==== API ====// + + /** + * Puts the instance to sleep. It wont respond to any changes in the DOM and won't update. Scrollbar Interactivity is also disabled as well as the resize handle. + * This behavior can be reset by calling the update method. + */ + _base.sleep = function () { + _sleeping = true; + }; + + /** + * Updates the plugin and DOM to the current options. + * This method should only be called if a update is 100% required. + * @param force True if every property shall be updated and the cache shall be ignored. + * !INTERNAL USAGE! : force can be a string "auto", "sync" or "zoom" too + * if "auto" then before a real update the content size and host element attributes gets checked, and if they changed only then the update method will be called. + * if "sync" then the async update process (MutationObserver or UpdateLoop) gets synchronized and a corresponding update takes place if one was needed due to pending changes. + * if "zoom" then a update takes place where it's assumed that content and host size changed + * @returns {boolean|undefined} + * If force is "sync" then a boolean is returned which indicates whether a update was needed due to pending changes. + * If force is "auto" then a boolean is returned whether a update was needed due to attribute or size changes. + * undefined otherwise. + */ + _base.update = function (force) { + if (_destroyed) + return; + + var attrsChanged; + var contentSizeC; + var isString = type(force) == TYPES.s; + var doUpdateAuto; + var mutHost; + var mutContent; + + if (isString) { + if (force === _strAuto) { + attrsChanged = meaningfulAttrsChanged(); + contentSizeC = updateAutoContentSizeChanged(); + doUpdateAuto = attrsChanged || contentSizeC; + if (doUpdateAuto) { + update({ + _contentSizeChanged: contentSizeC, + _changedOptions: _initialized ? undefined : _currentPreparedOptions + }); + } + } + else if (force === _strSync) { + if (_mutationObserversConnected) { + mutHost = _mutationObserverHostCallback(_mutationObserverHost.takeRecords()); + mutContent = _mutationObserverContentCallback(_mutationObserverContent.takeRecords()); + } + else { + mutHost = _base.update(_strAuto); + } + } + else if (force === 'zoom') { + update({ + _hostSizeChanged: true, + _contentSizeChanged: true + }); + } + } + else { + force = _sleeping || force; + _sleeping = false; + if (!_base.update(_strSync) || force) + update({ _force: force }); + } + + updateElementsOnLoad(); + + return doUpdateAuto || mutHost || mutContent; + }; + + /** + Gets or sets the current options. The update method will be called automatically if new options were set. + * @param newOptions If new options are given, then the new options will be set, if new options aren't given (undefined or a not a plain object) then the current options will be returned. + * @param value If new options is a property path string, then this value will be used to set the option to which the property path string leads. + * @returns {*} + */ + _base.options = function (newOptions, value) { + var option = {}; + var changedOps; + + //return current options if newOptions are undefined or empty + if (FRAMEWORK.isEmptyObject(newOptions) || !FRAMEWORK.isPlainObject(newOptions)) { + if (type(newOptions) == TYPES.s) { + if (arguments.length > 1) { + setObjectPropVal(option, newOptions, value); + changedOps = setOptions(option); + } + else + return getObjectPropVal(_currentOptions, newOptions); + } + else + return _currentOptions; + } + else { + changedOps = setOptions(newOptions); + } + + if (!FRAMEWORK.isEmptyObject(changedOps)) { + update({ _changedOptions: changedOps }); + } + }; + + /** + * Restore the DOM, disconnects all observers, remove all resize observers and put the instance to sleep. + */ + _base.destroy = function () { + if (_destroyed) + return; + + //remove this instance from auto update loop + autoUpdateLoop.remove(_base); + + //disconnect all mutation observers + disconnectMutationObservers(); + + //remove all resize observers + setupResizeObserver(_sizeObserverElement); + setupResizeObserver(_sizeAutoObserverElement); + + //remove all extensions + for (var extName in _extensions) + _base.removeExt(extName); + + //remove all 'destroy' events + while (_destroyEvents[LEXICON.l] > 0) + _destroyEvents.pop()(); + + //remove all events from host element + setupHostMouseTouchEvents(true); + + //remove all helper / detection elements + if (_contentGlueElement) + remove(_contentGlueElement); + if (_contentArrangeElement) + remove(_contentArrangeElement); + if (_sizeAutoObserverAdded) + remove(_sizeAutoObserverElement); + + //remove all generated DOM + setupScrollbarsDOM(true); + setupScrollbarCornerDOM(true); + setupStructureDOM(true); + + //remove all generated image load events + for (var i = 0; i < _updateOnLoadElms[LEXICON.l]; i++) + FRAMEWORK(_updateOnLoadElms[i]).off(_updateOnLoadEventName, updateOnLoadCallback); + _updateOnLoadElms = undefined; + + _destroyed = true; + _sleeping = true; + + //remove this instance from the instances list + INSTANCES(pluginTargetElement, 0); + dispatchCallback('onDestroyed'); + + //remove all properties and methods + //for (var property in _base) + // delete _base[property]; + //_base = undefined; + }; + + /** + * Scrolls to a given position or element. + * @param coordinates + * 1. Can be "coordinates" which looks like: + * { x : ?, y : ? } OR Object with x and y properties + * { left : ?, top : ? } OR Object with left and top properties + * { l : ?, t : ? } OR Object with l and t properties + * [ ?, ? ] OR Array where the first two element are the coordinates (first is x, second is y) + * ? A single value which stays for both axis + * A value can be a number, a string or a calculation. + * + * Operators: + * [NONE] The current scroll will be overwritten by the value. + * '+=' The value will be added to the current scroll offset + * '-=' The value will be subtracted from the current scroll offset + * '*=' The current scroll wil be multiplicated by the value. + * '/=' The current scroll wil be divided by the value. + * + * Units: + * [NONE] The value is the final scroll amount. final = (value * 1) + * 'px' Same as none + * '%' The value is dependent on the current scroll value. final = ((currentScrollValue / 100) * value) + * 'vw' The value is multiplicated by the viewport width. final = (value * viewportWidth) + * 'vh' The value is multiplicated by the viewport height. final = (value * viewportHeight) + * + * example final values: + * 200, '200px', '50%', '1vw', '1vh', '+=200', '/=1vw', '*=2px', '-=5vh', '+=33%', '+= 50% - 2px', '-= 1vw - 50%' + * + * 2. Can be a HTML or jQuery element: + * The final scroll offset is the offset (without margin) of the given HTML / jQuery element. + * + * 3. Can be a object with a HTML or jQuery element with additional settings: + * { + * el : [HTMLElement, jQuery element], MUST be specified, else this object isn't valid. + * scroll : [string, array, object], Default value is 'always'. + * block : [string, array, object], Default value is 'begin'. + * margin : [number, boolean, array, object] Default value is false. + * } + * + * Possible scroll settings are: + * 'always' Scrolls always. + * 'ifneeded' Scrolls only if the element isnt fully in view. + * 'never' Scrolls never. + * + * Possible block settings are: + * 'begin' Both axis shall be docked to the "begin" edge. - The element will be docked to the top and left edge of the viewport. + * 'end' Both axis shall be docked to the "end" edge. - The element will be docked to the bottom and right edge of the viewport. (If direction is RTL to the bottom and left edge.) + * 'center' Both axis shall be docked to "center". - The element will be centered in the viewport. + * 'nearest' The element will be docked to the nearest edge(s). + * + * Possible margin settings are: -- The actual margin of the element wont be affect, this option affects only the final scroll offset. + * [BOOLEAN] If true the css margin of the element will be used, if false no margin will be used. + * [NUMBER] The margin will be used for all edges. + * + * @param duration The duration of the scroll animation, OR a jQuery animation configuration object. + * @param easing The animation easing. + * @param complete The animation complete callback. + * @returns {{ + * position: {x: number, y: number}, + * ratio: {x: number, y: number}, + * max: {x: number, y: number}, + * handleOffset: {x: number, y: number}, + * handleLength: {x: number, y: number}, + * handleLengthRatio: {x: number, y: number}, t + * rackLength: {x: number, y: number}, + * isRTL: boolean, + * isRTLNormalized: boolean + * }} + */ + _base.scroll = function (coordinates, duration, easing, complete) { + if (arguments.length === 0 || coordinates === undefined) { + var infoX = _scrollHorizontalInfo; + var infoY = _scrollVerticalInfo; + var normalizeInvert = _normalizeRTLCache && _isRTL && _rtlScrollBehavior.i; + var normalizeNegate = _normalizeRTLCache && _isRTL && _rtlScrollBehavior.n; + var scrollX = infoX._currentScroll; + var scrollXRatio = infoX._currentScrollRatio; + var maxScrollX = infoX._maxScroll; + scrollXRatio = normalizeInvert ? 1 - scrollXRatio : scrollXRatio; + scrollX = normalizeInvert ? maxScrollX - scrollX : scrollX; + scrollX *= normalizeNegate ? -1 : 1; + maxScrollX *= normalizeNegate ? -1 : 1; + + return { + position: { + x: scrollX, + y: infoY._currentScroll + }, + ratio: { + x: scrollXRatio, + y: infoY._currentScrollRatio + }, + max: { + x: maxScrollX, + y: infoY._maxScroll + }, + handleOffset: { + x: infoX._handleOffset, + y: infoY._handleOffset + }, + handleLength: { + x: infoX._handleLength, + y: infoY._handleLength + }, + handleLengthRatio: { + x: infoX._handleLengthRatio, + y: infoY._handleLengthRatio + }, + trackLength: { + x: infoX._trackLength, + y: infoY._trackLength + }, + snappedHandleOffset: { + x: infoX._snappedHandleOffset, + y: infoY._snappedHandleOffset + }, + isRTL: _isRTL, + isRTLNormalized: _normalizeRTLCache + }; + } + + _base.update(_strSync); + + var normalizeRTL = _normalizeRTLCache; + var coordinatesXAxisProps = [_strX, _strLeft, 'l']; + var coordinatesYAxisProps = [_strY, _strTop, 't']; + var coordinatesOperators = ['+=', '-=', '*=', '/=']; + var durationIsObject = type(duration) == TYPES.o; + var completeCallback = durationIsObject ? duration.complete : complete; + var i; + var finalScroll = {}; + var specialEasing = {}; + var doScrollLeft; + var doScrollTop; + var animationOptions; + var strEnd = 'end'; + var strBegin = 'begin'; + var strCenter = 'center'; + var strNearest = 'nearest'; + var strAlways = 'always'; + var strNever = 'never'; + var strIfNeeded = 'ifneeded'; + var strLength = LEXICON.l; + var settingsAxis; + var settingsScroll; + var settingsBlock; + var settingsMargin; + var finalElement; + var elementObjSettingsAxisValues = [_strX, _strY, 'xy', 'yx']; + var elementObjSettingsBlockValues = [strBegin, strEnd, strCenter, strNearest]; + var elementObjSettingsScrollValues = [strAlways, strNever, strIfNeeded]; + var coordinatesIsElementObj = coordinates[LEXICON.hOP]('el'); + var possibleElement = coordinatesIsElementObj ? coordinates.el : coordinates; + var possibleElementIsJQuery = possibleElement instanceof FRAMEWORK || JQUERY ? possibleElement instanceof JQUERY : false; + var possibleElementIsHTMLElement = possibleElementIsJQuery ? false : isHTMLElement(possibleElement); + var updateScrollbarInfos = function () { + if (doScrollLeft) + refreshScrollbarHandleOffset(true); + if (doScrollTop) + refreshScrollbarHandleOffset(false); + }; + var proxyCompleteCallback = type(completeCallback) != TYPES.f ? undefined : function () { + updateScrollbarInfos(); + completeCallback(); + }; + function checkSettingsStringValue(currValue, allowedValues) { + for (i = 0; i < allowedValues[strLength]; i++) { + if (currValue === allowedValues[i]) + return true; + } + return false; + } + function getRawScroll(isX, coordinates) { + var coordinateProps = isX ? coordinatesXAxisProps : coordinatesYAxisProps; + coordinates = type(coordinates) == TYPES.s || type(coordinates) == TYPES.n ? [coordinates, coordinates] : coordinates; + + if (COMPATIBILITY.isA(coordinates)) + return isX ? coordinates[0] : coordinates[1]; + else if (type(coordinates) == TYPES.o) { + //decides RTL normalization "hack" with .n + //normalizeRTL = type(coordinates.n) == TYPES.b ? coordinates.n : normalizeRTL; + for (i = 0; i < coordinateProps[strLength]; i++) + if (coordinateProps[i] in coordinates) + return coordinates[coordinateProps[i]]; + } + } + function getFinalScroll(isX, rawScroll) { + var isString = type(rawScroll) == TYPES.s; + var operator; + var amount; + var scrollInfo = isX ? _scrollHorizontalInfo : _scrollVerticalInfo; + var currScroll = scrollInfo._currentScroll; + var maxScroll = scrollInfo._maxScroll; + var mult = ' * '; + var finalValue; + var isRTLisX = _isRTL && isX; + var normalizeShortcuts = isRTLisX && _rtlScrollBehavior.n && !normalizeRTL; + var strReplace = 'replace'; + var evalFunc = eval; + var possibleOperator; + if (isString) { + //check operator + if (rawScroll[strLength] > 2) { + possibleOperator = rawScroll.substr(0, 2); + if (inArray(possibleOperator, coordinatesOperators) > -1) + operator = possibleOperator; + } + + //calculate units and shortcuts + rawScroll = operator ? rawScroll.substr(2) : rawScroll; + rawScroll = rawScroll + [strReplace](/min/g, 0) //'min' = 0% + [strReplace](//g, (normalizeShortcuts ? '-' : _strEmpty) + _strHundredPercent) //'>' = 100% + [strReplace](/px/g, _strEmpty) + [strReplace](/%/g, mult + (maxScroll * (isRTLisX && _rtlScrollBehavior.n ? -1 : 1) / 100.0)) + [strReplace](/vw/g, mult + _viewportSize.w) + [strReplace](/vh/g, mult + _viewportSize.h); + amount = parseToZeroOrNumber(isNaN(rawScroll) ? parseToZeroOrNumber(evalFunc(rawScroll), true).toFixed() : rawScroll); + } + else { + amount = rawScroll; + } + + if (amount !== undefined && !isNaN(amount) && type(amount) == TYPES.n) { + var normalizeIsRTLisX = normalizeRTL && isRTLisX; + var operatorCurrScroll = currScroll * (normalizeIsRTLisX && _rtlScrollBehavior.n ? -1 : 1); + var invert = normalizeIsRTLisX && _rtlScrollBehavior.i; + var negate = normalizeIsRTLisX && _rtlScrollBehavior.n; + operatorCurrScroll = invert ? (maxScroll - operatorCurrScroll) : operatorCurrScroll; + switch (operator) { + case '+=': + finalValue = operatorCurrScroll + amount; + break; + case '-=': + finalValue = operatorCurrScroll - amount; + break; + case '*=': + finalValue = operatorCurrScroll * amount; + break; + case '/=': + finalValue = operatorCurrScroll / amount; + break; + default: + finalValue = amount; + break; + } + finalValue = invert ? maxScroll - finalValue : finalValue; + finalValue *= negate ? -1 : 1; + finalValue = isRTLisX && _rtlScrollBehavior.n ? MATH.min(0, MATH.max(maxScroll, finalValue)) : MATH.max(0, MATH.min(maxScroll, finalValue)); + } + return finalValue === currScroll ? undefined : finalValue; + } + function getPerAxisValue(value, valueInternalType, defaultValue, allowedValues) { + var resultDefault = [defaultValue, defaultValue]; + var valueType = type(value); + var valueArrLength; + var valueArrItem; + + //value can be [ string, or array of two strings ] + if (valueType == valueInternalType) { + value = [value, value]; + } + else if (valueType == TYPES.a) { + valueArrLength = value[strLength]; + if (valueArrLength > 2 || valueArrLength < 1) + value = resultDefault; + else { + if (valueArrLength === 1) + value[1] = defaultValue; + for (i = 0; i < valueArrLength; i++) { + valueArrItem = value[i]; + if (type(valueArrItem) != valueInternalType || !checkSettingsStringValue(valueArrItem, allowedValues)) { + value = resultDefault; + break; + } + } + } + } + else if (valueType == TYPES.o) + value = [value[_strX] || defaultValue, value[_strY] || defaultValue]; + else + value = resultDefault; + return { x: value[0], y: value[1] }; + } + function generateMargin(marginTopRightBottomLeftArray) { + var result = []; + var currValue; + var currValueType; + var valueDirections = [_strTop, _strRight, _strBottom, _strLeft]; + for (i = 0; i < marginTopRightBottomLeftArray[strLength]; i++) { + if (i === valueDirections[strLength]) + break; + currValue = marginTopRightBottomLeftArray[i]; + currValueType = type(currValue); + if (currValueType == TYPES.b) + result.push(currValue ? parseToZeroOrNumber(finalElement.css(_strMarginMinus + valueDirections[i])) : 0); + else + result.push(currValueType == TYPES.n ? currValue : 0); + } + return result; + } + + if (possibleElementIsJQuery || possibleElementIsHTMLElement) { + //get settings + var margin = coordinatesIsElementObj ? coordinates.margin : 0; + var axis = coordinatesIsElementObj ? coordinates.axis : 0; + var scroll = coordinatesIsElementObj ? coordinates.scroll : 0; + var block = coordinatesIsElementObj ? coordinates.block : 0; + var marginDefault = [0, 0, 0, 0]; + var marginType = type(margin); + var marginLength; + finalElement = possibleElementIsJQuery ? possibleElement : FRAMEWORK(possibleElement); + + if (finalElement[strLength] > 0) { + //margin can be [ boolean, number, array of 2, array of 4, object ] + if (marginType == TYPES.n || marginType == TYPES.b) + margin = generateMargin([margin, margin, margin, margin]); + else if (marginType == TYPES.a) { + marginLength = margin[strLength]; + if (marginLength === 2) + margin = generateMargin([margin[0], margin[1], margin[0], margin[1]]); + else if (marginLength >= 4) + margin = generateMargin(margin); + else + margin = marginDefault; + } + else if (marginType == TYPES.o) + margin = generateMargin([margin[_strTop], margin[_strRight], margin[_strBottom], margin[_strLeft]]); + else + margin = marginDefault; + + //block = type(block) === TYPES.b ? block ? [ strNearest, strBegin ] : [ strNearest, strEnd ] : block; + settingsAxis = checkSettingsStringValue(axis, elementObjSettingsAxisValues) ? axis : 'xy'; + settingsScroll = getPerAxisValue(scroll, TYPES.s, strAlways, elementObjSettingsScrollValues); + settingsBlock = getPerAxisValue(block, TYPES.s, strBegin, elementObjSettingsBlockValues); + settingsMargin = margin; + + var viewportScroll = { + l: _scrollHorizontalInfo._currentScroll, + t: _scrollVerticalInfo._currentScroll + }; + // use padding element instead of viewport element because padding element has never padding, margin or position applied. + var viewportOffset = _paddingElement.offset(); + + //get coordinates + var elementOffset = finalElement.offset(); + var doNotScroll = { + x: settingsScroll.x == strNever || settingsAxis == _strY, + y: settingsScroll.y == strNever || settingsAxis == _strX + }; + elementOffset[_strTop] -= settingsMargin[0]; + elementOffset[_strLeft] -= settingsMargin[3]; + var elementScrollCoordinates = { + x: MATH.round(elementOffset[_strLeft] - viewportOffset[_strLeft] + viewportScroll.l), + y: MATH.round(elementOffset[_strTop] - viewportOffset[_strTop] + viewportScroll.t) + }; + if (_isRTL) { + if (!_rtlScrollBehavior.n && !_rtlScrollBehavior.i) + elementScrollCoordinates.x = MATH.round(viewportOffset[_strLeft] - elementOffset[_strLeft] + viewportScroll.l); + if (_rtlScrollBehavior.n && normalizeRTL) + elementScrollCoordinates.x *= -1; + if (_rtlScrollBehavior.i && normalizeRTL) + elementScrollCoordinates.x = MATH.round(viewportOffset[_strLeft] - elementOffset[_strLeft] + (_scrollHorizontalInfo._maxScroll - viewportScroll.l)); + } + + //measuring is required + if (settingsBlock.x != strBegin || settingsBlock.y != strBegin || settingsScroll.x == strIfNeeded || settingsScroll.y == strIfNeeded || _isRTL) { + var measuringElm = finalElement[0]; + var rawElementSize = _supportTransform ? measuringElm[LEXICON.bCR]() : { + width: measuringElm[LEXICON.oW], + height: measuringElm[LEXICON.oH] + }; + var elementSize = { + w: rawElementSize[_strWidth] + settingsMargin[3] + settingsMargin[1], + h: rawElementSize[_strHeight] + settingsMargin[0] + settingsMargin[2] + }; + var finalizeBlock = function (isX) { + var vars = getScrollbarVars(isX); + var wh = vars._w_h; + var lt = vars._left_top; + var xy = vars._x_y; + var blockIsEnd = settingsBlock[xy] == (isX ? _isRTL ? strBegin : strEnd : strEnd); + var blockIsCenter = settingsBlock[xy] == strCenter; + var blockIsNearest = settingsBlock[xy] == strNearest; + var scrollNever = settingsScroll[xy] == strNever; + var scrollIfNeeded = settingsScroll[xy] == strIfNeeded; + var vpSize = _viewportSize[wh]; + var vpOffset = viewportOffset[lt]; + var elSize = elementSize[wh]; + var elOffset = elementOffset[lt]; + var divide = blockIsCenter ? 2 : 1; + var elementCenterOffset = elOffset + (elSize / 2); + var viewportCenterOffset = vpOffset + (vpSize / 2); + var isInView = + elSize <= vpSize + && elOffset >= vpOffset + && elOffset + elSize <= vpOffset + vpSize; + + if (scrollNever) + doNotScroll[xy] = true; + else if (!doNotScroll[xy]) { + if (blockIsNearest || scrollIfNeeded) { + doNotScroll[xy] = scrollIfNeeded ? isInView : false; + blockIsEnd = elSize < vpSize ? elementCenterOffset > viewportCenterOffset : elementCenterOffset < viewportCenterOffset; + } + elementScrollCoordinates[xy] -= blockIsEnd || blockIsCenter ? ((vpSize / divide) - (elSize / divide)) * (isX && _isRTL && normalizeRTL ? -1 : 1) : 0; + } + }; + finalizeBlock(true); + finalizeBlock(false); + } + + if (doNotScroll.y) + delete elementScrollCoordinates.y; + if (doNotScroll.x) + delete elementScrollCoordinates.x; + + coordinates = elementScrollCoordinates; + } + } + + finalScroll[_strScrollLeft] = getFinalScroll(true, getRawScroll(true, coordinates)); + finalScroll[_strScrollTop] = getFinalScroll(false, getRawScroll(false, coordinates)); + doScrollLeft = finalScroll[_strScrollLeft] !== undefined; + doScrollTop = finalScroll[_strScrollTop] !== undefined; + + if ((doScrollLeft || doScrollTop) && (duration > 0 || durationIsObject)) { + if (durationIsObject) { + duration.complete = proxyCompleteCallback; + _viewportElement.animate(finalScroll, duration); + } + else { + animationOptions = { + duration: duration, + complete: proxyCompleteCallback + }; + if (COMPATIBILITY.isA(easing) || FRAMEWORK.isPlainObject(easing)) { + specialEasing[_strScrollLeft] = easing[0] || easing.x; + specialEasing[_strScrollTop] = easing[1] || easing.y; + animationOptions.specialEasing = specialEasing; + } + else { + animationOptions.easing = easing; + } + _viewportElement.animate(finalScroll, animationOptions); + } + } + else { + if (doScrollLeft) + _viewportElement[_strScrollLeft](finalScroll[_strScrollLeft]); + if (doScrollTop) + _viewportElement[_strScrollTop](finalScroll[_strScrollTop]); + updateScrollbarInfos(); + } + }; + + /** + * Stops all scroll animations. + * @returns {*} The current OverlayScrollbars instance (for chaining). + */ + _base.scrollStop = function (param1, param2, param3) { + _viewportElement.stop(param1, param2, param3); + return _base; + }; + + /** + * Returns all relevant elements. + * @param elementName The name of the element which shall be returned. + * @returns {{target: *, host: *, padding: *, viewport: *, content: *, scrollbarHorizontal: {scrollbar: *, track: *, handle: *}, scrollbarVertical: {scrollbar: *, track: *, handle: *}, scrollbarCorner: *} | *} + */ + _base.getElements = function (elementName) { + var obj = { + target: _targetElementNative, + host: _hostElementNative, + padding: _paddingElementNative, + viewport: _viewportElementNative, + content: _contentElementNative, + scrollbarHorizontal: { + scrollbar: _scrollbarHorizontalElement[0], + track: _scrollbarHorizontalTrackElement[0], + handle: _scrollbarHorizontalHandleElement[0] + }, + scrollbarVertical: { + scrollbar: _scrollbarVerticalElement[0], + track: _scrollbarVerticalTrackElement[0], + handle: _scrollbarVerticalHandleElement[0] + }, + scrollbarCorner: _scrollbarCornerElement[0] + }; + return type(elementName) == TYPES.s ? getObjectPropVal(obj, elementName) : obj; + }; + + /** + * Returns a object which describes the current state of this instance. + * @param stateProperty A specific property from the state object which shall be returned. + * @returns {{widthAuto, heightAuto, overflowAmount, hideOverflow, hasOverflow, contentScrollSize, viewportSize, hostSize, autoUpdate} | *} + */ + _base.getState = function (stateProperty) { + function prepare(obj) { + if (!FRAMEWORK.isPlainObject(obj)) + return obj; + var extended = extendDeep({}, obj); + var changePropertyName = function (from, to) { + if (extended[LEXICON.hOP](from)) { + extended[to] = extended[from]; + delete extended[from]; + } + }; + changePropertyName('w', _strWidth); //change w to width + changePropertyName('h', _strHeight); //change h to height + delete extended.c; //delete c (the 'changed' prop) + return extended; + }; + var obj = { + destroyed: !!prepare(_destroyed), + sleeping: !!prepare(_sleeping), + autoUpdate: prepare(!_mutationObserversConnected), + widthAuto: prepare(_widthAutoCache), + heightAuto: prepare(_heightAutoCache), + padding: prepare(_cssPaddingCache), + overflowAmount: prepare(_overflowAmountCache), + hideOverflow: prepare(_hideOverflowCache), + hasOverflow: prepare(_hasOverflowCache), + contentScrollSize: prepare(_contentScrollSizeCache), + viewportSize: prepare(_viewportSize), + hostSize: prepare(_hostSizeCache), + documentMixed: prepare(_documentMixed) + }; + return type(stateProperty) == TYPES.s ? getObjectPropVal(obj, stateProperty) : obj; + }; + + /** + * Gets all or specific extension instance. + * @param extName The name of the extension from which the instance shall be got. + * @returns {{}} The instance of the extension with the given name or undefined if the instance couldn't be found. + */ + _base.ext = function (extName) { + var result; + var privateMethods = _extensionsPrivateMethods.split(' '); + var i = 0; + if (type(extName) == TYPES.s) { + if (_extensions[LEXICON.hOP](extName)) { + result = extendDeep({}, _extensions[extName]); + for (; i < privateMethods.length; i++) + delete result[privateMethods[i]]; + } + } + else { + result = {}; + for (i in _extensions) + result[i] = extendDeep({}, _base.ext(i)); + } + return result; + }; + + /** + * Adds a extension to this instance. + * @param extName The name of the extension which shall be added. + * @param extensionOptions The extension options which shall be used. + * @returns {{}} The instance of the added extension or undefined if the extension couldn't be added properly. + */ + _base.addExt = function (extName, extensionOptions) { + var registeredExtensionObj = _plugin.extension(extName); + var instance; + var instanceAdded; + var instanceContract; + var contractResult; + var contractFulfilled = true; + if (registeredExtensionObj) { + if (!_extensions[LEXICON.hOP](extName)) { + instance = registeredExtensionObj.extensionFactory.call(_base, + extendDeep({}, registeredExtensionObj.defaultOptions), + FRAMEWORK, + COMPATIBILITY); + + if (instance) { + instanceContract = instance.contract; + if (type(instanceContract) == TYPES.f) { + contractResult = instanceContract(window); + contractFulfilled = type(contractResult) == TYPES.b ? contractResult : contractFulfilled; + } + if (contractFulfilled) { + _extensions[extName] = instance; + instanceAdded = instance.added; + if (type(instanceAdded) == TYPES.f) + instanceAdded(extensionOptions); + + return _base.ext(extName); + } + } + } + else + return _base.ext(extName); + } + else + console.warn("A extension with the name \"" + extName + "\" isn't registered."); + }; + + /** + * Removes a extension from this instance. + * @param extName The name of the extension which shall be removed. + * @returns {boolean} True if the extension was removed, false otherwise e.g. if the extension wasn't added before. + */ + _base.removeExt = function (extName) { + var instance = _extensions[extName]; + var instanceRemoved; + if (instance) { + delete _extensions[extName]; + + instanceRemoved = instance.removed; + if (type(instanceRemoved) == TYPES.f) + instanceRemoved(); + + return true; + } + return false; + }; + + /** + * Constructs the plugin. + * @param targetElement The element to which the plugin shall be applied. + * @param options The initial options of the plugin. + * @param extensions The extension(s) which shall be added right after the initialization. + * @returns {boolean} True if the plugin was successfully initialized, false otherwise. + */ + function construct(targetElement, options, extensions) { + _defaultOptions = globals.defaultOptions; + _nativeScrollbarStyling = globals.nativeScrollbarStyling; + _nativeScrollbarSize = extendDeep({}, globals.nativeScrollbarSize); + _nativeScrollbarIsOverlaid = extendDeep({}, globals.nativeScrollbarIsOverlaid); + _overlayScrollbarDummySize = extendDeep({}, globals.overlayScrollbarDummySize); + _rtlScrollBehavior = extendDeep({}, globals.rtlScrollBehavior); + + //parse & set options but don't update + setOptions(extendDeep({}, _defaultOptions, options)); + + _cssCalc = globals.cssCalc; + _msieVersion = globals.msie; + _autoUpdateRecommended = globals.autoUpdateRecommended; + _supportTransition = globals.supportTransition; + _supportTransform = globals.supportTransform; + _supportPassiveEvents = globals.supportPassiveEvents; + _supportResizeObserver = globals.supportResizeObserver; + _supportMutationObserver = globals.supportMutationObserver; + _restrictedMeasuring = globals.restrictedMeasuring; + _documentElement = FRAMEWORK(targetElement.ownerDocument); + _documentElementNative = _documentElement[0]; + _windowElement = FRAMEWORK(_documentElementNative.defaultView || _documentElementNative.parentWindow); + _windowElementNative = _windowElement[0]; + _htmlElement = findFirst(_documentElement, 'html'); + _bodyElement = findFirst(_htmlElement, 'body'); + _targetElement = FRAMEWORK(targetElement); + _targetElementNative = _targetElement[0]; + _isTextarea = _targetElement.is('textarea'); + _isBody = _targetElement.is('body'); + _documentMixed = _documentElementNative !== document; + + /* On a div Element The if checks only whether: + * - the targetElement has the class "os-host" + * - the targetElement has a a child with the class "os-padding" + * + * If that's the case, its assumed the DOM has already the following structure: + * (The ".os-host" element is the targetElement) + * + *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * + * ===================================================================================== + * + * On a Textarea Element The if checks only whether: + * - the targetElement has the class "os-textarea" + * - the targetElement is inside a element with the class "os-content" + * + * If that's the case, its assumed the DOM has already the following structure: + * (The ".os-textarea" (textarea) element is the targetElement) + * + *
+ *
+ *
+ *
+ *
+ *
+ * + *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */ + _domExists = _isTextarea + ? _targetElement.hasClass(_classNameTextareaElement) && _targetElement.parent().hasClass(_classNameContentElement) + : _targetElement.hasClass(_classNameHostElement) && _targetElement.children(_strDot + _classNamePaddingElement)[LEXICON.l]; + + var initBodyScroll; + var bodyMouseTouchDownListener; + + //check if the plugin hasn't to be initialized + if (_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y && !_currentPreparedOptions.nativeScrollbarsOverlaid.initialize) { + dispatchCallback('onInitializationWithdrawn'); + if (_domExists) { + setupStructureDOM(true); + setupScrollbarsDOM(true); + setupScrollbarCornerDOM(true); + } + + _destroyed = true; + _sleeping = true; + + return _base; + } + + if (_isBody) { + initBodyScroll = {}; + initBodyScroll.l = MATH.max(_targetElement[_strScrollLeft](), _htmlElement[_strScrollLeft](), _windowElement[_strScrollLeft]()); + initBodyScroll.t = MATH.max(_targetElement[_strScrollTop](), _htmlElement[_strScrollTop](), _windowElement[_strScrollTop]()); + + bodyMouseTouchDownListener = function () { + _viewportElement.removeAttr(LEXICON.ti); + setupResponsiveEventListener(_viewportElement, _strMouseTouchDownEvent, bodyMouseTouchDownListener, true, true); + } + } + + //build OverlayScrollbars DOM + setupStructureDOM(); + setupScrollbarsDOM(); + setupScrollbarCornerDOM(); + + //create OverlayScrollbars events + setupStructureEvents(); + setupScrollbarEvents(true); + setupScrollbarEvents(false); + setupScrollbarCornerEvents(); + + //create mutation observers + createMutationObservers(); + + //build resize observer for the host element + setupResizeObserver(_sizeObserverElement, hostOnResized); + + if (_isBody) { + //apply the body scroll to handle it right in the update method + _viewportElement[_strScrollLeft](initBodyScroll.l)[_strScrollTop](initBodyScroll.t); + + //set the focus on the viewport element so you dont have to click on the page to use keyboard keys (up / down / space) for scrolling + if (document.activeElement == targetElement && _viewportElementNative.focus) { + //set a tabindex to make the viewportElement focusable + _viewportElement.attr(LEXICON.ti, '-1'); + _viewportElementNative.focus(); + + /* the tabindex has to be removed due to; + * If you set the tabindex attribute on an
, then its child content cannot be scrolled with the arrow keys unless you set tabindex on the content, too + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex + */ + setupResponsiveEventListener(_viewportElement, _strMouseTouchDownEvent, bodyMouseTouchDownListener, false, true); + } + } + + //update for the first time & initialize cache + _base.update(_strAuto); + + //the plugin is initialized now! + _initialized = true; + dispatchCallback('onInitialized'); + + //call all callbacks which would fire before the initialized was complete + each(_callbacksInitQeueue, function (index, value) { dispatchCallback(value.n, value.a); }); + _callbacksInitQeueue = []; + + //add extensions + if (type(extensions) == TYPES.s) + extensions = [extensions]; + if (COMPATIBILITY.isA(extensions)) + each(extensions, function (index, value) { _base.addExt(value); }); + else if (FRAMEWORK.isPlainObject(extensions)) + each(extensions, function (key, value) { _base.addExt(key, value); }); + + //add the transition class for transitions AFTER the first update & AFTER the applied extensions (for preventing unwanted transitions) + setTimeout(function () { + if (_supportTransition && !_destroyed) + addClass(_hostElement, _classNameHostTransition); + }, 333); + + return _base; + } + + if (_plugin.valid(construct(pluginTargetElement, options, extensions))) { + INSTANCES(pluginTargetElement, _base); + } + + return _base; + } + + /** + * Initializes a new OverlayScrollbarsInstance object or changes options if already initialized or returns the current instance. + * @param pluginTargetElements The elements to which the Plugin shall be initialized. + * @param options The custom options with which the plugin shall be initialized. + * @param extensions The extension(s) which shall be added right after initialization. + * @returns {*} + */ + _plugin = window[PLUGINNAME] = function (pluginTargetElements, options, extensions) { + if (arguments[LEXICON.l] === 0) + return this; + + var arr = []; + var optsIsPlainObj = FRAMEWORK.isPlainObject(options); + var inst; + var result; + + //pluginTargetElements is null or undefined + if (!pluginTargetElements) + return optsIsPlainObj || !options ? result : arr; + + /* + pluginTargetElements will be converted to: + 1. A jQueryElement Array + 2. A HTMLElement Array + 3. A Array with a single HTML Element + so pluginTargetElements is always a array. + */ + pluginTargetElements = pluginTargetElements[LEXICON.l] != undefined ? pluginTargetElements : [pluginTargetElements[0] || pluginTargetElements]; + initOverlayScrollbarsStatics(); + + if (pluginTargetElements[LEXICON.l] > 0) { + if (optsIsPlainObj) { + FRAMEWORK.each(pluginTargetElements, function (i, v) { + inst = v; + if (inst !== undefined) + arr.push(OverlayScrollbarsInstance(inst, options, extensions, _pluginsGlobals, _pluginsAutoUpdateLoop)); + }); + } + else { + FRAMEWORK.each(pluginTargetElements, function (i, v) { + inst = INSTANCES(v); + if ((options === '!' && _plugin.valid(inst)) || (COMPATIBILITY.type(options) == TYPES.f && options(v, inst))) + arr.push(inst); + else if (options === undefined) + arr.push(inst); + }); + } + result = arr[LEXICON.l] === 1 ? arr[0] : arr; + } + return result; + }; + + /** + * Returns a object which contains global information about the plugin and each instance of it. + * The returned object is just a copy, that means that changes to the returned object won't have any effect to the original object. + */ + _plugin.globals = function () { + initOverlayScrollbarsStatics(); + var globals = FRAMEWORK.extend(true, {}, _pluginsGlobals); + delete globals['msie']; + return globals; + }; + + /** + * Gets or Sets the default options for each new plugin initialization. + * @param newDefaultOptions The object with which the default options shall be extended. + */ + _plugin.defaultOptions = function (newDefaultOptions) { + initOverlayScrollbarsStatics(); + var currDefaultOptions = _pluginsGlobals.defaultOptions; + if (newDefaultOptions === undefined) + return FRAMEWORK.extend(true, {}, currDefaultOptions); + + //set the new default options + _pluginsGlobals.defaultOptions = FRAMEWORK.extend(true, {}, currDefaultOptions, _pluginsOptions._validate(newDefaultOptions, _pluginsOptions._template, true, currDefaultOptions)._default); + }; + + /** + * Checks whether the passed instance is a non-destroyed OverlayScrollbars instance. + * @param osInstance The potential OverlayScrollbars instance which shall be checked. + * @returns {boolean} True if the passed value is a non-destroyed OverlayScrollbars instance, false otherwise. + */ + _plugin.valid = function (osInstance) { + return osInstance instanceof _plugin && !osInstance.getState().destroyed; + }; + + /** + * Registers, Unregisters or returns a extension. + * Register: Pass the name and the extension. (defaultOptions is optional) + * Unregister: Pass the name and anything except a function as extension parameter. + * Get extension: Pass the name of the extension which shall be got. + * Get all extensions: Pass no arguments. + * @param extensionName The name of the extension which shall be registered, unregistered or returned. + * @param extension A function which generates the instance of the extension or anything other to remove a already registered extension. + * @param defaultOptions The default options which shall be used for the registered extension. + */ + _plugin.extension = function (extensionName, extension, defaultOptions) { + var extNameTypeString = COMPATIBILITY.type(extensionName) == TYPES.s; + var argLen = arguments[LEXICON.l]; + var i = 0; + if (argLen < 1 || !extNameTypeString) { + //return a copy of all extension objects + return FRAMEWORK.extend(true, { length: _pluginsExtensions[LEXICON.l] }, _pluginsExtensions); + } + else if (extNameTypeString) { + if (COMPATIBILITY.type(extension) == TYPES.f) { + //register extension + _pluginsExtensions.push({ + name: extensionName, + extensionFactory: extension, + defaultOptions: defaultOptions + }); + } + else { + for (; i < _pluginsExtensions[LEXICON.l]; i++) { + if (_pluginsExtensions[i].name === extensionName) { + if (argLen > 1) + _pluginsExtensions.splice(i, 1); //remove extension + else + return FRAMEWORK.extend(true, {}, _pluginsExtensions[i]); //return extension with the given name + } + } + } + } + }; + + return _plugin; + })(); + + if (JQUERY && JQUERY.fn) { + /** + * The jQuery initialization interface. + * @param options The initial options for the construction of the plugin. To initialize the plugin, this option has to be a object! If it isn't a object, the instance(s) are returned and the plugin wont be initialized. + * @param extensions The extension(s) which shall be added right after initialization. + * @returns {*} After initialization it returns the jQuery element array, else it returns the instance(s) of the elements which are selected. + */ + JQUERY.fn.overlayScrollbars = function (options, extensions) { + var _elements = this; + if (JQUERY.isPlainObject(options)) { + JQUERY.each(_elements, function () { PLUGIN(this, options, extensions); }); + return _elements; + } + else + return PLUGIN(_elements, options); + }; + } + return PLUGIN; + } +)); \ No newline at end of file diff --git a/src/main/resources/static/plugins/overlayScrollbars/js/jquery.overlayScrollbars.min.js b/src/main/resources/static/plugins/overlayScrollbars/js/jquery.overlayScrollbars.min.js new file mode 100644 index 0000000..3654b72 --- /dev/null +++ b/src/main/resources/static/plugins/overlayScrollbars/js/jquery.overlayScrollbars.min.js @@ -0,0 +1,13 @@ +/*! + * OverlayScrollbars + * https://github.com/KingSora/OverlayScrollbars + * + * Version: 1.13.0 + * + * Copyright KingSora | Rene Haas. + * https://github.com/KingSora + * + * Released under the MIT license. + * Date: 02.08.2020 + */ +!function(t,r){"function"==typeof define&&define.amd?define(["jquery"],function(n){return r(t,t.document,undefined,n)}):"object"==typeof module&&"object"==typeof module.exports?module.exports=r(t,t.document,undefined,require("jquery")):r(t,t.document,undefined,t.jQuery)}("undefined"!=typeof window?window:this,function(vi,di,hi,n){"use strict";var o,l,f,a,pi="object",bi="function",yi="array",mi="string",gi="boolean",wi="number",t="null",xi={c:"class",s:"style",i:"id",l:"length",p:"prototype",ti:"tabindex",oH:"offsetHeight",cH:"clientHeight",sH:"scrollHeight",oW:"offsetWidth",cW:"clientWidth",sW:"scrollWidth",hOP:"hasOwnProperty",bCR:"getBoundingClientRect"},_i=(o={},l={},{e:f=["-webkit-","-moz-","-o-","-ms-"],o:a=["WebKit","Moz","O","MS"],u:function(n){var t=l[n];if(l[xi.hOP](n))return t;for(var r,e,i,o=c(n),a=di.createElement("div")[xi.s],u=0;u
'),o=z[0],e=Ci(z.children("div").eq(0));S.append(z),z.hide().show();var t,r,a,u,f,c,s,l,v,d=T(o),h={x:0===d.x,y:0===d.y},p=(r=vi.navigator.userAgent,u="substring",f=r[a="indexOf"]("MSIE "),c=r[a]("Trident/"),s=r[a]("Edge/"),l=r[a]("rv:"),v=parseInt,0i&&(t.update("auto"),h[f]=new Date(o+=i)),n=Oi.max(1,Oi.min(n,i)));b=n}}else b=33};this.add=function(n){-1===e(n,d)&&(d.push(n),h.push(s()),0/g,(l?"-":ye)+we)[v](/px/g,ye)[v](/%/g," * "+c*(s&&Ot.n?-1:1)/100)[v](/vw/g," * "+ee.w)[v](/vh/g," * "+ee.h),ii(isNaN(t)?ii(d(t),!0).toFixed():t)):t)!==hi&&!isNaN(e)&&cn(e)==wi){var h=w&&s,p=f*(h&&Ot.n?-1:1),b=h&&Ot.i,y=h&&Ot.n;switch(p=b?c-p:p,r){case"+=":i=p+e;break;case"-=":i=p-e;break;case"*=":i=p*e;break;case"/=":i=p/e;break;default:i=e}i=b?c-i:i,i*=y?-1:1,i=s&&Ot.n?Oi.min(0,Oi.max(c,i)):Oi.max(0,Oi.min(c,i))}return i===f?hi:i}function V(n,t,r,e){var i,o,a=[r,r],u=cn(n);if(u==t)n=[n,n];else if(u==yi){if(2<(i=n[H])||i<1)n=a;else for(1===i&&(n[1]=r),c=0;c=t.left&&n.clientX<=t.right&&n.clientY>=t.top&&n.clientY<=t.bottom||Zn(),(Kr||Gr)&&Ge(!1)}}function M(n){i=Zt[H](),i=isNaN(i)?0:i,(Vt&&z&&!Ot.n||!Vt)&&(i=i<0?0:i),k=vt()[A],O=c(n),V=!s(a),ci(P,En),ci(e.cn,o),ci(e.an,o),Yn(F,[B,q,J],[h,D,tt]),Si.rAF()(function(){Yn(F,u,d,!1,{$:!0})}),!I&&w||Si.prvD(n),Si.stpP(n)}Kn(e.cn,$,function p(n){W(n)&&M(n)}),Kn(e.un,[$,X,Y],[function E(n){if(W(n)){var d,t=e.sn.D/Math.round(Oi.min(1,ee[e.F]/vr[e.F])*e.sn.I),h=Oi.round(ee[e.F]*t),p=270*t,b=400*t,y=e.un.offset()[e.P],r=n.ctrlKey,m=n.shiftKey,g=m&&r,w=!0,x=function(n){V&&Qe(z,n)},_=function(){x(),M(n)},S=function(){if(!Lt){var n=(O-y)*k,t=C.W,r=C.I,e=C.D,i=C.N,o=C.R,a=p*R,u=w?Oi.max(b,a):a,f=i*((n-e/2)/(r-e)),c=Vt&&z&&(!Ot.i&&!Ot.n||Mr),s=c?t"+(n||ye)+""}function pt(n,t){var r=cn(t)==gi,e=!r&&t||Kt;return p&&!e[xi.l]?null:p?e[r?"children":"find"](W+n.replace(/\s/g,W)).eq(0):Ci(ai(n))}function bt(n,t){for(var r,e=t.split(W),i=0;i