mirror of
https://github.com/apache/superset.git
synced 2026-04-23 10:04:45 +00:00
chore: Moves OnPasteSelect tests to component's folder (#13338)
This commit is contained in:
committed by
GitHub
parent
f91b4388ba
commit
19c8a62254
322
superset-frontend/src/components/Select/Select.tsx
Normal file
322
superset-frontend/src/components/Select/Select.tsx
Normal file
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { SyntheticEvent, MutableRefObject, ComponentType } from 'react';
|
||||
import { merge } from 'lodash';
|
||||
import BasicSelect, {
|
||||
OptionTypeBase,
|
||||
MultiValueProps,
|
||||
FormatOptionLabelMeta,
|
||||
ValueType,
|
||||
SelectComponentsConfig,
|
||||
components as defaultComponents,
|
||||
createFilter,
|
||||
} from 'react-select';
|
||||
import Async from 'react-select/async';
|
||||
import Creatable from 'react-select/creatable';
|
||||
import AsyncCreatable from 'react-select/async-creatable';
|
||||
import { withAsyncPaginate } from 'react-select-async-paginate';
|
||||
|
||||
import { SelectComponents } from 'react-select/src/components';
|
||||
import {
|
||||
SortableContainer,
|
||||
SortableElement,
|
||||
SortableContainerProps,
|
||||
} from 'react-sortable-hoc';
|
||||
import arrayMove from 'array-move';
|
||||
import { Props as SelectProps } from 'react-select/src/Select';
|
||||
import { useTheme } from '@superset-ui/core';
|
||||
import {
|
||||
WindowedSelectComponentType,
|
||||
WindowedSelectProps,
|
||||
WindowedSelect,
|
||||
WindowedAsyncSelect,
|
||||
WindowedCreatableSelect,
|
||||
WindowedAsyncCreatableSelect,
|
||||
} from './WindowedSelect';
|
||||
import {
|
||||
DEFAULT_CLASS_NAME,
|
||||
DEFAULT_CLASS_NAME_PREFIX,
|
||||
DEFAULT_STYLES,
|
||||
DEFAULT_COMPONENTS,
|
||||
VALUE_LABELED_STYLES,
|
||||
PartialThemeConfig,
|
||||
PartialStylesConfig,
|
||||
SelectComponentsType,
|
||||
InputProps,
|
||||
defaultTheme,
|
||||
} from './styles';
|
||||
import { findValue } from './utils';
|
||||
|
||||
type AnyReactSelect<OptionType extends OptionTypeBase> =
|
||||
| BasicSelect<OptionType>
|
||||
| Async<OptionType>
|
||||
| Creatable<OptionType>
|
||||
| AsyncCreatable<OptionType>;
|
||||
|
||||
export type SupersetStyledSelectProps<
|
||||
OptionType extends OptionTypeBase,
|
||||
T extends WindowedSelectProps<OptionType> = WindowedSelectProps<OptionType>
|
||||
> = T & {
|
||||
// additional props for easier usage or backward compatibility
|
||||
labelKey?: string;
|
||||
valueKey?: string;
|
||||
assistiveText?: string;
|
||||
multi?: boolean;
|
||||
clearable?: boolean;
|
||||
sortable?: boolean;
|
||||
ignoreAccents?: boolean;
|
||||
creatable?: boolean;
|
||||
selectRef?:
|
||||
| React.RefCallback<AnyReactSelect<OptionType>>
|
||||
| MutableRefObject<AnyReactSelect<OptionType>>;
|
||||
getInputValue?: (selectBalue: ValueType<OptionType>) => string | undefined;
|
||||
optionRenderer?: (option: OptionType) => React.ReactNode;
|
||||
valueRenderer?: (option: OptionType) => React.ReactNode;
|
||||
valueRenderedAsLabel?: boolean;
|
||||
// callback for paste event
|
||||
onPaste?: (e: SyntheticEvent) => void;
|
||||
forceOverflow?: boolean;
|
||||
// for simplier theme overrides
|
||||
themeConfig?: PartialThemeConfig;
|
||||
stylesConfig?: PartialStylesConfig;
|
||||
};
|
||||
|
||||
function styled<
|
||||
OptionType extends OptionTypeBase,
|
||||
SelectComponentType extends
|
||||
| WindowedSelectComponentType<OptionType>
|
||||
| ComponentType<
|
||||
SelectProps<OptionType>
|
||||
> = WindowedSelectComponentType<OptionType>
|
||||
>(SelectComponent: SelectComponentType) {
|
||||
type SelectProps = SupersetStyledSelectProps<OptionType>;
|
||||
type Components = SelectComponents<OptionType>;
|
||||
|
||||
const SortableSelectComponent = SortableContainer(SelectComponent, {
|
||||
withRef: true,
|
||||
});
|
||||
|
||||
// default components for the given OptionType
|
||||
const supersetDefaultComponents: SelectComponentsConfig<OptionType> = DEFAULT_COMPONENTS;
|
||||
|
||||
const getSortableMultiValue = (MultiValue: Components['MultiValue']) =>
|
||||
SortableElement((props: MultiValueProps<OptionType>) => {
|
||||
const onMouseDown = (e: SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
const innerProps = { onMouseDown };
|
||||
return <MultiValue {...props} innerProps={innerProps} />;
|
||||
});
|
||||
|
||||
/**
|
||||
* Superset styled `Select` component. Apply Superset themed stylesheets and
|
||||
* consolidate props API for backward compatibility with react-select v1.
|
||||
*/
|
||||
function StyledSelect(selectProps: SelectProps) {
|
||||
let stateManager: AnyReactSelect<OptionType>; // reference to react-select StateManager
|
||||
const {
|
||||
// additional props for Superset Select
|
||||
selectRef,
|
||||
labelKey = 'label',
|
||||
valueKey = 'value',
|
||||
themeConfig,
|
||||
stylesConfig = {},
|
||||
optionRenderer,
|
||||
valueRenderer,
|
||||
// whether value is rendered as `option-label` in input,
|
||||
// useful for AdhocMetric and AdhocFilter
|
||||
valueRenderedAsLabel: valueRenderedAsLabel_,
|
||||
onPaste,
|
||||
multi = false, // same as `isMulti`, used for backward compatibility
|
||||
clearable, // same as `isClearable`
|
||||
sortable = true, // whether to enable drag & drop sorting
|
||||
forceOverflow, // whether the dropdown should be forcefully overflowing
|
||||
|
||||
// react-select props
|
||||
className = DEFAULT_CLASS_NAME,
|
||||
classNamePrefix = DEFAULT_CLASS_NAME_PREFIX,
|
||||
options,
|
||||
value: value_,
|
||||
components: components_,
|
||||
isMulti: isMulti_,
|
||||
isClearable: isClearable_,
|
||||
minMenuHeight = 100, // apply different defaults
|
||||
maxMenuHeight = 220,
|
||||
filterOption,
|
||||
ignoreAccents = false, // default is `true`, but it is slow
|
||||
|
||||
getOptionValue = option =>
|
||||
typeof option === 'string' ? option : option[valueKey],
|
||||
|
||||
getOptionLabel = option =>
|
||||
typeof option === 'string'
|
||||
? option
|
||||
: option[labelKey] || option[valueKey],
|
||||
|
||||
formatOptionLabel = (
|
||||
option: OptionType,
|
||||
{ context }: FormatOptionLabelMeta<OptionType>,
|
||||
) => {
|
||||
if (context === 'value') {
|
||||
return valueRenderer ? valueRenderer(option) : getOptionLabel(option);
|
||||
}
|
||||
return optionRenderer ? optionRenderer(option) : getOptionLabel(option);
|
||||
},
|
||||
|
||||
...restProps
|
||||
} = selectProps;
|
||||
|
||||
// `value` may be rendered values (strings), we want option objects
|
||||
const value: OptionType[] = findValue(value_, options || [], valueKey);
|
||||
|
||||
// Add backward compability to v1 API
|
||||
const isMulti = isMulti_ === undefined ? multi : isMulti_;
|
||||
const isClearable = isClearable_ === undefined ? clearable : isClearable_;
|
||||
|
||||
// Sort is only applied when there are multiple selected values
|
||||
const shouldAllowSort =
|
||||
isMulti && sortable && Array.isArray(value) && value.length > 1;
|
||||
|
||||
const MaybeSortableSelect = shouldAllowSort
|
||||
? SortableSelectComponent
|
||||
: SelectComponent;
|
||||
const components = { ...supersetDefaultComponents, ...components_ };
|
||||
|
||||
// Make multi-select sortable as per https://react-select.netlify.app/advanced
|
||||
if (shouldAllowSort) {
|
||||
components.MultiValue = getSortableMultiValue(
|
||||
components.MultiValue || defaultComponents.MultiValue,
|
||||
);
|
||||
|
||||
const sortableContainerProps: Partial<SortableContainerProps> = {
|
||||
getHelperDimensions: ({ node }) => node.getBoundingClientRect(),
|
||||
axis: 'xy',
|
||||
onSortEnd: ({ oldIndex, newIndex }) => {
|
||||
const newValue = arrayMove(value, oldIndex, newIndex);
|
||||
if (restProps.onChange) {
|
||||
restProps.onChange(newValue, { action: 'set-value' });
|
||||
}
|
||||
},
|
||||
distance: 4,
|
||||
};
|
||||
Object.assign(restProps, sortableContainerProps);
|
||||
}
|
||||
|
||||
// When values are rendered as labels, adjust valueContainer padding
|
||||
const valueRenderedAsLabel =
|
||||
valueRenderedAsLabel_ === undefined ? isMulti : valueRenderedAsLabel_;
|
||||
if (valueRenderedAsLabel && !stylesConfig.valueContainer) {
|
||||
Object.assign(stylesConfig, VALUE_LABELED_STYLES);
|
||||
}
|
||||
|
||||
// Handle onPaste event
|
||||
if (onPaste) {
|
||||
const Input =
|
||||
(components.Input as SelectComponentsType['Input']) ||
|
||||
(defaultComponents.Input as SelectComponentsType['Input']);
|
||||
components.Input = (props: InputProps) => (
|
||||
<Input {...props} onPaste={onPaste} />
|
||||
);
|
||||
}
|
||||
// for CreaTable
|
||||
if (SelectComponent === WindowedCreatableSelect) {
|
||||
restProps.getNewOptionData = (inputValue: string, label: string) => ({
|
||||
label: label || inputValue,
|
||||
[valueKey]: inputValue,
|
||||
isNew: true,
|
||||
});
|
||||
}
|
||||
|
||||
// handle forcing dropdown overflow
|
||||
// use only when setting overflow:visible isn't possible on the container element
|
||||
if (forceOverflow) {
|
||||
Object.assign(restProps, {
|
||||
closeMenuOnScroll: (e: Event) => {
|
||||
const target = e.target as HTMLElement;
|
||||
return target && !target.classList?.contains('Select__menu-list');
|
||||
},
|
||||
menuPosition: 'fixed',
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure always return StateManager for the refs.
|
||||
// To get the real `Select` component, keep tap into `obj.select`:
|
||||
// - for normal <Select /> component: StateManager -> Select,
|
||||
// - for <Creatable />: StateManager -> Creatable -> Select
|
||||
const setRef = (instance: any) => {
|
||||
stateManager =
|
||||
shouldAllowSort && instance && 'refs' in instance
|
||||
? instance.refs.wrappedInstance // obtain StateManger from SortableContainer
|
||||
: instance;
|
||||
if (typeof selectRef === 'function') {
|
||||
selectRef(stateManager);
|
||||
} else if (selectRef && 'current' in selectRef) {
|
||||
selectRef.current = stateManager;
|
||||
}
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<MaybeSortableSelect
|
||||
ref={setRef}
|
||||
className={className}
|
||||
classNamePrefix={classNamePrefix}
|
||||
isMulti={isMulti}
|
||||
isClearable={isClearable}
|
||||
options={options}
|
||||
value={value}
|
||||
minMenuHeight={minMenuHeight}
|
||||
maxMenuHeight={maxMenuHeight}
|
||||
filterOption={
|
||||
// filterOption may be NULL
|
||||
filterOption !== undefined
|
||||
? filterOption
|
||||
: createFilter({ ignoreAccents })
|
||||
}
|
||||
styles={{ ...DEFAULT_STYLES, ...stylesConfig } as SelectProps['styles']}
|
||||
// merge default theme from `react-select`, default theme for Superset,
|
||||
// and the theme from props.
|
||||
theme={reactSelectTheme =>
|
||||
merge(reactSelectTheme, defaultTheme(theme), themeConfig)
|
||||
}
|
||||
formatOptionLabel={formatOptionLabel}
|
||||
getOptionLabel={getOptionLabel}
|
||||
getOptionValue={getOptionValue}
|
||||
components={components}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// React.memo makes sure the component does no rerender given the same props
|
||||
return React.memo(StyledSelect);
|
||||
}
|
||||
|
||||
export const Select = styled(WindowedSelect);
|
||||
export const AsyncSelect = styled(WindowedAsyncSelect);
|
||||
export const CreatableSelect = styled(WindowedCreatableSelect);
|
||||
export const AsyncCreatableSelect = styled(WindowedAsyncCreatableSelect);
|
||||
export const PaginatedSelect = withAsyncPaginate(
|
||||
styled<OptionTypeBase, ComponentType<SelectProps<OptionTypeBase>>>(
|
||||
BasicSelect,
|
||||
),
|
||||
);
|
||||
export default Select;
|
||||
Reference in New Issue
Block a user