Skip to content

Commit 5361e0a

Browse files
committed
Feat: finish attach
1 parent 083ad3a commit 5361e0a

File tree

2 files changed

+84
-41
lines changed

2 files changed

+84
-41
lines changed

src/behaviors/attach/attach.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
}
3737

3838
&[data-position^="hidden"] {
39-
display: none !important;
39+
pointer-events: none;
40+
opacity: 0;
4041
}
4142

4243
&[data-position^="center"] {

src/behaviors/attach/attach.js

Lines changed: 82 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import { registerBehavior } from '@semantic-ui/query';
2-
import { each, inArray, isArray, isNumber, isString, range, throttle, wrapFunction } from '@semantic-ui/utils';
2+
import {
3+
each,
4+
idleCallback,
5+
inArray,
6+
isArray,
7+
isNumber,
8+
isString,
9+
range,
10+
throttle,
11+
wrapFunction,
12+
} from '@semantic-ui/utils';
313

414
import css from './attach.css?raw';
515

@@ -34,12 +44,12 @@ const defaultSettings = {
3444
containToScroll: true, // whether to contain element to its scroll container
3545

3646
// throttle delays for performance optimization
37-
scrollThrottle: 150, // milliseconds to throttle scroll repositioning
47+
scrollThrottle: 15, // milliseconds to throttle scroll repositioning
3848
resizeThrottle: 300, // milliseconds to throttle resize repositioning
3949
throttleSettings: { leading: false, trailing: true },
4050
};
4151

42-
const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classNames, error, debug, index, warn }) => ({
52+
const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, error, debug, warn }) => ({
4353
anchorName: null, // current anchor name
4454

4555
// available positions to try
@@ -59,7 +69,7 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
5969
],
6070

6171
// current position
62-
position: null,
72+
position: undefined,
6373

6474
// map of starting position to fallbacks using index of 'positions'
6575
// to make this less confusing it is 1-indexed.
@@ -126,12 +136,18 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
126136

127137
// Throttled repositioning methods
128138
onScroll: (settings.scrollThrottle > 0)
129-
? throttle(() => requestAnimationFrame(self.reposition), settings.scrollThrottle, settings.throttleSettings)
130-
: requestAnimationFrame(self.reposition),
139+
? throttle(
140+
() => {
141+
idleCallback(self.reposition);
142+
},
143+
settings.scrollThrottle,
144+
settings.throttleSettings,
145+
)
146+
: idleCallback(self.reposition),
131147

132148
onResize: (settings.resizeThrottle > 0)
133-
? throttle(() => requestAnimationFrame(self.reposition), settings.resizeThrottle, settings.throttleSettings)
134-
: requestAnimationFrame(self.reposition),
149+
? throttle(() => idleCallback(self.reposition), settings.resizeThrottle, settings.throttleSettings)
150+
: idleCallback(self.reposition),
135151

136152
getNextAnchorName() {
137153
if (!cache.count) {
@@ -179,8 +195,8 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
179195
o: '-50% -50%', // offset
180196
};
181197

182-
let css = self.positionMapping[position] || {};
183-
let outputCSS = {};
198+
const css = self.positionMapping[position] || {};
199+
const outputCSS = {};
184200

185201
// Skip distance/offset for center position
186202
const isCenter = position === 'center';
@@ -256,8 +272,8 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
256272
return $el.computedStyle('display') === 'none';
257273
},
258274

259-
setPosition(position = settings.position) {
260-
if (self.isHidden()) {
275+
setPositioningCSS(position = settings.position) {
276+
if (self.isHidden() || position === 'hidden') {
261277
return;
262278
}
263279
const positioningCSS = self.getPositioningCSS(position);
@@ -268,27 +284,40 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
268284
if (self.isHidden()) {
269285
return;
270286
}
271-
self.setPosition(position);
287+
self.setPositioningCSS(position);
272288
self.maybeReposition(position);
273289
},
274290

291+
isCurrentlyPositioning() {
292+
return self.fallbackPositions?.length;
293+
},
294+
275295
reposition() {
276296
// current repositioning
277-
if (self.fallbackPositions?.length) {
297+
if (self.isCurrentlyPositioning()) {
278298
debug('Already searching');
279299
return;
280300
}
301+
if (self.currentPositionInView()) {
302+
return;
303+
}
304+
// but begin from current position if defined
281305
self.testPosition(settings.position);
282306
},
283307

308+
currentPositionInView() {
309+
return self.position && self.maybeReposition(self.position) === false;
310+
},
311+
284312
// check if position is in view
285-
maybeReposition(currentPosition = settings.position) {
313+
maybeReposition(currentPosition = self.position) {
314+
const $viewport = (settings.containToScroll)
315+
? $el.scrollParent()
316+
: $el.clippingParent();
286317
const view = $el.isInView({
287318
fully: true,
288319
returnDetails: true,
289-
viewport: (settings.containToScroll)
290-
? $el.scrollParent()
291-
: $el.clippingParent(),
320+
viewport: $viewport,
292321
});
293322
if (!view.intersects) {
294323
debug('Position not in view', currentPosition, view);
@@ -300,14 +329,15 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
300329
}
301330
else {
302331
if (settings.lastResort) {
303-
self.setPosition(settings.lastResort);
332+
self.setResolvedPosition(settings.lastResort);
304333
}
305334
else {
306-
error('No positions fit viewport');
335+
warn('No positions fit viewport');
307336
}
308337
debug('no positions left to test');
338+
self.endFallbackSearch();
309339
}
310-
return false;
340+
return true;
311341
}
312342
else if (self.position !== currentPosition) {
313343
self.setResolvedPosition(currentPosition);
@@ -329,7 +359,7 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
329359
}
330360
self.position = resolvedPosition;
331361
debug('Found position in view', resolvedPosition);
332-
self.setPosition(resolvedPosition);
362+
self.setPositioningCSS(resolvedPosition);
333363
},
334364

335365
// fallback order is implemented as a lookup table
@@ -339,10 +369,10 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
339369
if (isArray(settings.fallbackStrategy)) {
340370
return settings.fallbackStrategy;
341371
}
342-
if (settings.fallbackStrategy == 'adjacent') {
372+
if (settings.fallbackStrategy === 'adjacent') {
343373
return self.adjacentFallbacks[position] || allPositions;
344374
}
345-
if (settings.fallbackStrategy == 'opposite') {
375+
if (settings.fallbackStrategy === 'opposite') {
346376
return self.oppositeFallbacks[position] || allPositions;
347377
}
348378
return allPositions;
@@ -359,7 +389,7 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
359389
self.fallbacksTested++;
360390
if (isNumber(nextPosition)) {
361391
const index = nextPosition - 1; // positions are 1-indexed
362-
debug('Next position is', nextPosition, self.positions[index]);
392+
debug('Next position is', self.positions[nextPosition]);
363393
return self.positions[index] || null;
364394
}
365395
// if its a string its just the position name (user specified)
@@ -372,10 +402,26 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
372402
}
373403
},
374404

375-
startFallbackSearch(startingPosition) {
405+
startFallbackSearch(startingPosition, currentPosition) {
406+
const rotateToFront = (arr, value) => {
407+
const index = arr.indexOf(value);
408+
if (index === -1) {
409+
return arr;
410+
}
411+
return [...arr.slice(index), ...arr.slice(0, index)];
412+
};
376413
self.fallbacksTested = 0;
414+
let positions = self.getFallbackOrder(startingPosition);
415+
416+
// if we specify current position we want to process array but starting with current position
417+
// this makes sure if its still in view we use the current position
418+
if (currentPosition) {
419+
const positionNumber = self.positions.indexOf(currentPosition);
420+
if (inArray(positionNumber, positions)) {
421+
positions = rotateToFront(positions, positionNumber);
422+
}
423+
}
377424
// clone array
378-
const positions = self.getFallbackOrder(startingPosition);
379425
self.fallbackPositions = [
380426
...positions,
381427
];
@@ -392,7 +438,7 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
392438
const elParent = $el.positioningParent().el();
393439

394440
// same positioning context
395-
if (anchorParent == elParent) {
441+
if (anchorParent === elParent) {
396442
return;
397443
}
398444

@@ -409,20 +455,24 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
409455
if (!self.anchorName) {
410456
self.setAnchorName();
411457
}
412-
let attachCSS = {
458+
const attachCSS = {
413459
'position': 'absolute',
414460
'position-anchor': self.anchorName,
415461
};
416462
$el.css(attachCSS);
417463
self.testPosition(settings.position);
418464
},
419465

466+
bindScroll() {
467+
const $scrolls = $el.scrollParent({ all: true });
468+
attachEvent($scrolls, 'scroll', self.onScroll);
469+
},
470+
420471
bindObservers() {
421472
const viewport = settings.containToScroll
422473
? $el.scrollParent()
423474
: $el.clippingParent();
424475

425-
console.log(viewport.el());
426476
self.observer = new IntersectionObserver(
427477
self.onIntersectionChange,
428478
{
@@ -431,17 +481,9 @@ const createBehavior = ({ $, $el, el, self, attachEvent, cache, settings, classN
431481
rootMargin: '50px',
432482
},
433483
);
434-
484+
el.offsetHeight;
435485
self.observer.observe(el);
436486
},
437-
438-
onIntersectionChange(entries) {
439-
const entry = entries[0];
440-
console.log(entries);
441-
if (!entry.isIntersecting) {
442-
self.reposition();
443-
}
444-
},
445487
});
446488

447489
const onCreated = ({ self, settings }) => {
@@ -453,7 +495,7 @@ const onCreated = ({ self, settings }) => {
453495
self.maybeMoveElement();
454496
}
455497
self.attach();
456-
self.bindObservers();
498+
self.bindScroll();
457499
};
458500

459501
const onDestroyed = ({ self }) => {

0 commit comments

Comments
 (0)