@khanacademy/wonder-blocks-clickable 8.0.5 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/components/clickable-behavior.d.ts +42 -45
- package/dist/components/clickable.d.ts +2 -34
- package/dist/es/index.js +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,45 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { NavigateFunction } from "react-router-dom-v5-compat";
|
|
3
3
|
export type ClickableRole = "button" | "checkbox" | "link" | "listbox" | "menu" | "menuitem" | "menuitemcheckbox" | "option" | "radio" | "switch" | "tab";
|
|
4
|
-
|
|
4
|
+
export interface ExposedEventHandlers {
|
|
5
|
+
/**
|
|
6
|
+
* A function to be executed `onclick`.
|
|
7
|
+
*/
|
|
8
|
+
onClick?: (e: React.SyntheticEvent) => unknown;
|
|
9
|
+
/**
|
|
10
|
+
* Respond to raw "onfocus" event.
|
|
11
|
+
*/
|
|
12
|
+
onFocus?: (e: React.FocusEvent) => unknown;
|
|
13
|
+
/**
|
|
14
|
+
* Respont to raw "onblur" event.
|
|
15
|
+
*/
|
|
16
|
+
onBlur?: (e: React.FocusEvent) => unknown;
|
|
17
|
+
/**
|
|
18
|
+
* Respond to raw "keydown" event.
|
|
19
|
+
*/
|
|
20
|
+
onKeyDown?: (e: React.KeyboardEvent) => unknown;
|
|
21
|
+
/**
|
|
22
|
+
* Respond to raw "keyup" event.
|
|
23
|
+
*/
|
|
24
|
+
onKeyUp?: (e: React.KeyboardEvent) => unknown;
|
|
25
|
+
/**
|
|
26
|
+
* Respond to a raw "mousedown" event.
|
|
27
|
+
*/
|
|
28
|
+
onMouseDown?: (e: React.MouseEvent) => unknown;
|
|
29
|
+
/**
|
|
30
|
+
* Respond to a raw "mouseup" event.
|
|
31
|
+
*/
|
|
32
|
+
onMouseUp?: (e: React.MouseEvent) => unknown;
|
|
33
|
+
/**
|
|
34
|
+
* Respond to raw "mouseenter" event.
|
|
35
|
+
*/
|
|
36
|
+
onMouseEnter?: (e: React.MouseEvent) => unknown;
|
|
37
|
+
/**
|
|
38
|
+
* Respond to raw "mouseleave" event.
|
|
39
|
+
*/
|
|
40
|
+
onMouseLeave?: (e: React.MouseEvent) => unknown;
|
|
41
|
+
}
|
|
42
|
+
interface CommonProps extends ExposedEventHandlers {
|
|
5
43
|
/**
|
|
6
44
|
* A function that returns the a React `Element`.
|
|
7
45
|
*
|
|
@@ -44,10 +82,6 @@ type CommonProps = Readonly<{
|
|
|
44
82
|
* element non-focusable via keyboard navigation.
|
|
45
83
|
*/
|
|
46
84
|
tabIndex?: number;
|
|
47
|
-
/**
|
|
48
|
-
* A function to be executed `onclick`.
|
|
49
|
-
*/
|
|
50
|
-
onClick?: (e: React.SyntheticEvent) => unknown;
|
|
51
85
|
/**
|
|
52
86
|
* Run async code in the background while client-side navigating. If the
|
|
53
87
|
* browser does a full page load navigation, the callback promise must be
|
|
@@ -67,34 +101,6 @@ type CommonProps = Readonly<{
|
|
|
67
101
|
* enter and space keys.
|
|
68
102
|
*/
|
|
69
103
|
role?: ClickableRole;
|
|
70
|
-
/**
|
|
71
|
-
* Respond to raw "onfocus" event.
|
|
72
|
-
*/
|
|
73
|
-
onFocus?: (e: React.FocusEvent) => unknown;
|
|
74
|
-
/**
|
|
75
|
-
* Respond to raw "keydown" event.
|
|
76
|
-
*/
|
|
77
|
-
onKeyDown?: (e: React.KeyboardEvent) => unknown;
|
|
78
|
-
/**
|
|
79
|
-
* Respond to raw "keyup" event.
|
|
80
|
-
*/
|
|
81
|
-
onKeyUp?: (e: React.KeyboardEvent) => unknown;
|
|
82
|
-
/**
|
|
83
|
-
* Respond to a raw "mousedown" event.
|
|
84
|
-
*/
|
|
85
|
-
onMouseDown?: (e: React.MouseEvent) => unknown;
|
|
86
|
-
/**
|
|
87
|
-
* Respond to a raw "mouseup" event.
|
|
88
|
-
*/
|
|
89
|
-
onMouseUp?: (e: React.MouseEvent) => unknown;
|
|
90
|
-
/**
|
|
91
|
-
* Respond to raw "mouseenter" event.
|
|
92
|
-
*/
|
|
93
|
-
onMouseEnter?: (e: React.MouseEvent) => unknown;
|
|
94
|
-
/**
|
|
95
|
-
* Respond to raw "mouseleave" event.
|
|
96
|
-
*/
|
|
97
|
-
onMouseLeave?: (e: React.MouseEvent) => unknown;
|
|
98
104
|
/**
|
|
99
105
|
* An optional prop that enables a
|
|
100
106
|
* [https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API](View
|
|
@@ -104,7 +110,7 @@ type CommonProps = Readonly<{
|
|
|
104
110
|
* @see https://reactrouter.com/6.30.0/components/link#viewtransition
|
|
105
111
|
*/
|
|
106
112
|
viewTransition?: boolean;
|
|
107
|
-
}
|
|
113
|
+
}
|
|
108
114
|
type Props = (CommonProps & Readonly<{
|
|
109
115
|
/**
|
|
110
116
|
* A target destination window for a link to open in. Should only be used
|
|
@@ -160,22 +166,13 @@ export type ClickableState = Readonly<{
|
|
|
160
166
|
type DefaultProps = Readonly<{
|
|
161
167
|
disabled: Props["disabled"];
|
|
162
168
|
}>;
|
|
163
|
-
export
|
|
164
|
-
onClick: (e: React.SyntheticEvent) => unknown;
|
|
165
|
-
onMouseEnter: (e: React.MouseEvent) => unknown;
|
|
166
|
-
onMouseLeave: (e: React.MouseEvent) => unknown;
|
|
167
|
-
onMouseDown: (e: React.MouseEvent) => unknown;
|
|
168
|
-
onMouseUp: (e: React.MouseEvent) => unknown;
|
|
169
|
+
export interface ChildrenProps extends Required<ExposedEventHandlers> {
|
|
169
170
|
onTouchStart: () => unknown;
|
|
170
171
|
onTouchEnd: () => unknown;
|
|
171
172
|
onTouchCancel: () => unknown;
|
|
172
|
-
onKeyDown: (e: React.KeyboardEvent) => unknown;
|
|
173
|
-
onKeyUp: (e: React.KeyboardEvent) => unknown;
|
|
174
|
-
onFocus: (e: React.FocusEvent) => unknown;
|
|
175
|
-
onBlur: (e: React.FocusEvent) => unknown;
|
|
176
173
|
tabIndex?: number;
|
|
177
174
|
rel?: string;
|
|
178
|
-
}
|
|
175
|
+
}
|
|
179
176
|
/**
|
|
180
177
|
* Add hover, focus, and active status updates to a clickable component.
|
|
181
178
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { AriaProps, StyleType } from "@khanacademy/wonder-blocks-core";
|
|
3
|
-
import type { ClickableRole, ClickableState } from "./clickable-behavior";
|
|
3
|
+
import type { ClickableRole, ClickableState, ExposedEventHandlers } from "./clickable-behavior";
|
|
4
4
|
type CommonProps =
|
|
5
5
|
/**
|
|
6
6
|
* aria-label should be used when `spinner={true}` to let people using screen
|
|
@@ -14,14 +14,6 @@ Partial<Omit<AriaProps, "aria-disabled">> & {
|
|
|
14
14
|
* three boolean properties: hovered, focused, and pressed.
|
|
15
15
|
*/
|
|
16
16
|
children: (clickableState: ClickableState) => React.ReactNode;
|
|
17
|
-
/**
|
|
18
|
-
* An onClick function which Clickable can execute when clicked
|
|
19
|
-
*/
|
|
20
|
-
onClick?: (e: React.SyntheticEvent) => unknown;
|
|
21
|
-
/**
|
|
22
|
-
* An onFocus function which Clickable can execute when focused
|
|
23
|
-
*/
|
|
24
|
-
onFocus?: (e: React.FocusEvent) => unknown;
|
|
25
17
|
/**
|
|
26
18
|
* Optional href which Clickable should direct to, uses client-side routing
|
|
27
19
|
* by default if react-router is present
|
|
@@ -62,30 +54,6 @@ Partial<Omit<AriaProps, "aria-disabled">> & {
|
|
|
62
54
|
* Test ID used for e2e testing.
|
|
63
55
|
*/
|
|
64
56
|
testId?: string;
|
|
65
|
-
/**
|
|
66
|
-
* Respond to raw "keydown" event.
|
|
67
|
-
*/
|
|
68
|
-
onKeyDown?: (e: React.KeyboardEvent) => unknown;
|
|
69
|
-
/**
|
|
70
|
-
* Respond to raw "keyup" event.
|
|
71
|
-
*/
|
|
72
|
-
onKeyUp?: (e: React.KeyboardEvent) => unknown;
|
|
73
|
-
/**
|
|
74
|
-
* Respond to raw "mousedown" event.
|
|
75
|
-
*/
|
|
76
|
-
onMouseDown?: (e: React.MouseEvent) => unknown;
|
|
77
|
-
/**
|
|
78
|
-
* Respond to raw "mouseup" event.
|
|
79
|
-
*/
|
|
80
|
-
onMouseUp?: (e: React.MouseEvent) => unknown;
|
|
81
|
-
/**
|
|
82
|
-
* Respond to raw "mouseenter" event.
|
|
83
|
-
*/
|
|
84
|
-
onMouseEnter?: (e: React.MouseEvent) => unknown;
|
|
85
|
-
/**
|
|
86
|
-
* Respond to raw "mouseleave" event.
|
|
87
|
-
*/
|
|
88
|
-
onMouseLeave?: (e: React.MouseEvent) => unknown;
|
|
89
57
|
/**
|
|
90
58
|
* Don't show the default focus ring. This should be used when implementing
|
|
91
59
|
* a custom focus ring within your own component that uses Clickable.
|
|
@@ -117,7 +85,7 @@ Partial<Omit<AriaProps, "aria-disabled">> & {
|
|
|
117
85
|
* navigation is guaranteed to succeed.
|
|
118
86
|
*/
|
|
119
87
|
safeWithNav?: () => Promise<unknown>;
|
|
120
|
-
};
|
|
88
|
+
} & ExposedEventHandlers;
|
|
121
89
|
type Props = (CommonProps & {
|
|
122
90
|
href: string;
|
|
123
91
|
/**
|
package/dist/es/index.js
CHANGED
|
@@ -5,12 +5,12 @@ import { useNavigate, Link, useInRouterContext } from 'react-router-dom-v5-compa
|
|
|
5
5
|
import { keys, addStyle } from '@khanacademy/wonder-blocks-core';
|
|
6
6
|
import { border, semanticColor } from '@khanacademy/wonder-blocks-tokens';
|
|
7
7
|
|
|
8
|
-
const getAppropriateTriggersForRole=role=>{switch(role){case"link":return {triggerOnEnter:true,triggerOnSpace:false};case"checkbox":case"radio":case"listbox":return {triggerOnEnter:false,triggerOnSpace:true};case"button":case"menuitem":case"menu":case"option":default:return {triggerOnEnter:true,triggerOnSpace:true}}};const disabledHandlers={onClick:()=>void 0,onMouseEnter:()=>void 0,onMouseLeave:()=>void 0,onMouseDown:()=>void 0,onMouseUp:()=>void 0,onTouchStart:()=>void 0,onTouchEnd:()=>void 0,onTouchCancel:()=>void 0,onKeyDown:()=>void 0,onKeyUp:()=>void 0};const startState={hovered:false,focused:false,pressed:false,waiting:false};class ClickableBehavior extends React.Component{static getDerivedStateFromProps(props,state){if(props.disabled){return {...startState,focused:state.focused}}else {return null}}navigateOrReset(shouldNavigate){if(shouldNavigate){const{navigate,href,skipClientNav,target=undefined}=this.props;if(href){if(target==="_blank"){window.open(href,"_blank");this.setState({waiting:false});}else if(navigate&&!skipClientNav){navigate(href,{viewTransition:this.props.viewTransition});this.setState({waiting:false});}else {window.location.assign(href);}}}else {this.setState({waiting:false});}}handleSafeWithNav(safeWithNav,shouldNavigate){const{skipClientNav,navigate}=this.props;if(navigate&&!skipClientNav||this.props.target==="_blank"){safeWithNav();this.navigateOrReset(shouldNavigate);return Promise.resolve()}else {if(!this.state.waiting){this.setState({waiting:true});}return safeWithNav().then(()=>{if(!this.state.waiting){this.setState({waiting:true});}return}).catch(
|
|
8
|
+
const getAppropriateTriggersForRole=role=>{switch(role){case"link":return {triggerOnEnter:true,triggerOnSpace:false};case"checkbox":case"radio":case"listbox":return {triggerOnEnter:false,triggerOnSpace:true};case"button":case"menuitem":case"menu":case"option":default:return {triggerOnEnter:true,triggerOnSpace:true}}};const disabledHandlers={onClick:()=>void 0,onMouseEnter:()=>void 0,onMouseLeave:()=>void 0,onMouseDown:()=>void 0,onMouseUp:()=>void 0,onTouchStart:()=>void 0,onTouchEnd:()=>void 0,onTouchCancel:()=>void 0,onKeyDown:()=>void 0,onKeyUp:()=>void 0};const startState={hovered:false,focused:false,pressed:false,waiting:false};class ClickableBehavior extends React.Component{static getDerivedStateFromProps(props,state){if(props.disabled){return {...startState,focused:state.focused}}else {return null}}navigateOrReset(shouldNavigate){if(shouldNavigate){const{navigate,href,skipClientNav,target=undefined}=this.props;if(href){if(target==="_blank"){window.open(href,"_blank");this.setState({waiting:false});}else if(navigate&&!skipClientNav){navigate(href,{viewTransition:this.props.viewTransition});this.setState({waiting:false});}else {window.location.assign(href);}}}else {this.setState({waiting:false});}}handleSafeWithNav(safeWithNav,shouldNavigate){const{skipClientNav,navigate}=this.props;if(navigate&&!skipClientNav||this.props.target==="_blank"){safeWithNav();this.navigateOrReset(shouldNavigate);return Promise.resolve()}else {if(!this.state.waiting){this.setState({waiting:true});}return safeWithNav().then(()=>{if(!this.state.waiting){this.setState({waiting:true});}return}).catch(_=>{}).finally(()=>{this.navigateOrReset(shouldNavigate);})}}runCallbackAndMaybeNavigate(e){const{onClick=undefined,beforeNav=undefined,safeWithNav=undefined,href,type}=this.props;let shouldNavigate=true;let canSubmit=true;onClick?.(e);if(e.defaultPrevented){shouldNavigate=false;canSubmit=false;}e.preventDefault();if(!href&&type==="submit"&&canSubmit){let target=e.currentTarget;while(target){if(target instanceof window.HTMLFormElement){const event=new window.Event("submit",{bubbles:true,cancelable:true});target.dispatchEvent(event);break}target=target.parentElement;}}if(beforeNav){this.setState({waiting:true});beforeNav().then(()=>{if(safeWithNav){return this.handleSafeWithNav(safeWithNav,shouldNavigate)}else {return this.navigateOrReset(shouldNavigate)}}).catch(()=>{});}else if(safeWithNav){return this.handleSafeWithNav(safeWithNav,shouldNavigate)}else {this.navigateOrReset(shouldNavigate);}}render(){const rel=this.props.rel||(this.props.target==="_blank"?"noopener noreferrer":undefined);const childrenProps=this.props.disabled?{...disabledHandlers,onFocus:this.handleFocus,onBlur:this.handleBlur,tabIndex:this.props.tabIndex,rel}:{onClick:this.handleClick,onMouseEnter:this.handleMouseEnter,onMouseLeave:this.handleMouseLeave,onMouseDown:this.handleMouseDown,onMouseUp:this.handleMouseUp,onTouchStart:this.handleTouchStart,onTouchEnd:this.handleTouchEnd,onTouchCancel:this.handleTouchCancel,onKeyDown:this.handleKeyDown,onKeyUp:this.handleKeyUp,onFocus:this.handleFocus,onBlur:this.handleBlur,tabIndex:this.props.tabIndex,rel};const{children}=this.props;return children&&children(this.state,childrenProps)}constructor(props){super(props),this.handleClick=e=>{const{onClick=undefined,beforeNav=undefined,safeWithNav=undefined}=this.props;if(this.enterClick){return}if(onClick||beforeNav||safeWithNav){this.waitingForClick=false;}this.runCallbackAndMaybeNavigate(e);},this.handleMouseEnter=e=>{this.props.onMouseEnter?.(e);if(!this.waitingForClick){this.setState({hovered:true});}},this.handleMouseLeave=e=>{this.props.onMouseLeave?.(e);if(!this.waitingForClick){this.setState({hovered:false,pressed:false,focused:false});}},this.handleMouseDown=e=>{this.props.onMouseDown?.(e);this.setState({pressed:true});},this.handleMouseUp=e=>{this.props.onMouseUp?.(e);this.setState({pressed:false,focused:false});},this.handleTouchStart=()=>{this.setState({pressed:true});},this.handleTouchEnd=()=>{this.setState({pressed:false});this.waitingForClick=true;},this.handleTouchCancel=()=>{this.setState({pressed:false});this.waitingForClick=true;},this.handleKeyDown=e=>{const{onKeyDown,role}=this.props;onKeyDown?.(e);const keyName=e.key;const{triggerOnEnter,triggerOnSpace}=getAppropriateTriggersForRole(role);if(triggerOnEnter&&keyName===keys.enter||triggerOnSpace&&keyName===keys.space){e.preventDefault();this.setState({pressed:true});}else if(!triggerOnEnter&&keyName===keys.enter){this.enterClick=true;}},this.handleKeyUp=e=>{const{onKeyUp,role}=this.props;onKeyUp?.(e);const keyName=e.key;const{triggerOnEnter,triggerOnSpace}=getAppropriateTriggersForRole(role);if(triggerOnEnter&&keyName===keys.enter||triggerOnSpace&&keyName===keys.space){this.setState({pressed:false,focused:true});this.runCallbackAndMaybeNavigate(e);}else if(!triggerOnEnter&&keyName===keys.enter){this.enterClick=false;}},this.handleFocus=e=>{const{onFocus}=this.props;this.setState({focused:true},()=>{onFocus?.(e);});},this.handleBlur=e=>{const{onBlur}=this.props;this.setState({focused:false,pressed:false},()=>{onBlur?.(e);});};this.state=startState;this.waitingForClick=false;this.enterClick=false;}}ClickableBehavior.defaultProps={disabled:false};
|
|
9
9
|
|
|
10
10
|
const isClientSideUrl=href=>{if(typeof href!=="string"){return false}return !/^(https?:)?\/\//i.test(href)&&!/^([^#]*#[\w-]*|[\w\-.]+:)/.test(href)};
|
|
11
11
|
|
|
12
12
|
function withRouter(Component){function WithRouterWrapper(props){const navigate=useNavigate();return jsx(Component,{...props,navigate:navigate})}WithRouterWrapper.displayName="withRouter(ClickableBehavior)";return WithRouterWrapper}const ClickableBehaviorWithRouter=withRouter(ClickableBehavior);function getClickableBehavior(href,skipClientNav,inRouterContext){if(inRouterContext&&skipClientNav!==true&&href&&isClientSideUrl(href)){return ClickableBehaviorWithRouter}return ClickableBehavior}
|
|
13
13
|
|
|
14
|
-
const StyledA=addStyle("a");const StyledButton=addStyle("button");const StyledLink=addStyle(Link);const Clickable=React.forwardRef(function Clickable(props,ref){const getCorrectTag=(clickableState,inRouterContext,commonProps)=>{const activeHref=props.href&&!props.disabled;const useClient=inRouterContext&&!props.skipClientNav&&isClientSideUrl(props.href||"");if(activeHref&&useClient&&props.href){return jsx(StyledLink,{...commonProps,to:props.href,role:props.role,target:props.target||undefined,"aria-disabled":props.disabled?"true":"false",ref:ref,children:props.children(clickableState)})}else if(activeHref&&!useClient){return jsx(StyledA,{...commonProps,href:props.href,role:props.role,target:props.target||undefined,"aria-disabled":props.disabled?"true":"false",ref:ref,children:props.children(clickableState)})}else {return jsx(StyledButton,{...commonProps,type:"button","aria-disabled":props.disabled,ref:ref,children:props.children(clickableState)})}};const inRouterContext=useInRouterContext();const{href,onClick,onFocus,onKeyDown,onKeyUp,onMouseDown,onMouseUp,onMouseEnter,onMouseLeave,skipClientNav,beforeNav=undefined,safeWithNav=undefined,style,target=undefined,testId,hideDefaultFocusRing,disabled,tabIndex,...restProps}=props;const ClickableBehavior=getClickableBehavior(href,skipClientNav,inRouterContext);const getStyle=state=>[styles.reset,styles.link,!hideDefaultFocusRing&&state.focused&&styles.focused,disabled&&styles.disabled,style];
|
|
14
|
+
const StyledA=addStyle("a");const StyledButton=addStyle("button");const StyledLink=addStyle(Link);const Clickable=React.forwardRef(function Clickable(props,ref){const getCorrectTag=(clickableState,inRouterContext,commonProps)=>{const activeHref=props.href&&!props.disabled;const useClient=inRouterContext&&!props.skipClientNav&&isClientSideUrl(props.href||"");if(activeHref&&useClient&&props.href){return jsx(StyledLink,{...commonProps,to:props.href,role:props.role,target:props.target||undefined,"aria-disabled":props.disabled?"true":"false",ref:ref,children:props.children(clickableState)})}else if(activeHref&&!useClient){return jsx(StyledA,{...commonProps,href:props.href,role:props.role,target:props.target||undefined,"aria-disabled":props.disabled?"true":"false",ref:ref,children:props.children(clickableState)})}else {return jsx(StyledButton,{...commonProps,type:"button","aria-disabled":props.disabled,ref:ref,children:props.children(clickableState)})}};const inRouterContext=useInRouterContext();const{href,onClick,onFocus,onBlur,onKeyDown,onKeyUp,onMouseDown,onMouseUp,onMouseEnter,onMouseLeave,skipClientNav,beforeNav=undefined,safeWithNav=undefined,style,target=undefined,testId,hideDefaultFocusRing,disabled,tabIndex,...restProps}=props;const ClickableBehavior=getClickableBehavior(href,skipClientNav,inRouterContext);const getStyle=state=>[styles.reset,styles.link,!hideDefaultFocusRing&&state.focused&&styles.focused,disabled&&styles.disabled,style];const sharedProps={href,onClick,safeWithNav,onFocus,onBlur,onKeyDown,onKeyUp,onMouseDown,onMouseUp,onMouseEnter,onMouseLeave,disabled,tabIndex};if(beforeNav){return jsx(ClickableBehavior,{...sharedProps,beforeNav:beforeNav,children:(state,childrenProps)=>getCorrectTag(state,inRouterContext,{...restProps,"data-testid":testId,style:getStyle(state),...childrenProps})})}else {return jsx(ClickableBehavior,{...sharedProps,target:target,children:(state,childrenProps)=>getCorrectTag(state,inRouterContext,{...restProps,"data-testid":testId,style:getStyle(state),...childrenProps})})}});const styles=StyleSheet.create({reset:{border:"none",margin:0,padding:0,width:"auto",overflow:"visible",background:"transparent",textDecoration:"none",color:"inherit",font:"inherit",boxSizing:"border-box",touchAction:"manipulation",userSelect:"none",outline:"none",lineHeight:"normal",WebkitFontSmoothing:"inherit",MozOsxFontSmoothing:"inherit"},link:{cursor:"pointer"},focused:{":focus":{outline:`solid ${border.width.medium} ${semanticColor.focus.outer}`}},disabled:{color:semanticColor.action.secondary.disabled.foreground,cursor:"not-allowed",":focus":{outline:"none"},":focus-visible":{outline:`solid ${border.width.medium} ${semanticColor.focus.outer}`}}});
|
|
15
15
|
|
|
16
16
|
export { ClickableBehavior, Clickable as default, getClickableBehavior, isClientSideUrl };
|
package/dist/index.js
CHANGED
|
@@ -29,13 +29,13 @@ function _interopNamespace(e) {
|
|
|
29
29
|
|
|
30
30
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
31
31
|
|
|
32
|
-
const getAppropriateTriggersForRole=role=>{switch(role){case"link":return {triggerOnEnter:true,triggerOnSpace:false};case"checkbox":case"radio":case"listbox":return {triggerOnEnter:false,triggerOnSpace:true};case"button":case"menuitem":case"menu":case"option":default:return {triggerOnEnter:true,triggerOnSpace:true}}};const disabledHandlers={onClick:()=>void 0,onMouseEnter:()=>void 0,onMouseLeave:()=>void 0,onMouseDown:()=>void 0,onMouseUp:()=>void 0,onTouchStart:()=>void 0,onTouchEnd:()=>void 0,onTouchCancel:()=>void 0,onKeyDown:()=>void 0,onKeyUp:()=>void 0};const startState={hovered:false,focused:false,pressed:false,waiting:false};class ClickableBehavior extends React__namespace.Component{static getDerivedStateFromProps(props,state){if(props.disabled){return {...startState,focused:state.focused}}else {return null}}navigateOrReset(shouldNavigate){if(shouldNavigate){const{navigate,href,skipClientNav,target=undefined}=this.props;if(href){if(target==="_blank"){window.open(href,"_blank");this.setState({waiting:false});}else if(navigate&&!skipClientNav){navigate(href,{viewTransition:this.props.viewTransition});this.setState({waiting:false});}else {window.location.assign(href);}}}else {this.setState({waiting:false});}}handleSafeWithNav(safeWithNav,shouldNavigate){const{skipClientNav,navigate}=this.props;if(navigate&&!skipClientNav||this.props.target==="_blank"){safeWithNav();this.navigateOrReset(shouldNavigate);return Promise.resolve()}else {if(!this.state.waiting){this.setState({waiting:true});}return safeWithNav().then(()=>{if(!this.state.waiting){this.setState({waiting:true});}return}).catch(
|
|
32
|
+
const getAppropriateTriggersForRole=role=>{switch(role){case"link":return {triggerOnEnter:true,triggerOnSpace:false};case"checkbox":case"radio":case"listbox":return {triggerOnEnter:false,triggerOnSpace:true};case"button":case"menuitem":case"menu":case"option":default:return {triggerOnEnter:true,triggerOnSpace:true}}};const disabledHandlers={onClick:()=>void 0,onMouseEnter:()=>void 0,onMouseLeave:()=>void 0,onMouseDown:()=>void 0,onMouseUp:()=>void 0,onTouchStart:()=>void 0,onTouchEnd:()=>void 0,onTouchCancel:()=>void 0,onKeyDown:()=>void 0,onKeyUp:()=>void 0};const startState={hovered:false,focused:false,pressed:false,waiting:false};class ClickableBehavior extends React__namespace.Component{static getDerivedStateFromProps(props,state){if(props.disabled){return {...startState,focused:state.focused}}else {return null}}navigateOrReset(shouldNavigate){if(shouldNavigate){const{navigate,href,skipClientNav,target=undefined}=this.props;if(href){if(target==="_blank"){window.open(href,"_blank");this.setState({waiting:false});}else if(navigate&&!skipClientNav){navigate(href,{viewTransition:this.props.viewTransition});this.setState({waiting:false});}else {window.location.assign(href);}}}else {this.setState({waiting:false});}}handleSafeWithNav(safeWithNav,shouldNavigate){const{skipClientNav,navigate}=this.props;if(navigate&&!skipClientNav||this.props.target==="_blank"){safeWithNav();this.navigateOrReset(shouldNavigate);return Promise.resolve()}else {if(!this.state.waiting){this.setState({waiting:true});}return safeWithNav().then(()=>{if(!this.state.waiting){this.setState({waiting:true});}return}).catch(_=>{}).finally(()=>{this.navigateOrReset(shouldNavigate);})}}runCallbackAndMaybeNavigate(e){const{onClick=undefined,beforeNav=undefined,safeWithNav=undefined,href,type}=this.props;let shouldNavigate=true;let canSubmit=true;onClick?.(e);if(e.defaultPrevented){shouldNavigate=false;canSubmit=false;}e.preventDefault();if(!href&&type==="submit"&&canSubmit){let target=e.currentTarget;while(target){if(target instanceof window.HTMLFormElement){const event=new window.Event("submit",{bubbles:true,cancelable:true});target.dispatchEvent(event);break}target=target.parentElement;}}if(beforeNav){this.setState({waiting:true});beforeNav().then(()=>{if(safeWithNav){return this.handleSafeWithNav(safeWithNav,shouldNavigate)}else {return this.navigateOrReset(shouldNavigate)}}).catch(()=>{});}else if(safeWithNav){return this.handleSafeWithNav(safeWithNav,shouldNavigate)}else {this.navigateOrReset(shouldNavigate);}}render(){const rel=this.props.rel||(this.props.target==="_blank"?"noopener noreferrer":undefined);const childrenProps=this.props.disabled?{...disabledHandlers,onFocus:this.handleFocus,onBlur:this.handleBlur,tabIndex:this.props.tabIndex,rel}:{onClick:this.handleClick,onMouseEnter:this.handleMouseEnter,onMouseLeave:this.handleMouseLeave,onMouseDown:this.handleMouseDown,onMouseUp:this.handleMouseUp,onTouchStart:this.handleTouchStart,onTouchEnd:this.handleTouchEnd,onTouchCancel:this.handleTouchCancel,onKeyDown:this.handleKeyDown,onKeyUp:this.handleKeyUp,onFocus:this.handleFocus,onBlur:this.handleBlur,tabIndex:this.props.tabIndex,rel};const{children}=this.props;return children&&children(this.state,childrenProps)}constructor(props){super(props),this.handleClick=e=>{const{onClick=undefined,beforeNav=undefined,safeWithNav=undefined}=this.props;if(this.enterClick){return}if(onClick||beforeNav||safeWithNav){this.waitingForClick=false;}this.runCallbackAndMaybeNavigate(e);},this.handleMouseEnter=e=>{this.props.onMouseEnter?.(e);if(!this.waitingForClick){this.setState({hovered:true});}},this.handleMouseLeave=e=>{this.props.onMouseLeave?.(e);if(!this.waitingForClick){this.setState({hovered:false,pressed:false,focused:false});}},this.handleMouseDown=e=>{this.props.onMouseDown?.(e);this.setState({pressed:true});},this.handleMouseUp=e=>{this.props.onMouseUp?.(e);this.setState({pressed:false,focused:false});},this.handleTouchStart=()=>{this.setState({pressed:true});},this.handleTouchEnd=()=>{this.setState({pressed:false});this.waitingForClick=true;},this.handleTouchCancel=()=>{this.setState({pressed:false});this.waitingForClick=true;},this.handleKeyDown=e=>{const{onKeyDown,role}=this.props;onKeyDown?.(e);const keyName=e.key;const{triggerOnEnter,triggerOnSpace}=getAppropriateTriggersForRole(role);if(triggerOnEnter&&keyName===wonderBlocksCore.keys.enter||triggerOnSpace&&keyName===wonderBlocksCore.keys.space){e.preventDefault();this.setState({pressed:true});}else if(!triggerOnEnter&&keyName===wonderBlocksCore.keys.enter){this.enterClick=true;}},this.handleKeyUp=e=>{const{onKeyUp,role}=this.props;onKeyUp?.(e);const keyName=e.key;const{triggerOnEnter,triggerOnSpace}=getAppropriateTriggersForRole(role);if(triggerOnEnter&&keyName===wonderBlocksCore.keys.enter||triggerOnSpace&&keyName===wonderBlocksCore.keys.space){this.setState({pressed:false,focused:true});this.runCallbackAndMaybeNavigate(e);}else if(!triggerOnEnter&&keyName===wonderBlocksCore.keys.enter){this.enterClick=false;}},this.handleFocus=e=>{const{onFocus}=this.props;this.setState({focused:true},()=>{onFocus?.(e);});},this.handleBlur=e=>{const{onBlur}=this.props;this.setState({focused:false,pressed:false},()=>{onBlur?.(e);});};this.state=startState;this.waitingForClick=false;this.enterClick=false;}}ClickableBehavior.defaultProps={disabled:false};
|
|
33
33
|
|
|
34
34
|
const isClientSideUrl=href=>{if(typeof href!=="string"){return false}return !/^(https?:)?\/\//i.test(href)&&!/^([^#]*#[\w-]*|[\w\-.]+:)/.test(href)};
|
|
35
35
|
|
|
36
36
|
function withRouter(Component){function WithRouterWrapper(props){const navigate=reactRouterDomV5Compat.useNavigate();return jsxRuntime.jsx(Component,{...props,navigate:navigate})}WithRouterWrapper.displayName="withRouter(ClickableBehavior)";return WithRouterWrapper}const ClickableBehaviorWithRouter=withRouter(ClickableBehavior);function getClickableBehavior(href,skipClientNav,inRouterContext){if(inRouterContext&&skipClientNav!==true&&href&&isClientSideUrl(href)){return ClickableBehaviorWithRouter}return ClickableBehavior}
|
|
37
37
|
|
|
38
|
-
const StyledA=wonderBlocksCore.addStyle("a");const StyledButton=wonderBlocksCore.addStyle("button");const StyledLink=wonderBlocksCore.addStyle(reactRouterDomV5Compat.Link);const Clickable=React__namespace.forwardRef(function Clickable(props,ref){const getCorrectTag=(clickableState,inRouterContext,commonProps)=>{const activeHref=props.href&&!props.disabled;const useClient=inRouterContext&&!props.skipClientNav&&isClientSideUrl(props.href||"");if(activeHref&&useClient&&props.href){return jsxRuntime.jsx(StyledLink,{...commonProps,to:props.href,role:props.role,target:props.target||undefined,"aria-disabled":props.disabled?"true":"false",ref:ref,children:props.children(clickableState)})}else if(activeHref&&!useClient){return jsxRuntime.jsx(StyledA,{...commonProps,href:props.href,role:props.role,target:props.target||undefined,"aria-disabled":props.disabled?"true":"false",ref:ref,children:props.children(clickableState)})}else {return jsxRuntime.jsx(StyledButton,{...commonProps,type:"button","aria-disabled":props.disabled,ref:ref,children:props.children(clickableState)})}};const inRouterContext=reactRouterDomV5Compat.useInRouterContext();const{href,onClick,onFocus,onKeyDown,onKeyUp,onMouseDown,onMouseUp,onMouseEnter,onMouseLeave,skipClientNav,beforeNav=undefined,safeWithNav=undefined,style,target=undefined,testId,hideDefaultFocusRing,disabled,tabIndex,...restProps}=props;const ClickableBehavior=getClickableBehavior(href,skipClientNav,inRouterContext);const getStyle=state=>[styles.reset,styles.link,!hideDefaultFocusRing&&state.focused&&styles.focused,disabled&&styles.disabled,style];
|
|
38
|
+
const StyledA=wonderBlocksCore.addStyle("a");const StyledButton=wonderBlocksCore.addStyle("button");const StyledLink=wonderBlocksCore.addStyle(reactRouterDomV5Compat.Link);const Clickable=React__namespace.forwardRef(function Clickable(props,ref){const getCorrectTag=(clickableState,inRouterContext,commonProps)=>{const activeHref=props.href&&!props.disabled;const useClient=inRouterContext&&!props.skipClientNav&&isClientSideUrl(props.href||"");if(activeHref&&useClient&&props.href){return jsxRuntime.jsx(StyledLink,{...commonProps,to:props.href,role:props.role,target:props.target||undefined,"aria-disabled":props.disabled?"true":"false",ref:ref,children:props.children(clickableState)})}else if(activeHref&&!useClient){return jsxRuntime.jsx(StyledA,{...commonProps,href:props.href,role:props.role,target:props.target||undefined,"aria-disabled":props.disabled?"true":"false",ref:ref,children:props.children(clickableState)})}else {return jsxRuntime.jsx(StyledButton,{...commonProps,type:"button","aria-disabled":props.disabled,ref:ref,children:props.children(clickableState)})}};const inRouterContext=reactRouterDomV5Compat.useInRouterContext();const{href,onClick,onFocus,onBlur,onKeyDown,onKeyUp,onMouseDown,onMouseUp,onMouseEnter,onMouseLeave,skipClientNav,beforeNav=undefined,safeWithNav=undefined,style,target=undefined,testId,hideDefaultFocusRing,disabled,tabIndex,...restProps}=props;const ClickableBehavior=getClickableBehavior(href,skipClientNav,inRouterContext);const getStyle=state=>[styles.reset,styles.link,!hideDefaultFocusRing&&state.focused&&styles.focused,disabled&&styles.disabled,style];const sharedProps={href,onClick,safeWithNav,onFocus,onBlur,onKeyDown,onKeyUp,onMouseDown,onMouseUp,onMouseEnter,onMouseLeave,disabled,tabIndex};if(beforeNav){return jsxRuntime.jsx(ClickableBehavior,{...sharedProps,beforeNav:beforeNav,children:(state,childrenProps)=>getCorrectTag(state,inRouterContext,{...restProps,"data-testid":testId,style:getStyle(state),...childrenProps})})}else {return jsxRuntime.jsx(ClickableBehavior,{...sharedProps,target:target,children:(state,childrenProps)=>getCorrectTag(state,inRouterContext,{...restProps,"data-testid":testId,style:getStyle(state),...childrenProps})})}});const styles=aphrodite.StyleSheet.create({reset:{border:"none",margin:0,padding:0,width:"auto",overflow:"visible",background:"transparent",textDecoration:"none",color:"inherit",font:"inherit",boxSizing:"border-box",touchAction:"manipulation",userSelect:"none",outline:"none",lineHeight:"normal",WebkitFontSmoothing:"inherit",MozOsxFontSmoothing:"inherit"},link:{cursor:"pointer"},focused:{":focus":{outline:`solid ${wonderBlocksTokens.border.width.medium} ${wonderBlocksTokens.semanticColor.focus.outer}`}},disabled:{color:wonderBlocksTokens.semanticColor.action.secondary.disabled.foreground,cursor:"not-allowed",":focus":{outline:"none"},":focus-visible":{outline:`solid ${wonderBlocksTokens.border.width.medium} ${wonderBlocksTokens.semanticColor.focus.outer}`}}});
|
|
39
39
|
|
|
40
40
|
exports.ClickableBehavior = ClickableBehavior;
|
|
41
41
|
exports["default"] = Clickable;
|