@khanacademy/wonder-blocks-modal 2.3.5 → 2.3.6
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/es/index.js +18 -69
- package/dist/index.js +180 -164
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +208 -208
- package/src/components/__docs__/modal-dialog.stories.js +308 -0
- package/src/components/__docs__/modal-footer.stories.js +337 -0
- package/src/components/__docs__/modal-header.argtypes.js +76 -0
- package/src/components/__docs__/modal-header.stories.js +294 -0
- package/src/components/__docs__/modal-launcher.argtypes.js +78 -0
- package/src/components/__docs__/modal-launcher.stories.js +512 -0
- package/src/components/__docs__/modal-panel.stories.js +414 -0
- package/src/components/__docs__/one-pane-dialog.argtypes.js +102 -0
- package/src/components/__docs__/one-pane-dialog.stories.js +582 -0
- package/src/components/__tests__/focus-trap.test.js +101 -0
- package/src/components/focus-trap.js +47 -98
- package/src/components/modal-footer.js +8 -0
- package/src/components/one-pane-dialog.js +26 -1
- package/src/components/one-pane-dialog.stories.js +0 -248
package/dist/index.js
CHANGED
|
@@ -82,7 +82,7 @@ module.exports =
|
|
|
82
82
|
/******/
|
|
83
83
|
/******/
|
|
84
84
|
/******/ // Load entry module and return exports
|
|
85
|
-
/******/ return __webpack_require__(__webpack_require__.s =
|
|
85
|
+
/******/ return __webpack_require__(__webpack_require__.s = 27);
|
|
86
86
|
/******/ })
|
|
87
87
|
/************************************************************************/
|
|
88
88
|
/******/ ([
|
|
@@ -305,6 +305,14 @@ const styleSheets = {
|
|
|
305
305
|
* If you are creating a custom Dialog, make sure to follow these guidelines:
|
|
306
306
|
* - Make sure to include it as part of [ModalPanel](/#modalpanel) by using the `footer` prop.
|
|
307
307
|
* - The footer is completely flexible. Meaning the developer needs to add its own custom layout to match design specs.
|
|
308
|
+
*
|
|
309
|
+
* **Usage**
|
|
310
|
+
*
|
|
311
|
+
* ```js
|
|
312
|
+
* <ModalFooter>
|
|
313
|
+
* <Button onClick={() => {}}>Submit</Button>
|
|
314
|
+
* </ModalFooter>
|
|
315
|
+
* ```
|
|
308
316
|
*/
|
|
309
317
|
class ModalFooter extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
310
318
|
static isClassOf(instance) {
|
|
@@ -479,7 +487,7 @@ const styleSheets = {
|
|
|
479
487
|
/* harmony import */ var _modal_content_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(14);
|
|
480
488
|
/* harmony import */ var _modal_header_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(7);
|
|
481
489
|
/* harmony import */ var _modal_footer_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8);
|
|
482
|
-
/* harmony import */ var _close_button_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(
|
|
490
|
+
/* harmony import */ var _close_button_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(24);
|
|
483
491
|
|
|
484
492
|
|
|
485
493
|
|
|
@@ -714,7 +722,7 @@ const styleSheets = {
|
|
|
714
722
|
/* harmony import */ var _khanacademy_wonder_blocks_timing__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_timing__WEBPACK_IMPORTED_MODULE_3__);
|
|
715
723
|
/* harmony import */ var _focus_trap_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(19);
|
|
716
724
|
/* harmony import */ var _modal_backdrop_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(20);
|
|
717
|
-
/* harmony import */ var _scroll_disabler_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(
|
|
725
|
+
/* harmony import */ var _scroll_disabler_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(23);
|
|
718
726
|
/* harmony import */ var _modal_context_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(12);
|
|
719
727
|
|
|
720
728
|
|
|
@@ -965,7 +973,32 @@ const styles = aphrodite__WEBPACK_IMPORTED_MODULE_2__["StyleSheet"].create({
|
|
|
965
973
|
* This is the standard layout for most straightforward modal experiences.
|
|
966
974
|
*
|
|
967
975
|
* The ModalHeader is required, but the ModalFooter is optional.
|
|
968
|
-
* The content of the dialog itself is fully customizable, but the
|
|
976
|
+
* The content of the dialog itself is fully customizable, but the
|
|
977
|
+
* left/right/top/bottom padding is fixed.
|
|
978
|
+
*
|
|
979
|
+
* ### Usage
|
|
980
|
+
*
|
|
981
|
+
* ```jsx
|
|
982
|
+
* import {OnePaneDialog} from "@khanacademy/wonder-blocks-modal";
|
|
983
|
+
* import {Body} from "@khanacademy/wonder-blocks-typography";
|
|
984
|
+
*
|
|
985
|
+
* <OnePaneDialog
|
|
986
|
+
* title="Some title"
|
|
987
|
+
* content={
|
|
988
|
+
* <Body>
|
|
989
|
+
* {`Lorem ipsum dolor sit amet, consectetur adipiscing
|
|
990
|
+
* elit, sed do eiusmod tempor incididunt ut labore et
|
|
991
|
+
* dolore magna aliqua. Ut enim ad minim veniam,
|
|
992
|
+
* quis nostrud exercitation ullamco laboris nisi ut
|
|
993
|
+
* aliquip ex ea commodo consequat. Duis aute irure
|
|
994
|
+
* dolor in reprehenderit in voluptate velit esse
|
|
995
|
+
* cillum dolore eu fugiat nulla pariatur. Excepteur
|
|
996
|
+
* sint occaecat cupidatat non proident, sunt in culpa
|
|
997
|
+
* qui officia deserunt mollit anim id est.`}
|
|
998
|
+
* </Body>
|
|
999
|
+
* }
|
|
1000
|
+
* />
|
|
1001
|
+
* ```
|
|
969
1002
|
*/
|
|
970
1003
|
class OnePaneDialog extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
971
1004
|
renderHeader(uniqueId) {
|
|
@@ -1347,7 +1380,7 @@ function (modules) {
|
|
|
1347
1380
|
|
|
1348
1381
|
/******/
|
|
1349
1382
|
|
|
1350
|
-
return __webpack_require__(__webpack_require__.s =
|
|
1383
|
+
return __webpack_require__(__webpack_require__.s = 14);
|
|
1351
1384
|
/******/
|
|
1352
1385
|
}
|
|
1353
1386
|
/************************************************************************/
|
|
@@ -1551,33 +1584,23 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
1551
1584
|
/* harmony import */
|
|
1552
1585
|
|
|
1553
1586
|
|
|
1554
|
-
var
|
|
1587
|
+
var _babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8);
|
|
1555
1588
|
/* harmony import */
|
|
1556
1589
|
|
|
1557
1590
|
|
|
1558
|
-
var
|
|
1591
|
+
var _babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_0__);
|
|
1559
1592
|
/* harmony import */
|
|
1560
1593
|
|
|
1561
1594
|
|
|
1562
|
-
var
|
|
1595
|
+
var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1);
|
|
1596
|
+
/* harmony import */
|
|
1563
1597
|
|
|
1564
|
-
function _extends() {
|
|
1565
|
-
_extends = Object.assign || function (target) {
|
|
1566
|
-
for (var i = 1; i < arguments.length; i++) {
|
|
1567
|
-
var source = arguments[i];
|
|
1568
1598
|
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
target[key] = source[key];
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1599
|
+
var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
|
|
1600
|
+
/* harmony import */
|
|
1575
1601
|
|
|
1576
|
-
return target;
|
|
1577
|
-
};
|
|
1578
1602
|
|
|
1579
|
-
|
|
1580
|
-
}
|
|
1603
|
+
var _action_scheduler_provider_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
|
|
1581
1604
|
/**
|
|
1582
1605
|
* A higher order component that attaches the given component to an
|
|
1583
1606
|
* `IScheduleActions` instance. Any actions scheduled will automatically be
|
|
@@ -1590,9 +1613,9 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
1590
1613
|
|
|
1591
1614
|
|
|
1592
1615
|
function withActionScheduler(WrappedComponent) {
|
|
1593
|
-
return /*#__PURE__*/
|
|
1616
|
+
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["forwardRef"]((props, ref) => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_action_scheduler_provider_js__WEBPACK_IMPORTED_MODULE_2__[
|
|
1594
1617
|
/* default */
|
|
1595
|
-
"a"], null, schedule => /*#__PURE__*/
|
|
1618
|
+
"a"], null, schedule => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](WrappedComponent, _babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_0___default()({}, props, {
|
|
1596
1619
|
ref: ref,
|
|
1597
1620
|
schedule: schedule
|
|
1598
1621
|
}))));
|
|
@@ -1804,6 +1827,30 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
1804
1827
|
},
|
|
1805
1828
|
/* 8 */
|
|
1806
1829
|
|
|
1830
|
+
/***/
|
|
1831
|
+
function (module, exports) {
|
|
1832
|
+
function _extends() {
|
|
1833
|
+
module.exports = _extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
1834
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
1835
|
+
var source = arguments[i];
|
|
1836
|
+
|
|
1837
|
+
for (var key in source) {
|
|
1838
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
1839
|
+
target[key] = source[key];
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
return target;
|
|
1845
|
+
}, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
1846
|
+
return _extends.apply(this, arguments);
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
module.exports = _extends, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
1850
|
+
/***/
|
|
1851
|
+
},
|
|
1852
|
+
/* 9 */
|
|
1853
|
+
|
|
1807
1854
|
/***/
|
|
1808
1855
|
function (module, __webpack_exports__, __webpack_require__) {
|
|
1809
1856
|
"use strict";
|
|
@@ -1823,7 +1870,7 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
1823
1870
|
/* harmony import */
|
|
1824
1871
|
|
|
1825
1872
|
|
|
1826
|
-
var _util_action_scheduler_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
|
|
1873
|
+
var _util_action_scheduler_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(10);
|
|
1827
1874
|
/**
|
|
1828
1875
|
* A provider component that passes our action scheduling API to its children
|
|
1829
1876
|
* and ensures that all scheduled actions are cleared on unmount.
|
|
@@ -1859,7 +1906,7 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
1859
1906
|
/***/
|
|
1860
1907
|
|
|
1861
1908
|
},
|
|
1862
|
-
/*
|
|
1909
|
+
/* 10 */
|
|
1863
1910
|
|
|
1864
1911
|
/***/
|
|
1865
1912
|
function (module, __webpack_exports__, __webpack_require__) {
|
|
@@ -1872,15 +1919,15 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
1872
1919
|
/* harmony import */
|
|
1873
1920
|
|
|
1874
1921
|
|
|
1875
|
-
var _timeout_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
1922
|
+
var _timeout_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(11);
|
|
1876
1923
|
/* harmony import */
|
|
1877
1924
|
|
|
1878
1925
|
|
|
1879
|
-
var _interval_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
|
|
1926
|
+
var _interval_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(12);
|
|
1880
1927
|
/* harmony import */
|
|
1881
1928
|
|
|
1882
1929
|
|
|
1883
|
-
var _animation_frame_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(
|
|
1930
|
+
var _animation_frame_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(13);
|
|
1884
1931
|
/**
|
|
1885
1932
|
* Implements the `IScheduleActions` API to provide timeout, interval, and
|
|
1886
1933
|
* animation frame support. This is not intended for direct use, but instead
|
|
@@ -1966,7 +2013,7 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
1966
2013
|
};
|
|
1967
2014
|
/***/
|
|
1968
2015
|
},
|
|
1969
|
-
/*
|
|
2016
|
+
/* 11 */
|
|
1970
2017
|
|
|
1971
2018
|
/***/
|
|
1972
2019
|
function (module, __webpack_exports__, __webpack_require__) {
|
|
@@ -2098,7 +2145,7 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
2098
2145
|
/***/
|
|
2099
2146
|
|
|
2100
2147
|
},
|
|
2101
|
-
/*
|
|
2148
|
+
/* 12 */
|
|
2102
2149
|
|
|
2103
2150
|
/***/
|
|
2104
2151
|
function (module, __webpack_exports__, __webpack_require__) {
|
|
@@ -2226,7 +2273,7 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
2226
2273
|
/***/
|
|
2227
2274
|
|
|
2228
2275
|
},
|
|
2229
|
-
/*
|
|
2276
|
+
/* 13 */
|
|
2230
2277
|
|
|
2231
2278
|
/***/
|
|
2232
2279
|
function (module, __webpack_exports__, __webpack_require__) {
|
|
@@ -2354,7 +2401,7 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
2354
2401
|
/***/
|
|
2355
2402
|
|
|
2356
2403
|
},
|
|
2357
|
-
/*
|
|
2404
|
+
/* 14 */
|
|
2358
2405
|
|
|
2359
2406
|
/***/
|
|
2360
2407
|
function (module, __webpack_exports__, __webpack_require__) {
|
|
@@ -2448,19 +2495,32 @@ function (module, __webpack_exports__, __webpack_require__) {
|
|
|
2448
2495
|
|
|
2449
2496
|
|
|
2450
2497
|
|
|
2451
|
-
class FocusTrap extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
2452
|
-
/** The most recent node _inside this component_ to receive focus. */
|
|
2453
2498
|
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2499
|
+
/**
|
|
2500
|
+
* List of elements that can be focused
|
|
2501
|
+
* @see https://www.w3.org/TR/html5/editing.html#can-be-focused
|
|
2502
|
+
*/
|
|
2503
|
+
const FOCUSABLE_ELEMENTS = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
2504
|
+
/**
|
|
2505
|
+
* This component ensures that focus stays within itself. If the user uses Tab
|
|
2506
|
+
* at the end of the modal, or Shift-Tab at the start of the modal, then this
|
|
2507
|
+
* component wraps focus to the start/end respectively.
|
|
2508
|
+
*
|
|
2509
|
+
* We use this in `ModalBackdrop` to ensure that focus stays within the launched
|
|
2510
|
+
* modal.
|
|
2511
|
+
*
|
|
2512
|
+
* Adapted from the WAI-ARIA dialog behavior example.
|
|
2513
|
+
* https://www.w3.org/TR/2017/NOTE-wai-aria-practices-1.1-20171214/examples/dialog-modal/dialog.html
|
|
2514
|
+
*
|
|
2515
|
+
* NOTE(mdr): This component frequently references the "modal" and the "modal
|
|
2516
|
+
* root", to aid readability in this package. But this component isn't
|
|
2517
|
+
* actually coupled to the modal, and these could be renamed "children"
|
|
2518
|
+
* instead if we were to generalize!
|
|
2519
|
+
*/
|
|
2458
2520
|
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
constructor(props) {
|
|
2463
|
-
super(props);
|
|
2521
|
+
class FocusTrap extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
2522
|
+
constructor(...args) {
|
|
2523
|
+
super(...args);
|
|
2464
2524
|
|
|
2465
2525
|
this.getModalRoot = node => {
|
|
2466
2526
|
if (!node) {
|
|
@@ -2477,128 +2537,58 @@ class FocusTrap extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
|
2477
2537
|
this.modalRoot = modalRoot;
|
|
2478
2538
|
};
|
|
2479
2539
|
|
|
2480
|
-
this.
|
|
2481
|
-
|
|
2482
|
-
// changes, to avoid an infinite loop.
|
|
2483
|
-
if (this.ignoreFocusChanges) {
|
|
2484
|
-
return;
|
|
2485
|
-
}
|
|
2486
|
-
|
|
2487
|
-
const target = e.target;
|
|
2488
|
-
|
|
2489
|
-
if (!(target instanceof Node)) {
|
|
2490
|
-
// Sometimes focus events trigger on the document itself. Ignore!
|
|
2491
|
-
return;
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
const modalRoot = this.modalRoot;
|
|
2495
|
-
|
|
2496
|
-
if (!modalRoot) {
|
|
2497
|
-
return;
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
|
-
if (modalRoot.contains(target)) {
|
|
2501
|
-
// If the newly focused node is inside the modal, we just keep track
|
|
2502
|
-
// of that.
|
|
2503
|
-
this.lastNodeFocusedInModal = target;
|
|
2504
|
-
} else {
|
|
2505
|
-
// If the newly focused node is outside the modal, we try refocusing
|
|
2506
|
-
// the first focusable node of the modal. (This could be the user
|
|
2507
|
-
// pressing Tab on the last node of the modal, or focus escaping in
|
|
2508
|
-
// some other way.)
|
|
2509
|
-
this.focusFirstElementIn(modalRoot); // But, if it turns out that the first focusable node of the modal
|
|
2510
|
-
// was what we were previously focusing, then this is probably the
|
|
2511
|
-
// user pressing Shift-Tab on the first node, wanting to go to the
|
|
2512
|
-
// end. So, we instead try focusing the last focusable node of the
|
|
2513
|
-
// modal.
|
|
2514
|
-
|
|
2515
|
-
if (document.activeElement === this.lastNodeFocusedInModal) {
|
|
2516
|
-
this.focusLastElementIn(modalRoot);
|
|
2517
|
-
} // Focus should now be inside the modal, so record the newly-focused
|
|
2518
|
-
// node as the last node focused in the modal.
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
this.lastNodeFocusedInModal = document.activeElement;
|
|
2522
|
-
}
|
|
2540
|
+
this.handleFocusMoveToLast = () => {
|
|
2541
|
+
this.focusElementIn(false);
|
|
2523
2542
|
};
|
|
2524
2543
|
|
|
2525
|
-
this.
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
componentDidMount() {
|
|
2530
|
-
window.addEventListener("focus", this.handleGlobalFocus, true);
|
|
2531
|
-
}
|
|
2532
|
-
|
|
2533
|
-
componentWillUnmount() {
|
|
2534
|
-
window.removeEventListener("focus", this.handleGlobalFocus, true);
|
|
2544
|
+
this.handleFocusMoveToFirst = () => {
|
|
2545
|
+
this.focusElementIn(true);
|
|
2546
|
+
};
|
|
2535
2547
|
}
|
|
2536
2548
|
|
|
2537
|
-
/**
|
|
2549
|
+
/**
|
|
2550
|
+
* Try to focus the given node. Return true if successful.
|
|
2551
|
+
*/
|
|
2538
2552
|
tryToFocus(node) {
|
|
2539
2553
|
if (node instanceof HTMLElement) {
|
|
2540
|
-
this.ignoreFocusChanges = true;
|
|
2541
|
-
|
|
2542
2554
|
try {
|
|
2543
2555
|
node.focus();
|
|
2544
2556
|
} catch (e) {// ignore error
|
|
2545
2557
|
}
|
|
2546
2558
|
|
|
2547
|
-
this.ignoreFocusChanges = false;
|
|
2548
2559
|
return document.activeElement === node;
|
|
2549
2560
|
}
|
|
2550
2561
|
}
|
|
2551
2562
|
/**
|
|
2552
|
-
* Focus the
|
|
2563
|
+
* Focus the next available focusable element within the modal root.
|
|
2553
2564
|
*
|
|
2554
|
-
*
|
|
2555
|
-
*
|
|
2565
|
+
* @param {boolean} isLast Used to determine the next available item. true =
|
|
2566
|
+
* First element within the modal, false = Last element within the modal.
|
|
2556
2567
|
*/
|
|
2557
2568
|
|
|
2558
2569
|
|
|
2559
|
-
|
|
2560
|
-
const
|
|
2561
|
-
|
|
2562
|
-
for (let i = 0; i < children.length; i++) {
|
|
2563
|
-
const child = children[i];
|
|
2570
|
+
focusElementIn(isLast) {
|
|
2571
|
+
const modalRootAsHtmlEl = this.modalRoot; // Get the list of available focusable elements within the modal.
|
|
2564
2572
|
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
return false;
|
|
2573
|
+
const focusableNodes = Array.from(modalRootAsHtmlEl.querySelectorAll(FOCUSABLE_ELEMENTS));
|
|
2574
|
+
const nodeIndex = !isLast ? focusableNodes.length - 1 : 0;
|
|
2575
|
+
const focusableNode = focusableNodes[nodeIndex];
|
|
2576
|
+
this.tryToFocus(focusableNode);
|
|
2571
2577
|
}
|
|
2572
2578
|
/**
|
|
2573
|
-
*
|
|
2574
|
-
*
|
|
2575
|
-
* Return true if we succeed. Or, if the given node has no focusable
|
|
2576
|
-
* descendants, return false.
|
|
2579
|
+
* Triggered when the focus is set to the first sentinel. This way, the
|
|
2580
|
+
* focus will be redirected to the last element inside the modal dialog.
|
|
2577
2581
|
*/
|
|
2578
2582
|
|
|
2579
2583
|
|
|
2580
|
-
focusLastElementIn(currentParent) {
|
|
2581
|
-
const children = currentParent.childNodes;
|
|
2582
|
-
|
|
2583
|
-
for (let i = children.length - 1; i >= 0; i--) {
|
|
2584
|
-
const child = children[i];
|
|
2585
|
-
|
|
2586
|
-
if (this.tryToFocus(child) || this.focusLastElementIn(child)) {
|
|
2587
|
-
return true;
|
|
2588
|
-
}
|
|
2589
|
-
}
|
|
2590
|
-
|
|
2591
|
-
return false;
|
|
2592
|
-
}
|
|
2593
|
-
/** This method is called when any node on the page is focused. */
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
2584
|
render() {
|
|
2597
2585
|
const {
|
|
2598
2586
|
style
|
|
2599
2587
|
} = this.props;
|
|
2600
2588
|
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](react__WEBPACK_IMPORTED_MODULE_0__["Fragment"], null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"]("div", {
|
|
2601
2589
|
tabIndex: "0",
|
|
2590
|
+
className: "modal-focus-trap-first",
|
|
2591
|
+
onFocus: this.handleFocusMoveToLast,
|
|
2602
2592
|
style: {
|
|
2603
2593
|
position: "fixed"
|
|
2604
2594
|
}
|
|
@@ -2607,6 +2597,8 @@ class FocusTrap extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
|
2607
2597
|
ref: this.getModalRoot
|
|
2608
2598
|
}, this.props.children), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"]("div", {
|
|
2609
2599
|
tabIndex: "0",
|
|
2600
|
+
className: "modal-focus-trap-last",
|
|
2601
|
+
onFocus: this.handleFocusMoveToFirst,
|
|
2610
2602
|
style: {
|
|
2611
2603
|
position: "fixed"
|
|
2612
2604
|
}
|
|
@@ -2621,19 +2613,20 @@ class FocusTrap extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
|
2621
2613
|
|
|
2622
2614
|
"use strict";
|
|
2623
2615
|
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return ModalBackdrop; });
|
|
2624
|
-
/* harmony import */ var
|
|
2625
|
-
/* harmony import */ var
|
|
2626
|
-
/* harmony import */ var
|
|
2627
|
-
/* harmony import */ var
|
|
2628
|
-
/* harmony import */ var
|
|
2629
|
-
/* harmony import */ var
|
|
2630
|
-
/* harmony import */ var
|
|
2631
|
-
/* harmony import */ var
|
|
2632
|
-
/* harmony import */ var
|
|
2633
|
-
/* harmony import */ var
|
|
2634
|
-
/* harmony import */ var
|
|
2635
|
-
/* harmony import */ var
|
|
2636
|
-
|
|
2616
|
+
/* harmony import */ var _babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(21);
|
|
2617
|
+
/* harmony import */ var _babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_0__);
|
|
2618
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(0);
|
|
2619
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
|
|
2620
|
+
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6);
|
|
2621
|
+
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_2__);
|
|
2622
|
+
/* harmony import */ var aphrodite__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3);
|
|
2623
|
+
/* harmony import */ var aphrodite__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(aphrodite__WEBPACK_IMPORTED_MODULE_3__);
|
|
2624
|
+
/* harmony import */ var _khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(4);
|
|
2625
|
+
/* harmony import */ var _khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_4__);
|
|
2626
|
+
/* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(2);
|
|
2627
|
+
/* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_5__);
|
|
2628
|
+
/* harmony import */ var _util_constants_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(11);
|
|
2629
|
+
/* harmony import */ var _util_find_focusable_nodes_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(22);
|
|
2637
2630
|
|
|
2638
2631
|
|
|
2639
2632
|
|
|
@@ -2653,7 +2646,7 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
|
|
|
2653
2646
|
* and adding an `onClose` prop that will call `onCloseModal`. If an
|
|
2654
2647
|
* `onClose` prop is already provided, the two are merged.
|
|
2655
2648
|
*/
|
|
2656
|
-
class ModalBackdrop extends
|
|
2649
|
+
class ModalBackdrop extends react__WEBPACK_IMPORTED_MODULE_1__["Component"] {
|
|
2657
2650
|
constructor(...args) {
|
|
2658
2651
|
super(...args);
|
|
2659
2652
|
this._mousePressedOutside = false;
|
|
@@ -2675,7 +2668,7 @@ class ModalBackdrop extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
|
2675
2668
|
}
|
|
2676
2669
|
|
|
2677
2670
|
componentDidMount() {
|
|
2678
|
-
const node =
|
|
2671
|
+
const node = react_dom__WEBPACK_IMPORTED_MODULE_2__["findDOMNode"](this);
|
|
2679
2672
|
|
|
2680
2673
|
if (!node) {
|
|
2681
2674
|
return;
|
|
@@ -2704,7 +2697,7 @@ class ModalBackdrop extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
|
2704
2697
|
return null;
|
|
2705
2698
|
}
|
|
2706
2699
|
|
|
2707
|
-
return
|
|
2700
|
+
return react_dom__WEBPACK_IMPORTED_MODULE_2__["findDOMNode"](node.querySelector(`#${initialFocusId}`));
|
|
2708
2701
|
}
|
|
2709
2702
|
/**
|
|
2710
2703
|
* Returns the first focusable element found inside the Dialog
|
|
@@ -2713,7 +2706,7 @@ class ModalBackdrop extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
|
2713
2706
|
|
|
2714
2707
|
_getFirstFocusableElement(node) {
|
|
2715
2708
|
// get a collection of elements that can be focused
|
|
2716
|
-
const focusableElements = Object(
|
|
2709
|
+
const focusableElements = Object(_util_find_focusable_nodes_js__WEBPACK_IMPORTED_MODULE_7__[/* findFocusableNodes */ "a"])(node);
|
|
2717
2710
|
|
|
2718
2711
|
if (!focusableElements) {
|
|
2719
2712
|
return null;
|
|
@@ -2730,7 +2723,7 @@ class ModalBackdrop extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
|
2730
2723
|
_getDialogElement(node) {
|
|
2731
2724
|
// If no focusable elements are found,
|
|
2732
2725
|
// the dialog content element itself will receive focus.
|
|
2733
|
-
const dialogElement =
|
|
2726
|
+
const dialogElement = react_dom__WEBPACK_IMPORTED_MODULE_2__["findDOMNode"](node.querySelector('[role="dialog"]')); // add tabIndex to make the Dialog focusable
|
|
2734
2727
|
|
|
2735
2728
|
dialogElement.tabIndex = -1;
|
|
2736
2729
|
return dialogElement;
|
|
@@ -2748,9 +2741,9 @@ class ModalBackdrop extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
|
2748
2741
|
testId
|
|
2749
2742
|
} = this.props;
|
|
2750
2743
|
const backdropProps = {
|
|
2751
|
-
[
|
|
2744
|
+
[_util_constants_js__WEBPACK_IMPORTED_MODULE_6__[/* ModalLauncherPortalAttributeName */ "a"]]: true
|
|
2752
2745
|
};
|
|
2753
|
-
return /*#__PURE__*/
|
|
2746
|
+
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_5__["View"], _babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_0___default()({
|
|
2754
2747
|
style: styles.modalPositioner,
|
|
2755
2748
|
onMouseDown: this.handleMouseDown,
|
|
2756
2749
|
onMouseUp: this.handleMouseUp,
|
|
@@ -2759,7 +2752,7 @@ class ModalBackdrop extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
|
2759
2752
|
}
|
|
2760
2753
|
|
|
2761
2754
|
}
|
|
2762
|
-
const styles =
|
|
2755
|
+
const styles = aphrodite__WEBPACK_IMPORTED_MODULE_3__["StyleSheet"].create({
|
|
2763
2756
|
modalPositioner: {
|
|
2764
2757
|
position: "fixed",
|
|
2765
2758
|
left: 0,
|
|
@@ -2777,12 +2770,35 @@ const styles = aphrodite__WEBPACK_IMPORTED_MODULE_2__["StyleSheet"].create({
|
|
|
2777
2770
|
// turns out to be necessary. That sounds hard to do; punting for
|
|
2778
2771
|
// now!
|
|
2779
2772
|
overflow: "auto",
|
|
2780
|
-
background:
|
|
2773
|
+
background: _khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_4___default.a.offBlack64
|
|
2781
2774
|
}
|
|
2782
2775
|
});
|
|
2783
2776
|
|
|
2784
2777
|
/***/ }),
|
|
2785
2778
|
/* 21 */
|
|
2779
|
+
/***/ (function(module, exports) {
|
|
2780
|
+
|
|
2781
|
+
function _extends() {
|
|
2782
|
+
module.exports = _extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
2783
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
2784
|
+
var source = arguments[i];
|
|
2785
|
+
|
|
2786
|
+
for (var key in source) {
|
|
2787
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
2788
|
+
target[key] = source[key];
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
return target;
|
|
2794
|
+
}, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
2795
|
+
return _extends.apply(this, arguments);
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
module.exports = _extends, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
2799
|
+
|
|
2800
|
+
/***/ }),
|
|
2801
|
+
/* 22 */
|
|
2786
2802
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
2787
2803
|
|
|
2788
2804
|
"use strict";
|
|
@@ -2797,7 +2813,7 @@ function findFocusableNodes(root) {
|
|
|
2797
2813
|
}
|
|
2798
2814
|
|
|
2799
2815
|
/***/ }),
|
|
2800
|
-
/*
|
|
2816
|
+
/* 23 */
|
|
2801
2817
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
2802
2818
|
|
|
2803
2819
|
"use strict";
|
|
@@ -2894,16 +2910,16 @@ ScrollDisabler.numModalsOpened = 0;
|
|
|
2894
2910
|
/* harmony default export */ __webpack_exports__["a"] = (ScrollDisabler);
|
|
2895
2911
|
|
|
2896
2912
|
/***/ }),
|
|
2897
|
-
/*
|
|
2913
|
+
/* 24 */
|
|
2898
2914
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
2899
2915
|
|
|
2900
2916
|
"use strict";
|
|
2901
2917
|
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return CloseButton; });
|
|
2902
2918
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
|
|
2903
2919
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
2904
|
-
/* harmony import */ var _khanacademy_wonder_blocks_icon__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
|
|
2920
|
+
/* harmony import */ var _khanacademy_wonder_blocks_icon__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25);
|
|
2905
2921
|
/* harmony import */ var _khanacademy_wonder_blocks_icon__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_icon__WEBPACK_IMPORTED_MODULE_1__);
|
|
2906
|
-
/* harmony import */ var _khanacademy_wonder_blocks_icon_button__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(
|
|
2922
|
+
/* harmony import */ var _khanacademy_wonder_blocks_icon_button__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(26);
|
|
2907
2923
|
/* harmony import */ var _khanacademy_wonder_blocks_icon_button__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_icon_button__WEBPACK_IMPORTED_MODULE_2__);
|
|
2908
2924
|
/* harmony import */ var _modal_context_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(12);
|
|
2909
2925
|
|
|
@@ -2942,19 +2958,19 @@ class CloseButton extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
|
|
|
2942
2958
|
}
|
|
2943
2959
|
|
|
2944
2960
|
/***/ }),
|
|
2945
|
-
/*
|
|
2961
|
+
/* 25 */
|
|
2946
2962
|
/***/ (function(module, exports) {
|
|
2947
2963
|
|
|
2948
2964
|
module.exports = require("@khanacademy/wonder-blocks-icon");
|
|
2949
2965
|
|
|
2950
2966
|
/***/ }),
|
|
2951
|
-
/*
|
|
2967
|
+
/* 26 */
|
|
2952
2968
|
/***/ (function(module, exports) {
|
|
2953
2969
|
|
|
2954
2970
|
module.exports = require("@khanacademy/wonder-blocks-icon-button");
|
|
2955
2971
|
|
|
2956
2972
|
/***/ }),
|
|
2957
|
-
/*
|
|
2973
|
+
/* 27 */
|
|
2958
2974
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
2959
2975
|
|
|
2960
2976
|
"use strict";
|