@enact/spotlight
Advanced tools
Comparing version 1.0.0-beta.2 to 1.0.0-beta.3
@@ -5,2 +5,11 @@ # Change Log | ||
## [unreleased] | ||
### Fixed | ||
- `spotlight.Spotlight` behavior to follow container config rules when navigating between containers | ||
- `spotlight.Spotlight` behavior to not set focus on spottable components animating past the pointer when not in pointer-mode | ||
- `spotlight.Spotlight` 5-way behavior where selecting a spottable component may require multiple attempts before performing actions | ||
- `spotlight.Spotlight` to not unfocus elements on scroll | ||
## [1.0.0-beta.2] - 2017-01-30 | ||
@@ -7,0 +16,0 @@ |
@@ -65,2 +65,19 @@ --- | ||
Spottable controls can receive `onSpotlight[Direction]` properties to handle custom | ||
navigation actions. This is mainly a convenience function used for preventing natural | ||
5-way behavior and setting focus on specific spottable components that may not normally | ||
be in the next component to be spotted. | ||
```javascript | ||
handleSpotlightDown = (e) => { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
Spotlight.focus('[data-component-id="myButton"]'); | ||
} | ||
``` | ||
```jsx harmony | ||
<Button data-component-id='myButton'>Source Button</Button> | ||
<Button onSpotlightDown={this.handleSpotlightDown}>Target Button</Button> | ||
``` | ||
<a name="4"></a> | ||
@@ -267,2 +284,15 @@ ## 4. Selectors | ||
`onSpotlightLeft` | ||
`onSpotlightRight` | ||
`onSpotlightUp` | ||
`onSpotlightDown` | ||
+ Type: [function] | ||
A callback function to override default spotlight behavior when exiting the spottable control. | ||
`onSpotlightDisappear` | ||
+ Type: [function] | ||
A callback function to be called when the component is removed while retaining focus. | ||
### Container ### | ||
@@ -269,0 +299,0 @@ |
@@ -6,3 +6,3 @@ 'use strict'; | ||
}); | ||
exports.getDirection = exports.spotlightDefaultClass = exports.Spottable = exports.SpotlightContainerDecorator = exports.SpotlightRootDecorator = exports.Spotlight = undefined; | ||
exports.getDirection = exports.spotlightDefaultClass = exports.spottableClass = exports.Spottable = exports.SpotlightContainerDecorator = exports.SpotlightRootDecorator = exports.Spotlight = undefined; | ||
@@ -22,3 +22,4 @@ var _spotlight = require('./src/spotlight'); | ||
exports.Spottable = _spottable.Spottable; | ||
exports.spottableClass = _spottable.spottableClass; | ||
exports.spotlightDefaultClass = _container.spotlightDefaultClass; | ||
exports.getDirection = _spotlight.getDirection; |
{ | ||
"name": "@enact/spotlight", | ||
"version": "1.0.0-beta.2", | ||
"version": "1.0.0-beta.3", | ||
"description": "A focus management library", | ||
@@ -22,5 +22,5 @@ "main": "index.js", | ||
"dependencies": { | ||
"@enact/core": "^1.0.0-beta.2", | ||
"@enact/core": "^1.0.0-beta.3", | ||
"react": "~15.4.0" | ||
} | ||
} |
@@ -28,4 +28,2 @@ 'use strict'; | ||
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
@@ -37,4 +35,5 @@ | ||
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } | ||
var spotlightDefaultClass = 'spottable-default'; | ||
var enterEvent = 'onMouseEnter'; | ||
@@ -116,4 +115,5 @@ var leaveEvent = 'onMouseLeave'; | ||
var forwardMouseLeave = (0, _handle.forward)(leaveEvent); | ||
var preserveId = config.preserveId; | ||
var preserveId = config.preserveId, | ||
containerConfig = _objectWithoutProperties(config, ['preserveId']); | ||
@@ -146,3 +146,5 @@ return _temp = _class = function (_React$Component) { | ||
_this.handleMouseLeave = function (ev) { | ||
_spotlight2.default.setActiveContainer(null); | ||
var parentContainer = ev.currentTarget.parentNode.closest('[data-container-id]'); | ||
var activeContainer = parentContainer ? parentContainer.dataset.containerId : null; | ||
_spotlight2.default.setActiveContainer(activeContainer); | ||
forwardMouseLeave(ev, _this.props); | ||
@@ -179,3 +181,3 @@ }; | ||
var selector = '[data-container-id="' + this.state.id + '"]:not([data-container-disabled="true"]) .' + _spottable.spottableClass, | ||
cfg = Object.assign({}, config, { selector: selector, navigableFilter: this.navigableFilter, restrict: this.props.spotlightRestrict }); | ||
cfg = Object.assign({}, containerConfig, { selector: selector, navigableFilter: this.navigableFilter, restrict: this.props.spotlightRestrict }); | ||
@@ -182,0 +184,0 @@ _spotlight2.default.set(this.state.id, cfg); |
@@ -31,2 +31,4 @@ 'use strict'; | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } | ||
var isDown = (0, _keymap.is)('down'); | ||
@@ -87,4 +89,3 @@ var isEnter = (0, _keymap.is)('enter'); | ||
var _pause = false; | ||
var _containers = {}; | ||
var _containerCount = 0; | ||
var _containers = new Map(); | ||
var _defaultContainerId = ''; | ||
@@ -452,3 +453,3 @@ var _lastContainerId = ''; | ||
id = _containerPrefix + String(++_ids); | ||
if (!_containers[id]) { | ||
if (!_containers.get(id)) { | ||
break; | ||
@@ -521,3 +522,5 @@ } | ||
function isNavigable(elem, containerId, verifyContainerSelector) { | ||
if (!elem || !containerId || !_containers[containerId] || _containers[containerId].selectorDisabled) { | ||
var config = _containers.get(containerId); | ||
if (!elem || !containerId || !config || config.selectorDisabled) { | ||
return false; | ||
@@ -528,7 +531,7 @@ } | ||
} | ||
if (verifyContainerSelector && !matchSelector(elem, _containers[containerId].selector)) { | ||
if (verifyContainerSelector && !matchSelector(elem, config.selector)) { | ||
return false; | ||
} | ||
if (typeof _containers[containerId].navigableFilter === 'function') { | ||
if (_containers[containerId].navigableFilter(elem, containerId) === false) { | ||
if (typeof config.navigableFilter === 'function') { | ||
if (config.navigableFilter(elem, containerId) === false) { | ||
return false; | ||
@@ -545,4 +548,7 @@ } | ||
function getContainerId(elem) { | ||
for (var id in _containers) { | ||
if (!_containers[id].selectorDisabled && isNavigable(elem, id, true)) { | ||
var containers = [].concat(_toConsumableArray(_containers.keys())); | ||
for (var i = containers.length; i-- > 0;) { | ||
var id = containers[i]; | ||
if (!_containers.get(id).selectorDisabled && isNavigable(elem, id, true)) { | ||
return id; | ||
@@ -554,3 +560,3 @@ } | ||
function getContainerNavigableElements(containerId) { | ||
return parseSelector(_containers[containerId].selector).filter(function (elem) { | ||
return parseSelector(_containers.get(containerId).selector).filter(function (elem) { | ||
return isNavigable(elem, containerId); | ||
@@ -561,3 +567,3 @@ }); | ||
function getContainerDefaultElement(containerId) { | ||
var defaultElement = _containers[containerId].defaultElement; | ||
var defaultElement = _containers.get(containerId).defaultElement; | ||
if (!defaultElement) { | ||
@@ -576,3 +582,3 @@ return null; | ||
function getContainerLastFocusedElement(containerId) { | ||
var lastFocusedElement = _containers[containerId].lastFocusedElement; | ||
var lastFocusedElement = _containers.get(containerId).lastFocusedElement; | ||
if (!isNavigable(lastFocusedElement, containerId, true)) { | ||
@@ -590,3 +596,3 @@ return null; | ||
if (_pointerMode && !fromPointer) { | ||
_containers[containerId].lastFocusedElement = elem; | ||
_containers.get(containerId).lastFocusedElement = elem; | ||
return false; | ||
@@ -635,3 +641,3 @@ } | ||
if (containerId) { | ||
_containers[containerId].lastFocusedElement = elem; | ||
_containers.get(containerId).lastFocusedElement = elem; | ||
_lastContainerId = containerId; | ||
@@ -664,3 +670,4 @@ } | ||
var addRange = function addRange(id) { | ||
if (id && range.indexOf(id) < 0 && _containers[id] && !_containers[id].selectorDisabled) { | ||
var config = _containers.get(id); | ||
if (id && range.indexOf(id) < 0 && config && !config.selectorDisabled) { | ||
range.push(id); | ||
@@ -675,3 +682,3 @@ } | ||
addRange(_lastContainerId); | ||
Object.keys(_containers).map(addRange); | ||
[].concat(_toConsumableArray(_containers.keys())).map(addRange); | ||
} | ||
@@ -683,3 +690,3 @@ | ||
if (_containers[id].enterTo === 'last-focused') { | ||
if (_containers.get(id).enterTo === 'last-focused') { | ||
next = getContainerLastFocusedElement(id) || getContainerDefaultElement(id) || getContainerNavigableElements(id)[0]; | ||
@@ -699,5 +706,7 @@ } else { | ||
function gotoLeaveFor(containerId, direction) { | ||
if (_containers[containerId].leaveFor && typeof _containers[containerId].leaveFor[direction] !== 'undefined') { | ||
var next = _containers[containerId].leaveFor[direction]; | ||
var config = _containers.get(containerId); | ||
if (config.leaveFor && typeof config.leaveFor[direction] !== 'undefined') { | ||
var next = config.leaveFor[direction]; | ||
if (typeof next === 'string') { | ||
@@ -721,6 +730,29 @@ if (next === '') { | ||
var allNavigableElements = []; | ||
for (var id in _containers) { | ||
containerNavigableElements[id] = getContainerNavigableElements(id); | ||
allNavigableElements = allNavigableElements.concat(containerNavigableElements[id]); | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
try { | ||
for (var _iterator = _containers.keys()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var id = _step.value; | ||
containerNavigableElements[id] = getContainerNavigableElements(id); | ||
allNavigableElements = allNavigableElements.concat(containerNavigableElements[id]); | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
} | ||
} | ||
return { allNavigableElements: allNavigableElements, containerNavigableElements: containerNavigableElements }; | ||
@@ -744,3 +776,3 @@ } | ||
var enterToElement = void 0; | ||
switch (_containers[nextContainerId].enterTo) { | ||
switch (_containers.get(nextContainerId).enterTo) { | ||
case 'last-focused': | ||
@@ -762,3 +794,3 @@ enterToElement = getContainerLastFocusedElement(nextContainerId) || getContainerDefaultElement(nextContainerId); | ||
function spotNextFromPoint(direction, position, containerId) { | ||
var config = extend({}, GlobalConfig, _containers[containerId]); | ||
var config = extend({}, GlobalConfig, _containers.get(containerId)); | ||
@@ -779,3 +811,3 @@ var _getNavigableElements = getNavigableElements(), | ||
if (next) { | ||
_containers[containerId].previous = { | ||
_containers.get(containerId).previous = { | ||
target: getContainerLastFocusedElement(_lastContainerId), | ||
@@ -806,3 +838,3 @@ destination: next, | ||
var config = extend({}, GlobalConfig, _containers[currentContainerId]); | ||
var config = extend({}, GlobalConfig, _containers.get(currentContainerId)); | ||
var next = void 0; | ||
@@ -823,3 +855,3 @@ | ||
if (next) { | ||
_containers[currentContainerId].previous = { | ||
_containers.get(currentContainerId).previous = { | ||
target: currentFocusedElement, | ||
@@ -868,3 +900,3 @@ destination: next, | ||
function shouldPreventNavigation() { | ||
return !_containerCount || _pause; | ||
return !_containers.size || _pause; | ||
} | ||
@@ -875,3 +907,3 @@ | ||
if (!shouldPreventNavigation() && getDirection(keyCode)) { | ||
if (getDirection(keyCode) || isEnter(keyCode)) { | ||
SpotlightAccelerator.reset(); | ||
@@ -926,3 +958,5 @@ _5WayKeyHold = false; | ||
function onMouseOver(evt) { | ||
if (shouldPreventNavigation()) { | ||
// a motionless pointer over animated spottable dom (such as list scrolling via 5-way) still emits | ||
// an `onMouseOver` event even when `_pointerMode` is `false`, in which case we terminate early. | ||
if (!_pointerMode || shouldPreventNavigation()) { | ||
return; | ||
@@ -941,2 +975,7 @@ } | ||
function onMouseMove(evt) { | ||
// Chrome emits mousemove on scroll, but client coordinates do not change. | ||
if (!_pointerMode && evt.clientX === _pointerX && evt.clientY === _pointerY) { | ||
return; | ||
} | ||
_pointerMode = true; | ||
@@ -970,6 +1009,27 @@ | ||
function isFocusable(elem) { | ||
for (var id in _containers) { | ||
// check *all* the containers to see if the specified element is a focusable element | ||
if (isNavigable(elem, id, true)) return true; | ||
var _iteratorNormalCompletion2 = true; | ||
var _didIteratorError2 = false; | ||
var _iteratorError2 = undefined; | ||
try { | ||
for (var _iterator2 = _containers.keys()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { | ||
var id = _step2.value; | ||
// check *all* the containers to see if the specified element is a focusable element | ||
if (isNavigable(elem, id, true)) return true; | ||
} | ||
} catch (err) { | ||
_didIteratorError2 = true; | ||
_iteratorError2 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion2 && _iterator2.return) { | ||
_iterator2.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError2) { | ||
throw _iteratorError2; | ||
} | ||
} | ||
} | ||
return false; | ||
@@ -1004,4 +1064,3 @@ } | ||
clear: function clear() { | ||
_containers = {}; | ||
_containerCount = 0; | ||
_containers.clear(); | ||
_defaultContainerId = ''; | ||
@@ -1023,3 +1082,3 @@ _lastContainerId = ''; | ||
config = arguments[1]; | ||
if (!_containers[containerId]) { | ||
if (!_containers.get(containerId)) { | ||
throw new Error('Container "' + containerId + '" doesn\'t exist!'); | ||
@@ -1032,8 +1091,8 @@ } | ||
for (var key in config) { | ||
if (typeof GlobalConfig[key] !== 'undefined') { | ||
if (containerId) { | ||
_containers[containerId][key] = config[key]; | ||
} else if (typeof config[key] !== 'undefined') { | ||
GlobalConfig[key] = config[key]; | ||
} | ||
var validKey = typeof GlobalConfig[key] !== 'undefined'; | ||
if (!containerId && validKey && typeof config[key] !== 'undefined') { | ||
GlobalConfig[key] = config[key]; | ||
} else if (containerId && !validKey) { | ||
delete config[key]; | ||
} | ||
@@ -1044,3 +1103,3 @@ } | ||
// remove "undefined" items | ||
_containers[containerId] = extend({}, _containers[containerId]); | ||
_containers.set(containerId, extend({}, config)); | ||
} | ||
@@ -1066,8 +1125,7 @@ }, | ||
if (_containers[containerId]) { | ||
if (_containers.get(containerId)) { | ||
throw new Error('Container "' + containerId + '" has already existed!'); | ||
} | ||
_containers[containerId] = {}; | ||
_containerCount++; | ||
_containers.set(containerId, config); | ||
@@ -1083,6 +1141,4 @@ Spotlight.set(containerId, config); | ||
} | ||
if (_containers[containerId]) { | ||
_containers[containerId] = void 0; | ||
_containers = extend({}, _containers); | ||
_containerCount--; | ||
if (_containers.get(containerId)) { | ||
_containers.delete(containerId); | ||
if (_lastContainerId === containerId) { | ||
@@ -1097,4 +1153,7 @@ Spotlight.setActiveContainer(null); | ||
disableSelector: function disableSelector(containerId) { | ||
if (_containers[containerId]) { | ||
_containers[containerId].selectorDisabled = true; | ||
var config = _containers.get(containerId); | ||
if (config) { | ||
config.selectorDisabled = true; | ||
_containers.set(containerId, config); | ||
return true; | ||
@@ -1106,4 +1165,7 @@ } | ||
enableSelector: function enableSelector(containerId) { | ||
if (_containers[containerId]) { | ||
_containers[containerId].selectorDisabled = false; | ||
var config = _containers.get(containerId); | ||
if (config) { | ||
config.selectorDisabled = false; | ||
_containers.set(containerId, config); | ||
return true; | ||
@@ -1143,3 +1205,3 @@ } | ||
} else if (typeof elem === 'string') { | ||
if (_containers[elem]) { | ||
if (_containers.get(elem)) { | ||
result = focusContainer(elem); | ||
@@ -1190,3 +1252,3 @@ } else { | ||
_defaultContainerId = ''; | ||
} else if (!_containers[containerId]) { | ||
} else if (!_containers.get(containerId)) { | ||
throw new Error('Container "' + containerId + '" doesn\'t exist!'); | ||
@@ -1193,0 +1255,0 @@ } else { |
@@ -288,3 +288,3 @@ 'use strict'; | ||
/** | ||
* Whether or not the component can be navigated using spotlight. | ||
* When `true`, the component cannot be navigated using spotlight. | ||
* | ||
@@ -291,0 +291,0 @@ * @type {Boolean} |
77642
1703
Updated@enact/core@^1.0.0-beta.3