Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@khanacademy/wonder-blocks-clickable

Package Overview
Dependencies
Maintainers
1
Versions
247
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@khanacademy/wonder-blocks-clickable - npm Package Compare versions

Comparing version 1.4.1 to 1.5.0

__tests__/index.test.js

2

__tests__/generated-snapshot.test.js

@@ -19,2 +19,4 @@ // This file is auto-generated by gen-snapshot-tests.js

import ClickableBehavior from "./../components/clickable-behavior.js";
describe("wonder-blocks-clickable", () => {

@@ -21,0 +23,0 @@ it("example 1", () => {

13

components/clickable.js

@@ -6,9 +6,6 @@ // @flow

import {Link} from "react-router-dom";
import type {
AriaProps,
StyleType,
ClickableRole,
ClickableState,
} from "@khanacademy/wonder-blocks-core";
import {addStyle, getClickableBehavior} from "@khanacademy/wonder-blocks-core";
import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
import {addStyle} from "@khanacademy/wonder-blocks-core";
import getClickableBehavior from "../util/get-clickable-behavior.js";
import type {ClickableRole, ClickableState} from "./clickable-behavior.js";

@@ -253,2 +250,3 @@ type CommonProps = {|

style,
target,
testId,

@@ -271,2 +269,3 @@ onKeyDown,

safeWithNav={safeWithNav}
target={target}
onKeyDown={onKeyDown}

@@ -273,0 +272,0 @@ onKeyUp={onKeyUp}

@@ -1,6 +0,6 @@

import { createElement, Component } from 'react';
import { Component, createElement } from 'react';
import { StyleSheet } from 'aphrodite';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { addStyle, getClickableBehavior } from '@khanacademy/wonder-blocks-core';
import { withRouter, Link } from 'react-router-dom';
import { addStyle } from '@khanacademy/wonder-blocks-core';

@@ -211,2 +211,578 @@ 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,
target = _this$props4.target;
if (href) {
if (history && !skipClientNav) {
history.push(href);
this.setState({
waiting: false
});
} else {
if (target === "_blank") {
window.open(href, "_blank");
} 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
});
/**
* 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 StyledAnchor = addStyle("a");

@@ -298,6 +874,7 @@ var StyledButton = addStyle("button");

style = _this$props.style,
target = _this$props.target,
testId = _this$props.testId,
onKeyDown = _this$props.onKeyDown,
onKeyUp = _this$props.onKeyUp,
restProps = _objectWithoutProperties(_this$props, ["href", "onClick", "skipClientNav", "beforeNav", "safeWithNav", "style", "testId", "onKeyDown", "onKeyUp"]);
restProps = _objectWithoutProperties(_this$props, ["href", "onClick", "skipClientNav", "beforeNav", "safeWithNav", "style", "target", "testId", "onKeyDown", "onKeyUp"]);

@@ -310,2 +887,3 @@ var ClickableBehavior = getClickableBehavior(href, skipClientNav, this.context.router);

safeWithNav: safeWithNav,
target: target,
onKeyDown: onKeyDown,

@@ -366,1 +944,2 @@ onKeyUp: onKeyUp

export default Clickable;
export { ClickableBehavior, getClickableBehavior };

@@ -98,3 +98,3 @@ module.exports =

module.exports = require("@khanacademy/wonder-blocks-core");
module.exports = require("react-router-dom");

@@ -105,3 +105,3 @@ /***/ }),

module.exports = require("aphrodite");
module.exports = require("@khanacademy/wonder-blocks-core");

@@ -112,3 +112,3 @@ /***/ }),

module.exports = require("prop-types");
module.exports = require("aphrodite");

@@ -119,3 +119,3 @@ /***/ }),

module.exports = require("react-router-dom");
module.exports = require("prop-types");

@@ -131,2 +131,4 @@ /***/ }),

// EXPORTS
__webpack_require__.d(__webpack_exports__, "ClickableBehavior", function() { return /* reexport */ clickable_behavior_ClickableBehavior; });
__webpack_require__.d(__webpack_exports__, "getClickableBehavior", function() { return /* reexport */ getClickableBehavior; });
__webpack_require__.d(__webpack_exports__, "default", function() { return /* reexport */ clickable_Clickable; });

@@ -138,27 +140,17 @@

// EXTERNAL MODULE: external "aphrodite"
var external_aphrodite_ = __webpack_require__(2);
var external_aphrodite_ = __webpack_require__(3);
// EXTERNAL MODULE: external "prop-types"
var external_prop_types_ = __webpack_require__(3);
var external_prop_types_ = __webpack_require__(4);
var external_prop_types_default = /*#__PURE__*/__webpack_require__.n(external_prop_types_);
// EXTERNAL MODULE: external "react-router-dom"
var external_react_router_dom_ = __webpack_require__(4);
var external_react_router_dom_ = __webpack_require__(1);
// EXTERNAL MODULE: external "@khanacademy/wonder-blocks-core"
var wonder_blocks_core_ = __webpack_require__(1);
var wonder_blocks_core_ = __webpack_require__(2);
// CONCATENATED MODULE: ./packages/wonder-blocks-clickable/components/clickable.js
// CONCATENATED MODULE: ./packages/wonder-blocks-clickable/components/clickable-behavior.js
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

@@ -186,7 +178,625 @@

