@khanacademy/wonder-blocks-clickable 2.2.0 → 2.2.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 ADDED
@@ -0,0 +1,19 @@
1
+ # @khanacademy/wonder-blocks-clickable
2
+
3
+ ## 2.2.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 166ecc97: Use `aria-disabled` instead of disabled, fix focused + disabled styles.
8
+
9
+ ## 2.2.3
10
+
11
+ ### Patch Changes
12
+
13
+ - @khanacademy/wonder-blocks-core@4.2.1
14
+
15
+ ## 2.2.2
16
+
17
+ ### Patch Changes
18
+
19
+ - 901bfe82: Change disabled tabindex from -1 to 0
package/dist/es/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutPropertiesLoose';
2
2
  import _extends from '@babel/runtime/helpers/extends';
3
- import { Component, createElement } from 'react';
3
+ import * as React from 'react';
4
4
  import { StyleSheet } from 'aphrodite';
5
5
  import { withRouter, Link } from 'react-router-dom';
6
6
  import { __RouterContext } from 'react-router';
@@ -50,9 +50,9 @@ const disabledHandlers = {
50
50
  onTouchCancel: () => void 0,
51
51
  onKeyDown: () => void 0,
52
52
  onKeyUp: () => void 0,
53
- onFocus: () => void 0,
54
- onBlur: () => void 0,
55
- tabIndex: -1
53
+ // Clickable components should still be tabbable so they can
54
+ // be used as anchors.
55
+ tabIndex: 0
56
56
  };
