Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions lib/Draggable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,19 @@ class Draggable extends React.Component<Partial<DraggableProps>, 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 <DraggableCore> accepts.
...DraggableCore.propTypes,

Expand Down Expand Up @@ -200,7 +200,7 @@ class Draggable extends React.Component<Partial<DraggableProps>, DraggableState>

// React 16.3+
// Arity (props, state)
static getDerivedStateFromProps({position}: DraggableProps, {prevPropsPosition}: DraggableState): Partial<DraggableState> | null {
static getDerivedStateFromProps({position}: Partial<DraggableProps>, {prevPropsPosition}: DraggableState): Partial<DraggableState> | null {
// Set x/y if a new position is provided in props that is different than the previous.
if (
position &&
Expand All @@ -218,6 +218,8 @@ class Draggable extends React.Component<Partial<DraggableProps>, DraggableState>
return null;
}

// React resolves defaultProps before construction; expose the consumer shape.
constructor(props: Partial<DraggableProps>);
constructor(props: DraggableProps) {
super(props);

Expand Down
6 changes: 3 additions & 3 deletions lib/DraggableCore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,19 @@ export default class DraggableCore extends React.Component<Partial<DraggableCore
static displayName: string | undefined = 'DraggableCore';

// 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/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.
Expand Down
13 changes: 9 additions & 4 deletions scripts/verify-build.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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) ─
Expand Down Expand Up @@ -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 = () => <Draggable><div /></Draggable>;\n` +
`export const Core = () => <DraggableCore><div /></DraggableCore>;\n`
`export const Core = () => <DraggableCore><div /></DraggableCore>;\n` +
`const LazyDraggable = lazy(() => import('react-draggable'));\n` +
`const LazyCore = lazy(() => import('react-draggable').then(m => ({default: m.DraggableCore})));\n` +
`export const LazyDragged = () => <LazyDraggable><div /></LazyDraggable>;\n` +
`export const LazyDraggedCore = () => <LazyCore><div /></LazyCore>;\n`
);

try {
Expand Down
13 changes: 13 additions & 0 deletions typings/test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,16 @@ root.render(
root.render(<Draggable><div/></Draggable>);

root.render(<DraggableCore><div/></DraggableCore>);

// 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(<LazyDraggable><div/></LazyDraggable>);
root.render(<LazyCore><div/></LazyCore>);

// Lazy loading must preserve the component's prop checks.
// @ts-expect-error axis only accepts the documented directions
root.render(<LazyDraggable axis="diagonal"><div/></LazyDraggable>);
// @ts-expect-error scale must be a number
root.render(<LazyCore scale="large"><div/></LazyCore>);
1 change: 1 addition & 0 deletions typings/tsconfig.react18.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"noEmit": true,
"jsx": "preserve",
"strict": true,
"module": "commonjs",
"target": "ES2019",
"lib": ["ES2019", "DOM", "DOM.Iterable"],
"moduleResolution": "node",
Expand Down