Skip to content

Commit c27b743

Browse files
authored
Merge pull request #385 from mashmatrix/component-settings-context
use React.createContext to propagate component settings
2 parents eafcb12 + c131eaa commit c27b743

File tree

10 files changed

+353
-108
lines changed

10 files changed

+353
-108
lines changed

src/scripts/AutoAlign.tsx

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React, { ComponentType } from 'react';
2-
import PropTypes from 'prop-types';
32
import classnames from 'classnames';
43
import RelativePortal from 'react-relative-portal';
54
import { ComponentSettingsContext } from './ComponentSettings';
@@ -237,16 +236,6 @@ export function autoAlign(options: AutoAlignOptions) {
237236
content: any;
238237
/* eslint-enable react/sort-comp */
239238

240-
context!: Pick<
241-
ComponentSettingsContext,
242-
'portalClassName' | 'portalStyle'
243-
>;
244-
245-
static contextTypes = {
246-
portalClassName: PropTypes.string,
247-
portalStyle: PropTypes.object, // eslint-disable-line react/forbid-prop-types
248-
};
249-
250239
constructor(props: ResultProps) {
251240
super(props);
252241
this.state = {
@@ -380,10 +369,6 @@ export function autoAlign(options: AutoAlignOptions) {
380369
children,
381370
...pprops
382371
} = this.props;
383-
const {
384-
portalClassName = 'slds-scope',
385-
portalStyle = { position: 'absolute', top: 0, left: 0, right: 0 },
386-
} = this.context;
387372
// eslint-disable-next-line prefer-const
388373
let { top, left } = calcAlignmentRect(
389374
triggerNodeRect,
@@ -411,22 +396,37 @@ export function autoAlign(options: AutoAlignOptions) {
411396
content
412397
) : (
413398
<div ref={(node) => (this.node = node)}>
414-
<RelativePortal
415-
fullWidth
416-
left={offsetLeft}
417-
right={-offsetLeft}
418-
top={offsetTop}
419-
onScroll={ignoreFirstCall(this.requestRecalcAlignment)}
420-
component='div'
421-
className={classnames(portalClassName, additionalPortalClassName)}
422-
style={{ ...portalStyle, ...additionalPortalStyle }}
423-
>
424-
{this.state.triggerNodeRect ? (
425-
content
426-
) : (
427-
<div className='slds-hidden'>{content}</div>
399+
<ComponentSettingsContext.Consumer>
400+
{({
401+
portalClassName = 'slds-scope',
402+
portalStyle = {
403+
position: 'absolute',
404+
top: 0,
405+
left: 0,
406+
right: 0,
407+
},
408+
}) => (
409+
<RelativePortal
410+
fullWidth
411+
left={offsetLeft}
412+
right={-offsetLeft}
413+
top={offsetTop}
414+
onScroll={ignoreFirstCall(this.requestRecalcAlignment)}
415+
component='div'
416+
className={classnames(
417+
portalClassName,
418+
additionalPortalClassName
419+
)}
420+
style={{ ...portalStyle, ...additionalPortalStyle }}
421+
>
422+
{this.state.triggerNodeRect ? (
423+
content
424+
) : (
425+
<div className='slds-hidden'>{content}</div>
426+
)}
427+
</RelativePortal>
428428
)}
429-
</RelativePortal>
429+
</ComponentSettingsContext.Consumer>
430430
</div>
431431
);
432432
}

src/scripts/ComponentSettings.tsx

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,41 @@
1-
import React from 'react';
2-
import PropTypes from 'prop-types';
1+
import React, { createContext } from 'react';
32

43
export type ComponentSettingsProps = {
54
assetRoot?: string;
65
portalClassName?: string;
76
portalStyle?: object;
7+
getActiveElement?: () => HTMLElement | null;
88
};
99

10-
export type ComponentSettingsContext = {
11-
assetRoot?: string;
12-
portalClassName?: string;
13-
portalStyle?: object;
14-
};
10+
function getDocumentActiveElement() {
11+
return document.activeElement as HTMLElement | null;
12+
}
13+
14+
export const ComponentSettingsContext = createContext<
15+
ComponentSettingsProps &
16+
Required<Pick<ComponentSettingsProps, 'getActiveElement'>>
17+
>({ getActiveElement: getDocumentActiveElement });
1518

1619
/**
1720
*
1821
*/
19-
export class ComponentSettings extends React.Component<
20-
ComponentSettingsProps,
21-
{}
22+
export class ComponentSettings extends React.PureComponent<
23+
ComponentSettingsProps
2224
> {
23-
static childContextTypes = {
24-
assetRoot: PropTypes.string,
25-
portalClassName: PropTypes.string,
26-
portalStyle: PropTypes.object, // eslint-disable-line react/forbid-prop-types
27-
};
28-
29-
getChildContext(): ComponentSettingsContext {
30-
const { assetRoot, portalClassName, portalStyle } = this.props;
31-
return { assetRoot, portalClassName, portalStyle };
32-
}
33-
3425
render() {
35-
return this.props.children;
26+
const {
27+
assetRoot,
28+
portalClassName,
29+
portalStyle,
30+
getActiveElement = getDocumentActiveElement,
31+
children,
32+
} = this.props;
33+
return (
34+
<ComponentSettingsContext.Provider
35+
value={{ assetRoot, portalClassName, portalStyle, getActiveElement }}
36+
>
37+
{children}
38+
</ComponentSettingsContext.Provider>
39+
);
3640
}
3741
}

src/scripts/DateInput.tsx

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { FormElement } from './FormElement';
1212
import { Input, InputProps } from './Input';
1313
import { Datepicker } from './Datepicker';
1414
import { uuid, isElInChildren } from './util';
15+
import { ComponentSettingsContext } from './ComponentSettings';
1516

1617
type DatepickerDropdownProps = {
1718
className?: string;
@@ -95,16 +96,24 @@ export type DateInputProps = {
9596
extensionRenderer?: (...props: any[]) => JSX.Element;
9697
} & Omit<InputProps, 'value' | 'defaultValue' | 'onBlur' | 'onValueChange'>;
9798

98-
type DateInputState = {
99+
type DateInputInnerProps = DateInputProps & {
100+
getActiveElement: () => HTMLElement | null;
101+
};
102+
103+
type DateInputInnerState = {
99104
id: string;
100105
opened: boolean;
101106
value: string | null;
102107
inputValue: string | null;
103108
};
109+
104110
/**
105111
*
106112
*/
107-
export class DateInput extends Component<DateInputProps, DateInputState> {
113+
class DateInputInner extends Component<
114+
DateInputInnerProps,
115+
DateInputInnerState
116+
> {
108117
static isFormElement = true;
109118

110119
node: HTMLDivElement | null = null;
@@ -113,7 +122,7 @@ export class DateInput extends Component<DateInputProps, DateInputState> {
113122

114123
input: HTMLInputElement | null = null;
115124

116-
constructor(props: Readonly<DateInputProps>) {
125+
constructor(props: Readonly<DateInputInnerProps>) {
117126
super(props);
118127
this.state = {
119128
id: `form-element-${uuid()}`,
@@ -138,7 +147,10 @@ export class DateInput extends Component<DateInputProps, DateInputState> {
138147
this.onDatepickerClose = this.onDatepickerClose.bind(this);
139148
}
140149

141-
componentDidUpdate(prevProps: DateInputProps, prevState: DateInputState) {
150+
componentDidUpdate(
151+
prevProps: DateInputInnerProps,
152+
prevState: DateInputInnerState
153+
) {
142154
if (this.props.onValueChange && prevState.value !== this.state.value) {
143155
this.props.onValueChange(this.state.value, prevState.value);
144156
}
@@ -264,7 +276,8 @@ export class DateInput extends Component<DateInputProps, DateInputState> {
264276
}
265277

266278
isFocusedInComponent() {
267-
const targetEl = document.activeElement;
279+
const { getActiveElement } = this.props;
280+
const targetEl = getActiveElement();
268281
return (
269282
isElInChildren(this.node, targetEl) ||
270283
isElInChildren(this.datepicker, targetEl)
@@ -334,6 +347,7 @@ export class DateInput extends Component<DateInputProps, DateInputState> {
334347
includeTime,
335348
onComplete,
336349
onValueChange,
350+
getActiveElement,
337351
/* eslint-enable @typescript-eslint/no-unused-vars */
338352
...props
339353
} = this.props;
@@ -377,3 +391,38 @@ export class DateInput extends Component<DateInputProps, DateInputState> {
377391
);
378392
}
379393
}
394+
395+
/**
396+
*
397+
*/
398+
export class DateInput extends Component<DateInputProps> {
399+
static isFormElement = true;
400+
401+
inner: DateInputInner | null = null;
402+
403+
get node(): HTMLDivElement | null {
404+
return this.inner ? this.inner.node : null;
405+
}
406+
407+
get datepicker(): HTMLDivElement | null {
408+
return this.inner ? this.inner.datepicker : null;
409+
}
410+
411+
get input(): HTMLInputElement | null {
412+
return this.inner ? this.inner.input : null;
413+
}
414+
415+
render() {
416+
return (
417+
<ComponentSettingsContext.Consumer>
418+
{({ getActiveElement }) => (
419+
<DateInputInner
420+
ref={(cmp) => (this.inner = cmp)}
421+
{...this.props}
422+
getActiveElement={getActiveElement}
423+
/>
424+
)}
425+
</ComponentSettingsContext.Consumer>
426+
);
427+
}
428+
}

src/scripts/Datepicker.tsx

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import moment from 'moment';
44
import { Button } from './Button';
55
import { Select, Option } from './Select';
66
import { getToday, isElInChildren } from './util';
7+
import { ComponentSettingsContext } from './ComponentSettings';
78

89
type Date = {
910
year: number;
@@ -97,17 +98,24 @@ export type DatepickerProps = {
9798
onClose?: () => void;
9899
} & Omit<HTMLAttributes<HTMLDivElement>, 'onSelect'>;
99100

100-
export type DatepickerState = {
101+
type DatepickerInnerProps = DatepickerProps & {
102+
getActiveElement: () => HTMLElement | null;
103+
};
104+
105+
type DatepickerInnerState = {
101106
focusDate?: boolean;
102107
targetDate?: string;
103108
};
104109

105-
export class Datepicker extends Component<DatepickerProps, DatepickerState> {
110+
class DatepickerInner extends Component<
111+
DatepickerInnerProps,
112+
DatepickerInnerState
113+
> {
106114
node: HTMLDivElement | null = null;
107115

108116
month: HTMLTableElement | null = null;
109117

110-
constructor(props: Readonly<DatepickerProps>) {
118+
constructor(props: Readonly<DatepickerInnerProps>) {
111119
super(props);
112120
this.state = {};
113121

@@ -228,7 +236,9 @@ export class Datepicker extends Component<DatepickerProps, DatepickerState> {
228236
}
229237

230238
isFocusedInComponent() {
231-
return isElInChildren(this.node, document.activeElement);
239+
const { getActiveElement } = this.props;
240+
const targetEl = getActiveElement();
241+
return isElInChildren(this.node, targetEl);
232242
}
233243

234244
renderFilter(cal: Calendar) {
@@ -374,6 +384,7 @@ export class Datepicker extends Component<DatepickerProps, DatepickerState> {
374384
autoFocus,
375385
onSelect,
376386
onClose,
387+
getActiveElement,
377388
/* eslint-enable @typescript-eslint/no-unused-vars */
378389
...props
379390
} = this.props;
@@ -404,3 +415,28 @@ export class Datepicker extends Component<DatepickerProps, DatepickerState> {
404415
);
405416
}
406417
}
418+
419+
/**
420+
*
421+
*/
422+
export class Datepicker extends Component<DatepickerProps> {
423+
private inner: DatepickerInner | null = null;
424+
425+
get node(): HTMLDivElement | null {
426+
return this.inner ? this.inner.node : null;
427+
}
428+
429+
render() {
430+
return (
431+
<ComponentSettingsContext.Consumer>
432+
{({ getActiveElement }) => (
433+
<DatepickerInner
434+
ref={(cmp) => (this.inner = cmp)}
435+
{...this.props}
436+
getActiveElement={getActiveElement}
437+
/>
438+
)}
439+
</ComponentSettingsContext.Consumer>
440+
);
441+
}
442+
}

0 commit comments

Comments
 (0)