// eslint-disable-next-line no-redeclare
// noinspection JSVoidFunctionReturnValueUsed
// eslint-disable-next-line no-redeclare
let jsPanel = {
version: '4.16.1',
date: '2022-11-03 09:18',
ajaxAlwaysCallbacks: [],
autopositionSpacing: 4,
closeOnEscape: (() => {
document.addEventListener('keydown', e => {
if (e.key === 'Escape' || e.code === 'Escape' || e.key === 'Esc') {
jsPanel
.getPanels(panel => panel.classList.contains('jsPanel')) // Array is sorted by z-index (the highest first)
.some(item => {
if (item.options.closeOnEscape) {
if (typeof item.options.closeOnEscape === 'function') {
return item.options.closeOnEscape.call(item, item);
// if return value is falsy next panel in sequence will close, otherwise processing of Array.prototype.some() stops
} else {
item.close(null, true);
return true;
}
}
return false;
});
}
}, false);
})(),
defaults: {
boxShadow: 3,
container: 'window',
contentSize: { width: '400px', height: '200px' }, // must be an object
dragit: {
cursor: 'move',
handles: '.jsPanel-headerlogo, .jsPanel-titlebar, .jsPanel-ftr', // do not use .jsPanel-headerbar
opacity: 0.8,
disableOnMaximized: true,
},
header: true,
headerTitle: 'jsPanel',
headerControls: {size: 'md'}, // must be an object
iconfont: undefined,
maximizedMargin: 0,
minimizeTo: 'default',
paneltype: 'standard',
position: {my: 'center', at: 'center'}, // default position.of MUST NOT be set with new positioning engine
resizeit: {
handles: 'n, e, s, w, ne, se, sw, nw',
minWidth: 128,
minHeight: 38,
},
theme: 'default'
},
defaultAutocloseConfig: {time: '8s', progressbar: true},
defaultSnapConfig: {
sensitivity: 70,
trigger: 'panel',
active: 'both',
},
extensions: {},
globalCallbacks: false,
icons: {
close: ``,
maximize: ``,
normalize: ``,
minimize: ``,
smallify: ``,
},
idCounter: 0,
isIE: (() => {return document.documentMode || false;})(),
pointerdown: 'onpointerdown' in window ? ['pointerdown'] : 'ontouchend' in window ? ['touchstart', 'mousedown'] : ['mousedown'],
pointermove: 'onpointermove' in window ? ['pointermove'] : 'ontouchend' in window ? ['touchmove', 'mousemove'] : ['mousemove'],
pointerup: 'onpointerup' in window ? ['pointerup'] : 'ontouchend' in window ? ['touchend', 'mouseup'] : ['mouseup'],
polyfills: (() => {
// Polyfills for IE11 only
// Object.assign polyfill - https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
if (!Object.assign) {
Object.defineProperty(Object, 'assign', {
enumerable: false,
configurable: true,
writable: true,
value: function (target) {
if (target === undefined || target === null) {
throw new TypeError('Cannot convert first argument to object');
}
let to = Object(target);
for (let i = 1; i < arguments.length; i++) {
let nextSource = arguments[i];
if (nextSource === undefined || nextSource === null) {
continue;
}
nextSource = Object(nextSource);
let keysArray = Object.keys(Object(nextSource));
for (let nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
let nextKey = keysArray[nextIndex];
let desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
if (desc !== undefined && desc.enumerable) {
to[nextKey] = nextSource[nextKey];
}
}
}
return to;
},
});
}
// Object.entries() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
if (!Object.entries) {
Object.entries = function( obj ){
// noinspection ES6ConvertVarToLetConst
var ownProps = Object.keys( obj ),
i = ownProps.length,
resArray = new Array(i); // preallocate the Array
while (i--)
resArray[i] = [ownProps[i], obj[ownProps[i]]];
return resArray;
};
}
// NodeList.prototype.forEach() polyfill - https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = function (callback, thisArg) {
thisArg = thisArg || window;
for (let i = 0; i < this.length; i++) {
callback.call(thisArg, this[i], i, this);
}
};
}
// .append() polyfill - https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append
(function (arr) {
arr.forEach(function (item) {
item.append =
item.append ||
function () {
let argArr = Array.prototype.slice.call(arguments),
docFrag = document.createDocumentFragment();
argArr.forEach(function (argItem) {
let isNode = argItem instanceof Node;
docFrag.appendChild(isNode ? argItem : document.createTextNode(String(argItem)));
});
this.appendChild(docFrag);
};
});
})([Element.prototype, Document.prototype, DocumentFragment.prototype]);
// Element.closest() polyfill - https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
if (window.Element && !Element.prototype.closest) {
// noinspection JSValidateTypes
Element.prototype.closest = function (s) {
// noinspection JSUnresolvedVariable
let matches = (this.document || this.ownerDocument).querySelectorAll(s),
i,
el = this;
do {
i = matches.length;
// eslint-disable-next-line no-empty
while (--i >= 0 && matches.item(i) !== el) {}
} while (i < 0 && (el = el.parentElement));
return el;
};
}
// CustomEvent - https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
(function () {
if (typeof window.CustomEvent === 'function') return false;
function CustomEvent(event, params) {
params = params || {
bubbles: false,
cancelable: false,
detail: undefined,
};
let evt = document.createEvent('CustomEvent');
// noinspection JSDeprecatedSymbols
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
// noinspection JSValidateTypes
CustomEvent.prototype = window.Event.prototype;
// noinspection JSValidateTypes
window.CustomEvent = CustomEvent;
})();
// String.prototype.endsWith() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
if (!String.prototype.endsWith) {
String.prototype.endsWith = function(search, this_len) {
if (this_len === undefined || this_len > this.length) {
this_len = this.length;
}
return this.substring(this_len - search.length, this_len) === search;
};
}
// String.prototype.startsWith() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
if (!String.prototype.startsWith) {
Object.defineProperty(String.prototype, 'startsWith', {
value: function(search, rawPos) {
// noinspection ES6ConvertVarToLetConst
var pos = rawPos > 0 ? rawPos|0 : 0;
return this.substring(pos, pos + search.length) === search;
}
});
}
// String.prototype.includes() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
if (!String.prototype.includes) {
String.prototype.includes = function(search, start) {
'use strict';
if (search instanceof RegExp) {
throw TypeError('first argument must not be a RegExp');
}
if (start === undefined) { start = 0; }
return this.indexOf(search, start) !== -1;
};
}
// String.prototype repeat() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
if (!String.prototype.repeat) {
String.prototype.repeat = function(count) {
'use strict';
if (this == null)
throw new TypeError('can\'t convert ' + this + ' to object');
// noinspection ES6ConvertVarToLetConst
var str = '' + this;
count = +count;
if (count != count)
count = 0;
if (count < 0)
throw new RangeError('repeat count must be non-negative');
if (count == Infinity)
throw new RangeError('repeat count must be less than infinity');
count = Math.floor(count);
if (str.length == 0 || count == 0)
return '';
if (str.length * count >= 1 << 28)
throw new RangeError('repeat count must not overflow maximum string size');
// noinspection ES6ConvertVarToLetConst
var maxCount = str.length * count;
count = Math.floor(Math.log(count) / Math.log(2));
while (count) {
str += str;
count--;
}
str += str.substring(0, maxCount - str.length);
return str;
};
}
// Number.isInteger() -
Number.isInteger =
Number.isInteger ||
function (value) {
return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
};
// Array.prototype.includes() - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
value: function (searchElement, fromIndex) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
// 1. Let O be ? ToObject(this value).
let o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
let len = o.length >>> 0;
// 3. If len is 0, return false.
if (len === 0) {
return false;
}
// 4. Let n be ? ToInteger(fromIndex).
// (If fromIndex is undefined, this step produces the value 0.)
let n = fromIndex | 0;
// 5. If n ≥ 0, then
// a. Let k be n.
// 6. Else n < 0,
// a. Let k be len + n.
// b. If k < 0, let k be 0.
let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
function sameValueZero(x, y) {
return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
}
// 7. Repeat, while k < len
while (k < len) {
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
// b. If SameValueZero(searchElement, elementK) is true, return true.
if (sameValueZero(o[k], searchElement)) {
return true;
}
// c. Increase k by 1.
k++;
}
// 8. Return false
return false;
},
});
}
})(),
ziBase: 100,
colorFilledLight: 0.81,
colorFilledDark: 0.08,
colorFilled: 0,
colorBrightnessThreshold: 0.55,
colorNames: {
default: 'b0bec5', // Material Design bluegray200
secondary: 'b0bec5',
primary: '01579b', // Material Design lightblue900
info: '039be5', // Material Design lightblue600
success: '2e7d32', // Material Design green800
warning: 'f57f17', // Material Design yellow900
danger: 'dd2c00', // Material Design deeporangeA700
light: 'e0e0e0', // Material Design gray300
dark: '263238', // Material Design bluegray900
// https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords
aliceblue: 'f0f8ff',
antiquewhite: 'faebd7',
aqua: '00ffff',
aquamarine: '7fffd4',
azure: 'f0ffff',
beige: 'f5f5dc',
bisque: 'ffe4c4',
black: '000000',
blanchedalmond: 'ffebcd',
blue: '0000ff',
blueviolet: '8a2be2',
brown: 'a52a2a',
burlywood: 'deb887',
cadetblue: '5f9ea0',
chartreuse: '7fff00',
chocolate: 'd2691e',
coral: 'ff7f50',
cornflowerblue: '6495ed',
cornsilk: 'fff8dc',
crimson: 'dc143c',
cyan: '00ffff',
darkblue: '00008b',
darkcyan: '008b8b',
darkgoldenrod: 'b8860b',
darkgray: 'a9a9a9',
darkgrey: 'a9a9a9',
darkgreen: '006400',
darkkhaki: 'bdb76b',
darkmagenta: '8b008b',
darkolivegreen: '556b2f',
darkorange: 'ff8c00',
darkorchid: '9932cc',
darkred: '8b0000',
darksalmon: 'e9967a',
darkseagreen: '8fbc8f',
darkslateblue: '483d8b',
darkslategray: '2f4f4f',
darkslategrey: '2f4f4f',
darkturquoise: '00ced1',
darkviolet: '9400d3',
deeppink: 'ff1493',
deepskyblue: '00bfff',
dimgray: '696969',
dimgrey: '696969',
dodgerblue: '1e90ff',
firebrick: 'b22222',
floralwhite: 'fffaf0',
forestgreen: '228b22',
fuchsia: 'ff00ff',
gainsboro: 'dcdcdc',
ghostwhite: 'f8f8ff',
gold: 'ffd700',
goldenrod: 'daa520',
gray: '808080',
grey: '808080',
green: '008000',
greenyellow: 'adff2f',
honeydew: 'f0fff0',
hotpink: 'ff69b4',
indianred: 'cd5c5c',
indigo: '4b0082',
ivory: 'fffff0',
khaki: 'f0e68c',
lavender: 'e6e6fa',
lavenderblush: 'fff0f5',
lawngreen: '7cfc00',
lemonchiffon: 'fffacd',
lightblue: 'add8e6',
lightcoral: 'f08080',
lightcyan: 'e0ffff',
lightgoldenrodyellow: 'fafad2',
lightgray: 'd3d3d3',
lightgrey: 'd3d3d3',
lightgreen: '90ee90',
lightpink: 'ffb6c1',
lightsalmon: 'ffa07a',
lightseagreen: '20b2aa',
lightskyblue: '87cefa',
lightslategray: '778899',
lightslategrey: '778899',
lightsteelblue: 'b0c4de',
lightyellow: 'ffffe0',
lime: '00ff00',
limegreen: '32cd32',
linen: 'faf0e6',
magenta: 'ff00ff',
maroon: '800000',
mediumaquamarine: '66cdaa',
mediumblue: '0000cd',
mediumorchid: 'ba55d3',
mediumpurple: '9370d8',
mediumseagreen: '3cb371',
mediumslateblue: '7b68ee',
mediumspringgreen: '00fa9a',
mediumturquoise: '48d1cc',
mediumvioletred: 'c71585',
midnightblue: '191970',
mintcream: 'f5fffa',
mistyrose: 'ffe4e1',
moccasin: 'ffe4b5',
navajowhite: 'ffdead',
navy: '000080',
oldlace: 'fdf5e6',
olive: '808000',
olivedrab: '6b8e23',
orange: 'ffa500',
orangered: 'ff4500',
orchid: 'da70d6',
palegoldenrod: 'eee8aa',
palegreen: '98fb98',
paleturquoise: 'afeeee',
palevioletred: 'd87093',
papayawhip: 'ffefd5',
peachpuff: 'ffdab9',
peru: 'cd853f',
pink: 'ffc0cb',
plum: 'dda0dd',
powderblue: 'b0e0e6',
purple: '800080',
rebeccapurple: '663399',
red: 'ff0000',
rosybrown: 'bc8f8f',
royalblue: '4169e1',
saddlebrown: '8b4513',
salmon: 'fa8072',
sandybrown: 'f4a460',
seagreen: '2e8b57',
seashell: 'fff5ee',
sienna: 'a0522d',
silver: 'c0c0c0',
skyblue: '87ceeb',
slateblue: '6a5acd',
slategray: '708090',
slategrey: '708090',
snow: 'fffafa',
springgreen: '00ff7f',
steelblue: '4682b4',
tan: 'd2b48c',
teal: '008080',
thistle: 'd8bfd8',
tomato: 'ff6347',
turquoise: '40e0d0',
violet: 'ee82ee',
wheat: 'f5deb3',
white: 'ffffff',
whitesmoke: 'f5f5f5',
yellow: 'ffff00',
yellowgreen: '9acd32',
/* Material Design Colors https://material.io/design/color/the-color-system.html#tools-for-picking-colors */
grey50: 'fafafa',
grey100: 'f5f5f5',
grey200: 'eeeeee',
grey300: 'e0e0e0',
grey400: 'bdbdbd',
grey500: '9e9e9e',
grey600: '757575',
grey700: '616161',
grey800: '424242',
grey900: '212121',
gray50: 'fafafa',
gray100: 'f5f5f5',
gray200: 'eeeeee',
gray300: 'e0e0e0',
gray400: 'bdbdbd',
gray500: '9e9e9e',
gray600: '757575',
gray700: '616161',
gray800: '424242',
gray900: '212121',
bluegrey50: 'eceff1',
bluegrey100: 'CFD8DC',
bluegrey200: 'B0BEC5',
bluegrey300: '90A4AE',
bluegrey400: '78909C',
bluegrey500: '607D8B',
bluegrey600: '546E7A',
bluegrey700: '455A64',
bluegrey800: '37474F',
bluegrey900: '263238',
bluegray50: 'eceff1',
bluegray100: 'CFD8DC',
bluegray200: 'B0BEC5',
bluegray300: '90A4AE',
bluegray400: '78909C',
bluegray500: '607D8B',
bluegray600: '546E7A',
bluegray700: '455A64',
bluegray800: '37474F',
bluegray900: '263238',
red50: 'FFEBEE',
red100: 'FFCDD2',
red200: 'EF9A9A',
red300: 'E57373',
red400: 'EF5350',
red500: 'F44336',
red600: 'E53935',
red700: 'D32F2F',
red800: 'C62828',
red900: 'B71C1C',
reda100: 'FF8A80',
reda200: 'FF5252',
reda400: 'FF1744',
reda700: 'D50000',
pink50: 'FCE4EC',
pink100: 'F8BBD0',
pink200: 'F48FB1',
pink300: 'F06292',
pink400: 'EC407A',
pink500: 'E91E63',
pink600: 'D81B60',
pink700: 'C2185B',
pink800: 'AD1457',
pink900: '880E4F',
pinka100: 'FF80AB',
pinka200: 'FF4081',
pinka400: 'F50057',
pinka700: 'C51162',
purple50: 'F3E5F5',
purple100: 'E1BEE7',
purple200: 'CE93D8',
purple300: 'BA68C8',
purple400: 'AB47BC',
purple500: '9C27B0',
purple600: '8E24AA',
purple700: '7B1FA2',
purple800: '6A1B9A',
purple900: '4A148C',
purplea100: 'EA80FC',
purplea200: 'E040FB',
purplea400: 'D500F9',
purplea700: 'AA00FF',
deeppurple50: 'EDE7F6',
deeppurple100: 'D1C4E9',
deeppurple200: 'B39DDB',
deeppurple300: '9575CD',
deeppurple400: '7E57C2',
deeppurple500: '673AB7',
deeppurple600: '5E35B1',
deeppurple700: '512DA8',
deeppurple800: '4527A0',
deeppurple900: '311B92',
deeppurplea100: 'B388FF',
deeppurplea200: '7C4DFF',
deeppurplea400: '651FFF',
deeppurplea700: '6200EA',
indigo50: 'E8EAF6',
indigo100: 'C5CAE9',
indigo200: '9FA8DA',
indigo300: '7986CB',
indigo400: '5C6BC0',
indigo500: '3F51B5',
indigo600: '3949AB',
indigo700: '303F9F',
indigo800: '283593',
indigo900: '1A237E',
indigoa100: '8C9EFF',
indigoa200: '536DFE',
indigoa400: '3D5AFE',
indigoa700: '304FFE',
blue50: 'E3F2FD',
blue100: 'BBDEFB',
blue200: '90CAF9',
blue300: '64B5F6',
blue400: '42A5F5',
blue500: '2196F3',
blue600: '1E88E5',
blue700: '1976D2',
blue800: '1565C0',
blue900: '0D47A1',
bluea100: '82B1FF',
bluea200: '448AFF',
bluea400: '2979FF',
bluea700: '2962FF',
lightblue50: 'E1F5FE',
lightblue100: 'B3E5FC',
lightblue200: '81D4FA',
lightblue300: '4FC3F7',
lightblue400: '29B6F6',
lightblue500: '03A9F4',
lightblue600: '039BE5',
lightblue700: '0288D1',
lightblue800: '0277BD',
lightblue900: '01579B',
lightbluea100: '80D8FF',
lightbluea200: '40C4FF',
lightbluea400: '00B0FF',
lightbluea700: '0091EA',
cyan50: 'E0F7FA',
cyan100: 'B2EBF2',
cyan200: '80DEEA',
cyan300: '4DD0E1',
cyan400: '26C6DA',
cyan500: '00BCD4',
cyan600: '00ACC1',
cyan700: '0097A7',
cyan800: '00838F',
cyan900: '006064',
cyana100: '84FFFF',
cyana200: '18FFFF',
cyana400: '00E5FF',
cyana700: '00B8D4',
teal50: 'E0F2F1',
teal100: 'B2DFDB',
teal200: '80CBC4',
teal300: '4DB6AC',
teal400: '26A69A',
teal500: '009688',
teal600: '00897B',
teal700: '00796B',
teal800: '00695C',
teal900: '004D40',
teala100: 'A7FFEB',
teala200: '64FFDA',
teala400: '1DE9B6',
teala700: '00BFA5',
green50: 'E8F5E9',
green100: 'C8E6C9',
green200: 'A5D6A7',
green300: '81C784',
green400: '66BB6A',
green500: '4CAF50',
green600: '43A047',
green700: '388E3C',
green800: '2E7D32',
green900: '1B5E20',
greena100: 'B9F6CA',
greena200: '69F0AE',
greena400: '00E676',
greena700: '00C853',
lightgreen50: 'F1F8E9',
lightgreen100: 'DCEDC8',
lightgreen200: 'C5E1A5',
lightgreen300: 'AED581',
lightgreen400: '9CCC65',
lightgreen500: '8BC34A',
lightgreen600: '7CB342',
lightgreen700: '689F38',
lightgreen800: '558B2F',
lightgreen900: '33691E',
lightgreena100: 'CCFF90',
lightgreena200: 'B2FF59',
lightgreena400: '76FF03',
lightgreena700: '64DD17',
lime50: 'F9FBE7',
lime100: 'F0F4C3',
lime200: 'E6EE9C',
lime300: 'DCE775',
lime400: 'D4E157',
lime500: 'CDDC39',
lime600: 'C0CA33',
lime700: 'AFB42B',
lime800: '9E9D24',
lime900: '827717',
limea100: 'F4FF81',
limea200: 'EEFF41',
limea400: 'C6FF00',
limea700: 'AEEA00',
yellow50: 'FFFDE7',
yellow100: 'FFF9C4',
yellow200: 'FFF59D',
yellow300: 'FFF176',
yellow400: 'FFEE58',
yellow500: 'FFEB3B',
yellow600: 'FDD835',
yellow700: 'FBC02D',
yellow800: 'F9A825',
yellow900: 'F57F17',
yellowa100: 'FFFF8D',
yellowa200: 'FFFF00',
yellowa400: 'FFEA00',
yellowa700: 'FFD600',
amber50: 'FFF8E1',
amber100: 'FFECB3',
amber200: 'FFE082',
amber300: 'FFD54F',
amber400: 'FFCA28',
amber500: 'FFC107',
amber600: 'FFB300',
amber700: 'FFA000',
amber800: 'FF8F00',
amber900: 'FF6F00',
ambera100: 'FFE57F',
ambera200: 'FFD740',
ambera400: 'FFC400',
ambera700: 'FFAB00',
orange50: 'FFF3E0',
orange100: 'FFE0B2',
orange200: 'FFCC80',
orange300: 'FFB74D',
orange400: 'FFA726',
orange500: 'FF9800',
orange600: 'FB8C00',
orange700: 'F57C00',
orange800: 'EF6C00',
orange900: 'E65100',
orangea100: 'FFD180',
orangea200: 'FFAB40',
orangea400: 'FF9100',
orangea700: 'FF6D00',
deeporange50: 'FBE9E7',
deeporange100: 'FFCCBC',
deeporange200: 'FFAB91',
deeporange300: 'FF8A65',
deeporange400: 'FF7043',
deeporange500: 'FF5722',
deeporange600: 'F4511E',
deeporange700: 'E64A19',
deeporange800: 'D84315',
deeporange900: 'BF360C',
deeporangea100: 'FF9E80',
deeporangea200: 'FF6E40',
deeporangea400: 'FF3D00',
deeporangea700: 'DD2C00',
brown50: 'EFEBE9',
brown100: 'D7CCC8',
brown200: 'BCAAA4',
brown300: 'A1887F',
brown400: '8D6E63',
brown500: '795548',
brown600: '6D4C41',
brown700: '5D4037',
brown800: '4E342E',
brown900: '3E2723',
/* Material Design for Bootstrap v4 themes https://mdbootstrap.com/docs/b4/jquery/css/colors/#mdb-colors */
'mdb-default': '2BBBAD', 'mdb-default-dark': '00695c',
'mdb-primary': '4285F4', 'mdb-primary-dark': '0d47a1',
'mdb-secondary': 'aa66cc', 'mdb-secondary-dark': '9933CC',
'mdb-danger': 'ff4444', 'mdb-danger-dark': 'CC0000',
'mdb-warning': 'ffbb33', 'mdb-warning-dark': 'FF8800',
'mdb-success': '00C851', 'mdb-success-dark': '007E33',
'mdb-info': '33b5e5', 'mdb-info-dark': '0099CC',
'mdb-elegant': '2E2E2E', 'mdb-elegant-dark': '212121',
'mdb-stylish': '4B515D', 'mdb-stylish-dark': '3E4551',
'mdb-unique': '3F729B', 'mdb-unique-dark': '1C2331',
'mdb-special': '37474F', 'mdb-special-dark': '263238'
},
errorReporting: 1,
modifier: false,
helper: (() => {
document.addEventListener('keydown', e => jsPanel.modifier = e);
document.addEventListener('keyup', () => jsPanel.modifier = false);
})(),
usePointerEvents(use = true) {
if (!use) {
this.pointerdown = 'ontouchend' in window ? ['touchstart', 'mousedown'] : ['mousedown'];
this.pointermove = 'ontouchend' in window ? ['touchmove', 'mousemove'] : ['mousemove'];
this.pointerup = 'ontouchend' in window ? ['touchend', 'mouseup'] : ['mouseup'];
} else {
this.pointerdown = 'onpointerdown' in window ? ['pointerdown'] : 'ontouchend' in window ? ['touchstart', 'mousedown'] : ['mousedown'];
this.pointermove = 'onpointermove' in window ? ['pointermove'] : 'ontouchend' in window ? ['touchmove', 'mousemove'] : ['mousemove'];
this.pointerup = 'onpointerup' in window ? ['pointerup'] : 'ontouchend' in window ? ['touchend', 'mouseup'] : ['mouseup'];
}
},
// normalize some values passed via panel's options object
pOcontainer(container) {
if (container === 'window') {
return document.body;
} else if (typeof container === 'string') {
let list = document.querySelectorAll(container);
// a returned list is a NodeList
return list.length && list.length > 0 ? list : false;
} else if (container.nodeType === 1) {
return container;
} else if (container.length) {
return container[0];
}
return false;
},
pOcontainment(arg) {
let value = arg;
if (typeof arg === 'function') {
value = arg();
}
if (typeof value === 'number') {
// value: 20 => value: [20, 20, 20, 20]
return [value, value, value, value];
} else if (Array.isArray(value)) {
if (value.length === 1) {
// value: [20] => value: [20, 20, 20, 20]
return [value[0], value[0], value[0], value[0]];
} else if (value.length === 2) {
// value: [20, 40] => value: [20, 40, 20, 40]
return value.concat(value);
} else if (value.length === 3) {
value[3] = value[1];
}
}
return value; // assumed to be an array with 4 values
},
pOsize(panel, size) {
let values = size || this.defaults.contentSize;
const parent = panel.parentElement;
if (typeof values === 'string') {
const nums = values.trim().split(' ');
values = {};
values.width = nums[0];
nums.length === 2 ? (values.height = nums[1]) : (values.height = nums[0]);
} else {
if (values.width && !values.height) {
// noinspection JSSuspiciousNameCombination
values.height = values.width;
} else if (values.height && !values.width) {
// noinspection JSSuspiciousNameCombination
values.width = values.height;
}
}
if (String(values.width).match(/^[\d.]+$/gi)) {
// if number only
values.width += 'px';
} else if (typeof values.width === 'string' && values.width.endsWith('%')) {
if (parent === document.body) {
values.width = window.innerWidth * (parseFloat(values.width) / 100) + 'px';
} else {
const prtStyles = window.getComputedStyle(parent),
border = parseFloat(prtStyles.borderLeftWidth) + parseFloat(prtStyles.borderRightWidth);
values.width = (parseFloat(prtStyles.width) - border) * (parseFloat(values.width) / 100) + 'px';
}
} else if (typeof values.width === 'function') {
values.width = values.width.call(panel, panel);
if (typeof values.width === 'number') {
values.width += 'px';
} else if (typeof values.width === 'string' && values.width.match(/^[\d.]+$/gi)) {
values.width += 'px';
}
}
if (String(values.height).match(/^[\d.]+$/gi)) {
// if number only
values.height += 'px';
} else if (typeof values.height === 'string' && values.height.endsWith('%')) {
if (parent === document.body) {
values.height = window.innerHeight * (parseFloat(values.height) / 100) + 'px';
} else {
const prtStyles = window.getComputedStyle(parent),
border = parseFloat(prtStyles.borderTopWidth) + parseFloat(prtStyles.borderBottomWidth);
values.height = (parseFloat(prtStyles.height) - border) * (parseFloat(values.height) / 100) + 'px';
}
} else if (typeof values.height === 'function') {
values.height = values.height.call(panel, panel);
if (typeof values.height === 'number') {
values.height += 'px';
} else if (typeof values.height === 'string' && values.height.match(/^[\d.]+$/gi)) {
values.height += 'px';
}
}
return values; // return value must be an object {width: xxx, height: xxx}
},
pOborder(str) {
let result = [];
// remove all unnecessary whitepsace
let border = str.trim()
.replace(/\s*\(\s*/g, '(') // remove whitespace around opening brackets
.replace(/\s*\)/g, ')') // remove whitespace around closing brackets
.replace(/\s+/g, ' ') // replace all other whitespace(s) with a single whitespace
.split(' ');
// replace css custom props/variables with values
border.forEach((val, index) => {
if(val.startsWith('--') || val.startsWith('var')) {
border[index] = jsPanel.getCssVariableValue(val);
}
}); // border is now an array like ['5px', 'solid', 'red']
// check values for type (border width, style or color) and add to result array
border.forEach(val => {
if (jsPanel.colorNames[val]) {
result[2] = '#' + jsPanel.colorNames[val];
} else if (val.match(/(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)/)) {
result[1] = val;
} else if (val.match(/(thin|medium|thick)|(\d*\.?\d*(cap|ch|em|ex|ic|lh|rem|rlh|vh|vw|vmax|vmin|vb|vi|px|cm|mm|Q|in|pc|pt))/)) {
result[0] = val;
} else {
result[2] = val;
}
});
// set default values if needed
if (!result[0]) {result[0] = 'medium';}
if (!result[1]) {result[1] = 'solid';}
if (!result[2]) {result[2] = '';}
return result;
},
pOheaderControls(oHdrCtrls) {
if (typeof oHdrCtrls === 'string') {
let setting = {},
str = oHdrCtrls.toLowerCase(),
sizeMatch = str.match(/xl|lg|md|sm|xs/),
ctrlMatch = str.match(/closeonly|none/);
if (sizeMatch) {
setting.size = sizeMatch[0];
}
if (ctrlMatch) {
setting = Object.assign({}, setting, {
maximize: 'remove',
normalize: 'remove',
minimize: 'remove',
smallify: 'remove',
});
if (ctrlMatch[0] === 'none') {
setting.close = 'remove';
}
}
return Object.assign({}, this.defaults.headerControls, setting);
} else {
return Object.assign({}, this.defaults.headerControls, oHdrCtrls);
}
},
pOtheme(optionTheme) {
optionTheme = optionTheme.trim();
let color,
filling = '';
if (optionTheme.match(/^(rgb|hsl|var)/)) {
// for themes starting with rgb , hsl or var
let devide = optionTheme.indexOf(')');
color = optionTheme.slice(0, devide + 1).replace(/\s+/g, '');
if (color.startsWith('var')) {
color = jsPanel.getCssVariableValue(color);
}
filling = optionTheme.slice(devide + 1, optionTheme.length).trim();
} else if (optionTheme.match(/^(#|\w|--)/)) {
// for themes starting with #, [A-Za-z0-9_] or --
let devide = optionTheme.indexOf(' ');
if (devide > 0) {
color = optionTheme.slice(0,devide + 1).replace(/\s+/g, '');
filling = optionTheme.slice(devide + 1, optionTheme.length).trim();
} else {
color = optionTheme;
}
if (color.startsWith('--')) {
color = jsPanel.getCssVariableValue(color);
}
}
if (color.match(/^([\da-f]{3}|[\da-f]{6})$/gi)) {
color = '#' + color;
}
if (filling.startsWith('fillcolor')) {
let devide = filling.indexOf(' ');
filling = filling.slice(devide + 1, filling.length).trim().replace(/\s+/g, '');
if (filling.match(/^([\da-f]{3}|[\da-f]{6})$/gi)) {
filling = '#' + filling;
} else if (jsPanel.colorNames[filling]) {
filling = '#' + jsPanel.colorNames[filling];
} else if (filling.match(/^(--|var)/)) {
filling = jsPanel.getCssVariableValue(filling);
} else {
filling = '#fff';
}
}
return {color: color, colors: false, filling: filling};
},
// color methods ---------------
color(val) {
let color = val.toLowerCase(),
r,
g,
b,
h,
s,
l,
match,
channels,
hsl,
result = {};
const hexPattern = /^#?([\da-f]{3}|[\da-f]{6})$/gi, // matches "#123" or "#f05a78" with or without "#"
RGBAPattern = /^rgba?\((\d{1,3}),(\d{1,3}),(\d{1,3}),?(0|1|0\.\d{1,2}|\.\d{1,2})?\)$/gi, // matches rgb/rgba color values, whitespace allowed
HSLAPattern = /^hsla?\((\d{1,3}),(\d{1,3}%),(\d{1,3}%),?(0|1|0\.\d{1,2}|\.\d{1,2})?\)$/gi,
namedColors = this.colorNames;
// change named color to corresponding hex value
if (namedColors[color]) {
color = namedColors[color];
}
// check val for hex color
if (color.match(hexPattern) !== null) {
// '#' remove
color = color.replace('#', '');
// color has either 3 or 6 characters
if (color.length % 2 === 1) {
// color has 3 char -> convert to 6 char
r = color.slice(0,1).repeat(2);
g = color.slice(1,2).repeat(2);
b = color.slice(2,3).repeat(2);
result.rgb = {
r: parseInt(r, 16),
g: parseInt(g, 16),
b: parseInt(b, 16),
};
result.hex = `#${r}${g}${b}`;
} else {
// color has 6 char
result.rgb = {
r: parseInt(color.slice(0, 2), 16),
g: parseInt(color.slice(2, 4), 16),
b: parseInt(color.slice(4, 6), 16)
};
result.hex = `#${color}`;
}
hsl = this.rgbToHsl(result.rgb.r, result.rgb.g, result.rgb.b);
result.hsl = hsl;
result.rgb.css = `rgb(${result.rgb.r},${result.rgb.g},${result.rgb.b})`;
}
// check val for rgb/rgba color
else if (color.match(RGBAPattern)) {
match = RGBAPattern.exec(color);
result.rgb = { css: color, r: match[1], g: match[2], b: match[3] };
result.hex = this.rgbToHex(match[1], match[2], match[3]);
hsl = this.rgbToHsl(match[1], match[2], match[3]);
result.hsl = hsl;
}
// check val for hsl/hsla color
else if (color.match(HSLAPattern)) {
match = HSLAPattern.exec(color);
h = match[1] / 360;
s = match[2].slice(0, match[2].length - 1) / 100;
l = match[3].slice(0, match[3].length - 1) / 100;
channels = this.hslToRgb(h, s, l);
result.rgb = {
css: `rgb(${channels[0]},${channels[1]},${channels[2]})`,
r: channels[0],
g: channels[1],
b: channels[2],
};
result.hex = this.rgbToHex(result.rgb.r, result.rgb.g, result.rgb.b);
result.hsl = {
css: `hsl(${match[1]},${match[2]},${match[3]})`,
h: match[1],
s: match[2],
l: match[3],
};
}
// or return #f5f5f5
else {
result.hex = '#f5f5f5';
result.rgb = { css: 'rgb(245,245,245)', r: 245, g: 245, b: 245 };
result.hsl = { css: 'hsl(0,0%,96%)', h: 0, s: '0%', l: '96%' };
}
return result;
},
calcColors(primaryColor) {
const threshold = this.colorBrightnessThreshold,
primeColor = this.color(primaryColor),
filledlightColor = this.lighten(primaryColor, this.colorFilledLight),
filledColor = this.darken(primaryColor, this.colorFilled),
fontColorForPrimary = this.perceivedBrightness(primaryColor) <= threshold ? '#ffffff' : '#000000',
fontColorFilledlight = this.perceivedBrightness(filledlightColor) <= threshold ? '#ffffff' : '#000000',
fontColorFilled = this.perceivedBrightness(filledColor) <= threshold ? '#ffffff' : '#000000',
filleddarkColor = this.lighten(primaryColor, this.colorFilledDark),
fontColorFilleddark = this.perceivedBrightness(filleddarkColor) <= threshold ? '#ffffff' : '#000000';
return [
primeColor.hsl.css,
filledlightColor,
filledColor,
fontColorForPrimary,
fontColorFilledlight,
fontColorFilled,
filleddarkColor,
fontColorFilleddark,
];
},
darken(val, amount) {
// amount is value between 0 and 1
const hsl = this.color(val).hsl,
l = parseFloat(hsl.l),
lnew = Math.round(l - l * amount) + '%';
return `hsl(${hsl.h},${hsl.s},${lnew})`;
},
lighten(val, amount) {
// amount is value between 0 and 1
const hsl = this.color(val).hsl,
l = parseFloat(hsl.l),
lnew = Math.round(l + (100 - l) * amount) + '%';
return `hsl(${hsl.h},${hsl.s},${lnew})`;
},
hslToRgb(h, s, l) {
// h, s and l must be values between 0 and 1
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
let hue2rgb = (p, q, t) => {
if (t < 0) {
t += 1;
}
if (t > 1) {
t -= 1;
}
if (t < 1 / 6) {
return p + (q - p) * 6 * t;
}
if (t < 1 / 2) {
return q;
}
if (t < 2 / 3) {
return p + (q - p) * (2 / 3 - t) * 6;
}
return p;
};
let q = l < 0.5 ? l * (1 + s) : l + s - l * s,
p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
},
rgbToHsl(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
let max = Math.max(r, g, b),
min = Math.min(r, g, b),
h,
s,
l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic
} else {
let d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
//return [ h, s, l ];
h = Math.round(h * 360);
s = Math.round(s * 100) + '%';
l = Math.round(l * 100) + '%';
return { css: 'hsl(' + h + ',' + s + ',' + l + ')', h: h, s: s, l: l };
},
rgbToHex(r, g, b) {
let red = Number(r).toString(16),
green = Number(g).toString(16),
blue = Number(b).toString(16);
if (red.length === 1) {
red = `0${red}`;
}
if (green.length === 1) {
green = `0${green}`;
}
if (blue.length === 1) {
blue = `0${blue}`;
}
return `#${red}${green}${blue}`;
},
perceivedBrightness(val) {
const rgb = this.color(val).rgb;
// return value is in the range 0 - 1 and input rgb values must also be in the range 0 - 1
// https://www.w3.org/TR/WCAG20-TECHS/G18.html
return (rgb.r / 255) * 0.2126 + (rgb.g / 255) * 0.7152 + (rgb.b / 255) * 0.0722;
},
// positioning methods ---------------
pOposition(positionshorthand) {
let result = {};
// remove leading and trailing whitespace and split position shorthand string into array
let pos = positionshorthand.trim().split(/\s+/);
// find autoposition value and assign to result, must be the first item to find and remove
let auto = pos.filter(item => item.match(/^(down|right|up|left)$/i));
if (auto.length) {
result.autoposition = auto[0];
pos.splice(pos.indexOf(auto[0]), 1);
}
// find my and at values and assign to result
let my_at = pos.filter(item => item.match(/^(left-|right-)(top|center|bottom)$|(^center-)(top|bottom)$|(^center$)/i));
if (my_at.length) {
result.my = my_at[0];
result.at = my_at[1] || my_at[0];
pos.splice(pos.indexOf(my_at[0]), 1);
if (my_at[1]) {
pos.splice(pos.indexOf(my_at[1]), 1);
}
} else {
result.my = 'center';
result.at = 'center';
}
// find offset and assign to result
let offsets = pos.filter(item => item.match(/^[+-]?\d*\.?\d+[a-z%]*$/i));
if (offsets.length) {
result.offsetX = offsets[0].match(/^[+-]?\d*\.?\d+$/i) ? `${offsets[0]}px` : offsets[0];
if (offsets[1]) {
result.offsetY = offsets[1].match(/^[+-]?\d*\.?\d+$/i) ? `${offsets[1]}px` : offsets[1];
} else {
// noinspection JSSuspiciousNameCombination
result.offsetY = result.offsetX;
}
pos.splice(pos.indexOf(offsets[0]), 1);
if (offsets[1]) {
pos.splice(pos.indexOf(offsets[1]), 1);
}
}
// last to find and assign is of value and must be all the rest (if there is a rest)
if (pos.length) {
result.of = pos.join(' ');
}
return result;
},
position(panel, position) {
// @panel: selector string | Element | jQuery object
// - usually the jsPanel to position
// @position: object
// - positioning configuration
// - if panel config uses a position shorthand string it must be converted to object before it's passed to this function
// if @position is not set return panel
if (!position) {
panel.style.opacity = 1;
return panel;
}
// merge position defaults with @position
if (typeof position === 'string') {
position = Object.assign({}, this.defaults.position, this.pOposition(position));
} else {
position = Object.assign({}, this.defaults.position, position);
}
// process parameter functions for 'my', 'at', 'of'
// 'offsetX', 'offsetY', 'minLeft', 'maxLeft', 'minTop', 'maxTop' are processed when params are applied
['my', 'at', 'of'].forEach(item => {
if (typeof position[item] === 'function') {
position[item] = position[item].call(panel, panel);
}
});
// panel uses option.container: 'window' position is always fixed
if (panel.options.container === 'window') {
panel.style.position = 'fixed';
}
// normalize param @panel to ensure it's an Element object
if (typeof panel === 'string') {
panel = document.querySelector(panel);
} else if (Object.getPrototypeOf(panel).jquery) {
panel = panel[0];
} // else panel is assumed to be element object
// container is either 'window' or the panel's parent element
const container = panel.options.container === 'window' ? 'window' : panel.parentElement;
// get base values in order to calculate position deltas
// since getBoundingClientRect() calculates values relative to the viewport the parentElement of panel/elmtToPositionAgainst is irrelevant
const panelRect = panel.getBoundingClientRect(),
containerDomRect = panel.parentElement.getBoundingClientRect(),
containerRect =
container === 'window'
? {
left: 0,
top: 0,
width: document.documentElement.clientWidth,
height: window.innerHeight,
} // fake window.getBoundingClientRect() return value
: //: panel.parentElement.getBoundingClientRect(); // using 'container' instead of 'panel.parentElement' produces an error
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#Notes
// due to the infos from above link IE and EDGE (old version not based on Chromium) report an error in strict mode -> line of code above replaced with line below
{
width: containerDomRect.width,
height: containerDomRect.height,
left: containerDomRect.left,
top: containerDomRect.top,
};
// calculate scale factors, needed for correct positioning if container is scaled - transform: scale()
// window is never scaled --> scale factors default to 1
const scaleFactor =
container === 'window'
? {x: 1, y: 1}
: {
x: containerRect.width / container.offsetWidth,
y: containerRect.height / container.offsetHeight,
};
// get and apply border width values of container - needed for positioning corrections due to positioning with %-values
const containerStyle =
container === 'window'
? {
borderTopWidth: '0px',
borderRightWidth: '0px',
borderBottomWidth: '0px',
borderLeftWidth: '0px',
} // fake getComputedStyle(window) return value
: window.getComputedStyle(container);
containerRect.width -= (parseFloat(containerStyle.borderLeftWidth) + parseFloat(containerStyle.borderRightWidth)) * scaleFactor.x;
containerRect.height -= (parseFloat(containerStyle.borderTopWidth) + parseFloat(containerStyle.borderBottomWidth)) * scaleFactor.y;
// calculate @position.of rect if @position.of is set
let positionOfRect;
if (!position.of) {
positionOfRect = containerRect;
} else {
if (typeof position.of === 'string') {
positionOfRect =
position.of === 'window'
? {
borderTopWidth: '0px',
borderRightWidth: '0px',
borderBottomWidth: '0px',
borderLeftWidth: '0px',
} // fake getComputedStyle(window) return value
: document.querySelector(position.of).getBoundingClientRect();
} else if (Object.getPrototypeOf(position.of).jquery) {
positionOfRect = position.of[0].getBoundingClientRect();
} else {
positionOfRect = position.of.getBoundingClientRect();
}
}
// check for scrollbar width values
let scrollbarwidthsWindow = this.getScrollbarWidth(document.body),
scrollbarwidthsContainer = this.getScrollbarWidth(panel.parentElement);
// calc css left for @panel in regard of @position.my and @position.at
let left = '0px';
if (position.my.startsWith('left-')) {
if (position.at.startsWith('left-')) {
if (position.of) {
left = positionOfRect.left - containerRect.left - parseFloat(containerStyle.borderLeftWidth) + 'px';
} else {
left = '0px';
}
} else if (position.at.startsWith('center')) {
if (position.of) {
left = positionOfRect.left - containerRect.left - parseFloat(containerStyle.borderLeftWidth) + positionOfRect.width / 2 + 'px';
} else {
left = containerRect.width / 2 + 'px';
}
} else if (position.at.startsWith('right-')) {
if (position.of) {
left = positionOfRect.left - containerRect.left - parseFloat(containerStyle.borderLeftWidth) + positionOfRect.width + 'px';
} else {
left = containerRect.width + 'px';
}
}
} else if (position.my.startsWith('center')) {
if (position.at.startsWith('left-')) {
if (position.of) {
left = positionOfRect.left - containerRect.left - parseFloat(containerStyle.borderLeftWidth) - panelRect.width / 2 + 'px';
} else {
left = -panelRect.width / 2 + 'px';
}
} else if (position.at.startsWith('center')) {
if (position.of) {
left =
positionOfRect.left -
containerRect.left -
parseFloat(containerStyle.borderLeftWidth) -
(panelRect.width - positionOfRect.width) / 2 +
'px';
} else {
left = containerRect.width / 2 - panelRect.width / 2 + 'px';
}
} else if (position.at.startsWith('right-')) {
if (position.of) {
left =
positionOfRect.left -
containerRect.left -
parseFloat(containerStyle.borderLeftWidth) +
(positionOfRect.width - panelRect.width / 2) +
'px';
} else {
left = containerRect.width - panelRect.width / 2 + 'px';
}
}
} else if (position.my.startsWith('right-')) {
if (position.at.startsWith('left-')) {
if (position.of) {
left = positionOfRect.left - containerRect.left - parseFloat(containerStyle.borderLeftWidth) - panelRect.width + 'px';
} else {
left = -panelRect.width + 'px';
}
} else if (position.at.startsWith('center')) {
if (position.of) {
left =
positionOfRect.left -
containerRect.left -
parseFloat(containerStyle.borderLeftWidth) -
panelRect.width +
positionOfRect.width / 2 +
'px';
} else {
left = containerRect.width / 2 - panelRect.width + 'px';
}
} else if (position.at.startsWith('right-')) {
if (position.of) {
left =
positionOfRect.left -
containerRect.left -
parseFloat(containerStyle.borderLeftWidth) +
positionOfRect.width -
panelRect.width +
'px';
} else {
left = containerRect.width - panelRect.width + 'px';
}
// correction for vertical scrollbar only needed for panels using my: 'right-*' together with at: 'right-*'
if (container !== 'window') {
left = parseFloat(left) - scrollbarwidthsContainer.y + 'px';
}
}
}
// calc css top for @panel in regard of @position.my and @position.at
let top = '0px';
if (position.my.endsWith('-top')) {
if (position.at.endsWith('-top')) {
if (position.of) {
top = positionOfRect.top - containerRect.top - parseFloat(containerStyle.borderTopWidth) + 'px';
} else {
top = '0px';
}
} else if (position.at.endsWith('center')) {
if (position.of) {
top = positionOfRect.top - containerRect.top - parseFloat(containerStyle.borderTopWidth) + positionOfRect.height / 2 + 'px';
} else {
top = containerRect.height / 2 + 'px';
}
} else if (position.at.endsWith('-bottom')) {
if (position.of) {
top = positionOfRect.top - containerRect.top - parseFloat(containerStyle.borderTopWidth) + positionOfRect.height + 'px';
} else {
top = containerRect.height + 'px';
}
}
} else if (position.my.endsWith('center')) {
if (position.at.endsWith('-top')) {
if (position.of) {
top = positionOfRect.top - containerRect.top - parseFloat(containerStyle.borderTopWidth) - panelRect.height / 2 + 'px';
} else {
top = -panelRect.height / 2 + 'px';
}
} else if (position.at.endsWith('center')) {
if (position.of) {
top =
positionOfRect.top -
containerRect.top -
parseFloat(containerStyle.borderTopWidth) -
panelRect.height / 2 +
positionOfRect.height / 2 +
'px';
} else {
top = containerRect.height / 2 - panelRect.height / 2 + 'px';
}
} else if (position.at.endsWith('-bottom')) {
if (position.of) {
top =
positionOfRect.top -
containerRect.top -
parseFloat(containerStyle.borderTopWidth) -
panelRect.height / 2 +
positionOfRect.height +
'px';
} else {
top = containerRect.height - panelRect.height / 2 + 'px';
}
}
} else if (position.my.endsWith('-bottom')) {
if (position.at.endsWith('-top')) {
if (position.of) {
top = positionOfRect.top - containerRect.top - parseFloat(containerStyle.borderTopWidth) - panelRect.height + 'px';
} else {
top = -panelRect.height + 'px';
}
} else if (position.at.endsWith('center')) {
if (position.of) {
top =
positionOfRect.top -
containerRect.top -
parseFloat(containerStyle.borderTopWidth) -
panelRect.height +
positionOfRect.height / 2 +
'px';
} else {
top = containerRect.height / 2 - panelRect.height + 'px';
}
} else if (position.at.endsWith('-bottom')) {
if (position.of) {
top =
positionOfRect.top -
containerRect.top -
parseFloat(containerStyle.borderTopWidth) -
panelRect.height +
positionOfRect.height +
'px';
} else {
top = containerRect.height - panelRect.height + 'px';
}
// correction for horizontal scrollbar only needed for panels using my: '*-bottom' together with at: '*-bottom'
if (container !== 'window') {
top = parseFloat(top) - scrollbarwidthsContainer.x + 'px';
} else {
top = parseFloat(top) - scrollbarwidthsWindow.x + 'px';
}
}
}
panel.style.left = scaleFactor.x === 1 ? left : parseFloat(left) / scaleFactor.x + 'px';
panel.style.top = scaleFactor.y === 1 ? top : parseFloat(top) / scaleFactor.y + 'px';
// at this point panels are correctly positioned according to my/at values
let panelStyle = getComputedStyle(panel);
// eslint-disable-next-line no-unused-vars
let pos = { left: panelStyle.left, top: panelStyle.top };
//console.log('pos after applying my/at/of:', pos);
// apply autoposition only if ...
if (
position.autoposition &&
position.my === position.at &&
['left-top', 'center-top', 'right-top', 'left-bottom', 'center-bottom', 'right-bottom'].indexOf(position.my) >= 0
) {
pos = this.applyPositionAutopos(panel, pos, position);
//console.log('let pos after applying autoposition:', pos);
}
// apply position.offsetX and position.offsetY
if (position.offsetX || position.offsetY) {
pos = this.applyPositionOffset(panel, pos, position);
//console.log('pos after applying offsets:', pos);
}
// calculate and apply position.minLeft, position.minTop, position.maxLeft and position.maxTop
if (position.minLeft || position.minTop || position.maxLeft || position.maxTop) {
pos = this.applyPositionMinMax(panel, pos, position);
//console.log('pos after applying minLeft, maxLeft, maxTop, minTop:', pos);
}
// apply position.modify
// must be function returning an object with keys left/top, each with valid css length value
if (position.modify) {
// eslint-disable-next-line no-unused-vars
pos = this.applyPositionModify(panel, pos, position);
//console.log('pos after applying modify():', pos);
}
typeof panel.options.opacity === 'number' ? (panel.style.opacity = panel.options.opacity) : (panel.style.opacity = 1);
return panel;
},
applyPositionAutopos(panel, pos, position) {
// add class with position and autoposition direction
const newClass = `${position.my}-${position.autoposition.toLowerCase()}`;
panel.classList.add(newClass);
// get all panels with same class
const newClassAll = Array.prototype.slice.call(document.querySelectorAll(`.${newClass}`)),
ownIndex = newClassAll.indexOf(panel);
// if more than 1 position new panel
if (newClassAll.length > 1) {
switch (position.autoposition) {
case 'down':
// collect heights of all elmts to calc new top position
newClassAll.forEach((item, index) => {
if (index > 0 && index <= ownIndex) {
pos.top = parseFloat(pos.top) + newClassAll[--index].getBoundingClientRect().height + jsPanel.autopositionSpacing + 'px';
}
});
break;
case 'up':
newClassAll.forEach((item, index) => {
if (index > 0 && index <= ownIndex) {
pos.top = parseFloat(pos.top) - newClassAll[--index].getBoundingClientRect().height - jsPanel.autopositionSpacing + 'px';
}
});
break;
case 'right':
// collect widths of all elmts to calc new left position
newClassAll.forEach((item, index) => {
if (index > 0 && index <= ownIndex) {
pos.left = parseFloat(pos.left) + newClassAll[--index].getBoundingClientRect().width + jsPanel.autopositionSpacing + 'px';
}
});
break;
case 'left':
newClassAll.forEach((item, index) => {
if (index > 0 && index <= ownIndex) {
pos.left = parseFloat(pos.left) - newClassAll[--index].getBoundingClientRect().width - jsPanel.autopositionSpacing + 'px';
}
});
break;
}
panel.style.left = pos.left;
panel.style.top = pos.top;
}
return { left: pos.left, top: pos.top };
},
applyPositionOffset(panel, pos, position) {
['offsetX', 'offsetY'].forEach(offset => {
if (position[offset]) {
if (typeof position[offset] === 'function') {
position[offset] = position[offset].call(pos, pos, position);
}
if (isNaN(position[offset]) === false) {
// if an offset's value type is integer it's interpreted as pixel value
position[offset] = `${position[offset]}px`;
} // else it's assumed offsets are strings with valid css length values
} else {
position[offset] = '0px';
}
});
panel.style.left = `calc(${panel.style.left} + ${position.offsetX})`; // offsetX
panel.style.top = `calc(${panel.style.top} + ${position.offsetY})`; // offsetY
const panelStyle = getComputedStyle(panel);
return { left: panelStyle.left, top: panelStyle.top };
},
applyPositionMinMax(panel, pos, position) {
['minLeft', 'minTop', 'maxLeft', 'maxTop'].forEach(val => {
if (position[val]) {
if (typeof position[val] === 'function') {
position[val] = position[val].call(pos, pos, position);
}
if (Number.isInteger(position[val]) || position[val].match(/^\d+$/)) {
// if val type is integer it's interpreted as pixel value
position[val] = `${position[val]}px`;
} // else it's assumed val is string with valid css length value
}
});
// process minLeft
if (position.minLeft) {
// apply minLeft value in order to compare with previous left (pos.left)
panel.style.left = position.minLeft;
// now get computed css left
let left = getComputedStyle(panel).left; // returns string with pixel value
// now compare current left (minLeft) with pos.left
if (parseFloat(left) < parseFloat(pos.left)) {
// if minLeft is less than pos.left return to pos.left
panel.style.left = pos.left;
} else {
// if minLeft is greater than pos.left keep minLeft and reset pos.left to new value
pos.left = left;
}
}
// process minTop
if (position.minTop) {
panel.style.top = position.minTop;
let top = getComputedStyle(panel).top;
if (parseFloat(top) < parseFloat(pos.top)) {
panel.style.top = pos.top;
} else {
pos.top = top;
}
}
// process maxLeft
if (position.maxLeft) {
panel.style.left = position.maxLeft;
let left = getComputedStyle(panel).left;
if (parseFloat(left) > parseFloat(pos.left)) {
panel.style.left = pos.left;
} else {
pos.left = left;
}
}
// process maxTop
if (position.maxTop) {
panel.style.top = position.maxTop;
let top = getComputedStyle(panel).top;
if (parseFloat(top) > parseFloat(pos.top)) {
panel.style.top = pos.top;
} else {
pos.top = top;
}
}
const panelStyle = getComputedStyle(panel);
return { left: panelStyle.left, top: panelStyle.top };
},
applyPositionModify(panel, pos, position) {
if (position.modify && typeof position.modify === 'function') {
const modifiedPosition = position.modify.call(pos, pos, position);
panel.style.left =
Number.isInteger(modifiedPosition.left) || modifiedPosition.left.match(/^\d+$/)
? `${modifiedPosition.left}px`
: modifiedPosition.left;
panel.style.top =
Number.isInteger(modifiedPosition.top) || modifiedPosition.top.match(/^\d+$/) ? `${modifiedPosition.top}px` : modifiedPosition.top;
}
const panelStyle = getComputedStyle(panel);
return { left: panelStyle.left, top: panelStyle.top };
},
autopositionRemaining(panel) {
let autoPos,
parent = panel.options.container;
[
'left-top-down',
'left-top-right',
'center-top-down',
'right-top-down',
'right-top-left',
'left-bottom-up',
'left-bottom-right',
'center-bottom-up',
'right-bottom-up',
'right-bottom-left',
].forEach(item => {
if (panel.classList.contains(item)) {
autoPos = item;
}
});
if (autoPos) {
const box = parent === 'window' ? document.body : typeof parent === 'string' ? document.querySelector(parent) : parent;
box.querySelectorAll(`.${autoPos}`).forEach(item => item.reposition());
}
},
// theming methods -------------------------
getThemeDetails(th) {
const theme = this.pOtheme(th);
if (theme.color.startsWith('bootstrap-')) {
// works with bootstrap 3 and 4
let index = theme.color.indexOf('-'),
btn = document.createElement('button');
btn.className = 'btn btn' + theme.color.slice(index);
document.body.appendChild(btn);
theme.color = getComputedStyle(btn).backgroundColor.replace(/\s+/gi, '');
document.body.removeChild(btn);
// noinspection JSUnusedAssignment
btn = undefined;
}
theme.colors = this.calcColors(theme.color);
return theme;
},
clearTheme(panel, cb) {
panel.content.classList.remove('jsPanel-content-filled', 'jsPanel-content-filledlight');
panel.header.classList.remove('jsPanel-hdr-light');
panel.header.classList.remove('jsPanel-hdr-dark');
panel.style.backgroundColor = '';
this.setStyles(panel.headertoolbar, {
boxShadow: '',
width: '',
marginLeft: '',
borderTopColor: 'transparent',
});
this.setStyles(panel.content, {
background: '',
borderTopColor: 'transparent',
});
panel.header.style.background = '';
Array.prototype.slice
.call(panel.controlbar.querySelectorAll('.jsPanel-icon'))
.concat([panel.headerlogo, panel.headertitle, panel.headertoolbar, panel.content])
.forEach(item => item.style.color = '');
if (cb) {
cb.call(panel, panel);
}
return panel;
},
applyColorTheme(panel, themeDetails) {
panel.style.backgroundColor = themeDetails.colors[0];
panel.header.style.backgroundColor = themeDetails.colors[0];
panel.header.style.color = themeDetails.colors[3];
['.jsPanel-headerlogo', '.jsPanel-title', '.jsPanel-hdr-toolbar'].forEach(item => panel.querySelector(item).style.color = themeDetails.colors[3]);
panel.querySelectorAll('.jsPanel-controlbar .jsPanel-btn').forEach(item => item.style.color = themeDetails.colors[3]);
// apply border to content only themes 'filled'
if (typeof panel.options.theme === 'string' && themeDetails.filling === 'filled') {
panel.content.style.borderTop =
themeDetails.colors[3] === '#000000' ? '1px solid rgba(0,0,0,0.15)' : '1px solid rgba(255,255,255,0.15)';
}
if (themeDetails.colors[3] === '#000000') {
panel.header.classList.add('jsPanel-hdr-light');
} else {
panel.header.classList.add('jsPanel-hdr-dark');
}
if (themeDetails.filling) {
switch (themeDetails.filling) {
case 'filled':
this.setStyles(panel.content, {
backgroundColor: themeDetails.colors[2],
color: themeDetails.colors[3],
});
break;
case 'filledlight':
panel.content.style.backgroundColor = themeDetails.colors[1];
break;
case 'filleddark':
this.setStyles(panel.content, {
backgroundColor: themeDetails.colors[6],
color: themeDetails.colors[7],
});
break;
default:
panel.content.style.backgroundColor = themeDetails.filling;
panel.content.style.color =
this.perceivedBrightness(themeDetails.filling) <= this.colorBrightnessThreshold ? '#fff' : '#000';
}
}
return panel;
},
applyCustomTheme(panel, theme) {
let defaults = {
bgPanel: '#ffffff',
bgContent: '#ffffff',
bgFooter: '#f5f5f5',
colorHeader: '#000000',
colorContent: '#000000',
colorFooter: '#000000',
border: undefined,
borderRadius: undefined
},
passedTheme = typeof theme === 'object' ? Object.assign(defaults, theme) : defaults;
let bgPanel = passedTheme.bgPanel,
bgContent = passedTheme.bgContent,
colorHeader = passedTheme.colorHeader,
colorContent = passedTheme.colorContent,
bgFooter = passedTheme.bgFooter,
colorFooter = passedTheme.colorFooter;
// set background panel/header
if (this.colorNames[bgPanel]) {
panel.style.background = '#' + this.colorNames[bgPanel];
} else {
panel.style.background = this.getCssVariableValue(bgPanel);
// getCssVariableValue(bgPanel) returns bgPanel unchanged if value does not start with either '--' or 'var'
}
// set font color header
if (this.colorNames[colorHeader]) {
colorHeader = '#' + this.colorNames[colorHeader];
}
['.jsPanel-headerlogo', '.jsPanel-title', '.jsPanel-hdr-toolbar'].forEach(item => panel.querySelector(item).style.color = this.getCssVariableValue(colorHeader));
panel.querySelectorAll('.jsPanel-controlbar .jsPanel-btn').forEach(item => item.style.color = this.getCssVariableValue(colorHeader));
// set content background
if (this.colorNames[bgContent]) {
panel.content.style.background = '#' + this.colorNames[bgContent];
} else {
panel.content.style.background = this.getCssVariableValue(bgContent);
// getCssVariableValue(bgContent) returns bgContent unchanged if value does not start with either '--' or 'var'
}
// set content font color
this.colorNames[colorContent]
? (panel.content.style.color = '#' + this.colorNames[colorContent])
: (panel.content.style.color = this.getCssVariableValue(colorContent)); // getCssVariableValue(val) returns val unchanged if val does not start with either '--' or 'var'
// set border-top for header toolbar and add header class
const pbPanel = this.perceivedBrightness(colorHeader);
if (pbPanel > this.colorBrightnessThreshold) {
panel.header.classList.add('jsPanel-hdr-dark');
} else {
panel.header.classList.add('jsPanel-hdr-light');
}
// set border-top for content
const pbContent = this.perceivedBrightness(colorContent);
pbContent > this.colorBrightnessThreshold
? (panel.content.style.borderTop = '1px solid rgba(255,255,255,0.15)')
: (panel.content.style.borderTop = '1px solid rgba(0,0,0,0.15)');
// set footer background
if (this.colorNames[bgFooter]) {
panel.footer.style.background = '#' + this.colorNames[bgFooter];
} else {
panel.footer.style.background = this.getCssVariableValue(bgFooter);
// getCssVariableValue(bgFooter) returns bgFooter unchanged if value does not start with either '--' or 'var'
}
// set footer font color
this.colorNames[colorFooter]
? (panel.footer.style.color = '#' + this.colorNames[colorFooter])
: (panel.footer.style.color = this.getCssVariableValue(colorFooter));
// set panel border (option.border does not work for themes using an object)
if (passedTheme.border) {
panel.setBorder(passedTheme.border);
}
if (passedTheme.borderRadius) {
panel.options.borderRadius = undefined;
panel.setBorderRadius(passedTheme.borderRadius);
}
return panel;
},
// methods dealing with css and html -------
getCssVariableValue(str) {
// str may be something like '--my-color' or 'var(--my-color)'
// if str does not start with '--' or 'var' str is returned unchanged
// str = str.replace(/\s+/g, ''); do not uncomment this line !!
if (str.startsWith('--')) {
return getComputedStyle(document.documentElement).getPropertyValue(str).replace(/\s+/g, '');
} else if (str.startsWith('var')) {
let v = str.slice(str.indexOf('(')+1, str.indexOf(')'));
return getComputedStyle(document.documentElement).getPropertyValue(v).replace(/\s+/g, '');
}
return str;
},
getScrollbarWidth(elmt = document.body) {
if (elmt === document.body) {
return {
y: window.innerWidth - document.documentElement.clientWidth,
x: window.innerHeight - document.documentElement.clientHeight,
};
} else {
let styles = getComputedStyle(elmt);
return {
y: elmt.offsetWidth - elmt.clientWidth - parseFloat(styles.borderRightWidth) - parseFloat(styles.borderLeftWidth),
x: elmt.offsetHeight - elmt.clientHeight - parseFloat(styles.borderBottomWidth) - parseFloat(styles.borderTopWidth),
};
}
},
remClass(elmt, classnames) {
classnames
.trim()
.split(/\s+/)
.forEach(item => elmt.classList.remove(item));
return elmt;
},
setClass(elmt, classnames) {
classnames
.trim()
.split(/\s+/)
.forEach(item => elmt.classList.add(item));
return elmt;
},
setStyles(elmt, stylesobject) {
for (const [prop, value] of Object.entries(stylesobject)) {
elmt.style[prop] = typeof value === 'string' ? jsPanel.getCssVariableValue(value) : value;
}
return elmt;
},
setStyle(elmt, stylesobject) {
return this.setStyles.call(elmt, elmt, stylesobject);
}, // alias for setStyles()
strToHtml(str) {
// TODO: add param to strip script tags from returned DocumentFragment
/* str has to be an HTMLString
* returns a DocumentFragment - https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
* after inserting executes inline script and script tags */
return document.createRange().createContextualFragment(str);
},
toggleClass(elmt, classnames) {
// IE11 doesn't support https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/toggle
classnames
.trim()
.split(/\s+/)
.forEach(classname => elmt.classList.contains(classname) ? elmt.classList.remove(classname) : elmt.classList.add(classname));
return elmt;
},
emptyNode(node) {
while (node.firstChild) {
node.removeChild(node.firstChild);
}
return node;
},
addScript(path, type = 'application/javascript', cb) {
if (!document.querySelector(`script[src="${path}"]`)) {
const script = document.createElement('script');
script.src = path;
script.type = type;
document.head.appendChild(script);
if (cb) {
script.onload = cb;
}
}
},
ajax(ajaxConfig, panel) {
let config,
urlParts,
xhr = new XMLHttpRequest();
const configDefaults = {
method: 'GET',
async: true,
user: '',
pwd: '',
done: function () {
if (panel) {
let res = jsPanel.strToHtml(this.responseText);
if (config.urlSelector) {
res = res.querySelector(config.urlSelector);
}
panel.contentRemove();
panel.content.append(res);
}
},
autoresize: true,
autoreposition: true,
};
if (panel && typeof ajaxConfig === 'string') {
config = Object.assign({}, configDefaults, {
url: ajaxConfig,
});
} else if (typeof ajaxConfig === 'object' && ajaxConfig.url) {
config = Object.assign({}, configDefaults, ajaxConfig);
config.url = ajaxConfig.url;
// reset timeout to 0, withCredentials & responseType to false if request is synchronous
if (config.async === false) {
config.timeout = 0;
if (config.withCredentials) {
config.withCredentials = undefined;
}
if (config.responseType) {
config.responseType = undefined;
}
}
} else {
if (this.errorReporting) {
let err = 'XMLHttpRequest seems to miss the url parameter!';
jsPanel.errorpanel(err);
}
return;
}
// check url for added selector
urlParts = config.url.trim().split(/\s+/);
config.url = encodeURI(urlParts[0]);
if (urlParts.length > 1) {
urlParts.shift();
config.urlSelector = urlParts.join(' ');
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
panel ? config.done.call(xhr, xhr, panel) : config.done.call(xhr, xhr);
} else {
if (config.fail) {
panel ? config.fail.call(xhr, xhr, panel) : config.fail.call(xhr, xhr);
}
}
if (config.always) {
panel ? config.always.call(xhr, xhr, panel) : config.always.call(xhr, xhr);
}
// resize and/or reposition panel if either width or height is set to 'auto'
if (panel) {
if (config.autoresize || config.autoreposition) {
jsPanel.ajaxAutoresizeAutoreposition(panel, config);
}
}
// allows plugins to add callback functions to the ajax always callback
if (jsPanel.ajaxAlwaysCallbacks.length) {
jsPanel.ajaxAlwaysCallbacks.forEach(item => {
panel ? item.call(xhr, xhr, panel) : item.call(xhr, xhr);
});
}
}
};
xhr.open(config.method, config.url, config.async, config.user, config.pwd);
xhr.timeout = config.timeout || 0;
if (config.withCredentials) {
xhr.withCredentials = config.withCredentials;
}
if (config.responseType) {
xhr.responseType = config.responseType;
}
if (config.beforeSend) {
panel ? config.beforeSend.call(xhr, xhr, panel) : config.beforeSend.call(xhr, xhr);
}
config.data ? xhr.send(config.data) : xhr.send(null);
},
fetch(fetchConfig, panel) {
let config;
const configDefaults = {
bodyMethod: 'text',
autoresize: true,
autoreposition: true,
done: function (response, panel) {
if (panel) {
let res = jsPanel.strToHtml(response);
panel.contentRemove();
panel.content.append(res);
}
},
};
if (panel && typeof fetchConfig === 'string') {
config = Object.assign({}, configDefaults, {
resource: encodeURI(fetchConfig),
});
} else if (typeof fetchConfig === 'object' && fetchConfig.resource) {
config = Object.assign({}, configDefaults, fetchConfig);
config.resource = encodeURI(fetchConfig.resource);
} else {
if (this.errorReporting) {
let err = 'Fetch Request seems to miss the resource parameter!';
jsPanel.errorpanel(err);
}
return;
}
const fetchInit = config.fetchInit || {};
if (config.beforeSend) {
panel ? config.beforeSend.call(fetchConfig, fetchConfig, panel) : config.beforeSend.call(fetchConfig, fetchConfig);
}
fetch(config.resource, fetchInit)
.then(response => {
if (response.ok) {
return response[config.bodyMethod]();
}
})
.then(response => {
panel ? config.done.call(response, response, panel) : config.done.call(response, response);
if (panel) {
// resize and/or reposition panel if either width or height is set to 'auto'
if (config.autoresize || config.autoreposition) {
jsPanel.ajaxAutoresizeAutoreposition(panel, config);
}
}
});
},
ajaxAutoresizeAutoreposition(panel, ajaxOrFetchConfig) {
const oContentSize = panel.options.contentSize;
if (typeof oContentSize === 'string' && oContentSize.match(/auto/i)) {
const parts = oContentSize.split(' '),
sizes = Object.assign({}, { width: parts[0], height: parts[1] });
if (ajaxOrFetchConfig.autoresize) {
panel.resize(sizes);
}
if (!panel.classList.contains('jsPanel-contextmenu')) {
if (ajaxOrFetchConfig.autoreposition) {
panel.reposition();
}
}
} else if (typeof oContentSize === 'object' && (oContentSize.width === 'auto' || oContentSize.height === 'auto')) {
const sizes = Object.assign({}, oContentSize);
if (ajaxOrFetchConfig.autoresize) {
panel.resize(sizes);
}
if (!panel.classList.contains('jsPanel-contextmenu')) {
if (ajaxOrFetchConfig.autoreposition) {
panel.reposition();
}
}
}
},
// templates and other elements ----------
createPanelTemplate(dataAttr = true) {
const panel = document.createElement('div');
panel.className = 'jsPanel';
panel.style.left = '0';
panel.style.top = '0';
if (dataAttr) {
['close', 'maximize', 'normalize', 'minimize', 'smallify'].forEach(item => {
panel.setAttribute(`data-btn${item}`, 'enabled');
});
}
panel.innerHTML = `
`;
return panel;
},
createMinimizedTemplate() {
const panel = document.createElement('div');
panel.className = 'jsPanel-replacement';
panel.innerHTML = `
`;
return panel;
},
createSnapArea(panel, pos, snapsens) {
const el = document.createElement('div'),
parent = panel.parentElement;
el.className = `jsPanel-snap-area jsPanel-snap-area-${pos}`;
if (pos === 'lt' || pos === 'rt' || pos === 'rb' || pos === 'lb') {
el.style.width = snapsens + 'px';
el.style.height = snapsens + 'px';
} else if (pos === 'ct' || pos === 'cb') {
el.style.height = snapsens + 'px';
} else if (pos === 'lc' || pos === 'rc') {
el.style.width = snapsens + 'px';
}
if (parent !== document.body) {
el.style.position = 'absolute';
}
if (!document.querySelector(`.jsPanel-snap-area.jsPanel-snap-area-${pos}`)) {
panel.parentElement.appendChild(el);
}
},
removeSnapAreas() {
document.querySelectorAll('.jsPanel-snap-area').forEach(el => el.parentElement.removeChild(el));
},
// various helpers -------
extend(obj) {
// obj needs to be a plain object (to extend the individual panel, not the global object)
if (Object.prototype.toString.call(obj) === '[object Object]') {
for (let ext in obj) {
if (Object.prototype.hasOwnProperty.call(obj, ext)) {
this.extensions[ext] = obj[ext];
}
}
}
},
getPanels(condition = function() {return this.classList.contains('jsPanel-standard');}) {
return Array.prototype.slice
.call(document.querySelectorAll('.jsPanel'))
.filter(value => condition.call(value, value))
.sort((a, b) => b.style.zIndex - a.style.zIndex);
},
processCallbacks(panel, arg, someOrEvery = 'some', param, param2) {
// if arg != array make it one
if (typeof arg === 'function') {
arg = [arg];
}
// some(): execute callbacks until one is found returning a truthy value
// every(): execute callbacks until one is found returning a falsy value
// truthy values are: '0' (string with single zero), 'false' (string with text false), [] (empty array), {} (empty object), function(){} ("empty" function)
// falsy values are: false, 0, '', "", null, undefined, NaN
if (someOrEvery) {
return arg[someOrEvery](cb => cb.call(panel, panel, param, param2));
} else {
arg.forEach(cb => cb.call(panel, panel, param, param2));
}
},
resetZi() {
this.zi = ((startValue = jsPanel.ziBase) => {
let val = startValue;
return {
next: () => val++
};
})();
Array.prototype.slice
.call(document.querySelectorAll('.jsPanel-standard'))
.sort((a, b) => a.style.zIndex - b.style.zIndex)
.forEach(panel => panel.style.zIndex = jsPanel.zi.next());
},
errorpanel(e) {
this.create({
paneltype: 'error',
dragit: false,
resizeit: false,
theme: {
bgPanel: 'white',
bgContent: 'white',
colorHeader: 'rebeccapurple',
colorContent: '#333333',
border: '2px solid rebeccapurple',
},
borderRadius: '.33rem',
headerControls: 'closeonly xs',
headerTitle: '⚠ jsPanel Error',
contentSize: { width: '50%', height: 'auto' },
position: 'center-top 0 5 down',
animateIn: 'jsPanelFadeIn',
content: `${e}
`,
});
},
// METHOD CREATING THE PANEL ---------------------------------------------
create(options = {}, cb) {
// initialize z-index generator
if (!jsPanel.zi) {
jsPanel.zi = ((startValue = jsPanel.ziBase) => {
let val = startValue;
return {
next: () => val++
};
})();
}
if (options.config) {
options = Object.assign({}, this.defaults, options.config, options);
delete options.config;
} else {
options = Object.assign({}, this.defaults, options);
}
if (!options.id) {
options.id = `jsPanel-${(jsPanel.idCounter += 1)}`;
} else if (typeof options.id === 'function') {
options.id = options.id();
}
const p = document.getElementById(options.id);
if (p !== null) {
// if a panel with passed id already exists, front it and return false
if (p.classList.contains('jsPanel')) {
p.front();
}
if (this.errorReporting) {
let err = `◀ COULD NOT CREATE NEW JSPANEL ►
An element with the ID ${options.id} already exists in the document.`;
jsPanel.errorpanel(err);
}
return false;
}
// check whether container is valid -> if not return and throw error
let panelContainer = this.pOcontainer(options.container);
// panelContainer might be a NodeList, so use only first node in list
if (typeof panelContainer === 'object' && panelContainer.length && panelContainer.length > 0) {
panelContainer = panelContainer[0];
}
if (!panelContainer) {
if (this.errorReporting) {
let err = '◀ COULD NOT CREATE NEW JSPANEL ►
The container to append the panel to does not exist';
jsPanel.errorpanel(err);
}
return false;
}
// normalize on... callbacks
// these callbacks must be an array of function(s) in order to be able to dynamically add/remove callbacks (for example in extensions)
[
'onbeforeclose',
'onbeforemaximize',
'onbeforeminimize',
'onbeforenormalize',
'onbeforesmallify',
'onbeforeunsmallify',
'onclosed',
'onfronted',
'onmaximized',
'onminimized',
'onnormalized',
'onsmallified',
'onstatuschange',
'onunsmallified',
].forEach(item => {
if (options[item]) {
if (typeof options[item] === 'function') {
options[item] = [options[item]];
}
} else {
options[item] = [];
}
});
const self = options.template || this.createPanelTemplate();
// Create an abort controller and signal to detach dragit/resize events
// when the panel is closed
const abortController = new AbortController();
const abortSignal = abortController.signal;
// Properties
self.options = options;
self.closetimer = undefined;
self.status = 'initialized';
self.currentData = {};
self.header = self.querySelector('.jsPanel-hdr'); // complete header section
self.headerbar = self.header.querySelector('.jsPanel-headerbar'); // log, title and controls
self.titlebar = self.header.querySelector('.jsPanel-titlebar'); // div surrounding title div
self.headerlogo = self.headerbar.querySelector('.jsPanel-headerlogo'); // logo only
self.headertitle = self.headerbar.querySelector('.jsPanel-title'); // title div
self.controlbar = self.headerbar.querySelector('.jsPanel-controlbar'); // div surrounding all controls
self.headertoolbar = self.header.querySelector('.jsPanel-hdr-toolbar');
self.content = self.querySelector('.jsPanel-content');
self.footer = self.querySelector('.jsPanel-ftr');
self.snappableTo = false;
self.snapped = false;
self.droppableTo = false;
self.progressbar = self.autocloseProgressbar = self.querySelector('.jsPanel-progressbar'); // self.autocloseProgressbar kept for compatibility
// Events
const jspanelloaded = new CustomEvent('jspanelloaded', {
detail: options.id,
cancelable: true,
}),
jspanelstatuschange = new CustomEvent('jspanelstatuschange', {
detail: options.id,
cancelable: true,
}),
jspanelbeforenormalize = new CustomEvent('jspanelbeforenormalize', {
detail: options.id,
cancelable: true,
}),
jspanelnormalized = new CustomEvent('jspanelnormalized', {
detail: options.id,
cancelable: true,
}),
jspanelbeforemaximize = new CustomEvent('jspanelbeforemaximize', {
detail: options.id,
cancelable: true,
}),
jspanelmaximized = new CustomEvent('jspanelmaximized', {
detail: options.id,
cancelable: true,
}),
jspanelbeforeminimize = new CustomEvent('jspanelbeforeminimize', {
detail: options.id,
cancelable: true,
}),
jspanelminimized = new CustomEvent('jspanelminimized', {
detail: options.id,
cancelable: true,
}),
jspanelbeforesmallify = new CustomEvent('jspanelbeforesmallify', {
detail: options.id,
cancelable: true,
}),
jspanelsmallified = new CustomEvent('jspanelsmallified', {
detail: options.id,
cancelable: true,
}),
jspanelsmallifiedmax = new CustomEvent('jspanelsmallifiedmax', {
detail: options.id,
cancelable: true,
}),
jspanelbeforeunsmallify = new CustomEvent('jspanelbeforeunsmallify', {
detail: options.id,
cancelable: true,
}),
jspanelfronted = new CustomEvent('jspanelfronted', {
detail: options.id,
cancelable: true,
}),
jspanelbeforeclose = new CustomEvent('jspanelbeforeclose', {
detail: options.id,
cancelable: true,
}),
jspanelclosed = new CustomEvent('jspanelclosed', {
detail: options.id,
cancelable: true,
}),
jspanelcloseduser = new CustomEvent('jspanelcloseduser', {
detail: options.id,
cancelable: true,
});
// make panel available as event object property 'panel'
[
jspanelloaded,
jspanelstatuschange,
jspanelbeforenormalize,
jspanelnormalized,
jspanelbeforemaximize,
jspanelmaximized,
jspanelbeforeminimize,
jspanelminimized,
jspanelbeforesmallify,
jspanelsmallified,
jspanelsmallifiedmax,
jspanelbeforeunsmallify,
jspanelfronted,
jspanelbeforeclose,
].forEach(evt => evt.panel = self);
// controls buttons
const closeBtn = self.querySelector('.jsPanel-btn-close'),
maxBtn = self.querySelector('.jsPanel-btn-maximize'),
normBtn = self.querySelector('.jsPanel-btn-normalize'),
smallBtn = self.querySelector('.jsPanel-btn-smallify'),
minBtn = self.querySelector('.jsPanel-btn-minimize');
if (closeBtn) {
jsPanel.pointerup.forEach(item => {
closeBtn.addEventListener(item, e => {
e.preventDefault();
// disable close for all mouse buttons but left
if (e.button && e.button > 0) {
return false;
}
self.close(null, true); // true indicates panel closed by using the close control
});
});
}
if (maxBtn) {
jsPanel.pointerup.forEach(item => {
maxBtn.addEventListener(item, e => {
e.preventDefault();
if (e.button && e.button > 0) {
return false;
}
self.maximize();
});
});
}
if (normBtn) {
jsPanel.pointerup.forEach(item => {
normBtn.addEventListener(item, e => {
e.preventDefault();
if (e.button && e.button > 0) {
return false;
}
self.normalize();
});
});
}
if (smallBtn) {
jsPanel.pointerup.forEach(item => {
smallBtn.addEventListener(item, e => {
e.preventDefault();
if (e.button && e.button > 0) {
return false;
}
if (self.status === 'normalized' || self.status === 'maximized') {
self.smallify();
} else if (self.status === 'smallified' || self.status === 'smallifiedmax') {
self.unsmallify();
}
});
});
}
if (minBtn) {
jsPanel.pointerup.forEach(item => {
minBtn.addEventListener(item, e => {
e.preventDefault();
if (e.button && e.button > 0) {
return false;
}
self.minimize();
});
});
}
// import extensions (extensions of the individual panel, not the global object jsPanel)
let extensions = jsPanel.extensions;
for (let ext in extensions) {
if (Object.prototype.hasOwnProperty.call(extensions, ext)) {
self[ext] = extensions[ext];
}
}
// panel methods
self.setBorder = val => {
let border = jsPanel.pOborder(val);
if (!border[2].length) {
border[2] = self.style.backgroundColor;
}
border = border.join(' ');
self.style.border = border;
self.options.border = border;
return self;
};
self.setBorderRadius = rad => {
if (typeof rad === 'string') {
if (rad.startsWith('--') || rad.startsWith('var')) {
rad = rad.replace(/\s*\(\s*/g, '(') // remove whitespace around opening brackets
.replace(/\s*\)/g, ')') // remove whitespace around closing brackets
.replace(/\s+/g, ' '); // replace all other whitespace(s) with a single whitespace
rad = jsPanel.getCssVariableValue(rad);
}
}
if (typeof rad === 'number') {
rad += 'px';
}
self.style.borderRadius = rad;
const br = getComputedStyle(self);
// set border-radius of either header or content section depending on presence of header
if (self.options.header) {
self.header.style.borderTopLeftRadius = br.borderTopLeftRadius;
self.header.style.borderTopRightRadius = br.borderTopRightRadius;
} else {
self.content.style.borderTopLeftRadius = br.borderTopLeftRadius;
self.content.style.borderTopRightRadius = br.borderTopRightRadius;
}
// set border-radius of either footer or content section depending on presence of footer
if (self.options.footerToolbar) {
self.footer.style.borderBottomRightRadius = br.borderBottomRightRadius;
self.footer.style.borderBottomLeftRadius = br.borderBottomLeftRadius;
} else {
self.content.style.borderBottomRightRadius = br.borderBottomRightRadius;
self.content.style.borderBottomLeftRadius = br.borderBottomLeftRadius;
}
return self;
};
self.setTheme = (theme = options.theme, cb) => {
// if panel is minimized normalize it for theme change
let minimized;
if (self.status === 'minimized') {
minimized = true;
self.normalize();
}
// first remove all theme related styles
jsPanel.clearTheme(self);
if (typeof theme === 'object') {
options.border = undefined;
jsPanel.applyCustomTheme(self, theme);
} else if (typeof theme === 'string') {
if (theme === 'none') {
theme = 'white';
}
let themeDetails = jsPanel.getThemeDetails(theme);
jsPanel.applyColorTheme(self, themeDetails);
}
// minimize again if panel was minimized prior theme change
if (minimized) {
self.minimize();
}
if (cb) {
cb.call(self, self);
}
return self;
};
self.remove = (id, closedBy, cb) => {
// self.remove() is just a helper func used in self.close()
self.parentElement.removeChild(self);
if (!document.getElementById(id)) {
self.removeMinimizedReplacement();
self.status = 'closed';
if (closedBy) {
document.dispatchEvent(jspanelcloseduser);
}
document.dispatchEvent(jspanelclosed);
if (self.options.onclosed) {
jsPanel.processCallbacks(self, self.options.onclosed, 'every', closedBy);
}
jsPanel.autopositionRemaining(self);
if (cb) {
cb.call(id, id);
}
} else {
if (cb) {
cb.call(self, id, self);
}
}
window.removeEventListener('resize', self.windowResizeHandler);
document.removeEventListener('jspanelresize', self.parentResizeHandler);
// Send abort signal to remove events that can't be removed manually
abortController.abort();
};
self.close = (cb, closedByUser) => {
// if panel does not exist return
if (!self.parentElement) {return;}
if (self.closetimer) {
window.clearInterval(self.closetimer);
}
document.dispatchEvent(jspanelbeforeclose);
self.statusBefore = self.status;
if (
self.options.onbeforeclose &&
self.options.onbeforeclose.length > 0 &&
!jsPanel.processCallbacks(self, self.options.onbeforeclose, 'some', self.status, closedByUser)
) {
return self;
}
if (self.options.animateOut) {
if (self.options.animateIn) {
jsPanel.remClass(self, self.options.animateIn);
}
jsPanel.setClass(self, self.options.animateOut);
self.addEventListener('animationend', e => {
e.stopPropagation();
self.remove(self.id, closedByUser, cb);
});
} else {
self.remove(self.id, closedByUser, cb);
}
};
self.maximize = (cb, donotfront) => {
// Note: do not disable maximize method for already maximized panels -> onContainerResize wouldn't work
self.statusBefore = self.status;
if (
options.onbeforemaximize &&
options.onbeforemaximize.length > 0 &&
!jsPanel.processCallbacks(self, options.onbeforemaximize, 'some', self.statusBefore)
) {
return self;
}
document.dispatchEvent(jspanelbeforemaximize);
const parent = self.parentElement,
margins = jsPanel.pOcontainment(options.maximizedMargin); // normalize maximizedMargin
if (parent === document.body) {
// maximize within window
/*
When clientHeight is used on the root element (the element), (or on if the document is in quirks mode),
the viewport's height (excluding any scrollbar) is returned. This is a special case of clientHeight.
See https://developer.mozilla.org/en-US/docs/Web/API/Element/clientHeight
document.documentElement in the code below returns the element
*/
self.style.width = document.documentElement.clientWidth - margins[1] - margins[3] + 'px';
self.style.height = document.documentElement.clientHeight - margins[0] - margins[2] + 'px';
self.style.left = margins[3] + 'px';
self.style.top = margins[0] + 'px';
} else {
// maximize within parentElement
self.style.width = parent.clientWidth - margins[1] - margins[3] + 'px';
self.style.height = parent.clientHeight - margins[0] - margins[2] + 'px';
self.style.left = margins[3] + 'px';
self.style.top = margins[0] + 'px';
}
smallBtn.style.transform = 'unset';
self.removeMinimizedReplacement();
self.status = 'maximized';
self.setControls(['.jsPanel-btn-maximize']);
if (!donotfront) {
self.front();
}
document.dispatchEvent(jspanelmaximized);
document.dispatchEvent(jspanelstatuschange);
if (options.onstatuschange) {
jsPanel.processCallbacks(self, options.onstatuschange, 'every', self.statusBefore);
}
if (cb) {
cb.call(self, self, self.statusBefore);
}
if (options.onmaximized) {
jsPanel.processCallbacks(self, options.onmaximized, 'every', self.statusBefore);
}
return self;
};
self.minimize = cb => {
if (self.status === 'minimized') {
return self;
}
self.statusBefore = self.status;
if (
options.onbeforeminimize &&
options.onbeforeminimize.length > 0 &&
!jsPanel.processCallbacks(self, options.onbeforeminimize, 'some', self.statusBefore)
) {
return self;
}
document.dispatchEvent(jspanelbeforeminimize);
// create container for minimized replacements if not already there
if (!document.getElementById('jsPanel-replacement-container')) {
const replacementContainer = document.createElement('div');
replacementContainer.id = 'jsPanel-replacement-container';
document.body.append(replacementContainer);
}
self.style.left = '-9999px';
self.status = 'minimized';
document.dispatchEvent(jspanelminimized);
document.dispatchEvent(jspanelstatuschange);
if (options.onstatuschange) {
jsPanel.processCallbacks(self, options.onstatuschange, 'every', self.statusBefore);
}
if (options.minimizeTo) {
let replacement = self.createMinimizedReplacement(),
container,
parent,
list;
switch (options.minimizeTo) {
case 'default':
document.getElementById('jsPanel-replacement-container').append(replacement);
break;
case 'parentpanel':
parent = self.closest('.jsPanel-content').parentElement;
list = parent.querySelectorAll('.jsPanel-minimized-box');
container = list[list.length - 1];
container.append(replacement);
break;
case 'parent':
parent = self.parentElement;
container = parent.querySelector('.jsPanel-minimized-container');
if (!container) {
container = document.createElement('div');
container.className = 'jsPanel-minimized-container';
parent.append(container);
}
container.append(replacement);
break;
default:
// all other strings are assumed to be selector strings returning a single element to append the min replacement to
document.querySelector(options.minimizeTo).append(replacement);
}
}
if (cb) {
cb.call(self, self, self.statusBefore);
}
if (options.onminimized) {
jsPanel.processCallbacks(self, options.onminimized, 'every', self.statusBefore);
}
return self;
};
self.normalize = cb => {
if (self.status === 'normalized') {
return self;
}
self.statusBefore = self.status;
// ensure smallify/unsmallify transition is turned off when resizing begins
//self.style.transition = 'unset';
if (
options.onbeforenormalize &&
options.onbeforenormalize.length > 0 &&
!jsPanel.processCallbacks(self, options.onbeforenormalize, 'some', self.statusBefore)
) {
return self;
}
document.dispatchEvent(jspanelbeforenormalize);
self.style.width = self.currentData.width;
self.style.height = self.currentData.height;
if (self.snapped) {
// if panel is snapped before minimizing restore snapped position
self.snap(self.snapped, true);
} else {
self.style.left = self.currentData.left;
self.style.top = self.currentData.top;
}
smallBtn.style.transform = 'unset';
self.removeMinimizedReplacement();
self.status = 'normalized';
self.setControls(['.jsPanel-btn-normalize']);
self.front();
document.dispatchEvent(jspanelnormalized);
document.dispatchEvent(jspanelstatuschange);
if (options.onstatuschange) {
jsPanel.processCallbacks(self, options.onstatuschange, 'every', self.statusBefore);
}
if (cb) {
cb.call(self, self, self.statusBefore);
}
if (options.onnormalized) {
jsPanel.processCallbacks(self, options.onnormalized, 'every', self.statusBefore);
}
return self;
};
self.smallify = cb => {
if (self.status === 'smallified' || self.status === 'smallifiedmax') {
return self;
}
self.statusBefore = self.status;
if (
options.onbeforesmallify &&
options.onbeforesmallify.length > 0 &&
!jsPanel.processCallbacks(self, options.onbeforesmallify, 'some', self.statusBefore)
) {
return self;
}
document.dispatchEvent(jspanelbeforesmallify);
self.style.overflow = 'hidden';
const selfStyles = window.getComputedStyle(self),
selfHeaderHeight = parseFloat(window.getComputedStyle(self.headerbar).height);
self.style.height = parseFloat(selfStyles.borderTopWidth) + parseFloat(selfStyles.borderBottomWidth) + selfHeaderHeight + 'px';
smallBtn.style.transform = 'rotate(180deg)';
if (self.status === 'normalized') {
self.setControls(['.jsPanel-btn-normalize']);
self.status = 'smallified';
document.dispatchEvent(jspanelsmallified);
document.dispatchEvent(jspanelstatuschange);
if (options.onstatuschange) {
jsPanel.processCallbacks(self, options.onstatuschange, 'every', self.statusBefore);
}
} else if (self.status === 'maximized') {
self.setControls(['.jsPanel-btn-maximize']);
self.status = 'smallifiedmax';
document.dispatchEvent(jspanelsmallifiedmax);
document.dispatchEvent(jspanelstatuschange);
if (options.onstatuschange) {
jsPanel.processCallbacks(self, options.onstatuschange, 'every', self.statusBefore);
}
}
const minBoxes = self.querySelectorAll('.jsPanel-minimized-box');
minBoxes[minBoxes.length - 1].style.display = 'none';
if (cb) {
cb.call(self, self, self.statusBefore);
}
if (options.onsmallified) {
jsPanel.processCallbacks(self, options.onsmallified, 'every', self.statusBefore);
}
return self;
};
self.unsmallify = cb => {
self.statusBefore = self.status;
if (self.status === 'smallified' || self.status === 'smallifiedmax') {
if (
options.onbeforeunsmallify &&
options.onbeforeunsmallify.length > 0 &&
!jsPanel.processCallbacks(self, options.onbeforeunsmallify, 'some', self.statusBefore)
) {
return self;
}
document.dispatchEvent(jspanelbeforeunsmallify);
self.style.overflow = 'visible';
self.front();
if (self.status === 'smallified') {
self.style.height = self.currentData.height;
self.setControls(['.jsPanel-btn-normalize']);
self.status = 'normalized';
document.dispatchEvent(jspanelnormalized);
document.dispatchEvent(jspanelstatuschange);
if (options.onstatuschange) {
jsPanel.processCallbacks(self, options.onstatuschange, 'every', self.statusBefore);
}
} else if (self.status === 'smallifiedmax') {
self.maximize();
} else if (self.status === 'minimized') {
self.normalize();
}
smallBtn.style.transform = 'rotate(0deg)';
const minBoxes = self.querySelectorAll('.jsPanel-minimized-box');
minBoxes[minBoxes.length - 1].style.display = 'flex';
if (cb) {
cb.call(self, self, self.statusBefore);
}
if (options.onunsmallified) {
jsPanel.processCallbacks(self, options.onunsmallified, 'every', self.statusBefore);
}
}
return self;
};
self.front = (callback, execOnFrontedCallbacks = true) => {
if (self.status === 'minimized') {
self.statusBefore === 'maximized' ? self.maximize() : self.normalize();
} else {
const newArr = Array.prototype.slice.call(document.querySelectorAll('.jsPanel-standard')).map(panel => panel.style.zIndex);
if (Math.max(...newArr) > self.style.zIndex) {
self.style.zIndex = jsPanel.zi.next();
}
jsPanel.resetZi();
}
document.dispatchEvent(jspanelfronted);
if (callback) {
callback.call(self, self);
}
if (options.onfronted && execOnFrontedCallbacks) {
jsPanel.processCallbacks(self, options.onfronted, 'every', self.status);
}
return self;
};
self.snap = (pos, alreadySnapped = false) => {
// store panel size before it snaps, only if not snapped already
if (!alreadySnapped) {
self.currentData.beforeSnap = {
width: self.currentData.width,
height: self.currentData.height,
};
}
// snap panel
if (pos && typeof pos === 'function' && !alreadySnapped) {
pos.call(self, self, self.snappableTo);
} else if (pos !== false) {
let offsets = [0, 0];
if (self.options.dragit.snap.containment) {
if (self.options.dragit.containment) {
const containment = jsPanel.pOcontainment(self.options.dragit.containment),
position = self.snappableTo;
if (position.startsWith('left')) {
offsets[0] = containment[3];
} else if (position.startsWith('right')) {
offsets[0] = -containment[1];
}
if (position.endsWith('top')) {
offsets[1] = containment[0];
} else if (position.endsWith('bottom')) {
offsets[1] = -containment[2];
}
}
}
self.reposition(`${self.snappableTo} ${offsets[0]} ${offsets[1]}`);
}
if (!alreadySnapped) {
self.snapped = self.snappableTo;
}
};
self.move = (target, cb) => {
let overlaps = self.overlaps(target, 'paddingbox'),
source = self.parentElement;
target.appendChild(self);
self.options.container = target;
self.style.left = overlaps.left + 'px';
self.style.top = overlaps.top + 'px';
self.saveCurrentDimensions();
self.saveCurrentPosition();
self.calcSizeFactors(); // important for option.onContainerResize
if (cb) {
cb.call(self, self, target, source);
}
return self;
};
self.closeChildpanels = cb => {
self.getChildpanels().forEach(item => item.close());
if (cb) {
cb.call(self, self);
}
return self;
};
self.getChildpanels = cb => {
const childpanels = self.content.querySelectorAll('.jsPanel');
if (cb) {
childpanels.forEach((panel, index, list) => {
cb.call(panel, panel, index, list);
});
}
return childpanels;
};
self.isChildpanel = cb => {
const pp = self.closest('.jsPanel-content'),
parentpanel = pp ? pp.parentElement : null;
if (cb) {
cb.call(self, self, parentpanel);
}
// if panel is childpanel of another panel returns parentpanel, otherwise false
return pp ? parentpanel : false;
};
self.contentRemove = cb => {
jsPanel.emptyNode(self.content);
if (cb) {
cb.call(self, self);
}
return self;
};
self.createMinimizedReplacement = () => {
const tpl = jsPanel.createMinimizedTemplate(),
color = window.getComputedStyle(self.headertitle).color,
selfStyles = window.getComputedStyle(self),
font = options.iconfont,
controlbar = tpl.querySelector('.jsPanel-controlbar');
// if panel background is an image (that includes gradients) instead of a color value
if (self.options.header !== 'auto-show-hide') {
jsPanel.setStyles(tpl, {
backgroundColor: selfStyles.backgroundColor,
backgroundPositionX: selfStyles.backgroundPositionX,
backgroundPositionY: selfStyles.backgroundPositionY,
backgroundRepeat: selfStyles.backgroundRepeat,
backgroundAttachment: selfStyles.backgroundAttachment,
backgroundImage: selfStyles.backgroundImage,
backgroundSize: selfStyles.backgroundSize,
backgroundOrigin: selfStyles.backgroundOrigin,
backgroundClip: selfStyles.backgroundClip,
});
} else {
tpl.style.backgroundColor = window.getComputedStyle(self.header).backgroundColor;
}
tpl.id = self.id + '-min';
tpl.querySelector('.jsPanel-headerbar').replaceChild(self.headerlogo.cloneNode(true), tpl.querySelector('.jsPanel-headerlogo'));
tpl.querySelector('.jsPanel-titlebar').replaceChild(self.headertitle.cloneNode(true), tpl.querySelector('.jsPanel-title'));
tpl.querySelector('.jsPanel-titlebar').setAttribute('title', self.headertitle.textContent);
tpl.querySelector('.jsPanel-title').style.color = color;
controlbar.style.color = color;
controlbar.querySelectorAll('button').forEach(btn => btn.style.color = color);
['jsPanel-hdr-dark', 'jsPanel-hdr-light'].forEach(item => {
if (self.header.classList.contains(item)) {
tpl.querySelector('.jsPanel-hdr').classList.add(item);
}
});
// set iconfont
self.setIconfont(font, tpl);
if (self.dataset.btnnormalize === 'enabled') {
jsPanel.pointerup.forEach(evt => {
tpl.querySelector('.jsPanel-btn-normalize').addEventListener(evt, e => {
e.preventDefault();
if (e.button && e.button > 0) {
return false;
}
self.normalize();
});
});
} else {
controlbar.querySelector('.jsPanel-btn-normalize').style.display = 'none';
}
if (self.dataset.btnmaximize === 'enabled') {
jsPanel.pointerup.forEach(evt => {
tpl.querySelector('.jsPanel-btn-maximize').addEventListener(evt, e => {
e.preventDefault();
if (e.button && e.button > 0) {
return false;
}
self.maximize();
});
});
} else {
controlbar.querySelector('.jsPanel-btn-maximize').style.display = 'none';
}
if (self.dataset.btnclose === 'enabled') {
jsPanel.pointerup.forEach(evt => {
tpl.querySelector('.jsPanel-btn-close').addEventListener(evt, e => {
e.preventDefault();
if (e.button && e.button > 0) {
return false;
}
self.close(null, true);
});
});
} else {
controlbar.querySelector('.jsPanel-btn-close').style.display = 'none';
}
return tpl;
};
self.removeMinimizedReplacement = () => {
const elmt = document.getElementById(`${self.id}-min`);
if (elmt) {
elmt.parentElement.removeChild(elmt);
}
};
self.drag = (options = {}) => {
let dragstarted, dragElmt, opts;
const jspaneldragstart = new CustomEvent('jspaneldragstart', { detail: self.id }),
jspaneldrag = new CustomEvent('jspaneldrag', { detail: self.id }),
jspaneldragstop = new CustomEvent('jspaneldragstop', { detail: self.id });
// make panel available as event object property 'panel'
[jspaneldragstart, jspaneldrag, jspaneldragstop].forEach(evt => evt.panel = self);
const camelcase = string => {
// 'left-top' converted to 'snapLeftTop'
let str = string.split('-');
str.forEach((word, index) => {
str[index] = word.charAt(0).toUpperCase() + word.slice(1);
});
return 'snap' + str.join('');
};
function windowListener(e) {
if (e.relatedTarget === null) {
jsPanel.pointermove.forEach(evt => {
document.removeEventListener(evt, dragElmt, false);
self.style.opacity = 1;
});
}
}
// attach handler to each drag handle
let handles = options.handles || jsPanel.defaults.dragit.handles;
let cursor = options.cursor || jsPanel.defaults.dragit.cursor;
function pointerUpHandlerDragit(e) {
jsPanel.pointermove.forEach(e => document.removeEventListener(e, dragElmt));
jsPanel.removeSnapAreas();
if (dragstarted) {
self.style.opacity = 1;
dragstarted = undefined;
if (opts.snap) {
switch (self.snappableTo) {
case 'left-top':
self.snap(opts.snap.snapLeftTop);
break;
case 'center-top':
self.snap(opts.snap.snapCenterTop);
break;
case 'right-top':
self.snap(opts.snap.snapRightTop);
break;
case 'right-center':
self.snap(opts.snap.snapRightCenter);
break;
case 'right-bottom':
self.snap(opts.snap.snapRightBottom);
break;
case 'center-bottom':
self.snap(opts.snap.snapCenterBottom);
break;
case 'left-bottom':
self.snap(opts.snap.snapLeftBottom);
break;
case 'left-center':
self.snap(opts.snap.snapLeftCenter);
break;
}
if (opts.snap.callback && self.snappableTo && typeof opts.snap.callback === 'function') {
opts.snap.callback.call(self, self);
if (opts.snap.repositionOnSnap && opts.snap[camelcase(self.snappableTo)] !== false) {
self.repositionOnSnap(self.snappableTo);
}
}
if (self.snappableTo && opts.snap.repositionOnSnap && opts.snap[camelcase(self.snappableTo)]) {
self.repositionOnSnap(self.snappableTo);
}
}
// opts.drop
if (self.droppableTo && self.droppableTo) {
let sourceContainer = self.parentElement;
self.move(self.droppableTo);
if (opts.drop.callback) {
opts.drop.callback.call(self, self, self.droppableTo, sourceContainer);
}
}
document.dispatchEvent(jspaneldragstop);
if (opts.stop.length) {
let stopStyles = window.getComputedStyle(self),
paneldata = {
left: parseFloat(stopStyles.left),
top: parseFloat(stopStyles.top),
width: parseFloat(stopStyles.width),
height: parseFloat(stopStyles.height),
};
jsPanel.processCallbacks(self, opts.stop, false, paneldata, e);
}
self.saveCurrentPosition();
self.calcSizeFactors(); // important for options onwindowresize/onparentresize
}
self.controlbar.style.pointerEvents = 'inherit';
self.content.style.pointerEvents = 'inherit';
// restore other panel's css pointer-events
document.querySelectorAll('iframe').forEach(frame => frame.style.pointerEvents = 'auto');
document.removeEventListener(e, pointerUpHandlerDragit);
}
self.querySelectorAll(handles).forEach(handle => {
handle.style.touchAction = 'none';
handle.style.cursor = cursor;
jsPanel.pointerdown.forEach(evt => {
handle.addEventListener(evt, e => {
// disable dragging for all mouse buttons but left
if (e.button && e.button > 0) {
return false;
}
// setup and normalize dragit options
opts = Object.assign({}, jsPanel.defaults.dragit, options);
if (opts.disableOnMaximized && self.status === 'maximized') {
return false;
}
if (opts.containment || opts.containment === 0) {
opts.containment = jsPanel.pOcontainment(opts.containment);
}
if (opts.grid) {
if (Array.isArray(opts.grid)) {
if (opts.grid.length === 1) {
opts.grid[1] = opts.grid[0];
}
}
}
if (opts.snap) {
if (typeof opts.snap === 'object') {
opts.snap = Object.assign({}, jsPanel.defaultSnapConfig, opts.snap);
} else {
opts.snap = jsPanel.defaultSnapConfig;
}
}
// footer elmts with the class "jsPanel-ftr-btn" don't drag a panel
// do not compare e.target with e.currentTarget because there might be footer elmts supposed to drag the panel
// noinspection JSUnresolvedFunction
if (e.target.closest('.jsPanel-ftr-btn')) {
return;
}
self.controlbar.style.pointerEvents = 'none';
self.content.style.pointerEvents = 'none'; // without this code handler might not be unbound when content has iframe or object tag
// prevents iframes in other panel from interfering with drag action of dragged panel
document.querySelectorAll('iframe').forEach(frame => frame.style.pointerEvents = 'none');
let startStyles = window.getComputedStyle(self),
startLeft = parseFloat(startStyles.left),
startTop = parseFloat(startStyles.top),
startWidth = parseFloat(startStyles.width),
startHeight = parseFloat(startStyles.height),
psx = e.touches ? e.touches[0].clientX : e.clientX, // pointer x on mousedown (don't use pageX, doesn't work on FF for Android)
psy = e.touches ? e.touches[0].clientY : e.clientY, // same as above
parent = self.parentElement,
parentRect = parent.getBoundingClientRect(),
parentStyles = window.getComputedStyle(parent),
scaleFactor = self.getScaleFactor(),
startLeftCorrection = 0,
scrollbarwidths = jsPanel.getScrollbarWidth(parent);
// function actually dragging the elmt
dragElmt = e => {
e.preventDefault();
if (!dragstarted) {
document.dispatchEvent(jspaneldragstart);
self.style.opacity = opts.opacity;
// if configured restore panel size to a value before snap and reposition reasonable before drag actually starts
if (self.snapped && opts.snap.resizeToPreSnap && self.currentData.beforeSnap) {
self.resize(self.currentData.beforeSnap.width + ' ' + self.currentData.beforeSnap.height);
self.setControls(['.jsPanel-btn-normalize']);
let intermediateStyles = self.getBoundingClientRect(),
delta = psx - (intermediateStyles.left + intermediateStyles.width),
wHalf = intermediateStyles.width / 2;
if (delta > -wHalf) {
startLeftCorrection = delta + wHalf;
}
}
self.front();
self.snapped = false;
// panel is maximized on dragstart
if (self.status === 'maximized') {
self.setControls(['.jsPanel-btn-normalize']);
self.status = 'normalized';
}
// opts.drop
if (opts.drop && opts.drop.dropZones) {
//opts.drop.dropZones = opts.drop.dropZones.map(zone => jsPanel.pOcontainer(zone));
let dropzones = opts.drop.dropZones.map(zone => jsPanel.pOcontainer(zone)); // -> array where each item is a NodeList
let dropzonelist = [];
dropzones.forEach(function (nodelist) {
if (nodelist.length) {
// an element node does not have a length property
nodelist.forEach(function (node) {
dropzonelist.push(node);
});
} else {
dropzonelist.push(nodelist);
}
});
// filter list to have only unique values
dropzonelist = dropzonelist.filter(function (value, index, self) {
return self.indexOf(value) === index;
});
opts.drop.dropZones = dropzonelist;
}
// dragstart callback
if (opts.start.length) {
jsPanel.processCallbacks(
self,
opts.start,
false,
{
left: startLeft,
top: startTop,
width: startWidth,
height: startHeight,
},
e
);
}
}
dragstarted = 1;
let elmtL, elmtL2, elmtT, elmtT2, elmtR, elmtR2, elmtB, elmtB2, right, bottom;
let pmx = e.touches ? e.touches[0].clientX : e.clientX, // current pointer x while pointer moves (don't use pageX, doesn't work on FF for Android)
pmy = e.touches ? e.touches[0].clientY : e.clientY, // current pointer y while pointer moves (don't use pageY, doesn't work on FF for Android)
dragStyles = window.getComputedStyle(self), // get current styles while dragging
overlaps;
// EDGE reports "auto" instead of pixel value using getComputedStyle(), so some values need to be calculated different
// this whole block of code could be removed if EDGE not based on Chromium doesn't need to be supported
if (parent === document.body) {
let elmtRect = self.getBoundingClientRect();
right =
window.innerWidth -
parseInt(parentStyles.borderLeftWidth, 10) -
parseInt(parentStyles.borderRightWidth, 10) -
(elmtRect.left + elmtRect.width);
bottom =
window.innerHeight -
parseInt(parentStyles.borderTopWidth, 10) -
parseInt(parentStyles.borderBottomWidth, 10) -
(elmtRect.top + elmtRect.height);
} else {
right =
parseInt(parentStyles.width, 10) -
parseInt(parentStyles.borderLeftWidth, 10) -
parseInt(parentStyles.borderRightWidth, 10) -
(parseInt(dragStyles.left, 10) + parseInt(dragStyles.width, 10));
bottom =
parseInt(parentStyles.height, 10) -
parseInt(parentStyles.borderTopWidth, 10) -
parseInt(parentStyles.borderBottomWidth, 10) -
(parseInt(dragStyles.top, 10) + parseInt(dragStyles.height, 10));
}
// -- -- --
elmtL = parseFloat(dragStyles.left);
elmtT = parseFloat(dragStyles.top);
elmtR = right; // replace line with parseFloat(dragStyles.right); if EDGE code block above is removed
elmtB = bottom; // replace line with parseFloat(dragStyles.bottom); if EDGE code block above is removed
if (opts.snap) {
if (opts.snap.trigger === 'panel') {
elmtL2 = elmtL ** 2;
elmtT2 = elmtT ** 2;
elmtR2 = elmtR ** 2;
elmtB2 = elmtB ** 2;
} else if (opts.snap.trigger === 'pointer') {
if (self.options.container === 'window') {
elmtL = pmx;
elmtT = pmy;
elmtR = window.innerWidth - pmx;
elmtB = window.innerHeight - pmy;
elmtL2 = pmx ** 2;
elmtT2 = elmtT ** 2;
elmtR2 = elmtR ** 2;
elmtB2 = elmtB ** 2;
} else {
overlaps = self.overlaps(parent, 'paddingbox', e);
elmtL = overlaps.pointer.left;
elmtT = overlaps.pointer.top;
elmtR = overlaps.pointer.right;
elmtB = overlaps.pointer.bottom;
elmtL2 = overlaps.pointer.left ** 2;
elmtT2 = overlaps.pointer.top ** 2;
elmtR2 = overlaps.pointer.right ** 2;
elmtB2 = overlaps.pointer.bottom ** 2;
}
}
}
let lefttopVectorDrag = Math.sqrt(elmtL2 + elmtT2),
leftbottomVectorDrag = Math.sqrt(elmtL2 + elmtB2),
righttopVectorDrag = Math.sqrt(elmtR2 + elmtT2),
rightbottomVectorDrag = Math.sqrt(elmtR2 + elmtB2),
horizontalDeltaDrag = Math.abs(elmtL - elmtR) / 2,
verticalDeltaDrag = Math.abs(elmtT - elmtB) / 2,
leftVectorDrag = Math.sqrt(elmtL2 + verticalDeltaDrag ** 2),
topVectorDrag = Math.sqrt(elmtT2 + horizontalDeltaDrag ** 2),
rightVectorDrag = Math.sqrt(elmtR2 + verticalDeltaDrag ** 2),
bottomVectorDrag = Math.sqrt(elmtB2 + horizontalDeltaDrag ** 2);
// prevent selections while dragging
window.getSelection().removeAllRanges();
// trigger drag permanently while dragging
document.dispatchEvent(jspaneldrag);
// move elmt and apply axis option
if (!opts.axis || opts.axis === 'x') {
self.style.left = startLeft + (pmx - psx) / scaleFactor.x + startLeftCorrection + 'px'; // set new css left of elmt depending on opts.axis
}
if (!opts.axis || opts.axis === 'y') {
self.style.top = startTop + (pmy - psy) / scaleFactor.y + 'px'; // set new css top of elmt depending on opts.axis
}
// apply grid option
if (opts.grid) {
let grid = opts.grid,
axis = opts.axis;
// formula rounds to the nearest multiple of grid
// https://www.webveteran.com/blog/web-coding/javascript-round-to-any-multiple-of-a-specific-number/
let x = grid[0] * Math.round((startLeft + (pmx - psx)) / grid[0]),
y = grid[1] * Math.round((startTop + (pmy - psy)) / grid[1]);
if (!axis || axis === 'x') {
self.style.left = `${x}px`;
}
if (!axis || axis === 'y') {
self.style.top = `${y}px`;
}
}
// apply containment option
if (opts.containment || opts.containment === 0) {
let containment = opts.containment;
let maxLeft, maxTop;
// calc maxLeft and maxTop (minLeft and MinTop is equal to containment setting)
if (self.options.container === 'window') {
maxLeft = window.innerWidth - parseFloat(dragStyles.width) - containment[1] - scrollbarwidths.y;
maxTop = window.innerHeight - parseFloat(dragStyles.height) - containment[2] - scrollbarwidths.x;
} else {
let xCorr = parseFloat(parentStyles.borderLeftWidth) + parseFloat(parentStyles.borderRightWidth),
yCorr = parseFloat(parentStyles.borderTopWidth) + parseFloat(parentStyles.borderBottomWidth);
maxLeft =
parentRect.width / scaleFactor.x - parseFloat(dragStyles.width) - containment[1] - xCorr - scrollbarwidths.y;
maxTop =
parentRect.height / scaleFactor.y -
parseFloat(dragStyles.height) -
containment[2] -
yCorr -
scrollbarwidths.x;
}
if (parseFloat(self.style.left) <= containment[3]) {
self.style.left = containment[3] + 'px';
}
if (parseFloat(self.style.top) <= containment[0]) {
self.style.top = containment[0] + 'px';
}
if (parseFloat(self.style.left) >= maxLeft) {
self.style.left = maxLeft + 'px';
}
if (parseFloat(self.style.top) >= maxTop) {
self.style.top = maxTop + 'px';
}
}
// callback while dragging
if (opts.drag.length) {
let paneldata = {
left: elmtL,
top: elmtT,
right: elmtR,
bottom: elmtB,
width: parseFloat(dragStyles.width),
height: parseFloat(dragStyles.height),
};
jsPanel.processCallbacks(self, opts.drag, false, paneldata, e);
}
// apply snap options
if (opts.snap) {
let snapSens = opts.snap.sensitivity,
topSensAreaLength = parent === document.body ? window.innerWidth / 8 : parentRect.width / 8,
sideSensAreaLength = parent === document.body ? window.innerHeight / 8 : parentRect.height / 8;
self.snappableTo = false;
jsPanel.removeSnapAreas();
if (lefttopVectorDrag < snapSens) {
if (opts.snap.snapLeftTop !== false) {
if (!opts.snap.active || opts.snap.active === 'both') {
self.snappableTo = 'left-top';
jsPanel.createSnapArea(self, 'lt', snapSens);
} else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
if (overlaps.pointer.left > 0 && overlaps.pointer.top > 0) {
self.snappableTo = 'left-top';
jsPanel.createSnapArea(self, 'lt', snapSens);
} else {
self.snappableTo = false;
jsPanel.removeSnapAreas();
}
}
}
} else if (leftbottomVectorDrag < snapSens) {
if (opts.snap.snapLeftBottom !== false) {
if (!opts.snap.active || opts.snap.active === 'both') {
self.snappableTo = 'left-bottom';
jsPanel.createSnapArea(self, 'lb', snapSens);
} else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
if (overlaps.pointer.left > 0 && overlaps.pointer.bottom > 0) {
self.snappableTo = 'left-bottom';
jsPanel.createSnapArea(self, 'lb', snapSens);
} else {
self.snappableTo = false;
jsPanel.removeSnapAreas();
}
}
}
} else if (righttopVectorDrag < snapSens) {
if (opts.snap.snapRightTop !== false) {
if (!opts.snap.active || opts.snap.active === 'both') {
self.snappableTo = 'right-top';
jsPanel.createSnapArea(self, 'rt', snapSens);
} else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
if (overlaps.pointer.right > 0 && overlaps.pointer.top > 0) {
self.snappableTo = 'right-top';
jsPanel.createSnapArea(self, 'rt', snapSens);
} else {
self.snappableTo = false;
jsPanel.removeSnapAreas();
}
}
}
} else if (rightbottomVectorDrag < snapSens) {
if (opts.snap.snapRightBottom !== false) {
if (!opts.snap.active || opts.snap.active === 'both') {
self.snappableTo = 'right-bottom';
jsPanel.createSnapArea(self, 'rb', snapSens);
} else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
if (overlaps.pointer.right > 0 && overlaps.pointer.bottom > 0) {
self.snappableTo = 'right-bottom';
jsPanel.createSnapArea(self, 'rb', snapSens);
} else {
self.snappableTo = false;
jsPanel.removeSnapAreas();
}
}
}
} else if (elmtT < snapSens && topVectorDrag < topSensAreaLength) {
if (opts.snap.snapCenterTop !== false) {
if (!opts.snap.active || opts.snap.active === 'both') {
self.snappableTo = 'center-top';
jsPanel.createSnapArea(self, 'ct', snapSens);
} else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
if (overlaps.pointer.top > 0) {
self.snappableTo = 'center-top';
jsPanel.createSnapArea(self, 'ct', snapSens);
} else {
self.snappableTo = false;
jsPanel.removeSnapAreas();
}
}
}
} else if (elmtL < snapSens && leftVectorDrag < sideSensAreaLength) {
if (opts.snap.snapLeftCenter !== false) {
if (!opts.snap.active || opts.snap.active === 'both') {
self.snappableTo = 'left-center';
jsPanel.createSnapArea(self, 'lc', snapSens);
} else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
if (overlaps.pointer.left > 0) {
self.snappableTo = 'left-center';
jsPanel.createSnapArea(self, 'lc', snapSens);
} else {
self.snappableTo = false;
jsPanel.removeSnapAreas();
}
}
}
} else if (elmtR < snapSens && rightVectorDrag < sideSensAreaLength) {
if (opts.snap.snapRightCenter !== false) {
if (!opts.snap.active || opts.snap.active === 'both') {
self.snappableTo = 'right-center';
jsPanel.createSnapArea(self, 'rc', snapSens);
} else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
if (overlaps.pointer.right > 0) {
self.snappableTo = 'right-center';
jsPanel.createSnapArea(self, 'rc', snapSens);
} else {
self.snappableTo = false;
jsPanel.removeSnapAreas();
}
}
}
} else if (elmtB < snapSens && bottomVectorDrag < topSensAreaLength) {
if (opts.snap.snapCenterBottom !== false) {
if (!opts.snap.active || opts.snap.active === 'both') {
self.snappableTo = 'center-bottom';
jsPanel.createSnapArea(self, 'cb', snapSens);
} else if (opts.snap.trigger === 'pointer' && opts.snap.active && opts.snap.active === 'inside') {
if (overlaps.pointer.bottom > 0) {
self.snappableTo = 'center-bottom';
jsPanel.createSnapArea(self, 'cb', snapSens);
} else {
self.snappableTo = false;
jsPanel.removeSnapAreas();
}
}
}
}
}
// opts.drop
if (opts.drop && opts.drop.dropZones) {
// IE doesn't offer document.elementsFromPoint() but document.msElementsFromPoint()
let elementsFromPoint = jsPanel.isIE ? 'msElementsFromPoint' : 'elementsFromPoint';
let elementsFrom = document[elementsFromPoint](e.clientX, e.clientY);
// document.msElementsFromPoint() returns a nodeList -> convert to array
if (!Array.isArray(elementsFrom)) {
elementsFrom = Array.prototype.slice.call(elementsFrom);
}
opts.drop.dropZones.forEach(zone => {
// Array.prototype.includes() needs polyfill in IE
if (elementsFrom.includes(zone)) {
self.droppableTo = zone;
}
});
// do not include following if statement in this.options.dragit.drop.dropZones.forEach !!!!
if (!elementsFrom.includes(self.droppableTo)) {
self.droppableTo = false;
}
}
};
jsPanel.pointermove.forEach(e => document.addEventListener(e, dragElmt));
// remove drag handler when mouse leaves browser window (mouseleave doesn't work)
// include the abort signal here so this event can be removed when the panel is closed
window.addEventListener('mouseout', windowListener, false, {capture: false, signal: abortSignal});
});
});
jsPanel.pointerup.forEach(event => {
// include the abort signal here so this event can be removed when the panel is closed
document.addEventListener(event, pointerUpHandlerDragit, { signal: abortSignal});
window.removeEventListener('mouseout', windowListener);
});
// dragit is initialized - now disable if set
if (options.disable) {
handle.style.pointerEvents = 'none';
}
});
return self;
};
self.dragit = string => {
const dragitOptions = Object.assign({}, jsPanel.defaults.dragit, options.dragit),
handles = self.querySelectorAll(dragitOptions.handles);
if (string === 'disable') {
handles.forEach(handle => handle.style.pointerEvents = 'none');
} else {
handles.forEach(handle => handle.style.pointerEvents = 'auto');
}
return self;
};
self.sizeit = (options = {}) => {
const jspanelresizestart = new CustomEvent('jspanelresizestart', { detail: self.id }),
jspanelresize = new CustomEvent('jspanelresize', { detail: self.id }),
jspanelresizestop = new CustomEvent('jspanelresizestop', { detail: self.id });
// make panel available as event object property 'panel'
[jspanelresizestart, jspanelresize, jspanelresizestop].forEach(evt => evt.panel = self);
let opts = {},
resizePanel,
resizestarted,
w,
h,
startWidth,
startHeight;
opts.handles = options.handles || jsPanel.defaults.resizeit.handles;
opts.handles.split(',').forEach(item => {
const node = document.createElement('DIV');
node.className = `jsPanel-resizeit-handle jsPanel-resizeit-${item.trim()}`;
//node.style.zIndex = 90;
self.append(node);
});
// cache option aspectRatio of original resizeit configuration (is restored on pointerup)
let cachedOptionAspectRatio = options.aspectRatio ? options.aspectRatio : false;
function windowListener(e) {
if (e.relatedTarget === null) {
jsPanel.pointermove.forEach(evt => document.removeEventListener(evt, resizePanel, false));
}
}
function pointerUpHandlerResizeit(e) {
jsPanel.pointermove.forEach(evt => document.removeEventListener(evt, resizePanel, false));
if (e.target.classList && e.target.classList.contains('jsPanel-resizeit-handle')) {
let isLeftChange,
isTopChange,
cl = e.target.className;
if (cl.match(/jsPanel-resizeit-nw|jsPanel-resizeit-w|jsPanel-resizeit-sw/i)) {
isLeftChange = true;
}
if (cl.match(/jsPanel-resizeit-nw|jsPanel-resizeit-n|jsPanel-resizeit-ne/i)) {
isTopChange = true;
}
// snap panel to grid (doesn't work that well if inside function resizePanel)
if (opts.grid && Array.isArray(opts.grid)) {
if (opts.grid.length === 1) {
opts.grid[1] = opts.grid[0];
}
const cw = parseFloat(self.style.width),
ch = parseFloat(self.style.height),
modW = cw % opts.grid[0],
modH = ch % opts.grid[1],
cx = parseFloat(self.style.left),
cy = parseFloat(self.style.top),
modX = cx % opts.grid[0],
modY = cy % opts.grid[1];
if (modW < opts.grid[0] / 2) {
self.style.width = cw - modW + 'px';
} else {
self.style.width = cw + (opts.grid[0] - modW) + 'px';
}
if (modH < opts.grid[1] / 2) {
self.style.height = ch - modH + 'px';
} else {
self.style.height = ch + (opts.grid[1] - modH) + 'px';
}
if (isLeftChange) {
if (modX < opts.grid[0] / 2) {
self.style.left = cx - modX + 'px';
} else {
self.style.left = cx + (opts.grid[0] - modX) + 'px';
}
}
if (isTopChange) {
if (modY < opts.grid[1] / 2) {
self.style.top = cy - modY + 'px';
} else {
self.style.top = cy + (opts.grid[1] - modY) + 'px';
}
}
}
}
if (resizestarted) {
self.content.style.pointerEvents = 'inherit';
resizestarted = undefined;
self.saveCurrentDimensions();
self.saveCurrentPosition();
self.calcSizeFactors();
let smallifyBtn = self.controlbar.querySelector('.jsPanel-btn-smallify');
let elmtRect = self.getBoundingClientRect();
if (smallifyBtn && elmtRect.height > startHeight + 5) {
smallifyBtn.style.transform = 'rotate(0deg)';
}
document.dispatchEvent(jspanelresizestop);
if (opts.stop.length) {
let stopStyles = window.getComputedStyle(self),
paneldata = {
left: parseFloat(stopStyles.left),
top: parseFloat(stopStyles.top),
width: parseFloat(stopStyles.width),
height: parseFloat(stopStyles.height),
};
jsPanel.processCallbacks(self, opts.stop, false, paneldata, e);
}
}
self.content.style.pointerEvents = 'inherit';
// restore other panel's css pointer-events
document.querySelectorAll('iframe').forEach(frame => frame.style.pointerEvents = 'auto');
// restore option aspectRatio to original configuration
opts.aspectRatio = cachedOptionAspectRatio;
document.removeEventListener(e, pointerUpHandlerResizeit);
}
self.querySelectorAll('.jsPanel-resizeit-handle').forEach(handle => {
handle.style.touchAction = 'none';
jsPanel.pointerdown.forEach(event => {
handle.addEventListener(event, e => {
// prevent window scroll while resizing elmt
e.preventDefault();
e.stopPropagation();
// disable resizing for all mouse buttons but left
if (e.button && e.button > 0) {
return false;
}
// factor is needed only for the modifier key Shift feature
let factor = 1;
// setup and normalize resizeit options
opts = Object.assign({}, jsPanel.defaults.resizeit, options);
if (opts.containment || opts.containment === 0) {
opts.containment = jsPanel.pOcontainment(opts.containment);
}
// legacy line: aspectRatio should be either 'panel' or 'content', not just true
if (opts.aspectRatio && opts.aspectRatio === true) {
opts.aspectRatio = 'panel';
}
// set aspectRatio according to modifier key
if (jsPanel.modifier) {
let modifier = jsPanel.modifier;
if (modifier.altKey) {
opts.aspectRatio = 'content';
} else if (modifier.ctrlKey) {
opts.aspectRatio = 'panel';
} else if (modifier.shiftKey) {
opts.aspectRatio = false;
factor = 2; // does work only with 2 as value
}
}
// noinspection JSUnresolvedFunction
let maxWidth = typeof opts.maxWidth === 'function' ? opts.maxWidth() : opts.maxWidth || 10000,
maxHeight = typeof opts.maxHeight === 'function' ? opts.maxHeight() : opts.maxHeight || 10000,
minWidth = typeof opts.minWidth === 'function' ? opts.minWidth() : opts.minWidth,
minHeight = typeof opts.minHeight === 'function' ? opts.minHeight() : opts.minHeight;
self.content.style.pointerEvents = 'none';
// prevents iframes in other panel from interfering with resize action of dragged panel
document.querySelectorAll('iframe').forEach(frame => frame.style.pointerEvents = 'none');
// noinspection JSUnresolvedVariable
const elmtParent = self.parentElement,
elmtParentTagName = elmtParent.tagName.toLowerCase(),
elmtRect = self.getBoundingClientRect(),
elmtParentRect = elmtParent.getBoundingClientRect(),
elmtParentStyles = window.getComputedStyle(elmtParent, null),
elmtParentBLW = parseInt(elmtParentStyles.borderLeftWidth, 10),
elmtParentBTW = parseInt(elmtParentStyles.borderTopWidth, 10),
elmtParentPosition = elmtParentStyles.getPropertyValue('position'),
startX = (e.clientX || e.clientX === 0) || e.touches[0].clientX,
startY = (e.clientY || e.clientY === 0) || e.touches[0].clientY,
startRatio = startX / startY,
resizeHandleClassList = e.target.classList,
scaleFactor = self.getScaleFactor(),
aspectRatio = elmtRect.width / elmtRect.height,
elmtContentRect = self.content.getBoundingClientRect(),
aspectRatioContent = elmtContentRect.width / elmtContentRect.height,
hdrHeight = self.header.getBoundingClientRect().height, // needed in aspectRatio
ftrHeight = self.footer.getBoundingClientRect().height || 0; // needed in aspectRatio
let startLeft = elmtRect.left,
startTop = elmtRect.top,
maxWidthEast = 10000,
maxWidthWest = 10000,
maxHeightSouth = 10000,
maxHeightNorth = 10000;
startWidth = elmtRect.width;
startHeight = elmtRect.height;
if (elmtParentTagName !== 'body') {
startLeft = elmtRect.left - elmtParentRect.left + elmtParent.scrollLeft;
startTop = elmtRect.top - elmtParentRect.top + elmtParent.scrollTop;
}
// calc min/max left/top values if containment is set - code from jsDraggable
if (elmtParentTagName === 'body' && opts.containment) {
maxWidthEast = document.documentElement.clientWidth - elmtRect.left;
maxHeightSouth = document.documentElement.clientHeight - elmtRect.top;
maxWidthWest = elmtRect.width + elmtRect.left;
maxHeightNorth = elmtRect.height + elmtRect.top;
} else {
// if panel is NOT in body
if (opts.containment) {
if (elmtParentPosition === 'static') {
maxWidthEast = elmtParentRect.width - elmtRect.left + elmtParentBLW;
maxHeightSouth = elmtParentRect.height + elmtParentRect.top - elmtRect.top + elmtParentBTW;
maxWidthWest = elmtRect.width + (elmtRect.left - elmtParentRect.left) - elmtParentBLW;
maxHeightNorth = elmtRect.height + (elmtRect.top - elmtParentRect.top) - elmtParentBTW;
} else {
maxWidthEast = elmtParent.clientWidth - (elmtRect.left - elmtParentRect.left) / scaleFactor.x + elmtParentBLW;
maxHeightSouth = elmtParent.clientHeight - (elmtRect.top - elmtParentRect.top) / scaleFactor.y + elmtParentBTW;
maxWidthWest = (elmtRect.width + elmtRect.left - elmtParentRect.left) / scaleFactor.x - elmtParentBLW;
maxHeightNorth = self.clientHeight + (elmtRect.top - elmtParentRect.top) / scaleFactor.y - elmtParentBTW;
}
}
}
// if original opts.containment is an array
if (opts.containment) {
maxWidthWest -= opts.containment[3];
maxHeightNorth -= opts.containment[0];
maxWidthEast -= opts.containment[1];
maxHeightSouth -= opts.containment[2];
}
// calculate corrections for rotated panels
const computedStyle = window.getComputedStyle(self),
wDif = parseFloat(computedStyle.width) - elmtRect.width,
hDif = parseFloat(computedStyle.height) - elmtRect.height;
let xDif = parseFloat(computedStyle.left) - elmtRect.left,
yDif = parseFloat(computedStyle.top) - elmtRect.top;
if (elmtParent !== document.body) {
xDif += elmtParentRect.left;
yDif += elmtParentRect.top;
}
// used in aspectRatio code
let borderTopWidth = parseInt(computedStyle.borderTopWidth, 10),
borderRightWidth = parseInt(computedStyle.borderRightWidth, 10),
borderBottomWidth = parseInt(computedStyle.borderBottomWidth, 10),
borderLeftWidth = parseInt(computedStyle.borderLeftWidth, 10);
resizePanel = evt => {
evt.preventDefault();
// trigger resizestarted only once per resize
if (!resizestarted) {
document.dispatchEvent(jspanelresizestart);
if (opts.start.length) {
jsPanel.processCallbacks(
self,
opts.start,
false,
{
width: startWidth,
height: startHeight,
left: startLeft,
top: startTop,
},
evt
);
}
self.front();
// if panel is maximized on resize start set status to normalized and swap maximize/normalize buttons
if (self.status === 'maximized') {
self.status = 'normalized';
if (self.controlbar.querySelector('.jsPanel-btn-maximize')) {
self.setControlStatus('maximize', 'show');
}
if (self.controlbar.querySelector('.jsPanel-btn-normalize')) {
self.setControlStatus('normalize', 'hide');
}
}
if (elmtRect.height > startHeight + 5) {
self.status = 'normalized';
self.setControls(['.jsPanel-btn-normalize']);
}
}
resizestarted = 1;
// trigger resize permanently while resizing
document.dispatchEvent(jspanelresize);
// possibly updated while resizing
let eventX = evt.touches ? evt.touches[0].clientX : evt.clientX,
eventY = evt.touches ? evt.touches[0].clientY : evt.clientY,
overlaps;
if (resizeHandleClassList.contains('jsPanel-resizeit-e')) {
//w = startWidth + (eventX - startX) / scaleFactor.x + wDif;
w = startWidth + ((eventX - startX) * factor) / scaleFactor.x + wDif; // needs left adjust, for width and height adjust factor may be either 1 (no adjust) or 2
if (w >= maxWidthEast) {
w = maxWidthEast;
}
if (w >= maxWidth) {
w = maxWidth;
}
if (w <= minWidth) {
w = minWidth;
}
self.style.width = w + 'px';
if (factor === 2) {
// factor works only with value of 2 when adjusting left or top
self.style.left = startLeft - (eventX - startX) + 'px';
}
if (opts.aspectRatio === 'content') {
// if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
self.style.height =
(w - borderRightWidth - borderLeftWidth) / aspectRatioContent +
hdrHeight +
ftrHeight +
borderTopWidth +
borderBottomWidth +
'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.bottom <= opts.containment[2]) {
self.style.height = maxHeightSouth + 'px';
self.style.width = maxHeightSouth * aspectRatioContent + 'px';
}
}
} else if (opts.aspectRatio === 'panel') {
// otherwise the complete panel maintains its aspect ratio
self.style.height = w / aspectRatio + 'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.bottom <= opts.containment[2]) {
self.style.height = maxHeightSouth + 'px';
self.style.width = maxHeightSouth * aspectRatio + 'px';
}
}
}
} else if (resizeHandleClassList.contains('jsPanel-resizeit-s')) {
//h = startHeight + (eventY - startY) / scaleFactor.y + hDif;
h = startHeight + ((eventY - startY) * factor) / scaleFactor.y + hDif; // needs top adjust
if (h >= maxHeightSouth) {
h = maxHeightSouth;
}
if (h >= maxHeight) {
h = maxHeight;
}
if (h <= minHeight) {
h = minHeight;
}
self.style.height = h + 'px';
if (factor === 2) {
self.style.top = startTop - (eventY - startY) + 'px';
}
if (opts.aspectRatio === 'content') {
// if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
self.style.width =
(h - hdrHeight - ftrHeight - borderTopWidth - borderBottomWidth) * aspectRatioContent +
borderTopWidth +
borderBottomWidth +
'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.right <= opts.containment[1]) {
self.style.width = maxWidthEast + 'px';
self.style.height = maxWidthEast / aspectRatioContent + 'px';
}
}
} else if (opts.aspectRatio === 'panel') {
// otherwise the complete panel maintains its aspect ratio
self.style.width = h * aspectRatio + 'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.right <= opts.containment[1]) {
self.style.width = maxWidthEast + 'px';
self.style.height = maxWidthEast / aspectRatio + 'px';
}
}
}
} else if (resizeHandleClassList.contains('jsPanel-resizeit-w')) {
//w = startWidth + (startX - eventX) / scaleFactor.x + wDif;
w = startWidth + ((startX - eventX) * factor) / scaleFactor.x + wDif; // doesn't need left adjust
if (w <= maxWidth && w >= minWidth && w <= maxWidthWest) {
self.style.left = startLeft + (eventX - startX) / scaleFactor.x + xDif + 'px';
}
if (w >= maxWidthWest) {
w = maxWidthWest;
}
if (w >= maxWidth) {
w = maxWidth;
}
if (w <= minWidth) {
w = minWidth;
}
self.style.width = w + 'px';
if (opts.aspectRatio === 'content') {
// if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
self.style.height =
(w - borderRightWidth - borderLeftWidth) / aspectRatioContent +
hdrHeight +
ftrHeight +
borderTopWidth +
borderBottomWidth +
'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.bottom <= opts.containment[2]) {
self.style.height = maxHeightSouth + 'px';
self.style.width = maxHeightSouth * aspectRatioContent + 'px';
}
}
} else if (opts.aspectRatio === 'panel') {
// otherwise the complete panel maintains its aspect ratio
self.style.height = w / aspectRatio + 'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.bottom <= opts.containment[2]) {
self.style.height = maxHeightSouth + 'px';
self.style.width = maxHeightSouth * aspectRatio + 'px';
}
}
}
} else if (resizeHandleClassList.contains('jsPanel-resizeit-n')) {
//h = startHeight + (startY - eventY) / scaleFactor.y + hDif;
h = startHeight + ((startY - eventY) * factor) / scaleFactor.y + hDif; // doesn't need top adjust
if (h <= maxHeight && h >= minHeight && h <= maxHeightNorth) {
self.style.top = startTop + (eventY - startY) / scaleFactor.y + yDif + 'px';
}
if (h >= maxHeightNorth) {
h = maxHeightNorth;
}
if (h >= maxHeight) {
h = maxHeight;
}
if (h <= minHeight) {
h = minHeight;
}
self.style.height = h + 'px';
if (opts.aspectRatio === 'content') {
// if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
self.style.width =
(h - hdrHeight - ftrHeight - borderTopWidth - borderBottomWidth) * aspectRatioContent +
borderTopWidth +
borderBottomWidth +
'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.right <= opts.containment[1]) {
self.style.width = maxWidthEast + 'px';
self.style.height = maxWidthEast / aspectRatioContent + 'px';
}
}
} else if (opts.aspectRatio === 'panel') {
// otherwise the complete panel maintains its aspect ratio
self.style.width = h * aspectRatio + 'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.right <= opts.containment[1]) {
self.style.width = maxWidthEast + 'px';
self.style.height = maxWidthEast / aspectRatio + 'px';
}
}
}
} else if (resizeHandleClassList.contains('jsPanel-resizeit-se')) {
//w = startWidth + (eventX - startX) / scaleFactor.x + wDif;
w = startWidth + ((eventX - startX) * factor) / scaleFactor.x + wDif; // needs left adjust
if (w >= maxWidthEast) {
w = maxWidthEast;
}
if (w >= maxWidth) {
w = maxWidth;
}
if (w <= minWidth) {
w = minWidth;
}
self.style.width = w + 'px';
if (factor === 2) {
self.style.left = startLeft - (eventX - startX) + 'px';
}
if (opts.aspectRatio) {
self.style.height = w / aspectRatio + 'px';
}
//h = startHeight + (eventY - startY) / scaleFactor.y + hDif;
h = startHeight + ((eventY - startY) * factor) / scaleFactor.y + hDif; // needs top adjust
if (h >= maxHeightSouth) {
h = maxHeightSouth;
}
if (h >= maxHeight) {
h = maxHeight;
}
if (h <= minHeight) {
h = minHeight;
}
self.style.height = h + 'px';
if (factor === 2) {
self.style.top = startTop - (eventY - startY) + 'px';
}
if (opts.aspectRatio === 'content') {
// if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
self.style.width =
(h - hdrHeight - ftrHeight - borderTopWidth - borderBottomWidth) * aspectRatioContent +
borderTopWidth +
borderBottomWidth +
'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.right <= opts.containment[1]) {
self.style.width = maxWidthEast + 'px';
self.style.height = maxWidthEast / aspectRatioContent + 'px';
}
}
} else if (opts.aspectRatio === 'panel') {
// otherwise the complete panel maintains its aspect ratio
self.style.width = h * aspectRatio + 'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.right <= opts.containment[1]) {
self.style.width = maxWidthEast + 'px';
self.style.height = maxWidthEast / aspectRatio + 'px';
}
}
}
} else if (resizeHandleClassList.contains('jsPanel-resizeit-sw')) {
//h = startHeight + (eventY - startY) / scaleFactor.y + hDif;
h = startHeight + ((eventY - startY) * factor) / scaleFactor.y + hDif; // needs top adjust
if (h >= maxHeightSouth) {
h = maxHeightSouth;
}
if (h >= maxHeight) {
h = maxHeight;
}
if (h <= minHeight) {
h = minHeight;
}
self.style.height = h + 'px';
if (factor === 2) {
self.style.top = startTop - (eventY - startY) + 'px';
}
if (opts.aspectRatio) {
self.style.width = h * aspectRatio + 'px';
}
//w = startWidth + (startX - eventX) / scaleFactor.x + wDif;
w = startWidth + ((startX - eventX) * factor) / scaleFactor.x + wDif; // doesn't need left adjust
if (w <= maxWidth && w >= minWidth && w <= maxWidthWest) {
self.style.left = startLeft + (eventX - startX) / scaleFactor.x + xDif + 'px';
}
if (w >= maxWidthWest) {
w = maxWidthWest;
}
if (w >= maxWidth) {
w = maxWidth;
}
if (w <= minWidth) {
w = minWidth;
}
self.style.width = w + 'px';
if (opts.aspectRatio === 'content') {
// if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
self.style.height =
(w - borderRightWidth - borderLeftWidth) / aspectRatioContent +
hdrHeight +
ftrHeight +
borderTopWidth +
borderBottomWidth +
'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.bottom <= opts.containment[2]) {
self.style.height = maxHeightSouth + 'px';
self.style.width = maxHeightSouth * aspectRatioContent + 'px';
}
}
} else if (opts.aspectRatio === 'panel') {
// otherwise the complete panel maintains its aspect ratio
self.style.height = w / aspectRatio + 'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.bottom <= opts.containment[2]) {
self.style.height = maxHeightSouth + 'px';
self.style.width = maxHeightSouth * aspectRatio + 'px';
}
}
}
} else if (resizeHandleClassList.contains('jsPanel-resizeit-ne')) {
//w = startWidth + (eventX - startX) / scaleFactor.x + wDif;
w = startWidth + ((eventX - startX) * factor) / scaleFactor.x + wDif; // needs left adjust
if (w >= maxWidthEast) {
w = maxWidthEast;
}
if (w >= maxWidth) {
w = maxWidth;
}
if (w <= minWidth) {
w = minWidth;
}
self.style.width = w + 'px';
if (factor === 2) {
self.style.left = startLeft - (eventX - startX) + 'px';
}
if (opts.aspectRatio) {
self.style.height = w / aspectRatio + 'px';
}
h = startHeight + ((startY - eventY) * factor) / scaleFactor.y + hDif; // doesn't need top adjust
if (h <= maxHeight && h >= minHeight && h <= maxHeightNorth) {
self.style.top = startTop + (eventY - startY) / scaleFactor.y + yDif + 'px';
}
if (h >= maxHeightNorth) {
h = maxHeightNorth;
}
if (h >= maxHeight) {
h = maxHeight;
}
if (h <= minHeight) {
h = minHeight;
}
self.style.height = h + 'px';
if (opts.aspectRatio === 'content') {
// if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
self.style.width =
(h - hdrHeight - ftrHeight - borderTopWidth - borderBottomWidth) * aspectRatioContent +
borderTopWidth +
borderBottomWidth +
'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.right <= opts.containment[1]) {
self.style.width = maxWidthEast + 'px';
self.style.height = maxWidthEast / aspectRatioContent + 'px';
}
}
} else if (opts.aspectRatio === 'panel') {
// otherwise the complete panel maintains its aspect ratio
self.style.width = h * aspectRatio + 'px';
if (opts.containment) {
overlaps = self.overlaps(elmtParent);
if (overlaps.right <= opts.containment[1]) {
self.style.width = maxWidthEast + 'px';
self.style.height = maxWidthEast / aspectRatio + 'px';
}
}
}
} else if (resizeHandleClassList.contains('jsPanel-resizeit-nw')) {
if (opts.aspectRatio && resizeHandleClassList.contains('jsPanel-resizeit-nw')) {
eventX = eventY * startRatio;
eventY = eventX / startRatio;
}
//w = startWidth + (startX - eventX) / scaleFactor.x + wDif;
w = startWidth + ((startX - eventX) * factor) / scaleFactor.x + wDif; // doesn't need left adjust
if (w <= maxWidth && w >= minWidth && w <= maxWidthWest) {
self.style.left = startLeft + (eventX - startX) / scaleFactor.x + xDif + 'px';
}
if (w >= maxWidthWest) {
w = maxWidthWest;
}
if (w >= maxWidth) {
w = maxWidth;
}
if (w <= minWidth) {
w = minWidth;
}
self.style.width = w + 'px';
if (opts.aspectRatio) {
self.style.height = w / aspectRatio + 'px';
}
h = startHeight + ((startY - eventY) * factor) / scaleFactor.y + hDif; // doesn't need top adjust
if (h <= maxHeight && h >= minHeight && h <= maxHeightNorth) {
self.style.top = startTop + (eventY - startY) / scaleFactor.y + yDif + 'px';
}
if (h >= maxHeightNorth) {
h = maxHeightNorth;
}
if (h >= maxHeight) {
h = maxHeight;
}
if (h <= minHeight) {
h = minHeight;
}
self.style.height = h + 'px';
if (opts.aspectRatio === 'content') {
// if aspectRatio is true and set to 'content' the panels content section maintains its aspect ratio
self.style.width =
(h - hdrHeight - ftrHeight - borderTopWidth - borderBottomWidth) * aspectRatioContent +
borderTopWidth +
borderBottomWidth +
'px';
} else if (opts.aspectRatio === 'panel') {
// otherwise the complete panel maintains its aspect ratio
self.style.width = h * aspectRatio + 'px';
}
}
window.getSelection().removeAllRanges();
// get current position and size values while resizing
const styles = window.getComputedStyle(self),
values = {
left: parseFloat(styles.left),
top: parseFloat(styles.top),
right: parseFloat(styles.right),
bottom: parseFloat(styles.bottom),
width: parseFloat(styles.width),
height: parseFloat(styles.height),
};
// callback while resizing
if (opts.resize.length) {
jsPanel.processCallbacks(self, opts.resize, false, values, evt);
}
};
jsPanel.pointermove.forEach(event => document.addEventListener(event, resizePanel, false));
// remove resize handler when mouse leaves browser window (mouseleave doesn't work)
// include the abort signal here so the event can be removed when the panel is closed
window.addEventListener('mouseout', windowListener, {capture: false, signal: abortSignal});
});
});
jsPanel.pointerup.forEach(function (event) {
// include the abort signal here so the event can be removed when the panel is closed
document.addEventListener(event, pointerUpHandlerResizeit, {signal:abortSignal});
window.removeEventListener('mouseout', windowListener);
});
// resizeit is initialized - now disable if set
if (options.disable) {
handle.style.pointerEvents = 'none';
}
});
return self;
};
self.resizeit = string => {
const handles = self.querySelectorAll('.jsPanel-resizeit-handle');
if (string === 'disable') {
handles.forEach(handle => handle.style.pointerEvents = 'none');
} else {
handles.forEach(handle => handle.style.pointerEvents = 'auto');
}
return self;
};
self.getScaleFactor = () => {
const rect = self.getBoundingClientRect();
return {
x: rect.width / self.offsetWidth,
y: rect.height / self.offsetHeight,
};
};
self.calcSizeFactors = () => {
const styles = window.getComputedStyle(self);
if (options.container === 'window') {
self.hf = parseFloat(styles.left) / (window.innerWidth - parseFloat(styles.width));
self.vf = parseFloat(styles.top) / (window.innerHeight - parseFloat(styles.height));
} else {
if (self.parentElement) {
let parentStyles = self.parentElement.getBoundingClientRect();
self.hf = parseFloat(styles.left) / (parentStyles.width - parseFloat(styles.width));
self.vf = parseFloat(styles.top) / (parentStyles.height - parseFloat(styles.height));
}
}
};
self.saveCurrentDimensions = () => {
const normData = window.getComputedStyle(self);
self.currentData.width = normData.width;
self.currentData.height = normData.height;
};
self.saveCurrentPosition = () => {
const normData = window.getComputedStyle(self);
self.currentData.left = normData.left;
self.currentData.top = normData.top;
};
self.reposition = (...params) => {
let pos = options.position,
updateCache = true,
callback;
params.forEach(value => {
if (typeof value === 'string' || typeof value === 'object') {
pos = value;
} else if (typeof value === 'boolean') {
updateCache = value;
} else if (typeof value === 'function') {
callback = value;
}
});
jsPanel.position(self, pos);
// check whether self has docked panels -> reposition docked panels as well
if (self.slaves && self.slaves.size > 0) {
self.slaves.forEach(slave => slave.reposition());
}
if (updateCache) {
self.saveCurrentPosition();
}
if (callback) {
callback.call(self, self);
}
return self;
};
self.repositionOnSnap = pos => {
let offsetX = '0',
offsetY = '0',
margins = jsPanel.pOcontainment(options.dragit.containment);
// calculate offsets
if (options.dragit.snap.containment) {
switch (pos) {
case 'left-top':
offsetX = margins[3];
offsetY = margins[0];
break;
case 'right-top':
offsetX = -margins[1];
offsetY = margins[0];
break;
case 'right-bottom':
offsetX = -margins[1];
offsetY = -margins[2];
break;
case 'left-bottom':
offsetX = margins[3];
offsetY = -margins[2];
break;
case 'center-top':
offsetX = margins[3] / 2 - margins[1] / 2;
offsetY = margins[0];
break;
case 'center-bottom':
offsetX = margins[3] / 2 - margins[1] / 2;
offsetY = -margins[2];
break;
case 'left-center':
offsetX = margins[3];
offsetY = margins[0] / 2 - margins[2] / 2;
break;
case 'right-center':
offsetX = -margins[1];
offsetY = margins[0] / 2 - margins[2] / 2;
break;
}
}
/* jsPanel.position(self, `${pos} ${offsetX} ${offsetY}`);
For some reason I could not find the line above does not work (pos and offsets in one string), but only when
center-bottom is used with different settings for left/right margin.
*/
jsPanel.position(self, pos);
jsPanel.setStyles(self, {
left: `calc(${self.style.left} + ${offsetX}px)`,
top: `calc(${self.style.top} + ${offsetY}px)`,
});
};
self.overlaps = (reference, elmtBox, event) => {
let panelRect = self.getBoundingClientRect(),
parentStyle = getComputedStyle(self.parentElement),
scaleFactor = self.getScaleFactor(),
parentBorderWidth = { top: 0, right: 0, bottom: 0, left: 0 },
referenceRect,
evtX = 0,
evtY = 0,
evtXparent = 0,
evtYparent = 0;
if (self.options.container !== 'window' && elmtBox === 'paddingbox') {
parentBorderWidth.top = parseInt(parentStyle.borderTopWidth, 10) * scaleFactor.y;
parentBorderWidth.right = parseInt(parentStyle.borderRightWidth, 10) * scaleFactor.x;
parentBorderWidth.bottom = parseInt(parentStyle.borderBottomWidth, 10) * scaleFactor.y;
parentBorderWidth.left = parseInt(parentStyle.borderLeftWidth, 10) * scaleFactor.x;
}
if (typeof reference === 'string') {
if (reference === 'window') {
referenceRect = {
left: 0,
top: 0,
right: window.innerWidth,
bottom: window.innerHeight,
};
} else if (reference === 'parent') {
referenceRect = self.parentElement.getBoundingClientRect();
} else {
referenceRect = document.querySelector(reference).getBoundingClientRect();
}
} else {
referenceRect = reference.getBoundingClientRect();
}
if (event) {
evtX = event.touches ? event.touches[0].clientX : event.clientX;
evtY = event.touches ? event.touches[0].clientY : event.clientY;
evtXparent = evtX - referenceRect.left;
evtYparent = evtY - referenceRect.top;
}
let overlapsX = panelRect.left < referenceRect.right && panelRect.right > referenceRect.left,
overlapsY = panelRect.top < referenceRect.bottom && panelRect.bottom > referenceRect.top,
overlaps = overlapsX && overlapsY;
return {
overlaps: overlaps,
top: panelRect.top - referenceRect.top - parentBorderWidth.top,
right: referenceRect.right - panelRect.right - parentBorderWidth.right,
bottom: referenceRect.bottom - panelRect.bottom - parentBorderWidth.bottom,
left: panelRect.left - referenceRect.left - parentBorderWidth.left,
parentBorderWidth: parentBorderWidth,
panelRect: panelRect,
referenceRect: referenceRect,
pointer: {
// pointer coords relative to window and reference
clientX: evtX,
clientY: evtY,
left: evtXparent - parentBorderWidth.left,
top: evtYparent - parentBorderWidth.top,
right: referenceRect.width - evtXparent - parentBorderWidth.right,
bottom: referenceRect.height - evtYparent - parentBorderWidth.bottom,
},
};
};
self.setSize = () => {
if (options.panelSize) {
const values = jsPanel.pOsize(self, options.panelSize);
self.style.width = values.width;
self.style.height = values.height;
} else if (options.contentSize) {
const values = jsPanel.pOsize(self, options.contentSize);
self.content.style.width = values.width;
self.content.style.height = values.height;
self.style.width = values.width; // explicitly assign current width/height to panel
self.content.style.width = '100%';
}
return self;
};
self.resize = (...params) => {
let dimensions = window.getComputedStyle(self),
size = { width: dimensions.width, height: dimensions.height },
updateCache = true,
callback;
params.forEach(value => {
if (typeof value === 'string') {
size = value;
} else if (typeof value === 'object') {
size = Object.assign(size, value);
} else if (typeof value === 'boolean') {
updateCache = value;
} else if (typeof value === 'function') {
callback = value;
}
});
let values = jsPanel.pOsize(self, size);
self.style.width = values.width;
self.style.height = values.height;
// check whether self has docked panels -> reposition docked panels
if (self.slaves && self.slaves.size > 0) {
self.slaves.forEach(slave => slave.reposition());
}
if (updateCache) {
self.saveCurrentDimensions();
}
self.status = 'normalized';
let smallifyBtn = self.controlbar.querySelector('.jsPanel-btn-smallify');
if (smallifyBtn) {
smallifyBtn.style.transform = 'rotate(0deg)';
}
if (callback) {
callback.call(self, self);
}
self.calcSizeFactors();
return self;
};
self.windowResizeHandler = e => {
if (e.target === window) {
// see https://bugs.jqueryui.com/ticket/7514
let status = self.status,
onWindowResize = options.onwindowresize,
left,
top;
if (status === 'maximized' && onWindowResize) {
self.maximize(false, true);
} else if (self.snapped && status !== 'minimized') {
self.snap(self.snapped, true);
} else if (status === 'normalized' || status === 'smallified' || status === 'maximized') {
let settingType = typeof onWindowResize;
if (settingType === 'boolean') {
left = (window.innerWidth - self.offsetWidth) * self.hf;
self.style.left = left <= 0 ? 0 : left + 'px';
top = (window.innerHeight - self.offsetHeight) * self.vf;
self.style.top = top <= 0 ? 0 : top + 'px';
} else if (settingType === 'function') {
onWindowResize.call(self, e, self);
} else if (settingType === 'object') {
// { preset: boolean, callback: function(event, panel){} }
if (onWindowResize.preset === true) {
left = (window.innerWidth - self.offsetWidth) * self.hf;
self.style.left = left <= 0 ? 0 : left + 'px';
top = (window.innerHeight - self.offsetHeight) * self.vf;
self.style.top = top <= 0 ? 0 : top + 'px';
onWindowResize.callback.call(self, e, self);
} else {
onWindowResize.callback.call(self, e, self);
}
}
} else if (status === 'smallifiedmax' && onWindowResize) {
self.maximize(false, true).smallify();
}
// check whether self has docked panels -> reposition docked panels as well
if (self.slaves && self.slaves.size > 0) {
self.slaves.forEach(slave => slave.reposition());
}
}
};
self.setControls = (sel, cb) => {
self.header.querySelectorAll('.jsPanel-btn').forEach(item => {
const btnClass = item.className.split('-'),
btn = btnClass[btnClass.length - 1];
if (self.getAttribute(`data-btn${btn}`) !== 'hidden') {
item.style.display = 'block';
}
});
sel.forEach(item => {
const btn = self.controlbar.querySelector(item);
if (btn) {
btn.style.display = 'none';
}
});
if (cb) {
cb.call(self, self);
}
return self;
};
self.setControlStatus = (ctrl, action = 'enable', cb) => {
const btn = self.controlbar.querySelector(`.jsPanel-btn-${ctrl}`);
switch (action) {
case 'disable':
if (self.getAttribute(`data-btn${ctrl}`) !== 'removed') {
self.setAttribute(`data-btn${ctrl}`, 'disabled');
btn.style.pointerEvents = 'none';
btn.style.opacity = 0.4;
btn.style.cursor = 'default';
}
break;
case 'hide':
if (self.getAttribute(`data-btn${ctrl}`) !== 'removed') {
self.setAttribute(`data-btn${ctrl}`, 'hidden');
btn.style.display = 'none';
}
break;
case 'show':
if (self.getAttribute(`data-btn${ctrl}`) !== 'removed') {
self.setAttribute(`data-btn${ctrl}`, 'enabled');
btn.style.display = 'block';
btn.style.pointerEvents = 'auto';
btn.style.opacity = 1;
btn.style.cursor = 'pointer';
}
break;
case 'enable':
if (self.getAttribute(`data-btn${ctrl}`) !== 'removed') {
if (self.getAttribute(`data-btn${ctrl}`) === 'hidden') {
btn.style.display = 'block';
}
self.setAttribute(`data-btn${ctrl}`, 'enabled');
btn.style.pointerEvents = 'auto';
btn.style.opacity = 1;
btn.style.cursor = 'pointer';
}
break;
case 'remove':
self.controlbar.removeChild(btn);
self.setAttribute(`data-btn${ctrl}`, 'removed');
break;
}
if (cb) {
cb.call(self, self);
}
return self;
};
self.setControlSize = ctrlSize => {
// does not work with Font Awesome webfont (only svg/js)
const size = ctrlSize.toLowerCase(),
icons = self.controlbar.querySelectorAll('.jsPanel-btn');
icons.forEach(icon => {
['jsPanel-btn-xl', 'jsPanel-btn-lg', 'jsPanel-btn-md', 'jsPanel-btn-sm', 'jsPanel-btn-xs'].forEach(item => icon.classList.remove(item));
icon.classList.add(`jsPanel-btn-${size}`);
});
// adjust font-size of title
if (size === 'xl') {
self.titlebar.style.fontSize = '1.5rem';
} else if (size === 'lg') {
self.titlebar.style.fontSize = '1.25rem';
} else if (size === 'md') {
self.titlebar.style.fontSize = '1.05rem';
} else if (size === 'sm') {
self.titlebar.style.fontSize = '.9rem';
} else if (size === 'xs') {
self.titlebar.style.fontSize = '.8rem';
}
};
self.setHeaderControls = cb => {
// add custom controls
if (self.options.headerControls.add) {
let customControls = self.options.headerControls.add;
if (!Array.isArray(customControls)) {
// if options.addControls is not an array make it one
customControls = [customControls];
}
customControls.forEach(ctrl => self.addControl(ctrl));
}
// get all control names including controls added with option.addControls
let controls = [],
ctrls = self.controlbar.querySelectorAll('.jsPanel-btn');
ctrls.forEach(ctrl => {
let match = ctrl.className.match(/jsPanel-btn-[a-z\d]{3,}/i),
ctrlName = match[0].substring(12);
controls.push(ctrlName);
});
// normalize option headerControls and reset it accordingly
const option = jsPanel.pOheaderControls(options.headerControls);
options.headerControls = option;
// set status of controls
controls.forEach(item => {
if (option[item]) {
self.setControlStatus(item, option[item]);
}
});
// set size of controls
self.setControlSize(option.size);
if (cb) {
cb.call(self, self);
}
return self;
};
self.setHeaderLogo = (logo, cb) => {
let logos = [self.headerlogo],
minPanel = document.querySelector('#' + self.id + '-min');
if (minPanel) {
logos.push(minPanel.querySelector('.jsPanel-headerlogo'));
}
if (typeof logo === 'string') {
if (!logo.startsWith('<')) {
// is assumed to be an img url
logos.forEach(item => {
jsPanel.emptyNode(item);
let img = document.createElement('img');
img.src = logo;
item.append(img);
});
} else {
logos.forEach(item => item.innerHTML = logo);
}
} else {
// assumed to be a node object
logos.forEach(item => {
jsPanel.emptyNode(item);
item.append(logo);
});
}
// images must not be draggable, otherwise dragit interactions locks
self.headerlogo.childNodes.forEach(logo => {
if (logo.nodeName && logo.nodeName === 'IMG') {
logo.setAttribute('draggable', 'false');
}
});
if (cb) {
cb.call(self, self);
}
return self;
};
self.setHeaderRemove = cb => {
self.removeChild(self.header);
self.content.classList.add('jsPanel-content-noheader');
['close', 'maximize', 'normalize', 'minimize', 'smallify'].forEach(item => self.setAttribute(`data-btn${item}`, 'removed'));
if (cb) {
cb.call(self, self);
}
return self;
};
self.setHeaderTitle = (hdrTitle, cb) => {
let titles = [self.headertitle],
minPanel = document.querySelector('#' + self.id + '-min');
if (minPanel) {
titles.push(minPanel.querySelector('.jsPanel-title'));
}
if (typeof hdrTitle === 'string') {
titles.forEach(item => item.innerHTML = hdrTitle);
} else if (typeof hdrTitle === 'function') {
titles.forEach(item => {
jsPanel.emptyNode(item);
item.innerHTML = hdrTitle();
});
} else {
// assumed to be a node object
titles.forEach(item => {
jsPanel.emptyNode(item);
item.append(hdrTitle);
});
}
if (cb) {
cb.call(self, self);
}
return self;
};
self.setIconfont = (font, panel = self, cb) => {
if (font) {
let classArray, textArray;
if (font === 'fa' || font === 'far' || font === 'fal' || font === 'fas' || font === 'fad') {
classArray = [
`${font} fa-window-close`,
`${font} fa-window-maximize`,
`${font} fa-window-restore`,
`${font} fa-window-minimize`,
`${font} fa-chevron-up`,
];
} else if (font === 'material-icons') {
classArray = [font, font, font, font, font, font];
textArray = ['close', 'fullscreen', 'fullscreen_exit', 'call_received', 'expand_less'];
} else if (Array.isArray(font)) {
classArray = [
`custom-control-icon ${font[4]}`,
`custom-control-icon ${font[3]}`,
`custom-control-icon ${font[2]}`,
`custom-control-icon ${font[1]}`,
`custom-control-icon ${font[0]}`,
];
} else if (font === 'bootstrap' || font === 'glyphicon') {
classArray = [
'glyphicon glyphicon-remove',
'glyphicon glyphicon-fullscreen',
'glyphicon glyphicon-resize-full',
'glyphicon glyphicon-minus',
'glyphicon glyphicon-chevron-up',
];
} else {
return panel;
}
panel.querySelectorAll('.jsPanel-controlbar .jsPanel-btn').forEach(item => jsPanel.emptyNode(item).innerHTML = '');
Array.prototype.slice
.call(panel.querySelectorAll('.jsPanel-controlbar .jsPanel-btn > span'))
.reverse()
.forEach((item, i) => {
item.className = classArray[i];
if (font === 'material-icons') {
item.textContent = textArray[i];
}
});
}
if (cb) {
cb.call(panel, panel);
}
return panel;
};
self.addToolbar = (place, tb, cb) => {
if (place === 'header') {
place = self.headertoolbar;
} else if (place === 'footer') {
place = self.footer;
}
if (typeof tb === 'string') {
place.innerHTML = tb;
} else if (Array.isArray(tb)) {
tb.forEach(item => {
if (typeof item === 'string') {
place.innerHTML += item;
} else {
place.append(item);
}
});
} else if (typeof tb === 'function') {
let tool = tb.call(self, self);
if (typeof tool === 'string') {
place.innerHTML = tool;
} else {
place.append(tool);
}
} else {
place.append(tb);
}
place.classList.add('active');
if (cb) {
cb.call(self, self);
}
return self;
};
self.addCloseControl = () => {
let ctrl = document.createElement('button'),
colorContent = self.content.style.color;
ctrl.classList.add('jsPanel-addCloseCtrl');
ctrl.innerHTML = jsPanel.icons.close;
ctrl.style.color = colorContent;
if (self.options.rtl) {
ctrl.classList.add('rtl');
}
self.appendChild(ctrl);
jsPanel.pointerup.forEach(evt => {
ctrl.addEventListener(evt, e => {
e.preventDefault();
// disable close for all mouse buttons but left
if (e.button && e.button > 0) {
return false;
}
self.close(null, true);
});
});
// pointerdown handler needed to prevent side effect with resize handles
jsPanel.pointerdown.forEach(evt => {
ctrl.addEventListener(evt, e => e.preventDefault());
});
return self;
};
self.addControl = obj => {
// obj.name - string, the name of the control, results for example in "jsPanel-btn jsPanel-btn-menu"
// obj.html - string, the html of the control to add, for example ""
// obj.handler - function(panel, control), the event handler to assign to the new control
// obj.position - number, the position within the controlbar where the control is to be added
if (!obj.html) {
return self;
}
if (!obj.position) {
obj.position = 1;
}
const count = self.controlbar.querySelectorAll('.jsPanel-btn').length;
let control = document.createElement('button');
control.innerHTML = obj.html;
control.className = `jsPanel-btn jsPanel-btn-${obj.name} jsPanel-btn-${options.headerControls.size}`;
control.style.color = self.header.style.color;
if (obj.position > count) {
// new control is the first from right (default ltr text-direction) or the first from left (if option rtl is used)
self.controlbar.append(control);
} else {
self.controlbar.insertBefore(control, self.querySelector(`.jsPanel-controlbar .jsPanel-btn:nth-child(${obj.position})`));
}
const ariaLabel = obj.ariaLabel || obj.name;
if (ariaLabel) {
control.setAttribute('aria-label', ariaLabel);
}
jsPanel.pointerup.forEach(evt => {
control.addEventListener(evt, e => {
e.preventDefault();
if (e.button && e.button > 0) {
return false;
}
obj.handler.call(self, self, control);
});
});
if (obj.afterInsert) {
obj.afterInsert.call(control, control);
}
return self;
};
self.setRtl = () => {
[self.header, self.content, self.footer].forEach(item => {
item.dir = 'rtl';
if (options.rtl.lang) {
item.lang = options.rtl.lang;
}
});
};
// option.id
self.id = options.id;
// option.paneltype classname
self.classList.add('jsPanel-' + options.paneltype);
// set z-index and paneltype class
if (options.paneltype === 'standard') {
self.style.zIndex = this.zi.next();
}
// option.container
panelContainer.append(self);
self.front(false, false); // just to ensure iframe code in self.front() works for very first panel as well, second false prevents onfronted callbacks to be executed
// option.theme
self.setTheme(options.theme);
// option.boxShadow
if (options.boxShadow) {
self.classList.add(`jsPanel-depth-${options.boxShadow}`);
}
/* option.header, option.iconfont, option.headerControls, option.headerLogo, option.headerTitle */
if (options.header) {
if (options.headerLogo) {
self.setHeaderLogo(options.headerLogo);
}
self.setIconfont(options.iconfont);
self.setHeaderTitle(options.headerTitle);
self.setHeaderControls(); // now handles controls added with option addControls as well
// compatibility code for IE11 due to flex bug - https://caniuse.com/#feat=flexbox
if (jsPanel.isIE) {
let bars = [self.headerbar, self.controlbar];
switch (self.options.headerControls.size) {
case 'md':
bars.forEach(bar => {
bar.style.height = '34px';
});
break;
case 'xs':
bars.forEach(bar => {
bar.style.height = '26px';
});
break;
case 'sm':
bars.forEach(bar => {
bar.style.height = '30px';
});
break;
case 'lg':
bars.forEach(bar => {
bar.style.height = '38px';
});
break;
case 'xl':
bars.forEach(bar => {
bar.style.height = '42px';
});
break;
}
}
// end - - - - - - - - -
if (options.header === 'auto-show-hide') {
let boxShadow = 'jsPanel-depth-' + options.boxShadow;
self.header.style.opacity = 0;
self.style.backgroundColor = 'transparent';
this.remClass(self, boxShadow);
this.setClass(self.content, boxShadow);
self.header.addEventListener('mouseenter', () => {
self.header.style.opacity = 1;
jsPanel.setClass(self, boxShadow);
jsPanel.remClass(self.content, boxShadow);
});
self.header.addEventListener('mouseleave', () => {
self.header.style.opacity = 0;
jsPanel.remClass(self, boxShadow);
jsPanel.setClass(self.content, boxShadow);
});
}
} else {
self.setHeaderRemove();
if (options.addCloseControl) {
self.addCloseControl();
}
}
// option.headerToolbar
if (options.headerToolbar) {
self.addToolbar(self.headertoolbar, options.headerToolbar);
}
// option.footerToolbar
if (options.footerToolbar) {
self.addToolbar(self.footer, options.footerToolbar);
}
// option.border
if (options.border) {
self.setBorder(options.border);
}
// option.borderRadius
if (options.borderRadius) {
self.setBorderRadius(options.borderRadius);
}
// option.css - add custom css classes to the panel html
if (options.css) {
for (const [selector, classname] of Object.entries(options.css)) {
// option is a string used to build the desired class selector like `.jsPanel-${option}` except for the outermost DIV where option must be simply 'panel'
// value is a string with either a single class name or a space separated list of class names like 'classA classB classC'
if (selector === 'panel') {
// handles the special case outermost DIV of the panel
self.className += ` ${classname}`; // don't remove space at the beginning of template string
} else {
// handles all other elements within the panel template
let elmt = self.querySelector(`.jsPanel-${selector}`);
if (elmt) {
elmt.className += ` ${classname}`; // don't remove space at the beginning of template string
}
}
}
}
// option.content
if (options.content) {
if (typeof options.content === 'function') {
options.content.call(self, self);
} else if (typeof options.content === 'string') {
self.content.innerHTML = options.content;
} else {
self.content.append(options.content);
}
}
// option.contentAjax
if (options.contentAjax) {
this.ajax(options.contentAjax, self);
}
// option.contentFetch
if (options.contentFetch) {
this.fetch(options.contentFetch, self);
}
// option.contentOverflow
if (options.contentOverflow) {
const value = options.contentOverflow.split(' ');
if (value.length === 1) {
self.content.style.overflow = value[0];
} else if (value.length === 2) {
self.content.style.overflowX = value[0];
self.content.style.overflowY = value[1];
}
}
// option.autoclose -- should be before option.size
if (options.autoclose) {
if (typeof options.autoclose === 'number') {
options.autoclose = { time: options.autoclose + 'ms' };
} else if (typeof options.autoclose === 'string') {
options.autoclose = { time: options.autoclose };
}
let autoclose = Object.assign({}, jsPanel.defaultAutocloseConfig, options.autoclose);
if (autoclose.time && typeof autoclose.time === 'number') {
autoclose.time += 'ms';
}
let slider = self.progressbar.querySelector('div');
slider.addEventListener('animationend', e => {
e.stopPropagation();
self.progressbar.classList.remove('active');
self.close();
});
if (autoclose.progressbar) {
self.progressbar.classList.add('active');
if (autoclose.background) {
if (jsPanel.colorNames[autoclose.background]) {
self.progressbar.style.background = '#' + jsPanel.colorNames[autoclose.background];
} else {
self.progressbar.style.background = autoclose.background;
}
} else {
self.progressbar.classList.add('success-bg'); // default background for progressbar
}
}
slider.style.animation = `${autoclose.time} progressbar`;
}
// option.rtl
if (options.rtl) {
self.setRtl();
}
// option.size -- should be after option.theme
self.setSize();
// option.position
self.status = 'normalized';
// if option.position evaluates to false panel will not be positioned at all
if (options.position) {
this.position(self, options.position);
} else {
self.style.opacity = 1;
}
document.dispatchEvent(jspanelnormalized);
self.calcSizeFactors();
// option.animateIn
if (options.animateIn) {
// remove class again on animationend, otherwise opacity doesn't change when panel is dragged
self.addEventListener('animationend', () => {
this.remClass(self, options.animateIn);
});
this.setClass(self, options.animateIn);
}
// option.dragit AND option.resizeit AND option.syncMargins
if (options.syncMargins) {
let containment = this.pOcontainment(options.maximizedMargin);
if (options.dragit) {
options.dragit.containment = containment;
if (options.dragit.snap === true) {
// options.dragit.snap must be object in order to set options.dragit.snap.containment = true;
options.dragit.snap = jsPanel.defaultSnapConfig;
options.dragit.snap.containment = true;
} else if (options.dragit.snap) {
options.dragit.snap.containment = true;
}
}
if (options.resizeit) {
options.resizeit.containment = containment;
}
}
if (options.dragit) {
// callbacks must be an array of function(s) in order to be able to dynamically add/remove callbacks (for example in extensions)
['start', 'drag', 'stop'].forEach(item => {
if (options.dragit[item]) {
if (typeof options.dragit[item] === 'function') {
options.dragit[item] = [options.dragit[item]];
}
} else {
options.dragit[item] = [];
}
});
self.drag(options.dragit);
// do not use self.options.dragit.stop.push() !!!
self.addEventListener('jspaneldragstop', e => {
if (e.panel === self) {self.calcSizeFactors();}
}, false);
} else {
self.titlebar.style.cursor = 'default';
}
if (options.resizeit) {
// callbacks must be an array of function(s) in order to be able to dynamically add/remove callbacks (for example in extensions)
['start', 'resize', 'stop'].forEach(item => {
if (options.resizeit[item]) {
if (typeof options.resizeit[item] === 'function') {
options.resizeit[item] = [options.resizeit[item]];
}
} else {
options.resizeit[item] = [];
}
});
self.sizeit(options.resizeit);
let startstatus = void 0;
// do not use self.options.resizeit.start.push() !!!
self.addEventListener('jspanelresizestart', e => {
if (e.panel === self) {startstatus = self.status;}
}, false);
// do not use self.options.resizeit.stop.push() !!!
self.addEventListener(
'jspanelresizestop',
e => {
if (e.panel === self) {
if (
(startstatus === 'smallified' || startstatus === 'smallifiedmax' || startstatus === 'maximized') &&
parseFloat(self.style.height) > parseFloat(window.getComputedStyle(self.header).height)
) {
self.setControls(['.jsPanel-btn-normalize']);
self.status = 'normalized';
document.dispatchEvent(jspanelnormalized);
document.dispatchEvent(jspanelstatuschange);
if (options.onstatuschange) {
jsPanel.processCallbacks(self, options.onstatuschange, 'every');
}
self.calcSizeFactors();
}
}
},
false
);
}
// initialize self.currentData - must be after options position & size
//self.saveCurrentDimensions(true); // not clear why 'true' was added here or why this param is needed at all in the method
self.saveCurrentDimensions();
self.saveCurrentPosition();
// option.setStatus
if (options.setStatus) {
if (options.setStatus === 'smallifiedmax') {
self.maximize().smallify();
} else if (options.setStatus === 'smallified') {
self.smallify();
} else {
// remove last char ('d') from end of string to get function name to call
self[options.setStatus.slice(0, -1)]();
}
}
// front panel on mousedown
this.pointerdown.forEach(item => {
self.addEventListener(
item,
e => {
if (!e.target.closest('.jsPanel-btn-close') && !e.target.closest('.jsPanel-btn-minimize') && options.paneltype === 'standard') {
self.front();
}
},
false
);
});
// option onwindowresize
if (options.onwindowresize) {
// if container is 'window'
if (self.options.container === 'window') {
window.addEventListener('resize', self.windowResizeHandler, false);
}
}
// option onparentresize
if (options.onparentresize) {
let onResize = options.onparentresize,
settingType = typeof onResize,
parentPanel = self.isChildpanel();
if (parentPanel) {
const parentContainer = parentPanel.content;
let parentContainerSize = [];
self.parentResizeHandler = e => {
// if resized panel is the parent panel of the one whose option onContentResize is set to true
if (e.panel === parentPanel) {
// get dimensions of parent panel's content section
parentContainerSize[0] = parentContainer.offsetWidth;
parentContainerSize[1] = parentContainer.offsetHeight;
let status = self.status,
left,
top;
if (status === 'maximized' && onResize) {
self.maximize();
} else if (self.snapped && status !== 'minimized') {
self.snap(self.snapped, true);
} else if (status === 'normalized' || status === 'smallified' || status === 'maximized') {
if (settingType === 'function') {
onResize.call(self, self, {
width: parentContainerSize[0],
height: parentContainerSize[1],
});
} else if (settingType === 'object' && onResize.preset === true) {
left = (parentContainerSize[0] - self.offsetWidth) * self.hf;
self.style.left = left <= 0 ? 0 : left + 'px';
top = (parentContainerSize[1] - self.offsetHeight) * self.vf;
self.style.top = top <= 0 ? 0 : top + 'px';
onResize.callback.call(self, self, {
width: parentContainerSize[0],
height: parentContainerSize[1],
});
} else if (settingType === 'boolean') {
left = (parentContainerSize[0] - self.offsetWidth) * self.hf;
self.style.left = left <= 0 ? 0 : left + 'px';
top = (parentContainerSize[1] - self.offsetHeight) * self.vf;
self.style.top = top <= 0 ? 0 : top + 'px';
}
} else if (status === 'smallifiedmax' && onResize) {
self.maximize().smallify();
}
}
};
document.addEventListener('jspanelresize', self.parentResizeHandler, false);
}
}
// global callbacks
if (this.globalCallbacks) {
if (Array.isArray(this.globalCallbacks)) {
this.globalCallbacks.forEach(item => item.call(self, self));
} else {
this.globalCallbacks.call(self, self);
}
}
// option.callback
if (options.callback) {
if (Array.isArray(options.callback)) {
options.callback.forEach(item => item.call(self, self));
} else {
options.callback.call(self, self);
}
}
// constructor callback
// if (cb) {cb.call(self, self);}
if (cb) {
if (Array.isArray(cb)) {
cb.forEach(item => item.call(self, self));
} else {
cb.call(self, self);
}
}
document.dispatchEvent(jspanelloaded);
return self;
},
};