57
57
  const keyCodes = {
58
58
  enter: 13,
@@ -143,11 +143,14 @@ const startState = {
143
143
  * See https://reacttraining.com/react-router/web/guides/basic-components.
144
144
  */
145
145
 
146
- class ClickableBehavior extends Component {
146
+ class ClickableBehavior extends React.Component {
147
147
  static getDerivedStateFromProps(props, state) {
148
- // If new props are disabled, reset the hovered/focused/pressed states
148
+ // If new props are disabled, reset the hovered/pressed states
149
149
  if (props.disabled) {
150
- return startState;
150
+ // Keep the focused state for enabling keyboard navigation.
151
+ return _extends({}, startState, {
152
+ focused: state.focused
153
+ });
151
154
  } else {
152
155
  // Cannot return undefined
153
156
  return null;
@@ -461,7 +464,11 @@ class ClickableBehavior extends Component {
461
464
  }
462
465
 
463
466
  render() {
464
- const childrenProps = this.props.disabled ? disabledHandlers : {
467
+ const childrenProps = this.props.disabled ? _extends({}, disabledHandlers, {
468
+ // Keep these handlers for keyboard accessibility.
469
+ onFocus: this.handleFocus,
470
+ onBlur: this.handleBlur
471
+ }) : {
465
472
  onClick: this.handleClick,
466
473
  onMouseEnter: this.handleMouseEnter,
467
474
  onMouseLeave: this.handleMouseLeave,
@@ -574,7 +581,7 @@ const StyledLink = addStyle(Link);
574
581
  * ```
575
582
  */
576
583
 
577
- class Clickable extends Component {
584
+ class Clickable extends React.Component {
578
585
  constructor(...args) {
579
586
  super(...args);
580
587
 
@@ -584,23 +591,23 @@ class Clickable extends Component {
584
591
  // needs it to refine this.props.href to a string.
585
592
 
586
593
  if (activeHref && useClient && this.props.href) {
587
- return /*#__PURE__*/createElement(StyledLink, _extends({}, commonProps, {
594
+ return /*#__PURE__*/React.createElement(StyledLink, _extends({}, commonProps, {
588
595
  to: this.props.href,
589
596
  role: this.props.role,
590
597
  target: this.props.target || undefined,
591
598
  "aria-disabled": this.props.disabled ? "true" : undefined
592
599
  }), this.props.children(clickableState));
593
600
  } else if (activeHref && !useClient) {
594
- return /*#__PURE__*/createElement(StyledAnchor, _extends({}, commonProps, {
601
+ return /*#__PURE__*/React.createElement(StyledAnchor, _extends({}, commonProps, {
595
602
  href: this.props.href,
596
603
  role: this.props.role,
597
604
  target: this.props.target || undefined,
598
605
  "aria-disabled": this.props.disabled ? "true" : undefined
599
606
  }), this.props.children(clickableState));
600
607
  } else {
601
- return /*#__PURE__*/createElement(StyledButton, _extends({}, commonProps, {
608
+ return /*#__PURE__*/React.createElement(StyledButton, _extends({}, commonProps, {
602
609
  type: "button",
603
- disabled: this.props.disabled
610
+ "aria-disabled": this.props.disabled
604
611
  }), this.props.children(clickableState));
605
612
  }
606
613
  };
@@ -630,7 +637,7 @@ class Clickable extends Component {
630
637
  const getStyle = state => [styles.reset, styles.link, !hideDefaultFocusRing && state.focused && (light ? styles.focusedLight : styles.focused), style];
631
638
 
632
639
  if (beforeNav) {
633
- return /*#__PURE__*/createElement(ClickableBehavior, {
640
+ return /*#__PURE__*/React.createElement(ClickableBehavior, {
634
641
  href: href,
635
642
  onClick: onClick,
636
643
  beforeNav: beforeNav,
@@ -643,7 +650,7 @@ class Clickable extends Component {
643
650
  style: getStyle(state)
644
651
  }, childrenProps)));
645
652
  } else {
646
- return /*#__PURE__*/createElement(ClickableBehavior, {
653
+ return /*#__PURE__*/React.createElement(ClickableBehavior, {
647
654
  href: href,
648
655
  onClick: onClick,
649
656
  safeWithNav: safeWithNav,
@@ -659,7 +666,7 @@ class Clickable extends Component {
659
666
  }
660
667
 
661
668
  render() {
662
- return /*#__PURE__*/createElement(__RouterContext.Consumer, null, router => this.renderClickableBehavior(router));
669
+ return /*#__PURE__*/React.createElement(__RouterContext.Consumer, null, router => this.renderClickableBehavior(router));
663
670
  }
664
671
 
665
672
  } // Source: https://gist.github.com/MoOx/9137295
@@ -709,5 +716,4 @@ const styles = StyleSheet.create({
709
716
  }
710
717
  });
711
718
 
712
- export default Clickable;
713
- export { ClickableBehavior, getClickableBehavior, isClientSideUrl };
719
+ export { ClickableBehavior, Clickable as default, getClickableBehavior, isClientSideUrl };
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 = 11);
85
+ /******/ return __webpack_require__(__webpack_require__.s = 10);
86
86
  /******/ })
87
87
  /************************************************************************/
88
88
  /******/ ([
@@ -164,9 +164,9 @@ const disabledHandlers = {
164
164
  onTouchCancel: () => void 0,
165
165
  onKeyDown: () => void 0,
166
166
  onKeyUp: () => void 0,
167
- onFocus: () => void 0,
168
- onBlur: () => void 0,
169
- tabIndex: -1
167
+ // Clickable components should still be tabbable so they can
168
+ // be used as anchors.
169
+ tabIndex: 0
170
170
  };
171
171
  const keyCodes = {
172
172
  enter: 13,
@@ -259,9 +259,12 @@ const startState = {
259
259
 
260
260
  class ClickableBehavior extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
261
261
  static getDerivedStateFromProps(props, state) {
262
- // If new props are disabled, reset the hovered/focused/pressed states
262
+ // If new props are disabled, reset the hovered/pressed states
263
263
  if (props.disabled) {
264
- return startState;
264
+ // Keep the focused state for enabling keyboard navigation.
265
+ return { ...startState,
266
+ focused: state.focused
267
+ };
265
268
  } else {
266
269
  // Cannot return undefined
267
270
  return null;
@@ -575,7 +578,11 @@ class ClickableBehavior extends react__WEBPACK_IMPORTED_MODULE_0__["Component"]
575
578
  }
576
579
 
577
580
  render() {
578
- const childrenProps = this.props.disabled ? disabledHandlers : {
581
+ const childrenProps = this.props.disabled ? { ...disabledHandlers,
582
+ // Keep these handlers for keyboard accessibility.
583
+ onFocus: this.handleFocus,
584
+ onBlur: this.handleBlur
585
+ } : {
579
586
  onClick: this.handleClick,
580
587
  onMouseEnter: this.handleMouseEnter,
581
588
  onMouseLeave: this.handleMouseLeave,
@@ -675,142 +682,459 @@ module.exports = require("@khanacademy/wonder-blocks-core");
675
682
 
676
683
  /***/ }),
677
684
  /* 6 */
678
- /***/ (function(module, __webpack_exports__, __webpack_require__) {
685
+ /***/ (function(module, exports) {
679
686
 
680
- "use strict";
681
- /* unused harmony export SemanticColor */
682
- /* unused harmony export fade */
683
- /* unused harmony export mix */
684
- /* harmony import */ var _babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(10);
685
- /* harmony import */ var _babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_0__);
687
+ module.exports =
688
+ /******/
689
+ function (modules) {
690
+ // webpackBootstrap
686
691
 
687
- /**
688
- * A color manipulation library useful for dynamically
689
- * mixing colors together.
690
- */
692
+ /******/
693
+ // The module cache
691
694
 
692
- const color6Regexp = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i;
693
- const color3Regexp = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i;
694
- const rgbaRegexp = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\s*\)$/i; // Parse a color in #abcdef, rgb(...), or rgba(...) form into an object
695
- // with r,g,b,a keys.
695
+ /******/
696
+ var installedModules = {};
697
+ /******/
696
698
 
697
- const parse = color => {
698
- if (typeof color !== "string") {
699
- throw new Error(`Failed to parse color: ${color}`);
700
- }
699
+ /******/
700
+ // The require function
701
701
 
702
- const color3Match = color.match(color3Regexp);
702
+ /******/
703
703
 
704
- if (color3Match) {
705
- return {
706
- r: parseInt(`${color3Match[1]}${color3Match[1]}`, 16),
707
- g: parseInt(`${color3Match[2]}${color3Match[2]}`, 16),
708
- b: parseInt(`${color3Match[3]}${color3Match[3]}`, 16),
709
- a: 1
710
- };
711
- }
704
+ function __webpack_require__(moduleId) {
705
+ /******/
712
706
 
713
- const color6Match = color.match(color6Regexp);
707
+ /******/
708
+ // Check if module is in cache
714
709
 
715
- if (color6Match) {
716
- return {
717
- r: parseInt(color6Match[1], 16),
718
- g: parseInt(color6Match[2], 16),
719
- b: parseInt(color6Match[3], 16),
720
- a: 1
721
- };
722
- }
710
+ /******/
711
+ if (installedModules[moduleId]) {
712
+ /******/
713
+ return installedModules[moduleId].exports;
714
+ /******/
715
+ }
716
+ /******/
717
+ // Create a new module (and put it into the cache)
723
718
 
724
- const rgbaMatch = color.match(rgbaRegexp);
719
+ /******/
720
+
721
+
722
+ var module = installedModules[moduleId] = {
723
+ /******/
724
+ i: moduleId,
725
+
726
+ /******/
727
+ l: false,
728
+
729
+ /******/
730
+ exports: {}
731
+ /******/
725
732
 
726
- if (rgbaMatch) {
727
- return {
728
- r: parseFloat(rgbaMatch[1]),
729
- g: parseFloat(rgbaMatch[2]),
730
- b: parseFloat(rgbaMatch[3]),
731
- a: rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1
732
733
  };
734
+ /******/
735
+
736
+ /******/
737
+ // Execute the module function
738
+
739
+ /******/
740
+
741
+ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
742
+ /******/
743
+
744
+ /******/
745
+ // Flag the module as loaded
746
+
747
+ /******/
748
+
749
+ module.l = true;
750
+ /******/
751
+
752
+ /******/
753
+ // Return the exports of the module
754
+
755
+ /******/
756
+
757
+ return module.exports;
758
+ /******/
733
759
  }
760
+ /******/
761
+
762
+ /******/
763
+
764
+ /******/
765
+ // expose the modules object (__webpack_modules__)
766
+
767
+ /******/
734
768
 
735
- throw new Error(`Failed to parse color: ${color}`);
736
- }; // Stringify the color in an `rgba()` or `#abcdef` format.
737
769
 
770
+ __webpack_require__.m = modules;
771
+ /******/
772
+
773
+ /******/
774
+ // expose the module cache
775
+
776
+ /******/
777
+
778
+ __webpack_require__.c = installedModules;
779
+ /******/
780
+
781
+ /******/
782
+ // define getter function for harmony exports
783
+
784
+ /******/
785
+
786
+ __webpack_require__.d = function (exports, name, getter) {
787
+ /******/
788
+ if (!__webpack_require__.o(exports, name)) {
789
+ /******/
790
+ Object.defineProperty(exports, name, {
791
+ enumerable: true,
792
+ get: getter
793
+ });
794
+ /******/
795
+ }
796
+ /******/
797
+
798
+ };
799
+ /******/
738
800
 
739
- const format = color => {
740
- const r = Math.round(color.r);
741
- const g = Math.round(color.g);
742
- const b = Math.round(color.b);
801
+ /******/
802
+ // define __esModule on exports
743
803
 
744
- if (color.a === 1) {
745
- const _s = c => {
746
- const asString = c.toString(16);
747
- return asString.length === 1 ? asString + asString : asString;
804
+ /******/
805
+
806
+
807
+ __webpack_require__.r = function (exports) {
808
+ /******/
809
+ if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
810
+ /******/
811
+ Object.defineProperty(exports, Symbol.toStringTag, {
812
+ value: 'Module'
813
+ });
814
+ /******/
815
+ }
816
+ /******/
817
+
818
+
819
+ Object.defineProperty(exports, '__esModule', {
820
+ value: true
821
+ });
822
+ /******/
823
+ };
824
+ /******/
825
+
826
+ /******/
827
+ // create a fake namespace object
828
+
829
+ /******/
830
+ // mode & 1: value is a module id, require it
831
+
832
+ /******/
833
+ // mode & 2: merge all properties of value into the ns
834
+
835
+ /******/
836
+ // mode & 4: return value when already ns object
837
+
838
+ /******/
839
+ // mode & 8|1: behave like require
840
+
841
+ /******/
842
+
843
+
844
+ __webpack_require__.t = function (value, mode) {
845
+ /******/
846
+ if (mode & 1) value = __webpack_require__(value);
847
+ /******/
848
+
849
+ if (mode & 8) return value;
850
+ /******/
851
+
852
+ if (mode & 4 && typeof value === 'object' && value && value.__esModule) return value;
853
+ /******/
854
+
855
+ var ns = Object.create(null);
856
+ /******/
857
+
858
+ __webpack_require__.r(ns);
859
+ /******/
860
+
861
+
862
+ Object.defineProperty(ns, 'default', {
863
+ enumerable: true,
864
+ value: value
865
+ });
866
+ /******/
867
+
868
+ if (mode & 2 && typeof value != 'string') for (var key in value) __webpack_require__.d(ns, key, function (key) {
869
+ return value[key];
870
+ }.bind(null, key));
871
+ /******/
872
+
873
+ return ns;
874
+ /******/
875
+ };
876
+ /******/
877
+
878
+ /******/
879
+ // getDefaultExport function for compatibility with non-harmony modules
880
+
881
+ /******/
882
+
883
+
884
+ __webpack_require__.n = function (module) {
885
+ /******/
886
+ var getter = module && module.__esModule ?
887
+ /******/
888
+ function getDefault() {
889
+ return module['default'];
890
+ } :
891
+ /******/
892
+ function getModuleExports() {
893
+ return module;
748
894
  };
895
+ /******/
749
896
 
750
- return `#${_s(r)}${_s(g)}${_s(b)}`;
751
- } else {
752
- return `rgba(${r},${g},${b},${color.a.toFixed(2)})`;
753
- }
754
- }; // Adjust the alpha value of a color.
897
+ __webpack_require__.d(getter, 'a', getter);
898
+ /******/
755
899
 
756
900
 
757
- const fade = (color, percentage) => {
758
- if (percentage < 0 || percentage > 1) {
759
- throw new Error("Percentage must be between 0 and 1");
760
- }
901
+ return getter;
902
+ /******/
903
+ };
904
+ /******/
905
+
906
+ /******/
907
+ // Object.prototype.hasOwnProperty.call
908
+
909
+ /******/
910
+
911
+
912
+ __webpack_require__.o = function (object, property) {
913
+ return Object.prototype.hasOwnProperty.call(object, property);
914
+ };
915
+ /******/
916
+
917
+ /******/
918
+ // __webpack_public_path__
919
+
920
+ /******/
921
+
922
+
923
+ __webpack_require__.p = "";
924
+ /******/
925
+
926
+ /******/
761
927
 
762
- const components = parse(color);
763
- return format(_babel_runtime_helpers_extends__WEBPACK_IMPORTED_MODULE_0___default()({}, components, {
764
- a: components.a * percentage
765
- }));
766
- }; // Mix a color into a background color, using the alpha channel of the base
767
- // color to determine the linear blend.
768
-
769
-
770
- const mix = (color, background) => {
771
- const colorObj = parse(color);
772
- const bgObj = parse(background);
773
- return format({
774
- r: colorObj.r * colorObj.a + bgObj.r * (1 - colorObj.a),
775
- g: colorObj.g * colorObj.a + bgObj.g * (1 - colorObj.a),
776
- b: colorObj.b * colorObj.a + bgObj.b * (1 - colorObj.a),
777
- a: bgObj.a
928
+ /******/
929
+ // Load entry module and return exports
930
+
931
+ /******/
932
+
933
+ return __webpack_require__(__webpack_require__.s = 1);
934
+ /******/
935
+ }
936
+ /************************************************************************/
937
+
938
+ /******/
939
+ ([
940
+ /* 0 */
941
+
942
+ /***/
943
+ function (module, __webpack_exports__, __webpack_require__) {
944
+ "use strict";
945
+ /* harmony export (binding) */
946
+
947
+ __webpack_require__.d(__webpack_exports__, "a", function () {
948
+ return fade;
778
949
  });
779
- };
950
+ /* harmony export (binding) */
780
951
 
781
- const offBlack = "#21242c";
782
- const white = "#ffffff";
783
- const Color = {
784
- // Product
785
- blue: "#1865f2",
786
- purple: "#9059ff",
787
- green: "#00a60e",
788
- gold: "#ffb100",
789
- red: "#d92916",
790
- // Neutral
791
- offBlack,
792
- offBlack64: fade(offBlack, 0.64),
793
- offBlack50: fade(offBlack, 0.5),
794
- offBlack32: fade(offBlack, 0.32),
795
- offBlack16: fade(offBlack, 0.16),
796
- offBlack8: fade(offBlack, 0.08),
797
- offWhite: "#f7f8fa",
798
- white,
799
- white64: fade(white, 0.64),
800
- white50: fade(white, 0.5),
801
- // Brand
802
- darkBlue: "#0a2a66",
803
- teal: "#14bf96",
804
- lightBlue: "#37c5fd",
805
- pink: "#fa50ae"
806
- };
807
- const SemanticColor = {
808
- controlDefault: Color.blue,
809
- controlDestructive: Color.red
810
- };
811
- /* harmony default export */ __webpack_exports__["a"] = (Color);
952
+
953
+ __webpack_require__.d(__webpack_exports__, "b", function () {
954
+ return mix;
955
+ });
956
+ /**
957
+ * A color manipulation library useful for dynamically
958
+ * mixing colors together.
959
+ */
960
+
961
+
962
+ const color6Regexp = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i;
963
+ const color3Regexp = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i;
964
+ const rgbaRegexp = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\s*\)$/i; // Parse a color in #abcdef, rgb(...), or rgba(...) form into an object
965
+ // with r,g,b,a keys.
966
+
967
+ const parse = color => {
968
+ if (typeof color !== "string") {
969
+ throw new Error(`Failed to parse color: ${color}`);
970
+ }
971
+
972
+ const color3Match = color.match(color3Regexp);
973
+
974
+ if (color3Match) {
975
+ return {
976
+ r: parseInt(`${color3Match[1]}${color3Match[1]}`, 16),
977
+ g: parseInt(`${color3Match[2]}${color3Match[2]}`, 16),
978
+ b: parseInt(`${color3Match[3]}${color3Match[3]}`, 16),
979
+ a: 1
980
+ };
981
+ }
982
+
983
+ const color6Match = color.match(color6Regexp);
984
+
985
+ if (color6Match) {
986
+ return {
987
+ r: parseInt(color6Match[1], 16),
988
+ g: parseInt(color6Match[2], 16),
989
+ b: parseInt(color6Match[3], 16),
990
+ a: 1
991
+ };
992
+ }
993
+
994
+ const rgbaMatch = color.match(rgbaRegexp);
995
+
996
+ if (rgbaMatch) {
997
+ return {
998
+ r: parseFloat(rgbaMatch[1]),
999
+ g: parseFloat(rgbaMatch[2]),
1000
+ b: parseFloat(rgbaMatch[3]),
1001
+ a: rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1
1002
+ };
1003
+ }
1004
+
1005
+ throw new Error(`Failed to parse color: ${color}`);
1006
+ }; // Stringify the color in an `rgba()` or `#abcdef` format.
1007
+
1008
+
1009
+ const format = color => {
1010
+ const r = Math.round(color.r);
1011
+ const g = Math.round(color.g);
1012
+ const b = Math.round(color.b);
1013
+
1014
+ if (color.a === 1) {
1015
+ const _s = c => {
1016
+ const asString = c.toString(16);
1017
+ return asString.length === 1 ? asString + asString : asString;
1018
+ };
1019
+
1020
+ return `#${_s(r)}${_s(g)}${_s(b)}`;
1021
+ } else {
1022
+ return `rgba(${r},${g},${b},${color.a.toFixed(2)})`;
1023
+ }
1024
+ }; // Adjust the alpha value of a color.
1025
+
1026
+
1027
+ const fade = (color, percentage) => {
1028
+ if (percentage < 0 || percentage > 1) {
1029
+ throw new Error("Percentage must be between 0 and 1");
1030
+ }
1031
+
1032
+ const components = parse(color);
1033
+ return format({ ...components,
1034
+ a: components.a * percentage
1035
+ });
1036
+ }; // Mix a color into a background color, using the alpha channel of the base
1037
+ // color to determine the linear blend.
1038
+
1039
+
1040
+ const mix = (color, background) => {
1041
+ const colorObj = parse(color);
1042
+ const bgObj = parse(background);
1043
+ return format({
1044
+ r: colorObj.r * colorObj.a + bgObj.r * (1 - colorObj.a),
1045
+ g: colorObj.g * colorObj.a + bgObj.g * (1 - colorObj.a),
1046
+ b: colorObj.b * colorObj.a + bgObj.b * (1 - colorObj.a),
1047
+ a: bgObj.a
1048
+ });
1049
+ };
1050
+ /***/
1051
+
1052
+ },
1053
+ /* 1 */
1054
+
1055
+ /***/
1056
+ function (module, __webpack_exports__, __webpack_require__) {
1057
+ "use strict";
1058
+
1059
+ __webpack_require__.r(__webpack_exports__);
1060
+ /* harmony export (binding) */
1061
+
1062
+
1063
+ __webpack_require__.d(__webpack_exports__, "default", function () {
1064
+ return Color;
1065
+ });
1066
+ /* harmony export (binding) */
1067
+
1068
+
1069
+ __webpack_require__.d(__webpack_exports__, "SemanticColor", function () {
1070
+ return SemanticColor;
1071
+ });
1072
+ /* harmony import */
1073
+
1074
+
1075
+ var _util_utils_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
1076
+ /* harmony reexport (safe) */
1077
+
1078
+
1079
+ __webpack_require__.d(__webpack_exports__, "mix", function () {
1080
+ return _util_utils_js__WEBPACK_IMPORTED_MODULE_0__["b"];
1081
+ });
1082
+ /* harmony reexport (safe) */
812
1083
 
813
1084
 
1085
+ __webpack_require__.d(__webpack_exports__, "fade", function () {
1086
+ return _util_utils_js__WEBPACK_IMPORTED_MODULE_0__["a"];
1087
+ });
1088
+
1089
+ const offBlack = "#21242c";
1090
+ const white = "#ffffff";
1091
+ const Color = {
1092
+ // Product
1093
+ blue: "#1865f2",
1094
+ purple: "#9059ff",
1095
+ green: "#00a60e",
1096
+ gold: "#ffb100",
1097
+ red: "#d92916",
1098
+ // Neutral
1099
+ offBlack,
1100
+ offBlack64: Object(_util_utils_js__WEBPACK_IMPORTED_MODULE_0__[
1101
+ /* fade */
1102
+ "a"])(offBlack, 0.64),
1103
+ offBlack50: Object(_util_utils_js__WEBPACK_IMPORTED_MODULE_0__[
1104
+ /* fade */
1105
+ "a"])(offBlack, 0.5),
1106
+ offBlack32: Object(_util_utils_js__WEBPACK_IMPORTED_MODULE_0__[
1107
+ /* fade */
1108
+ "a"])(offBlack, 0.32),
1109
+ offBlack16: Object(_util_utils_js__WEBPACK_IMPORTED_MODULE_0__[
1110
+ /* fade */
1111
+ "a"])(offBlack, 0.16),
1112
+ offBlack8: Object(_util_utils_js__WEBPACK_IMPORTED_MODULE_0__[
1113
+ /* fade */
1114
+ "a"])(offBlack, 0.08),
1115
+ offWhite: "#f7f8fa",
1116
+ white,
1117
+ white64: Object(_util_utils_js__WEBPACK_IMPORTED_MODULE_0__[
1118
+ /* fade */
1119
+ "a"])(white, 0.64),
1120
+ white50: Object(_util_utils_js__WEBPACK_IMPORTED_MODULE_0__[
1121
+ /* fade */
1122
+ "a"])(white, 0.5),
1123
+ // Brand
1124
+ darkBlue: "#0a2a66",
1125
+ teal: "#14bf96",
1126
+ lightBlue: "#37c5fd",
1127
+ pink: "#fa50ae"
1128
+ };
1129
+ const SemanticColor = {
1130
+ controlDefault: Color.blue,
1131
+ controlDestructive: Color.red
1132
+ };
1133
+ /***/
1134
+ }
1135
+ /******/
1136
+ ]);
1137
+
814
1138
  /***/ }),
815
1139
  /* 7 */
816
1140
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
@@ -828,6 +1152,7 @@ const SemanticColor = {
828
1152
  /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(5);
829
1153
  /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_4__);
830
1154
  /* harmony import */ var _khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(6);
1155
+ /* harmony import */ var _khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5__);
831
1156
  /* harmony import */ var _util_get_clickable_behavior_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3);
832
1157
  /* harmony import */ var _util_is_client_side_url_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(1);
833
1158
  function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
@@ -894,7 +1219,7 @@ class Clickable extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
894
1219
  } else {
895
1220
  return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](StyledButton, _extends({}, commonProps, {
896
1221
  type: "button",
897
- disabled: this.props.disabled
1222
+ "aria-disabled": this.props.disabled
898
1223
  }), this.props.children(clickableState));
899
1224
  }
900
1225
  };
@@ -996,10 +1321,10 @@ const styles = aphrodite__WEBPACK_IMPORTED_MODULE_1__["StyleSheet"].create({
996
1321
  cursor: "pointer"
997
1322
  },
998
1323
  focused: {
999
- outline: `solid 2px ${_khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5__[/* default */ "a"].blue}`
1324
+ outline: `solid 2px ${_khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5___default.a.blue}`
1000
1325
  },
1001
1326
  focusedLight: {
1002
- outline: `solid 2px ${_khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5__[/* default */ "a"].white}`
1327
+ outline: `solid 2px ${_khanacademy_wonder_blocks_color__WEBPACK_IMPORTED_MODULE_5___default.a.white}`
1003
1328
  }
1004
1329
  });
1005
1330
 
@@ -1017,32 +1342,6 @@ module.exports = require("react-router");
1017
1342
 
1018
1343
  /***/ }),
