@adaptabletools/adaptable
Version:
Powerful AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
199 lines (198 loc) • 11.2 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import * as React from 'react';
import { useEffect, useLayoutEffect, useState } from 'react';
import useProperty from '../utils/useProperty';
import SimpleButton from '../SimpleButton';
import { ACCESS_LEVEL_FULL } from '../../Utilities/Constants/GeneralConstants';
import { DateFormatter } from '../../Utilities/Helpers/DisplayFormatHelper';
import { useDatepickerContext } from './DatepickerContext';
import { AdaptableDateInlineInput } from '../../View/Components/AdaptableInput/AdaptableDateInlineInput';
import { isValid, addYears, endOfYear, startOfYear, addDays, addBusinessDays, } from 'date-fns';
import { Flex } from '../Flex';
import { cn } from '../../lib/utils';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
import { Calendar } from '../ui/calendar';
import { InputGroup, InputGroupAddon } from '../ui/input-group';
import { SingleSelect } from '../NewSelect';
import { targetOwn } from '../twUtils';
const onCaptionKeyDown = (e) => {
if (e.key === 'Escape' || e.key === 'Enter') {
e.stopPropagation();
}
};
const DatepickerSelectPortalContext = React.createContext(null);
function DatepickerYearsDropdown(props) {
const container = React.useContext(DatepickerSelectPortalContext);
const { onChange, options = [], value } = props;
return (_jsx("div", { className: "twa:relative twa:z-10 twa:inline-flex twa:min-w-0 twa:shrink-0", onKeyDown: onCaptionKeyDown, children: _jsx(SingleSelect, { size: "small", className: "twa:min-w-0", items: options, value: value, onValueChange: (v) => onChange?.({ target: { value: v } }), container: container ?? undefined }) }));
}
function DatepickerMonthsDropdown(props) {
const container = React.useContext(DatepickerSelectPortalContext);
const { onChange, options = [], value } = props;
return (_jsx("div", { className: "twa:relative twa:z-10 twa:inline-flex twa:min-w-0 twa:shrink-0", onKeyDown: onCaptionKeyDown, children: _jsx(SingleSelect, { size: "small", className: "twa:min-w-0", items: options.map((o) => ({ label: o.label, value: String(o.value) })), value: String(value), onValueChange: (v) => onChange?.({ target: { value: v } }), container: container ?? undefined }) }));
}
const captionComponents = {
YearsDropdown: DatepickerYearsDropdown,
MonthsDropdown: DatepickerMonthsDropdown,
};
const END_MONTH = endOfYear(addYears(new Date(), 10));
const START_MONTH = startOfYear(addYears(new Date(), -50));
export const Datepicker = React.forwardRef((props, ref) => {
const { dateProps, disabled, style, className, placeholder, showWeekNumber, showOutsideDays, datepickerButtons, showClearButton, autoFocus, showTriggerButton = true, onHide, value: _value, defaultValue: _defaultValue, onChange: _onChange, } = props;
void _value;
void _defaultValue;
void _onChange;
const datepickerContext = useDatepickerContext();
let [value, setValue] = useProperty(props, 'value', undefined, {
onChange: props.onChange,
});
useEffect(() => {
if (!value) {
setMonth(new Date());
}
}, [value]);
const [month, setMonth] = useState(value ?? new Date());
const updateValue = (next) => {
setValue(next);
setMonth(next ?? new Date());
};
const inputValue = DateFormatter(value, { Pattern: dateProps.format }) ?? '';
const triggerRef = React.useRef(null);
const popupRef = React.useRef(null);
const isInsideDatepicker = React.useCallback((node) => {
if (!node)
return false;
if (node === document.body || node === document.documentElement) {
return true;
}
if (triggerRef.current?.contains(node)) {
return true;
}
const root = popupRef.current;
if (!root) {
return false;
}
if (root === node || root.contains(node)) {
return true;
}
if (node instanceof Element && root instanceof Element) {
let p = root.parentElement;
while (p) {
if (p === node) {
return true;
}
p = p.parentElement;
}
}
return false;
}, []);
const [open, setOpen] = useState(false);
const closedAtRef = React.useRef(0);
const tabFromOverlayRef = React.useRef(false);
const closedByTabOutRef = React.useRef(false);
const setPopoverOpen = React.useCallback((next, keyboardEventKey) => {
if (next) {
setOpen(true);
datepickerContext?.onShow?.();
}
else {
closedAtRef.current = Date.now();
setOpen(false);
datepickerContext?.onHide?.(keyboardEventKey);
onHide?.();
}
}, [datepickerContext, onHide]);
const handleOpenChange = React.useCallback((next, eventDetails) => {
if (next) {
setPopoverOpen(true);
return;
}
const reason = eventDetails?.reason;
if (reason === 'escape-key') {
setPopoverOpen(false, 'Escape');
return;
}
if (reason === 'outside-press' || reason === 'close-press') {
setPopoverOpen(false);
return;
}
}, [setPopoverOpen]);
useEffect(() => {
if (!open)
return;
const onFocusIn = (e) => {
const target = e.target;
if (tabFromOverlayRef.current) {
const popup = popupRef.current;
if (popup && target && !popup.contains(target) && popup !== target) {
tabFromOverlayRef.current = false;
closedByTabOutRef.current = true;
setPopoverOpen(false);
}
return;
}
if (!isInsideDatepicker(target)) {
setPopoverOpen(false);
}
};
document.addEventListener('focusin', onFocusIn);
return () => document.removeEventListener('focusin', onFocusIn);
}, [open, isInsideDatepicker, setPopoverOpen]);
useLayoutEffect(() => {
if (!open || disabled || autoFocus) {
return;
}
const id = requestAnimationFrame(() => {
popupRef.current?.focus({ preventScroll: true });
});
return () => cancelAnimationFrame(id);
}, [open, disabled, autoFocus]);
const clearValue = () => updateValue(undefined);
const renderButton = (label, onClick) => (_jsx(SimpleButton, { onClick: onClick, className: "twa:m-[2px]", children: label }));
const todayDate = new Date();
const footerButtonsMap = {
clear: renderButton('Clear', () => clearValue()),
close: renderButton('Close', () => setPopoverOpen(false)),
today: renderButton('Today', () => updateValue(todayDate)),
tomorrow: renderButton('Tomorrow', () => updateValue(addDays(todayDate, 1))),
yesterday: renderButton('Yesterday', () => updateValue(addDays(todayDate, -1))),
nextWorkday: renderButton('Next Workday', () => updateValue(addBusinessDays(todayDate, 1))),
prevWorkday: renderButton('Prev Workday', () => updateValue(addBusinessDays(todayDate, -1))),
'-': _jsx("div", { style: { flex: '1 1 1%' } }),
'|': _jsx("hr", { style: { width: '100%', height: 0, margin: 0, border: 'none' } }),
};
const footerButtons = datepickerButtons.map((buttonKey, index) => React.cloneElement(footerButtonsMap[buttonKey], { key: index }));
const clearButton = showClearButton !== false ? (_jsx(SimpleButton, { "data-name": "clear", variant: "text", tooltip: "Clear", iconSize: 20, className: "twa:p-0", icon: "close", onMouseDown: (e) => {
e.preventDefault();
clearValue();
console.log('onMouseDown', e);
}, onClick: (e) => {
console.log('onClick', e);
e.stopPropagation();
clearValue();
triggerRef.current?.firstChild?.focus();
}, accessLevel: ACCESS_LEVEL_FULL })) : null;
const calendarButton = showTriggerButton !== false ? (_jsx(SimpleButton, { disabled: disabled, variant: "text", icon: "calendar", tooltip: "Date", iconSize: 20, className: "twa:p-0", type: "button" })) : null;
const [selectPortalRoot, setSelectPortalRoot] = React.useState(null);
const popupRefCallback = React.useCallback((el) => {
popupRef.current = el;
setSelectPortalRoot(el);
}, []);
return (_jsxs(Popover, { open: open, onOpenChange: handleOpenChange, children: [_jsx(PopoverTrigger, { nativeButton: false, render: _jsxs(InputGroup, { ref: triggerRef, style: {
borderRadius: style?.borderRadius,
width: style?.width,
maxWidth: style?.maxWidth,
border: style?.border,
}, tabIndex: -1, className: cn('ab-Datepicker twa:flex twa:flex-row twa:shadow-none', 'twa:bg-input-background', targetOwn.focusWithinOutline, open && 'twa:border-ring twa:ring-3 twa:ring-ring/50', className), children: [_jsx(AdaptableDateInlineInput, { ref: ref, "data-slot": "input-group-control", value: inputValue, placeholder: placeholder ?? '', onChange: (v) => {
const date = new Date(v);
if (isValid(date)) {
updateValue(date);
}
}, onKeyDown: (e) => {
if (e.key === 'Enter' && !open && !disabled) {
e.preventDefault();
setPopoverOpen(true);
}
}, className: "twa:h-auto twa:min-w-0 twa:outline-none twa:min-h-auto twa:py-0 twa:flex-1 twa:rounded-none twa:border-0 twa:bg-transparent twa:shadow-none twa:ring-0 twa:focus-visible:ring-0 twa:dark:bg-transparent", style: style, disabled: disabled }), _jsxs(InputGroupAddon, { align: "inline-end", children: [inputValue ? clearButton : null, calendarButton] })] }) }), _jsx(PopoverContent, { className: "twa:w-auto twa:p-0", align: "start", sideOffset: 8, children: _jsx("div", { ref: popupRefCallback, tabIndex: -1, role: "region", "aria-label": "Calendar", className: cn('twa:relative twa:isolate twa:overflow-visible twa:outline-none twa:rounded-standard', targetOwn.focusWithinOutline), onKeyDown: () => {
}, children: _jsx(DatepickerSelectPortalContext.Provider, { value: selectPortalRoot, children: _jsx(Calendar, { fixedWeeks: true, autoFocus: !!autoFocus, showWeekNumber: showWeekNumber, showOutsideDays: showOutsideDays, mode: "single", captionLayout: "dropdown", month: month && !isNaN(month.getTime()) ? month : new Date(), selected: value, onMonthChange: setMonth, onSelect: updateValue, startMonth: START_MONTH, endMonth: END_MONTH, locale: dateProps.locale, components: captionComponents, footer: _jsx(Flex, { justifyContent: "space-between", className: "twa:mt-2", flexWrap: 'wrap', children: footerButtons }) }) }) }) })] }));
});