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 } }