@rio-cloud/rio-uikit 2.0.0 → 2.1.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/components/actionBarItem/ActionBarItem.d.ts +2 -2
- package/components/actionBarItem/ActionBarItem.js.map +1 -1
- package/components/applicationHeader/ApplicationHeader.d.ts +6 -7
- package/components/applicationHeader/ApplicationHeader.js.map +1 -1
- package/components/applicationLayout/SubNavigation.d.ts +7 -0
- package/components/applicationLayout/SubNavigation.js.map +1 -1
- package/components/assetTree/AssetTree.d.ts +7 -0
- package/components/assetTree/AssetTree.js.map +1 -1
- package/components/assetTree/Tree.d.ts +15 -0
- package/components/assetTree/Tree.js.map +1 -1
- package/components/assetTree/TreeIcon.d.ts +30 -0
- package/components/assetTree/TreeIcon.js +16 -0
- package/components/assetTree/TreeIcon.js.map +1 -0
- package/components/assetTree/TreeLeaf.js +22 -22
- package/components/assetTree/TreeLeaf.js.map +1 -1
- package/components/assetTree/TreeNode.js +25 -25
- package/components/assetTree/TreeNode.js.map +1 -1
- package/components/assetTree/TreeSearch.d.ts +2 -0
- package/components/assetTree/TreeSearch.js.map +1 -1
- package/components/barList/BarList.d.ts +26 -0
- package/components/barList/BarList.js.map +1 -1
- package/components/bottomSheet/BottomSheet.d.ts +17 -3
- package/components/bottomSheet/BottomSheet.js.map +1 -1
- package/components/bottomSheet/TimedBottomSheet.d.ts +10 -0
- package/components/bottomSheet/TimedBottomSheet.js.map +1 -1
- package/components/calendarStripe/CalendarStripe.d.ts +1 -1
- package/components/calendarStripe/CalendarStripe.js +34 -36
- package/components/calendarStripe/CalendarStripe.js.map +1 -1
- package/components/charts/Area.d.ts +2 -2
- package/components/charts/Area.js.map +1 -1
- package/components/charts/Line.d.ts +2 -2
- package/components/charts/Line.js.map +1 -1
- package/components/checkbox/Checkbox.d.ts +0 -3
- package/components/checkbox/Checkbox.js.map +1 -1
- package/components/clearableInput/ClearableInput.d.ts +21 -20
- package/components/clearableInput/ClearableInput.js.map +1 -1
- package/components/collapse/Collapse.d.ts +3 -0
- package/components/collapse/Collapse.js +12 -12
- package/components/collapse/Collapse.js.map +1 -1
- package/components/contentLoader/ContentLoader.d.ts +10 -2
- package/components/contentLoader/ContentLoader.js.map +1 -1
- package/components/dataTabs/DataTabs.d.ts +6 -0
- package/components/dataTabs/DataTabs.js.map +1 -1
- package/components/datepicker/DatePicker.d.ts +2 -2
- package/components/datepicker/DatePicker.js +31 -31
- package/components/datepicker/DatePicker.js.map +1 -1
- package/components/dialog/ConfirmationDialog.d.ts +22 -0
- package/components/dialog/ConfirmationDialog.js.map +1 -1
- package/components/dialog/Dialog.d.ts +13 -1
- package/components/dialog/Dialog.js.map +1 -1
- package/components/dialog/ReleaseNotesDialog.d.ts +3 -3
- package/components/dialog/ReleaseNotesDialog.js.map +1 -1
- package/components/dropdown/ButtonDropdown.d.ts +4 -0
- package/components/dropdown/ButtonDropdown.js +51 -51
- package/components/dropdown/ButtonDropdown.js.map +1 -1
- package/components/dropdown/DropdownSubmenu.d.ts +4 -0
- package/components/dropdown/DropdownSubmenu.js.map +1 -1
- package/components/editableContent/EditableContent.d.ts +6 -0
- package/components/editableContent/EditableContent.js.map +1 -1
- package/components/expander/ExpanderList.d.ts +3 -0
- package/components/expander/ExpanderList.js.map +1 -1
- package/components/expander/ExpanderPanel.d.ts +14 -4
- package/components/expander/ExpanderPanel.js.map +1 -1
- package/components/fade/Fade.d.ts +1 -1
- package/components/fade/Fade.js.map +1 -1
- package/components/filepicker/FilePicker.d.ts +0 -2
- package/components/filepicker/FilePicker.js.map +1 -1
- package/components/groupedItemList/GroupedItemList.d.ts +10 -7
- package/components/groupedItemList/GroupedItemList.js.map +1 -1
- package/components/listMenu/ListMenu.js.map +1 -1
- package/components/listMenu/ListMenuGroup.d.ts +2 -1
- package/components/listMenu/ListMenuGroup.js.map +1 -1
- package/components/map/components/Map.js.map +1 -1
- package/components/map/components/constants.js.map +1 -1
- package/components/map/components/features/ContextMenuItem.d.ts +1 -1
- package/components/map/components/features/ContextMenuItem.js +2 -17
- package/components/map/components/features/ContextMenuItem.js.map +1 -1
- package/components/map/components/features/basics/Polyline.d.ts +4 -1
- package/components/map/components/features/basics/Polyline.js +1 -1
- package/components/map/components/features/basics/Polyline.js.map +1 -1
- package/components/map/components/features/layers/MarkerLayer.d.ts +3 -1
- package/components/map/components/features/layers/MarkerLayer.js.map +1 -1
- package/components/map/components/features/layers/clustering/ClusterLayer.js +1 -1
- package/components/map/components/features/layers/clustering/ClusterLayer.js.map +1 -1
- package/components/map/components/features/layers/clustering/SimpleClusterLayer.d.ts +3 -2
- package/components/map/components/features/layers/clustering/SimpleClusterLayer.js.map +1 -1
- package/components/map/components/features/layers/overlayLayers/RoadRestrictionLayer.js +7 -7
- package/components/map/components/features/layers/overlayLayers/RoadRestrictionLayer.js.map +1 -1
- package/components/map/components/features/layers/overlayLayers/TrafficLayer.js +4 -4
- package/components/map/components/features/layers/overlayLayers/TrafficLayer.js.map +1 -1
- package/components/map/utils/clustering.d.ts +6 -1
- package/components/map/utils/clustering.js +25 -19
- package/components/map/utils/clustering.js.map +1 -1
- package/components/map/utils/rendering.d.ts +1 -1
- package/components/map/utils/rendering.js +23 -23
- package/components/map/utils/rendering.js.map +1 -1
- package/components/menuItems/MenuItem.d.ts +23 -0
- package/components/menuItems/MenuItem.js.map +1 -1
- package/components/notification/Notification.js +4 -4
- package/components/notification/Notification.js.map +1 -1
- package/components/onboarding/OnboardingTip.d.ts +18 -12
- package/components/onboarding/OnboardingTip.js.map +1 -1
- package/components/overlay/OverlayTrigger.d.ts +43 -1
- package/components/overlay/OverlayTrigger.js.map +1 -1
- package/components/pager/Pager.d.ts +3 -0
- package/components/pager/Pager.js.map +1 -1
- package/components/popover/Popover.d.ts +1 -0
- package/components/popover/Popover.js.map +1 -1
- package/components/preloader/ImagePreloader.d.ts +1 -1
- package/components/preloader/ImagePreloader.js.map +1 -1
- package/components/radiobutton/RadioButton.d.ts +9 -5
- package/components/radiobutton/RadioButton.js.map +1 -1
- package/components/releaseNotes/ReleaseNotes.d.ts +0 -3
- package/components/releaseNotes/ReleaseNotes.js.map +1 -1
- package/components/resizer/Resizer.d.ts +17 -3
- package/components/resizer/Resizer.js.map +1 -1
- package/components/rioglyph/Rioglyph.d.ts +20 -8
- package/components/rioglyph/Rioglyph.js.map +1 -1
- package/components/rules/RulesWrapper.js +1 -1
- package/components/rules/RulesWrapper.js.map +1 -1
- package/components/saveableInput/SaveableDateInput.d.ts +20 -2
- package/components/saveableInput/SaveableDateInput.js.map +1 -1
- package/components/saveableInput/SaveableInput.d.ts +10 -2
- package/components/saveableInput/SaveableInput.js.map +1 -1
- package/components/selects/BaseSelectDropdown.js +90 -79
- package/components/selects/BaseSelectDropdown.js.map +1 -1
- package/components/selects/Select.d.ts +5 -0
- package/components/selects/Select.js +94 -94
- package/components/selects/Select.js.map +1 -1
- package/components/sidebars/Sidebar.d.ts +19 -3
- package/components/sidebars/Sidebar.js.map +1 -1
- package/components/slider/RangeSlider.d.ts +15 -0
- package/components/slider/RangeSlider.js.map +1 -1
- package/components/slider/Slider.d.ts +9 -0
- package/components/slider/Slider.js.map +1 -1
- package/components/smoothScrollbars/SmoothScrollbars.d.ts +44 -0
- package/components/smoothScrollbars/SmoothScrollbars.js.map +1 -1
- package/components/spinner/Spinner.d.ts +3 -3
- package/components/spinner/Spinner.js.map +1 -1
- package/components/states/BaseStateProps.d.ts +6 -2
- package/components/states/StateIcon.d.ts +14 -1
- package/components/states/StateIcon.js.map +1 -1
- package/components/statsWidget/StatsWidget.d.ts +2 -0
- package/components/statsWidget/StatsWidget.js.map +1 -1
- package/components/statsWidget/StatsWidgetBody.d.ts +1 -0
- package/components/statsWidget/StatsWidgetBody.js.map +1 -1
- package/components/statsWidget/StatsWidgetNumber.d.ts +2 -0
- package/components/statsWidget/StatsWidgetNumber.js.map +1 -1
- package/components/statusBar/StatusBar.d.ts +2 -31
- package/components/statusBar/StatusBar.js.map +1 -1
- package/components/statusBar/StatusBarIcon.d.ts +2 -2
- package/components/statusBar/StatusBarIcon.js.map +1 -1
- package/components/statusBar/StatusBarLabel.d.ts +2 -2
- package/components/statusBar/StatusBarLabel.js.map +1 -1
- package/components/statusBar/StatusBarProgressBar.d.ts +1 -1
- package/components/statusBar/StatusBarProgressBar.js.map +1 -1
- package/components/statusBar/{StatusBar.types.d.ts → StatusBarProps.d.ts} +44 -2
- package/components/steppedProgressBar/SteppedProgressBar.d.ts +1 -1
- package/components/steppedProgressBar/SteppedProgressBar.js.map +1 -1
- package/components/switch/Switch.d.ts +13 -1
- package/components/switch/Switch.js.map +1 -1
- package/components/table/SortArrowDown.d.ts +1 -1
- package/components/table/SortArrowDown.js.map +1 -1
- package/components/table/SortArrowUp.d.ts +1 -1
- package/components/table/SortArrowUp.js.map +1 -1
- package/components/table/TableSettingsDialog.d.ts +5 -0
- package/components/table/TableSettingsDialog.js +119 -109
- package/components/table/TableSettingsDialog.js.map +1 -1
- package/components/table/TableSettingsDialogFooter.js +9 -9
- package/components/table/TableSettingsDialogFooter.js.map +1 -1
- package/components/table/TableViewToggles.d.ts +21 -1
- package/components/table/TableViewToggles.js.map +1 -1
- package/components/tag/Tag.d.ts +7 -2
- package/components/tag/Tag.js.map +1 -1
- package/components/tagManager/TagManager.d.ts +15 -0
- package/components/tagManager/TagManager.js.map +1 -1
- package/components/tagManager/TagManagerTag.d.ts +9 -0
- package/components/teaser/Teaser.d.ts +57 -55
- package/components/teaser/Teaser.js.map +1 -1
- package/components/teaser/TeaserContainer.d.ts +1 -1
- package/components/teaser/TeaserContainer.js.map +1 -1
- package/components/tooltip/SimpleTooltip.d.ts +22 -4
- package/components/tooltip/SimpleTooltip.js.map +1 -1
- package/components/tooltip/Tooltip.d.ts +22 -2
- package/components/tooltip/Tooltip.js.map +1 -1
- package/components/video/ResponsiveVideo.d.ts +8 -3
- package/components/video/ResponsiveVideo.js.map +1 -1
- package/hooks/useKey.d.ts +1 -1
- package/hooks/useKey.js.map +1 -1
- package/hooks/useOnboarding.d.ts +86 -80
- package/hooks/useOnboarding.js.map +1 -1
- package/hooks/useTableExport.js.map +1 -1
- package/hooks/useUncontrollable.d.ts +1 -1
- package/hooks/useUncontrollable.js.map +1 -1
- package/package.json +12 -14
- package/utils/colorScheme.js +14 -13
- package/utils/colorScheme.js.map +1 -1
- package/utils/cssuseragent.js.map +1 -1
- package/utils/scrollItemIntoView.js +12 -11
- package/utils/scrollItemIntoView.js.map +1 -1
- package/utils/urlFeatureToggles.js +19 -20
- package/utils/urlFeatureToggles.js.map +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SaveableDateInput.js","sources":["../../../src/components/saveableInput/SaveableDateInput.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type CSSProperties } from 'react';\nimport classNames from 'classnames';\nimport { noop } from 'es-toolkit/function';\nimport type { Moment } from 'moment';\n\nimport Button from '../../Button';\nimport DatePicker, { type DatePickerProps } from '../datepicker/DatePicker';\nimport useIsFocusWithin from '../../hooks/useIsFocusWithin';\nimport useEsc from '../../hooks/useEsc';\n\nconst DEFAULT_BUTTON_STYLE = 'primary';\n\nexport type SaveableDateInputProps = {\n /**\n * The input placeholder.\n */\n placeholder?: string;\n\n /**\n * The actual input value.\n */\n value?: Date | Moment | string;\n\n /**\n * Used to control the save button from the outside to disable it in case\n * the entered value is not valid.\n *\n * @default true\n */\n isValid?: boolean;\n\n /**\n * This is the error message that is shown below the input. It uses the built-in error handling, and will be shown when the \"isValid\" prop is set to false.\n */\n errorMessage?: string | React.ReactNode;\n\n /**\n * Defines wether the error icon is shown or not. If enabled, it will be shown when the \"isValid\" prop is set to false.\n */\n hideErrorIcon?: boolean;\n\n /**\n * Callback function triggered when the value changes and is saved.\n * @param value\n * @param previousValue\n * @returns\n */\n onValueChanged?: (value: Moment | string, previousValue: Moment | string | Date) => void;\n\n /**\n * Callback function that gets triggered on every input change. Use this to control the component\n * or when implementing key validation.\n * @param keyValue the key value that has been entered\n * @returns\n */\n onInputChange?: (value: Moment | string, isValid: boolean) => void;\n\n /**\n * Callback function that gets triggered when the input is in edit mode. Use this\n * to control the component and to handle the previous value on the outside.\n * @returns\n */\n onEnterEdit?: () => void;\n\n /**\n * Callback function that gets triggered when edit mode is exited (either saved or cancelled).\n * @param wasSaved - true if value was saved, false if cancelled/reset\n * @returns\n */\n onExitEdit?: (wasSaved: boolean) => void;\n\n /**\n * Callback function that gets triggered when the user aborts the edit mode. Use this\n * to control the component and handle the resetting of previous value on the outside.\n * @returns\n */\n onCancel?: () => void;\n\n /**\n * Behavior when trying to exit edit mode with invalid input:\n * - 'stay-open': Keep edit mode open until valid input is provided\n * - 'reset-and-close': Close edit mode and reset to initial value\n *\n * @default 'stay-open'\n */\n invalidExitBehavior?: 'stay-open' | 'reset-and-close';\n\n /**\n * Defines the button style: `default` or `primary`.\n */\n buttonStyle?: 'primary' | 'default';\n\n /**\n * Additional HTML attributes to be set on the input element.\n */\n inputProps?: HTMLAttributes<HTMLInputElement>;\n\n datePickerProps?: DatePickerProps;\n\n /**\n * Disables the component so the user cannot enter the edit mode.\n */\n disabled?: boolean;\n\n /**\n * Additional classes to be set on the input itself.\n */\n inputClassName?: string;\n\n /**\n * Additional classes to be set on the wrapper element.\n */\n className?: string;\n};\n\n// Validate date outside the component (via form library) if controlled usage\n// Use as controlled component: value, is Valid, and change callback\n// - if is valid, on save, call callback \"onValueChanged\"\n// - if not valid, keep edit mode open, outside is showing error message\n// - close edit mode only if date is valid - or close edit mode and reset to initial value -> customizable via prop\n// Keep picker open until user has clicked save, otherwise he might forget to click save if the dropdown closes automatically\n\nconst SaveableDateInput = (props: SaveableDateInputProps) => {\n const {\n placeholder,\n value: externalValue = '',\n isValid = true,\n errorMessage,\n hideErrorIcon = false,\n onValueChanged = noop,\n onInputChange, // for controlled usage\n onEnterEdit = noop,\n onExitEdit = noop,\n onCancel = noop,\n buttonStyle = DEFAULT_BUTTON_STYLE,\n inputClassName,\n inputProps,\n invalidExitBehavior = 'stay-open',\n datePickerProps = {},\n disabled = false,\n className,\n ...remainingProps\n } = props;\n\n // if callback is provided, assume it is controlled case\n const isControlledCase = onInputChange !== undefined;\n\n const [inputValue, setInputValue] = useState<Date | Moment | string>(externalValue);\n\n const [editInput, setEditInput] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n\n const [isPickerOpen, setIsPickerOpen] = useState(false);\n\n const wrapperRef = useRef<HTMLDivElement>(null);\n const buttonRef = useRef<HTMLButtonElement>(null);\n\n const initialInputValueRef = useRef(inputValue);\n\n useIsFocusWithin({\n ref: wrapperRef,\n onFocusWithin: () => {\n setIsFocused(true);\n },\n onBlurWithin: () => {\n setIsFocused(false);\n },\n });\n\n // Handle escape key to cancel edit mode\n useEsc(() => {\n if (isFocused && editInput) {\n handleCancelEdit();\n }\n });\n\n // Update internal state in a controlled environment\n const [previousExternalValue, setPreviousExternalValue] = useState(externalValue);\n if (previousExternalValue !== externalValue) {\n setInputValue(externalValue);\n setPreviousExternalValue(externalValue);\n // Update initial value ref when external value changes while not in edit mode\n if (!editInput) {\n initialInputValueRef.current = externalValue;\n }\n }\n\n const handleToggleInput = () => {\n if (!editInput) {\n // Enter edit mode\n setEditInput(true);\n initialInputValueRef.current = inputValue;\n\n // open the dropdown\n setIsPickerOpen(true);\n\n onEnterEdit();\n } else {\n // Try to exit edit mode\n handleSaveAttempt();\n }\n };\n\n const handleSaveAttempt = () => {\n if (isValid) {\n // Save the value (only if valid)\n setEditInput(false);\n onValueChanged(inputValue as Moment | string, initialInputValueRef.current);\n onExitEdit(true);\n\n // close the dropdown\n setIsPickerOpen(false);\n } else {\n // Handle invalid input based on behavior setting\n if (invalidExitBehavior === 'reset-and-close') {\n handleCancelEdit();\n }\n // If 'stay-open', do nothing - keep edit mode open\n }\n };\n\n const handleCancelEdit = () => {\n setEditInput(false);\n\n // Reset to initial value\n if (isControlledCase) {\n // In controlled mode, trigger change to reset to initial value\n onInputChange(initialInputValueRef.current as Moment | string, true);\n } else {\n setInputValue(initialInputValueRef.current);\n }\n\n // Close the dropdown\n setIsPickerOpen(false);\n\n onCancel();\n onExitEdit(false);\n };\n\n const handleDateChange = (value: Moment | string, isValid: boolean) => {\n if (isControlledCase) {\n onInputChange(value, isValid);\n } else {\n setInputValue(value);\n }\n };\n\n const handleCloseDropdown = () => setIsPickerOpen(false);\n\n const wrapperClasses = classNames('form-group', !isValid && 'has-feedback has-error', className);\n\n const buttonIconClasses = classNames('rioglyph', editInput ? 'rioglyph-ok' : 'rioglyph-pencil');\n\n const dateInputClasses = classNames('margin-0 width-100pct', inputClassName);\n\n // Button should be disabled if:\n // - Component is disabled, OR\n // - In edit mode and invalid input (and behavior is stay-open)\n const disableButton = disabled || (editInput && !isValid && invalidExitBehavior === 'stay-open');\n\n let inputStyle: CSSProperties = {\n borderTopRightRadius: 0,\n borderBottomRightRadius: 0,\n };\n\n if (!isValid && hideErrorIcon) {\n // If the error icon shall not be shown, remove the input padding to avoid cutting of the date value\n inputStyle = { ...inputStyle, paddingRight: '10px' };\n }\n\n return (\n <div ref={wrapperRef} {...remainingProps} className={wrapperClasses}>\n <div className='input-group'>\n <DatePicker\n {...{ ...datePickerProps, open: isPickerOpen }}\n className={dateInputClasses}\n inputProps={{\n ...inputProps,\n placeholder,\n disabled: !editInput,\n style: inputStyle,\n }}\n value={inputValue}\n onChange={handleDateChange}\n onClose={handleCloseDropdown}\n />\n {!isValid && !hideErrorIcon && (\n <span className='right-25 margin-right-10 form-control-feedback rioglyph rioglyph-error-sign' />\n )}\n <div className='input-group-btn'>\n <Button\n ref={buttonRef}\n bsStyle={buttonStyle}\n iconOnly\n onClick={handleToggleInput}\n disabled={disableButton}\n >\n <span className={buttonIconClasses} />\n </Button>\n </div>\n </div>\n {!isValid && errorMessage && (\n <span className='help-block z-index-max'>\n <span>{errorMessage}</span>\n </span>\n )}\n </div>\n );\n};\n\nexport default SaveableDateInput;\n"],"names":["DEFAULT_BUTTON_STYLE","SaveableDateInput","props","placeholder","externalValue","isValid","errorMessage","hideErrorIcon","onValueChanged","noop","onInputChange","onEnterEdit","onExitEdit","onCancel","buttonStyle","inputClassName","inputProps","invalidExitBehavior","datePickerProps","disabled","className","remainingProps","isControlledCase","inputValue","setInputValue","useState","editInput","setEditInput","isFocused","setIsFocused","isPickerOpen","setIsPickerOpen","wrapperRef","useRef","buttonRef","initialInputValueRef","useIsFocusWithin","useEsc","handleCancelEdit","previousExternalValue","setPreviousExternalValue","handleToggleInput","handleSaveAttempt","handleDateChange","value","handleCloseDropdown","wrapperClasses","classNames","buttonIconClasses","dateInputClasses","disableButton","inputStyle","jsxs","jsx","DatePicker","Button"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SaveableDateInput.js","sources":["../../../src/components/saveableInput/SaveableDateInput.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type CSSProperties } from 'react';\nimport classNames from 'classnames';\nimport { noop } from 'es-toolkit/function';\nimport type { Moment } from 'moment';\n\nimport Button from '../../Button';\nimport DatePicker, { type DatePickerProps } from '../datepicker/DatePicker';\nimport useIsFocusWithin from '../../hooks/useIsFocusWithin';\nimport useEsc from '../../hooks/useEsc';\n\nconst DEFAULT_BUTTON_STYLE = 'primary';\n\nexport type SaveableDateInputProps = {\n /**\n * The input placeholder.\n */\n placeholder?: string;\n\n /**\n * The actual input value.\n */\n value?: Date | Moment | string;\n\n /**\n * Used to control the save button from the outside to disable it in case\n * the entered value is not valid.\n *\n * @default true\n */\n isValid?: boolean;\n\n /**\n * This is the error message that is shown below the input. It uses the built-in error handling,\n * and will be shown when the \"isValid\" prop is set to false.\n */\n errorMessage?: string | React.ReactNode;\n\n /**\n * Defines wether the error icon is shown or not. If enabled, it will be shown when the \"isValid\" prop\n * is set to false.\n *\n * @default false\n */\n hideErrorIcon?: boolean;\n\n /**\n * Callback function triggered when the value changes and is saved.\n *\n * @param value\n * @param previousValue\n * @returns\n */\n onValueChanged?: (value: Moment | string, previousValue: Moment | string | Date) => void;\n\n /**\n * Callback function that gets triggered on every input change. Use this to control the component\n * or when implementing key validation.\n *\n * @param keyValue the key value that has been entered\n * @returns\n */\n onInputChange?: (value: Moment | string, isValid: boolean) => void;\n\n /**\n * Callback function that gets triggered when the input is in edit mode. Use this\n * to control the component and to handle the previous value on the outside.\n *\n * @returns\n */\n onEnterEdit?: () => void;\n\n /**\n * Callback function that gets triggered when edit mode is exited (either saved or cancelled).\n *\n * @param wasSaved - true if value was saved, false if cancelled/reset\n * @returns\n */\n onExitEdit?: (wasSaved: boolean) => void;\n\n /**\n * Callback function that gets triggered when the user aborts the edit mode. Use this\n * to control the component and handle the resetting of previous value on the outside.\n *\n * @returns\n */\n onCancel?: () => void;\n\n /**\n * Behavior when trying to exit edit mode with invalid input:\n * - 'stay-open': Keep edit mode open until valid input is provided\n * - 'reset-and-close': Close edit mode and reset to initial value\n *\n * @default 'stay-open'\n */\n invalidExitBehavior?: 'stay-open' | 'reset-and-close';\n\n /**\n * Defines the button style: `default` or `primary`.\n *\n * @default 'primary'\n */\n buttonStyle?: 'primary' | 'default';\n\n /**\n * Additional HTML attributes to be set on the input element.\n */\n inputProps?: HTMLAttributes<HTMLInputElement>;\n\n /**\n * Additional props passed to the underlying DatePicker component.\n *\n * @default {}\n */\n datePickerProps?: DatePickerProps;\n\n /**\n * Disables the component so the user cannot enter the edit mode.\n *\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Additional classes to be set on the input itself.\n */\n inputClassName?: string;\n\n /**\n * Additional classes to be set on the wrapper element.\n */\n className?: string;\n};\n\n// Validate date outside the component (via form library) if controlled usage\n// Use as controlled component: value, is Valid, and change callback\n// - if is valid, on save, call callback \"onValueChanged\"\n// - if not valid, keep edit mode open, outside is showing error message\n// - close edit mode only if date is valid - or close edit mode and reset to initial value -> customizable via prop\n// Keep picker open until user has clicked save, otherwise he might forget to click save if the dropdown closes automatically\n\nconst SaveableDateInput = (props: SaveableDateInputProps) => {\n const {\n placeholder,\n value: externalValue = '',\n isValid = true,\n errorMessage,\n hideErrorIcon = false,\n onValueChanged = noop,\n onInputChange, // for controlled usage\n onEnterEdit = noop,\n onExitEdit = noop,\n onCancel = noop,\n buttonStyle = DEFAULT_BUTTON_STYLE,\n inputClassName,\n inputProps,\n invalidExitBehavior = 'stay-open',\n datePickerProps = {},\n disabled = false,\n className,\n ...remainingProps\n } = props;\n\n // if callback is provided, assume it is controlled case\n const isControlledCase = onInputChange !== undefined;\n\n const [inputValue, setInputValue] = useState<Date | Moment | string>(externalValue);\n\n const [editInput, setEditInput] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n\n const [isPickerOpen, setIsPickerOpen] = useState(false);\n\n const wrapperRef = useRef<HTMLDivElement>(null);\n const buttonRef = useRef<HTMLButtonElement>(null);\n\n const initialInputValueRef = useRef(inputValue);\n\n useIsFocusWithin({\n ref: wrapperRef,\n onFocusWithin: () => {\n setIsFocused(true);\n },\n onBlurWithin: () => {\n setIsFocused(false);\n },\n });\n\n // Handle escape key to cancel edit mode\n useEsc(() => {\n if (isFocused && editInput) {\n handleCancelEdit();\n }\n });\n\n // Update internal state in a controlled environment\n const [previousExternalValue, setPreviousExternalValue] = useState(externalValue);\n if (previousExternalValue !== externalValue) {\n setInputValue(externalValue);\n setPreviousExternalValue(externalValue);\n // Update initial value ref when external value changes while not in edit mode\n if (!editInput) {\n initialInputValueRef.current = externalValue;\n }\n }\n\n const handleToggleInput = () => {\n if (!editInput) {\n // Enter edit mode\n setEditInput(true);\n initialInputValueRef.current = inputValue;\n\n // open the dropdown\n setIsPickerOpen(true);\n\n onEnterEdit();\n } else {\n // Try to exit edit mode\n handleSaveAttempt();\n }\n };\n\n const handleSaveAttempt = () => {\n if (isValid) {\n // Save the value (only if valid)\n setEditInput(false);\n onValueChanged(inputValue as Moment | string, initialInputValueRef.current);\n onExitEdit(true);\n\n // close the dropdown\n setIsPickerOpen(false);\n } else {\n // Handle invalid input based on behavior setting\n if (invalidExitBehavior === 'reset-and-close') {\n handleCancelEdit();\n }\n // If 'stay-open', do nothing - keep edit mode open\n }\n };\n\n const handleCancelEdit = () => {\n setEditInput(false);\n\n // Reset to initial value\n if (isControlledCase) {\n // In controlled mode, trigger change to reset to initial value\n onInputChange(initialInputValueRef.current as Moment | string, true);\n } else {\n setInputValue(initialInputValueRef.current);\n }\n\n // Close the dropdown\n setIsPickerOpen(false);\n\n onCancel();\n onExitEdit(false);\n };\n\n const handleDateChange = (value: Moment | string, isValid: boolean) => {\n if (isControlledCase) {\n onInputChange(value, isValid);\n } else {\n setInputValue(value);\n }\n };\n\n const handleCloseDropdown = () => setIsPickerOpen(false);\n\n const wrapperClasses = classNames('form-group', !isValid && 'has-feedback has-error', className);\n\n const buttonIconClasses = classNames('rioglyph', editInput ? 'rioglyph-ok' : 'rioglyph-pencil');\n\n const dateInputClasses = classNames('margin-0 width-100pct', inputClassName);\n\n // Button should be disabled if:\n // - Component is disabled, OR\n // - In edit mode and invalid input (and behavior is stay-open)\n const disableButton = disabled || (editInput && !isValid && invalidExitBehavior === 'stay-open');\n\n let inputStyle: CSSProperties = {\n borderTopRightRadius: 0,\n borderBottomRightRadius: 0,\n };\n\n if (!isValid && hideErrorIcon) {\n // If the error icon shall not be shown, remove the input padding to avoid cutting of the date value\n inputStyle = { ...inputStyle, paddingRight: '10px' };\n }\n\n return (\n <div ref={wrapperRef} {...remainingProps} className={wrapperClasses}>\n <div className='input-group'>\n <DatePicker\n {...{ ...datePickerProps, open: isPickerOpen }}\n className={dateInputClasses}\n inputProps={{\n ...inputProps,\n placeholder,\n disabled: !editInput,\n style: inputStyle,\n }}\n value={inputValue}\n onChange={handleDateChange}\n onClose={handleCloseDropdown}\n />\n {!isValid && !hideErrorIcon && (\n <span className='right-25 margin-right-10 form-control-feedback rioglyph rioglyph-error-sign' />\n )}\n <div className='input-group-btn'>\n <Button\n ref={buttonRef}\n bsStyle={buttonStyle}\n iconOnly\n onClick={handleToggleInput}\n disabled={disableButton}\n >\n <span className={buttonIconClasses} />\n </Button>\n </div>\n </div>\n {!isValid && errorMessage && (\n <span className='help-block z-index-max'>\n <span>{errorMessage}</span>\n </span>\n )}\n </div>\n );\n};\n\nexport default SaveableDateInput;\n"],"names":["DEFAULT_BUTTON_STYLE","SaveableDateInput","props","placeholder","externalValue","isValid","errorMessage","hideErrorIcon","onValueChanged","noop","onInputChange","onEnterEdit","onExitEdit","onCancel","buttonStyle","inputClassName","inputProps","invalidExitBehavior","datePickerProps","disabled","className","remainingProps","isControlledCase","inputValue","setInputValue","useState","editInput","setEditInput","isFocused","setIsFocused","isPickerOpen","setIsPickerOpen","wrapperRef","useRef","buttonRef","initialInputValueRef","useIsFocusWithin","useEsc","handleCancelEdit","previousExternalValue","setPreviousExternalValue","handleToggleInput","handleSaveAttempt","handleDateChange","value","handleCloseDropdown","wrapperClasses","classNames","buttonIconClasses","dateInputClasses","disableButton","inputStyle","jsxs","jsx","DatePicker","Button"],"mappings":"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"}
|
|
@@ -26,11 +26,15 @@ export type SaveableInputProps = {
|
|
|
26
26
|
*/
|
|
27
27
|
isValid?: boolean;
|
|
28
28
|
/**
|
|
29
|
-
* This is the error message that is shown below the input. It uses the built-in error handling,
|
|
29
|
+
* This is the error message that is shown below the input. It uses the built-in error handling,
|
|
30
|
+
* and will be shown when the "isValid" prop is set to false.
|
|
30
31
|
*/
|
|
31
32
|
errorMessage?: string | React.ReactNode;
|
|
32
33
|
/**
|
|
33
|
-
* Defines wether the error icon is shown or not. If enabled, it will be shown when the "isValid" prop
|
|
34
|
+
* Defines wether the error icon is shown or not. If enabled, it will be shown when the "isValid" prop
|
|
35
|
+
* is set to false.
|
|
36
|
+
*
|
|
37
|
+
* @default false
|
|
34
38
|
*/
|
|
35
39
|
hideErrorIcon?: boolean;
|
|
36
40
|
/**
|
|
@@ -62,6 +66,8 @@ export type SaveableInputProps = {
|
|
|
62
66
|
onEnterEdit?: () => void;
|
|
63
67
|
/**
|
|
64
68
|
* Defines the button style: `default` or `primary`.
|
|
69
|
+
*
|
|
70
|
+
* @default 'primary'
|
|
65
71
|
*/
|
|
66
72
|
buttonStyle?: 'primary' | 'default';
|
|
67
73
|
/**
|
|
@@ -79,6 +85,8 @@ export type SaveableInputProps = {
|
|
|
79
85
|
icon?: string;
|
|
80
86
|
/**
|
|
81
87
|
* Disables the component so the user cannot enter the edit mode.
|
|
88
|
+
*
|
|
89
|
+
* @default false
|
|
82
90
|
*/
|
|
83
91
|
disabled?: boolean;
|
|
84
92
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SaveableInput.js","sources":["../../../src/components/saveableInput/SaveableInput.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState, type ChangeEvent, type HTMLAttributes } from 'react';\nimport classNames from 'classnames';\nimport { isEmpty } from 'es-toolkit/compat';\nimport { noop } from 'es-toolkit/function';\n\n// @ts-ignore\nimport Button from '../../Button';\nimport useKey from '../../hooks/useKey';\nimport useEsc from '../../hooks/useEsc';\n\nconst DEFAULT_BUTTON_STYLE = 'primary';\n\nconst useFocus = (isEditable: boolean) => {\n const inputRef = useRef<HTMLInputElement>(null);\n useEffect(() => {\n if (inputRef.current && isEditable) {\n inputRef.current.focus();\n }\n }, [inputRef.current, isEditable]);\n\n return inputRef;\n};\n\nexport type SaveableInputProps = {\n /**\n * The input placeholder.\n */\n placeholder?: string;\n\n /**\n * The previous or old value shown above the input value.\n * This value will *not* be changed for new input values.\n */\n fixedPreviousValue?: string;\n\n /**\n * The previous or old value shown above the input value.\n * This value *changes* when a new input value is accepted.\n */\n previousValue?: string;\n\n /**\n * The actual input value.\n */\n value?: string;\n\n /**\n * Used to control the save button from the outside to disable it in case\n * the entered value is not valid.\n *\n * @default true\n */\n isValid?: boolean;\n\n /**\n * This is the error message that is shown below the input. It uses the built-in error handling, and will be shown when the \"isValid\" prop is set to false.\n */\n errorMessage?: string | React.ReactNode;\n\n /**\n * Defines wether the error icon is shown or not. If enabled, it will be shown when the \"isValid\" prop is set to false.\n */\n hideErrorIcon?: boolean;\n\n /**\n * Callback function triggered when the value changes.\n * @param value\n * @param previousValue\n * @returns\n */\n onValueChanged?: (value: string, previousValue: string) => void;\n\n /**\n * Callback function that gets triggered on every input change. Use this to control the component\n * or when implementing key validation.\n * @param keyValue the key value that has been entered\n * @param inputValue the current complete value of the input\n * @returns\n */\n onInputChange?: (keyValue: string, inputValue: string) => void;\n\n /**\n * Callback function that gets triggered when the user aborts the edit mode. Use this\n * to control the component and handle the resetting of previous value on the outside.\n * @returns\n */\n onEsc?: () => void;\n\n /**\n * Callback function that gets triggered when the input is in edit mode. Use this\n * to control the component dna to handle the previous value on the outside.\n * @returns\n */\n onEnterEdit?: () => void;\n\n /**\n * Defines the button style: `default` or `primary`.\n */\n buttonStyle?: 'primary' | 'default';\n\n /**\n * Additional HTML attributes to be set on the input element.\n */\n inputProps?: HTMLAttributes<HTMLInputElement>;\n\n /**\n * Adds a given unit to the input.\n */\n unit?: string | React.ReactNode;\n\n /**\n * Icon class name that shall be used. If defined, the input element is wrapped in an input-group\n * and the icon will be set in an input-addon. Example: `rioglyph-search`.\n */\n icon?: string;\n\n /**\n * Disables the component so the user cannot enter the edit mode.\n */\n disabled?: boolean;\n\n /**\n * Additional classes to be set on the input itself.\n */\n inputClassName?: string;\n\n /**\n * Additional classes to be set on the wrapper element.\n */\n className?: string;\n};\n\n// Features:\n// [ ] what shall happen when user leaves component while in edit mode (click outside or tab) - close on blur?\n// [x] avoid save without change\n// [x] use fixed previous value\n// [x] enter = save\n// [x] esc key to abort and leave edit mode\n// [x] tab focus + enter = go into edit mode\n// [x] validate after each key, i.e for number inputs - use onInputChange callback\n// [x] support form feedback error - wrap it with form-group and feedback classes\n// [x] allow for unit and icon\n// [x] disabled input\n\nconst SaveableInput = (props: SaveableInputProps) => {\n const {\n placeholder,\n fixedPreviousValue = '',\n previousValue = '',\n value: externalValue = '',\n isValid = true,\n errorMessage,\n hideErrorIcon = false,\n onValueChanged = noop,\n onInputChange,\n onEsc = noop,\n onEnterEdit = noop,\n buttonStyle = DEFAULT_BUTTON_STYLE,\n inputClassName,\n inputProps,\n icon,\n unit,\n disabled = false,\n className,\n ...remainingProps\n } = props;\n\n const externalOldValue = previousValue || fixedPreviousValue;\n\n const [inputValue, setInputValue] = useState(externalValue);\n const [oldInputValue, setOldInputValue] = useState(externalOldValue);\n\n const [editInput, setEditInput] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n\n const initialInputValueRef = useRef(inputValue);\n const initialOldInputValueRef = useRef(oldInputValue);\n\n // Update internal state in a controlled environment\n const [previousExternalValue, setPreviousExternalValue] = useState(externalValue);\n if (previousExternalValue !== externalValue) {\n setInputValue(externalValue);\n setPreviousExternalValue(externalValue);\n }\n\n // Update internal state in a controlled environment\n const [previousExternalOldValue, setPreviousExternalOldValue] = useState(externalOldValue);\n if (previousExternalOldValue !== externalOldValue) {\n setOldInputValue(externalOldValue);\n setPreviousExternalOldValue(externalOldValue);\n }\n\n // Set focus on input when being in edit mode\n const inputRef = useFocus(editInput);\n\n // Allow to exit \"edit\" mode with \"Enter\" to accept changes\n useKey((event: KeyboardEvent) => {\n if (isFocused && editInput && isValid && event.key === 'Enter') {\n handleToggleInput();\n }\n });\n\n // Allow to exit \"edit\" mode with \"Esc\" to ignore changes\n useEsc(() => {\n if (isFocused) {\n // Restore local state to initial vales as it was when entering edit mode\n setInputValue(initialInputValueRef.current);\n setOldInputValue(initialOldInputValueRef.current);\n setEditInput(false);\n onEsc();\n }\n });\n\n const handleToggleInput = () => {\n if (editInput === false) {\n setEditInput(true);\n\n // Temporarily store values of input and oldInput to be used when\n // discarding changes on \"esc\" or to avoid saving without changes\n if (!fixedPreviousValue) {\n initialOldInputValueRef.current = oldInputValue;\n }\n initialInputValueRef.current = inputValue;\n\n onEnterEdit();\n } else {\n setEditInput(false);\n\n // In case the new input value has not changed to the initial value\n // reset the internal old value to the initial\n if (initialInputValueRef.current !== inputValue) {\n setOldInputValue(fixedPreviousValue || initialInputValueRef.current);\n onValueChanged(inputValue, oldInputValue);\n }\n }\n };\n\n const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {\n // Only update internal value if the external \"onInputChange\" callback function is not defined\n // as it will be used in a controlled way\n if (onInputChange) {\n // Use type assertion to access nativeEvent.data\n const nativeEventData = (event.nativeEvent as InputEvent)?.data;\n\n // Whe \"backspace\" is used to remove a value, the nativeEventData is undefined. In this case\n // we need to use the target value for the callback otherwise the user will not be able to\n // remove a character from the input\n const currentInputValue = event.target.value;\n\n onInputChange(nativeEventData ?? currentInputValue, event.currentTarget.value);\n return;\n }\n\n setInputValue(event.target.value);\n };\n\n const handleFocus = () => setIsFocused(true);\n const handleBlur = () => setIsFocused(false);\n\n const showOldValue = !isEmpty(oldInputValue) && oldInputValue !== inputValue && !editInput;\n\n const wrapperClasses = classNames('form-group', !isValid && 'has-feedback has-error', className);\n\n const inputClasses = classNames(\n 'form-control',\n showOldValue && 'padding-bottom-0 padding-top-10 text-size-12',\n unit && 'padding-right-50', // This value is not perfect as with longer units it might conflict with the value\n !unit && !isValid && hideErrorIcon && 'padding-right-10', // remove the padding for the error icon if not needed\n inputClassName\n );\n\n const oldValueClasses = classNames(\n 'position-absolute',\n 'top-2',\n 'left-10',\n 'margin-left-3',\n 'text-size-10',\n 'text-decoration-line-through',\n icon && 'padding-left-20'\n );\n\n const buttonIconClasses = classNames('rioglyph', editInput ? 'rioglyph-ok' : 'rioglyph-pencil');\n\n const disableButton = (editInput && !isValid) || disabled;\n\n return (\n <div {...remainingProps} className={wrapperClasses}>\n <div className='input-group'>\n {icon && (\n <span className='input-group-addon'>\n <span className={`rioglyph ${icon}`} aria-hidden='true' aria-label='input icon' />\n </span>\n )}\n <input\n type='text'\n ref={inputRef}\n placeholder={placeholder}\n className={inputClasses}\n value={inputValue}\n onChange={handleInputChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n disabled={!editInput}\n {...inputProps}\n />\n {unit && (\n <div className='position-absolute right-0 margin-right-50' aria-label='unit'>\n {unit}\n </div>\n )}\n {showOldValue && (\n <div className={oldValueClasses} aria-label='previous value'>\n {oldInputValue}\n </div>\n )}\n <div className='input-group-btn'>\n <Button bsStyle={buttonStyle} iconOnly onClick={handleToggleInput} disabled={disableButton}>\n <span className={buttonIconClasses} />\n </Button>\n </div>\n </div>\n {!isValid && !hideErrorIcon && !unit && (\n <span className='right-25 margin-right-10 form-control-feedback rioglyph rioglyph-error-sign' />\n )}\n {!isValid && errorMessage && (\n <span className='help-block z-index-max'>\n <span>{errorMessage}</span>\n </span>\n )}\n </div>\n );\n};\n\nexport default SaveableInput;\n"],"names":["DEFAULT_BUTTON_STYLE","useFocus","isEditable","inputRef","useRef","useEffect","SaveableInput","props","placeholder","fixedPreviousValue","previousValue","externalValue","isValid","errorMessage","hideErrorIcon","onValueChanged","noop","onInputChange","onEsc","onEnterEdit","buttonStyle","inputClassName","inputProps","icon","unit","disabled","className","remainingProps","externalOldValue","inputValue","setInputValue","useState","oldInputValue","setOldInputValue","editInput","setEditInput","isFocused","setIsFocused","initialInputValueRef","initialOldInputValueRef","previousExternalValue","setPreviousExternalValue","previousExternalOldValue","setPreviousExternalOldValue","useKey","event","handleToggleInput","useEsc","handleInputChange","nativeEventData","currentInputValue","handleFocus","handleBlur","showOldValue","isEmpty","wrapperClasses","classNames","inputClasses","oldValueClasses","buttonIconClasses","disableButton","jsxs","jsx","Button"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SaveableInput.js","sources":["../../../src/components/saveableInput/SaveableInput.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState, type ChangeEvent, type HTMLAttributes } from 'react';\nimport classNames from 'classnames';\nimport { isEmpty } from 'es-toolkit/compat';\nimport { noop } from 'es-toolkit/function';\n\nimport Button from '../../Button';\nimport useKey from '../../hooks/useKey';\nimport useEsc from '../../hooks/useEsc';\n\nconst DEFAULT_BUTTON_STYLE = 'primary';\n\nconst useFocus = (isEditable: boolean) => {\n const inputRef = useRef<HTMLInputElement>(null);\n useEffect(() => {\n if (inputRef.current && isEditable) {\n inputRef.current.focus();\n }\n }, [inputRef.current, isEditable]);\n\n return inputRef;\n};\n\nexport type SaveableInputProps = {\n /**\n * The input placeholder.\n */\n placeholder?: string;\n\n /**\n * The previous or old value shown above the input value.\n * This value will *not* be changed for new input values.\n */\n fixedPreviousValue?: string;\n\n /**\n * The previous or old value shown above the input value.\n * This value *changes* when a new input value is accepted.\n */\n previousValue?: string;\n\n /**\n * The actual input value.\n */\n value?: string;\n\n /**\n * Used to control the save button from the outside to disable it in case\n * the entered value is not valid.\n *\n * @default true\n */\n isValid?: boolean;\n\n /**\n * This is the error message that is shown below the input. It uses the built-in error handling,\n * and will be shown when the \"isValid\" prop is set to false.\n */\n errorMessage?: string | React.ReactNode;\n\n /**\n * Defines wether the error icon is shown or not. If enabled, it will be shown when the \"isValid\" prop\n * is set to false.\n *\n * @default false\n */\n hideErrorIcon?: boolean;\n\n /**\n * Callback function triggered when the value changes.\n * @param value\n * @param previousValue\n * @returns\n */\n onValueChanged?: (value: string, previousValue: string) => void;\n\n /**\n * Callback function that gets triggered on every input change. Use this to control the component\n * or when implementing key validation.\n * @param keyValue the key value that has been entered\n * @param inputValue the current complete value of the input\n * @returns\n */\n onInputChange?: (keyValue: string, inputValue: string) => void;\n\n /**\n * Callback function that gets triggered when the user aborts the edit mode. Use this\n * to control the component and handle the resetting of previous value on the outside.\n * @returns\n */\n onEsc?: () => void;\n\n /**\n * Callback function that gets triggered when the input is in edit mode. Use this\n * to control the component dna to handle the previous value on the outside.\n * @returns\n */\n onEnterEdit?: () => void;\n\n /**\n * Defines the button style: `default` or `primary`.\n *\n * @default 'primary'\n */\n buttonStyle?: 'primary' | 'default';\n\n /**\n * Additional HTML attributes to be set on the input element.\n */\n inputProps?: HTMLAttributes<HTMLInputElement>;\n\n /**\n * Adds a given unit to the input.\n */\n unit?: string | React.ReactNode;\n\n /**\n * Icon class name that shall be used. If defined, the input element is wrapped in an input-group\n * and the icon will be set in an input-addon. Example: `rioglyph-search`.\n */\n icon?: string;\n\n /**\n * Disables the component so the user cannot enter the edit mode.\n *\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Additional classes to be set on the input itself.\n */\n inputClassName?: string;\n\n /**\n * Additional classes to be set on the wrapper element.\n */\n className?: string;\n};\n\n// Features:\n// [ ] what shall happen when user leaves component while in edit mode (click outside or tab) - close on blur?\n// [x] avoid save without change\n// [x] use fixed previous value\n// [x] enter = save\n// [x] esc key to abort and leave edit mode\n// [x] tab focus + enter = go into edit mode\n// [x] validate after each key, i.e for number inputs - use onInputChange callback\n// [x] support form feedback error - wrap it with form-group and feedback classes\n// [x] allow for unit and icon\n// [x] disabled input\n\nconst SaveableInput = (props: SaveableInputProps) => {\n const {\n placeholder,\n fixedPreviousValue = '',\n previousValue = '',\n value: externalValue = '',\n isValid = true,\n errorMessage,\n hideErrorIcon = false,\n onValueChanged = noop,\n onInputChange,\n onEsc = noop,\n onEnterEdit = noop,\n buttonStyle = DEFAULT_BUTTON_STYLE,\n inputClassName,\n inputProps,\n icon,\n unit,\n disabled = false,\n className,\n ...remainingProps\n } = props;\n\n const externalOldValue = previousValue || fixedPreviousValue;\n\n const [inputValue, setInputValue] = useState(externalValue);\n const [oldInputValue, setOldInputValue] = useState(externalOldValue);\n\n const [editInput, setEditInput] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n\n const initialInputValueRef = useRef(inputValue);\n const initialOldInputValueRef = useRef(oldInputValue);\n\n // Update internal state in a controlled environment\n const [previousExternalValue, setPreviousExternalValue] = useState(externalValue);\n if (previousExternalValue !== externalValue) {\n setInputValue(externalValue);\n setPreviousExternalValue(externalValue);\n }\n\n // Update internal state in a controlled environment\n const [previousExternalOldValue, setPreviousExternalOldValue] = useState(externalOldValue);\n if (previousExternalOldValue !== externalOldValue) {\n setOldInputValue(externalOldValue);\n setPreviousExternalOldValue(externalOldValue);\n }\n\n // Set focus on input when being in edit mode\n const inputRef = useFocus(editInput);\n\n // Allow to exit \"edit\" mode with \"Enter\" to accept changes\n useKey((event: KeyboardEvent) => {\n if (isFocused && editInput && isValid && event.key === 'Enter') {\n handleToggleInput();\n }\n });\n\n // Allow to exit \"edit\" mode with \"Esc\" to ignore changes\n useEsc(() => {\n if (isFocused) {\n // Restore local state to initial vales as it was when entering edit mode\n setInputValue(initialInputValueRef.current);\n setOldInputValue(initialOldInputValueRef.current);\n setEditInput(false);\n onEsc();\n }\n });\n\n const handleToggleInput = () => {\n if (editInput === false) {\n setEditInput(true);\n\n // Temporarily store values of input and oldInput to be used when\n // discarding changes on \"esc\" or to avoid saving without changes\n if (!fixedPreviousValue) {\n initialOldInputValueRef.current = oldInputValue;\n }\n initialInputValueRef.current = inputValue;\n\n onEnterEdit();\n } else {\n setEditInput(false);\n\n // In case the new input value has not changed to the initial value\n // reset the internal old value to the initial\n if (initialInputValueRef.current !== inputValue) {\n setOldInputValue(fixedPreviousValue || initialInputValueRef.current);\n onValueChanged(inputValue, oldInputValue);\n }\n }\n };\n\n const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {\n // Only update internal value if the external \"onInputChange\" callback function is not defined\n // as it will be used in a controlled way\n if (onInputChange) {\n // Use type assertion to access nativeEvent.data\n const nativeEventData = (event.nativeEvent as InputEvent)?.data;\n\n // Whe \"backspace\" is used to remove a value, the nativeEventData is undefined. In this case\n // we need to use the target value for the callback otherwise the user will not be able to\n // remove a character from the input\n const currentInputValue = event.target.value;\n\n onInputChange(nativeEventData ?? currentInputValue, event.currentTarget.value);\n return;\n }\n\n setInputValue(event.target.value);\n };\n\n const handleFocus = () => setIsFocused(true);\n const handleBlur = () => setIsFocused(false);\n\n const showOldValue = !isEmpty(oldInputValue) && oldInputValue !== inputValue && !editInput;\n\n const wrapperClasses = classNames('form-group', !isValid && 'has-feedback has-error', className);\n\n const inputClasses = classNames(\n 'form-control',\n showOldValue && 'padding-bottom-0 padding-top-10 text-size-12',\n unit && 'padding-right-50', // This value is not perfect as with longer units it might conflict with the value\n !unit && !isValid && hideErrorIcon && 'padding-right-10', // remove the padding for the error icon if not needed\n inputClassName\n );\n\n const oldValueClasses = classNames(\n 'position-absolute',\n 'top-2',\n 'left-10',\n 'margin-left-3',\n 'text-size-10',\n 'text-decoration-line-through',\n icon && 'padding-left-20'\n );\n\n const buttonIconClasses = classNames('rioglyph', editInput ? 'rioglyph-ok' : 'rioglyph-pencil');\n\n const disableButton = (editInput && !isValid) || disabled;\n\n return (\n <div {...remainingProps} className={wrapperClasses}>\n <div className='input-group'>\n {icon && (\n <span className='input-group-addon'>\n <span className={`rioglyph ${icon}`} aria-hidden='true' aria-label='input icon' />\n </span>\n )}\n <input\n type='text'\n ref={inputRef}\n placeholder={placeholder}\n className={inputClasses}\n value={inputValue}\n onChange={handleInputChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n disabled={!editInput}\n {...inputProps}\n />\n {unit && (\n <div className='position-absolute right-0 margin-right-50' aria-label='unit'>\n {unit}\n </div>\n )}\n {showOldValue && (\n <div className={oldValueClasses} aria-label='previous value'>\n {oldInputValue}\n </div>\n )}\n <div className='input-group-btn'>\n <Button bsStyle={buttonStyle} iconOnly onClick={handleToggleInput} disabled={disableButton}>\n <span className={buttonIconClasses} />\n </Button>\n </div>\n </div>\n {!isValid && !hideErrorIcon && !unit && (\n <span className='right-25 margin-right-10 form-control-feedback rioglyph rioglyph-error-sign' />\n )}\n {!isValid && errorMessage && (\n <span className='help-block z-index-max'>\n <span>{errorMessage}</span>\n </span>\n )}\n </div>\n );\n};\n\nexport default SaveableInput;\n"],"names":["DEFAULT_BUTTON_STYLE","useFocus","isEditable","inputRef","useRef","useEffect","SaveableInput","props","placeholder","fixedPreviousValue","previousValue","externalValue","isValid","errorMessage","hideErrorIcon","onValueChanged","noop","onInputChange","onEsc","onEnterEdit","buttonStyle","inputClassName","inputProps","icon","unit","disabled","className","remainingProps","externalOldValue","inputValue","setInputValue","useState","oldInputValue","setOldInputValue","editInput","setEditInput","isFocused","setIsFocused","initialInputValueRef","initialOldInputValueRef","previousExternalValue","setPreviousExternalValue","previousExternalOldValue","setPreviousExternalOldValue","useKey","event","handleToggleInput","useEsc","handleInputChange","nativeEventData","currentInputValue","handleFocus","handleBlur","showOldValue","isEmpty","wrapperClasses","classNames","inputClasses","oldValueClasses","buttonIconClasses","disableButton","jsxs","jsx","Button"],"mappings":"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"}
|
|
@@ -1,143 +1,154 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { useState as
|
|
3
|
-
import
|
|
4
|
-
import { isEmpty as
|
|
5
|
-
import { noop as
|
|
6
|
-
import { useDropDirection as
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
const
|
|
1
|
+
import { jsx as c, jsxs as x, Fragment as Q } from "react/jsx-runtime";
|
|
2
|
+
import { useState as y, useRef as _, useEffect as b, useLayoutEffect as Y } from "react";
|
|
3
|
+
import C from "classnames";
|
|
4
|
+
import { isEmpty as V } from "es-toolkit/compat";
|
|
5
|
+
import { noop as N } from "es-toolkit/function";
|
|
6
|
+
import { useDropDirection as Z } from "../../utils/useDropDirection.js";
|
|
7
|
+
import { DOWN as O, scrollItemIntoView as T, UP as g } from "../../utils/scrollItemIntoView.js";
|
|
8
|
+
import $ from "../../hooks/useEffectOnce.js";
|
|
9
|
+
import ee from "../../hooks/useKey.js";
|
|
10
|
+
import te from "./DropdownHeader.js";
|
|
11
|
+
import re from "./NoItemMessage.js";
|
|
12
|
+
import se from "../spinner/Spinner.js";
|
|
13
|
+
const B = "data-item-id", ne = 0, H = "focus", L = O, be = (h) => {
|
|
14
14
|
const {
|
|
15
15
|
isOpen: r = !1,
|
|
16
16
|
isLoading: l = !1,
|
|
17
|
-
updateDOMValues: u =
|
|
18
|
-
onOpen:
|
|
19
|
-
onSelect:
|
|
20
|
-
onClose:
|
|
21
|
-
options:
|
|
22
|
-
autoDropDirection:
|
|
23
|
-
dropup:
|
|
24
|
-
pullRight:
|
|
25
|
-
useActiveClass:
|
|
26
|
-
focusedItemIndex:
|
|
27
|
-
keyboardUsed:
|
|
28
|
-
noItemMessage:
|
|
29
|
-
dropdownClassName:
|
|
30
|
-
} =
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
updateDOMValues: u = N,
|
|
18
|
+
onOpen: i = N,
|
|
19
|
+
onSelect: p = N,
|
|
20
|
+
onClose: E = N,
|
|
21
|
+
options: o = [],
|
|
22
|
+
autoDropDirection: j = !0,
|
|
23
|
+
dropup: K = !1,
|
|
24
|
+
pullRight: G = !1,
|
|
25
|
+
useActiveClass: P = !1,
|
|
26
|
+
focusedItemIndex: m,
|
|
27
|
+
keyboardUsed: f,
|
|
28
|
+
noItemMessage: W,
|
|
29
|
+
dropdownClassName: X
|
|
30
|
+
} = h, [n, k] = y(m || ne), [q, A] = y(f), w = _(L), s = _(null);
|
|
31
|
+
b(() => {
|
|
32
|
+
r && s.current && (s.current.scrollTop = 0);
|
|
33
|
+
}, [r]), b(() => {
|
|
34
|
+
typeof m == "number" && k(m), typeof f == "boolean" && A(f), r || (w.current = L);
|
|
35
|
+
}, [m, f, r]), $(() => {
|
|
36
|
+
const e = F();
|
|
33
37
|
u(e);
|
|
34
38
|
});
|
|
35
|
-
const
|
|
36
|
-
|
|
39
|
+
const I = Z({ pullRight: G, dropup: K, autoDropDirection: j, dropdownMenuRef: s }, [r]);
|
|
40
|
+
Y(() => {
|
|
37
41
|
if (s.current) {
|
|
38
42
|
const e = s.current.parentElement;
|
|
39
|
-
|
|
43
|
+
I.dropup ? e?.classList.add("dropup") : e?.classList.remove("dropup");
|
|
40
44
|
}
|
|
41
|
-
}, [
|
|
42
|
-
s.current && u(
|
|
43
|
-
}, [s.current])
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
}, [I, s]), b(() => {
|
|
46
|
+
s.current && u(F());
|
|
47
|
+
}, [s.current]), b(() => {
|
|
48
|
+
r && T(
|
|
49
|
+
w.current || L,
|
|
50
|
+
s.current,
|
|
51
|
+
D()
|
|
52
|
+
);
|
|
53
|
+
}, [n, r]);
|
|
54
|
+
const [v, U] = y(r);
|
|
55
|
+
r && !v ? (i(I.dropup), U(r)) : !r && v && (E(), U(r)), ee((e) => {
|
|
46
56
|
if (r)
|
|
47
57
|
switch (e.key) {
|
|
48
58
|
case "Escape": {
|
|
49
|
-
|
|
59
|
+
E();
|
|
50
60
|
break;
|
|
51
61
|
}
|
|
52
62
|
case "Tab": {
|
|
53
|
-
|
|
63
|
+
E();
|
|
54
64
|
break;
|
|
55
65
|
}
|
|
56
66
|
case "Enter": {
|
|
57
|
-
|
|
67
|
+
z(e);
|
|
58
68
|
break;
|
|
59
69
|
}
|
|
60
70
|
case "ArrowUp": {
|
|
61
|
-
e.preventDefault(),
|
|
71
|
+
e.preventDefault(), M(g), T(g, s.current, D());
|
|
62
72
|
break;
|
|
63
73
|
}
|
|
64
74
|
case "ArrowDown": {
|
|
65
|
-
e.preventDefault(),
|
|
75
|
+
e.preventDefault(), M(O), T(O, s.current, D());
|
|
66
76
|
break;
|
|
67
77
|
}
|
|
68
78
|
}
|
|
69
79
|
});
|
|
70
|
-
const
|
|
80
|
+
const M = (e) => {
|
|
71
81
|
let t = 0;
|
|
72
82
|
switch (e) {
|
|
73
|
-
case
|
|
74
|
-
t =
|
|
83
|
+
case g: {
|
|
84
|
+
t = n <= 0 ? n : n - 1;
|
|
75
85
|
break;
|
|
76
86
|
}
|
|
77
|
-
case
|
|
78
|
-
t =
|
|
87
|
+
case O: {
|
|
88
|
+
t = n === o.length - 1 ? n : n + 1;
|
|
79
89
|
break;
|
|
80
90
|
}
|
|
81
91
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
92
|
+
w.current = e;
|
|
93
|
+
const a = e === g ? o.length - 1 : 0;
|
|
94
|
+
k(t < 0 ? a : t), A(!0);
|
|
95
|
+
}, S = () => s.current?.getElementsByTagName("a") || [], F = () => {
|
|
85
96
|
if (s.current)
|
|
86
|
-
return [...
|
|
87
|
-
id: t.getAttribute(
|
|
97
|
+
return [...S()].map((t) => ({
|
|
98
|
+
id: t.getAttribute(B),
|
|
88
99
|
text: t.textContent
|
|
89
100
|
}));
|
|
90
|
-
},
|
|
91
|
-
if (e.preventDefault(),
|
|
101
|
+
}, D = () => [...S()].find((t) => t.className.includes(H)), z = (e) => {
|
|
102
|
+
if (e.preventDefault(), V(o))
|
|
92
103
|
return;
|
|
93
|
-
const t =
|
|
104
|
+
const t = D();
|
|
94
105
|
if (t) {
|
|
95
|
-
const a =
|
|
96
|
-
|
|
106
|
+
const a = o.find((d) => d.id === t.getAttribute(B));
|
|
107
|
+
p(a);
|
|
97
108
|
}
|
|
98
|
-
},
|
|
109
|
+
}, J = (e) => {
|
|
99
110
|
e.preventDefault();
|
|
100
|
-
const t = e.currentTarget.getElementsByTagName("input")[0].value, a =
|
|
101
|
-
|
|
102
|
-
},
|
|
103
|
-
return
|
|
104
|
-
l && /* @__PURE__ */
|
|
105
|
-
!l &&
|
|
111
|
+
const t = e.currentTarget.getElementsByTagName("input")[0].value, a = o.find((d) => d.id === t);
|
|
112
|
+
p(a);
|
|
113
|
+
}, R = C("dropdown-menu", I.pullRight && "pull-right", X);
|
|
114
|
+
return V(o) ? /* @__PURE__ */ c(re, { noItemMessage: W, className: R }) : /* @__PURE__ */ x("ul", { className: R, ref: s, role: "menu", children: [
|
|
115
|
+
l && /* @__PURE__ */ c("div", { className: "display-flex justify-content-center padding-10", children: /* @__PURE__ */ c(se, {}) }),
|
|
116
|
+
!l && o.map((e, t) => {
|
|
106
117
|
if (e.header)
|
|
107
|
-
return /* @__PURE__ */
|
|
108
|
-
const a =
|
|
109
|
-
|
|
118
|
+
return /* @__PURE__ */ c(te, { icon: e.icon, label: e.label }, e.id);
|
|
119
|
+
const a = C(
|
|
120
|
+
q && n === t ? H : "",
|
|
110
121
|
e.disabled && "pointer-events-none",
|
|
111
122
|
"display-flex align-items-center gap-3"
|
|
112
|
-
), d =
|
|
123
|
+
), d = C(
|
|
113
124
|
e.disabled && "disabled",
|
|
114
|
-
|
|
125
|
+
P && e.selected && "active"
|
|
115
126
|
);
|
|
116
|
-
return /* @__PURE__ */
|
|
127
|
+
return /* @__PURE__ */ c("li", { className: d, role: "listitem", children: /* @__PURE__ */ x(
|
|
117
128
|
"a",
|
|
118
129
|
{
|
|
119
130
|
role: "menuitem",
|
|
120
131
|
className: a,
|
|
121
132
|
"data-item-id": e.id,
|
|
122
133
|
"data-item-index": t,
|
|
123
|
-
onClick: e.disabled ? void 0 :
|
|
134
|
+
onClick: e.disabled ? void 0 : J,
|
|
124
135
|
children: [
|
|
125
|
-
/* @__PURE__ */
|
|
126
|
-
e.icon && /* @__PURE__ */
|
|
136
|
+
/* @__PURE__ */ x("span", { className: "selected-option-dropdown-item", children: [
|
|
137
|
+
e.icon && /* @__PURE__ */ c(Q, { children: e.icon }),
|
|
127
138
|
e.label
|
|
128
139
|
] }),
|
|
129
|
-
/* @__PURE__ */
|
|
140
|
+
/* @__PURE__ */ c("input", { type: "hidden", value: e.id })
|
|
130
141
|
]
|
|
131
142
|
}
|
|
132
143
|
) }, e.id);
|
|
133
144
|
})
|
|
134
145
|
] });
|
|
135
|
-
},
|
|
136
|
-
const u =
|
|
137
|
-
return l.filter((
|
|
146
|
+
}, Ne = (h, r, l) => {
|
|
147
|
+
const u = h.filter((i) => i.text.toLowerCase().includes(r.toLowerCase()));
|
|
148
|
+
return l.filter((i) => u.find((p) => p.id === i.id));
|
|
138
149
|
};
|
|
139
150
|
export {
|
|
140
|
-
|
|
141
|
-
|
|
151
|
+
be as default,
|
|
152
|
+
Ne as filterOptions
|
|
142
153
|
};
|
|
143
154
|
//# sourceMappingURL=BaseSelectDropdown.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseSelectDropdown.js","sources":["../../../src/components/selects/BaseSelectDropdown.tsx"],"sourcesContent":["import type React from 'react';\nimport { useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { isEmpty } from 'es-toolkit/compat';\nimport { noop } from 'es-toolkit/function';\n\nimport { useDropDirection } from '../../utils/useDropDirection';\nimport { DOWN, scrollItemIntoView, UP } from '../../utils/scrollItemIntoView';\nimport useEffectOnce from '../../hooks/useEffectOnce';\nimport useKey from '../../useKey';\nimport DropdownHeader from './DropdownHeader';\nimport NoItemMessage from './NoItemMessage';\nimport Spinner from '../spinner/Spinner';\n\nconst DATA_ATTRIBUTE_ID = 'data-item-id';\nconst DEFAULT_FOCUSED_ITEM_INDEX = 0;\nconst HIGHLIGHT_CLASS = 'focus';\n\nexport type OptionDOMValue = {\n id: string;\n text: string;\n};\n\nexport type SelectOption = {\n /**\n * Used to identify an option.\n */\n id: string;\n\n /**\n * The option item text.\n */\n label: string | React.ReactNode;\n\n /**\n * Icon to be displayed in front of the label.\n */\n icon?: React.ReactNode;\n\n /**\n * Defines whether the menu item is selected.\n *\n * @default false\n */\n selected?: boolean;\n\n /**\n * Setting \"disabled\" to true will disable the respective item.\n *\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Will treat the given value as a menu header\n */\n header?: boolean;\n};\n\nexport type BaseSelectDropdownProps<T extends SelectOption> = {\n options?: T[];\n isOpen?: boolean;\n isLoading?: boolean;\n updateDOMValues?: (values: OptionDOMValue[] | undefined) => void;\n onOpen?: (hasDropup: boolean) => void;\n onSelect?: (selectedItem: T | undefined) => void;\n onClose?: () => void;\n placeholder?: string | React.ReactNode;\n dropup?: boolean;\n pullRight?: boolean;\n autoDropDirection?: boolean;\n noItemMessage?: string | React.ReactNode;\n focusedItemIndex?: number;\n dropdownClassName?: string;\n keyboardUsed?: boolean;\n useActiveClass?: boolean;\n};\n\nconst BaseSelectDropdown = <T extends SelectOption>(props: BaseSelectDropdownProps<T>) => {\n const {\n isOpen = false,\n isLoading = false,\n updateDOMValues = noop,\n onOpen = noop,\n onSelect = noop,\n onClose = noop,\n options = [],\n autoDropDirection = true,\n dropup = false,\n pullRight = false,\n useActiveClass = false,\n focusedItemIndex: externalFocusedItemIndex,\n keyboardUsed: externalKeyboardUsed,\n noItemMessage,\n dropdownClassName,\n } = props;\n\n const [focusedItemIndex, setFocusedItemIndex] = useState(externalFocusedItemIndex || DEFAULT_FOCUSED_ITEM_INDEX);\n const [keyboardUsed, setKeyboardUsed] = useState(externalKeyboardUsed);\n\n const dropdownMenuRef = useRef<HTMLUListElement>(null);\n\n useEffectOnce(() => {\n // all available items need to be rendered in order to know their DOM value\n // which will be used for filtering in the parent component\n const currentItemDOMValues = updateItemDOMValues();\n updateDOMValues(currentItemDOMValues);\n });\n\n // Overwrite position of dropdown menu in case auto drop is enabled\n const dropDirection = useDropDirection({ pullRight, dropup, autoDropDirection, dropdownMenuRef }, [isOpen]);\n\n // Add or remove the \"dropup\" class from the parent Select/Multiselect component to position\n // the dropdown element accordingly via CSS\n useLayoutEffect(() => {\n if (dropdownMenuRef.current) {\n const parent = dropdownMenuRef.current.parentElement;\n if (dropDirection.dropup) {\n parent?.classList.add('dropup');\n } else {\n parent?.classList.remove('dropup');\n }\n }\n }, [dropDirection, dropdownMenuRef]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>\n useEffect(() => {\n if (dropdownMenuRef.current) {\n updateDOMValues(updateItemDOMValues());\n }\n }, [dropdownMenuRef.current]);\n\n // update internal state for isOpen\n const [previousIsOpen, setPreviousIsOpen] = useState(isOpen);\n if (isOpen && !previousIsOpen) {\n onOpen(dropDirection.dropup);\n setPreviousIsOpen(isOpen);\n } else if (!isOpen && previousIsOpen) {\n onClose();\n setPreviousIsOpen(isOpen);\n }\n\n useKey(event => {\n if (isOpen) {\n switch (event.key) {\n case 'Escape': {\n // close dropdown on esc\n onClose();\n break;\n }\n case 'Tab': {\n // close dropdown on tab\n onClose();\n break;\n }\n case 'Enter': {\n // select item on enter\n selectOptionOnEnter(event);\n break;\n }\n case 'ArrowUp': {\n // prevent scrolling the page when dropdown menu is open\n event.preventDefault();\n\n // select item above on arrow up key\n focusOption(UP);\n scrollItemIntoView(UP, dropdownMenuRef.current, getFocusedOptionNode());\n break;\n }\n case 'ArrowDown': {\n // prevent scrolling the page when dropdown menu is open\n event.preventDefault();\n\n // select item below on arrow down key\n focusOption(DOWN);\n scrollItemIntoView(DOWN, dropdownMenuRef.current, getFocusedOptionNode());\n break;\n }\n default:\n break;\n }\n }\n });\n\n const focusOption = (direction: typeof UP | typeof DOWN) => {\n let nextFocusedItem = 0;\n\n switch (direction) {\n case UP: {\n nextFocusedItem = focusedItemIndex <= 0 ? focusedItemIndex : focusedItemIndex - 1;\n break;\n }\n case DOWN: {\n nextFocusedItem = focusedItemIndex === options.length - 1 ? focusedItemIndex : focusedItemIndex + 1;\n break;\n }\n default:\n break;\n }\n\n // In case the next item index is negative, means outside the bounds of the items,\n // reset it depending on the current direction\n const indexLimit = direction === UP ? options.length - 1 : 0;\n\n setFocusedItemIndex(nextFocusedItem < 0 ? indexLimit : nextFocusedItem);\n setKeyboardUsed(true);\n };\n\n const getOptionNodes = () => {\n const node = dropdownMenuRef.current;\n return node?.getElementsByTagName('a') || [];\n };\n\n const updateItemDOMValues = () => {\n if (dropdownMenuRef.current) {\n const optionNodes = getOptionNodes();\n return [...optionNodes].map(item => {\n return {\n id: item.getAttribute(DATA_ATTRIBUTE_ID),\n text: item.textContent,\n } as OptionDOMValue;\n });\n }\n };\n\n const getFocusedOptionNode = () => {\n const optionNodes = getOptionNodes();\n return [...optionNodes].find(item => item.className.includes(HIGHLIGHT_CLASS));\n };\n\n const selectOptionOnEnter = (event: KeyboardEvent) => {\n event.preventDefault();\n\n // When no filter result was found, avoid selecting anything\n if (isEmpty(options)) {\n return;\n }\n\n const match = getFocusedOptionNode();\n\n if (match) {\n const selectedItem = options.find(option => option.id === match.getAttribute(DATA_ATTRIBUTE_ID));\n onSelect(selectedItem);\n }\n };\n\n const handleOptionChange = (event: React.MouseEvent<HTMLAnchorElement>) => {\n event.preventDefault();\n\n const optionId = event.currentTarget.getElementsByTagName('input')[0].value;\n const selectedItem = options.find(option => option.id === optionId);\n\n onSelect(selectedItem);\n };\n\n const dropdownMenuClasses = classNames('dropdown-menu', dropDirection.pullRight && 'pull-right', dropdownClassName);\n\n // Don't show the dropdown, when no match are found when filtering unless there is a not found message\n if (isEmpty(options)) {\n return <NoItemMessage noItemMessage={noItemMessage} className={dropdownMenuClasses} />;\n }\n\n return (\n <ul className={dropdownMenuClasses} ref={dropdownMenuRef} role='menu'>\n {isLoading && (\n <div className='display-flex justify-content-center padding-10'>\n <Spinner />\n </div>\n )}\n {!isLoading &&\n options.map((option, index) => {\n if (option.header) {\n return <DropdownHeader key={option.id} icon={option.icon} label={option.label} />;\n }\n\n // Show focused style only when keyboard is in use\n const anchorClassNames = classNames(\n keyboardUsed && focusedItemIndex === index ? HIGHLIGHT_CLASS : '',\n option.disabled && 'pointer-events-none',\n 'display-flex align-items-center gap-3'\n );\n\n const wrapperClassNames = classNames(\n option.disabled && 'disabled',\n useActiveClass && option.selected && 'active'\n );\n\n return (\n <li key={option.id} className={wrapperClassNames} role='listitem'>\n <a\n role='menuitem'\n className={anchorClassNames}\n data-item-id={option.id}\n data-item-index={index}\n // Note, we need to assign the click callback only when it's not disabled\n // otherwise the functions is still triggered\n // biome-ignore lint/a11y/useValidAnchor: due to old structure + backwards compatibility\n onClick={option.disabled ? undefined : handleOptionChange}\n >\n <span className='selected-option-dropdown-item'>\n {option.icon && <>{option.icon}</>}\n {option.label}\n </span>\n <input type='hidden' value={option.id} />\n </a>\n </li>\n );\n })}\n </ul>\n );\n};\n\nexport const filterOptions = <T extends SelectOption>(\n itemDOMValues: OptionDOMValue[],\n filterValue: string,\n options: T[]\n) => {\n const filteredDOMValues = itemDOMValues.filter(item => item.text.toLowerCase().includes(filterValue.toLowerCase()));\n\n // Filter the options according to the filtered DOM values and map the IDs since the filter cannot be done\n // on the options itself as they might contain arbitrary components\n return options.filter(option => {\n return filteredDOMValues.find(domValue => domValue.id === option.id);\n });\n};\n\nexport default BaseSelectDropdown;\n"],"names":["DATA_ATTRIBUTE_ID","DEFAULT_FOCUSED_ITEM_INDEX","HIGHLIGHT_CLASS","BaseSelectDropdown","props","isOpen","isLoading","updateDOMValues","noop","onOpen","onSelect","onClose","options","autoDropDirection","dropup","pullRight","useActiveClass","externalFocusedItemIndex","externalKeyboardUsed","noItemMessage","dropdownClassName","focusedItemIndex","setFocusedItemIndex","useState","keyboardUsed","setKeyboardUsed","dropdownMenuRef","useRef","useEffectOnce","currentItemDOMValues","updateItemDOMValues","dropDirection","useDropDirection","useLayoutEffect","parent","useEffect","previousIsOpen","setPreviousIsOpen","useKey","event","selectOptionOnEnter","focusOption","UP","scrollItemIntoView","getFocusedOptionNode","DOWN","direction","nextFocusedItem","indexLimit","getOptionNodes","item","isEmpty","match","selectedItem","option","handleOptionChange","optionId","dropdownMenuClasses","classNames","jsx","NoItemMessage","Spinner","index","DropdownHeader","anchorClassNames","wrapperClassNames","jsxs","Fragment","filterOptions","itemDOMValues","filterValue","filteredDOMValues","domValue"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BaseSelectDropdown.js","sources":["../../../src/components/selects/BaseSelectDropdown.tsx"],"sourcesContent":["import type React from 'react';\nimport { useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { isEmpty } from 'es-toolkit/compat';\nimport { noop } from 'es-toolkit/function';\n\nimport { useDropDirection } from '../../utils/useDropDirection';\nimport { DOWN, scrollItemIntoView, UP } from '../../utils/scrollItemIntoView';\nimport useEffectOnce from '../../hooks/useEffectOnce';\nimport useKey from '../../useKey';\nimport DropdownHeader from './DropdownHeader';\nimport NoItemMessage from './NoItemMessage';\nimport Spinner from '../spinner/Spinner';\n\nconst DATA_ATTRIBUTE_ID = 'data-item-id';\nconst DEFAULT_FOCUSED_ITEM_INDEX = 0;\nconst HIGHLIGHT_CLASS = 'focus';\nconst DEFAULT_SCROLL_DIRECTION = DOWN;\n\nexport type OptionDOMValue = {\n id: string;\n text: string;\n};\n\nexport type SelectOption = {\n /**\n * Used to identify an option.\n */\n id: string;\n\n /**\n * The option item text.\n */\n label: string | React.ReactNode;\n\n /**\n * Icon to be displayed in front of the label.\n */\n icon?: React.ReactNode;\n\n /**\n * Defines whether the menu item is selected.\n *\n * @default false\n */\n selected?: boolean;\n\n /**\n * Setting \"disabled\" to true will disable the respective item.\n *\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Will treat the given value as a menu header\n */\n header?: boolean;\n};\n\nexport type BaseSelectDropdownProps<T extends SelectOption> = {\n options?: T[];\n isOpen?: boolean;\n isLoading?: boolean;\n updateDOMValues?: (values: OptionDOMValue[] | undefined) => void;\n onOpen?: (hasDropup: boolean) => void;\n onSelect?: (selectedItem: T | undefined) => void;\n onClose?: () => void;\n placeholder?: string | React.ReactNode;\n dropup?: boolean;\n pullRight?: boolean;\n autoDropDirection?: boolean;\n noItemMessage?: string | React.ReactNode;\n focusedItemIndex?: number;\n dropdownClassName?: string;\n keyboardUsed?: boolean;\n useActiveClass?: boolean;\n};\n\nconst BaseSelectDropdown = <T extends SelectOption>(props: BaseSelectDropdownProps<T>) => {\n const {\n isOpen = false,\n isLoading = false,\n updateDOMValues = noop,\n onOpen = noop,\n onSelect = noop,\n onClose = noop,\n options = [],\n autoDropDirection = true,\n dropup = false,\n pullRight = false,\n useActiveClass = false,\n focusedItemIndex: externalFocusedItemIndex,\n keyboardUsed: externalKeyboardUsed,\n noItemMessage,\n dropdownClassName,\n } = props;\n\n const [focusedItemIndex, setFocusedItemIndex] = useState(externalFocusedItemIndex || DEFAULT_FOCUSED_ITEM_INDEX);\n const [keyboardUsed, setKeyboardUsed] = useState(externalKeyboardUsed);\n const lastScrollDirectionRef = useRef<typeof UP | typeof DOWN>(DEFAULT_SCROLL_DIRECTION);\n\n const dropdownMenuRef = useRef<HTMLUListElement>(null);\n\n // Reset scroll position whenever the menu opens to avoid retaining old scroll offsets\n useEffect(() => {\n if (isOpen && dropdownMenuRef.current) {\n dropdownMenuRef.current.scrollTop = 0;\n }\n }, [isOpen]);\n\n // Keep internal focus state in sync when the dropdown opens/closes or when parent-controlled values change\n useEffect(() => {\n if (typeof externalFocusedItemIndex === 'number') {\n setFocusedItemIndex(externalFocusedItemIndex);\n }\n\n if (typeof externalKeyboardUsed === 'boolean') {\n setKeyboardUsed(externalKeyboardUsed);\n }\n\n if (!isOpen) {\n lastScrollDirectionRef.current = DEFAULT_SCROLL_DIRECTION;\n }\n }, [externalFocusedItemIndex, externalKeyboardUsed, isOpen]);\n\n useEffectOnce(() => {\n // all available items need to be rendered in order to know their DOM value\n // which will be used for filtering in the parent component\n const currentItemDOMValues = updateItemDOMValues();\n updateDOMValues(currentItemDOMValues);\n });\n\n // Overwrite position of dropdown menu in case auto drop is enabled\n const dropDirection = useDropDirection({ pullRight, dropup, autoDropDirection, dropdownMenuRef }, [isOpen]);\n\n // Add or remove the \"dropup\" class from the parent Select/Multiselect component to position\n // the dropdown element accordingly via CSS\n useLayoutEffect(() => {\n if (dropdownMenuRef.current) {\n const parent = dropdownMenuRef.current.parentElement;\n if (dropDirection.dropup) {\n parent?.classList.add('dropup');\n } else {\n parent?.classList.remove('dropup');\n }\n }\n }, [dropDirection, dropdownMenuRef]);\n\n useEffect(() => {\n if (dropdownMenuRef.current) {\n updateDOMValues(updateItemDOMValues());\n }\n }, [dropdownMenuRef.current]);\n\n // Scroll the focused item into view whenever focus changes\n useEffect(() => {\n if (!isOpen) {\n return;\n }\n\n scrollItemIntoView(\n lastScrollDirectionRef.current || DEFAULT_SCROLL_DIRECTION,\n dropdownMenuRef.current,\n getFocusedOptionNode()\n );\n }, [focusedItemIndex, isOpen]);\n\n // update internal state for isOpen\n const [previousIsOpen, setPreviousIsOpen] = useState(isOpen);\n if (isOpen && !previousIsOpen) {\n onOpen(dropDirection.dropup);\n setPreviousIsOpen(isOpen);\n } else if (!isOpen && previousIsOpen) {\n onClose();\n setPreviousIsOpen(isOpen);\n }\n\n useKey(event => {\n if (isOpen) {\n switch (event.key) {\n case 'Escape': {\n // close dropdown on esc\n onClose();\n break;\n }\n case 'Tab': {\n // close dropdown on tab\n onClose();\n break;\n }\n case 'Enter': {\n // select item on enter\n selectOptionOnEnter(event);\n break;\n }\n case 'ArrowUp': {\n // prevent scrolling the page when dropdown menu is open\n event.preventDefault();\n\n // select item above on arrow up key\n focusOption(UP);\n scrollItemIntoView(UP, dropdownMenuRef.current, getFocusedOptionNode());\n break;\n }\n case 'ArrowDown': {\n // prevent scrolling the page when dropdown menu is open\n event.preventDefault();\n\n // select item below on arrow down key\n focusOption(DOWN);\n scrollItemIntoView(DOWN, dropdownMenuRef.current, getFocusedOptionNode());\n break;\n }\n default:\n break;\n }\n }\n });\n\n const focusOption = (direction: typeof UP | typeof DOWN) => {\n let nextFocusedItem = 0;\n\n switch (direction) {\n case UP: {\n nextFocusedItem = focusedItemIndex <= 0 ? focusedItemIndex : focusedItemIndex - 1;\n break;\n }\n case DOWN: {\n nextFocusedItem = focusedItemIndex === options.length - 1 ? focusedItemIndex : focusedItemIndex + 1;\n break;\n }\n default:\n break;\n }\n\n lastScrollDirectionRef.current = direction;\n\n // In case the next item index is negative, means outside the bounds of the items,\n // reset it depending on the current direction\n const indexLimit = direction === UP ? options.length - 1 : 0;\n\n setFocusedItemIndex(nextFocusedItem < 0 ? indexLimit : nextFocusedItem);\n setKeyboardUsed(true);\n };\n\n const getOptionNodes = () => {\n const node = dropdownMenuRef.current;\n return node?.getElementsByTagName('a') || [];\n };\n\n const updateItemDOMValues = () => {\n if (dropdownMenuRef.current) {\n const optionNodes = getOptionNodes();\n return [...optionNodes].map(item => {\n return {\n id: item.getAttribute(DATA_ATTRIBUTE_ID),\n text: item.textContent,\n } as OptionDOMValue;\n });\n }\n };\n\n const getFocusedOptionNode = () => {\n const optionNodes = getOptionNodes();\n return [...optionNodes].find(item => item.className.includes(HIGHLIGHT_CLASS));\n };\n\n const selectOptionOnEnter = (event: KeyboardEvent) => {\n event.preventDefault();\n\n // When no filter result was found, avoid selecting anything\n if (isEmpty(options)) {\n return;\n }\n\n const match = getFocusedOptionNode();\n\n if (match) {\n const selectedItem = options.find(option => option.id === match.getAttribute(DATA_ATTRIBUTE_ID));\n onSelect(selectedItem);\n }\n };\n\n const handleOptionChange = (event: React.MouseEvent<HTMLAnchorElement>) => {\n event.preventDefault();\n\n const optionId = event.currentTarget.getElementsByTagName('input')[0].value;\n const selectedItem = options.find(option => option.id === optionId);\n\n onSelect(selectedItem);\n };\n\n const dropdownMenuClasses = classNames('dropdown-menu', dropDirection.pullRight && 'pull-right', dropdownClassName);\n\n // Don't show the dropdown, when no match are found when filtering unless there is a not found message\n if (isEmpty(options)) {\n return <NoItemMessage noItemMessage={noItemMessage} className={dropdownMenuClasses} />;\n }\n\n return (\n <ul className={dropdownMenuClasses} ref={dropdownMenuRef} role='menu'>\n {isLoading && (\n <div className='display-flex justify-content-center padding-10'>\n <Spinner />\n </div>\n )}\n {!isLoading &&\n options.map((option, index) => {\n if (option.header) {\n return <DropdownHeader key={option.id} icon={option.icon} label={option.label} />;\n }\n\n // Show focused style only when keyboard is in use\n const anchorClassNames = classNames(\n keyboardUsed && focusedItemIndex === index ? HIGHLIGHT_CLASS : '',\n option.disabled && 'pointer-events-none',\n 'display-flex align-items-center gap-3'\n );\n\n const wrapperClassNames = classNames(\n option.disabled && 'disabled',\n useActiveClass && option.selected && 'active'\n );\n\n return (\n <li key={option.id} className={wrapperClassNames} role='listitem'>\n <a\n role='menuitem'\n className={anchorClassNames}\n data-item-id={option.id}\n data-item-index={index}\n // Note, we need to assign the click callback only when it's not disabled\n // otherwise the functions is still triggered\n onClick={option.disabled ? undefined : handleOptionChange}\n >\n <span className='selected-option-dropdown-item'>\n {option.icon && <>{option.icon}</>}\n {option.label}\n </span>\n <input type='hidden' value={option.id} />\n </a>\n </li>\n );\n })}\n </ul>\n );\n};\n\nexport const filterOptions = <T extends SelectOption>(\n itemDOMValues: OptionDOMValue[],\n filterValue: string,\n options: T[]\n) => {\n const filteredDOMValues = itemDOMValues.filter(item => item.text.toLowerCase().includes(filterValue.toLowerCase()));\n\n // Filter the options according to the filtered DOM values and map the IDs since the filter cannot be done\n // on the options itself as they might contain arbitrary components\n return options.filter(option => {\n return filteredDOMValues.find(domValue => domValue.id === option.id);\n });\n};\n\nexport default BaseSelectDropdown;\n"],"names":["DATA_ATTRIBUTE_ID","DEFAULT_FOCUSED_ITEM_INDEX","HIGHLIGHT_CLASS","DEFAULT_SCROLL_DIRECTION","DOWN","BaseSelectDropdown","props","isOpen","isLoading","updateDOMValues","noop","onOpen","onSelect","onClose","options","autoDropDirection","dropup","pullRight","useActiveClass","externalFocusedItemIndex","externalKeyboardUsed","noItemMessage","dropdownClassName","focusedItemIndex","setFocusedItemIndex","useState","keyboardUsed","setKeyboardUsed","lastScrollDirectionRef","useRef","dropdownMenuRef","useEffect","useEffectOnce","currentItemDOMValues","updateItemDOMValues","dropDirection","useDropDirection","useLayoutEffect","parent","scrollItemIntoView","getFocusedOptionNode","previousIsOpen","setPreviousIsOpen","useKey","event","selectOptionOnEnter","focusOption","UP","direction","nextFocusedItem","indexLimit","getOptionNodes","item","isEmpty","match","selectedItem","option","handleOptionChange","optionId","dropdownMenuClasses","classNames","jsx","NoItemMessage","Spinner","index","DropdownHeader","anchorClassNames","wrapperClassNames","jsxs","Fragment","filterOptions","itemDOMValues","filterValue","filteredDOMValues","domValue"],"mappings":"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"}
|
|
@@ -2,6 +2,10 @@ import { default as React } from 'react';
|
|
|
2
2
|
import { SelectOption } from './BaseSelectDropdown';
|
|
3
3
|
import { WithFeedbackAndAddonProps } from './WithFeedbackAndAddon';
|
|
4
4
|
export type { SelectOption } from './BaseSelectDropdown';
|
|
5
|
+
/**
|
|
6
|
+
* Note: Even with a pre-selected item, when it first opens the dropdown it renders the full options list,
|
|
7
|
+
* not just the selected one. The filter is only usable while the dropdown is open.
|
|
8
|
+
*/
|
|
5
9
|
export type SelectProps<T extends SelectOption> = Omit<WithFeedbackAndAddonProps, 'bsSize'> & {
|
|
6
10
|
/**
|
|
7
11
|
* Passed through as HTML attribute to the toggle button.
|
|
@@ -105,6 +109,7 @@ export type SelectProps<T extends SelectOption> = Omit<WithFeedbackAndAddonProps
|
|
|
105
109
|
useClear?: boolean;
|
|
106
110
|
/**
|
|
107
111
|
* Shows a loading spinner instead of the menu items if set to true.
|
|
112
|
+
*
|
|
108
113
|
* @default false
|
|
109
114
|
*/
|
|
110
115
|
isLoading?: boolean;
|