Skip to content

Commit 3636162

Browse files
authoredJan 5, 2023
Addition of floating UI helpers (#344)
* Add necessary packages * Add floating-ui modifier * Add floating-ui component * Add floating-ui story (Storybook) * Add exemplary styling for floating-ui component * Fix static floater offset & unused variable * Reorder middleware options * Add middleware control & enable default values * Remove middleware argument & restructure middleware settings (bugfix) * Fix floating-ui story (missing argument) * Add test coverage for floating-ui modifier & component * Relocate example scroller/floater structure & styling to dummy component * Disable linting error for a moment (before extending functionality) * Add functionality for the example scroller component * Enable use of both negative & positive offset values for the arrow * Convert floating-ui modifier to function-based modifier * Remove component version of floating-ui helper * Remove floating-ui Storybook story * Delete re-export of floating-ui modifier * Move floating-ui modifier to private folder & rename identifier * Adjust floating-ui modifier assertions * Move floating-ui modifier test to private folder, rename identifier & rework import * Remove dummy floater styling (not needed anymore) * Remove example scroller component & styling
1 parent 99f3cea commit 3636162

File tree

5 files changed

+214
-5
lines changed

5 files changed

+214
-5
lines changed
 
+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { modifier } from 'ember-modifier';
2+
import { assert } from '@ember/debug';
3+
4+
import {
5+
autoUpdate,
6+
computePosition,
7+
flip,
8+
hide,
9+
offset,
10+
arrow,
11+
} from '@floating-ui/dom';
12+
13+
import { merge } from 'merge-anything';
14+
15+
export default modifier(
16+
(
17+
floatingElement,
18+
[_referenceElement, _arrowElement],
19+
{ defaultPlacement = 'bottom-start', options = {} }
20+
) => {
21+
const referenceElement =
22+
typeof _referenceElement === 'string'
23+
? document.querySelector(_referenceElement)
24+
: _referenceElement;
25+
26+
const arrowElement =
27+
typeof _arrowElement === 'string'
28+
? document.querySelector(_arrowElement)
29+
: _arrowElement;
30+
31+
const defaultOptions = {
32+
floater: {
33+
offset: 6,
34+
},
35+
arrow: {
36+
offset: 4,
37+
padding: 3,
38+
position: 'min(15%, 12px)',
39+
},
40+
};
41+
options = merge(defaultOptions, options);
42+
43+
assert(
44+
`FloatingUI (modifier): No reference element was defined.`,
45+
referenceElement instanceof HTMLElement
46+
);
47+
48+
assert(
49+
`FloatingUI (modifier): The reference and floating elements cannot be the same element.`,
50+
floatingElement !== referenceElement
51+
);
52+
53+
assert(
54+
`FloatingUI (modifier): @placement must start with either 'bottom' or 'top'.`,
55+
defaultPlacement.startsWith('bottom') ||
56+
defaultPlacement.startsWith('top')
57+
);
58+
59+
Object.assign(floatingElement.style, {
60+
position: 'fixed',
61+
top: '0',
62+
left: '0',
63+
});
64+
65+
let middleware = [
66+
offset(options.floater.offset),
67+
flip(),
68+
hide({ strategy: 'referenceHidden' }),
69+
hide({ strategy: 'escaped' }),
70+
];
71+
72+
if (arrowElement) {
73+
middleware.push(
74+
arrow({
75+
element: arrowElement,
76+
padding: options.arrow.padding,
77+
})
78+
);
79+
}
80+
81+
let update = async () => {
82+
let { x, y, placement, middlewareData } = await computePosition(
83+
referenceElement,
84+
floatingElement,
85+
{
86+
middleware,
87+
placement: defaultPlacement,
88+
}
89+
);
90+
91+
Object.assign(floatingElement.style, {
92+
transform: `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`,
93+
visibility: middlewareData.hide?.referenceHidden ? 'hidden' : 'visible',
94+
});
95+
96+
if (middlewareData.arrow) {
97+
const { x } = middlewareData.arrow;
98+
const [side, alignment] = placement.split('-');
99+
const isAligned = alignment != null;
100+
101+
const unsetSides = {
102+
top: '',
103+
bottom: '',
104+
left: '',
105+
right: '',
106+
};
107+
108+
const mainSide = {
109+
top: 'bottom',
110+
bottom: 'top',
111+
}[side];
112+
113+
const rotation = {
114+
top: '180deg',
115+
bottom: '0deg',
116+
}[side];
117+
118+
const crossSide = {
119+
'top-start': 'left',
120+
'top-end': 'right',
121+
'bottom-start': 'left',
122+
'bottom-end': 'right',
123+
}[placement];
124+
125+
Object.assign(arrowElement.style, {
126+
...unsetSides,
127+
transform: `rotate(${rotation})`,
128+
});
129+
130+
if (isAligned) {
131+
Object.assign(arrowElement.style, {
132+
[crossSide]:
133+
typeof options.arrow.position === 'string'
134+
? options.arrow.position
135+
: `${options.arrow.position}px`,
136+
});
137+
} else {
138+
Object.assign(arrowElement.style, {
139+
left: x != null ? `${x}px` : '',
140+
});
141+
}
142+
143+
Object.assign(arrowElement.style, {
144+
[mainSide]: `${-options.arrow.offset}px`,
145+
});
146+
}
147+
};
148+
149+
let cleanup = autoUpdate(referenceElement, floatingElement, update);
150+
151+
return () => {
152+
cleanup();
153+
};
154+
},
155+
{ eager: false }
156+
);

‎package-lock.json

+26
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"dependencies": {
5252
"@duetds/date-picker": "^1.4.0",
5353
"@embroider/macros": "^1.9.0",
54+
"@floating-ui/dom": "^1.0.4",
5455
"@glimmer/component": "^1.1.2",
5556
"@glimmer/tracking": "^1.1.2",
5657
"@zestia/ember-auto-focus": "^4.2.0",
@@ -65,6 +66,7 @@
6566
"ember-modifier": "^3.2.7",
6667
"ember-named-blocks-polyfill": "^0.2.5",
6768
"ember-test-selectors": "^6.0.0",
69+
"merge-anything": "^5.1.3",
6870
"tracked-toolbox": "^1.2.3"
6971
},
7072
"devDependencies": {

‎tests/dummy/app/styles/app.scss

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
@import 'app/styles/ember-appuniversum.scss';
33

44
// DUMMY STYLES
5-
@import "d-component";
6-
@import "d-swatch";
7-
@import "d-editor-chrome";
8-
@import "d-editor-mockup";
5+
@import 'd-component';
6+
@import 'd-swatch';
7+
@import 'd-editor-chrome';
8+
@import 'd-editor-mockup';
99

1010
// QUICK FIXES AND HACKS
11-
@import "shame";
11+
@import 'shame';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { module, test } from 'qunit';
2+
import { setupRenderingTest } from 'ember-qunit';
3+
import { render } from '@ember/test-helpers';
4+
import { hbs } from 'ember-cli-htmlbars';
5+
6+
import FloatingUiModifier from '@appuniversum/ember-appuniversum/private/modifiers/floating-ui';
7+
8+
module('Integration | Private Modifier | floating-ui', function (hooks) {
9+
setupRenderingTest(hooks);
10+
11+
test('it renders', async function (assert) {
12+
this.set('floatingUi', FloatingUiModifier);
13+
14+
await render(hbs`
15+
<button id="reference">Reference</button>
16+
<div data-floater {{this.floatingUi "#reference"}}>Floater</div>
17+
`);
18+
19+
assert.dom('[data-floater]').hasStyle({
20+
position: 'fixed',
21+
top: '0px',
22+
left: '0px',
23+
});
24+
});
25+
});

0 commit comments

Comments
 (0)