@pie-lib/graphing 4.0.16 → 4.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [4.0.18](https://github.com/pie-framework/pie-lib/compare/@pie-lib/graphing@4.0.17...@pie-lib/graphing@4.0.18) (2026-08-26)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **graphing:** hold the focus on a new label and save it on blur PIE-681 ([26c5f98](https://github.com/pie-framework/pie-lib/commit/26c5f9875b45f4495d76827633f28d82d93d8cb9))
11
+
12
+ ## [4.0.17](https://github.com/pie-framework/pie-lib/compare/@pie-lib/graphing@4.0.16...@pie-lib/graphing@4.0.17) (2026-08-21)
13
+
14
+ ### Bug Fixes
15
+
16
+ - **graphing:** focus a new label even if its tool was remounted PIE-681 ([663177b](https://github.com/pie-framework/pie-lib/commit/663177b95aa73f4f53ee2aff31c3bde750435d6e))
17
+
6
18
  ## [4.0.16](https://github.com/pie-framework/pie-lib/compare/@pie-lib/graphing@4.0.15...@pie-lib/graphing@4.0.16) (2026-08-18)
7
19
 
8
20
  ### Bug Fixes
package/lib/mark-label.js CHANGED
@@ -20,6 +20,11 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
20
20
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
21
21
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
22
22
  var DEBOUNCE_DELAY = 500;
23
+ // A new label insists on the focus for this long: in the DNA env something moves the focus while the
24
+ // model save that creating the label triggered is handled, after the last render. Nothing typed yet
25
+ // is what tells that apart from the user leaving the label.
26
+ var INSIST_ON_FOCUS_FOR = 500;
27
+ var INSIST_AT = [0, 50, 120, 250, 400];
23
28
  var StyledInputCorrect = (0, _styles.styled)('div')(function (_ref2) {
24
29
  var theme = _ref2.theme;
25
30
  return {
@@ -168,13 +173,16 @@ LabelInput.propTypes = {
168
173
  onBlur: _propTypes["default"].func
169
174
  };
170
175
  var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
176
+ // the state repositions the label once the input has a size, the ref is never a render behind
177
+ var inputNode = (0, _react.useRef)(null);
171
178
  var _useState = (0, _react.useState)(null),
172
179
  _useState2 = (0, _slicedToArray2["default"])(_useState, 2),
173
180
  input = _useState2[0],
174
181
  setInput = _useState2[1];
175
182
  var _ref = (0, _react.useCallback)(function (node) {
176
- return setInput(node);
177
- });
183
+ inputNode.current = node;
184
+ setInput(node);
185
+ }, []);
178
186
  var theme = (0, _styles.useTheme)();
179
187
  var mark = props.mark,
180
188
  graphProps = props.graphProps,
@@ -189,26 +197,51 @@ var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
189
197
  correctnesslabel = mark.correctnesslabel,
190
198
  correctlabel = mark.correctlabel;
191
199
  var isFocused = function isFocused() {
192
- return !!input && typeof document !== 'undefined' && document.activeElement === input;
200
+ return !!inputNode.current && typeof document !== 'undefined' && document.activeElement === inputNode.current;
201
+ };
202
+
203
+ // an empty label with an enabled input is one the user just added: label mode has to be on and it
204
+ // always starts off. Read from the mark, not from autoFocus, because the tool that asked for the
205
+ // label can be remounted by the marks coming back from the host before the input renders.
206
+ var isNewLabel = (0, _react.useRef)(mark.label === '' && !disabled && !mark.disabled);
207
+ var insistUntil = (0, _react.useRef)(0);
208
+ var lastSaved = (0, _react.useRef)(mark.label);
209
+ var insisting = function insisting() {
210
+ return Date.now() < insistUntil.current && !(inputNode.current && inputNode.current.value);
193
211
  };
194
212
  var onChange = function onChange(e) {
195
213
  return setLabel(e.target.value);
196
214
  };
215
+ var saveLabel = function saveLabel(value) {
216
+ if (value === lastSaved.current) {
217
+ return;
218
+ }
219
+ lastSaved.current = value;
220
+ props.onChange(value);
221
+ };
197
222
  var handleBlur = (0, _react.useCallback)(function () {
223
+ // in the DNA env the focus is moved while the model is saved - not the user leaving the label
224
+ if (insisting()) {
225
+ return;
226
+ }
227
+ isNewLabel.current = false;
198
228
  if (label === '') {
199
229
  props.onChange('');
230
+ } else if (label !== mark.label) {
231
+ // the debounce can still be pending, and once the focus is gone an echo of the model would
232
+ // overwrite what was typed before it fires
233
+ saveLabel(label);
200
234
  }
201
235
 
202
236
  // lets the tool know that this label is not being edited anymore, so it stops auto focusing it
203
237
  if (props.onBlur) {
204
238
  props.onBlur();
205
239
  }
206
- }, [label, props.onChange, props.onBlur]);
240
+ }, [label, mark.label, props.onChange, props.onBlur]);
207
241
  var debouncedLabel = (0, _useDebounce.useDebounce)(label, DEBOUNCE_DELAY);
208
242
 
209
- // useState only sets the value once, to synch props to state need useEffect.
210
- // While this input has the focus we keep what the user typed: the mark can come back from an
211
- // (async) model save with an older label and that would overwrite the characters typed meanwhile.
243
+ // props to state, but not while focused: the mark can come back from an async model save with an
244
+ // older label and overwrite what was typed meanwhile
212
245
  (0, _react.useEffect)(function () {
213
246
  if (isFocused()) {
214
247
  return;
@@ -219,25 +252,54 @@ var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
219
252
  // pick up the change to debouncedLabel and save it
220
253
  (0, _react.useEffect)(function () {
221
254
  if (typeof debouncedLabel === 'string' && debouncedLabel !== mark.label) {
222
- props.onChange(debouncedLabel);
255
+ saveLabel(debouncedLabel);
223
256
  }
224
257
  }, [debouncedLabel]);
258
+ var focusInput = function focusInput() {
259
+ var node = inputNode.current;
225
260
 
226
- // A label that has just been added has to be focused so that it can be typed into right away.
227
- // This also re-focuses the input if it gets remounted while it is being edited - saving the model
228
- // is done by the host (api call) and the model that comes back can replace the input node.
229
- (0, _react.useEffect)(function () {
230
- if (!autoFocus || !input || disabled || isFocused()) {
261
+ // node.disabled, not the disabled prop: a disabled input silently ignores focus()
262
+ if (!autoFocus && !isNewLabel.current && !insisting() || !node || node.disabled || isFocused()) {
231
263
  return;
232
264
  }
233
- input.focus();
265
+ node.focus();
266
+
267
+ // a new label is focused once, so it cannot fight over the focus with another one
268
+ if (isFocused()) {
269
+ isNewLabel.current = false;
270
+ }
234
271
 
235
272
  // keep the caret at the end, otherwise typing continues in front of the existing label
236
- var caret = (input.value || '').length;
237
- if (input.setSelectionRange) {
238
- input.setSelectionRange(caret, caret);
273
+ var caret = (node.value || '').length;
274
+ if (node.setSelectionRange) {
275
+ node.setSelectionRange(caret, caret);
239
276
  }
240
- }, [autoFocus, input, disabled]);
277
+ };
278
+
279
+ // after every render: the input can appear a render or more later (the mark comes back from an
280
+ // async model save) and a replaced node loses the focus without this component being told
281
+ (0, _react.useEffect)(function () {
282
+ return focusInput();
283
+ });
284
+
285
+ // and again on a timer, for the DNA env, where the focus is moved once the renders are over. The
286
+ // original code focused from a setTimeout, which is what let it survive that.
287
+ (0, _react.useEffect)(function () {
288
+ if (!autoFocus && !isNewLabel.current) {
289
+ return;
290
+ }
291
+ if (!insistUntil.current) {
292
+ insistUntil.current = Date.now() + INSIST_ON_FOCUS_FOR;
293
+ }
294
+ var timers = INSIST_AT.map(function (delay) {
295
+ return setTimeout(focusInput, delay);
296
+ });
297
+ return function () {
298
+ return timers.forEach(function (t) {
299
+ return clearTimeout(t);
300
+ });
301
+ };
302
+ }, [autoFocus]);
241
303
  var rect = input ? input.getBoundingClientRect() : {
242
304
  width: 0,
243
305
  height: 0
@@ -301,6 +363,12 @@ var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
301
363
  style: style
302
364
  }, renderInput(studentInputStyle, label));
303
365
  }
366
+
367
+ // an empty label that cannot be edited is just an empty box - the config draws the background
368
+ // marks into every correct answer graph too
369
+ if (disabledInput && !label) {
370
+ return null;
371
+ }
304
372
  return /*#__PURE__*/_react["default"].createElement("div", {
305
373
  style: style
306
374
  }, renderInput(getInputStyles(theme, disabled, mark.disabled), label));
@@ -1 +1 @@
1
- {"version":3,"file":"mark-label.js","names":["_react","_interopRequireWildcard","require","_propTypes","_interopRequireDefault","_styles","_reactInputAutosize","_useDebounce","_plot","_renderUi","_labelSvgIcon","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","_typeof","has","get","set","_t","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","ownKeys","keys","getOwnPropertySymbols","filter","enumerable","push","apply","_objectSpread","arguments","length","forEach","_defineProperty2","getOwnPropertyDescriptors","defineProperties","DEBOUNCE_DELAY","StyledInputCorrect","styled","_ref2","theme","padding","spacing","borderRadius","fontSize","backgroundColor","color","defaults","WHITE","CORRECT_WITH_ICON","border","concat","StyledInputIncorrect","_ref3","INCORRECT_WITH_ICON","StyledInputMissing","_ref4","MISSING_WITH_ICON","fontWeight","StyledIncorrect","getInputStyles","disabled","markDisabled","fontFamily","typography","PRIMARY_DARK","SECONDARY","WebkitOpacity","undefined","WebkitTextFillColor","getStudentInputStyles","position","exports","graphProps","mark","rect","width","height","scale","domain","range","shift","rightEdge","x","bottomEdge","y","h","max","v","min","coordinates","left","top","LabelInput","_ref5","_ref","externalInputRef","label","inputStyle","onChange","onBlur","createElement","inputRef","value","propTypes","PropTypes","func","string","bool","object","MarkLabel","props","_useState","useState","_useState2","_slicedToArray2","input","setInput","useCallback","node","useTheme","autoFocus","_useState3","_useState4","setLabel","correctness","correctnesslabel","correctlabel","isFocused","document","activeElement","target","handleBlur","debouncedLabel","useDebounce","useEffect","focus","caret","setSelectionRange","getBoundingClientRect","pos","leftTop","style","pointerEvents","display","alignItems","gap","secondLabelStyle","disabledInput","renderInput","labelValue","studentInputStyle","type","Fragment","marginLeft","types","GraphPropsType","_default"],"sources":["../src/mark-label.jsx"],"sourcesContent":["import React, { useCallback, useEffect, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { styled, useTheme } from '@mui/material/styles';\nimport AutosizeInput from 'react-input-autosize';\nimport { useDebounce } from './use-debounce';\nimport { types } from '@pie-lib/plot';\nimport { color } from '@pie-lib/render-ui';\nimport SvgIcon from './label-svg-icon';\n\nconst DEBOUNCE_DELAY = 500;\n\nconst StyledInputCorrect = styled('div')(({ theme }) => ({\n float: 'right',\n padding: theme.spacing(0.5),\n borderRadius: '4px',\n fontSize: '10px',\n backgroundColor: color.defaults.WHITE,\n color: color.defaults.CORRECT_WITH_ICON,\n border: `solid 1px ${color.defaults.CORRECT_WITH_ICON}`,\n}));\n\nconst StyledInputIncorrect = styled('div')(({ theme }) => ({\n float: 'right',\n padding: theme.spacing(0.5),\n borderRadius: '4px',\n fontSize: '10px',\n backgroundColor: color.defaults.WHITE,\n color: color.defaults.INCORRECT_WITH_ICON,\n border: `solid 1px ${color.defaults.INCORRECT_WITH_ICON}`,\n}));\n\nconst StyledInputMissing = styled('div')(({ theme }) => ({\n float: 'right',\n padding: theme.spacing(0.5),\n borderRadius: '4px',\n fontSize: '10px',\n backgroundColor: color.defaults.WHITE,\n color: color.defaults.MISSING_WITH_ICON,\n border: `solid 1px ${color.defaults.MISSING_WITH_ICON}`,\n fontWeight: 'bold',\n}));\n\nconst StyledIncorrect = styled('div')(() => ({\n float: 'right',\n padding: 0,\n borderRadius: '4px',\n fontSize: '10px',\n backgroundColor: color.defaults.WHITE,\n color: color.defaults.INCORRECT_WITH_ICON,\n fontWeight: 'bold',\n}));\n\nconst getInputStyles = (theme, disabled, markDisabled) => ({\n float: 'right',\n padding: theme.spacing(0.5),\n fontFamily: theme.typography.fontFamily,\n fontSize: '10px',\n border: disabled\n ? `solid 1px ${color.defaults.PRIMARY_DARK}`\n : markDisabled\n ? `solid 1px ${color.disabled()}`\n : `solid 1px ${color.defaults.SECONDARY}`,\n borderRadius: '3px',\n color: markDisabled ? color.disabled() : color.defaults.PRIMARY_DARK,\n backgroundColor: color.defaults.WHITE,\n WebkitOpacity: disabled ? '1' : undefined,\n WebkitTextFillColor: markDisabled ? color.disabled() : undefined,\n});\n\nconst getStudentInputStyles = () => ({\n padding: '0',\n border: 'none',\n color: 'inherit',\n fontWeight: 'bold',\n});\n\nexport const position = (graphProps, mark, rect = { width: 0, height: 0 }) => {\n const { scale, domain, range } = graphProps;\n const shift = 5;\n\n const rightEdge = scale.x(mark.x) + rect.width + shift;\n const bottomEdge = scale.y(mark.y) + rect.height + shift;\n\n const h = rightEdge >= scale.x(domain.max) ? 'left' : 'right';\n const v = bottomEdge >= scale.y(range.min) ? 'top' : 'bottom';\n\n return `${v}-${h}`;\n};\n\nexport const coordinates = (graphProps, mark, rect = { width: 0, height: 0 }, position) => {\n const { scale } = graphProps;\n const shift = 5;\n\n switch (position) {\n case 'bottom-right':\n return { left: scale.x(mark.x) + shift, top: scale.y(mark.y) + shift };\n case 'bottom-left':\n return { left: scale.x(mark.x) - shift - rect.width, top: scale.y(mark.y) + shift };\n case 'top-left':\n return { left: scale.x(mark.x) - shift - rect.width, top: scale.y(mark.y) - shift - rect.height };\n case 'top-right':\n return { left: scale.x(mark.x) + shift, top: scale.y(mark.y) - shift - rect.height };\n default:\n return {};\n }\n};\n\nconst LabelInput = ({ _ref, externalInputRef, label, disabled, inputStyle, onChange, onBlur }) => (\n <AutosizeInput\n inputRef={(r) => {\n _ref(r);\n externalInputRef(r);\n }}\n disabled={disabled}\n inputStyle={inputStyle}\n value={label}\n onChange={onChange}\n onBlur={onBlur}\n />\n);\n\nLabelInput.propTypes = {\n _ref: PropTypes.func,\n externalInputRef: PropTypes.func,\n label: PropTypes.string,\n disabled: PropTypes.bool,\n inputStyle: PropTypes.object,\n onChange: PropTypes.func,\n onBlur: PropTypes.func,\n};\n\nexport const MarkLabel = (props) => {\n const [input, setInput] = useState(null);\n const _ref = useCallback((node) => setInput(node));\n const theme = useTheme();\n\n const { mark, graphProps, disabled, autoFocus, inputRef: externalInputRef } = props;\n\n const [label, setLabel] = useState(mark.label);\n const { correctness, correctnesslabel, correctlabel } = mark;\n\n const isFocused = () => !!input && typeof document !== 'undefined' && document.activeElement === input;\n\n const onChange = (e) => setLabel(e.target.value);\n\n const handleBlur = useCallback(() => {\n if (label === '') {\n props.onChange('');\n }\n\n // lets the tool know that this label is not being edited anymore, so it stops auto focusing it\n if (props.onBlur) {\n props.onBlur();\n }\n }, [label, props.onChange, props.onBlur]);\n\n const debouncedLabel = useDebounce(label, DEBOUNCE_DELAY);\n\n // useState only sets the value once, to synch props to state need useEffect.\n // While this input has the focus we keep what the user typed: the mark can come back from an\n // (async) model save with an older label and that would overwrite the characters typed meanwhile.\n useEffect(() => {\n if (isFocused()) {\n return;\n }\n\n setLabel(mark.label);\n }, [mark.label]);\n\n // pick up the change to debouncedLabel and save it\n useEffect(() => {\n if (typeof debouncedLabel === 'string' && debouncedLabel !== mark.label) {\n props.onChange(debouncedLabel);\n }\n }, [debouncedLabel]);\n\n // A label that has just been added has to be focused so that it can be typed into right away.\n // This also re-focuses the input if it gets remounted while it is being edited - saving the model\n // is done by the host (api call) and the model that comes back can replace the input node.\n useEffect(() => {\n if (!autoFocus || !input || disabled || isFocused()) {\n return;\n }\n\n input.focus();\n\n // keep the caret at the end, otherwise typing continues in front of the existing label\n const caret = (input.value || '').length;\n\n if (input.setSelectionRange) {\n input.setSelectionRange(caret, caret);\n }\n }, [autoFocus, input, disabled]);\n\n const rect = input ? input.getBoundingClientRect() : { width: 0, height: 0 };\n const pos = position(graphProps, mark, rect);\n const leftTop = coordinates(graphProps, mark, rect, pos);\n\n const style = {\n position: 'fixed',\n pointerEvents: 'auto',\n display: 'flex',\n alignItems: 'center',\n gap: '3px',\n ...leftTop,\n };\n\n const secondLabelStyle = {\n ...style,\n top: leftTop.top + 25,\n };\n\n const disabledInput = disabled || mark.disabled;\n\n const renderInput = (inputStyle, labelValue) => (\n <LabelInput\n _ref={_ref}\n externalInputRef={externalInputRef}\n label={labelValue}\n disabled={disabledInput}\n inputStyle={inputStyle}\n onChange={onChange}\n onBlur={handleBlur}\n />\n );\n\n const studentInputStyle = getStudentInputStyles();\n\n if (correctness === 'correct' && correctnesslabel === 'correct' && correctlabel) {\n return (\n <StyledInputCorrect style={style}>\n <SvgIcon type=\"correct\" />\n {renderInput(studentInputStyle, correctlabel)}\n </StyledInputCorrect>\n );\n }\n\n if (correctness === 'correct' && correctnesslabel === 'correct' && !correctlabel) {\n return null;\n }\n\n if (correctness === 'correct' && correctnesslabel === 'incorrect') {\n return (\n <>\n <StyledInputIncorrect style={style}>\n <SvgIcon type=\"incorrect\" />\n {label === '' ? (\n <SvgIcon type=\"empty\" style={{ marginLeft: '3px' }} />\n ) : (\n renderInput(studentInputStyle, label)\n )}\n </StyledInputIncorrect>\n <StyledInputMissing style={secondLabelStyle}>{renderInput(studentInputStyle, correctlabel)}</StyledInputMissing>\n </>\n );\n }\n\n if (correctness === 'missing') {\n return <StyledInputMissing style={style}>{renderInput(studentInputStyle, label)}</StyledInputMissing>;\n }\n\n if (correctness === 'incorrect') {\n return <StyledIncorrect style={style}>{renderInput(studentInputStyle, label)}</StyledIncorrect>;\n }\n\n return <div style={style}>{renderInput(getInputStyles(theme, disabled, mark.disabled), label)}</div>;\n};\n\nMarkLabel.propTypes = {\n autoFocus: PropTypes.bool,\n disabled: PropTypes.bool,\n onBlur: PropTypes.func,\n onChange: PropTypes.func,\n graphProps: types.GraphPropsType,\n inputRef: PropTypes.func,\n mark: PropTypes.object,\n};\n\nexport default MarkLabel;\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"mark-label.js","names":["_react","_interopRequireWildcard","require","_propTypes","_interopRequireDefault","_styles","_reactInputAutosize","_useDebounce","_plot","_renderUi","_labelSvgIcon","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","_typeof","has","get","set","_t","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","ownKeys","keys","getOwnPropertySymbols","filter","enumerable","push","apply","_objectSpread","arguments","length","forEach","_defineProperty2","getOwnPropertyDescriptors","defineProperties","DEBOUNCE_DELAY","INSIST_ON_FOCUS_FOR","INSIST_AT","StyledInputCorrect","styled","_ref2","theme","padding","spacing","borderRadius","fontSize","backgroundColor","color","defaults","WHITE","CORRECT_WITH_ICON","border","concat","StyledInputIncorrect","_ref3","INCORRECT_WITH_ICON","StyledInputMissing","_ref4","MISSING_WITH_ICON","fontWeight","StyledIncorrect","getInputStyles","disabled","markDisabled","fontFamily","typography","PRIMARY_DARK","SECONDARY","WebkitOpacity","undefined","WebkitTextFillColor","getStudentInputStyles","position","exports","graphProps","mark","rect","width","height","scale","domain","range","shift","rightEdge","x","bottomEdge","y","h","max","v","min","coordinates","left","top","LabelInput","_ref5","_ref","externalInputRef","label","inputStyle","onChange","onBlur","createElement","inputRef","value","propTypes","PropTypes","func","string","bool","object","MarkLabel","props","inputNode","useRef","_useState","useState","_useState2","_slicedToArray2","input","setInput","useCallback","node","current","useTheme","autoFocus","_useState3","_useState4","setLabel","correctness","correctnesslabel","correctlabel","isFocused","document","activeElement","isNewLabel","insistUntil","lastSaved","insisting","Date","now","target","saveLabel","handleBlur","debouncedLabel","useDebounce","useEffect","focusInput","focus","caret","setSelectionRange","timers","map","delay","setTimeout","clearTimeout","getBoundingClientRect","pos","leftTop","style","pointerEvents","display","alignItems","gap","secondLabelStyle","disabledInput","renderInput","labelValue","studentInputStyle","type","Fragment","marginLeft","types","GraphPropsType","_default"],"sources":["../src/mark-label.jsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { styled, useTheme } from '@mui/material/styles';\nimport AutosizeInput from 'react-input-autosize';\nimport { useDebounce } from './use-debounce';\nimport { types } from '@pie-lib/plot';\nimport { color } from '@pie-lib/render-ui';\nimport SvgIcon from './label-svg-icon';\n\nconst DEBOUNCE_DELAY = 500;\n// A new label insists on the focus for this long: in the DNA env something moves the focus while the\n// model save that creating the label triggered is handled, after the last render. Nothing typed yet\n// is what tells that apart from the user leaving the label.\nconst INSIST_ON_FOCUS_FOR = 500;\nconst INSIST_AT = [0, 50, 120, 250, 400];\n\nconst StyledInputCorrect = styled('div')(({ theme }) => ({\n float: 'right',\n padding: theme.spacing(0.5),\n borderRadius: '4px',\n fontSize: '10px',\n backgroundColor: color.defaults.WHITE,\n color: color.defaults.CORRECT_WITH_ICON,\n border: `solid 1px ${color.defaults.CORRECT_WITH_ICON}`,\n}));\n\nconst StyledInputIncorrect = styled('div')(({ theme }) => ({\n float: 'right',\n padding: theme.spacing(0.5),\n borderRadius: '4px',\n fontSize: '10px',\n backgroundColor: color.defaults.WHITE,\n color: color.defaults.INCORRECT_WITH_ICON,\n border: `solid 1px ${color.defaults.INCORRECT_WITH_ICON}`,\n}));\n\nconst StyledInputMissing = styled('div')(({ theme }) => ({\n float: 'right',\n padding: theme.spacing(0.5),\n borderRadius: '4px',\n fontSize: '10px',\n backgroundColor: color.defaults.WHITE,\n color: color.defaults.MISSING_WITH_ICON,\n border: `solid 1px ${color.defaults.MISSING_WITH_ICON}`,\n fontWeight: 'bold',\n}));\n\nconst StyledIncorrect = styled('div')(() => ({\n float: 'right',\n padding: 0,\n borderRadius: '4px',\n fontSize: '10px',\n backgroundColor: color.defaults.WHITE,\n color: color.defaults.INCORRECT_WITH_ICON,\n fontWeight: 'bold',\n}));\n\nconst getInputStyles = (theme, disabled, markDisabled) => ({\n float: 'right',\n padding: theme.spacing(0.5),\n fontFamily: theme.typography.fontFamily,\n fontSize: '10px',\n border: disabled\n ? `solid 1px ${color.defaults.PRIMARY_DARK}`\n : markDisabled\n ? `solid 1px ${color.disabled()}`\n : `solid 1px ${color.defaults.SECONDARY}`,\n borderRadius: '3px',\n color: markDisabled ? color.disabled() : color.defaults.PRIMARY_DARK,\n backgroundColor: color.defaults.WHITE,\n WebkitOpacity: disabled ? '1' : undefined,\n WebkitTextFillColor: markDisabled ? color.disabled() : undefined,\n});\n\nconst getStudentInputStyles = () => ({\n padding: '0',\n border: 'none',\n color: 'inherit',\n fontWeight: 'bold',\n});\n\nexport const position = (graphProps, mark, rect = { width: 0, height: 0 }) => {\n const { scale, domain, range } = graphProps;\n const shift = 5;\n\n const rightEdge = scale.x(mark.x) + rect.width + shift;\n const bottomEdge = scale.y(mark.y) + rect.height + shift;\n\n const h = rightEdge >= scale.x(domain.max) ? 'left' : 'right';\n const v = bottomEdge >= scale.y(range.min) ? 'top' : 'bottom';\n\n return `${v}-${h}`;\n};\n\nexport const coordinates = (graphProps, mark, rect = { width: 0, height: 0 }, position) => {\n const { scale } = graphProps;\n const shift = 5;\n\n switch (position) {\n case 'bottom-right':\n return { left: scale.x(mark.x) + shift, top: scale.y(mark.y) + shift };\n case 'bottom-left':\n return { left: scale.x(mark.x) - shift - rect.width, top: scale.y(mark.y) + shift };\n case 'top-left':\n return { left: scale.x(mark.x) - shift - rect.width, top: scale.y(mark.y) - shift - rect.height };\n case 'top-right':\n return { left: scale.x(mark.x) + shift, top: scale.y(mark.y) - shift - rect.height };\n default:\n return {};\n }\n};\n\nconst LabelInput = ({ _ref, externalInputRef, label, disabled, inputStyle, onChange, onBlur }) => (\n <AutosizeInput\n inputRef={(r) => {\n _ref(r);\n externalInputRef(r);\n }}\n disabled={disabled}\n inputStyle={inputStyle}\n value={label}\n onChange={onChange}\n onBlur={onBlur}\n />\n);\n\nLabelInput.propTypes = {\n _ref: PropTypes.func,\n externalInputRef: PropTypes.func,\n label: PropTypes.string,\n disabled: PropTypes.bool,\n inputStyle: PropTypes.object,\n onChange: PropTypes.func,\n onBlur: PropTypes.func,\n};\n\nexport const MarkLabel = (props) => {\n // the state repositions the label once the input has a size, the ref is never a render behind\n const inputNode = useRef(null);\n const [input, setInput] = useState(null);\n const _ref = useCallback((node) => {\n inputNode.current = node;\n setInput(node);\n }, []);\n const theme = useTheme();\n\n const { mark, graphProps, disabled, autoFocus, inputRef: externalInputRef } = props;\n\n const [label, setLabel] = useState(mark.label);\n const { correctness, correctnesslabel, correctlabel } = mark;\n\n const isFocused = () =>\n !!inputNode.current && typeof document !== 'undefined' && document.activeElement === inputNode.current;\n\n // an empty label with an enabled input is one the user just added: label mode has to be on and it\n // always starts off. Read from the mark, not from autoFocus, because the tool that asked for the\n // label can be remounted by the marks coming back from the host before the input renders.\n const isNewLabel = useRef(mark.label === '' && !disabled && !mark.disabled);\n const insistUntil = useRef(0);\n const lastSaved = useRef(mark.label);\n const insisting = () => Date.now() < insistUntil.current && !(inputNode.current && inputNode.current.value);\n\n const onChange = (e) => setLabel(e.target.value);\n\n const saveLabel = (value) => {\n if (value === lastSaved.current) {\n return;\n }\n\n lastSaved.current = value;\n props.onChange(value);\n };\n\n const handleBlur = useCallback(() => {\n // in the DNA env the focus is moved while the model is saved - not the user leaving the label\n if (insisting()) {\n return;\n }\n\n isNewLabel.current = false;\n\n if (label === '') {\n props.onChange('');\n } else if (label !== mark.label) {\n // the debounce can still be pending, and once the focus is gone an echo of the model would\n // overwrite what was typed before it fires\n saveLabel(label);\n }\n\n // lets the tool know that this label is not being edited anymore, so it stops auto focusing it\n if (props.onBlur) {\n props.onBlur();\n }\n }, [label, mark.label, props.onChange, props.onBlur]);\n\n const debouncedLabel = useDebounce(label, DEBOUNCE_DELAY);\n\n // props to state, but not while focused: the mark can come back from an async model save with an\n // older label and overwrite what was typed meanwhile\n useEffect(() => {\n if (isFocused()) {\n return;\n }\n\n setLabel(mark.label);\n }, [mark.label]);\n\n // pick up the change to debouncedLabel and save it\n useEffect(() => {\n if (typeof debouncedLabel === 'string' && debouncedLabel !== mark.label) {\n saveLabel(debouncedLabel);\n }\n }, [debouncedLabel]);\n\n const focusInput = () => {\n const node = inputNode.current;\n\n // node.disabled, not the disabled prop: a disabled input silently ignores focus()\n if ((!autoFocus && !isNewLabel.current && !insisting()) || !node || node.disabled || isFocused()) {\n return;\n }\n\n node.focus();\n\n // a new label is focused once, so it cannot fight over the focus with another one\n if (isFocused()) {\n isNewLabel.current = false;\n }\n\n // keep the caret at the end, otherwise typing continues in front of the existing label\n const caret = (node.value || '').length;\n\n if (node.setSelectionRange) {\n node.setSelectionRange(caret, caret);\n }\n };\n\n // after every render: the input can appear a render or more later (the mark comes back from an\n // async model save) and a replaced node loses the focus without this component being told\n useEffect(() => focusInput());\n\n // and again on a timer, for the DNA env, where the focus is moved once the renders are over. The\n // original code focused from a setTimeout, which is what let it survive that.\n useEffect(() => {\n if (!autoFocus && !isNewLabel.current) {\n return;\n }\n\n if (!insistUntil.current) {\n insistUntil.current = Date.now() + INSIST_ON_FOCUS_FOR;\n }\n\n const timers = INSIST_AT.map((delay) => setTimeout(focusInput, delay));\n\n return () => timers.forEach((t) => clearTimeout(t));\n }, [autoFocus]);\n\n const rect = input ? input.getBoundingClientRect() : { width: 0, height: 0 };\n const pos = position(graphProps, mark, rect);\n const leftTop = coordinates(graphProps, mark, rect, pos);\n\n const style = {\n position: 'fixed',\n pointerEvents: 'auto',\n display: 'flex',\n alignItems: 'center',\n gap: '3px',\n ...leftTop,\n };\n\n const secondLabelStyle = {\n ...style,\n top: leftTop.top + 25,\n };\n\n const disabledInput = disabled || mark.disabled;\n\n const renderInput = (inputStyle, labelValue) => (\n <LabelInput\n _ref={_ref}\n externalInputRef={externalInputRef}\n label={labelValue}\n disabled={disabledInput}\n inputStyle={inputStyle}\n onChange={onChange}\n onBlur={handleBlur}\n />\n );\n\n const studentInputStyle = getStudentInputStyles();\n\n if (correctness === 'correct' && correctnesslabel === 'correct' && correctlabel) {\n return (\n <StyledInputCorrect style={style}>\n <SvgIcon type=\"correct\" />\n {renderInput(studentInputStyle, correctlabel)}\n </StyledInputCorrect>\n );\n }\n\n if (correctness === 'correct' && correctnesslabel === 'correct' && !correctlabel) {\n return null;\n }\n\n if (correctness === 'correct' && correctnesslabel === 'incorrect') {\n return (\n <>\n <StyledInputIncorrect style={style}>\n <SvgIcon type=\"incorrect\" />\n {label === '' ? (\n <SvgIcon type=\"empty\" style={{ marginLeft: '3px' }} />\n ) : (\n renderInput(studentInputStyle, label)\n )}\n </StyledInputIncorrect>\n <StyledInputMissing style={secondLabelStyle}>{renderInput(studentInputStyle, correctlabel)}</StyledInputMissing>\n </>\n );\n }\n\n if (correctness === 'missing') {\n return <StyledInputMissing style={style}>{renderInput(studentInputStyle, label)}</StyledInputMissing>;\n }\n\n if (correctness === 'incorrect') {\n return <StyledIncorrect style={style}>{renderInput(studentInputStyle, label)}</StyledIncorrect>;\n }\n\n // an empty label that cannot be edited is just an empty box - the config draws the background\n // marks into every correct answer graph too\n if (disabledInput && !label) {\n return null;\n }\n\n return <div style={style}>{renderInput(getInputStyles(theme, disabled, mark.disabled), label)}</div>;\n};\n\nMarkLabel.propTypes = {\n autoFocus: PropTypes.bool,\n disabled: PropTypes.bool,\n onBlur: PropTypes.func,\n onChange: PropTypes.func,\n graphProps: types.GraphPropsType,\n inputRef: PropTypes.func,\n mark: PropTypes.object,\n};\n\nexport default MarkLabel;\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "4.0.16",
6
+ "version": "4.0.18",
7
7
  "description": "Graphing components",
8
8
  "keywords": [
9
9
  "react",
@@ -22,11 +22,11 @@
22
22
  "@mapbox/point-geometry": "^1.1.0",
23
23
  "@mui/icons-material": "^7.3.4",
24
24
  "@mui/material": "^7.3.4",
25
- "@pie-lib/drag": "^4.1.0",
26
- "@pie-lib/editable-html-tip-tap": "^2.1.13",
25
+ "@pie-lib/drag": "^4.1.2",
26
+ "@pie-lib/editable-html-tip-tap": "^2.1.15",
27
27
  "@pie-lib/graphing-utils": "^3.0.2",
28
- "@pie-lib/plot": "^4.0.15",
29
- "@pie-lib/render-ui": "^6.1.3",
28
+ "@pie-lib/plot": "^4.0.17",
29
+ "@pie-lib/render-ui": "^7.0.1",
30
30
  "@pie-lib/translator": "^4.0.2",
31
31
  "@visx/axis": "^3.0.0",
32
32
  "@visx/clip-path": "^3.0.0",
@@ -54,6 +54,6 @@
54
54
  "peerDependencies": {
55
55
  "react": "^18.2.0"
56
56
  },
57
- "gitHead": "bb18a8752144b330cc74d49eac15d250c74fb541",
57
+ "gitHead": "38176e40de37e31fc3bf4edb8669cf2238e910d3",
58
58
  "scripts": {}
59
59
  }
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { render } from '@pie-lib/test-utils';
2
+ import { act, fireEvent, render } from '@pie-lib/test-utils';
3
3
  import { graphProps } from './utils';
4
4
  import Graph, { removeBuildingToolIfCurrentToolDiffers } from '../graph';
5
5
  import { toolsArr } from '../tools';
@@ -719,3 +719,78 @@ describe('Graph', () => {
719
719
  });
720
720
  });
721
721
  });
722
+
723
+ describe('label focus', () => {
724
+ // the marks are owned by the host and come back from a model save asynchronously; a mark that
725
+ // shows up in front of the labelled one changes its key, so the tool that handled the click is
726
+ // remounted and the editingLabel it set is gone before the label input ever renders
727
+ const Host = ({ shiftMarks }) => {
728
+ const [marks, setMarks] = React.useState([{ type: 'point', x: 2, y: 2 }]);
729
+
730
+ return (
731
+ <Graph
732
+ tools={toolsArr}
733
+ marks={marks}
734
+ labelModeEnabled={true}
735
+ onChangeMarks={(next) =>
736
+ setTimeout(() => setMarks(shiftMarks ? [{ type: 'point', x: 5, y: 5 }, ...next] : next), 30)
737
+ }
738
+ domain={{ min: 0, max: 10, step: 1 }}
739
+ range={{ min: 0, max: 10, step: 1 }}
740
+ size={{ width: 400, height: 400 }}
741
+ {...graphProps(0, 10, 0, 10)}
742
+ />
743
+ );
744
+ };
745
+
746
+ const clickPoint = (container) => {
747
+ const circle = container.querySelector('circle');
748
+
749
+ fireEvent.mouseDown(circle, { button: 0 });
750
+ fireEvent.mouseUp(circle, { button: 0 });
751
+ fireEvent.click(circle, { button: 0 });
752
+ act(() => jest.advanceTimersByTime(200));
753
+ };
754
+
755
+ beforeEach(() => jest.useFakeTimers());
756
+ afterEach(() => jest.useRealTimers());
757
+
758
+ it('focuses the label that was just added', () => {
759
+ const { container } = render(<Host />);
760
+
761
+ clickPoint(container);
762
+
763
+ const input = container.querySelector('foreignObject input');
764
+ expect(input).not.toBe(null);
765
+ expect(document.activeElement).toBe(input);
766
+ });
767
+
768
+ // in the DNA env the focus is moved while the model save is handled, after the last render
769
+ it('takes the focus back when something steals it while the model is saved', () => {
770
+ const { container } = render(<Host />);
771
+
772
+ clickPoint(container);
773
+ const input = container.querySelector('foreignObject input');
774
+ expect(document.activeElement).toBe(input);
775
+
776
+ const elsewhere = document.createElement('input');
777
+ document.body.appendChild(elsewhere);
778
+ elsewhere.focus();
779
+ expect(document.activeElement).toBe(elsewhere);
780
+
781
+ act(() => jest.advanceTimersByTime(300));
782
+
783
+ expect(document.activeElement).toBe(input);
784
+ elsewhere.remove();
785
+ });
786
+
787
+ it('focuses the label even if the tool was remounted before it rendered', () => {
788
+ const { container } = render(<Host shiftMarks={true} />);
789
+
790
+ clickPoint(container);
791
+
792
+ const input = container.querySelector('foreignObject input');
793
+ expect(input).not.toBe(null);
794
+ expect(document.activeElement).toBe(input);
795
+ });
796
+ });
@@ -75,18 +75,50 @@ describe('MarkLabel - editing', () => {
75
75
  expect(input.selectionEnd).toBe(2);
76
76
  });
77
77
 
78
- it('does not focus the input when autoFocus is not set', () => {
79
- const { input } = renderComponent();
78
+ it('does not focus the input of an existing label when autoFocus is not set', () => {
79
+ const { input } = renderComponent({ mark: { x: 1, y: 1, label: 'A' } });
80
80
 
81
81
  expect(document.activeElement).not.toBe(input);
82
82
  });
83
83
 
84
84
  it('does not focus the input when disabled', () => {
85
- const { input } = renderComponent({ autoFocus: true, disabled: true });
85
+ const { input } = renderComponent({ autoFocus: true, disabled: true, mark: { x: 1, y: 1, label: 'A' } });
86
+
87
+ expect(document.activeElement).not.toBe(input);
88
+ });
89
+
90
+ it('does not focus the input of a disabled mark', () => {
91
+ const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'A', disabled: true } });
86
92
 
87
93
  expect(document.activeElement).not.toBe(input);
88
94
  });
89
95
 
96
+ // the focus can go away without this component hearing about it - the input node gets replaced
97
+ // by a render higher up, so no blur is fired and autoFocus is still on
98
+ it('takes the focus back on the next render while it is still being edited', () => {
99
+ const { input, rerenderWith } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'A' } });
100
+
101
+ expect(document.activeElement).toBe(input);
102
+
103
+ input.blur();
104
+ expect(document.activeElement).not.toBe(input);
105
+
106
+ rerenderWith({ mark: { x: 1, y: 1, label: 'A' } });
107
+
108
+ expect(document.activeElement).toBe(input);
109
+ });
110
+
111
+ it('leaves the caret where the user put it while the input has the focus', () => {
112
+ const { input, rerenderWith } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'ABC' } });
113
+
114
+ input.setSelectionRange(1, 1);
115
+
116
+ rerenderWith({ mark: { x: 1, y: 1, label: 'ABC' } });
117
+
118
+ expect(document.activeElement).toBe(input);
119
+ expect(input.selectionStart).toBe(1);
120
+ });
121
+
90
122
  it('focuses the input once it is no longer disabled', () => {
91
123
  const { rerenderWith, container } = renderComponent({ autoFocus: true, disabled: true });
92
124
 
@@ -118,6 +150,76 @@ describe('MarkLabel - editing', () => {
118
150
  });
119
151
  });
