uikit 3.11.0 → 3.11.2-dev.0e5febb2b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/build/scope.js +4 -6
  3. package/dist/css/uikit-core-rtl.css +1 -1
  4. package/dist/css/uikit-core-rtl.min.css +1 -1
  5. package/dist/css/uikit-core.css +1 -1
  6. package/dist/css/uikit-core.min.css +1 -1
  7. package/dist/css/uikit-rtl.css +1 -1
  8. package/dist/css/uikit-rtl.min.css +1 -1
  9. package/dist/css/uikit.css +1 -1
  10. package/dist/css/uikit.min.css +1 -1
  11. package/dist/js/components/countdown.js +1 -1
  12. package/dist/js/components/countdown.min.js +1 -1
  13. package/dist/js/components/filter.js +1 -1
  14. package/dist/js/components/filter.min.js +1 -1
  15. package/dist/js/components/lightbox-panel.js +1 -1
  16. package/dist/js/components/lightbox-panel.min.js +1 -1
  17. package/dist/js/components/lightbox.js +1 -1
  18. package/dist/js/components/lightbox.min.js +1 -1
  19. package/dist/js/components/notification.js +1 -1
  20. package/dist/js/components/notification.min.js +1 -1
  21. package/dist/js/components/parallax.js +37 -41
  22. package/dist/js/components/parallax.min.js +1 -1
  23. package/dist/js/components/slider-parallax.js +32 -25
  24. package/dist/js/components/slider-parallax.min.js +1 -1
  25. package/dist/js/components/slider.js +2 -2
  26. package/dist/js/components/slider.min.js +1 -1
  27. package/dist/js/components/slideshow-parallax.js +32 -25
  28. package/dist/js/components/slideshow-parallax.min.js +1 -1
  29. package/dist/js/components/slideshow.js +1 -1
  30. package/dist/js/components/slideshow.min.js +1 -1
  31. package/dist/js/components/sortable.js +2 -3
  32. package/dist/js/components/sortable.min.js +1 -1
  33. package/dist/js/components/tooltip.js +1 -1
  34. package/dist/js/components/tooltip.min.js +1 -1
  35. package/dist/js/components/upload.js +1 -1
  36. package/dist/js/components/upload.min.js +1 -1
  37. package/dist/js/uikit-core.js +169 -102
  38. package/dist/js/uikit-core.min.js +1 -1
  39. package/dist/js/uikit-icons.js +1 -1
  40. package/dist/js/uikit-icons.min.js +1 -1
  41. package/dist/js/uikit.js +232 -170
  42. package/dist/js/uikit.min.js +1 -1
  43. package/package.json +1 -1
  44. package/src/js/api/component.js +1 -1
  45. package/src/js/api/state.js +20 -24
  46. package/src/js/components/parallax.js +5 -16
  47. package/src/js/components/slider.js +1 -1
  48. package/src/js/components/sortable.js +1 -2
  49. package/src/js/core/core.js +2 -2
  50. package/src/js/core/drop.js +1 -1
  51. package/src/js/core/height-viewport.js +2 -2
  52. package/src/js/core/navbar.js +6 -2
  53. package/src/js/core/sticky.js +82 -50
  54. package/src/js/mixin/parallax.js +32 -21
  55. package/src/js/util/dimensions.js +28 -12
  56. package/src/js/util/fastdom.js +2 -2
  57. package/src/js/util/viewport.js +7 -3
  58. package/tests/sticky-parallax.html +44 -41
  59. package/tests/sticky.html +56 -24
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "uikit",
3
3
  "title": "UIkit",
4
4
  "description": "UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.",
5
- "version": "3.11.0",
5
+ "version": "3.11.2-dev.0e5febb2b",
6
6
  "main": "dist/js/uikit.js",
7
7
  "style": "dist/css/uikit.css",
