@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,358 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ /**
6
+ * External dependencies
7
+ */
8
+ import { render, act, screen } from '@testing-library/react';
9
+
10
+ /**
11
+ * WordPress dependencies
12
+ */
13
+ import { useState } from '@wordpress/element';
14
+
15
+ /**
16
+ * Internal dependencies
17
+ */
18
+ import { GRID_ITEM_DATA_KEY } from '../../shared/grid-item-key';
19
+ import { useLanePlacement } from '../use-lane-placement';
20
+ import type {
21
+ UseLanePlacementInput,
22
+ UseLanePlacementResult,
23
+ } from '../use-lane-placement';
24
+
25
+ type Entry = {
26
+ target: Element;
27
+ contentRect: { height: number; width: number };
28
+ };
29
+
30
+ class MockResizeObserver {
31
+ static instances: MockResizeObserver[] = [];
32
+ static lastInstance(): MockResizeObserver | undefined {
33
+ return MockResizeObserver.instances[
34
+ MockResizeObserver.instances.length - 1
35
+ ];
36
+ }
37
+ static reset() {
38
+ MockResizeObserver.instances = [];
39
+ }
40
+
41
+ callback: ResizeObserverCallback;
42
+ observed: Set< Element > = new Set();
43
+ disconnected = false;
44
+
45
+ constructor( callback: ResizeObserverCallback ) {
46
+ this.callback = callback;
47
+ MockResizeObserver.instances.push( this );
48
+ }
49
+ observe( element: Element ) {
50
+ this.observed.add( element );
51
+ }
52
+ unobserve( element: Element ) {
53
+ this.observed.delete( element );
54
+ }
55
+ disconnect() {
56
+ this.observed.clear();
57
+ this.disconnected = true;
58
+ }
59
+ fire( entries: Entry[] ) {
60
+ this.callback(
61
+ entries as unknown as ResizeObserverEntry[],
62
+ this as unknown as ResizeObserver
63
+ );
64
+ }
65
+ }
66
+
67
+ let originalResizeObserver: typeof ResizeObserver;
68
+ let originalSupports: typeof CSS.supports | undefined;
69
+ let originalRaf: typeof requestAnimationFrame;
70
+
71
+ function installMockObserver() {
72
+ originalResizeObserver = global.ResizeObserver;
73
+ MockResizeObserver.reset();
74
+ ( global as unknown as { ResizeObserver: unknown } ).ResizeObserver =
75
+ MockResizeObserver;
76
+ }
77
+
78
+ function restoreObserver() {
79
+ ( global as unknown as { ResizeObserver: unknown } ).ResizeObserver =
80
+ originalResizeObserver;
81
+ }
82
+
83
+ function setNativeSupport( supported: boolean ) {
84
+ if ( typeof CSS === 'undefined' ) {
85
+ ( global as unknown as { CSS: unknown } ).CSS = {
86
+ supports: () => supported,
87
+ };
88
+ return;
89
+ }
90
+ originalSupports = CSS.supports;
91
+ CSS.supports = ( property: string, value?: string ) => {
92
+ if ( property === 'display' && value === 'grid-lanes' ) {
93
+ return supported;
94
+ }
95
+ return originalSupports
96
+ ? originalSupports.call( CSS, property, value as string )
97
+ : false;
98
+ };
99
+ }
100
+
101
+ function restoreSupport() {
102
+ if ( originalSupports ) {
103
+ CSS.supports = originalSupports;
104
+ originalSupports = undefined;
105
+ }
106
+ }
107
+
108
+ function flushRaf() {
109
+ // jsdom polyfills `requestAnimationFrame` via `setTimeout`; an
110
+ // `act` boundary lets React commit any state set inside the rAF.
111
+ jest.runAllTimers();
112
+ }
113
+
114
+ beforeEach( () => {
115
+ installMockObserver();
116
+ originalRaf = global.requestAnimationFrame;
117
+ global.requestAnimationFrame = ( cb ) =>
118
+ setTimeout( () => cb( performance.now() ), 0 ) as unknown as number;
119
+ jest.useFakeTimers();
120
+ } );
121
+
122
+ afterEach( () => {
123
+ jest.useRealTimers();
124
+ global.requestAnimationFrame = originalRaf;
125
+ restoreObserver();
126
+ restoreSupport();
127
+ } );
128
+
129
+ type HarnessProps = {
130
+ input: UseLanePlacementInput;
131
+ measuredHeights?: Record< string, number >;
132
+ onResult?: ( result: UseLanePlacementResult ) => void;
133
+ };
134
+
135
+ function Harness( { input, measuredHeights, onResult }: HarnessProps ) {
136
+ const [ container, setContainer ] = useState< HTMLDivElement | null >(
137
+ null
138
+ );
139
+ const result = useLanePlacement( container, input );
140
+ if ( onResult ) {
141
+ onResult( result );
142
+ }
143
+ return (
144
+ <div ref={ setContainer } data-testid="container">
145
+ { input.items.map( ( item ) => (
146
+ <div
147
+ key={ item.key }
148
+ { ...{ [ GRID_ITEM_DATA_KEY ]: item.key } }
149
+ data-testid={ `item-${ item.key }` }
150
+ style={ {
151
+ ...result.itemStyles.get( item.key ),
152
+ height: measuredHeights?.[ item.key ] ?? 0,
153
+ } }
154
+ />
155
+ ) ) }
156
+ </div>
157
+ );
158
+ }
159
+
160
+ function captureLatestResult() {
161
+ let latest: UseLanePlacementResult | null = null;
162
+ const onResult = ( result: UseLanePlacementResult ) => {
163
+ latest = result;
164
+ };
165
+ const get = (): UseLanePlacementResult => {
166
+ if ( ! latest ) {
167
+ throw new Error( 'Hook never produced a result' );
168
+ }
169
+ return latest;
170
+ };
171
+ return { onResult, get };
172
+ }
173
+
174
+ function fireMeasurements( heights: Record< string, number > ) {
175
+ const observer = MockResizeObserver.lastInstance();
176
+ if ( ! observer ) {
177
+ throw new Error( 'No ResizeObserver instance registered' );
178
+ }
179
+ const entries: Entry[] = [];
180
+ for ( const [ key, height ] of Object.entries( heights ) ) {
181
+ const element = screen.getByTestId( `item-${ key }` );
182
+ entries.push( {
183
+ target: element,
184
+ contentRect: { height, width: 0 },
185
+ } );
186
+ }
187
+ act( () => {
188
+ observer.fire( entries );
189
+ flushRaf();
190
+ } );
191
+ }
192
+
193
+ describe( 'useLanePlacement', () => {
194
+ describe( 'native support', () => {
195
+ it( 'returns isPolyfilled=false and span-only styles when supported', () => {
196
+ setNativeSupport( true );
197
+ const { onResult, get } = captureLatestResult();
198
+
199
+ render(
200
+ <Harness
201
+ input={ {
202
+ items: [
203
+ { key: 'a', span: 1 },
204
+ { key: 'b', span: 2 },
205
+ ],
206
+ lanes: 3,
207
+ gap: 16,
208
+ flowTolerance: 0,
209
+ } }
210
+ onResult={ onResult }
211
+ />
212
+ );
213
+ act( () => {
214
+ flushRaf();
215
+ } );
216
+
217
+ const result = get();
218
+ expect( result.isPolyfilled ).toBe( false );
219
+ expect( result.itemStyles.get( 'a' ) ).toEqual( {
220
+ gridColumn: 'span 1',
221
+ } );
222
+ expect( result.itemStyles.get( 'b' ) ).toEqual( {
223
+ gridColumn: 'span 2',
224
+ } );
225
+ // No observers should have been instantiated.
226
+ expect( MockResizeObserver.instances.length ).toBe( 0 );
227
+ } );
228
+ } );
229
+
230
+ describe( 'polyfill path', () => {
231
+ it( 'returns native-shape styles before measurement completes', () => {
232
+ setNativeSupport( false );
233
+ const { onResult, get } = captureLatestResult();
234
+
235
+ render(
236
+ <Harness
237
+ input={ {
238
+ items: [
239
+ { key: 'a', span: 1 },
240
+ { key: 'b', span: 2 },
241
+ ],
242
+ lanes: 3,
243
+ gap: 16,
244
+ flowTolerance: 0,
245
+ } }
246
+ onResult={ onResult }
247
+ />
248
+ );
249
+
250
+ const result = get();
251
+ expect( result.isPolyfilled ).toBe( true );
252
+ expect( result.itemStyles.get( 'a' ) ).toEqual( {
253
+ gridColumn: 'span 1',
254
+ } );
255
+ } );
256
+
257
+ it( 'observes children and emits placement styles after measurement', () => {
258
+ setNativeSupport( false );
259
+ const { onResult, get } = captureLatestResult();
260
+
261
+ render(
262
+ <Harness
263
+ input={ {
264
+ items: [
265
+ { key: 'a', span: 1 },
266
+ { key: 'b', span: 1 },
267
+ { key: 'c', span: 1 },
268
+ ],
269
+ lanes: 3,
270
+ gap: 0,
271
+ flowTolerance: 0,
272
+ rowUnit: 4,
273
+ } }
274
+ onResult={ onResult }
275
+ />
276
+ );
277
+
278
+ // One ResizeObserver should have been constructed for the
279
+ // container's children.
280
+ expect( MockResizeObserver.instances.length ).toBe( 1 );
281
+ const observer = MockResizeObserver.lastInstance();
282
+ expect( observer?.observed.size ).toBe( 3 );
283
+
284
+ fireMeasurements( {
285
+ a: 100,
286
+ b: 80,
287
+ c: 60,
288
+ } );
289
+
290
+ const result = get();
291
+ expect( result.isPolyfilled ).toBe( true );
292
+
293
+ // All three items should land in lane 0/1/2 respectively
294
+ // (zero baselines), at row 1.
295
+ const a = result.itemStyles.get( 'a' )!;
296
+ const b = result.itemStyles.get( 'b' )!;
297
+ const c = result.itemStyles.get( 'c' )!;
298
+ expect( a.gridColumnStart ).toBe( 1 );
299
+ expect( a.gridRowStart ).toBe( 1 );
300
+ expect( a.gridRowEnd ).toBe( 'span 25' ); // 100 / 4
301
+ expect( b.gridColumnStart ).toBe( 2 );
302
+ expect( b.gridRowEnd ).toBe( 'span 20' ); // 80 / 4
303
+ expect( c.gridColumnStart ).toBe( 3 );
304
+ expect( c.gridRowEnd ).toBe( 'span 15' ); // 60 / 4
305
+ } );
306
+
307
+ it( 'recomputes when an item resizes', () => {
308
+ setNativeSupport( false );
309
+ const { onResult, get } = captureLatestResult();
310
+
311
+ render(
312
+ <Harness
313
+ input={ {
314
+ items: [
315
+ { key: 'a', span: 1 },
316
+ { key: 'b', span: 1 },
317
+ ],
318
+ lanes: 1,
319
+ gap: 0,
320
+ flowTolerance: 0,
321
+ rowUnit: 4,
322
+ } }
323
+ onResult={ onResult }
324
+ />
325
+ );
326
+
327
+ fireMeasurements( { a: 100, b: 50 } );
328
+ expect( get().itemStyles.get( 'b' )?.gridRowStart ).toBe( 26 ); // 100/4 + 1
329
+
330
+ fireMeasurements( { a: 200 } );
331
+ expect( get().itemStyles.get( 'b' )?.gridRowStart ).toBe( 51 ); // 200/4 + 1
332
+ } );
333
+ } );
334
+
335
+ describe( 'cleanup', () => {
336
+ it( 'disconnects observers on unmount', () => {
337
+ setNativeSupport( false );
338
+
339
+ const { unmount } = render(
340
+ <Harness
341
+ input={ {
342
+ items: [ { key: 'a', span: 1 } ],
343
+ lanes: 3,
344
+ gap: 16,
345
+ flowTolerance: 0,
346
+ } }
347
+ />
348
+ );
349
+
350
+ const observer = MockResizeObserver.lastInstance();
351
+ expect( observer ).toBeDefined();
352
+ expect( observer!.disconnected ).toBe( false );
353
+
354
+ unmount();
355
+ expect( observer!.disconnected ).toBe( true );
356
+ } );
357
+ } );
358
+ } );
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import type {
5
+ DragPreviewRenderProps,
6
+ GridOverlayRenderProps,
7
+ ResizeHandleRenderProps,
8
+ } from '../shared/types';
9
+
10
+ /**
11
+ * Lanes layout item definition.
12
+ *
13
+ * Mirrors the public surface of `display: grid-lanes`: column span,
14
+ * an optional pinned lane, and an optional source order. Heights are
15
+ * content-driven; there is no `height` field. There is no `'fill'`
16
+ * (lanes pack their items by skyline; nothing is "left over").
17
+ * `'full'` is expressed by setting `width` to the lane count.
18
+ */
19
+ export type DashboardLanesLayoutItem = {
20
+ /**
21
+ * Unique key that matches a child component key.
22
+ */
23
+ key: string;
24
+
25
+ /**
26
+ * Number of lanes this item spans (`grid-column: span N`). Clamped
27
+ * to the surface's lane count.
28
+ *
29
+ * @default 1
30
+ */
31
+ width?: number;
32
+
33
+ /**
34
+ * Pin the item to a specific 0-indexed lane. Pinned items are
35
+ * placed before auto items, so the auto flow runs around them.
36
+ * Out-of-range values (negative, or beyond `columns - width`) are
37
+ * clamped to the available range.
38
+ */
39
+ lane?: number;
40
+
41
+ /**
42
+ * Display order. Lower values render first. When omitted, the
43
+ * item falls back to its index in the `layout` array.
44
+ */
45
+ order?: number;
46
+ };
47
+
48
+ /**
49
+ * Props for `DashboardLanes`.
50
+ *
51
+ * `columns` and `minColumnWidth` compose as a layered model:
52
+ * - `columns` alone: fixed N lanes; tiles scale with the container.
53
+ * - `minColumnWidth` alone: lane count derives from container width,
54
+ * floored by the per-tile minimum, down to 1.
55
+ * - Both together: `columns` caps the count, `minColumnWidth` enforces
56
+ * a per-tile width floor that can reduce the count below the cap.
57
+ */
58
+ export interface DashboardLanesProps
59
+ extends Omit<
60
+ React.ComponentPropsWithoutRef< 'div' >,
61
+ 'children' | 'className' | 'style'
62
+ > {
63
+ /**
64
+ * Array of layout items.
65
+ */
66
+ layout: DashboardLanesLayoutItem[];
67
+
68
+ /**
69
+ * Surface children. Each child must carry a `key` matching an
70
+ * entry in `layout`; children without a match render at the end
71
+ * of the surface without explicit placement and fall through the
72
+ * lanes auto-flow.
73
+ */
74
+ children: React.ReactNode;
75
+
76
+ /**
77
+ * Additional CSS class on the surface root.
78
+ */
79
+ className?: string;
80
+
81
+ /**
82
+ * Inline styles on the surface root. Merged underneath the
83
+ * surface's own layout styles, so `display` and
84
+ * `gridTemplateColumns` always win. The gap between tiles is
85
+ * owned by the design-system gap token and is not configurable
86
+ * per instance; override it via a theme or density change.
87
+ */
88
+ style?: React.CSSProperties;
89
+
90
+ /**
91
+ * `flow-tolerance` value in pixels. When two candidate lanes
92
+ * differ in baseline by no more than this, the earlier lane wins
93
+ * to preserve source order. Larger values keep tiles closer to
94
+ * reading order at the cost of bigger empty regions.
95
+ *
96
+ * @default 16
97
+ */
98
+ flowTolerance?: number;
99
+
100
+ /**
101
+ * Snap unit for the polyfill's `grid-row-start` / `grid-row-end:
102
+ * span N` math. Smaller values produce sharper placement at the
103
+ * cost of a larger implicit row count. Ignored on browsers with
104
+ * native `display: grid-lanes` support.
105
+ *
106
+ * @default 4
107
+ */
108
+ rowUnit?: number;
109
+
110
+ /**
111
+ * Whether the surface is in edit mode (drag-to-reorder, resize).
112
+ *
113
+ * @default false
114
+ */
115
+ editMode?: boolean;
116
+
117
+ /**
118
+ * Fired when the user commits a drag or resize.
119
+ */
120
+ onChangeLayout?: ( newLayout: DashboardLanesLayoutItem[] ) => void;
121
+
122
+ /**
123
+ * Fired continuously during a gesture with the in-progress
124
+ * layout. The committed result still emits via `onChangeLayout`.
125
+ */
126
+ onPreviewLayout?: ( previewLayout: DashboardLanesLayoutItem[] ) => void;
127
+
128
+ /**
129
+ * Override the default corner resize handle. See `DashboardGrid`
130
+ * for the full contract; on lanes the handle is horizontal-only
131
+ * because heights are content-driven.
132
+ */
133
+ renderResizeHandle?: React.ComponentType< ResizeHandleRenderProps >;
134
+
135
+ /**
136
+ * Custom wrapper for the dragged-clone visual mounted inside
137
+ * `<DragOverlay>`. The surface always wraps the clone with a thin
138
+ * functional frame (lift scale, grabbing cursor, pointer pass-
139
+ * through) and mounts this component inside it; the consumer
140
+ * owns the visual chrome (shadow, radius, padding).
141
+ *
142
+ * When omitted, the cloned children render directly inside the
143
+ * functional frame so any chrome the consumer applied to the
144
+ * persistent tile carries through unchanged.
145
+ *
146
+ * Token-only adjustments (lift scale, placeholder opacity,
147
+ * outline color, placeholder radius) flow through CSS custom
148
+ * properties documented in the README.
149
+ */
150
+ renderDragPreview?: React.ComponentType< DragPreviewRenderProps >;
151
+
152
+ /**
153
+ * Override the default edit-mode overlay (empty column tracks) with
154
+ * a custom component. Lanes are content-driven vertically, so no
155
+ * `rowHeight` or `rows` is supplied and the default visual paints
156
+ * columns only.
157
+ *
158
+ * The overlay only renders when `editMode` is true. When omitted,
159
+ * the package's default visual is used.
160
+ */
161
+ renderGridOverlay?: React.ComponentType< GridOverlayRenderProps >;
162
+
163
+ /**
164
+ * Target lane count, used as a cap. Defaults to six when neither
165
+ * `columns` nor `minColumnWidth` is set; with `minColumnWidth` set
166
+ * it can resolve lower on narrow containers.
167
+ */
168
+ columns?: number;
169
+
170
+ /**
171
+ * Per-tile minimum width in pixels. Enables responsive mode: the
172
+ * lane count derives from container width, floored by this value,
173
+ * down to 1.
174
+ */
175
+ minColumnWidth?: number;
176
+ }