@marimo-team/frontend 0.24.1-dev2 → 0.24.1-dev20
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/dist/assets/{CellStatus-DqJypoFY.js → CellStatus-B_dBNTJO.js} +1 -1
- package/dist/assets/{JsonOutput-DnrBQBRT.js → JsonOutput-C5g_bqiZ.js} +1 -1
- package/dist/assets/{MarimoErrorOutput-BVdFtnJz.js → MarimoErrorOutput-Cb0evuEp.js} +1 -1
- package/dist/assets/{RenderHTML-X9kskQZ0.js → RenderHTML-BoN8Qu7U.js} +1 -1
- package/dist/assets/{RunButton-D83R2wvd.js → RunButton-C3V3v58F.js} +1 -1
- package/dist/assets/{add-cell-with-ai-HD16K3PB.js → add-cell-with-ai-C5JH9LS2.js} +1 -1
- package/dist/assets/{add-connection-dialog-V86wrXca.js → add-connection-dialog-DHyonb9T.js} +1 -1
- package/dist/assets/{agent-panel-XdLSrKjl.js → agent-panel-D7DLUBam.js} +1 -1
- package/dist/assets/{ai-model-dropdown-BJp70TST.js → ai-model-dropdown-DWDm50eM.js} +4 -4
- package/dist/assets/app-config-button-DhZke6rt.js +1 -0
- package/dist/assets/{blob-BKW9TqBu.js → blob-XD7AvCQ1.js} +1 -1
- package/dist/assets/{cell-editor-DQSrfeL3.js → cell-editor-DZg8uKWr.js} +14 -14
- package/dist/assets/{cell-link-D26WjdN1.js → cell-link-WDIIwzUn.js} +1 -1
- package/dist/assets/{cells-DTFqEQb0.js → cells-BvgwUKB9.js} +1 -1
- package/dist/assets/{chat-display-CclXFQQP.js → chat-display-CR9X_gwB.js} +1 -1
- package/dist/assets/{chat-panel-ft0VIKS8.js → chat-panel-DnGBFlLk.js} +1 -1
- package/dist/assets/{chat-ui-DUYenr6D.js → chat-ui-BVFULpKr.js} +1 -1
- package/dist/assets/{column-preview-BLap3eAk.js → column-preview-DMqGohE5.js} +1 -1
- package/dist/assets/{command-palette-C7nz0zHt.js → command-palette-fK5U3VCd.js} +1 -1
- package/dist/assets/{common-DKQZgzC9.js → common-Dx_4ekWj.js} +1 -1
- package/dist/assets/{components-Bf_hykBb.js → components-DVs9Yu7X.js} +1 -1
- package/dist/assets/config-DFK5eCC3.js +1 -0
- package/dist/assets/{datasource-DZgUyqAK.js → datasource-Le540Ng6.js} +1 -1
- package/dist/assets/{dependency-graph-panel-CH6kIoat.js → dependency-graph-panel-BsdBM_AS.js} +1 -1
- package/dist/assets/{documentation-panel-BQYA8gCW.js → documentation-panel-RJNMjUK6.js} +1 -1
- package/dist/assets/{download-_oAlkvj6.js → download-B8NwKEtr.js} +1 -1
- package/dist/assets/{edit-page-DnXTBsfC.js → edit-page-DUnHDaYA.js} +6 -6
- package/dist/assets/{error-entries-uxjSsTal.js → error-entries-yYB8PZoo.js} +1 -1
- package/dist/assets/{error-panel-Bq0TKquh.js → error-panel-Dr928gBs.js} +1 -1
- package/dist/assets/{file-explorer-panel-uhg7VNMd.js → file-explorer-panel-Brf39GIG.js} +1 -1
- package/dist/assets/{file-icons-V3puz20c.js → file-icons-D39zbjsS.js} +1 -1
- package/dist/assets/{file-name-input-DWf6YZwM.js → file-name-input-BLH5fTEA.js} +1 -1
- package/dist/assets/{floating-outline-Vqr5D17R.js → floating-outline-UPrDtFZF.js} +1 -1
- package/dist/assets/{focus-BTlyH5K_.js → focus-BArgHYf8.js} +1 -1
- package/dist/assets/{form-CRSnWxLR.js → form-BN1AKD58.js} +1 -1
- package/dist/assets/{form-Bb1JVuzT.js → form-BvzcNsxB.js} +1 -1
- package/dist/assets/{home-page-RduyhdYP.js → home-page-Dwl_Carc.js} +1 -1
- package/dist/assets/{html-to-image-BsXiWo0v.js → html-to-image-DKEUbBwc.js} +1 -1
- package/dist/assets/index-Dqr3-dR1.js +38 -0
- package/dist/assets/{index-WoaGtlKd.css → index-n_AVX7aW.css} +1 -1
- package/dist/assets/{kiosk-mode-BH15-Gn-.js → kiosk-mode-B8UhlOLw.js} +1 -1
- package/dist/assets/{layout-1X5SpE18.js → layout-YbL5eg7A.js} +3 -3
- package/dist/assets/{logs-panel-CVxksxou.js → logs-panel-DItocggZ.js} +1 -1
- package/dist/assets/{markdown-renderer-BjuEVUOa.js → markdown-renderer-D7RLn6TA.js} +1 -1
- package/dist/assets/{name-cell-input-CtYGGTR5.js → name-cell-input-jausH-CL.js} +1 -1
- package/dist/assets/{outline-panel-BhxoKAbS.js → outline-panel-C_pASL6b.js} +1 -1
- package/dist/assets/{packages-panel-BWP3YWNW.js → packages-panel-ItQn6qH4.js} +1 -1
- package/dist/assets/{pair-with-agent-modal-BCMZbqSR.js → pair-with-agent-modal-DiDLAjxV.js} +1 -1
- package/dist/assets/{panel-accordion-state-0CKLFeAf.js → panel-accordion-state-DaMmC7UV.js} +1 -1
- package/dist/assets/{panels-D40pa9F1.js → panels-Bhzy-m2X.js} +1 -1
- package/dist/assets/{process-output-WaMx3G7b.js → process-output-ClE9lj2U.js} +1 -1
- package/dist/assets/{radio-group-5KDs5aO1.js → radio-group-CSwRGU0A.js} +1 -1
- package/dist/assets/{readonly-code-display-DnHcjfFl.js → readonly-code-display-kRUXu5aJ.js} +1 -1
- package/dist/assets/{readonly-python-code-BV8otm7e.js → readonly-python-code-BCOkadQf.js} +1 -1
- package/dist/assets/{reveal-component-yVci-fc9.js → reveal-component-CnIgubBp.js} +1 -1
- package/dist/assets/{run-page-aQycuCjn.js → run-page-Du8ylzny.js} +1 -1
- package/dist/assets/{scratchpad-panel-lsqN24Au.js → scratchpad-panel-BEn7TrAb.js} +1 -1
- package/dist/assets/{session-panel-C4JUkPt7.js → session-panel-CidCOM4_.js} +1 -1
- package/dist/assets/{skeleton-DOZFvsSE.js → skeleton-CL2xKYhl.js} +1 -1
- package/dist/assets/{snippets-panel-QNY_Dl2J.js → snippets-panel-V4x4wLMQ.js} +1 -1
- package/dist/assets/state-C7arWhwL.js +3 -0
- package/dist/assets/{state-ByxLiTvP.js → state-m2zUPztG.js} +1 -1
- package/dist/assets/{switch-CDT2KH2g.js → switch-ulnbZFaI.js} +1 -1
- package/dist/assets/{terminal-UyHT9ie2.js → terminal-Bo6pnmSU.js} +1 -1
- package/dist/assets/{tracing-BiBPyXVg.js → tracing-Cmwuiwj9.js} +1 -1
- package/dist/assets/{tracing-panel-D8LtcU_g.js → tracing-panel-CCXeYfOu.js} +2 -2
- package/dist/assets/{tree-actions-DUAdNn1i.js → tree-actions-QMLf8Qhz.js} +1 -1
- package/dist/assets/{useBoolean-CgN_4RAl.js → useBoolean-C_UV6tQ7.js} +1 -1
- package/dist/assets/{useCellActionButton-Bp2eEIzO.js → useCellActionButton-D8qyXNKv.js} +1 -1
- package/dist/assets/{useDeleteCell-DO-9USfB.js → useDeleteCell-Ctnxl8AT.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-Bi8pUBnn.js → useDependencyPanelTab-CYSEZi6j.js} +1 -1
- package/dist/assets/useNotebookActions-CblNR75b.js +12 -0
- package/dist/assets/{useRunCells-BpZ_0Xuz.js → useRunCells-DjQN3XYP.js} +1 -1
- package/dist/assets/{useSplitCell-C2fjcfID.js → useSplitCell-BG8Zd4lw.js} +1 -1
- package/dist/assets/{vega-component-3G_aA1Wa.js → vega-component-1VoQOWvU.js} +1 -1
- package/dist/index.html +27 -27
- package/package.json +1 -1
- package/src/__mocks__/requests.ts +4 -0
- package/src/components/app-config/optional-features.test.ts +78 -0
- package/src/components/app-config/optional-features.tsx +33 -6
- package/src/components/editor/actions/export-dialog/__tests__/export-dialog.test.tsx +206 -34
- package/src/components/editor/actions/export-dialog/export-dialog.tsx +9 -3
- package/src/components/editor/actions/export-dialog/format-notice.tsx +69 -24
- package/src/components/editor/actions/export-dialog/use-export-dialog.ts +27 -2
- package/src/components/ui/toaster.tsx +14 -1
- package/src/core/codemirror/code-lens/__tests__/popover.test.ts +13 -0
- package/src/core/codemirror/code-lens/popover.tsx +11 -5
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/mode.ts +6 -1
- package/src/core/network/__tests__/requests-lazy.test.ts +40 -0
- package/src/core/network/__tests__/requests-network.test.ts +13 -0
- package/src/core/network/requests-lazy.ts +11 -4
- package/src/core/network/requests-network.ts +8 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +1 -0
- package/src/core/network/types.ts +5 -0
- package/src/core/wasm/bridge.ts +1 -0
- package/src/plugins/impl/MatrixPlugin.tsx +256 -35
- package/src/plugins/impl/__tests__/MatrixPlugin.test.tsx +540 -3
- package/dist/assets/app-config-button-C2zJXSrK.js +0 -1
- package/dist/assets/config-EoJoeZuK.js +0 -1
- package/dist/assets/index-C5WESBbZ.js +0 -38
- package/dist/assets/state-Ci7n55A9.js +0 -3
- package/dist/assets/useNotebookActions-Bm9VzJ_M.js +0 -12
|
@@ -54,6 +54,40 @@ export class MatrixPlugin implements IPlugin<T, Data> {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const PIXELS_PER_STEP = 10;
|
|
57
|
+
const COARSE_MULTIPLIER = 10;
|
|
58
|
+
const FINE_MULTIPLIER = 0.1;
|
|
59
|
+
|
|
60
|
+
/** Step multiplier from modifier keys: shift = coarse, alt = fine. */
|
|
61
|
+
const stepMultiplier = (e: { shiftKey: boolean; altKey: boolean }): number => {
|
|
62
|
+
if (e.shiftKey) {
|
|
63
|
+
return COARSE_MULTIPLIER;
|
|
64
|
+
}
|
|
65
|
+
if (e.altKey) {
|
|
66
|
+
return FINE_MULTIPLIER;
|
|
67
|
+
}
|
|
68
|
+
return 1;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Strip floating-point noise from step arithmetic (e.g. 3 * 0.1).
|
|
73
|
+
* The noise tolerance scales with the step so that legitimately tiny
|
|
74
|
+
* results (e.g. from a 2e-14 step) are preserved, while noise left over
|
|
75
|
+
* from arithmetic on ordinary magnitudes (e.g. the 5.55e-17 residue of
|
|
76
|
+
* 0.3 - 3 * 0.1) still snaps away.
|
|
77
|
+
*/
|
|
78
|
+
const cleanFloat = (x: number, step: number): number => {
|
|
79
|
+
const rounded = Number(x.toFixed(12));
|
|
80
|
+
const tolerance =
|
|
81
|
+
Number.EPSILON * Math.max(Math.abs(x), Math.abs(step)) * 100;
|
|
82
|
+
return Math.abs(x - rounded) <= tolerance ? rounded : x;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
interface EditState {
|
|
86
|
+
row: number;
|
|
87
|
+
col: number;
|
|
88
|
+
text: string;
|
|
89
|
+
selectAll: boolean;
|
|
90
|
+
}
|
|
57
91
|
|
|
58
92
|
interface MatrixComponentProps extends Data {
|
|
59
93
|
value: T;
|
|
@@ -80,6 +114,7 @@ const MatrixComponent = ({
|
|
|
80
114
|
col: number;
|
|
81
115
|
startX: number;
|
|
82
116
|
startValue: number;
|
|
117
|
+
multiplier: number;
|
|
83
118
|
} | null>(null);
|
|
84
119
|
const [activeCell, setActiveCell] = useState<{
|
|
85
120
|
row: number;
|
|
@@ -95,6 +130,16 @@ const MatrixComponent = ({
|
|
|
95
130
|
}, [value]);
|
|
96
131
|
const displayValue = activeCell == null ? value : draft;
|
|
97
132
|
|
|
133
|
+
// Editing state is mirrored in a ref so that the blur fired while
|
|
134
|
+
// committing (refocusing the cell unmounts the input) can't commit twice.
|
|
135
|
+
const [editing, setEditingState] = useState<EditState | null>(null);
|
|
136
|
+
const editingRef = useRef<EditState | null>(null);
|
|
137
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
138
|
+
const setEditing = useCallback((edit: EditState | null) => {
|
|
139
|
+
editingRef.current = edit;
|
|
140
|
+
setEditingState(edit);
|
|
141
|
+
}, []);
|
|
142
|
+
|
|
98
143
|
const formatValue = (val: number) =>
|
|
99
144
|
scientific ? val.toExponential(precision) : val.toFixed(precision);
|
|
100
145
|
|
|
@@ -112,22 +157,131 @@ const MatrixComponent = ({
|
|
|
112
157
|
[minValue, maxValue],
|
|
113
158
|
);
|
|
114
159
|
|
|
160
|
+
// The single mutation chokepoint for every edit path (drag, arrow, type).
|
|
161
|
+
// Bounds are enforced here so no path can escape them. In symmetric mode
|
|
162
|
+
// the pair must stay equal *and* respect both cells' bounds, so the shared
|
|
163
|
+
// value is clamped to the intersection of the two ranges. Returns null
|
|
164
|
+
// when the clamped value is already in place, so callers can skip
|
|
165
|
+
// redundant updates.
|
|
166
|
+
const withCellValue = useCallback(
|
|
167
|
+
(base: T, row: number, col: number, newValue: number): T | null => {
|
|
168
|
+
const mirrored = symmetric && row !== col;
|
|
169
|
+
let clamped = clampValue(newValue, row, col);
|
|
170
|
+
if (mirrored) {
|
|
171
|
+
clamped = clampValue(clamped, col, row);
|
|
172
|
+
}
|
|
173
|
+
if (clamped === base[row][col]) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const copy = base.map((r) => [...r]);
|
|
177
|
+
copy[row][col] = clamped;
|
|
178
|
+
if (mirrored) {
|
|
179
|
+
copy[col][row] = clamped;
|
|
180
|
+
}
|
|
181
|
+
return copy;
|
|
182
|
+
},
|
|
183
|
+
[symmetric, clampValue],
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const startEditing = useCallback(
|
|
187
|
+
(row: number, col: number, seed?: string) => {
|
|
188
|
+
if (disabled[row][col]) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
dragState.current = null;
|
|
192
|
+
setActiveCell(null);
|
|
193
|
+
setEditing({
|
|
194
|
+
row,
|
|
195
|
+
col,
|
|
196
|
+
text: seed ?? String(value[row][col]),
|
|
197
|
+
selectAll: seed == null,
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
[disabled, value, setEditing],
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Callback ref: focus the input when it mounts. When editing starts from
|
|
204
|
+
// a typed character, place the caret after the seed instead of selecting.
|
|
205
|
+
const focusInput = useCallback((el: HTMLInputElement | null) => {
|
|
206
|
+
inputRef.current = el;
|
|
207
|
+
if (el) {
|
|
208
|
+
el.focus();
|
|
209
|
+
if (editingRef.current?.selectAll) {
|
|
210
|
+
el.select();
|
|
211
|
+
} else {
|
|
212
|
+
el.setSelectionRange(el.value.length, el.value.length);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}, []);
|
|
216
|
+
|
|
217
|
+
const commitEdit = useCallback(
|
|
218
|
+
(refocusCell: boolean) => {
|
|
219
|
+
const edit = editingRef.current;
|
|
220
|
+
if (!edit) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const cell = refocusCell ? inputRef.current?.closest("td") : null;
|
|
224
|
+
setEditing(null);
|
|
225
|
+
const text = edit.text.trim();
|
|
226
|
+
const parsed = Number(text);
|
|
227
|
+
// Typed values are clamped to bounds but not snapped to `step`,
|
|
228
|
+
// so exact values like 2.32e7 survive.
|
|
229
|
+
if (text !== "" && Number.isFinite(parsed)) {
|
|
230
|
+
const copy = withCellValue(value, edit.row, edit.col, parsed);
|
|
231
|
+
if (copy) {
|
|
232
|
+
setDraft(copy);
|
|
233
|
+
setValue(copy);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
cell?.focus();
|
|
237
|
+
},
|
|
238
|
+
[value, withCellValue, setValue, setEditing],
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const cancelEdit = useCallback(() => {
|
|
242
|
+
const cell = inputRef.current?.closest("td");
|
|
243
|
+
setEditing(null);
|
|
244
|
+
cell?.focus();
|
|
245
|
+
}, [setEditing]);
|
|
246
|
+
|
|
247
|
+
const handleInputKeyDown = useCallback(
|
|
248
|
+
(e: React.KeyboardEvent) => {
|
|
249
|
+
e.stopPropagation();
|
|
250
|
+
if (e.key === "Enter") {
|
|
251
|
+
e.preventDefault();
|
|
252
|
+
commitEdit(true);
|
|
253
|
+
} else if (e.key === "Escape") {
|
|
254
|
+
e.preventDefault();
|
|
255
|
+
cancelEdit();
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
[commitEdit, cancelEdit],
|
|
259
|
+
);
|
|
260
|
+
|
|
115
261
|
const handlePointerDown = useCallback(
|
|
116
|
-
(e: React.PointerEvent
|
|
117
|
-
if (
|
|
262
|
+
(e: React.PointerEvent<HTMLTableCellElement>, row: number, col: number) => {
|
|
263
|
+
if (
|
|
264
|
+
disabled[row][col] ||
|
|
265
|
+
editing != null ||
|
|
266
|
+
!(e.target instanceof Element)
|
|
267
|
+
) {
|
|
118
268
|
return;
|
|
119
269
|
}
|
|
120
270
|
e.preventDefault();
|
|
271
|
+
// preventDefault also suppresses the browser's click-to-focus, so
|
|
272
|
+
// focus the cell explicitly to keep keyboard stepping reachable.
|
|
273
|
+
e.currentTarget.focus();
|
|
121
274
|
e.target.setPointerCapture(e.pointerId);
|
|
122
275
|
dragState.current = {
|
|
123
276
|
row,
|
|
124
277
|
col,
|
|
125
278
|
startX: e.clientX,
|
|
126
279
|
startValue: displayValue[row][col],
|
|
280
|
+
multiplier: stepMultiplier(e),
|
|
127
281
|
};
|
|
128
282
|
setActiveCell({ row, col });
|
|
129
283
|
},
|
|
130
|
-
[disabled, displayValue],
|
|
284
|
+
[disabled, editing, displayValue],
|
|
131
285
|
);
|
|
132
286
|
|
|
133
287
|
const handlePointerMove = useCallback(
|
|
@@ -136,26 +290,32 @@ const MatrixComponent = ({
|
|
|
136
290
|
if (!state) {
|
|
137
291
|
return;
|
|
138
292
|
}
|
|
293
|
+
const multiplier = stepMultiplier(e);
|
|
294
|
+
if (multiplier !== state.multiplier) {
|
|
295
|
+
// Rebase so toggling a modifier mid-drag rescales future movement
|
|
296
|
+
// instead of jumping the value.
|
|
297
|
+
state.startX = e.clientX;
|
|
298
|
+
state.startValue = displayValue[state.row][state.col];
|
|
299
|
+
state.multiplier = multiplier;
|
|
300
|
+
}
|
|
139
301
|
const { row, col, startX, startValue } = state;
|
|
140
302
|
const dx = e.clientX - startX;
|
|
141
|
-
const cellStep = step[row][col];
|
|
303
|
+
const cellStep = step[row][col] * multiplier;
|
|
142
304
|
const steps = Math.round(dx / PIXELS_PER_STEP);
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
copy[col][row] = newValue;
|
|
151
|
-
}
|
|
305
|
+
const copy = withCellValue(
|
|
306
|
+
displayValue,
|
|
307
|
+
row,
|
|
308
|
+
col,
|
|
309
|
+
cleanFloat(startValue + steps * cellStep, cellStep),
|
|
310
|
+
);
|
|
311
|
+
if (copy) {
|
|
152
312
|
setDraft(copy);
|
|
153
313
|
if (!debounce) {
|
|
154
314
|
setValue(copy);
|
|
155
315
|
}
|
|
156
316
|
}
|
|
157
317
|
},
|
|
158
|
-
[step,
|
|
318
|
+
[step, displayValue, withCellValue, debounce, setValue],
|
|
159
319
|
);
|
|
160
320
|
|
|
161
321
|
const handlePointerUp = useCallback(() => {
|
|
@@ -168,27 +328,61 @@ const MatrixComponent = ({
|
|
|
168
328
|
|
|
169
329
|
const handleKeyDown = useCallback(
|
|
170
330
|
(e: React.KeyboardEvent, row: number, col: number) => {
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
331
|
+
if (disabled[row][col]) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (e.key === "Enter" || e.key === "F2") {
|
|
175
335
|
e.preventDefault();
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
336
|
+
startEditing(row, col);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
// Typing a number starts editing, seeded with the typed character.
|
|
340
|
+
if (
|
|
341
|
+
e.key.length === 1 &&
|
|
342
|
+
/[\d.+-]/.test(e.key) &&
|
|
343
|
+
!e.ctrlKey &&
|
|
344
|
+
!e.metaKey &&
|
|
345
|
+
!e.altKey
|
|
346
|
+
) {
|
|
347
|
+
e.preventDefault();
|
|
348
|
+
startEditing(row, col, e.key);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const cellStep = step[row][col];
|
|
352
|
+
let delta: number;
|
|
353
|
+
switch (e.key) {
|
|
354
|
+
// Right/left mirror up/down, matching the horizontal drag direction
|
|
355
|
+
// (and the native slider convention).
|
|
356
|
+
case "ArrowUp":
|
|
357
|
+
case "ArrowRight":
|
|
358
|
+
delta = cellStep * stepMultiplier(e);
|
|
359
|
+
break;
|
|
360
|
+
case "ArrowDown":
|
|
361
|
+
case "ArrowLeft":
|
|
362
|
+
delta = -cellStep * stepMultiplier(e);
|
|
363
|
+
break;
|
|
364
|
+
case "PageUp":
|
|
365
|
+
delta = cellStep * COARSE_MULTIPLIER;
|
|
366
|
+
break;
|
|
367
|
+
case "PageDown":
|
|
368
|
+
delta = -cellStep * COARSE_MULTIPLIER;
|
|
369
|
+
break;
|
|
370
|
+
default:
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
e.preventDefault();
|
|
374
|
+
const copy = withCellValue(
|
|
375
|
+
displayValue,
|
|
376
|
+
row,
|
|
377
|
+
col,
|
|
378
|
+
cleanFloat(displayValue[row][col] + delta, delta),
|
|
379
|
+
);
|
|
380
|
+
if (copy) {
|
|
381
|
+
setDraft(copy);
|
|
382
|
+
setValue(copy);
|
|
189
383
|
}
|
|
190
384
|
},
|
|
191
|
-
[disabled, step, displayValue,
|
|
385
|
+
[disabled, startEditing, step, displayValue, withCellValue, setValue],
|
|
192
386
|
);
|
|
193
387
|
|
|
194
388
|
const hasRowLabels = rowLabels != null && rowLabels.length > 0;
|
|
@@ -238,6 +432,10 @@ const MatrixComponent = ({
|
|
|
238
432
|
const isDisabled = disabled[i][j];
|
|
239
433
|
const isActive =
|
|
240
434
|
activeCell?.row === i && activeCell?.col === j;
|
|
435
|
+
const cellEdit =
|
|
436
|
+
editing != null && editing.row === i && editing.col === j
|
|
437
|
+
? editing
|
|
438
|
+
: null;
|
|
241
439
|
const rowLabel = rowLabels?.[i] ?? `Row ${i + 1}`;
|
|
242
440
|
const colLabel = columnLabels?.[j] ?? `Column ${j + 1}`;
|
|
243
441
|
return (
|
|
@@ -247,24 +445,47 @@ const MatrixComponent = ({
|
|
|
247
445
|
"relative text-center min-w-14 h-8 px-2 transition-colors touch-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-hidden",
|
|
248
446
|
isDisabled
|
|
249
447
|
? "cursor-default text-muted-foreground"
|
|
250
|
-
:
|
|
251
|
-
|
|
448
|
+
: cellEdit
|
|
449
|
+
? "cursor-text"
|
|
450
|
+
: "cursor-ew-resize text-link hover:bg-accent",
|
|
451
|
+
(isActive || cellEdit != null) && "bg-accent",
|
|
252
452
|
j === 0 && "bracket-l",
|
|
253
453
|
j === numCols - 1 && "bracket-r",
|
|
254
454
|
i === 0 && "bracket-t",
|
|
255
455
|
i === numRows - 1 && "bracket-b",
|
|
256
456
|
)}
|
|
257
457
|
tabIndex={isDisabled ? -1 : 0}
|
|
458
|
+
title={String(cellValue)}
|
|
258
459
|
aria-label={`${rowLabel}, ${colLabel}`}
|
|
259
460
|
aria-valuenow={cellValue}
|
|
260
461
|
aria-valuemin={minValue?.[i]?.[j]}
|
|
261
462
|
aria-valuemax={maxValue?.[i]?.[j]}
|
|
262
463
|
aria-disabled={isDisabled || undefined}
|
|
263
464
|
onPointerDown={(e) => handlePointerDown(e, i, j)}
|
|
465
|
+
onDoubleClick={() => startEditing(i, j)}
|
|
264
466
|
onKeyDown={(e) => handleKeyDown(e, i, j)}
|
|
265
467
|
data-testid={`matrix-cell-${i}-${j}`}
|
|
266
468
|
>
|
|
267
|
-
{
|
|
469
|
+
{cellEdit ? (
|
|
470
|
+
<input
|
|
471
|
+
ref={focusInput}
|
|
472
|
+
className="bg-transparent text-center font-mono text-sm text-foreground outline-none select-text"
|
|
473
|
+
style={{
|
|
474
|
+
width: `${Math.max(cellEdit.text.length + 2, 6)}ch`,
|
|
475
|
+
}}
|
|
476
|
+
value={cellEdit.text}
|
|
477
|
+
onChange={(e) =>
|
|
478
|
+
setEditing({ ...cellEdit, text: e.target.value })
|
|
479
|
+
}
|
|
480
|
+
onKeyDown={handleInputKeyDown}
|
|
481
|
+
onBlur={() => commitEdit(false)}
|
|
482
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
483
|
+
aria-label={`Edit ${rowLabel}, ${colLabel}`}
|
|
484
|
+
data-testid={`matrix-input-${i}-${j}`}
|
|
485
|
+
/>
|
|
486
|
+
) : (
|
|
487
|
+
formatValue(cellValue)
|
|
488
|
+
)}
|
|
268
489
|
</td>
|
|
269
490
|
);
|
|
270
491
|
})}
|