@khanacademy/wonder-blocks-clickable 2.4.3 → 2.4.5
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 +19 -0
- package/dist/index.js +551 -0
- package/dist/index.js.flow +1 -1
- package/package.json +5 -4
- package/src/components/__docs__/clickable-behavior.argtypes.js +1 -1
- package/src/components/__docs__/clickable-behavior.stories.js +2 -2
- package/src/components/__docs__/clickable.stories.js +2 -2
- package/src/components/__tests__/clickable-behavior.test.js +2 -2
- package/src/components/__tests__/clickable.test.js +1 -1
- package/src/components/clickable-behavior.js +24 -31
- package/src/components/clickable.js +32 -72
- package/src/index.js +5 -5
- package/src/util/__tests__/get-clickable-behavior.test.js +2 -2
- package/src/util/__tests__/is-client-side-url.js.test.js +1 -1
- package/src/util/get-clickable-behavior.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-clickable
|
|
2
2
|
|
|
3
|
+
## 2.4.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 91cb727c: Remove file extensions from imports
|
|
8
|
+
- 91cb727c: Merge disjoint prop types since the codemod doesn't handle these properly.
|
|
9
|
+
- Updated dependencies [91cb727c]
|
|
10
|
+
- Updated dependencies [91cb727c]
|
|
11
|
+
- @khanacademy/wonder-blocks-color@1.2.1
|
|
12
|
+
- @khanacademy/wonder-blocks-core@4.7.0
|
|
13
|
+
|
|
14
|
+
## 2.4.4
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 496119f2: Cleanup WB interdependencies
|
|
19
|
+
- Updated dependencies [496119f2]
|
|
20
|
+
- @khanacademy/wonder-blocks-core@4.6.2
|
|
21
|
+
|
|
3
22
|
## 2.4.3
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var _objectWithoutPropertiesLoose = require('@babel/runtime/helpers/objectWithoutPropertiesLoose');
|
|
6
|
+
var _extends = require('@babel/runtime/helpers/extends');
|
|
7
|
+
var React = require('react');
|
|
8
|
+
var aphrodite = require('aphrodite');
|
|
9
|
+
var reactRouterDom = require('react-router-dom');
|
|
10
|
+
var reactRouter = require('react-router');
|
|
11
|
+
var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
|
|
12
|
+
var Color = require('@khanacademy/wonder-blocks-color');
|
|
13
|
+
|
|
14
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
15
|
+
|
|
16
|
+
function _interopNamespace(e) {
|
|
17
|
+
if (e && e.__esModule) return e;
|
|
18
|
+
var n = Object.create(null);
|
|
19
|
+
if (e) {
|
|
20
|
+
Object.keys(e).forEach(function (k) {
|
|
21
|
+
if (k !== 'default') {
|
|
22
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
23
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return e[k]; }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
n["default"] = e;
|
|
31
|
+
return Object.freeze(n);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var _objectWithoutPropertiesLoose__default = /*#__PURE__*/_interopDefaultLegacy(_objectWithoutPropertiesLoose);
|
|
35
|
+
var _extends__default = /*#__PURE__*/_interopDefaultLegacy(_extends);
|
|
36
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
37
|
+
var Color__default = /*#__PURE__*/_interopDefaultLegacy(Color);
|
|
38
|
+
|
|
39
|
+
const getAppropriateTriggersForRole = role => {
|
|
40
|
+
switch (role) {
|
|
41
|
+
case "link":
|
|
42
|
+
return {
|
|
43
|
+
triggerOnEnter: true,
|
|
44
|
+
triggerOnSpace: false
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
case "checkbox":
|
|
48
|
+
case "radio":
|
|
49
|
+
case "listbox":
|
|
50
|
+
return {
|
|
51
|
+
triggerOnEnter: false,
|
|
52
|
+
triggerOnSpace: true
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
case "button":
|
|
56
|
+
case "menuitem":
|
|
57
|
+
case "menu":
|
|
58
|
+
case "option":
|
|
59
|
+
default:
|
|
60
|
+
return {
|
|
61
|
+
triggerOnEnter: true,
|
|
62
|
+
triggerOnSpace: true
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const disabledHandlers = {
|
|
68
|
+
onClick: () => void 0,
|
|
69
|
+
onMouseEnter: () => void 0,
|
|
70
|
+
onMouseLeave: () => void 0,
|
|
71
|
+
onMouseDown: () => void 0,
|
|
72
|
+
onMouseUp: () => void 0,
|
|
73
|
+
onTouchStart: () => void 0,
|
|
74
|
+
onTouchEnd: () => void 0,
|
|
75
|
+
onTouchCancel: () => void 0,
|
|
76
|
+
onKeyDown: () => void 0,
|
|
77
|
+
onKeyUp: () => void 0
|
|
78
|
+
};
|
|
79
|
+
const keyCodes = {
|
|
80
|
+
enter: 13,
|
|
81
|
+
space: 32
|
|
82
|
+
};
|
|
83
|
+
const startState = {
|
|
84
|
+
hovered: false,
|
|
85
|
+
focused: false,
|
|
86
|
+
pressed: false,
|
|
87
|
+
waiting: false
|
|
88
|
+
};
|
|
89
|
+
class ClickableBehavior extends React__namespace.Component {
|
|
90
|
+
static getDerivedStateFromProps(props, state) {
|
|
91
|
+
if (props.disabled) {
|
|
92
|
+
return _extends__default["default"]({}, startState, {
|
|
93
|
+
focused: state.focused
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
constructor(props) {
|
|
101
|
+
super(props);
|
|
102
|
+
|
|
103
|
+
this.handleClick = e => {
|
|
104
|
+
const {
|
|
105
|
+
onClick = undefined,
|
|
106
|
+
beforeNav = undefined,
|
|
107
|
+
safeWithNav = undefined
|
|
108
|
+
} = this.props;
|
|
109
|
+
|
|
110
|
+
if (this.enterClick) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (onClick || beforeNav || safeWithNav) {
|
|
115
|
+
this.waitingForClick = false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.runCallbackAndMaybeNavigate(e);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
this.handleMouseEnter = e => {
|
|
122
|
+
if (!this.waitingForClick) {
|
|
123
|
+
this.setState({
|
|
124
|
+
hovered: true
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
this.handleMouseLeave = () => {
|
|
130
|
+
if (!this.waitingForClick) {
|
|
131
|
+
this.setState({
|
|
132
|
+
hovered: false,
|
|
133
|
+
pressed: false,
|
|
134
|
+
focused: false
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.handleMouseDown = () => {
|
|
140
|
+
this.setState({
|
|
141
|
+
pressed: true
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
this.handleMouseUp = e => {
|
|
146
|
+
this.setState({
|
|
147
|
+
pressed: false,
|
|
148
|
+
focused: false
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
this.handleTouchStart = () => {
|
|
153
|
+
this.setState({
|
|
154
|
+
pressed: true
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
this.handleTouchEnd = () => {
|
|
159
|
+
this.setState({
|
|
160
|
+
pressed: false
|
|
161
|
+
});
|
|
162
|
+
this.waitingForClick = true;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
this.handleTouchCancel = () => {
|
|
166
|
+
this.setState({
|
|
167
|
+
pressed: false
|
|
168
|
+
});
|
|
169
|
+
this.waitingForClick = true;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
this.handleKeyDown = e => {
|
|
173
|
+
const {
|
|
174
|
+
onKeyDown,
|
|
175
|
+
role
|
|
176
|
+
} = this.props;
|
|
177
|
+
|
|
178
|
+
if (onKeyDown) {
|
|
179
|
+
onKeyDown(e);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const keyCode = e.which || e.keyCode;
|
|
183
|
+
const {
|
|
184
|
+
triggerOnEnter,
|
|
185
|
+
triggerOnSpace
|
|
186
|
+
} = getAppropriateTriggersForRole(role);
|
|
187
|
+
|
|
188
|
+
if (triggerOnEnter && keyCode === keyCodes.enter || triggerOnSpace && keyCode === keyCodes.space) {
|
|
189
|
+
e.preventDefault();
|
|
190
|
+
this.setState({
|
|
191
|
+
pressed: true
|
|
192
|
+
});
|
|
193
|
+
} else if (!triggerOnEnter && keyCode === keyCodes.enter) {
|
|
194
|
+
this.enterClick = true;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
this.handleKeyUp = e => {
|
|
199
|
+
const {
|
|
200
|
+
onKeyUp,
|
|
201
|
+
role
|
|
202
|
+
} = this.props;
|
|
203
|
+
|
|
204
|
+
if (onKeyUp) {
|
|
205
|
+
onKeyUp(e);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const keyCode = e.which || e.keyCode;
|
|
209
|
+
const {
|
|
210
|
+
triggerOnEnter,
|
|
211
|
+
triggerOnSpace
|
|
212
|
+
} = getAppropriateTriggersForRole(role);
|
|
213
|
+
|
|
214
|
+
if (triggerOnEnter && keyCode === keyCodes.enter || triggerOnSpace && keyCode === keyCodes.space) {
|
|
215
|
+
this.setState({
|
|
216
|
+
pressed: false,
|
|
217
|
+
focused: true
|
|
218
|
+
});
|
|
219
|
+
this.runCallbackAndMaybeNavigate(e);
|
|
220
|
+
} else if (!triggerOnEnter && keyCode === keyCodes.enter) {
|
|
221
|
+
this.enterClick = false;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
this.handleFocus = e => {
|
|
226
|
+
this.setState({
|
|
227
|
+
focused: true
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
this.handleBlur = e => {
|
|
232
|
+
this.setState({
|
|
233
|
+
focused: false,
|
|
234
|
+
pressed: false
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
this.state = startState;
|
|
239
|
+
this.waitingForClick = false;
|
|
240
|
+
this.enterClick = false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
navigateOrReset(shouldNavigate) {
|
|
244
|
+
if (shouldNavigate) {
|
|
245
|
+
const {
|
|
246
|
+
history,
|
|
247
|
+
href,
|
|
248
|
+
skipClientNav,
|
|
249
|
+
target = undefined
|
|
250
|
+
} = this.props;
|
|
251
|
+
|
|
252
|
+
if (href) {
|
|
253
|
+
if (target === "_blank") {
|
|
254
|
+
window.open(href, "_blank");
|
|
255
|
+
this.setState({
|
|
256
|
+
waiting: false
|
|
257
|
+
});
|
|
258
|
+
} else if (history && !skipClientNav) {
|
|
259
|
+
history.push(href);
|
|
260
|
+
this.setState({
|
|
261
|
+
waiting: false
|
|
262
|
+
});
|
|
263
|
+
} else {
|
|
264
|
+
window.location.assign(href);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
this.setState({
|
|
269
|
+
waiting: false
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
handleSafeWithNav(safeWithNav, shouldNavigate) {
|
|
275
|
+
const {
|
|
276
|
+
skipClientNav,
|
|
277
|
+
history
|
|
278
|
+
} = this.props;
|
|
279
|
+
|
|
280
|
+
if (history && !skipClientNav || this.props.target === "_blank") {
|
|
281
|
+
safeWithNav();
|
|
282
|
+
this.navigateOrReset(shouldNavigate);
|
|
283
|
+
return Promise.resolve();
|
|
284
|
+
} else {
|
|
285
|
+
if (!this.state.waiting) {
|
|
286
|
+
this.setState({
|
|
287
|
+
waiting: true
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return safeWithNav().then(() => {
|
|
292
|
+
if (!this.state.waiting) {
|
|
293
|
+
this.setState({
|
|
294
|
+
waiting: true
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return;
|
|
299
|
+
}).catch(error => {}).finally(() => {
|
|
300
|
+
this.navigateOrReset(shouldNavigate);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
runCallbackAndMaybeNavigate(e) {
|
|
306
|
+
const {
|
|
307
|
+
onClick = undefined,
|
|
308
|
+
beforeNav = undefined,
|
|
309
|
+
safeWithNav = undefined,
|
|
310
|
+
href,
|
|
311
|
+
type
|
|
312
|
+
} = this.props;
|
|
313
|
+
let shouldNavigate = true;
|
|
314
|
+
let canSubmit = true;
|
|
315
|
+
|
|
316
|
+
if (onClick) {
|
|
317
|
+
onClick(e);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (e.defaultPrevented) {
|
|
321
|
+
shouldNavigate = false;
|
|
322
|
+
canSubmit = false;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
e.preventDefault();
|
|
326
|
+
|
|
327
|
+
if (!href && type === "submit" && canSubmit) {
|
|
328
|
+
let target = e.currentTarget;
|
|
329
|
+
|
|
330
|
+
while (target) {
|
|
331
|
+
if (target instanceof window.HTMLFormElement) {
|
|
332
|
+
const event = new window.Event("submit", {
|
|
333
|
+
cancelable: true
|
|
334
|
+
});
|
|
335
|
+
target.dispatchEvent(event);
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
target = target.parentElement;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (beforeNav) {
|
|
344
|
+
this.setState({
|
|
345
|
+
waiting: true
|
|
346
|
+
});
|
|
347
|
+
beforeNav().then(() => {
|
|
348
|
+
if (safeWithNav) {
|
|
349
|
+
return this.handleSafeWithNav(safeWithNav, shouldNavigate);
|
|
350
|
+
} else {
|
|
351
|
+
return this.navigateOrReset(shouldNavigate);
|
|
352
|
+
}
|
|
353
|
+
}).catch(() => {});
|
|
354
|
+
} else if (safeWithNav) {
|
|
355
|
+
return this.handleSafeWithNav(safeWithNav, shouldNavigate);
|
|
356
|
+
} else {
|
|
357
|
+
this.navigateOrReset(shouldNavigate);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
render() {
|
|
362
|
+
const childrenProps = this.props.disabled ? _extends__default["default"]({}, disabledHandlers, {
|
|
363
|
+
onFocus: this.handleFocus,
|
|
364
|
+
onBlur: this.handleBlur,
|
|
365
|
+
tabIndex: this.props.tabIndex
|
|
366
|
+
}) : {
|
|
367
|
+
onClick: this.handleClick,
|
|
368
|
+
onMouseEnter: this.handleMouseEnter,
|
|
369
|
+
onMouseLeave: this.handleMouseLeave,
|
|
370
|
+
onMouseDown: this.handleMouseDown,
|
|
371
|
+
onMouseUp: this.handleMouseUp,
|
|
372
|
+
onTouchStart: this.handleTouchStart,
|
|
373
|
+
onTouchEnd: this.handleTouchEnd,
|
|
374
|
+
onTouchCancel: this.handleTouchCancel,
|
|
375
|
+
onKeyDown: this.handleKeyDown,
|
|
376
|
+
onKeyUp: this.handleKeyUp,
|
|
377
|
+
onFocus: this.handleFocus,
|
|
378
|
+
onBlur: this.handleBlur,
|
|
379
|
+
tabIndex: this.props.tabIndex
|
|
380
|
+
};
|
|
381
|
+
childrenProps.rel = this.props.rel || (this.props.target === "_blank" ? "noopener noreferrer" : undefined);
|
|
382
|
+
const {
|
|
383
|
+
children
|
|
384
|
+
} = this.props;
|
|
385
|
+
return children && children(this.state, childrenProps);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
}
|
|
389
|
+
ClickableBehavior.defaultProps = {
|
|
390
|
+
disabled: false
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const isClientSideUrl = href => {
|
|
394
|
+
if (typeof href !== "string") {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return !/^(https?:)?\/\//i.test(href) && !/^([^#]*#[\w-]*|[\w\-.]+:)/.test(href);
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const ClickableBehaviorWithRouter = reactRouterDom.withRouter(ClickableBehavior);
|
|
402
|
+
function getClickableBehavior(href, skipClientNav, router) {
|
|
403
|
+
if (router && skipClientNav !== true && href && isClientSideUrl(href)) {
|
|
404
|
+
return ClickableBehaviorWithRouter;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return ClickableBehavior;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const _excluded = ["href", "onClick", "skipClientNav", "beforeNav", "safeWithNav", "style", "target", "testId", "onKeyDown", "onKeyUp", "hideDefaultFocusRing", "light", "disabled"];
|
|
411
|
+
const StyledAnchor = wonderBlocksCore.addStyle("a");
|
|
412
|
+
const StyledButton = wonderBlocksCore.addStyle("button");
|
|
413
|
+
const StyledLink = wonderBlocksCore.addStyle(reactRouterDom.Link);
|
|
414
|
+
class Clickable extends React__namespace.Component {
|
|
415
|
+
constructor(...args) {
|
|
416
|
+
super(...args);
|
|
417
|
+
|
|
418
|
+
this.getCorrectTag = (clickableState, router, commonProps) => {
|
|
419
|
+
const activeHref = this.props.href && !this.props.disabled;
|
|
420
|
+
const useClient = router && !this.props.skipClientNav && isClientSideUrl(this.props.href || "");
|
|
421
|
+
|
|
422
|
+
if (activeHref && useClient && this.props.href) {
|
|
423
|
+
return React__namespace.createElement(StyledLink, _extends__default["default"]({}, commonProps, {
|
|
424
|
+
to: this.props.href,
|
|
425
|
+
role: this.props.role,
|
|
426
|
+
target: this.props.target || undefined,
|
|
427
|
+
"aria-disabled": this.props.disabled ? "true" : undefined
|
|
428
|
+
}), this.props.children(clickableState));
|
|
429
|
+
} else if (activeHref && !useClient) {
|
|
430
|
+
return React__namespace.createElement(StyledAnchor, _extends__default["default"]({}, commonProps, {
|
|
431
|
+
href: this.props.href,
|
|
432
|
+
role: this.props.role,
|
|
433
|
+
target: this.props.target || undefined,
|
|
434
|
+
"aria-disabled": this.props.disabled ? "true" : undefined
|
|
435
|
+
}), this.props.children(clickableState));
|
|
436
|
+
} else {
|
|
437
|
+
return React__namespace.createElement(StyledButton, _extends__default["default"]({}, commonProps, {
|
|
438
|
+
type: "button",
|
|
439
|
+
"aria-disabled": this.props.disabled
|
|
440
|
+
}), this.props.children(clickableState));
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
renderClickableBehavior(router) {
|
|
446
|
+
const _this$props = this.props,
|
|
447
|
+
{
|
|
448
|
+
href,
|
|
449
|
+
onClick,
|
|
450
|
+
skipClientNav,
|
|
451
|
+
beforeNav = undefined,
|
|
452
|
+
safeWithNav = undefined,
|
|
453
|
+
style,
|
|
454
|
+
target = undefined,
|
|
455
|
+
testId,
|
|
456
|
+
onKeyDown,
|
|
457
|
+
onKeyUp,
|
|
458
|
+
hideDefaultFocusRing,
|
|
459
|
+
light,
|
|
460
|
+
disabled
|
|
461
|
+
} = _this$props,
|
|
462
|
+
restProps = _objectWithoutPropertiesLoose__default["default"](_this$props, _excluded);
|
|
463
|
+
|
|
464
|
+
const ClickableBehavior = getClickableBehavior(href, skipClientNav, router);
|
|
465
|
+
|
|
466
|
+
const getStyle = state => [styles.reset, styles.link, !hideDefaultFocusRing && state.focused && (light ? styles.focusedLight : styles.focused), disabled && styles.disabled, style];
|
|
467
|
+
|
|
468
|
+
if (beforeNav) {
|
|
469
|
+
return React__namespace.createElement(ClickableBehavior, {
|
|
470
|
+
href: href,
|
|
471
|
+
onClick: onClick,
|
|
472
|
+
beforeNav: beforeNav,
|
|
473
|
+
safeWithNav: safeWithNav,
|
|
474
|
+
onKeyDown: onKeyDown,
|
|
475
|
+
onKeyUp: onKeyUp,
|
|
476
|
+
disabled: disabled
|
|
477
|
+
}, (state, childrenProps) => this.getCorrectTag(state, router, _extends__default["default"]({}, restProps, {
|
|
478
|
+
"data-test-id": testId,
|
|
479
|
+
style: getStyle(state)
|
|
480
|
+
}, childrenProps)));
|
|
481
|
+
} else {
|
|
482
|
+
return React__namespace.createElement(ClickableBehavior, {
|
|
483
|
+
href: href,
|
|
484
|
+
onClick: onClick,
|
|
485
|
+
safeWithNav: safeWithNav,
|
|
486
|
+
onKeyDown: onKeyDown,
|
|
487
|
+
onKeyUp: onKeyUp,
|
|
488
|
+
target: target,
|
|
489
|
+
disabled: disabled
|
|
490
|
+
}, (state, childrenProps) => this.getCorrectTag(state, router, _extends__default["default"]({}, restProps, {
|
|
491
|
+
"data-test-id": testId,
|
|
492
|
+
style: getStyle(state)
|
|
493
|
+
}, childrenProps)));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
render() {
|
|
498
|
+
return React__namespace.createElement(reactRouter.__RouterContext.Consumer, null, router => this.renderClickableBehavior(router));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
}
|
|
502
|
+
Clickable.defaultProps = {
|
|
503
|
+
light: false,
|
|
504
|
+
disabled: false
|
|
505
|
+
};
|
|
506
|
+
const styles = aphrodite.StyleSheet.create({
|
|
507
|
+
reset: {
|
|
508
|
+
border: "none",
|
|
509
|
+
margin: 0,
|
|
510
|
+
padding: 0,
|
|
511
|
+
width: "auto",
|
|
512
|
+
overflow: "visible",
|
|
513
|
+
background: "transparent",
|
|
514
|
+
textDecoration: "none",
|
|
515
|
+
color: "inherit",
|
|
516
|
+
font: "inherit",
|
|
517
|
+
boxSizing: "border-box",
|
|
518
|
+
touchAction: "manipulation",
|
|
519
|
+
userSelect: "none",
|
|
520
|
+
outline: "none",
|
|
521
|
+
lineHeight: "normal",
|
|
522
|
+
WebkitFontSmoothing: "inherit",
|
|
523
|
+
MozOsxFontSmoothing: "inherit"
|
|
524
|
+
},
|
|
525
|
+
link: {
|
|
526
|
+
cursor: "pointer"
|
|
527
|
+
},
|
|
528
|
+
focused: {
|
|
529
|
+
":focus": {
|
|
530
|
+
outline: `solid 2px ${Color__default["default"].blue}`
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
focusedLight: {
|
|
534
|
+
outline: `solid 2px ${Color__default["default"].white}`
|
|
535
|
+
},
|
|
536
|
+
disabled: {
|
|
537
|
+
color: Color__default["default"].offBlack32,
|
|
538
|
+
cursor: "not-allowed",
|
|
539
|
+
":focus": {
|
|
540
|
+
outline: "none"
|
|
541
|
+
},
|
|
542
|
+
":focus-visible": {
|
|
543
|
+
outline: `solid 2px ${Color__default["default"].blue}`
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
exports.ClickableBehavior = ClickableBehavior;
|
|
549
|
+
exports["default"] = Clickable;
|
|
550
|
+
exports.getClickableBehavior = getClickableBehavior;
|
|
551
|
+
exports.isClientSideUrl = isClientSideUrl;
|
package/dist/index.js.flow
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @flow
|
|
2
|
-
export * from "../src/index
|
|
2
|
+
export * from "../src/index";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-clickable",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.5",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"description": "Clickable component for Wonder-Blocks.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@babel/runtime": "^7.18.6",
|
|
19
|
-
"@khanacademy/wonder-blocks-
|
|
19
|
+
"@khanacademy/wonder-blocks-color": "^1.2.1",
|
|
20
|
+
"@khanacademy/wonder-blocks-core": "^4.7.0"
|
|
20
21
|
},
|
|
21
22
|
"peerDependencies": {
|
|
22
23
|
"aphrodite": "^1.2.5",
|
|
@@ -26,6 +27,6 @@
|
|
|
26
27
|
"react-router-dom": "5.3.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
|
-
"wb-dev-build-settings": "^0.
|
|
30
|
+
"wb-dev-build-settings": "^0.7.1"
|
|
30
31
|
}
|
|
31
|
-
}
|
|
32
|
+
}
|
|
@@ -8,9 +8,9 @@ import Color from "@khanacademy/wonder-blocks-color";
|
|
|
8
8
|
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
9
9
|
|
|
10
10
|
import type {StoryComponentType} from "@storybook/react";
|
|
11
|
-
import ComponentInfo from "../../../../../.storybook/components/component-info
|
|
11
|
+
import ComponentInfo from "../../../../../.storybook/components/component-info";
|
|
12
12
|
import {name, version} from "../../../package.json";
|
|
13
|
-
import argTypes from "./clickable-behavior.argtypes
|
|
13
|
+
import argTypes from "./clickable-behavior.argtypes";
|
|
14
14
|
|
|
15
15
|
const ClickableBehavior: React.ComponentType<
|
|
16
16
|
React.ElementConfig<typeof ClickableBehavior>,
|
|
@@ -11,9 +11,9 @@ import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
|
11
11
|
import {Body, LabelLarge} from "@khanacademy/wonder-blocks-typography";
|
|
12
12
|
|
|
13
13
|
import type {StoryComponentType} from "@storybook/react";
|
|
14
|
-
import ComponentInfo from "../../../../../.storybook/components/component-info
|
|
14
|
+
import ComponentInfo from "../../../../../.storybook/components/component-info";
|
|
15
15
|
import {name, version} from "../../../package.json";
|
|
16
|
-
import argTypes from "./clickable.argtypes
|
|
16
|
+
import argTypes from "./clickable.argtypes";
|
|
17
17
|
|
|
18
18
|
export default {
|
|
19
19
|
title: "Clickable / Clickable",
|
|
@@ -6,8 +6,8 @@ import {render, screen, fireEvent, waitFor} from "@testing-library/react";
|
|
|
6
6
|
import {MemoryRouter, Switch, Route} from "react-router-dom";
|
|
7
7
|
import userEvent from "@testing-library/user-event";
|
|
8
8
|
|
|
9
|
-
import getClickableBehavior from "../../util/get-clickable-behavior
|
|
10
|
-
import ClickableBehavior from "../clickable-behavior
|
|
9
|
+
import getClickableBehavior from "../../util/get-clickable-behavior";
|
|
10
|
+
import ClickableBehavior from "../clickable-behavior";
|
|
11
11
|
import type {ClickableState} from "../clickable-behavior";
|
|
12
12
|
|
|
13
13
|
const keyCodes = {
|
|
@@ -5,7 +5,7 @@ import {render, screen, fireEvent, waitFor} from "@testing-library/react";
|
|
|
5
5
|
import userEvent from "@testing-library/user-event";
|
|
6
6
|
|
|
7
7
|
import {View} from "@khanacademy/wonder-blocks-core";
|
|
8
|
-
import Clickable from "../clickable
|
|
8
|
+
import Clickable from "../clickable";
|
|
9
9
|
|
|
10
10
|
describe("Clickable", () => {
|
|
11
11
|
beforeEach(() => {
|
|
@@ -42,7 +42,8 @@ const getAppropriateTriggersForRole = (role: ?ClickableRole) => {
|
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
// TODO(FEI-5000): Convert back to conditional props after TS migration is complete.
|
|
46
|
+
type Props = {|
|
|
46
47
|
/**
|
|
47
48
|
* A function that returns the a React `Element`.
|
|
48
49
|
*
|
|
@@ -131,6 +132,28 @@ type CommonProps = {|
|
|
|
131
132
|
* Respond to raw "keyup" event.
|
|
132
133
|
*/
|
|
133
134
|
onKeyUp?: (e: SyntheticKeyboardEvent<>) => mixed,
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* A target destination window for a link to open in. Should only be used
|
|
138
|
+
* when `href` is specified.
|
|
139
|
+
*/
|
|
140
|
+
// TODO(WB-1262): only allow this prop when `href` is also set.
|
|
141
|
+
target?: "_blank",
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Run async code before navigating to the URL passed to `href`. If the
|
|
145
|
+
* promise returned rejects then navigation will not occur.
|
|
146
|
+
*
|
|
147
|
+
* If both safeWithNav and beforeNav are provided, beforeNav will be run
|
|
148
|
+
* first and safeWithNav will only be run if beforeNav does not reject.
|
|
149
|
+
*
|
|
150
|
+
* WARNING: Using this with `target="_blank"` will trigger built-in popup
|
|
151
|
+
* blockers in Firefox and Safari. This is because we do navigation
|
|
152
|
+
* programmatically and `beforeNav` causes a delay which means that the
|
|
153
|
+
* browser can't make a directly link between a user action and the
|
|
154
|
+
* navigation.
|
|
155
|
+
*/
|
|
156
|
+
beforeNav?: () => Promise<mixed>,
|
|
134
157
|
|};
|
|
135
158
|
|
|
136
159
|
export type ClickableState = {|
|
|
@@ -165,36 +188,6 @@ export type ClickableState = {|
|
|
|
165
188
|
waiting: boolean,
|
|
166
189
|
|};
|
|
167
190
|
|
|
168
|
-
type Props =
|
|
169
|
-
| {|
|
|
170
|
-
...CommonProps,
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* A target destination window for a link to open in. Should only be used
|
|
174
|
-
* when `href` is specified.
|
|
175
|
-
*/
|
|
176
|
-
// TODO(WB-1262): only allow this prop when `href` is also set.
|
|
177
|
-
target?: "_blank",
|
|
178
|
-
|}
|
|
179
|
-
| {|
|
|
180
|
-
...CommonProps,
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Run async code before navigating to the URL passed to `href`. If the
|
|
184
|
-
* promise returned rejects then navigation will not occur.
|
|
185
|
-
*
|
|
186
|
-
* If both safeWithNav and beforeNav are provided, beforeNav will be run
|
|
187
|
-
* first and safeWithNav will only be run if beforeNav does not reject.
|
|
188
|
-
*
|
|
189
|
-
* WARNING: Using this with `target="_blank"` will trigger built-in popup
|
|
190
|
-
* blockers in Firefox and Safari. This is because we do navigation
|
|
191
|
-
* programmatically and `beforeNav` causes a delay which means that the
|
|
192
|
-
* browser can't make a directly link between a user action and the
|
|
193
|
-
* navigation.
|
|
194
|
-
*/
|
|
195
|
-
beforeNav?: () => Promise<mixed>,
|
|
196
|
-
|};
|
|
197
|
-
|
|
198
191
|
type DefaultProps = {|
|
|
199
192
|
disabled: $PropertyType<Props, "disabled">,
|
|
200
193
|
|};
|
|
@@ -8,11 +8,12 @@ import {addStyle} from "@khanacademy/wonder-blocks-core";
|
|
|
8
8
|
import type {AriaProps, StyleType} from "@khanacademy/wonder-blocks-core";
|
|
9
9
|
import Color from "@khanacademy/wonder-blocks-color";
|
|
10
10
|
|
|
11
|
-
import getClickableBehavior from "../util/get-clickable-behavior
|
|
12
|
-
import type {ClickableRole, ClickableState} from "./clickable-behavior
|
|
13
|
-
import {isClientSideUrl} from "../util/is-client-side-url
|
|
11
|
+
import getClickableBehavior from "../util/get-clickable-behavior";
|
|
12
|
+
import type {ClickableRole, ClickableState} from "./clickable-behavior";
|
|
13
|
+
import {isClientSideUrl} from "../util/is-client-side-url";
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// TODO(FEI-5000): Convert back to conditional props after TS migration is complete.
|
|
16
|
+
type Props = {|
|
|
16
17
|
/**
|
|
17
18
|
* aria-label should be used when `spinner={true}` to let people using screen
|
|
18
19
|
* readers that the action taken by clicking the button will take some
|
|
@@ -103,75 +104,34 @@ type CommonProps = {|
|
|
|
103
104
|
* a custom focus ring within your own component that uses Clickable.
|
|
104
105
|
*/
|
|
105
106
|
hideDefaultFocusRing?: boolean,
|
|
106
|
-
|};
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
href: string,
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Run async code in the background while client-side navigating. If the
|
|
140
|
-
* browser does a full page load navigation, the callback promise must be
|
|
141
|
-
* settled before the navigation will occur. Errors are ignored so that
|
|
142
|
-
* navigation is guaranteed to succeed.
|
|
143
|
-
*/
|
|
144
|
-
safeWithNav: () => Promise<mixed>,
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* A target destination window for a link to open in.
|
|
148
|
-
*
|
|
149
|
-
* TODO(WB-1262): only allow this prop when `href` is also set.t
|
|
150
|
-
*/
|
|
151
|
-
target?: "_blank",
|
|
152
|
-
|}
|
|
153
|
-
| {|
|
|
154
|
-
...CommonProps,
|
|
155
|
-
|
|
156
|
-
href: string,
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Run async code before navigating. If the promise returned rejects then
|
|
160
|
-
* navigation will not occur.
|
|
161
|
-
*
|
|
162
|
-
* If both safeWithNav and beforeNav are provided, beforeNav will be run
|
|
163
|
-
* first and safeWithNav will only be run if beforeNav does not reject.
|
|
164
|
-
*/
|
|
165
|
-
beforeNav: () => Promise<mixed>,
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Run async code in the background while client-side navigating. If the
|
|
169
|
-
* browser does a full page load navigation, the callback promise must be
|
|
170
|
-
* settled before the navigation will occur. Errors are ignored so that
|
|
171
|
-
* navigation is guaranteed to succeed.
|
|
172
|
-
*/
|
|
173
|
-
safeWithNav: () => Promise<mixed>,
|
|
174
|
-
|};
|
|
108
|
+
/**
|
|
109
|
+
* A target destination window for a link to open in.
|
|
110
|
+
*
|
|
111
|
+
* TODO(WB-1262): only allow this prop when `href` is also set.t
|
|
112
|
+
*/
|
|
113
|
+
target?: "_blank",
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Run async code before navigating. If the promise returned rejects then
|
|
117
|
+
* navigation will not occur.
|
|
118
|
+
*
|
|
119
|
+
* If both safeWithNav and beforeNav are provided, beforeNav will be run
|
|
120
|
+
* first and safeWithNav will only be run if beforeNav does not reject.
|
|
121
|
+
*
|
|
122
|
+
* WARNING: This prop must be used with `href` and should not be used with
|
|
123
|
+
* `target="blank"`.
|
|
124
|
+
*/
|
|
125
|
+
beforeNav?: () => Promise<mixed>,
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Run async code in the background while client-side navigating. If the
|
|
129
|
+
* browser does a full page load navigation, the callback promise must be
|
|
130
|
+
* settled before the navigation will occur. Errors are ignored so that
|
|
131
|
+
* navigation is guaranteed to succeed.
|
|
132
|
+
*/
|
|
133
|
+
safeWithNav?: () => Promise<mixed>,
|
|
134
|
+
|};
|
|
175
135
|
|
|
176
136
|
type DefaultProps = {|
|
|
177
137
|
light: $PropertyType<Props, "light">,
|
package/src/index.js
CHANGED
|
@@ -3,12 +3,12 @@ import type {
|
|
|
3
3
|
ChildrenProps,
|
|
4
4
|
ClickableState,
|
|
5
5
|
ClickableRole,
|
|
6
|
-
} from "./components/clickable-behavior
|
|
7
|
-
import Clickable from "./components/clickable
|
|
6
|
+
} from "./components/clickable-behavior";
|
|
7
|
+
import Clickable from "./components/clickable";
|
|
8
8
|
|
|
9
|
-
export {default as ClickableBehavior} from "./components/clickable-behavior
|
|
10
|
-
export {default as getClickableBehavior} from "./util/get-clickable-behavior
|
|
11
|
-
export {isClientSideUrl} from "./util/is-client-side-url
|
|
9
|
+
export {default as ClickableBehavior} from "./components/clickable-behavior";
|
|
10
|
+
export {default as getClickableBehavior} from "./util/get-clickable-behavior";
|
|
11
|
+
export {isClientSideUrl} from "./util/is-client-side-url";
|
|
12
12
|
|
|
13
13
|
export {Clickable as default};
|
|
14
14
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import {MemoryRouter} from "react-router-dom";
|
|
4
|
-
import ClickableBehavior from "../../components/clickable-behavior
|
|
5
|
-
import getClickableBehavior from "../get-clickable-behavior
|
|
4
|
+
import ClickableBehavior from "../../components/clickable-behavior";
|
|
5
|
+
import getClickableBehavior from "../get-clickable-behavior";
|
|
6
6
|
|
|
7
7
|
describe("getClickableBehavior", () => {
|
|
8
8
|
test("Without href, returns ClickableBehavior", () => {
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
import * as React from "react";
|
|
13
13
|
import {withRouter} from "react-router-dom";
|
|
14
14
|
|
|
15
|
-
import ClickableBehavior from "../components/clickable-behavior
|
|
16
|
-
import {isClientSideUrl} from "./is-client-side-url
|
|
15
|
+
import ClickableBehavior from "../components/clickable-behavior";
|
|
16
|
+
import {isClientSideUrl} from "./is-client-side-url";
|
|
17
17
|
|
|
18
18
|
const ClickableBehaviorWithRouter = withRouter(ClickableBehavior);
|
|
19
19
|
|