@kanso-labs/react-native-kanso-ui 0.4.0 → 0.6.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/README.md +4 -1
- package/dist/autocomplete/index.js +39 -0
- package/dist/autocomplete/index.js.map +1 -0
- package/dist/badge/index.js +74 -0
- package/dist/badge/index.js.map +1 -0
- package/dist/badge/styles.js +51 -0
- package/dist/badge/styles.js.map +1 -0
- package/dist/button/index.d.ts +2 -1
- package/dist/button/index.js +35 -2
- package/dist/button/index.js.map +1 -1
- package/dist/button/slots.d.ts +2 -1
- package/dist/button/slots.js.map +1 -1
- package/dist/chip/index.js +2 -2
- package/dist/components/app-bar/index.d.ts +111 -0
- package/dist/components/app-bar/index.js +341 -0
- package/dist/components/app-bar/index.js.map +1 -0
- package/dist/components/autocomplete/index.d.ts +49 -0
- package/dist/components/autocomplete/index.js +87 -0
- package/dist/components/autocomplete/index.js.map +1 -0
- package/dist/components/button/index.d.ts +1 -1
- package/dist/components/button/index.js +197 -151
- package/dist/components/button/index.js.map +1 -1
- package/dist/components/card/index.js +2 -2
- package/dist/components/checkbox/index.js +106 -193
- package/dist/components/checkbox/index.js.map +1 -1
- package/dist/components/chip/index.js +1 -1
- package/dist/components/chip-group/index.js +3 -3
- package/dist/components/dialog/index.js +4 -3
- package/dist/components/dialog/index.js.map +1 -1
- package/dist/components/icon-button/index.d.ts +1 -1
- package/dist/components/icon-button/index.js +206 -160
- package/dist/components/icon-button/index.js.map +1 -1
- package/dist/components/link/index.js +2 -2
- package/dist/components/list/index.d.ts +216 -0
- package/dist/components/list/index.js +1118 -0
- package/dist/components/list/index.js.map +1 -0
- package/dist/components/list-detail/index.d.ts +56 -0
- package/dist/components/list-detail/index.js +169 -0
- package/dist/components/list-detail/index.js.map +1 -0
- package/dist/components/list-item/index.d.ts +63 -0
- package/dist/components/list-item/index.js +275 -0
- package/dist/components/list-item/index.js.map +1 -0
- package/dist/components/menu/index.d.ts +309 -0
- package/dist/components/menu/index.js +1283 -0
- package/dist/components/menu/index.js.map +1 -0
- package/dist/components/navigation-bar/index.d.ts +170 -0
- package/dist/components/navigation-bar/index.js +494 -0
- package/dist/components/navigation-bar/index.js.map +1 -0
- package/dist/components/number-field/index.js +3 -3
- package/dist/components/popover/index.d.ts +140 -0
- package/dist/components/popover/index.js +704 -0
- package/dist/components/popover/index.js.map +1 -0
- package/dist/components/progress-indicator/index.js +3 -3
- package/dist/components/radio-group/index.js +2 -2
- package/dist/components/search-field/index.d.ts +11 -3
- package/dist/components/search-field/index.js +66 -60
- package/dist/components/search-field/index.js.map +1 -1
- package/dist/components/segmented-button/index.js +4 -4
- package/dist/components/sheet/index.js +5 -4
- package/dist/components/sheet/index.js.map +1 -1
- package/dist/components/snackbar/index.js +4 -4
- package/dist/components/switch/index.js +3 -3
- package/dist/control/checkbox-box.js +137 -0
- package/dist/control/checkbox-box.js.map +1 -0
- package/dist/field/index.d.ts +1 -1
- package/dist/field/index.js +3 -3
- package/dist/form/index.d.ts +1 -1
- package/dist/glyphs/index.js +22 -3
- package/dist/glyphs/index.js.map +1 -1
- package/dist/hooks/useAppBarScroll.d.ts +56 -0
- package/dist/hooks/useAppBarScroll.js +87 -0
- package/dist/hooks/useAppBarScroll.js.map +1 -0
- package/dist/i18n/index.js +16 -1
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/messages.js +272 -34
- package/dist/i18n/messages.js.map +1 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.js +12 -3
- package/dist/indicator/index.d.ts +2 -2
- package/dist/indicator/index.js +1 -1
- package/dist/indicator/motion.js +1 -1
- package/dist/mark/index.js +1 -1
- package/dist/modal/index.d.ts +1 -1
- package/dist/modal/index.js +2 -9
- package/dist/modal/index.js.map +1 -1
- package/dist/navigation/index.d.ts +29 -0
- package/dist/navigation/index.js +405 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/navigation/styles.js +61 -0
- package/dist/navigation/styles.js.map +1 -0
- package/dist/overlay/anchored.d.ts +2 -0
- package/dist/overlay/anchored.js +182 -0
- package/dist/overlay/anchored.js.map +1 -0
- package/dist/overlay/host.d.ts +2 -0
- package/dist/overlay/index.d.ts +4 -0
- package/dist/overlay/panel.js +215 -0
- package/dist/overlay/panel.js.map +1 -0
- package/dist/overlay/placement.d.ts +8 -0
- package/dist/overlay/placement.js +87 -0
- package/dist/overlay/placement.js.map +1 -0
- package/dist/press/index.js +74 -68
- package/dist/press/index.js.map +1 -1
- package/dist/ref/index.js +15 -0
- package/dist/ref/index.js.map +1 -0
- package/dist/row/index.js +158 -0
- package/dist/row/index.js.map +1 -0
- package/dist/row/styles.js +51 -0
- package/dist/row/styles.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { useTheme } from "../theme/scope.js";
|
|
2
|
+
import { OverlayHostContext } from "./host.js";
|
|
3
|
+
import { boundsOf, place } from "./placement.js";
|
|
4
|
+
import { c } from "react/compiler-runtime";
|
|
5
|
+
import { Dimensions, I18nManager, useWindowDimensions } from "react-native";
|
|
6
|
+
import { SafeAreaInsetsContext } from "react-native-safe-area-context";
|
|
7
|
+
import { use, useEffect, useState } from "react";
|
|
8
|
+
//#region src/overlay/anchored.ts
|
|
9
|
+
const HIDDEN = {
|
|
10
|
+
left: 0,
|
|
11
|
+
opacity: 0,
|
|
12
|
+
pointerEvents: "none",
|
|
13
|
+
position: "absolute",
|
|
14
|
+
top: 0
|
|
15
|
+
};
|
|
16
|
+
const NO_INSETS = {
|
|
17
|
+
bottom: 0,
|
|
18
|
+
left: 0,
|
|
19
|
+
right: 0,
|
|
20
|
+
top: 0
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Places a surface drawn in an `Overlay` beside the view that opened it, as
|
|
24
|
+
* web's anchored overlays are placed: on `side`, lined up by `align`, flipped
|
|
25
|
+
* to the opposite side at the edge of the window, and kept inside the safe
|
|
26
|
+
* area's insets with `spacing.md` to spare, the padding React Aria keeps from
|
|
27
|
+
* the edge of the viewport. On `top` and `bottom`, `start` and `end` follow
|
|
28
|
+
* `I18nManager.isRTL`.
|
|
29
|
+
*
|
|
30
|
+
* The anchor is measured with `measureInWindow` whenever the surface reports
|
|
31
|
+
* a new size or the window changes size.
|
|
32
|
+
*/
|
|
33
|
+
function useAnchoredPlacement(t0) {
|
|
34
|
+
const $ = c(31);
|
|
35
|
+
const { align: t1, alignOffset: t2, anchor, side: t3, sideOffset } = t0;
|
|
36
|
+
const align = t1 === void 0 ? "center" : t1;
|
|
37
|
+
const alignOffset = t2 === void 0 ? 0 : t2;
|
|
38
|
+
const side = t3 === void 0 ? "bottom" : t3;
|
|
39
|
+
const store = use(OverlayHostContext);
|
|
40
|
+
const { spacing } = useTheme();
|
|
41
|
+
const window = useWindowDimensions();
|
|
42
|
+
const insets = use(SafeAreaInsetsContext) ?? NO_INSETS;
|
|
43
|
+
const [measured, setMeasured] = useState(null);
|
|
44
|
+
const [surface, setSurface] = useState(null);
|
|
45
|
+
let t4;
|
|
46
|
+
if ($[0] !== anchor.current || $[1] !== store || $[2] !== surface) {
|
|
47
|
+
t4 = () => {
|
|
48
|
+
const measure = () => {
|
|
49
|
+
const layer = store?.layer() ?? null;
|
|
50
|
+
const target = anchor.current;
|
|
51
|
+
if (surface === null || layer === null || target === null) return;
|
|
52
|
+
layer.measureInWindow((originX, originY) => {
|
|
53
|
+
target.measureInWindow((x, y, width, height) => {
|
|
54
|
+
setMeasured({
|
|
55
|
+
anchor: {
|
|
56
|
+
height,
|
|
57
|
+
width,
|
|
58
|
+
x,
|
|
59
|
+
y
|
|
60
|
+
},
|
|
61
|
+
origin: {
|
|
62
|
+
x: originX,
|
|
63
|
+
y: originY
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
measure();
|
|
70
|
+
const subscription = Dimensions.addEventListener("change", measure);
|
|
71
|
+
return () => {
|
|
72
|
+
subscription.remove();
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
$[0] = anchor.current;
|
|
76
|
+
$[1] = store;
|
|
77
|
+
$[2] = surface;
|
|
78
|
+
$[3] = t4;
|
|
79
|
+
} else t4 = $[3];
|
|
80
|
+
let t5;
|
|
81
|
+
if ($[4] !== anchor || $[5] !== store || $[6] !== surface) {
|
|
82
|
+
t5 = [
|
|
83
|
+
anchor,
|
|
84
|
+
store,
|
|
85
|
+
surface
|
|
86
|
+
];
|
|
87
|
+
$[4] = anchor;
|
|
88
|
+
$[5] = store;
|
|
89
|
+
$[6] = surface;
|
|
90
|
+
$[7] = t5;
|
|
91
|
+
} else t5 = $[7];
|
|
92
|
+
useEffect(t4, t5);
|
|
93
|
+
let t6;
|
|
94
|
+
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
|
|
95
|
+
t6 = (event) => {
|
|
96
|
+
const { height: height_0, width: width_0 } = event.nativeEvent.layout;
|
|
97
|
+
setSurface((known) => known?.height === height_0 && known.width === width_0 ? known : {
|
|
98
|
+
height: height_0,
|
|
99
|
+
width: width_0
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
$[8] = t6;
|
|
103
|
+
} else t6 = $[8];
|
|
104
|
+
const onLayout = t6;
|
|
105
|
+
if (measured === null || surface === null) {
|
|
106
|
+
let t7;
|
|
107
|
+
if ($[9] !== side) {
|
|
108
|
+
t7 = {
|
|
109
|
+
onLayout,
|
|
110
|
+
placed: false,
|
|
111
|
+
side,
|
|
112
|
+
style: HIDDEN
|
|
113
|
+
};
|
|
114
|
+
$[9] = side;
|
|
115
|
+
$[10] = t7;
|
|
116
|
+
} else t7 = $[10];
|
|
117
|
+
return t7;
|
|
118
|
+
}
|
|
119
|
+
let t7;
|
|
120
|
+
if ($[11] !== align || $[12] !== alignOffset || $[13] !== insets || $[14] !== measured.anchor || $[15] !== side || $[16] !== sideOffset || $[17] !== spacing.md || $[18] !== spacing.sm || $[19] !== surface || $[20] !== window) {
|
|
121
|
+
t7 = place({
|
|
122
|
+
align,
|
|
123
|
+
alignOffset,
|
|
124
|
+
anchor: measured.anchor,
|
|
125
|
+
bounds: boundsOf(window, insets, spacing.md),
|
|
126
|
+
rtl: I18nManager.isRTL,
|
|
127
|
+
side,
|
|
128
|
+
sideOffset: sideOffset ?? spacing.sm,
|
|
129
|
+
surface
|
|
130
|
+
});
|
|
131
|
+
$[11] = align;
|
|
132
|
+
$[12] = alignOffset;
|
|
133
|
+
$[13] = insets;
|
|
134
|
+
$[14] = measured.anchor;
|
|
135
|
+
$[15] = side;
|
|
136
|
+
$[16] = sideOffset;
|
|
137
|
+
$[17] = spacing.md;
|
|
138
|
+
$[18] = spacing.sm;
|
|
139
|
+
$[19] = surface;
|
|
140
|
+
$[20] = window;
|
|
141
|
+
$[21] = t7;
|
|
142
|
+
} else t7 = $[21];
|
|
143
|
+
const placement = t7;
|
|
144
|
+
const x_0 = placement.x - measured.origin.x;
|
|
145
|
+
const t8 = placement.y - measured.origin.y;
|
|
146
|
+
let t9;
|
|
147
|
+
if ($[22] !== x_0) {
|
|
148
|
+
t9 = I18nManager.isRTL && I18nManager.doLeftAndRightSwapInRTL ? { right: x_0 } : { left: x_0 };
|
|
149
|
+
$[22] = x_0;
|
|
150
|
+
$[23] = t9;
|
|
151
|
+
} else t9 = $[23];
|
|
152
|
+
let t10;
|
|
153
|
+
if ($[24] !== placement.maxHeight || $[25] !== t8 || $[26] !== t9) {
|
|
154
|
+
t10 = {
|
|
155
|
+
maxHeight: placement.maxHeight,
|
|
156
|
+
position: "absolute",
|
|
157
|
+
top: t8,
|
|
158
|
+
...t9
|
|
159
|
+
};
|
|
160
|
+
$[24] = placement.maxHeight;
|
|
161
|
+
$[25] = t8;
|
|
162
|
+
$[26] = t9;
|
|
163
|
+
$[27] = t10;
|
|
164
|
+
} else t10 = $[27];
|
|
165
|
+
let t11;
|
|
166
|
+
if ($[28] !== placement.side || $[29] !== t10) {
|
|
167
|
+
t11 = {
|
|
168
|
+
onLayout,
|
|
169
|
+
placed: true,
|
|
170
|
+
side: placement.side,
|
|
171
|
+
style: t10
|
|
172
|
+
};
|
|
173
|
+
$[28] = placement.side;
|
|
174
|
+
$[29] = t10;
|
|
175
|
+
$[30] = t11;
|
|
176
|
+
} else t11 = $[30];
|
|
177
|
+
return t11;
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
export { useAnchoredPlacement };
|
|
181
|
+
|
|
182
|
+
//# sourceMappingURL=anchored.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchored.js","names":["RefObject","HostInstance","LayoutChangeEvent","ViewStyle","use","useEffect","useState","Dimensions","I18nManager","useWindowDimensions","SafeAreaInsetsContext","OverlayAlign","OverlaySide","Rect","Size","useTheme","OverlayHostContext","boundsOf","place","AnchoredPlacement","onLayout","event","placed","side","style","AnchoredPlacementOptions","align","alignOffset","anchor","Pick","sideOffset","Measured","origin","x","y","HIDDEN","left","opacity","pointerEvents","position","top","NO_INSETS","bottom","right","useAnchoredPlacement","t0","$","_c","t1","t2","t3","undefined","store","spacing","window","insets","measured","setMeasured","surface","setSurface","t4","current","measure","layer","target","measureInWindow","originX","originY","width","height","subscription","addEventListener","remove","t5","t6","Symbol","for","height_0","width_0","nativeEvent","layout","known","t7","md","sm","bounds","rtl","isRTL","placement","x_0","t8","t9","doLeftAndRightSwapInRTL","t10","maxHeight","t11","_c"],"sources":["../../src/overlay/anchored.ts"],"sourcesContent":["import type { RefObject } from 'react'\nimport type { HostInstance, LayoutChangeEvent, ViewStyle } from 'react-native'\n\nimport { use, useEffect, useState } from 'react'\nimport { Dimensions, I18nManager, useWindowDimensions } from 'react-native'\nimport { SafeAreaInsetsContext } from 'react-native-safe-area-context'\n\nimport type { OverlayAlign, OverlaySide, Rect, Size } from './placement'\n\nimport { useTheme } from '../theme/scope'\nimport { OverlayHostContext } from './host'\nimport { boundsOf, place } from './placement'\n\ntype AnchoredPlacement = {\n /**\n * Hand to the surface's own `onLayout`. The surface is placed once it has\n * reported its size, and placed again whenever that size changes.\n */\n readonly onLayout: (event: LayoutChangeEvent) => void\n /**\n * Whether the surface has been placed. Until it has, it is laid out out of\n * sight, and a screen reader cannot be sent to it.\n */\n readonly placed: boolean\n /** The side the surface opened on, which is the opposite one after a flip. */\n readonly side: OverlaySide\n /**\n * The surface's position in the overlay layer, and the height it may take.\n * Until it has been placed it is laid out out of sight, to be measured.\n */\n readonly style: ViewStyle\n}\n\ntype AnchoredPlacementOptions = {\n /**\n * Where the surface sits along the side it opens on.\n * @default 'center'\n */\n readonly align?: OverlayAlign | undefined\n /** Moves the surface along that side, in dp: right or down when positive. */\n readonly alignOffset?: number | undefined\n /** The view the surface opens beside. */\n readonly anchor: RefObject<null | Pick<HostInstance, 'measureInWindow'>>\n /**\n * Which side of the anchor the surface opens on. It flips to the opposite\n * side when there is no room.\n * @default 'bottom'\n */\n readonly side?: OverlaySide | undefined\n /**\n * The gap between the anchor and the surface, in dp.\n * @default spacing.sm\n */\n readonly sideOffset?: number | undefined\n}\n\ntype Measured = {\n readonly anchor: Rect\n /** Where the overlay layer starts in the window. */\n readonly origin: { readonly x: number; readonly y: number }\n}\n\nconst HIDDEN: ViewStyle = {\n left: 0,\n opacity: 0,\n pointerEvents: 'none',\n position: 'absolute',\n top: 0,\n}\n\n// Outside a SafeAreaProvider the insets are unknown, and the window's own edge\n// is the best bound there is.\nconst NO_INSETS = { bottom: 0, left: 0, right: 0, top: 0 }\n\n/**\n * Places a surface drawn in an `Overlay` beside the view that opened it, as\n * web's anchored overlays are placed: on `side`, lined up by `align`, flipped\n * to the opposite side at the edge of the window, and kept inside the safe\n * area's insets with `spacing.md` to spare, the padding React Aria keeps from\n * the edge of the viewport. On `top` and `bottom`, `start` and `end` follow\n * `I18nManager.isRTL`.\n *\n * The anchor is measured with `measureInWindow` whenever the surface reports\n * a new size or the window changes size.\n */\nfunction useAnchoredPlacement({\n align = 'center',\n alignOffset = 0,\n anchor,\n side = 'bottom',\n sideOffset,\n}: AnchoredPlacementOptions): AnchoredPlacement {\n const store = use(OverlayHostContext)\n const { spacing } = useTheme()\n const window = useWindowDimensions()\n const insets = use(SafeAreaInsetsContext) ?? NO_INSETS\n const [measured, setMeasured] = useState<Measured | null>(null)\n const [surface, setSurface] = useState<null | Size>(null)\n\n useEffect(() => {\n // The layer is measured as well as the anchor, since the host need not\n // start at the window's corner: the surface is placed in the layer, and\n // the anchor is measured in the window.\n const measure = () => {\n const layer = store?.layer() ?? null\n const target = anchor.current\n\n if (surface === null || layer === null || target === null) {\n return\n }\n\n layer.measureInWindow((originX, originY) => {\n target.measureInWindow((x, y, width, height) => {\n setMeasured({\n anchor: { height, width, x, y },\n origin: { x: originX, y: originY },\n })\n })\n })\n }\n\n measure()\n\n // A rotation or a resized window can move the anchor without the\n // surface changing size.\n const subscription = Dimensions.addEventListener('change', measure)\n\n return () => {\n subscription.remove()\n }\n }, [anchor, store, surface])\n\n const onLayout = (event: LayoutChangeEvent) => {\n const { height, width } = event.nativeEvent.layout\n\n setSurface((known) =>\n known?.height === height && known.width === width\n ? known\n : { height, width },\n )\n }\n\n if (measured === null || surface === null) {\n return { onLayout, placed: false, side, style: HIDDEN }\n }\n\n const placement = place({\n align,\n alignOffset,\n anchor: measured.anchor,\n bounds: boundsOf(window, insets, spacing.md),\n rtl: I18nManager.isRTL,\n side,\n sideOffset: sideOffset ?? spacing.sm,\n surface,\n })\n const x = placement.x - measured.origin.x\n\n return {\n onLayout,\n placed: true,\n side: placement.side,\n style: {\n maxHeight: placement.maxHeight,\n position: 'absolute',\n top: placement.y - measured.origin.y,\n // The placement is in physical coordinates, as `measureInWindow`\n // reports them. React Native swaps `left` and `right` in a right-to-left\n // layout unless an app turns that off, so there `right` is what puts\n // the surface's left edge where the placement says.\n ...(I18nManager.isRTL && I18nManager.doLeftAndRightSwapInRTL\n ? { right: x }\n : { left: x }),\n },\n }\n}\n\nexport type { AnchoredPlacement, AnchoredPlacementOptions }\n\nexport { useAnchoredPlacement }\n"],"mappings":"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"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { useTheme } from "../theme/scope.js";
|
|
2
|
+
import { c } from "react/compiler-runtime";
|
|
3
|
+
import { AccessibilityInfo, BackHandler, Platform, Pressable, StyleSheet } from "react-native";
|
|
4
|
+
import { Easing, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming } from "react-native-reanimated";
|
|
5
|
+
import { useEffect, useRef } from "react";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
//#region src/overlay/panel.tsx
|
|
8
|
+
const SCALE_FROM = .95;
|
|
9
|
+
/** The edge a panel grows from, the one facing its anchor. */
|
|
10
|
+
const PANEL_ORIGINS = {
|
|
11
|
+
bottom: "top",
|
|
12
|
+
left: "right",
|
|
13
|
+
right: "left",
|
|
14
|
+
top: "bottom"
|
|
15
|
+
};
|
|
16
|
+
const styles = StyleSheet.create({ layer: {
|
|
17
|
+
bottom: 0,
|
|
18
|
+
left: 0,
|
|
19
|
+
position: "absolute",
|
|
20
|
+
right: 0,
|
|
21
|
+
top: 0
|
|
22
|
+
} });
|
|
23
|
+
/**
|
|
24
|
+
* The layer a press outside a non-modal panel lands on, drawn just before the
|
|
25
|
+
* panel so it sits under it. The press goes no further, so a press on the
|
|
26
|
+
* trigger closes the panel rather than opening it again, as on web. Nothing
|
|
27
|
+
* but that press reaches it: React Native Web's Pressable is a tab stop
|
|
28
|
+
* unless told otherwise, whatever `focusable` says.
|
|
29
|
+
*/
|
|
30
|
+
function DismissLayer(t0) {
|
|
31
|
+
const $ = c(2);
|
|
32
|
+
const { onPress } = t0;
|
|
33
|
+
let t1;
|
|
34
|
+
if ($[0] !== onPress) {
|
|
35
|
+
t1 = /* @__PURE__ */ jsx(Pressable, {
|
|
36
|
+
accessible: false,
|
|
37
|
+
"aria-hidden": true,
|
|
38
|
+
focusable: false,
|
|
39
|
+
onPress,
|
|
40
|
+
style: styles.layer,
|
|
41
|
+
tabIndex: -1
|
|
42
|
+
});
|
|
43
|
+
$[0] = onPress;
|
|
44
|
+
$[1] = t1;
|
|
45
|
+
} else t1 = $[1];
|
|
46
|
+
return t1;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Closes a non-modal panel from Android's back gesture, as a `Modal` closes a
|
|
50
|
+
* modal one. React Native calls the latest listener first, so a panel opened
|
|
51
|
+
* from another closes before the one it came from. React Native Web has no
|
|
52
|
+
* back gesture, and its BackHandler reports an error when one is listened for,
|
|
53
|
+
* so nothing listens there.
|
|
54
|
+
*/
|
|
55
|
+
function useBackToClose(close, active) {
|
|
56
|
+
const $ = c(4);
|
|
57
|
+
let t0;
|
|
58
|
+
let t1;
|
|
59
|
+
if ($[0] !== active || $[1] !== close) {
|
|
60
|
+
t0 = () => {
|
|
61
|
+
if (!active || Platform.OS === "web") return;
|
|
62
|
+
const subscription = BackHandler.addEventListener("hardwareBackPress", () => {
|
|
63
|
+
close();
|
|
64
|
+
return true;
|
|
65
|
+
});
|
|
66
|
+
return () => {
|
|
67
|
+
subscription.remove();
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
t1 = [active, close];
|
|
71
|
+
$[0] = active;
|
|
72
|
+
$[1] = close;
|
|
73
|
+
$[2] = t0;
|
|
74
|
+
$[3] = t1;
|
|
75
|
+
} else {
|
|
76
|
+
t0 = $[2];
|
|
77
|
+
t1 = $[3];
|
|
78
|
+
}
|
|
79
|
+
useEffect(t0, t1);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Sends the screen reader to `target` once `ready`, which a panel passes once
|
|
83
|
+
* it has been placed, and so can be seen. React Native Web has no screen
|
|
84
|
+
* reader of its own to send.
|
|
85
|
+
*/
|
|
86
|
+
function useFocusOnPlaced(ready, target) {
|
|
87
|
+
const $ = c(4);
|
|
88
|
+
let t0;
|
|
89
|
+
let t1;
|
|
90
|
+
if ($[0] !== ready || $[1] !== target) {
|
|
91
|
+
t0 = () => {
|
|
92
|
+
if (ready && target !== null && Platform.OS !== "web") AccessibilityInfo.sendAccessibilityEvent(target, "focus");
|
|
93
|
+
};
|
|
94
|
+
t1 = [ready, target];
|
|
95
|
+
$[0] = ready;
|
|
96
|
+
$[1] = target;
|
|
97
|
+
$[2] = t0;
|
|
98
|
+
$[3] = t1;
|
|
99
|
+
} else {
|
|
100
|
+
t0 = $[2];
|
|
101
|
+
t1 = $[3];
|
|
102
|
+
}
|
|
103
|
+
useEffect(t0, t1);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A panel's motion, and the timer that unmounts it once its exit has run: it
|
|
107
|
+
* fades and scales up from 95% over the short3 duration on the emphasised
|
|
108
|
+
* decelerating curve, and back over short2 on the accelerating one. The
|
|
109
|
+
* entrance waits for the panel to be placed, so it is not spent while the
|
|
110
|
+
* panel is still hidden being measured, and the panel stays transparent
|
|
111
|
+
* until then. Under reduced motion it arrives and leaves at once.
|
|
112
|
+
*/
|
|
113
|
+
function usePanelMotion(isOpen, placed, setPresent) {
|
|
114
|
+
const $ = c(8);
|
|
115
|
+
const { motion } = useTheme();
|
|
116
|
+
const reduceMotion = useReducedMotion();
|
|
117
|
+
const shown = useSharedValue(0);
|
|
118
|
+
let t0;
|
|
119
|
+
let t1;
|
|
120
|
+
if ($[0] !== isOpen || $[1] !== motion || $[2] !== placed || $[3] !== reduceMotion || $[4] !== setPresent || $[5] !== shown) {
|
|
121
|
+
t0 = () => {
|
|
122
|
+
if (isOpen && !placed) return;
|
|
123
|
+
if (isOpen) {
|
|
124
|
+
shown.set(reduceMotion ? 1 : withTiming(1, {
|
|
125
|
+
duration: motion.durationShort3,
|
|
126
|
+
easing: Easing.bezier(...motion.easingEmphasizedDecelerate)
|
|
127
|
+
}));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (reduceMotion) {
|
|
131
|
+
shown.set(0);
|
|
132
|
+
setPresent(false);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
shown.set(withTiming(0, {
|
|
136
|
+
duration: motion.durationShort2,
|
|
137
|
+
easing: Easing.bezier(...motion.easingEmphasizedAccelerate)
|
|
138
|
+
}));
|
|
139
|
+
const timer = setTimeout(() => {
|
|
140
|
+
setPresent(false);
|
|
141
|
+
}, motion.durationShort2);
|
|
142
|
+
return () => {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
t1 = [
|
|
147
|
+
isOpen,
|
|
148
|
+
motion,
|
|
149
|
+
placed,
|
|
150
|
+
reduceMotion,
|
|
151
|
+
setPresent,
|
|
152
|
+
shown
|
|
153
|
+
];
|
|
154
|
+
$[0] = isOpen;
|
|
155
|
+
$[1] = motion;
|
|
156
|
+
$[2] = placed;
|
|
157
|
+
$[3] = reduceMotion;
|
|
158
|
+
$[4] = setPresent;
|
|
159
|
+
$[5] = shown;
|
|
160
|
+
$[6] = t0;
|
|
161
|
+
$[7] = t1;
|
|
162
|
+
} else {
|
|
163
|
+
t0 = $[6];
|
|
164
|
+
t1 = $[7];
|
|
165
|
+
}
|
|
166
|
+
useEffect(t0, t1);
|
|
167
|
+
return useAnimatedStyle(() => {
|
|
168
|
+
"worklet";
|
|
169
|
+
const progress = shown.get();
|
|
170
|
+
return {
|
|
171
|
+
opacity: progress,
|
|
172
|
+
transform: [{ scale: SCALE_FROM + .050000000000000044 * progress }]
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Sends the screen reader back to the trigger once a panel has gone, unless
|
|
178
|
+
* `quiet` says nothing moved it there, as a pointer resting on a hover
|
|
179
|
+
* trigger does not. React Native Web's `Modal` restores focus itself, and has
|
|
180
|
+
* no screen reader to send otherwise.
|
|
181
|
+
*/
|
|
182
|
+
function useReturnFocus(present, quiet, trigger) {
|
|
183
|
+
const $ = c(8);
|
|
184
|
+
const shown = useRef(present);
|
|
185
|
+
let t0;
|
|
186
|
+
if ($[0] !== present || $[1] !== quiet || $[2] !== trigger.current) {
|
|
187
|
+
t0 = () => {
|
|
188
|
+
const gone = shown.current && !present;
|
|
189
|
+
const target = trigger.current;
|
|
190
|
+
shown.current = present;
|
|
191
|
+
if (gone && !quiet && target !== null && Platform.OS !== "web") AccessibilityInfo.sendAccessibilityEvent(target, "focus");
|
|
192
|
+
};
|
|
193
|
+
$[0] = present;
|
|
194
|
+
$[1] = quiet;
|
|
195
|
+
$[2] = trigger.current;
|
|
196
|
+
$[3] = t0;
|
|
197
|
+
} else t0 = $[3];
|
|
198
|
+
let t1;
|
|
199
|
+
if ($[4] !== present || $[5] !== quiet || $[6] !== trigger) {
|
|
200
|
+
t1 = [
|
|
201
|
+
quiet,
|
|
202
|
+
present,
|
|
203
|
+
trigger
|
|
204
|
+
];
|
|
205
|
+
$[4] = present;
|
|
206
|
+
$[5] = quiet;
|
|
207
|
+
$[6] = trigger;
|
|
208
|
+
$[7] = t1;
|
|
209
|
+
} else t1 = $[7];
|
|
210
|
+
useEffect(t0, t1);
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
export { DismissLayer, PANEL_ORIGINS, useBackToClose, useFocusOnPlaced, usePanelMotion, useReturnFocus };
|
|
214
|
+
|
|
215
|
+
//# sourceMappingURL=panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panel.js","names":["RefObject","HostInstance","useEffect","useRef","AccessibilityInfo","BackHandler","Platform","Pressable","StyleSheet","Easing","useAnimatedStyle","useReducedMotion","useSharedValue","withTiming","OverlaySide","useTheme","SCALE_FROM","PANEL_ORIGINS","bottom","left","right","top","const","Record","styles","create","layer","position","DismissLayer","t0","$","_c","onPress","t1","useBackToClose","close","active","OS","subscription","addEventListener","remove","useFocusOnPlaced","ready","target","sendAccessibilityEvent","usePanelMotion","isOpen","placed","setPresent","motion","reduceMotion","shown","set","duration","durationShort3","easing","bezier","easingEmphasizedDecelerate","durationShort2","easingEmphasizedAccelerate","timer","setTimeout","clearTimeout","progress","get","opacity","transform","scale","useReturnFocus","present","quiet","trigger","current","gone","_c"],"sources":["../../src/overlay/panel.tsx"],"sourcesContent":["import type { RefObject } from 'react'\nimport type { HostInstance } from 'react-native'\n\nimport { useEffect, useRef } from 'react'\nimport {\n AccessibilityInfo,\n BackHandler,\n Platform,\n Pressable,\n StyleSheet,\n} from 'react-native'\nimport {\n Easing,\n useAnimatedStyle,\n useReducedMotion,\n useSharedValue,\n withTiming,\n} from 'react-native-reanimated'\n\nimport type { OverlaySide } from './placement'\n\nimport { useTheme } from '../theme/scope'\n\n// What a non-modal panel anchored beside its trigger does besides being\n// placed, shared by Popover and Menu so the two cannot drift: the layer a\n// press outside lands on, Android's back gesture, the motion in and out, and\n// where a screen reader is sent as it comes and goes.\n\n// What the panel scales up from as it arrives, web's keyframes'.\nconst SCALE_FROM = 0.95\n\n/** The edge a panel grows from, the one facing its anchor. */\nconst PANEL_ORIGINS = {\n bottom: 'top',\n left: 'right',\n right: 'left',\n top: 'bottom',\n} as const satisfies Record<OverlaySide, string>\n\nconst styles = StyleSheet.create({\n // Over the whole screen and under the panel.\n layer: {\n bottom: 0,\n left: 0,\n position: 'absolute',\n right: 0,\n top: 0,\n },\n})\n\n/**\n * The layer a press outside a non-modal panel lands on, drawn just before the\n * panel so it sits under it. The press goes no further, so a press on the\n * trigger closes the panel rather than opening it again, as on web. Nothing\n * but that press reaches it: React Native Web's Pressable is a tab stop\n * unless told otherwise, whatever `focusable` says.\n */\nfunction DismissLayer({ onPress }: { readonly onPress: () => void }) {\n return (\n <Pressable\n accessible={false}\n aria-hidden\n focusable={false}\n onPress={onPress}\n style={styles.layer}\n tabIndex={-1}\n />\n )\n}\n\n/**\n * Closes a non-modal panel from Android's back gesture, as a `Modal` closes a\n * modal one. React Native calls the latest listener first, so a panel opened\n * from another closes before the one it came from. React Native Web has no\n * back gesture, and its BackHandler reports an error when one is listened for,\n * so nothing listens there.\n */\nfunction useBackToClose(close: () => void, active: boolean) {\n useEffect(() => {\n if (!active || Platform.OS === 'web') {\n return undefined\n }\n\n const subscription = BackHandler.addEventListener(\n 'hardwareBackPress',\n () => {\n close()\n return true\n },\n )\n\n return () => {\n subscription.remove()\n }\n }, [active, close])\n}\n\n/**\n * Sends the screen reader to `target` once `ready`, which a panel passes once\n * it has been placed, and so can be seen. React Native Web has no screen\n * reader of its own to send.\n */\nfunction useFocusOnPlaced(ready: boolean, target: HostInstance | null) {\n useEffect(() => {\n if (ready && target !== null && Platform.OS !== 'web') {\n AccessibilityInfo.sendAccessibilityEvent(target, 'focus')\n }\n }, [ready, target])\n}\n\n/**\n * A panel's motion, and the timer that unmounts it once its exit has run: it\n * fades and scales up from 95% over the short3 duration on the emphasised\n * decelerating curve, and back over short2 on the accelerating one. The\n * entrance waits for the panel to be placed, so it is not spent while the\n * panel is still hidden being measured, and the panel stays transparent\n * until then. Under reduced motion it arrives and leaves at once.\n */\nfunction usePanelMotion(\n isOpen: boolean,\n placed: boolean,\n setPresent: (present: boolean) => void,\n) {\n const { motion } = useTheme()\n const reduceMotion = useReducedMotion()\n const shown = useSharedValue(0)\n\n useEffect(() => {\n if (isOpen && !placed) {\n return undefined\n }\n\n if (isOpen) {\n shown.set(\n reduceMotion\n ? 1\n : withTiming(1, {\n duration: motion.durationShort3,\n easing: Easing.bezier(...motion.easingEmphasizedDecelerate),\n }),\n )\n return undefined\n }\n\n if (reduceMotion) {\n shown.set(0)\n setPresent(false)\n return undefined\n }\n\n shown.set(\n withTiming(0, {\n duration: motion.durationShort2,\n easing: Easing.bezier(...motion.easingEmphasizedAccelerate),\n }),\n )\n const timer = setTimeout(() => {\n setPresent(false)\n }, motion.durationShort2)\n\n return () => {\n clearTimeout(timer)\n }\n }, [isOpen, motion, placed, reduceMotion, setPresent, shown])\n\n return useAnimatedStyle(() => {\n 'worklet'\n const progress = shown.get()\n\n return {\n opacity: progress,\n transform: [{ scale: SCALE_FROM + (1 - SCALE_FROM) * progress }],\n }\n })\n}\n\n/**\n * Sends the screen reader back to the trigger once a panel has gone, unless\n * `quiet` says nothing moved it there, as a pointer resting on a hover\n * trigger does not. React Native Web's `Modal` restores focus itself, and has\n * no screen reader to send otherwise.\n */\nfunction useReturnFocus(\n present: boolean,\n quiet: boolean,\n trigger: RefObject<HostInstance | null>,\n) {\n const shown = useRef(present)\n\n useEffect(() => {\n const gone = shown.current && !present\n const target = trigger.current\n\n shown.current = present\n if (gone && !quiet && target !== null && Platform.OS !== 'web') {\n AccessibilityInfo.sendAccessibilityEvent(target, 'focus')\n }\n }, [quiet, present, trigger])\n}\n\nexport {\n DismissLayer,\n PANEL_ORIGINS,\n useBackToClose,\n useFocusOnPlaced,\n usePanelMotion,\n useReturnFocus,\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/overlay/placement.d.ts
|
|
2
|
+
/** Where along that side it lines up with the anchor. */
|
|
3
|
+
type OverlayAlign = 'center' | 'end' | 'start';
|
|
4
|
+
/** Which side of the anchor a surface opens on. */
|
|
5
|
+
type OverlaySide = 'bottom' | 'left' | 'right' | 'top';
|
|
6
|
+
//#endregion
|
|
7
|
+
export type { OverlayAlign, OverlaySide };
|
|
8
|
+
//# sourceMappingURL=placement.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//#region src/overlay/placement.ts
|
|
2
|
+
const OPPOSITE = {
|
|
3
|
+
bottom: "top",
|
|
4
|
+
left: "right",
|
|
5
|
+
right: "left",
|
|
6
|
+
top: "bottom"
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Where a surface of a given length starts along the anchor, for an edge,
|
|
10
|
+
* moved by the offset. However far the offset moves it, it keeps overlapping
|
|
11
|
+
* the anchor, as React Aria's placement does on web.
|
|
12
|
+
*/
|
|
13
|
+
function along(start, length, size, edge, offset) {
|
|
14
|
+
const lined = edge === "near" ? start : edge === "far" ? start + length - size : start + (length - size) / 2;
|
|
15
|
+
return Math.min(Math.max(lined + offset, start - size), start + length);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Bounds a surface stays inside: the window, less the safe area's insets and
|
|
19
|
+
* a padding on every side.
|
|
20
|
+
*/
|
|
21
|
+
function boundsOf(window, insets, padding) {
|
|
22
|
+
return {
|
|
23
|
+
height: window.height - insets.top - insets.bottom - padding * 2,
|
|
24
|
+
width: window.width - insets.left - insets.right - padding * 2,
|
|
25
|
+
x: insets.left + padding,
|
|
26
|
+
y: insets.top + padding
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function edgeOf(align, inline, rtl) {
|
|
30
|
+
if (align === "center") return "center";
|
|
31
|
+
return align === "start" === (inline && rtl) ? "far" : "near";
|
|
32
|
+
}
|
|
33
|
+
function isVertical(side) {
|
|
34
|
+
return side === "bottom" || side === "top";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Places a surface beside its anchor, as React Aria places a popover on web.
|
|
38
|
+
*
|
|
39
|
+
* It opens on `side`, lined up along it by `align`, and flips to the opposite
|
|
40
|
+
* side when it does not fit and that side has more room. Along the side it is
|
|
41
|
+
* shifted to stay inside the bounds; across it, on `top` and `bottom`, it
|
|
42
|
+
* stays inside them through `maxHeight`, and on `left` and `right`, where a
|
|
43
|
+
* surface wider than both sides has nowhere to go, it is shifted over the
|
|
44
|
+
* anchor rather than past the edge.
|
|
45
|
+
*/
|
|
46
|
+
function place({ align, alignOffset, anchor, bounds, rtl, side: preferred, sideOffset, surface }) {
|
|
47
|
+
const room = (on) => spaceOn(on, anchor, bounds, sideOffset);
|
|
48
|
+
const opposite = OPPOSITE[preferred];
|
|
49
|
+
const side = (isVertical(preferred) ? surface.height : surface.width) > room(preferred) && room(opposite) > room(preferred) ? opposite : preferred;
|
|
50
|
+
if (isVertical(side)) {
|
|
51
|
+
const maxHeight = Math.max(0, room(side));
|
|
52
|
+
const height = Math.min(surface.height, maxHeight);
|
|
53
|
+
return {
|
|
54
|
+
maxHeight,
|
|
55
|
+
side,
|
|
56
|
+
x: within(along(anchor.x, anchor.width, surface.width, edgeOf(align, true, rtl), alignOffset), bounds.x, bounds.width, surface.width),
|
|
57
|
+
y: side === "bottom" ? anchor.y + anchor.height + sideOffset : anchor.y - sideOffset - height
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const maxHeight = Math.max(0, bounds.height);
|
|
61
|
+
const height = Math.min(surface.height, maxHeight);
|
|
62
|
+
const x = side === "right" ? anchor.x + anchor.width + sideOffset : anchor.x - sideOffset - surface.width;
|
|
63
|
+
const y = along(anchor.y, anchor.height, height, edgeOf(align, false, rtl), alignOffset);
|
|
64
|
+
return {
|
|
65
|
+
maxHeight,
|
|
66
|
+
side,
|
|
67
|
+
x: within(x, bounds.x, bounds.width, surface.width),
|
|
68
|
+
y: within(y, bounds.y, bounds.height, height)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function spaceOn(side, anchor, bounds, sideOffset) {
|
|
72
|
+
return {
|
|
73
|
+
bottom: bounds.y + bounds.height - anchor.y - anchor.height,
|
|
74
|
+
left: anchor.x - bounds.x,
|
|
75
|
+
right: bounds.x + bounds.width - anchor.x - anchor.width,
|
|
76
|
+
top: anchor.y - bounds.y
|
|
77
|
+
}[side] - sideOffset;
|
|
78
|
+
}
|
|
79
|
+
function within(position, start, length, size) {
|
|
80
|
+
if (position < start) return start;
|
|
81
|
+
if (position + size > start + length) return Math.max(start + length - size, start);
|
|
82
|
+
return position;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
export { boundsOf, place };
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=placement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placement.js","names":["Edge","OverlayAlign","OverlaySide","Placement","maxHeight","side","x","y","PlacementInput","align","alignOffset","anchor","Rect","bounds","rtl","sideOffset","surface","Size","height","width","OPPOSITE","bottom","left","right","top","const","Record","along","start","length","size","edge","offset","lined","Math","min","max","boundsOf","window","insets","padding","edgeOf","inline","isVertical","place","preferred","room","on","spaceOn","opposite","within","position"],"sources":["../../src/overlay/placement.ts"],"sourcesContent":["// Which end of the anchor the surface lines up with, along the side.\ntype Edge = 'center' | 'far' | 'near'\n\n/** Where along that side it lines up with the anchor. */\ntype OverlayAlign = 'center' | 'end' | 'start'\n\n/** Which side of the anchor a surface opens on. */\ntype OverlaySide = 'bottom' | 'left' | 'right' | 'top'\n\n/** Where an anchored surface sits, in the coordinates its inputs were in. */\ntype Placement = {\n /**\n * The height the surface may take before it would pass the edge of the\n * bounds on the side it opened on. A surface taller than this scrolls.\n */\n readonly maxHeight: number\n /** The side it opened on, which is the opposite one after a flip. */\n readonly side: OverlaySide\n readonly x: number\n readonly y: number\n}\n\ntype PlacementInput = {\n readonly align: OverlayAlign\n /** Moves the surface along the side, in dp: right or down when positive. */\n readonly alignOffset: number\n readonly anchor: Rect\n /** The area the surface stays inside, padding already taken off. */\n readonly bounds: Rect\n /** Whether `start` and `end` read from the right, on `top` and `bottom`. */\n readonly rtl: boolean\n readonly side: OverlaySide\n /** The gap between the anchor and the surface, in dp. */\n readonly sideOffset: number\n readonly surface: Size\n}\n\ntype Rect = Size & { readonly x: number; readonly y: number }\n\ntype Size = { readonly height: number; readonly width: number }\n\nconst OPPOSITE = {\n bottom: 'top',\n left: 'right',\n right: 'left',\n top: 'bottom',\n} as const satisfies Record<OverlaySide, OverlaySide>\n\n/**\n * Where a surface of a given length starts along the anchor, for an edge,\n * moved by the offset. However far the offset moves it, it keeps overlapping\n * the anchor, as React Aria's placement does on web.\n */\nfunction along(\n start: number,\n length: number,\n size: number,\n edge: Edge,\n offset: number,\n) {\n const lined =\n edge === 'near'\n ? start\n : edge === 'far'\n ? start + length - size\n : start + (length - size) / 2\n\n return Math.min(Math.max(lined + offset, start - size), start + length)\n}\n\n/**\n * Bounds a surface stays inside: the window, less the safe area's insets and\n * a padding on every side.\n */\nfunction boundsOf(\n window: Size,\n insets: { bottom: number; left: number; right: number; top: number },\n padding: number,\n): Rect {\n return {\n height: window.height - insets.top - insets.bottom - padding * 2,\n width: window.width - insets.left - insets.right - padding * 2,\n x: insets.left + padding,\n y: insets.top + padding,\n }\n}\n\n// Which end of the anchor an alignment names. On `top` and `bottom` the\n// surface lines up along the inline axis, so `start` is the left edge, or the\n// right one in a right-to-left layout; on `left` and `right` it lines up along\n// the block axis, where `start` is the top edge in both.\nfunction edgeOf(align: OverlayAlign, inline: boolean, rtl: boolean): Edge {\n if (align === 'center') {\n return 'center'\n }\n\n return (align === 'start') === (inline && rtl) ? 'far' : 'near'\n}\n\nfunction isVertical(side: OverlaySide) {\n return side === 'bottom' || side === 'top'\n}\n\n/**\n * Places a surface beside its anchor, as React Aria places a popover on web.\n *\n * It opens on `side`, lined up along it by `align`, and flips to the opposite\n * side when it does not fit and that side has more room. Along the side it is\n * shifted to stay inside the bounds; across it, on `top` and `bottom`, it\n * stays inside them through `maxHeight`, and on `left` and `right`, where a\n * surface wider than both sides has nowhere to go, it is shifted over the\n * anchor rather than past the edge.\n */\nfunction place({\n align,\n alignOffset,\n anchor,\n bounds,\n rtl,\n side: preferred,\n sideOffset,\n surface,\n}: PlacementInput): Placement {\n const room = (on: OverlaySide) => spaceOn(on, anchor, bounds, sideOffset)\n const opposite = OPPOSITE[preferred]\n const length = isVertical(preferred) ? surface.height : surface.width\n const side =\n length > room(preferred) && room(opposite) > room(preferred)\n ? opposite\n : preferred\n\n if (isVertical(side)) {\n const maxHeight = Math.max(0, room(side))\n const height = Math.min(surface.height, maxHeight)\n const x = along(\n anchor.x,\n anchor.width,\n surface.width,\n edgeOf(align, true, rtl),\n alignOffset,\n )\n\n return {\n maxHeight,\n side,\n x: within(x, bounds.x, bounds.width, surface.width),\n y:\n side === 'bottom'\n ? anchor.y + anchor.height + sideOffset\n : anchor.y - sideOffset - height,\n }\n }\n\n const maxHeight = Math.max(0, bounds.height)\n const height = Math.min(surface.height, maxHeight)\n const x =\n side === 'right'\n ? anchor.x + anchor.width + sideOffset\n : anchor.x - sideOffset - surface.width\n const y = along(\n anchor.y,\n anchor.height,\n height,\n edgeOf(align, false, rtl),\n alignOffset,\n )\n\n return {\n maxHeight,\n side,\n x: within(x, bounds.x, bounds.width, surface.width),\n y: within(y, bounds.y, bounds.height, height),\n }\n}\n\n// The room between the anchor and the edge of the bounds on a side, less the\n// gap the surface keeps from the anchor.\nfunction spaceOn(\n side: OverlaySide,\n anchor: Rect,\n bounds: Rect,\n sideOffset: number,\n) {\n const room = {\n bottom: bounds.y + bounds.height - anchor.y - anchor.height,\n left: anchor.x - bounds.x,\n right: bounds.x + bounds.width - anchor.x - anchor.width,\n top: anchor.y - bounds.y,\n }\n\n return room[side] - sideOffset\n}\n\n// Shifts a surface along an axis until it is inside the bounds, or, when it is\n// longer than they are, until it starts where they start.\nfunction within(position: number, start: number, length: number, size: number) {\n if (position < start) {\n return start\n }\n\n if (position + size > start + length) {\n return Math.max(start + length - size, start)\n }\n\n return position\n}\n\nexport type { OverlayAlign, OverlaySide, Placement, PlacementInput, Rect, Size }\n\nexport { boundsOf, place }\n"],"mappings":"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"}
|