@wordpress/dom 3.2.2 → 3.2.6
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/README.md +3 -4
- package/build/dom/caret-range-from-point.js +7 -3
- package/build/dom/caret-range-from-point.js.map +1 -1
- package/build/dom/document-has-text-selection.js +1 -1
- package/build/dom/document-has-text-selection.js.map +1 -1
- package/build/dom/document-has-uncollapsed-selection.js +2 -2
- package/build/dom/document-has-uncollapsed-selection.js.map +1 -1
- package/build/dom/is-edge.js +1 -1
- package/build/dom/is-edge.js.map +1 -1
- package/build/dom/place-caret-at-edge.js +108 -0
- package/build/dom/place-caret-at-edge.js.map +1 -0
- package/build/dom/place-caret-at-horizontal-edge.js +2 -84
- package/build/dom/place-caret-at-horizontal-edge.js.map +1 -1
- package/build/dom/place-caret-at-vertical-edge.js +6 -56
- package/build/dom/place-caret-at-vertical-edge.js.map +1 -1
- package/build/dom/strip-html.js +14 -2
- package/build/dom/strip-html.js.map +1 -1
- package/build/focusable.js +29 -18
- package/build/focusable.js.map +1 -1
- package/build/phrasing-content.js +1 -1
- package/build/phrasing-content.js.map +1 -1
- package/build/tabbable.js +2 -2
- package/build/tabbable.js.map +1 -1
- package/build-module/dom/caret-range-from-point.js +7 -3
- package/build-module/dom/caret-range-from-point.js.map +1 -1
- package/build-module/dom/document-has-text-selection.js +1 -1
- package/build-module/dom/document-has-text-selection.js.map +1 -1
- package/build-module/dom/document-has-uncollapsed-selection.js +2 -2
- package/build-module/dom/document-has-uncollapsed-selection.js.map +1 -1
- package/build-module/dom/is-edge.js +1 -1
- package/build-module/dom/is-edge.js.map +1 -1
- package/build-module/dom/place-caret-at-edge.js +95 -0
- package/build-module/dom/place-caret-at-edge.js.map +1 -0
- package/build-module/dom/place-caret-at-horizontal-edge.js +2 -81
- package/build-module/dom/place-caret-at-horizontal-edge.js.map +1 -1
- package/build-module/dom/place-caret-at-vertical-edge.js +6 -54
- package/build-module/dom/place-caret-at-vertical-edge.js.map +1 -1
- package/build-module/dom/strip-html.js +11 -2
- package/build-module/dom/strip-html.js.map +1 -1
- package/build-module/focusable.js +29 -18
- package/build-module/focusable.js.map +1 -1
- package/build-module/phrasing-content.js +1 -1
- package/build-module/phrasing-content.js.map +1 -1
- package/build-module/tabbable.js +2 -2
- package/build-module/tabbable.js.map +1 -1
- package/build-types/dom/caret-range-from-point.d.ts +12 -4
- package/build-types/dom/caret-range-from-point.d.ts.map +1 -1
- package/build-types/dom/clean-node-list.d.ts +2 -2
- package/build-types/dom/document-has-text-selection.d.ts +1 -1
- package/build-types/dom/document-has-uncollapsed-selection.d.ts +2 -2
- package/build-types/dom/is-edge.d.ts.map +1 -1
- package/build-types/dom/place-caret-at-edge.d.ts +9 -0
- package/build-types/dom/place-caret-at-edge.d.ts.map +1 -0
- package/build-types/dom/place-caret-at-horizontal-edge.d.ts.map +1 -1
- package/build-types/dom/place-caret-at-vertical-edge.d.ts +4 -5
- package/build-types/dom/place-caret-at-vertical-edge.d.ts.map +1 -1
- package/build-types/dom/strip-html.d.ts.map +1 -1
- package/build-types/focusable.d.ts +11 -2
- package/build-types/focusable.d.ts.map +1 -1
- package/build-types/phrasing-content.d.ts +2 -4
- package/build-types/phrasing-content.d.ts.map +1 -1
- package/build-types/tabbable.d.ts +3 -3
- package/build-types/tabbable.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/dom/caret-range-from-point.js +8 -3
- package/src/dom/document-has-text-selection.js +1 -1
- package/src/dom/document-has-uncollapsed-selection.js +2 -2
- package/src/dom/is-edge.js +4 -1
- package/src/dom/place-caret-at-edge.js +95 -0
- package/src/dom/place-caret-at-horizontal-edge.js +2 -83
- package/src/dom/place-caret-at-vertical-edge.js +6 -64
- package/src/dom/strip-html.js +12 -5
- package/src/dom/test/strip-html.js +64 -0
- package/src/focusable.js +37 -32
- package/src/phrasing-content.js +1 -1
- package/src/tabbable.js +2 -4
- package/src/test/dom.js +8 -15
- package/tsconfig.tsbuildinfo +1 -1170
@@ -0,0 +1,95 @@
|
|
1
|
+
/**
|
2
|
+
* Internal dependencies
|
3
|
+
*/
|
4
|
+
import hiddenCaretRangeFromPoint from './hidden-caret-range-from-point';
|
5
|
+
import { assertIsDefined } from '../utils/assert-is-defined';
|
6
|
+
import isInputOrTextArea from './is-input-or-text-area';
|
7
|
+
import isRTL from './is-rtl';
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Gets the range to place.
|
11
|
+
*
|
12
|
+
* @param {HTMLElement} container Focusable element.
|
13
|
+
* @param {boolean} isReverse True for end, false for start.
|
14
|
+
* @param {number|undefined} x X coordinate to vertically position.
|
15
|
+
*
|
16
|
+
* @return {Range|null} The range to place.
|
17
|
+
*/
|
18
|
+
function getRange( container, isReverse, x ) {
|
19
|
+
const { ownerDocument } = container;
|
20
|
+
// In the case of RTL scripts, the horizontal edge is at the opposite side.
|
21
|
+
const isReverseDir = isRTL( container ) ? ! isReverse : isReverse;
|
22
|
+
const containerRect = container.getBoundingClientRect();
|
23
|
+
// When placing at the end (isReverse), find the closest range to the bottom
|
24
|
+
// right corner. When placing at the start, to the top left corner.
|
25
|
+
if ( x === undefined ) {
|
26
|
+
x = isReverse ? containerRect.right - 1 : containerRect.left + 1;
|
27
|
+
}
|
28
|
+
const y = isReverseDir ? containerRect.bottom - 1 : containerRect.top + 1;
|
29
|
+
return hiddenCaretRangeFromPoint( ownerDocument, x, y, container );
|
30
|
+
}
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Places the caret at start or end of a given element.
|
34
|
+
*
|
35
|
+
* @param {HTMLElement} container Focusable element.
|
36
|
+
* @param {boolean} isReverse True for end, false for start.
|
37
|
+
* @param {number|undefined} x X coordinate to vertically position.
|
38
|
+
*/
|
39
|
+
export default function placeCaretAtEdge( container, isReverse, x ) {
|
40
|
+
if ( ! container ) {
|
41
|
+
return;
|
42
|
+
}
|
43
|
+
|
44
|
+
container.focus();
|
45
|
+
|
46
|
+
if ( isInputOrTextArea( container ) ) {
|
47
|
+
// The element may not support selection setting.
|
48
|
+
if ( typeof container.selectionStart !== 'number' ) {
|
49
|
+
return;
|
50
|
+
}
|
51
|
+
|
52
|
+
if ( isReverse ) {
|
53
|
+
container.selectionStart = container.value.length;
|
54
|
+
container.selectionEnd = container.value.length;
|
55
|
+
} else {
|
56
|
+
container.selectionStart = 0;
|
57
|
+
container.selectionEnd = 0;
|
58
|
+
}
|
59
|
+
|
60
|
+
return;
|
61
|
+
}
|
62
|
+
|
63
|
+
if ( ! container.isContentEditable ) {
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
|
67
|
+
let range = getRange( container, isReverse, x );
|
68
|
+
|
69
|
+
// If no range range can be created or it is outside the container, the
|
70
|
+
// element may be out of view.
|
71
|
+
if (
|
72
|
+
! range ||
|
73
|
+
! range.startContainer ||
|
74
|
+
! container.contains( range.startContainer )
|
75
|
+
) {
|
76
|
+
container.scrollIntoView( isReverse );
|
77
|
+
range = range = getRange( container, isReverse, x );
|
78
|
+
|
79
|
+
if (
|
80
|
+
! range ||
|
81
|
+
! range.startContainer ||
|
82
|
+
! container.contains( range.startContainer )
|
83
|
+
) {
|
84
|
+
return;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
const { ownerDocument } = container;
|
89
|
+
const { defaultView } = ownerDocument;
|
90
|
+
assertIsDefined( defaultView, 'defaultView' );
|
91
|
+
const selection = defaultView.getSelection();
|
92
|
+
assertIsDefined( selection, 'selection' );
|
93
|
+
selection.removeAllRanges();
|
94
|
+
selection.addRange( range );
|
95
|
+
}
|
@@ -1,34 +1,7 @@
|
|
1
1
|
/**
|
2
2
|
* Internal dependencies
|
3
3
|
*/
|
4
|
-
import
|
5
|
-
|
6
|
-
/**
|
7
|
-
* Internal dependencies
|
8
|
-
*/
|
9
|
-
import hiddenCaretRangeFromPoint from './hidden-caret-range-from-point';
|
10
|
-
import isInputOrTextArea from './is-input-or-text-area';
|
11
|
-
import isRTL from './is-rtl';
|
12
|
-
|
13
|
-
/**
|
14
|
-
* Gets the range to place.
|
15
|
-
*
|
16
|
-
* @param {HTMLElement} container Focusable element.
|
17
|
-
* @param {boolean} isReverse True for end, false for start.
|
18
|
-
*
|
19
|
-
* @return {Range|null} The range to place.
|
20
|
-
*/
|
21
|
-
function getRange( container, isReverse ) {
|
22
|
-
const { ownerDocument } = container;
|
23
|
-
// In the case of RTL scripts, the horizontal edge is at the opposite side.
|
24
|
-
const isReverseDir = isRTL( container ) ? ! isReverse : isReverse;
|
25
|
-
const containerRect = container.getBoundingClientRect();
|
26
|
-
// When placing at the end (isReverse), find the closest range to the bottom
|
27
|
-
// right corner. When placing at the start, to the top left corner.
|
28
|
-
const x = isReverse ? containerRect.right - 1 : containerRect.left + 1;
|
29
|
-
const y = isReverseDir ? containerRect.bottom - 1 : containerRect.top + 1;
|
30
|
-
return hiddenCaretRangeFromPoint( ownerDocument, x, y, container );
|
31
|
-
}
|
4
|
+
import placeCaretAtEdge from './place-caret-at-edge';
|
32
5
|
|
33
6
|
/**
|
34
7
|
* Places the caret at start or end of a given element.
|
@@ -37,59 +10,5 @@ function getRange( container, isReverse ) {
|
|
37
10
|
* @param {boolean} isReverse True for end, false for start.
|
38
11
|
*/
|
39
12
|
export default function placeCaretAtHorizontalEdge( container, isReverse ) {
|
40
|
-
|
41
|
-
return;
|
42
|
-
}
|
43
|
-
|
44
|
-
container.focus();
|
45
|
-
|
46
|
-
if ( isInputOrTextArea( container ) ) {
|
47
|
-
// The element may not support selection setting.
|
48
|
-
if ( typeof container.selectionStart !== 'number' ) {
|
49
|
-
return;
|
50
|
-
}
|
51
|
-
|
52
|
-
if ( isReverse ) {
|
53
|
-
container.selectionStart = container.value.length;
|
54
|
-
container.selectionEnd = container.value.length;
|
55
|
-
} else {
|
56
|
-
container.selectionStart = 0;
|
57
|
-
container.selectionEnd = 0;
|
58
|
-
}
|
59
|
-
|
60
|
-
return;
|
61
|
-
}
|
62
|
-
|
63
|
-
if ( ! container.isContentEditable ) {
|
64
|
-
return;
|
65
|
-
}
|
66
|
-
|
67
|
-
let range = getRange( container, isReverse );
|
68
|
-
|
69
|
-
// If no range range can be created or it is outside the container, the
|
70
|
-
// element may be out of view.
|
71
|
-
if (
|
72
|
-
! range ||
|
73
|
-
! range.startContainer ||
|
74
|
-
! container.contains( range.startContainer )
|
75
|
-
) {
|
76
|
-
container.scrollIntoView( isReverse );
|
77
|
-
range = getRange( container, isReverse );
|
78
|
-
|
79
|
-
if (
|
80
|
-
! range ||
|
81
|
-
! range.startContainer ||
|
82
|
-
! container.contains( range.startContainer )
|
83
|
-
) {
|
84
|
-
return;
|
85
|
-
}
|
86
|
-
}
|
87
|
-
|
88
|
-
const { ownerDocument } = container;
|
89
|
-
const { defaultView } = ownerDocument;
|
90
|
-
assertIsDefined( defaultView, 'defaultView' );
|
91
|
-
const selection = defaultView.getSelection();
|
92
|
-
assertIsDefined( selection, 'selection' );
|
93
|
-
selection.removeAllRanges();
|
94
|
-
selection.addRange( range );
|
13
|
+
return placeCaretAtEdge( container, isReverse, undefined );
|
95
14
|
}
|
@@ -1,73 +1,15 @@
|
|
1
1
|
/**
|
2
2
|
* Internal dependencies
|
3
3
|
*/
|
4
|
-
import
|
5
|
-
import hiddenCaretRangeFromPoint from './hidden-caret-range-from-point';
|
6
|
-
import { assertIsDefined } from '../utils/assert-is-defined';
|
4
|
+
import placeCaretAtEdge from './place-caret-at-edge';
|
7
5
|
|
8
6
|
/**
|
9
7
|
* Places the caret at the top or bottom of a given element.
|
10
8
|
*
|
11
|
-
* @param {HTMLElement} container
|
12
|
-
* @param {boolean} isReverse
|
13
|
-
* @param {DOMRect} [rect]
|
14
|
-
* @param {boolean} [mayUseScroll=true] True to allow scrolling, false to disallow.
|
9
|
+
* @param {HTMLElement} container Focusable element.
|
10
|
+
* @param {boolean} isReverse True for bottom, false for top.
|
11
|
+
* @param {DOMRect} [rect] The rectangle to position the caret with.
|
15
12
|
*/
|
16
|
-
export default function placeCaretAtVerticalEdge(
|
17
|
-
container,
|
18
|
-
isReverse,
|
19
|
-
rect,
|
20
|
-
mayUseScroll = true
|
21
|
-
) {
|
22
|
-
if ( ! container ) {
|
23
|
-
return;
|
24
|
-
}
|
25
|
-
|
26
|
-
if ( ! rect || ! container.isContentEditable ) {
|
27
|
-
placeCaretAtHorizontalEdge( container, isReverse );
|
28
|
-
return;
|
29
|
-
}
|
30
|
-
|
31
|
-
container.focus();
|
32
|
-
|
33
|
-
// Offset by a buffer half the height of the caret rect. This is needed
|
34
|
-
// because caretRangeFromPoint may default to the end of the selection if
|
35
|
-
// offset is too close to the edge. It's unclear how to precisely calculate
|
36
|
-
// this threshold; it may be the padded area of some combination of line
|
37
|
-
// height, caret height, and font size. The buffer offset is effectively
|
38
|
-
// equivalent to a point at half the height of a line of text.
|
39
|
-
const buffer = rect.height / 2;
|
40
|
-
const editableRect = container.getBoundingClientRect();
|
41
|
-
const x = rect.left;
|
42
|
-
const y = isReverse
|
43
|
-
? editableRect.bottom - buffer
|
44
|
-
: editableRect.top + buffer;
|
45
|
-
|
46
|
-
const { ownerDocument } = container;
|
47
|
-
const { defaultView } = ownerDocument;
|
48
|
-
const range = hiddenCaretRangeFromPoint( ownerDocument, x, y, container );
|
49
|
-
|
50
|
-
if ( ! range || ! container.contains( range.startContainer ) ) {
|
51
|
-
if (
|
52
|
-
mayUseScroll &&
|
53
|
-
( ! range ||
|
54
|
-
! range.startContainer ||
|
55
|
-
! range.startContainer.contains( container ) )
|
56
|
-
) {
|
57
|
-
// Might be out of view.
|
58
|
-
// Easier than attempting to calculate manually.
|
59
|
-
container.scrollIntoView( isReverse );
|
60
|
-
placeCaretAtVerticalEdge( container, isReverse, rect, false );
|
61
|
-
return;
|
62
|
-
}
|
63
|
-
|
64
|
-
placeCaretAtHorizontalEdge( container, isReverse );
|
65
|
-
return;
|
66
|
-
}
|
67
|
-
|
68
|
-
assertIsDefined( defaultView, 'defaultView' );
|
69
|
-
const selection = defaultView.getSelection();
|
70
|
-
assertIsDefined( selection, 'selection' );
|
71
|
-
selection.removeAllRanges();
|
72
|
-
selection.addRange( range );
|
13
|
+
export default function placeCaretAtVerticalEdge( container, isReverse, rect ) {
|
14
|
+
return placeCaretAtEdge( container, isReverse, rect?.left );
|
73
15
|
}
|
package/src/dom/strip-html.js
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
/**
|
2
|
+
* Internal dependencies
|
3
|
+
*/
|
4
|
+
import safeHTML from './safe-html';
|
5
|
+
|
1
6
|
/**
|
2
7
|
* Removes any HTML tags from the provided string.
|
3
8
|
*
|
@@ -6,9 +11,11 @@
|
|
6
11
|
* @return {string} The text content with any html removed.
|
7
12
|
*/
|
8
13
|
export default function stripHTML( html ) {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
// Remove any script tags or on* attributes otherwise their *contents* will be left
|
15
|
+
// in place following removal of HTML tags.
|
16
|
+
html = safeHTML( html );
|
17
|
+
|
18
|
+
const doc = document.implementation.createHTMLDocument( '' );
|
19
|
+
doc.body.innerHTML = html;
|
20
|
+
return doc.body.textContent || '';
|
14
21
|
}
|
@@ -0,0 +1,64 @@
|
|
1
|
+
/**
|
2
|
+
* Internal dependencies
|
3
|
+
*/
|
4
|
+
import stripHTML from '../strip-html';
|
5
|
+
|
6
|
+
describe( 'stripHTML', () => {
|
7
|
+
it( 'should strip valid HTML, scripts and on attributes', () => {
|
8
|
+
const input = `<strong onClick="alert('and on attributes')">Here is some text</strong> that contains <em>HTML markup</em><script>alert("and scripts")</script>.`;
|
9
|
+
const output = 'Here is some text that contains HTML markup.';
|
10
|
+
expect( stripHTML( input ) ).toBe( output );
|
11
|
+
} );
|
12
|
+
|
13
|
+
it( 'should strip invalid HTML, scripts and on attributes', () => {
|
14
|
+
const input = `<strong onClick="alert('and on attributes')">Here is some text</em> <p></div>that contains HTML markup</p><script>alert("and scripts")</script>.`;
|
15
|
+
const output = 'Here is some text that contains HTML markup.';
|
16
|
+
expect( stripHTML( input ) ).toBe( output );
|
17
|
+
} );
|
18
|
+
|
19
|
+
describe( 'whitespace preservation', () => {
|
20
|
+
it( 'should preserve leading spaces', () => {
|
21
|
+
const input =
|
22
|
+
' <strong>Here is some text</strong> with <em>leading spaces</em>.';
|
23
|
+
const output = ' Here is some text with leading spaces.';
|
24
|
+
expect( stripHTML( input ) ).toBe( output );
|
25
|
+
} );
|
26
|
+
|
27
|
+
it( 'should preserve leading spaces with HTML', () => {
|
28
|
+
const input =
|
29
|
+
'<strong> Here is some text</strong> with <em>leading spaces</em>.';
|
30
|
+
const output = ' Here is some text with leading spaces.';
|
31
|
+
expect( stripHTML( input ) ).toBe( output );
|
32
|
+
} );
|
33
|
+
|
34
|
+
it( 'should preserve trailing spaces with HTML', () => {
|
35
|
+
const input =
|
36
|
+
'<strong>Here is some text</strong> with <em>trailing spaces</em>. ';
|
37
|
+
const output = 'Here is some text with trailing spaces. ';
|
38
|
+
expect( stripHTML( input ) ).toBe( output );
|
39
|
+
} );
|
40
|
+
|
41
|
+
it( 'should preserve consecutive spaces within string', () => {
|
42
|
+
const input =
|
43
|
+
'<strong>Here is some text</strong> with <em>a lot of spaces inside</em>.';
|
44
|
+
const output =
|
45
|
+
'Here is some text with a lot of spaces inside.';
|
46
|
+
expect( stripHTML( input ) ).toBe( output );
|
47
|
+
} );
|
48
|
+
|
49
|
+
it( 'should preserve new lines in multi-line HTML string', () => {
|
50
|
+
const input = `<div>
|
51
|
+
Here is some
|
52
|
+
<em>text</em>
|
53
|
+
with new lines
|
54
|
+
</div>`;
|
55
|
+
|
56
|
+
const output = `
|
57
|
+
Here is some
|
58
|
+
text
|
59
|
+
with new lines
|
60
|
+
`;
|
61
|
+
expect( stripHTML( input ) ).toBe( output );
|
62
|
+
} );
|
63
|
+
} );
|
64
|
+
} );
|
package/src/focusable.js
CHANGED
@@ -17,19 +17,32 @@
|
|
17
17
|
* - https://w3c.github.io/html/editing.html#data-model
|
18
18
|
*/
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
20
|
+
/**
|
21
|
+
* Returns a CSS selector used to query for focusable elements.
|
22
|
+
*
|
23
|
+
* @param {boolean} sequential If set, only query elements that are sequentially
|
24
|
+
* focusable. Non-interactive elements with a
|
25
|
+
* negative `tabindex` are focusable but not
|
26
|
+
* sequentially focusable.
|
27
|
+
* https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute
|
28
|
+
*
|
29
|
+
* @return {string} CSS selector.
|
30
|
+
*/
|
31
|
+
function buildSelector( sequential ) {
|
32
|
+
return [
|
33
|
+
sequential ? '[tabindex]:not([tabindex^="-"])' : '[tabindex]',
|
34
|
+
'a[href]',
|
35
|
+
'button:not([disabled])',
|
36
|
+
'input:not([type="hidden"]):not([disabled])',
|
37
|
+
'select:not([disabled])',
|
38
|
+
'textarea:not([disabled])',
|
39
|
+
'iframe:not([tabindex^="-"])',
|
40
|
+
'object',
|
41
|
+
'embed',
|
42
|
+
'area[href]',
|
43
|
+
'[contenteditable]:not([contenteditable=false])',
|
44
|
+
].join( ',' );
|
45
|
+
}
|
33
46
|
|
34
47
|
/**
|
35
48
|
* Returns true if the specified element is visible (i.e. neither display: none
|
@@ -47,21 +60,6 @@ function isVisible( element ) {
|
|
47
60
|
);
|
48
61
|
}
|
49
62
|
|
50
|
-
/**
|
51
|
-
* Returns true if the specified element should be skipped from focusable elements.
|
52
|
-
* For now it rather specific for `iframes` and if tabindex attribute is set to -1.
|
53
|
-
*
|
54
|
-
* @param {Element} element DOM element to test.
|
55
|
-
*
|
56
|
-
* @return {boolean} Whether element should be skipped from focusable elements.
|
57
|
-
*/
|
58
|
-
function skipFocus( element ) {
|
59
|
-
return (
|
60
|
-
element.nodeName.toLowerCase() === 'iframe' &&
|
61
|
-
element.getAttribute( 'tabindex' ) === '-1'
|
62
|
-
);
|
63
|
-
}
|
64
|
-
|
65
63
|
/**
|
66
64
|
* Returns true if the specified area element is a valid focusable element, or
|
67
65
|
* false otherwise. Area is only focusable if within a map where a named map
|
@@ -88,18 +86,25 @@ function isValidFocusableArea( element ) {
|
|
88
86
|
/**
|
89
87
|
* Returns all focusable elements within a given context.
|
90
88
|
*
|
91
|
-
* @param {Element} context
|
89
|
+
* @param {Element} context Element in which to search.
|
90
|
+
* @param {Object} [options]
|
91
|
+
* @param {boolean} [options.sequential] If set, only return elements that are
|
92
|
+
* sequentially focusable.
|
93
|
+
* Non-interactive elements with a
|
94
|
+
* negative `tabindex` are focusable but
|
95
|
+
* not sequentially focusable.
|
96
|
+
* https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute
|
92
97
|
*
|
93
98
|
* @return {Element[]} Focusable elements.
|
94
99
|
*/
|
95
|
-
export function find( context ) {
|
100
|
+
export function find( context, { sequential = false } = {} ) {
|
96
101
|
/* eslint-disable jsdoc/no-undefined-types */
|
97
102
|
/** @type {NodeListOf<HTMLElement>} */
|
98
103
|
/* eslint-enable jsdoc/no-undefined-types */
|
99
|
-
const elements = context.querySelectorAll(
|
104
|
+
const elements = context.querySelectorAll( buildSelector( sequential ) );
|
100
105
|
|
101
106
|
return Array.from( elements ).filter( ( element ) => {
|
102
|
-
if ( ! isVisible( element )
|
107
|
+
if ( ! isVisible( element ) ) {
|
103
108
|
return false;
|
104
109
|
}
|
105
110
|
|
package/src/phrasing-content.js
CHANGED
package/src/tabbable.js
CHANGED
@@ -181,10 +181,8 @@ export function findNext( element ) {
|
|
181
181
|
const focusables = findFocusable( element.ownerDocument.body );
|
182
182
|
const index = focusables.indexOf( element );
|
183
183
|
|
184
|
-
// Remove all focusables before and
|
185
|
-
const remaining = focusables
|
186
|
-
.slice( index + 1 )
|
187
|
-
.filter( ( node ) => ! element.contains( node ) );
|
184
|
+
// Remove all focusables before and including `element`.
|
185
|
+
const remaining = focusables.slice( index + 1 );
|
188
186
|
|
189
187
|
return first( filterTabbable( remaining ) );
|
190
188
|
}
|
package/src/test/dom.js
CHANGED
@@ -5,7 +5,6 @@ import {
|
|
5
5
|
isHorizontalEdge,
|
6
6
|
placeCaretAtHorizontalEdge,
|
7
7
|
isTextField,
|
8
|
-
__unstableStripHTML as stripHTML,
|
9
8
|
isNumberInput,
|
10
9
|
removeInvalidHTML,
|
11
10
|
isEmpty,
|
@@ -84,6 +83,14 @@ describe( 'DOM', () => {
|
|
84
83
|
expect( isHorizontalEdge( div, true ) ).toBe( true );
|
85
84
|
expect( isHorizontalEdge( div, false ) ).toBe( true );
|
86
85
|
} );
|
86
|
+
|
87
|
+
it( 'should return true for input types that do not have selection ranges', () => {
|
88
|
+
const input = document.createElement( 'input' );
|
89
|
+
input.setAttribute( 'type', 'checkbox' );
|
90
|
+
parent.appendChild( input );
|
91
|
+
expect( isHorizontalEdge( input, true ) ).toBe( true );
|
92
|
+
expect( isHorizontalEdge( input, false ) ).toBe( true );
|
93
|
+
} );
|
87
94
|
} );
|
88
95
|
|
89
96
|
describe( 'placeCaretAtHorizontalEdge', () => {
|
@@ -186,20 +193,6 @@ describe( 'DOM', () => {
|
|
186
193
|
);
|
187
194
|
} );
|
188
195
|
} );
|
189
|
-
|
190
|
-
describe( 'stripHTML', () => {
|
191
|
-
it( 'removes any HTML from a text string', () => {
|
192
|
-
expect( stripHTML( 'This is <em>emphasized</em>' ) ).toBe(
|
193
|
-
'This is emphasized'
|
194
|
-
);
|
195
|
-
} );
|
196
|
-
|
197
|
-
it( 'removes script tags, but does not execute them', () => {
|
198
|
-
const html = 'This will not <script>throw "Error"</script>';
|
199
|
-
expect( stripHTML( html ) ).toBe( 'This will not throw "Error"' );
|
200
|
-
expect( () => stripHTML( html ) ).not.toThrow();
|
201
|
-
} );
|
202
|
-
} );
|
203
196
|
} );
|
204
197
|
|
205
198
|
describe( 'removeInvalidHTML', () => {
|