@zag-js/rect-utils 0.39.0 → 0.41.0

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.
@@ -1,5 +1,5 @@
1
- import { createRect, type Rect } from "./rect"
2
- import type { RectSide } from "./types"
1
+ import { createRect } from "./rect"
2
+ import type { Rect, RectSide } from "./types"
3
3
 
4
4
  /**
5
5
  * Checks if a Rect intersects another Rect
package/src/operations.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { createRect, type Rect } from "./rect"
2
- import type { Point, RectInset, SymmetricRectInset } from "./types"
1
+ import { createRect } from "./rect"
2
+ import type { Point, Rect, RectInset, SymmetricRectInset } from "./types"
3
3
 
4
4
  export const isSymmetric = (v: any): v is SymmetricRectInset => "dx" in v || "dy" in v
5
5
 
package/src/polygon.ts CHANGED
@@ -1,4 +1,18 @@
1
- import type { Point } from "./types"
1
+ import { createRect, getRectCorners } from "./rect"
2
+ import type { Point, RectInit } from "./types"
3
+
4
+ export function getElementPolygon(rectValue: RectInit, placement: string) {
5
+ const rect = createRect(rectValue)
6
+ const { top, right, left, bottom } = getRectCorners(rect)
7
+ const [base] = placement.split("-")
8
+
9
+ return {
10
+ top: [left, top, right, bottom],
11
+ right: [top, right, bottom, left],
12
+ bottom: [top, left, bottom, right],
13
+ left: [right, top, left, bottom],
14
+ }[base]
15
+ }
2
16
 
3
17
  export function isPointInPolygon(polygon: Point[], point: Point) {
4
18
  const { x, y } = point
package/src/rect.ts CHANGED
@@ -1,8 +1,23 @@
1
- import type { RectEdge, RectValue } from "./types"
1
+ import type { Point, Rect, RectEdge, RectInit } from "./types"
2
2
 
3
- const point = (x: number, y: number) => ({ x, y })
3
+ /* -----------------------------------------------------------------------------
4
+ * Point
5
+ * -----------------------------------------------------------------------------*/
4
6
 
