Implemented top/bottom positioning of ActionMenu

This commit is contained in:
jos 2017-12-30 15:47:07 +01:00
parent ccf89162b7
commit d73b79cb4e
5 changed files with 160 additions and 106 deletions

View File

@ -6,22 +6,46 @@ import FloatingMenu from './menu/FloatingMenu'
import { escapeHTML, unescapeHTML } from '../utils/stringUtils' import { escapeHTML, unescapeHTML } from '../utils/stringUtils'
import { getInnerText, insideRect } from '../utils/domUtils' import { getInnerText, insideRect } from '../utils/domUtils'
import { stringConvert, valueType, isUrl } from '../utils/typeUtils' import { stringConvert, valueType, isUrl } from '../utils/typeUtils'
import { compileJSONPointer, META, SELECTED, SELECTED_END, SELECTED_AFTER, SELECTED_BEFORE } from '../eson' import {
compileJSONPointer,
META,
SELECTED, SELECTED_START, SELECTED_END, SELECTED_AFTER, SELECTED_BEFORE, SELECTED_FIRST, SELECTED_LAST
} from '../eson'
// TODO: rename SELECTED, SELECTED_END, etc to AREA_*? It's used for both selection and hovering const MENU_ITEMS_OBJECT = [
const SELECTED_CLASS_NAMES = { {type: 'sort'},
[SELECTED]: ' jsoneditor-selected', {type: 'duplicate'},
[SELECTED_END]: ' jsoneditor-selected jsoneditor-selected-end', {type: 'cut'},
[SELECTED_AFTER]: ' jsoneditor-selected jsoneditor-selected-insert-area', {type: 'copy'},
[SELECTED_BEFORE]: ' jsoneditor-selected jsoneditor-selected-insert-area', {type: 'paste'},
} {type: 'remove'}
]
const HOVERED_CLASS_NAMES = { const MENU_ITEMS_ARRAY = [
[SELECTED]: ' jsoneditor-hover', {type: 'sort'},
[SELECTED_END]: ' jsoneditor-hover jsoneditor-hover-end', {type: 'duplicate'},
[SELECTED_AFTER]: ' jsoneditor-hover jsoneditor-hover-insert-area', {type: 'cut'},
[SELECTED_BEFORE]: ' jsoneditor-hover jsoneditor-hover-insert-area', {type: 'copy'},
} {type: 'paste'},
{type: 'remove'}
]
const MENU_ITEMS_VALUE = [
// {text: 'String', onClick: this.props.emit('changeType', {type: 'checkbox', checked: false}}),
{type: 'duplicate'},
{type: 'cut'},
{type: 'copy'},
{type: 'paste'},
{type: 'remove'}
]
const MENU_ITEMS_INSERT_BEFORE = [
{type: 'insertStructure'},
{type: 'insertValue'},
{type: 'insertObject'},
{type: 'insertArray'},
{type: 'paste'},
]
export default class JSONNode extends PureComponent { export default class JSONNode extends PureComponent {
static URL_TITLE = 'Ctrl+Click or Ctrl+Enter to open url' static URL_TITLE = 'Ctrl+Click or Ctrl+Enter to open url'
@ -46,7 +70,7 @@ export default class JSONNode extends PureComponent {
state = { state = {
menu: null, // can contain object {anchor, root} menu: null, // can contain object {anchor, root}
appendMenu: null, // can contain object {anchor, root} appendMenu: null, // can contain object {anchor, root}
hover: false hover: null
} }
componentWillUnmount () { componentWillUnmount () {
@ -104,17 +128,7 @@ export default class JSONNode extends PureComponent {
} }
} }
const floatingMenu = (meta.selected === SELECTED_END) const floatingMenu = this.renderFloatingMenu(MENU_ITEMS_OBJECT, meta.selected)
? this.renderFloatingMenu([
{type: 'sort'},
{type: 'duplicate'},
{type: 'cut'},
{type: 'copy'},
{type: 'paste'},
{type: 'remove'}
])
: null
const insertArea = this.renderInsertBeforeArea() const insertArea = this.renderInsertBeforeArea()
return h('div', { return h('div', {
@ -159,17 +173,7 @@ export default class JSONNode extends PureComponent {
} }
} }
const floatingMenu = (meta.selected === SELECTED_END) const floatingMenu = this.renderFloatingMenu(MENU_ITEMS_ARRAY, meta.selected)
? this.renderFloatingMenu([
{type: 'sort'},
{type: 'duplicate'},
{type: 'cut'},
{type: 'copy'},
{type: 'paste'},
{type: 'remove'}
])
: null
const insertArea = this.renderInsertBeforeArea() const insertArea = this.renderInsertBeforeArea()
return h('div', { return h('div', {
@ -194,16 +198,7 @@ export default class JSONNode extends PureComponent {
this.renderError(meta.error) this.renderError(meta.error)
]) ])
const floatingMenu = (meta.selected === SELECTED_END) const floatingMenu = this.renderFloatingMenu(MENU_ITEMS_VALUE, meta.selected)
? this.renderFloatingMenu([
// {text: 'String', onClick: this.props.emit('changeType', {type: 'checkbox', checked: false}}),
{type: 'duplicate'},
{type: 'cut'},
{type: 'copy'},
{type: 'paste'},
{type: 'remove'}
])
: null
const insertArea = this.renderInsertBeforeArea() const insertArea = this.renderInsertBeforeArea()
@ -216,14 +211,9 @@ export default class JSONNode extends PureComponent {
} }
renderInsertBeforeArea () { renderInsertBeforeArea () {
const floatingMenu = (this.props.value[META].selected === SELECTED_BEFORE) const floatingMenu = ((this.props.value[META].selected & SELECTED_BEFORE) !== 0)
? this.renderFloatingMenu([ ? this.renderFloatingMenu(MENU_ITEMS_INSERT_BEFORE,
{type: 'insertStructure'}, SELECTED + SELECTED_END + SELECTED_FIRST)
{type: 'insertValue'},
{type: 'insertObject'},
{type: 'insertArray'},
{type: 'paste'},
])
: null : null
return h('div', { return h('div', {
@ -356,9 +346,23 @@ export default class JSONNode extends PureComponent {
} }
getContainerClassName (selected, hover) { getContainerClassName (selected, hover) {
return 'jsoneditor-node-container' + let classNames = ['jsoneditor-node-container']
(hover ? (HOVERED_CLASS_NAMES[hover]) : '') +
(selected ? (SELECTED_CLASS_NAMES[selected]) : '') if ((selected & SELECTED) !== 0) { classNames.push('jsoneditor-selected') }
if ((selected & SELECTED_START) !== 0) { classNames.push('jsoneditor-selected-start') }
if ((selected & SELECTED_END) !== 0) { classNames.push('jsoneditor-selected-end') }
if ((selected & SELECTED_FIRST) !== 0) { classNames.push('jsoneditor-selected-first') }
if ((selected & SELECTED_LAST) !== 0) { classNames.push('jsoneditor-selected-last') }
if ((selected & SELECTED_BEFORE) !== 0) { classNames.push('jsoneditor-selected-insert-area-before') }
if ((selected & SELECTED_AFTER) !== 0) { classNames.push('jsoneditor-selected-insert-area-after') }
if ((hover & SELECTED) !== 0) { classNames.push('jsoneditor-hover') }
if ((hover & SELECTED_START) !== 0) { classNames.push('jsoneditor-hover-start') }
if ((hover & SELECTED_END) !== 0) { classNames.push('jsoneditor-hover-end') }
if ((hover & SELECTED_BEFORE) !== 0) { classNames.push('jsoneditor-hover-insert-area-before') }
if ((hover & SELECTED_AFTER) !== 0) { classNames.push('jsoneditor-hover-insert-area-after') }
return classNames.join(' ')
} }
/** /**
@ -473,12 +477,20 @@ export default class JSONNode extends PureComponent {
) )
} }
renderFloatingMenu (items) { renderFloatingMenu (items, selected) {
if ((selected & SELECTED_END) === 0) {
return null
}
const isLastOfMultiple = ((selected & SELECTED_LAST) !== 0) &&
((selected & SELECTED_FIRST) === 0)
return h(FloatingMenu, { return h(FloatingMenu, {
key: 'floating-menu', key: 'floating-menu',
path: this.props.value[META].path, path: this.props.value[META].path,
emit: this.props.emit, emit: this.props.emit,
items items,
position: isLastOfMultiple ? 'bottom' : 'top'
}) })
} }
@ -487,7 +499,7 @@ export default class JSONNode extends PureComponent {
event.stopPropagation() event.stopPropagation()
const hover = (event.target.className.indexOf('jsoneditor-insert-area') !== -1) const hover = (event.target.className.indexOf('jsoneditor-insert-area') !== -1)
? SELECTED_AFTER ? (SELECTED + SELECTED_AFTER)
: SELECTED : SELECTED
if (hoveredNode && hoveredNode !== this) { if (hoveredNode && hoveredNode !== this) {
@ -505,7 +517,7 @@ export default class JSONNode extends PureComponent {
handleMouseLeave = (event) => { handleMouseLeave = (event) => {
event.stopPropagation() event.stopPropagation()
// FIXME: this gives issues when the hovered node doesn't exist anymore. check whether mounted? // FIXME: this gives issues when the hovered node doesn't exist anymore. check whether mounted?
hoveredNode.setState({hover: false}) hoveredNode.setState({hover: null})
this.setState({hover: null}) this.setState({hover: null})
} }

View File

@ -529,35 +529,35 @@ div.jsoneditor-node-container {
background-color: #ffed99; } background-color: #ffed99; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover { div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover {
background-color: #ffdb80; } background-color: #ffdb80; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover.jsoneditor-hover-insert-area { div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover.jsoneditor-hover-insert-area-after {
background-color: #ffed99; } background-color: #ffed99; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover.jsoneditor-hover-insert-area > div.jsoneditor-insert-area { div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover.jsoneditor-hover-insert-area-after > div.jsoneditor-insert-area {
border: 1px dashed gray; border: 1px dashed gray;
background-color: #f2f2f2; } background-color: #f2f2f2; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover.jsoneditor-hover-insert-area.jsoneditor-selected-insert-area > div.jsoneditor-insert-area { div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover.jsoneditor-hover-insert-area-after.jsoneditor-selected-insert-area-before > div.jsoneditor-insert-area {
border: 1px dashed #f4af41; border: 1px dashed #f4af41;
background-color: #ffdb80; } background-color: #ffdb80; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area { div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area-before {
background-color: inherit; } background-color: inherit; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area.jsoneditor-hover { div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area-before.jsoneditor-hover {
background-color: #f2f2f2; } background-color: #f2f2f2; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area.jsoneditor-hover.jsoneditor-hover-insert-area { div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area-before.jsoneditor-hover.jsoneditor-hover-insert-area-after {
background-color: inherit; } background-color: inherit; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area > div.jsoneditor-insert-area { div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area-before > div.jsoneditor-insert-area {
border: 1px dashed #f4af41; border: 1px dashed #f4af41;
background: #ffed99; } background: #ffed99; }
div.jsoneditor-node-container.jsoneditor-selected div.jsoneditor-hover { div.jsoneditor-node-container.jsoneditor-selected div.jsoneditor-hover {
background-color: #ffdb80; } background-color: #ffdb80; }
div.jsoneditor-node-container.jsoneditor-selected div.jsoneditor-hover.jsoneditor-hover-insert-area { div.jsoneditor-node-container.jsoneditor-selected div.jsoneditor-hover.jsoneditor-hover-insert-area-after {
background-color: inherit; } background-color: inherit; }
div.jsoneditor-node-container.jsoneditor-selected div.jsoneditor-hover.jsoneditor-hover-insert-area > div.jsoneditor-insert-area { div.jsoneditor-node-container.jsoneditor-selected div.jsoneditor-hover.jsoneditor-hover-insert-area-after > div.jsoneditor-insert-area {
border: 1px dashed #f4af41; border: 1px dashed #f4af41;
background: #ffdb80; } background: #ffdb80; }
div.jsoneditor-node-container.jsoneditor-hover { div.jsoneditor-node-container.jsoneditor-hover {
background-color: #f2f2f2; } background-color: #f2f2f2; }
div.jsoneditor-node-container.jsoneditor-hover.jsoneditor-hover-insert-area { div.jsoneditor-node-container.jsoneditor-hover.jsoneditor-hover-insert-area-after {
background-color: inherit; } background-color: inherit; }
div.jsoneditor-node-container.jsoneditor-hover.jsoneditor-hover-insert-area > div.jsoneditor-insert-area { div.jsoneditor-node-container.jsoneditor-hover.jsoneditor-hover-insert-area-after > div.jsoneditor-insert-area {
border: 1px dashed gray; border: 1px dashed gray;
background-color: #f2f2f2; } background-color: #f2f2f2; }
div.jsoneditor-node-container div.jsoneditor-insert-area { div.jsoneditor-node-container div.jsoneditor-insert-area {
@ -587,8 +587,17 @@ div.jsoneditor-node-container {
width: 0; width: 0;
height: 0; height: 0;
border-top: solid 10px #4d4d4d; border-top: solid 10px #4d4d4d;
border-bottom: none;
border-left: solid 10px transparent; border-left: solid 10px transparent;
border-right: solid 10px transparent; } border-right: solid 10px transparent; }
div.jsoneditor-node-container div.jsoneditor-floating-menu.jsoneditor-floating-menu-bottom {
bottom: auto;
top: 100%; }
div.jsoneditor-node-container div.jsoneditor-floating-menu.jsoneditor-floating-menu-bottom:after {
top: -10px;
margin-left: -10px;
border-top: none;
border-bottom: solid 10px #4d4d4d; }
div.jsoneditor-node-container div.jsoneditor-floating-menu button.jsoneditor-floating-menu-item { div.jsoneditor-node-container div.jsoneditor-floating-menu button.jsoneditor-floating-menu-item {
color: #fff; color: #fff;
background: #4d4d4d; background: #4d4d4d;

View File

@ -575,7 +575,7 @@ div.jsoneditor-node-container {
&.jsoneditor-hover { &.jsoneditor-hover {
background-color: $hoverAndSelectedColor; background-color: $hoverAndSelectedColor;
&.jsoneditor-hover-insert-area { &.jsoneditor-hover-insert-area-after {
background-color: $selectedColor; background-color: $selectedColor;
> div.jsoneditor-insert-area { > div.jsoneditor-insert-area {
@ -583,7 +583,7 @@ div.jsoneditor-node-container {
background-color: $hoverColor; background-color: $hoverColor;
} }
&.jsoneditor-selected-insert-area { &.jsoneditor-selected-insert-area-before {
> div.jsoneditor-insert-area { > div.jsoneditor-insert-area {
border: 1px dashed #f4af41; border: 1px dashed #f4af41;
background-color: $hoverAndSelectedColor; background-color: $hoverAndSelectedColor;
@ -592,13 +592,13 @@ div.jsoneditor-node-container {
} }
} }
&.jsoneditor-selected-insert-area { &.jsoneditor-selected-insert-area-before {
background-color: inherit; background-color: inherit;
&.jsoneditor-hover { &.jsoneditor-hover {
background-color: $hoverColor; background-color: $hoverColor;
&.jsoneditor-hover-insert-area { &.jsoneditor-hover-insert-area-after {
background-color: inherit; background-color: inherit;
} }
} }
@ -613,7 +613,7 @@ div.jsoneditor-node-container {
div.jsoneditor-hover { div.jsoneditor-hover {
background-color: $hoverAndSelectedColor; background-color: $hoverAndSelectedColor;
&.jsoneditor-hover-insert-area { &.jsoneditor-hover-insert-area-after {
background-color: inherit; background-color: inherit;
> div.jsoneditor-insert-area { > div.jsoneditor-insert-area {
@ -627,7 +627,7 @@ div.jsoneditor-node-container {
&.jsoneditor-hover { &.jsoneditor-hover {
background-color: $hoverColor; background-color: $hoverColor;
&.jsoneditor-hover-insert-area { &.jsoneditor-hover-insert-area-after {
background-color: inherit; background-color: inherit;
> div.jsoneditor-insert-area { > div.jsoneditor-insert-area {
@ -662,16 +662,29 @@ div.jsoneditor-node-container {
box-shadow: 0 2px 6px 0 rgba(0,0,0,.24); box-shadow: 0 2px 6px 0 rgba(0,0,0,.24);
&:after { &:after {
content:''; content: '';
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 35px; left: 35px;
margin-left: -10px;
width: 0;
height: 0;
border-top: solid 10px $floating-menu-background;
border-bottom: none;
border-left: solid 10px transparent;
border-right: solid 10px transparent;
}
&.jsoneditor-floating-menu-bottom {
bottom: auto;
top: 100%;
&:after {
top: -10px;
margin-left: -10px; margin-left: -10px;
width: 0; border-top: none;
height: 0; border-bottom: solid 10px $floating-menu-background;
border-top: solid 10px $floating-menu-background; }
border-left: solid 10px transparent;
border-right: solid 10px transparent;
} }
button.jsoneditor-floating-menu-item { button.jsoneditor-floating-menu-item {

View File

@ -2,6 +2,7 @@ import { createElement as h, PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
const MENU_CLASS_NAME = 'jsoneditor-floating-menu' const MENU_CLASS_NAME = 'jsoneditor-floating-menu'
const MENU_CLASS_NAME_BOTTOM = 'jsoneditor-floating-menu-bottom'
const MENU_ITEM_CLASS_NAME = 'jsoneditor-floating-menu-item' const MENU_ITEM_CLASS_NAME = 'jsoneditor-floating-menu-item'
// Array: Sort | Map | Filter | Duplicate | Cut | Copy | Paste | Remove // Array: Sort | Map | Filter | Duplicate | Cut | Copy | Paste | Remove
@ -134,7 +135,10 @@ export default class FloatingMenu extends PureComponent {
type: PropTypes.string.isRequired type: PropTypes.string.isRequired
}) })
]).isRequired ]).isRequired
).isRequired ).isRequired,
path: PropTypes.arrayOf(PropTypes.string).isRequired,
emit: PropTypes.func.isRequired,
position: PropTypes.string // 'top' or 'bottom'
} }
render () { render () {
@ -150,7 +154,8 @@ export default class FloatingMenu extends PureComponent {
}) })
return h('div', { return h('div', {
className: MENU_CLASS_NAME, className: MENU_CLASS_NAME +
(this.props.position === 'bottom' ? (' ' + MENU_CLASS_NAME_BOTTOM) : ''),
onMouseDown: this.handleTouchStart, onMouseDown: this.handleTouchStart,
onTouchStart: this.handleTouchStart, onTouchStart: this.handleTouchStart,
}, items) }, items)

View File

@ -13,9 +13,12 @@ import initial from 'lodash/initial'
import last from 'lodash/last' import last from 'lodash/last'
export const SELECTED = 1 export const SELECTED = 1
export const SELECTED_END = 2 export const SELECTED_START = 2
export const SELECTED_BEFORE = 3 export const SELECTED_END = 4
export const SELECTED_AFTER = 4 export const SELECTED_FIRST = 8
export const SELECTED_LAST = 16
export const SELECTED_BEFORE = 32
export const SELECTED_AFTER = 64
export const META = Symbol('meta') export const META = Symbol('meta')
@ -349,7 +352,7 @@ function setSearchStatus (eson, esonPointer, searchStatus) {
/** /**
* Merge selection status into the eson object, cleanup previous selection * Merge selection status into the eson object, cleanup previous selection
* @param {ESON} eson * @param {ESON} eson
* @param {Selection} [selection] * @param {Selection | null} selection
* @return {ESON} Returns updated eson object * @return {ESON} Returns updated eson object
*/ */
export function applySelection (eson, selection) { export function applySelection (eson, selection) {
@ -357,11 +360,13 @@ export function applySelection (eson, selection) {
return cleanupMetaData(eson, 'selected') return cleanupMetaData(eson, 'selected')
} }
else if (selection.before) { else if (selection.before) {
const updatedEson = setIn(eson, selection.before.concat([META, 'selected']), SELECTED_BEFORE) const updatedEson = setIn(eson, selection.before.concat([META, 'selected']),
SELECTED + SELECTED_BEFORE)
return cleanupMetaData(updatedEson, 'selected', [selection.before]) return cleanupMetaData(updatedEson, 'selected', [selection.before])
} }
else if (selection.after) { else if (selection.after) {
const updatedEson = setIn(eson, selection.after.concat([META, 'selected']), SELECTED_AFTER) const updatedEson = setIn(eson, selection.after.concat([META, 'selected']),
SELECTED + SELECTED_AFTER)
return cleanupMetaData(updatedEson, 'selected', [selection.after]) return cleanupMetaData(updatedEson, 'selected', [selection.after])
} }
else { // selection.start and selection.end else { // selection.start and selection.end
@ -379,15 +384,21 @@ export function applySelection (eson, selection) {
const startIndex = root[META].props.indexOf(start) const startIndex = root[META].props.indexOf(start)
const endIndex = root[META].props.indexOf(end) const endIndex = root[META].props.indexOf(end)
const minIndex = Math.min(startIndex, endIndex) const firstIndex = Math.min(startIndex, endIndex)
const maxIndex = Math.max(startIndex, endIndex) + 1 // include max index itself const lastIndex = Math.max(startIndex, endIndex) + 1 // include max index itself
const firstProp = root[META].props[firstIndex]
const lastProp = root[META].props[lastIndex - 1]
const selectedProps = root[META].props.slice(minIndex, maxIndex) const selectedProps = root[META].props.slice(firstIndex, lastIndex)
selectedPaths = selectedProps.map(prop => rootPath.concat(prop)) selectedPaths = selectedProps.map(prop => rootPath.concat(prop))
let updatedObj = cloneWithSymbols(root) let updatedObj = cloneWithSymbols(root)
selectedProps.forEach(prop => { selectedProps.forEach(prop => {
updatedObj[prop] = setIn(updatedObj[prop], [META, 'selected'], const selected = SELECTED +
prop === end ? SELECTED_END : SELECTED) (prop === start ? SELECTED_START : 0) +
(prop === end ? SELECTED_END : 0) +
(prop === firstProp ? SELECTED_FIRST : 0) +
(prop === lastProp ? SELECTED_LAST : 0)
updatedObj[prop] = setIn(updatedObj[prop], [META, 'selected'], selected)
}) })
return updatedObj return updatedObj
@ -396,17 +407,21 @@ export function applySelection (eson, selection) {
const startIndex = parseInt(start, 10) const startIndex = parseInt(start, 10)
const endIndex = parseInt(end, 10) const endIndex = parseInt(end, 10)
const minIndex = Math.min(startIndex, endIndex) const firstIndex = Math.min(startIndex, endIndex)
const maxIndex = Math.max(startIndex, endIndex) + 1 // include max index itself const lastIndex = Math.max(startIndex, endIndex) + 1 // include max index itself
const selectedIndices = range(minIndex, maxIndex) const selectedIndices = range(firstIndex, lastIndex)
selectedPaths = selectedIndices.map(index => rootPath.concat(String(index))) selectedPaths = selectedIndices.map(index => rootPath.concat(String(index)))
let updatedArr = root.slice() let updatedArr = root.slice()
updatedArr = cloneWithSymbols(root) updatedArr = cloneWithSymbols(root)
selectedIndices.forEach(index => { selectedIndices.forEach(index => {
updatedArr[index] = setIn(updatedArr[index], [META, 'selected'], const selected = SELECTED +
index === endIndex ? SELECTED_END : SELECTED) (index === start ? SELECTED_START : 0) +
(index === end ? SELECTED_END : 0) +
(index === firstIndex ? SELECTED_FIRST : 0) +
(index === lastIndex ? SELECTED_LAST : 0)
updatedArr[index] = setIn(updatedArr[index], [META, 'selected'], selected)
}) })
return updatedArr return updatedArr