Skip to content

Commit 6e52167

Browse files
committed
accept active element accessor as component settings
1 parent d6ec3f8 commit 6e52167

File tree

6 files changed

+280
-36
lines changed

6 files changed

+280
-36
lines changed

src/scripts/ComponentSettings.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ export type ComponentSettingsProps = {
44
assetRoot?: string;
55
portalClassName?: string;
66
portalStyle?: object;
7+
getActiveElement?: () => HTMLElement | null;
78
};
89

9-
export type ComponentSettingsContext = {
10-
assetRoot?: string;
11-
portalClassName?: string;
12-
portalStyle?: object;
13-
};
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 });
1418

1519
/**
1620
*
@@ -19,10 +23,16 @@ export class ComponentSettings extends React.PureComponent<
1923
ComponentSettingsProps
2024
> {
2125
render() {
22-
const { assetRoot, portalClassName, portalStyle, children } = this.props;
26+
const {
27+
assetRoot,
28+
portalClassName,
29+
portalStyle,
30+
getActiveElement = getDocumentActiveElement,
31+
children,
32+
} = this.props;
2333
return (
2434
<ComponentSettingsContext.Provider
25-
value={{ assetRoot, portalClassName, portalStyle }}
35+
value={{ assetRoot, portalClassName, portalStyle, getActiveElement }}
2636
>
2737
{children}
2838
</ComponentSettingsContext.Provider>

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+
}

src/scripts/DropdownButton.tsx

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import classnames from 'classnames';
99
import { Button, ButtonProps } from './Button';
1010
import { DropdownMenu } from './DropdownMenu';
1111
import { registerStyle, isElInChildren } from './util';
12+
import { ComponentSettingsContext } from './ComponentSettings';
1213

1314
export type DropdownMenuAlign = 'left' | 'right';
1415
export type DropdownMenuSize = 'small' | 'medium' | 'large';
@@ -32,21 +33,27 @@ export type DropdownButtonProps<EventKey extends Key> = {
3233
onMenuSelect?: (eventKey: EventKey) => void;
3334
} & Omit<ButtonProps, 'onClick' | 'onBlur'>;
3435

35-
type DropdownButtonState = {
36+
type DropdownButtonInnerProps<EventKey extends Key> = DropdownButtonProps<
37+
EventKey
38+
> & {
39+
getActiveElement: () => HTMLElement | null;
40+
};
41+
42+
type DropdownButtonInnerState = {
3643
opened: boolean;
3744
};
3845

39-
export class DropdownButton<EventKey extends Key> extends Component<
40-
DropdownButtonProps<EventKey>,
41-
DropdownButtonState
46+
class DropdownButtonInner<EventKey extends Key> extends Component<
47+
DropdownButtonInnerProps<EventKey>,
48+
DropdownButtonInnerState
4249
> {
4350
node: HTMLDivElement | null = null;
4451

4552
trigger: HTMLButtonElement | null = null;
4653

4754
dropdown: HTMLDivElement | null = null;
4855

49-
constructor(props: Readonly<DropdownButtonProps<EventKey>>) {
56+
constructor(props: Readonly<DropdownButtonInnerProps<EventKey>>) {
5057
super(props);
5158
this.state = { opened: false };
5259
registerStyle('no-hover-popup', [
@@ -126,7 +133,8 @@ export class DropdownButton<EventKey extends Key> extends Component<
126133
};
127134

128135
isFocusedInComponent() {
129-
const targetEl = document.activeElement;
136+
const { getActiveElement } = this.props;
137+
const targetEl = getActiveElement();
130138
return (
131139
isElInChildren(this.node, targetEl) ||
132140
isElInChildren(this.dropdown, targetEl)
@@ -192,6 +200,8 @@ export class DropdownButton<EventKey extends Key> extends Component<
192200
children,
193201
style,
194202
menuStyle,
203+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
204+
getActiveElement,
195205
...props
196206
} = this.props;
197207
let { icon } = this.props;
@@ -237,3 +247,38 @@ export class DropdownButton<EventKey extends Key> extends Component<
237247
);
238248
}
239249
}
250+
251+
/**
252+
*
253+
*/
254+
export class DropdownButton<EventKey extends Key> extends Component<
255+
DropdownButtonProps<EventKey>
256+
> {
257+
private inner: DropdownButtonInner<EventKey> | null = null;
258+
259+
get node(): HTMLDivElement | null {
260+
return this.inner ? this.inner.node : null;
261+
}
262+
263+
get trigger(): HTMLButtonElement | null {
264+
return this.inner ? this.inner.trigger : null;
265+
}
266+
267+
get dropdown(): HTMLDivElement | null {
268+
return this.inner ? this.inner.dropdown : null;
269+
}
270+
271+
render() {
272+
return (
273+
<ComponentSettingsContext.Consumer>
274+
{({ getActiveElement }) => (
275+
<DropdownButtonInner
276+
ref={(cmp) => (this.inner = cmp)}
277+
{...this.props}
278+
getActiveElement={getActiveElement}
279+
/>
280+
)}
281+
</ComponentSettingsContext.Consumer>
282+
);
283+
}
284+
}

0 commit comments

Comments
 (0)