5
- export function createRect(r: RectValue) {
7
+ export const createPoint = (x: number, y: number) => ({ x, y })
8
+
9
+ export const subtractPoints = (a: Point, b: Point) => createPoint(a.x - b.x, a.y - b.y)
10
+ export const addPoints = (a: Point, b: Point) => createPoint(a.x + b.x, a.y + b.y)
11
+
12
+ export function isPoint(v: any): v is Point {
13
+ return Reflect.has(v, "x") && Reflect.has(v, "y")
14
+ }
15
+
16
+ /* -----------------------------------------------------------------------------
17
+ * Rect
18
+ * -----------------------------------------------------------------------------*/
19
+
20
+ export function createRect(r: RectInit): Rect {
6
21
  const { x, y, width, height } = r
7
22
  const midX = x + width / 2
8
23
  const midY = y + height / 2
@@ -17,32 +32,27 @@ export function createRect(r: RectValue) {
17
32
  maxY: y + height,
18
33
  midX,
19
34
  midY,
20
- center: point(midX, midY),
35
+ center: createPoint(midX, midY),
21
36
  }
22
37
  }
23
38
 
24
- export type Rect = ReturnType<typeof createRect>
25
-
26
- const hasProp = <T extends string>(obj: any, prop: T): obj is Record<T, any> =>
27
- Object.prototype.hasOwnProperty.call(obj, prop)
28
-
29
39
  export function isRect(v: any): v is Rect {
30
- return hasProp(v, "x") && hasProp(v, "y") && hasProp(v, "width") && hasProp(v, "height")
40
+ return Reflect.has(v, "x") && Reflect.has(v, "y") && Reflect.has(v, "width") && Reflect.has(v, "height")
31
41
  }
32
42
 
33
43
  export function getRectCenters(v: Rect) {
34
- const top = point(v.midX, v.minY)
35
- const right = point(v.maxX, v.midY)
36
- const bottom = point(v.midX, v.maxY)
37
- const left = point(v.minX, v.midY)
44
+ const top = createPoint(v.midX, v.minY)
45
+ const right = createPoint(v.maxX, v.midY)
46
+ const bottom = createPoint(v.midX, v.maxY)
47
+ const left = createPoint(v.minX, v.midY)
38
48
  return { top, right, bottom, left }
39
49
  }
40
50
 
41
51
  export function getRectCorners(v: Rect) {
42
- const top = point(v.minX, v.minY)
43
- const right = point(v.maxX, v.minY)
44
- const bottom = point(v.maxX, v.maxY)
45
- const left = point(v.minX, v.maxY)
52
+ const top = createPoint(v.minX, v.minY)
53
+ const right = createPoint(v.maxX, v.minY)
54
+ const bottom = createPoint(v.maxX, v.maxY)
55
+ const left = createPoint(v.minX, v.maxY)
46
56
  return { top, right, bottom, left }
47
57
  }
48
58
 
package/src/resize.ts ADDED
@@ -0,0 +1,106 @@
1
+ import { AffineTransform } from "./affine-transform"
2
+ import { compassDirectionMap, oppositeDirectionMap, type CompassDirection } from "./compass"
3
+ import type { Point, Rect, RectInit, ScalingOptions } from "./types"
4
+
5
+ const { sign, abs, min } = Math
6
+
7
+ function getRectExtentPoint(rect: Rect, direction: CompassDirection) {
8
+ const { minX, minY, maxX, maxY, midX, midY } = rect
9
+ const x = direction.includes("w") ? minX : direction.includes("e") ? maxX : midX
10
+ const y = direction.includes("n") ? minY : direction.includes("s") ? maxY : midY
11
+ return { x, y }
12
+ }
13
+
14
+ function getOppositeDirection(direction: CompassDirection) {
15
+ return oppositeDirectionMap[direction]
16
+ }
17
+
18
+ export function resizeRect(rect: Rect, offset: Point, direction: CompassDirection, opts: ScalingOptions) {
19
+ const { scalingOriginMode, lockAspectRatio } = opts
20
+
21
+ const extent = getRectExtentPoint(rect, direction)
22
+
23
+ const oppositeDirection = getOppositeDirection(direction)
24
+ const oppositeExtent = getRectExtentPoint(rect, oppositeDirection)
25
+
26
+ if (scalingOriginMode === "center") {
27
+ offset = { x: offset.x * 2, y: offset.y * 2 }
28
+ }
29
+
30
+ const newExtent = {
31
+ x: extent.x + offset.x,
32
+ y: extent.y + offset.y,
33
+ }
34
+
35
+ const multiplier = {
36
+ x: compassDirectionMap[direction].x * 2 - 1,
37
+ y: compassDirectionMap[direction].y * 2 - 1,
38
+ }
39
+
40
+ const newSize = {
41
+ width: newExtent.x - oppositeExtent.x,
42
+ height: newExtent.y - oppositeExtent.y,
43
+ }
44
+
45
+ const scaleX = (multiplier.x * newSize.width) / rect.width
46
+ const scaleY = (multiplier.y * newSize.height) / rect.height
47
+
48
+ const largestMagnitude = abs(scaleX) > abs(scaleY) ? scaleX : scaleY
49
+
50
+ const scale = lockAspectRatio
51
+ ? { x: largestMagnitude, y: largestMagnitude }
52
+ : {
53
+ x: extent.x === oppositeExtent.x ? 1 : scaleX,
54
+ y: extent.y === oppositeExtent.y ? 1 : scaleY,
55
+ }
56
+
57
+ if (extent.y === oppositeExtent.y) {
58
+ scale.y = abs(scale.y)
59
+ } else if (sign(scale.y) !== sign(scaleY)) {
60
+ scale.y *= -1
61
+ }
62
+
63
+ if (extent.x === oppositeExtent.x) {
64
+ scale.x = abs(scale.x)
65
+ } else if (sign(scale.x) !== sign(scaleX)) {
66
+ scale.x *= -1
67
+ }
68
+
69
+ switch (scalingOriginMode) {
70
+ case "extent":
71
+ return transformRect(rect, AffineTransform.scale(scale.x, scale.y, oppositeExtent), false)
72
+ case "center":
73
+ return transformRect(
74
+ rect,
75
+ AffineTransform.scale(scale.x, scale.y, {
76
+ x: rect.midX,
77
+ y: rect.midY,
78
+ }),
79
+ false,
80
+ )
81
+ }
82
+ }
83
+
84
+ function createRectFromPoints(initialPoint: Point, finalPoint: Point, normalized: boolean = true): RectInit {
85
+ if (normalized) {
86
+ return {
87
+ x: min(finalPoint.x, initialPoint.x),
88
+ y: min(finalPoint.y, initialPoint.y),
89
+ width: abs(finalPoint.x - initialPoint.x),
90
+ height: abs(finalPoint.y - initialPoint.y),
91
+ }
92
+ }
93
+
94
+ return {
95
+ x: initialPoint.x,
96
+ y: initialPoint.y,
97
+ width: finalPoint.x - initialPoint.x,
98
+ height: finalPoint.y - initialPoint.y,
99
+ }
100
+ }
101
+
102
+ function transformRect(rect: Rect, transform: AffineTransform, normalized = true): RectInit {
103
+ const p1 = transform.applyTo({ x: rect.minX, y: rect.minY })
104
+ const p2 = transform.applyTo({ x: rect.maxX, y: rect.maxY })
105
+ return createRectFromPoints(p1, p2, normalized)
106
+ }
package/src/types.ts CHANGED
@@ -1,12 +1,38 @@
1
- export type Point = { x: number; y: number }
1
+ /* -----------------------------------------------------------------------------
2
+ * Basic
3
+ * -----------------------------------------------------------------------------*/
2
4
 
3
- export type RectValue = {
5
+ export interface Point {
4
6
  x: number
5
7
  y: number
8
+ }
9
+
10
+ export interface Size {
6
11
  width: number
7
12
  height: number
8
13
  }
9
14
 
15
+ export interface Bounds {
16
+ minX: number
17
+ midX: number
18
+ maxX: number
19
+ minY: number
20
+ midY: number
21
+ maxY: number
22
+ }
23
+
24
+ export interface CenterPoint {
25
+ center: Point
26
+ }
27
+
28
+ export interface RectInit extends Point, Size {}
29
+
30
+ export interface Rect extends Point, Size, Bounds, CenterPoint {}
31
+
32
+ /* -----------------------------------------------------------------------------
33
+ * Edge and Side
34
+ * -----------------------------------------------------------------------------*/
35
+
10
36
  export type RectSide = "top" | "right" | "bottom" | "left"
11
37
 
12
38
  export type RectPoint =
@@ -42,4 +68,25 @@ export type RectCenters = Record<RectCenter, Point> & {
42
68
 
43
69
  export type RectInset = Partial<Record<RectSide, number>>
44
70
 
45
- export type SymmetricRectInset = { dx?: number; dy?: number }
71
+ export interface SymmetricRectInset {
72
+ dx?: number
73
+ dy?: number
74
+ }
75
+
76
+ export interface ScalingOptions {
77
+ scalingOriginMode: "center" | "extent"
78
+ lockAspectRatio: boolean
79
+ }
80
+
81
+ /* -----------------------------------------------------------------------------
82
+ * Alignment
83
+ * -----------------------------------------------------------------------------*/
84
+
85
+ export interface AlignOptions {
86
+ h: HAlign
87
+ v: VAlign
88
+ }
89
+
90
+ export type HAlign = "left-inside" | "left-outside" | "center" | "right-inside" | "right-outside"
91
+
92
+ export type VAlign = "top-inside" | "top-outside" | "center" | "bottom-inside" | "bottom-outside"
package/src/union.ts CHANGED
@@ -1,28 +1,16 @@
1
1
  import { getRectFromPoints } from "./from-points"
2
- import type { Rect } from "./rect"
2
+ import type { Rect } from "./types"
3
3
 
4
4
  const { min, max } = Math
5
5
 
6
6
  export function union(...rs: Rect[]): Rect {
7
7
  const pMin = {
8
- x: min.apply(
9
- Math,
10
- rs.map((r) => r.minX),
11
- ),
12
- y: min.apply(
13
- Math,
14
- rs.map((r) => r.minY),
15
- ),
8
+ x: min(...rs.map((r) => r.minX)),
9
+ y: min(...rs.map((r) => r.minY)),
16
10
  }
17
11
  const pMax = {
18
- x: max.apply(
19
- Math,
20
- rs.map((r) => r.maxX),
21
- ),
22
- y: max.apply(
23
- Math,
24
- rs.map((r) => r.maxY),
25
- ),
12
+ x: max(...rs.map((r) => r.maxX)),
13
+ y: max(...rs.map((r) => r.maxY)),
26
14
  }
27
15
  return getRectFromPoints(pMin, pMax)
28
16
  }
@@ -1,15 +0,0 @@
1
- import { createRect, getRectCorners } from "./rect"
2
- import type { RectValue } from "./types"
3
-
4
- export function getElementPolygon(rectValue: RectValue, placement: string) {
5
- const rect = createRect(rectValue)
6
- const { top, right, left, bottom } = getRectCorners(rect)
7
- const [base] = placement.split("-")
8
-
9
- return {
10
- top: [left, top, right, bottom],
11
- right: [top, right, bottom, left],
12
- bottom: [top, left, bottom, right],
13
- left: [right, top, left, bottom],
14
- }[base]
15
- }