@cwellt_software/cwellt-reactjs-lib 1.4.11 → 1.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +728 -2
- package/dist/index.css +2 -2
- package/dist/index.d.ts +205 -12
- package/dist/index.es.js +726 -4
- package/dist/src/components/control/action/search/CwSearchAsync.d.ts +105 -0
- package/dist/src/components/control/action/search/CwSearchAsync.d.ts.map +1 -0
- package/dist/src/components/control/input/new-dates/CwDatePickerTemporal.d.ts +10 -1
- package/dist/src/components/control/input/new-dates/CwDatePickerTemporal.d.ts.map +1 -1
- package/dist/src/components/control/input/new-dates/CwDateRangePickerTemporal.d.ts +75 -0
- package/dist/src/components/control/input/new-dates/CwDateRangePickerTemporal.d.ts.map +1 -0
- package/dist/src/components/control/input/new-dates/dateRangePresetsTemporal.d.ts +16 -0
- package/dist/src/components/control/input/new-dates/dateRangePresetsTemporal.d.ts.map +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/playground/pages/DatePickerTemporalPage.d.ts.map +1 -1
- package/dist/src/playground/pages/SelectorsPage.d.ts.map +1 -1
- package/dist/test/components/control/input/new-dates/dateRangePresetsTemporal.test.d.ts +2 -0
- package/dist/test/components/control/input/new-dates/dateRangePresetsTemporal.test.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.es.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import React__default, { useState, useRef, useEffect, useCallback, useLayoutEffect, createContext, useContext, cloneElement, useMemo, forwardRef, useImperativeHandle, createElement, memo, Fragment as Fragment$1, Component } from 'react';
|
|
3
|
+
import React__default, { useState, useRef, useEffect, useCallback, useLayoutEffect, createContext, useContext, cloneElement, useMemo, forwardRef, useImperativeHandle, createElement, useId, memo, Fragment as Fragment$1, Component } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import { createRoot } from 'react-dom/client';
|
|
6
6
|
import Draggable from 'react-draggable';
|
|
@@ -5017,6 +5017,265 @@ function CwSearchInput(optionsProps) {
|
|
|
5017
5017
|
return (jsxs("div", { className: `cw-search-input ${className || ""}`, style: style, id: id, ...otherProps, "data-direction": direction, children: [jsxs(CwAlign, { ...alignProps, children: [labelProps && (jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxs("div", { className: "cw-search-input-wrapper", ref: wrapperRef, children: [jsx("input", { type: "text", value: searchText, onChange: handleInputChange, onFocus: handleInputFocus, onBlur: handleInputBlur, placeholder: placeholder, className: "cw-input-search", disabled: disabled }), isLoading && (jsx("div", { className: "cw-search-input-loading", children: jsx(CwIcon, { iconId: "spinner" }) })), jsx("div", { className: "cw-search-input-icons", children: searchText && !disabled && !isLoading ? (jsx(CwButton, { type: "button", onClick: handleClearClick, "aria-label": "Clear search", variant: "icon", icon: "close", color: "neutral" })) : (iconProps ? jsx(CwIcon, { ...iconProps }) : jsx(CwIcon, { iconId: "search" })) })] })] }), renderPanel(jsx("div", { ref: panelRef, className: dropdownStyles.dropdown, style: panelStyle, children: jsx("ul", { children: filteredOptions.map((option) => (jsx("li", { className: dropdownStyles.option, onClick: () => handleOptionSelect(option), onMouseDown: (e) => e.preventDefault(), children: renderOption ? renderOption(option) : option.description }, option.id))) }) }))] }));
|
|
5018
5018
|
}
|
|
5019
5019
|
|
|
5020
|
+
/**
|
|
5021
|
+
* Generic API-backed search input. Fetches matching options from a caller-supplied `onSearch`
|
|
5022
|
+
* callback as the user types (debounced), renders them in a portal-based dropdown with keyboard
|
|
5023
|
+
* navigation, and commits the selected id via `onChange`.
|
|
5024
|
+
*
|
|
5025
|
+
* All domain logic — API URLs, auth/config, display formatting, provider/secondary lookups — lives
|
|
5026
|
+
* in the caller's callbacks, so this component stays application-agnostic. It is the base for thin
|
|
5027
|
+
* wrappers such as `FindAirport` / `FindCrewmember`.
|
|
5028
|
+
*
|
|
5029
|
+
* Improvements over the legacy `CwFindAirport` / `CwFindCrewmember`: an `AbortController` cancels
|
|
5030
|
+
* superseded in-flight requests (fixes out-of-order results), and `minChars` / `debounceMs` are
|
|
5031
|
+
* configurable.
|
|
5032
|
+
*
|
|
5033
|
+
* @example
|
|
5034
|
+
* <CwSearchAsync
|
|
5035
|
+
* onSearch={(text, signal) => repo.search(text, signal)}
|
|
5036
|
+
* onChange={(id) => setSelectedId(id)}
|
|
5037
|
+
* labelProps={{ text: 'Airport' }}
|
|
5038
|
+
* icon={{ iconId: 'control-tower' }}
|
|
5039
|
+
* />
|
|
5040
|
+
*/
|
|
5041
|
+
const CwSearchAsync = ({ onSearch, onChange, value, onResolveInitial, onResolveSelection, icon, placeholder = "Search…", minChars = 2, debounceMs = 300, disabled = false, required = false, initialDisplayText, searchTrigger, labelProps, alignProps, width, className = "", }) => {
|
|
5042
|
+
// State
|
|
5043
|
+
const [inputValue, setInputValue] = useState("");
|
|
5044
|
+
const [options, setOptions] = useState([]);
|
|
5045
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
5046
|
+
const [isInitialLoading, setIsInitialLoading] = useState(false);
|
|
5047
|
+
const [showDropdown, setShowDropdown] = useState(false);
|
|
5048
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
5049
|
+
const [tooltipText, setTooltipText] = useState("");
|
|
5050
|
+
// Refs
|
|
5051
|
+
const inputRef = useRef(null);
|
|
5052
|
+
const wrapperRef = useRef(null);
|
|
5053
|
+
const debounceRef = useRef();
|
|
5054
|
+
const abortRef = useRef(null);
|
|
5055
|
+
const lastQueryRef = useRef("");
|
|
5056
|
+
const didMountRef = useRef(false);
|
|
5057
|
+
const lastResolvedValueRef = useRef(undefined);
|
|
5058
|
+
const initialResolveAbortRef = useRef(null);
|
|
5059
|
+
// Stable ids wiring the combobox input to its listbox popup for assistive tech.
|
|
5060
|
+
const listboxId = useId();
|
|
5061
|
+
const optionId = (index) => `${listboxId}-opt-${index}`;
|
|
5062
|
+
// Cancel a pending initial-value resolve when the user takes over (typing / clearing / reset),
|
|
5063
|
+
// so a slow resolve can't overwrite newer input and the loading flag never sticks.
|
|
5064
|
+
const cancelInitialResolve = useCallback(() => {
|
|
5065
|
+
initialResolveAbortRef.current?.abort();
|
|
5066
|
+
initialResolveAbortRef.current = null;
|
|
5067
|
+
setIsInitialLoading(false);
|
|
5068
|
+
}, []);
|
|
5069
|
+
const { panelRef, panelStyle, renderPanel } = useDropdownPortal({
|
|
5070
|
+
anchorRef: wrapperRef,
|
|
5071
|
+
isOpen: showDropdown && options.length > 0,
|
|
5072
|
+
onClose: () => { setShowDropdown(false); setHighlightedIndex(-1); },
|
|
5073
|
+
});
|
|
5074
|
+
// Core search runner — aborts superseded requests and drops stale responses.
|
|
5075
|
+
const runSearch = useCallback(async (text) => {
|
|
5076
|
+
abortRef.current?.abort();
|
|
5077
|
+
if (text.length < minChars) {
|
|
5078
|
+
setOptions([]);
|
|
5079
|
+
setShowDropdown(false);
|
|
5080
|
+
setHighlightedIndex(-1);
|
|
5081
|
+
return;
|
|
5082
|
+
}
|
|
5083
|
+
const controller = new AbortController();
|
|
5084
|
+
abortRef.current = controller;
|
|
5085
|
+
lastQueryRef.current = text;
|
|
5086
|
+
setIsLoading(true);
|
|
5087
|
+
try {
|
|
5088
|
+
const results = await onSearch(text, controller.signal);
|
|
5089
|
+
if (controller.signal.aborted)
|
|
5090
|
+
return; // superseded → drop
|
|
5091
|
+
setOptions(results);
|
|
5092
|
+
setShowDropdown(results.length > 0); // empty → close silently (dark cockpit)
|
|
5093
|
+
setHighlightedIndex(-1);
|
|
5094
|
+
}
|
|
5095
|
+
catch (error) {
|
|
5096
|
+
if (controller.signal.aborted)
|
|
5097
|
+
return; // aborts reject with AbortError → ignore
|
|
5098
|
+
console.error("CwSearchAsync onSearch failed:", error);
|
|
5099
|
+
setOptions([]);
|
|
5100
|
+
setShowDropdown(false);
|
|
5101
|
+
}
|
|
5102
|
+
finally {
|
|
5103
|
+
if (abortRef.current === controller) {
|
|
5104
|
+
setIsLoading(false);
|
|
5105
|
+
abortRef.current = null;
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
}, [minChars, onSearch]);
|
|
5109
|
+
// Keep a stable handle to the latest runSearch for the searchTrigger effect.
|
|
5110
|
+
const runSearchRef = useRef(runSearch);
|
|
5111
|
+
useEffect(() => { runSearchRef.current = runSearch; });
|
|
5112
|
+
const debouncedSearch = useCallback((text) => {
|
|
5113
|
+
if (debounceRef.current)
|
|
5114
|
+
window.clearTimeout(debounceRef.current);
|
|
5115
|
+
// Abort eagerly on every keystroke so a fast typist never has two live fetches.
|
|
5116
|
+
abortRef.current?.abort();
|
|
5117
|
+
debounceRef.current = window.setTimeout(() => runSearch(text), debounceMs);
|
|
5118
|
+
}, [runSearch, debounceMs]);
|
|
5119
|
+
const handleInputChange = (e) => {
|
|
5120
|
+
const newValue = e.target.value;
|
|
5121
|
+
setInputValue(newValue);
|
|
5122
|
+
if (newValue !== inputValue) {
|
|
5123
|
+
cancelInitialResolve();
|
|
5124
|
+
debouncedSearch(newValue);
|
|
5125
|
+
}
|
|
5126
|
+
};
|
|
5127
|
+
const handleOptionSelect = async (optionId) => {
|
|
5128
|
+
const picked = options.find(o => o.id === optionId);
|
|
5129
|
+
if (!picked)
|
|
5130
|
+
return;
|
|
5131
|
+
setShowDropdown(false);
|
|
5132
|
+
let committed = picked;
|
|
5133
|
+
if (onResolveSelection) {
|
|
5134
|
+
abortRef.current?.abort();
|
|
5135
|
+
const controller = new AbortController();
|
|
5136
|
+
abortRef.current = controller;
|
|
5137
|
+
try {
|
|
5138
|
+
const resolved = await onResolveSelection(picked, controller.signal);
|
|
5139
|
+
if (controller.signal.aborted)
|
|
5140
|
+
return;
|
|
5141
|
+
committed = resolved;
|
|
5142
|
+
setOptions([resolved]);
|
|
5143
|
+
}
|
|
5144
|
+
catch (error) {
|
|
5145
|
+
if (controller.signal.aborted)
|
|
5146
|
+
return;
|
|
5147
|
+
console.error("CwSearchAsync onResolveSelection failed:", error);
|
|
5148
|
+
return; // abort the commit, mirroring the legacy provider-airport behaviour
|
|
5149
|
+
}
|
|
5150
|
+
finally {
|
|
5151
|
+
if (abortRef.current === controller)
|
|
5152
|
+
abortRef.current = null;
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
5155
|
+
setInputValue(committed.inputLabel ?? committed.label);
|
|
5156
|
+
setTooltipText(committed.label);
|
|
5157
|
+
onChange(committed.id);
|
|
5158
|
+
};
|
|
5159
|
+
const handleKeyDown = (e) => {
|
|
5160
|
+
if (!showDropdown || options.length === 0)
|
|
5161
|
+
return;
|
|
5162
|
+
switch (e.key) {
|
|
5163
|
+
case "ArrowDown":
|
|
5164
|
+
e.preventDefault();
|
|
5165
|
+
setHighlightedIndex(prev => (prev < options.length - 1 ? prev + 1 : 0));
|
|
5166
|
+
break;
|
|
5167
|
+
case "ArrowUp":
|
|
5168
|
+
e.preventDefault();
|
|
5169
|
+
setHighlightedIndex(prev => (prev > 0 ? prev - 1 : options.length - 1));
|
|
5170
|
+
break;
|
|
5171
|
+
case "Enter":
|
|
5172
|
+
e.preventDefault();
|
|
5173
|
+
if (highlightedIndex >= 0 && highlightedIndex < options.length) {
|
|
5174
|
+
handleOptionSelect(options[highlightedIndex].id);
|
|
5175
|
+
}
|
|
5176
|
+
break;
|
|
5177
|
+
case "Escape":
|
|
5178
|
+
setShowDropdown(false);
|
|
5179
|
+
setHighlightedIndex(-1);
|
|
5180
|
+
break;
|
|
5181
|
+
}
|
|
5182
|
+
};
|
|
5183
|
+
const handleInputFocus = () => {
|
|
5184
|
+
if (options.length > 0)
|
|
5185
|
+
setShowDropdown(true);
|
|
5186
|
+
};
|
|
5187
|
+
const handleClear = () => {
|
|
5188
|
+
if (debounceRef.current)
|
|
5189
|
+
window.clearTimeout(debounceRef.current);
|
|
5190
|
+
abortRef.current?.abort();
|
|
5191
|
+
cancelInitialResolve();
|
|
5192
|
+
setInputValue("");
|
|
5193
|
+
setTooltipText("");
|
|
5194
|
+
setOptions([]);
|
|
5195
|
+
setShowDropdown(false);
|
|
5196
|
+
lastQueryRef.current = "";
|
|
5197
|
+
onChange(null);
|
|
5198
|
+
inputRef.current?.focus();
|
|
5199
|
+
};
|
|
5200
|
+
// Resolve a preselected value to display text.
|
|
5201
|
+
useEffect(() => {
|
|
5202
|
+
if (value == null)
|
|
5203
|
+
return;
|
|
5204
|
+
if (initialDisplayText) {
|
|
5205
|
+
setInputValue(initialDisplayText);
|
|
5206
|
+
setTooltipText(initialDisplayText);
|
|
5207
|
+
lastResolvedValueRef.current = value;
|
|
5208
|
+
return;
|
|
5209
|
+
}
|
|
5210
|
+
if (!onResolveInitial)
|
|
5211
|
+
return;
|
|
5212
|
+
if (lastResolvedValueRef.current === value)
|
|
5213
|
+
return; // already resolved this value
|
|
5214
|
+
const controller = new AbortController();
|
|
5215
|
+
initialResolveAbortRef.current = controller;
|
|
5216
|
+
setIsInitialLoading(true);
|
|
5217
|
+
onResolveInitial(value, controller.signal)
|
|
5218
|
+
.then(opt => {
|
|
5219
|
+
if (controller.signal.aborted)
|
|
5220
|
+
return;
|
|
5221
|
+
lastResolvedValueRef.current = value; // mark resolved only on success
|
|
5222
|
+
setOptions([opt]);
|
|
5223
|
+
setInputValue(opt.inputLabel ?? opt.label);
|
|
5224
|
+
setTooltipText(opt.label);
|
|
5225
|
+
})
|
|
5226
|
+
.catch(error => {
|
|
5227
|
+
if (!controller.signal.aborted)
|
|
5228
|
+
console.error("CwSearchAsync onResolveInitial failed:", error);
|
|
5229
|
+
})
|
|
5230
|
+
.finally(() => {
|
|
5231
|
+
// Clear the spinner only if this resolve is still the current one (not superseded/cancelled).
|
|
5232
|
+
if (initialResolveAbortRef.current === controller) {
|
|
5233
|
+
setIsInitialLoading(false);
|
|
5234
|
+
initialResolveAbortRef.current = null;
|
|
5235
|
+
}
|
|
5236
|
+
});
|
|
5237
|
+
return () => controller.abort();
|
|
5238
|
+
}, [value, initialDisplayText, onResolveInitial]);
|
|
5239
|
+
// Reset when the controlled value is cleared by the parent.
|
|
5240
|
+
useEffect(() => {
|
|
5241
|
+
if (value == null) {
|
|
5242
|
+
if (debounceRef.current)
|
|
5243
|
+
window.clearTimeout(debounceRef.current);
|
|
5244
|
+
abortRef.current?.abort();
|
|
5245
|
+
cancelInitialResolve();
|
|
5246
|
+
setInputValue("");
|
|
5247
|
+
setTooltipText("");
|
|
5248
|
+
setOptions([]);
|
|
5249
|
+
setShowDropdown(false);
|
|
5250
|
+
setHighlightedIndex(-1);
|
|
5251
|
+
lastQueryRef.current = "";
|
|
5252
|
+
lastResolvedValueRef.current = undefined;
|
|
5253
|
+
}
|
|
5254
|
+
}, [value, cancelInitialResolve]);
|
|
5255
|
+
// Re-run the last query when searchTrigger changes (skip first mount).
|
|
5256
|
+
useEffect(() => {
|
|
5257
|
+
if (!didMountRef.current) {
|
|
5258
|
+
didMountRef.current = true;
|
|
5259
|
+
return;
|
|
5260
|
+
}
|
|
5261
|
+
if (searchTrigger === undefined)
|
|
5262
|
+
return;
|
|
5263
|
+
if (lastQueryRef.current)
|
|
5264
|
+
runSearchRef.current(lastQueryRef.current);
|
|
5265
|
+
}, [searchTrigger]);
|
|
5266
|
+
// Cleanup on unmount.
|
|
5267
|
+
useEffect(() => () => {
|
|
5268
|
+
if (debounceRef.current)
|
|
5269
|
+
window.clearTimeout(debounceRef.current);
|
|
5270
|
+
abortRef.current?.abort();
|
|
5271
|
+
}, []);
|
|
5272
|
+
const direction = alignProps?.flexDirection || "row";
|
|
5273
|
+
return (jsxs("div", { className: `cw-search-input ${className}`, style: {
|
|
5274
|
+
...(width ? { width } : {}),
|
|
5275
|
+
...(labelProps?.labelWidth ? { "--label-width": labelProps.labelWidth } : {}),
|
|
5276
|
+
}, "data-direction": direction, children: [jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxs("div", { className: "cw-search-input-wrapper", ref: wrapperRef, children: [jsx("input", { ref: inputRef, type: "text", value: inputValue, onChange: handleInputChange, onKeyDown: handleKeyDown, onFocus: handleInputFocus, placeholder: isInitialLoading ? "Loading…" : placeholder, disabled: disabled, required: required, autoComplete: "off", role: "combobox", "aria-haspopup": "listbox", "aria-expanded": showDropdown, "aria-controls": listboxId, "aria-activedescendant": highlightedIndex >= 0 ? optionId(highlightedIndex) : undefined, title: tooltipText }), (isLoading || isInitialLoading) && (jsx("div", { className: "cw-search-input-loading", children: jsx(CwIcon, { iconId: "spinner" }) })), jsx("div", { className: "cw-search-input-icons", children: inputValue && !disabled && !isInitialLoading ? (jsx(CwButton, { type: "button", onClick: handleClear, "aria-label": "Clear selection", variant: "icon", icon: "close", color: "neutral" })) : (icon ? jsx(CwIcon, { ...icon }) : jsx(CwIcon, { iconId: "search" })) })] })] }), renderPanel(jsx("div", { ref: panelRef, id: listboxId, className: dropdownStyles.dropdown, style: panelStyle, role: "listbox", children: jsx("ul", { children: options.map((option, index) => (jsx("li", { id: optionId(index), className: `${dropdownStyles.option}${index === highlightedIndex ? ` ${dropdownStyles.optionFocused}` : ""}`, onClick: () => handleOptionSelect(option.id), onMouseDown: (e) => e.preventDefault(), onMouseEnter: () => setHighlightedIndex(index), role: "option", "aria-selected": index === highlightedIndex, children: option.subLabel ? (jsxs("span", { className: "cw-search-async-option-lines", children: [jsx("span", { children: option.label }), jsx("span", { className: "cw-search-async-option-sub", children: option.subLabel })] })) : (option.label) }, option.id))) }) }))] }));
|
|
5277
|
+
};
|
|
5278
|
+
|
|
5020
5279
|
var styles$5 = {"menu":"cw-context-menu-module__menu__tXmun","item":"cw-context-menu-module__item__-ko8L","arrow":"cw-context-menu-module__arrow__LHZmQ"};
|
|
5021
5280
|
|
|
5022
5281
|
const ARROW_SIZE = 8;
|
|
@@ -6983,7 +7242,7 @@ function formatPlainDate(pd, fmt) {
|
|
|
6983
7242
|
.replace("dd", String(pd.day).padStart(2, "0"));
|
|
6984
7243
|
}
|
|
6985
7244
|
|
|
6986
|
-
function CwDatePickerTemporal({ value, onChange, minDate, maxDate, disabledDates, disabledMatcher, defaultMonth, labelProps, alignProps, placeholder = "Select a date", displayFormat = "dd.MM.yyyy", disabled, required, className, showClear = true, popupPosition = "left-bottom", numberOfMonths = 1, showTodayButton = false, todayLabel = "Today", feedback, }) {
|
|
7245
|
+
function CwDatePickerTemporal({ value, onChange, minDate, maxDate, disabledDates, disabledMatcher, defaultMonth, labelProps, alignProps, placeholder = "Select a date", displayFormat = "dd.MM.yyyy", disabled, required, className, showClear = true, popupPosition = "left-bottom", numberOfMonths = 1, showTodayButton = false, locale = enGB, todayLabel = "Today", yearRangeStart, yearRangeEnd, feedback, }) {
|
|
6987
7246
|
const [isOpen, setIsOpen] = useState(false);
|
|
6988
7247
|
const [inputValue, setInputValue] = useState();
|
|
6989
7248
|
const containerRef = useRef(null);
|
|
@@ -7072,6 +7331,19 @@ function CwDatePickerTemporal({ value, onChange, minDate, maxDate, disabledDates
|
|
|
7072
7331
|
setIsOpen(false);
|
|
7073
7332
|
}
|
|
7074
7333
|
}, [minDate, maxDate, onChange]);
|
|
7334
|
+
// Derive start/end months for the year dropdown range, guarding against inverted yearRange props
|
|
7335
|
+
const dropdownStartMonth = useMemo(() => {
|
|
7336
|
+
const inferredStart = minDate ? minDate.year : Temporal.Now.plainDateISO().year - 10;
|
|
7337
|
+
const inferredEnd = yearRangeEnd ?? (maxDate ? maxDate.year : Temporal.Now.plainDateISO().year + 5);
|
|
7338
|
+
const year = Math.min(yearRangeStart ?? inferredStart, inferredEnd);
|
|
7339
|
+
return new Date(year, 0, 1);
|
|
7340
|
+
}, [yearRangeStart, yearRangeEnd, minDate, maxDate]);
|
|
7341
|
+
const dropdownEndMonth = useMemo(() => {
|
|
7342
|
+
const inferredEnd = maxDate ? maxDate.year : Temporal.Now.plainDateISO().year + 5;
|
|
7343
|
+
const inferredStart = yearRangeStart ?? (minDate ? minDate.year : Temporal.Now.plainDateISO().year - 10);
|
|
7344
|
+
const year = Math.max(yearRangeEnd ?? inferredEnd, inferredStart);
|
|
7345
|
+
return new Date(year, 11, 31);
|
|
7346
|
+
}, [yearRangeStart, yearRangeEnd, minDate, maxDate]);
|
|
7075
7347
|
// Memoize disabled days (convert PlainDate[] to Date[] for DayPicker)
|
|
7076
7348
|
const disabledDays = useMemo(() => [
|
|
7077
7349
|
...(disabledDates?.map(plainDateToJsDate) || []),
|
|
@@ -7083,13 +7355,463 @@ function CwDatePickerTemporal({ value, onChange, minDate, maxDate, disabledDates
|
|
|
7083
7355
|
const defaultMonthJsDate = defaultMonth
|
|
7084
7356
|
? new Date(defaultMonth.year, defaultMonth.month - 1, 1)
|
|
7085
7357
|
: selectedJsDate;
|
|
7086
|
-
return (jsxs("div", { ref: containerRef, className: `cw-datepicker ${className || ""}`, children: [jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxs("div", { ref: wrapperRef, className: styles$4.pickerWrapper, children: [jsx("input", { ref: inputRef, type: "text", value: inputValue, placeholder: placeholder, onChange: handleInputChange, onBlur: handleInputBlur, onClick: handleInputClick, onKeyDown: handleInputKeyDown, disabled: disabled, required: required }), jsx("div", { className: styles$4.pickerIcons, children: showClear && value && !disabled ? (jsx(CwButton, { type: "button", variant: "icon", color: "neutral", icon: "close", onClick: handleClear, tabIndex: -1, "aria-label": "Clear date" })) : (jsx(CwIcon, { iconId: "calendar" })) }), renderPopup(jsxs("div", { ref: popupRef, className: styles$4.pickerPopup, style: popupStyle, children: [jsx(DayPicker, { mode: "single", selected: selectedJsDate, defaultMonth: defaultMonthJsDate, onSelect: handleDaySelect, disabled: disabledDays, numberOfMonths: numberOfMonths, modifiers: {
|
|
7358
|
+
return (jsxs("div", { ref: containerRef, className: `cw-datepicker ${className || ""}`, children: [jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxs("div", { ref: wrapperRef, className: styles$4.pickerWrapper, children: [jsx("input", { ref: inputRef, type: "text", value: inputValue, placeholder: placeholder, onChange: handleInputChange, onBlur: handleInputBlur, onClick: handleInputClick, onKeyDown: handleInputKeyDown, disabled: disabled, required: required }), jsx("div", { className: styles$4.pickerIcons, children: showClear && value && !disabled ? (jsx(CwButton, { type: "button", variant: "icon", color: "neutral", icon: "close", onClick: handleClear, tabIndex: -1, "aria-label": "Clear date" })) : (jsx(CwIcon, { iconId: "calendar" })) }), renderPopup(jsxs("div", { ref: popupRef, className: styles$4.pickerPopup, style: popupStyle, children: [jsx(DayPicker, { mode: "single", selected: selectedJsDate, defaultMonth: defaultMonthJsDate, onSelect: handleDaySelect, disabled: disabledDays, numberOfMonths: numberOfMonths, locale: locale, captionLayout: "dropdown", startMonth: dropdownStartMonth, endMonth: dropdownEndMonth, modifiers: {
|
|
7087
7359
|
today: new Date(),
|
|
7088
7360
|
}, modifiersClassNames: {
|
|
7089
7361
|
today: "rdp-day-today",
|
|
7090
7362
|
} }), showTodayButton && (jsx("footer", { className: "cw-flex-row cw-align-right-center", children: jsx(CwButton, { type: "button", variant: "outline", onClick: handleTodayClick, text: todayLabel }) }))] }))] })] }), feedbackMessages.map((feedbackItem, index) => (jsx("p", { className: "cw-input-info", "data-color": feedbackItem.type, children: feedbackItem.message }, index)))] }));
|
|
7091
7363
|
}
|
|
7092
7364
|
|
|
7365
|
+
// dateRangePresetsTemporal.ts
|
|
7366
|
+
// Temporal-typed counterpart of dateRangePresets.ts (used by CwDateRangePickerTemporal).
|
|
7367
|
+
// Each preset is evaluated at click time; a reference date can be injected for testing.
|
|
7368
|
+
const ref = (today) => today ?? Temporal.Now.plainDateISO();
|
|
7369
|
+
/** Monday as first day of the week (ISO dayOfWeek: 1 = Mon … 7 = Sun). */
|
|
7370
|
+
const startOfWeek = (d) => d.subtract({ days: d.dayOfWeek - 1 });
|
|
7371
|
+
const startOfMonth = (d) => d.with({ day: 1 });
|
|
7372
|
+
const endOfMonth = (d) => d.with({ day: d.daysInMonth });
|
|
7373
|
+
/** First day of the quarter that contains `d`. */
|
|
7374
|
+
const startOfQuarter = (d) => Temporal.PlainDate.from({ year: d.year, month: Math.floor((d.month - 1) / 3) * 3 + 1, day: 1 });
|
|
7375
|
+
const PRESET_LIBRARY_TEMPORAL = {
|
|
7376
|
+
// === PAST DAYS ===
|
|
7377
|
+
today: {
|
|
7378
|
+
key: "today",
|
|
7379
|
+
label: "Today",
|
|
7380
|
+
getValue: (t) => { const d = ref(t); return { from: d, to: d }; },
|
|
7381
|
+
},
|
|
7382
|
+
yesterday: {
|
|
7383
|
+
key: "yesterday",
|
|
7384
|
+
label: "Yesterday",
|
|
7385
|
+
getValue: (t) => { const d = ref(t).subtract({ days: 1 }); return { from: d, to: d }; },
|
|
7386
|
+
},
|
|
7387
|
+
past2Days: {
|
|
7388
|
+
key: "past2Days",
|
|
7389
|
+
label: "Past 2 Days",
|
|
7390
|
+
getValue: (t) => { const d = ref(t); return { from: d.subtract({ days: 2 }), to: d }; },
|
|
7391
|
+
},
|
|
7392
|
+
past3Days: {
|
|
7393
|
+
key: "past3Days",
|
|
7394
|
+
label: "Past 3 Days",
|
|
7395
|
+
getValue: (t) => { const d = ref(t); return { from: d.subtract({ days: 3 }), to: d }; },
|
|
7396
|
+
},
|
|
7397
|
+
past7Days: {
|
|
7398
|
+
key: "past7Days",
|
|
7399
|
+
label: "Past 7 days",
|
|
7400
|
+
getValue: (t) => { const d = ref(t); return { from: d.subtract({ days: 7 }), to: d }; },
|
|
7401
|
+
},
|
|
7402
|
+
past14Days: {
|
|
7403
|
+
key: "past14Days",
|
|
7404
|
+
label: "Past 14 days",
|
|
7405
|
+
getValue: (t) => { const d = ref(t); return { from: d.subtract({ days: 14 }), to: d }; },
|
|
7406
|
+
},
|
|
7407
|
+
past30Days: {
|
|
7408
|
+
key: "past30Days",
|
|
7409
|
+
label: "Past 30 days",
|
|
7410
|
+
getValue: (t) => { const d = ref(t); return { from: d.subtract({ days: 30 }), to: d }; },
|
|
7411
|
+
},
|
|
7412
|
+
past90Days: {
|
|
7413
|
+
key: "past90Days",
|
|
7414
|
+
label: "Past 90 days",
|
|
7415
|
+
getValue: (t) => { const d = ref(t); return { from: d.subtract({ days: 90 }), to: d }; },
|
|
7416
|
+
},
|
|
7417
|
+
// === NEXT DAYS ===
|
|
7418
|
+
next7Days: {
|
|
7419
|
+
key: "next7Days",
|
|
7420
|
+
label: "Next 7 days",
|
|
7421
|
+
getValue: (t) => { const d = ref(t); return { from: d, to: d.add({ days: 7 }) }; },
|
|
7422
|
+
},
|
|
7423
|
+
next14Days: {
|
|
7424
|
+
key: "next14Days",
|
|
7425
|
+
label: "Next 14 days",
|
|
7426
|
+
getValue: (t) => { const d = ref(t); return { from: d, to: d.add({ days: 14 }) }; },
|
|
7427
|
+
},
|
|
7428
|
+
next30Days: {
|
|
7429
|
+
key: "next30Days",
|
|
7430
|
+
label: "Next 30 days",
|
|
7431
|
+
getValue: (t) => { const d = ref(t); return { from: d, to: d.add({ days: 30 }) }; },
|
|
7432
|
+
},
|
|
7433
|
+
// === WEEKS (Monday-first) ===
|
|
7434
|
+
thisWeek: {
|
|
7435
|
+
key: "thisWeek",
|
|
7436
|
+
label: "This week",
|
|
7437
|
+
getValue: (t) => { const s = startOfWeek(ref(t)); return { from: s, to: s.add({ days: 6 }) }; },
|
|
7438
|
+
},
|
|
7439
|
+
lastWeek: {
|
|
7440
|
+
key: "lastWeek",
|
|
7441
|
+
label: "Last week",
|
|
7442
|
+
getValue: (t) => { const end = startOfWeek(ref(t)).subtract({ days: 1 }); return { from: end.subtract({ days: 6 }), to: end }; },
|
|
7443
|
+
},
|
|
7444
|
+
nextWeek: {
|
|
7445
|
+
key: "nextWeek",
|
|
7446
|
+
label: "Next week",
|
|
7447
|
+
getValue: (t) => { const s = startOfWeek(ref(t)).add({ days: 7 }); return { from: s, to: s.add({ days: 6 }) }; },
|
|
7448
|
+
},
|
|
7449
|
+
// === MONTHS ===
|
|
7450
|
+
thisMonth: {
|
|
7451
|
+
key: "thisMonth",
|
|
7452
|
+
label: "This month",
|
|
7453
|
+
getValue: (t) => { const d = ref(t); return { from: startOfMonth(d), to: endOfMonth(d) }; },
|
|
7454
|
+
},
|
|
7455
|
+
lastMonth: {
|
|
7456
|
+
key: "lastMonth",
|
|
7457
|
+
label: "Last month",
|
|
7458
|
+
getValue: (t) => { const d = startOfMonth(ref(t)).subtract({ months: 1 }); return { from: d, to: endOfMonth(d) }; },
|
|
7459
|
+
},
|
|
7460
|
+
nextMonth: {
|
|
7461
|
+
key: "nextMonth",
|
|
7462
|
+
label: "Next month",
|
|
7463
|
+
getValue: (t) => { const d = startOfMonth(ref(t)).add({ months: 1 }); return { from: d, to: endOfMonth(d) }; },
|
|
7464
|
+
},
|
|
7465
|
+
// === QUARTERS ===
|
|
7466
|
+
thisQuarter: {
|
|
7467
|
+
key: "thisQuarter",
|
|
7468
|
+
label: "This quarter",
|
|
7469
|
+
getValue: (t) => { const from = startOfQuarter(ref(t)); return { from, to: from.add({ months: 3 }).subtract({ days: 1 }) }; },
|
|
7470
|
+
},
|
|
7471
|
+
lastQuarter: {
|
|
7472
|
+
key: "lastQuarter",
|
|
7473
|
+
label: "Last quarter",
|
|
7474
|
+
getValue: (t) => { const from = startOfQuarter(ref(t)).subtract({ months: 3 }); return { from, to: from.add({ months: 3 }).subtract({ days: 1 }) }; },
|
|
7475
|
+
},
|
|
7476
|
+
nextQuarter: {
|
|
7477
|
+
key: "nextQuarter",
|
|
7478
|
+
label: "Next quarter",
|
|
7479
|
+
getValue: (t) => { const from = startOfQuarter(ref(t)).add({ months: 3 }); return { from, to: from.add({ months: 3 }).subtract({ days: 1 }) }; },
|
|
7480
|
+
},
|
|
7481
|
+
// === YEAR ===
|
|
7482
|
+
thisYear: {
|
|
7483
|
+
key: "thisYear",
|
|
7484
|
+
label: "This year",
|
|
7485
|
+
getValue: (t) => {
|
|
7486
|
+
const y = ref(t).year;
|
|
7487
|
+
return { from: Temporal.PlainDate.from({ year: y, month: 1, day: 1 }), to: Temporal.PlainDate.from({ year: y, month: 12, day: 31 }) };
|
|
7488
|
+
},
|
|
7489
|
+
},
|
|
7490
|
+
lastYear: {
|
|
7491
|
+
key: "lastYear",
|
|
7492
|
+
label: "Last year",
|
|
7493
|
+
getValue: (t) => {
|
|
7494
|
+
const y = ref(t).year - 1;
|
|
7495
|
+
return { from: Temporal.PlainDate.from({ year: y, month: 1, day: 1 }), to: Temporal.PlainDate.from({ year: y, month: 12, day: 31 }) };
|
|
7496
|
+
},
|
|
7497
|
+
},
|
|
7498
|
+
};
|
|
7499
|
+
/**
|
|
7500
|
+
* Helper to get presets by their keys.
|
|
7501
|
+
* @example getPresetsByKeysTemporal(['thisWeek', 'past7Days', 'past30Days'])
|
|
7502
|
+
*/
|
|
7503
|
+
const getPresetsByKeysTemporal = (keys) => keys.map((key) => PRESET_LIBRARY_TEMPORAL[key]).filter((preset) => preset !== undefined);
|
|
7504
|
+
|
|
7505
|
+
function CwDateRangePickerTemporal({ value, onChange, minDate, maxDate, disabledDates, disabledMatcher, maxRangeDays, defaultMonth, labelProps, alignProps, placeholderFrom = "From date", placeholderTo = "To date", displayFormat = "dd.MM.yyyy", disabled, required, className, showClear = true, popupPosition = "left-bottom", numberOfMonths = 2, locale = enGB, yearRangeStart, yearRangeEnd, showPresets = false, presetKeys, customPresets, }) {
|
|
7506
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
7507
|
+
const [inputFromValue, setInputFromValue] = useState("");
|
|
7508
|
+
const [inputToValue, setInputToValue] = useState("");
|
|
7509
|
+
const [rangeErrorMessage, setRangeErrorMessage] = useState(null);
|
|
7510
|
+
const [focusedInput, setFocusedInput] = useState();
|
|
7511
|
+
const containerRef = useRef(null);
|
|
7512
|
+
const wrapperRef = useRef(null);
|
|
7513
|
+
const inputFromRef = useRef(null);
|
|
7514
|
+
const inputToRef = useRef(null);
|
|
7515
|
+
const prevValueRef = useRef(undefined);
|
|
7516
|
+
const prevDisplayFormatRef = useRef(displayFormat);
|
|
7517
|
+
const rangeError = useCallback(() => (maxRangeDays ? `Range cannot exceed ${maxRangeDays} days` : "Invalid date range"), [maxRangeDays]);
|
|
7518
|
+
const handleClose = useCallback(() => {
|
|
7519
|
+
setIsOpen(false);
|
|
7520
|
+
setFocusedInput(undefined);
|
|
7521
|
+
}, []);
|
|
7522
|
+
const { popupRef, popupStyle, renderPopup } = usePickerPopup({
|
|
7523
|
+
anchorRef: wrapperRef,
|
|
7524
|
+
isOpen,
|
|
7525
|
+
onClose: handleClose,
|
|
7526
|
+
position: popupPosition,
|
|
7527
|
+
});
|
|
7528
|
+
// Calculate presets to render
|
|
7529
|
+
const presetsToRender = useMemo(() => {
|
|
7530
|
+
let presets = [];
|
|
7531
|
+
if (presetKeys && presetKeys.length > 0) {
|
|
7532
|
+
presets = getPresetsByKeysTemporal(presetKeys);
|
|
7533
|
+
}
|
|
7534
|
+
if (customPresets && customPresets.length > 0) {
|
|
7535
|
+
presets = [...presets, ...customPresets];
|
|
7536
|
+
}
|
|
7537
|
+
return presets;
|
|
7538
|
+
}, [presetKeys, customPresets]);
|
|
7539
|
+
// Validate date range
|
|
7540
|
+
const validateRange = useCallback((from, to) => {
|
|
7541
|
+
if (!from || !to)
|
|
7542
|
+
return true; // Partial ranges are OK during selection
|
|
7543
|
+
if (Temporal.PlainDate.compare(from, to) > 0)
|
|
7544
|
+
return false;
|
|
7545
|
+
if (maxRangeDays) {
|
|
7546
|
+
const daysDiff = from.until(to, { largestUnit: "day" }).days;
|
|
7547
|
+
if (daysDiff > maxRangeDays)
|
|
7548
|
+
return false;
|
|
7549
|
+
}
|
|
7550
|
+
return true;
|
|
7551
|
+
}, [maxRangeDays]);
|
|
7552
|
+
// Sync input values with prop value
|
|
7553
|
+
useEffect(() => {
|
|
7554
|
+
const currentFrom = value?.from?.toString();
|
|
7555
|
+
const currentTo = value?.to?.toString();
|
|
7556
|
+
const prevFrom = prevValueRef.current?.from;
|
|
7557
|
+
const prevTo = prevValueRef.current?.to;
|
|
7558
|
+
if (currentFrom !== prevFrom ||
|
|
7559
|
+
currentTo !== prevTo ||
|
|
7560
|
+
prevDisplayFormatRef.current !== displayFormat) {
|
|
7561
|
+
prevValueRef.current = { from: currentFrom, to: currentTo };
|
|
7562
|
+
prevDisplayFormatRef.current = displayFormat;
|
|
7563
|
+
setInputFromValue(value?.from ? formatPlainDate(value.from, displayFormat) : "");
|
|
7564
|
+
setInputToValue(value?.to ? formatPlainDate(value.to, displayFormat) : "");
|
|
7565
|
+
}
|
|
7566
|
+
}, [value, displayFormat]);
|
|
7567
|
+
const handleRangeSelect = useCallback((range, selectedDay) => {
|
|
7568
|
+
const selected = jsDateToPlainDate(selectedDay);
|
|
7569
|
+
let newRange;
|
|
7570
|
+
if (focusedInput === 'from') {
|
|
7571
|
+
newRange = { from: selected, to: value?.to };
|
|
7572
|
+
if (newRange.to && Temporal.PlainDate.compare(selected, newRange.to) > 0) {
|
|
7573
|
+
newRange.to = undefined;
|
|
7574
|
+
setFocusedInput('to');
|
|
7575
|
+
setRangeErrorMessage(null);
|
|
7576
|
+
}
|
|
7577
|
+
else if (newRange.to) {
|
|
7578
|
+
if (!validateRange(newRange.from, newRange.to)) {
|
|
7579
|
+
setRangeErrorMessage(rangeError());
|
|
7580
|
+
return;
|
|
7581
|
+
}
|
|
7582
|
+
setRangeErrorMessage(null);
|
|
7583
|
+
setIsOpen(false);
|
|
7584
|
+
setFocusedInput(undefined);
|
|
7585
|
+
}
|
|
7586
|
+
else {
|
|
7587
|
+
setFocusedInput('to');
|
|
7588
|
+
setRangeErrorMessage(null);
|
|
7589
|
+
}
|
|
7590
|
+
}
|
|
7591
|
+
else if (focusedInput === 'to') {
|
|
7592
|
+
newRange = { from: value?.from, to: selected };
|
|
7593
|
+
if (newRange.from && Temporal.PlainDate.compare(selected, newRange.from) < 0) {
|
|
7594
|
+
newRange.from = undefined;
|
|
7595
|
+
setFocusedInput('from');
|
|
7596
|
+
setRangeErrorMessage(null);
|
|
7597
|
+
}
|
|
7598
|
+
else if (newRange.from) {
|
|
7599
|
+
if (!validateRange(newRange.from, newRange.to)) {
|
|
7600
|
+
setRangeErrorMessage(rangeError());
|
|
7601
|
+
return;
|
|
7602
|
+
}
|
|
7603
|
+
setRangeErrorMessage(null);
|
|
7604
|
+
setIsOpen(false);
|
|
7605
|
+
setFocusedInput(undefined);
|
|
7606
|
+
}
|
|
7607
|
+
else {
|
|
7608
|
+
setFocusedInput('from');
|
|
7609
|
+
setRangeErrorMessage(null);
|
|
7610
|
+
}
|
|
7611
|
+
}
|
|
7612
|
+
else {
|
|
7613
|
+
if (!range) {
|
|
7614
|
+
onChange(undefined);
|
|
7615
|
+
return;
|
|
7616
|
+
}
|
|
7617
|
+
newRange = {
|
|
7618
|
+
from: range.from ? jsDateToPlainDate(range.from) : undefined,
|
|
7619
|
+
to: range.to ? jsDateToPlainDate(range.to) : undefined,
|
|
7620
|
+
};
|
|
7621
|
+
if (range.from && range.to) {
|
|
7622
|
+
if (!validateRange(newRange.from, newRange.to)) {
|
|
7623
|
+
setRangeErrorMessage(rangeError());
|
|
7624
|
+
return;
|
|
7625
|
+
}
|
|
7626
|
+
setRangeErrorMessage(null);
|
|
7627
|
+
setIsOpen(false);
|
|
7628
|
+
setFocusedInput(undefined);
|
|
7629
|
+
}
|
|
7630
|
+
else if (range.from) {
|
|
7631
|
+
setFocusedInput('to');
|
|
7632
|
+
setRangeErrorMessage(null);
|
|
7633
|
+
}
|
|
7634
|
+
else {
|
|
7635
|
+
setFocusedInput('from');
|
|
7636
|
+
setRangeErrorMessage(null);
|
|
7637
|
+
}
|
|
7638
|
+
}
|
|
7639
|
+
onChange(newRange);
|
|
7640
|
+
}, [onChange, validateRange, value, focusedInput, rangeError]);
|
|
7641
|
+
const handleClearFrom = useCallback((e) => {
|
|
7642
|
+
e.stopPropagation();
|
|
7643
|
+
onChange({ from: undefined, to: value?.to });
|
|
7644
|
+
setInputFromValue("");
|
|
7645
|
+
}, [onChange, value]);
|
|
7646
|
+
const handleClearTo = useCallback((e) => {
|
|
7647
|
+
e.stopPropagation();
|
|
7648
|
+
onChange({ from: value?.from, to: undefined });
|
|
7649
|
+
setInputToValue("");
|
|
7650
|
+
}, [onChange, value]);
|
|
7651
|
+
const handleInputFromClick = useCallback(() => {
|
|
7652
|
+
if (!disabled) {
|
|
7653
|
+
setIsOpen(true);
|
|
7654
|
+
setFocusedInput('from');
|
|
7655
|
+
}
|
|
7656
|
+
}, [disabled]);
|
|
7657
|
+
const handleInputToClick = useCallback(() => {
|
|
7658
|
+
if (!disabled) {
|
|
7659
|
+
setIsOpen(true);
|
|
7660
|
+
setFocusedInput('to');
|
|
7661
|
+
}
|
|
7662
|
+
}, [disabled]);
|
|
7663
|
+
const handleInputFromChange = useCallback((e) => {
|
|
7664
|
+
const newValue = e.currentTarget.value;
|
|
7665
|
+
setInputFromValue(newValue);
|
|
7666
|
+
const parsed = parseDate(newValue, displayFormat);
|
|
7667
|
+
if (parsed) {
|
|
7668
|
+
if (validate(parsed, minDate, maxDate) && validateRange(parsed, value?.to)) {
|
|
7669
|
+
onChange({ from: parsed, to: value?.to });
|
|
7670
|
+
}
|
|
7671
|
+
}
|
|
7672
|
+
else if (newValue === "") {
|
|
7673
|
+
onChange({ from: undefined, to: value?.to });
|
|
7674
|
+
}
|
|
7675
|
+
}, [displayFormat, minDate, maxDate, validateRange, onChange, value]);
|
|
7676
|
+
const handleInputToChange = useCallback((e) => {
|
|
7677
|
+
const newValue = e.currentTarget.value;
|
|
7678
|
+
setInputToValue(newValue);
|
|
7679
|
+
const parsed = parseDate(newValue, displayFormat);
|
|
7680
|
+
if (parsed) {
|
|
7681
|
+
if (validate(parsed, minDate, maxDate) && validateRange(value?.from, parsed)) {
|
|
7682
|
+
onChange({ from: value?.from, to: parsed });
|
|
7683
|
+
}
|
|
7684
|
+
}
|
|
7685
|
+
else if (newValue === "") {
|
|
7686
|
+
onChange({ from: value?.from, to: undefined });
|
|
7687
|
+
}
|
|
7688
|
+
}, [displayFormat, minDate, maxDate, validateRange, onChange, value]);
|
|
7689
|
+
const handleInputBlur = useCallback((type) => {
|
|
7690
|
+
const inputValue = type === 'from' ? inputFromValue : inputToValue;
|
|
7691
|
+
const currentValue = type === 'from' ? value?.from : value?.to;
|
|
7692
|
+
const trimmed = inputValue?.trim();
|
|
7693
|
+
if (trimmed) {
|
|
7694
|
+
const parsed = parseDate(trimmed, displayFormat);
|
|
7695
|
+
if (parsed && validate(parsed, minDate, maxDate)) {
|
|
7696
|
+
const newRange = type === 'from'
|
|
7697
|
+
? { from: parsed, to: value?.to }
|
|
7698
|
+
: { from: value?.from, to: parsed };
|
|
7699
|
+
if (validateRange(newRange.from, newRange.to)) {
|
|
7700
|
+
onChange(newRange);
|
|
7701
|
+
if (type === 'from') {
|
|
7702
|
+
setInputFromValue(formatPlainDate(parsed, displayFormat));
|
|
7703
|
+
}
|
|
7704
|
+
else {
|
|
7705
|
+
setInputToValue(formatPlainDate(parsed, displayFormat));
|
|
7706
|
+
}
|
|
7707
|
+
return;
|
|
7708
|
+
}
|
|
7709
|
+
}
|
|
7710
|
+
}
|
|
7711
|
+
// Reset to current value if invalid
|
|
7712
|
+
if (type === 'from') {
|
|
7713
|
+
setInputFromValue(currentValue ? formatPlainDate(currentValue, displayFormat) : "");
|
|
7714
|
+
}
|
|
7715
|
+
else {
|
|
7716
|
+
setInputToValue(currentValue ? formatPlainDate(currentValue, displayFormat) : "");
|
|
7717
|
+
}
|
|
7718
|
+
}, [inputFromValue, inputToValue, value, displayFormat, minDate, maxDate, validateRange, onChange]);
|
|
7719
|
+
const handleInputKeyDown = useCallback((e, type) => {
|
|
7720
|
+
if (e.key === "Enter") {
|
|
7721
|
+
e.preventDefault();
|
|
7722
|
+
if (type === 'from') {
|
|
7723
|
+
inputFromRef.current?.blur();
|
|
7724
|
+
inputToRef.current?.focus();
|
|
7725
|
+
}
|
|
7726
|
+
else {
|
|
7727
|
+
inputToRef.current?.blur();
|
|
7728
|
+
setIsOpen(false);
|
|
7729
|
+
}
|
|
7730
|
+
}
|
|
7731
|
+
else if (e.key === "Escape") {
|
|
7732
|
+
setIsOpen(false);
|
|
7733
|
+
if (type === 'from') {
|
|
7734
|
+
inputFromRef.current?.blur();
|
|
7735
|
+
}
|
|
7736
|
+
else {
|
|
7737
|
+
inputToRef.current?.blur();
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
7740
|
+
}, []);
|
|
7741
|
+
// Handle preset click
|
|
7742
|
+
const handlePresetClick = useCallback((preset, e) => {
|
|
7743
|
+
e.preventDefault();
|
|
7744
|
+
e.stopPropagation();
|
|
7745
|
+
const nextRange = preset.getValue();
|
|
7746
|
+
if (!validate(nextRange.from, minDate, maxDate) ||
|
|
7747
|
+
!validate(nextRange.to, minDate, maxDate) ||
|
|
7748
|
+
!validateRange(nextRange.from, nextRange.to)) {
|
|
7749
|
+
setRangeErrorMessage(rangeError());
|
|
7750
|
+
return;
|
|
7751
|
+
}
|
|
7752
|
+
setRangeErrorMessage(null);
|
|
7753
|
+
onChange(nextRange);
|
|
7754
|
+
setIsOpen(false);
|
|
7755
|
+
}, [maxDate, minDate, onChange, rangeError, validateRange]);
|
|
7756
|
+
// Derive start/end months for the year dropdown range, guarding against inverted yearRange props
|
|
7757
|
+
const dropdownStartMonth = useMemo(() => {
|
|
7758
|
+
const inferredStart = minDate ? minDate.year : Temporal.Now.plainDateISO().year - 10;
|
|
7759
|
+
const inferredEnd = yearRangeEnd ?? (maxDate ? maxDate.year : Temporal.Now.plainDateISO().year + 5);
|
|
7760
|
+
const year = Math.min(yearRangeStart ?? inferredStart, inferredEnd);
|
|
7761
|
+
return new Date(year, 0, 1);
|
|
7762
|
+
}, [yearRangeStart, yearRangeEnd, minDate, maxDate]);
|
|
7763
|
+
const dropdownEndMonth = useMemo(() => {
|
|
7764
|
+
const inferredEnd = maxDate ? maxDate.year : Temporal.Now.plainDateISO().year + 5;
|
|
7765
|
+
const inferredStart = yearRangeStart ?? (minDate ? minDate.year : Temporal.Now.plainDateISO().year - 10);
|
|
7766
|
+
const year = Math.max(yearRangeEnd ?? inferredEnd, inferredStart);
|
|
7767
|
+
return new Date(year, 11, 31);
|
|
7768
|
+
}, [yearRangeStart, yearRangeEnd, minDate, maxDate]);
|
|
7769
|
+
// Memoize disabled days (convert Temporal → Date for DayPicker)
|
|
7770
|
+
const disabledDays = useMemo(() => {
|
|
7771
|
+
const baseDisabled = [
|
|
7772
|
+
...(disabledDates?.map(plainDateToJsDate) || []),
|
|
7773
|
+
...(minDate ? [{ before: plainDateToJsDate(minDate) }] : []),
|
|
7774
|
+
...(maxDate ? [{ after: plainDateToJsDate(maxDate) }] : []),
|
|
7775
|
+
...(disabledMatcher ? [disabledMatcher] : []),
|
|
7776
|
+
];
|
|
7777
|
+
// If maxRangeDays is set and we have a "from" date, disable dates beyond the range
|
|
7778
|
+
if (maxRangeDays) {
|
|
7779
|
+
if (value?.from) {
|
|
7780
|
+
const maxAllowedDate = value.from.add({ days: maxRangeDays });
|
|
7781
|
+
if (focusedInput !== 'from') {
|
|
7782
|
+
baseDisabled.push({ after: plainDateToJsDate(maxAllowedDate) });
|
|
7783
|
+
}
|
|
7784
|
+
if (focusedInput === 'to' && !value?.to) {
|
|
7785
|
+
const minAllowedDate = value.from.subtract({ days: maxRangeDays });
|
|
7786
|
+
baseDisabled.push({ before: plainDateToJsDate(minAllowedDate) });
|
|
7787
|
+
}
|
|
7788
|
+
}
|
|
7789
|
+
if (value?.to && !value?.from && focusedInput === 'from') {
|
|
7790
|
+
const minAllowedDate = value.to.subtract({ days: maxRangeDays });
|
|
7791
|
+
baseDisabled.push({ before: plainDateToJsDate(minAllowedDate) });
|
|
7792
|
+
}
|
|
7793
|
+
}
|
|
7794
|
+
return baseDisabled;
|
|
7795
|
+
}, [disabledDates, minDate, maxDate, disabledMatcher, maxRangeDays, value, focusedInput]);
|
|
7796
|
+
const selectedRange = useMemo(() => {
|
|
7797
|
+
if (value?.from || value?.to) {
|
|
7798
|
+
return {
|
|
7799
|
+
from: value?.from ? plainDateToJsDate(value.from) : undefined,
|
|
7800
|
+
to: value?.to ? plainDateToJsDate(value.to) : undefined,
|
|
7801
|
+
};
|
|
7802
|
+
}
|
|
7803
|
+
return undefined;
|
|
7804
|
+
}, [value]);
|
|
7805
|
+
const defaultMonthJsDate = defaultMonth
|
|
7806
|
+
? new Date(defaultMonth.year, defaultMonth.month - 1, 1)
|
|
7807
|
+
: (value?.from ? plainDateToJsDate(value.from) : undefined);
|
|
7808
|
+
return (jsx("div", { ref: containerRef, className: `cw-rangepicker ${className || ""}`, children: jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxs("div", { ref: wrapperRef, className: rangeStyles.rangeWrapper, children: [jsxs("div", { className: styles$4.pickerWrapper, children: [jsx("input", { ref: inputFromRef, type: "text", value: inputFromValue, placeholder: placeholderFrom, onChange: handleInputFromChange, onBlur: () => handleInputBlur('from'), onClick: handleInputFromClick, onKeyDown: (e) => handleInputKeyDown(e, 'from'), disabled: disabled, required: required, "data-focused": focusedInput === 'from' }), jsx("div", { className: styles$4.pickerIcons, children: showClear && value?.from && !disabled ? (jsx(CwButton, { type: "button", variant: "icon", color: "neutral", icon: "close", onClick: handleClearFrom, tabIndex: -1, "aria-label": "Clear from date" })) : (jsx(CwIcon, { iconId: "calendar" })) })] }), jsx(CwIcon, { iconId: "arrow-right", size: "medium" }), jsxs("div", { className: styles$4.pickerWrapper, children: [jsx("input", { ref: inputToRef, type: "text", value: inputToValue, placeholder: placeholderTo, onChange: handleInputToChange, onBlur: () => handleInputBlur('to'), onClick: handleInputToClick, onKeyDown: (e) => handleInputKeyDown(e, 'to'), disabled: disabled, required: required, "data-focused": focusedInput === 'to' }), jsx("div", { className: styles$4.pickerIcons, children: showClear && value?.to && !disabled ? (jsx(CwButton, { type: "button", variant: "icon", color: "neutral", icon: "close", onClick: handleClearTo, tabIndex: -1, "aria-label": "Clear to date" })) : (jsx(CwIcon, { iconId: "calendar" })) })] }), renderPopup(jsxs("div", { ref: popupRef, className: `${styles$4.pickerPopup} ${rangeStyles.rangePopup}`, style: popupStyle, children: [jsxs("div", { className: rangeStyles.rangeCalendarRow, children: [showPresets && presetsToRender.length > 0 && (jsx("div", { className: rangeStyles.presetList, children: presetsToRender.map((preset) => (jsx("button", { type: "button", onClick: (e) => handlePresetClick(preset, e), children: preset.label }, preset.key))) })), jsx(DayPicker, { mode: "range", selected: selectedRange, onSelect: handleRangeSelect, disabled: disabledDays, numberOfMonths: numberOfMonths, defaultMonth: defaultMonthJsDate, locale: locale, captionLayout: "dropdown", startMonth: dropdownStartMonth, endMonth: dropdownEndMonth, modifiers: {
|
|
7809
|
+
today: new Date(),
|
|
7810
|
+
}, modifiersClassNames: {
|
|
7811
|
+
today: "rdp-day-today",
|
|
7812
|
+
} })] }), jsx("div", { role: "alert", "aria-live": "polite", className: rangeStyles.rangeError, children: rangeErrorMessage })] }))] })] }) }));
|
|
7813
|
+
}
|
|
7814
|
+
|
|
7093
7815
|
const ResourcesTitleList = ({ resources, canBePinned, includesPinned, onCrewPinning, onClickResourceContextMenu, ResourceTitleComponent, }) => {
|
|
7094
7816
|
const onClickContextMenu = useCallback((key, resource) => {
|
|
7095
7817
|
if (onClickResourceContextMenu) {
|
|
@@ -10025,4 +10747,4 @@ const CwFindCrewmember = ({ handleChange, placeHolder = "Search crew…", requir
|
|
|
10025
10747
|
}, "data-direction": direction, children: [jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxs("div", { className: "cw-search-input-wrapper", ref: wrapperRef, children: [jsx("input", { ref: inputRef, type: "text", value: inputValue, onChange: handleInputChange, onKeyDown: handleKeyDown, onFocus: handleInputFocus, placeholder: isInitialLoading ? "Loading…" : placeHolder, disabled: disabled, required: required, autoComplete: "off", "aria-expanded": showDropdown, "aria-haspopup": "listbox", role: "combobox", title: tooltipText }), (isLoading || isInitialLoading) && (jsx("div", { className: "cw-search-input-loading", children: jsx(CwIcon, { iconId: "spinner" }) })), jsx("div", { className: "cw-search-input-icons", children: inputValue && !disabled && !isInitialLoading ? (jsx(CwButton, { type: "button", onClick: handleClear, "aria-label": "Clear selected crewmember", variant: "icon", icon: "close", color: "neutral" })) : (jsx(CwIcon, { iconId: "person" })) })] })] }), renderPanel(jsx("div", { ref: panelRef, className: dropdownStyles.dropdown, style: panelStyle, role: "listbox", children: jsx("ul", { children: options.map((option, index) => (jsx("li", { className: `${dropdownStyles.option}${index === highlightedIndex ? ` ${dropdownStyles.optionFocused}` : ""}`, onClick: () => handleOptionSelect(option.value), onMouseDown: (e) => e.preventDefault(), onMouseEnter: () => setHighlightedIndex(index), role: "option", "aria-selected": index === highlightedIndex, children: option.text }, option.value))) }) }))] }));
|
|
10026
10748
|
};
|
|
10027
10749
|
|
|
10028
|
-
export { CblDragAndDrop, CwAccordionContainer, CwAlign, CwAnchoredMenu, CwBtnApprove, CwBtnCancel, CwBtnDelete, CwBtnEdit, CwBtnSave, CwButton, CwCard, CwCardList, CwCheck, CwCheckbox, CwCheckboxGroup, CwChip, CwColorPicker, CwConfirmationPopup, CwContextMenu, CwDatePicker, CwDatePickerTemporal, CwDateRangePicker, CwDateTimePicker, CwDateTimePickerCompact, CwDialog, CwDigit, CwDisplayMessage, CwDropdown, CwDropdownFilter, CwExpandable, CwFileUpload, CwFileUploadMultiple, CwFindAirport, CwFindCrewmember, CwGenericTooltip, CwHeadingMain, CwHeadingSecond, CwIcon, CwImageArea, CwImageGallery, CwImageZoom, CwInput, CwInputColor, CwInputDateText, CwInputDatetime, CwInputImage, CwInputNumber, CwInputPhone, CwInputText, CwKeyValueList, CwLabel, CwLoading, CwLoadingSmall, CwMasterDetail, CwMessage, CwMessageManager, CwMessageType, CwModal, CwModalHover, CwModalReportFunctional, CwMultiFilter, CwMultiFilterTag, CwNote, CwOption, CwPopoverButton, CwReportModal, CwScheduler, CwSearchInput, CwSelect, CwSelectList, CwSelectListItems, CwSortableList, CwSortableTable, CwSuperScheduler, CwTable, CwTableGrouped, CwTableServerSide, CwTabs, CwTagSelector, CwTextArea, CwTime, CwTimePicker, CwToggle, CwTooltipManager, CwTooltipNew, CwTreeView, CwWeekdaySelector, DefaultRowHeader, OnClearPinned, OnClickContextMenu, OnClickEvent, OnClickRowEvent, OnClickRowHeader, OnClickUtc, OnDoubleClickEvent, OnDoubleClickRowEvent, OnDragEvent, OnDropCtrlEvent, OnDropEvent, OnEndClickHeaderEvent, OnLeftDragStart, OnMultiClickEvent, OnPinRow, OnRangeClickEvent, OnRightClickEvent, OnRightClickRow, OnRightDragStart, OnStartClickHeaderEvent, OnUnpinRow, PinRowHeader, Resource, Scheduler, SchedulerEvent, SuperScheduler, TooltipConstraintContext, UiEvent, Weekdays, cblEvent, eventIsVisible, filterVisibleEvents, getDefaultDivisions, getEventSizes, itemsToMultiFilterTags, useCwMessage, useSortableList, useSortableTable, useTooltipConstraint };
|
|
10750
|
+
export { CblDragAndDrop, CwAccordionContainer, CwAlign, CwAnchoredMenu, CwBtnApprove, CwBtnCancel, CwBtnDelete, CwBtnEdit, CwBtnSave, CwButton, CwCard, CwCardList, CwCheck, CwCheckbox, CwCheckboxGroup, CwChip, CwColorPicker, CwConfirmationPopup, CwContextMenu, CwDatePicker, CwDatePickerTemporal, CwDateRangePicker, CwDateRangePickerTemporal, CwDateTimePicker, CwDateTimePickerCompact, CwDialog, CwDigit, CwDisplayMessage, CwDropdown, CwDropdownFilter, CwExpandable, CwFileUpload, CwFileUploadMultiple, CwFindAirport, CwFindCrewmember, CwGenericTooltip, CwHeadingMain, CwHeadingSecond, CwIcon, CwImageArea, CwImageGallery, CwImageZoom, CwInput, CwInputColor, CwInputDateText, CwInputDatetime, CwInputImage, CwInputNumber, CwInputPhone, CwInputText, CwKeyValueList, CwLabel, CwLoading, CwLoadingSmall, CwMasterDetail, CwMessage, CwMessageManager, CwMessageType, CwModal, CwModalHover, CwModalReportFunctional, CwMultiFilter, CwMultiFilterTag, CwNote, CwOption, CwPopoverButton, CwReportModal, CwScheduler, CwSearchAsync, CwSearchInput, CwSelect, CwSelectList, CwSelectListItems, CwSortableList, CwSortableTable, CwSuperScheduler, CwTable, CwTableGrouped, CwTableServerSide, CwTabs, CwTagSelector, CwTextArea, CwTime, CwTimePicker, CwToggle, CwTooltipManager, CwTooltipNew, CwTreeView, CwWeekdaySelector, DefaultRowHeader, OnClearPinned, OnClickContextMenu, OnClickEvent, OnClickRowEvent, OnClickRowHeader, OnClickUtc, OnDoubleClickEvent, OnDoubleClickRowEvent, OnDragEvent, OnDropCtrlEvent, OnDropEvent, OnEndClickHeaderEvent, OnLeftDragStart, OnMultiClickEvent, OnPinRow, OnRangeClickEvent, OnRightClickEvent, OnRightClickRow, OnRightDragStart, OnStartClickHeaderEvent, OnUnpinRow, PRESET_LIBRARY_TEMPORAL, PinRowHeader, Resource, Scheduler, SchedulerEvent, SuperScheduler, TooltipConstraintContext, UiEvent, Weekdays, cblEvent, eventIsVisible, filterVisibleEvents, getDefaultDivisions, getEventSizes, getPresetsByKeysTemporal, itemsToMultiFilterTags, useCwMessage, useSortableList, useSortableTable, useTooltipConstraint };
|