mirror of
https://github.com/apache/superset.git
synced 2026-05-12 19:35:17 +00:00
chore(superset-ui-core): forward-compat fixes for TypeScript 6.0
Scoped to packages/superset-ui-core. All changes compile cleanly on
TypeScript 5.4.5 (current CI) and eliminate TS 6.0 errors in this package.
Key fixes:
- forwardRef callbacks use ForwardedRef<T> instead of RefObject<T>
(AsyncEsmComponent, DropdownContainer, ModalTrigger). ModalTrigger
also narrows object-ref access with a typeof-function guard.
- StatefulChart: cast onRenderFailure to the shared HandlerFunction type
so the stricter 6.0 inference matches the SuperChart prop.
- AntdEnhanced: drop the wider IconType.component from the spread so
{...rest} can no longer override the explicit <BaseIconComponent
component={...}> binding under 6.0's stricter JSX attribute checking.
- Select / AsyncSelect: widen antd sorter/filter/handler call-sites with
targeted `as unknown as ...` casts against antd's BaseOptionType /
DefaultOptionType / SelectHandler shapes. Behaviour unchanged; only
the type boundary moves.
- VirtualTable: accept width?: number from react-resize-detector's 6.0
callback signature, defaulting missing width to 0.
- TableCollection: cast columns / rowClassName / onChange to antd's
generic ColumnsType<object> and TableProps<object> variants.
- TimezoneSelector: cast our comparator to antd's LabeledValue-based
sorter signature; our comparator only reads fields that always exist
on TimezoneOption, so the broader shape is safe at runtime.
- connection/types: widen FetchRetryOptions retryDelay/retryOn to
accept `Error | null` / `Response | null`, matching fetch-retry.
- fetchTimeRange: rename catch variable and narrow it via the
getClientErrorObject parameter type, handling TS 6.0's `unknown`
default for caught values.
- lruCache: guard Map iterator .value before Map#delete (TS 6.0 types
IteratorResult.value as `string | undefined`).
- InteractiveTableUtils: initialise columnRef to null for strict
property initialization.
- types/assets.d.ts: add `declare module '*.css'` for CSS side-effect
imports under 6.0's stricter module resolution.
Part of the TypeScript 5.4 -> 6.0 migration, split per-package to keep
reviews small. No runtime behaviour changes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
||||
RequestConfig,
|
||||
getClientErrorObject,
|
||||
} from '../..';
|
||||
import type { HandlerFunction } from '../types/Base';
|
||||
import { Loading } from '../../components/Loading';
|
||||
import ChartClient from '../clients/ChartClient';
|
||||
import getChartBuildQueryRegistry from '../registries/ChartBuildQueryRegistrySingleton';
|
||||
@@ -482,7 +483,7 @@ export default function StatefulChart(props: StatefulChartProps) {
|
||||
enableNoResults={enableNoResults}
|
||||
noResults={NoDataComponent && <NoDataComponent />}
|
||||
onRenderSuccess={onRenderSuccess}
|
||||
onRenderFailure={onRenderFailure}
|
||||
onRenderFailure={onRenderFailure as HandlerFunction | undefined}
|
||||
hooks={hooks}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import {
|
||||
useEffect,
|
||||
useState,
|
||||
RefObject,
|
||||
ForwardedRef,
|
||||
forwardRef,
|
||||
ComponentType,
|
||||
ForwardRefExoticComponent,
|
||||
@@ -101,7 +101,7 @@ export function AsyncEsmComponent<
|
||||
|
||||
const AsyncComponent: AsyncComponent = forwardRef(function AsyncComponent(
|
||||
props: FullProps,
|
||||
ref: RefObject<ComponentType<FullProps>>,
|
||||
ref: ForwardedRef<ComponentType<FullProps>>,
|
||||
) {
|
||||
const [loaded, setLoaded] = useState(component !== undefined);
|
||||
useEffect(() => {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import {
|
||||
cloneElement,
|
||||
forwardRef,
|
||||
RefObject,
|
||||
ForwardedRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useLayoutEffect,
|
||||
@@ -54,7 +54,7 @@ export const DropdownContainer = forwardRef(
|
||||
forceRender,
|
||||
style,
|
||||
}: DropdownContainerProps,
|
||||
outerRef: RefObject<DropdownRef>,
|
||||
outerRef: ForwardedRef<DropdownRef>,
|
||||
) => {
|
||||
const theme = useTheme();
|
||||
const { ref, width = 0 } = useResizeDetector<HTMLDivElement>();
|
||||
|
||||
@@ -326,11 +326,17 @@ export const antdEnhancedIcons: Record<
|
||||
.filter(key => !EXCLUDED_ICONS.some(excluded => key.includes(excluded)))
|
||||
.reduce(
|
||||
(acc, key) => {
|
||||
acc[key as AntdIconNames] = (props: IconType) => (
|
||||
acc[key as AntdIconNames] = ({
|
||||
// Forward-compat: TS 6.0 treats IconComponentProps.component as a
|
||||
// different shape than BaseIconProps.component; strip it from spread
|
||||
// props so our own component binding is authoritative.
|
||||
component: _ignoredComponent,
|
||||
...rest
|
||||
}: IconType) => (
|
||||
<BaseIconComponent
|
||||
component={AntdIcons[key as AntdIconNames]}
|
||||
fileName={key}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
return acc;
|
||||
|
||||
@@ -16,7 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { forwardRef, useState, ReactNode, MouseEvent } from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
ForwardedRef,
|
||||
useState,
|
||||
ReactNode,
|
||||
MouseEvent,
|
||||
} from 'react';
|
||||
|
||||
import { Button } from '../Button';
|
||||
import { Modal } from '../Modal';
|
||||
@@ -51,7 +57,7 @@ export interface ModalTriggerRef {
|
||||
}
|
||||
|
||||
export const ModalTrigger = forwardRef(
|
||||
(props: ModalTriggerProps, ref: ModalTriggerRef | null) => {
|
||||
(props: ModalTriggerProps, ref: ForwardedRef<ModalTriggerRef['current']>) => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const {
|
||||
beforeOpen = () => {},
|
||||
@@ -84,7 +90,7 @@ export const ModalTrigger = forwardRef(
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
if (ref) {
|
||||
if (ref && typeof ref !== 'function') {
|
||||
ref.current = { close, open, showModal }; // eslint-disable-line
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
import {
|
||||
forwardRef,
|
||||
ForwardedRef,
|
||||
FocusEvent,
|
||||
ReactElement,
|
||||
RefObject,
|
||||
@@ -38,6 +39,8 @@ import {
|
||||
getClientErrorObject,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
BaseOptionType,
|
||||
DefaultOptionType,
|
||||
LabeledValue as AntdLabeledValue,
|
||||
RefSelectProps,
|
||||
} from 'antd/es/select';
|
||||
@@ -146,7 +149,7 @@ const AsyncSelect = forwardRef(
|
||||
maxTagCount: propsMaxTagCount,
|
||||
...props
|
||||
}: AsyncSelectProps,
|
||||
ref: RefObject<AsyncSelectRef>,
|
||||
ref: ForwardedRef<AsyncSelectRef>,
|
||||
) => {
|
||||
const isSingleMode = mode === 'single';
|
||||
const [selectValue, setSelectValue] = useState(value);
|
||||
@@ -307,7 +310,14 @@ const AsyncSelect = forwardRef(
|
||||
mergedData = prevOptions
|
||||
.filter(previousOption => !dataValues.has(previousOption.value))
|
||||
.concat(data)
|
||||
.sort(sortComparatorForNoSearch);
|
||||
// Forward-compat: TS 6.0 infers stricter antd option types; widen
|
||||
// the comparator to accept the broader DefaultOptionType shape.
|
||||
.sort(
|
||||
sortComparatorForNoSearch as unknown as (
|
||||
a: BaseOptionType | DefaultOptionType,
|
||||
b: BaseOptionType | DefaultOptionType,
|
||||
) => number,
|
||||
);
|
||||
return mergedData;
|
||||
});
|
||||
}
|
||||
@@ -435,7 +445,13 @@ const AsyncSelect = forwardRef(
|
||||
if (isDropdownVisible && !inputValue && selectOptions.length > 1) {
|
||||
const sortedOptions = selectOptions
|
||||
.slice()
|
||||
.sort(sortComparatorForNoSearch);
|
||||
// Forward-compat: see note in mergeData above.
|
||||
.sort(
|
||||
sortComparatorForNoSearch as unknown as (
|
||||
a: BaseOptionType | DefaultOptionType,
|
||||
b: BaseOptionType | DefaultOptionType,
|
||||
) => number,
|
||||
);
|
||||
if (!isEqual(sortedOptions, selectOptions)) {
|
||||
setSelectOptions(sortedOptions);
|
||||
}
|
||||
@@ -533,14 +549,16 @@ const AsyncSelect = forwardRef(
|
||||
|
||||
const clearCache = () => fetchedQueries.current.clear();
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
...(ref.current as RefSelectProps),
|
||||
useImperativeHandle(ref, () => {
|
||||
const current =
|
||||
ref && typeof ref !== 'function' && ref.current
|
||||
? (ref.current as RefSelectProps)
|
||||
: ({} as RefSelectProps);
|
||||
return {
|
||||
...current,
|
||||
clearCache,
|
||||
}),
|
||||
[ref],
|
||||
);
|
||||
};
|
||||
}, [ref]);
|
||||
|
||||
const getPastedTextValue = useCallback(
|
||||
async (text: string) => {
|
||||
@@ -606,8 +624,21 @@ const AsyncSelect = forwardRef(
|
||||
data-test={ariaLabel || name}
|
||||
autoClearSearchValue={autoClearSearchValue}
|
||||
popupRender={popupRender}
|
||||
filterOption={handleFilterOption}
|
||||
filterSort={sortComparatorWithSearch}
|
||||
// Forward-compat: TS 6.0 infers stricter antd option types; local
|
||||
// helpers typed against AntdLabeledValue are behaviorally compatible
|
||||
// with the broader BaseOptionType/DefaultOptionType antd expects.
|
||||
filterOption={
|
||||
handleFilterOption as unknown as (
|
||||
search: string,
|
||||
option?: BaseOptionType | DefaultOptionType,
|
||||
) => boolean
|
||||
}
|
||||
filterSort={
|
||||
sortComparatorWithSearch as unknown as (
|
||||
a: BaseOptionType | DefaultOptionType,
|
||||
b: BaseOptionType | DefaultOptionType,
|
||||
) => number
|
||||
}
|
||||
getPopupContainer={
|
||||
getPopupContainer || (triggerNode => triggerNode.parentNode)
|
||||
}
|
||||
@@ -617,13 +648,26 @@ const AsyncSelect = forwardRef(
|
||||
mode={mappedMode}
|
||||
notFoundContent={isLoading ? t('Loading...') : notFoundContent}
|
||||
onBlur={handleOnBlur}
|
||||
onDeselect={handleOnDeselect}
|
||||
// Forward-compat: TS 6.0 narrows the Select value type handed to
|
||||
// SelectHandler; our local handlers already accept the broader union.
|
||||
onDeselect={
|
||||
handleOnDeselect as unknown as (
|
||||
value: unknown,
|
||||
option: BaseOptionType | DefaultOptionType,
|
||||
) => void
|
||||
}
|
||||
onOpenChange={handleOnDropdownVisibleChange}
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error antd Select does not declare onPaste on its prop
|
||||
// surface, but the underlying input accepts it and we rely on that.
|
||||
onPaste={onPaste}
|
||||
onPopupScroll={handlePagination}
|
||||
onSearch={showSearch ? handleOnSearch : undefined}
|
||||
onSelect={handleOnSelect}
|
||||
onSelect={
|
||||
handleOnSelect as unknown as (
|
||||
value: unknown,
|
||||
option: BaseOptionType | DefaultOptionType,
|
||||
) => void
|
||||
}
|
||||
onClear={handleClear}
|
||||
options={fullSelectOptions}
|
||||
optionRender={option => <Space>{option.label || option.value}</Space>}
|
||||
|
||||
@@ -34,6 +34,8 @@ import { t } from '@apache-superset/core/translation';
|
||||
import { ensureIsArray, formatNumber, usePrevious } from '@superset-ui/core';
|
||||
import { Constants } from '@superset-ui/core/components';
|
||||
import {
|
||||
BaseOptionType,
|
||||
DefaultOptionType,
|
||||
LabeledValue as AntdLabeledValue,
|
||||
RefSelectProps,
|
||||
} from 'antd/es/select';
|
||||
@@ -211,7 +213,17 @@ const Select = forwardRef(
|
||||
);
|
||||
|
||||
const initialOptionsSorted = useMemo(
|
||||
() => initialOptions.slice().sort(sortSelectedFirst),
|
||||
() =>
|
||||
initialOptions
|
||||
.slice()
|
||||
// Forward-compat: TS 6.0 infers stricter antd option types; widen the
|
||||
// comparator to accept the broader DefaultOptionType shape.
|
||||
.sort(
|
||||
sortSelectedFirst as unknown as (
|
||||
a: BaseOptionType | DefaultOptionType,
|
||||
b: BaseOptionType | DefaultOptionType,
|
||||
) => number,
|
||||
),
|
||||
[initialOptions, sortSelectedFirst],
|
||||
);
|
||||
|
||||
@@ -239,7 +251,17 @@ const Select = forwardRef(
|
||||
missingValues.length > 0
|
||||
? missingValues.concat(selectOptions)
|
||||
: selectOptions;
|
||||
return result.slice().sort(sortSelectedFirst);
|
||||
return (
|
||||
result
|
||||
.slice()
|
||||
// Forward-compat: see note on initialOptionsSorted.
|
||||
.sort(
|
||||
sortSelectedFirst as unknown as (
|
||||
a: BaseOptionType | DefaultOptionType,
|
||||
b: BaseOptionType | DefaultOptionType,
|
||||
) => number,
|
||||
)
|
||||
);
|
||||
}, [selectOptions, selectValue, sortSelectedFirst]);
|
||||
|
||||
const enabledOptions = useMemo(
|
||||
@@ -751,8 +773,21 @@ const Select = forwardRef(
|
||||
data-test={ariaLabel || name}
|
||||
autoClearSearchValue={autoClearSearchValue}
|
||||
popupRender={popupRender}
|
||||
filterOption={handleFilterOption}
|
||||
filterSort={sortComparatorWithSearch}
|
||||
// Forward-compat: TS 6.0 infers stricter antd option types; local
|
||||
// helpers typed against AntdLabeledValue are behaviorally compatible
|
||||
// with the broader BaseOptionType/DefaultOptionType antd expects.
|
||||
filterOption={
|
||||
handleFilterOption as unknown as (
|
||||
search: string,
|
||||
option?: BaseOptionType | DefaultOptionType,
|
||||
) => boolean
|
||||
}
|
||||
filterSort={
|
||||
sortComparatorWithSearch as unknown as (
|
||||
a: BaseOptionType | DefaultOptionType,
|
||||
b: BaseOptionType | DefaultOptionType,
|
||||
) => number
|
||||
}
|
||||
getPopupContainer={
|
||||
getPopupContainer || (triggerNode => triggerNode.parentNode)
|
||||
}
|
||||
@@ -763,13 +798,26 @@ const Select = forwardRef(
|
||||
mode={mappedMode}
|
||||
notFoundContent={isLoading ? t('Loading...') : notFoundContent}
|
||||
onBlur={handleOnBlur}
|
||||
onDeselect={handleOnDeselect}
|
||||
// Forward-compat: TS 6.0 narrows the Select value type handed to
|
||||
// SelectHandler; our local handlers already accept the broader union.
|
||||
onDeselect={
|
||||
handleOnDeselect as unknown as (
|
||||
value: unknown,
|
||||
option: BaseOptionType | DefaultOptionType,
|
||||
) => void
|
||||
}
|
||||
onOpenChange={handleOnDropdownVisibleChange}
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error antd Select does not declare onPaste on its prop
|
||||
// surface, but the underlying input accepts it and we rely on that.
|
||||
onPaste={onPaste}
|
||||
onPopupScroll={undefined}
|
||||
onSearch={shouldShowSearch ? handleOnSearch : undefined}
|
||||
onSelect={handleOnSelect}
|
||||
onSelect={
|
||||
handleOnSelect as unknown as (
|
||||
value: unknown,
|
||||
option: BaseOptionType | DefaultOptionType,
|
||||
) => void
|
||||
}
|
||||
onClear={handleClear}
|
||||
placeholder={placeholder}
|
||||
tokenSeparators={tokenSeparators}
|
||||
|
||||
@@ -84,8 +84,8 @@ const VirtualTable = <RecordType extends object>(
|
||||
allowHTML = false,
|
||||
} = props;
|
||||
const [tableWidth, setTableWidth] = useState<number>(0);
|
||||
const onResize = useCallback((width: number) => {
|
||||
setTableWidth(width);
|
||||
const onResize = useCallback((width?: number) => {
|
||||
setTableWidth(width ?? 0);
|
||||
}, []);
|
||||
const { ref } = useResizeDetector({ onResize });
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -29,7 +29,7 @@ interface IInteractiveColumn extends HTMLElement {
|
||||
export default class InteractiveTableUtils {
|
||||
tableRef: HTMLTableElement | null;
|
||||
|
||||
columnRef: IInteractiveColumn | null;
|
||||
columnRef: IInteractiveColumn | null = null;
|
||||
|
||||
setDerivedColumns: Function;
|
||||
|
||||
|
||||
@@ -27,7 +27,12 @@ import {
|
||||
} from 'react-table';
|
||||
import { styled } from '@apache-superset/core/theme';
|
||||
import { Table, TableSize } from '@superset-ui/core/components/Table';
|
||||
import { TableRowSelection, SorterResult } from 'antd/es/table/interface';
|
||||
import {
|
||||
ColumnsType,
|
||||
TableRowSelection,
|
||||
SorterResult,
|
||||
} from 'antd/es/table/interface';
|
||||
import type { TableProps } from 'antd/es/table';
|
||||
import { mapColumns, mapRows } from './utils';
|
||||
|
||||
interface TableCollectionProps<T extends object> {
|
||||
@@ -290,7 +295,10 @@ function TableCollection<T extends object>({
|
||||
<StyledTable
|
||||
loading={loading}
|
||||
sticky={sticky ?? false}
|
||||
columns={mappedColumns}
|
||||
// Forward-compat: TS 6.0 tightens antd Table's generic inference so our
|
||||
// typed-against-react-table mapped columns must be widened to the antd
|
||||
// ColumnsType<object> surface the Table expects here.
|
||||
columns={mappedColumns as unknown as ColumnsType<object>}
|
||||
data={mappedRows}
|
||||
size={size}
|
||||
data-test="listview-table"
|
||||
@@ -303,7 +311,9 @@ function TableCollection<T extends object>({
|
||||
sortDirections={['ascend', 'descend', 'ascend']}
|
||||
isPaginationSticky={isPaginationSticky}
|
||||
showRowCount={showRowCount}
|
||||
rowClassName={getRowClassName}
|
||||
rowClassName={
|
||||
getRowClassName as unknown as TableProps<object>['rowClassName']
|
||||
}
|
||||
components={{
|
||||
header: {
|
||||
cell: (props: HTMLAttributes<HTMLTableCellElement>) => (
|
||||
@@ -319,7 +329,7 @@ function TableCollection<T extends object>({
|
||||
),
|
||||
},
|
||||
}}
|
||||
onChange={handleTableChange}
|
||||
onChange={handleTableChange as unknown as TableProps<object>['onChange']}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
getOffsetKey,
|
||||
DEFAULT_TIMEZONE,
|
||||
} from './TimezoneOptionsCache';
|
||||
import type { LabeledValue } from 'antd/es/select';
|
||||
import type { TimezoneOption } from './types';
|
||||
|
||||
// Import dayjs plugin types for TypeScript support
|
||||
@@ -156,7 +157,16 @@ export default function TimezoneSelector({
|
||||
onOpenChange={handleOpenChange}
|
||||
value={selectValue}
|
||||
options={timezoneOptions || []}
|
||||
sortComparator={sortComparator}
|
||||
// Forward-compat: TS 6.0 resolves sortComparator against antd's
|
||||
// LabeledValue; our comparator only reads properties that always exist
|
||||
// on TimezoneOption, so the broader shape is safe at runtime.
|
||||
sortComparator={
|
||||
sortComparator as unknown as (
|
||||
a: LabeledValue,
|
||||
b: LabeledValue,
|
||||
search?: string,
|
||||
) => number
|
||||
}
|
||||
loading={isLoadingOptions}
|
||||
placeholder={isLoadingOptions ? t('Loading timezones...') : placeholder}
|
||||
{...{ placement: 'topLeft', ...rest }}
|
||||
|
||||
@@ -26,10 +26,18 @@ export type FetchRetryOptions = {
|
||||
retries?: number;
|
||||
retryDelay?:
|
||||
| number
|
||||
| ((attempt: number, error: Error, response: Response) => number);
|
||||
| ((
|
||||
attempt: number,
|
||||
error: Error | null,
|
||||
response: Response | null,
|
||||
) => number);
|
||||
retryOn?:
|
||||
| number[]
|
||||
| ((attempt: number, error: Error, response: Response) => boolean);
|
||||
| ((
|
||||
attempt: number,
|
||||
error: Error | null,
|
||||
response: Response | null,
|
||||
) => boolean);
|
||||
};
|
||||
export type Headers = { [k: string]: string };
|
||||
export type Host = string;
|
||||
|
||||
@@ -103,10 +103,16 @@ export const fetchTimeRange = async (
|
||||
),
|
||||
),
|
||||
};
|
||||
} catch (response) {
|
||||
} catch (caught) {
|
||||
// Forward-compat: TS 6.0 types caught values as `unknown`; cast to the
|
||||
// shape getClientErrorObject accepts and narrow for statusText access.
|
||||
const response = caught as Parameters<typeof getClientErrorObject>[0];
|
||||
const clientError = await getClientErrorObject(response);
|
||||
return {
|
||||
error: clientError.message || clientError.error || response.statusText,
|
||||
error:
|
||||
clientError.message ||
|
||||
clientError.error ||
|
||||
(response as { statusText?: string }).statusText,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,7 +55,12 @@ class LRUCache<T> {
|
||||
throw new TypeError('The LRUCache key must be string.');
|
||||
}
|
||||
if (this.cache.size >= this.capacity) {
|
||||
this.cache.delete(this.cache.keys().next().value);
|
||||
// Forward-compat: TS 6.0 types IteratorResult.value as `string | undefined`
|
||||
// when not explicitly checked; guard before passing to Map#delete.
|
||||
const oldestKey = this.cache.keys().next().value;
|
||||
if (oldestKey !== undefined) {
|
||||
this.cache.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
|
||||
@@ -21,3 +21,4 @@ declare module '*.svg';
|
||||
declare module '*.png';
|
||||
declare module '*.jpg';
|
||||
declare module '*.jpeg';
|
||||
declare module '*.css';
|
||||
|
||||
Reference in New Issue
Block a user