diff --git a/.travis.yml b/.travis.yml
index 5655159268..583de7b4d5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,7 +11,7 @@ install:
- npm config set prefer-offline false
- npm install -g enactjs/cli#develop
- npm install -g codecov
- - git clone --branch=develop --depth 1 https://github.com/enactjs/enact ../enact
+ - git clone --branch=feature/WRO-478 --depth 1 https://github.com/enactjs/enact ../enact
- pushd ../enact
- npm install
- npm run lerna exec -- --ignore enact-sampler --concurrency 1 -- npm --no-package-lock install
diff --git a/VirtualList/VirtualList.js b/VirtualList/VirtualList.js
index 64e37049b6..8aa484a5fe 100644
--- a/VirtualList/VirtualList.js
+++ b/VirtualList/VirtualList.js
@@ -197,6 +197,8 @@ VirtualList.propTypes = /** @lends sandstone/VirtualList.VirtualList.prototype *
*/
direction: PropTypes.oneOf(['horizontal', 'vertical']),
+ editable: PropTypes.object, // TBD // FIXME
+
/**
* Specifies how to show horizontal scrollbar.
*
@@ -492,6 +494,7 @@ VirtualList.defaultProps = {
'data-spotlight-container-disabled': false,
cbScrollTo: nop,
direction: 'vertical',
+ editable: null,
horizontalScrollbar: 'auto',
noAffordance: false,
noScrollByDrag: false,
@@ -673,6 +676,8 @@ VirtualGridList.propTypes = /** @lends sandstone/VirtualList.VirtualGridList.pro
*/
direction: PropTypes.oneOf(['horizontal', 'vertical']),
+ editable: PropTypes.object, // TBD // FIXME
+
/**
* Specifies how to show horizontal scrollbar.
*
@@ -971,6 +976,7 @@ VirtualGridList.defaultProps = {
'data-spotlight-container-disabled': false,
cbScrollTo: nop,
direction: 'vertical',
+ editable: null,
horizontalScrollbar: 'auto',
noAffordance: false,
noScrollByDrag: false,
diff --git a/VirtualList/useEvent.js b/VirtualList/useEvent.js
index 30c9882e3d..2523802efc 100644
--- a/VirtualList/useEvent.js
+++ b/VirtualList/useEvent.js
@@ -1,4 +1,5 @@
import {is} from '@enact/core/keymap';
+import {Job} from '@enact/core/util';
import Spotlight, {getDirection} from '@enact/spotlight';
import {getTargetByDirectionFromElement} from '@enact/spotlight/src/target';
import utilDOM from '@enact/ui/useScroll/utilDOM';
@@ -20,6 +21,11 @@ const
// should return -1 if index is not a number or a negative value
return number >= 0 ? number : -1;
};
+const focusFirstChild = (node) => {
+ if (node.children[0]) {
+ node.children[0].focus();
+ }
+};
let prevKeyDownIndex = -1;
@@ -27,7 +33,9 @@ const useEventKey = (props, instances, context) => {
// Mutable value
const mutableRef = useRef({
- fn: null
+ fn: null,
+ editableJob: null,
+ editingIndex: null
});
// Functions
@@ -106,11 +114,22 @@ const useEventKey = (props, instances, context) => {
return {isDownKey, isUpKey, isLeftMovement, isRightMovement, isWrapped, nextIndex};
}, [findSpottableItem, props, instances]);
+ const editingStartByKey = useCallback(() => {
+ const {scrollContentHandle: {current: {featureEditable}}} = instances;
+ featureEditable.editingStart(mutableRef.current.editingIndex, 'index', focusFirstChild);
+ }, [instances]);
+
// Hooks
+ useEffect(() => {
+ const {scrollContentHandle: {current: {featureEditable}}} = instances;
+ mutableRef.current.editableJob = new Job(editingStartByKey, featureEditable.enablingTime);
+ }, [instances, editingStartByKey]);
+
useEffect(() => {
const {scrollContainerRef, scrollContentHandle} = instances;
const {
+ focusByIndex,
handle5WayKeyUp,
handleDirectionKeyDown,
handlePageUpDownKeyDown,
@@ -120,11 +139,26 @@ const useEventKey = (props, instances, context) => {
function handleKeyDown (ev) {
const {keyCode, target} = ev;
const direction = getDirection(keyCode);
+ const {featureEditable} = scrollContentHandle.current;
if (direction) {
Spotlight.setPointerMode(false);
+ if (featureEditable.enabled && featureEditable.editingMode) {
+ const {editingIndex, editingNode, lastVisualIndex, moveItem, movingItemUpdate} = featureEditable;
+
+ if (lastVisualIndex !== null) {
+ const nextIndex = getNextIndex({index: lastVisualIndex, keyCode, repeat: ev.repeat}).nextIndex;
+ if (nextIndex !== -1) {
+ moveItem(editingIndex, nextIndex, {scrollIntoView: true});
+ if (editingNode) {
+ movingItemUpdate();
+ }
+ }
- if (spotlightAcceleratorProcessKey(ev)) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+ } else if (spotlightAcceleratorProcessKey(ev)) {
ev.stopPropagation();
} else {
const {spotlightId} = props;
@@ -209,10 +243,33 @@ const useEventKey = (props, instances, context) => {
}
} else if (isPageUp(keyCode) || isPageDown(keyCode)) {
handlePageUpDownKeyDown();
+ } else if (isEnter(keyCode)) {
+ if (!ev.repeat && featureEditable.enabled) {
+ if (!featureEditable.editingMode) {
+ const targetIndex = target.dataset.index;
+ const index = targetIndex ? getNumberValue(targetIndex) : -1;
+ if (index !== -1) {
+ mutableRef.current.editingIndex = index;
+ mutableRef.current.editableJob.start();
+ }
+ } else {
+ const lastVisualIndex = featureEditable.lastVisualIndex;
+ featureEditable.editingFinish();
+ focusByIndex(lastVisualIndex);
+ }
+ }
}
}
function handleKeyUp ({keyCode}) {
+ const {scrollContentHandle: {current: {featureEditable}}} = instances;
+ if (featureEditable.enabled) {
+ mutableRef.current.editableJob.stop();
+ if (featureEditable.editingMode) {
+ return;
+ }
+ }
+
if (getDirection(keyCode) || isEnter(keyCode)) {
handle5WayKeyUp();
}
diff --git a/VirtualList/useThemeVirtualList.js b/VirtualList/useThemeVirtualList.js
index cbbb475da9..72382ddd9a 100644
--- a/VirtualList/useThemeVirtualList.js
+++ b/VirtualList/useThemeVirtualList.js
@@ -54,6 +54,7 @@ const useSpottable = (props, instances) => {
useSpotlightConfig(props, {spottable: mutableRef});
const {addGlobalKeyDownEventListener, removeGlobalKeyDownEventListener} = useEventKey(props, instances, {
+ focusByIndex,
handlePageUpDownKeyDown: () => {
mutableRef.current.isScrolledBy5way = false;
},
diff --git a/package.json b/package.json
index fccde1ff3d..7529473cfc 100644
--- a/package.json
+++ b/package.json
@@ -60,4 +60,4 @@
"eslint-config-enact-proxy": "^1.0.5",
"ilib": "^14.14.0"
}
-}
\ No newline at end of file
+}
diff --git a/samples/qa-virtualgridlist/src/components/ImageList/ImageList.js b/samples/qa-virtualgridlist/src/components/ImageList/ImageList.js
index 6fb9f45a0e..81e6815427 100644
--- a/samples/qa-virtualgridlist/src/components/ImageList/ImageList.js
+++ b/samples/qa-virtualgridlist/src/components/ImageList/ImageList.js
@@ -8,10 +8,20 @@ import ImageItem from '../ImageItem';
import css from './ImageList.module.less';
-const ImageList = ({imageitems, minHeight, minWidth, spacing, ...rest}) => {
+import {
+ updateItemsOrder as updateItemsOrderAction
+} from '../../store';
+
+const ImageList = ({imageitems, minHeight, minWidth, updateItemsOrder, spacing, ...rest}) => {
const calculateOfSize = (size) => ri.scale(parseInt(size) || 0);
- const renderItem = useCallback(({...renderRest}) => (), []);
+ const renderItem = useCallback(({editMode, ...renderRest}) => (
+
+ ), []);
+
delete rest.dispatch;
@@ -20,8 +30,15 @@ const ImageList = ({imageitems, minHeight, minWidth, spacing, ...rest}) => {
{...rest}
className={rest.direction === 'horizontal' ? css.horizontalPadding : css.verticalPadding}
dataSize={imageitems.length}
+ hoverToScroll
itemRenderer={renderItem}
itemSize={{minHeight: calculateOfSize(minHeight), minWidth: calculateOfSize(minWidth)}}
+ editable={{
+ css: css,
+ onComplete: ({detail: {order}}) => {
+ updateItemsOrder(order);
+ }
+ }}
spacing={calculateOfSize(spacing)}
/>
);
@@ -33,7 +50,8 @@ ImageList.propTypes = {
imageitems: PropTypes.array,
minHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
minWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
- spacing: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
+ spacing: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ updateItemsOrder: PropTypes.func
};
const mapStateToProps = ({data}) => ({
@@ -43,4 +61,10 @@ const mapStateToProps = ({data}) => ({
spacing: data.spacing
});
-export default connect(mapStateToProps)(ImageList);
+const mapDispatchToProps = (dispatch) => {
+ return {
+ updateItemsOrder: (newDataOrder) => dispatch(updateItemsOrderAction(newDataOrder))
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(ImageList);
diff --git a/samples/qa-virtualgridlist/src/components/ImageList/ImageList.module.less b/samples/qa-virtualgridlist/src/components/ImageList/ImageList.module.less
index b6fb46e0e8..f0f3b55215 100644
--- a/samples/qa-virtualgridlist/src/components/ImageList/ImageList.module.less
+++ b/samples/qa-virtualgridlist/src/components/ImageList/ImageList.module.less
@@ -9,3 +9,35 @@
.horizontalPadding {
padding-bottom: 36px;
}
+
+@keyframes selected {
+ 0% {
+ transform: scale(0.95) rotate(0);
+ }
+ 25% {
+ transform: scale(0.90) rotate(-2deg);
+ }
+ 50% {
+ transform: scale(0.95) rotate(0);
+ }
+ 75% {
+ transform: scale(0.90) rotate(2deg);
+ }
+ 100% {
+ transform: scale(0.95) rotate(0);
+ }
+}
+
+.selected {
+ &::before {
+ content: '';
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ border: 12px solid white;
+ border-radius: 48px;
+ animation-name: selected;
+ animation-duration: 0.8s;
+ animation-iteration-count: infinite;
+ }
+}
diff --git a/samples/qa-virtualgridlist/src/store/index.js b/samples/qa-virtualgridlist/src/store/index.js
index 4c23625771..8a53ef3052 100644
--- a/samples/qa-virtualgridlist/src/store/index.js
+++ b/samples/qa-virtualgridlist/src/store/index.js
@@ -120,11 +120,24 @@ const recordSlice = createSlice({
prepare: (index, item) => {
return {payload: {index, item}};
}
+ },
+ updateItemsOrder: (state, action) => {
+ const newData = {};
+ const newDataOrder = [];
+
+ if (action.payload) {
+ for (let i = 0; i < action.payload.length; i++) {
+ newData[i] = state.data[action.payload[i]];
+ newDataOrder.push(i);
+ }
+ }
+
+ return Object.assign(state, {data: newData, dataOrder: newDataOrder});
}
}
});
-export const {addItem, changeDataSize, changeMinHeight, changeMinWidth, changeSpacing, deleteItem, deleteSelectedItem, selectAll, selectItem, selectionEnable, setData} = recordSlice.actions;
+export const {addItem, changeDataSize, changeMinHeight, changeMinWidth, changeSpacing, deleteItem, deleteSelectedItem, selectAll, selectItem, selectionEnable, setData, updateItemsOrder} = recordSlice.actions;
const rootReducer = combineReducers({
data : recordSlice.reducer
diff --git a/useScroll/useScroll.js b/useScroll/useScroll.js
index 6074aeb4f5..c6911ce8f0 100644
--- a/useScroll/useScroll.js
+++ b/useScroll/useScroll.js
@@ -310,7 +310,6 @@ const useScroll = (props) => {
'data-webos-voice-disabled': voiceDisabled,
'data-webos-voice-focused': voiceFocused,
'data-webos-voice-group-label': voiceGroupLabel,
- editable,
focusableScrollbar,
fadeOut,
horizontalScrollThumbAriaLabel,
@@ -475,7 +474,7 @@ const useScroll = (props) => {
};
assignProperties('scrollContentProps', {
- ...(props.itemRenderer ? {itemRefs, noAffordance, snapToCenter} : {editable, fadeOut}),
+ ...(props.itemRenderer ? {itemRefs, noAffordance, snapToCenter} : {fadeOut}),
...voiceProps,
className: [
(props.direction === 'both' || props.direction === 'vertical') ? overscrollCss.vertical : overscrollCss.horizontal,