@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
1,177 lines (1,032 loc) • 31.3 kB
JavaScript
/**
* Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
* Node module: @schukai/monster
*
* This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
* The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
*
* For those who do not wish to adhere to the AGPLv3, a commercial license is available.
* Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
* For more information about purchasing a commercial license, please contact Volker Schukai.
*
* SPDX-License-Identifier: AGPL-3.0
*/
import { instanceSymbol } from "../../../constants.mjs";
import {
assembleMethodSymbol,
registerCustomElement,
} from "../../../dom/customelement.mjs";
import { FilterDateRangeStyleSheet } from "../stylesheet/filter-date-range.mjs";
import { FilterControlsDefaultsStyleSheet } from "../stylesheet/filter-controls-defaults.mjs";
import { AbstractBase } from "./abstract-base.mjs";
import { positionPopper } from "../../form/util/floating-ui.mjs";
import { ATTRIBUTE_ROLE } from "../../../dom/constants.mjs";
import { getDocument } from "../../../dom/util.mjs";
import { STYLE_DISPLAY_MODE_BLOCK } from "../constants.mjs";
import { DeadMansSwitch } from "../../../util/deadmansswitch.mjs";
import {
addAttributeToken,
removeAttributeToken,
} from "../../../dom/attributes.mjs";
import { findTargetElementFromEvent } from "../../../dom/events.mjs";
import { getLocaleOfDocument } from "../../../dom/locale.mjs";
export { DateRange };
/**
* @private
* @type {symbol}
*/
const timerCallbackSymbol = Symbol("timerCallback");
// /**
// * @private
// * @type {symbol}
// */
// const internalStateSymbol = Symbol("internalState");
/**
* @private
* @type {symbol}
*/
const radioInputsElementSymbol = Symbol("radioInputsElement");
/**
* @private
* @type {symbol}
*/
const inputInputsElementSymbol = Symbol("inputInputsElement");
/**
* @private
* @type {symbol}
*/
const selectInputsElementSymbol = Symbol("selectInputsElement");
/**
* local symbol
* @private
* @type {symbol}
*/
const resizeObserverSymbol = Symbol("resizeObserver");
/**
* @private
* @type {symbol}
*/
const controlElementSymbol = Symbol("controlElement");
/**
* local symbol
* @private
* @type {symbol}
*/
const closeEventHandler = Symbol("closeEventHandler");
/**
* @private
* @type {symbol}
*/
const inputElementSymbol = Symbol("inputElement");
/**
* @private
* @type {symbol}
*/
const formContainerElementSymbol = Symbol("formContainerElement");
/**
* local symbol
* @private
* @type {symbol}
*/
const popperElementSymbol = Symbol("popperElement");
/**
* local symbol
* @private
* @type {symbol}
*/
const arrowElementSymbol = Symbol("arrowElement");
/**
* The date-range filter control is used to filter a data set by a date range.
*
* <img src="./images/data-range.png">
*
* Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library
*
* You can create this control either by specifying the HTML tag <monster-filter-date-range />` directly in the HTML or using
* Javascript via the `document.createElement('monster-filter-date-range');` method.
*
* ```html
* <monster-filter-date-range></monster-filter-date-range>
* ```
*
* Or you can create this CustomControl directly in Javascript:
*
* ```js
* import '@schukai/component-datatable/source/filter/date-range.mjs';
* document.createElement('monster-filter-date-range');
* ```
*
* @startuml data-range.png
* skinparam monochrome true
* skinparam shadowing false
* HTMLElement <|-- CustomElement
* CustomElement <|-- AbstractBase
* AbstractBase <|-- DateRange
* @enduml
*
* @copyright Volker Schukai
* @summary A date range filter control
*/
class DateRange extends AbstractBase {
// constructor() {
// super();
// this[internalStateSymbol] = {}
// }
/**
* This method is called by the `instanceof` operator.
* @return {symbol}
*/
static get [instanceSymbol]() {
return Symbol.for(
"@schukai/monster/components/filter/date-range@@instance",
);
}
/**
*
* @return {FilterButton}
*/
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
initControlReferences.call(this);
initEventHandler.call(this);
}
/**
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @param {*} value
*/
set value(value) {
this[inputElementSymbol].value = value;
}
/**
* @return {*}
*/
get value() {
return this[inputElementSymbol].value;
}
/**
* To set the options via the HTML tag, the attribute `data-monster-options` must be used.
* @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
*
* The individual configuration values can be found in the table.
*
* @return {Object}
*
* @property {Object} templates
* @property {string} templates.main
* @property {Object} labels
* @property {Object} features
* @property {boolean} features.moreThan
* @property {boolean} features.within
* @property {boolean} features.today
*/
get defaults() {
const d = Object.assign({}, super.defaults, {
templates: {
main: getTemplate(),
},
labels: getTranslations(),
features: {
moreThan: true,
within: true,
today: true,
},
popper: {
placement: "bottom",
middleware: ["flip", "offset:1"],
},
});
return initOptionsFromArguments.call(this, d);
}
/**
*
* @return {CSSStyleSheet[]}
*/
static getCSSStyleSheet() {
return [FilterControlsDefaultsStyleSheet, FilterDateRangeStyleSheet];
}
/**
*
* @return {string}
*/
static getTag() {
return "monster-filter-date-range";
}
/**
* @return {void}
*/
connectedCallback() {
super.connectedCallback();
const document = getDocument();
for (const [, type] of Object.entries(["click", "touch"])) {
// close on outside ui-events
document.addEventListener(type, this[closeEventHandler]);
}
updatePopper.call(this);
attachResizeObserver.call(this);
}
/**
* @return {void}
*/
disconnectedCallback() {
super.disconnectedCallback();
// close on outside ui-events
for (const [, type] of Object.entries(["click", "touch"])) {
document.removeEventListener(type, this[closeEventHandler]);
}
disconnectResizeObserver.call(this);
}
/**
*
* @return {Monster.Datatable.Filter.Range}
*/
showDialog() {
show.call(this);
return this;
}
/**
*
* @return {Monster.Datatable.Filter.Range}
*/
hideDialog() {
hide.call(this);
return this;
}
/**
*
* @return {Monster.Datatable.Filter.Range}
*/
toggleDialog() {
if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
this.hideDialog();
} else {
this.showDialog();
}
return this;
}
}
function getTranslations() {
const locale = getLocaleOfDocument();
switch (locale.language) {
case "de":
return {
singleValue: "Wert",
fromValue: "Von",
toValue: "Bis",
rangeFrom: "Von",
rangeTo: "Bis",
todayValue: "Heute",
withinValue: "Innerhalb",
months: "Monate",
days: "Tage",
years: "Jahre",
weeks: "Wochen",
moreThanValue: "Mehr als",
};
case "fr":
return {
singleValue: "Valeur",
fromValue: "De",
toValue: "À",
rangeFrom: "De",
rangeTo: "À",
todayValue: "Aujourd'hui",
withinValue: "Dans",
months: "Mois",
days: "Jours",
years: "Années",
weeks: "Semaines",
moreThanValue: "Plus de",
};
case "sp":
return {
singleValue: "Valor",
fromValue: "Desde",
toValue: "Hasta",
rangeFrom: "Desde",
rangeTo: "Hasta",
todayValue: "Hoy",
withinValue: "Dentro de",
months: "Meses",
days: "Días",
years: "Años",
weeks: "Semanas",
moreThanValue: "Más de",
};
case "it":
return {
singleValue: "Valore",
fromValue: "Da",
toValue: "A",
rangeFrom: "Da",
rangeTo: "A",
todayValue: "Oggi",
withinValue: "Entro",
months: "Mesi",
days: "Giorni",
years: "Anni",
weeks: "Settimane",
moreThanValue: "Più di",
};
case "pl":
return {
singleValue: "Wartość",
fromValue: "Od",
toValue: "Do",
rangeFrom: "Od",
rangeTo: "Do",
todayValue: "Dziś",
withinValue: "W ciągu",
months: "Miesiące",
days: "Dni",
years: "Lata",
weeks: "Tygodnie",
moreThanValue: "Więcej niż",
};
case "no":
return {
singleValue: "Verdi",
fromValue: "Fra",
toValue: "Til",
rangeFrom: "Fra",
rangeTo: "Til",
todayValue: "I dag",
withinValue: "Innen",
months: "Måneder",
days: "Dager",
years: "År",
weeks: "Uker",
moreThanValue: "Mer enn",
};
case "dk":
return {
singleValue: "Værdi",
fromValue: "Fra",
toValue: "Til",
rangeFrom: "Fra",
rangeTo: "Til",
todayValue: "I dag",
withinValue: "Indenfor",
months: "Måneder",
days: "Dage",
years: "År",
weeks: "Uger",
moreThanValue: "Mere end",
};
case "sw":
return {
singleValue: "Värde",
fromValue: "Från",
toValue: "Till",
rangeFrom: "Från",
rangeTo: "Till",
todayValue: "Idag",
withinValue: "Inom",
months: "Månader",
days: "Dagar",
years: "År",
weeks: "Veckor",
moreThanValue: "Mer än",
};
default:
case "en":
return {
singleValue: "Value",
fromValue: "From",
toValue: "To",
rangeFrom: "From",
rangeTo: "To",
todayValue: "Today",
withinValue: "Within",
months: "Months",
days: "Days",
years: "Years",
weeks: "Weeks",
moreThanValue: "More than",
};
}
}
/**
*
* @param {HTMLElement} group
* @param {string} type
*/
function updateMainFilterValueFromPopperChange(group, type) {
function calculateTargetDate(diff) {
if (isNaN(diff)) {
diff = 0;
}
const currentDate = new Date();
currentDate.setDate(currentDate.getDate() + diff);
return currentDate.toISOString().slice(0, 10);
}
if (type === "single") {
this[inputElementSymbol].value =
group.querySelector("input[type=date]").value;
} else if (type === "from") {
this[inputElementSymbol].value =
group.querySelector("input[type=date]").value + "-";
} else if (type === "to") {
this[inputElementSymbol].value =
"-" + group.querySelector("input[type=date]").value;
} else if (type === "from-to") {
// this option contain two input fields, we check which one was changed and read the other one
// the other field is in the same form group, for simplification we get both controls from the group here
const group = this[formContainerElementSymbol].querySelectorAll(
"input[type=radio][value=" + type + "] ~ input[type=date]",
);
let from = group[0].value;
if (from === "" || from === null || from < 0) {
from = 0;
}
let to = group[1].value;
if (to === "" || to === null || to < 0) {
to = 0;
}
let range;
if (from === 0 && to === 0) {
range = "";
} else if (from === 0) {
range = "-" + to;
} else if (to === 0) {
range = from + "-";
} else if (from === to) {
range = from;
} else {
range = from + "-" + to;
}
this[inputElementSymbol].value = range;
} else if (type === "today") {
this[inputElementSymbol].value = new Date().toISOString().slice(0, 10);
} else if (type === "within" || type === "more-than") {
const diffValue = parseInt(group.querySelector("input[type=number]").value);
const duration = group.querySelector("select").value;
let diffInDays;
switch (duration) {
case "days":
diffInDays = diffValue;
break;
case "weeks":
diffInDays = diffValue * 7;
break;
case "months":
diffInDays = diffValue * 30;
break;
case "years":
diffInDays = diffValue * 365;
break;
default:
diffInDays = 0;
}
if (diffInDays === 0) {
this[inputElementSymbol].value = "";
return;
}
const suffix = type !== "within" ? "" : "-";
const prefix = type !== "within" ? "-" : "";
this[inputElementSymbol].value =
suffix +
calculateTargetDate(type === "within" ? diffInDays : -diffInDays) +
prefix;
}
}
/**
* @private
* @return {initEventHandler}
*/
function initEventHandler() {
this[closeEventHandler] = (event) => {
const path = event.composedPath();
for (const [, element] of Object.entries(path)) {
if (element === this) {
return;
}
}
hide.call(this);
};
this[inputElementSymbol].addEventListener("click", () => {
this.toggleDialog();
});
this[formContainerElementSymbol].addEventListener("click", (event) => {
const element = findTargetElementFromEvent(
event,
"data-monster-role",
"range-type",
);
if (!element) {
return;
}
const type = element.getAttribute("data-monster-range-type");
const radio = this[formContainerElementSymbol].querySelector(
"input[type=radio][value=" + type + "]",
);
if (!radio) {
return;
}
radio.checked = true;
if (type === "today") {
this[inputElementSymbol].value = new Date().toISOString().slice(0, 10);
return;
}
// enable input from this group and disable the other
const group = this[formContainerElementSymbol].querySelectorAll(
"input[type=radio][value=" +
type +
"] ~ input, input[type=radio][value=" +
type +
"] ~ select",
);
for (const [, element] of Object.entries(group)) {
element.disabled = false;
}
const otherGroup = this[formContainerElementSymbol].querySelectorAll(
"input[type=radio]:not([value=" +
type +
"]) ~ input, input[type=radio]:not([value=" +
type +
"]) ~ select",
);
for (const [, element] of Object.entries(otherGroup)) {
element.disabled = true;
}
});
// change the input value if the user change the value of the radio button
this[inputElementSymbol].addEventListener("change", (event) => {
queueMicrotask(() => {
updatePopperInputsFromMainValue.call(this);
});
});
// if the user change the value of on of the input fields, we should update the value of the input field
this[formContainerElementSymbol].addEventListener("change", (event) => {
const element = findTargetElementFromEvent(
event,
"data-monster-watch",
"true",
);
if (!element) {
return;
}
const group = findTargetElementFromEvent(
event,
"data-monster-role",
"range-type",
);
if (!group) {
return;
}
const type = group.getAttribute("data-monster-range-type");
updateMainFilterValueFromPopperChange.call(this, group, type);
});
this.addEventListener("keydown", (event) => {
// if key code esc than hide dialog
if (event.key === "Escape") {
hide.call(this);
queueMicrotask(() => {
this[inputElementSymbol].focus();
});
return;
}
const input = findTargetElementFromEvent(
event,
"data-monster-role",
"input",
);
if (!input) {
return;
}
// key code down should activate first radio with name="singleValue"
if (event.key === "ArrowDown") {
show.call(this);
}
});
return this;
}
/**
* @private
*/
function attachResizeObserver() {
// against flickering
this[resizeObserverSymbol] = new ResizeObserver((entries) => {
if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
try {
this[timerCallbackSymbol].touch();
return;
} catch (e) {
delete this[timerCallbackSymbol];
}
}
this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
updatePopper.call(this);
});
});
requestAnimationFrame(() => {
let parent = this.parentNode;
while (!(parent instanceof HTMLElement) && parent !== null) {
parent = parent.parentNode;
}
if (parent instanceof HTMLElement) {
this[resizeObserverSymbol].observe(parent);
}
});
}
function disconnectResizeObserver() {
if (this[resizeObserverSymbol] instanceof ResizeObserver) {
this[resizeObserverSymbol].disconnect();
}
}
/**
* @private
*/
function hide() {
this[popperElementSymbol].style.display = "none";
removeAttributeToken(this[controlElementSymbol], "class", "open");
}
/**
* @private
* @this PopperButton
*/
function show() {
if (this.getOption("disabled", false) === true) {
return;
}
if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
return;
}
this[popperElementSymbol].style.visibility = "hidden";
this[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK;
addAttributeToken(this[controlElementSymbol], "class", "open");
// if value is "" than we should clear the form
if (this[inputElementSymbol].value === "") {
clearAndDisableFormGroups.call(this);
}
queueMicrotask(() => {
updatePopperInputsFromMainValue.call(this);
updatePopper.call(this);
});
}
/**
* @private
*/
function clearAndDisableFormGroups() {
this[radioInputsElementSymbol].forEach((radio) => {
radio.checked = false;
});
this[inputInputsElementSymbol].forEach((input) => {
input.value = "";
input.disabled = true;
});
this[selectInputsElementSymbol].forEach((select) => {
select.value = "";
select.disabled = true;
});
}
/**
* @private
* @return {string}
*/
function guessRangeTypeFromCurrentValue() {
const value = this[inputElementSymbol].value.trim();
if (value === "") {
return "empty";
}
const singleDateRegex = /^\d{4}-\d{2}-\d{2}$/;
const fromDateRegex = /^\d{4}-\d{2}-\d{2}-$/;
const toDateRegex = /^-\d{4}-\d{2}-\d{2}$/;
const fromToDateRegex = /^\d{4}-\d{2}-\d{2}-\d{4}-\d{2}-\d{2}$/;
if (singleDateRegex.test(value)) {
return "single";
}
if (fromToDateRegex.test(value)) {
return "from-to";
}
if (fromDateRegex.test(value)) {
return "from";
}
if (toDateRegex.test(value)) {
return "to";
}
return "invalid";
}
function getRangeTypeFromForm() {
const group = this[formContainerElementSymbol].querySelector(
"input[type=radio][name=rangeType]:checked",
);
if (!group) {
return;
}
return group.value;
}
/**
* function, that calculates starting from a date a range of days, months, weeks or years
*
* @example
*
* // if today is 2020-10-12
* calculateDateRangeFromToday("2020-10-12", "days") // 0
* calculateDateRangeFromToday("2020-10-13", "days") // 1
* calculateDateRangeFromToday("2020-12-14", "days") // 63
* calculateDateRangeFromToday("2020-10-12", "months") // 0
* calculateDateRangeFromToday("2020-11-12", "months") // 1
* calculateDateRangeFromToday("2021-10-12", "months") // 11
*
* @param {string} date
* @param {string} unit (day, month, year, week)
* @return {number} range
*/
function calculateDateRangeFromToday(date, unit) {
// calculate the date of today -1
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const dateAsObj = new Date(date);
const diff = dateAsObj.getTime() - yesterday.getTime();
// return the rages in days, months, years or weeks
switch (unit) {
case "days":
return Math.floor(diff / (1000 * 60 * 60 * 24));
case "months":
return Math.floor(diff / (1000 * 60 * 60 * 24 * 30));
case "years":
return Math.floor(diff / (1000 * 60 * 60 * 24 * 365));
case "weeks":
return Math.floor(diff / (1000 * 60 * 60 * 24 * 7));
}
return 0;
}
/**
* @private
*/
function updatePopperInputsFromMainValue() {
const featuresMoreThan = this.getOption("features.moreThan", false);
const featuresWithin = this.getOption("features.within", false);
let guessedValueType = guessRangeTypeFromCurrentValue.call(this);
if (
!guessedValueType ||
guessedValueType === "empty" ||
guessedValueType === "invalid"
) {
return;
}
const rangeType = getRangeTypeFromForm.call(this);
if (guessedValueType === rangeType) {
return;
}
let lastRangeUnit = "days";
// check if type from form is within and current type is to
// if so, then do not update the form
if (
guessedValueType === "to" &&
rangeType === "within" &&
featuresWithin === true
) {
guessedValueType = "within";
lastRangeUnit = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=within] ~ select[name=withinValueUnit]`,
).value;
}
// check if type from form is more than and current type is to
// if so, then do not update the form
if (
guessedValueType === "from" &&
rangeType === "more-than" &&
featuresMoreThan === true
) {
return;
}
clearAndDisableFormGroups.call(this);
let radio = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=${guessedValueType}]`,
);
let input;
let select;
switch (guessedValueType) {
case "within":
radio.checked = true;
input = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=within] ~ input[name=withinValue]`,
);
select = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=within] ~ select[name=withinValueUnit]`,
);
input.disabled = false;
select.disabled = false;
const d1 = this[inputElementSymbol].value.substring(1);
const range1 = calculateDateRangeFromToday(d1, lastRangeUnit);
select.value = lastRangeUnit;
input.value = range1;
break;
case "more-than":
radio.checked = true;
input = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=more-than] ~ input[name=moreThanValue]`,
);
select = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=more-than] ~ select[name=moreThanValueUnit]`,
);
input.disabled = false;
select.disabled = false;
const d2 = this[inputElementSymbol].value.substring(0, 10);
const range2 = calculateDateRangeFromToday(d2, lastRangeUnit);
select.value = lastRangeUnit;
input.value = range2;
break;
case "single":
// check if the date is today
const today = new Date();
const date = new Date(this[inputElementSymbol].value);
const featuresToday = this.getOption("features.today", false);
if (
featuresToday &&
today.getFullYear() === date.getFullYear() &&
today.getMonth() === date.getMonth() &&
today.getDate() === date.getDate()
) {
radio = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=today]`,
);
input = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=today] ~ input[name=todayValue]`,
);
} else {
input = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=single] ~ input[name=singleValue]`,
);
input.disabled = false;
}
radio.checked = true;
input.value = this[inputElementSymbol].value;
break;
case "from":
radio.checked = true;
const fromInput = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=from] ~ input[name=fromValue]`,
);
// the input value has an - suffix
fromInput.value = this[inputElementSymbol].value.substring(
0,
this[inputElementSymbol].value.length - 1,
);
fromInput.disabled = false;
break;
case "to":
radio.checked = true;
const toInput = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=to] ~ input[name=toValue]`,
);
// the input value has an - prefix
toInput.value = this[inputElementSymbol].value.substring(1);
toInput.disabled = false;
break;
case "from-to":
radio.checked = true;
const fromToInput = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=from-to] ~ input[name=fromValue]`,
);
const toToInput = this[formContainerElementSymbol].querySelector(
`input[name=rangeType][value=from-to] ~ input[name=toValue]`,
);
const fromToRegexWithNamedGroups =
/(?<from>\d{4}-\d{2}-\d{2})-(?<to>\d{4}-\d{2}-\d{2})/;
const groups = fromToRegexWithNamedGroups.exec(
this[inputElementSymbol].value,
);
fromToInput.value = groups.groups.from;
toToInput.value = groups.groups.to;
fromToInput.disabled = false;
toToInput.disabled = false;
break;
}
}
/**
* @private
* @return {Monster.Components.Datatable.Filter.Range}
*/
function initControlReferences() {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
this[controlElementSymbol] = this.shadowRoot.querySelector(
`[${ATTRIBUTE_ROLE}=control]`,
);
this[inputElementSymbol] = this.shadowRoot.querySelector(
`[${ATTRIBUTE_ROLE}=input]`,
);
this[popperElementSymbol] = this.shadowRoot.querySelector(
`[${ATTRIBUTE_ROLE}=popper]`,
);
this[arrowElementSymbol] = this.shadowRoot.querySelector(
`[${ATTRIBUTE_ROLE}=arrow]`,
);
this[formContainerElementSymbol] = this.shadowRoot.querySelector(
`[${ATTRIBUTE_ROLE}=form]`,
);
this[radioInputsElementSymbol] = this[
formContainerElementSymbol
].querySelectorAll(`input[name=rangeType]`);
this[inputInputsElementSymbol] = this[
formContainerElementSymbol
].querySelectorAll(`input[name=rangeType] ~ input`);
this[selectInputsElementSymbol] = this[
formContainerElementSymbol
].querySelectorAll(`input[name=rangeType] ~ select`);
return this;
}
/**
* @private
* @param {object} options
* @return {object}
*/
function initOptionsFromArguments(options) {
return options;
}
/**
* @private
*/
function updatePopper() {
if (this[popperElementSymbol].style.display !== STYLE_DISPLAY_MODE_BLOCK) {
return;
}
if (this.getOption("disabled", false) === true) {
return;
}
positionPopper.call(
this,
this[controlElementSymbol],
this[popperElementSymbol],
this.getOption("popper", {}),
);
}
/**
* @private
* @return {string}
*/
function getTemplate() {
// language=HTML
return `
<div data-monster-role="control" part="control">
<input data-monster-attributes="disabled path:disabled | if:true, class path:classes.input"
data-monster-role="input"
part="button"
data-monster-replace="path:labels.button">
<div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1">
<div data-monster-role="arrow"></div>
<div part="filter" class="flex" data-monster-replace="path:filter">
<div class="form-container" data-monster-role="form">
<div data-monster-role="range-type" data-monster-range-type="today" data-monster-attributes="class path:features.moreThan | ?:form-group:hidden">
<input type="radio" name="rangeType" value="today" tabindex="0">
<label for="today"><span data-monster-replace="path:labels.todayValue"></span></label>
<input type="hidden" name="todayValue" disabled tabindex="0">
</div>
<div data-monster-role="range-type" data-monster-range-type="within" data-monster-attributes="class path:features.within | ?:form-group:hidden">
<input type="radio" name="rangeType" value="within" tabindex="0">
<label for="within"><span
data-monster-replace="path:labels.withinValue"></span></label>
<input data-monster-watch="true" type="number" name="withinValue" disabled
tabindex="0">
<select data-monster-watch="true" name="withinValueUnit" disabled tabindex="0">
<option selected value="days" data-monster-replace="path:labels.days"></option>
<option value="weeks" data-monster-replace="path:labels.weeks"></option>
<option value="months" data-monster-replace="path:labels.months"></option>
<option value="years" data-monster-replace="path:labels.years"></option>
</select>
</div>
<div data-monster-role="range-type" data-monster-range-type="more-than" data-monster-attributes="class path:features.moreThan | ?:form-group:hidden">
<input type="radio" name="rangeType" value="more-than" tabindex="0">
<label for="more-than"><span
data-monster-replace="path:labels.moreThanValue"></span></label>
<input data-monster-watch="true" type="number" name="moreThanValue" disabled
tabindex="0">
<select data-monster-watch="true" name="moreThanValueUnit" disabled tabindex="0">
<option selected value="days" data-monster-replace="path:labels.days"></option>
<option value="weeks" data-monster-replace="path:labels.weeks"></option>
<option value="months" data-monster-replace="path:labels.months"></option>
<option value="years" data-monster-replace="path:labels.years"></option>
</select>
</div>
<div class="form-group" data-monster-role="range-type" data-monster-range-type="single">
<input type="radio" name="rangeType" value="single" tabindex="0">
<label for="single"><span data-monster-replace="path:labels.singleValue"></span></label>
<input type="date" data-monster-watch="true" name="singleValue" disabled tabindex="0">
</div>
<div class="form-group" data-monster-role="range-type" data-monster-range-type="from">
<input type="radio" name="rangeType" value="from" tabindex="0">
<label for="from"><span data-monster-replace="path:labels.fromValue"></span></label>
<input type="date" data-monster-watch="true" name="fromValue" disabled tabindex="0">
</div>
<div class="form-group" data-monster-role="range-type" data-monster-range-type="to">
<input type="radio" name="rangeType" value="to" tabindex="0">
<label for="to"><span data-monster-replace="path:labels.toValue"></span></label>
<input type="date" data-monster-watch="true" name="toValue" disabled tabindex="0">
</div>
<div class="form-group" data-monster-role="range-type" data-monster-range-type="from-to">
<input type="radio" name="rangeType" value="from-to" tabindex="0">
<label for="rangeFrom"><span data-monster-replace="path:labels.fromValue"></span></label>
<input type="date" data-monster-watch="true" name="fromValue" disabled tabindex="0">
<label for="rangeTo"><span data-monster-replace="path:labels.toValue"></span></label>
<input type="date" data-monster-watch="true" name="toValue" disabled tabindex="0">
</div>
</div>
</div>
</div>
`;
}
registerCustomElement(DateRange);