1019
1344
  /* 10 */
1020
- /***/ (function(module, exports) {
1021
-
1022
- function _extends() {
1023
- module.exports = _extends = Object.assign || function (target) {
1024
- for (var i = 1; i < arguments.length; i++) {
1025
- var source = arguments[i];
1026
-
1027
- for (var key in source) {
1028
- if (Object.prototype.hasOwnProperty.call(source, key)) {
1029
- target[key] = source[key];
1030
- }
1031
- }
1032
- }
1033
-
1034
- return target;
1035
- };
1036
-
1037
- module.exports["default"] = module.exports, module.exports.__esModule = true;
1038
- return _extends.apply(this, arguments);
1039
- }
1040
-
1041
- module.exports = _extends;
1042
- module.exports["default"] = module.exports, module.exports.__esModule = true;
1043
-
1044
- /***/ }),
1045
- /* 11 */
1046
1345
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1047
1346
 
1048
1347
  "use strict";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-clickable",
3
- "version": "2.2.0",
3
+ "version": "2.2.4",
4
4
  "design": "v1",
5
5
  "description": "Clickable component for Wonder-Blocks.",
6
6
  "main": "dist/index.js",
@@ -15,8 +15,8 @@
15
15
  "access": "public"
16
16
  },
17
17
  "dependencies": {
18
- "@babel/runtime": "^7.13.10",
19
- "@khanacademy/wonder-blocks-core": "^3.2.0"
18
+ "@babel/runtime": "^7.16.3",
19
+ "@khanacademy/wonder-blocks-core": "^4.2.1"
20
20
  },
