From 7b6c6db9b093321de8dbbfd5ae500346257ed502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 15 Feb 2024 10:20:37 +0100 Subject: [PATCH] resolveVerticalDirection --- .../block-grid-entries.element.ts | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index fb61775397..3d1e5f0959 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -7,6 +7,48 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import '../block-grid-entry/index.js'; import { UmbSorterController, type UmbSorterConfig } from '@umbraco-cms/backoffice/sorter'; +// Utils: +// TODO: Maybe move these into their own files.. + +function getInterpolatedIndexOfPositionInWeightMap(target: number, weights: Array) { + const map = [0]; + weights.reduce((a, b, i) => { + return (map[i + 1] = a + b); + }, 0); + const foundValue = map.reduce((a, b) => { + const aDiff = Math.abs(a - target); + const bDiff = Math.abs(b - target); + + if (aDiff === bDiff) { + return a < b ? a : b; + } else { + return bDiff < aDiff ? b : a; + } + }); + const foundIndex = map.indexOf(foundValue); + const targetDiff = target - foundValue; + let interpolatedIndex = foundIndex; + if (targetDiff < 0 && foundIndex === 0) { + // Don't adjust. + } else if (targetDiff > 0 && foundIndex === map.length - 1) { + // Don't adjust. + } else { + const foundInterpolationWeight = weights[targetDiff >= 0 ? foundIndex : foundIndex - 1]; + interpolatedIndex += foundInterpolationWeight === 0 ? interpolatedIndex : targetDiff / foundInterpolationWeight; + } + return interpolatedIndex; +} + +function getAccumulatedValueOfIndex(index: number, weights: Array) { + const len = Math.min(index, weights.length); + let i = 0, + calc = 0; + while (i < len) { + calc += weights[i++]; + } + return calc; +} + const SORTER_CONFIG: UmbSorterConfig = { getUniqueOfElement: (element) => { return element.contentUdi!; @@ -14,12 +56,128 @@ const SORTER_CONFIG: UmbSorterConfig { return modelEntry.contentUdi; }, + resolveVerticalDirection: (args) => { + /** We need some data about the grid to figure out if there is room to be placed next to the found element */ + const approvedContainerComputedStyles = getComputedStyle(args.containerElement); + const gridColumnGap = Number(approvedContainerComputedStyles.columnGap.split('px')[0]) || 0; + const gridColumnNumber = parseInt( + approvedContainerComputedStyles.getPropertyValue('--umb-block-grid--grid-columns'), + 10, + ); + + const foundElColumns = parseInt(args.relatedElement.dataset.colSpan ?? '', 10); + const currentElementColumns = args.item.columnSpan; + + if (currentElementColumns >= gridColumnNumber) { + return true; + } + + // Get grid template: + const approvedContainerGridColumns = approvedContainerComputedStyles.gridTemplateColumns + .trim() + .split('px') + .map((x) => Number(x)) + .filter((n) => n > 0) + .map((n, i, list) => (list.length === i ? n : n + gridColumnGap)); + + // ensure all columns are there. + // This will also ensure handling non-css-grid mode, + // use container width divided by amount of columns( or the item width divided by its amount of columnSpan) + let amountOfColumnsInWeightMap = approvedContainerGridColumns.length; + const amountOfUnknownColumns = gridColumnNumber - amountOfColumnsInWeightMap; + if (amountOfUnknownColumns > 0) { + const accumulatedValue = + getAccumulatedValueOfIndex(amountOfColumnsInWeightMap, approvedContainerGridColumns) || 0; + const missingColumnWidth = (args.containerRect.width - accumulatedValue) / amountOfUnknownColumns; + if (missingColumnWidth > 0) { + while (amountOfColumnsInWeightMap++ < gridColumnNumber) { + approvedContainerGridColumns.push(missingColumnWidth); + } + } + } + + let offsetPlacement = 0; + /* If placeholder is in this same line, we want to assume that it will offset the placement of the found element, + which provides more potential space for the item to drop at. + This is relevant in this calculation where we look at the space to determine if its a vertical or horizontal drop in relation to the found element. + */ + if (args.placeholderIsInThisRow && args.elementRect.left < args.relatedRect.left) { + offsetPlacement = -(args.elementRect.width + gridColumnGap); + } + + const relatedStartX = Math.max(args.relatedRect.left - args.containerRect.left + offsetPlacement, 0); + const relatedStartCol = Math.round( + getInterpolatedIndexOfPositionInWeightMap(relatedStartX, approvedContainerGridColumns), + ); + + // If the found related element does not have enough room after which for the current element, then we go vertical mode: + return ( + relatedStartCol + (args.horizontalPlaceAfter ? foundElColumns : 0) + currentElementColumns > gridColumnNumber + ); + }, identifier: 'block-grid-editor', itemSelector: 'umb-block-grid-entry', //ignorerSelector: '', // No ignorerSelector, as we want to ignore nothing. containerSelector: '.umb-block-grid__layout-container', }; +function resolveVerticalDirection(data) { + /** We need some data about the grid to figure out if there is room to be placed next to the found element */ + const approvedContainerComputedStyles = getComputedStyle(data.containerElement); + const gridColumnGap = Number(approvedContainerComputedStyles.columnGap.split('px')[0]) || 0; + const gridColumnNumber = parseInt( + approvedContainerComputedStyles.getPropertyValue('--umb-block-grid--grid-columns'), + 10, + ); + const foundElColumns = parseInt(data.relatedElement.dataset.colSpan, 10); + const currentElementColumns = data.item.columnSpan; + + if (currentElementColumns >= gridColumnNumber) { + return true; + } + + // Get grid template: + const approvedContainerGridColumns = approvedContainerComputedStyles.gridTemplateColumns + .trim() + .split('px') + .map((x) => Number(x)) + .filter((n) => n > 0) + .map((n, i, list) => (list.length === i ? n : n + gridColumnGap)); + + // ensure all columns are there. + // This will also ensure handling non-css-grid mode, + // use container width divided by amount of columns( or the item width divided by its amount of columnSpan) + let amountOfColumnsInWeightMap = approvedContainerGridColumns.length; + const amountOfUnknownColumns = gridColumnNumber - amountOfColumnsInWeightMap; + if (amountOfUnknownColumns > 0) { + let accumulatedValue = getAccumulatedValueOfIndex(amountOfColumnsInWeightMap, approvedContainerGridColumns) || 0; + const layoutWidth = data.containerRect.width; + const missingColumnWidth = (layoutWidth - accumulatedValue) / amountOfUnknownColumns; + if (missingColumnWidth > 0) { + while (amountOfColumnsInWeightMap++ < gridColumnNumber) { + approvedContainerGridColumns.push(missingColumnWidth); + } + } + } + + let offsetPlacement = 0; + /* If placeholder is in this same line, we want to assume that it will offset the placement of the found element, + which provides more potential space for the item to drop at. + This is relevant in this calculation where we look at the space to determine if its a vertical or horizontal drop in relation to the found element. + */ + if (data.placeholderIsInThisRow && data.elementRect.left < data.relatedRect.left) { + offsetPlacement = -(data.elementRect.width + gridColumnGap); + } + + const relatedStartX = Math.max(data.relatedRect.left - data.containerRect.left + offsetPlacement, 0); + const relatedStartCol = Math.round( + getInterpolatedIndexOfPositionInWeightMap(relatedStartX, approvedContainerGridColumns), + ); + + // If the found related element does not have enough room after which for the current element, then we go vertical mode: + return relatedStartCol + (data.horizontalPlaceAfter ? foundElColumns : 0) + currentElementColumns > gridColumnNumber; +} + /** * @element umb-block-grid-entries */