Skip to content

Commit 1070f34

Browse files
committed
start typing
1 parent ec9b34a commit 1070f34

22 files changed

+417
-233
lines changed

addon/-base-popper-modifier.js

-94
This file was deleted.

addon/-base-popper-modifier.ts

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import Modifier, { type ArgsFor } from 'ember-modifier';
2+
import { registerDestructor } from '@ember/destroyable';
3+
import { isArray } from '@ember/array';
4+
import { isEmpty } from '@ember/utils';
5+
import {
6+
createPopper,
7+
type Instance as PopperInstance,
8+
type Options as PopperOptions,
9+
} from '@popperjs/core';
10+
11+
import {
12+
setPopperForElement,
13+
isModifier,
14+
type PopperModifierDescription,
15+
} from './index';
16+
import {
17+
beginRunLoopModifier as beginRunLoop,
18+
endRunLoopModifier as endRunLoop,
19+
} from './in-runloop-modifier';
20+
import type Owner from '@ember/owner';
21+
22+
export interface PopperSignature {
23+
Args: {
24+
Positional: [
25+
HTMLElement,
26+
...(Partial<PopperOptions> | PopperModifierDescription)[],
27+
];
28+
Named: Partial<PopperOptions>;
29+
};
30+
Element: HTMLElement;
31+
}
32+
33+
function getPopperOptions(
34+
positional: PopperSignature['Args']['Positional'],
35+
named: PopperSignature['Args']['Named'],
36+
): Partial<PopperOptions> {
37+
// Get an array of just positional "options"; first item is an element reference
38+
const [, ...positionalArguments] = positional;
39+
40+
// Positional args that are not modifiers should be treated as full "options" objects
41+
const allPositionalOptions = positionalArguments.filter<
42+
Partial<PopperOptions>
43+
>((arg): arg is Partial<PopperOptions> => !isModifier(arg));
44+
45+
// Positional args that are modifiers will extend the rest of the configuration
46+
const allPositionalModifiers =
47+
positionalArguments.filter<PopperModifierDescription>(
48+
(arg): arg is PopperModifierDescription => isModifier(arg),
49+
);
50+
51+
const { ...namedOptions } = named;
52+
const options: Partial<PopperOptions> = {
53+
...allPositionalOptions.reduce((acc, curr) => {
54+
return { ...acc, ...curr };
55+
}, {}),
56+
...namedOptions,
57+
};
58+
59+
// Ensure that the `modifiers` is always an array
60+
const modifiers =
61+
options.modifiers === undefined || isEmpty(options.modifiers)
62+
? []
63+
: isArray(options.modifiers)
64+
? options.modifiers
65+
: [options.modifiers];
66+
67+
// Add runloop integration and positional modifiers to the array of modifiers
68+
options.modifiers = [
69+
...modifiers,
70+
...allPositionalModifiers,
71+
beginRunLoop,
72+
endRunLoop,
73+
];
74+
75+
return options;
76+
}
77+
78+
export default abstract class PopperModifier extends Modifier<PopperSignature> {
79+
popper: PopperInstance | null = null;
80+
primaryElement: HTMLElement | null = null;
81+
secondaryElement: HTMLElement | null = null;
82+
83+
abstract get tooltipElement(): HTMLElement;
84+
abstract get referenceElement(): HTMLElement;
85+
86+
modify(
87+
element: PopperSignature['Element'],
88+
positionalArgs: PopperSignature['Args']['Positional'],
89+
namedArgs: PopperSignature['Args']['Named'],
90+
): void {
91+
this.primaryElement = element;
92+
this.secondaryElement = positionalArgs[0];
93+
94+
const popperOptions = getPopperOptions(positionalArgs, namedArgs);
95+
96+
// Create the popper once all required arguments are present
97+
if (!this.popper && this.referenceElement && this.tooltipElement) {
98+
this.popper = createPopper(
99+
this.referenceElement,
100+
this.tooltipElement,
101+
popperOptions,
102+
);
103+
104+
setPopperForElement(this.primaryElement, this.popper);
105+
}
106+
this.popper?.setOptions(popperOptions);
107+
}
108+
109+
constructor(owner: Owner, args: ArgsFor<PopperSignature>) {
110+
super(owner, args);
111+
registerDestructor(this, this.cleanup);
112+
}
113+
114+
cleanup = () => {
115+
this.popper?.destroy();
116+
};
117+
}

