@up42/up-components 9.0.0 → 9.2.0
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/esm/index.js +542 -12
- package/dist/index.d.ts +190 -9
- package/package.json +8 -4
package/dist/esm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fade, Slide, Grow, createTheme, SvgIcon, Alert as Alert$1, AlertTitle, capitalize as capitalize$1, Box, Snackbar, CssBaseline, CircularProgress, Avatar as Avatar$1, Grid, Container, Checkbox as Checkbox$1, FormLabel as FormLabel$1, FormControl, FormGroup, FormControlLabel, FormHelperText, InputAdornment, IconButton, Button as Button$1, Radio as Radio$1, RadioGroup, Switch as Switch$1, Select as Select$1, MenuItem, TextField, Slider as Slider$1, Link as Link$1, Tab as Tab$1, Tabs as Tabs$1, Divider as Divider$1, Card, CardContent, Modal, Tooltip, Popover as Popover$1, Typography as Typography$1, Stack, Badge as Badge$1,
|
|
1
|
+
import { Fade, Slide, Grow, createTheme, SvgIcon, Alert as Alert$1, AlertTitle, capitalize as capitalize$1, Box, Snackbar, CssBaseline, CircularProgress, Avatar as Avatar$1, Grid, Container, Checkbox as Checkbox$1, FormLabel as FormLabel$1, FormControl, FormGroup, FormControlLabel, FormHelperText, InputAdornment, IconButton, Button as Button$1, Radio as Radio$1, RadioGroup, Switch as Switch$1, Select as Select$1, MenuItem, TextField, Slider as Slider$1, Link as Link$1, Tab as Tab$1, Tabs as Tabs$1, Divider as Divider$1, Card, CardContent, Modal, Tooltip, Popover as Popover$1, Chip, Autocomplete, Typography as Typography$1, Stack, Badge as Badge$1, Menu, styled, Dialog, alpha } from '@mui/material';
|
|
2
2
|
export * from '@mui/material';
|
|
3
3
|
import { ThemeProvider } from '@mui/material/styles';
|
|
4
4
|
import * as React from 'react';
|
|
@@ -27,6 +27,7 @@ import MuiTablePagination$1 from '@mui/material/TablePagination';
|
|
|
27
27
|
import MuiTableSortLabel from '@mui/material/TableSortLabel';
|
|
28
28
|
import MuiTableFooter$1 from '@mui/material/TableFooter';
|
|
29
29
|
import { useSearchParams, useNavigate } from 'react-router';
|
|
30
|
+
import { isSchema } from 'yup';
|
|
30
31
|
|
|
31
32
|
var color$2 = {
|
|
32
33
|
gray0: "#FFFFFF",
|
|
@@ -804,6 +805,20 @@ const MuiSwitch = {
|
|
|
804
805
|
|
|
805
806
|
const MuiSlider = {
|
|
806
807
|
styleOverrides: {
|
|
808
|
+
root: {
|
|
809
|
+
'& .MuiSlider-markLabel': {
|
|
810
|
+
fontWeight: tokens.typography.fontWeight.regular,
|
|
811
|
+
fontSize: tokens.typography.fontSize400,
|
|
812
|
+
// Target the FIRST element with this specific class and remove the transform
|
|
813
|
+
'&:nth-child(1 of .MuiSlider-markLabel)': {
|
|
814
|
+
transform: 'none',
|
|
815
|
+
},
|
|
816
|
+
// Target the LAST element with this specific class and reset the transform
|
|
817
|
+
'&:nth-last-child(1 of .MuiSlider-markLabel)': {
|
|
818
|
+
transform: 'translateX(-100%)',
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
},
|
|
807
822
|
rail: {
|
|
808
823
|
height: tokens.size.spacingXS,
|
|
809
824
|
backgroundColor: tokens.color.gray100,
|
|
@@ -6895,6 +6910,14 @@ const BasePopover = ({ open, onClose, anchorEl, header, children, footerInfo, pr
|
|
|
6895
6910
|
e.stopPropagation();
|
|
6896
6911
|
primaryButton?.onClick?.(e).then(handleClose);
|
|
6897
6912
|
};
|
|
6913
|
+
const handleSecondaryButtonClick = (e) => {
|
|
6914
|
+
e.stopPropagation();
|
|
6915
|
+
if (secondaryButton?.onClick) {
|
|
6916
|
+
void Promise.resolve(secondaryButton.onClick(e));
|
|
6917
|
+
return;
|
|
6918
|
+
}
|
|
6919
|
+
handleClose();
|
|
6920
|
+
};
|
|
6898
6921
|
const handleClose = () => {
|
|
6899
6922
|
onClose?.();
|
|
6900
6923
|
setPopoverAnchorEl(null);
|
|
@@ -6969,7 +6992,7 @@ const BasePopover = ({ open, onClose, anchorEl, header, children, footerInfo, pr
|
|
|
6969
6992
|
} },
|
|
6970
6993
|
footerInfo && footerInfo,
|
|
6971
6994
|
React__default.createElement(Box, { sx: { display: 'flex', gap: tokens.size.spacing.scale8, marginLeft: 'auto' } },
|
|
6972
|
-
secondaryButton && (React__default.createElement(Button, { variant: "outlined", size: "slim", ...secondaryButton, onClick:
|
|
6995
|
+
secondaryButton && (React__default.createElement(Button, { variant: "outlined", size: "slim", ...secondaryButton, onClick: handleSecondaryButtonClick }, secondaryButton.label)),
|
|
6973
6996
|
primaryButton && (React__default.createElement(Button, { variant: "contained", size: "slim", ...primaryButton, onClick: handlePrimaryButtonClick, ...(primaryButton.startIcon && { startIcon: React__default.createElement(Icon, { name: primaryButton.startIcon }) }) }, primaryButton.label)))))),
|
|
6974
6997
|
children))));
|
|
6975
6998
|
};
|
|
@@ -7000,14 +7023,26 @@ const DEFAULT_HEADER = 'Filters';
|
|
|
7000
7023
|
*
|
|
7001
7024
|
* Documentation: https://up-components.up42.com/?path=/docs-patterns-popovers-filterpopover--docs
|
|
7002
7025
|
*/
|
|
7003
|
-
const FilterPopover = ({ anchorEl, onClose, header = DEFAULT_HEADER, content, applyButtonLabel = DEFAULT_APPLY_LABEL, clearButtonLabel = DEFAULT_CLEAR_LABEL, disableApplyButton = false, isLoadingCount = false, onApply, onClear, ...popoverProps }) => {
|
|
7026
|
+
const FilterPopover = ({ anchorEl, onClose, header = DEFAULT_HEADER, content, applyButtonLabel = DEFAULT_APPLY_LABEL, clearButtonLabel = DEFAULT_CLEAR_LABEL, disableApplyButton = false, isLoadingCount = false, maxContentWidth = '384px', onApply, onClear, ...popoverProps }) => {
|
|
7004
7027
|
const handleApply = async () => {
|
|
7005
7028
|
await onApply?.();
|
|
7006
7029
|
};
|
|
7007
7030
|
const handleClear = async () => {
|
|
7008
7031
|
await onClear?.();
|
|
7009
7032
|
};
|
|
7010
|
-
return (React__default.createElement(Popover, { open: Boolean(anchorEl), anchorEl: anchorEl, onClose: onClose, header: header, content: content
|
|
7033
|
+
return (React__default.createElement(Popover, { open: Boolean(anchorEl), anchorEl: anchorEl, onClose: onClose, header: header, content: content && content.length > 0 ? (React__default.createElement(Box, { sx: {
|
|
7034
|
+
display: 'flex',
|
|
7035
|
+
flexDirection: 'column',
|
|
7036
|
+
gap: tokens.size.spacing.scale16,
|
|
7037
|
+
maxWidth: maxContentWidth,
|
|
7038
|
+
'& .MuiSlider-root': {
|
|
7039
|
+
display: 'flex',
|
|
7040
|
+
width: '90%',
|
|
7041
|
+
mx: 'auto',
|
|
7042
|
+
},
|
|
7043
|
+
} }, content.map((item, index) => (React__default.createElement(React__default.Fragment, { key: index },
|
|
7044
|
+
item,
|
|
7045
|
+
index < content.length - 1 && React__default.createElement(Divider$1, { sx: { mx: 0 } })))))) : undefined, showDividers: true, hideBackdrop: true, disableEnforceFocus: true, disableScrollLock: true, sx: {
|
|
7011
7046
|
// Allows to interact with other elements outside the popover when the popover is open
|
|
7012
7047
|
pointerEvents: 'none',
|
|
7013
7048
|
'& > .MuiPaper-root': {
|
|
@@ -7024,6 +7059,216 @@ const FilterPopover = ({ anchorEl, onClose, header = DEFAULT_HEADER, content, ap
|
|
|
7024
7059
|
}, ...popoverProps }));
|
|
7025
7060
|
};
|
|
7026
7061
|
|
|
7062
|
+
/**
|
|
7063
|
+
* Documentation: https://up-components.up42.com/?path=/docs/data-display-tag--docs
|
|
7064
|
+
*/
|
|
7065
|
+
const Tag = ({ color = 'primary', ...props }) => {
|
|
7066
|
+
return React__default.createElement(Chip, { size: "small", color: color, deleteIcon: React__default.createElement(Icon, { name: "Close", "data-testid": "CloseIcon" }), ...props });
|
|
7067
|
+
};
|
|
7068
|
+
|
|
7069
|
+
const DELIMITERS = [',', ';'];
|
|
7070
|
+
const SPLIT_REGEXP = new RegExp(DELIMITERS.join('|'), 'g');
|
|
7071
|
+
/**
|
|
7072
|
+
* Autocomplete-based single or multi-select form input. Selected values are displayed
|
|
7073
|
+
* as chips inside the input field.
|
|
7074
|
+
* Supports adding custom values by typing and pressing Enter (comma/semicolon delimiters).
|
|
7075
|
+
*
|
|
7076
|
+
* Documentation: https://up-components.up42.com/?path=/docs/patterns-form-formautocomplete--docs
|
|
7077
|
+
*/
|
|
7078
|
+
const FormAutocomplete = ({ id, component = 'div', title, label, valuesMap = {}, values = [], onChange, placeholder, multiple = false, limitTags = -1, // -1 means no limit
|
|
7079
|
+
addCustom = false, allOptionsSelectedPlaceholder, required, disabled, error, helperText, sx, groupBy, }) => {
|
|
7080
|
+
const [inputValue, setInputValue] = useState('');
|
|
7081
|
+
/**
|
|
7082
|
+
* Used to show the allOptionsSelectedPlaceholder when the input is not focused.
|
|
7083
|
+
* @default false
|
|
7084
|
+
*/
|
|
7085
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
7086
|
+
/**
|
|
7087
|
+
* Options to display in the autocomplete.
|
|
7088
|
+
*/
|
|
7089
|
+
const options = useMemo(() => generateOptions(valuesMap), [valuesMap]);
|
|
7090
|
+
/**
|
|
7091
|
+
* Value for the autocomplete.
|
|
7092
|
+
*/
|
|
7093
|
+
const valueForAutocomplete = useMemo(() => {
|
|
7094
|
+
const inputValues = values.map((id) => ({
|
|
7095
|
+
label: valuesMap[id]?.title ?? id,
|
|
7096
|
+
id,
|
|
7097
|
+
}));
|
|
7098
|
+
return multiple ? inputValues : inputValues[0] ?? null;
|
|
7099
|
+
}, [values, valuesMap]);
|
|
7100
|
+
const allOptionsSelected = options.length > 0 && values.length === options.length;
|
|
7101
|
+
const showAllOptionsSelectedPlaceholder = multiple && !addCustom && Boolean(allOptionsSelectedPlaceholder) && allOptionsSelected && !isFocused;
|
|
7102
|
+
/** Converts typed string to options. With addCustom: split by comma/semicolon. Otherwise: match valuesMap by title. */
|
|
7103
|
+
const expandStringToOptions = (input) => {
|
|
7104
|
+
if (input.trim() === '')
|
|
7105
|
+
return [];
|
|
7106
|
+
if (addCustom) {
|
|
7107
|
+
return splitInput(input).map((p) => ({ label: p, id: p }));
|
|
7108
|
+
}
|
|
7109
|
+
const matchingIds = Object.values(valuesMap)
|
|
7110
|
+
.filter(({ title }) => isSubstring(title, input))
|
|
7111
|
+
.map(({ name }) => name);
|
|
7112
|
+
return matchingIds.map((id) => ({ label: valuesMap[id]?.title ?? id, id }));
|
|
7113
|
+
};
|
|
7114
|
+
/** Converts a selected option to options. With addCustom: id may contain "x, y" delimiters to split. */
|
|
7115
|
+
const expandOptionToOptions = (option) => {
|
|
7116
|
+
if (addCustom) {
|
|
7117
|
+
const inputValues = splitInput(option.id);
|
|
7118
|
+
if (inputValues.length > 1) {
|
|
7119
|
+
return inputValues.map((value) => ({ label: value, id: value }));
|
|
7120
|
+
}
|
|
7121
|
+
}
|
|
7122
|
+
return [option];
|
|
7123
|
+
};
|
|
7124
|
+
/** Expands a single input item (string or option) into an array of options. */
|
|
7125
|
+
const expandInputItem = (item) => {
|
|
7126
|
+
return typeof item === 'string' ? expandStringToOptions(item) : expandOptionToOptions(toOption(item));
|
|
7127
|
+
};
|
|
7128
|
+
/**
|
|
7129
|
+
* Handles the change event for the autocomplete input.
|
|
7130
|
+
* @param _event - The event object.
|
|
7131
|
+
* @param newValue{AutocompleteValue | AutocompleteValue[]} - The new value(s) for the input. Can be a single value or an array of values(string or FormAutocompleteOption).
|
|
7132
|
+
*/
|
|
7133
|
+
const handleChange = (_event, newValue) => {
|
|
7134
|
+
setInputValue('');
|
|
7135
|
+
if (newValue == null) {
|
|
7136
|
+
onChange([]);
|
|
7137
|
+
return;
|
|
7138
|
+
}
|
|
7139
|
+
const items = Array.isArray(newValue) ? newValue : [newValue];
|
|
7140
|
+
const expandedOptions = items.flatMap(expandInputItem);
|
|
7141
|
+
const uniqueOptionsById = expandedOptions.filter((option, index) => expandedOptions.findIndex((o) => o.id === option.id) === index);
|
|
7142
|
+
const optionIds = uniqueOptionsById.map((option) => option.id);
|
|
7143
|
+
onChange(multiple ? optionIds : optionIds.slice(0, 1));
|
|
7144
|
+
};
|
|
7145
|
+
const handleInputChange = (_event, newInputValue) => {
|
|
7146
|
+
setInputValue(newInputValue);
|
|
7147
|
+
};
|
|
7148
|
+
const getOptionLabel = (option) => {
|
|
7149
|
+
if (typeof option === 'string')
|
|
7150
|
+
return option;
|
|
7151
|
+
return option.label;
|
|
7152
|
+
};
|
|
7153
|
+
const filterOptions = (options, { inputValue }) => {
|
|
7154
|
+
const filteredOptions = options.filter((option) => {
|
|
7155
|
+
const alreadySelected = values.includes(option.id);
|
|
7156
|
+
const matchesInput = inputValue === '' || isSubstring(option.label, inputValue);
|
|
7157
|
+
return !alreadySelected && matchesInput;
|
|
7158
|
+
});
|
|
7159
|
+
// When addCustom, suggest "Add X, Y, Z" for typed input (supports ; or , delimiters)
|
|
7160
|
+
const inputValues = splitInput(inputValue);
|
|
7161
|
+
const isExisting = filteredOptions.some((opt) => inputValue === opt.id);
|
|
7162
|
+
if (addCustom && !isExisting && inputValues.length > 0) {
|
|
7163
|
+
filteredOptions.push({
|
|
7164
|
+
label: `Add ${inputValues.join(', ')}`,
|
|
7165
|
+
id: inputValue,
|
|
7166
|
+
});
|
|
7167
|
+
}
|
|
7168
|
+
return filteredOptions;
|
|
7169
|
+
};
|
|
7170
|
+
const getInputPlaceholder = () => {
|
|
7171
|
+
if (showAllOptionsSelectedPlaceholder)
|
|
7172
|
+
return allOptionsSelectedPlaceholder;
|
|
7173
|
+
if (placeholder)
|
|
7174
|
+
return placeholder;
|
|
7175
|
+
if (title)
|
|
7176
|
+
return `Search by ${title.toLowerCase()}`;
|
|
7177
|
+
};
|
|
7178
|
+
const renderTags = (tagValue, getTagProps) => {
|
|
7179
|
+
if (showAllOptionsSelectedPlaceholder)
|
|
7180
|
+
return null;
|
|
7181
|
+
return tagValue.map((option, index) => {
|
|
7182
|
+
const opt = toOption(option);
|
|
7183
|
+
return React__default.createElement(Tag, { key: opt.id, label: opt.label, ...getTagProps({ index }) });
|
|
7184
|
+
});
|
|
7185
|
+
};
|
|
7186
|
+
return (React__default.createElement(FormControl, { component: component, error: error, disabled: disabled, sx: sx },
|
|
7187
|
+
React__default.createElement(FormLabel, { label: title, required: required, htmlFor: id }),
|
|
7188
|
+
React__default.createElement(Autocomplete, { freeSolo: true, fullWidth: true, multiple: multiple, options: options, "aria-label": `Filter ${title || label}`, value: valueForAutocomplete, clearOnBlur: true, limitTags: limitTags, inputValue: inputValue, onInputChange: handleInputChange, onChange: handleChange, getOptionLabel: getOptionLabel, renderInput: (params) => (React__default.createElement(TextField, { ...params, label: label, placeholder: getInputPlaceholder(), InputLabelProps: { shrink: true }, InputProps: {
|
|
7189
|
+
...params.InputProps,
|
|
7190
|
+
// TODO: Remove when upgrading to MUI v6.
|
|
7191
|
+
// With limitTags set, the root's onClick fires after the input's mousedown,causing the dropdown to open then
|
|
7192
|
+
// immediately close on first click. Overriding with undefined fixes it; the input's onMouseDown still opens the dropdown.
|
|
7193
|
+
// PR fix for this on MUI v6: https://github.com/mui/material-ui/pull/42494
|
|
7194
|
+
onClick: undefined,
|
|
7195
|
+
}, inputProps: {
|
|
7196
|
+
...params.inputProps,
|
|
7197
|
+
onFocus: (e) => {
|
|
7198
|
+
setIsFocused(true);
|
|
7199
|
+
params.inputProps?.onFocus?.(e);
|
|
7200
|
+
},
|
|
7201
|
+
onBlur: (e) => {
|
|
7202
|
+
setIsFocused(false);
|
|
7203
|
+
params.inputProps?.onBlur?.(e);
|
|
7204
|
+
},
|
|
7205
|
+
} })), renderTags: multiple ? renderTags : undefined, disableCloseOnSelect: multiple, filterOptions: filterOptions, groupBy: groupBy, sx: { my: 1, mt: label ? 2 : 1 } }),
|
|
7206
|
+
error && helperText && React__default.createElement(FormHelperText, { error: error }, helperText)));
|
|
7207
|
+
};
|
|
7208
|
+
/** ===============================================================
|
|
7209
|
+
* Internal Helper functions
|
|
7210
|
+
* These are not exported and are used only within this component.
|
|
7211
|
+
* ================================================================
|
|
7212
|
+
*/
|
|
7213
|
+
/**
|
|
7214
|
+
* Generate options from a mapped object.
|
|
7215
|
+
* @param mappedObject - The mapped object to generate options from.
|
|
7216
|
+
* @returns An array of FormAutocompleteOption.
|
|
7217
|
+
*/
|
|
7218
|
+
function generateOptions(mappedObject = {}) {
|
|
7219
|
+
return Object.values(mappedObject)
|
|
7220
|
+
.map(({ title, name }) => ({ label: title, id: name }))
|
|
7221
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
7222
|
+
}
|
|
7223
|
+
/**
|
|
7224
|
+
* Split an input string into an array of strings based on delimiters (comma or semicolon).
|
|
7225
|
+
* @param str - The string to split.
|
|
7226
|
+
* @returns An array of strings.
|
|
7227
|
+
*/
|
|
7228
|
+
function splitInput(str) {
|
|
7229
|
+
return str
|
|
7230
|
+
.split(SPLIT_REGEXP)
|
|
7231
|
+
.map((s) => s.trim())
|
|
7232
|
+
.filter(Boolean);
|
|
7233
|
+
}
|
|
7234
|
+
/**
|
|
7235
|
+
* Check if a string is a substring of another string.
|
|
7236
|
+
* @param str - The string to check.
|
|
7237
|
+
* @param sub - The substring to check.
|
|
7238
|
+
* @returns True if the string is a substring of the other string, false otherwise.
|
|
7239
|
+
*/
|
|
7240
|
+
function isSubstring(str, sub) {
|
|
7241
|
+
const normalized = str
|
|
7242
|
+
.normalize('NFD')
|
|
7243
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
7244
|
+
.toLowerCase();
|
|
7245
|
+
return normalized.includes(sub.toLowerCase());
|
|
7246
|
+
}
|
|
7247
|
+
/**
|
|
7248
|
+
* Convert an AutocompleteValue to a FormAutocompleteOption.
|
|
7249
|
+
* @param val - The AutocompleteValue to convert.
|
|
7250
|
+
* @returns The FormAutocompleteOption.
|
|
7251
|
+
*/
|
|
7252
|
+
function toOption(val) {
|
|
7253
|
+
if (typeof val === 'string') {
|
|
7254
|
+
return { label: val, id: val };
|
|
7255
|
+
}
|
|
7256
|
+
return val;
|
|
7257
|
+
}
|
|
7258
|
+
|
|
7259
|
+
const BaseFormSlider = ({ id, component = 'div', required, disabled, label, error, helperText, sx, inputRef, ...props }) => {
|
|
7260
|
+
return (React__default.createElement(FormControl, { component: component, error: error, disabled: disabled, sx: sx },
|
|
7261
|
+
React__default.createElement(FormLabel, { label: label, required: required, htmlFor: id }),
|
|
7262
|
+
React__default.createElement(Slider, { ref: inputRef, ...props }),
|
|
7263
|
+
error && helperText && React__default.createElement(FormHelperText, { error: error }, helperText)));
|
|
7264
|
+
};
|
|
7265
|
+
/**
|
|
7266
|
+
* Form wrapper for Slider. Use with useGsdSlider for GSD (Ground Sampling Distance) range sliders.
|
|
7267
|
+
*
|
|
7268
|
+
* Documentation: https://up-components.up42.com/?path=/docs/patterns-form-formslider--docs
|
|
7269
|
+
*/
|
|
7270
|
+
const FormSlider = React__default.forwardRef((props, ref) => (React__default.createElement(BaseFormSlider, { inputRef: ref, ...props })));
|
|
7271
|
+
|
|
7027
7272
|
const { spacing } = tokens.size;
|
|
7028
7273
|
/**
|
|
7029
7274
|
* Documentation: https://up-components.up42.com/?path=/docs/patterns-pageheader--docs
|
|
@@ -7339,13 +7584,6 @@ const Badge = ({ children, color = 'info', ...props }) => {
|
|
|
7339
7584
|
return (React__default.createElement(Badge$1, { color: color, ...props }, children));
|
|
7340
7585
|
};
|
|
7341
7586
|
|
|
7342
|
-
/**
|
|
7343
|
-
* Documentation: https://up-components.up42.com/?path=/docs/data-display-tag--docs
|
|
7344
|
-
*/
|
|
7345
|
-
const Tag = ({ color = 'primary', ...props }) => {
|
|
7346
|
-
return React__default.createElement(Chip, { size: "small", color: color, deleteIcon: React__default.createElement(Icon, { name: "Close", "data-testid": "CloseIcon" }), ...props });
|
|
7347
|
-
};
|
|
7348
|
-
|
|
7349
7587
|
dayjs.extend(utc);
|
|
7350
7588
|
dayjs.extend(relativeTime);
|
|
7351
7589
|
/**
|
|
@@ -11815,6 +12053,68 @@ const useDebounce = (value, delay = 500) => {
|
|
|
11815
12053
|
return debouncedValue;
|
|
11816
12054
|
};
|
|
11817
12055
|
|
|
12056
|
+
const DEFAULT_MARK_VALUES = [0, 2.5, 10, 30];
|
|
12057
|
+
/**
|
|
12058
|
+
* Hook for GSD (Ground Sampling Distance) range slider with logarithmic scale.
|
|
12059
|
+
* Handles conversion between linear slider values and logarithmic GSD values.
|
|
12060
|
+
*/
|
|
12061
|
+
function useGsdSlider({ minValue: minValueProp, maxValue: maxValueProp, markValues: markValuesProp = DEFAULT_MARK_VALUES, onChange, }) {
|
|
12062
|
+
const hasMarks = markValuesProp.length > 0;
|
|
12063
|
+
// If there are marks, use the prop mark values, otherwise use the default mark values
|
|
12064
|
+
const markValues = hasMarks ? markValuesProp : DEFAULT_MARK_VALUES;
|
|
12065
|
+
const minMarkValue = Math.min(...markValues);
|
|
12066
|
+
const maxMarkValue = Math.max(...markValues);
|
|
12067
|
+
// If there are marks, use the min and max mark values, otherwise use undefined
|
|
12068
|
+
const minGsd = hasMarks ? minMarkValue : undefined;
|
|
12069
|
+
const maxGsd = hasMarks ? maxMarkValue : undefined;
|
|
12070
|
+
// If the min or max value is not provided, use the min and max mark values
|
|
12071
|
+
const minSliderValue = minValueProp ?? minMarkValue;
|
|
12072
|
+
const maxSliderValue = maxValueProp ?? maxMarkValue;
|
|
12073
|
+
// Get the slider value from the min and max slider values
|
|
12074
|
+
const sliderValue = useMemo(() => [
|
|
12075
|
+
getSliderValueFromGsd(minSliderValue, minMarkValue, maxMarkValue),
|
|
12076
|
+
getSliderValueFromGsd(maxSliderValue, minMarkValue, maxMarkValue),
|
|
12077
|
+
], [minSliderValue, maxSliderValue, minMarkValue, maxMarkValue]);
|
|
12078
|
+
const marks = useMemo(() => {
|
|
12079
|
+
if (!hasMarks)
|
|
12080
|
+
return [];
|
|
12081
|
+
return markValuesProp.map((mark) => ({
|
|
12082
|
+
value: getSliderValueFromGsd(mark, minMarkValue, maxMarkValue),
|
|
12083
|
+
label: `${mark} m`,
|
|
12084
|
+
}));
|
|
12085
|
+
}, [hasMarks, markValuesProp, minMarkValue, maxMarkValue]);
|
|
12086
|
+
const handleSliderChange = (_event, newValue) => {
|
|
12087
|
+
if (newValue === undefined)
|
|
12088
|
+
return;
|
|
12089
|
+
const values = Array.isArray(newValue) ? newValue : [newValue, newValue];
|
|
12090
|
+
const [min, max] = values.map((v) => getGsdFromSliderValue(v, minMarkValue, maxMarkValue));
|
|
12091
|
+
onChange(min, max);
|
|
12092
|
+
};
|
|
12093
|
+
const scale = (n) => getGsdFromSliderValue(n, minMarkValue, maxMarkValue);
|
|
12094
|
+
return {
|
|
12095
|
+
sliderValue,
|
|
12096
|
+
marks,
|
|
12097
|
+
handleSliderChange,
|
|
12098
|
+
scale,
|
|
12099
|
+
minGsd,
|
|
12100
|
+
maxGsd,
|
|
12101
|
+
};
|
|
12102
|
+
}
|
|
12103
|
+
/**
|
|
12104
|
+
* Convert the value from the slider to a GSD value using a logarithm.
|
|
12105
|
+
*/
|
|
12106
|
+
function getGsdFromSliderValue(value, minGsd, maxGsd) {
|
|
12107
|
+
const scaledValue = Math.pow(2, (value * Math.log2(maxGsd + 1)) / (maxGsd - minGsd)) - 1;
|
|
12108
|
+
return Number(scaledValue.toFixed(2));
|
|
12109
|
+
}
|
|
12110
|
+
/**
|
|
12111
|
+
* Convert the GSD value to a value for the slider using a reverse logarithm.
|
|
12112
|
+
*/
|
|
12113
|
+
function getSliderValueFromGsd(scaledValue, minGsd, maxGsd) {
|
|
12114
|
+
const value = ((Math.log(scaledValue + 1) / Math.log(2)) * (maxGsd - minGsd)) / Math.log2(maxGsd + 1);
|
|
12115
|
+
return Number(value.toFixed(2));
|
|
12116
|
+
}
|
|
12117
|
+
|
|
11818
12118
|
/**
|
|
11819
12119
|
* There could be many ways to parse URLSearchParams to an object.
|
|
11820
12120
|
* For example, how should `a` be treated in `a=1&b=2&a=3`?
|
|
@@ -11921,4 +12221,234 @@ function useUrlParams(options = {}) {
|
|
|
11921
12221
|
};
|
|
11922
12222
|
}
|
|
11923
12223
|
|
|
11924
|
-
|
|
12224
|
+
/** Parse a comma-separated string into an array of strings. */
|
|
12225
|
+
function parseCommaSeparatedParam(value) {
|
|
12226
|
+
if (value === '') {
|
|
12227
|
+
return [];
|
|
12228
|
+
}
|
|
12229
|
+
const segments = value.split(',');
|
|
12230
|
+
const trimmedNonEmpty = segments.map((segment) => segment.trim()).filter(Boolean);
|
|
12231
|
+
return trimmedNonEmpty;
|
|
12232
|
+
}
|
|
12233
|
+
/** Get the default value for a Yup field. */
|
|
12234
|
+
function getDefaultValueForYupField(fieldDefinition) {
|
|
12235
|
+
const isYupSchema = isSchema(fieldDefinition);
|
|
12236
|
+
if (!isYupSchema) {
|
|
12237
|
+
return undefined;
|
|
12238
|
+
}
|
|
12239
|
+
const yupField = fieldDefinition;
|
|
12240
|
+
return yupField.getDefault();
|
|
12241
|
+
}
|
|
12242
|
+
/** Get the default values for a schema (`.default()` on each field). */
|
|
12243
|
+
function getDefaultValues(schema) {
|
|
12244
|
+
const objectSchema = schema;
|
|
12245
|
+
const { fields } = objectSchema;
|
|
12246
|
+
const defaultEntries = Object.keys(fields).map((fieldKey) => {
|
|
12247
|
+
const defaultForField = getDefaultValueForYupField(fields[fieldKey]);
|
|
12248
|
+
return [fieldKey, defaultForField];
|
|
12249
|
+
});
|
|
12250
|
+
const defaults = Object.fromEntries(defaultEntries);
|
|
12251
|
+
const castOptions = { stripUnknown: true };
|
|
12252
|
+
return schema.cast(defaults, castOptions);
|
|
12253
|
+
}
|
|
12254
|
+
/** Check if a field is an array and the value is a string. */
|
|
12255
|
+
function isArrayFieldWithStringFromUrl(fieldDefinition, paramValue) {
|
|
12256
|
+
const isYupSchema = isSchema(fieldDefinition);
|
|
12257
|
+
if (!isYupSchema || fieldDefinition.type !== 'array') {
|
|
12258
|
+
return false;
|
|
12259
|
+
}
|
|
12260
|
+
const valueIsString = typeof paramValue === 'string';
|
|
12261
|
+
return valueIsString;
|
|
12262
|
+
}
|
|
12263
|
+
/** Get the URL parameter name for a field. */
|
|
12264
|
+
function urlParamNameForField(fieldKey, urlKeyMap) {
|
|
12265
|
+
const mappedName = urlKeyMap?.[fieldKey];
|
|
12266
|
+
if (mappedName === undefined) {
|
|
12267
|
+
return fieldKey;
|
|
12268
|
+
}
|
|
12269
|
+
return mappedName;
|
|
12270
|
+
}
|
|
12271
|
+
/** Build a raw record of URL parameters from a schema and URL parameters. */
|
|
12272
|
+
function buildRawParamsFromUrl(schema, urlParams, urlKeyMap) {
|
|
12273
|
+
const objectSchema = schema;
|
|
12274
|
+
const { fields } = objectSchema;
|
|
12275
|
+
const presentFieldEntries = Object.keys(fields)
|
|
12276
|
+
.map((fieldKey) => {
|
|
12277
|
+
const searchParamKey = urlParamNameForField(fieldKey, urlKeyMap);
|
|
12278
|
+
const paramValue = urlParams[searchParamKey];
|
|
12279
|
+
if (paramValue === undefined) {
|
|
12280
|
+
return null;
|
|
12281
|
+
}
|
|
12282
|
+
const fieldDefinition = fields[fieldKey];
|
|
12283
|
+
const shouldSplitCommaSeparatedString = isArrayFieldWithStringFromUrl(fieldDefinition, paramValue);
|
|
12284
|
+
if (shouldSplitCommaSeparatedString) {
|
|
12285
|
+
const stringFromQuery = paramValue;
|
|
12286
|
+
const parsedList = parseCommaSeparatedParam(stringFromQuery);
|
|
12287
|
+
return [fieldKey, parsedList];
|
|
12288
|
+
}
|
|
12289
|
+
return [fieldKey, paramValue];
|
|
12290
|
+
})
|
|
12291
|
+
.filter((entry) => entry !== null);
|
|
12292
|
+
return Object.fromEntries(presentFieldEntries);
|
|
12293
|
+
}
|
|
12294
|
+
/** Validate a raw record of URL parameters against a schema. */
|
|
12295
|
+
function validateRawAgainstSchema(schema, raw) {
|
|
12296
|
+
const castOptions = { stripUnknown: true };
|
|
12297
|
+
try {
|
|
12298
|
+
const castedSchema = schema.cast(raw, castOptions);
|
|
12299
|
+
return schema.validateSync(castedSchema, castOptions);
|
|
12300
|
+
}
|
|
12301
|
+
catch {
|
|
12302
|
+
return null;
|
|
12303
|
+
}
|
|
12304
|
+
}
|
|
12305
|
+
/** Parse URL params into a validated filter object using the schema and optional URL key map. */
|
|
12306
|
+
function parseFromUrl(schema, urlParams, urlKeyMap) {
|
|
12307
|
+
const raw = buildRawParamsFromUrl(schema, urlParams, urlKeyMap);
|
|
12308
|
+
const validated = validateRawAgainstSchema(schema, raw);
|
|
12309
|
+
// If the validation failed, return the default values
|
|
12310
|
+
if (validated === null) {
|
|
12311
|
+
return getDefaultValues(schema);
|
|
12312
|
+
}
|
|
12313
|
+
return validated;
|
|
12314
|
+
}
|
|
12315
|
+
/** Check if two arrays of filter values are equal. */
|
|
12316
|
+
function arrayFilterValuesEqual(left, right) {
|
|
12317
|
+
const { length: leftLength } = left;
|
|
12318
|
+
const { length: rightLength } = right;
|
|
12319
|
+
if (leftLength !== rightLength) {
|
|
12320
|
+
return false;
|
|
12321
|
+
}
|
|
12322
|
+
return left.every((leftItem, index) => {
|
|
12323
|
+
const rightItem = right[index];
|
|
12324
|
+
return sameFilterValue(leftItem, rightItem);
|
|
12325
|
+
});
|
|
12326
|
+
}
|
|
12327
|
+
/** Check if two plain record of filter values are equal. */
|
|
12328
|
+
function plainRecordFilterValuesEqual(leftRecord, rightRecord) {
|
|
12329
|
+
const keySet = new Set([...Object.keys(leftRecord), ...Object.keys(rightRecord)]);
|
|
12330
|
+
const allKeys = [...keySet];
|
|
12331
|
+
return allKeys.every((key) => {
|
|
12332
|
+
const leftValue = leftRecord[key];
|
|
12333
|
+
const rightValue = rightRecord[key];
|
|
12334
|
+
return sameFilterValue(leftValue, rightValue);
|
|
12335
|
+
});
|
|
12336
|
+
}
|
|
12337
|
+
/** Deep value equality for filter field values (primitives, arrays, plain objects, Date). */
|
|
12338
|
+
function sameFilterValue(left, right) {
|
|
12339
|
+
// Check if the values are the same object
|
|
12340
|
+
if (Object.is(left, right)) {
|
|
12341
|
+
return true;
|
|
12342
|
+
}
|
|
12343
|
+
// Check if the values are null or undefined
|
|
12344
|
+
const leftIsNullish = left == null;
|
|
12345
|
+
const rightIsNullish = right == null;
|
|
12346
|
+
if (leftIsNullish || rightIsNullish) {
|
|
12347
|
+
const bothNullish = leftIsNullish && rightIsNullish;
|
|
12348
|
+
return bothNullish;
|
|
12349
|
+
}
|
|
12350
|
+
// Check if the values are arrays
|
|
12351
|
+
const leftIsArray = Array.isArray(left);
|
|
12352
|
+
const rightIsArray = Array.isArray(right);
|
|
12353
|
+
const bothAreArrays = leftIsArray && rightIsArray;
|
|
12354
|
+
if (bothAreArrays) {
|
|
12355
|
+
return arrayFilterValuesEqual(left, right);
|
|
12356
|
+
}
|
|
12357
|
+
// Check if the values are dates
|
|
12358
|
+
const leftIsDate = left instanceof Date;
|
|
12359
|
+
const rightIsDate = right instanceof Date;
|
|
12360
|
+
const bothAreDates = leftIsDate && rightIsDate;
|
|
12361
|
+
if (bothAreDates) {
|
|
12362
|
+
const leftTime = left.getTime();
|
|
12363
|
+
const rightTime = right.getTime();
|
|
12364
|
+
return leftTime === rightTime;
|
|
12365
|
+
}
|
|
12366
|
+
// Check if the values are objects
|
|
12367
|
+
const leftIsObject = typeof left === 'object';
|
|
12368
|
+
const rightIsObject = typeof right === 'object';
|
|
12369
|
+
const bothAreObjects = leftIsObject && rightIsObject;
|
|
12370
|
+
if (!bothAreObjects) {
|
|
12371
|
+
return false;
|
|
12372
|
+
}
|
|
12373
|
+
// Check if the values are plain records
|
|
12374
|
+
const leftRecord = left;
|
|
12375
|
+
const rightRecord = right;
|
|
12376
|
+
return plainRecordFilterValuesEqual(leftRecord, rightRecord);
|
|
12377
|
+
}
|
|
12378
|
+
/**
|
|
12379
|
+
* True when `draft` and `applied` match on every key in `schema`.
|
|
12380
|
+
* Unrelated URL search params do not appear on these snapshots, so this stays accurate when other code adds params.
|
|
12381
|
+
*/
|
|
12382
|
+
function areFilterSnapshotsEqual(schema, draft, applied) {
|
|
12383
|
+
const objectSchema = schema;
|
|
12384
|
+
const { fields } = objectSchema;
|
|
12385
|
+
const schemaFieldKeys = Object.keys(fields);
|
|
12386
|
+
return schemaFieldKeys.every((fieldKey) => {
|
|
12387
|
+
const filterKey = fieldKey;
|
|
12388
|
+
const draftValue = draft[filterKey];
|
|
12389
|
+
const appliedValue = applied[filterKey];
|
|
12390
|
+
return sameFilterValue(draftValue, appliedValue);
|
|
12391
|
+
});
|
|
12392
|
+
}
|
|
12393
|
+
|
|
12394
|
+
/**
|
|
12395
|
+
* Draft vs applied filter state: the UI edits `filters` (draft); `apply` writes to the URL via {@link useUrlParams}.
|
|
12396
|
+
* Applied filters are always derived from the current search string and validated with Yup.
|
|
12397
|
+
*
|
|
12398
|
+
* When the query string changes, the draft syncs from the URL only if **schema-defined** filter values changed.
|
|
12399
|
+
* Extra params from other features do not reset the draft or affect **`isDirty`** by themselves.
|
|
12400
|
+
*/
|
|
12401
|
+
function useFilterState(schema, options = {}) {
|
|
12402
|
+
const { urlKeyMap } = options;
|
|
12403
|
+
const { setParams, stringParams } = useUrlParams();
|
|
12404
|
+
const defaults = useMemo(() => getDefaultValues(schema), [schema]);
|
|
12405
|
+
const appliedFilters = useMemo(() => {
|
|
12406
|
+
const raw = searchParamsToObject(new URLSearchParams(stringParams));
|
|
12407
|
+
return parseFromUrl(schema, raw, urlKeyMap);
|
|
12408
|
+
}, [schema, stringParams, urlKeyMap]);
|
|
12409
|
+
const [draft, setDraft] = useState(() => parseFromUrl(schema, searchParamsToObject(new URLSearchParams(stringParams)), urlKeyMap));
|
|
12410
|
+
const draftRef = useRef(draft);
|
|
12411
|
+
draftRef.current = draft;
|
|
12412
|
+
const prevAppliedRef = useRef(null);
|
|
12413
|
+
useEffect(() => {
|
|
12414
|
+
const prevApplied = prevAppliedRef.current;
|
|
12415
|
+
if (prevApplied !== null && areFilterSnapshotsEqual(schema, prevApplied, appliedFilters)) {
|
|
12416
|
+
prevAppliedRef.current = appliedFilters;
|
|
12417
|
+
return;
|
|
12418
|
+
}
|
|
12419
|
+
prevAppliedRef.current = appliedFilters;
|
|
12420
|
+
draftRef.current = appliedFilters;
|
|
12421
|
+
setDraft(appliedFilters);
|
|
12422
|
+
}, [appliedFilters, schema]);
|
|
12423
|
+
const setFilter = useCallback((key, value) => {
|
|
12424
|
+
const next = { ...draftRef.current, [key]: value };
|
|
12425
|
+
draftRef.current = next;
|
|
12426
|
+
setDraft(next);
|
|
12427
|
+
}, []);
|
|
12428
|
+
const apply = useCallback(() => {
|
|
12429
|
+
const rawParams = searchParamsToObject(new URLSearchParams(stringParams));
|
|
12430
|
+
const nextParams = { ...rawParams };
|
|
12431
|
+
Object.keys(schema.fields).map((fieldKey) => {
|
|
12432
|
+
const mappedUrlKey = urlKeyMap?.[fieldKey];
|
|
12433
|
+
const urlKey = mappedUrlKey ?? fieldKey;
|
|
12434
|
+
const fieldValue = draftRef.current[fieldKey];
|
|
12435
|
+
nextParams[urlKey] = fieldValue;
|
|
12436
|
+
});
|
|
12437
|
+
setParams(nextParams);
|
|
12438
|
+
}, [schema, setParams, stringParams, urlKeyMap]);
|
|
12439
|
+
const reset = useCallback(() => {
|
|
12440
|
+
draftRef.current = defaults;
|
|
12441
|
+
setDraft(defaults);
|
|
12442
|
+
}, [defaults]);
|
|
12443
|
+
const isDirty = useMemo(() => !areFilterSnapshotsEqual(schema, draft, appliedFilters), [schema, draft, appliedFilters]);
|
|
12444
|
+
return {
|
|
12445
|
+
filters: draft,
|
|
12446
|
+
setFilter,
|
|
12447
|
+
apply,
|
|
12448
|
+
reset,
|
|
12449
|
+
isDirty,
|
|
12450
|
+
defaults,
|
|
12451
|
+
};
|
|
12452
|
+
}
|
|
12453
|
+
|
|
12454
|
+
export { ActionToolbar, Alert, Avatar, Badge, Banner, Button, Checkbox, CodeBlock, CodeInline, CodeSnippet, ContactBox, ControlButton, CopyButton, DataGrid, DateTime, Divider, DocumentationPopover, EditTagsButton, EmptyState, FeatureCard, FeatureCardHeader, FeatureCardHeaderActions, FeatureFlagCheckbox, FilterPopover, FindUsersButton, FormAutocomplete, FormCheckbox, FormDatePicker, FormDateRangePicker, FormDateRangePickerList, FormDateTimePicker, FormInput, FormRadio, FormSelect, FormSlider, FormSwitch, GridContainer, GridItem, Icon, Illustration, InfoCard, InfoModal, InfoPopover, Input, LearnMoreButton, Link, Loading, Logo, NotFound, PageContainer, PageHeader, PageHeaderV2, Popover, Radio, RoleBanner, Select, Slider, StatusLight, Switch, Tab, TabGroup, Table, TableBody, TableCell, TableContainer, TableFooter, TableHead, TablePagination, TableRow, TableSortLabel, Tabs, Tag, TagsList, ToggleButton, Typography, UpComponentsProvider, analytics, areFilterSnapshotsEqual, capitalize, copyToClipboard, formatDate, formatFileSize, formatNumber, generateQueryKey, objectToSearchParams, searchParamsToObject, theme, tokens, useAlert, useCursorPagination, useDebounce, useFilterState, useGsdSlider, useQueryParams, useRemotePagination, useToggle, useUrlParams, valueToString };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { default as tokens } from '@up42/design-system-tokens/dist/json/tokens.json';
|
|
2
2
|
import * as _mui_material from '@mui/material';
|
|
3
|
-
import { BoxProps, TextFieldProps, AvatarProps as AvatarProps$1, GridProps, ContainerProps, CheckboxProps as CheckboxProps$1, RadioProps as RadioProps$1, RadioGroupProps, SwitchProps as SwitchProps$1, SelectProps as SelectProps$1, SliderProps as SliderProps$
|
|
3
|
+
import { BoxProps, TextFieldProps, AvatarProps as AvatarProps$1, GridProps, ContainerProps, CheckboxProps as CheckboxProps$1, RadioProps as RadioProps$1, RadioGroupProps, SwitchProps as SwitchProps$1, SelectProps as SelectProps$1, SliderProps as SliderProps$2, LinkProps as LinkProps$1, TabProps as TabProps$1, TabsProps as TabsProps$1, CardProps, ModalProps, AlertProps as AlertProps$1, SxProps, Theme, IconButtonProps, SvgIconProps, PopoverProps as PopoverProps$1, BadgeProps as BadgeProps$1, ChipProps, DividerProps as DividerProps$1, ButtonProps as ButtonProps$2, SnackbarProps } from '@mui/material';
|
|
4
4
|
export * from '@mui/material';
|
|
5
5
|
import { ThemeProviderProps } from '@mui/material/styles/ThemeProvider';
|
|
6
6
|
import * as React from 'react';
|
|
@@ -23,6 +23,7 @@ import { TableFooterProps as TableFooterProps$1 } from '@mui/material/TableFoote
|
|
|
23
23
|
import { DataGridPremiumProps } from '@mui/x-data-grid-premium';
|
|
24
24
|
export { GRID_DETAIL_PANEL_TOGGLE_COL_DEF, GRID_DETAIL_PANEL_TOGGLE_FIELD, GridCell, GridCellModes, GridCellModesModel, GridCellParams, GridColDef, GridColumnHeaderParams, GridEditCellProps, GridEditInputCell, GridFooter, GridInitialState, GridLoadingOverlay, GridNoRowsOverlay, GridPagination, GridPreProcessEditCellProps, GridRenderCellParams, GridRenderEditCellParams, GridRow, GridRowCount, GridRowId, GridRowParams, GridRowSelectionModel, GridRowsProp, GridSelectedRowCount, GridSortModel, GridTreeNodeWithRender, gridColumnDefinitionsSelector, gridColumnVisibilityModelSelector, gridDetailPanelExpandedRowIdsSelector, gridDetailPanelExpandedRowsContentCacheSelector, gridRowsLookupSelector, useGridApiContext, useGridApiRef, useGridSelector } from '@mui/x-data-grid-premium';
|
|
25
25
|
import { ToggleButtonProps as ToggleButtonProps$1 } from '@mui/material/ToggleButton';
|
|
26
|
+
import { AnyObjectSchema, InferType } from 'yup';
|
|
26
27
|
|
|
27
28
|
interface UpComponentsProviderProps extends Omit<ThemeProviderProps, 'theme'> {
|
|
28
29
|
licenseKey: string;
|
|
@@ -282,11 +283,11 @@ type FormInputProps = FormBaseProps & InputProps;
|
|
|
282
283
|
*/
|
|
283
284
|
declare const FormInput: React__default.ForwardRefExoticComponent<Omit<FormInputProps, "ref"> & React__default.RefAttributes<HTMLInputElement>>;
|
|
284
285
|
|
|
285
|
-
type SliderProps = MUIGlobalOmit<Omit<SliderProps$
|
|
286
|
+
type SliderProps$1 = MUIGlobalOmit<Omit<SliderProps$2, 'variant' | 'label'>>;
|
|
286
287
|
/**
|
|
287
288
|
* Documentation: https://up-components.up42.com/?path=/docs/data-entry-slider--default--docs
|
|
288
289
|
*/
|
|
289
|
-
declare const Slider: (props: SliderProps) => React__default.JSX.Element;
|
|
290
|
+
declare const Slider: (props: SliderProps$1) => React__default.JSX.Element;
|
|
290
291
|
|
|
291
292
|
type LinkProps<C extends React__default.ElementType> = LinkProps$1<C, {
|
|
292
293
|
component?: C;
|
|
@@ -4696,7 +4697,8 @@ type PopoverProps = Omit<PopoverProps$1, 'content' | 'open' | 'onClose'> & {
|
|
|
4696
4697
|
*/
|
|
4697
4698
|
primaryButton?: PopoverButtonProps;
|
|
4698
4699
|
/**
|
|
4699
|
-
* Secondary button props (optional)
|
|
4700
|
+
* Secondary button props (optional).
|
|
4701
|
+
* If `onClick` is omitted, clicking the button only closes the popover. If `onClick` is set, it runs and the popover stays open unless you call `onClose`.
|
|
4700
4702
|
*/
|
|
4701
4703
|
secondaryButton?: PopoverButtonProps;
|
|
4702
4704
|
/**
|
|
@@ -4742,9 +4744,10 @@ declare const Popover: React__default.ForwardRefExoticComponent<Omit<PopoverProp
|
|
|
4742
4744
|
|
|
4743
4745
|
type FilterPopoverProps = Pick<PopoverProps, 'anchorEl' | 'onClose' | 'header' | 'sx' | 'size'> & {
|
|
4744
4746
|
/**
|
|
4745
|
-
*
|
|
4747
|
+
* Array of filter input components to display. Each item is rendered in a vertical stack with spacing.
|
|
4748
|
+
* Use FormInput, FormDateRangePicker, FormSlider, FormAutocomplete, etc.
|
|
4746
4749
|
*/
|
|
4747
|
-
content?: React__default.ReactNode;
|
|
4750
|
+
content?: React__default.ReactNode[];
|
|
4748
4751
|
/**
|
|
4749
4752
|
* Apply button text.
|
|
4750
4753
|
* @default 'Apply filters'
|
|
@@ -4765,6 +4768,11 @@ type FilterPopoverProps = Pick<PopoverProps, 'anchorEl' | 'onClose' | 'header' |
|
|
|
4765
4768
|
* @default false
|
|
4766
4769
|
*/
|
|
4767
4770
|
disableApplyButton?: boolean;
|
|
4771
|
+
/**
|
|
4772
|
+
* Maximum width of the content.
|
|
4773
|
+
* @default '384px'
|
|
4774
|
+
*/
|
|
4775
|
+
maxContentWidth?: string;
|
|
4768
4776
|
/**
|
|
4769
4777
|
* Called when Apply is clicked; popover closes after.
|
|
4770
4778
|
*/
|
|
@@ -4780,7 +4788,115 @@ type FilterPopoverProps = Pick<PopoverProps, 'anchorEl' | 'onClose' | 'header' |
|
|
|
4780
4788
|
*
|
|
4781
4789
|
* Documentation: https://up-components.up42.com/?path=/docs-patterns-popovers-filterpopover--docs
|
|
4782
4790
|
*/
|
|
4783
|
-
declare const FilterPopover: ({ anchorEl, onClose, header, content, applyButtonLabel, clearButtonLabel, disableApplyButton, isLoadingCount, onApply, onClear, ...popoverProps }: FilterPopoverProps) => React__default.JSX.Element;
|
|
4791
|
+
declare const FilterPopover: ({ anchorEl, onClose, header, content, applyButtonLabel, clearButtonLabel, disableApplyButton, isLoadingCount, maxContentWidth, onApply, onClear, ...popoverProps }: FilterPopoverProps) => React__default.JSX.Element;
|
|
4792
|
+
|
|
4793
|
+
type FormAutocompleteMapper = Record<string, {
|
|
4794
|
+
title: string;
|
|
4795
|
+
name: string;
|
|
4796
|
+
}>;
|
|
4797
|
+
type FormAutocompleteOption = {
|
|
4798
|
+
label: string;
|
|
4799
|
+
id: string;
|
|
4800
|
+
};
|
|
4801
|
+
type FormAutocompleteProps = {
|
|
4802
|
+
/**
|
|
4803
|
+
* ID of the input.
|
|
4804
|
+
*/
|
|
4805
|
+
id?: string;
|
|
4806
|
+
/**
|
|
4807
|
+
* Component to wrap the input.
|
|
4808
|
+
*/
|
|
4809
|
+
component?: React__default.ElementType;
|
|
4810
|
+
/**
|
|
4811
|
+
* Title of the input.
|
|
4812
|
+
*/
|
|
4813
|
+
title?: string;
|
|
4814
|
+
/**
|
|
4815
|
+
* Label of the input.
|
|
4816
|
+
*/
|
|
4817
|
+
label?: string;
|
|
4818
|
+
/**
|
|
4819
|
+
* Selected values.
|
|
4820
|
+
*/
|
|
4821
|
+
values?: string[];
|
|
4822
|
+
/**
|
|
4823
|
+
* Map of values to options.
|
|
4824
|
+
*/
|
|
4825
|
+
valuesMap?: FormAutocompleteMapper;
|
|
4826
|
+
/**
|
|
4827
|
+
* Callback when selection changes.
|
|
4828
|
+
*/
|
|
4829
|
+
onChange: (values: string[]) => void;
|
|
4830
|
+
/**
|
|
4831
|
+
* Placeholder text when no selection.
|
|
4832
|
+
*/
|
|
4833
|
+
placeholder?: string;
|
|
4834
|
+
/**
|
|
4835
|
+
* When true, allows multiple selections. Selected values are displayed as chips inside the input.
|
|
4836
|
+
* @default false
|
|
4837
|
+
*/
|
|
4838
|
+
multiple?: boolean;
|
|
4839
|
+
/**
|
|
4840
|
+
* Maximum number of tags to display. -1 means no limit.
|
|
4841
|
+
* @default -1
|
|
4842
|
+
*/
|
|
4843
|
+
limitTags?: number;
|
|
4844
|
+
/**
|
|
4845
|
+
* When true, allows adding custom text as values. Shows "Add X, Y, Z" in the dropdown when typing.
|
|
4846
|
+
* Supports multiple values separated by comma or semicolon.
|
|
4847
|
+
* @default false
|
|
4848
|
+
*/
|
|
4849
|
+
addCustom?: boolean;
|
|
4850
|
+
/**
|
|
4851
|
+
* Text to display when all options are selected (multiple=true, addCustom=false).
|
|
4852
|
+
* Shown when input is not focused; selected chips are shown when focused.
|
|
4853
|
+
*/
|
|
4854
|
+
allOptionsSelectedPlaceholder?: string;
|
|
4855
|
+
/**
|
|
4856
|
+
* Whether the input is required.
|
|
4857
|
+
*/
|
|
4858
|
+
required?: boolean;
|
|
4859
|
+
/**
|
|
4860
|
+
* Whether the input is disabled.
|
|
4861
|
+
*/
|
|
4862
|
+
disabled?: boolean;
|
|
4863
|
+
/**
|
|
4864
|
+
* Error message to display.
|
|
4865
|
+
*/
|
|
4866
|
+
error?: boolean;
|
|
4867
|
+
/**
|
|
4868
|
+
* Helper text to display.
|
|
4869
|
+
*/
|
|
4870
|
+
helperText?: string;
|
|
4871
|
+
/**
|
|
4872
|
+
* Styles to apply to the input.
|
|
4873
|
+
*/
|
|
4874
|
+
sx?: SxProps<Theme>;
|
|
4875
|
+
/**
|
|
4876
|
+
* Function that returns the group key for each option. Groups options in the dropdown.
|
|
4877
|
+
*/
|
|
4878
|
+
groupBy?: (option: FormAutocompleteOption) => string;
|
|
4879
|
+
};
|
|
4880
|
+
/**
|
|
4881
|
+
* Autocomplete-based single or multi-select form input. Selected values are displayed
|
|
4882
|
+
* as chips inside the input field.
|
|
4883
|
+
* Supports adding custom values by typing and pressing Enter (comma/semicolon delimiters).
|
|
4884
|
+
*
|
|
4885
|
+
* Documentation: https://up-components.up42.com/?path=/docs/patterns-form-formautocomplete--docs
|
|
4886
|
+
*/
|
|
4887
|
+
declare const FormAutocomplete: ({ id, component, title, label, valuesMap, values, onChange, placeholder, multiple, limitTags, addCustom, allOptionsSelectedPlaceholder, required, disabled, error, helperText, sx, groupBy, }: FormAutocompleteProps) => React__default.JSX.Element;
|
|
4888
|
+
|
|
4889
|
+
type SliderProps = MUIGlobalOmit<Omit<SliderProps$2, 'variant' | 'label'>>
|
|
4890
|
+
|
|
4891
|
+
type FormSliderProps = Omit<FormBaseProps, 'onChange'> & SliderProps & {
|
|
4892
|
+
inputRef?: SliderProps['ref'];
|
|
4893
|
+
};
|
|
4894
|
+
/**
|
|
4895
|
+
* Form wrapper for Slider. Use with useGsdSlider for GSD (Ground Sampling Distance) range sliders.
|
|
4896
|
+
*
|
|
4897
|
+
* Documentation: https://up-components.up42.com/?path=/docs/patterns-form-formslider--docs
|
|
4898
|
+
*/
|
|
4899
|
+
declare const FormSlider: React__default.ForwardRefExoticComponent<Omit<FormSliderProps, "ref"> & React__default.RefAttributes<HTMLSpanElement>>;
|
|
4784
4900
|
|
|
4785
4901
|
type PageHeaderProps = {
|
|
4786
4902
|
title: string;
|
|
@@ -11709,6 +11825,44 @@ declare function useToggle(initialState: boolean | (() => boolean)): UseToggleRe
|
|
|
11709
11825
|
*/
|
|
11710
11826
|
declare const useDebounce: <T>(value: T, delay?: number) => T;
|
|
11711
11827
|
|
|
11828
|
+
type UseGsdSliderProps = {
|
|
11829
|
+
/**
|
|
11830
|
+
* Minimum GSD value (meters). Controlled by parent.
|
|
11831
|
+
* When omitted, the range thumb uses the axis minimum (lowest mark used for the logarithm).
|
|
11832
|
+
*/
|
|
11833
|
+
minValue?: number;
|
|
11834
|
+
/**
|
|
11835
|
+
* Maximum GSD value (meters). Controlled by parent.
|
|
11836
|
+
* When omitted, the range thumb uses the axis maximum (highest mark used for the logarithm).
|
|
11837
|
+
*/
|
|
11838
|
+
maxValue?: number;
|
|
11839
|
+
/**
|
|
11840
|
+
* Callback when the range changes.
|
|
11841
|
+
*/
|
|
11842
|
+
onChange: (minValue: number, maxValue: number) => void;
|
|
11843
|
+
/**
|
|
11844
|
+
* GSD values for the slider marks.
|
|
11845
|
+
* Pass an empty array if you want no marks and `minGsd` / `maxGsd` returned as `undefined` (axis still uses the default scale internally).
|
|
11846
|
+
* @default [0, 2.5, 10, 30]
|
|
11847
|
+
*/
|
|
11848
|
+
markValues?: number[];
|
|
11849
|
+
};
|
|
11850
|
+
/**
|
|
11851
|
+
* Hook for GSD (Ground Sampling Distance) range slider with logarithmic scale.
|
|
11852
|
+
* Handles conversion between linear slider values and logarithmic GSD values.
|
|
11853
|
+
*/
|
|
11854
|
+
declare function useGsdSlider({ minValue: minValueProp, maxValue: maxValueProp, markValues: markValuesProp, onChange, }: UseGsdSliderProps): {
|
|
11855
|
+
sliderValue: [number, number];
|
|
11856
|
+
marks: {
|
|
11857
|
+
value: number;
|
|
11858
|
+
label: string;
|
|
11859
|
+
}[];
|
|
11860
|
+
handleSliderChange: (_event: unknown, newValue?: number | number[]) => void;
|
|
11861
|
+
scale: (n: number) => number;
|
|
11862
|
+
minGsd: number | undefined;
|
|
11863
|
+
maxGsd: number | undefined;
|
|
11864
|
+
};
|
|
11865
|
+
|
|
11712
11866
|
type CreateAlertProps = {
|
|
11713
11867
|
title?: string;
|
|
11714
11868
|
message: string | ElementType;
|
|
@@ -11814,5 +11968,32 @@ declare function valueToString(value: unknown): string;
|
|
|
11814
11968
|
*/
|
|
11815
11969
|
declare function objectToSearchParams<T extends ParamsObject = ParamsObject>(object: T): string;
|
|
11816
11970
|
|
|
11817
|
-
|
|
11818
|
-
|
|
11971
|
+
/** Maps internal filter keys to URL search-param names. */
|
|
11972
|
+
type UrlKeyMap<FilterSchema extends AnyObjectSchema> = Partial<Record<keyof InferType<FilterSchema>, string>>;
|
|
11973
|
+
/**
|
|
11974
|
+
* True when `draft` and `applied` match on every key in `schema`.
|
|
11975
|
+
* Unrelated URL search params do not appear on these snapshots, so this stays accurate when other code adds params.
|
|
11976
|
+
*/
|
|
11977
|
+
declare function areFilterSnapshotsEqual<FilterSchema extends AnyObjectSchema>(schema: FilterSchema, draft: InferType<FilterSchema>, applied: InferType<FilterSchema>): boolean;
|
|
11978
|
+
|
|
11979
|
+
type UseFilterStateOptions<FilterSchema extends AnyObjectSchema> = {
|
|
11980
|
+
urlKeyMap?: UrlKeyMap<FilterSchema>;
|
|
11981
|
+
};
|
|
11982
|
+
/**
|
|
11983
|
+
* Draft vs applied filter state: the UI edits `filters` (draft); `apply` writes to the URL via {@link useUrlParams}.
|
|
11984
|
+
* Applied filters are always derived from the current search string and validated with Yup.
|
|
11985
|
+
*
|
|
11986
|
+
* When the query string changes, the draft syncs from the URL only if **schema-defined** filter values changed.
|
|
11987
|
+
* Extra params from other features do not reset the draft or affect **`isDirty`** by themselves.
|
|
11988
|
+
*/
|
|
11989
|
+
declare function useFilterState<FilterSchema extends AnyObjectSchema>(schema: FilterSchema, options?: UseFilterStateOptions<FilterSchema>): {
|
|
11990
|
+
readonly filters: InferType<FilterSchema>;
|
|
11991
|
+
readonly setFilter: <K extends keyof InferType<FilterSchema>>(key: K, value: InferType<FilterSchema>[K]) => void;
|
|
11992
|
+
readonly apply: () => void;
|
|
11993
|
+
readonly reset: () => void;
|
|
11994
|
+
readonly isDirty: boolean;
|
|
11995
|
+
readonly defaults: InferType<FilterSchema>;
|
|
11996
|
+
};
|
|
11997
|
+
|
|
11998
|
+
export { ActionToolbar, Alert, Avatar, Badge, Banner, Button, Checkbox, CodeBlock, CodeInline, CodeSnippet, ContactBox, ControlButton, CopyButton, DataGrid, DateTime, Divider, DocumentationPopover, EditTagsButton, EmptyState, FeatureCard, FeatureCardHeader, FeatureCardHeaderActions, FeatureFlagCheckbox, FilterPopover, FindUsersButton, FormAutocomplete, FormCheckbox, FormDatePicker, FormDateRangePicker, FormDateRangePickerList, FormDateTimePicker, FormInput, FormRadio, FormSelect, FormSlider, FormSwitch, GridContainer, GridItem, Icon, Illustration, InfoCard, InfoModal, InfoPopover, Input, LearnMoreButton, Link, Loading, Logo, NotFound, PageContainer, PageHeader, PageHeaderV2, Popover, Radio, RoleBanner, Select, Slider, StatusLight, Switch, Tab, TabGroup, Table, TableBody, TableCell, TableContainer, TableFooter, TableHead, TablePagination, TableRow, TableSortLabel, Tabs, Tag, TagsList, ToggleButton, Typography, UpComponentsProvider, analytics, areFilterSnapshotsEqual, capitalize, copyToClipboard, formatDate, formatFileSize, formatNumber, generateQueryKey, objectToSearchParams, searchParamsToObject, theme, useAlert, useCursorPagination, useDebounce, useFilterState, useGsdSlider, useQueryParams, useRemotePagination, useToggle, useUrlParams, valueToString };
|
|
11999
|
+
export type { ActionToolbarProps, AlertProps, AnalyticsConfig, AvatarProps, BadgeProps, BannerProps, ButtonProps, CheckboxProps, CodeBlockProps, CodeInlineProps, CodeSnippetItemProps, CodeSnippetProps, ContactBoxProps, ControlButtonProps, CopyButtonProps, CreateAlertProps, CreateSnackbarProps, CursorPaginatedResponse, DateRange, DateTimeProps, DividerProps, DocumentationPopoverProps, EditTagsButtonProps, EmptyStateProps, FeatureCardHeaderActionsProps, FeatureCardHeaderProps, FeatureCardProps, FeatureFlagCheckboxProps, FilterPopoverProps, FindUsersButtonProps, FormAutocompleteMapper, FormAutocompleteProps, FormCheckboxProps, FormDatePickerDateType, FormDatePickerProps, FormDateRangePickerListProps, FormDateRangePickerProps, FormDateTimePickerProps, FormInputProps, FormRadioProps, FormSelectProps, FormSliderProps, FormSwitchProps, GridContainerProps, GridItemProps, IconAction, IconProps$1 as IconProps, IllustrationProps, InfoCardProps, InfoModalProps, InfoPopoverProps, InputProps, LearnMoreButtonProps, LinkProps, LoadingProps, LogoProps, MenuAction, NotFoundProps, PageContainerProps, PageHeaderProps, PageHeaderV2Props, PaginatedResponse, ParamsObject, PopoverProps, RadioProps, RemoveParamRules, RoleBannerProps, SearchParamObject, SelectProps, SliderProps$1 as SliderProps, StatusLightProps, SwitchProps, TabGroupProps, TabProps, TableBodyProps, TableCellProps, TableContainerProps, TableFooterProps, TableHeadProps, TablePaginationProps, TableProps, TableRowProps, TableSortLabelProps, TabsProps, TagItem, TagProps, TagsListProps, ToggleButtonProps, TypographyProps, UrlKeyMap, UseFilterStateOptions, UseGsdSliderProps, UseToggleResult };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@up42/up-components",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"description": "UP42 Component Library",
|
|
5
5
|
"author": "Axel Fuhrmann axel.fuhrmann@up42.com",
|
|
6
6
|
"license": "ISC",
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"lint-staged": "^12.3.1",
|
|
82
82
|
"prettier": "^2.5.1",
|
|
83
83
|
"react": "^18.3.1",
|
|
84
|
+
"remark-gfm": "^4.0.1",
|
|
84
85
|
"rimraf": "^6.1.2",
|
|
85
86
|
"rollup": "^4.59.0",
|
|
86
87
|
"rollup-plugin-dts": "^6.1.0",
|
|
@@ -90,7 +91,8 @@
|
|
|
90
91
|
"typescript": "^4.5.4",
|
|
91
92
|
"vite": "^7.1.12",
|
|
92
93
|
"vite-plugin-svgr": "^4.2.0",
|
|
93
|
-
"vitest": "^3.2.4"
|
|
94
|
+
"vitest": "^3.2.4",
|
|
95
|
+
"yup": "^0.32.9"
|
|
94
96
|
},
|
|
95
97
|
"peerDependencies": {
|
|
96
98
|
"@emotion/react": "^11.7.1",
|
|
@@ -101,7 +103,8 @@
|
|
|
101
103
|
"dayjs": "^1.11.7",
|
|
102
104
|
"react": "^18.3.1",
|
|
103
105
|
"react-dom": "^18.3.1",
|
|
104
|
-
"react-router": "^7.12.0"
|
|
106
|
+
"react-router": "^7.12.0",
|
|
107
|
+
"yup": "^0.32.9"
|
|
105
108
|
},
|
|
106
109
|
"lint-staged": {
|
|
107
110
|
"*.{js,tsx,ts}": "eslint --cache --fix",
|
|
@@ -124,7 +127,8 @@
|
|
|
124
127
|
"lodash@>=4.0.0 <=4.17.22": ">=4.17.23",
|
|
125
128
|
"serialize-javascript@<=7.0.2": ">=7.0.3",
|
|
126
129
|
"csstype@>=3.0.0": "3.1.3",
|
|
127
|
-
"@tootallnate/once@<3.0.1": ">=3.0.1"
|
|
130
|
+
"@tootallnate/once@<3.0.1": ">=3.0.1",
|
|
131
|
+
"flatted": "^3.4.2"
|
|
128
132
|
}
|
|
129
133
|
}
|
|
130
134
|
}
|