Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
package.js.picker.Picker.js Maven / Gradle / Ivy
import {hasProperty, lastItemOf, isInRange, limitToRange} from '../lib/utils.js';
import {today} from '../lib/date.js';
import {parseHTML, showElement, hideElement, emptyChildNodes} from '../lib/dom.js';
import {registerListeners} from '../lib/event.js';
import pickerTemplate from './templates/pickerTemplate.js';
import DaysView from './views/DaysView.js';
import MonthsView from './views/MonthsView.js';
import YearsView from './views/YearsView.js';
import {triggerDatepickerEvent} from '../events/functions.js';
import {
onClickTodayBtn,
onClickClearBtn,
onClickViewSwitch,
onClickPrevBtn,
onClickNextBtn,
onClickView,
onClickPicker,
} from '../events/pickerListeners.js';
function processPickerOptions(picker, options) {
if (options.title !== undefined) {
if (options.title) {
picker.controls.title.textContent = options.title;
showElement(picker.controls.title);
} else {
picker.controls.title.textContent = '';
hideElement(picker.controls.title);
}
}
if (options.prevArrow) {
const prevBtn = picker.controls.prevBtn;
emptyChildNodes(prevBtn);
options.prevArrow.forEach((node) => {
prevBtn.appendChild(node.cloneNode(true));
});
}
if (options.nextArrow) {
const nextBtn = picker.controls.nextBtn;
emptyChildNodes(nextBtn);
options.nextArrow.forEach((node) => {
nextBtn.appendChild(node.cloneNode(true));
});
}
if (options.locale) {
picker.controls.todayBtn.textContent = options.locale.today;
picker.controls.clearBtn.textContent = options.locale.clear;
}
if (options.todayBtn !== undefined) {
if (options.todayBtn) {
showElement(picker.controls.todayBtn);
} else {
hideElement(picker.controls.todayBtn);
}
}
if (hasProperty(options, 'minDate') || hasProperty(options, 'maxDate')) {
const {minDate, maxDate} = picker.datepicker.config;
picker.controls.todayBtn.disabled = !isInRange(today(), minDate, maxDate);
}
if (options.clearBtn !== undefined) {
if (options.clearBtn) {
showElement(picker.controls.clearBtn);
} else {
hideElement(picker.controls.clearBtn);
}
}
}
// Compute view date to reset, which will be...
// - the last item of the selected dates or defaultViewDate if no selection
// - limitted to minDate or maxDate if it exceeds the range
function computeResetViewDate(datepicker) {
const {dates, config} = datepicker;
const viewDate = dates.length > 0 ? lastItemOf(dates) : config.defaultViewDate;
return limitToRange(viewDate, config.minDate, config.maxDate);
}
// Change current view's view date
function setViewDate(picker, newDate) {
const oldViewDate = new Date(picker.viewDate);
const newViewDate = new Date(newDate);
const {id, year, first, last} = picker.currentView;
const viewYear = newViewDate.getFullYear();
picker.viewDate = newDate;
if (viewYear !== oldViewDate.getFullYear()) {
triggerDatepickerEvent(picker.datepicker, 'changeYear');
}
if (newViewDate.getMonth() !== oldViewDate.getMonth()) {
triggerDatepickerEvent(picker.datepicker, 'changeMonth');
}
// return whether the new date is in different period on time from the one
// displayed in the current view
// when true, the view needs to be re-rendered on the next UI refresh.
switch (id) {
case 0:
return newDate < first || newDate > last;
case 1:
return viewYear !== year;
default:
return viewYear < first || viewYear > last;
}
}
function getTextDirection(el) {
return window.getComputedStyle(el).direction;
}
// Class representing the picker UI
export default class Picker {
constructor(datepicker) {
this.datepicker = datepicker;
const template = pickerTemplate.replace(/%buttonClass%/g, datepicker.config.buttonClass);
const element = this.element = parseHTML(template).firstChild;
const [header, main, footer] = element.firstChild.children;
const title = header.firstElementChild;
const [prevBtn, viewSwitch, nextBtn] = header.lastElementChild.children;
const [todayBtn, clearBtn] = footer.firstChild.children;
const controls = {
title,
prevBtn,
viewSwitch,
nextBtn,
todayBtn,
clearBtn,
};
this.main = main;
this.controls = controls;
const elementClass = datepicker.inline ? 'inline' : 'dropdown';
element.classList.add(`datepicker-${elementClass}`);
elementClass === 'dropdown' ? element.classList.add('dropdown', 'absolute', 'top-0', 'left-0', 'z-50', 'pt-2') : null;
processPickerOptions(this, datepicker.config);
this.viewDate = computeResetViewDate(datepicker);
// set up event listeners
registerListeners(datepicker, [
[element, 'click', onClickPicker.bind(null, datepicker), {capture: true}],
[main, 'click', onClickView.bind(null, datepicker)],
[controls.viewSwitch, 'click', onClickViewSwitch.bind(null, datepicker)],
[controls.prevBtn, 'click', onClickPrevBtn.bind(null, datepicker)],
[controls.nextBtn, 'click', onClickNextBtn.bind(null, datepicker)],
[controls.todayBtn, 'click', onClickTodayBtn.bind(null, datepicker)],
[controls.clearBtn, 'click', onClickClearBtn.bind(null, datepicker)],
]);
// set up views
this.views = [
new DaysView(this),
new MonthsView(this),
new YearsView(this, {id: 2, name: 'years', cellClass: 'year', step: 1}),
new YearsView(this, {id: 3, name: 'decades', cellClass: 'decade', step: 10}),
];
this.currentView = this.views[datepicker.config.startView];
this.currentView.render();
this.main.appendChild(this.currentView.element);
datepicker.config.container.appendChild(this.element);
}
setOptions(options) {
processPickerOptions(this, options);
this.views.forEach((view) => {
view.init(options, false);
});
this.currentView.render();
}
detach() {
this.datepicker.config.container.removeChild(this.element);
}
show() {
if (this.active) {
return;
}
this.element.classList.add('active', 'block');
this.element.classList.remove('hidden');
this.active = true;
const datepicker = this.datepicker;
if (!datepicker.inline) {
// ensure picker's direction matches input's
const inputDirection = getTextDirection(datepicker.inputField);
if (inputDirection !== getTextDirection(datepicker.config.container)) {
this.element.dir = inputDirection;
} else if (this.element.dir) {
this.element.removeAttribute('dir');
}
this.place();
if (datepicker.config.disableTouchKeyboard) {
datepicker.inputField.blur();
}
}
triggerDatepickerEvent(datepicker, 'show');
}
hide() {
if (!this.active) {
return;
}
this.datepicker.exitEditMode();
this.element.classList.remove('active', 'block');
this.element.classList.add('active', 'block', 'hidden');
this.active = false;
triggerDatepickerEvent(this.datepicker, 'hide');
}
place() {
const {classList, style} = this.element;
const {config, inputField} = this.datepicker;
const container = config.container;
const {
width: calendarWidth,
height: calendarHeight,
} = this.element.getBoundingClientRect();
const {
left: containerLeft,
top: containerTop,
width: containerWidth,
} = container.getBoundingClientRect();
const {
left: inputLeft,
top: inputTop,
width: inputWidth,
height: inputHeight
} = inputField.getBoundingClientRect();
let {x: orientX, y: orientY} = config.orientation;
let scrollTop;
let left;
let top;
if (container === document.body) {
scrollTop = window.scrollY;
left = inputLeft + window.scrollX;
top = inputTop + scrollTop;
} else {
scrollTop = container.scrollTop;
left = inputLeft - containerLeft;
top = inputTop - containerTop + scrollTop;
}
if (orientX === 'auto') {
if (left < 0) {
// align to the left and move into visible area if input's left edge < window's
orientX = 'left';
left = 10;
} else if (left + calendarWidth > containerWidth) {
// align to the right if canlendar's right edge > container's
orientX = 'right';
} else {
orientX = getTextDirection(inputField) === 'rtl' ? 'right' : 'left';
}
}
if (orientX === 'right') {
left -= calendarWidth - inputWidth;
}
if (orientY === 'auto') {
orientY = top - calendarHeight < scrollTop ? 'bottom' : 'top';
}
if (orientY === 'top') {
top -= calendarHeight;
} else {
top += inputHeight;
}
classList.remove(
'datepicker-orient-top',
'datepicker-orient-bottom',
'datepicker-orient-right',
'datepicker-orient-left'
);
classList.add(`datepicker-orient-${orientY}`, `datepicker-orient-${orientX}`);
style.top = top ? `${top}px` : top;
style.left = left ? `${left}px` : left;
}
setViewSwitchLabel(labelText) {
this.controls.viewSwitch.textContent = labelText;
}
setPrevBtnDisabled(disabled) {
this.controls.prevBtn.disabled = disabled;
}
setNextBtnDisabled(disabled) {
this.controls.nextBtn.disabled = disabled;
}
changeView(viewId) {
const oldView = this.currentView;
const newView = this.views[viewId];
if (newView.id !== oldView.id) {
this.currentView = newView;
this._renderMethod = 'render';
triggerDatepickerEvent(this.datepicker, 'changeView');
this.main.replaceChild(newView.element, oldView.element);
}
return this;
}
// Change the focused date (view date)
changeFocus(newViewDate) {
this._renderMethod = setViewDate(this, newViewDate) ? 'render' : 'refreshFocus';
this.views.forEach((view) => {
view.updateFocus();
});
return this;
}
// Apply the change of the selected dates
update() {
const newViewDate = computeResetViewDate(this.datepicker);
this._renderMethod = setViewDate(this, newViewDate) ? 'render' : 'refresh';
this.views.forEach((view) => {
view.updateFocus();
view.updateSelection();
});
return this;
}
// Refresh the picker UI
render(quickRender = true) {
const renderMethod = (quickRender && this._renderMethod) || 'render';
delete this._renderMethod;
this.currentView[renderMethod]();
}
}