diff --git a/package.json b/package.json
index 407db4520f232..4c2bab9110dc8 100644
--- a/package.json
+++ b/package.json
@@ -149,6 +149,7 @@
"querystring-browser": "1.0.4",
"raw-loader": "0.5.1",
"request": "2.61.0",
+ "resize-observer-polyfill": "1.2.1",
"rimraf": "2.4.3",
"rison-node": "1.0.0",
"rjs-repack-loader": "1.0.6",
diff --git a/src/core_plugins/console/public/src/sense_editor_resize.js b/src/core_plugins/console/public/src/sense_editor_resize.js
index ef989c1b63493..3644c6aef1793 100644
--- a/src/core_plugins/console/public/src/sense_editor_resize.js
+++ b/src/core_plugins/console/public/src/sense_editor_resize.js
@@ -1,4 +1,4 @@
-import ResizeCheckerProvider from 'ui/vislib/lib/resize_checker'
+import { ResizeCheckerProvider } from 'ui/resize_checker'
export function useResizeCheckerProvider(Private) {
const ResizeChecker = Private(ResizeCheckerProvider);
diff --git a/src/ui/public/reflow_watcher/__tests__/reflow_watcher.js b/src/ui/public/reflow_watcher/__tests__/reflow_watcher.js
deleted file mode 100644
index 1b1f8bafba505..0000000000000
--- a/src/ui/public/reflow_watcher/__tests__/reflow_watcher.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import 'angular';
-import $ from 'jquery';
-import _ from 'lodash';
-import expect from 'expect.js';
-import sinon from 'auto-release-sinon';
-import ngMock from 'ng_mock';
-import EventsProvider from 'ui/events';
-import ReflowWatcherProvider from 'ui/reflow_watcher';
-describe('Reflow watcher', function () {
-
- const $body = $(document.body);
- const $window = $(window);
- const expectStubbedEventAndEl = function (stub, event, $el) {
- expect(stub.getCalls().some(function (call) {
- const events = call.args[0].split(' ');
- return _.contains(events, event) && $el.is(call.thisValue);
- })).to.be(true);
- };
-
- let EventEmitter;
- let reflowWatcher;
- let $rootScope;
- let $onStub;
-
- beforeEach(ngMock.module('kibana', function () {
- // stub jQuery's $.on method while creating the reflowWatcher
- $onStub = sinon.stub($.fn, 'on');
- }));
- beforeEach(ngMock.inject(function (Private, $injector) {
- $rootScope = $injector.get('$rootScope');
- EventEmitter = Private(EventsProvider);
- reflowWatcher = Private(ReflowWatcherProvider);
- // setup the reflowWatchers $http watcher
- $rootScope.$apply();
- }));
- afterEach(function () {
- $onStub.restore();
- });
-
- it('is an event emitter', function () {
- expect(reflowWatcher).to.be.an(EventEmitter);
- });
-
- describe('listens', function () {
- it('to "mouseup" on the body', function () {
- expectStubbedEventAndEl($onStub, 'mouseup', $body);
- });
-
- it('to "resize" on the window', function () {
- expectStubbedEventAndEl($onStub, 'resize', $window);
- });
- });
-
- describe('un-listens in #destroy()', function () {
- let $offStub;
-
- beforeEach(function () {
- $offStub = sinon.stub($.fn, 'off');
- reflowWatcher.destroy();
- $offStub.restore();
- });
-
- it('to "mouseup" on the body', function () {
- expectStubbedEventAndEl($offStub, 'mouseup', $body);
- });
-
- it('to "resize" on the window', function () {
- expectStubbedEventAndEl($offStub, 'resize', $window);
- });
- });
-
- it('triggers the "reflow" event within a new angular tick', function () {
- const stub = sinon.stub();
- reflowWatcher.on('reflow', stub);
- reflowWatcher.trigger();
-
- expect(stub).to.have.property('callCount', 0);
- $rootScope.$apply();
- expect(stub).to.have.property('callCount', 1);
- });
-});
diff --git a/src/ui/public/reflow_watcher/reflow_watcher.js b/src/ui/public/reflow_watcher/reflow_watcher.js
deleted file mode 100644
index 43b052a5bafc8..0000000000000
--- a/src/ui/public/reflow_watcher/reflow_watcher.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import angular from 'angular';
-import $ from 'jquery';
-import _ from 'lodash';
-import EventsProvider from 'ui/events';
-export default function ReflowWatcherService(Private, $rootScope, $http) {
-
- const EventEmitter = Private(EventsProvider);
- const $body = $(document.body);
- const $window = $(window);
-
- const MOUSE_EVENTS = 'mouseup';
- const WINDOW_EVENTS = 'resize';
-
- _.class(ReflowWatcher).inherits(EventEmitter);
- /**
- * Watches global activity which might hint at a change in the content, which
- * in turn provides a hint to resizers that they should check their size
- */
- function ReflowWatcher() {
- ReflowWatcher.Super.call(this);
-
- // bound version of trigger that can be used as a handler
- this.trigger = _.bind(this.trigger, this);
- this._emitReflow = _.bind(this._emitReflow, this);
-
- // list of functions to call that will unbind our watchers
- this._unwatchers = [
- $rootScope.$watchCollection(function () {
- return $http.pendingRequests;
- }, this.trigger)
- ];
-
- $body.on(MOUSE_EVENTS, this.trigger);
- $window.on(WINDOW_EVENTS, this.trigger);
- }
-
- /**
- * Simply emit reflow, but in a way that can be bound and passed to
- * other functions. Using _.bind caused extra arguments to be added, and
- * then emitted to other places. No Bueno
- *
- * @return {void}
- */
- ReflowWatcher.prototype._emitReflow = function () {
- this.emit('reflow');
- };
-
- /**
- * Emit the "reflow" event in the next tick of the digest cycle
- * @return {void}
- */
- ReflowWatcher.prototype.trigger = function () {
- $rootScope.$evalAsync(this._emitReflow);
- };
-
- /**
- * Signal to the ReflowWatcher that it should clean up it's listeners
- * @return {void}
- */
- ReflowWatcher.prototype.destroy = function () {
- $body.off(MOUSE_EVENTS, this.trigger);
- $window.off(WINDOW_EVENTS, this.trigger);
- _.callEach(this._unwatchers);
- };
-
- return new ReflowWatcher();
-}
diff --git a/src/ui/public/resize_checker/__tests__/resize_checker.js b/src/ui/public/resize_checker/__tests__/resize_checker.js
new file mode 100644
index 0000000000000..80c8f7e6726df
--- /dev/null
+++ b/src/ui/public/resize_checker/__tests__/resize_checker.js
@@ -0,0 +1,145 @@
+import $ from 'jquery';
+import { delay, fromNode } from 'bluebird';
+import expect from 'expect.js';
+import sinon from 'auto-release-sinon';
+
+import ngMock from 'ng_mock';
+import EventsProvider from 'ui/events';
+import NoDigestPromises from 'test_utils/no_digest_promises';
+
+import { ResizeCheckerProvider } from '../resize_checker';
+
+describe('Resize Checker', () => {
+ NoDigestPromises.activateForSuite();
+
+ const teardown = [];
+ let setup;
+
+ beforeEach(ngMock.module('kibana'));
+ beforeEach(ngMock.inject(($injector) => {
+ setup = () => {
+ const Private = $injector.get('Private');
+ const ResizeChecker = Private(ResizeCheckerProvider);
+ const EventEmitter = Private(EventsProvider);
+
+ const createEl = () => {
+ const el = $('
').appendTo('body').get(0);
+ teardown.push(() => $(el).remove());
+ return el;
+ };
+
+ const createChecker = el => {
+ const checker = new ResizeChecker(el);
+ teardown.push(() => checker.destroy());
+ return checker;
+ };
+
+ const createListener = () => {
+ let resolveFirstCallPromise;
+ const listener = sinon.spy(() => resolveFirstCallPromise());
+ listener.firstCallPromise = new Promise(resolve => (resolveFirstCallPromise = resolve));
+ return listener;
+ };
+
+ return { EventEmitter, createEl, createChecker, createListener };
+ };
+ }));
+
+ afterEach(() => {
+ teardown.splice(0).forEach(fn => {
+ fn();
+ });
+ });
+
+ describe('contruction', () => {
+ it('accepts a jQuery wrapped element', () => {
+ const { createChecker } = setup();
+
+ createChecker($('
'));
+ });
+ });
+
+ describe('events', () => {
+ it('is an event emitter', () => {
+ const { createEl, createChecker, EventEmitter } = setup();
+
+ const checker = createChecker(createEl());
+ expect(checker).to.be.a(EventEmitter);
+ });
+
+ it('emits a "resize" event asynchronously', async () => {
+ const { createEl, createChecker, createListener } = setup();
+
+ const el = createEl();
+ const checker = createChecker(el);
+ const listener = createListener();
+
+ checker.on('resize', listener);
+ $(el).height(100);
+ sinon.assert.notCalled(listener);
+ await listener.firstCallPromise;
+ sinon.assert.calledOnce(listener);
+ });
+ });
+
+ describe('#modifySizeWithoutTriggeringResize()', () => {
+ it(`does not emit "resize" events caused by the block`, async () => {
+ const { createChecker, createEl, createListener } = setup();
+
+ const el = createEl();
+ const checker = createChecker(el);
+ const listener = createListener();
+
+ checker.on('resize', listener);
+ checker.modifySizeWithoutTriggeringResize(() => {
+ $(el).height(100);
+ });
+ await delay(1000);
+ sinon.assert.notCalled(listener);
+ });
+
+ it('does emit "resize" when modification is made between the block and resize notification', async () => {
+ const { createChecker, createEl, createListener } = setup();
+
+ const el = createEl();
+ const checker = createChecker(el);
+ const listener = createListener();
+
+ checker.on('resize', listener);
+ checker.modifySizeWithoutTriggeringResize(() => {
+ $(el).height(100);
+ });
+ sinon.assert.notCalled(listener);
+ $(el).height(200);
+ await listener.firstCallPromise;
+ sinon.assert.calledOnce(listener);
+ });
+ });
+
+ describe('#destroy()', () => {
+ it('destroys internal observer instance', () => {
+ const { createChecker, createEl, createListener } = setup();
+
+ const checker = createChecker(createEl());
+ const listener = createListener();
+
+ checker.destroy();
+ expect(!checker._observer).to.be(true);
+ });
+
+ it('does not emit future resize events', async () => {
+ const { createChecker, createEl, createListener } = setup();
+
+ const el = createEl();
+ const checker = createChecker(el);
+ const listener = createListener();
+
+ checker.on('resize', listener);
+ checker.destroy();
+
+ $(el).height(100);
+ await delay(1000);
+ sinon.assert.notCalled(listener);
+ });
+ });
+});
diff --git a/src/ui/public/resize_checker/index.js b/src/ui/public/resize_checker/index.js
new file mode 100644
index 0000000000000..fbc47d1f9afd7
--- /dev/null
+++ b/src/ui/public/resize_checker/index.js
@@ -0,0 +1 @@
+export { ResizeCheckerProvider } from './resize_checker';
diff --git a/src/ui/public/resize_checker/resize_checker.js b/src/ui/public/resize_checker/resize_checker.js
new file mode 100644
index 0000000000000..df6d7009f8825
--- /dev/null
+++ b/src/ui/public/resize_checker/resize_checker.js
@@ -0,0 +1,94 @@
+import $ from 'jquery';
+import ResizeObserver from 'resize-observer-polyfill';
+import { uniqueId, isEqual } from 'lodash';
+
+import EventsProvider from 'ui/events';
+
+export function ResizeCheckerProvider(Private) {
+ const EventEmitter = Private(EventsProvider);
+
+ function validateElArg(el) {
+ // the ResizeChecker historically accepted jquery elements,
+ // so we wrap in jQuery then extract the element
+ const $el = $(el);
+
+ if ($el.size() !== 1) {
+ throw new TypeError('ResizeChecker must be constructed with a single DOM element.');
+ }
+
+ return $el.get(0);
+ }
+
+ function getSize(el) {
+ return [el.clientWidth, el.clientHeight];
+ }
+
+ /**
+ * ResizeChecker receives an element and emits a "resize"
+ * event every time it changes size. Used by the vislib to re-render
+ * visualizations on resize as well as the console for the
+ * same reason, but for the editors.
+ */
+ return class ResizeChecker extends EventEmitter {
+ constructor(el) {
+ super();
+
+ this._el = validateElArg(el);
+
+ // the width and height of the element that we expect to see
+ // on the next resize notification. If it matches the size at
+ // the time of the notifications then it we will be ignored.
+ this._expectedSize = getSize(this._el);
+
+ this._observer = new ResizeObserver(() => {
+ if (this._expectedSize) {
+ const sameSize = isEqual(getSize(this._el), this._expectedSize);
+ this._expectedSize = null;
+
+ if (sameSize) {
+ // don't trigger resize notification if the size is what we expect
+ return;
+ }
+ }
+
+ this.emit('resize');
+ });
+
+ this._observer.observe(this._el);
+ }
+
+ /**
+ * Run a function and ignore all resizes that occur
+ * while it's running.
+ *
+ * @return {undefined}
+ */
+ modifySizeWithoutTriggeringResize(block) {
+ try {
+ block();
+ } finally {
+ this._expectedSize = getSize(this._el);
+ }
+ }
+
+ /**
+ * Tell the ResizeChecker to shutdown, stop listenings, and never
+ * emit another resize event.
+ *
+ * Cleans up it's listeners and timers.
+ *
+ * @method destroy
+ * @return {void}
+ */
+ destroy() {
+ if (this._destroyed) return;
+ this._destroyed = true;
+
+ this._observer.disconnect();
+ this._observer = null;
+ this._expectedSize = null;
+ this._el = null;
+ this.removeAllListeners();
+ }
+ };
+}
diff --git a/src/ui/public/utils/__tests__/sequencer.js b/src/ui/public/utils/__tests__/sequencer.js
deleted file mode 100644
index 3a163ff6ad76e..0000000000000
--- a/src/ui/public/utils/__tests__/sequencer.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import _ from 'lodash';
-import sequencer from 'ui/utils/sequencer';
-import expect from 'expect.js';
-describe('sequencer util', function () {
-
- const opts = [
- { min: 500, max: 7500, length: 1500 },
- { min: 50, max: 500, length: 1000 },
- { min: 5, max: 50, length: 100 }
- ];
-
- function eachSeqFor(method, fn) {
- opts.forEach(function (args) {
- fn(method(args.min, args.max, args.length), args);
- });
- }
-
- function getSlopes(seq, count) {
- return _.chunk(seq, Math.ceil(seq.length / count)).map(function (chunk) {
- return (_.last(chunk) - _.first(chunk)) / chunk.length;
- });
- }
-
- // using expect() here causes massive GC runs because seq can be +1000 elements
- function expectedChange(seq, up) {
- up = !!up;
-
- if (seq.length < 2) {
- throw new Error('unable to reach change without at least two elements');
- }
-
- seq.forEach(function (n, i) {
- if (i > 0 && (seq[i - 1] < n) !== up) {
- throw new Error('expected values to ' + (up ? 'increase' : 'decrease'));
- }
- });
- }
-
- function generalTests(seq, args) {
- it('obeys the min arg', function () {
- expect(Math.min.apply(Math, seq)).to.be(args.min);
- });
-
- it('obeys the max arg', function () {
- expect(Math.max.apply(Math, seq)).to.be(args.max);
- });
-
- it('obeys the length arg', function () {
- expect(seq).to.have.length(args.length);
- });
-
- it('always creates increasingly larger values', function () {
- expectedChange(seq, true);
- });
- }
-
- describe('#createEaseIn', function () {
- eachSeqFor(sequencer.createEaseIn, function (seq, args) {
- describe('with args: ' + JSON.stringify(args), function () {
- generalTests(seq, args);
-
- it('produces increasing slopes', function () {
- expectedChange(getSlopes(seq, 2), true);
- expectedChange(getSlopes(seq, 4), true);
- expectedChange(getSlopes(seq, 6), true);
- });
- });
- });
- });
-
- describe('#createEaseOut', function () {
- eachSeqFor(sequencer.createEaseOut, function (seq, args) {
- describe('with args: ' + JSON.stringify(args), function () {
- generalTests(seq, args);
-
- it('produces decreasing slopes', function () {
- expectedChange(getSlopes(seq, 2), false);
- expectedChange(getSlopes(seq, 4), false);
- expectedChange(getSlopes(seq, 6), false);
- });
-
- // Flipped version of previous test to ensure that expectedChange()
- // and friends are behaving properly
- it('doesn\'t produce increasing slopes', function () {
- expect(function () {
- expectedChange(getSlopes(seq, 2), true);
- }).to.throwError();
-
- expect(function () {
- expectedChange(getSlopes(seq, 4), true);
- }).to.throwError();
-
- expect(function () {
- expectedChange(getSlopes(seq, 6), true);
- }).to.throwError();
- });
- });
- });
- });
-});
diff --git a/src/ui/public/utils/sequencer.js b/src/ui/public/utils/sequencer.js
deleted file mode 100644
index 5783d1dcb9c08..0000000000000
--- a/src/ui/public/utils/sequencer.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import _ from 'lodash';
-
-function create(min, max, length, mod) {
- const seq = new Array(length);
-
- const valueDist = max - min;
-
- // range of values that the mod creates
- const modRange = [mod(0, length), mod(length - 1, length)];
-
- // distance between
- const modRangeDist = modRange[1] - modRange[0];
-
- _.times(length, function (i) {
- const modIPercent = (mod(i, length) - modRange[0]) / modRangeDist;
-
- // percent applied to distance and added to min to
- // produce value
- seq[i] = min + (valueDist * modIPercent);
- });
-
- seq.min = min;
- seq.max = max;
-
- return seq;
-}
-
-export default {
- /**
- * Create an exponential sequence of numbers.
- *
- * Creates a curve resembling:
- *
- * ;
- * /
- * /
- * .-'
- * _.-"
- * _.-'"
- * _,.-'"
- * _,..-'"
- * _,..-'""
- * _,..-'""
- * ____,..--'""
- *
- * @param {number} min - the min value to produce
- * @param {number} max - the max value to produce
- * @param {number} length - the number of values to produce
- * @return {number[]} - an array containing the sequence
- */
- createEaseIn: _.partialRight(create, function (i, length) {
- // generates numbers from 1 to +Infinity
- return i * Math.pow(i, 1.1111);
- }),
-
- /**
- * Create an sequence of numbers using sine.
- *
- * Create a curve resembling:
- *
- * ____,..--'""
- * _,..-'""
- * _,..-'""
- * _,..-'"
- * _,.-'"
- * _.-'"
- * _.-"
- * .-'
- * /
- * /
- * ;
- *
- *
- * @param {number} min - the min value to produce
- * @param {number} max - the max value to produce
- * @param {number} length - the number of values to produce
- * @return {number[]} - an array containing the sequence
- */
- createEaseOut: _.partialRight(create, function (i, length) {
- // adapted from output of http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
- // generates numbers from 0 to 100
-
- const ts = (i /= length) * i;
- const tc = ts * i;
- return 100 * (
- 0.5 * tc * ts +
- -3 * ts * ts +
- 6.5 * tc +
- -7 * ts +
- 4 * i
- );
- })
-};
diff --git a/src/ui/public/vislib/__tests__/lib/resize_checker.js b/src/ui/public/vislib/__tests__/lib/resize_checker.js
deleted file mode 100644
index bafaef16eaf2d..0000000000000
--- a/src/ui/public/vislib/__tests__/lib/resize_checker.js
+++ /dev/null
@@ -1,203 +0,0 @@
-import $ from 'jquery';
-import _ from 'lodash';
-import Promise from 'bluebird';
-import ngMock from 'ng_mock';
-import expect from 'expect.js';
-import sinon from 'auto-release-sinon';
-import VislibLibResizeCheckerProvider from 'ui/vislib/lib/resize_checker';
-import EventsProvider from 'ui/events';
-import ReflowWatcherProvider from 'ui/reflow_watcher';
-
-describe('Vislib Resize Checker', function () {
-
- require('test_utils/no_digest_promises').activateForSuite();
-
- let ResizeChecker;
- let EventEmitter;
- let checker;
- let reflowWatcher;
- const reflowSpies = {};
-
- beforeEach(ngMock.module('kibana'));
-
- beforeEach(ngMock.inject(function (Private) {
- ResizeChecker = Private(VislibLibResizeCheckerProvider);
- EventEmitter = Private(EventsProvider);
- reflowWatcher = Private(ReflowWatcherProvider);
- reflowSpies.on = sinon.spy(reflowWatcher, 'on');
- reflowSpies.off = sinon.spy(reflowWatcher, 'off');
-
- const $el = $(document.createElement('div'))
- .appendTo('body')
- .css('visibility', 'hidden')
- .get(0);
-
- checker = new ResizeChecker($el);
- }));
-
- afterEach(function () {
- checker.$el.remove();
- checker.destroy();
- });
-
- describe('basic functionality', function () {
- it('is an event emitter', function () {
- expect(checker).to.be.a(EventEmitter);
- });
-
- it('listens for the "reflow" event of the reflowWatchers', function () {
- expect(reflowSpies.on).to.have.property('callCount', 1);
- const call = reflowSpies.on.getCall(0);
- expect(call.args[0]).to.be('reflow');
- });
-
- it('emits a "resize" event when the el is resized', function (done) {
- checker.on('resize', function () {
- done();
- });
-
- checker.$el.text('haz contents');
- checker.check();
- });
- });
-
- describe('#read', function () {
- it('gets the proper dimensions for the element', function () {
- const dimensions = checker.read();
- const windowWidth = document.documentElement.clientWidth;
-
- expect(dimensions.w).to.equal(windowWidth);
- expect(dimensions.h).to.equal(0);
- });
- });
-
- describe('#saveSize', function () {
- it('calls #read() when no arg is passed', function () {
- const stub = sinon.stub(checker, 'read').returns({});
-
- checker.saveSize();
-
- expect(stub).to.have.property('callCount', 1);
- });
-
- it('saves the size of the element', function () {
- const football = {};
- checker.saveSize(football);
- expect(checker).to.have.property('_savedSize', football);
- });
-
- it('returns false if the size matches the previous value', function () {
- expect(checker.saveSize(checker._savedSize)).to.be(false);
- });
-
- it('returns true if the size is different than previous value', function () {
- expect(checker.saveSize({})).to.be(true);
- });
- });
-
- describe('#check()', function () {
- let emit;
-
- beforeEach(function () {
- emit = sinon.stub(checker, 'emit');
-
- // prevent the checker from auto-checking
- checker.destroy();
- checker.startSchedule = checker.continueSchedule = _.noop;
- });
-
- it('does not emit "resize" immediately after a resize, but waits for changes to stop', function () {
- expect(checker).to.have.property('_isDirty', false);
-
- checker.$el.css('height', 100);
- checker.check();
-
- expect(checker).to.have.property('_isDirty', true);
- expect(emit).to.have.property('callCount', 0);
-
- // no change in el size
- checker.check();
-
- expect(checker).to.have.property('_isDirty', false);
- expect(emit).to.have.property('callCount', 1);
- });
-
- it('emits "resize" based on MS_MAX_RESIZE_DELAY, even if el\'s constantly changing size', function () {
- const steps = _.random(5, 10);
- this.slow(steps * 10);
-
- // we are going to fake the delay using the fake clock
- const msStep = Math.floor(ResizeChecker.MS_MAX_RESIZE_DELAY / (steps - 1));
- const clock = sinon.useFakeTimers();
-
- _.times(steps, function step(i) {
- checker.$el.css('height', 100 + i);
- checker.check();
-
- expect(checker).to.have.property('_isDirty', true);
- expect(emit).to.have.property('callCount', i > steps ? 1 : 0);
-
- clock.tick(msStep); // move the clock forward one step
- });
-
- });
- });
-
- describe('#destroy()', function () {
- it('removes the "reflow" event from the reflowWatcher', function () {
- const onCall = reflowSpies.on.getCall(0);
- const handler = onCall.args[1];
-
- checker.destroy();
- expect(reflowSpies.off).to.have.property('callCount', 1);
- expect(reflowSpies.off.calledWith('reflow', handler)).to.be.ok();
- });
-
- it('clears the timeout', function () {
- const spy = sinon.spy(window, 'clearTimeout');
- checker.destroy();
- expect(spy).to.have.property('callCount', 1);
- });
- });
-
- describe('scheduling', function () {
- let clock;
- let schedule;
-
- beforeEach(function () {
- // prevent the checker from running automatically
- checker.destroy();
- clock = sinon.useFakeTimers();
-
- schedule = [];
- _.times(25, function () {
- schedule.push(_.random(3, 250));
- });
- });
-
- it('walks the schedule, using each value as it\'s next timeout', function () {
- let timerId = checker.startSchedule(schedule);
-
- // start at 0 even though "start" used the first slot, we will still check it
- for (let i = 0; i < schedule.length; i++) {
- expect(clock.timers[timerId]).to.have.property('callAt', schedule[i]);
- timerId = checker.continueSchedule();
- }
- });
-
- it('repeats the last value in the schedule', function () {
- let timerId = checker.startSchedule(schedule);
-
- // start at 1, and go until there is one left
- for (let i = 1; i < schedule.length - 1; i++) {
- timerId = checker.continueSchedule();
- }
-
- const last = _.last(schedule);
- _.times(5, function () {
- const timer = clock.timers[checker.continueSchedule()];
- expect(timer).to.have.property('callAt', last);
- });
- });
- });
-});
diff --git a/src/ui/public/vislib/lib/resize_checker.js b/src/ui/public/vislib/lib/resize_checker.js
deleted file mode 100644
index 69fcf343591a9..0000000000000
--- a/src/ui/public/vislib/lib/resize_checker.js
+++ /dev/null
@@ -1,203 +0,0 @@
-import $ from 'jquery';
-import _ from 'lodash';
-import sequencer from 'ui/utils/sequencer';
-import EventsProvider from 'ui/events';
-import ReflowWatcherProvider from 'ui/reflow_watcher';
-export default function ResizeCheckerFactory(Private, Notifier) {
-
- const EventEmitter = Private(EventsProvider);
- const reflowWatcher = Private(ReflowWatcherProvider);
-
- const SCHEDULE = ResizeChecker.SCHEDULE = sequencer.createEaseIn(
- 100, // shortest delay
- 10000, // longest delay
- 50 // tick count
- );
-
- // maximum ms that we can delay emitting 'resize'. This is only used
- // to debounce resizes when the size of the element is constantly changing
- const MS_MAX_RESIZE_DELAY = ResizeChecker.MS_MAX_RESIZE_DELAY = 500;
-
- /**
- * Checks the size of an element on a regular basis. Provides
- * an event that is emited when the element has changed size.
- *
- * @class ResizeChecker
- * @param {HtmlElement} el - the element to track the size of
- */
- _.class(ResizeChecker).inherits(EventEmitter);
- function ResizeChecker(el) {
- ResizeChecker.Super.call(this);
-
- this.$el = $(el);
- this.notify = new Notifier({ location: 'Vislib ResizeChecker ' + _.uniqueId() });
-
- this.saveSize();
-
- this.check = _.bind(this.check, this);
- this.check();
-
- this.onReflow = _.bind(this.onReflow, this);
- reflowWatcher.on('reflow', this.onReflow);
- }
-
- ResizeChecker.prototype.onReflow = function () {
- this.startSchedule(SCHEDULE);
- };
-
- /**
- * Read the size of the element
- *
- * @method read
- * @return {object} - an object with keys `w` (width) and `h` (height)
- */
- ResizeChecker.prototype.read = function () {
- return {
- w: this.$el[0].clientWidth,
- h: this.$el[0].clientHeight
- };
- };
-
-
- /**
- * Save the element size, preventing it from being considered as an
- * update.
- *
- * @method save
- * @param {object} [size] - optional size to save, otherwise #read() is called
- * @return {boolean} - true if their was a change in the new
- */
- ResizeChecker.prototype.saveSize = function (size) {
- if (!size) size = this.read();
-
- if (this._equalsSavedSize(size)) {
- return false;
- }
-
- this._savedSize = size;
- return true;
- };
-
-
- /**
- * Determine if a given size matches the currently saved size.
- *
- * @private
- * @method _equalsSavedSize
- * @param {object} a - an object that matches the return value of #read()
- * @return {boolean} - true if the passed in value matches the saved size
- */
- ResizeChecker.prototype._equalsSavedSize = function (a) {
- const b = this._savedSize || {};
- return a.w === b.w && a.h === b.h;
- };
-
- /**
- * Read the time that the dirty state last changed.
- *
- * @method lastDirtyChange
- * @return {timestamp} - the unix timestamp (in ms) of the last update
- * to the dirty state
- */
- ResizeChecker.prototype.lastDirtyChange = function () {
- return this._dirtyChangeStamp;
- };
-
- /**
- * Record the dirty state
- *
- * @method saveDirty
- * @param {boolean} val
- * @return {boolean} - true if the dirty state changed by this save
- */
- ResizeChecker.prototype.saveDirty = function (val) {
- val = !!val;
-
- if (val === this._isDirty) return false;
-
- this._isDirty = val;
- this._dirtyChangeStamp = Date.now();
- return true;
- };
-
- /**
- * The check routine that executes regularly and will reschedule itself
- * to run again in the future. It determines the state of the elements
- * size and decides when to emit the "update" event.
- *
- * @method check
- * @return {void}
- */
- ResizeChecker.prototype.check = function () {
- const newSize = this.read();
- const dirty = this.saveSize(newSize);
- const dirtyChanged = this.saveDirty(dirty);
-
- const doneDirty = !dirty && dirtyChanged;
- const muchDirty = dirty && (this.lastDirtyChange() - Date.now() > MS_MAX_RESIZE_DELAY);
- if (doneDirty || muchDirty) {
- this.emit('resize', newSize);
- }
-
- // if the dirty state is unchanged, continue using the previous schedule
- if (!dirtyChanged) {
- return this.continueSchedule();
- }
-
- return this.startSchedule(SCHEDULE);
- };
-
- /**
- * Start running a new schedule, using one of the SCHEDULE_* constants.
- *
- * @method startSchedule
- * @param {integer[]} schedule - an array of millisecond times that should
- * be used to schedule calls to #check();
- * @return {integer} - the id of the next timer
- */
- ResizeChecker.prototype.startSchedule = function (schedule) {
- this._tick = -1;
- this._currentSchedule = schedule;
- return this.continueSchedule();
- };
-
- /**
- * Continue running the current schedule. MUST BE CALLED AFTER #startSchedule()
- *
- * @method continueSchedule
- * @return {integer} - the id of the next timer
- */
- ResizeChecker.prototype.continueSchedule = function () {
- clearTimeout(this._timerId);
-
- if (this._tick < this._currentSchedule.length - 1) {
- // at the end of the schedule, don't progress any further but repeat the last value
- this._tick += 1;
- }
-
- const check = this.check; // already bound
- const ms = this._currentSchedule[this._tick];
- return (this._timerId = setTimeout(function () {
- check();
- }, ms));
- };
-
- ResizeChecker.prototype.stopSchedule = function () {
- clearTimeout(this._timerId);
- };
-
- /**
- * Signal that the ResizeChecker should shutdown.
- *
- * Cleans up it's listeners and timers.
- *
- * @method destroy
- * @return {void}
- */
- ResizeChecker.prototype.destroy = function () {
- reflowWatcher.off('reflow', this.onReflow);
- clearTimeout(this._timerId);
- };
-
- return ResizeChecker;
-}
diff --git a/src/ui/public/vislib/vis.js b/src/ui/public/vislib/vis.js
index f32326882665a..5972dc6215056 100644
--- a/src/ui/public/vislib/vis.js
+++ b/src/ui/public/vislib/vis.js
@@ -3,13 +3,13 @@ import d3 from 'd3';
import Binder from 'ui/binder';
import errors from 'ui/errors';
import EventsProvider from 'ui/events';
+import { ResizeCheckerProvider } from 'ui/resize_checker';
import './styles/main.less';
-import VislibLibResizeCheckerProvider from './lib/resize_checker';
import VisConifgProvider from './lib/vis_config';
import VisHandlerProvider from './lib/handler';
export default function VisFactory(Private) {
- const ResizeChecker = Private(VislibLibResizeCheckerProvider);
+ const ResizeChecker = Private(ResizeCheckerProvider);
const Events = Private(EventsProvider);
const VisConfig = Private(VisConifgProvider);
const Handler = Private(VisHandlerProvider);
@@ -90,11 +90,9 @@ export default function VisFactory(Private) {
}
_runWithoutResizeChecker(method) {
- this.resizeChecker.stopSchedule();
- this._runOnHandler(method);
- this.resizeChecker.saveSize();
- this.resizeChecker.saveDirty(false);
- this.resizeChecker.continueSchedule();
+ this.resizeChecker.modifySizeWithoutTriggeringResize(() => {
+ this._runOnHandler(method);
+ });
}
_runOnHandler(method) {