21
21
  "peerDependencies": {
22
22
  "aphrodite": "^1.2.5",
@@ -26,7 +26,6 @@
26
26
  "react-router-dom": "5.3.0"
27
27
  },
28
28
  "devDependencies": {
29
- "wb-dev-build-settings": "^0.1.2"
30
- },
31
- "gitHead": "b6193f70c73e70fbaf76bc688dc69a47fb1d0ef3"
29
+ "wb-dev-build-settings": "^0.3.0"
30
+ }
32
31
  }
@@ -21,9 +21,9 @@ exports[`wonder-blocks-clickable example 1 1`] = `
21
21
  }
22
22
  >
23
23
  <button
24
+ aria-disabled={false}
24
25
  aria-label=""
25
26
  className=""
26
- disabled={false}
27
27
  onBlur={[Function]}
28
28
  onClick={[Function]}
29
29
  onDragStart={[Function]}
@@ -128,9 +128,9 @@ exports[`wonder-blocks-clickable example 2 1`] = `
128
128
  }
129
129
  >
130
130
  <button
131
+ aria-disabled={false}
131
132
  aria-label=""
132
133
  className=""
133
- disabled={false}
134
134
  onBlur={[Function]}
135
135
  onClick={[Function]}
136
136
  onDragStart={[Function]}
