@khanacademy/wonder-blocks-core
Advanced tools
Comparing version 2.7.1 to 3.0.0
@@ -32,4 +32,2 @@ // This file is auto-generated by gen-snapshot-tests.js | ||
import ClickableBehavior from "./../components/clickable-behavior.js"; | ||
describe("wonder-blocks-core", () => { | ||
@@ -36,0 +34,0 @@ it("example 1", () => { |
@@ -13,3 +13,2 @@ // @flow | ||
[ | ||
"ClickableBehavior", | ||
"Text", | ||
@@ -19,3 +18,2 @@ "View", | ||
"addStyle", | ||
"getClickableBehavior", | ||
"getElementIntersection", | ||
@@ -22,0 +20,0 @@ "IDProvider", |
@@ -1,4 +0,3 @@ | ||
import React, { Component, createElement, createContext } from 'react'; | ||
import React, { createElement, Component, createContext } from 'react'; | ||
import { StyleSheet, css } from 'aphrodite'; | ||
import { withRouter } from 'react-router-dom'; | ||
@@ -338,533 +337,2 @@ function _classCallCheck(instance, Constructor) { | ||
var getAppropriateTriggersForRole = function getAppropriateTriggersForRole(role) { | ||
switch (role) { | ||
// Triggers on ENTER, but not SPACE | ||
case "link": | ||
return { | ||
triggerOnEnter: true, | ||
triggerOnSpace: false | ||
}; | ||
// Triggers on SPACE, but not ENTER | ||
case "checkbox": | ||
case "radio": | ||
case "listbox": | ||
case "option": | ||
return { | ||
triggerOnEnter: false, | ||
triggerOnSpace: true | ||
}; | ||
// Triggers on both ENTER and SPACE | ||
case "button": | ||
case "menuitem": | ||
case "menu": | ||
default: | ||
return { | ||
triggerOnEnter: true, | ||
triggerOnSpace: true | ||
}; | ||
} | ||
}; | ||
var disabledHandlers = { | ||
onClick: function onClick() { | ||
return void 0; | ||
}, | ||
onMouseEnter: function onMouseEnter() { | ||
return void 0; | ||
}, | ||
onMouseLeave: function onMouseLeave() { | ||
return void 0; | ||
}, | ||
onMouseDown: function onMouseDown() { | ||
return void 0; | ||
}, | ||
onMouseUp: function onMouseUp() { | ||
return void 0; | ||
}, | ||
onDragStart: function onDragStart() { | ||
return void 0; | ||
}, | ||
onTouchStart: function onTouchStart() { | ||
return void 0; | ||
}, | ||
onTouchEnd: function onTouchEnd() { | ||
return void 0; | ||
}, | ||
onTouchCancel: function onTouchCancel() { | ||
return void 0; | ||
}, | ||
onKeyDown: function onKeyDown() { | ||
return void 0; | ||
}, | ||
onKeyUp: function onKeyUp() { | ||
return void 0; | ||
}, | ||
onFocus: function onFocus() { | ||
return void 0; | ||
}, | ||
onBlur: function onBlur() { | ||
return void 0; | ||
}, | ||
tabIndex: -1 | ||
}; | ||
var keyCodes = { | ||
enter: 13, | ||
space: 32 | ||
}; | ||
var startState = { | ||
hovered: false, | ||
focused: false, | ||
pressed: false, | ||
waiting: false | ||
}; | ||
/** | ||
* Add hover, focus, and active status updates to a clickable component. | ||
* | ||
* Via mouse: | ||
* | ||
* 1. Hover over button -> hover state | ||
* 2. Mouse down -> active state | ||
* 3. Mouse up -> default state | ||
* 4. Press tab -> focus state | ||
* | ||
* Via touch: | ||
* | ||
* 1. Touch down -> press state | ||
* 2. Touch up -> default state | ||
* | ||
* Via keyboard: | ||
* | ||
* 1. Tab to focus -> focus state | ||
* 2. Keydown (spacebar/enter) -> active state | ||
* 3. Keyup (spacebar/enter) -> focus state | ||
* | ||
* Warning: The event handlers returned (onClick, onMouseEnter, onMouseLeave, | ||
* onMouseDown, onMouseUp, onDragStart, onTouchStart, onTouchEnd, onTouchCancel, onKeyDown, | ||
* onKeyUp, onFocus, onBlur, tabIndex) should be passed on to the component | ||
* that has the ClickableBehavior. You cannot override these handlers without | ||
* potentially breaking the functionality of ClickableBehavior. | ||
* | ||
* There are internal props triggerOnEnter and triggerOnSpace that can be set | ||
* to false if one of those keys shouldn't count as a click on this component. | ||
* Be careful about setting those to false -- make certain that the component | ||
* shouldn't process that key. | ||
* | ||
* See [this document](https://docs.google.com/document/d/1DG5Rg2f0cawIL5R8UqnPQpd7pbdObk8OyjO5ryYQmBM/edit#) | ||
* for a more thorough explanation of expected behaviors and potential cavaets. | ||
* | ||
* `ClickableBehavior` accepts a function as `children` which is passed state | ||
* and an object containing event handlers. The `children` function should | ||
* return a clickable React Element of some sort. | ||
* | ||
* Example: | ||
* | ||
* ```js | ||
* class MyClickableComponent extends React.Component<Props> { | ||
* render() { | ||
* const ClickableBehavior = getClickableBehavior(); | ||
* return <ClickableBehavior | ||
* disabled={this.props.disabled} | ||
* onClick={this.props.onClick} | ||
* > | ||
* {({hovered}, handlers) => | ||
* <RoundRect | ||
* textcolor='white' | ||
* backgroundColor={hovered ? 'red' : 'blue'}} | ||
* {...handlers} | ||
* > | ||
* {this.props.children} | ||
* </RoundRect> | ||
* } | ||
* </ClickableBehavior> | ||
* } | ||
* } | ||
* ``` | ||
* | ||
* This follows a pattern called [Function as Child Components] | ||
* (https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9). | ||
* | ||
* WARNING: Do not use this component directly, use getClickableBehavior | ||
* instead. getClickableBehavior takes three arguments (href, directtNav, and | ||
* router) and returns either the default ClickableBehavior or a react-router | ||
* aware version. | ||
* | ||
* The react-router aware version is returned if `router` is a react-router-dom | ||
* router, `skipClientNav` is not `true`, and `href` is an internal URL. | ||
* | ||
* The `router` can be accessed via this.context.router from a component | ||
* rendered as a descendant of a BrowserRouter. | ||
* See https://reacttraining.com/react-router/web/guides/basic-components. | ||
*/ | ||
var ClickableBehavior = /*#__PURE__*/function (_React$Component) { | ||
_inherits(ClickableBehavior, _React$Component); | ||
var _super = _createSuper(ClickableBehavior); | ||
_createClass(ClickableBehavior, null, [{ | ||
key: "getDerivedStateFromProps", | ||
value: function getDerivedStateFromProps(props, state) { | ||
// If new props are disabled, reset the hovered/focused/pressed states | ||
if (props.disabled) { | ||
return startState; | ||
} else { | ||
// Cannot return undefined | ||
return null; | ||
} | ||
} | ||
}]); | ||
function ClickableBehavior(props) { | ||
var _this; | ||
_classCallCheck(this, ClickableBehavior); | ||
_this = _super.call(this, props); | ||
_defineProperty(_assertThisInitialized(_this), "waitingForClick", void 0); | ||
_defineProperty(_assertThisInitialized(_this), "enterClick", void 0); | ||
_defineProperty(_assertThisInitialized(_this), "dragging", void 0); | ||
_defineProperty(_assertThisInitialized(_this), "handleClick", function (e) { | ||
var _this$props = _this.props, | ||
_this$props$onClick = _this$props.onClick, | ||
onClick = _this$props$onClick === void 0 ? undefined : _this$props$onClick, | ||
_this$props$beforeNav = _this$props.beforeNav, | ||
beforeNav = _this$props$beforeNav === void 0 ? undefined : _this$props$beforeNav, | ||
_this$props$safeWithN = _this$props.safeWithNav, | ||
safeWithNav = _this$props$safeWithN === void 0 ? undefined : _this$props$safeWithN; | ||
if (_this.enterClick) { | ||
return; | ||
} | ||
if (onClick || beforeNav || safeWithNav) { | ||
_this.waitingForClick = false; | ||
} | ||
_this.runCallbackAndMaybeNavigate(e); | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "handleMouseEnter", function (e) { | ||
// When the left button is pressed already, we want it to be pressed | ||
if (e.buttons === 1) { | ||
_this.dragging = true; | ||
_this.setState({ | ||
pressed: true | ||
}); | ||
} else if (!_this.waitingForClick) { | ||
_this.setState({ | ||
hovered: true | ||
}); | ||
} | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "handleMouseLeave", function () { | ||
if (!_this.waitingForClick) { | ||
_this.dragging = false; | ||
_this.setState({ | ||
hovered: false, | ||
pressed: false, | ||
focused: false | ||
}); | ||
} | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "handleMouseDown", function () { | ||
_this.setState({ | ||
pressed: true | ||
}); | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "handleMouseUp", function (e) { | ||
if (_this.dragging) { | ||
_this.dragging = false; | ||
_this.handleClick(e); | ||
} | ||
_this.setState({ | ||
pressed: false, | ||
focused: false | ||
}); | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "handleDragStart", function (e) { | ||
_this.dragging = true; | ||
e.preventDefault(); | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "handleTouchStart", function () { | ||
_this.setState({ | ||
pressed: true | ||
}); | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "handleTouchEnd", function () { | ||
_this.setState({ | ||
pressed: false | ||
}); | ||
_this.waitingForClick = true; | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "handleTouchCancel", function () { | ||
_this.setState({ | ||
pressed: false | ||
}); | ||
_this.waitingForClick = true; | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "handleKeyDown", function (e) { | ||
var _this$props2 = _this.props, | ||
onKeyDown = _this$props2.onKeyDown, | ||
role = _this$props2.role; | ||
if (onKeyDown) { | ||
onKeyDown(e); | ||
} | ||
var keyCode = e.which || e.keyCode; | ||
var _getAppropriateTrigge = getAppropriateTriggersForRole(role), | ||
triggerOnEnter = _getAppropriateTrigge.triggerOnEnter, | ||
triggerOnSpace = _getAppropriateTrigge.triggerOnSpace; | ||
if (triggerOnEnter && keyCode === keyCodes.enter || triggerOnSpace && keyCode === keyCodes.space) { | ||
// This prevents space from scrolling down. It also prevents the | ||
// space and enter keys from triggering click events. We manually | ||
// call the supplied onClick and handle potential navigation in | ||
// handleKeyUp instead. | ||
e.preventDefault(); | ||
_this.setState({ | ||
pressed: true | ||
}); | ||
} else if (!triggerOnEnter && keyCode === keyCodes.enter) { | ||
// If the component isn't supposed to trigger on enter, we have to | ||
// keep track of the enter keydown to negate the onClick callback | ||
_this.enterClick = true; | ||
} | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "handleKeyUp", function (e) { | ||
var _this$props3 = _this.props, | ||
onKeyUp = _this$props3.onKeyUp, | ||
role = _this$props3.role; | ||
if (onKeyUp) { | ||
onKeyUp(e); | ||
} | ||
var keyCode = e.which || e.keyCode; | ||
var _getAppropriateTrigge2 = getAppropriateTriggersForRole(role), | ||
triggerOnEnter = _getAppropriateTrigge2.triggerOnEnter, | ||
triggerOnSpace = _getAppropriateTrigge2.triggerOnSpace; | ||
if (triggerOnEnter && keyCode === keyCodes.enter || triggerOnSpace && keyCode === keyCodes.space) { | ||
_this.setState({ | ||
pressed: false, | ||
focused: true | ||
}); | ||
_this.runCallbackAndMaybeNavigate(e); | ||
} else if (!triggerOnEnter && keyCode === keyCodes.enter) { | ||
_this.enterClick = false; | ||
} | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "handleFocus", function (e) { | ||
_this.setState({ | ||
focused: true | ||
}); | ||
}); | ||
_defineProperty(_assertThisInitialized(_this), "handleBlur", function (e) { | ||
_this.setState({ | ||
focused: false, | ||
pressed: false | ||
}); | ||
}); | ||
_this.state = startState; | ||
_this.waitingForClick = false; | ||
_this.enterClick = false; | ||
_this.dragging = false; | ||
return _this; | ||
} | ||
_createClass(ClickableBehavior, [{ | ||
key: "navigateOrReset", | ||
value: function navigateOrReset(shouldNavigate) { | ||
if (shouldNavigate) { | ||
var _this$props4 = this.props, | ||
history = _this$props4.history, | ||
href = _this$props4.href, | ||
skipClientNav = _this$props4.skipClientNav; | ||
if (href) { | ||
if (history && !skipClientNav) { | ||
history.push(href); | ||
this.setState({ | ||
waiting: false | ||
}); | ||
} else { | ||
window.location.assign(href); // We don't bother clearing the waiting state, the full page | ||
// load navigation will do that for us by loading a new page. | ||
} | ||
} | ||
} else { | ||
this.setState({ | ||
waiting: false | ||
}); | ||
} | ||
} | ||
}, { | ||
key: "handleSafeWithNav", | ||
value: function handleSafeWithNav(safeWithNav, shouldNavigate) { | ||
var _this2 = this; | ||
var _this$props5 = this.props, | ||
skipClientNav = _this$props5.skipClientNav, | ||
history = _this$props5.history; | ||
if (history && !skipClientNav) { | ||
// client-side nav | ||
safeWithNav(); | ||
this.navigateOrReset(shouldNavigate); | ||
return Promise.resolve(); | ||
} else { | ||
if (!this.state.waiting) { | ||
// We only show the spinner for safeWithNav when doing | ||
// a full page load navigation since since the spinner is | ||
// indicating that we're waiting for navigation to occur. | ||
this.setState({ | ||
waiting: true | ||
}); | ||
} | ||
return safeWithNav().then(function () { | ||
if (!_this2.state.waiting) { | ||
// We only show the spinner for safeWithNav when doing | ||
// a full page load navigation since since the spinner is | ||
// indicating that we're waiting for navigation to occur. | ||
_this2.setState({ | ||
waiting: true | ||
}); | ||
} | ||
return; | ||
}).catch(function (error) {// We ignore the error here so that we always | ||
// navigate when using safeWithNav regardless of | ||
// whether we're doing a client-side nav or not. | ||
}).finally(function () { | ||
_this2.navigateOrReset(shouldNavigate); | ||
}); | ||
} | ||
} | ||
}, { | ||
key: "runCallbackAndMaybeNavigate", | ||
value: function runCallbackAndMaybeNavigate(e) { | ||
var _this3 = this; | ||
var _this$props6 = this.props, | ||
_this$props6$onClick = _this$props6.onClick, | ||
onClick = _this$props6$onClick === void 0 ? undefined : _this$props6$onClick, | ||
_this$props6$beforeNa = _this$props6.beforeNav, | ||
beforeNav = _this$props6$beforeNa === void 0 ? undefined : _this$props6$beforeNa, | ||
_this$props6$safeWith = _this$props6.safeWithNav, | ||
safeWithNav = _this$props6$safeWith === void 0 ? undefined : _this$props6$safeWith, | ||
href = _this$props6.href, | ||
type = _this$props6.type; | ||
var shouldNavigate = true; | ||
var canSubmit = true; | ||
if (onClick) { | ||
onClick(e); | ||
} // If onClick() has called e.preventDefault() then we shouldn't | ||
// navigate. | ||
if (e.defaultPrevented) { | ||
shouldNavigate = false; | ||
canSubmit = false; | ||
} | ||
e.preventDefault(); | ||
if (!href && type === "submit" && canSubmit) { | ||
var target = e.currentTarget; | ||
while (target) { | ||
if (target instanceof window.HTMLFormElement) { | ||
var event = new window.Event("submit"); | ||
target.dispatchEvent(event); | ||
break; | ||
} // All events should be typed as SyntheticEvent<HTMLElement>. | ||
// Updating all of the places will take some time so I'll do | ||
// this later - $FlowFixMe. | ||
target = target.parentElement; | ||
} | ||
} | ||
if (beforeNav) { | ||
this.setState({ | ||
waiting: true | ||
}); | ||
beforeNav().then(function () { | ||
if (safeWithNav) { | ||
return _this3.handleSafeWithNav(safeWithNav, shouldNavigate); | ||
} else { | ||
return _this3.navigateOrReset(shouldNavigate); | ||
} | ||
}).catch(function () {}); | ||
} else if (safeWithNav) { | ||
return this.handleSafeWithNav(safeWithNav, shouldNavigate); | ||
} else { | ||
this.navigateOrReset(shouldNavigate); | ||
} | ||
} | ||
}, { | ||
key: "render", | ||
value: function render() { | ||
var handlers = this.props.disabled ? disabledHandlers : { | ||
onClick: this.handleClick, | ||
onMouseEnter: this.handleMouseEnter, | ||
onMouseLeave: this.handleMouseLeave, | ||
onMouseDown: this.handleMouseDown, | ||
onMouseUp: this.handleMouseUp, | ||
onDragStart: this.handleDragStart, | ||
onTouchStart: this.handleTouchStart, | ||
onTouchEnd: this.handleTouchEnd, | ||
onTouchCancel: this.handleTouchCancel, | ||
onKeyDown: this.handleKeyDown, | ||
onKeyUp: this.handleKeyUp, | ||
onFocus: this.handleFocus, | ||
onBlur: this.handleBlur, | ||
// We set tabIndex to 0 so that users can tab to clickable | ||
// things that aren't buttons or anchors. | ||
tabIndex: 0 | ||
}; | ||
var children = this.props.children; | ||
return children && children(this.state, handlers); | ||
} | ||
}]); | ||
return ClickableBehavior; | ||
}(Component); | ||
_defineProperty(ClickableBehavior, "defaultProps", { | ||
disabled: false | ||
}); | ||
function flatten(list) { | ||
@@ -1596,41 +1064,2 @@ var result = []; | ||
/** | ||
* Returns true for external url. | ||
* Such as http://, https://, // | ||
*/ | ||
function isExternalUrl(url) { | ||
return /^(https?:)?\/\//i.test(url); | ||
} | ||
/** | ||
* Returns either the default ClickableBehavior or a react-router aware version. | ||
* | ||
* The react-router aware version is returned if `router` is a react-router-dom | ||
* router, `skipClientNav` is not `true`, and `href` is an internal URL. | ||
* | ||
* The `router` can be accessed via this.context.router from a component rendered | ||
* as a descendant of a BrowserRouter. | ||
* See https://reacttraining.com/react-router/web/guides/basic-components. | ||
*/ | ||
var ClickableBehaviorWithRouter = withRouter(ClickableBehavior); | ||
function getClickableBehavior( | ||
/** | ||
* The URL to navigate to. | ||
*/ | ||
href, | ||
/** | ||
* Should we skip using the react router and go to the page directly. | ||
*/ | ||
skipClientNav, | ||
/** | ||
* router object added to the React context object by react-router-dom. | ||
*/ | ||
router) { | ||
if (router && skipClientNav !== true && href && !isExternalUrl(href)) { | ||
return ClickableBehaviorWithRouter; | ||
} | ||
return ClickableBehavior; | ||
} | ||
var _Symbol$iterator; | ||
@@ -1889,2 +1318,2 @@ | ||
export { ClickableBehavior, IDProvider, server as Server, Text, UniqueIDProvider, View, WithSSRPlaceholder, addStyle, getClickableBehavior, getElementIntersection }; | ||
export { IDProvider, server as Server, Text, UniqueIDProvider, View, WithSSRPlaceholder, addStyle, getElementIntersection }; |
17
index.js
// @flow | ||
import type { | ||
ClickableHandlers, | ||
ClickableState, | ||
ClickableRole, | ||
} from "./components/clickable-behavior.js"; | ||
import type {AriaProps, IIdentifierFactory, StyleType} from "./util/types.js"; | ||
import type {Intersection} from "./util/get-element-intersection.js"; | ||
export {default as ClickableBehavior} from "./components/clickable-behavior.js"; | ||
export {default as Text} from "./components/text.js"; | ||
@@ -17,14 +11,5 @@ export {default as View} from "./components/view.js"; | ||
export {default as addStyle} from "./util/add-style.js"; | ||
export {default as getClickableBehavior} from "./util/get-clickable-behavior.js"; | ||
export {default as getElementIntersection} from "./util/get-element-intersection.js"; | ||
export {default as Server} from "./util/server.js"; | ||
export type { | ||
AriaProps, | ||
ClickableHandlers, | ||
ClickableState, | ||
ClickableRole, | ||
Intersection, | ||
IIdentifierFactory, | ||
StyleType, | ||
}; | ||
export type {AriaProps, Intersection, IIdentifierFactory, StyleType}; |
{ | ||
"name": "@khanacademy/wonder-blocks-core", | ||
"version": "2.7.1", | ||
"version": "3.0.0", | ||
"design": "v1", | ||
@@ -30,3 +30,3 @@ "publishConfig": { | ||
"license": "MIT", | ||
"gitHead": "55971121a333453523295abfe7d582071588a44e" | ||
"gitHead": "768de54e6082e80e2faee6ed6af75303d608c01c" | ||
} |
Sorry, the diff of this file is too big to display
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
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
273709
43
5051