uikit 3.14.3 → 3.14.4-dev.07daf8fb8

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 (145) hide show
  1. package/CHANGELOG.md +54 -3
  2. package/build/util.js +1 -0
  3. package/dist/css/uikit-core-rtl.css +428 -95
  4. package/dist/css/uikit-core-rtl.min.css +1 -1
  5. package/dist/css/uikit-core.css +428 -95
  6. package/dist/css/uikit-core.min.css +1 -1
  7. package/dist/css/uikit-rtl.css +444 -108
  8. package/dist/css/uikit-rtl.min.css +1 -1
  9. package/dist/css/uikit.css +444 -108
  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 +7 -5
  14. package/dist/js/components/filter.min.js +1 -1
  15. package/dist/js/components/lightbox-panel.js +203 -76
  16. package/dist/js/components/lightbox-panel.min.js +1 -1
  17. package/dist/js/components/lightbox.js +203 -76
  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 +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 +22 -7
  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 +22 -7
  30. package/dist/js/components/slideshow.min.js +1 -1
  31. package/dist/js/components/sortable.js +3 -3
  32. package/dist/js/components/sortable.min.js +1 -1
  33. package/dist/js/components/tooltip.js +138 -76
  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 +1801 -1573
  38. package/dist/js/uikit-core.min.js +14 -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 +1081 -836
  42. package/dist/js/uikit.min.js +14 -1
  43. package/package.json +1 -1
  44. package/src/images/{backgrounds/nav-parent-open.svg → components/nav-parent-icon-large.svg} +0 -0
  45. package/src/images/components/nav-parent-icon.svg +3 -0
  46. package/src/images/components/navbar-parent-icon.svg +3 -0
  47. package/src/images/components/navbar-toggle-icon.svg +22 -3
  48. package/src/js/api/state.js +2 -2
  49. package/src/js/components/filter.js +5 -3
  50. package/src/js/components/sortable.js +2 -3
  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 +102 -48
  54. package/src/js/core/height-viewport.js +22 -9
  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 +47 -33
  59. package/src/js/core/offcanvas.js +1 -47
  60. package/src/js/core/scroll.js +37 -10
  61. package/src/js/core/sticky.js +8 -9
  62. package/src/js/core/switcher.js +1 -1
  63. package/src/js/mixin/media.js +4 -5
  64. package/src/js/mixin/modal.js +97 -8
  65. package/src/js/mixin/position.js +59 -14
  66. package/src/js/mixin/slider-drag.js +20 -8
  67. package/src/js/mixin/style.js +11 -0
  68. package/src/js/mixin/togglable.js +100 -72
  69. package/src/js/util/animation.js +5 -3
  70. package/src/js/util/dimensions.js +6 -6
  71. package/src/js/util/filter.js +3 -7
  72. package/src/js/util/position.js +43 -47
  73. package/src/js/util/style.js +4 -13
  74. package/src/js/util/viewport.js +21 -37
  75. package/src/less/components/_import.less +1 -0
  76. package/src/less/components/drop.less +3 -6
  77. package/src/less/components/dropbar.less +115 -0
  78. package/src/less/components/dropdown.less +22 -6
  79. package/src/less/components/leader.less +1 -1
  80. package/src/less/components/margin.less +13 -14
  81. package/src/less/components/modal.less +19 -4
  82. package/src/less/components/nav.less +241 -64
  83. package/src/less/components/navbar.less +111 -35
  84. package/src/less/components/offcanvas.less +21 -21
  85. package/src/less/components/position.less +1 -1
  86. package/src/less/components/utility.less +21 -5
  87. package/src/less/theme/_import.less +1 -0
  88. package/src/less/theme/dropbar.less +44 -0
  89. package/src/less/theme/nav.less +43 -9
  90. package/src/less/theme/navbar.less +9 -15
  91. package/src/scss/components/_import.scss +1 -0
  92. package/src/scss/components/drop.scss +3 -6
  93. package/src/scss/components/dropbar.scss +115 -0
  94. package/src/scss/components/dropdown.scss +22 -6
  95. package/src/scss/components/leader.scss +1 -1
  96. package/src/scss/components/margin.scss +13 -14
  97. package/src/scss/components/modal.scss +19 -4
  98. package/src/scss/components/nav.scss +190 -52
  99. package/src/scss/components/navbar.scss +88 -24
  100. package/src/scss/components/offcanvas.scss +21 -21
  101. package/src/scss/components/position.scss +1 -1
  102. package/src/scss/components/utility.scss +19 -4
  103. package/src/scss/mixins-theme.scss +93 -29
  104. package/src/scss/mixins.scss +89 -15
  105. package/src/scss/theme/_import.scss +1 -0
  106. package/src/scss/theme/dropbar.scss +44 -0
  107. package/src/scss/theme/nav.scss +41 -9
  108. package/src/scss/theme/navbar.scss +9 -3
  109. package/src/scss/variables-theme.scss +93 -25
  110. package/src/scss/variables.scss +82 -23
  111. package/tests/accordion.html +2 -2
  112. package/tests/alert.html +2 -2
  113. package/tests/countdown.html +1 -1
  114. package/tests/drop.html +484 -255
  115. package/tests/dropbar.html +456 -0
  116. package/tests/dropdown.html +29 -189
  117. package/tests/filter.html +9 -12
  118. package/tests/form.html +1 -1
  119. package/tests/height-viewport.html +62 -0
  120. package/tests/index.html +126 -107
  121. package/tests/js/index.js +1 -4
  122. package/tests/lightbox.html +5 -5
  123. package/tests/list.html +8 -8
  124. package/tests/modal.html +13 -13
  125. package/tests/nav.html +117 -75
  126. package/tests/navbar.html +2270 -1104
  127. package/tests/offcanvas.html +25 -29
  128. package/tests/parallax.html +1 -1
  129. package/tests/position.html +13 -24
  130. package/tests/progress.html +9 -9
  131. package/tests/scroll.html +7 -10
  132. package/tests/search.html +5 -5
  133. package/tests/slider.html +6 -5
  134. package/tests/slideshow.html +8 -8
  135. package/tests/sortable.html +6 -8
  136. package/tests/sticky-navbar.html +132 -0
  137. package/tests/sticky.html +8 -8
  138. package/tests/switcher.html +1 -1
  139. package/tests/tab.html +1 -1
  140. package/tests/table.html +7 -7
  141. package/tests/toggle.html +2 -2
  142. package/tests/tooltip.html +1 -1
  143. package/tests/upload.html +11 -11
  144. package/tests/utility.html +19 -0
  145. package/src/images/backgrounds/nav-parent-close.svg +0 -3
