From a77a355bac376758a6c9f9948e258bd32ccd4302 Mon Sep 17 00:00:00 2001 From: Andi Pabst Date: Thu, 21 Oct 2021 13:53:49 +0200 Subject: [PATCH] feat(relation-widget): enable reordering for relation controls (#5873) --- .../netlify-cms-widget-relation/package.json | 3 +- .../src/RelationControl.js | 57 +++++++++++++++++-- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/packages/netlify-cms-widget-relation/package.json b/packages/netlify-cms-widget-relation/package.json index 1f6edd62..5437d08d 100644 --- a/packages/netlify-cms-widget-relation/package.json +++ b/packages/netlify-cms-widget-relation/package.json @@ -23,7 +23,8 @@ }, "dependencies": { "react-select": "^4.0.0", - "react-window": "^1.8.5" + "react-window": "^1.8.5", + "react-sortable-hoc": "^2.0.0" }, "peerDependencies": { "@emotion/core": "^10.0.35", diff --git a/packages/netlify-cms-widget-relation/src/RelationControl.js b/packages/netlify-cms-widget-relation/src/RelationControl.js index 29bd5f40..c7234214 100644 --- a/packages/netlify-cms-widget-relation/src/RelationControl.js +++ b/packages/netlify-cms-widget-relation/src/RelationControl.js @@ -1,12 +1,35 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { components } from 'react-select'; import AsyncSelect from 'react-select/async'; -import { find, isEmpty, last, debounce, get, uniqBy } from 'lodash'; -import { List, Map, fromJS } from 'immutable'; +import { debounce, find, get, isEmpty, last, uniqBy } from 'lodash'; +import { fromJS, List, Map } from 'immutable'; import { reactSelectStyles } from 'netlify-cms-ui-default'; import { stringTemplate, validations } from 'netlify-cms-lib-widgets'; import { FixedSizeList } from 'react-window'; +import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc'; + +function arrayMove(array, from, to) { + const slicedArray = array.slice(); + slicedArray.splice(to < 0 ? array.length + to : to, 0, slicedArray.splice(from, 1)[0]); + return slicedArray; +} + +const MultiValue = SortableElement(props => { + // prevent the menu from being opened/closed when the user clicks on a value to begin dragging it + function onMouseDown(e) { + e.preventDefault(); + e.stopPropagation(); + } + + const innerProps = { ...props.innerProps, onMouseDown }; + return ; +}); + +const MultiValueLabel = SortableHandle(props => ); + +const SortableSelect = SortableContainer(AsyncSelect); function Option({ index, style, data }) { return
{data.options[index]}
; @@ -164,6 +187,24 @@ export default class RelationControl extends React.Component { this.mounted = false; } + onSortEnd = + options => + ({ oldIndex, newIndex }) => { + const { onChange, field } = this.props; + const value = options.map(optionToString); + const newValue = arrayMove(value, oldIndex, newIndex); + const metadata = + (!isEmpty(options) && { + [field.get('name')]: { + [field.get('collection')]: { + [last(newValue)]: last(options).data, + }, + }, + }) || + {}; + onChange(fromJS(newValue), metadata); + }; + handleChange = selectedOption => { const { onChange, field } = this.props; @@ -262,8 +303,16 @@ export default class RelationControl extends React.Component { }); return ( - node.getBoundingClientRect()} + // react-select props: + components={{ MenuList, MultiValue, MultiValueLabel }} value={selectedValue} inputId={forID} cacheOptions