Skip to content

Commit afd7beb

Browse files
committed
Merge branch 'main' into hidden-date-input-console
2 parents 20c24d3 + 95acde1 commit afd7beb

File tree

7 files changed

+94
-150
lines changed

7 files changed

+94
-150
lines changed

packages/@react-aria/utils/src/scrollIntoView.ts

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
*/
1212

1313
import {getScrollParents} from './getScrollParents';
14-
import {isChrome} from './platform';
1514

1615
interface ScrollIntoViewportOpts {
1716
/** The optional containing element of the target to be centered in the viewport. */
@@ -41,64 +40,32 @@ export function scrollIntoView(scrollView: HTMLElement, element: HTMLElement): v
4140
scrollPaddingLeft
4241
} = getComputedStyle(scrollView);
4342

44-
// Account for scroll margin of the element
45-
let {
46-
scrollMarginTop,
47-
scrollMarginRight,
48-
scrollMarginBottom,
49-
scrollMarginLeft
50-
} = getComputedStyle(element);
51-
5243
let borderAdjustedX = x + parseInt(borderLeftWidth, 10);
5344
let borderAdjustedY = y + parseInt(borderTopWidth, 10);
5445
// Ignore end/bottom border via clientHeight/Width instead of offsetHeight/Width
5546
let maxX = borderAdjustedX + scrollView.clientWidth;
5647
let maxY = borderAdjustedY + scrollView.clientHeight;
5748

58-
// Get scroll padding / margin values as pixels - defaults to 0 if no scroll padding / margin
49+
// Get scroll padding values as pixels - defaults to 0 if no scroll padding
5950
// is used.
6051
let scrollPaddingTopNumber = parseInt(scrollPaddingTop, 10) || 0;
6152
let scrollPaddingBottomNumber = parseInt(scrollPaddingBottom, 10) || 0;
6253
let scrollPaddingRightNumber = parseInt(scrollPaddingRight, 10) || 0;
6354
let scrollPaddingLeftNumber = parseInt(scrollPaddingLeft, 10) || 0;
64-
let scrollMarginTopNumber = parseInt(scrollMarginTop, 10) || 0;
65-
let scrollMarginBottomNumber = parseInt(scrollMarginBottom, 10) || 0;
66-
let scrollMarginRightNumber = parseInt(scrollMarginRight, 10) || 0;
67-
let scrollMarginLeftNumber = parseInt(scrollMarginLeft, 10) || 0;
68-
69-
let targetLeft = offsetX - scrollMarginLeftNumber;
70-
let targetRight = offsetX + width + scrollMarginRightNumber;
71-
let targetTop = offsetY - scrollMarginTopNumber;
72-
let targetBottom = offsetY + height + scrollMarginBottomNumber;
7355

