@pie-lib/mask-markup 1.14.1-beta.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.json +1 -871
  2. package/CHANGELOG.md +145 -1
  3. package/lib/choices/choice.js +221 -0
  4. package/lib/choices/choice.js.map +1 -0
  5. package/lib/choices/index.js +135 -0
  6. package/lib/choices/index.js.map +1 -0
  7. package/lib/componentize.js +25 -0
  8. package/lib/componentize.js.map +1 -0
  9. package/lib/components/blank.js +415 -0
  10. package/lib/components/blank.js.map +1 -0
  11. package/lib/components/correct-input.js +118 -0
  12. package/lib/components/correct-input.js.map +1 -0
  13. package/lib/components/dropdown.js +484 -0
  14. package/lib/components/dropdown.js.map +1 -0
  15. package/lib/components/input.js +57 -0
  16. package/lib/components/input.js.map +1 -0
  17. package/lib/constructed-response.js +116 -0
  18. package/lib/constructed-response.js.map +1 -0
  19. package/lib/customizable.js +48 -0
  20. package/lib/customizable.js.map +1 -0
  21. package/lib/drag-in-the-blank.js +217 -0
  22. package/lib/drag-in-the-blank.js.map +1 -0
  23. package/lib/index.js +62 -0
  24. package/lib/index.js.map +1 -0
  25. package/lib/inline-dropdown.js +48 -0
  26. package/lib/inline-dropdown.js.map +1 -0
  27. package/lib/mask.js +254 -0
  28. package/lib/mask.js.map +1 -0
  29. package/lib/serialization.js +207 -0
  30. package/lib/serialization.js.map +1 -0
  31. package/lib/shared/index.js +136 -0
  32. package/lib/with-mask.js +125 -0
  33. package/lib/with-mask.js.map +1 -0
  34. package/package.json +4 -5
  35. package/src/choices/choice.jsx +1 -1
  36. package/src/components/__tests__/__snapshots__/dropdown.test.js.snap +15 -12
  37. package/src/components/blank.jsx +1 -1
  38. package/src/components/correct-input.jsx +1 -1
  39. package/src/components/dropdown.jsx +118 -23
  40. package/src/constructed-response.jsx +1 -1
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.withMask = exports.buildLayoutFromMarkup = void 0;
9
+
10
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
11
+
12
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
13
+
14
+ var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
15
+
16
+ var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
17
+
18
+ var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
19
+
20
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
21
+
22
+ var _react = _interopRequireDefault(require("react"));
23
+
24
+ var _reactDom = _interopRequireDefault(require("react-dom"));
25
+
26
+ var _propTypes = _interopRequireDefault(require("prop-types"));
27
+
28
+ var _mask = _interopRequireDefault(require("./mask"));
29
+
30
+ var _componentize2 = _interopRequireDefault(require("./componentize"));
31
+
32
+ var _serialization = require("./serialization");
33
+
34
+ function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }
35
+
36
+ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
37
+
38
+ var buildLayoutFromMarkup = function buildLayoutFromMarkup(markup, type) {
39
+ var _componentize = (0, _componentize2["default"])(markup, type),
40
+ processed = _componentize.markup;
41
+
42
+ var value = (0, _serialization.deserialize)(processed);
43
+ return value.document;
44
+ };
45
+
46
+ exports.buildLayoutFromMarkup = buildLayoutFromMarkup;
47
+
48
+ var withMask = function withMask(type, renderChildren) {
49
+ var _class;
50
+
51
+ return _class = /*#__PURE__*/function (_React$Component) {
52
+ (0, _inherits2["default"])(WithMask, _React$Component);
53
+
54
+ var _super = _createSuper(WithMask);
55
+
56
+ function WithMask() {
57
+ (0, _classCallCheck2["default"])(this, WithMask);
58
+ return _super.apply(this, arguments);
59
+ }
60
+
61
+ (0, _createClass2["default"])(WithMask, [{
62
+ key: "componentDidUpdate",
63
+ value: function componentDidUpdate(prevProps) {
64
+ if (this.props.markup !== prevProps.markup) {
65
+ // eslint-disable-next-line
66
+ var domNode = _reactDom["default"].findDOMNode(this); // Query all elements that may contain outdated MathJax renderings
67
+
68
+
69
+ var mathElements = domNode && domNode.querySelectorAll('[data-latex][data-math-handled="true"]'); // Clean up for fresh MathJax processing
70
+
71
+ (mathElements || []).forEach(function (el) {
72
+ // Remove the MathJax container to allow for clean updates
73
+ var mjxContainer = el.querySelector('mjx-container');
74
+
75
+ if (mjxContainer) {
76
+ el.removeChild(mjxContainer);
77
+ } // Update the innerHTML to match the raw LaTeX data, ensuring it is reprocessed correctly
78
+
79
+
80
+ var latexCode = el.getAttribute('data-raw');
81
+ el.innerHTML = latexCode; // Remove the attribute to signal that MathJax should reprocess this element
82
+
83
+ el.removeAttribute('data-math-handled');
84
+ });
85
+ }
86
+ }
87
+ }, {
88
+ key: "render",
89
+ value: function render() {
90
+ var _this$props = this.props,
91
+ markup = _this$props.markup,
92
+ layout = _this$props.layout,
93
+ value = _this$props.value,
94
+ onChange = _this$props.onChange,
95
+ elementType = _this$props.elementType;
96
+ var maskLayout = layout ? layout : buildLayoutFromMarkup(markup, type);
97
+ return /*#__PURE__*/_react["default"].createElement(_mask["default"], {
98
+ elementType: elementType,
99
+ layout: maskLayout,
100
+ value: value,
101
+ onChange: onChange,
102
+ renderChildren: renderChildren(this.props)
103
+ });
104
+ }
105
+ }]);
106
+ return WithMask;
107
+ }(_react["default"].Component), (0, _defineProperty2["default"])(_class, "propTypes", {
108
+ /**
109
+ * At the start we'll probably work with markup
110
+ */
111
+ markup: _propTypes["default"].string,
112
+
113
+ /**
114
+ * Once we start authoring, it may make sense for use to us layout, which will be a simple js object that maps to `slate.Value`.
115
+ */
116
+ layout: _propTypes["default"].object,
117
+ value: _propTypes["default"].object,
118
+ onChange: _propTypes["default"].func,
119
+ customMarkMarkupComponent: _propTypes["default"].func,
120
+ elementType: _propTypes["default"].string
121
+ }), _class;
122
+ };
123
+
124
+ exports.withMask = withMask;
125
+ //# sourceMappingURL=with-mask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-mask.jsx"],"names":["buildLayoutFromMarkup","markup","type","processed","value","document","withMask","renderChildren","prevProps","props","domNode","ReactDOM","findDOMNode","mathElements","querySelectorAll","forEach","el","mjxContainer","querySelector","removeChild","latexCode","getAttribute","innerHTML","removeAttribute","layout","onChange","elementType","maskLayout","React","Component","PropTypes","string","object","func","customMarkMarkupComponent"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;;AACA;;AACA;;AACA;;AACA;;AACA;;;;;;AAEO,IAAMA,qBAAqB,GAAG,SAAxBA,qBAAwB,CAACC,MAAD,EAASC,IAAT,EAAkB;AACrD,sBAA8B,+BAAaD,MAAb,EAAqBC,IAArB,CAA9B;AAAA,MAAgBC,SAAhB,iBAAQF,MAAR;;AACA,MAAMG,KAAK,GAAG,gCAAYD,SAAZ,CAAd;AACA,SAAOC,KAAK,CAACC,QAAb;AACD,CAJM;;;;AAMA,IAAMC,QAAQ,GAAG,SAAXA,QAAW,CAACJ,IAAD,EAAOK,cAAP,EAA0B;AAAA;;AAChD;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA,aAgBE,4BAAmBC,SAAnB,EAA8B;AAC5B,YAAI,KAAKC,KAAL,CAAWR,MAAX,KAAsBO,SAAS,CAACP,MAApC,EAA4C;AAC1C;AACA,cAAMS,OAAO,GAAGC,qBAASC,WAAT,CAAqB,IAArB,CAAhB,CAF0C,CAG1C;;;AACA,cAAMC,YAAY,GAAGH,OAAO,IAAIA,OAAO,CAACI,gBAAR,CAAyB,wCAAzB,CAAhC,CAJ0C,CAM1C;;AACA,WAACD,YAAY,IAAI,EAAjB,EAAqBE,OAArB,CAA6B,UAACC,EAAD,EAAQ;AACnC;AACA,gBAAMC,YAAY,GAAGD,EAAE,CAACE,aAAH,CAAiB,eAAjB,CAArB;;AAEA,gBAAID,YAAJ,EAAkB;AAChBD,cAAAA,EAAE,CAACG,WAAH,CAAeF,YAAf;AACD,aANkC,CAQnC;;;AACA,gBAAMG,SAAS,GAAGJ,EAAE,CAACK,YAAH,CAAgB,UAAhB,CAAlB;AACAL,YAAAA,EAAE,CAACM,SAAH,GAAeF,SAAf,CAVmC,CAYnC;;AACAJ,YAAAA,EAAE,CAACO,eAAH,CAAmB,mBAAnB;AACD,WAdD;AAeD;AACF;AAxCH;AAAA;AAAA,aA0CE,kBAAS;AACP,0BAAyD,KAAKd,KAA9D;AAAA,YAAQR,MAAR,eAAQA,MAAR;AAAA,YAAgBuB,MAAhB,eAAgBA,MAAhB;AAAA,YAAwBpB,KAAxB,eAAwBA,KAAxB;AAAA,YAA+BqB,QAA/B,eAA+BA,QAA/B;AAAA,YAAyCC,WAAzC,eAAyCA,WAAzC;AAEA,YAAMC,UAAU,GAAGH,MAAM,GAAGA,MAAH,GAAYxB,qBAAqB,CAACC,MAAD,EAASC,IAAT,CAA1D;AACA,4BACE,gCAAC,gBAAD;AACE,UAAA,WAAW,EAAEwB,WADf;AAEE,UAAA,MAAM,EAAEC,UAFV;AAGE,UAAA,KAAK,EAAEvB,KAHT;AAIE,UAAA,QAAQ,EAAEqB,QAJZ;AAKE,UAAA,cAAc,EAAElB,cAAc,CAAC,KAAKE,KAAN;AALhC,UADF;AASD;AAvDH;AAAA;AAAA,IAA8BmB,kBAAMC,SAApC,yDACqB;AACjB;AACN;AACA;AACM5B,IAAAA,MAAM,EAAE6B,sBAAUC,MAJD;;AAKjB;AACN;AACA;AACMP,IAAAA,MAAM,EAAEM,sBAAUE,MARD;AASjB5B,IAAAA,KAAK,EAAE0B,sBAAUE,MATA;AAUjBP,IAAAA,QAAQ,EAAEK,sBAAUG,IAVH;AAWjBC,IAAAA,yBAAyB,EAAEJ,sBAAUG,IAXpB;AAYjBP,IAAAA,WAAW,EAAEI,sBAAUC;AAZN,GADrB;AAyDD,CA1DM","sourcesContent":["import React from 'react';\nimport ReactDOM from 'react-dom';\nimport PropTypes from 'prop-types';\nimport Mask from './mask';\nimport componentize from './componentize';\nimport { deserialize } from './serialization';\n\nexport const buildLayoutFromMarkup = (markup, type) => {\n const { markup: processed } = componentize(markup, type);\n const value = deserialize(processed);\n return value.document;\n};\n\nexport const withMask = (type, renderChildren) => {\n return class WithMask extends React.Component {\n static propTypes = {\n /**\n * At the start we'll probably work with markup\n */\n markup: PropTypes.string,\n /**\n * Once we start authoring, it may make sense for use to us layout, which will be a simple js object that maps to `slate.Value`.\n */\n layout: PropTypes.object,\n value: PropTypes.object,\n onChange: PropTypes.func,\n customMarkMarkupComponent: PropTypes.func,\n elementType: PropTypes.string,\n };\n\n componentDidUpdate(prevProps) {\n if (this.props.markup !== prevProps.markup) {\n // eslint-disable-next-line\n const domNode = ReactDOM.findDOMNode(this);\n // Query all elements that may contain outdated MathJax renderings\n const mathElements = domNode && domNode.querySelectorAll('[data-latex][data-math-handled=\"true\"]');\n\n // Clean up for fresh MathJax processing\n (mathElements || []).forEach((el) => {\n // Remove the MathJax container to allow for clean updates\n const mjxContainer = el.querySelector('mjx-container');\n\n if (mjxContainer) {\n el.removeChild(mjxContainer);\n }\n\n // Update the innerHTML to match the raw LaTeX data, ensuring it is reprocessed correctly\n const latexCode = el.getAttribute('data-raw');\n el.innerHTML = latexCode;\n\n // Remove the attribute to signal that MathJax should reprocess this element\n el.removeAttribute('data-math-handled');\n });\n }\n }\n\n render() {\n const { markup, layout, value, onChange, elementType } = this.props;\n\n const maskLayout = layout ? layout : buildLayoutFromMarkup(markup, type);\n return (\n <Mask\n elementType={elementType}\n layout={maskLayout}\n value={value}\n onChange={onChange}\n renderChildren={renderChildren(this.props)}\n />\n );\n }\n };\n};\n"],"file":"with-mask.js"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-lib/mask-markup",
3
- "version": "1.14.1-beta.0",
3
+ "version": "1.16.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "module": "src/index.js",
@@ -10,10 +10,9 @@
10
10
  "dependencies": {
11
11
  "@material-ui/core": "^3.9.3",
12
12
  "@material-ui/icons": "^3.0.2",
13
- "@pie-lib/drag": "^2.3.1-beta.0",
13
+ "@pie-lib/drag": "beta",
14
14
  "@pie-lib/editable-html": "beta",
15
- "@pie-lib/math-rendering": "^3.3.1-beta.0",
16
- "@pie-lib/render-ui": "^4.16.1-beta.0",
15
+ "@pie-lib/math-rendering": "beta",
17
16
  "classnames": "^2.2.6",
18
17
  "debug": "^4.1.1",
19
18
  "immutable": ">=3.8.1",
@@ -31,5 +30,5 @@
31
30
  "keywords": [],
32
31
  "author": "",
33
32
  "license": "ISC",
34
- "gitHead": "e2aa3ddac60f49bcb8c2562370f496323642f453"
33
+ "gitHead": "ecb1ca31aa1c24d23af00d5ad04ce73ed8871057"
35
34
  }
