@sanity/visual-editing
Version:
[](https://npm-stat.com/charts.html?package=@sanity/visual-editing) [](https://
132 lines (126 loc) • 4 kB
text/typescript
import type {VisualEditingControllerMsg} from '@sanity/presentation-comlink'
import type {ElementState, OverlayMsg} from '../types'
/**
* Reducer for managing element state from received channel messages
* @internal
*/
export const elementsReducer = (
elements: ElementState[],
message: OverlayMsg | VisualEditingControllerMsg,
): ElementState[] => {
const {type} = message
switch (type) {
case 'element/register': {
const elementExists = !!elements.find((e) => e.id === message.id)
if (elementExists) return elements
return [
...elements,
{
id: message.id,
activated: false,
element: message.element,
focused: false,
hovered: false,
rect: message.rect,
sanity: message.sanity,
dragDisabled: message.dragDisabled,
targets: message.targets,
elementType: message.elementType,
},
]
}
case 'element/activate':
return elements.map((e) => {
if (e.id === message.id) {
return {...e, activated: true}
}
return e
})
case 'element/update': {
return elements.map((e) => {
if (e.id === message.id) {
return {
...e,
sanity: message.sanity,
rect: message.rect,
targets: message.targets,
elementType: message.elementType,
}
}
return e
})
}
case 'element/unregister':
return elements.filter((e) => e.id !== message.id)
case 'element/deactivate':
return elements.map((e) => {
if (e.id === message.id) {
return {...e, activated: false, hovered: false}
}
return e
})
case 'element/mouseenter':
return elements.map((e) => {
if (e.id === message.id) {
return {...e, rect: message.rect, hovered: true}
}
return {...e, hovered: false}
})
case 'element/mouseleave':
return elements.map((element) => {
if (element.id === message.id) {
return {...element, hovered: false}
}
return element
})
case 'element/updateRect':
return elements.map((element) => {
if (element.id === message.id) {
return {...element, rect: message.rect}
}
return element
})
case 'element/click':
return elements.map((e) => {
return {...e, focused: e.id === message.id && 'clicked'}
})
case 'overlay/reset-mouse-state':
return elements.map((e) => {
return {...e, focused: false, hovered: false}
})
case 'overlay/blur':
return elements.map((e) => {
return {...e, focused: false}
})
case 'presentation/blur':
return elements.map((e) => {
return {...e, focused: false}
})
case 'presentation/focus': {
// Before setting the focus state of each element, check to see if any
// element has gained focus from an `element/click` message. Presentation
// tool "reflects" these back as a `presentation/focus` message.
const clickedElement = elements.find((e) => e.focused === 'clicked')
return elements.map((e) => {
// We want to focus any element which matches the received id and path
const focused =
'path' in e.sanity &&
e.sanity.id === message.data.id &&
e.sanity.path === message.data.path
// If we have a 'clicked' element, and that element matches, it is a
// reflection, so we maintain the focus state
if (clickedElement && e === clickedElement && focused) {
return e
}
return {
...e,
// Mark as a dupe if another matching item has been clicked to prevent
// scrolling, otherwise just set focus as a boolean
focused: focused && clickedElement ? 'duplicate' : focused,
}
})
}
default:
return elements
}
}