mirror of
https://github.com/flarum/core.git
synced 2025-08-05 16:07:34 +02:00
First shot at extracting keyboard navigation code to separate util class
Refs #264.
This commit is contained in:
177
js/forum/dist/app.js
vendored
177
js/forum/dist/app.js
vendored
@@ -26137,8 +26137,8 @@ System.register('flarum/components/RequestErrorModal', ['flarum/components/Modal
|
|||||||
});;
|
});;
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
System.register('flarum/components/Search', ['flarum/Component', 'flarum/components/LoadingIndicator', 'flarum/utils/ItemList', 'flarum/utils/classList', 'flarum/utils/extractText', 'flarum/helpers/icon', 'flarum/components/DiscussionsSearchSource', 'flarum/components/UsersSearchSource'], function (_export, _context) {
|
System.register('flarum/components/Search', ['flarum/Component', 'flarum/components/LoadingIndicator', 'flarum/utils/ItemList', 'flarum/utils/classList', 'flarum/utils/extractText', 'flarum/utils/KeyboardNavigatable', 'flarum/helpers/icon', 'flarum/components/DiscussionsSearchSource', 'flarum/components/UsersSearchSource'], function (_export, _context) {
|
||||||
var Component, LoadingIndicator, ItemList, classList, extractText, icon, DiscussionsSearchSource, UsersSearchSource, Search;
|
var Component, LoadingIndicator, ItemList, classList, extractText, KeyboardNavigatable, icon, DiscussionsSearchSource, UsersSearchSource, Search;
|
||||||
return {
|
return {
|
||||||
setters: [function (_flarumComponent) {
|
setters: [function (_flarumComponent) {
|
||||||
Component = _flarumComponent.default;
|
Component = _flarumComponent.default;
|
||||||
@@ -26150,6 +26150,8 @@ System.register('flarum/components/Search', ['flarum/Component', 'flarum/compone
|
|||||||
classList = _flarumUtilsClassList.default;
|
classList = _flarumUtilsClassList.default;
|
||||||
}, function (_flarumUtilsExtractText) {
|
}, function (_flarumUtilsExtractText) {
|
||||||
extractText = _flarumUtilsExtractText.default;
|
extractText = _flarumUtilsExtractText.default;
|
||||||
|
}, function (_flarumUtilsKeyboardNavigatable) {
|
||||||
|
KeyboardNavigatable = _flarumUtilsKeyboardNavigatable.default;
|
||||||
}, function (_flarumHelpersIcon) {
|
}, function (_flarumHelpersIcon) {
|
||||||
icon = _flarumHelpersIcon.default;
|
icon = _flarumHelpersIcon.default;
|
||||||
}, function (_flarumComponentsDiscussionsSearchSource) {
|
}, function (_flarumComponentsDiscussionsSearchSource) {
|
||||||
@@ -26286,38 +26288,17 @@ System.register('flarum/components/Search', ['flarum/Component', 'flarum/compone
|
|||||||
search.setIndex(search.selectableItems().index(this));
|
search.setIndex(search.selectableItems().index(this));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle navigation key events on the search input.
|
var $input = this.$('input');
|
||||||
this.$('input').on('keydown', function (e) {
|
|
||||||
switch (e.which) {
|
|
||||||
case 40:case 38:
|
|
||||||
// Down/Up
|
|
||||||
_this3.setIndex(_this3.getCurrentNumericIndex() + (e.which === 40 ? 1 : -1), true);
|
|
||||||
e.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 13:
|
this.navigator = new KeyboardNavigatable();
|
||||||
// Return
|
this.navigator.onUp(function () {
|
||||||
if (_this3.value()) {
|
return _this3.setIndex(_this3.getCurrentNumericIndex() - 1, true);
|
||||||
m.route(_this3.getItem(_this3.index).find('a').attr('href'));
|
}).onDown(function () {
|
||||||
} else {
|
return _this3.setIndex(_this3.getCurrentNumericIndex() + 1, true);
|
||||||
_this3.clear();
|
}).onSelect(this.selectResult.bind(this)).onCancel(this.clear.bind(this)).bindTo($input);
|
||||||
}
|
|
||||||
_this3.$('input').blur();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 27:
|
// Handle input key events on the search input, triggering results to load.
|
||||||
// Escape
|
$input.on('input focus', function () {
|
||||||
_this3.clear();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// no default
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handle input key events on the search input, triggering results to
|
|
||||||
// load.
|
|
||||||
.on('input focus', function () {
|
|
||||||
var query = this.value.toLowerCase();
|
var query = this.value.toLowerCase();
|
||||||
|
|
||||||
if (!query) return;
|
if (!query) return;
|
||||||
@@ -26353,6 +26334,17 @@ System.register('flarum/components/Search', ['flarum/Component', 'flarum/compone
|
|||||||
value: function getCurrentSearch() {
|
value: function getCurrentSearch() {
|
||||||
return app.current && typeof app.current.searching === 'function' && app.current.searching();
|
return app.current && typeof app.current.searching === 'function' && app.current.searching();
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'selectResult',
|
||||||
|
value: function selectResult() {
|
||||||
|
if (this.value()) {
|
||||||
|
m.route(this.getItem(this.index).find('a').attr('href'));
|
||||||
|
} else {
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$('input').blur();
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'clear',
|
key: 'clear',
|
||||||
value: function clear() {
|
value: function clear() {
|
||||||
@@ -31632,4 +31624,125 @@ System.register('flarum/utils/UserControls', ['flarum/components/Button', 'flaru
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
});;
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
System.register('flarum/utils/KeyboardNavigatable', [], function (_export, _context) {
|
||||||
|
var KeyboardNavigatable;
|
||||||
|
return {
|
||||||
|
setters: [],
|
||||||
|
execute: function () {
|
||||||
|
KeyboardNavigatable = function () {
|
||||||
|
function KeyboardNavigatable() {
|
||||||
|
babelHelpers.classCallCheck(this, KeyboardNavigatable);
|
||||||
|
|
||||||
|
var defaultCallback = function defaultCallback() {/* noop */};
|
||||||
|
|
||||||
|
// Set all callbacks to a noop function so that not all of them have to be set.
|
||||||
|
this.upCallback = defaultCallback;
|
||||||
|
this.downCallback = defaultCallback;
|
||||||
|
this.selectCallback = defaultCallback;
|
||||||
|
this.cancelCallback = defaultCallback;
|
||||||
|
|
||||||
|
// By default, always handle keyboard navigation.
|
||||||
|
this.whenCallback = function () {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a callback to be executed when navigating upwards.
|
||||||
|
*
|
||||||
|
* This will be triggered by the Up key.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {Function} callback
|
||||||
|
* @return {KeyboardNavigatable}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
babelHelpers.createClass(KeyboardNavigatable, [{
|
||||||
|
key: 'onUp',
|
||||||
|
value: function onUp(callback) {
|
||||||
|
this.upCallback = callback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'onDown',
|
||||||
|
value: function onDown(callback) {
|
||||||
|
this.downCallback = callback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'onSelect',
|
||||||
|
value: function onSelect(callback) {
|
||||||
|
this.selectCallback = callback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'onCancel',
|
||||||
|
value: function onCancel(callback) {
|
||||||
|
this.cancelCallback = callback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'when',
|
||||||
|
value: function when(callback) {
|
||||||
|
this.whenCallback = callback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'bindTo',
|
||||||
|
value: function bindTo($element) {
|
||||||
|
// Handle navigation key events on the navigatable element.
|
||||||
|
$element.on('keydown', this.navigate.bind(this));
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'navigate',
|
||||||
|
value: function navigate(event) {
|
||||||
|
// This callback determines whether keyboard should be handled or ignored.
|
||||||
|
if (!this.whenCallback()) return;
|
||||||
|
|
||||||
|
switch (event.which) {
|
||||||
|
case 9:case 13:
|
||||||
|
// Tab / Return
|
||||||
|
this.selectCallback();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 27:
|
||||||
|
// Escape
|
||||||
|
this.cancelCallback();
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 38:
|
||||||
|
// Up
|
||||||
|
this.upCallback();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 40:
|
||||||
|
// Down
|
||||||
|
this.downCallback();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// no default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
return KeyboardNavigatable;
|
||||||
|
}();
|
||||||
|
|
||||||
|
_export('default', KeyboardNavigatable);
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
@@ -3,6 +3,7 @@ import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
|||||||
import ItemList from 'flarum/utils/ItemList';
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
import classList from 'flarum/utils/classList';
|
import classList from 'flarum/utils/classList';
|
||||||
import extractText from 'flarum/utils/extractText';
|
import extractText from 'flarum/utils/extractText';
|
||||||
|
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';
|
||||||
import icon from 'flarum/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
import DiscussionsSearchSource from 'flarum/components/DiscussionsSearchSource';
|
import DiscussionsSearchSource from 'flarum/components/DiscussionsSearchSource';
|
||||||
import UsersSearchSource from 'flarum/components/UsersSearchSource';
|
import UsersSearchSource from 'flarum/components/UsersSearchSource';
|
||||||
@@ -121,35 +122,18 @@ export default class Search extends Component {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle navigation key events on the search input.
|
const $input = this.$('input');
|
||||||
this.$('input')
|
|
||||||
.on('keydown', e => {
|
|
||||||
switch (e.which) {
|
|
||||||
case 40: case 38: // Down/Up
|
|
||||||
this.setIndex(this.getCurrentNumericIndex() + (e.which === 40 ? 1 : -1), true);
|
|
||||||
e.preventDefault();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 13: // Return
|
this.navigator = new KeyboardNavigatable();
|
||||||
if (this.value()) {
|
this.navigator
|
||||||
m.route(this.getItem(this.index).find('a').attr('href'));
|
.onUp(() => this.setIndex(this.getCurrentNumericIndex() - 1, true))
|
||||||
} else {
|
.onDown(() => this.setIndex(this.getCurrentNumericIndex() + 1, true))
|
||||||
this.clear();
|
.onSelect(this.selectResult.bind(this))
|
||||||
}
|
.onCancel(this.clear.bind(this))
|
||||||
this.$('input').blur();
|
.bindTo($input);
|
||||||
break;
|
|
||||||
|
|
||||||
case 27: // Escape
|
// Handle input key events on the search input, triggering results to load.
|
||||||
this.clear();
|
$input
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// no default
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handle input key events on the search input, triggering results to
|
|
||||||
// load.
|
|
||||||
.on('input focus', function() {
|
.on('input focus', function() {
|
||||||
const query = this.value.toLowerCase();
|
const query = this.value.toLowerCase();
|
||||||
|
|
||||||
@@ -191,6 +175,19 @@ export default class Search extends Component {
|
|||||||
return app.current && typeof app.current.searching === 'function' && app.current.searching();
|
return app.current && typeof app.current.searching === 'function' && app.current.searching();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the currently selected search result and close the list.
|
||||||
|
*/
|
||||||
|
selectResult() {
|
||||||
|
if (this.value()) {
|
||||||
|
m.route(this.getItem(this.index).find('a').attr('href'));
|
||||||
|
} else {
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$('input').blur();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the search input and the current controller's active search.
|
* Clear the search input and the current controller's active search.
|
||||||
*/
|
*/
|
||||||
|
142
js/forum/src/utils/KeyboardNavigatable.js
Normal file
142
js/forum/src/utils/KeyboardNavigatable.js
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* The `KeyboardNavigatable` class manages lists that can be navigated with the
|
||||||
|
* keyboard, calling callbacks for each actions.
|
||||||
|
*
|
||||||
|
* This helper encapsulates the key binding logic, providing a simple fluent
|
||||||
|
* API for use.
|
||||||
|
*/
|
||||||
|
export default class KeyboardNavigatable {
|
||||||
|
constructor() {
|
||||||
|
const defaultCallback = () => { /* noop */ };
|
||||||
|
|
||||||
|
// Set all callbacks to a noop function so that not all of them have to be set.
|
||||||
|
this.upCallback = defaultCallback;
|
||||||
|
this.downCallback = defaultCallback;
|
||||||
|
this.selectCallback = defaultCallback;
|
||||||
|
this.cancelCallback = defaultCallback;
|
||||||
|
|
||||||
|
// By default, always handle keyboard navigation.
|
||||||
|
this.whenCallback = () => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a callback to be executed when navigating upwards.
|
||||||
|
*
|
||||||
|
* This will be triggered by the Up key.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {Function} callback
|
||||||
|
* @return {KeyboardNavigatable}
|
||||||
|
*/
|
||||||
|
onUp(callback) {
|
||||||
|
this.upCallback = callback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a callback to be executed when navigating downwards.
|
||||||
|
*
|
||||||
|
* This will be triggered by the Down key.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {Function} callback
|
||||||
|
* @return {KeyboardNavigatable}
|
||||||
|
*/
|
||||||
|
onDown(callback) {
|
||||||
|
this.downCallback = callback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a callback to be executed when the current item is selected..
|
||||||
|
*
|
||||||
|
* This will be triggered by the Return and Tab keys..
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {Function} callback
|
||||||
|
* @return {KeyboardNavigatable}
|
||||||
|
*/
|
||||||
|
onSelect(callback) {
|
||||||
|
this.selectCallback = callback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a callback to be executed when the navigation is canceled.
|
||||||
|
*
|
||||||
|
* This will be triggered by the Escape key.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {Function} callback
|
||||||
|
* @return {KeyboardNavigatable}
|
||||||
|
*/
|
||||||
|
onCancel(callback) {
|
||||||
|
this.cancelCallback = callback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a callback that determines whether keyboard input should be handled.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {Function} callback
|
||||||
|
* @return {KeyboardNavigatable}
|
||||||
|
*/
|
||||||
|
when(callback) {
|
||||||
|
this.whenCallback = callback;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the navigation key bindings on the given jQuery element.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {jQuery} $element
|
||||||
|
*/
|
||||||
|
bindTo($element) {
|
||||||
|
// Handle navigation key events on the navigatable element.
|
||||||
|
$element.on('keydown', this.navigate.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpret the given keyboard event as navigation commands.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {KeyboardEvent} event
|
||||||
|
*/
|
||||||
|
navigate(event) {
|
||||||
|
// This callback determines whether keyboard should be handled or ignored.
|
||||||
|
if (!this.whenCallback()) return;
|
||||||
|
|
||||||
|
switch (event.which) {
|
||||||
|
case 9: case 13: // Tab / Return
|
||||||
|
this.selectCallback();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 27: // Escape
|
||||||
|
this.cancelCallback();
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 38: // Up
|
||||||
|
this.upCallback();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 40: // Down
|
||||||
|
this.downCallback();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// no default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user