@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.
- package/CHANGELOG.md +33 -0
- package/LICENSE.md +788 -0
- package/README.md +534 -0
- package/build/dashboard-grid/grid-item.cjs +308 -0
- package/build/dashboard-grid/grid-item.cjs.map +7 -0
- package/build/dashboard-grid/index.cjs +591 -0
- package/build/dashboard-grid/index.cjs.map +7 -0
- package/build/dashboard-grid/resolve-fill-widths.cjs +189 -0
- package/build/dashboard-grid/resolve-fill-widths.cjs.map +7 -0
- package/build/dashboard-grid/types.cjs +19 -0
- package/build/dashboard-grid/types.cjs.map +7 -0
- package/build/dashboard-lanes/index.cjs +558 -0
- package/build/dashboard-lanes/index.cjs.map +7 -0
- package/build/dashboard-lanes/lane-placement.cjs +110 -0
- package/build/dashboard-lanes/lane-placement.cjs.map +7 -0
- package/build/dashboard-lanes/lanes-item.cjs +295 -0
- package/build/dashboard-lanes/lanes-item.cjs.map +7 -0
- package/build/dashboard-lanes/types.cjs +19 -0
- package/build/dashboard-lanes/types.cjs.map +7 -0
- package/build/dashboard-lanes/use-lane-placement.cjs +206 -0
- package/build/dashboard-lanes/use-lane-placement.cjs.map +7 -0
- package/build/index.cjs +34 -0
- package/build/index.cjs.map +7 -0
- package/build/shared/drag-overlay-drop-animation.cjs +70 -0
- package/build/shared/drag-overlay-drop-animation.cjs.map +7 -0
- package/build/shared/grid-item-key.cjs +31 -0
- package/build/shared/grid-item-key.cjs.map +7 -0
- package/build/shared/grid-overlay.cjs +187 -0
- package/build/shared/grid-overlay.cjs.map +7 -0
- package/build/shared/item-exit-overlay.cjs +150 -0
- package/build/shared/item-exit-overlay.cjs.map +7 -0
- package/build/shared/resize-handle.cjs +224 -0
- package/build/shared/resize-handle.cjs.map +7 -0
- package/build/shared/resize-snap.cjs +47 -0
- package/build/shared/resize-snap.cjs.map +7 -0
- package/build/shared/types.cjs +19 -0
- package/build/shared/types.cjs.map +7 -0
- package/build/shared/use-item-exit-animation.cjs +148 -0
- package/build/shared/use-item-exit-animation.cjs.map +7 -0
- package/build/shared/use-layout-shift-animation.cjs +167 -0
- package/build/shared/use-layout-shift-animation.cjs.map +7 -0
- package/build-module/dashboard-grid/grid-item.mjs +273 -0
- package/build-module/dashboard-grid/grid-item.mjs.map +7 -0
- package/build-module/dashboard-grid/index.mjs +579 -0
- package/build-module/dashboard-grid/index.mjs.map +7 -0
- package/build-module/dashboard-grid/resolve-fill-widths.mjs +164 -0
- package/build-module/dashboard-grid/resolve-fill-widths.mjs.map +7 -0
- package/build-module/dashboard-grid/types.mjs +1 -0
- package/build-module/dashboard-grid/types.mjs.map +7 -0
- package/build-module/dashboard-lanes/index.mjs +547 -0
- package/build-module/dashboard-lanes/index.mjs.map +7 -0
- package/build-module/dashboard-lanes/lane-placement.mjs +85 -0
- package/build-module/dashboard-lanes/lane-placement.mjs.map +7 -0
- package/build-module/dashboard-lanes/lanes-item.mjs +260 -0
- package/build-module/dashboard-lanes/lanes-item.mjs.map +7 -0
- package/build-module/dashboard-lanes/types.mjs +1 -0
- package/build-module/dashboard-lanes/types.mjs.map +7 -0
- package/build-module/dashboard-lanes/use-lane-placement.mjs +181 -0
- package/build-module/dashboard-lanes/use-lane-placement.mjs.map +7 -0
- package/build-module/index.mjs +8 -0
- package/build-module/index.mjs.map +7 -0
- package/build-module/shared/drag-overlay-drop-animation.mjs +47 -0
- package/build-module/shared/drag-overlay-drop-animation.mjs.map +7 -0
- package/build-module/shared/grid-item-key.mjs +6 -0
- package/build-module/shared/grid-item-key.mjs.map +7 -0
- package/build-module/shared/grid-overlay.mjs +152 -0
- package/build-module/shared/grid-overlay.mjs.map +7 -0
- package/build-module/shared/item-exit-overlay.mjs +125 -0
- package/build-module/shared/item-exit-overlay.mjs.map +7 -0
- package/build-module/shared/resize-handle.mjs +193 -0
- package/build-module/shared/resize-handle.mjs.map +7 -0
- package/build-module/shared/resize-snap.mjs +21 -0
- package/build-module/shared/resize-snap.mjs.map +7 -0
- package/build-module/shared/types.mjs +1 -0
- package/build-module/shared/types.mjs.map +7 -0
- package/build-module/shared/use-item-exit-animation.mjs +128 -0
- package/build-module/shared/use-item-exit-animation.mjs.map +7 -0
- package/build-module/shared/use-layout-shift-animation.mjs +140 -0
- package/build-module/shared/use-layout-shift-animation.mjs.map +7 -0
- package/build-types/dashboard-grid/grid-item.d.ts +3 -0
- package/build-types/dashboard-grid/grid-item.d.ts.map +1 -0
- package/build-types/dashboard-grid/index.d.ts +35 -0
- package/build-types/dashboard-grid/index.d.ts.map +1 -0
- package/build-types/dashboard-grid/resolve-fill-widths.d.ts +26 -0
- package/build-types/dashboard-grid/resolve-fill-widths.d.ts.map +1 -0
- package/build-types/dashboard-grid/stories/index.story.d.ts +98 -0
- package/build-types/dashboard-grid/stories/index.story.d.ts.map +1 -0
- package/build-types/dashboard-grid/types.d.ts +232 -0
- package/build-types/dashboard-grid/types.d.ts.map +1 -0
- package/build-types/dashboard-lanes/index.d.ts +40 -0
- package/build-types/dashboard-lanes/index.d.ts.map +1 -0
- package/build-types/dashboard-lanes/lane-placement.d.ts +126 -0
- package/build-types/dashboard-lanes/lane-placement.d.ts.map +1 -0
- package/build-types/dashboard-lanes/lanes-item.d.ts +52 -0
- package/build-types/dashboard-lanes/lanes-item.d.ts.map +1 -0
- package/build-types/dashboard-lanes/stories/index.story.d.ts +64 -0
- package/build-types/dashboard-lanes/stories/index.story.d.ts.map +1 -0
- package/build-types/dashboard-lanes/types.d.ts +151 -0
- package/build-types/dashboard-lanes/types.d.ts.map +1 -0
- package/build-types/dashboard-lanes/use-lane-placement.d.ts +74 -0
- package/build-types/dashboard-lanes/use-lane-placement.d.ts.map +1 -0
- package/build-types/index.d.ts +6 -0
- package/build-types/index.d.ts.map +1 -0
- package/build-types/shared/drag-overlay-drop-animation.d.ts +13 -0
- package/build-types/shared/drag-overlay-drop-animation.d.ts.map +1 -0
- package/build-types/shared/grid-item-key.d.ts +6 -0
- package/build-types/shared/grid-item-key.d.ts.map +1 -0
- package/build-types/shared/grid-overlay.d.ts +19 -0
- package/build-types/shared/grid-overlay.d.ts.map +1 -0
- package/build-types/shared/item-exit-overlay.d.ts +20 -0
- package/build-types/shared/item-exit-overlay.d.ts.map +1 -0
- package/build-types/shared/resize-handle.d.ts +23 -0
- package/build-types/shared/resize-handle.d.ts.map +1 -0
- package/build-types/shared/resize-snap.d.ts +41 -0
- package/build-types/shared/resize-snap.d.ts.map +1 -0
- package/build-types/shared/types.d.ts +144 -0
- package/build-types/shared/types.d.ts.map +1 -0
- package/build-types/shared/use-item-exit-animation.d.ts +37 -0
- package/build-types/shared/use-item-exit-animation.d.ts.map +1 -0
- package/build-types/shared/use-layout-shift-animation.d.ts +77 -0
- package/build-types/shared/use-layout-shift-animation.d.ts.map +1 -0
- package/package.json +80 -0
- package/src/dashboard-grid/grid-item.module.css +94 -0
- package/src/dashboard-grid/grid-item.tsx +205 -0
- package/src/dashboard-grid/grid.module.css +134 -0
- package/src/dashboard-grid/index.tsx +713 -0
- package/src/dashboard-grid/resolve-fill-widths.ts +224 -0
- package/src/dashboard-grid/stories/index.story.tsx +930 -0
- package/src/dashboard-grid/test/keyboard-activation.test.tsx +76 -0
- package/src/dashboard-grid/test/resolve-fill-widths.test.ts +250 -0
- package/src/dashboard-grid/types.ts +271 -0
- package/src/dashboard-lanes/index.tsx +629 -0
- package/src/dashboard-lanes/lane-placement.ts +245 -0
- package/src/dashboard-lanes/lanes-item.module.css +93 -0
- package/src/dashboard-lanes/lanes-item.tsx +236 -0
- package/src/dashboard-lanes/lanes.module.css +152 -0
- package/src/dashboard-lanes/stories/index.story.tsx +518 -0
- package/src/dashboard-lanes/test/keyboard-activation.test.tsx +71 -0
- package/src/dashboard-lanes/test/lane-placement.test.ts +442 -0
- package/src/dashboard-lanes/test/use-lane-placement.test.tsx +358 -0
- package/src/dashboard-lanes/types.ts +176 -0
- package/src/dashboard-lanes/use-lane-placement.ts +313 -0
- package/src/index.ts +17 -0
- package/src/shared/actionable-area-slot.module.css +16 -0
- package/src/shared/drag-overlay-drop-animation.ts +66 -0
- package/src/shared/grid-item-key.ts +5 -0
- package/src/shared/grid-overlay.module.css +82 -0
- package/src/shared/grid-overlay.tsx +93 -0
- package/src/shared/item-exit-animation.module.css +49 -0
- package/src/shared/item-exit-overlay.tsx +57 -0
- package/src/shared/layout-shift-animation.module.css +16 -0
- package/src/shared/resize-handle.module.css +88 -0
- package/src/shared/resize-handle.tsx +163 -0
- package/src/shared/resize-snap.ts +63 -0
- package/src/shared/test/resize-snap.test.ts +35 -0
- package/src/shared/types.ts +164 -0
- package/src/shared/use-item-exit-animation.ts +199 -0
- 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
|
+
}
|