mirror of
https://github.com/Redocly/redoc.git
synced 2025-04-22 09:42:00 +03:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
00bc6edfc4 | ||
|
45476135ad | ||
|
05a04c85ed | ||
|
2db293bfb2 | ||
|
1b4126fde4 | ||
|
1fa13270a1 | ||
|
7cedd59826 | ||
|
cab07bad3b |
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,3 +1,17 @@
|
||||||
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
|
1072
package-lock.json
generated
1072
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "redoc",
|
"name": "redoc",
|
||||||
"version": "2.4.0",
|
"version": "2.5.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": "^3.0.1",
|
"esbuild-loader": "^4.3.0",
|
||||||
"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.0.6",
|
"dompurify": "^3.2.4",
|
||||||
"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",
|
||||||
|
|
|
@ -41,6 +41,7 @@ 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!)}
|
||||||
|
|
|
@ -6,11 +6,14 @@ 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,
|
||||||
|
|
|
@ -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 { shortenHTTPVerb } from '../../utils/openapi';
|
|
||||||
import { MenuItems } from './MenuItems';
|
|
||||||
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
|
|
||||||
import { l } from '../../services/Labels';
|
import { l } from '../../services/Labels';
|
||||||
import { scrollIntoViewIfNeeded } from '../../utils';
|
import { scrollIntoViewIfNeeded } from '../../utils';
|
||||||
|
import { shortenHTTPVerb } from '../../utils/openapi';
|
||||||
import { OptionsContext } from '../OptionsProvider';
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import type { IMenuItem } from '../../services';
|
import { MenuItems } from './MenuItems';
|
||||||
|
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
|
||||||
|
|
||||||
export interface MenuItemProps {
|
export interface MenuItemProps {
|
||||||
item: IMenuItem;
|
item: IMenuItem;
|
||||||
|
@ -47,9 +47,18 @@ 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} />
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
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';
|
||||||
|
@ -50,5 +51,54 @@ 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user