@wordpress/grid 0.1.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.
Files changed (158) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE.md +788 -0
  3. package/README.md +534 -0
  4. package/build/dashboard-grid/grid-item.cjs +308 -0
  5. package/build/dashboard-grid/grid-item.cjs.map +7 -0
  6. package/build/dashboard-grid/index.cjs +591 -0
  7. package/build/dashboard-grid/index.cjs.map +7 -0
  8. package/build/dashboard-grid/resolve-fill-widths.cjs +189 -0
  9. package/build/dashboard-grid/resolve-fill-widths.cjs.map +7 -0
  10. package/build/dashboard-grid/types.cjs +19 -0
  11. package/build/dashboard-grid/types.cjs.map +7 -0
  12. package/build/dashboard-lanes/index.cjs +558 -0
  13. package/build/dashboard-lanes/index.cjs.map +7 -0
  14. package/build/dashboard-lanes/lane-placement.cjs +110 -0
  15. package/build/dashboard-lanes/lane-placement.cjs.map +7 -0
  16. package/build/dashboard-lanes/lanes-item.cjs +295 -0
  17. package/build/dashboard-lanes/lanes-item.cjs.map +7 -0
  18. package/build/dashboard-lanes/types.cjs +19 -0
  19. package/build/dashboard-lanes/types.cjs.map +7 -0
  20. package/build/dashboard-lanes/use-lane-placement.cjs +206 -0
  21. package/build/dashboard-lanes/use-lane-placement.cjs.map +7 -0
  22. package/build/index.cjs +34 -0
  23. package/build/index.cjs.map +7 -0
  24. package/build/shared/drag-overlay-drop-animation.cjs +70 -0
  25. package/build/shared/drag-overlay-drop-animation.cjs.map +7 -0
  26. package/build/shared/grid-item-key.cjs +31 -0
  27. package/build/shared/grid-item-key.cjs.map +7 -0
  28. package/build/shared/grid-overlay.cjs +187 -0
  29. package/build/shared/grid-overlay.cjs.map +7 -0
  30. package/build/shared/item-exit-overlay.cjs +150 -0
  31. package/build/shared/item-exit-overlay.cjs.map +7 -0
  32. package/build/shared/resize-handle.cjs +224 -0
  33. package/build/shared/resize-handle.cjs.map +7 -0
  34. package/build/shared/resize-snap.cjs +47 -0
  35. package/build/shared/resize-snap.cjs.map +7 -0
  36. package/build/shared/types.cjs +19 -0
  37. package/build/shared/types.cjs.map +7 -0
  38. package/build/shared/use-item-exit-animation.cjs +148 -0
  39. package/build/shared/use-item-exit-animation.cjs.map +7 -0
  40. package/build/shared/use-layout-shift-animation.cjs +167 -0
  41. package/build/shared/use-layout-shift-animation.cjs.map +7 -0
  42. package/build-module/dashboard-grid/grid-item.mjs +273 -0
  43. package/build-module/dashboard-grid/grid-item.mjs.map +7 -0
  44. package/build-module/dashboard-grid/index.mjs +579 -0
  45. package/build-module/dashboard-grid/index.mjs.map +7 -0
  46. package/build-module/dashboard-grid/resolve-fill-widths.mjs +164 -0
  47. package/build-module/dashboard-grid/resolve-fill-widths.mjs.map +7 -0
  48. package/build-module/dashboard-grid/types.mjs +1 -0
  49. package/build-module/dashboard-grid/types.mjs.map +7 -0
  50. package/build-module/dashboard-lanes/index.mjs +547 -0
  51. package/build-module/dashboard-lanes/index.mjs.map +7 -0
  52. package/build-module/dashboard-lanes/lane-placement.mjs +85 -0
  53. package/build-module/dashboard-lanes/lane-placement.mjs.map +7 -0
  54. package/build-module/dashboard-lanes/lanes-item.mjs +260 -0
  55. package/build-module/dashboard-lanes/lanes-item.mjs.map +7 -0
  56. package/build-module/dashboard-lanes/types.mjs +1 -0
  57. package/build-module/dashboard-lanes/types.mjs.map +7 -0
  58. package/build-module/dashboard-lanes/use-lane-placement.mjs +181 -0
  59. package/build-module/dashboard-lanes/use-lane-placement.mjs.map +7 -0
  60. package/build-module/index.mjs +8 -0
  61. package/build-module/index.mjs.map +7 -0
  62. package/build-module/shared/drag-overlay-drop-animation.mjs +47 -0
  63. package/build-module/shared/drag-overlay-drop-animation.mjs.map +7 -0
  64. package/build-module/shared/grid-item-key.mjs +6 -0
  65. package/build-module/shared/grid-item-key.mjs.map +7 -0
  66. package/build-module/shared/grid-overlay.mjs +152 -0
  67. package/build-module/shared/grid-overlay.mjs.map +7 -0
  68. package/build-module/shared/item-exit-overlay.mjs +125 -0
  69. package/build-module/shared/item-exit-overlay.mjs.map +7 -0
  70. package/build-module/shared/resize-handle.mjs +193 -0
  71. package/build-module/shared/resize-handle.mjs.map +7 -0
  72. package/build-module/shared/resize-snap.mjs +21 -0
  73. package/build-module/shared/resize-snap.mjs.map +7 -0
  74. package/build-module/shared/types.mjs +1 -0
  75. package/build-module/shared/types.mjs.map +7 -0
  76. package/build-module/shared/use-item-exit-animation.mjs +128 -0
  77. package/build-module/shared/use-item-exit-animation.mjs.map +7 -0
  78. package/build-module/shared/use-layout-shift-animation.mjs +140 -0
  79. package/build-module/shared/use-layout-shift-animation.mjs.map +7 -0
  80. package/build-types/dashboard-grid/grid-item.d.ts +3 -0
  81. package/build-types/dashboard-grid/grid-item.d.ts.map +1 -0
  82. package/build-types/dashboard-grid/index.d.ts +35 -0
  83. package/build-types/dashboard-grid/index.d.ts.map +1 -0
  84. package/build-types/dashboard-grid/resolve-fill-widths.d.ts +26 -0
  85. package/build-types/dashboard-grid/resolve-fill-widths.d.ts.map +1 -0
  86. package/build-types/dashboard-grid/stories/index.story.d.ts +98 -0
  87. package/build-types/dashboard-grid/stories/index.story.d.ts.map +1 -0
  88. package/build-types/dashboard-grid/types.d.ts +232 -0
  89. package/build-types/dashboard-grid/types.d.ts.map +1 -0
  90. package/build-types/dashboard-lanes/index.d.ts +40 -0
  91. package/build-types/dashboard-lanes/index.d.ts.map +1 -0
  92. package/build-types/dashboard-lanes/lane-placement.d.ts +126 -0
  93. package/build-types/dashboard-lanes/lane-placement.d.ts.map +1 -0
  94. package/build-types/dashboard-lanes/lanes-item.d.ts +52 -0
  95. package/build-types/dashboard-lanes/lanes-item.d.ts.map +1 -0
  96. package/build-types/dashboard-lanes/stories/index.story.d.ts +64 -0
  97. package/build-types/dashboard-lanes/stories/index.story.d.ts.map +1 -0
  98. package/build-types/dashboard-lanes/types.d.ts +151 -0
  99. package/build-types/dashboard-lanes/types.d.ts.map +1 -0
  100. package/build-types/dashboard-lanes/use-lane-placement.d.ts +74 -0
  101. package/build-types/dashboard-lanes/use-lane-placement.d.ts.map +1 -0
  102. package/build-types/index.d.ts +6 -0
  103. package/build-types/index.d.ts.map +1 -0
  104. package/build-types/shared/drag-overlay-drop-animation.d.ts +13 -0
  105. package/build-types/shared/drag-overlay-drop-animation.d.ts.map +1 -0
  106. package/build-types/shared/grid-item-key.d.ts +6 -0
  107. package/build-types/shared/grid-item-key.d.ts.map +1 -0
  108. package/build-types/shared/grid-overlay.d.ts +19 -0
  109. package/build-types/shared/grid-overlay.d.ts.map +1 -0
  110. package/build-types/shared/item-exit-overlay.d.ts +20 -0
  111. package/build-types/shared/item-exit-overlay.d.ts.map +1 -0
  112. package/build-types/shared/resize-handle.d.ts +23 -0
  113. package/build-types/shared/resize-handle.d.ts.map +1 -0
  114. package/build-types/shared/resize-snap.d.ts +41 -0
  115. package/build-types/shared/resize-snap.d.ts.map +1 -0
  116. package/build-types/shared/types.d.ts +144 -0
  117. package/build-types/shared/types.d.ts.map +1 -0
  118. package/build-types/shared/use-item-exit-animation.d.ts +37 -0
  119. package/build-types/shared/use-item-exit-animation.d.ts.map +1 -0
  120. package/build-types/shared/use-layout-shift-animation.d.ts +77 -0
  121. package/build-types/shared/use-layout-shift-animation.d.ts.map +1 -0
  122. package/package.json +80 -0
  123. package/src/dashboard-grid/grid-item.module.css +94 -0
  124. package/src/dashboard-grid/grid-item.tsx +205 -0
  125. package/src/dashboard-grid/grid.module.css +134 -0
  126. package/src/dashboard-grid/index.tsx +713 -0
  127. package/src/dashboard-grid/resolve-fill-widths.ts +224 -0
  128. package/src/dashboard-grid/stories/index.story.tsx +930 -0
  129. package/src/dashboard-grid/test/keyboard-activation.test.tsx +76 -0
  130. package/src/dashboard-grid/test/resolve-fill-widths.test.ts +250 -0
  131. package/src/dashboard-grid/types.ts +271 -0
  132. package/src/dashboard-lanes/index.tsx +629 -0
  133. package/src/dashboard-lanes/lane-placement.ts +245 -0
  134. package/src/dashboard-lanes/lanes-item.module.css +93 -0
  135. package/src/dashboard-lanes/lanes-item.tsx +236 -0
  136. package/src/dashboard-lanes/lanes.module.css +152 -0
  137. package/src/dashboard-lanes/stories/index.story.tsx +518 -0
  138. package/src/dashboard-lanes/test/keyboard-activation.test.tsx +71 -0
  139. package/src/dashboard-lanes/test/lane-placement.test.ts +442 -0
  140. package/src/dashboard-lanes/test/use-lane-placement.test.tsx +358 -0
  141. package/src/dashboard-lanes/types.ts +176 -0
  142. package/src/dashboard-lanes/use-lane-placement.ts +313 -0
  143. package/src/index.ts +17 -0
  144. package/src/shared/actionable-area-slot.module.css +16 -0
  145. package/src/shared/drag-overlay-drop-animation.ts +66 -0
  146. package/src/shared/grid-item-key.ts +5 -0
  147. package/src/shared/grid-overlay.module.css +82 -0
  148. package/src/shared/grid-overlay.tsx +93 -0
  149. package/src/shared/item-exit-animation.module.css +49 -0
  150. package/src/shared/item-exit-overlay.tsx +57 -0
  151. package/src/shared/layout-shift-animation.module.css +16 -0
  152. package/src/shared/resize-handle.module.css +88 -0
  153. package/src/shared/resize-handle.tsx +163 -0
  154. package/src/shared/resize-snap.ts +63 -0
  155. package/src/shared/test/resize-snap.test.ts +35 -0
  156. package/src/shared/types.ts +164 -0
  157. package/src/shared/use-item-exit-animation.ts +199 -0
  158. package/src/shared/use-layout-shift-animation.ts +284 -0
