@astryxdesign/core 0.2.0-canary.523df3f → 0.2.0-canary.5392b88
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/README.md +5 -3
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.js +3 -7
- package/dist/Carousel/Carousel.d.ts +53 -2
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +81 -14
- package/dist/Carousel/index.d.ts +1 -1
- package/dist/Carousel/index.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +3 -1
- package/dist/ContextMenu/ContextMenu.d.ts +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +13 -4
- package/dist/DropdownMenu/DropdownMenu.d.ts +8 -2
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +22 -18
- package/dist/Icon/globalIconRegistry.d.ts +42 -2
- package/dist/Icon/globalIconRegistry.d.ts.map +1 -1
- package/dist/Icon/globalIconRegistry.js +49 -1
- package/dist/Icon/index.d.ts +2 -2
- package/dist/Icon/index.d.ts.map +1 -1
- package/dist/Icon/index.js +1 -1
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +31 -9
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +48 -14
- package/dist/Popover/Popover.d.ts +19 -2
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +4 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +48 -14
- package/dist/Timestamp/Timestamp.d.ts +4 -1
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +76 -5
- package/dist/Timestamp/formatInstant.d.ts +3 -3
- package/dist/Timestamp/formatInstant.d.ts.map +1 -1
- package/dist/Timestamp/formatInstant.js +2 -2
- package/dist/Timestamp/tooltipEntries.d.ts +3 -2
- package/dist/Timestamp/tooltipEntries.d.ts.map +1 -1
- package/dist/Timestamp/tooltipEntries.js +3 -2
- package/dist/astryx.css +0 -1
- package/dist/astryx.umd.js +48 -48
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/useGridFocus.d.ts +8 -6
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.d.ts +8 -7
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useTreeFocus.d.ts.map +1 -1
- package/dist/hooks/useTreeFocus.js +13 -2
- package/package.json +2 -2
- package/src/AvatarGroup/AvatarGroup.tsx +2 -6
- package/src/Carousel/Carousel.doc.mjs +7 -0
- package/src/Carousel/Carousel.test.tsx +232 -1
- package/src/Carousel/Carousel.tsx +143 -20
- package/src/Carousel/index.ts +1 -1
- package/src/CommandPalette/CommandPaletteInput.tsx +4 -0
- package/src/ContextMenu/ContextMenu.tsx +16 -4
- package/src/DropdownMenu/DropdownMenu.doc.mjs +12 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +87 -0
- package/src/DropdownMenu/DropdownMenu.tsx +30 -19
- package/src/Icon/Icon.doc.mjs +1 -0
- package/src/Icon/globalIconRegistry.test.tsx +35 -0
- package/src/Icon/globalIconRegistry.tsx +68 -5
- package/src/Icon/index.ts +2 -0
- package/src/Markdown/Markdown.doc.mjs +80 -0
- package/src/Markdown/Markdown.test.tsx +92 -0
- package/src/Markdown/Markdown.tsx +93 -65
- package/src/Markdown/parser.perf.test.ts +47 -35
- package/src/MultiSelector/MultiSelector.doc.mjs +1 -1
- package/src/MultiSelector/MultiSelector.test.tsx +141 -0
- package/src/MultiSelector/MultiSelector.tsx +51 -34
- package/src/Popover/Popover.doc.mjs +28 -0
- package/src/Popover/Popover.test.tsx +41 -0
- package/src/Popover/Popover.tsx +24 -1
- package/src/Selector/Selector.doc.mjs +1 -1
- package/src/Selector/Selector.test.tsx +122 -0
- package/src/Selector/Selector.tsx +51 -34
- package/src/Timestamp/Timestamp.doc.mjs +2 -2
- package/src/Timestamp/Timestamp.test.tsx +131 -0
- package/src/Timestamp/Timestamp.tsx +91 -8
- package/src/Timestamp/formatInstant.ts +3 -4
- package/src/Timestamp/tooltipEntries.ts +3 -2
- package/src/hooks/useGridFocus.ts +8 -6
- package/src/hooks/useListFocus.ts +8 -7
- package/src/hooks/useTreeFocus.test.tsx +53 -1
- package/src/hooks/useTreeFocus.ts +13 -2
package/README.md
CHANGED
|
@@ -75,10 +75,12 @@ astryx gap-report # report a missing capability
|
|
|
75
75
|
|
|
76
76
|
## Quick Start
|
|
77
77
|
|
|
78
|
+
Astryx requires **React 19** or later (`react` and `react-dom` >= 19.0.0 are peer dependencies).
|
|
79
|
+
|
|
78
80
|
Install Astryx and a theme:
|
|
79
81
|
|
|
80
82
|
```bash
|
|
81
|
-
npm install @astryxdesign/core @astryxdesign/theme-neutral
|
|
83
|
+
npm install @astryxdesign/core @astryxdesign/theme-neutral @stylexjs/stylex
|
|
82
84
|
```
|
|
83
85
|
|
|
84
86
|
Then pick your setup below based on your framework and styling approach.
|
|
@@ -226,7 +228,7 @@ export default function Page() {
|
|
|
226
228
|
Use the pre-built dist alongside StyleX for your own styles.
|
|
227
229
|
|
|
228
230
|
```bash
|
|
229
|
-
npm install @astryxdesign/core @astryxdesign/theme-neutral
|
|
231
|
+
npm install @astryxdesign/core @astryxdesign/theme-neutral @stylexjs/stylex
|
|
230
232
|
```
|
|
231
233
|
|
|
232
234
|
**`src/app/globals.css`**
|
|
@@ -242,7 +244,7 @@ Providers and layout are the same as the Tailwind example (use `@astryxdesign/th
|
|
|
242
244
|
### Vite
|
|
243
245
|
|
|
244
246
|
```bash
|
|
245
|
-
npm install @astryxdesign/core @astryxdesign/theme-neutral
|
|
247
|
+
npm install @astryxdesign/core @astryxdesign/theme-neutral @stylexjs/stylex
|
|
246
248
|
```
|
|
247
249
|
|
|
248
250
|
Same CSS imports and providers as above. No build plugins needed; Astryx ships pre-built.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AvatarGroup.d.ts","sourceRoot":"","sources":["../../src/AvatarGroup/AvatarGroup.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAA2B,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAC/D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAc,KAAK,UAAU,EAAC,MAAM,WAAW,CAAC;AAavD,MAAM,WAAW,gBAAiB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACjE,yCAAyC;IACzC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AASD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,IAAW,EACX,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,kBAAkB,EAChC,kBAAkB,EAAE,wBAAwB,EAC5C,SAAS,EACT,OAAO,EACP,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,gBAAgB,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"AvatarGroup.d.ts","sourceRoot":"","sources":["../../src/AvatarGroup/AvatarGroup.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAA2B,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAC/D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAc,KAAK,UAAU,EAAC,MAAM,WAAW,CAAC;AAavD,MAAM,WAAW,gBAAiB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACjE,yCAAyC;IACzC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AASD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,IAAW,EACX,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,kBAAkB,EAChC,kBAAkB,EAAE,wBAAwB,EAC5C,SAAS,EACT,OAAO,EACP,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,gBAAgB,GAAG,SAAS,CAmE9B;yBAhFe,WAAW"}
|
|
@@ -87,11 +87,9 @@ export function AvatarGroup({
|
|
|
87
87
|
}), [size, overlap, numericSize]);
|
|
88
88
|
|
|
89
89
|
// The keyboard hint and roving tab stop only make sense once the group has
|
|
90
|
-
// interactive children. Detect their presence
|
|
91
|
-
//
|
|
92
|
-
// how the codebase reads direction elsewhere (`getComputedStyle(el).direction`).
|
|
90
|
+
// interactive children. Detect their presence from the rendered DOM after
|
|
91
|
+
// commit. (Arrow-key direction is auto-detected inside useListFocus.)
|
|
93
92
|
const [hasInteractiveItems, setHasInteractiveItems] = useState(false);
|
|
94
|
-
const [isRtl, setIsRtl] = useState(false);
|
|
95
93
|
|
|
96
94
|
// Single tab stop + roving arrow focus over the group's interactive items.
|
|
97
95
|
// `itemSelector` targets the shared `[data-avatar-item]` marker stamped on
|
|
@@ -105,8 +103,7 @@ export function AvatarGroup({
|
|
|
105
103
|
} = useListFocus({
|
|
106
104
|
itemSelector: '[data-avatar-item]',
|
|
107
105
|
orientation: 'horizontal',
|
|
108
|
-
hasRovingTabIndex: true
|
|
109
|
-
isRtl
|
|
106
|
+
hasRovingTabIndex: true
|
|
110
107
|
});
|
|
111
108
|
useIsomorphicLayoutEffect(() => {
|
|
112
109
|
const root = listRef.current;
|
|
@@ -114,7 +111,6 @@ export function AvatarGroup({
|
|
|
114
111
|
return;
|
|
115
112
|
}
|
|
116
113
|
setHasInteractiveItems(root.querySelector('[data-avatar-item]') != null);
|
|
117
|
-
setIsRtl(getComputedStyle(root).direction === 'rtl');
|
|
118
114
|
});
|
|
119
115
|
const hintId = useId();
|
|
120
116
|
const describedBy = [ariaDescribedByFromProps, hasInteractiveItems ? hintId : null].filter(Boolean).join(' ') || undefined;
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* @position Horizontal scroll container with fade-edge overflow indication,
|
|
6
6
|
* optional prev/next buttons on the top layer, scroll-snap, a 1px
|
|
7
7
|
* visual bleed allowance for child selection indicators, and Shift + wheel
|
|
8
|
-
* mapping so mouse users can scroll horizontally.
|
|
8
|
+
* mapping so mouse users can scroll horizontally. Supports optional
|
|
9
|
+
* wrap-around looping and an imperative handle (handleRef) for programmatic
|
|
10
|
+
* scroll control. Exposes APG
|
|
9
11
|
* carousel semantics: the root is a labelled region with
|
|
10
12
|
* aria-roledescription="carousel" and each item wrapper is a group with
|
|
11
13
|
* aria-roledescription="slide" named "Slide N of M".
|
|
@@ -18,8 +20,47 @@
|
|
|
18
20
|
import { type ReactNode } from 'react';
|
|
19
21
|
import type { BaseProps } from '../BaseProps';
|
|
20
22
|
import type { SpacingStep } from '../utils/types';
|
|
23
|
+
/**
|
|
24
|
+
* Imperative control surface for the Carousel, accessed via the `handleRef`
|
|
25
|
+
* prop. Methods drive the same native-scroll machinery as the built-in
|
|
26
|
+
* buttons, so they respect RTL, reduced-motion, and `hasLoop`.
|
|
27
|
+
*/
|
|
28
|
+
export interface CarouselHandle {
|
|
29
|
+
/**
|
|
30
|
+
* Scroll forward by roughly one viewport. With `hasLoop`, wraps to the
|
|
31
|
+
* start once the end is reached.
|
|
32
|
+
*/
|
|
33
|
+
scrollNext(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Scroll backward by roughly one viewport. With `hasLoop`, wraps to the
|
|
36
|
+
* end once the start is reached.
|
|
37
|
+
*/
|
|
38
|
+
scrollPrev(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Scroll the item at the given 0-based index to the start edge. The index
|
|
41
|
+
* is clamped to the item range, and only the carousel scrolls — the page
|
|
42
|
+
* position is left untouched.
|
|
43
|
+
*/
|
|
44
|
+
scrollTo(index: number): void;
|
|
45
|
+
/**
|
|
46
|
+
* Whether there is scrollable content past the trailing edge. With
|
|
47
|
+
* `hasLoop`, returns true whenever the content overflows, since wrapping
|
|
48
|
+
* is always available. Reads live state — safe to call in an event handler.
|
|
49
|
+
*/
|
|
50
|
+
canScrollNext(): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Whether there is scrollable content past the leading edge. With
|
|
53
|
+
* `hasLoop`, returns true whenever the content overflows. Reads live state.
|
|
54
|
+
*/
|
|
55
|
+
canScrollPrev(): boolean;
|
|
56
|
+
}
|
|
21
57
|
export interface CarouselProps extends BaseProps<HTMLDivElement> {
|
|
22
58
|
ref?: React.Ref<HTMLDivElement>;
|
|
59
|
+
/**
|
|
60
|
+
* Imperative handle for programmatic scroll control. Exposes scrollNext,
|
|
61
|
+
* scrollPrev, scrollTo, and the canScrollNext/canScrollPrev queries.
|
|
62
|
+
*/
|
|
63
|
+
handleRef?: React.Ref<CarouselHandle>;
|
|
23
64
|
/** Carousel items — rendered in a horizontal scroll container. */
|
|
24
65
|
children: ReactNode;
|
|
25
66
|
/**
|
|
@@ -39,6 +80,16 @@ export interface CarouselProps extends BaseProps<HTMLDivElement> {
|
|
|
39
80
|
* @default true
|
|
40
81
|
*/
|
|
41
82
|
hasEdgeFade?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Enable wrap-around scrolling. When the content overflows, pressing Next
|
|
85
|
+
* at the end scrolls back to the start, and Prev at the start scrolls to
|
|
86
|
+
* the end — for both the built-in buttons and the imperative handle. The
|
|
87
|
+
* navigation buttons stay visible at both edges instead of hiding, since a
|
|
88
|
+
* scroll is always available. Has no effect when the content fits without
|
|
89
|
+
* overflowing.
|
|
90
|
+
* @default false
|
|
91
|
+
*/
|
|
92
|
+
hasLoop?: boolean;
|
|
42
93
|
/**
|
|
43
94
|
* Enable scroll-snap on items. Each direct child snaps to the start edge.
|
|
44
95
|
* @default false
|
|
@@ -79,7 +130,7 @@ export interface CarouselProps extends BaseProps<HTMLDivElement> {
|
|
|
79
130
|
* </Carousel>
|
|
80
131
|
* ```
|
|
81
132
|
*/
|
|
82
|
-
export declare function Carousel({ ref, children, gap, hasButtons, hasEdgeFade, hasSnap, padding, 'aria-label': ariaLabelFromProps, xstyle, className, style, 'data-testid': testId, ...htmlProps }: CarouselProps): import("react").JSX.Element;
|
|
133
|
+
export declare function Carousel({ ref, handleRef, children, gap, hasButtons, hasEdgeFade, hasLoop, hasSnap, padding, 'aria-label': ariaLabelFromProps, xstyle, className, style, 'data-testid': testId, ...htmlProps }: CarouselProps): import("react").JSX.Element;
|
|
83
134
|
export declare namespace Carousel {
|
|
84
135
|
var displayName: string;
|
|
85
136
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Carousel.d.ts","sourceRoot":"","sources":["../../src/Carousel/Carousel.tsx"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"Carousel.d.ts","sourceRoot":"","sources":["../../src/Carousel/Carousel.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACL,KAAK,SAAS,EAOf,MAAM,OAAO,CAAC;AAef,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAIhD;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,UAAU,IAAI,IAAI,CAAC;IACnB;;;OAGG;IACH,UAAU,IAAI,IAAI,CAAC;IACnB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;;OAIG;IACH,aAAa,IAAI,OAAO,CAAC;IACzB;;;OAGG;IACH,aAAa,IAAI,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,aAAc,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC9D,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACtC,kEAAkE;IAClE,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpC;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA+JD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,SAAS,EACT,QAAQ,EACR,GAAO,EACP,UAAiB,EACjB,WAAkB,EAClB,OAAe,EACf,OAAe,EACf,OAAO,EACP,YAAY,EAAE,kBAAkB,EAChC,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,GAAG,SAAS,EACb,EAAE,aAAa,+BAiRf;yBAjSe,QAAQ"}
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* @position Horizontal scroll container with fade-edge overflow indication,
|
|
10
10
|
* optional prev/next buttons on the top layer, scroll-snap, a 1px
|
|
11
11
|
* visual bleed allowance for child selection indicators, and Shift + wheel
|
|
12
|
-
* mapping so mouse users can scroll horizontally.
|
|
12
|
+
* mapping so mouse users can scroll horizontally. Supports optional
|
|
13
|
+
* wrap-around looping and an imperative handle (handleRef) for programmatic
|
|
14
|
+
* scroll control. Exposes APG
|
|
13
15
|
* carousel semantics: the root is a labelled region with
|
|
14
16
|
* aria-roledescription="carousel" and each item wrapper is a group with
|
|
15
17
|
* aria-roledescription="slide" named "Slide N of M".
|
|
@@ -19,7 +21,7 @@
|
|
|
19
21
|
* - /apps/storybook/stories/Carousel.stories.tsx
|
|
20
22
|
* - /packages/cli/assets/templates/blocks/components/Carousel/ (showcase blocks)
|
|
21
23
|
*/
|
|
22
|
-
import { useRef, useCallback, useEffect, Children, isValidElement } from 'react';
|
|
24
|
+
import { useRef, useCallback, useEffect, useImperativeHandle, Children, isValidElement } from 'react';
|
|
23
25
|
import * as stylex from '@stylexjs/stylex';
|
|
24
26
|
import "../theme/tokens.stylex.js";
|
|
25
27
|
import { spacingVars, colorVars, shadowVars, radiusVars, durationVars, easeVars } from "../theme/tokens.stylex.js";
|
|
@@ -31,6 +33,12 @@ import { isRtlElement } from "../hooks/isRtlElement.js";
|
|
|
31
33
|
import { mergeProps, mergeRefs, rtlStyles } from "../utils/index.js";
|
|
32
34
|
import { themeProps } from "../utils/themeProps.js";
|
|
33
35
|
import { useTranslator } from "../i18n/index.js";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Imperative control surface for the Carousel, accessed via the `handleRef`
|
|
39
|
+
* prop. Methods drive the same native-scroll machinery as the built-in
|
|
40
|
+
* buttons, so they respect RTL, reduced-motion, and `hasLoop`.
|
|
41
|
+
*/
|
|
34
42
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
35
43
|
// =============================================================================
|
|
36
44
|
// Styles
|
|
@@ -203,10 +211,12 @@ const paddingStyles = {
|
|
|
203
211
|
*/
|
|
204
212
|
export function Carousel({
|
|
205
213
|
ref,
|
|
214
|
+
handleRef,
|
|
206
215
|
children,
|
|
207
216
|
gap = 1,
|
|
208
217
|
hasButtons = true,
|
|
209
218
|
hasEdgeFade = true,
|
|
219
|
+
hasLoop = false,
|
|
210
220
|
hasSnap = false,
|
|
211
221
|
padding,
|
|
212
222
|
'aria-label': ariaLabelFromProps,
|
|
@@ -222,7 +232,8 @@ export function Carousel({
|
|
|
222
232
|
const {
|
|
223
233
|
scrollRef,
|
|
224
234
|
overflowStart,
|
|
225
|
-
overflowEnd
|
|
235
|
+
overflowEnd,
|
|
236
|
+
hasOverflow
|
|
226
237
|
} = useScrollOverflow();
|
|
227
238
|
|
|
228
239
|
// Children.toArray drops null/undefined/boolean children and assigns
|
|
@@ -277,22 +288,78 @@ export function Carousel({
|
|
|
277
288
|
if (!el) {
|
|
278
289
|
return;
|
|
279
290
|
}
|
|
280
|
-
const firstChild = el.firstElementChild;
|
|
281
|
-
const itemWidth = firstChild ? firstChild.offsetWidth : 0;
|
|
282
|
-
const amount = el.clientWidth - itemWidth * 0.5;
|
|
283
291
|
// Respect the user's reduced-motion preference — mirrors the CSS
|
|
284
292
|
// scroll-behavior override so button-driven scrolling doesn't animate
|
|
285
293
|
// for users who opted out of motion.
|
|
286
294
|
const prefersReducedMotion = typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
295
|
+
const behavior = prefersReducedMotion ? 'auto' : 'smooth';
|
|
296
|
+
// Under RTL the scroll axis is inverted (start is scrollLeft 0, the end
|
|
297
|
+
// is negative), so flip the physical delta sign.
|
|
298
|
+
const rtlSign = isRtlElement(el) ? -1 : 1;
|
|
299
|
+
|
|
300
|
+
// Wrap-around: when looping over overflowing content, a press that would
|
|
301
|
+
// run past an edge jumps to the opposite edge instead. Overshoot by the
|
|
302
|
+
// full scroll width in the opposite logical direction and let the browser
|
|
303
|
+
// clamp to the far edge — this reuses the same RTL sign convention as a
|
|
304
|
+
// normal scroll, so it stays direction-correct without special-casing.
|
|
305
|
+
if (hasLoop && hasOverflow) {
|
|
306
|
+
const atEnd = direction === 1 && !overflowEnd;
|
|
307
|
+
const atStart = direction === -1 && !overflowStart;
|
|
308
|
+
if (atEnd || atStart) {
|
|
309
|
+
el.scrollBy({
|
|
310
|
+
left: rtlSign * -direction * el.scrollWidth,
|
|
311
|
+
behavior
|
|
312
|
+
});
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const firstChild = el.firstElementChild;
|
|
317
|
+
const itemWidth = firstChild ? firstChild.offsetWidth : 0;
|
|
318
|
+
const amount = el.clientWidth - itemWidth * 0.5;
|
|
287
319
|
el.scrollBy({
|
|
288
320
|
// `direction` is the logical intent (-1 = toward content start, +1 =
|
|
289
|
-
// toward content end).
|
|
290
|
-
|
|
291
|
-
|
|
321
|
+
// toward content end).
|
|
322
|
+
left: rtlSign * direction * Math.max(amount, itemWidth),
|
|
323
|
+
behavior
|
|
324
|
+
});
|
|
325
|
+
}, [hasLoop, hasOverflow, overflowStart, overflowEnd]);
|
|
326
|
+
const scrollToIndex = useCallback(index => {
|
|
327
|
+
const el = scrollElRef.current;
|
|
328
|
+
const items = el?.children;
|
|
329
|
+
if (!el || !items || items.length === 0) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const clamped = Math.max(0, Math.min(index, items.length - 1));
|
|
333
|
+
const target = items[clamped];
|
|
334
|
+
const prefersReducedMotion = typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
335
|
+
// Bring the item to the start edge by scrolling the container by the
|
|
336
|
+
// measured gap between the item and the container edge. Using scrollBy
|
|
337
|
+
// (not scrollIntoView) keeps the scroll contained to the carousel and
|
|
338
|
+
// never moves ancestors or the page. In RTL the start edge is the right
|
|
339
|
+
// edge, so align the trailing edges instead of the leading ones.
|
|
340
|
+
const containerRect = el.getBoundingClientRect();
|
|
341
|
+
const itemRect = target.getBoundingClientRect();
|
|
342
|
+
const delta = isRtlElement(el) ? itemRect.right - containerRect.right : itemRect.left - containerRect.left;
|
|
343
|
+
el.scrollBy({
|
|
344
|
+
left: delta,
|
|
292
345
|
behavior: prefersReducedMotion ? 'auto' : 'smooth'
|
|
293
346
|
});
|
|
294
347
|
}, []);
|
|
295
|
-
|
|
348
|
+
useImperativeHandle(handleRef, () => ({
|
|
349
|
+
scrollNext: () => scrollBy(1),
|
|
350
|
+
scrollPrev: () => scrollBy(-1),
|
|
351
|
+
scrollTo: index => scrollToIndex(index),
|
|
352
|
+
// With loop, either direction is reachable whenever the content
|
|
353
|
+
// overflows; otherwise reflect the live per-edge overflow state.
|
|
354
|
+
canScrollNext: () => hasLoop ? hasOverflow : overflowEnd,
|
|
355
|
+
canScrollPrev: () => hasLoop ? hasOverflow : overflowStart
|
|
356
|
+
}), [scrollBy, scrollToIndex, hasLoop, hasOverflow, overflowStart, overflowEnd]);
|
|
357
|
+
|
|
358
|
+
// With loop, the buttons stay reachable at both edges as long as there's
|
|
359
|
+
// something to scroll; without loop they follow the per-edge overflow state.
|
|
360
|
+
const canScrollStart = hasLoop && hasOverflow ? true : overflowStart;
|
|
361
|
+
const canScrollEnd = hasLoop && hasOverflow ? true : overflowEnd;
|
|
362
|
+
const fadeStyle = hasEdgeFade ? hasLoop && hasOverflow || overflowStart && overflowEnd ? styles.fadeBoth : overflowStart ? styles.fadeStart : overflowEnd ? styles.fadeEnd : null : null;
|
|
296
363
|
|
|
297
364
|
// Self-authored position styles (positioning: 'custom' below): a cover
|
|
298
365
|
// centered on the anchor, sized to it — direction-neutral by construction.
|
|
@@ -341,7 +408,7 @@ export function Carousel({
|
|
|
341
408
|
1: {
|
|
342
409
|
className: "x78zum5 x6s0dn4 xl56j7k x1prclbq xjspbzw x14hfi27 x19991ni xuedmi6 xlr8y92 xuuh30 x1nyx83j xg01cxk x47corl"
|
|
343
410
|
}
|
|
344
|
-
}[!!!
|
|
411
|
+
}[!!!canScrollStart << 0],
|
|
345
412
|
children: /*#__PURE__*/_jsx(Button, {
|
|
346
413
|
icon: /*#__PURE__*/_jsx("span", {
|
|
347
414
|
...stylex.props(rtlStyles.mirror),
|
|
@@ -359,7 +426,7 @@ export function Carousel({
|
|
|
359
426
|
// tab order and a11y tree while it's visually hidden, so
|
|
360
427
|
// keyboard users don't land on an invisible control.
|
|
361
428
|
,
|
|
362
|
-
isDisabled: !
|
|
429
|
+
isDisabled: !canScrollStart,
|
|
363
430
|
onClick: () => scrollBy(-1),
|
|
364
431
|
xstyle: styles.buttonRadiusOverride
|
|
365
432
|
})
|
|
@@ -371,7 +438,7 @@ export function Carousel({
|
|
|
371
438
|
1: {
|
|
372
439
|
className: "x78zum5 x6s0dn4 xl56j7k x1prclbq xjspbzw x14hfi27 x19991ni xuedmi6 xlr8y92 xitnhlw x36dvrd xg01cxk x47corl"
|
|
373
440
|
}
|
|
374
|
-
}[!!!
|
|
441
|
+
}[!!!canScrollEnd << 0],
|
|
375
442
|
children: /*#__PURE__*/_jsx(Button, {
|
|
376
443
|
icon: /*#__PURE__*/_jsx("span", {
|
|
377
444
|
...stylex.props(rtlStyles.mirror),
|
|
@@ -387,7 +454,7 @@ export function Carousel({
|
|
|
387
454
|
// See "Scroll left" — disabled while visually hidden so the
|
|
388
455
|
// button stays mounted but out of the tab order / a11y tree.
|
|
389
456
|
,
|
|
390
|
-
isDisabled: !
|
|
457
|
+
isDisabled: !canScrollEnd,
|
|
391
458
|
onClick: () => scrollBy(1),
|
|
392
459
|
xstyle: styles.buttonRadiusOverride
|
|
393
460
|
})
|
package/dist/Carousel/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Carousel/index.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AAEH,OAAO,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAC;AACpC,YAAY,EAAC,aAAa,EAAC,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Carousel/index.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AAEH,OAAO,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAC;AACpC,YAAY,EAAC,aAAa,EAAE,cAAc,EAAC,MAAM,YAAY,CAAC"}
|
|
@@ -67,7 +67,7 @@ export interface CommandPaletteInputProps extends Omit<BaseProps<HTMLInputElemen
|
|
|
67
67
|
* </CommandPalette>
|
|
68
68
|
* ```
|
|
69
69
|
*/
|
|
70
|
-
export declare function CommandPaletteInput({ value: controlledValue, onValueChange, placeholder: placeholderFromProps, label, hasAutoFocus, endContent, onChange, onKeyDown, ref, xstyle, ...props }: CommandPaletteInputProps): import("react").JSX.Element;
|
|
70
|
+
export declare function CommandPaletteInput({ value: controlledValue, onValueChange, placeholder: placeholderFromProps, label, hasAutoFocus, endContent, onChange, onKeyDown, ref, xstyle, className, style, ...props }: CommandPaletteInputProps): import("react").JSX.Element;
|
|
71
71
|
export declare namespace CommandPaletteInput {
|
|
72
72
|
var displayName: string;
|
|
73
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandPaletteInput.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPaletteInput.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAarE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA2D5C,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CACpD,SAAS,CAAC,gBAAgB,CAAC,EAC3B,UAAU,CACX;IACC,iEAAiE;IACjE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAElC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IAEvB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,KAAK,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EAAE,eAAe,EACtB,aAAa,EACb,WAAW,EAAE,oBAAoB,EACjC,KAAK,EACL,YAAmB,EACnB,UAAU,EACV,QAAQ,EACR,SAAS,EACT,GAAG,EACH,MAAM,EACN,GAAG,KAAK,EACT,EAAE,wBAAwB,+
|
|
1
|
+
{"version":3,"file":"CommandPaletteInput.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPaletteInput.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAarE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA2D5C,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CACpD,SAAS,CAAC,gBAAgB,CAAC,EAC3B,UAAU,CACX;IACC,iEAAiE;IACjE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAElC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IAEvB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,KAAK,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EAAE,eAAe,EACtB,aAAa,EACb,WAAW,EAAE,oBAAoB,EACjC,KAAK,EACL,YAAmB,EACnB,UAAU,EACV,QAAQ,EACR,SAAS,EACT,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,wBAAwB,+BAuF1B;yBArGe,mBAAmB"}
|
|
@@ -65,6 +65,8 @@ export function CommandPaletteInput({
|
|
|
65
65
|
onKeyDown,
|
|
66
66
|
ref,
|
|
67
67
|
xstyle,
|
|
68
|
+
className,
|
|
69
|
+
style,
|
|
68
70
|
...props
|
|
69
71
|
}) {
|
|
70
72
|
const t = useTranslator();
|
|
@@ -100,7 +102,7 @@ export function CommandPaletteInput({
|
|
|
100
102
|
ctx?.onKeyDown(e);
|
|
101
103
|
}, [ctx, onKeyDown]);
|
|
102
104
|
return /*#__PURE__*/_jsxs("div", {
|
|
103
|
-
...mergeProps(themeProps('command-palette-input'), stylex.props(styles.wrapper, xstyle)),
|
|
105
|
+
...mergeProps(themeProps('command-palette-input'), stylex.props(styles.wrapper, xstyle), className, style),
|
|
104
106
|
children: [/*#__PURE__*/_jsx("span", {
|
|
105
107
|
...{
|
|
106
108
|
className: "x78zum5 x6s0dn4 x2lah0s xv1l7n4"
|
|
@@ -96,7 +96,7 @@ export type ContextMenuProps = ContextMenuDataProps | ContextMenuCompoundProps;
|
|
|
96
96
|
* </ContextMenu>
|
|
97
97
|
* ```
|
|
98
98
|
*/
|
|
99
|
-
export declare function ContextMenu({ children, menuWidth, size, label: labelFromProps, isDisabled, onOpenChange, ref, className, style, xstyle, triggerXstyle, 'data-testid': testId, ...
|
|
99
|
+
export declare function ContextMenu({ children, menuWidth, size, label: labelFromProps, isDisabled, onOpenChange, ref, className, style, xstyle, triggerXstyle, 'data-testid': testId, ...rest }: ContextMenuProps): React.JSX.Element;
|
|
100
100
|
export declare namespace ContextMenu {
|
|
101
101
|
var displayName: string;
|
|
102
102
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextMenu.d.ts","sourceRoot":"","sources":["../../src/ContextMenu/ContextMenu.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAON,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AA0BrC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAGjD,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,8BAA8B,CAAC;AAqDtC,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AAEvD,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAErD,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAMnD,UAAU,oBAAqB,SAAQ,SAAS;IAC9C,oDAAoD;IACpD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;IAC9C,+DAA+D;IAC/D,QAAQ,EAAE,SAAS,CAAC;IACpB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,wCAAwC;IACxC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,oBAAqB,SAAQ,oBAAoB;IACzD,gDAAgD;IAChD,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,SAAS,CAAC;CACzB;AAED,UAAU,wBAAyB,SAAQ,oBAAoB;IAC7D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+CAA+C;IAC/C,WAAW,EAAE,SAAS,CAAC;CACxB;AAED,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GAAG,wBAAwB,CAAC;AAM/E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,IAAW,EACX,KAAK,EAAE,cAAc,EACrB,UAAkB,EAClB,YAAY,EACZ,GAAG,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,aAAa,EACb,aAAa,EAAE,MAAM,EACrB,GAAG,
|
|
1
|
+
{"version":3,"file":"ContextMenu.d.ts","sourceRoot":"","sources":["../../src/ContextMenu/ContextMenu.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAON,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AA0BrC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAGjD,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,8BAA8B,CAAC;AAqDtC,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AAEvD,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAErD,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAMnD,UAAU,oBAAqB,SAAQ,SAAS;IAC9C,oDAAoD;IACpD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;IAC9C,+DAA+D;IAC/D,QAAQ,EAAE,SAAS,CAAC;IACpB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,wCAAwC;IACxC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,oBAAqB,SAAQ,oBAAoB;IACzD,gDAAgD;IAChD,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,SAAS,CAAC;CACzB;AAED,UAAU,wBAAyB,SAAQ,oBAAoB;IAC7D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+CAA+C;IAC/C,WAAW,EAAE,SAAS,CAAC;CACxB;AAED,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GAAG,wBAAwB,CAAC;AAM/E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,IAAW,EACX,KAAK,EAAE,cAAc,EACrB,UAAkB,EAClB,YAAY,EACZ,GAAG,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,aAAa,EACb,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,gBAAgB,qBAwSlB;yBAtTe,WAAW"}
|
|
@@ -131,12 +131,20 @@ export function ContextMenu({
|
|
|
131
131
|
xstyle,
|
|
132
132
|
triggerXstyle,
|
|
133
133
|
'data-testid': testId,
|
|
134
|
-
...
|
|
134
|
+
...rest
|
|
135
135
|
}) {
|
|
136
136
|
const t = useTranslator();
|
|
137
137
|
const label = labelFromProps ?? t('@astryx.contextMenu.label');
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
// Separate content props (union discriminant) from DOM pass-through attrs.
|
|
139
|
+
// The union means exactly one of items/menuContent exists in rest; destructure
|
|
140
|
+
// both so triggerProps contains only DOM-safe attributes.
|
|
141
|
+
const {
|
|
142
|
+
items: itemsProp,
|
|
143
|
+
menuContent: menuContentProp,
|
|
144
|
+
...triggerProps
|
|
145
|
+
} = rest;
|
|
146
|
+
const items = itemsProp ?? [];
|
|
147
|
+
const menuContent = menuContentProp;
|
|
140
148
|
const menuId = useId();
|
|
141
149
|
// Cursor point in the trigger's local coordinate space (offset from the
|
|
142
150
|
// trigger's top-left, in-flow). Stored here and written to the zero-size
|
|
@@ -325,12 +333,13 @@ export function ContextMenu({
|
|
|
325
333
|
closeMenu,
|
|
326
334
|
menuSize: size
|
|
327
335
|
}), [closeMenu, size]);
|
|
328
|
-
const resolvedMenuContent =
|
|
336
|
+
const resolvedMenuContent = itemsProp !== undefined ? renderDropdownItems(items) : menuContent;
|
|
329
337
|
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
330
338
|
children: [/*#__PURE__*/_jsxs("div", {
|
|
331
339
|
ref: mergeRefs(ref, triggerRef),
|
|
332
340
|
onContextMenu: handleContextMenu,
|
|
333
341
|
...longPressHandlers,
|
|
342
|
+
...triggerProps,
|
|
334
343
|
"data-testid": testId,
|
|
335
344
|
...stylex.props(styles.trigger, ...(triggerXstyle ? Array.isArray(triggerXstyle) ? triggerXstyle : [triggerXstyle] : [])),
|
|
336
345
|
children: [children, /*#__PURE__*/_jsx("span", {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import React, { type ReactNode } from 'react';
|
|
21
21
|
import { type ButtonProps } from '../Button';
|
|
22
22
|
import type { IconType } from '../Icon';
|
|
23
|
-
import type { LayerPlacement } from '../Layer/useLayer';
|
|
23
|
+
import type { LayerAlignment, LayerPlacement } from '../Layer/useLayer';
|
|
24
24
|
import type { BaseProps } from '../BaseProps';
|
|
25
25
|
export interface DropdownMenuItemData {
|
|
26
26
|
label: string;
|
|
@@ -57,6 +57,12 @@ interface DropdownMenuBaseProps extends BaseProps {
|
|
|
57
57
|
* @default 'below'
|
|
58
58
|
*/
|
|
59
59
|
placement?: LayerPlacement;
|
|
60
|
+
/**
|
|
61
|
+
* Alignment along the placement axis.
|
|
62
|
+
* Uses the same alignment values as other Astryx layer-based components.
|
|
63
|
+
* @default 'start'
|
|
64
|
+
*/
|
|
65
|
+
alignment?: LayerAlignment;
|
|
60
66
|
'data-testid'?: string;
|
|
61
67
|
}
|
|
62
68
|
interface DropdownMenuDataProps extends DropdownMenuBaseProps {
|
|
@@ -68,7 +74,7 @@ interface DropdownMenuCompoundProps extends DropdownMenuBaseProps {
|
|
|
68
74
|
children: ReactNode;
|
|
69
75
|
}
|
|
70
76
|
export type DropdownMenuProps = DropdownMenuDataProps | DropdownMenuCompoundProps;
|
|
71
|
-
export declare function DropdownMenu({ button: buttonFromProps, isMenuOpen: controlledIsOpen, onOpenChange, menuWidth, onClick, hasChevron, placement, className, style, xstyle, 'data-testid': testId, ...props }: DropdownMenuProps): React.JSX.Element;
|
|
77
|
+
export declare function DropdownMenu({ button: buttonFromProps, isMenuOpen: controlledIsOpen, onOpenChange, menuWidth, onClick, hasChevron, placement, alignment, className, style, xstyle, 'data-testid': testId, ...props }: DropdownMenuProps): React.JSX.Element;
|
|
72
78
|
export declare namespace DropdownMenu {
|
|
73
79
|
var displayName: string;
|
|
74
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenu.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,EAAS,KAAK,WAAW,EAAC,MAAM,WAAW,CAAC;AAEnD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,SAAS,CAAC;AAetC,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenu.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,EAAS,KAAK,WAAW,EAAC,MAAM,WAAW,CAAC;AAEnD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,SAAS,CAAC;AAetC,OAAO,KAAK,EAAC,cAAc,EAAE,cAAc,EAAC,MAAM,mBAAmB,CAAC;AAQtE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAyC5C,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B;;;;OAIG;IACH,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,oBAAoB,EAAE,CAAC;CAC/B;AAED,MAAM,MAAM,kBAAkB,GAC5B,oBAAoB,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;AAMnE,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;AAEnE,UAAU,qBAAsB,SAAQ,SAAS;IAC/C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B;;;;OAIG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,qBAAsB,SAAQ,qBAAqB;IAC3D,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,UAAU,yBAA0B,SAAQ,qBAAqB;IAC/D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,MAAM,MAAM,iBAAiB,GAC3B,qBAAqB,GAAG,yBAAyB,CAAC;AA+BpD,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EAAE,eAAe,EACvB,UAAU,EAAE,gBAAgB,EAC5B,YAAY,EACZ,SAAS,EACT,OAAO,EACP,UAAiB,EACjB,SAAmB,EACnB,SAAmB,EACnB,SAAS,EACT,KAAK,EACL,MAAM,EACN,aAAa,EAAE,MAAM,EACrB,GAAG,KAAK,EACT,EAAE,iBAAiB,qBAwRnB;yBAtSe,YAAY"}
|
|
@@ -122,6 +122,7 @@ export function DropdownMenu({
|
|
|
122
122
|
onClick,
|
|
123
123
|
hasChevron = true,
|
|
124
124
|
placement = 'below',
|
|
125
|
+
alignment = 'start',
|
|
125
126
|
className,
|
|
126
127
|
style,
|
|
127
128
|
xstyle,
|
|
@@ -159,27 +160,21 @@ export function DropdownMenu({
|
|
|
159
160
|
// Close menu + return focus to trigger
|
|
160
161
|
const handleLayerHide = useCallback(() => {
|
|
161
162
|
lastHideTimeRef.current = Date.now();
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
} else {
|
|
163
|
+
onOpenChange?.(false);
|
|
164
|
+
if (!isControlled) {
|
|
165
165
|
setInternalIsOpen(false);
|
|
166
166
|
}
|
|
167
167
|
buttonRef.current?.focus();
|
|
168
168
|
}, [isControlled, onOpenChange]);
|
|
169
169
|
|
|
170
|
-
//
|
|
170
|
+
// Defer item focus until the layer has committed open, so focus restore
|
|
171
|
+
// captures the trigger instead of the first menu item.
|
|
171
172
|
const shouldFocusOnOpenRef = useRef(false);
|
|
172
173
|
const handleLayerShow = useCallback(() => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
} else {
|
|
174
|
+
onOpenChange?.(true);
|
|
175
|
+
if (!isControlled) {
|
|
176
176
|
setInternalIsOpen(true);
|
|
177
177
|
}
|
|
178
|
-
if (shouldFocusOnOpenRef.current) {
|
|
179
|
-
shouldFocusOnOpenRef.current = false;
|
|
180
|
-
// focusFirst is called via openAndFocus below — defer to rAF
|
|
181
|
-
// so the popover content is rendered before we query for items
|
|
182
|
-
}
|
|
183
178
|
}, [isControlled, onOpenChange]);
|
|
184
179
|
const popover = usePopover({
|
|
185
180
|
onHide: handleLayerHide,
|
|
@@ -222,17 +217,26 @@ export function DropdownMenu({
|
|
|
222
217
|
getCurrentIndex: () => getMenuItems().findIndex(el => el === document.activeElement || el.contains(document.activeElement))
|
|
223
218
|
});
|
|
224
219
|
|
|
225
|
-
// Sync controlled open state → popover
|
|
220
|
+
// Sync controlled open state → popover.
|
|
226
221
|
useEffect(() => {
|
|
227
222
|
if (isControlled) {
|
|
228
223
|
if (controlledIsOpen && !popover.isOpen) {
|
|
224
|
+
shouldFocusOnOpenRef.current = true;
|
|
229
225
|
popover.show();
|
|
230
|
-
requestAnimationFrame(() => focusFirst());
|
|
231
226
|
} else if (!controlledIsOpen && popover.isOpen) {
|
|
232
227
|
popover.hide();
|
|
233
228
|
}
|
|
234
229
|
}
|
|
235
|
-
}, [controlledIsOpen, isControlled, popover
|
|
230
|
+
}, [controlledIsOpen, isControlled, popover]);
|
|
231
|
+
|
|
232
|
+
// Move focus into the menu only after the layer has committed open.
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (!popover.isOpen || !shouldFocusOnOpenRef.current) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
shouldFocusOnOpenRef.current = false;
|
|
238
|
+
requestAnimationFrame(() => focusFirst());
|
|
239
|
+
}, [popover.isOpen, focusFirst]);
|
|
236
240
|
|
|
237
241
|
// Extend useListFocus with Enter/Space activation + typeahead
|
|
238
242
|
const listKeyDown = useCallback(e => {
|
|
@@ -266,9 +270,9 @@ export function DropdownMenu({
|
|
|
266
270
|
listNavKeyDown(e);
|
|
267
271
|
}, [listNavKeyDown, closeMenu, typeahead, ownsEvent]);
|
|
268
272
|
const openAndFocus = useCallback(() => {
|
|
273
|
+
shouldFocusOnOpenRef.current = true;
|
|
269
274
|
popover.show();
|
|
270
|
-
|
|
271
|
-
}, [popover, focusFirst]);
|
|
275
|
+
}, [popover]);
|
|
272
276
|
const handleButtonClick = useCallback(() => {
|
|
273
277
|
// If the menu was just closed by light dismiss (e.g. iOS Safari fires
|
|
274
278
|
// pointerdown → hide before the trigger's click), the click would
|
|
@@ -356,7 +360,7 @@ export function DropdownMenu({
|
|
|
356
360
|
})
|
|
357
361
|
}), {
|
|
358
362
|
placement,
|
|
359
|
-
alignment
|
|
363
|
+
alignment,
|
|
360
364
|
xstyle: [popoverXstyle, popoverGapStyle, layerAnimations[placement]]
|
|
361
365
|
})]
|
|
362
366
|
});
|