74-
let scrollPortLeft = x + parseInt(borderLeftWidth, 10) + scrollPaddingLeftNumber;
75-
let scrollPortRight = maxX - scrollPaddingRightNumber;
76-
let scrollPortTop = y + parseInt(borderTopWidth, 10) + scrollPaddingTopNumber;
77-
let scrollPortBottom = maxY - scrollPaddingBottomNumber;
78-
79-
if (targetLeft > scrollPortLeft || targetRight < scrollPortRight) {
80-
if (targetLeft <= x + scrollPaddingLeftNumber) {
81-
x = targetLeft - parseInt(borderLeftWidth, 10) - scrollPaddingLeftNumber;
82-
} else if (targetRight > maxX - scrollPaddingRightNumber) {
83-
x += targetRight - maxX + scrollPaddingRightNumber;
84-
}
56+
if (offsetX <= x + scrollPaddingLeftNumber) {
57+
x = offsetX - parseInt(borderLeftWidth, 10) - scrollPaddingLeftNumber;
58+
} else if (offsetX + width > maxX - scrollPaddingRightNumber) {
59+
x += offsetX + width - maxX + scrollPaddingRightNumber;
8560
}
86-
87-
if (targetTop > scrollPortTop || targetBottom < scrollPortBottom) {
88-
if (targetTop <= borderAdjustedY + scrollPaddingTopNumber) {
89-
y = targetTop - parseInt(borderTopWidth, 10) - scrollPaddingTopNumber;
90-
} else if (targetBottom > maxY - scrollPaddingBottomNumber) {
91-
y += targetBottom - maxY + scrollPaddingBottomNumber;
92-
}
93-
}
94-
95-
if (process.env.NODE_ENV === 'test') {
96-
scrollView.scrollLeft = x;
97-
scrollView.scrollTop = y;
98-
return;
61+
if (offsetY <= borderAdjustedY + scrollPaddingTopNumber) {
62+
y = offsetY - parseInt(borderTopWidth, 10) - scrollPaddingTopNumber;
63+
} else if (offsetY + height > maxY - scrollPaddingBottomNumber) {
64+
y += offsetY + height - maxY + scrollPaddingBottomNumber;
9965
}
10066

101-
scrollView.scrollTo({left: x, top: y});
67+
scrollView.scrollLeft = x;
68+
scrollView.scrollTop = y;
10269
}
10370

10471
/**
@@ -134,9 +101,8 @@ export function scrollIntoViewport(targetElement: Element | null, opts?: ScrollI
134101
if (targetElement && document.contains(targetElement)) {
135102
let root = document.scrollingElement || document.documentElement;
136103
let isScrollPrevented = window.getComputedStyle(root).overflow === 'hidden';
137-
// If scrolling is not currently prevented then we aren't in a overlay nor is a overlay open, just use element.scrollIntoView to bring the element into view
138-
// Also ignore in chrome because of this bug: https://issues.chromium.org/issues/40074749
139-
if (!isScrollPrevented && !isChrome()) {
104+
// If scrolling is not currently prevented then we aren’t in a overlay nor is a overlay open, just use element.scrollIntoView to bring the element into view
105+
if (!isScrollPrevented) {
140106
let {left: originalLeft, top: originalTop} = targetElement.getBoundingClientRect();
141107

142108
// use scrollIntoView({block: 'nearest'}) instead of .focus to check if the element is fully in view or not since .focus()

packages/@react-spectrum/s2/src/Field.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,8 @@ export const FieldGroup = forwardRef(function FieldGroup(props: FieldGroupProps,
200200
(e.currentTarget.querySelector('input, textarea') as HTMLElement)?.focus();
201201
}
202202
}}
203-
onPointerUp={e => {
204-
if (e.pointerType !== 'mouse' && !(e.target as Element).closest('button,input,textarea')) {
203+
onTouchEnd={e => {
204+
if (!(e.target as Element).closest('button,input,textarea')) {
205205
e.preventDefault();
206206
(e.currentTarget.querySelector('input, textarea') as HTMLElement)?.focus();
207207
}

packages/@react-stately/select/src/useSelectState.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,13 @@ export function useSelectState<T extends object, M extends SelectionMode = 'sing
162162
focusStrategy,
163163
open(focusStrategy: FocusStrategy | null = null) {
164164
// Don't open if the collection is empty.
165-
if (listState.collection.size !== 0) {
165+
if (listState.collection.size !== 0 || props.allowsEmptyCollection) {
166166
setFocusStrategy(focusStrategy);
167167
triggerState.open();
168168
}
169169
},
170170
toggle(focusStrategy: FocusStrategy | null = null) {
171-
if (listState.collection.size !== 0) {
171+
if (listState.collection.size !== 0 || props.allowsEmptyCollection) {
172172
setFocusStrategy(focusStrategy);
173173
triggerState.toggle();
174174
}

packages/@react-types/select/src/index.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ export interface SelectProps<T, M extends SelectionMode = 'single'> extends Coll
6161
/** Sets the default open state of the menu. */
6262
defaultOpen?: boolean,
6363
/** Method that is called when the open state of the menu changes. */
64-
onOpenChange?: (isOpen: boolean) => void
64+
onOpenChange?: (isOpen: boolean) => void,
65+
/** Whether the select should be allowed to be open when the collection is empty. */
66+
allowsEmptyCollection?: boolean
6567
}
6668

6769
export interface AriaSelectProps<T, M extends SelectionMode = 'single'> extends SelectProps<T, M>, DOMProps, AriaLabelingProps, FocusableDOMProps {

packages/react-aria-components/stories/ListBox.stories.tsx

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -743,46 +743,6 @@ export const AsyncListBoxVirtualized: StoryFn<typeof AsyncListBoxRender> = (args
743743
);
744744
};
745745

