aeon-ui-engine 1.4.0 → 1.5.1
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 +27 -0
- package/CONSUMER.md +67 -27
- package/INTEGRATION.md +1 -1
- package/README.md +17 -8
- package/package.json +4 -1
- package/packages/cli/dist/bin.js +29 -3
- package/packages/cli/dist/{chunk-5L47SLNV.js → chunk-JMNRJCWF.js} +63 -9
- package/packages/cli/dist/index.d.ts +15 -1
- package/packages/cli/dist/index.js +3 -1
- package/packages/cli/src/bin.ts +30 -2
- package/packages/cli/src/index.ts +1 -0
- package/packages/cli/src/init.ts +10 -4
- package/packages/cli/src/link.ts +61 -0
- package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/primitives/dist/{chunk-PB3LFHXU.js → chunk-VUPWYBGU.js} +14 -0
- package/packages/primitives/dist/index.js +1 -1
- package/packages/primitives/dist/machines/range-slider.d.ts +3 -3
- package/packages/primitives/dist/machines/slider.d.ts +22 -0
- package/packages/primitives/dist/machines/slider.js +1 -1
- package/packages/primitives/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/primitives/src/machines/slider.ts +17 -0
- package/packages/react/dist/index.d.ts +15 -1
- package/packages/react/dist/index.js +89 -21
- package/packages/react/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/react/src/floating/FloatingPositioner.tsx +8 -0
- package/packages/react/src/hooks/use-anchor-position.ts +88 -17
- package/packages/react/src/menu/index.tsx +37 -3
- package/packages/react/src/popover/index.tsx +7 -1
- package/packages/react/src/slider/index.tsx +54 -14
- package/packages/react/src/tooltip/index.tsx +7 -1
- package/packages/schemas/prompts/cursorrules.md +3 -1
- package/packages/schemas/prompts/statecharts.md +1 -1
- package/packages/schemas/src/prompts.ts +3 -1
- package/scripts/link-scoped-packages.mjs +206 -0
- package/scripts/vite-aliases.mjs +6 -8
|
@@ -83,14 +83,14 @@ declare const rangeSliderMachine: xstate.StateMachine<RangeSliderContext, {
|
|
|
83
83
|
type: "END";
|
|
84
84
|
thumb: 0 | 1;
|
|
85
85
|
}, {}, never, {
|
|
86
|
-
type: "setValues";
|
|
87
|
-
params: xstate.NonReducibleUnknown;
|
|
88
|
-
} | {
|
|
89
86
|
type: "setBounds";
|
|
90
87
|
params: xstate.NonReducibleUnknown;
|
|
91
88
|
} | {
|
|
92
89
|
type: "setDisabled";
|
|
93
90
|
params: xstate.NonReducibleUnknown;
|
|
91
|
+
} | {
|
|
92
|
+
type: "setValues";
|
|
93
|
+
params: xstate.NonReducibleUnknown;
|
|
94
94
|
} | {
|
|
95
95
|
type: "pointerDown";
|
|
96
96
|
params: xstate.NonReducibleUnknown;
|
|
@@ -21,6 +21,14 @@ type SliderEvent = {
|
|
|
21
21
|
type: 'HOME';
|
|
22
22
|
} | {
|
|
23
23
|
type: 'END';
|
|
24
|
+
} | {
|
|
25
|
+
type: 'SET_BOUNDS';
|
|
26
|
+
min: number;
|
|
27
|
+
max: number;
|
|
28
|
+
step: number;
|
|
29
|
+
} | {
|
|
30
|
+
type: 'SET_DISABLED';
|
|
31
|
+
disabled: boolean;
|
|
24
32
|
};
|
|
25
33
|
declare function clamp(value: number, min: number, max: number): number;
|
|
26
34
|
declare function snap(value: number, min: number, max: number, step: number): number;
|
|
@@ -39,6 +47,14 @@ declare const sliderMachine: xstate.StateMachine<SliderContext, {
|
|
|
39
47
|
type: "HOME";
|
|
40
48
|
} | {
|
|
41
49
|
type: "END";
|
|
50
|
+
} | {
|
|
51
|
+
type: "SET_BOUNDS";
|
|
52
|
+
min: number;
|
|
53
|
+
max: number;
|
|
54
|
+
step: number;
|
|
55
|
+
} | {
|
|
56
|
+
type: "SET_DISABLED";
|
|
57
|
+
disabled: boolean;
|
|
42
58
|
}, {}, never, {
|
|
43
59
|
type: "setValue";
|
|
44
60
|
params: xstate.NonReducibleUnknown;
|
|
@@ -51,6 +67,12 @@ declare const sliderMachine: xstate.StateMachine<SliderContext, {
|
|
|
51
67
|
} | {
|
|
52
68
|
type: "end";
|
|
53
69
|
params: xstate.NonReducibleUnknown;
|
|
70
|
+
} | {
|
|
71
|
+
type: "setBounds";
|
|
72
|
+
params: xstate.NonReducibleUnknown;
|
|
73
|
+
} | {
|
|
74
|
+
type: "setDisabled";
|
|
75
|
+
params: xstate.NonReducibleUnknown;
|
|
54
76
|
}, {
|
|
55
77
|
type: "notDisabled";
|
|
56
78
|
params: unknown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"3.2.4","results":[[":src/machines/dialog.test.ts",{"duration":5.771292000000017,"failed":false}],[":src/machines/select.test.ts",{"duration":6.394416000000035,"failed":false}],[":src/__tests__/dialog.test.ts",{"duration":3.686000000000007,"failed":false}],[":src/__tests__/select.test.ts",{"duration":2.8540000000000134,"failed":false}],[":src/__tests__/floating-surface.test.ts",{"duration":
|
|
1
|
+
{"version":"3.2.4","results":[[":src/machines/dialog.test.ts",{"duration":5.771292000000017,"failed":false}],[":src/machines/select.test.ts",{"duration":6.394416000000035,"failed":false}],[":src/__tests__/dialog.test.ts",{"duration":3.686000000000007,"failed":false}],[":src/__tests__/select.test.ts",{"duration":2.8540000000000134,"failed":false}],[":src/__tests__/floating-surface.test.ts",{"duration":13.601374999999962,"failed":false}],[":src/__tests__/app-nav.test.ts",{"duration":9.962167000000022,"failed":false}],[":src/__tests__/file-upload.test.ts",{"duration":13.053917000000013,"failed":false}],[":src/__tests__/range-slider.test.ts",{"duration":13.141374999999982,"failed":false}]]}
|
|
@@ -15,6 +15,8 @@ export type SliderEvent =
|
|
|
15
15
|
| { type: 'POINTER_UP' }
|
|
16
16
|
| { type: 'HOME' }
|
|
17
17
|
| { type: 'END' }
|
|
18
|
+
| { type: 'SET_BOUNDS'; min: number; max: number; step: number }
|
|
19
|
+
| { type: 'SET_DISABLED'; disabled: boolean }
|
|
18
20
|
|
|
19
21
|
function clamp(value: number, min: number, max: number) {
|
|
20
22
|
return Math.min(max, Math.max(min, value))
|
|
@@ -55,6 +57,15 @@ export const sliderMachine = setup({
|
|
|
55
57
|
end: assign({
|
|
56
58
|
value: ({ context }) => context.max,
|
|
57
59
|
}),
|
|
60
|
+
setBounds: assign(({ context, event }) => {
|
|
61
|
+
if (event.type !== 'SET_BOUNDS') return context
|
|
62
|
+
const { min, max, step } = event
|
|
63
|
+
return { ...context, min, max, step, value: snap(context.value, min, max, step) }
|
|
64
|
+
}),
|
|
65
|
+
setDisabled: assign({
|
|
66
|
+
disabled: ({ context, event }) =>
|
|
67
|
+
event.type === 'SET_DISABLED' ? event.disabled : context.disabled,
|
|
68
|
+
}),
|
|
58
69
|
},
|
|
59
70
|
}).createMachine({
|
|
60
71
|
id: 'slider',
|
|
@@ -66,6 +77,12 @@ export const sliderMachine = setup({
|
|
|
66
77
|
disabled: input.disabled ?? false,
|
|
67
78
|
}),
|
|
68
79
|
initial: 'interaction',
|
|
80
|
+
// Bounds and disabled follow the host regardless of interaction state, so a
|
|
81
|
+
// range that widens (audio duration arriving) stops clamping to stale limits.
|
|
82
|
+
on: {
|
|
83
|
+
SET_BOUNDS: { actions: 'setBounds' },
|
|
84
|
+
SET_DISABLED: { actions: 'setDisabled' },
|
|
85
|
+
},
|
|
69
86
|
states: {
|
|
70
87
|
interaction: {
|
|
71
88
|
initial: 'idle',
|
|
@@ -519,7 +519,9 @@ declare const Combobox: {
|
|
|
519
519
|
Empty: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>;
|
|
520
520
|
};
|
|
521
521
|
|
|
522
|
-
type
|
|
522
|
+
type AnchorSide = 'top' | 'bottom';
|
|
523
|
+
type AnchorAlign = 'start' | 'end' | 'center';
|
|
524
|
+
type AnchorPlacement = `${AnchorSide}-${AnchorAlign}`;
|
|
523
525
|
|
|
524
526
|
interface PopoverRootProps {
|
|
525
527
|
open?: boolean;
|
|
@@ -532,6 +534,10 @@ interface PopoverPositionerProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
532
534
|
/** Mount panel in document.body (default true). */
|
|
533
535
|
portalled?: boolean;
|
|
534
536
|
placement?: AnchorPlacement;
|
|
537
|
+
/** Gap between trigger and panel (default 4). */
|
|
538
|
+
gutter?: number;
|
|
539
|
+
/** Stack level for the portalled surface (default 50). */
|
|
540
|
+
zIndex?: number;
|
|
535
541
|
children?: ReactNode;
|
|
536
542
|
}
|
|
537
543
|
declare const Popover: {
|
|
@@ -554,6 +560,10 @@ interface MenuPositionerProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
554
560
|
/** Mount menu in document.body (default true). */
|
|
555
561
|
portalled?: boolean;
|
|
556
562
|
placement?: AnchorPlacement;
|
|
563
|
+
/** Gap between trigger and menu (default 4). */
|
|
564
|
+
gutter?: number;
|
|
565
|
+
/** Stack level for the portalled surface (default 50). */
|
|
566
|
+
zIndex?: number;
|
|
557
567
|
children?: ReactNode;
|
|
558
568
|
}
|
|
559
569
|
interface MenuItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
@@ -588,6 +598,10 @@ interface TooltipPositionerProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
588
598
|
/** Mount tooltip in document.body (default true). */
|
|
589
599
|
portalled?: boolean;
|
|
590
600
|
placement?: AnchorPlacement;
|
|
601
|
+
/** Gap between trigger and tip (default 4). */
|
|
602
|
+
gutter?: number;
|
|
603
|
+
/** Stack level for the portalled surface (default 50). */
|
|
604
|
+
zIndex?: number;
|
|
591
605
|
children?: ReactNode;
|
|
592
606
|
}
|
|
593
607
|
declare const Tooltip: {
|
|
@@ -2628,20 +2628,35 @@ import { forwardRef as forwardRef23 } from "react";
|
|
|
2628
2628
|
|
|
2629
2629
|
// src/hooks/use-anchor-position.ts
|
|
2630
2630
|
import { useEffect as useEffect13 } from "react";
|
|
2631
|
+
var DEFAULT_GUTTER = 4;
|
|
2632
|
+
var DEFAULT_Z_INDEX = 50;
|
|
2633
|
+
var DEFAULT_PADDING = 8;
|
|
2634
|
+
function splitPlacement(placement) {
|
|
2635
|
+
const [side, align] = placement.split("-");
|
|
2636
|
+
return [side, align];
|
|
2637
|
+
}
|
|
2631
2638
|
function useAnchorPosition(anchorRef, floatingRef, active, placement = "bottom-start", options = {}) {
|
|
2632
|
-
const {
|
|
2639
|
+
const {
|
|
2640
|
+
matchWidth = false,
|
|
2641
|
+
gutter = DEFAULT_GUTTER,
|
|
2642
|
+
zIndex = DEFAULT_Z_INDEX,
|
|
2643
|
+
padding = DEFAULT_PADDING
|
|
2644
|
+
} = options;
|
|
2633
2645
|
useEffect13(() => {
|
|
2634
2646
|
if (!active || !anchorRef.current || !floatingRef.current) return;
|
|
2647
|
+
let frame = 0;
|
|
2635
2648
|
const update = () => {
|
|
2636
2649
|
const anchor = anchorRef.current;
|
|
2637
2650
|
const floating = floatingRef.current;
|
|
2638
2651
|
if (!anchor || !floating) return;
|
|
2652
|
+
const [preferredSide, align] = splitPlacement(placement);
|
|
2639
2653
|
const rect = anchor.getBoundingClientRect();
|
|
2654
|
+
const viewportWidth = window.innerWidth;
|
|
2655
|
+
const viewportHeight = window.innerHeight;
|
|
2640
2656
|
floating.style.position = "fixed";
|
|
2641
|
-
floating.style.left = `${rect.left}px`;
|
|
2642
2657
|
floating.style.bottom = "";
|
|
2643
2658
|
floating.style.right = "";
|
|
2644
|
-
floating.style.zIndex =
|
|
2659
|
+
floating.style.zIndex = String(zIndex);
|
|
2645
2660
|
if (matchWidth) {
|
|
2646
2661
|
floating.style.width = `${rect.width}px`;
|
|
2647
2662
|
floating.style.minWidth = `${rect.width}px`;
|
|
@@ -2649,22 +2664,43 @@ function useAnchorPosition(anchorRef, floatingRef, active, placement = "bottom-s
|
|
|
2649
2664
|
floating.style.width = "";
|
|
2650
2665
|
floating.style.minWidth = "";
|
|
2651
2666
|
}
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2667
|
+
const width = floating.offsetWidth || floating.getBoundingClientRect().width;
|
|
2668
|
+
const height = floating.offsetHeight || floating.getBoundingClientRect().height;
|
|
2669
|
+
const roomBelow = viewportHeight - rect.bottom - gutter - padding;
|
|
2670
|
+
const roomAbove = rect.top - gutter - padding;
|
|
2671
|
+
const room = preferredSide === "top" ? roomAbove : roomBelow;
|
|
2672
|
+
const side = room >= height ? preferredSide : roomAbove > roomBelow ? "top" : "bottom";
|
|
2673
|
+
const available = side === "top" ? roomAbove : roomBelow;
|
|
2674
|
+
let left = rect.left;
|
|
2675
|
+
if (align === "end") left = rect.right - width;
|
|
2676
|
+
else if (align === "center") left = rect.left + rect.width / 2 - width / 2;
|
|
2677
|
+
const maxLeft = Math.max(padding, viewportWidth - width - padding);
|
|
2678
|
+
left = Math.min(Math.max(padding, left), maxLeft);
|
|
2679
|
+
const top = side === "top" ? Math.max(padding, rect.top - gutter - Math.min(height, Math.max(available, 0))) : Math.min(
|
|
2680
|
+
rect.bottom + gutter,
|
|
2681
|
+
Math.max(padding, viewportHeight - height - padding)
|
|
2682
|
+
);
|
|
2683
|
+
floating.style.left = `${Math.round(left)}px`;
|
|
2684
|
+
floating.style.top = `${Math.round(top)}px`;
|
|
2685
|
+
floating.style.setProperty("--aeon-available-height", `${Math.round(Math.max(available, 0))}px`);
|
|
2686
|
+
floating.style.setProperty("--aeon-anchor-width", `${Math.round(rect.width)}px`);
|
|
2687
|
+
floating.dataset.side = side;
|
|
2688
|
+
floating.dataset.align = align;
|
|
2689
|
+
};
|
|
2690
|
+
const schedule = () => {
|
|
2691
|
+
cancelAnimationFrame(frame);
|
|
2692
|
+
frame = requestAnimationFrame(update);
|
|
2658
2693
|
};
|
|
2659
2694
|
update();
|
|
2660
|
-
|
|
2661
|
-
window.addEventListener("resize",
|
|
2662
|
-
window.addEventListener("scroll",
|
|
2695
|
+
schedule();
|
|
2696
|
+
window.addEventListener("resize", schedule);
|
|
2697
|
+
window.addEventListener("scroll", schedule, true);
|
|
2663
2698
|
return () => {
|
|
2664
|
-
|
|
2665
|
-
window.removeEventListener("
|
|
2699
|
+
cancelAnimationFrame(frame);
|
|
2700
|
+
window.removeEventListener("resize", schedule);
|
|
2701
|
+
window.removeEventListener("scroll", schedule, true);
|
|
2666
2702
|
};
|
|
2667
|
-
}, [active, anchorRef, floatingRef, placement, matchWidth]);
|
|
2703
|
+
}, [active, anchorRef, floatingRef, placement, matchWidth, gutter, zIndex, padding]);
|
|
2668
2704
|
}
|
|
2669
2705
|
|
|
2670
2706
|
// src/floating/FloatingPositioner.tsx
|
|
@@ -2679,13 +2715,17 @@ var FloatingPositioner = forwardRef23(
|
|
|
2679
2715
|
portalled = true,
|
|
2680
2716
|
placement = "bottom-start",
|
|
2681
2717
|
matchAnchorWidth = false,
|
|
2718
|
+
gutter,
|
|
2719
|
+
zIndex,
|
|
2682
2720
|
children,
|
|
2683
2721
|
className,
|
|
2684
2722
|
...rest
|
|
2685
2723
|
}, ref) {
|
|
2686
2724
|
const mounted = usePortalledMount();
|
|
2687
2725
|
useAnchorPosition(triggerRef, positionerRef, open && portalled, placement, {
|
|
2688
|
-
matchWidth: matchAnchorWidth
|
|
2726
|
+
matchWidth: matchAnchorWidth,
|
|
2727
|
+
gutter,
|
|
2728
|
+
zIndex
|
|
2689
2729
|
});
|
|
2690
2730
|
if (!open) return null;
|
|
2691
2731
|
const node = /* @__PURE__ */ jsx23(
|
|
@@ -3425,7 +3465,7 @@ var Trigger4 = forwardRef26(function PopoverTrigger({ onClick, ...rest }, ref) {
|
|
|
3425
3465
|
}
|
|
3426
3466
|
);
|
|
3427
3467
|
});
|
|
3428
|
-
var Positioner3 = forwardRef26(function PopoverPositioner({ portalled = true, placement = "bottom-start", children, className, ...rest }, ref) {
|
|
3468
|
+
var Positioner3 = forwardRef26(function PopoverPositioner({ portalled = true, placement = "bottom-start", gutter, zIndex, children, className, ...rest }, ref) {
|
|
3429
3469
|
const { open, triggerRef, positionerRef } = usePopoverCtx();
|
|
3430
3470
|
return /* @__PURE__ */ jsx26(
|
|
3431
3471
|
FloatingPositioner,
|
|
@@ -3438,6 +3478,8 @@ var Positioner3 = forwardRef26(function PopoverPositioner({ portalled = true, pl
|
|
|
3438
3478
|
positionerRef,
|
|
3439
3479
|
portalled,
|
|
3440
3480
|
placement,
|
|
3481
|
+
gutter,
|
|
3482
|
+
zIndex,
|
|
3441
3483
|
className,
|
|
3442
3484
|
...rest,
|
|
3443
3485
|
children
|
|
@@ -3583,7 +3625,7 @@ var Trigger5 = forwardRef27(function MenuTrigger({ onClick, ...rest }, ref) {
|
|
|
3583
3625
|
}
|
|
3584
3626
|
);
|
|
3585
3627
|
});
|
|
3586
|
-
var Positioner4 = forwardRef27(function MenuPositioner({ portalled = true, placement = "bottom-start", children, className, ...rest }, ref) {
|
|
3628
|
+
var Positioner4 = forwardRef27(function MenuPositioner({ portalled = true, placement = "bottom-start", gutter, zIndex, children, className, ...rest }, ref) {
|
|
3587
3629
|
const { open, triggerRef, positionerRef } = useMenuCtx();
|
|
3588
3630
|
return /* @__PURE__ */ jsx27(
|
|
3589
3631
|
FloatingPositioner,
|
|
@@ -3596,6 +3638,8 @@ var Positioner4 = forwardRef27(function MenuPositioner({ portalled = true, place
|
|
|
3596
3638
|
positionerRef,
|
|
3597
3639
|
portalled,
|
|
3598
3640
|
placement,
|
|
3641
|
+
gutter,
|
|
3642
|
+
zIndex,
|
|
3599
3643
|
className,
|
|
3600
3644
|
...rest,
|
|
3601
3645
|
children
|
|
@@ -3603,14 +3647,36 @@ var Positioner4 = forwardRef27(function MenuPositioner({ portalled = true, place
|
|
|
3603
3647
|
);
|
|
3604
3648
|
});
|
|
3605
3649
|
var Content7 = forwardRef27(function MenuContent({ onKeyDown, ...rest }, ref) {
|
|
3606
|
-
const { open, send, menuId } = useMenuCtx();
|
|
3650
|
+
const { open, send, menuId, triggerRef } = useMenuCtx();
|
|
3651
|
+
const contentRef = useRef10(null);
|
|
3652
|
+
useEffect17(() => {
|
|
3653
|
+
if (!open) return;
|
|
3654
|
+
const trigger = triggerRef.current;
|
|
3655
|
+
contentRef.current?.focus({ preventScroll: true });
|
|
3656
|
+
return () => {
|
|
3657
|
+
if (trigger?.isConnected) trigger.focus({ preventScroll: true });
|
|
3658
|
+
};
|
|
3659
|
+
}, [open, triggerRef]);
|
|
3660
|
+
useEffect17(() => {
|
|
3661
|
+
if (!open) return;
|
|
3662
|
+
const onDocumentKeyDown = (event) => {
|
|
3663
|
+
if (event.key === "Escape") send({ type: "ESCAPE" });
|
|
3664
|
+
};
|
|
3665
|
+
document.addEventListener("keydown", onDocumentKeyDown);
|
|
3666
|
+
return () => document.removeEventListener("keydown", onDocumentKeyDown);
|
|
3667
|
+
}, [open, send]);
|
|
3607
3668
|
if (!open) return null;
|
|
3608
3669
|
return /* @__PURE__ */ jsx27(
|
|
3609
3670
|
"div",
|
|
3610
3671
|
{
|
|
3611
|
-
ref
|
|
3672
|
+
ref: (node) => {
|
|
3673
|
+
contentRef.current = node;
|
|
3674
|
+
if (typeof ref === "function") ref(node);
|
|
3675
|
+
else if (ref) ref.current = node;
|
|
3676
|
+
},
|
|
3612
3677
|
id: menuId,
|
|
3613
3678
|
role: "menu",
|
|
3679
|
+
tabIndex: -1,
|
|
3614
3680
|
...mergeProps(
|
|
3615
3681
|
partAttrs23(menuAnatomy.scope, menuAnatomy.content, { state: "open" }),
|
|
3616
3682
|
{
|
|
@@ -3870,7 +3936,7 @@ var Trigger6 = forwardRef28(function TooltipTrigger({ onClick, onMouseEnter, onM
|
|
|
3870
3936
|
}
|
|
3871
3937
|
);
|
|
3872
3938
|
});
|
|
3873
|
-
var Positioner5 = forwardRef28(function TooltipPositioner({ portalled = true, placement = "top-start", children, className, ...rest }, ref) {
|
|
3939
|
+
var Positioner5 = forwardRef28(function TooltipPositioner({ portalled = true, placement = "top-start", gutter, zIndex, children, className, ...rest }, ref) {
|
|
3874
3940
|
const { open, triggerRef, positionerRef } = useTooltipCtx();
|
|
3875
3941
|
return /* @__PURE__ */ jsx28(
|
|
3876
3942
|
FloatingPositioner,
|
|
@@ -3883,6 +3949,8 @@ var Positioner5 = forwardRef28(function TooltipPositioner({ portalled = true, pl
|
|
|
3883
3949
|
positionerRef,
|
|
3884
3950
|
portalled,
|
|
3885
3951
|
placement,
|
|
3952
|
+
gutter,
|
|
3953
|
+
zIndex,
|
|
3886
3954
|
className,
|
|
3887
3955
|
...rest,
|
|
3888
3956
|
children
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"3.2.4","results":[[":src/select/select.test.tsx",{"duration":
|
|
1
|
+
{"version":"3.2.4","results":[[":src/select/select.test.tsx",{"duration":374.59737499999983,"failed":false}],[":src/dialog/dialog.test.tsx",{"duration":302.22662500000024,"failed":false}],[":src/avatar/avatar.test.tsx",{"duration":222.467666,"failed":false}],[":src/tooltip/tooltip.test.tsx",{"duration":394.93274999999994,"failed":false}],[":src/toast/toast.test.tsx",{"duration":290.9687079999999,"failed":false}]]}
|
|
@@ -13,6 +13,10 @@ export interface FloatingPositionerProps extends HTMLAttributes<HTMLDivElement>
|
|
|
13
13
|
portalled?: boolean
|
|
14
14
|
placement?: AnchorPlacement
|
|
15
15
|
matchAnchorWidth?: boolean
|
|
16
|
+
/** Gap between trigger and surface. */
|
|
17
|
+
gutter?: number
|
|
18
|
+
/** Stack level for the portalled surface. */
|
|
19
|
+
zIndex?: number
|
|
16
20
|
children?: ReactNode
|
|
17
21
|
}
|
|
18
22
|
|
|
@@ -27,6 +31,8 @@ export const FloatingPositioner = forwardRef<HTMLDivElement, FloatingPositionerP
|
|
|
27
31
|
portalled = true,
|
|
28
32
|
placement = 'bottom-start',
|
|
29
33
|
matchAnchorWidth = false,
|
|
34
|
+
gutter,
|
|
35
|
+
zIndex,
|
|
30
36
|
children,
|
|
31
37
|
className,
|
|
32
38
|
...rest
|
|
@@ -36,6 +42,8 @@ export const FloatingPositioner = forwardRef<HTMLDivElement, FloatingPositionerP
|
|
|
36
42
|
const mounted = usePortalledMount()
|
|
37
43
|
useAnchorPosition(triggerRef, positionerRef, open && portalled, placement, {
|
|
38
44
|
matchWidth: matchAnchorWidth,
|
|
45
|
+
gutter,
|
|
46
|
+
zIndex,
|
|
39
47
|
})
|
|
40
48
|
|
|
41
49
|
if (!open) return null
|
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
import { useEffect, type RefObject } from 'react'
|
|
2
2
|
|
|
3
|
-
export type
|
|
3
|
+
export type AnchorSide = 'top' | 'bottom'
|
|
4
|
+
export type AnchorAlign = 'start' | 'end' | 'center'
|
|
5
|
+
export type AnchorPlacement = `${AnchorSide}-${AnchorAlign}`
|
|
4
6
|
|
|
5
7
|
export interface AnchorPositionOptions {
|
|
6
8
|
/** Match floating width to anchor (select listboxes). */
|
|
7
9
|
matchWidth?: boolean
|
|
10
|
+
/** Gap between anchor and floating surface. */
|
|
11
|
+
gutter?: number
|
|
12
|
+
/** Stack level written to the floating surface. */
|
|
13
|
+
zIndex?: number
|
|
14
|
+
/** Viewport padding kept clear when flipping and clamping. */
|
|
15
|
+
padding?: number
|
|
8
16
|
}
|
|
9
17
|
|
|
10
|
-
|
|
18
|
+
const DEFAULT_GUTTER = 4
|
|
19
|
+
const DEFAULT_Z_INDEX = 50
|
|
20
|
+
const DEFAULT_PADDING = 8
|
|
21
|
+
|
|
22
|
+
function splitPlacement(placement: AnchorPlacement): [AnchorSide, AnchorAlign] {
|
|
23
|
+
const [side, align] = placement.split('-') as [AnchorSide, AnchorAlign]
|
|
24
|
+
return [side, align]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Pin floating surface to trigger rect (for portalled overlays).
|
|
29
|
+
*
|
|
30
|
+
* Collision aware: the preferred side flips when it has no room, and the
|
|
31
|
+
* surface is clamped inside the viewport so an end-aligned trigger near an edge
|
|
32
|
+
* never renders offscreen. The resolved geometry is published as
|
|
33
|
+
* `data-side` / `data-align` plus `--aeon-available-height` and
|
|
34
|
+
* `--aeon-anchor-width` so recipes can cap their own scroll area.
|
|
35
|
+
*/
|
|
11
36
|
export function useAnchorPosition(
|
|
12
37
|
anchorRef: RefObject<HTMLElement | null>,
|
|
13
38
|
floatingRef: RefObject<HTMLElement | null>,
|
|
@@ -15,22 +40,32 @@ export function useAnchorPosition(
|
|
|
15
40
|
placement: AnchorPlacement = 'bottom-start',
|
|
16
41
|
options: AnchorPositionOptions = {},
|
|
17
42
|
) {
|
|
18
|
-
const {
|
|
43
|
+
const {
|
|
44
|
+
matchWidth = false,
|
|
45
|
+
gutter = DEFAULT_GUTTER,
|
|
46
|
+
zIndex = DEFAULT_Z_INDEX,
|
|
47
|
+
padding = DEFAULT_PADDING,
|
|
48
|
+
} = options
|
|
19
49
|
|
|
20
50
|
useEffect(() => {
|
|
21
51
|
if (!active || !anchorRef.current || !floatingRef.current) return
|
|
22
52
|
|
|
53
|
+
let frame = 0
|
|
54
|
+
|
|
23
55
|
const update = () => {
|
|
24
56
|
const anchor = anchorRef.current
|
|
25
57
|
const floating = floatingRef.current
|
|
26
58
|
if (!anchor || !floating) return
|
|
27
59
|
|
|
60
|
+
const [preferredSide, align] = splitPlacement(placement)
|
|
28
61
|
const rect = anchor.getBoundingClientRect()
|
|
62
|
+
const viewportWidth = window.innerWidth
|
|
63
|
+
const viewportHeight = window.innerHeight
|
|
64
|
+
|
|
29
65
|
floating.style.position = 'fixed'
|
|
30
|
-
floating.style.left = `${rect.left}px`
|
|
31
66
|
floating.style.bottom = ''
|
|
32
67
|
floating.style.right = ''
|
|
33
|
-
floating.style.zIndex =
|
|
68
|
+
floating.style.zIndex = String(zIndex)
|
|
34
69
|
|
|
35
70
|
if (matchWidth) {
|
|
36
71
|
floating.style.width = `${rect.width}px`
|
|
@@ -40,22 +75,58 @@ export function useAnchorPosition(
|
|
|
40
75
|
floating.style.minWidth = ''
|
|
41
76
|
}
|
|
42
77
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
78
|
+
const width = floating.offsetWidth || floating.getBoundingClientRect().width
|
|
79
|
+
const height = floating.offsetHeight || floating.getBoundingClientRect().height
|
|
80
|
+
|
|
81
|
+
const roomBelow = viewportHeight - rect.bottom - gutter - padding
|
|
82
|
+
const roomAbove = rect.top - gutter - padding
|
|
83
|
+
const room = preferredSide === 'top' ? roomAbove : roomBelow
|
|
84
|
+
const side =
|
|
85
|
+
room >= height
|
|
86
|
+
? preferredSide
|
|
87
|
+
: roomAbove > roomBelow
|
|
88
|
+
? 'top'
|
|
89
|
+
: 'bottom'
|
|
90
|
+
const available = side === 'top' ? roomAbove : roomBelow
|
|
91
|
+
|
|
92
|
+
let left = rect.left
|
|
93
|
+
if (align === 'end') left = rect.right - width
|
|
94
|
+
else if (align === 'center') left = rect.left + rect.width / 2 - width / 2
|
|
95
|
+
|
|
96
|
+
const maxLeft = Math.max(padding, viewportWidth - width - padding)
|
|
97
|
+
left = Math.min(Math.max(padding, left), maxLeft)
|
|
98
|
+
|
|
99
|
+
const top =
|
|
100
|
+
side === 'top'
|
|
101
|
+
? Math.max(padding, rect.top - gutter - Math.min(height, Math.max(available, 0)))
|
|
102
|
+
: Math.min(
|
|
103
|
+
rect.bottom + gutter,
|
|
104
|
+
Math.max(padding, viewportHeight - height - padding),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
floating.style.left = `${Math.round(left)}px`
|
|
108
|
+
floating.style.top = `${Math.round(top)}px`
|
|
109
|
+
floating.style.setProperty('--aeon-available-height', `${Math.round(Math.max(available, 0))}px`)
|
|
110
|
+
floating.style.setProperty('--aeon-anchor-width', `${Math.round(rect.width)}px`)
|
|
111
|
+
floating.dataset.side = side
|
|
112
|
+
floating.dataset.align = align
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const schedule = () => {
|
|
116
|
+
cancelAnimationFrame(frame)
|
|
117
|
+
frame = requestAnimationFrame(update)
|
|
49
118
|
}
|
|
50
119
|
|
|
51
120
|
update()
|
|
52
|
-
|
|
121
|
+
// Measure again once the surface has laid out (flip/clamp need real height).
|
|
122
|
+
schedule()
|
|
53
123
|
|
|
54
|
-
window.addEventListener('resize',
|
|
55
|
-
window.addEventListener('scroll',
|
|
124
|
+
window.addEventListener('resize', schedule)
|
|
125
|
+
window.addEventListener('scroll', schedule, true)
|
|
56
126
|
return () => {
|
|
57
|
-
|
|
58
|
-
window.removeEventListener('
|
|
127
|
+
cancelAnimationFrame(frame)
|
|
128
|
+
window.removeEventListener('resize', schedule)
|
|
129
|
+
window.removeEventListener('scroll', schedule, true)
|
|
59
130
|
}
|
|
60
|
-
}, [active, anchorRef, floatingRef, placement, matchWidth])
|
|
131
|
+
}, [active, anchorRef, floatingRef, placement, matchWidth, gutter, zIndex, padding])
|
|
61
132
|
}
|
|
@@ -129,11 +129,15 @@ export interface MenuPositionerProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
129
129
|
/** Mount menu in document.body (default true). */
|
|
130
130
|
portalled?: boolean
|
|
131
131
|
placement?: AnchorPlacement
|
|
132
|
+
/** Gap between trigger and menu (default 4). */
|
|
133
|
+
gutter?: number
|
|
134
|
+
/** Stack level for the portalled surface (default 50). */
|
|
135
|
+
zIndex?: number
|
|
132
136
|
children?: ReactNode
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
const Positioner = forwardRef<HTMLDivElement, MenuPositionerProps>(function MenuPositioner(
|
|
136
|
-
{ portalled = true, placement = 'bottom-start', children, className, ...rest },
|
|
140
|
+
{ portalled = true, placement = 'bottom-start', gutter, zIndex, children, className, ...rest },
|
|
137
141
|
ref,
|
|
138
142
|
) {
|
|
139
143
|
const { open, triggerRef, positionerRef } = useMenuCtx()
|
|
@@ -148,6 +152,8 @@ const Positioner = forwardRef<HTMLDivElement, MenuPositionerProps>(function Menu
|
|
|
148
152
|
positionerRef={positionerRef}
|
|
149
153
|
portalled={portalled}
|
|
150
154
|
placement={placement}
|
|
155
|
+
gutter={gutter}
|
|
156
|
+
zIndex={zIndex}
|
|
151
157
|
className={className}
|
|
152
158
|
{...rest}
|
|
153
159
|
>
|
|
@@ -160,13 +166,41 @@ const Content = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(funct
|
|
|
160
166
|
{ onKeyDown, ...rest },
|
|
161
167
|
ref,
|
|
162
168
|
) {
|
|
163
|
-
const { open, send, menuId } = useMenuCtx()
|
|
169
|
+
const { open, send, menuId, triggerRef } = useMenuCtx()
|
|
170
|
+
const contentRef = useRef<HTMLDivElement | null>(null)
|
|
171
|
+
|
|
172
|
+
// Focus enters the surface while it is open and returns to the trigger on
|
|
173
|
+
// dismiss, so keyboard users stay inside the menu they opened.
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (!open) return
|
|
176
|
+
const trigger = triggerRef.current
|
|
177
|
+
contentRef.current?.focus({ preventScroll: true })
|
|
178
|
+
return () => {
|
|
179
|
+
if (trigger?.isConnected) trigger.focus({ preventScroll: true })
|
|
180
|
+
}
|
|
181
|
+
}, [open, triggerRef])
|
|
182
|
+
|
|
183
|
+
// Escape closes wherever focus sits — trigger, item or elsewhere on the page.
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (!open) return
|
|
186
|
+
const onDocumentKeyDown = (event: globalThis.KeyboardEvent) => {
|
|
187
|
+
if (event.key === 'Escape') send({ type: 'ESCAPE' })
|
|
188
|
+
}
|
|
189
|
+
document.addEventListener('keydown', onDocumentKeyDown)
|
|
190
|
+
return () => document.removeEventListener('keydown', onDocumentKeyDown)
|
|
191
|
+
}, [open, send])
|
|
192
|
+
|
|
164
193
|
if (!open) return null
|
|
165
194
|
return (
|
|
166
195
|
<div
|
|
167
|
-
ref={
|
|
196
|
+
ref={(node) => {
|
|
197
|
+
contentRef.current = node
|
|
198
|
+
if (typeof ref === 'function') ref(node)
|
|
199
|
+
else if (ref) ref.current = node
|
|
200
|
+
}}
|
|
168
201
|
id={menuId}
|
|
169
202
|
role="menu"
|
|
203
|
+
tabIndex={-1}
|
|
170
204
|
{...mergeProps(
|
|
171
205
|
partAttrs(menuAnatomy.scope, menuAnatomy.content, { state: 'open' }),
|
|
172
206
|
{
|
|
@@ -132,11 +132,15 @@ export interface PopoverPositionerProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
132
132
|
/** Mount panel in document.body (default true). */
|
|
133
133
|
portalled?: boolean
|
|
134
134
|
placement?: AnchorPlacement
|
|
135
|
+
/** Gap between trigger and panel (default 4). */
|
|
136
|
+
gutter?: number
|
|
137
|
+
/** Stack level for the portalled surface (default 50). */
|
|
138
|
+
zIndex?: number
|
|
135
139
|
children?: ReactNode
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
const Positioner = forwardRef<HTMLDivElement, PopoverPositionerProps>(function PopoverPositioner(
|
|
139
|
-
{ portalled = true, placement = 'bottom-start', children, className, ...rest },
|
|
143
|
+
{ portalled = true, placement = 'bottom-start', gutter, zIndex, children, className, ...rest },
|
|
140
144
|
ref,
|
|
141
145
|
) {
|
|
142
146
|
const { open, triggerRef, positionerRef } = usePopoverCtx()
|
|
@@ -151,6 +155,8 @@ const Positioner = forwardRef<HTMLDivElement, PopoverPositionerProps>(function P
|
|
|
151
155
|
positionerRef={positionerRef}
|
|
152
156
|
portalled={portalled}
|
|
153
157
|
placement={placement}
|
|
158
|
+
gutter={gutter}
|
|
159
|
+
zIndex={zIndex}
|
|
154
160
|
className={className}
|
|
155
161
|
{...rest}
|
|
156
162
|
>
|