@schukai/monster 4.110.0 → 4.111.0
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 +8 -0
- package/package.json +1 -1
- package/source/components/form/sheet.mjs +1015 -0
- package/source/types/version.mjs +1 -1
- package/test/cases/monster.mjs +1 -1
- package/test/web/test.html +2 -2
- package/test/web/tests.js +3 -3
|
@@ -46,6 +46,16 @@ const lastViewportSymbol = Symbol("sheetLastViewport");
|
|
|
46
46
|
const scrollFrameSymbol = Symbol("sheetScrollFrame");
|
|
47
47
|
const forceRenderSymbol = Symbol("sheetForceRender");
|
|
48
48
|
const resizeFrameSymbol = Symbol("sheetResizeFrame");
|
|
49
|
+
const selectionSymbol = Symbol("sheetSelection");
|
|
50
|
+
const selectionBoxSymbol = Symbol("sheetSelectionBox");
|
|
51
|
+
const fillHandleSymbol = Symbol("sheetFillHandle");
|
|
52
|
+
const statusSymbol = Symbol("sheetStatus");
|
|
53
|
+
const statusTimeoutSymbol = Symbol("sheetStatusTimeout");
|
|
54
|
+
const contextMenuSymbol = Symbol("sheetContextMenu");
|
|
55
|
+
const menuStateSymbol = Symbol("sheetMenuState");
|
|
56
|
+
const dragFillSymbol = Symbol("sheetDragFill");
|
|
57
|
+
const lastCopySymbol = Symbol("sheetLastCopy");
|
|
58
|
+
const dragSelectSymbol = Symbol("sheetDragSelect");
|
|
49
59
|
|
|
50
60
|
class Sheet extends CustomControl {
|
|
51
61
|
static get [instanceSymbol]() {
|
|
@@ -144,6 +154,16 @@ function initControlReferences() {
|
|
|
144
154
|
this[addColumnButtonSymbol] = root.querySelector(
|
|
145
155
|
`[${ATTRIBUTE_ROLE}=add-column]`,
|
|
146
156
|
);
|
|
157
|
+
this[selectionBoxSymbol] = root.querySelector(
|
|
158
|
+
`[${ATTRIBUTE_ROLE}=selection]`,
|
|
159
|
+
);
|
|
160
|
+
this[fillHandleSymbol] = root.querySelector(
|
|
161
|
+
`[${ATTRIBUTE_ROLE}=fill-handle]`,
|
|
162
|
+
);
|
|
163
|
+
this[statusSymbol] = root.querySelector(`[${ATTRIBUTE_ROLE}=status]`);
|
|
164
|
+
this[contextMenuSymbol] = root.querySelector(
|
|
165
|
+
`[${ATTRIBUTE_ROLE}=context-menu]`,
|
|
166
|
+
);
|
|
147
167
|
}
|
|
148
168
|
|
|
149
169
|
function initEventHandler() {
|
|
@@ -181,6 +201,14 @@ function initEventHandler() {
|
|
|
181
201
|
if (isString(formula)) {
|
|
182
202
|
input.value = formula;
|
|
183
203
|
}
|
|
204
|
+
const selection = this[selectionSymbol];
|
|
205
|
+
const isMulti =
|
|
206
|
+
selection?.anchor &&
|
|
207
|
+
selection?.focus &&
|
|
208
|
+
(selection.anchor.rowId !== selection.focus.rowId ||
|
|
209
|
+
selection.anchor.colId !== selection.focus.colId);
|
|
210
|
+
if (!event.shiftKey && isMulti) return;
|
|
211
|
+
setSelectionFromCell.call(this, rowId, colId, event.shiftKey);
|
|
184
212
|
});
|
|
185
213
|
|
|
186
214
|
this[gridElementSymbol].addEventListener("focusout", (event) => {
|
|
@@ -195,6 +223,7 @@ function initEventHandler() {
|
|
|
195
223
|
});
|
|
196
224
|
|
|
197
225
|
this[gridElementSymbol].addEventListener("pointerdown", (event) => {
|
|
226
|
+
if (event.button !== 0) return;
|
|
198
227
|
const columnHandle = event.target.closest(
|
|
199
228
|
`[${ATTRIBUTE_ROLE}=column-resize]`,
|
|
200
229
|
);
|
|
@@ -208,9 +237,55 @@ function initEventHandler() {
|
|
|
208
237
|
if (rowHandle) {
|
|
209
238
|
if (!this.getOption("features.resizeRows", true)) return;
|
|
210
239
|
startRowResize.call(this, event, rowHandle);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const cell = event.target.closest(`[${ATTRIBUTE_ROLE}=cell]`);
|
|
244
|
+
if (!cell) return;
|
|
245
|
+
const rowId = cell.dataset.rowId;
|
|
246
|
+
const colId = cell.dataset.colId;
|
|
247
|
+
if (!rowId || !colId) return;
|
|
248
|
+
setSelectionFromCell.call(this, rowId, colId, event.shiftKey);
|
|
249
|
+
startSelectionDrag.call(this, event);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
this[gridElementSymbol].addEventListener("keydown", (event) => {
|
|
253
|
+
if (!(event.ctrlKey || event.metaKey)) return;
|
|
254
|
+
if (event.key === "c" || event.key === "C") {
|
|
255
|
+
event.preventDefault();
|
|
256
|
+
void copySelectionToClipboard.call(this);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (event.key === "x" || event.key === "X") {
|
|
260
|
+
event.preventDefault();
|
|
261
|
+
void cutSelectionToClipboard.call(this);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (event.key === "v" || event.key === "V") {
|
|
265
|
+
event.preventDefault();
|
|
266
|
+
void pasteFromClipboard.call(this);
|
|
211
267
|
}
|
|
212
268
|
});
|
|
213
269
|
|
|
270
|
+
this[gridElementSymbol].addEventListener("contextmenu", (event) => {
|
|
271
|
+
const cell = event.target.closest(`[${ATTRIBUTE_ROLE}=cell]`);
|
|
272
|
+
if (!cell) return;
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
const rowId = cell.dataset.rowId;
|
|
275
|
+
const colId = cell.dataset.colId;
|
|
276
|
+
if (rowId && colId) {
|
|
277
|
+
setSelectionFromCell.call(this, rowId, colId, false);
|
|
278
|
+
}
|
|
279
|
+
showContextMenu.call(this, event.clientX, event.clientY);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
this.shadowRoot.addEventListener("pointerdown", (event) => {
|
|
283
|
+
const menu = this[contextMenuSymbol];
|
|
284
|
+
if (!menu || menu.hidden) return;
|
|
285
|
+
if (menu.contains(event.target)) return;
|
|
286
|
+
hideContextMenu.call(this);
|
|
287
|
+
});
|
|
288
|
+
|
|
214
289
|
if (this[gridWrapperSymbol]) {
|
|
215
290
|
this[gridWrapperSymbol].addEventListener("scroll", () => {
|
|
216
291
|
if (!this.getOption("features.virtualize", false)) return;
|
|
@@ -221,6 +296,20 @@ function initEventHandler() {
|
|
|
221
296
|
});
|
|
222
297
|
});
|
|
223
298
|
}
|
|
299
|
+
|
|
300
|
+
if (this[gridWrapperSymbol]) {
|
|
301
|
+
this[gridWrapperSymbol].addEventListener("scroll", () => {
|
|
302
|
+
hideContextMenu.call(this);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (this[fillHandleSymbol]) {
|
|
307
|
+
this[fillHandleSymbol].addEventListener("pointerdown", (event) => {
|
|
308
|
+
startFillDrag.call(this, event);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
wireContextMenuActions.call(this);
|
|
224
313
|
}
|
|
225
314
|
|
|
226
315
|
function initOptionObserver() {
|
|
@@ -280,6 +369,7 @@ function updateControl() {
|
|
|
280
369
|
}
|
|
281
370
|
syncToolbarState.call(this);
|
|
282
371
|
applyDisabledState.call(this);
|
|
372
|
+
updateSelectionDisplay.call(this);
|
|
283
373
|
}
|
|
284
374
|
|
|
285
375
|
function syncToolbarState() {
|
|
@@ -303,6 +393,7 @@ function syncToolbarState() {
|
|
|
303
393
|
if (isString(labels.addColumn)) {
|
|
304
394
|
this[addColumnButtonSymbol].textContent = labels.addColumn;
|
|
305
395
|
}
|
|
396
|
+
syncContextMenuLabels.call(this);
|
|
306
397
|
}
|
|
307
398
|
|
|
308
399
|
function applyDisabledState() {
|
|
@@ -1029,6 +1120,802 @@ function refreshDisplayValues() {
|
|
|
1029
1120
|
});
|
|
1030
1121
|
}
|
|
1031
1122
|
|
|
1123
|
+
function setSelectionFromCell(rowId, colId, expand) {
|
|
1124
|
+
hideContextMenu.call(this);
|
|
1125
|
+
const selection = this[selectionSymbol] || {};
|
|
1126
|
+
if (expand && selection.anchor) {
|
|
1127
|
+
selection.focus = { rowId, colId };
|
|
1128
|
+
} else {
|
|
1129
|
+
selection.anchor = { rowId, colId };
|
|
1130
|
+
selection.focus = { rowId, colId };
|
|
1131
|
+
}
|
|
1132
|
+
this[selectionSymbol] = selection;
|
|
1133
|
+
updateSelectionDisplay.call(this);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function setSelectionFocus(rowId, colId) {
|
|
1137
|
+
const selection = this[selectionSymbol] || {};
|
|
1138
|
+
if (!selection.anchor) {
|
|
1139
|
+
selection.anchor = { rowId, colId };
|
|
1140
|
+
}
|
|
1141
|
+
selection.focus = { rowId, colId };
|
|
1142
|
+
this[selectionSymbol] = selection;
|
|
1143
|
+
updateSelectionDisplay.call(this);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function startSelectionDrag(event) {
|
|
1147
|
+
if (event.button !== 0) return;
|
|
1148
|
+
const cell = event.target.closest(`[${ATTRIBUTE_ROLE}=cell]`);
|
|
1149
|
+
if (!cell) return;
|
|
1150
|
+
const state = {
|
|
1151
|
+
active: true,
|
|
1152
|
+
startX: event.clientX,
|
|
1153
|
+
startY: event.clientY,
|
|
1154
|
+
moved: false,
|
|
1155
|
+
};
|
|
1156
|
+
this[dragSelectSymbol] = state;
|
|
1157
|
+
this.setAttribute("data-selecting", "");
|
|
1158
|
+
const move = (moveEvent) => {
|
|
1159
|
+
handleSelectionDragMove.call(this, moveEvent);
|
|
1160
|
+
};
|
|
1161
|
+
const end = () => {
|
|
1162
|
+
handleSelectionDragEnd.call(this);
|
|
1163
|
+
};
|
|
1164
|
+
window.addEventListener("pointermove", move);
|
|
1165
|
+
window.addEventListener("pointerup", end, { once: true });
|
|
1166
|
+
window.addEventListener("pointercancel", end, { once: true });
|
|
1167
|
+
state.cleanup = () => {
|
|
1168
|
+
window.removeEventListener("pointermove", move);
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
function handleSelectionDragMove(event) {
|
|
1173
|
+
const state = this[dragSelectSymbol];
|
|
1174
|
+
if (!state?.active) return;
|
|
1175
|
+
if (!state.moved) {
|
|
1176
|
+
const dx = event.clientX - state.startX;
|
|
1177
|
+
const dy = event.clientY - state.startY;
|
|
1178
|
+
if (Math.abs(dx) < 3 && Math.abs(dy) < 3) return;
|
|
1179
|
+
state.moved = true;
|
|
1180
|
+
}
|
|
1181
|
+
const cell = getCellFromPoint.call(this, event.clientX, event.clientY);
|
|
1182
|
+
if (!cell) return;
|
|
1183
|
+
const rowId = cell.dataset.rowId;
|
|
1184
|
+
const colId = cell.dataset.colId;
|
|
1185
|
+
if (!rowId || !colId) return;
|
|
1186
|
+
setSelectionFocus.call(this, rowId, colId);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function handleSelectionDragEnd() {
|
|
1190
|
+
const state = this[dragSelectSymbol];
|
|
1191
|
+
if (!state) return;
|
|
1192
|
+
if (state.cleanup) state.cleanup();
|
|
1193
|
+
this[dragSelectSymbol] = null;
|
|
1194
|
+
this.removeAttribute("data-selecting");
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function updateSelectionDisplay() {
|
|
1198
|
+
const grid = this[gridElementSymbol];
|
|
1199
|
+
if (!grid) return;
|
|
1200
|
+
const data = normalizeValue.call(this);
|
|
1201
|
+
const selection = this[selectionSymbol];
|
|
1202
|
+
const range = selection ? getSelectionRange(selection, data) : null;
|
|
1203
|
+
const virtualize = this.getOption("features.virtualize", false) === true;
|
|
1204
|
+
|
|
1205
|
+
const cells = grid.querySelectorAll(`[${ATTRIBUTE_ROLE}=cell]`);
|
|
1206
|
+
let overlay = null;
|
|
1207
|
+
const wrapper = this[gridWrapperSymbol];
|
|
1208
|
+
const wrapperRect = wrapper?.getBoundingClientRect() ?? null;
|
|
1209
|
+
let minLeft = Infinity;
|
|
1210
|
+
let minTop = Infinity;
|
|
1211
|
+
let maxRight = -Infinity;
|
|
1212
|
+
let maxBottom = -Infinity;
|
|
1213
|
+
|
|
1214
|
+
cells.forEach((cell) => {
|
|
1215
|
+
const rowId = cell.dataset.rowId;
|
|
1216
|
+
const colId = cell.dataset.colId;
|
|
1217
|
+
let selected = false;
|
|
1218
|
+
if (range && rowId && colId) {
|
|
1219
|
+
const { rowIndexById, colIndexById } = range;
|
|
1220
|
+
const rowIndex = rowIndexById.get(rowId);
|
|
1221
|
+
const colIndex = colIndexById.get(colId);
|
|
1222
|
+
if (
|
|
1223
|
+
rowIndex !== undefined &&
|
|
1224
|
+
colIndex !== undefined &&
|
|
1225
|
+
rowIndex >= range.rowStart &&
|
|
1226
|
+
rowIndex <= range.rowEnd &&
|
|
1227
|
+
colIndex >= range.colStart &&
|
|
1228
|
+
colIndex <= range.colEnd
|
|
1229
|
+
) {
|
|
1230
|
+
selected = true;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
if (selected) {
|
|
1235
|
+
cell.dataset.selected = "true";
|
|
1236
|
+
if (wrapperRect) {
|
|
1237
|
+
const rect = cell.getBoundingClientRect();
|
|
1238
|
+
minLeft = Math.min(minLeft, rect.left);
|
|
1239
|
+
minTop = Math.min(minTop, rect.top);
|
|
1240
|
+
maxRight = Math.max(maxRight, rect.right);
|
|
1241
|
+
maxBottom = Math.max(maxBottom, rect.bottom);
|
|
1242
|
+
}
|
|
1243
|
+
} else {
|
|
1244
|
+
delete cell.dataset.selected;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
if (
|
|
1248
|
+
selection?.focus?.rowId === rowId &&
|
|
1249
|
+
selection?.focus?.colId === colId
|
|
1250
|
+
) {
|
|
1251
|
+
cell.dataset.active = "true";
|
|
1252
|
+
} else {
|
|
1253
|
+
delete cell.dataset.active;
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
if (Number.isFinite(minLeft) && Number.isFinite(maxRight) && wrapperRect) {
|
|
1258
|
+
overlay = {
|
|
1259
|
+
left: minLeft - wrapperRect.left + wrapper.scrollLeft,
|
|
1260
|
+
top: minTop - wrapperRect.top + wrapper.scrollTop,
|
|
1261
|
+
width: Math.max(0, maxRight - minLeft),
|
|
1262
|
+
height: Math.max(0, maxBottom - minTop),
|
|
1263
|
+
};
|
|
1264
|
+
} else if (range && virtualize) {
|
|
1265
|
+
const sizes = getVirtualSizes.call(this, data);
|
|
1266
|
+
const colStart = range.colStart;
|
|
1267
|
+
const colEnd = range.colEnd;
|
|
1268
|
+
const rowStart = range.rowStart;
|
|
1269
|
+
const rowEnd = range.rowEnd;
|
|
1270
|
+
const left = sizes.rowHeaderWidth + sizes.columnOffsets[colStart];
|
|
1271
|
+
const top = sizes.headerHeight + sizes.rowOffsets[rowStart];
|
|
1272
|
+
const width =
|
|
1273
|
+
sizes.columnOffsets[colEnd] +
|
|
1274
|
+
sizes.columnWidths[colEnd] -
|
|
1275
|
+
sizes.columnOffsets[colStart];
|
|
1276
|
+
const height =
|
|
1277
|
+
sizes.rowOffsets[rowEnd] +
|
|
1278
|
+
sizes.rowHeights[rowEnd] -
|
|
1279
|
+
sizes.rowOffsets[rowStart];
|
|
1280
|
+
overlay = { left, top, width, height };
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
updateSelectionOverlay.call(this, overlay);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
function updateSelectionOverlay(bounds) {
|
|
1287
|
+
const box = this[selectionBoxSymbol];
|
|
1288
|
+
const wrapper = this[gridWrapperSymbol];
|
|
1289
|
+
const handle = this[fillHandleSymbol];
|
|
1290
|
+
if (!box || !wrapper || !handle) return;
|
|
1291
|
+
if (!bounds) {
|
|
1292
|
+
box.style.display = "none";
|
|
1293
|
+
handle.style.display = "none";
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
box.style.display = "block";
|
|
1297
|
+
box.style.left = `${bounds.left}px`;
|
|
1298
|
+
box.style.top = `${bounds.top}px`;
|
|
1299
|
+
box.style.width = `${bounds.width}px`;
|
|
1300
|
+
box.style.height = `${bounds.height}px`;
|
|
1301
|
+
handle.style.display = "block";
|
|
1302
|
+
handle.style.left = `${bounds.left + bounds.width - 4}px`;
|
|
1303
|
+
handle.style.top = `${bounds.top + bounds.height - 4}px`;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
function getSelectionRange(selection, data) {
|
|
1307
|
+
if (!selection?.anchor || !selection?.focus) return null;
|
|
1308
|
+
const { rowIndexById, colIndexById } = getIndexMaps(data);
|
|
1309
|
+
const startRow = rowIndexById.get(selection.anchor.rowId);
|
|
1310
|
+
const endRow = rowIndexById.get(selection.focus.rowId);
|
|
1311
|
+
const startCol = colIndexById.get(selection.anchor.colId);
|
|
1312
|
+
const endCol = colIndexById.get(selection.focus.colId);
|
|
1313
|
+
if (
|
|
1314
|
+
startRow === undefined ||
|
|
1315
|
+
endRow === undefined ||
|
|
1316
|
+
startCol === undefined ||
|
|
1317
|
+
endCol === undefined
|
|
1318
|
+
) {
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
return {
|
|
1322
|
+
rowStart: Math.min(startRow, endRow),
|
|
1323
|
+
rowEnd: Math.max(startRow, endRow),
|
|
1324
|
+
colStart: Math.min(startCol, endCol),
|
|
1325
|
+
colEnd: Math.max(startCol, endCol),
|
|
1326
|
+
rowIndexById,
|
|
1327
|
+
colIndexById,
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function getIndexMaps(data) {
|
|
1332
|
+
const rowIndexById = new Map();
|
|
1333
|
+
const colIndexById = new Map();
|
|
1334
|
+
data.rows.forEach((row, index) => {
|
|
1335
|
+
if (row?.id) rowIndexById.set(row.id, index);
|
|
1336
|
+
});
|
|
1337
|
+
data.columns.forEach((col, index) => {
|
|
1338
|
+
if (col?.id) colIndexById.set(col.id, index);
|
|
1339
|
+
});
|
|
1340
|
+
return { rowIndexById, colIndexById };
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
function getActiveCell() {
|
|
1344
|
+
const active = this.shadowRoot?.activeElement;
|
|
1345
|
+
if (!(active instanceof HTMLInputElement)) return null;
|
|
1346
|
+
const cell = active.closest(`[${ATTRIBUTE_ROLE}=cell]`);
|
|
1347
|
+
if (!cell) return null;
|
|
1348
|
+
const rowId = cell.dataset.rowId;
|
|
1349
|
+
const colId = cell.dataset.colId;
|
|
1350
|
+
if (!rowId || !colId) return null;
|
|
1351
|
+
return { rowId, colId };
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
async function copySelectionToClipboard() {
|
|
1355
|
+
const data = normalizeValue.call(this);
|
|
1356
|
+
const range = getSelectionRange.call(this, this[selectionSymbol], data);
|
|
1357
|
+
const resolvedRange = range ?? getRangeFromActiveCell.call(this, data);
|
|
1358
|
+
if (!resolvedRange) return;
|
|
1359
|
+
|
|
1360
|
+
const text = buildClipboardText(data, resolvedRange);
|
|
1361
|
+
this[lastCopySymbol] = {
|
|
1362
|
+
text,
|
|
1363
|
+
range: {
|
|
1364
|
+
rowStart: resolvedRange.rowStart,
|
|
1365
|
+
rowEnd: resolvedRange.rowEnd,
|
|
1366
|
+
colStart: resolvedRange.colStart,
|
|
1367
|
+
colEnd: resolvedRange.colEnd,
|
|
1368
|
+
},
|
|
1369
|
+
values: buildClipboardMatrix(data, resolvedRange),
|
|
1370
|
+
};
|
|
1371
|
+
const success = await writeClipboardText(text);
|
|
1372
|
+
if (success) {
|
|
1373
|
+
showStatus.call(this, this.getOption("labels.copied") || "Copied");
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
async function cutSelectionToClipboard() {
|
|
1378
|
+
const data = normalizeValue.call(this);
|
|
1379
|
+
const range = getSelectionRange.call(this, this[selectionSymbol], data);
|
|
1380
|
+
const resolvedRange = range ?? getRangeFromActiveCell.call(this, data);
|
|
1381
|
+
if (!resolvedRange) return;
|
|
1382
|
+
|
|
1383
|
+
const text = buildClipboardText(data, resolvedRange);
|
|
1384
|
+
const success = await writeClipboardText(text);
|
|
1385
|
+
if (!success) return;
|
|
1386
|
+
|
|
1387
|
+
const next = clearRange(data, resolvedRange);
|
|
1388
|
+
this.value = next;
|
|
1389
|
+
showStatus.call(this, this.getOption("labels.cutDone") || "Cut");
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
async function pasteFromClipboard() {
|
|
1393
|
+
const text = await readClipboardText();
|
|
1394
|
+
if (text === null) return;
|
|
1395
|
+
const data = normalizeValue.call(this);
|
|
1396
|
+
const range = getSelectionRange.call(this, this[selectionSymbol], data);
|
|
1397
|
+
const startRange = range ?? getRangeFromActiveCell.call(this, data);
|
|
1398
|
+
if (!startRange) return;
|
|
1399
|
+
|
|
1400
|
+
const { startRow, startCol } = {
|
|
1401
|
+
startRow: startRange.rowStart,
|
|
1402
|
+
startCol: startRange.colStart,
|
|
1403
|
+
};
|
|
1404
|
+
const cached = this[lastCopySymbol];
|
|
1405
|
+
const next =
|
|
1406
|
+
cached && cached.text === text
|
|
1407
|
+
? applyPasteCached(data, cached, startRow, startCol)
|
|
1408
|
+
: applyPasteText(data, text, startRow, startCol);
|
|
1409
|
+
this.value = next.value;
|
|
1410
|
+
setSelectionByRange.call(this, next.range, next.value);
|
|
1411
|
+
showStatus.call(this, this.getOption("labels.pasted") || "Pasted");
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
function buildClipboardText(data, range) {
|
|
1415
|
+
const rows = [];
|
|
1416
|
+
for (let r = range.rowStart; r <= range.rowEnd; r += 1) {
|
|
1417
|
+
const rowId = data.rows[r]?.id;
|
|
1418
|
+
if (!rowId) continue;
|
|
1419
|
+
const cols = [];
|
|
1420
|
+
for (let c = range.colStart; c <= range.colEnd; c += 1) {
|
|
1421
|
+
const colId = data.columns[c]?.id;
|
|
1422
|
+
if (!colId) continue;
|
|
1423
|
+
const value = getCellRawValue(data, rowId, colId);
|
|
1424
|
+
cols.push(value);
|
|
1425
|
+
}
|
|
1426
|
+
rows.push(cols.join("\t"));
|
|
1427
|
+
}
|
|
1428
|
+
return rows.join("\n");
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function buildClipboardMatrix(data, range) {
|
|
1432
|
+
const rows = [];
|
|
1433
|
+
for (let r = range.rowStart; r <= range.rowEnd; r += 1) {
|
|
1434
|
+
const rowId = data.rows[r]?.id;
|
|
1435
|
+
if (!rowId) continue;
|
|
1436
|
+
const cols = [];
|
|
1437
|
+
for (let c = range.colStart; c <= range.colEnd; c += 1) {
|
|
1438
|
+
const colId = data.columns[c]?.id;
|
|
1439
|
+
if (!colId) continue;
|
|
1440
|
+
cols.push(getCellRawValue(data, rowId, colId));
|
|
1441
|
+
}
|
|
1442
|
+
rows.push(cols);
|
|
1443
|
+
}
|
|
1444
|
+
return rows;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
function getCellRawValue(data, rowId, colId) {
|
|
1448
|
+
const formula = data.formulas?.[rowId]?.[colId];
|
|
1449
|
+
if (isString(formula)) return formula;
|
|
1450
|
+
const raw = data.cells?.[rowId]?.[colId];
|
|
1451
|
+
return raw === undefined || raw === null ? "" : String(raw);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
function clearRange(data, range) {
|
|
1455
|
+
const next = {
|
|
1456
|
+
...data,
|
|
1457
|
+
cells: data.cells ? { ...data.cells } : {},
|
|
1458
|
+
formulas: data.formulas ? { ...data.formulas } : {},
|
|
1459
|
+
};
|
|
1460
|
+
for (let r = range.rowStart; r <= range.rowEnd; r += 1) {
|
|
1461
|
+
const rowId = data.rows[r]?.id;
|
|
1462
|
+
if (!rowId) continue;
|
|
1463
|
+
for (let c = range.colStart; c <= range.colEnd; c += 1) {
|
|
1464
|
+
const colId = data.columns[c]?.id;
|
|
1465
|
+
if (!colId) continue;
|
|
1466
|
+
if (next.cells[rowId]) delete next.cells[rowId][colId];
|
|
1467
|
+
if (next.formulas[rowId]) delete next.formulas[rowId][colId];
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return next;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function getRangeFromActiveCell(data) {
|
|
1474
|
+
const active = getActiveCell.call(this);
|
|
1475
|
+
if (!active) return null;
|
|
1476
|
+
const { rowIndexById, colIndexById } = getIndexMaps(data);
|
|
1477
|
+
const rowIndex = rowIndexById.get(active.rowId);
|
|
1478
|
+
const colIndex = colIndexById.get(active.colId);
|
|
1479
|
+
if (rowIndex === undefined || colIndex === undefined) return null;
|
|
1480
|
+
return {
|
|
1481
|
+
rowStart: rowIndex,
|
|
1482
|
+
rowEnd: rowIndex,
|
|
1483
|
+
colStart: colIndex,
|
|
1484
|
+
colEnd: colIndex,
|
|
1485
|
+
rowIndexById,
|
|
1486
|
+
colIndexById,
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
function applyPasteText(data, text, startRowIndex, startColIndex) {
|
|
1491
|
+
const rows = normalizeClipboardRows(text);
|
|
1492
|
+
const next = {
|
|
1493
|
+
...data,
|
|
1494
|
+
cells: data.cells ? { ...data.cells } : {},
|
|
1495
|
+
formulas: data.formulas ? { ...data.formulas } : {},
|
|
1496
|
+
};
|
|
1497
|
+
let rowEnd = startRowIndex;
|
|
1498
|
+
let colEnd = startColIndex;
|
|
1499
|
+
|
|
1500
|
+
rows.forEach((rowText, rowOffset) => {
|
|
1501
|
+
const rowIndex = startRowIndex + rowOffset;
|
|
1502
|
+
if (rowIndex >= data.rows.length) return;
|
|
1503
|
+
const rowId = data.rows[rowIndex]?.id;
|
|
1504
|
+
if (!rowId) return;
|
|
1505
|
+
const cols = rowText.split("\t");
|
|
1506
|
+
cols.forEach((cellText, colOffset) => {
|
|
1507
|
+
const colIndex = startColIndex + colOffset;
|
|
1508
|
+
if (colIndex >= data.columns.length) return;
|
|
1509
|
+
const colId = data.columns[colIndex]?.id;
|
|
1510
|
+
if (!colId) return;
|
|
1511
|
+
setCellData(next, rowId, colId, cellText);
|
|
1512
|
+
rowEnd = Math.max(rowEnd, rowIndex);
|
|
1513
|
+
colEnd = Math.max(colEnd, colIndex);
|
|
1514
|
+
});
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
return {
|
|
1518
|
+
value: next,
|
|
1519
|
+
range: {
|
|
1520
|
+
rowStart: startRowIndex,
|
|
1521
|
+
rowEnd,
|
|
1522
|
+
colStart: startColIndex,
|
|
1523
|
+
colEnd,
|
|
1524
|
+
},
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function applyPasteCached(data, cached, startRowIndex, startColIndex) {
|
|
1529
|
+
const next = {
|
|
1530
|
+
...data,
|
|
1531
|
+
cells: data.cells ? { ...data.cells } : {},
|
|
1532
|
+
formulas: data.formulas ? { ...data.formulas } : {},
|
|
1533
|
+
};
|
|
1534
|
+
const deltaRow = startRowIndex - cached.range.rowStart;
|
|
1535
|
+
const deltaCol = startColIndex - cached.range.colStart;
|
|
1536
|
+
let rowEnd = startRowIndex;
|
|
1537
|
+
let colEnd = startColIndex;
|
|
1538
|
+
|
|
1539
|
+
cached.values.forEach((rowValues, rowOffset) => {
|
|
1540
|
+
const rowIndex = startRowIndex + rowOffset;
|
|
1541
|
+
if (rowIndex >= data.rows.length) return;
|
|
1542
|
+
const rowId = data.rows[rowIndex]?.id;
|
|
1543
|
+
if (!rowId) return;
|
|
1544
|
+
rowValues.forEach((cellValue, colOffset) => {
|
|
1545
|
+
const colIndex = startColIndex + colOffset;
|
|
1546
|
+
if (colIndex >= data.columns.length) return;
|
|
1547
|
+
const colId = data.columns[colIndex]?.id;
|
|
1548
|
+
if (!colId) return;
|
|
1549
|
+
let nextValue = cellValue;
|
|
1550
|
+
if (isFormulaString(cellValue)) {
|
|
1551
|
+
nextValue = adjustFormulaReferences(cellValue, deltaRow, deltaCol);
|
|
1552
|
+
}
|
|
1553
|
+
setCellData(next, rowId, colId, nextValue);
|
|
1554
|
+
rowEnd = Math.max(rowEnd, rowIndex);
|
|
1555
|
+
colEnd = Math.max(colEnd, colIndex);
|
|
1556
|
+
});
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
return {
|
|
1560
|
+
value: next,
|
|
1561
|
+
range: {
|
|
1562
|
+
rowStart: startRowIndex,
|
|
1563
|
+
rowEnd,
|
|
1564
|
+
colStart: startColIndex,
|
|
1565
|
+
colEnd,
|
|
1566
|
+
},
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
function normalizeClipboardRows(text) {
|
|
1571
|
+
const normalized = String(text).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1572
|
+
const rows = normalized.split("\n");
|
|
1573
|
+
while (rows.length > 1 && rows[rows.length - 1] === "") {
|
|
1574
|
+
rows.pop();
|
|
1575
|
+
}
|
|
1576
|
+
return rows;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
function setCellData(data, rowId, colId, value) {
|
|
1580
|
+
if (!data.cells) data.cells = {};
|
|
1581
|
+
if (!data.formulas) data.formulas = {};
|
|
1582
|
+
if (!data.cells[rowId]) data.cells[rowId] = {};
|
|
1583
|
+
if (!data.formulas[rowId]) data.formulas[rowId] = {};
|
|
1584
|
+
const next = String(value ?? "");
|
|
1585
|
+
if (next.trim().startsWith("=")) {
|
|
1586
|
+
data.formulas[rowId][colId] = next.trim();
|
|
1587
|
+
delete data.cells[rowId][colId];
|
|
1588
|
+
} else {
|
|
1589
|
+
delete data.formulas[rowId][colId];
|
|
1590
|
+
data.cells[rowId][colId] = next;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
function isFormulaString(value) {
|
|
1595
|
+
return isString(value) && value.trim().startsWith("=");
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
function adjustFormulaReferences(formula, deltaRow, deltaCol) {
|
|
1599
|
+
const expr = formula.trim();
|
|
1600
|
+
if (!expr.startsWith("=")) return formula;
|
|
1601
|
+
const body = expr.slice(1);
|
|
1602
|
+
const adjusted = body.replace(/([A-Za-z]+)([0-9]+)/g, (match, col, row) => {
|
|
1603
|
+
const colIndex = columnLabelToIndex(col);
|
|
1604
|
+
if (colIndex === null) return match;
|
|
1605
|
+
const rowIndex = Number(row) - 1;
|
|
1606
|
+
if (!Number.isFinite(rowIndex)) return match;
|
|
1607
|
+
const nextCol = colIndex + deltaCol;
|
|
1608
|
+
const nextRow = rowIndex + deltaRow;
|
|
1609
|
+
if (nextCol < 0 || nextRow < 0) return match;
|
|
1610
|
+
return `${indexToColumnLabel(nextCol)}${nextRow + 1}`;
|
|
1611
|
+
});
|
|
1612
|
+
return `=${adjusted}`;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
function columnLabelToIndex(label) {
|
|
1616
|
+
const text = String(label || "").toUpperCase();
|
|
1617
|
+
if (!/^[A-Z]+$/.test(text)) return null;
|
|
1618
|
+
let index = 0;
|
|
1619
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
1620
|
+
index = index * 26 + (text.charCodeAt(i) - 64);
|
|
1621
|
+
}
|
|
1622
|
+
return index - 1;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function indexToColumnLabel(index) {
|
|
1626
|
+
if (!Number.isFinite(index) || index < 0) return "A";
|
|
1627
|
+
return nextColumnLabel(index);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
function setSelectionByRange(range, data) {
|
|
1631
|
+
if (!range) return;
|
|
1632
|
+
const rows = data.rows;
|
|
1633
|
+
const cols = data.columns;
|
|
1634
|
+
if (
|
|
1635
|
+
range.rowStart < 0 ||
|
|
1636
|
+
range.colStart < 0 ||
|
|
1637
|
+
range.rowEnd >= rows.length ||
|
|
1638
|
+
range.colEnd >= cols.length
|
|
1639
|
+
) {
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
this[selectionSymbol] = {
|
|
1643
|
+
anchor: {
|
|
1644
|
+
rowId: rows[range.rowStart].id,
|
|
1645
|
+
colId: cols[range.colStart].id,
|
|
1646
|
+
},
|
|
1647
|
+
focus: {
|
|
1648
|
+
rowId: rows[range.rowEnd].id,
|
|
1649
|
+
colId: cols[range.colEnd].id,
|
|
1650
|
+
},
|
|
1651
|
+
};
|
|
1652
|
+
updateSelectionDisplay.call(this);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
async function writeClipboardText(text) {
|
|
1656
|
+
try {
|
|
1657
|
+
if (navigator.clipboard?.writeText) {
|
|
1658
|
+
await navigator.clipboard.writeText(text);
|
|
1659
|
+
return true;
|
|
1660
|
+
}
|
|
1661
|
+
} catch (e) {
|
|
1662
|
+
return false;
|
|
1663
|
+
}
|
|
1664
|
+
return false;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
async function readClipboardText() {
|
|
1668
|
+
try {
|
|
1669
|
+
if (navigator.clipboard?.readText) {
|
|
1670
|
+
return await navigator.clipboard.readText();
|
|
1671
|
+
}
|
|
1672
|
+
} catch (e) {
|
|
1673
|
+
return null;
|
|
1674
|
+
}
|
|
1675
|
+
return null;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
function showStatus(message) {
|
|
1679
|
+
const status = this[statusSymbol];
|
|
1680
|
+
if (!status) return;
|
|
1681
|
+
status.textContent = message;
|
|
1682
|
+
status.dataset.show = "true";
|
|
1683
|
+
if (this[statusTimeoutSymbol]) {
|
|
1684
|
+
clearTimeout(this[statusTimeoutSymbol]);
|
|
1685
|
+
}
|
|
1686
|
+
this[statusTimeoutSymbol] = setTimeout(() => {
|
|
1687
|
+
status.dataset.show = "false";
|
|
1688
|
+
}, 1200);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
function syncContextMenuLabels() {
|
|
1692
|
+
const menu = this[contextMenuSymbol];
|
|
1693
|
+
if (!menu) return;
|
|
1694
|
+
const labels = this.getOption("labels", {});
|
|
1695
|
+
const copy = menu.querySelector(`[${ATTRIBUTE_ROLE}=menu-copy]`);
|
|
1696
|
+
const paste = menu.querySelector(`[${ATTRIBUTE_ROLE}=menu-paste]`);
|
|
1697
|
+
const cut = menu.querySelector(`[${ATTRIBUTE_ROLE}=menu-cut]`);
|
|
1698
|
+
if (copy && isString(labels.copy)) copy.textContent = labels.copy;
|
|
1699
|
+
if (paste && isString(labels.paste)) paste.textContent = labels.paste;
|
|
1700
|
+
if (cut && isString(labels.cut)) cut.textContent = labels.cut;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function wireContextMenuActions() {
|
|
1704
|
+
const menu = this[contextMenuSymbol];
|
|
1705
|
+
if (!menu) return;
|
|
1706
|
+
const copy = menu.querySelector(`[${ATTRIBUTE_ROLE}=menu-copy]`);
|
|
1707
|
+
const paste = menu.querySelector(`[${ATTRIBUTE_ROLE}=menu-paste]`);
|
|
1708
|
+
const cut = menu.querySelector(`[${ATTRIBUTE_ROLE}=menu-cut]`);
|
|
1709
|
+
const labels = this.getOption("labels", {});
|
|
1710
|
+
if (copy) copy.textContent = labels.copy || "Copy";
|
|
1711
|
+
if (paste) paste.textContent = labels.paste || "Paste";
|
|
1712
|
+
if (cut) cut.textContent = labels.cut || "Cut";
|
|
1713
|
+
|
|
1714
|
+
if (copy) {
|
|
1715
|
+
copy.addEventListener("click", async () => {
|
|
1716
|
+
await copySelectionToClipboard.call(this);
|
|
1717
|
+
hideContextMenu.call(this);
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
if (paste) {
|
|
1721
|
+
paste.addEventListener("click", async () => {
|
|
1722
|
+
await pasteFromClipboard.call(this);
|
|
1723
|
+
hideContextMenu.call(this);
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
if (cut) {
|
|
1727
|
+
cut.addEventListener("click", async () => {
|
|
1728
|
+
await cutSelectionToClipboard.call(this);
|
|
1729
|
+
hideContextMenu.call(this);
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
function showContextMenu(clientX, clientY) {
|
|
1735
|
+
const menu = this[contextMenuSymbol];
|
|
1736
|
+
const wrapper = this[gridWrapperSymbol];
|
|
1737
|
+
if (!menu || !wrapper) return;
|
|
1738
|
+
menu.hidden = false;
|
|
1739
|
+
menu.style.display = "block";
|
|
1740
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
1741
|
+
const offsetX = clientX - wrapperRect.left + wrapper.scrollLeft;
|
|
1742
|
+
const offsetY = clientY - wrapperRect.top + wrapper.scrollTop;
|
|
1743
|
+
const menuWidth = menu.offsetWidth || 160;
|
|
1744
|
+
const menuHeight = menu.offsetHeight || 96;
|
|
1745
|
+
const maxLeft = wrapper.scrollLeft + wrapper.clientWidth - menuWidth;
|
|
1746
|
+
const maxTop = wrapper.scrollTop + wrapper.clientHeight - menuHeight;
|
|
1747
|
+
menu.style.left = `${Math.max(wrapper.scrollLeft, Math.min(offsetX, maxLeft))}px`;
|
|
1748
|
+
menu.style.top = `${Math.max(wrapper.scrollTop, Math.min(offsetY, maxTop))}px`;
|
|
1749
|
+
this[menuStateSymbol] = { open: true };
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
function hideContextMenu() {
|
|
1753
|
+
const menu = this[contextMenuSymbol];
|
|
1754
|
+
if (!menu || menu.hidden) return;
|
|
1755
|
+
menu.hidden = true;
|
|
1756
|
+
menu.style.display = "none";
|
|
1757
|
+
this[menuStateSymbol] = { open: false };
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
function startFillDrag(event) {
|
|
1761
|
+
if (event.button !== 0) return;
|
|
1762
|
+
const data = normalizeValue.call(this);
|
|
1763
|
+
const range = getSelectionRange.call(this, this[selectionSymbol], data);
|
|
1764
|
+
if (!range) return;
|
|
1765
|
+
event.preventDefault();
|
|
1766
|
+
event.stopPropagation();
|
|
1767
|
+
if (event.target?.setPointerCapture) {
|
|
1768
|
+
event.target.setPointerCapture(event.pointerId);
|
|
1769
|
+
}
|
|
1770
|
+
const state = {
|
|
1771
|
+
origin: {
|
|
1772
|
+
rowStart: range.rowStart,
|
|
1773
|
+
rowEnd: range.rowEnd,
|
|
1774
|
+
colStart: range.colStart,
|
|
1775
|
+
colEnd: range.colEnd,
|
|
1776
|
+
},
|
|
1777
|
+
current: null,
|
|
1778
|
+
startX: event.clientX,
|
|
1779
|
+
startY: event.clientY,
|
|
1780
|
+
moved: false,
|
|
1781
|
+
};
|
|
1782
|
+
this[dragFillSymbol] = state;
|
|
1783
|
+
this.setAttribute("data-filling", "");
|
|
1784
|
+
|
|
1785
|
+
const move = (moveEvent) => {
|
|
1786
|
+
handleFillMove.call(this, moveEvent);
|
|
1787
|
+
};
|
|
1788
|
+
const end = (endEvent) => {
|
|
1789
|
+
handleFillEnd.call(this, endEvent);
|
|
1790
|
+
};
|
|
1791
|
+
window.addEventListener("pointermove", move);
|
|
1792
|
+
window.addEventListener("pointerup", end, { once: true });
|
|
1793
|
+
window.addEventListener("pointercancel", end, { once: true });
|
|
1794
|
+
state.cleanup = () => {
|
|
1795
|
+
window.removeEventListener("pointermove", move);
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
function handleFillMove(event) {
|
|
1800
|
+
const state = this[dragFillSymbol];
|
|
1801
|
+
if (!state) return;
|
|
1802
|
+
if (!state.moved) {
|
|
1803
|
+
const dx = event.clientX - state.startX;
|
|
1804
|
+
const dy = event.clientY - state.startY;
|
|
1805
|
+
if (Math.abs(dx) < 3 && Math.abs(dy) < 3) return;
|
|
1806
|
+
state.moved = true;
|
|
1807
|
+
}
|
|
1808
|
+
const data = normalizeValue.call(this);
|
|
1809
|
+
const cell = getCellFromPoint.call(this, event.clientX, event.clientY);
|
|
1810
|
+
if (!cell) return;
|
|
1811
|
+
const rowId = cell.dataset.rowId;
|
|
1812
|
+
const colId = cell.dataset.colId;
|
|
1813
|
+
if (!rowId || !colId) return;
|
|
1814
|
+
const { rowIndexById, colIndexById } = getIndexMaps(data);
|
|
1815
|
+
const rowIndex = rowIndexById.get(rowId);
|
|
1816
|
+
const colIndex = colIndexById.get(colId);
|
|
1817
|
+
if (rowIndex === undefined || colIndex === undefined) return;
|
|
1818
|
+
|
|
1819
|
+
const nextRange = {
|
|
1820
|
+
rowStart: Math.min(state.origin.rowStart, rowIndex),
|
|
1821
|
+
rowEnd: Math.max(state.origin.rowEnd, rowIndex),
|
|
1822
|
+
colStart: Math.min(state.origin.colStart, colIndex),
|
|
1823
|
+
colEnd: Math.max(state.origin.colEnd, colIndex),
|
|
1824
|
+
};
|
|
1825
|
+
state.current = nextRange;
|
|
1826
|
+
setSelectionByRange.call(this, nextRange, data);
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
function handleFillEnd() {
|
|
1830
|
+
const state = this[dragFillSymbol];
|
|
1831
|
+
if (!state) return;
|
|
1832
|
+
if (state.cleanup) state.cleanup();
|
|
1833
|
+
this.removeAttribute("data-filling");
|
|
1834
|
+
const data = normalizeValue.call(this);
|
|
1835
|
+
const target = state.current;
|
|
1836
|
+
const origin = state.origin;
|
|
1837
|
+
this[dragFillSymbol] = null;
|
|
1838
|
+
if (!state.moved || !target) return;
|
|
1839
|
+
const next = applyFillRange(data, origin, target);
|
|
1840
|
+
this.value = next;
|
|
1841
|
+
setSelectionByRange.call(this, target, next);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
function getCellFromPoint(clientX, clientY) {
|
|
1845
|
+
if (!this.shadowRoot) return null;
|
|
1846
|
+
const elements = document.elementsFromPoint(clientX, clientY);
|
|
1847
|
+
for (const el of elements) {
|
|
1848
|
+
if (!this.shadowRoot.contains(el)) continue;
|
|
1849
|
+
const cell = el.closest(`[${ATTRIBUTE_ROLE}=cell]`);
|
|
1850
|
+
if (cell) return cell;
|
|
1851
|
+
}
|
|
1852
|
+
const virtualize = this.getOption("features.virtualize", false) === true;
|
|
1853
|
+
if (!virtualize) return null;
|
|
1854
|
+
const wrapper = this[gridWrapperSymbol];
|
|
1855
|
+
if (!wrapper) return null;
|
|
1856
|
+
const rect = wrapper.getBoundingClientRect();
|
|
1857
|
+
const data = normalizeValue.call(this);
|
|
1858
|
+
const sizes = getVirtualSizes.call(this, data);
|
|
1859
|
+
const x = clientX - rect.left + wrapper.scrollLeft - sizes.rowHeaderWidth;
|
|
1860
|
+
const y = clientY - rect.top + wrapper.scrollTop - sizes.headerHeight;
|
|
1861
|
+
if (x < 0 || y < 0) return null;
|
|
1862
|
+
const colIndex = findStartIndex(sizes.columnOffsets, x);
|
|
1863
|
+
const rowIndex = findStartIndex(sizes.rowOffsets, y);
|
|
1864
|
+
if (
|
|
1865
|
+
colIndex < 0 ||
|
|
1866
|
+
rowIndex < 0 ||
|
|
1867
|
+
colIndex >= data.columns.length ||
|
|
1868
|
+
rowIndex >= data.rows.length
|
|
1869
|
+
) {
|
|
1870
|
+
return null;
|
|
1871
|
+
}
|
|
1872
|
+
return {
|
|
1873
|
+
dataset: {
|
|
1874
|
+
rowId: data.rows[rowIndex].id,
|
|
1875
|
+
colId: data.columns[colIndex].id,
|
|
1876
|
+
},
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
function applyFillRange(data, origin, target) {
|
|
1881
|
+
const next = {
|
|
1882
|
+
...data,
|
|
1883
|
+
cells: data.cells ? { ...data.cells } : {},
|
|
1884
|
+
formulas: data.formulas ? { ...data.formulas } : {},
|
|
1885
|
+
};
|
|
1886
|
+
const originRows = origin.rowEnd - origin.rowStart + 1;
|
|
1887
|
+
const originCols = origin.colEnd - origin.colStart + 1;
|
|
1888
|
+
for (let r = target.rowStart; r <= target.rowEnd; r += 1) {
|
|
1889
|
+
const rowId = data.rows[r]?.id;
|
|
1890
|
+
if (!rowId) continue;
|
|
1891
|
+
for (let c = target.colStart; c <= target.colEnd; c += 1) {
|
|
1892
|
+
if (
|
|
1893
|
+
r >= origin.rowStart &&
|
|
1894
|
+
r <= origin.rowEnd &&
|
|
1895
|
+
c >= origin.colStart &&
|
|
1896
|
+
c <= origin.colEnd
|
|
1897
|
+
) {
|
|
1898
|
+
continue;
|
|
1899
|
+
}
|
|
1900
|
+
const colId = data.columns[c]?.id;
|
|
1901
|
+
if (!colId) continue;
|
|
1902
|
+
const sourceRow = origin.rowStart + ((r - origin.rowStart) % originRows);
|
|
1903
|
+
const sourceCol = origin.colStart + ((c - origin.colStart) % originCols);
|
|
1904
|
+
const sourceRowId = data.rows[sourceRow]?.id;
|
|
1905
|
+
const sourceColId = data.columns[sourceCol]?.id;
|
|
1906
|
+
if (!sourceRowId || !sourceColId) continue;
|
|
1907
|
+
let value = getCellRawValue(data, sourceRowId, sourceColId);
|
|
1908
|
+
if (isFormulaString(value)) {
|
|
1909
|
+
const deltaRow = r - sourceRow;
|
|
1910
|
+
const deltaCol = c - sourceCol;
|
|
1911
|
+
value = adjustFormulaReferences(value, deltaRow, deltaCol);
|
|
1912
|
+
}
|
|
1913
|
+
setCellData(next, rowId, colId, value);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
return next;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1032
1919
|
function scheduleVirtualResizeRender() {
|
|
1033
1920
|
if (this[resizeFrameSymbol]) return;
|
|
1034
1921
|
this[resizeFrameSymbol] = requestAnimationFrame(() => {
|
|
@@ -1211,120 +2098,240 @@ function getTranslations() {
|
|
|
1211
2098
|
addRow: "Zeile hinzufügen",
|
|
1212
2099
|
addColumn: "Spalte hinzufügen",
|
|
1213
2100
|
corner: "",
|
|
2101
|
+
copy: "Kopieren",
|
|
2102
|
+
paste: "Einfügen",
|
|
2103
|
+
cut: "Ausschneiden",
|
|
2104
|
+
copied: "Kopiert",
|
|
2105
|
+
pasted: "Eingefügt",
|
|
2106
|
+
cutDone: "Ausgeschnitten",
|
|
1214
2107
|
};
|
|
1215
2108
|
case "fr":
|
|
1216
2109
|
return {
|
|
1217
2110
|
addRow: "Ajouter une ligne",
|
|
1218
2111
|
addColumn: "Ajouter une colonne",
|
|
1219
2112
|
corner: "",
|
|
2113
|
+
copy: "Copier",
|
|
2114
|
+
paste: "Coller",
|
|
2115
|
+
cut: "Couper",
|
|
2116
|
+
copied: "Copié",
|
|
2117
|
+
pasted: "Collé",
|
|
2118
|
+
cutDone: "Coupé",
|
|
1220
2119
|
};
|
|
1221
2120
|
case "es":
|
|
1222
2121
|
return {
|
|
1223
2122
|
addRow: "Agregar fila",
|
|
1224
2123
|
addColumn: "Agregar columna",
|
|
1225
2124
|
corner: "",
|
|
2125
|
+
copy: "Copiar",
|
|
2126
|
+
paste: "Pegar",
|
|
2127
|
+
cut: "Cortar",
|
|
2128
|
+
copied: "Copiado",
|
|
2129
|
+
pasted: "Pegado",
|
|
2130
|
+
cutDone: "Cortado",
|
|
1226
2131
|
};
|
|
1227
2132
|
case "zh":
|
|
1228
2133
|
return {
|
|
1229
2134
|
addRow: "添加行",
|
|
1230
2135
|
addColumn: "添加列",
|
|
1231
2136
|
corner: "",
|
|
2137
|
+
copy: "复制",
|
|
2138
|
+
paste: "粘贴",
|
|
2139
|
+
cut: "剪切",
|
|
2140
|
+
copied: "已复制",
|
|
2141
|
+
pasted: "已粘贴",
|
|
2142
|
+
cutDone: "已剪切",
|
|
1232
2143
|
};
|
|
1233
2144
|
case "hi":
|
|
1234
2145
|
return {
|
|
1235
2146
|
addRow: "पंक्ति जोड़ें",
|
|
1236
2147
|
addColumn: "स्तंभ जोड़ें",
|
|
1237
2148
|
corner: "",
|
|
2149
|
+
copy: "कॉपी करें",
|
|
2150
|
+
paste: "पेस्ट करें",
|
|
2151
|
+
cut: "कट करें",
|
|
2152
|
+
copied: "कॉपी हो गया",
|
|
2153
|
+
pasted: "पेस्ट हो गया",
|
|
2154
|
+
cutDone: "कट हो गया",
|
|
1238
2155
|
};
|
|
1239
2156
|
case "bn":
|
|
1240
2157
|
return {
|
|
1241
2158
|
addRow: "সারি যোগ করুন",
|
|
1242
2159
|
addColumn: "কলাম যোগ করুন",
|
|
1243
2160
|
corner: "",
|
|
2161
|
+
copy: "কপি",
|
|
2162
|
+
paste: "পেস্ট",
|
|
2163
|
+
cut: "কাট",
|
|
2164
|
+
copied: "কপি করা হয়েছে",
|
|
2165
|
+
pasted: "পেস্ট করা হয়েছে",
|
|
2166
|
+
cutDone: "কাট করা হয়েছে",
|
|
1244
2167
|
};
|
|
1245
2168
|
case "pt":
|
|
1246
2169
|
return {
|
|
1247
2170
|
addRow: "Adicionar linha",
|
|
1248
2171
|
addColumn: "Adicionar coluna",
|
|
1249
2172
|
corner: "",
|
|
2173
|
+
copy: "Copiar",
|
|
2174
|
+
paste: "Colar",
|
|
2175
|
+
cut: "Recortar",
|
|
2176
|
+
copied: "Copiado",
|
|
2177
|
+
pasted: "Colado",
|
|
2178
|
+
cutDone: "Recortado",
|
|
1250
2179
|
};
|
|
1251
2180
|
case "ru":
|
|
1252
2181
|
return {
|
|
1253
2182
|
addRow: "Добавить строку",
|
|
1254
2183
|
addColumn: "Добавить столбец",
|
|
1255
2184
|
corner: "",
|
|
2185
|
+
copy: "Копировать",
|
|
2186
|
+
paste: "Вставить",
|
|
2187
|
+
cut: "Вырезать",
|
|
2188
|
+
copied: "Скопировано",
|
|
2189
|
+
pasted: "Вставлено",
|
|
2190
|
+
cutDone: "Вырезано",
|
|
1256
2191
|
};
|
|
1257
2192
|
case "ja":
|
|
1258
2193
|
return {
|
|
1259
2194
|
addRow: "行を追加",
|
|
1260
2195
|
addColumn: "列を追加",
|
|
1261
2196
|
corner: "",
|
|
2197
|
+
copy: "コピー",
|
|
2198
|
+
paste: "貼り付け",
|
|
2199
|
+
cut: "切り取り",
|
|
2200
|
+
copied: "コピーしました",
|
|
2201
|
+
pasted: "貼り付けました",
|
|
2202
|
+
cutDone: "切り取りました",
|
|
1262
2203
|
};
|
|
1263
2204
|
case "pa":
|
|
1264
2205
|
return {
|
|
1265
2206
|
addRow: "ਕਤਾਰ ਸ਼ਾਮਲ ਕਰੋ",
|
|
1266
2207
|
addColumn: "ਕਾਲਮ ਸ਼ਾਮਲ ਕਰੋ",
|
|
1267
2208
|
corner: "",
|
|
2209
|
+
copy: "ਕਾਪੀ",
|
|
2210
|
+
paste: "ਪੇਸਟ",
|
|
2211
|
+
cut: "ਕੱਟੋ",
|
|
2212
|
+
copied: "ਕਾਪੀ ਹੋਇਆ",
|
|
2213
|
+
pasted: "ਪੇਸਟ ਹੋਇਆ",
|
|
2214
|
+
cutDone: "ਕੱਟਿਆ ਗਿਆ",
|
|
1268
2215
|
};
|
|
1269
2216
|
case "mr":
|
|
1270
2217
|
return {
|
|
1271
2218
|
addRow: "ओळ जोडा",
|
|
1272
2219
|
addColumn: "स्तंभ जोडा",
|
|
1273
2220
|
corner: "",
|
|
2221
|
+
copy: "कॉपी",
|
|
2222
|
+
paste: "पेस्ट",
|
|
2223
|
+
cut: "कट",
|
|
2224
|
+
copied: "कॉपी झाले",
|
|
2225
|
+
pasted: "पेस्ट झाले",
|
|
2226
|
+
cutDone: "कट झाले",
|
|
1274
2227
|
};
|
|
1275
2228
|
case "it":
|
|
1276
2229
|
return {
|
|
1277
2230
|
addRow: "Aggiungi riga",
|
|
1278
2231
|
addColumn: "Aggiungi colonna",
|
|
1279
2232
|
corner: "",
|
|
2233
|
+
copy: "Copia",
|
|
2234
|
+
paste: "Incolla",
|
|
2235
|
+
cut: "Taglia",
|
|
2236
|
+
copied: "Copiato",
|
|
2237
|
+
pasted: "Incollato",
|
|
2238
|
+
cutDone: "Tagliato",
|
|
1280
2239
|
};
|
|
1281
2240
|
case "nl":
|
|
1282
2241
|
return {
|
|
1283
2242
|
addRow: "Rij toevoegen",
|
|
1284
2243
|
addColumn: "Kolom toevoegen",
|
|
1285
2244
|
corner: "",
|
|
2245
|
+
copy: "Kopiëren",
|
|
2246
|
+
paste: "Plakken",
|
|
2247
|
+
cut: "Knippen",
|
|
2248
|
+
copied: "Gekopieerd",
|
|
2249
|
+
pasted: "Geplakt",
|
|
2250
|
+
cutDone: "Geknipt",
|
|
1286
2251
|
};
|
|
1287
2252
|
case "sv":
|
|
1288
2253
|
return {
|
|
1289
2254
|
addRow: "Lägg till rad",
|
|
1290
2255
|
addColumn: "Lägg till kolumn",
|
|
1291
2256
|
corner: "",
|
|
2257
|
+
copy: "Kopiera",
|
|
2258
|
+
paste: "Klistra in",
|
|
2259
|
+
cut: "Klipp ut",
|
|
2260
|
+
copied: "Kopierad",
|
|
2261
|
+
pasted: "Inklistrad",
|
|
2262
|
+
cutDone: "Urklippt",
|
|
1292
2263
|
};
|
|
1293
2264
|
case "pl":
|
|
1294
2265
|
return {
|
|
1295
2266
|
addRow: "Dodaj wiersz",
|
|
1296
2267
|
addColumn: "Dodaj kolumnę",
|
|
1297
2268
|
corner: "",
|
|
2269
|
+
copy: "Kopiuj",
|
|
2270
|
+
paste: "Wklej",
|
|
2271
|
+
cut: "Wytnij",
|
|
2272
|
+
copied: "Skopiowano",
|
|
2273
|
+
pasted: "Wklejono",
|
|
2274
|
+
cutDone: "Wycięto",
|
|
1298
2275
|
};
|
|
1299
2276
|
case "da":
|
|
1300
2277
|
return {
|
|
1301
2278
|
addRow: "Tilføj række",
|
|
1302
2279
|
addColumn: "Tilføj kolonne",
|
|
1303
2280
|
corner: "",
|
|
2281
|
+
copy: "Kopier",
|
|
2282
|
+
paste: "Indsæt",
|
|
2283
|
+
cut: "Klip",
|
|
2284
|
+
copied: "Kopieret",
|
|
2285
|
+
pasted: "Indsat",
|
|
2286
|
+
cutDone: "Klippet",
|
|
1304
2287
|
};
|
|
1305
2288
|
case "fi":
|
|
1306
2289
|
return {
|
|
1307
2290
|
addRow: "Lisää rivi",
|
|
1308
2291
|
addColumn: "Lisää sarake",
|
|
1309
2292
|
corner: "",
|
|
2293
|
+
copy: "Kopioi",
|
|
2294
|
+
paste: "Liitä",
|
|
2295
|
+
cut: "Leikkaa",
|
|
2296
|
+
copied: "Kopioitu",
|
|
2297
|
+
pasted: "Liitetty",
|
|
2298
|
+
cutDone: "Leikattu",
|
|
1310
2299
|
};
|
|
1311
2300
|
case "no":
|
|
1312
2301
|
return {
|
|
1313
2302
|
addRow: "Legg til rad",
|
|
1314
2303
|
addColumn: "Legg til kolonne",
|
|
1315
2304
|
corner: "",
|
|
2305
|
+
copy: "Kopier",
|
|
2306
|
+
paste: "Lim inn",
|
|
2307
|
+
cut: "Klipp ut",
|
|
2308
|
+
copied: "Kopiert",
|
|
2309
|
+
pasted: "Limt inn",
|
|
2310
|
+
cutDone: "Klippet",
|
|
1316
2311
|
};
|
|
1317
2312
|
case "cs":
|
|
1318
2313
|
return {
|
|
1319
2314
|
addRow: "Přidat řádek",
|
|
1320
2315
|
addColumn: "Přidat sloupec",
|
|
1321
2316
|
corner: "",
|
|
2317
|
+
copy: "Kopírovat",
|
|
2318
|
+
paste: "Vložit",
|
|
2319
|
+
cut: "Vyjmout",
|
|
2320
|
+
copied: "Zkopírováno",
|
|
2321
|
+
pasted: "Vloženo",
|
|
2322
|
+
cutDone: "Vyjmuto",
|
|
1322
2323
|
};
|
|
1323
2324
|
default:
|
|
1324
2325
|
return {
|
|
1325
2326
|
addRow: "Add row",
|
|
1326
2327
|
addColumn: "Add column",
|
|
1327
2328
|
corner: "",
|
|
2329
|
+
copy: "Copy",
|
|
2330
|
+
paste: "Paste",
|
|
2331
|
+
cut: "Cut",
|
|
2332
|
+
copied: "Copied",
|
|
2333
|
+
pasted: "Pasted",
|
|
2334
|
+
cutDone: "Cut",
|
|
1328
2335
|
};
|
|
1329
2336
|
}
|
|
1330
2337
|
}
|
|
@@ -1338,6 +2345,14 @@ function getTemplate() {
|
|
|
1338
2345
|
</div>
|
|
1339
2346
|
<div data-monster-role="grid-wrapper" part="grid-wrapper">
|
|
1340
2347
|
<div data-monster-role="grid" part="grid"></div>
|
|
2348
|
+
<div data-monster-role="selection" part="selection" aria-hidden="true"></div>
|
|
2349
|
+
<span data-monster-role="fill-handle" part="fill-handle" aria-hidden="true"></span>
|
|
2350
|
+
<div data-monster-role="status" part="status" role="status" aria-live="polite"></div>
|
|
2351
|
+
<div data-monster-role="context-menu" part="context-menu" role="menu" hidden>
|
|
2352
|
+
<button type="button" data-monster-role="menu-copy" part="menu-item" role="menuitem"></button>
|
|
2353
|
+
<button type="button" data-monster-role="menu-paste" part="menu-item" role="menuitem"></button>
|
|
2354
|
+
<button type="button" data-monster-role="menu-cut" part="menu-item" role="menuitem"></button>
|
|
2355
|
+
</div>
|
|
1341
2356
|
</div>
|
|
1342
2357
|
</div>
|
|
1343
2358
|
`;
|