addon/helpers/popper-modifier.js

-16
This file was deleted.

addon/helpers/popper-modifier.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { helper } from '@ember/component/helper';
2+
import { createModifier, type PopperModifierDescription } from '../index';
3+
import type { Modifier as PopperModifier } from '@popperjs/core';
4+
5+
type PopperModifierOptions = Partial<
6+
PopperModifier<unknown, { [key: string]: unknown }>
7+
>;
8+
9+
export interface PopperSignature {
10+
Args: {
11+
Positional: [string, ...PopperModifierOptions[]];
12+
Named: PopperModifierOptions;
13+
};
14+
Return: PopperModifierDescription;
15+
}
16+
17+
export function buildPopperModifier(
18+
[name, positionalOptions]: PopperSignature['Args']['Positional'],
19+
optionsHash: PopperSignature['Args']['Named'],
20+
) {
21+
const options = {
22+
...positionalOptions,
23+
...optionsHash,
24+
};
25+
26+
return createModifier({
27+
name,
28+
options,
29+
});
30+
}
31+
32+
export default helper<PopperSignature>(buildPopperModifier);

addon/in-runloop-modifier.js addon/in-runloop-modifier.ts

+13-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import {
22
begin as begingWrappingInRunLoop,
33
end as endWrappingInRunLoop,
44
} from '@ember/runloop';
5+
import {
6+
type Instance as PopperInstance,
7+
type Modifier as PopperModifier,
8+
} from '@popperjs/core';
59

610
// This set keeps track of whether a particular Popper instance is currently in an update
711
// loop that is tracked by the Ember Run Loop
@@ -14,16 +18,18 @@ import {
1418
// because the number of "starts" and "ends" of the Popper update look do not always match;
1519
// if we "start" again before we have "ended", then we can open an Ember run loop that is never
1620
// closed, which will cause our application to hang.
17-
const POPPER_IN_RUN_LOOP = new WeakSet();
21+
const POPPER_IN_RUN_LOOP: WeakSet<PopperInstance> = new WeakSet();
1822

1923
const FIRST_PHASE = 'beforeRead';
2024
const LAST_PHASE = 'afterWrite';
2125

22-
export const beginRunLoopModifier = {
26+
export const beginRunLoopModifier: Partial<
27+
PopperModifier<'ember-runloop-begin', { [key: string]: unknown }>
28+
> = {
2329
name: 'ember-runloop-begin',
2430
phase: FIRST_PHASE,
2531
enabled: true,
26-
fn({ instance }) {
32+
fn({ instance }: { instance: PopperInstance }) {
2733
if (!POPPER_IN_RUN_LOOP.has(instance)) {
2834
POPPER_IN_RUN_LOOP.add(instance);
2935

@@ -32,11 +38,13 @@ export const beginRunLoopModifier = {
3238
},
3339
};
3440

35-
export const endRunLoopModifier = {
41+
export const endRunLoopModifier: Partial<
42+
PopperModifier<'ember-runloop-end', { [key: string]: unknown }>
43+
> = {
3644
name: 'ember-runloop-end',
3745
phase: LAST_PHASE,
3846
enabled: true,
39-
fn({ instance }) {
47+
fn({ instance }: { instance: PopperInstance }) {
4048
if (POPPER_IN_RUN_LOOP.has(instance)) {
4149
POPPER_IN_RUN_LOOP.delete(instance);
4250

addon/index.js

-34
This file was deleted.

0 commit comments

Comments
 (0)