@cloudscape-design/board-components 3.0.31 → 3.0.33
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/board/interfaces.d.ts +2 -0
- package/board/internal.js +2 -4
- package/board/styles.css.js +5 -5
- package/board/styles.scoped.css +10 -10
- package/board/styles.selectors.js +5 -5
- package/board/transition.js +2 -1
- package/board/utils/layout.js +4 -8
- package/board-item/styles.css.js +11 -11
- package/board-item/styles.scoped.css +25 -25
- package/board-item/styles.selectors.js +11 -11
- package/internal/debug-tools/generators.js +1 -1
- package/internal/environment.js +1 -1
- package/internal/environment.json +1 -1
- package/internal/handle/styles.css.js +1 -1
- package/internal/handle/styles.scoped.css +5 -5
- package/internal/handle/styles.selectors.js +1 -1
- package/internal/layout-engine/engine-cache.d.ts +32 -0
- package/internal/layout-engine/engine-cache.js +43 -0
- package/internal/layout-engine/engine-solution.d.ts +25 -0
- package/internal/layout-engine/engine-solution.js +205 -0
- package/internal/layout-engine/engine-state.d.ts +17 -0
- package/internal/layout-engine/engine-state.js +13 -0
- package/internal/layout-engine/engine-step.d.ts +8 -10
- package/internal/layout-engine/engine-step.js +184 -348
- package/internal/layout-engine/engine.d.ts +17 -13
- package/internal/layout-engine/engine.js +67 -59
- package/internal/layout-engine/grid.d.ts +8 -19
- package/internal/layout-engine/grid.js +36 -98
- package/internal/layout-engine/interfaces.d.ts +6 -2
- package/internal/layout-engine/utils.d.ts +8 -3
- package/internal/layout-engine/utils.js +48 -9
- package/internal/manifest.json +1 -1
- package/package.json +1 -1
- package/internal/utils/stack-set.d.ts +0 -8
- package/internal/utils/stack-set.js +0 -23
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { Position } from "../utils/position";
|
|
4
|
+
import { LayoutEngineGrid } from "./grid";
|
|
5
|
+
import { checkOppositeDirections, createMove, getMoveOriginalRect, getMoveRect } from "./utils";
|
|
6
|
+
// All directions in which overlaps can be incrementally resolved.
|
|
7
|
+
const PRIORITY_DIRECTIONS = ["down", "right", "left", "up"];
|
|
8
|
+
// The class represents an intermediate layout state used to find the next set of solutions for.
|
|
9
|
+
// The solution is terminal when no overlaps are left and it can become the next layout state if its
|
|
10
|
+
// score is smaller than that of the alternative solutions.
|
|
11
|
+
export class MoveSolutionState {
|
|
12
|
+
constructor(grid, moves, conflicts) {
|
|
13
|
+
this.moveIndex = 0;
|
|
14
|
+
this.overlaps = new Map();
|
|
15
|
+
this.score = 0;
|
|
16
|
+
this.grid = LayoutEngineGrid.clone(grid);
|
|
17
|
+
this.moves = [...moves];
|
|
18
|
+
this.moveIndex = moves.length;
|
|
19
|
+
this.conflicts = conflicts;
|
|
20
|
+
}
|
|
21
|
+
// The solution state needs to be cloned after the move is performed in case there are overlaps left
|
|
22
|
+
// so that the next solutions won't have the shared state to corrupt.
|
|
23
|
+
// The conflicts never change and can be carried over w/o cloning.
|
|
24
|
+
static clone({ grid, moves, moveIndex, conflicts, overlaps, score }) {
|
|
25
|
+
return {
|
|
26
|
+
grid: LayoutEngineGrid.clone(grid),
|
|
27
|
+
moves: [...moves],
|
|
28
|
+
moveIndex,
|
|
29
|
+
conflicts,
|
|
30
|
+
overlaps: new Map([...overlaps]),
|
|
31
|
+
score,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Given a solution state finds a set of all possible moves each resolving a particular overlap.
|
|
37
|
+
*/
|
|
38
|
+
export function findNextSolutions(state) {
|
|
39
|
+
// For every overlap and direction found a move if exists that resolves the overlap.
|
|
40
|
+
// A pair of the given state and the overlap resolution move is a new solution to try.
|
|
41
|
+
const nextMoveSolutions = [];
|
|
42
|
+
for (const [overlapId, overlapIssuerId] of state.overlaps) {
|
|
43
|
+
for (const moveDirection of PRIORITY_DIRECTIONS) {
|
|
44
|
+
const move = getOverlapMove(state, overlapId, overlapIssuerId, moveDirection);
|
|
45
|
+
if (move !== null) {
|
|
46
|
+
nextMoveSolutions.push([MoveSolutionState.clone(state), move]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return nextMoveSolutions;
|
|
51
|
+
}
|
|
52
|
+
// Returns an evaluated move to resolve the given overlap in the given direction or null if such move is not possible.
|
|
53
|
+
function getOverlapMove(state, overlapId, overlapIssuerId, moveDirection) {
|
|
54
|
+
var _a;
|
|
55
|
+
const userItem = state.grid.getItem(state.moves[0].itemId);
|
|
56
|
+
const overlapItem = state.grid.getItem(overlapId);
|
|
57
|
+
const overlapIssuerItem = state.grid.getItem(overlapIssuerId);
|
|
58
|
+
const overlapMove = getMoveForDirection(overlapItem, overlapIssuerItem, moveDirection);
|
|
59
|
+
// The move position is outside the grid boundaries.
|
|
60
|
+
if (overlapMove.x < 0 || overlapMove.y < 0 || overlapMove.x + overlapMove.width > state.grid.width) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
// Subsequent item overlap moves in the opposite directions do not contribute to solution.
|
|
64
|
+
const prevOverlapMove = getLastSolutionMove(state, overlapItem.id);
|
|
65
|
+
if (prevOverlapMove && checkOppositeDirections(prevOverlapMove.direction, moveDirection)) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const pathOverlaps = getPathOverlaps(state, overlapMove, overlapIssuerItem);
|
|
69
|
+
for (const overlap of pathOverlaps) {
|
|
70
|
+
// Not allowed to intersect with the user-controlled item.
|
|
71
|
+
if (overlap.id === userItem.id) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// Not allowed to intersect with conflicting items.
|
|
75
|
+
if ((_a = state.conflicts) === null || _a === void 0 ? void 0 : _a.items.has(overlap.id)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
// Intersecting with items having unresolved overlaps does not contribute to solution.
|
|
79
|
+
if (state.overlaps.has(overlap.id)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const lastIssuerMove = getLastSolutionMove(state, overlapIssuerItem.id);
|
|
84
|
+
if (!lastIssuerMove) {
|
|
85
|
+
throw new Error("Invariant violation: overlap issuer has no associated moves.");
|
|
86
|
+
}
|
|
87
|
+
const issuerDirection = lastIssuerMove.direction;
|
|
88
|
+
const isSwap = checkIfSwap(overlapMove, lastIssuerMove);
|
|
89
|
+
const isDifferentIssuerDirection = moveDirection !== issuerDirection;
|
|
90
|
+
const isOppositeIssuerDirection = checkOppositeDirections(moveDirection, issuerDirection);
|
|
91
|
+
const userMoveBoundaries = getUserMoveBoundaries(state);
|
|
92
|
+
const moveVector = getSolutionMovesVector(state);
|
|
93
|
+
// Swap score penalizes non-swap overlap resolutions in case the direction does not match that of the issuer.
|
|
94
|
+
const swapPenalty = isSwap ? 0 : 20;
|
|
95
|
+
const differentDirectionPenalty = !isSwap && isDifferentIssuerDirection ? 10 : 0;
|
|
96
|
+
const oppositeDirectionPenalty = !isSwap && isOppositeIssuerDirection ? 500 : 0;
|
|
97
|
+
const swapScore = swapPenalty + differentDirectionPenalty + oppositeDirectionPenalty;
|
|
98
|
+
// Overlaps score penalizes moves that cause additional overlaps.
|
|
99
|
+
const overlapsScore = pathOverlaps.size * 50;
|
|
100
|
+
// Boundaries score penalize movements of items that are outside the area covered by the user move.
|
|
101
|
+
const moveOutsideUserTopPenalty = overlapItem.y + overlapItem.height - 1 < userMoveBoundaries.top ? 500 : 0;
|
|
102
|
+
const moveOutsideUserLeftPenalty = overlapItem.x + overlapItem.width - 1 < userMoveBoundaries.left ? 50 : 0;
|
|
103
|
+
const moveOutsideUserRightPenalty = overlapItem.x > userMoveBoundaries.right ? 50 : 0;
|
|
104
|
+
const boundariesScore = moveOutsideUserTopPenalty + moveOutsideUserLeftPenalty + moveOutsideUserRightPenalty;
|
|
105
|
+
// Move vector score penalize movements that are against the common move direction of other items.
|
|
106
|
+
const vectorXPenalty = overlapMove.distanceX * moveVector.x < 0 ? moveVector.x * 2 : 0;
|
|
107
|
+
const vectorYPenalty = overlapMove.distanceY * moveVector.y < 0 ? moveVector.y * 2 : 0;
|
|
108
|
+
const moveVectorScore = vectorXPenalty + vectorYPenalty;
|
|
109
|
+
// Score starts from 1 to avoid overlap moves having 0 score which breaks the solutions cache.
|
|
110
|
+
const score = 1 + swapScore + overlapsScore + moveVectorScore + boundariesScore;
|
|
111
|
+
return { ...overlapMove, score };
|
|
112
|
+
}
|
|
113
|
+
// Retrieves the first possible move for the given direction to resolve the overlap.
|
|
114
|
+
function getMoveForDirection(moveTarget, overlap, direction) {
|
|
115
|
+
switch (direction) {
|
|
116
|
+
case "up":
|
|
117
|
+
return createMove("OVERLAP", moveTarget, new Position({ x: moveTarget.x, y: overlap.y - moveTarget.height }));
|
|
118
|
+
case "down":
|
|
119
|
+
return createMove("OVERLAP", moveTarget, new Position({ x: moveTarget.x, y: overlap.y + overlap.height }));
|
|
120
|
+
case "left":
|
|
121
|
+
return createMove("OVERLAP", moveTarget, new Position({ x: overlap.x - moveTarget.width, y: moveTarget.y }));
|
|
122
|
+
case "right":
|
|
123
|
+
return createMove("OVERLAP", moveTarget, new Position({ x: overlap.x + overlap.width, y: moveTarget.y }));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Retrieves the last move if exists within the given solution.
|
|
127
|
+
function getLastSolutionMove(state, itemId) {
|
|
128
|
+
let lastMove = null;
|
|
129
|
+
for (let i = state.moves.length - 1; i >= state.moveIndex; i--) {
|
|
130
|
+
if (state.moves[i].itemId === itemId) {
|
|
131
|
+
lastMove = state.moves[i];
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return lastMove;
|
|
136
|
+
}
|
|
137
|
+
// Calculates vector as the amount of cell movements to either direction.
|
|
138
|
+
// All moves in one direction are summarized, the opposite moves cancel each other.
|
|
139
|
+
// The vector show in which direction (left / right, up / down) the most overlaps were resolved.
|
|
140
|
+
function getSolutionMovesVector(state) {
|
|
141
|
+
const vector = { x: 0, y: 0 };
|
|
142
|
+
for (let i = state.moveIndex; i < state.moves.length; i++) {
|
|
143
|
+
const move = state.moves[i];
|
|
144
|
+
if (move.type === "OVERLAP") {
|
|
145
|
+
vector.x += move.distanceX * move.height;
|
|
146
|
+
vector.y += move.distanceY * move.width;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return vector;
|
|
150
|
+
}
|
|
151
|
+
// Finds a rectangle within which the user-controlled item was moved (previous and current positions only).
|
|
152
|
+
// The layout items outside the boundaries are not expected to be disturbed.
|
|
153
|
+
function getUserMoveBoundaries(state) {
|
|
154
|
+
const firstUserMove = state.moves[0];
|
|
155
|
+
const lastUserMove = state.moves[state.moveIndex];
|
|
156
|
+
if (!firstUserMove || !lastUserMove || firstUserMove.itemId !== lastUserMove.itemId) {
|
|
157
|
+
throw new Error("Invariant violation: unexpected user move.");
|
|
158
|
+
}
|
|
159
|
+
const original = getMoveOriginalRect(lastUserMove);
|
|
160
|
+
const current = getMoveRect(lastUserMove);
|
|
161
|
+
return {
|
|
162
|
+
top: Math.min(original.top, current.top),
|
|
163
|
+
right: Math.max(original.right, current.right),
|
|
164
|
+
bottom: Math.max(original.bottom, current.bottom),
|
|
165
|
+
left: Math.min(original.left, current.left),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
// Finds all overlaps that the move will cause along its path not considering the original location and original overlap.
|
|
169
|
+
function getPathOverlaps(state, move, overlapIssuerItem) {
|
|
170
|
+
const { left, right, top, bottom } = getMoveOriginalRect(move);
|
|
171
|
+
const startX = move.distanceX <= 0 ? move.x : right + 1;
|
|
172
|
+
const endX = move.distanceX < 0 ? left - 1 : right + move.distanceX;
|
|
173
|
+
const startY = move.distanceY <= 0 ? move.y : bottom + 1;
|
|
174
|
+
const endY = move.distanceY < 0 ? top - 1 : bottom + move.distanceY;
|
|
175
|
+
const pathOverlaps = new Set(state.grid.getOverlaps({
|
|
176
|
+
id: move.itemId,
|
|
177
|
+
x: startX,
|
|
178
|
+
width: 1 + endX - startX,
|
|
179
|
+
y: startY,
|
|
180
|
+
height: 1 + endY - startY,
|
|
181
|
+
}));
|
|
182
|
+
pathOverlaps.delete(overlapIssuerItem);
|
|
183
|
+
return pathOverlaps;
|
|
184
|
+
}
|
|
185
|
+
// Checks if the overlap move is a swap with the user-moved item.
|
|
186
|
+
function checkIfSwap(overlapMove, lastIssuerMove) {
|
|
187
|
+
if (lastIssuerMove.type !== "MOVE") {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (!checkOppositeDirections(overlapMove.direction, lastIssuerMove.direction)) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
const overlapRect = getMoveOriginalRect(overlapMove);
|
|
194
|
+
const issuerRect = getMoveRect(lastIssuerMove);
|
|
195
|
+
switch (lastIssuerMove.direction) {
|
|
196
|
+
case "up":
|
|
197
|
+
return overlapRect.top === issuerRect.top;
|
|
198
|
+
case "right":
|
|
199
|
+
return overlapRect.right === issuerRect.right;
|
|
200
|
+
case "down":
|
|
201
|
+
return overlapRect.bottom === issuerRect.bottom;
|
|
202
|
+
case "left":
|
|
203
|
+
return overlapRect.left === issuerRect.left;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Direction, ItemId } from "../interfaces";
|
|
2
|
+
import { LayoutEngineGrid, ReadonlyLayoutEngineGrid } from "./grid";
|
|
3
|
+
import { CommittedMove } from "./interfaces";
|
|
4
|
+
/**
|
|
5
|
+
* The class describes the layout engine state at a particular path step.
|
|
6
|
+
* The state of the last performed step is the command result.
|
|
7
|
+
*/
|
|
8
|
+
export declare class LayoutEngineState {
|
|
9
|
+
grid: ReadonlyLayoutEngineGrid;
|
|
10
|
+
moves: readonly CommittedMove[];
|
|
11
|
+
conflicts: null | Conflicts;
|
|
12
|
+
constructor(grid: LayoutEngineGrid, moves?: CommittedMove[], conflicts?: null | Conflicts);
|
|
13
|
+
}
|
|
14
|
+
export interface Conflicts {
|
|
15
|
+
items: ReadonlySet<ItemId>;
|
|
16
|
+
direction: Direction;
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* The class describes the layout engine state at a particular path step.
|
|
5
|
+
* The state of the last performed step is the command result.
|
|
6
|
+
*/
|
|
7
|
+
export class LayoutEngineState {
|
|
8
|
+
constructor(grid, moves = new Array(), conflicts = null) {
|
|
9
|
+
this.grid = grid;
|
|
10
|
+
this.moves = moves;
|
|
11
|
+
this.conflicts = conflicts;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { LayoutEngineGrid, ReadonlyLayoutEngineGrid } from "./grid";
|
|
1
|
+
import { LayoutEngineState } from "./engine-state";
|
|
3
2
|
import { CommittedMove } from "./interfaces";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export declare function resolveOverlaps(
|
|
11
|
-
export declare function refloatGrid(state: LayoutEngineStepState): LayoutEngineStepState;
|
|
3
|
+
/**
|
|
4
|
+
* The function takes the current layout state (item placements from the previous steps and all moves done so far)
|
|
5
|
+
* and a user command increment that describes an item transition by one cell in some direction.
|
|
6
|
+
* The function finds overlapping elements and resolves all overlaps if possible (always possible when no conflicts).
|
|
7
|
+
* The result in an updated state (new item placements, additional moves, and item conflicts if any).
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveOverlaps(layoutState: LayoutEngineState, userMove: CommittedMove): LayoutEngineState;
|