@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
559 lines (551 loc) • 25.3 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
exports.getNewValue = getNewValue;
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _uuid = require("uuid");
var _uiA11yContent = require("@instructure/ui-a11y-content");
var _uiView = require("@instructure/ui-view");
var _uiGrid = require("@instructure/ui-grid");
var _withEditTools = _interopRequireDefault(require("../../../util/withEditTools"));
var _Errors = _interopRequireDefault(require("../../common/edit/components/Errors"));
var _FocusGroup = _interopRequireDefault(require("../../common/components/FocusGroup"));
var _Footer = _interopRequireDefault(require("../../common/edit/components/Footer"));
var _numeric = _interopRequireDefault(require("../../../records/interactions/numeric"));
var _QuestionSettingsContainer = _interopRequireDefault(require("../../common/edit/components/QuestionSettingsContainer"));
var _QuestionContainer = _interopRequireDefault(require("../../common/edit/components/QuestionContainer"));
var _RemoveChoiceButton = _interopRequireDefault(require("../../common/edit/components/RemoveChoiceButton"));
var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/es/format-message"));
var _quizI18n = require("@instructure/quiz-i18n");
var _QuestionSettingsPanel = _interopRequireDefault(require("../../common/edit/components/QuestionSettingsPanel"));
var _CalculatorOptionWithOqaatAlert = _interopRequireDefault(require("../../common/edit/components/CalculatorOptionWithOqaatAlert"));
var _ExactResponse = _interopRequireDefault(require("./ExactResponse"));
var _MarginOfError = _interopRequireDefault(require("./MarginOfError"));
var _PreciseResponse = _interopRequireDefault(require("./PreciseResponse"));
var _RequirementHelpModal = _interopRequireDefault(require("./RequirementHelpModal"));
var _WithinARange = _interopRequireDefault(require("./WithinARange"));
var _constants = require("./constants");
var _NumericTypeSelect = _interopRequireDefault(require("./NumericTypeSelect"));
var _quizCommon = require("@instructure/quiz-common");
var _uiI18n = require("@instructure/ui-i18n");
var _class, _NumericEdit;
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _callSuper(_this, derived, args) {
function isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
return !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
} catch (e) {
return false;
}
}
derived = (0, _getPrototypeOf2["default"])(derived);
return (0, _possibleConstructorReturn2["default"])(_this, isNativeReflectConstruct() ? Reflect.construct(derived, args || [], (0, _getPrototypeOf2["default"])(_this).constructor) : derived.apply(_this, args));
}
function getNewValue(answer, type) {
var newValue = {
id: answer.id || '',
type: type
};
switch (type) {
case _constants.EXACT_RESPONSE:
Object.assign(newValue, {
value: answer.value || ''
});
break;
case _constants.MARGIN_OF_ERROR:
Object.assign(newValue, {
value: answer.value || '',
margin: answer.margin || '',
marginType: answer.marginType || _constants.PERCENT
});
break;
case _constants.WITHIN_A_RANGE:
Object.assign(newValue, {
start: answer.start || '',
end: answer.end || ''
});
break;
case _constants.PRECISE_RESPONSE:
Object.assign(newValue, {
value: answer.value || '',
precision: answer.precision || '',
precisionType: answer.precisionType || _constants.SIGNIFICANT_DIGITS
});
break;
}
return newValue;
}
/**
---
category: Numeric
---
Numeric Edit component
```jsx_example
class Example extends React.Component {
render () {
const exampleProps = {
itemId: '1',
itemBody: 'x is an integer and 9 < x^2 < 99. What\'s the max value of x, minus the minimum value of x',
scoringData: {
value: [{
type: 'exactResponse',
id: '1',
value: '1800.5'
}, {
type: 'withinARange',
id: '2',
start: '1800.5',
end: '1801.5'
}, {
type: 'preciseResponse',
id: '3',
value: '1800.5',
precision: '1',
precisionType: 'decimals'
}, {
type: 'marginOfError',
id: '4',
value: '1800.5',
margin: '0.4',
marginType: 'absolute'
}]
}
}
return (
<NumericEdit {...exampleProps} {...this.props} />
)
}
}
<SettingsSwitcher locales={LOCALES}>
<EditStateProvider>
<Example />
</EditStateProvider>
</SettingsSwitcher>
```
**/
var NumericEdit = exports["default"] = (0, _withEditTools["default"])(_class = (_NumericEdit = /*#__PURE__*/function (_Component) {
function NumericEdit() {
var _this2;
(0, _classCallCheck2["default"])(this, NumericEdit);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this2 = _callSuper(this, NumericEdit, [].concat(args));
(0, _defineProperty2["default"])(_this2, "typeSelectInputRefs", {});
(0, _defineProperty2["default"])(_this2, "choicesFocusGroup", null);
(0, _defineProperty2["default"])(_this2, "stemElement", null);
(0, _defineProperty2["default"])(_this2, "_timeouts", []);
(0, _defineProperty2["default"])(_this2, "state", {
helpModalOpen: false
});
// ===========
// HANDLERS
// ===========
(0, _defineProperty2["default"])(_this2, "handleTypeChange", function (event, _ref) {
var id = _ref.id,
type = _ref.type;
var answers = (0, _toConsumableArray2["default"])(_this2.answers);
var index = answers.findIndex(function (answer) {
return answer.id === id;
});
console.assert(index !== -1); // eslint-disable-line no-console
answers[index] = getNewValue(answers[index], type);
_this2.props.changeItemState({
scoringData: _objectSpread(_objectSpread({}, _this2.props.scoringData), {}, {
value: answers
})
});
setTimeout(function () {
_this2.typeSelectInputRefs[id].focus();
}, 0);
});
(0, _defineProperty2["default"])(_this2, "handleChange", function (event, answer) {
var answers = (0, _toConsumableArray2["default"])(_this2.answers);
var index = answers.findIndex(function (_ref2) {
var id = _ref2.id;
return id === answer.id;
});
console.assert(index !== -1); // eslint-disable-line no-console
var updatedAnswer = _objectSpread(_objectSpread({}, answers[index]), answer);
answers[index] = updatedAnswer;
_this2.props.changeItemState({
scoringData: _objectSpread(_objectSpread({}, _this2.props.scoringData), {}, {
value: answers
})
});
});
(0, _defineProperty2["default"])(_this2, "handleCalculatorTypeChange", function (e, value) {
_this2.props.changeItemState({
calculatorType: value
});
});
(0, _defineProperty2["default"])(_this2, "handleCreateAnswer", function () {
_this2._timeouts = [].concat((0, _toConsumableArray2["default"])(_this2._timeouts), [setTimeout(function () {
return _this2.choicesFocusGroup.focusLast('select');
}, 100)]);
var id = _this2.props.newId();
var newValue = getNewValue({
id: id
}, _constants.EXACT_RESPONSE);
_this2.props.changeItemState({
scoringData: _objectSpread(_objectSpread({}, _this2.props.scoringData), {}, {
value: [].concat((0, _toConsumableArray2["default"])(_this2.answers), [newValue])
})
});
});
(0, _defineProperty2["default"])(_this2, "handleRemoveChoice", function (id) {
// In FF clicking on a button doesn't focus it
if (document.activeElement !== document.body) {
_this2.updateFocusOnRemove();
}
var answers = _this2.answers.filter(function (answer) {
return answer.id !== id;
});
_this2.props.changeItemState({
scoringData: _objectSpread(_objectSpread({}, _this2.props.scoringData), {}, {
value: answers
})
});
});
(0, _defineProperty2["default"])(_this2, "openHelpModal", function () {
return _this2.setState({
helpModalOpen: true
});
});
(0, _defineProperty2["default"])(_this2, "closeHelpModal", function () {
return _this2.setState({
helpModalOpen: false
});
});
(0, _defineProperty2["default"])(_this2, "handleChoicesFocusGroupRef", function (node) {
_this2.choicesFocusGroup = node;
});
(0, _defineProperty2["default"])(_this2, "handleStemRef", function (node) {
_this2.stemElement = node;
});
(0, _defineProperty2["default"])(_this2, "setTypeSelectInputRef", function (id, node) {
_this2.typeSelectInputRefs[id] = node;
});
(0, _defineProperty2["default"])(_this2, "renderAnswerWrapper", function (answer) {
var id = answer.id,
type = answer.type;
return /*#__PURE__*/_react["default"].createElement(_uiGrid.Grid, {
startAt: "medium",
key: id,
colSpacing: "small"
}, /*#__PURE__*/_react["default"].createElement(_uiGrid.Grid.Row, null, /*#__PURE__*/_react["default"].createElement(_uiGrid.Grid.Col, {
rowSpacing: "none"
}, /*#__PURE__*/_react["default"].createElement(_quizCommon.FormFieldGroup, {
vAlign: "top",
rowSpacing: "none",
name: id,
layout: "columns",
description: /*#__PURE__*/_react["default"].createElement(_uiA11yContent.ScreenReaderContent, null, (0, _formatMessage["default"])('Possible answer'))
}, _this2.renderAnswerContents(answer))), _this2.answers.length > 1 && /*#__PURE__*/_react["default"].createElement(_uiGrid.Grid.Col, {
width: "auto"
}, /*#__PURE__*/_react["default"].createElement(_uiView.View, {
as: "div",
margin: "large 0 0",
themeOverride: {
marginLarge: '1.875rem'
}
}, /*#__PURE__*/_react["default"].createElement(_RemoveChoiceButton["default"], {
onClick: function onClick() {
return _this2.handleRemoveChoice(answer.id);
},
choiceId: type,
screenReaderText: (0, _formatMessage["default"])('Remove Answer: {answer}', {
answer: _this2.stringifyAnswer(answer)
})
})))));
});
return _this2;
}
(0, _inherits2["default"])(NumericEdit, _Component);
return (0, _createClass2["default"])(NumericEdit, [{
key: "componentWillUnmount",
value: function componentWillUnmount() {
this._timeouts.forEach(clearTimeout);
}
}, {
key: "componentDidMount",
value: function componentDidMount() {
_quizI18n.Decimal.accountSettingDelimiters = this.props.separatorConfig ? (0, _quizCommon.parseSeparators)(this.props.separatorConfig) : null;
}
}, {
key: "locale",
get: function get() {
return this.props.locale || this.context.locale || 'en-US';
}
}, {
key: "answers",
get: function get() {
return this.props.scoringData.value;
}
}, {
key: "stringifyAnswer",
value: function stringifyAnswer(_ref3) {
var type = _ref3.type,
value = _ref3.value,
start = _ref3.start,
end = _ref3.end,
margin = _ref3.margin,
marginType = _ref3.marginType,
precision = _ref3.precision,
precisionType = _ref3.precisionType;
var orBlank = function orBlank(val) {
return val || 'blank';
};
return (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, _constants.EXACT_RESPONSE, (0, _formatMessage["default"])('value of {value}', {
value: orBlank(value)
})), _constants.MARGIN_OF_ERROR, (0, _formatMessage["default"])('value of {value} with a margin of error of {margin} {marginType}', {
value: orBlank(value),
margin: orBlank(margin),
marginType: marginType
})), _constants.WITHIN_A_RANGE, (0, _formatMessage["default"])('range from {start} to {end}', {
start: orBlank(start),
end: orBlank(end)
})), _constants.PRECISE_RESPONSE, (0, _formatMessage["default"])('value of {value} with a precision of {precision} {precisionType}', {
value: orBlank(value),
precision: orBlank(precision),
precisionType: precisionType
}))[type];
}
}, {
key: "updateFocusOnRemove",
value: function updateFocusOnRemove() {
var _this3 = this;
var selector = 'button';
var textContent = 'Remove Answer';
if (!this.choicesFocusGroup.previousExists(selector, textContent)) {
// if removing the first choice, focus on stem
// added timeout to compensate for RCE sluggishness
this._timeouts = [].concat((0, _toConsumableArray2["default"])(this._timeouts), [setTimeout(function () {
return _this3.stemElement.focus();
}, 100)]);
} else if (this.answers.length === 2) {
// if removing the second choice out of two choices
this._timeouts = [].concat((0, _toConsumableArray2["default"])(this._timeouts), [setTimeout(function () {
return _this3.choicesFocusGroup.focusLast();
}, 100)]);
} else {
// all the other cases
this.choicesFocusGroup.focusPrevious(selector, textContent);
}
}
}, {
key: "renderAnswerField",
value:
// ===========
// RENDERS
// ===========
function renderAnswerField(_ref4, index) {
var id = _ref4.id,
value = _ref4.value;
return /*#__PURE__*/_react["default"].createElement(_ExactResponse["default"], {
id: id,
locale: this.locale,
messages: this.props.getErrors("scoringData.value[".concat(index, "]"), {}),
numericTypeSelect: this.renderNumericTypeSelect(id, _constants.EXACT_RESPONSE),
onChange: this.handleChange,
value: value
});
}
}, {
key: "renderPrecisionAnswer",
value: function renderPrecisionAnswer(_ref5, index) {
var id = _ref5.id,
precision = _ref5.precision,
precisionType = _ref5.precisionType,
value = _ref5.value;
return /*#__PURE__*/_react["default"].createElement(_PreciseResponse["default"], {
id: id,
locale: this.locale,
messages: this.props.getErrors("scoringData.value[".concat(index, "]"), {}),
numericTypeSelect: this.renderNumericTypeSelect(id, _constants.PRECISE_RESPONSE),
onChange: this.handleChange,
precision: precision,
precisionType: precisionType,
value: value
});
}
}, {
key: "renderMarginAnswer",
value: function renderMarginAnswer(_ref6, index) {
var id = _ref6.id,
margin = _ref6.margin,
marginType = _ref6.marginType,
value = _ref6.value;
return /*#__PURE__*/_react["default"].createElement(_MarginOfError["default"], {
id: id,
locale: this.locale,
margin: margin,
marginType: marginType,
messages: this.props.getErrors("scoringData.value[".concat(index, "]"), {}),
numericTypeSelect: this.renderNumericTypeSelect(id, _constants.MARGIN_OF_ERROR),
onChange: this.handleChange,
value: value
});
}
}, {
key: "renderRangeAnswer",
value: function renderRangeAnswer(_ref7, index) {
var id = _ref7.id,
start = _ref7.start,
end = _ref7.end;
return /*#__PURE__*/_react["default"].createElement(_WithinARange["default"], {
end: end,
id: id,
locale: this.locale,
messages: this.props.getErrors("scoringData.value[".concat(index, "]"), {}),
numericTypeSelect: this.renderNumericTypeSelect(id, _constants.WITHIN_A_RANGE),
onChange: this.handleChange,
start: start
});
}
}, {
key: "renderNumericTypeSelect",
value: function renderNumericTypeSelect(id, value) {
return /*#__PURE__*/_react["default"].createElement(_NumericTypeSelect["default"], {
id: id,
inputRef: this.setTypeSelectInputRef,
onChange: this.handleTypeChange,
onClickHelp: this.openHelpModal,
value: value
});
}
}, {
key: "renderAnswerContents",
value: function renderAnswerContents(answer) {
var index = this.answers.findIndex(function (_ref8) {
var id = _ref8.id;
return id === answer.id;
});
console.assert(index !== -1); // eslint-disable-line no-console
switch (answer.type) {
case _constants.WITHIN_A_RANGE:
return this.renderRangeAnswer(answer, index);
case _constants.MARGIN_OF_ERROR:
return this.renderMarginAnswer(answer, index);
case _constants.PRECISE_RESPONSE:
return this.renderPrecisionAnswer(answer, index);
default:
return this.renderAnswerField(answer, index);
}
}
}, {
key: "renderOptionsDescription",
value: function renderOptionsDescription() {
return /*#__PURE__*/_react["default"].createElement(_uiA11yContent.ScreenReaderContent, null, (0, _formatMessage["default"])('Numeric options'));
}
}, {
key: "render",
value: function render() {
var name = "edit_interaction_".concat(this.props.itemId);
return /*#__PURE__*/_react["default"].createElement("div", null, /*#__PURE__*/_react["default"].createElement(_QuestionContainer["default"], {
disabled: this.props.overrideEditableForRegrading,
enableRichContentEditor: this.props.enableRichContentEditor,
itemBody: this.props.itemBody,
onDescriptionChange: this.props.onDescriptionChange,
onModalClose: this.props.onModalClose,
onModalOpen: this.props.onModalOpen,
openImportModal: this.props.openImportModal,
stemErrors: this.props.getErrors('itemBody'),
textareaRef: this.handleStemRef
}, /*#__PURE__*/_react["default"].createElement(_FocusGroup["default"], {
ref: this.handleChoicesFocusGroupRef,
asComponent: _Errors["default"],
asProps: {
errorList: this.props.getErrors('scoringData.errors')
}
}, /*#__PURE__*/_react["default"].createElement(_quizCommon.FormFieldGroup, {
vAlign: "bottom",
rowSpacing: "medium",
name: name,
description: /*#__PURE__*/_react["default"].createElement(_uiA11yContent.ScreenReaderContent, null, (0, _formatMessage["default"])('A list of possible answers'))
}, this.answers.map(this.renderAnswerWrapper))), /*#__PURE__*/_react["default"].createElement(_Footer["default"], {
buttonText: (0, _formatMessage["default"])('Possible Answer'),
onCreateChoice: this.handleCreateAnswer,
screenReaderText: (0, _formatMessage["default"])('Add Possible Answer'),
notifyScreenreader: this.props.notifyScreenreader,
automationData: "sdk-add-possible-answer-button"
})), /*#__PURE__*/_react["default"].createElement(_QuestionSettingsContainer["default"], {
additionalOptions: this.props.additionalOptions
}, this.props.showCalculatorOption && /*#__PURE__*/_react["default"].createElement(_QuestionSettingsPanel["default"], {
label: (0, _formatMessage["default"])('Options'),
defaultExpanded: true
}, /*#__PURE__*/_react["default"].createElement(_quizCommon.FormFieldGroup, {
rowSpacing: "small",
description: this.renderOptionsDescription()
}, /*#__PURE__*/_react["default"].createElement(_CalculatorOptionWithOqaatAlert["default"], {
disabled: this.props.overrideEditableForRegrading,
calculatorValue: this.props.calculatorType,
onCalculatorTypeChange: this.handleCalculatorTypeChange,
oqaatChecked: this.props.oneQuestionAtATime,
onOqaatChange: this.props.setOneQuestionAtATime
})))), /*#__PURE__*/_react["default"].createElement(_RequirementHelpModal["default"], {
onDismiss: this.closeHelpModal,
open: this.state.helpModalOpen
}));
}
}]);
}(_react.Component), (0, _defineProperty2["default"])(_NumericEdit, "interactionType", _numeric["default"]), (0, _defineProperty2["default"])(_NumericEdit, "propTypes", _objectSpread(_objectSpread({
additionalOptions: _QuestionSettingsContainer["default"].propTypes.additionalOptions,
calculatorType: _propTypes["default"].string,
changeItemState: _propTypes["default"].func,
enableRichContentEditor: _propTypes["default"].bool,
itemBody: _propTypes["default"].string.isRequired,
itemId: _propTypes["default"].string,
locale: _propTypes["default"].string,
newId: _propTypes["default"].func,
onModalClose: _propTypes["default"].func,
onModalOpen: _propTypes["default"].func,
oneQuestionAtATime: _propTypes["default"].bool,
openImportModal: _propTypes["default"].func,
overrideEditableForRegrading: _propTypes["default"].bool,
scoringData: _propTypes["default"].shape({
value: _propTypes["default"].arrayOf(_propTypes["default"].shape({
id: _propTypes["default"].string.isRequired,
type: _propTypes["default"].string.isRequired
})).isRequired
}).isRequired,
setOneQuestionAtATime: _propTypes["default"].func
}, _withEditTools["default"].injectedProps), {}, {
showCalculatorOption: _propTypes["default"].bool,
separatorConfig: _propTypes["default"].shape({
decimalSeparator: _propTypes["default"].string,
thousandSeparator: _propTypes["default"].string
})
})), (0, _defineProperty2["default"])(_NumericEdit, "contextType", _uiI18n.ApplyLocaleContext), (0, _defineProperty2["default"])(_NumericEdit, "defaultProps", {
calculatorType: 'none',
enableRichContentEditor: true,
oneQuestionAtATime: false,
overrideEditableForRegrading: false,
newId: _uuid.v4,
setOneQuestionAtATime: Function.prototype,
additionalOptions: void 0,
changeItemState: void 0,
itemId: void 0,
locale: void 0,
onModalClose: void 0,
onModalOpen: void 0,
openImportModal: void 0,
showCalculatorOption: true
}), _NumericEdit)) || _class;