Compare commits

..

No commits in common. "main" and "v2.4.0" have entirely different histories.
main ... v2.4.0

7 changed files with 340 additions and 527 deletions

View File

@ -1,17 +1,3 @@
# [2.5.0](https://github.com/Redocly/redoc/compare/v2.4.0...v2.5.0) (2025-04-14)
### Bug Fixes
* enhance accessibility for menu items with keyboard support ([#2655](https://github.com/Redocly/redoc/issues/2655)) ([2db293b](https://github.com/Redocly/redoc/commit/2db293bfb2973497dd33f31dc99e97f5bb90bbe8))
### Features
* add keyboard navigation support to JsonViewer component ([#2654](https://github.com/Redocly/redoc/issues/2654)) ([1b4126f](https://github.com/Redocly/redoc/commit/1b4126fde4531387f49c90f52efbd0c0e5f7b6ea))
# [2.4.0](https://github.com/Redocly/redoc/compare/v2.3.0...v2.4.0) (2025-02-07) # [2.4.0](https://github.com/Redocly/redoc/compare/v2.3.0...v2.4.0) (2025-02-07)

774
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "redoc", "name": "redoc",
"version": "2.5.0", "version": "2.4.0",
"description": "ReDoc", "description": "ReDoc",
"repository": { "repository": {
"type": "git", "type": "git",
@ -93,7 +93,7 @@
"cypress": "^13.8.1", "cypress": "^13.8.1",
"enzyme": "^3.11.0", "enzyme": "^3.11.0",
"enzyme-to-json": "^3.6.2", "enzyme-to-json": "^3.6.2",
"esbuild-loader": "^4.3.0", "esbuild-loader": "^3.0.1",
"eslint": "^7.27.0", "eslint": "^7.27.0",
"eslint-plugin-import": "^2.23.4", "eslint-plugin-import": "^2.23.4",
"eslint-plugin-react": "^7.34.2", "eslint-plugin-react": "^7.34.2",
@ -141,7 +141,7 @@
"@redocly/openapi-core": "^1.4.0", "@redocly/openapi-core": "^1.4.0",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"decko": "^1.2.0", "decko": "^1.2.0",
"dompurify": "^3.2.4", "dompurify": "^3.0.6",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"json-pointer": "^0.6.2", "json-pointer": "^0.6.2",
"lunr": "^2.3.9", "lunr": "^2.3.9",

View File

@ -41,7 +41,6 @@ const Json = (props: JsonProps) => {
<OptionsContext.Consumer> <OptionsContext.Consumer>
{options => ( {options => (
<PrismDiv <PrismDiv
tabIndex={0}
className={props.className} className={props.className}
// tslint:disable-next-line // tslint:disable-next-line
ref={node => setNode(node!)} ref={node => setNode(node!)}

View File

@ -6,14 +6,11 @@ import { StylingMarkdownProps } from './Markdown';
import { StyledMarkdownBlock } from './styled.elements'; import { StyledMarkdownBlock } from './styled.elements';
import styled from 'styled-components'; import styled from 'styled-components';
// Workaround for DOMPurify type issues (https://github.com/cure53/DOMPurify/issues/1034)
const dompurify = DOMPurify['default'] as DOMPurify.DOMPurify;
const StyledMarkdownSpan = styled(StyledMarkdownBlock)` const StyledMarkdownSpan = styled(StyledMarkdownBlock)`
display: inline; display: inline;
`; `;
const sanitize = (sanitize, html) => (sanitize ? dompurify.sanitize(html) : html); const sanitize = (sanitize, html) => (sanitize ? DOMPurify.sanitize(html) : html);
export function SanitizedMarkdownHTML({ export function SanitizedMarkdownHTML({
inline, inline,

View File

@ -2,14 +2,14 @@ import { observer } from 'mobx-react';
import * as React from 'react'; import * as React from 'react';
import { ShelfIcon } from '../../common-elements/shelfs'; import { ShelfIcon } from '../../common-elements/shelfs';
import type { IMenuItem } from '../../services';
import { OperationModel } from '../../services'; import { OperationModel } from '../../services';
import { l } from '../../services/Labels';
import { scrollIntoViewIfNeeded } from '../../utils';
import { shortenHTTPVerb } from '../../utils/openapi'; import { shortenHTTPVerb } from '../../utils/openapi';
import { OptionsContext } from '../OptionsProvider';
import { MenuItems } from './MenuItems'; import { MenuItems } from './MenuItems';
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements'; import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
import { l } from '../../services/Labels';
import { scrollIntoViewIfNeeded } from '../../utils';
import { OptionsContext } from '../OptionsProvider';
import type { IMenuItem } from '../../services';
export interface MenuItemProps { export interface MenuItemProps {
item: IMenuItem; item: IMenuItem;
@ -47,18 +47,9 @@ export class MenuItem extends React.Component<MenuItemProps> {
<MenuItemLi <MenuItemLi
tabIndex={0} tabIndex={0}
onClick={this.activate} onClick={this.activate}
onKeyDown={evt => {
// Space or Enter key will activate the menu item
if (evt.key === 'Enter' || evt.key === ' ') {
this.props.onActivate!(this.props.item);
evt.stopPropagation();
}
}}
depth={item.depth} depth={item.depth}
data-item-id={item.id} data-item-id={item.id}
role="menuitem" role="menuitem"
aria-label={item.sidebarLabel}
aria-expanded={item.expanded}
> >
{item.type === 'operation' ? ( {item.type === 'operation' ? (
<OperationMenuItemContent {...this.props} item={item as OperationModel} /> <OperationMenuItemContent {...this.props} item={item as OperationModel} />

View File

@ -2,7 +2,6 @@
import { mount, ReactWrapper } from 'enzyme'; import { mount, ReactWrapper } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { act } from 'react';
import { JsonViewer } from '../'; import { JsonViewer } from '../';
import { withTheme } from '../testProviders'; import { withTheme } from '../testProviders';
@ -51,54 +50,5 @@ describe('Components', () => {
expect(flatDataComponent.html()).not.toContain('Expand all'); expect(flatDataComponent.html()).not.toContain('Expand all');
expect(flatDataComponent.html()).not.toContain('Collapse all'); expect(flatDataComponent.html()).not.toContain('Collapse all');
}); });
describe('Keyboard Navigation', () => {
let component: ReactWrapper;
const data = {
a: 1,
b: {
c:
// Long string to test horizontal scrolling
Array(100).fill('hello').join(''),
},
};
beforeEach(() => {
component = mount(withTheme(<JsonViewer data={data} />));
ClipboardService.copySelected = origCopySelected;
});
test('should handle arrow key navigation', () => {
const prismDiv = component.find('div[tabIndex=0]');
const divElement = prismDiv.getDOMNode();
// Mock scrollLeft before events
Object.defineProperty(divElement, 'scrollLeft', {
get: jest.fn(() => 0),
set: jest.fn(),
});
// Trigger events inside act()
act(() => {
divElement.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'ArrowRight',
bubbles: true,
}),
);
});
act(() => {
divElement.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'ArrowLeft',
bubbles: true,
}),
);
});
expect(divElement.scrollLeft).toBe(0);
});
});
}); });
}); });