@cwellt_software/cwellt-reactjs-lib 1.4.10 → 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 +731 -5
- package/dist/index.css +2 -2
- package/dist/index.d.ts +208 -13
- package/dist/index.es.js +729 -7
- 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/components/custom/super-scheduler/SuperScheduler.d.ts +4 -2
- package/dist/src/components/custom/super-scheduler/SuperScheduler.d.ts.map +1 -1
- 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.cjs.js
CHANGED
|
@@ -5037,6 +5037,265 @@ function CwSearchInput(optionsProps) {
|
|
|
5037
5037
|
return (jsxRuntime.jsxs("div", { className: `cw-search-input ${className || ""}`, style: style, id: id, ...otherProps, "data-direction": direction, children: [jsxRuntime.jsxs(CwAlign, { ...alignProps, children: [labelProps && (jsxRuntime.jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxRuntime.jsxs("div", { className: "cw-search-input-wrapper", ref: wrapperRef, children: [jsxRuntime.jsx("input", { type: "text", value: searchText, onChange: handleInputChange, onFocus: handleInputFocus, onBlur: handleInputBlur, placeholder: placeholder, className: "cw-input-search", disabled: disabled }), isLoading && (jsxRuntime.jsx("div", { className: "cw-search-input-loading", children: jsxRuntime.jsx(CwIcon, { iconId: "spinner" }) })), jsxRuntime.jsx("div", { className: "cw-search-input-icons", children: searchText && !disabled && !isLoading ? (jsxRuntime.jsx(CwButton, { type: "button", onClick: handleClearClick, "aria-label": "Clear search", variant: "icon", icon: "close", color: "neutral" })) : (iconProps ? jsxRuntime.jsx(CwIcon, { ...iconProps }) : jsxRuntime.jsx(CwIcon, { iconId: "search" })) })] })] }), renderPanel(jsxRuntime.jsx("div", { ref: panelRef, className: dropdownStyles.dropdown, style: panelStyle, children: jsxRuntime.jsx("ul", { children: filteredOptions.map((option) => (jsxRuntime.jsx("li", { className: dropdownStyles.option, onClick: () => handleOptionSelect(option), onMouseDown: (e) => e.preventDefault(), children: renderOption ? renderOption(option) : option.description }, option.id))) }) }))] }));
|
|
5038
5038
|
}
|
|
5039
5039
|
|
|
5040
|
+
/**
|
|
5041
|
+
* Generic API-backed search input. Fetches matching options from a caller-supplied `onSearch`
|
|
5042
|
+
* callback as the user types (debounced), renders them in a portal-based dropdown with keyboard
|
|
5043
|
+
* navigation, and commits the selected id via `onChange`.
|
|
5044
|
+
*
|
|
5045
|
+
* All domain logic — API URLs, auth/config, display formatting, provider/secondary lookups — lives
|
|
5046
|
+
* in the caller's callbacks, so this component stays application-agnostic. It is the base for thin
|
|
5047
|
+
* wrappers such as `FindAirport` / `FindCrewmember`.
|
|
5048
|
+
*
|
|
5049
|
+
* Improvements over the legacy `CwFindAirport` / `CwFindCrewmember`: an `AbortController` cancels
|
|
5050
|
+
* superseded in-flight requests (fixes out-of-order results), and `minChars` / `debounceMs` are
|
|
5051
|
+
* configurable.
|
|
5052
|
+
*
|
|
5053
|
+
* @example
|
|
5054
|
+
* <CwSearchAsync
|
|
5055
|
+
* onSearch={(text, signal) => repo.search(text, signal)}
|
|
5056
|
+
* onChange={(id) => setSelectedId(id)}
|
|
5057
|
+
* labelProps={{ text: 'Airport' }}
|
|
5058
|
+
* icon={{ iconId: 'control-tower' }}
|
|
5059
|
+
* />
|
|
5060
|
+
*/
|
|
5061
|
+
const CwSearchAsync = ({ onSearch, onChange, value, onResolveInitial, onResolveSelection, icon, placeholder = "Search…", minChars = 2, debounceMs = 300, disabled = false, required = false, initialDisplayText, searchTrigger, labelProps, alignProps, width, className = "", }) => {
|
|
5062
|
+
// State
|
|
5063
|
+
const [inputValue, setInputValue] = React.useState("");
|
|
5064
|
+
const [options, setOptions] = React.useState([]);
|
|
5065
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
5066
|
+
const [isInitialLoading, setIsInitialLoading] = React.useState(false);
|
|
5067
|
+
const [showDropdown, setShowDropdown] = React.useState(false);
|
|
5068
|
+
const [highlightedIndex, setHighlightedIndex] = React.useState(-1);
|
|
5069
|
+
const [tooltipText, setTooltipText] = React.useState("");
|
|
5070
|
+
// Refs
|
|
5071
|
+
const inputRef = React.useRef(null);
|
|
5072
|
+
const wrapperRef = React.useRef(null);
|
|
5073
|
+
const debounceRef = React.useRef();
|
|
5074
|
+
const abortRef = React.useRef(null);
|
|
5075
|
+
const lastQueryRef = React.useRef("");
|
|
5076
|
+
const didMountRef = React.useRef(false);
|
|
5077
|
+
const lastResolvedValueRef = React.useRef(undefined);
|
|
5078
|
+
const initialResolveAbortRef = React.useRef(null);
|
|
5079
|
+
// Stable ids wiring the combobox input to its listbox popup for assistive tech.
|
|
5080
|
+
const listboxId = React.useId();
|
|
5081
|
+
const optionId = (index) => `${listboxId}-opt-${index}`;
|
|
5082
|
+
// Cancel a pending initial-value resolve when the user takes over (typing / clearing / reset),
|
|
5083
|
+
// so a slow resolve can't overwrite newer input and the loading flag never sticks.
|
|
5084
|
+
const cancelInitialResolve = React.useCallback(() => {
|
|
5085
|
+
initialResolveAbortRef.current?.abort();
|
|
5086
|
+
initialResolveAbortRef.current = null;
|
|
5087
|
+
setIsInitialLoading(false);
|
|
5088
|
+
}, []);
|
|
5089
|
+
const { panelRef, panelStyle, renderPanel } = useDropdownPortal({
|
|
5090
|
+
anchorRef: wrapperRef,
|
|
5091
|
+
isOpen: showDropdown && options.length > 0,
|
|
5092
|
+
onClose: () => { setShowDropdown(false); setHighlightedIndex(-1); },
|
|
5093
|
+
});
|
|
5094
|
+
// Core search runner — aborts superseded requests and drops stale responses.
|
|
5095
|
+
const runSearch = React.useCallback(async (text) => {
|
|
5096
|
+
abortRef.current?.abort();
|
|
5097
|
+
if (text.length < minChars) {
|
|
5098
|
+
setOptions([]);
|
|
5099
|
+
setShowDropdown(false);
|
|
5100
|
+
setHighlightedIndex(-1);
|
|
5101
|
+
return;
|
|
5102
|
+
}
|
|
5103
|
+
const controller = new AbortController();
|
|
5104
|
+
abortRef.current = controller;
|
|
5105
|
+
lastQueryRef.current = text;
|
|
5106
|
+
setIsLoading(true);
|
|
5107
|
+
try {
|
|
5108
|
+
const results = await onSearch(text, controller.signal);
|
|
5109
|
+
if (controller.signal.aborted)
|
|
5110
|
+
return; // superseded → drop
|
|
5111
|
+
setOptions(results);
|
|
5112
|
+
setShowDropdown(results.length > 0); // empty → close silently (dark cockpit)
|
|
5113
|
+
setHighlightedIndex(-1);
|
|
5114
|
+
}
|
|
5115
|
+
catch (error) {
|
|
5116
|
+
if (controller.signal.aborted)
|
|
5117
|
+
return; // aborts reject with AbortError → ignore
|
|
5118
|
+
console.error("CwSearchAsync onSearch failed:", error);
|
|
5119
|
+
setOptions([]);
|
|
5120
|
+
setShowDropdown(false);
|
|
5121
|
+
}
|
|
5122
|
+
finally {
|
|
5123
|
+
if (abortRef.current === controller) {
|
|
5124
|
+
setIsLoading(false);
|
|
5125
|
+
abortRef.current = null;
|
|
5126
|
+
}
|
|
5127
|
+
}
|
|
5128
|
+
}, [minChars, onSearch]);
|
|
5129
|
+
// Keep a stable handle to the latest runSearch for the searchTrigger effect.
|
|
5130
|
+
const runSearchRef = React.useRef(runSearch);
|
|
5131
|
+
React.useEffect(() => { runSearchRef.current = runSearch; });
|
|
5132
|
+
const debouncedSearch = React.useCallback((text) => {
|
|
5133
|
+
if (debounceRef.current)
|
|
5134
|
+
window.clearTimeout(debounceRef.current);
|
|
5135
|
+
// Abort eagerly on every keystroke so a fast typist never has two live fetches.
|
|
5136
|
+
abortRef.current?.abort();
|
|
5137
|
+
debounceRef.current = window.setTimeout(() => runSearch(text), debounceMs);
|
|
5138
|
+
}, [runSearch, debounceMs]);
|
|
5139
|
+
const handleInputChange = (e) => {
|
|
5140
|
+
const newValue = e.target.value;
|
|
5141
|
+
setInputValue(newValue);
|
|
5142
|
+
if (newValue !== inputValue) {
|
|
5143
|
+
cancelInitialResolve();
|
|
5144
|
+
debouncedSearch(newValue);
|
|
5145
|
+
}
|
|
5146
|
+
};
|
|
5147
|
+
const handleOptionSelect = async (optionId) => {
|
|
5148
|
+
const picked = options.find(o => o.id === optionId);
|
|
5149
|
+
if (!picked)
|
|
5150
|
+
return;
|
|
5151
|
+
setShowDropdown(false);
|
|
5152
|
+
let committed = picked;
|
|
5153
|
+
if (onResolveSelection) {
|
|
5154
|
+
abortRef.current?.abort();
|
|
5155
|
+
const controller = new AbortController();
|
|
5156
|
+
abortRef.current = controller;
|
|
5157
|
+
try {
|
|
5158
|
+
const resolved = await onResolveSelection(picked, controller.signal);
|
|
5159
|
+
if (controller.signal.aborted)
|
|
5160
|
+
return;
|
|
5161
|
+
committed = resolved;
|
|
5162
|
+
setOptions([resolved]);
|
|
5163
|
+
}
|
|
5164
|
+
catch (error) {
|
|
5165
|
+
if (controller.signal.aborted)
|
|
5166
|
+
return;
|
|
5167
|
+
console.error("CwSearchAsync onResolveSelection failed:", error);
|
|
5168
|
+
return; // abort the commit, mirroring the legacy provider-airport behaviour
|
|
5169
|
+
}
|
|
5170
|
+
finally {
|
|
5171
|
+
if (abortRef.current === controller)
|
|
5172
|
+
abortRef.current = null;
|
|
5173
|
+
}
|
|
5174
|
+
}
|
|
5175
|
+
setInputValue(committed.inputLabel ?? committed.label);
|
|
5176
|
+
setTooltipText(committed.label);
|
|
5177
|
+
onChange(committed.id);
|
|
5178
|
+
};
|
|
5179
|
+
const handleKeyDown = (e) => {
|
|
5180
|
+
if (!showDropdown || options.length === 0)
|
|
5181
|
+
return;
|
|
5182
|
+
switch (e.key) {
|
|
5183
|
+
case "ArrowDown":
|
|
5184
|
+
e.preventDefault();
|
|
5185
|
+
setHighlightedIndex(prev => (prev < options.length - 1 ? prev + 1 : 0));
|
|
5186
|
+
break;
|
|
5187
|
+
case "ArrowUp":
|
|
5188
|
+
e.preventDefault();
|
|
5189
|
+
setHighlightedIndex(prev => (prev > 0 ? prev - 1 : options.length - 1));
|
|
5190
|
+
break;
|
|
5191
|
+
case "Enter":
|
|
5192
|
+
e.preventDefault();
|
|
5193
|
+
if (highlightedIndex >= 0 && highlightedIndex < options.length) {
|
|
5194
|
+
handleOptionSelect(options[highlightedIndex].id);
|
|
5195
|
+
}
|
|
5196
|
+
break;
|
|
5197
|
+
case "Escape":
|
|
5198
|
+
setShowDropdown(false);
|
|
5199
|
+
setHighlightedIndex(-1);
|
|
5200
|
+
break;
|
|
5201
|
+
}
|
|
5202
|
+
};
|
|
5203
|
+
const handleInputFocus = () => {
|
|
5204
|
+
if (options.length > 0)
|
|
5205
|
+
setShowDropdown(true);
|
|
5206
|
+
};
|
|
5207
|
+
const handleClear = () => {
|
|
5208
|
+
if (debounceRef.current)
|
|
5209
|
+
window.clearTimeout(debounceRef.current);
|
|
5210
|
+
abortRef.current?.abort();
|
|
5211
|
+
cancelInitialResolve();
|
|
5212
|
+
setInputValue("");
|
|
5213
|
+
setTooltipText("");
|
|
5214
|
+
setOptions([]);
|
|
5215
|
+
setShowDropdown(false);
|
|
5216
|
+
lastQueryRef.current = "";
|
|
5217
|
+
onChange(null);
|
|
5218
|
+
inputRef.current?.focus();
|
|
5219
|
+
};
|
|
5220
|
+
// Resolve a preselected value to display text.
|
|
5221
|
+
React.useEffect(() => {
|
|
5222
|
+
if (value == null)
|
|
5223
|
+
return;
|
|
5224
|
+
if (initialDisplayText) {
|
|
5225
|
+
setInputValue(initialDisplayText);
|
|
5226
|
+
setTooltipText(initialDisplayText);
|
|
5227
|
+
lastResolvedValueRef.current = value;
|
|
5228
|
+
return;
|
|
5229
|
+
}
|
|
5230
|
+
if (!onResolveInitial)
|
|
5231
|
+
return;
|
|
5232
|
+
if (lastResolvedValueRef.current === value)
|
|
5233
|
+
return; // already resolved this value
|
|
5234
|
+
const controller = new AbortController();
|
|
5235
|
+
initialResolveAbortRef.current = controller;
|
|
5236
|
+
setIsInitialLoading(true);
|
|
5237
|
+
onResolveInitial(value, controller.signal)
|
|
5238
|
+
.then(opt => {
|
|
5239
|
+
if (controller.signal.aborted)
|
|
5240
|
+
return;
|
|
5241
|
+
lastResolvedValueRef.current = value; // mark resolved only on success
|
|
5242
|
+
setOptions([opt]);
|
|
5243
|
+
setInputValue(opt.inputLabel ?? opt.label);
|
|
5244
|
+
setTooltipText(opt.label);
|
|
5245
|
+
})
|
|
5246
|
+
.catch(error => {
|
|
5247
|
+
if (!controller.signal.aborted)
|
|
5248
|
+
console.error("CwSearchAsync onResolveInitial failed:", error);
|
|
5249
|
+
})
|
|
5250
|
+
.finally(() => {
|
|
5251
|
+
// Clear the spinner only if this resolve is still the current one (not superseded/cancelled).
|
|
5252
|
+
if (initialResolveAbortRef.current === controller) {
|
|
5253
|
+
setIsInitialLoading(false);
|
|
5254
|
+
initialResolveAbortRef.current = null;
|
|
5255
|
+
}
|
|
5256
|
+
});
|
|
5257
|
+
return () => controller.abort();
|
|
5258
|
+
}, [value, initialDisplayText, onResolveInitial]);
|
|
5259
|
+
// Reset when the controlled value is cleared by the parent.
|
|
5260
|
+
React.useEffect(() => {
|
|
5261
|
+
if (value == null) {
|
|
5262
|
+
if (debounceRef.current)
|
|
5263
|
+
window.clearTimeout(debounceRef.current);
|
|
5264
|
+
abortRef.current?.abort();
|
|
5265
|
+
cancelInitialResolve();
|
|
5266
|
+
setInputValue("");
|
|
5267
|
+
setTooltipText("");
|
|
5268
|
+
setOptions([]);
|
|
5269
|
+
setShowDropdown(false);
|
|
5270
|
+
setHighlightedIndex(-1);
|
|
5271
|
+
lastQueryRef.current = "";
|
|
5272
|
+
lastResolvedValueRef.current = undefined;
|
|
5273
|
+
}
|
|
5274
|
+
}, [value, cancelInitialResolve]);
|
|
5275
|
+
// Re-run the last query when searchTrigger changes (skip first mount).
|
|
5276
|
+
React.useEffect(() => {
|
|
5277
|
+
if (!didMountRef.current) {
|
|
5278
|
+
didMountRef.current = true;
|
|
5279
|
+
return;
|
|
5280
|
+
}
|
|
5281
|
+
if (searchTrigger === undefined)
|
|
5282
|
+
return;
|
|
5283
|
+
if (lastQueryRef.current)
|
|
5284
|
+
runSearchRef.current(lastQueryRef.current);
|
|
5285
|
+
}, [searchTrigger]);
|
|
5286
|
+
// Cleanup on unmount.
|
|
5287
|
+
React.useEffect(() => () => {
|
|
5288
|
+
if (debounceRef.current)
|
|
5289
|
+
window.clearTimeout(debounceRef.current);
|
|
5290
|
+
abortRef.current?.abort();
|
|
5291
|
+
}, []);
|
|
5292
|
+
const direction = alignProps?.flexDirection || "row";
|
|
5293
|
+
return (jsxRuntime.jsxs("div", { className: `cw-search-input ${className}`, style: {
|
|
5294
|
+
...(width ? { width } : {}),
|
|
5295
|
+
...(labelProps?.labelWidth ? { "--label-width": labelProps.labelWidth } : {}),
|
|
5296
|
+
}, "data-direction": direction, children: [jsxRuntime.jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsxRuntime.jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxRuntime.jsxs("div", { className: "cw-search-input-wrapper", ref: wrapperRef, children: [jsxRuntime.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) && (jsxRuntime.jsx("div", { className: "cw-search-input-loading", children: jsxRuntime.jsx(CwIcon, { iconId: "spinner" }) })), jsxRuntime.jsx("div", { className: "cw-search-input-icons", children: inputValue && !disabled && !isInitialLoading ? (jsxRuntime.jsx(CwButton, { type: "button", onClick: handleClear, "aria-label": "Clear selection", variant: "icon", icon: "close", color: "neutral" })) : (icon ? jsxRuntime.jsx(CwIcon, { ...icon }) : jsxRuntime.jsx(CwIcon, { iconId: "search" })) })] })] }), renderPanel(jsxRuntime.jsx("div", { ref: panelRef, id: listboxId, className: dropdownStyles.dropdown, style: panelStyle, role: "listbox", children: jsxRuntime.jsx("ul", { children: options.map((option, index) => (jsxRuntime.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 ? (jsxRuntime.jsxs("span", { className: "cw-search-async-option-lines", children: [jsxRuntime.jsx("span", { children: option.label }), jsxRuntime.jsx("span", { className: "cw-search-async-option-sub", children: option.subLabel })] })) : (option.label) }, option.id))) }) }))] }));
|
|
5297
|
+
};
|
|
5298
|
+
|
|
5040
5299
|
var styles$5 = {"menu":"cw-context-menu-module__menu__tXmun","item":"cw-context-menu-module__item__-ko8L","arrow":"cw-context-menu-module__arrow__LHZmQ"};
|
|
5041
5300
|
|
|
5042
5301
|
const ARROW_SIZE = 8;
|
|
@@ -7003,7 +7262,7 @@ function formatPlainDate(pd, fmt) {
|
|
|
7003
7262
|
.replace("dd", String(pd.day).padStart(2, "0"));
|
|
7004
7263
|
}
|
|
7005
7264
|
|
|
7006
|
-
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, }) {
|
|
7265
|
+
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: locale$1 = locale.enGB, todayLabel = "Today", yearRangeStart, yearRangeEnd, feedback, }) {
|
|
7007
7266
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
7008
7267
|
const [inputValue, setInputValue] = React.useState();
|
|
7009
7268
|
const containerRef = React.useRef(null);
|
|
@@ -7092,6 +7351,19 @@ function CwDatePickerTemporal({ value, onChange, minDate, maxDate, disabledDates
|
|
|
7092
7351
|
setIsOpen(false);
|
|
7093
7352
|
}
|
|
7094
7353
|
}, [minDate, maxDate, onChange]);
|
|
7354
|
+
// Derive start/end months for the year dropdown range, guarding against inverted yearRange props
|
|
7355
|
+
const dropdownStartMonth = React.useMemo(() => {
|
|
7356
|
+
const inferredStart = minDate ? minDate.year : Temporal.Now.plainDateISO().year - 10;
|
|
7357
|
+
const inferredEnd = yearRangeEnd ?? (maxDate ? maxDate.year : Temporal.Now.plainDateISO().year + 5);
|
|
7358
|
+
const year = Math.min(yearRangeStart ?? inferredStart, inferredEnd);
|
|
7359
|
+
return new Date(year, 0, 1);
|
|
7360
|
+
}, [yearRangeStart, yearRangeEnd, minDate, maxDate]);
|
|
7361
|
+
const dropdownEndMonth = React.useMemo(() => {
|
|
7362
|
+
const inferredEnd = maxDate ? maxDate.year : Temporal.Now.plainDateISO().year + 5;
|
|
7363
|
+
const inferredStart = yearRangeStart ?? (minDate ? minDate.year : Temporal.Now.plainDateISO().year - 10);
|
|
7364
|
+
const year = Math.max(yearRangeEnd ?? inferredEnd, inferredStart);
|
|
7365
|
+
return new Date(year, 11, 31);
|
|
7366
|
+
}, [yearRangeStart, yearRangeEnd, minDate, maxDate]);
|
|
7095
7367
|
// Memoize disabled days (convert PlainDate[] to Date[] for DayPicker)
|
|
7096
7368
|
const disabledDays = React.useMemo(() => [
|
|
7097
7369
|
...(disabledDates?.map(plainDateToJsDate) || []),
|
|
@@ -7103,13 +7375,463 @@ function CwDatePickerTemporal({ value, onChange, minDate, maxDate, disabledDates
|
|
|
7103
7375
|
const defaultMonthJsDate = defaultMonth
|
|
7104
7376
|
? new Date(defaultMonth.year, defaultMonth.month - 1, 1)
|
|
7105
7377
|
: selectedJsDate;
|
|
7106
|
-
return (jsxRuntime.jsxs("div", { ref: containerRef, className: `cw-datepicker ${className || ""}`, children: [jsxRuntime.jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsxRuntime.jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxRuntime.jsxs("div", { ref: wrapperRef, className: styles$4.pickerWrapper, children: [jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: inputValue, placeholder: placeholder, onChange: handleInputChange, onBlur: handleInputBlur, onClick: handleInputClick, onKeyDown: handleInputKeyDown, disabled: disabled, required: required }), jsxRuntime.jsx("div", { className: styles$4.pickerIcons, children: showClear && value && !disabled ? (jsxRuntime.jsx(CwButton, { type: "button", variant: "icon", color: "neutral", icon: "close", onClick: handleClear, tabIndex: -1, "aria-label": "Clear date" })) : (jsxRuntime.jsx(CwIcon, { iconId: "calendar" })) }), renderPopup(jsxRuntime.jsxs("div", { ref: popupRef, className: styles$4.pickerPopup, style: popupStyle, children: [jsxRuntime.jsx(reactDayPicker.DayPicker, { mode: "single", selected: selectedJsDate, defaultMonth: defaultMonthJsDate, onSelect: handleDaySelect, disabled: disabledDays, numberOfMonths: numberOfMonths, modifiers: {
|
|
7378
|
+
return (jsxRuntime.jsxs("div", { ref: containerRef, className: `cw-datepicker ${className || ""}`, children: [jsxRuntime.jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsxRuntime.jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxRuntime.jsxs("div", { ref: wrapperRef, className: styles$4.pickerWrapper, children: [jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: inputValue, placeholder: placeholder, onChange: handleInputChange, onBlur: handleInputBlur, onClick: handleInputClick, onKeyDown: handleInputKeyDown, disabled: disabled, required: required }), jsxRuntime.jsx("div", { className: styles$4.pickerIcons, children: showClear && value && !disabled ? (jsxRuntime.jsx(CwButton, { type: "button", variant: "icon", color: "neutral", icon: "close", onClick: handleClear, tabIndex: -1, "aria-label": "Clear date" })) : (jsxRuntime.jsx(CwIcon, { iconId: "calendar" })) }), renderPopup(jsxRuntime.jsxs("div", { ref: popupRef, className: styles$4.pickerPopup, style: popupStyle, children: [jsxRuntime.jsx(reactDayPicker.DayPicker, { mode: "single", selected: selectedJsDate, defaultMonth: defaultMonthJsDate, onSelect: handleDaySelect, disabled: disabledDays, numberOfMonths: numberOfMonths, locale: locale$1, captionLayout: "dropdown", startMonth: dropdownStartMonth, endMonth: dropdownEndMonth, modifiers: {
|
|
7107
7379
|
today: new Date(),
|
|
7108
7380
|
}, modifiersClassNames: {
|
|
7109
7381
|
today: "rdp-day-today",
|
|
7110
7382
|
} }), showTodayButton && (jsxRuntime.jsx("footer", { className: "cw-flex-row cw-align-right-center", children: jsxRuntime.jsx(CwButton, { type: "button", variant: "outline", onClick: handleTodayClick, text: todayLabel }) }))] }))] })] }), feedbackMessages.map((feedbackItem, index) => (jsxRuntime.jsx("p", { className: "cw-input-info", "data-color": feedbackItem.type, children: feedbackItem.message }, index)))] }));
|
|
7111
7383
|
}
|
|
7112
7384
|
|
|
7385
|
+
// dateRangePresetsTemporal.ts
|
|
7386
|
+
// Temporal-typed counterpart of dateRangePresets.ts (used by CwDateRangePickerTemporal).
|
|
7387
|
+
// Each preset is evaluated at click time; a reference date can be injected for testing.
|
|
7388
|
+
const ref = (today) => today ?? Temporal.Now.plainDateISO();
|
|
7389
|
+
/** Monday as first day of the week (ISO dayOfWeek: 1 = Mon … 7 = Sun). */
|
|
7390
|
+
const startOfWeek = (d) => d.subtract({ days: d.dayOfWeek - 1 });
|
|
7391
|
+
const startOfMonth = (d) => d.with({ day: 1 });
|
|
7392
|
+
const endOfMonth = (d) => d.with({ day: d.daysInMonth });
|
|
7393
|
+
/** First day of the quarter that contains `d`. */
|
|
7394
|
+
const startOfQuarter = (d) => Temporal.PlainDate.from({ year: d.year, month: Math.floor((d.month - 1) / 3) * 3 + 1, day: 1 });
|
|
7395
|
+
const PRESET_LIBRARY_TEMPORAL = {
|
|
7396
|
+
// === PAST DAYS ===
|
|
7397
|
+
today: {
|
|
7398
|
+
key: "today",
|
|
7399
|
+
label: "Today",
|
|
7400
|
+
getValue: (t) => { const d = ref(t); return { from: d, to: d }; },
|
|
7401
|
+
},
|
|
7402
|
+
yesterday: {
|
|
7403
|
+
key: "yesterday",
|
|
7404
|
+
label: "Yesterday",
|
|
7405
|
+
getValue: (t) => { const d = ref(t).subtract({ days: 1 }); return { from: d, to: d }; },
|
|
7406
|
+
},
|
|
7407
|
+
past2Days: {
|
|
7408
|
+
key: "past2Days",
|
|
7409
|
+
label: "Past 2 Days",
|
|
7410
|
+
getValue: (t) => { const d = ref(t); return { from: d.subtract({ days: 2 }), to: d }; },
|
|
7411
|
+
},
|
|
7412
|
+
past3Days: {
|
|
7413
|
+
key: "past3Days",
|
|
7414
|
+
label: "Past 3 Days",
|
|
7415
|
+
getValue: (t) => { const d = ref(t); return { from: d.subtract({ days: 3 }), to: d }; },
|
|
7416
|
+
},
|
|
7417
|
+
past7Days: {
|
|
7418
|
+
key: "past7Days",
|
|
7419
|
+
label: "Past 7 days",
|
|
7420
|
+
getValue: (t) => { const d = ref(t); return { from: d.subtract({ days: 7 }), to: d }; },
|
|
7421
|
+
},
|
|
7422
|
+
past14Days: {
|
|
7423
|
+
key: "past14Days",
|
|
7424
|
+
label: "Past 14 days",
|
|
7425
|
+
getValue: (t) => { const d = ref(t); return { from: d.subtract({ days: 14 }), to: d }; },
|
|
7426
|
+
},
|
|
7427
|
+
past30Days: {
|
|
7428
|
+
key: "past30Days",
|
|
7429
|
+
label: "Past 30 days",
|
|
7430
|
+
getValue: (t) => { const d = ref(t); return { from: d.subtract({ days: 30 }), to: d }; },
|
|
7431
|
+
},
|
|
7432
|
+
past90Days: {
|
|
7433
|
+
key: "past90Days",
|
|
7434
|
+
label: "Past 90 days",
|
|
7435
|
+
getValue: (t) => { const d = ref(t); return { from: d.subtract({ days: 90 }), to: d }; },
|
|
7436
|
+
},
|
|
7437
|
+
// === NEXT DAYS ===
|
|
7438
|
+
next7Days: {
|
|
7439
|
+
key: "next7Days",
|
|
7440
|
+
label: "Next 7 days",
|
|
7441
|
+
getValue: (t) => { const d = ref(t); return { from: d, to: d.add({ days: 7 }) }; },
|
|
7442
|
+
},
|
|
7443
|
+
next14Days: {
|
|
7444
|
+
key: "next14Days",
|
|
7445
|
+
label: "Next 14 days",
|
|
7446
|
+
getValue: (t) => { const d = ref(t); return { from: d, to: d.add({ days: 14 }) }; },
|
|
7447
|
+
},
|
|
7448
|
+
next30Days: {
|
|
7449
|
+
key: "next30Days",
|
|
7450
|
+
label: "Next 30 days",
|
|
7451
|
+
getValue: (t) => { const d = ref(t); return { from: d, to: d.add({ days: 30 }) }; },
|
|
7452
|
+
},
|
|
7453
|
+
// === WEEKS (Monday-first) ===
|
|
7454
|
+
thisWeek: {
|
|
7455
|
+
key: "thisWeek",
|
|
7456
|
+
label: "This week",
|
|
7457
|
+
getValue: (t) => { const s = startOfWeek(ref(t)); return { from: s, to: s.add({ days: 6 }) }; },
|
|
7458
|
+
},
|
|
7459
|
+
lastWeek: {
|
|
7460
|
+
key: "lastWeek",
|
|
7461
|
+
label: "Last week",
|
|
7462
|
+
getValue: (t) => { const end = startOfWeek(ref(t)).subtract({ days: 1 }); return { from: end.subtract({ days: 6 }), to: end }; },
|
|
7463
|
+
},
|
|
7464
|
+
nextWeek: {
|
|
7465
|
+
key: "nextWeek",
|
|
7466
|
+
label: "Next week",
|
|
7467
|
+
getValue: (t) => { const s = startOfWeek(ref(t)).add({ days: 7 }); return { from: s, to: s.add({ days: 6 }) }; },
|
|
7468
|
+
},
|
|
7469
|
+
// === MONTHS ===
|
|
7470
|
+
thisMonth: {
|
|
7471
|
+
key: "thisMonth",
|
|
7472
|
+
label: "This month",
|
|
7473
|
+
getValue: (t) => { const d = ref(t); return { from: startOfMonth(d), to: endOfMonth(d) }; },
|
|
7474
|
+
},
|
|
7475
|
+
lastMonth: {
|
|
7476
|
+
key: "lastMonth",
|
|
7477
|
+
label: "Last month",
|
|
7478
|
+
getValue: (t) => { const d = startOfMonth(ref(t)).subtract({ months: 1 }); return { from: d, to: endOfMonth(d) }; },
|
|
7479
|
+
},
|
|
7480
|
+
nextMonth: {
|
|
7481
|
+
key: "nextMonth",
|
|
7482
|
+
label: "Next month",
|
|
7483
|
+
getValue: (t) => { const d = startOfMonth(ref(t)).add({ months: 1 }); return { from: d, to: endOfMonth(d) }; },
|
|
7484
|
+
},
|
|
7485
|
+
// === QUARTERS ===
|
|
7486
|
+
thisQuarter: {
|
|
7487
|
+
key: "thisQuarter",
|
|
7488
|
+
label: "This quarter",
|
|
7489
|
+
getValue: (t) => { const from = startOfQuarter(ref(t)); return { from, to: from.add({ months: 3 }).subtract({ days: 1 }) }; },
|
|
7490
|
+
},
|
|
7491
|
+
lastQuarter: {
|
|
7492
|
+
key: "lastQuarter",
|
|
7493
|
+
label: "Last quarter",
|
|
7494
|
+
getValue: (t) => { const from = startOfQuarter(ref(t)).subtract({ months: 3 }); return { from, to: from.add({ months: 3 }).subtract({ days: 1 }) }; },
|
|
7495
|
+
},
|
|
7496
|
+
nextQuarter: {
|
|
7497
|
+
key: "nextQuarter",
|
|
7498
|
+
label: "Next quarter",
|
|
7499
|
+
getValue: (t) => { const from = startOfQuarter(ref(t)).add({ months: 3 }); return { from, to: from.add({ months: 3 }).subtract({ days: 1 }) }; },
|
|
7500
|
+
},
|
|
7501
|
+
// === YEAR ===
|
|
7502
|
+
thisYear: {
|
|
7503
|
+
key: "thisYear",
|
|
7504
|
+
label: "This year",
|
|
7505
|
+
getValue: (t) => {
|
|
7506
|
+
const y = ref(t).year;
|
|
7507
|
+
return { from: Temporal.PlainDate.from({ year: y, month: 1, day: 1 }), to: Temporal.PlainDate.from({ year: y, month: 12, day: 31 }) };
|
|
7508
|
+
},
|
|
7509
|
+
},
|
|
7510
|
+
lastYear: {
|
|
7511
|
+
key: "lastYear",
|
|
7512
|
+
label: "Last year",
|
|
7513
|
+
getValue: (t) => {
|
|
7514
|
+
const y = ref(t).year - 1;
|
|
7515
|
+
return { from: Temporal.PlainDate.from({ year: y, month: 1, day: 1 }), to: Temporal.PlainDate.from({ year: y, month: 12, day: 31 }) };
|
|
7516
|
+
},
|
|
7517
|
+
},
|
|
7518
|
+
};
|
|
7519
|
+
/**
|
|
7520
|
+
* Helper to get presets by their keys.
|
|
7521
|
+
* @example getPresetsByKeysTemporal(['thisWeek', 'past7Days', 'past30Days'])
|
|
7522
|
+
*/
|
|
7523
|
+
const getPresetsByKeysTemporal = (keys) => keys.map((key) => PRESET_LIBRARY_TEMPORAL[key]).filter((preset) => preset !== undefined);
|
|
7524
|
+
|
|
7525
|
+
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: locale$1 = locale.enGB, yearRangeStart, yearRangeEnd, showPresets = false, presetKeys, customPresets, }) {
|
|
7526
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
7527
|
+
const [inputFromValue, setInputFromValue] = React.useState("");
|
|
7528
|
+
const [inputToValue, setInputToValue] = React.useState("");
|
|
7529
|
+
const [rangeErrorMessage, setRangeErrorMessage] = React.useState(null);
|
|
7530
|
+
const [focusedInput, setFocusedInput] = React.useState();
|
|
7531
|
+
const containerRef = React.useRef(null);
|
|
7532
|
+
const wrapperRef = React.useRef(null);
|
|
7533
|
+
const inputFromRef = React.useRef(null);
|
|
7534
|
+
const inputToRef = React.useRef(null);
|
|
7535
|
+
const prevValueRef = React.useRef(undefined);
|
|
7536
|
+
const prevDisplayFormatRef = React.useRef(displayFormat);
|
|
7537
|
+
const rangeError = React.useCallback(() => (maxRangeDays ? `Range cannot exceed ${maxRangeDays} days` : "Invalid date range"), [maxRangeDays]);
|
|
7538
|
+
const handleClose = React.useCallback(() => {
|
|
7539
|
+
setIsOpen(false);
|
|
7540
|
+
setFocusedInput(undefined);
|
|
7541
|
+
}, []);
|
|
7542
|
+
const { popupRef, popupStyle, renderPopup } = usePickerPopup({
|
|
7543
|
+
anchorRef: wrapperRef,
|
|
7544
|
+
isOpen,
|
|
7545
|
+
onClose: handleClose,
|
|
7546
|
+
position: popupPosition,
|
|
7547
|
+
});
|
|
7548
|
+
// Calculate presets to render
|
|
7549
|
+
const presetsToRender = React.useMemo(() => {
|
|
7550
|
+
let presets = [];
|
|
7551
|
+
if (presetKeys && presetKeys.length > 0) {
|
|
7552
|
+
presets = getPresetsByKeysTemporal(presetKeys);
|
|
7553
|
+
}
|
|
7554
|
+
if (customPresets && customPresets.length > 0) {
|
|
7555
|
+
presets = [...presets, ...customPresets];
|
|
7556
|
+
}
|
|
7557
|
+
return presets;
|
|
7558
|
+
}, [presetKeys, customPresets]);
|
|
7559
|
+
// Validate date range
|
|
7560
|
+
const validateRange = React.useCallback((from, to) => {
|
|
7561
|
+
if (!from || !to)
|
|
7562
|
+
return true; // Partial ranges are OK during selection
|
|
7563
|
+
if (Temporal.PlainDate.compare(from, to) > 0)
|
|
7564
|
+
return false;
|
|
7565
|
+
if (maxRangeDays) {
|
|
7566
|
+
const daysDiff = from.until(to, { largestUnit: "day" }).days;
|
|
7567
|
+
if (daysDiff > maxRangeDays)
|
|
7568
|
+
return false;
|
|
7569
|
+
}
|
|
7570
|
+
return true;
|
|
7571
|
+
}, [maxRangeDays]);
|
|
7572
|
+
// Sync input values with prop value
|
|
7573
|
+
React.useEffect(() => {
|
|
7574
|
+
const currentFrom = value?.from?.toString();
|
|
7575
|
+
const currentTo = value?.to?.toString();
|
|
7576
|
+
const prevFrom = prevValueRef.current?.from;
|
|
7577
|
+
const prevTo = prevValueRef.current?.to;
|
|
7578
|
+
if (currentFrom !== prevFrom ||
|
|
7579
|
+
currentTo !== prevTo ||
|
|
7580
|
+
prevDisplayFormatRef.current !== displayFormat) {
|
|
7581
|
+
prevValueRef.current = { from: currentFrom, to: currentTo };
|
|
7582
|
+
prevDisplayFormatRef.current = displayFormat;
|
|
7583
|
+
setInputFromValue(value?.from ? formatPlainDate(value.from, displayFormat) : "");
|
|
7584
|
+
setInputToValue(value?.to ? formatPlainDate(value.to, displayFormat) : "");
|
|
7585
|
+
}
|
|
7586
|
+
}, [value, displayFormat]);
|
|
7587
|
+
const handleRangeSelect = React.useCallback((range, selectedDay) => {
|
|
7588
|
+
const selected = jsDateToPlainDate(selectedDay);
|
|
7589
|
+
let newRange;
|
|
7590
|
+
if (focusedInput === 'from') {
|
|
7591
|
+
newRange = { from: selected, to: value?.to };
|
|
7592
|
+
if (newRange.to && Temporal.PlainDate.compare(selected, newRange.to) > 0) {
|
|
7593
|
+
newRange.to = undefined;
|
|
7594
|
+
setFocusedInput('to');
|
|
7595
|
+
setRangeErrorMessage(null);
|
|
7596
|
+
}
|
|
7597
|
+
else if (newRange.to) {
|
|
7598
|
+
if (!validateRange(newRange.from, newRange.to)) {
|
|
7599
|
+
setRangeErrorMessage(rangeError());
|
|
7600
|
+
return;
|
|
7601
|
+
}
|
|
7602
|
+
setRangeErrorMessage(null);
|
|
7603
|
+
setIsOpen(false);
|
|
7604
|
+
setFocusedInput(undefined);
|
|
7605
|
+
}
|
|
7606
|
+
else {
|
|
7607
|
+
setFocusedInput('to');
|
|
7608
|
+
setRangeErrorMessage(null);
|
|
7609
|
+
}
|
|
7610
|
+
}
|
|
7611
|
+
else if (focusedInput === 'to') {
|
|
7612
|
+
newRange = { from: value?.from, to: selected };
|
|
7613
|
+
if (newRange.from && Temporal.PlainDate.compare(selected, newRange.from) < 0) {
|
|
7614
|
+
newRange.from = undefined;
|
|
7615
|
+
setFocusedInput('from');
|
|
7616
|
+
setRangeErrorMessage(null);
|
|
7617
|
+
}
|
|
7618
|
+
else if (newRange.from) {
|
|
7619
|
+
if (!validateRange(newRange.from, newRange.to)) {
|
|
7620
|
+
setRangeErrorMessage(rangeError());
|
|
7621
|
+
return;
|
|
7622
|
+
}
|
|
7623
|
+
setRangeErrorMessage(null);
|
|
7624
|
+
setIsOpen(false);
|
|
7625
|
+
setFocusedInput(undefined);
|
|
7626
|
+
}
|
|
7627
|
+
else {
|
|
7628
|
+
setFocusedInput('from');
|
|
7629
|
+
setRangeErrorMessage(null);
|
|
7630
|
+
}
|
|
7631
|
+
}
|
|
7632
|
+
else {
|
|
7633
|
+
if (!range) {
|
|
7634
|
+
onChange(undefined);
|
|
7635
|
+
return;
|
|
7636
|
+
}
|
|
7637
|
+
newRange = {
|
|
7638
|
+
from: range.from ? jsDateToPlainDate(range.from) : undefined,
|
|
7639
|
+
to: range.to ? jsDateToPlainDate(range.to) : undefined,
|
|
7640
|
+
};
|
|
7641
|
+
if (range.from && range.to) {
|
|
7642
|
+
if (!validateRange(newRange.from, newRange.to)) {
|
|
7643
|
+
setRangeErrorMessage(rangeError());
|
|
7644
|
+
return;
|
|
7645
|
+
}
|
|
7646
|
+
setRangeErrorMessage(null);
|
|
7647
|
+
setIsOpen(false);
|
|
7648
|
+
setFocusedInput(undefined);
|
|
7649
|
+
}
|
|
7650
|
+
else if (range.from) {
|
|
7651
|
+
setFocusedInput('to');
|
|
7652
|
+
setRangeErrorMessage(null);
|
|
7653
|
+
}
|
|
7654
|
+
else {
|
|
7655
|
+
setFocusedInput('from');
|
|
7656
|
+
setRangeErrorMessage(null);
|
|
7657
|
+
}
|
|
7658
|
+
}
|
|
7659
|
+
onChange(newRange);
|
|
7660
|
+
}, [onChange, validateRange, value, focusedInput, rangeError]);
|
|
7661
|
+
const handleClearFrom = React.useCallback((e) => {
|
|
7662
|
+
e.stopPropagation();
|
|
7663
|
+
onChange({ from: undefined, to: value?.to });
|
|
7664
|
+
setInputFromValue("");
|
|
7665
|
+
}, [onChange, value]);
|
|
7666
|
+
const handleClearTo = React.useCallback((e) => {
|
|
7667
|
+
e.stopPropagation();
|
|
7668
|
+
onChange({ from: value?.from, to: undefined });
|
|
7669
|
+
setInputToValue("");
|
|
7670
|
+
}, [onChange, value]);
|
|
7671
|
+
const handleInputFromClick = React.useCallback(() => {
|
|
7672
|
+
if (!disabled) {
|
|
7673
|
+
setIsOpen(true);
|
|
7674
|
+
setFocusedInput('from');
|
|
7675
|
+
}
|
|
7676
|
+
}, [disabled]);
|
|
7677
|
+
const handleInputToClick = React.useCallback(() => {
|
|
7678
|
+
if (!disabled) {
|
|
7679
|
+
setIsOpen(true);
|
|
7680
|
+
setFocusedInput('to');
|
|
7681
|
+
}
|
|
7682
|
+
}, [disabled]);
|
|
7683
|
+
const handleInputFromChange = React.useCallback((e) => {
|
|
7684
|
+
const newValue = e.currentTarget.value;
|
|
7685
|
+
setInputFromValue(newValue);
|
|
7686
|
+
const parsed = parseDate(newValue, displayFormat);
|
|
7687
|
+
if (parsed) {
|
|
7688
|
+
if (validate(parsed, minDate, maxDate) && validateRange(parsed, value?.to)) {
|
|
7689
|
+
onChange({ from: parsed, to: value?.to });
|
|
7690
|
+
}
|
|
7691
|
+
}
|
|
7692
|
+
else if (newValue === "") {
|
|
7693
|
+
onChange({ from: undefined, to: value?.to });
|
|
7694
|
+
}
|
|
7695
|
+
}, [displayFormat, minDate, maxDate, validateRange, onChange, value]);
|
|
7696
|
+
const handleInputToChange = React.useCallback((e) => {
|
|
7697
|
+
const newValue = e.currentTarget.value;
|
|
7698
|
+
setInputToValue(newValue);
|
|
7699
|
+
const parsed = parseDate(newValue, displayFormat);
|
|
7700
|
+
if (parsed) {
|
|
7701
|
+
if (validate(parsed, minDate, maxDate) && validateRange(value?.from, parsed)) {
|
|
7702
|
+
onChange({ from: value?.from, to: parsed });
|
|
7703
|
+
}
|
|
7704
|
+
}
|
|
7705
|
+
else if (newValue === "") {
|
|
7706
|
+
onChange({ from: value?.from, to: undefined });
|
|
7707
|
+
}
|
|
7708
|
+
}, [displayFormat, minDate, maxDate, validateRange, onChange, value]);
|
|
7709
|
+
const handleInputBlur = React.useCallback((type) => {
|
|
7710
|
+
const inputValue = type === 'from' ? inputFromValue : inputToValue;
|
|
7711
|
+
const currentValue = type === 'from' ? value?.from : value?.to;
|
|
7712
|
+
const trimmed = inputValue?.trim();
|
|
7713
|
+
if (trimmed) {
|
|
7714
|
+
const parsed = parseDate(trimmed, displayFormat);
|
|
7715
|
+
if (parsed && validate(parsed, minDate, maxDate)) {
|
|
7716
|
+
const newRange = type === 'from'
|
|
7717
|
+
? { from: parsed, to: value?.to }
|
|
7718
|
+
: { from: value?.from, to: parsed };
|
|
7719
|
+
if (validateRange(newRange.from, newRange.to)) {
|
|
7720
|
+
onChange(newRange);
|
|
7721
|
+
if (type === 'from') {
|
|
7722
|
+
setInputFromValue(formatPlainDate(parsed, displayFormat));
|
|
7723
|
+
}
|
|
7724
|
+
else {
|
|
7725
|
+
setInputToValue(formatPlainDate(parsed, displayFormat));
|
|
7726
|
+
}
|
|
7727
|
+
return;
|
|
7728
|
+
}
|
|
7729
|
+
}
|
|
7730
|
+
}
|
|
7731
|
+
// Reset to current value if invalid
|
|
7732
|
+
if (type === 'from') {
|
|
7733
|
+
setInputFromValue(currentValue ? formatPlainDate(currentValue, displayFormat) : "");
|
|
7734
|
+
}
|
|
7735
|
+
else {
|
|
7736
|
+
setInputToValue(currentValue ? formatPlainDate(currentValue, displayFormat) : "");
|
|
7737
|
+
}
|
|
7738
|
+
}, [inputFromValue, inputToValue, value, displayFormat, minDate, maxDate, validateRange, onChange]);
|
|
7739
|
+
const handleInputKeyDown = React.useCallback((e, type) => {
|
|
7740
|
+
if (e.key === "Enter") {
|
|
7741
|
+
e.preventDefault();
|
|
7742
|
+
if (type === 'from') {
|
|
7743
|
+
inputFromRef.current?.blur();
|
|
7744
|
+
inputToRef.current?.focus();
|
|
7745
|
+
}
|
|
7746
|
+
else {
|
|
7747
|
+
inputToRef.current?.blur();
|
|
7748
|
+
setIsOpen(false);
|
|
7749
|
+
}
|
|
7750
|
+
}
|
|
7751
|
+
else if (e.key === "Escape") {
|
|
7752
|
+
setIsOpen(false);
|
|
7753
|
+
if (type === 'from') {
|
|
7754
|
+
inputFromRef.current?.blur();
|
|
7755
|
+
}
|
|
7756
|
+
else {
|
|
7757
|
+
inputToRef.current?.blur();
|
|
7758
|
+
}
|
|
7759
|
+
}
|
|
7760
|
+
}, []);
|
|
7761
|
+
// Handle preset click
|
|
7762
|
+
const handlePresetClick = React.useCallback((preset, e) => {
|
|
7763
|
+
e.preventDefault();
|
|
7764
|
+
e.stopPropagation();
|
|
7765
|
+
const nextRange = preset.getValue();
|
|
7766
|
+
if (!validate(nextRange.from, minDate, maxDate) ||
|
|
7767
|
+
!validate(nextRange.to, minDate, maxDate) ||
|
|
7768
|
+
!validateRange(nextRange.from, nextRange.to)) {
|
|
7769
|
+
setRangeErrorMessage(rangeError());
|
|
7770
|
+
return;
|
|
7771
|
+
}
|
|
7772
|
+
setRangeErrorMessage(null);
|
|
7773
|
+
onChange(nextRange);
|
|
7774
|
+
setIsOpen(false);
|
|
7775
|
+
}, [maxDate, minDate, onChange, rangeError, validateRange]);
|
|
7776
|
+
// Derive start/end months for the year dropdown range, guarding against inverted yearRange props
|
|
7777
|
+
const dropdownStartMonth = React.useMemo(() => {
|
|
7778
|
+
const inferredStart = minDate ? minDate.year : Temporal.Now.plainDateISO().year - 10;
|
|
7779
|
+
const inferredEnd = yearRangeEnd ?? (maxDate ? maxDate.year : Temporal.Now.plainDateISO().year + 5);
|
|
7780
|
+
const year = Math.min(yearRangeStart ?? inferredStart, inferredEnd);
|
|
7781
|
+
return new Date(year, 0, 1);
|
|
7782
|
+
}, [yearRangeStart, yearRangeEnd, minDate, maxDate]);
|
|
7783
|
+
const dropdownEndMonth = React.useMemo(() => {
|
|
7784
|
+
const inferredEnd = maxDate ? maxDate.year : Temporal.Now.plainDateISO().year + 5;
|
|
7785
|
+
const inferredStart = yearRangeStart ?? (minDate ? minDate.year : Temporal.Now.plainDateISO().year - 10);
|
|
7786
|
+
const year = Math.max(yearRangeEnd ?? inferredEnd, inferredStart);
|
|
7787
|
+
return new Date(year, 11, 31);
|
|
7788
|
+
}, [yearRangeStart, yearRangeEnd, minDate, maxDate]);
|
|
7789
|
+
// Memoize disabled days (convert Temporal → Date for DayPicker)
|
|
7790
|
+
const disabledDays = React.useMemo(() => {
|
|
7791
|
+
const baseDisabled = [
|
|
7792
|
+
...(disabledDates?.map(plainDateToJsDate) || []),
|
|
7793
|
+
...(minDate ? [{ before: plainDateToJsDate(minDate) }] : []),
|
|
7794
|
+
...(maxDate ? [{ after: plainDateToJsDate(maxDate) }] : []),
|
|
7795
|
+
...(disabledMatcher ? [disabledMatcher] : []),
|
|
7796
|
+
];
|
|
7797
|
+
// If maxRangeDays is set and we have a "from" date, disable dates beyond the range
|
|
7798
|
+
if (maxRangeDays) {
|
|
7799
|
+
if (value?.from) {
|
|
7800
|
+
const maxAllowedDate = value.from.add({ days: maxRangeDays });
|
|
7801
|
+
if (focusedInput !== 'from') {
|
|
7802
|
+
baseDisabled.push({ after: plainDateToJsDate(maxAllowedDate) });
|
|
7803
|
+
}
|
|
7804
|
+
if (focusedInput === 'to' && !value?.to) {
|
|
7805
|
+
const minAllowedDate = value.from.subtract({ days: maxRangeDays });
|
|
7806
|
+
baseDisabled.push({ before: plainDateToJsDate(minAllowedDate) });
|
|
7807
|
+
}
|
|
7808
|
+
}
|
|
7809
|
+
if (value?.to && !value?.from && focusedInput === 'from') {
|
|
7810
|
+
const minAllowedDate = value.to.subtract({ days: maxRangeDays });
|
|
7811
|
+
baseDisabled.push({ before: plainDateToJsDate(minAllowedDate) });
|
|
7812
|
+
}
|
|
7813
|
+
}
|
|
7814
|
+
return baseDisabled;
|
|
7815
|
+
}, [disabledDates, minDate, maxDate, disabledMatcher, maxRangeDays, value, focusedInput]);
|
|
7816
|
+
const selectedRange = React.useMemo(() => {
|
|
7817
|
+
if (value?.from || value?.to) {
|
|
7818
|
+
return {
|
|
7819
|
+
from: value?.from ? plainDateToJsDate(value.from) : undefined,
|
|
7820
|
+
to: value?.to ? plainDateToJsDate(value.to) : undefined,
|
|
7821
|
+
};
|
|
7822
|
+
}
|
|
7823
|
+
return undefined;
|
|
7824
|
+
}, [value]);
|
|
7825
|
+
const defaultMonthJsDate = defaultMonth
|
|
7826
|
+
? new Date(defaultMonth.year, defaultMonth.month - 1, 1)
|
|
7827
|
+
: (value?.from ? plainDateToJsDate(value.from) : undefined);
|
|
7828
|
+
return (jsxRuntime.jsx("div", { ref: containerRef, className: `cw-rangepicker ${className || ""}`, children: jsxRuntime.jsxs(CwAlign, { ...alignProps, itemProp: required ? "required" : "", children: [labelProps && (jsxRuntime.jsx(CwLabel, { ...labelProps, children: labelProps.text })), jsxRuntime.jsxs("div", { ref: wrapperRef, className: rangeStyles.rangeWrapper, children: [jsxRuntime.jsxs("div", { className: styles$4.pickerWrapper, children: [jsxRuntime.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' }), jsxRuntime.jsx("div", { className: styles$4.pickerIcons, children: showClear && value?.from && !disabled ? (jsxRuntime.jsx(CwButton, { type: "button", variant: "icon", color: "neutral", icon: "close", onClick: handleClearFrom, tabIndex: -1, "aria-label": "Clear from date" })) : (jsxRuntime.jsx(CwIcon, { iconId: "calendar" })) })] }), jsxRuntime.jsx(CwIcon, { iconId: "arrow-right", size: "medium" }), jsxRuntime.jsxs("div", { className: styles$4.pickerWrapper, children: [jsxRuntime.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' }), jsxRuntime.jsx("div", { className: styles$4.pickerIcons, children: showClear && value?.to && !disabled ? (jsxRuntime.jsx(CwButton, { type: "button", variant: "icon", color: "neutral", icon: "close", onClick: handleClearTo, tabIndex: -1, "aria-label": "Clear to date" })) : (jsxRuntime.jsx(CwIcon, { iconId: "calendar" })) })] }), renderPopup(jsxRuntime.jsxs("div", { ref: popupRef, className: `${styles$4.pickerPopup} ${rangeStyles.rangePopup}`, style: popupStyle, children: [jsxRuntime.jsxs("div", { className: rangeStyles.rangeCalendarRow, children: [showPresets && presetsToRender.length > 0 && (jsxRuntime.jsx("div", { className: rangeStyles.presetList, children: presetsToRender.map((preset) => (jsxRuntime.jsx("button", { type: "button", onClick: (e) => handlePresetClick(preset, e), children: preset.label }, preset.key))) })), jsxRuntime.jsx(reactDayPicker.DayPicker, { mode: "range", selected: selectedRange, onSelect: handleRangeSelect, disabled: disabledDays, numberOfMonths: numberOfMonths, defaultMonth: defaultMonthJsDate, locale: locale$1, captionLayout: "dropdown", startMonth: dropdownStartMonth, endMonth: dropdownEndMonth, modifiers: {
|
|
7829
|
+
today: new Date(),
|
|
7830
|
+
}, modifiersClassNames: {
|
|
7831
|
+
today: "rdp-day-today",
|
|
7832
|
+
} })] }), jsxRuntime.jsx("div", { role: "alert", "aria-live": "polite", className: rangeStyles.rangeError, children: rangeErrorMessage })] }))] })] }) }));
|
|
7833
|
+
}
|
|
7834
|
+
|
|
7113
7835
|
const ResourcesTitleList = ({ resources, canBePinned, includesPinned, onCrewPinning, onClickResourceContextMenu, ResourceTitleComponent, }) => {
|
|
7114
7836
|
const onClickContextMenu = React.useCallback((key, resource) => {
|
|
7115
7837
|
if (onClickResourceContextMenu) {
|
|
@@ -9602,13 +10324,13 @@ const PinRowHeader = ({ value, width, onEvent }) => {
|
|
|
9602
10324
|
jsxRuntime.jsxs("span", { className: styles["scheduler-crewmember-functions"], children: ["(", value.title3, ")"] }), value.subtitle2 && jsxRuntime.jsxs("span", { children: ["-", value.subtitle2] })] })] }), isLoading ? jsxRuntime.jsx("span", { className: "cwi-icons cwi-spinner" }) : undefined] }) }, value.rowId) }, value.rowId);
|
|
9603
10325
|
};
|
|
9604
10326
|
|
|
9605
|
-
const SuperScheduler = ({ id, state, header, rows, events, headerColumnComp, pinnedOrderCategory, unPinnedOrderCategory, backgroundEvents, indicatorRows = [], contextMenuItems, onEvent, rowHeightRem = 1.75, rowHeaderWidth, maxEventHeightRem, }) => {
|
|
10327
|
+
const SuperScheduler = ({ id, state, header, rows, events, headerColumnComp, pinnedOrderCategory, unPinnedOrderCategory, backgroundEvents, indicatorRows = [], contextMenuItems, onEvent, rowHeightRem = 1.75, rowHeaderWidth, maxEventHeightRem, EventComp = SchedulerEvent, }) => {
|
|
9606
10328
|
const pinnedRows = rows.filter((it) => it.isPinned);
|
|
9607
10329
|
const notPinnedRows = rows.filter((it) => !it.isPinned);
|
|
9608
10330
|
const isFirstVisible = pinnedRows.length > 0;
|
|
9609
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [isFirstVisible && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Scheduler, { id: `${id}-pinned`, state: state, header: header, rows: pinnedRows, events: events, backgroundEvents: backgroundEvents, indicatorRows: indicatorRows, contextMenuItems: contextMenuItems, orderCategories: pinnedOrderCategory, onEvent: onEvent, EventComp:
|
|
10331
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [isFirstVisible && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Scheduler, { id: `${id}-pinned`, state: state, header: header, rows: pinnedRows, events: events, backgroundEvents: backgroundEvents, indicatorRows: indicatorRows, contextMenuItems: contextMenuItems, orderCategories: pinnedOrderCategory, onEvent: onEvent, EventComp: EventComp, RowTitleComp: headerColumnComp, rowHeightRem: rowHeightRem, rowHeaderWidth: rowHeaderWidth, maxEventHeightRem: maxEventHeightRem }), jsxRuntime.jsx("div", { children: jsxRuntime.jsx(CwButton, { onClick: () => {
|
|
9610
10332
|
onEvent(new OnClearPinned());
|
|
9611
|
-
}, children: "Clear pinned" }) })] })), jsxRuntime.jsx(Scheduler, { id: `${id}-notPinned`, state: { ...state, isHeaderVisible: !isFirstVisible }, header: header, rows: notPinnedRows, events: events, backgroundEvents: backgroundEvents, indicatorRows: indicatorRows, contextMenuItems: contextMenuItems, orderCategories: unPinnedOrderCategory, onEvent: onEvent, EventComp:
|
|
10333
|
+
}, children: "Clear pinned" }) })] })), jsxRuntime.jsx(Scheduler, { id: `${id}-notPinned`, state: { ...state, isHeaderVisible: !isFirstVisible }, header: header, rows: notPinnedRows, events: events, backgroundEvents: backgroundEvents, indicatorRows: indicatorRows, contextMenuItems: contextMenuItems, orderCategories: unPinnedOrderCategory, onEvent: onEvent, EventComp: EventComp, RowTitleComp: headerColumnComp, rowHeightRem: rowHeightRem, rowHeaderWidth: rowHeaderWidth, maxEventHeightRem: maxEventHeightRem })] }));
|
|
9612
10334
|
};
|
|
9613
10335
|
|
|
9614
10336
|
/**
|
|
@@ -10067,6 +10789,7 @@ exports.CwContextMenu = CwContextMenu;
|
|
|
10067
10789
|
exports.CwDatePicker = CwDatePicker;
|
|
10068
10790
|
exports.CwDatePickerTemporal = CwDatePickerTemporal;
|
|
10069
10791
|
exports.CwDateRangePicker = CwDateRangePicker;
|
|
10792
|
+
exports.CwDateRangePickerTemporal = CwDateRangePickerTemporal;
|
|
10070
10793
|
exports.CwDateTimePicker = CwDateTimePicker;
|
|
10071
10794
|
exports.CwDateTimePickerCompact = CwDateTimePickerCompact;
|
|
10072
10795
|
exports.CwDialog = CwDialog;
|
|
@@ -10111,6 +10834,7 @@ exports.CwOption = CwOption;
|
|
|
10111
10834
|
exports.CwPopoverButton = CwPopoverButton;
|
|
10112
10835
|
exports.CwReportModal = CwReportModal;
|
|
10113
10836
|
exports.CwScheduler = CwScheduler;
|
|
10837
|
+
exports.CwSearchAsync = CwSearchAsync;
|
|
10114
10838
|
exports.CwSearchInput = CwSearchInput;
|
|
10115
10839
|
exports.CwSelect = CwSelect;
|
|
10116
10840
|
exports.CwSelectList = CwSelectList;
|
|
@@ -10153,6 +10877,7 @@ exports.OnRightClickRow = OnRightClickRow;
|
|
|
10153
10877
|
exports.OnRightDragStart = OnRightDragStart;
|
|
10154
10878
|
exports.OnStartClickHeaderEvent = OnStartClickHeaderEvent;
|
|
10155
10879
|
exports.OnUnpinRow = OnUnpinRow;
|
|
10880
|
+
exports.PRESET_LIBRARY_TEMPORAL = PRESET_LIBRARY_TEMPORAL;
|
|
10156
10881
|
exports.PinRowHeader = PinRowHeader;
|
|
10157
10882
|
exports.Resource = Resource;
|
|
10158
10883
|
exports.Scheduler = Scheduler;
|
|
@@ -10166,6 +10891,7 @@ exports.eventIsVisible = eventIsVisible;
|
|
|
10166
10891
|
exports.filterVisibleEvents = filterVisibleEvents;
|
|
10167
10892
|
exports.getDefaultDivisions = getDefaultDivisions;
|
|
10168
10893
|
exports.getEventSizes = getEventSizes;
|
|
10894
|
+
exports.getPresetsByKeysTemporal = getPresetsByKeysTemporal;
|
|
10169
10895
|
exports.itemsToMultiFilterTags = itemsToMultiFilterTags;
|
|
10170
10896
|
exports.useCwMessage = useCwMessage;
|
|
10171
10897
|
exports.useSortableList = useSortableList;
|