uikit 3.14.4-dev.4bd89c5ca → 3.14.4-dev.6002e7046

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 (136) hide show
  1. package/CHANGELOG.md +39 -18
  2. package/build/util.js +1 -0
  3. package/dist/css/uikit-core-rtl.css +349 -160
  4. package/dist/css/uikit-core-rtl.min.css +1 -1
  5. package/dist/css/uikit-core.css +349 -160
  6. package/dist/css/uikit-core.min.css +1 -1
  7. package/dist/css/uikit-rtl.css +372 -190
  8. package/dist/css/uikit-rtl.min.css +1 -1
  9. package/dist/css/uikit.css +372 -190
  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 +96 -131
  16. package/dist/js/components/lightbox-panel.min.js +1 -1
  17. package/dist/js/components/lightbox.js +96 -131
  18. package/dist/js/components/lightbox.min.js +1 -1
  19. package/dist/js/components/notification.js +4 -2
  20. package/dist/js/components/notification.min.js +1 -1
  21. package/dist/js/components/parallax.js +4 -5
  22. package/dist/js/components/parallax.min.js +1 -1
  23. package/dist/js/components/slider-parallax.js +4 -5
  24. package/dist/js/components/slider-parallax.min.js +1 -1
  25. package/dist/js/components/slider.js +1 -1
  26. package/dist/js/components/slider.min.js +1 -1
  27. package/dist/js/components/slideshow-parallax.js +4 -5
  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 +1 -1
  32. package/dist/js/components/sortable.min.js +1 -1
  33. package/dist/js/components/tooltip.js +102 -139
  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 +527 -486
  38. package/dist/js/uikit-core.min.js +17 -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 +530 -487
  42. package/dist/js/uikit.min.js +17 -1
  43. package/package.json +11 -11
  44. package/src/images/{backgrounds/nav-parent-close.svg → components/nav-parent-icon-large.svg} +0 -0
  45. package/src/images/{backgrounds/navbar-parent-close.svg → components/nav-parent-icon.svg} +0 -0
  46. package/src/images/{backgrounds/navbar-parent-open.svg → components/navbar-parent-icon.svg} +1 -1
  47. package/src/images/components/navbar-toggle-icon.svg +25 -3
  48. package/src/js/api/hooks.js +5 -1
  49. package/src/js/api/state.js +2 -2
  50. package/src/js/components/notification.js +3 -1
  51. package/src/js/core/accordion.js +9 -17
  52. package/src/js/core/alert.js +35 -14
  53. package/src/js/core/drop.js +110 -82
  54. package/src/js/core/height-viewport.js +4 -2
  55. package/src/js/core/icon.js +16 -0
  56. package/src/js/core/index.js +2 -0
  57. package/src/js/core/leader.js +2 -2
  58. package/src/js/core/navbar.js +30 -45
  59. package/src/js/core/offcanvas.js +8 -4
  60. package/src/js/core/scroll.js +37 -10
  61. package/src/js/core/toggle.js +3 -5
  62. package/src/js/mixin/media.js +4 -5
  63. package/src/js/mixin/modal.js +15 -12
  64. package/src/js/mixin/position.js +24 -26
  65. package/src/js/mixin/style.js +11 -0
  66. package/src/js/mixin/togglable.js +88 -124
  67. package/src/js/util/animation.js +9 -7
  68. package/src/js/util/class.js +3 -1
  69. package/src/js/util/filter.js +3 -7
  70. package/src/js/util/position.js +115 -114
  71. package/src/js/util/style.js +4 -13
  72. package/src/js/util/viewport.js +3 -5
  73. package/src/less/components/_import.less +1 -0
  74. package/src/less/components/drop.less +1 -18
  75. package/src/less/components/dropbar.less +126 -0
  76. package/src/less/components/dropdown.less +11 -19
  77. package/src/less/components/leader.less +1 -1
  78. package/src/less/components/nav.less +219 -58
  79. package/src/less/components/navbar.less +49 -81
  80. package/src/less/components/utility.less +10 -2
  81. package/src/less/theme/_import.less +1 -0
  82. package/src/less/theme/dropbar.less +44 -0
  83. package/src/less/theme/dropdown.less +0 -11
  84. package/src/less/theme/nav.less +45 -7
  85. package/src/less/theme/navbar.less +5 -44
  86. package/src/scss/components/_import.scss +1 -0
  87. package/src/scss/components/drop.scss +1 -18
  88. package/src/scss/components/dropbar.scss +126 -0
  89. package/src/scss/components/dropdown.scss +11 -19
  90. package/src/scss/components/leader.scss +1 -1
  91. package/src/scss/components/nav.scss +168 -46
  92. package/src/scss/components/navbar.scss +49 -69
  93. package/src/scss/components/utility.scss +8 -1
  94. package/src/scss/mixins-theme.scss +83 -61
  95. package/src/scss/mixins.scss +79 -29
  96. package/src/scss/theme/_import.scss +1 -0
  97. package/src/scss/theme/dropbar.scss +44 -0
  98. package/src/scss/theme/dropdown.scss +0 -8
  99. package/src/scss/theme/nav.scss +43 -7
  100. package/src/scss/theme/navbar.scss +4 -16
  101. package/src/scss/variables-theme.scss +62 -26
  102. package/src/scss/variables.scss +50 -21
  103. package/tests/accordion.html +2 -2
  104. package/tests/alert.html +2 -2
  105. package/tests/countdown.html +1 -1
  106. package/tests/drop.html +446 -416
  107. package/tests/dropbar.html +458 -0
  108. package/tests/dropdown.html +8 -470
  109. package/tests/filter.html +9 -12
  110. package/tests/form.html +1 -1
  111. package/tests/index.html +126 -107
  112. package/tests/js/index.js +1 -4
  113. package/tests/lightbox.html +5 -5
  114. package/tests/list.html +8 -8
  115. package/tests/modal.html +13 -13
  116. package/tests/nav.html +117 -75
  117. package/tests/navbar.html +129 -249
  118. package/tests/offcanvas.html +17 -21
  119. package/tests/parallax.html +1 -1
  120. package/tests/position.html +18 -16
  121. package/tests/progress.html +9 -9
  122. package/tests/scroll.html +7 -10
  123. package/tests/search.html +6 -6
  124. package/tests/slider.html +6 -5
  125. package/tests/slideshow.html +8 -8
  126. package/tests/sortable.html +6 -8
  127. package/tests/sticky-navbar.html +15 -15
  128. package/tests/sticky.html +8 -8
  129. package/tests/switcher.html +1 -1
  130. package/tests/tab.html +1 -1
  131. package/tests/table.html +7 -7
  132. package/tests/toggle.html +2 -2
  133. package/tests/tooltip.html +1 -1
  134. package/tests/upload.html +11 -11
  135. package/tests/utility.html +19 -0
  136. package/src/images/backgrounds/nav-parent-open.svg +0 -3
