mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-27 21:03:29 +03:00
Adjusted keyboard nav in gif selector to reduce vertical scroll jumps
refs https://github.com/TryGhost/Team/issues/1225 - `TAB` / `SHIFT+TAB` now cycle through gifs in the search return order. It means the highlight gif will not always be in the next column over but it drastically reduces the vertical scroll jumping - `LEFT` / `RIGHT` now select the gif to the left/right that visually lines up with the top third of the currently highlighted gif and will stop at the grid edges. The result is `UP` / `DOWN` / `LEFT` / `RIGHT` act more like spatial navigation with no unexpected scroll jumps - switching to only storing the highlighted gif and relying on indexes added to each gif by the `tenor` service when assigning to columns means that column number changes when resizing the viewport are automatically handled
This commit is contained in:
parent
a45e345a95
commit
5b90fe87ad
@ -200,6 +200,9 @@ export default class TenorService extends Service {
|
|||||||
// add to general gifs list
|
// add to general gifs list
|
||||||
this.gifs.push(gif);
|
this.gifs.push(gif);
|
||||||
|
|
||||||
|
// store index for use in templates and keyboard nav
|
||||||
|
gif.index = this.gifs.indexOf(gif);
|
||||||
|
|
||||||
// add to least populated column
|
// add to least populated column
|
||||||
this._addGifToColumns(gif);
|
this._addGifToColumns(gif);
|
||||||
}
|
}
|
||||||
@ -211,6 +214,10 @@ export default class TenorService extends Service {
|
|||||||
// use a fixed width when calculating height to compensate for different overall sizes
|
// use a fixed width when calculating height to compensate for different overall sizes
|
||||||
this._columnHeights[columnIndex] += 300 * gif.ratio;
|
this._columnHeights[columnIndex] += 300 * gif.ratio;
|
||||||
this.columns[columnIndex].push(gif);
|
this.columns[columnIndex].push(gif);
|
||||||
|
|
||||||
|
// store the column indexes on the gif for use in keyboard nav
|
||||||
|
gif.columnIndex = columnIndex;
|
||||||
|
gif.columnRowIndex = this.columns[columnIndex].length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
_resetColumns() {
|
_resetColumns() {
|
||||||
|
@ -33,7 +33,8 @@
|
|||||||
@gif={{gif}}
|
@gif={{gif}}
|
||||||
@select={{fn this.select gif}}
|
@select={{fn this.select gif}}
|
||||||
@isHighlighted={{eq gif this.highlightedGif}}
|
@isHighlighted={{eq gif this.highlightedGif}}
|
||||||
{{scroll-into-view (eq gif this.highlightedGif) offset=20}} />
|
{{scroll-into-view (eq gif this.highlightedGif) offset=20}}
|
||||||
|
data-tenor-index={{gif.index}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
@ -12,20 +12,7 @@ const THREE_COLUMN_WIDTH = 940;
|
|||||||
export default class KoenigCardImageTenorSelector extends Component {
|
export default class KoenigCardImageTenorSelector extends Component {
|
||||||
@service tenor;
|
@service tenor;
|
||||||
|
|
||||||
@tracked highlightedColumnIndex;
|
@tracked highlightedGif;
|
||||||
@tracked highlightedRowIndex;
|
|
||||||
|
|
||||||
get highlightedGif() {
|
|
||||||
if (this.highlightedColumnIndex === undefined || this.highlightedRowIndex === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.tenor.columns[this.highlightedColumnIndex][this.highlightedRowIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
get highlightedColumn() {
|
|
||||||
return this.tenor.columns[this.highlightedColumnIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
willDestroy() {
|
willDestroy() {
|
||||||
super.willDestroy(...arguments);
|
super.willDestroy(...arguments);
|
||||||
@ -91,74 +78,117 @@ export default class KoenigCardImageTenorSelector extends Component {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
clearHighlight() {
|
clearHighlight() {
|
||||||
this.highlightedColumnIndex = undefined;
|
this.highlightedGif = undefined;
|
||||||
this.highlightedRowIndex = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
highlightFirst() {
|
highlightFirst() {
|
||||||
this.highlightedColumnIndex = 0;
|
this.highlightedGif = this.tenor.gifs[0];
|
||||||
this.highlightedRowIndex = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
highlightNext() {
|
highlightNext() {
|
||||||
if (this.highlightedColumnIndex === this.tenor.columns.length - 1) {
|
if (this.highlightedGif === this.tenor.gifs[this.tenor.gifs.length - 1]) {
|
||||||
// at the end of a row, drop down to the next one
|
|
||||||
const newColumn = 0;
|
|
||||||
const newRow = this.highlightedRowIndex + 1;
|
|
||||||
|
|
||||||
if (newRow >= this.tenor.columns[newColumn].length) {
|
|
||||||
// reached the end, do nothing
|
// reached the end, do nothing
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.highlightedColumnIndex = newColumn;
|
this.highlightedGif = this.tenor.gifs[this.highlightedGif.index + 1];
|
||||||
this.highlightedRowIndex = newRow;
|
|
||||||
} else {
|
|
||||||
// mid-row, move to next column
|
|
||||||
this.highlightedColumnIndex += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
highlightPrev() {
|
highlightPrev() {
|
||||||
if (this.highlightedColumnIndex === 0) {
|
if (this.highlightedGif.index === 0) {
|
||||||
// at the start of a row, jump up to the prev one
|
|
||||||
const newColumn = this.tenor.columns.length - 1;
|
|
||||||
const newRow = this.highlightedRowIndex - 1;
|
|
||||||
|
|
||||||
if (newRow < 0) {
|
|
||||||
// reached the beginning, focus the search bar
|
// reached the beginning, focus the search bar
|
||||||
return this.focusSearch();
|
return this.focusSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.highlightedColumnIndex = newColumn;
|
this.highlightedGif = this.tenor.gifs[this.highlightedGif.index - 1];
|
||||||
this.highlightedRowIndex = newRow;
|
|
||||||
} else {
|
|
||||||
// mid-row, move to prev column
|
|
||||||
this.highlightedColumnIndex -= 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
moveHighlightDown() {
|
moveHighlightDown() {
|
||||||
if (this.highlightedRowIndex === this.highlightedColumn.length - 1) {
|
const nextGif = this.tenor.columns[this.highlightedGif.columnIndex][this.highlightedGif.columnRowIndex + 1];
|
||||||
// aready at bottom, do nothing
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.highlightedRowIndex += 1;
|
if (nextGif) {
|
||||||
|
this.highlightedGif = nextGif;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
moveHighlightUp() {
|
moveHighlightUp() {
|
||||||
if (this.highlightedRowIndex === 0) {
|
const nextGif = this.tenor.columns[this.highlightedGif.columnIndex][this.highlightedGif.columnRowIndex - 1];
|
||||||
// already at top, focus to the search bar
|
|
||||||
|
if (nextGif) {
|
||||||
|
this.highlightedGif = nextGif;
|
||||||
|
} else {
|
||||||
|
// already at top, focus the search bar
|
||||||
|
return this.focusSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
moveHighlightRight() {
|
||||||
|
if (this.highlightedGif.columnIndex === this.tenor.columns.length - 1) {
|
||||||
|
// we don't wrap and we're on the last column, do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._moveToNextHorizontalGif('right');
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
moveHighlightLeft() {
|
||||||
|
if (this.highlightedGif.index === 0) {
|
||||||
|
// on the first Gif, focus the search bar
|
||||||
return this.focusSearch();
|
return this.focusSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.highlightedRowIndex -= 1;
|
if (this.highlightedGif.columnIndex === 0) {
|
||||||
|
// we don't wrap and we're on the first column, do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._moveToNextHorizontalGif('left');
|
||||||
|
}
|
||||||
|
|
||||||
|
_moveToNextHorizontalGif(direction) {
|
||||||
|
const highlightedElem = document.querySelector(`[data-tenor-index="${this.highlightedGif.index}"]`);
|
||||||
|
const highlightedElemRect = highlightedElem.getBoundingClientRect();
|
||||||
|
|
||||||
|
let x;
|
||||||
|
if (direction === 'left') {
|
||||||
|
x = highlightedElemRect.left - (highlightedElemRect.width / 2);
|
||||||
|
} else {
|
||||||
|
x = highlightedElemRect.right + (highlightedElemRect.width / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
let y = highlightedElemRect.top + (highlightedElemRect.height / 3);
|
||||||
|
|
||||||
|
let foundGifElem;
|
||||||
|
let jumps = 0;
|
||||||
|
|
||||||
|
// we might hit spacing between gifs, keep moving up 5 px until we get a match
|
||||||
|
while (!foundGifElem) {
|
||||||
|
let possibleMatch = document.elementFromPoint(x, y)?.closest('[data-tenor-index]');
|
||||||
|
|
||||||
|
if (possibleMatch?.dataset.tenorIndex !== undefined) {
|
||||||
|
foundGifElem = possibleMatch;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
jumps += 1;
|
||||||
|
y -= 5;
|
||||||
|
|
||||||
|
if (jumps > 10) {
|
||||||
|
// give up to avoid infinite loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundGifElem) {
|
||||||
|
this.highlightedGif = this.tenor.gifs[foundGifElem.dataset.tenorIndex];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@onKey('Tab')
|
@onKey('Tab')
|
||||||
@ -186,7 +216,7 @@ export default class KoenigCardImageTenorSelector extends Component {
|
|||||||
handleLeft(event) {
|
handleLeft(event) {
|
||||||
if (this.highlightedGif) {
|
if (this.highlightedGif) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.highlightPrev();
|
this.moveHighlightLeft();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +224,7 @@ export default class KoenigCardImageTenorSelector extends Component {
|
|||||||
handleRight(event) {
|
handleRight(event) {
|
||||||
if (this.highlightedGif) {
|
if (this.highlightedGif) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.highlightNext();
|
this.moveHighlightRight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user