Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REFACTOR: InsertModeSelector & dndTypes to TypeScript #3430

Open
wants to merge 6 commits into
base: 8.3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/neos-ui-decorators/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"typescript": "^4.6.4"
},
"peerDependencies": {
"hoist-non-react-statics": "^3.3.0",
"prop-types": "^15.5.10",
"react": "^16.12.0"
},
Expand Down
92 changes: 92 additions & 0 deletions packages/neos-ui-decorators/src/decorator-type-helpers.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Type definitions extracted from react-redux 7.1
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/59c89cc048de1e795eb3c88108fcb982ee4674ae/types/react-redux/index.d.ts
// bundled via rollup https://github.com/Swatinem/rollup-plugin-dts/tree/master to extract only the { ConnectedProps, InferableComponentEnhancerWithProps }

import {JSXElementConstructor, ComponentClass, ClassAttributes, NamedExoticComponent} from 'react';
import {NonReactStatics} from 'hoist-non-react-statics';

type DistributiveOmit<T, K extends keyof T> = T extends
unknown ? Omit<T, K> : never;

/**
* A property P will be present if:
* - it is present in DecorationTargetProps
*
* Its value will be dependent on the following conditions
* - if property P is present in InjectedProps and its definition extends the definition
* in DecorationTargetProps, then its definition will be that of DecorationTargetProps[P]
* - if property P is not present in InjectedProps then its definition will be that of
* DecorationTargetProps[P]
* - if property P is present in InjectedProps but does not extend the
* DecorationTargetProps[P] definition, its definition will be that of InjectedProps[P]
*/
type Matching<InjectedProps, DecorationTargetProps> = {
[P in keyof DecorationTargetProps]: P extends keyof InjectedProps
? InjectedProps[P] extends DecorationTargetProps[P]
? DecorationTargetProps[P]
: InjectedProps[P]
: DecorationTargetProps[P];
};

/**
* a property P will be present if :
* - it is present in both DecorationTargetProps and InjectedProps
* - InjectedProps[P] can satisfy DecorationTargetProps[P]
* ie: decorated component can accept more types than decorator is injecting
*
* For decoration, inject props or ownProps are all optionally
* required by the decorated (right hand side) component.
* But any property required by the decorated component must be satisfied by the injected property.
*/
type Shared<
InjectedProps,
DecorationTargetProps
> = {
[P in Extract<keyof InjectedProps, keyof DecorationTargetProps>]?: InjectedProps[P] extends DecorationTargetProps[P] ? DecorationTargetProps[P] : never;
};

// Infers prop type from component C
type GetProps<C> = C extends JSXElementConstructor<infer P>
? C extends ComponentClass<P> ? ClassAttributes<InstanceType<C>> & P : P
: never;

// Applies LibraryManagedAttributes (proper handling of defaultProps
// and propTypes).
type GetLibraryManagedProps<C> = JSX.LibraryManagedAttributes<C, GetProps<C>>;

// Defines WrappedComponent and derives non-react statics.
type ConnectedComponent<
C extends JSXElementConstructor<any>,
P
> = NamedExoticComponent<P> & NonReactStatics<C> & {
WrappedComponent: C;
};

// Injects props and removes them from the prop requirements.
// Will not pass through the injected props if they are passed in during
// render. Also adds new prop requirements from TNeedsProps.
// Uses distributive omit to preserve discriminated unions part of original prop type
type InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> =
<C extends JSXElementConstructor<Matching<TInjectedProps, GetProps<C>>>>(
component: C
) => ConnectedComponent<C, DistributiveOmit<GetLibraryManagedProps<C>, keyof Shared<TInjectedProps, GetLibraryManagedProps<C>>> & TNeedsProps>;

// Injects props and removes them from the prop requirements.
// Will not pass through the injected props if they are passed in during
// render.
type InferableComponentEnhancer<TInjectedProps> =
InferableComponentEnhancerWithProps<TInjectedProps, {}>;

/**
* Infers the type of props that a connector will inject into a component.
*/
type ConnectedProps<TConnector> =
TConnector extends InferableComponentEnhancerWithProps<infer TInjectedProps, any>
? unknown extends TInjectedProps
? TConnector extends InferableComponentEnhancer<infer TInjectedProps>
? TInjectedProps
: never
: TInjectedProps
: never;

export {ConnectedProps as HighOrderComponentProps, InferableComponentEnhancerWithProps};
8 changes: 1 addition & 7 deletions packages/neos-ui-decorators/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
import neos, {NeosContext, NeosInjectedProps} from './neos';

