@khanacademy/wonder-blocks-clickable 2.4.3 → 2.4.4

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @khanacademy/wonder-blocks-clickable
2
2
 
3
+ ## 2.4.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 496119f2: Cleanup WB interdependencies
8
+ - Updated dependencies [496119f2]
9
+ - @khanacademy/wonder-blocks-core@4.6.2
10
+
3
11
  ## 2.4.3
4
12
 
5
13
  ### 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-clickable",
3
- "version": "2.4.3",
3
+ "version": "2.4.4",
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-core": "^4.6.1"
19
+ "@khanacademy/wonder-blocks-color": "^1.2.0",
20
+ "@khanacademy/wonder-blocks-core": "^4.6.2"
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.6.0"
30
+ "wb-dev-build-settings": "^0.7.0"
30
31
  }
31
- }
32
+ }