mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Fix: reverted back to import * as module
instead of using namespaces (#23069)
* Removed namespace declaration to prevent issues with external plugins. * fixed imports and tests.
This commit is contained in:
parent
c4693378dd
commit
f75387bd14
@ -1,7 +1,7 @@
|
|||||||
import sinon, { SinonFakeTimers } from 'sinon';
|
import sinon, { SinonFakeTimers } from 'sinon';
|
||||||
import each from 'lodash/each';
|
import each from 'lodash/each';
|
||||||
|
|
||||||
import { dateMath } from './datemath';
|
import * as dateMath from './datemath';
|
||||||
import { dateTime, DurationUnit, DateTime } from './moment_wrapper';
|
import { dateTime, DurationUnit, DateTime } from './moment_wrapper';
|
||||||
|
|
||||||
describe('DateMath', () => {
|
describe('DateMath', () => {
|
||||||
|
@ -5,161 +5,158 @@ import { TimeZone } from '../types/index';
|
|||||||
|
|
||||||
const units: DurationUnit[] = ['y', 'M', 'w', 'd', 'h', 'm', 's'];
|
const units: DurationUnit[] = ['y', 'M', 'w', 'd', 'h', 'm', 's'];
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
export function isMathString(text: string | DateTime | Date): boolean {
|
||||||
export namespace dateMath {
|
if (!text) {
|
||||||
export function isMathString(text: string | DateTime | Date): boolean {
|
|
||||||
if (!text) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof text === 'string' && (text.substring(0, 3) === 'now' || text.includes('||'))) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses different types input to a moment instance. There is a specific formatting language that can be used
|
|
||||||
* if text arg is string. See unit tests for examples.
|
|
||||||
* @param text
|
|
||||||
* @param roundUp See parseDateMath function.
|
|
||||||
* @param timezone Only string 'utc' is acceptable here, for anything else, local timezone is used.
|
|
||||||
*/
|
|
||||||
export function parse(
|
|
||||||
text?: string | DateTime | Date | null,
|
|
||||||
roundUp?: boolean,
|
|
||||||
timezone?: TimeZone
|
|
||||||
): DateTime | undefined {
|
|
||||||
if (!text) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof text !== 'string') {
|
|
||||||
if (isDateTime(text)) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
if (isDate(text)) {
|
|
||||||
return dateTime(text);
|
|
||||||
}
|
|
||||||
// We got some non string which is not a moment nor Date. TS should be able to check for that but not always.
|
|
||||||
return undefined;
|
|
||||||
} else {
|
|
||||||
let time;
|
|
||||||
let mathString = '';
|
|
||||||
let index;
|
|
||||||
let parseString;
|
|
||||||
|
|
||||||
if (text.substring(0, 3) === 'now') {
|
|
||||||
time = dateTimeForTimeZone(timezone);
|
|
||||||
mathString = text.substring('now'.length);
|
|
||||||
} else {
|
|
||||||
index = text.indexOf('||');
|
|
||||||
if (index === -1) {
|
|
||||||
parseString = text;
|
|
||||||
mathString = ''; // nothing else
|
|
||||||
} else {
|
|
||||||
parseString = text.substring(0, index);
|
|
||||||
mathString = text.substring(index + 2);
|
|
||||||
}
|
|
||||||
// We're going to just require ISO8601 timestamps, k?
|
|
||||||
time = dateTime(parseString, ISO_8601);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mathString.length) {
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseDateMath(mathString, time, roundUp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if text is a valid date which in this context means that it is either a Moment instance or it can be parsed
|
|
||||||
* by parse function. See parse function to see what is considered acceptable.
|
|
||||||
* @param text
|
|
||||||
*/
|
|
||||||
export function isValid(text: string | DateTime): boolean {
|
|
||||||
const date = parse(text);
|
|
||||||
if (!date) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDateTime(date)) {
|
|
||||||
return date.isValid();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (typeof text === 'string' && (text.substring(0, 3) === 'now' || text.includes('||'))) {
|
||||||
* Parses math part of the time string and shifts supplied time according to that math. See unit tests for examples.
|
return true;
|
||||||
* @param mathString
|
} else {
|
||||||
* @param time
|
return false;
|
||||||
* @param roundUp If true it will round the time to endOf time unit, otherwise to startOf time unit.
|
}
|
||||||
*/
|
}
|
||||||
// TODO: Had to revert Andrejs `time: moment.Moment` to `time: any`
|
|
||||||
export function parseDateMath(mathString: string, time: any, roundUp?: boolean): DateTime | undefined {
|
|
||||||
const strippedMathString = mathString.replace(/\s/g, '');
|
|
||||||
const dateTime = time;
|
|
||||||
let i = 0;
|
|
||||||
const len = strippedMathString.length;
|
|
||||||
|
|
||||||
while (i < len) {
|
/**
|
||||||
const c = strippedMathString.charAt(i++);
|
* Parses different types input to a moment instance. There is a specific formatting language that can be used
|
||||||
let type;
|
* if text arg is string. See unit tests for examples.
|
||||||
let num;
|
* @param text
|
||||||
let unit;
|
* @param roundUp See parseDateMath function.
|
||||||
|
* @param timezone Only string 'utc' is acceptable here, for anything else, local timezone is used.
|
||||||
|
*/
|
||||||
|
export function parse(
|
||||||
|
text?: string | DateTime | Date | null,
|
||||||
|
roundUp?: boolean,
|
||||||
|
timezone?: TimeZone
|
||||||
|
): DateTime | undefined {
|
||||||
|
if (!text) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (c === '/') {
|
if (typeof text !== 'string') {
|
||||||
type = 0;
|
if (isDateTime(text)) {
|
||||||
} else if (c === '+') {
|
return text;
|
||||||
type = 1;
|
}
|
||||||
} else if (c === '-') {
|
if (isDate(text)) {
|
||||||
type = 2;
|
return dateTime(text);
|
||||||
|
}
|
||||||
|
// We got some non string which is not a moment nor Date. TS should be able to check for that but not always.
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
let time;
|
||||||
|
let mathString = '';
|
||||||
|
let index;
|
||||||
|
let parseString;
|
||||||
|
|
||||||
|
if (text.substring(0, 3) === 'now') {
|
||||||
|
time = dateTimeForTimeZone(timezone);
|
||||||
|
mathString = text.substring('now'.length);
|
||||||
|
} else {
|
||||||
|
index = text.indexOf('||');
|
||||||
|
if (index === -1) {
|
||||||
|
parseString = text;
|
||||||
|
mathString = ''; // nothing else
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
parseString = text.substring(0, index);
|
||||||
|
mathString = text.substring(index + 2);
|
||||||
}
|
}
|
||||||
|
// We're going to just require ISO8601 timestamps, k?
|
||||||
|
time = dateTime(parseString, ISO_8601);
|
||||||
|
}
|
||||||
|
|
||||||
if (isNaN(parseInt(strippedMathString.charAt(i), 10))) {
|
if (!mathString.length) {
|
||||||
num = 1;
|
return time;
|
||||||
} else if (strippedMathString.length === 2) {
|
}
|
||||||
num = strippedMathString.charAt(i);
|
|
||||||
} else {
|
|
||||||
const numFrom = i;
|
|
||||||
while (!isNaN(parseInt(strippedMathString.charAt(i), 10))) {
|
|
||||||
i++;
|
|
||||||
if (i > 10) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
num = parseInt(strippedMathString.substring(numFrom, i), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 0) {
|
return parseDateMath(mathString, time, roundUp);
|
||||||
// rounding is only allowed on whole, single, units (eg M or 1M, not 0.5M or 2M)
|
}
|
||||||
if (num !== 1) {
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if text is a valid date which in this context means that it is either a Moment instance or it can be parsed
|
||||||
|
* by parse function. See parse function to see what is considered acceptable.
|
||||||
|
* @param text
|
||||||
|
*/
|
||||||
|
export function isValid(text: string | DateTime): boolean {
|
||||||
|
const date = parse(text);
|
||||||
|
if (!date) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDateTime(date)) {
|
||||||
|
return date.isValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses math part of the time string and shifts supplied time according to that math. See unit tests for examples.
|
||||||
|
* @param mathString
|
||||||
|
* @param time
|
||||||
|
* @param roundUp If true it will round the time to endOf time unit, otherwise to startOf time unit.
|
||||||
|
*/
|
||||||
|
// TODO: Had to revert Andrejs `time: moment.Moment` to `time: any`
|
||||||
|
export function parseDateMath(mathString: string, time: any, roundUp?: boolean): DateTime | undefined {
|
||||||
|
const strippedMathString = mathString.replace(/\s/g, '');
|
||||||
|
const dateTime = time;
|
||||||
|
let i = 0;
|
||||||
|
const len = strippedMathString.length;
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
const c = strippedMathString.charAt(i++);
|
||||||
|
let type;
|
||||||
|
let num;
|
||||||
|
let unit;
|
||||||
|
|
||||||
|
if (c === '/') {
|
||||||
|
type = 0;
|
||||||
|
} else if (c === '+') {
|
||||||
|
type = 1;
|
||||||
|
} else if (c === '-') {
|
||||||
|
type = 2;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(parseInt(strippedMathString.charAt(i), 10))) {
|
||||||
|
num = 1;
|
||||||
|
} else if (strippedMathString.length === 2) {
|
||||||
|
num = strippedMathString.charAt(i);
|
||||||
|
} else {
|
||||||
|
const numFrom = i;
|
||||||
|
while (!isNaN(parseInt(strippedMathString.charAt(i), 10))) {
|
||||||
|
i++;
|
||||||
|
if (i > 10) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unit = strippedMathString.charAt(i++);
|
num = parseInt(strippedMathString.substring(numFrom, i), 10);
|
||||||
|
}
|
||||||
|
|
||||||
if (!includes(units, unit)) {
|
if (type === 0) {
|
||||||
|
// rounding is only allowed on whole, single, units (eg M or 1M, not 0.5M or 2M)
|
||||||
|
if (num !== 1) {
|
||||||
return undefined;
|
return undefined;
|
||||||
} else {
|
|
||||||
if (type === 0) {
|
|
||||||
if (roundUp) {
|
|
||||||
dateTime.endOf(unit);
|
|
||||||
} else {
|
|
||||||
dateTime.startOf(unit);
|
|
||||||
}
|
|
||||||
} else if (type === 1) {
|
|
||||||
dateTime.add(num, unit);
|
|
||||||
} else if (type === 2) {
|
|
||||||
dateTime.subtract(num, unit);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dateTime;
|
unit = strippedMathString.charAt(i++);
|
||||||
|
|
||||||
|
if (!includes(units, unit)) {
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
if (type === 0) {
|
||||||
|
if (roundUp) {
|
||||||
|
dateTime.endOf(unit);
|
||||||
|
} else {
|
||||||
|
dateTime.startOf(unit);
|
||||||
|
}
|
||||||
|
} else if (type === 1) {
|
||||||
|
dateTime.add(num, unit);
|
||||||
|
} else if (type === 2) {
|
||||||
|
dateTime.subtract(num, unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return dateTime;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Names are too general to export globally
|
// Names are too general to export globally
|
||||||
export { dateMath } from './datemath';
|
import * as dateMath from './datemath';
|
||||||
export { rangeUtil } from './rangeutil';
|
import * as rangeUtil from './rangeutil';
|
||||||
export * from './moment_wrapper';
|
export * from './moment_wrapper';
|
||||||
export * from './timezones';
|
export * from './timezones';
|
||||||
export * from './formats';
|
export * from './formats';
|
||||||
|
export { dateMath, rangeUtil };
|
||||||
|
@ -3,183 +3,180 @@ import groupBy from 'lodash/groupBy';
|
|||||||
|
|
||||||
import { RawTimeRange } from '../types/time';
|
import { RawTimeRange } from '../types/time';
|
||||||
|
|
||||||
import { dateMath } from './datemath';
|
import * as dateMath from './datemath';
|
||||||
import { isDateTime, DateTime } from './moment_wrapper';
|
import { isDateTime, DateTime } from './moment_wrapper';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
const spans: { [key: string]: { display: string; section?: number } } = {
|
||||||
export namespace rangeUtil {
|
s: { display: 'second' },
|
||||||
const spans: { [key: string]: { display: string; section?: number } } = {
|
m: { display: 'minute' },
|
||||||
s: { display: 'second' },
|
h: { display: 'hour' },
|
||||||
m: { display: 'minute' },
|
d: { display: 'day' },
|
||||||
h: { display: 'hour' },
|
w: { display: 'week' },
|
||||||
d: { display: 'day' },
|
M: { display: 'month' },
|
||||||
w: { display: 'week' },
|
y: { display: 'year' },
|
||||||
M: { display: 'month' },
|
};
|
||||||
y: { display: 'year' },
|
|
||||||
};
|
|
||||||
|
|
||||||
const rangeOptions = [
|
const rangeOptions = [
|
||||||
{ from: 'now/d', to: 'now/d', display: 'Today', section: 2 },
|
{ from: 'now/d', to: 'now/d', display: 'Today', section: 2 },
|
||||||
{ from: 'now/d', to: 'now', display: 'Today so far', section: 2 },
|
{ from: 'now/d', to: 'now', display: 'Today so far', section: 2 },
|
||||||
{ from: 'now/w', to: 'now/w', display: 'This week', section: 2 },
|
{ from: 'now/w', to: 'now/w', display: 'This week', section: 2 },
|
||||||
{ from: 'now/w', to: 'now', display: 'This week so far', section: 2 },
|
{ from: 'now/w', to: 'now', display: 'This week so far', section: 2 },
|
||||||
{ from: 'now/M', to: 'now/M', display: 'This month', section: 2 },
|
{ from: 'now/M', to: 'now/M', display: 'This month', section: 2 },
|
||||||
{ from: 'now/M', to: 'now', display: 'This month so far', section: 2 },
|
{ from: 'now/M', to: 'now', display: 'This month so far', section: 2 },
|
||||||
{ from: 'now/y', to: 'now/y', display: 'This year', section: 2 },
|
{ from: 'now/y', to: 'now/y', display: 'This year', section: 2 },
|
||||||
{ from: 'now/y', to: 'now', display: 'This year so far', section: 2 },
|
{ from: 'now/y', to: 'now', display: 'This year so far', section: 2 },
|
||||||
|
|
||||||
{ from: 'now-1d/d', to: 'now-1d/d', display: 'Yesterday', section: 1 },
|
{ from: 'now-1d/d', to: 'now-1d/d', display: 'Yesterday', section: 1 },
|
||||||
{
|
{
|
||||||
from: 'now-2d/d',
|
from: 'now-2d/d',
|
||||||
to: 'now-2d/d',
|
to: 'now-2d/d',
|
||||||
display: 'Day before yesterday',
|
display: 'Day before yesterday',
|
||||||
section: 1,
|
section: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: 'now-7d/d',
|
from: 'now-7d/d',
|
||||||
to: 'now-7d/d',
|
to: 'now-7d/d',
|
||||||
display: 'This day last week',
|
display: 'This day last week',
|
||||||
section: 1,
|
section: 1,
|
||||||
},
|
},
|
||||||
{ from: 'now-1w/w', to: 'now-1w/w', display: 'Previous week', section: 1 },
|
{ from: 'now-1w/w', to: 'now-1w/w', display: 'Previous week', section: 1 },
|
||||||
{ from: 'now-1M/M', to: 'now-1M/M', display: 'Previous month', section: 1 },
|
{ from: 'now-1M/M', to: 'now-1M/M', display: 'Previous month', section: 1 },
|
||||||
{ from: 'now-1y/y', to: 'now-1y/y', display: 'Previous year', section: 1 },
|
{ from: 'now-1y/y', to: 'now-1y/y', display: 'Previous year', section: 1 },
|
||||||
|
|
||||||
{ from: 'now-5m', to: 'now', display: 'Last 5 minutes', section: 3 },
|
{ from: 'now-5m', to: 'now', display: 'Last 5 minutes', section: 3 },
|
||||||
{ from: 'now-15m', to: 'now', display: 'Last 15 minutes', section: 3 },
|
{ from: 'now-15m', to: 'now', display: 'Last 15 minutes', section: 3 },
|
||||||
{ from: 'now-30m', to: 'now', display: 'Last 30 minutes', section: 3 },
|
{ from: 'now-30m', to: 'now', display: 'Last 30 minutes', section: 3 },
|
||||||
{ from: 'now-1h', to: 'now', display: 'Last 1 hour', section: 3 },
|
{ from: 'now-1h', to: 'now', display: 'Last 1 hour', section: 3 },
|
||||||
{ from: 'now-3h', to: 'now', display: 'Last 3 hours', section: 3 },
|
{ from: 'now-3h', to: 'now', display: 'Last 3 hours', section: 3 },
|
||||||
{ from: 'now-6h', to: 'now', display: 'Last 6 hours', section: 3 },
|
{ from: 'now-6h', to: 'now', display: 'Last 6 hours', section: 3 },
|
||||||
{ from: 'now-12h', to: 'now', display: 'Last 12 hours', section: 3 },
|
{ from: 'now-12h', to: 'now', display: 'Last 12 hours', section: 3 },
|
||||||
{ from: 'now-24h', to: 'now', display: 'Last 24 hours', section: 3 },
|
{ from: 'now-24h', to: 'now', display: 'Last 24 hours', section: 3 },
|
||||||
{ from: 'now-2d', to: 'now', display: 'Last 2 days', section: 0 },
|
{ from: 'now-2d', to: 'now', display: 'Last 2 days', section: 0 },
|
||||||
{ from: 'now-7d', to: 'now', display: 'Last 7 days', section: 0 },
|
{ from: 'now-7d', to: 'now', display: 'Last 7 days', section: 0 },
|
||||||
{ from: 'now-30d', to: 'now', display: 'Last 30 days', section: 0 },
|
{ from: 'now-30d', to: 'now', display: 'Last 30 days', section: 0 },
|
||||||
{ from: 'now-90d', to: 'now', display: 'Last 90 days', section: 0 },
|
{ from: 'now-90d', to: 'now', display: 'Last 90 days', section: 0 },
|
||||||
{ from: 'now-6M', to: 'now', display: 'Last 6 months', section: 0 },
|
{ from: 'now-6M', to: 'now', display: 'Last 6 months', section: 0 },
|
||||||
{ from: 'now-1y', to: 'now', display: 'Last 1 year', section: 0 },
|
{ from: 'now-1y', to: 'now', display: 'Last 1 year', section: 0 },
|
||||||
{ from: 'now-2y', to: 'now', display: 'Last 2 years', section: 0 },
|
{ from: 'now-2y', to: 'now', display: 'Last 2 years', section: 0 },
|
||||||
{ from: 'now-5y', to: 'now', display: 'Last 5 years', section: 0 },
|
{ from: 'now-5y', to: 'now', display: 'Last 5 years', section: 0 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const absoluteFormat = 'YYYY-MM-DD HH:mm:ss';
|
const absoluteFormat = 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
|
||||||
const rangeIndex: any = {};
|
const rangeIndex: any = {};
|
||||||
each(rangeOptions, (frame: any) => {
|
each(rangeOptions, (frame: any) => {
|
||||||
rangeIndex[frame.from + ' to ' + frame.to] = frame;
|
rangeIndex[frame.from + ' to ' + frame.to] = frame;
|
||||||
|
});
|
||||||
|
|
||||||
|
export function getRelativeTimesList(timepickerSettings: any, currentDisplay: any) {
|
||||||
|
const groups = groupBy(rangeOptions, (option: any) => {
|
||||||
|
option.active = option.display === currentDisplay;
|
||||||
|
return option.section;
|
||||||
});
|
});
|
||||||
|
|
||||||
export function getRelativeTimesList(timepickerSettings: any, currentDisplay: any) {
|
// _.each(timepickerSettings.time_options, (duration: string) => {
|
||||||
const groups = groupBy(rangeOptions, (option: any) => {
|
// let info = describeTextRange(duration);
|
||||||
option.active = option.display === currentDisplay;
|
// if (info.section) {
|
||||||
return option.section;
|
// groups[info.section].push(info);
|
||||||
});
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
// _.each(timepickerSettings.time_options, (duration: string) => {
|
return groups;
|
||||||
// let info = describeTextRange(duration);
|
}
|
||||||
// if (info.section) {
|
|
||||||
// groups[info.section].push(info);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
return groups;
|
function formatDate(date: DateTime) {
|
||||||
|
return date.format(absoluteFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handles expressions like
|
||||||
|
// 5m
|
||||||
|
// 5m to now/d
|
||||||
|
// now/d to now
|
||||||
|
// now/d
|
||||||
|
// if no to <expr> then to now is assumed
|
||||||
|
export function describeTextRange(expr: any) {
|
||||||
|
const isLast = expr.indexOf('+') !== 0;
|
||||||
|
if (expr.indexOf('now') === -1) {
|
||||||
|
expr = (isLast ? 'now-' : 'now') + expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDate(date: DateTime) {
|
let opt = rangeIndex[expr + ' to now'];
|
||||||
return date.format(absoluteFormat);
|
if (opt) {
|
||||||
}
|
|
||||||
|
|
||||||
// handles expressions like
|
|
||||||
// 5m
|
|
||||||
// 5m to now/d
|
|
||||||
// now/d to now
|
|
||||||
// now/d
|
|
||||||
// if no to <expr> then to now is assumed
|
|
||||||
export function describeTextRange(expr: any) {
|
|
||||||
const isLast = expr.indexOf('+') !== 0;
|
|
||||||
if (expr.indexOf('now') === -1) {
|
|
||||||
expr = (isLast ? 'now-' : 'now') + expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
let opt = rangeIndex[expr + ' to now'];
|
|
||||||
if (opt) {
|
|
||||||
return opt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLast) {
|
|
||||||
opt = { from: expr, to: 'now' };
|
|
||||||
} else {
|
|
||||||
opt = { from: 'now', to: expr };
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = /^now([-+])(\d+)(\w)/.exec(expr);
|
|
||||||
if (parts) {
|
|
||||||
const unit = parts[3];
|
|
||||||
const amount = parseInt(parts[2], 10);
|
|
||||||
const span = spans[unit];
|
|
||||||
if (span) {
|
|
||||||
opt.display = isLast ? 'Last ' : 'Next ';
|
|
||||||
opt.display += amount + ' ' + span.display;
|
|
||||||
opt.section = span.section;
|
|
||||||
if (amount > 1) {
|
|
||||||
opt.display += 's';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
opt.display = opt.from + ' to ' + opt.to;
|
|
||||||
opt.invalid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return opt;
|
return opt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (isLast) {
|
||||||
* Use this function to get a properly formatted string representation of a {@link @grafana/data:RawTimeRange | range}.
|
opt = { from: expr, to: 'now' };
|
||||||
*
|
} else {
|
||||||
* @example
|
opt = { from: 'now', to: expr };
|
||||||
* ```
|
|
||||||
* // Prints "2":
|
|
||||||
* console.log(add(1,1));
|
|
||||||
* ```
|
|
||||||
* @category TimeUtils
|
|
||||||
* @param range - a time range (usually specified by the TimePicker)
|
|
||||||
* @alpha
|
|
||||||
*/
|
|
||||||
export function describeTimeRange(range: RawTimeRange): string {
|
|
||||||
const option = rangeIndex[range.from.toString() + ' to ' + range.to.toString()];
|
|
||||||
if (option) {
|
|
||||||
return option.display;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDateTime(range.from) && isDateTime(range.to)) {
|
|
||||||
return formatDate(range.from) + ' to ' + formatDate(range.to);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDateTime(range.from)) {
|
|
||||||
const toMoment = dateMath.parse(range.to, true);
|
|
||||||
return toMoment ? formatDate(range.from) + ' to ' + toMoment.fromNow() : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDateTime(range.to)) {
|
|
||||||
const from = dateMath.parse(range.from, false);
|
|
||||||
return from ? from.fromNow() + ' to ' + formatDate(range.to) : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (range.to.toString() === 'now') {
|
|
||||||
const res = describeTextRange(range.from);
|
|
||||||
return res.display;
|
|
||||||
}
|
|
||||||
|
|
||||||
return range.from.toString() + ' to ' + range.to.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isValidTimeSpan = (value: string) => {
|
const parts = /^now([-+])(\d+)(\w)/.exec(expr);
|
||||||
if (value.indexOf('$') === 0 || value.indexOf('+$') === 0) {
|
if (parts) {
|
||||||
return true;
|
const unit = parts[3];
|
||||||
|
const amount = parseInt(parts[2], 10);
|
||||||
|
const span = spans[unit];
|
||||||
|
if (span) {
|
||||||
|
opt.display = isLast ? 'Last ' : 'Next ';
|
||||||
|
opt.display += amount + ' ' + span.display;
|
||||||
|
opt.section = span.section;
|
||||||
|
if (amount > 1) {
|
||||||
|
opt.display += 's';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
opt.display = opt.from + ' to ' + opt.to;
|
||||||
|
opt.invalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
const info = describeTextRange(value);
|
return opt;
|
||||||
return info.invalid !== true;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this function to get a properly formatted string representation of a {@link @grafana/data:RawTimeRange | range}.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* // Prints "2":
|
||||||
|
* console.log(add(1,1));
|
||||||
|
* ```
|
||||||
|
* @category TimeUtils
|
||||||
|
* @param range - a time range (usually specified by the TimePicker)
|
||||||
|
* @alpha
|
||||||
|
*/
|
||||||
|
export function describeTimeRange(range: RawTimeRange): string {
|
||||||
|
const option = rangeIndex[range.from.toString() + ' to ' + range.to.toString()];
|
||||||
|
if (option) {
|
||||||
|
return option.display;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDateTime(range.from) && isDateTime(range.to)) {
|
||||||
|
return formatDate(range.from) + ' to ' + formatDate(range.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDateTime(range.from)) {
|
||||||
|
const toMoment = dateMath.parse(range.to, true);
|
||||||
|
return toMoment ? formatDate(range.from) + ' to ' + toMoment.fromNow() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDateTime(range.to)) {
|
||||||
|
const from = dateMath.parse(range.from, false);
|
||||||
|
return from ? from.fromNow() + ' to ' + formatDate(range.to) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (range.to.toString() === 'now') {
|
||||||
|
const res = describeTextRange(range.from);
|
||||||
|
return res.display;
|
||||||
|
}
|
||||||
|
|
||||||
|
return range.from.toString() + ' to ' + range.to.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isValidTimeSpan = (value: string) => {
|
||||||
|
if (value.indexOf('$') === 0 || value.indexOf('+$') === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = describeTextRange(value);
|
||||||
|
return info.invalid !== true;
|
||||||
|
};
|
||||||
|
@ -5,11 +5,8 @@ export interface AppEvent<T> {
|
|||||||
payload?: T;
|
payload?: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
export type AlertPayload = [string, string?];
|
||||||
export namespace AppEvents {
|
|
||||||
export type AlertPayload = [string, string?];
|
|
||||||
|
|
||||||
export const alertSuccess = eventFactory<AlertPayload>('alert-success');
|
export const alertSuccess = eventFactory<AlertPayload>('alert-success');
|
||||||
export const alertWarning = eventFactory<AlertPayload>('alert-warning');
|
export const alertWarning = eventFactory<AlertPayload>('alert-warning');
|
||||||
export const alertError = eventFactory<AlertPayload>('alert-error');
|
export const alertError = eventFactory<AlertPayload>('alert-error');
|
||||||
}
|
|
||||||
|
@ -24,5 +24,9 @@ export * from './theme';
|
|||||||
export * from './orgs';
|
export * from './orgs';
|
||||||
export * from './flot';
|
export * from './flot';
|
||||||
|
|
||||||
export { AppEvent, AppEvents } from './appEvents';
|
import * as AppEvents from './appEvents';
|
||||||
export { PanelEvents } from './panelEvents';
|
import { AppEvent } from './appEvents';
|
||||||
|
export { AppEvent, AppEvents };
|
||||||
|
|
||||||
|
import * as PanelEvents from './panelEvents';
|
||||||
|
export { PanelEvents };
|
||||||
|
@ -2,28 +2,25 @@ import { eventFactory } from './utils';
|
|||||||
import { DataQueryError, DataQueryResponseData } from './datasource';
|
import { DataQueryError, DataQueryResponseData } from './datasource';
|
||||||
import { AngularPanelMenuItem } from './panel';
|
import { AngularPanelMenuItem } from './panel';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
/** Payloads */
|
||||||
export namespace PanelEvents {
|
export interface PanelChangeViewPayload {
|
||||||
/** Payloads */
|
fullscreen?: boolean;
|
||||||
export interface PanelChangeViewPayload {
|
edit?: boolean;
|
||||||
fullscreen?: boolean;
|
panelId?: number;
|
||||||
edit?: boolean;
|
toggle?: boolean;
|
||||||
panelId?: number;
|
|
||||||
toggle?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Events */
|
|
||||||
export const refresh = eventFactory('refresh');
|
|
||||||
export const componentDidMount = eventFactory('component-did-mount');
|
|
||||||
export const dataError = eventFactory<DataQueryError>('data-error');
|
|
||||||
export const dataReceived = eventFactory<DataQueryResponseData[]>('data-received');
|
|
||||||
export const dataSnapshotLoad = eventFactory<DataQueryResponseData[]>('data-snapshot-load');
|
|
||||||
export const editModeInitialized = eventFactory('init-edit-mode');
|
|
||||||
export const initPanelActions = eventFactory<AngularPanelMenuItem[]>('init-panel-actions');
|
|
||||||
export const panelChangeView = eventFactory<PanelChangeViewPayload>('panel-change-view');
|
|
||||||
export const panelInitialized = eventFactory('panel-initialized');
|
|
||||||
export const panelSizeChanged = eventFactory('panel-size-changed');
|
|
||||||
export const panelTeardown = eventFactory('panel-teardown');
|
|
||||||
export const render = eventFactory<any>('render');
|
|
||||||
export const viewModeChanged = eventFactory('view-mode-changed');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Events */
|
||||||
|
export const refresh = eventFactory('refresh');
|
||||||
|
export const componentDidMount = eventFactory('component-did-mount');
|
||||||
|
export const dataError = eventFactory<DataQueryError>('data-error');
|
||||||
|
export const dataReceived = eventFactory<DataQueryResponseData[]>('data-received');
|
||||||
|
export const dataSnapshotLoad = eventFactory<DataQueryResponseData[]>('data-snapshot-load');
|
||||||
|
export const editModeInitialized = eventFactory('init-edit-mode');
|
||||||
|
export const initPanelActions = eventFactory<AngularPanelMenuItem[]>('init-panel-actions');
|
||||||
|
export const panelChangeView = eventFactory<PanelChangeViewPayload>('panel-change-view');
|
||||||
|
export const panelInitialized = eventFactory('panel-initialized');
|
||||||
|
export const panelSizeChanged = eventFactory('panel-size-changed');
|
||||||
|
export const panelTeardown = eventFactory('panel-teardown');
|
||||||
|
export const render = eventFactory<any>('render');
|
||||||
|
export const viewModeChanged = eventFactory('view-mode-changed');
|
||||||
|
@ -2,7 +2,7 @@ import React, { ChangeEvent } from 'react';
|
|||||||
import { HorizontalGroup } from '../Layout/Layout';
|
import { HorizontalGroup } from '../Layout/Layout';
|
||||||
import Forms from '../Forms';
|
import Forms from '../Forms';
|
||||||
import { MappingType, RangeMap, ValueMap, ValueMapping } from '@grafana/data';
|
import { MappingType, RangeMap, ValueMap, ValueMapping } from '@grafana/data';
|
||||||
import { styleMixins } from '../../themes/mixins';
|
import * as styleMixins from '../../themes/mixins';
|
||||||
import { useTheme } from '../../themes';
|
import { useTheme } from '../../themes';
|
||||||
import { FieldConfigItemHeaderTitle } from '../FieldConfigs/FieldConfigItemHeaderTitle';
|
import { FieldConfigItemHeaderTitle } from '../FieldConfigs/FieldConfigItemHeaderTitle';
|
||||||
|
|
||||||
|
@ -3,4 +3,6 @@ import { getTheme, mockTheme } from './getTheme';
|
|||||||
import { selectThemeVariant } from './selectThemeVariant';
|
import { selectThemeVariant } from './selectThemeVariant';
|
||||||
export { stylesFactory } from './stylesFactory';
|
export { stylesFactory } from './stylesFactory';
|
||||||
export { ThemeContext, withTheme, mockTheme, getTheme, selectThemeVariant, useTheme, mockThemeContext };
|
export { ThemeContext, withTheme, mockTheme, getTheme, selectThemeVariant, useTheme, mockThemeContext };
|
||||||
export { styleMixins } from './mixins';
|
|
||||||
|
import * as styleMixins from './mixins';
|
||||||
|
export { styleMixins };
|
||||||
|
@ -3,11 +3,9 @@ import { selectThemeVariant } from './selectThemeVariant';
|
|||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
import { stylesFactory } from './stylesFactory';
|
import { stylesFactory } from './stylesFactory';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
export function cardChrome(theme: GrafanaTheme): string {
|
||||||
export namespace styleMixins {
|
if (theme.isDark) {
|
||||||
export function cardChrome(theme: GrafanaTheme): string {
|
return `
|
||||||
if (theme.isDark) {
|
|
||||||
return `
|
|
||||||
background: linear-gradient(135deg, ${theme.colors.dark8}, ${theme.colors.dark6});
|
background: linear-gradient(135deg, ${theme.colors.dark8}, ${theme.colors.dark6});
|
||||||
&:hover {
|
&:hover {
|
||||||
background: linear-gradient(135deg, ${theme.colors.dark9}, ${theme.colors.dark6});
|
background: linear-gradient(135deg, ${theme.colors.dark9}, ${theme.colors.dark6});
|
||||||
@ -15,9 +13,9 @@ export namespace styleMixins {
|
|||||||
box-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.3);
|
box-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.3);
|
||||||
border-radius: ${theme.border.radius.md};
|
border-radius: ${theme.border.radius.md};
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
background: linear-gradient(135deg, ${theme.colors.gray6}, ${theme.colors.gray7});
|
background: linear-gradient(135deg, ${theme.colors.gray6}, ${theme.colors.gray7});
|
||||||
&:hover {
|
&:hover {
|
||||||
background: linear-gradient(135deg, ${theme.colors.gray7}, ${theme.colors.gray6});
|
background: linear-gradient(135deg, ${theme.colors.gray7}, ${theme.colors.gray6});
|
||||||
@ -25,11 +23,11 @@ export namespace styleMixins {
|
|||||||
box-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.1);
|
box-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||||
border-radius: ${theme.border.radius.md};
|
border-radius: ${theme.border.radius.md};
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listItem(theme: GrafanaTheme): string {
|
export function listItem(theme: GrafanaTheme): string {
|
||||||
if (theme.isDark) {
|
if (theme.isDark) {
|
||||||
return `
|
return `
|
||||||
background: ${theme.colors.dark7};
|
background: ${theme.colors.dark7};
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${theme.colors.dark9};
|
background: ${theme.colors.dark9};
|
||||||
@ -37,9 +35,9 @@ export namespace styleMixins {
|
|||||||
box-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.3);
|
box-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.3);
|
||||||
border-radius: ${theme.border.radius.md};
|
border-radius: ${theme.border.radius.md};
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
background: ${theme.colors.gray7};
|
background: ${theme.colors.gray7};
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${theme.colors.gray6};
|
background: ${theme.colors.gray6};
|
||||||
@ -47,55 +45,54 @@ export namespace styleMixins {
|
|||||||
box-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.1);
|
box-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||||
border-radius: ${theme.border.radius.md};
|
border-radius: ${theme.border.radius.md};
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const panelEditorNestedListStyles = stylesFactory((theme: GrafanaTheme) => {
|
export const panelEditorNestedListStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
const borderColor = selectThemeVariant(
|
const borderColor = selectThemeVariant(
|
||||||
{
|
{
|
||||||
light: theme.colors.gray85,
|
light: theme.colors.gray85,
|
||||||
dark: theme.colors.dark9,
|
dark: theme.colors.dark9,
|
||||||
},
|
},
|
||||||
theme.type
|
theme.type
|
||||||
);
|
);
|
||||||
const shadow = selectThemeVariant(
|
const shadow = selectThemeVariant(
|
||||||
{
|
{
|
||||||
light: theme.colors.gray85,
|
light: theme.colors.gray85,
|
||||||
dark: theme.colors.black,
|
dark: theme.colors.black,
|
||||||
},
|
},
|
||||||
theme.type
|
theme.type
|
||||||
);
|
);
|
||||||
const headerBg = selectThemeVariant(
|
const headerBg = selectThemeVariant(
|
||||||
{
|
{
|
||||||
light: theme.colors.white,
|
light: theme.colors.white,
|
||||||
dark: theme.colors.dark1,
|
dark: theme.colors.dark1,
|
||||||
},
|
},
|
||||||
theme.type
|
theme.type
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wrapper: css`
|
wrapper: css`
|
||||||
border: 1px dashed ${borderColor};
|
border: 1px dashed ${borderColor};
|
||||||
margin-bottom: ${theme.spacing.md};
|
margin-bottom: ${theme.spacing.md};
|
||||||
transition: box-shadow 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
transition: box-shadow 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 0 10px ${shadow};
|
box-shadow: 0 0 10px ${shadow};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
headerWrapper: css`
|
headerWrapper: css`
|
||||||
background: ${headerBg};
|
background: ${headerBg};
|
||||||
padding: ${theme.spacing.xs} 0;
|
padding: ${theme.spacing.xs} 0;
|
||||||
`,
|
`,
|
||||||
|
|
||||||
content: css`
|
content: css`
|
||||||
padding: ${theme.spacing.xs} ${theme.spacing.sm} ${theme.spacing.sm} ${theme.spacing.sm};
|
padding: ${theme.spacing.xs} ${theme.spacing.sm} ${theme.spacing.sm} ${theme.spacing.sm};
|
||||||
border-top: 1px dashed ${borderColor};
|
border-top: 1px dashed ${borderColor};
|
||||||
> *:last-child {
|
> *:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
`,
|
`,
|
||||||
itemContent: css`
|
itemContent: css`
|
||||||
padding: ${theme.spacing.xs} ${theme.spacing.sm} ${theme.spacing.sm} ${theme.spacing.sm};
|
padding: ${theme.spacing.xs} ${theme.spacing.sm} ${theme.spacing.sm} ${theme.spacing.sm};
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
@ -1,44 +1,41 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
// Node.closest() polyfill
|
||||||
export namespace DOMUtil {
|
if ('Element' in window && !Element.prototype.closest) {
|
||||||
// Node.closest() polyfill
|
Element.prototype.closest = function(this: any, s: string) {
|
||||||
if ('Element' in window && !Element.prototype.closest) {
|
const matches = (this.document || this.ownerDocument).querySelectorAll(s);
|
||||||
Element.prototype.closest = function(this: any, s: string) {
|
let el = this;
|
||||||
const matches = (this.document || this.ownerDocument).querySelectorAll(s);
|
let i;
|
||||||
let el = this;
|
// eslint-disable-next-line
|
||||||
let i;
|
do {
|
||||||
|
i = matches.length;
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
do {
|
while (--i >= 0 && matches.item(i) !== el) {}
|
||||||
i = matches.length;
|
el = el.parentElement;
|
||||||
// eslint-disable-next-line
|
} while (i < 0 && el);
|
||||||
while (--i >= 0 && matches.item(i) !== el) {}
|
return el;
|
||||||
el = el.parentElement;
|
};
|
||||||
} while (i < 0 && el);
|
}
|
||||||
return el;
|
|
||||||
};
|
export function getPreviousCousin(node: any, selector: string) {
|
||||||
}
|
let sibling = node.parentElement.previousSibling;
|
||||||
|
let el;
|
||||||
export function getPreviousCousin(node: any, selector: string) {
|
while (sibling) {
|
||||||
let sibling = node.parentElement.previousSibling;
|
el = sibling.querySelector(selector);
|
||||||
let el;
|
if (el) {
|
||||||
while (sibling) {
|
return el;
|
||||||
el = sibling.querySelector(selector);
|
}
|
||||||
if (el) {
|
sibling = sibling.previousSibling;
|
||||||
return el;
|
}
|
||||||
}
|
return undefined;
|
||||||
sibling = sibling.previousSibling;
|
}
|
||||||
}
|
|
||||||
return undefined;
|
export function getNextCharacter(global?: any) {
|
||||||
}
|
const selection = (global || window).getSelection();
|
||||||
|
if (!selection || !selection.anchorNode) {
|
||||||
export function getNextCharacter(global?: any) {
|
return null;
|
||||||
const selection = (global || window).getSelection();
|
}
|
||||||
if (!selection || !selection.anchorNode) {
|
|
||||||
return null;
|
const range = selection.getRangeAt(0);
|
||||||
}
|
const text = selection.anchorNode.textContent;
|
||||||
|
const offset = range.startOffset;
|
||||||
const range = selection.getRangeAt(0);
|
return text!.substr(offset, 1);
|
||||||
const text = selection.anchorNode.textContent;
|
|
||||||
const offset = range.startOffset;
|
|
||||||
return text!.substr(offset, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@ export * from './tags';
|
|||||||
export * from './measureText';
|
export * from './measureText';
|
||||||
export { default as ansicolor } from './ansicolor';
|
export { default as ansicolor } from './ansicolor';
|
||||||
|
|
||||||
// Export with a namespace
|
import * as DOMUtil from './dom'; // includes Element.closest polyfil
|
||||||
export { DOMUtil } from './dom'; // includes Element.closest polyfil
|
export { DOMUtil };
|
||||||
|
Loading…
Reference in New Issue
Block a user