Skip to content

Commit

Permalink
Move popper instances to the store
Browse files Browse the repository at this point in the history
- add getPopper, setPopper and cleanupPoppers to the store
  • Loading branch information
gilbarbara committed Dec 14, 2023
1 parent e0d72ef commit 7f45d58
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 45 deletions.
33 changes: 14 additions & 19 deletions src/components/Step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,9 @@ import Overlay from './Overlay';
import Portal from './Portal';
import Tooltip from './Tooltip/index';

type PopperData = Parameters<NonNullable<FloaterProps['getPopper']>>[0];

export default class JoyrideStep extends React.Component<StepProps> {
beaconPopper: PopperData | null = null;
scope: Scope | null = null;
tooltip: HTMLElement | null = null;
tooltipPopper: PopperData | null = null;

componentDidMount() {
const { debug, index } = this.props;
Expand All @@ -47,7 +43,7 @@ export default class JoyrideStep extends React.Component<StepProps> {
size,
status,
step,
update,
store,
} = this.props;
const { changed, changedFrom } = treeChanges(previousProps, this.props);
const state = { action, controlled, index, lifecycle, size, status };
Expand Down Expand Up @@ -82,7 +78,7 @@ export default class JoyrideStep extends React.Component<StepProps> {
action !== ACTIONS.START &&
lifecycle === LIFECYCLE.INIT
) {
update({ lifecycle: LIFECYCLE.READY });
store.update({ lifecycle: LIFECYCLE.READY });
}

if (hasStoreChanged) {
Expand Down Expand Up @@ -111,13 +107,15 @@ export default class JoyrideStep extends React.Component<StepProps> {
});

if (!controlled) {
update({ index: index + (action === ACTIONS.PREV ? -1 : 1) });
store.update({ index: index + (action === ACTIONS.PREV ? -1 : 1) });
}
}
}

if (changedFrom('lifecycle', LIFECYCLE.INIT, LIFECYCLE.READY)) {
update({ lifecycle: hideBeacon(step) || skipBeacon ? LIFECYCLE.TOOLTIP : LIFECYCLE.BEACON });
store.update({
lifecycle: hideBeacon(step) || skipBeacon ? LIFECYCLE.TOOLTIP : LIFECYCLE.BEACON,
});
}

