panelgrid 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/PanelGridProvider.cjs +3 -1
- package/dist/PanelGridProvider.cjs.map +1 -1
- package/dist/PanelGridProvider.d.cts +2 -0
- package/dist/PanelGridProvider.d.mts +2 -0
- package/dist/PanelGridProvider.mjs +3 -1
- package/dist/PanelGridProvider.mjs.map +1 -1
- package/dist/PanelGridRenderer.cjs +6 -2
- package/dist/PanelGridRenderer.cjs.map +1 -1
- package/dist/PanelGridRenderer.mjs +6 -2
- package/dist/PanelGridRenderer.mjs.map +1 -1
- package/dist/helpers/index.cjs +1 -0
- package/dist/helpers/index.d.cts +2 -2
- package/dist/helpers/index.d.mts +2 -2
- package/dist/helpers/index.mjs +2 -2
- package/dist/helpers/rearrangement.cjs +26 -5
- package/dist/helpers/rearrangement.cjs.map +1 -1
- package/dist/helpers/rearrangement.d.cts +11 -1
- package/dist/helpers/rearrangement.d.mts +11 -1
- package/dist/helpers/rearrangement.mjs +26 -6
- package/dist/helpers/rearrangement.mjs.map +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/styles.css +4 -0
- package/dist/types.d.cts +1 -0
- package/dist/types.d.mts +1 -0
- package/dist/usePanelGrid.cjs +53 -18
- package/dist/usePanelGrid.cjs.map +1 -1
- package/dist/usePanelGrid.d.cts +3 -0
- package/dist/usePanelGrid.d.mts +3 -0
- package/dist/usePanelGrid.mjs +53 -18
- package/dist/usePanelGrid.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ const PanelGridControlsContext = (0, react.createContext)(void 0);
|
|
|
11
11
|
const DEFAULT_RESIZE_HANDLE_POSITIONS = ["se"];
|
|
12
12
|
function PanelGridProvider({ panels: initialPanels, columnCount, gap, children, resizeHandlePositions, rearrangement }) {
|
|
13
13
|
const [baseSize, setBaseSize] = (0, react.useState)(null);
|
|
14
|
-
const { panels, panelMap, addPanel, removePanel, lockPanelSize, unlockPanelSize, exportState, ghostPanelRef } = require_usePanelGrid.usePanelGrid({
|
|
14
|
+
const { panels, panelMap, addPanel, removePanel, lockPanelSize, unlockPanelSize, lockPanelPosition, unlockPanelPosition, exportState, ghostPanelRef } = require_usePanelGrid.usePanelGrid({
|
|
15
15
|
panels: initialPanels,
|
|
16
16
|
columnCount,
|
|
17
17
|
baseSize: baseSize || 256,
|
|
@@ -36,6 +36,8 @@ function PanelGridProvider({ panels: initialPanels, columnCount, gap, children,
|
|
|
36
36
|
removePanel,
|
|
37
37
|
lockPanelSize,
|
|
38
38
|
unlockPanelSize,
|
|
39
|
+
lockPanelPosition,
|
|
40
|
+
unlockPanelPosition,
|
|
39
41
|
exportState
|
|
40
42
|
},
|
|
41
43
|
children
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PanelGridProvider.cjs","names":["DEFAULT_RESIZE_HANDLE_POSITIONS: ResizeHandlePosition[]","usePanelGrid"],"sources":["../src/PanelGridProvider.tsx"],"sourcesContent":["\"use client\";\nimport { createContext, useContext, useState } from \"react\";\nimport type { PanelCoordinate, PanelId, RearrangementFunction, ResizeHandlePosition } from \"./types\";\nimport { usePanelGrid } from \"./usePanelGrid\";\n\ntype PanelGridState = ReturnType<typeof usePanelGrid>;\n\ninterface PanelGridContextType {\n panels: PanelGridState[\"panels\"];\n panelMap: PanelGridState[\"panelMap\"];\n baseSize: number | null;\n columnCount: number;\n gap: number;\n resizeHandlePositions: ResizeHandlePosition[];\n ghostPanelRef: React.RefObject<HTMLDivElement | null>;\n}\n\ninterface PanelGridControlsContextType {\n setBaseSize: (baseSize: number) => void;\n addPanel: (panel: Partial<PanelCoordinate>) => void;\n removePanel: (id: PanelId) => void;\n lockPanelSize: (id: PanelId) => void;\n unlockPanelSize: (id: PanelId) => void;\n exportState: () => PanelCoordinate[];\n}\n\nconst PanelGridStateContext = createContext<PanelGridContextType | undefined>(undefined);\nconst PanelGridControlsContext = createContext<PanelGridControlsContextType | undefined>(undefined);\n\ninterface PanelGridProviderProps {\n panels: PanelCoordinate[];\n columnCount: number;\n gap: number;\n resizeHandlePositions?: ResizeHandlePosition[];\n children: React.ReactNode;\n /**\n * Optional custom rearrangement function to override default collision resolution logic\n * If provided, this function will be called instead of the default rearrangePanels\n */\n rearrangement?: RearrangementFunction;\n}\n\nconst DEFAULT_RESIZE_HANDLE_POSITIONS: ResizeHandlePosition[] = [\"se\"];\n\nexport function PanelGridProvider({\n panels: initialPanels,\n columnCount,\n gap,\n children,\n resizeHandlePositions,\n rearrangement,\n}: PanelGridProviderProps) {\n const [baseSize, setBaseSize] = useState<number | null>(null);\n\n const {
|
|
1
|
+
{"version":3,"file":"PanelGridProvider.cjs","names":["DEFAULT_RESIZE_HANDLE_POSITIONS: ResizeHandlePosition[]","usePanelGrid"],"sources":["../src/PanelGridProvider.tsx"],"sourcesContent":["\"use client\";\nimport { createContext, useContext, useState } from \"react\";\nimport type { PanelCoordinate, PanelId, RearrangementFunction, ResizeHandlePosition } from \"./types\";\nimport { usePanelGrid } from \"./usePanelGrid\";\n\ntype PanelGridState = ReturnType<typeof usePanelGrid>;\n\ninterface PanelGridContextType {\n panels: PanelGridState[\"panels\"];\n panelMap: PanelGridState[\"panelMap\"];\n baseSize: number | null;\n columnCount: number;\n gap: number;\n resizeHandlePositions: ResizeHandlePosition[];\n ghostPanelRef: React.RefObject<HTMLDivElement | null>;\n}\n\ninterface PanelGridControlsContextType {\n setBaseSize: (baseSize: number) => void;\n addPanel: (panel: Partial<PanelCoordinate>) => void;\n removePanel: (id: PanelId) => void;\n lockPanelSize: (id: PanelId) => void;\n unlockPanelSize: (id: PanelId) => void;\n lockPanelPosition: (id: PanelId) => void;\n unlockPanelPosition: (id: PanelId) => void;\n exportState: () => PanelCoordinate[];\n}\n\nconst PanelGridStateContext = createContext<PanelGridContextType | undefined>(undefined);\nconst PanelGridControlsContext = createContext<PanelGridControlsContextType | undefined>(undefined);\n\ninterface PanelGridProviderProps {\n panels: PanelCoordinate[];\n columnCount: number;\n gap: number;\n resizeHandlePositions?: ResizeHandlePosition[];\n children: React.ReactNode;\n /**\n * Optional custom rearrangement function to override default collision resolution logic\n * If provided, this function will be called instead of the default rearrangePanels\n */\n rearrangement?: RearrangementFunction;\n}\n\nconst DEFAULT_RESIZE_HANDLE_POSITIONS: ResizeHandlePosition[] = [\"se\"];\n\nexport function PanelGridProvider({\n panels: initialPanels,\n columnCount,\n gap,\n children,\n resizeHandlePositions,\n rearrangement,\n}: PanelGridProviderProps) {\n const [baseSize, setBaseSize] = useState<number | null>(null);\n\n const {\n panels,\n panelMap,\n addPanel,\n removePanel,\n lockPanelSize,\n unlockPanelSize,\n lockPanelPosition,\n unlockPanelPosition,\n exportState,\n ghostPanelRef,\n } = usePanelGrid({\n panels: initialPanels,\n columnCount,\n baseSize: baseSize || 256,\n gap,\n resizeHandlePositions: resizeHandlePositions || DEFAULT_RESIZE_HANDLE_POSITIONS,\n rearrangement,\n });\n\n return (\n <PanelGridStateContext.Provider\n value={{\n panels,\n panelMap,\n columnCount,\n gap,\n baseSize,\n resizeHandlePositions: resizeHandlePositions || DEFAULT_RESIZE_HANDLE_POSITIONS,\n ghostPanelRef,\n }}\n >\n <PanelGridControlsContext.Provider\n value={{\n setBaseSize,\n addPanel,\n removePanel,\n lockPanelSize,\n unlockPanelSize,\n lockPanelPosition,\n unlockPanelPosition,\n exportState,\n }}\n >\n {children}\n </PanelGridControlsContext.Provider>\n </PanelGridStateContext.Provider>\n );\n}\n\nexport function usePanelGridState() {\n const context = useContext(PanelGridStateContext);\n if (!context) {\n throw new Error(\"usePanelGridState must be used within a PanelGridProvider\");\n }\n return context;\n}\n\nexport function usePanelGridControls() {\n const context = useContext(PanelGridControlsContext);\n if (!context) {\n throw new Error(\"usePanelGridControls must be used within a PanelGridProvider\");\n }\n return context;\n}\n"],"mappings":";;;;;;;;AA4BA,MAAM,iDAAwE,OAAU;AACxF,MAAM,oDAAmF,OAAU;AAenG,MAAMA,kCAA0D,CAAC,KAAK;AAEtE,SAAgB,kBAAkB,EAChC,QAAQ,eACR,aACA,KACA,UACA,uBACA,iBACyB;CACzB,MAAM,CAAC,UAAU,mCAAuC,KAAK;CAE7D,MAAM,EACJ,QACA,UACA,UACA,aACA,eACA,iBACA,mBACA,qBACA,aACA,kBACEC,kCAAa;EACf,QAAQ;EACR;EACA,UAAU,YAAY;EACtB;EACA,uBAAuB,yBAAyB;EAChD;EACD,CAAC;AAEF,QACE,2CAAC,sBAAsB;EACrB,OAAO;GACL;GACA;GACA;GACA;GACA;GACA,uBAAuB,yBAAyB;GAChD;GACD;YAED,2CAAC,yBAAyB;GACxB,OAAO;IACL;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACD;GAEA;IACiC;GACL;;AAIrC,SAAgB,oBAAoB;CAClC,MAAM,gCAAqB,sBAAsB;AACjD,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,4DAA4D;AAE9E,QAAO;;AAGT,SAAgB,uBAAuB;CACrC,MAAM,gCAAqB,yBAAyB;AACpD,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,+DAA+D;AAEjF,QAAO"}
|
|
@@ -19,6 +19,8 @@ interface PanelGridControlsContextType {
|
|
|
19
19
|
removePanel: (id: PanelId) => void;
|
|
20
20
|
lockPanelSize: (id: PanelId) => void;
|
|
21
21
|
unlockPanelSize: (id: PanelId) => void;
|
|
22
|
+
lockPanelPosition: (id: PanelId) => void;
|
|
23
|
+
unlockPanelPosition: (id: PanelId) => void;
|
|
22
24
|
exportState: () => PanelCoordinate[];
|
|
23
25
|
}
|
|
24
26
|
interface PanelGridProviderProps {
|
|
@@ -19,6 +19,8 @@ interface PanelGridControlsContextType {
|
|
|
19
19
|
removePanel: (id: PanelId) => void;
|
|
20
20
|
lockPanelSize: (id: PanelId) => void;
|
|
21
21
|
unlockPanelSize: (id: PanelId) => void;
|
|
22
|
+
lockPanelPosition: (id: PanelId) => void;
|
|
23
|
+
unlockPanelPosition: (id: PanelId) => void;
|
|
22
24
|
exportState: () => PanelCoordinate[];
|
|
23
25
|
}
|
|
24
26
|
interface PanelGridProviderProps {
|
|
@@ -11,7 +11,7 @@ const PanelGridControlsContext = createContext(void 0);
|
|
|
11
11
|
const DEFAULT_RESIZE_HANDLE_POSITIONS = ["se"];
|
|
12
12
|
function PanelGridProvider({ panels: initialPanels, columnCount, gap, children, resizeHandlePositions, rearrangement }) {
|
|
13
13
|
const [baseSize, setBaseSize] = useState(null);
|
|
14
|
-
const { panels, panelMap, addPanel, removePanel, lockPanelSize, unlockPanelSize, exportState, ghostPanelRef } = usePanelGrid({
|
|
14
|
+
const { panels, panelMap, addPanel, removePanel, lockPanelSize, unlockPanelSize, lockPanelPosition, unlockPanelPosition, exportState, ghostPanelRef } = usePanelGrid({
|
|
15
15
|
panels: initialPanels,
|
|
16
16
|
columnCount,
|
|
17
17
|
baseSize: baseSize || 256,
|
|
@@ -36,6 +36,8 @@ function PanelGridProvider({ panels: initialPanels, columnCount, gap, children,
|
|
|
36
36
|
removePanel,
|
|
37
37
|
lockPanelSize,
|
|
38
38
|
unlockPanelSize,
|
|
39
|
+
lockPanelPosition,
|
|
40
|
+
unlockPanelPosition,
|
|
39
41
|
exportState
|
|
40
42
|
},
|
|
41
43
|
children
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PanelGridProvider.mjs","names":["DEFAULT_RESIZE_HANDLE_POSITIONS: ResizeHandlePosition[]"],"sources":["../src/PanelGridProvider.tsx"],"sourcesContent":["\"use client\";\nimport { createContext, useContext, useState } from \"react\";\nimport type { PanelCoordinate, PanelId, RearrangementFunction, ResizeHandlePosition } from \"./types\";\nimport { usePanelGrid } from \"./usePanelGrid\";\n\ntype PanelGridState = ReturnType<typeof usePanelGrid>;\n\ninterface PanelGridContextType {\n panels: PanelGridState[\"panels\"];\n panelMap: PanelGridState[\"panelMap\"];\n baseSize: number | null;\n columnCount: number;\n gap: number;\n resizeHandlePositions: ResizeHandlePosition[];\n ghostPanelRef: React.RefObject<HTMLDivElement | null>;\n}\n\ninterface PanelGridControlsContextType {\n setBaseSize: (baseSize: number) => void;\n addPanel: (panel: Partial<PanelCoordinate>) => void;\n removePanel: (id: PanelId) => void;\n lockPanelSize: (id: PanelId) => void;\n unlockPanelSize: (id: PanelId) => void;\n exportState: () => PanelCoordinate[];\n}\n\nconst PanelGridStateContext = createContext<PanelGridContextType | undefined>(undefined);\nconst PanelGridControlsContext = createContext<PanelGridControlsContextType | undefined>(undefined);\n\ninterface PanelGridProviderProps {\n panels: PanelCoordinate[];\n columnCount: number;\n gap: number;\n resizeHandlePositions?: ResizeHandlePosition[];\n children: React.ReactNode;\n /**\n * Optional custom rearrangement function to override default collision resolution logic\n * If provided, this function will be called instead of the default rearrangePanels\n */\n rearrangement?: RearrangementFunction;\n}\n\nconst DEFAULT_RESIZE_HANDLE_POSITIONS: ResizeHandlePosition[] = [\"se\"];\n\nexport function PanelGridProvider({\n panels: initialPanels,\n columnCount,\n gap,\n children,\n resizeHandlePositions,\n rearrangement,\n}: PanelGridProviderProps) {\n const [baseSize, setBaseSize] = useState<number | null>(null);\n\n const {
|
|
1
|
+
{"version":3,"file":"PanelGridProvider.mjs","names":["DEFAULT_RESIZE_HANDLE_POSITIONS: ResizeHandlePosition[]"],"sources":["../src/PanelGridProvider.tsx"],"sourcesContent":["\"use client\";\nimport { createContext, useContext, useState } from \"react\";\nimport type { PanelCoordinate, PanelId, RearrangementFunction, ResizeHandlePosition } from \"./types\";\nimport { usePanelGrid } from \"./usePanelGrid\";\n\ntype PanelGridState = ReturnType<typeof usePanelGrid>;\n\ninterface PanelGridContextType {\n panels: PanelGridState[\"panels\"];\n panelMap: PanelGridState[\"panelMap\"];\n baseSize: number | null;\n columnCount: number;\n gap: number;\n resizeHandlePositions: ResizeHandlePosition[];\n ghostPanelRef: React.RefObject<HTMLDivElement | null>;\n}\n\ninterface PanelGridControlsContextType {\n setBaseSize: (baseSize: number) => void;\n addPanel: (panel: Partial<PanelCoordinate>) => void;\n removePanel: (id: PanelId) => void;\n lockPanelSize: (id: PanelId) => void;\n unlockPanelSize: (id: PanelId) => void;\n lockPanelPosition: (id: PanelId) => void;\n unlockPanelPosition: (id: PanelId) => void;\n exportState: () => PanelCoordinate[];\n}\n\nconst PanelGridStateContext = createContext<PanelGridContextType | undefined>(undefined);\nconst PanelGridControlsContext = createContext<PanelGridControlsContextType | undefined>(undefined);\n\ninterface PanelGridProviderProps {\n panels: PanelCoordinate[];\n columnCount: number;\n gap: number;\n resizeHandlePositions?: ResizeHandlePosition[];\n children: React.ReactNode;\n /**\n * Optional custom rearrangement function to override default collision resolution logic\n * If provided, this function will be called instead of the default rearrangePanels\n */\n rearrangement?: RearrangementFunction;\n}\n\nconst DEFAULT_RESIZE_HANDLE_POSITIONS: ResizeHandlePosition[] = [\"se\"];\n\nexport function PanelGridProvider({\n panels: initialPanels,\n columnCount,\n gap,\n children,\n resizeHandlePositions,\n rearrangement,\n}: PanelGridProviderProps) {\n const [baseSize, setBaseSize] = useState<number | null>(null);\n\n const {\n panels,\n panelMap,\n addPanel,\n removePanel,\n lockPanelSize,\n unlockPanelSize,\n lockPanelPosition,\n unlockPanelPosition,\n exportState,\n ghostPanelRef,\n } = usePanelGrid({\n panels: initialPanels,\n columnCount,\n baseSize: baseSize || 256,\n gap,\n resizeHandlePositions: resizeHandlePositions || DEFAULT_RESIZE_HANDLE_POSITIONS,\n rearrangement,\n });\n\n return (\n <PanelGridStateContext.Provider\n value={{\n panels,\n panelMap,\n columnCount,\n gap,\n baseSize,\n resizeHandlePositions: resizeHandlePositions || DEFAULT_RESIZE_HANDLE_POSITIONS,\n ghostPanelRef,\n }}\n >\n <PanelGridControlsContext.Provider\n value={{\n setBaseSize,\n addPanel,\n removePanel,\n lockPanelSize,\n unlockPanelSize,\n lockPanelPosition,\n unlockPanelPosition,\n exportState,\n }}\n >\n {children}\n </PanelGridControlsContext.Provider>\n </PanelGridStateContext.Provider>\n );\n}\n\nexport function usePanelGridState() {\n const context = useContext(PanelGridStateContext);\n if (!context) {\n throw new Error(\"usePanelGridState must be used within a PanelGridProvider\");\n }\n return context;\n}\n\nexport function usePanelGridControls() {\n const context = useContext(PanelGridControlsContext);\n if (!context) {\n throw new Error(\"usePanelGridControls must be used within a PanelGridProvider\");\n }\n return context;\n}\n"],"mappings":";;;;;;;;AA4BA,MAAM,wBAAwB,cAAgD,OAAU;AACxF,MAAM,2BAA2B,cAAwD,OAAU;AAenG,MAAMA,kCAA0D,CAAC,KAAK;AAEtE,SAAgB,kBAAkB,EAChC,QAAQ,eACR,aACA,KACA,UACA,uBACA,iBACyB;CACzB,MAAM,CAAC,UAAU,eAAe,SAAwB,KAAK;CAE7D,MAAM,EACJ,QACA,UACA,UACA,aACA,eACA,iBACA,mBACA,qBACA,aACA,kBACE,aAAa;EACf,QAAQ;EACR;EACA,UAAU,YAAY;EACtB;EACA,uBAAuB,yBAAyB;EAChD;EACD,CAAC;AAEF,QACE,oBAAC,sBAAsB;EACrB,OAAO;GACL;GACA;GACA;GACA;GACA;GACA,uBAAuB,yBAAyB;GAChD;GACD;YAED,oBAAC,yBAAyB;GACxB,OAAO;IACL;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACD;GAEA;IACiC;GACL;;AAIrC,SAAgB,oBAAoB;CAClC,MAAM,UAAU,WAAW,sBAAsB;AACjD,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,4DAA4D;AAE9E,QAAO;;AAGT,SAAgB,uBAAuB;CACrC,MAAM,UAAU,WAAW,yBAAyB;AACpD,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,+DAA+D;AAEjF,QAAO"}
|
|
@@ -51,8 +51,12 @@ function PanelGridRenderer({ children: ItemComponent }) {
|
|
|
51
51
|
}),
|
|
52
52
|
panels.map((panel) => {
|
|
53
53
|
const { panelProps, resizeHandleProps } = panel;
|
|
54
|
-
const { key, lockSize, style, positionData, ref, onMouseDown } = panelProps;
|
|
55
|
-
const className =
|
|
54
|
+
const { key, lockSize, lockPosition, style, positionData, ref, onMouseDown } = panelProps;
|
|
55
|
+
const className = [
|
|
56
|
+
"panelgrid-panel",
|
|
57
|
+
lockSize ? "panelgrid-panel--size-locked" : "",
|
|
58
|
+
lockPosition ? "panelgrid-panel--position-locked" : ""
|
|
59
|
+
].filter(Boolean).join(" ");
|
|
56
60
|
const { x, y, w, h } = positionData;
|
|
57
61
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
58
62
|
className,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PanelGridRenderer.cjs","names":["usePanelGridState","usePanelGridControls","getGridRowCount"],"sources":["../src/PanelGridRenderer.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentType } from \"react\";\nimport { useLayoutEffect, useRef } from \"react\";\nimport { getGridRowCount } from \"./helpers/gridCalculations\";\nimport { usePanelGridControls, usePanelGridState } from \"./PanelGridProvider\";\nimport type { PanelId } from \"./types\";\n\ninterface PanelGridRendererProps {\n children: ComponentType<{ id: PanelId }>;\n}\n\nexport function PanelGridRenderer({ children: ItemComponent }: PanelGridRendererProps) {\n const { panels, columnCount, gap, baseSize, resizeHandlePositions, ghostPanelRef } = usePanelGridState();\n const { setBaseSize } = usePanelGridControls();\n const containerRef = useRef<HTMLDivElement | null>(null);\n const rowCount = getGridRowCount(\n panels.map(({ panelProps: p }) => ({\n id: p.key,\n x: p.positionData.x,\n y: p.positionData.y,\n w: p.positionData.w,\n h: p.positionData.h,\n }))\n );\n const count = Math.max(columnCount * (rowCount + 1), columnCount * columnCount);\n\n useLayoutEffect(() => {\n if (!containerRef.current) return;\n const observer = new ResizeObserver((entries) => {\n const [entry] = entries;\n const rect = entry.contentRect;\n const baseSize = Math.floor((rect.width - gap * (columnCount - 1)) / columnCount);\n setBaseSize(baseSize);\n });\n observer.observe(containerRef.current);\n return () => observer.disconnect();\n }, [columnCount, gap, setBaseSize]);\n\n return (\n <div\n className=\"panelgrid-renderer\"\n style={{\n \"--column-count\": `${columnCount}`,\n \"--gap\": `${gap}px`,\n opacity: baseSize ? 1 : 0,\n }}\n ref={containerRef}\n >\n {Array.from({ length: count }).map((_, i) => {\n return <div key={i} className=\"panelgrid-panel-placeholder\" />;\n })}\n\n <div className=\"panelgrid-panel-ghost\" ref={ghostPanelRef}></div>\n\n {panels.map((panel) => {\n const { panelProps, resizeHandleProps } = panel;\n const { key, lockSize, style, positionData, ref, onMouseDown } = panelProps;\n\n const className = lockSize ? \"panelgrid-panel
|
|
1
|
+
{"version":3,"file":"PanelGridRenderer.cjs","names":["usePanelGridState","usePanelGridControls","getGridRowCount"],"sources":["../src/PanelGridRenderer.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentType } from \"react\";\nimport { useLayoutEffect, useRef } from \"react\";\nimport { getGridRowCount } from \"./helpers/gridCalculations\";\nimport { usePanelGridControls, usePanelGridState } from \"./PanelGridProvider\";\nimport type { PanelId } from \"./types\";\n\ninterface PanelGridRendererProps {\n children: ComponentType<{ id: PanelId }>;\n}\n\nexport function PanelGridRenderer({ children: ItemComponent }: PanelGridRendererProps) {\n const { panels, columnCount, gap, baseSize, resizeHandlePositions, ghostPanelRef } = usePanelGridState();\n const { setBaseSize } = usePanelGridControls();\n const containerRef = useRef<HTMLDivElement | null>(null);\n const rowCount = getGridRowCount(\n panels.map(({ panelProps: p }) => ({\n id: p.key,\n x: p.positionData.x,\n y: p.positionData.y,\n w: p.positionData.w,\n h: p.positionData.h,\n }))\n );\n const count = Math.max(columnCount * (rowCount + 1), columnCount * columnCount);\n\n useLayoutEffect(() => {\n if (!containerRef.current) return;\n const observer = new ResizeObserver((entries) => {\n const [entry] = entries;\n const rect = entry.contentRect;\n const baseSize = Math.floor((rect.width - gap * (columnCount - 1)) / columnCount);\n setBaseSize(baseSize);\n });\n observer.observe(containerRef.current);\n return () => observer.disconnect();\n }, [columnCount, gap, setBaseSize]);\n\n return (\n <div\n className=\"panelgrid-renderer\"\n style={{\n \"--column-count\": `${columnCount}`,\n \"--gap\": `${gap}px`,\n opacity: baseSize ? 1 : 0,\n }}\n ref={containerRef}\n >\n {Array.from({ length: count }).map((_, i) => {\n // biome-ignore lint/suspicious/noArrayIndexKey: The order of placeholders will not be changed.\n return <div key={i} className=\"panelgrid-panel-placeholder\" />;\n })}\n\n <div className=\"panelgrid-panel-ghost\" ref={ghostPanelRef}></div>\n\n {panels.map((panel) => {\n const { panelProps, resizeHandleProps } = panel;\n const { key, lockSize, lockPosition, style, positionData, ref, onMouseDown } = panelProps;\n\n const className = [\n \"panelgrid-panel\",\n lockSize ? \"panelgrid-panel--size-locked\" : \"\",\n lockPosition ? \"panelgrid-panel--position-locked\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n const { x, y, w, h } = positionData;\n\n return (\n <div\n key={key}\n className={className}\n style={style}\n ref={ref}\n onMouseDown={onMouseDown}\n data-panel-id={key}\n data-pg-x={x}\n data-pg-y={y}\n data-pg-w={w}\n data-pg-h={h}\n >\n <ItemComponent id={key} />\n {resizeHandleProps &&\n resizeHandlePositions.map((pos) => {\n return (\n <span\n key={pos}\n className={`panelgrid-resize-handle panelgrid-resize-handle--${pos}`}\n {...resizeHandleProps}\n data-pg-resize-handle={pos}\n />\n );\n })}\n </div>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAWA,SAAgB,kBAAkB,EAAE,UAAU,iBAAyC;CACrF,MAAM,EAAE,QAAQ,aAAa,KAAK,UAAU,uBAAuB,kBAAkBA,6CAAmB;CACxG,MAAM,EAAE,gBAAgBC,gDAAsB;CAC9C,MAAM,iCAA6C,KAAK;CACxD,MAAM,WAAWC,yCACf,OAAO,KAAK,EAAE,YAAY,SAAS;EACjC,IAAI,EAAE;EACN,GAAG,EAAE,aAAa;EAClB,GAAG,EAAE,aAAa;EAClB,GAAG,EAAE,aAAa;EAClB,GAAG,EAAE,aAAa;EACnB,EAAE,CACJ;CACD,MAAM,QAAQ,KAAK,IAAI,eAAe,WAAW,IAAI,cAAc,YAAY;AAE/E,kCAAsB;AACpB,MAAI,CAAC,aAAa,QAAS;EAC3B,MAAM,WAAW,IAAI,gBAAgB,YAAY;GAC/C,MAAM,CAAC,SAAS;GAChB,MAAM,OAAO,MAAM;AAEnB,eADiB,KAAK,OAAO,KAAK,QAAQ,OAAO,cAAc,MAAM,YAAY,CAC5D;IACrB;AACF,WAAS,QAAQ,aAAa,QAAQ;AACtC,eAAa,SAAS,YAAY;IACjC;EAAC;EAAa;EAAK;EAAY,CAAC;AAEnC,QACE,4CAAC;EACC,WAAU;EACV,OAAO;GACL,kBAAkB,GAAG;GACrB,SAAS,GAAG,IAAI;GAChB,SAAS,WAAW,IAAI;GACzB;EACD,KAAK;;GAEJ,MAAM,KAAK,EAAE,QAAQ,OAAO,CAAC,CAAC,KAAK,GAAG,MAAM;AAE3C,WAAO,2CAAC,SAAY,WAAU,iCAAb,EAA6C;KAC9D;GAEF,2CAAC;IAAI,WAAU;IAAwB,KAAK;KAAqB;GAEhE,OAAO,KAAK,UAAU;IACrB,MAAM,EAAE,YAAY,sBAAsB;IAC1C,MAAM,EAAE,KAAK,UAAU,cAAc,OAAO,cAAc,KAAK,gBAAgB;IAE/E,MAAM,YAAY;KAChB;KACA,WAAW,iCAAiC;KAC5C,eAAe,qCAAqC;KACrD,CACE,OAAO,QAAQ,CACf,KAAK,IAAI;IACZ,MAAM,EAAE,GAAG,GAAG,GAAG,MAAM;AAEvB,WACE,4CAAC;KAEY;KACJ;KACF;KACQ;KACb,iBAAe;KACf,aAAW;KACX,aAAW;KACX,aAAW;KACX,aAAW;gBAEX,2CAAC,iBAAc,IAAI,MAAO,EACzB,qBACC,sBAAsB,KAAK,QAAQ;AACjC,aACE,2CAAC;OAEC,WAAW,oDAAoD;OAC/D,GAAI;OACJ,yBAAuB;SAHlB,IAIL;OAEJ;OAtBC,IAuBD;KAER;;GACE"}
|
|
@@ -51,8 +51,12 @@ function PanelGridRenderer({ children: ItemComponent }) {
|
|
|
51
51
|
}),
|
|
52
52
|
panels.map((panel) => {
|
|
53
53
|
const { panelProps, resizeHandleProps } = panel;
|
|
54
|
-
const { key, lockSize, style, positionData, ref, onMouseDown } = panelProps;
|
|
55
|
-
const className =
|
|
54
|
+
const { key, lockSize, lockPosition, style, positionData, ref, onMouseDown } = panelProps;
|
|
55
|
+
const className = [
|
|
56
|
+
"panelgrid-panel",
|
|
57
|
+
lockSize ? "panelgrid-panel--size-locked" : "",
|
|
58
|
+
lockPosition ? "panelgrid-panel--position-locked" : ""
|
|
59
|
+
].filter(Boolean).join(" ");
|
|
56
60
|
const { x, y, w, h } = positionData;
|
|
57
61
|
return /* @__PURE__ */ jsxs("div", {
|
|
58
62
|
className,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PanelGridRenderer.mjs","names":[],"sources":["../src/PanelGridRenderer.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentType } from \"react\";\nimport { useLayoutEffect, useRef } from \"react\";\nimport { getGridRowCount } from \"./helpers/gridCalculations\";\nimport { usePanelGridControls, usePanelGridState } from \"./PanelGridProvider\";\nimport type { PanelId } from \"./types\";\n\ninterface PanelGridRendererProps {\n children: ComponentType<{ id: PanelId }>;\n}\n\nexport function PanelGridRenderer({ children: ItemComponent }: PanelGridRendererProps) {\n const { panels, columnCount, gap, baseSize, resizeHandlePositions, ghostPanelRef } = usePanelGridState();\n const { setBaseSize } = usePanelGridControls();\n const containerRef = useRef<HTMLDivElement | null>(null);\n const rowCount = getGridRowCount(\n panels.map(({ panelProps: p }) => ({\n id: p.key,\n x: p.positionData.x,\n y: p.positionData.y,\n w: p.positionData.w,\n h: p.positionData.h,\n }))\n );\n const count = Math.max(columnCount * (rowCount + 1), columnCount * columnCount);\n\n useLayoutEffect(() => {\n if (!containerRef.current) return;\n const observer = new ResizeObserver((entries) => {\n const [entry] = entries;\n const rect = entry.contentRect;\n const baseSize = Math.floor((rect.width - gap * (columnCount - 1)) / columnCount);\n setBaseSize(baseSize);\n });\n observer.observe(containerRef.current);\n return () => observer.disconnect();\n }, [columnCount, gap, setBaseSize]);\n\n return (\n <div\n className=\"panelgrid-renderer\"\n style={{\n \"--column-count\": `${columnCount}`,\n \"--gap\": `${gap}px`,\n opacity: baseSize ? 1 : 0,\n }}\n ref={containerRef}\n >\n {Array.from({ length: count }).map((_, i) => {\n return <div key={i} className=\"panelgrid-panel-placeholder\" />;\n })}\n\n <div className=\"panelgrid-panel-ghost\" ref={ghostPanelRef}></div>\n\n {panels.map((panel) => {\n const { panelProps, resizeHandleProps } = panel;\n const { key, lockSize, style, positionData, ref, onMouseDown } = panelProps;\n\n const className = lockSize ? \"panelgrid-panel
|
|
1
|
+
{"version":3,"file":"PanelGridRenderer.mjs","names":[],"sources":["../src/PanelGridRenderer.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentType } from \"react\";\nimport { useLayoutEffect, useRef } from \"react\";\nimport { getGridRowCount } from \"./helpers/gridCalculations\";\nimport { usePanelGridControls, usePanelGridState } from \"./PanelGridProvider\";\nimport type { PanelId } from \"./types\";\n\ninterface PanelGridRendererProps {\n children: ComponentType<{ id: PanelId }>;\n}\n\nexport function PanelGridRenderer({ children: ItemComponent }: PanelGridRendererProps) {\n const { panels, columnCount, gap, baseSize, resizeHandlePositions, ghostPanelRef } = usePanelGridState();\n const { setBaseSize } = usePanelGridControls();\n const containerRef = useRef<HTMLDivElement | null>(null);\n const rowCount = getGridRowCount(\n panels.map(({ panelProps: p }) => ({\n id: p.key,\n x: p.positionData.x,\n y: p.positionData.y,\n w: p.positionData.w,\n h: p.positionData.h,\n }))\n );\n const count = Math.max(columnCount * (rowCount + 1), columnCount * columnCount);\n\n useLayoutEffect(() => {\n if (!containerRef.current) return;\n const observer = new ResizeObserver((entries) => {\n const [entry] = entries;\n const rect = entry.contentRect;\n const baseSize = Math.floor((rect.width - gap * (columnCount - 1)) / columnCount);\n setBaseSize(baseSize);\n });\n observer.observe(containerRef.current);\n return () => observer.disconnect();\n }, [columnCount, gap, setBaseSize]);\n\n return (\n <div\n className=\"panelgrid-renderer\"\n style={{\n \"--column-count\": `${columnCount}`,\n \"--gap\": `${gap}px`,\n opacity: baseSize ? 1 : 0,\n }}\n ref={containerRef}\n >\n {Array.from({ length: count }).map((_, i) => {\n // biome-ignore lint/suspicious/noArrayIndexKey: The order of placeholders will not be changed.\n return <div key={i} className=\"panelgrid-panel-placeholder\" />;\n })}\n\n <div className=\"panelgrid-panel-ghost\" ref={ghostPanelRef}></div>\n\n {panels.map((panel) => {\n const { panelProps, resizeHandleProps } = panel;\n const { key, lockSize, lockPosition, style, positionData, ref, onMouseDown } = panelProps;\n\n const className = [\n \"panelgrid-panel\",\n lockSize ? \"panelgrid-panel--size-locked\" : \"\",\n lockPosition ? \"panelgrid-panel--position-locked\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n const { x, y, w, h } = positionData;\n\n return (\n <div\n key={key}\n className={className}\n style={style}\n ref={ref}\n onMouseDown={onMouseDown}\n data-panel-id={key}\n data-pg-x={x}\n data-pg-y={y}\n data-pg-w={w}\n data-pg-h={h}\n >\n <ItemComponent id={key} />\n {resizeHandleProps &&\n resizeHandlePositions.map((pos) => {\n return (\n <span\n key={pos}\n className={`panelgrid-resize-handle panelgrid-resize-handle--${pos}`}\n {...resizeHandleProps}\n data-pg-resize-handle={pos}\n />\n );\n })}\n </div>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAWA,SAAgB,kBAAkB,EAAE,UAAU,iBAAyC;CACrF,MAAM,EAAE,QAAQ,aAAa,KAAK,UAAU,uBAAuB,kBAAkB,mBAAmB;CACxG,MAAM,EAAE,gBAAgB,sBAAsB;CAC9C,MAAM,eAAe,OAA8B,KAAK;CACxD,MAAM,WAAW,gBACf,OAAO,KAAK,EAAE,YAAY,SAAS;EACjC,IAAI,EAAE;EACN,GAAG,EAAE,aAAa;EAClB,GAAG,EAAE,aAAa;EAClB,GAAG,EAAE,aAAa;EAClB,GAAG,EAAE,aAAa;EACnB,EAAE,CACJ;CACD,MAAM,QAAQ,KAAK,IAAI,eAAe,WAAW,IAAI,cAAc,YAAY;AAE/E,uBAAsB;AACpB,MAAI,CAAC,aAAa,QAAS;EAC3B,MAAM,WAAW,IAAI,gBAAgB,YAAY;GAC/C,MAAM,CAAC,SAAS;GAChB,MAAM,OAAO,MAAM;AAEnB,eADiB,KAAK,OAAO,KAAK,QAAQ,OAAO,cAAc,MAAM,YAAY,CAC5D;IACrB;AACF,WAAS,QAAQ,aAAa,QAAQ;AACtC,eAAa,SAAS,YAAY;IACjC;EAAC;EAAa;EAAK;EAAY,CAAC;AAEnC,QACE,qBAAC;EACC,WAAU;EACV,OAAO;GACL,kBAAkB,GAAG;GACrB,SAAS,GAAG,IAAI;GAChB,SAAS,WAAW,IAAI;GACzB;EACD,KAAK;;GAEJ,MAAM,KAAK,EAAE,QAAQ,OAAO,CAAC,CAAC,KAAK,GAAG,MAAM;AAE3C,WAAO,oBAAC,SAAY,WAAU,iCAAb,EAA6C;KAC9D;GAEF,oBAAC;IAAI,WAAU;IAAwB,KAAK;KAAqB;GAEhE,OAAO,KAAK,UAAU;IACrB,MAAM,EAAE,YAAY,sBAAsB;IAC1C,MAAM,EAAE,KAAK,UAAU,cAAc,OAAO,cAAc,KAAK,gBAAgB;IAE/E,MAAM,YAAY;KAChB;KACA,WAAW,iCAAiC;KAC5C,eAAe,qCAAqC;KACrD,CACE,OAAO,QAAQ,CACf,KAAK,IAAI;IACZ,MAAM,EAAE,GAAG,GAAG,GAAG,MAAM;AAEvB,WACE,qBAAC;KAEY;KACJ;KACF;KACQ;KACb,iBAAe;KACf,aAAW;KACX,aAAW;KACX,aAAW;KACX,aAAW;gBAEX,oBAAC,iBAAc,IAAI,MAAO,EACzB,qBACC,sBAAsB,KAAK,QAAQ;AACjC,aACE,oBAAC;OAEC,WAAW,oDAAoD;OAC/D,GAAI;OACJ,yBAAuB;SAHlB,IAIL;OAEJ;OAtBC,IAuBD;KAER;;GACE"}
|
package/dist/helpers/index.cjs
CHANGED
|
@@ -14,4 +14,5 @@ exports.pixelsToGridPosition = require_gridCalculations.pixelsToGridPosition;
|
|
|
14
14
|
exports.pixelsToGridSize = require_gridCalculations.pixelsToGridSize;
|
|
15
15
|
exports.rearrangePanels = require_rearrangement.rearrangePanels;
|
|
16
16
|
exports.rectanglesOverlap = require_rearrangement.rectanglesOverlap;
|
|
17
|
+
exports.resolveWithLockRollback = require_rearrangement.resolveWithLockRollback;
|
|
17
18
|
exports.snapToGrid = require_gridCalculations.snapToGrid;
|
package/dist/helpers/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { applySnapAnimation } from "./animation.cjs";
|
|
2
2
|
import { gridPositionToPixels, gridToPixels, pixelsToGridPosition, pixelsToGridSize, snapToGrid } from "./gridCalculations.cjs";
|
|
3
3
|
import { detectAnimatingPanels } from "./panelDetection.cjs";
|
|
4
|
-
import { detectCollisions, findNewPosition, hasCollision, rearrangePanels, rectanglesOverlap } from "./rearrangement.cjs";
|
|
5
|
-
export { applySnapAnimation, detectAnimatingPanels, detectCollisions, findNewPosition, gridPositionToPixels, gridToPixels, hasCollision, pixelsToGridPosition, pixelsToGridSize, rearrangePanels, rectanglesOverlap, snapToGrid };
|
|
4
|
+
import { detectCollisions, findNewPosition, hasCollision, rearrangePanels, rectanglesOverlap, resolveWithLockRollback } from "./rearrangement.cjs";
|
|
5
|
+
export { applySnapAnimation, detectAnimatingPanels, detectCollisions, findNewPosition, gridPositionToPixels, gridToPixels, hasCollision, pixelsToGridPosition, pixelsToGridSize, rearrangePanels, rectanglesOverlap, resolveWithLockRollback, snapToGrid };
|
package/dist/helpers/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { applySnapAnimation } from "./animation.mjs";
|
|
2
2
|
import { gridPositionToPixels, gridToPixels, pixelsToGridPosition, pixelsToGridSize, snapToGrid } from "./gridCalculations.mjs";
|
|
3
3
|
import { detectAnimatingPanels } from "./panelDetection.mjs";
|
|
4
|
-
import { detectCollisions, findNewPosition, hasCollision, rearrangePanels, rectanglesOverlap } from "./rearrangement.mjs";
|
|
5
|
-
export { applySnapAnimation, detectAnimatingPanels, detectCollisions, findNewPosition, gridPositionToPixels, gridToPixels, hasCollision, pixelsToGridPosition, pixelsToGridSize, rearrangePanels, rectanglesOverlap, snapToGrid };
|
|
4
|
+
import { detectCollisions, findNewPosition, hasCollision, rearrangePanels, rectanglesOverlap, resolveWithLockRollback } from "./rearrangement.mjs";
|
|
5
|
+
export { applySnapAnimation, detectAnimatingPanels, detectCollisions, findNewPosition, gridPositionToPixels, gridToPixels, hasCollision, pixelsToGridPosition, pixelsToGridSize, rearrangePanels, rectanglesOverlap, resolveWithLockRollback, snapToGrid };
|
package/dist/helpers/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { detectCollisions, findNewPosition, hasCollision, rearrangePanels, rectanglesOverlap } from "./rearrangement.mjs";
|
|
1
|
+
import { detectCollisions, findNewPosition, hasCollision, rearrangePanels, rectanglesOverlap, resolveWithLockRollback } from "./rearrangement.mjs";
|
|
2
2
|
import { applySnapAnimation } from "./animation.mjs";
|
|
3
3
|
import { gridPositionToPixels, gridToPixels, pixelsToGridPosition, pixelsToGridSize, snapToGrid } from "./gridCalculations.mjs";
|
|
4
4
|
import { detectAnimatingPanels } from "./panelDetection.mjs";
|
|
5
5
|
|
|
6
|
-
export { applySnapAnimation, detectAnimatingPanels, detectCollisions, findNewPosition, gridPositionToPixels, gridToPixels, hasCollision, pixelsToGridPosition, pixelsToGridSize, rearrangePanels, rectanglesOverlap, snapToGrid };
|
|
6
|
+
export { applySnapAnimation, detectAnimatingPanels, detectCollisions, findNewPosition, gridPositionToPixels, gridToPixels, hasCollision, pixelsToGridPosition, pixelsToGridSize, rearrangePanels, rectanglesOverlap, resolveWithLockRollback, snapToGrid };
|
|
@@ -101,18 +101,37 @@ function rearrangePanels(movingPanel, allPanels, columnCount) {
|
|
|
101
101
|
const widthChanged = originalPanel.w !== constrainedMovingPanel.w;
|
|
102
102
|
const heightChanged = originalPanel.h !== constrainedMovingPanel.h;
|
|
103
103
|
if (widthChanged && heightChanged) {
|
|
104
|
-
const afterWidthChange = rearrangePanelsInternal({
|
|
104
|
+
const afterWidthChange = resolveWithLockRollback(rearrangePanelsInternal({
|
|
105
105
|
...constrainedMovingPanel,
|
|
106
106
|
h: originalPanel.h
|
|
107
|
-
}, allPanels, columnCount);
|
|
108
|
-
return
|
|
107
|
+
}, allPanels, columnCount), allPanels);
|
|
108
|
+
if (afterWidthChange === allPanels) return allPanels;
|
|
109
|
+
return resolveWithLockRollback(rearrangePanelsInternal({
|
|
109
110
|
...constrainedMovingPanel,
|
|
110
111
|
x: afterWidthChange.find((p) => p.id === movingPanel.id)?.x ?? constrainedMovingPanel.x,
|
|
111
112
|
y: afterWidthChange.find((p) => p.id === movingPanel.id)?.y ?? constrainedMovingPanel.y
|
|
112
|
-
}, afterWidthChange, columnCount);
|
|
113
|
+
}, afterWidthChange, columnCount), allPanels);
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
|
-
return rearrangePanelsInternal(constrainedMovingPanel, allPanels, columnCount);
|
|
116
|
+
return resolveWithLockRollback(rearrangePanelsInternal(constrainedMovingPanel, allPanels, columnCount), allPanels);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Check if any non-locked panel in the result overlaps with a locked panel.
|
|
120
|
+
* If so, return the original positions (rollback).
|
|
121
|
+
*
|
|
122
|
+
* Exported so custom RearrangementFunction implementations can compose with it:
|
|
123
|
+
* return resolveWithLockRollback(myCustomResult, allPanels);
|
|
124
|
+
*
|
|
125
|
+
* ロックされたパネルに重なりが生じた場合、元の位置に戻す(ロールバック)
|
|
126
|
+
*/
|
|
127
|
+
function resolveWithLockRollback(result, original) {
|
|
128
|
+
const lockedPanels = result.filter((p) => p.lockPosition);
|
|
129
|
+
if (lockedPanels.length === 0) return result;
|
|
130
|
+
for (const locked of lockedPanels) for (const panel of result) {
|
|
131
|
+
if (panel.id === locked.id) continue;
|
|
132
|
+
if (rectanglesOverlap(locked, panel)) return original;
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
116
135
|
}
|
|
117
136
|
/**
|
|
118
137
|
* Internal implementation of panel rearrangement
|
|
@@ -150,6 +169,7 @@ function rearrangePanelsInternal(movingPanel, allPanels, columnCount) {
|
|
|
150
169
|
for (const collidingId of sortedCollidingIds) {
|
|
151
170
|
const colliding = panelMap.get(collidingId);
|
|
152
171
|
if (!colliding) continue;
|
|
172
|
+
if (colliding.lockPosition) continue;
|
|
153
173
|
const newPos = findNewPosition(colliding, current, columnCount);
|
|
154
174
|
let candidate = {
|
|
155
175
|
...colliding,
|
|
@@ -235,4 +255,5 @@ exports.findNewPositionToAddPanel = findNewPositionToAddPanel;
|
|
|
235
255
|
exports.hasCollision = hasCollision;
|
|
236
256
|
exports.rearrangePanels = rearrangePanels;
|
|
237
257
|
exports.rectanglesOverlap = rectanglesOverlap;
|
|
258
|
+
exports.resolveWithLockRollback = resolveWithLockRollback;
|
|
238
259
|
//# sourceMappingURL=rearrangement.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rearrangement.cjs","names":["queue: PanelCoordinate[]","candidate: PanelCoordinate"],"sources":["../../src/helpers/rearrangement.ts"],"sourcesContent":["import type { PanelCoordinate, PanelId } from \"../types\";\n\n/**\n * Check if two rectangles overlap using AABB (Axis-Aligned Bounding Box) test\n * 2つの矩形が重なっているかをAABBテストで判定\n */\nexport function rectanglesOverlap(\n a: { x: number; y: number; w: number; h: number },\n b: { x: number; y: number; w: number; h: number }\n): boolean {\n return !(\n (\n a.x + a.w <= b.x || // a is to the left of b\n b.x + b.w <= a.x || // b is to the left of a\n a.y + a.h <= b.y || // a is above b\n b.y + b.h <= a.y\n ) // b is above a\n );\n}\n\n/**\n * Detect all panels that collide with the given panel\n * 指定されたパネルと衝突する全てのパネルを検出\n */\nexport function detectCollisions(panel: PanelCoordinate, panelMap: Map<PanelId, PanelCoordinate>): PanelId[] {\n const collisions = new Set<PanelId>();\n\n for (const [id, other] of panelMap) {\n if (id === panel.id) continue;\n\n if (rectanglesOverlap(panel, other)) {\n collisions.add(id);\n }\n }\n\n return Array.from(collisions);\n}\n\n/**\n * Check if a panel at the given position would collide with any existing panels\n * 指定された位置にパネルを配置した場合に衝突があるかをチェック\n */\nexport function hasCollision(\n candidate: { x: number; y: number; w: number; h: number },\n excludeId: PanelId,\n panelMap: Map<PanelId, PanelCoordinate>\n): boolean {\n for (const [id, panel] of panelMap) {\n if (id === excludeId) continue;\n if (rectanglesOverlap(candidate, panel)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Calculate the minimum distance to push a panel to avoid collision\n * パネルを押しのけるための最小距離を計算\n */\nfunction calculatePushDistance(\n pusher: PanelCoordinate,\n pushed: PanelCoordinate,\n columnCount: number\n): { direction: \"right\" | \"down\"; distance: number } | null {\n // Calculate how far to push horizontally (to the right)\n // 横方向(右)にどれだけ押すか計算\n const pushRight = pusher.x + pusher.w - pushed.x;\n const canPushRight = pushed.x + pushed.w + pushRight <= columnCount;\n\n // Calculate how far to push vertically (down)\n // 縦方向(下)にどれだけ押すか計算\n const pushDown = pusher.y + pusher.h - pushed.y;\n\n // Priority 1: Horizontal push if possible\n // 優先順位1: 可能なら横方向に押す\n if (canPushRight && pushRight > 0) {\n return { direction: \"right\", distance: pushRight };\n }\n\n // Priority 2: Vertical push\n // 優先順位2: 縦方向に押す\n if (pushDown > 0) {\n return { direction: \"down\", distance: pushDown };\n }\n\n return null;\n}\n\n/**\n * Find a new position for a panel by pushing it away from the colliding panel\n * Priority: horizontal (right) first, then vertical (down)\n * 衝突したパネルを押しのける方向に移動させる\n * 優先順位: 横方向(右)→縦方向(下)\n */\nexport function findNewPosition(\n panel: PanelCoordinate,\n pusher: PanelCoordinate,\n columnCount: number\n): { x: number; y: number } {\n const pushInfo = calculatePushDistance(pusher, panel, columnCount);\n\n if (!pushInfo) {\n // No push needed or can't determine direction\n // Fallback: move down\n return { x: panel.x, y: panel.y + 1 };\n }\n\n if (pushInfo.direction === \"right\") {\n // Push horizontally to the right\n // 横方向(右)に押す\n const newX = panel.x + pushInfo.distance;\n if (newX + panel.w <= columnCount) {\n return { x: newX, y: panel.y };\n }\n // Can't fit horizontally, push down instead\n // 横方向に入らない場合は下に押す\n }\n\n // Push vertically down\n // 縦方向(下)に押す\n return { x: panel.x, y: panel.y + pushInfo.distance };\n}\n\n/**\n * Constrain a panel to stay within grid boundaries\n * パネルをグリッド境界内に制約\n */\nfunction constrainToGrid(panel: PanelCoordinate, columnCount: number): PanelCoordinate {\n // Ensure x + w doesn't exceed columnCount\n // x + w が columnCount を超えないようにする\n const maxX = Math.max(0, columnCount - panel.w);\n const constrainedX = Math.max(0, Math.min(panel.x, maxX));\n\n // Ensure y is non-negative\n // y が負にならないようにする\n const constrainedY = Math.max(0, panel.y);\n\n return {\n ...panel,\n x: constrainedX,\n y: constrainedY,\n };\n}\n\n/**\n * Rearrange panels to resolve collisions when a panel is moved or resized\n * Panels are moved horizontally first, then vertically if needed\n * For compound resizes (both width and height change), uses two-phase processing\n * パネルの移動・リサイズ時に衝突を解決するようにパネルを再配置\n * 横方向を優先し、必要に応じて縦方向に移動\n * 幅と高さが同時に変更される場合は、二段階処理を使用\n */\nexport function rearrangePanels(\n movingPanel: PanelCoordinate,\n allPanels: PanelCoordinate[],\n columnCount: number\n): PanelCoordinate[] {\n // Constrain the moving panel to grid boundaries\n // 移動中のパネルをグリッド境界内に制約\n const constrainedMovingPanel = constrainToGrid(movingPanel, columnCount);\n\n // Check if this is a compound resize (both width and height changed)\n // 幅と高さの両方が変更されたかチェック\n const originalPanel = allPanels.find((p) => p.id === movingPanel.id);\n if (originalPanel) {\n const widthChanged = originalPanel.w !== constrainedMovingPanel.w;\n const heightChanged = originalPanel.h !== constrainedMovingPanel.h;\n\n if (widthChanged && heightChanged) {\n // Two-phase processing: width first, then height\n // 二段階処理: 幅を先に、次に高さ\n\n // Phase 1: Apply width change only\n // フェーズ1: 幅の変更のみを適用\n const widthOnlyPanel = {\n ...constrainedMovingPanel,\n h: originalPanel.h, // Keep original height\n };\n const afterWidthChange = rearrangePanelsInternal(widthOnlyPanel, allPanels, columnCount);\n\n // Phase 2: Apply height change to the result\n // フェーズ2: 結果に高さの変更を適用\n const heightChangedPanel = {\n ...constrainedMovingPanel,\n // Use the new position if panel-1 was moved during width phase\n x: afterWidthChange.find((p) => p.id === movingPanel.id)?.x ?? constrainedMovingPanel.x,\n y: afterWidthChange.find((p) => p.id === movingPanel.id)?.y ?? constrainedMovingPanel.y,\n };\n return rearrangePanelsInternal(heightChangedPanel, afterWidthChange, columnCount);\n }\n }\n\n // Single dimension change or move - use normal processing\n // 単一次元の変更または移動 - 通常の処理を使用\n return rearrangePanelsInternal(constrainedMovingPanel, allPanels, columnCount);\n}\n\n/**\n * Internal implementation of panel rearrangement\n * パネル再配置の内部実装\n */\nfunction rearrangePanelsInternal(\n movingPanel: PanelCoordinate,\n allPanels: PanelCoordinate[],\n columnCount: number\n): PanelCoordinate[] {\n // Create a map for fast panel lookup\n // パネルIDから座標への高速マップを作成\n const panelMap = new Map<PanelId, PanelCoordinate>();\n for (const panel of allPanels) {\n panelMap.set(panel.id, { ...panel });\n }\n\n // Update the moving panel's position in the map\n // 移動中のパネルの位置をマップに反映\n panelMap.set(movingPanel.id, { ...movingPanel });\n\n // Queue for processing panels that need to be repositioned\n // 再配置が必要なパネルの処理キュー\n const queue: PanelCoordinate[] = [{ ...movingPanel }];\n\n // Track processed panels to avoid reprocessing unnecessarily\n // 不要な再処理を避けるため処理済みパネルを追跡\n const processed = new Set<PanelId>();\n\n // Track all panels that have been repositioned across all queue iterations\n // 全キュー反復を通じて再配置されたパネルを追跡\n const repositioned = new Set<PanelId>();\n\n // Track processing count to prevent infinite loops\n // 無限ループを防ぐため処理回数を追跡\n const processCount = new Map<PanelId, number>();\n const MAX_PROCESS_COUNT = 10;\n\n // Process panels until no more collisions\n // 衝突がなくなるまでパネルを処理\n while (queue.length > 0) {\n const current = queue.shift()!;\n\n // Safety check: prevent infinite loops\n // 安全チェック: 無限ループを防止\n const count = processCount.get(current.id) || 0;\n if (count >= MAX_PROCESS_COUNT) continue;\n processCount.set(current.id, count + 1);\n\n // Skip if the position in queue doesn't match current position in map (outdated entry)\n // キューの位置がマップの現在位置と一致しない場合はスキップ(古いエントリ)\n const currentInMap = panelMap.get(current.id);\n if (currentInMap && (currentInMap.x !== current.x || currentInMap.y !== current.y)) {\n continue;\n }\n\n // Skip if already processed at this position\n // この位置で既に処理済みの場合はスキップ\n if (processed.has(current.id)) {\n continue;\n }\n\n // Detect collisions with current panel position\n // 現在のパネル位置での衝突を検出\n const collidingIds = detectCollisions(current, panelMap);\n\n if (collidingIds.length === 0) {\n panelMap.set(current.id, current);\n processed.add(current.id);\n continue;\n }\n\n // Sort colliding panels by position (top-left to bottom-right) for consistent processing\n // 一貫した処理のため、衝突パネルを位置順(左上から右下)にソート\n const sortedCollidingIds = collidingIds.sort((a, b) => {\n const panelA = panelMap.get(a)!;\n const panelB = panelMap.get(b)!;\n if (panelA.y !== panelB.y) return panelA.y - panelB.y;\n return panelA.x - panelB.x;\n });\n\n // Resolve collisions by pushing colliding panels\n // 衝突したパネルを押しのけて衝突を解決\n for (const collidingId of sortedCollidingIds) {\n const colliding = panelMap.get(collidingId);\n if (!colliding) continue;\n\n // Calculate new position for the colliding panel\n // 衝突パネルの新しい位置を計算\n const newPos = findNewPosition(colliding, current, columnCount);\n let candidate = { ...colliding, x: newPos.x, y: newPos.y };\n\n // Check if candidate overlaps with any previously repositioned panel\n // Re-validate after each adjustment to catch multi-overlap scenarios\n // 候補位置が既に再配置されたパネルと重なる場合、さらに右に押す。無理なら下に押す\n // 各調整後に再検証し、複数の重なりシナリオに対応\n let needsPushDown = false;\n let adjusted = true;\n let adjustAttempts = 0;\n while (adjusted && !needsPushDown && adjustAttempts < repositioned.size + 1) {\n adjusted = false;\n adjustAttempts++;\n for (const repoId of repositioned) {\n const repoPanel = panelMap.get(repoId);\n if (!repoPanel || repoId === collidingId) continue;\n if (!rectanglesOverlap(candidate, repoPanel)) continue;\n\n const furtherX = repoPanel.x + repoPanel.w;\n if (furtherX + candidate.w <= columnCount) {\n candidate = { ...candidate, x: furtherX };\n adjusted = true;\n break; // Restart loop to re-validate against all repositioned panels\n } else {\n needsPushDown = true;\n break;\n }\n }\n }\n\n if (needsPushDown) {\n const pushDown = current.y + current.h - colliding.y;\n candidate = { ...candidate, x: colliding.x, y: colliding.y + (pushDown > 0 ? pushDown : 1) };\n }\n\n // Update panel map and add to queue for further processing\n // パネルマップを更新し、さらなる処理のためキューに追加\n panelMap.set(collidingId, candidate);\n queue.push(candidate);\n repositioned.add(collidingId);\n }\n\n panelMap.set(current.id, current);\n processed.add(current.id);\n }\n\n // Return the rearranged panels\n // 再配置後のパネルを返す\n return Array.from(panelMap.values());\n}\n\n/**\n * Find a new position for a panel to be added\n * 追加するパネルの新しい位置を見つける\n */\nexport function findNewPositionToAddPanel(\n panelToAdd: Partial<PanelCoordinate>,\n allPanels: PanelCoordinate[],\n columnCount: number\n): { x: number; y: number } {\n const id = panelToAdd.id || Math.random().toString(36).substring(2, 15);\n const w = panelToAdd.w || 1;\n const h = panelToAdd.h || 1;\n\n // Create a map for fast panel lookup\n const panelMap = new Map<PanelId, PanelCoordinate>();\n for (const panel of allPanels) {\n panelMap.set(panel.id, panel);\n }\n\n // Calculate maximum row based on existing panels\n // Add some buffer rows to ensure we can find a position\n const maxExistingY = allPanels.length > 0 ? Math.max(...allPanels.map((p) => p.y + p.h)) : 0;\n const MAX_ROWS = Math.max(maxExistingY + 100, 1000);\n\n // Try to find a position starting from top-left\n for (let y = 0; y < MAX_ROWS; y++) {\n for (let x = 0; x <= columnCount - w; x++) {\n const candidate: PanelCoordinate = {\n id,\n x,\n y,\n w,\n h,\n };\n\n // Check if this position has any collisions\n if (!hasCollision(candidate, candidate.id, panelMap)) {\n return { x, y };\n }\n }\n }\n\n // Fallback: if no position found, place at the bottom\n return { x: 0, y: maxExistingY };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rearrangement.cjs","names":["queue: PanelCoordinate[]","candidate: PanelCoordinate"],"sources":["../../src/helpers/rearrangement.ts"],"sourcesContent":["import type { PanelCoordinate, PanelId } from \"../types\";\n\n/**\n * Check if two rectangles overlap using AABB (Axis-Aligned Bounding Box) test\n * 2つの矩形が重なっているかをAABBテストで判定\n */\nexport function rectanglesOverlap(\n a: { x: number; y: number; w: number; h: number },\n b: { x: number; y: number; w: number; h: number }\n): boolean {\n return !(\n (\n a.x + a.w <= b.x || // a is to the left of b\n b.x + b.w <= a.x || // b is to the left of a\n a.y + a.h <= b.y || // a is above b\n b.y + b.h <= a.y\n ) // b is above a\n );\n}\n\n/**\n * Detect all panels that collide with the given panel\n * 指定されたパネルと衝突する全てのパネルを検出\n */\nexport function detectCollisions(panel: PanelCoordinate, panelMap: Map<PanelId, PanelCoordinate>): PanelId[] {\n const collisions = new Set<PanelId>();\n\n for (const [id, other] of panelMap) {\n if (id === panel.id) continue;\n\n if (rectanglesOverlap(panel, other)) {\n collisions.add(id);\n }\n }\n\n return Array.from(collisions);\n}\n\n/**\n * Check if a panel at the given position would collide with any existing panels\n * 指定された位置にパネルを配置した場合に衝突があるかをチェック\n */\nexport function hasCollision(\n candidate: { x: number; y: number; w: number; h: number },\n excludeId: PanelId,\n panelMap: Map<PanelId, PanelCoordinate>\n): boolean {\n for (const [id, panel] of panelMap) {\n if (id === excludeId) continue;\n if (rectanglesOverlap(candidate, panel)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Calculate the minimum distance to push a panel to avoid collision\n * パネルを押しのけるための最小距離を計算\n */\nfunction calculatePushDistance(\n pusher: PanelCoordinate,\n pushed: PanelCoordinate,\n columnCount: number\n): { direction: \"right\" | \"down\"; distance: number } | null {\n // Calculate how far to push horizontally (to the right)\n // 横方向(右)にどれだけ押すか計算\n const pushRight = pusher.x + pusher.w - pushed.x;\n const canPushRight = pushed.x + pushed.w + pushRight <= columnCount;\n\n // Calculate how far to push vertically (down)\n // 縦方向(下)にどれだけ押すか計算\n const pushDown = pusher.y + pusher.h - pushed.y;\n\n // Priority 1: Horizontal push if possible\n // 優先順位1: 可能なら横方向に押す\n if (canPushRight && pushRight > 0) {\n return { direction: \"right\", distance: pushRight };\n }\n\n // Priority 2: Vertical push\n // 優先順位2: 縦方向に押す\n if (pushDown > 0) {\n return { direction: \"down\", distance: pushDown };\n }\n\n return null;\n}\n\n/**\n * Find a new position for a panel by pushing it away from the colliding panel\n * Priority: horizontal (right) first, then vertical (down)\n * 衝突したパネルを押しのける方向に移動させる\n * 優先順位: 横方向(右)→縦方向(下)\n */\nexport function findNewPosition(\n panel: PanelCoordinate,\n pusher: PanelCoordinate,\n columnCount: number\n): { x: number; y: number } {\n const pushInfo = calculatePushDistance(pusher, panel, columnCount);\n\n if (!pushInfo) {\n // No push needed or can't determine direction\n // Fallback: move down\n return { x: panel.x, y: panel.y + 1 };\n }\n\n if (pushInfo.direction === \"right\") {\n // Push horizontally to the right\n // 横方向(右)に押す\n const newX = panel.x + pushInfo.distance;\n if (newX + panel.w <= columnCount) {\n return { x: newX, y: panel.y };\n }\n // Can't fit horizontally, push down instead\n // 横方向に入らない場合は下に押す\n }\n\n // Push vertically down\n // 縦方向(下)に押す\n return { x: panel.x, y: panel.y + pushInfo.distance };\n}\n\n/**\n * Constrain a panel to stay within grid boundaries\n * パネルをグリッド境界内に制約\n */\nfunction constrainToGrid(panel: PanelCoordinate, columnCount: number): PanelCoordinate {\n // Ensure x + w doesn't exceed columnCount\n // x + w が columnCount を超えないようにする\n const maxX = Math.max(0, columnCount - panel.w);\n const constrainedX = Math.max(0, Math.min(panel.x, maxX));\n\n // Ensure y is non-negative\n // y が負にならないようにする\n const constrainedY = Math.max(0, panel.y);\n\n return {\n ...panel,\n x: constrainedX,\n y: constrainedY,\n };\n}\n\n/**\n * Rearrange panels to resolve collisions when a panel is moved or resized\n * Panels are moved horizontally first, then vertically if needed\n * For compound resizes (both width and height change), uses two-phase processing\n * パネルの移動・リサイズ時に衝突を解決するようにパネルを再配置\n * 横方向を優先し、必要に応じて縦方向に移動\n * 幅と高さが同時に変更される場合は、二段階処理を使用\n */\nexport function rearrangePanels(\n movingPanel: PanelCoordinate,\n allPanels: PanelCoordinate[],\n columnCount: number\n): PanelCoordinate[] {\n // Constrain the moving panel to grid boundaries\n // 移動中のパネルをグリッド境界内に制約\n const constrainedMovingPanel = constrainToGrid(movingPanel, columnCount);\n\n // Check if this is a compound resize (both width and height changed)\n // 幅と高さの両方が変更されたかチェック\n const originalPanel = allPanels.find((p) => p.id === movingPanel.id);\n if (originalPanel) {\n const widthChanged = originalPanel.w !== constrainedMovingPanel.w;\n const heightChanged = originalPanel.h !== constrainedMovingPanel.h;\n\n if (widthChanged && heightChanged) {\n // Two-phase processing: width first, then height\n // 二段階処理: 幅を先に、次に高さ\n\n // Phase 1: Apply width change only\n // フェーズ1: 幅の変更のみを適用\n const widthOnlyPanel = {\n ...constrainedMovingPanel,\n h: originalPanel.h, // Keep original height\n };\n const afterWidthChange = resolveWithLockRollback(\n rearrangePanelsInternal(widthOnlyPanel, allPanels, columnCount),\n allPanels\n );\n // If phase 1 already triggered a rollback, propagate it immediately\n // フェーズ1でロールバックが発生した場合は即座に返す\n if (afterWidthChange === allPanels) return allPanels;\n\n // Phase 2: Apply height change to the result\n // フェーズ2: 結果に高さの変更を適用\n const heightChangedPanel = {\n ...constrainedMovingPanel,\n // Use the new position if panel-1 was moved during width phase\n x: afterWidthChange.find((p) => p.id === movingPanel.id)?.x ?? constrainedMovingPanel.x,\n y: afterWidthChange.find((p) => p.id === movingPanel.id)?.y ?? constrainedMovingPanel.y,\n };\n return resolveWithLockRollback(\n rearrangePanelsInternal(heightChangedPanel, afterWidthChange, columnCount),\n allPanels\n );\n }\n }\n\n // Single dimension change or move - use normal processing\n // 単一次元の変更または移動 - 通常の処理を使用\n return resolveWithLockRollback(rearrangePanelsInternal(constrainedMovingPanel, allPanels, columnCount), allPanels);\n}\n\n/**\n * Check if any non-locked panel in the result overlaps with a locked panel.\n * If so, return the original positions (rollback).\n *\n * Exported so custom RearrangementFunction implementations can compose with it:\n * return resolveWithLockRollback(myCustomResult, allPanels);\n *\n * ロックされたパネルに重なりが生じた場合、元の位置に戻す(ロールバック)\n */\nexport function resolveWithLockRollback(result: PanelCoordinate[], original: PanelCoordinate[]): PanelCoordinate[] {\n const lockedPanels = result.filter((p) => p.lockPosition);\n if (lockedPanels.length === 0) return result;\n\n for (const locked of lockedPanels) {\n for (const panel of result) {\n if (panel.id === locked.id) continue;\n if (rectanglesOverlap(locked, panel)) {\n // A non-locked panel overlaps a locked panel — rollback everything\n // ロックされたパネルに重なりが発生 — 全てをロールバック\n return original;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Internal implementation of panel rearrangement\n * パネル再配置の内部実装\n */\nfunction rearrangePanelsInternal(\n movingPanel: PanelCoordinate,\n allPanels: PanelCoordinate[],\n columnCount: number\n): PanelCoordinate[] {\n // Create a map for fast panel lookup\n // パネルIDから座標への高速マップを作成\n const panelMap = new Map<PanelId, PanelCoordinate>();\n for (const panel of allPanels) {\n panelMap.set(panel.id, { ...panel });\n }\n\n // Update the moving panel's position in the map\n // 移動中のパネルの位置をマップに反映\n panelMap.set(movingPanel.id, { ...movingPanel });\n\n // Queue for processing panels that need to be repositioned\n // 再配置が必要なパネルの処理キュー\n const queue: PanelCoordinate[] = [{ ...movingPanel }];\n\n // Track processed panels to avoid reprocessing unnecessarily\n // 不要な再処理を避けるため処理済みパネルを追跡\n const processed = new Set<PanelId>();\n\n // Track all panels that have been repositioned across all queue iterations\n // 全キュー反復を通じて再配置されたパネルを追跡\n const repositioned = new Set<PanelId>();\n\n // Track processing count to prevent infinite loops\n // 無限ループを防ぐため処理回数を追跡\n const processCount = new Map<PanelId, number>();\n const MAX_PROCESS_COUNT = 10;\n\n // Process panels until no more collisions\n // 衝突がなくなるまでパネルを処理\n while (queue.length > 0) {\n const current = queue.shift()!;\n\n // Safety check: prevent infinite loops\n // 安全チェック: 無限ループを防止\n const count = processCount.get(current.id) || 0;\n if (count >= MAX_PROCESS_COUNT) continue;\n processCount.set(current.id, count + 1);\n\n // Skip if the position in queue doesn't match current position in map (outdated entry)\n // キューの位置がマップの現在位置と一致しない場合はスキップ(古いエントリ)\n const currentInMap = panelMap.get(current.id);\n if (currentInMap && (currentInMap.x !== current.x || currentInMap.y !== current.y)) {\n continue;\n }\n\n // Skip if already processed at this position\n // この位置で既に処理済みの場合はスキップ\n if (processed.has(current.id)) {\n continue;\n }\n\n // Detect collisions with current panel position\n // 現在のパネル位置での衝突を検出\n const collidingIds = detectCollisions(current, panelMap);\n\n if (collidingIds.length === 0) {\n panelMap.set(current.id, current);\n processed.add(current.id);\n continue;\n }\n\n // Sort colliding panels by position (top-left to bottom-right) for consistent processing\n // 一貫した処理のため、衝突パネルを位置順(左上から右下)にソート\n const sortedCollidingIds = collidingIds.sort((a, b) => {\n const panelA = panelMap.get(a)!;\n const panelB = panelMap.get(b)!;\n if (panelA.y !== panelB.y) return panelA.y - panelB.y;\n return panelA.x - panelB.x;\n });\n\n // Resolve collisions by pushing colliding panels\n // 衝突したパネルを押しのけて衝突を解決\n for (const collidingId of sortedCollidingIds) {\n const colliding = panelMap.get(collidingId);\n if (!colliding) continue;\n\n // Skip locked panels — they cannot be pushed\n // 位置がロックされたパネルはスキップ — 押しのけられない\n if (colliding.lockPosition) continue;\n\n // Calculate new position for the colliding panel\n // 衝突パネルの新しい位置を計算\n const newPos = findNewPosition(colliding, current, columnCount);\n let candidate = { ...colliding, x: newPos.x, y: newPos.y };\n\n // Check if candidate overlaps with any previously repositioned panel\n // Re-validate after each adjustment to catch multi-overlap scenarios\n // 候補位置が既に再配置されたパネルと重なる場合、さらに右に押す。無理なら下に押す\n // 各調整後に再検証し、複数の重なりシナリオに対応\n let needsPushDown = false;\n let adjusted = true;\n let adjustAttempts = 0;\n while (adjusted && !needsPushDown && adjustAttempts < repositioned.size + 1) {\n adjusted = false;\n adjustAttempts++;\n for (const repoId of repositioned) {\n const repoPanel = panelMap.get(repoId);\n if (!repoPanel || repoId === collidingId) continue;\n if (!rectanglesOverlap(candidate, repoPanel)) continue;\n\n const furtherX = repoPanel.x + repoPanel.w;\n if (furtherX + candidate.w <= columnCount) {\n candidate = { ...candidate, x: furtherX };\n adjusted = true;\n break; // Restart loop to re-validate against all repositioned panels\n } else {\n needsPushDown = true;\n break;\n }\n }\n }\n\n if (needsPushDown) {\n const pushDown = current.y + current.h - colliding.y;\n candidate = { ...candidate, x: colliding.x, y: colliding.y + (pushDown > 0 ? pushDown : 1) };\n }\n\n // Update panel map and add to queue for further processing\n // パネルマップを更新し、さらなる処理のためキューに追加\n panelMap.set(collidingId, candidate);\n queue.push(candidate);\n repositioned.add(collidingId);\n }\n\n panelMap.set(current.id, current);\n processed.add(current.id);\n }\n\n // Return the rearranged panels\n // 再配置後のパネルを返す\n return Array.from(panelMap.values());\n}\n\n/**\n * Find a new position for a panel to be added\n * 追加するパネルの新しい位置を見つける\n */\nexport function findNewPositionToAddPanel(\n panelToAdd: Partial<PanelCoordinate>,\n allPanels: PanelCoordinate[],\n columnCount: number\n): { x: number; y: number } {\n const id = panelToAdd.id || Math.random().toString(36).substring(2, 15);\n const w = panelToAdd.w || 1;\n const h = panelToAdd.h || 1;\n\n // Create a map for fast panel lookup\n const panelMap = new Map<PanelId, PanelCoordinate>();\n for (const panel of allPanels) {\n panelMap.set(panel.id, panel);\n }\n\n // Calculate maximum row based on existing panels\n // Add some buffer rows to ensure we can find a position\n const maxExistingY = allPanels.length > 0 ? Math.max(...allPanels.map((p) => p.y + p.h)) : 0;\n const MAX_ROWS = Math.max(maxExistingY + 100, 1000);\n\n // Try to find a position starting from top-left\n for (let y = 0; y < MAX_ROWS; y++) {\n for (let x = 0; x <= columnCount - w; x++) {\n const candidate: PanelCoordinate = {\n id,\n x,\n y,\n w,\n h,\n };\n\n // Check if this position has any collisions\n if (!hasCollision(candidate, candidate.id, panelMap)) {\n return { x, y };\n }\n }\n }\n\n // Fallback: if no position found, place at the bottom\n return { x: 0, y: maxExistingY };\n}\n"],"mappings":";;;;;;AAMA,SAAgB,kBACd,GACA,GACS;AACT,QAAO,EAEH,EAAE,IAAI,EAAE,KAAK,EAAE,KACf,EAAE,IAAI,EAAE,KAAK,EAAE,KACf,EAAE,IAAI,EAAE,KAAK,EAAE,KACf,EAAE,IAAI,EAAE,KAAK,EAAE;;;;;;AASrB,SAAgB,iBAAiB,OAAwB,UAAoD;CAC3G,MAAM,6BAAa,IAAI,KAAc;AAErC,MAAK,MAAM,CAAC,IAAI,UAAU,UAAU;AAClC,MAAI,OAAO,MAAM,GAAI;AAErB,MAAI,kBAAkB,OAAO,MAAM,CACjC,YAAW,IAAI,GAAG;;AAItB,QAAO,MAAM,KAAK,WAAW;;;;;;AAO/B,SAAgB,aACd,WACA,WACA,UACS;AACT,MAAK,MAAM,CAAC,IAAI,UAAU,UAAU;AAClC,MAAI,OAAO,UAAW;AACtB,MAAI,kBAAkB,WAAW,MAAM,CACrC,QAAO;;AAGX,QAAO;;;;;;AAOT,SAAS,sBACP,QACA,QACA,aAC0D;CAG1D,MAAM,YAAY,OAAO,IAAI,OAAO,IAAI,OAAO;CAC/C,MAAM,eAAe,OAAO,IAAI,OAAO,IAAI,aAAa;CAIxD,MAAM,WAAW,OAAO,IAAI,OAAO,IAAI,OAAO;AAI9C,KAAI,gBAAgB,YAAY,EAC9B,QAAO;EAAE,WAAW;EAAS,UAAU;EAAW;AAKpD,KAAI,WAAW,EACb,QAAO;EAAE,WAAW;EAAQ,UAAU;EAAU;AAGlD,QAAO;;;;;;;;AAST,SAAgB,gBACd,OACA,QACA,aAC0B;CAC1B,MAAM,WAAW,sBAAsB,QAAQ,OAAO,YAAY;AAElE,KAAI,CAAC,SAGH,QAAO;EAAE,GAAG,MAAM;EAAG,GAAG,MAAM,IAAI;EAAG;AAGvC,KAAI,SAAS,cAAc,SAAS;EAGlC,MAAM,OAAO,MAAM,IAAI,SAAS;AAChC,MAAI,OAAO,MAAM,KAAK,YACpB,QAAO;GAAE,GAAG;GAAM,GAAG,MAAM;GAAG;;AAQlC,QAAO;EAAE,GAAG,MAAM;EAAG,GAAG,MAAM,IAAI,SAAS;EAAU;;;;;;AAOvD,SAAS,gBAAgB,OAAwB,aAAsC;CAGrF,MAAM,OAAO,KAAK,IAAI,GAAG,cAAc,MAAM,EAAE;CAC/C,MAAM,eAAe,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,GAAG,KAAK,CAAC;CAIzD,MAAM,eAAe,KAAK,IAAI,GAAG,MAAM,EAAE;AAEzC,QAAO;EACL,GAAG;EACH,GAAG;EACH,GAAG;EACJ;;;;;;;;;;AAWH,SAAgB,gBACd,aACA,WACA,aACmB;CAGnB,MAAM,yBAAyB,gBAAgB,aAAa,YAAY;CAIxE,MAAM,gBAAgB,UAAU,MAAM,MAAM,EAAE,OAAO,YAAY,GAAG;AACpE,KAAI,eAAe;EACjB,MAAM,eAAe,cAAc,MAAM,uBAAuB;EAChE,MAAM,gBAAgB,cAAc,MAAM,uBAAuB;AAEjE,MAAI,gBAAgB,eAAe;GAUjC,MAAM,mBAAmB,wBACvB,wBALqB;IACrB,GAAG;IACH,GAAG,cAAc;IAClB,EAEyC,WAAW,YAAY,EAC/D,UACD;AAGD,OAAI,qBAAqB,UAAW,QAAO;AAU3C,UAAO,wBACL,wBAPyB;IACzB,GAAG;IAEH,GAAG,iBAAiB,MAAM,MAAM,EAAE,OAAO,YAAY,GAAG,EAAE,KAAK,uBAAuB;IACtF,GAAG,iBAAiB,MAAM,MAAM,EAAE,OAAO,YAAY,GAAG,EAAE,KAAK,uBAAuB;IACvF,EAE6C,kBAAkB,YAAY,EAC1E,UACD;;;AAML,QAAO,wBAAwB,wBAAwB,wBAAwB,WAAW,YAAY,EAAE,UAAU;;;;;;;;;;;AAYpH,SAAgB,wBAAwB,QAA2B,UAAgD;CACjH,MAAM,eAAe,OAAO,QAAQ,MAAM,EAAE,aAAa;AACzD,KAAI,aAAa,WAAW,EAAG,QAAO;AAEtC,MAAK,MAAM,UAAU,aACnB,MAAK,MAAM,SAAS,QAAQ;AAC1B,MAAI,MAAM,OAAO,OAAO,GAAI;AAC5B,MAAI,kBAAkB,QAAQ,MAAM,CAGlC,QAAO;;AAKb,QAAO;;;;;;AAOT,SAAS,wBACP,aACA,WACA,aACmB;CAGnB,MAAM,2BAAW,IAAI,KAA+B;AACpD,MAAK,MAAM,SAAS,UAClB,UAAS,IAAI,MAAM,IAAI,EAAE,GAAG,OAAO,CAAC;AAKtC,UAAS,IAAI,YAAY,IAAI,EAAE,GAAG,aAAa,CAAC;CAIhD,MAAMA,QAA2B,CAAC,EAAE,GAAG,aAAa,CAAC;CAIrD,MAAM,4BAAY,IAAI,KAAc;CAIpC,MAAM,+BAAe,IAAI,KAAc;CAIvC,MAAM,+BAAe,IAAI,KAAsB;CAC/C,MAAM,oBAAoB;AAI1B,QAAO,MAAM,SAAS,GAAG;EACvB,MAAM,UAAU,MAAM,OAAO;EAI7B,MAAM,QAAQ,aAAa,IAAI,QAAQ,GAAG,IAAI;AAC9C,MAAI,SAAS,kBAAmB;AAChC,eAAa,IAAI,QAAQ,IAAI,QAAQ,EAAE;EAIvC,MAAM,eAAe,SAAS,IAAI,QAAQ,GAAG;AAC7C,MAAI,iBAAiB,aAAa,MAAM,QAAQ,KAAK,aAAa,MAAM,QAAQ,GAC9E;AAKF,MAAI,UAAU,IAAI,QAAQ,GAAG,CAC3B;EAKF,MAAM,eAAe,iBAAiB,SAAS,SAAS;AAExD,MAAI,aAAa,WAAW,GAAG;AAC7B,YAAS,IAAI,QAAQ,IAAI,QAAQ;AACjC,aAAU,IAAI,QAAQ,GAAG;AACzB;;EAKF,MAAM,qBAAqB,aAAa,MAAM,GAAG,MAAM;GACrD,MAAM,SAAS,SAAS,IAAI,EAAE;GAC9B,MAAM,SAAS,SAAS,IAAI,EAAE;AAC9B,OAAI,OAAO,MAAM,OAAO,EAAG,QAAO,OAAO,IAAI,OAAO;AACpD,UAAO,OAAO,IAAI,OAAO;IACzB;AAIF,OAAK,MAAM,eAAe,oBAAoB;GAC5C,MAAM,YAAY,SAAS,IAAI,YAAY;AAC3C,OAAI,CAAC,UAAW;AAIhB,OAAI,UAAU,aAAc;GAI5B,MAAM,SAAS,gBAAgB,WAAW,SAAS,YAAY;GAC/D,IAAI,YAAY;IAAE,GAAG;IAAW,GAAG,OAAO;IAAG,GAAG,OAAO;IAAG;GAM1D,IAAI,gBAAgB;GACpB,IAAI,WAAW;GACf,IAAI,iBAAiB;AACrB,UAAO,YAAY,CAAC,iBAAiB,iBAAiB,aAAa,OAAO,GAAG;AAC3E,eAAW;AACX;AACA,SAAK,MAAM,UAAU,cAAc;KACjC,MAAM,YAAY,SAAS,IAAI,OAAO;AACtC,SAAI,CAAC,aAAa,WAAW,YAAa;AAC1C,SAAI,CAAC,kBAAkB,WAAW,UAAU,CAAE;KAE9C,MAAM,WAAW,UAAU,IAAI,UAAU;AACzC,SAAI,WAAW,UAAU,KAAK,aAAa;AACzC,kBAAY;OAAE,GAAG;OAAW,GAAG;OAAU;AACzC,iBAAW;AACX;YACK;AACL,sBAAgB;AAChB;;;;AAKN,OAAI,eAAe;IACjB,MAAM,WAAW,QAAQ,IAAI,QAAQ,IAAI,UAAU;AACnD,gBAAY;KAAE,GAAG;KAAW,GAAG,UAAU;KAAG,GAAG,UAAU,KAAK,WAAW,IAAI,WAAW;KAAI;;AAK9F,YAAS,IAAI,aAAa,UAAU;AACpC,SAAM,KAAK,UAAU;AACrB,gBAAa,IAAI,YAAY;;AAG/B,WAAS,IAAI,QAAQ,IAAI,QAAQ;AACjC,YAAU,IAAI,QAAQ,GAAG;;AAK3B,QAAO,MAAM,KAAK,SAAS,QAAQ,CAAC;;;;;;AAOtC,SAAgB,0BACd,YACA,WACA,aAC0B;CAC1B,MAAM,KAAK,WAAW,MAAM,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,UAAU,GAAG,GAAG;CACvE,MAAM,IAAI,WAAW,KAAK;CAC1B,MAAM,IAAI,WAAW,KAAK;CAG1B,MAAM,2BAAW,IAAI,KAA+B;AACpD,MAAK,MAAM,SAAS,UAClB,UAAS,IAAI,MAAM,IAAI,MAAM;CAK/B,MAAM,eAAe,UAAU,SAAS,IAAI,KAAK,IAAI,GAAG,UAAU,KAAK,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG;CAC3F,MAAM,WAAW,KAAK,IAAI,eAAe,KAAK,IAAK;AAGnD,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,IAC5B,MAAK,IAAI,IAAI,GAAG,KAAK,cAAc,GAAG,KAAK;EACzC,MAAMC,YAA6B;GACjC;GACA;GACA;GACA;GACA;GACD;AAGD,MAAI,CAAC,aAAa,WAAW,UAAU,IAAI,SAAS,CAClD,QAAO;GAAE;GAAG;GAAG;;AAMrB,QAAO;EAAE,GAAG;EAAG,GAAG;EAAc"}
|
|
@@ -51,6 +51,16 @@ declare function findNewPosition(panel: PanelCoordinate, pusher: PanelCoordinate
|
|
|
51
51
|
* 幅と高さが同時に変更される場合は、二段階処理を使用
|
|
52
52
|
*/
|
|
53
53
|
declare function rearrangePanels(movingPanel: PanelCoordinate, allPanels: PanelCoordinate[], columnCount: number): PanelCoordinate[];
|
|
54
|
+
/**
|
|
55
|
+
* Check if any non-locked panel in the result overlaps with a locked panel.
|
|
56
|
+
* If so, return the original positions (rollback).
|
|
57
|
+
*
|
|
58
|
+
* Exported so custom RearrangementFunction implementations can compose with it:
|
|
59
|
+
* return resolveWithLockRollback(myCustomResult, allPanels);
|
|
60
|
+
*
|
|
61
|
+
* ロックされたパネルに重なりが生じた場合、元の位置に戻す(ロールバック)
|
|
62
|
+
*/
|
|
63
|
+
declare function resolveWithLockRollback(result: PanelCoordinate[], original: PanelCoordinate[]): PanelCoordinate[];
|
|
54
64
|
//#endregion
|
|
55
|
-
export { detectCollisions, findNewPosition, hasCollision, rearrangePanels, rectanglesOverlap };
|
|
65
|
+
export { detectCollisions, findNewPosition, hasCollision, rearrangePanels, rectanglesOverlap, resolveWithLockRollback };
|
|
56
66
|
//# sourceMappingURL=rearrangement.d.cts.map
|
|
@@ -51,6 +51,16 @@ declare function findNewPosition(panel: PanelCoordinate, pusher: PanelCoordinate
|
|
|
51
51
|
* 幅と高さが同時に変更される場合は、二段階処理を使用
|
|
52
52
|
*/
|
|
53
53
|
declare function rearrangePanels(movingPanel: PanelCoordinate, allPanels: PanelCoordinate[], columnCount: number): PanelCoordinate[];
|
|
54
|
+
/**
|
|
55
|
+
* Check if any non-locked panel in the result overlaps with a locked panel.
|
|
56
|
+
* If so, return the original positions (rollback).
|
|
57
|
+
*
|
|
58
|
+
* Exported so custom RearrangementFunction implementations can compose with it:
|
|
59
|
+
* return resolveWithLockRollback(myCustomResult, allPanels);
|
|
60
|
+
*
|
|
61
|
+
* ロックされたパネルに重なりが生じた場合、元の位置に戻す(ロールバック)
|
|
62
|
+
*/
|
|
63
|
+
declare function resolveWithLockRollback(result: PanelCoordinate[], original: PanelCoordinate[]): PanelCoordinate[];
|
|
54
64
|
//#endregion
|
|
55
|
-
export { detectCollisions, findNewPosition, hasCollision, rearrangePanels, rectanglesOverlap };
|
|
65
|
+
export { detectCollisions, findNewPosition, hasCollision, rearrangePanels, rectanglesOverlap, resolveWithLockRollback };
|
|
56
66
|
//# sourceMappingURL=rearrangement.d.mts.map
|
|
@@ -100,18 +100,37 @@ function rearrangePanels(movingPanel, allPanels, columnCount) {
|
|
|
100
100
|
const widthChanged = originalPanel.w !== constrainedMovingPanel.w;
|
|
101
101
|
const heightChanged = originalPanel.h !== constrainedMovingPanel.h;
|
|
102
102
|
if (widthChanged && heightChanged) {
|
|
103
|
-
const afterWidthChange = rearrangePanelsInternal({
|
|
103
|
+
const afterWidthChange = resolveWithLockRollback(rearrangePanelsInternal({
|
|
104
104
|
...constrainedMovingPanel,
|
|
105
105
|
h: originalPanel.h
|
|
106
|
-
}, allPanels, columnCount);
|
|
107
|
-
return
|
|
106
|
+
}, allPanels, columnCount), allPanels);
|
|
107
|
+
if (afterWidthChange === allPanels) return allPanels;
|
|
108
|
+
return resolveWithLockRollback(rearrangePanelsInternal({
|
|
108
109
|
...constrainedMovingPanel,
|
|
109
110
|
x: afterWidthChange.find((p) => p.id === movingPanel.id)?.x ?? constrainedMovingPanel.x,
|
|
110
111
|
y: afterWidthChange.find((p) => p.id === movingPanel.id)?.y ?? constrainedMovingPanel.y
|
|
111
|
-
}, afterWidthChange, columnCount);
|
|
112
|
+
}, afterWidthChange, columnCount), allPanels);
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
|
-
return rearrangePanelsInternal(constrainedMovingPanel, allPanels, columnCount);
|
|
115
|
+
return resolveWithLockRollback(rearrangePanelsInternal(constrainedMovingPanel, allPanels, columnCount), allPanels);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check if any non-locked panel in the result overlaps with a locked panel.
|
|
119
|
+
* If so, return the original positions (rollback).
|
|
120
|
+
*
|
|
121
|
+
* Exported so custom RearrangementFunction implementations can compose with it:
|
|
122
|
+
* return resolveWithLockRollback(myCustomResult, allPanels);
|
|
123
|
+
*
|
|
124
|
+
* ロックされたパネルに重なりが生じた場合、元の位置に戻す(ロールバック)
|
|
125
|
+
*/
|
|
126
|
+
function resolveWithLockRollback(result, original) {
|
|
127
|
+
const lockedPanels = result.filter((p) => p.lockPosition);
|
|
128
|
+
if (lockedPanels.length === 0) return result;
|
|
129
|
+
for (const locked of lockedPanels) for (const panel of result) {
|
|
130
|
+
if (panel.id === locked.id) continue;
|
|
131
|
+
if (rectanglesOverlap(locked, panel)) return original;
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
115
134
|
}
|
|
116
135
|
/**
|
|
117
136
|
* Internal implementation of panel rearrangement
|
|
@@ -149,6 +168,7 @@ function rearrangePanelsInternal(movingPanel, allPanels, columnCount) {
|
|
|
149
168
|
for (const collidingId of sortedCollidingIds) {
|
|
150
169
|
const colliding = panelMap.get(collidingId);
|
|
151
170
|
if (!colliding) continue;
|
|
171
|
+
if (colliding.lockPosition) continue;
|
|
152
172
|
const newPos = findNewPosition(colliding, current, columnCount);
|
|
153
173
|
let candidate = {
|
|
154
174
|
...colliding,
|
|
@@ -228,5 +248,5 @@ function findNewPositionToAddPanel(panelToAdd, allPanels, columnCount) {
|
|
|
228
248
|
}
|
|
229
249
|
|
|
230
250
|
//#endregion
|
|
231
|
-
export { detectCollisions, findNewPosition, findNewPositionToAddPanel, hasCollision, rearrangePanels, rectanglesOverlap };
|
|
251
|
+
export { detectCollisions, findNewPosition, findNewPositionToAddPanel, hasCollision, rearrangePanels, rectanglesOverlap, resolveWithLockRollback };
|
|
232
252
|
//# sourceMappingURL=rearrangement.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rearrangement.mjs","names":["queue: PanelCoordinate[]","candidate: PanelCoordinate"],"sources":["../../src/helpers/rearrangement.ts"],"sourcesContent":["import type { PanelCoordinate, PanelId } from \"../types\";\n\n/**\n * Check if two rectangles overlap using AABB (Axis-Aligned Bounding Box) test\n * 2つの矩形が重なっているかをAABBテストで判定\n */\nexport function rectanglesOverlap(\n a: { x: number; y: number; w: number; h: number },\n b: { x: number; y: number; w: number; h: number }\n): boolean {\n return !(\n (\n a.x + a.w <= b.x || // a is to the left of b\n b.x + b.w <= a.x || // b is to the left of a\n a.y + a.h <= b.y || // a is above b\n b.y + b.h <= a.y\n ) // b is above a\n );\n}\n\n/**\n * Detect all panels that collide with the given panel\n * 指定されたパネルと衝突する全てのパネルを検出\n */\nexport function detectCollisions(panel: PanelCoordinate, panelMap: Map<PanelId, PanelCoordinate>): PanelId[] {\n const collisions = new Set<PanelId>();\n\n for (const [id, other] of panelMap) {\n if (id === panel.id) continue;\n\n if (rectanglesOverlap(panel, other)) {\n collisions.add(id);\n }\n }\n\n return Array.from(collisions);\n}\n\n/**\n * Check if a panel at the given position would collide with any existing panels\n * 指定された位置にパネルを配置した場合に衝突があるかをチェック\n */\nexport function hasCollision(\n candidate: { x: number; y: number; w: number; h: number },\n excludeId: PanelId,\n panelMap: Map<PanelId, PanelCoordinate>\n): boolean {\n for (const [id, panel] of panelMap) {\n if (id === excludeId) continue;\n if (rectanglesOverlap(candidate, panel)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Calculate the minimum distance to push a panel to avoid collision\n * パネルを押しのけるための最小距離を計算\n */\nfunction calculatePushDistance(\n pusher: PanelCoordinate,\n pushed: PanelCoordinate,\n columnCount: number\n): { direction: \"right\" | \"down\"; distance: number } | null {\n // Calculate how far to push horizontally (to the right)\n // 横方向(右)にどれだけ押すか計算\n const pushRight = pusher.x + pusher.w - pushed.x;\n const canPushRight = pushed.x + pushed.w + pushRight <= columnCount;\n\n // Calculate how far to push vertically (down)\n // 縦方向(下)にどれだけ押すか計算\n const pushDown = pusher.y + pusher.h - pushed.y;\n\n // Priority 1: Horizontal push if possible\n // 優先順位1: 可能なら横方向に押す\n if (canPushRight && pushRight > 0) {\n return { direction: \"right\", distance: pushRight };\n }\n\n // Priority 2: Vertical push\n // 優先順位2: 縦方向に押す\n if (pushDown > 0) {\n return { direction: \"down\", distance: pushDown };\n }\n\n return null;\n}\n\n/**\n * Find a new position for a panel by pushing it away from the colliding panel\n * Priority: horizontal (right) first, then vertical (down)\n * 衝突したパネルを押しのける方向に移動させる\n * 優先順位: 横方向(右)→縦方向(下)\n */\nexport function findNewPosition(\n panel: PanelCoordinate,\n pusher: PanelCoordinate,\n columnCount: number\n): { x: number; y: number } {\n const pushInfo = calculatePushDistance(pusher, panel, columnCount);\n\n if (!pushInfo) {\n // No push needed or can't determine direction\n // Fallback: move down\n return { x: panel.x, y: panel.y + 1 };\n }\n\n if (pushInfo.direction === \"right\") {\n // Push horizontally to the right\n // 横方向(右)に押す\n const newX = panel.x + pushInfo.distance;\n if (newX + panel.w <= columnCount) {\n return { x: newX, y: panel.y };\n }\n // Can't fit horizontally, push down instead\n // 横方向に入らない場合は下に押す\n }\n\n // Push vertically down\n // 縦方向(下)に押す\n return { x: panel.x, y: panel.y + pushInfo.distance };\n}\n\n/**\n * Constrain a panel to stay within grid boundaries\n * パネルをグリッド境界内に制約\n */\nfunction constrainToGrid(panel: PanelCoordinate, columnCount: number): PanelCoordinate {\n // Ensure x + w doesn't exceed columnCount\n // x + w が columnCount を超えないようにする\n const maxX = Math.max(0, columnCount - panel.w);\n const constrainedX = Math.max(0, Math.min(panel.x, maxX));\n\n // Ensure y is non-negative\n // y が負にならないようにする\n const constrainedY = Math.max(0, panel.y);\n\n return {\n ...panel,\n x: constrainedX,\n y: constrainedY,\n };\n}\n\n/**\n * Rearrange panels to resolve collisions when a panel is moved or resized\n * Panels are moved horizontally first, then vertically if needed\n * For compound resizes (both width and height change), uses two-phase processing\n * パネルの移動・リサイズ時に衝突を解決するようにパネルを再配置\n * 横方向を優先し、必要に応じて縦方向に移動\n * 幅と高さが同時に変更される場合は、二段階処理を使用\n */\nexport function rearrangePanels(\n movingPanel: PanelCoordinate,\n allPanels: PanelCoordinate[],\n columnCount: number\n): PanelCoordinate[] {\n // Constrain the moving panel to grid boundaries\n // 移動中のパネルをグリッド境界内に制約\n const constrainedMovingPanel = constrainToGrid(movingPanel, columnCount);\n\n // Check if this is a compound resize (both width and height changed)\n // 幅と高さの両方が変更されたかチェック\n const originalPanel = allPanels.find((p) => p.id === movingPanel.id);\n if (originalPanel) {\n const widthChanged = originalPanel.w !== constrainedMovingPanel.w;\n const heightChanged = originalPanel.h !== constrainedMovingPanel.h;\n\n if (widthChanged && heightChanged) {\n // Two-phase processing: width first, then height\n // 二段階処理: 幅を先に、次に高さ\n\n // Phase 1: Apply width change only\n // フェーズ1: 幅の変更のみを適用\n const widthOnlyPanel = {\n ...constrainedMovingPanel,\n h: originalPanel.h, // Keep original height\n };\n const afterWidthChange = rearrangePanelsInternal(widthOnlyPanel, allPanels, columnCount);\n\n // Phase 2: Apply height change to the result\n // フェーズ2: 結果に高さの変更を適用\n const heightChangedPanel = {\n ...constrainedMovingPanel,\n // Use the new position if panel-1 was moved during width phase\n x: afterWidthChange.find((p) => p.id === movingPanel.id)?.x ?? constrainedMovingPanel.x,\n y: afterWidthChange.find((p) => p.id === movingPanel.id)?.y ?? constrainedMovingPanel.y,\n };\n return rearrangePanelsInternal(heightChangedPanel, afterWidthChange, columnCount);\n }\n }\n\n // Single dimension change or move - use normal processing\n // 単一次元の変更または移動 - 通常の処理を使用\n return rearrangePanelsInternal(constrainedMovingPanel, allPanels, columnCount);\n}\n\n/**\n * Internal implementation of panel rearrangement\n * パネル再配置の内部実装\n */\nfunction rearrangePanelsInternal(\n movingPanel: PanelCoordinate,\n allPanels: PanelCoordinate[],\n columnCount: number\n): PanelCoordinate[] {\n // Create a map for fast panel lookup\n // パネルIDから座標への高速マップを作成\n const panelMap = new Map<PanelId, PanelCoordinate>();\n for (const panel of allPanels) {\n panelMap.set(panel.id, { ...panel });\n }\n\n // Update the moving panel's position in the map\n // 移動中のパネルの位置をマップに反映\n panelMap.set(movingPanel.id, { ...movingPanel });\n\n // Queue for processing panels that need to be repositioned\n // 再配置が必要なパネルの処理キュー\n const queue: PanelCoordinate[] = [{ ...movingPanel }];\n\n // Track processed panels to avoid reprocessing unnecessarily\n // 不要な再処理を避けるため処理済みパネルを追跡\n const processed = new Set<PanelId>();\n\n // Track all panels that have been repositioned across all queue iterations\n // 全キュー反復を通じて再配置されたパネルを追跡\n const repositioned = new Set<PanelId>();\n\n // Track processing count to prevent infinite loops\n // 無限ループを防ぐため処理回数を追跡\n const processCount = new Map<PanelId, number>();\n const MAX_PROCESS_COUNT = 10;\n\n // Process panels until no more collisions\n // 衝突がなくなるまでパネルを処理\n while (queue.length > 0) {\n const current = queue.shift()!;\n\n // Safety check: prevent infinite loops\n // 安全チェック: 無限ループを防止\n const count = processCount.get(current.id) || 0;\n if (count >= MAX_PROCESS_COUNT) continue;\n processCount.set(current.id, count + 1);\n\n // Skip if the position in queue doesn't match current position in map (outdated entry)\n // キューの位置がマップの現在位置と一致しない場合はスキップ(古いエントリ)\n const currentInMap = panelMap.get(current.id);\n if (currentInMap && (currentInMap.x !== current.x || currentInMap.y !== current.y)) {\n continue;\n }\n\n // Skip if already processed at this position\n // この位置で既に処理済みの場合はスキップ\n if (processed.has(current.id)) {\n continue;\n }\n\n // Detect collisions with current panel position\n // 現在のパネル位置での衝突を検出\n const collidingIds = detectCollisions(current, panelMap);\n\n if (collidingIds.length === 0) {\n panelMap.set(current.id, current);\n processed.add(current.id);\n continue;\n }\n\n // Sort colliding panels by position (top-left to bottom-right) for consistent processing\n // 一貫した処理のため、衝突パネルを位置順(左上から右下)にソート\n const sortedCollidingIds = collidingIds.sort((a, b) => {\n const panelA = panelMap.get(a)!;\n const panelB = panelMap.get(b)!;\n if (panelA.y !== panelB.y) return panelA.y - panelB.y;\n return panelA.x - panelB.x;\n });\n\n // Resolve collisions by pushing colliding panels\n // 衝突したパネルを押しのけて衝突を解決\n for (const collidingId of sortedCollidingIds) {\n const colliding = panelMap.get(collidingId);\n if (!colliding) continue;\n\n // Calculate new position for the colliding panel\n // 衝突パネルの新しい位置を計算\n const newPos = findNewPosition(colliding, current, columnCount);\n let candidate = { ...colliding, x: newPos.x, y: newPos.y };\n\n // Check if candidate overlaps with any previously repositioned panel\n // Re-validate after each adjustment to catch multi-overlap scenarios\n // 候補位置が既に再配置されたパネルと重なる場合、さらに右に押す。無理なら下に押す\n // 各調整後に再検証し、複数の重なりシナリオに対応\n let needsPushDown = false;\n let adjusted = true;\n let adjustAttempts = 0;\n while (adjusted && !needsPushDown && adjustAttempts < repositioned.size + 1) {\n adjusted = false;\n adjustAttempts++;\n for (const repoId of repositioned) {\n const repoPanel = panelMap.get(repoId);\n if (!repoPanel || repoId === collidingId) continue;\n if (!rectanglesOverlap(candidate, repoPanel)) continue;\n\n const furtherX = repoPanel.x + repoPanel.w;\n if (furtherX + candidate.w <= columnCount) {\n candidate = { ...candidate, x: furtherX };\n adjusted = true;\n break; // Restart loop to re-validate against all repositioned panels\n } else {\n needsPushDown = true;\n break;\n }\n }\n }\n\n if (needsPushDown) {\n const pushDown = current.y + current.h - colliding.y;\n candidate = { ...candidate, x: colliding.x, y: colliding.y + (pushDown > 0 ? pushDown : 1) };\n }\n\n // Update panel map and add to queue for further processing\n // パネルマップを更新し、さらなる処理のためキューに追加\n panelMap.set(collidingId, candidate);\n queue.push(candidate);\n repositioned.add(collidingId);\n }\n\n panelMap.set(current.id, current);\n processed.add(current.id);\n }\n\n // Return the rearranged panels\n // 再配置後のパネルを返す\n return Array.from(panelMap.values());\n}\n\n/**\n * Find a new position for a panel to be added\n * 追加するパネルの新しい位置を見つける\n */\nexport function findNewPositionToAddPanel(\n panelToAdd: Partial<PanelCoordinate>,\n allPanels: PanelCoordinate[],\n columnCount: number\n): { x: number; y: number } {\n const id = panelToAdd.id || Math.random().toString(36).substring(2, 15);\n const w = panelToAdd.w || 1;\n const h = panelToAdd.h || 1;\n\n // Create a map for fast panel lookup\n const panelMap = new Map<PanelId, PanelCoordinate>();\n for (const panel of allPanels) {\n panelMap.set(panel.id, panel);\n }\n\n // Calculate maximum row based on existing panels\n // Add some buffer rows to ensure we can find a position\n const maxExistingY = allPanels.length > 0 ? Math.max(...allPanels.map((p) => p.y + p.h)) : 0;\n const MAX_ROWS = Math.max(maxExistingY + 100, 1000);\n\n // Try to find a position starting from top-left\n for (let y = 0; y < MAX_ROWS; y++) {\n for (let x = 0; x <= columnCount - w; x++) {\n const candidate: PanelCoordinate = {\n id,\n x,\n y,\n w,\n h,\n };\n\n // Check if this position has any collisions\n if (!hasCollision(candidate, candidate.id, panelMap)) {\n return { x, y };\n }\n }\n }\n\n // Fallback: if no position found, place at the bottom\n return { x: 0, y: maxExistingY };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rearrangement.mjs","names":["queue: PanelCoordinate[]","candidate: PanelCoordinate"],"sources":["../../src/helpers/rearrangement.ts"],"sourcesContent":["import type { PanelCoordinate, PanelId } from \"../types\";\n\n/**\n * Check if two rectangles overlap using AABB (Axis-Aligned Bounding Box) test\n * 2つの矩形が重なっているかをAABBテストで判定\n */\nexport function rectanglesOverlap(\n a: { x: number; y: number; w: number; h: number },\n b: { x: number; y: number; w: number; h: number }\n): boolean {\n return !(\n (\n a.x + a.w <= b.x || // a is to the left of b\n b.x + b.w <= a.x || // b is to the left of a\n a.y + a.h <= b.y || // a is above b\n b.y + b.h <= a.y\n ) // b is above a\n );\n}\n\n/**\n * Detect all panels that collide with the given panel\n * 指定されたパネルと衝突する全てのパネルを検出\n */\nexport function detectCollisions(panel: PanelCoordinate, panelMap: Map<PanelId, PanelCoordinate>): PanelId[] {\n const collisions = new Set<PanelId>();\n\n for (const [id, other] of panelMap) {\n if (id === panel.id) continue;\n\n if (rectanglesOverlap(panel, other)) {\n collisions.add(id);\n }\n }\n\n return Array.from(collisions);\n}\n\n/**\n * Check if a panel at the given position would collide with any existing panels\n * 指定された位置にパネルを配置した場合に衝突があるかをチェック\n */\nexport function hasCollision(\n candidate: { x: number; y: number; w: number; h: number },\n excludeId: PanelId,\n panelMap: Map<PanelId, PanelCoordinate>\n): boolean {\n for (const [id, panel] of panelMap) {\n if (id === excludeId) continue;\n if (rectanglesOverlap(candidate, panel)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Calculate the minimum distance to push a panel to avoid collision\n * パネルを押しのけるための最小距離を計算\n */\nfunction calculatePushDistance(\n pusher: PanelCoordinate,\n pushed: PanelCoordinate,\n columnCount: number\n): { direction: \"right\" | \"down\"; distance: number } | null {\n // Calculate how far to push horizontally (to the right)\n // 横方向(右)にどれだけ押すか計算\n const pushRight = pusher.x + pusher.w - pushed.x;\n const canPushRight = pushed.x + pushed.w + pushRight <= columnCount;\n\n // Calculate how far to push vertically (down)\n // 縦方向(下)にどれだけ押すか計算\n const pushDown = pusher.y + pusher.h - pushed.y;\n\n // Priority 1: Horizontal push if possible\n // 優先順位1: 可能なら横方向に押す\n if (canPushRight && pushRight > 0) {\n return { direction: \"right\", distance: pushRight };\n }\n\n // Priority 2: Vertical push\n // 優先順位2: 縦方向に押す\n if (pushDown > 0) {\n return { direction: \"down\", distance: pushDown };\n }\n\n return null;\n}\n\n/**\n * Find a new position for a panel by pushing it away from the colliding panel\n * Priority: horizontal (right) first, then vertical (down)\n * 衝突したパネルを押しのける方向に移動させる\n * 優先順位: 横方向(右)→縦方向(下)\n */\nexport function findNewPosition(\n panel: PanelCoordinate,\n pusher: PanelCoordinate,\n columnCount: number\n): { x: number; y: number } {\n const pushInfo = calculatePushDistance(pusher, panel, columnCount);\n\n if (!pushInfo) {\n // No push needed or can't determine direction\n // Fallback: move down\n return { x: panel.x, y: panel.y + 1 };\n }\n\n if (pushInfo.direction === \"right\") {\n // Push horizontally to the right\n // 横方向(右)に押す\n const newX = panel.x + pushInfo.distance;\n if (newX + panel.w <= columnCount) {\n return { x: newX, y: panel.y };\n }\n // Can't fit horizontally, push down instead\n // 横方向に入らない場合は下に押す\n }\n\n // Push vertically down\n // 縦方向(下)に押す\n return { x: panel.x, y: panel.y + pushInfo.distance };\n}\n\n/**\n * Constrain a panel to stay within grid boundaries\n * パネルをグリッド境界内に制約\n */\nfunction constrainToGrid(panel: PanelCoordinate, columnCount: number): PanelCoordinate {\n // Ensure x + w doesn't exceed columnCount\n // x + w が columnCount を超えないようにする\n const maxX = Math.max(0, columnCount - panel.w);\n const constrainedX = Math.max(0, Math.min(panel.x, maxX));\n\n // Ensure y is non-negative\n // y が負にならないようにする\n const constrainedY = Math.max(0, panel.y);\n\n return {\n ...panel,\n x: constrainedX,\n y: constrainedY,\n };\n}\n\n/**\n * Rearrange panels to resolve collisions when a panel is moved or resized\n * Panels are moved horizontally first, then vertically if needed\n * For compound resizes (both width and height change), uses two-phase processing\n * パネルの移動・リサイズ時に衝突を解決するようにパネルを再配置\n * 横方向を優先し、必要に応じて縦方向に移動\n * 幅と高さが同時に変更される場合は、二段階処理を使用\n */\nexport function rearrangePanels(\n movingPanel: PanelCoordinate,\n allPanels: PanelCoordinate[],\n columnCount: number\n): PanelCoordinate[] {\n // Constrain the moving panel to grid boundaries\n // 移動中のパネルをグリッド境界内に制約\n const constrainedMovingPanel = constrainToGrid(movingPanel, columnCount);\n\n // Check if this is a compound resize (both width and height changed)\n // 幅と高さの両方が変更されたかチェック\n const originalPanel = allPanels.find((p) => p.id === movingPanel.id);\n if (originalPanel) {\n const widthChanged = originalPanel.w !== constrainedMovingPanel.w;\n const heightChanged = originalPanel.h !== constrainedMovingPanel.h;\n\n if (widthChanged && heightChanged) {\n // Two-phase processing: width first, then height\n // 二段階処理: 幅を先に、次に高さ\n\n // Phase 1: Apply width change only\n // フェーズ1: 幅の変更のみを適用\n const widthOnlyPanel = {\n ...constrainedMovingPanel,\n h: originalPanel.h, // Keep original height\n };\n const afterWidthChange = resolveWithLockRollback(\n rearrangePanelsInternal(widthOnlyPanel, allPanels, columnCount),\n allPanels\n );\n // If phase 1 already triggered a rollback, propagate it immediately\n // フェーズ1でロールバックが発生した場合は即座に返す\n if (afterWidthChange === allPanels) return allPanels;\n\n // Phase 2: Apply height change to the result\n // フェーズ2: 結果に高さの変更を適用\n const heightChangedPanel = {\n ...constrainedMovingPanel,\n // Use the new position if panel-1 was moved during width phase\n x: afterWidthChange.find((p) => p.id === movingPanel.id)?.x ?? constrainedMovingPanel.x,\n y: afterWidthChange.find((p) => p.id === movingPanel.id)?.y ?? constrainedMovingPanel.y,\n };\n return resolveWithLockRollback(\n rearrangePanelsInternal(heightChangedPanel, afterWidthChange, columnCount),\n allPanels\n );\n }\n }\n\n // Single dimension change or move - use normal processing\n // 単一次元の変更または移動 - 通常の処理を使用\n return resolveWithLockRollback(rearrangePanelsInternal(constrainedMovingPanel, allPanels, columnCount), allPanels);\n}\n\n/**\n * Check if any non-locked panel in the result overlaps with a locked panel.\n * If so, return the original positions (rollback).\n *\n * Exported so custom RearrangementFunction implementations can compose with it:\n * return resolveWithLockRollback(myCustomResult, allPanels);\n *\n * ロックされたパネルに重なりが生じた場合、元の位置に戻す(ロールバック)\n */\nexport function resolveWithLockRollback(result: PanelCoordinate[], original: PanelCoordinate[]): PanelCoordinate[] {\n const lockedPanels = result.filter((p) => p.lockPosition);\n if (lockedPanels.length === 0) return result;\n\n for (const locked of lockedPanels) {\n for (const panel of result) {\n if (panel.id === locked.id) continue;\n if (rectanglesOverlap(locked, panel)) {\n // A non-locked panel overlaps a locked panel — rollback everything\n // ロックされたパネルに重なりが発生 — 全てをロールバック\n return original;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Internal implementation of panel rearrangement\n * パネル再配置の内部実装\n */\nfunction rearrangePanelsInternal(\n movingPanel: PanelCoordinate,\n allPanels: PanelCoordinate[],\n columnCount: number\n): PanelCoordinate[] {\n // Create a map for fast panel lookup\n // パネルIDから座標への高速マップを作成\n const panelMap = new Map<PanelId, PanelCoordinate>();\n for (const panel of allPanels) {\n panelMap.set(panel.id, { ...panel });\n }\n\n // Update the moving panel's position in the map\n // 移動中のパネルの位置をマップに反映\n panelMap.set(movingPanel.id, { ...movingPanel });\n\n // Queue for processing panels that need to be repositioned\n // 再配置が必要なパネルの処理キュー\n const queue: PanelCoordinate[] = [{ ...movingPanel }];\n\n // Track processed panels to avoid reprocessing unnecessarily\n // 不要な再処理を避けるため処理済みパネルを追跡\n const processed = new Set<PanelId>();\n\n // Track all panels that have been repositioned across all queue iterations\n // 全キュー反復を通じて再配置されたパネルを追跡\n const repositioned = new Set<PanelId>();\n\n // Track processing count to prevent infinite loops\n // 無限ループを防ぐため処理回数を追跡\n const processCount = new Map<PanelId, number>();\n const MAX_PROCESS_COUNT = 10;\n\n // Process panels until no more collisions\n // 衝突がなくなるまでパネルを処理\n while (queue.length > 0) {\n const current = queue.shift()!;\n\n // Safety check: prevent infinite loops\n // 安全チェック: 無限ループを防止\n const count = processCount.get(current.id) || 0;\n if (count >= MAX_PROCESS_COUNT) continue;\n processCount.set(current.id, count + 1);\n\n // Skip if the position in queue doesn't match current position in map (outdated entry)\n // キューの位置がマップの現在位置と一致しない場合はスキップ(古いエントリ)\n const currentInMap = panelMap.get(current.id);\n if (currentInMap && (currentInMap.x !== current.x || currentInMap.y !== current.y)) {\n continue;\n }\n\n // Skip if already processed at this position\n // この位置で既に処理済みの場合はスキップ\n if (processed.has(current.id)) {\n continue;\n }\n\n // Detect collisions with current panel position\n // 現在のパネル位置での衝突を検出\n const collidingIds = detectCollisions(current, panelMap);\n\n if (collidingIds.length === 0) {\n panelMap.set(current.id, current);\n processed.add(current.id);\n continue;\n }\n\n // Sort colliding panels by position (top-left to bottom-right) for consistent processing\n // 一貫した処理のため、衝突パネルを位置順(左上から右下)にソート\n const sortedCollidingIds = collidingIds.sort((a, b) => {\n const panelA = panelMap.get(a)!;\n const panelB = panelMap.get(b)!;\n if (panelA.y !== panelB.y) return panelA.y - panelB.y;\n return panelA.x - panelB.x;\n });\n\n // Resolve collisions by pushing colliding panels\n // 衝突したパネルを押しのけて衝突を解決\n for (const collidingId of sortedCollidingIds) {\n const colliding = panelMap.get(collidingId);\n if (!colliding) continue;\n\n // Skip locked panels — they cannot be pushed\n // 位置がロックされたパネルはスキップ — 押しのけられない\n if (colliding.lockPosition) continue;\n\n // Calculate new position for the colliding panel\n // 衝突パネルの新しい位置を計算\n const newPos = findNewPosition(colliding, current, columnCount);\n let candidate = { ...colliding, x: newPos.x, y: newPos.y };\n\n // Check if candidate overlaps with any previously repositioned panel\n // Re-validate after each adjustment to catch multi-overlap scenarios\n // 候補位置が既に再配置されたパネルと重なる場合、さらに右に押す。無理なら下に押す\n // 各調整後に再検証し、複数の重なりシナリオに対応\n let needsPushDown = false;\n let adjusted = true;\n let adjustAttempts = 0;\n while (adjusted && !needsPushDown && adjustAttempts < repositioned.size + 1) {\n adjusted = false;\n adjustAttempts++;\n for (const repoId of repositioned) {\n const repoPanel = panelMap.get(repoId);\n if (!repoPanel || repoId === collidingId) continue;\n if (!rectanglesOverlap(candidate, repoPanel)) continue;\n\n const furtherX = repoPanel.x + repoPanel.w;\n if (furtherX + candidate.w <= columnCount) {\n candidate = { ...candidate, x: furtherX };\n adjusted = true;\n break; // Restart loop to re-validate against all repositioned panels\n } else {\n needsPushDown = true;\n break;\n }\n }\n }\n\n if (needsPushDown) {\n const pushDown = current.y + current.h - colliding.y;\n candidate = { ...candidate, x: colliding.x, y: colliding.y + (pushDown > 0 ? pushDown : 1) };\n }\n\n // Update panel map and add to queue for further processing\n // パネルマップを更新し、さらなる処理のためキューに追加\n panelMap.set(collidingId, candidate);\n queue.push(candidate);\n repositioned.add(collidingId);\n }\n\n panelMap.set(current.id, current);\n processed.add(current.id);\n }\n\n // Return the rearranged panels\n // 再配置後のパネルを返す\n return Array.from(panelMap.values());\n}\n\n/**\n * Find a new position for a panel to be added\n * 追加するパネルの新しい位置を見つける\n */\nexport function findNewPositionToAddPanel(\n panelToAdd: Partial<PanelCoordinate>,\n allPanels: PanelCoordinate[],\n columnCount: number\n): { x: number; y: number } {\n const id = panelToAdd.id || Math.random().toString(36).substring(2, 15);\n const w = panelToAdd.w || 1;\n const h = panelToAdd.h || 1;\n\n // Create a map for fast panel lookup\n const panelMap = new Map<PanelId, PanelCoordinate>();\n for (const panel of allPanels) {\n panelMap.set(panel.id, panel);\n }\n\n // Calculate maximum row based on existing panels\n // Add some buffer rows to ensure we can find a position\n const maxExistingY = allPanels.length > 0 ? Math.max(...allPanels.map((p) => p.y + p.h)) : 0;\n const MAX_ROWS = Math.max(maxExistingY + 100, 1000);\n\n // Try to find a position starting from top-left\n for (let y = 0; y < MAX_ROWS; y++) {\n for (let x = 0; x <= columnCount - w; x++) {\n const candidate: PanelCoordinate = {\n id,\n x,\n y,\n w,\n h,\n };\n\n // Check if this position has any collisions\n if (!hasCollision(candidate, candidate.id, panelMap)) {\n return { x, y };\n }\n }\n }\n\n // Fallback: if no position found, place at the bottom\n return { x: 0, y: maxExistingY };\n}\n"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -6,6 +6,7 @@ const require_usePanel = require('./usePanel.cjs');
|
|
|
6
6
|
exports.PanelGridProvider = require_PanelGridProvider.PanelGridProvider;
|
|
7
7
|
exports.PanelGridRenderer = require_PanelGridRenderer.PanelGridRenderer;
|
|
8
8
|
exports.rearrangePanels = require_rearrangement.rearrangePanels;
|
|
9
|
+
exports.resolveWithLockRollback = require_rearrangement.resolveWithLockRollback;
|
|
9
10
|
exports.usePanel = require_usePanel.usePanel;
|
|
10
11
|
exports.usePanelGridControls = require_PanelGridProvider.usePanelGridControls;
|
|
11
12
|
exports.usePanelGridState = require_PanelGridProvider.usePanelGridState;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PanelCoordinate, PanelId, RearrangementFunction } from "./types.cjs";
|
|
2
|
-
import { rearrangePanels } from "./helpers/rearrangement.cjs";
|
|
2
|
+
import { rearrangePanels, resolveWithLockRollback } from "./helpers/rearrangement.cjs";
|
|
3
3
|
import { PanelGridProvider, usePanelGridControls, usePanelGridState } from "./PanelGridProvider.cjs";
|
|
4
4
|
import { PanelGridRenderer } from "./PanelGridRenderer.cjs";
|
|
5
5
|
import { usePanel } from "./usePanel.cjs";
|
|
6
|
-
export { type PanelCoordinate, PanelGridProvider, PanelGridRenderer, type PanelId, type RearrangementFunction, rearrangePanels, usePanel, usePanelGridControls, usePanelGridState };
|
|
6
|
+
export { type PanelCoordinate, PanelGridProvider, PanelGridRenderer, type PanelId, type RearrangementFunction, rearrangePanels, resolveWithLockRollback, usePanel, usePanelGridControls, usePanelGridState };
|