// NOTE: Potentially add to this as more cases come up.
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 clickable_behavior_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,
target = _this$props4.target;
if (href) {
if (history && !skipClientNav) {
history.push(href);
this.setState({
waiting: false
});
} else {
if (target === "_blank") {
window.open(href, "_blank");
} 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;
}(external_react_["Component"]);
_defineProperty(clickable_behavior_ClickableBehavior, "defaultProps", {
disabled: false
});
// CONCATENATED MODULE: ./packages/wonder-blocks-clickable/util/is-external-url.js
/**
* Returns true for external url.
* Such as http://, https://, //
*/
function isExternalUrl(url) {
return /^(https?:)?\/\//i.test(url);
}
// CONCATENATED MODULE: ./packages/wonder-blocks-clickable/util/get-clickable-behavior.js
/**
* 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 = Object(external_react_router_dom_["withRouter"])(clickable_behavior_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 clickable_behavior_ClickableBehavior;
}
// CONCATENATED MODULE: ./packages/wonder-blocks-clickable/components/clickable.js
function clickable_typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { clickable_typeof = function _typeof(obj) { return typeof obj; }; } else { clickable_typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return clickable_typeof(obj); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { clickable_defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function clickable_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function clickable_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function clickable_createClass(Constructor, protoProps, staticProps) { if (protoProps) clickable_defineProperties(Constructor.prototype, protoProps); if (staticProps) clickable_defineProperties(Constructor, staticProps); return Constructor; }
function clickable_inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) clickable_setPrototypeOf(subClass, superClass); }
function clickable_setPrototypeOf(o, p) { clickable_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return clickable_setPrototypeOf(o, p); }
function clickable_createSuper(Derived) { var hasNativeReflectConstruct = clickable_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = clickable_getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = clickable_getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return clickable_possibleConstructorReturn(this, result); }; }
function clickable_possibleConstructorReturn(self, call) { if (call && (clickable_typeof(call) === "object" || typeof call === "function")) { return call; } return clickable_assertThisInitialized(self); }
function clickable_assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function clickable_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
function clickable_getPrototypeOf(o) { clickable_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return clickable_getPrototypeOf(o); }
function clickable_defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var StyledAnchor = Object(wonder_blocks_core_["addStyle"])("a");

@@ -221,5 +831,5 @@ var StyledButton = Object(wonder_blocks_core_["addStyle"])("button");

var clickable_Clickable = /*#__PURE__*/function (_React$Component) {
_inherits(Clickable, _React$Component);
clickable_inherits(Clickable, _React$Component);
var _super = _createSuper(Clickable);
var _super = clickable_createSuper(Clickable);

@@ -229,3 +839,3 @@ function Clickable() {

_classCallCheck(this, Clickable);
clickable_classCallCheck(this, Clickable);

@@ -238,3 +848,3 @@ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {

_defineProperty(_assertThisInitialized(_this), "getCorrectTag", function (clickableState, commonProps) {
clickable_defineProperty(clickable_assertThisInitialized(_this), "getCorrectTag", function (clickableState, commonProps) {
var activeHref = _this.props.href && !_this.props.disabled;

@@ -267,3 +877,3 @@ var useClient = _this.context.router && !_this.props.skipClientNav; // NOTE: checking this.props.href here is redundant, but flow

_createClass(Clickable, [{
clickable_createClass(Clickable, [{
key: "render",

@@ -282,8 +892,9 @@ value: function render() {

style = _this$props.style,
target = _this$props.target,
testId = _this$props.testId,
onKeyDown = _this$props.onKeyDown,
onKeyUp = _this$props.onKeyUp,
restProps = _objectWithoutProperties(_this$props, ["href", "onClick", "skipClientNav", "beforeNav", "safeWithNav", "style", "testId", "onKeyDown", "onKeyUp"]);
restProps = _objectWithoutProperties(_this$props, ["href", "onClick", "skipClientNav", "beforeNav", "safeWithNav", "style", "target", "testId", "onKeyDown", "onKeyUp"]);
var ClickableBehavior = Object(wonder_blocks_core_["getClickableBehavior"])(href, skipClientNav, this.context.router);
var ClickableBehavior = getClickableBehavior(href, skipClientNav, this.context.router);
return /*#__PURE__*/external_react_["createElement"](ClickableBehavior, {

@@ -294,2 +905,3 @@ href: href,

safeWithNav: safeWithNav,
target: target,
onKeyDown: onKeyDown,

@@ -310,7 +922,7 @@ onKeyUp: onKeyUp

_defineProperty(clickable_Clickable, "contextTypes", {
clickable_defineProperty(clickable_Clickable, "contextTypes", {
router: external_prop_types_default.a.any
});
_defineProperty(clickable_Clickable, "defaultProps", {
clickable_defineProperty(clickable_Clickable, "defaultProps", {
disabled: false,

@@ -355,3 +967,5 @@ "aria-label": ""

/***/ })
/******/ ]);
// @flow
import type {
ClickableHandlers,
ClickableState,
ClickableRole,
} from "./components/clickable-behavior.js";
import Clickable from "./components/clickable.js";
export {default as ClickableBehavior} from "./components/clickable-behavior.js";
export {default as getClickableBehavior} from "./util/get-clickable-behavior.js";
export {Clickable as default};
export type {ClickableHandlers, ClickableState, ClickableRole};
{
"name": "@khanacademy/wonder-blocks-clickable",
"version": "1.4.1",
"version": "1.5.0",
"design": "v1",

@@ -18,3 +18,3 @@ "description": "Clickable component for Wonder-Blocks.",

"dependencies": {
"@khanacademy/wonder-blocks-core": "^2.7.1"
"@khanacademy/wonder-blocks-core": "^3.0.0"
},

@@ -31,3 +31,3 @@ "peerDependencies": {

},
"gitHead": "55971121a333453523295abfe7d582071588a44e"
"gitHead": "768de54e6082e80e2faee6ed6af75303d608c01c"
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc