@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 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, Chip, Menu, styled, Autocomplete, Dialog, alpha } from '@mui/material';
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: handleClose }, secondaryButton.label)),
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 != null ? React__default.createElement(React__default.Fragment, null, content) : undefined, showDividers: true, hideBackdrop: true, disableEnforceFocus: true, disableScrollLock: true, sx: {
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
- 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, FormCheckbox, FormDatePicker, FormDateRangePicker, FormDateRangePickerList, FormDateTimePicker, FormInput, FormRadio, FormSelect, 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, capitalize, copyToClipboard, formatDate, formatFileSize, formatNumber, generateQueryKey, objectToSearchParams, searchParamsToObject, theme, tokens, useAlert, useCursorPagination, useDebounce, useQueryParams, useRemotePagination, useToggle, useUrlParams, valueToString };
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$1, 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';
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$1, 'variant' | 'label'>>;
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
- * Filter area content (placeholder node for now).
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
- 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, FormCheckbox, FormDatePicker, FormDateRangePicker, FormDateRangePickerList, FormDateTimePicker, FormInput, FormRadio, FormSelect, 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, capitalize, copyToClipboard, formatDate, formatFileSize, formatNumber, generateQueryKey, objectToSearchParams, searchParamsToObject, theme, useAlert, useCursorPagination, useDebounce, useQueryParams, useRemotePagination, useToggle, useUrlParams, valueToString };
11818
- 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, FormCheckboxProps, FormDatePickerDateType, FormDatePickerProps, FormDateRangePickerListProps, FormDateRangePickerProps, FormDateTimePickerProps, FormInputProps, FormRadioProps, FormSelectProps, 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, StatusLightProps, SwitchProps, TabGroupProps, TabProps, TableBodyProps, TableCellProps, TableContainerProps, TableFooterProps, TableHeadProps, TablePaginationProps, TableProps, TableRowProps, TableSortLabelProps, TabsProps, TagItem, TagProps, TagsListProps, ToggleButtonProps, TypographyProps, UseToggleResult };
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.0.0",
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
  }