@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.
@@ -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
  `;