@@ -1,37 +1,44 @@
1
- import { flipPosition, getCssVar, includes, isRtl, positionAt, toPx } from 'uikit-util';
1
+ import {
2
+ css,
3
+ dimensions,
4
+ flipPosition,
5
+ includes,
6
+ isRtl,
7
+ positionAt,
8
+ scrollParents,
9
+ toPx,
10
+ } from 'uikit-util';
2
11
 
3
12
  export default {
4
13
  props: {
5
14
  pos: String,
6
15
  offset: null,
7
16
  flip: Boolean,
17
+ shift: Boolean,
18
+ inset: Boolean,
8
19
  },
9
20
 
10
21
  data: {
11
22
  pos: `bottom-${isRtl ? 'right' : 'left'}`,
12
- flip: true,
13
23
  offset: false,
24
+ flip: true,
25
+ shift: true,
26
+ inset: false,
14
27
  },
15
28
 
16
29
  connected() {
17
30
  this.pos = this.$props.pos.split('-').concat('center').slice(0, 2);
18
- this.axis = includes(['top', 'bottom'], this.pos[0]) ? 'y' : 'x';
31
+ [this.dir, this.align] = this.pos;
32
+ this.axis = includes(['top', 'bottom'], this.dir) ? 'y' : 'x';
19
33
  },
20
34
 
21
35
  methods: {
22
36
  positionAt(element, target, boundary) {
23
- const [dir, align] = this.pos;
24
-
25
- let offset = toPx(
26
- this.offset === false ? getCssVar('position-offset', element) : this.offset,
27
- this.axis === 'x' ? 'width' : 'height',
28
- element
29
- );
30
- offset = [includes(['left', 'top'], dir) ? -offset : +offset, 0];
37
+ let offset = [this.getPositionOffset(element), this.getShiftOffset(element)];
31
38
 
32
39
  const attach = {
33
- element: [flipPosition(dir), align],
34
- target: [dir, align],
40
+ element: [this.inset ? this.dir : flipPosition(this.dir), this.align],
41
+ target: [this.dir, this.align],
35
42
  };
36
43
 
37
44
  if (this.axis === 'y') {
@@ -41,13 +48,51 @@ export default {
41
48
  offset = offset.reverse();
42
49
  }
43
50
 
51
+ const [scrollElement] = scrollParents(element, /auto|scroll/);
52
+ const { scrollTop, scrollLeft } = scrollElement;
53
+
54
+ // Ensure none positioned element does not generate scrollbars
55
+ const elDim = dimensions(element);
56
+ css(element, { top: -elDim.height, left: -elDim.width });
57
+
44
58
  positionAt(element, target, {
45
59
  attach,
46
60
  offset,
47
61
  boundary,
48
62
  flip: this.flip,
49
- viewportOffset: toPx(getCssVar('position-viewport-offset', element)),
63
+ shift: this.shift,
64
+ viewportOffset: this.getViewportOffset(element),
50
65
  });
66
+
67
+ // Restore scroll position
68
+ scrollElement.scrollTop = scrollTop;
69
+ scrollElement.scrollLeft = scrollLeft;
70
+ },
71
+
72
+ getPositionOffset(element) {
73
+ return (
74
+ toPx(
75
+ this.offset === false ? css(element, '--uk-position-offset') : this.offset,
76
+ this.axis === 'x' ? 'width' : 'height',
77
+ element
78
+ ) *
79
+ (includes(['left', 'top'], this.dir) ? -1 : 1) *
80
+ (this.inset ? -1 : 1)
81
+ );
82
+ },
83
+
84
+ getShiftOffset(element) {
85
+ return this.align === 'center'
86
+ ? 0
87
+ : toPx(
88
+ css(element, '--uk-position-shift-offset'),
89
+ this.axis === 'y' ? 'width' : 'height',
90
+ element
91
+ ) * (includes(['left', 'top'], this.align) ? 1 : -1);
92
+ },
93
+
94
+ getViewportOffset(element) {
95
+ return toPx(css(element, '--uk-position-viewport-offset'));
51
96
  },
52
97
  },
53
98
  };
@@ -5,16 +5,18 @@ import {
5
5
  includes,
6
6
  isRtl,
7
7
  isTouch,
8
+ noop,
8
9
  off,
9
10
  on,
10
- pointerCancel,
11
- pointerDown,
12
- pointerMove,
13
- pointerUp,
14
11
  selInput,
15
12
  trigger,
16
13
  } from 'uikit-util';
17
14
 
15
+ const pointerOptions = { passive: false, capture: true };
16
+ const pointerDown = 'touchstart mousedown';
17
+ const pointerMove = 'touchmove mousemove';
18
+ const pointerUp = 'touchend touchcancel mouseup click input';
19
+
18
20
  export default {
19
21
  props: {
20
22
  draggable: Boolean,
@@ -69,6 +71,16 @@ export default {
69
71
  e.preventDefault();
70
72
  },
71
73
  },
74
+
75
+ {
76
+ // iOS workaround for slider stopping if swiping fast
77
+ name: `${pointerMove} ${pointerUp}`,
78
+ el() {
79
+ return this.list;
80
+ },
81
+ handler: noop,
82
+ ...pointerOptions,
83
+ },
72
84
  ],
73
85
 
74
86
  methods: {
@@ -89,10 +101,10 @@ export default {
89
101
  this.prevIndex = this.index;
90
102
  }
91
103
 
92
- on(document, pointerMove, this.move, { passive: false });
104
+ on(document, pointerMove, this.move, pointerOptions);
93
105
 
94
106
  // 'input' event is triggered by video controls
95
- on(document, `${pointerUp} ${pointerCancel} input`, this.end, true);
107
+ on(document, pointerUp, this.end, pointerOptions);
96
108
 
97
109
  css(this.list, 'userSelect', 'none');
98
110
  },
@@ -172,8 +184,8 @@ export default {
172
184
  },
173
185
 
174
186
  end() {
175
- off(document, pointerMove, this.move, { passive: false });
176
- off(document, `${pointerUp} ${pointerCancel} input`, this.end, true);
187
+ off(document, pointerMove, this.move, pointerOptions);
188
+ off(document, pointerUp, this.end, pointerOptions);
177
189
 
178
190
  if (this.dragging) {
179
191
  this.dragging = null;
@@ -0,0 +1,11 @@
1
+ import { attr } from 'uikit-util';
2
+
3
+ export default {
4
+ beforeConnect() {
5
+ this._style = attr(this.$el, 'style');
6
+ },
7
+
8
+ disconnected() {
9
+ attr(this.$el, 'style', this._style);
10
+ },
11
+ };
@@ -3,20 +3,22 @@ import {
3
3
  addClass,
4
4
  Animation,
5
5
  css,
6
- fastdom,
6
+ dimensions,
7
7
  hasClass,
8
- height,
9
8
  includes,
10
9
  isBoolean,
11
10
  isFunction,
12
11
  isVisible,
13
12
  noop,
14
13
  removeClass,
14
+ startsWith,
15
15
  toFloat,
16
16
  toggleClass,
17
17
  toNodes,
18
18
  Transition,
19
19
  trigger,
20
+ unwrap,
21
+ wrapInner,
20
22
  } from 'uikit-util';
21
23
 
22
24
  export default {
@@ -38,26 +40,6 @@ export default {
38
40
  transition: 'ease',
39
41
  clsEnter: 'uk-togglabe-enter',
40
42
  clsLeave: 'uk-togglabe-leave',
41
-
42
- initProps: {
43
- overflow: '',
44
- height: '',
45
- paddingTop: '',
46
- paddingBottom: '',
47
- marginTop: '',
48
- marginBottom: '',
49
- boxShadow: '',
50
- },
51
-
52
- hideProps: {
53
- overflow: 'hidden',
54
- height: 0,
55
- paddingTop: 0,
56
- paddingBottom: 0,
57
- marginTop: 0,
58
- marginBottom: 0,
59
- boxShadow: 'none',
60
- },
61
43
  },
62
44
 
63
45
  computed: {
@@ -66,7 +48,7 @@ export default {
66
48
  },
67
49
 
68
50
  hasTransition({ animation }) {
69
- return this.hasAnimation && animation[0] === true;
51
+ return ['slide', 'reveal'].some((transition) => startsWith(animation[0], transition));
70
52
  },
71
53
  },
72
54
 
@@ -81,18 +63,13 @@ export default {
81
63
  return Promise.reject();
82
64
  }
83
65
 
84
- if (!animate) {
85
- Animation.cancel(el);
86
- Transition.cancel(el);
87
- }
88
-
89
66
  const promise = (
90
67
  isFunction(animate)
91
68
  ? animate
92
69
  : animate === false || !this.hasAnimation
93
- ? this._toggle
70
+ ? toggleInstant(this)
94
71
  : this.hasTransition
95
- ? toggleHeight(this)
72
+ ? toggleTransition(this)
96
73
  : toggleAnimation(this)
97
74
  )(el, show);
98
75
 
@@ -156,54 +133,105 @@ export default {
156
133
  },
157
134
  };
158
135
 
159
- export function toggleHeight({
160
- isToggled,
161
- duration,
162
- velocity,
163
- initProps,
164
- hideProps,
165
- transition,
166
- _toggle,
167
- }) {
136
+ function toggleInstant({ _toggle }) {
168
137
  return (el, show) => {
169
- const inProgress = Transition.inProgress(el);
170
- const inner = el.hasChildNodes()
171
- ? toFloat(css(el.firstElementChild, 'marginTop')) +
172
- toFloat(css(el.lastElementChild, 'marginBottom'))
173
- : 0;
174
- const currentHeight = isVisible(el) ? height(el) + (inProgress ? 0 : inner) : 0;
175
-
138
+ Animation.cancel(el);
176
139
  Transition.cancel(el);
140
+ return _toggle(el, show);
141
+ };
142
+ }
143
+
144
+ export function toggleTransition(cmp) {
145
+ const [mode = 'reveal', startProp = 'top'] = cmp.animation[0]?.split('-') || [];
146
+
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}`;
157
+
158
+ return async (el, show) => {
159
+ let { duration, velocity, transition, _toggle } = cmp;
160
+
161
+ let currentDim = dimensions(el)[dimProp];
162
+
163
+ const inProgress = Transition.inProgress(el);
164
+ await Transition.cancel(el);
177
165
 
178
- if (!isToggled(el)) {
166
+ if (show) {
179
167
  _toggle(el, true);
180
168
  }
181
169
 
182
- height(el, '');
183
-
184
- // Update child components first
185
- fastdom.flush();
186
-
187
- const endHeight = height(el) + (inProgress ? 0 : inner);
188
- duration = velocity * el.offsetHeight + duration;
189
-
190
- height(el, currentHeight);
191
-
192
- return (
193
- show
194
- ? Transition.start(
195
- el,
196
- { ...initProps, overflow: 'hidden', height: endHeight },
197
- Math.round(duration * (1 - currentHeight / endHeight)),
198
- transition
199
- )
200
- : Transition.start(
201
- el,
202
- hideProps,
203
- Math.round(duration * (currentHeight / endHeight)),
204
- transition
205
- ).then(() => _toggle(el, false))
206
- ).then(() => css(el, initProps));
170
+ const prevProps = Object.fromEntries(
171
+ ['padding', 'border', 'width', 'height', 'overflow', marginProp, marginStartProp].map(
172
+ (key) => [key, el.style[key]]
173
+ )
174
+ );
175
+
176
+ const dim = dimensions(el);
177
+ const currentMargin = toFloat(css(el, marginProp));
178
+ const marginStart = toFloat(css(el, marginStartProp));
179
+ const endDim = dim[dimProp] + marginStart;
180
+
181
+ if (!inProgress && !show) {
182
+ currentDim += marginStart;
183
+ }
184
+
185
+ const [wrapper] = wrapInner(el, '<div>');
186
+ css(wrapper, {
187
+ boxSizing: 'border-box',
188
+ height: dim.height,
189
+ width: dim.width,
190
+ ...css(el, [
191
+ 'padding',
192
+ 'borderTop',
193
+ 'borderRight',
194
+ 'borderBottom',
195
+ 'borderLeft',
196
+ 'borderImage',
197
+ marginStartProp,
198
+ ]),
199
+ });
200
+
201
+ css(el, {
202
+ padding: 0,
203
+ border: 0,
204
+ [marginStartProp]: 0,
205
+ width: dim.width,
206
+ height: dim.height,
207
+ overflow: 'hidden',
208
+ [dimProp]: currentDim,
209
+ });
210
+
211
+ const percent = currentDim / endDim;
212
+ duration = (velocity * endDim + duration) * (show ? 1 - percent : percent);
213
+ const endProps = { [dimProp]: show ? endDim : 0 };
214
+
215
+ if (end) {
216
+ css(el, marginProp, endDim - currentDim + currentMargin);
217
+ endProps[marginProp] = show ? currentMargin : endDim + currentMargin;
218
+ }
219
+
220
+ if (!end ^ (mode === 'reveal')) {
221
+ css(wrapper, marginProp, -endDim + currentDim);
222
+ Transition.start(wrapper, { [marginProp]: show ? 0 : -endDim }, duration, transition);
223
+ }
224
+
225
+ try {
226
+ await Transition.start(el, endProps, duration, transition);
227
+ } finally {
228
+ css(el, prevProps);
229
+ unwrap(wrapper.firstChild);
230
+
231
+ if (!show) {
232
+ _toggle(el, false);
233
+ }
234
+ }
207
235
  };
208
236
  }
209
237
 
@@ -5,6 +5,7 @@ import { startsWith, toNodes } from './lang';
5
5
  import { addClass, hasClass, removeClass, removeClasses } from './class';
6
6
 
7
7
  export function transition(element, props, duration = 400, timing = 'linear') {
8
+ duration = Math.round(duration);
8
9
  return Promise.all(
9
10
  toNodes(element).map(
10
11
  (element) =>
@@ -49,13 +50,14 @@ export function transition(element, props, duration = 400, timing = 'linear') {
49
50
  export const Transition = {
50
51
  start: transition,
51
52
 
52
- stop(element) {
53
+ async stop(element) {
53
54
  trigger(element, 'transitionend');
54
- return Promise.resolve();
55
+ await Promise.resolve();
55
56
  },
56
57
 
57
- cancel(element) {
58
+ async cancel(element) {
58
59
  trigger(element, 'transitioncanceled');
60
+ await Promise.resolve();
59
61
  },
60
62
 
61
63
  inProgress(element) {
@@ -94,13 +94,13 @@ export function position(element) {
94
94
  }
95
95
 
96
96
  export function offsetPosition(element) {
97
- const offset = [0, 0];
98
-
99
97
  element = toNode(element);
100
98
 
101
- do {
102
- offset[0] += element.offsetTop;
103
- offset[1] += element.offsetLeft;
99
+ const offset = [element.offsetTop, element.offsetLeft];
100
+
101
+ while ((element = element.offsetParent)) {
102
+ offset[0] += element.offsetTop + toFloat(css(element, `borderTopWidth`));
103
+ offset[1] += element.offsetLeft + toFloat(css(element, `borderLeftWidth`));
104
104
 
105
105
  if (css(element, 'position') === 'fixed') {
106
106
  const win = toWindow(element);
@@ -108,7 +108,7 @@ export function offsetPosition(element) {
108
108
  offset[1] += win.scrollX;
109
109
  return offset;
110
110
  }
111
- } while ((element = element.offsetParent));
111
+ }
112
112
 
113
113
  return offset;
114
114
  }
@@ -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 = [
@@ -18,11 +18,11 @@ export function positionAt(element, target, options) {
18
18
  ...options,
19
19
  };
20
20
 
21
- const dim = options.flip
22
- ? attachToWithFlip(element, target, options)
23
- : attachTo(element, target, options);
21
+ if (!isArray(target)) {
22
+ target = [target, target];
23
+ }
24
24
 
25
- offset(element, dim);
25
+ offset(element, getPosition(element, target, options));
26
26
  }
27
27
 
28
28
  function attachTo(element, target, options) {
@@ -37,8 +37,11 @@ function attachTo(element, target, options) {
37
37
  };
38
38
 
39
39
  const position = offset(element);
40
- const targetOffset = offset(target);
40
+
41
41
  for (const [i, [prop, dir, start, end]] of Object.entries(dirs)) {
42
+ const targetOffset =
43
+ attach.target[i] === attach.element[i] ? offsetViewport(target[i]) : offset(target[i]);
44
+
42
45
  position[start] = position[dir] =
43
46
  targetOffset[start] +
44
47
  moveBy(attach.target[i], end, targetOffset[prop]) -
@@ -53,60 +56,54 @@ function moveBy(start, end, dim) {
53
56
  return start === 'center' ? dim / 2 : start === end ? dim : 0;
54
57
  }
55
58
 
56
- function attachToWithFlip(element, target, options) {
59
+ function getPosition(element, target, options) {
57
60
  const position = attachTo(element, target, options);
58
- const targetDim = offset(target);
59
61
 
60
62
  let {
61
63
  flip,
64
+ shift,
62
65
  attach: { element: elAttach, target: targetAttach },
63
66
  offset: elOffset,
64
67
  boundary,
65
- viewport,
66
68
  viewportOffset,
67
69
  } = options;
68
70
 
69
- let viewports = scrollParents(element);
70
- if (boundary === target) {
71
- viewports = viewports.filter((viewport) => viewport !== boundary);
71
+ if (!flip && !shift) {
72
+ return position;
72
73
  }
73
- const [scrollElement] = viewports;
74
- viewports.push(viewport);
75
74
 
76
75
  const offsetPosition = { ...position };
77
76
  for (const [i, [prop, dir, start, end]] of Object.entries(dirs)) {
78
- if (flip !== true && !includes(flip, dir)) {
79
- continue;
80
- }
77
+ let viewports = scrollParents(target[i]);
78
+ const [scrollElement] = viewports;
81
79
 
82
- const willFlip =
83
- !intersectLine(position, targetDim, i) && intersectLine(position, targetDim, 1 - i);
84
-
85
- viewport = getIntersectionArea(...viewports.filter(Boolean).map(offsetViewport));
80
+ let viewport = getIntersectionArea(...viewports.map(offsetViewport));
86
81
 
87
82
  if (viewportOffset) {
88
83
  viewport[start] += viewportOffset;
89
84
  viewport[end] -= viewportOffset;
90
85
  }
91
86
 
92
- if (boundary && !willFlip && position[prop] <= offset(boundary)[prop]) {
93
- viewport = getIntersectionArea(viewport, offset(boundary));
87
+ if (boundary) {
88
+ viewport = getIntersectionArea(viewport, offsetViewport(boundary));
94
89
  }
95
90
 
96
- const isInStartBoundary = position[start] >= viewport[start];
97
- const isInEndBoundary = position[end] <= viewport[end];
91
+ const isInStartViewport = position[start] >= viewport[start];
92
+ const isInEndViewport = position[end] <= viewport[end];
98
93
 
99
- if (isInStartBoundary && isInEndBoundary) {
94
+ if (isInStartViewport && isInEndViewport) {
100
95
  continue;
101
96
  }
102
97
 
103
- let offsetBy;
98
+ let offsetBy = 0;
99
+ const targetDim = offset(target[i]);
104
100
 
105
101
  // Flip
106
- if (willFlip) {
102
+ if (!intersectLine(position, targetDim, i) && intersectLine(position, targetDim, 1 - i)) {
107
103
  if (
108
- (elAttach[i] === end && isInStartBoundary) ||
109
- (elAttach[i] === start && isInEndBoundary)
104
+ !flip ||
105
+ (elAttach[i] === end && isInStartViewport) ||
106
+ (elAttach[i] === start && isInEndViewport)
110
107
  ) {
111
108
  continue;
112
109
  }
@@ -143,26 +140,25 @@ function attachToWithFlip(element, target, options) {
143
140
  return false;
144
141
  }
145
142
 
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
- }
143
+ const newPos = getPosition(element, target, {
144
+ ...options,
145
+ attach: {
146
+ element: elAttach.map(flipDir).reverse(),
147
+ target: targetAttach.map(flipDir).reverse(),
148
+ },
149
+ offset: elOffset.reverse(),
150
+ recursion: true,
151
+ });
152
+
153
+ if (newPos && isInScrollArea(newPos, scrollElement, 1 - i)) {
154
+ return newPos;
161
155
  }
156
+
157
+ continue;
162
158
  }
163
159
 
164
- // Move
165
- } else {
160
+ // Shift
161
+ } else if (shift) {
166
162
  offsetBy =
167
163
  clamp(
168
164
  clamp(position[start], viewport[start], viewport[end] - position[prop]),