@@ -0,0 +1,77 @@
1
+ export type RectSnapshot = {
2
+ left: number;
3
+ top: number;
4
+ width: number;
5
+ height: number;
6
+ };
7
+ type UseLayoutShiftAnimationOptions = {
8
+ /**
9
+ * Surface root that contains grid tiles.
10
+ */
11
+ container: HTMLElement | null;
12
+ /**
13
+ * When false, snapshots are cleared and no transforms run.
14
+ */
15
+ enabled: boolean;
16
+ /**
17
+ * Serialized layout/placement state. The hook runs FLIP when this
18
+ * value changes while `enabled` is true.
19
+ */
20
+ layoutFingerprint: string;
21
+ /**
22
+ * Item key to skip (the tile being dragged or resized).
23
+ */
24
+ excludeItemKey?: string | null;
25
+ };
26
+ type UseLayoutShiftAnimationResult = {
27
+ /**
28
+ * Capture tile positions synchronously **before** a layout update
29
+ * (call immediately before `setTemporaryLayout` / similar).
30
+ */
31
+ captureLayoutSnapshot: () => void;
32
+ /**
33
+ * Container-relative rects from the last committed paint (settled, no
34
+ * FLIP invert transforms).
35
+ */
36
+ getLastPositions: () => ReadonlyMap<string, RectSnapshot> | null;
37
+ /**
38
+ * Tile positions immediately before the latest layout commit. Used
39
+ * by item-exit animation when keys drop out of `layout`.
40
+ */
41
+ getPositionsBeforeLastChange: () => ReadonlyMap<string, RectSnapshot> | null;
42
+ };
43
+ /**
44
+ * Animates sibling tiles when grid layout reflows during drag or resize
45
+ * using a FLIP transform (see `layout-shift-animation.module.css`).
46
+ *
47
+ * @param root0 Hook options.
48
+ * @param root0.container Surface root that contains grid tiles.
49
+ * @param root0.enabled When false, snapshots are cleared and no transforms run.
50
+ * @param root0.layoutFingerprint Serialized layout/placement state.
51
+ * @param root0.excludeItemKey Item key to skip (the tile being dragged or resized).
52
+ * @return Snapshot capture callback for use before layout updates.
53
+ */
54
+ export declare function useLayoutShiftAnimation({ container, enabled, layoutFingerprint, excludeItemKey }: UseLayoutShiftAnimationOptions): UseLayoutShiftAnimationResult;
55
+ /**
56
+ * Stable fingerprint for {@link useLayoutShiftAnimation}. Width/height
57
+ * values may be numbers or layout keywords (`'fill'`, `'full'`).
58
+ *
59
+ * @param layout Layout items to serialize.
60
+ * @return Fingerprint string.
61
+ */
62
+ export declare function getLayoutFingerprint(layout: ReadonlyArray<{
63
+ key: string;
64
+ width?: number | string;
65
+ height?: number;
66
+ order?: number;
67
+ lane?: number;
68
+ }>): string;
69
+ /**
70
+ * Placement fingerprint for lanes polyfill / explicit grid positions.
71
+ *
72
+ * @param itemStyles Per-item inline placement styles.
73
+ * @return Fingerprint string.
74
+ */
75
+ export declare function getPlacementFingerprint(itemStyles: Map<string, React.CSSProperties>): string;
76
+ export {};
77
+ //# sourceMappingURL=use-layout-shift-animation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-layout-shift-animation.d.ts","sourceRoot":"","sources":["../../src/shared/use-layout-shift-animation.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,8BAA8B,GAAG;IACrC;;OAEG;IACH,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAE9B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACpC;;;OAGG;IACH,qBAAqB,EAAE,MAAM,IAAI,CAAC;IAElC;;;OAGG;IACH,gBAAgB,EAAE,MAAM,WAAW,CAAE,MAAM,EAAE,YAAY,CAAE,GAAG,IAAI,CAAC;IAEnE;;;OAGG;IACH,4BAA4B,EAAE,MAAM,WAAW,CAC9C,MAAM,EACN,YAAY,CACZ,GAAG,IAAI,CAAC;CACT,CAAC;AA0EF;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAE,EACxC,SAAS,EACT,OAAO,EACP,iBAAiB,EACjB,cAAqB,EACrB,EAAE,8BAA8B,GAAI,6BAA6B,CAkFjE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CACnC,MAAM,EAAE,aAAa,CAAE;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACd,CAAE,GACD,MAAM,CASR;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACtC,UAAU,EAAE,GAAG,CAAE,MAAM,EAAE,KAAK,CAAC,aAAa,CAAE,GAC5C,MAAM,CAaR"}
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@wordpress/grid",
3
+ "version": "0.1.0",
4
+ "description": "Grid layout components for arranging tiles in dashboard-style surfaces, with drag-to-reorder and tile resizing.",
5
+ "author": "The WordPress Contributors",
6
+ "license": "GPL-2.0-or-later",
7
+ "keywords": [
8
+ "wordpress",
9
+ "gutenberg",
10
+ "grid",
11
+ "dashboard"
12
+ ],
13
+ "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/grid/README.md",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/WordPress/gutenberg.git",
17
+ "directory": "packages/grid"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/WordPress/gutenberg/issues"
21
+ },
22
+ "engines": {
23
+ "node": ">=20.10.0",
24
+ "npm": ">=10.2.3"
25
+ },
26
+ "files": [
27
+ "src",
28
+ "build",
29
+ "build-module",
30
+ "build-types",
31
+ "*.md"
32
+ ],
33
+ "main": "build/index.cjs",
34
+ "module": "build-module/index.mjs",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./build-types/index.d.ts",
38
+ "import": "./build-module/index.mjs",
39
+ "default": "./build/index.cjs"
40
+ },
41
+ "./package.json": "./package.json"
42
+ },
43
+ "wpScript": false,
44
+ "types": "build-types",
45
+ "sideEffects": [
46
+ "src/**/*.module.css"
47
+ ],
48
+ "dependencies": {
49
+ "@dnd-kit/core": "^6.3.1",
50
+ "@dnd-kit/sortable": "^10.0.0",
51
+ "@dnd-kit/utilities": "^3.2.2",
52
+ "@wordpress/compose": "^8.2.0",
53
+ "@wordpress/element": "^8.1.0",
54
+ "@wordpress/style-runtime": "^0.5.0",
55
+ "clsx": "^2.1.1"
56
+ },
57
+ "devDependencies": {
58
+ "@storybook/react-vite": "^10.4.3",
59
+ "@testing-library/dom": "^10.4.1",
60
+ "@testing-library/react": "^16.3.2",
61
+ "@types/jest": "^29.5.14",
62
+ "@wordpress/icons": "^15.0.0",
63
+ "@wordpress/ui": "^0.16.0",
64
+ "storybook": "^10.4.3"
65
+ },
66
+ "peerDependencies": {
67
+ "@types/react": "^18.3.27",
68
+ "react": "^18.0.0",
69
+ "react-dom": "^18.0.0"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "@types/react": {
73
+ "optional": true
74
+ }
75
+ },
76
+ "publishConfig": {
77
+ "access": "public"
78
+ },
79
+ "gitHead": "0e7112a4f4fde4ea15bd9060489b8f6fe11eb6ca"
80
+ }
@@ -0,0 +1,94 @@
1
+ .item {
2
+ position: relative;
3
+ }
4
+
5
+ .item-content {
6
+ position: relative;
7
+ height: 100%;
8
+ }
9
+
10
+ .is-resizing {
11
+ overflow: visible;
12
+ z-index: 1;
13
+ }
14
+
15
+ .is-resizing .item-content {
16
+ position: relative;
17
+ z-index: 2;
18
+ overflow: visible;
19
+ }
20
+
21
+ /*
22
+ * During drag, the original item acts as a placeholder in its grid
23
+ * cell while `<DragOverlay>` renders a clone that follows the cursor.
24
+ * Fading the placeholder and outlining it makes the destination visible
25
+ * without any scaling or translation on the original element.
26
+ *
27
+ * Placeholder chrome waits until `data-wp-grid-dragging` is set and the
28
+ * drag-preview enter animation (`--wpds-motion-duration-sm`) finishes
29
+ * so the dashed outline does not flash under the lifting clone.
30
+ */
31
+ .is-dragging {
32
+ pointer-events: none;
33
+ }
34
+
35
+ :global([data-wp-grid-dragging]) .is-dragging {
36
+ border-radius: var(--wp-grid-placeholder-radius, 0);
37
+ }
38
+
39
+ @media not (prefers-reduced-motion: reduce) {
40
+ :global([data-wp-grid-dragging]) .is-dragging {
41
+ opacity: 1;
42
+ outline-width: 0;
43
+ outline-style: var(--wp-grid-placeholder-outline-style, dashed);
44
+ outline-color: transparent;
45
+ animation:
46
+ wp-grid-item-placeholder-in 0ms linear
47
+ var(--wpds-motion-duration-sm) forwards;
48
+ }
49
+
50
+ @keyframes wp-grid-item-placeholder-in {
51
+ to {
52
+ opacity: var(--wp-grid-placeholder-opacity, 0.4);
53
+ outline-width: var(--wpds-border-width-sm);
54
+ outline-color: var(--wp-grid-placeholder-outline-color, var(--wpds-color-stroke-interactive-brand));
55
+ }
56
+ }
57
+ }
58
+
59
+ @media (prefers-reduced-motion: reduce) {
60
+ :global([data-wp-grid-dragging]) .is-dragging {
61
+ opacity: var(--wp-grid-placeholder-opacity, 0.4);
62
+ outline:
63
+ var(--wpds-border-width-sm)
64
+ var(--wp-grid-placeholder-outline-style, dashed)
65
+ var(--wp-grid-placeholder-outline-color, var(--wpds-color-stroke-interactive-brand));
66
+ }
67
+ }
68
+
69
+ @media (forced-colors: active) {
70
+ :global([data-wp-grid-dragging]) .is-dragging {
71
+ --wp-grid-placeholder-outline-color: Highlight;
72
+ }
73
+ }
74
+
75
+ .preview-overlay {
76
+ position: absolute;
77
+ top: 0;
78
+ inset-inline-start: 0;
79
+ box-sizing: border-box;
80
+ pointer-events: none;
81
+ z-index: 0;
82
+ border:
83
+ var(--wpds-border-width-sm)
84
+ var(--wp-grid-resize-preview-outline-style, solid)
85
+ var(--wp-grid-placeholder-outline-color, var(--wpds-color-stroke-interactive-brand));
86
+ background: transparent;
87
+ border-radius: var(--wp-grid-placeholder-radius, 0);
88
+ }
89
+
90
+ @media (forced-colors: active) {
91
+ .preview-overlay {
92
+ border-color: Highlight;
93
+ }
94
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { useSortable } from '@dnd-kit/sortable';
5
+ import clsx from 'clsx';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { useState, useRef } from '@wordpress/element';
11
+ import { useMergeRefs } from '@wordpress/compose';
12
+
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ import actionableAreaStyles from '../shared/actionable-area-slot.module.css';
17
+ import { GRID_ITEM_DATA_KEY } from '../shared/grid-item-key';
18
+ import ResizeHandle from '../shared/resize-handle';
19
+ import { clampResizeDelta, type ResizeSnapSize } from '../shared/resize-snap';
20
+ import type { ResizeDelta } from '../shared/types';
21
+ import type { GridItemProps } from './types';
22
+ import styles from './grid-item.module.css';
23
+
24
+ function getItemCursor(
25
+ disabled: boolean,
26
+ interacting: boolean
27
+ ): React.CSSProperties[ 'cursor' ] {
28
+ if ( disabled ) {
29
+ return 'default';
30
+ }
31
+
32
+ if ( interacting ) {
33
+ return undefined;
34
+ }
35
+
36
+ return 'grab';
37
+ }
38
+
39
+ export function GridItem( {
40
+ item,
41
+ maxColumns,
42
+ disabled = false,
43
+ verticalResizable = true,
44
+ interacting = false,
45
+ dragging = false,
46
+ children,
47
+ actionableArea = null,
48
+ onResize,
49
+ onResizeEnd,
50
+ resizeSnapPreview = null,
51
+ minResizeWidthPx,
52
+ minResizeHeightPx,
53
+ renderResizeHandle,
54
+ }: GridItemProps ) {
55
+ const [ resizeDelta, setResizeDelta ] = useState< ResizeDelta | null >(
56
+ null
57
+ );
58
+ const [ initialContentSize, setInitialContentSize ] = useState< {
59
+ width: number;
60
+ height: number;
61
+ } | null >( null );
62
+ const itemRef = useRef< HTMLDivElement >( null );
63
+ const contentRef = useRef< HTMLDivElement >( null );
64
+ const {
65
+ attributes,
66
+ listeners,
67
+ setNodeRef,
68
+ setActivatorNodeRef,
69
+ isDragging,
70
+ } = useSortable( {
71
+ id: item.key,
72
+ disabled,
73
+ } );
74
+ const mergedRef = useMergeRefs( [ itemRef, setNodeRef ] );
75
+ const contentMergedRef = useMergeRefs( [ contentRef ] );
76
+ /*
77
+ * With `<DragOverlay>` handling the cursor-following clone, the
78
+ * sortable item stays put in its grid cell and acts as a
79
+ * placeholder. No `transform` is applied here — applying one
80
+ * would double-move the placeholder alongside the overlay.
81
+ */
82
+ const style = {
83
+ gridColumnEnd: `span ${
84
+ item.width === 'full'
85
+ ? maxColumns
86
+ : Math.min(
87
+ typeof item.width === 'number' ? item.width : 1,
88
+ maxColumns
89
+ )
90
+ }`,
91
+ gridRowEnd: `span ${ item.height || 1 }`,
92
+ };
93
+
94
+ const isResizing = resizeDelta !== null;
95
+ const itemClassName = clsx(
96
+ styles.item,
97
+ isDragging && styles[ 'is-dragging' ],
98
+ isResizing && styles[ 'is-resizing' ]
99
+ );
100
+
101
+ const handleResize = ( delta: ResizeDelta ) => {
102
+ const contentNode = contentRef.current;
103
+ let baselineSize = initialContentSize;
104
+ if ( contentNode && ! baselineSize ) {
105
+ const { width, height } = contentNode.getBoundingClientRect();
106
+ baselineSize = { width, height };
107
+ setInitialContentSize( baselineSize );
108
+ }
109
+ let clamped: ResizeDelta = {
110
+ width: delta.width,
111
+ height: verticalResizable ? delta.height : 0,
112
+ };
113
+ if ( baselineSize ) {
114
+ clamped = clampResizeDelta( clamped, baselineSize, {
115
+ width: minResizeWidthPx,
116
+ height: verticalResizable ? minResizeHeightPx : undefined,
117
+ } );
118
+ }
119
+ setResizeDelta( clamped );
120
+ onResize( item.key, clamped );
121
+ };
122
+
123
+ const handleResizeEnd = () => {
124
+ setResizeDelta( null );
125
+ setInitialContentSize( null );
126
+ onResizeEnd();
127
+ };
128
+
129
+ const continuousContentStyle: React.CSSProperties | undefined =
130
+ resizeDelta && initialContentSize
131
+ ? {
132
+ width: initialContentSize.width + resizeDelta.width,
133
+ height: verticalResizable
134
+ ? initialContentSize.height + resizeDelta.height
135
+ : undefined,
136
+ }
137
+ : undefined;
138
+
139
+ const previewOverlay = resizeSnapPreview ? (
140
+ <SnapPreviewOverlay snap={ resizeSnapPreview } />
141
+ ) : null;
142
+
143
+ return (
144
+ <div
145
+ ref={ mergedRef }
146
+ className={ itemClassName }
147
+ style={ style }
148
+ { ...{ [ GRID_ITEM_DATA_KEY ]: item.key } }
149
+ data-wp-grid-item-resizing={ isResizing || undefined }
150
+ >
151
+ { actionableArea ? (
152
+ <div
153
+ className={ actionableAreaStyles[ 'actionable-area-slot' ] }
154
+ >
155
+ <div
156
+ style={ { display: 'contents' } }
157
+ { ...( dragging ? { inert: '' } : {} ) }
158
+ >
159
+ { actionableArea }
160
+ </div>
161
+ </div>
162
+ ) : null }
163
+
164
+ <div
165
+ ref={ setActivatorNodeRef }
166
+ { ...attributes }
167
+ { ...listeners }
168
+ style={ {
169
+ height: '100%',
170
+ cursor: getItemCursor( disabled, interacting ),
171
+ } }
172
+ >
173
+ <div
174
+ ref={ contentMergedRef }
175
+ className={ styles[ 'item-content' ] }
176
+ style={ continuousContentStyle }
177
+ >
178
+ { children }
179
+ { ! disabled && (
180
+ <ResizeHandle
181
+ itemId={ item.key }
182
+ verticalResizable={ verticalResizable }
183
+ onResize={ handleResize }
184
+ onResizeEnd={ handleResizeEnd }
185
+ renderResizeHandle={ renderResizeHandle }
186
+ />
187
+ ) }
188
+ </div>
189
+ { previewOverlay }
190
+ </div>
191
+ </div>
192
+ );
193
+ }
194
+
195
+ function SnapPreviewOverlay( { snap }: { snap: ResizeSnapSize } ) {
196
+ return (
197
+ <div
198
+ className={ styles[ 'preview-overlay' ] }
199
+ style={ {
200
+ width: snap.widthPx,
201
+ height: snap.heightPx ?? '100%',
202
+ } }
203
+ />
204
+ );
205
+ }
@@ -0,0 +1,134 @@
1
+ .grid {
2
+ display: grid;
3
+ gap: var(--wp-grid-gap, var(--wpds-dimension-gap-xl));
4
+ position: relative;
5
+ }
6
+
7
+ /*
8
+ * Functional frame for the drag-preview clone inside `<DragOverlay>`.
9
+ * Always applied, including when the consumer passes a
10
+ * `renderDragPreview` wrapper. The outer frame has no `transform` so
11
+ * @dnd-kit’s drop translation matches the placeholder; scale lives on
12
+ * `__lift` (see dnd-kit #398). Owns elevation, radius, cursor, and
13
+ * pointer pass-through. Further chrome belongs to the consumer. Drag
14
+ * elevation animates `xs` → `md` so it reads above edit tiles at `xs`.
15
+ */
16
+ .drag-preview-frame {
17
+ height: 100%;
18
+ border-radius: var(--wp-grid-drag-preview-radius, 0);
19
+ cursor: grabbing;
20
+ pointer-events: none;
21
+ box-shadow: var(--wpds-elevation-md);
22
+ }
23
+
24
+ .drag-preview-frame__lift {
25
+ height: 100%;
26
+ transform: scale(var(--wp-grid-drag-preview-scale, 1.05));
27
+ transform-origin: center;
28
+ }
29
+
30
+ @media not (prefers-reduced-motion: reduce) {
31
+ .drag-preview-frame {
32
+ animation:
33
+ wp-grid-drag-preview-shadow-enter
34
+ var(--wpds-motion-duration-sm) var(--wpds-motion-easing-balanced) both;
35
+ }
36
+
37
+ .drag-preview-frame__lift {
38
+ animation:
39
+ wp-grid-drag-preview-scale-enter
40
+ var(--wpds-motion-duration-sm) var(--wpds-motion-easing-balanced) both;
41
+ }
42
+ }
43
+
44
+ @media not (prefers-reduced-motion: reduce) {
45
+ @keyframes wp-grid-drag-preview-shadow-enter {
46
+ from {
47
+ box-shadow: var(--wpds-elevation-xs);
48
+ }
49
+
50
+ to {
51
+ box-shadow: var(--wpds-elevation-md);
52
+ }
53
+ }
54
+
55
+ @keyframes wp-grid-drag-preview-scale-enter {
56
+ from {
57
+ transform: scale(1);
58
+ }
59
+
60
+ to {
61
+ transform: scale(var(--wp-grid-drag-preview-scale, 1.05));
62
+ }
63
+ }
64
+ }
65
+
66
+ /*
67
+ * Applied by @dnd-kit `DragOverlay` drop side-effects while the
68
+ * default overlay transform runs; duration matches
69
+ * `createDashboardDragDropAnimation` (200ms / md token). Use keyframed
70
+ * exit (not transition) so scale does not snap when the enter animation
71
+ * is replaced.
72
+ */
73
+ @media not (prefers-reduced-motion: reduce) {
74
+ .drag-preview-frame.dragPreviewFrameExiting {
75
+ animation:
76
+ wp-grid-drag-preview-shadow-exit
77
+ var(--wpds-motion-duration-md) var(--wpds-motion-easing-balanced)
78
+ forwards;
79
+ }
80
+
81
+ .drag-preview-frame.dragPreviewFrameExiting .drag-preview-frame__lift {
82
+ animation:
83
+ wp-grid-drag-preview-scale-exit
84
+ var(--wpds-motion-duration-md) var(--wpds-motion-easing-balanced)
85
+ forwards;
86
+ }
87
+
88
+ @keyframes wp-grid-drag-preview-shadow-exit {
89
+ from {
90
+ box-shadow: var(--wpds-elevation-md);
91
+ }
92
+
93
+ to {
94
+ box-shadow: var(--wpds-elevation-xs);
95
+ }
96
+ }
97
+
98
+ @keyframes wp-grid-drag-preview-scale-exit {
99
+ from {
100
+ transform: scale(var(--wp-grid-drag-preview-scale, 1.05));
101
+ }
102
+
103
+ to {
104
+ transform: scale(1);
105
+ }
106
+ }
107
+ }
108
+
109
+ .drag-preview-frame.dragPreviewFrameExiting {
110
+ box-shadow: var(--wpds-elevation-xs);
111
+ }
112
+
113
+ .drag-preview-frame.dragPreviewFrameExiting .drag-preview-frame__lift {
114
+ transform: scale(1);
115
+ }
116
+
117
+ @media (prefers-reduced-motion: reduce) {
118
+ .drag-preview-frame {
119
+ box-shadow: var(--wpds-elevation-md);
120
+ }
121
+
122
+ .drag-preview-frame__lift {
123
+ transform: none;
124
+ }
125
+
126
+ .drag-preview-frame.dragPreviewFrameExiting {
127
+ box-shadow: var(--wpds-elevation-xs);
128
+ transition: none;
129
+ }
130
+
131
+ .drag-preview-frame.dragPreviewFrameExiting .drag-preview-frame__lift {
132
+ transition: none;
133
+ }
134
+ }