From 49b68bbbea41e723f9192cf7ad845cdc0a8bd8f6 Mon Sep 17 00:00:00 2001 From: heswell Date: Mon, 1 Apr 2024 21:35:28 +0100 Subject: [PATCH] cell splitting partially implemented --- .../vuu-layout/src/drag-drop/BoxModel.ts | 2 +- .../vuu-layout/src/drag-drop/index.ts | 2 +- .../src/grid-layout/GridLayoutModel.ts | 156 ++++++++++++++-- .../src/grid-layout/GridPlaceholder.css | 49 +++++ .../src/grid-layout/GridPlaceholder.tsx | 172 ++++++++++++++++++ .../src/grid-layout/grid-dom-utils.ts | 6 +- .../src/grid-layout/grid-layout-utils.ts | 43 +++++ .../vuu-layout/src/grid-layout/index.ts | 1 + .../src/examples/html/GridLayout.examples.css | 9 + .../src/examples/html/GridLayout.examples.tsx | 4 +- .../examples/html/components/GridLayout.tsx | 19 +- .../examples/html/components/GridPalette.tsx | 10 +- .../html/components/GridPlaceholder.css | 3 - .../components/useGridSplitterResizing.ts | 128 ++++++++----- 14 files changed, 514 insertions(+), 90 deletions(-) create mode 100644 vuu-ui/packages/vuu-layout/src/grid-layout/GridPlaceholder.css create mode 100644 vuu-ui/packages/vuu-layout/src/grid-layout/GridPlaceholder.tsx delete mode 100644 vuu-ui/showcase/src/examples/html/components/GridPlaceholder.css diff --git a/vuu-ui/packages/vuu-layout/src/drag-drop/BoxModel.ts b/vuu-ui/packages/vuu-layout/src/drag-drop/BoxModel.ts index f47ca39c0..21710e3d6 100644 --- a/vuu-ui/packages/vuu-layout/src/drag-drop/BoxModel.ts +++ b/vuu-ui/packages/vuu-layout/src/drag-drop/BoxModel.ts @@ -191,7 +191,7 @@ export function getPosition( return { position: position!, x, y, closeToTheEdge, tab }; } -function getPositionWithinBox( +export function getPositionWithinBox( x: number, y: number, rect: DragDropRect, diff --git a/vuu-ui/packages/vuu-layout/src/drag-drop/index.ts b/vuu-ui/packages/vuu-layout/src/drag-drop/index.ts index fe7211cb3..7f5015a97 100644 --- a/vuu-ui/packages/vuu-layout/src/drag-drop/index.ts +++ b/vuu-ui/packages/vuu-layout/src/drag-drop/index.ts @@ -1,5 +1,5 @@ +export * from "./BoxModel"; export * from "./dragDropTypes"; export * from "./Draggable"; export * from "./DropMenu"; export * from "./DropTarget"; - diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutModel.ts b/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutModel.ts index adf02a170..ff43409e7 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutModel.ts +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutModel.ts @@ -3,7 +3,9 @@ import { AdjacentItems, collectItemsByColumnPosition, collectItemsByRowPosition, + getBisectingTrackEdge, occupySameTrack, + splitTrack, } from "./grid-layout-utils"; import { getEmptyExtents, getGridMatrix } from "./grid-matrix"; @@ -90,7 +92,7 @@ type GridItemMaps = { start: GridItemMap; }; -type GridItemUpdate = [string, GridLayoutModelPosition]; +export type GridItemUpdate = [string, GridLayoutModelPosition]; const flipDirection = (resizeDirection: GridLayoutResizeDirection) => resizeDirection === "horizontal" ? "vertical" : "horizontal"; @@ -990,7 +992,7 @@ export class GridLayoutModel { ? [this.setRowContracted, this.setShiftRowBackward, this.setGridRow] : [this.setColContracted, this.setShiftColBackward, this.setGridColumn]; - const updates: [string, GridLayoutModelPosition][] = []; + const updates: GridItemUpdate[] = []; if (anulledResizeOperation === "contract") { updates.push(setShiftBackward(resizeItem)); @@ -1017,10 +1019,124 @@ export class GridLayoutModel { return updates; } + splitGridItem( + gridItemId: string, + resizeDirection: GridLayoutResizeDirection, + tracks: number[] + ): { + updates: GridItemUpdate[]; + tracks: number[]; + } { + const gridItem = this.getGridItem(gridItemId); + if (gridItem) { + let updates: GridItemUpdate[] | undefined = undefined; + let newTracks = tracks; + const isVertical = resizeDirection === "vertical"; + const track = isVertical ? "row" : "column"; + const { + [track]: { start, end }, + } = gridItem; + + const trackIndex = start - 1; + + if (end - start === 1) { + newTracks = splitTrack(tracks, trackIndex); + updates = this.addTrack(trackIndex, resizeDirection); + + // Update the targetted gridItem and make sure update is applied + gridItem[track].end -= 1; + const [, position] = updates.find( + ([id]) => id === gridItemId + ) as GridItemUpdate; + position.end -= 1; + } else { + // If there is already a trackEdge that bisects this gridItem , + // we just have to realign the gridItem + const bisectingTrackEdge = getBisectingTrackEdge(tracks, start, end); + if (bisectingTrackEdge !== -1) { + console.log(`we have a bisection track edge ${bisectingTrackEdge}`); + updates = [[gridItemId, { start, end: bisectingTrackEdge }]]; + } else { + console.log(`need to add a track`); + } + } + + return { + updates: updates ?? [], + tracks: newTracks, + }; + } else { + throw Error( + `GridLayoutModel splitGridItem: no gridItem with id ${gridItemId}` + ); + } + } + + /* + When we add a track, all current track edges will be increased by 1. + Any gridItem bound to an edge equal to or greater than the one being + added must be adjusted. + */ + addTrack(trackIndex: number, resizeDirection: GridLayoutResizeDirection) { + const gridPosition = trackIndex + 1; + const updates: GridItemUpdate[] = []; + + const [ + { end: endMap, start: startMap }, + setExpanded, + setShiftForward, + setTrack, + ] = + resizeDirection === "vertical" + ? [ + this.rowMaps, + this.setRowExpanded, + this.setShiftRowForward, + this.setGridRow, + ] + : [ + this.columnMaps, + this.setColExpanded, + this.setShiftColForward, + this.setGridColumn, + ]; + + for (const [position, gridItems] of startMap) { + if (position > gridPosition) { + gridItems.forEach((item) => { + updates.push(setShiftForward(item)); + }); + } + } + + for (const [position, gridItems] of endMap) { + if (position > gridPosition) { + gridItems.forEach((item) => { + const existingUpdate = updates.find(([id]) => id === item.id); + if (!existingUpdate) { + updates.push(setExpanded(item)); + } + }); + } + } + + updates.forEach(([id, position]) => { + setTrack(id, position); + }); + + if (resizeDirection === "horizontal") { + this.colCount += 1; + } else { + this.rowCount += 1; + } + + return updates; + } + /* - When we remove a track edge, all following track edges will be reduced by 1. - Any gridItem bound to an edge greater than the one being removed must be - adjusted. + When we remove a track edge, all following track edges will be reduced by 1. + Any gridItem bound to an edge greater than the one being removed must be + adjusted. */ removeTrack(trackIndex: number, resizeDirection: GridLayoutResizeDirection) { const gridPosition = trackIndex + 1; @@ -1157,6 +1273,22 @@ export class GridLayoutModel { splitterAlign ); + adjacentItems.contra.forEach( + ({ id }) => (document.getElementById(id).dataset.resizeRole = "contra") + ); + adjacentItems.contraOtherTrack.forEach( + ({ id }) => + (document.getElementById(id).dataset.resizeRole = "contra-other-track") + ); + adjacentItems.siblingsOtherTrack.forEach( + ({ id }) => + (document.getElementById(id).dataset.resizeRole = "sibling-other-track") + ); + adjacentItems.nonAdjacent.forEach( + ({ id }) => + (document.getElementById(id).dataset.resizeRole = "non-adjacent") + ); + const track: GridLayoutTrack = resizeDirection === "horizontal" ? "column" : "row"; @@ -1165,21 +1297,13 @@ export class GridLayoutModel { ? [resizeItem[track].start - 1, resizeItem[track].start - 2] : [resizeItem[track].end - 2, resizeItem[track].end - 1]; - console.log( - ` - indexOfPrimaryResizeTrack = ${indexOfPrimaryResizeTrack} - indexOfSecondaryResizeTrack = ${indexOfSecondaryResizeTrack}, - `, - { - contraItems: adjacentItems.contra, - } - ); - const simpleResize = adjacentItems.contraOtherTrack.length === 0 || (adjacentItems.contra.length === 0 && adjacentItems.contraOtherTrack.length > 0); + console.log(`simple resize ${simpleResize}`); + return { ...rest, adjacentItems, @@ -1195,8 +1319,6 @@ export class GridLayoutModel { simpleResize, splitterAlign, }; - - console.log({ adjacentItems, simpleResize }); } toDebugString() { diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/GridPlaceholder.css b/vuu-ui/packages/vuu-layout/src/grid-layout/GridPlaceholder.css new file mode 100644 index 000000000..0add3db7d --- /dev/null +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/GridPlaceholder.css @@ -0,0 +1,49 @@ +.vuuGridPlaceholder { + background-color: rgba(255,12,12,.1); + position: relative; +} +.vuuGridPlaceholder-EAST { + --grid-dropzone-top:0px; + --grid-dropzone-left:50%; + --grid-dropzone-bottom:0px; + --grid-dropzone-right:0px; +} +.vuuGridPlaceholder-WEST { + --grid-dropzone-top:0px; + --grid-dropzone-left:0px; + --grid-dropzone-bottom:0px; + --grid-dropzone-right:50%; +} +.vuuGridPlaceholder-NORTH { + --grid-dropzone-top:0px; + --grid-dropzone-left:0px; + --grid-dropzone-bottom:50%; + --grid-dropzone-right: 0px; +} +.vuuGridPlaceholder-SOUTH { + --grid-dropzone-top:50%; + --grid-dropzone-left:0px; + --grid-dropzone-bottom:0px; + --grid-dropzone-right:0px; +} + +.vuuGridPlaceholder-CENTRE { + --grid-dropzone-top:0px; + --grid-dropzone-left:0px; + --grid-dropzone-bottom:0px; + --grid-dropzone-right:0px; +} + +.vuuGridPlaceholder:after { + background-color: cornflowerblue; + content: ''; + position: absolute; + top: var(--grid-dropzone-top); + right: var(--grid-dropzone-right); + bottom: var(--grid-dropzone-bottom); + left: var(--grid-dropzone-left); + transition-property: top,left,right, bottom; + transition-duration: .3s; + transition-timing-function: ease-in-out; +} + diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/GridPlaceholder.tsx b/vuu-ui/packages/vuu-layout/src/grid-layout/GridPlaceholder.tsx new file mode 100644 index 000000000..ec12f767f --- /dev/null +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/GridPlaceholder.tsx @@ -0,0 +1,172 @@ +import { rect, queryClosest, boxContainsPoint } from "@finos/vuu-utils"; +import { MousePosition } from "@finos/vuu-ui-controls"; +import { DragEventHandler, HTMLAttributes, useCallback, useRef } from "react"; + +import "./GridPlaceholder.css"; + +function getCenteredBox({ right, left, top, bottom }: rect, pctSize: number) { + const pctOffset = (1 - pctSize) / 2; + const w = (right - left) * pctOffset; + const h = (bottom - top) * pctOffset; + return { left: left + w, top: top + h, right: right - w, bottom: bottom - h }; +} + +export type DropPosition = "NORTH" | "SOUTH" | "EAST" | "WEST" | "CENTRE"; + +export type GridLayoutDropHandler = ( + targetId: string, + payloadId: string, + position: DropPosition +) => void; + +const classBase = "vuuGridPlaceholder"; + +export function pointPositionWithinRect( + x: number, + y: number, + rect: rect, + borderZone = 30 +) { + const width = rect.right - rect.left; + const height = rect.bottom - rect.top; + const posX = x - rect.left; + const posY = y - rect.top; + let closeToTheEdge = 0; + + if (posX < borderZone) closeToTheEdge += 8; + if (posX > width - borderZone) closeToTheEdge += 2; + if (posY < borderZone) closeToTheEdge += 1; + if (posY > height - borderZone) closeToTheEdge += 4; + + return { pctX: posX / width, pctY: posY / height, closeToTheEdge }; +} + +function getPositionWithinBox( + x: number, + y: number, + rect: rect, + pctX: number, + pctY: number +) { + const centerBox = getCenteredBox(rect, 0.2); + if (boxContainsPoint(centerBox, x, y)) { + return "CENTRE"; + } else { + const quadrant = `${pctY < 0.5 ? "north" : "south"}${ + pctX < 0.5 ? "west" : "east" + }`; + + switch (quadrant) { + case "northwest": + return pctX > pctY ? "NORTH" : "WEST"; + case "northeast": + return 1 - pctX > pctY ? "NORTH" : "EAST"; + case "southeast": + return pctX > pctY ? "EAST" : "SOUTH"; + case "southwest": + return 1 - pctX > pctY ? "WEST" : "SOUTH"; + default: + return ""; + } + } +} + +export interface GridPlaceholderProps + extends Omit, "onDrop"> { + debugLabel?: string; + onDrop: GridLayoutDropHandler; +} + +export const GridPlaceholder = ({ + onDrop, + ...htmlAttributes +}: GridPlaceholderProps) => { + const placeholderRef = useRef(); + const positionRef = useRef(""); + const mousePosRef = useRef({ clientX: -1, clientY: -1 }); + const targetRectRef = useRef({ + bottom: -1, + left: -1, + right: -1, + top: -1, + }); + + const onDragEnter = useCallback((evt) => { + console.log(`drag enter ${evt.target.id}`); + const target = queryClosest(evt.target, ".vuuGridPlaceholder"); + if (target) { + placeholderRef.current = target; + const { bottom, left, right, top } = target.getBoundingClientRect(); + targetRectRef.current.bottom = bottom; + targetRectRef.current.left = left; + targetRectRef.current.right = right; + targetRectRef.current.top = top; + } + }, []); + const onDragLeave = useCallback((evt) => { + if (placeholderRef.current && positionRef.current) { + if (positionRef.current !== "") { + placeholderRef.current.classList.remove( + `${classBase}-${positionRef.current}` + ); + positionRef.current = ""; + } + } + }, []); + const onDragOver = useCallback((evt) => { + evt.preventDefault(); + const { clientX, clientY } = evt; + const { current: mousePos } = mousePosRef; + if (clientX !== mousePos.clientX || clientY !== mousePos.clientY) { + mousePos.clientX = clientX; + mousePos.clientY = clientY; + + const { current: rect } = targetRectRef; + + const { pctX, pctY, closeToTheEdge } = pointPositionWithinRect( + clientX, + clientY, + rect + ); + + const position = getPositionWithinBox(clientX, clientY, rect, pctX, pctY); + const { current: lastPosition } = positionRef; + if (position !== positionRef.current) { + if (placeholderRef.current) { + if (lastPosition === "") { + placeholderRef.current.classList.add(`${classBase}-${position}`); + } else { + placeholderRef.current.classList.replace( + `${classBase}-${lastPosition}`, + `${classBase}-${position}` + ); + } + } + positionRef.current = position; + } + } + }, []); + + const handleDrop = useCallback( + (evt) => { + console.log("onDrop"); + onDrop( + placeholderRef.current.id, + evt.dataTransfer.getData("text/plain"), + positionRef.current + ); + }, + [onDrop] + ); + + return ( +
+ ); +}; diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/grid-dom-utils.ts b/vuu-ui/packages/vuu-layout/src/grid-layout/grid-dom-utils.ts index 76084b2f1..b5a4b791e 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/grid-dom-utils.ts +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/grid-dom-utils.ts @@ -2,8 +2,8 @@ import { GridLayoutModelPosition, GridLayoutResizeDirection, IGridLayoutModelItem, -} from "@finos/vuu-layout"; -import { ResizeState } from "../../../../showcase/src/examples/html/components/useGridSplitterResizing"; + ResizeState, +} from "./GridLayoutModel"; export const classNameLayoutItem = "vuuGridLayoutItem"; @@ -212,7 +212,7 @@ export const splitGridTracks = ( }; export const setGridTrackTemplate = ( - { grid, resizeDirection }: ResizeState, + { grid, resizeDirection }: Pick, tracks: number[] ) => { const trackTemplate = tracks.map((r) => `${r}px`).join(" "); diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/grid-layout-utils.ts b/vuu-ui/packages/vuu-layout/src/grid-layout/grid-layout-utils.ts index 9b0862eca..d93ba953e 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/grid-layout-utils.ts +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/grid-layout-utils.ts @@ -102,3 +102,46 @@ export const collectItemsByRowPosition = ( items.nonAdjacent.push(gridItem); } }; + +export const splitTrack = (tracks: number[], trackIndex: number) => { + const size = tracks[trackIndex]; + const newTracks = tracks.slice(); + newTracks.splice(trackIndex, 0, 0); + newTracks[trackIndex] = Math.floor(size / 2); + newTracks[trackIndex + 1] = Math.ceil(size / 2); + return newTracks; +}; + +/** + * + * @param tracks the track sizes + * @param start the start track Edge (1 based) + * @param end the end track edge (1 based) + * @returns + */ +export const getBisectingTrackEdge = ( + tracks: number[], + start: number, + end: number +) => { + if (end - start > 1) { + // Total the sizes between start and end + // find the half way point + // see if an existing edge occurs at that point (or wiuthin .5 pixesl, if decimal) + } + let size = 0; + for (let i = start - 1; i < end - 1; i++) { + size += tracks[i]; + } + const halfSize = size / 2; + console.log(`half of ${size} = ${halfSize} tracks : [${tracks.join(", ")}]`); + + size = 0; + for (let i = start - 1; i < end - 1; i++) { + size += tracks[i]; + if (Math.abs(halfSize - size) < 1) { + return i + 2; + } + } + return -1; +}; diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/index.ts b/vuu-ui/packages/vuu-layout/src/grid-layout/index.ts index c3f7fdb49..a0e6adf45 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/index.ts +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/index.ts @@ -1,4 +1,5 @@ export * from "./GridLayoutModel"; export * from "./GridLayoutProvider"; +export * from "./GridPlaceholder"; export * from "./grid-dom-utils"; export * from "./grid-layout-utils"; diff --git a/vuu-ui/showcase/src/examples/html/GridLayout.examples.css b/vuu-ui/showcase/src/examples/html/GridLayout.examples.css index 1ca61d21f..71ff67a92 100644 --- a/vuu-ui/showcase/src/examples/html/GridLayout.examples.css +++ b/vuu-ui/showcase/src/examples/html/GridLayout.examples.css @@ -10,3 +10,12 @@ grid-template-rows: repeat(var(--row-count), 1fr); } +[data-resize-role]:after { + color: white; + content: attr(data-resize-role); + font-size: 16px; + font-weight: bold; + left: 9px; + position: absolute; + top: 32px; +} \ No newline at end of file diff --git a/vuu-ui/showcase/src/examples/html/GridLayout.examples.tsx b/vuu-ui/showcase/src/examples/html/GridLayout.examples.tsx index d2841ab51..7e9c24325 100644 --- a/vuu-ui/showcase/src/examples/html/GridLayout.examples.tsx +++ b/vuu-ui/showcase/src/examples/html/GridLayout.examples.tsx @@ -10,7 +10,7 @@ export const GridLayoutA = () => { const layoutApi = useRef(null) const splitSelectedRow = useCallback(() => { - const activeComponent = document.querySelector(".component-active"); + const activeComponent = document.querySelector(".vuuGridLayoutItem-active"); if (activeComponent && layoutApi.current) { console.log("split active"); layoutApi.current.splitGridRow(activeComponent.id); @@ -18,7 +18,7 @@ export const GridLayoutA = () => { }, []); const splitSelectedCol = useCallback(() => { - const activeComponent = document.querySelector(".component-active"); + const activeComponent = document.querySelector(".vuuGridLayoutItem-active"); if (activeComponent && layoutApi.current) { console.log("split active"); layoutApi.current.splitGridCol(activeComponent.id); diff --git a/vuu-ui/showcase/src/examples/html/components/GridLayout.tsx b/vuu-ui/showcase/src/examples/html/components/GridLayout.tsx index 947ed3062..fff2e24b9 100644 --- a/vuu-ui/showcase/src/examples/html/components/GridLayout.tsx +++ b/vuu-ui/showcase/src/examples/html/components/GridLayout.tsx @@ -7,11 +7,10 @@ import { useCallback, useImperativeHandle, } from "react"; -import { useGridSplitterResizing } from "./useGridSplitterResizing"; import { useDragDrop } from "./useDragDrop"; +import { useGridSplitterResizing } from "./useGridSplitterResizing"; import "./GridLayout.css"; -import "./GridPlaceholder.css"; import "./GridSplitter.css"; import { @@ -20,8 +19,9 @@ import { useGridLayoutProps, useGridLayoutProviderDispatch, } from "@finos/vuu-layout"; -import { ResizeOrientation } from "@finos/vuu-layout/src/grid-layout/grid-dom-utils"; +import { ResizeOrientation } from "@finos/vuu-layout"; import { IconButton } from "@finos/vuu-ui-controls"; +import { GridPlaceholder } from "@finos/vuu-layout"; const classBase = "vuuGridLayout"; const classBaseItem = "vuuGridLayoutItem"; @@ -35,17 +35,6 @@ export interface GridSplitterProps orientation: ResizeOrientation; } -export interface GridPlaceholderProps extends HTMLAttributes { - debugLabel?: string; -} - -export const GridPlaceholder = ({ - debugLabel, - ...htmlAttributes -}: GridPlaceholderProps) => { - return
; -}; - export const GridSplitter = ({ align, "aria-controls": ariaControls, @@ -163,6 +152,7 @@ export const GridLayout = ({ gridTemplateColumns, gridTemplateRows, layoutMap, + onDrop, splitGridCol, splitGridRow, containerRef, @@ -211,6 +201,7 @@ export const GridLayout = ({ { +export type GridPaletteProps = HTMLAttributes; + +export const GridPalette = ({ ...htmlAttributes }: GridPaletteProps) => { const onDragStart = useCallback((evt) => { const draggedItem = queryClosest(evt.target, ".vuuGridPalette-item"); if (draggedItem) { + evt.dataTransfer.setData("text/plain", draggedItem.id); + evt.dataTransfer.effectAllowed = "move"; console.log(`drag start ${draggedItem.id}`); } }, []); return ( -
+
{colors.map((color, index) => (
{ const gridItemStyle = layoutMapRef.current[id]; - if (resizeDirection === "horizontal") { - gridItemStyle.gridColumnStart = start; - gridItemStyle.gridColumnEnd = end; - } else { - gridItemStyle.gridRowStart = start; - gridItemStyle.gridRowEnd = end; + if (gridItemStyle) { + // we will have no entries for placeholders + if (resizeDirection === "horizontal") { + gridItemStyle.gridColumnStart = start; + gridItemStyle.gridColumnEnd = end; + } else { + gridItemStyle.gridRowStart = start; + gridItemStyle.gridRowEnd = end; + } } }, [] @@ -166,24 +168,15 @@ export const useGridSplitterResizing = ({ const moveBy = currentMousePos - mousePos; const newTracks = getTracks(resizingState.current, true); - console.log( - `%cflip resize tracks original mousePos ${mousePos}, newPos ${currentMousePos} - tracks ${newTracks.join(",")} - `, - "color:blue;font-weight: bold;" - ); - if (resizeOperation === "contract") { const targetTrackSize = newTracks[indexOfPrimaryResizedItem]; newTracks[indexOfSecondaryResizedItem] += targetTrackSize; - // Note, should be moveBy newTracks[indexOfPrimaryResizedItem] = 0; } else { const targetTrackSize = newTracks[indexOfPrimaryResizedItem - 1]; const resizeAmount = targetTrackSize - Math.abs(moveBy); newTracks[indexOfPrimaryResizedItem] += targetTrackSize; - // Note, should be moveBy newTracks[indexOfSecondaryResizedItem] = Math.abs(resizeAmount); newTracks[indexOfSecondaryResizedItem - 1] -= Math.abs(resizeAmount); } @@ -224,7 +217,7 @@ export const useGridSplitterResizing = ({ setGridLayoutMap(id, position, resizeDirection); }); }, - [] + [setGridLayoutMap] ); const restoreComponentPositions = useCallback( @@ -548,10 +541,10 @@ export const useGridSplitterResizing = ({ gridTracks[indexOfPrimaryResizeTrack] = Math.abs(moveBy); gridTracks[indexOfSecondaryResizeTrack] -= moveBy; setGridTrackTemplate(state, gridTracks); - } - if (adjacentItems.contra.length > 0 && !simpleResize) { - repositionComponentsForExpand(false); + if (adjacentItems.contra.length > 0) { + repositionComponentsForExpand(false); + } } } }, @@ -614,10 +607,10 @@ export const useGridSplitterResizing = ({ gridTracks[indexOfPrimaryResizedItem] = Math.abs(moveBy); gridTracks[indexOfPrimaryResizedItem + 1] += moveBy; setGridTrackTemplate(state, gridTracks); - } - if (adjacentItems.contra.length > 0 && !simpleResize) { - repositionComponentsForContract(false); + if (adjacentItems.contra.length > 0) { + repositionComponentsForContract(false); + } } } }, @@ -780,27 +773,65 @@ export const useGridSplitterResizing = ({ } }, []); - const splitGridCol = useCallback((id: string) => { - const target = document.getElementById(id) as HTMLElement; - const col = getColumn(target); - const splitTracks = splitGridTracks( - containerRef.current, - col, - "horizontal" - ); - if (splitTracks) { - setGridColumn(target, splitTracks[0]); - } - }, []); - const splitGridRow = useCallback((id: string) => { - const target = document.getElementById(id) as HTMLElement; - const row = getRow(target); - const splitTracks = splitGridTracks(containerRef.current, row, "vertical"); - if (splitTracks) { - setGridRow(target, splitTracks[0]); - } - console.log({ splitTracks }); - }, []); + const splitGridCol = useCallback( + (gridItemId: string) => { + const { current: grid } = containerRef; + // const target = document.getElementById(gridItemId) as HTMLElement; + const gridItem = layoutModel.getGridItem(gridItemId); + if (grid && gridItem) { + const columns = getColumns(grid); + const { tracks, updates } = layoutModel.splitGridItem( + gridItemId, + "horizontal", + columns + ); + if (updates.length > 0) { + setGridTrackTemplate({ grid, resizeDirection: "horizontal" }, tracks); + applyUpdates("horizontal", updates); + + layoutModel.createPlaceholders(); + const splitters = layoutModel.getSplitterPositions(); + const placeholders = layoutModel.getPlaceholders(); + setNonContentGridItems({ placeholders, splitters }); + + // add placeholders to the layoutMap + } + } else { + throw Error(`splitGridCol no gridItem with id ${gridItemId}`); + } + }, + [applyUpdates, layoutModel] + ); + + const splitGridRow = useCallback( + (gridItemId: string) => { + const { current: grid } = containerRef; + // const target = document.getElementById(gridItemId) as HTMLElement; + const gridItem = layoutModel.getGridItem(gridItemId); + if (grid && gridItem) { + const rows = getRows(grid); + const { tracks, updates } = layoutModel.splitGridItem( + gridItemId, + "vertical", + rows + ); + + if (updates.length > 0) { + // TODO move all into model + setGridTrackTemplate({ grid, resizeDirection: "vertical" }, tracks); + applyUpdates("vertical", updates); + layoutModel.createPlaceholders(); + const splitters = layoutModel.getSplitterPositions(); + const placeholders = layoutModel.getPlaceholders(); + + setNonContentGridItems({ placeholders, splitters }); + } + } else { + throw Error(`splitGridRow no gridItem with id ${gridItemId}`); + } + }, + [applyUpdates, layoutModel] + ); useLayoutEffect(() => { /* @@ -847,6 +878,10 @@ export const useGridSplitterResizing = ({ [applyUpdates, layoutModel] ); + const handleDrop = useCallback((target, payload, position) => { + console.log(`handle Drop ${target} ${payload} ${position}`); + }, []); + return { children, containerRef, @@ -855,6 +890,7 @@ export const useGridSplitterResizing = ({ gridTemplateRows: rows.join(" "), layoutMap: layoutMapRef.current, onClick: clickHandler, + onDrop: handleDrop, onMouseDown, onMouseUp, splitGridCol,