@agilant/toga-blox 1.0.131 → 1.0.133

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.
@@ -29,6 +29,7 @@ interface InputFieldProps {
29
29
  register?: UseFormRegister<any>;
30
30
  focusRingColor?: string;
31
31
  firstIconClasses?: string;
32
+ firstIconTop?: string;
32
33
  }
33
34
  /**
34
35
  * A properly formed forwardRef component: exactly two parameters:
@@ -6,7 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6
6
  * A properly formed forwardRef component: exactly two parameters:
7
7
  * (props, ref). We pass `ref` to the <input ref={ref} ...>.
8
8
  */
9
- const InputField = forwardRef(function InputField({ label, placeholder, required, checked, id, name, type = "text", firstIcon, secondIcon, iconPosition = "before", iconColor = "primary", isValid = true, isReadOnly = false, onChange, value, readOnlyInfo = "", toolTipText = "", hasToolTip = false, additionalClasses = "", labelClasses = "", hasAutoFocus, onIconClick, onKeyDown, disabled, register, focusRingColor = "focus:ring-transparent", firstIconClasses, }, ref) {
9
+ const InputField = forwardRef(function InputField({ label, placeholder, required, checked, id, name, type = "text", firstIcon, secondIcon, iconPosition = "before", iconColor = "primary", isValid = true, isReadOnly = false, onChange, value, readOnlyInfo = "", toolTipText = "", hasToolTip = false, additionalClasses = "", labelClasses = "", hasAutoFocus, onIconClick, onKeyDown, disabled, register, focusRingColor = "focus:ring-transparent", firstIconClasses, firstIconTop = "top-[8px]", }, ref) {
10
10
  const [isFocused, setIsFocused] = useState(false);
11
11
  const hasValue = value != null && !!value.toString().trim();
12
12
  const isNumberInput = type === "number";
@@ -27,7 +27,7 @@ const InputField = forwardRef(function InputField({ label, placeholder, required
27
27
  const renderFirstIcon = () => {
28
28
  if (!firstIcon)
29
29
  return null;
30
- return (_jsx("span", { onClick: onIconClick, className: `input-icon input-icon--first-icon absolute top-[8px] left-4 ${firstIconClasses} ${disabled
30
+ return (_jsx("span", { onClick: onIconClick, className: `input-icon input-icon--first-icon absolute left-4 ${firstIconTop} ${firstIconClasses} ${disabled
31
31
  ? "text-gray-500"
32
32
  : isValid
33
33
  ? `text-${iconColor}`
@@ -77,7 +77,7 @@ const InputField = forwardRef(function InputField({ label, placeholder, required
77
77
  ${isValid ? "" : "border-redText focus:ring-red-500"}
78
78
  ${firstIcon ? "pl-10" : ""}
79
79
  ${secondIcon ? "pr-10" : ""}
80
- ${isFocused ? "hover:border-transparent" : ""}
80
+ ${isFocused ? "" : ""}
81
81
  ${additionalClasses}
82
82
  `, autoFocus: hasAutoFocus, placeholder: placeholder, type: type, id: id, value: formattedValue, name: name, checked: type === "checkbox" ? checked : undefined, onChange: onChange, onKeyDown: onKeyDown, onFocus: () => setIsFocused(true), onBlur: () => setIsFocused(false), disabled: disabled, required: required && !hasValue })] })] }));
83
83
  });
@@ -1,3 +1,3 @@
1
1
  import { SearchInputProps } from "./SearchInput.types";
2
- declare const SearchInput: <T extends object>({ textHighlight, inputType, dropdownIconProp, dropdownOptions, selectedDropdownOption, onDropdownOptionSelect, searchItems, setSearchItems, toggleStatus, setToggleStatus, minValue, setMinValue, maxValue, setMaxValue, onChange, selectedValue, selectedDate, onDateSelect, selectedStartDate, onStartDateSelect, selectedEndDate, onEndDateSelect, handleFilter, column, setSearchCriteria, setEditingHeader, pillColor, firstIconClasses, dataPickerThemeColor, dataPickerThemeColorAccent, isBoolean, toggleColor, toggleTextColor, fontFamily, removePattern, isSearchable, hasOperator, tooltipText, hoverBgColor, clearTextHoverColor, }: SearchInputProps<T>) => import("react/jsx-runtime").JSX.Element;
2
+ declare const SearchInput: <T extends object>({ textHighlight, inputType, dropdownIconProp, dropdownOptions, selectedDropdownOption, onDropdownOptionSelect, searchItems, setSearchItems, toggleStatus, setToggleStatus, minValue, setMinValue, maxValue, setMaxValue, onChange, selectedValue, selectedDate, onDateSelect, selectedStartDate, onStartDateSelect, selectedEndDate, onEndDateSelect, handleFilter, column, setSearchCriteria, setEditingHeader, pillColor, firstIconClasses, dataPickerThemeColor, dataPickerThemeColorAccent, isBoolean, toggleColor, toggleTextColor, fontFamily, removePattern, isSearchable, hasOperator, tooltipText, hoverBgColor, clearTextHoverColor, numberTypeIcon, firstIconTop, }: SearchInputProps<T>) => import("react/jsx-runtime").JSX.Element;
3
3
  export default SearchInput;
@@ -8,7 +8,7 @@ const SearchInput = ({ textHighlight = "text-sky-500", inputType = "text", dropd
8
8
  name: "chevronDown",
9
9
  weight: "bold",
10
10
  iconClasses: "text-black",
11
- }, dropdownOptions = [], selectedDropdownOption = "", onDropdownOptionSelect, searchItems = [], setSearchItems, toggleStatus = false, setToggleStatus, minValue, setMinValue, maxValue, setMaxValue, onChange, selectedValue, selectedDate, onDateSelect, selectedStartDate, onStartDateSelect, selectedEndDate, onEndDateSelect, handleFilter, column, setSearchCriteria, setEditingHeader, pillColor, firstIconClasses, dataPickerThemeColor, dataPickerThemeColorAccent, isBoolean = false, toggleColor = "bg-sky-500", toggleTextColor = "text-black", fontFamily, removePattern = /^[^:]*:/, isSearchable = false, hasOperator = false, tooltipText = "Clear filters to edit operator", hoverBgColor, clearTextHoverColor, }) => {
11
+ }, dropdownOptions = [], selectedDropdownOption = "", onDropdownOptionSelect, searchItems = [], setSearchItems, toggleStatus = false, setToggleStatus, minValue, setMinValue, maxValue, setMaxValue, onChange, selectedValue, selectedDate, onDateSelect, selectedStartDate, onStartDateSelect, selectedEndDate, onEndDateSelect, handleFilter, column, setSearchCriteria, setEditingHeader, pillColor, firstIconClasses, dataPickerThemeColor, dataPickerThemeColorAccent, isBoolean = false, toggleColor = "bg-sky-500", toggleTextColor = "text-black", fontFamily, removePattern = /^[^:]*:/, isSearchable = false, hasOperator = false, tooltipText = "Clear filters to edit operator", hoverBgColor, clearTextHoverColor, numberTypeIcon, firstIconTop, }) => {
12
12
  const containerRef = useRef(null);
13
13
  const inputRef = useRef(null);
14
14
  useEffect(() => {
@@ -20,7 +20,7 @@ const SearchInput = ({ textHighlight = "text-sky-500", inputType = "text", dropd
20
20
  case "text":
21
21
  return (_jsx(SearchTextInput, { dropdownIconProp: dropdownIconProp, dropdownOptions: dropdownOptions, selectedDropdownOption: selectedDropdownOption, onDropdownOptionSelect: onDropdownOptionSelect, searchItems: searchItems, setSearchItems: setSearchItems, handleFilter: handleFilter, setSearchCriteria: setSearchCriteria, column: column, setEditingHeader: setEditingHeader, pillColor: pillColor, firstIconClasses: firstIconClasses, fontFamily: fontFamily, removePattern: removePattern, textHighlight: textHighlight, hasOperator: hasOperator, tooltipText: tooltipText }));
22
22
  case "number":
23
- return (_jsx(SearchNumberInput, { dropdownIconProp: dropdownIconProp, dropdownOptions: dropdownOptions, selectedDropdownOption: selectedDropdownOption, onDropdownOptionSelect: onDropdownOptionSelect, toggleStatus: toggleStatus, setToggleStatus: setToggleStatus, minValue: minValue, maxValue: maxValue, setMinValue: setMinValue, setMaxValue: setMaxValue, handleFilter: handleFilter, setSearchCriteria: setSearchCriteria, column: column, themeBgColor: dataPickerThemeColor, toggleColor: toggleColor, toggleTextColor: toggleTextColor, searchItems: searchItems, setSearchItems: setSearchItems, removePattern: removePattern, pillColor: pillColor, hasOperator: hasOperator, tooltipText: tooltipText }));
23
+ return (_jsx(SearchNumberInput, { dropdownIconProp: dropdownIconProp, dropdownOptions: dropdownOptions, selectedDropdownOption: selectedDropdownOption, onDropdownOptionSelect: onDropdownOptionSelect, toggleStatus: toggleStatus, setToggleStatus: setToggleStatus, minValue: minValue, maxValue: maxValue, setMinValue: setMinValue, setMaxValue: setMaxValue, handleFilter: handleFilter, setSearchCriteria: setSearchCriteria, column: column, themeBgColor: dataPickerThemeColor, toggleColor: toggleColor, toggleTextColor: toggleTextColor, searchItems: searchItems, setSearchItems: setSearchItems, removePattern: removePattern, pillColor: pillColor, hasOperator: hasOperator, tooltipText: tooltipText, numberTypeIcon: numberTypeIcon, firstIconTop: firstIconTop }));
24
24
  case "multiSelect":
25
25
  return (_jsx(SearchDropdownInput, { options: dropdownOptions, placeholder: "Search", additionalClasses: "", onChange: onChange, selectedValue: selectedValue, bgColor: dataPickerThemeColor, clearText: "Clear", clearTextColor: "text-sky-500", buttonText: "Filter", handleFilter: handleFilter, column: column, setSearchCriteria: setSearchCriteria, textHighlight: textHighlight, isBoolean: isBoolean, isSearchable: isSearchable, setSearchItems: setSearchItems, fontFamily: fontFamily, pillColor: pillColor, hoverBgColor: hoverBgColor, clearTextHoverColor: clearTextHoverColor }));
26
26
  case "date":
@@ -52,6 +52,11 @@ export type SearchInputProps<T extends object> = {
52
52
  tooltipText?: string;
53
53
  hoverBgColor?: string;
54
54
  clearTextHoverColor?: string;
55
+ numberTypeIcon?: {
56
+ icon: string;
57
+ weight: string;
58
+ };
59
+ firstIconTop?: string;
55
60
  };
56
61
  export interface OptionType {
57
62
  uuid: string;
@@ -10,38 +10,62 @@ import Text from "../Text";
10
10
  import { getFontAwesomeIcon } from "../../utils/getFontAwesomeIcon";
11
11
  import updateLocalStorage from "../../utils/updateLocalStorage";
12
12
  import { getCleanedText } from "../../utils/getCleanedText";
13
- // A tiny helper to parse e.g. "lt:2025-03-28" into operator="lt:" + dateStr="2025-03-28"
13
+ import { Input } from "../Input";
14
+ // Helper to parse strings like "lt:2025-03-28"
14
15
  function parseOperatorAndDate(text) {
15
16
  const colonIndex = text.indexOf(":");
16
17
  if (colonIndex > -1) {
17
18
  return {
18
- operator: text.slice(0, colonIndex + 1), // "lt:"
19
- dateStr: text.slice(colonIndex + 1), // "2025-03-28"
19
+ operator: text.slice(0, colonIndex + 1),
20
+ dateStr: text.slice(colonIndex + 1),
20
21
  };
21
22
  }
22
- // If there's no colon, just return operator="", dateStr=the entire thing
23
23
  return { operator: "", dateStr: text };
24
24
  }
25
- /** Formats a JS Date as YYYY-MM-DD */
25
+ /** Formats a JS Date as YYYY-MM-DD in local time */
26
26
  function formatAsYMD(date) {
27
27
  if (!date)
28
28
  return "";
29
- return date.toISOString().slice(0, 10);
29
+ const year = date.getFullYear();
30
+ const month = (date.getMonth() + 1).toString().padStart(2, "0");
31
+ const day = date.getDate().toString().padStart(2, "0");
32
+ return `${year}-${month}-${day}`;
33
+ }
34
+ /** Attempts to parse a date string input by the user as a local date */
35
+ function parseUserDateInput(input) {
36
+ if (!input)
37
+ return undefined;
38
+ // Check for YYYY-MM-DD explicitly.
39
+ const ymdRegex = /^(\d{4})-(\d{2})-(\d{2})$/;
40
+ const match = input.match(ymdRegex);
41
+ if (match) {
42
+ const year = parseInt(match[1], 10);
43
+ const month = parseInt(match[2], 10);
44
+ const day = parseInt(match[3], 10);
45
+ return new Date(year, month - 1, day);
46
+ }
47
+ // Fallback to standard Date parsing.
48
+ const date = new Date(input);
49
+ if (!isNaN(date.getTime()))
50
+ return date;
51
+ // Try MM/DD/YYYY or MM-DD-YYYY.
52
+ const mdyRegex = /^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/;
53
+ const mdyMatch = input.match(mdyRegex);
54
+ if (mdyMatch) {
55
+ const month = parseInt(mdyMatch[1], 10);
56
+ const day = parseInt(mdyMatch[2], 10);
57
+ const year = parseInt(mdyMatch[3], 10);
58
+ return new Date(year, month - 1, day);
59
+ }
60
+ return undefined;
30
61
  }
31
62
  function SearchDatePickerInput({ themeBgColor = "bg-sky-500", lightThemeBg = "bg-sky-100", pillColor = "bg-sky-500", textHighlight = "text-sky-700", dropdownOptions = [], dropdownIconProp = {
32
63
  iconClasses: "text-sky-500",
33
64
  name: "chevronDown",
34
65
  weight: "solid",
35
- }, toggleStatus = false, setToggleStatus,
36
- // Single-date external states
37
- selectedDate, onDateSelect,
38
- // Range external states
39
- selectedStartDate, onStartDateSelect, selectedEndDate, onEndDateSelect,
40
- // Operator from the parent
41
- selectedDropdownOption, onDropdownOptionSelect, buttonText = "Filter", buttonColor, searchItems = [], setSearchItems, handleFilter, setSearchCriteria, column, setEditingHeader, localStorageKey = "searchCriteria", toggleColor = "bg-sky-500", toggleTextColor = "text-black", fontFamily, removePattern, hasOperator, tooltipText, }) {
66
+ }, toggleStatus = false, setToggleStatus, selectedDate, onDateSelect, selectedStartDate, onStartDateSelect, selectedEndDate, onEndDateSelect, selectedDropdownOption, onDropdownOptionSelect, buttonText = "Filter", buttonColor, searchItems = [], setSearchItems, handleFilter, setSearchCriteria, column, setEditingHeader, localStorageKey = "searchCriteria", toggleColor = "bg-sky-500", toggleTextColor = "text-black", fontFamily, removePattern, hasOperator, tooltipText, }) {
42
67
  const containerRef = useRef(null);
43
- // Track local operator so the user sees the dropdown selection
44
- // If no selectedDropdownOption from parent, fallback to first or blank
68
+ // Local operator state.
45
69
  const [localOperator, setLocalOperator] = useState(() => {
46
70
  if (selectedDropdownOption?.value)
47
71
  return selectedDropdownOption;
@@ -49,14 +73,18 @@ selectedDropdownOption, onDropdownOptionSelect, buttonText = "Filter", buttonCol
49
73
  return dropdownOptions[0];
50
74
  return { label: "", value: "" };
51
75
  });
52
- // Local single-date
76
+ // Local single-date and its input value.
53
77
  const [localDate, setLocalDate] = useState(selectedDate);
54
- // Local start/end if range is toggled
78
+ const [dateInputValue, setDateInputValue] = useState(selectedDate ? formatAsYMD(selectedDate) : "");
79
+ // Local start/end for range mode and their input values.
55
80
  const [localStartDate, setLocalStartDate] = useState(selectedStartDate);
81
+ const [startDateInputValue, setStartDateInputValue] = useState(selectedStartDate ? formatAsYMD(selectedStartDate) : "");
56
82
  const [localEndDate, setLocalEndDate] = useState(selectedEndDate);
83
+ const [endDateInputValue, setEndDateInputValue] = useState(selectedEndDate ? formatAsYMD(selectedEndDate) : "");
57
84
  const [isDatePickerOpen, setIsDatePickerOpen] = useState(false);
85
+ // activeInput can be "start", "end", "single" or null (when not editing)
58
86
  const [activeInput, setActiveInput] = useState(null);
59
- // Load from localStorage if there's an existing criterion for this column
87
+ // Load from localStorage if there's an existing criterion for this column.
60
88
  useEffect(() => {
61
89
  if (!column?.id)
62
90
  return;
@@ -66,23 +94,34 @@ selectedDropdownOption, onDropdownOptionSelect, buttonText = "Filter", buttonCol
66
94
  const parsed = JSON.parse(stored);
67
95
  const existing = parsed.find((crit) => crit.searchColumn?.id === column.id);
68
96
  if (existing?.submittedSearchText) {
69
- // e.g. "lt:2025-03-28" or "2025-03-28"
70
97
  const { operator, dateStr } = parseOperatorAndDate(existing.submittedSearchText);
71
- // If there's an operator, find it in the dropdown
72
98
  if (operator && dropdownOptions.length) {
73
99
  const foundOp = dropdownOptions.find((opt) => opt.value === operator);
74
100
  if (foundOp)
75
101
  setLocalOperator(foundOp);
76
102
  }
77
- // If single date mode
78
103
  if (!toggleStatus && dateStr) {
79
104
  const d = new Date(dateStr.trim());
80
105
  if (!isNaN(d.getTime())) {
81
106
  setLocalDate(d);
107
+ setDateInputValue(formatAsYMD(d));
108
+ }
109
+ }
110
+ else if (toggleStatus && dateStr) {
111
+ const parts = dateStr.split(" - ");
112
+ if (parts.length === 2) {
113
+ const startDate = new Date(parts[0].trim());
114
+ const endDate = new Date(parts[1].trim());
115
+ if (!isNaN(startDate.getTime())) {
116
+ setLocalStartDate(startDate);
117
+ setStartDateInputValue(formatAsYMD(startDate));
118
+ }
119
+ if (!isNaN(endDate.getTime())) {
120
+ setLocalEndDate(endDate);
121
+ setEndDateInputValue(formatAsYMD(endDate));
122
+ }
82
123
  }
83
124
  }
84
- // If range mode, you'd parse "2025-03-10 - 2025-03-15" etc.
85
- // but let's keep it simple for now.
86
125
  }
87
126
  }
88
127
  catch (err) {
@@ -90,27 +129,59 @@ selectedDropdownOption, onDropdownOptionSelect, buttonText = "Filter", buttonCol
90
129
  }
91
130
  }
92
131
  }, [column?.id, localStorageKey, dropdownOptions, toggleStatus]);
93
- // If the parent changes selectedDate, sync it to local state
132
+ // Sync props with local state—but only update if the user isn’t actively editing.
94
133
  useEffect(() => {
95
- setLocalDate(selectedDate);
96
- }, [selectedDate]);
97
- // If the parent changes selectedOperator, sync that too
134
+ if (selectedDate && activeInput !== "single") {
135
+ setLocalDate(selectedDate);
136
+ setDateInputValue(formatAsYMD(selectedDate));
137
+ }
138
+ }, [selectedDate, activeInput]);
139
+ useEffect(() => {
140
+ if (selectedStartDate && activeInput !== "start") {
141
+ setLocalStartDate(selectedStartDate);
142
+ setStartDateInputValue(formatAsYMD(selectedStartDate));
143
+ }
144
+ }, [selectedStartDate, activeInput]);
145
+ useEffect(() => {
146
+ if (selectedEndDate && activeInput !== "end") {
147
+ setLocalEndDate(selectedEndDate);
148
+ setEndDateInputValue(formatAsYMD(selectedEndDate));
149
+ }
150
+ }, [selectedEndDate, activeInput]);
98
151
  useEffect(() => {
99
152
  if (selectedDropdownOption && selectedDropdownOption.value) {
100
153
  setLocalOperator(selectedDropdownOption);
101
154
  }
102
155
  }, [selectedDropdownOption]);
103
- // Handler for the operator dropdown
156
+ // When toggling between single and range, close any open picker.
157
+ useEffect(() => {
158
+ setIsDatePickerOpen(false);
159
+ setActiveInput(null);
160
+ }, [toggleStatus]);
161
+ // Handler for the operator dropdown.
104
162
  const handleOperatorSelect = (option) => {
105
163
  setLocalOperator(option);
106
164
  onDropdownOptionSelect?.(option);
107
165
  };
108
- // Single date pick
166
+ // Single-date handlers.
109
167
  const openSinglePicker = () => {
110
- setActiveInput(null);
168
+ setActiveInput("single");
111
169
  setIsDatePickerOpen(!isDatePickerOpen);
112
170
  };
113
- // Range pick
171
+ const handleSingleDateInputChange = (e) => {
172
+ const value = e.target.value;
173
+ setDateInputValue(value);
174
+ const parsedDate = parseUserDateInput(value);
175
+ if (parsedDate) {
176
+ setLocalDate(parsedDate);
177
+ onDateSelect?.(parsedDate);
178
+ }
179
+ else {
180
+ setLocalDate(undefined);
181
+ onDateSelect?.(undefined);
182
+ }
183
+ };
184
+ // Range-date handlers.
114
185
  const openStartPicker = () => {
115
186
  setActiveInput("start");
116
187
  setIsDatePickerOpen(true);
@@ -119,16 +190,41 @@ selectedDropdownOption, onDropdownOptionSelect, buttonText = "Filter", buttonCol
119
190
  setActiveInput("end");
120
191
  setIsDatePickerOpen(true);
121
192
  };
122
- /** Called when user clicks "Filter" */
193
+ const handleStartDateInputChange = (e) => {
194
+ const value = e.target.value;
195
+ setStartDateInputValue(value);
196
+ const parsedDate = parseUserDateInput(value);
197
+ if (parsedDate) {
198
+ setLocalStartDate(parsedDate);
199
+ onStartDateSelect?.(parsedDate);
200
+ }
201
+ else {
202
+ setLocalStartDate(undefined);
203
+ onStartDateSelect?.(undefined);
204
+ }
205
+ };
206
+ const handleEndDateInputChange = (e) => {
207
+ const value = e.target.value;
208
+ setEndDateInputValue(value);
209
+ const parsedDate = parseUserDateInput(value);
210
+ if (parsedDate) {
211
+ setLocalEndDate(parsedDate);
212
+ onEndDateSelect?.(parsedDate);
213
+ }
214
+ else {
215
+ setLocalEndDate(undefined);
216
+ onEndDateSelect?.(undefined);
217
+ }
218
+ };
219
+ // When the user clicks "Filter", build the final text and store criteria.
123
220
  const handleFilterClick = () => {
124
221
  let finalText = "";
125
222
  if (!toggleStatus) {
126
- // Single date: operator + date
127
223
  const dateStr = localDate ? formatAsYMD(localDate) : "";
128
224
  finalText = localOperator.value + dateStr;
225
+ onDateSelect?.(localDate);
129
226
  }
130
227
  else {
131
- // Range date
132
228
  const startStr = localStartDate ? formatAsYMD(localStartDate) : "";
133
229
  const endStr = localEndDate ? formatAsYMD(localEndDate) : "";
134
230
  if (startStr && endStr) {
@@ -140,10 +236,11 @@ selectedDropdownOption, onDropdownOptionSelect, buttonText = "Filter", buttonCol
140
236
  else if (endStr) {
141
237
  finalText = endStr;
142
238
  }
239
+ onStartDateSelect?.(localStartDate);
240
+ onEndDateSelect?.(localEndDate);
143
241
  }
144
242
  const trimmed = finalText.trim();
145
243
  if (!trimmed) {
146
- // remove existing criterion for this column
147
244
  setSearchCriteria?.((prev) => {
148
245
  const newCriteria = prev.filter((c) => c.searchColumn?.id !== column?.id);
149
246
  updateLocalStorage(newCriteria, localStorageKey);
@@ -151,7 +248,6 @@ selectedDropdownOption, onDropdownOptionSelect, buttonText = "Filter", buttonCol
151
248
  });
152
249
  }
153
250
  else {
154
- // store new or updated criterion
155
251
  setSearchCriteria?.((prev) => {
156
252
  const filtered = prev.filter((c) => c.searchColumn?.id !== column?.id);
157
253
  const newCriterion = {
@@ -167,41 +263,79 @@ selectedDropdownOption, onDropdownOptionSelect, buttonText = "Filter", buttonCol
167
263
  setEditingHeader?.(null);
168
264
  setIsDatePickerOpen(false);
169
265
  setActiveInput(null);
170
- if (!toggleStatus && onDateSelect) {
171
- onDateSelect(localDate);
172
- }
173
266
  };
174
- // The daypicker for single or range
267
+ // Close the picker when clicking outside.
268
+ useEffect(() => {
269
+ function handleClickOutside(event) {
270
+ if (containerRef.current &&
271
+ !containerRef.current.contains(event.target) &&
272
+ isDatePickerOpen) {
273
+ setIsDatePickerOpen(false);
274
+ setActiveInput(null);
275
+ }
276
+ }
277
+ document.addEventListener("mousedown", handleClickOutside);
278
+ return () => {
279
+ document.removeEventListener("mousedown", handleClickOutside);
280
+ };
281
+ }, [isDatePickerOpen]);
282
+ // Render the DayPicker – for both single and range modes.
175
283
  const dayPicker = !toggleStatus
176
- ? // Single date
177
- isDatePickerOpen && (_jsx("div", { className: "absolute p-4 top-16 w-auto z-50 shadow-lg bg-white", children: _jsx(DayPicker, { mode: "single", selected: localDate, onSelect: (day) => {
178
- if (day) {
179
- setLocalDate(day);
180
- }
181
- setIsDatePickerOpen(false);
182
- } }) }))
183
- : activeInput && (_jsx("div", { className: "absolute p-4 top-16 w-auto z-50 shadow-lg bg-white", children: activeInput === "start" ? (_jsx(DayPicker, { mode: "single", selected: localStartDate, onSelect: (day) => {
184
- if (day)
284
+ ? isDatePickerOpen && (_jsx("div", { className: "absolute p-4 top-16 w-auto z-50 shadow-lg bg-white", children: _jsx(DayPicker, { mode: "single", selected: parseUserDateInput(dateInputValue) || localDate, onSelect: (day) => {
285
+ if (day) {
286
+ setLocalDate(day);
287
+ setDateInputValue(formatAsYMD(day));
288
+ onDateSelect?.(day);
289
+ }
290
+ setIsDatePickerOpen(false);
291
+ setActiveInput(null);
292
+ } }) }))
293
+ : isDatePickerOpen &&
294
+ activeInput && (_jsx("div", { className: "absolute p-4 top-16 w-auto z-50 shadow-lg bg-white", children: activeInput === "start" ? (_jsx(DayPicker, { mode: "single", selected: parseUserDateInput(startDateInputValue) ||
295
+ localStartDate, onSelect: (day) => {
296
+ if (day) {
185
297
  setLocalStartDate(day);
298
+ setStartDateInputValue(formatAsYMD(day));
299
+ onStartDateSelect?.(day);
300
+ }
186
301
  setIsDatePickerOpen(false);
187
302
  setActiveInput(null);
188
- } })) : (_jsx(DayPicker, { mode: "single", selected: localEndDate, onSelect: (day) => {
189
- if (day)
303
+ } })) : (_jsx(DayPicker, { mode: "single", selected: parseUserDateInput(endDateInputValue) ||
304
+ localEndDate, onSelect: (day) => {
305
+ if (day) {
190
306
  setLocalEndDate(day);
307
+ setEndDateInputValue(formatAsYMD(day));
308
+ onEndDateSelect?.(day);
309
+ }
191
310
  setIsDatePickerOpen(false);
192
311
  setActiveInput(null);
193
312
  } })) }));
194
- return (_jsxs("div", { ref: containerRef, className: "relative w-[425px] border-2 p-4", children: [_jsx("div", { className: "flex items-center justify-between h-12 mb-2", children: toggleStatus ? (_jsxs("div", { className: "flex items-center w-full", children: [_jsx("button", { onClick: openStartPicker, className: "border-2 px-3 py-2 flex-1 h-10 text-left", children: localStartDate
195
- ? formatAsYMD(localStartDate)
196
- : "Start Date" }), _jsx("span", { className: "mx-2", children: "to" }), _jsx("button", { onClick: openEndPicker, className: "border-2 px-3 py-2 flex-1 h-10 text-left", children: localEndDate
197
- ? formatAsYMD(localEndDate)
198
- : "End Date" })] })) : (_jsxs(_Fragment, { children: [_jsx(Dropdown, { options: dropdownOptions, selectedOption: localOperator, onOptionSelect: handleOperatorSelect, optionClasses: "px-4 h-full flex items-center", menuClasses: "bg-white min-w-32xw rounded-md shadow-md top-12", dropdownClasses: "border-2 border-r-0 flex-[1] h-10 w-auto", icon: dropdownIconProp, isEnabled: !hasOperator, tooltipText: tooltipText }), _jsx("button", { onClick: openSinglePicker, className: "border-2 px-3 py-2 flex-[2] h-10 text-left min-w-40", children: localDate ? formatAsYMD(localDate) : "Select Date" })] })) }), dayPicker, searchItems?.length ? (_jsx("div", { className: "flex flex-wrap bg-white py-2 px-2 mt-2 rounded-md", children: searchItems.map((item, index) => {
313
+ return (_jsxs("div", { ref: containerRef, className: "relative w-[425px] border-2 p-4", children: [_jsx("div", { className: "flex items-center justify-between h-12 mb-2", children: toggleStatus ? (_jsxs("div", { className: "flex items-center w-full", children: [activeInput === "start" ? (_jsx(Input, { type: "text", value: startDateInputValue, onChange: handleStartDateInputChange, placeholder: "mm/dd/yyy", onKeyDown: (e) => {
314
+ if (e.key === "Enter") {
315
+ setIsDatePickerOpen(false);
316
+ setActiveInput(null);
317
+ }
318
+ }, firstIcon: getFontAwesomeIcon("calendar", "regular"), required: false, additionalClasses: "border-2 px-3 py-2 flex-[2] h-10 text-left min-w-44 max-w-44", hasAutoFocus: true })) : (_jsxs("button", { onClick: openStartPicker, className: "border-2 px-3 py-2 flex-1 h-10 text-left min-w-44 max-w-44", children: [_jsx("span", { className: "pr-2", children: getFontAwesomeIcon("calendar", "regular") }), localStartDate
319
+ ? formatAsYMD(localStartDate)
320
+ : "mm/dd/yyyy"] })), _jsx("span", { className: "mx-2", children: "to" }), activeInput === "end" ? (_jsx(Input, { type: "text", value: endDateInputValue, onChange: handleEndDateInputChange, placeholder: "mm/dd/yyy", onKeyDown: (e) => {
321
+ if (e.key === "Enter") {
322
+ setIsDatePickerOpen(false);
323
+ setActiveInput(null);
324
+ }
325
+ }, firstIcon: getFontAwesomeIcon("calendar", "regular"), required: false, additionalClasses: "border-2 px-3 py-2 flex-[2] h-10 text-left min-w-44 max-w-44", hasAutoFocus: true })) : (_jsxs("button", { onClick: openEndPicker, className: "border-2 px-3 py-2 flex-1 h-10 text-left min-w-44 max-w-44", children: [_jsx("span", { className: "pr-2", children: getFontAwesomeIcon("calendar", "regular") }), localEndDate
326
+ ? formatAsYMD(localEndDate)
327
+ : "mm/dd/yyyy"] }))] })) : (_jsxs(_Fragment, { children: [_jsx(Dropdown, { options: dropdownOptions, selectedOption: localOperator, onOptionSelect: handleOperatorSelect, optionClasses: "px-4 h-full flex items-center", menuClasses: "bg-white min-w-32xw rounded-md shadow-md top-12", dropdownClasses: "border-2 border-r-0 flex-[1] h-10 w-auto", icon: dropdownIconProp, isEnabled: !hasOperator, tooltipText: tooltipText }), activeInput === "single" ? (_jsx("div", { children: _jsx(Input, { type: "text", value: dateInputValue, onChange: handleSingleDateInputChange, placeholder: "mm/dd/yyy", onKeyDown: (e) => {
328
+ if (e.key === "Enter") {
329
+ setIsDatePickerOpen(false);
330
+ setActiveInput(null);
331
+ }
332
+ }, firstIcon: getFontAwesomeIcon("calendar", "regular"), required: false, additionalClasses: "border-2 px-3 py-2 flex-[2] h-10 text-left min-w-40", hasAutoFocus: true }) })) : (_jsx(_Fragment, { children: _jsxs("button", { onClick: openSinglePicker, className: "border-2 px-3 py-2 flex-[2] h-10 text-left min-w-40", children: [_jsx("span", { className: "pr-2", children: getFontAwesomeIcon("calendar", "regular") }), localDate
333
+ ? formatAsYMD(localDate)
334
+ : "mm/dd/yyyy"] }) }))] })) }), dayPicker, searchItems?.length ? (_jsx("div", { className: "flex flex-wrap bg-white py-2 px-2 mt-2 rounded-md", children: searchItems.map((item, index) => {
199
335
  const cleanedText = getCleanedText(item, removePattern);
200
336
  return (_jsx(Badge, { backgroundColor: pillColor, borderRadius: "rounded-full", hasRightIcon: true, icon: _jsx("div", { className: "text-white text-xxs", children: getFontAwesomeIcon("xmark", "solid") }), iconSize: "text-sm", onClick: () => {
201
- // remove from searchItems
202
337
  const newSearchItems = searchItems.filter((it) => it !== item);
203
338
  setSearchItems?.(newSearchItems);
204
- // remove from searchCriteria
205
339
  setSearchCriteria?.((prev) => {
206
340
  const filtered = prev.filter((crit) => crit.submittedSearchText !==
207
341
  item);
@@ -209,6 +343,6 @@ selectedDropdownOption, onDropdownOptionSelect, buttonText = "Filter", buttonCol
209
343
  return filtered;
210
344
  });
211
345
  }, text: _jsx(Text, { color: "text-white", size: "text-sm", tag: "span", text: cleanedText }), badgeContainerClasses: `${pillColor} p-1 max-w-fit min-w-20 rounded-full flex justify-between items-center text-white text-xs px-4 border-none mr-4 mb-1`, type: "span" }, index));
212
- }) })) : null, _jsxs("div", { className: "flex justify-between items-end bg-white px-2 rounded-md mt-4", children: [_jsx(ToggleButton, { initialStatus: toggleStatus, onClick: () => setToggleStatus?.(!toggleStatus), activeColorBackground: toggleColor, activeColorBorder: "border-sky-500", activeLabel: "Range", activeTextColor: toggleTextColor, additionalClasses: "flex items-center", inactiveColorBackground: "bg-gray-300", inactiveColorBorder: "border-gray-300", inactiveLabel: "Range", inactiveTextColor: "text-gray-500", pillHeight: "h-8", textPosition: "right", textSize: "text-sm", smallToggle: false, borderStyle: false }), _jsx(BaseButton, { text: buttonText, backgroundColor: themeBgColor, additionalClasses: "py-1.5 px-6 text-white", borderColor: "border-none", onClick: handleFilterClick, shape: "rounded-full" })] })] }));
346
+ }) })) : null, _jsxs("div", { className: "flex justify-between items-end bg-white px-2 rounded-md mt-4", children: [_jsx(ToggleButton, { initialStatus: toggleStatus, onClick: () => setToggleStatus?.(!toggleStatus), activeColorBackground: toggleColor, activeColorBorder: "border-sky-500", activeLabel: "Range", activeTextColor: toggleTextColor, additionalClasses: "flex items-center", inactiveColorBackground: "bg-gray-300", inactiveColorBorder: "border-gray-300", inactiveLabel: "Range", inactiveTextColor: "text-gray-500", pillHeight: "h-8", textPosition: "right", textSize: "text-sm", smallToggle: true, borderStyle: true }), _jsx(BaseButton, { text: buttonText, backgroundColor: themeBgColor, additionalClasses: "py-0.5 px-6 text-white", borderColor: "border-none", onClick: handleFilterClick, shape: "rounded-full" })] })] }));
213
347
  }
214
348
  export default SearchDatePickerInput;
@@ -44,6 +44,11 @@ type SearchNumberInputProps<T extends object> = {
44
44
  fontFamily?: string;
45
45
  hasOperator?: boolean;
46
46
  tooltipText?: string;
47
+ numberTypeIcon?: {
48
+ icon: string;
49
+ weight: string;
50
+ };
51
+ firstIconTop?: string;
47
52
  };
48
53
  /**
49
54
  * A numeric filter component that:
@@ -51,5 +56,5 @@ type SearchNumberInputProps<T extends object> = {
51
56
  * 2. On Filter => either remove or store a string in localStorage
52
57
  * 3. On mount => read any existing filter for this column, parse, and set min/max
53
58
  */
54
- declare const SearchNumberInput: <T extends object>({ textHighlight, dropdownIconProp, dropdownOptions, selectedDropdownOption, onDropdownOptionSelect, toggleStatus, setToggleStatus, minValue, setMinValue, maxValue, setMaxValue, handleFilter, searchItems, setSearchItems, setSearchCriteria, column, setEditingHeader, localStorageKey, themeBgColor, toggleColor, toggleTextColor, pillColor, fontFamily, removePattern, hasOperator, tooltipText, }: SearchNumberInputProps<T>) => import("react/jsx-runtime").JSX.Element;
59
+ declare const SearchNumberInput: <T extends object>({ textHighlight, dropdownIconProp, dropdownOptions, selectedDropdownOption, onDropdownOptionSelect, toggleStatus, setToggleStatus, minValue, setMinValue, maxValue, setMaxValue, handleFilter, searchItems, setSearchItems, setSearchCriteria, column, setEditingHeader, localStorageKey, themeBgColor, toggleColor, toggleTextColor, pillColor, fontFamily, removePattern, hasOperator, tooltipText, numberTypeIcon, firstIconTop, }: SearchNumberInputProps<T>) => import("react/jsx-runtime").JSX.Element;
55
60
  export default SearchNumberInput;
@@ -22,7 +22,7 @@ const SearchNumberInput = ({ textHighlight = "text-sky-500", dropdownIconProp =
22
22
  weight: "solid",
23
23
  }, dropdownOptions = [], selectedDropdownOption = { label: "", value: "" }, onDropdownOptionSelect, toggleStatus = false, setToggleStatus, minValue = "", setMinValue, maxValue = "", setMaxValue, handleFilter,
24
24
  // local-storage
25
- searchItems = [], setSearchItems, setSearchCriteria, column, setEditingHeader, localStorageKey = DEFAULT_STORAGE_KEY, themeBgColor = "bg-sky-500", toggleColor = "bg-sky-500", toggleTextColor = "text-black", pillColor, fontFamily, removePattern, hasOperator, tooltipText, }) => {
25
+ searchItems = [], setSearchItems, setSearchCriteria, column, setEditingHeader, localStorageKey = DEFAULT_STORAGE_KEY, themeBgColor = "bg-sky-500", toggleColor = "bg-sky-500", toggleTextColor = "text-black", pillColor, fontFamily, removePattern, hasOperator, tooltipText, numberTypeIcon = { icon: "x", weight: "regular" }, firstIconTop, }) => {
26
26
  const containerRef = useRef(null);
27
27
  const inputRef = useRef(null);
28
28
  /** Decide if we can store for this column */
@@ -167,7 +167,7 @@ searchItems = [], setSearchItems, setSearchCriteria, column, setEditingHeader, l
167
167
  _jsxs(_Fragment, { children: [_jsx(Dropdown, { options: dropdownOptions, selectedOption: selectedDropdownOption || {
168
168
  label: "",
169
169
  value: "",
170
- }, onOptionSelect: onDropdownOptionSelect, optionClasses: "px-4 h-full flex items-center", menuClasses: "bg-white min-w-32xw rounded-md shadow-md top-12", dropdownClasses: "border-0 w-auto", icon: dropdownIconProp, isEnabled: !hasOperator, tooltipText: tooltipText }), _jsx(Input, { ref: inputRef, focusRingColor: "focus:ring-transparent", hasAutoFocus: true, value: minValue, iconColor: "text-navy-400", required: false, id: "", name: "", type: "number", onChange: (e) => setMinValue?.(e.target.value), additionalClasses: "min-w-[200px] h-10 text-gray flex border-l-2 ", placeholder: "Amount", hasIcons: true, iconPosition: "both" })] })) }), _jsxs("div", { className: " flex flex-[1] justify-between items-end py-1 px-4", children: [_jsx(ToggleButton, { initialStatus: toggleStatus, onClick: () => setToggleStatus?.(!toggleStatus), activeColorBackground: toggleColor, activeColorBorder: "border-sky-500", activeLabel: "Range", activeTextColor: toggleTextColor, additionalClasses: "flex items-center", inactiveColorBackground: "bg-gray-300", inactiveColorBorder: "border-gray-300", inactiveLabel: "Range", inactiveTextColor: "text-gray-500", pillHeight: "h-8", textPosition: "right", textSize: "text-sm", smallToggle: false, borderStyle: false }), _jsx(BaseButton, { text: "Filter", backgroundColor: themeBgColor, additionalClasses: "py-1.5 px-6 text-white", borderColor: "border-none", onClick: handleFilterClick, shape: "rounded-full" })] }), searchItems?.length ? (_jsx("div", { className: "border-t border-gray-300 ", children: _jsx("div", { className: "flex flex-wrap bg-white pt-2 px-4 rounded-md", children: searchItems.map((item, index) => {
170
+ }, onOptionSelect: onDropdownOptionSelect, optionClasses: "px-4 h-full flex items-center", menuClasses: "bg-white min-w-32xw rounded-md shadow-md top-12", dropdownClasses: "border-0 w-auto", icon: dropdownIconProp, isEnabled: !hasOperator, tooltipText: tooltipText }), _jsx(Input, { ref: inputRef, focusRingColor: "focus:ring-transparent", hasAutoFocus: true, value: minValue, iconColor: "text-navy-400", required: false, id: "", name: "", type: "number", onChange: (e) => setMinValue?.(e.target.value), additionalClasses: "min-w-[200px] h-10 text-gray flex border-l-2 flex items-center", placeholder: "Amount", hasIcons: true, iconPosition: "both", firstIcon: _jsx("div", { children: getFontAwesomeIcon(numberTypeIcon.icon, numberTypeIcon.weight) }), firstIconTop: firstIconTop })] })) }), _jsxs("div", { className: " flex flex-[1] justify-between items-end py-1 px-4", children: [_jsx(ToggleButton, { initialStatus: toggleStatus, onClick: () => setToggleStatus?.(!toggleStatus), activeColorBackground: toggleColor, activeColorBorder: "border-sky-500", activeLabel: "Range", activeTextColor: toggleTextColor, additionalClasses: "flex items-center", inactiveColorBackground: "bg-gray-300", inactiveColorBorder: "border-gray-300", inactiveLabel: "Range", inactiveTextColor: "text-gray-500", pillHeight: "h-8", textPosition: "right", textSize: "text-sm", smallToggle: true, borderStyle: true }), _jsx(BaseButton, { text: "Filter", backgroundColor: themeBgColor, additionalClasses: "py-0.5 px-6 text-white", borderColor: "border-none", onClick: handleFilterClick, shape: "rounded-full" })] }), searchItems?.length ? (_jsx("div", { className: "border-t border-gray-300 ", children: _jsx("div", { className: "flex flex-wrap bg-white pt-2 px-4 rounded-md", children: searchItems.map((item, index) => {
171
171
  const cleanedText = getCleanedText(item, removePattern);
172
172
  return (_jsx(Badge, { backgroundColor: pillColor, borderRadius: "rounded-full", hasRightIcon: true, icon: _jsx("div", { className: "text-white text-xxs", "data-testid": "item-clear-icon", children: getFontAwesomeIcon("xmark", "solid") }), iconSize: "text-sm", mobileIconLabel: item, onClick: () => handleSearchBadgeClick(item), text: _jsx(Text, { color: "text-white", fontFamily: fontFamily, size: "text-sm", tag: "span", text: cleanedText }), badgeContainerClasses: `${pillColor} cursor-pointer p-1 max-w-fit min-w-20 rounded-full flex justify-between items-center text-white text-xs px-4 border-none mr-4 mb-1`, type: "span" }, index));
173
173
  }) }) })) : null] }) }));
@@ -36,6 +36,6 @@ const ToggleButton = ({ initialStatus, onClick, id, textPosition, textSize, acti
36
36
  const toggleClasses = getToggleClasses(initialStatus, smallToggle, borderStyle, activeColorBorder, inactiveColorBorder, activeColorBackground, inactiveColorBackground);
37
37
  return (_jsx("div", { className: `flex justify-center ${hasDivider && textPosition === "right"
38
38
  ? "border-r-1 border-navy-200 pr-2"
39
- : ""} mr-2 ${pillHeight}`, children: hasDisabledStatus ? (_jsx("div", { className: `${pillHeight}`, children: _jsx(Text, { fontFamily: fontFamily, size: textSize, color: textColor, text: initialStatus ? activeLabel : inactiveLabel }) })) : (_jsxs("div", { className: `justify-between w-full ${textPositionClasses} ${textSize} ${textColor} ${additionalClasses} `, children: [_jsx("label", { htmlFor: id, "data-testid": "inactive-label", className: `flex justify-center ${smallToggle ? " min-w-10" : "min-w-20"}`, children: initialStatus ? activeLabel : inactiveLabel }), _jsxs("label", { className: "relative inline-flex cursor-pointer items-center", children: [_jsx("input", { id: id, type: "checkbox", className: "peer sr-only", checked: initialStatus, onChange: handleToggle }), _jsx("div", { className: toggleClasses })] })] })) }));
39
+ : ""} mr-2 ${pillHeight}`, children: hasDisabledStatus ? (_jsx("div", { className: `${pillHeight}`, children: _jsx(Text, { fontFamily: fontFamily, size: textSize, color: textColor, text: initialStatus ? activeLabel : inactiveLabel }) })) : (_jsxs("div", { className: `justify-between w-full ${textPositionClasses} ${textSize} ${textColor} ${additionalClasses} `, children: [_jsx("label", { htmlFor: id, "data-testid": "inactive-label", className: `flex justify-center pl-2 ${smallToggle ? " min-w-10" : "min-w-20"}`, children: initialStatus ? activeLabel : inactiveLabel }), _jsxs("label", { className: "relative inline-flex cursor-pointer items-center", children: [_jsx("input", { id: id, type: "checkbox", className: "peer sr-only", checked: initialStatus, onChange: handleToggle }), _jsx("div", { className: toggleClasses })] })] })) }));
40
40
  };
41
41
  export default ToggleButton;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agilant/toga-blox",
3
3
  "private": false,
4
- "version": "1.0.131",
4
+ "version": "1.0.133",
5
5
  "description": "",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",