UNPKG

@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
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 }) }) }) }) })] })); });