@@ -6,7 +6,7 @@ import Chip from '@material-ui/core/Chip';
6
6
  import classnames from 'classnames';
7
7
 
8
8
  import { renderMath } from '@pie-lib/math-rendering';
9
- import { color } from '@pie-lib/render-ui';
9
+ import { color } from '../../../render-ui/src/index';
10
10
  import { DragSource } from '@pie-lib/drag';
11
11
 
12
12
  export const DRAG_TYPE = 'MaskBlank';
@@ -23,17 +23,18 @@ exports[`Dropdown render renders correctly with correct as true 1`] = `
23
23
  }
24
24
  classes={
25
25
  Object {
26
- "correctIcon": "Dropdown-correctIcon-10",
27
- "correctnessIndicatorIcon": "Dropdown-correctnessIndicatorIcon-9",
26
+ "correctIcon": "Dropdown-correctIcon-11",
27
+ "correctnessIndicatorIcon": "Dropdown-correctnessIndicatorIcon-10",
28
28
  "disabledCorrect": "Dropdown-disabledCorrect-2",
29
29
  "disabledIncorrect": "Dropdown-disabledIncorrect-3",
30
- "incorrectIcon": "Dropdown-incorrectIcon-11",
30
+ "incorrectIcon": "Dropdown-incorrectIcon-12",
31
31
  "label": "Dropdown-label-7",
32
32
  "menuRoot": "Dropdown-menuRoot-6",
33
33
  "root": "Dropdown-root-1",
34
34
  "selectMenu": "Dropdown-selectMenu-4",
35
35
  "selected": "Dropdown-selected-5",
36
- "srOnly": "Dropdown-srOnly-8",
36
+ "selectedIndicator": "Dropdown-selectedIndicator-8",
37
+ "srOnly": "Dropdown-srOnly-9",
37
38
  }
38
39
  }
39
40
  correct={true}
@@ -67,17 +68,18 @@ exports[`Dropdown render renders correctly with default props 1`] = `
67
68
  }
