@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,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/shared/resize-handle.tsx", "../../../style-runtime/src/index.ts", "../../src/shared/resize-handle.module.css"],
|
|
4
|
+
"sourcesContent": ["/**\n * External dependencies\n */\nimport { DndContext, useDraggable } from '@dnd-kit/core';\nimport type { DragMoveEvent } from '@dnd-kit/core';\nimport clsx from 'clsx';\n\n/**\n * WordPress dependencies\n */\nimport { useCallback, useEffect, useRef } from '@wordpress/element';\nimport { useMergeRefs, useThrottle } from '@wordpress/compose';\n\n/**\n * Internal dependencies\n */\nimport type { ResizeDelta, ResizeHandleProps } from './types';\nimport styles from './resize-handle.module.css';\n\n/**\n * Sets `document.documentElement.style.cursor` for the duration of a drag\n * and restores it on cleanup. Lives outside the component so cursor writes\n * are not analyzed as mutating values derived from refs in the component\n * body (react-hooks/immutability).\n *\n * @param getDocument Returns the document whose root element should receive\n * the cursor (handle owner, or global `document`).\n * @param cursor CSS cursor value while active.\n * @return Cleanup that restores the previous cursor.\n */\nfunction lockDocumentCursorWhileActive(\n\tgetDocument: () => Document,\n\tcursor: string\n): () => void {\n\tconst root = getDocument().documentElement;\n\tconst previous = root.style.cursor;\n\troot.style.cursor = cursor;\n\treturn () => {\n\t\troot.style.cursor = previous;\n\t};\n}\n\nfunction ResizeHandle( {\n\titemId,\n\tverticalResizable = true,\n\trenderResizeHandle,\n}: ResizeHandleProps ) {\n\tconst { attributes, listeners, setNodeRef, isDragging } = useDraggable( {\n\t\tid: 'draggable',\n\t\tdata: { itemId },\n\t} );\n\n\t// Snapshot owner document on mount/update via ref callback so the\n\t// cursor-lock effect can resolve the correct document in an iframe.\n\tconst ownerDocumentRef = useRef< Document | null >( null );\n\tconst setOwnerDocumentRef = useCallback( ( node: HTMLElement | null ) => {\n\t\townerDocumentRef.current = node?.ownerDocument ?? null;\n\t}, [] );\n\tconst mergedRef = useMergeRefs( [ setOwnerDocumentRef, setNodeRef ] );\n\n\t// Lock the document cursor while the gesture is active. Without\n\t// this, the OS pointer reverts to the default arrow as soon as it\n\t// leaves the handle's hit area, even though the resize is still\n\t// in progress.\n\tuseEffect( () => {\n\t\tif ( ! isDragging ) {\n\t\t\treturn;\n\t\t}\n\t\tconst cursor = verticalResizable ? 'nwse-resize' : 'ew-resize';\n\t\treturn lockDocumentCursorWhileActive(\n\t\t\t() => ownerDocumentRef.current ?? document,\n\t\t\tcursor\n\t\t);\n\t}, [ isDragging, verticalResizable ] );\n\n\tif ( renderResizeHandle ) {\n\t\tconst RenderResizeHandle = renderResizeHandle;\n\t\treturn (\n\t\t\t<RenderResizeHandle\n\t\t\t\tref={ mergedRef }\n\t\t\t\tlisteners={ listeners }\n\t\t\t\tattributes={ attributes }\n\t\t\t\tverticalResizable={ verticalResizable }\n\t\t\t\tisResizing={ isDragging }\n\t\t\t\titemId={ itemId }\n\t\t\t/>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tref={ mergedRef }\n\t\t\tclassName={ clsx(\n\t\t\t\tstyles[ 'resize-handle' ],\n\t\t\t\t! verticalResizable && styles[ 'is-horizontal-only' ]\n\t\t\t) }\n\t\t\t{ ...listeners }\n\t\t\t{ ...attributes }\n\t\t/>\n\t);\n}\n\n/**\n * Renders a corner resize handle inside an isolated `<DndContext>`.\n * Reports the cursor offset since the gesture started (in pixels)\n * via `onResize`, throttled to one animation frame so the grid\n * commit loop runs at most once per paint.\n *\n * Auto-scroll is enabled with a tight trigger zone and a low\n * acceleration so a resize gesture near the viewport edge scrolls\n * the page only when the user deliberately pushes against the very\n * edge, and even then at a pace the user can interrupt by releasing.\n * Default tuning would otherwise produce a runaway loop where the\n * page scrolls fast, dnd-kit's document-coordinate `delta` inflates\n * with the scroll, and the tile keeps growing without further user\n * input.\n *\n * @param props Component props.\n */\nexport default function ResizeHandleWrapper( props: ResizeHandleProps ) {\n\tconst throttleDelay = 16;\n\tconst throttledResize = useThrottle( ( delta: ResizeDelta ) => {\n\t\tif ( props.onResize ) {\n\t\t\tprops.onResize( delta );\n\t\t}\n\t}, throttleDelay );\n\n\t// `event.delta` is the cursor offset from the gesture start —\n\t// not from the handle's current position — so it stays stable\n\t// even when the tile (and therefore the handle) jumps a column.\n\t// The grid's resize logic snapshots the start width and adds\n\t// `delta`, so the two must share the same frame of reference.\n\tconst handleDragMove = ( event: DragMoveEvent ) => {\n\t\tif ( event.active.id !== 'draggable' ) {\n\t\t\treturn;\n\t\t}\n\t\tthrottledResize( {\n\t\t\twidth: event.delta.x,\n\t\t\theight: event.delta.y,\n\t\t} );\n\t};\n\n\tconst handleDragEnd = () => {\n\t\tif ( props.onResizeEnd ) {\n\t\t\tprops.onResizeEnd();\n\t\t}\n\t};\n\n\treturn (\n\t\t<DndContext\n\t\t\tautoScroll={ {\n\t\t\t\tthreshold: { x: 0.005, y: 0.005 },\n\t\t\t\tacceleration: 1,\n\t\t\t} }\n\t\t\tonDragMove={ handleDragMove }\n\t\t\tonDragEnd={ handleDragEnd }\n\t\t>\n\t\t\t<div className={ styles[ 'resize-handle-slot' ] }>\n\t\t\t\t<ResizeHandle { ...props } />\n\t\t\t</div>\n\t\t</DndContext>\n\t);\n}\n", "type GlobalScopeWithStyleRuntime = typeof globalThis & {\n\t// This global is shared by separately bundled copies of this package.\n\t// Keep its shape backward compatible after release.\n\t__wpStyleRuntime?: {\n\t\tdocuments: Map< Document, number >;\n\t\tstyles: Map< string, string >;\n\t\tinjectedStyles: WeakMap< Document, Set< string > >;\n\t};\n};\n\nconst STYLE_HASH_ATTRIBUTE = 'data-wp-hash';\n\n/**\n * Returns the shared style runtime registry.\n *\n * The registry is stored on `globalThis` so separately bundled copies of this\n * package can coordinate through the same document and style maps.\n *\n * @return The shared runtime registry.\n */\nfunction getRuntime() {\n\tconst globalScope = globalThis as GlobalScopeWithStyleRuntime;\n\n\tif ( globalScope.__wpStyleRuntime ) {\n\t\treturn globalScope.__wpStyleRuntime;\n\t}\n\n\tglobalScope.__wpStyleRuntime = {\n\t\tdocuments: new Map(),\n\t\tstyles: new Map(),\n\t\tinjectedStyles: new WeakMap(),\n\t};\n\n\tif ( typeof document !== 'undefined' ) {\n\t\tregisterDocument( document );\n\t}\n\n\treturn globalScope.__wpStyleRuntime;\n}\n\n/**\n * Checks whether a document already contains a style tag for a hash.\n *\n * @param targetDocument Document to inspect.\n * @param hash Stable hash for the transformed CSS.\n *\n * @return Whether the style hash already exists in the document.\n */\nfunction documentContainsStyleHash(\n\ttargetDocument: Document,\n\thash: string\n): boolean {\n\tif ( ! targetDocument.head ) {\n\t\treturn false;\n\t}\n\n\tfor ( const style of targetDocument.head.querySelectorAll(\n\t\t`style[${ STYLE_HASH_ATTRIBUTE }]`\n\t) ) {\n\t\tif ( style.getAttribute( STYLE_HASH_ATTRIBUTE ) === hash ) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Injects a registered style into a document, unless that document already\n * contains a style tag for the same hash.\n *\n * @param targetDocument Document to inject the style into.\n * @param hash Stable hash for the transformed CSS.\n * @param css CSS text to inject.\n */\nfunction injectStyle( targetDocument: Document, hash: string, css: string ) {\n\tif ( ! targetDocument.head ) {\n\t\treturn;\n\t}\n\n\tconst runtime = getRuntime();\n\tlet injectedStyles = runtime.injectedStyles.get( targetDocument );\n\n\tif ( ! injectedStyles ) {\n\t\tinjectedStyles = new Set();\n\t\truntime.injectedStyles.set( targetDocument, injectedStyles );\n\t}\n\n\tif ( injectedStyles.has( hash ) ) {\n\t\treturn;\n\t}\n\n\t// Older generated CSS module output can still inject matching style tags\n\t// after this document's cache is created, so keep the DOM as the fallback\n\t// source of truth on cache misses.\n\tif ( documentContainsStyleHash( targetDocument, hash ) ) {\n\t\tinjectedStyles.add( hash );\n\t\treturn;\n\t}\n\n\tconst style = targetDocument.createElement( 'style' );\n\tstyle.setAttribute( STYLE_HASH_ATTRIBUTE, hash );\n\tstyle.appendChild( targetDocument.createTextNode( css ) );\n\ttargetDocument.head.appendChild( style );\n\tinjectedStyles.add( hash );\n}\n\n/**\n * Registers a document as a style injection target.\n *\n * Existing registered styles are replayed into the document immediately.\n * Documents are reference-counted so multiple providers can safely register the\n * same document without one cleanup removing it while another registration is\n * still active.\n *\n * @param targetDocument Document to receive registered styles.\n * @return Cleanup function that unregisters this document registration.\n */\nexport function registerDocument( targetDocument: Document ) {\n\tconst runtime = getRuntime();\n\n\truntime.documents.set(\n\t\ttargetDocument,\n\t\t( runtime.documents.get( targetDocument ) ?? 0 ) + 1\n\t);\n\n\tfor ( const [ hash, css ] of runtime.styles ) {\n\t\tinjectStyle( targetDocument, hash, css );\n\t}\n\n\treturn () => {\n\t\tconst count = runtime.documents.get( targetDocument );\n\n\t\tif ( count === undefined ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( count <= 1 ) {\n\t\t\truntime.documents.delete( targetDocument );\n\t\t\treturn;\n\t\t}\n\n\t\truntime.documents.set( targetDocument, count - 1 );\n\t};\n}\n\n/**\n * Registers a style and injects it into all registered documents.\n *\n * The hash is used as the deduplication key, so calling this repeatedly with\n * the same hash will not add duplicate style tags to a document.\n * Registered styles are retained for the lifetime of the page so they can be\n * replayed into documents that are registered later.\n *\n * @param hash Stable hash for the transformed CSS.\n * @param css CSS text to inject.\n */\nexport function registerStyle( hash: string, css: string ) {\n\tconst runtime = getRuntime();\n\n\truntime.styles.set( hash, css );\n\n\tfor ( const targetDocument of runtime.documents.keys() ) {\n\t\tinjectStyle( targetDocument, hash, css );\n\t}\n}\n", "import { registerStyle } from '@wordpress/style-runtime';\nif (typeof process === 'undefined' || process.env.NODE_ENV !== 'test') {\n\tregisterStyle(\"13645360b3\", \"._91523dc3a37aecb8__resize-handle-slot{opacity:1}@media (prefers-reduced-motion:no-preference){._91523dc3a37aecb8__resize-handle-slot{transition:opacity var(--wpds-motion-duration-md,.2s) var(--wpds-motion-easing-subtle,cubic-bezier(.15,0,.15,1))}}[data-wp-grid-dragging] ._91523dc3a37aecb8__resize-handle-slot,[data-wp-grid-resizing] ._91523dc3a37aecb8__resize-handle-slot{opacity:0;pointer-events:none}[data-wp-grid-resizing]\\n[data-wp-grid-item-resizing]\\n._91523dc3a37aecb8__resize-handle-slot{opacity:1;pointer-events:auto}._4da787f72dc00d8b__resize-handle{border-block-end:12px solid var(--wpds-color-foreground-interactive-brand,var(--wp-admin-theme-color,#3858e9));border-inline-start:12px solid transparent;bottom:0;cursor:nwse-resize;height:0;inset-inline-end:0;position:absolute;width:0;z-index:1}._885412b44a137da0__is-horizontal-only{align-items:center;background:transparent;border-style:none;bottom:auto;cursor:ew-resize;display:flex;height:40px;justify-content:center;top:50%;transform:translateY(-50%);width:16px}._885412b44a137da0__is-horizontal-only:before{background-color:var(--wpds-color-foreground-interactive-brand,var(--wp-admin-theme-color,#3858e9));border-radius:2px;content:\\\"\\\";height:24px;width:4px}@media (forced-colors:active){._885412b44a137da0__is-horizontal-only:before{background-color:Highlight}}\");\n}\nexport default {\"resize-handle-slot\":\"_91523dc3a37aecb8__resize-handle-slot\",\"resize-handle\":\"_4da787f72dc00d8b__resize-handle\",\"is-horizontal-only\":\"_885412b44a137da0__is-horizontal-only\"};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// packages/grid/src/shared/resize-snap.ts
|
|
2
|
+
function clampResizeDelta(delta, initialSize, minSize) {
|
|
3
|
+
const maxShrinkWidth = initialSize.width - minSize.width;
|
|
4
|
+
const width = Math.max(delta.width, -maxShrinkWidth);
|
|
5
|
+
if (minSize.height === void 0) {
|
|
6
|
+
return { ...delta, width };
|
|
7
|
+
}
|
|
8
|
+
const maxShrinkHeight = initialSize.height - minSize.height;
|
|
9
|
+
const height = Math.max(delta.height, -maxShrinkHeight);
|
|
10
|
+
return { width, height };
|
|
11
|
+
}
|
|
12
|
+
function gridSpanToPixelSize(columnSpan, rowSpan, columnWidth, gapPx, rowHeightPx) {
|
|
13
|
+
const widthPx = columnSpan * columnWidth + (columnSpan - 1) * gapPx;
|
|
14
|
+
const heightPx = rowHeightPx === null ? null : rowSpan * rowHeightPx + (rowSpan - 1) * gapPx;
|
|
15
|
+
return { widthPx, heightPx };
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
clampResizeDelta,
|
|
19
|
+
gridSpanToPixelSize
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=resize-snap.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/shared/resize-snap.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ResizeDelta } from './types';\n\n/**\n * Pixel dimensions for the snapped resize preview outline.\n */\nexport type ResizeSnapSize = {\n\twidthPx: number;\n\t/** When `null`, the preview spans the item's content height (lanes). */\n\theightPx: number | null;\n};\n\n/**\n * Clamps a resize delta so the tile cannot shrink below the given\n * minimum width (and height when provided).\n *\n * @param delta Cursor offset from the gesture start in pixels.\n * @param initialSize Size captured at gesture start.\n * @param initialSize.width Initial width in pixels.\n * @param initialSize.height Initial height in pixels.\n * @param minSize Minimum tile size in pixels.\n * @param minSize.width Minimum width in pixels.\n * @param minSize.height Minimum height in pixels, when vertical resize applies.\n */\nexport function clampResizeDelta(\n\tdelta: ResizeDelta,\n\tinitialSize: { width: number; height: number },\n\tminSize: { width: number; height?: number }\n): ResizeDelta {\n\tconst maxShrinkWidth = initialSize.width - minSize.width;\n\tconst width = Math.max( delta.width, -maxShrinkWidth );\n\tif ( minSize.height === undefined ) {\n\t\treturn { ...delta, width };\n\t}\n\tconst maxShrinkHeight = initialSize.height - minSize.height;\n\tconst height = Math.max( delta.height, -maxShrinkHeight );\n\treturn { width, height };\n}\n\n/**\n * Converts grid spans to pixel width/height for the resize-preview\n * outline, using the same track math the surface uses for placement.\n *\n * @param columnSpan Number of columns the snap target spans.\n * @param rowSpan Number of rows the snap target spans.\n * @param columnWidth Width of one column track in pixels.\n * @param gapPx Gap between tracks in pixels.\n * @param rowHeightPx Row track height in pixels, or `null` when rows\n * are content-sized.\n */\nexport function gridSpanToPixelSize(\n\tcolumnSpan: number,\n\trowSpan: number,\n\tcolumnWidth: number,\n\tgapPx: number,\n\trowHeightPx: number | null\n): ResizeSnapSize {\n\tconst widthPx = columnSpan * columnWidth + ( columnSpan - 1 ) * gapPx;\n\tconst heightPx =\n\t\trowHeightPx === null\n\t\t\t? null\n\t\t\t: rowSpan * rowHeightPx + ( rowSpan - 1 ) * gapPx;\n\treturn { widthPx, heightPx };\n}\n"],
|
|
5
|
+
"mappings": ";AAuBO,SAAS,iBACf,OACA,aACA,SACc;AACd,QAAM,iBAAiB,YAAY,QAAQ,QAAQ;AACnD,QAAM,QAAQ,KAAK,IAAK,MAAM,OAAO,CAAC,cAAe;AACrD,MAAK,QAAQ,WAAW,QAAY;AACnC,WAAO,EAAE,GAAG,OAAO,MAAM;AAAA,EAC1B;AACA,QAAM,kBAAkB,YAAY,SAAS,QAAQ;AACrD,QAAM,SAAS,KAAK,IAAK,MAAM,QAAQ,CAAC,eAAgB;AACxD,SAAO,EAAE,OAAO,OAAO;AACxB;AAaO,SAAS,oBACf,YACA,SACA,aACA,OACA,aACiB;AACjB,QAAM,UAAU,aAAa,eAAgB,aAAa,KAAM;AAChE,QAAM,WACL,gBAAgB,OACb,OACA,UAAU,eAAgB,UAAU,KAAM;AAC9C,SAAO,EAAE,SAAS,SAAS;AAC5B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.mjs.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// packages/grid/src/shared/use-item-exit-animation.ts
|
|
2
|
+
import {
|
|
3
|
+
useCallback,
|
|
4
|
+
useLayoutEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState
|
|
7
|
+
} from "@wordpress/element";
|
|
8
|
+
var EXIT_SAFETY_TIMEOUT_MS = 1e3;
|
|
9
|
+
function prefersReducedMotion() {
|
|
10
|
+
return typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
11
|
+
}
|
|
12
|
+
function useItemExitAnimation({
|
|
13
|
+
container,
|
|
14
|
+
enabled,
|
|
15
|
+
layoutKeys,
|
|
16
|
+
getPositionsBeforeLastChange,
|
|
17
|
+
childrenCacheRef
|
|
18
|
+
}) {
|
|
19
|
+
const [exitingItems, setExitingItems] = useState(
|
|
20
|
+
[]
|
|
21
|
+
);
|
|
22
|
+
const prevLayoutKeysRef = useRef(/* @__PURE__ */ new Set());
|
|
23
|
+
const exitTimeoutsRef = useRef(/* @__PURE__ */ new Map());
|
|
24
|
+
const clearExitingItem = useCallback(
|
|
25
|
+
(key) => {
|
|
26
|
+
const timeout = exitTimeoutsRef.current.get(key);
|
|
27
|
+
if (timeout) {
|
|
28
|
+
clearTimeout(timeout);
|
|
29
|
+
exitTimeoutsRef.current.delete(key);
|
|
30
|
+
}
|
|
31
|
+
setExitingItems(
|
|
32
|
+
(current) => current.filter((item) => item.key !== key)
|
|
33
|
+
);
|
|
34
|
+
childrenCacheRef.current.delete(key);
|
|
35
|
+
},
|
|
36
|
+
[childrenCacheRef]
|
|
37
|
+
);
|
|
38
|
+
const scheduleExitComplete = useCallback(
|
|
39
|
+
(key) => {
|
|
40
|
+
if (exitTimeoutsRef.current.has(key)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const timeout = setTimeout(() => {
|
|
44
|
+
exitTimeoutsRef.current.delete(key);
|
|
45
|
+
clearExitingItem(key);
|
|
46
|
+
}, EXIT_SAFETY_TIMEOUT_MS);
|
|
47
|
+
exitTimeoutsRef.current.set(key, timeout);
|
|
48
|
+
},
|
|
49
|
+
[clearExitingItem]
|
|
50
|
+
);
|
|
51
|
+
useLayoutEffect(() => {
|
|
52
|
+
if (!enabled || !container) {
|
|
53
|
+
prevLayoutKeysRef.current = new Set(layoutKeys);
|
|
54
|
+
for (const timeout of exitTimeoutsRef.current.values()) {
|
|
55
|
+
clearTimeout(timeout);
|
|
56
|
+
}
|
|
57
|
+
exitTimeoutsRef.current.clear();
|
|
58
|
+
setExitingItems([]);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const prevKeys = prevLayoutKeysRef.current;
|
|
62
|
+
const removed = [];
|
|
63
|
+
for (const key of prevKeys) {
|
|
64
|
+
if (!layoutKeys.has(key)) {
|
|
65
|
+
removed.push(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
prevLayoutKeysRef.current = new Set(layoutKeys);
|
|
69
|
+
if (removed.length === 0) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const lastPositions = getPositionsBeforeLastChange();
|
|
73
|
+
if (!lastPositions) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const nextExiting = [];
|
|
77
|
+
for (const key of removed) {
|
|
78
|
+
const position = lastPositions.get(key);
|
|
79
|
+
const child = childrenCacheRef.current.get(key);
|
|
80
|
+
if (!position || !child) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
nextExiting.push({
|
|
84
|
+
key,
|
|
85
|
+
rect: position,
|
|
86
|
+
child
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (nextExiting.length === 0) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (prefersReducedMotion()) {
|
|
93
|
+
for (const { key } of nextExiting) {
|
|
94
|
+
childrenCacheRef.current.delete(key);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
setExitingItems((current) => [...current, ...nextExiting]);
|
|
99
|
+
for (const { key } of nextExiting) {
|
|
100
|
+
scheduleExitComplete(key);
|
|
101
|
+
}
|
|
102
|
+
}, [
|
|
103
|
+
container,
|
|
104
|
+
enabled,
|
|
105
|
+
getPositionsBeforeLastChange,
|
|
106
|
+
layoutKeys,
|
|
107
|
+
childrenCacheRef,
|
|
108
|
+
scheduleExitComplete
|
|
109
|
+
]);
|
|
110
|
+
useLayoutEffect(() => {
|
|
111
|
+
const exitTimeouts = exitTimeoutsRef.current;
|
|
112
|
+
return () => {
|
|
113
|
+
for (const timeout of exitTimeouts.values()) {
|
|
114
|
+
clearTimeout(timeout);
|
|
115
|
+
}
|
|
116
|
+
exitTimeouts.clear();
|
|
117
|
+
};
|
|
118
|
+
}, []);
|
|
119
|
+
return {
|
|
120
|
+
exitingItems,
|
|
121
|
+
hasExitingItems: exitingItems.length > 0,
|
|
122
|
+
clearExitingItem
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
useItemExitAnimation
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=use-item-exit-animation.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/shared/use-item-exit-animation.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport {\n\tuseCallback,\n\tuseLayoutEffect,\n\tuseRef,\n\tuseState,\n} from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport type { ItemExitOverlayRect } from './item-exit-overlay';\nimport type { RectSnapshot } from './use-layout-shift-animation';\n\n/*\n * Last-resort cleanup if `animationend` never fires (the overlay's\n * `onAnimationEnd` is the primary path). Kept well above the motion\n * token durations so the timeout can never clip the exit animation.\n */\nconst EXIT_SAFETY_TIMEOUT_MS = 1000;\n\nexport type ExitingGridItem = {\n\tkey: string;\n\trect: ItemExitOverlayRect;\n\tchild: React.ReactElement;\n};\n\ntype UseItemExitAnimationOptions = {\n\tcontainer: HTMLElement | null;\n\tenabled: boolean;\n\tlayoutKeys: ReadonlySet< string >;\n\tgetPositionsBeforeLastChange: () => ReadonlyMap<\n\t\tstring,\n\t\tRectSnapshot\n\t> | null;\n\tchildrenCacheRef: React.MutableRefObject<\n\t\tMap< string, React.ReactElement >\n\t>;\n};\n\ntype UseItemExitAnimationResult = {\n\texitingItems: ExitingGridItem[];\n\thasExitingItems: boolean;\n\tclearExitingItem: ( key: string ) => void;\n};\n\nfunction prefersReducedMotion(): boolean {\n\treturn (\n\t\ttypeof window !== 'undefined' &&\n\t\twindow.matchMedia( '(prefers-reduced-motion: reduce)' ).matches\n\t);\n}\n\n/**\n * When `layout` loses keys in edit mode, keeps a short-lived overlay at\n * the removed tile's last position (scale + fade) while siblings FLIP.\n *\n * @param root0 Hook options.\n * @param root0.container Surface root that contains grid tiles.\n * @param root0.enabled When false, exiting state is cleared.\n * @param root0.layoutKeys Keys in the committed `layout` prop.\n * @param root0.getPositionsBeforeLastChange Container-relative rects before the latest layout commit.\n * @param root0.childrenCacheRef Last rendered children keyed by tile id.\n * @return Exiting overlays and a callback to dismiss one by key.\n */\nexport function useItemExitAnimation( {\n\tcontainer,\n\tenabled,\n\tlayoutKeys,\n\tgetPositionsBeforeLastChange,\n\tchildrenCacheRef,\n}: UseItemExitAnimationOptions ): UseItemExitAnimationResult {\n\tconst [ exitingItems, setExitingItems ] = useState< ExitingGridItem[] >(\n\t\t[]\n\t);\n\tconst prevLayoutKeysRef = useRef< Set< string > >( new Set() );\n\tconst exitTimeoutsRef = useRef<\n\t\tMap< string, ReturnType< typeof setTimeout > >\n\t>( new Map() );\n\n\tconst clearExitingItem = useCallback(\n\t\t( key: string ) => {\n\t\t\tconst timeout = exitTimeoutsRef.current.get( key );\n\t\t\tif ( timeout ) {\n\t\t\t\tclearTimeout( timeout );\n\t\t\t\texitTimeoutsRef.current.delete( key );\n\t\t\t}\n\t\t\tsetExitingItems( ( current ) =>\n\t\t\t\tcurrent.filter( ( item ) => item.key !== key )\n\t\t\t);\n\t\t\tchildrenCacheRef.current.delete( key );\n\t\t},\n\t\t[ childrenCacheRef ]\n\t);\n\n\tconst scheduleExitComplete = useCallback(\n\t\t( key: string ) => {\n\t\t\tif ( exitTimeoutsRef.current.has( key ) ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst timeout = setTimeout( () => {\n\t\t\t\texitTimeoutsRef.current.delete( key );\n\t\t\t\tclearExitingItem( key );\n\t\t\t}, EXIT_SAFETY_TIMEOUT_MS );\n\t\t\texitTimeoutsRef.current.set( key, timeout );\n\t\t},\n\t\t[ clearExitingItem ]\n\t);\n\n\tuseLayoutEffect( () => {\n\t\tif ( ! enabled || ! container ) {\n\t\t\tprevLayoutKeysRef.current = new Set( layoutKeys );\n\t\t\tfor ( const timeout of exitTimeoutsRef.current.values() ) {\n\t\t\t\tclearTimeout( timeout );\n\t\t\t}\n\t\t\texitTimeoutsRef.current.clear();\n\t\t\tsetExitingItems( [] );\n\t\t\treturn;\n\t\t}\n\n\t\tconst prevKeys = prevLayoutKeysRef.current;\n\t\tconst removed: string[] = [];\n\t\tfor ( const key of prevKeys ) {\n\t\t\tif ( ! layoutKeys.has( key ) ) {\n\t\t\t\tremoved.push( key );\n\t\t\t}\n\t\t}\n\t\tprevLayoutKeysRef.current = new Set( layoutKeys );\n\n\t\tif ( removed.length === 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst lastPositions = getPositionsBeforeLastChange();\n\t\tif ( ! lastPositions ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst nextExiting: ExitingGridItem[] = [];\n\t\tfor ( const key of removed ) {\n\t\t\tconst position = lastPositions.get( key );\n\t\t\tconst child = childrenCacheRef.current.get( key );\n\t\t\tif ( ! position || ! child ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tnextExiting.push( {\n\t\t\t\tkey,\n\t\t\t\trect: position,\n\t\t\t\tchild,\n\t\t\t} );\n\t\t}\n\n\t\tif ( nextExiting.length === 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( prefersReducedMotion() ) {\n\t\t\t// Siblings snap into place via the layout-shift hook; skip the\n\t\t\t// exit ghost (and its synchronous mount) entirely.\n\t\t\tfor ( const { key } of nextExiting ) {\n\t\t\t\tchildrenCacheRef.current.delete( key );\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// A state update inside a layout effect is flushed before paint,\n\t\t// so the ghost mounts in the same frame the tile is removed.\n\t\tsetExitingItems( ( current ) => [ ...current, ...nextExiting ] );\n\n\t\tfor ( const { key } of nextExiting ) {\n\t\t\tscheduleExitComplete( key );\n\t\t}\n\t}, [\n\t\tcontainer,\n\t\tenabled,\n\t\tgetPositionsBeforeLastChange,\n\t\tlayoutKeys,\n\t\tchildrenCacheRef,\n\t\tscheduleExitComplete,\n\t] );\n\n\tuseLayoutEffect( () => {\n\t\tconst exitTimeouts = exitTimeoutsRef.current;\n\t\treturn () => {\n\t\t\tfor ( const timeout of exitTimeouts.values() ) {\n\t\t\t\tclearTimeout( timeout );\n\t\t\t}\n\t\t\texitTimeouts.clear();\n\t\t};\n\t}, [] );\n\n\treturn {\n\t\texitingItems,\n\t\thasExitingItems: exitingItems.length > 0,\n\t\tclearExitingItem,\n\t};\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// packages/grid/src/shared/use-layout-shift-animation.ts
|
|
2
|
+
import { useCallback, useLayoutEffect, useRef } from "@wordpress/element";
|
|
3
|
+
import { GRID_ITEM_DATA_KEY } from "./grid-item-key.mjs";
|
|
4
|
+
function queryGridItems(container) {
|
|
5
|
+
return Array.from(
|
|
6
|
+
container.querySelectorAll(
|
|
7
|
+
`[${GRID_ITEM_DATA_KEY}]:not([data-wp-grid-item-exiting])`
|
|
8
|
+
)
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
function readItemKey(element) {
|
|
12
|
+
return element.getAttribute(GRID_ITEM_DATA_KEY);
|
|
13
|
+
}
|
|
14
|
+
function snapshotPositions(container) {
|
|
15
|
+
const base = container.getBoundingClientRect();
|
|
16
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17
|
+
for (const element of queryGridItems(container)) {
|
|
18
|
+
const key = readItemKey(element);
|
|
19
|
+
if (!key) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const { left, top, width, height } = element.getBoundingClientRect();
|
|
23
|
+
positions.set(key, {
|
|
24
|
+
left: left - base.left,
|
|
25
|
+
top: top - base.top,
|
|
26
|
+
width,
|
|
27
|
+
height
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return positions;
|
|
31
|
+
}
|
|
32
|
+
function clearLayoutShiftStyles(element) {
|
|
33
|
+
element.style.removeProperty("transform");
|
|
34
|
+
element.style.removeProperty("transition");
|
|
35
|
+
}
|
|
36
|
+
function playLayoutShift(element, deltaX, deltaY) {
|
|
37
|
+
if (deltaX === 0 && deltaY === 0) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
element.style.transition = "none";
|
|
41
|
+
element.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
|
|
42
|
+
void element.offsetHeight;
|
|
43
|
+
requestAnimationFrame(() => {
|
|
44
|
+
element.style.removeProperty("transition");
|
|
45
|
+
element.style.transform = "";
|
|
46
|
+
const onTransitionEnd = (event) => {
|
|
47
|
+
if (event.propertyName !== "transform") {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
element.removeEventListener("transitionend", onTransitionEnd);
|
|
51
|
+
clearLayoutShiftStyles(element);
|
|
52
|
+
};
|
|
53
|
+
element.addEventListener("transitionend", onTransitionEnd);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function useLayoutShiftAnimation({
|
|
57
|
+
container,
|
|
58
|
+
enabled,
|
|
59
|
+
layoutFingerprint,
|
|
60
|
+
excludeItemKey = null
|
|
61
|
+
}) {
|
|
62
|
+
const snapshotBeforeChangeRef = useRef(null);
|
|
63
|
+
const lastRenderedPositionsRef = useRef(null);
|
|
64
|
+
const positionsBeforeLastChangeRef = useRef(null);
|
|
65
|
+
const captureLayoutSnapshot = useCallback(() => {
|
|
66
|
+
if (container) {
|
|
67
|
+
snapshotBeforeChangeRef.current = snapshotPositions(container);
|
|
68
|
+
}
|
|
69
|
+
}, [container]);
|
|
70
|
+
useLayoutEffect(() => {
|
|
71
|
+
if (!container || !enabled) {
|
|
72
|
+
snapshotBeforeChangeRef.current = null;
|
|
73
|
+
lastRenderedPositionsRef.current = null;
|
|
74
|
+
positionsBeforeLastChangeRef.current = null;
|
|
75
|
+
if (container) {
|
|
76
|
+
for (const element of queryGridItems(container)) {
|
|
77
|
+
clearLayoutShiftStyles(element);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
for (const element of queryGridItems(container)) {
|
|
83
|
+
clearLayoutShiftStyles(element);
|
|
84
|
+
}
|
|
85
|
+
const previous = snapshotBeforeChangeRef.current ?? lastRenderedPositionsRef.current;
|
|
86
|
+
snapshotBeforeChangeRef.current = null;
|
|
87
|
+
positionsBeforeLastChangeRef.current = previous ? new Map(previous) : null;
|
|
88
|
+
lastRenderedPositionsRef.current = snapshotPositions(container);
|
|
89
|
+
if (previous) {
|
|
90
|
+
const base = container.getBoundingClientRect();
|
|
91
|
+
for (const element of queryGridItems(container)) {
|
|
92
|
+
const key = readItemKey(element);
|
|
93
|
+
if (!key || key === excludeItemKey) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const old = previous.get(key);
|
|
97
|
+
if (!old) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const { left, top } = element.getBoundingClientRect();
|
|
101
|
+
const deltaX = old.left - (left - base.left);
|
|
102
|
+
const deltaY = old.top - (top - base.top);
|
|
103
|
+
playLayoutShift(element, deltaX, deltaY);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}, [container, enabled, layoutFingerprint, excludeItemKey]);
|
|
107
|
+
const getLastPositions = useCallback(() => {
|
|
108
|
+
return lastRenderedPositionsRef.current;
|
|
109
|
+
}, []);
|
|
110
|
+
const getPositionsBeforeLastChange = useCallback(() => {
|
|
111
|
+
return positionsBeforeLastChangeRef.current;
|
|
112
|
+
}, []);
|
|
113
|
+
return {
|
|
114
|
+
captureLayoutSnapshot,
|
|
115
|
+
getLastPositions,
|
|
116
|
+
getPositionsBeforeLastChange
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function getLayoutFingerprint(layout) {
|
|
120
|
+
return layout.map(
|
|
121
|
+
(item) => `${item.key}:${String(item.width ?? "")}:${item.height ?? 1}:${item.order ?? ""}:${item.lane ?? ""}`
|
|
122
|
+
).join("|");
|
|
123
|
+
}
|
|
124
|
+
function getPlacementFingerprint(itemStyles) {
|
|
125
|
+
return [...itemStyles.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([key, style]) => {
|
|
126
|
+
const column = style.gridColumn ?? "";
|
|
127
|
+
const columnStart = style.gridColumnStart ?? "";
|
|
128
|
+
const rowStart = style.gridRowStart ?? "";
|
|
129
|
+
const rowEnd = style.gridRowEnd ?? "";
|
|
130
|
+
return `${key}:${String(column)}:${String(
|
|
131
|
+
columnStart
|
|
132
|
+
)}:${String(rowStart)}:${String(rowEnd)}`;
|
|
133
|
+
}).join("|");
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
getLayoutFingerprint,
|
|
137
|
+
getPlacementFingerprint,
|
|
138
|
+
useLayoutShiftAnimation
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=use-layout-shift-animation.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/shared/use-layout-shift-animation.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useCallback, useLayoutEffect, useRef } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport { GRID_ITEM_DATA_KEY } from './grid-item-key';\n\n/* `left`/`top` are relative to the grid container, not the viewport. */\nexport type RectSnapshot = {\n\tleft: number;\n\ttop: number;\n\twidth: number;\n\theight: number;\n};\n\ntype UseLayoutShiftAnimationOptions = {\n\t/**\n\t * Surface root that contains grid tiles.\n\t */\n\tcontainer: HTMLElement | null;\n\n\t/**\n\t * When false, snapshots are cleared and no transforms run.\n\t */\n\tenabled: boolean;\n\n\t/**\n\t * Serialized layout/placement state. The hook runs FLIP when this\n\t * value changes while `enabled` is true.\n\t */\n\tlayoutFingerprint: string;\n\n\t/**\n\t * Item key to skip (the tile being dragged or resized).\n\t */\n\texcludeItemKey?: string | null;\n};\n\ntype UseLayoutShiftAnimationResult = {\n\t/**\n\t * Capture tile positions synchronously **before** a layout update\n\t * (call immediately before `setTemporaryLayout` / similar).\n\t */\n\tcaptureLayoutSnapshot: () => void;\n\n\t/**\n\t * Container-relative rects from the last committed paint (settled, no\n\t * FLIP invert transforms).\n\t */\n\tgetLastPositions: () => ReadonlyMap< string, RectSnapshot > | null;\n\n\t/**\n\t * Tile positions immediately before the latest layout commit. Used\n\t * by item-exit animation when keys drop out of `layout`.\n\t */\n\tgetPositionsBeforeLastChange: () => ReadonlyMap<\n\t\tstring,\n\t\tRectSnapshot\n\t> | null;\n};\n\nfunction queryGridItems( container: HTMLElement ): HTMLElement[] {\n\treturn Array.from(\n\t\tcontainer.querySelectorAll< HTMLElement >(\n\t\t\t`[${ GRID_ITEM_DATA_KEY }]:not([data-wp-grid-item-exiting])`\n\t\t)\n\t);\n}\n\nfunction readItemKey( element: HTMLElement ): string | null {\n\treturn element.getAttribute( GRID_ITEM_DATA_KEY );\n}\n\nfunction snapshotPositions(\n\tcontainer: HTMLElement\n): Map< string, RectSnapshot > {\n\t// Measure relative to the container so positions stay valid even if the\n\t// page scroll shifts between capture and use (e.g. the document reflowing\n\t// shorter after a tile is removed).\n\tconst base = container.getBoundingClientRect();\n\tconst positions = new Map< string, RectSnapshot >();\n\tfor ( const element of queryGridItems( container ) ) {\n\t\tconst key = readItemKey( element );\n\t\tif ( ! key ) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst { left, top, width, height } = element.getBoundingClientRect();\n\t\tpositions.set( key, {\n\t\t\tleft: left - base.left,\n\t\t\ttop: top - base.top,\n\t\t\twidth,\n\t\t\theight,\n\t\t} );\n\t}\n\treturn positions;\n}\n\nfunction clearLayoutShiftStyles( element: HTMLElement ): void {\n\telement.style.removeProperty( 'transform' );\n\telement.style.removeProperty( 'transition' );\n}\n\nfunction playLayoutShift(\n\telement: HTMLElement,\n\tdeltaX: number,\n\tdeltaY: number\n): void {\n\tif ( deltaX === 0 && deltaY === 0 ) {\n\t\treturn;\n\t}\n\n\t// Invert: show the tile where it was before the layout change.\n\telement.style.transition = 'none';\n\telement.style.transform = `translate(${ deltaX }px, ${ deltaY }px)`;\n\tvoid element.offsetHeight;\n\n\t// Play on the next frame so the inverted transform paints before\n\t// the transition back to the committed grid position.\n\trequestAnimationFrame( () => {\n\t\telement.style.removeProperty( 'transition' );\n\t\telement.style.transform = '';\n\n\t\tconst onTransitionEnd = ( event: TransitionEvent ) => {\n\t\t\tif ( event.propertyName !== 'transform' ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telement.removeEventListener( 'transitionend', onTransitionEnd );\n\t\t\tclearLayoutShiftStyles( element );\n\t\t};\n\t\telement.addEventListener( 'transitionend', onTransitionEnd );\n\t} );\n}\n\n/**\n * Animates sibling tiles when grid layout reflows during drag or resize\n * using a FLIP transform (see `layout-shift-animation.module.css`).\n *\n * @param root0 Hook options.\n * @param root0.container Surface root that contains grid tiles.\n * @param root0.enabled When false, snapshots are cleared and no transforms run.\n * @param root0.layoutFingerprint Serialized layout/placement state.\n * @param root0.excludeItemKey Item key to skip (the tile being dragged or resized).\n * @return Snapshot capture callback for use before layout updates.\n */\nexport function useLayoutShiftAnimation( {\n\tcontainer,\n\tenabled,\n\tlayoutFingerprint,\n\texcludeItemKey = null,\n}: UseLayoutShiftAnimationOptions ): UseLayoutShiftAnimationResult {\n\tconst snapshotBeforeChangeRef = useRef< Map<\n\t\tstring,\n\t\tRectSnapshot\n\t> | null >( null );\n\tconst lastRenderedPositionsRef = useRef< Map<\n\t\tstring,\n\t\tRectSnapshot\n\t> | null >( null );\n\tconst positionsBeforeLastChangeRef = useRef< Map<\n\t\tstring,\n\t\tRectSnapshot\n\t> | null >( null );\n\n\tconst captureLayoutSnapshot = useCallback( () => {\n\t\tif ( container ) {\n\t\t\tsnapshotBeforeChangeRef.current = snapshotPositions( container );\n\t\t}\n\t}, [ container ] );\n\n\tuseLayoutEffect( () => {\n\t\tif ( ! container || ! enabled ) {\n\t\t\tsnapshotBeforeChangeRef.current = null;\n\t\t\tlastRenderedPositionsRef.current = null;\n\t\t\tpositionsBeforeLastChangeRef.current = null;\n\t\t\tif ( container ) {\n\t\t\t\tfor ( const element of queryGridItems( container ) ) {\n\t\t\t\t\tclearLayoutShiftStyles( element );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tfor ( const element of queryGridItems( container ) ) {\n\t\t\tclearLayoutShiftStyles( element );\n\t\t}\n\n\t\tconst previous =\n\t\t\tsnapshotBeforeChangeRef.current ?? lastRenderedPositionsRef.current;\n\t\tsnapshotBeforeChangeRef.current = null;\n\n\t\tpositionsBeforeLastChangeRef.current = previous\n\t\t\t? new Map( previous )\n\t\t\t: null;\n\n\t\t// Record settled grid positions for the next FLIP. Must run before\n\t\t// invert transforms — measuring after `playLayoutShift` would bake\n\t\t// translate offsets into the baseline and skew the next animation.\n\t\tlastRenderedPositionsRef.current = snapshotPositions( container );\n\n\t\tif ( previous ) {\n\t\t\tconst base = container.getBoundingClientRect();\n\t\t\tfor ( const element of queryGridItems( container ) ) {\n\t\t\t\tconst key = readItemKey( element );\n\t\t\t\tif ( ! key || key === excludeItemKey ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst old = previous.get( key );\n\t\t\t\tif ( ! old ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst { left, top } = element.getBoundingClientRect();\n\t\t\t\tconst deltaX = old.left - ( left - base.left );\n\t\t\t\tconst deltaY = old.top - ( top - base.top );\n\t\t\t\tplayLayoutShift( element, deltaX, deltaY );\n\t\t\t}\n\t\t}\n\t}, [ container, enabled, layoutFingerprint, excludeItemKey ] );\n\n\tconst getLastPositions = useCallback( () => {\n\t\treturn lastRenderedPositionsRef.current;\n\t}, [] );\n\n\tconst getPositionsBeforeLastChange = useCallback( () => {\n\t\treturn positionsBeforeLastChangeRef.current;\n\t}, [] );\n\n\treturn {\n\t\tcaptureLayoutSnapshot,\n\t\tgetLastPositions,\n\t\tgetPositionsBeforeLastChange,\n\t};\n}\n\n/**\n * Stable fingerprint for {@link useLayoutShiftAnimation}. Width/height\n * values may be numbers or layout keywords (`'fill'`, `'full'`).\n *\n * @param layout Layout items to serialize.\n * @return Fingerprint string.\n */\nexport function getLayoutFingerprint(\n\tlayout: ReadonlyArray< {\n\t\tkey: string;\n\t\twidth?: number | string;\n\t\theight?: number;\n\t\torder?: number;\n\t\tlane?: number;\n\t} >\n): string {\n\treturn layout\n\t\t.map(\n\t\t\t( item ) =>\n\t\t\t\t`${ item.key }:${ String( item.width ?? '' ) }:${\n\t\t\t\t\titem.height ?? 1\n\t\t\t\t}:${ item.order ?? '' }:${ item.lane ?? '' }`\n\t\t)\n\t\t.join( '|' );\n}\n\n/**\n * Placement fingerprint for lanes polyfill / explicit grid positions.\n *\n * @param itemStyles Per-item inline placement styles.\n * @return Fingerprint string.\n */\nexport function getPlacementFingerprint(\n\titemStyles: Map< string, React.CSSProperties >\n): string {\n\treturn [ ...itemStyles.entries() ]\n\t\t.sort( ( [ a ], [ b ] ) => a.localeCompare( b ) )\n\t\t.map( ( [ key, style ] ) => {\n\t\t\tconst column = style.gridColumn ?? '';\n\t\t\tconst columnStart = style.gridColumnStart ?? '';\n\t\t\tconst rowStart = style.gridRowStart ?? '';\n\t\t\tconst rowEnd = style.gridRowEnd ?? '';\n\t\t\treturn `${ key }:${ String( column ) }:${ String(\n\t\t\t\tcolumnStart\n\t\t\t) }:${ String( rowStart ) }:${ String( rowEnd ) }`;\n\t\t} )\n\t\t.join( '|' );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { GridItemProps } from './types';
|
|
2
|
+
export declare function GridItem({ item, maxColumns, disabled, verticalResizable, interacting, dragging, children, actionableArea, onResize, onResizeEnd, resizeSnapPreview, minResizeWidthPx, minResizeHeightPx, renderResizeHandle }: GridItemProps): import("react").JSX.Element;
|
|
3
|
+
//# sourceMappingURL=grid-item.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grid-item.d.ts","sourceRoot":"","sources":["../../src/dashboard-grid/grid-item.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAkB7C,wBAAgB,QAAQ,CAAE,EACzB,IAAI,EACJ,UAAU,EACV,QAAgB,EAChB,iBAAwB,EACxB,WAAmB,EACnB,QAAgB,EAChB,QAAQ,EACR,cAAqB,EACrB,QAAQ,EACR,WAAW,EACX,iBAAwB,EACxB,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,EAAE,aAAa,+BA2If"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { DashboardGridProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 2D packed dashboard grid with drag-to-reorder and resize handles.
|
|
4
|
+
* Supports fixed-column and responsive modes, `number | 'fill' | 'full'`
|
|
5
|
+
* widths, and multi-row tiles.
|
|
6
|
+
*
|
|
7
|
+
* Each child's `key` must match an entry in the `layout` array;
|
|
8
|
+
* children without a match render at the end of the grid without
|
|
9
|
+
* explicit placement and fall through CSS Grid's auto-flow.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```jsx
|
|
13
|
+
* const layout = [
|
|
14
|
+
* { key: 'a', width: 2 },
|
|
15
|
+
* { key: 'b', width: 'fill' },
|
|
16
|
+
* { key: 'c', width: 'full' },
|
|
17
|
+
* ];
|
|
18
|
+
*
|
|
19
|
+
* <DashboardGrid
|
|
20
|
+
* layout={ layout }
|
|
21
|
+
* columns={ 6 }
|
|
22
|
+
* editMode
|
|
23
|
+
* onChangeLayout={ setLayout }
|
|
24
|
+
* >
|
|
25
|
+
* <div key="a">A</div>
|
|
26
|
+
* <div key="b">B</div>
|
|
27
|
+
* <div key="c">C</div>
|
|
28
|
+
* </DashboardGrid>
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @param props Component props.
|
|
32
|
+
* @param ref Forwarded to the grid's root `<div>`.
|
|
33
|
+
*/
|
|
34
|
+
export declare const DashboardGrid: import("react").ForwardRefExoticComponent<DashboardGridProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard-grid/index.tsx"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAA2B,kBAAkB,EAAE,MAAM,SAAS,CAAC;AA8B3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,aAAa,+GA0lBzB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { DashboardGridLayoutItem } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Resolves items with `width: 'fill'` by computing how many columns they
|
|
7
|
+
* should span. Simulates CSS Grid's row-sparse auto-flow placement so the
|
|
8
|
+
* resolved span matches the free run that CSS Grid will actually use.
|
|
9
|
+
*
|
|
10
|
+
* Two paths:
|
|
11
|
+
* - Fast path (no `height > 1` items): single-row column tracker, O(n).
|
|
12
|
+
* Each fixed item between two fills is visited at most once by a fill's
|
|
13
|
+
* look-ahead.
|
|
14
|
+
* - Multi-row path (any item with `height > 1`): per-column skyline that
|
|
15
|
+
* tracks shadow occupation of tall tiles. Placement scans rows in
|
|
16
|
+
* row-major order, so worst-case cost depends on both columns and rows
|
|
17
|
+
* scanned (rows bounded by the sum of item heights), not only on
|
|
18
|
+
* `maxColumns`.
|
|
19
|
+
*
|
|
20
|
+
* @param sortedKeys - Item keys in display order.
|
|
21
|
+
* @param layoutMap - Map of key to DashboardGridLayoutItem.
|
|
22
|
+
* @param maxColumns - Total columns in the grid.
|
|
23
|
+
* @return Map of fill item keys to their resolved column spans.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveFillWidths(sortedKeys: string[], layoutMap: Map<string, DashboardGridLayoutItem>, maxColumns: number): Map<string, number>;
|
|
26
|
+
//# sourceMappingURL=resolve-fill-widths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-fill-widths.d.ts","sourceRoot":"","sources":["../../src/dashboard-grid/resolve-fill-widths.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAChC,UAAU,EAAE,MAAM,EAAE,EACpB,SAAS,EAAE,GAAG,CAAE,MAAM,EAAE,uBAAuB,CAAE,EACjD,UAAU,EAAE,MAAM,GAChB,GAAG,CAAE,MAAM,EAAE,MAAM,CAAE,CAkMvB"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
5
|
+
/**
|
|
6
|
+
* Internal dependencies
|
|
7
|
+
*/
|
|
8
|
+
import { DashboardGrid } from '..';
|
|
9
|
+
declare const meta: Meta<typeof DashboardGrid>;
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof DashboardGrid>;
|
|
12
|
+
/**
|
|
13
|
+
* Static grid with a fixed number of columns. Each item declares its
|
|
14
|
+
* column span via `width`. Items flow left-to-right and wrap to new
|
|
15
|
+
* rows as the total exceeds `columns`.
|
|
16
|
+
*/
|
|
17
|
+
export declare const Default: Story;
|
|
18
|
+
/**
|
|
19
|
+
* Responsive grid: the column count is derived from the container
|
|
20
|
+
* width using `minColumnWidth` as the lower bound per column. A
|
|
21
|
+
* `ResizeObserver` recomputes the count on container resize.
|
|
22
|
+
*/
|
|
23
|
+
export declare const Responsive: Story;
|
|
24
|
+
/**
|
|
25
|
+
* Layered configuration: `columns` caps the count and
|
|
26
|
+
* `minColumnWidth` enforces a per-tile width floor. The grid renders
|
|
27
|
+
* up to `columns` columns on wide containers and reduces the count
|
|
28
|
+
* on narrow ones whenever fitting all of them would push tiles
|
|
29
|
+
* below `minColumnWidth`. Resize the preview to see the cap apply
|
|
30
|
+
* on wide widths and the floor reduce the count on narrow widths.
|
|
31
|
+
*/
|
|
32
|
+
export declare const Layered: Story;
|
|
33
|
+
/**
|
|
34
|
+
* A `width: 'fill'` item expands to cover the remaining columns in
|
|
35
|
+
* its row. Mix it with fixed-width items on either side to build
|
|
36
|
+
* sidebar-like layouts that adapt to the column count.
|
|
37
|
+
*/
|
|
38
|
+
export declare const FillWidth: Story;
|
|
39
|
+
/**
|
|
40
|
+
* A `width: 'full'` item spans every column (`grid-column: 1 / -1`),
|
|
41
|
+
* forcing a row break around it. Useful for dividers, hero banners,
|
|
42
|
+
* or embedded content that should always take the full width.
|
|
43
|
+
*/
|
|
44
|
+
export declare const FullWidth: Story;
|
|
45
|
+
/**
|
|
46
|
+
* Numeric `rowHeight` lets items span multiple rows via `height`.
|
|
47
|
+
* Combined with `width`, this produces tile-based dashboards where
|
|
48
|
+
* each cell can be tuned independently.
|
|
49
|
+
*/
|
|
50
|
+
export declare const RowHeight: Story;
|
|
51
|
+
/**
|
|
52
|
+
* Edit mode with drag, resize, and all width modes. While `editMode`
|
|
53
|
+
* is on, `<DashboardGrid />` paints its default overlay behind the
|
|
54
|
+
* tiles to visualize the underlying template: rounded row-marker
|
|
55
|
+
* tiles in each column when `rowHeight` is numeric. The overlay
|
|
56
|
+
* disappears when `editMode` flips back to `false`.
|
|
57
|
+
*
|
|
58
|
+
* Theme the default look in place via `--wp-grid-overlay-tile-bg`,
|
|
59
|
+
* or replace the visual wholesale by passing `renderGridOverlay`.
|
|
60
|
+
* See the `Custom Grid Overlay` story for a full override example.
|
|
61
|
+
*
|
|
62
|
+
* A state panel shows the raw layout JSON. Drag items to reorder;
|
|
63
|
+
* resize from the bottom-right handle. Keyboard sensor is enabled:
|
|
64
|
+
* use Tab to focus an item, Space to grab, arrow keys to move, Space
|
|
65
|
+
* to drop.
|
|
66
|
+
*/
|
|
67
|
+
export declare const EditMode: Story;
|
|
68
|
+
/**
|
|
69
|
+
* Exercises the three customization vectors on a single grid:
|
|
70
|
+
*
|
|
71
|
+
* 1. `renderResizeHandle` swaps the default corner triangle for a
|
|
72
|
+
* custom diagonal-arrow icon.
|
|
73
|
+
* 2. `renderDragPreview` wraps the dragged clone (here only for the
|
|
74
|
+
* height chain; lift and shadow stay on the grid frame).
|
|
75
|
+
* 3. CSS custom properties on an ancestor retheme the lift scale,
|
|
76
|
+
* placeholder opacity, placeholder outline color, and placeholder
|
|
77
|
+
* border-radius without touching the package.
|
|
78
|
+
*
|
|
79
|
+
* Toggle `editMode`, then drag and resize a tile to see all three
|
|
80
|
+
* respond.
|
|
81
|
+
*/
|
|
82
|
+
export declare const Customization: Story;
|
|
83
|
+
/**
|
|
84
|
+
* Replaces the package's default edit-mode overlay with a custom
|
|
85
|
+
* visual through the `renderGridOverlay` prop. The grid mounts the
|
|
86
|
+
* supplied component as a sibling behind the tiles whenever
|
|
87
|
+
* `editMode` is on, passing the resolved `{ columns, rowHeight }`
|
|
88
|
+
* so the override can reproduce the column and row tracks
|
|
89
|
+
* pixel-accurately without re-deriving them.
|
|
90
|
+
*
|
|
91
|
+
* Here the override (see `NumberedOverlay` above) swaps the warning
|
|
92
|
+
* tone for info, drops the row dividers, and labels each column
|
|
93
|
+
* track with its index. Pass `renderGridOverlay={ () => null }` to
|
|
94
|
+
* suppress the overlay entirely while keeping `editMode` interactions
|
|
95
|
+
* on.
|
|
96
|
+
*/
|
|
97
|
+
export declare const CustomGridOverlayStory: Story;
|
|
98
|
+
//# sourceMappingURL=index.story.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.story.d.ts","sourceRoot":"","sources":["../../../src/dashboard-grid/stories/index.story.tsx"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAU5D;;GAEG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAQnC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAE,OAAO,aAAa,CAuCrC,CAAC;eACa,IAAI;AAEnB,KAAK,KAAK,GAAG,QAAQ,CAAE,OAAO,aAAa,CAAE,CAAC;AAgM9C;;;;GAIG;AACH,eAAO,MAAM,OAAO,EAAE,KA4BrB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,KAiCxB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,EAAE,KAkCrB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS,EAAE,KAwBvB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS,EAAE,KA4BvB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS,EAAE,KA6BvB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,QAAQ,EAAE,KA8JtB,CAAC;AAoEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,EAAE,KA6D3B,CAAC;AAoEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,EAAE,KAmDpC,CAAC"}
|