@pie-lib/graphing 4.0.15-next.31 → 4.0.15-next.39
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/lib/mark-label.js +104 -15
- package/lib/mark-label.js.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/mark-label.test.jsx +84 -0
- package/src/mark-label.jsx +112 -15
package/lib/mark-label.js
CHANGED
|
@@ -20,11 +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 for this long
|
|
24
|
-
//
|
|
25
|
-
// is
|
|
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.
|
|
26
27
|
var INSIST_ON_FOCUS_FOR = 500;
|
|
27
|
-
var INSIST_AT = [0, 50, 120, 250, 400];
|
|
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;
|
|
28
33
|
var StyledInputCorrect = (0, _styles.styled)('div')(function (_ref2) {
|
|
29
34
|
var theme = _ref2.theme;
|
|
30
35
|
return {
|
|
@@ -143,12 +148,30 @@ var coordinates = exports.coordinates = function coordinates(graphProps, mark) {
|
|
|
143
148
|
return {};
|
|
144
149
|
}
|
|
145
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
|
+
};
|
|
146
168
|
var LabelInput = function LabelInput(_ref5) {
|
|
147
169
|
var _ref = _ref5._ref,
|
|
148
170
|
externalInputRef = _ref5.externalInputRef,
|
|
149
171
|
label = _ref5.label,
|
|
150
172
|
disabled = _ref5.disabled,
|
|
151
173
|
inputStyle = _ref5.inputStyle,
|
|
174
|
+
minWidth = _ref5.minWidth,
|
|
152
175
|
onChange = _ref5.onChange,
|
|
153
176
|
onBlur = _ref5.onBlur;
|
|
154
177
|
return /*#__PURE__*/_react["default"].createElement(_reactInputAutosize["default"], {
|
|
@@ -158,6 +181,7 @@ var LabelInput = function LabelInput(_ref5) {
|
|
|
158
181
|
},
|
|
159
182
|
disabled: disabled,
|
|
160
183
|
inputStyle: inputStyle,
|
|
184
|
+
minWidth: minWidth,
|
|
161
185
|
value: label,
|
|
162
186
|
onChange: onChange,
|
|
163
187
|
onBlur: onBlur
|
|
@@ -169,6 +193,7 @@ LabelInput.propTypes = {
|
|
|
169
193
|
label: _propTypes["default"].string,
|
|
170
194
|
disabled: _propTypes["default"].bool,
|
|
171
195
|
inputStyle: _propTypes["default"].object,
|
|
196
|
+
minWidth: _propTypes["default"].number,
|
|
172
197
|
onChange: _propTypes["default"].func,
|
|
173
198
|
onBlur: _propTypes["default"].func
|
|
174
199
|
};
|
|
@@ -209,6 +234,13 @@ var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
|
|
|
209
234
|
var insisting = function insisting() {
|
|
210
235
|
return Date.now() < insistUntil.current && !(inputNode.current && inputNode.current.value);
|
|
211
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);
|
|
212
244
|
var onChange = function onChange(e) {
|
|
213
245
|
return setLabel(e.target.value);
|
|
214
246
|
};
|
|
@@ -221,7 +253,7 @@ var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
|
|
|
221
253
|
};
|
|
222
254
|
var handleBlur = (0, _react.useCallback)(function () {
|
|
223
255
|
// in the DNA env the focus is moved while the model is saved - not the user leaving the label
|
|
224
|
-
if (insisting()) {
|
|
256
|
+
if (insisting() || windowAway.current || selfFocusing.current) {
|
|
225
257
|
return;
|
|
226
258
|
}
|
|
227
259
|
isNewLabel.current = false;
|
|
@@ -259,21 +291,49 @@ var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
|
|
|
259
291
|
var node = inputNode.current;
|
|
260
292
|
|
|
261
293
|
// node.disabled, not the disabled prop: a disabled input silently ignores focus()
|
|
262
|
-
if (!autoFocus && !isNewLabel.current && !insisting() || !node || node.disabled
|
|
294
|
+
if (!autoFocus && !isNewLabel.current && !insisting() || !node || node.disabled) {
|
|
263
295
|
return;
|
|
264
296
|
}
|
|
265
|
-
|
|
297
|
+
var wasFocused = isFocused();
|
|
298
|
+
if (!wasFocused) {
|
|
299
|
+
node.focus();
|
|
266
300
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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;
|
|
308
|
+
}
|
|
309
|
+
|
|
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);
|
|
270
322
|
}
|
|
271
323
|
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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;
|
|
276
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;
|
|
277
337
|
};
|
|
278
338
|
|
|
279
339
|
// after every render: the input can appear a render or more later (the mark comes back from an
|
|
@@ -300,6 +360,29 @@ var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
|
|
|
300
360
|
});
|
|
301
361
|
};
|
|
302
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]);
|
|
303
386
|
var rect = input ? input.getBoundingClientRect() : {
|
|
304
387
|
width: 0,
|
|
305
388
|
height: 0
|
|
@@ -323,7 +406,13 @@ var MarkLabel = exports.MarkLabel = function MarkLabel(props) {
|
|
|
323
406
|
externalInputRef: externalInputRef,
|
|
324
407
|
label: labelValue,
|
|
325
408
|
disabled: disabledInput,
|
|
326
|
-
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,
|
|
327
416
|
onChange: onChange,
|
|
328
417
|
onBlur: handleBlur
|
|
329
418
|
});
|
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","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":[]}
|
|
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.15-next.
|
|
6
|
+
"version": "4.0.15-next.39+8378b2996",
|
|
7
7
|
"description": "Graphing components",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"react",
|
|
@@ -22,10 +22,10 @@
|
|
|
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.0.
|
|
26
|
-
"@pie-lib/editable-html-tip-tap": "^2.1.
|
|
25
|
+
"@pie-lib/drag": "^4.0.6",
|
|
26
|
+
"@pie-lib/editable-html-tip-tap": "^2.1.11",
|
|
27
27
|
"@pie-lib/graphing-utils": "^3.0.2",
|
|
28
|
-
"@pie-lib/plot": "^4.0.
|
|
28
|
+
"@pie-lib/plot": "^4.0.13",
|
|
29
29
|
"@pie-lib/render-ui": "^6.1.3",
|
|
30
30
|
"@pie-lib/translator": "^4.0.2",
|
|
31
31
|
"@visx/axis": "^3.0.0",
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"react": "^18.2.0"
|
|
56
56
|
},
|
|
57
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "8378b2996b6632469d466aee6f8aadc9f99a7443",
|
|
58
58
|
"scripts": {}
|
|
59
59
|
}
|
|
@@ -218,6 +218,21 @@ describe('MarkLabel - editing', () => {
|
|
|
218
218
|
|
|
219
219
|
expect(container.querySelector('input')).toBe(null);
|
|
220
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);
|
|
235
|
+
});
|
|
221
236
|
});
|
|
222
237
|
|
|
223
238
|
describe('syncing mark.label into the input', () => {
|
|
@@ -246,6 +261,75 @@ describe('MarkLabel - editing', () => {
|
|
|
246
261
|
});
|
|
247
262
|
});
|
|
248
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
|
+
|
|
249
333
|
describe('onBlur', () => {
|
|
250
334
|
it('is called so the tool stops auto focusing the label', () => {
|
|
251
335
|
const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'A' } });
|
package/src/mark-label.jsx
CHANGED
|
@@ -8,11 +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 for this long
|
|
12
|
-
//
|
|
13
|
-
// is
|
|
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.
|
|
14
15
|
const INSIST_ON_FOCUS_FOR = 500;
|
|
15
|
-
const INSIST_AT = [0, 50, 120, 250, 400];
|
|
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;
|
|
16
21
|
|
|
17
22
|
const StyledInputCorrect = styled('div')(({ theme }) => ({
|
|
18
23
|
float: 'right',
|
|
@@ -110,7 +115,28 @@ export const coordinates = (graphProps, mark, rect = { width: 0, height: 0 }, po
|
|
|
110
115
|
}
|
|
111
116
|
};
|
|
112
117
|
|
|
113
|
-
|
|
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 }) => (
|
|
114
140
|
<AutosizeInput
|
|
115
141
|
inputRef={(r) => {
|
|
116
142
|
_ref(r);
|
|
@@ -118,6 +144,7 @@ const LabelInput = ({ _ref, externalInputRef, label, disabled, inputStyle, onCha
|
|
|
118
144
|
}}
|
|
119
145
|
disabled={disabled}
|
|
120
146
|
inputStyle={inputStyle}
|
|
147
|
+
minWidth={minWidth}
|
|
121
148
|
value={label}
|
|
122
149
|
onChange={onChange}
|
|
123
150
|
onBlur={onBlur}
|
|
@@ -130,6 +157,7 @@ LabelInput.propTypes = {
|
|
|
130
157
|
label: PropTypes.string,
|
|
131
158
|
disabled: PropTypes.bool,
|
|
132
159
|
inputStyle: PropTypes.object,
|
|
160
|
+
minWidth: PropTypes.number,
|
|
133
161
|
onChange: PropTypes.func,
|
|
134
162
|
onBlur: PropTypes.func,
|
|
135
163
|
};
|
|
@@ -159,6 +187,13 @@ export const MarkLabel = (props) => {
|
|
|
159
187
|
const insistUntil = useRef(0);
|
|
160
188
|
const lastSaved = useRef(mark.label);
|
|
161
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);
|
|
162
197
|
|
|
163
198
|
const onChange = (e) => setLabel(e.target.value);
|
|
164
199
|
|
|
@@ -173,7 +208,7 @@ export const MarkLabel = (props) => {
|
|
|
173
208
|
|
|
174
209
|
const handleBlur = useCallback(() => {
|
|
175
210
|
// in the DNA env the focus is moved while the model is saved - not the user leaving the label
|
|
176
|
-
if (insisting()) {
|
|
211
|
+
if (insisting() || windowAway.current || selfFocusing.current) {
|
|
177
212
|
return;
|
|
178
213
|
}
|
|
179
214
|
|
|
@@ -216,23 +251,53 @@ export const MarkLabel = (props) => {
|
|
|
216
251
|
const node = inputNode.current;
|
|
217
252
|
|
|
218
253
|
// node.disabled, not the disabled prop: a disabled input silently ignores focus()
|
|
219
|
-
if ((!autoFocus && !isNewLabel.current && !insisting()) || !node || node.disabled
|
|
254
|
+
if ((!autoFocus && !isNewLabel.current && !insisting()) || !node || node.disabled) {
|
|
220
255
|
return;
|
|
221
256
|
}
|
|
222
257
|
|
|
223
|
-
|
|
258
|
+
const wasFocused = isFocused();
|
|
224
259
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
+
}
|
|
228
267
|
}
|
|
229
268
|
|
|
230
|
-
|
|
231
|
-
|
|
269
|
+
if (!node.setSelectionRange) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
232
272
|
|
|
233
|
-
|
|
234
|
-
|
|
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
|
+
}
|
|
286
|
+
|
|
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;
|
|
235
292
|
}
|
|
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;
|
|
236
301
|
};
|
|
237
302
|
|
|
238
303
|
// after every render: the input can appear a render or more later (the mark comes back from an
|
|
@@ -255,6 +320,33 @@ export const MarkLabel = (props) => {
|
|
|
255
320
|
return () => timers.forEach((t) => clearTimeout(t));
|
|
256
321
|
}, [autoFocus]);
|
|
257
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]);
|
|
349
|
+
|
|
258
350
|
const rect = input ? input.getBoundingClientRect() : { width: 0, height: 0 };
|
|
259
351
|
const pos = position(graphProps, mark, rect);
|
|
260
352
|
const leftTop = coordinates(graphProps, mark, rect, pos);
|
|
@@ -282,6 +374,11 @@ export const MarkLabel = (props) => {
|
|
|
282
374
|
label={labelValue}
|
|
283
375
|
disabled={disabledInput}
|
|
284
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}
|
|
285
382
|
onChange={onChange}
|
|
286
383
|
onBlur={handleBlur}
|
|
287
384
|
/>
|