@@ -3,7 +3,7 @@ import {
3
3
  addClass,
4
4
  Animation,
5
5
  css,
6
- fastdom,
6
+ dimensions,
7
7
  hasClass,
8
8
  includes,
9
9
  isBoolean,
@@ -11,13 +11,14 @@ import {
11
11
  isVisible,
12
12
  noop,
13
13
  removeClass,
14
- scrollParents,
15
14
  startsWith,
16
15
  toFloat,
17
16
  toggleClass,
18
17
  toNodes,
19
18
  Transition,
20
19
  trigger,
20
+ unwrap,
21
+ wrapInner,
21
22
  } from 'uikit-util';
22
23
 
23
24
  export default {
@@ -39,26 +40,6 @@ export default {
39
40
  transition: 'ease',
40
41
  clsEnter: 'uk-togglabe-enter',
41
42
  clsLeave: 'uk-togglabe-leave',
42
-
43
- initProps: {
44
- overflow: '',
45
- maxHeight: '',
46
- paddingTop: '',
47
- paddingBottom: '',
48
- marginTop: '',
49
- marginBottom: '',
50
- boxShadow: '',
51
- },
52
-
53
- hideProps: {
54
- overflow: 'hidden',
55
- maxHeight: 0,
56
- paddingTop: 0,
57
- paddingBottom: 0,
58
- marginTop: 0,
59
- marginBottom: 0,
60
- boxShadow: 'none',
61
- },
62
43
  },
63
44
 
64
45
  computed: {
@@ -67,7 +48,7 @@ export default {
67
48
  },
68
49
 
69
50
  hasTransition({ animation }) {
70
- return startsWith(animation[0], 'slide');
51
+ return ['slide', 'reveal'].some((transition) => startsWith(animation[0], transition));
71
52
  },
72
53
  },
73
54
 
@@ -160,122 +141,105 @@ function toggleInstant({ _toggle }) {
160
141
  };
161
142
  }
162
143
 
163
- function toggleTransition(cmp) {
164
- switch (cmp.animation[0]) {
165
- case 'slide-left':
166
- return slideHorizontal(cmp);
167
- case 'slide-right':
168
- return slideHorizontal(cmp, true);
169
- }
170
- return slide(cmp);
171
- }
144
+ export function toggleTransition(cmp) {
145
+ const [mode = 'reveal', startProp = 'top'] = cmp.animation[0]?.split('-') || [];
172
146
 
173
- export function slide({
174
- isToggled,
175
- duration,
176
- velocity,
177
- initProps,
178
- hideProps,
179
- transition,
180
- _toggle,
181
- }) {
182
- return (el, show) => {
183
- const inProgress = Transition.inProgress(el);
184
- const inner =
185
- !inProgress && el.hasChildNodes()
186
- ? toFloat(css(el.firstElementChild, 'marginTop')) +
187
- toFloat(css(el.lastElementChild, 'marginBottom'))
188
- : 0;
189
- const currentHeight = isVisible(el) ? toFloat(css(el, 'height')) + inner : 0;
147
+ const dirs = [
148
+ ['left', 'right'],
149
+ ['top', 'bottom'],
150
+ ];
151
+ const dir = dirs[includes(dirs[0], startProp) ? 0 : 1];
152
+ const end = dir[1] === startProp;
153
+ const props = ['width', 'height'];
154
+ const dimProp = props[dirs.indexOf(dir)];
155
+ const marginProp = `margin-${dir[0]}`;
156
+ const marginStartProp = `margin-${startProp}`;
190
157
 
191
- const props = inProgress ? css(el, Object.keys(initProps)) : show ? hideProps : initProps;
158
+ return async (el, show) => {
159
+ let { duration, velocity, transition, _toggle } = cmp;
192
160
 
193
- Transition.cancel(el);
161
+ let currentDim = dimensions(el)[dimProp];
162
+
163
+ const inProgress = Transition.inProgress(el);
164
+ await Transition.cancel(el);
194
165
 
195
- if (!isToggled(el)) {
166
+ if (show) {
196
167
  _toggle(el, true);
197
168
  }
198
169
 
199
- css(el, 'maxHeight', '');
200
-
201
- // Update child components first
202
- fastdom.flush();
203
-
204
- const endHeight = toFloat(css(el, 'height')) + inner;
205
- duration = velocity * endHeight + duration;
206
-
207
- css(el, { ...props, maxHeight: currentHeight });
208
-
209
- return (
210
- show
211
- ? Transition.start(
212
- el,
213
- { ...initProps, overflow: 'hidden', maxHeight: endHeight },
214
- duration * (1 - currentHeight / endHeight),
215
- transition
216
- )
217
- : Transition.start(
218
- el,
219
- hideProps,
220
- duration * (currentHeight / endHeight),
221
- transition
222
- ).then(() => _toggle(el, false))
223
- ).then(() => css(el, initProps));
224
- };
225
- }
226
-
227
- function slideHorizontal({ isToggled, duration, velocity, transition, _toggle }, right) {
228
- return (el, show) => {
229
- const visible = isVisible(el);
230
- const marginLeft = toFloat(css(el, 'marginLeft'));
231
-
232
- Transition.cancel(el);
170
+ const prevProps = Object.fromEntries(
171
+ [
172
+ 'padding',
173
+ 'border',
174
+ 'width',
175
+ 'height',
176
+ 'overflowY',
177
+ 'overflowX',
178
+ marginProp,
179
+ marginStartProp,
180
+ ].map((key) => [key, el.style[key]])
181
+ );
233
182
 
234
- const [scrollElement] = scrollParents(el.offsetParent);
235
- css(scrollElement, 'overflowX', 'hidden');
183
+ const dim = dimensions(el);
184
+ const currentMargin = toFloat(css(el, marginProp));
185
+ const marginStart = toFloat(css(el, marginStartProp));
186
+ const endDim = dim[dimProp] + marginStart;
236
187
 
237
- if (!isToggled(el)) {
238
- _toggle(el, true);
188
+ if (!inProgress && !show) {
189
+ currentDim += marginStart;
239
190
  }
240
191
 
241
- const width = toFloat(css(el, 'width'));
242
- duration = velocity * width + duration;
243
-
244
- const percent = visible ? ((width + marginLeft * (right ? -1 : 1)) / width) * 100 : 0;
192
+ const [wrapper] = wrapInner(el, '<div>');
193
+ css(wrapper, {
194
+ boxSizing: 'border-box',
195
+ height: dim.height,
196
+ width: dim.width,
197
+ ...css(el, [
198
+ 'overflow',
199
+ 'padding',
200
+ 'borderTop',
201
+ 'borderRight',
202
+ 'borderBottom',
203
+ 'borderLeft',
204
+ 'borderImage',
205
+ marginStartProp,
206
+ ]),
207
+ });
245
208
 
246
209
  css(el, {
247
- clipPath: right
248
- ? `polygon(0 0,${percent}% 0,${percent}% 100%,0 100%)`
249
- : `polygon(${100 - percent}% 0,100% 0,100% 100%,${100 - percent}% 100%)`,
250
- marginLeft: (((100 - percent) * (right ? 1 : -1)) / 100) * width,
210
+ padding: 0,
211
+ border: 0,
212
+ [marginStartProp]: 0,
213
+ width: dim.width,
214
+ height: dim.height,
215
+ overflow: 'hidden',
216
+ [dimProp]: currentDim,
251
217
  });
252
218
 
253
- return (
254
- show
255
- ? Transition.start(
256
- el,
257
- {
258
- clipPath: `polygon(0 0,100% 0,100% 100%,0 100%)`,
259
- marginLeft: 0,
260
- },
261
- duration * (1 - percent / 100),
262
- transition
263
- )
264
- : Transition.start(
265
- el,
266
- {
267
- clipPath: right
268
- ? `polygon(0 0,0 0,0 100%,0 100%)`
269
- : `polygon(100% 0,100% 0,100% 100%,100% 100%)`,
270
- marginLeft: (right ? 1 : -1) * width,
271
- },
272
- duration * (percent / 100),
273
- transition
274
- ).then(() => _toggle(el, false))
275
- ).then(() => {
276
- css(scrollElement, 'overflowX', '');
277
- css(el, { clipPath: '', marginLeft: '' });
278
- });
219
+ const percent = currentDim / endDim;
220
+ duration = (velocity * endDim + duration) * (show ? 1 - percent : percent);
221
+ const endProps = { [dimProp]: show ? endDim : 0 };
222
+
223
+ if (end) {
224
+ css(el, marginProp, endDim - currentDim + currentMargin);
225
+ endProps[marginProp] = show ? currentMargin : endDim + currentMargin;
226
+ }
227
+
228
+ if (!end ^ (mode === 'reveal')) {
229
+ css(wrapper, marginProp, -endDim + currentDim);
230
+ Transition.start(wrapper, { [marginProp]: show ? 0 : -endDim }, duration, transition);
231
+ }
232
+
233
+ try {
234
+ await Transition.start(el, endProps, duration, transition);
235
+ } finally {
236
+ css(el, prevProps);
237
+ unwrap(wrapper.firstChild);
238
+
239
+ if (!show) {
240
+ _toggle(el, false);
241
+ }
242
+ }
279
243
  };
280
244
  }
281
245
 
@@ -4,7 +4,7 @@ import { css, propName } from './style';
4
4
  import { startsWith, toNodes } from './lang';
5
5
  import { addClass, hasClass, removeClass, removeClasses } from './class';
6
6
 
7
- export function transition(element, props, duration = 400, timing = 'linear') {
7
+ function transition(element, props, duration = 400, timing = 'linear') {
8
8
  duration = Math.round(duration);
9
9
  return Promise.all(
10
10
  toNodes(element).map(
@@ -50,13 +50,14 @@ export function transition(element, props, duration = 400, timing = 'linear') {
50
50
  export const Transition = {
51
51
  start: transition,
52
52
 
53
- stop(element) {
53
+ async stop(element) {
54
54
  trigger(element, 'transitionend');
55
- return Promise.resolve();
55
+ await Promise.resolve();
56
56
  },
57
57
 
58
- cancel(element) {
58
+ async cancel(element) {
59
59
  trigger(element, 'transitioncanceled');
60
+ await Promise.resolve();
60
61
  },
61
62
 
62
63
  inProgress(element) {
@@ -66,7 +67,7 @@ export const Transition = {
66
67
 
67
68
  const animationPrefix = 'uk-animation-';
68
69
 
69
- export function animate(element, animation, duration = 200, origin, out) {
70
+ function animate(element, animation, duration = 200, origin, out) {
70
71
  return Promise.all(
71
72
  toNodes(element).map(
72
73
  (element) =>
@@ -100,7 +101,8 @@ export function animate(element, animation, duration = 200, origin, out) {
100
101
  );
101
102
  }
102
103
 
103
- const inProgress = new RegExp(`${animationPrefix}(enter|leave)`);
104
+ const inProgressRe = new RegExp(`${animationPrefix}(enter|leave)`);
105
+
104
106
  export const Animation = {
105
107
  in: animate,
106
108
 
@@ -109,7 +111,7 @@ export const Animation = {
109
111
  },
110
112
 
111
113
  inProgress(element) {
112
- return inProgress.test(attr(element, 'class'));
114
+ return inProgressRe.test(attr(element, 'class'));
113
115
  },
114
116
 
115
117
  cancel(element) {
@@ -10,7 +10,9 @@ export function removeClass(element, ...args) {
10
10
  }
11
11
 
12
12
  export function removeClasses(element, cls) {
13
- attr(element, 'class', (value) => (value || '').replace(new RegExp(`\\b${cls}\\b`, 'g'), ''));
13
+ attr(element, 'class', (value) =>
14
+ (value || '').replace(new RegExp(`\\b${cls}\\b\\s?`, 'g'), '')
15
+ );
14
16
  }
15
17
 
16
18
  export function replaceClass(element, ...args) {
@@ -51,12 +51,8 @@ export function matches(element, selector) {
51
51
  }
52
52
 
53
53
  export function closest(element, selector) {
54
- if (startsWith(selector, '>')) {
55
- selector = selector.slice(1);
56
- }
57
-
58
54
  return isElement(element)
59
- ? element.closest(selector)
55
+ ? element.closest(startsWith(selector, '>') ? selector.slice(1) : selector)
60
56
  : toNodes(element)
61
57
  .map((element) => closest(element, selector))
62
58
  .filter(Boolean);
@@ -64,8 +60,8 @@ export function closest(element, selector) {
64
60
 
65
61
  export function within(element, selector) {
66
62
  return isString(selector)
67
- ? matches(element, selector) || !!closest(element, selector)
68
- : element === selector || toNode(selector).contains(toNode(element));
63
+ ? !!closest(element, selector)
64
+ : toNode(selector).contains(toNode(element));
69
65
  }
70
66
 
71
67
  export function parents(element, selector) {
@@ -1,5 +1,5 @@
1
1
  import { offset } from './dimensions';
2
- import { clamp, includes, ucfirst } from './lang';
2
+ import { clamp, isArray, ucfirst } from './lang';
3
3
  import { offsetViewport, scrollParents } from './viewport';
4
4
 
5
5
  const dirs = [
@@ -15,127 +15,63 @@ export function positionAt(element, target, options) {
15
15
  ...options.attach,
16
16
  },
17
17
  offset: [0, 0],
18
+ placement: [],
18
19
  ...options,
19
20
  };
20
21
 
21
- const dim = options.flip
22
- ? attachToWithFlip(element, target, options)
23
- : attachTo(element, target, options);
24
-
25
- offset(element, dim);
26
- }
27
-
28
- function attachTo(element, target, options) {
29
- let { attach, offset: offsetBy } = {
30
- attach: {
31
- element: ['left', 'top'],
32
- target: ['left', 'top'],
33
- ...options.attach,
34
- },
35
- offset: [0, 0],
36
- ...options,
37
- };
38
-
39
- const position = offset(element);
40
- const targetOffset = offset(target);
41
- for (const [i, [prop, dir, start, end]] of Object.entries(dirs)) {
42
- position[start] = position[dir] =
43
- targetOffset[start] +
44
- moveBy(attach.target[i], end, targetOffset[prop]) -
45
- moveBy(attach.element[i], end, position[prop]) +
46
- +offsetBy[i];
47
- position[end] = position[start] + position[prop];
22
+ if (!isArray(target)) {
23
+ target = [target, target];
48
24
  }
49
- return position;
50
- }
51
25
 
52
- function moveBy(start, end, dim) {
53
- return start === 'center' ? dim / 2 : start === end ? dim : 0;
26
+ offset(element, getPosition(element, target, options));
54
27
  }
55
28
 
56
- function attachToWithFlip(element, target, options) {
29
+ function getPosition(element, target, options) {
57
30
  const position = attachTo(element, target, options);
58
- const targetDim = offset(target);
59
31
 
60
32
  let {
61
- flip,
62
33
  attach: { element: elAttach, target: targetAttach },
63
34
  offset: elOffset,
64
35
  boundary,
65
- viewport,
66
36
  viewportOffset,
37
+ placement,
67
38
  } = options;
68
39
 
69
- let viewports = scrollParents(element);
70
- if (boundary === target) {
71
- viewports = viewports.filter((viewport) => viewport !== boundary);
72
- }
73
- const [scrollElement] = viewports;
74
- viewports.push(viewport);
40
+ let offsetPosition = position;
41
+ for (const [i, [prop, , start, end]] of Object.entries(dirs)) {
42
+ let viewports = scrollParents(target[i]);
43
+ const scrollArea = getScrollArea(viewports[0], viewportOffset, i);
75
44
 
76
- const offsetPosition = { ...position };
77
- for (const [i, [prop, dir, start, end]] of Object.entries(dirs)) {
78
- if (flip !== true && !includes(flip, dir)) {
79
- continue;
80
- }
81
-
82
- const willFlip =
83
- !intersectLine(position, targetDim, i) && intersectLine(position, targetDim, 1 - i);
84
-
85
- viewport = getIntersectionArea(...viewports.filter(Boolean).map(offsetViewport));
45
+ let viewport = getIntersectionArea(...viewports.map(offsetViewport));
86
46
 
87
47
  if (viewportOffset) {
88
48
  viewport[start] += viewportOffset;
89
49
  viewport[end] -= viewportOffset;
90
50
  }
91
51
 
92
- if (boundary && !willFlip && position[prop] <= offset(boundary)[prop]) {
52
+ if (boundary) {
93
53
  viewport = getIntersectionArea(viewport, offset(boundary));
94
54
  }
95
55
 
96
- const isInStartBoundary = position[start] >= viewport[start];
97
- const isInEndBoundary = position[end] <= viewport[end];
98
-
99
- if (isInStartBoundary && isInEndBoundary) {
56
+ if (isWithin(position, viewport, i)) {
100
57
  continue;
101
58
  }
102
59
 
103
- let offsetBy;
60
+ let offsetBy = 0;
104
61
 
105
62
  // Flip
106
- if (willFlip) {
63
+ if (placement[i] === 'flip') {
107
64
  if (
108
- (elAttach[i] === end && isInStartBoundary) ||
109
- (elAttach[i] === start && isInEndBoundary)
65
+ (targetAttach[i] === end && position[end] <= viewport[end]) ||
66
+ (targetAttach[i] === start && position[start] >= viewport[start])
110
67
  ) {
111
68
  continue;
112
69
  }
113
70
 
114
- offsetBy =
115
- (elAttach[i] === start
116
- ? -position[prop]
117
- : elAttach[i] === end
118
- ? position[prop]
119
- : 0) +
120
- (targetAttach[i] === start
121
- ? targetDim[prop]
122
- : targetAttach[i] === end
123
- ? -targetDim[prop]
124
- : 0) -
125
- elOffset[i] * 2;
71
+ offsetBy = flip(element, target, options, i)[start] - position[start];
126
72
 
127
- if (
128
- !isInScrollArea(
129
- {
130
- ...position,
131
- [start]: position[start] + offsetBy,
132
- [end]: position[end] + offsetBy,
133
- },
134
- scrollElement,
135
- i
136
- )
137
- ) {
138
- if (isInScrollArea(position, scrollElement, i)) {
73
+ if (!isWithin(applyOffset(position, offsetBy, i), scrollArea, i)) {
74
+ if (isWithin(position, scrollArea, i)) {
139
75
  continue;
140
76
  }
141
77
 
@@ -143,27 +79,27 @@ function attachToWithFlip(element, target, options) {
143
79
  return false;
144
80
  }
145
81
 
146
- if (flip === true || includes(flip, dirs[1 - i][1])) {
147
- const newPos = attachToWithFlip(element, target, {
148
- ...options,
149
- attach: {
150
- element: elAttach.map(flipDir).reverse(),
151
- target: targetAttach.map(flipDir).reverse(),
152
- },
153
- offset: elOffset.reverse(),
154
- flip: flip === true ? flip : [...flip, dirs[1 - i][1]],
155
- recursion: true,
156
- });
157
-
158
- if (newPos && isInScrollArea(newPos, scrollElement, 1 - i)) {
159
- return newPos;
160
- }
82
+ const newPos = getPosition(element, target, {
83
+ ...options,
84
+ attach: {
85
+ element: elAttach.map(flipAxis).reverse(),
86
+ target: targetAttach.map(flipAxis).reverse(),
87
+ },
88
+ offset: elOffset.reverse(),
89
+ placement: placement.reverse(),
90
+ recursion: true,
91
+ });
92
+
93
+ if (newPos && isWithin(newPos, scrollArea, 1 - i)) {
94
+ return newPos;
161
95
  }
96
+
162
97
  continue;
163
98
  }
164
99
 
165
- // Move
166
- } else {
100
+ // Shift
101
+ } else if (placement[i] === 'shift') {
102
+ const targetDim = offset(target[i]);
167
103
  offsetBy =
168
104
  clamp(
169
105
  clamp(position[start], viewport[start], viewport[end] - position[prop]),
@@ -172,13 +108,54 @@ function attachToWithFlip(element, target, options) {
172
108
  ) - position[start];
173
109
  }
174
110
 
175
- offsetPosition[start] = position[dir] = position[start] + offsetBy;
176
- offsetPosition[end] += offsetBy;
111
+ offsetPosition = applyOffset(offsetPosition, offsetBy, i);
177
112
  }
178
113
 
179
114
  return offsetPosition;
180
115
  }
181
116
 
117
+ function attachTo(element, target, options) {
118
+ let { attach, offset: offsetBy } = {
119
+ attach: {
120
+ element: ['left', 'top'],
121
+ target: ['left', 'top'],
122
+ ...options.attach,
123
+ },
124
+ offset: [0, 0],
125
+ ...options,
126
+ };
127
+
128
+ let elOffset = offset(element);
129
+
130
+ for (const [i, [prop, , start, end]] of Object.entries(dirs)) {
131
+ const targetOffset =
132
+ attach.target[i] === attach.element[i] ? offsetViewport(target[i]) : offset(target[i]);
133
+
134
+ elOffset = applyOffset(
135
+ elOffset,
136
+ targetOffset[start] -
137
+ elOffset[start] +
138
+ moveBy(attach.target[i], end, targetOffset[prop]) -
139
+ moveBy(attach.element[i], end, elOffset[prop]) +
140
+ +offsetBy[i],
141
+ i
142
+ );
143
+ }
144
+ return elOffset;
145
+ }
146
+
147
+ function applyOffset(position, offset, i) {
148
+ const [, dir, start, end] = dirs[i];
149
+ const newPos = { ...position };
150
+ newPos[start] = position[dir] = position[start] + offset;
151
+ newPos[end] += offset;
152
+ return newPos;
153
+ }
154
+
155
+ function moveBy(attach, end, dim) {
156
+ return attach === 'center' ? dim / 2 : attach === end ? dim : 0;
157
+ }
158
+
182
159
  function getIntersectionArea(...rects) {
183
160
  let area = {};
184
161
  for (const rect of rects) {
@@ -190,21 +167,45 @@ function getIntersectionArea(...rects) {
190
167
  return area;
191
168
  }
192
169
 
193
- function isInScrollArea(position, scrollElement, dir) {
194
- const viewport = offsetViewport(scrollElement, false);
195
- const [prop, , start, end] = dirs[dir];
196
- viewport[start] -= scrollElement[`scroll${ucfirst(start)}`];
197
- viewport[end] = viewport[start] + scrollElement[`scroll${ucfirst(prop)}`];
170
+ function getScrollArea(scrollElement, viewportOffset, i) {
171
+ const [prop, , start, end] = dirs[i];
172
+ const viewport = offsetViewport(scrollElement);
173
+ viewport[start] -= scrollElement[`scroll${ucfirst(start)}`] - viewportOffset;
174
+ viewport[end] = viewport[start] + scrollElement[`scroll${ucfirst(prop)}`] - viewportOffset;
175
+ return viewport;
176
+ }
198
177
 
199
- return position[start] >= viewport[start] && position[end] <= viewport[end];
178
+ function isWithin(positionA, positionB, i) {
179
+ const [, , start, end] = dirs[i];
180
+ return positionA[start] >= positionB[start] && positionA[end] <= positionB[end];
181
+ }
182
+
183
+ function flip(element, target, { offset, attach }, i) {
184
+ return attachTo(element, target, {
185
+ attach: {
186
+ element: flipAttach(attach.element, i),
187
+ target: flipAttach(attach.target, i),
188
+ },
189
+ offset: flipOffset(offset, i),
190
+ });
191
+ }
192
+
193
+ function flipAttach(attach, i) {
194
+ const newAttach = [...attach];
195
+ const index = dirs[i].indexOf(attach[i]);
196
+ if (~index) {
197
+ newAttach[i] = dirs[i][1 - (index % 2) + 2];
198
+ }
199
+ return newAttach;
200
200
  }
201
201
 
202
- function intersectLine(dimA, dimB, dir) {
203
- const [, , start, end] = dirs[dir];
204
- return dimA[end] > dimB[start] && dimB[end] > dimA[start];
202
+ function flipOffset(offset, i) {
203
+ offset = [...offset];
204
+ offset[i] *= -1;
205
+ return offset;
205
206
  }
206
207
 
207
- function flipDir(prop) {
208
+ function flipAxis(prop) {
208
209
  for (let i = 0; i < dirs.length; i++) {
209
210
  const index = dirs[i].indexOf(prop);
210
211
  if (~index) {