120
152
 
153
+ // the tool asks for the focus with autoFocus, but its state can be gone before the input renders:
154
+ // the marks come back from the host asynchronously and can remount the tool. An empty label in
155
+ // label mode is a label the user has just added, so the input can tell on its own.
156
+ describe('a label that was just added', () => {
157
+ it('focuses itself without the tool asking', () => {
158
+ const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
159
+
160
+ expect(document.activeElement).toBe(input);
161
+ });
162
+
163
+ it('focuses itself again when it is remounted before it was typed into', () => {
164
+ const props = {
165
+ onChange,
166
+ onBlur,
167
+ inputRef: jest.fn(),
168
+ mark: { x: 1, y: 1, label: '' },
169
+ graphProps: getGraphProps(0, 10, 0, 10),
170
+ };
171
+ const { container, rerender } = render(<MarkLabel key="first" {...props} />);
172
+ const first = input(container);
173
+
174
+ rerender(<MarkLabel key="second" {...props} />);
175
+ const second = input(container);
176
+
177
+ expect(second).not.toBe(first);
178
+ expect(document.activeElement).toBe(second);
179
+ });
180
+
181
+ it('stops chasing the focus once it has been left', () => {
182
+ const { input, rerenderWith } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
183
+
184
+ expect(document.activeElement).toBe(input);
185
+
186
+ input.blur();
187
+ rerenderWith({ mark: { x: 1, y: 1, label: '' } });
188
+
189
+ expect(document.activeElement).not.toBe(input);
190
+ });
191
+
192
+ // outside of label mode the input is disabled, which is how an empty label that was loaded with
193
+ // the item is told apart from one the user has just added - label mode always starts off
194
+ // the config draws the background marks into the correct answer graphs as disabled marks
195
+ it('is not rendered at all while it cannot be edited', () => {
196
+ const { container } = renderComponent({ disabled: true, mark: { x: 1, y: 1, label: '' } });
197
+
198
+ expect(container.querySelector('input')).toBe(null);
199
+ });
200
+
201
+ it('is still rendered uneditable once it has text', () => {
202
+ const { container } = renderComponent({ disabled: true, mark: { x: 1, y: 1, label: 'A' } });
203
+
204
+ expect(container.querySelector('input').value).toBe('A');
205
+ });
206
+
207
+ it('is not focused when label mode is turned on afterwards', () => {
208
+ const { rerenderWith, container } = renderComponent({ disabled: true, mark: { x: 1, y: 1, label: '' } });
209
+
210
+ rerenderWith({ disabled: false });
211
+
212
+ expect(input(container)).not.toBe(null);
213
+ expect(document.activeElement).not.toBe(input(container));
214
+ });
215
+
216
+ it('is not rendered for a disabled mark while it is empty', () => {
217
+ const { container } = renderComponent({ mark: { x: 1, y: 1, label: '', disabled: true } });
218
+
219
+ expect(container.querySelector('input')).toBe(null);
220
+ });
221
+ });
222
+
121
223
  describe('syncing mark.label into the input', () => {
122
224
  it('picks up a new mark.label while the input does not have the focus', () => {
123
225
  const { rerenderWith, container } = renderComponent({ mark: { x: 1, y: 1, label: 'A' } });
@@ -153,6 +255,29 @@ describe('MarkLabel - editing', () => {
153
255
  expect(onBlur).toHaveBeenCalled();
154
256
  });
155
257
 
258
+ // clicking a button or anything else interactive blurs the label while the debounce is pending
259
+ it('saves what was typed straight away', () => {
260
+ const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
261
+
262
+ fireEvent.change(input, { target: { value: 'AB' } });
263
+ fireEvent.blur(input);
264
+
265
+ expect(onChange).toHaveBeenCalledWith('AB');
266
+ });
267
+
268
+ it('does not save it twice when the debounce catches up', () => {
269
+ jest.useFakeTimers();
270
+ const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
271
+
272
+ fireEvent.change(input, { target: { value: 'AB' } });
273
+ fireEvent.blur(input);
274
+ act(() => jest.advanceTimersByTime(1000));
275
+
276
+ expect(onChange).toHaveBeenCalledTimes(1);
277
+ expect(onChange).toHaveBeenCalledWith('AB');
278
+ jest.useRealTimers();
279
+ });
280
+
156
281
  it('reports an empty label on blur', () => {
157
282
  const { input } = renderComponent({ mark: { x: 1, y: 1, label: 'A' } });
158
283
 
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useState } from 'react';
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { styled, useTheme } from '@mui/material/styles';
4
4
  import AutosizeInput from 'react-input-autosize';
@@ -8,6 +8,11 @@ import { color } from '@pie-lib/render-ui';
8
8
  import SvgIcon from './label-svg-icon';
9
9
 
10
10
  const DEBOUNCE_DELAY = 500;
11
+ // A new label insists on the focus for this long: in the DNA env something moves the focus while the
12
+ // model save that creating the label triggered is handled, after the last render. Nothing typed yet
13
+ // is what tells that apart from the user leaving the label.
14
+ const INSIST_ON_FOCUS_FOR = 500;
15
+ const INSIST_AT = [0, 50, 120, 250, 400];
11
16
 
12
17
  const StyledInputCorrect = styled('div')(({ theme }) => ({
13
18
  float: 'right',
@@ -130,8 +135,13 @@ LabelInput.propTypes = {
130
135
  };
131
136
 
132
137
  export const MarkLabel = (props) => {
138
+ // the state repositions the label once the input has a size, the ref is never a render behind
139
+ const inputNode = useRef(null);
133
140
  const [input, setInput] = useState(null);
134
- const _ref = useCallback((node) => setInput(node));
141
+ const _ref = useCallback((node) => {
142
+ inputNode.current = node;
143
+ setInput(node);
144
+ }, []);
135
145
  const theme = useTheme();
136
146
 
137
147
  const { mark, graphProps, disabled, autoFocus, inputRef: externalInputRef } = props;
@@ -139,26 +149,54 @@ export const MarkLabel = (props) => {
139
149
  const [label, setLabel] = useState(mark.label);
140
150
  const { correctness, correctnesslabel, correctlabel } = mark;
141
151
 
142
- const isFocused = () => !!input && typeof document !== 'undefined' && document.activeElement === input;
152
+ const isFocused = () =>
153
+ !!inputNode.current && typeof document !== 'undefined' && document.activeElement === inputNode.current;
154
+
155
+ // an empty label with an enabled input is one the user just added: label mode has to be on and it
156
+ // always starts off. Read from the mark, not from autoFocus, because the tool that asked for the
157
+ // label can be remounted by the marks coming back from the host before the input renders.
158
+ const isNewLabel = useRef(mark.label === '' && !disabled && !mark.disabled);
159
+ const insistUntil = useRef(0);
160
+ const lastSaved = useRef(mark.label);
161
+ const insisting = () => Date.now() < insistUntil.current && !(inputNode.current && inputNode.current.value);
143
162
 
144
163
  const onChange = (e) => setLabel(e.target.value);
145
164
 
165
+ const saveLabel = (value) => {
166
+ if (value === lastSaved.current) {
167
+ return;
168
+ }
169
+
170
+ lastSaved.current = value;
171
+ props.onChange(value);
172
+ };
173
+
146
174
  const handleBlur = useCallback(() => {
175
+ // in the DNA env the focus is moved while the model is saved - not the user leaving the label
176
+ if (insisting()) {
177
+ return;
178
+ }
179
+
180
+ isNewLabel.current = false;
181
+
147
182
  if (label === '') {
148
183
  props.onChange('');
184
+ } else if (label !== mark.label) {
185
+ // the debounce can still be pending, and once the focus is gone an echo of the model would
186
+ // overwrite what was typed before it fires
187
+ saveLabel(label);
149
188
  }
150
189
 
151
190
  // lets the tool know that this label is not being edited anymore, so it stops auto focusing it
152
191
  if (props.onBlur) {
153
192
  props.onBlur();
154
193
  }
155
- }, [label, props.onChange, props.onBlur]);
194
+ }, [label, mark.label, props.onChange, props.onBlur]);
156
195
 
157
196
  const debouncedLabel = useDebounce(label, DEBOUNCE_DELAY);
158
197
 
159
- // useState only sets the value once, to synch props to state need useEffect.
160
- // While this input has the focus we keep what the user typed: the mark can come back from an
161
- // (async) model save with an older label and that would overwrite the characters typed meanwhile.
198
+ // props to state, but not while focused: the mark can come back from an async model save with an
199
+ // older label and overwrite what was typed meanwhile
162
200
  useEffect(() => {
163
201
  if (isFocused()) {
164
202
  return;
@@ -170,27 +208,52 @@ export const MarkLabel = (props) => {
170
208
  // pick up the change to debouncedLabel and save it
171
209
  useEffect(() => {
172
210
  if (typeof debouncedLabel === 'string' && debouncedLabel !== mark.label) {
173
- props.onChange(debouncedLabel);
211
+ saveLabel(debouncedLabel);
174
212
  }
175
213
  }, [debouncedLabel]);
176
214
 
177
- // A label that has just been added has to be focused so that it can be typed into right away.
178
- // This also re-focuses the input if it gets remounted while it is being edited - saving the model
179
- // is done by the host (api call) and the model that comes back can replace the input node.
180
- useEffect(() => {
181
- if (!autoFocus || !input || disabled || isFocused()) {
215
+ const focusInput = () => {
216
+ const node = inputNode.current;
217
+
218
+ // node.disabled, not the disabled prop: a disabled input silently ignores focus()
219
+ if ((!autoFocus && !isNewLabel.current && !insisting()) || !node || node.disabled || isFocused()) {
182
220
  return;
183
221
  }
184
222
 
185
- input.focus();
223
+ node.focus();
224
+
225
+ // a new label is focused once, so it cannot fight over the focus with another one
226
+ if (isFocused()) {
227
+ isNewLabel.current = false;
228
+ }
186
229
 
187
230
  // keep the caret at the end, otherwise typing continues in front of the existing label
188
- const caret = (input.value || '').length;
231
+ const caret = (node.value || '').length;
232
+
233
+ if (node.setSelectionRange) {
234
+ node.setSelectionRange(caret, caret);
235
+ }
236
+ };
237
+
238
+ // after every render: the input can appear a render or more later (the mark comes back from an
239
+ // async model save) and a replaced node loses the focus without this component being told
240
+ useEffect(() => focusInput());
241
+
242
+ // and again on a timer, for the DNA env, where the focus is moved once the renders are over. The
243
+ // original code focused from a setTimeout, which is what let it survive that.
244
+ useEffect(() => {
245
+ if (!autoFocus && !isNewLabel.current) {
246
+ return;
247
+ }
189
248
 
190
- if (input.setSelectionRange) {
191
- input.setSelectionRange(caret, caret);
249
+ if (!insistUntil.current) {
250
+ insistUntil.current = Date.now() + INSIST_ON_FOCUS_FOR;
192
251
  }
193
- }, [autoFocus, input, disabled]);
252
+
253
+ const timers = INSIST_AT.map((delay) => setTimeout(focusInput, delay));
254
+
255
+ return () => timers.forEach((t) => clearTimeout(t));
256
+ }, [autoFocus]);
194
257
 
195
258
  const rect = input ? input.getBoundingClientRect() : { width: 0, height: 0 };
196
259
  const pos = position(graphProps, mark, rect);
@@ -263,6 +326,12 @@ export const MarkLabel = (props) => {
263
326
  return <StyledIncorrect style={style}>{renderInput(studentInputStyle, label)}</StyledIncorrect>;
264
327
  }
265
328
 
329
+ // an empty label that cannot be edited is just an empty box - the config draws the background
330
+ // marks into every correct answer graph too
331
+ if (disabledInput && !label) {
332
+ return null;
333
+ }
334
+
266
335
  return <div style={style}>{renderInput(getInputStyles(theme, disabled, mark.disabled), label)}</div>;
267
336
  };
268
337
 
@@ -409,7 +409,8 @@ describe('Component', () => {
409
409
  expect(document.activeElement).not.toBe(labelNode.querySelector('input'));
410
410
  });
411
411
 
412
- it('stops auto focusing the label once it is blurred', () => {
412
+ // the blur that the DNA env causes by moving the focus is not the user leaving the label
413
+ it('keeps the focus on a new empty label that something else blurred', () => {
413
414
  const { container } = render(<Controlled initialMark={xy(1, 1)} labelModeEnabled={true} />);
414
415
 
415
416
  clickPoint(container);
@@ -417,9 +418,23 @@ describe('Component', () => {
417
418
  expect(document.activeElement).toBe(input);
418
419
 
419
420
  fireEvent.blur(input);
421
+ fireEvent.change(input, { target: { value: 'A' } });
422
+
423
+ expect(document.activeElement).toBe(input);
424
+ });
425
+
426
+ it('stops auto focusing the label once it has text and is blurred', () => {
427
+ const { container } = render(<Controlled initialMark={xy(1, 1)} labelModeEnabled={true} />);
428
+
429
+ clickPoint(container);
430
+ const input = labelNode.querySelector('input');
420
431
 
421
- // a re-render must not pull the focus back into the label the user just left
422
432
  fireEvent.change(input, { target: { value: 'A' } });
433
+ fireEvent.blur(input);
434
+ input.blur();
435
+
436
+ // a re-render must not pull the focus back into the label the user just left
437
+ fireEvent.change(input, { target: { value: 'AB' } });
423
438
  expect(document.activeElement).not.toBe(input);
424
439
  });
425
440