@wordpress/grid 0.1.1-next.v.202606191442.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,245 @@
1
+ /**
2
+ * Lane placement algorithm for `display: grid-lanes` polyfill.
3
+ *
4
+ * Implements the source-ordered, shortest-lane placement described in
5
+ * https://webkit.org/blog/17660/introducing-css-grid-lanes/.
6
+ *
7
+ * The skyline + tolerance core is adapted from the CSS Grid Lanes
8
+ * Polyfill by Simon Willison (MIT,
9
+ * https://tools.simonwillison.net/grid-lanes-polyfill.js). The rest
10
+ * of this module is a pure function suitable for unit testing in
11
+ * isolation from any DOM.
12
+ *
13
+ * Concepts:
14
+ * - "Lane" is the cross-axis track: a column in waterfall mode, a row
15
+ * in brick mode. The algorithm is axis-agnostic; the renderer maps
16
+ * the chosen lane index plus offset to `grid-column-start` /
17
+ * `grid-row-start` (or vice versa).
18
+ * - Items are placed in source order. Explicit-lane items are placed
19
+ * first so auto-placed items can flow around them.
20
+ * - `flowTolerance` is a length: when two candidate lanes differ in
21
+ * baseline by no more than this, the earlier lane wins (preserves
22
+ * reading order).
23
+ */
24
+
25
+ /**
26
+ * A single item to place. Heights are pre-measured by the caller.
27
+ */
28
+ export type LanePlacementItem = {
29
+ /**
30
+ * Stable identity. Returned in the result map.
31
+ */
32
+ key: string;
33
+
34
+ /**
35
+ * Number of contiguous lanes this item occupies. Clamped to
36
+ * `[ 1, lanes ]` by the algorithm.
37
+ */
38
+ span: number;
39
+
40
+ /**
41
+ * Measured cross-axis size (typically pixels). The algorithm only
42
+ * adds and compares heights; the unit is whatever the caller uses,
43
+ * as long as `gap` and `flowTolerance` use the same one.
44
+ */
45
+ height: number;
46
+
47
+ /**
48
+ * Explicit 0-indexed starting lane. When set, the item bypasses
49
+ * the skyline lookup and is placed at this lane regardless of
50
+ * source order. Out-of-range values are clamped.
51
+ */
52
+ lane?: number;
53
+ };
54
+
55
+ /**
56
+ * Algorithm input.
57
+ */
58
+ export type LanePlacementInput = {
59
+ /**
60
+ * Items in source order.
61
+ */
62
+ items: ReadonlyArray< LanePlacementItem >;
63
+
64
+ /**
65
+ * Total number of lanes. Clamped to `>= 1`.
66
+ */
67
+ lanes: number;
68
+
69
+ /**
70
+ * Gap between items in the same lane. Same unit as `height`.
71
+ */
72
+ gap: number;
73
+
74
+ /**
75
+ * Tolerance for source-order tiebreaking. When two candidate
76
+ * lanes have baselines within this amount, the earlier lane wins.
77
+ * Defaults to `0` if a negative value is passed.
78
+ */
79
+ flowTolerance: number;
80
+ };
81
+
82
+ /**
83
+ * Resolved position for a single item.
84
+ */
85
+ export type LanePlacement = {
86
+ /**
87
+ * Mirrors the input key.
88
+ */
89
+ key: string;
90
+
91
+ /**
92
+ * 0-indexed starting lane. The renderer adds 1 for
93
+ * `grid-column-start`.
94
+ */
95
+ lane: number;
96
+
97
+ /**
98
+ * Cross-axis offset from the container's start edge, in the same
99
+ * unit as the input heights. Use as the item's start position
100
+ * (e.g. `top`, or `grid-row-start` after dividing by a row unit).
101
+ */
102
+ top: number;
103
+
104
+ /**
105
+ * Effective span after clamping. Useful for the renderer when the
106
+ * input span exceeded the lane count.
107
+ */
108
+ span: number;
109
+ };
110
+
111
+ /**
112
+ * Algorithm output.
113
+ */
114
+ export type LanePlacementResult = {
115
+ /**
116
+ * Per-key placement. Insertion-ordered: the first iteration yields
117
+ * the explicit items in source order, then the auto items in
118
+ * source order.
119
+ */
120
+ placements: Map< string, LanePlacement >;
121
+
122
+ /**
123
+ * Sum of the tallest lane after all items are placed. The renderer
124
+ * applies this as the container's intrinsic height.
125
+ */
126
+ totalHeight: number;
127
+ };
128
+
129
+ function clampSpan( span: number, lanes: number ): number {
130
+ if ( ! Number.isFinite( span ) ) {
131
+ return 1;
132
+ }
133
+ return Math.max( 1, Math.min( Math.floor( span ), lanes ) );
134
+ }
135
+
136
+ function clampLane( lane: number, span: number, lanes: number ): number {
137
+ if ( ! Number.isFinite( lane ) ) {
138
+ return 0;
139
+ }
140
+ return Math.max( 0, Math.min( Math.floor( lane ), lanes - span ) );
141
+ }
142
+
143
+ function maxBaselineAcross(
144
+ laneBottoms: ReadonlyArray< number >,
145
+ startLane: number,
146
+ span: number
147
+ ): number {
148
+ let maxBaseline = 0;
149
+ for ( let i = startLane; i < startLane + span; i++ ) {
150
+ if ( laneBottoms[ i ] > maxBaseline ) {
151
+ maxBaseline = laneBottoms[ i ];
152
+ }
153
+ }
154
+ return maxBaseline;
155
+ }
156
+
157
+ /**
158
+ * Places all items into a fixed lane count using the grid-lanes
159
+ * algorithm: explicit items first, then auto items chosen by the
160
+ * shortest-lane skyline with a tolerance for source order.
161
+ *
162
+ * Pure: no DOM access, no mutation of inputs. Safe to call from a
163
+ * worker or during SSR.
164
+ *
165
+ * @param input Items, lane count, gap, and tolerance.
166
+ * @return Per-key placements plus the resulting total height.
167
+ */
168
+ export function computeLanePlacements(
169
+ input: LanePlacementInput
170
+ ): LanePlacementResult {
171
+ const lanes = Math.max( 1, Math.floor( input.lanes ) );
172
+ const gap = Math.max( 0, input.gap );
173
+ const tolerance = Math.max( 0, input.flowTolerance );
174
+
175
+ const laneBottoms = new Array< number >( lanes ).fill( 0 );
176
+ const placements = new Map< string, LanePlacement >();
177
+
178
+ const explicitItems: LanePlacementItem[] = [];
179
+ const autoItems: LanePlacementItem[] = [];
180
+ for ( const item of input.items ) {
181
+ if ( item.lane !== undefined ) {
182
+ explicitItems.push( item );
183
+ } else {
184
+ autoItems.push( item );
185
+ }
186
+ }
187
+
188
+ for ( const item of explicitItems ) {
189
+ const span = clampSpan( item.span, lanes );
190
+ const lane = clampLane( item.lane as number, span, lanes );
191
+ const baseline = maxBaselineAcross( laneBottoms, lane, span );
192
+ const top = baseline === 0 ? 0 : baseline + gap;
193
+ const height = Math.max( 0, item.height );
194
+
195
+ placements.set( item.key, { key: item.key, lane, top, span } );
196
+
197
+ const newBottom = top + height;
198
+ for ( let i = lane; i < lane + span; i++ ) {
199
+ laneBottoms[ i ] = newBottom;
200
+ }
201
+ }
202
+
203
+ for ( const item of autoItems ) {
204
+ const span = clampSpan( item.span, lanes );
205
+ let bestLane = 0;
206
+ let bestBaseline = Infinity;
207
+
208
+ for ( let candidate = 0; candidate <= lanes - span; candidate++ ) {
209
+ const baseline = maxBaselineAcross( laneBottoms, candidate, span );
210
+
211
+ // Only take a lane that is strictly shorter beyond
212
+ // tolerance. Within-tolerance ties keep the earlier lane
213
+ // because candidates iterate in lane order, so the first
214
+ // acceptable baseline wins.
215
+ if ( bestBaseline - baseline > tolerance ) {
216
+ bestBaseline = baseline;
217
+ bestLane = candidate;
218
+ }
219
+ }
220
+
221
+ const top = bestBaseline === 0 ? 0 : bestBaseline + gap;
222
+ const height = Math.max( 0, item.height );
223
+
224
+ placements.set( item.key, {
225
+ key: item.key,
226
+ lane: bestLane,
227
+ top,
228
+ span,
229
+ } );
230
+
231
+ const newBottom = top + height;
232
+ for ( let i = bestLane; i < bestLane + span; i++ ) {
233
+ laneBottoms[ i ] = newBottom;
234
+ }
235
+ }
236
+
237
+ let totalHeight = 0;
238
+ for ( const bottom of laneBottoms ) {
239
+ if ( bottom > totalHeight ) {
240
+ totalHeight = bottom;
241
+ }
242
+ }
243
+
244
+ return { placements, totalHeight };
245
+ }
@@ -0,0 +1,93 @@
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 lane
23
+ * 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
+ }
88
+
89
+ @media (forced-colors: active) {
90
+ .preview-overlay {
91
+ border-color: Highlight;
92
+ }
93
+ }
@@ -0,0 +1,236 @@
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 ResizeHandle from '../shared/resize-handle';
18
+ import { clampResizeDelta, type ResizeSnapSize } from '../shared/resize-snap';
19
+ import { GRID_ITEM_DATA_KEY } from '../shared/grid-item-key';
20
+ import type { ResizeDelta, ResizeHandleRenderProps } from '../shared/types';
21
+ import styles from './lanes-item.module.css';
22
+
23
+ function getItemCursor(
24
+ disabled: boolean,
25
+ interacting: boolean
26
+ ): React.CSSProperties[ 'cursor' ] {
27
+ if ( disabled ) {
28
+ return 'default';
29
+ }
30
+ if ( interacting ) {
31
+ return undefined;
32
+ }
33
+ return 'grab';
34
+ }
35
+
36
+ /**
37
+ * Props for the internal `<LanesItem />` wrapper.
38
+ */
39
+ export type LanesItemProps = {
40
+ /**
41
+ * Item key. Forwarded to dnd-kit and emitted as the
42
+ * `data-wp-grid-item-key` attribute the hook reads to map measured DOM
43
+ * nodes back to logical items.
44
+ */
45
+ itemKey: string;
46
+
47
+ /**
48
+ * Inline placement style produced by `useLanePlacement`. On native
49
+ * (`display: grid-lanes`), only `gridColumn: span N`. While
50
+ * polyfilling, also `gridColumnStart` / `gridRowStart` /
51
+ * `gridRowEnd: span N`.
52
+ */
53
+ placementStyle: React.CSSProperties;
54
+
55
+ /**
56
+ * Whether drag and resize interactions are disabled.
57
+ */
58
+ disabled?: boolean;
59
+
60
+ /**
61
+ * Whether any tile in the surface is currently being dragged or
62
+ * resized. Drives the drag activator cursor.
63
+ */
64
+ interacting?: boolean;
65
+
66
+ /**
67
+ * Whether a tile drag is in progress. Mutes each tile's
68
+ * `actionableArea` with `inert` so hovers on other tiles' controls
69
+ * do not steal the gesture.
70
+ *
71
+ * @default false
72
+ */
73
+ dragging?: boolean;
74
+
75
+ children: React.ReactNode;
76
+
77
+ actionableArea?: React.ReactNode;
78
+
79
+ onResize: ( id: string, delta: ResizeDelta ) => void;
80
+
81
+ /**
82
+ * Snapped column span in pixels for the resize-preview outline.
83
+ */
84
+ resizeSnapPreview?: ResizeSnapSize | null;
85
+
86
+ /**
87
+ * Minimum tile width while resizing, in pixels (one column track).
88
+ */
89
+ minResizeWidthPx: number;
90
+
91
+ onResizeEnd: () => void;
92
+
93
+ renderResizeHandle?: React.ComponentType< ResizeHandleRenderProps >;
94
+ };
95
+
96
+ export function LanesItem( {
97
+ itemKey,
98
+ placementStyle,
99
+ disabled = false,
100
+ interacting = false,
101
+ children,
102
+ actionableArea = null,
103
+ onResize,
104
+ onResizeEnd,
105
+ resizeSnapPreview = null,
106
+ minResizeWidthPx,
107
+ renderResizeHandle,
108
+ dragging = false,
109
+ }: LanesItemProps ) {
110
+ const [ resizeDelta, setResizeDelta ] = useState< ResizeDelta | null >(
111
+ null
112
+ );
113
+ const [ initialContentSize, setInitialContentSize ] = useState< {
114
+ width: number;
115
+ height: number;
116
+ } | null >( null );
117
+ const itemRef = useRef< HTMLDivElement >( null );
118
+ const contentRef = useRef< HTMLDivElement >( null );
119
+
120
+ const {
121
+ attributes,
122
+ listeners,
123
+ setNodeRef,
124
+ setActivatorNodeRef,
125
+ isDragging,
126
+ } = useSortable( {
127
+ id: itemKey,
128
+ disabled,
129
+ } );
130
+ const mergedRef = useMergeRefs( [ itemRef, setNodeRef ] );
131
+ const contentMergedRef = useMergeRefs( [ contentRef ] );
132
+
133
+ const style: React.CSSProperties = {
134
+ ...placementStyle,
135
+ alignSelf: 'start',
136
+ };
137
+
138
+ const isResizing = resizeDelta !== null;
139
+ const itemClassName = clsx(
140
+ styles.item,
141
+ isDragging && styles[ 'is-dragging' ],
142
+ isResizing && styles[ 'is-resizing' ]
143
+ );
144
+
145
+ const handleResize = ( delta: ResizeDelta ) => {
146
+ const contentNode = contentRef.current;
147
+ let baselineSize = initialContentSize;
148
+ if ( contentNode && ! baselineSize ) {
149
+ const { width, height } = contentNode.getBoundingClientRect();
150
+ baselineSize = { width, height };
151
+ setInitialContentSize( baselineSize );
152
+ }
153
+ let clamped: ResizeDelta = { width: delta.width, height: 0 };
154
+ if ( baselineSize ) {
155
+ clamped = clampResizeDelta( clamped, baselineSize, {
156
+ width: minResizeWidthPx,
157
+ } );
158
+ }
159
+ setResizeDelta( clamped );
160
+ onResize( itemKey, clamped );
161
+ };
162
+
163
+ const handleResizeEnd = () => {
164
+ setResizeDelta( null );
165
+ setInitialContentSize( null );
166
+ onResizeEnd();
167
+ };
168
+
169
+ const continuousContentStyle: React.CSSProperties | undefined =
170
+ resizeDelta && initialContentSize
171
+ ? {
172
+ width: initialContentSize.width + resizeDelta.width,
173
+ }
174
+ : undefined;
175
+
176
+ const previewOverlay = resizeSnapPreview ? (
177
+ <div
178
+ className={ styles[ 'preview-overlay' ] }
179
+ style={ {
180
+ width: resizeSnapPreview.widthPx,
181
+ height: resizeSnapPreview.heightPx ?? '100%',
182
+ } }
183
+ />
184
+ ) : null;
185
+
186
+ return (
187
+ <div
188
+ ref={ mergedRef }
189
+ className={ itemClassName }
190
+ style={ style }
191
+ { ...{ [ GRID_ITEM_DATA_KEY ]: itemKey } }
192
+ data-wp-grid-item-resizing={ isResizing || undefined }
193
+ >
194
+ { actionableArea ? (
195
+ <div
196
+ className={ actionableAreaStyles[ 'actionable-area-slot' ] }
197
+ >
198
+ <div
199
+ style={ { display: 'contents' } }
200
+ { ...( dragging ? { inert: '' } : {} ) }
201
+ >
202
+ { actionableArea }
203
+ </div>
204
+ </div>
205
+ ) : null }
206
+
207
+ <div
208
+ ref={ setActivatorNodeRef }
209
+ { ...attributes }
210
+ { ...listeners }
211
+ style={ {
212
+ height: '100%',
213
+ cursor: getItemCursor( disabled, interacting ),
214
+ } }
215
+ >
216
+ <div
217
+ ref={ contentMergedRef }
218
+ className={ styles[ 'item-content' ] }
219
+ style={ continuousContentStyle }
220
+ >
221
+ { children }
222
+ { ! disabled && (
223
+ <ResizeHandle
224
+ itemId={ itemKey }
225
+ verticalResizable={ false }
226
+ onResize={ handleResize }
227
+ onResizeEnd={ handleResizeEnd }
228
+ renderResizeHandle={ renderResizeHandle }
229
+ />
230
+ ) }
231
+ </div>
232
+ { previewOverlay }
233
+ </div>
234
+ </div>
235
+ );
236
+ }
@@ -0,0 +1,152 @@
1
+ .lanes {
2
+ display: grid-lanes;
3
+ column-gap: var(--wp-grid-gap, var(--wpds-dimension-gap-xl));
4
+ row-gap: var(--wp-grid-gap, var(--wpds-dimension-gap-xl));
5
+ position: relative;
6
+ }
7
+
8
+ /*
9
+ * Polyfill fallback. The hook computes per-item `grid-column-start`
10
+ * and `grid-row-start`/`grid-row-end: span N` against this row unit.
11
+ * The skyline already builds inter-item vertical spacing into each
12
+ * tile's `top`, so `row-gap` must be zero here to avoid compounding
13
+ * on top of the algorithm's spacing. `grid-auto-flow: dense` is a
14
+ * safety net; the algorithm drives placement.
15
+ */
16
+ @supports not (display: grid-lanes) {
17
+ .lanes {
18
+ display: grid;
19
+ grid-auto-rows: var(--wp-grid-lane-row-unit, 4px);
20
+ grid-auto-flow: dense;
21
+ row-gap: 0;
22
+ }
23
+ }
24
+
25
+ /*
26
+ * Functional frame for the drag-preview clone inside `<DragOverlay>`.
27
+ * Always applied, including when the consumer passes a
28
+ * `renderDragPreview` wrapper. The outer frame has no `transform` so
29
+ * @dnd-kit’s drop translation matches the placeholder; scale lives on
30
+ * `__lift` (see dnd-kit #398). Owns elevation, radius, cursor, and
31
+ * pointer pass-through. Further chrome belongs to the consumer. Drag
32
+ * elevation animates `xs` → `md` so it reads above edit tiles at `xs`.
33
+ */
34
+ .drag-preview-frame {
35
+ height: 100%;
36
+ border-radius: var(--wp-grid-drag-preview-radius, 0);
37
+ cursor: grabbing;
38
+ pointer-events: none;
39
+ box-shadow: var(--wpds-elevation-md);
40
+ }
41
+
42
+ .drag-preview-frame__lift {
43
+ height: 100%;
44
+ transform: scale(var(--wp-grid-drag-preview-scale, 1.05));
45
+ transform-origin: center;
46
+ }
47
+
48
+ @media not (prefers-reduced-motion: reduce) {
49
+ .drag-preview-frame {
50
+ animation:
51
+ wp-grid-drag-preview-shadow-enter
52
+ var(--wpds-motion-duration-sm) var(--wpds-motion-easing-balanced) both;
53
+ }
54
+
55
+ .drag-preview-frame__lift {
56
+ animation:
57
+ wp-grid-drag-preview-scale-enter
58
+ var(--wpds-motion-duration-sm) var(--wpds-motion-easing-balanced) both;
59
+ }
60
+ }
61
+
62
+ @media not (prefers-reduced-motion: reduce) {
63
+ @keyframes wp-grid-drag-preview-shadow-enter {
64
+ from {
65
+ box-shadow: var(--wpds-elevation-xs);
66
+ }
67
+
68
+ to {
69
+ box-shadow: var(--wpds-elevation-md);
70
+ }
71
+ }
72
+
73
+ @keyframes wp-grid-drag-preview-scale-enter {
74
+ from {
75
+ transform: scale(1);
76
+ }
77
+
78
+ to {
79
+ transform: scale(var(--wp-grid-drag-preview-scale, 1.05));
80
+ }
81
+ }
82
+ }
83
+
84
+ /*
85
+ * Applied by @dnd-kit `DragOverlay` drop side-effects while the
86
+ * default overlay transform runs; duration matches
87
+ * `createDashboardDragDropAnimation` (200ms / md token). Use keyframed
88
+ * exit (not transition) so scale does not snap when the enter animation
89
+ * is replaced.
90
+ */
91
+ @media not (prefers-reduced-motion: reduce) {
92
+ .drag-preview-frame.dragPreviewFrameExiting {
93
+ animation:
94
+ wp-grid-drag-preview-shadow-exit
95
+ var(--wpds-motion-duration-md) var(--wpds-motion-easing-balanced)
96
+ forwards;
97
+ }
98
+
99
+ .drag-preview-frame.dragPreviewFrameExiting .drag-preview-frame__lift {
100
+ animation:
101
+ wp-grid-drag-preview-scale-exit
102
+ var(--wpds-motion-duration-md) var(--wpds-motion-easing-balanced)
103
+ forwards;
104
+ }
105
+
106
+ @keyframes wp-grid-drag-preview-shadow-exit {
107
+ from {
108
+ box-shadow: var(--wpds-elevation-md);
109
+ }
110
+
111
+ to {
112
+ box-shadow: var(--wpds-elevation-xs);
113
+ }
114
+ }
115
+
116
+ @keyframes wp-grid-drag-preview-scale-exit {
117
+ from {
118
+ transform: scale(var(--wp-grid-drag-preview-scale, 1.05));
119
+ }
120
+
121
+ to {
122
+ transform: scale(1);
123
+ }
124
+ }
125
+ }
126
+
127
+ .drag-preview-frame.dragPreviewFrameExiting {
128
+ box-shadow: var(--wpds-elevation-xs);
129
+ }
130
+
131
+ .drag-preview-frame.dragPreviewFrameExiting .drag-preview-frame__lift {
132
+ transform: scale(1);
133
+ }
134
+
135
+ @media (prefers-reduced-motion: reduce) {
136
+ .drag-preview-frame {
137
+ box-shadow: var(--wpds-elevation-md);
138
+ }
139
+
140
+ .drag-preview-frame__lift {
141
+ transform: none;
142
+ }
143
+
144
+ .drag-preview-frame.dragPreviewFrameExiting {
145
+ box-shadow: var(--wpds-elevation-xs);
146
+ transition: none;
147
+ }
148
+
149
+ .drag-preview-frame.dragPreviewFrameExiting .drag-preview-frame__lift {
150
+ transition: none;
151
+ }
152
+ }