@pie-lib/graphing 4.0.17 → 4.0.19
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 +12 -0
- package/lib/mark-label.js +164 -30
- package/lib/mark-label.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/graph.test.jsx +19 -0
- package/src/__tests__/mark-label.test.jsx +123 -8
- package/src/mark-label.jsx +169 -30
- package/src/tools/point/__tests__/component.test.jsx +17 -2
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.19](https://github.com/pie-framework/pie-lib/compare/@pie-lib/graphing@4.0.18...@pie-lib/graphing@4.0.19) (2026-08-28)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **graphing:** give a new label a caret, not just the focus PIE-681 ([eccf0df](https://github.com/pie-framework/pie-lib/commit/eccf0df50183eab617bf492eec197a1ce9a55087))
|
|
11
|
+
|
|
12
|
+
## [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)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
- **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))
|
|
17
|
+
|
|
6
18
|
## [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)
|
|
7
19
|
|
|
8
20
|
### Bug Fixes
|
package/lib/mark-label.js
CHANGED
|
@@ -20,6 +20,16 @@ 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 and the caret for this long, and again on each of the ticks
|
|
24
|
+
// below. No single moment is reliable: the authoring host re-renders the whole element while it
|
|
25
|
+
// saves, and the input is replaced under this component more than once. Nothing typed yet is what
|
|
26
|
+
// tells a transient blur apart from the user leaving the label.
|
|
27
|
+
var INSIST_ON_FOCUS_FOR = 500;
|
|
28
|
+
var INSIST_AT = [0, 50, 120, 250, 400, 700, 1000];
|
|
29
|
+
// how many times a label will do the heavier focus cycle to get a caret back
|
|
30
|
+
var MAX_CARET_CYCLES = 3;
|
|
31
|
+
// how wide an editable label input is while it is empty
|
|
32
|
+
var EMPTY_INPUT_WIDTH = 30;
|
|
23
33
|
var StyledInputCorrect = (0, _styles.styled)('div')(function (_ref2) {
|
|
24
34
|
var theme = _ref2.theme;
|
|
25
35
|
return {
|
|
@@ -138,12 +148,30 @@ var coordinates = exports.coordinates = function coordinates(graphProps, mark) {
|
|
|
138
148
|
return {};
|
|
139
149
|
}
|
|
140
150
|
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Whether the document's caret is in this input. The characters land at the caret, not at the
|
|
154
|
+
* focused node - an input can be the active element with no caret anywhere, and then keypress fires
|
|
155
|
+
* and nothing is inserted, with nothing in the page cancelling anything.
|
|
156
|
+
*/
|
|
157
|
+
var caretIsInside = function caretIsInside(node) {
|
|
158
|
+
if (typeof document === 'undefined' || !document.getSelection) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
var selection = document.getSelection();
|
|
162
|
+
if (!selection || !selection.rangeCount || !selection.anchorNode) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
var anchor = selection.anchorNode;
|
|
166
|
+
return anchor === node || anchor === node.parentNode || node.contains(anchor);
|
|
167
|
+
};
|
|
141
168
|
var LabelInput = function LabelInput(_ref5) {
|
|
142
169
|
var _ref = _ref5._ref,
|
|
143
170
|
externalInputRef = _ref5.externalInputRef,
|
|
144
171
|
label = _ref5.label,
|
|
145
172
|
disabled = _ref5.disabled,
|
|
146
173
|
inputStyle = _ref5.inputStyle,
|
|
174
|
+
minWidth = _ref5.minWidth,
|
|
147
175
|
onChange = _ref5.onChange,
|
|
148
176
|
onBlur = _ref5.onBlur;
|
|
149
177
|
return /*#__PURE__*/_react["default"].createElement(_reactInputAutosize["default"], {
|
|
@@ -153,6 +181,7 @@ var LabelInput = function LabelInput(_ref5) {
|
|
|
153
181
|
},
|
|
154
182
|
disabled: disabled,
|
|
155
183
|
inputStyle: inputStyle,
|
|
184
|
+
minWidth: minWidth,
|
|
156
185
|
value: label,
|
|
157
186
|
onChange: onChange,
|
|
158
187
|
onBlur: onBlur
|
|
@@ -164,12 +193,12 @@ LabelInput.propTypes = {
|
|
|
164
193
|
label: _propTypes["default"].string,
|
|
165
194
|
disabled: _propTypes["default"].bool,
|
|
166
195
|
inputStyle: _propTypes["default"].object,
|
|
196
|
+
minWidth: _propTypes["default"].number,
|
|
167
197
|
onChange: _propTypes["default"].func,
|
|
168
198
|
onBlur: _propTypes["default"].func
|
|
169
199
|
};
|
|
170
200
|
var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
|
|
171
|
-
// the
|
|
172
|
-
// once it has a size, the ref is what the focus is asserted on - it is never a render behind
|
|
201
|
+
// the state repositions the label once the input has a size, the ref is never a render behind
|
|
173
202
|
var inputNode = (0, _react.useRef)(null);
|
|
174
203
|
var _useState = (0, _react.useState)(null),
|
|
175
204
|
_useState2 = (0, _slicedToArray2["default"])(_useState, 2),
|
|
@@ -196,31 +225,55 @@ var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
|
|
|
196
225
|
return !!inputNode.current && typeof document !== 'undefined' && document.activeElement === inputNode.current;
|
|
197
226
|
};
|
|
198
227
|
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
// because the tool that asked for the label can be remounted by the marks coming back from the
|
|
203
|
-
// host (async model save) before the input ever renders, and its autoFocus goes with it.
|
|
228
|
+
// an empty label with an enabled input is one the user just added: label mode has to be on and it
|
|
229
|
+
// always starts off. Read from the mark, not from autoFocus, because the tool that asked for the
|
|
230
|
+
// label can be remounted by the marks coming back from the host before the input renders.
|
|
204
231
|
var isNewLabel = (0, _react.useRef)(mark.label === '' && !disabled && !mark.disabled);
|
|
232
|
+
var insistUntil = (0, _react.useRef)(0);
|
|
233
|
+
var lastSaved = (0, _react.useRef)(mark.label);
|
|
234
|
+
var insisting = function insisting() {
|
|
235
|
+
return Date.now() < insistUntil.current && !(inputNode.current && inputNode.current.value);
|
|
236
|
+
};
|
|
237
|
+
// the whole window losing the focus is not the user leaving the label - another window, the
|
|
238
|
+
// devtools, or the host moving the focus while it saves. The window blur arrives before the
|
|
239
|
+
// input's own blur, so this flag is set by the time the label is asked to give itself up.
|
|
240
|
+
var windowAway = (0, _react.useRef)(false);
|
|
241
|
+
// our own blur, from the focus cycle that puts a missing caret back
|
|
242
|
+
var selfFocusing = (0, _react.useRef)(false);
|
|
243
|
+
var caretCycles = (0, _react.useRef)(0);
|
|
205
244
|
var onChange = function onChange(e) {
|
|
206
245
|
return setLabel(e.target.value);
|
|
207
246
|
};
|
|
247
|
+
var saveLabel = function saveLabel(value) {
|
|
248
|
+
if (value === lastSaved.current) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
lastSaved.current = value;
|
|
252
|
+
props.onChange(value);
|
|
253
|
+
};
|
|
208
254
|
var handleBlur = (0, _react.useCallback)(function () {
|
|
255
|
+
// in the DNA env the focus is moved while the model is saved - not the user leaving the label
|
|
256
|
+
if (insisting() || windowAway.current || selfFocusing.current) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
209
259
|
isNewLabel.current = false;
|
|
210
260
|
if (label === '') {
|
|
211
261
|
props.onChange('');
|
|
262
|
+
} else if (label !== mark.label) {
|
|
263
|
+
// the debounce can still be pending, and once the focus is gone an echo of the model would
|
|
264
|
+
// overwrite what was typed before it fires
|
|
265
|
+
saveLabel(label);
|
|
212
266
|
}
|
|
213
267
|
|
|
214
268
|
// lets the tool know that this label is not being edited anymore, so it stops auto focusing it
|
|
215
269
|
if (props.onBlur) {
|
|
216
270
|
props.onBlur();
|
|
217
271
|
}
|
|
218
|
-
}, [label, props.onChange, props.onBlur]);
|
|
272
|
+
}, [label, mark.label, props.onChange, props.onBlur]);
|
|
219
273
|
var debouncedLabel = (0, _useDebounce.useDebounce)(label, DEBOUNCE_DELAY);
|
|
220
274
|
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
// (async) model save with an older label and that would overwrite the characters typed meanwhile.
|
|
275
|
+
// props to state, but not while focused: the mark can come back from an async model save with an
|
|
276
|
+
// older label and overwrite what was typed meanwhile
|
|
224
277
|
(0, _react.useEffect)(function () {
|
|
225
278
|
if (isFocused()) {
|
|
226
279
|
return;
|
|
@@ -231,36 +284,105 @@ var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
|
|
|
231
284
|
// pick up the change to debouncedLabel and save it
|
|
232
285
|
(0, _react.useEffect)(function () {
|
|
233
286
|
if (typeof debouncedLabel === 'string' && debouncedLabel !== mark.label) {
|
|
234
|
-
|
|
287
|
+
saveLabel(debouncedLabel);
|
|
235
288
|
}
|
|
236
289
|
}, [debouncedLabel]);
|
|
237
|
-
|
|
238
|
-
// A label that has just been added has to be focused so that it can be typed into right away.
|
|
239
|
-
// This runs after every render on purpose - there is no reliable single moment to focus at:
|
|
240
|
-
// the input shows up whenever the mark comes back from the (async) model save, and a remount
|
|
241
|
-
// of the input (same save, replaced node) drops the focus without this component being told.
|
|
242
|
-
// Asserting the focus on every render is what makes it stick; it is a no-op once the input has
|
|
243
|
-
// it, so the caret the user placed is left alone.
|
|
244
|
-
(0, _react.useEffect)(function () {
|
|
290
|
+
var focusInput = function focusInput() {
|
|
245
291
|
var node = inputNode.current;
|
|
246
292
|
|
|
247
293
|
// node.disabled, not the disabled prop: a disabled input silently ignores focus()
|
|
248
|
-
if (!autoFocus && !isNewLabel.current || !node || node.disabled
|
|
294
|
+
if (!autoFocus && !isNewLabel.current && !insisting() || !node || node.disabled) {
|
|
249
295
|
return;
|
|
250
296
|
}
|
|
251
|
-
|
|
297
|
+
var wasFocused = isFocused();
|
|
298
|
+
if (!wasFocused) {
|
|
299
|
+
node.focus();
|
|
252
300
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
301
|
+
// a new label is focused once, so it cannot fight over the focus with another one
|
|
302
|
+
if (isFocused()) {
|
|
303
|
+
isNewLabel.current = false;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (!node.setSelectionRange) {
|
|
307
|
+
return;
|
|
256
308
|
}
|
|
257
309
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
310
|
+
// a label that was already being edited keeps the caret the user put in it; one that has just
|
|
311
|
+
// taken the focus gets it at the end, otherwise typing continues in front of the existing label
|
|
312
|
+
var keepCaret = wasFocused && node.value;
|
|
313
|
+
var start = keepCaret ? node.selectionStart : (node.value || '').length;
|
|
314
|
+
var end = keepCaret ? node.selectionEnd : start;
|
|
315
|
+
|
|
316
|
+
// The characters land at the document's caret, not at the focused node. An input focused while
|
|
317
|
+
// it was being replaced, or while the window was away, ends up as the active element with no
|
|
318
|
+
// caret anywhere: keypress fires and nothing is inserted, with nothing in the page cancelling
|
|
319
|
+
// anything. Re-asserting the range is what puts the caret back.
|
|
320
|
+
if (!caretIsInside(node)) {
|
|
321
|
+
node.setSelectionRange(start, end);
|
|
262
322
|
}
|
|
323
|
+
|
|
324
|
+
// and if it still is not there, a focus cycle is the heavier way to get it. Every focusInput
|
|
325
|
+
// call is another go at it - after each render, on each insist tick, and when the window comes
|
|
326
|
+
// back - up to a few times per label, since the state comes back as the input is replaced.
|
|
327
|
+
if (caretIsInside(node) || caretCycles.current >= MAX_CARET_CYCLES) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
caretCycles.current += 1;
|
|
331
|
+
// our own blur, so the label is not given up over it
|
|
332
|
+
selfFocusing.current = true;
|
|
333
|
+
node.blur();
|
|
334
|
+
node.focus();
|
|
335
|
+
node.setSelectionRange(start, end);
|
|
336
|
+
selfFocusing.current = false;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// after every render: the input can appear a render or more later (the mark comes back from an
|
|
340
|
+
// async model save) and a replaced node loses the focus without this component being told
|
|
341
|
+
(0, _react.useEffect)(function () {
|
|
342
|
+
return focusInput();
|
|
263
343
|
});
|
|
344
|
+
|
|
345
|
+
// and again on a timer, for the DNA env, where the focus is moved once the renders are over. The
|
|
346
|
+
// original code focused from a setTimeout, which is what let it survive that.
|
|
347
|
+
(0, _react.useEffect)(function () {
|
|
348
|
+
if (!autoFocus && !isNewLabel.current) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (!insistUntil.current) {
|
|
352
|
+
insistUntil.current = Date.now() + INSIST_ON_FOCUS_FOR;
|
|
353
|
+
}
|
|
354
|
+
var timers = INSIST_AT.map(function (delay) {
|
|
355
|
+
return setTimeout(focusInput, delay);
|
|
356
|
+
});
|
|
357
|
+
return function () {
|
|
358
|
+
return timers.forEach(function (t) {
|
|
359
|
+
return clearTimeout(t);
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
}, [autoFocus]);
|
|
363
|
+
|
|
364
|
+
// the blur that came with the window going away is ignored, so the label is still the one being
|
|
365
|
+
// edited when the window comes back and it takes the focus, and its selection, back
|
|
366
|
+
(0, _react.useEffect)(function () {
|
|
367
|
+
var onWindowBlur = function onWindowBlur() {
|
|
368
|
+
windowAway.current = true;
|
|
369
|
+
};
|
|
370
|
+
var onWindowFocus = function onWindowFocus() {
|
|
371
|
+
// coming back only means something if the window went away: moving the focus from one element
|
|
372
|
+
// to another inside the page reaches window too in some implementations
|
|
373
|
+
if (!windowAway.current) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
windowAway.current = false;
|
|
377
|
+
focusInput();
|
|
378
|
+
};
|
|
379
|
+
window.addEventListener('blur', onWindowBlur);
|
|
380
|
+
window.addEventListener('focus', onWindowFocus);
|
|
381
|
+
return function () {
|
|
382
|
+
window.removeEventListener('blur', onWindowBlur);
|
|
383
|
+
window.removeEventListener('focus', onWindowFocus);
|
|
384
|
+
};
|
|
385
|
+
}, [autoFocus]);
|
|
264
386
|
var rect = input ? input.getBoundingClientRect() : {
|
|
265
387
|
width: 0,
|
|
266
388
|
height: 0
|
|
@@ -284,7 +406,13 @@ var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
|
|
|
284
406
|
externalInputRef: externalInputRef,
|
|
285
407
|
label: labelValue,
|
|
286
408
|
disabled: disabledInput,
|
|
287
|
-
inputStyle: inputStyle
|
|
409
|
+
inputStyle: inputStyle
|
|
410
|
+
// an editable label sizes itself to its content, so an empty one is 2px wide: enough for the
|
|
411
|
+
// caret, not enough for anybody to see that the label is waiting to be typed into. Chrome
|
|
412
|
+
// does not blink the caret of an input inside an svg foreignObject, it only paints a hairline,
|
|
413
|
+
// so the box itself has to say that it has the focus.
|
|
414
|
+
,
|
|
415
|
+
minWidth: disabledInput ? 1 : EMPTY_INPUT_WIDTH,
|
|
288
416
|
onChange: onChange,
|
|
289
417
|
onBlur: handleBlur
|
|
290
418
|
});
|
|
@@ -324,6 +452,12 @@ var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
|
|
|
324
452
|
style: style
|
|
325
453
|
}, renderInput(studentInputStyle, label));
|
|
326
454
|
}
|
|
455
|
+
|
|
456
|
+
// an empty label that cannot be edited is just an empty box - the config draws the background
|
|
457
|
+
// marks into every correct answer graph too
|
|
458
|
+
if (disabledInput && !label) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
327
461
|
return /*#__PURE__*/_react["default"].createElement("div", {
|
|
328
462
|
style: style
|
|
329
463
|
}, renderInput(getInputStyles(theme, disabled, mark.disabled), label));
|
package/lib/mark-label.js.map
CHANGED
|
@@ -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","inputNode","useRef","_useState","useState","_useState2","_slicedToArray2","input","setInput","useCallback","node","current","useTheme","autoFocus","_useState3","_useState4","setLabel","correctness","correctnesslabel","correctlabel","isFocused","document","activeElement","isNewLabel","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, 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\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 node is kept in a ref as well as in the state: the state is needed to reposition the label\n // once it has a size, the ref is what the focus is asserted on - it 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 // A label that has just been added is empty and its input is enabled - label mode has to be on\n // for that to happen and it always starts off, so a mark that was loaded with an empty label\n // cannot grab the focus. This is read from the mark rather than taken from the autoFocus prop\n // because the tool that asked for the label can be remounted by the marks coming back from the\n // host (async model save) before the input ever renders, and its autoFocus goes with it.\n const isNewLabel = useRef(mark.label === '' && !disabled && !mark.disabled);\n\n const onChange = (e) => setLabel(e.target.value);\n\n const handleBlur = useCallback(() => {\n isNewLabel.current = false;\n\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 runs after every render on purpose - there is no reliable single moment to focus at:\n // the input shows up whenever the mark comes back from the (async) model save, and a remount\n // of the input (same save, replaced node) drops the focus without this component being told.\n // Asserting the focus on every render is what makes it stick; it is a no-op once the input has\n // it, so the caret the user placed is left alone.\n useEffect(() => {\n const node = inputNode.current;\n\n // node.disabled, not the disabled prop: a disabled input silently ignores focus()\n if ((!autoFocus && !isNewLabel.current) || !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 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","MAX_CARET_CYCLES","EMPTY_INPUT_WIDTH","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","caretIsInside","node","document","getSelection","selection","rangeCount","anchorNode","anchor","parentNode","contains","LabelInput","_ref5","_ref","externalInputRef","label","inputStyle","minWidth","onChange","onBlur","createElement","inputRef","value","propTypes","PropTypes","func","string","bool","object","number","MarkLabel","props","inputNode","useRef","_useState","useState","_useState2","_slicedToArray2","input","setInput","useCallback","current","useTheme","autoFocus","_useState3","_useState4","setLabel","correctness","correctnesslabel","correctlabel","isFocused","activeElement","isNewLabel","insistUntil","lastSaved","insisting","Date","now","windowAway","selfFocusing","caretCycles","target","saveLabel","handleBlur","debouncedLabel","useDebounce","useEffect","focusInput","wasFocused","focus","setSelectionRange","keepCaret","start","selectionStart","end","selectionEnd","blur","timers","map","delay","setTimeout","clearTimeout","onWindowBlur","onWindowFocus","window","addEventListener","removeEventListener","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 and the caret for this long, and again on each of the ticks\n// below. No single moment is reliable: the authoring host re-renders the whole element while it\n// saves, and the input is replaced under this component more than once. Nothing typed yet is what\n// tells a transient blur apart from the user leaving the label.\nconst INSIST_ON_FOCUS_FOR = 500;\nconst INSIST_AT = [0, 50, 120, 250, 400, 700, 1000];\n// how many times a label will do the heavier focus cycle to get a caret back\nconst MAX_CARET_CYCLES = 3;\n// how wide an editable label input is while it is empty\nconst EMPTY_INPUT_WIDTH = 30;\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\n/**\n * Whether the document's caret is in this input. The characters land at the caret, not at the\n * focused node - an input can be the active element with no caret anywhere, and then keypress fires\n * and nothing is inserted, with nothing in the page cancelling anything.\n */\nconst caretIsInside = (node) => {\n if (typeof document === 'undefined' || !document.getSelection) {\n return true;\n }\n\n const selection = document.getSelection();\n\n if (!selection || !selection.rangeCount || !selection.anchorNode) {\n return false;\n }\n\n const anchor = selection.anchorNode;\n\n return anchor === node || anchor === node.parentNode || node.contains(anchor);\n};\n\nconst LabelInput = ({ _ref, externalInputRef, label, disabled, inputStyle, minWidth, onChange, onBlur }) => (\n <AutosizeInput\n inputRef={(r) => {\n _ref(r);\n externalInputRef(r);\n }}\n disabled={disabled}\n inputStyle={inputStyle}\n minWidth={minWidth}\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 minWidth: PropTypes.number,\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 // the whole window losing the focus is not the user leaving the label - another window, the\n // devtools, or the host moving the focus while it saves. The window blur arrives before the\n // input's own blur, so this flag is set by the time the label is asked to give itself up.\n const windowAway = useRef(false);\n // our own blur, from the focus cycle that puts a missing caret back\n const selfFocusing = useRef(false);\n const caretCycles = useRef(0);\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() || windowAway.current || selfFocusing.current) {\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) {\n return;\n }\n\n const wasFocused = isFocused();\n\n if (!wasFocused) {\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\n if (!node.setSelectionRange) {\n return;\n }\n\n // a label that was already being edited keeps the caret the user put in it; one that has just\n // taken the focus gets it at the end, otherwise typing continues in front of the existing label\n const keepCaret = wasFocused && node.value;\n const start = keepCaret ? node.selectionStart : (node.value || '').length;\n const end = keepCaret ? node.selectionEnd : start;\n\n // The characters land at the document's caret, not at the focused node. An input focused while\n // it was being replaced, or while the window was away, ends up as the active element with no\n // caret anywhere: keypress fires and nothing is inserted, with nothing in the page cancelling\n // anything. Re-asserting the range is what puts the caret back.\n if (!caretIsInside(node)) {\n node.setSelectionRange(start, end);\n }\n\n // and if it still is not there, a focus cycle is the heavier way to get it. Every focusInput\n // call is another go at it - after each render, on each insist tick, and when the window comes\n // back - up to a few times per label, since the state comes back as the input is replaced.\n if (caretIsInside(node) || caretCycles.current >= MAX_CARET_CYCLES) {\n return;\n }\n\n caretCycles.current += 1;\n // our own blur, so the label is not given up over it\n selfFocusing.current = true;\n node.blur();\n node.focus();\n node.setSelectionRange(start, end);\n selfFocusing.current = false;\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 // the blur that came with the window going away is ignored, so the label is still the one being\n // edited when the window comes back and it takes the focus, and its selection, back\n useEffect(() => {\n const onWindowBlur = () => {\n windowAway.current = true;\n };\n\n const onWindowFocus = () => {\n // coming back only means something if the window went away: moving the focus from one element\n // to another inside the page reaches window too in some implementations\n if (!windowAway.current) {\n return;\n }\n\n windowAway.current = false;\n focusInput();\n };\n\n window.addEventListener('blur', onWindowBlur);\n window.addEventListener('focus', onWindowFocus);\n\n return () => {\n window.removeEventListener('blur', onWindowBlur);\n window.removeEventListener('focus', onWindowFocus);\n };\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 // an editable label sizes itself to its content, so an empty one is 2px wide: enough for the\n // caret, not enough for anybody to see that the label is waiting to be typed into. Chrome\n // does not blink the caret of an input inside an svg foreignObject, it only paints a hairline,\n // so the box itself has to say that it has the focus.\n minWidth={disabledInput ? 1 : EMPTY_INPUT_WIDTH}\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.
|
|
6
|
+
"version": "4.0.19",
|
|
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.
|
|
26
|
-
"@pie-lib/editable-html-tip-tap": "^2.1.
|
|
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.
|
|
29
|
-
"@pie-lib/render-ui": "^7.0.
|
|
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": "
|
|
57
|
+
"gitHead": "371130452b7db8525d6f162c0c99495be08ced46",
|
|
58
58
|
"scripts": {}
|
|
59
59
|
}
|
|
@@ -765,6 +765,25 @@ describe('label focus', () => {
|
|
|
765
765
|
expect(document.activeElement).toBe(input);
|
|
766
766
|
});
|
|
767
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
|
+
|
|
768
787
|
it('focuses the label even if the tool was remounted before it rendered', () => {
|
|
769
788
|
const { container } = render(<Host shiftMarks={true} />);
|
|
770
789
|
|
|
@@ -82,13 +82,13 @@ describe('MarkLabel - editing', () => {
|
|
|
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
86
|
|
|
87
87
|
expect(document.activeElement).not.toBe(input);
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
it('does not focus the input of a disabled mark', () => {
|
|
91
|
-
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: '', disabled: true } });
|
|
91
|
+
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'A', disabled: true } });
|
|
92
92
|
|
|
93
93
|
expect(document.activeElement).not.toBe(input);
|
|
94
94
|
});
|
|
@@ -191,10 +191,17 @@ describe('MarkLabel - editing', () => {
|
|
|
191
191
|
|
|
192
192
|
// outside of label mode the input is disabled, which is how an empty label that was loaded with
|
|
193
193
|
// the item is told apart from one the user has just added - label mode always starts off
|
|
194
|
-
|
|
195
|
-
|
|
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: '' } });
|
|
196
197
|
|
|
197
|
-
expect(
|
|
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');
|
|
198
205
|
});
|
|
199
206
|
|
|
200
207
|
it('is not focused when label mode is turned on afterwards', () => {
|
|
@@ -202,13 +209,29 @@ describe('MarkLabel - editing', () => {
|
|
|
202
209
|
|
|
203
210
|
rerenderWith({ disabled: false });
|
|
204
211
|
|
|
212
|
+
expect(input(container)).not.toBe(null);
|
|
205
213
|
expect(document.activeElement).not.toBe(input(container));
|
|
206
214
|
});
|
|
207
215
|
|
|
208
|
-
it('is not
|
|
209
|
-
const {
|
|
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 } });
|
|
210
218
|
|
|
211
|
-
expect(
|
|
219
|
+
expect(container.querySelector('input')).toBe(null);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// an input sized to its content is 2px wide while it is empty, and chrome paints the caret of
|
|
223
|
+
// an input inside an svg foreignObject as a hairline that never blinks - so an empty label that
|
|
224
|
+
// has the focus has to be wide enough to be seen as a box waiting for text
|
|
225
|
+
it('is wide enough to be seen while it is empty', () => {
|
|
226
|
+
const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
|
|
227
|
+
|
|
228
|
+
expect(parseInt(input.style.width, 10)).toBeGreaterThanOrEqual(30);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('is only as wide as its text once it cannot be edited', () => {
|
|
232
|
+
const { container } = renderComponent({ disabled: true, mark: { x: 1, y: 1, label: 'A' } });
|
|
233
|
+
|
|
234
|
+
expect(parseInt(container.querySelector('input').style.width, 10)).toBeLessThan(30);
|
|
212
235
|
});
|
|
213
236
|
});
|
|
214
237
|
|
|
@@ -238,6 +261,75 @@ describe('MarkLabel - editing', () => {
|
|
|
238
261
|
});
|
|
239
262
|
});
|
|
240
263
|
|
|
264
|
+
// the characters land at the document's caret, not at the focused node. In the authoring
|
|
265
|
+
// environment the input ends up focused with no caret anywhere: keypress fires and nothing is
|
|
266
|
+
// inserted. jsdom never reports a caret either, so this is that state - the focus cycle that
|
|
267
|
+
// repairs it must not hand the label over to the blur handling.
|
|
268
|
+
describe('a focused input with no caret', () => {
|
|
269
|
+
it('is repaired without reporting the label as removed', () => {
|
|
270
|
+
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: '' } });
|
|
271
|
+
|
|
272
|
+
expect(document.activeElement).toBe(input);
|
|
273
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
274
|
+
expect(onBlur).not.toHaveBeenCalled();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('leaves the caret at the end of a label that already has text', () => {
|
|
278
|
+
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'AB' } });
|
|
279
|
+
|
|
280
|
+
expect(document.activeElement).toBe(input);
|
|
281
|
+
expect(input.selectionStart).toBe(2);
|
|
282
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// clicking another window, the devtools, or the host moving the focus while it saves the model:
|
|
287
|
+
// the label is not being left, so it must survive and take the focus back
|
|
288
|
+
describe('while the window is away', () => {
|
|
289
|
+
const windowGoesAway = () => window.dispatchEvent(new Event('blur'));
|
|
290
|
+
const windowComesBack = () => window.dispatchEvent(new Event('focus'));
|
|
291
|
+
|
|
292
|
+
it('keeps an empty label instead of reporting it as removed', () => {
|
|
293
|
+
const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
|
|
294
|
+
|
|
295
|
+
windowGoesAway();
|
|
296
|
+
fireEvent.blur(input);
|
|
297
|
+
|
|
298
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
299
|
+
expect(onBlur).not.toHaveBeenCalled();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('takes the focus back when the window comes back', () => {
|
|
303
|
+
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: '' } });
|
|
304
|
+
|
|
305
|
+
// the focus is moved to another input rather than with input.blur(): jsdom fires a window
|
|
306
|
+
// focus event as part of blur(), which is the very thing being tested here
|
|
307
|
+
const elsewhere = document.createElement('input');
|
|
308
|
+
|
|
309
|
+
document.body.appendChild(elsewhere);
|
|
310
|
+
|
|
311
|
+
windowGoesAway();
|
|
312
|
+
elsewhere.focus();
|
|
313
|
+
expect(document.activeElement).toBe(elsewhere);
|
|
314
|
+
|
|
315
|
+
windowComesBack();
|
|
316
|
+
|
|
317
|
+
expect(document.activeElement).toBe(input);
|
|
318
|
+
|
|
319
|
+
elsewhere.remove();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('still reports a label the user leaves once the window is back', () => {
|
|
323
|
+
const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
|
|
324
|
+
|
|
325
|
+
windowGoesAway();
|
|
326
|
+
windowComesBack();
|
|
327
|
+
fireEvent.blur(input);
|
|
328
|
+
|
|
329
|
+
expect(onChange).toHaveBeenCalledWith('');
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
241
333
|
describe('onBlur', () => {
|
|
242
334
|
it('is called so the tool stops auto focusing the label', () => {
|
|
243
335
|
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'A' } });
|
|
@@ -247,6 +339,29 @@ describe('MarkLabel - editing', () => {
|
|
|
247
339
|
expect(onBlur).toHaveBeenCalled();
|
|
248
340
|
});
|
|
249
341
|
|
|
342
|
+
// clicking a button or anything else interactive blurs the label while the debounce is pending
|
|
343
|
+
it('saves what was typed straight away', () => {
|
|
344
|
+
const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
|
|
345
|
+
|
|
346
|
+
fireEvent.change(input, { target: { value: 'AB' } });
|
|
347
|
+
fireEvent.blur(input);
|
|
348
|
+
|
|
349
|
+
expect(onChange).toHaveBeenCalledWith('AB');
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('does not save it twice when the debounce catches up', () => {
|
|
353
|
+
jest.useFakeTimers();
|
|
354
|
+
const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
|
|
355
|
+
|
|
356
|
+
fireEvent.change(input, { target: { value: 'AB' } });
|
|
357
|
+
fireEvent.blur(input);
|
|
358
|
+
act(() => jest.advanceTimersByTime(1000));
|
|
359
|
+
|
|
360
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
361
|
+
expect(onChange).toHaveBeenCalledWith('AB');
|
|
362
|
+
jest.useRealTimers();
|
|
363
|
+
});
|
|
364
|
+
|
|
250
365
|
it('reports an empty label on blur', () => {
|
|
251
366
|
const { input } = renderComponent({ mark: { x: 1, y: 1, label: 'A' } });
|
|
252
367
|
|
package/src/mark-label.jsx
CHANGED
|
@@ -8,6 +8,16 @@ 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 and the caret for this long, and again on each of the ticks
|
|
12
|
+
// below. No single moment is reliable: the authoring host re-renders the whole element while it
|
|
13
|
+
// saves, and the input is replaced under this component more than once. Nothing typed yet is what
|
|
14
|
+
// tells a transient blur apart from the user leaving the label.
|
|
15
|
+
const INSIST_ON_FOCUS_FOR = 500;
|
|
16
|
+
const INSIST_AT = [0, 50, 120, 250, 400, 700, 1000];
|
|
17
|
+
// how many times a label will do the heavier focus cycle to get a caret back
|
|
18
|
+
const MAX_CARET_CYCLES = 3;
|
|
19
|
+
// how wide an editable label input is while it is empty
|
|
20
|
+
const EMPTY_INPUT_WIDTH = 30;
|
|
11
21
|
|
|
12
22
|
const StyledInputCorrect = styled('div')(({ theme }) => ({
|
|
13
23
|
float: 'right',
|
|
@@ -105,7 +115,28 @@ export const coordinates = (graphProps, mark, rect = { width: 0, height: 0 }, po
|
|
|
105
115
|
}
|
|
106
116
|
};
|
|
107
117
|
|
|
108
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Whether the document's caret is in this input. The characters land at the caret, not at the
|
|
120
|
+
* focused node - an input can be the active element with no caret anywhere, and then keypress fires
|
|
121
|
+
* and nothing is inserted, with nothing in the page cancelling anything.
|
|
122
|
+
*/
|
|
123
|
+
const caretIsInside = (node) => {
|
|
124
|
+
if (typeof document === 'undefined' || !document.getSelection) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const selection = document.getSelection();
|
|
129
|
+
|
|
130
|
+
if (!selection || !selection.rangeCount || !selection.anchorNode) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const anchor = selection.anchorNode;
|
|
135
|
+
|
|
136
|
+
return anchor === node || anchor === node.parentNode || node.contains(anchor);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const LabelInput = ({ _ref, externalInputRef, label, disabled, inputStyle, minWidth, onChange, onBlur }) => (
|
|
109
140
|
<AutosizeInput
|
|
110
141
|
inputRef={(r) => {
|
|
111
142
|
_ref(r);
|
|
@@ -113,6 +144,7 @@ const LabelInput = ({ _ref, externalInputRef, label, disabled, inputStyle, onCha
|
|
|
113
144
|
}}
|
|
114
145
|
disabled={disabled}
|
|
115
146
|
inputStyle={inputStyle}
|
|
147
|
+
minWidth={minWidth}
|
|
116
148
|
value={label}
|
|
117
149
|
onChange={onChange}
|
|
118
150
|
onBlur={onBlur}
|
|
@@ -125,13 +157,13 @@ LabelInput.propTypes = {
|
|
|
125
157
|
label: PropTypes.string,
|
|
126
158
|
disabled: PropTypes.bool,
|
|
127
159
|
inputStyle: PropTypes.object,
|
|
160
|
+
minWidth: PropTypes.number,
|
|
128
161
|
onChange: PropTypes.func,
|
|
129
162
|
onBlur: PropTypes.func,
|
|
130
163
|
};
|
|
131
164
|
|
|
132
165
|
export const MarkLabel = (props) => {
|
|
133
|
-
// the
|
|
134
|
-
// once it has a size, the ref is what the focus is asserted on - it is never a render behind
|
|
166
|
+
// the state repositions the label once the input has a size, the ref is never a render behind
|
|
135
167
|
const inputNode = useRef(null);
|
|
136
168
|
const [input, setInput] = useState(null);
|
|
137
169
|
const _ref = useCallback((node) => {
|
|
@@ -148,33 +180,58 @@ export const MarkLabel = (props) => {
|
|
|
148
180
|
const isFocused = () =>
|
|
149
181
|
!!inputNode.current && typeof document !== 'undefined' && document.activeElement === inputNode.current;
|
|
150
182
|
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
// because the tool that asked for the label can be remounted by the marks coming back from the
|
|
155
|
-
// host (async model save) before the input ever renders, and its autoFocus goes with it.
|
|
183
|
+
// an empty label with an enabled input is one the user just added: label mode has to be on and it
|
|
184
|
+
// always starts off. Read from the mark, not from autoFocus, because the tool that asked for the
|
|
185
|
+
// label can be remounted by the marks coming back from the host before the input renders.
|
|
156
186
|
const isNewLabel = useRef(mark.label === '' && !disabled && !mark.disabled);
|
|
187
|
+
const insistUntil = useRef(0);
|
|
188
|
+
const lastSaved = useRef(mark.label);
|
|
189
|
+
const insisting = () => Date.now() < insistUntil.current && !(inputNode.current && inputNode.current.value);
|
|
190
|
+
// the whole window losing the focus is not the user leaving the label - another window, the
|
|
191
|
+
// devtools, or the host moving the focus while it saves. The window blur arrives before the
|
|
192
|
+
// input's own blur, so this flag is set by the time the label is asked to give itself up.
|
|
193
|
+
const windowAway = useRef(false);
|
|
194
|
+
// our own blur, from the focus cycle that puts a missing caret back
|
|
195
|
+
const selfFocusing = useRef(false);
|
|
196
|
+
const caretCycles = useRef(0);
|
|
157
197
|
|
|
158
198
|
const onChange = (e) => setLabel(e.target.value);
|
|
159
199
|
|
|
200
|
+
const saveLabel = (value) => {
|
|
201
|
+
if (value === lastSaved.current) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
lastSaved.current = value;
|
|
206
|
+
props.onChange(value);
|
|
207
|
+
};
|
|
208
|
+
|
|
160
209
|
const handleBlur = useCallback(() => {
|
|
210
|
+
// in the DNA env the focus is moved while the model is saved - not the user leaving the label
|
|
211
|
+
if (insisting() || windowAway.current || selfFocusing.current) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
161
215
|
isNewLabel.current = false;
|
|
162
216
|
|
|
163
217
|
if (label === '') {
|
|
164
218
|
props.onChange('');
|
|
219
|
+
} else if (label !== mark.label) {
|
|
220
|
+
// the debounce can still be pending, and once the focus is gone an echo of the model would
|
|
221
|
+
// overwrite what was typed before it fires
|
|
222
|
+
saveLabel(label);
|
|
165
223
|
}
|
|
166
224
|
|
|
167
225
|
// lets the tool know that this label is not being edited anymore, so it stops auto focusing it
|
|
168
226
|
if (props.onBlur) {
|
|
169
227
|
props.onBlur();
|
|
170
228
|
}
|
|
171
|
-
}, [label, props.onChange, props.onBlur]);
|
|
229
|
+
}, [label, mark.label, props.onChange, props.onBlur]);
|
|
172
230
|
|
|
173
231
|
const debouncedLabel = useDebounce(label, DEBOUNCE_DELAY);
|
|
174
232
|
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
// (async) model save with an older label and that would overwrite the characters typed meanwhile.
|
|
233
|
+
// props to state, but not while focused: the mark can come back from an async model save with an
|
|
234
|
+
// older label and overwrite what was typed meanwhile
|
|
178
235
|
useEffect(() => {
|
|
179
236
|
if (isFocused()) {
|
|
180
237
|
return;
|
|
@@ -186,38 +243,109 @@ export const MarkLabel = (props) => {
|
|
|
186
243
|
// pick up the change to debouncedLabel and save it
|
|
187
244
|
useEffect(() => {
|
|
188
245
|
if (typeof debouncedLabel === 'string' && debouncedLabel !== mark.label) {
|
|
189
|
-
|
|
246
|
+
saveLabel(debouncedLabel);
|
|
190
247
|
}
|
|
191
248
|
}, [debouncedLabel]);
|
|
192
249
|
|
|
193
|
-
|
|
194
|
-
// This runs after every render on purpose - there is no reliable single moment to focus at:
|
|
195
|
-
// the input shows up whenever the mark comes back from the (async) model save, and a remount
|
|
196
|
-
// of the input (same save, replaced node) drops the focus without this component being told.
|
|
197
|
-
// Asserting the focus on every render is what makes it stick; it is a no-op once the input has
|
|
198
|
-
// it, so the caret the user placed is left alone.
|
|
199
|
-
useEffect(() => {
|
|
250
|
+
const focusInput = () => {
|
|
200
251
|
const node = inputNode.current;
|
|
201
252
|
|
|
202
253
|
// node.disabled, not the disabled prop: a disabled input silently ignores focus()
|
|
203
|
-
if ((!autoFocus && !isNewLabel.current) || !node || node.disabled
|
|
254
|
+
if ((!autoFocus && !isNewLabel.current && !insisting()) || !node || node.disabled) {
|
|
204
255
|
return;
|
|
205
256
|
}
|
|
206
257
|
|
|
207
|
-
|
|
258
|
+
const wasFocused = isFocused();
|
|
208
259
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
260
|
+
if (!wasFocused) {
|
|
261
|
+
node.focus();
|
|
262
|
+
|
|
263
|
+
// a new label is focused once, so it cannot fight over the focus with another one
|
|
264
|
+
if (isFocused()) {
|
|
265
|
+
isNewLabel.current = false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!node.setSelectionRange) {
|
|
270
|
+
return;
|
|
212
271
|
}
|
|
213
272
|
|
|
214
|
-
//
|
|
215
|
-
|
|
273
|
+
// a label that was already being edited keeps the caret the user put in it; one that has just
|
|
274
|
+
// taken the focus gets it at the end, otherwise typing continues in front of the existing label
|
|
275
|
+
const keepCaret = wasFocused && node.value;
|
|
276
|
+
const start = keepCaret ? node.selectionStart : (node.value || '').length;
|
|
277
|
+
const end = keepCaret ? node.selectionEnd : start;
|
|
278
|
+
|
|
279
|
+
// The characters land at the document's caret, not at the focused node. An input focused while
|
|
280
|
+
// it was being replaced, or while the window was away, ends up as the active element with no
|
|
281
|
+
// caret anywhere: keypress fires and nothing is inserted, with nothing in the page cancelling
|
|
282
|
+
// anything. Re-asserting the range is what puts the caret back.
|
|
283
|
+
if (!caretIsInside(node)) {
|
|
284
|
+
node.setSelectionRange(start, end);
|
|
285
|
+
}
|
|
216
286
|
|
|
217
|
-
if
|
|
218
|
-
|
|
287
|
+
// and if it still is not there, a focus cycle is the heavier way to get it. Every focusInput
|
|
288
|
+
// call is another go at it - after each render, on each insist tick, and when the window comes
|
|
289
|
+
// back - up to a few times per label, since the state comes back as the input is replaced.
|
|
290
|
+
if (caretIsInside(node) || caretCycles.current >= MAX_CARET_CYCLES) {
|
|
291
|
+
return;
|
|
219
292
|
}
|
|
220
|
-
|
|
293
|
+
|
|
294
|
+
caretCycles.current += 1;
|
|
295
|
+
// our own blur, so the label is not given up over it
|
|
296
|
+
selfFocusing.current = true;
|
|
297
|
+
node.blur();
|
|
298
|
+
node.focus();
|
|
299
|
+
node.setSelectionRange(start, end);
|
|
300
|
+
selfFocusing.current = false;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// after every render: the input can appear a render or more later (the mark comes back from an
|
|
304
|
+
// async model save) and a replaced node loses the focus without this component being told
|
|
305
|
+
useEffect(() => focusInput());
|
|
306
|
+
|
|
307
|
+
// and again on a timer, for the DNA env, where the focus is moved once the renders are over. The
|
|
308
|
+
// original code focused from a setTimeout, which is what let it survive that.
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
if (!autoFocus && !isNewLabel.current) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!insistUntil.current) {
|
|
315
|
+
insistUntil.current = Date.now() + INSIST_ON_FOCUS_FOR;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const timers = INSIST_AT.map((delay) => setTimeout(focusInput, delay));
|
|
319
|
+
|
|
320
|
+
return () => timers.forEach((t) => clearTimeout(t));
|
|
321
|
+
}, [autoFocus]);
|
|
322
|
+
|
|
323
|
+
// the blur that came with the window going away is ignored, so the label is still the one being
|
|
324
|
+
// edited when the window comes back and it takes the focus, and its selection, back
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
const onWindowBlur = () => {
|
|
327
|
+
windowAway.current = true;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const onWindowFocus = () => {
|
|
331
|
+
// coming back only means something if the window went away: moving the focus from one element
|
|
332
|
+
// to another inside the page reaches window too in some implementations
|
|
333
|
+
if (!windowAway.current) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
windowAway.current = false;
|
|
338
|
+
focusInput();
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
window.addEventListener('blur', onWindowBlur);
|
|
342
|
+
window.addEventListener('focus', onWindowFocus);
|
|
343
|
+
|
|
344
|
+
return () => {
|
|
345
|
+
window.removeEventListener('blur', onWindowBlur);
|
|
346
|
+
window.removeEventListener('focus', onWindowFocus);
|
|
347
|
+
};
|
|
348
|
+
}, [autoFocus]);
|
|
221
349
|
|
|
222
350
|
const rect = input ? input.getBoundingClientRect() : { width: 0, height: 0 };
|
|
223
351
|
const pos = position(graphProps, mark, rect);
|
|
@@ -246,6 +374,11 @@ export const MarkLabel = (props) => {
|
|
|
246
374
|
label={labelValue}
|
|
247
375
|
disabled={disabledInput}
|
|
248
376
|
inputStyle={inputStyle}
|
|
377
|
+
// an editable label sizes itself to its content, so an empty one is 2px wide: enough for the
|
|
378
|
+
// caret, not enough for anybody to see that the label is waiting to be typed into. Chrome
|
|
379
|
+
// does not blink the caret of an input inside an svg foreignObject, it only paints a hairline,
|
|
380
|
+
// so the box itself has to say that it has the focus.
|
|
381
|
+
minWidth={disabledInput ? 1 : EMPTY_INPUT_WIDTH}
|
|
249
382
|
onChange={onChange}
|
|
250
383
|
onBlur={handleBlur}
|
|
251
384
|
/>
|
|
@@ -290,6 +423,12 @@ export const MarkLabel = (props) => {
|
|
|
290
423
|
return <StyledIncorrect style={style}>{renderInput(studentInputStyle, label)}</StyledIncorrect>;
|
|
291
424
|
}
|
|
292
425
|
|
|
426
|
+
// an empty label that cannot be edited is just an empty box - the config draws the background
|
|
427
|
+
// marks into every correct answer graph too
|
|
428
|
+
if (disabledInput && !label) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
|
|
293
432
|
return <div style={style}>{renderInput(getInputStyles(theme, disabled, mark.disabled), label)}</div>;
|
|
294
433
|
};
|
|
295
434
|
|
|
@@ -409,7 +409,8 @@ describe('Component', () => {
|
|
|
409
409
|
expect(document.activeElement).not.toBe(labelNode.querySelector('input'));
|
|
410
410
|
});
|
|
411
411
|
|
|
412
|
-
|
|
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
|
|