68
69
  classes={
69
70
  Object {
70
- "correctIcon": "Dropdown-correctIcon-10",
71
- "correctnessIndicatorIcon": "Dropdown-correctnessIndicatorIcon-9",
71
+ "correctIcon": "Dropdown-correctIcon-11",
72
+ "correctnessIndicatorIcon": "Dropdown-correctnessIndicatorIcon-10",
72
73
  "disabledCorrect": "Dropdown-disabledCorrect-2",
73
74
  "disabledIncorrect": "Dropdown-disabledIncorrect-3",
74
- "incorrectIcon": "Dropdown-incorrectIcon-11",
75
+ "incorrectIcon": "Dropdown-incorrectIcon-12",
75
76
  "label": "Dropdown-label-7",
76
77
  "menuRoot": "Dropdown-menuRoot-6",
77
78
  "root": "Dropdown-root-1",
78
79
  "selectMenu": "Dropdown-selectMenu-4",
79
80
  "selected": "Dropdown-selected-5",
80
- "srOnly": "Dropdown-srOnly-8",
81
+ "selectedIndicator": "Dropdown-selectedIndicator-8",
82
+ "srOnly": "Dropdown-srOnly-9",
81
83
  }
82
84
  }
83
85
  correct={false}
@@ -111,17 +113,18 @@ exports[`Dropdown render renders correctly with disabled prop as true 1`] = `
111
113
  }
112
114
  classes={
113
115
  Object {
114
- "correctIcon": "Dropdown-correctIcon-10",
115
- "correctnessIndicatorIcon": "Dropdown-correctnessIndicatorIcon-9",
116
+ "correctIcon": "Dropdown-correctIcon-11",
117
+ "correctnessIndicatorIcon": "Dropdown-correctnessIndicatorIcon-10",
116
118
  "disabledCorrect": "Dropdown-disabledCorrect-2",
117
119
  "disabledIncorrect": "Dropdown-disabledIncorrect-3",
118
- "incorrectIcon": "Dropdown-incorrectIcon-11",
120
+ "incorrectIcon": "Dropdown-incorrectIcon-12",
119
121
  "label": "Dropdown-label-7",
120
122
  "menuRoot": "Dropdown-menuRoot-6",
121
123
  "root": "Dropdown-root-1",
122
124
  "selectMenu": "Dropdown-selectMenu-4",
123
125
  "selected": "Dropdown-selected-5",
124
- "srOnly": "Dropdown-srOnly-8",
126
+ "selectedIndicator": "Dropdown-selectedIndicator-8",
127
+ "srOnly": "Dropdown-srOnly-9",
125
128
  }
126
129
  }
127
130
  correct={false}
@@ -8,7 +8,7 @@ import { DragSource, DropTarget } from '@pie-lib/drag';
8
8
  import { withStyles } from '@material-ui/core/styles';
9
9
  import Chip from '@material-ui/core/Chip';
10
10
  import classnames from 'classnames';
11
- import { color } from '@pie-lib/render-ui';
11
+ import { color } from '../../../render-ui/src/index';
12
12
 
13
13
  const log = debug('pie-lib:mask-markup:blank');
14
14
  export const DRAG_TYPE = 'MaskBlank';
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import OutlinedInput from '@material-ui/core/OutlinedInput';
3
3
  import classnames from 'classnames';
4
4
  import { withStyles } from '@material-ui/core/styles';
5
- import { color } from '@pie-lib/render-ui';
5
+ import { color } from '../../../render-ui/src/index';
6
6
 
7
7
  const correctStyle = (color) => ({
8
8
  borderColor: `${color} !important`,
@@ -5,12 +5,14 @@ import InputLabel from '@material-ui/core/InputLabel';
5
5
  import Menu from '@material-ui/core/Menu';
6
6
  import MenuItem from '@material-ui/core/MenuItem';
7
7
  import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
8
+ import ArrowDropUpIcon from '@material-ui/icons/ArrowDropUp';
8
9
  import Close from '@material-ui/icons/Close';
9
10
  import Check from '@material-ui/icons/Check';
10
11
  import { withStyles } from '@material-ui/core/styles';
11
12
  import classNames from 'classnames';
13
+ import isEqual from 'lodash/isEqual';
12
14
 
13
- import { color } from '@pie-lib/render-ui';
15
+ import { color } from '../../../render-ui/src/index';
14
16
  import { renderMath } from '@pie-lib/math-rendering';
15
17
 
16
18
  class Dropdown extends React.Component {
@@ -23,6 +25,8 @@ class Dropdown extends React.Component {
23
25
  correct: PropTypes.bool,
24
26
  choices: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string, label: PropTypes.string })),
25
27
  showCorrectAnswer: PropTypes.bool,
28
+ singleQuery: PropTypes.bool,
29
+ correctValue: PropTypes.string,
26
30
  };
27
31
 
28
32
  constructor(props) {
@@ -31,27 +35,73 @@ class Dropdown extends React.Component {
31
35
  this.state = {
32
36
  anchorEl: null,
33
37
  highlightedOptionId: null,
38
+ menuWidth: null,
39
+ previewValue: null,
34
40
  };
35
-
41
+ this.hiddenRef = React.createRef();
42
+ this.buttonRef = React.createRef();
43
+ this.previewRef = React.createRef();
36
44
  this.elementRefs = [];
37
45
  }
38
46
 
39
- componentDidUpdate() {
40
- this.elementRefs.forEach((ref) => {
41
- if (ref) {
42
- renderMath(ref);
47
+ componentDidMount() {
48
+ // measure hidden menu width once
49
+ if (this.hiddenRef.current && this.state.menuWidth === null) {
50
+ this.setState({ menuWidth: this.hiddenRef.current.clientWidth });
51
+ }
52
+ }
53
+
54
+ componentDidUpdate(prevProps, prevState) {
55
+ const hiddenEl = this.hiddenRef.current;
56
+
57
+ const dropdownJustOpened = !prevState.anchorEl && this.state.anchorEl;
58
+ if (dropdownJustOpened) {
59
+ this.elementRefs.forEach((ref) => {
60
+ if (!ref) return;
61
+
62
+ const containsLatex = ref.querySelector('[data-latex], [data-raw]');
63
+ const hasMathJax = ref.querySelector('mjx-container');
64
+ const mathHandled = ref.querySelector('[data-math-handled="true"]');
65
+
66
+ if (containsLatex && (!mathHandled || !hasMathJax)) {
67
+ renderMath(ref);
68
+ }
69
+ });
70
+ }
71
+
72
+ if (hiddenEl) {
73
+ const newWidth = hiddenEl.clientWidth;
74
+ if (newWidth !== this.state.menuWidth) {
75
+ this.elementRefs.forEach((ref) => {
76
+ if (ref) renderMath(ref);
77
+ });
78
+
79
+ renderMath(hiddenEl);
80
+ this.setState({ menuWidth: newWidth });
43
81
  }
44
- });
82
+ }
45
83
  }
46
84
 
47
85
  handleClick = (event) => this.setState({ anchorEl: event.currentTarget });
48
86
 
49
- handleClose = () => this.setState({ anchorEl: null });
87
+ handleClose = () => {
88
+ const { value } = this.props;
89
+ this.setState({ anchorEl: null, previewValue: null, highlightedOptionId: null });
90
+ // clear displayed preview if no selection
91
+ if (!value && this.previewRef.current) {
92
+ this.previewRef.current.innerHTML = '';
93
+ }
94
+ };
50
95
 
51
96
  handleHighlight = (index) => {
52
97
  const highlightedOptionId = `dropdown-option-${this.props.id}-${index}`;
53
98
 
54
- this.setState({ highlightedOptionId });
99
+ // preview on hover if nothing selected
100
+ const stateUpdate = { highlightedOptionId };
101
+ if (!this.props.value) {
102
+ stateUpdate.previewValue = this.props.choices[index].value;
103
+ }
104
+ this.setState(stateUpdate);
55
105
  };
56
106
 
57
107
  handleSelect = (value, index) => {
@@ -60,6 +110,25 @@ class Dropdown extends React.Component {
60
110
  this.handleClose();
61
111
  };
62
112
 
113
+ handleHover = (index) => {
114
+ const selectedValue = this.props.value;
115
+
116
+ if (selectedValue) return;
117
+
118
+ const highlightedOptionId = `dropdown-option-${this.props.id}-${index}`;
119
+ const previewValue = this.state.previewValue;
120
+
121
+ this.setState({ highlightedOptionId, previewValue }, () => {
122
+ // On hover, preview the math-rendered content inside the button if no value is selected.
123
+ const ref = this.elementRefs[index];
124
+ const preview = this.previewRef.current;
125
+
126
+ if (ref && preview) {
127
+ preview.innerHTML = ref.innerHTML;
128
+ }
129
+ });
130
+ };
131
+
63
132
  getLabel(choices, value) {
64
133
  const found = (choices || []).find((choice) => choice.value === value);
65
134
 
@@ -68,7 +137,6 @@ class Dropdown extends React.Component {
68
137
 
69
138
  render() {
70
139
  const { classes, id, correct, disabled, value, choices, showCorrectAnswer, singleQuery, correctValue } = this.props;
71
-
72
140
  const { anchorEl } = this.state;
73
141
  const open = Boolean(anchorEl);
74
142
  const buttonId = `dropdown-button-${id}`;
@@ -90,7 +158,6 @@ class Dropdown extends React.Component {
90
158
  const labelText = singleQuery ? 'Query' : `Query ${incrementedId}`;
91
159
 
92
160
  // Changed from Select to Button for dropdown to enhance accessibility. This modification offers explicit control over aria attributes and focuses management, ensuring the dropdown is compliant with accessibility standards. The use of Button and Menu components allows for better handling of keyboard interactions and provides accessible labels and menus, aligning with WCAG guidelines and improving usability for assistive technology users.
93
-
94
161
  let correctnessIcon = null;
95
162
  if (disabled && correct !== undefined) {
96
163
  correctnessIcon =
@@ -103,10 +170,23 @@ class Dropdown extends React.Component {
103
170
 
104
171
  return (
105
172
  <>
173
+ <div ref={this.hiddenRef} style={{ position: 'absolute', visibility: 'hidden', top: 0, left: 0 }}>
174
+ {(choices || []).map((c, index) => (
175
+ <MenuItem key={index} classes={{ root: classes.menuRoot, selected: classes.selected }}>
176
+ <span className={classes.label} dangerouslySetInnerHTML={{ __html: c.label }} />
177
+ </MenuItem>
178
+ ))}
179
+ </div>
106
180
  <InputLabel className={classes.srOnly} id={labelId}>
107
181
  {labelText}
108
182
  </InputLabel>
109
183
  <Button
184
+ ref={this.buttonRef}
185
+ style={{
186
+ ...(this.state.menuWidth && { minWidth: `calc(${this.state.menuWidth}px + 8px)` }),
187
+ borderWidth: open ? '2px' : '1px',
188
+ transition: 'border-width 0.2s ease-in-out',
189
+ }}
110
190
  aria-controls={open ? menuId : undefined}
111
191
  aria-haspopup="listbox"
112
192
  aria-expanded={open ? 'true' : undefined}
@@ -125,12 +205,17 @@ class Dropdown extends React.Component {
125
205
  {correctnessIcon}
126
206
  <span
127
207
  id={valueDisplayId}
208
+ ref={this.previewRef}
128
209
  className={classes.label}
129
210
  dangerouslySetInnerHTML={{
130
- __html: correctValue ? correctValue : this.getLabel(choices, value) ? this.getLabel(choices, value) : '',
211
+ __html: correctValue
212
+ ? correctValue
213
+ : open && this.state.previewValue
214
+ ? this.getLabel(choices, this.state.previewValue)
215
+ : this.getLabel(choices, value) || '',
131
216
  }}
132
217
  />
133
- <ArrowDropDownIcon />
218
+ {open ? <ArrowDropUpIcon /> : <ArrowDropDownIcon />}
134
219
  </Button>
135
220
  <Menu
136
221
  id={menuId}
@@ -139,9 +224,14 @@ class Dropdown extends React.Component {
139
224
  keepMounted
140
225
  open={open}
141
226
  onClose={this.handleClose}
227
+ getContentAnchorEl={null}
228
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
229
+ transformOrigin={{ vertical: 'top', horizontal: 'left' }}
230
+ PaperProps={this.state.menuWidth ? { style: { minWidth: this.state.menuWidth, padding: '4px' } } : undefined}
142
231
  MenuListProps={{
143
232
  'aria-labelledby': buttonId,
144
233
  role: 'listbox',
234
+ disablePadding: true,
145
235
  }}
146
236
  >
147
237
  {(choices || []).map((c, index) => {
@@ -156,6 +246,7 @@ class Dropdown extends React.Component {
156
246
  onClick={() => this.handleSelect(c.value, index)}
157
247
  role="option"
158
248
  aria-selected={this.state.highlightedOptionId === optionId ? 'true' : undefined}
249
+ onMouseOver={() => this.handleHover(index)}
159
250
  >
160
251
  <span
161
252
  ref={(ref) => (this.elementRefs[index] = ref)}
@@ -163,7 +254,7 @@ class Dropdown extends React.Component {
163
254
  dangerouslySetInnerHTML={{ __html: c.label }}
164
255
  />
165
256
  <span
166
- className={classes.label}
257
+ className={classes.selectedIndicator}
167
258
  dangerouslySetInnerHTML={{ __html: c.value === value ? ' &check;' : '' }}
168
259
  />
169
260
  </MenuItem>
@@ -178,7 +269,7 @@ class Dropdown extends React.Component {
178
269
  const styles = () => ({
179
270
  root: {
180
271
  color: color.text(),
181
- border: `1px solid ${color.text()}`,
272
+ border: `1px solid ${color.borderGray()}`,
182
273
  borderRadius: '4px',
183
274
  justifyContent: 'space-between',
184
275
  backgroundColor: color.background(),
@@ -220,13 +311,17 @@ const styles = () => ({
220
311
  border: `1px solid ${color.text()}`,
221
312
  borderColor: 'initial',
222
313
  },
314
+ // remove default padding on the inner list
315
+ '& .MuiList-root': {
316
+ padding: 0,
317
+ },
223
318
  },
224
319
  selected: {
225
320
  color: `${color.text()} !important`,
226
321
  backgroundColor: `${color.background()} !important`,
227
322
  '&:hover': {
228
323
  color: color.text(),
229
- backgroundColor: `${color.secondaryLight()} !important`,
324
+ backgroundColor: `${color.dropdownBackground()} !important`,
230
325
  },
231
326
  },
232
327
  menuRoot: {
@@ -238,20 +333,20 @@ const styles = () => ({
238
333
  },
239
334
  '&:hover': {
240
335
  color: color.text(),
241
- backgroundColor: color.secondaryLight(),
336
+ backgroundColor: color.dropdownBackground(),
242
337
  },
243
338
  boxSizing: 'border-box',
244
339
  padding: '25px',
245
- '&:first-of-type': {
246
- borderRadius: '3px 3px 0 0',
247
- },
248
- '&:last-of-type': {
249
- borderRadius: '0 0 3px 3px',
250
- },
340
+ borderRadius: '4px',
251
341
  },
252
342
  label: {
253
343
  fontSize: 'max(1rem, 14px)',
254
344
  },
345
+ selectedIndicator: {
346
+ fontSize: 'max(1rem, 14px)',
347
+ position: 'absolute',
348
+ right: '10px',
349
+ },
255
350
  srOnly: {
256
351
  position: 'absolute',
257
352
  left: '-10000px',
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { withStyles } from '@material-ui/core/styles';
3
3
  import classnames from 'classnames';
4
4
 
5
- import { color } from '@pie-lib/render-ui';
5
+ import { color } from '../../render-ui/src/index';
6
6
  import EditableHtml from '@pie-lib/editable-html';
7
7
  import { withMask } from './with-mask';
8
8