@wordpress/block-editor
Version:
210 lines (201 loc) • 7.75 kB
JavaScript
/**
* WordPress dependencies
*/
import { useDispatch, useSelect } from '@wordpress/data';
import { useEffect, useMemo } from '@wordpress/element';
import { usePrevious } from '@wordpress/compose';
/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
import { GridRect } from './utils';
import { setImmutably } from '../../utils/object';
export function useGridLayoutSync({
clientId: gridClientId
}) {
const {
gridLayout,
blockOrder,
selectedBlockLayout
} = useSelect(select => {
var _getBlockAttributes$l;
const {
getBlockAttributes,
getBlockOrder
} = select(blockEditorStore);
const selectedBlock = select(blockEditorStore).getSelectedBlock();
return {
gridLayout: (_getBlockAttributes$l = getBlockAttributes(gridClientId).layout) !== null && _getBlockAttributes$l !== void 0 ? _getBlockAttributes$l : {},
blockOrder: getBlockOrder(gridClientId),
selectedBlockLayout: selectedBlock?.attributes.style?.layout
};
}, [gridClientId]);
const {
getBlockAttributes,
getBlockRootClientId
} = useSelect(blockEditorStore);
const {
updateBlockAttributes,
__unstableMarkNextChangeAsNotPersistent
} = useDispatch(blockEditorStore);
const selectedBlockRect = useMemo(() => selectedBlockLayout ? new GridRect(selectedBlockLayout) : null, [selectedBlockLayout]);
const previouslySelectedBlockRect = usePrevious(selectedBlockRect);
const previousIsManualPlacement = usePrevious(gridLayout.isManualPlacement);
const previousBlockOrder = usePrevious(blockOrder);
useEffect(() => {
const updates = {};
if (gridLayout.isManualPlacement) {
const occupiedRects = [];
// Respect the position of blocks that already have a columnStart and rowStart value.
for (const clientId of blockOrder) {
var _getBlockAttributes$s;
const {
columnStart,
rowStart,
columnSpan = 1,
rowSpan = 1
} = (_getBlockAttributes$s = getBlockAttributes(clientId).style?.layout) !== null && _getBlockAttributes$s !== void 0 ? _getBlockAttributes$s : {};
if (!columnStart || !rowStart) {
continue;
}
occupiedRects.push(new GridRect({
columnStart,
rowStart,
columnSpan,
rowSpan
}));
}
// When in manual mode, ensure that every block has a columnStart and rowStart value.
for (const clientId of blockOrder) {
var _attributes$style$lay;
const attributes = getBlockAttributes(clientId);
const {
columnStart,
rowStart,
columnSpan = 1,
rowSpan = 1
} = (_attributes$style$lay = attributes.style?.layout) !== null && _attributes$style$lay !== void 0 ? _attributes$style$lay : {};
if (columnStart && rowStart) {
continue;
}
const [newColumnStart, newRowStart] = placeBlock(occupiedRects, gridLayout.columnCount, columnSpan, rowSpan, previouslySelectedBlockRect?.columnEnd, previouslySelectedBlockRect?.rowEnd);
occupiedRects.push(new GridRect({
columnStart: newColumnStart,
rowStart: newRowStart,
columnSpan,
rowSpan
}));
updates[clientId] = {
style: {
...attributes.style,
layout: {
...attributes.style?.layout,
columnStart: newColumnStart,
rowStart: newRowStart
}
}
};
}
// Ensure there's enough rows to fit all blocks.
const bottomMostRow = Math.max(...occupiedRects.map(r => r.rowEnd));
if (!gridLayout.rowCount || gridLayout.rowCount < bottomMostRow) {
updates[gridClientId] = {
layout: {
...gridLayout,
rowCount: bottomMostRow
}
};
}
// Unset grid layout attributes for blocks removed from the grid.
for (const clientId of previousBlockOrder !== null && previousBlockOrder !== void 0 ? previousBlockOrder : []) {
if (!blockOrder.includes(clientId)) {
var _attributes$style$lay2;
const rootClientId = getBlockRootClientId(clientId);
// Block was removed from the editor, so nothing to do.
if (rootClientId === null) {
continue;
}
// Check if the block is being moved to another grid.
// If so, do nothing and let the new grid parent handle
// the attributes.
const rootAttributes = getBlockAttributes(rootClientId);
if (rootAttributes?.layout?.type === 'grid') {
continue;
}
const attributes = getBlockAttributes(clientId);
const {
columnStart,
rowStart,
columnSpan,
rowSpan,
...layout
} = (_attributes$style$lay2 = attributes.style?.layout) !== null && _attributes$style$lay2 !== void 0 ? _attributes$style$lay2 : {};
if (columnStart || rowStart || columnSpan || rowSpan) {
const hasEmptyLayoutAttribute = Object.keys(layout).length === 0;
updates[clientId] = setImmutably(attributes, ['style', 'layout'], hasEmptyLayoutAttribute ? undefined : layout);
}
}
}
} else {
// Remove all of the columnStart and rowStart values
// when switching from manual to auto mode,
if (previousIsManualPlacement === true) {
for (const clientId of blockOrder) {
var _attributes$style$lay3;
const attributes = getBlockAttributes(clientId);
const {
columnStart,
rowStart,
...layout
} = (_attributes$style$lay3 = attributes.style?.layout) !== null && _attributes$style$lay3 !== void 0 ? _attributes$style$lay3 : {};
// Only update attributes if columnStart or rowStart are set.
if (columnStart || rowStart) {
const hasEmptyLayoutAttribute = Object.keys(layout).length === 0;
updates[clientId] = setImmutably(attributes, ['style', 'layout'], hasEmptyLayoutAttribute ? undefined : layout);
}
}
}
// Remove row styles in auto mode
if (gridLayout.rowCount) {
updates[gridClientId] = {
layout: {
...gridLayout,
rowCount: undefined
}
};
}
}
if (Object.keys(updates).length) {
__unstableMarkNextChangeAsNotPersistent();
updateBlockAttributes(Object.keys(updates), updates, /* uniqueByBlock: */true);
}
}, [
// Actual deps to sync:
gridClientId, gridLayout, previousBlockOrder, blockOrder, previouslySelectedBlockRect, previousIsManualPlacement,
// These won't change, but the linter thinks they might:
__unstableMarkNextChangeAsNotPersistent, getBlockAttributes, getBlockRootClientId, updateBlockAttributes]);
}
/**
* @param {GridRect[]} occupiedRects
* @param {number} gridColumnCount
* @param {number} blockColumnSpan
* @param {number} blockRowSpan
* @param {number?} startColumn
* @param {number?} startRow
*/
function placeBlock(occupiedRects, gridColumnCount, blockColumnSpan, blockRowSpan, startColumn = 1, startRow = 1) {
for (let row = startRow;; row++) {
for (let column = row === startRow ? startColumn : 1; column <= gridColumnCount; column++) {
const candidateRect = new GridRect({
columnStart: column,
rowStart: row,
columnSpan: blockColumnSpan,
rowSpan: blockRowSpan
});
if (!occupiedRects.some(r => r.intersectsRect(candidateRect))) {
return [column, row];
}
}
}
}
//# sourceMappingURL=use-grid-layout-sync.js.map