@astryxdesign/lab 0.4.5-canary.bc34238 → 0.4.5-canary.ffd05c4
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TransferList.d.ts","sourceRoot":"","sources":["../../src/TransferList/TransferList.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"TransferList.d.ts","sourceRoot":"","sources":["../../src/TransferList/TransferList.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,EAWL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAuFlD,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAC3D,0DAA0D;IAC1D,KAAK,EAAE,CAAC,CAAC;IACT,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,4EAA4E;IAC5E,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,IAAI,CACxE,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,uCAAuC;IACvC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,OAAO,EAAE,SAAS,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1C,yEAAyE;IACzE,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IACpB,uEAAuE;IACvE,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,KAAK,IAAI,CAAC;IACxC,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;IAC5D,yCAAyC;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,0CAA0C;IAC1C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA+MD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,EACtD,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,OAAO,EACP,KAAK,EACL,QAAQ,EACR,aAA0B,EAC1B,cAA4B,EAC5B,SAAiB,EACjB,WAAW,EACX,iBAA6B,EAC7B,aAAoB,EACpB,YAAoB,EACpB,QAAgB,EAChB,YAAY,EACZ,iBAAyC,EACzC,kBAA2C,EAC3C,aAA4B,EAC5B,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,IAAI,EACJ,iBAAiB,EAAE,cAAc,EACjC,kBAAkB,EAAE,eAAe,EACnC,GAAG,SAAS,EACb,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,SAAS,CA24BlC;yBAt6Be,YAAY"}
|
|
@@ -13,13 +13,14 @@
|
|
|
13
13
|
* - /packages/lab/src/TransferList/TransferList.test.tsx
|
|
14
14
|
* - /packages/lab/src/TransferList/index.ts
|
|
15
15
|
*/
|
|
16
|
-
import { Fragment, useCallback, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
16
|
+
import { Fragment, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
17
17
|
import * as stylex from '@stylexjs/stylex';
|
|
18
|
-
import {
|
|
18
|
+
import { flushSync } from 'react-dom';
|
|
19
19
|
import { Button } from '@astryxdesign/core/Button';
|
|
20
20
|
import { Icon } from '@astryxdesign/core/Icon';
|
|
21
21
|
import { IconButton } from '@astryxdesign/core/IconButton';
|
|
22
22
|
import { Item } from '@astryxdesign/core/Item';
|
|
23
|
+
import { useLayer } from '@astryxdesign/core/Layer';
|
|
23
24
|
import { List } from '@astryxdesign/core/List';
|
|
24
25
|
import { Text } from '@astryxdesign/core/Text';
|
|
25
26
|
import { TextInput } from '@astryxdesign/core/TextInput';
|
|
@@ -171,17 +172,12 @@ const styles = {
|
|
|
171
172
|
$$css: true
|
|
172
173
|
}
|
|
173
174
|
};
|
|
174
|
-
const _temp = {
|
|
175
|
-
k3aq6I: "xsqj5wx",
|
|
176
|
-
"$$css": true
|
|
177
|
-
};
|
|
178
175
|
const dynamicStyles = {
|
|
179
|
-
dragPreview:
|
|
176
|
+
dragPreview: width => [{
|
|
180
177
|
kzqmXN: width != null ? "x5lhr3w" : width,
|
|
181
178
|
$$css: true
|
|
182
179
|
}, {
|
|
183
|
-
"--x-width": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(width)
|
|
184
|
-
"--x-transform": `translate3d(${x}px, ${y}px, 0)` != null ? `translate3d(${x}px, ${y}px, 0)` : undefined
|
|
180
|
+
"--x-width": (val => typeof val === "number" ? val + "px" : val != null ? val : undefined)(width)
|
|
185
181
|
}]
|
|
186
182
|
};
|
|
187
183
|
function moveItem(value, fromIndex, toIndex) {
|
|
@@ -256,6 +252,13 @@ export function TransferList({
|
|
|
256
252
|
const availablePanelRef = useRef(null);
|
|
257
253
|
const selectedRowRefs = useRef(new Map());
|
|
258
254
|
const dragPreviewRef = useRef(null);
|
|
255
|
+
// The ghost that follows the pointer is a layer, not a portal into body.
|
|
256
|
+
// Its coordinates come from getBoundingClientRect, so they are viewport
|
|
257
|
+
// relative; the top layer is the only host that reads them that way from
|
|
258
|
+
// any scroll position and still paints above a Popover or open dialog.
|
|
259
|
+
const dragPreviewLayer = useLayer({
|
|
260
|
+
mode: 'fixed'
|
|
261
|
+
});
|
|
259
262
|
const currentValueRef = useRef(value);
|
|
260
263
|
const reorderSessionRef = useRef(null);
|
|
261
264
|
const suppressClickRef = useRef(false);
|
|
@@ -666,7 +669,18 @@ export function TransferList({
|
|
|
666
669
|
y: reorderSession.pointerClientY - reorderSession.pointerOffsetY,
|
|
667
670
|
width: reorderSession.previewWidth
|
|
668
671
|
} : null;
|
|
669
|
-
const
|
|
672
|
+
const isDraggingPreview = dragPreviewPosition != null;
|
|
673
|
+
const {
|
|
674
|
+
show: showDragPreview,
|
|
675
|
+
hide: hideDragPreview
|
|
676
|
+
} = dragPreviewLayer;
|
|
677
|
+
useEffect(() => {
|
|
678
|
+
if (!isDraggingPreview) {
|
|
679
|
+
return undefined;
|
|
680
|
+
}
|
|
681
|
+
showDragPreview();
|
|
682
|
+
return hideDragPreview;
|
|
683
|
+
}, [isDraggingPreview, showDragPreview, hideDragPreview]);
|
|
670
684
|
const reorderControl = option => {
|
|
671
685
|
if (!isReorderable) {
|
|
672
686
|
return undefined;
|
|
@@ -897,12 +911,15 @@ export function TransferList({
|
|
|
897
911
|
})
|
|
898
912
|
})]
|
|
899
913
|
})]
|
|
900
|
-
}), dragPreviewPosition != null
|
|
914
|
+
}), dragPreviewPosition != null ? dragPreviewLayer.render(/*#__PURE__*/_jsx("div", {
|
|
901
915
|
ref: dragPreviewRef,
|
|
902
916
|
"aria-hidden": "true",
|
|
903
917
|
"data-transfer-list-drag-preview": "",
|
|
904
|
-
...stylex.props(reorderStyles.preview, styles.dragPreviewContainer, dynamicStyles.dragPreview(dragPreviewPosition.
|
|
905
|
-
}),
|
|
918
|
+
...stylex.props(reorderStyles.preview, styles.dragPreviewContainer, dynamicStyles.dragPreview(dragPreviewPosition.width))
|
|
919
|
+
}), {
|
|
920
|
+
x: dragPreviewPosition.x,
|
|
921
|
+
y: dragPreviewPosition.y
|
|
922
|
+
}) : null]
|
|
906
923
|
});
|
|
907
924
|
}
|
|
908
925
|
TransferList.displayName = 'TransferList';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/lab",
|
|
3
|
-
"version": "0.4.5-canary.
|
|
3
|
+
"version": "0.4.5-canary.ffd05c4",
|
|
4
4
|
"description": "Experimental Astryx components — published to npm only under the @canary dist-tag for early testing; never released as a stable (latest) version.",
|
|
5
5
|
"author": "Meta Open Source",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@stylexjs/stylex": ">=0.10.0",
|
|
48
|
-
"@astryxdesign/core": "0.4.5-canary.
|
|
48
|
+
"@astryxdesign/core": "0.4.5-canary.ffd05c4",
|
|
49
49
|
"react": ">=19.0.0",
|
|
50
50
|
"react-dom": ">=19.0.0"
|
|
51
51
|
},
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"d3-shape": "^3.2.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@astryxdesign/cli": "0.4.5-canary.
|
|
58
|
+
"@astryxdesign/cli": "0.4.5-canary.ffd05c4",
|
|
59
59
|
"@types/d3-array": "^3.2.2",
|
|
60
60
|
"@types/d3-scale": "^4.0.9",
|
|
61
61
|
"@types/d3-shape": "^3.1.8",
|
|
@@ -482,6 +482,29 @@ export const docs = {
|
|
|
482
482
|
},
|
|
483
483
|
],
|
|
484
484
|
},
|
|
485
|
+
// Mirrors the themeProps() calls in TransferList.tsx. `astryx theme build`
|
|
486
|
+
// reads this inventory to learn which visual props a target can vary on, so
|
|
487
|
+
// a target missing here cannot be themed even though it carries the class.
|
|
488
|
+
theming: {
|
|
489
|
+
targets: [
|
|
490
|
+
{
|
|
491
|
+
className: 'astryx-transfer-list',
|
|
492
|
+
visualProps: [],
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
className: 'astryx-transfer-list-collection',
|
|
496
|
+
visualProps: [],
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
className: 'astryx-transfer-list-panel',
|
|
500
|
+
visualProps: ['side'],
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
className: 'astryx-transfer-list-item',
|
|
504
|
+
visualProps: ['side', 'state'],
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
},
|
|
485
508
|
examples: [
|
|
486
509
|
{
|
|
487
510
|
label: 'Default immediate selector',
|
|
@@ -496,7 +496,7 @@ describe('TransferList', () => {
|
|
|
496
496
|
});
|
|
497
497
|
|
|
498
498
|
describe('pointer reordering', () => {
|
|
499
|
-
it('
|
|
499
|
+
it('promotes the drag preview to the top layer from inside a popover host', () => {
|
|
500
500
|
const {container} = render(
|
|
501
501
|
<div data-testid="popover-host" popover="auto">
|
|
502
502
|
<ControlledTransferList initialValue={['name', 'owner', 'status']} />
|
|
@@ -521,7 +521,13 @@ describe('TransferList', () => {
|
|
|
521
521
|
const preview = document.querySelector<HTMLElement>(
|
|
522
522
|
'[data-transfer-list-drag-preview]',
|
|
523
523
|
);
|
|
524
|
-
|
|
524
|
+
// The ghost reaches the top layer through its host's popover attribute
|
|
525
|
+
// rather than by being relocated into an ancestor. That keeps it painted
|
|
526
|
+
// above this popover host while it still inherits the host's theme
|
|
527
|
+
// variables, which a portal to document.body would have dropped.
|
|
528
|
+
const layer = preview?.parentElement ?? null;
|
|
529
|
+
expect(layer).toHaveAttribute('popover');
|
|
530
|
+
expect(screen.getByTestId('popover-host')).toContainElement(layer);
|
|
525
531
|
});
|
|
526
532
|
|
|
527
533
|
it('keeps rows stationary, locks X, follows pointer Y, and commits once', () => {
|
|
@@ -560,9 +566,10 @@ describe('TransferList', () => {
|
|
|
560
566
|
expect(getComputedStyle(preview!).opacity).toBe('0.5');
|
|
561
567
|
expect(preview).toHaveAttribute('aria-hidden', 'true');
|
|
562
568
|
expect(preview).toHaveTextContent('Owner');
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
569
|
+
// Placement lives on the layer as fixed insets. The coordinates come
|
|
570
|
+
// from getBoundingClientRect, so reading them as viewport insets is what
|
|
571
|
+
// keeps the ghost under the pointer once the page has been scrolled.
|
|
572
|
+
expect(preview!.parentElement).toHaveStyle({top: '40px', left: '0px'});
|
|
566
573
|
expect(
|
|
567
574
|
container.querySelector('[data-transfer-list-drop-target]'),
|
|
568
575
|
).not.toBeInTheDocument();
|
|
@@ -573,9 +580,7 @@ describe('TransferList', () => {
|
|
|
573
580
|
clientY: 5,
|
|
574
581
|
});
|
|
575
582
|
|
|
576
|
-
expect(preview!.
|
|
577
|
-
'translate3d(0px, -5px, 0)',
|
|
578
|
-
);
|
|
583
|
+
expect(preview!.parentElement).toHaveStyle({top: '-5px', left: '0px'});
|
|
579
584
|
expect([
|
|
580
585
|
...container.querySelectorAll<HTMLElement>('[data-transfer-list-row]'),
|
|
581
586
|
]).toEqual(rows);
|
|
@@ -596,9 +601,7 @@ describe('TransferList', () => {
|
|
|
596
601
|
clientY: 115,
|
|
597
602
|
});
|
|
598
603
|
|
|
599
|
-
expect(preview!.
|
|
600
|
-
'translate3d(0px, 105px, 0)',
|
|
601
|
-
);
|
|
604
|
+
expect(preview!.parentElement).toHaveStyle({top: '105px', left: '0px'});
|
|
602
605
|
expect(rows[0]).not.toHaveAttribute('data-transfer-list-drop-target');
|
|
603
606
|
expect(rows[2]).toHaveAttribute(
|
|
604
607
|
'data-transfer-list-drop-target',
|
|
@@ -655,11 +658,12 @@ describe('TransferList', () => {
|
|
|
655
658
|
expect(
|
|
656
659
|
document.querySelector('[data-transfer-list-drag-preview]'),
|
|
657
660
|
).toBeInTheDocument();
|
|
661
|
+
// A horizontal-only gesture must not move the ghost off the row it came
|
|
662
|
+
// from: the drag is locked to the Y axis.
|
|
658
663
|
expect(
|
|
659
|
-
document
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
).toBe('translate3d(0px, 0px, 0)');
|
|
664
|
+
document.querySelector<HTMLElement>('[data-transfer-list-drag-preview]')
|
|
665
|
+
?.parentElement,
|
|
666
|
+
).toHaveStyle({top: '0px', left: '0px'});
|
|
663
667
|
expect(
|
|
664
668
|
container.querySelector('[data-transfer-list-drop-target]'),
|
|
665
669
|
).not.toBeInTheDocument();
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import {
|
|
18
18
|
Fragment,
|
|
19
19
|
useCallback,
|
|
20
|
+
useEffect,
|
|
20
21
|
useId,
|
|
21
22
|
useLayoutEffect,
|
|
22
23
|
useMemo,
|
|
@@ -27,12 +28,13 @@ import {
|
|
|
27
28
|
type ReactNode,
|
|
28
29
|
} from 'react';
|
|
29
30
|
import * as stylex from '@stylexjs/stylex';
|
|
30
|
-
import {
|
|
31
|
+
import {flushSync} from 'react-dom';
|
|
31
32
|
import type {BaseProps} from '@astryxdesign/core';
|
|
32
33
|
import {Button} from '@astryxdesign/core/Button';
|
|
33
34
|
import {Icon} from '@astryxdesign/core/Icon';
|
|
34
35
|
import {IconButton} from '@astryxdesign/core/IconButton';
|
|
35
36
|
import {Item} from '@astryxdesign/core/Item';
|
|
37
|
+
import {useLayer} from '@astryxdesign/core/Layer';
|
|
36
38
|
import {List} from '@astryxdesign/core/List';
|
|
37
39
|
import {Text} from '@astryxdesign/core/Text';
|
|
38
40
|
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
@@ -345,10 +347,10 @@ const styles = stylex.create({
|
|
|
345
347
|
});
|
|
346
348
|
|
|
347
349
|
const dynamicStyles = stylex.create({
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}),
|
|
350
|
+
// Only the width is dynamic here. The layer owns the placement: it reads the
|
|
351
|
+
// same viewport coordinates as `position: fixed` insets, which is what
|
|
352
|
+
// getBoundingClientRect gives us.
|
|
353
|
+
dragPreview: (width: number) => ({width}),
|
|
352
354
|
});
|
|
353
355
|
|
|
354
356
|
function moveItem<T>(
|
|
@@ -435,6 +437,11 @@ export function TransferList<T extends string = string>({
|
|
|
435
437
|
const availablePanelRef = useRef<HTMLDivElement | null>(null);
|
|
436
438
|
const selectedRowRefs = useRef(new Map<T, HTMLElement>());
|
|
437
439
|
const dragPreviewRef = useRef<HTMLDivElement | null>(null);
|
|
440
|
+
// The ghost that follows the pointer is a layer, not a portal into body.
|
|
441
|
+
// Its coordinates come from getBoundingClientRect, so they are viewport
|
|
442
|
+
// relative; the top layer is the only host that reads them that way from
|
|
443
|
+
// any scroll position and still paints above a Popover or open dialog.
|
|
444
|
+
const dragPreviewLayer = useLayer({mode: 'fixed'});
|
|
438
445
|
const currentValueRef = useRef<readonly T[]>(value);
|
|
439
446
|
const reorderSessionRef = useRef<ReorderSession<T> | null>(null);
|
|
440
447
|
const suppressClickRef = useRef(false);
|
|
@@ -1010,11 +1017,15 @@ export function TransferList<T extends string = string>({
|
|
|
1010
1017
|
width: reorderSession.previewWidth,
|
|
1011
1018
|
}
|
|
1012
1019
|
: null;
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1020
|
+
const isDraggingPreview = dragPreviewPosition != null;
|
|
1021
|
+
const {show: showDragPreview, hide: hideDragPreview} = dragPreviewLayer;
|
|
1022
|
+
useEffect(() => {
|
|
1023
|
+
if (!isDraggingPreview) {
|
|
1024
|
+
return undefined;
|
|
1025
|
+
}
|
|
1026
|
+
showDragPreview();
|
|
1027
|
+
return hideDragPreview;
|
|
1028
|
+
}, [isDraggingPreview, showDragPreview, hideDragPreview]);
|
|
1018
1029
|
|
|
1019
1030
|
const reorderControl = (option: TransferListOption<T>) => {
|
|
1020
1031
|
if (!isReorderable) {
|
|
@@ -1304,8 +1315,8 @@ export function TransferList<T extends string = string>({
|
|
|
1304
1315
|
</div>
|
|
1305
1316
|
</div>
|
|
1306
1317
|
</div>
|
|
1307
|
-
{dragPreviewPosition != null
|
|
1308
|
-
?
|
|
1318
|
+
{dragPreviewPosition != null
|
|
1319
|
+
? dragPreviewLayer.render(
|
|
1309
1320
|
<div
|
|
1310
1321
|
ref={dragPreviewRef}
|
|
1311
1322
|
aria-hidden="true"
|
|
@@ -1313,14 +1324,10 @@ export function TransferList<T extends string = string>({
|
|
|
1313
1324
|
{...stylex.props(
|
|
1314
1325
|
reorderStyles.preview,
|
|
1315
1326
|
styles.dragPreviewContainer,
|
|
1316
|
-
dynamicStyles.dragPreview(
|
|
1317
|
-
dragPreviewPosition.x,
|
|
1318
|
-
dragPreviewPosition.y,
|
|
1319
|
-
dragPreviewPosition.width,
|
|
1320
|
-
),
|
|
1327
|
+
dynamicStyles.dragPreview(dragPreviewPosition.width),
|
|
1321
1328
|
)}
|
|
1322
1329
|
/>,
|
|
1323
|
-
|
|
1330
|
+
{x: dragPreviewPosition.x, y: dragPreviewPosition.y},
|
|
1324
1331
|
)
|
|
1325
1332
|
: null}
|
|
1326
1333
|
</div>
|