746-
export const ListBoxScrollMargin: ListBoxStory = (args) => {
747-
let items: {id: number, name: string, description: string}[] = [];
748-
for (let i = 0; i < 100; i++) {
749-
items.push({id: i, name: `Item ${i}`, description: `Description ${i}`});
750-
}
751-
return (
752-
<ListBox
753-
className={styles.menu}
754-
{...args}
755-
aria-label="test listbox"
756-
style={{height: 200, width: 100, overflow: 'scroll'}}
757-
items={items}>
758-
{item => (
759-
<MyListBoxItem style={{scrollMargin: 10, width: 150, display: 'flex', padding: '2px 20px', justifyContent: 'space-between'}}>
760-
<span>{item.name}</span>
761-
<span>{item.description}</span>
762-
</MyListBoxItem>
763-
)}
764-
</ListBox>
765-
);
766-
};
767-
768-
export const ListBoxSmoothScroll: ListBoxStory = (args) => {
769-
let items: {id: number, name: string}[] = [];
770-
for (let i = 0; i < 100; i++) {
771-
items.push({id: i, name: `Item ${i}`});
772-
}
773-
return (
774-
<ListBox
775-
className={styles.menu}
776-
{...args}
777-
aria-label="test listbox"
778-
style={{height: 200, width: 200, overflow: 'scroll', display: 'grid', gridTemplateColumns: 'repeat(4, 80px)', scrollBehavior: 'smooth'}}
779-
items={items}
780-
layout="grid">
781-
{item => <MyListBoxItem style={{minHeight: 32}}>{item.name}</MyListBoxItem>}
782-
</ListBox>
783-
);
784-
};
785-
786746
AsyncListBoxVirtualized.story = {
787747
args: {
788748
delay: 50
Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
1-
import {Autocomplete} from '../src/Autocomplete';
1+
import {Button} from '../src/Button';
2+
import {CommandPalette} from '../src/CommandPalette';
3+
import {DialogTrigger} from '../src/Dialog';
24
import {MenuItem} from '../src/Menu';
35

46
import type {Meta, StoryFn} from '@storybook/react';
57

6-
const meta: Meta<typeof Autocomplete> = {
7-
component: Autocomplete,
8+
const meta: Meta<typeof CommandPalette> = {
9+
component: CommandPalette,
810
parameters: {
911
layout: 'centered'
1012
},
1113
tags: ['autodocs']
1214
};
1315

1416
export default meta;
15-
type Story = StoryFn<typeof Autocomplete>;
17+
type Story = StoryFn<typeof CommandPalette>;
1618

1719
export const Example: Story = (args) => (
18-
<Autocomplete {...args}>
19-
<MenuItem>Create new file...</MenuItem>
20-
<MenuItem>Create new folder...</MenuItem>
21-
<MenuItem>Assign to...</MenuItem>
22-
<MenuItem>Assign to me</MenuItem>
23-
<MenuItem>Change status...</MenuItem>
24-
<MenuItem>Change priority...</MenuItem>
25-
<MenuItem>Add label...</MenuItem>
26-
<MenuItem>Remove label...</MenuItem>
27-
</Autocomplete>
20+
<DialogTrigger>
21+
<Button>Open Command Palette <kbd>⌘ J</kbd></Button>
22+
<CommandPalette {...args}>
23+
<MenuItem>Create new file...</MenuItem>
24+
<MenuItem>Create new folder...</MenuItem>
25+
<MenuItem>Assign to...</MenuItem>
26+
<MenuItem>Assign to me</MenuItem>
27+
<MenuItem>Change status...</MenuItem>
28+
<MenuItem>Change priority...</MenuItem>
29+
<MenuItem>Add label...</MenuItem>
30+
<MenuItem>Remove label...</MenuItem>
31+
</CommandPalette>
32+
</DialogTrigger>
2833
);
29-
30-
Example.args = {
31-
label: 'Commands',
32-
placeholder: 'Search commands...'
33-
};
Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
1-
import { Meta } from '@storybook/react';
2-
import React from 'react';
3-
import { Autocomplete, AutocompleteItem, AutocompleteSection } from '../src/Autocomplete';
1+
import {Meta} from "@storybook/react";
2+
import React from "react";
3+
import {CommandPalette} from "../src/CommandPalette";
4+
import {Button} from "../src/Button";
5+
import {DialogTrigger} from "react-aria-components";
6+
import {MenuItem, MenuSection} from "../src/Menu";
47

5-
const meta: Meta<typeof Autocomplete> = {
6-
component: Autocomplete,
8+
const meta: Meta<typeof CommandPalette> = {
9+
component: CommandPalette,
710
parameters: {
811
layout: 'centered'
912
},
10-
tags: ['autodocs'],
11-
args: {
12-
label: 'Ice cream flavor'
13-
}
13+
tags: ['autodocs']
1414
};
1515

1616
export default meta;
1717

1818
export const Example = (args: any) => (
19-
<Autocomplete {...args}>
20-
<AutocompleteItem>Chocolate</AutocompleteItem>
21-
<AutocompleteItem id="mint">Mint</AutocompleteItem>
22-
<AutocompleteItem>Strawberry</AutocompleteItem>
23-
<AutocompleteItem>Vanilla</AutocompleteItem>
24-
<AutocompleteItem>Cookies and Cream</AutocompleteItem>
25-
</Autocomplete>
19+
<DialogTrigger>
20+
<Button>
21+
Open Command Palette{' '}
22+
<kbd className="px-1 ml-4 font-sans text-xs rounded-sm border border-white/20 bg-white/10">
23+
⌘ J
24+
</kbd>
25+
</Button>
26+
<CommandPalette {...args}>
27+
<MenuItem>Chocolate</MenuItem>
28+
<MenuItem id="mint">Mint</MenuItem>
29+
<MenuItem>Strawberry</MenuItem>
30+
<MenuItem>Vanilla</MenuItem>
31+
<MenuItem>Cookies and Cream</MenuItem>
32+
</CommandPalette>
33+
</DialogTrigger>
2634
);
2735

2836
export const DisabledItems = (args: any) => <Example {...args} />;
@@ -31,30 +39,38 @@ DisabledItems.args = {
3139
};
3240

3341
export const Sections = (args: any) => (
34-
<Autocomplete {...args}>
35-
<AutocompleteSection title="Fruit">
36-
<AutocompleteItem id="Apple">Apple</AutocompleteItem>
37-
<AutocompleteItem id="Banana">Banana</AutocompleteItem>
38-
<AutocompleteItem id="Orange">Orange</AutocompleteItem>
39-
<AutocompleteItem id="Honeydew">Honeydew</AutocompleteItem>
40-
<AutocompleteItem id="Grapes">Grapes</AutocompleteItem>
41-
<AutocompleteItem id="Watermelon">Watermelon</AutocompleteItem>
42-
<AutocompleteItem id="Cantaloupe">Cantaloupe</AutocompleteItem>
43-
<AutocompleteItem id="Pear">Pear</AutocompleteItem>
44-
</AutocompleteSection>
45-
<AutocompleteSection title="Vegetable">
46-
<AutocompleteItem id="Cabbage">Cabbage</AutocompleteItem>
47-
<AutocompleteItem id="Broccoli">Broccoli</AutocompleteItem>
48-
<AutocompleteItem id="Carrots">Carrots</AutocompleteItem>
49-
<AutocompleteItem id="Lettuce">Lettuce</AutocompleteItem>
50-
<AutocompleteItem id="Spinach">Spinach</AutocompleteItem>
51-
<AutocompleteItem id="Bok Choy">Bok Choy</AutocompleteItem>
52-
<AutocompleteItem id="Cauliflower">Cauliflower</AutocompleteItem>
53-
<AutocompleteItem id="Potatoes">Potatoes</AutocompleteItem>
54-
</AutocompleteSection>
55-
</Autocomplete>
42+
<DialogTrigger>
43+
<Button>
44+
Open Command Palette{' '}
45+
<kbd className="px-1 ml-4 font-sans text-xs rounded-sm border border-white/20 bg-white/10">
46+
⌘ J
47+
</kbd>
48+
</Button>
49+
<CommandPalette {...args}>
50+
<MenuSection title="Fruit">
51+
<MenuItem id="Apple">Apple</MenuItem>
52+
<MenuItem id="Banana">Banana</MenuItem>
53+
<MenuItem id="Orange">Orange</MenuItem>
54+
<MenuItem id="Honeydew">Honeydew</MenuItem>
55+
<MenuItem id="Grapes">Grapes</MenuItem>
56+
<MenuItem id="Watermelon">Watermelon</MenuItem>
57+
<MenuItem id="Cantaloupe">Cantaloupe</MenuItem>
58+
<MenuItem id="Pear">Pear</MenuItem>
59+
</MenuSection>
60+
<MenuSection title="Vegetable">
61+
<MenuItem id="Cabbage">Cabbage</MenuItem>
62+
<MenuItem id="Broccoli">Broccoli</MenuItem>
63+
<MenuItem id="Carrots">Carrots</MenuItem>
64+
<MenuItem id="Lettuce">Lettuce</MenuItem>
65+
<MenuItem id="Spinach">Spinach</MenuItem>
66+
<MenuItem id="Bok Choy">Bok Choy</MenuItem>
67+
<MenuItem id="Cauliflower">Cauliflower</MenuItem>
68+
<MenuItem id="Potatoes">Potatoes</MenuItem>
69+
</MenuSection>
70+
</CommandPalette>
71+
</DialogTrigger>
5672
);
5773

5874
Sections.args = {
59-
label: 'Preferred fruit or vegetable'
75+
label: 'Preferred fruit or vegetable',
6076
};

0 commit comments

Comments
 (0)