UNPKG

@elastic/eui

Version:

Elastic UI Component Library

248 lines (243 loc) 10.7 kB
/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0 and the Server Side Public License, v 1; you may not use this file except * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ import React from 'react'; import dateMath from '@elastic/datemath'; import moment from 'moment'; import { usePrettyInterval } from './pretty_interval'; import { isRelativeToNow } from './relative_utils'; import { EuiButtonGroupButton } from '../../button/button_group/button_group_button'; import { euiButtonGroupButtonsStyles } from '../../button/button_group/button_group.styles'; import { useEuiMemoizedStyles, useGeneratedHtmlId } from '../../../services'; import { useEuiI18n } from '../../i18n'; import { jsx as ___EmotionJSX } from "@emotion/react"; export var ZOOM_FACTOR_DEFAULT = 0.5; export var ZOOM_DELTA_FALLBACK_MS = 500; /** * Button group with time window controls for shifting the time window * forwards and backwards, and zooming out. */ export var EuiTimeWindowButtons = function EuiTimeWindowButtons(_ref) { var applyTime = _ref.applyTime, start = _ref.start, end = _ref.end, compressed = _ref.compressed, isDisabled = _ref.isDisabled, _ref$showZoomOut = _ref.showZoomOut, showZoomOut = _ref$showZoomOut === void 0 ? true : _ref$showZoomOut, _ref$showZoomIn = _ref.showZoomIn, showZoomIn = _ref$showZoomIn === void 0 ? false : _ref$showZoomIn, _ref$showShiftArrows = _ref.showShiftArrows, showShiftArrows = _ref$showShiftArrows === void 0 ? true : _ref$showShiftArrows, _ref$zoomFactor = _ref.zoomFactor, zoomFactor = _ref$zoomFactor === void 0 ? ZOOM_FACTOR_DEFAULT : _ref$zoomFactor; var buttonColor = 'text'; var buttonSize = compressed ? 's' : 'm'; var iconSize = 'm'; var styles = useEuiMemoizedStyles(euiButtonGroupButtonsStyles); var _useEuiTimeWindow = useEuiTimeWindow(start, end, applyTime, { zoomFactor: zoomFactor }), displayInterval = _useEuiTimeWindow.displayInterval, isInvalid = _useEuiTimeWindow.isInvalid, stepForward = _useEuiTimeWindow.stepForward, stepBackward = _useEuiTimeWindow.stepBackward, expandWindow = _useEuiTimeWindow.expandWindow, shrinkWindow = _useEuiTimeWindow.shrinkWindow, isWindowDurationZero = _useEuiTimeWindow.isWindowDurationZero; var previousDescription = useEuiI18n('euiTimeWindowButtons.previousDescription', 'Previous {displayInterval}', { displayInterval: displayInterval }); var nextDescription = useEuiI18n('euiTimeWindowButtons.nextDescription', 'Next {displayInterval}', { displayInterval: displayInterval }); var invalidShiftDescription = useEuiI18n('euiTimeWindowButtons.invalidShiftLabel', 'Cannot shift invalid time window'); var invalidZoomInDescription = useEuiI18n('euiTimeWindowButtons.invalidZoomInLabel', 'Cannot zoom in invalid time window'); var cannotZoomInDescription = useEuiI18n('euiTimeWindowButtons.cannotZoomInLabel', 'Cannot zoom in any further'); var invalidZoomOutDescription = useEuiI18n('euiTimeWindowButtons.invalidZoomOutLabel', 'Cannot zoom out invalid time window'); var previousId = useGeneratedHtmlId({ prefix: 'previous' }); var previousLabel = useEuiI18n('euiTimeWindowButtons.previousLabel', 'Previous'); var previousTooltipContent = isInvalid ? invalidShiftDescription : previousDescription; var zoomInId = useGeneratedHtmlId({ prefix: 'zoom_in' }); var zoomInLabel = useEuiI18n('euiTimeWindowButtons.zoomInLabel', 'Zoom in'); var zoomInTooltipContent = isInvalid ? invalidZoomInDescription : isWindowDurationZero ? cannotZoomInDescription : zoomInLabel; var zoomOutId = useGeneratedHtmlId({ prefix: 'zoom_out' }); var zoomOutLabel = useEuiI18n('euiTimeWindowButtons.zoomOutLabel', 'Zoom out'); var zoomOutTooltipContent = isInvalid ? invalidZoomOutDescription : zoomOutLabel; var nextId = useGeneratedHtmlId({ prefix: 'next' }); var nextLabel = useEuiI18n('euiTimeWindowButtons.nextLabel', 'Next'); var nextTooltipContent = isInvalid ? invalidShiftDescription : nextDescription; if (!showZoomIn && !showZoomOut && !showShiftArrows) return null; return ___EmotionJSX("div", { className: "euiSuperDatePicker__timeWindowButtons", css: [styles.euiButtonGroup__buttons, styles[buttonSize], ";label:EuiTimeWindowButtons;"], "data-test-subj": "timeWindowButtons" }, showShiftArrows && ___EmotionJSX(EuiButtonGroupButton, { id: previousId, "data-test-subj": "timeWindowButtonsPrevious", label: previousLabel, title: "", toolTipContent: !isDisabled && previousTooltipContent, color: buttonColor, size: buttonSize, iconType: "chevronSingleLeft", iconSize: iconSize, isIconOnly: true, isDisabled: isWindowDurationZero || isDisabled, onClick: stepBackward }), showZoomIn && ___EmotionJSX(EuiButtonGroupButton, { id: zoomInId, "data-test-subj": "timeWindowButtonsZoomIn", label: zoomInLabel, title: "", toolTipContent: !isDisabled && zoomInTooltipContent, toolTipProps: { disableScreenReaderOutput: zoomInLabel === zoomInTooltipContent }, color: buttonColor, size: buttonSize, iconType: "magnifyPlus", iconSize: iconSize, isIconOnly: true, isDisabled: isWindowDurationZero || isDisabled, onClick: shrinkWindow }), showZoomOut && ___EmotionJSX(EuiButtonGroupButton, { id: zoomOutId, "data-test-subj": "timeWindowButtonsZoomOut", label: zoomOutLabel, title: "", toolTipContent: !isDisabled && zoomOutTooltipContent, toolTipProps: { disableScreenReaderOutput: zoomOutLabel === zoomOutTooltipContent }, color: buttonColor, size: buttonSize, iconType: "magnifyMinus", iconSize: iconSize, isIconOnly: true, isDisabled: isDisabled, onClick: expandWindow }), showShiftArrows && ___EmotionJSX(EuiButtonGroupButton, { id: nextId, "data-test-subj": "timeWindowButtonsNext", label: nextLabel, title: "", toolTipContent: !isDisabled && nextTooltipContent, color: buttonColor, size: buttonSize, iconType: "chevronSingleRight", iconSize: iconSize, isIconOnly: true, isDisabled: isWindowDurationZero || isDisabled, onClick: stepForward })); }; /** * Partly adapted from date_picker/super_date_picker/quick_select_popover/quick_select.tsx */ export function useEuiTimeWindow(start, end, apply, options) { var _options$zoomFactor; var min = dateMath.parse(start); /* `roundUp: true` will result in an "inclusive" time (e.g. 23:59:59.999 for 'now/d'). It only changes the value for relative expressions (e.g. 'now/d') but not absolute ISO strings. */ var max = dateMath.parse(end, { roundUp: true }); var isInvalid = !min || !min.isValid() || !max || !max.isValid(); /* An end at .999ms is always considered an inclusive boundary (either as result of `roundUp: true` or entered manually). To avoid a 1ms drift on every time window or zoom step, windowDuration has to be increased by 1ms. This ensures the window is always at a clean boundary (e.g. 00:00:00.000 - 23:59:59.999). */ var isInclusiveBoundary = !isInvalid && max.milliseconds() === 999; var endBoundary = !isInvalid ? isInclusiveBoundary ? moment(max).add(1, 'ms') : moment(max) : null; var windowDuration = isInvalid || !endBoundary ? -1 : endBoundary.diff(min); var isWindowDurationZero = windowDuration === 0; var zoomFactor = getPercentageMultiplier((_options$zoomFactor = options === null || options === void 0 ? void 0 : options.zoomFactor) !== null && _options$zoomFactor !== void 0 ? _options$zoomFactor : ZOOM_FACTOR_DEFAULT); var zoomDelta = windowDuration * (zoomFactor / 2); // Gets added to each end, that's why it's split in half var prettyInterval = usePrettyInterval(false, windowDuration); var displayInterval = isInvalid ? '' : prettyInterval; if (!isInvalid && !isRelativeToNow(start, end) && !isExactMinuteRange(windowDuration)) { displayInterval = "~".concat(displayInterval); } return { displayInterval: displayInterval, isInvalid: isInvalid, stepForward: stepForward, stepBackward: stepBackward, expandWindow: expandWindow, shrinkWindow: shrinkWindow, isWindowDurationZero: isWindowDurationZero }; function stepForward() { if (isInvalid || isWindowDurationZero) return; apply({ /* Prevent 1ms drifts for inclusive boundaries by using the exclusive max (+ 1ms) as the start of the next window (e.g. 00:00:00.000 instead of 23:59:59.999) */ start: (isInclusiveBoundary ? endBoundary // `!` is safe here because we early return on `isInvalid` : moment(max)).toISOString(), end: moment(max).add(windowDuration, 'ms').toISOString() }); } function stepBackward() { if (isInvalid || isWindowDurationZero) return; apply({ start: moment(min).subtract(windowDuration, 'ms').toISOString(), /* Prevent 1ms drifts for inclusive boundaries by using the exclusive min (- 1ms) as the end of the previous window (e.g. 23:59:59.999 instead of 00:00:00.000) */ end: (isInclusiveBoundary ? moment(min).subtract(1, 'ms') : moment(min)).toISOString() }); } function expandWindow() { if (isInvalid) return; // when the window is 0 it'll remain 0 unless we help it a little var addition = zoomDelta === 0 ? ZOOM_DELTA_FALLBACK_MS : zoomDelta; apply({ start: moment(min).subtract(addition, 'ms').toISOString(), end: moment(max).add(addition, 'ms').toISOString() }); } function shrinkWindow() { if (isInvalid || isWindowDurationZero) return; apply({ start: moment(min).add(zoomDelta, 'ms').toISOString(), end: moment(max).subtract(zoomDelta, 'ms').toISOString() }); } } /** * Convert strings with % to a multiplier e.g. "50%" = 0.5 * Strings without % are returned as-is as number */ function getPercentageMultiplier(value) { if (typeof value === 'string' && value.includes('%')) { var parsed = parseFloat(value.replace('%', '').trim()); if (isNaN(parsed)) { throw new TypeError('Invalid percentage string'); } return parsed / 100; } var result = typeof value === 'number' ? value : parseFloat(String(value)); if (isNaN(result)) { throw new TypeError('Please provide a valid number or percentage string e.g. "25%"'); } return result; } /** * Useful to determine whether to show the tilde in the display */ function isExactMinuteRange(diffMs) { // 60 * 1000 = ms per minute return diffMs % (60 * 1000) === 0; }