@@ -1,8 +1,10 @@
1
1
  /* eslint-disable max-lines */
2
2
  // @flow
3
3
  import * as React from "react";
4
+ import {render, screen} from "@testing-library/react";
4
5
  import {MemoryRouter, Switch, Route} from "react-router-dom";
5
6
  import {mount, shallow} from "enzyme";
7
+ import "jest-enzyme";
6
8
 
7
9
  import getClickableBehavior from "../../util/get-clickable-behavior.js";
8
10
  import ClickableBehavior from "../clickable-behavior.js";
@@ -22,8 +24,9 @@ const wait = (delay: number = 0) =>
22
24
  describe("ClickableBehavior", () => {
23
25
  beforeEach(() => {
24
26
  // Note: window.location.assign and window.open need mock functions in
25
- // the testing environment.
26
- window.location.assign = jest.fn();
27
+ // the testing environment
28
+ delete window.location;
29
+ window.location = {assign: jest.fn()};
27
30
  window.open = jest.fn();
28
31
  });
29
32
 
@@ -274,6 +277,46 @@ describe("ClickableBehavior", () => {
274
277
  expect(button.state("focused")).toEqual(false);
275
278
  });
276
279
 
280
+ test("tabIndex should be 0", () => {
281
+ // Arrange
282
+ // Act
283
+ render(
284
+ <ClickableBehavior disabled={false} onClick={(e) => {}}>
285
+ {(state, childrenProps) => {
286
+ return (
287
+ <button data-test-id="test-button-1" {...childrenProps}>
288
+ Label
289
+ </button>
290
+ );
291
+ }}
292
+ </ClickableBehavior>,
293
+ );
294
+
295
+ // Assert
296
+ const button = screen.getByTestId("test-button-1");
297
+ expect(button).toHaveAttribute("tabIndex", "0");
298
+ });
299
+
300
+ test("tabIndex should be 0 even for disabled components", () => {
301
+ // Arrange
302
+ // Act
303
+ render(
304
+ <ClickableBehavior disabled={true} onClick={(e) => {}}>
305
+ {(state, childrenProps) => {
306
+ return (
307
+ <button data-test-id="test-button-2" {...childrenProps}>
308
+ Label
309
+ </button>
310
+ );
311
+ }}
312
+ </ClickableBehavior>,
313
+ );
314
+
315
+ // Assert
316
+ const button = screen.getByTestId("test-button-2");
317
+ expect(button).toHaveAttribute("tabIndex", "0");
318
+ });
319
+
277
320
  it("does not change state if disabled", () => {
278
321
  const onClick = jest.fn();
279
322
  const button = shallow(
@@ -335,7 +378,7 @@ describe("ClickableBehavior", () => {
335
378
  expect(button.state("pressed")).toEqual(false);
336
379
 
337
380
  button.simulate("focus");
338
- expect(button.state("focused")).toEqual(false);
381
+ expect(button.state("focused")).toEqual(true);
339
382
 
340
383
  const anchor = shallow(
341
384
  <ClickableBehavior
@@ -421,7 +464,7 @@ describe("ClickableBehavior", () => {
421
464
 
422
465
  expect(button.state("hovered")).toEqual(false);
423
466
  expect(button.state("pressed")).toEqual(false);
424
- expect(button.state("focused")).toEqual(false);
467
+ expect(button.state("focused")).toEqual(true);
425
468
  });
426
469
 
427
470
  describe("full page load navigation", () => {
@@ -566,6 +609,7 @@ describe("ClickableBehavior", () => {
566
609
  const button = wrapper.find("#test-button").first();
567
610
  button.simulate("click", {
568
611
  preventDefault() {
612
+ // $FlowIgnore[object-this-reference]
569
613
  this.defaultPrevented = true;
570
614
  },
571
615
  });
@@ -1052,6 +1096,7 @@ describe("ClickableBehavior", () => {
1052
1096
  const button = wrapper.find("#test-button").first();
1053
1097
  button.simulate("click", {
1054
1098
  preventDefault() {
1099
+ // $FlowIgnore[object-this-reference]
1055
1100
  this.defaultPrevented = true;
1056
1101
  },
1057
1102
  });
@@ -2,6 +2,9 @@
2
2
  import * as React from "react";
3
3
  import {MemoryRouter, Route, Switch} from "react-router-dom";
4
4
  import {mount} from "enzyme";
5
+ import "jest-enzyme";
6
+ import {render, screen} from "@testing-library/react";
7
+ import userEvent from "@testing-library/user-event";
5
8
 
6
9
  import {View} from "@khanacademy/wonder-blocks-core";
7
10
  import Clickable from "../clickable.js";
@@ -13,6 +16,11 @@ const wait = (delay: number = 0) =>
13
16
  });
14
17
 
15
18
  describe("Clickable", () => {
19
+ beforeEach(() => {
20
+ delete window.location;
21
+ window.location = {assign: jest.fn()};
22
+ });
23
+
16
24
  test("client-side navigation", () => {
17
25
  // Arrange
18
26
  const wrapper = mount(
@@ -129,8 +137,6 @@ describe("Clickable", () => {
129
137
 
130
138
  test("should navigate to a specific link using the keyboard", () => {
131
139
  // Arrange
132
- window.location.assign = jest.fn();
133
-
134
140
  const wrapper = mount(
135
141
  <Clickable testId="button" href="/foo" skipClientNav={true}>
136
142
  {(eventState) => <h1>Click Me!</h1>}
@@ -276,7 +282,6 @@ describe("Clickable", () => {
276
282
 
277
283
  test("safeWithNav with skipClientNav=true waits for promise resolution", async () => {
278
284
  // Arrange
279
- jest.spyOn(window.location, "assign");
280
285
  const wrapper = mount(
281
286
  <MemoryRouter>
282
287
  <div>
@@ -309,7 +314,6 @@ describe("Clickable", () => {
309
314
 
310
315
  test("beforeNav resolution and safeWithNav with skipClientNav=true waits for promise resolution", async () => {
311
316
  // Arrange
312
- jest.spyOn(window.location, "assign");
313
317
  const wrapper = mount(
314
318
  <MemoryRouter>
315
319
  <div>
@@ -336,8 +340,6 @@ describe("Clickable", () => {
336
340
  buttonWrapper.simulate("click", {button: 0});
337
341
  await wait(0);
338
342
  buttonWrapper.update();
339
- await wait(0);
340
- buttonWrapper.update();
341
343
 
342
344
  // Assert
343
345
  expect(window.location.assign).toHaveBeenCalledWith("/foo");
@@ -345,7 +347,6 @@ describe("Clickable", () => {
345
347
 
346
348
  test("safeWithNav with skipClientNav=true waits for promise rejection", async () => {
347
349
  // Arrange
348
- jest.spyOn(window.location, "assign");
349
350
  const wrapper = mount(
350
351
  <MemoryRouter>
351
352
  <div>
@@ -376,9 +377,8 @@ describe("Clickable", () => {
376
377
  expect(window.location.assign).toHaveBeenCalledWith("/foo");
377
378
  });
378
379
 
379
- test("safeWithNav with skipClientNav=false calls safeWithNav but doesn't wait to navigate", async () => {
380
+ test("safeWithNav with skipClientNav=false calls safeWithNav but doesn't wait to navigate", () => {
380
381
  // Arrange
381
- jest.spyOn(window.location, "assign");
382
382
  const safeWithNavMock = jest.fn();
383
383
  const wrapper = mount(
384
384
  <MemoryRouter>
@@ -406,12 +406,16 @@ describe("Clickable", () => {
406
406
 
407
407
  // Assert
408
408
  expect(safeWithNavMock).toHaveBeenCalled();
409
- expect(window.location.assign).toHaveBeenCalledWith("/foo");
409
+ expect(wrapper).toIncludeText(
410
+ "Hello, world!" /*client side nav to /foo*/,
411
+ );
412
+ expect(window.location.assign).not.toHaveBeenCalledWith(
413
+ "/foo" /*not a full page nav*/,
414
+ );
410
415
  });
411
416
 
412
417
  test("safeWithNav with beforeNav resolution and skipClientNav=false calls safeWithNav but doesn't wait to navigate", async () => {
413
418
  // Arrange
414
- jest.spyOn(window.location, "assign");
415
419
  const safeWithNavMock = jest.fn();
416
420
  const wrapper = mount(
417
421
  <MemoryRouter>
@@ -442,7 +446,53 @@ describe("Clickable", () => {
442
446
 
443
447
  // Assert
444
448
  expect(safeWithNavMock).toHaveBeenCalled();
445
- expect(window.location.assign).toHaveBeenCalledWith("/foo");
449
+ expect(wrapper).toIncludeText(
450
+ "Hello, world!" /*client side nav to /foo*/,
451
+ );
452
+ expect(window.location.assign).not.toHaveBeenCalledWith(
453
+ "/foo" /*not a full page nav*/,
454
+ );
455
+ });
456
+
457
+ test("should add aria-disabled if disabled is set", () => {
458
+ // Arrange
459
+
460
+ // Act
461
+ render(
462
+ <Clickable testId="clickable-button" disabled={true}>
463
+ {(eventState) => <h1>Click Me!</h1>}
464
+ </Clickable>,
465
+ );
466
+
467
+ const button = screen.getByTestId("clickable-button");
468
+
469
+ // Assert
470
+ expect(button).toHaveAttribute("aria-disabled", "true");
471
+ });
472
+
473
+ test("allow keyboard navigation when disabled is set", () => {
474
+ // Arrange
475
+ render(
476
+ <div>
477
+ <button>First focusable button</button>
478
+ <Clickable testId="clickable-button" disabled={true}>
479
+ {(eventState) => <h1>Click Me!</h1>}
480
+ </Clickable>
481
+ </div>,
482
+ );
483
+
484
+ // Act
485
+ // RTL's focuses on `document.body` by default, so we need to focus on
486
+ // the first button
487
+ userEvent.tab();
488
+
489
+ // Then we focus on our Clickable button.
490
+ userEvent.tab();
491
+
492
+ const button = screen.getByTestId("clickable-button");
493
+
494
+ // Assert
495
+ expect(button).toHaveFocus();
446
496
  });
447
497
 
448
498
  describe("raw events", () => {
@@ -221,9 +221,9 @@ const disabledHandlers = {
221
221
  onTouchCancel: () => void 0,
222
222
  onKeyDown: () => void 0,
223
223
  onKeyUp: () => void 0,
224
- onFocus: () => void 0,
225
- onBlur: () => void 0,
226
- tabIndex: -1,
224
+ // Clickable components should still be tabbable so they can
225
+ // be used as anchors.
226
+ tabIndex: 0,
227
227
  };
228
228
 
229
229
  const keyCodes = {
@@ -332,9 +332,10 @@ export default class ClickableBehavior extends React.Component<
332
332
  props: Props,
333
333
  state: ClickableState,
334
334
  ): ?Partial<ClickableState> {
335
- // If new props are disabled, reset the hovered/focused/pressed states
335
+ // If new props are disabled, reset the hovered/pressed states
336
336
  if (props.disabled) {
337
- return startState;
337
+ // Keep the focused state for enabling keyboard navigation.
338
+ return {...startState, focused: state.focused};
338
339
  } else {
339
340
  // Cannot return undefined
340
341
  return null;
@@ -558,9 +559,8 @@ export default class ClickableBehavior extends React.Component<
558
559
  }
559
560
 
560
561
  const keyCode = e.which || e.keyCode;
561
- const {triggerOnEnter, triggerOnSpace} = getAppropriateTriggersForRole(
562
- role,
563
- );
562
+ const {triggerOnEnter, triggerOnSpace} =
563
+ getAppropriateTriggersForRole(role);
564
564
  if (
565
565
  (triggerOnEnter && keyCode === keyCodes.enter) ||
566
566
  (triggerOnSpace && keyCode === keyCodes.space)
@@ -585,9 +585,8 @@ export default class ClickableBehavior extends React.Component<
585
585
  }
586
586
 
587
587
  const keyCode = e.which || e.keyCode;
588
- const {triggerOnEnter, triggerOnSpace} = getAppropriateTriggersForRole(
589
- role,
590
- );
588
+ const {triggerOnEnter, triggerOnSpace} =
589
+ getAppropriateTriggersForRole(role);
591
590
  if (
592
591
  (triggerOnEnter && keyCode === keyCodes.enter) ||
593
592
  (triggerOnSpace && keyCode === keyCodes.space)
@@ -610,7 +609,12 @@ export default class ClickableBehavior extends React.Component<
610
609
 
611
610
  render(): React.Node {
612
611
  const childrenProps: ChildrenProps = this.props.disabled
613
- ? disabledHandlers
612
+ ? {
613
+ ...disabledHandlers,
614
+ // Keep these handlers for keyboard accessibility.
615
+ onFocus: this.handleFocus,
616
+ onBlur: this.handleBlur,
617
+ }
614
618
  : {
615
619
  onClick: this.handleClick,
616
620
  onMouseEnter: this.handleMouseEnter,
@@ -252,7 +252,7 @@ export default class Clickable extends React.Component<Props> {
252
252
  <StyledButton
253
253
  {...commonProps}
254
254
  type="button"
255
- disabled={this.props.disabled}
255
+ aria-disabled={this.props.disabled}
256
256
  >
257
257
  {this.props.children(clickableState)}
258
258
  </StyledButton>
@@ -145,6 +145,13 @@ const styles = StyleSheet.create({
145
145
  </MemoryRouter>
146
146
  ```
147
147
 
148
+ ### Running callbacks on navigation
149
+
150
+ When using the `href` prop, the `onClick`, `beforeNav`, and `safeWithNav` props
151
+ can be used to run callbacks when navigating to the new URL. Which prop to use
152
+ depends on the use case. See the [Button](#section-button) documentation for
153
+ details.
154
+
148
155
  ### Navigating with the Keyboard
149
156
 
150
157
  Clickable adds support to keyboard navigation. This way, your components are
@@ -15,7 +15,7 @@ export default {
15
15
  title: "Navigation/Clickable",
16
16
  };
17
17
 
18
- export const basic: StoryComponentType = () => (
18
+ export const Basic: StoryComponentType = () => (
19
19
  <View>
20
20
  <View style={styles.centerText}>
21
21
  <Clickable
@@ -56,7 +56,7 @@ export const basic: StoryComponentType = () => (
56
56
  </View>
57
57
  );
58
58
 
59
- export const keyboardNavigation: StoryComponentType = () => (
59
+ export const KeyboardNavigation: StoryComponentType = () => (
60
60
  <View>
61
61
  <Clickable
62
62
  href="https://www.khanacademy.org/about/tos"
@@ -77,14 +77,14 @@ export const keyboardNavigation: StoryComponentType = () => (
77
77
  </View>
78
78
  );
79
79
 
80
- keyboardNavigation.parameters = {
80
+ KeyboardNavigation.parameters = {
81
81
  chromatic: {
82
82
  // we don't need screenshots because this story only tests behavior.
83
83
  disableSnapshot: true,
84
84
  },
85
85
  };
86
86
 
87
- export const keyboardNavigationTab: StoryComponentType = () => (
87
+ export const KeyboardNavigationTab: StoryComponentType = () => (
88
88
  <View>
89
89
  <Clickable role="tab" aria-controls="panel-1" id="tab-1">
90
90
  {({hovered, focused, pressed}) => (
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2018 Khan Academy
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.