@wordpress/dom
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -167,3 +167,9 @@ /** | ||
var selection = window.getSelection(); | ||
var selection = window.getSelection(); // Only consider the selection at the edge if the direction is towards the | ||
// edge. | ||
if (!selection.isCollapsed && isSelectionForward(selection) === isReverse) { | ||
return false; | ||
} | ||
var range = selection.rangeCount ? selection.getRangeAt(0) : null; | ||
@@ -181,5 +187,12 @@ | ||
var buffer = rangeRect.height / 2; | ||
var editableRect = container.getBoundingClientRect(); // Too low. | ||
var editableRect = container.getBoundingClientRect(); // Calculate a buffer that is half the line height. In some browsers, the | ||
// selection rectangle may not fill the entire height of the line, so we add | ||
// half the line height to the selection rectangle to ensure that it is well | ||
// over its line boundary. | ||
var _window$getComputedSt = window.getComputedStyle(container), | ||
lineHeight = _window$getComputedSt.lineHeight; | ||
var buffer = parseInt(lineHeight, 10) / 2; // Too low. | ||
if (isReverse && rangeRect.top - buffer > editableRect.top) { | ||
@@ -521,4 +534,4 @@ return false; | ||
// ...except when overflow is defined to be hidden or visible | ||
var _window$getComputedSt = window.getComputedStyle(node), | ||
overflowY = _window$getComputedSt.overflowY; | ||
var _window$getComputedSt2 = window.getComputedStyle(node), | ||
overflowY = _window$getComputedSt2.overflowY; | ||
@@ -525,0 +538,0 @@ if (/(auto|scroll)/.test(overflowY)) { |
@@ -6,2 +6,7 @@ /** | ||
import * as tabbable from './tabbable'; | ||
/** | ||
* Object grouping `focusable` and `tabbable` utils | ||
* under the keys with the same name. | ||
*/ | ||
export var focus = { | ||
@@ -8,0 +13,0 @@ focusable: focusable, |
/** | ||
* External dependencies | ||
*/ | ||
import { without } from 'lodash'; | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { find as findFocusable } from './focusable'; | ||
@@ -35,2 +40,43 @@ /** | ||
/** | ||
* Returns a stateful reducer function which constructs a filtered array of | ||
* tabbable elements, where at most one radio input is selected for a given | ||
* name, giving priority to checked input, falling back to the first | ||
* encountered. | ||
* | ||
* @return {Function} Radio group collapse reducer. | ||
*/ | ||
function createStatefulCollapseRadioGroup() { | ||
var CHOSEN_RADIO_BY_NAME = {}; | ||
return function collapseRadioGroup(result, element) { | ||
var nodeName = element.nodeName, | ||
type = element.type, | ||
checked = element.checked, | ||
name = element.name; // For all non-radio tabbables, construct to array by concatenating. | ||
if (nodeName !== 'INPUT' || type !== 'radio' || !name) { | ||
return result.concat(element); | ||
} | ||
var hasChosen = CHOSEN_RADIO_BY_NAME.hasOwnProperty(name); // Omit by skipping concatenation if the radio element is not chosen. | ||
var isChosen = checked || !hasChosen; | ||
if (!isChosen) { | ||
return result; | ||
} // At this point, if there had been a chosen element, the current | ||
// element is checked and should take priority. Retroactively remove | ||
// the element which had previously been considered the chosen one. | ||
if (hasChosen) { | ||
var hadChosenElement = CHOSEN_RADIO_BY_NAME[name]; | ||
result = without(result, hadChosenElement); | ||
} | ||
CHOSEN_RADIO_BY_NAME[name] = element; | ||
return result.concat(element); | ||
}; | ||
} | ||
/** | ||
* An array map callback, returning an object with the element value and its | ||
@@ -47,2 +93,3 @@ * array index location as properties. This is used to emulate a proper stable | ||
function mapElementToObjectTabbable(element, index) { | ||
@@ -91,4 +138,4 @@ return { | ||
export function find(context) { | ||
return findFocusable(context).filter(isTabbableIndex).map(mapElementToObjectTabbable).sort(compareObjectTabbables).map(mapObjectTabbableToElement); | ||
return findFocusable(context).filter(isTabbableIndex).map(mapElementToObjectTabbable).sort(compareObjectTabbables).map(mapObjectTabbableToElement).reduce(createStatefulCollapseRadioGroup(), []); | ||
} | ||
//# sourceMappingURL=tabbable.js.map |
@@ -192,3 +192,9 @@ "use strict"; | ||
var selection = window.getSelection(); | ||
var selection = window.getSelection(); // Only consider the selection at the edge if the direction is towards the | ||
// edge. | ||
if (!selection.isCollapsed && isSelectionForward(selection) === isReverse) { | ||
return false; | ||
} | ||
var range = selection.rangeCount ? selection.getRangeAt(0) : null; | ||
@@ -206,5 +212,12 @@ | ||
var buffer = rangeRect.height / 2; | ||
var editableRect = container.getBoundingClientRect(); // Too low. | ||
var editableRect = container.getBoundingClientRect(); // Calculate a buffer that is half the line height. In some browsers, the | ||
// selection rectangle may not fill the entire height of the line, so we add | ||
// half the line height to the selection rectangle to ensure that it is well | ||
// over its line boundary. | ||
var _window$getComputedSt = window.getComputedStyle(container), | ||
lineHeight = _window$getComputedSt.lineHeight; | ||
var buffer = parseInt(lineHeight, 10) / 2; // Too low. | ||
if (isReverse && rangeRect.top - buffer > editableRect.top) { | ||
@@ -554,4 +567,4 @@ return false; | ||
// ...except when overflow is defined to be hidden or visible | ||
var _window$getComputedSt = window.getComputedStyle(node), | ||
overflowY = _window$getComputedSt.overflowY; | ||
var _window$getComputedSt2 = window.getComputedStyle(node), | ||
overflowY = _window$getComputedSt2.overflowY; | ||
@@ -558,0 +571,0 @@ if (/(auto|scroll)/.test(overflowY)) { |
@@ -33,2 +33,7 @@ "use strict"; | ||
*/ | ||
/** | ||
* Object grouping `focusable` and `tabbable` utils | ||
* under the keys with the same name. | ||
*/ | ||
var focus = { | ||
@@ -35,0 +40,0 @@ focusable: focusable, |
@@ -9,5 +9,11 @@ "use strict"; | ||
var _lodash = require("lodash"); | ||
var _focusable = require("./focusable"); | ||
/** | ||
* External dependencies | ||
*/ | ||
/** | ||
* Internal dependencies | ||
@@ -45,2 +51,44 @@ */ | ||
/** | ||
* Returns a stateful reducer function which constructs a filtered array of | ||
* tabbable elements, where at most one radio input is selected for a given | ||
* name, giving priority to checked input, falling back to the first | ||
* encountered. | ||
* | ||
* @return {Function} Radio group collapse reducer. | ||
*/ | ||
function createStatefulCollapseRadioGroup() { | ||
var CHOSEN_RADIO_BY_NAME = {}; | ||
return function collapseRadioGroup(result, element) { | ||
var nodeName = element.nodeName, | ||
type = element.type, | ||
checked = element.checked, | ||
name = element.name; // For all non-radio tabbables, construct to array by concatenating. | ||
if (nodeName !== 'INPUT' || type !== 'radio' || !name) { | ||
return result.concat(element); | ||
} | ||
var hasChosen = CHOSEN_RADIO_BY_NAME.hasOwnProperty(name); // Omit by skipping concatenation if the radio element is not chosen. | ||
var isChosen = checked || !hasChosen; | ||
if (!isChosen) { | ||
return result; | ||
} // At this point, if there had been a chosen element, the current | ||
// element is checked and should take priority. Retroactively remove | ||
// the element which had previously been considered the chosen one. | ||
if (hasChosen) { | ||
var hadChosenElement = CHOSEN_RADIO_BY_NAME[name]; | ||
result = (0, _lodash.without)(result, hadChosenElement); | ||
} | ||
CHOSEN_RADIO_BY_NAME[name] = element; | ||
return result.concat(element); | ||
}; | ||
} | ||
/** | ||
* An array map callback, returning an object with the element value and its | ||
@@ -101,4 +149,4 @@ * array index location as properties. This is used to emulate a proper stable | ||
function find(context) { | ||
return (0, _focusable.find)(context).filter(isTabbableIndex).map(mapElementToObjectTabbable).sort(compareObjectTabbables).map(mapObjectTabbableToElement); | ||
return (0, _focusable.find)(context).filter(isTabbableIndex).map(mapElementToObjectTabbable).sort(compareObjectTabbables).map(mapObjectTabbableToElement).reduce(createStatefulCollapseRadioGroup(), []); | ||
} | ||
//# sourceMappingURL=tabbable.js.map |
@@ -1,2 +0,2 @@ | ||
## 2.0.9 (Unreleased) | ||
## 2.1.0 (2019-03-06) | ||
@@ -6,2 +6,3 @@ ### Bug Fix | ||
- Update `isHorizontalEdge` to account for empty text nodes. | ||
- `tabbables.find` considers at most a single radio input for a given name. The checked input is given priority, falling back to the first in the tabindex-sorted set if there is no checked input. | ||
@@ -8,0 +9,0 @@ ## 2.0.8 (2019-01-03) |
{ | ||
"name": "@wordpress/dom", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "DOM utilities module for WordPress.", | ||
@@ -31,3 +31,3 @@ "author": "The WordPress Contributors", | ||
}, | ||
"gitHead": "80d228669adadb8dfcd24b8421517fed3be2d474" | ||
"gitHead": "1e024a20a20369af7bc9720a676fdd3837a3a105" | ||
} |
258
README.md
@@ -13,2 +13,260 @@ # DOM | ||
## API | ||
<!-- START TOKEN(Autogenerated API docs) --> | ||
### computeCaretRect | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Get the rectangle for the selection in a container. | ||
**Parameters** | ||
- **container** `Element`: Editable container. | ||
**Returns** | ||
`?DOMRect`: The rectangle. | ||
### documentHasSelection | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Check wether the current document has a selection. | ||
This checks both for focus in an input field and general text selection. | ||
**Returns** | ||
`boolean`: True if there is selection, false if not. | ||
### focus | ||
[src/index.js#L11-L11](src/index.js#L11-L11) | ||
Object grouping `focusable` and `tabbable` utils | ||
under the keys with the same name. | ||
### getOffsetParent | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Returns the closest positioned element, or null under any of the conditions | ||
of the offsetParent specification. Unlike offsetParent, this function is not | ||
limited to HTMLElement and accepts any Node (e.g. Node.TEXT_NODE). | ||
**Related** | ||
- <https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent> | ||
**Parameters** | ||
- **node** `Node`: Node from which to find offset parent. | ||
**Returns** | ||
`?Node`: Offset parent. | ||
### getRectangleFromRange | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Get the rectangle of a given Range. | ||
**Parameters** | ||
- **range** `Range`: The range. | ||
**Returns** | ||
`DOMRect`: The rectangle. | ||
### getScrollContainer | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Given a DOM node, finds the closest scrollable container node. | ||
**Parameters** | ||
- **node** `Element`: Node from which to start. | ||
**Returns** | ||
`?Element`: Scrollable container node, if found. | ||
### insertAfter | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Given two DOM nodes, inserts the former in the DOM as the next sibling of | ||
the latter. | ||
**Parameters** | ||
- **newNode** `Element`: Node to be inserted. | ||
- **referenceNode** `Element`: Node after which to perform the insertion. | ||
**Returns** | ||
`void`: | ||
### isEntirelySelected | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Check whether the contents of the element have been entirely selected. | ||
Returns true if there is no possibility of selection. | ||
**Parameters** | ||
- **element** `Element`: The element to check. | ||
**Returns** | ||
`boolean`: True if entirely selected, false if not. | ||
### isHorizontalEdge | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Check whether the selection is horizontally at the edge of the container. | ||
**Parameters** | ||
- **container** `Element`: Focusable element. | ||
- **isReverse** `boolean`: Set to true to check left, false for right. | ||
**Returns** | ||
`boolean`: True if at the horizontal edge, false if not. | ||
### isTextField | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Check whether the given element is a text field, where text field is defined | ||
by the ability to select within the input, or that it is contenteditable. | ||
See: <https://html.spec.whatwg.org/#textFieldSelection> | ||
**Parameters** | ||
- **element** `HTMLElement`: The HTML element. | ||
**Returns** | ||
`boolean`: True if the element is an text field, false if not. | ||
### isVerticalEdge | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Check whether the selection is vertically at the edge of the container. | ||
**Parameters** | ||
- **container** `Element`: Focusable element. | ||
- **isReverse** `boolean`: Set to true to check top, false for bottom. | ||
**Returns** | ||
`boolean`: True if at the edge, false if not. | ||
### placeCaretAtHorizontalEdge | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Places the caret at start or end of a given element. | ||
**Parameters** | ||
- **container** `Element`: Focusable element. | ||
- **isReverse** `boolean`: True for end, false for start. | ||
### placeCaretAtVerticalEdge | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Places the caret at the top or bottom of a given element. | ||
**Parameters** | ||
- **container** `Element`: Focusable element. | ||
- **isReverse** `boolean`: True for bottom, false for top. | ||
- **rect** `[DOMRect]`: The rectangle to position the caret with. | ||
- **mayUseScroll** `[boolean]`: True to allow scrolling, false to disallow. | ||
### remove | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Given a DOM node, removes it from the DOM. | ||
**Parameters** | ||
- **node** `Element`: Node to be removed. | ||
**Returns** | ||
`void`: | ||
### replace | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Given two DOM nodes, replaces the former with the latter in the DOM. | ||
**Parameters** | ||
- **processedNode** `Element`: Node to be removed. | ||
- **newNode** `Element`: Node to be inserted in its place. | ||
**Returns** | ||
`void`: | ||
### replaceTag | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Replaces the given node with a new node with the given tag name. | ||
**Parameters** | ||
- **node** `Element`: The node to replace | ||
- **tagName** `string`: The new tag name. | ||
**Returns** | ||
`Element`: The new node. | ||
### unwrap | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Unwrap the given node. This means any child nodes are moved to the parent. | ||
**Parameters** | ||
- **node** `Node`: The node to unwrap. | ||
**Returns** | ||
`void`: | ||
### wrap | ||
[src/index.js#L13-L13](src/index.js#L13-L13) | ||
Wraps the given node with a new node with the given tag name. | ||
**Parameters** | ||
- **newNode** `Element`: The node to insert. | ||
- **referenceNode** `Element`: The node to wrap. | ||
<!-- END TOKEN(Autogenerated API docs) --> | ||
<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> |
@@ -172,3 +172,14 @@ /** | ||
const selection = window.getSelection(); | ||
// Only consider the selection at the edge if the direction is towards the | ||
// edge. | ||
if ( | ||
! selection.isCollapsed && | ||
isSelectionForward( selection ) === isReverse | ||
) { | ||
return false; | ||
} | ||
const range = selection.rangeCount ? selection.getRangeAt( 0 ) : null; | ||
if ( ! range ) { | ||
@@ -184,5 +195,11 @@ return false; | ||
const buffer = rangeRect.height / 2; | ||
const editableRect = container.getBoundingClientRect(); | ||
// Calculate a buffer that is half the line height. In some browsers, the | ||
// selection rectangle may not fill the entire height of the line, so we add | ||
// half the line height to the selection rectangle to ensure that it is well | ||
// over its line boundary. | ||
const { lineHeight } = window.getComputedStyle( container ); | ||
const buffer = parseInt( lineHeight, 10 ) / 2; | ||
// Too low. | ||
@@ -189,0 +206,0 @@ if ( isReverse && rangeRect.top - buffer > editableRect.top ) { |
@@ -7,4 +7,8 @@ /** | ||
/** | ||
* Object grouping `focusable` and `tabbable` utils | ||
* under the keys with the same name. | ||
*/ | ||
export const focus = { focusable, tabbable }; | ||
export * from './dom'; |
/** | ||
* External dependencies | ||
*/ | ||
import { without } from 'lodash'; | ||
/** | ||
* Internal dependencies | ||
@@ -35,2 +40,43 @@ */ | ||
/** | ||
* Returns a stateful reducer function which constructs a filtered array of | ||
* tabbable elements, where at most one radio input is selected for a given | ||
* name, giving priority to checked input, falling back to the first | ||
* encountered. | ||
* | ||
* @return {Function} Radio group collapse reducer. | ||
*/ | ||
function createStatefulCollapseRadioGroup() { | ||
const CHOSEN_RADIO_BY_NAME = {}; | ||
return function collapseRadioGroup( result, element ) { | ||
const { nodeName, type, checked, name } = element; | ||
// For all non-radio tabbables, construct to array by concatenating. | ||
if ( nodeName !== 'INPUT' || type !== 'radio' || ! name ) { | ||
return result.concat( element ); | ||
} | ||
const hasChosen = CHOSEN_RADIO_BY_NAME.hasOwnProperty( name ); | ||
// Omit by skipping concatenation if the radio element is not chosen. | ||
const isChosen = checked || ! hasChosen; | ||
if ( ! isChosen ) { | ||
return result; | ||
} | ||
// At this point, if there had been a chosen element, the current | ||
// element is checked and should take priority. Retroactively remove | ||
// the element which had previously been considered the chosen one. | ||
if ( hasChosen ) { | ||
const hadChosenElement = CHOSEN_RADIO_BY_NAME[ name ]; | ||
result = without( result, hadChosenElement ); | ||
} | ||
CHOSEN_RADIO_BY_NAME[ name ] = element; | ||
return result.concat( element ); | ||
}; | ||
} | ||
/** | ||
* An array map callback, returning an object with the element value and its | ||
@@ -88,3 +134,4 @@ * array index location as properties. This is used to emulate a proper stable | ||
.sort( compareObjectTabbables ) | ||
.map( mapObjectTabbableToElement ); | ||
.map( mapObjectTabbableToElement ) | ||
.reduce( createStatefulCollapseRadioGroup(), [] ); | ||
} |
@@ -35,3 +35,99 @@ /** | ||
} ); | ||
it( 'consolidates radio group to the first, if unchecked', () => { | ||
const node = createElement( 'div' ); | ||
const firstRadio = createElement( 'input' ); | ||
firstRadio.type = 'radio'; | ||
firstRadio.name = 'a'; | ||
firstRadio.value = 'firstRadio'; | ||
const secondRadio = createElement( 'input' ); | ||
secondRadio.type = 'radio'; | ||
secondRadio.name = 'a'; | ||
secondRadio.value = 'secondRadio'; | ||
const text = createElement( 'input' ); | ||
text.type = 'text'; | ||
text.name = 'b'; | ||
const thirdRadio = createElement( 'input' ); | ||
thirdRadio.type = 'radio'; | ||
thirdRadio.name = 'a'; | ||
thirdRadio.value = 'thirdRadio'; | ||
const fourthRadio = createElement( 'input' ); | ||
fourthRadio.type = 'radio'; | ||
fourthRadio.name = 'b'; | ||
fourthRadio.value = 'fourthRadio'; | ||
const fifthRadio = createElement( 'input' ); | ||
fifthRadio.type = 'radio'; | ||
fifthRadio.name = 'b'; | ||
fifthRadio.value = 'fifthRadio'; | ||
node.appendChild( firstRadio ); | ||
node.appendChild( secondRadio ); | ||
node.appendChild( text ); | ||
node.appendChild( thirdRadio ); | ||
node.appendChild( fourthRadio ); | ||
node.appendChild( fifthRadio ); | ||
const tabbables = find( node ); | ||
expect( tabbables ).toEqual( [ | ||
firstRadio, | ||
text, | ||
fourthRadio, | ||
] ); | ||
} ); | ||
it( 'consolidates radio group to the checked', () => { | ||
const node = createElement( 'div' ); | ||
const firstRadio = createElement( 'input' ); | ||
firstRadio.type = 'radio'; | ||
firstRadio.name = 'a'; | ||
firstRadio.value = 'firstRadio'; | ||
const secondRadio = createElement( 'input' ); | ||
secondRadio.type = 'radio'; | ||
secondRadio.name = 'a'; | ||
secondRadio.value = 'secondRadio'; | ||
const text = createElement( 'input' ); | ||
text.type = 'text'; | ||
text.name = 'b'; | ||
const thirdRadio = createElement( 'input' ); | ||
thirdRadio.type = 'radio'; | ||
thirdRadio.name = 'a'; | ||
thirdRadio.value = 'thirdRadio'; | ||
thirdRadio.checked = true; | ||
node.appendChild( firstRadio ); | ||
node.appendChild( secondRadio ); | ||
node.appendChild( text ); | ||
node.appendChild( thirdRadio ); | ||
const tabbables = find( node ); | ||
expect( tabbables ).toEqual( [ | ||
text, | ||
thirdRadio, | ||
] ); | ||
} ); | ||
it( 'not consolidate unnamed radio inputs', () => { | ||
const node = createElement( 'div' ); | ||
const firstRadio = createElement( 'input' ); | ||
firstRadio.type = 'radio'; | ||
firstRadio.value = 'firstRadio'; | ||
const text = createElement( 'input' ); | ||
text.type = 'text'; | ||
text.name = 'b'; | ||
const secondRadio = createElement( 'input' ); | ||
secondRadio.type = 'radio'; | ||
secondRadio.value = 'secondRadio'; | ||
node.appendChild( firstRadio ); | ||
node.appendChild( text ); | ||
node.appendChild( secondRadio ); | ||
const tabbables = find( node ); | ||
expect( tabbables ).toEqual( [ | ||
firstRadio, | ||
text, | ||
secondRadio, | ||
] ); | ||
} ); | ||
} ); | ||
} ); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
213382
2748
272
1