Skip to content

Commit 223fcf8

Browse files
committed
React.js support and improvements (floating-ui#11)
* React.js support and improvements - moved the DOM manipulating logic inside the `applyStyle` modifier - the callback is now called using `.onCreate` method - added `.onUpdate` callback - improved README examples
1 parent 17c8484 commit 223fcf8

4 files changed

Lines changed: 127 additions & 54 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## v0.3.0-dev
2+
3+
- 5521b0d: added support for integration with React.js and Ember.js
4+
15
## v0.2.6
26

37
- 6732353: removed shared state;

README.md

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@
88
Popper.js is a library used to create **poppers** in web applications.
99

1010
## Wut? Poppers?
11-
A popper is an element on the screen which "pops out" from the natural flow of your application.
11+
A popper is an element on the screen which "pops out" from the natural flow of your application.
1212
Common examples of poppers are tooltips and popovers.
1313

1414
## So, yet another tooltip library?
15-
Well, basically, **no**.
16-
Popper.js is built from the ground up to being modular and fully ~~hackable~~ **customizable**.
17-
It supports a **plugin system** you can use to add particular behaviors to your poppers.
15+
Well, basically, **no**.
16+
Popper.js is built from the ground up to being modular and fully ~~hackable~~ **customizable**.
17+
It supports a **plugin system** you can use to add particular behaviors to your poppers.
1818
It's **AMD** and **CommonJS** compatible and it's well documented thanks to our [JSDoc page](https://fezvrasta.github.io/popper.js/documentation.html).
1919

2020

2121
## The Library
22-
Popper.js is mostly a library with the job of making sure your popper stays near the defined reference element (if you want so).
22+
Popper.js is mostly a library with the job of making sure your popper stays near the defined reference element (if you want so).
2323
Additionally, it provides an easy way to generate your popper element if you don't want to use one already in your DOM.
2424

2525
### Installation
@@ -71,9 +71,33 @@ var anotherPopper = new Popper(
7171
);
7272
```
7373

74+
### Callbacks
75+
```js
76+
var reference = document.querySelector('.my-button');
77+
var popper = document.querySelector('.my-popper');
78+
var anotherPopper = new Popper(reference, popper).onCreate(instance) {
79+
// instance is Popper.js instance
80+
}).onUpdate(function(data) {
81+
// data is an object containing all the informations computed by Popper.js and used to style the popper and its arrow
82+
});
83+
```
84+
85+
### React.js and Ember.js integration
86+
If you prefer to let your framework apply the styles to your DOM objects, you can follow an approach like the one below:
87+
```js
88+
var reference = document.querySelector('.my-button');
89+
var popper = document.querySelector('.my-popper');
90+
var anotherPopper = new Popper(reference, popper, {
91+
modifiersIgnored: ['applyStyle'] // prevent Popper.js from applying styles to your DOM
92+
}).onUpdate(function(data) {
93+
// export data in your framework and use its content to apply the style to your popper
94+
});
95+
```
96+
97+
7498
If you are wondering about the available options of the third argument, check out [our documentation](http://fezvrasta.github.io/popper.js/documentation.html#new_Popper_new)
7599

76-
Visit our [GitHub Page](https://fezvrasta.github.io/popper.js) to see a lot of examples of what you can already do right now!
100+
Visit our [GitHub Page](https://fezvrasta.github.io/popper.js) to see a lot of examples of what you can do right now!
77101

78102

79103
## Notes

src/popper.js

Lines changed: 75 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
arrowElement: '[x-arrow]',
7373

7474
// list of functions used to modify the offsets before they are applied to the popper
75-
modifiers: [ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip'],
75+
modifiers: [ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip', 'applyStyle'],
7676

7777
modifiersIgnored: [],
7878
};
@@ -124,29 +124,24 @@
124124
* how alter the placement when a flip is needed. (eg. in the above example, it would first flip from right to left,
125125
* then, if even in its new placement, the popper is overlapping its trigger, it will be moved to top)
126126
*
127-
* @param {Array} [options.modifiers=[ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip']]
127+
* @param {Array} [options.modifiers=[ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip', 'applyStyle']]
128128
* List of functions used to modify the data before they are applied to the popper, add your custom functions
129129
* to this array to edit the offsets and placement.
130130
* The function should reflect the @params and @returns of preventOverflow
131131
*
132132
* @param {Array} [options.modifiersIgnored=[]]
133133
* Put here any built-in modifier name you want to exclude from the modifiers list
134134
* The function should reflect the @params and @returns of preventOverflow
135-
*
136-
* @param {Function}
137-
* If the last argument of Popper.js is a function, it will be executed after the initialization of the popper
138-
* it's scope will be window, the first argument will be the Popper.js instance.
139135
*/
140-
function Popper(trigger, popper, options/*, callback*/) {
136+
function Popper(trigger, popper, options) {
141137
this._trigger = trigger;
142138
this.state = {};
143139

144140
// if the popper variable is a configuration object, parse it to generate an HTMLElement
145141
// generate a default popper if is not defined
146142
var isNotDefined = popper === undefined || popper === null;
147-
var isFunction = typeof popper === 'function';
148143
var isConfig = popper && popper.constructor.name === 'Object';
149-
if ( isNotDefined || isFunction || isConfig) {
144+
if ( isNotDefined || isConfig) {
150145
this._popper = this.parse(isConfig ? popper : {});
151146
}
152147
// otherwise, use the given HTMLElement as popper
@@ -179,10 +174,6 @@
179174

180175
// setup event listeners, they will take care of update the position in specific situations
181176
this._setupEventListeners();
182-
183-
if (typeof arguments[arguments.length -1] === 'function') {
184-
arguments[arguments.length -1].call(root, this);
185-
}
186177
}
187178

188179

@@ -224,35 +215,32 @@
224215

225216
data = this.runModifiers(data, this._options.modifiers);
226217

227-
// apply the final offsets to the popper
228-
// NOTE: 1 DOM access here
229-
var styles = {
230-
position: data.offsets.popper.position
231-
};
232-
233-
// round top and left to avoid blurry text
234-
var left = Math.round(data.offsets.popper.left);
235-
var top = Math.round(data.offsets.popper.top);
236-
237-
// if gpuAcceleration is set to true and transform is supported, we use `translate3d` to apply the position to the popper
238-
// we automatically use the supported prefixed version if needed
239-
var prefixedProperty;
240-
if (this._options.gpuAcceleration && (prefixedProperty = getSupportedPropertyName('transform'))) {
241-
styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
242-
styles.top = 0;
243-
styles.left = 0;
244-
}
245-
// othwerise, we use the standard `left` and `top` properties
246-
else {
247-
styles.left =left;
248-
styles.top = top;
218+
if (typeof this.state.updateCallback === 'function') {
219+
this.state.updateCallback(data);
249220
}
250221

251-
setStyle(this._popper, styles);
222+
};
252223

253-
// set an attribute which will be useful to style the tooltip (use it to properly position its arrow)
254-
// NOTE: 1 DOM access here
255-
this._popper.setAttribute('x-placement', data.placement);
224+
/**
225+
* If a function is passed, it will be executed after the initialization of popper with as first argument the Popper instance.
226+
* @method
227+
* @memberof Popper
228+
* @param {Function} callback
229+
*/
230+
Popper.prototype.onCreate = function(callback) {
231+
// the createCallbacks return as first argument the popper instance
232+
callback(this);
233+
};
234+
235+
/**
236+
* If a function is passed, it will be executed after each update of popper with as first argument the set of coordinates and informations
237+
* used to style popper and its arrow.
238+
* @method
239+
* @memberof Popper
240+
* @param {Function} callback
241+
*/
242+
Popper.prototype.onUpdate = function(callback) {
243+
this.state.updateCallback = callback;
256244
};
257245

258246
/**
@@ -565,6 +553,52 @@
565553
*/
566554
Popper.prototype.modifiers = {};
567555

556+
/**
557+
* Apply the computed styles to the popper element
558+
* @method
559+
* @memberof Popper.modifiers
560+
* @argument {Object} data - The data object generated by `update` method
561+
* @returns {Object} The same data object
562+
*/
563+
Popper.prototype.modifiers.applyStyle = function(data) {
564+
// apply the final offsets to the popper
565+
// NOTE: 1 DOM access here
566+
var styles = {
567+
position: data.offsets.popper.position
568+
};
569+
570+
// round top and left to avoid blurry text
571+
var left = Math.round(data.offsets.popper.left);
572+
var top = Math.round(data.offsets.popper.top);
573+
574+
// if gpuAcceleration is set to true and transform is supported, we use `translate3d` to apply the position to the popper
575+
// we automatically use the supported prefixed version if needed
576+
var prefixedProperty;
577+
if (this._options.gpuAcceleration && (prefixedProperty = getSupportedPropertyName('transform'))) {
578+
styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
579+
styles.top = 0;
580+
styles.left = 0;
581+
}
582+
// othwerise, we use the standard `left` and `top` properties
583+
else {
584+
styles.left =left;
585+
styles.top = top;
586+
}
587+
588+
setStyle(this._popper, styles);
589+
590+
// set an attribute which will be useful to style the tooltip (use it to properly position its arrow)
591+
// NOTE: 1 DOM access here
592+
this._popper.setAttribute('x-placement', data.placement);
593+
594+
// if the arrow modifier is required and the arrow style has been computed, apply the arrow style
595+
if (this.isModifierRequired(this.modifiers.applyStyle, this.modifiers.arrow) && data.offsets.arrow) {
596+
setStyle(data.arrowElement, data.offsets.arrow);
597+
}
598+
599+
return data;
600+
};
601+
568602
/**
569603
* Modifier used to shift the popper on the start or end of its reference element side
570604
* @method
@@ -889,8 +923,8 @@
889923
arrowStyle.left = left;
890924
arrowStyle.top = ''; // make sure to remove any old style from the arrow
891925
}
892-
893-
setStyle(arrow, arrowStyle);
926+
data.offsets.arrow = arrowStyle;
927+
data.arrowElement = arrow;
894928

895929
return data;
896930
};

tests/test-popper.js

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ describe('Popper.js', function() {
100100
var ref = appendNewRef(1, 'ref', relative);
101101
var popper = appendNewPopper(2, 'popper');
102102

103-
new TestPopper(ref, popper, function(pop) {
103+
new TestPopper(ref, popper).onCreate(function(pop) {
104104
expect(popper.getBoundingClientRect().top).toBeApprox(63);
105105
expect(popper.getBoundingClientRect().left).toBeApprox(5);
106106
pop.destroy();
@@ -120,7 +120,7 @@ describe('Popper.js', function() {
120120
ref.style.marginTop = '200px';
121121
var popper = appendNewPopper(2, 'popper');
122122

123-
new TestPopper(ref, popper, function(pop) {
123+
new TestPopper(ref, popper).onCreate(function(pop) {
124124
expect(popper.getBoundingClientRect().top).toBeApprox(-800 + 263);
125125
expect(popper.getBoundingClientRect().left).toBeApprox(5);
126126
pop.destroy();
@@ -152,7 +152,7 @@ describe('Popper.js', function() {
152152
ref.style.marginTop = '200px';
153153
var popper = appendNewPopper(2, 'popper');
154154

155-
new TestPopper(ref, popper, function(pop) {
155+
new TestPopper(ref, popper).onCreate(function(pop) {
156156
// force redraw
157157
window.dispatchEvent(new Event('resize'));
158158

@@ -180,7 +180,7 @@ describe('Popper.js', function() {
180180
var ref = appendNewRef(1, 'ref', fixed);
181181
var popper = appendNewPopper(2, 'popper', fixed);
182182

183-
new TestPopper(ref, popper, function(pop) {
183+
new TestPopper(ref, popper).onCreate(function(pop) {
184184
// force redraw
185185
window.dispatchEvent(new Event('resize'));
186186

@@ -195,7 +195,7 @@ describe('Popper.js', function() {
195195
var reference = appendNewRef(1);
196196
var popper = appendNewPopper(2);
197197

198-
new TestPopper(reference, popper, function(pop) {
198+
new TestPopper(reference, popper).onCreate(function(pop) {
199199
pop.destroy();
200200
expect(popper.style.top).toBe('');
201201
done();
@@ -205,7 +205,7 @@ describe('Popper.js', function() {
205205
it('creates a popper using the default configuration', function(done) {
206206
var reference = appendNewRef(1);
207207

208-
new TestPopper(reference, function(instance) {
208+
new TestPopper(reference).onCreate(function(instance) {
209209
expect(document.querySelectorAll('.popper').length).toBe(1);
210210
document.body.removeChild(instance._popper);
211211
done();
@@ -217,10 +217,21 @@ describe('Popper.js', function() {
217217

218218
new TestPopper(reference, {
219219
content: 'something'
220-
}, function(instance) {
220+
}).onCreate(function(instance) {
221221
expect(instance._popper.innerText).toBe('something');
222222
document.body.removeChild(instance._popper);
223223
done();
224224
});
225225
});
226+
227+
it('creates a popper and sets an onUpdate callback', function(done) {
228+
var reference = appendNewRef(1);
229+
230+
new TestPopper(reference, {content: 'react'}, {
231+
modifiersIgnored: ['applyStyle']
232+
}).onUpdate(function(data) {
233+
expect(data.offsets.popper.top).toBeApprox(46);
234+
done();
235+
});
236+
});
226237
});

0 commit comments

Comments
 (0)