UNPKG

jspanel4

Version:

A JavaScript library to create highly configurable multifunctional floating panels that can also be used as modal, tooltip, hint or contextmenu

514 lines (495 loc) 25.6 kB
/** * jsPanel - A JavaScript library to create highly configurable multifunctional floating panels that can also be used as modal, tooltip, hint or contextmenu * @version v4.16.1 * @homepage https://jspanel.de/ * @license MIT * @author Stefan Sträßer - info@jspanel.de * @author of dialog extension: Michael Daumling - michael@terrapinlogo.com * @github https://github.com/Flyer53/jsPanel4.git */ 'use strict'; function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } /** * requires moment.js < https://momentjs.com/ > to be loaded prior this extension */ // TODO: - cancelable events dateselect, rangeselect, selectionclear, etc. ?? // - alternative way to select a range, e.g start by Alt+Click end end by another Alt-Click ?? // - make dates not selectable and mark them accordingly ?? // - load list of days to highlight somehow (e.g. holidays) if (!jsPanel.datepicker) { // add some icons for the datepicker controls jsPanel.icons.chevronLeft = "<svg focusable=\"false\" class=\"jsPanel-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 22 22\"><g transform=\"matrix(6.12323e-17,-1,1,6.12323e-17,0.0375,22.0375)\"><path fill=\"currentColor\" d=\"M2.1,15.2L2.9,16C3.1,16.2 3.4,16.2 3.6,16L11,8.7L18.4,16C18.6,16.2 18.9,16.2 19.1,16L19.9,15.2C20.1,15 20.1,14.7 19.9,14.5L11.3,6C11.1,5.8 10.8,5.8 10.6,6L2.1,14.5C2,14.7 2,15 2.1,15.2Z\"/></g></svg>"; jsPanel.icons.chevronRight = "<svg focusable=\"false\" class=\"jsPanel-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 22 22\"><g transform=\"matrix(6.12323e-17,1,-1,6.12323e-17,22.0375,-0.0375)\"><path fill=\"currentColor\" d=\"M2.1,15.2L2.9,16C3.1,16.2 3.4,16.2 3.6,16L11,8.7L18.4,16C18.6,16.2 18.9,16.2 19.1,16L19.9,15.2C20.1,15 20.1,14.7 19.9,14.5L11.3,6C11.1,5.8 10.8,5.8 10.6,6L2.1,14.5C2,14.7 2,15 2.1,15.2Z\"/></g></svg>"; jsPanel.icons.square = "<svg focusable=\"false\" class=\"jsPanel-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 22 22\"><g transform=\"matrix(0.0401786,0,0,0.0401786,2,0.714286)\"><path fill=\"currentColor\" d=\"M400,32L48,32C21.5,32 0,53.5 0,80L0,432C0,458.5 21.5,480 48,480L400,480C426.5,480 448,458.5 448,432L448,80C448,53.5 426.5,32 400,32ZM394,432L54,432C50.7,432 48,429.3 48,426L48,86C48,82.7 50.7,80 54,80L394,80C397.3,80 400,82.7 400,86L400,426C400,429.3 397.3,432 394,432Z\"/></g></svg>"; jsPanel.icons.undo = "<svg focusable=\"false\" class=\"jsPanel-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 22 22\"><g transform=\"matrix(2.18687e-18,0.0357143,-0.0357143,2.18687e-18,20.1429,2)\"><path fill=\"currentColor\" d=\"M12,8L39.711,8C46.45,8 51.868,13.548 51.708,20.286L49.361,118.854C93.925,51.834 170.212,7.73 256.793,8.001C393.18,8.428 504.213,120.009 504,256.396C503.786,393.181 392.835,504 256,504C192.074,504 133.798,479.813 89.822,440.092C84.709,435.474 84.468,427.531 89.34,422.659L109.078,402.921C113.576,398.423 120.831,398.136 125.579,402.369C160.213,433.246 205.895,452 256,452C364.322,452 452,364.338 452,256C452,147.678 364.338,60 256,60C176.455,60 108.059,107.282 77.325,175.302L203.714,172.293C210.451,172.133 216,177.55 216,184.29L216,212C216,218.627 210.627,224 204,224L12,224C5.373,224 0,218.627 0,212L0,20C0,13.373 5.373,8 12,8Z\"/></g></svg>"; jsPanel.datepicker = { version: '0.3.2', date: '2020-06-19 09:48', defaults: { locale: 'en', startdate: undefined, months: 1, showWeekNumbers: true, ondateselect: undefined, onrangeselect: undefined, onselectionclear: undefined, callback: undefined }, keyValue: undefined, generateHTML: function generateHTML() { var wrapper = document.createElement('div'); wrapper.className = 'jsPanel-cal-wrapper'; wrapper.innerHTML = "<div class=\"jsPanel-cal-sub jsPanel-cal-clear\" title=\"Clear all selections\">".concat(jsPanel.icons.square, "</div>\n <div class=\"jsPanel-cal-sub jsPanel-cal-back\" title=\"Go back one month\">").concat(jsPanel.icons.chevronLeft, "</div>\n <div class=\"jsPanel-cal-sub jsPanel-cal-month\"></div>\n <div class=\"jsPanel-cal-sub jsPanel-cal-forward\" title=\"Go forward one month\">").concat(jsPanel.icons.chevronRight, "</div>\n <div class=\"jsPanel-cal-sub jsPanel-cal-reset\" title=\"Reset to current month\">").concat(jsPanel.icons.undo, "</div>\n <div class=\"jsPanel-cal-sub jsPanel-cal-blank3\"></div>\n <div class=\"jsPanel-cal-sub day-name day-name-0\"></div>\n <div class=\"jsPanel-cal-sub day-name day-name-1\"></div>\n <div class=\"jsPanel-cal-sub day-name day-name-2\"></div>\n <div class=\"jsPanel-cal-sub day-name day-name-3\"></div>\n <div class=\"jsPanel-cal-sub day-name day-name-4\"></div>\n <div class=\"jsPanel-cal-sub day-name day-name-5\"></div>\n <div class=\"jsPanel-cal-sub day-name day-name-6\"></div>"); for (var i = 0; i < 6; i++) { wrapper.innerHTML += "<div class=\"jsPanel-cal-sub week week-".concat(i, "\"></div>"); } for (var _i = 1; _i < 43; _i++) { wrapper.innerHTML += "<div class=\"jsPanel-cal-sub day day-".concat(_i, "\"></div>"); // ${i} is just a counter of days listed in the calendar, not a date value! } return wrapper; }, // method to fill a month with data fillMonth: function fillMonth(datepicker) { var startdate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : moment(); moment.locale(datepicker.options.locale); // set locale globally var now = moment(startdate) || moment(datepicker.options.startdate); now.locale(datepicker.options.locale); var month = now.month(), // returns number 0 to 11 where 0 is January firstDay = now.date(1).weekday(), // returns locale aware number 0 to 6 where 0 is either Sunday or Monday localeData = now.localeData(); // fill selected month incl. year var monthBox = datepicker.querySelector('.jsPanel-cal-month'); monthBox.innerHTML = now.format('MMMM YYYY'); monthBox.dataset.date = now.format('YYYY-MM-DD'); // fill day names (Mo, Tu, etc.) considering used locale var dayNames = datepicker.querySelectorAll('.jsPanel-cal-sub.day-name'), weekdays = localeData.weekdaysMin(); if (localeData.firstDayOfWeek() === 1) { // week starts with Monday for (var i = 0, j = 1; i < 7; i++, j++) { dayNames[i].textContent = weekdays[j]; } dayNames[6].textContent = weekdays[0]; dayNames[5].classList.add('weekend'); dayNames[6].classList.add('weekend'); } else { for (var _i2 = 0; _i2 < 7; _i2++) { dayNames[_i2].textContent = weekdays[_i2]; } dayNames[0].classList.add('weekend'); dayNames[6].classList.add('weekend'); } // fill dates var firstEntry = now.subtract(++firstDay, 'days'); var days = datepicker.querySelectorAll('.jsPanel-cal-sub.day'); var _iterator = _createForOfIteratorHelper(days), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var day = _step.value; day.classList.remove('today', 'notInMonth', 'selected', 'range', 'remove-border-radius-right', 'remove-border-radius-left'); var value = firstEntry.add(1, 'days'); day.textContent = value.format('D'); day.dataset.date = now.format('YYYY-MM-DD'); if (value.month() !== month) { day.classList.add('notInMonth'); } else if (day.dataset.date === moment().format('YYYY-MM-DD')) { day.classList.add('today'); } } // fill week numbers } catch (err) { _iterator.e(err); } finally { _iterator.f(); } if (datepicker.options.showWeekNumbers) { datepicker.querySelectorAll('.jsPanel-cal-sub.week').forEach(function (week, index) { week.textContent = moment(datepicker.querySelector(".jsPanel-cal-sub.day-".concat((index + 1) * 7)).dataset.date).week(); }); } }, // deselect all days (remove .selected class; does not empty selectedDays/selectedRange) // do not empty selectedDays/selectedRange -> selection would be lost when clicking forward/back buttons deselectAllDays: function deselectAllDays(container) { var _iterator2 = _createForOfIteratorHelper(container.querySelectorAll('.jsPanel-cal-sub.day')), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var day = _step2.value; day.classList.remove('selected', 'range', 'remove-border-radius-right', 'remove-border-radius-left'); } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } }, // method to restore selected dates or range restoreSelections: function restoreSelections(container) { // restore selections of days var days = container.querySelectorAll('.jsPanel-cal-sub.day'); var _iterator3 = _createForOfIteratorHelper(days), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var _day = _step3.value; if (container.selectedDays.has(_day.dataset.date)) { _day.classList.add('selected'); } } // restore selection of a range } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } if (container.selectedRange.size) { var rangeIterator = container.selectedRange.values(); var rangeArray = rangeIterator.next().value.split('/'); var _iterator4 = _createForOfIteratorHelper(days), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var day = _step4.value; var date = day.dataset.date; if (date >= rangeArray[0] && date <= rangeArray[1]) { day.classList.add('selected', 'range'); // remove border radius of dates between start and end if (date === rangeArray[0]) { day.classList.add('remove-border-radius-right'); } else if (date === rangeArray[1]) { day.classList.add('remove-border-radius-left'); } else if (date > rangeArray[0] || date < rangeArray[1]) { day.classList.add('remove-border-radius-right', 'remove-border-radius-left'); } } } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } } }, create: function create(container) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; container.style.display = 'flex'; container.selectedDays = new Set(); container.selectedRange = new Set(); var opts = Object.assign({}, this.defaults, options); var wrapper; // fill container with monthly calendars according to option.months for (var i = 0; i < opts.months; i++) { wrapper = this.generateHTML(); wrapper.options = opts; container.append(wrapper); // fill month with data this.fillMonth(wrapper, opts.startdate); // increase startdate 1 month for next calendar opts.startdate = moment(opts.startdate).add(1, 'months'); } // add handlers for back, forward etc. buttons var pickers = container.querySelectorAll('.jsPanel-cal-wrapper'); var _iterator5 = _createForOfIteratorHelper(pickers), _step5; try { for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) { var picker = _step5.value; // clear buttons var _iterator8 = _createForOfIteratorHelper(picker.querySelectorAll('.jsPanel-cal-clear')), _step8; try { for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) { var clearbtn = _step8.value; clearbtn.addEventListener('click', function (e) { if (opts.onselectionclear && typeof opts.onselectionclear === 'function') { opts.onselectionclear.call(container, container, e); } if (!e.defaultPrevented) { jsPanel.datepicker.deselectAllDays(container); container.selectedDays.clear(); container.selectedRange.clear(); } }); } // back buttons } catch (err) { _iterator8.e(err); } finally { _iterator8.f(); } var _iterator9 = _createForOfIteratorHelper(picker.querySelectorAll('.jsPanel-cal-back')), _step9; try { for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) { var backbtn = _step9.value; backbtn.addEventListener('click', function () { // get all wrappers and decrease their date var _iterator12 = _createForOfIteratorHelper(pickers), _step12; try { for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) { var _picker = _step12.value; var monthshown = _picker.querySelector('.jsPanel-cal-month').dataset.date, // string like '2020-02-12' monthwanted = moment(monthshown).subtract(1, 'months').format('YYYY-MM'); jsPanel.datepicker.fillMonth(_picker, monthwanted); } } catch (err) { _iterator12.e(err); } finally { _iterator12.f(); } jsPanel.datepicker.deselectAllDays(container); jsPanel.datepicker.restoreSelections(container); }); } // forward buttons } catch (err) { _iterator9.e(err); } finally { _iterator9.f(); } var _iterator10 = _createForOfIteratorHelper(picker.querySelectorAll('.jsPanel-cal-forward')), _step10; try { for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) { var fwdbtn = _step10.value; fwdbtn.addEventListener('click', function () { // get all wrappers and increase their date var _iterator13 = _createForOfIteratorHelper(pickers), _step13; try { for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) { var _picker2 = _step13.value; var monthshown = _picker2.querySelector('.jsPanel-cal-month').dataset.date, // string like '2020-02-12' monthwanted = moment(monthshown).add(1, 'months').format('YYYY-MM'); jsPanel.datepicker.fillMonth(_picker2, monthwanted); } } catch (err) { _iterator13.e(err); } finally { _iterator13.f(); } jsPanel.datepicker.deselectAllDays(container); jsPanel.datepicker.restoreSelections(container); }); } // reset buttons } catch (err) { _iterator10.e(err); } finally { _iterator10.f(); } var _iterator11 = _createForOfIteratorHelper(picker.querySelectorAll('.jsPanel-cal-reset')), _step11; try { for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) { var resetbtn = _step11.value; resetbtn.addEventListener('click', function (e) { // get month shown of clicked picker var picker = e.target.closest('.jsPanel-cal-wrapper'), counter = 0; while (picker.previousSibling) { counter++; picker = picker.previousSibling; } // counter is now the zero-based position of the clicked picker in the container // get month for first picker in sequence var month = moment().subtract(counter, 'months'); // reset each pickers month var _iterator14 = _createForOfIteratorHelper(pickers), _step14; try { for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) { var _picker3 = _step14.value; jsPanel.datepicker.fillMonth(_picker3, month); month = moment(month).add(1, 'months'); } } catch (err) { _iterator14.e(err); } finally { _iterator14.f(); } jsPanel.datepicker.deselectAllDays(container); jsPanel.datepicker.restoreSelections(container); }); } } catch (err) { _iterator11.e(err); } finally { _iterator11.f(); } } /** * CLICK ON A DAY * MEANS SELECTION/DESELECTION OF SINGLE OR MULTIPLE DAYS; NO RANGES */ } catch (err) { _iterator5.e(err); } finally { _iterator5.f(); } container.addEventListener('click', function (e) { e.preventDefault(); var target = e.target, altKey = e.altKey, ctrlKey = e.ctrlKey, shiftKey = e.shiftKey; if (target.classList.contains('day')) { // check whether day is already selected var selected = target.classList.contains('selected'); var date = target.dataset.date; /** * IF NO MODIFIER KEY IS PRESSED */ if (!ctrlKey && !shiftKey && !altKey) { // unselect all selected days and clear container.selectedDays jsPanel.datepicker.deselectAllDays(container); container.selectedDays.clear(); // select/unselect day depending on let selected if (selected) { target.classList.remove('selected'); } else { target.classList.add('selected'); // add selected day to storage container.selectedDays.add(date); } } else if (!altKey && ctrlKey && !shiftKey) { /** * IF CTRL KEY IS PRESSED */ container.selectedRange.clear(); var _iterator6 = _createForOfIteratorHelper(container.querySelectorAll('.day')), _step6; try { for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) { var day = _step6.value; if (day.classList.contains('selected') && day.classList.contains('range')) { day.classList.remove('range', 'selected'); } } // select/unselect day depending on let selected } catch (err) { _iterator6.e(err); } finally { _iterator6.f(); } if (selected) { target.classList.remove('selected'); // remove selected day from storage container.selectedDays["delete"](date); } else { target.classList.add('selected'); // add selected day to storage container.selectedDays.add(date); } } // custom callback if (opts.ondateselect && typeof opts.ondateselect === 'function') { opts.ondateselect.call(container, container, date, e); } } }); /** * POINTERDOWN HANDLER TO STARTING A RANGE SELECTION */ var rangeSelectionStarted; container.addEventListener('pointerdown', function (e) { e.preventDefault(); var target = e.target, altKey = e.altKey, ctrlKey = e.ctrlKey, shiftKey = e.shiftKey; var start = e.target.dataset.date, current = e.target.dataset.date, range = [start, start]; var calcRange = function calcRange(e) { e.preventDefault(); rangeSelectionStarted = true; if (container.selectedDays.size) { container.selectedDays.clear(); } // build range array and sort it if (e.target.classList.contains('day')) { current = e.target.dataset.date; range = [start, current].sort(function (a, b) { return moment(a).unix() - moment(b).unix(); // convert values to number for comparison }); } // add needed classes to selected range var _iterator7 = _createForOfIteratorHelper(container.querySelectorAll('.day')), _step7; try { for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) { var day = _step7.value; var date = day.dataset.date; day.classList.remove('remove-border-radius-right', 'remove-border-radius-left'); if (date < range[0] || date > range[1]) { day.classList.remove('selected', 'range'); } else { day.classList.add('selected', 'range'); // remove border radius of dates between start and end if (date === range[0]) { day.classList.add('remove-border-radius-right'); } else if (date === range[1]) { day.classList.add('remove-border-radius-left'); } else if (date > range[0] || date < range[1]) { day.classList.add('remove-border-radius-right', 'remove-border-radius-left'); } } } // build range string for selectedRange } catch (err) { _iterator7.e(err); } finally { _iterator7.f(); } container.selectedRange.clear(); container.selectedRange.add(container.querySelector(".day[data-date=\"".concat(range[0], "\"]")).dataset.date + '/' + container.querySelector(".day[data-date=\"".concat(range[1], "\"]")).dataset.date); }; // if pointerdown is on a day and Shift key is pressed if (target.classList.contains('day') && !altKey && !ctrlKey && shiftKey) { container.addEventListener('pointermove', calcRange); container.addEventListener('pointerup', function () { container.removeEventListener('pointermove', calcRange); }); } }); container.addEventListener('pointerup', function (e) { if (rangeSelectionStarted) { if (opts.onrangeselect && typeof opts.onrangeselect === 'function') { opts.onrangeselect.call(container, container, container.selectedRange, e); rangeSelectionStarted = undefined; } } rangeSelectionStarted = undefined; }); if (opts.callback) { opts.callback.call(container, container); } return container; } }; // jsPanel.datepicker.keyValue is set to the value of the pressed number key while key is down and if key's value is between 1 and 9 document.addEventListener('keydown', function (e) { if (e.key.match(/^[2-9]$/)) { jsPanel.datepicker.keyValue = e.key; } else if (e.key.match(/^1$/)) { jsPanel.datepicker.keyValue = 12; } }); document.addEventListener('keyup', function () { jsPanel.datepicker.keyValue = undefined; }); } // Add CommonJS module exports, so it can be imported using require() in Node.js // https://nodejs.org/docs/latest/api/modules.html if (typeof module !== 'undefined') { module.exports = jsPanel; }