Comparing version 1.11.0 to 1.12.0
Next Release | ||
------------- | ||
1.12.0 | ||
------ | ||
* Add support for different styles during iframe focus/blur events. | ||
1.11.0 | ||
------ | ||
* Create a custom dispatch to override parent.postMessage | ||
* Create a custom dispatch to override parent.postMessage | ||
@@ -7,0 +11,0 @@ 1.10.1 |
@@ -82,3 +82,4 @@ "use strict"; | ||
_ref$customMethods = _ref.customMethods, | ||
customMethods = _ref$customMethods === void 0 ? {} : _ref$customMethods; | ||
customMethods = _ref$customMethods === void 0 ? {} : _ref$customMethods, | ||
focusIndicator = _ref.focusIndicator; | ||
@@ -93,2 +94,3 @@ this.source = source; | ||
this.resizeConfig = resizeConfig; | ||
this.focusIndicator = focusIndicator || null; | ||
var self = this; | ||
@@ -133,2 +135,17 @@ this.JSONRPC = new _jsonrpcDispatch.default(self.send, _objectSpread({ | ||
}, | ||
setFocus: function setFocus() { | ||
if (self.focusIndicator && self.focusIndicator.classNameFocusStyle) { | ||
self.iframe.classList.add(self.focusIndicator.classNameFocusStyle); | ||
} | ||
return Promise.resolve(); | ||
}, | ||
setBlur: function setBlur() { | ||
// Removing the focus style className | ||
self.iframe.classList.remove(self.focusIndicator.classNameFocusStyle); | ||
return Promise.resolve(); | ||
}, | ||
isScrollingEnabled: function isScrollingEnabled() { | ||
return Promise.resolve(self.iframe.getAttribute('scrolling') !== 'no'); | ||
}, | ||
event: function event(_event, detail) { | ||
@@ -135,0 +152,0 @@ self.emit(_event, detail); |
@@ -12,2 +12,4 @@ "use strict"; | ||
exports.getOffsetHeightToBody = getOffsetHeightToBody; | ||
exports.isContentScrollable = isContentScrollable; | ||
exports.hasInteractableElement = hasInteractableElement; | ||
@@ -148,2 +150,27 @@ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); | ||
return !node ? 0 : getOffsetToBody(node) + node.scrollHeight; | ||
} | ||
/** | ||
* This function returns boolean value representing whether scroll width or scroll height | ||
* is larger than the client width or client height. It indicates that the content is larger | ||
* than the viewable area and can be scrolled. | ||
* | ||
* @returns boolean true - when the content is scrollable, false - when the content is not scrollable | ||
*/ | ||
function isContentScrollable() { | ||
return document.documentElement.scrollHeight > document.documentElement.clientHeight || document.body.scrollHeight > document.body.clientHeight || document.documentElement.scrollWidth > document.documentElement.clientWidth || document.body.scrollWidth > document.body.clientWidth; | ||
} | ||
/** | ||
* Determines if the document has interactable element in it. | ||
* | ||
* @returns boolean true - when there is interactable element in the document, false otherwise | ||
*/ | ||
function hasInteractableElement() { | ||
var interactableElementSelector = 'a[href]:not([tabindex=\'-1\']), area[href]:not([tabindex=\'-1\']), input:not([disabled]):not([tabindex=\'-1\']), ' + "select:not([disabled]):not([tabindex='-1']), textarea:not([disabled]):not([tabindex='-1']), button:not([disabled]):not([tabindex='-1']), " + "[contentEditable=true]:not([tabindex='-1'])"; | ||
return (0, _toConsumableArray2.default)(document.body.querySelectorAll("".concat(interactableElementSelector))).some(function (element) { | ||
return !element.hasAttribute('disabled') && !element.getAttribute('aria-hidden') && !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length) && window.getComputedStyle(element).visibility !== 'hidden' && element.closest('[inert]') === null; | ||
}); | ||
} |
@@ -107,2 +107,11 @@ "use strict"; | ||
this.unload = this.unload.bind(this); | ||
this.handleLoadEvent = this.handleLoadEvent.bind(this); | ||
this.handleResizeEvent = this.handleResizeEvent.bind(this); | ||
this.handleFocusEvent = this.handleFocusEvent.bind(this); | ||
this.handleBlurEvent = this.handleBlurEvent.bind(this); | ||
this.isIframeScrollable = this.isIframeScrollable.bind(this); | ||
this.setTabIndexWhenRequired = this.setTabIndexWhenRequired.bind(this); | ||
this.hasInteractableElement = true; | ||
this.isScrollingEnabled = false; | ||
this.originalTabIndexValue = null; | ||
@@ -157,2 +166,6 @@ this.dispatchFunction = dispatchFunction || function (message, targetOrigin) { | ||
window.addEventListener('beforeunload', this.unload); | ||
window.addEventListener('load', this.handleLoadEvent, true); | ||
window.addEventListener('resize', this.handleResizeEvent, true); | ||
window.addEventListener('focus', this.handleFocusEvent, true); | ||
window.addEventListener('blur', this.handleBlurEvent, true); | ||
} | ||
@@ -422,2 +435,88 @@ /** | ||
} | ||
/** | ||
* When page load is completed, get the | ||
* iframe's scrolling config, and set the tabIndex | ||
* on the document.body of the embedded page if | ||
* necessary. | ||
*/ | ||
}, { | ||
key: "handleLoadEvent", | ||
value: function handleLoadEvent() { | ||
this.JSONRPC.request('isScrollingEnabled', []).then(this.setTabIndexWhenRequired); | ||
} | ||
/** | ||
* Handle the resize event and check if tabIndex is needed to be set or removed. | ||
* Depending on the content is scrollable or not, we need to update the tabIndex | ||
* accordingly so focus isn't getting in the document when not needed. | ||
*/ | ||
}, { | ||
key: "handleResizeEvent", | ||
value: function handleResizeEvent() { | ||
if (this.isIframeScrollable() && (0, _dimension.isContentScrollable)() && !this.hasInteractableElement) { | ||
// Set tabIndex="0" so focus can go into the document | ||
document.body.tabIndex = 0; | ||
} else if (this.originalTabIndexValue === null) { | ||
document.body.removeAttribute('tabIndex'); | ||
} else { | ||
document.body.tabIndex = this.originalTabIndexValue; | ||
} | ||
} | ||
/** | ||
* Handle the focus event by sending a message to the frame. | ||
*/ | ||
}, { | ||
key: "handleFocusEvent", | ||
value: function handleFocusEvent() { | ||
// Send message to the consumer/frame.js to handle `setFocus` event | ||
if (this.hasInteractableElement) { | ||
return; | ||
} | ||
this.JSONRPC.notification('setFocus'); | ||
} | ||
/** | ||
* Handle the blur event by sending a message to the frame. | ||
*/ | ||
}, { | ||
key: "handleBlurEvent", | ||
value: function handleBlurEvent() { | ||
// Send message to the consumer/frame.js to handle `setBlur` event | ||
this.JSONRPC.notification('setBlur'); | ||
} | ||
/** | ||
* Return the value of iframe's scroll config stored in `isScrollingEnabled`. | ||
* | ||
* @returns boolean true - when the iframe is scrollable, false - when the iframe is not scrollable | ||
*/ | ||
}, { | ||
key: "isIframeScrollable", | ||
value: function isIframeScrollable() { | ||
return this.isScrollingEnabled; | ||
} | ||
/** | ||
* Sets the `tabIndex=0` on the `document.body` if required | ||
* so focus can get into the embedded document. | ||
* | ||
* @param {*} iframeScrollingEnabled - boolean true when iframe's scrolling is true, | ||
* false when iframe's scrolling is false | ||
*/ | ||
}, { | ||
key: "setTabIndexWhenRequired", | ||
value: function setTabIndexWhenRequired(iframeScrollingEnabled) { | ||
this.isScrollingEnabled = iframeScrollingEnabled; | ||
this.hasInteractableElement = (0, _dimension.hasInteractableElement)(); | ||
this.originalTabIndexValue = document.body.getAttribute('tabIndex'); | ||
if (iframeScrollingEnabled && (0, _dimension.isContentScrollable)() && !this.hasInteractableElement) { | ||
// Set tabIndex="0" so focus can go into the document when | ||
// using tab key when scrolling is enabled | ||
document.body.tabIndex = 0; | ||
} | ||
} | ||
}]); | ||
@@ -424,0 +523,0 @@ return Application; |
{ | ||
"name": "xfc", | ||
"version": "1.11.0", | ||
"version": "1.12.0", | ||
"description": "A Cross Frame Container that handles securely embedding web content into a 3rd party domain", | ||
@@ -5,0 +5,0 @@ "author": "Cerner Corporation", |
@@ -92,3 +92,31 @@ # XFC (Cross-Frame-Container) | ||
### Applying Visual Focus Indicator Style on Iframes | ||
Certain configuration within this library such as `fixedWidth` or `fixedHeight` may prevent the content to be fully displayed within the viewport, and thereby needing `scrolling` to be enabled. For keyboard only users, it is important to display a visual focus indicator to indicate where the focus currently is at to help guide them with page navigation. | ||
Example CSS and JS Code: | ||
```css | ||
.iframe-focus-style { | ||
outline: 2px dashed #000; | ||
outline-offset: 1px; | ||
} | ||
``` | ||
```js | ||
XFC.Consumer.mount(document.body, | ||
'http://localprovider.com:8080/example/provider.html', | ||
{ | ||
iframeAttrs: { | ||
id: "frame-id", | ||
style: "margin-top: 4px; margin-bottom: 4px;", | ||
}, | ||
focusIndicator: { | ||
classNameFocusStyle: "iframe-focus-style", | ||
} | ||
} | ||
); | ||
``` | ||
Please note that the style associated with `classNameFocusStyle` will be applied to the iframe by setting the `classNameFocusStyle` to `class` attribute on the iframe when there is a focus event. During a blur event, the `classNameFocusStyle` name will be removed. | ||
### Monitoring Embedded App Lifecycles | ||
@@ -95,0 +123,0 @@ |
@@ -28,3 +28,3 @@ import { EventEmitter } from 'events'; | ||
init(container, source, { | ||
secret = null, resizeConfig = {}, iframeAttrs = {}, customMethods = {}, | ||
secret = null, resizeConfig = {}, iframeAttrs = {}, customMethods = {}, focusIndicator, | ||
} = {}) { | ||
@@ -39,2 +39,3 @@ this.source = source; | ||
this.resizeConfig = resizeConfig; | ||
this.focusIndicator = focusIndicator || null; | ||
@@ -80,2 +81,19 @@ const self = this; | ||
setFocus() { | ||
if (self.focusIndicator && self.focusIndicator.classNameFocusStyle) { | ||
self.iframe.classList.add(self.focusIndicator.classNameFocusStyle); | ||
} | ||
return Promise.resolve(); | ||
}, | ||
setBlur() { | ||
// Removing the focus style className | ||
self.iframe.classList.remove(self.focusIndicator.classNameFocusStyle); | ||
return Promise.resolve(); | ||
}, | ||
isScrollingEnabled() { | ||
return Promise.resolve(self.iframe.getAttribute('scrolling') !== 'no'); | ||
}, | ||
event(event, detail) { | ||
@@ -82,0 +100,0 @@ self.emit(event, detail); |
@@ -112,1 +112,34 @@ import logger from './logger'; | ||
} | ||
/** | ||
* This function returns boolean value representing whether scroll width or scroll height | ||
* is larger than the client width or client height. It indicates that the content is larger | ||
* than the viewable area and can be scrolled. | ||
* | ||
* @returns boolean true - when the content is scrollable, false - when the content is not scrollable | ||
*/ | ||
export function isContentScrollable() { | ||
return (document.documentElement.scrollHeight > document.documentElement.clientHeight | ||
|| document.body.scrollHeight > document.body.clientHeight | ||
|| document.documentElement.scrollWidth > document.documentElement.clientWidth | ||
|| document.body.scrollWidth > document.body.clientWidth); | ||
} | ||
/** | ||
* Determines if the document has interactable element in it. | ||
* | ||
* @returns boolean true - when there is interactable element in the document, false otherwise | ||
*/ | ||
export function hasInteractableElement() { | ||
const interactableElementSelector = 'a[href]:not([tabindex=\'-1\']), area[href]:not([tabindex=\'-1\']), input:not([disabled]):not([tabindex=\'-1\']), ' | ||
+ "select:not([disabled]):not([tabindex='-1']), textarea:not([disabled]):not([tabindex='-1']), button:not([disabled]):not([tabindex='-1']), " | ||
+ "[contentEditable=true]:not([tabindex='-1'])"; | ||
return [...document.body.querySelectorAll(`${interactableElementSelector}`)].some( | ||
(element) => !element.hasAttribute('disabled') | ||
&& !element.getAttribute('aria-hidden') | ||
&& !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length) | ||
&& window.getComputedStyle(element).visibility !== 'hidden' | ||
&& element.closest('[inert]') === null, | ||
); | ||
} |
@@ -9,3 +9,5 @@ /* eslint-disable no-mixed-operators, no-restricted-globals */ | ||
import logger from '../lib/logger'; | ||
import { getOffsetHeightToBody, calculateHeight, calculateWidth } from '../lib/dimension'; | ||
import { | ||
getOffsetHeightToBody, calculateHeight, calculateWidth, isContentScrollable, hasInteractableElement, | ||
} from '../lib/dimension'; | ||
@@ -54,2 +56,11 @@ /** Application class which represents an embedded application. */ | ||
this.unload = this.unload.bind(this); | ||
this.handleLoadEvent = this.handleLoadEvent.bind(this); | ||
this.handleResizeEvent = this.handleResizeEvent.bind(this); | ||
this.handleFocusEvent = this.handleFocusEvent.bind(this); | ||
this.handleBlurEvent = this.handleBlurEvent.bind(this); | ||
this.isIframeScrollable = this.isIframeScrollable.bind(this); | ||
this.setTabIndexWhenRequired = this.setTabIndexWhenRequired.bind(this); | ||
this.hasInteractableElement = true; | ||
this.isScrollingEnabled = false; | ||
this.originalTabIndexValue = null; | ||
@@ -86,2 +97,3 @@ this.dispatchFunction = dispatchFunction || ((message, targetOrigin) => { | ||
); | ||
observer.observe( | ||
@@ -111,2 +123,6 @@ document.body, | ||
window.addEventListener('beforeunload', this.unload); | ||
window.addEventListener('load', this.handleLoadEvent, true); | ||
window.addEventListener('resize', this.handleResizeEvent, true); | ||
window.addEventListener('focus', this.handleFocusEvent, true); | ||
window.addEventListener('blur', this.handleBlurEvent, true); | ||
} | ||
@@ -127,2 +143,3 @@ | ||
if (!this.resizeConfig) return; | ||
if (this.resizeConfig.customCal) { | ||
@@ -342,4 +359,79 @@ this.JSONRPC.notification('resize'); | ||
} | ||
/** | ||
* When page load is completed, get the | ||
* iframe's scrolling config, and set the tabIndex | ||
* on the document.body of the embedded page if | ||
* necessary. | ||
*/ | ||
handleLoadEvent() { | ||
this.JSONRPC.request('isScrollingEnabled', []) | ||
.then(this.setTabIndexWhenRequired); | ||
} | ||
/** | ||
* Handle the resize event and check if tabIndex is needed to be set or removed. | ||
* Depending on the content is scrollable or not, we need to update the tabIndex | ||
* accordingly so focus isn't getting in the document when not needed. | ||
*/ | ||
handleResizeEvent() { | ||
if (this.isIframeScrollable() && isContentScrollable() && !this.hasInteractableElement) { | ||
// Set tabIndex="0" so focus can go into the document | ||
document.body.tabIndex = 0; | ||
} else if (this.originalTabIndexValue === null) { | ||
document.body.removeAttribute('tabIndex'); | ||
} else { | ||
document.body.tabIndex = this.originalTabIndexValue; | ||
} | ||
} | ||
/** | ||
* Handle the focus event by sending a message to the frame. | ||
*/ | ||
handleFocusEvent() { | ||
// Send message to the consumer/frame.js to handle `setFocus` event | ||
if (this.hasInteractableElement) { | ||
return; | ||
} | ||
this.JSONRPC.notification('setFocus'); | ||
} | ||
/** | ||
* Handle the blur event by sending a message to the frame. | ||
*/ | ||
handleBlurEvent() { | ||
// Send message to the consumer/frame.js to handle `setBlur` event | ||
this.JSONRPC.notification('setBlur'); | ||
} | ||
/** | ||
* Return the value of iframe's scroll config stored in `isScrollingEnabled`. | ||
* | ||
* @returns boolean true - when the iframe is scrollable, false - when the iframe is not scrollable | ||
*/ | ||
isIframeScrollable() { | ||
return this.isScrollingEnabled; | ||
} | ||
/** | ||
* Sets the `tabIndex=0` on the `document.body` if required | ||
* so focus can get into the embedded document. | ||
* | ||
* @param {*} iframeScrollingEnabled - boolean true when iframe's scrolling is true, | ||
* false when iframe's scrolling is false | ||
*/ | ||
setTabIndexWhenRequired(iframeScrollingEnabled) { | ||
this.isScrollingEnabled = iframeScrollingEnabled; | ||
this.hasInteractableElement = hasInteractableElement(); | ||
this.originalTabIndexValue = document.body.getAttribute('tabIndex'); | ||
if (iframeScrollingEnabled && isContentScrollable() && !this.hasInteractableElement) { | ||
// Set tabIndex="0" so focus can go into the document when | ||
// using tab key when scrolling is enabled | ||
document.body.tabIndex = 0; | ||
} | ||
} | ||
} | ||
export default Application; |
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
111121
1932
329
1