@uniflowed/ui 0.0.0-alpha.40 → 0.0.0-alpha.42
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/accordion.js +2 -0
- package/alert-dialog.js +2 -0
- package/avatar.js +4 -0
- package/calendar.js +8 -8
- package/collapsible.js +2 -0
- package/combobox.js +26 -7
- package/context-menu.js +12 -3
- package/date-picker.js +11 -0
- package/dialog.js +6 -0
- package/drawer.js +38 -24
- package/hover-card.js +26 -22
- package/interactions.js +4 -0
- package/internal/menu-tree.js +3 -3
- package/internal/roving-focus.js +2 -0
- package/menu.js +39 -6
- package/menubar.js +8 -1
- package/package.json +5 -5
- package/popover.js +23 -0
- package/radio-group.js +2 -0
- package/resizable.js +6 -0
- package/scroll-area.js +13 -13
- package/select.js +23 -10
- package/sidebar.js +9 -2
- package/skeleton.js +4 -0
- package/slider.js +6 -0
- package/toast.js +13 -11
- package/toggle-group.js +2 -0
- package/tooltip.js +19 -15
package/toast.js
CHANGED
|
@@ -356,26 +356,26 @@ export component ToastRegion(
|
|
|
356
356
|
...rest: Rest
|
|
357
357
|
) {
|
|
358
358
|
const queued = useSyncExternalStore(subscribeToQueue, readQueue, readQueue);
|
|
359
|
-
const
|
|
359
|
+
const regionRef = useElementRef<HTMLElement>();
|
|
360
360
|
// Where `F6` came from, so pressing it again gives focus back.
|
|
361
|
-
const
|
|
361
|
+
const cameFromRef = useRef<HTMLElement | null>(null);
|
|
362
362
|
const passed = withoutComposed(rest, ["ref"]);
|
|
363
363
|
|
|
364
364
|
useKeyCombo(
|
|
365
365
|
"f6",
|
|
366
366
|
() => {
|
|
367
|
-
const element =
|
|
367
|
+
const element = regionRef.current;
|
|
368
368
|
if (element == null) {
|
|
369
369
|
return;
|
|
370
370
|
}
|
|
371
371
|
const active: $FlowFixMe = element.ownerDocument.activeElement;
|
|
372
372
|
if (active != null && element.contains(active)) {
|
|
373
|
-
const back =
|
|
374
|
-
|
|
373
|
+
const back = cameFromRef.current;
|
|
374
|
+
cameFromRef.current = null;
|
|
375
375
|
back?.focus?.();
|
|
376
376
|
return;
|
|
377
377
|
}
|
|
378
|
-
|
|
378
|
+
cameFromRef.current = active;
|
|
379
379
|
element.focus();
|
|
380
380
|
},
|
|
381
381
|
// The one case that matters is a notification arriving while the reader is
|
|
@@ -400,7 +400,7 @@ export component ToastRegion(
|
|
|
400
400
|
{...passed}
|
|
401
401
|
aria-label={label}
|
|
402
402
|
ref={composeRefs(rest.ref, (element) => {
|
|
403
|
-
|
|
403
|
+
regionRef.current = element;
|
|
404
404
|
})}
|
|
405
405
|
role="region"
|
|
406
406
|
// So `F6` can put focus on the region itself, including while it is
|
|
@@ -430,13 +430,13 @@ export component ToastRegion(
|
|
|
430
430
|
export component ToastRoot(children: React.Node, ...rest: Rest) {
|
|
431
431
|
const notification = useNotification("Toast.Root");
|
|
432
432
|
const base = useId();
|
|
433
|
-
const
|
|
433
|
+
const elementRef = useElementRef<HTMLElement>();
|
|
434
434
|
const [titled, setTitled] = useState(false);
|
|
435
435
|
const [described, setDescribed] = useState(false);
|
|
436
436
|
const passed = withoutComposed(rest, ["ref"]);
|
|
437
437
|
|
|
438
|
-
const hovered = useHover(
|
|
439
|
-
const focusInside = useFocusWithin(
|
|
438
|
+
const hovered = useHover(elementRef);
|
|
439
|
+
const focusInside = useFocusWithin(elementRef);
|
|
440
440
|
const documentVisible = useDocumentVisible();
|
|
441
441
|
const paused = hovered || focusInside || !documentVisible;
|
|
442
442
|
|
|
@@ -465,6 +465,8 @@ export component ToastRoot(children: React.Node, ...rest: Rest) {
|
|
|
465
465
|
// A notification whose duration was changed under it — "Uploading…" with no
|
|
466
466
|
// duration becoming "Uploaded" with one — starts its countdown from there.
|
|
467
467
|
useEffect(() => {
|
|
468
|
+
// The timeout duration is state because render hands it to useTimeout.
|
|
469
|
+
// uf-lint-disable-next-line react-compiler/set-state-in-effect
|
|
468
470
|
setLeft(duration);
|
|
469
471
|
startedAt.current = Date.now();
|
|
470
472
|
}, [duration]);
|
|
@@ -486,7 +488,7 @@ export component ToastRoot(children: React.Node, ...rest: Rest) {
|
|
|
486
488
|
aria-describedby={described ? parts.descriptionId : undefined}
|
|
487
489
|
aria-labelledby={titled ? parts.titleId : undefined}
|
|
488
490
|
ref={composeRefs(rest.ref, (node) => {
|
|
489
|
-
|
|
491
|
+
elementRef.current = node;
|
|
490
492
|
})}
|
|
491
493
|
role="group"
|
|
492
494
|
>
|
package/toggle-group.js
CHANGED
|
@@ -201,6 +201,8 @@ export component ToggleGroupRoot(
|
|
|
201
201
|
// dozen commands on the way to the one the reader wanted.
|
|
202
202
|
moveOnKey(event, group, toggleSet(orientation));
|
|
203
203
|
}),
|
|
204
|
+
// React calls callback refs during commit; keyboard handlers read it later.
|
|
205
|
+
// uf-lint-disable-next-line react-compiler/refs
|
|
204
206
|
ref: composeRefs(rest.ref, (element: HTMLElement | null) => {
|
|
205
207
|
rootRef.current = element;
|
|
206
208
|
}),
|
package/tooltip.js
CHANGED
|
@@ -126,7 +126,7 @@ type TooltipState = {|
|
|
|
126
126
|
* cannot be closed. It is cleared when the reader leaves the trigger, so
|
|
127
127
|
* coming back opens it again.
|
|
128
128
|
*/
|
|
129
|
-
readonly
|
|
129
|
+
readonly dismissedRef: { current: boolean },
|
|
130
130
|
|};
|
|
131
131
|
|
|
132
132
|
const TooltipContext: React.Context<TooltipState | null> = createContext(null);
|
|
@@ -180,7 +180,7 @@ export component TooltipRoot(
|
|
|
180
180
|
const scope = useContext(TooltipScopeContext);
|
|
181
181
|
const [isOpen, setOpen] = useControlled(open, defaultOpen, onOpenChange);
|
|
182
182
|
const triggerRef = useRef<HTMLElement | null>(null);
|
|
183
|
-
const
|
|
183
|
+
const dismissedRef = useRef(false);
|
|
184
184
|
const intent = useHoverIntent(setOpen);
|
|
185
185
|
const own = openDelay ?? scope?.delayDuration ?? DEFAULT_OPEN_DELAY;
|
|
186
186
|
const group = scope?.group;
|
|
@@ -217,7 +217,7 @@ export component TooltipRoot(
|
|
|
217
217
|
() => ({
|
|
218
218
|
base,
|
|
219
219
|
closeDelay,
|
|
220
|
-
|
|
220
|
+
dismissedRef,
|
|
221
221
|
intent,
|
|
222
222
|
open: isOpen,
|
|
223
223
|
openDelay: () => group?.delayFor(own) ?? own,
|
|
@@ -245,19 +245,19 @@ export component TooltipRoot(
|
|
|
245
245
|
*/
|
|
246
246
|
export component TooltipTrigger(children?: React.Node, render?: RenderProp, ...rest: Rest) {
|
|
247
247
|
const tooltip = useTooltip("Tooltip.Trigger");
|
|
248
|
-
const { closeDelay,
|
|
248
|
+
const { closeDelay, dismissedRef, intent, openDelay, setOpen, triggerRef } = tooltip;
|
|
249
249
|
useFocusableTrigger(triggerRef, "Tooltip.Trigger");
|
|
250
250
|
|
|
251
251
|
// Whether the pointer put focus here. A click focuses the button, and
|
|
252
252
|
// reopening the tooltip the click just dismissed would put it back over the
|
|
253
253
|
// thing the reader pressed — so a focus that arrived with a press opens
|
|
254
254
|
// nothing, and the next one, which is the keyboard's, does.
|
|
255
|
-
const
|
|
255
|
+
const pressedRef = useRef(false);
|
|
256
256
|
|
|
257
257
|
useEventListener(triggerRef, "pointerenter", (event: $FlowFixMe) => {
|
|
258
258
|
// A tap is not a hover. See the module header: opening here is what eats
|
|
259
259
|
// the tap the control was there to receive.
|
|
260
|
-
if (event.pointerType === "touch" ||
|
|
260
|
+
if (event.pointerType === "touch" || dismissedRef.current) {
|
|
261
261
|
return;
|
|
262
262
|
}
|
|
263
263
|
intent.openAfter(openDelay());
|
|
@@ -265,33 +265,35 @@ export component TooltipTrigger(children?: React.Node, render?: RenderProp, ...r
|
|
|
265
265
|
useEventListener(triggerRef, "pointerleave", () => {
|
|
266
266
|
// Leaving is what makes a dismissal stop applying: coming back is a fresh
|
|
267
267
|
// gesture and deserves a fresh answer.
|
|
268
|
-
|
|
268
|
+
dismissedRef.current = false;
|
|
269
269
|
intent.closeAfter(closeDelay);
|
|
270
270
|
});
|
|
271
271
|
useEventListener(triggerRef, "pointerdown", () => {
|
|
272
|
-
|
|
272
|
+
pressedRef.current = true;
|
|
273
273
|
intent.cancel();
|
|
274
274
|
setOpen(false);
|
|
275
275
|
});
|
|
276
276
|
useEventListener(triggerRef, "focusin", () => {
|
|
277
|
-
if (
|
|
278
|
-
|
|
277
|
+
if (pressedRef.current) {
|
|
278
|
+
pressedRef.current = false;
|
|
279
279
|
return;
|
|
280
280
|
}
|
|
281
|
-
if (
|
|
281
|
+
if (dismissedRef.current) {
|
|
282
282
|
return;
|
|
283
283
|
}
|
|
284
284
|
// No delay: the reader has already said what they want by arriving here.
|
|
285
285
|
intent.openAfter(0);
|
|
286
286
|
});
|
|
287
287
|
useEventListener(triggerRef, "focusout", () => {
|
|
288
|
-
|
|
289
|
-
|
|
288
|
+
pressedRef.current = false;
|
|
289
|
+
dismissedRef.current = false;
|
|
290
290
|
intent.closeAfter(0);
|
|
291
291
|
});
|
|
292
292
|
|
|
293
293
|
// Annotated because this one is not written inside a `ref={...}`, and there
|
|
294
294
|
// is nothing else here for Flow to infer the element's type from.
|
|
295
|
+
// React calls callback refs during commit; pointer and focus handlers read it later.
|
|
296
|
+
// uf-lint-disable-next-line react-compiler/refs
|
|
295
297
|
const attach = composeRefs(rest.ref, (element: HTMLElement | null) => {
|
|
296
298
|
triggerRef.current = element;
|
|
297
299
|
});
|
|
@@ -330,14 +332,14 @@ export component TooltipBody(
|
|
|
330
332
|
...rest: Rest
|
|
331
333
|
) {
|
|
332
334
|
const tooltip = useTooltip("Tooltip.Body");
|
|
333
|
-
const { closeDelay, intent, open, triggerRef } = tooltip;
|
|
335
|
+
const { closeDelay, dismissedRef, intent, open, triggerRef } = tooltip;
|
|
334
336
|
const bodyRef = useRef<HTMLElement | null>(null);
|
|
335
337
|
const close = useStableCallback(() => {
|
|
336
338
|
// `Escape` dismisses it *and* keeps it dismissed while the reader is still
|
|
337
339
|
// on the trigger. Without the flag the pointer that is still resting there
|
|
338
340
|
// — or, for a hover card, the focus it hands back — reopens it at once,
|
|
339
341
|
// and the key does nothing a reader can see.
|
|
340
|
-
|
|
342
|
+
dismissedRef.current = true;
|
|
341
343
|
intent.cancel();
|
|
342
344
|
tooltip.setOpen(false);
|
|
343
345
|
});
|
|
@@ -385,6 +387,8 @@ export component TooltipBody(
|
|
|
385
387
|
"data-side": anchored.side,
|
|
386
388
|
"data-state": "open",
|
|
387
389
|
id: `${tooltip.base}-body`,
|
|
390
|
+
// React calls callback refs during commit; placement effects read it later.
|
|
391
|
+
// uf-lint-disable-next-line react-compiler/refs
|
|
388
392
|
ref: composeRefs(rest.ref, (element) => {
|
|
389
393
|
bodyRef.current = element;
|
|
390
394
|
}),
|