@lablup/ui-common 0.2.0-alpha.17 → 0.2.0-alpha.18
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/README.md +1 -0
- package/astryx/components/Board.doc.mjs +122 -0
- package/astryx/components/Board.tsx +3 -0
- package/astryx/docs/ui-common.doc.mjs +1 -1
- package/dist/assets/components/Board/Board.css +2 -0
- package/dist/components/Board/Board.d.ts +102 -0
- package/dist/components/Board/Board.messages.d.ts +62 -0
- package/dist/components/Board/Board.messages.js +68 -0
- package/dist/components/Board/Board.messages.js.map +1 -0
- package/dist/components/Board/Board2.js +607 -0
- package/dist/components/Board/Board2.js.map +1 -0
- package/dist/components/Board/engine.d.ts +60 -0
- package/dist/components/Board/engine.js +397 -0
- package/dist/components/Board/engine.js.map +1 -0
- package/dist/components/Board/index.d.ts +4 -0
- package/dist/components/Board/index.js +3 -0
- package/dist/components/Board/layout.d.ts +81 -0
- package/dist/components/Board/layout.js +67 -0
- package/dist/components/Board/layout.js.map +1 -0
- package/dist/components/Board/scroll.d.ts +38 -0
- package/dist/components/Board/scroll.js +42 -0
- package/dist/components/Board/scroll.js.map +1 -0
- package/dist/components/DataGrid/DataGrid2.js +128 -128
- package/dist/components/DataGrid/DataGrid2.js.map +1 -1
- package/dist/components/Form/FormItem.d.ts +1 -1
- package/dist/components/PageHeader/PageHeader2.js +4 -4
- package/dist/components/PagedSelector/PagedSelector2.js +37 -37
- package/dist/components/PagedSelector/PagedSelector2.js.map +1 -1
- package/dist/components/SelectionLabel/SelectionLabel.js +4 -4
- package/dist/i18n/catalog.js +27 -25
- package/dist/i18n/catalog.js.map +1 -1
- package/dist/i18n/locales/de-DE.js +60 -0
- package/dist/i18n/locales/de-DE.js.map +1 -1
- package/dist/i18n/locales/el-GR.js +60 -0
- package/dist/i18n/locales/el-GR.js.map +1 -1
- package/dist/i18n/locales/es-ES.js +60 -0
- package/dist/i18n/locales/es-ES.js.map +1 -1
- package/dist/i18n/locales/fi-FI.js +60 -0
- package/dist/i18n/locales/fi-FI.js.map +1 -1
- package/dist/i18n/locales/fr-FR.js +60 -0
- package/dist/i18n/locales/fr-FR.js.map +1 -1
- package/dist/i18n/locales/id-ID.js +60 -0
- package/dist/i18n/locales/id-ID.js.map +1 -1
- package/dist/i18n/locales/it-IT.js +60 -0
- package/dist/i18n/locales/it-IT.js.map +1 -1
- package/dist/i18n/locales/ja-JP.js +60 -0
- package/dist/i18n/locales/ja-JP.js.map +1 -1
- package/dist/i18n/locales/ko-KR.js +60 -0
- package/dist/i18n/locales/ko-KR.js.map +1 -1
- package/dist/i18n/locales/mn-MN.js +60 -0
- package/dist/i18n/locales/mn-MN.js.map +1 -1
- package/dist/i18n/locales/ms-MY.js +60 -0
- package/dist/i18n/locales/ms-MY.js.map +1 -1
- package/dist/i18n/locales/pl-PL.js +60 -0
- package/dist/i18n/locales/pl-PL.js.map +1 -1
- package/dist/i18n/locales/pt-BR.js +60 -0
- package/dist/i18n/locales/pt-BR.js.map +1 -1
- package/dist/i18n/locales/pt-PT.js +60 -0
- package/dist/i18n/locales/pt-PT.js.map +1 -1
- package/dist/i18n/locales/ru-RU.js +60 -0
- package/dist/i18n/locales/ru-RU.js.map +1 -1
- package/dist/i18n/locales/th-TH.js +60 -0
- package/dist/i18n/locales/th-TH.js.map +1 -1
- package/dist/i18n/locales/tr-TR.js +60 -0
- package/dist/i18n/locales/tr-TR.js.map +1 -1
- package/dist/i18n/locales/vi-VN.js +60 -0
- package/dist/i18n/locales/vi-VN.js.map +1 -1
- package/dist/i18n/locales/zh-CN.js +60 -0
- package/dist/i18n/locales/zh-CN.js.map +1 -1
- package/dist/i18n/locales/zh-TW.js +60 -0
- package/dist/i18n/locales/zh-TW.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +61 -59
- package/dist/instance.js +1 -1
- package/dist/ui-common-locales/de-DE.json +60 -0
- package/dist/ui-common-locales/el-GR.json +60 -0
- package/dist/ui-common-locales/en.json +60 -0
- package/dist/ui-common-locales/es-ES.json +60 -0
- package/dist/ui-common-locales/fi-FI.json +60 -0
- package/dist/ui-common-locales/fr-FR.json +60 -0
- package/dist/ui-common-locales/id-ID.json +60 -0
- package/dist/ui-common-locales/it-IT.json +60 -0
- package/dist/ui-common-locales/ja-JP.json +60 -0
- package/dist/ui-common-locales/ko-KR.json +60 -0
- package/dist/ui-common-locales/mn-MN.json +60 -0
- package/dist/ui-common-locales/ms-MY.json +60 -0
- package/dist/ui-common-locales/pl-PL.json +60 -0
- package/dist/ui-common-locales/pt-BR.json +60 -0
- package/dist/ui-common-locales/pt-PT.json +60 -0
- package/dist/ui-common-locales/ru-RU.json +60 -0
- package/dist/ui-common-locales/th-TH.json +60 -0
- package/dist/ui-common-locales/tr-TR.json +60 -0
- package/dist/ui-common-locales/vi-VN.json +60 -0
- package/dist/ui-common-locales/zh-CN.json +60 -0
- package/dist/ui-common-locales/zh-TW.json +60 -0
- package/exports.customs.json +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","names":[],"sources":["../../../src/components/Board/engine.ts"],"sourcesContent":["/**\n * The board's layout engine: what the rest of the board does while one item\n * moves, resizes or leaves.\n *\n * A move or resize is a path of one-cell steps. Each step places the active\n * item, then every item it now overlaps is moved out of the way: a search\n * over the four directions, scored so that a swap with the item the user is\n * pushing against is cheap, a move against the user's direction is dear, and\n * a move that overlaps something else costs more again. The best solution\n * wins; when none exists in time, overlaps are pushed straight down. Then\n * everything but the active item floats up to the first free row.\n *\n * An item the user's own move cannot settle (it reaches past the active\n * item in the direction of the move) is a conflict: nothing else moves, and\n * the step must not be committed.\n *\n * This follows the engine of `@cloudscape-design/board-components`\n * (Apache-2.0), reimplemented here, so a board that moved from it behaves\n * the same.\n */\nimport { compareLayoutItems, type GridLayout, type GridLayoutItem } from \"./layout\";\n\nexport interface Position {\n x: number;\n y: number;\n}\n\nexport interface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport type Direction = \"up\" | \"down\" | \"left\" | \"right\";\n\ntype MoveType = \"MOVE\" | \"OVERLAP\" | \"FLOAT\" | \"RESIZE\" | \"REMOVE\";\n\nexport interface LayoutMove extends Rect {\n type: MoveType;\n itemId: string;\n direction: Direction;\n distanceX: number;\n distanceY: number;\n score: number;\n}\n\nexport interface LayoutShift {\n current: GridLayout;\n /** The layout after the moves, in reading order. */\n next: GridLayout;\n moves: LayoutMove[];\n /** Ids of items the step could not settle; a shift with any must not commit. */\n conflicts: string[];\n}\n\nexport function intersects(a: Rect, b: Rect): boolean {\n return (\n a.x < b.x + b.width &&\n b.x < a.x + a.width &&\n a.y < b.y + b.height &&\n b.y < a.y + a.height\n );\n}\n\nconst oppositeDirections = (d1: Direction, d2: Direction) =>\n (d1 === \"down\" && d2 === \"up\") ||\n (d1 === \"up\" && d2 === \"down\") ||\n (d1 === \"left\" && d2 === \"right\") ||\n (d1 === \"right\" && d2 === \"left\");\n\nfunction createMove(\n type: MoveType,\n item: Rect & { id: string },\n next: Position,\n score = 0,\n): LayoutMove {\n const distanceX = type === \"RESIZE\" ? next.x - item.width : next.x - item.x;\n const distanceY = type === \"RESIZE\" ? next.y - item.height : next.y - item.y;\n return {\n type,\n itemId: item.id,\n x: type !== \"RESIZE\" ? next.x : item.x,\n y: type !== \"RESIZE\" ? next.y : item.y,\n width: type === \"RESIZE\" ? next.x : item.width,\n height: type === \"RESIZE\" ? next.y : item.height,\n direction:\n distanceX > 0 ? \"right\" : distanceX < 0 ? \"left\" : distanceY < 0 ? \"up\" : \"down\",\n distanceX,\n distanceY,\n score,\n };\n}\n\n/** Inclusive edges of where a move started. */\nconst originalEdges = (move: LayoutMove) => ({\n left: move.x - move.distanceX,\n right: move.x - move.distanceX + move.width - 1,\n top: move.y - move.distanceY,\n bottom: move.y - move.distanceY + move.height - 1,\n});\n\n/** Inclusive edges of where a move ends. */\nconst edges = (move: LayoutMove) => ({\n left: move.x,\n right: move.x + move.width - 1,\n top: move.y,\n bottom: move.y + move.height - 1,\n});\n\n/** The items, mutable, with overlap lookups. */\nclass Grid {\n readonly items: GridLayoutItem[];\n constructor(\n items: ReadonlyArray<GridLayoutItem>,\n readonly columns: number,\n ) {\n this.items = items.map((item) => ({ ...item }));\n }\n clone(): Grid {\n return new Grid(this.items, this.columns);\n }\n get rows(): number {\n return this.items.reduce((max, item) => Math.max(max, item.y + item.height), 0);\n }\n getItem(id: string): GridLayoutItem {\n const item = this.items.find((it) => it.id === id);\n if (!item) throw new Error(`Board: no item \"${id}\" on the grid.`);\n return item;\n }\n getOverlaps(rect: Rect & { id: string }): GridLayoutItem[] {\n return this.items.filter((item) => item.id !== rect.id && intersects(item, rect));\n }\n move(id: string, x: number, y: number): void {\n const item = this.getItem(id);\n item.x = x;\n item.y = y;\n }\n resize(id: string, width: number, height: number): void {\n const item = this.getItem(id);\n item.width = width;\n item.height = height;\n }\n remove(id: string): void {\n const index = this.items.findIndex((it) => it.id === id);\n if (index !== -1) this.items.splice(index, 1);\n }\n}\n\ninterface Conflicts {\n direction: Direction;\n items: Set<string>;\n}\n\ninterface State {\n grid: Grid;\n moves: LayoutMove[];\n /** Index of the user's move in `moves`; what follows resolves it. */\n moveIndex: number;\n conflicts: Conflicts | null;\n /** Overlapping item id → the id of the move that made it overlap. */\n overlaps: Map<string, string>;\n score: number;\n}\n\nconst newState = (\n grid: Grid,\n moves: LayoutMove[],\n conflicts: Conflicts | null,\n): State => ({\n grid: grid.clone(),\n moves: [...moves],\n moveIndex: moves.length,\n conflicts,\n overlaps: new Map(),\n score: 0,\n});\n\nconst cloneState = (state: State): State => ({\n grid: state.grid.clone(),\n moves: [...state.moves],\n moveIndex: state.moveIndex,\n conflicts: state.conflicts,\n overlaps: new Map(state.overlaps),\n score: state.score,\n});\n\n/** Bottom-most first: the order overlaps are resolved in. */\nconst bottomUp = (items: GridLayoutItem[]) =>\n items.sort((a, b) => (b.y - a.y === 0 ? b.x - a.x : b.y - a.y));\n\nfunction applyMove(state: State, move: LayoutMove): void {\n const { grid } = state;\n switch (move.type) {\n case \"MOVE\":\n case \"OVERLAP\":\n case \"FLOAT\":\n grid.move(move.itemId, move.x, move.y);\n break;\n case \"RESIZE\":\n grid.resize(move.itemId, move.width, move.height);\n break;\n case \"REMOVE\":\n grid.remove(move.itemId);\n break;\n }\n if (move.type !== \"REMOVE\") {\n for (const overlap of grid.getOverlaps({ ...move, id: move.itemId })) {\n if (!state.conflicts?.items.has(overlap.id))\n state.overlaps.set(overlap.id, move.itemId);\n }\n }\n for (const [overlapId, issuerId] of state.overlaps) {\n const a = grid.items.find((it) => it.id === overlapId);\n const b = grid.items.find((it) => it.id === issuerId);\n if (!a || !b || !intersects(a, b)) state.overlaps.delete(overlapId);\n }\n state.moves.push(move);\n state.score += move.score;\n}\n\nfunction findConflicts(\n grid: Grid,\n previous: Conflicts | null,\n userMove: LayoutMove,\n): Conflicts | null {\n if (userMove.type !== \"MOVE\") return null;\n const direction = previous?.direction ?? userMove.direction;\n const overlaps = grid.getOverlaps({ ...userMove, id: userMove.itemId });\n const conflicts = overlaps.filter((overlap) => {\n switch (direction) {\n case \"left\":\n return overlap.x < userMove.x;\n case \"right\":\n return overlap.x + overlap.width - 1 > userMove.x + userMove.width - 1;\n case \"up\":\n return overlap.y < userMove.y;\n case \"down\":\n return overlap.y + overlap.height - 1 > userMove.y + userMove.height - 1;\n }\n });\n return conflicts.length > 0\n ? { direction, items: new Set(conflicts.map((it) => it.id)) }\n : null;\n}\n\nconst PRIORITY_DIRECTIONS: Direction[] = [\"down\", \"right\", \"left\", \"up\"];\nconst MAX_SOLUTION_DEPTH = 100;\nconst NUM_BEST_SOLUTIONS = 5;\n\nfunction lastMoveOf(state: State, itemId: string): LayoutMove | null {\n for (let i = state.moves.length - 1; i >= state.moveIndex; i--) {\n const move = state.moves[i];\n if (move && move.itemId === itemId) return move;\n }\n return null;\n}\n\nfunction moveForDirection(\n target: GridLayoutItem,\n overlap: GridLayoutItem,\n direction: Direction,\n): LayoutMove {\n switch (direction) {\n case \"up\":\n return createMove(\"OVERLAP\", target, {\n x: target.x,\n y: overlap.y - target.height,\n });\n case \"down\":\n return createMove(\"OVERLAP\", target, {\n x: target.x,\n y: overlap.y + overlap.height,\n });\n case \"left\":\n return createMove(\"OVERLAP\", target, {\n x: overlap.x - target.width,\n y: target.y,\n });\n case \"right\":\n return createMove(\"OVERLAP\", target, {\n x: overlap.x + overlap.width,\n y: target.y,\n });\n }\n}\n\n/** Everything the move sweeps over on its way, except the item that caused it. */\nfunction pathOverlaps(\n state: State,\n move: LayoutMove,\n issuer: GridLayoutItem,\n): Set<GridLayoutItem> {\n const { left, right, top, bottom } = originalEdges(move);\n const startX = move.distanceX <= 0 ? move.x : right + 1;\n const endX = move.distanceX < 0 ? left - 1 : right + move.distanceX;\n const startY = move.distanceY <= 0 ? move.y : bottom + 1;\n const endY = move.distanceY < 0 ? top - 1 : bottom + move.distanceY;\n const found = new Set(\n state.grid.getOverlaps({\n id: move.itemId,\n x: startX,\n width: 1 + endX - startX,\n y: startY,\n height: 1 + endY - startY,\n }),\n );\n found.delete(issuer);\n return found;\n}\n\nfunction isSwap(overlapMove: LayoutMove, issuerMove: LayoutMove): boolean {\n if (issuerMove.type !== \"MOVE\") return false;\n if (!oppositeDirections(overlapMove.direction, issuerMove.direction)) return false;\n const overlapRect = originalEdges(overlapMove);\n const issuerRect = edges(issuerMove);\n switch (issuerMove.direction) {\n case \"up\":\n return overlapRect.top === issuerRect.top;\n case \"right\":\n return overlapRect.right === issuerRect.right;\n case \"down\":\n return overlapRect.bottom === issuerRect.bottom;\n case \"left\":\n return overlapRect.left === issuerRect.left;\n }\n}\n\nfunction solutionVector(state: State): Position {\n const vector = { x: 0, y: 0 };\n for (let i = state.moveIndex; i < state.moves.length; i++) {\n const move = state.moves[i];\n if (move?.type === \"OVERLAP\") {\n vector.x += move.distanceX * move.height;\n vector.y += move.distanceY * move.width;\n }\n }\n return vector;\n}\n\n/** The area the user's own move spans, from where it started to where it is. */\nfunction userMoveBounds(state: State) {\n const last = state.moves[state.moveIndex];\n if (!last) throw new Error(\"Board: no user move to bound.\");\n const original = originalEdges(last);\n const current = edges(last);\n return {\n top: Math.min(original.top, current.top),\n right: Math.max(original.right, current.right),\n bottom: Math.max(original.bottom, current.bottom),\n left: Math.min(original.left, current.left),\n };\n}\n\nfunction overlapMove(\n state: State,\n overlapId: string,\n issuerId: string,\n direction: Direction,\n): LayoutMove | null {\n const first = state.moves[0];\n if (!first) throw new Error(\"Board: no user move.\");\n const userItem = state.grid.getItem(first.itemId);\n const overlapItem = state.grid.getItem(overlapId);\n const issuerItem = state.grid.getItem(issuerId);\n const move = moveForDirection(overlapItem, issuerItem, direction);\n if (move.x < 0 || move.y < 0 || move.x + move.width > state.grid.columns) return null;\n\n const previous = lastMoveOf(state, overlapItem.id);\n if (previous && oppositeDirections(previous.direction, direction)) return null;\n\n const swept = pathOverlaps(state, move, issuerItem);\n for (const item of swept) {\n if (item.id === userItem.id) return null;\n if (state.conflicts?.items.has(item.id)) return null;\n if (state.overlaps.has(item.id)) return null;\n }\n\n const issuerMove = lastMoveOf(state, issuerItem.id);\n if (!issuerMove) throw new Error(\"Board: an overlap's issuer has no move.\");\n const swap = isSwap(move, issuerMove);\n const differentDirection = direction !== issuerMove.direction;\n const oppositeDirection = oppositeDirections(direction, issuerMove.direction);\n const bounds = userMoveBounds(state);\n const vector = solutionVector(state);\n\n const swapScore =\n (swap ? 0 : 20) +\n (!swap && differentDirection ? 10 : 0) +\n (!swap && oppositeDirection ? 500 : 0);\n const overlapsScore = swept.size * 50;\n const boundsScore =\n (overlapItem.y + overlapItem.height - 1 < bounds.top ? 500 : 0) +\n (overlapItem.x + overlapItem.width - 1 < bounds.left ? 50 : 0) +\n (overlapItem.x > bounds.right ? 50 : 0);\n const vectorScore =\n (move.distanceX * vector.x < 0 ? vector.x * 2 : 0) +\n (move.distanceY * vector.y < 0 ? vector.y * 2 : 0);\n return { ...move, score: 1 + swapScore + overlapsScore + vectorScore + boundsScore };\n}\n\nfunction nextSolutions(state: State): Array<[State, LayoutMove]> {\n const solutions: Array<[State, LayoutMove]> = [];\n for (const [overlapId, issuerId] of state.overlaps) {\n for (const direction of PRIORITY_DIRECTIONS) {\n const move = overlapMove(state, overlapId, issuerId, direction);\n if (move) solutions.push([cloneState(state), move]);\n }\n }\n return solutions;\n}\n\nfunction pushOverlapsDown(state: State): State {\n while (state.overlaps.size > 0) {\n const overlaps = bottomUp(\n [...state.overlaps.keys()].map((id) => state.grid.getItem(id)),\n );\n for (const overlap of overlaps) {\n let y = overlap.y + 1;\n while (state.grid.getOverlaps({ ...overlap, y }).length > 0) y++;\n applyMove(state, createMove(\"OVERLAP\", overlap, { x: overlap.x, y }));\n }\n }\n return state;\n}\n\nfunction refloat(from: State, userMove: LayoutMove | null): State {\n const state = newState(from.grid, from.moves, from.conflicts);\n const pass = (): boolean => {\n let again = false;\n for (const item of state.grid.items) {\n if (item.id === userMove?.itemId) continue;\n let move: LayoutMove | null = null;\n for (let y = item.y - 1; y >= 0; y--) {\n const attempt = createMove(\"FLOAT\", item, { x: item.x, y });\n if (state.grid.getOverlaps({ ...attempt, id: item.id }).length > 0) break;\n move = attempt;\n }\n if (move) {\n applyMove(state, move);\n again = true;\n }\n }\n return again;\n };\n while (pass()) {\n /* until nothing floats */\n }\n return state;\n}\n\n/** One step: the user's move applied and every overlap it causes resolved. */\nfunction resolveOverlaps(from: State, userMove: LayoutMove): State {\n const conflicts = findConflicts(from.grid, from.conflicts, userMove);\n const initial = newState(from.grid, from.moves, conflicts);\n const seen = new Set<string>();\n const keyOf = ([state, move]: [State, LayoutMove]) =>\n `${move.itemId} ${move.x}:${move.y}:${state.score + move.score}`;\n\n let solutions: Array<[State, LayoutMove]> = [[initial, userMove]];\n let best: State | null = null;\n let depth = MAX_SOLUTION_DEPTH;\n while (solutions.length > 0) {\n const next: Array<[State, LayoutMove]> = [];\n for (let i = 0; i < Math.min(NUM_BEST_SOLUTIONS, solutions.length); i++) {\n const solution = solutions[i];\n if (!solution) break;\n const [state, move] = solution;\n if (best && state.score + move.score >= best.score) continue;\n applyMove(state, move);\n if (state.overlaps.size === 0) {\n best = state;\n } else {\n for (const solution of nextSolutions(state)) {\n const key = keyOf(solution);\n if (!seen.has(key)) {\n seen.add(key);\n next.push(solution);\n }\n }\n }\n }\n solutions = next.sort(\n (a, b) => a[0].score + a[1].score - (b[0].score + b[1].score),\n );\n depth -= 1;\n if (depth <= 0) break;\n }\n if (!best) {\n // The search found nothing: the initial state still needs the user move\n // applied before its overlaps can be pushed down.\n if (initial.moves.length === initial.moveIndex) applyMove(initial, userMove);\n best = initial.conflicts ? initial : pushOverlapsDown(initial);\n }\n return best.conflicts ? best : refloat(best, userMove);\n}\n\n/** `path` without a leading return to `origin`, as single-cell steps. */\nfunction normalizeSteps(origin: Position, path: ReadonlyArray<Position>): Position[] {\n const steps: Position[] = [];\n let prevX = origin.x;\n let prevY = origin.y;\n for (const step of path) {\n const vx = Math.sign(step.x - prevX);\n const vy = Math.sign(step.y - prevY);\n for (let x = prevX, y = prevY; x !== step.x || y !== step.y;) {\n if (x !== step.x) x += vx;\n else y += vy;\n steps.push({ x, y });\n }\n prevX = step.x;\n prevY = step.y;\n }\n return steps;\n}\n\nfunction dropLeadingOrigin(\n origin: Position,\n path: ReadonlyArray<Position>,\n): Position[] {\n let last = -1;\n path.forEach((p, i) => {\n if (p.x === origin.x && p.y === origin.y) last = i;\n });\n return path.slice(last + 1);\n}\n\n/** A move path with its loops cut out: only the last visit to a cell counts. */\nexport function normalizeMovePath(\n origin: Position,\n path: ReadonlyArray<Position>,\n): Position[] {\n const trimmed = dropLeadingOrigin(origin, path);\n const lastIndexOf = new Map<string, number>();\n trimmed.forEach((p, i) => lastIndexOf.set(`${p.x}:${p.y}`, i));\n const direct: Position[] = [];\n for (let i = 0; i < trimmed.length;) {\n const at = trimmed[i];\n if (!at) break;\n const last = lastIndexOf.get(`${at.x}:${at.y}`) ?? i;\n const visit = trimmed[last];\n if (visit) direct.push(visit);\n i = last + 1;\n }\n return normalizeSteps(origin, direct);\n}\n\n/** A resize path reduced to the shrink steps that matter and the final size. */\nexport function normalizeResizePath(\n origin: Position,\n path: ReadonlyArray<Position>,\n): Position[] {\n const trimmed = dropLeadingOrigin(origin, path);\n const final = trimmed[trimmed.length - 1];\n if (!final) return [];\n const kept: Position[] = [final];\n for (let i = trimmed.length - 2; i >= 0; i--) {\n const prev = kept[kept.length - 1];\n const current = trimmed[i];\n if (prev && current && (current.x < prev.x || current.y < prev.y))\n kept.push(current);\n }\n kept.reverse();\n return normalizeSteps(origin, kept);\n}\n\n/** Appends single-cell steps from the path's end to `next`. */\nexport function appendPath(path: ReadonlyArray<Position>, next: Position): Position[] {\n const last = path[path.length - 1];\n if (!last) return [next];\n return [...path, ...normalizeSteps(last, [next])];\n}\n\n/**\n * Computes layout shifts for one operation. Keep one instance for the whole\n * operation: each step is resolved once and reused while the path's prefix\n * stays the same.\n */\nexport class LayoutEngine {\n private readonly initial: State;\n private cachedSteps: Position[] = [];\n private cachedStates: State[] = [];\n\n constructor(readonly layout: GridLayout) {\n this.initial = newState(new Grid(layout.items, layout.columns), [], null);\n }\n\n move(itemId: string, path: ReadonlyArray<Position>): LayoutShift {\n const item = this.initial.grid.getItem(itemId);\n for (const step of path) {\n if (step.x < 0 || step.y < 0 || step.x + item.width > this.initial.grid.columns) {\n throw new RangeError(\"Board: move outside the grid.\");\n }\n }\n const steps = normalizeMovePath({ x: item.x, y: item.y }, path);\n return this.shift(\n this.run(steps, (state, step) =>\n resolveOverlaps(state, createMove(\"MOVE\", state.grid.getItem(itemId), step)),\n ),\n );\n }\n\n resize(itemId: string, path: ReadonlyArray<Position>): LayoutShift {\n const item = this.initial.grid.getItem(itemId);\n for (const step of path) {\n if (step.x < 1 || step.y < 1) throw new RangeError(\"Board: resize to nothing.\");\n if (step.x > this.initial.grid.columns)\n throw new RangeError(\"Board: resize outside the grid.\");\n }\n const steps = normalizeResizePath(\n { x: item.x + item.width, y: item.y + item.height },\n path,\n );\n return this.shift(\n this.run(steps, (state, step) => {\n const target = state.grid.getItem(itemId);\n return resolveOverlaps(\n state,\n createMove(\"RESIZE\", target, { x: step.x - target.x, y: step.y - target.y }),\n );\n }),\n );\n }\n\n remove(itemId: string): LayoutShift {\n const item = this.initial.grid.getItem(itemId);\n const move = createMove(\"REMOVE\", item, { x: item.x, y: item.y });\n return this.shift(resolveOverlaps(this.initial, move));\n }\n\n private run(steps: Position[], step: (state: State, at: Position) => State): State {\n let reuse = 0;\n while (reuse < steps.length && reuse < this.cachedSteps.length) {\n const cached = this.cachedSteps[reuse];\n const wanted = steps[reuse];\n if (!cached || !wanted || cached.x !== wanted.x || cached.y !== wanted.y) break;\n reuse++;\n }\n this.cachedSteps = this.cachedSteps.slice(0, reuse);\n this.cachedStates = this.cachedStates.slice(0, reuse);\n let state = this.cachedStates[reuse - 1] ?? this.initial;\n for (const at of steps.slice(reuse)) {\n state = step(state, at);\n this.cachedSteps.push(at);\n this.cachedStates.push(state);\n }\n return state;\n }\n\n private shift(state: State): LayoutShift {\n const items = state.grid.items\n .map((item) => ({ ...item }))\n .sort(compareLayoutItems);\n return {\n current: this.layout,\n next: { items, columns: state.grid.columns, rows: state.grid.rows },\n moves: state.moves,\n conflicts: state.conflicts ? [...state.conflicts.items] : [],\n };\n }\n}\n\n/** The layout with `itemId` moved to `(x, y)` in one straight path. */\nexport function moveItem(\n layout: GridLayout,\n itemId: string,\n x: number,\n y: number,\n): GridLayout {\n return new LayoutEngine(layout).move(itemId, [{ x, y }]).next;\n}\n\n/** The layout with `itemId` spanning `width` x `height`. */\nexport function resizeItem(\n layout: GridLayout,\n itemId: string,\n width: number,\n height: number,\n): GridLayout {\n const item = layout.items.find((it) => it.id === itemId);\n if (!item) return layout;\n return new LayoutEngine(layout).resize(itemId, [\n { x: item.x + width, y: item.y + height },\n ]).next;\n}\n\n/** The layout without `itemId`, the rest floated up into its place. */\nexport function removeItem(layout: GridLayout, itemId: string): GridLayout {\n return new LayoutEngine(layout).remove(itemId).next;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { Board, DEFAULT_BOARD_COLUMN_BREAKPOINTS, columnsForWidth } from './Board';
|
|
2
|
+
export type { BoardProps, BoardOperation, BoardItemPlacement, BoardDndDetail, BoardItemRemovedDetail, BoardItemsChangeDetail, BoardItemActions, BoardColumnBreakpoint, } from './Board';
|
|
3
|
+
export { interpretItems, transformItems } from './layout';
|
|
4
|
+
export type { BoardItem, BoardItemDefinition, GridLayout, GridLayoutItem, } from './layout';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { interpretItems as e, transformItems as t } from "./layout.js";
|
|
2
|
+
import { Board as n, DEFAULT_BOARD_COLUMN_BREAKPOINTS as r, columnsForWidth as i } from "./Board2.js";
|
|
3
|
+
export { n as Board, r as DEFAULT_BOARD_COLUMN_BREAKPOINTS, i as columnsForWidth, e as interpretItems, t as transformItems };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The board's layout model.
|
|
3
|
+
*
|
|
4
|
+
* Items persist as `BoardItem`s: spans plus a column offset per column count.
|
|
5
|
+
* A board renders them as a `GridLayout` for the column count it has at the
|
|
6
|
+
* moment, and writes a committed change back the same way. The two
|
|
7
|
+
* conversions, `interpretItems` and `transformItems`, keep the model
|
|
8
|
+
* compatible with layouts persisted by `@cloudscape-design/board-components`
|
|
9
|
+
* (the board this one replaces), so a stored dashboard renders identically.
|
|
10
|
+
*/
|
|
11
|
+
export interface BoardItemDefinition {
|
|
12
|
+
/** The least rows the item may take. Floor 2. @default 2 */
|
|
13
|
+
minRowSpan?: number;
|
|
14
|
+
/** The least columns the item may take. Floor 1. @default 1 */
|
|
15
|
+
minColumnSpan?: number;
|
|
16
|
+
/** Rows the item takes when it has no `rowSpan`. Floored at `minRowSpan`. */
|
|
17
|
+
defaultRowSpan?: number;
|
|
18
|
+
/** Columns the item takes when it has no `columnSpan`. Floored at `minColumnSpan`. */
|
|
19
|
+
defaultColumnSpan?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface BoardItem<D = unknown> {
|
|
22
|
+
/** Unique on the board. */
|
|
23
|
+
id: string;
|
|
24
|
+
definition?: BoardItemDefinition;
|
|
25
|
+
/**
|
|
26
|
+
* Column offset per column count, e.g. `{ 4: 2, 6: 3 }`. Written back by
|
|
27
|
+
* `onItemsChange`; an offset that no longer fits renders at 0.
|
|
28
|
+
*/
|
|
29
|
+
columnOffset?: Readonly<Record<number, number>>;
|
|
30
|
+
/** Rows, from 2. Written back by `onItemsChange` after a resize. */
|
|
31
|
+
rowSpan?: number;
|
|
32
|
+
/** Columns, from 1. Written back by `onItemsChange` after a resize. */
|
|
33
|
+
columnSpan?: number;
|
|
34
|
+
data: D;
|
|
35
|
+
}
|
|
36
|
+
export interface GridLayoutItem {
|
|
37
|
+
id: string;
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
}
|
|
43
|
+
export interface GridLayout {
|
|
44
|
+
items: GridLayoutItem[];
|
|
45
|
+
columns: number;
|
|
46
|
+
rows: number;
|
|
47
|
+
}
|
|
48
|
+
export declare const MIN_ROW_SPAN = 2;
|
|
49
|
+
export declare const MIN_COL_SPAN = 1;
|
|
50
|
+
type Spanned = Pick<BoardItem, "definition" | "rowSpan" | "columnSpan">;
|
|
51
|
+
export declare function getMinColumnSpan(item: Spanned, columns: number): number;
|
|
52
|
+
export declare function getDefaultColumnSpan(item: Spanned, columns: number): number;
|
|
53
|
+
export declare function getMinRowSpan(item: Spanned): number;
|
|
54
|
+
export declare function getDefaultRowSpan(item: Spanned): number;
|
|
55
|
+
/** The span an item renders with: its own, clamped to its minimum and the board. */
|
|
56
|
+
export declare function getColumnSpan(item: Spanned, columns: number): number;
|
|
57
|
+
export declare function getRowSpan(item: Spanned): number;
|
|
58
|
+
/** Reading order: by row, then by column. */
|
|
59
|
+
export declare function compareLayoutItems(a: GridLayoutItem, b: GridLayoutItem): number;
|
|
60
|
+
/**
|
|
61
|
+
* Lays the items out on `columns` columns.
|
|
62
|
+
*
|
|
63
|
+
* Each item in turn takes `columnOffset[columns]` when it fits there, else the
|
|
64
|
+
* first offset, scanning right from where the previous item ended and then
|
|
65
|
+
* from the start, where it fits without rising above the tallest column; the
|
|
66
|
+
* row is the height of the columns it covers. The result is in reading order.
|
|
67
|
+
*/
|
|
68
|
+
export declare function interpretItems<D>(items: ReadonlyArray<BoardItem<D>>, columns: number): GridLayout;
|
|
69
|
+
/**
|
|
70
|
+
* Writes a committed layout back into the items.
|
|
71
|
+
*
|
|
72
|
+
* The items come back in the layout's reading order, each with
|
|
73
|
+
* `columnOffset[columns]` set to its x. The offsets stored for other column
|
|
74
|
+
* counts are kept up to the first item whose position in the order changed
|
|
75
|
+
* (or the resized item) and dropped from there on: a change at index n cannot
|
|
76
|
+
* leave the placement of what follows valid on another column count.
|
|
77
|
+
*/
|
|
78
|
+
export declare function transformItems<D>(sourceItems: ReadonlyArray<BoardItem<D>>, layout: GridLayout, resizeTargetId?: string): BoardItem<D>[];
|
|
79
|
+
/** Whether two layouts place the same items the same way. */
|
|
80
|
+
export declare function isSameLayout(a: GridLayout, b: GridLayout): boolean;
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
function e(e, t) {
|
|
2
|
+
return Math.min(t, Math.max(1, e.definition?.minColumnSpan ?? 0));
|
|
3
|
+
}
|
|
4
|
+
function t(t, n) {
|
|
5
|
+
return Math.min(n, Math.max(e(t, n), t.definition?.defaultColumnSpan ?? 0));
|
|
6
|
+
}
|
|
7
|
+
function n(e) {
|
|
8
|
+
return Math.max(2, e.definition?.minRowSpan ?? 0);
|
|
9
|
+
}
|
|
10
|
+
function r(e) {
|
|
11
|
+
return Math.max(n(e), e.definition?.defaultRowSpan ?? 0);
|
|
12
|
+
}
|
|
13
|
+
function i(n, r) {
|
|
14
|
+
let i = n.columnSpan ?? t(n, r);
|
|
15
|
+
return Math.min(r, Math.max(e(n, r), i));
|
|
16
|
+
}
|
|
17
|
+
function a(e) {
|
|
18
|
+
return Math.max(n(e), e.rowSpan ?? r(e));
|
|
19
|
+
}
|
|
20
|
+
function o(e, t) {
|
|
21
|
+
return e.y === t.y ? e.x - t.x : e.y - t.y;
|
|
22
|
+
}
|
|
23
|
+
function s(e, t) {
|
|
24
|
+
let n = Array(t).fill(0), r = (e, t) => {
|
|
25
|
+
let r = 0;
|
|
26
|
+
for (let i = e; i < e + t; i++) r = Math.max(r, n[i] ?? 0);
|
|
27
|
+
return r;
|
|
28
|
+
}, s = (e, n, i) => {
|
|
29
|
+
let a = r(0, t);
|
|
30
|
+
for (let o = e; o + n <= t; o++) if (r(o, n) + i <= a) return o;
|
|
31
|
+
for (let e = 0; e + n <= t; e++) if (r(e, n) + i <= a) return e;
|
|
32
|
+
return e;
|
|
33
|
+
}, c = [], l = 0;
|
|
34
|
+
for (let o of e) {
|
|
35
|
+
let e = i(o, t), u = a(o), d = o.columnOffset?.[t] ?? s(l, e, u), f = d + e <= t ? d : 0, p = r(f, e);
|
|
36
|
+
c.push({
|
|
37
|
+
id: o.id,
|
|
38
|
+
x: f,
|
|
39
|
+
y: p,
|
|
40
|
+
width: e,
|
|
41
|
+
height: u
|
|
42
|
+
});
|
|
43
|
+
for (let t = f; t < f + e; t++) n[t] = p + u;
|
|
44
|
+
l = f + e;
|
|
45
|
+
}
|
|
46
|
+
return c.sort(o), {
|
|
47
|
+
items: c,
|
|
48
|
+
columns: t,
|
|
49
|
+
rows: r(0, t)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function c(e, t, n) {
|
|
53
|
+
let r = new Map(e.map((e) => [e.id, e])), i = [...t.items].sort(o), a = i.findIndex(({ id: t }, r) => t !== e[r]?.id || t === n);
|
|
54
|
+
return a === -1 && (a = i.length), i.map(({ id: e, x: i, width: o, height: s }, c) => {
|
|
55
|
+
let l = r.get(e);
|
|
56
|
+
if (!l) throw Error(`Board: no item matches layout id "${e}".`);
|
|
57
|
+
let u = { ...l };
|
|
58
|
+
return u.columnOffset = {
|
|
59
|
+
...c >= a ? void 0 : u.columnOffset,
|
|
60
|
+
[t.columns]: i
|
|
61
|
+
}, e === n && (u.columnSpan = o, u.rowSpan = s), u;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { o as compareLayoutItems, i as getColumnSpan, t as getDefaultColumnSpan, r as getDefaultRowSpan, e as getMinColumnSpan, n as getMinRowSpan, a as getRowSpan, s as interpretItems, c as transformItems };
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","names":[],"sources":["../../../src/components/Board/layout.ts"],"sourcesContent":["/**\n * The board's layout model.\n *\n * Items persist as `BoardItem`s: spans plus a column offset per column count.\n * A board renders them as a `GridLayout` for the column count it has at the\n * moment, and writes a committed change back the same way. The two\n * conversions, `interpretItems` and `transformItems`, keep the model\n * compatible with layouts persisted by `@cloudscape-design/board-components`\n * (the board this one replaces), so a stored dashboard renders identically.\n */\n\nexport interface BoardItemDefinition {\n /** The least rows the item may take. Floor 2. @default 2 */\n minRowSpan?: number;\n /** The least columns the item may take. Floor 1. @default 1 */\n minColumnSpan?: number;\n /** Rows the item takes when it has no `rowSpan`. Floored at `minRowSpan`. */\n defaultRowSpan?: number;\n /** Columns the item takes when it has no `columnSpan`. Floored at `minColumnSpan`. */\n defaultColumnSpan?: number;\n}\n\nexport interface BoardItem<D = unknown> {\n /** Unique on the board. */\n id: string;\n definition?: BoardItemDefinition;\n /**\n * Column offset per column count, e.g. `{ 4: 2, 6: 3 }`. Written back by\n * `onItemsChange`; an offset that no longer fits renders at 0.\n */\n columnOffset?: Readonly<Record<number, number>>;\n /** Rows, from 2. Written back by `onItemsChange` after a resize. */\n rowSpan?: number;\n /** Columns, from 1. Written back by `onItemsChange` after a resize. */\n columnSpan?: number;\n data: D;\n}\n\nexport interface GridLayoutItem {\n id: string;\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface GridLayout {\n items: GridLayoutItem[];\n columns: number;\n rows: number;\n}\n\nexport const MIN_ROW_SPAN = 2;\nexport const MIN_COL_SPAN = 1;\n\ntype Spanned = Pick<BoardItem, \"definition\" | \"rowSpan\" | \"columnSpan\">;\n\nexport function getMinColumnSpan(item: Spanned, columns: number): number {\n return Math.min(columns, Math.max(MIN_COL_SPAN, item.definition?.minColumnSpan ?? 0));\n}\n\nexport function getDefaultColumnSpan(item: Spanned, columns: number): number {\n return Math.min(\n columns,\n Math.max(getMinColumnSpan(item, columns), item.definition?.defaultColumnSpan ?? 0),\n );\n}\n\nexport function getMinRowSpan(item: Spanned): number {\n return Math.max(MIN_ROW_SPAN, item.definition?.minRowSpan ?? 0);\n}\n\nexport function getDefaultRowSpan(item: Spanned): number {\n return Math.max(getMinRowSpan(item), item.definition?.defaultRowSpan ?? 0);\n}\n\n/** The span an item renders with: its own, clamped to its minimum and the board. */\nexport function getColumnSpan(item: Spanned, columns: number): number {\n const span = item.columnSpan ?? getDefaultColumnSpan(item, columns);\n return Math.min(columns, Math.max(getMinColumnSpan(item, columns), span));\n}\n\nexport function getRowSpan(item: Spanned): number {\n return Math.max(getMinRowSpan(item), item.rowSpan ?? getDefaultRowSpan(item));\n}\n\n/** Reading order: by row, then by column. */\nexport function compareLayoutItems(a: GridLayoutItem, b: GridLayoutItem): number {\n if (a.y !== b.y) return a.y - b.y;\n return a.x - b.x;\n}\n\n/**\n * Lays the items out on `columns` columns.\n *\n * Each item in turn takes `columnOffset[columns]` when it fits there, else the\n * first offset, scanning right from where the previous item ended and then\n * from the start, where it fits without rising above the tallest column; the\n * row is the height of the columns it covers. The result is in reading order.\n */\nexport function interpretItems<D>(\n items: ReadonlyArray<BoardItem<D>>,\n columns: number,\n): GridLayout {\n const columnHeights: number[] = Array<number>(columns).fill(0);\n\n const rowOffset = (x: number, width: number): number => {\n let row = 0;\n for (let col = x; col < x + width; col++) {\n row = Math.max(row, columnHeights[col] ?? 0);\n }\n return row;\n };\n\n const findOffset = (from: number, width: number, height: number): number => {\n const total = rowOffset(0, columns);\n for (let x = from; x + width <= columns; x++) {\n if (rowOffset(x, width) + height <= total) return x;\n }\n for (let x = 0; x + width <= columns; x++) {\n if (rowOffset(x, width) + height <= total) return x;\n }\n return from;\n };\n\n const layoutItems: GridLayoutItem[] = [];\n let next = 0;\n for (const item of items) {\n const width = getColumnSpan(item, columns);\n const height = getRowSpan(item);\n const wanted = item.columnOffset?.[columns] ?? findOffset(next, width, height);\n const x = wanted + width <= columns ? wanted : 0;\n const y = rowOffset(x, width);\n layoutItems.push({ id: item.id, x, y, width, height });\n for (let col = x; col < x + width; col++) columnHeights[col] = y + height;\n next = x + width;\n }\n\n layoutItems.sort(compareLayoutItems);\n return { items: layoutItems, columns, rows: rowOffset(0, columns) };\n}\n\n/**\n * Writes a committed layout back into the items.\n *\n * The items come back in the layout's reading order, each with\n * `columnOffset[columns]` set to its x. The offsets stored for other column\n * counts are kept up to the first item whose position in the order changed\n * (or the resized item) and dropped from there on: a change at index n cannot\n * leave the placement of what follows valid on another column count.\n */\nexport function transformItems<D>(\n sourceItems: ReadonlyArray<BoardItem<D>>,\n layout: GridLayout,\n resizeTargetId?: string,\n): BoardItem<D>[] {\n const byId = new Map(sourceItems.map((item) => [item.id, item]));\n const sorted = [...layout.items].sort(compareLayoutItems);\n\n let changeFrom = sorted.findIndex(\n ({ id }, index) => id !== sourceItems[index]?.id || id === resizeTargetId,\n );\n if (changeFrom === -1) changeFrom = sorted.length;\n\n return sorted.map(({ id, x, width, height }, index) => {\n const source = byId.get(id);\n if (!source) throw new Error(`Board: no item matches layout id \"${id}\".`);\n const item: BoardItem<D> = { ...source };\n const kept = index >= changeFrom ? undefined : item.columnOffset;\n item.columnOffset = { ...kept, [layout.columns]: x };\n if (id === resizeTargetId) {\n item.columnSpan = width;\n item.rowSpan = height;\n }\n return item;\n });\n}\n\n/** Whether two layouts place the same items the same way. */\nexport function isSameLayout(a: GridLayout, b: GridLayout): boolean {\n if (a.columns !== b.columns || a.items.length !== b.items.length) return false;\n const byId = new Map(b.items.map((item) => [item.id, item]));\n return a.items.every((item) => {\n const other = byId.get(item.id);\n return (\n other !== undefined &&\n other.x === item.x &&\n other.y === item.y &&\n other.width === item.width &&\n other.height === item.height\n );\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrolling during a pointer drag on the board.
|
|
3
|
+
*
|
|
4
|
+
* The pointer is read in viewport coordinates, so when an ancestor scrolls
|
|
5
|
+
* mid-drag the grid moves under a pointer that did not: the drag delta has
|
|
6
|
+
* to add how far the grid's origin travelled since the drag started. And the
|
|
7
|
+
* board is often taller than its scroll container, so a drag near that
|
|
8
|
+
* container's edge scrolls it, at a speed that grows with how close to the
|
|
9
|
+
* edge the pointer is.
|
|
10
|
+
*/
|
|
11
|
+
/** How close to a scroll container's edge, in px, a drag starts scrolling it. */
|
|
12
|
+
export declare const AUTO_SCROLL_EDGE_PX = 48;
|
|
13
|
+
/** The most a frame scrolls, in px, right at the edge. */
|
|
14
|
+
export declare const AUTO_SCROLL_MAX_STEP_PX = 16;
|
|
15
|
+
export interface Point {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
}
|
|
19
|
+
/** The pointer's travel since the drag started, with the grid's own travel added back. */
|
|
20
|
+
export declare function compensateForScroll(pointer: Point, startPointer: Point, startGridOrigin: Point, gridOrigin: Point): Point;
|
|
21
|
+
/**
|
|
22
|
+
* The nearest ancestor that scrolls (computed `overflow-x` or `overflow-y`
|
|
23
|
+
* `auto` or `scroll`), else the document's scrolling element.
|
|
24
|
+
*/
|
|
25
|
+
export declare function findScrollParent(element: Element): Element;
|
|
26
|
+
export interface Bounds {
|
|
27
|
+
left: number;
|
|
28
|
+
top: number;
|
|
29
|
+
right: number;
|
|
30
|
+
bottom: number;
|
|
31
|
+
}
|
|
32
|
+
/** The visible box of a scroll container; the viewport for the document's. */
|
|
33
|
+
export declare function scrollBounds(container: Element): Bounds;
|
|
34
|
+
/**
|
|
35
|
+
* How far to scroll this frame: nothing while the pointer is inside the
|
|
36
|
+
* edge band, up to `maxStep` at the edge, in the direction of the edge.
|
|
37
|
+
*/
|
|
38
|
+
export declare function autoScrollStep(pointer: Point, bounds: Bounds, edge?: number, maxStep?: number): Point;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
function e(e, t, n, r) {
|
|
2
|
+
return {
|
|
3
|
+
x: e.x - t.x + (n.x - r.x),
|
|
4
|
+
y: e.y - t.y + (n.y - r.y)
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
var t = /^(auto|scroll)/;
|
|
8
|
+
function n(e) {
|
|
9
|
+
let n = e.parentElement;
|
|
10
|
+
for (; n && n !== document.body;) {
|
|
11
|
+
let e = getComputedStyle(n);
|
|
12
|
+
if (t.test(e.overflowY) || t.test(e.overflowX)) return n;
|
|
13
|
+
n = n.parentElement;
|
|
14
|
+
}
|
|
15
|
+
return document.scrollingElement ?? document.documentElement;
|
|
16
|
+
}
|
|
17
|
+
function r(e) {
|
|
18
|
+
if (e === (document.scrollingElement ?? document.documentElement)) return {
|
|
19
|
+
left: 0,
|
|
20
|
+
top: 0,
|
|
21
|
+
right: window.innerWidth,
|
|
22
|
+
bottom: window.innerHeight
|
|
23
|
+
};
|
|
24
|
+
let t = e.getBoundingClientRect();
|
|
25
|
+
return {
|
|
26
|
+
left: t.left,
|
|
27
|
+
top: t.top,
|
|
28
|
+
right: t.right,
|
|
29
|
+
bottom: t.bottom
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function i(e, t, n = 48, r = 16) {
|
|
33
|
+
let i = (e) => e >= n || n <= 0 ? 0 : Math.ceil((n - Math.max(e, 0)) / n * r);
|
|
34
|
+
return {
|
|
35
|
+
x: i(e.x - t.left) ? -i(e.x - t.left) : i(t.right - e.x),
|
|
36
|
+
y: i(e.y - t.top) ? -i(e.y - t.top) : i(t.bottom - e.y)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { i as autoScrollStep, e as compensateForScroll, n as findScrollParent, r as scrollBounds };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=scroll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll.js","names":[],"sources":["../../../src/components/Board/scroll.ts"],"sourcesContent":["/**\n * Scrolling during a pointer drag on the board.\n *\n * The pointer is read in viewport coordinates, so when an ancestor scrolls\n * mid-drag the grid moves under a pointer that did not: the drag delta has\n * to add how far the grid's origin travelled since the drag started. And the\n * board is often taller than its scroll container, so a drag near that\n * container's edge scrolls it, at a speed that grows with how close to the\n * edge the pointer is.\n */\n\n/** How close to a scroll container's edge, in px, a drag starts scrolling it. */\nexport const AUTO_SCROLL_EDGE_PX = 48;\n/** The most a frame scrolls, in px, right at the edge. */\nexport const AUTO_SCROLL_MAX_STEP_PX = 16;\n\nexport interface Point {\n x: number;\n y: number;\n}\n\n/** The pointer's travel since the drag started, with the grid's own travel added back. */\nexport function compensateForScroll(\n pointer: Point,\n startPointer: Point,\n startGridOrigin: Point,\n gridOrigin: Point,\n): Point {\n return {\n x: pointer.x - startPointer.x + (startGridOrigin.x - gridOrigin.x),\n y: pointer.y - startPointer.y + (startGridOrigin.y - gridOrigin.y),\n };\n}\n\nconst SCROLLS = /^(auto|scroll)/;\n\n/**\n * The nearest ancestor that scrolls (computed `overflow-x` or `overflow-y`\n * `auto` or `scroll`), else the document's scrolling element.\n */\nexport function findScrollParent(element: Element): Element {\n let node = element.parentElement;\n while (node && node !== document.body) {\n const style = getComputedStyle(node);\n if (SCROLLS.test(style.overflowY) || SCROLLS.test(style.overflowX)) return node;\n node = node.parentElement;\n }\n return document.scrollingElement ?? document.documentElement;\n}\n\nexport interface Bounds {\n left: number;\n top: number;\n right: number;\n bottom: number;\n}\n\n/** The visible box of a scroll container; the viewport for the document's. */\nexport function scrollBounds(container: Element): Bounds {\n if (container === (document.scrollingElement ?? document.documentElement)) {\n return {\n left: 0,\n top: 0,\n right: window.innerWidth,\n bottom: window.innerHeight,\n };\n }\n const rect = container.getBoundingClientRect();\n return { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom };\n}\n\n/**\n * How far to scroll this frame: nothing while the pointer is inside the\n * edge band, up to `maxStep` at the edge, in the direction of the edge.\n */\nexport function autoScrollStep(\n pointer: Point,\n bounds: Bounds,\n edge = AUTO_SCROLL_EDGE_PX,\n maxStep = AUTO_SCROLL_MAX_STEP_PX,\n): Point {\n const toward = (distance: number) =>\n distance >= edge || edge <= 0\n ? 0\n : Math.ceil(((edge - Math.max(distance, 0)) / edge) * maxStep);\n const x = toward(pointer.x - bounds.left)\n ? -toward(pointer.x - bounds.left)\n : toward(bounds.right - pointer.x);\n const y = toward(pointer.y - bounds.top)\n ? -toward(pointer.y - bounds.top)\n : toward(bounds.bottom - pointer.y);\n return { x, y };\n}\n"],"mappings":"AAsBA,SAAgB,EACd,GACA,GACA,GACA,GACO;CACP,OAAO;EACL,GAAG,EAAQ,IAAI,EAAa,KAAK,EAAgB,IAAI,EAAW;EAChE,GAAG,EAAQ,IAAI,EAAa,KAAK,EAAgB,IAAI,EAAW;CAClE;AACF;AAEA,IAAM,IAAU;AAMhB,SAAgB,EAAiB,GAA2B;CAC1D,IAAI,IAAO,EAAQ;CACnB,OAAO,KAAQ,MAAS,SAAS,OAAM;EACrC,IAAM,IAAQ,iBAAiB,CAAI;EACnC,IAAI,EAAQ,KAAK,EAAM,SAAS,KAAK,EAAQ,KAAK,EAAM,SAAS,GAAG,OAAO;EAC3E,IAAO,EAAK;CACd;CACA,OAAO,SAAS,oBAAoB,SAAS;AAC/C;AAUA,SAAgB,EAAa,GAA4B;CACvD,IAAI,OAAe,SAAS,oBAAoB,SAAS,kBACvD,OAAO;EACL,MAAM;EACN,KAAK;EACL,OAAO,OAAO;EACd,QAAQ,OAAO;CACjB;CAEF,IAAM,IAAO,EAAU,sBAAsB;CAC7C,OAAO;EAAE,MAAM,EAAK;EAAM,KAAK,EAAK;EAAK,OAAO,EAAK;EAAO,QAAQ,EAAK;CAAO;AAClF;AAMA,SAAgB,EACd,GACA,GACA,IAAA,IACA,IAAA,IACO;CACP,IAAM,KAAU,MACd,KAAY,KAAQ,KAAQ,IACxB,IACA,KAAK,MAAO,IAAO,KAAK,IAAI,GAAU,CAAC,KAAK,IAAQ,CAAO;CAOjE,OAAO;EAAE,GANC,EAAO,EAAQ,IAAI,EAAO,IAAI,IACpC,CAAC,EAAO,EAAQ,IAAI,EAAO,IAAI,IAC/B,EAAO,EAAO,QAAQ,EAAQ,CAAC;EAIvB,GAHF,EAAO,EAAQ,IAAI,EAAO,GAAG,IACnC,CAAC,EAAO,EAAQ,IAAI,EAAO,GAAG,IAC9B,EAAO,EAAO,SAAS,EAAQ,CAAC;CACtB;AAChB"}
|