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,