@agilant/toga-blox 1.0.318-beta.9 → 1.0.319-beta.75
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/api/tableData/getDataTableData.js +10 -18
- package/dist/assets/Logo.png +0 -0
- package/dist/assets/cable.jpg +0 -0
- package/dist/assets/card-1.jpg +0 -0
- package/dist/assets/cat-logo.png +0 -0
- package/dist/assets/compass-card-image-2.png +0 -0
- package/dist/assets/compass-card-image-3.png +0 -0
- package/dist/assets/compass-card-image-4.png +0 -0
- package/dist/assets/compass-card-image.png +0 -0
- package/dist/assets/compass-logo.png +0 -0
- package/dist/assets/compass-tech-hero-bg.png +0 -0
- package/dist/assets/contact-image.png +0 -0
- package/dist/assets/green-laptop.png +0 -0
- package/dist/assets/heroImage.png +0 -0
- package/dist/assets/item.jpg +0 -0
- package/dist/assets/map.png +0 -0
- package/dist/assets/placeholder-no-image-available.png +0 -0
- package/dist/assets/team.png +0 -0
- package/dist/components/AdvancedSelect/AdvancedSelect.d.ts +3 -0
- package/dist/components/AdvancedSelect/AdvancedSelect.js +294 -0
- package/dist/components/AdvancedSelect/AdvancedSelect.module.css +448 -0
- package/dist/components/AdvancedSelect/AdvancedSelect.types.d.ts +74 -0
- package/dist/components/AdvancedSelect/index.d.ts +3 -0
- package/dist/components/AdvancedSelect/index.js +2 -0
- package/dist/components/BaseAddress/BaseAddress.module.css +1 -2
- package/dist/components/BaseButton/BaseButton.d.ts +4 -2
- package/dist/components/BaseButton/BaseButton.js +2 -0
- package/dist/components/BaseButton/BaseButton.module.css +112 -0
- package/dist/components/BaseDetailField/BaseDetailField.js +2 -2
- package/dist/components/BaseDetailField/BaseDetailField.module.css +3 -7
- package/dist/components/BaseDetailField/renderBaseDetailFieldLabel.d.ts +1 -1
- package/dist/components/BaseDetailField/renderBaseDetailFieldLabel.js +4 -2
- package/dist/components/BaseDetailField/renderBaseDetailFieldValue.js +70 -11
- package/dist/components/BaseDetailField/types.d.ts +1 -0
- package/dist/components/BaseInput/BaseCheckbox.d.ts +1 -3
- package/dist/components/BaseInput/BaseCheckbox.js +7 -5
- package/dist/components/BaseInput/BaseInput.js +42 -39
- package/dist/components/BaseInput/BaseInput.module.css +489 -0
- package/dist/components/BaseInput/BaseInput.types.d.ts +13 -6
- package/dist/components/BaseInput/BaseMultiSelect.d.ts +1 -15
- package/dist/components/BaseInput/BaseMultiSelect.js +40 -30
- package/dist/components/BaseInput/BaseRadio.d.ts +3 -6
- package/dist/components/BaseInput/BaseRadio.js +4 -2
- package/dist/components/BaseInput/BaseSelect.d.ts +3 -16
- package/dist/components/BaseInput/BaseSelect.js +20 -37
- package/dist/components/BaseInput/BaseTextInput.d.ts +1 -7
- package/dist/components/BaseInput/BaseTextInput.js +29 -21
- package/dist/components/BaseInput/BaseTextareaInput.d.ts +3 -13
- package/dist/components/BaseInput/BaseTextareaInput.js +14 -24
- package/dist/components/BaseInput/BaseToggle.d.ts +2 -11
- package/dist/components/BaseInput/BaseToggle.js +3 -3
- package/dist/components/BaseInput/DisabledSelect.d.ts +0 -1
- package/dist/components/BaseInput/DisabledSelect.js +6 -3
- package/dist/components/BaseInput/components/BaseErrorMessage.d.ts +1 -3
- package/dist/components/BaseInput/components/BaseErrorMessage.js +4 -2
- package/dist/components/BaseInput/components/CharacterLimitMessage.js +3 -3
- package/dist/components/BaseInput/components/Option.js +12 -5
- package/dist/components/BaseInput/components/SingleValue.js +2 -2
- package/dist/components/BaseToolTip/BaseToolTip.module.css +5 -2
- package/dist/components/BaseTotals/BaseTotals.js +8 -4
- package/dist/components/BaseTotals/BaseTotals.module.css +16 -4
- package/dist/components/BaseTotals/types.d.ts +4 -1
- package/dist/components/Card/templates/CategoryCardTemplate.js +2 -1
- package/dist/components/Card/templates/CompassCardTemplate.js +2 -1
- package/dist/components/Card/templates/CounterContentCardTemplate.js +2 -1
- package/dist/components/Card/templates/HorizontalCardTemplate.js +2 -1
- package/dist/components/Card/templates/ItemCardTemplate.js +2 -1
- package/dist/components/Card/templates/KitContentCardTemplate.js +2 -1
- package/dist/components/Card/templates/ShippingAddressCardTemplate.js +2 -1
- package/dist/components/Card/templates/VerticalCardTemplate.js +2 -1
- package/dist/components/DetailSection/types.d.ts +20 -1
- package/dist/components/EnvironmentBadge/EnvironmentBadge.css +55 -0
- package/dist/components/EnvironmentBadge/EnvironmentBadge.d.ts +4 -0
- package/dist/components/EnvironmentBadge/EnvironmentBadge.js +26 -0
- package/dist/components/EnvironmentBadge/index.d.ts +1 -0
- package/dist/components/EnvironmentBadge/index.js +1 -0
- package/dist/components/Footer/Footer.js +2 -1
- package/dist/components/Hero/Hero.js +3 -2
- package/dist/components/Image/Image.js +3 -5
- package/dist/components/Image/types.d.ts +1 -0
- package/dist/components/Nav/Nav.js +2 -1
- package/dist/components/Table/components/cellTypes/ClientCell.d.ts +5 -1
- package/dist/components/Table/components/cellTypes/ClientCell.js +7 -4
- package/dist/components/Table/components/cellTypes/CopyableCell.d.ts +2 -1
- package/dist/components/Table/components/cellTypes/CopyableCell.js +4 -3
- package/dist/components/Table/components/cellTypes/EditableCell.js +2 -2
- package/dist/components/Table/components/cellTypes/ImageCell.d.ts +4 -0
- package/dist/components/Table/components/cellTypes/ImageCell.js +4 -0
- package/dist/components/Table/components/cellTypes/UserAvatarCell.d.ts +5 -1
- package/dist/components/Table/components/cellTypes/UserAvatarCell.js +7 -4
- package/dist/components/Table/components/columnFiltersAndSorts/HeaderFilterDate.js +2 -2
- package/dist/components/Table/components/columnFiltersAndSorts/HeaderFilterMultiselect.js +1 -1
- package/dist/components/Table/components/columnFiltersAndSorts/HeaderFilterRange.js +1 -1
- package/dist/components/Table/components/columnFiltersAndSorts/HeaderFilterSearch.js +2 -2
- package/dist/components/Table/components/columnFiltersAndSorts/SortIcon.js +2 -2
- package/dist/components/Table/hooks/index.d.ts +2 -0
- package/dist/components/Table/hooks/index.js +2 -0
- package/dist/components/Table/hooks/useAssignTableFieldLabels.d.ts +16 -0
- package/dist/components/Table/hooks/useAssignTableFieldLabels.js +39 -0
- package/dist/components/Table/hooks/useTableData.d.ts +21 -0
- package/dist/components/Table/hooks/useTableData.js +94 -0
- package/dist/components/Table/index.d.ts +1 -0
- package/dist/components/Table/index.js +1 -0
- package/dist/components/Table/themeConfig/toga.module.css +125 -33
- package/dist/components/Table/types.d.ts +27 -0
- package/dist/components/Table/utils/buildActionColumn.js +1 -1
- package/dist/components/Table/utils/buildTanstackColumns.js +26 -6
- package/dist/components/Table/utils/resolveCellType.d.ts +1 -2
- package/dist/components/Table/utils/resolveCellType.js +20 -34
- package/dist/components/Table/variants.d.ts +1 -1
- package/dist/components/TableRecordModal/TableRecordModal.d.ts +2 -1
- package/dist/components/TableRecordModal/TableRecordModal.js +2 -2
- package/dist/components/TableRecordModal/tableRecordModal.module.css +106 -116
- package/dist/components/Toaster/Toaster.js +12 -8
- package/dist/components/Toaster/Toaster.module.css +53 -0
- package/dist/components/Toaster/types.d.ts +6 -2
- package/dist/components/ToggleButton/ToggleButton.d.ts +3 -0
- package/dist/components/ToggleButton/ToggleButton.js +26 -47
- package/dist/components/ToggleButton/ToggleButton.module.css +203 -0
- package/dist/components/ToggleButton/ToggleButton.types.d.ts +13 -11
- package/dist/components/index.d.ts +2 -3
- package/dist/components/index.js +2 -3
- package/dist/components/old/BaseInput/BaseCheckbox.d.ts +10 -0
- package/dist/components/old/BaseInput/BaseCheckbox.js +9 -0
- package/dist/components/old/BaseInput/BaseInput.d.ts +4 -0
- package/dist/components/old/BaseInput/BaseInput.js +100 -0
- package/dist/components/old/BaseInput/BaseInput.types.d.ts +139 -0
- package/dist/components/old/BaseInput/BaseMultiSelect.d.ts +33 -0
- package/dist/components/old/BaseInput/BaseMultiSelect.js +68 -0
- package/dist/components/old/BaseInput/BaseRadio.d.ts +18 -0
- package/dist/components/old/BaseInput/BaseRadio.js +7 -0
- package/dist/components/old/BaseInput/BaseSelect.d.ts +41 -0
- package/dist/components/old/BaseInput/BaseSelect.js +83 -0
- package/dist/components/old/BaseInput/BaseTextInput.d.ts +27 -0
- package/dist/components/old/BaseInput/BaseTextInput.js +44 -0
- package/dist/components/old/BaseInput/BaseTextareaInput.d.ts +27 -0
- package/dist/components/old/BaseInput/BaseTextareaInput.js +36 -0
- package/dist/components/old/BaseInput/BaseToggle.d.ts +24 -0
- package/dist/components/old/BaseInput/BaseToggle.js +8 -0
- package/dist/components/old/BaseInput/DisabledSelect.d.ts +7 -0
- package/dist/components/old/BaseInput/DisabledSelect.js +6 -0
- package/dist/components/old/BaseInput/components/BaseErrorMessage.d.ts +8 -0
- package/dist/components/old/BaseInput/components/BaseErrorMessage.js +5 -0
- package/dist/components/old/BaseInput/components/CharacterLimitMessage.d.ts +9 -0
- package/dist/components/old/BaseInput/components/CharacterLimitMessage.js +7 -0
- package/dist/components/old/BaseInput/components/DropDownIndicator.d.ts +6 -0
- package/dist/components/old/BaseInput/components/DropDownIndicator.js +5 -0
- package/dist/components/old/BaseInput/components/MultiValueRemove.d.ts +4 -0
- package/dist/components/old/BaseInput/components/MultiValueRemove.js +7 -0
- package/dist/components/old/BaseInput/components/Option.d.ts +2 -0
- package/dist/components/old/BaseInput/components/Option.js +19 -0
- package/dist/components/old/BaseInput/components/SingleValue.d.ts +4 -0
- package/dist/components/old/BaseInput/components/SingleValue.js +14 -0
- package/dist/components/old/BaseInput/index.d.ts +1 -0
- package/dist/components/old/BaseInput/index.js +1 -0
- package/dist/components/old/Toaster/Toaster.d.ts +4 -0
- package/dist/components/old/Toaster/Toaster.js +20 -0
- package/dist/components/old/Toaster/index.d.ts +2 -0
- package/dist/components/old/Toaster/index.js +2 -0
- package/dist/components/old/Toaster/types.d.ts +17 -0
- package/dist/components/old/ToggleButton/ToggleButton.d.ts +4 -0
- package/dist/components/old/ToggleButton/ToggleButton.js +52 -0
- package/dist/components/old/ToggleButton/ToggleButton.types.d.ts +24 -0
- package/dist/components/old/ToggleButton/index.d.ts +1 -0
- package/dist/components/old/ToggleButton/index.js +1 -0
- package/dist/main.css +1 -1
- package/dist/reactQuery/queryHelpers.js +8 -4
- package/dist/templates/AddNotes/AddNotes.module.css +24 -1
- package/dist/templates/AddNotes/AddNotesTemplate.js +6 -7
- package/dist/templates/AddNotes/types.d.ts +31 -0
- package/dist/templates/PrimaryTable/PrimaryTable.js +270 -9
- package/dist/templates/PrimaryTable/PrimaryTableClientTemplate.js +2 -2
- package/dist/templates/PrimaryTable/PrimaryTableHeaderCell.d.ts +1 -1
- package/dist/templates/PrimaryTable/PrimaryTableHeaderCell.js +4 -4
- package/dist/templates/PrimaryTable/PrimaryTableServerTemplate.js +2 -2
- package/dist/templates/PrimaryTable/types.d.ts +5 -0
- package/dist/utils/getEndpointFromEnvironment.d.ts +20 -0
- package/dist/utils/getEndpointFromEnvironment.js +53 -0
- package/package.json +2 -2
- package/dist/components/Dropdown/Dropdown.d.ts +0 -4
- package/dist/components/Dropdown/Dropdown.js +0 -26
- package/dist/components/Dropdown/index.d.ts +0 -2
- package/dist/components/Dropdown/index.js +0 -2
- package/dist/components/Dropdown/types.d.ts +0 -19
- package/dist/components/ModalTag/ModalTag.d.ts +0 -4
- package/dist/components/ModalTag/ModalTag.js +0 -9
- package/dist/components/ModalTag/ModalTag.module.css +0 -22
- package/dist/components/ModalTag/index.d.ts +0 -3
- package/dist/components/ModalTag/index.js +0 -2
- package/dist/components/ModalTag/types.d.ts +0 -7
- package/dist/components/NotesList/NoteItem.d.ts +0 -4
- package/dist/components/NotesList/NoteItem.js +0 -7
- package/dist/components/NotesList/NotesList.d.ts +0 -4
- package/dist/components/NotesList/NotesList.js +0 -7
- package/dist/components/NotesList/index.d.ts +0 -2
- package/dist/components/NotesList/index.js +0 -2
- package/dist/components/NotesList/types.d.ts +0 -50
- package/dist/components/SearchInput/BaseSearchInput.d.ts +0 -4
- package/dist/components/SearchInput/BaseSearchInput.js +0 -21
- package/dist/components/SearchInput/SearchDropdown.scss +0 -41
- package/dist/components/SearchInput/SearchDropdownInput.d.ts +0 -36
- package/dist/components/SearchInput/SearchDropdownInput.js +0 -122
- package/dist/components/SearchInput/SearchInput.d.ts +0 -3
- package/dist/components/SearchInput/SearchInput.js +0 -32
- package/dist/components/SearchInput/SearchInput.types.d.ts +0 -109
- package/dist/components/SearchInput/SearchInputDatePicker.d.ts +0 -47
- package/dist/components/SearchInput/SearchInputDatePicker.js +0 -388
- package/dist/components/SearchInput/SearchNumberInput.d.ts +0 -66
- package/dist/components/SearchInput/SearchNumberInput.js +0 -169
- package/dist/components/SearchInput/SearchTextInput.d.ts +0 -29
- package/dist/components/SearchInput/SearchTextInput.js +0 -122
- package/dist/components/Table/components/RowMenu.d.ts +0 -4
- package/dist/components/Table/components/RowMenu.js +0 -23
- /package/dist/components/{Dropdown/types.js → AdvancedSelect/AdvancedSelect.types.js} +0 -0
- /package/dist/components/{ModalTag/types.js → old/BaseInput/BaseInput.types.js} +0 -0
- /package/dist/components/{NotesList → old/Toaster}/types.js +0 -0
- /package/dist/components/{SearchInput/SearchInput.types.js → old/ToggleButton/ToggleButton.types.js} +0 -0
|
@@ -3,14 +3,6 @@
|
|
|
3
3
|
// Supply-specific logic lives in the consuming app via additionalData/extraWhereConditions
|
|
4
4
|
import { apiGet } from "../apiFunctions.js";
|
|
5
5
|
export const getDataTableData = async (tableViewMeta, additionalData = {}, extraFields = [], searchParams, options) => {
|
|
6
|
-
// console.group(`[getDataTableData] slug: ${dataTableMeta?.slug}`);
|
|
7
|
-
// console.log("dataTableMeta:", dataTableMeta);
|
|
8
|
-
// console.log("additionalData:", additionalData);
|
|
9
|
-
// console.log("extraFields:", extraFields);
|
|
10
|
-
// console.log(
|
|
11
|
-
// "searchParams:",
|
|
12
|
-
// Object.fromEntries((searchParams ?? new URLSearchParams()).entries()),
|
|
13
|
-
// );
|
|
14
6
|
if (!tableViewMeta) {
|
|
15
7
|
console.error("[getDataTableData] Missing table metadata");
|
|
16
8
|
console.groupEnd();
|
|
@@ -22,7 +14,6 @@ export const getDataTableData = async (tableViewMeta, additionalData = {}, extra
|
|
|
22
14
|
console.groupEnd();
|
|
23
15
|
throw new Error(`tableViewMeta.fields is not iterable for slug: ${tableViewMeta.slug}`);
|
|
24
16
|
}
|
|
25
|
-
// console.log("tableViewFields:", tableViewFields);
|
|
26
17
|
const parentTableSlug = tableViewMeta.slug;
|
|
27
18
|
const queryParams = Object.fromEntries(searchParams);
|
|
28
19
|
const isInfinite = Boolean(additionalData.hasInfiniteScroll);
|
|
@@ -84,8 +75,17 @@ export const getDataTableData = async (tableViewMeta, additionalData = {}, extra
|
|
|
84
75
|
const stripBackticks = (s) => s.replace(/`/g, "");
|
|
85
76
|
for (const j of tableViewMeta.joins) {
|
|
86
77
|
const key = j.type === "OUTER" ? "ojoin" : "join";
|
|
78
|
+
// When a join aliases its table (e.g. joining `Addresses` a second
|
|
79
|
+
// time as `Addresses_C`), emit the `table@alias` form so the API
|
|
80
|
+
// declares the alias. Without this, two joins on the same physical
|
|
81
|
+
// table collide → MySQL 1066 "Not unique table/alias".
|
|
82
|
+
const tableKey = j.alias && j.alias !== j.table
|
|
83
|
+
? `${j.table}@${j.alias}`
|
|
84
|
+
: j.table;
|
|
87
85
|
apiOptions[key].push({
|
|
88
|
-
[
|
|
86
|
+
[tableKey]: {
|
|
87
|
+
[stripBackticks(j.onA)]: [stripBackticks(j.onB)],
|
|
88
|
+
},
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
91
|
if (apiOptions.join.length === 0)
|
|
@@ -457,11 +457,6 @@ export const getDataTableData = async (tableViewMeta, additionalData = {}, extra
|
|
|
457
457
|
apiOptions.where.and.push(...additionalData.extraWhereConditions);
|
|
458
458
|
}
|
|
459
459
|
// ── Fetch ─────────────────────────────────────────────────────
|
|
460
|
-
// console.log(
|
|
461
|
-
// "[getDataTableData] Final apiOptions:",
|
|
462
|
-
// JSON.stringify(apiOptions, null, 2),
|
|
463
|
-
// );
|
|
464
|
-
// console.log("[getDataTableData] Fetching route:", dataTableMeta.route);
|
|
465
460
|
let parentResponse;
|
|
466
461
|
try {
|
|
467
462
|
parentResponse = (await apiGet(tableViewMeta.route, apiOptions));
|
|
@@ -471,15 +466,12 @@ export const getDataTableData = async (tableViewMeta, additionalData = {}, extra
|
|
|
471
466
|
console.groupEnd();
|
|
472
467
|
throw err;
|
|
473
468
|
}
|
|
474
|
-
// console.log("[getDataTableData] Raw response:", parentResponse);
|
|
475
469
|
if (!parentResponse.isSuccess) {
|
|
476
470
|
console.error("[getDataTableData] Response not successful:", parentResponse);
|
|
477
471
|
options?.onError?.("parentResponse", parentResponse);
|
|
478
472
|
console.groupEnd();
|
|
479
473
|
return;
|
|
480
474
|
}
|
|
481
|
-
console.log("[getDataTableData] Success. Records count:", parentResponse?.data?.length ?? "unknown");
|
|
482
|
-
console.groupEnd();
|
|
483
475
|
return parentResponse;
|
|
484
476
|
};
|
|
485
477
|
// ── Page meta ─────────────────────────────────────────────────────
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
3
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
4
|
+
import classes from "./AdvancedSelect.module.css";
|
|
5
|
+
import { getFontAwesomeIcon } from "../../utils/index.js";
|
|
6
|
+
import BaseErrorMessage from "../BaseInput/components/BaseErrorMessage.js";
|
|
7
|
+
const cx = (...parts) => parts.filter(Boolean).join(" ");
|
|
8
|
+
const getAvatarInitials = (name) => name
|
|
9
|
+
.trim()
|
|
10
|
+
.split(/\s+/)
|
|
11
|
+
.slice(0, 2)
|
|
12
|
+
.map((part) => part.charAt(0).toUpperCase())
|
|
13
|
+
.join("");
|
|
14
|
+
function OptionAvatar({ name, className, }) {
|
|
15
|
+
const initials = getAvatarInitials(name); // Styling (including background color) is fully CSS-driven via the // `--advancedSelect-avatar-*` custom properties and the `avatarClassName` // prop. See AdvancedSelect.module.css for the available variables.
|
|
16
|
+
return (_jsx("span", { className: cx(classes.avatar, className), "aria-hidden": "true", children: initials }));
|
|
17
|
+
}
|
|
18
|
+
function AdvancedSelect(props) {
|
|
19
|
+
const { errorMessage, isFieldDirty, isFieldError, mode = "single", label, placeholder = "Search...", disabled = false, data, getOptionValue, getOptionLabel, getOptionSecondaryLabel, renderOption, showAvatar = false, getOptionAvatarText, avatarClassName, hasMorePages = false, isFetchingMore = false, fetchMoreData, findSpecificValue, searchDebounceMs = 350, searchMinChars = 3, noOptionsMessage = "No options", loadingMessage = "Loading...", rowHeight = 36, maxVisibleRows = 8, overscan = 6, className, menuClassName, onClearInput, onOpen, onClose, valueKey, } = props;
|
|
20
|
+
const isMulti = mode === "multi";
|
|
21
|
+
const [open, setOpen] = useState(false);
|
|
22
|
+
const [input, setInput] = useState("");
|
|
23
|
+
const [searchResults, setSearchResults] = useState(null);
|
|
24
|
+
const [searching, setSearching] = useState(false);
|
|
25
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
26
|
+
const wrapperRef = useRef(null);
|
|
27
|
+
const inputRef = useRef(null);
|
|
28
|
+
const listRef = useRef(null);
|
|
29
|
+
const debounceRef = useRef(null);
|
|
30
|
+
const abortRef = useRef(null); // Build options from current data source. // - With async search (findSpecificValue): use server results once length >= min chars. // - Without async search: filter `data` client-side by input text.
|
|
31
|
+
const sourceData = searchResults ?? data;
|
|
32
|
+
const allOptions = useMemo(() => (sourceData ?? []).map((item) => ({
|
|
33
|
+
value: getOptionValue(item),
|
|
34
|
+
label: getOptionLabel(item),
|
|
35
|
+
secondaryLabel: getOptionSecondaryLabel?.(item),
|
|
36
|
+
item,
|
|
37
|
+
})), [sourceData, getOptionValue, getOptionLabel, getOptionSecondaryLabel]); // Selected values as Set for fast lookup
|
|
38
|
+
const selectedValueSet = useMemo(() => {
|
|
39
|
+
const set = new Set();
|
|
40
|
+
if (isMulti) {
|
|
41
|
+
(props.value ?? []).forEach((it) => set.add(getOptionValue(it)));
|
|
42
|
+
}
|
|
43
|
+
else if (props.value) {
|
|
44
|
+
set.add(getOptionValue(props.value));
|
|
45
|
+
}
|
|
46
|
+
return set;
|
|
47
|
+
}, [isMulti, props.value, getOptionValue]);
|
|
48
|
+
const options = useMemo(() => {
|
|
49
|
+
const q = input.trim().toLowerCase();
|
|
50
|
+
const filtered = findSpecificValue || !q
|
|
51
|
+
? allOptions
|
|
52
|
+
: allOptions.filter((o) => o.label.toLowerCase().includes(q)); // Float selected option(s) to the top while preserving the relative // order of everything else (stable sort).
|
|
53
|
+
if (selectedValueSet.size === 0)
|
|
54
|
+
return filtered;
|
|
55
|
+
const selected = filtered.filter((o) => selectedValueSet.has(o.value));
|
|
56
|
+
const rest = filtered.filter((o) => !selectedValueSet.has(o.value));
|
|
57
|
+
return [...selected, ...rest];
|
|
58
|
+
}, [allOptions, input, findSpecificValue, selectedValueSet]); // Debounced server-side search
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!findSpecificValue)
|
|
61
|
+
return;
|
|
62
|
+
if (input.length < searchMinChars) {
|
|
63
|
+
setSearchResults(null);
|
|
64
|
+
setSearching(false);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
setSearching(true);
|
|
68
|
+
if (debounceRef.current)
|
|
69
|
+
clearTimeout(debounceRef.current);
|
|
70
|
+
if (abortRef.current)
|
|
71
|
+
abortRef.current.abort();
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
abortRef.current = controller;
|
|
74
|
+
debounceRef.current = setTimeout(async () => {
|
|
75
|
+
try {
|
|
76
|
+
const results = await findSpecificValue(input);
|
|
77
|
+
if (!controller.signal.aborted)
|
|
78
|
+
setSearchResults(results ?? []);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
if (!controller.signal.aborted)
|
|
82
|
+
setSearchResults([]);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
if (!controller.signal.aborted)
|
|
86
|
+
setSearching(false);
|
|
87
|
+
}
|
|
88
|
+
}, searchDebounceMs);
|
|
89
|
+
return () => {
|
|
90
|
+
if (debounceRef.current)
|
|
91
|
+
clearTimeout(debounceRef.current);
|
|
92
|
+
controller.abort();
|
|
93
|
+
};
|
|
94
|
+
}, [input, findSpecificValue, searchDebounceMs, searchMinChars]); // Outside click → close
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!open)
|
|
97
|
+
return;
|
|
98
|
+
const handler = (e) => {
|
|
99
|
+
if (!wrapperRef.current?.contains(e.target)) {
|
|
100
|
+
closeMenu();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
document.addEventListener("mousedown", handler);
|
|
104
|
+
return () => document.removeEventListener("mousedown", handler); // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
105
|
+
}, [open]);
|
|
106
|
+
const openMenu = useCallback(() => {
|
|
107
|
+
if (disabled || open)
|
|
108
|
+
return;
|
|
109
|
+
setOpen(true);
|
|
110
|
+
onOpen?.();
|
|
111
|
+
}, [disabled, open, onOpen]);
|
|
112
|
+
const closeMenu = useCallback(() => {
|
|
113
|
+
setOpen(false);
|
|
114
|
+
setActiveIndex(0);
|
|
115
|
+
onClose?.();
|
|
116
|
+
}, [onClose]); // Virtualizer
|
|
117
|
+
const showLoaderRow = hasMorePages && !searchResults;
|
|
118
|
+
const totalRows = options.length + (showLoaderRow ? 1 : 0);
|
|
119
|
+
const rowVirtualizer = useVirtualizer({
|
|
120
|
+
count: totalRows,
|
|
121
|
+
getScrollElement: () => listRef.current,
|
|
122
|
+
estimateSize: () => rowHeight,
|
|
123
|
+
overscan,
|
|
124
|
+
}); // Infinite scroll trigger
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (!open || !fetchMoreData || !hasMorePages || isFetchingMore)
|
|
127
|
+
return;
|
|
128
|
+
if (searchResults)
|
|
129
|
+
return; // don't auto-fetch while searching
|
|
130
|
+
const items = rowVirtualizer.getVirtualItems();
|
|
131
|
+
const last = items[items.length - 1];
|
|
132
|
+
if (last && last.index >= options.length - 1) {
|
|
133
|
+
void fetchMoreData();
|
|
134
|
+
}
|
|
135
|
+
}, [
|
|
136
|
+
open,
|
|
137
|
+
rowVirtualizer.getVirtualItems(),
|
|
138
|
+
fetchMoreData,
|
|
139
|
+
hasMorePages,
|
|
140
|
+
isFetchingMore,
|
|
141
|
+
searchResults,
|
|
142
|
+
options.length,
|
|
143
|
+
]);
|
|
144
|
+
const clearAll = useCallback(() => {
|
|
145
|
+
setInput("");
|
|
146
|
+
setSearchResults(null);
|
|
147
|
+
if (isMulti) {
|
|
148
|
+
props.onSelect([], valueKey);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
props.onSelect(null, valueKey);
|
|
152
|
+
}
|
|
153
|
+
onClearInput?.();
|
|
154
|
+
}, [isMulti, props.onSelect, valueKey, onClearInput]);
|
|
155
|
+
const handleSelect = useCallback((option) => {
|
|
156
|
+
if (isMulti) {
|
|
157
|
+
const current = props.value ?? [];
|
|
158
|
+
const already = selectedValueSet.has(option.value);
|
|
159
|
+
const next = already
|
|
160
|
+
? current.filter((it) => getOptionValue(it) !== option.value)
|
|
161
|
+
: [...current, option.item];
|
|
162
|
+
props.onSelect(next, valueKey);
|
|
163
|
+
setInput("");
|
|
164
|
+
inputRef.current?.focus();
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
props.onSelect(option.item, valueKey); // Clear the search text (not set it to the label) so reopening // the menu shows the full options list instead of filtering it // down to the selected label. The selected label is still shown // in the closed input via `singleSelectedLabel`.
|
|
168
|
+
setInput("");
|
|
169
|
+
closeMenu();
|
|
170
|
+
}
|
|
171
|
+
}, [
|
|
172
|
+
isMulti,
|
|
173
|
+
props.value,
|
|
174
|
+
props.onSelect,
|
|
175
|
+
selectedValueSet,
|
|
176
|
+
getOptionValue,
|
|
177
|
+
valueKey,
|
|
178
|
+
closeMenu,
|
|
179
|
+
]);
|
|
180
|
+
const removeChip = useCallback((itemValue) => {
|
|
181
|
+
if (!isMulti)
|
|
182
|
+
return;
|
|
183
|
+
const current = props.value ?? [];
|
|
184
|
+
const next = current.filter((it) => getOptionValue(it) !== itemValue);
|
|
185
|
+
props.onSelect(next, valueKey);
|
|
186
|
+
}, [isMulti, props.value, props.onSelect, getOptionValue, valueKey]);
|
|
187
|
+
const onKeyDown = (e) => {
|
|
188
|
+
if (e.key === "ArrowDown") {
|
|
189
|
+
e.preventDefault();
|
|
190
|
+
openMenu();
|
|
191
|
+
setActiveIndex((i) => Math.min(i + 1, options.length - 1));
|
|
192
|
+
}
|
|
193
|
+
else if (e.key === "ArrowUp") {
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
setActiveIndex((i) => Math.max(i - 1, 0));
|
|
196
|
+
}
|
|
197
|
+
else if (e.key === "Enter") {
|
|
198
|
+
if (open && options[activeIndex]) {
|
|
199
|
+
e.preventDefault();
|
|
200
|
+
handleSelect(options[activeIndex]);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else if (e.key === "Escape") {
|
|
204
|
+
closeMenu();
|
|
205
|
+
}
|
|
206
|
+
else if (e.key === "Backspace" &&
|
|
207
|
+
isMulti &&
|
|
208
|
+
input === "" &&
|
|
209
|
+
props.value?.length) {
|
|
210
|
+
const list = props.value;
|
|
211
|
+
removeChip(getOptionValue(list[list.length - 1]));
|
|
212
|
+
}
|
|
213
|
+
}; // Display value for single mode when not focused/typing
|
|
214
|
+
const singleSelectedLabel = !isMulti && props.value ? getOptionLabel(props.value) : "";
|
|
215
|
+
const singleSelectedSecondaryLabel = !isMulti && props.value
|
|
216
|
+
? getOptionSecondaryLabel?.(props.value) ?? ""
|
|
217
|
+
: "";
|
|
218
|
+
// When a single value has a secondary label and the menu is closed, show a
|
|
219
|
+
// styled label+secondary node instead of the plain native input text.
|
|
220
|
+
const showStyledSingleValue = !isMulti && !!props.value && !open && !!singleSelectedSecondaryLabel;
|
|
221
|
+
const displayedInput = isMulti
|
|
222
|
+
? input
|
|
223
|
+
: open
|
|
224
|
+
? input
|
|
225
|
+
: singleSelectedLabel || input;
|
|
226
|
+
const chips = isMulti && props.value?.length
|
|
227
|
+
? props.value.map((it) => ({
|
|
228
|
+
value: getOptionValue(it),
|
|
229
|
+
label: getOptionLabel(it),
|
|
230
|
+
avatarText: getOptionAvatarText?.(it) ?? getOptionLabel(it),
|
|
231
|
+
}))
|
|
232
|
+
: []; // Avatar shown alongside the selected option in single mode, while the // selected label (not an active search) is displayed in the input.
|
|
233
|
+
const showSingleAvatar = !isMulti &&
|
|
234
|
+
showAvatar &&
|
|
235
|
+
!!props.value &&
|
|
236
|
+
!open &&
|
|
237
|
+
!!singleSelectedLabel;
|
|
238
|
+
const singleAvatarText = showSingleAvatar
|
|
239
|
+
? getOptionAvatarText?.(props.value) ?? singleSelectedLabel
|
|
240
|
+
: "";
|
|
241
|
+
const virtualItems = rowVirtualizer.getVirtualItems();
|
|
242
|
+
const hasAnyValue = isMulti
|
|
243
|
+
? chips.length > 0 || input.length > 0
|
|
244
|
+
: !!props.value || input.length > 0;
|
|
245
|
+
return (_jsxs("div", { ref: wrapperRef, className: cx(classes.wrapper, className, mode === "multi" && classes.multi), children: [label && _jsx("label", { className: classes.label, children: label }), _jsxs("div", { className: cx(classes.control, disabled && classes.disabled, isFieldError
|
|
246
|
+
? classes.error
|
|
247
|
+
: isFieldDirty && classes.dirty), onClick: () => {
|
|
248
|
+
if (!disabled) {
|
|
249
|
+
openMenu();
|
|
250
|
+
inputRef.current?.focus();
|
|
251
|
+
}
|
|
252
|
+
}, children: [_jsxs("div", { className: classes.chipsAndInput, children: [chips.map((c) => (_jsxs("span", { className: classes.chip, children: [showAvatar && (_jsx("div", { className: "pr-0.5", children: _jsx(OptionAvatar, { name: singleAvatarText, className: avatarClassName }) })), c.label, _jsx("button", { type: "button", className: classes.chipRemove, onClick: (e) => {
|
|
253
|
+
e.stopPropagation();
|
|
254
|
+
removeChip(c.value);
|
|
255
|
+
}, "aria-label": `Remove ${c.label}`, children: getFontAwesomeIcon("xmark", "regular") })] }, c.value))), showSingleAvatar && (_jsx("div", { className: "pr-0.5", children: _jsx(OptionAvatar, { name: singleAvatarText, className: avatarClassName }) })), showStyledSingleValue && (_jsxs("span", { className: classes.singleValue, children: [_jsx("span", { className: classes.optionPrimary, children: singleSelectedLabel }), _jsx("span", { className: classes.optionSecondary, children: singleSelectedSecondaryLabel })] })), _jsx("input", { ref: inputRef, id: valueKey, type: "text", className: cx(classes.input, mode === "multi" && classes.multiInput, showStyledSingleValue && classes.inputCollapsed), value: showStyledSingleValue ? "" : displayedInput, placeholder: chips.length === 0 ? placeholder : "", disabled: disabled, autoComplete: "off", onChange: (e) => {
|
|
256
|
+
setInput(e.target.value);
|
|
257
|
+
openMenu();
|
|
258
|
+
}, onFocus: openMenu, onKeyDown: onKeyDown })] }), _jsxs("div", { className: classes.indicators, children: [hasAnyValue && (_jsx("button", { type: "button", className: classes.indicatorBtn, disabled: disabled, onClick: (e) => {
|
|
259
|
+
e.stopPropagation();
|
|
260
|
+
clearAll();
|
|
261
|
+
}, "aria-label": "Clear", children: _jsx("span", { className: classes.clearSingleInput, children: getFontAwesomeIcon("xmark", "regular") }) })), _jsx("button", { type: "button", className: classes.indicatorBtn, disabled: disabled, onClick: (e) => {
|
|
262
|
+
e.stopPropagation();
|
|
263
|
+
if (open)
|
|
264
|
+
closeMenu();
|
|
265
|
+
else {
|
|
266
|
+
openMenu();
|
|
267
|
+
inputRef.current?.focus();
|
|
268
|
+
}
|
|
269
|
+
}, "aria-label": "Toggle menu", children: getFontAwesomeIcon("chevronDown", "regular") })] })] }), open && (_jsx("div", { className: cx(classes.menu, menuClassName), children: _jsx("div", { ref: listRef, className: classes.menuList, style: {
|
|
270
|
+
maxHeight: `${rowHeight * maxVisibleRows}px`,
|
|
271
|
+
}, children: searching ? (_jsx("div", { className: classes.statusRow, children: loadingMessage })) : options.length === 0 ? (_jsx("div", { className: classes.statusRow, children: noOptionsMessage })) : (_jsx("div", { className: classes.virtualRows, style: {
|
|
272
|
+
height: `${rowVirtualizer.getTotalSize()}px`,
|
|
273
|
+
}, children: virtualItems.map((vRow) => {
|
|
274
|
+
const isLoaderRow = showLoaderRow &&
|
|
275
|
+
vRow.index === options.length;
|
|
276
|
+
const style = {
|
|
277
|
+
position: "absolute",
|
|
278
|
+
top: 0,
|
|
279
|
+
left: 0,
|
|
280
|
+
width: "100%",
|
|
281
|
+
transform: `translateY(${vRow.start}px)`,
|
|
282
|
+
height: `${vRow.size}px`,
|
|
283
|
+
};
|
|
284
|
+
if (isLoaderRow) {
|
|
285
|
+
return (_jsx("div", { style: style, className: classes.shimmerRow }, "loader"));
|
|
286
|
+
}
|
|
287
|
+
const option = options[vRow.index];
|
|
288
|
+
const isSelected = selectedValueSet.has(option.value);
|
|
289
|
+
const isActive = vRow.index === activeIndex;
|
|
290
|
+
return (_jsx("div", { style: style, className: cx(classes.option, isSelected && classes.selected, isActive && classes.active), onMouseEnter: () => setActiveIndex(vRow.index), onMouseDown: (e) => e.preventDefault(), onClick: () => handleSelect(option), children: _jsxs("label", { className: classes.optionLabel, children: [mode === "multi" ? (_jsxs(_Fragment, { children: [_jsx("input", { type: "checkbox", checked: isSelected, onChange: () => handleSelect(option) }), _jsx("span", { className: classes.checkIcon, children: getFontAwesomeIcon("check", "regular") })] })) : (_jsx(_Fragment, { children: isSelected && (_jsx("span", { className: classes.optionCheck, children: getFontAwesomeIcon("check", "regular") })) })), showAvatar && (_jsx("div", { className: "pr-0.5", children: _jsx(OptionAvatar, { name: getOptionAvatarText?.(option.item) ??
|
|
291
|
+
option.label, className: avatarClassName }) })), option.secondaryLabel ? (_jsxs("span", { className: classes.optionText, children: [_jsx("span", { className: classes.optionPrimary, children: option.label }), _jsx("span", { className: classes.optionSecondary, children: option.secondaryLabel })] })) : (_jsx("span", { children: option.label }))] }) }, option.value));
|
|
292
|
+
}) })) }) })), errorMessage && _jsx(BaseErrorMessage, { errorMessage: errorMessage })] }));
|
|
293
|
+
}
|
|
294
|
+
export default AdvancedSelect;
|