8
8
  "sideEffects": [
@@ -28,7 +28,7 @@ export default function (UIkit) {
28
28
 
29
29
  return component.options.functional
30
30
  ? new component({data: isPlainObject(element) ? element : [...arguments]})
31
- : !element ? init(element) : $$(element).map(init)[0];
31
+ : element ? $$(element).map(init)[0] : init();
32
32
 
33
33
  function init(element) {
34
34
 
@@ -1,4 +1,4 @@
1
- import {assign, camelize, data as getData, hasOwn, hyphenate, isArray, isEmpty, isFunction, isNumeric, isPlainObject, isString, isUndefined, mergeOptions, on, parseOptions, startsWith, toBoolean, toNumber} from 'uikit-util';
1
+ import {assign, camelize, data as getData, hasOwn, hyphenate, isArray, isFunction, isNumeric, isPlainObject, isString, isUndefined, mergeOptions, on, parseOptions, startsWith, toBoolean, toNumber} from 'uikit-util';
2
2
 
3
3
  export default function (UIkit) {
4
4
 
@@ -211,11 +211,11 @@ export default function (UIkit) {
211
211
  on(
212
212
  el,
213
213
  name,
214
- !delegate
215
- ? null
216
- : isString(delegate)
214
+ delegate
215
+ ? isString(delegate)
217
216
  ? delegate
218
- : delegate.call(component),
217
+ : delegate.call(component)
218
+ : null,
219
219
  isString(handler) ? component[handler] : handler.bind(component),
220
220
  {passive, capture, self}
221
221
  )
@@ -250,27 +250,23 @@ export default function (UIkit) {
250
250
  : [value];
251
251
  }
252
252
 
253
- function normalizeData({data}, {args, props = {}}) {
254
- data = isArray(data)
255
- ? !isEmpty(args)
256
- ? data.slice(0, args.length).reduce((data, value, index) => {
257
- if (isPlainObject(value)) {
258
- assign(data, value);
259
- } else {
260
- data[args[index]] = value;
261
- }
262
- return data;
263
- }, {})
264
- : undefined
265
- : data;
266
-
267
- if (data) {
268
- for (const key in data) {
269
- if (isUndefined(data[key])) {
270
- delete data[key];
253
+ function normalizeData({data = {}}, {args = [], props = {}}) {
254
+ if (isArray(data)) {
255
+ data = data.slice(0, args.length).reduce((data, value, index) => {
256
+ if (isPlainObject(value)) {
257
+ assign(data, value);
271
258
  } else {
272
- data[key] = props[key] ? coerce(props[key], data[key]) : data[key];
259
+ data[args[index]] = value;
273
260
  }
261
+ return data;
262
+ }, {});
263
+ }
264
+
265
+ for (const key in data) {
266
+ if (isUndefined(data[key])) {
267
+ delete data[key];
268
+ } else if (props[key]) {
269
+ data[key] = coerce(props[key], data[key]);
274
270
  }
275
271
  }
276
272
 
@@ -28,13 +28,15 @@ export default {
28
28
  },
29
29
 
30
30
  start({start}) {
31
- return parseCalc(start, this.target);
31
+ return toPx(start, 'height', this.target, true);
32
32
  },
33
33
 
34
34
  end({end, viewport}) {
35
- return parseCalc(
35
+ return toPx(
36
36
  end || (viewport = (1 - viewport) * 100) && `${viewport}vh+${viewport}%`,
37
- this.target
37
+ 'height',
38
+ this.target,
39
+ true
38
40
  );
39
41
  }
40
42
 
@@ -77,19 +79,6 @@ export default {
77
79
 
78
80
  };
79
81
 
80
- const calcRe = /-?\d+(?:\.\d+)?(?:v[wh]|%|px)?/g;
81
- function parseCalc(calc, el) {
82
- let match;
83
- let result = 0;
84
- calc = calc.toString().replace(/\s/g, '');
85
- calcRe.lastIndex = 0;
86
- while ((match = calcRe.exec(calc)) !== null) {
87
- result += toPx(match, 'height', el, true);
88
- }
89
-
90
- return result;
91
- }
92
-
93
82
  function ease(percent, easing) {
94
83
  return easing >= 0
95
84
  ? Math.pow(percent, easing + 1)
@@ -30,7 +30,7 @@ export default {
30
30
  },
31
31
 
32
32
  finite({finite}) {
33
- return finite || Math.ceil(getWidth(this.list)) < dimensions(this.list).width + getMaxElWidth(this.list) + this.center;
33
+ return finite || Math.ceil(getWidth(this.list)) < Math.floor(dimensions(this.list).width + getMaxElWidth(this.list) + this.center);
34
34
  },
35
35
 
36
36
  maxIndex() {
@@ -218,7 +218,6 @@ export default {
218
218
 
219
219
  off(document, pointerMove, this.move);
220
220
  off(document, pointerUp, this.end);
221
- off(window, 'scroll', this.scroll);
222
221
 
223
222
  if (!this.drag) {
224
223
  return;
@@ -295,7 +294,7 @@ function trackScroll(pos) {
295
294
  trackTimer = setInterval(() => {
296
295
 
297
296
  let {x, y} = pos;
298
- y += window.pageYOffset;
297
+ y += scrollTop(window);
299
298
 
300
299
  const dist = (Date.now() - last) * .3;
301
300
  last = Date.now();
@@ -13,7 +13,7 @@ export default function (UIkit) {
13
13
  return;
14
14
  }
15
15
  pendingResize = true;
16
- fastdom.write(() => pendingResize = false);
16
+ fastdom.read(() => pendingResize = false);
17
17
  UIkit.update(null, 'resize');
18
18
  };
19
19
 
@@ -32,7 +32,7 @@ export default function (UIkit) {
32
32
  return;
33
33
  }
34
34
  pending = true;
35
- fastdom.write(() => pending = false);
35
+ fastdom.read(() => pending = false);
36
36
 
37
37
  UIkit.update(null, e.type);
38
38
 
@@ -308,7 +308,7 @@ export default {
308
308
  if (active) {
309
309
 
310
310
  if (delay && active.isDelaying) {
311
- this.showTimer = setTimeout(this.show, 10);
311
+ this.showTimer = setTimeout(() => matches(target, ':hover') && this.show(), 10);
312
312
  return;
313
313
  }
314
314
 
@@ -1,5 +1,5 @@
1
1
  import FlexBug from '../mixin/flex-bug';
2
- import {boxModelAdjust, css, dimensions, endsWith, height, isNumeric, isString, isVisible, offset, query, toFloat} from 'uikit-util';
2
+ import {boxModelAdjust, css, dimensions, endsWith, height, isNumeric, isString, isVisible, offset, query, toFloat, trigger} from 'uikit-util';
3
3
 
4
4
  export default {
5
5
 
@@ -76,7 +76,7 @@ export default {
76
76
  css(this.$el, {minHeight});
77
77
 
78
78
  if (minHeight !== prev) {
79
- this.$update(this.$el, 'resize');
79
+ trigger(this.$el, 'resize');
80
80
  }
81
81
 
82
82
  if (this.minHeight && toFloat(css(this.$el, 'minHeight')) < this.minHeight) {
@@ -137,7 +137,7 @@ export default {
137
137
 
138
138
  handler({current}) {
139
139
  const active = this.getActive();
140
- if (active && includes(active.mode, 'hover') && active.target && !within(active.target, current) && !active.tracker.movesTo(active.$el)) {
140
+ if (active && includes(active.mode, 'hover') && active.target && !within(active.target, current) && !active.isDelaying) {
141
141
  active.hide(false);
142
142
  }
143
143
  }
@@ -301,7 +301,11 @@ export default {
301
301
 
302
302
  const active = this.getActive();
303
303
 
304
- if (matches(this.dropbar, ':hover') && active && active.$el === $el) {
304
+ if (matches(this.dropbar, ':hover')
305
+ && active
306
+ && active.$el === $el
307
+ && !this.toggles.some(el => active.target !== el && matches(el, ':focus'))
308
+ ) {
305
309
  e.preventDefault();
306
310
  }
307
311
  }
@@ -1,12 +1,13 @@
1
1
  import Class from '../mixin/class';
2
2
  import Media from '../mixin/media';
3
- import {$, addClass, after, Animation, assign, css, dimensions, fastdom, height as getHeight, hasClass, isNumeric, isString, isVisible, noop, offset, offsetPosition, parent, query, remove, removeClass, replaceClass, scrollTop, toFloat, toggleClass, toPx, trigger, within} from 'uikit-util';
3
+ import {$, addClass, after, Animation, clamp, css, dimensions, fastdom, height as getHeight, getScrollingElement, hasClass, isNumeric, isString, isVisible, noop, offset, offsetPosition, parent, query, remove, removeClass, replaceClass, scrollTop, toggleClass, toPx, trigger, within} from 'uikit-util';
4
4
 
5
5
  export default {
6
6
 
7
7
  mixins: [Class, Media],
8
8
 
9
9
  props: {
10
+ position: String,
10
11
  top: null,
11
12
  bottom: Boolean,
12
13
  offset: String,
@@ -22,6 +23,7 @@ export default {
22
23
  },
23
24
 
24
25
  data: {
26
+ position: 'top',
25
27
  top: 0,
26
28
  bottom: false,
27
29
  offset: 0,
@@ -38,8 +40,19 @@ export default {
38
40
 
39
41
  computed: {
40
42
 
41
- offset({offset}) {
42
- return toPx(offset);
43
+ position({position}, $el) {
44
+ return position === 'auto'
45
+ ? (this.isFixed ? this.placeholder : $el).offsetHeight > getHeight(window)
46
+ ? 'bottom'
47
+ : 'top'
48
+ : position;
49
+ },
50
+
51
+ offset({offset}, $el) {
52
+ if (this.position === 'bottom') {
53
+ offset += '+100vh-100%';
54
+ }
55
+ return toPx(offset, 'height', $el);
43
56
  },
44
57
 
45
58
  selTarget({selTarget}, $el) {
@@ -100,7 +113,7 @@ export default {
100
113
 
101
114
  handler() {
102
115
 
103
- if (!(this.targetOffset !== false && location.hash && window.pageYOffset > 0)) {
116
+ if (!(this.targetOffset !== false && location.hash && scrollTop(window) > 0)) {
104
117
  return;
105
118
  }
106
119
 
@@ -130,7 +143,7 @@ export default {
130
143
 
131
144
  {
132
145
 
133
- read({height}, types) {
146
+ read({height, margin}, types) {
134
147
 
135
148
  this.inactive = !this.matchMedia || !isVisible(this.$el);
136
149
 
@@ -138,42 +151,52 @@ export default {
138
151
  return false;
139
152
  }
140
153
 
141
- if (this.isActive && types.has('resize')) {
154
+ const hide = this.isActive && types.has('resize');
155
+ if (hide) {
142
156
  this.hide();
143
- height = this.$el.offsetHeight;
144
- this.show();
145
157
  }
146
158
 
147
- height = this.isActive ? height : this.$el.offsetHeight;
159
+ if (!this.isActive) {
160
+ height = this.$el.offsetHeight;
161
+ margin = css(this.$el, 'margin');
162
+ }
148
163
 
149
- if (height + this.offset > getHeight(window)) {
150
- this.inactive = true;
151
- return false;
164
+ if (hide) {
165
+ this.show();
152
166
  }
153
167
 
168
+ const overflow = Math.max(0, height + this.offset - getHeight(window));
169
+
154
170
  const referenceElement = this.isFixed ? this.placeholder : this.$el;
155
- this.topOffset = offset(referenceElement).top;
156
- this.bottomOffset = this.topOffset + height;
157
- this.offsetParentTop = offset(referenceElement.offsetParent).top;
171
+ const topOffset = offset(referenceElement).top;
172
+ const offsetParentTop = offset(referenceElement.offsetParent).top;
158
173
 
159
- const bottom = parseProp('bottom', this);
174
+ const top = parseProp(this.top, this.$el, topOffset);
175
+ const bottom = parseProp(this.bottom, this.$el, topOffset + height);
160
176
 
161
- this.top = Math.max(toFloat(parseProp('top', this)), this.topOffset) - this.offset;
162
- this.bottom = bottom && bottom - this.$el.offsetHeight;
163
- this.width = dimensions(isVisible(this.widthElement) ? this.widthElement : this.$el).width;
177
+ const start = Math.max(top, topOffset) - this.offset;
178
+ const end = bottom
179
+ ? bottom - this.$el.offsetHeight + overflow - this.offset
180
+ : getScrollingElement(this.$el).scrollHeight - getHeight(window);
164
181
 
165
182
  return {
183
+ start,
184
+ end,
185
+ overflow,
186
+ topOffset,
187
+ offsetParentTop,
166
188
  height,
167
- top: offsetPosition(this.placeholder)[0],
168
- margins: css(this.$el, ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'])
189
+ margin,
190
+ width: dimensions(isVisible(this.widthElement) ? this.widthElement : this.$el).width,
191
+ top: offsetPosition(this.placeholder)[0]
169
192
  };
170
193
  },
171
194
 
172
- write({height, margins}) {
195
+ write({height, margin}) {
173
196
 
174
197
  const {placeholder} = this;
175
198
 
176
- css(placeholder, assign({height}, margins));
199
+ css(placeholder, {height, margin});
177
200
 
178
201
  if (!within(placeholder, document)) {
179
202
  after(this.$el, placeholder);
@@ -190,42 +213,48 @@ export default {
190
213
 
191
214
  {
192
215
 
193
- read({scroll = 0}) {
216
+ read({scroll: prevScroll = 0, dir: prevDir = 'down', overflow, overflowScroll = 0, start, end}) {
194
217
 
195
- this.scroll = window.pageYOffset;
218
+ const scroll = scrollTop(window);
219
+ const dir = prevScroll <= scroll ? 'down' : 'up';
196
220
 
197
221
  return {
198
- dir: scroll <= this.scroll ? 'down' : 'up',
199
- scroll: this.scroll
222
+ dir,
223
+ prevDir,
224
+ scroll,
225
+ prevScroll,
226
+ overflowScroll: clamp(
227
+ overflowScroll
228
+ + clamp(scroll, start, end)
229
+ - clamp(prevScroll, start, end),
230
+ 0,
231
+ overflow
232
+ )
200
233
  };
201
234
  },
202
235
 
203
236
  write(data, types) {
204
237
 
205
- const now = Date.now();
206
238
  const isScrollUpdate = types.has('scroll');
207
- const {initTimestamp = 0, dir, lastDir, lastScroll, scroll, top} = data;
239
+ const {initTimestamp = 0, dir, prevDir, scroll, prevScroll = 0, top, start, topOffset, height} = data;
208
240
 
209
- data.lastScroll = scroll;
210
-
211
- if (scroll < 0 || scroll === lastScroll && isScrollUpdate || this.showOnUp && !isScrollUpdate && !this.isFixed) {
241
+ if (scroll < 0 || scroll === prevScroll && isScrollUpdate || this.showOnUp && !isScrollUpdate && !this.isFixed) {
212
242
  return;
213
243
  }
214
244
 
215
- if (now - initTimestamp > 300 || dir !== lastDir) {
245
+ const now = Date.now();
246
+ if (now - initTimestamp > 300 || dir !== prevDir) {
216
247
  data.initScroll = scroll;
217
248
  data.initTimestamp = now;
218
249
  }
219
250
 
220
- data.lastDir = dir;
221
-
222
- if (this.showOnUp && !this.isFixed && Math.abs(data.initScroll - scroll) <= 30 && Math.abs(lastScroll - scroll) <= 10) {
251
+ if (this.showOnUp && !this.isFixed && Math.abs(data.initScroll - scroll) <= 30 && Math.abs(prevScroll - scroll) <= 10) {
223
252
  return;
224
253
  }
225
254
 
226
255
  if (this.inactive
227
- || scroll < this.top
228
- || this.showOnUp && (scroll <= this.top || dir === 'down' && isScrollUpdate || dir === 'up' && !this.isFixed && scroll <= this.bottomOffset)
256
+ || scroll < start
257
+ || this.showOnUp && (scroll <= start || dir === 'down' && isScrollUpdate || dir === 'up' && !this.isFixed && scroll <= topOffset + height)
229
258
  ) {
230
259
 
231
260
  if (!this.isFixed) {
@@ -240,7 +269,7 @@ export default {
240
269
 
241
270
  this.isFixed = false;
242
271
 
243
- if (this.animation && scroll > this.topOffset) {
272
+ if (this.animation && scroll > topOffset) {
244
273
  Animation.cancel(this.$el);
245
274
  Animation.out(this.$el, this.animation).then(() => this.hide(), noop);
246
275
  } else {
@@ -290,23 +319,28 @@ export default {
290
319
 
291
320
  update() {
292
321
 
293
- const active = this.top !== 0 || this.scroll > this.top;
294
- let top = Math.max(0, this.offset);
322
+ const {width, scroll = 0, overflow, overflowScroll = 0, start, end, topOffset, height, offsetParentTop} = this._data;
323
+ const active = start !== 0 || scroll > start;
324
+ let top = this.offset;
295
325
  let position = 'fixed';
296
326
 
297
- if (isNumeric(this.bottom) && this.scroll > this.bottom - this.offset) {
298
- top = this.bottom - this.offsetParentTop;
327
+ if (scroll > end) {
328
+ top = end + this.offset - offsetParentTop;
299
329
  position = 'absolute';
300
330
  }
301
331
 
332
+ if (overflow) {
333
+ top -= overflowScroll;
334
+ }
335
+
302
336
  css(this.$el, {
303
337
  position,
304
338
  top: `${top}px`,
305
- width: this.width
339
+ width
306
340
  });
307
341
 
308
342
  this.isActive = active;
309
- toggleClass(this.$el, this.clsBelow, this.scroll > this.bottomOffset);
343
+ toggleClass(this.$el, this.clsBelow, scroll > topOffset + height);
310
344
  addClass(this.$el, this.clsFixed);
311
345
 
312
346
  }
@@ -315,12 +349,10 @@ export default {
315
349
 
316
350
  };
317
351
 
318
- function parseProp(prop, {$props, $el, [`${prop}Offset`]: propOffset}) {
319
-
320
- const value = $props[prop];
352
+ function parseProp(value, el, propOffset) {
321
353
 
322
354
  if (!value) {
323
- return;
355
+ return 0;
324
356
  }
325
357
 
326
358
  if (isString(value) && value.match(/^-?\d/)) {
@@ -329,7 +361,7 @@ function parseProp(prop, {$props, $el, [`${prop}Offset`]: propOffset}) {
329
361
 
330
362
  } else {
331
363
 
332
- return offset(value === true ? parent($el) : query(value, $el)).bottom;
364
+ return offset(value === true ? parent(el) : query(value, el)).bottom;
333
365
 
334
366
  }
335
367
  }
@@ -1,6 +1,6 @@
1
1
  import Media from '../mixin/media';
2
2
  import {getMaxPathLength} from '../core/svg';
3
- import {css, Dimensions, each, isNumber, isString, isUndefined, noop, startsWith, toFloat, toPx, ucfirst} from 'uikit-util';
3
+ import {css, Dimensions, each, isNumber, isString, isUndefined, noop, startsWith, toFloat, toPx, trigger, ucfirst} from 'uikit-util';
4
4
 
5
5
  const props = {
6
6
  x: transformFn,
@@ -38,7 +38,7 @@ export default {
38
38
  props(properties, $el) {
39
39
  return keys(props).reduce((result, prop) => {
40
40
  if (!isUndefined(properties[prop])) {
41
- result[prop] = props[prop].call(this, prop, $el, properties[prop].slice());
41
+ result[prop] = props[prop](prop, $el, properties[prop].slice());
42
42
  }
43
43
  return result;
44
44
  }, {});
@@ -46,6 +46,12 @@ export default {
46
46
 
47
47
  },
48
48
 
49
+ events: {
50
+ bgimageload() {
51
+ this.$emit();
52
+ }
53
+ },
54
+
49
55
  methods: {
50
56
 
51
57
  reset() {
@@ -186,15 +192,15 @@ function backgroundFn(prop, el, steps) {
186
192
  const bgPos = css(el, 'backgroundPosition').split(' ')[prop === 'x' ? 0 : 1]; // IE 11 can't read background-position-[x|y]
187
193
 
188
194
  return getCssValue(el, 'backgroundSize', '') === 'cover'
189
- ? backgroundCoverFn.call(this, prop, el, steps, bgPos, attr)
195
+ ? backgroundCoverFn(prop, el, steps, bgPos, attr)
190
196
  : setBackgroundPosFn(prop, steps, bgPos);
191
197
  }
192
198
 
193
199
  function backgroundCoverFn(prop, el, steps, bgPos, attr) {
194
200
 
195
- const image = getBackgroundImage.call(this, el);
201
+ const dimImage = getBackgroundImageDimensions(el);
196
202
 
197
- if (!image.naturalWidth) {
203
+ if (!dimImage.width) {
198
204
  return noop;
199
205
  }
200
206
 
@@ -210,11 +216,6 @@ function backgroundCoverFn(prop, el, steps, bgPos, attr) {
210
216
  height: el.offsetHeight
211
217
  };
212
218
 
213
- const dimImage = {
214
- width: image.naturalWidth,
215
- height: image.naturalHeight
216
- };
217
-
218
219
  const baseDim = Dimensions.cover(dimImage, dimEl);
219
220
  const span = baseDim[attr] - dimEl[attr];
220
221
 
@@ -245,24 +246,34 @@ function setBackgroundPosFn(prop, steps, pos) {
245
246
  };
246
247
  }
247
248
 
248
- function getBackgroundImage(el) {
249
+ const dimensions = {};
250
+ function getBackgroundImageDimensions(el) {
249
251
  const src = css(el, 'backgroundImage').replace(/^none|url\(["']?(.+?)["']?\)$/, '$1');
250
252
 
251
- const data = this._data;
252
-
253
- if (data[src]) {
254
- return data[src];
253
+ if (dimensions[src]) {
254
+ return dimensions[src];
255
255
  }
256
256
 
257
+ const image = new Image();
257
258
  if (src) {
258
- const img = new Image();
259
- img.src = src;
260
- if (!img.naturalWidth) {
261
- img.onload = () => this.$update();
262
- }
259
+ image.src = src;
263
260
 
264
- return data[src] = img;
261
+ if (!image.naturalWidth) {
262
+ image.onload = () => {
263
+ dimensions[src] = toDimensions(image);
264
+ trigger(el, 'bgimageload');
265
+ };
266
+ }
265
267
  }
268
+
269
+ return dimensions[src] = toDimensions(image);
270
+ }
271
+
272
+ function toDimensions(image) {
273
+ return {
274
+ width: image.naturalWidth,
275
+ height: image.naturalHeight
276
+ };
266
277
  }
267
278
 
268
279
  function getStep(steps, percent) {
@@ -1,5 +1,5 @@
1
1
  import {css} from './style';
2
- import {each, endsWith, isDocument, isElement, isNumeric, isUndefined, isWindow, toFloat, toNode, toWindow, ucfirst} from './lang';
2
+ import {each, isDocument, isElement, isString, isUndefined, isWindow, memoize, toFloat, toNode, toWindow, ucfirst} from './lang';
3
3
 
4
4
  const dirs = {
5
5
  width: ['left', 'right'],
@@ -157,19 +157,35 @@ export function flipPosition(pos) {
157
157
  }
158
158
 
159
159
  export function toPx(value, property = 'width', element = window, offsetDim = false) {
160
- return isNumeric(value)
161
- ? +value
162
- : endsWith(value, 'vh')
163
- ? percent(height(toWindow(element)), value)
164
- : endsWith(value, 'vw')
165
- ? percent(width(toWindow(element)), value)
166
- : endsWith(value, '%')
167
- ? percent(offsetDim
168
- ? dimension(property)(element)
169
- : dimensions(element)[property], value)
170
- : toFloat(value);
160
+
161
+ if (!isString(value)) {
162
+ return toFloat(value);
163
+ }
164
+
165
+ return parseCalc(value).reduce((result, value) => {
166
+ const unit = parseUnit(value);
167
+ if (unit) {
168
+ value = percent(
169
+ unit === 'vh'
170
+ ? height(toWindow(element))
171
+ : unit === 'vw'
172
+ ? width(toWindow(element))
173
+ : offsetDim
174
+ ? element[`offset${ucfirst(property)}`]
175
+ : dimensions(element)[property],
176
+ value
177
+ );
178
+ }
179
+
180
+ return result + toFloat(value);
181
+ }, 0);
171
182
  }
172
183
 
184
+ const calcRe = /-?\d+(?:\.\d+)?(?:v[wh]|%|px)?/g;
185
+ const parseCalc = memoize(calc => calc.toString().replace(/\s/g, '').match(calcRe) || []);
186
+ const unitRe = /(?:v[hw]|%)$/;
187
+ const parseUnit = memoize(str => (str.match(unitRe) || [])[0]);
188
+
173
189
  function percent(base, value) {
174
190
  return base * toFloat(value) / 100;
175
191
  }
@@ -31,7 +31,7 @@ export const fastdom = {
31
31
 
32
32
  };
33
33
 
34
- function flush(recursion = 1) {
34
+ function flush(recursion) {
35
35
  runTasks(fastdom.reads);
36
36
  runTasks(fastdom.writes.splice(0));
37
37
 
@@ -53,7 +53,7 @@ function scheduleFlush(recursion) {
53
53
  if (recursion && recursion < RECURSION_LIMIT) {
54
54
  Promise.resolve().then(() => flush(recursion));
55
55
  } else {
56
- requestAnimationFrame(() => flush());
56
+ requestAnimationFrame(() => flush(1));
57
57
  }
58
58
 
59
59
  }