export {
neos,
NeosContext,
NeosInjectedProps
};
export {neos, NeosContext, NeosifiedProps} from './neos';
JamesAlias marked this conversation as resolved.
Show resolved Hide resolved
48 changes: 48 additions & 0 deletions packages/neos-ui-decorators/src/neos.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
InferableComponentEnhancerWithProps,
HighOrderComponentProps
} from './decorator-type-helpers';
import {GlobalRegistry} from '@neos-project/neos-ts-interfaces';

export interface NeosContextInterface {
globalRegistry: GlobalRegistry;
configuration: {};
routes: {};
}

/**
* Infers the type of props that a neosifier will inject into a component.
*
* @example
* const neosifier = neos((globalRegistry: GlobalRegistry) => ({
* i18nRegistry: globalRegistry.get('i18n')
* }));
* type NeosProps = NeosifiedProps<typeof neosifier>;
*
* const MyPlainComponent = (props: NeosProps & OwnProps) => "huhu";
*
* export const MyComponent = neosifier(MyPlainComponent);
*
*/
export type NeosifiedProps<TNeosifier> = HighOrderComponentProps<TNeosifier>;

export const NeosContext: React.Context<NeosContextInterface | null>;

type MapRegistryToPropsParam<TStateProps> = (
globalRegistry: GlobalRegistry
) => TStateProps;

interface Neos {
<TStateProps = {}, TOwnProps = {}>(
mapRegistryToProps: MapRegistryToPropsParam<TStateProps>
): InferableComponentEnhancerWithProps<
TStateProps & { neos: NeosContextInterface },
TOwnProps
>;
}

/**
* Creates an higher order component to easily spread global configuration
* {@link NeosifiedProps}
*/
export const neos: Neos;
40 changes: 40 additions & 0 deletions packages/neos-ui-decorators/src/neos.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import {defaultMemoize} from 'reselect';

// We need to memoize configuration and global registry; otherwise a new object is created at every render; leading to
// LOADS of unnecessary re-draws.
const buildConfigurationAndGlobalRegistry = defaultMemoize((configuration, globalRegistry, routes) => ({configuration, globalRegistry, routes}));

export const NeosContext = React.createContext(null);

export const neos = (mapRegistriesToProps) => (WrappedComponent) => {
const Decorator = class NeosDecorator extends React.PureComponent {
static Original = WrappedComponent;

static contextType = NeosContext;

static displayName = `Neos(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;

render() {
return (
<NeosContext.Consumer>
{context => {
if (!context) {
console.error('Context missing!', this.props);
return null;
}
const registriesToPropsMap = mapRegistriesToProps ? mapRegistriesToProps(context.globalRegistry) : {};
return (
<WrappedComponent
neos={buildConfigurationAndGlobalRegistry(context.configuration, context.globalRegistry, context.routes)}
JamesAlias marked this conversation as resolved.
Show resolved Hide resolved
{...this.props}
{...registriesToPropsMap}
/>
);
}}
</NeosContext.Consumer>
);
}
};
return Decorator;
};
52 changes: 0 additions & 52 deletions packages/neos-ui-decorators/src/neos.tsx

This file was deleted.

11 changes: 5 additions & 6 deletions packages/neos-ui-i18n/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React from 'react';
import {neos} from '@neos-project/neos-ui-decorators';
import {neos, NeosifiedProps} from '@neos-project/neos-ui-decorators';
import {GlobalRegistry} from '@neos-project/neos-ts-interfaces';
import {NeosInjectedProps} from '@neos-project/neos-ui-decorators/src/neos';

const regsToProps = (globalRegistry: GlobalRegistry) => ({
const neosifier = neos((globalRegistry: GlobalRegistry) => ({
i18nRegistry: globalRegistry.get('i18n')
});
type InjectedProps = NeosInjectedProps<typeof regsToProps>;
}));
type InjectedProps = NeosifiedProps<typeof neosifier>;

interface I18nProps {
// Fallback key which gets rendered once the i18n service doesn't return a translation.
Expand Down Expand Up @@ -36,4 +35,4 @@ class I18n extends React.PureComponent<I18nProps & InjectedProps> {
}
}

export default neos<I18nProps, InjectedProps>(regsToProps)(I18n);
export default neosifier(I18n);
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2421,6 +2421,7 @@ __metadata:
reselect: ^3.0.1
typescript: ^4.6.4
peerDependencies:
hoist-non-react-statics: "*"
prop-types: ^15.5.10
react: ^16.12.0
languageName: unknown
Expand Down