panelui-native 0.73.0 → 0.75.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/README.md +3 -1
- package/lib/module/components/avatar/avatar-group.js +64 -0
- package/lib/module/components/avatar/avatar-group.js.map +1 -0
- package/lib/module/components/avatar/index.js +84 -2
- package/lib/module/components/avatar/index.js.map +1 -1
- package/lib/module/components/color-picker/color-picker-accessibility.js +12 -0
- package/lib/module/components/color-picker/color-picker-accessibility.js.map +1 -0
- package/lib/module/components/color-picker/index.js +9 -5
- package/lib/module/components/color-picker/index.js.map +1 -1
- package/lib/module/components/date-time-picker/index.js +11 -3
- package/lib/module/components/date-time-picker/index.js.map +1 -1
- package/lib/module/components/drawer/index.js +62 -13
- package/lib/module/components/drawer/index.js.map +1 -1
- package/lib/module/components/input/index.js +7 -4
- package/lib/module/components/input/index.js.map +1 -1
- package/lib/module/components/input/input-content-padding.js +6 -0
- package/lib/module/components/input/input-content-padding.js.map +1 -0
- package/lib/module/components/input-group/index.js +14 -9
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/input-group/input-group-measurements.js +60 -0
- package/lib/module/components/input-group/input-group-measurements.js.map +1 -0
- package/lib/module/components/otp-input/index.js +3 -1
- package/lib/module/components/otp-input/index.js.map +1 -1
- package/lib/module/components/otp-input/otp-input-state.js +7 -0
- package/lib/module/components/otp-input/otp-input-state.js.map +1 -0
- package/lib/module/components/planner/index.js +20 -6
- package/lib/module/components/planner/index.js.map +1 -1
- package/lib/module/components/planner/planner-today.js +8 -0
- package/lib/module/components/planner/planner-today.js.map +1 -0
- package/lib/module/components/plot/index.js +8 -11
- package/lib/module/components/plot/index.js.map +1 -1
- package/lib/module/components/plot/plot-series-registry.js +32 -0
- package/lib/module/components/plot/plot-series-registry.js.map +1 -0
- package/lib/module/components/radio-group/index.js +2 -1
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/radio-group/radio-group-value.js +8 -0
- package/lib/module/components/radio-group/radio-group-value.js.map +1 -0
- package/lib/module/components/rating/index.js +7 -4
- package/lib/module/components/rating/index.js.map +1 -1
- package/lib/module/components/rating/rating-inputs.js +15 -0
- package/lib/module/components/rating/rating-inputs.js.map +1 -0
- package/lib/module/components/search-bar/index.js +7 -7
- package/lib/module/components/search-bar/index.js.map +1 -1
- package/lib/module/components/search-bar/search-bar-debounce.js +23 -0
- package/lib/module/components/search-bar/search-bar-debounce.js.map +1 -0
- package/lib/module/components/select/index.js +9 -1
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/select/native-select-contract.js +11 -0
- package/lib/module/components/select/native-select-contract.js.map +1 -0
- package/lib/module/components/split-view/index.js +439 -0
- package/lib/module/components/split-view/index.js.map +1 -0
- package/lib/module/components/split-view/split-view-math.js +132 -0
- package/lib/module/components/split-view/split-view-math.js.map +1 -0
- package/lib/module/components/splitter/index.js +433 -0
- package/lib/module/components/splitter/index.js.map +1 -0
- package/lib/module/components/splitter/splitter-math.js +206 -0
- package/lib/module/components/splitter/splitter-math.js.map +1 -0
- package/lib/module/components/steps/index.js +16 -5
- package/lib/module/components/steps/index.js.map +1 -1
- package/lib/module/components/steps/steps-trigger.js +6 -0
- package/lib/module/components/steps/steps-trigger.js.map +1 -0
- package/lib/module/components/tag-input/index.js +15 -10
- package/lib/module/components/tag-input/index.js.map +1 -1
- package/lib/module/components/tag-input/tag-input-state.js +14 -0
- package/lib/module/components/tag-input/tag-input-state.js.map +1 -0
- package/lib/module/components/textarea/index.js +9 -4
- package/lib/module/components/textarea/index.js.map +1 -1
- package/lib/module/components/textarea/textarea-disabled.js +6 -0
- package/lib/module/components/textarea/textarea-disabled.js.map +1 -0
- package/lib/module/components/timeline/index.js +83 -27
- package/lib/module/components/timeline/index.js.map +1 -1
- package/lib/module/components/timeline/timeline-geometry.js +11 -0
- package/lib/module/components/timeline/timeline-geometry.js.map +1 -0
- package/lib/module/components/toast/index.js +18 -1
- package/lib/module/components/toast/index.js.map +1 -1
- package/lib/module/components/toast/toast-store.js +36 -4
- package/lib/module/components/toast/toast-store.js.map +1 -1
- package/lib/module/components/tour/index.js +4 -2
- package/lib/module/components/tour/index.js.map +1 -1
- package/lib/module/components/tour/tour-card-measurement.js +24 -0
- package/lib/module/components/tour/tour-card-measurement.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/avatar/avatar-group.d.ts +46 -0
- package/lib/typescript/src/components/avatar/avatar-group.d.ts.map +1 -0
- package/lib/typescript/src/components/avatar/index.d.ts +23 -1
- package/lib/typescript/src/components/avatar/index.d.ts.map +1 -1
- package/lib/typescript/src/components/color-picker/color-picker-accessibility.d.ts +8 -0
- package/lib/typescript/src/components/color-picker/color-picker-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/color-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/date-time-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input/input-content-padding.d.ts +2 -0
- package/lib/typescript/src/components/input/input-content-padding.d.ts.map +1 -0
- package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input-group/input-group-measurements.d.ts +16 -0
- package/lib/typescript/src/components/input-group/input-group-measurements.d.ts.map +1 -0
- package/lib/typescript/src/components/otp-input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/otp-input/otp-input-state.d.ts +3 -0
- package/lib/typescript/src/components/otp-input/otp-input-state.d.ts.map +1 -0
- package/lib/typescript/src/components/planner/index.d.ts.map +1 -1
- package/lib/typescript/src/components/planner/planner-today.d.ts +2 -0
- package/lib/typescript/src/components/planner/planner-today.d.ts.map +1 -0
- package/lib/typescript/src/components/plot/index.d.ts +1 -1
- package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
- package/lib/typescript/src/components/plot/plot-series-registry.d.ts +8 -0
- package/lib/typescript/src/components/plot/plot-series-registry.d.ts.map +1 -0
- package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/radio-group/radio-group-value.d.ts +2 -0
- package/lib/typescript/src/components/radio-group/radio-group-value.d.ts.map +1 -0
- package/lib/typescript/src/components/rating/index.d.ts.map +1 -1
- package/lib/typescript/src/components/rating/rating-inputs.d.ts +4 -0
- package/lib/typescript/src/components/rating/rating-inputs.d.ts.map +1 -0
- package/lib/typescript/src/components/search-bar/index.d.ts.map +1 -1
- package/lib/typescript/src/components/search-bar/search-bar-debounce.d.ts +7 -0
- package/lib/typescript/src/components/search-bar/search-bar-debounce.d.ts.map +1 -0
- package/lib/typescript/src/components/select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/select/native-select-contract.d.ts +11 -0
- package/lib/typescript/src/components/select/native-select-contract.d.ts.map +1 -0
- package/lib/typescript/src/components/split-view/index.d.ts +159 -0
- package/lib/typescript/src/components/split-view/index.d.ts.map +1 -0
- package/lib/typescript/src/components/split-view/split-view-math.d.ts +54 -0
- package/lib/typescript/src/components/split-view/split-view-math.d.ts.map +1 -0
- package/lib/typescript/src/components/splitter/index.d.ts +78 -0
- package/lib/typescript/src/components/splitter/index.d.ts.map +1 -0
- package/lib/typescript/src/components/splitter/splitter-math.d.ts +67 -0
- package/lib/typescript/src/components/splitter/splitter-math.d.ts.map +1 -0
- package/lib/typescript/src/components/steps/index.d.ts +2 -2
- package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
- package/lib/typescript/src/components/steps/steps-trigger.d.ts +2 -0
- package/lib/typescript/src/components/steps/steps-trigger.d.ts.map +1 -0
- package/lib/typescript/src/components/tag-input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tag-input/tag-input-state.d.ts +12 -0
- package/lib/typescript/src/components/tag-input/tag-input-state.d.ts.map +1 -0
- package/lib/typescript/src/components/textarea/index.d.ts.map +1 -1
- package/lib/typescript/src/components/textarea/textarea-disabled.d.ts +2 -0
- package/lib/typescript/src/components/textarea/textarea-disabled.d.ts.map +1 -0
- package/lib/typescript/src/components/timeline/index.d.ts +1 -0
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
- package/lib/typescript/src/components/timeline/timeline-geometry.d.ts +4 -0
- package/lib/typescript/src/components/timeline/timeline-geometry.d.ts.map +1 -0
- package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/toast-store.d.ts +7 -2
- package/lib/typescript/src/components/toast/toast-store.d.ts.map +1 -1
- package/lib/typescript/src/components/tour/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tour/tour-card-measurement.d.ts +14 -0
- package/lib/typescript/src/components/tour/tour-card-measurement.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/avatar/avatar-group.ts +78 -0
- package/src/components/avatar/index.tsx +121 -1
- package/src/components/color-picker/color-picker-accessibility.ts +12 -0
- package/src/components/color-picker/index.tsx +27 -8
- package/src/components/date-time-picker/index.tsx +11 -3
- package/src/components/drawer/index.tsx +47 -15
- package/src/components/input/index.tsx +6 -2
- package/src/components/input/input-content-padding.ts +3 -0
- package/src/components/input-group/index.tsx +38 -10
- package/src/components/input-group/input-group-measurements.ts +98 -0
- package/src/components/otp-input/index.tsx +3 -1
- package/src/components/otp-input/otp-input-state.ts +7 -0
- package/src/components/planner/index.tsx +24 -6
- package/src/components/planner/planner-today.ts +5 -0
- package/src/components/plot/index.tsx +16 -12
- package/src/components/plot/plot-series-registry.ts +41 -0
- package/src/components/radio-group/index.tsx +4 -1
- package/src/components/radio-group/radio-group-value.ts +9 -0
- package/src/components/rating/index.tsx +11 -4
- package/src/components/rating/rating-inputs.ts +19 -0
- package/src/components/search-bar/index.tsx +12 -7
- package/src/components/search-bar/search-bar-debounce.ts +35 -0
- package/src/components/select/index.tsx +10 -1
- package/src/components/select/native-select-contract.ts +12 -0
- package/src/components/split-view/index.tsx +565 -0
- package/src/components/split-view/split-view-math.ts +146 -0
- package/src/components/splitter/index.tsx +589 -0
- package/src/components/splitter/splitter-math.ts +244 -0
- package/src/components/steps/index.tsx +35 -9
- package/src/components/steps/steps-trigger.ts +6 -0
- package/src/components/tag-input/index.tsx +13 -11
- package/src/components/tag-input/tag-input-state.ts +18 -0
- package/src/components/textarea/index.tsx +8 -4
- package/src/components/textarea/textarea-disabled.ts +6 -0
- package/src/components/timeline/index.tsx +89 -26
- package/src/components/timeline/timeline-geometry.ts +9 -0
- package/src/components/toast/index.tsx +26 -1
- package/src/components/toast/toast-store.ts +43 -8
- package/src/components/tour/index.tsx +11 -3
- package/src/components/tour/tour-card-measurement.ts +29 -0
- package/src/index.ts +17 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The arithmetic behind a stack of avatars: how far each one slides under the
|
|
3
|
+
* one before it, and how many faces are left over once the row is capped.
|
|
4
|
+
*
|
|
5
|
+
* It lives apart from the component because both answers are pure and both are
|
|
6
|
+
* easy to get subtly wrong — a cap of zero that hides everybody, a `total`
|
|
7
|
+
* lower than the number of faces already on screen, a negative overlap that
|
|
8
|
+
* turns a stack into a gap.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Rendered diameter of each avatar size, in points. */
|
|
12
|
+
export const AVATAR_SIZE_POINTS = { sm: 32, md: 40, lg: 56, xl: 80 } as const;
|
|
13
|
+
|
|
14
|
+
export type AvatarSizeName = keyof typeof AVATAR_SIZE_POINTS;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* How much of each avatar the next one covers, as a share of its diameter.
|
|
18
|
+
* A third is enough for the faces to read as one group and to still leave the
|
|
19
|
+
* widest part of every face visible.
|
|
20
|
+
*/
|
|
21
|
+
export const AVATAR_GROUP_OVERLAP_RATIO = 0.32;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Points one avatar slides under its neighbour.
|
|
25
|
+
*
|
|
26
|
+
* Derived from the size rather than fixed, so the stack keeps its proportions
|
|
27
|
+
* across the size ladder instead of looking crowded at `xl` and loose at `sm`.
|
|
28
|
+
* A caller's own number wins, clamped so it can close a stack up entirely but
|
|
29
|
+
* never open it into a gap — that is what a plain row is for.
|
|
30
|
+
*/
|
|
31
|
+
export function avatarGroupOverlap(
|
|
32
|
+
size: AvatarSizeName,
|
|
33
|
+
overlap?: number
|
|
34
|
+
): number {
|
|
35
|
+
const diameter = AVATAR_SIZE_POINTS[size] ?? AVATAR_SIZE_POINTS.md;
|
|
36
|
+
if (overlap === undefined || !Number.isFinite(overlap)) {
|
|
37
|
+
return Math.round(diameter * AVATAR_GROUP_OVERLAP_RATIO);
|
|
38
|
+
}
|
|
39
|
+
return Math.min(Math.max(overlap, 0), diameter);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface AvatarGroupCount {
|
|
43
|
+
/** How many avatars to render. */
|
|
44
|
+
visible: number;
|
|
45
|
+
/** How many are left over, for the trailing count. `0` renders no count. */
|
|
46
|
+
overflow: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Splits a row of faces into the ones shown and the number hidden behind them.
|
|
51
|
+
*
|
|
52
|
+
* `max` counts the faces, not the slots — a cap of three with five people shows
|
|
53
|
+
* three avatars and a `+2`, rather than two and a `+3`.
|
|
54
|
+
*/
|
|
55
|
+
export function avatarGroupCount(
|
|
56
|
+
count: number,
|
|
57
|
+
max?: number,
|
|
58
|
+
total?: number
|
|
59
|
+
): AvatarGroupCount {
|
|
60
|
+
const people = Number.isFinite(count) ? Math.max(Math.trunc(count), 0) : 0;
|
|
61
|
+
|
|
62
|
+
// An unusable cap is no cap: a `max` of zero or NaN reads as "not set", never
|
|
63
|
+
// as "hide everyone", because an empty row is never what a caller meant.
|
|
64
|
+
const capped =
|
|
65
|
+
max !== undefined && Number.isFinite(max) && max >= 1
|
|
66
|
+
? Math.min(Math.trunc(max), people)
|
|
67
|
+
: people;
|
|
68
|
+
|
|
69
|
+
// `total` describes a population the children only sample, so it can only add
|
|
70
|
+
// to the count — a total below what is already on screen is stale, not a
|
|
71
|
+
// reason to claim there are fewer people than the row is showing.
|
|
72
|
+
const population =
|
|
73
|
+
total !== undefined && Number.isFinite(total)
|
|
74
|
+
? Math.max(Math.trunc(total), people)
|
|
75
|
+
: people;
|
|
76
|
+
|
|
77
|
+
return { visible: capped, overflow: Math.max(population - capped, 0) };
|
|
78
|
+
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
forwardRef,
|
|
5
|
+
isValidElement,
|
|
6
|
+
useState,
|
|
7
|
+
type ReactElement,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from 'react';
|
|
2
10
|
import {
|
|
3
11
|
Image,
|
|
4
12
|
View,
|
|
@@ -9,6 +17,11 @@ import {
|
|
|
9
17
|
import { tv, type VariantProps } from 'tailwind-variants';
|
|
10
18
|
import { Text, textChildren } from '../../primitives/text';
|
|
11
19
|
import { cn } from '../../utils/cn';
|
|
20
|
+
import {
|
|
21
|
+
avatarGroupCount,
|
|
22
|
+
avatarGroupOverlap,
|
|
23
|
+
type AvatarSizeName,
|
|
24
|
+
} from './avatar-group';
|
|
12
25
|
|
|
13
26
|
const avatarVariants = tv({
|
|
14
27
|
slots: {
|
|
@@ -111,6 +124,113 @@ const AvatarBadge = forwardRef<View, AvatarBadgeProps>(
|
|
|
111
124
|
);
|
|
112
125
|
AvatarBadge.displayName = 'Avatar.Badge';
|
|
113
126
|
|
|
127
|
+
export interface AvatarGroupProps extends ViewProps {
|
|
128
|
+
className?: string;
|
|
129
|
+
/** Size for every avatar in the stack. A child's own `size` still wins. */
|
|
130
|
+
size?: AvatarSizeName;
|
|
131
|
+
/**
|
|
132
|
+
* How many faces to show. The rest are counted into a trailing `+N`.
|
|
133
|
+
*
|
|
134
|
+
* It caps the faces, not the row: `max={3}` with five people shows three
|
|
135
|
+
* avatars and a `+2`.
|
|
136
|
+
*/
|
|
137
|
+
max?: number;
|
|
138
|
+
/**
|
|
139
|
+
* How many people there are, when the children are only the first few of
|
|
140
|
+
* them. The count is measured against this instead of against the number of
|
|
141
|
+
* children, so a stack of three out of forty reads `+37`.
|
|
142
|
+
*/
|
|
143
|
+
total?: number;
|
|
144
|
+
/** Points each avatar slides under the one before it. Defaults to a third of the size. */
|
|
145
|
+
overlap?: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* A row of avatars, each overlapping the one after it, with the people who did
|
|
150
|
+
* not fit counted at the end.
|
|
151
|
+
*
|
|
152
|
+
* The faces are drawn back to front — the first is on top — which is why the
|
|
153
|
+
* row lays itself out in reverse: later siblings paint over earlier ones, so
|
|
154
|
+
* the stacking order and the reading order pull against each other and one of
|
|
155
|
+
* them has to be flipped. Flipping the layout keeps the children in the order
|
|
156
|
+
* they were written.
|
|
157
|
+
*
|
|
158
|
+
* Each face gets a ring in the page background so it separates from the one
|
|
159
|
+
* underneath whatever surface the stack sits on.
|
|
160
|
+
*
|
|
161
|
+
* ```tsx
|
|
162
|
+
* <Avatar.Group max={3} total={members.length}>
|
|
163
|
+
* {members.map((member) => (
|
|
164
|
+
* <Avatar key={member.id} source={{ uri: member.avatarUrl }} fallback={member.initials} />
|
|
165
|
+
* ))}
|
|
166
|
+
* </Avatar.Group>
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
const AvatarGroup = forwardRef<View, AvatarGroupProps>(
|
|
170
|
+
({ className, size = 'md', max, total, overlap, children, ...props }, ref) => {
|
|
171
|
+
const faces = Children.toArray(children).filter(isValidElement) as ReactElement<AvatarProps>[];
|
|
172
|
+
const { visible, overflow } = avatarGroupCount(faces.length, max, total);
|
|
173
|
+
const slide = avatarGroupOverlap(size, overlap);
|
|
174
|
+
|
|
175
|
+
// Back to front: the count sits under the last face, which sits under the
|
|
176
|
+
// one before it, and so on up to the first.
|
|
177
|
+
const stack: { key: string; node: ReactNode }[] = [];
|
|
178
|
+
|
|
179
|
+
if (overflow > 0) {
|
|
180
|
+
stack.push({
|
|
181
|
+
key: 'overflow',
|
|
182
|
+
node: (
|
|
183
|
+
<AvatarRoot
|
|
184
|
+
size={size}
|
|
185
|
+
fallback={`+${overflow}`}
|
|
186
|
+
accessibilityLabel={`${overflow} more`}
|
|
187
|
+
className="border-2 border-background"
|
|
188
|
+
/>
|
|
189
|
+
),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
for (let index = visible - 1; index >= 0; index -= 1) {
|
|
194
|
+
const face = faces[index]!;
|
|
195
|
+
stack.push({
|
|
196
|
+
key: String(face.key ?? index),
|
|
197
|
+
node: cloneElement(face, {
|
|
198
|
+
size: face.props.size ?? size,
|
|
199
|
+
className: cn('border-2 border-background', face.props.className),
|
|
200
|
+
}),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<View
|
|
206
|
+
ref={ref}
|
|
207
|
+
accessibilityRole="list"
|
|
208
|
+
className={cn('flex-row-reverse self-start', className)}
|
|
209
|
+
{...props}
|
|
210
|
+
>
|
|
211
|
+
{stack.map((entry, index) => (
|
|
212
|
+
// The first node laid out is the trailing one, and it is the only one
|
|
213
|
+
// with nothing to slide under.
|
|
214
|
+
<View key={entry.key} style={index === 0 ? undefined : { marginEnd: -slide }}>
|
|
215
|
+
{entry.node}
|
|
216
|
+
</View>
|
|
217
|
+
))}
|
|
218
|
+
</View>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
AvatarGroup.displayName = 'Avatar.Group';
|
|
223
|
+
|
|
114
224
|
export const Avatar = Object.assign(AvatarRoot, {
|
|
115
225
|
Badge: AvatarBadge,
|
|
226
|
+
Group: AvatarGroup,
|
|
116
227
|
});
|
|
228
|
+
|
|
229
|
+
export {
|
|
230
|
+
AVATAR_GROUP_OVERLAP_RATIO,
|
|
231
|
+
AVATAR_SIZE_POINTS,
|
|
232
|
+
avatarGroupCount,
|
|
233
|
+
avatarGroupOverlap,
|
|
234
|
+
type AvatarGroupCount,
|
|
235
|
+
type AvatarSizeName,
|
|
236
|
+
} from './avatar-group';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns an accessibility action only while the picker is interactive.
|
|
3
|
+
*
|
|
4
|
+
* React Native can still deliver an action to a disabled adjustable view, so
|
|
5
|
+
* the handler has to enforce the same disabled boundary as its touch gesture.
|
|
6
|
+
*/
|
|
7
|
+
export function enabledColorPickerAction(
|
|
8
|
+
actionName: string,
|
|
9
|
+
disabled: boolean
|
|
10
|
+
): string | undefined {
|
|
11
|
+
return disabled ? undefined : actionName;
|
|
12
|
+
}
|
|
@@ -108,6 +108,7 @@ import {
|
|
|
108
108
|
import { cn } from '../../utils/cn';
|
|
109
109
|
import { selectionTick } from '../../utils/haptics';
|
|
110
110
|
import { Popover, type PopoverContentProps } from '../popover';
|
|
111
|
+
import { enabledColorPickerAction } from './color-picker-accessibility';
|
|
111
112
|
|
|
112
113
|
/** Settles a thumb that was moved by something other than a finger. */
|
|
113
114
|
const TIMING = { duration: 140 } as const;
|
|
@@ -586,7 +587,10 @@ const ColorPickerArea = forwardRef<View, ColorPickerAreaProps>(
|
|
|
586
587
|
};
|
|
587
588
|
|
|
588
589
|
const onAccessibilityAction = (event: AccessibilityActionEvent) => {
|
|
589
|
-
const action =
|
|
590
|
+
const action = enabledColorPickerAction(
|
|
591
|
+
event.nativeEvent.actionName,
|
|
592
|
+
ctx.disabled
|
|
593
|
+
);
|
|
590
594
|
if (action === 'increment') nudge('saturation', 1);
|
|
591
595
|
else if (action === 'decrement') nudge('saturation', -1);
|
|
592
596
|
else if (action === 'brighter') nudge('brightness', 1);
|
|
@@ -829,8 +833,12 @@ const ColorPickerHue = forwardRef<View, ColorPickerHueProps>(
|
|
|
829
833
|
disabled={ctx.disabled}
|
|
830
834
|
accessibilityValue={{ min: 0, max: 360, now: announced, text: `${announced}°` }}
|
|
831
835
|
onAccessibilityAction={(event) => {
|
|
832
|
-
|
|
833
|
-
|
|
836
|
+
const action = enabledColorPickerAction(
|
|
837
|
+
event.nativeEvent.actionName,
|
|
838
|
+
ctx.disabled
|
|
839
|
+
);
|
|
840
|
+
if (action === 'increment') nudge(1);
|
|
841
|
+
else if (action === 'decrement') nudge(-1);
|
|
834
842
|
}}
|
|
835
843
|
>
|
|
836
844
|
<LinearGradient
|
|
@@ -948,8 +956,12 @@ const ColorPickerAlpha = forwardRef<View, ColorPickerAlphaProps>(
|
|
|
948
956
|
text: `${announced}%`,
|
|
949
957
|
}}
|
|
950
958
|
onAccessibilityAction={(event) => {
|
|
951
|
-
|
|
952
|
-
|
|
959
|
+
const action = enabledColorPickerAction(
|
|
960
|
+
event.nativeEvent.actionName,
|
|
961
|
+
ctx.disabled
|
|
962
|
+
);
|
|
963
|
+
if (action === 'increment') nudge(1);
|
|
964
|
+
else if (action === 'decrement') nudge(-1);
|
|
953
965
|
}}
|
|
954
966
|
>
|
|
955
967
|
<Checkerboard />
|
|
@@ -1504,8 +1516,12 @@ const ColorPickerBrightness = forwardRef<View, ColorPickerBrightnessProps>(
|
|
|
1504
1516
|
text: `${announced}%`,
|
|
1505
1517
|
}}
|
|
1506
1518
|
onAccessibilityAction={(event) => {
|
|
1507
|
-
|
|
1508
|
-
|
|
1519
|
+
const action = enabledColorPickerAction(
|
|
1520
|
+
event.nativeEvent.actionName,
|
|
1521
|
+
ctx.disabled
|
|
1522
|
+
);
|
|
1523
|
+
if (action === 'increment') nudge(1);
|
|
1524
|
+
else if (action === 'decrement') nudge(-1);
|
|
1509
1525
|
}}
|
|
1510
1526
|
>
|
|
1511
1527
|
<LinearGradient
|
|
@@ -1667,7 +1683,10 @@ const ColorPickerWheel = forwardRef<View, ColorPickerWheelProps>(
|
|
|
1667
1683
|
};
|
|
1668
1684
|
|
|
1669
1685
|
const onAccessibilityAction = (event: AccessibilityActionEvent) => {
|
|
1670
|
-
const action =
|
|
1686
|
+
const action = enabledColorPickerAction(
|
|
1687
|
+
event.nativeEvent.actionName,
|
|
1688
|
+
ctx.disabled
|
|
1689
|
+
);
|
|
1671
1690
|
if (action === 'increment') nudge('hue', 1);
|
|
1672
1691
|
else if (action === 'decrement') nudge('hue', -1);
|
|
1673
1692
|
else if (action === 'saturate') nudge('saturation', 1);
|
|
@@ -84,6 +84,9 @@ const DEFAULT_PLACEHOLDER = 'Pick a date and time';
|
|
|
84
84
|
*/
|
|
85
85
|
const PANEL_WIDTH = 308;
|
|
86
86
|
|
|
87
|
+
/** A stable rule that makes every calendar day unavailable while disabled. */
|
|
88
|
+
const DISABLE_ALL_DATES = () => true;
|
|
89
|
+
|
|
87
90
|
/** How a `Date` is written on the closed trigger. */
|
|
88
91
|
function describe(
|
|
89
92
|
value: Date,
|
|
@@ -204,18 +207,23 @@ function DateTimePickerRoot({
|
|
|
204
207
|
|
|
205
208
|
const setOpen = useCallback(
|
|
206
209
|
(next: boolean) => {
|
|
210
|
+
if (disabled && next) return;
|
|
207
211
|
if (!isOpenControlled) setInternalOpen(next);
|
|
208
212
|
onOpenChange?.(next);
|
|
209
213
|
},
|
|
210
|
-
[isOpenControlled, onOpenChange]
|
|
214
|
+
[disabled, isOpenControlled, onOpenChange]
|
|
211
215
|
);
|
|
212
216
|
|
|
213
217
|
const commit = useCallback(
|
|
214
218
|
(next: Date) => {
|
|
219
|
+
// A controlled caller can keep the panel mounted or open while disabled.
|
|
220
|
+
// Guard the value boundary as well as the visible controls so a stale
|
|
221
|
+
// press cannot commit during the same render that disables the picker.
|
|
222
|
+
if (disabled) return;
|
|
215
223
|
if (!isValueControlled) setInternalValue(next);
|
|
216
224
|
onValueChange?.(next);
|
|
217
225
|
},
|
|
218
|
-
[isValueControlled, onValueChange]
|
|
226
|
+
[disabled, isValueControlled, onValueChange]
|
|
219
227
|
);
|
|
220
228
|
|
|
221
229
|
/*
|
|
@@ -281,7 +289,7 @@ function DateTimePickerRoot({
|
|
|
281
289
|
mode="single"
|
|
282
290
|
selected={value}
|
|
283
291
|
onSelect={handleDay}
|
|
284
|
-
disabled={disabledDates}
|
|
292
|
+
disabled={disabled ? DISABLE_ALL_DATES : disabledDates}
|
|
285
293
|
minDate={minDate}
|
|
286
294
|
maxDate={maxDate}
|
|
287
295
|
startMonth={minDate}
|
|
@@ -66,14 +66,11 @@ import {
|
|
|
66
66
|
} from 'react-native';
|
|
67
67
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
68
68
|
import Animated, {
|
|
69
|
+
Easing,
|
|
69
70
|
SlideInDown,
|
|
70
71
|
SlideInLeft,
|
|
71
72
|
SlideInRight,
|
|
72
73
|
SlideInUp,
|
|
73
|
-
SlideOutDown,
|
|
74
|
-
SlideOutLeft,
|
|
75
|
-
SlideOutRight,
|
|
76
|
-
SlideOutUp,
|
|
77
74
|
runOnJS,
|
|
78
75
|
useAnimatedStyle,
|
|
79
76
|
useSharedValue,
|
|
@@ -96,6 +93,9 @@ import {
|
|
|
96
93
|
import { cn } from '../../utils/cn';
|
|
97
94
|
|
|
98
95
|
const SPRING = { damping: 24, stiffness: 300, mass: 0.7 } as const;
|
|
96
|
+
/** How long the panel takes to leave, whether by a drag or a press. */
|
|
97
|
+
const EXIT_DURATION = 200;
|
|
98
|
+
|
|
99
99
|
/** How far a drag has to travel outward before releasing it dismisses. */
|
|
100
100
|
const DISMISS_DISTANCE = 80;
|
|
101
101
|
/** A flick this fast dismisses regardless of how far it got. */
|
|
@@ -375,9 +375,11 @@ function DrawerContent({
|
|
|
375
375
|
const velocity =
|
|
376
376
|
(horizontal ? event.velocityX : event.velocityY) * outward;
|
|
377
377
|
if (travel.value > DISMISS_DISTANCE || velocity > DISMISS_VELOCITY) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
378
|
+
// Straight to the exit, without finishing the slide first. The exit
|
|
379
|
+
// starts from wherever `travel` is and carries it the rest of the
|
|
380
|
+
// way, so driving it to `extent` here would be the same distance
|
|
381
|
+
// animated twice.
|
|
382
|
+
runOnJS(close)();
|
|
381
383
|
} else {
|
|
382
384
|
travel.value = withSpring(0, SPRING);
|
|
383
385
|
}
|
|
@@ -413,6 +415,43 @@ function DrawerContent({
|
|
|
413
415
|
[closeCorner, showClose]
|
|
414
416
|
);
|
|
415
417
|
|
|
418
|
+
/*
|
|
419
|
+
* The slide out is written by hand, because the panel is not always at rest
|
|
420
|
+
* when it starts.
|
|
421
|
+
*
|
|
422
|
+
* A preset animates from the view's *layout* position and applies its own
|
|
423
|
+
* transform, which overrides the one the drag was already using. So a
|
|
424
|
+
* swipe-dismiss played twice: the gesture carried the panel off-screen, and
|
|
425
|
+
* then the exit put it back at zero and slid it out again. Seeding the
|
|
426
|
+
* initial transform from `travel` makes the two one animation — the exit
|
|
427
|
+
* picks the panel up wherever the finger left it and carries it the rest of
|
|
428
|
+
* the way. A press on the close button or the backdrop leaves `travel` at
|
|
429
|
+
* zero, so that path is the same slide it always was.
|
|
430
|
+
*
|
|
431
|
+
* Above the early return, with every other hook. It reads nothing that is
|
|
432
|
+
* only known once the drawer is open, and a hook below that return is one
|
|
433
|
+
* that runs on an open drawer and not on a closed one.
|
|
434
|
+
*/
|
|
435
|
+
const exiting = useCallback(() => {
|
|
436
|
+
'worklet';
|
|
437
|
+
const shift = extent * outward;
|
|
438
|
+
const from = travel.value * outward;
|
|
439
|
+
const timing = { duration: EXIT_DURATION, easing: Easing.out(Easing.quad) };
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
initialValues: {
|
|
443
|
+
transform: horizontal
|
|
444
|
+
? [{ translateX: from }, { translateY: 0 }]
|
|
445
|
+
: [{ translateX: 0 }, { translateY: from }],
|
|
446
|
+
},
|
|
447
|
+
animations: {
|
|
448
|
+
transform: horizontal
|
|
449
|
+
? [{ translateX: withTiming(shift, timing) }, { translateY: 0 }]
|
|
450
|
+
: [{ translateX: 0 }, { translateY: withTiming(shift, timing) }],
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
}, [extent, horizontal, outward, travel]);
|
|
454
|
+
|
|
416
455
|
if (!open) return null;
|
|
417
456
|
|
|
418
457
|
/*
|
|
@@ -433,13 +472,6 @@ function DrawerContent({
|
|
|
433
472
|
bottom: SlideInDown,
|
|
434
473
|
}[physical];
|
|
435
474
|
|
|
436
|
-
const exiting = {
|
|
437
|
-
left: SlideOutLeft,
|
|
438
|
-
right: SlideOutRight,
|
|
439
|
-
top: SlideOutUp,
|
|
440
|
-
bottom: SlideOutDown,
|
|
441
|
-
}[physical];
|
|
442
|
-
|
|
443
475
|
/*
|
|
444
476
|
* The inset and the panel's own padding stack rather than compete.
|
|
445
477
|
*
|
|
@@ -499,7 +531,7 @@ function DrawerContent({
|
|
|
499
531
|
.damping(24)
|
|
500
532
|
.stiffness(260)
|
|
501
533
|
.mass(0.7)}
|
|
502
|
-
exiting={exiting
|
|
534
|
+
exiting={exiting}
|
|
503
535
|
accessibilityViewIsModal
|
|
504
536
|
className={panelVariants({ side, className })}
|
|
505
537
|
{...props}
|
|
@@ -59,6 +59,7 @@ import { IconColorProvider } from '../../icons';
|
|
|
59
59
|
import { KeyboardAvoider } from '../../primitives/keyboard-avoider';
|
|
60
60
|
import { Text, textChildren } from '../../primitives/text';
|
|
61
61
|
import { Label } from '../label';
|
|
62
|
+
import { inputContentPadding } from './input-content-padding';
|
|
62
63
|
|
|
63
64
|
/** Long enough to read as a transition, short enough not to lag a fast tab. */
|
|
64
65
|
const FOCUS_DURATION = 150;
|
|
@@ -303,6 +304,9 @@ export const Input = forwardRef<TextInput, InputProps>(
|
|
|
303
304
|
setEndWidth(event.nativeEvent.layout.width);
|
|
304
305
|
}, []);
|
|
305
306
|
|
|
307
|
+
const startPadding = inputContentPadding(startWidth, !!startContent);
|
|
308
|
+
const endPadding = inputContentPadding(endWidth, !!endContent);
|
|
309
|
+
|
|
306
310
|
/*
|
|
307
311
|
* The content is quieter than the value. An icon at the same weight as the
|
|
308
312
|
* text reads as part of what was typed, and the placeholder colour is
|
|
@@ -325,8 +329,8 @@ export const Input = forwardRef<TextInput, InputProps>(
|
|
|
325
329
|
borderStyle,
|
|
326
330
|
// Only the side that has content, and only once it has been
|
|
327
331
|
// measured — a 0 here would wipe out the field's own padding.
|
|
328
|
-
|
|
329
|
-
|
|
332
|
+
startPadding ? { paddingStart: startPadding } : null,
|
|
333
|
+
endPadding ? { paddingEnd: endPadding } : null,
|
|
330
334
|
// Caller styles come last so InputGroup can pad the field around its
|
|
331
335
|
// decorators, but never last enough to drop the animated border.
|
|
332
336
|
style,
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
useCallback,
|
|
12
12
|
useContext,
|
|
13
13
|
useMemo,
|
|
14
|
-
useState,
|
|
15
14
|
type ReactNode,
|
|
16
15
|
} from 'react';
|
|
17
16
|
import {
|
|
@@ -23,6 +22,12 @@ import {
|
|
|
23
22
|
import { tv } from 'tailwind-variants';
|
|
24
23
|
import { Input, type InputProps } from '../input';
|
|
25
24
|
import { textChildren } from '../../primitives/text';
|
|
25
|
+
import {
|
|
26
|
+
useInputGroupDecoratorMeasurement,
|
|
27
|
+
useInputGroupMeasurements,
|
|
28
|
+
type InputGroupDecoratorOwner,
|
|
29
|
+
type InputGroupDecoratorSide,
|
|
30
|
+
} from './input-group-measurements';
|
|
26
31
|
|
|
27
32
|
const inputGroupVariants = tv({
|
|
28
33
|
slots: {
|
|
@@ -41,8 +46,15 @@ interface InputGroupState {
|
|
|
41
46
|
isDisabled: boolean;
|
|
42
47
|
prefixWidth: number;
|
|
43
48
|
suffixWidth: number;
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
measureDecorator: (
|
|
50
|
+
side: InputGroupDecoratorSide,
|
|
51
|
+
owner: InputGroupDecoratorOwner,
|
|
52
|
+
width: number
|
|
53
|
+
) => void;
|
|
54
|
+
removeDecorator: (
|
|
55
|
+
side: InputGroupDecoratorSide,
|
|
56
|
+
owner: InputGroupDecoratorOwner
|
|
57
|
+
) => void;
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
const InputGroupContext = createContext<InputGroupState | null>(null);
|
|
@@ -61,12 +73,24 @@ export interface InputGroupProps extends ViewProps {
|
|
|
61
73
|
const InputGroupRoot = forwardRef<View, InputGroupProps>(
|
|
62
74
|
({ className, isDisabled = false, children, ...props }, ref) => {
|
|
63
75
|
const { root } = inputGroupVariants();
|
|
64
|
-
const
|
|
65
|
-
|
|
76
|
+
const { prefixWidth, suffixWidth, measureDecorator, removeDecorator } =
|
|
77
|
+
useInputGroupMeasurements();
|
|
66
78
|
|
|
67
79
|
const value = useMemo<InputGroupState>(
|
|
68
|
-
() => ({
|
|
69
|
-
|
|
80
|
+
() => ({
|
|
81
|
+
isDisabled,
|
|
82
|
+
prefixWidth,
|
|
83
|
+
suffixWidth,
|
|
84
|
+
measureDecorator,
|
|
85
|
+
removeDecorator,
|
|
86
|
+
}),
|
|
87
|
+
[
|
|
88
|
+
isDisabled,
|
|
89
|
+
prefixWidth,
|
|
90
|
+
suffixWidth,
|
|
91
|
+
measureDecorator,
|
|
92
|
+
removeDecorator,
|
|
93
|
+
]
|
|
70
94
|
);
|
|
71
95
|
|
|
72
96
|
return (
|
|
@@ -97,14 +121,18 @@ function createDecorator(side: 'prefix' | 'suffix') {
|
|
|
97
121
|
({ className, isDecorative = false, onLayout, children, ...props }, ref) => {
|
|
98
122
|
const group = useInputGroup();
|
|
99
123
|
const slots = inputGroupVariants({ isDisabled: group?.isDisabled ?? false });
|
|
100
|
-
const
|
|
124
|
+
const measure = useInputGroupDecoratorMeasurement(
|
|
125
|
+
side,
|
|
126
|
+
group?.measureDecorator,
|
|
127
|
+
group?.removeDecorator
|
|
128
|
+
);
|
|
101
129
|
|
|
102
130
|
const handleLayout = useCallback(
|
|
103
131
|
(event: LayoutChangeEvent) => {
|
|
104
|
-
|
|
132
|
+
measure(event.nativeEvent.layout.width);
|
|
105
133
|
onLayout?.(event);
|
|
106
134
|
},
|
|
107
|
-
[
|
|
135
|
+
[measure, onLayout]
|
|
108
136
|
);
|
|
109
137
|
|
|
110
138
|
return (
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export type InputGroupDecoratorSide = 'prefix' | 'suffix';
|
|
4
|
+
export type InputGroupDecoratorOwner = symbol;
|
|
5
|
+
|
|
6
|
+
type Widths = Record<InputGroupDecoratorSide, number>;
|
|
7
|
+
type Registries = Record<
|
|
8
|
+
InputGroupDecoratorSide,
|
|
9
|
+
Map<InputGroupDecoratorOwner, number>
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
export interface InputGroupMeasurements {
|
|
13
|
+
prefixWidth: number;
|
|
14
|
+
suffixWidth: number;
|
|
15
|
+
measureDecorator: (
|
|
16
|
+
side: InputGroupDecoratorSide,
|
|
17
|
+
owner: InputGroupDecoratorOwner,
|
|
18
|
+
width: number
|
|
19
|
+
) => void;
|
|
20
|
+
removeDecorator: (
|
|
21
|
+
side: InputGroupDecoratorSide,
|
|
22
|
+
owner: InputGroupDecoratorOwner
|
|
23
|
+
) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type MeasureDecorator = InputGroupMeasurements['measureDecorator'];
|
|
27
|
+
type RemoveDecorator = InputGroupMeasurements['removeDecorator'];
|
|
28
|
+
|
|
29
|
+
const emptyWidths = (): Widths => ({ prefix: 0, suffix: 0 });
|
|
30
|
+
|
|
31
|
+
function measuredWidth(registry: Map<InputGroupDecoratorOwner, number>) {
|
|
32
|
+
let width = 0;
|
|
33
|
+
for (const candidate of registry.values()) width = Math.max(width, candidate);
|
|
34
|
+
return width;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Keeps measured padding tied to the decorators that are still mounted. */
|
|
38
|
+
export function useInputGroupMeasurements(): InputGroupMeasurements {
|
|
39
|
+
const registries = useRef<Registries>({ prefix: new Map(), suffix: new Map() });
|
|
40
|
+
const [widths, setWidths] = useState<Widths>(emptyWidths);
|
|
41
|
+
|
|
42
|
+
const commitWidth = useCallback((side: InputGroupDecoratorSide) => {
|
|
43
|
+
const width = measuredWidth(registries.current[side]);
|
|
44
|
+
setWidths((current) =>
|
|
45
|
+
current[side] === width ? current : { ...current, [side]: width }
|
|
46
|
+
);
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const measureDecorator = useCallback(
|
|
50
|
+
(side: InputGroupDecoratorSide, owner: InputGroupDecoratorOwner, width: number) => {
|
|
51
|
+
const registry = registries.current[side];
|
|
52
|
+
if (Number.isFinite(width) && width > 0) registry.set(owner, width);
|
|
53
|
+
else registry.delete(owner);
|
|
54
|
+
commitWidth(side);
|
|
55
|
+
},
|
|
56
|
+
[commitWidth]
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const removeDecorator = useCallback(
|
|
60
|
+
(side: InputGroupDecoratorSide, owner: InputGroupDecoratorOwner) => {
|
|
61
|
+
if (!registries.current[side].delete(owner)) return;
|
|
62
|
+
commitWidth(side);
|
|
63
|
+
},
|
|
64
|
+
[commitWidth]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
prefixWidth: widths.prefix,
|
|
69
|
+
suffixWidth: widths.suffix,
|
|
70
|
+
measureDecorator,
|
|
71
|
+
removeDecorator,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Gives one mounted decorator an owner token and unregisters only that owner. */
|
|
76
|
+
export function useInputGroupDecoratorMeasurement(
|
|
77
|
+
side: InputGroupDecoratorSide,
|
|
78
|
+
measureDecorator: MeasureDecorator | undefined,
|
|
79
|
+
removeDecorator: RemoveDecorator | undefined
|
|
80
|
+
) {
|
|
81
|
+
const owner = useRef<InputGroupDecoratorOwner>(Symbol(side));
|
|
82
|
+
const width = useRef(0);
|
|
83
|
+
|
|
84
|
+
useLayoutEffect(() => {
|
|
85
|
+
// Re-register the last layout if React replays effects in Strict Mode or
|
|
86
|
+
// the decorator moves between InputGroup providers.
|
|
87
|
+
if (width.current > 0) measureDecorator?.(side, owner.current, width.current);
|
|
88
|
+
return () => removeDecorator?.(side, owner.current);
|
|
89
|
+
}, [measureDecorator, removeDecorator, side]);
|
|
90
|
+
|
|
91
|
+
return useCallback(
|
|
92
|
+
(nextWidth: number) => {
|
|
93
|
+
width.current = nextWidth;
|
|
94
|
+
measureDecorator?.(side, owner.current, nextWidth);
|
|
95
|
+
},
|
|
96
|
+
[measureDecorator, side]
|
|
97
|
+
);
|
|
98
|
+
}
|