Skip to content
This repository has been archived by the owner on Nov 9, 2024. It is now read-only.

Commit

Permalink
types: refactor interfaces (#632)
Browse files Browse the repository at this point in the history
* Refactor interfaces to generics

* Add function overloading for return types

* Add included plugins to Props interface

* Add section to Plugins page about TypeScript

* Add destroy() argument type for Delegate
  • Loading branch information
atomiks authored Nov 2, 2019
1 parent bf25478 commit dc672f4
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 103 deletions.
25 changes: 13 additions & 12 deletions src/addons/createSingleton.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Instance, Props, Plugin} from '../types';
import {Instance, Props, CreateSingleton} from '../types';
import tippy from '..';
import {preserveInvocation, useIfDefined} from '../utils';
import {defaultProps} from '../props';
Expand All @@ -8,12 +8,12 @@ import {throwErrorWhen} from '../validation';
* Re-uses a single tippy element for many different tippy instances.
* Replaces v4's `tippy.group()`.
*/
export default function createSingleton(
tippyInstances: Instance[],
optionalProps: Partial<Props> = {},
const createSingleton: CreateSingleton = (
tippyInstances,
optionalProps = {},
/** @deprecated use Props.plugins */
plugins: Plugin[] = [],
): Instance {
plugins = [],
) => {
if (__DEV__) {
throwErrorWhen(
!Array.isArray(tippyInstances),
Expand All @@ -33,16 +33,15 @@ export default function createSingleton(
let currentAria: string | null | undefined;
let currentTarget: Element;

const userProps: Partial<Props> = {};
const userProps = {...defaultProps, ...optionalProps};

function setUserProps(props: Partial<Props>): void {
Object.keys(props).forEach(prop => {
userProps[prop] = useIfDefined(props[prop], userProps[prop]);
const keys = Object.keys(props) as Array<keyof Props>;
keys.forEach(prop => {
(userProps as any)[prop] = useIfDefined(props[prop], userProps[prop]);
});
}

setUserProps({...defaultProps, ...optionalProps});

function handleAriaDescribedByAttribute(
id: string,
isInteractive: boolean,
Expand Down Expand Up @@ -138,4 +137,6 @@ export default function createSingleton(
};

return tippy(document.createElement('div'), props) as Instance;
}
};

export default createSingleton;
6 changes: 4 additions & 2 deletions src/addons/delegate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Targets, Instance, Props, Plugin} from '../types';
import {Instance, Targets, Plugin, Props} from '../types';
import tippy from '..';
import {throwErrorWhen} from '../validation';
import {removeProperties, normalizeToArray, includes} from '../utils';
Expand All @@ -15,7 +15,7 @@ const BUBBLING_EVENTS_MAP = {
* Creates a delegate instance that controls the creation of tippy instances
* for child elements (`target` CSS selector).
*/
export default function delegate(
function delegate(
targets: Targets,
props: Partial<Props> & {target: string},
/** @deprecated use Props.plugins */
Expand Down Expand Up @@ -122,3 +122,5 @@ export default function delegate(

return returnValue;
}

export default delegate;
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,6 @@ export function createTippyWithPlugins(outerPlugins: Plugin[]): Tippy {
tippyPluginsWrapper.setDefaultProps = setDefaultProps;
tippyPluginsWrapper.currentInput = currentInput;

// @ts-ignore
return tippyPluginsWrapper;
}
8 changes: 5 additions & 3 deletions src/plugins/animateFill.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {LifecycleHooks, AnimateFillInstance} from '../types';
import {AnimateFill} from '../types';
import {BACKDROP_CLASS} from '../constants';
import {div, setVisibilityState} from '../utils';
import {isUCBrowser} from '../browser';
import {warnWhen} from '../validation';

export default {
const animateFill: AnimateFill = {
name: 'animateFill',
defaultValue: false,
fn(instance: AnimateFillInstance): Partial<LifecycleHooks> {
fn(instance) {
const {tooltip, content} = instance.popperChildren;

const backdrop =
Expand Down Expand Up @@ -85,6 +85,8 @@ export default {
},
};

export default animateFill;

function createBackdropElement(): HTMLDivElement {
const backdrop = div();
backdrop.className = BACKDROP_CLASS;
Expand Down
21 changes: 13 additions & 8 deletions src/plugins/followCursor.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
Props,
PopperElement,
LifecycleHooks,
Placement,
Instance,
FollowCursor,
FollowCursorProps,
Props,
} from '../types';
import {
includes,
Expand All @@ -15,10 +15,12 @@ import {
import {getBasePlacement} from '../popper';
import {currentInput} from '../bindGlobalEventListeners';

export default {
type ExtendedProps = Props & FollowCursorProps;

const followCursor: FollowCursor = {
name: 'followCursor',
defaultValue: false,
fn(instance: Instance): Partial<LifecycleHooks> {
fn(instance) {
const {reference, popper} = instance;

// Support iframe contexts
Expand All @@ -35,9 +37,10 @@ export default {
// original prop value
const userProps = instance.props;

function setUserProps(props: Partial<Props>): void {
Object.keys(props).forEach(prop => {
userProps[prop] = useIfDefined(props[prop], userProps[prop]);
function setUserProps(props: Partial<ExtendedProps>): void {
const keys = Object.keys(props) as Array<keyof ExtendedProps>;
keys.forEach(prop => {
(userProps as any)[prop] = useIfDefined(props[prop], userProps[prop]);
});
}

Expand Down Expand Up @@ -246,6 +249,8 @@ export default {
},
};

export default followCursor;

export function getVirtualOffsets(
popper: PopperElement,
isVerticalPlacement: boolean,
Expand Down
9 changes: 5 additions & 4 deletions src/plugins/inlinePositioning.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
Instance,
LifecycleHooks,
InlinePositioning,
InlinePositioningProps,
BasePlacement,
} from '../types';
Expand All @@ -10,10 +9,10 @@ import {getBasePlacement} from '../popper';
// TODO: Work on a "cursor" value so it chooses a rect optimal to the cursor
// position. This will require the `followCursor` plugin's fixes for overflow
// due to using event.clientX/Y values. (normalizedPlacement, getVirtualOffsets)
export default {
const inlinePositioning: InlinePositioning = {
name: 'inlinePositioning',
defaultValue: false,
fn(instance: Instance): Partial<LifecycleHooks> {
fn(instance) {
const {reference} = instance;

function getIsEnabled(): InlinePositioningProps['inlinePositioning'] {
Expand Down Expand Up @@ -50,6 +49,8 @@ export default {
},
};

export default inlinePositioning;

export function getInlineBoundingClientRect(
currentBasePlacement: BasePlacement | null,
boundingRect: ClientRect,
Expand Down
8 changes: 5 additions & 3 deletions src/plugins/sticky.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {LifecycleHooks, Instance} from '../types';
import {Sticky} from '../types';

export default {
const sticky: Sticky = {
name: 'sticky',
defaultValue: false,
fn(instance: Instance): Partial<LifecycleHooks> {
fn(instance) {
const {reference, popper} = instance;

function shouldCheck(value: 'reference' | 'popper'): boolean {
Expand Down Expand Up @@ -46,6 +46,8 @@ export default {
},
};

export default sticky;

function areRectsDifferent(
rectA: ClientRect | null,
rectB: ClientRect | null,
Expand Down
32 changes: 23 additions & 9 deletions src/props.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {Props, DefaultProps, ReferenceElement, Plugin} from './types';
import {Props, DefaultProps, ReferenceElement, Plugin, Tippy} from './types';
import {invokeWithArgsOrReturn, hasOwnProperty, includes} from './utils';
import {warnWhen} from './validation';
import {PropsV4} from './types-internal';

export const defaultProps: DefaultProps = {
allowHTML: true,
animateFill: false,
animation: 'fade',
appendTo: () => document.body,
aria: 'describedby',
Expand All @@ -16,8 +18,10 @@ export const defaultProps: DefaultProps = {
flip: true,
flipBehavior: 'flip',
flipOnUpdate: false,
followCursor: false,
hideOnClick: true,
ignoreAttributes: false,
inlinePositioning: false,
inertia: false,
interactive: false,
interactiveBorder: 2,
Expand All @@ -42,6 +46,7 @@ export const defaultProps: DefaultProps = {
popperOptions: {},
role: 'tooltip',
showOnCreate: false,
sticky: false,
theme: '',
touch: true,
trigger: 'mouseenter focus',
Expand Down Expand Up @@ -71,27 +76,32 @@ export const POPPER_INSTANCE_DEPENDENCIES: Array<keyof Props> = [
/**
* Mutates the defaultProps object by setting the props specified
*/
export function setDefaultProps(partialProps: Partial<DefaultProps>): void {
export const setDefaultProps: Tippy['setDefaultProps'] = (
partialProps,
): void => {
if (__DEV__) {
validateProps(partialProps, []);
}

Object.keys(partialProps).forEach(key => {
defaultProps[key] = partialProps[key];
const keys = Object.keys(partialProps) as Array<keyof DefaultProps>;
keys.forEach(key => {
(defaultProps as any)[key] = partialProps[key];
});
}
};

/**
* Returns an extended props object including plugin props
*/
export function getExtendedProps(props: Props): Props {
const iProps: Props & {[key: string]: any} = props;

return {
...props,
...props.plugins.reduce<{[key: string]: any}>((acc, plugin) => {
const {name, defaultValue} = plugin;

if (name) {
acc[name] = props[name] !== undefined ? props[name] : defaultValue;
acc[name] = iProps[name] !== undefined ? iProps[name] : defaultValue;
}

return acc;
Expand Down Expand Up @@ -161,14 +171,18 @@ export function evaluateProps(
* Validates props with the valid `defaultProps` object
*/
export function validateProps(
partialProps: Partial<Props> = {},
partialProps: Partial<PropsV4> = {},
plugins: Plugin[] = [],
): void {
Object.keys(partialProps).forEach(prop => {
const keys = Object.keys(partialProps) as Array<keyof PropsV4>;
keys.forEach(prop => {
const value = partialProps[prop];

const didSpecifyPlacementInPopperOptions =
prop === 'popperOptions' && value && hasOwnProperty(value, 'placement');
prop === 'popperOptions' &&
value !== null &&
typeof value === 'object' &&
hasOwnProperty(value, 'placement');

const didPassUnknownProp =
!hasOwnProperty(getExtendedProps({...defaultProps, plugins}), prop) &&
Expand Down
11 changes: 11 additions & 0 deletions src/types-internal.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import {Props} from './types';

export interface ListenerObject {
node: Element;
eventType: string;
handler: EventListenerOrEventListenerObject;
options: boolean | object;
}

export interface PropsV4 extends Props {
a11y: boolean;
arrowType: 'sharp' | 'round';
showOnInit: boolean;
size: 'small' | 'regular' | 'large';
target: string;
touchHold: boolean;
}
Loading

0 comments on commit dc672f4

Please sign in to comment.