if (changed('index')) {
Expand Down Expand Up @@ -152,8 +150,7 @@ export default class JoyrideStep extends React.Component<StepProps> {

if (changedFrom('lifecycle', [LIFECYCLE.TOOLTIP, LIFECYCLE.INIT], LIFECYCLE.INIT)) {
this.scope?.removeScope();
this.beaconPopper = null;
this.tooltipPopper = null;
store.cleanupPoppers();
}
}

Expand All @@ -165,13 +162,13 @@ export default class JoyrideStep extends React.Component<StepProps> {
* Beacon click/hover event listener
*/
handleClickHoverBeacon = (event: React.MouseEvent<HTMLElement>) => {
const { step, update } = this.props;
const { step, store } = this.props;

if (event.type === 'mouseenter' && step.event !== 'hover') {
return;
}

update({ lifecycle: LIFECYCLE.TOOLTIP });
store.update({ lifecycle: LIFECYCLE.TOOLTIP });
};

handleClickOverlay = () => {
Expand All @@ -187,18 +184,16 @@ export default class JoyrideStep extends React.Component<StepProps> {
};

setPopper: FloaterProps['getPopper'] = (popper, type) => {
const { action, setPopper, update } = this.props;
const { action, store } = this.props;

if (type === 'wrapper') {
this.beaconPopper = popper;
store.setPopper('beacon', popper);
} else {
this.tooltipPopper = popper;
store.setPopper('tooltip', popper);
}

setPopper?.(popper, type);

if (this.beaconPopper && this.tooltipPopper) {
update({
if (store.getPopper('beacon') && store.getPopper('tooltip')) {
store.update({
action,
lifecycle: LIFECYCLE.READY,
});
Expand Down
29 changes: 10 additions & 19 deletions src/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react';
import { Props as FloaterProps } from 'react-floater';
import isEqual from '@gilbarbara/deep-equal';
import is from 'is-lite';
import treeChanges from 'tree-changes';
Expand All @@ -26,8 +25,6 @@ import Step from './Step';
class Joyride extends React.Component<Props, State> {
private readonly helpers: StoreHelpers;
private readonly store: ReturnType<typeof createStore>;
private beaconPopper: any;
private tooltipPopper: any;

static defaultProps = defaultProps;

Expand Down Expand Up @@ -251,14 +248,6 @@ class Joyride extends React.Component<Props, State> {
this.setState(state);
};

setPopper: FloaterProps['getPopper'] = (popper, type) => {
if (type === 'wrapper') {
this.beaconPopper = popper;
} else {
this.tooltipPopper = popper;
}
};

scrollToStep(previousState: State) {
const { index, lifecycle, status } = this.state;
const {
Expand Down Expand Up @@ -296,19 +285,22 @@ class Joyride extends React.Component<Props, State> {
debug,
});

const beaconPopper = this.store.getPopper('beacon');
const tooltipPopper = this.store.getPopper('tooltip');

/* istanbul ignore else */
if (lifecycle === LIFECYCLE.BEACON && this.beaconPopper) {
const { placement, popper } = this.beaconPopper;
if (lifecycle === LIFECYCLE.BEACON && beaconPopper) {
const { offsets, placement } = beaconPopper;

/* istanbul ignore else */
if (!['bottom'].includes(placement) && !hasCustomScroll) {
scrollY = Math.floor(popper.top - scrollOffset);
scrollY = Math.floor(offsets.popper.top - scrollOffset);
}
} else if (lifecycle === LIFECYCLE.TOOLTIP && this.tooltipPopper) {
const { flipped, placement, popper } = this.tooltipPopper;
} else if (lifecycle === LIFECYCLE.TOOLTIP && tooltipPopper) {
const { flipped, offsets, placement } = tooltipPopper;

if (['top', 'right', 'left'].includes(placement) && !flipped && !hasCustomScroll) {
scrollY = Math.floor(popper.top - scrollOffset);
scrollY = Math.floor(offsets.popper.top - scrollOffset);
} else {
scrollY -= step.spotlightPadding;
}
Expand Down Expand Up @@ -349,10 +341,9 @@ class Joyride extends React.Component<Props, State> {
debug={debug}
helpers={this.helpers}
nonce={nonce}
setPopper={this.setPopper}
shouldScroll={!step.disableScrolling && (index !== 0 || scrollToFirstStep)}
step={step}
update={this.store.update}
store={this.store}
/>
);
}
Expand Down
37 changes: 32 additions & 5 deletions src/modules/store.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Props as FloaterProps } from 'react-floater';
import is from 'is-lite';

import { ACTIONS, LIFECYCLE, STATUS } from '~/literals';
Expand All @@ -6,6 +7,10 @@ import { AnyObject, State, Status, Step, StoreHelpers, StoreOptions } from '~/ty

import { hasValidKeys } from './helpers';

type StateWithContinuous = State & { continuous: boolean };
type Listener = (state: State) => void;
type PopperData = Parameters<NonNullable<FloaterProps['getPopper']>>[0];

const defaultState: State = {
action: 'init',
controlled: false,
Expand All @@ -14,14 +19,11 @@ const defaultState: State = {
size: 0,
status: STATUS.IDLE,
};

type StateWithContinuous = State & { continuous: boolean };

const validKeys = ['action', 'index', 'lifecycle', 'status'];

type Listener = (state: State) => void;

class Store {
private beaconPopper: PopperData | null;
private tooltipPopper: PopperData | null;
private data: Map<string, any> = new Map();
private listener: Listener | null;
private store: Map<string, any> = new Map();
Expand All @@ -41,6 +43,8 @@ class Store {
true,
);

this.beaconPopper = null;
this.tooltipPopper = null;
this.listener = null;
this.setSteps(steps);
}
Expand Down Expand Up @@ -146,6 +150,27 @@ class Store {
};
}

public getPopper = (name: 'beacon' | 'tooltip') => {
if (name === 'beacon') {
return this.beaconPopper;
}

return this.tooltipPopper;
};

public setPopper = (name: 'beacon' | 'tooltip', popper: PopperData) => {
if (name === 'beacon') {
this.beaconPopper = popper;
} else {
this.tooltipPopper = popper;
}
};

public cleanupPoppers = () => {
this.beaconPopper = null;
this.tooltipPopper = null;
};

public close = () => {
const { index, status } = this.getState();

Expand Down Expand Up @@ -282,6 +307,8 @@ class Store {
};
}

export type StoreInstance = ReturnType<typeof createStore>;

export default function createStore(options?: StoreOptions) {
return new Store(options);
}
5 changes: 3 additions & 2 deletions src/types/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { ElementType, MouseEventHandler, ReactNode, RefCallback } from 'react';
import { Props as FloaterProps } from 'react-floater';
import { PartialDeep, SetRequired, Simplify, ValueOf } from 'type-fest';

import type { StoreInstance } from '~/modules/store';

import { Actions, Events, Lifecycle, Locale, Placement, Status, Styles } from './common';

export type BaseProps = {
Expand Down Expand Up @@ -136,10 +138,9 @@ export type StepProps = Simplify<
debug: boolean;
helpers: StoreHelpers;
nonce?: string;
setPopper: FloaterProps['getPopper'];
shouldScroll: boolean;
step: StepMerged;
update: (state: Partial<State>) => void;
store: StoreInstance;
}
>;

Expand Down
24 changes: 24 additions & 0 deletions test/modules/store.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { fromPartial } from '@total-typescript/shoehorn';

import createStore from '~/modules/store';

import { LIFECYCLE, STATUS } from '~/literals';
Expand Down Expand Up @@ -217,4 +219,26 @@ describe('store', () => {
expect(mockSyncStore).toHaveBeenCalledTimes(3);
});
});

describe('with popper', () => {
const store = createStore();
const popperData = { placement: 'top' } as const;

it('should set/get both poppers', () => {
store.setPopper('beacon', fromPartial(popperData));
expect(store.getPopper('beacon')).toEqual(popperData);

store.setPopper('tooltip', fromPartial(popperData));
expect(store.getPopper('tooltip')).toEqual(popperData);
});

it('should clear both poppers', () => {
expect(store.getPopper('beacon')).toEqual(popperData);
expect(store.getPopper('tooltip')).toEqual(popperData);

store.cleanupPoppers();
expect(store.getPopper('beacon')).toBeNull();
expect(store.getPopper('tooltip')).toBeNull();
});
});
});

0 comments on commit 7f45d58

Please sign in to comment.