VulpesOS/gaia/apps/keyboard/js/views/swipeable_panel_view.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

299 lines
8.4 KiB
JavaScript
Executable file

'use strict';
/* global EmojiKeyView, KeyView, SwipingDetector */
(function(exports) {
/**
* SwipeablePanelView is used to render a panel that is swipeable to go through
* several sections.
*/
function SwipeablePanelView(layout, options, viewManager) {
this.layout = layout;
this.options = options;
this.viewManager = viewManager;
this.sections = [];
this.currentSectionIndex = 0;
this.indicators = [];
this.startX = 0;
this.swipingDetector = null;
}
SwipeablePanelView.prototype.COLUMN_COUNT = 6;
SwipeablePanelView.prototype.KEY_COUNT_PER_PANEL = 18;
SwipeablePanelView.prototype.SWIPE_THRESHOLD = 20;
SwipeablePanelView.prototype.SWIPE_FRACTION = 0.25;
SwipeablePanelView.prototype.SWIPE_SPEED = 0.5;
SwipeablePanelView.prototype.render = function() {
var panel = document.createElement('div');
panel.classList.add('swipe-panel');
var i = 0;
while (true) {
var section = this._renderSection(i);
if (!section) {
break;
}
this.sections.push(section);
section.classList.add('swipe-section');
panel.appendChild(section);
i++;
}
// SwipingDetector and SectionIndicator should be initialized
// only when there are more than one sections in panel.
if (this.sections.length > 1) {
this._createSwipingDetector(panel);
this._renderSectionIndicator(panel);
}
this.element = panel;
};
SwipeablePanelView.prototype.gotoSection = function(index) {
// Stay on the current section.
if (index === this.currentSectionIndex) {
var currentPos = this.sections[this.currentSectionIndex].
getBoundingClientRect();
if (currentPos.left !== 0) {
// Move the previous section backwards
if (index > 0) {
this._moveSection(index - 1, -this.options.totalWidth);
}
this._moveSection(index, 0);
// Move the next section forwards
if (index < this.sections.length - 1) {
this._moveSection(index + 1, this.options.totalWidth);
}
}
return;
}
this._moveSection(index, 0);
var forward = (index >= this.currentSectionIndex) ? 1 : -1;
// Move the original section
this._moveSection(this.currentSectionIndex,
-forward * this.options.totalWidth);
this.currentSectionIndex = index;
this._updateIndicator();
};
SwipeablePanelView.prototype.resize = function resize(totalWidth) {
this.options.totalWidth = totalWidth;
this.swipeDetectingPaused = true;
// Reposition sections when resize;
this.sections.forEach(function(section, index) {
var translateX;
if (index === this.currentSectionIndex) {
translateX = 0;
} else if (this.currentSectionIndex > index) {
translateX = -totalWidth;
} else {
translateX = totalWidth;
}
section.style.transform = 'translateX(' + translateX + 'px)';
}, this);
};
SwipeablePanelView.prototype._moveSection = function(index, distance) {
var style = this.sections[index].style;
style.transform = 'translateX(' + distance + 'px)';
style.transition = 'transform ' + 300 + 'ms ease';
};
SwipeablePanelView.prototype._updateIndicator = function() {
// Change the section indicator.
this.indicators.forEach(function(indicator, index) {
indicator.classList.toggle('active',
(index == this.currentSectionIndex));
}, this);
};
SwipeablePanelView.prototype._handleTouchStart = function(evt) {
this.startX = evt.position.clientX;
this.deltaX = 0;
this.swipeDetectingPaused = false;
};
SwipeablePanelView.prototype._handlePan = function(evt) {
if (this.swipeDetectingPaused) {
return;
}
var totalWidth = this.options.totalWidth;
// Clear all transition styles
this.sections.forEach(function(section) {
section.style.transition = '';
});
var currentX = evt.position.clientX;
this.deltaX = currentX - this.startX;
var previous;
var next;
var current;
var forward = this.deltaX < 0;
if (this.currentSectionIndex === 0) {
if (forward) {
this.sections[1].style.transform =
'translateX(' + (totalWidth + this.deltaX) + 'px)';
this.sections[0].style.transform =
'translateX(' + this.deltaX + 'px)';
} else {
this.startX = currentX;
}
} else if (this.currentSectionIndex === this.sections.length - 1) {
previous =
this.sections[this.currentSectionIndex - 1].style;
if (this.deltaX >= 0) {
previous.transform =
'translateX(' + (-totalWidth + this.deltaX) + 'px)';
this.sections[this.currentSectionIndex].style.transform =
'translateX(' + this.deltaX + 'px)';
} else {
this.startX = currentX;
}
} else {
previous = this.sections[this.currentSectionIndex - 1].style;
next = this.sections[this.currentSectionIndex + 1].style;
if (this.deltaX >= 0) {
previous.transform =
'translateX(' + (-totalWidth + this.deltaX) + 'px)';
// If we change direction make sure there isn't any part
// of the page on the other side that stays visible.
next.transform = 'translateX(' + totalWidth + 'px)';
} else {
next.transform = 'translateX(' + (totalWidth + this.deltaX) + 'px)';
// If we change direction make sure there isn't any part
// of the page on the other side that stays visible.
previous.transform =
'translateX(-' + totalWidth + 'px)';
}
current = this.sections[this.currentSectionIndex].style;
current.transform = 'translateX(' + this.deltaX + 'px)';
}
};
SwipeablePanelView.prototype._handleSwipe = function(evt) {
if (this.swipeDetectingPaused) {
return;
}
var totalWidth = this.options.totalWidth;
var targetIndex = this.currentSectionIndex;
var forward = evt.direction === 'left';
var isFarEnough = Math.abs(this.deltaX) > totalWidth * this.SWIPE_FRACTION;
var velocity = evt.vx;
var isFastEnough = Math.abs(velocity) > this.SWIPE_SPEED;
var isSameDirection = velocity === 0 || this.deltaX / velocity >= 0;
if ((isFarEnough || isFastEnough) && isSameDirection) {
if (forward && this.currentSectionIndex < this.sections.length - 1) {
targetIndex = this.currentSectionIndex + 1;
} else if (!forward && this.currentSectionIndex > 0) {
targetIndex = this.currentSectionIndex - 1;
}
}
this.gotoSection(targetIndex);
};
SwipeablePanelView.prototype._renderSection = function(index) {
var panelKeys = this.layout.panelKeys;
var startIndex = this.KEY_COUNT_PER_PANEL * index;
if (startIndex >= panelKeys.length) {
return null;
}
var endIndex = Math.min(startIndex + this.KEY_COUNT_PER_PANEL,
panelKeys.length);
var section = document.createElement('div');
for (var i = startIndex; i < endIndex; i++) {
var keyView = this._createKeyView(panelKeys[i]);
keyView.render();
section.appendChild(keyView.element);
}
// Create a dummy element to make the UI look like a grid.
var itemCount = i - startIndex;
if (itemCount % this.COLUMN_COUNT !== 0) {
var dummyEl = document.createElement('span');
dummyEl.style.flex = this.COLUMN_COUNT - (itemCount % this.COLUMN_COUNT);
section.appendChild(dummyEl);
}
this.element = section;
return section;
};
SwipeablePanelView.prototype._renderSectionIndicator = function(panel) {
var indicatorContainer = document.createElement('div');
indicatorContainer.classList.add('section-indicator-container');
for (var j = 0; j < this.sections.length; j++) {
var dot = document.createElement('span');
dot.classList.add('section-indicator');
this.indicators.push(dot);
indicatorContainer.appendChild(dot);
}
this._updateIndicator();
panel.appendChild(indicatorContainer);
};
SwipeablePanelView.prototype._createSwipingDetector = function(panel) {
this.swipingDetector = new SwipingDetector(panel);
this.swipingDetector.start();
this.swipingDetector.ontouchstart = this._handleTouchStart.bind(this);
this.swipingDetector.onpan = this._handlePan.bind(this);
this.swipingDetector.onswipe = this._handleSwipe.bind(this);
};
SwipeablePanelView.prototype._createKeyView = function(keyObject) {
var target = Object.create(keyObject);
// Set selection property as true, so it won't trigger send key when
// swiping.
target.selection = true;
var keyView;
switch (target.type) {
case 'emoji':
keyView = new EmojiKeyView(target, {}, this.viewManager);
break;
default:
keyView = new KeyView(target, {}, this.viewManager);
break;
}
return keyView;
};
exports.SwipeablePanelView = SwipeablePanelView;
})(window);