Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,42 @@ index 55027cb256f66b808d17280dc01bc55a796a1032..993d5de5a838a711d7ae009344354772
focus.focusElement(dest);
if (selection.hasOwnSelection(dest)) {
UI.setUISelection(dest, {
diff --git a/dist/cjs/keyboard/index.js b/dist/cjs/keyboard/index.js
index 18f5b4b8eb995fe5ecb352230f71b25e90f2a8a0..e5e17d1d3089aa8ea5c3f55a06aa746eb3a8258a 100644
--- a/dist/cjs/keyboard/index.js
+++ b/dist/cjs/keyboard/index.js
@@ -19,7 +19,7 @@ async function keyboardAction(instance, { keyDef, releasePrevious, releaseSelf,
}
if (!releasePrevious) {
for(let i = 1; i <= repeat; i++){
- await system.keyboard.keydown(instance, keyDef);
+ await system.keyboard.keydown(instance, keyDef, i > 1);
if (i < repeat) {
await wait.wait(instance.config);
}
diff --git a/dist/cjs/system/keyboard.js b/dist/cjs/system/keyboard.js
index 3272fd7e642e79a8f67052b4b521355314cbc496..e1b16e9213eb609e7cd2335ecee792c3c19d35b4 100644
--- a/dist/cjs/system/keyboard.js
+++ b/dist/cjs/system/keyboard.js
@@ -52,7 +52,7 @@ class KeyboardHost {
getPressedKeys() {
return this.pressed.values().map((p)=>p.keyDef);
}
- /** Press a key */ async keydown(instance, keyDef) {
+ /** Press a key */ async keydown(instance, keyDef, repeat = false) {
const key = String(keyDef.key);
const code = String(keyDef.code);
const target = getActiveElement.getActiveElementOrBody(instance.config.document);
@@ -63,7 +63,8 @@ class KeyboardHost {
}
const unprevented = instance.dispatchUIEvent(target, 'keydown', {
key,
- code
+ code,
+ repeat
});
if (isModifierLock(key) && !this.modifiers[key]) {
this.modifiers[key] = true;
diff --git a/dist/cjs/utils/focus/getActiveElement.js b/dist/cjs/utils/focus/getActiveElement.js
index d25f3a8ef67e856e43614559f73012899c0b53d7..4ed9ee45565ed438ee9284d8d3043c0bd50463eb 100644
--- a/dist/cjs/utils/focus/getActiveElement.js
Expand All @@ -37,3 +73,39 @@ index d25f3a8ef67e856e43614559f73012899c0b53d7..4ed9ee45565ed438ee9284d8d3043c0b
} else {
// Browser does not yield disabled elements as document.activeElement - jsdom does
if (isDisabled.isDisabled(activeElement)) {
diff --git a/dist/esm/keyboard/index.js b/dist/esm/keyboard/index.js
index 9c9feb7b82aabeadd8acbd9ec8f4127e302a4634..1456a5cf406382ee08b4bb7184499632bd8d3bd3 100644
--- a/dist/esm/keyboard/index.js
+++ b/dist/esm/keyboard/index.js
@@ -17,7 +17,7 @@ async function keyboardAction(instance, { keyDef, releasePrevious, releaseSelf,
}
if (!releasePrevious) {
for(let i = 1; i <= repeat; i++){
- await system.keyboard.keydown(instance, keyDef);
+ await system.keyboard.keydown(instance, keyDef, i > 1);
if (i < repeat) {
await wait(instance.config);
}
diff --git a/dist/esm/system/keyboard.js b/dist/esm/system/keyboard.js
index 43700ca3a873aa6eb1cb4553c0889b685ff90fff..9607372660dcb1737b26abcdc4bc71f35e4d43c3 100644
--- a/dist/esm/system/keyboard.js
+++ b/dist/esm/system/keyboard.js
@@ -50,7 +50,7 @@ class KeyboardHost {
getPressedKeys() {
return this.pressed.values().map((p)=>p.keyDef);
}
- /** Press a key */ async keydown(instance, keyDef) {
+ /** Press a key */ async keydown(instance, keyDef, repeat = false) {
const key = String(keyDef.key);
const code = String(keyDef.code);
const target = getActiveElementOrBody(instance.config.document);
@@ -61,7 +61,8 @@ class KeyboardHost {
}
const unprevented = instance.dispatchUIEvent(target, 'keydown', {
key,
- code
+ code,
+ repeat
});
if (isModifierLock(key) && !this.modifiers[key]) {
this.modifiers[key] = true;
21 changes: 18 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: clean test lint build
.PHONY: clean test lint build sync-starter-css check-starter-css

SHELL := /bin/bash
PATH := ./node_modules/.bin:$(PATH)
Expand Down Expand Up @@ -121,13 +121,22 @@ website-production:
# $(MAKE) s2-docs-production
$(MAKE) starter-zip
$(MAKE) tailwind-starter
$(MAKE) hooks-starter
$(MAKE) s2-storybook-docs
mv starters/docs/storybook-static dist/production/docs/react-aria-starter
mv starters/docs/react-aria-starter.zip dist/production/docs/react-aria-starter.$$(git rev-parse --short HEAD).zip
mv starters/tailwind/storybook-static dist/production/docs/react-aria-tailwind-starter
mv starters/tailwind/react-aria-tailwind-starter.zip dist/production/docs/react-aria-tailwind-starter.$$(git rev-parse --short HEAD).zip
mv starters/hooks/storybook-static dist/production/docs/react-aria-hooks-starter
mv starters/hooks/react-aria-hooks-starter.zip dist/production/docs/react-aria-hooks-starter.$$(git rev-parse --short HEAD).zip

check-examples:
sync-starter-css:
yarn sync:starter-css

check-starter-css:
yarn check:starter-css

check-examples: check-starter-css
node scripts/extractExamplesS2.mjs
yarn tsgo --project dist/docs-examples/tsconfig.json

Expand All @@ -146,6 +155,12 @@ tailwind-starter:
cd starters/tailwind && zip -r react-aria-tailwind-starter.zip . -x .gitignore .DS_Store "node_modules/*" "storybook-static/*"
cd starters/tailwind && yarn build-storybook

hooks-starter: sync-starter-css
cp LICENSE starters/hooks/.
cd starters/hooks && yarn --no-immutable && yarn up react-aria react-stately @internationalized/date && yarn tsc
cd starters/hooks && zip -r react-aria-hooks-starter.zip . -x .gitignore .DS_Store "node_modules/*" "storybook-static/*"
cd starters/hooks && yarn build-storybook

s2-storybook-docs:
yarn build:s2-storybook-docs -o dist/production/docs/s2

Expand Down Expand Up @@ -174,7 +189,7 @@ s2-docs-production:
cd starters/tailwind && yarn install --no-immutable && yarn up react-aria-components tailwindcss-react-aria-components
$(MAKE) build-starters

build-s2-docs:
build-s2-docs: check-starter-css
yarn workspace @react-spectrum/s2-docs generate:md
yarn workspace @react-spectrum/s2-docs generate:og
LIBRARY=react-aria node scripts/buildRegistry.mjs
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"clean:all": "make clean_all",
"format": "oxfmt",
"format:check": "oxfmt --check",
"sync:starter-css": "node scripts/syncStarterCss.mjs",
"check:starter-css": "node scripts/syncStarterCss.mjs --check",
"install-16": "node scripts/react-16-install-prep.mjs && yarn add react@^16.8.0 react-dom@^16.8.0 @testing-library/react@^12 @testing-library/react-hooks@^8 @testing-library/dom@^8 react-test-renderer@^16.9.0 && node scripts/oldReactSupport.mjs",
"install-17": "node scripts/react-17-install-prep.mjs && yarn add react@^17 react-dom@^17 @testing-library/react@^12 @testing-library/react-hooks@^8 @testing-library/dom@^8 react-test-renderer@^16.9.0 && node scripts/oldReactSupport.mjs",
"install-18": "node scripts/react-18-install-prep.mjs && yarn add react@^18 react-dom@^18 react-test-renderer@^18.3.1 && node scripts/oldReactSupport.mjs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,28 @@ describe('ActionGroup', function () {
}
);

it('should support repeat keydown events when holding an arrow key', async function () {
// Arrow key navigation is handled by useActionGroup's useKeyboard (allowRepeats).
let tree = render(
<Provider theme={theme} locale="de-DE">
<ActionGroup>
<Item key="1">Click me 1</Item>
<Item key="2">Click me 2</Item>
<Item key="3">Click me 3</Item>
</ActionGroup>
</Provider>
);

let buttons = tree.getAllByRole('button');
await user.tab();
expect(document.activeElement).toBe(buttons[0]);

// Hold the key down for two keydown events (the second is a repeat).
await user.keyboard('{ArrowRight>2/}');

expect(document.activeElement).toBe(buttons[2]);
});

it.each`
Name | props | disabledKeys | orders
${'middle disabled'} | ${{locale: 'de-DE'}} | ${['1']} | ${[{action: tab, result: () => ['0', '-1', '-1']}, {action: pressArrowRight, result: () => ['-1', '-1', '0']}, {action: pressArrowRight, result: () => ['0', '-1', '-1']}, {action: pressArrowLeft, result: () => ['-1', '-1', '0']}, {action: pressArrowLeft, result: () => ['0', '-1', '-1']}]}
Expand Down
3 changes: 2 additions & 1 deletion packages/dev/s2-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
},
"alias": {
"tailwind-starter": "../../../starters/tailwind/src",
"vanilla-starter": "../../../starters/docs/src"
"vanilla-starter": "../../../starters/docs/src",
"hooks-starter": "../../../starters/hooks/src"
},
"targets": {
"react-static": {
Expand Down
2 changes: 1 addition & 1 deletion packages/dev/s2-docs/pages/react-aria/Breadcrumbs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Anatomy from '/packages/react-aria/docs/breadcrumbs/anatomy.svg';
import {InlineAlert, Heading, Content} from '@react-spectrum/s2';

export const tags = ['navigation'];
export const relatedPages = [{'title': 'useBreadcrumbs', 'url': 'Breadcrumbs/useBreadcrumbs.html'}];
export const relatedPages = [{'title': 'useBreadcrumbs', 'url': './Breadcrumbs/useBreadcrumbs'}];
export const description = 'Displays a hierarchy of links to the current page or resource in an application.';

# Breadcrumbs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{/* Copyright 2026 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License. */}

import {Layout} from '../../../src/Layout';
export default Layout;
import {FunctionAPIGroup} from '../../../src/FunctionAPI';
import {InterfaceType} from '../../../src/types';
import docs from 'docs:@react-aria/breadcrumbs';
import Anatomy from '/packages/react-aria/docs/breadcrumbs/anatomy.svg';

export const section = 'Hooks';
export const description = 'Provides the behavior and accessibility implementation for a breadcrumbs component.';
export const isSubpage = true;

# useBreadcrumbs

<PageDescription>{docs.exports.useBreadcrumbs.description}</PageDescription>

```tsx render files={["starters/hooks/src/Breadcrumbs.tsx", "starters/hooks/src/Breadcrumbs.css", "starters/hooks/src/Link.css"]}
"use client";
import {Breadcrumbs, Breadcrumb} from 'hooks-starter/Breadcrumbs';

<Breadcrumbs>
<Breadcrumb onPress={() => alert('Pressed Folder 1')}>Folder 1</Breadcrumb>
<Breadcrumb onPress={() => alert('Pressed Folder 2')}>Folder 2</Breadcrumb>
<Breadcrumb>Folder 3</Breadcrumb>
</Breadcrumbs>
```

## API

<Anatomy />

```tsx
<Breadcrumbs>
<Breadcrumb>
<Link />
</Breadcrumb>
</Breadcrumbs>
```

<FunctionAPIGroup
functions={[
{function: docs.exports.useBreadcrumbs, links: docs.links},
{function: docs.exports.useBreadcrumbItem, links: docs.links},
]}
/>

### AriaBreadcrumbsProps

<InterfaceType {...docs.exports.AriaBreadcrumbsProps} />

### BreadcrumbsAria

<InterfaceType {...docs.exports.BreadcrumbsAria} />

### AriaBreadcrumbItemProps

<InterfaceType {...docs.exports.AriaBreadcrumbItemProps} />

### BreadcrumbItemAria

<InterfaceType {...docs.exports.BreadcrumbItemAria} />
2 changes: 1 addition & 1 deletion packages/dev/s2-docs/pages/react-aria/Button.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import '../../tailwind/tailwind.css';
import typesDocs from 'docs:@react-types/shared/src/events.d.ts';

export const tags = ['btn'];
export const relatedPages = [{'title': 'useButton', 'url': 'Button/useButton.html'}];
export const relatedPages = [{'title': 'useButton', 'url': './Button/useButton'}];
export const description = 'Allows a user to perform an action, with mouse, touch, and keyboard interactions.';

# Button
Expand Down
41 changes: 41 additions & 0 deletions packages/dev/s2-docs/pages/react-aria/Button/useButton.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{/* Copyright 2026 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License. */}

import {Layout} from '../../../src/Layout';
export default Layout;
import {FunctionAPI} from '../../../src/FunctionAPI';
import {InterfaceType} from '../../../src/types';
import docs from 'docs:@react-aria/button';

export const section = 'Hooks';
export const description = 'Provides the behavior and accessibility implementation for a button.';
export const isSubpage = true;

# useButton

<PageDescription>{docs.exports.useButton.description}</PageDescription>

```tsx render files={["starters/hooks/src/Button.tsx", "starters/hooks/src/Button.css"]}
"use client";
import {Button} from 'hooks-starter/Button';

<Button onPress={() => alert('Button pressed!')}>Press me</Button>
```

## API

<FunctionAPI function={docs.exports.useButton} links={docs.links} />

### AriaButtonProps

<InterfaceType {...docs.exports.AriaButtonProps} />

### ButtonAria

<InterfaceType {...docs.exports.ButtonAria} />
2 changes: 1 addition & 1 deletion packages/dev/s2-docs/pages/react-aria/Calendar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import '../../tailwind/tailwind.css';
import Anatomy from '/packages/react-aria/docs/calendar/calendar-anatomy.svg';

export const tags = ['date'];
export const relatedPages = [{'title': 'useCalendar', 'url': 'Calendar/useCalendar.html'}];
export const relatedPages = [{'title': 'useCalendar', 'url': './Calendar/useCalendar'}];
export const description = 'Displays one or more date grids and allows users to select a single date.';

# Calendar
Expand Down
Loading
Loading