From 929b5a0b246bcfb672f19e6fb6ba20edd3cc0d3c Mon Sep 17 00:00:00 2001 From: Tevinch Date: Mon, 28 Sep 2026 19:07:07 +0800 Subject: [PATCH] fix: allow lazy loading Draggable and DraggableCore --- lib/Draggable.tsx | 10 ++++++---- lib/DraggableCore.tsx | 6 +++--- scripts/verify-build.cjs | 13 +++++++++---- typings/test.tsx | 13 +++++++++++++ typings/tsconfig.react18.json | 1 + 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/lib/Draggable.tsx b/lib/Draggable.tsx index 3c434219..d8e6df0e 100644 --- a/lib/Draggable.tsx +++ b/lib/Draggable.tsx @@ -52,19 +52,19 @@ class Draggable extends React.Component, DraggableState> static displayName?: string = 'Draggable'; // Both the annotation and the `?` are load-bearing: - // - The index-signature annotation stops tsc from inferring the + // - The React.ComponentClass annotation stops tsc from inferring the // PropTypes.Requireable<...> types and emitting `import PropTypes from // 'prop-types'` into the generated public .d.ts, which would force consumers // to install @types/prop-types (the v4.5.0 hand-written typings had none). // - The `?` keeps `propTypes` from being a *required* member of the public // type. React <= 18's JSX LibraryManagedAttributes only consults a // component's `propTypes` when it is required (`C extends {propTypes: ...}`); - // when it does, this index-signature `propTypes` makes `defaultProps` stop + // when it does, a broadly typed `propTypes` can make `defaultProps` stop // marking props optional, so consumers are forced to pass every prop. // Optional dodges that branch; React 19 ignores `propTypes` entirely. The // typings/tsconfig.react18.json check guards against a regression here. // Do not remove. See lib/DraggableCore.tsx for the same guard. - static propTypes?: {[key: string]: unknown} = { + static propTypes?: React.ComponentClass['propTypes'] = { // Accepts all props accepts. ...DraggableCore.propTypes, @@ -200,7 +200,7 @@ class Draggable extends React.Component, DraggableState> // React 16.3+ // Arity (props, state) - static getDerivedStateFromProps({position}: DraggableProps, {prevPropsPosition}: DraggableState): Partial | null { + static getDerivedStateFromProps({position}: Partial, {prevPropsPosition}: DraggableState): Partial | null { // Set x/y if a new position is provided in props that is different than the previous. if ( position && @@ -218,6 +218,8 @@ class Draggable extends React.Component, DraggableState> return null; } + // React resolves defaultProps before construction; expose the consumer shape. + constructor(props: Partial); constructor(props: DraggableProps) { super(props); diff --git a/lib/DraggableCore.tsx b/lib/DraggableCore.tsx index c7f80043..a53b54e5 100644 --- a/lib/DraggableCore.tsx +++ b/lib/DraggableCore.tsx @@ -75,19 +75,19 @@ export default class DraggableCore extends React.Component types and emitting `import PropTypes from // 'prop-types'` into the generated public .d.ts, which would force consumers // to install @types/prop-types (the v4.5.0 hand-written typings had none). // - The `?` keeps `propTypes` from being a *required* member of the public // type. React <= 18's JSX LibraryManagedAttributes only consults a // component's `propTypes` when it is required (`C extends {propTypes: ...}`); - // when it does, this index-signature `propTypes` makes `defaultProps` stop + // when it does, a broadly typed `propTypes` can make `defaultProps` stop // marking props optional, so consumers are forced to pass every prop. // Optional dodges that branch; React 19 ignores `propTypes` entirely. The // typings/tsconfig.react18.json check guards against a regression here. // Do not remove. See lib/Draggable.tsx for the same guard. - static propTypes?: {[key: string]: unknown} = { + static propTypes?: React.ComponentClass['propTypes'] = { /** * `allowAnyClick` allows dragging using any mouse button. * By default, we only accept the left button. diff --git a/scripts/verify-build.cjs b/scripts/verify-build.cjs index 05aac5c3..31fb0508 100644 --- a/scripts/verify-build.cjs +++ b/scripts/verify-build.cjs @@ -61,7 +61,7 @@ assert.ok( // ── Contract 3: generated declarations must not leak internal-only deps ────── // The shipped .d.ts is generated from source. The v4.5.0 hand-written typings // depended only on `react`; if a `propTypes`/`defaultProps` static loses its -// index-signature annotation, tsc infers PropTypes.* types and emits +// explicit React.ComponentClass annotation, tsc infers PropTypes.* types and emits // `import ... 'prop-types'` into the public declaration, silently forcing // consumers to install @types/prop-types. Fail the build if that creeps back. const dtsDir = path.join(root, 'build', 'cjs'); @@ -75,7 +75,7 @@ assert.equal( leaks.length, 0, `Generated declarations leak 'prop-types' (consumers would need @types/prop-types): ${leaks.join(', ')}. ` + - `Annotate the offending static (e.g. \`static propTypes?: {[key: string]: unknown}\`) so tsc does not emit PropTypes types.` + `Annotate the offending static (e.g. \`static propTypes?: React.ComponentClass['propTypes']\`) so tsc does not emit PropTypes types.` ); // ── Contract 4: no unguarded `process` in browser-facing bundles (issue #806) ─ @@ -185,9 +185,14 @@ try { ); fs.writeFileSync( path.join(consumerDir, 'consumer.tsx'), - `import Draggable, {DraggableCore} from 'react-draggable';\n` + + `import {lazy} from 'react';\n` + + `import Draggable, {DraggableCore} from 'react-draggable';\n` + `export const Dragged = () =>
;\n` + - `export const Core = () =>
;\n` + `export const Core = () =>
;\n` + + `const LazyDraggable = lazy(() => import('react-draggable'));\n` + + `const LazyCore = lazy(() => import('react-draggable').then(m => ({default: m.DraggableCore})));\n` + + `export const LazyDragged = () =>
;\n` + + `export const LazyDraggedCore = () =>
;\n` ); try { diff --git a/typings/test.tsx b/typings/test.tsx index 50fe504b..e91ddd9f 100644 --- a/typings/test.tsx +++ b/typings/test.tsx @@ -67,3 +67,16 @@ root.render( root.render(
); root.render(
); + +// Both exports must be ComponentTypes for React.lazy, without consumer casts. +const LazyDraggable = React.lazy(() => import('react-draggable')); +const LazyCore = React.lazy(() => import('react-draggable').then(m => ({default: m.DraggableCore}))); + +root.render(
); +root.render(
); + +// Lazy loading must preserve the component's prop checks. +// @ts-expect-error axis only accepts the documented directions +root.render(
); +// @ts-expect-error scale must be a number +root.render(
); diff --git a/typings/tsconfig.react18.json b/typings/tsconfig.react18.json index a3a1987d..d3c07bab 100644 --- a/typings/tsconfig.react18.json +++ b/typings/tsconfig.react18.json @@ -4,6 +4,7 @@ "noEmit": true, "jsx": "preserve", "strict": true, + "module": "commonjs", "target": "ES2019", "lib": ["ES2019", "DOM", "DOM.Iterable"], "moduleResolution": "node",